use crate::{FourWordError, Result};
#[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, }
impl IPv6PatternId {
pub fn to_bits(self) -> u8 {
self as u8 & 0x7 }
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!(),
}
}
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,
}
}
}
pub struct IPv6PatternFeistel;
impl IPv6PatternFeistel {
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;
if data_bits <= 32 {
Self::encode_small(pattern_bits, data, data_bits)
} else {
Self::encode_large(pattern_bits, data, data_bits)
}
}
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(),
));
}
if original_data_bits <= 32 {
Self::decode_small(mixed_data, original_data_bits)
} else {
Self::decode_large(mixed_data, original_data_bits)
}
}
fn encode_small(pattern_bits: u64, data: u64, data_bits: usize) -> Result<u64> {
let mut result = 0u64;
let pattern_mask = pattern_bits;
let data_mask = data & ((1u64 << data_bits) - 1);
result |= pattern_mask & 1; result |= ((pattern_mask >> 1) & 1) << 8; result |= ((pattern_mask >> 2) & 1) << 16;
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; continue;
}
let bit = (data_mask >> data_pos) & 1;
result |= bit << result_pos;
data_pos += 1;
result_pos += 1;
}
Ok(result)
}
fn decode_small(mixed_data: u64, original_data_bits: usize) -> Result<(IPv6PatternId, u64)> {
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);
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; 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))
}
fn encode_large(pattern_bits: u64, data: u64, data_bits: usize) -> Result<u64> {
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;
let result = (pattern_bits << 61) | masked_data;
Ok(result)
}
fn decode_large(mixed_data: u64, original_data_bits: usize) -> Result<(IPv6PatternId, u64)> {
let pattern_bits = (mixed_data >> 61) & 0x7;
let pattern_id = IPv6PatternId::from_bits(pattern_bits as u8);
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");
}
}