use crate::descriptors::{descriptor_body, write_descriptor_header};
use crate::error::{Error, Result};
use broadcast_common::{Parse, Serialize};
pub const TAG: u8 = 0x15;
const HEADER_LEN: usize = 2;
const FIXED_LEN: usize = 8;
const SELECTOR_LENGTH_FLAG: u8 = 0x80;
const SELECTOR_VALUE_MASK: u8 = 0x7F;
const SELECTOR_VALUE_BITS: u32 = 7;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum ModulationSystemType {
Dvbs2,
Dvbt2,
Dvbc2,
DvbNgh,
Dvbs2x,
Reserved(u8),
}
impl ModulationSystemType {
#[must_use]
pub fn from_u8(v: u8) -> Self {
match v {
0x00 => Self::Dvbs2,
0x01 => Self::Dvbt2,
0x02 => Self::Dvbc2,
0x03 => Self::DvbNgh,
0x04 => Self::Dvbs2x,
_ => Self::Reserved(v),
}
}
#[must_use]
pub const fn to_u8(self) -> u8 {
match self {
Self::Dvbs2 => 0x00,
Self::Dvbt2 => 0x01,
Self::Dvbc2 => 0x02,
Self::DvbNgh => 0x03,
Self::Dvbs2x => 0x04,
Self::Reserved(v) => v,
}
}
#[must_use]
pub fn name(self) -> &'static str {
match self {
Self::Dvbs2 => "DVB-S2",
Self::Dvbt2 => "DVB-T2",
Self::Dvbc2 => "DVB-C2",
Self::DvbNgh => "DVB-NGH",
Self::Dvbs2x => "DVB-S2X",
Self::Reserved(_) => "reserved",
}
}
}
broadcast_common::impl_spec_display!(ModulationSystemType, Reserved);
const PHY_HIGH_SHIFT: u32 = 8;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[non_exhaustive]
pub enum PhyStreamId {
Isi(u16),
Plp(u16),
DataSlicePlp {
data_slice_id: u8,
plp_id: u8,
},
TimeSliceIsi {
time_slice: u8,
isi: u8,
},
Unspecified(u16),
}
impl PhyStreamId {
#[must_use]
pub fn decode(system: ModulationSystemType, raw: u16) -> Self {
let [hi, lo] = raw.to_be_bytes();
match system {
ModulationSystemType::Dvbs2 => Self::Isi(raw),
ModulationSystemType::Dvbt2 | ModulationSystemType::DvbNgh => Self::Plp(raw),
ModulationSystemType::Dvbc2 => Self::DataSlicePlp {
data_slice_id: hi,
plp_id: lo,
},
ModulationSystemType::Dvbs2x => Self::TimeSliceIsi {
time_slice: hi,
isi: lo,
},
ModulationSystemType::Reserved(_) => Self::Unspecified(raw),
}
}
#[must_use]
pub const fn to_u16(self) -> u16 {
match self {
Self::Isi(v) | Self::Plp(v) | Self::Unspecified(v) => v,
Self::DataSlicePlp {
data_slice_id,
plp_id,
} => ((data_slice_id as u16) << PHY_HIGH_SHIFT) | plp_id as u16,
Self::TimeSliceIsi { time_slice, isi } => {
((time_slice as u16) << PHY_HIGH_SHIFT) | isi as u16
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[non_exhaustive]
pub enum GenericStreamSelector<'a> {
Flags(u8),
Bytes(&'a [u8]),
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
pub struct IpMacGenericStreamLocationDescriptor<'a> {
pub interactive_network_id: u16,
pub modulation_system_type: ModulationSystemType,
pub modulation_system_id: u16,
pub phy_stream_id: u16,
pub selector: GenericStreamSelector<'a>,
pub trailing: &'a [u8],
}
impl<'a> IpMacGenericStreamLocationDescriptor<'a> {
#[must_use]
pub fn phy_stream(&self) -> PhyStreamId {
PhyStreamId::decode(self.modulation_system_type, self.phy_stream_id)
}
fn selector_len(&self) -> usize {
match self.selector {
GenericStreamSelector::Flags(_) => 1,
GenericStreamSelector::Bytes(b) => 1 + b.len(),
}
}
}
impl<'a> Parse<'a> for IpMacGenericStreamLocationDescriptor<'a> {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = descriptor_body(
bytes,
TAG,
"IpMacGenericStreamLocationDescriptor",
"unexpected tag for IP/MAC_generic_stream_location_descriptor",
)?;
let (f, rest) = body
.split_first_chunk::<FIXED_LEN>()
.ok_or(Error::InvalidDescriptor {
tag: TAG,
reason: "IP/MAC_generic_stream_location_descriptor body shorter than 8 bytes",
})?;
let value = f[7] & SELECTOR_VALUE_MASK;
let (selector, trailing) = if f[7] & SELECTOR_LENGTH_FLAG == 0 {
(GenericStreamSelector::Flags(value), rest)
} else {
let n = usize::from(value);
if rest.len() < n {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "selector_length runs past descriptor end",
});
}
let (sel, trailing) = rest.split_at(n);
(GenericStreamSelector::Bytes(sel), trailing)
};
Ok(Self {
interactive_network_id: u16::from_be_bytes([f[0], f[1]]),
modulation_system_type: ModulationSystemType::from_u8(f[2]),
modulation_system_id: u16::from_be_bytes([f[3], f[4]]),
phy_stream_id: u16::from_be_bytes([f[5], f[6]]),
selector,
trailing,
})
}
}
impl Serialize for IpMacGenericStreamLocationDescriptor<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
HEADER_LEN + FIXED_LEN - 1 + self.selector_len() + self.trailing.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(),
});
}
let (flag, value, sel): (u8, u8, &[u8]) = match self.selector {
GenericStreamSelector::Flags(flags) => (
0,
broadcast_common::len::fit_mask(flags, SELECTOR_VALUE_MASK, "selector_flags")?,
&[],
),
GenericStreamSelector::Bytes(b) => (
SELECTOR_LENGTH_FLAG,
u8::try_from(broadcast_common::len::fit_bits(
b.len() as u64,
SELECTOR_VALUE_BITS,
"selector_length",
)?)
.map_err(|_| Error::InvalidDescriptor {
tag: TAG,
reason: "selector_length does not fit",
})?,
b,
),
};
write_descriptor_header(buf, TAG, len - HEADER_LEN)?;
buf[2..4].copy_from_slice(&self.interactive_network_id.to_be_bytes());
buf[4] = self.modulation_system_type.to_u8();
buf[5..7].copy_from_slice(&self.modulation_system_id.to_be_bytes());
buf[7..9].copy_from_slice(&self.phy_stream_id.to_be_bytes());
buf[9] = flag | value;
let sel_end = 10 + sel.len();
buf[10..sel_end].copy_from_slice(sel);
buf[sel_end..len].copy_from_slice(self.trailing);
Ok(len)
}
}
impl<'a> crate::traits::DescriptorDef<'a> for IpMacGenericStreamLocationDescriptor<'a> {
const TAG: u8 = TAG;
const NAME: &'static str = "IP_MAC_GENERIC_STREAM_LOCATION";
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_and_round_trip_flags_selector() {
let bytes = [TAG, 8, 0x12, 0x34, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00];
let d = IpMacGenericStreamLocationDescriptor::parse(&bytes).unwrap();
assert_eq!(d.interactive_network_id, 0x1234);
assert_eq!(d.modulation_system_type, ModulationSystemType::Dvbt2);
assert_eq!(d.selector, GenericStreamSelector::Flags(0));
let mut out = vec![0u8; d.serialized_len()];
d.serialize_into(&mut out).unwrap();
assert_eq!(out, bytes);
}
#[test]
fn parse_and_round_trip_byte_selector() {
let bytes = [
TAG, 12, 0x12, 0x34, 0xAB, 0x78, 0x94, 0x45, 0x68, 0x84, 0x12, 0x34, 0x56, 0x78,
];
let d = IpMacGenericStreamLocationDescriptor::parse(&bytes).unwrap();
assert_eq!(
d.modulation_system_type,
ModulationSystemType::Reserved(0xAB)
);
assert_eq!(d.modulation_system_id, 0x7894);
assert_eq!(d.phy_stream_id, 0x4568);
assert_eq!(
d.selector,
GenericStreamSelector::Bytes(&[0x12, 0x34, 0x56, 0x78])
);
let mut out = vec![0u8; d.serialized_len()];
d.serialize_into(&mut out).unwrap();
assert_eq!(out, bytes);
}
#[test]
fn trailing_round_trip_and_errors() {
let bytes = [TAG, 9, 0, 1, 4, 0, 2, 0, 3, 0x05, 0xEE];
let d = IpMacGenericStreamLocationDescriptor::parse(&bytes).unwrap();
assert_eq!(d.modulation_system_type, ModulationSystemType::Dvbs2x);
assert_eq!(d.selector, GenericStreamSelector::Flags(5));
assert_eq!(d.trailing, &[0xEE]);
let mut out = vec![0u8; d.serialized_len()];
d.serialize_into(&mut out).unwrap();
assert_eq!(out, bytes);
assert!(
IpMacGenericStreamLocationDescriptor::parse(&[TAG, 9, 0, 1, 4, 0, 2, 0, 3, 0x83, 1])
.is_err()
);
assert!(
IpMacGenericStreamLocationDescriptor::parse(&[TAG, 7, 0, 0, 0, 0, 0, 0, 0]).is_err()
);
}
#[test]
fn serialize_rejects_over_range_selector() {
let d = IpMacGenericStreamLocationDescriptor {
interactive_network_id: 0,
modulation_system_type: ModulationSystemType::Dvbs2,
modulation_system_id: 0,
phy_stream_id: 0,
selector: GenericStreamSelector::Flags(0x80),
trailing: &[],
};
let mut out = vec![0u8; d.serialized_len()];
assert!(d.serialize_into(&mut out).is_err());
let big = [0u8; 128];
let d = IpMacGenericStreamLocationDescriptor {
selector: GenericStreamSelector::Bytes(&big),
..d
};
let mut out = vec![0u8; d.serialized_len()];
assert!(d.serialize_into(&mut out).is_err());
}
#[test]
fn phy_stream_id_sub_fields_per_table_35() {
let mk = |t, id| IpMacGenericStreamLocationDescriptor {
interactive_network_id: 0,
modulation_system_type: t,
modulation_system_id: 0,
phy_stream_id: id,
selector: GenericStreamSelector::Flags(0),
trailing: &[],
};
assert_eq!(
mk(ModulationSystemType::Dvbs2, 0x1234).phy_stream(),
PhyStreamId::Isi(0x1234)
);
assert_eq!(
mk(ModulationSystemType::DvbNgh, 0x1234).phy_stream(),
PhyStreamId::Plp(0x1234)
);
assert_eq!(
mk(ModulationSystemType::Dvbc2, 0x1234).phy_stream(),
PhyStreamId::DataSlicePlp {
data_slice_id: 0x12,
plp_id: 0x34
}
);
assert_eq!(
mk(ModulationSystemType::Dvbs2x, 0x1234).phy_stream(),
PhyStreamId::TimeSliceIsi {
time_slice: 0x12,
isi: 0x34
}
);
assert_eq!(
mk(ModulationSystemType::Reserved(9), 0x1234).phy_stream(),
PhyStreamId::Unspecified(0x1234)
);
for t in [
ModulationSystemType::Dvbs2,
ModulationSystemType::Dvbt2,
ModulationSystemType::Dvbc2,
ModulationSystemType::DvbNgh,
ModulationSystemType::Dvbs2x,
ModulationSystemType::Reserved(7),
] {
for raw in [0u16, 0x00FF, 0xFF00, 0xABCD, 0xFFFF] {
assert_eq!(PhyStreamId::decode(t, raw).to_u16(), raw);
}
}
}
#[test]
fn modulation_system_type_is_lossless() {
for b in 0..=u8::MAX {
assert_eq!(ModulationSystemType::from_u8(b).to_u8(), b);
}
assert_eq!(ModulationSystemType::Dvbt2.to_string(), "DVB-T2");
}
}