mirror of
https://gitlab.com/pholy/OSCAR-code.git
synced 2025-04-07 11:40:42 +00:00
Add wrapper around DreamStation 2 files to read their header and provide an interface to their data.
This commit is contained in:
parent
818eafcc7c
commit
f1e31282bb
@ -1,6 +1,6 @@
|
|||||||
/* SleepLib PRS1 Loader Implementation
|
/* SleepLib PRS1 Loader Implementation
|
||||||
*
|
*
|
||||||
* Copyright (c) 2019-2020 The OSCAR Team
|
* Copyright (c) 2019-2021 The OSCAR Team
|
||||||
* Copyright (c) 2011-2018 Mark Watkins <mark@jedimark.net>
|
* Copyright (c) 2011-2018 Mark Watkins <mark@jedimark.net>
|
||||||
*
|
*
|
||||||
* This file is subject to the terms and conditions of the GNU General Public
|
* This file is subject to the terms and conditions of the GNU General Public
|
||||||
@ -21,6 +21,7 @@
|
|||||||
#include "prs1_loader.h"
|
#include "prs1_loader.h"
|
||||||
#include "SleepLib/session.h"
|
#include "SleepLib/session.h"
|
||||||
#include "SleepLib/calcs.h"
|
#include "SleepLib/calcs.h"
|
||||||
|
#include "rawdata.h"
|
||||||
|
|
||||||
|
|
||||||
// Disable this to cut excess debug messages
|
// Disable this to cut excess debug messages
|
||||||
@ -408,6 +409,153 @@ const char* PRS1ModelInfo::Name(const QString & model) const
|
|||||||
return name;
|
return name;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//********************************************************************************************
|
||||||
|
|
||||||
|
// Decoder for DreamStation 2 files, which encrypt the actual data after a header with the key.
|
||||||
|
// The public read/seek/pos/etc. functions are all in terms of the decoded stream.
|
||||||
|
class PRDS2File : public RawDataFile
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PRDS2File(class QFile & file);
|
||||||
|
virtual ~PRDS2File() {};
|
||||||
|
private:
|
||||||
|
void parseDS2Header();
|
||||||
|
int read16();
|
||||||
|
QByteArray readBytes();
|
||||||
|
protected:
|
||||||
|
virtual qint64 readData(char *data, qint64 maxSize);
|
||||||
|
virtual bool seek(qint64 pos);
|
||||||
|
virtual qint64 pos() const;
|
||||||
|
virtual qint64 size() const;
|
||||||
|
|
||||||
|
QByteArray m_guid;
|
||||||
|
static const int m_header_size = 0xCA;
|
||||||
|
};
|
||||||
|
|
||||||
|
PRDS2File::PRDS2File(class QFile & file)
|
||||||
|
: RawDataFile(file)
|
||||||
|
{
|
||||||
|
parseDS2Header();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PRDS2File::seek(qint64 pos)
|
||||||
|
{
|
||||||
|
QIODevice::seek(pos);
|
||||||
|
return RawDataFile::seek(pos + m_header_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 PRDS2File::pos() const
|
||||||
|
{
|
||||||
|
return RawDataFile::pos() - m_header_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 PRDS2File::size() const
|
||||||
|
{
|
||||||
|
return RawDataFile::size() - m_header_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 PRDS2File::readData(char *data, qint64 maxSize)
|
||||||
|
{
|
||||||
|
//qint64 pos = this->pos();
|
||||||
|
int result = RawDataFile::readData(data, maxSize);
|
||||||
|
|
||||||
|
// TODO: calculate key stream for byte pos+i
|
||||||
|
/*
|
||||||
|
for (int i = 0; i < result; i++) {
|
||||||
|
data[i] ^= key[i];
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PRDS2File::parseDS2Header()
|
||||||
|
{
|
||||||
|
int a = read16();
|
||||||
|
int b = read16();
|
||||||
|
int c = read16();
|
||||||
|
if (a != 0x0D || b != 1 || c != 1) {
|
||||||
|
qWarning() << "DS2 unexpected first bytes =" << a << b << c;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_guid = readBytes();
|
||||||
|
if (m_guid.size() != 36) {
|
||||||
|
qWarning() << "DS2 guid unexpected length" << m_guid.size();
|
||||||
|
} else {
|
||||||
|
qDebug() << "DS2 guid {" << m_guid << "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray d = readBytes(); // 96 bits, probably IV
|
||||||
|
QByteArray e = readBytes(); // 128 bits, probably key
|
||||||
|
if (d.size() != 12 || e.size() != 16) {
|
||||||
|
qWarning() << "DS2 d,e sizes =" << d.size() << e.size();
|
||||||
|
} else {
|
||||||
|
qDebug() << "DS2 key? =" << d.toHex() << e.toHex();
|
||||||
|
}
|
||||||
|
|
||||||
|
int f = read16();
|
||||||
|
int g = read16();
|
||||||
|
if (f != 0 || g != 1) {
|
||||||
|
qWarning() << "DS2 unexpected middle bytes =" << f << g;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray h = readBytes(); // same per d,e pair, varies per machine
|
||||||
|
QByteArray i = readBytes(); // same per d,e pair, varies per machine
|
||||||
|
if (h.size() != 32 || i.size() != 16) {
|
||||||
|
qWarning() << "DS2 h,i sizes =" << h.size() << i.size();
|
||||||
|
} else {
|
||||||
|
qDebug() << "DS2 h,i =" << h.toHex() << i.toHex();
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray j = readBytes(); // same per d,e pair, does NOT vary per machine
|
||||||
|
QByteArray k = readBytes(); // same per d,e pair, does NOT vary per machine
|
||||||
|
if (j.size() != 32 || k.size() != 16) {
|
||||||
|
qWarning() << "DS2 j,k sizes =" << j.size() << k.size();
|
||||||
|
} else {
|
||||||
|
qDebug() << "DS2 j,k =" << j.toHex() << k.toHex();
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray l = readBytes(); // differs for EVERY file, and machine, even with same values above
|
||||||
|
if (l.size() != 16) {
|
||||||
|
qWarning() << "DS2 l size =" << l.size();
|
||||||
|
} else {
|
||||||
|
qDebug() << "DS2 l =" << l.toHex();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RawDataFile::pos() != m_header_size) {
|
||||||
|
qWarning() << "DS2 header size !=" << m_header_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int PRDS2File::read16()
|
||||||
|
{
|
||||||
|
unsigned char data[2];
|
||||||
|
int result;
|
||||||
|
|
||||||
|
result = RawDataFile::read((char*) data, sizeof(data));
|
||||||
|
if (result == sizeof(data)) {
|
||||||
|
result = data[0] | (data[1] << 8);
|
||||||
|
} else {
|
||||||
|
result = 0;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray PRDS2File::readBytes()
|
||||||
|
{
|
||||||
|
int length = read16();
|
||||||
|
QByteArray result = RawDataFile::read(length);
|
||||||
|
if (result.size() < length) {
|
||||||
|
result.clear();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//********************************************************************************************
|
||||||
|
|
||||||
|
|
||||||
QMap<const char*,const char*> s_PRS1Series = {
|
QMap<const char*,const char*> s_PRS1Series = {
|
||||||
{ "System One 60 Series", ":/icons/prs1_60s.png" }, // needs to come before following substring
|
{ "System One 60 Series", ":/icons/prs1_60s.png" }, // needs to come before following substring
|
||||||
{ "System One", ":/icons/prs1.png" },
|
{ "System One", ":/icons/prs1.png" },
|
||||||
@ -579,7 +727,18 @@ bool PRS1Loader::PeekProperties(const QString & filename, QHash<QString,QString>
|
|||||||
if (!f.open(QFile::ReadOnly)) {
|
if (!f.open(QFile::ReadOnly)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
QTextStream in(&f);
|
|
||||||
|
RawDataFile* src;
|
||||||
|
if (QFileInfo(f).suffix().toUpper() == "BIN") {
|
||||||
|
// If it's a DS2 file, insert the DS2 wrapper to decode the chunk stream.
|
||||||
|
src = new PRDS2File(f);
|
||||||
|
} else {
|
||||||
|
// Otherwise just use the file as input.
|
||||||
|
src = new RawDataFile(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextStream in(src);
|
||||||
|
|
||||||
do {
|
do {
|
||||||
QString line = in.readLine();
|
QString line = in.readLine();
|
||||||
QStringList pair = line.split("=");
|
QStringList pair = line.split("=");
|
||||||
@ -597,6 +756,7 @@ bool PRS1Loader::PeekProperties(const QString & filename, QHash<QString,QString>
|
|||||||
props[pair[0]] = pair[1];
|
props[pair[0]] = pair[1];
|
||||||
} while (!in.atEnd());
|
} while (!in.atEnd());
|
||||||
|
|
||||||
|
delete src;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -654,11 +814,15 @@ MachineInfo PRS1Loader::PeekInfo(const QString & path)
|
|||||||
if (!PeekProperties(info, newpath+"/PROP.TXT")) {
|
if (!PeekProperties(info, newpath+"/PROP.TXT")) {
|
||||||
// Detect (unsupported) DreamStation 2
|
// Detect (unsupported) DreamStation 2
|
||||||
QString filepath(newpath + "/PROP.BIN");
|
QString filepath(newpath + "/PROP.BIN");
|
||||||
|
#ifdef TEST_DS2
|
||||||
|
if (!PeekProperties(info, filepath)) {
|
||||||
|
#else
|
||||||
QFile f(filepath);
|
QFile f(filepath);
|
||||||
if (f.exists()) {
|
if (f.exists()) {
|
||||||
info.series = "DreamStation 2";
|
info.series = "DreamStation 2";
|
||||||
qWarning() << "DreamStation 2 not supported:" << filepath;
|
qWarning() << "DreamStation 2 not supported:" << filepath;
|
||||||
} else {
|
} else {
|
||||||
|
#endif
|
||||||
qWarning() << "No properties file found in" << newpath;
|
qWarning() << "No properties file found in" << newpath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -807,6 +971,7 @@ int PRS1Loader::FindSessionDirsAndProperties(const QString & path, QStringList &
|
|||||||
|
|
||||||
Machine* PRS1Loader::CreateMachineFromProperties(QString propertyfile)
|
Machine* PRS1Loader::CreateMachineFromProperties(QString propertyfile)
|
||||||
{
|
{
|
||||||
|
#ifndef TEST_DS2
|
||||||
if (propertyfile.endsWith("PROP.BIN")) {
|
if (propertyfile.endsWith("PROP.BIN")) {
|
||||||
qWarning() << "DreamStation 2 not supported:" << propertyfile;
|
qWarning() << "DreamStation 2 not supported:" << propertyfile;
|
||||||
#ifndef UNITTEST_MODE
|
#ifndef UNITTEST_MODE
|
||||||
@ -818,6 +983,7 @@ Machine* PRS1Loader::CreateMachineFromProperties(QString propertyfile)
|
|||||||
#endif
|
#endif
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
QHash<QString,QString> props;
|
QHash<QString,QString> props;
|
||||||
PeekProperties(propertyfile, props);
|
PeekProperties(propertyfile, props);
|
||||||
@ -8975,12 +9141,21 @@ QList<PRS1DataChunk *> PRS1Loader::ParseFile(const QString & path)
|
|||||||
return CHUNKS;
|
return CHUNKS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RawDataFile* src;
|
||||||
|
if (QFileInfo(f).suffix().toUpper() == "BIN") {
|
||||||
|
// If it's a DS2 file, insert the DS2 wrapper to decode the chunk stream.
|
||||||
|
src = new PRDS2File(f);
|
||||||
|
} else {
|
||||||
|
// Otherwise just use the file as input.
|
||||||
|
src = new RawDataFile(f);
|
||||||
|
}
|
||||||
|
|
||||||
PRS1DataChunk *chunk = nullptr, *lastchunk = nullptr;
|
PRS1DataChunk *chunk = nullptr, *lastchunk = nullptr;
|
||||||
|
|
||||||
int cnt = 0;
|
int cnt = 0;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
chunk = PRS1DataChunk::ParseNext(f, this);
|
chunk = PRS1DataChunk::ParseNext(*src, this);
|
||||||
if (chunk == nullptr) {
|
if (chunk == nullptr) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -9004,15 +9179,16 @@ QList<PRS1DataChunk *> PRS1Loader::ParseFile(const QString & path)
|
|||||||
CHUNKS.append(chunk);
|
CHUNKS.append(chunk);
|
||||||
lastchunk = chunk;
|
lastchunk = chunk;
|
||||||
cnt++;
|
cnt++;
|
||||||
} while (!f.atEnd());
|
} while (!src->atEnd());
|
||||||
|
|
||||||
|
delete src;
|
||||||
return CHUNKS;
|
return CHUNKS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
PRS1DataChunk::PRS1DataChunk(QFile & f, PRS1Loader* in_loader) : loader(in_loader)
|
PRS1DataChunk::PRS1DataChunk(RawDataDevice & f, PRS1Loader* in_loader) : loader(in_loader)
|
||||||
{
|
{
|
||||||
m_path = QFileInfo(f).canonicalFilePath();
|
m_path = f.name();
|
||||||
}
|
}
|
||||||
|
|
||||||
PRS1DataChunk::~PRS1DataChunk()
|
PRS1DataChunk::~PRS1DataChunk()
|
||||||
@ -9024,7 +9200,7 @@ PRS1DataChunk::~PRS1DataChunk()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
PRS1DataChunk* PRS1DataChunk::ParseNext(QFile & f, PRS1Loader* loader)
|
PRS1DataChunk* PRS1DataChunk::ParseNext(RawDataDevice & f, PRS1Loader* loader)
|
||||||
{
|
{
|
||||||
PRS1DataChunk* out_chunk = nullptr;
|
PRS1DataChunk* out_chunk = nullptr;
|
||||||
PRS1DataChunk* chunk = new PRS1DataChunk(f, loader);
|
PRS1DataChunk* chunk = new PRS1DataChunk(f, loader);
|
||||||
@ -9071,7 +9247,7 @@ PRS1DataChunk* PRS1DataChunk::ParseNext(QFile & f, PRS1Loader* loader)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool PRS1DataChunk::ReadHeader(QFile & f)
|
bool PRS1DataChunk::ReadHeader(RawDataDevice & f)
|
||||||
{
|
{
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
do {
|
do {
|
||||||
@ -9165,14 +9341,14 @@ bool PRS1DataChunk::ReadHeader(QFile & f)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool PRS1DataChunk::ReadNormalHeaderV2(QFile & /*f*/)
|
bool PRS1DataChunk::ReadNormalHeaderV2(RawDataDevice & /*f*/)
|
||||||
{
|
{
|
||||||
this->m_headerblock = QByteArray();
|
this->m_headerblock = QByteArray();
|
||||||
return true; // always OK
|
return true; // always OK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool PRS1DataChunk::ReadNormalHeaderV3(QFile & f)
|
bool PRS1DataChunk::ReadNormalHeaderV3(RawDataDevice & f)
|
||||||
{
|
{
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
unsigned char * header;
|
unsigned char * header;
|
||||||
@ -9216,7 +9392,7 @@ bool PRS1DataChunk::ReadNormalHeaderV3(QFile & f)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool PRS1DataChunk::ReadWaveformHeader(QFile & f)
|
bool PRS1DataChunk::ReadWaveformHeader(RawDataDevice & f)
|
||||||
{
|
{
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
unsigned char * header;
|
unsigned char * header;
|
||||||
@ -9280,7 +9456,7 @@ bool PRS1DataChunk::ReadWaveformHeader(QFile & f)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool PRS1DataChunk::ReadData(QFile & f)
|
bool PRS1DataChunk::ReadData(RawDataDevice & f)
|
||||||
{
|
{
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
do {
|
do {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/* SleepLib PRS1 Loader Header
|
/* SleepLib PRS1 Loader Header
|
||||||
*
|
*
|
||||||
* Copyright (c) 2019-2020 The OSCAR Team
|
* Copyright (c) 2019-2021 The OSCAR Team
|
||||||
* Copyright (C) 2011-2018 Mark Watkins <mark@jedimark.net>
|
* Copyright (C) 2011-2018 Mark Watkins <mark@jedimark.net>
|
||||||
*
|
*
|
||||||
* This file is subject to the terms and conditions of the GNU General Public
|
* This file is subject to the terms and conditions of the GNU General Public
|
||||||
@ -78,7 +78,7 @@ public:
|
|||||||
m_index = -1;
|
m_index = -1;
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
PRS1DataChunk(class QFile & f, class PRS1Loader* loader);
|
PRS1DataChunk(class RawDataDevice & f, class PRS1Loader* loader);
|
||||||
~PRS1DataChunk();
|
~PRS1DataChunk();
|
||||||
inline int size() const { return m_data.size(); }
|
inline int size() const { return m_data.size(); }
|
||||||
|
|
||||||
@ -124,13 +124,13 @@ public:
|
|||||||
inline quint64 hash(void) const { return ((((quint64) this->calcCrc) << 32) | this->timestamp); }
|
inline quint64 hash(void) const { return ((((quint64) this->calcCrc) << 32) | this->timestamp); }
|
||||||
|
|
||||||
//! \brief Parse and return the next chunk from a PRS1 file
|
//! \brief Parse and return the next chunk from a PRS1 file
|
||||||
static PRS1DataChunk* ParseNext(class QFile & f, class PRS1Loader* loader);
|
static PRS1DataChunk* ParseNext(class RawDataDevice & f, class PRS1Loader* loader);
|
||||||
|
|
||||||
//! \brief Read and parse the next chunk header from a PRS1 file
|
//! \brief Read and parse the next chunk header from a PRS1 file
|
||||||
bool ReadHeader(class QFile & f);
|
bool ReadHeader(class RawDataDevice & f);
|
||||||
|
|
||||||
//! \brief Read the chunk's data from a PRS1 file and calculate its CRC, must be called after ReadHeader
|
//! \brief Read the chunk's data from a PRS1 file and calculate its CRC, must be called after ReadHeader
|
||||||
bool ReadData(class QFile & f);
|
bool ReadData(class RawDataDevice & f);
|
||||||
|
|
||||||
//! \brief Figures out which Compliance Parser to call, based on machine family/version and calls it.
|
//! \brief Figures out which Compliance Parser to call, based on machine family/version and calls it.
|
||||||
bool ParseCompliance(void);
|
bool ParseCompliance(void);
|
||||||
@ -229,13 +229,13 @@ protected:
|
|||||||
void AddEvent(class PRS1ParsedEvent* event);
|
void AddEvent(class PRS1ParsedEvent* event);
|
||||||
|
|
||||||
//! \brief Read and parse the non-waveform header data from a V2 PRS1 file
|
//! \brief Read and parse the non-waveform header data from a V2 PRS1 file
|
||||||
bool ReadNormalHeaderV2(class QFile & f);
|
bool ReadNormalHeaderV2(class RawDataDevice & f);
|
||||||
|
|
||||||
//! \brief Read and parse the non-waveform header data from a V3 PRS1 file
|
//! \brief Read and parse the non-waveform header data from a V3 PRS1 file
|
||||||
bool ReadNormalHeaderV3(class QFile & f);
|
bool ReadNormalHeaderV3(class RawDataDevice & f);
|
||||||
|
|
||||||
//! \brief Read and parse the waveform-specific header data from a PRS1 file
|
//! \brief Read and parse the waveform-specific header data from a PRS1 file
|
||||||
bool ReadWaveformHeader(class QFile & f);
|
bool ReadWaveformHeader(class RawDataDevice & f);
|
||||||
|
|
||||||
//! \brief Extract the stored CRC from the end of the data of a PRS1 chunk
|
//! \brief Extract the stored CRC from the end of the data of a PRS1 chunk
|
||||||
bool ExtractStoredCrc(int size);
|
bool ExtractStoredCrc(int size);
|
||||||
|
Loading…
Reference in New Issue
Block a user