Skip to main content

wire/
header.rs

1//! Packet header types and constants.
2
3/// Magic number identifying sdec packets.
4///
5/// This value is fixed and must never change across versions.
6pub const MAGIC: u32 = 0x5344_4543; // "SDEC" in ASCII
7
8/// Current wire format version.
9pub const VERSION: u16 = 2;
10
11/// Header size in bytes (28 total).
12pub const HEADER_SIZE: usize = 4 + 2 + 2 + 8 + 4 + 4 + 4;
13
14/// Packet flags.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
16pub struct PacketFlags(u16);
17
18impl PacketFlags {
19    /// Flag indicating a full snapshot packet.
20    pub const FULL_SNAPSHOT: u16 = 1 << 0;
21
22    /// Flag indicating a delta snapshot packet.
23    pub const DELTA_SNAPSHOT: u16 = 1 << 1;
24
25    /// Flag indicating a session init packet.
26    pub const SESSION_INIT: u16 = 1 << 2;
27
28    /// Reserved bits mask (must be zero in version 2).
29    const RESERVED_MASK: u16 = !0b111;
30
31    /// Creates new flags from a raw value.
32    #[must_use]
33    pub const fn from_raw(raw: u16) -> Self {
34        Self(raw)
35    }
36
37    /// Returns the raw flag bits.
38    #[must_use]
39    pub const fn raw(self) -> u16 {
40        self.0
41    }
42
43    /// Returns `true` if this is a full snapshot.
44    #[must_use]
45    pub const fn is_full_snapshot(self) -> bool {
46        self.0 & Self::FULL_SNAPSHOT != 0
47    }
48
49    /// Returns `true` if this is a delta snapshot.
50    #[must_use]
51    pub const fn is_delta_snapshot(self) -> bool {
52        self.0 & Self::DELTA_SNAPSHOT != 0
53    }
54
55    /// Returns `true` if the flags are valid for version 0.
56    ///
57    /// Valid means exactly one of `FULL_SNAPSHOT` or `DELTA_SNAPSHOT` is set,
58    /// and no reserved bits are set.
59    #[must_use]
60    pub const fn is_valid_v0(self) -> bool {
61        let has_full = self.is_full_snapshot();
62        let has_delta = self.is_delta_snapshot();
63        let has_reserved = self.0 & Self::RESERVED_MASK != 0;
64
65        has_full ^ has_delta && !has_reserved
66    }
67
68    /// Returns `true` if this is a session init packet.
69    #[must_use]
70    pub const fn is_session_init(self) -> bool {
71        self.0 & Self::SESSION_INIT != 0
72    }
73
74    /// Returns `true` if the flags are valid for version 2.
75    ///
76    /// Valid means either:
77    /// - session init set alone (no full/delta), or
78    /// - exactly one of full/delta set, with no session init,
79    ///   and no reserved bits are set.
80    #[must_use]
81    pub const fn is_valid_v2(self) -> bool {
82        let has_full = self.is_full_snapshot();
83        let has_delta = self.is_delta_snapshot();
84        let has_session = self.is_session_init();
85        let has_reserved = self.0 & Self::RESERVED_MASK != 0;
86        if has_reserved {
87            return false;
88        }
89        if has_session {
90            return !has_full && !has_delta;
91        }
92        has_full ^ has_delta
93    }
94
95    /// Creates flags for a full snapshot.
96    #[must_use]
97    pub const fn full_snapshot() -> Self {
98        Self(Self::FULL_SNAPSHOT)
99    }
100
101    /// Creates flags for a delta snapshot.
102    #[must_use]
103    pub const fn delta_snapshot() -> Self {
104        Self(Self::DELTA_SNAPSHOT)
105    }
106
107    /// Creates flags for a session init packet.
108    #[must_use]
109    pub const fn session_init() -> Self {
110        Self(Self::SESSION_INIT)
111    }
112}
113
114/// Packet header (version 0).
115///
116/// This struct represents the header fields *after* the magic number.
117/// The magic number is validated separately during decoding and is not
118/// stored in this struct.
119///
120/// See `WIRE_FORMAT.md` for the complete specification.
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub struct PacketHeader {
123    /// Wire format version.
124    pub version: u16,
125    /// Packet flags.
126    pub flags: PacketFlags,
127    /// Schema hash for compatibility checking.
128    pub schema_hash: u64,
129    /// Simulation tick this snapshot represents.
130    pub tick: u32,
131    /// Baseline tick for delta packets (0 for full snapshots).
132    pub baseline_tick: u32,
133    /// Payload length in bytes.
134    pub payload_len: u32,
135}
136
137impl PacketHeader {
138    /// Creates a new header for a full snapshot.
139    #[must_use]
140    pub const fn full_snapshot(schema_hash: u64, tick: u32, payload_len: u32) -> Self {
141        Self {
142            version: VERSION,
143            flags: PacketFlags::full_snapshot(),
144            schema_hash,
145            tick,
146            baseline_tick: 0,
147            payload_len,
148        }
149    }
150
151    /// Creates a new header for a delta snapshot.
152    #[must_use]
153    pub const fn delta_snapshot(
154        schema_hash: u64,
155        tick: u32,
156        baseline_tick: u32,
157        payload_len: u32,
158    ) -> Self {
159        Self {
160            version: VERSION,
161            flags: PacketFlags::delta_snapshot(),
162            schema_hash,
163            tick,
164            baseline_tick,
165            payload_len,
166        }
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    // Constants tests
175    #[test]
176    fn magic_is_sdec_ascii() {
177        // S=0x53, D=0x44, E=0x45, C=0x43
178        assert_eq!(MAGIC, 0x5344_4543);
179        let bytes = MAGIC.to_be_bytes();
180        assert_eq!(&bytes, b"SDEC");
181    }
182
183    #[test]
184    fn version_is_two() {
185        assert_eq!(VERSION, 2);
186    }
187
188    #[test]
189    fn header_size_is_correct() {
190        // magic(4) + version(2) + flags(2) + schema_hash(8) + tick(4) + baseline_tick(4) + payload_len(4)
191        assert_eq!(HEADER_SIZE, 28);
192    }
193
194    // PacketFlags tests
195    #[test]
196    fn flags_full_snapshot() {
197        let flags = PacketFlags::full_snapshot();
198        assert!(flags.is_full_snapshot());
199        assert!(!flags.is_delta_snapshot());
200        assert_eq!(flags.raw(), 0b01);
201    }
202
203    #[test]
204    fn flags_delta_snapshot() {
205        let flags = PacketFlags::delta_snapshot();
206        assert!(!flags.is_full_snapshot());
207        assert!(flags.is_delta_snapshot());
208        assert_eq!(flags.raw(), 0b10);
209    }
210
211    #[test]
212    fn flags_from_raw_roundtrip() {
213        let flags = PacketFlags::from_raw(0b01);
214        assert_eq!(flags.raw(), 0b01);
215        assert!(flags.is_full_snapshot());
216    }
217
218    #[test]
219    fn flags_validity_full() {
220        assert!(PacketFlags::full_snapshot().is_valid_v0());
221    }
222
223    #[test]
224    fn flags_validity_delta() {
225        assert!(PacketFlags::delta_snapshot().is_valid_v0());
226    }
227
228    #[test]
229    fn flags_invalid_neither_set() {
230        assert!(!PacketFlags::from_raw(0).is_valid_v0());
231    }
232
233    #[test]
234    fn flags_invalid_both_set() {
235        assert!(!PacketFlags::from_raw(0b11).is_valid_v2());
236    }
237
238    #[test]
239    fn flags_invalid_reserved_bits() {
240        // Full snapshot + reserved bit
241        assert!(!PacketFlags::from_raw(0b1001).is_valid_v2());
242        // High bits set
243        assert!(!PacketFlags::from_raw(0xFF01).is_valid_v2());
244    }
245
246    #[test]
247    fn flags_default() {
248        let flags = PacketFlags::default();
249        assert_eq!(flags.raw(), 0);
250        assert!(!flags.is_valid_v2()); // default is invalid (neither set)
251    }
252
253    #[test]
254    fn flags_equality() {
255        assert_eq!(PacketFlags::full_snapshot(), PacketFlags::from_raw(0b01));
256        assert_ne!(PacketFlags::full_snapshot(), PacketFlags::delta_snapshot());
257    }
258
259    #[test]
260    fn flags_clone_copy() {
261        let flags = PacketFlags::full_snapshot();
262        let copied = flags; // Copy
263        assert_eq!(flags, copied);
264    }
265
266    // PacketHeader tests
267    #[test]
268    fn header_full_snapshot() {
269        let header = PacketHeader::full_snapshot(0x1234_5678_9ABC_DEF0, 100, 512);
270
271        assert_eq!(header.version, VERSION);
272        assert!(header.flags.is_full_snapshot());
273        assert!(!header.flags.is_delta_snapshot());
274        assert_eq!(header.schema_hash, 0x1234_5678_9ABC_DEF0);
275        assert_eq!(header.tick, 100);
276        assert_eq!(header.baseline_tick, 0);
277        assert_eq!(header.payload_len, 512);
278    }
279
280    #[test]
281    fn header_delta_snapshot() {
282        let header = PacketHeader::delta_snapshot(0xABCD, 100, 95, 256);
283
284        assert_eq!(header.version, VERSION);
285        assert!(header.flags.is_delta_snapshot());
286        assert!(!header.flags.is_full_snapshot());
287        assert_eq!(header.schema_hash, 0xABCD);
288        assert_eq!(header.tick, 100);
289        assert_eq!(header.baseline_tick, 95);
290        assert_eq!(header.payload_len, 256);
291    }
292
293    #[test]
294    fn header_equality() {
295        let h1 = PacketHeader::full_snapshot(0x1234, 100, 512);
296        let h2 = PacketHeader::full_snapshot(0x1234, 100, 512);
297        let h3 = PacketHeader::full_snapshot(0x1234, 101, 512);
298
299        assert_eq!(h1, h2);
300        assert_ne!(h1, h3);
301    }
302
303    #[test]
304    fn header_clone_copy() {
305        let header = PacketHeader::full_snapshot(0x1234, 100, 512);
306        let copied = header; // Copy
307        assert_eq!(header, copied);
308    }
309
310    #[test]
311    fn header_debug() {
312        let header = PacketHeader::full_snapshot(0x1234, 100, 512);
313        let debug = format!("{header:?}");
314        assert!(debug.contains("PacketHeader"));
315        assert!(debug.contains("100")); // tick
316    }
317
318    #[test]
319    fn header_const_constructible() {
320        const HEADER: PacketHeader = PacketHeader::full_snapshot(0, 0, 0);
321        assert_eq!(HEADER.tick, 0);
322    }
323}