use super::descriptor_body;
use crate::error::{Error, Result};
use alloc::vec::Vec;
use broadcast_common::len::{need_in, need_out};
use broadcast_common::{Parse, Serialize};
pub const TAG: u8 = 0x4A;
const HEADER_LEN: usize = 2;
const FIXED_FIELDS_LEN: usize = 7;
const HANDOVER_TYPE_MASK: u8 = 0xF0;
const HANDOVER_TYPE_VALUE_MASK: u8 = 0x0F;
const ORIGIN_TYPE_MASK: u8 = 0x01;
const RESERVED_HANDOVER_MASK: u8 = 0x0E;
const RESERVED_HANDOVER_SHIFT: u8 = 1;
pub const RESERVED_HANDOVER_DEFAULT: u8 = 0b111;
const TARGET_LISTED_MASK: u8 = 0x80;
const EVENT_SIMULCAST_MASK: u8 = 0x40;
const RESERVED_EVENT_MASK: u8 = 0x3F;
pub const RESERVED_EVENT_DEFAULT: u8 = 0x3F;
const EXT_TARGET_LISTED_MASK: u8 = 0x80;
const EXT_EVENT_SIMULCAST_MASK: u8 = 0x40;
const EXT_LINK_TYPE_MASK: u8 = 0x30;
const EXT_TARGET_ID_TYPE_MASK: u8 = 0x0C;
const EXT_ONID_FLAG_MASK: u8 = 0x02;
const EXT_SID_FLAG_MASK: u8 = 0x01;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum LinkageType {
InformationService,
EpgService,
CaReplacementService,
TsContainingCompleteSi,
ServiceReplacementService,
DataBroadcastService,
RcsMap,
MobileHandOver,
SsuService,
TsContainingSsuBatOrNit,
IpMacNotificationService,
TsContainingIntBatOrNit,
EventLinkage,
ExtendedEventLinkage(u8),
DownloadableFontInfoLinkage,
NativeIpBootstrapMpeStream,
Reserved(u8),
}
impl LinkageType {
#[must_use]
pub fn from_u8(v: u8) -> Self {
match v {
0x01 => Self::InformationService,
0x02 => Self::EpgService,
0x03 => Self::CaReplacementService,
0x04 => Self::TsContainingCompleteSi,
0x05 => Self::ServiceReplacementService,
0x06 => Self::DataBroadcastService,
0x07 => Self::RcsMap,
0x08 => Self::MobileHandOver,
0x09 => Self::SsuService,
0x0A => Self::TsContainingSsuBatOrNit,
0x0B => Self::IpMacNotificationService,
0x0C => Self::TsContainingIntBatOrNit,
0x0D => Self::EventLinkage,
0x0E..=0x1F => Self::ExtendedEventLinkage(v),
0x20 => Self::DownloadableFontInfoLinkage,
0x21 => Self::NativeIpBootstrapMpeStream,
v => Self::Reserved(v),
}
}
#[must_use]
pub const fn to_u8(self) -> u8 {
match self {
Self::InformationService => 0x01,
Self::EpgService => 0x02,
Self::CaReplacementService => 0x03,
Self::TsContainingCompleteSi => 0x04,
Self::ServiceReplacementService => 0x05,
Self::DataBroadcastService => 0x06,
Self::RcsMap => 0x07,
Self::MobileHandOver => 0x08,
Self::SsuService => 0x09,
Self::TsContainingSsuBatOrNit => 0x0A,
Self::IpMacNotificationService => 0x0B,
Self::TsContainingIntBatOrNit => 0x0C,
Self::EventLinkage => 0x0D,
Self::ExtendedEventLinkage(v) => v,
Self::DownloadableFontInfoLinkage => 0x20,
Self::NativeIpBootstrapMpeStream => 0x21,
Self::Reserved(v) => v,
}
}
#[must_use]
pub fn name(self) -> &'static str {
match self {
Self::InformationService => "information service",
Self::EpgService => "EPG service",
Self::CaReplacementService => "CA replacement service",
Self::TsContainingCompleteSi => "TS containing complete network/bouquet SI",
Self::ServiceReplacementService => "service replacement service",
Self::DataBroadcastService => "data broadcast service",
Self::RcsMap => "RCS map",
Self::MobileHandOver => "mobile hand-over",
Self::SsuService => "SSU service",
Self::TsContainingSsuBatOrNit => "TS containing SSU BAT or NIT",
Self::IpMacNotificationService => "IP/MAC notification service",
Self::TsContainingIntBatOrNit => "TS containing INT BAT or NIT",
Self::EventLinkage => "event linkage",
Self::ExtendedEventLinkage(_) => "extended event linkage",
Self::DownloadableFontInfoLinkage => "downloadable font info linkage",
Self::NativeIpBootstrapMpeStream => "Native IP bootstrap MPE stream",
Self::Reserved(_) => "reserved",
}
}
}
broadcast_common::impl_spec_display!(LinkageType, ExtendedEventLinkage, Reserved);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum HandOverType {
DvbIdenticalNeighbouringCountry,
DvbLocalVariation,
DvbAssociatedService,
Reserved(u8),
}
impl HandOverType {
#[must_use]
pub fn from_u8(v: u8) -> Self {
match v {
0x1 => Self::DvbIdenticalNeighbouringCountry,
0x2 => Self::DvbLocalVariation,
0x3 => Self::DvbAssociatedService,
v => Self::Reserved(v),
}
}
#[must_use]
pub const fn to_u8(self) -> u8 {
match self {
Self::DvbIdenticalNeighbouringCountry => 0x1,
Self::DvbLocalVariation => 0x2,
Self::DvbAssociatedService => 0x3,
Self::Reserved(v) => v,
}
}
#[must_use]
pub fn name(self) -> &'static str {
match self {
Self::DvbIdenticalNeighbouringCountry => {
"DVB hand-over to an identical service in a neighbouring country"
}
Self::DvbLocalVariation => "DVB hand-over to a local variation of the same service",
Self::DvbAssociatedService => "DVB hand-over to an associated service",
Self::Reserved(_) => "reserved",
}
}
}
broadcast_common::impl_spec_display!(HandOverType, Reserved);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum LinkType {
SdOrUhd,
HdOrServiceFrameCompatible,
FrameCompatiblePlanoStereoscopic,
ServiceCompatiblePlanoStereoscopicMvc,
Reserved(u8),
}
impl LinkType {
#[must_use]
pub fn from_u8(v: u8) -> Self {
match v {
0 => Self::SdOrUhd,
1 => Self::HdOrServiceFrameCompatible,
2 => Self::FrameCompatiblePlanoStereoscopic,
3 => Self::ServiceCompatiblePlanoStereoscopicMvc,
v => Self::Reserved(v),
}
}
#[must_use]
pub const fn to_u8(self) -> u8 {
match self {
Self::SdOrUhd => 0,
Self::HdOrServiceFrameCompatible => 1,
Self::FrameCompatiblePlanoStereoscopic => 2,
Self::ServiceCompatiblePlanoStereoscopicMvc => 3,
Self::Reserved(v) => v,
}
}
#[must_use]
pub fn name(self) -> &'static str {
match self {
Self::SdOrUhd => "SD (0x0E) / UHD (0x0F)",
Self::HdOrServiceFrameCompatible => {
"HD (0x0E) / service frame compatible plano-stereoscopic (0x0F)"
}
Self::FrameCompatiblePlanoStereoscopic => {
"frame compatible plano-stereoscopic H.264/AVC"
}
Self::ServiceCompatiblePlanoStereoscopicMvc => {
"service compatible plano-stereoscopic MVC"
}
Self::Reserved(_) => "reserved",
}
}
}
broadcast_common::impl_spec_display!(LinkType, Reserved);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum TargetIdType {
UseTransportStreamId,
UseTargetTransportStreamId,
MatchAnyTransportStreamId,
UseUserDefinedId,
Reserved(u8),
}
impl TargetIdType {
#[must_use]
pub fn from_u8(v: u8) -> Self {
match v {
0 => Self::UseTransportStreamId,
1 => Self::UseTargetTransportStreamId,
2 => Self::MatchAnyTransportStreamId,
3 => Self::UseUserDefinedId,
v => Self::Reserved(v),
}
}
#[must_use]
pub const fn to_u8(self) -> u8 {
match self {
Self::UseTransportStreamId => 0,
Self::UseTargetTransportStreamId => 1,
Self::MatchAnyTransportStreamId => 2,
Self::UseUserDefinedId => 3,
Self::Reserved(v) => v,
}
}
#[must_use]
pub fn name(self) -> &'static str {
match self {
Self::UseTransportStreamId => "use transport_stream_id",
Self::UseTargetTransportStreamId => "use target_transport_stream_id",
Self::MatchAnyTransportStreamId => "match any transport_stream_id (wildcard)",
Self::UseUserDefinedId => "use user_defined_id",
Self::Reserved(_) => "reserved",
}
}
}
broadcast_common::impl_spec_display!(TargetIdType, Reserved);
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct MobileHandOverInfo {
pub reserved_future_use: u8,
pub hand_over_type: HandOverType,
pub origin_type: bool,
pub network_id: Option<u16>,
pub initial_service_id: Option<u16>,
}
impl MobileHandOverInfo {
#[must_use]
pub const fn new(
hand_over_type: HandOverType,
origin_type: bool,
network_id: Option<u16>,
initial_service_id: Option<u16>,
) -> Self {
Self {
reserved_future_use: RESERVED_HANDOVER_DEFAULT,
hand_over_type,
origin_type,
network_id,
initial_service_id,
}
}
fn serialized_len(&self) -> usize {
1 + self.network_id.map_or(0, |_| 2) + self.initial_service_id.map_or(0, |_| 2)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len();
need_out(buf, len)?;
let flags_byte = (broadcast_common::len::fit_mask(
self.hand_over_type.to_u8(),
HANDOVER_TYPE_VALUE_MASK,
"hand_over_type",
)? << 4)
| (broadcast_common::len::fit_mask(
self.reserved_future_use,
RESERVED_HANDOVER_MASK >> RESERVED_HANDOVER_SHIFT,
"reserved_future_use",
)? << RESERVED_HANDOVER_SHIFT)
| u8::from(self.origin_type);
buf[0] = flags_byte;
let mut pos = 1;
if let Some(nid) = self.network_id {
buf[pos..pos + 2].copy_from_slice(&nid.to_be_bytes());
pos += 2;
}
if let Some(sid) = self.initial_service_id {
buf[pos..pos + 2].copy_from_slice(&sid.to_be_bytes());
}
Ok(len)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct EventLinkageInfo {
pub reserved_future_use: u8,
pub target_event_id: u16,
pub target_listed: bool,
pub event_simulcast: bool,
}
impl EventLinkageInfo {
#[must_use]
pub const fn new(target_event_id: u16, target_listed: bool, event_simulcast: bool) -> Self {
Self {
reserved_future_use: RESERVED_EVENT_DEFAULT,
target_event_id,
target_listed,
event_simulcast,
}
}
const SERIALIZED_LEN: usize = 3;
fn serialized_len(&self) -> usize {
Self::SERIALIZED_LEN
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
need_out(buf, Self::SERIALIZED_LEN)?;
buf[0..2].copy_from_slice(&self.target_event_id.to_be_bytes());
let mut byte2: u8 = broadcast_common::len::fit_mask(
self.reserved_future_use,
RESERVED_EVENT_MASK,
"reserved_future_use",
)?;
if self.target_listed {
byte2 |= TARGET_LISTED_MASK;
}
if self.event_simulcast {
byte2 |= EVENT_SIMULCAST_MASK;
}
buf[2] = byte2;
Ok(Self::SERIALIZED_LEN)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum TargetId {
#[non_exhaustive]
UserDefined {
user_defined_id: u16,
original_network_id_flag: bool,
service_id_flag: bool,
},
Dvb {
target_id_type: TargetIdType,
target_transport_stream_id: Option<u16>,
target_original_network_id: Option<u16>,
target_service_id: Option<u16>,
},
}
impl TargetId {
#[must_use]
pub const fn user_defined(user_defined_id: u16) -> Self {
TargetId::UserDefined {
user_defined_id,
original_network_id_flag: false,
service_id_flag: false,
}
}
#[must_use]
pub fn target_id_type(&self) -> TargetIdType {
match self {
TargetId::UserDefined { .. } => TargetIdType::UseUserDefinedId,
TargetId::Dvb { target_id_type, .. } => *target_id_type,
}
}
#[must_use]
pub fn target_original_network_id(&self) -> Option<u16> {
match self {
TargetId::UserDefined { .. } => None,
TargetId::Dvb {
target_original_network_id,
..
} => *target_original_network_id,
}
}
#[must_use]
pub fn target_service_id(&self) -> Option<u16> {
match self {
TargetId::UserDefined { .. } => None,
TargetId::Dvb {
target_service_id, ..
} => *target_service_id,
}
}
fn serialized_len(&self) -> usize {
match self {
TargetId::UserDefined { .. } => 2,
TargetId::Dvb {
target_transport_stream_id,
target_original_network_id,
target_service_id,
..
} => {
usize::from(target_transport_stream_id.is_some()) * 2
+ usize::from(target_original_network_id.is_some()) * 2
+ usize::from(target_service_id.is_some()) * 2
}
}
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len();
need_out(buf, len)?;
match self {
TargetId::UserDefined {
user_defined_id, ..
} => {
buf[..2].copy_from_slice(&user_defined_id.to_be_bytes());
}
TargetId::Dvb {
target_transport_stream_id,
target_original_network_id,
target_service_id,
..
} => {
let ts_len = target_transport_stream_id.map_or(0, |_| 2);
let onid_len = target_original_network_id.map_or(0, |_| 2);
if let Some(ts_id) = target_transport_stream_id {
buf[..2].copy_from_slice(&ts_id.to_be_bytes());
}
if let Some(onid) = target_original_network_id {
buf[ts_len..ts_len + 2].copy_from_slice(&onid.to_be_bytes());
}
if let Some(sid) = target_service_id {
let off = ts_len + onid_len;
buf[off..off + 2].copy_from_slice(&sid.to_be_bytes());
}
}
}
Ok(len)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ExtendedEventLinkageEntry {
pub target_event_id: u16,
pub target_listed: bool,
pub event_simulcast: bool,
pub link_type: LinkType,
pub target_id: TargetId,
}
impl ExtendedEventLinkageEntry {
fn serialized_len(&self) -> usize {
2 + 1 + self.target_id.serialized_len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let flags_byte_len = 3;
need_out(buf, flags_byte_len)?;
buf[0..2].copy_from_slice(&self.target_event_id.to_be_bytes());
let mut byte2: u8 = 0;
if self.target_listed {
byte2 |= EXT_TARGET_LISTED_MASK;
}
if self.event_simulcast {
byte2 |= EXT_EVENT_SIMULCAST_MASK;
}
byte2 |= broadcast_common::len::fit_mask(self.link_type.to_u8(), 0x03, "link_type")? << 4;
byte2 |= broadcast_common::len::fit_mask(
self.target_id.target_id_type().to_u8(),
0x03,
"target_id_type",
)? << 2;
let (onid_flag, sid_flag) = match &self.target_id {
TargetId::UserDefined {
original_network_id_flag,
service_id_flag,
..
} => (*original_network_id_flag, *service_id_flag),
other => (
other.target_original_network_id().is_some(),
other.target_service_id().is_some(),
),
};
if onid_flag {
byte2 |= EXT_ONID_FLAG_MASK;
}
if sid_flag {
byte2 |= EXT_SID_FLAG_MASK;
}
buf[2] = byte2;
let tid_written = self.target_id.serialize_into(&mut buf[3..])?;
Ok(3 + tid_written)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ExtendedEventLinkageInfo {
pub entries: Vec<ExtendedEventLinkageEntry>,
}
impl ExtendedEventLinkageInfo {
fn serialized_len(&self) -> usize {
1 + self
.entries
.iter()
.map(|e| e.serialized_len())
.sum::<usize>()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let loop_len: usize = self.entries.iter().map(|e| e.serialized_len()).sum();
let total = 1 + loop_len;
need_out(buf, total)?;
buf[0] = broadcast_common::len::fit_u8(loop_len, "linkage_extended_event_loop_length")?;
let mut pos = 1;
for entry in &self.entries {
let written = entry.serialize_into(&mut buf[pos..])?;
pos += written;
}
Ok(total)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum LinkageData<'a> {
MobileHandOver(MobileHandOverInfo),
EventLinkage(EventLinkageInfo),
ExtendedEventLinkage(ExtendedEventLinkageInfo),
None,
#[cfg_attr(feature = "serde", serde(borrow))]
Other(&'a [u8]),
}
impl LinkageData<'_> {
fn serialized_len(&self) -> usize {
match self {
LinkageData::MobileHandOver(m) => m.serialized_len(),
LinkageData::EventLinkage(e) => e.serialized_len(),
LinkageData::ExtendedEventLinkage(x) => x.serialized_len(),
LinkageData::None => 0,
LinkageData::Other(b) => b.len(),
}
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
match self {
LinkageData::MobileHandOver(m) => m.serialize_into(buf),
LinkageData::EventLinkage(e) => e.serialize_into(buf),
LinkageData::ExtendedEventLinkage(x) => x.serialize_into(buf),
LinkageData::None => Ok(0),
LinkageData::Other(b) => {
need_out(buf, b.len())?;
buf[..b.len()].copy_from_slice(b);
Ok(b.len())
}
}
}
}
fn parse_mobile_handover(bytes: &[u8], end: usize) -> Result<MobileHandOverInfo> {
if end < 1 {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "mobile hand-over info needs at least flags byte",
});
}
let flags_byte = bytes[0];
let hand_over_type = HandOverType::from_u8((flags_byte & HANDOVER_TYPE_MASK) >> 4);
let origin_type = (flags_byte & ORIGIN_TYPE_MASK) != 0;
let mut pos = 1;
let network_id = if matches!(hand_over_type.to_u8(), 0x01..=0x03) {
if pos + 2 > end {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "mobile hand-over info with gated network_id needs at least 3 bytes",
});
}
let (nid_bytes, _) = bytes
.get(pos..)
.and_then(|s| s.split_first_chunk::<2>())
.ok_or(Error::InvalidDescriptor {
tag: TAG,
reason: "mobile hand-over info with gated network_id needs at least 3 bytes",
})?;
let nid = u16::from_be_bytes(*nid_bytes);
pos += 2;
Some(nid)
} else {
None
};
let initial_service_id = if !origin_type {
if pos + 2 > end {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "mobile hand-over info with origin_type=NIT needs initial_service_id",
});
}
let (sid_bytes, _) = bytes
.get(pos..)
.and_then(|s| s.split_first_chunk::<2>())
.ok_or(Error::InvalidDescriptor {
tag: TAG,
reason: "mobile hand-over info with origin_type=NIT needs initial_service_id",
})?;
Some(u16::from_be_bytes(*sid_bytes))
} else {
None
};
Ok(MobileHandOverInfo {
reserved_future_use: (flags_byte & RESERVED_HANDOVER_MASK) >> RESERVED_HANDOVER_SHIFT,
hand_over_type,
origin_type,
network_id,
initial_service_id,
})
}
fn parse_event_linkage(bytes: &[u8]) -> Result<EventLinkageInfo> {
let (hdr, _) = bytes
.split_first_chunk::<3>()
.ok_or(Error::InvalidDescriptor {
tag: TAG,
reason: "event linkage info needs 3 bytes",
})?;
let target_event_id = u16::from_be_bytes([hdr[0], hdr[1]]);
let target_listed = (hdr[2] & TARGET_LISTED_MASK) != 0;
let event_simulcast = (hdr[2] & EVENT_SIMULCAST_MASK) != 0;
Ok(EventLinkageInfo {
reserved_future_use: hdr[2] & RESERVED_EVENT_MASK,
target_event_id,
target_listed,
event_simulcast,
})
}
fn parse_extended_event_linkage(bytes: &[u8]) -> Result<ExtendedEventLinkageInfo> {
if bytes.is_empty() {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "extended event linkage info needs at least loop_length byte",
});
}
let loop_length = bytes[0] as usize;
let loop_end = 1 + loop_length;
need_in(bytes, loop_end, "extended event linkage info loop")?;
let mut entries = Vec::new();
let mut pos = 1;
let read_u16 = |p: &mut usize| -> Result<u16> {
if *p + 2 > loop_end {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "extended event linkage entry truncated (need u16)",
});
}
let (b, _) = bytes[*p..]
.split_first_chunk::<2>()
.ok_or(Error::InvalidDescriptor {
tag: TAG,
reason: "extended event linkage entry truncated (need u16)",
})?;
let v = u16::from_be_bytes(*b);
*p += 2;
Ok(v)
};
while pos < loop_end {
let target_event_id = read_u16(&mut pos)?;
if pos >= loop_end {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "extended event linkage entry truncated (need flags byte)",
});
}
let fb = bytes[pos];
pos += 1;
let target_listed = (fb & EXT_TARGET_LISTED_MASK) != 0;
let event_simulcast = (fb & EXT_EVENT_SIMULCAST_MASK) != 0;
let link_type = LinkType::from_u8((fb & EXT_LINK_TYPE_MASK) >> 4);
let target_id_type = TargetIdType::from_u8((fb & EXT_TARGET_ID_TYPE_MASK) >> 2);
let original_network_id_flag = (fb & EXT_ONID_FLAG_MASK) != 0;
let service_id_flag = (fb & EXT_SID_FLAG_MASK) != 0;
let target_id = if target_id_type == TargetIdType::UseUserDefinedId {
let user_defined_id = read_u16(&mut pos)?;
TargetId::UserDefined {
user_defined_id,
original_network_id_flag,
service_id_flag,
}
} else {
let target_transport_stream_id =
if target_id_type == TargetIdType::UseTargetTransportStreamId {
Some(read_u16(&mut pos)?)
} else {
None
};
let target_original_network_id = if original_network_id_flag {
Some(read_u16(&mut pos)?)
} else {
None
};
let target_service_id = if service_id_flag {
Some(read_u16(&mut pos)?)
} else {
None
};
TargetId::Dvb {
target_id_type,
target_transport_stream_id,
target_original_network_id,
target_service_id,
}
};
entries.push(ExtendedEventLinkageEntry {
target_event_id,
target_listed,
event_simulcast,
link_type,
target_id,
});
}
Ok(ExtendedEventLinkageInfo { entries })
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
pub struct LinkageDescriptor<'a> {
pub transport_stream_id: u16,
pub original_network_id: u16,
pub service_id: u16,
pub linkage_type: LinkageType,
pub linkage_data: LinkageData<'a>,
pub private_data: &'a [u8],
}
const LINKAGE_TYPES_WITH_OTHER: &[u8] = &[0x09, 0x0A, 0x0B, 0x0C, 0x20, 0x21];
impl<'a> Parse<'a> for LinkageDescriptor<'a> {
type Error = crate::error::Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = descriptor_body(
bytes,
TAG,
"LinkageDescriptor",
"unexpected tag for linkage_descriptor",
)?;
let (fixed, tail) =
body.split_first_chunk::<FIXED_FIELDS_LEN>()
.ok_or(Error::InvalidDescriptor {
tag: TAG,
reason: "linkage_descriptor body shorter than minimum 7 bytes",
})?;
let transport_stream_id = u16::from_be_bytes([fixed[0], fixed[1]]);
let original_network_id = u16::from_be_bytes([fixed[2], fixed[3]]);
let service_id = u16::from_be_bytes([fixed[4], fixed[5]]);
let linkage_type_raw = fixed[6];
let linkage_type = LinkageType::from_u8(linkage_type_raw);
let tail_len = tail.len();
let (linkage_data, private_data) = match linkage_type_raw {
0x08 => {
let info = parse_mobile_handover(tail, tail_len)?;
let consumed = info.serialized_len();
(LinkageData::MobileHandOver(info), &tail[consumed..])
}
0x0D => {
let info = parse_event_linkage(tail)?;
let consumed = EventLinkageInfo::SERIALIZED_LEN;
(LinkageData::EventLinkage(info), &tail[consumed..])
}
0x0E..=0x1F => {
let info = parse_extended_event_linkage(tail)?;
let consumed = info.serialized_len();
(LinkageData::ExtendedEventLinkage(info), &tail[consumed..])
}
lt if LINKAGE_TYPES_WITH_OTHER.contains(<) || (0x80..=0xFE).contains(<) => {
(LinkageData::Other(tail), &[] as &[u8])
}
_ => (LinkageData::None, tail),
};
Ok(Self {
transport_stream_id,
original_network_id,
service_id,
linkage_type,
linkage_data,
private_data,
})
}
}
impl Serialize for LinkageDescriptor<'_> {
type Error = crate::error::Error;
fn serialized_len(&self) -> usize {
HEADER_LEN + FIXED_FIELDS_LEN + self.linkage_data.serialized_len() + self.private_data.len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len();
need_out(buf, len)?;
crate::descriptors::write_descriptor_header(buf, TAG, len - HEADER_LEN)?;
let bs = HEADER_LEN;
buf[bs..bs + 2].copy_from_slice(&self.transport_stream_id.to_be_bytes());
buf[bs + 2..bs + 4].copy_from_slice(&self.original_network_id.to_be_bytes());
buf[bs + 4..bs + 6].copy_from_slice(&self.service_id.to_be_bytes());
buf[bs + 6] = self.linkage_type.to_u8();
let ld_start = bs + FIXED_FIELDS_LEN;
let ld_written = self.linkage_data.serialize_into(&mut buf[ld_start..])?;
let pd_start = ld_start + ld_written;
if !self.private_data.is_empty() {
buf[pd_start..pd_start + self.private_data.len()].copy_from_slice(self.private_data);
}
Ok(len)
}
}
impl<'a> crate::traits::DescriptorDef<'a> for LinkageDescriptor<'a> {
const TAG: u8 = TAG;
const NAME: &'static str = "LINKAGE";
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_extracts_tsid_onid_sid() {
let bytes = [
TAG, 0x09, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x05, 0xAA, 0xBB,
];
let d = LinkageDescriptor::parse(&bytes).unwrap();
assert_eq!(d.transport_stream_id, 0x0001);
assert_eq!(d.original_network_id, 0x0002);
assert_eq!(d.service_id, 0x0003);
}
#[test]
fn parse_extracts_linkage_type() {
let bytes = [TAG, 0x07, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x06];
let d = LinkageDescriptor::parse(&bytes).unwrap();
assert_eq!(d.linkage_type, LinkageType::DataBroadcastService);
}
#[test]
fn parse_none_type_preserves_private_data() {
let bytes = [
TAG, 0x0A, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x05, 0xAA, 0xBB, 0xCC,
];
let d = LinkageDescriptor::parse(&bytes).unwrap();
assert!(matches!(d.linkage_data, LinkageData::None));
assert_eq!(d.private_data, &[0xAA, 0xBB, 0xCC]);
}
#[test]
fn parse_accepts_empty_private_data() {
let bytes = [TAG, 0x07, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x05];
let d = LinkageDescriptor::parse(&bytes).unwrap();
assert!(d.private_data.is_empty());
}
#[test]
fn parse_mobile_handover_with_initial_sid() {
let bytes = [
TAG, 0x0E, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x08, 0x12, 0x00, 0x10, 0x00, 0x20, 0xDE, 0xAD, ];
let d = LinkageDescriptor::parse(&bytes).unwrap();
assert_eq!(d.linkage_type, LinkageType::MobileHandOver);
match &d.linkage_data {
LinkageData::MobileHandOver(m) => {
assert_eq!(
m.hand_over_type,
HandOverType::DvbIdenticalNeighbouringCountry
);
assert!(!m.origin_type);
assert_eq!(m.network_id, Some(0x0010));
assert_eq!(m.initial_service_id, Some(0x0020));
}
other => panic!("expected MobileHandOver, got {other:?}"),
}
assert_eq!(d.private_data, &[0xDE, 0xAD]);
}
#[test]
fn parse_mobile_handover_sdt_no_initial_sid() {
let bytes = [
TAG, 0x0C, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x08, 0x2F, 0x00, 0x10, 0xCA, 0xFE, ];
let d = LinkageDescriptor::parse(&bytes).unwrap();
match &d.linkage_data {
LinkageData::MobileHandOver(m) => {
assert_eq!(m.hand_over_type, HandOverType::DvbLocalVariation);
assert!(m.origin_type);
assert_eq!(m.network_id, Some(0x0010));
assert_eq!(m.initial_service_id, None);
}
other => panic!("expected MobileHandOver, got {other:?}"),
}
assert_eq!(d.private_data, &[0xCA, 0xFE]);
}
#[test]
fn parse_event_linkage() {
let bytes = [
TAG, 0x0C, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x0D, 0xAB, 0xCD, 0xC0, 0xBE, 0xEF, ];
let d = LinkageDescriptor::parse(&bytes).unwrap();
match &d.linkage_data {
LinkageData::EventLinkage(e) => {
assert_eq!(e.target_event_id, 0xABCD);
assert!(e.target_listed);
assert!(e.event_simulcast);
}
other => panic!("expected EventLinkage, got {other:?}"),
}
assert_eq!(d.private_data, &[0xBE, 0xEF]);
}
#[test]
fn parse_extended_event_linkage_user_defined() {
let bytes = [
TAG, 0x0E, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x0E, 0x05, 0x12, 0x34, 0xCC, 0x56, 0x78, 0xCC, ];
let d = LinkageDescriptor::parse(&bytes).unwrap();
match &d.linkage_data {
LinkageData::ExtendedEventLinkage(x) => {
assert_eq!(x.entries.len(), 1);
let e = &x.entries[0];
assert_eq!(e.target_event_id, 0x1234);
assert!(e.target_listed);
assert!(e.event_simulcast);
assert_eq!(e.link_type, LinkType::SdOrUhd);
assert_eq!(e.target_id.target_id_type(), TargetIdType::UseUserDefinedId);
assert_eq!(e.target_id, TargetId::user_defined(0x5678));
}
other => panic!("expected ExtendedEventLinkage, got {other:?}"),
}
assert_eq!(d.private_data, &[0xCC]);
}
#[test]
fn parse_extended_event_linkage_dvb_target() {
let bytes = [
TAG, 0x0F, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x0F, 0x07, 0xAA, 0xBB, 0x26, 0x00, 0x11, 0x00, 0x22, ];
let d = LinkageDescriptor::parse(&bytes).unwrap();
match &d.linkage_data {
LinkageData::ExtendedEventLinkage(x) => {
assert_eq!(x.entries.len(), 1);
let e = &x.entries[0];
assert_eq!(
e.target_id.target_id_type(),
TargetIdType::UseTargetTransportStreamId
);
assert!(e.target_id.target_original_network_id().is_some());
assert!(e.target_id.target_service_id().is_none());
assert_eq!(
e.target_id,
TargetId::Dvb {
target_id_type: TargetIdType::UseTargetTransportStreamId,
target_transport_stream_id: Some(0x0011),
target_original_network_id: Some(0x0022),
target_service_id: None,
}
);
}
other => panic!("expected ExtendedEventLinkage, got {other:?}"),
}
assert_eq!(d.private_data, &[] as &[u8]);
}
#[test]
fn parse_other_type_captures_raw_tail() {
let bytes = [
TAG, 0x0A, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x0B, 0xAA, 0xBB, 0xCC, ];
let d = LinkageDescriptor::parse(&bytes).unwrap();
match &d.linkage_data {
LinkageData::Other(b) => assert_eq!(*b, &[0xAA, 0xBB, 0xCC]),
other => panic!("expected Other, got {other:?}"),
}
assert!(d.private_data.is_empty());
}
#[test]
fn parse_user_defined_type_is_other() {
let bytes = [
TAG, 0x09, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x90, 0xFF, 0xFE, ];
let d = LinkageDescriptor::parse(&bytes).unwrap();
assert!(matches!(d.linkage_data, LinkageData::Other(_)));
}
#[test]
fn parse_rejects_wrong_tag() {
let err = LinkageDescriptor::parse(&[0x4B, 0x07, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x05])
.unwrap_err();
assert!(matches!(err, Error::InvalidDescriptor { tag: 0x4B, .. }));
}
#[test]
fn parse_rejects_body_shorter_than_seven() {
let bytes = [TAG, 0x05, 0x00, 0x01, 0x00, 0x02, 0x00];
let err = LinkageDescriptor::parse(&bytes).unwrap_err();
assert!(matches!(err, Error::InvalidDescriptor { tag: TAG, .. }));
}
#[test]
fn parse_rejects_truncated_buffer() {
let err = LinkageDescriptor::parse(&[TAG]).unwrap_err();
assert!(matches!(err, Error::BufferTooShort(..)));
}
#[test]
fn parse_rejects_truncated_mobile_handover() {
let bytes = [
TAG, 0x08, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x08, 0x10, 0x00, 0x10, ];
let err = LinkageDescriptor::parse(&bytes).unwrap_err();
assert!(
matches!(err, Error::InvalidDescriptor { .. }),
"expected InvalidDescriptor for truncated mobile hand-over, got {err:?}"
);
}
#[test]
fn serialize_round_trip_no_linkage_data() {
let d = LinkageDescriptor {
transport_stream_id: 0x1234,
original_network_id: 0x5678,
service_id: 0xABCD,
linkage_type: LinkageType::EpgService,
linkage_data: LinkageData::None,
private_data: &[],
};
let mut buf = vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
let re = LinkageDescriptor::parse(&buf).unwrap();
assert_eq!(d, re);
}
#[test]
fn serialize_round_trip_with_private_data() {
let d = LinkageDescriptor {
transport_stream_id: 0x0001,
original_network_id: 0x0002,
service_id: 0x0003,
linkage_type: LinkageType::ServiceReplacementService,
linkage_data: LinkageData::None,
private_data: &[0xDE, 0xAD, 0xBE, 0xEF],
};
let mut buf = vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
let re = LinkageDescriptor::parse(&buf).unwrap();
assert_eq!(d, re);
}
#[test]
fn serialize_round_trip_mobile_handover() {
let d = LinkageDescriptor {
transport_stream_id: 0x0001,
original_network_id: 0x0002,
service_id: 0x0003,
linkage_type: LinkageType::MobileHandOver,
linkage_data: LinkageData::MobileHandOver(MobileHandOverInfo::new(
HandOverType::DvbAssociatedService,
false,
Some(0x0044),
Some(0x0055),
)),
private_data: &[0xFF],
};
let mut buf = vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
let re = LinkageDescriptor::parse(&buf).unwrap();
assert_eq!(d, re);
}
#[test]
fn serialize_round_trip_event_linkage() {
let d = LinkageDescriptor {
transport_stream_id: 0x0001,
original_network_id: 0x0002,
service_id: 0x0003,
linkage_type: LinkageType::EventLinkage,
linkage_data: LinkageData::EventLinkage(EventLinkageInfo::new(0x1234, true, false)),
private_data: &[],
};
let mut buf = vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
let re = LinkageDescriptor::parse(&buf).unwrap();
assert_eq!(d, re);
}
#[test]
fn serialize_round_trip_extended_event_linkage() {
let d = LinkageDescriptor {
transport_stream_id: 0x0001,
original_network_id: 0x0002,
service_id: 0x0003,
linkage_type: LinkageType::ExtendedEventLinkage(0x0E),
linkage_data: LinkageData::ExtendedEventLinkage(ExtendedEventLinkageInfo {
entries: vec![ExtendedEventLinkageEntry {
target_event_id: 0xAAAA,
target_listed: true,
event_simulcast: true,
link_type: LinkType::HdOrServiceFrameCompatible,
target_id: TargetId::Dvb {
target_id_type: TargetIdType::UseTargetTransportStreamId,
target_transport_stream_id: Some(0x1111),
target_original_network_id: Some(0x2222),
target_service_id: Some(0x3333),
},
}],
}),
private_data: &[0xCC],
};
let mut buf = vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
let re = LinkageDescriptor::parse(&buf).unwrap();
assert_eq!(d, re);
}
#[test]
fn serialize_round_trip_other() {
let raw = [0xAA, 0xBB, 0xCC];
let d = LinkageDescriptor {
transport_stream_id: 0x0001,
original_network_id: 0x0002,
service_id: 0x0003,
linkage_type: LinkageType::IpMacNotificationService,
linkage_data: LinkageData::Other(&raw),
private_data: &[],
};
let mut buf = vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
let re = LinkageDescriptor::parse(&buf).unwrap();
assert_eq!(d, re);
}
#[test]
fn serialize_reserved_bits_are_set() {
let d = LinkageDescriptor {
transport_stream_id: 0x0001,
original_network_id: 0x0002,
service_id: 0x0003,
linkage_type: LinkageType::EventLinkage,
linkage_data: LinkageData::EventLinkage(EventLinkageInfo::new(0x0000, false, false)),
private_data: &[],
};
let mut buf = vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
assert_eq!(buf[11] & RESERVED_EVENT_MASK, RESERVED_EVENT_MASK);
let d2 = LinkageDescriptor {
transport_stream_id: 0x0001,
original_network_id: 0x0002,
service_id: 0x0003,
linkage_type: LinkageType::MobileHandOver,
linkage_data: LinkageData::MobileHandOver(MobileHandOverInfo::new(
HandOverType::Reserved(0),
true,
None,
None,
)),
private_data: &[],
};
let mut buf2 = vec![0u8; d2.serialized_len()];
d2.serialize_into(&mut buf2).unwrap();
assert_eq!(buf2[9] & RESERVED_HANDOVER_MASK, RESERVED_HANDOVER_MASK);
}
#[test]
fn reserved_preserved_handover_and_event() {
for r in 0u8..=7 {
for origin in [0u8, 1] {
let flags = (4 << 4) | (r << 1) | origin;
let mut bytes = vec![TAG, 0, 0, 1, 0, 2, 0, 3, 0x08, flags];
if origin == 0 {
bytes.extend_from_slice(&[0x00, 0x20]);
}
bytes[1] = (bytes.len() - 2) as u8;
let d = LinkageDescriptor::parse(&bytes).unwrap();
match &d.linkage_data {
LinkageData::MobileHandOver(m) => assert_eq!(m.reserved_future_use, r),
other => panic!("expected MobileHandOver, got {other:?}"),
}
let mut buf = vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
assert_eq!(buf, bytes);
}
}
for r in 0u8..=0x3F {
for hi in [0u8, 0x40, 0x80, 0xC0] {
let bytes = [TAG, 10, 0, 1, 0, 2, 0, 3, 0x0D, 0xAB, 0xCD, hi | r];
let d = LinkageDescriptor::parse(&bytes).unwrap();
match &d.linkage_data {
LinkageData::EventLinkage(e) => assert_eq!(e.reserved_future_use, r),
other => panic!("expected EventLinkage, got {other:?}"),
}
let mut buf = vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
assert_eq!(buf, bytes);
}
}
}
#[test]
fn default_is_spec_value() {
let m = MobileHandOverInfo::new(HandOverType::Reserved(0), true, None, None);
let mut b = [0u8; 1];
m.serialize_into(&mut b).unwrap();
assert_eq!(b[0], 0x0F);
let e = EventLinkageInfo::new(0, false, false);
let mut b = [0u8; 3];
e.serialize_into(&mut b).unwrap();
assert_eq!(b, [0, 0, 0x3F]);
}
#[test]
fn parse_mobile_handover_type4_no_network_id() {
let bytes = [
TAG, 0x0A, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x08, 0x4E, 0x00, 0x20, ];
let d = LinkageDescriptor::parse(&bytes).unwrap();
match &d.linkage_data {
LinkageData::MobileHandOver(m) => {
assert_eq!(m.hand_over_type, HandOverType::Reserved(4));
assert!(!m.origin_type);
assert_eq!(m.network_id, None);
assert_eq!(m.initial_service_id, Some(0x0020));
}
other => panic!("expected MobileHandOver, got {other:?}"),
}
}
#[test]
fn parse_mobile_handover_type1_network_id_present() {
let bytes = [
TAG, 0x0C, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x08, 0x1F, 0x00, 0x10, 0xCA, 0xFE, ];
let d = LinkageDescriptor::parse(&bytes).unwrap();
match &d.linkage_data {
LinkageData::MobileHandOver(m) => {
assert_eq!(
m.hand_over_type,
HandOverType::DvbIdenticalNeighbouringCountry
);
assert!(m.origin_type);
assert_eq!(m.network_id, Some(0x0010));
assert_eq!(m.initial_service_id, None);
}
other => panic!("expected MobileHandOver, got {other:?}"),
}
assert_eq!(d.private_data, &[0xCA, 0xFE]);
}
#[test]
fn linkage_type_full_range_round_trip() {
for b in 0..=0xFF_u8 {
let lt = LinkageType::from_u8(b);
assert_eq!(lt.to_u8(), b, "round-trip failed for byte 0x{b:02X}");
}
}
#[test]
fn hand_over_type_full_range_round_trip() {
for b in 0..=0xFF_u8 {
let ht = HandOverType::from_u8(b);
assert_eq!(ht.to_u8(), b, "round-trip failed for byte 0x{b:02X}");
}
}
#[test]
fn link_type_full_range_round_trip() {
for b in 0..=0xFF_u8 {
let lt = LinkType::from_u8(b);
assert_eq!(lt.to_u8(), b, "round-trip failed for byte 0x{b:02X}");
}
}
#[test]
fn target_id_type_full_range_round_trip() {
for b in 0..=0xFF_u8 {
let tt = TargetIdType::from_u8(b);
assert_eq!(tt.to_u8(), b, "round-trip failed for byte 0x{b:02X}");
}
}
#[test]
fn extended_event_linkage_flags_cannot_contradict_target_id() {
let entry = ExtendedEventLinkageEntry {
target_event_id: 0xAAAA,
target_listed: false,
event_simulcast: false,
link_type: LinkType::SdOrUhd,
target_id: TargetId::Dvb {
target_id_type: TargetIdType::UseTransportStreamId,
target_transport_stream_id: None,
target_original_network_id: Some(0x2222),
target_service_id: None,
},
};
let d = LinkageDescriptor {
transport_stream_id: 1,
original_network_id: 2,
service_id: 3,
linkage_type: LinkageType::ExtendedEventLinkage(0x0E),
linkage_data: LinkageData::ExtendedEventLinkage(ExtendedEventLinkageInfo {
entries: alloc::vec![entry],
}),
private_data: &[],
};
let mut buf = alloc::vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
let flags = buf[HEADER_LEN + 1 + 2 + 3];
assert_eq!(flags & EXT_ONID_FLAG_MASK, EXT_ONID_FLAG_MASK);
assert_eq!(flags & EXT_SID_FLAG_MASK, 0);
let re = LinkageDescriptor::parse(&buf).unwrap();
assert_eq!(d, re);
}
}