use common::cipher::Cipher;
use common::{substitute, alphabet};
use common::alphabet::Alphabet;
pub struct Autokey {
key: String,
}
impl Cipher for Autokey {
type Key = String;
type Algorithm = Autokey;
fn new(key: String) -> Result<Autokey, &'static str> {
if key.len() < 1 {
return Err("Invalid key. It must have at least one character.");
} else if !alphabet::STANDARD.is_valid(&key) {
return Err("Invalid key. Autokey keys cannot contain non-alphabetic symbols.");
}
Ok(Autokey { key: key })
}
fn encrypt(&self, message: &str) -> Result<String, &'static str> {
substitute::key_substitution(message, &mut self.encrypt_keystream(message),
|mi, ki| alphabet::STANDARD.modulo((mi + ki) as isize))
}
fn decrypt(&self, ciphertext: &str) -> Result<String, &'static str> {
self.autokey_decrypt(ciphertext)
}
}
impl Autokey {
fn autokey_decrypt(&self, ciphertext: &str) -> Result<String, &'static str> {
let mut plaintext = String::new();
let mut keystream: Vec<char> = String::from(self.key.clone()).chars().collect();
for cc in ciphertext.chars() {
let pos = alphabet::STANDARD.find_position(cc);
match pos {
Some(ci) => {
if keystream.len() < 1 {
return Err("Keystream is not large enough for full substitution of message");
}
let kc = keystream[0];
if let Some(ki) = alphabet::STANDARD.find_position(kc) {
let si = alphabet::STANDARD.modulo(ci as isize - ki as isize);
let s = alphabet::STANDARD.get_letter(si, cc.is_uppercase()).unwrap();
plaintext.push(s);
keystream.push(s);
keystream.remove(0); } else {
return Err("Keystream contains a non-alphabetic symbol.")
}
},
None => plaintext.push(cc), }
}
Ok(plaintext)
}
fn encrypt_keystream(&self, message: &str) -> Vec<char> {
let scrubbed_msg = alphabet::STANDARD.scrub(&message);
if self.key.len() >= scrubbed_msg.len() {
return self.key[0..scrubbed_msg.len()].chars().collect();
}
let mut keystream = String::from(self.key.clone());
keystream.push_str(&scrubbed_msg);
keystream[0..scrubbed_msg.len()].chars().collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn with_utf8() {
let m = "Attack 🗡️ the east wall";
let a = Autokey::new(String::from("fort")).unwrap();
assert_eq!(m, a.decrypt(&a.encrypt(m).unwrap()).unwrap());
}
#[test]
fn simple_encrypt_decrypt_test() {
let message = "defend the east wall of the castle";
let v = Autokey::new(String::from("fortification")).unwrap();
let c_text = v.encrypt(message).unwrap();
let p_text = v.decrypt(&c_text).unwrap();
assert_eq!(message, p_text);
}
#[test]
fn decrypt_test() {
let ciphertext = "lxfopktmdcgn";
let v = Autokey::new(String::from("lemon")).unwrap();
assert_eq!("attackatdawn", v.decrypt(ciphertext).unwrap());
}
#[test]
fn larger_base_key() {
let message = "Hello";
let v = Autokey::new(String::from("fortification")).unwrap();
assert_eq!(vec!['f', 'o', 'r', 't', 'i'], v.encrypt_keystream(message));
}
#[test]
fn smaller_base_key() {
let message = "We are under seige";
let v = Autokey::new(String::from("lemon")).unwrap();
assert_eq!(vec!['l', 'e', 'm', 'o', 'n', 'W',
'e', 'a', 'r', 'e', 'u', 'n',
'd', 'e', 'r'], v.encrypt_keystream(message));
}
#[test]
fn valid_key() {
assert!(Autokey::new(String::from("LeMon")).is_ok());
}
#[test]
fn key_with_symbols() {
assert!(Autokey::new(String::from("!em@n")).is_err());
}
#[test]
fn key_with_whitespace() {
assert!(Autokey::new(String::from("wow this key is a real lemon")).is_err());
}
}