CRC16チェックサムの信頼性

少し前、勤務中に、私はかなり興味深い問題に遭遇しました。



内部RS485バスで集中的な交換を実行するデバイスがあり、送信されるパケットの数は1秒あたり約数千で、各パケットは7バイト長で、そのうち2つはCMSバージョンにCRC16チェックサムを格納するように設計されています(多項式= 0x8005、開始値= 0xFFFF)。 受信はFIFOバッファで実行され、後続の各バイトの受信後に混雑してシフトアップされます。 実際のパケットを受信したことの指標は、そのチェックサムがパケット自体で送信された値と一致するという事実です。 ヘッダーや追加パラメーターはありません。



問題は次のとおりです。定期的に、データを送信するときに約5分に1回、パケットがスリップし、そのデータはチャネルの1つにデータ異常値を与え、ほとんどの場合、異常値は同じ値になりました。 最初は物理的な衝突の方向に目を向けましたが、データが収集されたバッファに時々異なる結果が出ました。前のパケットの終わりと次のパケットの始まりからなるパケットが出現し、そのような結合されたパケットのチェックサムは正しいことが判明しました。 つまり、チェックサムの衝突があります。パッケージは意味がありませんが、正しいチェックサムを提供します。



当然、パッケージにヘッダーがなかったため、エラーはすでにシステム設計レベルにありました。追加のバイトヘッダーの導入により、エラーの数が検出できないレベルに減少しましたが、これは十分ではないように思われました。 さまざまな種類の16ビットチェックサムが実際の状態でどのように異なるかを確認することにしました。 実際、これと記事について。



比較のために、さまざまな多項式、開始値、ビット到着メカニズムを備えた、最も一般的に使用される16ビットチェックサムを選択しました。 選択した金額を次の表にまとめます。

指定 多項式 初期化 精製 Refout Xorout
CMS 0x8005 0xFFFF 0x0000
CCITT 0x1021 0xFFFF 0x0000
8月-CCITT 0x1021 0x1D0F 0x0000
バイパス 0x8005 0x0000 0x0000
CDMA2000 0xC867 0xFFFF 0x0000
DDS-110 0x8005 0x800D 0x0000
DECT-X 0x0589 0x0000 0x0000
EN-13757 0x3D65 0x0000 0xFFFF
Modbus 0x8005 0xFFFF 本当 本当 0x0000
T10-DIF 0x8BB7 0x0000 0x0000
TELEDISK 0xA097 0x0000 0x0000
XMODEM 0x1021 0x0000 0x0000


この場合:





パケットの破損をエミュレートするとき、次のモデルを実装しました。





次に、プログラムはランダムに1億個のパッケージを生成し、それぞれが上記の操作を実行した後、元のパッケージと最新化されたパッケージのチェックサムを比較しました。 変換中に変更されなかったパケットは破棄されました。 チェックサムが同じ場合、エラーが記録されました。



その結果、エラー数とともに次の表が得られました。

指定 シャッフル ビットシフト ロールパケット 右シフト 左シフト ゼロを埋める 記入する バイトインジェクション 合計
CMS 5101 3874 2937 1439 1688 3970 4010 1080 24099
CCITT 2012 1127 3320 1494 1486 1063 1096 1130 12728
8月-CCITT 2012 1127 3320 1494 1486 1063 1096 1130 12728
バイパス 5101 3874 2937 1439 1688 3970 4010 1080 24099
CDMA2000 1368 1025 1946 1462 1678 1043 1028 1112 10662
DDS-110 5101 3874 2937 1439 1688 3970 4010 1080 24099
DECT-X 1432 1189 5915 1779 1580 1215 1209 1093 15412
EN-13757 1281 2209 3043 1520 1528 2193 2187 1039 15,000
Modbus 5090 3888 3086 1282 1582 3947 3897 1073 23845
T10-DIF 1390 922 1424 1421 1630 994 938 1093 9812
TELEDISK 1394 1049 5398 1451 1512 1096 1066 1065 14031
XMODEM 2012 1127 3320 1494 1486 1063 1096 1130 12728


明らかに、アルゴリズムの開始値は結果に何の影響も与えません。これは論理的です。開始値はチェックサムの異なる値のみを提供しますが、計算メカニズム自体は変更されません。 したがって、これらのチェックサムを今後の検討から除外しました。 同様に、単一ビットのエラーを考慮することは意味がありません。すべてのチェックサムはエラーなしでこれに対処しましたが、実際には作成中にエラーが必要でした。



さて、チェックサムの最終品質表は、すでに重複アルゴリズムを考慮せずに:

指定 衝突回数 場所
CMS 24099 8
CCITT 12728 3
CDMA2000 10662 2
DECT-X 15412 6
EN-13757 15,000 5
Modbus 23845 7
T10-DIF 9812 1
TELEDISK 14031 4


残りの結論は読者に任せます。 私は、チェックサム多項式の単位数が結果に一定の影響を与えることに自分自身から注意します。 しかし、これは私の個人的な主観的な意見です。 他の説明を聞いてうれしいです。



プログラムのソースコードを以下に示します。



ソースコード
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <stdbool.h> #include <string.h> #include <time.h> #define PACKET_LEN (7) #define NUM_OF_CYCLES (100000) static unsigned char reverse_table[16] = { 0x0, 0x8, 0x4, 0xC, 0x2, 0xA, 0x6, 0xE, 0x1, 0x9, 0x5, 0xD, 0x3, 0xB, 0x7, 0xF }; uint8_t reverse_bits(uint8_t byte) { // Reverse the top and bottom nibble then swap them. return (reverse_table[byte & 0b1111] << 4) | reverse_table[byte >> 4]; } uint16_t reverse_word(uint16_t word) { return ((reverse_bits(word & 0xFF) << 8) | reverse_bits(word >> 8)); } uint16_t crc16_common(uint8_t *data, uint8_t len, uint16_t poly, uint16_t init, uint16_t doXor, bool refIn, bool refOut) { uint8_t y; uint16_t crc; crc = init; while (len--) { if (refIn) crc = ((uint16_t)reverse_bits(*data++) << 8) ^ crc; else crc = ((uint16_t)*data++ << 8) ^ crc; for (y = 0; y < 8; y++) { if (crc & 0x8000) crc = (crc << 1) ^ poly; else crc = crc << 1; } } if (refOut) crc = reverse_word(crc); return (crc ^ doXor); } uint16_t crc16_ccitt(uint8_t *data, uint8_t len) { return crc16_common(data, len, 0x1021, 0xFFFF, 0x0000, false, false); } uint16_t crc16_bypass(uint8_t *data, uint8_t len) { return crc16_common(data, len, 0x8005, 0x0000, 0x0000, false, false); } uint16_t crc16_xmodem(uint8_t *data, uint8_t len) { return crc16_common(data, len, 0x1021, 0x0000, 0x0000, false, false); } uint16_t crc16_teledisk(uint8_t *data, uint8_t len) { return crc16_common(data, len, 0xA097, 0x0000, 0x0000, false, false); } uint16_t crc16_augccitt(uint8_t *data, uint8_t len) { return crc16_common(data, len, 0x1021, 0x1d0f, 0x0000, false, false); } uint16_t crc16_cdma2000(uint8_t *data, uint8_t len) { return crc16_common(data, len, 0xc867, 0xffff, 0x0000, false, false); } uint16_t crc16_dds110(uint8_t *data, uint8_t len) { return crc16_common(data, len, 0x8005, 0x800d, 0x0000, false, false); } uint16_t crc16_dect(uint8_t *data, uint8_t len) { return crc16_common(data, len, 0x0589, 0x0000, 0x0000, false, false); } uint16_t crc16_en13757(uint8_t *data, uint8_t len) { return crc16_common(data, len, 0x3d65, 0x0000, 0xffff, false, false); } uint16_t crc16_t10dif(uint8_t *data, uint8_t len) { return crc16_common(data, len, 0x8bb7, 0x0000, 0x0000, false, false); } uint16_t crc16_cms(uint8_t *data, uint8_t len) { return crc16_common(data, len, 0x8005, 0xFFFF, 0x0000, false, false); } uint16_t crc16_modbus(uint8_t *data, uint8_t len) { return crc16_common(data, len, 0x8005, 0xFFFF, 0x0000, true, true); } bool compare_buf(uint8_t *buf1, uint8_t *buf2) { uint8_t x; for (x = 0; x < PACKET_LEN; x++) { if (buf1[x] != buf2[x]) return true; } return false; } bool method_shuffle(uint8_t *buf) { uint8_t i, j; uint16_t rnd; uint8_t copy[PACKET_LEN]; memcpy(copy, buf, PACKET_LEN); for (i = 0; i < PACKET_LEN; i++) { for (j = 0; j < 10; j++) { rnd = (uint16_t)rand(); if (rnd % 7 == 0) buf[i] ^= (1 << (rnd % 8)); } } return compare_buf(buf, copy); } bool method_bitshift(uint8_t *buf) { uint8_t x, i, j; uint8_t copy[PACKET_LEN]; memcpy(copy, buf, PACKET_LEN); x = (uint8_t)(rand() % PACKET_LEN) + 1; for (j = 0; j < x; j++) { i = (uint8_t)(rand() % PACKET_LEN); if (buf[i] == 0) buf[i] = 0x01; else buf[i] <<= 1; } return compare_buf(buf, copy); } bool method_packetroll(uint8_t *buf) { uint8_t x, i, j; uint8_t temp; uint8_t copy[PACKET_LEN]; memcpy(copy, buf, PACKET_LEN); x = (uint8_t)(rand() % (PACKET_LEN - 1)) + 1; for (j = 0; j < x; j++) { temp = buf[0]; for (i = 0; i < PACKET_LEN - 1; i++) buf[i] = buf[i + 1]; buf[PACKET_LEN - 1] = temp; } return compare_buf(buf, copy); } bool method_shiftright(uint8_t *buf) { uint8_t i; uint8_t copy[PACKET_LEN]; memcpy(copy, buf, PACKET_LEN); for (i = 0; i < PACKET_LEN - 1; i++) buf[i + 1] = buf[i]; buf[0] = 0xff; return compare_buf(buf, copy); } bool method_shiftleft(uint8_t *buf) { uint8_t i; uint8_t copy[PACKET_LEN]; memcpy(copy, buf, PACKET_LEN); for (i = 0; i < PACKET_LEN - 1; i++) buf[i] = buf[i + 1]; buf[PACKET_LEN - 1] = 0xff; return compare_buf(buf, copy); } bool method_zero(uint8_t *buf) { uint8_t x, i, j; uint8_t copy[PACKET_LEN]; memcpy(copy, buf, PACKET_LEN); x = (uint8_t)(rand() % PACKET_LEN) + 1; for (j = 0; j < x; j++) { i = (uint8_t)(rand() % PACKET_LEN); if (buf[i] != 0x00) buf[i] = 0x00; else buf[i] = 0xFF; } return compare_buf(buf, copy); } bool method_one(uint8_t *buf) { uint8_t x, i, j; uint8_t copy[PACKET_LEN]; memcpy(copy, buf, PACKET_LEN); x = (uint8_t)(rand() % PACKET_LEN) + 1; for (j = 0; j < x; j++) { i = (uint8_t)(rand() % PACKET_LEN); if (buf[i] != 0xFF) buf[i] = 0xFF; else buf[i] = 0x00; } return compare_buf(buf, copy); } bool method_injection(uint8_t *buf) { uint8_t x, i; uint8_t copy[PACKET_LEN]; memcpy(copy, buf, PACKET_LEN); x = (uint8_t)(rand() % PACKET_LEN); for (i = PACKET_LEN - 1; i > x; i--) { buf[i] = buf[i - 1]; } buf[x] = (uint8_t)rand(); return compare_buf(buf, copy); } bool method_single(uint8_t *buf) { uint8_t x; x = (uint8_t)(rand() % (PACKET_LEN * 8)); buf[(uint8_t)(x / 8)] ^= (1 << (x % 8)); return true; } typedef struct { uint16_t crc1; uint16_t crc2; uint32_t errors; uint16_t (*fn)(uint8_t *data, uint8_t len); char name[32]; } tCRC; typedef struct { bool (*fn)(uint8_t *buf); char name[32]; } tMethod; static tMethod methods[] = { {method_shuffle, "Shuffle"}, {method_bitshift, "Bit shift"}, {method_packetroll, "Roll packet"}, {method_shiftright, "Right shift"}, {method_shiftleft, "Left shift"}, {method_zero, "Fill zeros"}, {method_one, "Fill ones"}, {method_injection, "Byte injection"}, {method_single, "Single bit"} }; static tCRC crcs[] = { {0, 0, 0, crc16_cms, "CMS"}, {0, 0, 0, crc16_ccitt, "CCITT"}, {0, 0, 0, crc16_augccitt, "AUG-CCITT"}, {0, 0, 0, crc16_bypass, "BYPASS"}, {0, 0, 0, crc16_cdma2000, "CDMA2000"}, {0, 0, 0, crc16_dds110, "DDS-110"}, {0, 0, 0, crc16_dect, "DECT-X"}, {0, 0, 0, crc16_en13757, "EN-13757"}, {0, 0, 0, crc16_modbus, "Modbus"}, {0, 0, 0, crc16_t10dif, "T10-DIF"}, {0, 0, 0, crc16_teledisk, "TELEDISK"}, {0, 0, 0, crc16_xmodem, "XMODEM"} }; int main(int argc, char * argv[]) { uint32_t num_of_cycle; uint32_t num_of_sums; uint8_t packet[PACKET_LEN]; uint8_t i; uint8_t m; //uint8_t buf[8] = {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17}; srand(time(NULL)); printf("------------------------- CRC16 comparison -------------------------\n"); num_of_sums = sizeof(crcs) / sizeof(tCRC); for (m = 0; m < sizeof(methods) / sizeof(tMethod); m++) { printf("\r%s:\n", methods[m].name); for (i = 0; i < num_of_sums; i++) { crcs[i].errors = 0; } for (num_of_cycle = 0; num_of_cycle < NUM_OF_CYCLES; num_of_cycle++) { for (i = 0; i < PACKET_LEN; i++) packet[i] = (uint8_t)rand(); for (i = 0; i < num_of_sums; i++) crcs[i].crc1 = crcs[i].fn(packet, PACKET_LEN); if (!methods[m].fn(packet)) continue; for (i = 0; i < num_of_sums; i++) { crcs[i].crc2 = crcs[i].fn(packet, PACKET_LEN); if (crcs[i].crc1 == crcs[i].crc2) crcs[i].errors++; } if (num_of_cycle % 1000 == 0) printf("\r%.2f%%", (float)num_of_cycle / NUM_OF_CYCLES * 100); } for (i = 0; i < num_of_sums; i++) printf("\r %20s: %10d\n", crcs[i].name, crcs[i].errors); } return 0; }
      
      







その結果、製品の次のバージョンでは、内部バスにCCITTチェックサムが選択されました。これは、主に、使用されているマイクロコントローラーのハードウェアで実装が利用可能であったためです。



All Articles