From 6fa261d7016ba8e8be4bac8f35d8f95c714988a0 Mon Sep 17 00:00:00 2001 From: Stuart Baker Date: Wed, 15 Jan 2025 08:47:57 -0600 Subject: [PATCH] Add 16-bit CCITT CRC implementation. --- src/utils/Crc.cxx | 54 ++++++++++++- src/utils/Crc.cxxtest | 57 +++++++++++++ src/utils/Crc.hxx | 182 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 279 insertions(+), 14 deletions(-) diff --git a/src/utils/Crc.cxx b/src/utils/Crc.cxx index ce4adb55d..55d38c1ea 100644 --- a/src/utils/Crc.cxx +++ b/src/utils/Crc.cxx @@ -222,7 +222,7 @@ void crc3_crc16_ibm(const void* data, size_t length_bytes, uint16_t* checksum) } // static -uint8_t Crc8DallasMaxim::table256[256] = +const uint8_t Crc8DallasMaxim::table256[256] = { 0x00, 0x5e, 0xbc, 0xe2, 0x61, 0x3f, 0xdd, 0x83, 0xc2, 0x9c, 0x7e, 0x20, 0xa3, 0xfd, 0x1f, 0x41, @@ -259,15 +259,63 @@ uint8_t Crc8DallasMaxim::table256[256] = }; // static -uint8_t Crc8DallasMaxim::tableLo16[16] = +const uint8_t Crc8DallasMaxim::tableLo16[16] = { 0x00, 0x5e, 0xbc, 0xe2, 0x61, 0x3f, 0xdd, 0x83, 0xc2, 0x9c, 0x7e, 0x20, 0xa3, 0xfd, 0x1f, 0x41, }; // static -uint8_t Crc8DallasMaxim::tableHi16[16] = +const uint8_t Crc8DallasMaxim::tableHi16[16] = { 0x00, 0x9d, 0x23, 0xbe, 0x46, 0xdb, 0x65, 0xf8, 0x8c, 0x11, 0xaf, 0x32, 0xca, 0x57, 0xe9, 0x74 }; + +const uint16_t Crc16CCITT::table256[256] = +{ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, + 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, + 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, + 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, + 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, + 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, + 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, + 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, + 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, + 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, + 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, + 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, + 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, + 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, + 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, + 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, + 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, + 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, + 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, + 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, + 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, + 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 +}; + +const uint16_t Crc16CCITT::tableLo16[16] = +{ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF +}; + +const uint16_t Crc16CCITT::tableHi16[16] = +{ + 0x0000, 0x1231, 0x2462, 0x3653, 0x48C4, 0x5AF5, 0x6CA6, 0x7E97, + 0x9188, 0x83B9, 0xB5EA, 0xA7DB, 0xD94C, 0xCB7D, 0xFD2E, 0xEF1F +}; \ No newline at end of file diff --git a/src/utils/Crc.cxxtest b/src/utils/Crc.cxxtest index 2c5db06df..f0cd53428 100644 --- a/src/utils/Crc.cxxtest +++ b/src/utils/Crc.cxxtest @@ -248,3 +248,60 @@ TEST(Crc8Test, LogonExample4) m.update256(0xaa); LOG(INFO, "example: 0x%02x", m.get()); } + +TEST(CRC16CCITTTest, Example) +{ + std::basic_string vector = + { + '1', '2', '3', '4', '5', '6', '7', '8', '9' + }; + + printf("sizeof(vector): %zu\n", sizeof(vector)); + Crc16CCITT crc; + crc.crc(vector.data(), vector.size()); + EXPECT_EQ(0x29B1U, crc.get()); + + crc.init(); + for (unsigned i = 0; i < vector.size(); ++i) + { + crc.update16(vector[i]); + } + EXPECT_EQ(0x29B1U, crc.get()); + + crc.init(); + for (unsigned i = 0; i < vector.size(); ++i) + { + crc.update256(vector[i]); + } + EXPECT_EQ(0x29B1U, crc.get()); +} + +TEST(CRC16CCITTTest, ExampleCRC3) +{ + { + // Even number of bytes. + const uint8_t vector[] = + { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 + }; + uint16_t results[3]; + crc3_crc16_ccitt(vector, sizeof(vector), results); + EXPECT_EQ(0x4792, results[0]); + EXPECT_EQ(0x2436, results[1]); + EXPECT_EQ(0x9EC6, results[2]); + } + + { + // Odd number of bytes. + const uint8_t vector[] = + { + 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B + }; + uint16_t results[3]; + crc3_crc16_ccitt(vector, sizeof(vector), results); + EXPECT_EQ(0x6989, results[0]); + EXPECT_EQ(0x23ED, results[1]); + EXPECT_EQ(0xF408, results[2]); + } + +} diff --git a/src/utils/Crc.hxx b/src/utils/Crc.hxx index 00765be73..bdb3adf82 100644 --- a/src/utils/Crc.hxx +++ b/src/utils/Crc.hxx @@ -38,6 +38,16 @@ #include #include +#ifndef CRC8DALLAS_TABLE_SIZE +/// Use the smaller (slower) table by default. +#define CRC8DALLAS_TABLE_SIZE 16 +#endif +#ifndef CRC16CCITT_TABLE_SIZE +/// Use the larger (faster) table by default. +#define CRC16CCITT_TABLE_SIZE 256 +#endif + + /** Computes the 16-bit CRC value over data using the CRC16-ANSI (aka * CRC16-IBM) settings. This involves zero init value, zero terminating value, * reversed polynomial 0xA001, reversing input bits and reversing output @@ -63,7 +73,6 @@ uint16_t crc_16_ibm(const void* data, size_t length_bytes); */ void crc3_crc16_ibm(const void* data, size_t length_bytes, uint16_t* checksum); - /// Helper class for computing CRC-8 according to Dallas/Maxim specification /// for 1-wire protocol. This specification is used for BiDiB, RCN-218 and /// S-9.2.1.1. @@ -166,29 +175,180 @@ public: state_ = table256[state_ ^ message_byte]; } -#ifdef CRC8_TABLE_SIZE /// Processes one byte of the incoming message. /// @param message_byte next byte in the message. void update(uint8_t message_byte) { - update##CRC8_TABLE_SIZE(message_byte); - } +#if CRC8DALLAS_TABLE_SIZE == 0 + update0(message_byte); +#elif CRC8DALLAS_TABLE_SIZE == 16 + update16(message_byte); +#elif CRC8DALLAS_TABLE_SIZE == 256 + update256(message_byte); +#else +#error "Invalid value for CRC16CCITT_TABLE_SIZE" #endif + } private: // Of the static tables here only those will be linked into a binary which // have been used there. - + /// 256-entry lookup table for the update256 function. - static uint8_t table256[256]; + static const uint8_t table256[256]; /// 16-entry lookup table for the update16 function. - static uint8_t tableHi16[16]; + static const uint8_t tableHi16[16]; + /// 16-entry lookup table for the update16 function. - static uint8_t tableLo16[16]; - + static const uint8_t tableLo16[16]; + /// Current value of the state register for the CRC computation. uint8_t state_; -}; +}; // Crc8DallasMaxim + +/// Helper class for computing CRC-16 according to the CCITT specification +/// (polynomial = 0x1021, x^16 + x^12 + x^5 + 1, initial value = 0xFFFF). +/// +/// This class can incrementally compute CRC byte by byte. There are two +/// implementations available, with different code space requirements. +class Crc16CCITT +{ +public: + Crc16CCITT() + : state_(0xFFFF) + { + } + + /// Re-sets the state machine for checksumming a new message. + void init() + { + state_ = 0xFFFF; + } + + /// @return the checksum of the currently consumed message. + uint16_t get() + { + return state_; + } + + /// Checks that the message has a correct CRC. This function assumes that + /// the CRC byte has already been consumed. + /// @return true if the message checksum is correct. + bool check_ok() + { + return (state_ == 0); + } + + /// Checks that the message has a correct CRC. This function assumes that + /// the CRC byte was not part of the message. + /// @param checksum the CRC16 of the received message. + /// @return true if the message checksum is correct. + bool check_ok(uint16_t checksum) + { + return (state_ == checksum); + } + + /// Processes one byte of the incoming message. A small lookup table will be + /// used. + /// @param message_byte next byte in the message. + void update16(uint8_t message_byte) + { + uint8_t data = (state_ >> 8) ^ message_byte; + state_ = (state_ << 8) ^ + tableLo16[data & 0xF] ^ tableHi16[data >> 4]; + } + + /// Processes one byte of the incoming message. A 256-byte lookup table + /// will be used. + /// @param message_byte next byte in the message. + void update256(uint8_t message_byte) + { + state_ = (state_ << 8) ^ table256[(state_ >> 8) ^ message_byte]; + } + + /// Processes one byte of the incoming message. + /// @param message_byte next byte in the message. + void update(uint8_t message_byte) + { +#if CRC16CCITT_TABLE_SIZE == 16 + update16(message_byte); +#elif CRC16CCITT_TABLE_SIZE == 256 + update256(message_byte); +#else +#error "Invalid value for CRC16CCITT_TABLE_SIZE" +#endif + } + + /// Computes the 16-bit CRC value over data + /// @param data what to compute the checksum over + /// @param length_bytes how long data is + void crc(const void* data, size_t length_bytes) + { + const uint8_t *payload = static_cast(data); + init(); + for (size_t i = 0; i < length_bytes; ++i) + { + update(payload[i]); + } + } + +private: + // Of the static tables here only those will be linked into a binary which + // have been used there. + + /// 256-entry lookup table for the update256 function. + static const uint16_t table256[256]; + + /// 16-entry lookup table for the update16 function. + static const uint16_t tableHi16[16]; + + /// 16-entry lookup table for the update16 function. + static const uint16_t tableLo16[16]; + + /// Current value of the state register for the CRC computation. + uint16_t state_; +}; // Crc16CCITT + +/// Computes the triple-CRC value over a chunk of data. checksum is an array of +/// 3 halfwords. The first halfword will get the CRC of the data array, the +/// second halfword the CRC of all even index bytes (starting with the first +/// byte, index 0), the third halfword will get the CRC of all odd index bytes +/// (starting with the the second byte, index 1). +/// +/// This implementation has no requirements for starting alignment or size +/// modulus. It will work on any number of bytes. +/// +/// @param data what to compute the checksum over +/// @param length_bytes how long data is +/// @param checksum is the output buffer where to store the 48-bit checksum. +static inline void crc3_crc16_ccitt( + const void* data, size_t length_bytes, uint16_t checksum[3]) +{ + const uint8_t* payload = static_cast(data); + + Crc16CCITT crc_all; + Crc16CCITT crc_even; + Crc16CCITT crc_odd; + + for (size_t i = 0; i < length_bytes; ++i) + { + crc_all.update(payload[i]); + if (i & 0x1) + { + // odd index bytes + crc_odd.update(payload[i]); + } + else + { + // even index byte + crc_even.update(payload[i]); + } + } + + checksum[0] = crc_all.get(); + checksum[1] = crc_even.get(); + checksum[2] = crc_odd.get(); +} -#endif // _UTILS_CRC_HXX_ +#endif // _UTILS_CRC_HXX_ \ No newline at end of file