cipher_salad/
vigenere.rs

1use crate::Alphabet;
2use unicode_segmentation::UnicodeSegmentation;
3
4pub struct Vigenere {
5    foreign_policy: ForeignGraphemesPolicy,
6    alphabet: Alphabet,
7}
8pub enum ForeignGraphemesPolicy {
9    Include,
10    Exclude,
11}
12
13enum CipherOperation {
14    Encrypt,
15    Decrypt,
16}
17
18#[derive(Debug)]
19pub enum VigenereError {
20    InvalidKey,
21}
22
23impl Vigenere {
24    pub fn new(alphabet: Alphabet, foreign_policy: ForeignGraphemesPolicy) -> Vigenere {
25        Vigenere {
26            foreign_policy,
27            alphabet,
28        }
29    }
30
31    fn cipher(
32        &self,
33        contents: &str,
34        key: &str,
35        operation: CipherOperation,
36    ) -> Result<String, VigenereError> {
37        let valid_key = key
38            .graphemes(true)
39            .all(|grapheme| self.alphabet.contains(grapheme));
40        if !valid_key {
41            return Err(VigenereError::InvalidKey);
42        }
43
44        let approx_length = contents.len();
45        let mut result = String::with_capacity(approx_length);
46
47        let mut contents = contents.graphemes(true);
48        let mut key = key.graphemes(true).cycle();
49
50        loop {
51            let next = contents.next();
52            let plain_grapheme = match next {
53                Some(p) => p,
54                None => break,
55            };
56
57            if !self.alphabet.contains(plain_grapheme) {
58                match self.foreign_policy {
59                    ForeignGraphemesPolicy::Include => result.push_str(plain_grapheme),
60                    ForeignGraphemesPolicy::Exclude => (),
61                }
62                continue;
63            }
64
65            let key_grapheme = key.next().unwrap();
66
67            let plain_index = self.alphabet.index_of(plain_grapheme);
68            let key_index = self.alphabet.index_of(key_grapheme);
69
70            let ciphered = plain_index
71                .zip(key_index)
72                .and_then(|(plain_index, key_index)| {
73                    use CipherOperation::*;
74                    let ciphered_index = match operation {
75                        Encrypt => (plain_index + key_index).rem_euclid(self.alphabet.length()),
76                        Decrypt => {
77                            // Vecs in Rust are limited to max isize::MAX elements
78                            // https://doc.rust-lang.org/nomicon/vec-alloc.html
79                            // As such we must limit all allocations to isize::MAX elements
80                            // This is why it is possible to cast to an isize "losslessly"
81                            let plain_index = plain_index as isize;
82                            let key_index = key_index as isize;
83                            // rem_euclid has slightly different semantics than %, which in this case is what is needed
84                            (plain_index - key_index).rem_euclid(self.alphabet.length() as isize)
85                                as usize
86                        }
87                    };
88                    self.alphabet.grapheme_at(ciphered_index)
89                })
90                .cloned()
91                .unwrap_or_else(String::new);
92            result.push_str(&ciphered);
93        }
94        Ok(result)
95    }
96
97    pub fn encrypt(&self, contents: &str, key: &str) -> Result<String, VigenereError> {
98        self.cipher(contents, key, CipherOperation::Encrypt)
99    }
100
101    pub fn decrypt(&self, ciphertext: &str, key: &str) -> Result<String, VigenereError> {
102        self.cipher(ciphertext, key, CipherOperation::Decrypt)
103    }
104}