use super::*;
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum ChangeType {
MessageOnly,
MinorDefault,
MinorMultiplexRemoved,
MinorServiceChanged,
MinorReserved(u8),
MajorDefault,
MajorFrequencyChanged,
MajorCoverageChanged,
MajorMultiplexAdded,
MajorReserved(u8),
}
impl ChangeType {
#[must_use]
pub fn from_u8(v: u8) -> Self {
match v & 0x0F {
0 => Self::MessageOnly,
1 => Self::MinorDefault,
2 => Self::MinorMultiplexRemoved,
3 => Self::MinorServiceChanged,
v @ 4..=7 => Self::MinorReserved(v),
8 => Self::MajorDefault,
9 => Self::MajorFrequencyChanged,
10 => Self::MajorCoverageChanged,
11 => Self::MajorMultiplexAdded,
v => Self::MajorReserved(v),
}
}
#[must_use]
pub fn to_u8(self) -> u8 {
match self {
Self::MessageOnly => 0,
Self::MinorDefault => 1,
Self::MinorMultiplexRemoved => 2,
Self::MinorServiceChanged => 3,
Self::MinorReserved(v) | Self::MajorReserved(v) => v,
Self::MajorDefault => 8,
Self::MajorFrequencyChanged => 9,
Self::MajorCoverageChanged => 10,
Self::MajorMultiplexAdded => 11,
}
}
#[must_use]
pub fn name(self) -> &'static str {
match self {
Self::MessageOnly => "message only",
Self::MinorDefault => "minor - default",
Self::MinorMultiplexRemoved => "minor - multiplex removed",
Self::MinorServiceChanged => "minor - service changed",
Self::MinorReserved(_) => "reserved (minor)",
Self::MajorDefault => "major - default",
Self::MajorFrequencyChanged => "major - multiplex frequency changed",
Self::MajorCoverageChanged => "major - multiplex coverage changed",
Self::MajorMultiplexAdded => "major - multiplex added",
Self::MajorReserved(_) => "reserved (major)",
}
}
}
dvb_common::impl_spec_display!(ChangeType, MinorReserved, MajorReserved);
const 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)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct NetworkChangeNotify {
pub cells: Vec<NetworkChangeCell>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct NetworkChangeCell {
pub cell_id: u16,
pub changes: Vec<NetworkChange>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct NetworkChange {
pub network_change_id: u8,
pub network_change_version: u8,
pub start_time_of_change: u64,
pub change_duration: u32,
pub receiver_category: u8,
pub change_type: ChangeType,
pub message_id: u8,
pub invariant_ts: Option<InvariantTs>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct InvariantTs {
pub tsid: u16,
pub onid: u16,
}
impl<'a> ExtensionBodyDef<'a> for NetworkChangeNotify {
const TAG_EXTENSION: u8 = 0x07;
const NAME: &'static str = "NETWORK_CHANGE_NOTIFY";
}
impl NetworkChange {
#[cfg(feature = "chrono")]
#[must_use]
pub fn start_time_of_change_utc(&self) -> Option<chrono::DateTime<chrono::Utc>> {
let raw = self.start_time_of_change;
let bytes = [
(raw >> 32) as u8,
(raw >> 24) as u8,
(raw >> 16) as u8,
(raw >> 8) as u8,
raw as u8,
];
dvb_common::time::decode_mjd_bcd_utc(bytes)
}
#[cfg(feature = "chrono")]
#[must_use]
pub fn change_duration_secs(&self) -> Option<u32> {
let raw = self.change_duration;
let bytes = [(raw >> 16) as u8, (raw >> 8) as u8, raw as u8];
dvb_common::time::decode_bcd_duration(bytes).map(|d| d.as_secs() as u32)
}
}
fn change_serialized_len(ch: &NetworkChange) -> usize {
CHANGE_BASE_LEN
+ if ch.invariant_ts.is_some() {
INVARIANT_TS_LEN
} else {
0
}
}
impl<'a> Parse<'a> for NetworkChangeNotify {
type Error = crate::error::Error;
fn parse(sel: &'a [u8]) -> Result<Self> {
let mut cells = Vec::new();
let mut pos = 0;
while pos < sel.len() {
if pos + CELL_HEADER_LEN + LOOP_LENGTH_LEN > sel.len() {
return Err(Error::BufferTooShort {
need: pos + CELL_HEADER_LEN + LOOP_LENGTH_LEN,
have: sel.len(),
what: "network_change_notify body",
});
}
let cell_id = u16::from_be_bytes([sel[pos], sel[pos + 1]]);
let loop_length = sel[pos + CELL_HEADER_LEN] as usize;
pos += CELL_HEADER_LEN + LOOP_LENGTH_LEN;
if pos + loop_length > sel.len() {
return Err(Error::BufferTooShort {
need: pos + loop_length,
have: sel.len(),
what: "network_change_notify body",
});
}
let inner_end = pos + loop_length;
let mut changes = Vec::new();
while pos < inner_end {
let remaining = inner_end - pos;
if remaining < CHANGE_BASE_LEN {
return Err(Error::BufferTooShort {
need: inner_end - remaining + CHANGE_BASE_LEN,
have: sel.len(),
what: "network_change_notify body",
});
}
let network_change_id = sel[pos];
let network_change_version = sel[pos + 1];
let start_time_of_change = (u64::from(sel[pos + 2]) << 32)
| (u64::from(sel[pos + 3]) << 24)
| (u64::from(sel[pos + 4]) << 16)
| (u64::from(sel[pos + 5]) << 8)
| u64::from(sel[pos + 6]);
let change_duration = (u32::from(sel[pos + 7]) << 16)
| (u32::from(sel[pos + 8]) << 8)
| u32::from(sel[pos + 9]);
let packed = sel[pos + 10];
let receiver_category = packed >> 5;
let invariant_ts_present = (packed >> 4) & 1;
let change_type = ChangeType::from_u8(packed & 0x0F);
let message_id = sel[pos + 11];
pos += CHANGE_BASE_LEN;
let invariant_ts = if invariant_ts_present == 1 {
if pos + INVARIANT_TS_LEN > inner_end {
return Err(Error::BufferTooShort {
need: pos + INVARIANT_TS_LEN,
have: sel.len(),
what: "network_change_notify body",
});
}
let ts = InvariantTs {
tsid: u16::from_be_bytes([sel[pos], sel[pos + 1]]),
onid: u16::from_be_bytes([sel[pos + 2], sel[pos + 3]]),
};
pos += INVARIANT_TS_LEN;
Some(ts)
} else {
None
};
changes.push(NetworkChange {
network_change_id,
network_change_version,
start_time_of_change,
change_duration,
receiver_category,
change_type,
message_id,
invariant_ts,
});
}
if pos != inner_end {
return Err(invalid("network_change_notify: change entry overruns loop"));
}
cells.push(NetworkChangeCell { cell_id, changes });
}
Ok(NetworkChangeNotify { cells })
}
}
impl Serialize for NetworkChangeNotify {
type Error = crate::error::Error;
fn serialized_len(&self) -> usize {
self.cells
.iter()
.map(|cell| {
CELL_HEADER_LEN
+ LOOP_LENGTH_LEN
+ cell
.changes
.iter()
.map(change_serialized_len)
.sum::<usize>()
})
.sum()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len();
if buf.len() < len {
return Err(Error::OutputBufferTooSmall {
need: len,
have: buf.len(),
});
}
let mut pos = 0;
for cell in &self.cells {
buf[pos..pos + 2].copy_from_slice(&cell.cell_id.to_be_bytes());
pos += 2;
let loop_length: usize = cell.changes.iter().map(change_serialized_len).sum();
buf[pos] = loop_length as u8;
pos += 1;
for ch in &cell.changes {
buf[pos] = ch.network_change_id;
buf[pos + 1] = ch.network_change_version;
let st = ch.start_time_of_change;
buf[pos + 2] = (st >> 32) as u8;
buf[pos + 3] = (st >> 24) as u8;
buf[pos + 4] = (st >> 16) as u8;
buf[pos + 5] = (st >> 8) as u8;
buf[pos + 6] = st as u8;
let dur = ch.change_duration;
buf[pos + 7] = (dur >> 16) as u8;
buf[pos + 8] = (dur >> 8) as u8;
buf[pos + 9] = dur as u8;
let packed = ((ch.receiver_category & 0x07) << 5)
| ((ch.invariant_ts.is_some() as u8) << 4)
| (ch.change_type.to_u8() & 0x0F);
buf[pos + 10] = packed;
buf[pos + 11] = ch.message_id;
pos += CHANGE_BASE_LEN;
if let Some(ref inv) = ch.invariant_ts {
buf[pos..pos + 2].copy_from_slice(&inv.tsid.to_be_bytes());
buf[pos + 2..pos + 4].copy_from_slice(&inv.onid.to_be_bytes());
pos += INVARIANT_TS_LEN;
}
}
}
Ok(len)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::descriptors::extension::test_support::*;
use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor, ExtensionTag};
#[test]
fn parse_network_change_notify_structured() {
let sel = [
0x00, 0x01, 0x00, 0x00, 0x02, 0x1C,
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, ];
let bytes = wrap(0x07, &sel);
let d = ExtensionDescriptor::parse(&bytes).unwrap();
match &d.body {
ExtensionBody::NetworkChangeNotify(b) => {
assert_eq!(b.cells.len(), 2);
assert_eq!(b.cells[0].cell_id, 0x0001);
assert!(b.cells[0].changes.is_empty());
assert_eq!(b.cells[1].cell_id, 0x0002);
assert_eq!(b.cells[1].changes.len(), 2);
let ch0 = &b.cells[1].changes[0];
assert_eq!(ch0.network_change_id, 0x10);
assert_eq!(ch0.network_change_version, 0x20);
assert_eq!(ch0.start_time_of_change, 1);
assert_eq!(ch0.change_duration, 1);
assert_eq!(ch0.receiver_category, 1);
assert_eq!(ch0.change_type, ChangeType::MinorServiceChanged);
assert_eq!(ch0.message_id, 0x40);
assert!(ch0.invariant_ts.is_none());
let ch1 = &b.cells[1].changes[1];
assert_eq!(ch1.network_change_id, 0x30);
assert_eq!(ch1.network_change_version, 0x40);
assert_eq!(ch1.start_time_of_change, 2);
assert_eq!(ch1.change_duration, 2);
assert_eq!(ch1.receiver_category, 2);
assert_eq!(ch1.change_type, ChangeType::MinorReserved(4));
assert_eq!(ch1.message_id, 0x50);
let inv = ch1.invariant_ts.as_ref().unwrap();
assert_eq!(inv.tsid, 0xAAAA);
assert_eq!(inv.onid, 0xBBBB);
}
other => panic!("expected NetworkChangeNotify, got {other:?}"),
}
round_trip(&d);
}
#[test]
fn network_change_notify_tsduck_byte_exact() {
let bytes =
from_hex("7f230712340056781cabcde5cc2312340852030281ef67e5e20234561132453b83deadbeef");
let d = ExtensionDescriptor::parse(&bytes).unwrap();
assert_eq!(d.kind(), Some(ExtensionTag::NetworkChangeNotify));
match &d.body {
ExtensionBody::NetworkChangeNotify(b) => {
assert_eq!(b.cells.len(), 2);
assert_eq!(b.cells[0].cell_id, 0x1234);
assert!(b.cells[0].changes.is_empty());
assert_eq!(b.cells[1].cell_id, 0x5678);
assert_eq!(b.cells[1].changes.len(), 2);
let ch0 = &b.cells[1].changes[0];
assert_eq!(ch0.network_change_id, 0xAB);
assert_eq!(ch0.network_change_version, 0xCD);
assert_eq!(ch0.start_time_of_change, 0xE5CC231234);
assert_eq!(ch0.change_duration, 0x085203);
assert_eq!(ch0.receiver_category, 0);
assert_eq!(ch0.change_type, ChangeType::MinorMultiplexRemoved);
assert_eq!(ch0.message_id, 0x81);
assert!(ch0.invariant_ts.is_none());
let ch1 = &b.cells[1].changes[1];
assert_eq!(ch1.network_change_id, 0xEF);
assert_eq!(ch1.network_change_version, 0x67);
assert_eq!(ch1.start_time_of_change, 0xE5E2023456);
assert_eq!(ch1.change_duration, 0x113245);
assert_eq!(ch1.receiver_category, 1);
assert_eq!(ch1.change_type, ChangeType::MajorMultiplexAdded);
assert_eq!(ch1.message_id, 0x83);
let inv = ch1.invariant_ts.as_ref().unwrap();
assert_eq!(inv.tsid, 0xDEAD);
assert_eq!(inv.onid, 0xBEEF);
}
other => panic!("expected NetworkChangeNotify, got {other:?}"),
}
let mut out = vec![0u8; d.serialized_len()];
let n = d.serialize_into(&mut out).unwrap();
assert_eq!(
out[..n],
bytes[..],
"byte-exact re-serialize for TSDuck vector"
);
}
#[cfg(feature = "chrono")]
#[test]
fn chrono_accessors_decode_start_time_and_duration() {
use chrono::{Datelike, Timelike};
let sel: Vec<u8> = vec![
0x00, 0x01, 12, 0x10, 0x20, 0xE4, 0x09, 0x12, 0x34, 0x56, 0x01, 0x30, 0x45, 0x23, 0x40, ];
let bytes = wrap(0x07, &sel);
let d = ExtensionDescriptor::parse(&bytes).unwrap();
match &d.body {
ExtensionBody::NetworkChangeNotify(b) => {
assert_eq!(b.cells.len(), 1);
let ch = &b.cells[0].changes[0];
let utc = ch.start_time_of_change_utc().expect("should decode");
assert_eq!(utc.year(), 2018);
assert_eq!((utc.hour(), utc.minute(), utc.second()), (12, 34, 56));
let secs = ch.change_duration_secs().expect("should decode");
assert_eq!(secs, 5445); }
other => panic!("expected NetworkChangeNotify, got {other:?}"),
}
}
#[test]
fn change_type_full_range_round_trip() {
for v in 0u8..=0x0F {
let ct = ChangeType::from_u8(v);
assert_eq!(ct.to_u8(), v, "ChangeType round-trip failed for {v}");
}
}
#[test]
fn change_type_known_values() {
assert_eq!(ChangeType::from_u8(0), ChangeType::MessageOnly);
assert_eq!(ChangeType::from_u8(1), ChangeType::MinorDefault);
assert_eq!(ChangeType::from_u8(4), ChangeType::MinorReserved(4));
assert_eq!(ChangeType::from_u8(8), ChangeType::MajorDefault);
assert_eq!(ChangeType::from_u8(9), ChangeType::MajorFrequencyChanged);
assert_eq!(ChangeType::from_u8(10), ChangeType::MajorCoverageChanged);
assert_eq!(ChangeType::from_u8(11), ChangeType::MajorMultiplexAdded);
assert_eq!(ChangeType::from_u8(12), ChangeType::MajorReserved(12));
assert_eq!(ChangeType::MessageOnly.name(), "message only");
assert_eq!(ChangeType::MajorDefault.name(), "major - default");
assert_eq!(ChangeType::MinorReserved(5).name(), "reserved (minor)");
assert_eq!(ChangeType::MajorReserved(13).name(), "reserved (major)");
}
}