three-word-networking 2.3.0

Convert IP addresses to memorable, family-friendly word groups. IPv4 = 3 words, IPv6 = 6 or 9 words. Perfect reconstruction with human-readable vocabulary.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
//! Three-Word Network Address Encoder
//!
//! This module implements the complete three-word encoding system for IPv4+port
//! addresses using a 65,536-word dictionary and Feistel network for bit diffusion.
//!
//! ## Key Features:
//! - Perfect reconstruction for all IPv4+port combinations (48 bits)
//! - Cryptographically secure bit diffusion using Feistel network
//! - Human-readable three-word addresses
//! - Error detection and validation

use crate::dictionary65k::{Dictionary65K, get_global_dictionary};
use crate::{FourWordError, Result};
use std::fmt;
use std::net::{Ipv4Addr, SocketAddrV4};
use std::str::FromStr;

/// Three-word encoding result
#[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 {
    /// Convert to space-separated string format
    pub fn to_space_string(&self) -> String {
        format!("{} {} {}", self.words[0], self.words[1], self.words[2])
    }

    /// Get the three words as a slice
    pub fn words(&self) -> &[String; 3] {
        &self.words
    }

    /// Get the original address information
    pub fn original_address(&self) -> SocketAddrV4 {
        SocketAddrV4::new(self.original_ip, self.original_port)
    }
}

/// Three-word encoder for IPv4 addresses
pub struct ThreeWordEncoder {
    dictionary: &'static Dictionary65K,
}

impl ThreeWordEncoder {
    /// Create a new three-word encoder
    pub fn new() -> Result<Self> {
        let dictionary = get_global_dictionary()
            .map_err(|e| FourWordError::InvalidInput(format!("Dictionary error: {e}")))?;

        Ok(Self { dictionary })
    }

    /// Encode IPv4 address and port to three words
    pub fn encode(&self, ip: Ipv4Addr, port: u16) -> Result<ThreeWordEncoding> {
        // Pack IPv4 + port into 48 bits
        let ip_bits = u32::from(ip) as u64;
        let port_bits = port as u64;
        let combined = (ip_bits << 16) | port_bits;

        // Apply Feistel network for bit diffusion
        let shuffled = self.feistel_encode(combined)?;

        // Split into three 16-bit words
        let word1_idx = ((shuffled >> 32) & 0xFFFF) as u16;
        let word2_idx = ((shuffled >> 16) & 0xFFFF) as u16;
        let word3_idx = (shuffled & 0xFFFF) as u16;

        // Convert indices to words
        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,
        })
    }

    /// Decode three words back to IPv4 address and 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()
            )));
        }

        // Convert words to indices
        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))
        })?;

        // Reconstruct 48-bit value
        let shuffled = ((word1_idx as u64) << 32) | ((word2_idx as u64) << 16) | (word3_idx as u64);

        // Reverse Feistel network
        let combined = self.feistel_decode(shuffled)?;

        // Extract IP and port
        let ip_bits = ((combined >> 16) & 0xFFFFFFFF) as u32;
        let port = (combined & 0xFFFF) as u16;
        let ip = Ipv4Addr::from(ip_bits);

        Ok((ip, port))
    }

    /// Decode from string format (dot-separated or space-separated)
    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)
    }

    /// Encode socket address to three words
    pub fn encode_socket(&self, addr: SocketAddrV4) -> Result<ThreeWordEncoding> {
        self.encode(*addr.ip(), addr.port())
    }

    /// Decode three words to socket address
    pub fn decode_to_socket(&self, words: &[&str]) -> Result<SocketAddrV4> {
        let (ip, port) = self.decode(words)?;
        Ok(SocketAddrV4::new(ip, port))
    }

    /// Feistel network encoding for bit diffusion
    /// Uses 8 rounds for strong mixing while maintaining performance
    fn feistel_encode(&self, input: u64) -> Result<u64> {
        const ROUNDS: u32 = 8;

        // Split 48-bit input into two 24-bit halves
        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;
        }

        // Combine back to 48 bits, but return as u64 for convenience
        let result = ((left as u64) << 24) | (right as u64);
        Ok(result)
    }

    /// Feistel network decoding (reverse of encoding)
    fn feistel_decode(&self, input: u64) -> Result<u64> {
        const ROUNDS: u32 = 8;

        // Split back into 24-bit halves
        let mut left = ((input >> 24) & 0xFFFFFF) as u32;
        let mut right = (input & 0xFFFFFF) as u32;

        // Apply rounds in reverse order
        for round in (0..ROUNDS).rev() {
            let new_left = right ^ self.feistel_round_function(left, round);
            right = left;
            left = new_left;
        }

        // Combine back to 48 bits
        let result = ((left as u64) << 24) | (right as u64);
        Ok(result)
    }

    /// Feistel round function - provides pseudorandom mixing
    /// Uses a simple but effective hash function for demonstration
    fn feistel_round_function(&self, input: u32, round: u32) -> u32 {
        // Use a simple hash based on the round and input
        // In production, this could use a cryptographic hash
        let mut hash = input.wrapping_mul(0x9E3779B9); // Golden ratio constant
        hash ^= round.wrapping_mul(0x85EBCA6B); // Another mixing constant
        hash ^= hash >> 16;
        hash = hash.wrapping_mul(0x85EBCA6B);
        hash ^= hash >> 13;
        hash = hash.wrapping_mul(0xC2B2AE35);
        hash ^= hash >> 16;

        // Ensure result fits in 24 bits for our use case
        hash & 0xFFFFFF
    }

    /// Validate that the three words can be decoded
    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(())
    }

    /// Get dictionary statistics
    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
        )
    }

    /// Test the encoder with a comprehensive set of addresses
    pub fn test_comprehensive(&self) -> Result<()> {
        let test_cases = [
            (Ipv4Addr::new(127, 0, 0, 1), 80),          // localhost
            (Ipv4Addr::new(192, 168, 1, 1), 443),       // private
            (Ipv4Addr::new(8, 8, 8, 8), 53),            // public DNS
            (Ipv4Addr::new(0, 0, 0, 0), 22),            // any address
            (Ipv4Addr::new(255, 255, 255, 255), 65535), // broadcast
        ];

        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")
    }
}

/// Parse three-word encoding from string
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();

        // Test dot format
        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);

        // Test space format
        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();

        // Test that adjacent IPs produce very different encodings
        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();

        // The words should be completely different due to Feistel diffusion
        assert_ne!(encoded1.words, encoded2.words);

        // But both should decode correctly
        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();

        // Test wrong number of words
        assert!(encoder.decode(&["one", "two"]).is_err());
        assert!(encoder.decode(&["one", "two", "three", "four"]).is_err());

        // Test invalid words (non-existent in dictionary)
        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();

        // Valid words should pass
        assert!(encoder.validate_words(&words).is_ok());

        // Invalid words should fail (non-existent in dictionary)
        assert!(
            encoder
                .validate_words(&["zzzzz999", "xxxxx888", "qqqqq777"])
                .is_err()
        );
    }
}