use common::{alphabet, alphabet::Alphabet, cipher::Cipher, keygen::PlayfairTable};
const PLAYFAIR_FIX_CHAR: char = 'X';
pub struct Playfair {
table: PlayfairTable,
}
impl Cipher for Playfair {
type Key = String;
type Algorithm = Playfair;
fn new(key: Self::Key) -> Result<Playfair, &'static str> {
let key_table = PlayfairTable::new(&key)?;
Ok(Playfair { table: key_table })
}
fn encrypt(&self, message: &str) -> Result<String, &'static str> {
let message: String = message.split_whitespace().collect();
if !alphabet::STANDARD.is_valid(message.as_str()) {
return Err("Message must only consist of alphabetic characters");
}
let bmsg = bigram(message.to_uppercase())?;
apply_rules(bmsg, &self.table, |v, first, second| {
(v[(first + 1) % 5], v[(second + 1) % 5])
})
}
fn decrypt(&self, message: &str) -> Result<String, &'static str> {
let message: String = message.split_whitespace().collect();
if !alphabet::STANDARD.is_valid(message.as_str()) {
return Err("Message must only consist of alphabetic characters");
}
let bmsg = bigram(message.to_uppercase())?;
apply_rules(bmsg, &self.table, |v, first, second| {
(v[(first - 1) % 5], v[(second - 1) % 5])
})
}
}
type Bigram = (char, char);
fn bigram<S: AsRef<str>>(message: S) -> Result<Vec<Bigram>, &'static str> {
if message.as_ref().contains(char::is_whitespace) {
return Err("Message contains whitespace");
}
if !alphabet::STANDARD.is_valid(message.as_ref()) {
return Err("Message must only consist of alphabetic characters");
}
let mut iter = message.as_ref().chars().peekable();
let mut bigrams: Vec<Bigram> = Vec::new();
loop {
let first: char;
if let Some(x) = iter.next() {
first = x;
} else {
break;
}
if let Some(y) = iter.peek() {
if *y == first {
bigrams.push((first, PLAYFAIR_FIX_CHAR));
continue;
}
}
if let Some(y) = iter.next() {
bigrams.push((first, y));
} else {
bigrams.push((first, PLAYFAIR_FIX_CHAR));
}
}
Ok(bigrams)
}
fn apply_row_col<F>(b: &Bigram, row_col: &[String; 5], shift: &F) -> Option<Bigram>
where
F: Fn(Vec<char>, usize, usize) -> Bigram,
{
for rc in row_col.iter() {
if let Some(first) = rc.find(b.0) {
if let Some(second) = rc.find(b.1) {
let v: Vec<char> = rc.chars().collect();
return Some(shift(v, first, second));
}
}
}
None
}
fn find_separate(b: &Bigram, table: &[String; 5]) -> (usize, usize) {
let mut result = (0, 0);
for rc in table.iter() {
if let Some(pos) = rc.find(b.0) {
result.0 = pos;
continue;
}
if let Some(pos) = rc.find(b.1) {
result.1 = pos;
continue;
}
}
result
}
fn apply_rectangle(b: &Bigram, table: &PlayfairTable) -> Bigram {
let rows = find_separate(&b, &table.cols);
let cols = find_separate(&b, &table.rows);
let row0: Vec<char> = table.rows[rows.0].chars().collect();
let row1: Vec<char> = table.rows[rows.1].chars().collect();
(row0[cols.1], row1[cols.0])
}
fn apply_rules<F>(
bigrams: Vec<Bigram>,
table: &PlayfairTable,
shift: F,
) -> Result<String, &'static str>
where
F: Fn(Vec<char>, usize, usize) -> Bigram,
{
let mut text = String::new();
for b in bigrams {
if let Some(bigram) = apply_row_col(&b, &table.rows, &shift) {
text.push(bigram.0);
text.push(bigram.1);
continue;
}
if let Some(bigram) = apply_row_col(&b, &table.cols, &shift) {
text.push(bigram.0);
text.push(bigram.1);
continue;
}
let bigram = apply_rectangle(&b, &table);
text.push(bigram.0);
text.push(bigram.1);
}
Ok(text)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bigram_accepts_alpha_message() {
assert!(bigram("HelloWorld").is_ok());
}
#[test]
fn bigram_handles_repeats() {
let message = "FIZZBAR";
let mut expected: Vec<Bigram> = Vec::new();
expected.push(('F', 'I'));
expected.push(('Z', PLAYFAIR_FIX_CHAR));
expected.push(('Z', 'B'));
expected.push(('A', 'R'));
assert!(bigram(message).is_ok());
assert_eq!(bigram(message).unwrap(), expected);
}
#[test]
fn bigram_handles_odd_length() {
let message = "WORLD";
let mut expected: Vec<Bigram> = Vec::new();
expected.push(('W', 'O'));
expected.push(('R', 'L'));
expected.push(('D', PLAYFAIR_FIX_CHAR));
assert!(bigram(message).is_ok());
assert_eq!(bigram(message).unwrap(), expected);
}
#[test]
fn bigram_errors_on_spaces() {
assert!(bigram("Has Spaces").is_err());
}
#[test]
fn bigram_errors_on_nonalpha() {
assert!(bigram("Bad123").is_err());
}
#[test]
fn cipher_encrypts_std_message() {
let cipher = Playfair::new("playfair example".to_string()).unwrap();
assert!(cipher.encrypt("Hide the gold in the tree stump").is_ok());
assert_eq!(
cipher.encrypt("Hide the gold in the tree stump").unwrap(),
"BMODZBXDNABEKUDMUIXMMOUVIF"
);
}
#[test]
fn cipher_decrypts_std_message() {
let cipher = Playfair::new("playfair example".to_string()).unwrap();
assert!(cipher.decrypt("BMODZBXDNABEKUDMUIXMMOUVIF").is_ok());
assert_eq!(
cipher.decrypt("BMODZBXDNABEKUDMUIXMMOUVIF").unwrap(),
"HIDETHEGOLDINTHETREXESTUMP"
);
}
#[test]
fn new_accepts_alpha_key() {
assert!(Playfair::new("Foo".to_string()).is_ok());
}
#[test]
fn new_accepts_spaced_key() {
assert!(Playfair::new("Foo Bar".to_string()).is_ok());
}
#[test]
fn new_rejects_alphanumeric_key() {
assert!(Playfair::new("Bad123".to_string()).is_err());
}
#[test]
fn new_rejects_symbolic_key() {
assert!(Playfair::new("Bad?".to_string()).is_err());
}
#[test]
fn new_rejects_unicode_key() {
assert!(Playfair::new("Bad☢".to_string()).is_err());
}
#[test]
fn encrypt_accepts_spaced_message() {
let cipher = Playfair::new("Foo".to_string()).unwrap();
assert!(cipher.encrypt("Bar Baz").is_ok());
}
#[test]
fn encrypt_rejects_alphanumeric_message() {
let cipher = Playfair::new("Foo".to_string()).unwrap();
assert!(cipher.encrypt("Bad123").is_err());
}
#[test]
fn encrypt_rejects_symbolic_message() {
let cipher = Playfair::new("Foo".to_string()).unwrap();
assert!(cipher.encrypt("Bad?").is_err());
}
#[test]
fn encrypt_rejects_unicode_message() {
let cipher = Playfair::new("Foo".to_string()).unwrap();
assert!(cipher.encrypt("Bad☢").is_err());
}
#[test]
fn decrypt_accepts_spaced_message() {
let cipher = Playfair::new("Foo".to_string()).unwrap();
assert!(cipher.decrypt("Bar Baz").is_ok());
}
#[test]
fn decrypt_rejects_alphanumeric_message() {
let cipher = Playfair::new("Foo".to_string()).unwrap();
assert!(cipher.decrypt("Bad123").is_err());
}
#[test]
fn decrypt_rejects_symbolic_message() {
let cipher = Playfair::new("Foo".to_string()).unwrap();
assert!(cipher.decrypt("Bad?").is_err());
}
#[test]
fn decrypt_rejects_unicode_message() {
let cipher = Playfair::new("Foo".to_string()).unwrap();
assert!(cipher.decrypt("Bad☢").is_err());
}
}