three_word_networking/
ipv6_pattern_feistel.rs

1//! IPv6 Pattern Feistel Network
2//!
3//! A lightweight Feistel-like cipher designed specifically for preserving IPv6 pattern
4//! information during encoding/decoding cycles. This is NOT a cryptographic cipher,
5//! but rather a reversible mixing function that embeds pattern IDs into compressed data.
6//!
7//! ## Problem Solved
8//! When IPv6 addresses are compressed based on patterns (Loopback, LinkLocal, etc.)
9//! and then encoded to words, the pattern information is lost during decoding.
10//! This network reversibly mixes the 3-bit pattern ID with the compressed data.
11//!
12//! ## Design Principles
13//! - Lightweight and fast (<1μs operations)
14//! - Variable-length data support (16-144 bits)
15//! - Perfect reversibility (bijective function)
16//! - No cryptographic dependencies
17//! - Deterministic behavior
18
19use crate::{FourWordError, Result};
20
21/// IPv6 pattern IDs for Feistel encoding
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23#[repr(u8)]
24pub enum IPv6PatternId {
25    Loopback = 0,
26    Unspecified = 1,
27    LinkLocal = 2,
28    Documentation = 3,
29    UniqueLocal = 4,
30    GlobalUnicast = 5,
31    Multicast = 6,
32    Other = 7, // CloudProvider, CommonProvider, Unstructured all map to this
33}
34
35impl IPv6PatternId {
36    /// Convert pattern ID to 3-bit value
37    pub fn to_bits(self) -> u8 {
38        self as u8 & 0x7 // Ensure only 3 bits
39    }
40
41    /// Create pattern ID from 3-bit value
42    pub fn from_bits(bits: u8) -> Self {
43        match bits & 0x7 {
44            0 => IPv6PatternId::Loopback,
45            1 => IPv6PatternId::Unspecified,
46            2 => IPv6PatternId::LinkLocal,
47            3 => IPv6PatternId::Documentation,
48            4 => IPv6PatternId::UniqueLocal,
49            5 => IPv6PatternId::GlobalUnicast,
50            6 => IPv6PatternId::Multicast,
51            7 => IPv6PatternId::Other,
52            _ => unreachable!(),
53        }
54    }
55
56    /// Convert from IPv6Pattern to our simplified PatternId
57    pub fn from_ipv6_pattern(pattern: &crate::ipv6_perfect_patterns::IPv6Pattern) -> Self {
58        use crate::ipv6_perfect_patterns::IPv6Pattern;
59        match pattern {
60            IPv6Pattern::Loopback => IPv6PatternId::Loopback,
61            IPv6Pattern::Unspecified => IPv6PatternId::Unspecified,
62            IPv6Pattern::LinkLocal(_) => IPv6PatternId::LinkLocal,
63            IPv6Pattern::Documentation(_) => IPv6PatternId::Documentation,
64            IPv6Pattern::UniqueLocal(_) => IPv6PatternId::UniqueLocal,
65            IPv6Pattern::GlobalUnicast(_) => IPv6PatternId::GlobalUnicast,
66            IPv6Pattern::Multicast(_) => IPv6PatternId::Multicast,
67            IPv6Pattern::CloudProvider(_)
68            | IPv6Pattern::CommonProvider(_)
69            | IPv6Pattern::Unstructured => IPv6PatternId::Other,
70        }
71    }
72}
73
74/// IPv6 Pattern Feistel Network for reversible pattern-data mixing
75pub struct IPv6PatternFeistel;
76
77impl IPv6PatternFeistel {
78    /// Encode pattern ID into compressed IPv6 data
79    ///
80    /// Takes a 3-bit pattern ID and variable-length data (16-144 bits),
81    /// returns mixed data with pattern information embedded.
82    pub fn encode(pattern_id: IPv6PatternId, data: u64, data_bits: usize) -> Result<u64> {
83        if data_bits > 61 {
84            return Err(FourWordError::InvalidInput(
85                "Data too large for Feistel encoding (max 61 bits)".to_string(),
86            ));
87        }
88
89        let pattern_bits = pattern_id.to_bits() as u64;
90
91        // For small data, use simple bit interleaving
92        if data_bits <= 32 {
93            Self::encode_small(pattern_bits, data, data_bits)
94        } else {
95            Self::encode_large(pattern_bits, data, data_bits)
96        }
97    }
98
99    /// Decode pattern ID from mixed data
100    ///
101    /// Takes mixed data and returns the original pattern ID and data.
102    pub fn decode(mixed_data: u64, original_data_bits: usize) -> Result<(IPv6PatternId, u64)> {
103        if original_data_bits > 61 {
104            return Err(FourWordError::InvalidInput(
105                "Data too large for Feistel decoding (max 61 bits)".to_string(),
106            ));
107        }
108
109        // For small data, use simple bit de-interleaving
110        if original_data_bits <= 32 {
111            Self::decode_small(mixed_data, original_data_bits)
112        } else {
113            Self::decode_large(mixed_data, original_data_bits)
114        }
115    }
116
117    /// Encode small data (≤32 bits) using bit interleaving
118    fn encode_small(pattern_bits: u64, data: u64, data_bits: usize) -> Result<u64> {
119        // Strategy: Interleave pattern bits with data bits
120        // Pattern: ABC (3 bits)
121        // Data: DEFGHIJKLMNOPQRSTUVWXYZ... (up to 32 bits)
122        // Result: ADEFBGHICJKLMNOPQRSTUVWXYZ...
123
124        let mut result = 0u64;
125        let pattern_mask = pattern_bits;
126        let data_mask = data & ((1u64 << data_bits) - 1);
127
128        // Place pattern bits at positions 0, 8, 16 for easy extraction
129        result |= pattern_mask & 1; // A at bit 0
130        result |= ((pattern_mask >> 1) & 1) << 8; // B at bit 8  
131        result |= ((pattern_mask >> 2) & 1) << 16; // C at bit 16
132
133        // Place data starting at bit 1, leaving gaps for pattern bits
134        let mut data_pos = 0;
135        let mut result_pos = 1;
136
137        while data_pos < data_bits && result_pos < 64 {
138            if result_pos == 8 || result_pos == 16 {
139                result_pos += 1; // Skip pattern bit positions
140                continue;
141            }
142
143            let bit = (data_mask >> data_pos) & 1;
144            result |= bit << result_pos;
145            data_pos += 1;
146            result_pos += 1;
147        }
148
149        Ok(result)
150    }
151
152    /// Decode small data (≤32 bits) using bit de-interleaving
153    fn decode_small(mixed_data: u64, original_data_bits: usize) -> Result<(IPv6PatternId, u64)> {
154        // Extract pattern bits from positions 0, 8, 16
155        let pattern_a = mixed_data & 1;
156        let pattern_b = (mixed_data >> 8) & 1;
157        let pattern_c = (mixed_data >> 16) & 1;
158        let pattern_bits = pattern_a | (pattern_b << 1) | (pattern_c << 2);
159
160        // Extract data bits from remaining positions
161        let mut data = 0u64;
162        let mut data_pos = 0;
163        let mut mixed_pos = 1;
164
165        while data_pos < original_data_bits && mixed_pos < 64 {
166            if mixed_pos == 8 || mixed_pos == 16 {
167                mixed_pos += 1; // Skip pattern bit positions
168                continue;
169            }
170
171            let bit = (mixed_data >> mixed_pos) & 1;
172            data |= bit << data_pos;
173            data_pos += 1;
174            mixed_pos += 1;
175        }
176
177        let pattern_id = IPv6PatternId::from_bits(pattern_bits as u8);
178        Ok((pattern_id, data))
179    }
180
181    /// Encode large data (>32 bits) using direct bit insertion
182    fn encode_large(pattern_bits: u64, data: u64, data_bits: usize) -> Result<u64> {
183        // For large data, use a simpler approach:
184        // Insert pattern bits at specific positions in the data
185
186        // Reserve the top 3 bits for pattern ID
187        if data_bits > 61 {
188            return Err(FourWordError::InvalidInput(
189                "Data too large for pattern encoding (max 61 bits)".to_string(),
190            ));
191        }
192
193        let data_mask = (1u64 << data_bits) - 1;
194        let masked_data = data & data_mask;
195
196        // Insert pattern at top 3 bits
197        let result = (pattern_bits << 61) | masked_data;
198        Ok(result)
199    }
200
201    /// Decode large data (>32 bits) using direct bit extraction
202    fn decode_large(mixed_data: u64, original_data_bits: usize) -> Result<(IPv6PatternId, u64)> {
203        // Extract pattern from top 3 bits
204        let pattern_bits = (mixed_data >> 61) & 0x7;
205        let pattern_id = IPv6PatternId::from_bits(pattern_bits as u8);
206
207        // Extract data from remaining bits
208        let data_mask = (1u64 << original_data_bits) - 1;
209        let data = mixed_data & data_mask;
210
211        Ok((pattern_id, data))
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218
219    #[test]
220    fn test_pattern_id_conversion() {
221        for id in 0..8 {
222            let pattern = IPv6PatternId::from_bits(id);
223            assert_eq!(pattern.to_bits(), id);
224        }
225    }
226
227    #[test]
228    fn test_small_data_roundtrip() {
229        let test_cases = [
230            (IPv6PatternId::Loopback, 0x1234, 16),
231            (IPv6PatternId::Documentation, 0xABCDEF, 24),
232            (IPv6PatternId::LinkLocal, 0x12345678, 32),
233        ];
234
235        for (pattern_id, data, bits) in test_cases {
236            let encoded = IPv6PatternFeistel::encode(pattern_id, data, bits).unwrap();
237            let (decoded_pattern, decoded_data) =
238                IPv6PatternFeistel::decode(encoded, bits).unwrap();
239
240            assert_eq!(decoded_pattern, pattern_id);
241            assert_eq!(decoded_data, data);
242        }
243    }
244
245    #[test]
246    fn test_large_data_roundtrip() {
247        let test_cases = [
248            (IPv6PatternId::Loopback, 0x123456789ABC, 48),
249            (IPv6PatternId::Documentation, 0x1FFFFFFFFFFFF, 53),
250            (IPv6PatternId::LinkLocal, 0x1FFFFFFFFFFFFFFF, 61),
251        ];
252
253        for (pattern_id, data, bits) in test_cases {
254            println!("Testing pattern {pattern_id:?} with data 0x{data:X} ({bits} bits)");
255
256            let encoded = IPv6PatternFeistel::encode(pattern_id, data, bits).unwrap();
257            println!("  Encoded: 0x{encoded:X}");
258
259            let (decoded_pattern, decoded_data) =
260                IPv6PatternFeistel::decode(encoded, bits).unwrap();
261            println!("  Decoded pattern: {decoded_pattern:?}, data: 0x{decoded_data:X}");
262
263            assert_eq!(
264                decoded_pattern, pattern_id,
265                "Pattern mismatch for {pattern_id:?}"
266            );
267            assert_eq!(decoded_data, data, "Data mismatch for {pattern_id:?}");
268        }
269    }
270
271    #[test]
272    fn test_different_patterns_produce_different_output() {
273        let data = 0x12345678;
274        let bits = 32;
275
276        let encoded1 = IPv6PatternFeistel::encode(IPv6PatternId::Loopback, data, bits).unwrap();
277        let encoded2 =
278            IPv6PatternFeistel::encode(IPv6PatternId::Documentation, data, bits).unwrap();
279
280        assert_ne!(
281            encoded1, encoded2,
282            "Different patterns should produce different encoded data"
283        );
284    }
285
286    #[test]
287    fn test_deterministic_encoding() {
288        let pattern_id = IPv6PatternId::Documentation;
289        let data = 0xABCDEF;
290        let bits = 24;
291
292        let encoded1 = IPv6PatternFeistel::encode(pattern_id, data, bits).unwrap();
293        let encoded2 = IPv6PatternFeistel::encode(pattern_id, data, bits).unwrap();
294
295        assert_eq!(encoded1, encoded2, "Encoding should be deterministic");
296    }
297}