Escreva um adaptador de streaming que contém uma referência a uma chave e faz XOR bit a bit dessa chave com dados arbitrários.
XOR é uma operação binária fundamental: para cada bit nas entradas, defina o bit correspondente da saída como 1 se os bits de entrada forem diferentes. Se as duas entradas forem 1 ou as duas forem 0, o bit correspondente da saída é 0.
Ao fazer XOR de um documento com uma chave, a chave é repetida quantas vezes forem necessárias, produzindo um documento de saída com o mesmo comprimento do documento de entrada.
O XOR de um documento com uma chave foi usado em criptografia até o início do século XX. Embora esteja completamente obsoleto como método para esconder dados, pode ser surpreendentemente útil para gerar dados ruidosos que parecem aleatórios sem a complicação da aleatoriedade de verdade. Ele ainda é usado de vez em quando na criptografia moderna em certas cifras: a própria cifra é apenas um mecanismo para gerar uma chave muito aleatória e infinitamente longa, que é combinada por XOR com o documento.
Uma propriedade interessante da cifra XOR é que ela é simétrica: fazer XOR de qualquer número com ele mesmo produz 0, e fazer XOR de qualquer número com 0 retorna o número de entrada sem alteração. Portanto, para descriptografar um documento que foi cifrado com XOR, basta cifrá-lo de novo com XOR usando a mesma chave.
Não é prático escrever um teste que garanta que sua struct guarda uma referência à chave em vez de copiá-la. Da mesma forma, não é prático provar com um teste que nem munge nem munge_in_place, nem qualquer uma de suas funções auxiliares, alocam na heap. Ainda assim, você deve tentar escrever sua solução dessa forma.
Você vai precisar escrever uma struct Xorcism que guarda uma referência a uma chave. Essa struct deve fornecer dois métodos: munge_in_place e munge. O primeiro ajusta um buffer de bytes no próprio local. O segundo é um adaptador de iterador: aceita um iterador de dados arbitrário e retorna um novo iterador de dados.
As assinaturas do esqueleto deste exercício estão em grande parte corretas na sintaxe, mas não compilam: boa parte do objetivo deste exercício é você se familiarizar com o uso de lifetimes e generics, então você vai precisar preenchê-las por conta própria. Outro objetivo deste exercício é você descobrir uma fatoração adequada que permita implementar esses dois métodos com o mínimo de esforço duplicado. Não tenha medo de introduzir funções auxiliares adicionais!
Estes traits serão úteis:
Este exercício contém testes bônus, atrás da feature flag io. Para habilitá-los, rode
cargo test --features io
Para esses testes, você vai precisar implementar um método reader com a assinatura
fn reader(self, impl Read) -> impl Read
e um método writer com a assinatura
fn writer(self, impl Write) -> impl Write
Cada uma dessas funções converte a struct Xorcism em um adaptador de stream na direção apropriada. Elas usam estes traits:
munge
Devido ao uso do recurso impl Trait, o gerenciamento de tempo de vida pode ser um pouco complicado ao implementar o método munge. Talvez seja mais fácil escrever sua própria struct com uma implementação de Iterator e retornar esse tipo concreto, ao menos para começar. No fim das contas, é uma boa ideia tentar implementar a solução usando diretamente os combinadores de Iterator.
Crie sua conta no Exercism para aprender e dominar Rust com 99 exercícios e mentoria humana de verdade, tudo de graça.