use crate::checkers::CheckerTypes;
use crate::decoders::interface::check_string_success;
use gibberish_or_not::Sensitivity;
use super::crack_results::CrackResult;
use super::interface::Crack;
use super::interface::Decoder;
use log::{info, trace};
pub struct RailfenceDecoder;
impl Crack for Decoder<RailfenceDecoder> {
fn new() -> Decoder<RailfenceDecoder> {
Decoder {
name: "railfence",
description: "The rail fence cipher (also called a zigzag cipher) is a classical type of transposition cipher. It derives its name from the manner in which encryption is performed, in analogy to a fence built with horizontal rails.",
link: "https://en.wikipedia.org/wiki/Rail_fence_cipher",
tags: vec!["railfence", "cipher", "classic", "transposition"],
popularity: 5.0,
phantom: std::marker::PhantomData,
}
}
fn crack(&self, text: &str, checker: &CheckerTypes) -> CrackResult {
trace!("Trying railfence with text {:?}", text);
let mut results = CrackResult::new(self, text.to_string());
let mut decoded_strings = Vec::new();
let checker_with_sensitivity = checker.with_sensitivity(Sensitivity::Low);
for rails in 2..10 {
for offset in 0..=(rails * 2 - 3) {
let decoded_text = railfence_decoder(text, rails, offset);
decoded_strings.push(decoded_text);
let borrowed_decoded_text = &decoded_strings[decoded_strings.len() - 1];
if !check_string_success(borrowed_decoded_text, text) {
info!(
"Failed to decode railfence because check_string_success returned false on string {}. This means the string is 'funny' as it wasn't modified.",
borrowed_decoded_text
);
return results;
}
let checker_result = checker_with_sensitivity.check(borrowed_decoded_text);
if checker_result.is_identified {
trace!(
"Found a match with railfence {} rails and {} offset",
rails,
offset
);
results.unencrypted_text = Some(vec![borrowed_decoded_text.to_string()]);
results.update_checker(&checker_result);
return results;
}
}
}
results.unencrypted_text = Some(decoded_strings);
results
}
fn get_tags(&self) -> &Vec<&str> {
&self.tags
}
fn get_name(&self) -> &str {
self.name
}
}
fn railfence_decoder(text: &str, rails: usize, offset: usize) -> String {
let mut indexes: Vec<_> = zigzag(rails, offset).zip(1..).take(text.len()).collect();
indexes.sort();
let mut char_with_index: Vec<_> = text
.chars()
.zip(indexes)
.map(|(c, (_, i))| (i, c))
.collect();
char_with_index.sort();
char_with_index.iter().map(|(_, c)| c).collect()
}
fn zigzag(n: usize, offset: usize) -> impl Iterator<Item = usize> {
(0..n - 1).chain((1..n).rev()).cycle().skip(offset)
}
#[cfg(test)]
mod tests {
use super::RailfenceDecoder;
use super::*;
use crate::{
checkers::{
athena::Athena,
checker_type::{Check, Checker},
english::EnglishChecker,
CheckerTypes,
},
decoders::interface::{Crack, Decoder},
};
fn get_athena_checker() -> CheckerTypes {
let athena_checker = Checker::<Athena>::new();
CheckerTypes::CheckAthena(athena_checker)
}
#[test]
#[ignore]
fn railfence_decodes_successfully() {
let railfence_decoder_instance = Decoder::<RailfenceDecoder>::new();
let input = "xcz n akt,emiol r gywShfbqajd op uuv";
let expected = "Sphinx of black quartz, judge my vow";
println!("Input text: {:?}", input);
let manual_decode = railfence_decoder(input, 5, 3);
println!("Manual decode with 5 rails, 3 offset: {:?}", manual_decode);
for rails in 2..7 {
for offset in 0..5 {
let decoded = railfence_decoder(input, rails, offset);
println!(
"Rails: {}, Offset: {}, Result: {:?}",
rails, offset, decoded
);
}
}
let result = railfence_decoder_instance.crack(input, &get_athena_checker());
if let Some(decoded_texts) = &result.unencrypted_text {
println!("Number of decoded texts: {}", decoded_texts.len());
for (i, text) in decoded_texts.iter().enumerate() {
println!("Decoded text {}: {:?}", i, text);
}
if !decoded_texts.is_empty() {
println!("First decoded text: {:?}", decoded_texts[0]);
println!("Expected text: {:?}", expected);
}
} else {
println!("No decoded texts found");
}
assert_eq!(result.unencrypted_text.unwrap()[0], expected);
}
#[test]
fn railfence_handles_panic_if_empty_string() {
let railfence_decoder = Decoder::<RailfenceDecoder>::new();
let result = railfence_decoder
.crack("", &get_athena_checker())
.unencrypted_text;
assert!(result.is_none());
}
#[test]
fn railfence_handles_panic_if_emoji() {
let railfence_decoder = Decoder::<RailfenceDecoder>::new();
let result = railfence_decoder
.crack("😂", &get_athena_checker())
.unencrypted_text;
assert!(result.is_none());
}
#[test]
fn test_railfence_uses_low_sensitivity() {
let railfence_decoder = Decoder::<RailfenceDecoder>::new();
let text = "Test text";
let result = railfence_decoder.crack(
text,
&CheckerTypes::CheckEnglish(Checker::<EnglishChecker>::new()),
);
assert!(
result.unencrypted_text.is_none(),
"Railfence decoder should return none for this test text"
);
}
}