Escribe un adaptador de flujo que contenga una referencia a una clave y 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 en 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 entre un documento y 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.
Aplicar XOR entre un documento y una clave se usó para 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 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 por XOR es que es simétrico: aplicar XOR de cualquier número consigo mismo produce 0, y aplicar XOR de cualquier número con 0 devuelve el número de entrada sin cambios. Por lo tanto, para descifrar un documento que fue cifrado con XOR, vuelve a cifrarlo con XOR usando la misma clave.
No es práctico escribir una prueba que verifique que tu struct contiene una referencia a la clave en lugar de copiarla. Del mismo modo, tampoco es práctico demostrar con una prueba que ni munge ni munge_in_place, ni ninguna de sus funciones auxiliares, asignan memoria en el heap. Aun así, deberías intentar escribir tu solución de esta manera.
Tendrás que escribir un struct Xorcism que contenga una referencia a una clave. Ese 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 nuevo iterador de datos.
Las firmas de los stubs de este ejercicio son en gran medida correctas en cuanto a sintaxis, pero no compilan: gran parte del objetivo de este ejercicio es que te familiarices con el uso de tiempos de vida 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 agregar funciones auxiliares!
Estos traits te serán útiles:
Este ejercicio incluye pruebas adicionales, detrás del flag de funcionalidad io. Para habilitarlas, ejecuta
cargo test --features io
Para estas pruebas, 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
Cada una de estas funciones convierte el struct Xorcism en un adaptador de flujo en la dirección correspondiente. Usan estos traits:
munge
Debido al uso de la funcionalidad impl Trait, la gestión de los tiempos de vida puede ser un poco complicada cuando implementas el método munge. Puede que te resulte más fácil escribir tu propia struct con una implementación de Iterator y devolver ese tipo concreto, al menos para empezar. A fin de cuentas, 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.