1. Introduction

CRC veut dire Cyclic Redundancy Code Le calcul de CRC permet au récepteur d'un message de verifier que les données transmises ne contiennent pas d'erreurs. Pour faire cela, l'émetteur du message calcule une valeur "CheckSum" qui est fonction du contenu du message, puis l'ajoute à la fin du message. Le récepteur fait le même calcul, et contôle que le "CheckSum" a la même valeur que celui de l'émetteur.
Un programme écrit en C++Builder vous permettra d'effectuer des tests de calcul. Nous écrirons une version dite " low speed " de calcul des CRC en langage C.

2. Calcul d'une somme simple

On décide de faire la somme des valeurs numériques de tous les caractères du message, d'appliquer un module 256 au résultat pour obtenir une valeur sur 8 Bits, puis de l'ajouter à la fin du message. Le récepteur utilise la même technique et compare le résultat à celui qui est attaché au message, afin de voir si il a été correctement reçu ou pas.

Type utilisé dans le calcul des checksum
Sélectionnez

typedef unsigned int        TU_int32;   // Mot de 32 bit non signé
typedef unsigned short int  TU_int16;   // Mot de 16 bits non signé
typedef unsigned char       TU_int8;    // Mot de 8 Bits non signé
Calcul de somme simple
Sélectionnez

TU_int8 __fastcall CalcSomme(AnsiString Message)
{
    TU_int8 iuResult = 0;   // Resultat sur 8 bits
    //--- Calcul de la somme, le premier caractère est en position 1
    for (int i = 1; i <= Message.Length(); i++)
        iuResult += Message[i];
    //--- Modulo Valeur Maxi sur 8 Bits
    return (iuResult %= 0xFF);
}


Démarrer le programme CalcCRC.
Sélectionner dans le combo type de calcul " Somme simple modulo 256 ", entrer une chaîne de caractères et cliquer sur calculer.


Image non disponible

Entrer la chaîne "Bonjour Papa". La somme est 0x81.
Entrer la chaîne "Bonjour Papi". La somme est 0x89.

Le message est corrompu, cependant le récepteur peut le détecter en comparant la somme transmise (0x81) à la somme qu'il a calculé (0x89) et demander une nouvelle transmission. Bien évidemment, si la somme elle-même est corrompue, un message correctement transmis peut être identifié comme corrompu. Cependant, ce n'est pas très grave, car le message est reconnu comme corrompu. Ce qui peut être dangereux, c'est que le message soit corrompu et que le résultat du calcul de contrôle soit correct. Malheureusement, cette possibilité est inévitable et on ne peut qu'essayer d'en réduire la probabilité en améliorant le calcul de la somme.

3. Le besoin de complexité

Dans le paragraphe précédent, nous avons vu comment le message corrompu a été détecté par le simple calcul de la somme des caractères et un modulo 256. Le problème avec cet algorithme est qu'il est trop simple, et que la somme peut être juste alors qu'il y a bien une corruption du message.

Reprenons notre exemple :
Entrer la chaine "Bonjour Papa". La somme est 0x81.
Entrer la chaine "Banjour Papo". la somme est 0x81.

Le message n'est pas le même, mais le calcul de la somme donne la même valeur. Dans cet exemple, le deuxième message contient exactement les mêmes caractères, mais dans un ordre différent, ce qui produit la même somme. Pour améliorer le calcul de la somme, on peut par exemple effectuer le calcul sur 2 bytes au lieu de 1 byte. En utilisant 65536 comme modulo, ce qui permettrait de ramener la probabilité de l'échec de 1/256 à 1/65536. Malgré l'ajout d'un byte au contrôle, cette somme produira toujours des erreurs, car la formule utilisée n'est pas suffisamment aléatoire. Avec une adition simple des caractères du message, chaque byte entrant affecte approximativement un seul byte de l'addition. Ce problème peut être résolu par l'utilisation d'une formule de calcul plus sophistiquée où chaque byte en entrée peut avoir un effet sur l'ensemble du résultat de la somme.
On constate donc qu'il y a au moins deux aspects à prendre en compte pour réaliser une fonction de somme correcte.
1 - La largeur du code de contrôle pour réduire la probabilité d'erreur. Par exemple sur 32 bits la probabilité et de (½^32)
2 - Une formule qui donne à chaque byte d'entrée le potentiel de changer la valeur du code de contrôle.


Le terme "Somme ou Checksum" a été employé pour décrire la formule de contrôle, mais a maintenant pris une signification plus générale entourant des algorithmes plus sophistiqués tels que le CRC. Les algorithmes de CRC que l'on va décrire satisfont très bien la deuxième condition, et peuvent être configurés pour fonctionner avec plusieurs largeurs de somme. Ils sont basés sur de simples calculs d'arithmétique modulaire.

4. La division binaire

L'idée fondamentale des algorithmes de CRC est simplement de traiter le message comme une grande valeur numérique, de le diviser par une valeur fixe, de prendre le reste de la division en tant que somme de contrôle.
La valeur numérique du message est bien supérieure à la valeur maximum d'un mot de 32 bits de large. Nous allons donc faire la division Byte par Byte mais cette fois en binaire. La division binaire ressemble beaucoup à une division euclidienne comme on l'apprend à l'école, le résultat donne un quotient et un reste. Le calcul bit à bit se fait par un "ou exclusif" (encore appelé XOR).

XOR 1 0
1 0 1
0 1 0


La division binaire


Image non disponible




On sélectionne le bit de poids fort du dividende, ici (1), il sera le bit de poids fort du quotient. Maintenant, on peut faire (1 x diviseur) = 10011. On fait ensuite un XOR entre le dividende et ce résultat qui est décalé de suffisamment de bits pour commencer au début du dividende. Ensuite, on ajoute au résultat de l'opération le bit suivant correspondant au dividende.
L'opération s'enchaîne ainsi de suite. On sélectionne le bit de poids fort du résultat précédemment trouvé, ici, c'est un 1. On lui applique un XOR avec 10011. Le résultat sera 0000, auquel on fera descendre 1, ce qui donne 00001. Le bit de poids fort est un 0, donc (0x diviseur)=00000. On refait l'opération avec le XOR. Ces opérations seront réalisées jusqu'à ce qu'on ne puisse plus " descendre " de bit depuis le dividende, donc on réalise (Taille du dividende-Taille du diviseur+1) opérations (en fait, la taille correspondant au nombre de bits).

5. Les polynômes

Le mot qui revient souvent en traitant les algorithmes de CRC est " POLYNOME ". On dira qu'un algorithme de CRC donné utilise un polynôme particulier. Comme dans la division binaire, le résultat donne un quotient et un reste, mais vus comme un polynôme avec un coefficient binaires.
Un polynôme P(x) est une fonction de x de la forme :

P(x) = FN * XN + FN-1 * XN-1 + ... + F0 * X0

Les parties Fi sont membres de l'ensemble des réels et sont appelées " Facteur " d'indice i. N est le " Degré " du polynôme (l'exposant le plus élevé). La partie Fk * Xk est appelée terme de degré k du polynôme.
La division de polynômes est une opération mathématique donnant comme résultat le calcul de deux valeurs polynomiales : un Quotient et un Reste (division euclidienne). Le degré du Reste est égal au plus à celui du Diviseur moins 1.

Dividende = Quotient * Diviseur + Reste

Exemple:

23 (Décimale) = 17 (Hexa) = 10111 (Binaire)

Ce qui correspond au polynôme :

1*x^4 + 0*x^3 + 1*x^2 + 1*x^1 + 1*x^0
Ou plus simplement
x^4 + x^2 + x^1 + x^0

En utilisant cette technique, le message, et le diviseur peuvent être représentés comme des polynômes et nous pouvons faire notre calcul juste comme avant. Par exemple, pour multiplier 1101 par 1011. Nous pouvons le faire simplement en multipliant les polynômes

(x^3 + x^2 + x^0) (x^3 + x^1 + x^0)
=
(x^6 + x^4 + x^3+ x^5 + x^3 + x^2+ x^3 + x^1 + x^0)
=
x^6 + x^5 + x^4 + 3*x^3 + x^2 + x^1 + x^0

6. Fonctionnement

Mettre en application un algorithme de calcul de CRC revient à effectuer une division. Mais nous ne pouvons pas directement effectuer cette division. Nous allons donc effectuer une division, octet par octet, de la trame d'entrée. Le récepteur de la trame divisera la somme des octets de la trame + la valeur du CRC par le même polynôme. Si le reste est NULL, c'est que la trame est correctement reçue.

Le choix du polynôme générateur est fonction de la qualité recherchée : le plus simple est d'utiliser ceux qui sont déjà définis.

CRC16 CCITT x16 + x12 + x5 + 1
CRC32 (Ethernet) x32 + x26 + x23 + x22 + x16 + x12 + x11 + x10 + x8 + x7 + x5 + x4 + x2 + x1 + 1

Donc pour effectuer cette division, nous allons créer un " Registre de division " auquel nous passerons les octets de la trame. Un octet = 8 Bits, Bit7 = MSB, Bit0 = LSB. Par exemple avec un registre de 4Bits de large.

  3 2 1 0 Bits
Sortie         Entrée
1 0 0 1 1 Polynôme


Traitement
Sélectionnez

Charger le Registre de division avec des 0
Faire
		Décaler le registre de 1 bit vers la gauche
		Si (Bit en sortie = 1) Alors
			Registre = Registre XOR Polynôme
		FinSi
Tant Que (Il reste des bits)

A la fin du calcul, le registre de division contient le reste de la division.

7. Implémentation d'un registre de division en langage C

Registre de division pour faciliter le calcul des CRC16
Sélectionnez

typedef union {
   TU_int32 Value;          	// Valeur sur 32 Bit
   struct {
      TU_int8  Tail;        	//  8 bits Entrée du registre
      TU_int16 Remainder;   	// 16 bits Reste de la division
      TU_int8  Head;        	//  8 bits Sortie du registre
   } Part;
}TCRC16_Part;
// Registre de Division
static TCRC16_Part _CCITTCrc16;
Valeur du polynôme CRC16 CCITT
Sélectionnez

const int CRC_POLYNOMIAL = 0x1021; // Specifique CRC16-CCITT
const int CRC_INIT_VALUE = 0xFFFF; // Specifique CRC16-CCITT
Division des bits de la trame
Sélectionnez

void __fastcall OneByteCrc16(TU_int8 Byte)
{
    int ByteCnt = 8;
    //--- Stocker la nouvelle valeur a la fin
    _CCITTCrc16.Part.Tail = Byte;
    //--- Test des bit du Byte
    do {
        //--- Décalage de 1 bit vers la Gauche
        _CCITTCrc16.Value <<= 1;
        //--- Si le bit est à 1
        if (_CCITTCrc16.Part.Head & 0x01)
            //--- XOR avec le POLYNOME
            _CCITTCrc16.Part.Remainder ^= CRC_POLYNOMIAL;
    } while (--ByteCnt);
}
Boucle de lecture de la trame
Sélectionnez

TU_int16 __fastcall StringToCCITTCrc16(AnsiString S)
{
    //--- Reste de la division initiale 
    _CCITTCrc16.Part.Remainder = CRC_INIT_VALUE;
    //--- Boucle de lecture de la chaine
    for (int i = 1; i <= S.Length(); i++)
        OneByteCrc16(S[i]);
    //--- Ajouter 0
    OneByteCrc16(0x00);
    //--- Polynome = 16
    OneByteCrc16(0x00);
    //--- Retourne le reste de la division sur 16 bits du CRC16 
    return _CCITTCrc16.Part.Remainder;
}

Avec un dessin on comprend mieux

Registre de division


Image non disponible
Initialisation
Sélectionnez

_CCITTCrc16.Part.Remainder = CRC_INIT_VALUE
Image non disponible
Le premier octet = A 0x41 0b01000001
Sélectionnez

_CCITTCrc16.Part.Tail = Byte;
Image non disponible
Boucle de décalage du registre de division
Sélectionnez

_CCITTCrc16.Value <<= 1;
Image non disponible
Test du Bit0 de Head
Sélectionnez

if (_CCITTCrc16.Part.Head & 0x01)
Image non disponible
XOR du reste de la division avec le POLYNOME
Sélectionnez

_CCITTCrc16.Part.Remainder ^= CRC_POLYNOMIAL;
Image non disponible
Nouvelle valeur du registre
Image non disponible

Et ainsi de suite pour les 8 bits de chaque octet de la trame.

8. Programme de test

Démarrer le programme CalcCRC.
Sélectionner dans le combo type de calcul CRC16 selon CCITT
entrer une chaîne de caractères est cliquer sur calculer

Image non disponible


Le programme affiche la valeur calculée et le reste de la division pour chaque caractère en entrée.
Vous pouvez également effectuer des calculs suivant un polynôme spécifique. Dans le combo type de calcul, sélectionner " CRC16 selon paramètre ", entrer une valeur pour le polynôme ainsi que pour la valeur initiale.

Image non disponible

9. Téléchargement

10. Conclusion

A suivre la version de calcul des CRC dit "Table-Driven", dès que j'aurais un peu plus de temps.
Bonne lecture est à bientôt sur developpez.com

Merci à BWP-Necromance pour son aide.
Merci à Rupella pour la correction sur le dessin, aprés un XOR
DVSoft