1pub const MAGIC: u32 = 0x5344_4543; pub const VERSION: u16 = 0;
10
11pub const HEADER_SIZE: usize = 4 + 2 + 2 + 8 + 4 + 4 + 4;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
16pub struct PacketFlags(u16);
17
18impl PacketFlags {
19 pub const FULL_SNAPSHOT: u16 = 1 << 0;
21
22 pub const DELTA_SNAPSHOT: u16 = 1 << 1;
24
25 const RESERVED_MASK: u16 = !0b11;
27
28 #[must_use]
30 pub const fn from_raw(raw: u16) -> Self {
31 Self(raw)
32 }
33
34 #[must_use]
36 pub const fn raw(self) -> u16 {
37 self.0
38 }
39
40 #[must_use]
42 pub const fn is_full_snapshot(self) -> bool {
43 self.0 & Self::FULL_SNAPSHOT != 0
44 }
45
46 #[must_use]
48 pub const fn is_delta_snapshot(self) -> bool {
49 self.0 & Self::DELTA_SNAPSHOT != 0
50 }
51
52 #[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 #[must_use]
67 pub const fn full_snapshot() -> Self {
68 Self(Self::FULL_SNAPSHOT)
69 }
70
71 #[must_use]
73 pub const fn delta_snapshot() -> Self {
74 Self(Self::DELTA_SNAPSHOT)
75 }
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub struct PacketHeader {
87 pub version: u16,
89 pub flags: PacketFlags,
91 pub schema_hash: u64,
93 pub tick: u32,
95 pub baseline_tick: u32,
97 pub payload_len: u32,
99}
100
101impl PacketHeader {
102 #[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 #[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 #[test]
140 fn magic_is_sdec_ascii() {
141 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 assert_eq!(HEADER_SIZE, 28);
156 }
157
158 #[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 assert!(!PacketFlags::from_raw(0b101).is_valid_v0());
206 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()); }
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; assert_eq!(flags, copied);
228 }
229
230 #[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; 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")); }
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}