use crate::error::{Error, Result};
#[repr(u16)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RepresentationIdentifier {
CdrBE = 0x0000,
CdrLE = 0x0001,
PlCdrBE = 0x0002,
PlCdrLE = 0x0003,
Xcdr2BE = 0x0006,
Xcdr2LE = 0x0007,
DCdr2BE = 0x0008,
DCdr2LE = 0x0009,
PlXcdr2BE = 0x000a,
PlXcdr2LE = 0x000b,
}
impl RepresentationIdentifier {
pub const fn is_little_endian(&self) -> bool {
matches!(
self,
Self::CdrLE | Self::PlCdrLE | Self::Xcdr2LE | Self::DCdr2LE | Self::PlXcdr2LE
)
}
pub const fn is_big_endian(&self) -> bool {
!self.is_little_endian()
}
pub const fn is_plain_cdr(&self) -> bool {
matches!(self, Self::CdrBE | Self::CdrLE)
}
pub const fn is_supported(&self) -> bool {
self.is_plain_cdr()
}
pub const fn is_cdr_v1(&self) -> bool {
matches!(
self,
Self::CdrBE | Self::CdrLE | Self::PlCdrBE | Self::PlCdrLE
)
}
pub const fn is_cdr_v2(&self) -> bool {
matches!(
self,
Self::Xcdr2BE
| Self::Xcdr2LE
| Self::DCdr2BE
| Self::DCdr2LE
| Self::PlXcdr2BE
| Self::PlXcdr2LE
)
}
pub const fn is_parameter_list(&self) -> bool {
matches!(
self,
Self::PlCdrBE | Self::PlCdrLE | Self::PlXcdr2BE | Self::PlXcdr2LE
)
}
pub const fn to_bytes(&self) -> [u8; 2] {
let val = *self as u16;
[(val >> 8) as u8, val as u8]
}
pub fn from_bytes(bytes: [u8; 2]) -> Result<Self> {
let val = ((bytes[0] as u16) << 8) | (bytes[1] as u16);
match val {
0x0000 => Ok(Self::CdrBE),
0x0001 => Ok(Self::CdrLE),
0x0002 => Ok(Self::PlCdrBE),
0x0003 => Ok(Self::PlCdrLE),
0x0006 => Ok(Self::Xcdr2BE),
0x0007 => Ok(Self::Xcdr2LE),
0x0008 => Ok(Self::DCdr2BE),
0x0009 => Ok(Self::DCdr2LE),
0x000a => Ok(Self::PlXcdr2BE),
0x000b => Ok(Self::PlXcdr2LE),
_ => Err(Error::CdrError(format!(
"Unknown representation identifier: 0x{:04x}",
val
))),
}
}
}
impl Default for RepresentationIdentifier {
fn default() -> Self {
Self::CdrLE
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CdrEncapsulationHeader {
pub representation_id: RepresentationIdentifier,
pub options: u16,
}
impl CdrEncapsulationHeader {
pub const SIZE: usize = 4;
pub const fn new(representation_id: RepresentationIdentifier) -> Self {
Self {
representation_id,
options: 0,
}
}
pub const fn with_options(representation_id: RepresentationIdentifier, options: u16) -> Self {
Self {
representation_id,
options,
}
}
pub const fn to_bytes(&self) -> [u8; 4] {
let rep_bytes = self.representation_id.to_bytes();
[
rep_bytes[0],
rep_bytes[1],
(self.options >> 8) as u8,
self.options as u8,
]
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.len() < Self::SIZE {
return Err(Error::CdrError(format!(
"CDR encapsulation header requires {} bytes, got {}",
Self::SIZE,
bytes.len()
)));
}
let representation_id = RepresentationIdentifier::from_bytes([bytes[0], bytes[1]])?;
let options = ((bytes[2] as u16) << 8) | (bytes[3] as u16);
Ok(Self {
representation_id,
options,
})
}
}
impl Default for CdrEncapsulationHeader {
fn default() -> Self {
Self::new(RepresentationIdentifier::CdrLE)
}
}
pub trait CdrSerde: Sized {
fn serialize(&self) -> Result<Vec<u8>>;
fn serialize_with_header(&self, header: CdrEncapsulationHeader) -> Result<Vec<u8>>;
fn deserialize(bytes: &[u8]) -> Result<Self>;
}
impl<T: serde::Serialize + serde::de::DeserializeOwned> CdrSerde for T {
fn serialize(&self) -> Result<Vec<u8>> {
self.serialize_with_header(CdrEncapsulationHeader::default())
}
fn serialize_with_header(&self, header: CdrEncapsulationHeader) -> Result<Vec<u8>> {
if !header.representation_id.is_supported() {
return Err(Error::CdrError(format!(
"Unsupported CDR encoding for serialization: {:?}. Only CdrLE and CdrBE are supported.",
header.representation_id
)));
}
let mut result = header.to_bytes().to_vec();
let buffer = if header.representation_id.is_little_endian() {
cdr_encoding::to_vec::<T, byteorder::LittleEndian>(self)
.map_err(|e| Error::CdrError(e.to_string()))?
} else {
cdr_encoding::to_vec::<T, byteorder::BigEndian>(self)
.map_err(|e| Error::CdrError(e.to_string()))?
};
result.extend(buffer);
Ok(result)
}
fn deserialize(bytes: &[u8]) -> Result<Self> {
let header = CdrEncapsulationHeader::from_bytes(bytes)?;
if !header.representation_id.is_supported() {
return Err(Error::CdrError(format!(
"Unsupported CDR encoding for deserialization: {:?}. Only CdrLE and CdrBE are supported.",
header.representation_id
)));
}
let payload = &bytes[CdrEncapsulationHeader::SIZE..];
if header.representation_id.is_little_endian() {
let (value, _) = cdr_encoding::from_bytes::<T, byteorder::LittleEndian>(payload)
.map_err(|e| Error::CdrError(e.to_string()))?;
Ok(value)
} else {
let (value, _) = cdr_encoding::from_bytes::<T, byteorder::BigEndian>(payload)
.map_err(|e| Error::CdrError(e.to_string()))?;
Ok(value)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_representation_identifier_bytes() {
assert_eq!(RepresentationIdentifier::CdrBE.to_bytes(), [0x00, 0x00]);
assert_eq!(RepresentationIdentifier::CdrLE.to_bytes(), [0x00, 0x01]);
assert_eq!(RepresentationIdentifier::PlCdrBE.to_bytes(), [0x00, 0x02]);
assert_eq!(RepresentationIdentifier::PlCdrLE.to_bytes(), [0x00, 0x03]);
assert_eq!(RepresentationIdentifier::Xcdr2BE.to_bytes(), [0x00, 0x06]);
assert_eq!(RepresentationIdentifier::Xcdr2LE.to_bytes(), [0x00, 0x07]);
}
#[test]
fn test_representation_identifier_roundtrip() {
let identifiers = [
RepresentationIdentifier::CdrBE,
RepresentationIdentifier::CdrLE,
RepresentationIdentifier::PlCdrBE,
RepresentationIdentifier::PlCdrLE,
RepresentationIdentifier::Xcdr2BE,
RepresentationIdentifier::Xcdr2LE,
RepresentationIdentifier::DCdr2BE,
RepresentationIdentifier::DCdr2LE,
RepresentationIdentifier::PlXcdr2BE,
RepresentationIdentifier::PlXcdr2LE,
];
for id in identifiers {
let bytes = id.to_bytes();
let parsed = RepresentationIdentifier::from_bytes(bytes).unwrap();
assert_eq!(id, parsed);
}
}
#[test]
fn test_header_default() {
let header = CdrEncapsulationHeader::default();
assert_eq!(header.representation_id, RepresentationIdentifier::CdrLE);
assert_eq!(header.options, 0);
assert_eq!(header.to_bytes(), [0x00, 0x01, 0x00, 0x00]);
}
#[test]
fn test_header_roundtrip() {
let header = CdrEncapsulationHeader::new(RepresentationIdentifier::Xcdr2LE);
let bytes = header.to_bytes();
let parsed = CdrEncapsulationHeader::from_bytes(&bytes).unwrap();
assert_eq!(header, parsed);
}
#[test]
fn test_header_with_options() {
let header = CdrEncapsulationHeader::with_options(RepresentationIdentifier::CdrLE, 0x1234);
assert_eq!(header.to_bytes(), [0x00, 0x01, 0x12, 0x34]);
}
#[test]
fn test_endianness_detection() {
assert!(RepresentationIdentifier::CdrLE.is_little_endian());
assert!(RepresentationIdentifier::CdrBE.is_big_endian());
assert!(RepresentationIdentifier::Xcdr2LE.is_little_endian());
assert!(RepresentationIdentifier::Xcdr2BE.is_big_endian());
}
#[test]
fn test_cdr_version_detection() {
assert!(RepresentationIdentifier::CdrLE.is_cdr_v1());
assert!(RepresentationIdentifier::CdrBE.is_cdr_v1());
assert!(RepresentationIdentifier::Xcdr2LE.is_cdr_v2());
assert!(RepresentationIdentifier::Xcdr2BE.is_cdr_v2());
}
#[test]
fn test_supported_encodings() {
assert!(RepresentationIdentifier::CdrLE.is_supported());
assert!(RepresentationIdentifier::CdrBE.is_supported());
assert!(!RepresentationIdentifier::PlCdrBE.is_supported());
assert!(!RepresentationIdentifier::PlCdrLE.is_supported());
assert!(!RepresentationIdentifier::Xcdr2BE.is_supported());
assert!(!RepresentationIdentifier::Xcdr2LE.is_supported());
assert!(!RepresentationIdentifier::DCdr2BE.is_supported());
assert!(!RepresentationIdentifier::DCdr2LE.is_supported());
assert!(!RepresentationIdentifier::PlXcdr2BE.is_supported());
assert!(!RepresentationIdentifier::PlXcdr2LE.is_supported());
}
#[test]
fn test_unsupported_encoding_error() {
let bytes = [0x00, 0x03, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04];
let result = CdrEncapsulationHeader::from_bytes(&bytes);
assert!(result.is_ok());
let header = result.unwrap();
assert_eq!(header.representation_id, RepresentationIdentifier::PlCdrLE);
assert!(!header.representation_id.is_supported());
}
#[test]
fn test_invalid_representation_identifier() {
let result = RepresentationIdentifier::from_bytes([0xFF, 0xFF]);
assert!(result.is_err());
}
#[test]
fn test_header_too_short() {
let result = CdrEncapsulationHeader::from_bytes(&[0x00, 0x01]);
assert!(result.is_err());
}
}