1use crate::zeitgeist::Zeitgeist;
4
5pub const FLUX_MAGIC: [u8; 4] = [0x46, 0x4C, 0x55, 0x58]; pub const FLAG_COMPRESSED: u8 = 0x01;
10pub const FLAG_ENCRYPTED: u8 = 0x02;
11
12#[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 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, }
43 }
44
45 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 pub fn encode(&self) -> Vec<u8> {
56 let zeitgeist_bytes = self.zeitgeist.encode();
57
58 let mut header = Vec::with_capacity(25);
60 header.extend_from_slice(&self.magic); header.extend_from_slice(&self.version.to_be_bytes()); header.push(self.flags); header.extend_from_slice(&self.source.to_be_bytes()); header.extend_from_slice(&self.target.to_be_bytes()); header.extend_from_slice(&self.timestamp.to_be_bytes()); header.extend_from_slice(&(self.payload.len() as u32).to_be_bytes()); header.extend_from_slice(&(zeitgeist_bytes.len() as u32).to_be_bytes()); 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 pub fn decode(data: &[u8]) -> Result<Self, String> {
84 if data.len() < 26 {
85 return Err("Packet too short".into());
86 }
87
88 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; 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 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 encoded[10] ^= 0xFF;
193 assert!(FluxPacket::decode(&encoded).is_err());
194 }
195}