kryptos/ciphers/
substitution.rs1use common::ALPHABET;
2
3pub struct Substitution {
8 key: &'static str,
9}
10
11impl Substitution {
12 pub fn new(key: &'static str) -> Result<Self, String> {
27 if key.chars().count() != 26 {
28 return Err(String::from("Key is not the correct length"));
29 }
30
31 let mut alphabet_check = String::new();
32 for c in key.chars() {
33 if c.is_alphabetic() {
34 if alphabet_check.contains(c) {
35 return Err(String::from("Key alphabet must be unique"));
36 }
37
38 alphabet_check.push(c);
39 continue;
40 } else {
41 return Err(String::from("Key must be alphabetic"));
42 }
43 }
44
45 Ok(Substitution { key })
46 }
47
48 pub fn encipher(&self, plaintext: &str) -> Result<String, &'static str> {
67 Ok(plaintext
68 .chars()
69 .map(|c| match c as u8 {
70 65..=90 => {
71 let index = ALPHABET.chars().position(|i| i == c).unwrap();
72 self.key
73 .chars()
74 .nth(index)
75 .expect("Something is wrong with the key alphabet")
76 }
77 97..=122 => {
78 let index = ALPHABET
79 .chars()
80 .position(|i| i == char::from(c as u8 - 97 + 65))
81 .unwrap();
82 (self
83 .key
84 .chars()
85 .nth(index)
86 .expect("Something is wrong with the key alphabet")
87 as u8
88 - 65
89 + 97) as char
90 }
91 _ => c,
92 })
93 .collect::<String>())
94 }
95
96 pub fn decipher(&self, plaintext: &str) -> Result<String, &'static str> {
115 Ok(plaintext
116 .chars()
117 .map(|c| match c as u8 {
118 65..=90 => {
119 let index = self.key.chars().position(|i| i == c).unwrap();
120 ALPHABET
121 .chars()
122 .nth(index)
123 .expect("Something is wrong with the key alphabet")
124 }
125 97..=122 => {
126 let index = self
127 .key
128 .chars()
129 .position(|i| i == char::from(c as u8 - 97 + 65))
130 .unwrap();
131 (ALPHABET
132 .chars()
133 .nth(index)
134 .expect("Something is wrong with the key alphabet")
135 as u8
136 - 65
137 + 97) as char
138 }
139 _ => c,
140 })
141 .collect::<String>())
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::Substitution;
148
149 #[test]
150 fn unique_alphabet() {
151 assert!(Substitution::new("RBQIZDJCFMELOPAVNHYGWKTUXS").is_ok());
152 }
153
154 #[test]
155 fn non_unique_alphabet() {
156 assert!(Substitution::new("ABCDEFGHIJKLMNOPQRSTUVWXAZ").is_err());
157 }
158
159 #[test]
160 fn non_alphabetic() {
161 assert!(Substitution::new("RBQI1DJCFMELOPAVNHYGWKTUXS").is_err());
162 }
163
164 #[test]
165 fn too_small_alphabet() {
166 assert!(Substitution::new("ABC").is_err());
167 }
168
169 #[test]
170 fn too_large_alphabet() {
171 assert!(Substitution::new("ABCDEFGHIJKLMNOPQRSTUVWXYZA").is_err());
172 }
173
174 #[test]
175 fn encipher() {
176 let s = Substitution::new("NAKYQRTXBZPFIVEJSDCHGOUMWL").unwrap();
177 assert_eq!(
178 "Ye weg fbpq cqkdqh iqccntqc",
179 s.encipher("Do you like secret messages").unwrap()
180 );
181 }
182
183 #[test]
184 fn with_punctuation() {
185 let s = Substitution::new("NAKYQRTXBZPFIVEJSDCHGOUMWL").unwrap();
186 assert_eq!(
187 "Ye weg fbpq cqkdqh iqccntqc?",
188 s.encipher("Do you like secret messages?").unwrap()
189 );
190 }
191
192 #[test]
193 fn with_unicode() {
194 let s = Substitution::new("NAKYQRTXBZPFIVEJSDCHGOUMWL").unwrap();
195 assert_eq!(
196 "Ye weg fbpq cqkdqh 🖤 iqccntqc",
197 s.encipher("Do you like secret 🖤 messages").unwrap()
198 );
199 }
200
201 #[test]
202 fn decipher() {
203 let s = Substitution::new("NAKYQRTXBZPFIVEJSDCHGOUMWL").unwrap();
204 assert_eq!(
205 "Do you like secret messages",
206 s.decipher("Ye weg fbpq cqkdqh iqccntqc").unwrap()
207 );
208 }
209}