From c096ca37323f4e525a7cd57650473f8251b7e6d1 Mon Sep 17 00:00:00 2001 From: Peter Goodspeed-Niklaus Date: Fri, 13 Nov 2020 08:35:21 +0100 Subject: [PATCH] create new practice exercise: xorcism (#994) * add exercise metadata * start working on tests * add source stubs * test varying input types and statefulness * update io tests appropriately * ignore all but first test (to the degree practicable) * fix write link; emphasize key repetition; rewrap * add test: munge_output_has_definite_len This test is primarily a guard against students who come up with a creative solution to the generic bounds of `munge` which accepts an input whose length is not known in advance. They may be tempted to remove the `ExactSizeIterator` bound from `MungeOutput`. This test prevents them from doing so. * fix equal input bytes * capitalize all instances of XOR in readme --- config.json | 12 + .../xorcism/.meta/ALLOWED_TO_NOT_COMPILE | 3 + exercises/xorcism/.meta/description.md | 81 +++++ exercises/xorcism/.meta/ignore-count-ignores | 2 + exercises/xorcism/.meta/metadata.yml | 3 + exercises/xorcism/Cargo.toml | 15 + exercises/xorcism/README.md | 163 ++++++++++ exercises/xorcism/example.rs | 207 ++++++++++++ exercises/xorcism/src/lib.rs | 46 +++ exercises/xorcism/tests/xorcism.rs | 303 ++++++++++++++++++ 10 files changed, 835 insertions(+) create mode 100644 exercises/xorcism/.meta/ALLOWED_TO_NOT_COMPILE create mode 100644 exercises/xorcism/.meta/description.md create mode 100644 exercises/xorcism/.meta/ignore-count-ignores create mode 100644 exercises/xorcism/.meta/metadata.yml create mode 100644 exercises/xorcism/Cargo.toml create mode 100644 exercises/xorcism/README.md create mode 100644 exercises/xorcism/example.rs create mode 100644 exercises/xorcism/src/lib.rs create mode 100644 exercises/xorcism/tests/xorcism.rs diff --git a/config.json b/config.json index 788356d6d..0f02de0c2 100644 --- a/config.json +++ b/config.json @@ -809,6 +809,18 @@ "board_state" ] }, + { + "slug": "xorcism", + "uuid": "0520ad82-75d9-450c-9267-d9758b3b0513", + "core": false, + "unlocked_by": "luhn", + "difficulty": 7, + "topics": [ + "bitwise", + "generics", + "lifetimes" + ] + }, { "slug": "rectangles", "uuid": "cc4ccd99-1c97-4ee7-890c-d629b4e1e46d", diff --git a/exercises/xorcism/.meta/ALLOWED_TO_NOT_COMPILE b/exercises/xorcism/.meta/ALLOWED_TO_NOT_COMPILE new file mode 100644 index 000000000..399169fc9 --- /dev/null +++ b/exercises/xorcism/.meta/ALLOWED_TO_NOT_COMPILE @@ -0,0 +1,3 @@ +The point of this exercise is for students to figure out the appropriate +function signatures and generic bounds for their implementations, so we +cannot provide those. diff --git a/exercises/xorcism/.meta/description.md b/exercises/xorcism/.meta/description.md new file mode 100644 index 000000000..2e9436059 --- /dev/null +++ b/exercises/xorcism/.meta/description.md @@ -0,0 +1,81 @@ +Write a streaming adaptor which contains a reference to a key, and bitwise-XORs +it with arbitrary data. + +XOR is a fundamental binary operation: for each bit in the inputs, set the +corresponding bit of the output to `1` if the input bits are different. If both +inputs are `1` or both are `0`, then the corresponding output bit is `0`. + +When XORing a document with a key, the key is repeated as many times as +necessary, producing an output document equal in length to the input document. + +XORing a document with a key has been used for cryptography as recently as the +early 1900s. While this is thoroughly obsolete as a method for hiding data, it +can be surprisingly useful for generating noisy random-seeming data without +needing the complication of true randomness. It is still used occasionally in +modern cryptography for certain ciphers: the cipher itself is just a mechanism +for generating a very random, infinitely long key, which is XOR'd with the +document. + +One interesting property of XOR encryption is that it is symmetrical: XORing any +number with itself produces `0`, and XORing any number with `0` returns the +input number unchanged. Therefore, to decrypt a document which has been +XOR-encrypted, XOR-encrypt it again using the same key. + +## Nonallocation + +It is not practical to write a test which ensures that your struct holds a +reference to the key instead of copying it. Likewise, it is not practical to +prove with a test that neither `munge` nor `munge_in_place`, nor any of their +helper functions, allocate on the heap. Nevertheless, you should attempt to +write your solution in this way. + +## Implementation + +You will need to write a `struct Xorcism` which holds a reference to a key. That +struct must provide two methods: `munge_in_place` and `munge`. The former +adjusts a byte buffer in-place. The latter is an iterator adaptor: it accepts an +arbitrary iterator of data, and returns a new iterator of data. + +This exercise's stub signatures are largely correct in syntax, but they do not +compile: a large part of the point of this exercise is for you to get familiar +with using lifetimes and generics, so you will need to fill them in on your own. +Another goal of this exercise is for you to figure out an appropriate +factorization which enables you to implement both of those methods with minimal +duplication of effort. Don't be afraid to introduce additional helpers! + +## Useful Traits + +These traits will be useful: + +- [`AsRef`](https://doc.rust-lang.org/std/convert/trait.AsRef.html) +- [`Borrow`](https://doc.rust-lang.org/std/borrow/trait.Borrow.html) +- [`ExactSizeIterator`](https://doc.rust-lang.org/std/iter/trait.ExactSizeIterator.html) +- [`IntoIterator`](https://doc.rust-lang.org/std/iter/trait.IntoIterator.html) +- [`Sized`](https://doc.rust-lang.org/std/marker/trait.Sized.html) + +## Bonus Tests + +This exercise contains bonus tests, behind the `io` feature flag. To enable +them, run + +```sh +cargo test --features io +``` + +For these tests, you will need to implement a method `reader` with the signature + +```rust +fn reader(self, impl Read) -> impl Read +``` + +and a method `writer` with the signature + +```rust +fn writer(self, impl Write) -> impl Write +``` + +These functions each convert the `Xorcism` struct into a stream adaptor in the +appropriate direction. They use these traits: + +- [`Read`](https://doc.rust-lang.org/std/io/trait.Read.html) +- [`Write`](https://doc.rust-lang.org/std/io/trait.Write.html) diff --git a/exercises/xorcism/.meta/ignore-count-ignores b/exercises/xorcism/.meta/ignore-count-ignores new file mode 100644 index 000000000..e2bfa3aa4 --- /dev/null +++ b/exercises/xorcism/.meta/ignore-count-ignores @@ -0,0 +1,2 @@ +xorcism has tests generated by macro. +This breaks the count-ignores.sh script. diff --git a/exercises/xorcism/.meta/metadata.yml b/exercises/xorcism/.meta/metadata.yml new file mode 100644 index 000000000..03bcbd356 --- /dev/null +++ b/exercises/xorcism/.meta/metadata.yml @@ -0,0 +1,3 @@ +--- +blurb: "Implement zero-copy streaming adaptors" +source: "Peter Goodspeed-Niklaus" diff --git a/exercises/xorcism/Cargo.toml b/exercises/xorcism/Cargo.toml new file mode 100644 index 000000000..74abc7e9a --- /dev/null +++ b/exercises/xorcism/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "xorcism" +version = "0.1.0" +edition = "2018" + + +[dependencies] + +[features] +io = [] + +[dev-dependencies] +hexlit = "0.3.0" +rstest = "0.6.4" +rstest_reuse = "0.1.0" diff --git a/exercises/xorcism/README.md b/exercises/xorcism/README.md new file mode 100644 index 000000000..e6185b19c --- /dev/null +++ b/exercises/xorcism/README.md @@ -0,0 +1,163 @@ +# Xorcism + +Write a streaming adaptor which contains a reference to a key, and bitwise-XORs +it with arbitrary data. + +XOR is a fundamental binary operation: for each bit in the inputs, set the +corresponding bit of the output to `1` if the input bits are different. If both +inputs are `1` or both are `0`, then the corresponding output bit is `0`. + +When XORing a document with a key, the key is repeated as many times as +necessary, producing an output document equal in length to the input document. + +XORing a document with a key has been used for cryptography as recently as the +early 1900s. While this is thoroughly obsolete as a method for hiding data, it +can be surprisingly useful for generating noisy random-seeming data without +needing the complication of true randomness. It is still used occasionally in +modern cryptography for certain ciphers: the cipher itself is just a mechanism +for generating a very random, infinitely long key, which is XOR'd with the +document. + +One interesting property of XOR encryption is that it is symmetrical: XORing any +number with itself produces `0`, and XORing any number with `0` returns the +input number unchanged. Therefore, to decrypt a document which has been +XOR-encrypted, XOR-encrypt it again using the same key. + +## Nonallocation + +It is not practical to write a test which ensures that your struct holds a +reference to the key instead of copying it. Likewise, it is not practical to +prove with a test that neither `munge` nor `munge_in_place`, nor any of their +helper functions, allocate on the heap. Nevertheless, you should attempt to +write your solution in this way. + +## Implementation + +You will need to write a `struct Xorcism` which holds a reference to a key. That +struct must provide two methods: `munge_in_place` and `munge`. The former +adjusts a byte buffer in-place. The latter is an iterator adaptor: it accepts an +arbitrary iterator of data, and returns a new iterator of data. + +This exercise's stub signatures are largely correct in syntax, but they do not +compile: a large part of the point of this exercise is for you to get familiar +with using lifetimes and generics, so you will need to fill them in on your own. +Another goal of this exercise is for you to figure out an appropriate +factorization which enables you to implement both of those methods with minimal +duplication of effort. Don't be afraid to introduce additional helpers! + +## Useful Traits + +These traits will be useful: + +- [`AsRef`](https://doc.rust-lang.org/std/convert/trait.AsRef.html) +- [`Borrow`](https://doc.rust-lang.org/std/borrow/trait.Borrow.html) +- [`ExactSizeIterator`](https://doc.rust-lang.org/std/iter/trait.ExactSizeIterator.html) +- [`IntoIterator`](https://doc.rust-lang.org/std/iter/trait.IntoIterator.html) +- [`Sized`](https://doc.rust-lang.org/std/marker/trait.Sized.html) + +## Bonus Tests + +This exercise contains bonus tests, behind the `io` feature flag. To enable +them, run + +```sh +cargo test --features io +``` + +For these tests, you will need to implement a method `reader` with the signature + +```rust +fn reader(self, impl Read) -> impl Read +``` + +and a method `writer` with the signature + +```rust +fn writer(self, impl Write) -> impl Write +``` + +These functions each convert the `Xorcism` struct into a stream adaptor in the +appropriate direction. They use these traits: + +- [`Read`](https://doc.rust-lang.org/std/io/trait.Read.html) +- [`Write`](https://doc.rust-lang.org/std/io/trait.Write.html) + +## Rust Installation + +Refer to the [exercism help page][help-page] for Rust installation and learning +resources. + +## Writing the Code + +Execute the tests with: + +```bash +$ cargo test +``` + +All but the first test have been ignored. After you get the first test to +pass, open the tests source file which is located in the `tests` directory +and remove the `#[ignore]` flag from the next test and get the tests to pass +again. Each separate test is a function with `#[test]` flag above it. +Continue, until you pass every test. + +If you wish to run all ignored tests without editing the tests source file, use: + +```bash +$ cargo test -- --ignored +``` + +To run a specific test, for example `some_test`, you can use: + +```bash +$ cargo test some_test +``` + +If the specific test is ignored use: + +```bash +$ cargo test some_test -- --ignored +``` + +To learn more about Rust tests refer to the [online test documentation][rust-tests] + +Make sure to read the [Modules][modules] chapter if you +haven't already, it will help you with organizing your files. + +## Further improvements + +After you have solved the exercise, please consider using the additional utilities, described in the [installation guide](https://exercism.io/tracks/rust/installation), to further refine your final solution. + +To format your solution, inside the solution directory use + +```bash +cargo fmt +``` + +To see, if your solution contains some common ineffective use cases, inside the solution directory use + +```bash +cargo clippy --all-targets +``` + +## Submitting the solution + +Generally you should submit all files in which you implemented your solution (`src/lib.rs` in most cases). If you are using any external crates, please consider submitting the `Cargo.toml` file. This will make the review process faster and clearer. + +## Feedback, Issues, Pull Requests + +The [exercism/rust](https://github.com/exercism/rust) repository on GitHub is the home for all of the Rust exercises. If you have feedback about an exercise, or want to help implement new exercises, head over there and create an issue. Members of the rust track team are happy to help! + +If you want to know more about Exercism, take a look at the [contribution guide](https://github.com/exercism/docs/blob/master/contributing-to-language-tracks/README.md). + +[help-page]: https://exercism.io/tracks/rust/learning +[modules]: https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html +[cargo]: https://doc.rust-lang.org/book/ch14-00-more-about-cargo.html +[rust-tests]: https://doc.rust-lang.org/book/ch11-02-running-tests.html + +## Source + +Peter Goodspeed-Niklaus + +## Submitting Incomplete Solutions +It's possible to submit an incomplete solution so you can see how others have completed the exercise. diff --git a/exercises/xorcism/example.rs b/exercises/xorcism/example.rs new file mode 100644 index 000000000..8e3644682 --- /dev/null +++ b/exercises/xorcism/example.rs @@ -0,0 +1,207 @@ +use std::borrow::Borrow; +use std::io::{self, Read, Write}; + +/// A munger which XORs a key with some data +/// +/// This is a low-level structure; more often, you'll want to use [`Writer`], [`Reader`], or [`munge`]. +// +// You might wonder: why implement this manually, instead of just storing `key: Cycle>,`? +// +// If we do it like that, the lifetimes get kind of crazy. In particular, in `fn munge`, we want to do +// `data.zip(self.key.by_ref())`, and that `by_ref()` thing really confuses the lifetime inferencer. +// It ended up being simpler to just handle the key indexing manually than to figure out the correct +// incantation to get that lifetime style to work. +#[derive(Clone)] +pub struct Xorcism<'a> { + key: &'a [u8], + pos: usize, +} + +/// For composability, it is important that `munge` returns an iterator compatible with its input. +/// +/// However, `impl Trait` syntax can specify only a single non-auto trait. +/// Therefore, we define this output trait with generic implementations on all compatible types, +/// and return that instead. +pub trait MungeOutput: Iterator + ExactSizeIterator {} +impl MungeOutput for T where T: Iterator + ExactSizeIterator {} + +/// WARNING: This could be construed as abusing the type system +/// +/// We need to be able to assert that a particular iterator has +/// an exact size. We need this because `iter::Zip` implements +/// `ExactSizeIterator` only if both its inputs implement `ExactSizeIterator`, evne though +/// it will always terminate on the shorter of them. +/// +/// We wouldn't need this type if negative trait bounds were possible, but it looks like +/// even in a post-chalk world, those are not likely to be added to the language, as they +/// could make new trait implementations into a breaking change. +/// +/// Essentially, this type is used to assert that an infinite iterator has an exact size, +/// of "the biggest number". +struct AssertExactSizeIterator(I); + +impl Iterator for AssertExactSizeIterator +where + I: Iterator, +{ + type Item = ::Item; + + fn next(&mut self) -> Option { + self.0.next() + } + + fn size_hint(&self) -> (usize, Option) { + // oops, all 1s + const SIZE: usize = !0; + (SIZE, Some(SIZE)) + } +} + +impl ExactSizeIterator for AssertExactSizeIterator where I: Iterator {} + +impl<'a> Xorcism<'a> { + /// Create a new Xorcism munger from a key + pub fn new(key: &'a Key) -> Xorcism<'a> + where + Key: AsRef<[u8]> + ?Sized, + { + let key = key.as_ref(); + Xorcism { key, pos: 0 } + } + + /// Increase the stored pos by the specified amount, returning the old value. + fn incr_pos(&mut self, by: usize) -> usize { + let old_pos = self.pos; + self.pos += by; + old_pos + } + + /// Produce the key iterator, offset by `pos`. + fn key<'b>(&mut self, pos: usize) -> impl 'b + MungeOutput + where + 'a: 'b, + { + AssertExactSizeIterator(self.key.iter().copied().cycle().skip(pos)) + } + + /// XOR each byte of the input buffer with a byte from the key. + /// + /// Note that this is stateful: repeated calls are likely to produce different results, + /// even with identical inputs. + pub fn munge_in_place(&mut self, data: &mut [u8]) { + let pos = self.incr_pos(data.len()); + for (d, k) in data.iter_mut().zip(self.key(pos)) { + *d ^= k; + } + } + + /// XOR each byte of the data with a byte from the key. + /// + /// Note that this is stateful: repeated calls are likely to produce different results, + /// even with identical inputs. + pub fn munge<'b, Data, B>(&mut self, data: Data) -> impl 'b + MungeOutput + where + 'a: 'b, + Data: IntoIterator, + ::IntoIter: 'b + ExactSizeIterator, + B: Borrow, + { + let data = data.into_iter(); + let pos = self.incr_pos(data.len()); + data.zip(self.key(pos)).map(|(d, k)| d.borrow() ^ k) + } + + /// Convert this into a [`Writer`] + pub fn writer(self, writer: W) -> Writer<'a, W> + where + W: Write, + { + Writer { + xorcism: self, + writer, + } + } + + /// Convert this into a [`Reader`] + pub fn reader(self, reader: R) -> Reader<'a, R> + where + R: Read, + { + Reader { + xorcism: self, + reader, + } + } +} + +/// This implements `Write` and performs xor munging on the data stream. +#[derive(Clone)] +pub struct Writer<'a, W> { + xorcism: Xorcism<'a>, + writer: W, +} + +impl<'a, W> Writer<'a, W> +where + W: Write, +{ + pub fn new(key: &'a Key, writer: W) -> Writer<'a, W> + where + Key: AsRef<[u8]> + ?Sized, + { + Writer { + xorcism: Xorcism::new(key), + writer, + } + } +} + +impl<'a, W> Write for Writer<'a, W> +where + W: Write, +{ + /// This implementation will block until the underlying writer + /// has written the entire input buffer. + fn write(&mut self, data: &[u8]) -> io::Result { + let munged: Vec<_> = self.xorcism.munge(data).collect(); + self.writer.write_all(&munged)?; + Ok(data.len()) + } + + fn flush(&mut self) -> io::Result<()> { + self.writer.flush() + } +} + +/// This implements `Read` and performs xor munging on the data stream. +#[derive(Clone)] +pub struct Reader<'a, R> { + xorcism: Xorcism<'a>, + reader: R, +} + +impl<'a, R> Reader<'a, R> +where + R: Read, +{ + pub fn new(key: &'a Key, reader: R) -> Reader<'a, R> + where + Key: AsRef<[u8]> + ?Sized, + { + Reader { + xorcism: Xorcism::new(key), + reader, + } + } +} + +impl<'a, R> Read for Reader<'a, R> +where + R: Read, +{ + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let bytes_read = self.reader.read(buf)?; + self.xorcism.munge_in_place(&mut buf[..bytes_read]); + Ok(bytes_read) + } +} diff --git a/exercises/xorcism/src/lib.rs b/exercises/xorcism/src/lib.rs new file mode 100644 index 000000000..c91f4c19a --- /dev/null +++ b/exercises/xorcism/src/lib.rs @@ -0,0 +1,46 @@ +/// A munger which XORs a key with some data +#[derive(Clone)] +pub struct Xorcism<'a> { + // This field is just to suppress compiler complaints; + // feel free to delete it at any point. + _phantom: std::marker::PhantomData<&'a u8>, +} + +/// For composability, it is important that `munge` returns an iterator compatible with its input. +/// +/// However, `impl Trait` syntax can specify only a single non-auto trait. +/// Therefore, we define this output trait with generic implementations on all compatible types, +/// and return that instead. +pub trait MungeOutput: Iterator + ExactSizeIterator {} +impl MungeOutput for T where T: Iterator + ExactSizeIterator {} + +impl<'a> Xorcism<'a> { + /// Create a new Xorcism munger from a key + /// + /// Should accept anything which has a cheap conversion to a byte slice. + pub fn new(key: &Key) -> Xorcism<'a> { + unimplemented!() + } + + /// XOR each byte of the input buffer with a byte from the key. + /// + /// Note that this is stateful: repeated calls are likely to produce different results, + /// even with identical inputs. + pub fn munge_in_place(&mut self, data: &mut [u8]) { + unimplemented!() + } + + /// XOR each byte of the data with a byte from the key. + /// + /// Note that this is stateful: repeated calls are likely to produce different results, + /// even with identical inputs. + /// + /// Should accept anything which has a cheap conversion to a byte iterator. + /// Shouldn't matter whether the byte iterator's values are owned or borrowed. + pub fn munge(&mut self, data: Data) -> impl MungeOutput { + unimplemented!(); + // this empty iterator silences a compiler complaint that + // () doesn't implement ExactSizeIterator + std::iter::empty() + } +} diff --git a/exercises/xorcism/tests/xorcism.rs b/exercises/xorcism/tests/xorcism.rs new file mode 100644 index 000000000..323fc2c07 --- /dev/null +++ b/exercises/xorcism/tests/xorcism.rs @@ -0,0 +1,303 @@ +use rstest::rstest; +use rstest_reuse::{self, *}; +#[cfg(feature = "io")] +use std::io::{Read, Write}; +use xorcism::Xorcism; +use hexlit::hex; + +#[test] +fn identity() { + let mut xs = Xorcism::new(&[0]); + let data = "This is super-secret, cutting edge encryption, folks."; + + assert_eq!( + xs.munge(data.as_bytes()).collect::>(), + data.as_bytes() + ); +} + +#[test] +#[ignore] +fn munge_output_has_len() { + let mut xs = Xorcism::new(&[0]); + let data = "The output must have a definite length"; + assert_eq!(xs.munge(data.as_bytes()).len(), 38); +} + +#[test] +#[ignore] +fn statefulness() { + // we expect Xorcism to be stateful: at the end of a munging run, the key has rotated. + // this means that until the key has completely rotated around, equal inputs will produce + // unequal outputs. + let key = &[0, 1, 2, 3, 4, 5, 6, 7]; + let input = &[0b1010_1010, 0b0101_0101]; + + let mut xs = Xorcism::new(&key); + let out1: Vec<_> = xs.munge(input).collect(); + let out2: Vec<_> = xs.munge(input).collect(); + let out3: Vec<_> = xs.munge(input).collect(); + let out4: Vec<_> = xs.munge(input).collect(); + let out5: Vec<_> = xs.munge(input).collect(); + + assert_ne!(out1, out2); + assert_ne!(out2, out3); + assert_ne!(out3, out4); + assert_ne!(out4, out5); + assert_eq!(out1, out5); +} + +#[template] +#[rstest( + key, + input, + expect, + case::key_shorter_than_data("abcde", "123455", &hex!("5050 5050 5054")), + case::key_len_equal_to_data( + "The quick brown fox jumped over the lazy dog.", + "Wait, oops, this is not the pangram exercise!", + &hex!("0309 0c54 5d55 060c 1b53 4e52 1b1f 0753 4606 0b00 041a 1950 110c 454f 0604 1c47 0609 0800 0919 1f0b 430d 1c02 0f"), + ), + case::key_longer_than_data( + "A properly cryptographically random key longer than the data can actually be fairly secure.", + "Text is not cryptographically random.", + &hex!("1545 0806 4f19 1652 0216 5443 110b 0904 1b08 1513 1118 010a 020d 0015 5952 130f 0a0b 024d 45"), + ), + case::shakespearean( + "Forsooth, let us never break our trust!", + "The sacred brothership in which we share shall never from our hearts be lost.", + &hex!("1207 1753 1c0e 171a 4944 4c07 064f 011b 451c 161e 0c02 000b 1c45 1603 490c 1d52 5711 5206 1b15 5323 4f01 1b0e 0318 4842 451a 0006 0013 014f 0345 1910 0000 0a17 0413 1f53 4f17 1700 181d 0607 5a"), + ), + case::comics( + "Who knows what evil lurks in the hearts of men?", + "Spiderman! It's spiderman! Not a bird, or a plane, or a fireman! Just spiderman!", + &hex!("0418 0644 0e1c 0216 1d01 5721 1553 5345 0519 0544 0907 1f0a 1d01 4920 4f00 4804 000a 0c13 1658 534f 1d46 414d 1502 5e39 0d43 0004 1c4f 1653 461e 1a04 1941 0b57 4926 551f 0152 1803 490d 0b52 1909 0b01"), + ), + case::mad_science( + "TRANSMUTATION_NOTES_1", + "If wishes were horses, beggars would ride.", + &hex!("1d34 6139 3a3e 3d31 3274 3e2a 3c3a 6e27 3b37 203a 4278 7223 2b34 2a34 2632 743e 203b 332a 6f26 2c37 3a1f"), + ), + case::metaphor( + "Contextualism", + "The globe is text, its people prose; all the world's a page.", + &hex!("1707 0b54 0214 1b17 044c 0000 4d37 0a16 0049 581d 0112 4c19 1602 3303 0b54 150a 1b06 0457 4912 012f 4f1a 1c00 5803 1a13 000d 541e 630e 4e04 041f 115b"), + ), + case::emoji("🔑🗝️… 🎹?", "⌨️! 🔒+💻+🧠=🔓", &hex!("1213 3c7e 4810 b6bd 1f27 1b70 ab56 bf62 24a5 49a0 573f a961 6f0b 04")) +)] +// note the emoji case above. Most exercism tests don't test emoji, because we like to use strings +// as input, but in this case, they're fine: they're arbitrary binary data that _just happen_ to +// represent emoji when construed as a string, in this case. +fn ref_str(key: &str, input: &str, expect: &[u8]) {} + +/// tests where the key is expressed as `&str`, and input is expressed as `&[u8]` +mod str_slice { + use super::*; + + #[apply(ref_str)] + #[ignore] + fn munge_in_place(key: &str, input: &str, expect: &[u8]) { + // we transform the input into a `Vec` despite its presence in this module because of + // the more restricted syntax that this function accepts + let mut input = input.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, expect); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[apply(ref_str)] + #[ignore] + fn munges(key: &str, input: &str, expect: &[u8]) { + let mut xorcism = Xorcism::new(key); + let result: Vec = xorcism.munge(input.as_bytes()).collect(); + assert_eq!(input.len(), result.len()); + assert_ne!(input.as_bytes(), result); + assert_eq!(result, expect); + } + + #[apply(ref_str)] + #[ignore] + fn round_trip(key: &str, input: &str, expect: &[u8]) { + // we can't just do _expect, because the apply macro is sensitive to the name of the variable + let _ = expect; + + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(input.as_bytes()); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(input.as_bytes(), result); + } +} + +/// tests where the key and input are both expressed as `&[u8]` +mod slice_slice { + use super::*; + + #[apply(ref_str)] + #[ignore] + fn munge_in_place(key: &str, input: &str, expect: &[u8]) { + let key = key.as_bytes(); + + // we transform the input into a `Vec` despite its presence in this module because of + // the more restricted syntax that this function accepts + let mut input = input.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, expect); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[apply(ref_str)] + #[ignore] + fn munges(key: &str, input: &str, expect: &[u8]) { + let key = key.as_bytes(); + let input = input.as_bytes(); + + let mut xorcism = Xorcism::new(key); + let result: Vec = xorcism.munge(input).collect(); + assert_eq!(input.len(), result.len()); + assert_ne!(input, result); + assert_eq!(result, expect); + } + + #[apply(ref_str)] + #[ignore] + fn round_trip(key: &str, input: &str, expect: &[u8]) { + let key = key.as_bytes(); + let input = input.as_bytes(); + + // we can't just do _expect, because the apply macro is sensitive to the name of the variable + let _ = expect; + + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(input, result); + } +} + +/// tests where the key is expressed as `&str` and input is expressed as `Vec` +mod vec_vec { + use super::*; + + #[apply(ref_str)] + #[ignore] + fn munge_in_place(key: &str, input: &str, expect: &[u8]) { + let mut input = input.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, expect); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[apply(ref_str)] + #[ignore] + fn munges(key: &str, input: &str, expect: &[u8]) { + let owned_input = input.as_bytes().to_vec(); + + let mut xorcism = Xorcism::new(key); + let result: Vec = xorcism.munge(owned_input).collect(); + assert_eq!(input.len(), result.len()); + assert_ne!(input.as_bytes(), result); + assert_eq!(result, expect); + } + + #[apply(ref_str)] + #[ignore] + fn round_trip(key: &str, input: &str, expect: &[u8]) { + let owned_input = input.as_bytes().to_vec(); + + // we can't just do _expect, because the apply macro is sensitive to the name of the variable + let _ = expect; + + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(owned_input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(input.as_bytes(), result); + } +} + +#[cfg(feature = "io")] +mod io { + use super::*; + + #[apply(ref_str)] + #[ignore] + fn reader_munges(key: &str, input: &str, expect: &[u8]) { + let mut reader = Xorcism::new(key).reader(input.as_bytes()); + let mut buf = Vec::with_capacity(input.len()); + let bytes_read = reader.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, input.len()); + assert_eq!(buf, expect); + } + + #[apply(ref_str)] + #[ignore] + fn reader_roundtrip(key: &str, input: &str, expect: &[u8]) { + let _ = expect; + + let xs = Xorcism::new(key); + let reader1 = xs.clone().reader(input.as_bytes()); + let mut reader2 = xs.clone().reader(reader1); + let mut buf = Vec::with_capacity(input.len()); + let bytes_read = reader2.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, input.len()); + assert_eq!(buf, input.as_bytes()); + } + + #[apply(ref_str)] + #[ignore] + fn writer_munges(key: &str, input: &str, expect: &[u8]) { + let mut writer_dest = Vec::new(); + { + let mut writer = Xorcism::new(key).writer(&mut writer_dest); + assert!(writer.write_all(input.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, expect); + } + + #[apply(ref_str)] + #[ignore] + fn writer_roundtrip(key: &str, input: &str, expect: &[u8]) { + let _ = expect; + + let mut writer_dest = Vec::new(); + let xs = Xorcism::new(key); + { + let writer1 = xs.clone().writer(&mut writer_dest); + let mut writer2 = xs.writer(writer1); + assert!(writer2.write_all(input.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, input.as_bytes()); + } +}