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