use super::BitSlice;
use bitvec::prelude::*;
use num_enum::TryFromPrimitive;
use std::fmt::{Display, Formatter};
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct BBHeader<'a>(&'a [u8; BBHeader::LEN]);
lazy_static::lazy_static! {
static ref CRC8: crc::Crc<u8> = crc::Crc::<u8>::new(&crc::CRC_8_DVB_S2);
}
impl BBHeader<'_> {
pub const LEN: usize = 10;
pub fn new(data: &[u8; BBHeader::LEN]) -> BBHeader<'_> {
BBHeader(data)
}
fn matype1(&self) -> &BitSlice {
BitSlice::from_slice(&self.0[..1])
}
pub fn tsgs(&self) -> TsGs {
TsGs::try_from(self.matype1()[..2].load_be::<u8>()).unwrap()
}
pub fn is_gse_hem(&self) -> bool {
matches!(self.tsgs(), TsGs::GseHem)
}
pub fn sismis(&self) -> SisMis {
SisMis::try_from(self.matype1()[2..3].load_be::<u8>()).unwrap()
}
pub fn ccmacm(&self) -> CcmAcm {
CcmAcm::try_from(self.matype1()[3..4].load_be::<u8>()).unwrap()
}
pub fn issyi(&self) -> bool {
self.matype1()[4]
}
pub fn npd(&self) -> bool {
self.matype1()[5]
}
pub fn gse_lite(&self) -> bool {
self.npd()
}
pub fn rolloff(&self) -> RollOff {
RollOff::try_from_primitive(self.matype1()[6..8].load_be::<u8>()).unwrap()
}
pub fn isi(&self) -> u8 {
self.0[1]
}
pub fn upl(&self) -> Option<u16> {
if self.is_gse_hem() {
None
} else {
Some(u16::from_be_bytes(self.0[2..4].try_into().unwrap()))
}
}
pub fn issy(&self) -> Option<[u8; 3]> {
if self.is_gse_hem() && self.issyi() {
let mut field = [0; 3];
field[0] = self.0[2];
field[1] = self.0[3];
field[2] = self.0[6];
Some(field)
} else {
None
}
}
pub fn dfl(&self) -> u16 {
u16::from_be_bytes(self.0[4..6].try_into().unwrap())
}
pub fn sync(&self) -> Option<u8> {
if self.is_gse_hem() {
None
} else {
Some(self.0[6])
}
}
pub fn syncd(&self) -> u16 {
u16::from_be_bytes(self.0[7..9].try_into().unwrap())
}
pub fn crc8(&self) -> u8 {
self.0[BBHeader::LEN - 1]
}
pub fn compute_crc8(&self) -> u8 {
let crc = CRC8.checksum(&self.0[..BBHeader::LEN - 1]);
if self.is_gse_hem() {
crc ^ 1
} else {
crc
}
}
pub fn crc_is_valid(&self) -> bool {
self.crc8() == self.compute_crc8()
}
}
impl Display for BBHeader<'_> {
fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
write!(
f,
"BBHEADER(TS/GS = {}, SIS/MIS = {}, CCM/ACM = {}, ISSYI = {}, \
NPD/GSE-Lite = {}, {}, ISI = {}, ",
self.tsgs(),
self.sismis(),
self.ccmacm(),
self.issyi(),
self.npd(),
self.rolloff(),
self.isi(),
)?;
if let Some(upl) = self.upl() {
write!(f, "UPL = {} bits, ", upl)?;
}
if let Some(issy) = self.issy() {
let issy = (u32::from(issy[0]) << 16) | (u32::from(issy[1]) << 8) | u32::from(issy[2]);
write!(f, "ISSY = {:#06x}, ", issy)?;
}
write!(f, "DFL = {} bits, ", self.dfl())?;
if let Some(sync) = self.sync() {
write!(f, "SYNC = {:#04x}, ", sync)?;
}
write!(f, "SYNCD = {:#06x})", self.syncd())
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, TryFromPrimitive)]
#[repr(u8)]
pub enum TsGs {
Transport = 0b11,
GenericPacketized = 0b00,
GenericContinuous = 0b01,
GseHem = 0b10,
}
impl Display for TsGs {
fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
write!(
f,
"{}",
match self {
TsGs::Transport => "Transport",
TsGs::GenericPacketized => "Generic packetized",
TsGs::GenericContinuous => "Generic continuous",
TsGs::GseHem => "GSE-HEM",
}
)
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, TryFromPrimitive)]
#[repr(u8)]
pub enum SisMis {
Sis = 0b1,
Mis = 0b0,
}
impl Display for SisMis {
fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
write!(
f,
"{}",
match self {
SisMis::Sis => "single",
SisMis::Mis => "multiple",
}
)
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, TryFromPrimitive)]
#[repr(u8)]
pub enum CcmAcm {
Ccm = 0b1,
Acm = 0b0,
}
impl Display for CcmAcm {
fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
write!(
f,
"{}",
match self {
CcmAcm::Ccm => "CCM",
CcmAcm::Acm => "ACM",
}
)
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, TryFromPrimitive)]
#[repr(u8)]
pub enum RollOff {
Ro0_35 = 0b00,
Ro0_25 = 0b01,
Ro0_20 = 0b10,
Reserved = 0b11,
}
impl Display for RollOff {
fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
write!(
f,
"α = {}",
match self {
RollOff::Ro0_35 => "0.35",
RollOff::Ro0_25 => "0.25",
RollOff::Ro0_20 => "0.20",
RollOff::Reserved => "reserved",
}
)
}
}
#[cfg(test)]
mod test {
use super::*;
use hex_literal::hex;
use test_log::test;
const CONTINUOUS_GSE_HEADER: [u8; 10] = hex!("72 00 00 00 02 f0 00 00 00 15");
const GSE_HEM_HEADER: [u8; 10] = hex!("b2 00 00 00 02 f0 00 00 00 87");
const GSE_HEM_HEADER_ISSY: [u8; 10] = hex!("ba 00 12 34 02 f0 56 02 11 7c");
#[test]
fn continuous_gse_header() {
let header = BBHeader::new(&CONTINUOUS_GSE_HEADER);
assert_eq!(
format!("{}", header),
"BBHEADER(TS/GS = Generic continuous, SIS/MIS = single, CCM/ACM = CCM, \
ISSYI = false, NPD/GSE-Lite = false, α = 0.20, ISI = 0, UPL = 0 bits, \
DFL = 752 bits, SYNC = 0x00, SYNCD = 0x0000)"
);
assert_eq!(header.tsgs(), TsGs::GenericContinuous);
assert_eq!(header.sismis(), SisMis::Sis);
assert_eq!(header.ccmacm(), CcmAcm::Ccm);
assert!(!header.issyi());
assert!(!header.npd());
assert!(!header.gse_lite());
assert_eq!(header.rolloff(), RollOff::Ro0_20);
assert_eq!(header.isi(), 0);
assert_eq!(header.issy(), None);
assert_eq!(header.upl(), Some(0));
assert_eq!(header.dfl(), 752);
assert_eq!(header.sync(), Some(0));
assert_eq!(header.syncd(), 0);
assert_eq!(header.crc8(), CONTINUOUS_GSE_HEADER[9]);
assert_eq!(header.compute_crc8(), header.crc8());
assert!(header.crc_is_valid());
}
#[test]
fn gse_hem_header() {
let header = BBHeader::new(&GSE_HEM_HEADER);
assert_eq!(
format!("{}", header),
"BBHEADER(TS/GS = GSE-HEM, SIS/MIS = single, CCM/ACM = CCM, \
ISSYI = false, NPD/GSE-Lite = false, α = 0.20, ISI = 0, \
DFL = 752 bits, SYNCD = 0x0000)"
);
assert_eq!(header.tsgs(), TsGs::GseHem);
assert_eq!(header.sismis(), SisMis::Sis);
assert_eq!(header.ccmacm(), CcmAcm::Ccm);
assert!(!header.issyi());
assert!(!header.npd());
assert!(!header.gse_lite());
assert_eq!(header.rolloff(), RollOff::Ro0_20);
assert_eq!(header.isi(), 0);
assert_eq!(header.issy(), None);
assert_eq!(header.upl(), None);
assert_eq!(header.dfl(), 752);
assert_eq!(header.sync(), None);
assert_eq!(header.syncd(), 0);
assert_eq!(header.crc8(), GSE_HEM_HEADER[9]);
assert_eq!(header.compute_crc8(), header.crc8());
assert!(header.crc_is_valid());
}
#[test]
fn gse_hem_header_issy() {
let header = BBHeader::new(&GSE_HEM_HEADER_ISSY);
assert_eq!(
format!("{}", header),
"BBHEADER(TS/GS = GSE-HEM, SIS/MIS = single, CCM/ACM = CCM, \
ISSYI = true, NPD/GSE-Lite = false, α = 0.20, ISI = 0, \
ISSY = 0x123456, DFL = 752 bits, SYNCD = 0x0211)"
);
assert_eq!(header.tsgs(), TsGs::GseHem);
assert_eq!(header.sismis(), SisMis::Sis);
assert_eq!(header.ccmacm(), CcmAcm::Ccm);
assert!(header.issyi());
assert!(!header.npd());
assert!(!header.gse_lite());
assert_eq!(header.rolloff(), RollOff::Ro0_20);
assert_eq!(header.isi(), 0);
assert_eq!(header.issy(), Some([0x12, 0x34, 0x56]));
assert_eq!(header.upl(), None);
assert_eq!(header.dfl(), 752);
assert_eq!(header.sync(), None);
assert_eq!(header.syncd(), 0x211);
assert_eq!(header.crc8(), GSE_HEM_HEADER_ISSY[9]);
assert_eq!(header.compute_crc8(), header.crc8());
assert!(header.crc_is_valid());
}
}
#[cfg(test)]
mod proptests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn doesnt_panic(header: [u8; 10]) {
let header = BBHeader::new(&header);
header.tsgs();
header.sismis();
header.ccmacm();
header.issyi();
header.npd();
header.gse_lite();
header.rolloff();
header.isi();
header.upl();
header.dfl();
header.sync();
header.syncd();
header.crc8();
header.compute_crc8();
header.crc_is_valid();
let _ = format!("{header}");
}
}
}