extern crate alloc;
use alloc::vec::Vec;
use crate::buffer::{BufferReader, BufferWriter};
use crate::error::{DecodeError, EncodeError};
pub const PID_LIST_END: u16 = 0x3F02;
pub const PID_EXTENDED: u16 = 0x3F01;
pub const PID_EXTENDED_THRESHOLD: u32 = 0x3F00;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PlCdr1Member {
pub member_id: u32,
pub body: Vec<u8>,
}
pub fn encode_pl_cdr1_member<F>(
writer: &mut BufferWriter,
member_id: u32,
body: F,
) -> Result<(), EncodeError>
where
F: FnOnce(&mut BufferWriter) -> Result<(), EncodeError>,
{
let mut inner = BufferWriter::new(writer.endianness());
body(&mut inner)?;
let mut body_bytes = inner.into_bytes();
while body_bytes.len() % 4 != 0 {
body_bytes.push(0);
}
let body_len = body_bytes.len();
let needs_extended = member_id >= PID_EXTENDED_THRESHOLD || body_len > 0xFFFF;
if needs_extended {
writer.write_u16(PID_EXTENDED)?;
writer.write_u16(8u16)?;
writer.write_u32(member_id)?;
let len_u32 = u32::try_from(body_len).map_err(|_| EncodeError::ValueOutOfRange {
message: "PL_CDR1 body length exceeds u32::MAX",
})?;
writer.write_u32(len_u32)?;
writer.write_bytes(&body_bytes)?;
} else {
let id_u16 = u16::try_from(member_id).map_err(|_| EncodeError::ValueOutOfRange {
message: "PL_CDR1 standard member_id must fit in u16",
})?;
let len_u16 = u16::try_from(body_len).map_err(|_| EncodeError::ValueOutOfRange {
message: "PL_CDR1 standard length must fit in u16",
})?;
writer.write_u16(id_u16)?;
writer.write_u16(len_u16)?;
writer.write_bytes(&body_bytes)?;
}
Ok(())
}
pub fn write_pl_cdr1_sentinel(writer: &mut BufferWriter) -> Result<(), EncodeError> {
writer.write_u16(PID_LIST_END)?;
writer.write_u16(0u16)
}
pub fn read_pl_cdr1_member(
reader: &mut BufferReader<'_>,
) -> Result<Option<PlCdr1Member>, DecodeError> {
if reader.remaining() < 4 {
return Err(DecodeError::UnexpectedEof {
needed: 4,
offset: reader.position(),
});
}
let pid = reader.read_u16()?;
let len_u16 = reader.read_u16()?;
if pid == PID_LIST_END {
return Ok(None);
}
let (member_id, body_len) = if pid == PID_EXTENDED {
if len_u16 != 8 {
return Err(DecodeError::LengthExceeded {
announced: usize::from(len_u16),
remaining: 8,
offset: reader.position(),
});
}
let m_id = reader.read_u32()?;
let b_len_u32 = reader.read_u32()?;
let b_len = usize::try_from(b_len_u32).map_err(|_| DecodeError::LengthExceeded {
announced: usize::MAX,
remaining: reader.remaining(),
offset: reader.position(),
})?;
(m_id, b_len)
} else {
(u32::from(pid), usize::from(len_u16))
};
if body_len > reader.remaining() {
return Err(DecodeError::LengthExceeded {
announced: body_len,
remaining: reader.remaining(),
offset: reader.position(),
});
}
let body = reader.read_bytes(body_len)?.to_vec();
Ok(Some(PlCdr1Member { member_id, body }))
}
pub fn read_all_pl_cdr1_members(
reader: &mut BufferReader<'_>,
) -> Result<Vec<PlCdr1Member>, DecodeError> {
let mut out = Vec::new();
while let Some(m) = read_pl_cdr1_member(reader)? {
out.push(m);
}
Ok(out)
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used)]
mod tests {
use super::*;
use crate::Endianness;
use crate::encode::CdrEncode;
use alloc::vec;
#[test]
fn standard_header_for_small_id_and_length() {
let mut w = BufferWriter::new(Endianness::Little);
encode_pl_cdr1_member(&mut w, 7, |w| 42u32.encode(w)).unwrap();
let bytes = w.into_bytes();
assert_eq!(&bytes[0..4], &[0x07, 0x00, 0x04, 0x00]);
assert_eq!(&bytes[4..8], &[42, 0, 0, 0]);
assert_eq!(bytes.len(), 8);
}
#[test]
fn extended_header_for_id_above_threshold() {
let mut w = BufferWriter::new(Endianness::Little);
encode_pl_cdr1_member(&mut w, 16_129, |w| 99u32.encode(w)).unwrap();
let bytes = w.into_bytes();
assert_eq!(&bytes[0..4], &[0x01, 0x3F, 0x08, 0x00]);
assert_eq!(&bytes[4..8], &[0x01, 0x3F, 0x00, 0x00]);
assert_eq!(&bytes[8..12], &[0x04, 0x00, 0x00, 0x00]);
assert_eq!(&bytes[12..16], &[99, 0, 0, 0]);
}
#[test]
fn extended_header_for_large_body_length() {
let mut w = BufferWriter::new(Endianness::Little);
let big = vec![0xABu8; 70_000];
encode_pl_cdr1_member(&mut w, 1, |w| {
for b in &big {
w.write_u8(*b)?;
}
Ok(())
})
.unwrap();
let bytes = w.into_bytes();
assert_eq!(&bytes[0..4], &[0x01, 0x3F, 0x08, 0x00]);
assert_eq!(&bytes[4..8], &[0x01, 0x00, 0x00, 0x00]);
let len_field = u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
assert_eq!(len_field, 70_000);
}
#[test]
fn xcdr1_pl_cdr_long_header_roundtrip() {
let mut w = BufferWriter::new(Endianness::Little);
encode_pl_cdr1_member(&mut w, 10, |w| 0xCAFEu32.encode(w)).unwrap();
encode_pl_cdr1_member(&mut w, 70_000, |w| 0xBEEFu32.encode(w)).unwrap();
write_pl_cdr1_sentinel(&mut w).unwrap();
let bytes = w.into_bytes();
let mut r = BufferReader::new(&bytes, Endianness::Little);
let members = read_all_pl_cdr1_members(&mut r).unwrap();
assert_eq!(members.len(), 2);
assert_eq!(members[0].member_id, 10);
assert_eq!(members[1].member_id, 70_000);
assert_eq!(&members[0].body[..4], &0xCAFEu32.to_le_bytes());
assert_eq!(&members[1].body[..4], &0xBEEFu32.to_le_bytes());
}
#[test]
fn xcdr1_member_id_above_threshold_uses_extended_pid() {
let mut w = BufferWriter::new(Endianness::Little);
encode_pl_cdr1_member(&mut w, 0x3F00, |w| 1u8.encode(w)).unwrap();
let bytes = w.into_bytes();
assert_eq!(&bytes[0..2], &[0x01, 0x3F]); }
#[test]
fn xcdr1_member_id_just_below_threshold_uses_standard() {
let mut w = BufferWriter::new(Endianness::Little);
encode_pl_cdr1_member(&mut w, 0x3EFF, |w| 1u8.encode(w)).unwrap();
let bytes = w.into_bytes();
assert_eq!(&bytes[0..2], &[0xFF, 0x3E]); }
#[test]
fn xcdr1_sentinel_terminates_decode() {
let bytes = vec![0x02, 0x3F, 0x00, 0x00];
let mut r = BufferReader::new(&bytes, Endianness::Little);
let members = read_all_pl_cdr1_members(&mut r).unwrap();
assert!(members.is_empty());
}
#[test]
fn xcdr1_truncated_extended_header_rejected() {
let bytes = vec![0x01, 0x3F, 0x04, 0x00, 0, 0, 0, 0];
let mut r = BufferReader::new(&bytes, Endianness::Little);
let res = read_pl_cdr1_member(&mut r);
assert!(matches!(res, Err(DecodeError::LengthExceeded { .. })));
}
#[test]
fn xcdr1_truncated_body_rejected() {
let bytes = vec![0x01, 0x00, 0x64, 0x00, 1, 2, 3, 4, 5, 6, 7, 8];
let mut r = BufferReader::new(&bytes, Endianness::Little);
let res = read_pl_cdr1_member(&mut r);
assert!(matches!(res, Err(DecodeError::LengthExceeded { .. })));
}
}