Skip to main content

dvb_t2mi/payload/
fef_iq.rs

1//! T2-MI payload type 0x31: FEF part — I/Q data — §5.2.10.
2
3use std::fmt;
4
5use dvb_common::{Parse, Serialize};
6
7use super::fef_null::S1Field;
8
9/// FEF part: I/Q data payload (type 0x31) per ETSI TS 102 773 §5.2.10.
10#[derive(Debug, Clone, PartialEq, Eq)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize))]
12#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
13pub struct FefIqPayload<'a> {
14    /// FEF index within super-frame.
15    pub fef_idx: u8,
16    /// S1 field per EN 302 755 §7.2.1.
17    pub s1_field: S1Field,
18    /// S2 field per EN 302 755 §7.2.1.
19    pub s2_field: u8,
20    /// Complex time-domain samples: 12-bit two's complement I, then 12-bit Q.
21    pub fef_part_data: &'a [u8],
22}
23
24const FEF_IQ_HEADER_LEN: usize = 3;
25
26impl<'a> Parse<'a> for FefIqPayload<'a> {
27    type Error = crate::error::Error;
28
29    fn parse(bytes: &'a [u8]) -> Result<Self, crate::error::Error> {
30        if bytes.len() < FEF_IQ_HEADER_LEN {
31            return Err(crate::Error::BufferTooShort {
32                need: FEF_IQ_HEADER_LEN,
33                have: bytes.len(),
34                what: "FefIqPayload header",
35            });
36        }
37
38        // Layout (Figure 13): fef_idx(8) | rfu(9) | s1_field(3) | s2_field(4) | data.
39        // rfu spans all of byte 1 plus the top bit of byte 2.
40        if bytes[1] != 0 || bytes[2] & 0x80 != 0 {
41            return Err(crate::Error::ReservedBitsViolation {
42                field: "9-bit rfu",
43                reason: "Must be zero (ETSI TS 102 773 §5.2.10)",
44            });
45        }
46
47        Ok(FefIqPayload {
48            fef_idx: bytes[0],
49            // byte 2: rfu(1) | s1_field(3) [6:4] | s2_field(4) [3:0]
50            s1_field: S1Field::try_from((bytes[2] >> 4) & 0x07)?,
51            s2_field: bytes[2] & 0x0F,
52            fef_part_data: &bytes[FEF_IQ_HEADER_LEN..],
53        })
54    }
55}
56
57impl<'a> crate::traits::PayloadDef<'a> for FefIqPayload<'a> {
58    const PACKET_TYPE: u8 = 0x31;
59    const NAME: &'static str = "FEF_IQ";
60}
61
62impl Serialize for FefIqPayload<'_> {
63    type Error = crate::error::Error;
64
65    fn serialized_len(&self) -> usize {
66        FEF_IQ_HEADER_LEN + self.fef_part_data.len()
67    }
68
69    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize, crate::error::Error> {
70        if buf.len() < self.serialized_len() {
71            return Err(crate::Error::OutputBufferTooSmall {
72                need: self.serialized_len(),
73                have: buf.len(),
74            });
75        }
76
77        if self.s2_field > 0x0F {
78            return Err(crate::Error::ReservedBitsViolation {
79                field: "s2_field",
80                reason: "Must fit in 4 bits",
81            });
82        }
83
84        buf[0] = self.fef_idx;
85        buf[1] = 0; // rfu (high 8 of the 9 reserved bits)
86        buf[2] = ((u8::from(self.s1_field) & 0x07) << 4) | (self.s2_field & 0x0F);
87
88        if !self.fef_part_data.is_empty() {
89            buf[FEF_IQ_HEADER_LEN..FEF_IQ_HEADER_LEN + self.fef_part_data.len()]
90                .copy_from_slice(self.fef_part_data);
91        }
92
93        Ok(self.serialized_len())
94    }
95}
96
97impl fmt::Display for FefIqPayload<'_> {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        write!(
100            f,
101            "FEF I/Q {{ fef_idx: {}, s1: {:?}, s2: {:04b}, data_len: {} }}",
102            self.fef_idx,
103            self.s1_field,
104            self.s2_field,
105            self.fef_part_data.len()
106        )
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn parse_extracts_fields_and_data() {
116        let data = [0xDE, 0xAD];
117        // fef_idx=5, rfu=0, byte2 = s1(2)<<4 | s2(0x0A) = 0x2A
118        let mut buf = vec![0x05u8, 0x00, 0x2A];
119        buf.extend_from_slice(&data);
120
121        let result = FefIqPayload::parse(&buf).unwrap();
122        assert_eq!(result.fef_idx, 5);
123        assert_eq!(result.s1_field, S1Field::V2);
124        assert_eq!(result.s2_field, 0x0A);
125        assert_eq!(result.fef_part_data, &data[..]);
126    }
127
128    #[test]
129    fn parse_rejects_nonzero_rfu() {
130        let buf = [0x00u8, 0x1F, 0x00];
131        assert!(FefIqPayload::parse(&buf).is_err());
132    }
133
134    #[test]
135    fn serialize_round_trip() {
136        let orig = FefIqPayload {
137            fef_idx: 7,
138            s1_field: S1Field::V3,
139            s2_field: 0x0B,
140            fef_part_data: &[0xCA, 0xFE],
141        };
142        let mut buf = vec![0u8; orig.serialized_len()];
143        orig.serialize_into(&mut buf).unwrap();
144        let parsed = FefIqPayload::parse(&buf).unwrap();
145        assert_eq!(orig, parsed);
146    }
147}