Escribe un adaptador de stream que contenga una referencia a una clave y que le aplique XOR bit a bit con datos arbitrarios.
XOR es una operación binaria fundamental: para cada bit de las entradas, pon el bit
correspondiente de la salida a 1 si los bits de entrada son diferentes. Si ambas
entradas son 1 o ambas son 0, el bit correspondiente de la salida es 0.
Cuando se aplica XOR a un documento con una clave, la clave se repite tantas veces como sea necesario, lo que produce un documento de salida de la misma longitud que el documento de entrada.
El XOR de un documento con una clave se ha usado en criptografía hasta principios del siglo XX. Aunque está completamente obsoleto como método para ocultar datos, puede resultar sorprendentemente útil para generar datos ruidosos que parecen aleatorios sin necesidad de complicarse con la aleatoriedad verdadera. Todavía se usa de vez en cuando en la criptografía moderna para ciertos cifrados: el cifrado en sí no es más que un mecanismo para generar una clave muy aleatoria e infinitamente larga, a la que se le aplica XOR con el documento.
Una propiedad interesante del cifrado XOR es que es simétrico: aplicar XOR de un número
consigo mismo produce 0, y aplicar XOR de un número con 0 devuelve el número de
entrada sin cambios. Por lo tanto, para descifrar un documento que se ha cifrado con
XOR, vuelve a cifrarlo con XOR usando la misma clave.
No es práctico escribir un test que garantice que tu struct contiene una referencia a la
clave en lugar de copiarla. Asimismo, tampoco es práctico demostrar con un test que ni
munge ni munge_in_place, ni ninguna de sus funciones auxiliares, reservan memoria en
el heap. No obstante, deberías intentar escribir tu solución de esta manera.
Tendrás que escribir una struct Xorcism que contenga una referencia a una clave. Esa
struct debe proporcionar dos métodos: munge_in_place y munge. El primero ajusta un
búfer de bytes in situ. El segundo es un adaptador de iterador: acepta un iterador de
datos arbitrario y devuelve un iterador de datos nuevo.
Las firmas del esqueleto de este ejercicio son en gran medida correctas sintácticamente, pero no compilan: gran parte del objetivo de este ejercicio es que te familiarices con el uso de lifetimes y genéricos, así que tendrás que completarlas por tu cuenta. Otro objetivo de este ejercicio es que descubras una factorización adecuada que te permita implementar ambos métodos con una duplicación de esfuerzo mínima. ¡No tengas miedo de añadir funciones auxiliares adicionales!
Estos traits te resultarán útiles:
Este ejercicio contiene tests adicionales, tras la feature flag io. Para activarlos,
ejecuta
cargo test --features io
Para estos tests, tendrás que implementar un método reader con la firma
fn reader(self, impl Read) -> impl Read
y un método writer con la firma
fn writer(self, impl Write) -> impl Write
Estas funciones convierten la struct Xorcism en un adaptador de stream en la dirección
adecuada. Usan estos traits:
munge
Debido al uso de la característica impl Trait, la gestión de los tiempos de vida puede resultar un poco complicada al implementar el método munge. Puede que te resulte más fácil escribir una struct propia con una implementación de Iterator y devolver ese tipo concreto, al menos para empezar. En última instancia, es buena idea intentar implementar la solución usando directamente los combinadores de Iterator.
Regístrate en Exercism para aprender y dominar Rust con 99 ejercicios y mentoría humana real, todo gratis.