Skip to main content

dvb_t2mi/payload/
arbitrary_cells.rs

1//! T2-MI payload type 0x02: Arbitrary cell insertion — §5.2.3.
2
3use dvb_common::{Parse, Serialize};
4
5/// Arbitrary cell insertion payload (type 0x02) per ETSI TS 102 773 §5.2.3.
6///
7/// Layout (byte offsets relative to payload start):
8/// - byte 0: frame_idx (8 bits)
9/// - byte 1-2: tx_identifier (16 bits) — 0x0000 = broadcast
10/// - byte 3-4: rfu (16 bits) — must be 0
11/// - byte 5 \[7:6\]: rfu (2 bits) — must be 0
12/// - byte 5 \[5:0\] + byte 6-7: start_cell_address (22 bits)
13/// - bytes 8..: arbitrary_cell_data (variable I/Q pairs)
14#[derive(Debug, Clone, PartialEq, Eq)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize))]
16#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
17pub struct ArbitraryCellsPayload<'a> {
18    /// FRAME_IDX of T2 frame.
19    pub frame_idx: u8,
20    /// Transmitter identifier (0x0000 = broadcast to all).
21    pub tx_identifier: u16,
22    /// Cell address per EN 302 755 §8.3.6.2 (22-bit field).
23    pub start_cell_address: u32,
24    /// Raw I/Q sample data (12-bit two's complement I + 12-bit Q pairs).
25    pub arbitrary_cell_data: &'a [u8],
26}
27
28const ARB_CELLS_HEADER_LEN: usize = 8;
29const CELL_ADDR_MASK: u32 = 0x003F_FFFF; // 22 bits
30
31impl<'a> Parse<'a> for ArbitraryCellsPayload<'a> {
32    type Error = crate::error::Error;
33
34    fn parse(bytes: &'a [u8]) -> Result<Self, crate::error::Error> {
35        if bytes.len() < ARB_CELLS_HEADER_LEN {
36            return Err(crate::Error::BufferTooShort {
37                need: ARB_CELLS_HEADER_LEN,
38                have: bytes.len(),
39                what: "ArbitraryCellsPayload header",
40            });
41        }
42
43        let frame_idx = bytes[0];
44        let tx_identifier = u16::from_be_bytes([bytes[1], bytes[2]]);
45
46        // RFU: bytes 3-4 (16 bits) + byte 5 top 2 bits = 18 bits total, must be 0
47        if bytes[3] != 0 || bytes[4] != 0 || (bytes[5] & 0xC0 != 0) {
48            return Err(crate::Error::ReservedBitsViolation {
49                field: "18-bit RFU",
50                reason: "Must be zero (ETSI TS 102 773 §5.2.3)",
51            });
52        }
53
54        // start_cell_address: byte 5 bottom 6 bits + bytes 6-7 = 22 bits
55        let start_cell_address =
56            ((bytes[5] as u32 & 0x3F) << 16) | ((bytes[6] as u32) << 8) | (bytes[7] as u32);
57
58        Ok(ArbitraryCellsPayload {
59            frame_idx,
60            tx_identifier,
61            start_cell_address,
62            arbitrary_cell_data: &bytes[ARB_CELLS_HEADER_LEN..],
63        })
64    }
65}
66
67impl<'a> crate::traits::PayloadDef<'a> for ArbitraryCellsPayload<'a> {
68    const PACKET_TYPE: u8 = 0x02;
69    const NAME: &'static str = "ARBITRARY_CELLS";
70}
71
72impl Serialize for ArbitraryCellsPayload<'_> {
73    type Error = crate::error::Error;
74
75    fn serialized_len(&self) -> usize {
76        ARB_CELLS_HEADER_LEN + self.arbitrary_cell_data.len()
77    }
78
79    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize, crate::error::Error> {
80        if buf.len() < self.serialized_len() {
81            return Err(crate::Error::OutputBufferTooSmall {
82                need: self.serialized_len(),
83                have: buf.len(),
84            });
85        }
86
87        if self.start_cell_address > CELL_ADDR_MASK {
88            return Err(crate::Error::ReservedBitsViolation {
89                field: "start_cell_address",
90                reason: "Must fit in 22 bits",
91            });
92        }
93
94        buf[0] = self.frame_idx;
95        let tx_id = self.tx_identifier.to_be_bytes();
96        buf[1] = tx_id[0];
97        buf[2] = tx_id[1];
98        buf[3] = 0; // RFU byte 1
99        buf[4] = 0; // RFU byte 2
100        buf[5] = ((self.start_cell_address >> 16) & 0x3F) as u8; // RFU top 2 bits = 0, cell addr top 6
101        buf[6] = ((self.start_cell_address >> 8) & 0xFF) as u8;
102        buf[7] = (self.start_cell_address & 0xFF) as u8;
103
104        if !self.arbitrary_cell_data.is_empty() {
105            buf[ARB_CELLS_HEADER_LEN..ARB_CELLS_HEADER_LEN + self.arbitrary_cell_data.len()]
106                .copy_from_slice(self.arbitrary_cell_data);
107        }
108
109        Ok(self.serialized_len())
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn parse_extracts_fields() {
119        let data = [0x10, 0xCA, 0xFE];
120        let mut buf = vec![0x42u8, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00];
121        buf.extend_from_slice(&data);
122
123        let result = ArbitraryCellsPayload::parse(&buf).unwrap();
124        assert_eq!(result.frame_idx, 0x42);
125        assert_eq!(result.tx_identifier, 0x0001);
126        assert_eq!(result.start_cell_address, 0);
127        assert_eq!(result.arbitrary_cell_data, &data[..]);
128    }
129
130    #[test]
131    fn parse_rejects_short_buffer() {
132        let buf = [0x00u8; 7];
133        assert!(ArbitraryCellsPayload::parse(&buf).is_err());
134    }
135
136    #[test]
137    fn parse_rejects_nonzero_rfu_byte_3() {
138        let buf = [0x00u8, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00];
139        assert!(ArbitraryCellsPayload::parse(&buf).is_err());
140    }
141
142    #[test]
143    fn parse_rejects_nonzero_rfu_byte_5_high_bits() {
144        let mut buf = [0x00u8; 8];
145        buf[5] = 0xC0; // top 2 bits set = RFU violation
146        assert!(ArbitraryCellsPayload::parse(&buf).is_err());
147    }
148
149    #[test]
150    fn serialize_round_trip() {
151        let orig = ArbitraryCellsPayload {
152            frame_idx: 0xAB,
153            tx_identifier: 0x0005,
154            start_cell_address: 0x123456,
155            arbitrary_cell_data: &[0xDE, 0xAD],
156        };
157        let mut buf = vec![0u8; orig.serialized_len()];
158        orig.serialize_into(&mut buf).unwrap();
159        let parsed = ArbitraryCellsPayload::parse(&buf).unwrap();
160        assert_eq!(orig, parsed);
161    }
162
163    #[test]
164    fn tx_id_broadcast_accepted() {
165        let buf = [0x00u8; 8];
166        let result = ArbitraryCellsPayload::parse(&buf).unwrap();
167        assert_eq!(result.tx_identifier, 0x0000);
168    }
169
170    #[test]
171    fn cell_address_max_22bit() {
172        let max_addr: u32 = 0x3F_FFFF;
173        let buf = [0x00u8, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF];
174        let result = ArbitraryCellsPayload::parse(&buf).unwrap();
175        assert_eq!(result.start_cell_address, max_addr);
176    }
177
178    #[test]
179    fn serialize_rejects_too_small_buffer() {
180        let payload = ArbitraryCellsPayload {
181            frame_idx: 0,
182            tx_identifier: 0,
183            start_cell_address: 0,
184            arbitrary_cell_data: &[],
185        };
186        let mut buf = [0u8; 7];
187        assert!(payload.serialize_into(&mut buf).is_err());
188    }
189}