use crate::cryptable::{Cipher, Crypt};
use crate::errors::CharNotInKeyError;
use rustc_hash::FxHashMap;
use crate::structs::{CryptModus, CryptResult, Payload, SquarePosition};
pub(crate) const EMPTY_SQ_POS: &SquarePosition = &SquarePosition {
column: 42,
row: 42,
};
use std::collections::HashMap;
const KEY_CARS: &str = "ABCDEFGHIKLMNOPQRSTUVWXYZ";
const KEY_CARS_A_TO_Z_0_TO_9: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
#[derive(Debug)]
pub struct PlayFairKey {
pub(crate) key: Vec<char>,
pub(crate) key_map: FxHashMap<char, SquarePosition>,
pub(crate) row_len: u8,
pub(crate) square: u8,
fptr: fn(&str) -> String,
}
impl PlayFairKey {
pub fn new_5_to_5(key: &str) -> Self {
create_play_fair_key(key, KEY_CARS)
}
pub fn new_6_to_6(key: &str) -> Self {
create_play_fair_key(key, KEY_CARS_A_TO_Z_0_TO_9)
}
pub(crate) fn payload(&self, payload: &str) -> String {
if self.row_len == 4 {
PlayFairKey::payload_5_to_5(payload)
} else {
PlayFairKey::payload_6_to_6(payload)
}
}
fn payload_6_to_6(payload: &str) -> String {
let mut counter: usize = 0;
let mut payload_cleared = String::with_capacity(payload.len());
let payload_uc = payload.to_uppercase();
while counter < payload_uc.len() {
let character = &payload_uc[counter..counter + 1];
if ("A"..="Z").contains(&character) || ("0"..="9").contains(&character) {
payload_cleared += character;
}
counter += 1;
}
payload_cleared
}
fn payload_5_to_5(payload: &str) -> String {
let mut counter: usize = 0;
let mut payload_cleared = String::with_capacity(payload.len());
let payload_uc = payload.to_uppercase();
while counter < payload_uc.len() {
let character = &payload_uc[counter..counter + 1];
if character == "J" {
payload_cleared += "I";
} else if ("A"..="Z").contains(&character) {
payload_cleared += character;
}
counter += 1;
}
payload_cleared
}
}
fn create_play_fair_key(key: &str, key_chars: &str) -> PlayFairKey {
let raw_key: String = key.to_uppercase() + key_chars;
let mut temp_key = String::with_capacity(key_chars.len());
let row_len = match key_chars.len() {
25 => 4,
_ => 5,
};
let fptr = match key_chars.len() {
25 => PlayFairKey::payload_5_to_5,
_ => PlayFairKey::payload_6_to_6,
};
let mut counter = 0;
let mut row_counter = 0;
let mut col_counter = 0;
let mut key_map: FxHashMap<char, SquarePosition> = FxHashMap::default();
while counter < raw_key.len() && temp_key.len() < key_chars.len() {
if col_counter > row_len {
col_counter = 0;
row_counter += 1;
}
let temp_key_char = &raw_key[counter..counter + 1];
counter += 1;
if temp_key.contains(temp_key_char) || !key_chars.contains(temp_key_char) {
continue;
} else {
temp_key += temp_key_char;
let temp_key_char_vec: Vec<char> = temp_key_char.chars().collect();
key_map.insert(
match temp_key_char_vec.first() {
Some(k) => *k,
None => '*',
},
SquarePosition {
row: row_counter,
column: col_counter,
},
);
col_counter += 1;
}
}
PlayFairKey {
key: temp_key.chars().collect(),
key_map,
row_len,
square: row_len + 1,
fptr,
}
}
impl Crypt for PlayFairKey {
fn crypt(
&self,
a: char,
b: char,
modus: &CryptModus,
) -> Result<CryptResult, CharNotInKeyError> {
let a_sq_pos = match self.key_map.get(&a) {
Some(p) => p,
None => EMPTY_SQ_POS,
};
let b_sq_pos = match self.key_map.get(&b) {
Some(p) => p,
None => EMPTY_SQ_POS,
};
if a_sq_pos.column == EMPTY_SQ_POS.column {
return Err(CharNotInKeyError::new(format!(
"Only chars A-Z possible - '{}' was not found in key {:?}",
a, &self.key
)));
} else if b_sq_pos.column == EMPTY_SQ_POS.column {
return Err(CharNotInKeyError::new(format!(
"Only chars A-Z possible - '{}' was not found in key {:?}",
b, &self.key
)));
}
let mut a_crypted_idx: u8 = 0;
let mut b_crypted_idx: u8 = 0;
if a_sq_pos.column != b_sq_pos.column && a_sq_pos.row != b_sq_pos.row {
a_crypted_idx = a_sq_pos.row * self.square + b_sq_pos.column;
b_crypted_idx = b_sq_pos.row * self.square + a_sq_pos.column;
} else if a_sq_pos.column == b_sq_pos.column {
if modus == &CryptModus::Encrypt {
if a_sq_pos.row == self.row_len {
a_crypted_idx = a_sq_pos.column;
} else {
a_crypted_idx = (a_sq_pos.row + 1) * self.square + a_sq_pos.column
}
if b_sq_pos.row == self.row_len {
b_crypted_idx = b_sq_pos.column;
} else {
b_crypted_idx = (b_sq_pos.row + 1) * self.square + b_sq_pos.column
}
} else {
if a_sq_pos.row == 0 {
a_crypted_idx = 20 + a_sq_pos.column;
} else {
a_crypted_idx = (a_sq_pos.row - 1) * self.square + a_sq_pos.column;
}
if b_sq_pos.row == 0 {
b_crypted_idx = 20 + b_sq_pos.column;
} else {
b_crypted_idx = (b_sq_pos.row - 1) * self.square + b_sq_pos.column;
}
}
} else if a_sq_pos.row == b_sq_pos.row {
if modus == &CryptModus::Encrypt {
if a_sq_pos.column == self.row_len {
a_crypted_idx = a_sq_pos.row * self.square;
} else {
a_crypted_idx = a_sq_pos.row * self.square + a_sq_pos.column + 1;
}
if b_sq_pos.column == self.row_len {
b_crypted_idx = b_sq_pos.row * self.square;
} else {
b_crypted_idx = b_sq_pos.row * self.square + b_sq_pos.column + 1;
}
} else {
if a_sq_pos.column == 0 {
a_crypted_idx = (a_sq_pos.row * self.square) + self.row_len;
} else {
a_crypted_idx = a_sq_pos.row * self.square + a_sq_pos.column - 1;
}
if b_sq_pos.column == 0 {
b_crypted_idx = (b_sq_pos.row * self.square) + self.row_len;
} else {
b_crypted_idx = b_sq_pos.row * self.square + b_sq_pos.column - 1;
}
}
}
let a_crypted: char = match self.key.get(a_crypted_idx as usize) {
Some(c) => *c,
None => '*',
};
let b_crypted: char = match self.key.get(b_crypted_idx as usize) {
Some(c) => *c,
None => '*',
};
Ok(CryptResult {
a: a_crypted,
b: b_crypted,
})
}
fn crypt_payload(
&self,
payload: &str,
modus: &crate::structs::CryptModus,
) -> Result<String, crate::errors::CharNotInKeyError> {
let mut payload_iter = Payload::new(self.payload(payload));
payload_iter.crypt_payload(self, modus)
}
fn playload(&self, payload: &str) -> String {
(self.fptr)(payload)
}
}
impl Cipher for PlayFairKey {
fn encrypt(&self, payload: &str) -> Result<String, CharNotInKeyError> {
self.crypt_payload(payload, &CryptModus::Encrypt)
}
fn decrypt(&self, payload: &str) -> Result<String, CharNotInKeyError> {
self.crypt_payload(payload, &CryptModus::Decrypt)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_payload() {
let pfk = PlayFairKey::new_5_to_5("");
let payload = Payload::new(pfk.payload("I would like 4 tins of jam."));
assert_eq!(payload.payload, "IWOULDLIKETINSOFIAM");
}
#[test]
fn test_payload_6_to_6() {
let pfk = PlayFairKey::new_6_to_6("");
let payload = Payload::new(pfk.payload("I would like 4 tins of jam."));
assert_eq!(payload.payload, "IWOULDLIKE4TINSOFJAM");
}
#[test]
fn test_key_gen_empty_key() {
let pfk = PlayFairKey::new_5_to_5("");
assert_eq!(
pfk.key,
vec![
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
]
)
}
#[test]
fn test_key_gen_empty_key_6_to_6() {
let pfk = PlayFairKey::new_6_to_6("");
assert_eq!(
pfk.key,
vec![
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9'
]
)
}
#[test]
fn test_key_gen_simple() {
let pfk = PlayFairKey::new_5_to_5("simple");
assert_eq!(
pfk.key,
vec![
'S', 'I', 'M', 'P', 'L', 'E', 'A', 'B', 'C', 'D', 'F', 'G', 'H', 'K', 'N', 'O',
'Q', 'R', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
]
)
}
#[test]
fn test_key_gen_seecretisjj() {
let pfk = PlayFairKey::new_5_to_5("seecretisJJ");
assert_eq!(
pfk.key,
vec![
'S', 'E', 'C', 'R', 'T', 'I', 'A', 'B', 'D', 'F', 'G', 'H', 'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'U', 'V', 'W', 'X', 'Y', 'Z'
]
)
}
#[test]
fn test_key_gen_seecretisjj_6_to_6() {
let pfk = PlayFairKey::new_6_to_6("seecretisJJ");
assert_eq!(
pfk.key,
vec![
'S', 'E', 'C', 'R', 'T', 'I', 'J', 'A', 'B', 'D', 'F', 'G', 'H', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9'
]
)
}
#[test]
fn test_key_gen_zxy_and_so_on() {
let pfk = PlayFairKey::new_5_to_5("ZYXWVUTSRQPONMLKJIHGFECA");
assert_eq!(
pfk.key,
vec![
'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K',
'I', 'H', 'G', 'F', 'E', 'C', 'A', 'B', 'D'
]
)
}
#[test]
fn test_iterator() {
let pfk = PlayFairKey::new_5_to_5("key");
let mut payload = Payload::new(pfk.payload("my secret message"));
let mut digrams: Vec<[char; 2]> = Vec::new();
loop {
let digram = payload.next();
let [a, b] = match digram {
Some(d) => d,
None => break,
};
digrams.push([a, b]);
}
assert_eq!(
digrams,
vec![
['M', 'Y'],
['S', 'E'],
['C', 'R'],
['E', 'T'],
['M', 'E'],
['S', 'X'],
['S', 'A'],
['G', 'E']
]
);
}
#[test]
fn test_encrypt_square_rule_one_char() {
let pfx = PlayFairKey::new_5_to_5("secret");
match pfx.encrypt("a") {
Ok(s) => assert_eq!(s, "DV"),
Err(e) => panic!("CharNotInKeyError {}", e),
};
}
#[test]
fn test_position_map() {
let pfx = PlayFairKey::new_5_to_5("playfair example");
let valid_positions: Vec<SquarePosition> = vec![
SquarePosition { row: 0, column: 0 },
SquarePosition { row: 0, column: 1 },
SquarePosition { row: 0, column: 2 },
SquarePosition { row: 0, column: 3 },
SquarePosition { row: 0, column: 4 },
SquarePosition { row: 1, column: 0 },
SquarePosition { row: 1, column: 1 },
SquarePosition { row: 1, column: 2 },
SquarePosition { row: 1, column: 3 },
SquarePosition { row: 1, column: 4 },
SquarePosition { row: 2, column: 0 },
SquarePosition { row: 2, column: 1 },
SquarePosition { row: 2, column: 2 },
SquarePosition { row: 2, column: 3 },
SquarePosition { row: 2, column: 4 },
SquarePosition { row: 3, column: 0 },
SquarePosition { row: 3, column: 1 },
SquarePosition { row: 3, column: 2 },
SquarePosition { row: 3, column: 3 },
SquarePosition { row: 3, column: 4 },
SquarePosition { row: 4, column: 0 },
SquarePosition { row: 4, column: 1 },
SquarePosition { row: 4, column: 2 },
SquarePosition { row: 4, column: 3 },
SquarePosition { row: 4, column: 4 },
];
let mut valid_positions_iter = valid_positions.iter();
let empty_must_be_sqrt_pos = SquarePosition {
row: 43,
column: 43,
};
for (counter, c) in pfx.key.into_iter().enumerate() {
let must_be_sqrt_pos = match valid_positions_iter.next() {
Some(t) => t,
None => &empty_must_be_sqrt_pos,
};
let check_sqrt_pos = match pfx.key_map.get(&c) {
Some(t) => t,
None => EMPTY_SQ_POS,
};
assert_eq!(
check_sqrt_pos.row, must_be_sqrt_pos.row,
"row assertion failed at iteration {}",
counter
);
assert_eq!(
check_sqrt_pos.column, must_be_sqrt_pos.column,
"column assertion failed at iteration {}",
counter
);
}
}
#[test]
fn test_crypt_square() {
let pfc = PlayFairKey::new_5_to_5("playfair example");
match pfc.crypt('H', 'I', &CryptModus::Encrypt) {
Ok(digram_crypt) => {
assert_eq!(digram_crypt.a, 'B');
assert_eq!(digram_crypt.b, 'M');
}
Err(e) => panic!("CharNotInKeyError {}", e),
};
match pfc.crypt('B', 'M', &CryptModus::Decrypt) {
Ok(digram_crypt) => {
assert_eq!(
digram_crypt.a, 'H',
"decrypt B failed - transformed to {} key {:?}",
digram_crypt.a, pfc.key
);
assert_eq!(
digram_crypt.b, 'I',
"decrypt M failed - transformed to {} ",
digram_crypt.b
);
}
Err(e) => panic!("CharNotInKeyError {}", e),
};
}
#[test]
fn test_crypt_column() {
let pfc = PlayFairKey::new_5_to_5("playfair example");
match pfc.crypt('D', 'E', &CryptModus::Encrypt) {
Ok(digram_crypt) => {
assert_eq!(digram_crypt.a, 'O');
assert_eq!(digram_crypt.b, 'D');
}
Err(e) => panic!("CharNotInKeyError {}", e),
};
match pfc.crypt('O', 'D', &CryptModus::Decrypt) {
Ok(digram_crypt) => {
assert_eq!(digram_crypt.a, 'D');
assert_eq!(digram_crypt.b, 'E');
}
Err(e) => panic!("CharNotInKeyError {}", e),
};
match pfc.crypt('A', 'V', &CryptModus::Encrypt) {
Ok(digram_crypt) => {
assert_eq!(digram_crypt.a, 'E');
assert_eq!(digram_crypt.b, 'A');
}
Err(e) => panic!("CharNotInKeyError {}", e),
};
match pfc.crypt('E', 'A', &CryptModus::Decrypt) {
Ok(digram_crypt) => {
assert_eq!(digram_crypt.a, 'A');
assert_eq!(digram_crypt.b, 'V', "A transforms to {}", digram_crypt.b);
}
Err(e) => panic!("CharNotInKeyError {}", e),
};
}
#[test]
fn test_crypt_row() {
let pfc = PlayFairKey::new_5_to_5("playfair example");
match pfc.crypt('E', 'X', &CryptModus::Encrypt) {
Ok(digram_crypt) => {
assert_eq!(
digram_crypt.a, 'X',
"E transfers to {} key {:?}",
digram_crypt.a, pfc.key
);
assert_eq!(digram_crypt.b, 'M');
}
Err(e) => panic!("CharNotInKeyError {}", e),
};
match pfc.crypt('X', 'M', &CryptModus::Decrypt) {
Ok(digram_crypt) => {
assert_eq!(
digram_crypt.a, 'E',
"X transfers to {} key {:?}",
digram_crypt.a, pfc.key
);
assert_eq!(digram_crypt.b, 'X');
}
Err(e) => panic!("CharNotInKeyError {}", e),
};
match pfc.crypt('I', 'M', &CryptModus::Encrypt) {
Ok(digram_crypt) => {
assert_eq!(
digram_crypt.a, 'R',
"I transfers to {} key {:?}",
digram_crypt.a, pfc.key
);
assert_eq!(digram_crypt.b, 'I');
}
Err(e) => panic!("CharNotInKeyError {}", e),
};
match pfc.crypt('R', 'I', &CryptModus::Decrypt) {
Ok(digram_crypt) => {
assert_eq!(
digram_crypt.a, 'I',
"R transfers to {} key {:?}",
digram_crypt.a, pfc.key
);
assert_eq!(digram_crypt.b, 'M');
}
Err(e) => panic!("CharNotInKeyError {}", e),
};
}
#[test]
fn test_encrypt() {
let pfc = PlayFairKey::new_5_to_5("rust rules");
match pfc.encrypt(&String::from("cratesio")) {
Ok(crypt) => {
assert_eq!(crypt, String::from("ETCUBRHP"));
}
Err(e) => panic!("CharNotInKeyError {}", e),
};
}
#[test]
fn test_decrypt() {
let pfc = PlayFairKey::new_5_to_5("rustrules");
match pfc.decrypt(&String::from("ETCUBRHP")) {
Ok(crypt) => {
assert_eq!(crypt, String::from("cratesio").to_uppercase());
}
Err(e) => panic!("CharNotInKeyError {}", e),
};
}
}