1use elara_core::{ElaraError, ElaraResult, NodeId, PacketClass, RepresentationProfile, SessionId};
15
16use crate::FrameFlags;
17
18pub const FIXED_HEADER_SIZE: usize = 30;
20
21pub const WIRE_VERSION: u8 = 0;
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
26#[repr(u8)]
27pub enum CryptoSuite {
28 #[default]
30 Suite0 = 0,
31 Suite1 = 1,
33 Suite2 = 2,
35}
36
37impl CryptoSuite {
38 pub fn from_nibble(n: u8) -> Option<Self> {
39 match n {
40 0 => Some(CryptoSuite::Suite0),
41 1 => Some(CryptoSuite::Suite1),
42 2 => Some(CryptoSuite::Suite2),
43 _ => None,
44 }
45 }
46
47 #[inline]
48 pub fn to_nibble(self) -> u8 {
49 self as u8
50 }
51}
52
53#[derive(Clone, Debug)]
55pub struct FixedHeader {
56 pub version: u8,
58 pub crypto_suite: CryptoSuite,
60 pub flags: FrameFlags,
62 pub header_len: u16,
64 pub session_id: SessionId,
66 pub node_id: NodeId,
68 pub class: PacketClass,
70 pub profile: RepresentationProfile,
72 pub time_hint: i32,
74 pub seq_window: u32,
76}
77
78impl FixedHeader {
79 pub fn new(session_id: SessionId, node_id: NodeId) -> Self {
81 FixedHeader {
82 version: WIRE_VERSION,
83 crypto_suite: CryptoSuite::default(),
84 flags: FrameFlags::NONE,
85 header_len: FIXED_HEADER_SIZE as u16,
86 session_id,
87 node_id,
88 class: PacketClass::Core,
89 profile: RepresentationProfile::Textual,
90 time_hint: 0,
91 seq_window: 0,
92 }
93 }
94
95 #[inline]
97 pub fn seq(&self) -> u16 {
98 (self.seq_window >> 16) as u16
99 }
100
101 #[inline]
103 pub fn window(&self) -> u16 {
104 (self.seq_window & 0xFFFF) as u16
105 }
106
107 #[inline]
109 pub fn set_seq(&mut self, seq: u16) {
110 self.seq_window = ((seq as u32) << 16) | (self.seq_window & 0xFFFF);
111 }
112
113 #[inline]
115 pub fn set_window(&mut self, window: u16) {
116 self.seq_window = (self.seq_window & 0xFFFF0000) | (window as u32);
117 }
118
119 pub fn parse(buf: &[u8]) -> ElaraResult<Self> {
121 if buf.len() < FIXED_HEADER_SIZE {
122 return Err(ElaraError::BufferTooShort {
123 expected: FIXED_HEADER_SIZE,
124 actual: buf.len(),
125 });
126 }
127
128 let version = buf[0] >> 4;
130 let crypto_suite = CryptoSuite::from_nibble(buf[0] & 0x0F)
131 .ok_or_else(|| ElaraError::InvalidWireFormat("Unknown crypto suite".into()))?;
132
133 let flags = FrameFlags::new(buf[1]);
135
136 let header_len = u16::from_le_bytes([buf[2], buf[3]]);
138
139 let session_id = SessionId::from_bytes(buf[4..12].try_into().unwrap());
141
142 let node_id = NodeId::from_bytes(buf[12..20].try_into().unwrap());
144
145 let class = PacketClass::from_byte(buf[20])
147 .ok_or_else(|| ElaraError::UnknownPacketClass(buf[20]))?;
148
149 let profile = RepresentationProfile::from_byte(buf[21]);
151
152 let time_hint = i32::from_le_bytes(buf[22..26].try_into().unwrap());
154
155 let seq_window = u32::from_le_bytes(buf[26..30].try_into().unwrap());
157
158 Ok(FixedHeader {
159 version,
160 crypto_suite,
161 flags,
162 header_len,
163 session_id,
164 node_id,
165 class,
166 profile,
167 time_hint,
168 seq_window,
169 })
170 }
171
172 pub fn serialize(&self, buf: &mut [u8]) -> ElaraResult<()> {
174 if buf.len() < FIXED_HEADER_SIZE {
175 return Err(ElaraError::BufferTooShort {
176 expected: FIXED_HEADER_SIZE,
177 actual: buf.len(),
178 });
179 }
180
181 buf[0] = (self.version << 4) | self.crypto_suite.to_nibble();
183
184 buf[1] = self.flags.0;
186
187 buf[2..4].copy_from_slice(&self.header_len.to_le_bytes());
189
190 buf[4..12].copy_from_slice(&self.session_id.to_bytes());
192
193 buf[12..20].copy_from_slice(&self.node_id.to_bytes());
195
196 buf[20] = self.class.to_byte();
198
199 buf[21] = self.profile.to_byte();
201
202 buf[22..26].copy_from_slice(&self.time_hint.to_le_bytes());
204
205 buf[26..30].copy_from_slice(&self.seq_window.to_le_bytes());
207
208 Ok(())
209 }
210
211 pub fn to_bytes(&self) -> Vec<u8> {
213 let mut buf = vec![0u8; FIXED_HEADER_SIZE];
214 self.serialize(&mut buf).unwrap();
215 buf
216 }
217}
218
219impl Default for FixedHeader {
220 fn default() -> Self {
221 FixedHeader::new(SessionId::ZERO, NodeId::ZERO)
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[test]
230 fn test_header_roundtrip() {
231 let header = FixedHeader {
232 version: WIRE_VERSION,
233 crypto_suite: CryptoSuite::Suite0,
234 flags: FrameFlags(FrameFlags::MULTIPATH | FrameFlags::PRIORITY),
235 header_len: 30,
236 session_id: SessionId::new(0xDEADBEEF_CAFEBABE),
237 node_id: NodeId::new(0x12345678_9ABCDEF0),
238 class: PacketClass::Perceptual,
239 profile: RepresentationProfile::VoiceMinimal,
240 time_hint: -12345,
241 seq_window: 0x00010002,
242 };
243
244 let bytes = header.to_bytes();
245 assert_eq!(bytes.len(), FIXED_HEADER_SIZE);
246
247 let parsed = FixedHeader::parse(&bytes).unwrap();
248
249 assert_eq!(parsed.version, header.version);
250 assert_eq!(parsed.crypto_suite, header.crypto_suite);
251 assert_eq!(parsed.flags, header.flags);
252 assert_eq!(parsed.header_len, header.header_len);
253 assert_eq!(parsed.session_id, header.session_id);
254 assert_eq!(parsed.node_id, header.node_id);
255 assert_eq!(parsed.class, header.class);
256 assert_eq!(parsed.profile, header.profile);
257 assert_eq!(parsed.time_hint, header.time_hint);
258 assert_eq!(parsed.seq_window, header.seq_window);
259 }
260
261 #[test]
262 fn test_seq_window_accessors() {
263 let mut header = FixedHeader::default();
264
265 header.set_seq(0x1234);
266 header.set_window(0x5678);
267
268 assert_eq!(header.seq(), 0x1234);
269 assert_eq!(header.window(), 0x5678);
270 assert_eq!(header.seq_window, 0x12345678);
271 }
272
273 #[test]
274 fn test_header_too_short() {
275 let buf = [0u8; 20]; let result = FixedHeader::parse(&buf);
277 assert!(matches!(result, Err(ElaraError::BufferTooShort { .. })));
278 }
279
280 #[test]
281 fn test_header_size() {
282 assert_eq!(FIXED_HEADER_SIZE, 30);
283 }
284}