Skip to main content

dvb_t2mi/payload/
p2_bias.rs

1//! T2-MI payload type 0x12: P2 bias balancing cells — §5.2.6.
2
3use dvb_common::{Parse, Serialize};
4
5/// P2 bias balancing payload (type 0x12), ETSI TS 102 773 §5.2.6 Figure 9.
6///
7/// Layout: `frame_idx(8) | rfu(17) | num_active_bias_cells_per_p2(15)` = 40 bits
8/// = 5 bytes.
9#[derive(Debug, Clone, PartialEq, Eq)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize))]
11pub struct P2BiasPayload {
12    /// FRAME_IDX of the T2 frame carrying the bias balancing cells.
13    pub frame_idx: u8,
14    /// Number of active bias balancing cells per P2 symbol (15-bit).
15    pub num_active_bias_cells_per_p2: u16,
16}
17
18const P2_BIAS_HEADER_LEN: usize = 5;
19
20impl<'a> Parse<'a> for P2BiasPayload {
21    type Error = crate::error::Error;
22
23    fn parse(bytes: &'a [u8]) -> Result<Self, crate::error::Error> {
24        if bytes.len() < P2_BIAS_HEADER_LEN {
25            return Err(crate::Error::BufferTooShort {
26                need: P2_BIAS_HEADER_LEN,
27                have: bytes.len(),
28                what: "P2BiasPayload header",
29            });
30        }
31
32        // rfu is 17 bits: all of bytes[1], bytes[2], and the top bit of bytes[3].
33        if bytes[1] != 0 || bytes[2] != 0 || (bytes[3] & 0x80) != 0 {
34            return Err(crate::Error::ReservedBitsViolation {
35                field: "17-bit RFU",
36                reason: "Must be zero (ETSI TS 102 773 §5.2.6)",
37            });
38        }
39
40        // num_active_bias_cells_per_p2 is 15 bits: bottom 7 of bytes[3] + bytes[4].
41        let num_active = ((bytes[3] as u16 & 0x7F) << 8) | (bytes[4] as u16);
42
43        Ok(P2BiasPayload {
44            frame_idx: bytes[0],
45            num_active_bias_cells_per_p2: num_active,
46        })
47    }
48}
49
50impl<'a> crate::traits::PayloadDef<'a> for P2BiasPayload {
51    const PACKET_TYPE: u8 = 0x12;
52    const NAME: &'static str = "P2_BIAS";
53}
54
55impl Serialize for P2BiasPayload {
56    type Error = crate::error::Error;
57
58    fn serialized_len(&self) -> usize {
59        P2_BIAS_HEADER_LEN
60    }
61
62    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize, crate::error::Error> {
63        if buf.len() < self.serialized_len() {
64            return Err(crate::Error::OutputBufferTooSmall {
65                need: self.serialized_len(),
66                have: buf.len(),
67            });
68        }
69
70        if self.num_active_bias_cells_per_p2 > 0x7FFF {
71            return Err(crate::Error::ReservedBitsViolation {
72                field: "num_active_bias_cells_per_p2",
73                reason: "Must fit in 15 bits",
74            });
75        }
76
77        buf[0] = self.frame_idx;
78        buf[1] = 0; // rfu
79        buf[2] = 0; // rfu
80        buf[3] = ((self.num_active_bias_cells_per_p2 >> 8) as u8) & 0x7F;
81        buf[4] = (self.num_active_bias_cells_per_p2 & 0xFF) as u8;
82
83        Ok(self.serialized_len())
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn parse_extracts_fields() {
93        // frame_idx=0x42, rfu=0, num_active=0x0F
94        let buf = [0x42u8, 0x00, 0x00, 0x00, 0x0F];
95        let result = P2BiasPayload::parse(&buf).unwrap();
96        assert_eq!(result.frame_idx, 0x42);
97        assert_eq!(result.num_active_bias_cells_per_p2, 0x0F);
98    }
99
100    #[test]
101    fn parse_extracts_15bit_max() {
102        // num_active = 0x7FFF (all 15 bits): bytes[3]=0x7F, bytes[4]=0xFF
103        let buf = [0x00u8, 0x00, 0x00, 0x7F, 0xFF];
104        let result = P2BiasPayload::parse(&buf).unwrap();
105        assert_eq!(result.num_active_bias_cells_per_p2, 0x7FFF);
106    }
107
108    #[test]
109    fn parse_rejects_nonzero_rfu() {
110        // top bit of bytes[3] is part of the RFU
111        let buf = [0x00u8, 0x00, 0x00, 0x80, 0x00];
112        assert!(P2BiasPayload::parse(&buf).is_err());
113        // a byte in the middle of the RFU span
114        assert!(P2BiasPayload::parse(&[0x00u8, 0x01, 0x00, 0x00, 0x00]).is_err());
115    }
116
117    #[test]
118    fn parse_rejects_short_buffer() {
119        assert!(P2BiasPayload::parse(&[0x00, 0x00, 0x00, 0x00]).is_err());
120    }
121
122    #[test]
123    fn serialize_round_trip() {
124        let orig = P2BiasPayload {
125            frame_idx: 5,
126            num_active_bias_cells_per_p2: 3000,
127        };
128        let mut buf = [0u8; 5];
129        orig.serialize_into(&mut buf).unwrap();
130        let parsed = P2BiasPayload::parse(&buf).unwrap();
131        assert_eq!(orig, parsed);
132    }
133
134    #[test]
135    fn serialize_rejects_too_small_buffer() {
136        let payload = P2BiasPayload {
137            frame_idx: 0,
138            num_active_bias_cells_per_p2: 0,
139        };
140        let mut buf = [0u8; 4];
141        assert!(payload.serialize_into(&mut buf).is_err());
142    }
143}