use crate::decoded::Decoded;
use crate::error::DecodeError;
use crate::warn::DynamicHdrWarning;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum DynamicHdrInfoFrame {
Unknown {
format_id: u8,
},
}
impl DynamicHdrInfoFrame {
pub fn decode_sequence(
packets: &[[u8; 31]],
) -> Result<Decoded<DynamicHdrInfoFrame, DynamicHdrWarning>, DecodeError> {
let mut decoded = Decoded::new(DynamicHdrInfoFrame::Unknown { format_id: 0 });
for packet in packets {
let length = packet[2];
if length > 27 {
return Err(DecodeError::Truncated { claimed: length });
}
let total: u8 = packet.iter().fold(0u8, |acc, &b| acc.wrapping_add(b));
if total != 0x00 {
let expected = crate::checksum::compute_checksum(packet[..30].try_into().unwrap());
decoded.push_warning(DynamicHdrWarning::ChecksumMismatch {
expected,
found: packet[3],
});
}
}
if let Some(first) = packets.first() {
let format_id = first[7];
decoded.value = DynamicHdrInfoFrame::Unknown { format_id };
}
Ok(decoded)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct DynamicHdrFragment {
pub seq_num: u8,
pub total_bytes: u16,
pub format_id: u8,
pub chunk: [u8; 23],
pub chunk_len: u8,
}
impl DynamicHdrFragment {
pub fn decode(
packet: &[u8; 31],
) -> Result<Decoded<DynamicHdrFragment, DynamicHdrWarning>, DecodeError> {
let length = packet[2];
if length > 27 {
return Err(DecodeError::Truncated { claimed: length });
}
let mut decoded = Decoded::new(DynamicHdrFragment {
seq_num: 0,
total_bytes: 0,
format_id: 0,
chunk: [0u8; 23],
chunk_len: 0,
});
let total: u8 = packet.iter().fold(0u8, |acc, &b| acc.wrapping_add(b));
if total != 0x00 {
let expected = crate::checksum::compute_checksum(packet[..30].try_into().unwrap());
decoded.push_warning(DynamicHdrWarning::ChecksumMismatch {
expected,
found: packet[3],
});
}
decoded.value.seq_num = packet[4];
decoded.value.total_bytes = u16::from_le_bytes([packet[5], packet[6]]);
decoded.value.format_id = packet[7];
let chunk_len = length.saturating_sub(4).min(23);
decoded.value.chunk_len = chunk_len;
decoded.value.chunk[..chunk_len as usize]
.copy_from_slice(&packet[8..8 + chunk_len as usize]);
Ok(decoded)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_packet(seq_num: u8, total_bytes: u16, format_id: u8, chunk: &[u8]) -> [u8; 31] {
let chunk_len = chunk.len().min(23) as u8;
let length = 4 + chunk_len;
let mut packet = [0u8; 31];
packet[0] = 0x20; packet[1] = 0x01; packet[2] = length;
packet[4] = seq_num;
packet[5] = (total_bytes & 0xFF) as u8;
packet[6] = (total_bytes >> 8) as u8;
packet[7] = format_id;
packet[8..8 + chunk_len as usize].copy_from_slice(&chunk[..chunk_len as usize]);
let sum: u8 = packet.iter().fold(0u8, |a, &b| a.wrapping_add(b));
packet[3] = packet[3].wrapping_sub(sum);
packet
}
#[test]
fn round_trip_fields() {
let chunk_data: [u8; 23] = core::array::from_fn(|i| i as u8);
let packet = make_packet(3, 0x0142, 0x04, &chunk_data);
let decoded = DynamicHdrFragment::decode(&packet).unwrap();
assert!(decoded.iter_warnings().next().is_none());
assert_eq!(decoded.value.seq_num, 3);
assert_eq!(decoded.value.total_bytes, 0x0142);
assert_eq!(decoded.value.format_id, 0x04);
assert_eq!(decoded.value.chunk_len, 23);
assert_eq!(&decoded.value.chunk[..23], &chunk_data);
}
#[test]
fn partial_chunk_last_packet() {
let chunk_data = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE];
let packet = make_packet(1, 28, 0x04, &chunk_data);
let decoded = DynamicHdrFragment::decode(&packet).unwrap();
assert_eq!(decoded.value.chunk_len, 5);
assert_eq!(&decoded.value.chunk[..5], &chunk_data);
}
#[test]
fn checksum_mismatch_warning() {
let mut packet = make_packet(0, 23, 0x04, &[0u8; 23]);
packet[3] = packet[3].wrapping_add(1); let decoded = DynamicHdrFragment::decode(&packet).unwrap();
assert!(
decoded
.iter_warnings()
.any(|w| matches!(w, DynamicHdrWarning::ChecksumMismatch { .. }))
);
}
#[test]
fn truncated_length_is_error() {
let mut packet = make_packet(0, 0, 0x00, &[]);
packet[2] = 28; assert!(matches!(
DynamicHdrFragment::decode(&packet),
Err(DecodeError::Truncated { claimed: 28 })
));
}
#[test]
fn zero_length_payload() {
let mut packet = [0u8; 31];
packet[0] = 0x20;
packet[1] = 0x01;
packet[2] = 0; let sum: u8 = packet.iter().fold(0u8, |a, &b| a.wrapping_add(b));
packet[3] = packet[3].wrapping_sub(sum);
let decoded = DynamicHdrFragment::decode(&packet).unwrap();
assert!(decoded.iter_warnings().next().is_none());
assert_eq!(decoded.value.chunk_len, 0);
}
#[test]
fn decode_sequence_single_packet_unknown_format() {
let packet = make_packet(0, 23, 0x04, &[0xAAu8; 23]);
let decoded = DynamicHdrInfoFrame::decode_sequence(&[packet]).unwrap();
assert!(decoded.iter_warnings().next().is_none());
assert_eq!(
decoded.value,
DynamicHdrInfoFrame::Unknown { format_id: 0x04 }
);
}
#[test]
fn decode_sequence_multi_packet_format_id_from_first() {
let p0 = make_packet(0, 46, 0x04, &[0xAAu8; 23]);
let p1 = make_packet(1, 46, 0x04, &[0xBBu8; 23]);
let decoded = DynamicHdrInfoFrame::decode_sequence(&[p0, p1]).unwrap();
assert!(decoded.iter_warnings().next().is_none());
assert_eq!(
decoded.value,
DynamicHdrInfoFrame::Unknown { format_id: 0x04 }
);
}
#[test]
fn decode_sequence_empty_yields_unknown_zero() {
let decoded = DynamicHdrInfoFrame::decode_sequence(&[]).unwrap();
assert!(decoded.iter_warnings().next().is_none());
assert_eq!(decoded.value, DynamicHdrInfoFrame::Unknown { format_id: 0 });
}
#[test]
fn decode_sequence_checksum_mismatch_warning() {
let mut p0 = make_packet(0, 23, 0x04, &[0u8; 23]);
p0[3] = p0[3].wrapping_add(1); let decoded = DynamicHdrInfoFrame::decode_sequence(&[p0]).unwrap();
assert!(
decoded
.iter_warnings()
.any(|w| matches!(w, DynamicHdrWarning::ChecksumMismatch { .. }))
);
}
#[test]
fn decode_sequence_truncated_returns_error() {
let mut p0 = make_packet(0, 23, 0x04, &[0u8; 23]);
p0[2] = 28; assert!(matches!(
DynamicHdrInfoFrame::decode_sequence(&[p0]),
Err(DecodeError::Truncated { claimed: 28 })
));
}
}