1use cipher_utils::alphabet::Alphabet;
2
3pub struct Vigenere {
4 alphabet: Alphabet,
5 key: String,
6}
7
8impl Vigenere {
9 pub fn encrypt(&self, plaintext: &str) -> String {
10 let repeated_key = self.key.repeat(plaintext.len() / self.key.len());
11 let key_bytes = repeated_key.as_bytes();
12 let mut index = 0;
13 plaintext
14 .chars()
15 .map(|plain_char| {
16 if !plain_char.is_alphabetic() {
17 return plain_char;
18 }
19 let key_char = key_bytes[index] as char;
20 let plaintext_index = self.alphabet.index_of(plain_char).unwrap();
21 let key_index = self.alphabet.index_of(key_char).unwrap();
22 let result = self.alphabet.letter_at(plaintext_index + key_index - 1);
23 index += 1;
24 if plain_char.is_uppercase() {
25 result.to_ascii_uppercase()
26 } else {
27 result.to_ascii_lowercase()
28 }
29 })
30 .collect()
31 }
32
33 pub fn decrypt(&self, ciphertext: &str) -> String {
34 let repeated_key = self.key.repeat(ciphertext.len() / self.key.len());
35 let key_bytes = repeated_key.as_bytes();
36 let mut index = 0;
37 ciphertext
38 .chars()
39 .map(|cipher_char| {
40 if !cipher_char.is_alphabetic() {
41 return cipher_char;
42 }
43 let key_char = key_bytes[index] as char;
44 let ciphertext_index = self.alphabet.index_of(cipher_char).unwrap();
45 let key_index = self.alphabet.index_of(key_char).unwrap();
46 let result = self.alphabet.letter_at(ciphertext_index - key_index + 1);
47 index += 1;
48 if cipher_char.is_uppercase() {
49 result.to_ascii_uppercase()
50 } else {
51 result.to_ascii_lowercase()
52 }
53 })
54 .collect()
55 }
56}
57
58pub trait VigenereBuilder {
59 fn alphabet<T: AsRef<str>>(self, alphabet: T) -> impl VigenereBuilder;
60 fn key<T: AsRef<str>>(self, key: T) -> impl VigenereBuilder;
61 fn build(self) -> anyhow::Result<Vigenere>;
62}
63
64#[derive(Debug, Default)]
65struct IncompleteVigenere {
66 key: Option<String>,
67 alphabet: Option<Alphabet>,
68}
69
70impl VigenereBuilder for anyhow::Result<IncompleteVigenere> {
71 fn key<T: AsRef<str>>(self, key: T) -> impl VigenereBuilder {
72 if let Ok(mut vigenere) = self {
73 vigenere.key = Some(key.as_ref().to_owned());
74 Ok(vigenere)
75 } else {
76 self
77 }
78 }
79
80 fn alphabet<T: AsRef<str>>(self, alphabet: T) -> impl VigenereBuilder {
81 if let Ok(mut vigenere) = self {
82 vigenere.alphabet = Some(Alphabet::caseless(alphabet.as_ref())?);
83 Ok(vigenere)
84 } else {
85 self
86 }
87 }
88
89 fn build(self) -> anyhow::Result<Vigenere> {
90 if let Ok(vigenere) = self {
91 let Some(key) = vigenere.key else {
92 anyhow::bail!("Error building Vigenere: No key provided.");
93 };
94
95 let Some(alphabet) = vigenere.alphabet else {
96 anyhow::bail!("Error building Vigenere: No alphabet provided.");
97 };
98
99 Ok(Vigenere { alphabet, key })
100 } else {
101 Err(self.unwrap_err())
102 }
103 }
104}
105
106impl Vigenere {
107 #[allow(clippy::new_ret_no_self)]
108 pub fn new() -> impl VigenereBuilder {
109 Ok(IncompleteVigenere::default())
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use crate::{Vigenere, VigenereBuilder as _};
116
117 #[test]
118 fn encrypt_decrypt() -> anyhow::Result<()> {
119 let plaintext = include_str!("../tests/letter.txt");
120 let ciphertext = include_str!("../tests/encrypted_letter.txt");
121
122 let vigenere = Vigenere::new().alphabet("AYCDWZIHGJKLQNOPMVSTXREUBF").key("MYSUPERTOPSECRETKEY").build()?;
123
124 assert_eq!(ciphertext, vigenere.encrypt(plaintext));
125 assert_eq!(plaintext, vigenere.decrypt(ciphertext));
126
127 Ok(())
128 }
129}