Skip to main content

dvb_t2mi/payload/
aux_iq.rs

1//! T2-MI payload type 0x01: Auxiliary stream I/Q data — §5.2.2.
2//!
3//! Carries complex I/Q samples for auxiliary streams at `1/T` symbol rate.
4//! Each sample: 12-bit two's complement I, then 12-bit two's complement Q.
5//! `Re(x) = I / 2^9`, `Im(x) = Q / 2^9`. Can span multiple T2-MI packets.
6
7use dvb_common::{Parse, Serialize};
8
9/// Minimum valid aux_id value.
10const AUX_ID_MIN: u8 = 1;
11/// Maximum valid aux_id value (4-bit field).
12const AUX_ID_MAX: u8 = 0x0F;
13
14/// Auxiliary I/Q payload (type 0x01) per ETSI TS 102 773 §5.2.2.
15///
16/// Layout:
17/// - byte 0: frame_idx (8 bits)
18/// - byte 1 \[7:4\]: aux_id (4 bits), range 1..=15
19/// - byte 1 \[3:0\] + byte 2 \[7:0\]: rfu (12 bits), must be 0
20/// - bytes 3..: aux_stream_data (variable, 12-bit I + 12-bit Q samples)
21#[derive(Debug, Clone, PartialEq, Eq)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize))]
23#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
24pub struct AuxIqPayload<'a> {
25    /// FRAME_IDX of the T2 frame.
26    pub frame_idx: u8,
27    /// Auxiliary stream identifier (1..=15).
28    pub aux_id: u8,
29    /// Raw auxiliary stream data bytes (I/Q pairs).
30    pub aux_stream_data: &'a [u8],
31}
32
33impl<'a> Parse<'a> for AuxIqPayload<'a> {
34    type Error = crate::error::Error;
35
36    fn parse(bytes: &'a [u8]) -> Result<Self, crate::error::Error> {
37        if bytes.len() < 3 {
38            return Err(crate::Error::BufferTooShort {
39                need: 3,
40                have: bytes.len(),
41                what: "AuxIqPayload header",
42            });
43        }
44
45        let frame_idx = bytes[0];
46        let aux_id = (bytes[1] >> 4) & 0x0F;
47
48        if !(AUX_ID_MIN..=AUX_ID_MAX).contains(&aux_id) {
49            return Err(crate::Error::ReservedBitsViolation {
50                field: "aux_id",
51                reason: "aux_id out of range 1..=15 (ETSI TS 102 773 §5.2.2)",
52            });
53        }
54
55        // rfu: byte 1 [3:0] + byte 2 [7:0] = 12 bits
56        let rfu = ((bytes[1] & 0x0F) as u16) << 8 | (bytes[2] as u16);
57        if rfu != 0 {
58            return Err(crate::Error::ReservedBitsViolation {
59                field: "byte 1 [3:0] + byte 2",
60                reason: "12-bit RFU must be zero (ETSI TS 102 773 §5.2.2)",
61            });
62        }
63
64        Ok(AuxIqPayload {
65            frame_idx,
66            aux_id,
67            aux_stream_data: &bytes[3..],
68        })
69    }
70}
71
72impl<'a> crate::traits::PayloadDef<'a> for AuxIqPayload<'a> {
73    const PACKET_TYPE: u8 = 0x01;
74    const NAME: &'static str = "AUX_IQ";
75}
76
77impl Serialize for AuxIqPayload<'_> {
78    type Error = crate::error::Error;
79
80    fn serialized_len(&self) -> usize {
81        3 + self.aux_stream_data.len()
82    }
83
84    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize, crate::error::Error> {
85        if buf.len() < self.serialized_len() {
86            return Err(crate::Error::OutputBufferTooSmall {
87                need: self.serialized_len(),
88                have: buf.len(),
89            });
90        }
91
92        if !(AUX_ID_MIN..=AUX_ID_MAX).contains(&self.aux_id) {
93            return Err(crate::Error::ReservedBitsViolation {
94                field: "aux_id",
95                reason: "aux_id out of range 1..=15 (ETSI TS 102 773 §5.2.2)",
96            });
97        }
98
99        buf[0] = self.frame_idx;
100        buf[1] = (self.aux_id & 0x0F) << 4; // lower 4 bits of aux_id, upper 4 of header
101        buf[2] = 0; // rfu = 0
102        buf[3..3 + self.aux_stream_data.len()].copy_from_slice(self.aux_stream_data);
103
104        Ok(self.serialized_len())
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn parse_extracts_frame_idx_and_aux_id() {
114        let buf = [0x42u8, 0x70, 0x00, 0xCA, 0xFE];
115        let result = AuxIqPayload::parse(&buf).unwrap();
116        assert_eq!(result.frame_idx, 0x42);
117        assert_eq!(result.aux_id, 0x07);
118    }
119
120    #[test]
121    fn parse_rejects_aux_id_zero() {
122        let buf = [0x00u8, 0x00, 0x00, 0xCA];
123        let result = AuxIqPayload::parse(&buf);
124        assert!(result.is_err());
125    }
126
127    #[test]
128    fn parse_rejects_aux_id_above_f() {
129        let _buf = [0x00u8, 0xF0, 0x00, 0xCA];
130        // aux_id = (0xF0 >> 4) & 0x0F = 0x0F → valid max
131        // Actually this IS valid. Let's test truly invalid:
132        let buf2 = [0x00u8, 0x01, 0x00, 0xCA];
133        // aux_id = (0x01 >> 4) & 0x0F = 0x00 → invalid
134        let result2 = AuxIqPayload::parse(&buf2);
135        assert!(result2.is_err());
136    }
137
138    #[test]
139    fn parse_rejects_nonzero_rfu_bits() {
140        let buf = [0x00u8, 0x1F, 0xFF, 0xCA];
141        // rfu = 0x0F_F0 != 0 → reject
142        let result = AuxIqPayload::parse(&buf);
143        assert!(result.is_err());
144    }
145
146    #[test]
147    fn parse_preserves_raw_aux_stream_data_bytes() {
148        let data: Vec<u8> = (0..50).collect();
149        let mut buf = vec![0x01u8, 0x50, 0x00];
150        buf.extend_from_slice(&data);
151        let result = AuxIqPayload::parse(&buf).unwrap();
152        assert_eq!(result.aux_stream_data, data.as_slice());
153    }
154
155    #[test]
156    fn parse_rejects_buffer_shorter_than_3() {
157        assert!(AuxIqPayload::parse(&[0x00]).is_err());
158        assert!(AuxIqPayload::parse(&[]).is_err());
159    }
160
161    #[test]
162    fn serialize_round_trip() {
163        let orig = AuxIqPayload {
164            frame_idx: 0xAB,
165            aux_id: 0x05,
166            aux_stream_data: &[0x12, 0x34, 0x56, 0x78],
167        };
168        let mut buf = vec![0u8; orig.serialized_len()];
169        orig.serialize_into(&mut buf).unwrap();
170        let parsed = AuxIqPayload::parse(&buf).unwrap();
171        assert_eq!(orig, parsed);
172    }
173
174    #[test]
175    fn serialize_rejects_invalid_aux_id() {
176        let payload = AuxIqPayload {
177            frame_idx: 0x00,
178            aux_id: 0x00, // must be 1..=15
179            aux_stream_data: &[],
180        };
181        let mut buf = [0u8; 3];
182        assert!(payload.serialize_into(&mut buf).is_err());
183    }
184
185    #[test]
186    fn serialize_zeros_rfu_byte() {
187        let payload = AuxIqPayload {
188            frame_idx: 0x11,
189            aux_id: 0x0A,
190            aux_stream_data: &[],
191        };
192        let mut buf = [0xFFu8; 3];
193        payload.serialize_into(&mut buf).unwrap();
194        assert_eq!(buf[1] & 0x0F, 0x00);
195        assert_eq!(buf[2], 0x00);
196    }
197}