diff --git a/src/de.rs b/src/de.rs index 08534a2..be18c84 100644 --- a/src/de.rs +++ b/src/de.rs @@ -44,6 +44,52 @@ where Ok(t) } +/// Deserializes a `&[u8]` into a type. +/// +/// This function will attempt to interpret `bytes` as the BCS serialized form of `T` and +/// deserialize `T` from `bytes`. +/// +/// # Safety +/// +/// This is unsafe. Developers must ensure that their `Deserialize` +/// structs correctly reflect the data being deserialized. +/// If the deserialization logic of one type overlaps with the next field, +/// the overall deserialization might succeed with undefined behavior. +/// +/// If you're not sure what you're doing, consider using `from_bytes` instead. +/// +/// # Examples +/// +/// ``` +/// use bcs::from_bytes_discarding_remaining_input; +/// use serde::Deserialize; +/// +/// #[derive(Deserialize)] +/// struct Ip([u8; 4]); +/// +/// #[derive(Deserialize)] +/// struct SocketAddr { +/// ip: Ip, +/// } +/// +/// let bytes = vec![0x7f, 0x00, 0x00, 0x01, 0x41, 0x1f]; +/// let socket_addr: SocketAddr = unsafe { +/// from_bytes_discarding_remaining_input(&bytes).unwrap() +/// }; +/// +/// assert_eq!(socket_addr.ip.0, [127, 0, 0, 1]); +/// ``` +#[allow(unsafe_code)] +pub unsafe fn from_bytes_discarding_remaining_input<'a, T>(bytes: &'a [u8]) -> Result +where + T: Deserialize<'a>, +{ + let mut deserializer = Deserializer::new_discarding_remaining_input(bytes, crate::MAX_CONTAINER_DEPTH); + let t = T::deserialize(&mut deserializer)?; + deserializer.end()?; + Ok(t) +} + /// Same as `from_bytes` but use `limit` as max container depth instead of MAX_CONTAINER_DEPTH` /// Note that `limit` has to be lower than MAX_CONTAINER_DEPTH pub fn from_bytes_with_limit<'a, T>(bytes: &'a [u8], limit: usize) -> Result @@ -141,13 +187,18 @@ where struct Deserializer { input: R, max_remaining_depth: usize, + discard_remaining_input: bool } impl<'de, R: Read> Deserializer> { - fn from_reader(input: &'de mut R, max_remaining_depth: usize) -> Self { + fn from_reader( + input: &'de mut R, + max_remaining_depth: usize, + ) -> Self { Deserializer { input: TeeReader::new(input), max_remaining_depth, + discard_remaining_input: false, } } } @@ -155,10 +206,35 @@ impl<'de, R: Read> Deserializer> { impl<'de> Deserializer<&'de [u8]> { /// Creates a new `Deserializer` which will be deserializing the provided /// input. - fn new(input: &'de [u8], max_remaining_depth: usize) -> Self { + fn new( + input: &'de [u8], + max_remaining_depth: usize, + ) -> Self { Deserializer { input, max_remaining_depth, + discard_remaining_input: false, + } + } + + /// Creates a new `Deserializer` which will be deserializing the provided + /// input, and will discard remaining input. + /// + /// # Safety + /// + /// This is unsafe. Developers must ensure that their `Deserialize` + /// structs correctly reflect the data being deserialized. + /// If the deserialization logic of one type overlaps with the next field, + /// the overall deserialization might succeed with undefined behavior. + #[allow(unsafe_code)] + unsafe fn new_discarding_remaining_input( + input: &'de [u8], + max_remaining_depth: usize, + ) -> Self { + Deserializer { + input, + max_remaining_depth, + discard_remaining_input: true, } } } @@ -340,7 +416,13 @@ impl<'de, R: Read> BcsDeserializer<'de> for Deserializer> { fn end(&mut self) -> Result<()> { let mut byte = [0u8; 1]; match self.input.read_exact(&mut byte) { - Ok(_) => Err(Error::RemainingInput), + Ok(_) => { + if !self.discard_remaining_input { + Err(Error::RemainingInput) + } else { + Ok(()) + } + }, Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => Ok(()), Err(e) => Err(e.into()), } @@ -388,11 +470,12 @@ impl<'de> BcsDeserializer<'de> for Deserializer<&'de [u8]> { } fn end(&mut self) -> Result<()> { - if self.input.is_empty() { - Ok(()) - } else { + if !self.discard_remaining_input && !self.input.is_empty() { Err(Error::RemainingInput) + } else { + Ok(()) } + } } diff --git a/src/lib.rs b/src/lib.rs index 4ede21a..fefcead 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ // Copyright (c) The Diem Core Contributors // SPDX-License-Identifier: Apache-2.0 -#![forbid(unsafe_code)] +#![deny(unsafe_code)] //! # Binary Canonical Serialization (BCS) //! @@ -315,7 +315,7 @@ pub const MAX_SEQUENCE_LENGTH: usize = (1 << 31) - 1; pub const MAX_CONTAINER_DEPTH: usize = 500; pub use de::{ - from_bytes, from_bytes_seed, from_bytes_seed_with_limit, from_bytes_with_limit, from_reader, + from_bytes, from_bytes_discarding_remaining_input, from_bytes_seed, from_bytes_seed_with_limit, from_bytes_with_limit, from_reader, from_reader_seed, from_reader_seed_with_limit, from_reader_with_limit, }; pub use error::{Error, Result}; diff --git a/tests/serde.rs b/tests/serde.rs index 8305f61..43dcef6 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -14,10 +14,7 @@ use proptest::prelude::*; use proptest_derive::Arbitrary; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use bcs::{ - from_bytes, from_bytes_with_limit, from_reader, serialized_size, to_bytes, to_bytes_with_limit, - Error, MAX_CONTAINER_DEPTH, MAX_SEQUENCE_LENGTH, -}; +use bcs::{from_bytes, from_bytes_discarding_remaining_input, from_bytes_with_limit, from_reader, serialized_size, to_bytes, to_bytes_with_limit, Error, MAX_CONTAINER_DEPTH, MAX_SEQUENCE_LENGTH}; /// A helper function to attempt deserialization via reader fn from_bytes_via_reader(bytes: &[u8]) -> Result { @@ -470,6 +467,16 @@ fn leftover_bytes() { ); } +#[test] +fn leftover_bytes_discarding_remaining_bytes() { + let seq = vec![5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // 5 extra elements + let result = unsafe { + from_bytes_discarding_remaining_input::>(&seq) + }; + + assert_eq!(result, Ok(vec![1, 2, 3, 4, 5])); +} + #[test] fn test_f32() { assert!(to_bytes(&1.0f32).is_err());