CRC16 موثوقية المجموع الاختباري

منذ وقت ليس ببعيد ، في الخدمة ، واجهت مشكلة مثيرة للاهتمام إلى حد ما.

لدينا جهاز يقوم بتبادل مكثف على ناقل RS485 الداخلي ، يبلغ عدد الحزم المارة حوالي عدة آلاف في الثانية ، وطول كل حزمة 7 بايت ، اثنان منها مصممان لتخزين المجموع الاختباري CRC16 في إصدار CMS (كثير الحدود = 0x8005 ، قيمة البدء = 0xFFFF). يتم الاستقبال في المخزن المؤقت FIFO ، والذي يتحول لأعلى مع الازدحام بعد استقبال كل بايت لاحق. مؤشر على تلقي حزمة حقيقية هو حقيقة أن المجموع الاختباري يطابق القيمة المرسلة في الحزمة نفسها. لا رؤوس أو معلمات إضافية.

كانت المشكلة كما يلي - بشكل دوري ، مرة واحدة كل 5 دقائق تقريبًا ، انزلقت حزمة أثناء نقل البيانات ، والتي أعطت بياناتها بيانات خارجة عن إحدى القنوات ، وغالبًا ما حدث الخارج لنفس القيمة. في البداية نظرنا في اتجاه التصادمات المادية ، ولكن اتضح بشكل مختلف - من وقت لآخر في المخزن المؤقت حيث تم جمع البيانات ، ظهرت حزمة تتكون من نهاية الحزمة السابقة وبداية الحزمة التالية ، وتبين أن المجموع الاختباري لهذه الحزمة المدمجة صحيح. أي أن هناك تصادمًا في المجموع الاختباري: الحزمة لا معنى لها ، ولكنها تعطي المجموع الاختباري الصحيح.

بطبيعة الحال ، كان الخطأ بالفعل على مستوى تصميم النظام ، نظرًا لأن الحزم لم يكن بها أي رؤوس ، فقد أدى إدخال رأس بايت إضافي إلى تقليل عدد الأخطاء إلى مستوى لا يمكن اكتشافه ، ولكن هذا بدا لي غير كافٍ. قررت التحقق من مدى اختلاف الأنواع المختلفة من المجموع الاختباري 16 بت عن بعضها البعض في الظروف الحقيقية. في الواقع ، حول هذا والمقال.

للمقارنة ، اخترت بعضًا من المجموع الاختباري الأكثر استخدامًا ذي 16 بت مع كثيرات الحدود المختلفة ، وقيم البدء ، وآلية استقبال البتات. المبالغ التي اخترتها ملخصة في الجدول التالي:
التعيينكثير الحدودالأوليصقلرفضXorout
CMS0x80050xFFFFخطأخطأ0x0000
CCITT0x10210xFFFFخطأخطأ0x0000
AUG-CCITT0x10210x1D0Fخطأخطأ0x0000
تجاوز0x80050x0000خطأخطأ0x0000
CDMA20000xC8670xFFFFخطأخطأ0x0000
DDS-1100x80050x800Dخطأخطأ0x0000
DECT-X0x05890x0000خطأخطأ0x0000
EN-137570x3D650x0000خطأخطأ0xFFFF
مودبوس0x80050xFFFFصحيحصحيح0x0000
T10-DIF0x8BB70x0000خطأخطأ0x0000
TELEDISK0xA0970x0000خطأخطأ0x0000
XMODEM0x10210x0000خطأخطأ0x0000

في هذه الحالة:

  • RefIn - ترتيب وصول البتات من مخزن البيانات المؤقت: false - بدءًا من البت الأكثر أهمية (MSB أولاً) ، صحيح - LSB أولاً ؛
  • RefOut - علم عكس ترتيب البتات عند الإخراج: صحيح - عكس.

عند محاكاة تلف الحزمة ، قمت بتطبيق النماذج التالية:

  • تبديل عشوائي : تعبئة عدد وحدات البايت العشوائية في حزمة بقيم عشوائية
  • تحويل البت: يحول وحدات البايت العشوائية في حزمة إلى اليسار
  • حزمة لفة: تحول بايت الدائري في الحزمة إلى اليسار
  • التحول الأيمن: إزاحة الحزمة إلى اليمين بايت واحد ، تتم إضافة 0xFF إلى اليسار (يتم الإرسال عبر UART)
  • التحول الأيسر: انقل الحزمة إلى اليسار بمقدار بايت واحد ، تتم إضافة 0xFF إلى اليمين
  • تعبئة الأصفار: ملء عدد عشوائي من البايتات في حزمة مع 0 × 00 بايت (جميع الأصفار)
  • الحشو : ملء عدد عشوائي من البايتات في حزمة ببايت 0xFF (جميع الوحدات)
  • حقن البايت: أدخل بايتة عشوائية في حزمة في مكان عشوائي ، وتحول البايتات بعد الإدخال في اتجاه الذيل
  • بتة واحدة: تلف بتة عشوائية واحدة

بعد ذلك ، قام البرنامج بشكل عشوائي بتوليد 100000000 حزمة ، نفذ كل منها العمليات المذكورة أعلاه ، وبعد ذلك تمت مقارنة المجموع الاختباري للحزم الأصلية والمطورة. تم تجاهل الحزم التي لم تتغير أثناء التحويل. إذا تطابق المجموع الاختباري ، يتم تسجيل خطأ.

ونتيجة لذلك ، تم الحصول على الجدول التالي مع عدد الأخطاء:
التعيينخلط ورق اللعبتحول بتحزمة لفةالتحول الأيمنالتحول الأيسراملأ الأصفاراملأ منهاحقن البايتالمجموع
CMS5101387429371439168839704010108024099
CCITT2012112733201494148610631096113012728
AUG-CCITT2012112733201494148610631096113012728
تجاوز5101387429371439168839704010108024099
CDMA20001368102519461462167810431028111210662
DDS-1105101387429371439168839704010108024099
DECT-X1432118959151779158012151209109315412
EN-137571281220930431520152821932187103915000
مودبوس5090388830861282158239473897107323845
T10-DIF139092214241421163099493810939812
TELEDISK1394104953981451151210961066106514031
XMODEM2012112733201494148610631096113012728

من الواضح أن قيمة البدء للخوارزمية لا تؤثر على النتيجة بأي طريقة ، وهو أمر منطقي ، تعطينا قيمة البدء قيمة مختلفة فقط من المجموع الاختباري ، ولكن آلية الحساب نفسها لا تتغير بأي شكل من الأشكال. لذلك ، استبعدت هذه الاختبارية من مزيد من النظر. وبنفس الطريقة ، ليس من المنطقي مراعاة الأخطاء في البتات الفردية ، حيث تم التعامل مع جميع الاختبارية مع هذا دون خطأ ، والذي كان مطلوبًا في الواقع أثناء الإنشاء.

حسنًا ، جدول الجودة النهائي للمجموع الاختباري ، بالفعل دون مراعاة الخوارزميات المكررة:
التعيينعدد التصادماتمكان
CMS240998
CCITT127283
CDMA2000106622
DECT-X154126
EN-13757150005
مودبوس238457
T10-DIF98121
TELEDISK140314

أترك باقي الاستنتاجات للقراء. ألاحظ من نفسي فقط أن عدد الوحدات في كثير المجموع الاختباري له تأثير معين على النتائج. ولكن هذا مجرد رأيي الشخصي الشخصي. سأكون سعيدا لسماع تفسيرات أخرى.

ويرد رمز المصدر للبرنامج أدناه.

كود المصدر
#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 ، إلى حد كبير لأن تطبيقه كان متاحًا في أجهزة وحدة التحكم الدقيقة المستخدمة.

Source: https://habr.com/ru/post/ar428746/


All Articles