1use crate::common::cipher::Cipher;
15use lipsum::lipsum;
16use std::collections::HashMap;
17use std::string::String;
18
19const CODE_LEN: usize = 5;
21
22lazy_static! {
26 static ref CODE_MAP: HashMap<&'static str, &'static str> = hashmap! {
27 "A" => "AAAAA",
28 "B" => "AAAAB",
29 "C" => "AAABA",
30 "D" => "AAABB",
31 "E" => "AABAA",
32 "F" => "AABAB",
33 "G" => "AABBA",
34 "H" => "AABBB",
35 "I" => "ABAAA",
36 "J" => "ABAAB",
37 "K" => "ABABA",
38 "L" => "ABABB",
39 "M" => "ABBAA",
40 "N" => "ABBAB",
41 "O" => "ABBBA",
42 "P" => "ABBBB",
43 "Q" => "BAAAA",
44 "R" => "BAAAB",
45 "S" => "BAABA",
46 "T" => "BAABB",
47 "U" => "BABAA",
48 "V" => "BABAB",
49 "W" => "BABBA",
50 "X" => "BABBB",
51 "Y" => "BBAAA",
52 "Z" => "BBAAB"
53 };
54}
55
56lazy_static! {
58 static ref ITALIC_CODES: HashMap<&'static str, char> = hashmap!{
59 "A" => '\u{1D434}',
61 "B" => '\u{1D435}',
62 "C" => '\u{1D436}',
63 "D" => '\u{1D437}',
64 "E" => '\u{1D438}',
65 "F" => '\u{1D439}',
66 "G" => '\u{1D43a}',
67 "H" => '\u{1D43b}',
68 "I" => '\u{1D43c}',
69 "J" => '\u{1D43d}',
70 "K" => '\u{1D43e}',
71 "L" => '\u{1D43f}',
72 "M" => '\u{1D440}',
73 "N" => '\u{1D441}',
74 "O" => '\u{1D442}',
75 "P" => '\u{1D443}',
76 "Q" => '\u{1D444}',
77 "R" => '\u{1D445}',
78 "S" => '\u{1D446}',
79 "T" => '\u{1D447}',
80 "U" => '\u{1D448}',
81 "V" => '\u{1D449}',
82 "W" => '\u{1D44a}',
83 "X" => '\u{1D44b}',
84 "Y" => '\u{1D44c}',
85 "Z" => '\u{1D44d}',
86 "a" => '\u{1D622}',
88 "b" => '\u{1D623}',
89 "c" => '\u{1D624}',
90 "d" => '\u{1D625}',
91 "e" => '\u{1D626}',
92 "f" => '\u{1D627}',
93 "g" => '\u{1D628}',
94 "h" => '\u{1D629}',
95 "i" => '\u{1D62a}',
96 "j" => '\u{1D62b}',
97 "k" => '\u{1D62c}',
98 "l" => '\u{1D62d}',
99 "m" => '\u{1D62e}',
100 "n" => '\u{1D62f}',
101 "o" => '\u{1D630}',
102 "p" => '\u{1D631}',
103 "q" => '\u{1D632}',
104 "r" => '\u{1D633}',
105 "s" => '\u{1D634}',
106 "t" => '\u{1D635}',
107 "u" => '\u{1D636}',
108 "v" => '\u{1D637}',
109 "w" => '\u{1D638}',
110 "x" => '\u{1D639}',
111 "y" => '\u{1D63a}',
112 "z" => '\u{1D63b}'
113 };
114}
115
116fn get_code(use_distinct_alphabet: bool, key: &str) -> String {
118 let mut code = String::new();
119 let mut key_upper = key.to_uppercase();
122 if !use_distinct_alphabet {
123 match key_upper.as_str() {
124 "J" => key_upper = "I".to_string(),
125 "U" => key_upper = "V".to_string(),
126 _ => {}
127 }
128 }
129 if CODE_MAP.contains_key(key_upper.as_str()) {
130 code.push_str(CODE_MAP.get(key_upper.as_str()).unwrap());
131 }
132 code
133}
134
135fn get_key(code: &str) -> String {
137 let mut key = String::new();
138
139 for (_key, val) in CODE_MAP.iter() {
140 if val == &code {
141 key.push_str(_key);
142 }
143 }
144 key
145}
146
147pub struct Baconian {
149 use_distinct_alphabet: bool,
150 decoy_text: String,
151}
152
153impl Cipher for Baconian {
154 type Key = (bool, Option<String>);
155 type Algorithm = Baconian;
156
157 fn new(key: (bool, Option<String>)) -> Baconian {
168 Baconian {
169 use_distinct_alphabet: key.0,
170 decoy_text: key.1.unwrap_or_else(|| lipsum(160)),
171 }
172 }
173
174 fn encrypt(&self, message: &str) -> Result<String, &'static str> {
194 let num_non_alphas = self
195 .decoy_text
196 .chars()
197 .filter(|c| !c.is_alphabetic())
198 .count();
199
200 if (message.len() * CODE_LEN) > self.decoy_text.len() - num_non_alphas {
203 return Err("Message too long for supplied decoy text.");
204 }
205
206 let secret: String = message
208 .chars()
209 .map(|c| get_code(self.use_distinct_alphabet, &c.to_string()))
210 .collect();
211
212 let mut num_alphas = 0;
213 let mut num_non_alphas = 0;
214 for c in self.decoy_text.chars() {
215 if num_alphas == secret.len() {
216 break;
217 }
218 if c.is_alphabetic() {
219 num_alphas += 1
220 } else {
221 num_non_alphas += 1
222 };
223 }
224
225 let decoy_slice: String = self
226 .decoy_text
227 .chars()
228 .take(num_alphas + num_non_alphas)
229 .collect();
230
231 let mut decoy_msg = String::new();
237 let mut secret_iter = secret.chars();
238 for c in decoy_slice.chars() {
239 if c.is_alphabetic() {
240 if let Some(sc) = secret_iter.next() {
241 if sc == 'B' {
242 let italic = *ITALIC_CODES.get(c.to_string().as_str()).unwrap();
244 decoy_msg.push(italic);
245 } else {
246 decoy_msg.push(c);
247 }
248 }
249 } else {
250 decoy_msg.push(c);
251 }
252 }
253
254 Ok(decoy_msg)
255 }
256
257 fn decrypt(&self, message: &str) -> Result<String, &'static str> {
272 let ciphertext: String = message
276 .chars()
277 .filter(|c| c.is_alphabetic())
278 .map(|c| {
279 if ITALIC_CODES.iter().any(|e| *e.1 == c) {
280 'B'
281 } else {
282 'A'
283 }
284 })
285 .collect();
286
287 let mut plaintext = String::new();
288 let mut code = String::new();
289 for c in ciphertext.chars() {
290 code.push(c);
291 if code.len() == CODE_LEN {
292 plaintext += &get_key(&code);
294 code.clear();
295 }
296 }
297
298 Ok(plaintext)
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305
306 #[test]
307 fn encrypt_simple() {
308 let b = Baconian::new((false, None));
309 let message = "Hello";
310 let cipher_text = "Lo𝘳𝘦𝘮 ip𝘴um d𝘰l𝘰𝘳 s𝘪t 𝘢𝘮e𝘵, 𝘤𝘰n";
311 assert_eq!(cipher_text, b.encrypt(message).unwrap());
312 }
313 #[test]
315 fn encrypt_trad_v_dist() {
316 let b_trad = Baconian::new((false, None));
317 let b_dist = Baconian::new((true, None));
318 let message = "I JADE YOU VERVENT UNICORN";
319
320 assert_ne!(
321 b_dist.encrypt(&message).unwrap(),
322 b_trad.encrypt(message).unwrap()
323 );
324 }
325
326 #[test]
327 fn encrypt_message_spaced() {
328 let decoy_text = String::from(
329 "The world's a bubble; and the life of man less than a span. \
331 In his conception wretched; from the womb so to the tomb: \
332 Curst from the cradle, and brought up to years, with cares and fears. \
333 Who then to frail mortality shall trust, \
334 But limns the water, or but writes in dust. \
335 Yet, since with sorrow here we live oppress'd, what life is best? \
336 Courts are but only superficial schools to dandle fools: \
337 The rural parts are turn'd into a den of savage men: \
338 And where's a city from all vice so free, \
339 But may be term'd the worst of all the three?",
340 );
341 let b = Baconian::new((false, Some(decoy_text)));
342 let message = "Peace, Freedom 🗡️ and Liberty!";
343 let cipher_text = "T𝘩𝘦 𝘸𝘰rl𝘥\'s a bubble; an𝘥 the 𝘭ife o𝘧 m𝘢𝘯 les𝘴 th𝘢n a sp𝘢n. \
344 In hi𝘴 𝘤o𝘯𝘤𝘦pt𝘪𝘰n wretche𝘥; 𝘧r𝘰m th𝘦 𝘸o𝘮b 𝘴𝘰 t𝘰 the tomb: \
345 𝐶ur𝘴t f𝘳om t𝘩𝘦 cr𝘢𝘥𝘭𝘦, and";
346 assert_eq!(cipher_text, b.encrypt(message).unwrap());
347 }
348 #[test]
350 #[should_panic(expected = r#"Message too long for supplied decoy text."#)]
351 fn encrypt_decoy_too_short() {
352 let b = Baconian::new((false, None));
353 let message = "This is a long message that will be too long to encode using \
354 the default decoy text. In order to have a long message encoded you need a \
355 decoy text that is at least five times as long, plus the non-alphabeticals.";
356
357 b.encrypt(message).unwrap();
358 }
359
360 #[test]
361 fn encrypt_with_use_distinct_alphabet_codeset() {
362 let message = "Peace, Freedom 🗡️ and Liberty!";
363 let decoy_text = String::from(
364 "The world's a bubble; and the life of man less than a span. \
366 In his conception wretched; from the womb so to the tomb: \
367 Curst from the cradle, and brought up to years, with cares and fears. \
368 Who then to frail mortality shall trust, \
369 But limns the water, or but writes in dust. \
370 Yet, since with sorrow here we live oppress'd, what life is best? \
371 Courts are but only superficial schools to dandle fools: \
372 The rural parts are turn'd into a den of savage men: \
373 And where's a city from all vice so free, \
374 But may be term'd the worst of all the three?",
375 );
376 let cipher_text = "T𝘩𝘦 𝘸𝘰rl𝘥's a bubble; an𝘥 the 𝘭ife o𝘧 m𝘢𝘯 les𝘴 th𝘢n a sp𝘢n. \
377 In hi𝘴 𝘤o𝘯𝘤𝘦pt𝘪𝘰n wretche𝘥; 𝘧r𝘰m th𝘦 𝘸o𝘮b 𝘴𝘰 t𝘰 the tomb: \
378 𝐶ur𝘴t f𝘳om t𝘩𝘦 cr𝘢𝘥𝘭𝘦, and";
379 let b = Baconian::new((true, Some(decoy_text)));
380 assert_eq!(cipher_text, b.encrypt(message).unwrap());
381 }
382
383 #[test]
384 fn decrypt_a_classic() {
385 let cipher_text = String::from("Let's c𝘰mp𝘳𝘰𝘮is𝘦. 𝐻old off th𝘦 at𝘵a𝘤k");
386 let message = "ATTACK";
387 let decoy_text = String::from("Let's compromise. Hold off the attack");
388 let b = Baconian::new((true, Some(decoy_text)));
389 assert_eq!(message, b.decrypt(&cipher_text).unwrap());
390 }
391
392 #[test]
393 fn decrypt_traditional() {
394 let cipher_text = String::from(
395 "T𝘩e wor𝘭d's a bubble; an𝘥 𝘵he 𝘭if𝘦 𝘰f man 𝘭𝘦𝘴s 𝘵h𝘢n 𝘢 𝘴p𝘢n. \
396 𝐼n h𝘪s c𝘰nce𝘱𝘵i𝘰n 𝘸re𝘵che𝘥; 𝘧r𝘰𝘮 th𝘦 𝘸𝘰m𝘣 s𝘰 t𝘰 𝘵h𝘦 t𝘰mb: \
397 Curs𝘵 fr𝘰𝘮 𝘵h𝘦 cra𝘥l𝘦, 𝘢n𝘥",
398 );
399 let message = "IIADEYOVVERVENTVNICORN";
401 let decoy_text = String::from(
402 "The world's a bubble; and the life of man less than a span. \
404 In his conception wretched; from the womb so to the tomb: \
405 Curst from the cradle, and brought up to years, with cares and fears. \
406 Who then to frail mortality shall trust, \
407 But limns the water, or but writes in dust. \
408 Yet, since with sorrow here we live oppress'd, what life is best? \
409 Courts are but only superficial schools to dandle fools: \
410 The rural parts are turn'd into a den of savage men: \
411 And where's a city from all vice so free, \
412 But may be term'd the worst of all the three?",
413 );
414 let b = Baconian::new((false, Some(decoy_text)));
415 assert_eq!(message, b.decrypt(&cipher_text).unwrap());
416 }
417}