use crate::error::{Error, Result};
use crate::traits::Descriptor;
use dvb_common::{Parse, Serialize};
pub const TAG: u8 = 0x79;
const HEADER_LEN: usize = 2;
const FLAGS_LEN: usize = 1;
const SCRAMBLING_FIELD_LEN: usize = 3;
const ISI_FIELD_LEN: usize = 1;
const TIMESLICE_FIELD_LEN: usize = 1;
const FLAG_SCRAMBLING_SELECTOR: u8 = 0x80;
const FLAG_MULTIPLE_INPUT_STREAM: u8 = 0x40;
const FLAG_NOT_TIMESLICE: u8 = 0x10;
const TS_GS_MODE_MASK: u8 = 0x03;
const FLAG_RESERVED_BITS: u8 = 0x0C;
const SCRAMBLING_RESERVED_MASK: u8 = 0xFC;
const SCRAMBLING_INDEX_HI_MASK: u8 = 0x03;
const SCRAMBLING_INDEX_MAX: u32 = 0x3FFFF;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct S2SatelliteDeliverySystemDescriptor {
pub scrambling_sequence_selector: bool,
pub multiple_input_stream_flag: bool,
pub not_timeslice_flag: bool,
pub ts_gs_mode: u8,
pub scrambling_sequence_index: Option<u32>,
pub input_stream_identifier: Option<u8>,
pub timeslice_number: Option<u8>,
}
impl<'a> Parse<'a> for S2SatelliteDeliverySystemDescriptor {
type Error = crate::error::Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
if bytes.len() < HEADER_LEN + FLAGS_LEN {
return Err(Error::BufferTooShort {
need: HEADER_LEN + FLAGS_LEN,
have: bytes.len(),
what: "S2SatelliteDeliverySystemDescriptor header",
});
}
if bytes[0] != TAG {
return Err(Error::InvalidDescriptor {
tag: bytes[0],
reason: "unexpected tag for s2_satellite_delivery_system_descriptor",
});
}
let length = bytes[1] as usize;
let end = HEADER_LEN + length;
if bytes.len() < end {
return Err(Error::BufferTooShort {
need: end,
have: bytes.len(),
what: "S2SatelliteDeliverySystemDescriptor body",
});
}
if length < FLAGS_LEN {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "body must contain at least the flags byte",
});
}
let flags = bytes[HEADER_LEN];
let scrambling_sequence_selector = (flags & FLAG_SCRAMBLING_SELECTOR) != 0;
let multiple_input_stream_flag = (flags & FLAG_MULTIPLE_INPUT_STREAM) != 0;
let not_timeslice_flag = (flags & FLAG_NOT_TIMESLICE) != 0;
let ts_gs_mode = flags & TS_GS_MODE_MASK;
let mut pos = HEADER_LEN + FLAGS_LEN;
let scrambling_sequence_index = if scrambling_sequence_selector {
if pos + SCRAMBLING_FIELD_LEN > end {
return Err(Error::BufferTooShort {
need: pos + SCRAMBLING_FIELD_LEN - HEADER_LEN,
have: length,
what: "S2SatelliteDeliverySystemDescriptor scrambling_sequence_index",
});
}
let b0 = bytes[pos];
let index = (u32::from(b0 & SCRAMBLING_INDEX_HI_MASK) << 16)
| (u32::from(bytes[pos + 1]) << 8)
| u32::from(bytes[pos + 2]);
pos += SCRAMBLING_FIELD_LEN;
Some(index)
} else {
None
};
let input_stream_identifier = if multiple_input_stream_flag {
if pos + ISI_FIELD_LEN > end {
return Err(Error::BufferTooShort {
need: pos + ISI_FIELD_LEN - HEADER_LEN,
have: length,
what: "S2SatelliteDeliverySystemDescriptor input_stream_identifier",
});
}
let isi = bytes[pos];
pos += ISI_FIELD_LEN;
Some(isi)
} else {
None
};
let timeslice_number = if !not_timeslice_flag {
if pos + TIMESLICE_FIELD_LEN > end {
return Err(Error::BufferTooShort {
need: pos + TIMESLICE_FIELD_LEN - HEADER_LEN,
have: length,
what: "S2SatelliteDeliverySystemDescriptor timeslice_number",
});
}
Some(bytes[pos])
} else {
None
};
Ok(Self {
scrambling_sequence_selector,
multiple_input_stream_flag,
not_timeslice_flag,
ts_gs_mode,
scrambling_sequence_index,
input_stream_identifier,
timeslice_number,
})
}
}
impl Serialize for S2SatelliteDeliverySystemDescriptor {
type Error = crate::error::Error;
fn serialized_len(&self) -> usize {
HEADER_LEN
+ FLAGS_LEN
+ if self.scrambling_sequence_selector {
SCRAMBLING_FIELD_LEN
} else {
0
}
+ if self.multiple_input_stream_flag {
ISI_FIELD_LEN
} else {
0
}
+ if self.not_timeslice_flag {
0
} else {
TIMESLICE_FIELD_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 mut flags = FLAG_RESERVED_BITS | (self.ts_gs_mode & TS_GS_MODE_MASK);
if self.scrambling_sequence_selector {
flags |= FLAG_SCRAMBLING_SELECTOR;
}
if self.multiple_input_stream_flag {
flags |= FLAG_MULTIPLE_INPUT_STREAM;
}
if self.not_timeslice_flag {
flags |= FLAG_NOT_TIMESLICE;
}
buf[HEADER_LEN] = flags;
let mut pos = HEADER_LEN + FLAGS_LEN;
if self.scrambling_sequence_selector {
let index = self.scrambling_sequence_index.unwrap_or(0) & SCRAMBLING_INDEX_MAX;
buf[pos] = SCRAMBLING_RESERVED_MASK | ((index >> 16) as u8 & SCRAMBLING_INDEX_HI_MASK);
buf[pos + 1] = (index >> 8) as u8;
buf[pos + 2] = index as u8;
pos += SCRAMBLING_FIELD_LEN;
}
if self.multiple_input_stream_flag {
buf[pos] = self.input_stream_identifier.unwrap_or(0);
pos += ISI_FIELD_LEN;
}
if !self.not_timeslice_flag {
buf[pos] = self.timeslice_number.unwrap_or(0);
}
Ok(len)
}
}
impl<'a> Descriptor<'a> for S2SatelliteDeliverySystemDescriptor {
const TAG: u8 = TAG;
fn descriptor_length(&self) -> u8 {
(self.serialized_len() - HEADER_LEN) as u8
}
}
impl<'a> crate::traits::DescriptorDef<'a> for S2SatelliteDeliverySystemDescriptor {
const TAG: u8 = TAG;
const NAME: &'static str = "S2_SATELLITE_DELIVERY_SYSTEM";
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_minimal_with_timeslice() {
let raw = [TAG, 2, 0x2C, 0x07];
let d = S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
assert!(!d.scrambling_sequence_selector);
assert!(!d.multiple_input_stream_flag);
assert!(!d.not_timeslice_flag);
assert_eq!(d.ts_gs_mode, 0);
assert_eq!(d.timeslice_number, Some(0x07));
assert_eq!(d.scrambling_sequence_index, None);
assert_eq!(d.input_stream_identifier, None);
}
#[test]
fn parse_not_timeslice_omits_timeslice_number() {
let raw = [TAG, 1, 0x3E];
let d = S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
assert!(d.not_timeslice_flag);
assert_eq!(d.ts_gs_mode, 2);
assert_eq!(d.timeslice_number, None);
}
#[test]
fn parse_extracts_isi_and_timeslice() {
let raw = [TAG, 3, 0x6C, 0x05, 0x09];
let d = S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
assert!(d.multiple_input_stream_flag);
assert_eq!(d.input_stream_identifier, Some(5));
assert_eq!(d.timeslice_number, Some(9));
}
#[test]
fn parse_extracts_scrambling_index() {
let raw = [TAG, 4, 0xBC, 0xFD, 0x23, 0x45];
let d = S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
assert!(d.scrambling_sequence_selector);
assert_eq!(d.scrambling_sequence_index, Some(0x12345));
assert!(d.not_timeslice_flag);
assert_eq!(d.timeslice_number, None);
}
#[test]
fn parse_full_18_bit_index() {
let raw = [TAG, 4, 0xBC, 0xFF, 0xFF, 0xFF];
let d = S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
assert_eq!(d.scrambling_sequence_index, Some(0x3FFFF));
}
#[test]
fn parse_rejects_wrong_tag() {
let raw = [0x44, 1, 0x3E];
assert!(matches!(
S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap_err(),
Error::InvalidDescriptor { tag: 0x44, .. }
));
}
#[test]
fn parse_rejects_truncated_scrambling() {
let raw = [TAG, 1, 0x9C]; assert!(matches!(
S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap_err(),
Error::BufferTooShort { .. }
));
}
#[test]
fn serialize_round_trip_all_fields() {
let d = S2SatelliteDeliverySystemDescriptor {
scrambling_sequence_selector: true,
multiple_input_stream_flag: true,
not_timeslice_flag: false,
ts_gs_mode: 2,
scrambling_sequence_index: Some(0x2BCDE),
input_stream_identifier: Some(0x42),
timeslice_number: Some(0x11),
};
let mut buf = vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
assert_eq!(S2SatelliteDeliverySystemDescriptor::parse(&buf).unwrap(), d);
}
#[test]
fn serialize_round_trip_not_timeslice() {
let d = S2SatelliteDeliverySystemDescriptor {
scrambling_sequence_selector: false,
multiple_input_stream_flag: false,
not_timeslice_flag: true,
ts_gs_mode: 1,
scrambling_sequence_index: None,
input_stream_identifier: None,
timeslice_number: None,
};
let mut buf = vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
assert_eq!(S2SatelliteDeliverySystemDescriptor::parse(&buf).unwrap(), d);
}
}