use crate::CrackResult;
use once_cell::sync::Lazy;
use rand::Rng;
use std::collections::HashMap;
use std::sync::Mutex;
pub static DECODER_SUCCESS_RATES: Lazy<Mutex<HashMap<String, (usize, usize)>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
pub fn update_decoder_stats(decoder: &str, success: bool) {
let mut stats = DECODER_SUCCESS_RATES.lock().unwrap();
let (successes, total) = stats.entry(decoder.to_string()).or_insert((0, 0));
if success {
*successes += 1;
}
*total += 1;
}
pub fn get_decoder_success_rate(decoder: &str) -> f32 {
let stats = DECODER_SUCCESS_RATES.lock().unwrap();
if let Some((successes, total)) = stats.get(decoder) {
if *total > 0 {
return *successes as f32 / *total as f32;
}
}
0.5
}
pub fn get_cipher_identifier_score(text: &str) -> (String, f32) {
let results = cipher_identifier::identify_cipher::identify_cipher(text, 5, None);
if let Some((cipher, score)) = results.first() {
return (cipher.clone(), (score / 10.0) as f32);
}
let mut rng = rand::rng();
("unknown".to_string(), rng.random_range(0.5..1.0) as f32)
}
pub fn is_common_sequence(prev_decoder: &str, current_cipher: &str) -> bool {
match (prev_decoder, current_cipher) {
("Base64Decoder", "Base32Decoder") => true,
("Base64Decoder", "Base58Decoder") => true,
("Base64Decoder", "Base85Decoder") => true,
("Base64Decoder", "Base64Decoder") => true,
("Base32Decoder", "Base64Decoder") => true,
("Base32Decoder", "Base85Decoder") => true,
("Base32Decoder", "Base32Decoder") => true,
("Base58Decoder", "Base64Decoder") => true,
("Base58Decoder", "Base32Decoder") => true,
("Base58Decoder", "Base58Decoder") => true,
("Base85Decoder", "Base64Decoder") => true,
("Base85Decoder", "Base32Decoder") => true,
("Base85Decoder", "Base85Decoder") => true,
_ => false,
}
}
pub fn calculate_string_quality(s: &str) -> f32 {
let non_printable_ratio = calculate_non_printable_ratio(s);
if non_printable_ratio > 0.5 {
return 0.0; }
if s.len() < 3 {
0.1
} else if s.len() > 5000 {
0.3
} else {
1.0 - (s.len() as f32 - 100.0).abs() / 900.0
}
}
pub fn calculate_non_printable_ratio(text: &str) -> f32 {
if text.is_empty() {
return 1.0;
}
let non_printable_count = text
.chars()
.filter(|&c| {
(c.is_control() && c != '\n' && c != '\r' && c != '\t') || !c.is_ascii()
})
.count();
non_printable_count as f32 / text.len() as f32
}
pub fn generate_heuristic(text: &str, path: &[CrackResult]) -> f32 {
let (cipher, base_score) = get_cipher_identifier_score(text);
let mut final_score = base_score;
if let Some(last_result) = path.last() {
if !is_common_sequence(last_result.decoder, &cipher) {
final_score *= 1.75; }
let success_rate = get_decoder_success_rate(last_result.decoder);
final_score *= 1.0 + (1.0 - success_rate);
let popularity = success_rate;
final_score *= 1.0 + (2.0 * (1.0 - popularity)); }
final_score *= 1.0 + (1.0 - calculate_string_quality(text));
let non_printable_ratio = calculate_non_printable_ratio(text);
if non_printable_ratio > 0.0 {
final_score *= 1.0 + (non_printable_ratio * 100.0).exp();
}
final_score
}
pub fn check_if_string_cant_be_decoded(text: &str) -> bool {
if text.len() <= 2 {
return true;
}
let non_printable_ratio = calculate_non_printable_ratio(text);
if non_printable_ratio > 0.3 {
return true;
}
let quality = calculate_string_quality(text);
if quality < 0.2 {
return true;
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Decoder;
#[test]
fn test_generate_heuristic() {
let normal_h = generate_heuristic("Hello World", &[]);
let suspicious_h = generate_heuristic("H\u{0}ll\u{1} W\u{2}rld", &[]);
let nonprint_h = generate_heuristic("\u{0}\u{1}\u{2}", &[]);
assert!(normal_h < suspicious_h);
assert!(suspicious_h < nonprint_h);
assert!(normal_h >= 0.0);
}
#[test]
fn test_calculate_non_printable_ratio() {
assert_eq!(calculate_non_printable_ratio("Hello World"), 0.0);
assert_eq!(calculate_non_printable_ratio("123!@#\n\t"), 0.0);
let mixed = "Hello\u{0}World\u{1}".to_string(); assert!((calculate_non_printable_ratio(&mixed) - 0.1666).abs() < 0.001);
assert_eq!(calculate_non_printable_ratio("\u{0}\u{1}\u{2}"), 1.0);
assert_eq!(calculate_non_printable_ratio(""), 1.0);
}
#[test]
fn test_heuristic_with_non_printable() {
let normal = generate_heuristic("Hello World", &[]);
let with_non_printable = generate_heuristic("Hello\u{0}World", &[]);
let all_non_printable = generate_heuristic("\u{0}\u{1}\u{2}", &[]);
assert!(normal < with_non_printable);
assert!(with_non_printable < all_non_printable);
assert!(all_non_printable > 100.0); }
#[test]
fn test_success_rate_affects_heuristic() {
let mut high_success_result = CrackResult::new(&Decoder::default(), "test".to_string());
high_success_result.decoder = "HighSuccessDecoder";
let mut low_success_result = CrackResult::new(&Decoder::default(), "test".to_string());
low_success_result.decoder = "LowSuccessDecoder";
update_decoder_stats("HighSuccessDecoder", true);
update_decoder_stats("HighSuccessDecoder", true);
update_decoder_stats("HighSuccessDecoder", true);
update_decoder_stats("HighSuccessDecoder", false);
update_decoder_stats("LowSuccessDecoder", true);
update_decoder_stats("LowSuccessDecoder", false);
update_decoder_stats("LowSuccessDecoder", false);
update_decoder_stats("LowSuccessDecoder", false);
let high_success_heuristic = generate_heuristic("test", &[high_success_result]);
let low_success_heuristic = generate_heuristic("test", &[low_success_result]);
assert!(
low_success_heuristic > high_success_heuristic,
"Low success decoder should have a higher (worse) heuristic score. \
High Success: {}, Low Success: {}",
high_success_heuristic,
low_success_heuristic
);
}
#[test]
fn test_calculate_string_quality_with_invisible_chars() {
let normal_quality = calculate_string_quality("Hello World");
assert!(normal_quality > 0.0);
let text_with_some_invisible = "Hello\u{0}\u{0}\u{0}\u{0}World"; let some_invisible_quality = calculate_string_quality(text_with_some_invisible);
assert!(some_invisible_quality > 0.0);
let text_with_many_invisible = "\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}Hello"; let many_invisible_quality = calculate_string_quality(text_with_many_invisible);
assert_eq!(many_invisible_quality, 0.0);
let all_invisible = "\u{0}\u{0}\u{0}\u{0}\u{0}";
let all_invisible_quality = calculate_string_quality(all_invisible);
assert_eq!(all_invisible_quality, 0.0);
}
#[test]
fn test_check_if_string_cant_be_decoded() {
assert!(
check_if_string_cant_be_decoded(""),
"Empty string should be rejected"
);
assert!(
check_if_string_cant_be_decoded("a"),
"Single character should be rejected"
);
assert!(
check_if_string_cant_be_decoded("ab"),
"Two characters should be rejected"
);
let high_non_printable = "abc\u{0}\u{1}\u{2}"; assert!(
check_if_string_cant_be_decoded(high_non_printable),
"String with 50% non-printable characters should be rejected"
);
let low_quality = "\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}abc"; assert!(
check_if_string_cant_be_decoded(low_quality),
"Low quality string should be rejected"
);
assert!(
!check_if_string_cant_be_decoded("Hello World"),
"Normal text should be accepted"
);
assert!(
!check_if_string_cant_be_decoded("SGVsbG8gV29ybGQ="), "Valid Base64 should be accepted"
);
}
}