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 = u16::from_be_bytes([sel[pos], sel[pos + 1]]);
201 let loop_length = sel[pos + CELL_HEADER_LEN] as usize;
202 pos += CELL_HEADER_LEN + LOOP_LENGTH_LEN;
203
204 if pos + loop_length > sel.len() {
205 return Err(Error::BufferTooShort {
206 need: pos + loop_length,
207 have: sel.len(),
208 what: "network_change_notify body",
209 });
210 }
211
212 let inner_end = pos + loop_length;
213 let mut changes = Vec::new();
214 while pos < inner_end {
215 let remaining = inner_end - pos;
216 if remaining < CHANGE_BASE_LEN {
218 return Err(Error::BufferTooShort {
219 need: inner_end - remaining + CHANGE_BASE_LEN,
220 have: sel.len(),
221 what: "network_change_notify body",
222 });
223 }
224 let network_change_id = sel[pos];
225 let network_change_version = sel[pos + 1];
226 let start_time_of_change = (u64::from(sel[pos + 2]) << 32)
227 | (u64::from(sel[pos + 3]) << 24)
228 | (u64::from(sel[pos + 4]) << 16)
229 | (u64::from(sel[pos + 5]) << 8)
230 | u64::from(sel[pos + 6]);
231 let change_duration = (u32::from(sel[pos + 7]) << 16)
232 | (u32::from(sel[pos + 8]) << 8)
233 | u32::from(sel[pos + 9]);
234 let packed = sel[pos + 10];
235 let receiver_category = packed >> 5;
236 let invariant_ts_present = (packed >> 4) & 1;
237 let change_type = ChangeType::from_u8(packed & 0x0F);
238 let message_id = sel[pos + 11];
239 pos += CHANGE_BASE_LEN;
240
241 let invariant_ts = if invariant_ts_present == 1 {
242 if pos + INVARIANT_TS_LEN > inner_end {
243 return Err(Error::BufferTooShort {
244 need: pos + INVARIANT_TS_LEN,
245 have: sel.len(),
246 what: "network_change_notify body",
247 });
248 }
249 let ts = InvariantTs {
250 tsid: u16::from_be_bytes([sel[pos], sel[pos + 1]]),
251 onid: u16::from_be_bytes([sel[pos + 2], sel[pos + 3]]),
252 };
253 pos += INVARIANT_TS_LEN;
254 Some(ts)
255 } else {
256 None
257 };
258
259 changes.push(NetworkChange {
260 network_change_id,
261 network_change_version,
262 start_time_of_change,
263 change_duration,
264 receiver_category,
265 change_type,
266 message_id,
267 invariant_ts,
268 });
269 }
270
271 if pos != inner_end {
272 return Err(invalid("network_change_notify: change entry overruns loop"));
273 }
274
275 cells.push(NetworkChangeCell { cell_id, changes });
276 }
277 Ok(NetworkChangeNotify { cells })
278 }
279}
280
281impl Serialize for NetworkChangeNotify {
282 type Error = crate::error::Error;
283 fn serialized_len(&self) -> usize {
284 self.cells
285 .iter()
286 .map(|cell| {
287 CELL_HEADER_LEN
288 + LOOP_LENGTH_LEN
289 + cell
290 .changes
291 .iter()
292 .map(change_serialized_len)
293 .sum::<usize>()
294 })
295 .sum()
296 }
297 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
298 let len = self.serialized_len();
299 if buf.len() < len {
300 return Err(Error::OutputBufferTooSmall {
301 need: len,
302 have: buf.len(),
303 });
304 }
305 let mut pos = 0;
306 for cell in &self.cells {
307 buf[pos..pos + 2].copy_from_slice(&cell.cell_id.to_be_bytes());
308 pos += 2;
309 let loop_length: usize = cell.changes.iter().map(change_serialized_len).sum();
310 buf[pos] = loop_length as u8;
311 pos += 1;
312 for ch in &cell.changes {
313 buf[pos] = ch.network_change_id;
314 buf[pos + 1] = ch.network_change_version;
315 let st = ch.start_time_of_change;
316 buf[pos + 2] = (st >> 32) as u8;
317 buf[pos + 3] = (st >> 24) as u8;
318 buf[pos + 4] = (st >> 16) as u8;
319 buf[pos + 5] = (st >> 8) as u8;
320 buf[pos + 6] = st as u8;
321 let dur = ch.change_duration;
322 buf[pos + 7] = (dur >> 16) as u8;
323 buf[pos + 8] = (dur >> 8) as u8;
324 buf[pos + 9] = dur as u8;
325 let packed = ((ch.receiver_category & 0x07) << 5)
326 | ((ch.invariant_ts.is_some() as u8) << 4)
327 | (ch.change_type.to_u8() & 0x0F);
328 buf[pos + 10] = packed;
329 buf[pos + 11] = ch.message_id;
330 pos += CHANGE_BASE_LEN;
331 if let Some(ref inv) = ch.invariant_ts {
332 buf[pos..pos + 2].copy_from_slice(&inv.tsid.to_be_bytes());
333 buf[pos + 2..pos + 4].copy_from_slice(&inv.onid.to_be_bytes());
334 pos += INVARIANT_TS_LEN;
335 }
336 }
337 }
338 Ok(len)
339 }
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345 use crate::descriptors::extension::test_support::*;
346 use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor, ExtensionTag};
347
348 #[test]
349 fn parse_network_change_notify_structured() {
350 let sel = [
353 0x00, 0x01, 0x00, 0x00, 0x02, 0x1C,
356 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, ];
373 let bytes = wrap(0x07, &sel);
374 let d = ExtensionDescriptor::parse(&bytes).unwrap();
375 match &d.body {
376 ExtensionBody::NetworkChangeNotify(b) => {
377 assert_eq!(b.cells.len(), 2);
378
379 assert_eq!(b.cells[0].cell_id, 0x0001);
380 assert!(b.cells[0].changes.is_empty());
381
382 assert_eq!(b.cells[1].cell_id, 0x0002);
383 assert_eq!(b.cells[1].changes.len(), 2);
384
385 let ch0 = &b.cells[1].changes[0];
386 assert_eq!(ch0.network_change_id, 0x10);
387 assert_eq!(ch0.network_change_version, 0x20);
388 assert_eq!(ch0.start_time_of_change, 1);
389 assert_eq!(ch0.change_duration, 1);
390 assert_eq!(ch0.receiver_category, 1);
391 assert_eq!(ch0.change_type, ChangeType::MinorServiceChanged);
392 assert_eq!(ch0.message_id, 0x40);
393 assert!(ch0.invariant_ts.is_none());
394
395 let ch1 = &b.cells[1].changes[1];
396 assert_eq!(ch1.network_change_id, 0x30);
397 assert_eq!(ch1.network_change_version, 0x40);
398 assert_eq!(ch1.start_time_of_change, 2);
399 assert_eq!(ch1.change_duration, 2);
400 assert_eq!(ch1.receiver_category, 2);
401 assert_eq!(ch1.change_type, ChangeType::MinorReserved(4));
402 assert_eq!(ch1.message_id, 0x50);
403 let inv = ch1.invariant_ts.as_ref().unwrap();
404 assert_eq!(inv.tsid, 0xAAAA);
405 assert_eq!(inv.onid, 0xBBBB);
406 }
407 other => panic!("expected NetworkChangeNotify, got {other:?}"),
408 }
409 round_trip(&d);
410 }
411
412 #[test]
416 fn network_change_notify_tsduck_byte_exact() {
417 let bytes =
418 from_hex("7f230712340056781cabcde5cc2312340852030281ef67e5e20234561132453b83deadbeef");
419 let d = ExtensionDescriptor::parse(&bytes).unwrap();
420 assert_eq!(d.kind(), Some(ExtensionTag::NetworkChangeNotify));
421
422 match &d.body {
423 ExtensionBody::NetworkChangeNotify(b) => {
424 assert_eq!(b.cells.len(), 2);
425
426 assert_eq!(b.cells[0].cell_id, 0x1234);
428 assert!(b.cells[0].changes.is_empty());
429
430 assert_eq!(b.cells[1].cell_id, 0x5678);
432 assert_eq!(b.cells[1].changes.len(), 2);
433
434 let ch0 = &b.cells[1].changes[0];
436 assert_eq!(ch0.network_change_id, 0xAB);
437 assert_eq!(ch0.network_change_version, 0xCD);
438 assert_eq!(ch0.start_time_of_change, 0xE5CC231234);
439 assert_eq!(ch0.change_duration, 0x085203);
440 assert_eq!(ch0.receiver_category, 0);
441 assert_eq!(ch0.change_type, ChangeType::MinorMultiplexRemoved);
442 assert_eq!(ch0.message_id, 0x81);
443 assert!(ch0.invariant_ts.is_none());
444
445 let ch1 = &b.cells[1].changes[1];
447 assert_eq!(ch1.network_change_id, 0xEF);
448 assert_eq!(ch1.network_change_version, 0x67);
449 assert_eq!(ch1.start_time_of_change, 0xE5E2023456);
450 assert_eq!(ch1.change_duration, 0x113245);
451 assert_eq!(ch1.receiver_category, 1);
452 assert_eq!(ch1.change_type, ChangeType::MajorMultiplexAdded);
453 assert_eq!(ch1.message_id, 0x83);
454 let inv = ch1.invariant_ts.as_ref().unwrap();
455 assert_eq!(inv.tsid, 0xDEAD);
456 assert_eq!(inv.onid, 0xBEEF);
457 }
458 other => panic!("expected NetworkChangeNotify, got {other:?}"),
459 }
460
461 let mut out = vec![0u8; d.serialized_len()];
462 let n = d.serialize_into(&mut out).unwrap();
463 assert_eq!(
464 out[..n],
465 bytes[..],
466 "byte-exact re-serialize for TSDuck vector"
467 );
468 }
469
470 #[cfg(feature = "chrono")]
471 #[test]
472 fn chrono_accessors_decode_start_time_and_duration() {
473 use chrono::{Datelike, Timelike};
474 let sel: Vec<u8> = vec![
479 0x00, 0x01, 12, 0x10, 0x20, 0xE4, 0x09, 0x12, 0x34, 0x56, 0x01, 0x30, 0x45, 0x23, 0x40, ];
488 let bytes = wrap(0x07, &sel);
489 let d = ExtensionDescriptor::parse(&bytes).unwrap();
490 match &d.body {
491 ExtensionBody::NetworkChangeNotify(b) => {
492 assert_eq!(b.cells.len(), 1);
493 let ch = &b.cells[0].changes[0];
494 let utc = ch.start_time_of_change_utc().expect("should decode");
495 assert_eq!(utc.year(), 2018);
496 assert_eq!((utc.hour(), utc.minute(), utc.second()), (12, 34, 56));
497 let secs = ch.change_duration_secs().expect("should decode");
498 assert_eq!(secs, 5445); }
500 other => panic!("expected NetworkChangeNotify, got {other:?}"),
501 }
502 }
503
504 #[test]
505 fn change_type_full_range_round_trip() {
506 for v in 0u8..=0x0F {
507 let ct = ChangeType::from_u8(v);
508 assert_eq!(ct.to_u8(), v, "ChangeType round-trip failed for {v}");
509 }
510 }
511
512 #[test]
513 fn change_type_known_values() {
514 assert_eq!(ChangeType::from_u8(0), ChangeType::MessageOnly);
515 assert_eq!(ChangeType::from_u8(1), ChangeType::MinorDefault);
516 assert_eq!(ChangeType::from_u8(4), ChangeType::MinorReserved(4));
517 assert_eq!(ChangeType::from_u8(8), ChangeType::MajorDefault);
518 assert_eq!(ChangeType::from_u8(9), ChangeType::MajorFrequencyChanged);
519 assert_eq!(ChangeType::from_u8(10), ChangeType::MajorCoverageChanged);
520 assert_eq!(ChangeType::from_u8(11), ChangeType::MajorMultiplexAdded);
521 assert_eq!(ChangeType::from_u8(12), ChangeType::MajorReserved(12));
522 assert_eq!(ChangeType::MessageOnly.name(), "message only");
523 assert_eq!(ChangeType::MajorDefault.name(), "major - default");
524 assert_eq!(ChangeType::MinorReserved(5).name(), "reserved (minor)");
525 assert_eq!(ChangeType::MajorReserved(13).name(), "reserved (major)");
526 }
527}