use zerodds_cdr::{BufferReader, BufferWriter, Endianness};
use super::shape::{ScalarKind, TypeShape};
use crate::error::RoutingError;
#[derive(Debug, Clone, PartialEq)]
pub enum DynValue {
Bool(bool),
U8(u8),
I8(i8),
U16(u16),
I16(i16),
U32(u32),
I32(i32),
U64(u64),
I64(i64),
F32(f32),
F64(f64),
Str(String),
}
fn dec_err(what: &str) -> RoutingError {
RoutingError::Transform {
route: "<decode>".into(),
reason: format!("cdr decode: {what}"),
}
}
fn enc_err(what: &str) -> RoutingError {
RoutingError::Transform {
route: "<encode>".into(),
reason: format!("cdr encode: {what}"),
}
}
fn align_for(kind: ScalarKind, xcdr2: bool) -> usize {
if xcdr2 {
kind.xcdr2_align()
} else {
kind.natural_align()
}
}
pub fn decode(
shape: &TypeShape,
body: &[u8],
representation: u8,
) -> crate::error::Result<Vec<DynValue>> {
let xcdr2 = representation != 0;
let mut r = BufferReader::new(body, Endianness::Little);
if xcdr2 {
r = r.xcdr2();
if shape.appendable {
r.read_u32().map_err(|_| dec_err("dheader"))?;
}
}
let mut out = Vec::with_capacity(shape.members.len());
for m in &shape.members {
let a = align_for(m.kind, xcdr2);
r.align(a).map_err(|_| dec_err("align"))?;
let v = match m.kind {
ScalarKind::Bool => DynValue::Bool(r.read_u8().map_err(|_| dec_err("bool"))? != 0),
ScalarKind::U8 => DynValue::U8(r.read_u8().map_err(|_| dec_err("u8"))?),
ScalarKind::I8 => DynValue::I8(r.read_u8().map_err(|_| dec_err("i8"))? as i8),
ScalarKind::U16 => DynValue::U16(r.read_u16().map_err(|_| dec_err("u16"))?),
ScalarKind::I16 => DynValue::I16(r.read_u16().map_err(|_| dec_err("i16"))? as i16),
ScalarKind::U32 => DynValue::U32(r.read_u32().map_err(|_| dec_err("u32"))?),
ScalarKind::I32 => DynValue::I32(r.read_u32().map_err(|_| dec_err("i32"))? as i32),
ScalarKind::U64 => DynValue::U64(r.read_u64().map_err(|_| dec_err("u64"))?),
ScalarKind::I64 => DynValue::I64(r.read_u64().map_err(|_| dec_err("i64"))? as i64),
ScalarKind::F32 => {
DynValue::F32(f32::from_bits(r.read_u32().map_err(|_| dec_err("f32"))?))
}
ScalarKind::F64 => {
DynValue::F64(f64::from_bits(r.read_u64().map_err(|_| dec_err("f64"))?))
}
ScalarKind::String => DynValue::Str(r.read_string().map_err(|_| dec_err("string"))?),
};
out.push(v);
}
Ok(out)
}
pub fn encode(
shape: &TypeShape,
values: &[DynValue],
representation: u8,
) -> crate::error::Result<Vec<u8>> {
if values.len() != shape.members.len() {
return Err(enc_err("value/member count mismatch"));
}
let xcdr2 = representation != 0;
let mut w = if xcdr2 {
BufferWriter::new(Endianness::Little).xcdr2()
} else {
BufferWriter::new(Endianness::Little)
};
let dheader_at = if xcdr2 && shape.appendable {
w.align(4);
let at = w.position();
w.write_u32(0).map_err(|_| enc_err("dheader placeholder"))?;
Some(at)
} else {
None
};
for (m, v) in shape.members.iter().zip(values) {
let a = align_for(m.kind, xcdr2);
w.align(a);
match (m.kind, v) {
(ScalarKind::Bool, DynValue::Bool(b)) => {
w.write_u8(u8::from(*b)).map_err(|_| enc_err("bool"))?;
}
(ScalarKind::U8, DynValue::U8(x)) => w.write_u8(*x).map_err(|_| enc_err("u8"))?,
(ScalarKind::I8, DynValue::I8(x)) => {
w.write_u8(*x as u8).map_err(|_| enc_err("i8"))?;
}
(ScalarKind::U16, DynValue::U16(x)) => w.write_u16(*x).map_err(|_| enc_err("u16"))?,
(ScalarKind::I16, DynValue::I16(x)) => {
w.write_u16(*x as u16).map_err(|_| enc_err("i16"))?;
}
(ScalarKind::U32, DynValue::U32(x)) => w.write_u32(*x).map_err(|_| enc_err("u32"))?,
(ScalarKind::I32, DynValue::I32(x)) => {
w.write_u32(*x as u32).map_err(|_| enc_err("i32"))?;
}
(ScalarKind::U64, DynValue::U64(x)) => w.write_u64(*x).map_err(|_| enc_err("u64"))?,
(ScalarKind::I64, DynValue::I64(x)) => {
w.write_u64(*x as u64).map_err(|_| enc_err("i64"))?;
}
(ScalarKind::F32, DynValue::F32(x)) => {
w.write_u32(x.to_bits()).map_err(|_| enc_err("f32"))?;
}
(ScalarKind::F64, DynValue::F64(x)) => {
w.write_u64(x.to_bits()).map_err(|_| enc_err("f64"))?;
}
(ScalarKind::String, DynValue::Str(s)) => {
w.write_string(s).map_err(|_| enc_err("string"))?;
}
_ => return Err(enc_err("value/kind mismatch")),
}
}
let mut bytes = w.into_bytes();
if let Some(at) = dheader_at {
let size = (bytes.len() - at - 4) as u32;
bytes[at..at + 4].copy_from_slice(&size.to_le_bytes());
}
Ok(bytes)
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
use crate::transform::shape::Member;
fn shape_final() -> TypeShape {
TypeShape {
name: "T".into(),
members: vec![
Member {
name: "a".into(),
kind: ScalarKind::U32,
},
Member {
name: "b".into(),
kind: ScalarKind::String,
},
Member {
name: "c".into(),
kind: ScalarKind::F64,
},
],
appendable: false,
}
}
#[test]
fn roundtrip_xcdr1() {
let sh = shape_final();
let vals = vec![
DynValue::U32(0x1122_3344),
DynValue::Str("hi".into()),
DynValue::F64(3.5),
];
let body = encode(&sh, &vals, 0).unwrap();
let back = decode(&sh, &body, 0).unwrap();
assert_eq!(vals, back);
}
#[test]
fn roundtrip_xcdr2() {
let sh = shape_final();
let vals = vec![
DynValue::U32(7),
DynValue::Str("world".into()),
DynValue::F64(-1.25),
];
let body = encode(&sh, &vals, 2).unwrap();
let back = decode(&sh, &body, 2).unwrap();
assert_eq!(vals, back);
}
#[test]
fn roundtrip_xcdr2_appendable_dheader() {
let mut sh = shape_final();
sh.appendable = true;
let vals = vec![
DynValue::U32(42),
DynValue::Str("x".into()),
DynValue::F64(2.0),
];
let body = encode(&sh, &vals, 2).unwrap();
let dh = u32::from_le_bytes([body[0], body[1], body[2], body[3]]) as usize;
assert_eq!(dh, body.len() - 4);
let back = decode(&sh, &body, 2).unwrap();
assert_eq!(vals, back);
}
}