use crate::dictionary65k::{Dictionary65K, get_global_dictionary};
use crate::{FourWordError, Result};
use std::fmt;
use std::net::{Ipv4Addr, SocketAddrV4};
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ThreeWordEncoding {
pub words: [String; 3],
pub original_ip: Ipv4Addr,
pub original_port: u16,
}
impl fmt::Display for ThreeWordEncoding {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {} {}", self.words[0], self.words[1], self.words[2])
}
}
impl ThreeWordEncoding {
pub fn to_space_string(&self) -> String {
format!("{} {} {}", self.words[0], self.words[1], self.words[2])
}
pub fn words(&self) -> &[String; 3] {
&self.words
}
pub fn original_address(&self) -> SocketAddrV4 {
SocketAddrV4::new(self.original_ip, self.original_port)
}
}
pub struct ThreeWordEncoder {
dictionary: &'static Dictionary65K,
}
impl ThreeWordEncoder {
pub fn new() -> Result<Self> {
let dictionary = get_global_dictionary()
.map_err(|e| FourWordError::InvalidInput(format!("Dictionary error: {e}")))?;
Ok(Self { dictionary })
}
pub fn encode(&self, ip: Ipv4Addr, port: u16) -> Result<ThreeWordEncoding> {
let ip_bits = u32::from(ip) as u64;
let port_bits = port as u64;
let combined = (ip_bits << 16) | port_bits;
let shuffled = self.feistel_encode(combined)?;
let word1_idx = ((shuffled >> 32) & 0xFFFF) as u16;
let word2_idx = ((shuffled >> 16) & 0xFFFF) as u16;
let word3_idx = (shuffled & 0xFFFF) as u16;
let words = [
self.dictionary
.get_word(word1_idx)
.map_err(|e| FourWordError::InvalidInput(format!("Word lookup error: {e}")))?
.to_string(),
self.dictionary
.get_word(word2_idx)
.map_err(|e| FourWordError::InvalidInput(format!("Word lookup error: {e}")))?
.to_string(),
self.dictionary
.get_word(word3_idx)
.map_err(|e| FourWordError::InvalidInput(format!("Word lookup error: {e}")))?
.to_string(),
];
Ok(ThreeWordEncoding {
words,
original_ip: ip,
original_port: port,
})
}
pub fn decode(&self, words: &[&str]) -> Result<(Ipv4Addr, u16)> {
if words.len() != 3 {
return Err(FourWordError::InvalidInput(format!(
"Expected 3 words, got {}",
words.len()
)));
}
let word1_idx = self.dictionary.get_index(words[0]).map_err(|e| {
FourWordError::InvalidInput(format!("Unknown word '{}': {}", words[0], e))
})?;
let word2_idx = self.dictionary.get_index(words[1]).map_err(|e| {
FourWordError::InvalidInput(format!("Unknown word '{}': {}", words[1], e))
})?;
let word3_idx = self.dictionary.get_index(words[2]).map_err(|e| {
FourWordError::InvalidInput(format!("Unknown word '{}': {}", words[2], e))
})?;
let shuffled = ((word1_idx as u64) << 32) | ((word2_idx as u64) << 16) | (word3_idx as u64);
let combined = self.feistel_decode(shuffled)?;
let ip_bits = ((combined >> 16) & 0xFFFFFFFF) as u32;
let port = (combined & 0xFFFF) as u16;
let ip = Ipv4Addr::from(ip_bits);
Ok((ip, port))
}
pub fn decode_string(&self, input: &str) -> Result<(Ipv4Addr, u16)> {
let words: Vec<&str> = if input.contains('.') {
input.split('.').collect()
} else {
input.split_whitespace().collect()
};
if words.len() != 3 {
return Err(FourWordError::InvalidInput(format!(
"Expected 3 words separated by dots or spaces, got: '{input}'"
)));
}
self.decode(&words)
}
pub fn encode_socket(&self, addr: SocketAddrV4) -> Result<ThreeWordEncoding> {
self.encode(*addr.ip(), addr.port())
}
pub fn decode_to_socket(&self, words: &[&str]) -> Result<SocketAddrV4> {
let (ip, port) = self.decode(words)?;
Ok(SocketAddrV4::new(ip, port))
}
fn feistel_encode(&self, input: u64) -> Result<u64> {
const ROUNDS: u32 = 8;
let mut left = ((input >> 24) & 0xFFFFFF) as u32;
let mut right = (input & 0xFFFFFF) as u32;
for round in 0..ROUNDS {
let new_right = left ^ self.feistel_round_function(right, round);
left = right;
right = new_right;
}
let result = ((left as u64) << 24) | (right as u64);
Ok(result)
}
fn feistel_decode(&self, input: u64) -> Result<u64> {
const ROUNDS: u32 = 8;
let mut left = ((input >> 24) & 0xFFFFFF) as u32;
let mut right = (input & 0xFFFFFF) as u32;
for round in (0..ROUNDS).rev() {
let new_left = right ^ self.feistel_round_function(left, round);
right = left;
left = new_left;
}
let result = ((left as u64) << 24) | (right as u64);
Ok(result)
}
fn feistel_round_function(&self, input: u32, round: u32) -> u32 {
let mut hash = input.wrapping_mul(0x9E3779B9); hash ^= round.wrapping_mul(0x85EBCA6B); hash ^= hash >> 16;
hash = hash.wrapping_mul(0x85EBCA6B);
hash ^= hash >> 13;
hash = hash.wrapping_mul(0xC2B2AE35);
hash ^= hash >> 16;
hash & 0xFFFFFF
}
pub fn validate_words(&self, words: &[&str]) -> Result<()> {
if words.len() != 3 {
return Err(FourWordError::InvalidInput(format!(
"Expected 3 words, got {}",
words.len()
)));
}
for (i, word) in words.iter().enumerate() {
self.dictionary.get_index(word).map_err(|_| {
FourWordError::InvalidInput(format!(
"Unknown word '{}' at position {}",
word,
i + 1
))
})?;
}
Ok(())
}
pub fn dictionary_stats(&self) -> String {
let stats = self.dictionary.stats();
format!(
"Dictionary: {} words, avg length: {:.1}, range: {}-{}",
stats.total_words, stats.avg_length, stats.min_length, stats.max_length
)
}
pub fn test_comprehensive(&self) -> Result<()> {
let test_cases = [
(Ipv4Addr::new(127, 0, 0, 1), 80), (Ipv4Addr::new(192, 168, 1, 1), 443), (Ipv4Addr::new(8, 8, 8, 8), 53), (Ipv4Addr::new(0, 0, 0, 0), 22), (Ipv4Addr::new(255, 255, 255, 255), 65535), ];
for (ip, port) in test_cases {
let encoded = self.encode(ip, port)?;
let words: Vec<&str> = encoded.words.iter().map(|s| s.as_str()).collect();
let (decoded_ip, decoded_port) = self.decode(&words)?;
if decoded_ip != ip || decoded_port != port {
return Err(FourWordError::InvalidInput(format!(
"Roundtrip failed for {}:{} -> {} -> {}:{}",
ip,
port,
encoded.to_string(),
decoded_ip,
decoded_port
)));
}
}
Ok(())
}
}
impl Default for ThreeWordEncoder {
fn default() -> Self {
Self::new().expect("Failed to create ThreeWordEncoder")
}
}
impl FromStr for ThreeWordEncoding {
type Err = FourWordError;
fn from_str(s: &str) -> Result<Self> {
let encoder = ThreeWordEncoder::new()?;
let (ip, port) = encoder.decode_string(s)?;
encoder.encode(ip, port)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encoder_creation() {
let encoder = ThreeWordEncoder::new().unwrap();
println!("{}", encoder.dictionary_stats());
}
#[test]
fn test_basic_encoding_decoding() {
let encoder = ThreeWordEncoder::new().unwrap();
let ip = Ipv4Addr::new(192, 168, 1, 100);
let port = 8080;
let encoded = encoder.encode(ip, port).unwrap();
assert_eq!(encoded.words.len(), 3);
assert_eq!(encoded.original_ip, ip);
assert_eq!(encoded.original_port, port);
let words: Vec<&str> = encoded.words.iter().map(|s| s.as_str()).collect();
let (decoded_ip, decoded_port) = encoder.decode(&words).unwrap();
assert_eq!(decoded_ip, ip);
assert_eq!(decoded_port, port);
}
#[test]
fn test_string_formats() {
let encoder = ThreeWordEncoder::new().unwrap();
let ip = Ipv4Addr::new(10, 0, 0, 1);
let port = 443;
let encoded = encoder.encode(ip, port).unwrap();
let dot_string = encoded.to_string();
let (decoded_ip1, decoded_port1) = encoder.decode_string(&dot_string).unwrap();
assert_eq!(decoded_ip1, ip);
assert_eq!(decoded_port1, port);
let space_string = encoded.to_space_string();
let (decoded_ip2, decoded_port2) = encoder.decode_string(&space_string).unwrap();
assert_eq!(decoded_ip2, ip);
assert_eq!(decoded_port2, port);
}
#[test]
fn test_socket_address_encoding() {
let encoder = ThreeWordEncoder::new().unwrap();
let socket_addr = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 22);
let encoded = encoder.encode_socket(socket_addr).unwrap();
let words: Vec<&str> = encoded.words.iter().map(|s| s.as_str()).collect();
let decoded_socket = encoder.decode_to_socket(&words).unwrap();
assert_eq!(decoded_socket, socket_addr);
}
#[test]
fn test_comprehensive_roundtrip() {
let encoder = ThreeWordEncoder::new().unwrap();
encoder.test_comprehensive().unwrap();
}
#[test]
fn test_feistel_property() {
let encoder = ThreeWordEncoder::new().unwrap();
let ip1 = Ipv4Addr::new(192, 168, 1, 1);
let ip2 = Ipv4Addr::new(192, 168, 1, 2);
let port = 80;
let encoded1 = encoder.encode(ip1, port).unwrap();
let encoded2 = encoder.encode(ip2, port).unwrap();
assert_ne!(encoded1.words, encoded2.words);
let words1: Vec<&str> = encoded1.words.iter().map(|s| s.as_str()).collect();
let words2: Vec<&str> = encoded2.words.iter().map(|s| s.as_str()).collect();
let (decoded_ip1, decoded_port1) = encoder.decode(&words1).unwrap();
let (decoded_ip2, decoded_port2) = encoder.decode(&words2).unwrap();
assert_eq!(decoded_ip1, ip1);
assert_eq!(decoded_port1, port);
assert_eq!(decoded_ip2, ip2);
assert_eq!(decoded_port2, port);
}
#[test]
fn test_error_cases() {
let encoder = ThreeWordEncoder::new().unwrap();
assert!(encoder.decode(&["one", "two"]).is_err());
assert!(encoder.decode(&["one", "two", "three", "four"]).is_err());
assert!(
encoder
.decode(&["zzzzz999", "xxxxx888", "qqqqq777"])
.is_err()
);
}
#[test]
fn test_from_str() {
let encoder = ThreeWordEncoder::new().unwrap();
let ip = Ipv4Addr::new(8, 8, 8, 8);
let port = 53;
let encoded = encoder.encode(ip, port).unwrap();
let string_repr = encoded.to_string();
let parsed: ThreeWordEncoding = string_repr.parse().unwrap();
assert_eq!(parsed.original_ip, ip);
assert_eq!(parsed.original_port, port);
}
#[test]
fn test_validation() {
let encoder = ThreeWordEncoder::new().unwrap();
let ip = Ipv4Addr::new(1, 2, 3, 4);
let port = 5678;
let encoded = encoder.encode(ip, port).unwrap();
let words: Vec<&str> = encoded.words.iter().map(|s| s.as_str()).collect();
assert!(encoder.validate_words(&words).is_ok());
assert!(
encoder
.validate_words(&["zzzzz999", "xxxxx888", "qqqqq777"])
.is_err()
);
}
}