use super::errors::DecodeError;
use crate::core::alternating_dictionary::AlternatingWordDictionary;
pub fn encode(data: &[u8], dictionary: &AlternatingWordDictionary) -> Result<String, DecodeError> {
if data.is_empty() {
return Ok(String::new());
}
let mut words: Vec<&str> = Vec::with_capacity(data.len());
for (pos, &byte) in data.iter().enumerate() {
let word =
dictionary
.encode_byte(byte, pos)
.ok_or_else(|| DecodeError::InvalidCharacter {
char: byte as char,
position: pos,
input: format!("byte {} at position {}", byte, pos),
valid_chars: "bytes 0-255".to_string(),
})?;
words.push(word);
}
Ok(words.join(dictionary.delimiter()))
}
pub fn decode(
encoded: &str,
dictionary: &AlternatingWordDictionary,
) -> Result<Vec<u8>, DecodeError> {
if encoded.is_empty() {
return Ok(Vec::new());
}
let delimiter = dictionary.delimiter();
let words: Vec<&str> = if delimiter.is_empty() {
vec![encoded]
} else {
encoded.split(delimiter).collect()
};
let mut result = Vec::with_capacity(words.len());
for (pos, word) in words.iter().enumerate() {
let byte =
dictionary
.decode_word(word.trim(), pos)
.ok_or_else(|| DecodeError::InvalidWord {
word: word.to_string(),
position: pos,
input: encoded.to_string(),
})?;
result.push(byte);
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::WordDictionary;
fn create_full_dictionaries() -> AlternatingWordDictionary {
let even_words: Vec<String> = (0..256).map(|i| format!("even{}", i)).collect();
let odd_words: Vec<String> = (0..256).map(|i| format!("odd{}", i)).collect();
let even = WordDictionary::builder().words(even_words).build().unwrap();
let odd = WordDictionary::builder().words(odd_words).build().unwrap();
AlternatingWordDictionary::new(vec![even, odd], "-".to_string())
}
fn create_small_dictionaries() -> AlternatingWordDictionary {
let mut even_words: Vec<String> = vec![
"aardvark".to_string(),
"absurd".to_string(),
"accrue".to_string(),
"acme".to_string(),
];
even_words.extend((even_words.len()..256).map(|i| format!("even{}", i)));
let mut odd_words: Vec<String> = vec![
"adroitness".to_string(),
"adviser".to_string(),
"aftermath".to_string(),
"aggregate".to_string(),
];
odd_words.extend((odd_words.len()..256).map(|i| format!("odd{}", i)));
let even = WordDictionary::builder().words(even_words).build().unwrap();
let odd = WordDictionary::builder().words(odd_words).build().unwrap();
AlternatingWordDictionary::new(vec![even, odd], "-".to_string())
}
#[test]
fn test_encode_empty() {
let dict = create_full_dictionaries();
assert_eq!(encode(&[], &dict).unwrap(), "");
}
#[test]
fn test_encode_single_byte() {
let dict = create_full_dictionaries();
let data = vec![0x42];
let encoded = encode(&data, &dict).unwrap();
assert_eq!(encoded, "even66"); }
#[test]
fn test_encode_two_bytes() {
let dict = create_full_dictionaries();
let data = vec![0x42, 0xAB];
let encoded = encode(&data, &dict).unwrap();
assert_eq!(encoded, "even66-odd171"); }
#[test]
fn test_encode_decode_roundtrip() {
let dict = create_full_dictionaries();
let data = vec![0x00, 0x01, 0x42, 0xAB, 0xFF];
let encoded = encode(&data, &dict).unwrap();
let decoded = decode(&encoded, &dict).unwrap();
assert_eq!(decoded, data);
}
#[test]
fn test_decode_empty() {
let dict = create_full_dictionaries();
let decoded = decode("", &dict).unwrap();
assert_eq!(decoded, Vec::<u8>::new());
}
#[test]
fn test_decode_single_word() {
let dict = create_full_dictionaries();
let decoded = decode("even66", &dict).unwrap();
assert_eq!(decoded, vec![0x42]);
}
#[test]
fn test_decode_multiple_words() {
let dict = create_full_dictionaries();
let decoded = decode("even66-odd171", &dict).unwrap();
assert_eq!(decoded, vec![0x42, 0xAB]);
}
#[test]
fn test_decode_case_insensitive() {
let dict = create_small_dictionaries();
let data = vec![0, 1];
let encoded = encode(&data, &dict).unwrap();
let decoded_upper = decode(&encoded.to_uppercase(), &dict).unwrap();
let decoded_lower = decode(&encoded.to_lowercase(), &dict).unwrap();
assert_eq!(decoded_upper, data);
assert_eq!(decoded_lower, data);
}
#[test]
fn test_decode_unknown_word() {
let dict = create_full_dictionaries();
let result = decode("even0-unknown-even2", &dict);
assert!(result.is_err());
assert!(matches!(result, Err(DecodeError::InvalidWord { .. })));
}
#[test]
fn test_decode_wrong_dictionary_for_position() {
let dict = create_small_dictionaries();
let result = decode("adroitness-absurd", &dict);
assert!(result.is_err());
}
#[test]
fn test_alternating_pattern() {
let dict = create_small_dictionaries();
let data = vec![0, 1, 2, 3];
let encoded = encode(&data, &dict).unwrap();
assert_eq!(encoded, "aardvark-adviser-accrue-aggregate");
let decoded = decode(&encoded, &dict).unwrap();
assert_eq!(decoded, data);
}
#[test]
fn test_custom_delimiter() {
let even = WordDictionary::builder()
.words((0..256).map(|i| format!("e{}", i)).collect::<Vec<_>>())
.build()
.unwrap();
let odd = WordDictionary::builder()
.words((0..256).map(|i| format!("o{}", i)).collect::<Vec<_>>())
.build()
.unwrap();
let dict = AlternatingWordDictionary::new(vec![even, odd], " ".to_string());
let data = vec![0, 1, 2];
let encoded = encode(&data, &dict).unwrap();
assert_eq!(encoded, "e0 o1 e2");
let decoded = decode(&encoded, &dict).unwrap();
assert_eq!(decoded, data);
}
#[test]
fn test_whitespace_handling() {
let dict = create_small_dictionaries();
let decoded = decode(" aardvark - adviser ", &dict).unwrap();
assert_eq!(decoded, vec![0, 1]);
}
#[test]
fn test_encode_all_bytes() {
let dict = create_full_dictionaries();
let data: Vec<u8> = (0..=255).collect();
let encoded = encode(&data, &dict).unwrap();
let decoded = decode(&encoded, &dict).unwrap();
assert_eq!(decoded, data);
}
#[test]
fn test_pgp_wordlists_roundtrip() {
use crate::wordlists;
let pgp_even = wordlists::pgp_even();
let pgp_odd = wordlists::pgp_odd();
let dict = AlternatingWordDictionary::new(vec![pgp_even, pgp_odd], "-".to_string());
let all_bytes: Vec<u8> = (0..=255).collect();
let encoded = encode(&all_bytes, &dict).unwrap();
let decoded = decode(&encoded, &dict).unwrap();
assert_eq!(decoded, all_bytes);
let test_data = vec![0x42, 0xAB, 0xCD, 0xEF];
let encoded_test = encode(&test_data, &dict).unwrap();
let decoded_test = decode(&encoded_test, &dict).unwrap();
assert_eq!(decoded_test, test_data);
}
#[test]
fn test_pgp_wordlists_have_256_words() {
use crate::wordlists;
let pgp_even = wordlists::pgp_even();
let pgp_odd = wordlists::pgp_odd();
assert_eq!(pgp_even.base(), 256);
assert_eq!(pgp_odd.base(), 256);
}
}