use crate::error::{Error, Result};
use crate::traits::Descriptor;
use dvb_common::{Parse, Serialize};
pub const TAG: u8 = 0x44;
const HEADER_LEN: usize = 2;
const BODY_LEN: u8 = 11;
const RESERVED_FU_MASK: u16 = 0xFFF0;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum FecOuter {
NotDefined,
NoOuterFec,
ReedSolomon204_188,
Reserved(u8),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum Modulation {
NotDefined,
Qam16,
Qam32,
Qam64,
Qam128,
Qam256,
Reserved(u8),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum FecInner {
NotDefined,
Rate1_2,
Rate2_3,
Rate3_4,
Rate5_6,
Rate7_8,
Rate8_9,
Rate3_5,
Rate4_5,
Rate9_10,
NoConvCoding,
Reserved(u8),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct CableDeliverySystemDescriptor {
pub frequency_bcd: u32,
pub fec_outer: FecOuter,
pub modulation: Modulation,
pub symbol_rate_bcd: u32,
pub fec_inner: FecInner,
}
fn parse_fec_outer(raw: u8) -> FecOuter {
match raw {
0x00 => FecOuter::NotDefined,
0x01 => FecOuter::NoOuterFec,
0x02 => FecOuter::ReedSolomon204_188,
other => FecOuter::Reserved(other),
}
}
fn parse_modulation(raw: u8) -> Modulation {
match raw {
0x00 => Modulation::NotDefined,
0x01 => Modulation::Qam16,
0x02 => Modulation::Qam32,
0x03 => Modulation::Qam64,
0x04 => Modulation::Qam128,
0x05 => Modulation::Qam256,
other => Modulation::Reserved(other),
}
}
fn parse_fec_inner(raw: u8) -> FecInner {
match raw {
0x00 => FecInner::NotDefined,
0x01 => FecInner::Rate1_2,
0x02 => FecInner::Rate2_3,
0x03 => FecInner::Rate3_4,
0x04 => FecInner::Rate5_6,
0x05 => FecInner::Rate7_8,
0x06 => FecInner::Rate8_9,
0x07 => FecInner::Rate3_5,
0x08 => FecInner::Rate4_5,
0x09 => FecInner::Rate9_10,
0x0F => FecInner::NoConvCoding,
other => FecInner::Reserved(other),
}
}
fn serialize_fec_outer(fec: FecOuter) -> u8 {
match fec {
FecOuter::NotDefined => 0x00,
FecOuter::NoOuterFec => 0x01,
FecOuter::ReedSolomon204_188 => 0x02,
FecOuter::Reserved(v) => v,
}
}
fn serialize_modulation(m: Modulation) -> u8 {
match m {
Modulation::NotDefined => 0x00,
Modulation::Qam16 => 0x01,
Modulation::Qam32 => 0x02,
Modulation::Qam64 => 0x03,
Modulation::Qam128 => 0x04,
Modulation::Qam256 => 0x05,
Modulation::Reserved(v) => v,
}
}
fn serialize_fec_inner(fec: FecInner) -> u8 {
match fec {
FecInner::NotDefined => 0x00,
FecInner::Rate1_2 => 0x01,
FecInner::Rate2_3 => 0x02,
FecInner::Rate3_4 => 0x03,
FecInner::Rate5_6 => 0x04,
FecInner::Rate7_8 => 0x05,
FecInner::Rate8_9 => 0x06,
FecInner::Rate3_5 => 0x07,
FecInner::Rate4_5 => 0x08,
FecInner::Rate9_10 => 0x09,
FecInner::NoConvCoding => 0x0F,
FecInner::Reserved(v) => v,
}
}
impl<'a> Parse<'a> for CableDeliverySystemDescriptor {
type Error = crate::error::Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
if bytes.len() < HEADER_LEN + BODY_LEN as usize {
return Err(Error::BufferTooShort {
need: HEADER_LEN + BODY_LEN as usize,
have: bytes.len(),
what: "CableDeliverySystemDescriptor",
});
}
if bytes[0] != TAG {
return Err(Error::InvalidDescriptor {
tag: bytes[0],
reason: "unexpected tag for cable_delivery_system_descriptor",
});
}
let length = bytes[1];
if length != BODY_LEN {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "body length must equal 11",
});
}
let frequency_bcd = u32::from_be_bytes(bytes[2..6].try_into().unwrap());
let bytes_6_7 = u16::from_be_bytes([bytes[6], bytes[7]]);
let fec_outer_raw = (bytes_6_7 & !RESERVED_FU_MASK) as u8;
let modulation_byte = bytes[8];
let spec_value = u32::from_be_bytes([0, bytes[9], bytes[10], bytes[11]]);
let symbol_rate_bcd = (spec_value << 4) | ((bytes[12] >> 4) & 0x0F) as u32;
let fec_inner_raw = bytes[12] & 0x0F;
Ok(Self {
frequency_bcd,
fec_outer: parse_fec_outer(fec_outer_raw),
modulation: parse_modulation(modulation_byte),
symbol_rate_bcd,
fec_inner: parse_fec_inner(fec_inner_raw),
})
}
}
impl Serialize for CableDeliverySystemDescriptor {
type Error = crate::error::Error;
fn serialized_len(&self) -> usize {
HEADER_LEN + BODY_LEN as usize
}
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] = BODY_LEN;
buf[2..6].copy_from_slice(&self.frequency_bcd.to_be_bytes());
let reserved_fu = RESERVED_FU_MASK;
let fec_outer_byte = reserved_fu | serialize_fec_outer(self.fec_outer) as u16;
let [fu_hi, fec_lo] = fec_outer_byte.to_be_bytes();
buf[6] = fu_hi;
buf[7] = fec_lo;
buf[8] = serialize_modulation(self.modulation);
let spec_value = self.symbol_rate_bcd >> 4;
buf[9] = (spec_value >> 16) as u8;
buf[10] = (spec_value >> 8) as u8;
buf[11] = spec_value as u8;
buf[12] = ((self.symbol_rate_bcd & 0x0F) as u8) << 4 | serialize_fec_inner(self.fec_inner);
Ok(len)
}
}
impl<'a> Descriptor<'a> for CableDeliverySystemDescriptor {
const TAG: u8 = TAG;
fn descriptor_length(&self) -> u8 {
BODY_LEN
}
}
impl<'a> crate::traits::DescriptorDef<'a> for CableDeliverySystemDescriptor {
const TAG: u8 = TAG;
const NAME: &'static str = "CABLE_DELIVERY_SYSTEM";
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_extracts_frequency_bcd() {
let raw: [u8; 13] = [
TAG, BODY_LEN, 0x03, 0x46, 0x00, 0x00, 0xFF, 0xF1, 0x05, 0x00, 0x00, 0x00, 0x03,
];
let d = CableDeliverySystemDescriptor::parse(&raw).unwrap();
assert_eq!(d.frequency_bcd, 0x03460000);
}
#[test]
fn parse_extracts_modulation_qam256() {
let raw: [u8; 13] = [
TAG, BODY_LEN, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x05, 0x00, 0x00, 0x00, 0x00,
];
let d = CableDeliverySystemDescriptor::parse(&raw).unwrap();
assert_eq!(d.modulation, Modulation::Qam256);
}
#[test]
fn parse_extracts_fec_outer_reed_solomon() {
let raw: [u8; 13] = [
TAG, BODY_LEN, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let d = CableDeliverySystemDescriptor::parse(&raw).unwrap();
assert_eq!(d.fec_outer, FecOuter::ReedSolomon204_188);
}
#[test]
fn parse_extracts_fec_inner_rate_3_4() {
let raw: [u8; 13] = [
TAG, BODY_LEN, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03,
];
let d = CableDeliverySystemDescriptor::parse(&raw).unwrap();
assert_eq!(d.fec_inner, FecInner::Rate3_4);
}
#[test]
fn parse_extracts_symbol_rate_bcd() {
let raw: [u8; 13] = [
TAG, BODY_LEN, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x06, 0x87, 0x50, 0x00,
];
let d = CableDeliverySystemDescriptor::parse(&raw).unwrap();
assert_eq!(d.symbol_rate_bcd, 0x0687500);
}
#[test]
fn parse_preserves_reserved_modulation_in_reserved_variant() {
let raw: [u8; 13] = [
TAG, BODY_LEN, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x0A, 0x00, 0x00, 0x00, 0x00,
];
let d = CableDeliverySystemDescriptor::parse(&raw).unwrap();
assert_eq!(d.modulation, Modulation::Reserved(0x0A));
}
#[test]
fn parse_rejects_wrong_tag() {
let raw: [u8; 13] = [
0x5B, BODY_LEN, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00,
];
assert!(matches!(
CableDeliverySystemDescriptor::parse(&raw).unwrap_err(),
Error::InvalidDescriptor { tag: 0x5B, .. }
));
}
#[test]
fn parse_rejects_wrong_length() {
let raw: [u8; 13] = [
TAG, 12, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00,
];
assert!(matches!(
CableDeliverySystemDescriptor::parse(&raw).unwrap_err(),
Error::InvalidDescriptor { tag: TAG, .. }
));
}
#[test]
fn serialize_round_trip() {
let d = CableDeliverySystemDescriptor {
frequency_bcd: 0x03460000,
fec_outer: FecOuter::ReedSolomon204_188,
modulation: Modulation::Qam256,
symbol_rate_bcd: 0x0687500,
fec_inner: FecInner::Rate3_4,
};
let mut buf = vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
let parsed = CableDeliverySystemDescriptor::parse(&buf).unwrap();
assert_eq!(parsed, d);
}
#[test]
fn enum_round_trip_covers_every_defined_variant() {
for fec_outer in [
FecOuter::NotDefined,
FecOuter::NoOuterFec,
FecOuter::ReedSolomon204_188,
] {
let v = serialize_fec_outer(fec_outer);
assert_eq!(parse_fec_outer(v), fec_outer);
}
for mod_ in [
Modulation::NotDefined,
Modulation::Qam16,
Modulation::Qam32,
Modulation::Qam64,
Modulation::Qam128,
Modulation::Qam256,
] {
let v = serialize_modulation(mod_);
assert_eq!(parse_modulation(v), mod_);
}
for fec_inner in [
FecInner::NotDefined,
FecInner::Rate1_2,
FecInner::Rate2_3,
FecInner::Rate3_4,
FecInner::Rate5_6,
FecInner::Rate7_8,
FecInner::Rate8_9,
FecInner::Rate3_5,
FecInner::Rate4_5,
FecInner::Rate9_10,
FecInner::NoConvCoding,
] {
let v = serialize_fec_inner(fec_inner);
assert_eq!(parse_fec_inner(v), fec_inner);
}
}
}