use super::descriptor_body;
use crate::error::{Error, Result};
use dvb_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 ORIGIN_TYPE_MASK: u8 = 0x01;
const RESERVED_HANDOVER_MASK: u8 = 0x0E;
const TARGET_LISTED_MASK: u8 = 0x80;
const EVENT_SIMULCAST_MASK: u8 = 0x40;
const RESERVED_EVENT_MASK: 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 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",
}
}
}
#[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 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",
}
}
}
#[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 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",
}
}
}
#[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 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",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct MobileHandOverInfo {
pub hand_over_type: HandOverType,
pub origin_type: bool,
pub network_id: Option<u16>,
pub initial_service_id: Option<u16>,
}
impl MobileHandOverInfo {
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();
if buf.len() < len {
return Err(Error::OutputBufferTooSmall {
need: len,
have: buf.len(),
});
}
let flags_byte = (self.hand_over_type.to_u8() << 4)
| RESERVED_HANDOVER_MASK
| 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))]
pub struct EventLinkageInfo {
pub target_event_id: u16,
pub target_listed: bool,
pub event_simulcast: bool,
}
impl EventLinkageInfo {
const SERIALIZED_LEN: usize = 3;
fn serialized_len(&self) -> usize {
Self::SERIALIZED_LEN
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
if buf.len() < Self::SERIALIZED_LEN {
return Err(Error::OutputBufferTooSmall {
need: Self::SERIALIZED_LEN,
have: buf.len(),
});
}
buf[0..2].copy_from_slice(&self.target_event_id.to_be_bytes());
let mut byte2: u8 = RESERVED_EVENT_MASK;
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))]
pub enum TargetId {
UserDefined {
user_defined_id: u16,
},
Dvb {
target_id_type: TargetIdType,
target_transport_stream_id: Option<u16>,
target_original_network_id: Option<u16>,
target_service_id: Option<u16>,
},
}
impl TargetId {
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();
if buf.len() < len {
return Err(Error::OutputBufferTooSmall {
need: len,
have: 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_type: TargetIdType,
pub original_network_id_flag: bool,
pub service_id_flag: bool,
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;
if buf.len() < flags_byte_len {
return Err(Error::OutputBufferTooSmall {
need: flags_byte_len,
have: buf.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 |= (self.link_type.to_u8() & 0x03) << 4;
byte2 |= (self.target_id_type.to_u8() & 0x03) << 2;
if self.original_network_id_flag {
byte2 |= EXT_ONID_FLAG_MASK;
}
if self.service_id_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;
if buf.len() < total {
return Err(Error::OutputBufferTooSmall {
need: total,
have: buf.len(),
});
}
buf[0] = loop_len as u8;
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) => {
if buf.len() < b.len() {
return Err(Error::OutputBufferTooSmall {
need: b.len(),
have: buf.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 = u16::from_be_bytes([bytes[pos], bytes[pos + 1]]);
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",
});
}
Some(u16::from_be_bytes([bytes[pos], bytes[pos + 1]]))
} else {
None
};
Ok(MobileHandOverInfo {
hand_over_type,
origin_type,
network_id,
initial_service_id,
})
}
fn parse_event_linkage(bytes: &[u8]) -> Result<EventLinkageInfo> {
if bytes.len() < 3 {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "event linkage info needs 3 bytes",
});
}
let target_event_id = u16::from_be_bytes([bytes[0], bytes[1]]);
let target_listed = (bytes[2] & TARGET_LISTED_MASK) != 0;
let event_simulcast = (bytes[2] & EVENT_SIMULCAST_MASK) != 0;
Ok(EventLinkageInfo {
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;
if bytes.len() < loop_end {
return Err(Error::BufferTooShort {
need: loop_end,
have: bytes.len(),
what: "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 v = u16::from_be_bytes([bytes[*p], bytes[*p + 1]]);
*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 }
} 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_type,
original_network_id_flag,
service_id_flag,
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",
)?;
if body.len() < FIXED_FIELDS_LEN {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "linkage_descriptor body shorter than minimum 7 bytes",
});
}
let transport_stream_id = u16::from_be_bytes([body[0], body[1]]);
let original_network_id = u16::from_be_bytes([body[2], body[3]]);
let service_id = u16::from_be_bytes([body[4], body[5]]);
let linkage_type_raw = body[6];
let linkage_type = LinkageType::from_u8(linkage_type_raw);
let tail = &body[FIXED_FIELDS_LEN..];
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();
if buf.len() < len {
return Err(Error::OutputBufferTooSmall {
need: len,
have: buf.len(),
});
}
buf[0] = TAG;
buf[1] = (len - HEADER_LEN) as u8;
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_type, TargetIdType::UseUserDefinedId);
assert_eq!(
e.target_id,
TargetId::UserDefined {
user_defined_id: 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_type, TargetIdType::UseTargetTransportStreamId);
assert!(e.original_network_id_flag);
assert!(!e.service_id_flag);
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 {
hand_over_type: HandOverType::DvbAssociatedService,
origin_type: false,
network_id: Some(0x0044),
initial_service_id: 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 {
target_event_id: 0x1234,
target_listed: true,
event_simulcast: 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_type: TargetIdType::UseTargetTransportStreamId,
original_network_id_flag: true,
service_id_flag: true,
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 {
target_event_id: 0x0000,
target_listed: false,
event_simulcast: 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 {
hand_over_type: HandOverType::Reserved(0),
origin_type: true,
network_id: None,
initial_service_id: 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 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}");
}
}
}