Calculate and check PRS1 CRC16 on V2 files.

This commit is contained in:
sawinglogz 2019-05-15 15:16:14 -04:00
parent c8cd66992a
commit 1c564fb296
2 changed files with 97 additions and 67 deletions

View File

@ -43,61 +43,55 @@
QHash<int, QString> ModelMap; QHash<int, QString> ModelMap;
#define PRS1_CRC_CHECK
#ifdef PRS1_CRC_CHECK // CRC-16/KERMIT, polynomial: 0x11021, bit reverse algorithm
typedef quint16 crc_t; // Table generated by crcmod (crc-kermit)
static const crc_t crc_table[256] = { typedef quint16 crc16_t;
0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, static crc16_t CRC16(unsigned char * data, size_t data_len, crc16_t crc=0)
0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, {
0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, static const crc16_t table[256] = {
0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, 0x0000U, 0x1189U, 0x2312U, 0x329bU, 0x4624U, 0x57adU, 0x6536U, 0x74bfU,
0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, 0x8c48U, 0x9dc1U, 0xaf5aU, 0xbed3U, 0xca6cU, 0xdbe5U, 0xe97eU, 0xf8f7U,
0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, 0x1081U, 0x0108U, 0x3393U, 0x221aU, 0x56a5U, 0x472cU, 0x75b7U, 0x643eU,
0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, 0x9cc9U, 0x8d40U, 0xbfdbU, 0xae52U, 0xdaedU, 0xcb64U, 0xf9ffU, 0xe876U,
0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, 0x2102U, 0x308bU, 0x0210U, 0x1399U, 0x6726U, 0x76afU, 0x4434U, 0x55bdU,
0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, 0xad4aU, 0xbcc3U, 0x8e58U, 0x9fd1U, 0xeb6eU, 0xfae7U, 0xc87cU, 0xd9f5U,
0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, 0x3183U, 0x200aU, 0x1291U, 0x0318U, 0x77a7U, 0x662eU, 0x54b5U, 0x453cU,
0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, 0xbdcbU, 0xac42U, 0x9ed9U, 0x8f50U, 0xfbefU, 0xea66U, 0xd8fdU, 0xc974U,
0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, 0x4204U, 0x538dU, 0x6116U, 0x709fU, 0x0420U, 0x15a9U, 0x2732U, 0x36bbU,
0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, 0xce4cU, 0xdfc5U, 0xed5eU, 0xfcd7U, 0x8868U, 0x99e1U, 0xab7aU, 0xbaf3U,
0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, 0x5285U, 0x430cU, 0x7197U, 0x601eU, 0x14a1U, 0x0528U, 0x37b3U, 0x263aU,
0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, 0xdecdU, 0xcf44U, 0xfddfU, 0xec56U, 0x98e9U, 0x8960U, 0xbbfbU, 0xaa72U,
0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, 0x6306U, 0x728fU, 0x4014U, 0x519dU, 0x2522U, 0x34abU, 0x0630U, 0x17b9U,
0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, 0xef4eU, 0xfec7U, 0xcc5cU, 0xddd5U, 0xa96aU, 0xb8e3U, 0x8a78U, 0x9bf1U,
0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, 0x7387U, 0x620eU, 0x5095U, 0x411cU, 0x35a3U, 0x242aU, 0x16b1U, 0x0738U,
0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, 0xffcfU, 0xee46U, 0xdcddU, 0xcd54U, 0xb9ebU, 0xa862U, 0x9af9U, 0x8b70U,
0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, 0x8408U, 0x9581U, 0xa71aU, 0xb693U, 0xc22cU, 0xd3a5U, 0xe13eU, 0xf0b7U,
0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, 0x0840U, 0x19c9U, 0x2b52U, 0x3adbU, 0x4e64U, 0x5fedU, 0x6d76U, 0x7cffU,
0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, 0x9489U, 0x8500U, 0xb79bU, 0xa612U, 0xd2adU, 0xc324U, 0xf1bfU, 0xe036U,
0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, 0x18c1U, 0x0948U, 0x3bd3U, 0x2a5aU, 0x5ee5U, 0x4f6cU, 0x7df7U, 0x6c7eU,
0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, 0xa50aU, 0xb483U, 0x8618U, 0x9791U, 0xe32eU, 0xf2a7U, 0xc03cU, 0xd1b5U,
0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, 0x2942U, 0x38cbU, 0x0a50U, 0x1bd9U, 0x6f66U, 0x7eefU, 0x4c74U, 0x5dfdU,
0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, 0xb58bU, 0xa402U, 0x9699U, 0x8710U, 0xf3afU, 0xe226U, 0xd0bdU, 0xc134U,
0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, 0x39c3U, 0x284aU, 0x1ad1U, 0x0b58U, 0x7fe7U, 0x6e6eU, 0x5cf5U, 0x4d7cU,
0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, 0xc60cU, 0xd785U, 0xe51eU, 0xf497U, 0x8028U, 0x91a1U, 0xa33aU, 0xb2b3U,
0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, 0x4a44U, 0x5bcdU, 0x6956U, 0x78dfU, 0x0c60U, 0x1de9U, 0x2f72U, 0x3efbU,
0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, 0xd68dU, 0xc704U, 0xf59fU, 0xe416U, 0x90a9U, 0x8120U, 0xb3bbU, 0xa232U,
0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, 0x5ac5U, 0x4b4cU, 0x79d7U, 0x685eU, 0x1ce1U, 0x0d68U, 0x3ff3U, 0x2e7aU,
0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 0xe70eU, 0xf687U, 0xc41cU, 0xd595U, 0xa12aU, 0xb0a3U, 0x8238U, 0x93b1U,
0x6b46U, 0x7acfU, 0x4854U, 0x59ddU, 0x2d62U, 0x3cebU, 0x0e70U, 0x1ff9U,
0xf78fU, 0xe606U, 0xd49dU, 0xc514U, 0xb1abU, 0xa022U, 0x92b9U, 0x8330U,
0x7bc7U, 0x6a4eU, 0x58d5U, 0x495cU, 0x3de3U, 0x2c6aU, 0x1ef1U, 0x0f78U,
}; };
crc_t CRC16(const unsigned char *data, size_t data_len) for (size_t i=0; i < data_len; i++) {
{ crc = table[(*data ^ (unsigned char)crc) & 0xFF] ^ (crc >> 8);
crc_t crc = 0;
unsigned int tbl_idx;
while (data_len--) {
tbl_idx = (crc ^ *data) & 0xff;
crc = (crc_table[tbl_idx] ^ (crc >> 8)) & 0xffff;
data++; data++;
} }
return crc;
return crc & 0xffff;
} }
#endif
enum FlexMode { FLEX_None, FLEX_CFlex, FLEX_CFlexPlus, FLEX_AFlex, FLEX_RiseTime, FLEX_BiFlex, FLEX_Unknown }; enum FlexMode { FLEX_None, FLEX_CFlex, FLEX_CFlexPlus, FLEX_AFlex, FLEX_RiseTime, FLEX_BiFlex, FLEX_Unknown };
@ -3479,7 +3473,12 @@ PRS1DataChunk* PRS1DataChunk::ParseNext(QFile & f)
break; break;
} }
// TODO: move block CRC comparison here // Make sure the calculated CRC over the entire chunk (header and data) matches the stored CRC.
if (chunk->calcCrc != chunk->storedCrc) {
// corrupt data block.. bleh..
qDebug() << chunk->m_path << "@" << chunk->m_filepos << "block CRC calc" << hex << chunk->calcCrc << "!= stored" << hex << chunk->storedCrc;
//break; // don't break to avoid changing behavior (for now)
}
// Only return the chunk if it has passed all tests above. // Only return the chunk if it has passed all tests above.
out_chunk = chunk; out_chunk = chunk;
@ -3494,14 +3493,15 @@ bool PRS1DataChunk::ReadHeader(QFile & f)
{ {
bool ok = false; bool ok = false;
do { do {
// Read common header fields.
this->m_filepos = f.pos(); this->m_filepos = f.pos();
this->m_header = f.read(15); this->m_header = f.read(15);
if (this->m_header.size() != 15) { if (this->m_header.size() != 15) {
qWarning() << this->m_path << "file too short?"; qWarning() << this->m_path << "file too short?";
break; break;
} }
unsigned char * header = (unsigned char *)this->m_header.data();
unsigned char * header = (unsigned char *)this->m_header.data();
this->fileVersion = header[0]; // Correlates to DataFileVersion in PROP[erties].TXT, only 2 or 3 has ever been observed this->fileVersion = header[0]; // Correlates to DataFileVersion in PROP[erties].TXT, only 2 or 3 has ever been observed
this->blockSize = (header[2] << 8) | header[1]; this->blockSize = (header[2] << 8) | header[1];
this->htype = header[3]; // 00 = normal, 01=waveform this->htype = header[3]; // 00 = normal, 01=waveform
@ -3511,16 +3511,17 @@ bool PRS1DataChunk::ReadHeader(QFile & f)
this->sessionid = (header[10] << 24) | (header[9] << 16) | (header[8] << 8) | header[7]; this->sessionid = (header[10] << 24) | (header[9] << 16) | (header[8] << 8) | header[7];
this->timestamp = (header[14] << 24) | (header[13] << 16) | (header[12] << 8) | header[11]; this->timestamp = (header[14] << 24) | (header[13] << 16) | (header[12] << 8) | header[11];
// Do a few early sanity checks before any variable-length header data.
if (this->blockSize == 0) { if (this->blockSize == 0) {
qWarning() << this->m_path << "blocksize 0?"; qWarning() << this->m_path << "blocksize 0?";
break; break;
} }
if (this->fileVersion < 2 || this->fileVersion > 3) { if (this->fileVersion < 2 || this->fileVersion > 3) {
qWarning() << this->m_path << "Never seen PRS1 header version < 2 or > 3 before"; qWarning() << this->m_path << "Never seen PRS1 header version < 2 or > 3 before";
break; break;
} }
// Read format-specific variable-length header data.
bool hdr_ok = false; bool hdr_ok = false;
if (this->ext < 5) { // Not a waveform chunk if (this->ext < 5) { // Not a waveform chunk
switch (this->fileVersion) { switch (this->fileVersion) {
@ -3681,29 +3682,29 @@ bool PRS1DataChunk::ReadData(QFile & f)
do { do {
// Read data block // Read data block
int data_size = this->blockSize - this->m_header.size(); int data_size = this->blockSize - this->m_header.size();
if (data_size < 0) {
qWarning() << this->m_path << "chunk size smaller than header";
break;
}
this->m_data = f.read(data_size); this->m_data = f.read(data_size);
if (this->m_data.size() < data_size) { if (this->m_data.size() < data_size) {
qWarning() << this->m_path << "less data in file than specified in header"; qWarning() << this->m_path << "less data in file than specified in header";
break; break;
} }
// Extract the stored CRC from the data buffer and calculate the current CRC.
if (this->fileVersion==3) { if (this->fileVersion==3) {
//int ds = this->m_data.size(); // The last 4 bytes contain a CRC32 checksum of the data.
//quint32 crc16 = this->m_data.at(ds-2) | this->m_data.at(ds-1) << 8; if (!ExtractStoredCrc(4)) {
this->m_data.chop(4); break;
} else {
// last two bytes contain crc16 checksum.
int ds = this->m_data.size();
quint16 crc16 = this->m_data.at(ds-2) | this->m_data.at(ds-1) << 8;
this->m_data.chop(2);
#ifdef PRS1_CRC_CHECK
// This fails.. it needs to include the header!
quint16 calc16 = CRC16((unsigned char *)this->m_data.data(), this->m_data.size());
if (calc16 != crc16) {
// corrupt data block.. bleh..
// qDebug() << "CRC16 doesn't match for chunk" << this->sessionid << "for" << path;
} }
#endif this->calcCrc = this->storedCrc; // TODO
} else {
// The last 2 bytes contain a CRC16 checksum of the data.
if (!ExtractStoredCrc(2)) {
break;
}
this->calcCrc = CRC16((unsigned char *)this->m_data.data(), this->m_data.size());
} }
ok = true; ok = true;
@ -3713,6 +3714,30 @@ bool PRS1DataChunk::ReadData(QFile & f)
} }
bool PRS1DataChunk::ExtractStoredCrc(int size)
{
// Make sure there's enough data for the CRC.
int offset = this->m_data.size() - size;
if (offset < 0) {
qWarning() << this->m_path << "chunk truncated";
return false;
}
// Read the last 16- or 32-bit little-endian integer.
quint32 storedCrc = 0;
unsigned char* data = (unsigned char*)this->m_data.data();
for (int i=0; i < size; i++) {
storedCrc |= data[offset+i] << (8*i);
}
this->storedCrc = storedCrc;
// Drop the CRC from the data.
this->m_data.chop(size);
return true;
}
void InitModelMap() void InitModelMap()
{ {
ModelMap[0x34] = QObject::tr("RemStar Pro with C-Flex+"); // 450/460P ModelMap[0x34] = QObject::tr("RemStar Pro with C-Flex+"); // 450/460P

View File

@ -106,6 +106,8 @@ public:
quint8 storedChecksum; // header checksum stored in file, last byte of m_header quint8 storedChecksum; // header checksum stored in file, last byte of m_header
quint8 calcChecksum; // header checksum as calculated when parsing quint8 calcChecksum; // header checksum as calculated when parsing
quint32 storedCrc; // header + data CRC stored in file, last 2-4 bytes of chunk
quint32 calcCrc; // header + data CRC as calculated when parsing
//! \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); static PRS1DataChunk* ParseNext(class QFile & f);
@ -125,6 +127,9 @@ protected:
//! \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 QFile & f);
//! \brief Extract the stored CRC from the end of the data of a PRS1 chunk
bool ExtractStoredCrc(int size);
}; };
class PRS1Loader; class PRS1Loader;