use byteorder::{BigEndian, ByteOrder, LittleEndian};
use crate::error::Result;
use crate::primitives::CdrReader;
pub trait CdrEndian: ByteOrder {
const IS_LITTLE: bool;
}
impl CdrEndian for LittleEndian {
const IS_LITTLE: bool = true;
}
impl CdrEndian for BigEndian {
const IS_LITTLE: bool = false;
}
pub trait CdrPod: bytemuck::Pod {
const CDR_ALIGN: usize;
fn read_one<BO: ByteOrder>(reader: &mut CdrReader<'_, BO>) -> Result<Self>;
}
macro_rules! impl_cdr_pod {
($ty:ty, $align:expr, $read:ident) => {
impl CdrPod for $ty {
const CDR_ALIGN: usize = $align;
#[inline]
fn read_one<BO: ByteOrder>(reader: &mut CdrReader<'_, BO>) -> Result<Self> {
reader.$read()
}
}
};
}
impl_cdr_pod!(i8, 1, read_i8);
impl_cdr_pod!(u8, 1, read_u8);
impl_cdr_pod!(i16, 2, read_i16);
impl_cdr_pod!(u16, 2, read_u16);
impl_cdr_pod!(i32, 4, read_i32);
impl_cdr_pod!(u32, 4, read_u32);
impl_cdr_pod!(i64, 8, read_i64);
impl_cdr_pod!(u64, 8, read_u64);
impl_cdr_pod!(f32, 4, read_f32);
impl_cdr_pod!(f64, 8, read_f64);
impl<BO: CdrEndian> CdrReader<'_, BO> {
#[inline]
pub fn read_numeric_vec<T: CdrPod>(&mut self, count: usize) -> Result<Vec<T>> {
if count == 0 {
return Ok(Vec::new());
}
self.align(T::CDR_ALIGN)?;
#[cfg(target_endian = "little")]
{
if BO::IS_LITTLE {
let byte_count = count
.checked_mul(core::mem::size_of::<T>())
.ok_or(crate::error::Error::UnexpectedEof)?;
let bytes = self.read_bytes(byte_count)?;
return Ok(bytemuck::pod_collect_to_vec(bytes));
}
}
let mut out = Vec::with_capacity(count);
for _ in 0..count {
out.push(T::read_one(self)?);
}
Ok(out)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::primitives::{CdrReader, CdrWriter};
use proptest::prelude::*;
fn check_numeric_roundtrip<T, BO>(values: &[T], write: impl Fn(&mut CdrWriter<'_, BO>, T))
where
T: CdrPod + PartialEq + std::fmt::Debug + Copy,
BO: CdrEndian,
{
let mut buf = Vec::new();
{
let mut w = CdrWriter::<BO>::new(&mut buf);
for &v in values {
write(&mut w, v);
}
}
let mut bulk_reader = CdrReader::<BO>::new(&buf);
let bulk = bulk_reader
.read_numeric_vec::<T>(values.len())
.expect("bulk decode failed");
assert_eq!(bulk, values, "bulk decode mismatch");
let mut elem_reader = CdrReader::<BO>::new(&buf);
let mut elem = Vec::with_capacity(values.len());
for _ in 0..values.len() {
elem.push(T::read_one(&mut elem_reader).expect("element decode failed"));
}
assert_eq!(elem, values, "element decode mismatch");
assert_eq!(
bulk_reader.position(),
elem_reader.position(),
"consumed length differs"
);
}
#[test]
fn numeric_vec_f32_both_endian() {
let v = vec![0.0f32, 1.5, -2.25, f32::MIN, f32::MAX, 1e30];
check_numeric_roundtrip::<f32, LittleEndian>(&v, |w, x| w.write_f32(x));
check_numeric_roundtrip::<f32, BigEndian>(&v, |w, x| w.write_f32(x));
}
#[test]
fn numeric_vec_u32_both_endian() {
let v = vec![0u32, 1, 2, 100, u32::MAX, 0xDEAD_BEEF];
check_numeric_roundtrip::<u32, LittleEndian>(&v, |w, x| w.write_u32(x));
check_numeric_roundtrip::<u32, BigEndian>(&v, |w, x| w.write_u32(x));
}
#[test]
fn numeric_vec_i16_both_endian() {
let v = vec![0i16, -1, 1, i16::MIN, i16::MAX, 12345];
check_numeric_roundtrip::<i16, LittleEndian>(&v, |w, x| w.write_i16(x));
check_numeric_roundtrip::<i16, BigEndian>(&v, |w, x| w.write_i16(x));
}
#[test]
fn numeric_vec_empty_is_ok() {
let buf: Vec<u8> = Vec::new();
let mut reader = CdrReader::<LittleEndian>::new(&buf);
let out = reader.read_numeric_vec::<f64>(0).unwrap();
assert!(out.is_empty());
}
#[test]
fn numeric_vec_respects_alignment() {
let values = vec![10u32, 20, 30];
let mut buf = Vec::new();
{
let mut w = CdrWriter::<LittleEndian>::new(&mut buf);
w.write_u8(0xAB);
for &v in &values {
w.write_u32(v);
}
}
assert_eq!(buf.len(), 16);
let mut reader = CdrReader::<LittleEndian>::new(&buf);
assert_eq!(reader.read_u8().unwrap(), 0xAB);
let out = reader.read_numeric_vec::<u32>(values.len()).unwrap();
assert_eq!(out, values);
assert_eq!(reader.remaining(), 0);
}
#[test]
fn cross_endian_values_match() {
let v = vec![1.0f64, -2.0, 3.5, f64::MIN, f64::MAX];
let mut le = Vec::new();
{
let mut w = CdrWriter::<LittleEndian>::new(&mut le);
for &x in &v {
w.write_f64(x);
}
}
let mut be = Vec::new();
{
let mut w = CdrWriter::<BigEndian>::new(&mut be);
for &x in &v {
w.write_f64(x);
}
}
let le_out = CdrReader::<LittleEndian>::new(&le)
.read_numeric_vec::<f64>(v.len())
.unwrap();
let be_out = CdrReader::<BigEndian>::new(&be)
.read_numeric_vec::<f64>(v.len())
.unwrap();
assert_eq!(le_out, v);
assert_eq!(be_out, v);
assert_eq!(le_out, be_out);
}
proptest! {
#[test]
fn prop_numeric_vec_f32_le(values in proptest::collection::vec(any::<u32>().prop_map(f32::from_bits), 0..256)) {
let mut buf = Vec::new();
{
let mut w = CdrWriter::<LittleEndian>::new(&mut buf);
for &x in &values { w.write_f32(x); }
}
let out = CdrReader::<LittleEndian>::new(&buf).read_numeric_vec::<f32>(values.len()).unwrap();
let a: Vec<u32> = out.iter().map(|x| x.to_bits()).collect();
let b: Vec<u32> = values.iter().map(|x| x.to_bits()).collect();
prop_assert_eq!(a, b);
}
#[test]
fn prop_numeric_vec_u64_be(values in proptest::collection::vec(any::<u64>(), 0..256)) {
let mut buf = Vec::new();
{
let mut w = CdrWriter::<BigEndian>::new(&mut buf);
for &x in &values { w.write_u64(x); }
}
let out = CdrReader::<BigEndian>::new(&buf).read_numeric_vec::<u64>(values.len()).unwrap();
prop_assert_eq!(out, values);
}
}
}