use common::cipher::Cipher;
use lipsum::lipsum;
use std::collections::HashMap;
use std::string::String;
const CODE_LEN: usize = 5;
lazy_static! {
static ref CODE_MAP: HashMap<&'static str, &'static str> = hashmap!{
"A" => "AAAAA",
"B" => "AAAAB",
"C" => "AAABA",
"D" => "AAABB",
"E" => "AABAA",
"F" => "AABAB",
"G" => "AABBA",
"H" => "AABBB",
"I" => "ABAAA",
"J" => "ABAAB",
"K" => "ABABA",
"L" => "ABABB",
"M" => "ABBAA",
"N" => "ABBAB",
"O" => "ABBBA",
"P" => "ABBBB",
"Q" => "BAAAA",
"R" => "BAAAB",
"S" => "BAABA",
"T" => "BAABB",
"U" => "BABAA",
"V" => "BABAB",
"W" => "BABBA",
"X" => "BABBB",
"Y" => "BBAAA",
"Z" => "BBAAB"
};
}
lazy_static! {
static ref ITALIC_CODES: HashMap<&'static str, char> = hashmap!{
"A" => '\u{1D434}',
"B" => '\u{1D435}',
"C" => '\u{1D436}',
"D" => '\u{1D437}',
"E" => '\u{1D438}',
"F" => '\u{1D439}',
"G" => '\u{1D43a}',
"H" => '\u{1D43b}',
"I" => '\u{1D43c}',
"J" => '\u{1D43d}',
"K" => '\u{1D43e}',
"L" => '\u{1D43f}',
"M" => '\u{1D440}',
"N" => '\u{1D441}',
"O" => '\u{1D442}',
"P" => '\u{1D443}',
"Q" => '\u{1D444}',
"R" => '\u{1D445}',
"S" => '\u{1D446}',
"T" => '\u{1D447}',
"U" => '\u{1D448}',
"V" => '\u{1D449}',
"W" => '\u{1D44a}',
"X" => '\u{1D44b}',
"Y" => '\u{1D44c}',
"Z" => '\u{1D44d}',
"a" => '\u{1D622}',
"b" => '\u{1D623}',
"c" => '\u{1D624}',
"d" => '\u{1D625}',
"e" => '\u{1D626}',
"f" => '\u{1D627}',
"g" => '\u{1D628}',
"h" => '\u{1D629}',
"i" => '\u{1D62a}',
"j" => '\u{1D62b}',
"k" => '\u{1D62c}',
"l" => '\u{1D62d}',
"m" => '\u{1D62e}',
"n" => '\u{1D62f}',
"o" => '\u{1D630}',
"p" => '\u{1D631}',
"q" => '\u{1D632}',
"r" => '\u{1D633}',
"s" => '\u{1D634}',
"t" => '\u{1D635}',
"u" => '\u{1D636}',
"v" => '\u{1D637}',
"w" => '\u{1D638}',
"x" => '\u{1D639}',
"y" => '\u{1D63a}',
"z" => '\u{1D63b}'
};
}
fn get_code(use_distinct_alphabet: bool, key: &str) -> String {
let mut code = String::new();
let mut key_upper = key.to_uppercase();
if !use_distinct_alphabet {
match key_upper.as_str() {
"J" => key_upper = "I".to_string(),
"U" => key_upper = "V".to_string(),
_ => {}
}
}
if CODE_MAP.contains_key(key_upper.as_str()) {
code.push_str(CODE_MAP.get(key_upper.as_str()).unwrap());
}
code
}
fn get_key(code: &str) -> String {
let mut key = String::new();
for (_key, val) in CODE_MAP.iter() {
if val == &code {
key.push_str(_key);
}
}
key
}
pub struct Baconian {
use_distinct_alphabet: bool,
decoy_text: String,
}
impl Cipher for Baconian {
type Key = (bool, Option<String>);
type Algorithm = Baconian;
fn new(key: (bool, Option<String>)) -> Result<Baconian, &'static str> {
Ok(Baconian {
use_distinct_alphabet: key.0,
decoy_text: key.1.unwrap_or_else(|| lipsum(160)),
})
}
fn encrypt(&self, message: &str) -> Result<String, &'static str> {
let mut non_alphas = 0;
for c in self.decoy_text.chars() {
if !c.is_alphabetic() {
non_alphas += 1;
}
}
if (message.len() * CODE_LEN) > self.decoy_text.len() - non_alphas {
return Err("Message too long for supplied decoy text.");
}
let mut decoy_slice = self.decoy_text.clone();
let mut secret = String::new();
for c in message.chars() {
let key = String::from(c.to_string());
secret += &get_code(self.use_distinct_alphabet, &key);
}
let mut alphas = 0;
non_alphas = 0;
for c in self.decoy_text.chars() {
if c.is_alphabetic() {
alphas += 1;
} else {
non_alphas += 1;
}
if alphas == secret.len() {
break;
}
}
decoy_slice.truncate(alphas + non_alphas);
let mut decoy_msg = String::new();
for c in decoy_slice.chars() {
if c.is_alphabetic() {
match secret.remove(0) {
'B' => {
let italic = *ITALIC_CODES.get(c.to_string().as_str()).unwrap();
decoy_msg.push(italic);
}
_ => decoy_msg.push(c),
}
} else {
decoy_msg.push(c);
}
}
Ok(decoy_msg)
}
fn decrypt(&self, message: &str) -> Result<String, &'static str> {
println!("Baconian decrypt");
let mut plaintext = String::new();
let mut ciphertext = String::new();
let mut code = String::new();
for c in message.chars() {
if c.is_alphabetic() {
let mut is_code = false;
for (_key, val) in ITALIC_CODES.iter() {
if *val == c {
is_code = true;
break;
}
}
if is_code {
ciphertext.push('B');
} else {
ciphertext.push('A');
}
}
}
for c in ciphertext.chars() {
code.push(c);
if code.len() == CODE_LEN {
plaintext += &get_key(&code);
code.clear();
}
}
Ok(plaintext)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encrypt_simple() {
let b = Baconian::new((false, None)).unwrap();
let message = "Hello";
let cipher_text = "Lo𝘳𝘦𝘮 ip𝘴um d𝘰l𝘰𝘳 s𝘪t 𝘢𝘮e𝘵, 𝘤𝘰n";
assert_eq!(cipher_text, b.encrypt(message).unwrap());
}
#[test]
fn encrypt_trad_v_dist() {
let b_trad = Baconian::new((false, None)).unwrap();
let b_dist = Baconian::new((true, None)).unwrap();
let message = "I JADE YOU VERVENT UNICORN";
assert_ne!(
b_dist.encrypt(&message).unwrap(),
b_trad.encrypt(message).unwrap()
);
}
#[test]
fn encrypt_message_spaced() {
let decoy_text = String::from(
"The world's a bubble; and the life of man less than a span. \
In his conception wretched; from the womb so to the tomb: \
Curst from the cradle, and brought up to years, with cares and fears. \
Who then to frail mortality shall trust, \
But limns the water, or but writes in dust. \
Yet, since with sorrow here we live oppress'd, what life is best? \
Courts are but only superficial schools to dandle fools: \
The rural parts are turn'd into a den of savage men: \
And where's a city from all vice so free, \
But may be term'd the worst of all the three?",
);
let b = Baconian::new((false, Some(decoy_text))).unwrap();
let message = "Peace, Freedom 🗡️ and Liberty!";
let cipher_text =
"T𝘩𝘦 𝘸𝘰rl𝘥\'s a bubble; an𝘥 the 𝘭ife o𝘧 m𝘢𝘯 les𝘴 th𝘢n a sp𝘢n. \
In hi𝘴 𝘤o𝘯𝘤𝘦pt𝘪𝘰n wretche𝘥; 𝘧r𝘰m th𝘦 𝘸o𝘮b 𝘴𝘰 t𝘰 the tomb: \
𝐶ur𝘴t f𝘳om t𝘩𝘦 cr𝘢𝘥𝘭𝘦, and";
assert_eq!(cipher_text, b.encrypt(message).unwrap());
}
#[test]
#[should_panic(expected = r#"Message too long for supplied decoy text."#)]
fn encrypt_decoy_too_short() {
let b = Baconian::new((false, None)).unwrap();
let message = "This is a long message that will be too long to encode using \
the default decoy text. In order to have a long message encoded you need a \
decoy text that is at least five times as long, plus the non-alphabeticals.";
b.encrypt(message).unwrap();
}
#[test]
fn encrypt_with_use_distinct_alphabet_codeset() {
let message = "Peace, Freedom 🗡️ and Liberty!";
let decoy_text = String::from(
"The world's a bubble; and the life of man less than a span. \
In his conception wretched; from the womb so to the tomb: \
Curst from the cradle, and brought up to years, with cares and fears. \
Who then to frail mortality shall trust, \
But limns the water, or but writes in dust. \
Yet, since with sorrow here we live oppress'd, what life is best? \
Courts are but only superficial schools to dandle fools: \
The rural parts are turn'd into a den of savage men: \
And where's a city from all vice so free, \
But may be term'd the worst of all the three?",
);
let cipher_text =
"T𝘩𝘦 𝘸𝘰rl𝘥's a bubble; an𝘥 the 𝘭ife o𝘧 m𝘢𝘯 les𝘴 th𝘢n a sp𝘢n. \
In hi𝘴 𝘤o𝘯𝘤𝘦pt𝘪𝘰n wretche𝘥; 𝘧r𝘰m th𝘦 𝘸o𝘮b 𝘴𝘰 t𝘰 the tomb: \
𝐶ur𝘴t f𝘳om t𝘩𝘦 cr𝘢𝘥𝘭𝘦, and";
let b = Baconian::new((true, Some(decoy_text))).unwrap();
assert_eq!(cipher_text, b.encrypt(message).unwrap());
}
#[test]
fn decrypt_a_classic() {
let cipher_text =
String::from("Let's c𝘰mp𝘳𝘰𝘮is𝘦. 𝐻old off th𝘦 at𝘵a𝘤k");
let message = "ATTACK";
let decoy_text = String::from("Let's compromise. Hold off the attack");
let b = Baconian::new((true, Some(decoy_text))).unwrap();
assert_eq!(message, b.decrypt(&cipher_text).unwrap());
}
#[test]
fn decrypt_traditional() {
let cipher_text =
String::from(
"T𝘩e wor𝘭d's a bubble; an𝘥 𝘵he 𝘭if𝘦 𝘰f man 𝘭𝘦𝘴s 𝘵h𝘢n 𝘢 𝘴p𝘢n. \
𝐼n h𝘪s c𝘰nce𝘱𝘵i𝘰n 𝘸re𝘵che𝘥; 𝘧r𝘰𝘮 th𝘦 𝘸𝘰m𝘣 s𝘰 t𝘰 𝘵h𝘦 t𝘰mb: \
Curs𝘵 fr𝘰𝘮 𝘵h𝘦 cra𝘥l𝘦, 𝘢n𝘥"
);
let message = "IIADEYOVVERVENTVNICORN";
let decoy_text = String::from(
"The world's a bubble; and the life of man less than a span. \
In his conception wretched; from the womb so to the tomb: \
Curst from the cradle, and brought up to years, with cares and fears. \
Who then to frail mortality shall trust, \
But limns the water, or but writes in dust. \
Yet, since with sorrow here we live oppress'd, what life is best? \
Courts are but only superficial schools to dandle fools: \
The rural parts are turn'd into a den of savage men: \
And where's a city from all vice so free, \
But may be term'd the worst of all the three?",
);
let b = Baconian::new((false, Some(decoy_text))).unwrap();
assert_eq!(message, b.decrypt(&cipher_text).unwrap());
}
}