use four_word_networking::FourWordAdaptiveEncoder;
use std::net::SocketAddr;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum IdentityError {
#[error("Failed to encode identity: {0}")]
EncodingFailed(String),
#[error("Failed to decode identity: {0}")]
DecodingFailed(String),
#[error("Invalid four-word format: {0}")]
InvalidFormat(String),
#[error("Four-word encoder initialization failed: {0}")]
EncoderInitFailed(String),
}
pub type IdentityResult<T> = Result<T, IdentityError>;
pub fn generate_id_words() -> IdentityResult<String> {
let encoder = FourWordAdaptiveEncoder::new().map_err(|e| {
IdentityError::EncoderInitFailed(format!("Failed to initialize encoder: {}", e))
})?;
let words = encoder.get_random_words(4);
if words.len() != 4 {
return Err(IdentityError::EncodingFailed(
"Failed to generate 4 dictionary words".to_string(),
));
}
Ok(words.join("-"))
}
pub fn identity_to_seed(identity: &str) -> IdentityResult<[u8; 32]> {
if !validate_identity_format(identity) {
return Err(IdentityError::InvalidFormat(format!(
"Invalid four-word format: expected word-word-word-word, got: {}",
identity
)));
}
let hash = blake3::hash(identity.as_bytes());
Ok(*hash.as_bytes())
}
pub fn validate_id_words(identity: &str) -> bool {
if !validate_identity_format(identity) {
return false;
}
let encoder = match FourWordAdaptiveEncoder::new() {
Ok(e) => e,
Err(_) => return false,
};
for word in identity.split('-') {
if !encoder.is_valid_word(word) {
return false;
}
}
true
}
pub fn conn_words(addr: &SocketAddr) -> IdentityResult<String> {
let encoder = FourWordAdaptiveEncoder::new().map_err(|e| {
IdentityError::EncoderInitFailed(format!("Failed to initialize encoder: {}", e))
})?;
let words = encoder.encode(&addr.to_string()).map_err(|e| {
IdentityError::EncodingFailed(format!("Failed to encode connection address: {}", e))
})?;
Ok(words)
}
pub fn conn_from_words(words: &str) -> IdentityResult<SocketAddr> {
if !words.contains(' ') && !words.contains('-') {
return Err(IdentityError::InvalidFormat(
"Connection identity must contain word separators".to_string(),
));
}
let encoder = FourWordAdaptiveEncoder::new().map_err(|e| {
IdentityError::EncoderInitFailed(format!("Failed to initialize encoder: {}", e))
})?;
let addr_str = encoder.decode(words).map_err(|e| {
IdentityError::DecodingFailed(format!("Failed to decode connection address: {}", e))
})?;
let addr: SocketAddr = addr_str.parse().map_err(|e| {
IdentityError::DecodingFailed(format!("Failed to parse decoded address: {}", e))
})?;
Ok(addr)
}
pub fn validate_identity_format(words: &str) -> bool {
let parts: Vec<&str> = words.split('-').collect();
parts.len() == 4 && parts.iter().all(|part| !part.is_empty())
}
pub fn validate_connection_format(words: &str) -> bool {
let parts: Vec<&str> = words.split('-').collect();
parts.len() >= 4 && parts.iter().all(|part| !part.is_empty())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_id_words() {
let identity = generate_id_words().unwrap();
let parts: Vec<&str> = identity.split('-').collect();
assert_eq!(parts.len(), 4);
for part in parts {
assert!(!part.is_empty());
}
assert!(validate_id_words(&identity));
}
#[test]
fn test_identity_to_seed_deterministic() {
let identity = generate_id_words().unwrap();
let seed1 = identity_to_seed(&identity).unwrap();
let seed2 = identity_to_seed(&identity).unwrap();
assert_eq!(seed1, seed2);
assert_eq!(seed1.len(), 32);
}
#[test]
fn test_identity_to_seed_different_identities() {
let identity1 = generate_id_words().unwrap();
let identity2 = generate_id_words().unwrap();
if identity1 == identity2 {
return;
}
let seed1 = identity_to_seed(&identity1).unwrap();
let seed2 = identity_to_seed(&identity2).unwrap();
assert_ne!(seed1, seed2);
}
#[test]
fn test_validate_id_words() {
let valid_identity = generate_id_words().unwrap();
assert!(validate_id_words(&valid_identity));
assert!(!validate_id_words("only-three-words"));
assert!(!validate_id_words("too-many-words-here-now"));
assert!(!validate_id_words(""));
}
#[test]
fn test_conn_words_ipv4() {
let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
let conn_id = conn_words(&addr).unwrap();
assert!(conn_id.contains(' '));
let parts: Vec<&str> = conn_id.split(' ').collect();
assert!(parts.len() >= 4);
}
#[test]
fn test_conn_words_ipv6() {
let addr: SocketAddr = "[::1]:8080".parse().unwrap();
let conn_id = conn_words(&addr).unwrap();
assert!(conn_id.contains(' '));
let parts: Vec<&str> = conn_id.split(' ').collect();
assert!(parts.len() >= 4);
}
#[test]
fn test_conn_roundtrip_ipv4() {
let original: SocketAddr = "192.168.1.100:9000".parse().unwrap();
let words = conn_words(&original).unwrap();
let decoded = conn_from_words(&words).unwrap();
assert_eq!(original, decoded);
}
#[test]
fn test_conn_roundtrip_ipv6() {
let original: SocketAddr = "[2001:db8::1]:9000".parse().unwrap();
let words = conn_words(&original).unwrap();
let decoded = conn_from_words(&words).unwrap();
assert_eq!(original, decoded);
}
#[test]
fn test_conn_from_words_invalid_format() {
let result = conn_from_words("invalid");
assert!(result.is_err());
}
#[test]
fn test_validate_identity_format() {
assert!(validate_identity_format("ocean-forest-moon-star"));
assert!(validate_identity_format("river-mountain-sun-cloud"));
assert!(!validate_identity_format("only-three-words"));
assert!(!validate_identity_format("too-many-words-here-now"));
assert!(!validate_identity_format("no spaces allowed"));
assert!(!validate_identity_format(""));
}
#[test]
fn test_validate_connection_format() {
assert!(validate_connection_format("ocean-forest-moon-star"));
assert!(validate_connection_format("ocean-forest-moon-star-extra"));
assert!(validate_connection_format(
"ocean-forest-moon-star-extra-more"
));
assert!(!validate_connection_format("only-three"));
assert!(!validate_connection_format("no spaces"));
assert!(!validate_connection_format(""));
}
#[test]
fn test_conn_words_deterministic() {
let addr: SocketAddr = "10.0.0.1:5000".parse().unwrap();
let words1 = conn_words(&addr).unwrap();
let words2 = conn_words(&addr).unwrap();
assert_eq!(words1, words2);
}
#[test]
fn test_different_ports_different_words() {
let addr1: SocketAddr = "127.0.0.1:8080".parse().unwrap();
let addr2: SocketAddr = "127.0.0.1:8081".parse().unwrap();
let words1 = conn_words(&addr1).unwrap();
let words2 = conn_words(&addr2).unwrap();
assert_ne!(words1, words2);
}
}