four-word-networking 2.7.0

Convert IP addresses to memorable, family-friendly word groups. Interactive TUI with real-time autocomplete. IPv4 = 4 words, IPv6 = 6-12 words. Perfect reconstruction with progressive hints.
Documentation
//! IPv6 Pattern Feistel Network
//!
//! A lightweight Feistel-like cipher designed specifically for preserving IPv6 pattern
//! information during encoding/decoding cycles. This is NOT a cryptographic cipher,
//! but rather a reversible mixing function that embeds pattern IDs into compressed data.
//!
//! ## Problem Solved
//! When IPv6 addresses are compressed based on patterns (Loopback, LinkLocal, etc.)
//! and then encoded to words, the pattern information is lost during decoding.
//! This network reversibly mixes the 3-bit pattern ID with the compressed data.
//!
//! ## Design Principles
//! - Lightweight and fast (<1μs operations)
//! - Variable-length data support (16-144 bits)
//! - Perfect reversibility (bijective function)
//! - No cryptographic dependencies
//! - Deterministic behavior

use crate::{FourWordError, Result};

/// IPv6 pattern IDs for Feistel encoding
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum IPv6PatternId {
    Loopback = 0,
    Unspecified = 1,
    LinkLocal = 2,
    Documentation = 3,
    UniqueLocal = 4,
    GlobalUnicast = 5,
    Multicast = 6,
    Other = 7, // CloudProvider, CommonProvider, Unstructured all map to this
}

impl IPv6PatternId {
    /// Convert pattern ID to 3-bit value
    pub fn to_bits(self) -> u8 {
        self as u8 & 0x7 // Ensure only 3 bits
    }

    /// Create pattern ID from 3-bit value
    pub fn from_bits(bits: u8) -> Self {
        match bits & 0x7 {
            0 => IPv6PatternId::Loopback,
            1 => IPv6PatternId::Unspecified,
            2 => IPv6PatternId::LinkLocal,
            3 => IPv6PatternId::Documentation,
            4 => IPv6PatternId::UniqueLocal,
            5 => IPv6PatternId::GlobalUnicast,
            6 => IPv6PatternId::Multicast,
            7 => IPv6PatternId::Other,
            _ => unreachable!(),
        }
    }

    /// Convert from IPv6Pattern to our simplified PatternId
    pub fn from_ipv6_pattern(pattern: &crate::ipv6_perfect_patterns::IPv6Pattern) -> Self {
        use crate::ipv6_perfect_patterns::IPv6Pattern;
        match pattern {
            IPv6Pattern::Loopback => IPv6PatternId::Loopback,
            IPv6Pattern::Unspecified => IPv6PatternId::Unspecified,
            IPv6Pattern::LinkLocal(_) => IPv6PatternId::LinkLocal,
            IPv6Pattern::Documentation(_) => IPv6PatternId::Documentation,
            IPv6Pattern::UniqueLocal(_) => IPv6PatternId::UniqueLocal,
            IPv6Pattern::GlobalUnicast(_) => IPv6PatternId::GlobalUnicast,
            IPv6Pattern::Multicast(_) => IPv6PatternId::Multicast,
            IPv6Pattern::CloudProvider(_)
            | IPv6Pattern::CommonProvider(_)
            | IPv6Pattern::Unstructured => IPv6PatternId::Other,
        }
    }
}

/// IPv6 Pattern Feistel Network for reversible pattern-data mixing
pub struct IPv6PatternFeistel;

impl IPv6PatternFeistel {
    /// Encode pattern ID into compressed IPv6 data
    ///
    /// Takes a 3-bit pattern ID and variable-length data (16-144 bits),
    /// returns mixed data with pattern information embedded.
    pub fn encode(pattern_id: IPv6PatternId, data: u64, data_bits: usize) -> Result<u64> {
        if data_bits > 61 {
            return Err(FourWordError::InvalidInput(
                "Data too large for Feistel encoding (max 61 bits)".to_string(),
            ));
        }

        let pattern_bits = pattern_id.to_bits() as u64;

        // For small data, use simple bit interleaving
        if data_bits <= 32 {
            Self::encode_small(pattern_bits, data, data_bits)
        } else {
            Self::encode_large(pattern_bits, data, data_bits)
        }
    }

    /// Decode pattern ID from mixed data
    ///
    /// Takes mixed data and returns the original pattern ID and data.
    pub fn decode(mixed_data: u64, original_data_bits: usize) -> Result<(IPv6PatternId, u64)> {
        if original_data_bits > 61 {
            return Err(FourWordError::InvalidInput(
                "Data too large for Feistel decoding (max 61 bits)".to_string(),
            ));
        }

        // For small data, use simple bit de-interleaving
        if original_data_bits <= 32 {
            Self::decode_small(mixed_data, original_data_bits)
        } else {
            Self::decode_large(mixed_data, original_data_bits)
        }
    }

    /// Encode small data (≤32 bits) using bit interleaving
    fn encode_small(pattern_bits: u64, data: u64, data_bits: usize) -> Result<u64> {
        // Strategy: Interleave pattern bits with data bits
        // Pattern: ABC (3 bits)
        // Data: DEFGHIJKLMNOPQRSTUVWXYZ... (up to 32 bits)
        // Result: ADEFBGHICJKLMNOPQRSTUVWXYZ...

        let mut result = 0u64;
        let pattern_mask = pattern_bits;
        let data_mask = data & ((1u64 << data_bits) - 1);

        // Place pattern bits at positions 0, 8, 16 for easy extraction
        result |= pattern_mask & 1; // A at bit 0
        result |= ((pattern_mask >> 1) & 1) << 8; // B at bit 8
        result |= ((pattern_mask >> 2) & 1) << 16; // C at bit 16

        // Place data starting at bit 1, leaving gaps for pattern bits
        let mut data_pos = 0;
        let mut result_pos = 1;

        while data_pos < data_bits && result_pos < 64 {
            if result_pos == 8 || result_pos == 16 {
                result_pos += 1; // Skip pattern bit positions
                continue;
            }

            let bit = (data_mask >> data_pos) & 1;
            result |= bit << result_pos;
            data_pos += 1;
            result_pos += 1;
        }

        Ok(result)
    }

    /// Decode small data (≤32 bits) using bit de-interleaving
    fn decode_small(mixed_data: u64, original_data_bits: usize) -> Result<(IPv6PatternId, u64)> {
        // Extract pattern bits from positions 0, 8, 16
        let pattern_a = mixed_data & 1;
        let pattern_b = (mixed_data >> 8) & 1;
        let pattern_c = (mixed_data >> 16) & 1;
        let pattern_bits = pattern_a | (pattern_b << 1) | (pattern_c << 2);

        // Extract data bits from remaining positions
        let mut data = 0u64;
        let mut data_pos = 0;
        let mut mixed_pos = 1;

        while data_pos < original_data_bits && mixed_pos < 64 {
            if mixed_pos == 8 || mixed_pos == 16 {
                mixed_pos += 1; // Skip pattern bit positions
                continue;
            }

            let bit = (mixed_data >> mixed_pos) & 1;
            data |= bit << data_pos;
            data_pos += 1;
            mixed_pos += 1;
        }

        let pattern_id = IPv6PatternId::from_bits(pattern_bits as u8);
        Ok((pattern_id, data))
    }

    /// Encode large data (>32 bits) using direct bit insertion
    fn encode_large(pattern_bits: u64, data: u64, data_bits: usize) -> Result<u64> {
        // For large data, use a simpler approach:
        // Insert pattern bits at specific positions in the data

        // Reserve the top 3 bits for pattern ID
        if data_bits > 61 {
            return Err(FourWordError::InvalidInput(
                "Data too large for pattern encoding (max 61 bits)".to_string(),
            ));
        }

        let data_mask = (1u64 << data_bits) - 1;
        let masked_data = data & data_mask;

        // Insert pattern at top 3 bits
        let result = (pattern_bits << 61) | masked_data;
        Ok(result)
    }

    /// Decode large data (>32 bits) using direct bit extraction
    fn decode_large(mixed_data: u64, original_data_bits: usize) -> Result<(IPv6PatternId, u64)> {
        // Extract pattern from top 3 bits
        let pattern_bits = (mixed_data >> 61) & 0x7;
        let pattern_id = IPv6PatternId::from_bits(pattern_bits as u8);

        // Extract data from remaining bits
        let data_mask = (1u64 << original_data_bits) - 1;
        let data = mixed_data & data_mask;

        Ok((pattern_id, data))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_pattern_id_conversion() {
        for id in 0..8 {
            let pattern = IPv6PatternId::from_bits(id);
            assert_eq!(pattern.to_bits(), id);
        }
    }

    #[test]
    fn test_small_data_roundtrip() {
        let test_cases = [
            (IPv6PatternId::Loopback, 0x1234, 16),
            (IPv6PatternId::Documentation, 0xABCDEF, 24),
            (IPv6PatternId::LinkLocal, 0x12345678, 32),
        ];

        for (pattern_id, data, bits) in test_cases {
            let encoded = IPv6PatternFeistel::encode(pattern_id, data, bits).unwrap();
            let (decoded_pattern, decoded_data) =
                IPv6PatternFeistel::decode(encoded, bits).unwrap();

            assert_eq!(decoded_pattern, pattern_id);
            assert_eq!(decoded_data, data);
        }
    }

    #[test]
    fn test_large_data_roundtrip() {
        let test_cases = [
            (IPv6PatternId::Loopback, 0x123456789ABC, 48),
            (IPv6PatternId::Documentation, 0x1FFFFFFFFFFFF, 53),
            (IPv6PatternId::LinkLocal, 0x1FFFFFFFFFFFFFFF, 61),
        ];

        for (pattern_id, data, bits) in test_cases {
            println!("Testing pattern {pattern_id:?} with data 0x{data:X} ({bits} bits)");

            let encoded = IPv6PatternFeistel::encode(pattern_id, data, bits).unwrap();
            println!("  Encoded: 0x{encoded:X}");

            let (decoded_pattern, decoded_data) =
                IPv6PatternFeistel::decode(encoded, bits).unwrap();
            println!("  Decoded pattern: {decoded_pattern:?}, data: 0x{decoded_data:X}");

            assert_eq!(
                decoded_pattern, pattern_id,
                "Pattern mismatch for {pattern_id:?}"
            );
            assert_eq!(decoded_data, data, "Data mismatch for {pattern_id:?}");
        }
    }

    #[test]
    fn test_different_patterns_produce_different_output() {
        let data = 0x12345678;
        let bits = 32;

        let encoded1 = IPv6PatternFeistel::encode(IPv6PatternId::Loopback, data, bits).unwrap();
        let encoded2 =
            IPv6PatternFeistel::encode(IPv6PatternId::Documentation, data, bits).unwrap();

        assert_ne!(
            encoded1, encoded2,
            "Different patterns should produce different encoded data"
        );
    }

    #[test]
    fn test_deterministic_encoding() {
        let pattern_id = IPv6PatternId::Documentation;
        let data = 0xABCDEF;
        let bits = 24;

        let encoded1 = IPv6PatternFeistel::encode(pattern_id, data, bits).unwrap();
        let encoded2 = IPv6PatternFeistel::encode(pattern_id, data, bits).unwrap();

        assert_eq!(encoded1, encoded2, "Encoding should be deterministic");
    }
}