rapport - hash / aggregation tlv / fautes d'ortographe

This commit is contained in:
GONZALEZ ARENAS felipe 2020-05-08 16:58:06 +02:00
parent 28102c9b0b
commit 880e719ff4

View File

@ -10,15 +10,15 @@ By 人民画报 - 《人民畫報》1967年, Public Domain, <a href="https://com
## Abstract
Dazi bao est l'implémentation d'un protocol de commérage, qui permet de publier un message, ce dernier est envoyé de proche en proche à chaque pair du réseau.
Dazi bao est l'implémentation d'un protocole de commérage, qui permet de publier un message, ce dernier est envoyé de proche en proche à chaque pair du réseau.
L'utilisation se fait depuis un terminal, en ligne de commande.
## Structure et déroulement
Le programme est structuré autour du fichier `node.c`, dans lequel se trouve la fonction main. Cette fonction va se charger d'appeler les fonctions d'initialisation, et de lancer le protocol. La construction des TLV, la production d'un hash et les fonctions de gestion et d'affichage d'erreurs sont dans leurs propre fichier, car ce sont des points qui peuvent être amené à changé lors de l'implémentation d'extensions.
Le programme est structuré autour du fichier `node.c`, dans lequel se trouve la fonction main. Cette fonction va se charger d'appeler les fonctions d'initialisation, et de lancer le protocole. La construction des TLV, la production d'un hash et les fonctions de gestion et d'affichage d'erreurs sont dans leurs propres fichiers, car ce sont des points qui peuvent être amenés à changer lors de l'implémentation d'extensions.
Le noeud n'a pas de mémoire, et va donc être intialisé à partir de 0 à chaque lancement. Entre le lancement, et le moment où l'on connaît l'ensemble des messages publiés ( avec le site web comme référence, bien que ça ne soit pas forcément valide), le temps de convergance est en dessous d'une seconde.
Le noeud n'a pas de mémoire, et va donc être intialisé à partir de 0 à chaque lancement. Entre le lancement, et le moment où l'on connaît l'ensemble des messages publiés (avec le site web comme référence, bien que ça ne soit pas forcément valide), le temps de convergance est en dessous d'une seconde.
### Structure
@ -26,47 +26,55 @@ Le programme se découpe en 2 parties ; l'initialisation, puis la récupération
### Initialisation
Lorsque l'on éxécute le programme, on donne une URL ainsi qu'un numéro de port. Cette URL va être décodée par `getaddrinfo`, qui va nous donner >1 adresses IP, qui peuvent être en format IPv4 ou IPv6. Pour chaque adresse IP, nous crééons un pair, que l'on va marqué comme permanent. On y associe le numéro de port donné lors du lancement. Nous crééons aussi le socket que l'on utilisera ensuite. Nous le fixons sur le port 1212.
Lorsque l'on éxécute le programme, on donne une URL ainsi qu'un numéro de port. Cette URL va être décodée par `getaddrinfo`, qui va nous donner >1 adresses IP, qui peuvent être en format IPv4 ou IPv6. Pour chaque adresse IP, nous crééons un pair, que l'on va marquer comme permanent. On y associe le numéro de port donné lors du lancement. Nous crééons aussi le socket que l'on utilisera ensuite. Nous le fixons sur le port 1212.
Par soucis de simplicité, et dans l'espoir que IPv4 disparaisse rapidement des internet, nous traitons tout les pairs comme étant en IPv6. Les adresses IPv4 sont _IPv6 mapped_. Une fois le programme lancé, on ne peux plus ajouter de pair permanent, ni les supprimer. On pourrait étendre le programme en permettant d'ajouter les pairs de façon dynamique, en les ajoutant ou supprimant. On pourrait aussi choisir de "bloquer" un pair, mais cela reste impossible en l'étant, voir la note 1.
Nous initialisations aussi la liste des messages, en créeant un permier message, vide. Il est vide, pour plusieurs raisons : d'une part, lorsque l'on lance le programme, on peux ne pas avoir envie de publier quoique ce soit, et de juste recevoir les messages. On peux aussi avoir envie d'effacer les messages publiés précédement. Lors de la publication de notre message, il remplacera les messages précédents connu des pairs par une phrase vide, ce qui effacera le message. À noter que, vu qu'il n'existe pas, dans le protocol de base, d'authentification, ça peux aussi être un moyen d'effacer tout les messages du réseau.¹
Nous initialisations aussi la liste des messages, en créeant un permier message, vide. Il est vide, pour plusieurs raisons : d'une part, lorsque l'on lance le programme, on peux ne pas avoir envie de publier quoique ce soit, et de juste recevoir les messages. On peux aussi avoir envie d'effacer les messages publiés précédement. Lors de la publication de notre message, il remplacera les messages précédents connu des pairs par une phrase vide, ce qui effacera le message. À noter que, vu qu'il n'existe pas, dans le protocole de base, d'authentification, ça peux aussi être un moyen d'effacer tout les messages du réseau.¹
La liste de pair et la liste de messages sont des listes chainés simple. Elles sont suffisante, car permettent l'ajout et la suppression. Lors de l'envoie de messages, nous devons parcourir toute la liste des pairs, et lors de la réception ou l'inondation de message, nous devons aussi passer en revue tout les messages.
La liste de pairs et la liste de messages sont des listes chainées simples. Elles sont suffisantes, car permettent l'ajout et la suppression. Lors de l'envoi de messages, nous devons parcourir toute la liste de pairs, et lors de la réception ou l'inondation de messages, nous devons aussi passer en revue tous les messages.
Une extension possible serait de stocker le hash de chaque message, afin d'avoir une hash map. On pourrait donc vérifier uniquement le hash, sans avoir besoin de le recalculer. Cela réduirait aussi fortement le temps de mise à jour et d'accès aux messages.
### Déroulement
Le noeud va ensuite initiliser une structure `pollfd`, un variable de délais, ainsi que deux tableau de char. Ces derniers font 1024 bytes, ce qui est recommandé dans le projet. C'est suffisant, car un paquet ne peux pas faire plus de 1024 bytes, et nous traitons les paquets les uns à la suite des autres. La structure `pollfd` va servir lors de l'appel à la fonction `poll`, qui se charge de surveiller deux descripteurs de fichiers : `stdin`, et le socket définit lors de l'initialisation. Quand des données sont écrites dans ces fichiers, nous effectuons une lecture.
Le noeud va ensuite initiliser une structure `pollfd`, une variable de délai, ainsi que deux tableaux de char. Ces derniers font 1024 bytes, ce qui est recommandé dans le projet. C'est suffisant, car un paquet ne peux pas faire plus de 1024 bytes, et nous traitons les paquets les uns à la suite des autres. La structure `pollfd` va servir lors de l'appel à la fonction `poll`, qui se charge de surveiller deux descripteurs de fichiers : `stdin`, et le socket définit lors de l'initialisation. Quand des données sont écrites dans ces fichiers, nous effectuons une lecture.
La lecture de stdin premet de récupérer à la fois des commandes pour la gestion du noeud, ainsi que le message à publier. La lecture du socket nous permet de récupérer les paquets entrant.
La lecture de stdin premet de récupérer à la fois des commandes pour la gestion du noeud, ainsi que le message à publier. La lecture du socket nous permet de récupérer les paquets entrants.
La variable de délais est utilisé pour mettre à jour la liste de pair, demander plus de pair le cas échéant, et demander l'état du réseau aux autres pairs. Nous l'initialisation à une valeur inférieure à 20 secondes, car lors du lancement, nous ne connaissons rien du réseau. Pas la peine d'attendre. Une fois la première éxécution passée, nous mettons un délais de vingt à trentes secondes. C'est suffisant, car la publication de nouveaux messages sur le réseau est assez lente. Nous pourrions faire évoluer ce délais en fonction du rythme de publication de nouveaux messages.
La variable de délai est utilisée pour mettre à jour la liste de pairs, demander plus de pairs le cas échéant, et demander l'état du réseau aux autres pairs. Nous l'initialisons à une valeur inférieure à 20 secondes, car lors du lancement, nous ne connaissons rien du réseau. Pas la peine d'attendre. Une fois la première éxécution passée, nous mettons un délais de vingt à trentes secondes. C'est suffisant, car la publication de nouveaux messages sur le réseau est assez lente. Nous pourrions faire évoluer ce délais en fonction du rythme de publication de nouveaux messages.
#### Ajouts de nouveaux messages
L'ajout de nouveaux messages est fait uniquement si on écrit un message non-vide. Dans le cas contraire, on affiche les messages connus.
On va ensuite appeler la fonction `add_message(char * message, int message_len)`. Elle se charge de vérifier qu'on n'ajoute pas un message vide, puis va chercher le message publié par notre propre identifiant. On mets à jour le numéro de séquence, et l'on copie le nouveau message dans la liste. Afin d'éviter des erreures, on libère l'ancienne allocation, et on alloue une nouvelle mémoire de la taille du nouveau message.
On va ensuite appeler la fonction `add_message(char * message, int message_len)`. Elle se charge de vérifier qu'on n'ajoute pas un message vide, puis va chercher le message publié par notre propre identifiant. On met à jour le numéro de séquence, et l'on copie le nouveau message dans la liste. Afin d'éviter des erreurs, on libère l'ancienne allocation, et on alloue une nouvelle mémoire de la taille du nouveau message.
Ce message sera envoyé aux pairs en faisant la demance lors de l'inondation.
Ce message sera envoyé aux pairs en faisant la demande lors de l'inondation.
#### Réception des paquets et traitement
Lors de la réception d'un message, on effectue plusieurs vérifications. La première consiste à vérifier l'entête du paquet. Une fois cette entête validée, on ajoute le pair à la liste des pairs connus.
On passe ensuite à la validation de chaque TLV. On commence par créer un paquet vide, qui va nous servir lors du renvoie éventuel de paquet vers les pairs. Ensuite, l'un à la suite des autres, nous validons les TLVs. Une fois qu'un TLV est valide, nous le traitons. Si nous avons besoin de renvoyer un paquet, nous allons soit le renvoyer directement, soit agrégé des TLVs dans un paquet, qui se remplira jusqu'a être pleins, suite à quoi il sera envoyé.
On passe ensuite à la validation de chaque TLV. On commence par créer un paquet vide, qui va nous servir lors du renvoi éventuel de paquet vers les pairs. Ensuite, l'un à la suite des autres, nous validons les TLVs.
#### Gestion et génération des hashs.
Une fois qu'un TLV est valide, nous le traitons. Si nous avons besoin de renvoyer un paquet, nous allons soit envoyer un seul TLV dans un paquet dans certains cas spécifiques, soit aggréger des TLVs dans un paquet. Celui-ci servira de buffer pour la fonction d'envoi, qui sera appelée lorsque le paquet ne peux plus accueillir de nouveaux TLV, suite à quoi le paquet sera réinitialisé afin de pouvoir le réutiliser pour des futurs envois.
?
Après avoir traité tous les TLVs reçus, nous enverrons le paquet courrant, qui peut encore contenir des TLVs non envoyés.
#### Gestion et génération des hashes.
On utilise la librairie OpenSSL pour générer notre hash SHA256. Dans le cas de hash d'un seul noeud, on concatenne les données id, seqno et data (en faisant attention à convertir id et seqno en big endian) dans un même buffer et on le passe à la fonction de hashage.
Dans le cas d'un network hash, on concatenera dans un grand buffer tous les hashes de chaque donnée connue (qui sont ajoutées peu à peu dans une liste chainée de façon à ce qu'elle soit en ordre croissant par rapport à l'id) puis nous passerons ce buffer à la fonction de hashage.
On n'utilisera que les 16 premiers bytes de ces hashes.
## Choix d'implémentation
### TLV
Les TLVs sont représentés par un type `union TLV`, qui contiens des pointeurs vers le `struct` TLV en lui-même.
Les TLVs sont représentés par un type `union TLV`, qui contient des pointeurs vers le `struct` TLV en lui-même, différent pour chaque type de TLV.
### poll()