use alloc::vec::Vec;
const ENCAPSULATION: [u8; 4] = [0x00, 0x01, 0x00, 0x00];
const ENCAPSULATION_LEN: usize = 4;
#[derive(Clone, Debug, Default)]
pub struct CdrWriter {
buf: Vec<u8>,
}
impl CdrWriter {
pub fn new() -> Self {
let mut buf = Vec::new();
buf.extend_from_slice(&ENCAPSULATION);
Self { buf }
}
fn align(&mut self, alignment: usize) {
let offset = self.buf.len() - ENCAPSULATION_LEN;
let padding = (alignment - (offset % alignment)) % alignment;
self.buf.resize(self.buf.len() + padding, 0);
}
pub fn write_i32(&mut self, value: i32) {
self.align(4);
self.buf.extend_from_slice(&value.to_le_bytes());
}
pub fn write_u32(&mut self, value: u32) {
self.align(4);
self.buf.extend_from_slice(&value.to_le_bytes());
}
pub fn write_f32(&mut self, value: f32) {
self.align(4);
self.buf.extend_from_slice(&value.to_le_bytes());
}
pub fn write_f64(&mut self, value: f64) {
self.align(8);
self.buf.extend_from_slice(&value.to_le_bytes());
}
pub fn into_bytes(self) -> Vec<u8> {
self.buf
}
}
pub struct CdrReader<'a> {
body: &'a [u8],
pos: usize,
}
impl<'a> CdrReader<'a> {
pub fn new(data: &'a [u8]) -> Option<Self> {
if data.len() < ENCAPSULATION_LEN || data[0] != 0x00 || data[1] != 0x01 {
return None;
}
Some(Self {
body: &data[ENCAPSULATION_LEN..],
pos: 0,
})
}
fn align(&mut self, alignment: usize) {
let padding = (alignment - (self.pos % alignment)) % alignment;
self.pos += padding;
}
fn take<const N: usize>(&mut self, alignment: usize) -> Option<[u8; N]> {
self.align(alignment);
let end = self.pos.checked_add(N)?;
if end > self.body.len() {
return None;
}
let bytes: [u8; N] = self.body[self.pos..end].try_into().ok()?;
self.pos = end;
Some(bytes)
}
pub fn read_i32(&mut self) -> Option<i32> {
self.take::<4>(4).map(i32::from_le_bytes)
}
pub fn read_u32(&mut self) -> Option<u32> {
self.take::<4>(4).map(u32::from_le_bytes)
}
pub fn read_f32(&mut self) -> Option<f32> {
self.take::<4>(4).map(f32::from_le_bytes)
}
pub fn read_f64(&mut self) -> Option<f64> {
self.take::<8>(8).map(f64::from_le_bytes)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Vector3 {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl Vector3 {
pub fn new(x: f64, y: f64, z: f64) -> Self {
Self { x, y, z }
}
pub fn encode(&self, writer: &mut CdrWriter) {
writer.write_f64(self.x);
writer.write_f64(self.y);
writer.write_f64(self.z);
}
pub fn decode(reader: &mut CdrReader) -> Option<Self> {
Some(Self {
x: reader.read_f64()?,
y: reader.read_f64()?,
z: reader.read_f64()?,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Twist {
pub linear: Vector3,
pub angular: Vector3,
}
impl Twist {
pub fn to_cdr(&self) -> Vec<u8> {
let mut writer = CdrWriter::new();
self.linear.encode(&mut writer);
self.angular.encode(&mut writer);
writer.into_bytes()
}
pub fn from_cdr(data: &[u8]) -> Option<Self> {
let mut reader = CdrReader::new(data)?;
let linear = Vector3::decode(&mut reader)?;
let angular = Vector3::decode(&mut reader)?;
Some(Self { linear, angular })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn alignment_pads_a_double_after_an_int() {
let mut w = CdrWriter::new();
w.write_u32(0x0102_0304);
w.write_f64(1.0);
let bytes = w.into_bytes();
assert_eq!(
bytes,
[
0x00, 0x01, 0x00, 0x00, 0x04, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, ]
);
}
#[test]
fn twist_matches_a_hand_computed_cdr_vector() {
let cmd = Twist {
linear: Vector3::new(1.0, 0.0, 0.0),
angular: Vector3::new(0.0, 0.0, 0.5),
};
let bytes = cmd.to_cdr();
let mut expected = Vec::new();
expected.extend_from_slice(&[0x00, 0x01, 0x00, 0x00]); expected.extend_from_slice(&1.0_f64.to_le_bytes()); expected.extend_from_slice(&0.0_f64.to_le_bytes()); expected.extend_from_slice(&0.0_f64.to_le_bytes()); expected.extend_from_slice(&0.0_f64.to_le_bytes()); expected.extend_from_slice(&0.0_f64.to_le_bytes()); expected.extend_from_slice(&0.5_f64.to_le_bytes()); assert_eq!(bytes, expected);
assert_eq!(bytes.len(), 4 + 48);
}
#[test]
fn twist_round_trips_through_cdr() {
let cmd = Twist {
linear: Vector3::new(0.5, -1.5, 0.0),
angular: Vector3::new(0.0, 0.0, 0.25),
};
assert_eq!(Twist::from_cdr(&cmd.to_cdr()), Some(cmd));
}
#[test]
fn primitives_round_trip_with_alignment() {
let mut w = CdrWriter::new();
w.write_i32(-7);
w.write_f64(2.5);
w.write_f32(1.25);
w.write_u32(42);
let bytes = w.into_bytes();
let mut r = CdrReader::new(&bytes).unwrap();
assert_eq!(r.read_i32(), Some(-7));
assert_eq!(r.read_f64(), Some(2.5));
assert_eq!(r.read_f32(), Some(1.25));
assert_eq!(r.read_u32(), Some(42));
}
#[test]
fn a_short_or_wrong_endian_buffer_is_rejected() {
assert!(CdrReader::new(&[0x00]).is_none()); assert!(CdrReader::new(&[0x00, 0x00, 0x00, 0x00]).is_none()); assert!(Twist::from_cdr(&[0x00, 0x01, 0x00, 0x00]).is_none()); }
}