use crate::{input, Cipher, CipherInputError, CipherResult};
pub struct Playfair {
key: String,
pad: u8,
}
impl Playfair {
pub fn new(key: &str, pad: char) -> Self {
assert_eq!(key.len(), 25, "`key` must be 25 chars in length");
input::is_ascii(key).expect("`key` must be valid ascii");
input::no_repeated_chars(key).expect("`key` cannot contain repeated chars");
assert_ne!(key.find(pad), None, "`key` must contain `pad`");
Self {
key: String::from(key),
pad: pad as u8,
}
}
}
impl Cipher for Playfair {
fn encipher(&self, ptext: &str) -> CipherResult {
input::in_alphabet(ptext, &self.key)?;
let mut ptext: Vec<u8> = ptext.bytes().collect();
if ptext.len() % 2 != 0 {
ptext.push(self.pad);
}
let key = self.key.as_bytes();
let mut ctext = Vec::with_capacity(ptext.len());
for i in (0..ptext.len()).step_by(2) {
if ptext[i] == ptext[i + 1] {
ptext[i + 1] = self.pad;
}
let yx1 = match key.iter().position(|&c| c == ptext[i]) {
Some(val) => val,
None => return Err(CipherInputError::NotInAlphabet),
};
let yx2 = match key.iter().position(|&c| c == ptext[i + 1]) {
Some(val) => val,
None => return Err(CipherInputError::NotInAlphabet),
};
let (y1, x1) = (yx1 / 5, yx1 % 5);
let (y2, x2) = (yx2 / 5, yx2 % 5);
if y1 != y2 && x1 != x2 {
ctext.push(key[y1 * 5 + x2]);
ctext.push(key[y2 * 5 + x1]);
} else if y1 == y2 {
ctext.push(key[y1 * 5 + (x1 + 1) % 5]);
ctext.push(key[y2 * 5 + (x2 + 1) % 5]);
} else if x1 == x2 {
ctext.push(key[(y1 + 1) % 5 * 5 + x1]);
ctext.push(key[(y2 + 1) % 5 * 5 + x2]);
}
}
Ok(String::from_utf8(ctext).unwrap())
}
fn decipher(&self, ctext: &str) -> CipherResult {
input::in_alphabet(ctext, &self.key)?;
if ctext.len() % 2 != 0 {
return Err(CipherInputError::BadInput(String::from(
"`ctext` must contain an even number of chars",
)));
}
let ctext = ctext.as_bytes();
let key = self.key.as_bytes();
let mut ptext = Vec::with_capacity(ctext.len());
for i in (0..ctext.len()).step_by(2) {
let yx1 = match key.iter().position(|&c| c == ctext[i]) {
Some(val) => val,
None => return Err(CipherInputError::NotInAlphabet),
};
let yx2 = match key.iter().position(|&c| c == ctext[i + 1]) {
Some(val) => val,
None => return Err(CipherInputError::NotInAlphabet),
};
let (y1, x1) = (yx1 / 5, yx1 % 5);
let (y2, x2) = (yx2 / 5, yx2 % 5);
if y1 != y2 && x1 != x2 {
ptext.push(key[y1 * 5 + x2]);
ptext.push(key[y2 * 5 + x1]);
} else if y1 == y2 {
ptext.push(key[y1 * 5 + (x1 + 5 - 1) % 5]);
ptext.push(key[y2 * 5 + (x2 + 5 - 1) % 5]);
} else if x1 == x2 {
ptext.push(key[(y1 + 5 - 1) % 5 * 5 + x1]);
ptext.push(key[(y2 + 5 - 1) % 5 * 5 + x2]);
}
}
Ok(String::from_utf8(ptext).unwrap())
}
}