CRC16 Fiabilidad de suma de comprobación

No hace mucho tiempo, de servicio, me encontré con un problema bastante interesante.

Tenemos un dispositivo que realiza un intercambio intensivo en el bus interno RS485, el número de paquetes que pasan es de varios miles por segundo, cada paquete tiene una longitud de 7 bytes, dos de los cuales están diseñados para almacenar la suma de verificación CRC16 en su versión CMS (polinomio = 0x8005, valor inicial = 0xFFFF). La recepción se lleva a cabo en el búfer FIFO, que se desplaza hacia arriba con desplazamiento después de la recepción de cada byte posterior. Un indicador de recibir un paquete real es el hecho de que su suma de verificación coincide con el valor transmitido en el paquete mismo. Sin encabezados ni parámetros adicionales.

El problema era el siguiente: periódicamente, aproximadamente una vez cada 5 minutos, un paquete se deslizaba durante la transferencia de datos, cuyos datos daban un valor atípico para uno de los canales y, con mayor frecuencia, el valor atípico se producía con el mismo valor. Al principio miramos en la dirección de colisiones físicas, pero resultó diferente: de vez en cuando en el búfer donde se recopilaron los datos, apareció un paquete que consistía en el final del paquete anterior y el comienzo del siguiente, y la suma de comprobación de dicho paquete combinado resultó ser correcta. Es decir, hay una colisión de la suma de comprobación: el paquete no tiene sentido, pero proporciona la suma de comprobación correcta.

Naturalmente, el error ya estaba en el nivel de diseño del sistema, ya que los paquetes no tenían encabezados, la introducción de un encabezado de byte adicional redujo el número de errores a un nivel indetectable, pero esto no me pareció suficiente. Decidí comprobar cómo los diferentes tipos de sumas de comprobación de 16 bits difieren entre sí en condiciones reales. En realidad, sobre esto y el artículo.

A modo de comparación, seleccioné algunas de las sumas de comprobación de 16 bits más utilizadas con varios polinomios, valores iniciales y un mecanismo de llegada de bits. Las cantidades que he seleccionado se resumen en la siguiente tabla:
DesignaciónPolinomioInitRefinarRefoutXorout
CMS0x80050xFFFFfalsofalso0x0000
CCITT0x10210xFFFFfalsofalso0x0000
AGO-CCITT0x10210x1D0Ffalsofalso0x0000
BYPASS0x80050x0000falsofalso0x0000
CDMA20000xC8670xFFFFfalsofalso0x0000
DDS-1100x80050x800Dfalsofalso0x0000
DECT-X0x05890x0000falsofalso0x0000
EN-137570x3D650x0000falsofalso0xFFFF
Modbus0x80050xFFFFciertocierto0x0000
T10-DIF0x8BB70x0000falsofalso0x0000
TELEDISCO0xA0970x0000falsofalso0x0000
XMODEM0x10210x0000falsofalso0x0000

En este caso:

  • RefIn - el orden de llegada de los bits desde el búfer de datos: falso - comenzando con el bit más significativo (MSB primero), verdadero - LSB primero;
  • RefOut: indicador de inversión del orden de bits en la salida: verdadero: inversión.

Al emular la corrupción de paquetes, implementé los siguientes modelos:

  • Reproducción aleatoria: llenar un número aleatorio de bytes en un paquete con valores aleatorios
  • Desplazamiento de bits: desplaza bytes aleatorios en un paquete a la izquierda
  • Paquete de rollo: desplazamiento de byte de anillo en el paquete a la izquierda
  • Desplazamiento a la derecha : desplazamiento de paquetes a la derecha en un byte, 0xFF se agrega a la izquierda (la transmisión es a través de UART)
  • Desplazamiento a la izquierda: desplaza el paquete a la izquierda en un byte, 0xFF se agrega a la derecha
  • Rellenar ceros: rellenar un número aleatorio de bytes en un paquete con 0x00 bytes (todos ceros)
  • Rellenar unos: llenar un número aleatorio de bytes en un paquete con 0xFF bytes (todas las unidades)
  • Inyección de bytes: inserte un byte aleatorio en un paquete en un lugar aleatorio, los bytes después del insertado se desplazan en la dirección de la cola
  • Solo bit: daño a un solo bit aleatorio

Luego, el programa generó aleatoriamente 100,000,000 paquetes, cada uno de ellos llevó a cabo las operaciones anteriores, después de lo cual se compararon las sumas de verificación de los paquetes originales y modernizados. Los paquetes que no cambiaron durante la conversión fueron descartados. Si la suma de comprobación coincide, se registra un error.

Como resultado, se obtuvo la siguiente tabla con el número de errores:
DesignaciónBarajarCambio de bitPaquete de rolloDesplazamiento a la derechaDesplazamiento a la izquierdaLlenar cerosLlenar losInyección de bytesSuma
CMS5101387429371439168839704010108024099
CCITT2012112733201494148610631096113012728
AGO-CCITT2012112733201494148610631096113012728
BYPASS5101387429371439168839704010108024099
CDMA20001368102519461462167810431028111210662
DDS-1105101387429371439168839704010108024099
DECT-X1432118959151779158012151209109315412
EN-137571281220930431520152821932187103915,000
Modbus5090388830861282158239473897107323845
T10-DIF139092214241421163099493810939812
TELEDISCO1394104953981451151210961066106514031
XMODEM2012112733201494148610631096113012728

Obviamente, el valor inicial del algoritmo no afecta el resultado de ninguna manera, lo cual es lógico, el valor inicial nos da solo un valor diferente de la suma de verificación, pero el mecanismo de cálculo en sí no cambia de ninguna manera. Por lo tanto, excluí estas sumas de verificación de mayor consideración. De la misma manera, no tiene sentido considerar los errores en bits individuales, todas las sumas de verificación lo resolvieron sin error, lo que, de hecho, se les exigió durante la creación.

Bueno, la tabla de calidad final de la suma de comprobación, ya sin tener en cuenta algoritmos duplicados:
DesignaciónNumero de colisionesLugar
CMS240998
CCITT127283
CDMA2000106622
DECT-X154126 6
EN-1375715,0005 5
Modbus238457 7
T10-DIF98121
TELEDISCO140314 4

Dejo las conclusiones restantes a los lectores. Solo noto de mí mismo que el número de unidades en el polinomio de suma de verificación tiene un cierto efecto en los resultados. Pero esta es solo mi opinión subjetiva personal. Estaré encantado de escuchar otras explicaciones.

El código fuente del programa se proporciona a continuación.

Código fuente
#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; } 


Como resultado, en la próxima versión del producto para el bus interno, se eligió la suma de verificación CCITT, en mayor medida porque su implementación estaba disponible en el hardware del microcontrolador utilizado.

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


All Articles