1use crate::error::{Error, Result};
8use dvb_common::{Parse, Serialize};
9
10pub const TABLE_ID: u8 = 0x00;
12pub const PID: u16 = 0x0000;
14pub const PROGRAM_NUMBER_NIT: u16 = 0x0000;
16
17const MIN_HEADER_LEN: usize = 3;
18const EXTENSION_HEADER_LEN: usize = 5;
19const CRC_LEN: usize = 4;
20const MIN_SECTION_LEN: usize = MIN_HEADER_LEN + EXTENSION_HEADER_LEN + CRC_LEN;
21const ENTRY_LEN: usize = 4;
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize))]
26pub struct PatEntry {
27 pub program_number: u16,
29 pub pid: u16,
31}
32
33#[derive(Debug, Clone, Default, PartialEq, Eq)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize))]
36pub struct PatSection {
37 pub transport_stream_id: u16,
39 pub version_number: u8,
41 pub current_next_indicator: bool,
43 pub section_number: u8,
45 pub last_section_number: u8,
47 pub entries: Vec<PatEntry>,
49}
50
51impl<'a> Parse<'a> for PatSection {
52 type Error = crate::error::Error;
53 fn parse(bytes: &'a [u8]) -> Result<Self> {
54 if bytes.len() < MIN_HEADER_LEN + EXTENSION_HEADER_LEN + CRC_LEN {
55 return Err(Error::BufferTooShort {
56 need: MIN_HEADER_LEN + EXTENSION_HEADER_LEN + CRC_LEN,
57 have: bytes.len(),
58 what: "PatSection",
59 });
60 }
61
62 if bytes[0] != TABLE_ID {
63 return Err(Error::UnexpectedTableId {
64 table_id: bytes[0],
65 what: "PatSection",
66 expected: &[TABLE_ID],
67 });
68 }
69
70 let section_length = ((bytes[1] & 0x0F) as u16) << 8 | bytes[2] as u16;
71 let total = super::check_section_length(
72 bytes.len(),
73 MIN_HEADER_LEN,
74 section_length as usize,
75 MIN_SECTION_LEN,
76 )?;
77
78 let transport_stream_id = u16::from_be_bytes([bytes[3], bytes[4]]);
79 let version_number = (bytes[5] >> 1) & 0x1F;
80 let current_next_indicator = (bytes[5] & 0x01) != 0;
81 let section_number = bytes[6];
82 let last_section_number = bytes[7];
83
84 let end = total - CRC_LEN;
85 let mut entries = Vec::new();
86 let mut pos = 8;
87 while pos < end {
88 if pos + ENTRY_LEN > end {
89 return Err(Error::BufferTooShort {
90 need: ENTRY_LEN,
91 have: end - pos,
92 what: "PatSection trailing entry bytes",
93 });
94 }
95 let chunk = &bytes[pos..pos + ENTRY_LEN];
96 let program_number = u16::from_be_bytes([chunk[0], chunk[1]]);
97 let pid = (((chunk[2] & 0x1F) as u16) << 8) | chunk[3] as u16;
98 entries.push(PatEntry {
99 program_number,
100 pid,
101 });
102 pos += ENTRY_LEN;
103 }
104
105 Ok(PatSection {
106 transport_stream_id,
107 version_number,
108 current_next_indicator,
109 section_number,
110 last_section_number,
111 entries,
112 })
113 }
114}
115
116impl Serialize for PatSection {
117 type Error = crate::error::Error;
118 fn serialized_len(&self) -> usize {
119 MIN_HEADER_LEN + EXTENSION_HEADER_LEN + self.entries.len() * ENTRY_LEN + CRC_LEN
120 }
121
122 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
123 let len = self.serialized_len();
124 if buf.len() < len {
125 return Err(Error::OutputBufferTooSmall {
126 need: len,
127 have: buf.len(),
128 });
129 }
130
131 let section_length: u16 =
132 (EXTENSION_HEADER_LEN + self.entries.len() * ENTRY_LEN + CRC_LEN) as u16;
133
134 buf[0] = TABLE_ID;
135 buf[1] = super::SECTION_B1_FLAGS_PSI | ((section_length >> 8) as u8 & 0x0F);
136 buf[2] = (section_length & 0xFF) as u8;
137 buf[3..5].copy_from_slice(&self.transport_stream_id.to_be_bytes());
138 buf[5] = 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
139 buf[6] = self.section_number;
140 buf[7] = self.last_section_number;
141
142 let mut pos = 8;
143 for entry in &self.entries {
144 buf[pos..pos + 2].copy_from_slice(&entry.program_number.to_be_bytes());
145 buf[pos + 2] = 0xE0 | ((entry.pid >> 8) as u8 & 0x1F);
146 buf[pos + 3] = (entry.pid & 0xFF) as u8;
147 pos += ENTRY_LEN;
148 }
149
150 let crc_pos = len - CRC_LEN;
151 let crc = dvb_common::crc32_mpeg2::compute(&buf[..crc_pos]);
152 buf[crc_pos..len].copy_from_slice(&crc.to_be_bytes());
153
154 Ok(len)
155 }
156}
157impl<'a> crate::traits::TableDef<'a> for PatSection {
158 const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
159 const NAME: &'static str = "PROGRAM_ASSOCIATION";
160}
161
162impl PatSection {
163 pub fn programmes(&self) -> impl Iterator<Item = &PatEntry> {
165 self.entries
166 .iter()
167 .filter(|e| e.program_number != PROGRAM_NUMBER_NIT)
168 }
169
170 pub fn nit_pid(&self) -> Option<u16> {
172 self.entries
173 .iter()
174 .find(|e| e.program_number == PROGRAM_NUMBER_NIT)
175 .map(|e| e.pid)
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 fn build_pat(tsid: u16, version: u8, entries: &[(u16, u16)]) -> Vec<u8> {
185 let section_length: u16 =
186 (EXTENSION_HEADER_LEN + entries.len() * ENTRY_LEN + CRC_LEN) as u16;
187 let mut v = Vec::new();
188 v.push(TABLE_ID);
189 v.push(super::super::SECTION_B1_FLAGS_PSI | ((section_length >> 8) as u8 & 0x0F));
190 v.push((section_length & 0xFF) as u8);
191 v.extend_from_slice(&tsid.to_be_bytes());
192 v.push(0xC0 | ((version & 0x1F) << 1) | 0x01); v.push(0x00); v.push(0x00); for &(pn, pid) in entries {
196 v.extend_from_slice(&pn.to_be_bytes());
197 v.push(0xE0 | ((pid >> 8) as u8 & 0x1F));
198 v.push((pid & 0xFF) as u8);
199 }
200 v.extend_from_slice(&[0, 0, 0, 0]); v
202 }
203
204 #[test]
205 fn parse_empty_pat_zero_programs() {
206 let bytes = build_pat(0x1234, 5, &[]);
207 let pat = PatSection::parse(&bytes).expect("parse");
208 assert_eq!(pat.transport_stream_id, 0x1234);
209 assert_eq!(pat.version_number, 5);
210 assert!(pat.current_next_indicator);
211 assert_eq!(pat.section_number, 0);
212 assert_eq!(pat.last_section_number, 0);
213 assert_eq!(pat.entries.len(), 0);
214 }
215
216 #[test]
217 fn parse_single_program_extracts_pmt_pid() {
218 let bytes = build_pat(1, 0, &[(42, 0x1234)]);
219 let pat = PatSection::parse(&bytes).unwrap();
220 assert_eq!(pat.entries.len(), 1);
221 assert_eq!(pat.entries[0].program_number, 42);
222 assert_eq!(pat.entries[0].pid, 0x1234);
223 }
224
225 #[test]
226 fn parse_many_programs_preserves_order() {
227 let entries: Vec<(u16, u16)> = (1..=10).map(|i| (i, 0x1000 + i)).collect();
228 let bytes = build_pat(1, 0, &entries);
229 let pat = PatSection::parse(&bytes).unwrap();
230 assert_eq!(pat.entries.len(), 10);
231 for (i, e) in pat.entries.iter().enumerate() {
232 assert_eq!(e.program_number, (i + 1) as u16);
233 assert_eq!(e.pid, 0x1000 + (i + 1) as u16);
234 }
235 }
236
237 #[test]
238 fn parse_rejects_wrong_table_id() {
239 let mut bytes = build_pat(1, 0, &[]);
240 bytes[0] = 0x02; let err = PatSection::parse(&bytes).unwrap_err();
242 assert!(matches!(
243 err,
244 Error::UnexpectedTableId { table_id: 0x02, .. }
245 ));
246 }
247
248 #[test]
249 fn parse_rejects_short_buffer() {
250 let err = PatSection::parse(&[0x00, 0x00]).unwrap_err();
251 assert!(matches!(err, Error::BufferTooShort { .. }));
252 }
253
254 #[test]
255 fn serialize_round_trip_empty() {
256 let pat = PatSection {
257 transport_stream_id: 0x0001,
258 version_number: 0,
259 current_next_indicator: true,
260 section_number: 0,
261 last_section_number: 0,
262 entries: vec![],
263 };
264 let mut buf = vec![0u8; pat.serialized_len()];
265 pat.serialize_into(&mut buf).expect("serialize");
266 let reparsed = PatSection::parse(&buf).expect("reparse");
267 assert_eq!(pat, reparsed);
268 }
269
270 #[test]
271 fn serialize_round_trip_many_programs() {
272 let entries: Vec<PatEntry> = (1..=5)
273 .map(|i| PatEntry {
274 program_number: i,
275 pid: 0x1000 + i,
276 })
277 .collect();
278 let pat = PatSection {
279 transport_stream_id: 0xABCD,
280 version_number: 3,
281 current_next_indicator: true,
282 section_number: 0,
283 last_section_number: 0,
284 entries,
285 };
286 let mut buf = vec![0u8; pat.serialized_len()];
287 pat.serialize_into(&mut buf).unwrap();
288 let reparsed = PatSection::parse(&buf).unwrap();
289 assert_eq!(pat, reparsed);
290 }
291
292 #[test]
293 fn parse_rejects_zero_section_length() {
294 let mut buf = vec![0u8; 64];
295 buf[0] = TABLE_ID;
296 buf[1] = 0xB0;
297 buf[2] = 0x00;
298 for b in &mut buf[3..] {
299 *b = 0xFF;
300 }
301 assert!(matches!(
302 PatSection::parse(&buf).unwrap_err(),
303 Error::SectionLengthOverflow { .. }
304 ));
305 }
306
307 #[test]
308 fn network_pid_entry_identified_by_program_number_0() {
309 let bytes = build_pat(1, 0, &[(0, 0x0010), (1, 0x0100)]);
310 let pat = PatSection::parse(&bytes).unwrap();
311 assert_eq!(pat.nit_pid(), Some(0x0010));
312 assert_eq!(pat.programmes().count(), 1);
313 assert_eq!(pat.programmes().next().unwrap().program_number, 1);
314 }
315
316 #[test]
317 fn parse_rejects_trailing_slack_bytes() {
318 let mut bytes = build_pat(1, 0, &[(1, 0x100)]);
319 let sl = (bytes.len() - MIN_HEADER_LEN) as u16;
320 bytes[1] = (bytes[1] & 0xF0) | (((sl + 2) >> 8) as u8 & 0x0F);
321 bytes[2] = ((sl + 2) & 0xFF) as u8;
322 bytes.extend_from_slice(&[0xFF, 0xFF]);
323 let crc_pos = bytes.len();
324 let crc = dvb_common::crc32_mpeg2::compute(&bytes[..crc_pos - CRC_LEN]);
325 bytes[crc_pos - CRC_LEN..crc_pos].copy_from_slice(&crc.to_be_bytes());
326 assert!(matches!(
327 PatSection::parse(&bytes).unwrap_err(),
328 Error::BufferTooShort {
329 what: "PatSection trailing entry bytes",
330 ..
331 }
332 ));
333 }
334}