Skip to main content

rmt_flute/
norm.rs

1//! NORM — NACK-Oriented Reliable Multicast common header + message types
2//! (RFC 5740 §4).
3//!
4//! NORM uses its **own** common message header (not the LCT header), but borrows
5//! the same HET/HEL header-extension convention ([`crate::ext`]) and the
6//! FEC-Payload-ID concept. This module models the 8-byte common header, the
7//! `type` registry, the shared sender word, and the fixed-header portions of
8//! every defined message type. Variable trailing regions whose length the spec
9//! infers from the datagram length (FEC Payload IDs, node lists, NACK content,
10//! payloads) are exposed as opaque byte slices for the caller to interpret with
11//! knowledge of the FEC scheme.
12//!
13//! ⚠ FEC Payload ID layouts are FEC-scheme dependent (RFC 5740 §4.2.1 only
14//! reproduces one example, `fec_id` = 129); they are opaque here. NORM_REPORT
15//! has no defined wire format (RFC 5740 §4.4.1) — exposed as opaque content.
16
17use alloc::vec::Vec;
18
19use crate::error::{Error, Result};
20use crate::ext::{self, HeaderExtension, WORD};
21
22/// NORM protocol version (RFC 5740 = 1).
23pub const NORM_VERSION: u8 = 1;
24/// Size of the NORM common message header in bytes.
25pub const COMMON_HEADER_LEN: usize = 8;
26/// Size of the shared sender word (instance_id/grtt/backoff/gsize) in bytes.
27pub const SENDER_WORD_LEN: usize = 4;
28
29/// Reserved NormNodeId: invalid / none.
30pub const NORM_NODE_NONE: u32 = 0x0000_0000;
31/// Reserved NormNodeId: wildcard / any.
32pub const NORM_NODE_ANY: u32 = 0xFFFF_FFFF;
33
34/// HET for NORM EXT_AUTH (variable-length) — RFC 5740 §8.5.
35pub const HET_EXT_AUTH: u8 = 1;
36/// HET for NORM EXT_CC (variable-length, hel = 3) — RFC 5740 §4.2.3.
37pub const HET_EXT_CC: u8 = 3;
38/// HET for NORM EXT_FTI (variable-length) — RFC 5740 §4.2.1.
39pub const HET_EXT_FTI: u8 = 64;
40/// HET for NORM EXT_RATE (fixed-length) — RFC 5740 §4.2.3.
41pub const HET_EXT_RATE: u8 = 128;
42
43/// NORM message `type` (RFC 5740 §4.1).
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
45#[cfg_attr(feature = "serde", derive(serde::Serialize))]
46#[non_exhaustive]
47pub enum NormMessageType {
48    /// NORM_INFO (1).
49    Info,
50    /// NORM_DATA (2).
51    Data,
52    /// NORM_CMD (3).
53    Cmd,
54    /// NORM_NACK (4).
55    Nack,
56    /// NORM_ACK (5).
57    Ack,
58    /// NORM_REPORT (6).
59    Report,
60    /// Any other (unassigned) 4-bit type value.
61    Other(u8),
62}
63
64impl NormMessageType {
65    /// Decode a 4-bit type value.
66    pub fn from_u8(v: u8) -> Self {
67        match v {
68            1 => NormMessageType::Info,
69            2 => NormMessageType::Data,
70            3 => NormMessageType::Cmd,
71            4 => NormMessageType::Nack,
72            5 => NormMessageType::Ack,
73            6 => NormMessageType::Report,
74            other => NormMessageType::Other(other),
75        }
76    }
77
78    /// The 4-bit wire value.
79    pub fn to_u8(self) -> u8 {
80        match self {
81            NormMessageType::Info => 1,
82            NormMessageType::Data => 2,
83            NormMessageType::Cmd => 3,
84            NormMessageType::Nack => 4,
85            NormMessageType::Ack => 5,
86            NormMessageType::Report => 6,
87            NormMessageType::Other(v) => v,
88        }
89    }
90
91    /// Spec label.
92    pub fn name(&self) -> &'static str {
93        match self {
94            NormMessageType::Info => "NORM_INFO",
95            NormMessageType::Data => "NORM_DATA",
96            NormMessageType::Cmd => "NORM_CMD",
97            NormMessageType::Nack => "NORM_NACK",
98            NormMessageType::Ack => "NORM_ACK",
99            NormMessageType::Report => "NORM_REPORT",
100            NormMessageType::Other(_) => "reserved",
101        }
102    }
103}
104
105broadcast_common::impl_spec_display!(NormMessageType, Other);
106
107/// The NORM common message header (RFC 5740 §4.1, Figure 1): 8 bytes carrying
108/// `version | type | hdr_len | sequence | source_id`.
109///
110/// `hdr_len` is **not** stored: it is recomputed on serialize from the typed
111/// message body so the round-trip is field-driven.
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113#[cfg_attr(feature = "serde", derive(serde::Serialize))]
114pub struct NormCommonHeader {
115    /// Protocol version (`version`, 4 bits). RFC 5740 = [`NORM_VERSION`] (1).
116    pub version: u8,
117    /// Message type (`type`, 4 bits).
118    pub message_type: NormMessageType,
119    /// Sequence number (16 bits).
120    pub sequence: u16,
121    /// Originator's NormNodeId (`source_id`, 32 bits).
122    pub source_id: u32,
123}
124
125impl NormCommonHeader {
126    /// Parse the 8-byte common header. Returns the header and the `hdr_len`
127    /// value (in 32-bit words) read from the wire.
128    pub fn parse(data: &[u8]) -> Result<(Self, u8)> {
129        if data.len() < COMMON_HEADER_LEN {
130            return Err(Error::BufferTooShort {
131                need: COMMON_HEADER_LEN,
132                have: data.len(),
133                what: "NORM common header",
134            });
135        }
136        let version = data[0] >> 4;
137        let message_type = NormMessageType::from_u8(data[0] & 0x0F);
138        let hdr_len = data[1];
139        let sequence = u16::from_be_bytes([data[2], data[3]]);
140        let source_id = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
141        Ok((
142            NormCommonHeader {
143                version,
144                message_type,
145                sequence,
146                source_id,
147            },
148            hdr_len,
149        ))
150    }
151
152    /// Serialize the common header into `out`, writing the supplied `hdr_len`
153    /// (caller computes it from the message body). Returns bytes written.
154    pub fn serialize_into(&self, out: &mut [u8], hdr_len: u8) -> Result<usize> {
155        if out.len() < COMMON_HEADER_LEN {
156            return Err(Error::OutputBufferTooSmall {
157                need: COMMON_HEADER_LEN,
158                have: out.len(),
159            });
160        }
161        if self.version > 0x0F {
162            return Err(Error::FieldTooWide {
163                what: "version",
164                value: self.version as u64,
165                bits: 4,
166            });
167        }
168        let ty = self.message_type.to_u8();
169        if ty > 0x0F {
170            return Err(Error::FieldTooWide {
171                what: "type",
172                value: ty as u64,
173                bits: 4,
174            });
175        }
176        out[0] = (self.version << 4) | (ty & 0x0F);
177        out[1] = hdr_len;
178        out[2..4].copy_from_slice(&self.sequence.to_be_bytes());
179        out[4..8].copy_from_slice(&self.source_id.to_be_bytes());
180        Ok(COMMON_HEADER_LEN)
181    }
182}
183
184/// The shared sender word carried by NORM_DATA / NORM_INFO / NORM_CMD
185/// (RFC 5740 §4.2): `instance_id(16) | grtt(8) | backoff(4) | gsize(4)`.
186#[derive(Debug, Clone, Copy, PartialEq, Eq)]
187#[cfg_attr(feature = "serde", derive(serde::Serialize))]
188pub struct SenderWord {
189    /// Sender's current participation instance.
190    pub instance_id: u16,
191    /// Quantized group RTT estimate.
192    pub grtt: u8,
193    /// NACK backoff factor (4 bits).
194    pub backoff: u8,
195    /// Quantized group-size estimate (4 bits).
196    pub gsize: u8,
197}
198
199impl SenderWord {
200    /// Parse the 4-byte sender word.
201    pub fn parse(data: &[u8]) -> Result<Self> {
202        if data.len() < SENDER_WORD_LEN {
203            return Err(Error::BufferTooShort {
204                need: SENDER_WORD_LEN,
205                have: data.len(),
206                what: "NORM sender word",
207            });
208        }
209        Ok(SenderWord {
210            instance_id: u16::from_be_bytes([data[0], data[1]]),
211            grtt: data[2],
212            backoff: data[3] >> 4,
213            gsize: data[3] & 0x0F,
214        })
215    }
216
217    /// Serialize the 4-byte sender word into `out`.
218    pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
219        if out.len() < SENDER_WORD_LEN {
220            return Err(Error::OutputBufferTooSmall {
221                need: SENDER_WORD_LEN,
222                have: out.len(),
223            });
224        }
225        if self.backoff > 0x0F {
226            return Err(Error::FieldTooWide {
227                what: "backoff",
228                value: self.backoff as u64,
229                bits: 4,
230            });
231        }
232        if self.gsize > 0x0F {
233            return Err(Error::FieldTooWide {
234                what: "gsize",
235                value: self.gsize as u64,
236                bits: 4,
237            });
238        }
239        out[0..2].copy_from_slice(&self.instance_id.to_be_bytes());
240        out[2] = self.grtt;
241        out[3] = (self.backoff << 4) | (self.gsize & 0x0F);
242        Ok(SENDER_WORD_LEN)
243    }
244}
245
246/// NORM_INFO fixed header beyond the common header + sender word (RFC 5740
247/// §4.2.2, Figure 8): `flags | fec_id | object_transport_id`.
248///
249/// NORM_INFO is the **atomic** out-of-band context message for one object.
250/// Unlike NORM_DATA it carries **no** `fec_payload_id` field — the fixed part
251/// of the header is exactly 4 words (common 8 + sender 4 + flags-word 4 = 16
252/// bytes = hdr_len 4 when there are no extensions). The payload is the
253/// application-defined info content (≤ NormSegmentSize).
254#[derive(Debug, Clone, PartialEq, Eq)]
255#[cfg_attr(feature = "serde", derive(serde::Serialize))]
256pub struct NormInfo<'a> {
257    /// Common header (message_type = `NormMessageType::Info`).
258    pub common: NormCommonHeader,
259    /// Shared sender word.
260    pub sender: SenderWord,
261    /// Object flags (NORM_FLAG_*). Same set as NORM_DATA.
262    pub flags: u8,
263    /// FEC Encoding ID.
264    pub fec_id: u8,
265    /// NormTransportId of the object this INFO is associated with.
266    pub object_transport_id: u16,
267    /// Header-extension chain (e.g. EXT_FTI per §4.2.1 Figure 6).
268    pub extensions: Vec<HeaderExtension<'a>>,
269    /// Application-defined content (≤ NormSegmentSize). NOT part of `hdr_len`.
270    pub payload: &'a [u8],
271}
272
273/// Fixed header size of NORM_INFO before header extensions:
274/// common(8) + sender(4) + flags-word(4) = 16 bytes.
275pub const NORM_INFO_FIXED_LEN: usize = COMMON_HEADER_LEN + SENDER_WORD_LEN + WORD;
276
277impl<'a> NormInfo<'a> {
278    /// Total header bytes (common + sender + flags-word + extensions).
279    fn header_bytes(&self) -> usize {
280        NORM_INFO_FIXED_LEN + ext::chain_len(&self.extensions)
281    }
282
283    /// Total serialized length in bytes.
284    pub fn serialized_len(&self) -> usize {
285        self.header_bytes() + self.payload.len()
286    }
287
288    /// Parse a NORM_INFO message.
289    pub fn parse(data: &'a [u8]) -> Result<Self> {
290        let (common, hdr_len) = NormCommonHeader::parse(data)?;
291        let sender = SenderWord::parse(&data[COMMON_HEADER_LEN..])?;
292        let off = COMMON_HEADER_LEN + SENDER_WORD_LEN;
293        if data.len() < off + WORD {
294            return Err(Error::BufferTooShort {
295                need: off + WORD,
296                have: data.len(),
297                what: "NORM_INFO flags word",
298            });
299        }
300        let flags = data[off];
301        let fec_id = data[off + 1];
302        let object_transport_id = u16::from_be_bytes([data[off + 2], data[off + 3]]);
303
304        // hdr_len bounds the header (incl. extensions); payload starts after.
305        let header_end = hdr_len as usize * WORD;
306        if header_end < NORM_INFO_FIXED_LEN {
307            return Err(Error::InconsistentLength {
308                length: hdr_len,
309                reason: "hdr_len smaller than the NORM_INFO fixed header",
310            });
311        }
312        if data.len() < header_end {
313            return Err(Error::BufferTooShort {
314                need: header_end,
315                have: data.len(),
316                what: "NORM_INFO header (per hdr_len)",
317            });
318        }
319        let extensions = ext::parse_chain(&data[NORM_INFO_FIXED_LEN..header_end])?;
320        let payload = &data[header_end..];
321
322        Ok(NormInfo {
323            common,
324            sender,
325            flags,
326            fec_id,
327            object_transport_id,
328            extensions,
329            payload,
330        })
331    }
332
333    /// Serialize into `out`, recomputing `hdr_len`. Returns bytes written.
334    pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
335        let total = self.serialized_len();
336        if out.len() < total {
337            return Err(Error::OutputBufferTooSmall {
338                need: total,
339                have: out.len(),
340            });
341        }
342        let header_bytes = self.header_bytes();
343        if !header_bytes.is_multiple_of(WORD) {
344            return Err(Error::InvalidField {
345                what: "hdr_len",
346                reason: "NORM_INFO header length is not a multiple of 4 bytes",
347            });
348        }
349        let words = header_bytes / WORD;
350        if words > u8::MAX as usize {
351            return Err(Error::FieldTooWide {
352                what: "hdr_len",
353                value: words as u64,
354                bits: 8,
355            });
356        }
357        let mut off = self.common.serialize_into(out, words as u8)?;
358        off += self.sender.serialize_into(&mut out[off..])?;
359        out[off] = self.flags;
360        out[off + 1] = self.fec_id;
361        out[off + 2..off + 4].copy_from_slice(&self.object_transport_id.to_be_bytes());
362        off += WORD;
363        off += ext::serialize_chain(&self.extensions, &mut out[off..])?;
364        out[off..off + self.payload.len()].copy_from_slice(self.payload);
365        off += self.payload.len();
366        Ok(off)
367    }
368}
369
370// NORM_DATA `flags` bits (RFC 5740 §4.2.1).
371/// Message is a repair transmission.
372pub const NORM_FLAG_REPAIR: u8 = 0x01;
373/// Repair segment meeting a specific erasure.
374pub const NORM_FLAG_EXPLICIT: u8 = 0x02;
375/// NORM_INFO is available for this object.
376pub const NORM_FLAG_INFO: u8 = 0x04;
377/// No repair will be supplied (one-shot best-effort).
378pub const NORM_FLAG_UNRELIABLE: u8 = 0x08;
379/// Object is file-based.
380pub const NORM_FLAG_FILE: u8 = 0x10;
381/// Object is a NORM_OBJECT_STREAM (enables the payload_* fields).
382pub const NORM_FLAG_STREAM: u8 = 0x20;
383
384/// NORM_DATA fixed header beyond the common header + sender word (RFC 5740
385/// §4.2.1, Figure 4): `flags | fec_id | object_transport_id | fec_payload_id`.
386///
387/// The `fec_payload_id` is opaque (size per `fec_id`). STREAM-only
388/// `payload_len`/`payload_msg_start`/`payload_offset` fields and the payload
389/// data are part of `payload` (they do not contribute to `hdr_len`); the caller
390/// interprets them when `NORM_FLAG_STREAM` is set.
391#[derive(Debug, Clone, PartialEq, Eq)]
392#[cfg_attr(feature = "serde", derive(serde::Serialize))]
393pub struct NormData<'a> {
394    /// Common header.
395    pub common: NormCommonHeader,
396    /// Shared sender word.
397    pub sender: SenderWord,
398    /// Object flags (NORM_FLAG_*).
399    pub flags: u8,
400    /// FEC Encoding ID (implies `fec_payload_id` size/format).
401    pub fec_id: u8,
402    /// Monotonic NormTransportId of the object.
403    pub object_transport_id: u16,
404    /// FEC coding-block + symbol identifier (opaque, size per `fec_id`).
405    pub fec_payload_id: &'a [u8],
406    /// Header-extension chain.
407    pub extensions: Vec<HeaderExtension<'a>>,
408    /// Source/parity content (and, for STREAM, the leading payload_* fields).
409    pub payload: &'a [u8],
410}
411
412impl<'a> NormData<'a> {
413    /// `hdr_len` (32-bit words) = common(2) + sender(1) + the 4-byte
414    /// flags/fec_id/object_transport_id word + fec_payload_id + extensions.
415    fn header_bytes(&self) -> usize {
416        COMMON_HEADER_LEN
417            + SENDER_WORD_LEN
418            + WORD
419            + self.fec_payload_id.len()
420            + ext::chain_len(&self.extensions)
421    }
422
423    /// Total serialized length in bytes.
424    pub fn serialized_len(&self) -> usize {
425        self.header_bytes() + self.payload.len()
426    }
427
428    /// Parse a NORM_DATA. `fec_payload_id_len` is the FEC-scheme-defined size of
429    /// the FEC Payload ID in bytes.
430    pub fn parse(data: &'a [u8], fec_payload_id_len: usize) -> Result<Self> {
431        let (common, hdr_len) = NormCommonHeader::parse(data)?;
432        let sender = SenderWord::parse(&data[COMMON_HEADER_LEN..])?;
433        let mut off = COMMON_HEADER_LEN + SENDER_WORD_LEN;
434        if data.len() < off + WORD {
435            return Err(Error::BufferTooShort {
436                need: off + WORD,
437                have: data.len(),
438                what: "NORM_DATA flags word",
439            });
440        }
441        let flags = data[off];
442        let fec_id = data[off + 1];
443        let object_transport_id = u16::from_be_bytes([data[off + 2], data[off + 3]]);
444        off += WORD;
445
446        if data.len() < off + fec_payload_id_len {
447            return Err(Error::BufferTooShort {
448                need: off + fec_payload_id_len,
449                have: data.len(),
450                what: "NORM_DATA fec_payload_id",
451            });
452        }
453        let fec_payload_id = &data[off..off + fec_payload_id_len];
454        off += fec_payload_id_len;
455
456        // hdr_len bounds the header (incl. extensions). Everything from off..
457        // header_end is the extension chain; the rest is payload.
458        let header_end = hdr_len as usize * WORD;
459        if header_end < off {
460            return Err(Error::InconsistentLength {
461                length: hdr_len,
462                reason: "hdr_len smaller than the fixed NORM_DATA header + fec_payload_id",
463            });
464        }
465        if data.len() < header_end {
466            return Err(Error::BufferTooShort {
467                need: header_end,
468                have: data.len(),
469                what: "NORM_DATA header (per hdr_len)",
470            });
471        }
472        let extensions = ext::parse_chain(&data[off..header_end])?;
473        let payload = &data[header_end..];
474
475        Ok(NormData {
476            common,
477            sender,
478            flags,
479            fec_id,
480            object_transport_id,
481            fec_payload_id,
482            extensions,
483            payload,
484        })
485    }
486
487    /// Serialize into `out`, recomputing `hdr_len`. Returns bytes written.
488    pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
489        let total = self.serialized_len();
490        if out.len() < total {
491            return Err(Error::OutputBufferTooSmall {
492                need: total,
493                have: out.len(),
494            });
495        }
496        let header_bytes = self.header_bytes();
497        if !header_bytes.is_multiple_of(WORD) {
498            return Err(Error::InvalidField {
499                what: "hdr_len",
500                reason: "NORM_DATA header length is not a multiple of 4 bytes",
501            });
502        }
503        let words = header_bytes / WORD;
504        if words > u8::MAX as usize {
505            return Err(Error::FieldTooWide {
506                what: "hdr_len",
507                value: words as u64,
508                bits: 8,
509            });
510        }
511        let mut off = self.common.serialize_into(out, words as u8)?;
512        off += self.sender.serialize_into(&mut out[off..])?;
513        out[off] = self.flags;
514        out[off + 1] = self.fec_id;
515        out[off + 2..off + 4].copy_from_slice(&self.object_transport_id.to_be_bytes());
516        off += WORD;
517        out[off..off + self.fec_payload_id.len()].copy_from_slice(self.fec_payload_id);
518        off += self.fec_payload_id.len();
519        off += ext::serialize_chain(&self.extensions, &mut out[off..])?;
520        out[off..off + self.payload.len()].copy_from_slice(self.payload);
521        off += self.payload.len();
522        Ok(off)
523    }
524}
525
526/// NORM_CMD sub-type (RFC 5740 §4.2.3).
527#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
528#[cfg_attr(feature = "serde", derive(serde::Serialize))]
529#[non_exhaustive]
530pub enum NormCmdType {
531    /// NORM_CMD(FLUSH) (1).
532    Flush,
533    /// NORM_CMD(EOT) (2).
534    Eot,
535    /// NORM_CMD(SQUELCH) (3).
536    Squelch,
537    /// NORM_CMD(CC) (4).
538    Cc,
539    /// NORM_CMD(REPAIR_ADV) (5).
540    RepairAdv,
541    /// NORM_CMD(ACK_REQ) (6).
542    AckReq,
543    /// NORM_CMD(APPLICATION) (7).
544    Application,
545    /// Any other sub-type value.
546    Other(u8),
547}
548
549impl NormCmdType {
550    /// Decode a sub-type byte.
551    pub fn from_u8(v: u8) -> Self {
552        match v {
553            1 => NormCmdType::Flush,
554            2 => NormCmdType::Eot,
555            3 => NormCmdType::Squelch,
556            4 => NormCmdType::Cc,
557            5 => NormCmdType::RepairAdv,
558            6 => NormCmdType::AckReq,
559            7 => NormCmdType::Application,
560            other => NormCmdType::Other(other),
561        }
562    }
563
564    /// The sub-type wire byte.
565    pub fn to_u8(self) -> u8 {
566        match self {
567            NormCmdType::Flush => 1,
568            NormCmdType::Eot => 2,
569            NormCmdType::Squelch => 3,
570            NormCmdType::Cc => 4,
571            NormCmdType::RepairAdv => 5,
572            NormCmdType::AckReq => 6,
573            NormCmdType::Application => 7,
574            NormCmdType::Other(v) => v,
575        }
576    }
577
578    /// Spec label.
579    pub fn name(&self) -> &'static str {
580        match self {
581            NormCmdType::Flush => "NORM_CMD(FLUSH)",
582            NormCmdType::Eot => "NORM_CMD(EOT)",
583            NormCmdType::Squelch => "NORM_CMD(SQUELCH)",
584            NormCmdType::Cc => "NORM_CMD(CC)",
585            NormCmdType::RepairAdv => "NORM_CMD(REPAIR_ADV)",
586            NormCmdType::AckReq => "NORM_CMD(ACK_REQ)",
587            NormCmdType::Application => "NORM_CMD(APPLICATION)",
588            NormCmdType::Other(_) => "reserved",
589        }
590    }
591}
592
593broadcast_common::impl_spec_display!(NormCmdType, Other);
594
595/// NORM ack_type (RFC 5740 §4.2.3, shared by NORM_CMD(ACK_REQ) and NORM_ACK).
596#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
597#[cfg_attr(feature = "serde", derive(serde::Serialize))]
598#[non_exhaustive]
599pub enum NormAckType {
600    /// NORM_ACK(CC) (1).
601    Cc,
602    /// NORM_ACK(FLUSH) (2).
603    Flush,
604    /// Reserved for future NORM use (3..=15).
605    Reserved(u8),
606    /// Application discretion (16..=255).
607    Application(u8),
608}
609
610impl NormAckType {
611    /// Decode an ack_type byte.
612    pub fn from_u8(v: u8) -> Self {
613        match v {
614            1 => NormAckType::Cc,
615            2 => NormAckType::Flush,
616            3..=15 => NormAckType::Reserved(v),
617            _ => NormAckType::Application(v),
618        }
619    }
620
621    /// The wire byte.
622    pub fn to_u8(self) -> u8 {
623        match self {
624            NormAckType::Cc => 1,
625            NormAckType::Flush => 2,
626            NormAckType::Reserved(v) | NormAckType::Application(v) => v,
627        }
628    }
629
630    /// Spec label.
631    pub fn name(&self) -> &'static str {
632        match self {
633            NormAckType::Cc => "NORM_ACK(CC)",
634            NormAckType::Flush => "NORM_ACK(FLUSH)",
635            NormAckType::Reserved(_) => "reserved",
636            NormAckType::Application(_) => "application",
637        }
638    }
639}
640
641broadcast_common::impl_spec_display!(NormAckType, Reserved, Application);
642
643/// A NORM_CMD message (RFC 5740 §4.2.3): common header + sender word + an 8-bit
644/// `sub-type` selecting the body, then the sub-type-specific content (kept
645/// opaque, plus the extension chain).
646///
647/// The fixed per-sub-type layouts (FLUSH/EOT/SQUELCH/CC/REPAIR_ADV/ACK_REQ/
648/// APPLICATION) live in `content`; the `sub_type` discriminant tells the caller
649/// how to interpret it. This keeps the variable, length-inferred regions
650/// (node lists, fec_payload_id, app content) opaque as the spec requires.
651#[derive(Debug, Clone, PartialEq, Eq)]
652#[cfg_attr(feature = "serde", derive(serde::Serialize))]
653pub struct NormCmd<'a> {
654    /// Common header.
655    pub common: NormCommonHeader,
656    /// Shared sender word.
657    pub sender: SenderWord,
658    /// The command sub-type.
659    pub sub_type: NormCmdType,
660    /// The 3 bytes that share the sub-type's first word (sub-type-specific:
661    /// e.g. fec_id+object_transport_id for FLUSH, reserved for EOT/APPLICATION,
662    /// reserved+cc_sequence for CC).
663    pub head: [u8; 3],
664    /// Header-extension chain (e.g. EXT_RATE in CC, EXT_CC in REPAIR_ADV).
665    pub extensions: Vec<HeaderExtension<'a>>,
666    /// Remaining sub-type-specific content (fec_payload_id, node lists, etc.),
667    /// opaque to this layer.
668    pub content: &'a [u8],
669}
670
671impl<'a> NormCmd<'a> {
672    fn header_bytes(&self) -> usize {
673        // common + sender + the sub-type word + extensions.
674        COMMON_HEADER_LEN + SENDER_WORD_LEN + WORD + ext::chain_len(&self.extensions)
675    }
676
677    /// Total serialized length in bytes.
678    pub fn serialized_len(&self) -> usize {
679        self.header_bytes() + self.content.len()
680    }
681
682    /// Parse a NORM_CMD.
683    pub fn parse(data: &'a [u8]) -> Result<Self> {
684        let (common, hdr_len) = NormCommonHeader::parse(data)?;
685        let sender = SenderWord::parse(&data[COMMON_HEADER_LEN..])?;
686        let mut off = COMMON_HEADER_LEN + SENDER_WORD_LEN;
687        if data.len() < off + WORD {
688            return Err(Error::BufferTooShort {
689                need: off + WORD,
690                have: data.len(),
691                what: "NORM_CMD sub-type word",
692            });
693        }
694        let sub_type = NormCmdType::from_u8(data[off]);
695        let head = [data[off + 1], data[off + 2], data[off + 3]];
696        off += WORD;
697
698        let header_end = hdr_len as usize * WORD;
699        if header_end < off {
700            return Err(Error::InconsistentLength {
701                length: hdr_len,
702                reason: "hdr_len smaller than the NORM_CMD fixed header",
703            });
704        }
705        if data.len() < header_end {
706            return Err(Error::BufferTooShort {
707                need: header_end,
708                have: data.len(),
709                what: "NORM_CMD header (per hdr_len)",
710            });
711        }
712        let extensions = ext::parse_chain(&data[off..header_end])?;
713        let content = &data[header_end..];
714        Ok(NormCmd {
715            common,
716            sender,
717            sub_type,
718            head,
719            extensions,
720            content,
721        })
722    }
723
724    /// Serialize into `out`, recomputing `hdr_len`. Returns bytes written.
725    pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
726        let total = self.serialized_len();
727        if out.len() < total {
728            return Err(Error::OutputBufferTooSmall {
729                need: total,
730                have: out.len(),
731            });
732        }
733        let words = self.header_bytes() / WORD;
734        if words > u8::MAX as usize {
735            return Err(Error::FieldTooWide {
736                what: "hdr_len",
737                value: words as u64,
738                bits: 8,
739            });
740        }
741        let mut off = self.common.serialize_into(out, words as u8)?;
742        off += self.sender.serialize_into(&mut out[off..])?;
743        out[off] = self.sub_type.to_u8();
744        out[off + 1..off + 4].copy_from_slice(&self.head);
745        off += WORD;
746        off += ext::serialize_chain(&self.extensions, &mut out[off..])?;
747        out[off..off + self.content.len()].copy_from_slice(self.content);
748        off += self.content.len();
749        Ok(off)
750    }
751}
752
753/// A NORM feedback message — NORM_NACK (type 4) or NORM_ACK (type 5)
754/// (RFC 5740 §4.3): common header, `server_id`, `instance_id`, a 16-bit field
755/// that is `reserved` for NACK / `ack_type|ack_id` for ACK, then
756/// `grtt_response_sec`/`grtt_response_usec`, extensions, and opaque payload.
757#[derive(Debug, Clone, PartialEq, Eq)]
758#[cfg_attr(feature = "serde", derive(serde::Serialize))]
759pub struct NormFeedback<'a> {
760    /// Common header (type = NORM_NACK or NORM_ACK).
761    pub common: NormCommonHeader,
762    /// Destination sender NormNodeId.
763    pub server_id: u32,
764    /// Sender's current instance_id.
765    pub instance_id: u16,
766    /// The 16-bit word after instance_id: reserved (NACK) or ack_type|ack_id
767    /// (ACK). Stored as raw bytes; interpret per `common.message_type`.
768    pub ack_or_reserved: u16,
769    /// Adjusted NORM_CMD(CC) send_time seconds (0 = none yet).
770    pub grtt_response_sec: u32,
771    /// Adjusted send_time microseconds.
772    pub grtt_response_usec: u32,
773    /// Header-extension chain (e.g. EXT_CC).
774    pub extensions: Vec<HeaderExtension<'a>>,
775    /// nack_payload (NACK) or ack_payload (ACK), opaque here.
776    pub payload: &'a [u8],
777}
778
779/// Fixed-header byte size of a NORM feedback message before extensions:
780/// common(8) + server_id(4) + instance_id+ack/reserved(4) + 2×grtt(8) = 24.
781pub const FEEDBACK_FIXED_LEN: usize = COMMON_HEADER_LEN + 4 + 4 + 8;
782
783impl<'a> NormFeedback<'a> {
784    /// The 8-bit `ack_type` (NORM_ACK): the high byte of `ack_or_reserved`.
785    pub fn ack_type(&self) -> NormAckType {
786        NormAckType::from_u8((self.ack_or_reserved >> 8) as u8)
787    }
788    /// The 8-bit `ack_id` (NORM_ACK): the low byte of `ack_or_reserved`.
789    pub fn ack_id(&self) -> u8 {
790        self.ack_or_reserved as u8
791    }
792
793    fn header_bytes(&self) -> usize {
794        FEEDBACK_FIXED_LEN + ext::chain_len(&self.extensions)
795    }
796
797    /// Total serialized length in bytes.
798    pub fn serialized_len(&self) -> usize {
799        self.header_bytes() + self.payload.len()
800    }
801
802    /// Parse a NORM_NACK / NORM_ACK message.
803    pub fn parse(data: &'a [u8]) -> Result<Self> {
804        let (common, hdr_len) = NormCommonHeader::parse(data)?;
805        if data.len() < FEEDBACK_FIXED_LEN {
806            return Err(Error::BufferTooShort {
807                need: FEEDBACK_FIXED_LEN,
808                have: data.len(),
809                what: "NORM feedback fixed header",
810            });
811        }
812        let server_id = u32::from_be_bytes([data[8], data[9], data[10], data[11]]);
813        let instance_id = u16::from_be_bytes([data[12], data[13]]);
814        let ack_or_reserved = u16::from_be_bytes([data[14], data[15]]);
815        let grtt_response_sec = u32::from_be_bytes([data[16], data[17], data[18], data[19]]);
816        let grtt_response_usec = u32::from_be_bytes([data[20], data[21], data[22], data[23]]);
817
818        let header_end = hdr_len as usize * WORD;
819        if header_end < FEEDBACK_FIXED_LEN {
820            return Err(Error::InconsistentLength {
821                length: hdr_len,
822                reason: "hdr_len smaller than the NORM feedback fixed header",
823            });
824        }
825        if data.len() < header_end {
826            return Err(Error::BufferTooShort {
827                need: header_end,
828                have: data.len(),
829                what: "NORM feedback header (per hdr_len)",
830            });
831        }
832        let extensions = ext::parse_chain(&data[FEEDBACK_FIXED_LEN..header_end])?;
833        let payload = &data[header_end..];
834        Ok(NormFeedback {
835            common,
836            server_id,
837            instance_id,
838            ack_or_reserved,
839            grtt_response_sec,
840            grtt_response_usec,
841            extensions,
842            payload,
843        })
844    }
845
846    /// Serialize into `out`, recomputing `hdr_len`. Returns bytes written.
847    pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
848        let total = self.serialized_len();
849        if out.len() < total {
850            return Err(Error::OutputBufferTooSmall {
851                need: total,
852                have: out.len(),
853            });
854        }
855        let words = self.header_bytes() / WORD;
856        if words > u8::MAX as usize {
857            return Err(Error::FieldTooWide {
858                what: "hdr_len",
859                value: words as u64,
860                bits: 8,
861            });
862        }
863        let mut off = self.common.serialize_into(out, words as u8)?;
864        out[off..off + 4].copy_from_slice(&self.server_id.to_be_bytes());
865        out[off + 4..off + 6].copy_from_slice(&self.instance_id.to_be_bytes());
866        out[off + 6..off + 8].copy_from_slice(&self.ack_or_reserved.to_be_bytes());
867        out[off + 8..off + 12].copy_from_slice(&self.grtt_response_sec.to_be_bytes());
868        out[off + 12..off + 16].copy_from_slice(&self.grtt_response_usec.to_be_bytes());
869        off += 16;
870        off += ext::serialize_chain(&self.extensions, &mut out[off..])?;
871        out[off..off + self.payload.len()].copy_from_slice(self.payload);
872        off += self.payload.len();
873        Ok(off)
874    }
875}
876
877#[cfg(test)]
878mod tests {
879    use super::*;
880    use alloc::string::ToString;
881    use alloc::vec;
882
883    fn common(ty: NormMessageType) -> NormCommonHeader {
884        NormCommonHeader {
885            version: NORM_VERSION,
886            message_type: ty,
887            sequence: 0x1234,
888            source_id: 0xCAFEBABE,
889        }
890    }
891
892    fn sender() -> SenderWord {
893        SenderWord {
894            instance_id: 0x00FF,
895            grtt: 0x40,
896            backoff: 0x0A,
897            gsize: 0x05,
898        }
899    }
900
901    #[test]
902    fn message_type_round_trip() {
903        for v in 0u8..=6 {
904            assert_eq!(NormMessageType::from_u8(v).to_u8(), v);
905        }
906        assert_eq!(NormMessageType::Data.to_string(), "NORM_DATA");
907        assert_eq!(NormMessageType::Other(9).to_string(), "reserved(0x09)");
908    }
909
910    #[test]
911    fn common_header_exact_bytes() {
912        let c = common(NormMessageType::Data);
913        let mut out = [0u8; COMMON_HEADER_LEN];
914        c.serialize_into(&mut out, 7).unwrap();
915        // version=1, type=2 -> 0x12; hdr_len=7; seq=0x1234; source=0xCAFEBABE.
916        assert_eq!(out, [0x12, 0x07, 0x12, 0x34, 0xCA, 0xFE, 0xBA, 0xBE]);
917        let (re, hl) = NormCommonHeader::parse(&out).unwrap();
918        assert_eq!(re, c);
919        assert_eq!(hl, 7);
920    }
921
922    #[test]
923    fn sender_word_exact_bytes() {
924        let s = sender();
925        let mut out = [0u8; SENDER_WORD_LEN];
926        s.serialize_into(&mut out).unwrap();
927        // instance=0x00FF, grtt=0x40, backoff=0xA gsize=0x5 -> 0xA5.
928        assert_eq!(out, [0x00, 0xFF, 0x40, 0xA5]);
929        assert_eq!(SenderWord::parse(&out).unwrap(), s);
930    }
931
932    #[test]
933    fn norm_data_round_trip_with_fec_payload_id() {
934        // fec_id=129 → 8-byte fec_payload_id.
935        let fpid = [0x00u8, 0x00, 0x00, 0x01, 0x00, 0x05, 0x00, 0x02];
936        let payload = [0xDEu8, 0xAD, 0xBE, 0xEF];
937        let d = NormData {
938            common: common(NormMessageType::Data),
939            sender: sender(),
940            flags: NORM_FLAG_FILE,
941            fec_id: 129,
942            object_transport_id: 0x0007,
943            fec_payload_id: &fpid,
944            extensions: vec![],
945            payload: &payload,
946        };
947        // hdr_len = (8 + 4 + 4 + 8)/4 = 6 words.
948        let mut out = vec![0u8; d.serialized_len()];
949        let n = d.serialize_into(&mut out).unwrap();
950        assert_eq!(n, d.serialized_len());
951        assert_eq!(out[1], 6, "hdr_len");
952        let re = NormData::parse(&out, 8).unwrap();
953        assert_eq!(re, d);
954    }
955
956    #[test]
957    fn norm_data_with_ext_fti() {
958        // EXT_FTI HET=64, hel=4 → 16-byte extension.
959        let fti = [
960            0x40u8, 0x04, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, // het|hel|object_size
961            0x00, 0x01, 0x02, 0x00, 0x04, 0x00, 0x00, 0x10, // scheme-specific
962        ];
963        let (ext_fti, used) = HeaderExtension::parse(&fti).unwrap();
964        assert_eq!(used, 16);
965        let fpid = [0u8; 8];
966        let d = NormData {
967            common: common(NormMessageType::Data),
968            sender: sender(),
969            flags: 0,
970            fec_id: 129,
971            object_transport_id: 1,
972            fec_payload_id: &fpid,
973            extensions: vec![ext_fti],
974            payload: &[],
975        };
976        // hdr_len = (8 + 4 + 4 + 8 + 16)/4 = 10.
977        let mut out = vec![0u8; d.serialized_len()];
978        d.serialize_into(&mut out).unwrap();
979        assert_eq!(out[1], 10);
980        let re = NormData::parse(&out, 8).unwrap();
981        assert_eq!(re, d);
982        assert_eq!(re.extensions.len(), 1);
983        assert_eq!(re.extensions[0].het, HET_EXT_FTI);
984    }
985
986    #[test]
987    fn norm_cmd_eot_round_trip() {
988        let c = NormCmd {
989            common: common(NormMessageType::Cmd),
990            sender: sender(),
991            sub_type: NormCmdType::Eot,
992            head: [0, 0, 0], // reserved
993            extensions: vec![],
994            content: &[],
995        };
996        let mut out = vec![0u8; c.serialized_len()];
997        c.serialize_into(&mut out).unwrap();
998        // hdr_len = (8+4+4)/4 = 4.
999        assert_eq!(out[1], 4);
1000        // sub-type byte sits right after sender word (offset 12).
1001        assert_eq!(out[12], 2);
1002        let re = NormCmd::parse(&out).unwrap();
1003        assert_eq!(re, c);
1004        assert_eq!(re.sub_type, NormCmdType::Eot);
1005    }
1006
1007    #[test]
1008    fn norm_cmd_flush_with_content() {
1009        // FLUSH: head = fec_id | object_transport_id(16); content = fec_payload_id.
1010        let c = NormCmd {
1011            common: common(NormMessageType::Cmd),
1012            sender: sender(),
1013            sub_type: NormCmdType::Flush,
1014            head: [129, 0x00, 0x07], // fec_id=129, object_transport_id=7
1015            extensions: vec![],
1016            content: &[0, 0, 0, 1, 0, 5, 0, 2],
1017        };
1018        let mut out = vec![0u8; c.serialized_len()];
1019        c.serialize_into(&mut out).unwrap();
1020        let re = NormCmd::parse(&out).unwrap();
1021        assert_eq!(re, c);
1022    }
1023
1024    #[test]
1025    fn norm_nack_round_trip() {
1026        let f = NormFeedback {
1027            common: common(NormMessageType::Nack),
1028            server_id: 0x11223344,
1029            instance_id: 0x00FF,
1030            ack_or_reserved: 0, // reserved for NACK
1031            grtt_response_sec: 0x55667788,
1032            grtt_response_usec: 0x99AABBCC,
1033            extensions: vec![],
1034            payload: &[0x01, 0x02, 0x00, 0x04],
1035        };
1036        // hdr_len = 24/4 = 6.
1037        let mut out = vec![0u8; f.serialized_len()];
1038        f.serialize_into(&mut out).unwrap();
1039        assert_eq!(out[1], 6);
1040        let re = NormFeedback::parse(&out).unwrap();
1041        assert_eq!(re, f);
1042    }
1043
1044    #[test]
1045    fn norm_ack_ack_type_id() {
1046        let f = NormFeedback {
1047            common: common(NormMessageType::Ack),
1048            server_id: 1,
1049            instance_id: 2,
1050            ack_or_reserved: (2 << 8) | 0x07, // ack_type=FLUSH(2), ack_id=7
1051            grtt_response_sec: 0,
1052            grtt_response_usec: 0,
1053            extensions: vec![],
1054            payload: &[],
1055        };
1056        assert_eq!(f.ack_type(), NormAckType::Flush);
1057        assert_eq!(f.ack_id(), 7);
1058        let mut out = vec![0u8; f.serialized_len()];
1059        f.serialize_into(&mut out).unwrap();
1060        let re = NormFeedback::parse(&out).unwrap();
1061        assert_eq!(re, f);
1062    }
1063
1064    #[test]
1065    fn ack_type_ranges() {
1066        assert_eq!(NormAckType::from_u8(1), NormAckType::Cc);
1067        assert_eq!(NormAckType::from_u8(2), NormAckType::Flush);
1068        assert_eq!(NormAckType::from_u8(10), NormAckType::Reserved(10));
1069        assert_eq!(NormAckType::from_u8(200), NormAckType::Application(200));
1070        assert_eq!(NormAckType::Reserved(10).to_string(), "reserved(0x0A)");
1071    }
1072
1073    /// NORM_INFO round-trip: construct from typed fields, serialize, parse back,
1074    /// verify byte-exact. The NormInfo header has NO fec_payload_id —
1075    /// `hdr_len` base is 4 words (16 bytes).
1076    #[test]
1077    fn norm_info_round_trip() {
1078        let payload = [0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01, 0x02, 0x03u8];
1079        let info = NormInfo {
1080            common: common(NormMessageType::Info),
1081            sender: sender(),
1082            flags: NORM_FLAG_FILE,
1083            fec_id: 129,
1084            object_transport_id: 0x0042,
1085            extensions: vec![],
1086            payload: &payload,
1087        };
1088        // hdr_len = (8 + 4 + 4) / 4 = 4 words; no fec_payload_id.
1089        assert_eq!(info.header_bytes(), NORM_INFO_FIXED_LEN);
1090        let total = info.serialized_len();
1091        assert_eq!(total, NORM_INFO_FIXED_LEN + payload.len());
1092
1093        let mut out = vec![0u8; total];
1094        let n = info.serialize_into(&mut out).unwrap();
1095        assert_eq!(n, total);
1096
1097        // Byte-exact checks: version|type=0x11, hdr_len=4.
1098        assert_eq!(out[0], 0x11, "version=1 type=1");
1099        assert_eq!(out[1], 4, "hdr_len = 4 words");
1100        // flags-word: flags | fec_id | oti.
1101        assert_eq!(out[12], NORM_FLAG_FILE);
1102        assert_eq!(out[13], 129);
1103        assert_eq!(u16::from_be_bytes([out[14], out[15]]), 0x0042);
1104        // Payload follows immediately (no fec_payload_id).
1105        assert_eq!(&out[16..], &payload);
1106
1107        let re = NormInfo::parse(&out).unwrap();
1108        assert_eq!(re, info);
1109    }
1110
1111    /// NORM_INFO round-trip with a mutated field confirms the value is encoded
1112    /// and recovered (not silently dropped).
1113    #[test]
1114    fn norm_info_mutated_field_changes_wire() {
1115        let payload = [0u8; 4];
1116        let make = |oti: u16| {
1117            let i = NormInfo {
1118                common: common(NormMessageType::Info),
1119                sender: sender(),
1120                flags: 0,
1121                fec_id: 0,
1122                object_transport_id: oti,
1123                extensions: vec![],
1124                payload: &payload,
1125            };
1126            let mut out = vec![0u8; i.serialized_len()];
1127            i.serialize_into(&mut out).unwrap();
1128            out
1129        };
1130        let a = make(0x0001);
1131        let b = make(0x0002);
1132        assert_ne!(a, b);
1133        // OTI is at bytes 14..16 in the header.
1134        assert_eq!(u16::from_be_bytes([a[14], a[15]]), 0x0001);
1135        assert_eq!(u16::from_be_bytes([b[14], b[15]]), 0x0002);
1136    }
1137
1138    /// NORM_INFO with an EXT_FTI extension: hdr_len grows by the extension words.
1139    #[test]
1140    fn norm_info_with_ext_fti() {
1141        // EXT_FTI: het=64, hel=4 → 16 bytes.
1142        let fti = [
1143            0x40u8, 0x04, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x01, 0x02, 0x00, 0x04, 0x00,
1144            0x00, 0x10,
1145        ];
1146        let (ext_fti, _) = HeaderExtension::parse(&fti).unwrap();
1147        let payload = [0xAB, 0xCDu8];
1148        let info = NormInfo {
1149            common: common(NormMessageType::Info),
1150            sender: sender(),
1151            flags: NORM_FLAG_INFO,
1152            fec_id: 129,
1153            object_transport_id: 3,
1154            extensions: vec![ext_fti],
1155            payload: &payload,
1156        };
1157        // hdr_len = (8 + 4 + 4 + 16) / 4 = 8 words.
1158        assert_eq!(info.header_bytes(), 32);
1159        let mut out = vec![0u8; info.serialized_len()];
1160        info.serialize_into(&mut out).unwrap();
1161        assert_eq!(out[1], 8, "hdr_len with EXT_FTI");
1162        let re = NormInfo::parse(&out).unwrap();
1163        assert_eq!(re, info);
1164        assert_eq!(re.extensions.len(), 1);
1165        assert_eq!(re.extensions[0].het, HET_EXT_FTI);
1166    }
1167}