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: tx_identifier + function loop
4//! with entries like ACE-PAPR (0x10), MISO group (0x11), Frequency (0x17), etc.
5
6use std::fmt;
7
8use num_enum::TryFromPrimitive;
9
10use dvb_common::{Parse, Serialize};
11
12/// Function tags per §5.2.8.2 Table 5.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize))]
15#[repr(u8)]
16pub enum AddressingFunctionTag {
17    /// Transmitter time offset.
18    TimeOffset = 0x00,
19    /// Transmitter frequency offset.
20    FrequencyOffset = 0x01,
21    /// Transmitter power.
22    Power = 0x02,
23    /// Private data.
24    PrivateData = 0x03,
25    /// Cell ID.
26    CellId = 0x04,
27    /// Enable.
28    Enable = 0x05,
29    /// Bandwidth (not applicable for T2).
30    Bandwidth = 0x06,
31    /// ACE-PAPR reduction (T2-specific).
32    AcePapr = 0x10,
33    /// MISO group (T2-specific).
34    MisoGroup = 0x11,
35    /// TR-PAPR reduction (T2-specific).
36    TrPapr = 0x12,
37    /// L1-ACE-PAPR (T2-specific).
38    L1AcePapr = 0x13,
39    /// TX-SIG FEF sequence number (T2-specific).
40    TxSigFefSeqNum = 0x15,
41    /// TX-SIG auxiliary stream TX ID (T2-specific).
42    TxSigAuxStreamTxId = 0x16,
43    /// Frequency (T2-specific).
44    Frequency = 0x17,
45}
46
47impl From<AddressingFunctionTag> for u8 {
48    fn from(tag: AddressingFunctionTag) -> Self {
49        tag as u8
50    }
51}
52
53impl fmt::Display for AddressingFunctionTag {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        match self {
56            AddressingFunctionTag::TimeOffset => write!(f, "TimeOffset"),
57            AddressingFunctionTag::FrequencyOffset => write!(f, "FrequencyOffset"),
58            AddressingFunctionTag::Power => write!(f, "Power"),
59            AddressingFunctionTag::PrivateData => write!(f, "PrivateData"),
60            AddressingFunctionTag::CellId => write!(f, "CellId"),
61            AddressingFunctionTag::Enable => write!(f, "Enable"),
62            AddressingFunctionTag::Bandwidth => write!(f, "Bandwidth"),
63            AddressingFunctionTag::AcePapr => write!(f, "AcePapr"),
64            AddressingFunctionTag::MisoGroup => write!(f, "MisoGroup"),
65            AddressingFunctionTag::TrPapr => write!(f, "TrPapr"),
66            AddressingFunctionTag::L1AcePapr => write!(f, "L1AcePapr"),
67            AddressingFunctionTag::TxSigFefSeqNum => write!(f, "TxSigFefSeqNum"),
68            AddressingFunctionTag::TxSigAuxStreamTxId => write!(f, "TxSigAuxStreamTxId"),
69            AddressingFunctionTag::Frequency => write!(f, "Frequency"),
70        }
71    }
72}
73
74/// A single function entry within individual addressing.
75#[derive(Debug, Clone, PartialEq, Eq)]
76#[cfg_attr(feature = "serde", derive(serde::Serialize))]
77#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
78pub struct FunctionEntry<'a> {
79    /// Function tag identifying the entry type.
80    pub tag: AddressingFunctionTag,
81    /// Raw function body (including tag + length bytes).
82    pub raw: &'a [u8],
83}
84
85/// Individual addressing payload (type 0x21) per ETSI TS 102 773 §5.2.8.1, Fig 11.
86///
87/// Top-level layout:
88/// - byte 0: rfu (8 bits) — reserved, ignored on parse, preserved for round-trip
89/// - byte 1: individual_addressing_length (8 bits) — length of the data loop in bytes
90/// - bytes 2..: individual_addressing_data — a loop of per-transmitter entries, each
91///   `tx_identifier(16) · function_loop_length(8) · function()…`. The tx_identifier
92///   lives INSIDE each entry, not at the top level; the loop is kept raw here.
93#[derive(Debug, Clone, PartialEq, Eq)]
94#[cfg_attr(feature = "serde", derive(serde::Serialize))]
95#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
96pub struct IndividualAddressingPayload<'a> {
97    /// Reserved-for-future-use byte (byte 0); preserved verbatim for round-trip.
98    pub rfu: u8,
99    /// Raw individual_addressing_data loop. Length is the 8-bit
100    /// `individual_addressing_length` field, derived from this slice on serialize.
101    pub individual_addressing_data: &'a [u8],
102}
103
104const HEADER_LEN: usize = 2;
105
106impl<'a> Parse<'a> for IndividualAddressingPayload<'a> {
107    type Error = crate::error::Error;
108
109    fn parse(bytes: &'a [u8]) -> Result<Self, crate::error::Error> {
110        if bytes.len() < HEADER_LEN {
111            return Err(crate::Error::BufferTooShort {
112                need: HEADER_LEN,
113                have: bytes.len(),
114                what: "IndividualAddressingPayload header",
115            });
116        }
117
118        let rfu = bytes[0];
119        let individual_addressing_length = bytes[1] as usize;
120        // The data loop must hold exactly the declared number of bytes (§5.2.8.1).
121        let need = HEADER_LEN + individual_addressing_length;
122        if bytes.len() < need {
123            return Err(crate::Error::BufferTooShort {
124                need,
125                have: bytes.len(),
126                what: "IndividualAddressingPayload data",
127            });
128        }
129
130        Ok(IndividualAddressingPayload {
131            rfu,
132            individual_addressing_data: &bytes[HEADER_LEN..need],
133        })
134    }
135}
136
137impl<'a> crate::traits::PayloadDef<'a> for IndividualAddressingPayload<'a> {
138    const PACKET_TYPE: u8 = 0x21;
139    const NAME: &'static str = "INDIVIDUAL_ADDRESSING";
140}
141
142impl Serialize for IndividualAddressingPayload<'_> {
143    type Error = crate::error::Error;
144
145    fn serialized_len(&self) -> usize {
146        HEADER_LEN + self.individual_addressing_data.len()
147    }
148
149    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize, crate::error::Error> {
150        if buf.len() < self.serialized_len() {
151            return Err(crate::Error::OutputBufferTooSmall {
152                need: self.serialized_len(),
153                have: buf.len(),
154            });
155        }
156
157        // individual_addressing_length is an 8-bit field — the data loop cannot
158        // exceed 255 bytes.
159        if self.individual_addressing_data.len() > u8::MAX as usize {
160            return Err(crate::Error::ReservedBitsViolation {
161                field: "individual_addressing_length",
162                reason: "individual_addressing_data exceeds 255 bytes (8-bit length field)",
163            });
164        }
165
166        buf[0] = self.rfu;
167        buf[1] = self.individual_addressing_data.len() as u8;
168
169        if !self.individual_addressing_data.is_empty() {
170            buf[HEADER_LEN..HEADER_LEN + self.individual_addressing_data.len()]
171                .copy_from_slice(self.individual_addressing_data);
172        }
173
174        Ok(self.serialized_len())
175    }
176}
177
178impl fmt::Display for IndividualAddressingPayload<'_> {
179    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180        write!(
181            f,
182            "IndividualAddressing {{ rfu: 0x{:02X}, addr_data_len: {} }}",
183            self.rfu,
184            self.individual_addressing_data.len()
185        )
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192
193    #[test]
194    fn addressing_function_tag_try_from_valid() {
195        assert_eq!(
196            AddressingFunctionTag::try_from(0x10),
197            Ok(AddressingFunctionTag::AcePapr)
198        );
199        assert_eq!(
200            AddressingFunctionTag::try_from(0x17),
201            Ok(AddressingFunctionTag::Frequency)
202        );
203    }
204
205    #[test]
206    fn addressing_function_tag_try_from_rejects_unknown() {
207        assert!(AddressingFunctionTag::try_from(0x14).is_err());
208        assert!(AddressingFunctionTag::try_from(0xFF).is_err());
209    }
210
211    #[test]
212    fn exhaustive_byte_sweep() {
213        let mut matched = 0u16;
214        for byte in 0u8..=0xFF {
215            if let Ok(v) = AddressingFunctionTag::try_from(byte) {
216                assert_eq!(v as u8, byte, "round-trip failed for {byte:#04x}");
217                matched += 1;
218            }
219        }
220        assert_eq!(matched, 14, "expected 14 matched variants");
221    }
222
223    #[test]
224    fn address_function_tag_display() {
225        assert_eq!(AddressingFunctionTag::AcePapr.to_string(), "AcePapr");
226    }
227
228    #[test]
229    fn parse_extracts_rfu_and_addressing_data() {
230        // rfu=0x00, length=4, then a 4-byte data loop. The data loop here is one
231        // transmitter entry: tx_identifier=0x0005, function_loop_length=0x04, ...
232        // — but parse keeps the whole loop raw.
233        let buf = [0x00u8, 0x04, 0x00, 0x05, 0x04, 0x10];
234        let result = IndividualAddressingPayload::parse(&buf).unwrap();
235        assert_eq!(result.rfu, 0x00);
236        assert_eq!(result.individual_addressing_data, &[0x00, 0x05, 0x04, 0x10]);
237    }
238
239    #[test]
240    fn parse_preserves_rfu_byte() {
241        let buf = [0xFFu8, 0x00];
242        let result = IndividualAddressingPayload::parse(&buf).unwrap();
243        assert_eq!(result.rfu, 0xFF);
244        assert!(result.individual_addressing_data.is_empty());
245    }
246
247    #[test]
248    fn parse_rejects_short_buffer() {
249        assert!(IndividualAddressingPayload::parse(&[0x00]).is_err());
250    }
251
252    #[test]
253    fn parse_rejects_truncated_data() {
254        // declares 4 data bytes but only 2 follow
255        assert!(IndividualAddressingPayload::parse(&[0x00, 0x04, 0xAA, 0xBB]).is_err());
256    }
257
258    #[test]
259    fn serialize_round_trip() {
260        let orig = IndividualAddressingPayload {
261            rfu: 0x00,
262            individual_addressing_data: &[0x00, 0x03, 0x04, 0xDE, 0xAD],
263        };
264        let mut buf = vec![0u8; orig.serialized_len()];
265        orig.serialize_into(&mut buf).unwrap();
266        // length field is derived from the data slice
267        assert_eq!(buf[1], 5);
268        let parsed = IndividualAddressingPayload::parse(&buf).unwrap();
269        assert_eq!(orig, parsed);
270    }
271
272    #[test]
273    fn serialize_empty_data() {
274        let orig = IndividualAddressingPayload {
275            rfu: 0x00,
276            individual_addressing_data: &[],
277        };
278        let mut buf = vec![0u8; orig.serialized_len()];
279        orig.serialize_into(&mut buf).unwrap();
280        assert_eq!(buf, [0x00, 0x00]);
281    }
282}