use crate::common::cipher::Cipher;
pub struct Scytale {
height: usize,
}
impl Cipher for Scytale {
type Key = usize;
type Algorithm = Scytale;
fn new(key: usize) -> Scytale {
if key == 0 {
panic!("Invalid key, height cannot be zero.");
}
Scytale { height: key }
}
fn encrypt(&self, message: &str) -> Result<String, &'static str> {
if self.height >= message.chars().count() || self.height == 1 {
return Ok(message.to_string());
}
let width = (message.chars().count() as f64 / self.height as f64).ceil() as usize;
let mut table = vec![vec![' '; width]; self.height];
for (pos, element) in message.chars().enumerate() {
let col = pos % self.height;
let row = pos / self.height;
table[col][row] = element;
}
Ok(table
.iter()
.flatten()
.collect::<String>()
.trim_end()
.to_string())
}
fn decrypt(&self, ciphertext: &str) -> Result<String, &'static str> {
if self.height >= ciphertext.chars().count() || self.height == 1 {
return Ok(ciphertext.to_string());
}
let width = (ciphertext.chars().count() as f64 / self.height as f64).ceil() as usize;
let mut table = vec![vec![' '; width]; self.height];
for (pos, element) in ciphertext.chars().enumerate() {
let col = pos / width;
let row = pos % width;
table[col][row] = element;
}
let mut plaintext = String::new();
while table.iter().filter(|v| !v.is_empty()).count() > 0 {
for column in table.iter_mut() {
plaintext.push(column.remove(0));
}
}
Ok(plaintext.trim_end().to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn simple_encrypt() {
let s = Scytale::new(6);
assert_eq!("aatttdaacwkn", s.encrypt("attackatdawn").unwrap());
}
#[test]
fn simple_decrypt() {
let s = Scytale::new(6);
assert_eq!("attackatdawn", s.decrypt("aatttdaacwkn").unwrap());
}
#[test]
fn padding_required() {
let s = Scytale::new(5);
let m = "attackatdawn";
assert_eq!(m, s.decrypt(&s.encrypt(m).unwrap()).unwrap());
}
#[test]
#[should_panic]
fn invalid_height() {
Scytale::new(0);
}
#[test]
fn with_utf8() {
let s = Scytale::new(5);
let m = "Attack 🗡️ at once.";
assert_eq!(m, s.decrypt(&s.encrypt(m).unwrap()).unwrap());
}
#[test]
fn with_spaces() {
let s = Scytale::new(5);
let m = "Attack At Dawn comrades! ";
assert_eq!(
"Attack At Dawn comrades!",
s.decrypt(&s.encrypt(m).unwrap()).unwrap()
);
}
#[test]
fn longer_height() {
let s = Scytale::new(20);
let m = "attackatdawn";
assert_eq!(m, s.decrypt(&s.encrypt(m).unwrap()).unwrap());
}
#[test]
fn longer_msg() {
let s = Scytale::new(7);
let m = concat!(
"We attack at dawn, not later when it is light, ",
"or at some strange time of the clock. Only at dawn. ",
"Why do we like to attack at dawn, actually, I don\'t ",
"get it. I hate getting up that early, it puts me in ",
"a bad mood. Can\'t we do it a bit later, say nine-thirty?"
);
assert_eq!(m, s.decrypt(&s.encrypt(m).unwrap()).unwrap());
}
}