use alloc::vec::Vec;
use crate::error::{Error, Result};
use crate::ext_header::PayloadChain;
use crate::type_field::TypeField;
pub const BASE_HEADER_LEN: usize = 4;
pub const NPA_LEN: usize = 6;
pub const CRC_LEN: usize = 4;
pub const END_INDICATOR_LENGTH: u16 = 0x7FFF;
pub const END_INDICATOR: u16 = 0xFFFF;
pub const PADDING_BYTE: u8 = 0xFF;
pub(crate) const D_BIT_MASK: u16 = 0x8000;
pub(crate) const LENGTH_MASK: u16 = 0x7FFF;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Sndu<'a> {
pub dest_address: Option<[u8; NPA_LEN]>,
pub payload: PayloadChain<'a>,
}
impl<'a> Sndu<'a> {
pub fn new(type_field: TypeField, dest_address: Option<[u8; NPA_LEN]>, pdu: &'a [u8]) -> Self {
Sndu {
dest_address,
payload: PayloadChain {
headers: Vec::new(),
final_type: type_field,
pdu,
},
}
}
pub fn type_field(&self) -> TypeField {
self.payload.base_type()
}
pub fn d_bit(&self) -> bool {
self.dest_address.is_none()
}
pub fn pdu(&self) -> &'a [u8] {
self.payload.pdu
}
pub fn length_field(&self) -> usize {
let npa = if self.dest_address.is_some() {
NPA_LEN
} else {
0
};
npa + self.payload.serialized_len() + CRC_LEN
}
pub fn serialized_len(&self) -> usize {
BASE_HEADER_LEN + self.length_field()
}
pub fn parse(data: &'a [u8]) -> Result<Self> {
if data.len() < BASE_HEADER_LEN {
return Err(Error::BufferTooShort {
need: BASE_HEADER_LEN,
have: data.len(),
what: "SNDU base header",
});
}
let first = u16::from_be_bytes([data[0], data[1]]);
let d_bit = (first & D_BIT_MASK) != 0;
let length = first & LENGTH_MASK;
let raw_type = u16::from_be_bytes([data[2], data[3]]);
let type_field = TypeField::from_u16(raw_type);
let region_end = BASE_HEADER_LEN + length as usize;
if length as usize > data.len() - BASE_HEADER_LEN {
return Err(Error::InvalidLength {
length,
reason: "Length field exceeds available bytes",
});
}
if (length as usize) < CRC_LEN {
return Err(Error::InvalidLength {
length,
reason: "Length shorter than the CRC trailer",
});
}
let dest_address = if d_bit {
None
} else {
if BASE_HEADER_LEN + NPA_LEN > region_end {
return Err(Error::InvalidLength {
length,
reason: "Length cannot hold the NPA address + CRC",
});
}
let mut npa = [0u8; NPA_LEN];
npa.copy_from_slice(&data[BASE_HEADER_LEN..BASE_HEADER_LEN + NPA_LEN]);
Some(npa)
};
let payload_start = BASE_HEADER_LEN + if d_bit { 0 } else { NPA_LEN };
let crc_start = region_end - CRC_LEN;
if crc_start < payload_start {
return Err(Error::InvalidLength {
length,
reason: "Length leaves no room for the payload",
});
}
let computed = dvb_common::crc32_mpeg2::compute(&data[..crc_start]);
let found = u32::from_be_bytes([
data[crc_start],
data[crc_start + 1],
data[crc_start + 2],
data[crc_start + 3],
]);
if computed != found {
return Err(Error::CrcMismatch { computed, found });
}
let payload = PayloadChain::parse(type_field, &data[payload_start..crc_start])?;
Ok(Sndu {
dest_address,
payload,
})
}
pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
let total = self.serialized_len();
if out.len() < total {
return Err(Error::OutputBufferTooSmall {
need: total,
have: out.len(),
});
}
let length = self.length_field();
if length > END_INDICATOR_LENGTH as usize {
return Err(Error::FieldTooWide {
what: "Length",
value: length as u32,
bits: 15,
});
}
let base_type = self.payload.base_type();
let mut first = length as u16 & LENGTH_MASK;
if self.d_bit() {
first |= D_BIT_MASK;
}
out[0..2].copy_from_slice(&first.to_be_bytes());
out[2..4].copy_from_slice(&base_type.to_u16().to_be_bytes());
let mut off = BASE_HEADER_LEN;
if let Some(npa) = self.dest_address {
out[off..off + NPA_LEN].copy_from_slice(&npa);
off += NPA_LEN;
}
let written = self
.payload
.serialize_into(&mut out[off..total - CRC_LEN])?;
off += written;
let crc = dvb_common::crc32_mpeg2::compute(&out[..off]);
out[off..off + CRC_LEN].copy_from_slice(&crc.to_be_bytes());
off += CRC_LEN;
Ok(off)
}
}
pub fn is_end_indicator(data: &[u8]) -> bool {
data.len() >= 2 && data[0] == PADDING_BYTE && data[1] == PADDING_BYTE
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn d0_with_npa_exact_wire_bytes() {
let pdu = [0x45u8, 0x00, 0x00, 0x14, 0xAB, 0xCD];
let npa = [0x01u8, 0x02, 0x03, 0x04, 0x05, 0x06];
let sndu = Sndu::new(TypeField::EtherType(0x0800), Some(npa), &pdu);
assert_eq!(sndu.length_field(), 16);
let mut out = vec![0u8; sndu.serialized_len()];
let n = sndu.serialize_into(&mut out).unwrap();
assert_eq!(n, 4 + 16);
assert_eq!(&out[0..2], &[0x00, 0x10]);
assert_eq!(&out[2..4], &[0x08, 0x00]);
assert_eq!(&out[4..10], &npa);
assert_eq!(&out[10..16], &pdu);
let crc = dvb_common::crc32_mpeg2::compute(&out[..16]);
assert_eq!(&out[16..20], &crc.to_be_bytes());
assert_eq!(Sndu::parse(&out).unwrap(), sndu);
}
#[test]
fn d1_without_npa_exact_wire_bytes() {
let pdu = [0x60u8, 0x00, 0x00, 0x00];
let sndu = Sndu::new(TypeField::EtherType(0x86DD), None, &pdu);
assert_eq!(sndu.length_field(), 8);
let mut out = vec![0u8; sndu.serialized_len()];
sndu.serialize_into(&mut out).unwrap();
assert_eq!(&out[0..2], &[0x80, 0x08]);
assert_eq!(&out[2..4], &[0x86, 0xDD]);
assert_eq!(&out[4..8], &pdu);
let crc = dvb_common::crc32_mpeg2::compute(&out[..8]);
assert_eq!(&out[8..12], &crc.to_be_bytes());
assert_eq!(Sndu::parse(&out).unwrap(), sndu);
}
#[test]
fn mutating_a_field_changes_crc() {
let pdu = [0xDEu8, 0xAD, 0xBE, 0xEF];
let a = Sndu::new(TypeField::EtherType(0x0800), None, &pdu);
let mut buf_a = vec![0u8; a.serialized_len()];
a.serialize_into(&mut buf_a).unwrap();
let pdu2 = [0xDEu8, 0xAD, 0xBE, 0xEE]; let b = Sndu::new(TypeField::EtherType(0x0800), None, &pdu2);
let mut buf_b = vec![0u8; b.serialized_len()];
b.serialize_into(&mut buf_b).unwrap();
assert_ne!(
buf_a, buf_b,
"different PDU must yield different wire bytes"
);
assert_ne!(buf_a[8..12], buf_b[8..12]);
}
#[test]
fn npa_presence_drives_d_bit_and_length() {
let pdu = [0u8; 10];
let with = Sndu::new(TypeField::EtherType(0x0800), Some([0xFF; 6]), &pdu);
let without = Sndu::new(TypeField::EtherType(0x0800), None, &pdu);
assert!(!with.d_bit());
assert!(without.d_bit());
assert_eq!(with.length_field(), without.length_field() + NPA_LEN);
}
#[test]
fn rejects_bad_crc() {
let pdu = [1u8, 2, 3, 4];
let sndu = Sndu::new(TypeField::EtherType(0x0800), None, &pdu);
let mut buf = vec![0u8; sndu.serialized_len()];
sndu.serialize_into(&mut buf).unwrap();
let last = buf.len() - 1;
buf[last] ^= 0x01;
assert!(matches!(Sndu::parse(&buf), Err(Error::CrcMismatch { .. })));
}
#[test]
fn rejects_length_overrun() {
let data = [0x00u8, 0x64, 0x08, 0x00, 0x00];
assert!(matches!(
Sndu::parse(&data),
Err(Error::InvalidLength { .. })
));
}
#[test]
fn end_indicator_detected() {
assert!(is_end_indicator(&[0xFF, 0xFF, 0xFF]));
assert!(!is_end_indicator(&[0x00, 0x10]));
}
#[test]
fn type_field_accessor_matches_serialized_wire() {
let pdu = [0xAAu8; 4];
let sndu = Sndu::new(TypeField::EtherType(0x0800), None, &pdu);
assert_eq!(sndu.type_field(), TypeField::EtherType(0x0800));
let mut buf = vec![0u8; sndu.serialized_len()];
sndu.serialize_into(&mut buf).unwrap();
let wire_type = u16::from_be_bytes([buf[2], buf[3]]);
assert_eq!(sndu.type_field().to_u16(), wire_type);
}
}