1use crate::{Error, RecordKind, Result};
5use alloc::vec::Vec;
6
7const PACKET_MAGIC: [u8; 4] = *b"CULS";
8pub const PACKET_HEADER_LEN: usize = 51;
11const COMMON_HEADER_LEN: usize = 28;
12const RLC_SOURCE_HEADER_LEN: usize = 32;
13const RLC_REPAIR_HEADER_LEN: usize = 36;
14
15const fn packet_header_len(scheme: FecScheme, kind: FecSymbolKind) -> usize {
16 match (scheme, kind) {
17 (FecScheme::RaptorQ, _) => PACKET_HEADER_LEN,
18 (_, FecSymbolKind::Source) => RLC_SOURCE_HEADER_LEN,
19 (_, FecSymbolKind::Repair) => RLC_REPAIR_HEADER_LEN,
20 }
21}
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25#[repr(u8)]
26pub enum Lane {
27 Control = 0,
28 ReplayCritical = 1,
29 LargeObject = 2,
30 StructuredLog = 3,
31 Visualization = 4,
32}
33
34impl TryFrom<u8> for Lane {
35 type Error = Error;
36
37 fn try_from(value: u8) -> Result<Self> {
38 match value {
39 0 => Ok(Self::Control),
40 1 => Ok(Self::ReplayCritical),
41 2 => Ok(Self::LargeObject),
42 3 => Ok(Self::StructuredLog),
43 4 => Ok(Self::Visualization),
44 _ => Err(Error::UnknownLane(value)),
45 }
46 }
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50#[repr(u8)]
51pub enum FecScheme {
52 RlcGf2 = 0,
53 RlcGf256 = 1,
54 RaptorQ = 2,
55}
56
57impl TryFrom<u8> for FecScheme {
58 type Error = Error;
59
60 fn try_from(value: u8) -> Result<Self> {
61 match value {
62 0 => Ok(Self::RlcGf2),
63 1 => Ok(Self::RlcGf256),
64 2 => Ok(Self::RaptorQ),
65 _ => Err(Error::UnknownFecScheme(value)),
66 }
67 }
68}
69
70#[derive(Clone, Copy, Debug, PartialEq, Eq)]
71#[repr(u8)]
72pub enum FecSymbolKind {
73 Source = 0,
74 Repair = 1,
75}
76
77impl TryFrom<u8> for FecSymbolKind {
78 type Error = Error;
79
80 fn try_from(value: u8) -> Result<Self> {
81 match value {
82 0 => Ok(Self::Source),
83 1 => Ok(Self::Repair),
84 _ => Err(Error::UnknownSymbolKind(value)),
85 }
86 }
87}
88
89#[derive(Clone, Copy, Debug, PartialEq, Eq)]
91pub struct WireHeader {
92 pub lane: Lane,
93 pub record_kind: RecordKind,
94 pub fec_scheme: FecScheme,
95 pub symbol_kind: FecSymbolKind,
96 pub session_id: [u8; 16],
97 pub sender_id: u32,
98 pub object_id: u64,
100 pub fec_metadata: [u8; 12],
105 pub fragment_count: u32,
107}
108
109#[derive(Clone, Debug, PartialEq, Eq)]
112pub struct WirePacket {
113 pub header: WireHeader,
114 pub payload: Vec<u8>,
115}
116
117impl WirePacket {
118 pub fn encode(&self) -> Result<Vec<u8>> {
119 let mut bytes = alloc::vec![0; packet_header_len(self.header.fec_scheme, self.header.symbol_kind) + self.payload.len()];
120 let encoded = encode_packet_into(self.header, &self.payload, &mut bytes)?;
121 debug_assert_eq!(encoded, bytes.len());
122 Ok(bytes)
123 }
124
125 pub fn decode(bytes: &[u8]) -> Result<Self> {
126 let packet = WirePacketRef::decode(bytes)?;
127 Ok(Self {
128 header: packet.header,
129 payload: packet.payload.to_vec(),
130 })
131 }
132}
133
134#[derive(Clone, Copy, Debug, PartialEq, Eq)]
136pub struct WirePacketRef<'a> {
137 pub header: WireHeader,
138 pub payload: &'a [u8],
139}
140
141impl<'a> WirePacketRef<'a> {
142 pub fn decode(bytes: &'a [u8]) -> Result<Self> {
147 if bytes.len() < COMMON_HEADER_LEN {
148 return Err(Error::TruncatedPacket);
149 }
150 if bytes[..4] != PACKET_MAGIC {
151 return Err(Error::InvalidMagic);
152 }
153 let fec_scheme = FecScheme::try_from(bytes[6])?;
154 let symbol_kind = FecSymbolKind::try_from(bytes[7])?;
155 let header_len = packet_header_len(fec_scheme, symbol_kind);
156 if bytes.len() < header_len {
157 return Err(Error::TruncatedPacket);
158 }
159 let mut session_id = [0_u8; 16];
160 session_id.copy_from_slice(&bytes[8..24]);
161 let mut fec_metadata = [0_u8; 12];
162 let (object_id, fragment_count) = match fec_scheme {
163 FecScheme::RaptorQ => {
164 fec_metadata[..5].copy_from_slice(&bytes[36..41]);
165 fec_metadata[6..].copy_from_slice(&bytes[41..47]);
166 (
167 u64::from_be_bytes(bytes[28..36].try_into().unwrap()),
168 u32::from_be_bytes(bytes[47..51].try_into().unwrap()),
169 )
170 }
171 FecScheme::RlcGf2 | FecScheme::RlcGf256 => {
172 let metadata_len = header_len - COMMON_HEADER_LEN;
173 fec_metadata[..metadata_len].copy_from_slice(&bytes[COMMON_HEADER_LEN..header_len]);
174 (0, 0)
175 }
176 };
177
178 Ok(Self {
179 header: WireHeader {
180 lane: Lane::try_from(bytes[4])?,
181 record_kind: RecordKind::try_from(bytes[5])?,
182 fec_scheme,
183 symbol_kind,
184 session_id,
185 sender_id: u32::from_be_bytes(bytes[24..28].try_into().unwrap()),
186 object_id,
187 fec_metadata,
188 fragment_count,
189 },
190 payload: &bytes[header_len..],
191 })
192 }
193}
194
195pub fn encode_packet_into(header: WireHeader, payload: &[u8], output: &mut [u8]) -> Result<usize> {
197 if header.fec_scheme == FecScheme::RaptorQ && header.fec_metadata[5] != 0 {
198 return Err(Error::InvalidFecMetadata(
199 "RaptorQ OTI reserved byte is nonzero",
200 ));
201 }
202 let header_len = packet_header_len(header.fec_scheme, header.symbol_kind);
203 let needed = header_len
204 .checked_add(payload.len())
205 .ok_or(Error::InvalidConfig("packet length overflow"))?;
206 if output.len() < needed {
207 return Err(Error::BufferTooSmall {
208 needed,
209 available: output.len(),
210 });
211 }
212
213 let bytes = &mut output[..needed];
214 bytes.fill(0);
215 bytes[..4].copy_from_slice(&PACKET_MAGIC);
216 bytes[4] = header.lane as u8;
217 bytes[5] = header.record_kind as u8;
218 bytes[6] = header.fec_scheme as u8;
219 bytes[7] = header.symbol_kind as u8;
220 bytes[8..24].copy_from_slice(&header.session_id);
221 bytes[24..28].copy_from_slice(&header.sender_id.to_be_bytes());
222 match header.fec_scheme {
223 FecScheme::RaptorQ => {
224 bytes[28..36].copy_from_slice(&header.object_id.to_be_bytes());
225 bytes[36..41].copy_from_slice(&header.fec_metadata[..5]);
226 bytes[41..47].copy_from_slice(&header.fec_metadata[6..]);
227 bytes[47..51].copy_from_slice(&header.fragment_count.to_be_bytes());
228 }
229 FecScheme::RlcGf2 | FecScheme::RlcGf256 => {
230 bytes[COMMON_HEADER_LEN..header_len]
231 .copy_from_slice(&header.fec_metadata[..header_len - COMMON_HEADER_LEN]);
232 }
233 }
234 bytes[header_len..].copy_from_slice(payload);
235 Ok(needed)
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241 use alloc::vec;
242
243 fn fixture() -> WirePacket {
244 WirePacket {
245 header: WireHeader {
246 lane: Lane::LargeObject,
247 record_kind: RecordKind::KeyFrame,
248 fec_scheme: FecScheme::RaptorQ,
249 symbol_kind: FecSymbolKind::Source,
250 session_id: [0x11; 16],
251 sender_id: 0x2233_4455,
252 object_id: 0x0102_0304_0506_0708,
253 fec_metadata: [0, 0, 0, 0, 3, 0, 0, 8, 1, 0, 1, 1],
254 fragment_count: 1,
255 },
256 payload: vec![0, 0, 0, 0, 1, 2, 3, 0, 0, 0, 0, 0],
257 }
258 }
259
260 #[test]
261 fn fixed_header_has_a_stable_golden_prefix() {
262 let encoded = fixture().encode().unwrap();
263 assert_eq!(&encoded[..8], &[b'C', b'U', b'L', b'S', 2, 2, 2, 0]);
264 assert_eq!(encoded.len(), 51 + fixture().payload.len());
265 assert_eq!(&encoded[8..24], &[0x11; 16]);
266 assert_eq!(&encoded[24..28], &0x2233_4455_u32.to_be_bytes());
267 assert_eq!(&encoded[28..36], &0x0102_0304_0506_0708_u64.to_be_bytes());
268 assert_eq!(&encoded[36..47], &[0, 0, 0, 0, 3, 0, 8, 1, 0, 1, 1]);
269 assert_eq!(&encoded[47..51], &1_u32.to_be_bytes());
270 assert_eq!(&encoded[51..], fixture().payload);
271 assert_eq!(WirePacket::decode(&encoded).unwrap(), fixture());
272 }
273
274 #[test]
275 fn raptorq_oti_preserves_every_nonreserved_byte() {
276 for kind in [FecSymbolKind::Source, FecSymbolKind::Repair] {
277 let mut packet = fixture();
278 packet.header.symbol_kind = kind;
279 packet.header.fec_metadata = [1, 2, 3, 4, 5, 0, 6, 7, 8, 9, 10, 11];
280 packet.header.fragment_count = 0x1234_5678;
281 let encoded = packet.encode().unwrap();
282 assert_eq!(&encoded[36..47], &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
283 assert_eq!(&encoded[47..51], &[0x12, 0x34, 0x56, 0x78]);
284 assert_eq!(WirePacket::decode(&encoded).unwrap(), packet);
285 }
286 }
287
288 #[test]
289 fn raptorq_oti_rejects_nonzero_reserved_byte_before_encoding() {
290 for kind in [FecSymbolKind::Source, FecSymbolKind::Repair] {
291 let mut packet = fixture();
292 packet.header.symbol_kind = kind;
293 for reserved in 1..=u8::MAX {
294 packet.header.fec_metadata[5] = reserved;
295 let expected = Error::InvalidFecMetadata("RaptorQ OTI reserved byte is nonzero");
296 let mut storage = [0xaa; 128];
297 assert_eq!(
298 encode_packet_into(packet.header, &packet.payload, &mut storage),
299 Err(expected.clone())
300 );
301 assert_eq!(storage, [0xaa; 128]);
302 assert_eq!(packet.encode(), Err(expected));
303 }
304 }
305 }
306
307 fn rlc_fixture(scheme: FecScheme, kind: FecSymbolKind) -> WirePacket {
308 let mut packet = fixture();
309 packet.header.lane = Lane::ReplayCritical;
310 packet.header.record_kind = RecordKind::CopperList;
311 packet.header.fec_scheme = scheme;
312 packet.header.symbol_kind = kind;
313 packet.header.object_id = 0;
314 packet.header.fragment_count = 0;
315 packet.header.fec_metadata = [0; 12];
316 let id_len = match kind {
317 FecSymbolKind::Source => 4,
318 FecSymbolKind::Repair => 8,
319 };
320 packet.header.fec_metadata[..id_len].copy_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8][..id_len]);
321 packet
322 }
323
324 #[test]
325 fn rlc_headers_carry_only_the_active_fec_id() {
326 for scheme in [FecScheme::RlcGf2, FecScheme::RlcGf256] {
327 for (kind, header_len, id_len) in [
328 (FecSymbolKind::Source, 32, 4),
329 (FecSymbolKind::Repair, 36, 8),
330 ] {
331 let packet = rlc_fixture(scheme, kind);
332 let encoded = packet.encode().unwrap();
333 assert_eq!(encoded.len(), header_len + packet.payload.len());
334 assert_eq!(
335 &encoded[..8],
336 &[
337 b'C',
338 b'U',
339 b'L',
340 b'S',
341 1,
342 RecordKind::CopperList as u8,
343 scheme as u8,
344 kind as u8
345 ]
346 );
347 assert_eq!(&encoded[8..24], &[0x11; 16]);
348 assert_eq!(&encoded[24..28], &0x2233_4455_u32.to_be_bytes());
349 assert_eq!(
350 &encoded[28..28 + id_len],
351 &packet.header.fec_metadata[..id_len]
352 );
353 assert_eq!(header_len, 28 + id_len);
354 assert_eq!(&encoded[header_len..], packet.payload);
355 assert_eq!(WirePacket::decode(&encoded).unwrap(), packet);
356
357 let mut exact_storage = vec![0; encoded.len()];
358 assert_eq!(
359 encode_packet_into(packet.header, &packet.payload, &mut exact_storage).unwrap(),
360 encoded.len()
361 );
362 assert_eq!(exact_storage, encoded);
363 let available = encoded.len() - 1;
364 assert_eq!(
365 encode_packet_into(
366 packet.header,
367 &packet.payload,
368 &mut exact_storage[..available]
369 ),
370 Err(Error::BufferTooSmall {
371 needed: encoded.len(),
372 available
373 })
374 );
375 }
376 }
377 }
378
379 #[test]
380 fn all_header_layouts_reject_truncated_headers_and_invalid_tags() {
381 for scheme in [FecScheme::RlcGf2, FecScheme::RlcGf256, FecScheme::RaptorQ] {
382 for kind in [FecSymbolKind::Source, FecSymbolKind::Repair] {
383 let mut packet = if scheme == FecScheme::RaptorQ {
384 fixture()
385 } else {
386 rlc_fixture(scheme, kind)
387 };
388 packet.header.symbol_kind = kind;
389 for payload in [vec![], vec![0x5a; 64]] {
390 packet.payload = payload;
391 let encoded = packet.encode().unwrap();
392 assert_eq!(WirePacket::decode(&encoded).unwrap(), packet);
393 let header_len = packet_header_len(scheme, kind);
394 for end in 0..header_len {
395 assert_eq!(
396 WirePacketRef::decode(&encoded[..end]),
397 Err(Error::TruncatedPacket)
398 );
399 }
400 for (offset, expected) in [
401 (0, Error::InvalidMagic),
402 (4, Error::UnknownLane(255)),
403 (5, Error::UnknownRecordKind(255)),
404 (6, Error::UnknownFecScheme(255)),
405 (7, Error::UnknownSymbolKind(255)),
406 ] {
407 let mut invalid = encoded.clone();
408 invalid[offset] = 255;
409 assert_eq!(WirePacketRef::decode(&invalid), Err(expected));
410 }
411 }
412 }
413 }
414 }
415
416 #[test]
417 fn packet_extent_defines_payload_length_for_every_layout() {
418 for scheme in [FecScheme::RlcGf2, FecScheme::RlcGf256, FecScheme::RaptorQ] {
419 for kind in [FecSymbolKind::Source, FecSymbolKind::Repair] {
420 let mut packet = if scheme == FecScheme::RaptorQ {
421 fixture()
422 } else {
423 rlc_fixture(scheme, kind)
424 };
425 packet.header.symbol_kind = kind;
426 let header_len = packet_header_len(scheme, kind);
427 for payload_len in [0, 1, crate::DEFAULT_MAX_SYMBOL_SIZE, u16::MAX as usize + 1] {
428 packet.payload = vec![0x5a; payload_len];
429 let needed = header_len + payload_len;
430 let mut storage = vec![0xaa; needed + 1];
431 assert_eq!(
432 encode_packet_into(packet.header, &packet.payload, &mut storage).unwrap(),
433 needed
434 );
435 assert_eq!(storage[needed], 0xaa);
436 let decoded = WirePacketRef::decode(&storage[..needed]).unwrap();
437 assert_eq!(decoded.header, packet.header);
438 assert_eq!(decoded.payload, packet.payload);
439 assert_eq!(decoded.payload.as_ptr(), storage[header_len..].as_ptr());
440 let extended = WirePacketRef::decode(&storage).unwrap();
442 assert_eq!(extended.payload.len(), payload_len + 1);
443 assert_eq!(extended.payload[payload_len], 0xaa);
444 for end in [header_len, header_len + payload_len / 2, needed] {
445 assert_eq!(
446 WirePacketRef::decode(&storage[..end]).unwrap().payload,
447 &packet.payload[..end - header_len]
448 );
449 }
450 assert_eq!(
451 encode_packet_into(
452 packet.header,
453 &packet.payload,
454 &mut storage[..needed - 1]
455 ),
456 Err(Error::BufferTooSmall {
457 needed,
458 available: needed - 1,
459 })
460 );
461 }
462 }
463 }
464 }
465
466 #[test]
467 fn caller_owned_encoding_matches_allocating_encoding() {
468 let packet = fixture();
469 let expected = packet.encode().unwrap();
470 let mut storage = [0xaa; 128];
471 let encoded = encode_packet_into(packet.header, &packet.payload, &mut storage).unwrap();
472
473 assert_eq!(&storage[..encoded], expected);
474 assert!(storage[encoded..].iter().all(|byte| *byte == 0xaa));
475 }
476}