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