Skip to main content

dvb_si/tables/
dit.rs

1//! Discontinuity Information Table — ETSI EN 300 468 §7.1.2.
2//!
3//! Carried on PID `0x001E` with `table_id = 0x7E`, only in partial transport
4//! streams (e.g. a recording). A short-form section whose body is a single
5//! byte: `transition_flag(1) | reserved_future_use(7)`. No CRC.
6
7use crate::error::{Error, Result};
8use crate::traits::Table;
9use dvb_common::{Parse, Serialize};
10
11/// table_id for the Discontinuity Information Table.
12pub const TABLE_ID: u8 = 0x7E;
13/// Well-known PID on which the DIT is carried.
14pub const PID: u16 = 0x001E;
15
16const HEADER_LEN: usize = 3;
17/// Body length: one byte holding `transition_flag` + reserved bits (§7.1.2).
18const BODY_LEN: usize = 1;
19
20/// Discontinuity Information Table (§7.1.2, Table 163).
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize))]
23pub struct Dit {
24    /// When set, a discontinuity in the transport stream occurs at this point.
25    pub transition_flag: bool,
26}
27
28impl<'a> Parse<'a> for Dit {
29    type Error = crate::error::Error;
30
31    fn parse(bytes: &'a [u8]) -> Result<Self> {
32        let min_len = HEADER_LEN + BODY_LEN;
33        if bytes.len() < min_len {
34            return Err(Error::BufferTooShort {
35                need: min_len,
36                have: bytes.len(),
37                what: "Dit",
38            });
39        }
40        if bytes[0] != TABLE_ID {
41            return Err(Error::UnexpectedTableId {
42                table_id: bytes[0],
43                what: "Dit",
44                expected: &[TABLE_ID],
45            });
46        }
47        let section_length = ((bytes[1] & 0x0F) as usize) << 8 | bytes[2] as usize;
48        if section_length != BODY_LEN {
49            return Err(Error::SectionLengthOverflow {
50                declared: section_length,
51                available: BODY_LEN,
52            });
53        }
54        // transition_flag is the top bit of the body byte; rest is reserved.
55        let transition_flag = bytes[3] & 0x80 != 0;
56        Ok(Dit { transition_flag })
57    }
58}
59
60impl Serialize for Dit {
61    type Error = crate::error::Error;
62
63    fn serialized_len(&self) -> usize {
64        HEADER_LEN + BODY_LEN
65    }
66
67    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
68        let len = self.serialized_len();
69        if buf.len() < len {
70            return Err(Error::OutputBufferTooSmall {
71                need: len,
72                have: buf.len(),
73            });
74        }
75        buf[0] = TABLE_ID;
76        // section_syntax_indicator=0 (short form), reserved_future_use=1,
77        // reserved=11, section_length high nibble.
78        buf[1] = 0x70 | ((BODY_LEN >> 8) as u8 & 0x0F);
79        buf[2] = (BODY_LEN & 0xFF) as u8;
80        // transition_flag in bit 7; remaining 7 bits reserved (set to 1).
81        buf[3] = (u8::from(self.transition_flag) << 7) | 0x7F;
82        Ok(len)
83    }
84}
85
86impl<'a> Table<'a> for Dit {
87    const TABLE_ID: u8 = TABLE_ID;
88    const PID: u16 = PID;
89}
90
91impl<'a> crate::traits::TableDef<'a> for Dit {
92    const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
93    const NAME: &'static str = "DISCONTINUITY_INFORMATION";
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn parse_transition_flag_set() {
102        // section_length=1, body byte 0x80 → transition_flag=1
103        let bytes = [TABLE_ID, 0x70, 0x01, 0x80];
104        let dit = Dit::parse(&bytes).unwrap();
105        assert!(dit.transition_flag);
106    }
107
108    #[test]
109    fn parse_transition_flag_clear() {
110        let bytes = [TABLE_ID, 0x70, 0x01, 0x7F];
111        let dit = Dit::parse(&bytes).unwrap();
112        assert!(!dit.transition_flag);
113    }
114
115    #[test]
116    fn parse_rejects_wrong_tag() {
117        let bytes = [0x7F, 0x70, 0x01, 0x80];
118        assert!(matches!(
119            Dit::parse(&bytes).unwrap_err(),
120            Error::UnexpectedTableId { table_id: 0x7F, .. }
121        ));
122    }
123
124    #[test]
125    fn parse_rejects_wrong_section_length() {
126        let bytes = [TABLE_ID, 0x70, 0x02, 0x80, 0x00];
127        assert!(matches!(
128            Dit::parse(&bytes).unwrap_err(),
129            Error::SectionLengthOverflow { .. }
130        ));
131    }
132
133    #[test]
134    fn parse_rejects_short_buffer() {
135        let bytes = [TABLE_ID, 0x70];
136        assert!(matches!(
137            Dit::parse(&bytes).unwrap_err(),
138            Error::BufferTooShort { .. }
139        ));
140    }
141
142    #[test]
143    fn serialize_round_trip_set() {
144        let dit = Dit {
145            transition_flag: true,
146        };
147        let mut buf = vec![0u8; dit.serialized_len()];
148        dit.serialize_into(&mut buf).unwrap();
149        assert_eq!(buf, [TABLE_ID, 0x70, 0x01, 0xFF]);
150        assert_eq!(Dit::parse(&buf).unwrap(), dit);
151    }
152
153    #[test]
154    fn serialize_round_trip_clear() {
155        let dit = Dit {
156            transition_flag: false,
157        };
158        let mut buf = vec![0u8; dit.serialized_len()];
159        dit.serialize_into(&mut buf).unwrap();
160        assert_eq!(buf, [TABLE_ID, 0x70, 0x01, 0x7F]);
161        assert_eq!(Dit::parse(&buf).unwrap(), dit);
162    }
163
164    #[test]
165    fn serialize_into_too_small_buffer() {
166        let dit = Dit {
167            transition_flag: false,
168        };
169        let mut buf = [0u8; 3];
170        assert!(matches!(
171            dit.serialize_into(&mut buf).unwrap_err(),
172            Error::OutputBufferTooSmall { .. }
173        ));
174    }
175
176    #[test]
177    fn serialized_len_is_four() {
178        assert_eq!(
179            Dit {
180                transition_flag: false
181            }
182            .serialized_len(),
183            4
184        );
185    }
186
187    #[test]
188    fn serde_json_serializes_fields() {
189        // Serialize-only: assert the emitted JSON carries the field.
190        let dit = Dit {
191            transition_flag: true,
192        };
193        let v = serde_json::to_value(dit).unwrap();
194        assert_eq!(v["transition_flag"], true);
195    }
196
197    #[test]
198    fn table_trait_constants() {
199        assert_eq!(Dit::TABLE_ID, 0x7E);
200        assert_eq!(Dit::PID, 0x001E);
201    }
202}