Le TP est à rendre, avant le mercredi 19 novembre à 23h59...
Le rendu de TP se fera uniquement à travers l'interface web TPLab. Vous devrez fournir un unique fichier .c
contenant le code des fonctions demandées.
Vous pouvez utiliser le fichier Makefile
fournit pour faciliter la compilation.
Certaines questions appellent à une réponse que vous pouvez mettre en commentaire dans votre fichier principal.
Attention : les points suivants ne rapportent rien, mais ne pas les respecter pourra retrancher jusqu'à 10 points sur la note finale :
tp1-nom.c
" dans le répertoire "info-528-TP2
",
tp2-nom.c
",
-Wall -Werror -Wextra -pedantic -std=c99
,
Le seul fait que votre programme fonctionne ne suffit pas pour avoir une bonne note. Les points suivants seront pris en compte :
Nous avons vu dans le TP1 que les QR codes (version 1) contenaient 26 octets de données. Parmi ces 26 octets, certains sont réservés à de la redondance d'information permettant de corriger certaines erreurs qui auraient pu survenir lors de la lecture du code : zones d'ombres, parties manquantes, déformation de l'image, etc.
Cette correction est souvent détournée à des fins commerciales ou artistiques en modifiant un QR code existant pour incorporer messages, logos, etc. (exemples du QR codes personnalisés sur Google images).
La norme des QR codes propose 4 niveaux de correction d'erreurs :
L
, qui permet en général de corriger 7% d'erreurs,
M
, qui permet en général de corriger 15% d'erreurs,
Q
, qui permet en général de corriger 25% d'erreurs,
H
, qui permet en général de corriger 30% d'erreurs.
Le taux de correction utilisé est codé dans 2 des bits du format (cf TP1) :
01
pour le niveau L
,
00
pour le niveau M
,
11
pour le niveau Q
,
10
pour le niveau H
.
Le format est codé sur 15 bits, répétés à deux endroit dans le code. Sur ces 15 bits, seulement 5 sont des bits de données, et les 10 autres sont des bits de redondances. Les bits de redondances sont calculés grâce à un code « BCH » qui peut corriger 3 bits arbitraires du format.
Comme il n'y a que 25 = 32 possibilités de format, le plus simple en cas d'erreurs détectées sur les 15 bits du format est simplement d'essayer chacun des 32 formats corrects et de garder celui qui diffère le moins du format erroné.
Écrivez la fonction
int correct_format(bit F[15]);
qui essaie de corriger un format (récupéré avec votre fonction get_format_bits
du TP1) de 15 bits.
Vous devrez :
encode_format(const bit format_data[5], bit F[15])
)
int hamming_format(bit *F1, bit *F2, int n)
Attention, s'il y a deux formats valides distincts à distance minimale du format donné en argument, vous ne pourrez donc pas choisir le format valide adéquat. La fonction devra donc renvoyer -1
.
Pour tester:
Modifiez la fonction get_format
du fichier tp1-nom.c
pour qu'elle applique la correction du format tout de suite après l'appel à la fonction get_format_bits
. (Il est important de faire cette correction avant de récupérer les valeurs du taux de correction et le numéro du masque !)
Pour les QR codes de version 1, sur les 26 octets de données :
L
(correction_level
à 1
), 7 sont des octets de redondance, permettant de corriger 3 octets erronés,
M
(correction_level
à 0
), 10 sont des octets de redondance, permettant de corriger 5 octets erronés,
Q
(correction_level
à 3
), 13 sont des octets de redondance, permettant de corriger 6 octets erronés,
H
(correction_level
à 2
), 17 sont des octets de redondance, permettant de corriger 8 octets erronés.
Ce nombre est accessible à travers le tableau NB_DATA_BYTES
qui donne le nombre d'octets de données pour une valeur de correction_level
. (Attention, le nombre d'octets de redondances est égal à 26
moins le nombre de d'octets de données...)
Ces données contiennent suffisamment d'information pour coder :
Les 26 octets de données sont considérés comme les coefficients d'un polynôme de degré 25. La case 0
correspond au coefficient du monôme de degré 25, la case 1
au coefficient du monôme de degré 24, etc.
Les syndromes de ce polynôme sont les valeurs qu'il prend sur les octets 0x02
i pour les valeurs de i entre 0 (compris) et le nombre d'octets de redondance (pas compris). Il y a donc autant de syndromes que d'octets de redondance.
Si les octets de donnés sont corrects, alors tous les syndromes sont égaux à 0x00
.
Les opérations sur les octets se font grace à :
^
" en C),
mult_coeff
,
div_coeff
,
pow_coeff
.
Notez bien que la soustraction est identique à l'addition, c'est à dire au XOR bit à bit.
Programmez la fonction
byte eval_poly(const byte *P, int d, byte x);
qui évalue un polynôme (donné par le tableau P
) de degré d
sur une valeur x
en utilisant les opération sur les octets.
Programmez ensuite la fonction
void data_syndrome(const byte *data, int n, byte *S);
qui calcule les n
syndromes correspondants aux données contenues dans data
(polynome de degré 25).
Pour tester :
Et un exemple du TP1 :
fichier | exemple-ASCII.pbm |
nombre de syndromes | 7 |
syndromes | 00, 00, 00, 00, 00, 00, 00 |
Note : pour obtenir le nombre d'octets de redondances (et donc le nombre de syndromes à calculer), il faut récupérer le taux de correction (fonction get_format
) et prendre la valeur correspondante dans le tableau NB_DATA_BYTES
.
Le pivot de Gauss est une méthode pour résoudre les systèmes d'équations linéaires. L'idée est de transformer la matrice des coefficients du système d'équations par une suite d'étapes élémentaires pour obtenir une matrice où l'on peut directement lire les solutions.
Les opérations élémentaires sont :
La fonction
int solve_linear_system(int height, int width, byte **M, byte *X);
est déjà programmée dans le fichier fourni. Elle utilise trois fonctions auxiliaires, dont
static void divide_row(int width, byte *R, byte c);
et
static void add_row(int width, const byte *R1, byte *R2);
Complétez ses fonctions pour que solve_linear_system
fonctionne correctement.
0x00
, c'est que le système d'équations est sous spécifié : il risque d'avoir un nombre de solution infini. La fonction renvoie -1
dans ce cas.
Pour tester :
byte tmp[3][4] = { { 0x20, 0x31, 0x07, 0x36}, { 0x12, 0xfa, 0xff, 0x88}, { 0xa5, 0x00, 0x76, 0xa8} };
X[0] = 0x5c X[1] = 0x45 X[2] = 0xce
Pour des raisons techniques, il faut appeler la fonction solve_linear_system
avec une matrice allouée avec malloc
car tmp
n'est pas un pointeur de pointeurs. Le plus simple est de déclarer ensuite la matrice M
en recopiant tmp
dedans de la manière suivante :
int width = 4; int height = 3; byte **M = malloc(height*sizeof(byte*)); for (i=0; i<height; i++) M[i] = malloc(width*sizeof(byte)); for (i=0; i<taille+1; i++) for (j=0; j<taille+1; j++) M[i][j] = tmp[i][j]; byte *result = malloc(sizeof(byte)*(height)); int r = solve_linear_system(height, width, M, result);
byte tmp[3][4] = { { 0x00, 0x00, 0x01, 0xaa}, { 0x00, 0x01, 0x00, 0xbb}, { 0x01, 0x00, 0x00, 0xcc}, };
X[0] = 0xcc X[1] = 0xbb X[2] = 0xaa
byte tmp[4][4] = { { 0x00, 0x57, 0x4a, 0x20}, { 0x20, 0x31, 0x07, 0x36}, { 0x12, 0xfa, 0xff, 0x88}, { 0xa5, 0x00, 0x76, 0xa8}, };
X[0] = 0x5c X[1] = 0x45 X[2] = 0xce
height
)
byte tmp[4][4] = { { 0x01, 0x57, 0x4a, 0x20}, { 0x20, 0x31, 0x07, 0x36}, { 0x12, 0xfa, 0xff, 0x88}, { 0xa5, 0x00, 0x76, 0xa8}, }
S'il y a des erreurs en nombre raisonnable, il est possible de construire un polynôme localisateur d'erreurs. Son degré est exactement le nombre d'erreurs. Si le taux de correction permet de corriger au plus t erreurs, c'est à dire que le nombre d'octets de redondances est des 2t ou 2t+1, on construit la matrice suivante :
S[0] S[1] ... S[t-1] S[t] S[1] S[2] ... S[t] S[t+1] ... ... ... ... ... ... S[t-1] S[t] ... S[2t-2] S[2t-1]
Cette matrice est construite à partir des syndromes et a t lignes et t+1 colonnes.
Pour trouver le nombre d'erreurs ainsi que les coefficients du polynôme localisateur, il faut trouver le plus gros système de cette forme qui a une unique solution :
t = t-1
et on recommence...
Attention : s'il y a e erreurs, le polynôme localisateur est de degré e. La procédure décrite plus haut donne seulement les coefficients des monômes de degrés strictement positif. Le coefficient constant vaut toujours 1
.
Programmez la fonction
int error_locator(int nb_correction_bytes, const byte *S, byte *L);
qui cherche le nombre d'erreurs en utilisant la méthode décrite plus haut.
nb_correction_bytes
est le nombre d'octets de redondances,
S
est le tableau des syndromes (de taille nb_correction_bytes
),
L
est un tableau (déjà alloué) qui contiendra les coefficients du polynôme localisateur.
La fonction renvoie le nombre d'erreurs.
Pour tester :
Le polynôme localisateur L a la propriété surprenante que L(2-i) = 0 si et seulement si le coefficient de degré i du message (c'est à dire l'octet 25-i) est erroné.
Pour trouver les endroits où L s'annule, il suffit donc de calculer (avec la fonction eval_poly
) le polynôme L sur 20, 2-1, ..., 2-25.
Programmez la fonction qui calcule les positions des erreurs :
int find_locations(int nb_errors, const byte *L, byte *X);
nb_errors
est le nombre d'erreurs détectées (question précédente),
L
contient les coefficients du polynôme localisateur,
X
est un tableau (alloué précédemment) qui contiendra les position des erreurs.
Note : la fonction inv_coeff
vous permet de calculer l'inverse d'un octet ; 2-i est donc simplement inv_coeff(pow_coeff(2,i))
.
Pour tester :
Maintenant que l'on connait la position des erreurs, on peut chercher la valeur des erreurs. Pour ça, il suffit de résoudre le système donné par la matrice
1 1 ... 1 S[0] x0 x1 ... xe S[1] x0^2 x1^2 ... xe^2 S[2] x0^3 x1^3 ... xe^3 S[3] ... ... ... ... ... ... x0^(n-1) x1^(n-1) ... xe^(n-1) S[n-1]
où
x0
, x1
, ... xe
sont les inverses des zéros du polynôme localisateur (autrement dit, x0 = pow_coeff(2,X[0])
, etc.)
n
est le nombre d'octets de redondance.
Programmez la fonction qui calcule les valeurs des erreurs
int error_values(int nb_correction_bytes, int nb_errors, const byte *S, const byte *X, byte *V);
avec
nb_correction_bytes
est le nombre d'octets de redondance,
nb_errors
est le nombre d'erreurs détectées,
S
est le tableau des syndromes (de taille nb_correction_bytes
),
X
est le tableau de position des erreurs (de taille nb_errors
),
V
est le tableau (déjà alloué) qui contiendra les valeurs des erreurs.
La fonction renvoie 0
si elle a trouvée une unique solution au système (normal) et -1
sinon (c'est qu'il y a un problème).
Pour tester :
fichier | exemple-TP2-1.pbm |
exemple-TP2-2.pbm |
exemple-TP2-3.pbm |
valeurs des erreurs | 24, 46, 93, 05, c2, a0 |
87, 08, 52, 04, b0 |
80, d0, 16, 50, f0, c9, 45 |
Pour corriger, il suffit maintenant de soustraire (avec le XOR bit à bit) chacune des valeurs au coefficient du polynôme des données correspondant (le tableau data
du TP1).
Programmer la fonction
int correct_data(byte *data, int correction_level);
qui utilisent toutes les fonctions précédentes pour corriger le tableau data
.
Pour tester :
Modifiez les fonctions
void info_file(const char *filename, int margin_size, int module_size); void decode_file(const char *filename, int margin_size, int module_size);
du TP1 pour utiliser la correction d'erreurs.