1use super::*;
3use alloc::vec::Vec;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize))]
11#[non_exhaustive]
12pub enum ChangeType {
13 MessageOnly,
15 MinorDefault,
17 MinorMultiplexRemoved,
19 MinorServiceChanged,
21 MinorReserved(u8),
23 MajorDefault,
25 MajorFrequencyChanged,
27 MajorCoverageChanged,
29 MajorMultiplexAdded,
31 MajorReserved(u8),
33}
34
35impl ChangeType {
36 #[must_use]
37 pub fn from_u8(v: u8) -> Self {
40 match v & 0x0F {
41 0 => Self::MessageOnly,
42 1 => Self::MinorDefault,
43 2 => Self::MinorMultiplexRemoved,
44 3 => Self::MinorServiceChanged,
45 v @ 4..=7 => Self::MinorReserved(v),
46 8 => Self::MajorDefault,
47 9 => Self::MajorFrequencyChanged,
48 10 => Self::MajorCoverageChanged,
49 11 => Self::MajorMultiplexAdded,
50 v => Self::MajorReserved(v),
51 }
52 }
53
54 #[must_use]
55 pub const fn to_u8(self) -> u8 {
57 match self {
58 Self::MessageOnly => 0,
59 Self::MinorDefault => 1,
60 Self::MinorMultiplexRemoved => 2,
61 Self::MinorServiceChanged => 3,
62 Self::MinorReserved(v) | Self::MajorReserved(v) => v,
63 Self::MajorDefault => 8,
64 Self::MajorFrequencyChanged => 9,
65 Self::MajorCoverageChanged => 10,
66 Self::MajorMultiplexAdded => 11,
67 }
68 }
69
70 #[must_use]
71 pub fn name(self) -> &'static str {
73 match self {
74 Self::MessageOnly => "message only",
75 Self::MinorDefault => "minor - default",
76 Self::MinorMultiplexRemoved => "minor - multiplex removed",
77 Self::MinorServiceChanged => "minor - service changed",
78 Self::MinorReserved(_) => "reserved (minor)",
79 Self::MajorDefault => "major - default",
80 Self::MajorFrequencyChanged => "major - multiplex frequency changed",
81 Self::MajorCoverageChanged => "major - multiplex coverage changed",
82 Self::MajorMultiplexAdded => "major - multiplex added",
83 Self::MajorReserved(_) => "reserved (major)",
84 }
85 }
86}
87dvb_common::impl_spec_display!(ChangeType, MinorReserved, MajorReserved);
88
89const CELL_HEADER_LEN: usize = 2; const LOOP_LENGTH_LEN: usize = 1; const CHANGE_BASE_LEN: usize = 12; const INVARIANT_TS_LEN: usize = 4; #[derive(Debug, Clone, PartialEq, Eq)]
96#[cfg_attr(feature = "serde", derive(serde::Serialize))]
97pub struct NetworkChangeNotify {
98 pub cells: Vec<NetworkChangeCell>,
100}
101
102#[derive(Debug, Clone, PartialEq, Eq)]
104#[cfg_attr(feature = "serde", derive(serde::Serialize))]
105pub struct NetworkChangeCell {
106 pub cell_id: u16,
108 pub changes: Vec<NetworkChange>,
110}
111
112#[derive(Debug, Clone, PartialEq, Eq)]
114#[cfg_attr(feature = "serde", derive(serde::Serialize))]
115pub struct NetworkChange {
116 pub network_change_id: u8,
118 pub network_change_version: u8,
120 pub start_time_of_change: u64,
122 pub change_duration: u32,
124 pub receiver_category: u8,
126 pub change_type: ChangeType,
128 pub message_id: u8,
130 pub invariant_ts: Option<InvariantTs>,
132}
133
134#[derive(Debug, Clone, PartialEq, Eq)]
136#[cfg_attr(feature = "serde", derive(serde::Serialize))]
137pub struct InvariantTs {
138 pub tsid: u16,
140 pub onid: u16,
142}
143
144impl<'a> ExtensionBodyDef<'a> for NetworkChangeNotify {
145 const TAG_EXTENSION: u8 = 0x07;
146 const NAME: &'static str = "NETWORK_CHANGE_NOTIFY";
147}
148
149impl NetworkChange {
150 #[cfg(feature = "chrono")]
154 #[must_use]
155 pub fn start_time_of_change_utc(&self) -> Option<chrono::DateTime<chrono::Utc>> {
156 let raw = self.start_time_of_change;
157 let bytes = [
158 (raw >> 32) as u8,
159 (raw >> 24) as u8,
160 (raw >> 16) as u8,
161 (raw >> 8) as u8,
162 raw as u8,
163 ];
164 dvb_common::time::decode_mjd_bcd_utc(bytes)
165 }
166
167 #[cfg(feature = "chrono")]
170 #[must_use]
171 pub fn change_duration_secs(&self) -> Option<u32> {
172 let raw = self.change_duration;
173 let bytes = [(raw >> 16) as u8, (raw >> 8) as u8, raw as u8];
174 dvb_common::time::decode_bcd_duration(bytes).map(|d| d.as_secs() as u32)
175 }
176}
177
178fn change_serialized_len(ch: &NetworkChange) -> usize {
179 CHANGE_BASE_LEN
180 + if ch.invariant_ts.is_some() {
181 INVARIANT_TS_LEN
182 } else {
183 0
184 }
185}
186
187impl<'a> Parse<'a> for NetworkChangeNotify {
188 type Error = crate::error::Error;
189 fn parse(sel: &'a [u8]) -> Result<Self> {
190 let mut cells = Vec::new();
191 let mut pos = 0;
192 while pos < sel.len() {
193 if pos + CELL_HEADER_LEN + LOOP_LENGTH_LEN > sel.len() {
194 return Err(Error::BufferTooShort {
195 need: pos + CELL_HEADER_LEN + LOOP_LENGTH_LEN,
196 have: sel.len(),
197 what: "network_change_notify body",
198 });
199 }
200 let (cell_id_bytes, _) = sel
201 .get(pos..)
202 .and_then(|s| s.split_first_chunk::<2>())
203 .ok_or(Error::BufferTooShort {
204 need: pos + CELL_HEADER_LEN + LOOP_LENGTH_LEN,
205 have: sel.len(),
206 what: "network_change_notify body",
207 })?;
208 let cell_id = u16::from_be_bytes(*cell_id_bytes);
209 let loop_length = sel[pos + CELL_HEADER_LEN] as usize;
210 pos += CELL_HEADER_LEN + LOOP_LENGTH_LEN;
211
212 if pos + loop_length > sel.len() {
213 return Err(Error::BufferTooShort {
214 need: pos + loop_length,
215 have: sel.len(),
216 what: "network_change_notify body",
217 });
218 }
219
220 let inner_end = pos + loop_length;
221 let mut changes = Vec::new();
222 while pos < inner_end {
223 let remaining = inner_end - pos;
224 if remaining < CHANGE_BASE_LEN {
226 return Err(Error::BufferTooShort {
227 need: inner_end - remaining + CHANGE_BASE_LEN,
228 have: sel.len(),
229 what: "network_change_notify body",
230 });
231 }
232 let network_change_id = sel[pos];
233 let network_change_version = sel[pos + 1];
234 let start_time_of_change = (u64::from(sel[pos + 2]) << 32)
235 | (u64::from(sel[pos + 3]) << 24)
236 | (u64::from(sel[pos + 4]) << 16)
237 | (u64::from(sel[pos + 5]) << 8)
238 | u64::from(sel[pos + 6]);
239 let change_duration = (u32::from(sel[pos + 7]) << 16)
240 | (u32::from(sel[pos + 8]) << 8)
241 | u32::from(sel[pos + 9]);
242 let packed = sel[pos + 10];
243 let receiver_category = packed >> 5;
244 let invariant_ts_present = (packed >> 4) & 1;
245 let change_type = ChangeType::from_u8(packed & 0x0F);
246 let message_id = sel[pos + 11];
247 pos += CHANGE_BASE_LEN;
248
249 let invariant_ts = if invariant_ts_present == 1 {
250 if pos + INVARIANT_TS_LEN > inner_end {
251 return Err(Error::BufferTooShort {
252 need: pos + INVARIANT_TS_LEN,
253 have: sel.len(),
254 what: "network_change_notify body",
255 });
256 }
257 let (inv_bytes, _) = sel
258 .get(pos..)
259 .and_then(|s| s.split_first_chunk::<INVARIANT_TS_LEN>())
260 .ok_or(Error::BufferTooShort {
261 need: pos + INVARIANT_TS_LEN,
262 have: sel.len(),
263 what: "network_change_notify body",
264 })?;
265 let ts = InvariantTs {
266 tsid: u16::from_be_bytes([inv_bytes[0], inv_bytes[1]]),
267 onid: u16::from_be_bytes([inv_bytes[2], inv_bytes[3]]),
268 };
269 pos += INVARIANT_TS_LEN;
270 Some(ts)
271 } else {
272 None
273 };
274
275 changes.push(NetworkChange {
276 network_change_id,
277 network_change_version,
278 start_time_of_change,
279 change_duration,
280 receiver_category,
281 change_type,
282 message_id,
283 invariant_ts,
284 });
285 }
286
287 if pos != inner_end {
288 return Err(invalid("network_change_notify: change entry overruns loop"));
289 }
290
291 cells.push(NetworkChangeCell { cell_id, changes });
292 }
293 Ok(NetworkChangeNotify { cells })
294 }
295}
296
297impl Serialize for NetworkChangeNotify {
298 type Error = crate::error::Error;
299 fn serialized_len(&self) -> usize {
300 self.cells
301 .iter()
302 .map(|cell| {
303 CELL_HEADER_LEN
304 + LOOP_LENGTH_LEN
305 + cell
306 .changes
307 .iter()
308 .map(change_serialized_len)
309 .sum::<usize>()
310 })
311 .sum()
312 }
313 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
314 let len = self.serialized_len();
315 if buf.len() < len {
316 return Err(Error::OutputBufferTooSmall {
317 need: len,
318 have: buf.len(),
319 });
320 }
321 let mut pos = 0;
322 for cell in &self.cells {
323 buf[pos..pos + 2].copy_from_slice(&cell.cell_id.to_be_bytes());
324 pos += 2;
325 let loop_length: usize = cell.changes.iter().map(change_serialized_len).sum();
326 buf[pos] = loop_length as u8;
327 pos += 1;
328 for ch in &cell.changes {
329 buf[pos] = ch.network_change_id;
330 buf[pos + 1] = ch.network_change_version;
331 let st = ch.start_time_of_change;
332 buf[pos + 2] = (st >> 32) as u8;
333 buf[pos + 3] = (st >> 24) as u8;
334 buf[pos + 4] = (st >> 16) as u8;
335 buf[pos + 5] = (st >> 8) as u8;
336 buf[pos + 6] = st as u8;
337 let dur = ch.change_duration;
338 buf[pos + 7] = (dur >> 16) as u8;
339 buf[pos + 8] = (dur >> 8) as u8;
340 buf[pos + 9] = dur as u8;
341 let packed = ((ch.receiver_category & 0x07) << 5)
342 | ((ch.invariant_ts.is_some() as u8) << 4)
343 | (ch.change_type.to_u8() & 0x0F);
344 buf[pos + 10] = packed;
345 buf[pos + 11] = ch.message_id;
346 pos += CHANGE_BASE_LEN;
347 if let Some(ref inv) = ch.invariant_ts {
348 buf[pos..pos + 2].copy_from_slice(&inv.tsid.to_be_bytes());
349 buf[pos + 2..pos + 4].copy_from_slice(&inv.onid.to_be_bytes());
350 pos += INVARIANT_TS_LEN;
351 }
352 }
353 }
354 Ok(len)
355 }
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361 use crate::descriptors::extension::test_support::*;
362 use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor, ExtensionTag};
363
364 #[test]
365 fn parse_network_change_notify_structured() {
366 let sel = [
369 0x00, 0x01, 0x00, 0x00, 0x02, 0x1C,
372 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x23, 0x40, 0x30, 0x40, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x54, 0x50, 0xAA, 0xAA, 0xBB, 0xBB, ];
389 let bytes = wrap(0x07, &sel);
390 let d = ExtensionDescriptor::parse(&bytes).unwrap();
391 match &d.body {
392 ExtensionBody::NetworkChangeNotify(b) => {
393 assert_eq!(b.cells.len(), 2);
394
395 assert_eq!(b.cells[0].cell_id, 0x0001);
396 assert!(b.cells[0].changes.is_empty());
397
398 assert_eq!(b.cells[1].cell_id, 0x0002);
399 assert_eq!(b.cells[1].changes.len(), 2);
400
401 let ch0 = &b.cells[1].changes[0];
402 assert_eq!(ch0.network_change_id, 0x10);
403 assert_eq!(ch0.network_change_version, 0x20);
404 assert_eq!(ch0.start_time_of_change, 1);
405 assert_eq!(ch0.change_duration, 1);
406 assert_eq!(ch0.receiver_category, 1);
407 assert_eq!(ch0.change_type, ChangeType::MinorServiceChanged);
408 assert_eq!(ch0.message_id, 0x40);
409 assert!(ch0.invariant_ts.is_none());
410
411 let ch1 = &b.cells[1].changes[1];
412 assert_eq!(ch1.network_change_id, 0x30);
413 assert_eq!(ch1.network_change_version, 0x40);
414 assert_eq!(ch1.start_time_of_change, 2);
415 assert_eq!(ch1.change_duration, 2);
416 assert_eq!(ch1.receiver_category, 2);
417 assert_eq!(ch1.change_type, ChangeType::MinorReserved(4));
418 assert_eq!(ch1.message_id, 0x50);
419 let inv = ch1.invariant_ts.as_ref().unwrap();
420 assert_eq!(inv.tsid, 0xAAAA);
421 assert_eq!(inv.onid, 0xBBBB);
422 }
423 other => panic!("expected NetworkChangeNotify, got {other:?}"),
424 }
425 round_trip(&d);
426 }
427
428 #[test]
432 fn network_change_notify_tsduck_byte_exact() {
433 let bytes =
434 from_hex("7f230712340056781cabcde5cc2312340852030281ef67e5e20234561132453b83deadbeef");
435 let d = ExtensionDescriptor::parse(&bytes).unwrap();
436 assert_eq!(d.kind(), Some(ExtensionTag::NetworkChangeNotify));
437
438 match &d.body {
439 ExtensionBody::NetworkChangeNotify(b) => {
440 assert_eq!(b.cells.len(), 2);
441
442 assert_eq!(b.cells[0].cell_id, 0x1234);
444 assert!(b.cells[0].changes.is_empty());
445
446 assert_eq!(b.cells[1].cell_id, 0x5678);
448 assert_eq!(b.cells[1].changes.len(), 2);
449
450 let ch0 = &b.cells[1].changes[0];
452 assert_eq!(ch0.network_change_id, 0xAB);
453 assert_eq!(ch0.network_change_version, 0xCD);
454 assert_eq!(ch0.start_time_of_change, 0xE5CC231234);
455 assert_eq!(ch0.change_duration, 0x085203);
456 assert_eq!(ch0.receiver_category, 0);
457 assert_eq!(ch0.change_type, ChangeType::MinorMultiplexRemoved);
458 assert_eq!(ch0.message_id, 0x81);
459 assert!(ch0.invariant_ts.is_none());
460
461 let ch1 = &b.cells[1].changes[1];
463 assert_eq!(ch1.network_change_id, 0xEF);
464 assert_eq!(ch1.network_change_version, 0x67);
465 assert_eq!(ch1.start_time_of_change, 0xE5E2023456);
466 assert_eq!(ch1.change_duration, 0x113245);
467 assert_eq!(ch1.receiver_category, 1);
468 assert_eq!(ch1.change_type, ChangeType::MajorMultiplexAdded);
469 assert_eq!(ch1.message_id, 0x83);
470 let inv = ch1.invariant_ts.as_ref().unwrap();
471 assert_eq!(inv.tsid, 0xDEAD);
472 assert_eq!(inv.onid, 0xBEEF);
473 }
474 other => panic!("expected NetworkChangeNotify, got {other:?}"),
475 }
476
477 let mut out = vec![0u8; d.serialized_len()];
478 let n = d.serialize_into(&mut out).unwrap();
479 assert_eq!(
480 out[..n],
481 bytes[..],
482 "byte-exact re-serialize for TSDuck vector"
483 );
484 }
485
486 #[cfg(feature = "chrono")]
487 #[test]
488 fn chrono_accessors_decode_start_time_and_duration() {
489 use chrono::{Datelike, Timelike};
490 let sel: Vec<u8> = vec![
495 0x00, 0x01, 12, 0x10, 0x20, 0xE4, 0x09, 0x12, 0x34, 0x56, 0x01, 0x30, 0x45, 0x23, 0x40, ];
504 let bytes = wrap(0x07, &sel);
505 let d = ExtensionDescriptor::parse(&bytes).unwrap();
506 match &d.body {
507 ExtensionBody::NetworkChangeNotify(b) => {
508 assert_eq!(b.cells.len(), 1);
509 let ch = &b.cells[0].changes[0];
510 let utc = ch.start_time_of_change_utc().expect("should decode");
511 assert_eq!(utc.year(), 2018);
512 assert_eq!((utc.hour(), utc.minute(), utc.second()), (12, 34, 56));
513 let secs = ch.change_duration_secs().expect("should decode");
514 assert_eq!(secs, 5445); }
516 other => panic!("expected NetworkChangeNotify, got {other:?}"),
517 }
518 }
519
520 #[test]
521 fn change_type_full_range_round_trip() {
522 for v in 0u8..=0x0F {
523 let ct = ChangeType::from_u8(v);
524 assert_eq!(ct.to_u8(), v, "ChangeType round-trip failed for {v}");
525 }
526 }
527
528 #[test]
529 fn change_type_known_values() {
530 assert_eq!(ChangeType::from_u8(0), ChangeType::MessageOnly);
531 assert_eq!(ChangeType::from_u8(1), ChangeType::MinorDefault);
532 assert_eq!(ChangeType::from_u8(4), ChangeType::MinorReserved(4));
533 assert_eq!(ChangeType::from_u8(8), ChangeType::MajorDefault);
534 assert_eq!(ChangeType::from_u8(9), ChangeType::MajorFrequencyChanged);
535 assert_eq!(ChangeType::from_u8(10), ChangeType::MajorCoverageChanged);
536 assert_eq!(ChangeType::from_u8(11), ChangeType::MajorMultiplexAdded);
537 assert_eq!(ChangeType::from_u8(12), ChangeType::MajorReserved(12));
538 assert_eq!(ChangeType::MessageOnly.name(), "message only");
539 assert_eq!(ChangeType::MajorDefault.name(), "major - default");
540 assert_eq!(ChangeType::MinorReserved(5).name(), "reserved (minor)");
541 assert_eq!(ChangeType::MajorReserved(13).name(), "reserved (major)");
542 }
543}