use alloc::vec::Vec;
use core::fmt;
use num_enum::TryFromPrimitive;
use dvb_common::{Parse, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[repr(u8)]
#[non_exhaustive]
pub enum AddressingFunctionTag {
TimeOffset = 0x00,
FrequencyOffset = 0x01,
Power = 0x02,
PrivateData = 0x03,
CellId = 0x04,
Enable = 0x05,
Bandwidth = 0x06,
AcePapr = 0x10,
MisoGroup = 0x11,
TrPapr = 0x12,
L1AcePapr = 0x13,
TxSigFefSeqNum = 0x15,
TxSigAuxStreamTxId = 0x16,
Frequency = 0x17,
}
impl From<AddressingFunctionTag> for u8 {
fn from(tag: AddressingFunctionTag) -> Self {
tag as u8
}
}
impl AddressingFunctionTag {
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::TimeOffset => "Transmitter time offset",
Self::FrequencyOffset => "Transmitter frequency offset",
Self::Power => "Transmitter power",
Self::PrivateData => "Private data",
Self::CellId => "Cell ID",
Self::Enable => "Enable",
Self::Bandwidth => "Bandwidth",
Self::AcePapr => "ACE-PAPR reduction",
Self::MisoGroup => "MISO group",
Self::TrPapr => "TR-PAPR reduction",
Self::L1AcePapr => "L1-ACE-PAPR",
Self::TxSigFefSeqNum => "TX-SIG FEF sequence number",
Self::TxSigAuxStreamTxId => "TX-SIG auxiliary stream TX ID",
Self::Frequency => "Frequency",
}
}
}
dvb_common::impl_spec_display!(AddressingFunctionTag);
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct AcePaprBody {
pub ace_gain: u8,
pub ace_maximal_extension: u8,
pub ace_clipping_threshold: u8,
pub rfu: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct MisoGroupBody {
pub miso_group: bool,
pub rfu: u8,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TrPaprBody {
pub rfu1: u8,
pub tr_clipping_threshold: u16,
pub rfu2: u16,
pub number_of_iterations: u16,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct L1AcePaprBody {
pub l1_ace_max_correction: u16,
pub rfu: u16,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TxSigFefSeqNumBody {
pub rfu1: u8,
pub seq_num_1: u8,
pub rfu2: u8,
pub seq_num_2: u8,
pub rfu3: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TxSigAuxStreamTxIdBody {
pub tx_sig_aux_tx_id: u16,
pub rfu: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct FrequencyBody {
pub rf_idx: u8,
pub frequency: u32,
pub rfu: u8,
}
const ACE_PAPR_BODY_LEN: usize = 2;
const MISO_GROUP_BODY_LEN: usize = 1;
const TR_PAPR_BODY_LEN: usize = 5;
const L1_ACE_PAPR_BODY_LEN: usize = 4;
const TX_SIG_FEF_SEQ_NUM_BODY_LEN: usize = 5;
const TX_SIG_AUX_STREAM_TX_ID_BODY_LEN: usize = 4;
const FREQUENCY_BODY_LEN: usize = 5;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum FunctionBody<'a> {
AcePapr(AcePaprBody),
MisoGroup(MisoGroupBody),
TrPapr(TrPaprBody),
L1AcePapr(L1AcePaprBody),
TxSigFefSeqNum(TxSigFefSeqNumBody),
TxSigAuxStreamTxId(TxSigAuxStreamTxIdBody),
Frequency(FrequencyBody),
Raw(&'a [u8]),
}
impl FunctionBody<'_> {
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::AcePapr(_) => "ACE-PAPR",
Self::MisoGroup(_) => "MISO group",
Self::TrPapr(_) => "TR-PAPR",
Self::L1AcePapr(_) => "L1-ACE-PAPR",
Self::TxSigFefSeqNum(_) => "TX-SIG FEF sequence numbers",
Self::TxSigAuxStreamTxId(_) => "TX-SIG auxiliary stream TX ID",
Self::Frequency(_) => "Frequency",
Self::Raw(_) => "raw",
}
}
}
dvb_common::impl_spec_display!(FunctionBody<'_>);
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
pub struct TransmitterEntry<'a> {
pub transmitter_id: u16,
pub functions: Vec<FunctionEntry<'a>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
pub struct FunctionEntry<'a> {
pub tag: u8,
pub body: FunctionBody<'a>,
}
impl<'a> FunctionEntry<'a> {
#[must_use]
pub fn addressing_tag(&self) -> Option<AddressingFunctionTag> {
AddressingFunctionTag::try_from(self.tag).ok()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
pub struct IndividualAddressingPayload<'a> {
pub rfu: u8,
pub transmitters: Vec<TransmitterEntry<'a>>,
}
const HEADER_LEN: usize = 2;
const TX_HEADER_LEN: usize = 3;
const FUNC_HEADER_LEN: usize = 2;
fn parse_ace_papr(body: &[u8]) -> Result<AcePaprBody, crate::Error> {
if body.len() < ACE_PAPR_BODY_LEN {
return Err(crate::Error::BufferTooShort {
need: ACE_PAPR_BODY_LEN,
have: body.len(),
what: "ACE-PAPR function body",
});
}
Ok(AcePaprBody {
ace_gain: (body[0] >> 3) & 0x1F,
ace_maximal_extension: body[0] & 0x07,
ace_clipping_threshold: (body[1] >> 1) & 0x7F,
rfu: body[1] & 0x01 != 0,
})
}
fn parse_miso_group(body: &[u8]) -> Result<MisoGroupBody, crate::Error> {
if body.len() < MISO_GROUP_BODY_LEN {
return Err(crate::Error::BufferTooShort {
need: MISO_GROUP_BODY_LEN,
have: body.len(),
what: "MISO group function body",
});
}
Ok(MisoGroupBody {
miso_group: body[0] & 0x80 != 0,
rfu: body[0] & 0x7F,
})
}
fn parse_tr_papr(body: &[u8]) -> Result<TrPaprBody, crate::Error> {
if body.len() < TR_PAPR_BODY_LEN {
return Err(crate::Error::BufferTooShort {
need: TR_PAPR_BODY_LEN,
have: body.len(),
what: "TR-PAPR function body",
});
}
Ok(TrPaprBody {
rfu1: (body[0] >> 4) & 0x0F,
tr_clipping_threshold: ((body[0] as u16 & 0x0F) << 8) | body[1] as u16,
rfu2: ((body[2] as u16) << 6) | ((body[3] as u16) >> 2),
number_of_iterations: ((body[3] as u16 & 0x03) << 8) | body[4] as u16,
})
}
fn parse_l1_ace_papr(body: &[u8]) -> Result<L1AcePaprBody, crate::Error> {
if body.len() < L1_ACE_PAPR_BODY_LEN {
return Err(crate::Error::BufferTooShort {
need: L1_ACE_PAPR_BODY_LEN,
have: body.len(),
what: "L1-ACE-PAPR function body",
});
}
Ok(L1AcePaprBody {
l1_ace_max_correction: u16::from_be_bytes([body[0], body[1]]),
rfu: u16::from_be_bytes([body[2], body[3]]),
})
}
fn parse_tx_sig_fef_seq_num(body: &[u8]) -> Result<TxSigFefSeqNumBody, crate::Error> {
if body.len() < TX_SIG_FEF_SEQ_NUM_BODY_LEN {
return Err(crate::Error::BufferTooShort {
need: TX_SIG_FEF_SEQ_NUM_BODY_LEN,
have: body.len(),
what: "TX-SIG FEF sequence numbers function body",
});
}
Ok(TxSigFefSeqNumBody {
rfu1: (body[0] >> 3) & 0x1F,
seq_num_1: body[0] & 0x07,
rfu2: (body[1] >> 3) & 0x1F,
seq_num_2: body[1] & 0x07,
rfu3: (body[2] as u32) << 16 | (body[3] as u32) << 8 | body[4] as u32,
})
}
fn parse_tx_sig_aux_stream_tx_id(body: &[u8]) -> Result<TxSigAuxStreamTxIdBody, crate::Error> {
if body.len() < TX_SIG_AUX_STREAM_TX_ID_BODY_LEN {
return Err(crate::Error::BufferTooShort {
need: TX_SIG_AUX_STREAM_TX_ID_BODY_LEN,
have: body.len(),
what: "TX-SIG aux stream TX ID function body",
});
}
Ok(TxSigAuxStreamTxIdBody {
tx_sig_aux_tx_id: ((body[0] as u16) << 4) | ((body[1] as u16) >> 4),
rfu: ((body[1] as u32 & 0x0F) << 16) | (body[2] as u32) << 8 | body[3] as u32,
})
}
fn parse_frequency(body: &[u8]) -> Result<FrequencyBody, crate::Error> {
if body.len() < FREQUENCY_BODY_LEN {
return Err(crate::Error::BufferTooShort {
need: FREQUENCY_BODY_LEN,
have: body.len(),
what: "Frequency function body",
});
}
Ok(FrequencyBody {
rf_idx: (body[0] >> 5) & 0x07,
frequency: ((body[0] as u32 & 0x1F) << 27)
| (body[1] as u32) << 19
| (body[2] as u32) << 11
| (body[3] as u32) << 3
| ((body[4] >> 5) as u32 & 0x07),
rfu: body[4] & 0x1F,
})
}
fn try_parse_typed_body(
tag: u8,
body_bytes: &[u8],
) -> Option<Result<FunctionBody<'_>, crate::Error>> {
match tag {
t if t == AddressingFunctionTag::AcePapr as u8 && body_bytes.len() == ACE_PAPR_BODY_LEN => {
Some(parse_ace_papr(body_bytes).map(FunctionBody::AcePapr))
}
t if t == AddressingFunctionTag::MisoGroup as u8
&& body_bytes.len() == MISO_GROUP_BODY_LEN =>
{
Some(parse_miso_group(body_bytes).map(FunctionBody::MisoGroup))
}
t if t == AddressingFunctionTag::TrPapr as u8 && body_bytes.len() == TR_PAPR_BODY_LEN => {
Some(parse_tr_papr(body_bytes).map(FunctionBody::TrPapr))
}
t if t == AddressingFunctionTag::L1AcePapr as u8
&& body_bytes.len() == L1_ACE_PAPR_BODY_LEN =>
{
Some(parse_l1_ace_papr(body_bytes).map(FunctionBody::L1AcePapr))
}
t if t == AddressingFunctionTag::TxSigFefSeqNum as u8
&& body_bytes.len() == TX_SIG_FEF_SEQ_NUM_BODY_LEN =>
{
Some(parse_tx_sig_fef_seq_num(body_bytes).map(FunctionBody::TxSigFefSeqNum))
}
t if t == AddressingFunctionTag::TxSigAuxStreamTxId as u8
&& body_bytes.len() == TX_SIG_AUX_STREAM_TX_ID_BODY_LEN =>
{
Some(parse_tx_sig_aux_stream_tx_id(body_bytes).map(FunctionBody::TxSigAuxStreamTxId))
}
t if t == AddressingFunctionTag::Frequency as u8
&& body_bytes.len() == FREQUENCY_BODY_LEN =>
{
Some(parse_frequency(body_bytes).map(FunctionBody::Frequency))
}
_ => None,
}
}
fn serialize_ace_papr(body: &AcePaprBody, buf: &mut [u8]) {
buf[0] = (body.ace_gain & 0x1F) << 3 | (body.ace_maximal_extension & 0x07);
buf[1] = (body.ace_clipping_threshold & 0x7F) << 1 | if body.rfu { 1 } else { 0 };
}
fn serialize_miso_group(body: &MisoGroupBody, buf: &mut [u8]) {
buf[0] = if body.miso_group { 0x80 } else { 0x00 } | (body.rfu & 0x7F);
}
fn serialize_tr_papr(body: &TrPaprBody, buf: &mut [u8]) {
buf[0] = (body.rfu1 & 0x0F) << 4 | ((body.tr_clipping_threshold >> 8) as u8 & 0x0F);
buf[1] = (body.tr_clipping_threshold & 0xFF) as u8;
buf[2] = (body.rfu2 >> 6) as u8;
buf[3] = ((body.rfu2 & 0x3F) as u8) << 2 | ((body.number_of_iterations >> 8) as u8 & 0x03);
buf[4] = (body.number_of_iterations & 0xFF) as u8;
}
fn serialize_l1_ace_papr(body: &L1AcePaprBody, buf: &mut [u8]) {
buf[0..2].copy_from_slice(&body.l1_ace_max_correction.to_be_bytes());
buf[2..4].copy_from_slice(&body.rfu.to_be_bytes());
}
fn serialize_tx_sig_fef_seq_num(body: &TxSigFefSeqNumBody, buf: &mut [u8]) {
buf[0] = (body.rfu1 & 0x1F) << 3 | (body.seq_num_1 & 0x07);
buf[1] = (body.rfu2 & 0x1F) << 3 | (body.seq_num_2 & 0x07);
buf[2] = ((body.rfu3 >> 16) & 0xFF) as u8;
buf[3] = ((body.rfu3 >> 8) & 0xFF) as u8;
buf[4] = (body.rfu3 & 0xFF) as u8;
}
fn serialize_tx_sig_aux_stream_tx_id(body: &TxSigAuxStreamTxIdBody, buf: &mut [u8]) {
buf[0] = ((body.tx_sig_aux_tx_id >> 4) & 0xFF) as u8;
buf[1] = ((body.tx_sig_aux_tx_id & 0x0F) as u8) << 4 | ((body.rfu >> 16) & 0x0F) as u8;
buf[2] = ((body.rfu >> 8) & 0xFF) as u8;
buf[3] = (body.rfu & 0xFF) as u8;
}
fn serialize_frequency(body: &FrequencyBody, buf: &mut [u8]) {
buf[0] = (body.rf_idx & 0x07) << 5 | ((body.frequency >> 27) & 0x1F) as u8;
buf[1] = ((body.frequency >> 19) & 0xFF) as u8;
buf[2] = ((body.frequency >> 11) & 0xFF) as u8;
buf[3] = ((body.frequency >> 3) & 0xFF) as u8;
buf[4] = ((body.frequency & 0x07) as u8) << 5 | (body.rfu & 0x1F);
}
fn body_serialized_len(body: &FunctionBody<'_>) -> usize {
match body {
FunctionBody::AcePapr(_) => ACE_PAPR_BODY_LEN,
FunctionBody::MisoGroup(_) => MISO_GROUP_BODY_LEN,
FunctionBody::TrPapr(_) => TR_PAPR_BODY_LEN,
FunctionBody::L1AcePapr(_) => L1_ACE_PAPR_BODY_LEN,
FunctionBody::TxSigFefSeqNum(_) => TX_SIG_FEF_SEQ_NUM_BODY_LEN,
FunctionBody::TxSigAuxStreamTxId(_) => TX_SIG_AUX_STREAM_TX_ID_BODY_LEN,
FunctionBody::Frequency(_) => FREQUENCY_BODY_LEN,
FunctionBody::Raw(bytes) => bytes.len(),
}
}
fn serialize_body_into(body: &FunctionBody<'_>, buf: &mut [u8]) -> usize {
match body {
FunctionBody::AcePapr(b) => {
serialize_ace_papr(b, buf);
ACE_PAPR_BODY_LEN
}
FunctionBody::MisoGroup(b) => {
serialize_miso_group(b, buf);
MISO_GROUP_BODY_LEN
}
FunctionBody::TrPapr(b) => {
serialize_tr_papr(b, buf);
TR_PAPR_BODY_LEN
}
FunctionBody::L1AcePapr(b) => {
serialize_l1_ace_papr(b, buf);
L1_ACE_PAPR_BODY_LEN
}
FunctionBody::TxSigFefSeqNum(b) => {
serialize_tx_sig_fef_seq_num(b, buf);
TX_SIG_FEF_SEQ_NUM_BODY_LEN
}
FunctionBody::TxSigAuxStreamTxId(b) => {
serialize_tx_sig_aux_stream_tx_id(b, buf);
TX_SIG_AUX_STREAM_TX_ID_BODY_LEN
}
FunctionBody::Frequency(b) => {
serialize_frequency(b, buf);
FREQUENCY_BODY_LEN
}
FunctionBody::Raw(bytes) => {
buf[..bytes.len()].copy_from_slice(bytes);
bytes.len()
}
}
}
fn function_entry_serialized_len(entry: &FunctionEntry<'_>) -> usize {
FUNC_HEADER_LEN + body_serialized_len(&entry.body)
}
fn transmitter_entry_serialized_len(entry: &TransmitterEntry<'_>) -> usize {
let func_loop_len: usize = entry
.functions
.iter()
.map(function_entry_serialized_len)
.sum();
TX_HEADER_LEN + func_loop_len
}
impl<'a> Parse<'a> for IndividualAddressingPayload<'a> {
type Error = crate::error::Error;
fn parse(bytes: &'a [u8]) -> Result<Self, crate::error::Error> {
if bytes.len() < HEADER_LEN {
return Err(crate::Error::BufferTooShort {
need: HEADER_LEN,
have: bytes.len(),
what: "IndividualAddressingPayload header",
});
}
let rfu = bytes[0];
let individual_addressing_length = bytes[1] as usize;
let need = HEADER_LEN + individual_addressing_length;
if bytes.len() < need {
return Err(crate::Error::BufferTooShort {
need,
have: bytes.len(),
what: "IndividualAddressingPayload data",
});
}
let data = &bytes[HEADER_LEN..need];
let mut pos: usize = 0;
let max_tx = individual_addressing_length / TX_HEADER_LEN + 1;
let mut transmitters = Vec::with_capacity(max_tx.min(individual_addressing_length));
while pos < individual_addressing_length {
if pos + TX_HEADER_LEN > individual_addressing_length {
return Err(crate::Error::BufferTooShort {
need: pos + TX_HEADER_LEN,
have: individual_addressing_length,
what: "transmitter entry header",
});
}
let transmitter_id = u16::from_be_bytes([data[pos], data[pos + 1]]);
let function_loop_length = data[pos + 2] as usize;
pos += TX_HEADER_LEN;
let func_end = pos + function_loop_length;
if func_end > individual_addressing_length {
return Err(crate::Error::BufferTooShort {
need: func_end,
have: individual_addressing_length,
what: "function loop",
});
}
let max_funcs = function_loop_length / FUNC_HEADER_LEN + 1;
let mut functions = Vec::with_capacity(max_funcs.min(function_loop_length));
while pos < func_end {
if pos + FUNC_HEADER_LEN > func_end {
return Err(crate::Error::BufferTooShort {
need: pos + FUNC_HEADER_LEN,
have: func_end,
what: "function entry header",
});
}
let tag = data[pos];
let function_length = data[pos + 1] as usize;
pos += FUNC_HEADER_LEN;
let body_end = pos + function_length;
if body_end > func_end {
return Err(crate::Error::BufferTooShort {
need: body_end,
have: func_end,
what: "function body",
});
}
let body_bytes = &data[pos..body_end];
pos = body_end;
let body = match try_parse_typed_body(tag, body_bytes) {
Some(Ok(typed)) => typed,
Some(Err(e)) => return Err(e),
None => FunctionBody::Raw(body_bytes),
};
functions.push(FunctionEntry { tag, body });
}
transmitters.push(TransmitterEntry {
transmitter_id,
functions,
});
}
Ok(IndividualAddressingPayload { rfu, transmitters })
}
}
impl<'a> crate::traits::PayloadDef<'a> for IndividualAddressingPayload<'a> {
const PACKET_TYPE: u8 = 0x21;
const NAME: &'static str = "INDIVIDUAL_ADDRESSING";
}
impl Serialize for IndividualAddressingPayload<'_> {
type Error = crate::error::Error;
fn serialized_len(&self) -> usize {
HEADER_LEN
+ self
.transmitters
.iter()
.map(transmitter_entry_serialized_len)
.sum::<usize>()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize, crate::error::Error> {
let data_len: usize = self
.transmitters
.iter()
.map(transmitter_entry_serialized_len)
.sum();
let total = HEADER_LEN + data_len;
if buf.len() < total {
return Err(crate::Error::OutputBufferTooSmall {
need: total,
have: buf.len(),
});
}
if data_len > u8::MAX as usize {
return Err(crate::Error::ReservedBitsViolation {
field: "individual_addressing_length",
reason: "transmitter loop exceeds 255 bytes (8-bit length field)",
});
}
buf[0] = self.rfu;
buf[1] = data_len as u8;
let mut pos: usize = HEADER_LEN;
for tx in &self.transmitters {
let func_loop_len: usize = tx.functions.iter().map(function_entry_serialized_len).sum();
if func_loop_len > u8::MAX as usize {
return Err(crate::Error::ReservedBitsViolation {
field: "function_loop_length",
reason: "function loop exceeds 255 bytes (8-bit length field)",
});
}
buf[pos] = (tx.transmitter_id >> 8) as u8;
buf[pos + 1] = (tx.transmitter_id & 0xFF) as u8;
buf[pos + 2] = func_loop_len as u8;
pos += TX_HEADER_LEN;
for func in &tx.functions {
let body_len = body_serialized_len(&func.body);
if body_len > u8::MAX as usize {
return Err(crate::Error::ReservedBitsViolation {
field: "function_length",
reason: "function body exceeds 255 bytes (8-bit length field)",
});
}
buf[pos] = func.tag;
buf[pos + 1] = body_len as u8;
pos += FUNC_HEADER_LEN;
let written = serialize_body_into(&func.body, &mut buf[pos..]);
debug_assert_eq!(written, body_len);
pos += body_len;
}
}
debug_assert_eq!(pos, total);
Ok(total)
}
}
impl fmt::Display for IndividualAddressingPayload<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"IndividualAddressing {{ rfu: 0x{:02X}, tx_count: {} }}",
self.rfu,
self.transmitters.len()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn addressing_function_tag_try_from_valid() {
assert_eq!(
AddressingFunctionTag::try_from(0x10),
Ok(AddressingFunctionTag::AcePapr)
);
assert_eq!(
AddressingFunctionTag::try_from(0x17),
Ok(AddressingFunctionTag::Frequency)
);
}
#[test]
fn addressing_function_tag_try_from_rejects_unknown() {
assert!(AddressingFunctionTag::try_from(0x14).is_err());
assert!(AddressingFunctionTag::try_from(0xFF).is_err());
}
#[test]
fn exhaustive_byte_sweep() {
let mut matched = 0u16;
for byte in 0u8..=0xFF {
if let Ok(v) = AddressingFunctionTag::try_from(byte) {
assert_eq!(v as u8, byte, "round-trip failed for {byte:#04x}");
matched += 1;
}
}
assert_eq!(matched, 14, "expected 14 matched variants");
}
#[test]
fn address_function_tag_display() {
assert_eq!(
AddressingFunctionTag::AcePapr.to_string(),
"ACE-PAPR reduction"
);
assert_eq!(AddressingFunctionTag::AcePapr.name(), "ACE-PAPR reduction");
assert_eq!(AddressingFunctionTag::CellId.to_string(), "Cell ID");
}
#[test]
fn parse_empty_data_loop() {
let buf = [0x00u8, 0x00];
let result = IndividualAddressingPayload::parse(&buf).unwrap();
assert_eq!(result.rfu, 0x00);
assert!(result.transmitters.is_empty());
}
#[test]
fn parse_preserves_rfu_byte() {
let buf = [0xFFu8, 0x00];
let result = IndividualAddressingPayload::parse(&buf).unwrap();
assert_eq!(result.rfu, 0xFF);
assert!(result.transmitters.is_empty());
}
#[test]
fn parse_rejects_short_buffer() {
assert!(IndividualAddressingPayload::parse(&[0x00]).is_err());
}
#[test]
fn parse_rejects_truncated_data() {
assert!(IndividualAddressingPayload::parse(&[0x00, 0x04, 0xAA, 0xBB]).is_err());
}
#[test]
fn parse_single_transmitter_single_function_ace_papr() {
let func_body = [0xA8u8, 0x54];
let func_loop_len = (FUNC_HEADER_LEN + func_body.len()) as u8;
let tx_loop = [
0x00,
0x05,
func_loop_len,
0x10,
ACE_PAPR_BODY_LEN as u8,
func_body[0],
func_body[1],
];
let mut buf = vec![0x00u8, tx_loop.len() as u8];
buf.extend_from_slice(&tx_loop);
let result = IndividualAddressingPayload::parse(&buf).unwrap();
assert_eq!(result.transmitters.len(), 1);
assert_eq!(result.transmitters[0].transmitter_id, 0x0005);
assert_eq!(result.transmitters[0].functions.len(), 1);
let func = &result.transmitters[0].functions[0];
assert_eq!(func.tag, 0x10);
assert_eq!(func.addressing_tag(), Some(AddressingFunctionTag::AcePapr));
match &func.body {
FunctionBody::AcePapr(body) => {
assert_eq!(body.ace_gain, 0x15);
assert_eq!(body.ace_maximal_extension, 0x00);
assert_eq!(body.ace_clipping_threshold, 0x2A);
assert!(!body.rfu);
}
other => panic!("expected AcePapr, got {other:?}"),
}
}
#[test]
fn parse_single_transmitter_single_function_miso_group() {
let func_body = [0x85u8];
let func_loop_len = (FUNC_HEADER_LEN + func_body.len()) as u8;
let tx_loop = [
0x00,
0x0A,
func_loop_len,
0x11,
MISO_GROUP_BODY_LEN as u8,
func_body[0],
];
let mut buf = vec![0x00u8, tx_loop.len() as u8];
buf.extend_from_slice(&tx_loop);
let result = IndividualAddressingPayload::parse(&buf).unwrap();
assert_eq!(result.transmitters.len(), 1);
assert_eq!(result.transmitters[0].transmitter_id, 0x000A);
let func = &result.transmitters[0].functions[0];
assert_eq!(func.tag, 0x11);
match &func.body {
FunctionBody::MisoGroup(body) => {
assert!(body.miso_group);
assert_eq!(body.rfu, 0x05);
}
other => panic!("expected MisoGroup, got {other:?}"),
}
}
#[test]
fn parse_single_transmitter_single_function_frequency() {
let freq_body = FrequencyBody {
rf_idx: 3,
frequency: 0x80000000,
rfu: 0,
};
let mut func_bytes = [0u8; FREQUENCY_BODY_LEN];
serialize_frequency(&freq_body, &mut func_bytes);
let func_loop_len = (FUNC_HEADER_LEN + func_bytes.len()) as u8;
let mut tx_loop = vec![0x00, 0x07, func_loop_len, 0x17, FREQUENCY_BODY_LEN as u8];
tx_loop.extend_from_slice(&func_bytes);
let mut buf = vec![0x00u8, tx_loop.len() as u8];
buf.extend_from_slice(&tx_loop);
let result = IndividualAddressingPayload::parse(&buf).unwrap();
let func = &result.transmitters[0].functions[0];
assert_eq!(func.tag, 0x17);
match &func.body {
FunctionBody::Frequency(body) => {
assert_eq!(body.rf_idx, 3);
assert_eq!(body.frequency, 0x80000000);
assert_eq!(body.rfu, 0);
}
other => panic!("expected Frequency, got {other:?}"),
}
}
#[test]
fn parse_unknown_tag_produces_raw_body() {
let func_body = [0xDE, 0xAD, 0xBE];
let func_loop_len = (FUNC_HEADER_LEN + func_body.len()) as u8;
let tx_loop = [
0x00,
0x01,
func_loop_len,
0x14,
func_body.len() as u8,
func_body[0],
func_body[1],
func_body[2],
];
let mut buf = vec![0x00u8, tx_loop.len() as u8];
buf.extend_from_slice(&tx_loop);
let result = IndividualAddressingPayload::parse(&buf).unwrap();
let func = &result.transmitters[0].functions[0];
assert_eq!(func.tag, 0x14);
assert_eq!(func.addressing_tag(), None);
match &func.body {
FunctionBody::Raw(bytes) => assert_eq!(*bytes, &[0xDE, 0xAD, 0xBE]),
other => panic!("expected Raw, got {other:?}"),
}
}
#[test]
fn parse_known_tag_wrong_length_falls_back_to_raw() {
let func_body = [0xAA, 0xBB, 0xCC];
let func_loop_len = (FUNC_HEADER_LEN + func_body.len()) as u8;
let tx_loop = [
0x00,
0x01,
func_loop_len,
0x10,
func_body.len() as u8,
func_body[0],
func_body[1],
func_body[2],
];
let mut buf = vec![0x00u8, tx_loop.len() as u8];
buf.extend_from_slice(&tx_loop);
let result = IndividualAddressingPayload::parse(&buf).unwrap();
let func = &result.transmitters[0].functions[0];
assert_eq!(func.tag, 0x10);
assert_eq!(func.addressing_tag(), Some(AddressingFunctionTag::AcePapr));
match &func.body {
FunctionBody::Raw(bytes) => assert_eq!(*bytes, &[0xAA, 0xBB, 0xCC]),
other => panic!("expected Raw fallback for length mismatch, got {other:?}"),
}
}
#[test]
fn parse_truncated_transmitter_header() {
let buf = [0x00u8, 0x02, 0x00];
assert!(IndividualAddressingPayload::parse(&buf).is_err());
}
#[test]
fn parse_truncated_function_header() {
let tx_loop = [0x00, 0x01, 0x02, 0x10];
let buf = [0x00u8, tx_loop.len() as u8, 0x00, 0x01, 0x02, 0x10];
assert!(IndividualAddressingPayload::parse(&buf).is_err());
}
#[test]
fn parse_function_body_exceeds_function_loop() {
let tx_loop = [0x00, 0x01, 0x04, 0x10, 0xFF, 0xAA, 0xBB];
let mut buf = vec![0x00u8, tx_loop.len() as u8];
buf.extend_from_slice(&tx_loop);
assert!(IndividualAddressingPayload::parse(&buf).is_err());
}
#[test]
fn round_trip_two_transmitters_mixed_bodies() {
let ace_body = AcePaprBody {
ace_gain: 0x0A,
ace_maximal_extension: 0x03,
ace_clipping_threshold: 0x5A,
rfu: true,
};
let raw_body: &[u8] = &[0xDE, 0xAD, 0xBE, 0xEF];
let orig = IndividualAddressingPayload {
rfu: 0xAB,
transmitters: vec![
TransmitterEntry {
transmitter_id: 0x0005,
functions: vec![
FunctionEntry {
tag: 0x10,
body: FunctionBody::AcePapr(ace_body.clone()),
},
FunctionEntry {
tag: 0x14,
body: FunctionBody::Raw(raw_body),
},
],
},
TransmitterEntry {
transmitter_id: 0x00FF,
functions: vec![FunctionEntry {
tag: 0x11,
body: FunctionBody::MisoGroup(MisoGroupBody {
miso_group: true,
rfu: 0x42,
}),
}],
},
],
};
let mut buf = vec![0u8; orig.serialized_len()];
orig.serialize_into(&mut buf).unwrap();
assert_eq!(buf[0], 0xAB);
let parsed = IndividualAddressingPayload::parse(&buf).unwrap();
assert_eq!(orig, parsed);
}
#[test]
fn round_trip_all_typed_bodies() {
let orig = IndividualAddressingPayload {
rfu: 0x00,
transmitters: vec![
TransmitterEntry {
transmitter_id: 0x0001,
functions: vec![
FunctionEntry {
tag: 0x10,
body: FunctionBody::AcePapr(AcePaprBody {
ace_gain: 0x1F,
ace_maximal_extension: 0x07,
ace_clipping_threshold: 0x7F,
rfu: true,
}),
},
FunctionEntry {
tag: 0x11,
body: FunctionBody::MisoGroup(MisoGroupBody {
miso_group: false,
rfu: 0x7F,
}),
},
FunctionEntry {
tag: 0x12,
body: FunctionBody::TrPapr(TrPaprBody {
rfu1: 0x0A,
tr_clipping_threshold: 0xABC,
rfu2: 0x1FFF,
number_of_iterations: 0x1FF,
}),
},
],
},
TransmitterEntry {
transmitter_id: 0x0002,
functions: vec![
FunctionEntry {
tag: 0x13,
body: FunctionBody::L1AcePapr(L1AcePaprBody {
l1_ace_max_correction: 0x1234,
rfu: 0x5678,
}),
},
FunctionEntry {
tag: 0x15,
body: FunctionBody::TxSigFefSeqNum(TxSigFefSeqNumBody {
rfu1: 0x1F,
seq_num_1: 0x07,
rfu2: 0x00,
seq_num_2: 0x05,
rfu3: 0xABCDEF,
}),
},
FunctionEntry {
tag: 0x16,
body: FunctionBody::TxSigAuxStreamTxId(TxSigAuxStreamTxIdBody {
tx_sig_aux_tx_id: 0xFFF,
rfu: 0x0000F,
}),
},
FunctionEntry {
tag: 0x17,
body: FunctionBody::Frequency(FrequencyBody {
rf_idx: 0x05,
frequency: 0x87654321,
rfu: 0x1F,
}),
},
],
},
],
};
let mut buf = vec![0u8; orig.serialized_len()];
orig.serialize_into(&mut buf).unwrap();
let parsed = IndividualAddressingPayload::parse(&buf).unwrap();
assert_eq!(orig, parsed);
}
#[test]
fn serialize_empty_data() {
let orig = IndividualAddressingPayload {
rfu: 0x00,
transmitters: vec![],
};
let mut buf = vec![0u8; orig.serialized_len()];
orig.serialize_into(&mut buf).unwrap();
assert_eq!(buf, [0x00, 0x00]);
}
#[test]
fn serialize_detects_data_loop_overflow() {
let mut functions = Vec::new();
for _ in 0..100 {
functions.push(FunctionEntry {
tag: AddressingFunctionTag::MisoGroup as u8,
body: FunctionBody::MisoGroup(MisoGroupBody {
miso_group: false,
rfu: 0,
}),
});
}
let payload = IndividualAddressingPayload {
rfu: 0,
transmitters: vec![TransmitterEntry {
transmitter_id: 0,
functions,
}],
};
let mut buf = vec![0u8; payload.serialized_len()];
let result = payload.serialize_into(&mut buf);
assert!(
matches!(
result.unwrap_err(),
crate::Error::ReservedBitsViolation { .. }
),
"expected ReservedBitsViolation for overflowing length field"
);
}
#[test]
fn body_parse_round_trip_ace_papr() {
let body = AcePaprBody {
ace_gain: 0x0A,
ace_maximal_extension: 0x05,
ace_clipping_threshold: 0x41,
rfu: true,
};
let mut buf = [0u8; ACE_PAPR_BODY_LEN];
serialize_ace_papr(&body, &mut buf);
let parsed = parse_ace_papr(&buf).unwrap();
assert_eq!(body, parsed);
}
#[test]
fn body_parse_round_trip_tr_papr() {
let body = TrPaprBody {
rfu1: 0x0C,
tr_clipping_threshold: 0xFFF,
rfu2: 0x0ABC,
number_of_iterations: 0x3FF,
};
let mut buf = [0u8; TR_PAPR_BODY_LEN];
serialize_tr_papr(&body, &mut buf);
let parsed = parse_tr_papr(&buf).unwrap();
assert_eq!(body, parsed);
}
#[test]
fn body_parse_round_trip_frequency() {
let body = FrequencyBody {
rf_idx: 0x07,
frequency: 0xFFFFFFFF,
rfu: 0x1F,
};
let mut buf = [0u8; FREQUENCY_BODY_LEN];
serialize_frequency(&body, &mut buf);
let parsed = parse_frequency(&buf).unwrap();
assert_eq!(body, parsed);
}
#[test]
fn body_parse_round_trip_tx_sig_aux_stream_tx_id() {
let body = TxSigAuxStreamTxIdBody {
tx_sig_aux_tx_id: 0xFFF,
rfu: 0xFFFFF,
};
let mut buf = [0u8; TX_SIG_AUX_STREAM_TX_ID_BODY_LEN];
serialize_tx_sig_aux_stream_tx_id(&body, &mut buf);
let parsed = parse_tx_sig_aux_stream_tx_id(&buf).unwrap();
assert_eq!(body, parsed);
}
#[test]
fn function_entry_addressing_tag_method() {
let entry_known = FunctionEntry {
tag: 0x10,
body: FunctionBody::AcePapr(AcePaprBody {
ace_gain: 0,
ace_maximal_extension: 0,
ace_clipping_threshold: 0,
rfu: false,
}),
};
assert_eq!(
entry_known.addressing_tag(),
Some(AddressingFunctionTag::AcePapr)
);
let entry_unknown = FunctionEntry {
tag: 0xFF,
body: FunctionBody::Raw(&[]),
};
assert_eq!(entry_unknown.addressing_tag(), None);
}
}