use super::*;
use crate::descriptors::satellite_delivery_system::{Polarization, RollOff};
impl<'a> ExtensionBodyDef<'a> for S2XSatelliteDeliverySystem<'a> {
const TAG_EXTENSION: u8 = 0x17;
const NAME: &'static str = "S2X_SATELLITE_DELIVERY_SYSTEM";
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum S2XMode {
Reserved0,
S2X,
S2XTimeSlicing,
S2XChannelBonding,
Reserved(u8),
}
impl S2XMode {
#[must_use]
pub fn from_u8(v: u8) -> Self {
match v {
0 => S2XMode::Reserved0,
1 => S2XMode::S2X,
2 => S2XMode::S2XTimeSlicing,
3 => S2XMode::S2XChannelBonding,
other => S2XMode::Reserved(other),
}
}
#[must_use]
pub fn to_u8(self) -> u8 {
match self {
S2XMode::Reserved0 => 0,
S2XMode::S2X => 1,
S2XMode::S2XTimeSlicing => 2,
S2XMode::S2XChannelBonding => 3,
S2XMode::Reserved(v) => v,
}
}
#[must_use]
pub fn name(self) -> &'static str {
match self {
S2XMode::Reserved0 => "reserved for future use",
S2XMode::S2X => "S2X",
S2XMode::S2XTimeSlicing => "S2X + time slicing",
S2XMode::S2XChannelBonding => "S2X + channel bonding",
S2XMode::Reserved(_) => "reserved",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum TsGsS2XMode {
GenericPacketized,
Gse,
GseHighEfficiency,
DvbTransportStream,
Reserved(u8),
}
impl TsGsS2XMode {
#[must_use]
pub fn from_u8(v: u8) -> Self {
match v {
0 => TsGsS2XMode::GenericPacketized,
1 => TsGsS2XMode::Gse,
2 => TsGsS2XMode::GseHighEfficiency,
3 => TsGsS2XMode::DvbTransportStream,
other => TsGsS2XMode::Reserved(other),
}
}
#[must_use]
pub fn to_u8(self) -> u8 {
match self {
TsGsS2XMode::GenericPacketized => 0,
TsGsS2XMode::Gse => 1,
TsGsS2XMode::GseHighEfficiency => 2,
TsGsS2XMode::DvbTransportStream => 3,
TsGsS2XMode::Reserved(v) => v,
}
}
#[must_use]
pub fn name(self) -> &'static str {
match self {
TsGsS2XMode::GenericPacketized => "generic packetized",
TsGsS2XMode::Gse => "GSE",
TsGsS2XMode::GseHighEfficiency => "GSE high efficiency mode",
TsGsS2XMode::DvbTransportStream => "DVB transport stream",
TsGsS2XMode::Reserved(_) => "reserved",
}
}
}
const RP_BROADCAST: u8 = 0x01;
const RP_INTERACTIVE: u8 = 0x02;
const RP_DSNG: u8 = 0x04;
const RP_PROFESSIONAL: u8 = 0x08;
const RP_VL_SNR: u8 = 0x10;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct S2XChannelBond {
pub frequency: u32,
pub orbital_position: u16,
pub west_east_flag: bool,
pub polarization: Polarization,
pub multiple_input_stream_flag: bool,
pub roll_off: RollOff,
pub symbol_rate: u32,
pub input_stream_identifier: Option<u8>,
}
impl S2XChannelBond {
#[must_use]
pub fn frequency_hz(&self) -> Option<u64> {
dvb_common::bcd::bcd_to_decimal(u64::from(self.frequency), 8).map(|v| v * 10_000)
}
#[must_use]
pub fn orbital_position_deg(&self) -> Option<f64> {
dvb_common::bcd::bcd_to_decimal(u64::from(self.orbital_position), 4)
.map(|tenths| tenths as f64 / 10.0)
}
#[must_use]
pub fn symbol_rate_sps(&self) -> Option<u64> {
dvb_common::bcd::bcd_to_decimal(u64::from(self.symbol_rate), 7).map(|v| v * 100)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
pub struct S2XSatelliteDeliverySystem<'a> {
pub receiver_profiles: u8,
pub s2x_mode: S2XMode,
pub scrambling_sequence_selector: bool,
pub ts_gs_s2x_mode: TsGsS2XMode,
pub scrambling_sequence_index: Option<u32>,
pub frequency: u32,
pub orbital_position: u16,
pub west_east_flag: bool,
pub polarization: Polarization,
pub multiple_input_stream_flag: bool,
pub roll_off: RollOff,
pub symbol_rate: u32,
pub input_stream_identifier: Option<u8>,
pub timeslice_number: Option<u8>,
pub channel_bonds: Vec<S2XChannelBond>,
pub reserved_tail: &'a [u8],
}
impl S2XSatelliteDeliverySystem<'_> {
#[must_use]
pub fn frequency_hz(&self) -> Option<u64> {
dvb_common::bcd::bcd_to_decimal(u64::from(self.frequency), 8).map(|v| v * 10_000)
}
#[must_use]
pub fn orbital_position_deg(&self) -> Option<f64> {
dvb_common::bcd::bcd_to_decimal(u64::from(self.orbital_position), 4)
.map(|tenths| tenths as f64 / 10.0)
}
#[must_use]
pub fn symbol_rate_sps(&self) -> Option<u64> {
dvb_common::bcd::bcd_to_decimal(u64::from(self.symbol_rate), 7).map(|v| v * 100)
}
#[must_use]
pub fn receiver_broadcast(&self) -> bool {
(self.receiver_profiles & RP_BROADCAST) != 0
}
#[must_use]
pub fn receiver_interactive(&self) -> bool {
(self.receiver_profiles & RP_INTERACTIVE) != 0
}
#[must_use]
pub fn receiver_dsng(&self) -> bool {
(self.receiver_profiles & RP_DSNG) != 0
}
#[must_use]
pub fn receiver_professional(&self) -> bool {
(self.receiver_profiles & RP_PROFESSIONAL) != 0
}
#[must_use]
pub fn receiver_vl_snr(&self) -> bool {
(self.receiver_profiles & RP_VL_SNR) != 0
}
}
const BOND_BASE_LEN: usize = S2X_PRIMARY_LEN;
fn parse_channel_common(
sel: &[u8],
pos: &mut usize,
) -> Result<(u32, u16, bool, Polarization, bool, RollOff, u32)> {
if sel.len() < *pos + BOND_BASE_LEN {
return Err(Error::BufferTooShort {
need: *pos + BOND_BASE_LEN,
have: sel.len(),
what: "S2X body",
});
}
let frequency = u32::from_be_bytes([sel[*pos], sel[*pos + 1], sel[*pos + 2], sel[*pos + 3]]);
let orbital_position = u16::from_be_bytes([sel[*pos + 4], sel[*pos + 5]]);
let pb = sel[*pos + 6];
let west_east_flag = (pb & 0x80) != 0;
let polarization = Polarization::from_u8((pb >> 5) & 0x03);
let multiple_input_stream_flag = (pb & 0x10) != 0;
let roll_off = RollOff::from_u8(pb & 0x07);
let symbol_rate = (u32::from(sel[*pos + 7] & 0x0F) << 24)
| (u32::from(sel[*pos + 8]) << 16)
| (u32::from(sel[*pos + 9]) << 8)
| u32::from(sel[*pos + 10]);
*pos += BOND_BASE_LEN;
Ok((
frequency,
orbital_position,
west_east_flag,
polarization,
multiple_input_stream_flag,
roll_off,
symbol_rate,
))
}
fn write_channel_common(
buf: &mut [u8],
p: &mut usize,
frequency: u32,
orbital_position: u16,
packed: u8,
symbol_rate: u32,
) {
buf[*p..*p + 4].copy_from_slice(&frequency.to_be_bytes());
buf[*p + 4..*p + 6].copy_from_slice(&orbital_position.to_be_bytes());
buf[*p + 6] = packed;
let sr = symbol_rate & 0x0FFF_FFFF;
buf[*p + 7] = (sr >> 24) as u8 & 0x0F;
buf[*p + 8] = (sr >> 16) as u8;
buf[*p + 9] = (sr >> 8) as u8;
buf[*p + 10] = sr as u8;
*p += BOND_BASE_LEN;
}
fn pack_we_pol_mis_ro(we: bool, pol: Polarization, mis: bool, ro: RollOff) -> u8 {
(u8::from(we) << 7) | ((pol.to_u8() & 0x03) << 5) | (u8::from(mis) << 4) | (ro.to_u8() & 0x07)
}
impl<'a> Parse<'a> for S2XSatelliteDeliverySystem<'a> {
type Error = crate::error::Error;
fn parse(sel: &'a [u8]) -> Result<Self> {
if sel.len() < 2 {
return Err(Error::BufferTooShort {
need: 2,
have: sel.len(),
what: "S2X body",
});
}
let receiver_profiles = sel[0] >> 3;
let b1 = sel[1];
let s2x_mode = S2XMode::from_u8((b1 >> 6) & 0x03);
let scrambling_sequence_selector = (b1 & 0x20) != 0;
let ts_gs_s2x_mode = TsGsS2XMode::from_u8(b1 & 0x03);
let mut pos = 2;
let scrambling_sequence_index = if scrambling_sequence_selector {
if sel.len() < pos + S2X_SCRAMBLING_LEN {
return Err(Error::BufferTooShort {
need: pos + S2X_SCRAMBLING_LEN,
have: sel.len(),
what: "S2X body",
});
}
let idx = (u32::from(sel[pos] & 0x03) << 16)
| (u32::from(sel[pos + 1]) << 8)
| u32::from(sel[pos + 2]);
pos += S2X_SCRAMBLING_LEN;
Some(idx)
} else {
None
};
let (
frequency,
orbital_position,
west_east_flag,
polarization,
multiple_input_stream_flag,
roll_off,
symbol_rate,
) = parse_channel_common(sel, &mut pos)?;
let input_stream_identifier = if multiple_input_stream_flag {
if sel.len() < pos + 1 {
return Err(Error::BufferTooShort {
need: pos + 1,
have: sel.len(),
what: "S2X body",
});
}
let isi = sel[pos];
pos += 1;
Some(isi)
} else {
None
};
let timeslice_number = if s2x_mode == S2XMode::S2XTimeSlicing {
if sel.len() < pos + 1 {
return Err(Error::BufferTooShort {
need: pos + 1,
have: sel.len(),
what: "S2X body",
});
}
let ts = sel[pos];
pos += 1;
Some(ts)
} else {
None
};
let (channel_bonds, reserved_tail) = if s2x_mode == S2XMode::S2XChannelBonding {
if sel.len() < pos + 1 {
return Err(Error::BufferTooShort {
need: pos + 1,
have: sel.len(),
what: "S2X body",
});
}
let bond_byte = sel[pos];
pos += 1;
let num_channel_bonds = (bond_byte & 0x01) as usize + 1;
let mut bonds = Vec::with_capacity(num_channel_bonds);
for _ in 0..num_channel_bonds {
let (freq, orb, we, pol, mis, ro, sr) = parse_channel_common(sel, &mut pos)?;
let isi = if mis {
if sel.len() < pos + 1 {
return Err(Error::BufferTooShort {
need: pos + 1,
have: sel.len(),
what: "S2X body",
});
}
let v = sel[pos];
pos += 1;
Some(v)
} else {
None
};
bonds.push(S2XChannelBond {
frequency: freq,
orbital_position: orb,
west_east_flag: we,
polarization: pol,
multiple_input_stream_flag: mis,
roll_off: ro,
symbol_rate: sr,
input_stream_identifier: isi,
});
}
(bonds, &sel[pos..])
} else {
(Vec::new(), &sel[pos..])
};
Ok(S2XSatelliteDeliverySystem {
receiver_profiles,
s2x_mode,
scrambling_sequence_selector,
ts_gs_s2x_mode,
scrambling_sequence_index,
frequency,
orbital_position,
west_east_flag,
polarization,
multiple_input_stream_flag,
roll_off,
symbol_rate,
input_stream_identifier,
timeslice_number,
channel_bonds,
reserved_tail,
})
}
}
impl Serialize for S2XSatelliteDeliverySystem<'_> {
type Error = crate::error::Error;
fn serialized_len(&self) -> usize {
let bond_len: usize = if self.s2x_mode == S2XMode::S2XChannelBonding {
1 + self
.channel_bonds
.iter()
.map(|b| BOND_BASE_LEN + usize::from(b.input_stream_identifier.is_some()))
.sum::<usize>()
} else {
0
};
2 + if self.scrambling_sequence_selector {
S2X_SCRAMBLING_LEN
} else {
0
} + S2X_PRIMARY_LEN
+ usize::from(self.input_stream_identifier.is_some())
+ usize::from(self.timeslice_number.is_some())
+ bond_len
+ self.reserved_tail.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] = self.receiver_profiles << 3;
buf[1] = ((self.s2x_mode.to_u8() & 0x03) << 6)
| (u8::from(self.scrambling_sequence_selector) << 5)
| (self.ts_gs_s2x_mode.to_u8() & 0x03);
let mut p = 2;
if self.scrambling_sequence_selector {
let idx = self.scrambling_sequence_index.unwrap_or(0) & 0x3FFFF;
buf[p] = (idx >> 16) as u8 & 0x03;
buf[p + 1] = (idx >> 8) as u8;
buf[p + 2] = idx as u8;
p += S2X_SCRAMBLING_LEN;
}
write_channel_common(
buf,
&mut p,
self.frequency,
self.orbital_position,
pack_we_pol_mis_ro(
self.west_east_flag,
self.polarization,
self.multiple_input_stream_flag,
self.roll_off,
),
self.symbol_rate,
);
if let Some(isi) = self.input_stream_identifier {
buf[p] = isi;
p += 1;
}
if let Some(ts) = self.timeslice_number {
buf[p] = ts;
p += 1;
}
if self.s2x_mode == S2XMode::S2XChannelBonding {
buf[p] = (self.channel_bonds.len() as u8).saturating_sub(1) & 0x01;
p += 1;
for bond in &self.channel_bonds {
write_channel_common(
buf,
&mut p,
bond.frequency,
bond.orbital_position,
pack_we_pol_mis_ro(
bond.west_east_flag,
bond.polarization,
bond.multiple_input_stream_flag,
bond.roll_off,
),
bond.symbol_rate,
);
if let Some(isi) = bond.input_stream_identifier {
buf[p] = isi;
p += 1;
}
}
}
buf[p..p + self.reserved_tail.len()].copy_from_slice(self.reserved_tail);
Ok(len)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::descriptors::extension::test_support::*;
use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor};
#[test]
fn s2x_mode_roundtrip() {
for b in 0..=0xFFu8 {
assert_eq!(S2XMode::from_u8(b).to_u8(), b);
}
}
#[test]
fn ts_gs_s2x_mode_roundtrip() {
for b in 0..=0xFFu8 {
assert_eq!(TsGsS2XMode::from_u8(b).to_u8(), b);
}
}
#[test]
fn parse_s2x_primary_with_isi_and_timeslice() {
let b0 = 0x05 << 3;
let b1 = (0x02 << 6) | 0x01; let mut sel = vec![b0, b1];
sel.extend_from_slice(&0x0102_0304u32.to_be_bytes()); sel.extend_from_slice(&0x00C8u16.to_be_bytes()); sel.push((1 << 7) | (0x02 << 5) | (1 << 4) | 0x03); let sr: u32 = 0x0AB_CDEF; sel.push((sr >> 24) as u8 & 0x0F);
sel.push((sr >> 16) as u8);
sel.push((sr >> 8) as u8);
sel.push(sr as u8);
sel.push(0x42); sel.push(0x09); let bytes = wrap(0x17, &sel);
let d = ExtensionDescriptor::parse(&bytes).unwrap();
match &d.body {
ExtensionBody::S2XSatelliteDeliverySystem(b) => {
assert_eq!(b.receiver_profiles, 0x05);
assert_eq!(b.s2x_mode, S2XMode::S2XTimeSlicing);
assert!(!b.scrambling_sequence_selector);
assert_eq!(b.ts_gs_s2x_mode, TsGsS2XMode::Gse);
assert_eq!(b.frequency, 0x0102_0304);
assert_eq!(b.orbital_position, 0x00C8);
assert!(b.west_east_flag);
assert_eq!(b.polarization, Polarization::CircularLeft);
assert!(b.multiple_input_stream_flag);
assert_eq!(b.roll_off, RollOff::Reserved(3));
assert_eq!(b.symbol_rate, 0x0AB_CDEF);
assert_eq!(b.input_stream_identifier, Some(0x42));
assert_eq!(b.timeslice_number, Some(0x09));
assert!(b.channel_bonds.is_empty());
assert!(b.reserved_tail.is_empty());
}
other => panic!("expected S2X, got {other:?}"),
}
round_trip(&d);
}
#[test]
fn parse_s2x_with_scrambling_index() {
let b0 = 0x01 << 3;
let b1 = (0x01 << 6) | 0x20; let mut sel = vec![b0, b1];
sel.push(0x02);
sel.push(0xAB);
sel.push(0xCD);
sel.extend_from_slice(&0u32.to_be_bytes()); sel.extend_from_slice(&0u16.to_be_bytes()); sel.push(0x00); sel.extend_from_slice(&[0, 0, 0, 0]); let bytes = wrap(0x17, &sel);
let d = ExtensionDescriptor::parse(&bytes).unwrap();
match &d.body {
ExtensionBody::S2XSatelliteDeliverySystem(b) => {
assert!(b.scrambling_sequence_selector);
assert_eq!(b.scrambling_sequence_index, Some(0x2ABCD));
assert_eq!(b.input_stream_identifier, None);
assert_eq!(b.timeslice_number, None);
assert!(b.channel_bonds.is_empty());
assert!(b.reserved_tail.is_empty());
}
other => panic!("expected S2X, got {other:?}"),
}
round_trip(&d);
}
#[test]
fn parse_s2x_mode1_tail_preserved() {
let b0 = 0x01 << 3;
let b1 = 0x01 << 6; let mut sel = vec![b0, b1];
sel.extend_from_slice(&0u32.to_be_bytes());
sel.extend_from_slice(&0u16.to_be_bytes());
sel.push(0x00); sel.extend_from_slice(&[0, 0, 0, 0]); sel.extend_from_slice(&[0xAA, 0xBB, 0xCC]); let bytes = wrap(0x17, &sel);
let d = ExtensionDescriptor::parse(&bytes).unwrap();
match &d.body {
ExtensionBody::S2XSatelliteDeliverySystem(b) => {
assert_eq!(b.s2x_mode, S2XMode::S2X);
assert_eq!(b.timeslice_number, None);
assert!(b.channel_bonds.is_empty());
assert_eq!(b.reserved_tail, &[0xAA, 0xBB, 0xCC]);
}
other => panic!("expected S2X, got {other:?}"),
}
round_trip(&d);
}
#[test]
fn parse_s2x_mode3_channel_bonds() {
let b0 = 0x01 << 3;
let b1 = 0x03 << 6; let mut sel = vec![b0, b1];
sel.extend_from_slice(&0x1111_1111u32.to_be_bytes()); sel.extend_from_slice(&0x0001u16.to_be_bytes()); sel.push(0x00); sel.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
sel.push(0x01);
sel.extend_from_slice(&0x2222_2222u32.to_be_bytes());
sel.extend_from_slice(&0x0002u16.to_be_bytes());
sel.push((1 << 7) | (0x02 << 5) | (1 << 4) | 0x03); let sr: u32 = 0x0AB_CDEF;
sel.push((sr >> 24) as u8 & 0x0F);
sel.push((sr >> 16) as u8);
sel.push((sr >> 8) as u8);
sel.push(sr as u8);
sel.push(0x77);
sel.extend_from_slice(&0x3333_3333u32.to_be_bytes());
sel.extend_from_slice(&0x0003u16.to_be_bytes());
sel.push((0x01 << 5) | 0x04); let sr2: u32 = 0x005_4321;
sel.push((sr2 >> 24) as u8 & 0x0F);
sel.push((sr2 >> 16) as u8);
sel.push((sr2 >> 8) as u8);
sel.push(sr2 as u8);
let bytes = wrap(0x17, &sel);
let d = ExtensionDescriptor::parse(&bytes).unwrap();
match &d.body {
ExtensionBody::S2XSatelliteDeliverySystem(b) => {
assert_eq!(b.s2x_mode, S2XMode::S2XChannelBonding);
assert_eq!(b.channel_bonds.len(), 2);
let b0 = &b.channel_bonds[0];
assert_eq!(b0.frequency, 0x2222_2222);
assert_eq!(b0.orbital_position, 0x0002);
assert!(b0.west_east_flag);
assert_eq!(b0.polarization, Polarization::CircularLeft);
assert!(b0.multiple_input_stream_flag);
assert_eq!(b0.roll_off, RollOff::Reserved(3));
assert_eq!(b0.symbol_rate, 0x0AB_CDEF);
assert_eq!(b0.input_stream_identifier, Some(0x77));
let b1 = &b.channel_bonds[1];
assert_eq!(b1.frequency, 0x3333_3333);
assert_eq!(b1.orbital_position, 0x0003);
assert!(!b1.west_east_flag);
assert_eq!(b1.polarization, Polarization::LinearVertical);
assert!(!b1.multiple_input_stream_flag);
assert_eq!(b1.roll_off, RollOff::Reserved(4));
assert_eq!(b1.symbol_rate, 0x005_4321);
assert_eq!(b1.input_stream_identifier, None);
assert!(b.reserved_tail.is_empty());
}
other => panic!("expected S2X, got {other:?}"),
}
round_trip(&d);
}
#[test]
fn tsduck_s2x_mode3_byte_exact() {
let hex = "7f2a1750e3023456876543210037250456789601065432180340f600246754bd00654367123451000087642e";
let bytes = from_hex(hex);
let d = ExtensionDescriptor::parse(&bytes)
.unwrap_or_else(|e| panic!("parse tsduck s2x: {e:?}"));
assert_eq!(d.kind(), Some(ExtensionTag::S2XSatelliteDeliverySystem));
match &d.body {
ExtensionBody::S2XSatelliteDeliverySystem(b) => {
assert_eq!(b.s2x_mode, S2XMode::S2XChannelBonding);
assert!(b.scrambling_sequence_selector);
assert_eq!(b.scrambling_sequence_index, Some(0x023456));
assert!(!b.channel_bonds.is_empty());
assert_eq!(b.channel_bonds.len(), 2);
let b0 = &b.channel_bonds[0];
assert_eq!(b0.frequency, 0x0654_3218);
assert_eq!(b0.orbital_position, 0x0340);
assert!(b0.west_east_flag);
assert_eq!(b0.polarization, Polarization::CircularRight);
assert!(b0.multiple_input_stream_flag);
assert_eq!(b0.roll_off, RollOff::Reserved(6));
assert_eq!(b0.symbol_rate, 0x0024_6754);
assert_eq!(b0.input_stream_identifier, Some(0xBD));
let b1 = &b.channel_bonds[1];
assert_eq!(b1.frequency, 0x0065_4367);
assert_eq!(b1.orbital_position, 0x1234);
assert!(!b1.west_east_flag);
assert_eq!(b1.polarization, Polarization::CircularLeft);
assert!(b1.multiple_input_stream_flag);
assert_eq!(b1.roll_off, RollOff::Alpha025);
assert_eq!(b1.symbol_rate, 0x0000_8764);
assert_eq!(b1.input_stream_identifier, Some(0x2E));
assert!(b.reserved_tail.is_empty());
}
other => panic!("expected S2X, got {other:?}"),
}
let mut out = vec![0u8; d.serialized_len()];
let n = d.serialize_into(&mut out).unwrap();
assert_eq!(
&out[..n],
&bytes[..],
"S2X mode 3 byte-exact re-serialize failed"
);
}
}