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