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 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, _) =
201 sel[pos..]
202 .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[pos..].split_first_chunk::<INVARIANT_TS_LEN>().ok_or(
258 Error::BufferTooShort {
259 need: pos + INVARIANT_TS_LEN,
260 have: sel.len(),
261 what: "network_change_notify body",
262 },
263 )?;
264 let ts = InvariantTs {
265 tsid: u16::from_be_bytes([inv_bytes[0], inv_bytes[1]]),
266 onid: u16::from_be_bytes([inv_bytes[2], inv_bytes[3]]),
267 };
268 pos += INVARIANT_TS_LEN;
269 Some(ts)
270 } else {
271 None
272 };
273
274 changes.push(NetworkChange {
275 network_change_id,
276 network_change_version,
277 start_time_of_change,
278 change_duration,
279 receiver_category,
280 change_type,
281 message_id,
282 invariant_ts,
283 });
284 }
285
286 if pos != inner_end {
287 return Err(invalid("network_change_notify: change entry overruns loop"));
288 }
289
290 cells.push(NetworkChangeCell { cell_id, changes });
291 }
292 Ok(NetworkChangeNotify { cells })
293 }
294}
295
296impl Serialize for NetworkChangeNotify {
297 type Error = crate::error::Error;
298 fn serialized_len(&self) -> usize {
299 self.cells
300 .iter()
301 .map(|cell| {
302 CELL_HEADER_LEN
303 + LOOP_LENGTH_LEN
304 + cell
305 .changes
306 .iter()
307 .map(change_serialized_len)
308 .sum::<usize>()
309 })
310 .sum()
311 }
312 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
313 let len = self.serialized_len();
314 if buf.len() < len {
315 return Err(Error::OutputBufferTooSmall {
316 need: len,
317 have: buf.len(),
318 });
319 }
320 let mut pos = 0;
321 for cell in &self.cells {
322 buf[pos..pos + 2].copy_from_slice(&cell.cell_id.to_be_bytes());
323 pos += 2;
324 let loop_length: usize = cell.changes.iter().map(change_serialized_len).sum();
325 buf[pos] = loop_length as u8;
326 pos += 1;
327 for ch in &cell.changes {
328 buf[pos] = ch.network_change_id;
329 buf[pos + 1] = ch.network_change_version;
330 let st = ch.start_time_of_change;
331 buf[pos + 2] = (st >> 32) as u8;
332 buf[pos + 3] = (st >> 24) as u8;
333 buf[pos + 4] = (st >> 16) as u8;
334 buf[pos + 5] = (st >> 8) as u8;
335 buf[pos + 6] = st as u8;
336 let dur = ch.change_duration;
337 buf[pos + 7] = (dur >> 16) as u8;
338 buf[pos + 8] = (dur >> 8) as u8;
339 buf[pos + 9] = dur as u8;
340 let packed = ((ch.receiver_category & 0x07) << 5)
341 | ((ch.invariant_ts.is_some() as u8) << 4)
342 | (ch.change_type.to_u8() & 0x0F);
343 buf[pos + 10] = packed;
344 buf[pos + 11] = ch.message_id;
345 pos += CHANGE_BASE_LEN;
346 if let Some(ref inv) = ch.invariant_ts {
347 buf[pos..pos + 2].copy_from_slice(&inv.tsid.to_be_bytes());
348 buf[pos + 2..pos + 4].copy_from_slice(&inv.onid.to_be_bytes());
349 pos += INVARIANT_TS_LEN;
350 }
351 }
352 }
353 Ok(len)
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use super::*;
360 use crate::descriptors::extension::test_support::*;
361 use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor, ExtensionTag};
362
363 #[test]
364 fn parse_network_change_notify_structured() {
365 let sel = [
368 0x00, 0x01, 0x00, 0x00, 0x02, 0x1C,
371 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, ];
388 let bytes = wrap(0x07, &sel);
389 let d = ExtensionDescriptor::parse(&bytes).unwrap();
390 match &d.body {
391 ExtensionBody::NetworkChangeNotify(b) => {
392 assert_eq!(b.cells.len(), 2);
393
394 assert_eq!(b.cells[0].cell_id, 0x0001);
395 assert!(b.cells[0].changes.is_empty());
396
397 assert_eq!(b.cells[1].cell_id, 0x0002);
398 assert_eq!(b.cells[1].changes.len(), 2);
399
400 let ch0 = &b.cells[1].changes[0];
401 assert_eq!(ch0.network_change_id, 0x10);
402 assert_eq!(ch0.network_change_version, 0x20);
403 assert_eq!(ch0.start_time_of_change, 1);
404 assert_eq!(ch0.change_duration, 1);
405 assert_eq!(ch0.receiver_category, 1);
406 assert_eq!(ch0.change_type, ChangeType::MinorServiceChanged);
407 assert_eq!(ch0.message_id, 0x40);
408 assert!(ch0.invariant_ts.is_none());
409
410 let ch1 = &b.cells[1].changes[1];
411 assert_eq!(ch1.network_change_id, 0x30);
412 assert_eq!(ch1.network_change_version, 0x40);
413 assert_eq!(ch1.start_time_of_change, 2);
414 assert_eq!(ch1.change_duration, 2);
415 assert_eq!(ch1.receiver_category, 2);
416 assert_eq!(ch1.change_type, ChangeType::MinorReserved(4));
417 assert_eq!(ch1.message_id, 0x50);
418 let inv = ch1.invariant_ts.as_ref().unwrap();
419 assert_eq!(inv.tsid, 0xAAAA);
420 assert_eq!(inv.onid, 0xBBBB);
421 }
422 other => panic!("expected NetworkChangeNotify, got {other:?}"),
423 }
424 round_trip(&d);
425 }
426
427 #[test]
431 fn network_change_notify_tsduck_byte_exact() {
432 let bytes =
433 from_hex("7f230712340056781cabcde5cc2312340852030281ef67e5e20234561132453b83deadbeef");
434 let d = ExtensionDescriptor::parse(&bytes).unwrap();
435 assert_eq!(d.kind(), Some(ExtensionTag::NetworkChangeNotify));
436
437 match &d.body {
438 ExtensionBody::NetworkChangeNotify(b) => {
439 assert_eq!(b.cells.len(), 2);
440
441 assert_eq!(b.cells[0].cell_id, 0x1234);
443 assert!(b.cells[0].changes.is_empty());
444
445 assert_eq!(b.cells[1].cell_id, 0x5678);
447 assert_eq!(b.cells[1].changes.len(), 2);
448
449 let ch0 = &b.cells[1].changes[0];
451 assert_eq!(ch0.network_change_id, 0xAB);
452 assert_eq!(ch0.network_change_version, 0xCD);
453 assert_eq!(ch0.start_time_of_change, 0xE5CC231234);
454 assert_eq!(ch0.change_duration, 0x085203);
455 assert_eq!(ch0.receiver_category, 0);
456 assert_eq!(ch0.change_type, ChangeType::MinorMultiplexRemoved);
457 assert_eq!(ch0.message_id, 0x81);
458 assert!(ch0.invariant_ts.is_none());
459
460 let ch1 = &b.cells[1].changes[1];
462 assert_eq!(ch1.network_change_id, 0xEF);
463 assert_eq!(ch1.network_change_version, 0x67);
464 assert_eq!(ch1.start_time_of_change, 0xE5E2023456);
465 assert_eq!(ch1.change_duration, 0x113245);
466 assert_eq!(ch1.receiver_category, 1);
467 assert_eq!(ch1.change_type, ChangeType::MajorMultiplexAdded);
468 assert_eq!(ch1.message_id, 0x83);
469 let inv = ch1.invariant_ts.as_ref().unwrap();
470 assert_eq!(inv.tsid, 0xDEAD);
471 assert_eq!(inv.onid, 0xBEEF);
472 }
473 other => panic!("expected NetworkChangeNotify, got {other:?}"),
474 }
475
476 let mut out = vec![0u8; d.serialized_len()];
477 let n = d.serialize_into(&mut out).unwrap();
478 assert_eq!(
479 out[..n],
480 bytes[..],
481 "byte-exact re-serialize for TSDuck vector"
482 );
483 }
484
485 #[cfg(feature = "chrono")]
486 #[test]
487 fn chrono_accessors_decode_start_time_and_duration() {
488 use chrono::{Datelike, Timelike};
489 let sel: Vec<u8> = vec![
494 0x00, 0x01, 12, 0x10, 0x20, 0xE4, 0x09, 0x12, 0x34, 0x56, 0x01, 0x30, 0x45, 0x23, 0x40, ];
503 let bytes = wrap(0x07, &sel);
504 let d = ExtensionDescriptor::parse(&bytes).unwrap();
505 match &d.body {
506 ExtensionBody::NetworkChangeNotify(b) => {
507 assert_eq!(b.cells.len(), 1);
508 let ch = &b.cells[0].changes[0];
509 let utc = ch.start_time_of_change_utc().expect("should decode");
510 assert_eq!(utc.year(), 2018);
511 assert_eq!((utc.hour(), utc.minute(), utc.second()), (12, 34, 56));
512 let secs = ch.change_duration_secs().expect("should decode");
513 assert_eq!(secs, 5445); }
515 other => panic!("expected NetworkChangeNotify, got {other:?}"),
516 }
517 }
518
519 #[test]
520 fn change_type_full_range_round_trip() {
521 for v in 0u8..=0x0F {
522 let ct = ChangeType::from_u8(v);
523 assert_eq!(ct.to_u8(), v, "ChangeType round-trip failed for {v}");
524 }
525 }
526
527 #[test]
528 fn change_type_known_values() {
529 assert_eq!(ChangeType::from_u8(0), ChangeType::MessageOnly);
530 assert_eq!(ChangeType::from_u8(1), ChangeType::MinorDefault);
531 assert_eq!(ChangeType::from_u8(4), ChangeType::MinorReserved(4));
532 assert_eq!(ChangeType::from_u8(8), ChangeType::MajorDefault);
533 assert_eq!(ChangeType::from_u8(9), ChangeType::MajorFrequencyChanged);
534 assert_eq!(ChangeType::from_u8(10), ChangeType::MajorCoverageChanged);
535 assert_eq!(ChangeType::from_u8(11), ChangeType::MajorMultiplexAdded);
536 assert_eq!(ChangeType::from_u8(12), ChangeType::MajorReserved(12));
537 assert_eq!(ChangeType::MessageOnly.name(), "message only");
538 assert_eq!(ChangeType::MajorDefault.name(), "major - default");
539 assert_eq!(ChangeType::MinorReserved(5).name(), "reserved (minor)");
540 assert_eq!(ChangeType::MajorReserved(13).name(), "reserved (major)");
541 }
542}