Écris un adaptateur de flux qui contient une référence vers une clé et qui lui applique un XOR bit à bit avec des données arbitraires.
XOR est une opération binaire fondamentale : pour chaque bit des entrées, on met
le bit correspondant de la sortie à 1 si les bits d'entrée sont différents. Si
les deux entrées valent 1 ou si les deux valent 0, alors le bit de sortie
correspondant est 0.
Lorsqu'on applique un XOR entre un document et une clé, la clé est répétée autant de fois que nécessaire, ce qui produit un document de sortie de même longueur que le document d'entrée.
Le XOR entre un document et une clé a servi en cryptographie, et ce jusqu'au début du XXe siècle. Si cette méthode est complètement obsolète pour cacher des données, elle peut être étonnamment utile pour générer des données bruitées qui semblent aléatoires, sans avoir à se compliquer la vie avec du vrai hasard. Elle sert encore ponctuellement dans la cryptographie moderne, pour certains chiffrements : le chiffrement lui-même n'est qu'un mécanisme qui génère une clé très aléatoire et infiniment longue, que l'on combine par XOR avec le document.
Une propriété intéressante du chiffrement par XOR est sa symétrie : le XOR d'un
nombre avec lui-même donne 0, et le XOR d'un nombre avec 0 renvoie le nombre
d'entrée inchangé. Par conséquent, pour déchiffrer un document qui a été chiffré
par XOR, il suffit de le rechiffrer par XOR avec la même clé.
Il n'est pas réaliste d'écrire un test qui vérifie que ta structure contient une
référence vers la clé plutôt qu'une copie de celle-ci. De même, il n'est pas
réaliste de démontrer par un test que ni munge ni munge_in_place, ni aucune
de leurs fonctions auxiliaires, n'alloue sur le tas. Tu devrais tout de même
essayer d'écrire ta solution de cette façon.
Tu vas devoir écrire une struct Xorcism qui contient une référence vers une
clé. Cette structure doit fournir deux méthodes : munge_in_place et munge.
La première modifie un tampon d'octets sur place. La seconde est un adaptateur
d'itérateur : elle prend un itérateur de données quelconque et renvoie un nouvel
itérateur de données.
Les signatures fournies dans le squelette de cet exercice sont en grande partie correctes sur le plan syntaxique, mais elles ne compilent pas : une bonne partie de l'intérêt de cet exercice est de te familiariser avec l'utilisation des durées de vie et des génériques, tu vas donc devoir les compléter toi-même. Cet exercice a aussi pour but de te faire trouver une factorisation adaptée qui te permette d'implémenter ces deux méthodes avec un minimum de duplication d'effort. N'hésite pas à ajouter des fonctions auxiliaires !
Ces traits te seront utiles :
Cet exercice contient des tests bonus, cachés derrière la fonctionnalité io.
Pour les activer, exécute
cargo test --features io
Pour ces tests, tu devras implémenter une méthode reader avec la signature
fn reader(self, impl Read) -> impl Read
et une méthode writer avec la signature
fn writer(self, impl Write) -> impl Write
Ces fonctions convertissent chacune la structure Xorcism en adaptateur de flux
dans la direction appropriée. Elles utilisent les traits suivants :
munge
En raison de l'utilisation de la fonctionnalité impl Trait, la gestion des durées de vie peut être un peu délicate lors de l'implémentation de la méthode munge. On peut trouver plus simple d'écrire sa propre struct avec une implémentation de Iterator et de renvoyer ce type concret, au moins pour commencer. Au final, c'est une bonne idée d'essayer d'implémenter la solution en utilisant directement les combinateurs de Iterator.
Inscris-toi sur Exercism pour apprendre et maîtriser Rust avec 99 exercices, et un vrai mentorat humain, le tout gratuitement.