Skip to main content

dvb_t2mi/payload/
individual_addressing.rs

1//! T2-MI payload type 0x21: Individual addressing — §5.2.8.
2//!
3//! Carries per-transmitter addressing data: an outer loop of transmitter
4//! entries, each containing a `transmitter_identifier` and an inner loop of
5//! typed function entries (ACE-PAPR, MISO group, Frequency, etc.).
6//!
7//! # Wire layout (ETSI TS 102 773 §5.2.8.1, Fig 11)
8//!
9//! ```text
10//! rfu(8) · individual_addressing_length(8) · transmitter_loop()
11//!
12//! transmitter_loop() = for each transmitter:
13//!   transmitter_identifier(16) · function_loop_length(8) · function()…
14//!
15//! function() = function_tag(8) · function_length(8) · function_body(function_length bytes)
16//! ```
17//!
18//! `individual_addressing_length` counts the bytes of the transmitter loop
19//! (everything after the 2-byte header).  `function_loop_length` counts the
20//! bytes of the function loop within one transmitter entry (tag + length +
21//! body of every function).  `function_length` counts only the body bytes.
22//!
23//! # RFU policy (individual-addressing exception)
24//!
25//! This payload is the crate's documented exception: non-zero RFU bits are
26//! **preserved verbatim** rather than rejected, so gateway streams round-trip
27//! byte-exact.  This applies to the top-level `rfu` byte and to every RFU
28//! field inside the typed function bodies.
29
30use alloc::vec::Vec;
31use core::fmt;
32
33use num_enum::TryFromPrimitive;
34
35use broadcast_common::{Parse, Serialize};
36
37/// Function tags per §5.2.8.2 Tables 5 & 6.
38#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize))]
40#[repr(u8)]
41#[non_exhaustive]
42pub enum AddressingFunctionTag {
43    /// Transmitter time offset.
44    TimeOffset = 0x00,
45    /// Transmitter frequency offset.
46    FrequencyOffset = 0x01,
47    /// Transmitter power.
48    Power = 0x02,
49    /// Private data.
50    PrivateData = 0x03,
51    /// Cell ID.
52    CellId = 0x04,
53    /// Enable.
54    Enable = 0x05,
55    /// Bandwidth (not applicable for T2).
56    Bandwidth = 0x06,
57    /// ACE-PAPR reduction (T2-specific).
58    AcePapr = 0x10,
59    /// MISO group (T2-specific).
60    MisoGroup = 0x11,
61    /// TR-PAPR reduction (T2-specific).
62    TrPapr = 0x12,
63    /// L1-ACE-PAPR (T2-specific).
64    L1AcePapr = 0x13,
65    /// TX-SIG FEF sequence number (T2-specific).
66    TxSigFefSeqNum = 0x15,
67    /// TX-SIG auxiliary stream TX ID (T2-specific).
68    TxSigAuxStreamTxId = 0x16,
69    /// Frequency (T2-specific).
70    Frequency = 0x17,
71}
72
73impl From<AddressingFunctionTag> for u8 {
74    fn from(tag: AddressingFunctionTag) -> Self {
75        tag as u8
76    }
77}
78
79impl AddressingFunctionTag {
80    /// Human-readable spec label (ETSI TS 102 773 §5.2.8.2 Tables 5 & 6).
81    #[must_use]
82    pub fn name(&self) -> &'static str {
83        match self {
84            Self::TimeOffset => "Transmitter time offset",
85            Self::FrequencyOffset => "Transmitter frequency offset",
86            Self::Power => "Transmitter power",
87            Self::PrivateData => "Private data",
88            Self::CellId => "Cell ID",
89            Self::Enable => "Enable",
90            Self::Bandwidth => "Bandwidth",
91            Self::AcePapr => "ACE-PAPR reduction",
92            Self::MisoGroup => "MISO group",
93            Self::TrPapr => "TR-PAPR reduction",
94            Self::L1AcePapr => "L1-ACE-PAPR",
95            Self::TxSigFefSeqNum => "TX-SIG FEF sequence number",
96            Self::TxSigAuxStreamTxId => "TX-SIG auxiliary stream TX ID",
97            Self::Frequency => "Frequency",
98        }
99    }
100}
101broadcast_common::impl_spec_display!(AddressingFunctionTag);
102
103// ── Typed function bodies (§5.2.8.2 Tables 7–12b) ──────────────────────────
104
105/// ACE-PAPR function body per §5.2.8.2.1, Table 7.
106///
107/// Layout (16 bits = 2 bytes):
108/// - byte 0 `[7:3]`: ACE_gain (5 bits)
109/// - byte 0 `[2:0]`: ACE_maximal_extension (3 bits)
110/// - byte 1 `[7:1]`: ACE_clipping_threshold (7 bits)
111/// - byte 1 `[0]`: rfu (1 bit) — preserved per individual-addressing exception
112#[derive(Debug, Clone, PartialEq, Eq)]
113#[cfg_attr(feature = "serde", derive(serde::Serialize))]
114pub struct AcePaprBody {
115    /// ACE gain `[7:3]` (5 bits).
116    pub ace_gain: u8,
117    /// ACE maximal extension `[2:0]` (3 bits).
118    pub ace_maximal_extension: u8,
119    /// ACE clipping threshold `[7:1]` (7 bits).
120    pub ace_clipping_threshold: u8,
121    /// Reserved-for-future-use bit `[0]` — preserved verbatim.
122    pub rfu: bool,
123}
124
125/// MISO group function body per §5.2.8.2.2, Table 8.
126///
127/// Layout (8 bits = 1 byte):
128/// - `[7]`: MISO_group (1 bit)
129/// - `[6:0]`: rfu (7 bits) — preserved per individual-addressing exception
130#[derive(Debug, Clone, PartialEq, Eq)]
131#[cfg_attr(feature = "serde", derive(serde::Serialize))]
132pub struct MisoGroupBody {
133    /// MISO group flag `[7]` (1 bit).
134    pub miso_group: bool,
135    /// Reserved-for-future-use `[6:0]` (7 bits) — preserved verbatim.
136    pub rfu: u8,
137}
138
139/// TR-PAPR function body per §5.2.8.2.4, Table 9.
140///
141/// Layout (40 bits = 5 bytes):
142/// - byte 0 `[7:4]`: rfu1 (4 bits) — preserved
143/// - byte 0 `[3:0]` + byte 1: TR_clipping_threshold (12 bits)
144/// - byte 2 + byte 3 `[7:2]`: rfu2 (14 bits) — preserved
145/// - byte 3 `[1:0]` + byte 4: number_of_iterations (10 bits)
146#[derive(Debug, Clone, PartialEq, Eq)]
147#[cfg_attr(feature = "serde", derive(serde::Serialize))]
148pub struct TrPaprBody {
149    /// Reserved-for-future-use `[7:4]` (4 bits) — preserved verbatim.
150    pub rfu1: u8,
151    /// TR clipping threshold (12 bits).
152    pub tr_clipping_threshold: u16,
153    /// Reserved-for-future-use (14 bits) — preserved verbatim.
154    pub rfu2: u16,
155    /// Number of iterations (10 bits).
156    pub number_of_iterations: u16,
157}
158
159/// L1-ACE-PAPR function body per §5.2.8.2.5, Table 10.
160///
161/// Layout (32 bits = 4 bytes):
162/// - bytes 0-1: L1_ACE_max_correction (16 bits)
163/// - bytes 2-3: rfu (16 bits) — preserved per individual-addressing exception
164#[derive(Debug, Clone, PartialEq, Eq)]
165#[cfg_attr(feature = "serde", derive(serde::Serialize))]
166pub struct L1AcePaprBody {
167    /// L1 ACE max correction (16 bits).
168    pub l1_ace_max_correction: u16,
169    /// Reserved-for-future-use (16 bits) — preserved verbatim.
170    pub rfu: u16,
171}
172
173/// TX-SIG FEF sequence numbers function body per §5.2.8.2.5, Table 11.
174///
175/// Layout (40 bits = 5 bytes):
176/// - byte 0 `[7:3]`: rfu1 (5 bits) — preserved
177/// - byte 0 `[2:0]`: TX_SIG_FEF_SEQ_NUM_1 (3 bits)
178/// - byte 1 `[7:3]`: rfu2 (5 bits) — preserved
179/// - byte 1 `[2:0]`: TX_SIG_FEF_SEQ_NUM_2 (3 bits)
180/// - bytes 2-4: rfu3 (24 bits) — preserved
181#[derive(Debug, Clone, PartialEq, Eq)]
182#[cfg_attr(feature = "serde", derive(serde::Serialize))]
183pub struct TxSigFefSeqNumBody {
184    /// Reserved-for-future-use `[7:3]` (5 bits) — preserved verbatim.
185    pub rfu1: u8,
186    /// TX-SIG FEF sequence number 1 `[2:0]` (3 bits).
187    pub seq_num_1: u8,
188    /// Reserved-for-future-use `[7:3]` (5 bits) — preserved verbatim.
189    pub rfu2: u8,
190    /// TX-SIG FEF sequence number 2 `[2:0]` (3 bits).
191    pub seq_num_2: u8,
192    /// Reserved-for-future-use (24 bits) — preserved verbatim.
193    pub rfu3: u32,
194}
195
196/// TX-SIG auxiliary stream transmitter ID function body per §5.2.9, Table 12a.
197///
198/// Layout (32 bits = 4 bytes):
199/// - byte 0 + byte 1 `[7:4]`: TX_SIG_AUX_TX_ID (12 bits)
200/// - byte 1 `[3:0]` + bytes 2-3: rfu (20 bits) — preserved
201#[derive(Debug, Clone, PartialEq, Eq)]
202#[cfg_attr(feature = "serde", derive(serde::Serialize))]
203pub struct TxSigAuxStreamTxIdBody {
204    /// TX-SIG auxiliary stream transmitter ID (12 bits).
205    pub tx_sig_aux_tx_id: u16,
206    /// Reserved-for-future-use (20 bits) — preserved verbatim.
207    pub rfu: u32,
208}
209
210/// Frequency function body per §5.2.9, Table 12b.
211///
212/// Layout (40 bits = 5 bytes):
213/// - byte 0 `[7:5]`: rf_idx (3 bits)
214/// - byte 0 `[4:0]`: frequency `[31:27]`
215/// - bytes 1-3: frequency `[26:3]`
216/// - byte 4 `[7:5]`: frequency `[2:0]`
217/// - byte 4 `[4:0]`: rfu (5 bits) — preserved
218#[derive(Debug, Clone, PartialEq, Eq)]
219#[cfg_attr(feature = "serde", derive(serde::Serialize))]
220pub struct FrequencyBody {
221    /// RF index `[7:5]` (3 bits).
222    pub rf_idx: u8,
223    /// Frequency in Hz (32 bits).
224    pub frequency: u32,
225    /// Reserved-for-future-use `[4:0]` (5 bits) — preserved verbatim.
226    pub rfu: u8,
227}
228
229// ── Body size constants ────────────────────────────────────────────────────
230
231const ACE_PAPR_BODY_LEN: usize = 2;
232const MISO_GROUP_BODY_LEN: usize = 1;
233const TR_PAPR_BODY_LEN: usize = 5;
234const L1_ACE_PAPR_BODY_LEN: usize = 4;
235const TX_SIG_FEF_SEQ_NUM_BODY_LEN: usize = 5;
236const TX_SIG_AUX_STREAM_TX_ID_BODY_LEN: usize = 4;
237const FREQUENCY_BODY_LEN: usize = 5;
238
239// ── FunctionBody ───────────────────────────────────────────────────────────
240
241/// Parsed function body — typed for known tags, raw for unknown/reserved or
242/// length-mismatched entries.
243///
244/// Typed variants correspond to the function bodies whose wire layouts are
245/// defined in ETSI TS 102 773 §5.2.8.2 Tables 7–12b.  [`FunctionBody::Raw`]
246/// is the escape for:
247/// - tags not in [`AddressingFunctionTag`] (reserved / private),
248/// - tags in [`AddressingFunctionTag`] whose body layout is not vendored
249///   (e.g. 0x00–0x06), or
250/// - a known tag whose `function_length` doesn't match the expected body size
251///   (future extension).
252#[derive(Debug, Clone, PartialEq, Eq)]
253#[cfg_attr(feature = "serde", derive(serde::Serialize))]
254#[non_exhaustive]
255pub enum FunctionBody<'a> {
256    /// ACE-PAPR (tag 0x10) — §5.2.8.2.1, Table 7.
257    AcePapr(AcePaprBody),
258    /// MISO group (tag 0x11) — §5.2.8.2.2, Table 8.
259    MisoGroup(MisoGroupBody),
260    /// TR-PAPR (tag 0x12) — §5.2.8.2.4, Table 9.
261    TrPapr(TrPaprBody),
262    /// L1-ACE-PAPR (tag 0x13) — §5.2.8.2.5, Table 10.
263    L1AcePapr(L1AcePaprBody),
264    /// TX-SIG FEF sequence numbers (tag 0x15) — §5.2.8.2.5, Table 11.
265    TxSigFefSeqNum(TxSigFefSeqNumBody),
266    /// TX-SIG auxiliary stream transmitter ID (tag 0x16) — §5.2.9, Table 12a.
267    TxSigAuxStreamTxId(TxSigAuxStreamTxIdBody),
268    /// Frequency (tag 0x17) — §5.2.9, Table 12b.
269    Frequency(FrequencyBody),
270    /// Unknown/reserved tag or known tag with unexpected `function_length`.
271    /// `body` is the raw function body bytes (after tag + length, exactly
272    /// `function_length` bytes).
273    Raw(&'a [u8]),
274}
275
276impl FunctionBody<'_> {
277    /// Human-readable spec label (ETSI TS 102 773 §5.2.8.2 Tables 7–12b).
278    #[must_use]
279    pub fn name(&self) -> &'static str {
280        match self {
281            Self::AcePapr(_) => "ACE-PAPR",
282            Self::MisoGroup(_) => "MISO group",
283            Self::TrPapr(_) => "TR-PAPR",
284            Self::L1AcePapr(_) => "L1-ACE-PAPR",
285            Self::TxSigFefSeqNum(_) => "TX-SIG FEF sequence numbers",
286            Self::TxSigAuxStreamTxId(_) => "TX-SIG auxiliary stream TX ID",
287            Self::Frequency(_) => "Frequency",
288            Self::Raw(_) => "raw",
289        }
290    }
291}
292broadcast_common::impl_spec_display!(FunctionBody<'_>);
293
294// ── TransmitterEntry ───────────────────────────────────────────────────────
295
296/// One transmitter entry in the individual-addressing loop (§5.2.8.1).
297///
298/// Wire layout within the transmitter loop:
299/// `transmitter_identifier(16) · function_loop_length(8) · function()…`
300#[derive(Debug, Clone, PartialEq, Eq)]
301#[cfg_attr(feature = "serde", derive(serde::Serialize))]
302#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
303pub struct TransmitterEntry<'a> {
304    /// Transmitter identifier (16 bits).
305    pub transmitter_id: u16,
306    /// Function entries within this transmitter.
307    pub functions: Vec<FunctionEntry<'a>>,
308}
309
310// ── FunctionEntry ───────────────────────────────────────────────────────────
311
312/// One function entry within a transmitter's function loop (§5.2.8.2).
313///
314/// Wire layout: `function_tag(8) · function_length(8) · function_body(function_length bytes)`
315#[derive(Debug, Clone, PartialEq, Eq)]
316#[cfg_attr(feature = "serde", derive(serde::Serialize))]
317#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
318pub struct FunctionEntry<'a> {
319    /// Raw function tag byte (8 bits). Use [`FunctionEntry::addressing_tag`]
320    /// to convert to [`AddressingFunctionTag`] when the tag is known.
321    pub tag: u8,
322    /// Parsed function body — typed for known tags, raw otherwise.
323    pub body: FunctionBody<'a>,
324}
325
326impl<'a> FunctionEntry<'a> {
327    /// Convert the raw `tag` byte to [`AddressingFunctionTag`], if it is a
328    /// known value.
329    #[must_use]
330    pub fn addressing_tag(&self) -> Option<AddressingFunctionTag> {
331        AddressingFunctionTag::try_from(self.tag).ok()
332    }
333}
334
335// ── IndividualAddressingPayload ─────────────────────────────────────────────
336
337/// Individual addressing payload (type 0x21) per ETSI TS 102 773 §5.2.8.1, Fig 11.
338///
339/// Top-level layout:
340/// - byte 0: rfu (8 bits) — preserved verbatim (individual-addressing RFU exception)
341/// - byte 1: individual_addressing_length (8 bits) — length of the transmitter loop
342/// - bytes 2..: transmitter loop — fully typed as [`TransmitterEntry`] vector
343#[derive(Debug, Clone, PartialEq, Eq)]
344#[cfg_attr(feature = "serde", derive(serde::Serialize))]
345#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
346pub struct IndividualAddressingPayload<'a> {
347    /// Reserved-for-future-use byte (byte 0); preserved verbatim for round-trip.
348    pub rfu: u8,
349    /// Typed transmitter entries.  The 8-bit `individual_addressing_length`
350    /// field is derived from the serialized size of this vector on serialize.
351    pub transmitters: Vec<TransmitterEntry<'a>>,
352}
353
354// ── Wire constants ─────────────────────────────────────────────────────────
355
356const HEADER_LEN: usize = 2;
357const TX_HEADER_LEN: usize = 3;
358const FUNC_HEADER_LEN: usize = 2;
359
360// ── Private body parse helpers ──────────────────────────────────────────────
361
362fn parse_ace_papr(body: &[u8]) -> Result<AcePaprBody, crate::Error> {
363    if body.len() < ACE_PAPR_BODY_LEN {
364        return Err(crate::Error::BufferTooShort {
365            need: ACE_PAPR_BODY_LEN,
366            have: body.len(),
367            what: "ACE-PAPR function body",
368        });
369    }
370    Ok(AcePaprBody {
371        ace_gain: (body[0] >> 3) & 0x1F,
372        ace_maximal_extension: body[0] & 0x07,
373        ace_clipping_threshold: (body[1] >> 1) & 0x7F,
374        rfu: body[1] & 0x01 != 0,
375    })
376}
377
378fn parse_miso_group(body: &[u8]) -> Result<MisoGroupBody, crate::Error> {
379    if body.len() < MISO_GROUP_BODY_LEN {
380        return Err(crate::Error::BufferTooShort {
381            need: MISO_GROUP_BODY_LEN,
382            have: body.len(),
383            what: "MISO group function body",
384        });
385    }
386    Ok(MisoGroupBody {
387        miso_group: body[0] & 0x80 != 0,
388        rfu: body[0] & 0x7F,
389    })
390}
391
392fn parse_tr_papr(body: &[u8]) -> Result<TrPaprBody, crate::Error> {
393    if body.len() < TR_PAPR_BODY_LEN {
394        return Err(crate::Error::BufferTooShort {
395            need: TR_PAPR_BODY_LEN,
396            have: body.len(),
397            what: "TR-PAPR function body",
398        });
399    }
400    Ok(TrPaprBody {
401        rfu1: (body[0] >> 4) & 0x0F,
402        tr_clipping_threshold: ((body[0] as u16 & 0x0F) << 8) | body[1] as u16,
403        rfu2: ((body[2] as u16) << 6) | ((body[3] as u16) >> 2),
404        number_of_iterations: ((body[3] as u16 & 0x03) << 8) | body[4] as u16,
405    })
406}
407
408fn parse_l1_ace_papr(body: &[u8]) -> Result<L1AcePaprBody, crate::Error> {
409    let (b, _) =
410        body.split_first_chunk::<L1_ACE_PAPR_BODY_LEN>()
411            .ok_or(crate::Error::BufferTooShort {
412                need: L1_ACE_PAPR_BODY_LEN,
413                have: body.len(),
414                what: "L1-ACE-PAPR function body",
415            })?;
416    Ok(L1AcePaprBody {
417        l1_ace_max_correction: u16::from_be_bytes([b[0], b[1]]),
418        rfu: u16::from_be_bytes([b[2], b[3]]),
419    })
420}
421
422fn parse_tx_sig_fef_seq_num(body: &[u8]) -> Result<TxSigFefSeqNumBody, crate::Error> {
423    if body.len() < TX_SIG_FEF_SEQ_NUM_BODY_LEN {
424        return Err(crate::Error::BufferTooShort {
425            need: TX_SIG_FEF_SEQ_NUM_BODY_LEN,
426            have: body.len(),
427            what: "TX-SIG FEF sequence numbers function body",
428        });
429    }
430    Ok(TxSigFefSeqNumBody {
431        rfu1: (body[0] >> 3) & 0x1F,
432        seq_num_1: body[0] & 0x07,
433        rfu2: (body[1] >> 3) & 0x1F,
434        seq_num_2: body[1] & 0x07,
435        rfu3: (body[2] as u32) << 16 | (body[3] as u32) << 8 | body[4] as u32,
436    })
437}
438
439fn parse_tx_sig_aux_stream_tx_id(body: &[u8]) -> Result<TxSigAuxStreamTxIdBody, crate::Error> {
440    if body.len() < TX_SIG_AUX_STREAM_TX_ID_BODY_LEN {
441        return Err(crate::Error::BufferTooShort {
442            need: TX_SIG_AUX_STREAM_TX_ID_BODY_LEN,
443            have: body.len(),
444            what: "TX-SIG aux stream TX ID function body",
445        });
446    }
447    Ok(TxSigAuxStreamTxIdBody {
448        tx_sig_aux_tx_id: ((body[0] as u16) << 4) | ((body[1] as u16) >> 4),
449        rfu: ((body[1] as u32 & 0x0F) << 16) | (body[2] as u32) << 8 | body[3] as u32,
450    })
451}
452
453fn parse_frequency(body: &[u8]) -> Result<FrequencyBody, crate::Error> {
454    if body.len() < FREQUENCY_BODY_LEN {
455        return Err(crate::Error::BufferTooShort {
456            need: FREQUENCY_BODY_LEN,
457            have: body.len(),
458            what: "Frequency function body",
459        });
460    }
461    Ok(FrequencyBody {
462        rf_idx: (body[0] >> 5) & 0x07,
463        frequency: ((body[0] as u32 & 0x1F) << 27)
464            | (body[1] as u32) << 19
465            | (body[2] as u32) << 11
466            | (body[3] as u32) << 3
467            | ((body[4] >> 5) as u32 & 0x07),
468        rfu: body[4] & 0x1F,
469    })
470}
471
472/// Try to parse a typed body for `tag` from `body_bytes`.
473/// Returns `None` if the tag is known but `body_bytes.len()` doesn't match
474/// the expected size (fall back to Raw).  Returns `None` for unknown tags.
475/// (A length-matched body parse cannot fail for valid data; the per-helper
476/// `BufferTooShort` checks are defensive and unreachable at this call site.)
477fn try_parse_typed_body(
478    tag: u8,
479    body_bytes: &[u8],
480) -> Option<Result<FunctionBody<'_>, crate::Error>> {
481    match tag {
482        t if t == AddressingFunctionTag::AcePapr as u8 && body_bytes.len() == ACE_PAPR_BODY_LEN => {
483            Some(parse_ace_papr(body_bytes).map(FunctionBody::AcePapr))
484        }
485        t if t == AddressingFunctionTag::MisoGroup as u8
486            && body_bytes.len() == MISO_GROUP_BODY_LEN =>
487        {
488            Some(parse_miso_group(body_bytes).map(FunctionBody::MisoGroup))
489        }
490        t if t == AddressingFunctionTag::TrPapr as u8 && body_bytes.len() == TR_PAPR_BODY_LEN => {
491            Some(parse_tr_papr(body_bytes).map(FunctionBody::TrPapr))
492        }
493        t if t == AddressingFunctionTag::L1AcePapr as u8
494            && body_bytes.len() == L1_ACE_PAPR_BODY_LEN =>
495        {
496            Some(parse_l1_ace_papr(body_bytes).map(FunctionBody::L1AcePapr))
497        }
498        t if t == AddressingFunctionTag::TxSigFefSeqNum as u8
499            && body_bytes.len() == TX_SIG_FEF_SEQ_NUM_BODY_LEN =>
500        {
501            Some(parse_tx_sig_fef_seq_num(body_bytes).map(FunctionBody::TxSigFefSeqNum))
502        }
503        t if t == AddressingFunctionTag::TxSigAuxStreamTxId as u8
504            && body_bytes.len() == TX_SIG_AUX_STREAM_TX_ID_BODY_LEN =>
505        {
506            Some(parse_tx_sig_aux_stream_tx_id(body_bytes).map(FunctionBody::TxSigAuxStreamTxId))
507        }
508        t if t == AddressingFunctionTag::Frequency as u8
509            && body_bytes.len() == FREQUENCY_BODY_LEN =>
510        {
511            Some(parse_frequency(body_bytes).map(FunctionBody::Frequency))
512        }
513        _ => None,
514    }
515}
516
517// ── Private body serialize helpers ─────────────────────────────────────────
518
519fn serialize_ace_papr(body: &AcePaprBody, buf: &mut [u8]) {
520    buf[0] = (body.ace_gain & 0x1F) << 3 | (body.ace_maximal_extension & 0x07);
521    buf[1] = (body.ace_clipping_threshold & 0x7F) << 1 | if body.rfu { 1 } else { 0 };
522}
523
524fn serialize_miso_group(body: &MisoGroupBody, buf: &mut [u8]) {
525    buf[0] = if body.miso_group { 0x80 } else { 0x00 } | (body.rfu & 0x7F);
526}
527
528fn serialize_tr_papr(body: &TrPaprBody, buf: &mut [u8]) {
529    buf[0] = (body.rfu1 & 0x0F) << 4 | ((body.tr_clipping_threshold >> 8) as u8 & 0x0F);
530    buf[1] = (body.tr_clipping_threshold & 0xFF) as u8;
531    buf[2] = (body.rfu2 >> 6) as u8;
532    buf[3] = ((body.rfu2 & 0x3F) as u8) << 2 | ((body.number_of_iterations >> 8) as u8 & 0x03);
533    buf[4] = (body.number_of_iterations & 0xFF) as u8;
534}
535
536fn serialize_l1_ace_papr(body: &L1AcePaprBody, buf: &mut [u8]) {
537    buf[0..2].copy_from_slice(&body.l1_ace_max_correction.to_be_bytes());
538    buf[2..4].copy_from_slice(&body.rfu.to_be_bytes());
539}
540
541fn serialize_tx_sig_fef_seq_num(body: &TxSigFefSeqNumBody, buf: &mut [u8]) {
542    buf[0] = (body.rfu1 & 0x1F) << 3 | (body.seq_num_1 & 0x07);
543    buf[1] = (body.rfu2 & 0x1F) << 3 | (body.seq_num_2 & 0x07);
544    buf[2] = ((body.rfu3 >> 16) & 0xFF) as u8;
545    buf[3] = ((body.rfu3 >> 8) & 0xFF) as u8;
546    buf[4] = (body.rfu3 & 0xFF) as u8;
547}
548
549fn serialize_tx_sig_aux_stream_tx_id(body: &TxSigAuxStreamTxIdBody, buf: &mut [u8]) {
550    buf[0] = ((body.tx_sig_aux_tx_id >> 4) & 0xFF) as u8;
551    buf[1] = ((body.tx_sig_aux_tx_id & 0x0F) as u8) << 4 | ((body.rfu >> 16) & 0x0F) as u8;
552    buf[2] = ((body.rfu >> 8) & 0xFF) as u8;
553    buf[3] = (body.rfu & 0xFF) as u8;
554}
555
556fn serialize_frequency(body: &FrequencyBody, buf: &mut [u8]) {
557    buf[0] = (body.rf_idx & 0x07) << 5 | ((body.frequency >> 27) & 0x1F) as u8;
558    buf[1] = ((body.frequency >> 19) & 0xFF) as u8;
559    buf[2] = ((body.frequency >> 11) & 0xFF) as u8;
560    buf[3] = ((body.frequency >> 3) & 0xFF) as u8;
561    buf[4] = ((body.frequency & 0x07) as u8) << 5 | (body.rfu & 0x1F);
562}
563
564fn body_serialized_len(body: &FunctionBody<'_>) -> usize {
565    match body {
566        FunctionBody::AcePapr(_) => ACE_PAPR_BODY_LEN,
567        FunctionBody::MisoGroup(_) => MISO_GROUP_BODY_LEN,
568        FunctionBody::TrPapr(_) => TR_PAPR_BODY_LEN,
569        FunctionBody::L1AcePapr(_) => L1_ACE_PAPR_BODY_LEN,
570        FunctionBody::TxSigFefSeqNum(_) => TX_SIG_FEF_SEQ_NUM_BODY_LEN,
571        FunctionBody::TxSigAuxStreamTxId(_) => TX_SIG_AUX_STREAM_TX_ID_BODY_LEN,
572        FunctionBody::Frequency(_) => FREQUENCY_BODY_LEN,
573        FunctionBody::Raw(bytes) => bytes.len(),
574    }
575}
576
577fn serialize_body_into(body: &FunctionBody<'_>, buf: &mut [u8]) -> usize {
578    match body {
579        FunctionBody::AcePapr(b) => {
580            serialize_ace_papr(b, buf);
581            ACE_PAPR_BODY_LEN
582        }
583        FunctionBody::MisoGroup(b) => {
584            serialize_miso_group(b, buf);
585            MISO_GROUP_BODY_LEN
586        }
587        FunctionBody::TrPapr(b) => {
588            serialize_tr_papr(b, buf);
589            TR_PAPR_BODY_LEN
590        }
591        FunctionBody::L1AcePapr(b) => {
592            serialize_l1_ace_papr(b, buf);
593            L1_ACE_PAPR_BODY_LEN
594        }
595        FunctionBody::TxSigFefSeqNum(b) => {
596            serialize_tx_sig_fef_seq_num(b, buf);
597            TX_SIG_FEF_SEQ_NUM_BODY_LEN
598        }
599        FunctionBody::TxSigAuxStreamTxId(b) => {
600            serialize_tx_sig_aux_stream_tx_id(b, buf);
601            TX_SIG_AUX_STREAM_TX_ID_BODY_LEN
602        }
603        FunctionBody::Frequency(b) => {
604            serialize_frequency(b, buf);
605            FREQUENCY_BODY_LEN
606        }
607        FunctionBody::Raw(bytes) => {
608            buf[..bytes.len()].copy_from_slice(bytes);
609            bytes.len()
610        }
611    }
612}
613
614fn function_entry_serialized_len(entry: &FunctionEntry<'_>) -> usize {
615    FUNC_HEADER_LEN + body_serialized_len(&entry.body)
616}
617
618fn transmitter_entry_serialized_len(entry: &TransmitterEntry<'_>) -> usize {
619    let func_loop_len: usize = entry
620        .functions
621        .iter()
622        .map(function_entry_serialized_len)
623        .sum();
624    TX_HEADER_LEN + func_loop_len
625}
626
627// ── Parse ──────────────────────────────────────────────────────────────────
628
629impl<'a> Parse<'a> for IndividualAddressingPayload<'a> {
630    type Error = crate::error::Error;
631
632    fn parse(bytes: &'a [u8]) -> Result<Self, crate::error::Error> {
633        if bytes.len() < HEADER_LEN {
634            return Err(crate::Error::BufferTooShort {
635                need: HEADER_LEN,
636                have: bytes.len(),
637                what: "IndividualAddressingPayload header",
638            });
639        }
640
641        let rfu = bytes[0];
642        let individual_addressing_length = bytes[1] as usize;
643        let need = HEADER_LEN + individual_addressing_length;
644        if bytes.len() < need {
645            return Err(crate::Error::BufferTooShort {
646                need,
647                have: bytes.len(),
648                what: "IndividualAddressingPayload data",
649            });
650        }
651
652        let data = &bytes[HEADER_LEN..need];
653        let mut pos: usize = 0;
654        let max_tx = individual_addressing_length / TX_HEADER_LEN + 1;
655        let mut transmitters = Vec::with_capacity(max_tx.min(individual_addressing_length));
656
657        while pos < individual_addressing_length {
658            if pos + TX_HEADER_LEN > individual_addressing_length {
659                return Err(crate::Error::BufferTooShort {
660                    need: pos + TX_HEADER_LEN,
661                    have: individual_addressing_length,
662                    what: "transmitter entry header",
663                });
664            }
665
666            let (tx_id_bytes, _) = data
667                .get(pos..)
668                .and_then(|s| s.split_first_chunk::<2>())
669                .ok_or(crate::Error::BufferTooShort {
670                    need: pos + 2,
671                    have: individual_addressing_length,
672                    what: "transmitter entry header",
673                })?;
674            let transmitter_id = u16::from_be_bytes(*tx_id_bytes);
675            let function_loop_length = data[pos + 2] as usize;
676            pos += TX_HEADER_LEN;
677
678            let func_end = pos + function_loop_length;
679            if func_end > individual_addressing_length {
680                return Err(crate::Error::BufferTooShort {
681                    need: func_end,
682                    have: individual_addressing_length,
683                    what: "function loop",
684                });
685            }
686
687            let max_funcs = function_loop_length / FUNC_HEADER_LEN + 1;
688            let mut functions = Vec::with_capacity(max_funcs.min(function_loop_length));
689
690            while pos < func_end {
691                if pos + FUNC_HEADER_LEN > func_end {
692                    return Err(crate::Error::BufferTooShort {
693                        need: pos + FUNC_HEADER_LEN,
694                        have: func_end,
695                        what: "function entry header",
696                    });
697                }
698
699                let tag = data[pos];
700                let function_length = data[pos + 1] as usize;
701                pos += FUNC_HEADER_LEN;
702
703                let body_end = pos + function_length;
704                if body_end > func_end {
705                    return Err(crate::Error::BufferTooShort {
706                        need: body_end,
707                        have: func_end,
708                        what: "function body",
709                    });
710                }
711
712                let body_bytes = &data[pos..body_end];
713                pos = body_end;
714
715                let body = match try_parse_typed_body(tag, body_bytes) {
716                    Some(Ok(typed)) => typed,
717                    Some(Err(e)) => return Err(e),
718                    None => FunctionBody::Raw(body_bytes),
719                };
720
721                functions.push(FunctionEntry { tag, body });
722            }
723
724            transmitters.push(TransmitterEntry {
725                transmitter_id,
726                functions,
727            });
728        }
729
730        Ok(IndividualAddressingPayload { rfu, transmitters })
731    }
732}
733
734impl<'a> crate::traits::PayloadDef<'a> for IndividualAddressingPayload<'a> {
735    const PACKET_TYPE: u8 = 0x21;
736    const NAME: &'static str = "INDIVIDUAL_ADDRESSING";
737}
738
739// ── Serialize ──────────────────────────────────────────────────────────────
740
741impl Serialize for IndividualAddressingPayload<'_> {
742    type Error = crate::error::Error;
743
744    fn serialized_len(&self) -> usize {
745        HEADER_LEN
746            + self
747                .transmitters
748                .iter()
749                .map(transmitter_entry_serialized_len)
750                .sum::<usize>()
751    }
752
753    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize, crate::error::Error> {
754        let data_len: usize = self
755            .transmitters
756            .iter()
757            .map(transmitter_entry_serialized_len)
758            .sum();
759        let total = HEADER_LEN + data_len;
760
761        if buf.len() < total {
762            return Err(crate::Error::OutputBufferTooSmall {
763                need: total,
764                have: buf.len(),
765            });
766        }
767
768        if data_len > u8::MAX as usize {
769            return Err(crate::Error::ReservedBitsViolation {
770                field: "individual_addressing_length",
771                reason: "transmitter loop exceeds 255 bytes (8-bit length field)",
772            });
773        }
774
775        buf[0] = self.rfu;
776        buf[1] = data_len as u8;
777
778        let mut pos: usize = HEADER_LEN;
779
780        for tx in &self.transmitters {
781            let func_loop_len: usize = tx.functions.iter().map(function_entry_serialized_len).sum();
782            if func_loop_len > u8::MAX as usize {
783                return Err(crate::Error::ReservedBitsViolation {
784                    field: "function_loop_length",
785                    reason: "function loop exceeds 255 bytes (8-bit length field)",
786                });
787            }
788
789            buf[pos] = (tx.transmitter_id >> 8) as u8;
790            buf[pos + 1] = (tx.transmitter_id & 0xFF) as u8;
791            buf[pos + 2] = func_loop_len as u8;
792            pos += TX_HEADER_LEN;
793
794            for func in &tx.functions {
795                let body_len = body_serialized_len(&func.body);
796                if body_len > u8::MAX as usize {
797                    return Err(crate::Error::ReservedBitsViolation {
798                        field: "function_length",
799                        reason: "function body exceeds 255 bytes (8-bit length field)",
800                    });
801                }
802
803                buf[pos] = func.tag;
804                buf[pos + 1] = body_len as u8;
805                pos += FUNC_HEADER_LEN;
806
807                let written = serialize_body_into(&func.body, &mut buf[pos..]);
808                debug_assert_eq!(written, body_len);
809                pos += body_len;
810            }
811        }
812
813        debug_assert_eq!(pos, total);
814        Ok(total)
815    }
816}
817
818// ── Display ────────────────────────────────────────────────────────────────
819
820impl fmt::Display for IndividualAddressingPayload<'_> {
821    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
822        write!(
823            f,
824            "IndividualAddressing {{ rfu: 0x{:02X}, tx_count: {} }}",
825            self.rfu,
826            self.transmitters.len()
827        )
828    }
829}
830
831// ── Tests ──────────────────────────────────────────────────────────────────
832
833#[cfg(test)]
834mod tests {
835    use super::*;
836
837    #[test]
838    fn addressing_function_tag_try_from_valid() {
839        assert_eq!(
840            AddressingFunctionTag::try_from(0x10),
841            Ok(AddressingFunctionTag::AcePapr)
842        );
843        assert_eq!(
844            AddressingFunctionTag::try_from(0x17),
845            Ok(AddressingFunctionTag::Frequency)
846        );
847    }
848
849    #[test]
850    fn addressing_function_tag_try_from_rejects_unknown() {
851        assert!(AddressingFunctionTag::try_from(0x14).is_err());
852        assert!(AddressingFunctionTag::try_from(0xFF).is_err());
853    }
854
855    #[test]
856    fn exhaustive_byte_sweep() {
857        let mut matched = 0u16;
858        for byte in 0u8..=0xFF {
859            if let Ok(v) = AddressingFunctionTag::try_from(byte) {
860                assert_eq!(v as u8, byte, "round-trip failed for {byte:#04x}");
861                matched += 1;
862            }
863        }
864        assert_eq!(matched, 14, "expected 14 matched variants");
865    }
866
867    #[test]
868    fn address_function_tag_display() {
869        // §204 convention: Display is the spec label (was Debug-style before).
870        assert_eq!(
871            AddressingFunctionTag::AcePapr.to_string(),
872            "ACE-PAPR reduction"
873        );
874        assert_eq!(AddressingFunctionTag::AcePapr.name(), "ACE-PAPR reduction");
875        assert_eq!(AddressingFunctionTag::CellId.to_string(), "Cell ID");
876    }
877
878    #[test]
879    fn parse_empty_data_loop() {
880        let buf = [0x00u8, 0x00];
881        let result = IndividualAddressingPayload::parse(&buf).unwrap();
882        assert_eq!(result.rfu, 0x00);
883        assert!(result.transmitters.is_empty());
884    }
885
886    #[test]
887    fn parse_preserves_rfu_byte() {
888        let buf = [0xFFu8, 0x00];
889        let result = IndividualAddressingPayload::parse(&buf).unwrap();
890        assert_eq!(result.rfu, 0xFF);
891        assert!(result.transmitters.is_empty());
892    }
893
894    #[test]
895    fn parse_rejects_short_buffer() {
896        assert!(IndividualAddressingPayload::parse(&[0x00]).is_err());
897    }
898
899    #[test]
900    fn parse_rejects_truncated_data() {
901        assert!(IndividualAddressingPayload::parse(&[0x00, 0x04, 0xAA, 0xBB]).is_err());
902    }
903
904    #[test]
905    fn parse_single_transmitter_single_function_ace_papr() {
906        let func_body = [0xA8u8, 0x54];
907        let func_loop_len = (FUNC_HEADER_LEN + func_body.len()) as u8;
908        let tx_loop = [
909            0x00,
910            0x05,
911            func_loop_len,
912            0x10,
913            ACE_PAPR_BODY_LEN as u8,
914            func_body[0],
915            func_body[1],
916        ];
917        let mut buf = vec![0x00u8, tx_loop.len() as u8];
918        buf.extend_from_slice(&tx_loop);
919
920        let result = IndividualAddressingPayload::parse(&buf).unwrap();
921        assert_eq!(result.transmitters.len(), 1);
922        assert_eq!(result.transmitters[0].transmitter_id, 0x0005);
923        assert_eq!(result.transmitters[0].functions.len(), 1);
924
925        let func = &result.transmitters[0].functions[0];
926        assert_eq!(func.tag, 0x10);
927        assert_eq!(func.addressing_tag(), Some(AddressingFunctionTag::AcePapr));
928
929        match &func.body {
930            FunctionBody::AcePapr(body) => {
931                assert_eq!(body.ace_gain, 0x15);
932                assert_eq!(body.ace_maximal_extension, 0x00);
933                assert_eq!(body.ace_clipping_threshold, 0x2A);
934                assert!(!body.rfu);
935            }
936            other => panic!("expected AcePapr, got {other:?}"),
937        }
938    }
939
940    #[test]
941    fn parse_single_transmitter_single_function_miso_group() {
942        let func_body = [0x85u8];
943        let func_loop_len = (FUNC_HEADER_LEN + func_body.len()) as u8;
944        let tx_loop = [
945            0x00,
946            0x0A,
947            func_loop_len,
948            0x11,
949            MISO_GROUP_BODY_LEN as u8,
950            func_body[0],
951        ];
952        let mut buf = vec![0x00u8, tx_loop.len() as u8];
953        buf.extend_from_slice(&tx_loop);
954
955        let result = IndividualAddressingPayload::parse(&buf).unwrap();
956        assert_eq!(result.transmitters.len(), 1);
957        assert_eq!(result.transmitters[0].transmitter_id, 0x000A);
958
959        let func = &result.transmitters[0].functions[0];
960        assert_eq!(func.tag, 0x11);
961        match &func.body {
962            FunctionBody::MisoGroup(body) => {
963                assert!(body.miso_group);
964                assert_eq!(body.rfu, 0x05);
965            }
966            other => panic!("expected MisoGroup, got {other:?}"),
967        }
968    }
969
970    #[test]
971    fn parse_single_transmitter_single_function_frequency() {
972        let freq_body = FrequencyBody {
973            rf_idx: 3,
974            frequency: 0x80000000,
975            rfu: 0,
976        };
977        let mut func_bytes = [0u8; FREQUENCY_BODY_LEN];
978        serialize_frequency(&freq_body, &mut func_bytes);
979
980        let func_loop_len = (FUNC_HEADER_LEN + func_bytes.len()) as u8;
981        let mut tx_loop = vec![0x00, 0x07, func_loop_len, 0x17, FREQUENCY_BODY_LEN as u8];
982        tx_loop.extend_from_slice(&func_bytes);
983
984        let mut buf = vec![0x00u8, tx_loop.len() as u8];
985        buf.extend_from_slice(&tx_loop);
986
987        let result = IndividualAddressingPayload::parse(&buf).unwrap();
988        let func = &result.transmitters[0].functions[0];
989        assert_eq!(func.tag, 0x17);
990        match &func.body {
991            FunctionBody::Frequency(body) => {
992                assert_eq!(body.rf_idx, 3);
993                assert_eq!(body.frequency, 0x80000000);
994                assert_eq!(body.rfu, 0);
995            }
996            other => panic!("expected Frequency, got {other:?}"),
997        }
998    }
999
1000    #[test]
1001    fn parse_unknown_tag_produces_raw_body() {
1002        let func_body = [0xDE, 0xAD, 0xBE];
1003        let func_loop_len = (FUNC_HEADER_LEN + func_body.len()) as u8;
1004        let tx_loop = [
1005            0x00,
1006            0x01,
1007            func_loop_len,
1008            0x14,
1009            func_body.len() as u8,
1010            func_body[0],
1011            func_body[1],
1012            func_body[2],
1013        ];
1014        let mut buf = vec![0x00u8, tx_loop.len() as u8];
1015        buf.extend_from_slice(&tx_loop);
1016
1017        let result = IndividualAddressingPayload::parse(&buf).unwrap();
1018        let func = &result.transmitters[0].functions[0];
1019        assert_eq!(func.tag, 0x14);
1020        assert_eq!(func.addressing_tag(), None);
1021        match &func.body {
1022            FunctionBody::Raw(bytes) => assert_eq!(*bytes, &[0xDE, 0xAD, 0xBE]),
1023            other => panic!("expected Raw, got {other:?}"),
1024        }
1025    }
1026
1027    #[test]
1028    fn parse_known_tag_wrong_length_falls_back_to_raw() {
1029        let func_body = [0xAA, 0xBB, 0xCC];
1030        let func_loop_len = (FUNC_HEADER_LEN + func_body.len()) as u8;
1031        let tx_loop = [
1032            0x00,
1033            0x01,
1034            func_loop_len,
1035            0x10,
1036            func_body.len() as u8,
1037            func_body[0],
1038            func_body[1],
1039            func_body[2],
1040        ];
1041        let mut buf = vec![0x00u8, tx_loop.len() as u8];
1042        buf.extend_from_slice(&tx_loop);
1043
1044        let result = IndividualAddressingPayload::parse(&buf).unwrap();
1045        let func = &result.transmitters[0].functions[0];
1046        assert_eq!(func.tag, 0x10);
1047        assert_eq!(func.addressing_tag(), Some(AddressingFunctionTag::AcePapr));
1048        match &func.body {
1049            FunctionBody::Raw(bytes) => assert_eq!(*bytes, &[0xAA, 0xBB, 0xCC]),
1050            other => panic!("expected Raw fallback for length mismatch, got {other:?}"),
1051        }
1052    }
1053
1054    #[test]
1055    fn parse_truncated_transmitter_header() {
1056        let buf = [0x00u8, 0x02, 0x00];
1057        assert!(IndividualAddressingPayload::parse(&buf).is_err());
1058    }
1059
1060    #[test]
1061    fn parse_truncated_function_header() {
1062        let tx_loop = [0x00, 0x01, 0x02, 0x10];
1063        let buf = [0x00u8, tx_loop.len() as u8, 0x00, 0x01, 0x02, 0x10];
1064        assert!(IndividualAddressingPayload::parse(&buf).is_err());
1065    }
1066
1067    #[test]
1068    fn parse_function_body_exceeds_function_loop() {
1069        let tx_loop = [0x00, 0x01, 0x04, 0x10, 0xFF, 0xAA, 0xBB];
1070        let mut buf = vec![0x00u8, tx_loop.len() as u8];
1071        buf.extend_from_slice(&tx_loop);
1072        assert!(IndividualAddressingPayload::parse(&buf).is_err());
1073    }
1074
1075    #[test]
1076    fn round_trip_two_transmitters_mixed_bodies() {
1077        let ace_body = AcePaprBody {
1078            ace_gain: 0x0A,
1079            ace_maximal_extension: 0x03,
1080            ace_clipping_threshold: 0x5A,
1081            rfu: true,
1082        };
1083        let raw_body: &[u8] = &[0xDE, 0xAD, 0xBE, 0xEF];
1084
1085        let orig = IndividualAddressingPayload {
1086            rfu: 0xAB,
1087            transmitters: vec![
1088                TransmitterEntry {
1089                    transmitter_id: 0x0005,
1090                    functions: vec![
1091                        FunctionEntry {
1092                            tag: 0x10,
1093                            body: FunctionBody::AcePapr(ace_body.clone()),
1094                        },
1095                        FunctionEntry {
1096                            tag: 0x14,
1097                            body: FunctionBody::Raw(raw_body),
1098                        },
1099                    ],
1100                },
1101                TransmitterEntry {
1102                    transmitter_id: 0x00FF,
1103                    functions: vec![FunctionEntry {
1104                        tag: 0x11,
1105                        body: FunctionBody::MisoGroup(MisoGroupBody {
1106                            miso_group: true,
1107                            rfu: 0x42,
1108                        }),
1109                    }],
1110                },
1111            ],
1112        };
1113
1114        let mut buf = vec![0u8; orig.serialized_len()];
1115        orig.serialize_into(&mut buf).unwrap();
1116
1117        assert_eq!(buf[0], 0xAB);
1118
1119        let parsed = IndividualAddressingPayload::parse(&buf).unwrap();
1120        assert_eq!(orig, parsed);
1121    }
1122
1123    #[test]
1124    fn round_trip_all_typed_bodies() {
1125        let orig = IndividualAddressingPayload {
1126            rfu: 0x00,
1127            transmitters: vec![
1128                TransmitterEntry {
1129                    transmitter_id: 0x0001,
1130                    functions: vec![
1131                        FunctionEntry {
1132                            tag: 0x10,
1133                            body: FunctionBody::AcePapr(AcePaprBody {
1134                                ace_gain: 0x1F,
1135                                ace_maximal_extension: 0x07,
1136                                ace_clipping_threshold: 0x7F,
1137                                rfu: true,
1138                            }),
1139                        },
1140                        FunctionEntry {
1141                            tag: 0x11,
1142                            body: FunctionBody::MisoGroup(MisoGroupBody {
1143                                miso_group: false,
1144                                rfu: 0x7F,
1145                            }),
1146                        },
1147                        FunctionEntry {
1148                            tag: 0x12,
1149                            body: FunctionBody::TrPapr(TrPaprBody {
1150                                rfu1: 0x0A,
1151                                tr_clipping_threshold: 0xABC,
1152                                rfu2: 0x1FFF,
1153                                number_of_iterations: 0x1FF,
1154                            }),
1155                        },
1156                    ],
1157                },
1158                TransmitterEntry {
1159                    transmitter_id: 0x0002,
1160                    functions: vec![
1161                        FunctionEntry {
1162                            tag: 0x13,
1163                            body: FunctionBody::L1AcePapr(L1AcePaprBody {
1164                                l1_ace_max_correction: 0x1234,
1165                                rfu: 0x5678,
1166                            }),
1167                        },
1168                        FunctionEntry {
1169                            tag: 0x15,
1170                            body: FunctionBody::TxSigFefSeqNum(TxSigFefSeqNumBody {
1171                                rfu1: 0x1F,
1172                                seq_num_1: 0x07,
1173                                rfu2: 0x00,
1174                                seq_num_2: 0x05,
1175                                rfu3: 0xABCDEF,
1176                            }),
1177                        },
1178                        FunctionEntry {
1179                            tag: 0x16,
1180                            body: FunctionBody::TxSigAuxStreamTxId(TxSigAuxStreamTxIdBody {
1181                                tx_sig_aux_tx_id: 0xFFF,
1182                                rfu: 0x0000F,
1183                            }),
1184                        },
1185                        FunctionEntry {
1186                            tag: 0x17,
1187                            body: FunctionBody::Frequency(FrequencyBody {
1188                                rf_idx: 0x05,
1189                                frequency: 0x87654321,
1190                                rfu: 0x1F,
1191                            }),
1192                        },
1193                    ],
1194                },
1195            ],
1196        };
1197
1198        let mut buf = vec![0u8; orig.serialized_len()];
1199        orig.serialize_into(&mut buf).unwrap();
1200        let parsed = IndividualAddressingPayload::parse(&buf).unwrap();
1201        assert_eq!(orig, parsed);
1202    }
1203
1204    #[test]
1205    fn serialize_empty_data() {
1206        let orig = IndividualAddressingPayload {
1207            rfu: 0x00,
1208            transmitters: vec![],
1209        };
1210        let mut buf = vec![0u8; orig.serialized_len()];
1211        orig.serialize_into(&mut buf).unwrap();
1212        assert_eq!(buf, [0x00, 0x00]);
1213    }
1214
1215    #[test]
1216    fn serialize_detects_data_loop_overflow() {
1217        let mut functions = Vec::new();
1218        for _ in 0..100 {
1219            functions.push(FunctionEntry {
1220                tag: AddressingFunctionTag::MisoGroup as u8,
1221                body: FunctionBody::MisoGroup(MisoGroupBody {
1222                    miso_group: false,
1223                    rfu: 0,
1224                }),
1225            });
1226        }
1227        let payload = IndividualAddressingPayload {
1228            rfu: 0,
1229            transmitters: vec![TransmitterEntry {
1230                transmitter_id: 0,
1231                functions,
1232            }],
1233        };
1234        let mut buf = vec![0u8; payload.serialized_len()];
1235        let result = payload.serialize_into(&mut buf);
1236        assert!(
1237            matches!(
1238                result.unwrap_err(),
1239                crate::Error::ReservedBitsViolation { .. }
1240            ),
1241            "expected ReservedBitsViolation for overflowing length field"
1242        );
1243    }
1244
1245    #[test]
1246    fn body_parse_round_trip_ace_papr() {
1247        let body = AcePaprBody {
1248            ace_gain: 0x0A,
1249            ace_maximal_extension: 0x05,
1250            ace_clipping_threshold: 0x41,
1251            rfu: true,
1252        };
1253        let mut buf = [0u8; ACE_PAPR_BODY_LEN];
1254        serialize_ace_papr(&body, &mut buf);
1255        let parsed = parse_ace_papr(&buf).unwrap();
1256        assert_eq!(body, parsed);
1257    }
1258
1259    #[test]
1260    fn body_parse_round_trip_tr_papr() {
1261        let body = TrPaprBody {
1262            rfu1: 0x0C,
1263            tr_clipping_threshold: 0xFFF,
1264            rfu2: 0x0ABC,
1265            number_of_iterations: 0x3FF,
1266        };
1267        let mut buf = [0u8; TR_PAPR_BODY_LEN];
1268        serialize_tr_papr(&body, &mut buf);
1269        let parsed = parse_tr_papr(&buf).unwrap();
1270        assert_eq!(body, parsed);
1271    }
1272
1273    #[test]
1274    fn body_parse_round_trip_frequency() {
1275        let body = FrequencyBody {
1276            rf_idx: 0x07,
1277            frequency: 0xFFFFFFFF,
1278            rfu: 0x1F,
1279        };
1280        let mut buf = [0u8; FREQUENCY_BODY_LEN];
1281        serialize_frequency(&body, &mut buf);
1282        let parsed = parse_frequency(&buf).unwrap();
1283        assert_eq!(body, parsed);
1284    }
1285
1286    #[test]
1287    fn body_parse_round_trip_tx_sig_aux_stream_tx_id() {
1288        let body = TxSigAuxStreamTxIdBody {
1289            tx_sig_aux_tx_id: 0xFFF,
1290            rfu: 0xFFFFF,
1291        };
1292        let mut buf = [0u8; TX_SIG_AUX_STREAM_TX_ID_BODY_LEN];
1293        serialize_tx_sig_aux_stream_tx_id(&body, &mut buf);
1294        let parsed = parse_tx_sig_aux_stream_tx_id(&buf).unwrap();
1295        assert_eq!(body, parsed);
1296    }
1297
1298    #[test]
1299    fn function_entry_addressing_tag_method() {
1300        let entry_known = FunctionEntry {
1301            tag: 0x10,
1302            body: FunctionBody::AcePapr(AcePaprBody {
1303                ace_gain: 0,
1304                ace_maximal_extension: 0,
1305                ace_clipping_threshold: 0,
1306                rfu: false,
1307            }),
1308        };
1309        assert_eq!(
1310            entry_known.addressing_tag(),
1311            Some(AddressingFunctionTag::AcePapr)
1312        );
1313
1314        let entry_unknown = FunctionEntry {
1315            tag: 0xFF,
1316            body: FunctionBody::Raw(&[]),
1317        };
1318        assert_eq!(entry_unknown.addressing_tag(), None);
1319    }
1320}