Skip to main content

cipher_salad/cipher/
vigenere.rs

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