use std::fmt;
pub const CDR_LE_HEADER: [u8; 4] = [0x00, 0x01, 0x00, 0x00];
pub const CDR_HEADER_SIZE: usize = 4;
#[derive(Debug)]
pub enum CdrError {
BufferTooShort { need: usize, have: usize },
InvalidUtf8,
MissingNul,
InvalidHeader,
InvalidBool(u8),
}
impl fmt::Display for CdrError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CdrError::BufferTooShort { need, have } => {
write!(
f,
"CDR buffer too short: need {} bytes, have {}",
need, have
)
}
CdrError::InvalidUtf8 => write!(f, "CDR string contains invalid UTF-8"),
CdrError::MissingNul => write!(f, "CDR string missing NUL terminator"),
CdrError::InvalidHeader => write!(f, "invalid CDR encapsulation header"),
CdrError::InvalidBool(v) => write!(f, "invalid CDR bool value: {}", v),
}
}
}
impl std::error::Error for CdrError {}
#[inline(always)]
pub const fn align(pos: usize, n: usize) -> usize {
(pos + n - 1) & !(n - 1)
}
#[inline(always)]
pub const fn cdr_align(pos: usize, n: usize) -> usize {
CDR_HEADER_SIZE + align(pos - CDR_HEADER_SIZE, n)
}
pub struct CdrCursor<'a> {
buf: &'a [u8],
pos: usize,
}
impl<'a> CdrCursor<'a> {
pub fn new(buf: &'a [u8]) -> Result<Self, CdrError> {
if buf.len() < CDR_HEADER_SIZE {
return Err(CdrError::BufferTooShort {
need: CDR_HEADER_SIZE,
have: buf.len(),
});
}
if buf[0..4] != CDR_LE_HEADER {
return Err(CdrError::InvalidHeader);
}
Ok(CdrCursor {
buf,
pos: CDR_HEADER_SIZE,
})
}
pub fn resume(buf: &'a [u8], offset: usize) -> Self {
CdrCursor { buf, pos: offset }
}
#[inline(always)]
pub fn offset(&self) -> usize {
self.pos
}
#[inline(always)]
pub fn remaining(&self) -> usize {
self.buf.len() - self.pos
}
#[inline(always)]
pub fn align(&mut self, n: usize) {
self.pos = CDR_HEADER_SIZE + align(self.pos - CDR_HEADER_SIZE, n);
}
#[inline(always)]
pub fn skip(&mut self, n: usize) -> Result<(), CdrError> {
self.ensure(n)?;
self.pos += n;
Ok(())
}
#[inline(always)]
pub fn set_pos(&mut self, pos: usize) {
self.pos = pos;
}
#[inline(always)]
fn ensure(&self, n: usize) -> Result<(), CdrError> {
if self.pos + n > self.buf.len() {
Err(CdrError::BufferTooShort {
need: self.pos + n,
have: self.buf.len(),
})
} else {
Ok(())
}
}
pub fn read_u8(&mut self) -> Result<u8, CdrError> {
self.ensure(1)?;
let v = self.buf[self.pos];
self.pos += 1;
Ok(v)
}
pub fn read_i8(&mut self) -> Result<i8, CdrError> {
Ok(self.read_u8()? as i8)
}
pub fn read_bool(&mut self) -> Result<bool, CdrError> {
let v = self.read_u8()?;
match v {
0 => Ok(false),
1 => Ok(true),
_ => Err(CdrError::InvalidBool(v)),
}
}
pub fn read_u16(&mut self) -> Result<u16, CdrError> {
self.align(2);
self.ensure(2)?;
let v = u16::from_le_bytes([self.buf[self.pos], self.buf[self.pos + 1]]);
self.pos += 2;
Ok(v)
}
pub fn read_i16(&mut self) -> Result<i16, CdrError> {
Ok(self.read_u16()? as i16)
}
pub fn read_u32(&mut self) -> Result<u32, CdrError> {
self.align(4);
self.ensure(4)?;
let v = u32::from_le_bytes(
self.buf[self.pos..self.pos + 4]
.try_into()
.expect("slice is exactly 4 bytes after bounds check"),
);
self.pos += 4;
Ok(v)
}
pub fn read_i32(&mut self) -> Result<i32, CdrError> {
Ok(self.read_u32()? as i32)
}
pub fn read_u64(&mut self) -> Result<u64, CdrError> {
self.align(8);
self.ensure(8)?;
let v = u64::from_le_bytes(
self.buf[self.pos..self.pos + 8]
.try_into()
.expect("slice is exactly 8 bytes after bounds check"),
);
self.pos += 8;
Ok(v)
}
pub fn read_i64(&mut self) -> Result<i64, CdrError> {
Ok(self.read_u64()? as i64)
}
pub fn read_f32(&mut self) -> Result<f32, CdrError> {
Ok(f32::from_bits(self.read_u32()?))
}
pub fn read_f64(&mut self) -> Result<f64, CdrError> {
Ok(f64::from_bits(self.read_u64()?))
}
pub fn read_seq_len(&mut self) -> Result<u32, CdrError> {
self.read_u32()
}
pub fn check_seq_count(&self, count: u32, min_element_bytes: usize) -> Result<usize, CdrError> {
let n = count as usize;
if min_element_bytes > 0 && n > self.remaining() / min_element_bytes {
return Err(CdrError::BufferTooShort {
need: self.pos + n * min_element_bytes,
have: self.buf.len(),
});
}
Ok(n)
}
pub fn read_string(&mut self) -> Result<&'a str, CdrError> {
let len = self.read_u32()? as usize;
if len == 0 {
return Ok("");
}
self.ensure(len)?;
let bytes = &self.buf[self.pos..self.pos + len];
self.pos += len;
if bytes[len - 1] != 0 {
return Err(CdrError::MissingNul);
}
let s = std::str::from_utf8(&bytes[..len - 1]).map_err(|_| CdrError::InvalidUtf8)?;
Ok(s)
}
pub fn read_bytes(&mut self) -> Result<&'a [u8], CdrError> {
let len = self.read_u32()? as usize;
self.ensure(len)?;
let bytes = &self.buf[self.pos..self.pos + len];
self.pos += len;
Ok(bytes)
}
fn skip_typed_seq(&mut self, count: usize, elem_size: usize) -> Result<(), CdrError> {
if count > 0 {
self.align(elem_size);
let byte_len = count * elem_size;
self.ensure(byte_len)?;
self.pos += byte_len;
}
Ok(())
}
pub fn skip_seq_2(&mut self, count: usize) -> Result<(), CdrError> {
self.skip_typed_seq(count, 2)
}
pub fn skip_seq_4(&mut self, count: usize) -> Result<(), CdrError> {
self.skip_typed_seq(count, 4)
}
pub fn skip_seq_8(&mut self, count: usize) -> Result<(), CdrError> {
self.skip_typed_seq(count, 8)
}
pub fn read_raw(&mut self, count: usize) -> Result<&'a [u8], CdrError> {
self.ensure(count)?;
let bytes = &self.buf[self.pos..self.pos + count];
self.pos += count;
Ok(bytes)
}
}
pub struct CdrWriter<'a> {
buf: &'a mut [u8],
pos: usize,
err: Option<CdrError>,
}
impl<'a> CdrWriter<'a> {
pub fn new(buf: &'a mut [u8]) -> Result<Self, CdrError> {
if buf.len() < CDR_HEADER_SIZE {
return Err(CdrError::BufferTooShort {
need: CDR_HEADER_SIZE,
have: buf.len(),
});
}
buf[0..4].copy_from_slice(&CDR_LE_HEADER);
Ok(CdrWriter {
buf,
pos: CDR_HEADER_SIZE,
err: None,
})
}
#[inline(always)]
pub fn offset(&self) -> usize {
self.pos
}
#[inline(always)]
pub fn align(&mut self, n: usize) {
if self.err.is_some() {
return;
}
let aligned = CDR_HEADER_SIZE + align(self.pos - CDR_HEADER_SIZE, n);
if aligned > self.buf.len() {
self.err = Some(CdrError::BufferTooShort {
need: aligned,
have: self.buf.len(),
});
return;
}
for i in self.pos..aligned {
self.buf[i] = 0;
}
self.pos = aligned;
}
#[inline(always)]
fn check(&mut self, need: usize) -> bool {
if self.err.is_some() {
return false;
}
if self.pos + need > self.buf.len() {
self.err = Some(CdrError::BufferTooShort {
need: self.pos + need,
have: self.buf.len(),
});
return false;
}
true
}
pub fn finish(self) -> Result<(), CdrError> {
match self.err {
Some(e) => Err(e),
None => Ok(()),
}
}
pub fn write_u8(&mut self, v: u8) {
if !self.check(1) {
return;
}
self.buf[self.pos] = v;
self.pos += 1;
}
pub fn write_i8(&mut self, v: i8) {
self.write_u8(v as u8);
}
pub fn write_bool(&mut self, v: bool) {
self.write_u8(v as u8);
}
pub fn write_u16(&mut self, v: u16) {
self.align(2);
if !self.check(2) {
return;
}
self.buf[self.pos..self.pos + 2].copy_from_slice(&v.to_le_bytes());
self.pos += 2;
}
pub fn write_i16(&mut self, v: i16) {
self.write_u16(v as u16);
}
pub fn write_u32(&mut self, v: u32) {
self.align(4);
if !self.check(4) {
return;
}
self.buf[self.pos..self.pos + 4].copy_from_slice(&v.to_le_bytes());
self.pos += 4;
}
pub fn write_i32(&mut self, v: i32) {
self.write_u32(v as u32);
}
pub fn write_u64(&mut self, v: u64) {
self.align(8);
if !self.check(8) {
return;
}
self.buf[self.pos..self.pos + 8].copy_from_slice(&v.to_le_bytes());
self.pos += 8;
}
pub fn write_i64(&mut self, v: i64) {
self.write_u64(v as u64);
}
pub fn write_f32(&mut self, v: f32) {
self.write_u32(v.to_bits());
}
pub fn write_f64(&mut self, v: f64) {
self.write_u64(v.to_bits());
}
pub fn write_string(&mut self, s: &str) {
let len = s.len() + 1; self.write_u32(len as u32);
if !self.check(s.len() + 1) {
return;
}
self.buf[self.pos..self.pos + s.len()].copy_from_slice(s.as_bytes());
self.pos += s.len();
self.buf[self.pos] = 0; self.pos += 1;
}
pub fn write_bytes(&mut self, data: &[u8]) {
self.write_u32(data.len() as u32);
if !self.check(data.len()) {
return;
}
self.buf[self.pos..self.pos + data.len()].copy_from_slice(data);
self.pos += data.len();
}
pub fn write_raw(&mut self, data: &[u8]) {
if !self.check(data.len()) {
return;
}
self.buf[self.pos..self.pos + data.len()].copy_from_slice(data);
self.pos += data.len();
}
fn write_typed_slice(&mut self, data: &[u8], elem_size: usize) {
if !data.is_empty() {
self.align(elem_size);
if !self.check(data.len()) {
return;
}
self.buf[self.pos..self.pos + data.len()].copy_from_slice(data);
self.pos += data.len();
}
}
pub fn write_slice_u16(&mut self, data: &[u16]) {
let bytes =
unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 2) };
self.write_typed_slice(bytes, 2);
}
pub fn write_slice_i16(&mut self, data: &[i16]) {
let bytes =
unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 2) };
self.write_typed_slice(bytes, 2);
}
pub fn write_slice_u32(&mut self, data: &[u32]) {
let bytes =
unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) };
self.write_typed_slice(bytes, 4);
}
pub fn write_slice_f32(&mut self, data: &[f32]) {
let bytes =
unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) };
self.write_typed_slice(bytes, 4);
}
pub fn write_slice_f64(&mut self, data: &[f64]) {
let bytes =
unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 8) };
self.write_typed_slice(bytes, 8);
}
}
pub struct CdrSizer {
pos: usize,
}
impl Default for CdrSizer {
fn default() -> Self {
Self::new()
}
}
impl CdrSizer {
pub fn new() -> Self {
CdrSizer {
pos: CDR_HEADER_SIZE,
}
}
#[inline(always)]
pub fn offset(&self) -> usize {
self.pos
}
#[inline(always)]
pub fn size(&self) -> usize {
self.pos
}
#[inline(always)]
pub fn align(&mut self, n: usize) {
self.pos = CDR_HEADER_SIZE + align(self.pos - CDR_HEADER_SIZE, n);
}
pub fn size_u8(&mut self) {
self.pos += 1;
}
pub fn size_i8(&mut self) {
self.pos += 1;
}
pub fn size_bool(&mut self) {
self.pos += 1;
}
pub fn size_u16(&mut self) {
self.align(2);
self.pos += 2;
}
pub fn size_i16(&mut self) {
self.size_u16();
}
pub fn size_u32(&mut self) {
self.align(4);
self.pos += 4;
}
pub fn size_i32(&mut self) {
self.size_u32();
}
pub fn size_u64(&mut self) {
self.align(8);
self.pos += 8;
}
pub fn size_i64(&mut self) {
self.size_u64();
}
pub fn size_f32(&mut self) {
self.size_u32();
}
pub fn size_f64(&mut self) {
self.size_u64();
}
pub fn size_string(&mut self, s: &str) {
self.size_u32(); self.pos += s.len() + 1; }
pub fn size_bytes(&mut self, len: usize) {
self.size_u32(); self.pos += len;
}
pub fn size_raw(&mut self, len: usize) {
self.pos += len;
}
fn size_typed_seq(&mut self, count: usize, elem_size: usize) {
if count > 0 {
self.align(elem_size);
self.pos += count * elem_size;
}
}
pub fn size_seq_2(&mut self, count: usize) {
self.size_typed_seq(count, 2);
}
pub fn size_seq_4(&mut self, count: usize) {
self.size_typed_seq(count, 4);
}
pub fn size_seq_8(&mut self, count: usize) {
self.size_typed_seq(count, 8);
}
}
pub trait CdrFixed: Copy + Sized {
const CDR_SIZE: usize;
fn read_cdr(cursor: &mut CdrCursor<'_>) -> Result<Self, CdrError>;
fn write_cdr(&self, writer: &mut CdrWriter<'_>);
fn size_cdr(sizer: &mut CdrSizer);
}
#[inline(always)]
pub(crate) fn rd_u8(b: &[u8], pos: usize) -> u8 {
b[pos]
}
#[inline(always)]
pub(crate) fn rd_bool(b: &[u8], pos: usize) -> bool {
b[pos] != 0
}
#[inline(always)]
pub(crate) fn rd_u32(b: &[u8], pos: usize) -> u32 {
u32::from_le_bytes(
b[pos..pos + 4]
.try_into()
.expect("slice is exactly 4 bytes"),
)
}
#[inline(always)]
pub(crate) fn rd_i32(b: &[u8], pos: usize) -> i32 {
rd_u32(b, pos) as i32
}
#[inline(always)]
pub(crate) fn rd_u64(b: &[u8], pos: usize) -> u64 {
u64::from_le_bytes(
b[pos..pos + 8]
.try_into()
.expect("slice is exactly 8 bytes"),
)
}
#[inline(always)]
pub(crate) fn rd_f32(b: &[u8], pos: usize) -> f32 {
f32::from_bits(rd_u32(b, pos))
}
#[inline(always)]
pub(crate) fn rd_f64(b: &[u8], pos: usize) -> f64 {
f64::from_bits(rd_u64(b, pos))
}
#[inline(always)]
pub(crate) fn wr_u32(b: &mut [u8], pos: usize, v: u32) -> Result<(), CdrError> {
if pos + 4 > b.len() {
return Err(CdrError::BufferTooShort {
need: pos + 4,
have: b.len(),
});
}
b[pos..pos + 4].copy_from_slice(&v.to_le_bytes());
Ok(())
}
#[inline(always)]
pub(crate) fn wr_i32(b: &mut [u8], pos: usize, v: i32) -> Result<(), CdrError> {
wr_u32(b, pos, v as u32)
}
#[inline(always)]
pub(crate) fn wr_u8(b: &mut [u8], pos: usize, v: u8) -> Result<(), CdrError> {
if pos >= b.len() {
return Err(CdrError::BufferTooShort {
need: pos + 1,
have: b.len(),
});
}
b[pos] = v;
Ok(())
}
#[inline(always)]
pub(crate) fn wr_u16(b: &mut [u8], pos: usize, v: u16) -> Result<(), CdrError> {
if pos + 2 > b.len() {
return Err(CdrError::BufferTooShort {
need: pos + 2,
have: b.len(),
});
}
b[pos..pos + 2].copy_from_slice(&v.to_le_bytes());
Ok(())
}
#[inline(always)]
pub(crate) fn wr_i16(b: &mut [u8], pos: usize, v: i16) -> Result<(), CdrError> {
wr_u16(b, pos, v as u16)
}
#[inline(always)]
pub(crate) fn wr_u64(b: &mut [u8], pos: usize, v: u64) -> Result<(), CdrError> {
if pos + 8 > b.len() {
return Err(CdrError::BufferTooShort {
need: pos + 8,
have: b.len(),
});
}
b[pos..pos + 8].copy_from_slice(&v.to_le_bytes());
Ok(())
}
#[inline(always)]
pub(crate) fn wr_i8(b: &mut [u8], pos: usize, v: i8) -> Result<(), CdrError> {
wr_u8(b, pos, v as u8)
}
#[inline(always)]
pub(crate) fn wr_f32(b: &mut [u8], pos: usize, v: f32) -> Result<(), CdrError> {
wr_u32(b, pos, v.to_bits())
}
#[inline(always)]
pub(crate) fn wr_f64(b: &mut [u8], pos: usize, v: f64) -> Result<(), CdrError> {
if pos + 8 > b.len() {
return Err(CdrError::BufferTooShort {
need: pos + 8,
have: b.len(),
});
}
b[pos..pos + 8].copy_from_slice(&v.to_le_bytes());
Ok(())
}
#[inline(always)]
pub(crate) fn wr_bool(b: &mut [u8], pos: usize, v: bool) -> Result<(), CdrError> {
wr_u8(b, pos, v as u8)
}
#[inline]
pub(crate) fn rd_string(b: &[u8], pos: usize) -> (&str, usize) {
let p = align(pos, 4);
let len = rd_u32(b, p) as usize;
if len <= 1 {
("", p + 4 + len)
} else {
let start = p + 4;
let s = std::str::from_utf8(&b[start..start + len - 1]).unwrap_or("");
(s, start + len)
}
}
#[inline]
pub(crate) fn rd_bytes(b: &[u8], pos: usize) -> (&[u8], usize) {
let p = align(pos, 4);
let len = rd_u32(b, p) as usize;
let start = p + 4;
(&b[start..start + len], start + len)
}
#[inline(always)]
pub(crate) fn rd_time(b: &[u8], pos: usize) -> crate::builtin_interfaces::Time {
crate::builtin_interfaces::Time {
sec: rd_i32(b, pos),
nanosec: rd_u32(b, pos + 4),
}
}
#[inline(always)]
pub(crate) fn rd_duration(b: &[u8], pos: usize) -> crate::builtin_interfaces::Duration {
crate::builtin_interfaces::Duration {
sec: rd_i32(b, pos),
nanosec: rd_u32(b, pos + 4),
}
}
#[inline(always)]
pub(crate) fn rd_slice_u16(b: &[u8], pos: usize, count: usize) -> &[u16] {
let ptr = b[pos..].as_ptr();
debug_assert!(
(ptr as usize).is_multiple_of(std::mem::align_of::<u16>()),
"rd_slice_u16: misaligned pointer"
);
unsafe { std::slice::from_raw_parts(ptr as *const u16, count) }
}
#[inline(always)]
pub(crate) fn rd_slice_i16(b: &[u8], pos: usize, count: usize) -> &[i16] {
let ptr = b[pos..].as_ptr();
debug_assert!(
(ptr as usize).is_multiple_of(std::mem::align_of::<i16>()),
"rd_slice_i16: misaligned pointer"
);
unsafe { std::slice::from_raw_parts(ptr as *const i16, count) }
}
#[inline(always)]
pub(crate) fn rd_slice_u32(b: &[u8], pos: usize, count: usize) -> &[u32] {
let ptr = b[pos..].as_ptr();
debug_assert!(
(ptr as usize).is_multiple_of(std::mem::align_of::<u32>()),
"rd_slice_u32: misaligned pointer"
);
unsafe { std::slice::from_raw_parts(ptr as *const u32, count) }
}
#[inline(always)]
pub(crate) fn rd_slice_f32(b: &[u8], pos: usize, count: usize) -> &[f32] {
let ptr = b[pos..].as_ptr();
debug_assert!(
(ptr as usize).is_multiple_of(std::mem::align_of::<f32>()),
"rd_slice_f32: misaligned pointer"
);
unsafe { std::slice::from_raw_parts(ptr as *const f32, count) }
}
pub fn encode_fixed<T: CdrFixed>(val: &T) -> Result<Vec<u8>, CdrError> {
let mut sizer = CdrSizer::new();
T::size_cdr(&mut sizer);
let mut buf = vec![0u8; sizer.size()];
let mut writer = CdrWriter::new(&mut buf)?;
val.write_cdr(&mut writer);
writer.finish()?;
Ok(buf)
}
pub fn decode_fixed<T: CdrFixed>(buf: &[u8]) -> Result<T, CdrError> {
let mut cursor = CdrCursor::new(buf)?;
T::read_cdr(&mut cursor)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn align_basic() {
assert_eq!(align(0, 4), 0);
assert_eq!(align(1, 4), 4);
assert_eq!(align(4, 4), 4);
assert_eq!(align(5, 4), 8);
assert_eq!(align(0, 8), 0);
assert_eq!(align(1, 8), 8);
assert_eq!(align(7, 8), 8);
assert_eq!(align(8, 8), 8);
assert_eq!(align(0, 2), 0);
assert_eq!(align(1, 2), 2);
assert_eq!(align(2, 2), 2);
assert_eq!(align(3, 2), 4);
}
#[test]
fn cursor_invalid_header() {
let buf = [0x00, 0x00, 0x00, 0x00, 0x42];
assert!(CdrCursor::new(&buf).is_err());
}
#[test]
fn cursor_too_short() {
let buf = [0x00, 0x01, 0x00];
assert!(CdrCursor::new(&buf).is_err());
}
#[test]
fn cursor_read_primitives() {
let mut buf = vec![0x00, 0x01, 0x00, 0x00]; buf.push(42u8); buf.push(0); buf.extend_from_slice(&1000u16.to_le_bytes()); buf.extend_from_slice(&123456u32.to_le_bytes()); buf.extend_from_slice(&9876543210u64.to_le_bytes());
let mut cursor = CdrCursor::new(&buf).unwrap();
assert_eq!(cursor.read_u8().unwrap(), 42);
assert_eq!(cursor.read_u16().unwrap(), 1000);
assert_eq!(cursor.read_u32().unwrap(), 123456);
assert_eq!(cursor.read_u64().unwrap(), 9876543210);
}
#[test]
fn cursor_read_i32_negative() {
let mut buf = vec![0x00, 0x01, 0x00, 0x00];
buf.extend_from_slice(&(-42i32).to_le_bytes());
let mut cursor = CdrCursor::new(&buf).unwrap();
assert_eq!(cursor.read_i32().unwrap(), -42);
}
#[test]
fn cursor_read_f32_f64() {
let mut buf = vec![0x00, 0x01, 0x00, 0x00]; buf.extend_from_slice(&std::f32::consts::PI.to_le_bytes()); buf.extend_from_slice(&[0; 4]); buf.extend_from_slice(&std::f64::consts::E.to_le_bytes());
let mut cursor = CdrCursor::new(&buf).unwrap();
assert!((cursor.read_f32().unwrap() - std::f32::consts::PI).abs() < 1e-7);
assert!((cursor.read_f64().unwrap() - std::f64::consts::E).abs() < 1e-15);
}
#[test]
fn cursor_read_bool() {
let buf = vec![0x00, 0x01, 0x00, 0x00, 0, 1, 2];
let mut cursor = CdrCursor::new(&buf).unwrap();
assert!(!cursor.read_bool().unwrap());
assert!(cursor.read_bool().unwrap());
assert!(cursor.read_bool().is_err()); }
#[test]
fn cursor_read_string() {
let mut buf = vec![0x00, 0x01, 0x00, 0x00];
buf.extend_from_slice(&6u32.to_le_bytes());
buf.extend_from_slice(b"hello\0");
let mut cursor = CdrCursor::new(&buf).unwrap();
assert_eq!(cursor.read_string().unwrap(), "hello");
}
#[test]
fn cursor_read_empty_string() {
let mut buf = vec![0x00, 0x01, 0x00, 0x00];
buf.extend_from_slice(&1u32.to_le_bytes());
buf.push(0);
let mut cursor = CdrCursor::new(&buf).unwrap();
assert_eq!(cursor.read_string().unwrap(), "");
}
#[test]
fn cursor_read_zero_len_string() {
let mut buf = vec![0x00, 0x01, 0x00, 0x00];
buf.extend_from_slice(&0u32.to_le_bytes());
let mut cursor = CdrCursor::new(&buf).unwrap();
assert_eq!(cursor.read_string().unwrap(), "");
}
#[test]
fn cursor_read_bytes() {
let mut buf = vec![0x00, 0x01, 0x00, 0x00];
buf.extend_from_slice(&3u32.to_le_bytes());
buf.extend_from_slice(&[10, 20, 30]);
let mut cursor = CdrCursor::new(&buf).unwrap();
assert_eq!(cursor.read_bytes().unwrap(), &[10, 20, 30]);
}
#[test]
fn writer_primitives_roundtrip() {
let mut buf = [0u8; 64];
{
let mut w = CdrWriter::new(&mut buf).unwrap();
w.write_u8(42);
w.write_u16(1000);
w.write_u32(123456);
w.write_u64(9876543210);
w.write_i32(-42);
w.write_f32(std::f32::consts::PI);
w.write_f64(std::f64::consts::E);
w.write_bool(true);
w.write_bool(false);
}
let mut cursor = CdrCursor::new(&buf).unwrap();
assert_eq!(cursor.read_u8().unwrap(), 42);
assert_eq!(cursor.read_u16().unwrap(), 1000);
assert_eq!(cursor.read_u32().unwrap(), 123456);
assert_eq!(cursor.read_u64().unwrap(), 9876543210);
assert_eq!(cursor.read_i32().unwrap(), -42);
assert!((cursor.read_f32().unwrap() - std::f32::consts::PI).abs() < 1e-7);
assert!((cursor.read_f64().unwrap() - std::f64::consts::E).abs() < 1e-15);
assert!(cursor.read_bool().unwrap());
assert!(!cursor.read_bool().unwrap());
}
#[test]
fn writer_string_roundtrip() {
let mut buf = [0u8; 64];
{
let mut w = CdrWriter::new(&mut buf).unwrap();
w.write_string("hello");
w.write_string("");
w.write_string("world");
}
let mut cursor = CdrCursor::new(&buf).unwrap();
assert_eq!(cursor.read_string().unwrap(), "hello");
assert_eq!(cursor.read_string().unwrap(), "");
assert_eq!(cursor.read_string().unwrap(), "world");
}
#[test]
fn writer_bytes_roundtrip() {
let mut buf = [0u8; 32];
{
let mut w = CdrWriter::new(&mut buf).unwrap();
w.write_bytes(&[1, 2, 3, 4, 5]);
}
let mut cursor = CdrCursor::new(&buf).unwrap();
assert_eq!(cursor.read_bytes().unwrap(), &[1, 2, 3, 4, 5]);
}
#[test]
fn sizer_matches_writer() {
let test_string = "hello world";
let test_bytes = [1u8, 2, 3, 4, 5];
let mut sizer = CdrSizer::new();
sizer.size_u8();
sizer.size_u16();
sizer.size_u32();
sizer.size_u64();
sizer.size_i32();
sizer.size_f32();
sizer.size_f64();
sizer.size_bool();
sizer.size_string(test_string);
sizer.size_bytes(test_bytes.len());
let mut buf = vec![0u8; sizer.size()];
let pos = {
let mut w = CdrWriter::new(&mut buf).unwrap();
w.write_u8(1);
w.write_u16(2);
w.write_u32(3);
w.write_u64(4);
w.write_i32(-5);
w.write_f32(6.0);
w.write_f64(7.0);
w.write_bool(true);
w.write_string(test_string);
w.write_bytes(&test_bytes);
w.offset()
};
assert_eq!(sizer.size(), pos, "sizer and writer disagree on total size");
}
fn assert_roundtrip<T: CdrFixed + PartialEq + std::fmt::Debug>(val: &T, name: &str) {
let bytes = encode_fixed(val).unwrap();
let decoded = decode_fixed::<T>(&bytes).unwrap();
assert_eq!(*val, decoded, "{}: CdrFixed roundtrip failed", name);
}
#[test]
fn roundtrip_time() {
use crate::builtin_interfaces::Time;
assert_roundtrip(&Time::new(0, 0), "zero");
assert_roundtrip(&Time::new(42, 123456789), "typical");
assert_roundtrip(&Time::new(-100, 500_000_000), "negative");
assert_roundtrip(&Time::new(i32::MAX, 999_999_999), "max");
}
#[test]
fn roundtrip_duration() {
use crate::builtin_interfaces::Duration;
assert_roundtrip(&Duration { sec: 0, nanosec: 0 }, "zero");
assert_roundtrip(
&Duration {
sec: 5,
nanosec: 500_000_000,
},
"typical",
);
}
#[test]
fn roundtrip_vector3() {
use crate::geometry_msgs::Vector3;
assert_roundtrip(
&Vector3 {
x: 1.5,
y: -2.5,
z: 3.0,
},
"vec3",
);
}
#[test]
fn roundtrip_quaternion() {
use crate::geometry_msgs::Quaternion;
assert_roundtrip(
&Quaternion {
x: 0.0,
y: 0.0,
z: 0.707,
w: 0.707,
},
"quat",
);
}
#[test]
fn roundtrip_pose() {
use crate::geometry_msgs::{Point, Pose, Quaternion};
assert_roundtrip(
&Pose {
position: Point {
x: 1.0,
y: 2.0,
z: 3.0,
},
orientation: Quaternion {
x: 0.0,
y: 0.0,
z: 0.0,
w: 1.0,
},
},
"pose",
);
}
#[test]
fn roundtrip_color_rgba() {
use crate::std_msgs::ColorRGBA;
assert_roundtrip(
&ColorRGBA {
r: 1.0,
g: 0.5,
b: 0.0,
a: 1.0,
},
"color",
);
}
#[test]
fn roundtrip_service_header() {
use crate::service::ServiceHeader;
assert_roundtrip(
&ServiceHeader {
guid: 12345678901234567i64,
seq: 42,
},
"svc_header",
);
}
#[test]
fn roundtrip_clock() {
use crate::builtin_interfaces::Time;
use crate::rosgraph_msgs::Clock;
assert_roundtrip(
&Clock {
clock: Time::new(100, 500_000_000),
},
"clock",
);
}
#[test]
fn roundtrip_nav_sat_status() {
use crate::sensor_msgs::NavSatStatus;
assert_roundtrip(
&NavSatStatus {
status: 0,
service: 1,
},
"nav_sat_status",
);
}
#[test]
fn roundtrip_region_of_interest() {
use crate::sensor_msgs::RegionOfInterest;
assert_roundtrip(
&RegionOfInterest {
x_offset: 10,
y_offset: 20,
height: 100,
width: 200,
do_rectify: true,
},
"roi",
);
}
#[test]
fn roundtrip_date() {
use crate::edgefirst_msgs::Date;
assert_roundtrip(
&Date {
year: 2025,
month: 6,
day: 15,
},
"date",
);
}
#[test]
fn roundtrip_inertia() {
use crate::geometry_msgs::{Inertia, Vector3};
assert_roundtrip(
&Inertia {
m: 10.0,
com: Vector3 {
x: 0.0,
y: 0.0,
z: 0.0,
},
ixx: 1.0,
ixy: 0.0,
ixz: 0.0,
iyy: 1.0,
iyz: 0.0,
izz: 1.0,
},
"inertia",
);
}
#[test]
fn roundtrip_foxglove_circle() {
use crate::builtin_interfaces::Time;
use crate::foxglove_msgs::{FoxgloveCircleAnnotations, FoxgloveColor, FoxglovePoint2};
assert_roundtrip(
&FoxgloveCircleAnnotations {
timestamp: Time::new(100, 0),
position: FoxglovePoint2 { x: 320.0, y: 240.0 },
diameter: 50.0,
thickness: 2.0,
fill_color: FoxgloveColor {
r: 1.0,
g: 0.0,
b: 0.0,
a: 0.5,
},
outline_color: FoxgloveColor {
r: 0.0,
g: 1.0,
b: 0.0,
a: 1.0,
},
},
"foxglove_circle",
);
}
}