#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use crate::endianness::Endianness;
use crate::error::DecodeError;
#[cfg(feature = "alloc")]
use crate::error::EncodeError;
#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
pub struct BufferWriter {
bytes: Vec<u8>,
endianness: Endianness,
}
#[cfg(feature = "alloc")]
impl BufferWriter {
#[must_use]
pub fn new(endianness: Endianness) -> Self {
Self {
bytes: Vec::new(),
endianness,
}
}
#[must_use]
pub fn with_capacity(endianness: Endianness, cap: usize) -> Self {
Self {
bytes: Vec::with_capacity(cap),
endianness,
}
}
#[must_use]
pub fn endianness(&self) -> Endianness {
self.endianness
}
#[must_use]
pub fn position(&self) -> usize {
self.bytes.len()
}
#[must_use]
pub fn into_bytes(self) -> Vec<u8> {
self.bytes
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
pub fn align(&mut self, alignment: usize) {
debug_assert!(
alignment.is_power_of_two(),
"alignment must be a power of two"
);
let pos = self.bytes.len();
let pad = padding_for(pos, alignment);
for _ in 0..pad {
self.bytes.push(0);
}
}
pub fn write_bytes(&mut self, data: &[u8]) -> Result<(), EncodeError> {
self.bytes.extend_from_slice(data);
Ok(())
}
pub fn write_u8(&mut self, value: u8) -> Result<(), EncodeError> {
self.bytes.push(value);
Ok(())
}
pub fn write_u16(&mut self, value: u16) -> Result<(), EncodeError> {
self.align(2);
self.write_bytes(&self.endianness.write_u16(value))
}
pub fn write_u32(&mut self, value: u32) -> Result<(), EncodeError> {
self.align(4);
self.write_bytes(&self.endianness.write_u32(value))
}
pub fn write_u64(&mut self, value: u64) -> Result<(), EncodeError> {
self.align(8);
self.write_bytes(&self.endianness.write_u64(value))
}
pub fn write_string(&mut self, s: &str) -> Result<(), EncodeError> {
let bytes = s.as_bytes();
let len = u32::try_from(bytes.len().saturating_add(1)).map_err(|_| {
EncodeError::ValueOutOfRange {
message: "CDR string length exceeds u32::MAX",
}
})?;
self.write_u32(len)?;
self.write_bytes(bytes)?;
self.write_u8(0)
}
}
#[derive(Debug, Clone)]
pub struct BufferReader<'a> {
bytes: &'a [u8],
pos: usize,
endianness: Endianness,
}
impl<'a> BufferReader<'a> {
#[must_use]
pub fn new(bytes: &'a [u8], endianness: Endianness) -> Self {
Self {
bytes,
pos: 0,
endianness,
}
}
#[must_use]
pub fn endianness(&self) -> Endianness {
self.endianness
}
#[must_use]
pub fn position(&self) -> usize {
self.pos
}
#[must_use]
pub fn remaining(&self) -> usize {
self.bytes.len().saturating_sub(self.pos)
}
pub fn align(&mut self, alignment: usize) -> Result<(), DecodeError> {
debug_assert!(
alignment.is_power_of_two(),
"alignment must be power of two"
);
let pad = padding_for(self.pos, alignment);
if self.remaining() < pad {
return Err(DecodeError::UnexpectedEof {
needed: pad,
offset: self.pos,
});
}
self.pos += pad;
Ok(())
}
pub fn read_bytes(&mut self, n: usize) -> Result<&'a [u8], DecodeError> {
if self.remaining() < n {
return Err(DecodeError::UnexpectedEof {
needed: n,
offset: self.pos,
});
}
let slice = &self.bytes[self.pos..self.pos + n];
self.pos += n;
Ok(slice)
}
pub fn read_u8(&mut self) -> Result<u8, DecodeError> {
let slice = self.read_bytes(1)?;
Ok(slice[0])
}
pub fn read_u16(&mut self) -> Result<u16, DecodeError> {
self.align(2)?;
let slice = self.read_bytes(2)?;
let mut buf = [0u8; 2];
buf.copy_from_slice(slice);
Ok(self.endianness.read_u16(buf))
}
pub fn read_u32(&mut self) -> Result<u32, DecodeError> {
self.align(4)?;
let slice = self.read_bytes(4)?;
let mut buf = [0u8; 4];
buf.copy_from_slice(slice);
Ok(self.endianness.read_u32(buf))
}
pub fn read_u64(&mut self) -> Result<u64, DecodeError> {
self.align(8)?;
let slice = self.read_bytes(8)?;
let mut buf = [0u8; 8];
buf.copy_from_slice(slice);
Ok(self.endianness.read_u64(buf))
}
#[cfg(feature = "alloc")]
pub fn read_string(&mut self) -> Result<alloc::string::String, DecodeError> {
use alloc::string::String;
let start = self.pos;
let len = self.read_u32()? as usize;
if len == 0 {
return Err(DecodeError::InvalidString {
offset: start,
reason: "length must be > 0 (null terminator required)",
});
}
let raw = self.read_bytes(len)?;
if raw[len - 1] != 0 {
return Err(DecodeError::InvalidString {
offset: start,
reason: "missing null terminator",
});
}
String::from_utf8(raw[..len - 1].to_vec())
.map_err(|_| DecodeError::InvalidUtf8 { offset: start + 4 })
}
}
#[must_use]
pub fn padding_for(pos: usize, alignment: usize) -> usize {
let mask = alignment - 1;
(alignment - (pos & mask)) & mask
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use super::*;
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::vec;
#[test]
fn padding_for_zero_position_is_zero() {
assert_eq!(padding_for(0, 4), 0);
assert_eq!(padding_for(0, 8), 0);
}
#[test]
fn padding_for_already_aligned_is_zero() {
assert_eq!(padding_for(8, 4), 0);
assert_eq!(padding_for(16, 8), 0);
}
#[test]
fn padding_for_one_byte_to_4_align_is_three() {
assert_eq!(padding_for(1, 4), 3);
}
#[test]
fn padding_for_three_bytes_to_8_align_is_five() {
assert_eq!(padding_for(3, 8), 5);
}
#[cfg(feature = "alloc")]
#[test]
fn writer_writes_u8_without_padding() {
let mut w = BufferWriter::new(Endianness::Little);
w.write_u8(0xAB).unwrap();
assert_eq!(w.as_bytes(), &[0xAB]);
assert_eq!(w.position(), 1);
}
#[cfg(feature = "alloc")]
#[test]
fn writer_aligns_u32_after_u8() {
let mut w = BufferWriter::new(Endianness::Little);
w.write_u8(0xAB).unwrap();
w.write_u32(0xDEAD_BEEF).unwrap();
assert_eq!(w.as_bytes(), &[0xAB, 0, 0, 0, 0xEF, 0xBE, 0xAD, 0xDE]);
}
#[cfg(feature = "alloc")]
#[test]
fn writer_aligns_u64_after_u8() {
let mut w = BufferWriter::new(Endianness::Big);
w.write_u8(0x01).unwrap();
w.write_u64(0x0203_0405_0607_0809).unwrap();
assert_eq!(
w.as_bytes(),
&[0x01, 0, 0, 0, 0, 0, 0, 0, 2, 3, 4, 5, 6, 7, 8, 9]
);
}
#[cfg(feature = "alloc")]
#[test]
fn writer_with_capacity_preserves_endianness() {
let w = BufferWriter::with_capacity(Endianness::Big, 64);
assert_eq!(w.endianness(), Endianness::Big);
assert_eq!(w.position(), 0);
}
#[cfg(feature = "alloc")]
#[test]
fn writer_into_bytes_returns_full_buffer() {
let mut w = BufferWriter::new(Endianness::Little);
w.write_u32(0xCAFE_BABE).unwrap();
let bytes = w.into_bytes();
assert_eq!(bytes, vec![0xBE, 0xBA, 0xFE, 0xCA]);
}
#[test]
fn reader_reads_u8() {
let bytes = [0xAB, 0xCD];
let mut r = BufferReader::new(&bytes, Endianness::Little);
assert_eq!(r.read_u8().unwrap(), 0xAB);
assert_eq!(r.position(), 1);
}
#[test]
fn reader_aligns_before_u32() {
let bytes = [0xAB, 0, 0, 0, 0xEF, 0xBE, 0xAD, 0xDE];
let mut r = BufferReader::new(&bytes, Endianness::Little);
assert_eq!(r.read_u8().unwrap(), 0xAB);
assert_eq!(r.read_u32().unwrap(), 0xDEAD_BEEF);
assert_eq!(r.remaining(), 0);
}
#[test]
fn reader_aligns_before_u64_be() {
let bytes = [0x01, 0, 0, 0, 0, 0, 0, 0, 2, 3, 4, 5, 6, 7, 8, 9];
let mut r = BufferReader::new(&bytes, Endianness::Big);
assert_eq!(r.read_u8().unwrap(), 0x01);
assert_eq!(r.read_u64().unwrap(), 0x0203_0405_0607_0809);
}
#[test]
fn reader_unexpected_eof_on_short_read() {
let bytes = [0u8; 2];
let mut r = BufferReader::new(&bytes, Endianness::Little);
let res = r.read_u32();
match res {
Err(DecodeError::UnexpectedEof {
needed: 4,
offset: 0,
}) => {}
other => panic!("expected UnexpectedEof, got {other:?}"),
}
}
#[test]
fn reader_eof_on_align_overflow() {
let bytes = [0u8; 1];
let mut r = BufferReader::new(&bytes, Endianness::Little);
let _ = r.read_u8().unwrap();
let res = r.align(8);
assert!(matches!(res, Err(DecodeError::UnexpectedEof { .. })));
}
#[cfg(feature = "alloc")]
#[test]
fn writer_reader_roundtrip_mixed_primitives() {
let mut w = BufferWriter::new(Endianness::Little);
w.write_u8(1).unwrap();
w.write_u16(0x1234).unwrap();
w.write_u32(0x5678_9ABC).unwrap();
w.write_u64(0x0102_0304_0506_0708).unwrap();
let bytes = w.into_bytes();
let mut r = BufferReader::new(&bytes, Endianness::Little);
assert_eq!(r.read_u8().unwrap(), 1);
assert_eq!(r.read_u16().unwrap(), 0x1234);
assert_eq!(r.read_u32().unwrap(), 0x5678_9ABC);
assert_eq!(r.read_u64().unwrap(), 0x0102_0304_0506_0708);
assert_eq!(r.remaining(), 0);
}
#[cfg(feature = "alloc")]
#[test]
fn write_read_string_roundtrip_ascii() {
let mut w = BufferWriter::new(Endianness::Little);
w.write_string("ChatterTopic").unwrap();
let bytes = w.into_bytes();
assert_eq!(&bytes[0..4], &[13, 0, 0, 0]);
assert_eq!(&bytes[4..16], b"ChatterTopic");
assert_eq!(bytes[16], 0);
let mut r = BufferReader::new(&bytes, Endianness::Little);
assert_eq!(r.read_string().unwrap(), "ChatterTopic");
}
#[cfg(feature = "alloc")]
#[test]
fn write_read_string_empty() {
let mut w = BufferWriter::new(Endianness::Little);
w.write_string("").unwrap();
let bytes = w.into_bytes();
assert_eq!(&bytes[..], &[1, 0, 0, 0, 0]);
let mut r = BufferReader::new(&bytes, Endianness::Little);
assert_eq!(r.read_string().unwrap(), "");
}
#[cfg(feature = "alloc")]
#[test]
fn write_read_string_utf8_multibyte() {
let mut w = BufferWriter::new(Endianness::Little);
w.write_string("Zähler").unwrap(); let bytes = w.into_bytes();
let mut r = BufferReader::new(&bytes, Endianness::Little);
assert_eq!(r.read_string().unwrap(), "Zähler");
}
#[cfg(feature = "alloc")]
#[test]
fn read_string_rejects_length_zero() {
let bytes = [0u8, 0, 0, 0];
let mut r = BufferReader::new(&bytes, Endianness::Little);
assert!(matches!(
r.read_string(),
Err(DecodeError::InvalidString { .. })
));
}
#[cfg(feature = "alloc")]
#[test]
fn read_string_rejects_missing_null_terminator() {
let bytes = [4u8, 0, 0, 0, b'A', b'B', b'C', b'D'];
let mut r = BufferReader::new(&bytes, Endianness::Little);
assert!(matches!(
r.read_string(),
Err(DecodeError::InvalidString { .. })
));
}
#[cfg(feature = "alloc")]
#[test]
fn read_string_rejects_invalid_utf8() {
let bytes = [3u8, 0, 0, 0, 0xFF, 0xFE, 0];
let mut r = BufferReader::new(&bytes, Endianness::Little);
assert!(matches!(
r.read_string(),
Err(DecodeError::InvalidUtf8 { .. })
));
}
#[test]
fn reader_endianness_getter_returns_construction_value() {
let r_be = BufferReader::new(&[0u8; 4], Endianness::Big);
assert_eq!(r_be.endianness(), Endianness::Big);
let r_le = BufferReader::new(&[0u8; 4], Endianness::Little);
assert_eq!(r_le.endianness(), Endianness::Little);
}
#[test]
fn reader_align_succeeds_when_remaining_equals_pad() {
let bytes = [0xAA, 0, 0, 0];
let mut r = BufferReader::new(&bytes, Endianness::Little);
r.read_u8().unwrap();
assert!(r.align(4).is_ok());
assert_eq!(r.position(), 4);
}
#[test]
fn reader_align_advances_position_strictly() {
let bytes = [0xAA, 0, 0, 0, 1, 2, 3, 4];
let mut r = BufferReader::new(&bytes, Endianness::Little);
r.read_u8().unwrap();
assert_eq!(r.position(), 1);
r.align(4).unwrap();
assert_eq!(r.position(), 4);
}
#[test]
fn reader_read_bytes_advances_position_strictly() {
let bytes = [1, 2, 3, 4, 5, 6, 7, 8];
let mut r = BufferReader::new(&bytes, Endianness::Little);
let _ = r.read_bytes(3).unwrap();
assert_eq!(r.position(), 3);
let _ = r.read_bytes(2).unwrap();
assert_eq!(r.position(), 5);
}
#[test]
fn reader_read_u16_returns_actual_bytes_not_zero() {
let bytes = [0x12, 0x34];
let mut r = BufferReader::new(&bytes, Endianness::Little);
assert_eq!(r.read_u16().unwrap(), 0x3412);
let mut r_be = BufferReader::new(&bytes, Endianness::Big);
assert_eq!(r_be.read_u16().unwrap(), 0x1234);
}
#[test]
fn reader_read_string_invalid_utf8_offset_is_start_plus_four() {
let mut bytes = vec![0xAB]; bytes.extend_from_slice(&[0, 0, 0]);
bytes.extend_from_slice(&3u32.to_le_bytes());
bytes.extend_from_slice(&[0xFF, 0xFE, 0]);
let mut r = BufferReader::new(&bytes, Endianness::Little);
r.read_u8().unwrap();
let err = r.read_string().unwrap_err();
match err {
DecodeError::InvalidUtf8 { offset } => assert_eq!(offset, 5),
other => panic!("expected InvalidUtf8, got {other:?}"),
}
}
#[test]
fn reader_read_string_invalid_utf8_offset_with_start_two() {
let mut bytes = vec![0xAB, 0xCD]; bytes.extend_from_slice(&[0, 0]);
bytes.extend_from_slice(&3u32.to_le_bytes());
bytes.extend_from_slice(&[0xFF, 0xFE, 0]);
let mut r = BufferReader::new(&bytes, Endianness::Little);
r.read_u8().unwrap();
r.read_u8().unwrap();
let err = r.read_string().unwrap_err();
match err {
DecodeError::InvalidUtf8 { offset } => assert_eq!(offset, 6),
other => panic!("expected InvalidUtf8, got {other:?}"),
}
}
}