use super::alphabet;
use super::alphabet::{Alphabet, ALPHANUMERIC, STANDARD};
use std::collections::HashMap;
pub fn keyed_alphabet<T: Alphabet>(
key: &str,
alpha_type: T,
to_uppercase: bool,
) -> Result<String, &'static str> {
if !alpha_type.is_valid(key) {
return Err("Invalid key. Key cannot contain non-alphabetic symbols.");
}
let mut keyed_alphabet = String::new();
for c in key.chars() {
if keyed_alphabet
.chars()
.find(|a| a.eq_ignore_ascii_case(&c))
.is_none()
{
if to_uppercase {
keyed_alphabet.push_str(&c.to_uppercase().to_string())
} else {
keyed_alphabet.push_str(&c.to_lowercase().to_string())
}
}
}
for index in 0..alpha_type.length() {
let c = alpha_type.get_letter(index, to_uppercase).unwrap();
if keyed_alphabet
.chars()
.find(|a| a.eq_ignore_ascii_case(&c))
.is_none()
{
keyed_alphabet.push(c);
}
}
Ok(keyed_alphabet)
}
pub fn columnar_key(key: &str) -> Result<Vec<(char, Vec<char>)>, &'static str> {
let unique_chars: HashMap<_, _> = key.chars().into_iter().map(|c| (c, c)).collect();
if key.is_empty() {
return Err("The key cannot be zero length.");
} else if key.len() - unique_chars.len() > 0 {
return Err("The key cannot contain duplicate alphanumeric characters.");
} else if !ALPHANUMERIC.is_valid(key) {
return Err("The key cannot contain non-alphanumeric symbols.");
}
let mut c_key: Vec<(char, Vec<char>)> = Vec::new();
for chr in key.chars() {
c_key.push((chr, Vec::new()));
}
Ok(c_key)
}
pub fn polybius_square(
key: &str,
column_ids: [char; 6],
row_ids: [char; 6],
) -> Result<HashMap<String, char>, &'static str> {
let unique_chars: HashMap<_, _> = key.chars().into_iter().map(|c| (c, c)).collect();
if key.len() != 36 {
return Err("The key must contain each character of the alphanumeric alphabet a-z 0-9.");
} else if key.len() - unique_chars.len() > 0 {
return Err("The key cannot contain duplicate alphanumeric characters.");
} else if !ALPHANUMERIC.is_valid(key) {
return Err("The key cannot contain non-alphanumeric symbols.");
}
if !STANDARD.is_valid(&column_ids.iter().cloned().collect::<String>())
|| !STANDARD.is_valid(&row_ids.iter().cloned().collect::<String>())
{
return Err("The column and row ids cannot contain non-alphabetic symbols.");
}
let unique_cols: HashMap<_, _> = column_ids
.iter()
.cloned()
.map(|c| (c.to_ascii_lowercase(), c))
.collect();
let unique_rows: HashMap<_, _> = row_ids
.iter()
.cloned()
.map(|c| (c.to_ascii_lowercase(), c))
.collect();
if column_ids.len() - unique_cols.len() > 0 || row_ids.len() - unique_rows.len() > 0 {
return Err("The column or row ids cannot contain repeated characters.");
}
let mut polybius_square = HashMap::new();
let mut values = key.chars().into_iter();
for r in 0..6 {
for c in 0..6 {
let k = row_ids[r].to_string() + &column_ids[c].to_string();
let v = values.next().expect("alphabet square is invalid");
if alphabet::is_numeric(v) {
polybius_square.insert(k.to_uppercase(), v.to_ascii_uppercase());
} else {
polybius_square.insert(k.to_lowercase(), v.to_ascii_lowercase());
polybius_square.insert(k.to_uppercase(), v.to_ascii_uppercase());
}
}
}
Ok(polybius_square)
}
#[derive(Debug)]
pub struct PlayfairTable {
pub rows: [String; 5],
pub cols: [String; 5],
}
impl PlayfairTable {
pub fn new<K: AsRef<str>>(key: K) -> Result<PlayfairTable, &'static str> {
const PLAYFAIR_ALPHABET: &'static str = "ABCDEFGHIKLMNOPQRSTUVWXYZ";
if key.as_ref().is_empty() {
return Err("Key must not be empty");
}
if key.as_ref().len() > PLAYFAIR_ALPHABET.len() {
return Err("Key length must not exceed 25 characters");
}
let mut key: String = key.as_ref().split_whitespace().collect();
if !alphabet::STANDARD.is_valid(key.as_str()) {
return Err("Key must only consist of alphabetic characters");
}
key = key.to_uppercase();
key.replace("J", "I");
let mut ukey = String::new();
for c in key.chars() {
if !ukey.contains(c) {
ukey.push(c);
}
}
let mut vtable: Vec<char> = ukey.chars().collect();
for c in PLAYFAIR_ALPHABET.chars() {
if !vtable.contains(&c) {
vtable.push(c);
}
}
vtable.shrink_to_fit();
assert_eq!(vtable.len(), PLAYFAIR_ALPHABET.len());
let mut rows: [String; 5] = Default::default();
for (k, r) in vtable.chunks(5).enumerate() {
rows[k] = r.iter().collect();
}
let mut cols: [String; 5] = Default::default();
for i in 0..5 {
for r in vtable.chunks(5) {
cols[i].push(r[i]);
}
}
Ok(PlayfairTable {
rows: rows,
cols: cols,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn polybius_hashmap_order() {
let p = polybius_square(
"abcdefghijklmnopqrstuvwxyz0123456789",
['a', 'b', 'c', 'd', 'e', 'f'],
['a', 'b', 'c', 'd', 'e', 'f'],
).unwrap();
assert_eq!(&'a', p.get("aa").unwrap());
assert_eq!(&'c', p.get("ac").unwrap());
assert_eq!(&'e', p.get("ae").unwrap());
assert_eq!(&'h', p.get("bb").unwrap());
assert_eq!(&'z', p.get("eb").unwrap());
}
#[test]
fn polybius_duplicate_characters() {
assert!(
polybius_square(
"abcdefghijklnnopqrstuvwxyz0123456789",
['a', 'b', 'c', 'd', 'e', 'f'],
['a', 'b', 'c', 'd', 'e', 'f']
).is_err()
);
}
#[test]
fn polybius_missing_characters() {
assert!(
polybius_square(
"adefghiklnnopqrstuvwxyz",
['a', 'b', 'c', 'd', 'e', 'f'],
['a', 'b', 'c', 'd', 'e', 'f']
).is_err()
);
}
#[test]
fn polybius_non_alpha_characters() {
assert!(
polybius_square(
"abcd@#!ghiklnnopqrstuvwxyz0123456789",
['a', 'b', 'c', 'd', 'e', 'f'],
['a', 'b', 'c', 'd', 'e', 'f']
).is_err()
);
}
#[test]
fn polybius_repeated_column_ids() {
assert!(
polybius_square(
"abcdefghijklmnopqrstuvwxyz0123456789",
['a', 'a', 'c', 'd', 'e', 'f'],
['a', 'b', 'c', 'd', 'e', 'f']
).is_err()
);
}
#[test]
fn polybius_repeated_row_ids() {
assert!(
polybius_square(
"abcdefghijklmnopqrstuvwxyz0123456789",
['a', 'b', 'c', 'd', 'e', 'f'],
['a', 'b', 'c', 'c', 'e', 'f']
).is_err()
);
}
#[test]
fn generate_numeric_alphabet() {
let keyed_alphabet = keyed_alphabet("or0ange", ALPHANUMERIC, false).unwrap();
assert_eq!(keyed_alphabet, "or0angebcdfhijklmpqstuvwxyz123456789");
}
#[test]
fn generate_standard_alphabet() {
let keyed_alphabet = keyed_alphabet("test", STANDARD, false).unwrap();
assert_eq!(keyed_alphabet, "tesabcdfghijklmnopqruvwxyz");
}
#[test]
fn generate_alphabet_mixed_key() {
let keyed_alphabet = keyed_alphabet("ALphaBEt", STANDARD, false).unwrap();
assert_eq!(keyed_alphabet, "alphbetcdfgijkmnoqrsuvwxyz");
}
#[test]
fn generate_uppercase_alphabet() {
let keyed_alphabet = keyed_alphabet("OranGE", STANDARD, true).unwrap();
assert_eq!(keyed_alphabet, "ORANGEBCDFHIJKLMPQSTUVWXYZ");
}
#[test]
fn generate_alphabet_bad_key() {
assert!(keyed_alphabet("bad key", STANDARD, false).is_err());
}
#[test]
fn generate_alphabet_no_key() {
let keyed_alphabet = keyed_alphabet("", STANDARD, false).unwrap();
assert_eq!(keyed_alphabet, "abcdefghijklmnopqrstuvwxyz");
}
#[test]
fn generate_alphabet_long_key() {
let keyed_alphabet =
keyed_alphabet("nnhhyqzabguuxwdrvvctspefmjoklii", STANDARD, true).unwrap();
assert_eq!(keyed_alphabet, "NHYQZABGUXWDRVCTSPEFMJOKLI");
}
#[test]
fn generate_columnar_key() {
assert_eq!(
vec![
('z', vec![]),
('e', vec![]),
('b', vec![]),
('r', vec![]),
('a', vec![]),
('s', vec![]),
],
columnar_key("zebras").unwrap()
);
}
#[test]
fn generate_columnar_empty_key() {
assert!(columnar_key("").is_err());
}
#[test]
fn generate_columnar_invalid_key() {
assert!(columnar_key("Fx !@#$").is_err());
}
#[test]
fn playfairtable_new_accepts_alpha_key() {
assert!(PlayfairTable::new("Foo").is_ok());
}
#[test]
fn playfairtable_new_accepts_spaced_key() {
assert!(PlayfairTable::new("Foo Bar").is_ok());
}
#[test]
fn playfairtable_new_accepts_alphanumeric_key() {
assert!(PlayfairTable::new("Bad123").is_err());
}
#[test]
fn playfairtable_new_rejects_symbolic_key() {
assert!(PlayfairTable::new("Bad?").is_err());
}
#[test]
fn playfairtable_new_rejects_unicode_key() {
assert!(PlayfairTable::new("Bad☢").is_err());
}
#[test]
fn playfairtable_new_rejects_empty_key() {
assert!(PlayfairTable::new("").is_err());
}
#[test]
fn playfairtable_new_rejects_long_key() {
assert!(PlayfairTable::new("ABCDEFGHIJKLMNOPQRSTUVWXYZA").is_err());
}
}