Skip to main content

zeitgeist_protocol/
packet.rs

1//! FLUX packet encode/decode
2
3use crate::zeitgeist::Zeitgeist;
4
5/// FLUX magic bytes: 0xFLUX
6pub const FLUX_MAGIC: [u8; 4] = [0x46, 0x4C, 0x55, 0x58]; // "FLUX"
7
8/// Packet flags
9pub const FLAG_COMPRESSED: u8 = 0x01;
10pub const FLAG_ENCRYPTED: u8 = 0x02;
11
12/// FLUX packet — the wire format for Zeitgeist transference
13#[derive(Debug, Clone, PartialEq)]
14pub struct FluxPacket {
15    pub magic: [u8; 4],
16    pub version: u16,
17    pub flags: u8,
18    pub source: u32,
19    pub target: u32,
20    pub timestamp: f64,
21    pub payload: Vec<u8>,
22    pub zeitgeist: Zeitgeist,
23    pub parity: u8,
24}
25
26impl FluxPacket {
27    /// Create a new FLUX packet with default magic
28    pub fn new(source: u32, target: u32, payload: Vec<u8>, zeitgeist: Zeitgeist) -> Self {
29        Self {
30            magic: FLUX_MAGIC,
31            version: 1,
32            flags: 0,
33            source,
34            target,
35            timestamp: std::time::SystemTime::now()
36                .duration_since(std::time::UNIX_EPOCH)
37                .unwrap()
38                .as_secs_f64(),
39            payload,
40            zeitgeist,
41            parity: 0, // computed during encode
42        }
43    }
44
45    /// Compute parity: XOR of all bytes in the packet (excluding parity field itself)
46    fn compute_parity(header: &[u8], payload: &[u8], zeitgeist_bytes: &[u8]) -> u8 {
47        let mut p: u8 = 0;
48        for &b in header { p ^= b; }
49        for &b in payload { p ^= b; }
50        for &b in zeitgeist_bytes { p ^= b; }
51        p
52    }
53
54    /// Encode to binary wire format
55    pub fn encode(&self) -> Vec<u8> {
56        let zeitgeist_bytes = self.zeitgeist.encode();
57
58        // Build header (25 bytes before lengths)
59        let mut header = Vec::with_capacity(25);
60        header.extend_from_slice(&self.magic);           // 4 bytes
61        header.extend_from_slice(&self.version.to_be_bytes()); // 2 bytes
62        header.push(self.flags);                          // 1 byte
63        header.extend_from_slice(&self.source.to_be_bytes()); // 4 bytes
64        header.extend_from_slice(&self.target.to_be_bytes()); // 4 bytes
65        header.extend_from_slice(&self.timestamp.to_be_bytes()); // 8 bytes
66        header.extend_from_slice(&(self.payload.len() as u32).to_be_bytes()); // 4 bytes
67        header.extend_from_slice(&(zeitgeist_bytes.len() as u32).to_be_bytes()); // 4 bytes
68
69        let parity = Self::compute_parity(&header, &self.payload, &zeitgeist_bytes);
70
71        let mut buf = Vec::with_capacity(
72            header.len() + self.payload.len() + zeitgeist_bytes.len() + 1
73        );
74        buf.extend_from_slice(&header);
75        buf.extend_from_slice(&self.payload);
76        buf.extend_from_slice(&zeitgeist_bytes);
77        buf.push(parity);
78
79        buf
80    }
81
82    /// Decode from binary wire format
83    pub fn decode(data: &[u8]) -> Result<Self, String> {
84        if data.len() < 26 {
85            return Err("Packet too short".into());
86        }
87
88        // Parse header
89        let magic = &data[0..4];
90        if magic != FLUX_MAGIC {
91            return Err(format!("Invalid magic: {:02X?}", magic));
92        }
93
94        let version = u16::from_be_bytes([data[4], data[5]]);
95        let flags = data[6];
96        let source = u32::from_be_bytes([data[7], data[8], data[9], data[10]]);
97        let target = u32::from_be_bytes([data[11], data[12], data[13], data[14]]);
98        let timestamp = f64::from_be_bytes([
99            data[15], data[16], data[17], data[18],
100            data[19], data[20], data[21], data[22],
101        ]);
102        let payload_len = u32::from_be_bytes([data[23], data[24], data[25], data[26]]) as usize;
103        let zeitgeist_len = u32::from_be_bytes([data[27], data[28], data[29], data[30]]) as usize;
104
105        let expected_len = 31 + payload_len + zeitgeist_len + 1; // +1 for parity
106        if data.len() < expected_len {
107            return Err(format!(
108                "Packet truncated: expected {} bytes, got {}",
109                expected_len,
110                data.len()
111            ));
112        }
113
114        let payload = data[31..31 + payload_len].to_vec();
115        let zeitgeist_data = &data[31 + payload_len..31 + payload_len + zeitgeist_len];
116        let zeitgeist = Zeitgeist::decode(zeitgeist_data)?;
117        let parity = data[31 + payload_len + zeitgeist_len];
118
119        // Verify parity
120        let header = &data[0..31];
121        let computed_parity = Self::compute_parity(header, &payload, zeitgeist_data);
122        if computed_parity != parity {
123            return Err(format!(
124                "Parity mismatch: computed 0x{:02X}, got 0x{:02X}",
125                computed_parity, parity
126            ));
127        }
128
129        Ok(Self {
130            magic: [magic[0], magic[1], magic[2], magic[3]],
131            version,
132            flags,
133            source,
134            target,
135            timestamp,
136            payload,
137            zeitgeist,
138            parity,
139        })
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use crate::precision::PrecisionState;
147    use crate::confidence::ConfidenceState;
148    use crate::trajectory::{TrajectoryState, Trend};
149    use crate::consensus::ConsensusState;
150    use crate::temporal::{TemporalState, Phase};
151    use std::collections::BTreeMap;
152
153    fn test_zeitgeist() -> Zeitgeist {
154        Zeitgeist::new(
155            PrecisionState::new(100.0, 0.5, false),
156            ConfidenceState::new([1u8; 32], 0xFF, 0.8),
157            TrajectoryState::new(0.7, Trend::Rising, 0.3),
158            ConsensusState::new(0.1, 0.9, BTreeMap::from([(1u64, 5u64), (2u64, 3u64)])),
159            TemporalState::new(0.5, Phase::Approaching, 0.95),
160        )
161    }
162
163    #[test]
164    fn test_packet_roundtrip() {
165        let zg = test_zeitgeist();
166        let packet = FluxPacket::new(42, 99, b"hello flux".to_vec(), zg);
167        let encoded = packet.encode();
168        let decoded = FluxPacket::decode(&encoded).unwrap();
169
170        assert_eq!(decoded.magic, FLUX_MAGIC);
171        assert_eq!(decoded.version, 1);
172        assert_eq!(decoded.flags, 0);
173        assert_eq!(decoded.source, 42);
174        assert_eq!(decoded.target, 99);
175        assert_eq!(decoded.payload, b"hello flux".to_vec());
176        assert_eq!(decoded.zeitgeist, packet.zeitgeist);
177    }
178
179    #[test]
180    fn test_invalid_magic() {
181        let mut data = vec![0x00, 0x00, 0x00, 0x00];
182        data.extend_from_slice(&[0; 22]);
183        assert!(FluxPacket::decode(&data).is_err());
184    }
185
186    #[test]
187    fn test_parity_check() {
188        let zg = test_zeitgeist();
189        let packet = FluxPacket::new(1, 2, vec![], zg);
190        let mut encoded = packet.encode();
191        // Corrupt a byte
192        encoded[10] ^= 0xFF;
193        assert!(FluxPacket::decode(&encoded).is_err());
194    }
195}