use crate::three_word_encoder::ThreeWordEncoder;
use crate::three_word_ipv6_encoder::{Ipv6ThreeWordGroupEncoding, ThreeWordIpv6Encoder};
use crate::{FourWordError, Result};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
#[derive(Debug, Clone)]
pub struct ThreeWordEncoding {
pub words: Vec<String>,
pub is_ipv6: bool,
}
impl ThreeWordEncoding {
pub fn to_string(&self) -> String {
self.words.join(" ")
}
pub fn from_string(s: &str) -> Result<Self> {
let parts: Vec<&str> = if s.contains('.') {
s.split('.').collect()
} else if s.contains('-') {
s.split('-').collect()
} else {
s.split_whitespace().collect()
};
let is_ipv6 = parts.len() == 6 || parts.len() == 9;
let valid_count = match is_ipv6 {
false => parts.len() == 3,
true => parts.len() == 6 || parts.len() == 9,
};
if !valid_count {
return Err(FourWordError::InvalidInput(format!(
"Expected {} words, found {}",
if is_ipv6 { "6 or 9" } else { "3" },
parts.len()
)));
}
let words: Vec<String> = parts.iter().map(|p| p.to_lowercase()).collect();
Ok(ThreeWordEncoding { words, is_ipv6 })
}
}
pub struct ThreeWordPerfectEncoder {
ipv4_encoder: ThreeWordEncoder,
ipv6_encoder: ThreeWordIpv6Encoder,
}
impl ThreeWordPerfectEncoder {
pub fn new() -> Result<Self> {
Ok(Self {
ipv4_encoder: ThreeWordEncoder::new()?,
ipv6_encoder: ThreeWordIpv6Encoder::new()?,
})
}
pub fn encode_ipv4(&self, ip: Ipv4Addr, port: Option<u16>) -> Result<ThreeWordEncoding> {
let encoding = self.ipv4_encoder.encode(ip, port.unwrap_or(80))?;
Ok(ThreeWordEncoding {
words: encoding.words().to_vec(),
is_ipv6: false,
})
}
pub fn decode_ipv4(&self, encoding: &ThreeWordEncoding) -> Result<(Ipv4Addr, Option<u16>)> {
if encoding.words.len() != 3 {
return Err(FourWordError::InvalidInput(
"IPv4 requires exactly 3 words".to_string(),
));
}
let words: Vec<&str> = encoding.words.iter().map(|s| s.as_str()).collect();
let (ip, port) = self.ipv4_encoder.decode(&words)?;
Ok((ip, Some(port)))
}
pub fn encode_ipv6(&self, ip: Ipv6Addr, port: u16) -> Result<ThreeWordEncoding> {
let encoding = self.ipv6_encoder.encode(ip, port)?;
let mut all_words = Vec::new();
match &encoding {
Ipv6ThreeWordGroupEncoding::SixWords { groups, .. } => {
for group in groups {
all_words.extend(group.words.iter().cloned());
}
}
Ipv6ThreeWordGroupEncoding::NineWords { groups, .. } => {
for group in groups {
all_words.extend(group.words.iter().cloned());
}
}
}
Ok(ThreeWordEncoding {
words: all_words,
is_ipv6: true,
})
}
pub fn decode_ipv6(&self, encoding: &ThreeWordEncoding) -> Result<(Ipv6Addr, Option<u16>)> {
if encoding.words.len() != 6 && encoding.words.len() != 9 {
return Err(FourWordError::InvalidInput(
"IPv6 requires exactly 6 or 9 words".to_string(),
));
}
let words: Vec<&str> = encoding.words.iter().map(|s| s.as_str()).collect();
let (ip, port) = self.ipv6_encoder.decode_from_words(&words)?;
Ok((ip, Some(port)))
}
}
pub struct ThreeWordAdaptiveEncoder {
encoder: ThreeWordPerfectEncoder,
}
impl ThreeWordAdaptiveEncoder {
pub fn new() -> Result<Self> {
Ok(Self {
encoder: ThreeWordPerfectEncoder::new()?,
})
}
pub fn encode(&self, address: &str) -> Result<String> {
let (ip, port) = self.parse_address(address)?;
let encoding = match ip {
IpAddr::V4(ipv4) => self.encoder.encode_ipv4(ipv4, port)?,
IpAddr::V6(ipv6) => self.encoder.encode_ipv6(ipv6, port.unwrap_or(0))?,
};
Ok(encoding.to_string())
}
pub fn decode(&self, words: &str) -> Result<String> {
let encoding = ThreeWordEncoding::from_string(words)?;
if encoding.is_ipv6 {
let (ip, port) = self.encoder.decode_ipv6(&encoding)?;
if let Some(p) = port {
Ok(format!("[{ip}]:{p}"))
} else {
Ok(ip.to_string())
}
} else {
let (ip, port) = self.encoder.decode_ipv4(&encoding)?;
if let Some(p) = port {
Ok(format!("{ip}:{p}"))
} else {
Ok(ip.to_string())
}
}
}
fn parse_address(&self, input: &str) -> Result<(IpAddr, Option<u16>)> {
if input.starts_with('[') {
if let Some(close_idx) = input.find(']') {
let addr_part = &input[1..close_idx];
let remaining = &input[close_idx + 1..];
let port_part = if remaining.starts_with(':') {
Some(&remaining[1..])
} else {
None
};
let ip = addr_part.parse::<Ipv6Addr>().map_err(|_| {
FourWordError::InvalidInput(format!("Invalid IPv6 address: {addr_part}"))
})?;
let port = if let Some(port_str) = port_part {
Some(port_str.parse::<u16>().map_err(|_| {
FourWordError::InvalidInput(format!("Invalid port: {port_str}"))
})?)
} else {
None
};
return Ok((IpAddr::V6(ip), port));
}
}
if let Some(last_colon) = input.rfind(':') {
let colon_count = input.matches(':').count();
if colon_count == 1 {
let addr_part = &input[..last_colon];
let port_part = &input[last_colon + 1..];
if let Ok(ip) = addr_part.parse::<Ipv4Addr>() {
let port = port_part.parse::<u16>().map_err(|_| {
FourWordError::InvalidInput(format!("Invalid port: {port_part}"))
})?;
return Ok((IpAddr::V4(ip), Some(port)));
}
}
}
if let Ok(ip) = input.parse::<IpAddr>() {
Ok((ip, None))
} else {
Err(FourWordError::InvalidInput(format!(
"Invalid IP address: {input}"
)))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ipv4_perfect_reconstruction() {
let encoder = ThreeWordAdaptiveEncoder::new().unwrap();
let test_cases = vec![
"192.168.1.1:443",
"10.0.0.1:22",
"8.8.8.8:53",
"127.0.0.1:8080",
"255.255.255.255:65535",
"0.0.0.0:0",
"172.16.0.1:3389",
];
for address in test_cases {
let encoded = encoder.encode(address).unwrap();
println!(
"{} -> {} ({} words)",
address,
encoded,
encoded.split('.').count()
);
assert_eq!(
encoded.split(' ').count(),
3,
"IPv4 should use exactly 3 words"
);
let decoded = encoder.decode(&encoded).unwrap();
assert_eq!(
address, decoded,
"Failed to perfectly reconstruct {address}"
);
}
}
#[test]
fn test_ipv6_adaptive_encoding() {
let encoder = ThreeWordAdaptiveEncoder::new().unwrap();
let test_cases = vec![
"[::1]:443",
"[fe80::1]:22",
"[2001:db8::1]:80",
"[fc00::1]:8080",
];
for address in test_cases {
let encoded = encoder.encode(address).unwrap();
let word_count = encoded.split(' ').count();
println!("{address} -> {encoded} ({word_count} words)");
assert!(
word_count == 6 || word_count == 9,
"IPv6 should use 6 or 9 words, got {word_count}"
);
}
}
#[test]
fn test_word_count_distinction() {
let encoder = ThreeWordAdaptiveEncoder::new().unwrap();
let ipv4 = encoder.encode("192.168.1.1:443").unwrap();
let ipv6 = encoder.encode("[::1]:443").unwrap();
println!("IPv4: {ipv4}");
println!("IPv6: {ipv6}");
assert_eq!(
ipv4.split(' ').count(),
3,
"IPv4 uses exactly 3 words"
);
let ipv6_count = ipv6.split(' ').count();
assert!(
ipv6_count == 6 || ipv6_count == 9,
"IPv6 uses 6 or 9 words"
);
}
}