From 8561640f1339d889031174592a82a199d8ab0bd8 Mon Sep 17 00:00:00 2001 From: Isaac Blankenau Date: Wed, 25 Mar 2026 15:58:05 -0500 Subject: [PATCH 1/2] Enable zero-copy byte deserialization --- Cargo.toml | 1 + src/cdr_deserializer.rs | 101 +++++++++++++++++++++++++++++++++------- src/cdr_serializer.rs | 1 + 3 files changed, 85 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8454aac..de3bd24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,3 +38,4 @@ static_assertions = "1.1" [dev-dependencies] test-case = "3.1.0" +serde_bytes = "0.11" diff --git a/src/cdr_deserializer.rs b/src/cdr_deserializer.rs index d5fb55b..58e8d99 100644 --- a/src/cdr_deserializer.rs +++ b/src/cdr_deserializer.rs @@ -43,7 +43,7 @@ where } /// Read the first bytes in the input. - fn next_bytes(&mut self, count: usize) -> Result<&[u8]> { + fn next_bytes(&mut self, count: usize) -> Result<&'de [u8]> { if count <= self.input.len() { let (head, tail) = self.input.split_at(count); self.input = tail; @@ -78,7 +78,7 @@ where /// implementation. /// /// return deserialized object + count of bytes consumed -pub fn from_bytes<'de, T, BO>(input_bytes: &[u8]) -> Result<(T, usize)> +pub fn from_bytes<'de, T, BO>(input_bytes: &'de [u8]) -> Result<(T, usize)> where T: serde::Deserialize<'de>, BO: ByteOrder, @@ -89,7 +89,7 @@ where /// Deserialize type based on a [`serde::Deserialize`] implementation. /// /// return deserialized object + count of bytes consumed -pub fn from_bytes_with<'de, S, BO>(input_bytes: &[u8], decoder: S) -> Result<(S::Value, usize)> +pub fn from_bytes_with<'de, S, BO>(input_bytes: &'de [u8], decoder: S) -> Result<(S::Value, usize)> where S: DeserializeSeed<'de>, BO: ByteOrder, @@ -138,6 +138,7 @@ macro_rules! deserialize_multibyte_number { impl<'de, 'a, 'c, BO> de::Deserializer<'de> for &'a mut CdrDeserializer<'c, BO> where + 'c: 'de, BO: ByteOrder, { type Error = Error; @@ -260,14 +261,17 @@ where where V: Visitor<'de>, { - self.deserialize_seq(visitor) + self.calculate_padding_count_from_written_bytes_and_remove(4)?; + let len = self.next_bytes(4)?.read_u32::().unwrap() as usize; + let bytes = self.next_bytes(len)?; + visitor.visit_borrowed_bytes(bytes) } fn deserialize_byte_buf(self, visitor: V) -> Result where V: Visitor<'de>, { - self.deserialize_seq(visitor) + self.deserialize_bytes(visitor) } fn deserialize_option(self, visitor: V) -> Result @@ -418,8 +422,9 @@ where } } -impl<'de, 'a, BO> EnumAccess<'de> for EnumerationHelper<'a, '_, BO> +impl<'de, 'a, 'i, BO> EnumAccess<'de> for EnumerationHelper<'a, 'i, BO> where + 'i: 'de, BO: ByteOrder, { type Error = Error; @@ -438,8 +443,9 @@ where // ---------------------------------------------------------- -impl<'de, 'a, BO> VariantAccess<'de> for EnumerationHelper<'a, '_, BO> +impl<'de, 'a, 'i, BO> VariantAccess<'de> for EnumerationHelper<'a, 'i, BO> where + 'i: 'de, BO: ByteOrder, { type Error = Error; @@ -490,8 +496,9 @@ impl<'a, 'i, BO> SequenceHelper<'a, 'i, BO> { // `SeqAccess` is provided to the `Visitor` to give it the ability to iterate // through elements of the sequence. -impl<'a, 'de, BO> SeqAccess<'de> for SequenceHelper<'a, '_, BO> +impl<'a, 'de, 'i, BO> SeqAccess<'de> for SequenceHelper<'a, 'i, BO> where + 'i: 'de, BO: ByteOrder, { type Error = Error; @@ -511,8 +518,9 @@ where // `MapAccess` is provided to the `Visitor` to give it the ability to iterate // through entries of the map. -impl<'de, 'a, BO> MapAccess<'de> for SequenceHelper<'a, '_, BO> +impl<'de, 'a, 'i, BO> MapAccess<'de> for SequenceHelper<'a, 'i, BO> where + 'i: 'de, BO: ByteOrder, { type Error = Error; @@ -1003,17 +1011,74 @@ mod tests { assert_eq!(serialized.len(), bytes_consumed); } - /* #[test] - fn cdr_deserialization_bytes(){ - let mut buf = B::with_capacity(1024); - buf.put(&b"hello world"[..]); - buf.put_u16(1234); + fn cdr_serde_round_trip_bytes() { + let input: serde_bytes::ByteBuf = serde_bytes::ByteBuf::from(vec![1u8, 2, 3, 4, 5]); + let serialized = to_vec::<_, LittleEndian>(&input).unwrap(); + // Wire format: 4-byte LE length prefix (5) + 5 raw bytes + assert_eq!( + serialized, + vec![0x05, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05] + ); + let (deserialized, bytes_consumed): (serde_bytes::ByteBuf, usize) = + from_bytes::(&serialized).unwrap(); + assert_eq!(input, deserialized); + assert_eq!(serialized.len(), bytes_consumed); + } - let ubuf = buf.into(u8); - let mut serialized = to_little_endian_binary(&ubuf).unwrap(); - let deserialized : Vec = deserialize_from_little_endian(&mut serialized).unwrap(); + #[test] + fn cdr_serde_round_trip_bytes_empty() { + let input: serde_bytes::ByteBuf = serde_bytes::ByteBuf::from(vec![]); + let serialized = to_vec::<_, LittleEndian>(&input).unwrap(); + // Wire format: just 4-byte LE length prefix (0) + assert_eq!(serialized, vec![0x00, 0x00, 0x00, 0x00]); + let (deserialized, bytes_consumed): (serde_bytes::ByteBuf, usize) = + from_bytes::(&serialized).unwrap(); + assert_eq!(input, deserialized); + assert_eq!(serialized.len(), bytes_consumed); + } + + #[test] + fn cdr_serde_round_trip_borrowed_bytes() { + // Wire format: u32 LE length (3) followed by 3 payload bytes + let raw = vec![0x03, 0x00, 0x00, 0x00, 0xAA, 0xBB, 0xCC]; + let (deserialized, bytes_consumed): (&serde_bytes::Bytes, usize) = + from_bytes::<&serde_bytes::Bytes, LittleEndian>(&raw).unwrap(); + assert_eq!(deserialized.as_ref(), &[0xAA, 0xBB, 0xCC]); + assert_eq!(bytes_consumed, 7); + } + #[test] + fn cdr_serde_round_trip_bytes_in_struct() { + #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] + struct WithBytes { + id: u32, + #[serde(with = "serde_bytes")] + payload: Vec, + tag: u8, + } + + let input = WithBytes { + id: 42, + payload: vec![0xDE, 0xAD, 0xBE, 0xEF], + tag: 7, + }; + let serialized = to_vec::<_, LittleEndian>(&input).unwrap(); + let (deserialized, bytes_consumed): (WithBytes, usize) = + from_bytes::(&serialized).unwrap(); + assert_eq!(input, deserialized); + assert_eq!(serialized.len(), bytes_consumed); + } + + #[test] + fn cdr_serde_vec_u8_still_works_via_seq() { + // Plain Vec (without serde_bytes) goes through serialize_seq/deserialize_seq, + // not serialize_bytes/deserialize_bytes. Confirm this path still works. + let input: Vec = vec![10, 20, 30]; + let serialized = to_vec::<_, LittleEndian>(&input).unwrap(); + let (deserialized, bytes_consumed): (Vec, usize) = + from_bytes::, LittleEndian>(&serialized).unwrap(); + assert_eq!(input, deserialized); + assert_eq!(serialized.len(), bytes_consumed); } - */ } diff --git a/src/cdr_serializer.rs b/src/cdr_serializer.rs index 0e78038..f152e5f 100644 --- a/src/cdr_serializer.rs +++ b/src/cdr_serializer.rs @@ -321,6 +321,7 @@ where } fn serialize_bytes(self, v: &[u8]) -> Result<()> { + self.serialize_u32(v.len() as u32)?; self.writer.write_all(v)?; Ok(()) } From c7bc7c2ddf5c42fd0056f7419ca50755c6f3e893 Mon Sep 17 00:00:00 2001 From: Isaac Blankenau Date: Wed, 25 Mar 2026 16:10:04 -0500 Subject: [PATCH 2/2] adding test --- src/cdr_deserializer.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/cdr_deserializer.rs b/src/cdr_deserializer.rs index 58e8d99..9f095b1 100644 --- a/src/cdr_deserializer.rs +++ b/src/cdr_deserializer.rs @@ -1081,4 +1081,15 @@ mod tests { assert_eq!(input, deserialized); assert_eq!(serialized.len(), bytes_consumed); } + + #[test] + fn cdr_serde_seq_serialized_bytes_deserializable_via_bytes_path() { + // Data serialized as Vec (seq path) must be deserializable via serde_bytes (bytes path). + let input: Vec = vec![0xDE, 0xAD, 0xBE, 0xEF]; + let serialized = to_vec::<_, LittleEndian>(&input).unwrap(); + let (deserialized, bytes_consumed): (serde_bytes::ByteBuf, usize) = + from_bytes::(&serialized).unwrap(); + assert_eq!(input, deserialized.as_ref()); + assert_eq!(serialized.len(), bytes_consumed); + } }