Skip to content

Commit

Permalink
feat: support zero-copy deserialization
Browse files Browse the repository at this point in the history
Signed-off-by: Ahmed Charles <[email protected]>
  • Loading branch information
ahmedcharles committed Feb 21, 2024
1 parent 88cf117 commit 33235ff
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 24 deletions.
12 changes: 12 additions & 0 deletions ciborium-io/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ pub trait Write {
fn flush(&mut self) -> Result<(), Self::Error>;
}

#[cfg(feature = "std")]
/// Abstracted EOF error.
pub fn eof() -> std::io::Error {
std::io::ErrorKind::UnexpectedEof.into()
}

#[cfg(feature = "std")]
impl<T: std::io::Read> Read for T {
type Error = std::io::Error;
Expand Down Expand Up @@ -108,6 +114,12 @@ impl<W: Write + ?Sized> Write for &mut W {
#[derive(Debug)]
pub struct EndOfFile(());

#[cfg(not(feature = "std"))]
/// Abstracted EOF error.
pub fn eof() -> EndOfFile {
EndOfFile(())
}

#[cfg(not(feature = "std"))]
impl Read for &[u8] {
type Error = EndOfFile;
Expand Down
156 changes: 132 additions & 24 deletions ciborium/src/de/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub use error::Error;

use alloc::{string::String, vec::Vec};

Check warning on line 9 in ciborium/src/de/mod.rs

View workflow job for this annotation

GitHub Actions / test nightly debug ciborium std

the item `String` is imported redundantly

Check warning on line 9 in ciborium/src/de/mod.rs

View workflow job for this annotation

GitHub Actions / test nightly debug ciborium std

the item `Vec` is imported redundantly

Check warning on line 9 in ciborium/src/de/mod.rs

View workflow job for this annotation

GitHub Actions / test nightly release ciborium std

the item `String` is imported redundantly

Check warning on line 9 in ciborium/src/de/mod.rs

View workflow job for this annotation

GitHub Actions / test nightly release ciborium std

the item `Vec` is imported redundantly

use ciborium_io::Read;
use ciborium_io::{eof, Read};
use ciborium_ll::*;
use serde::{
de::{self, value::BytesDeserializer, Deserializer as _},
Expand Down Expand Up @@ -48,14 +48,103 @@ impl<E: de::Error> Expected<E> for Header {
}
}

enum Reference<'b, 'c, T: ?Sized + 'static> {

Check warning on line 51 in ciborium/src/de/mod.rs

View workflow job for this annotation

GitHub Actions / test nightly debug ciborium

enum `Reference` is never used

Check warning on line 51 in ciborium/src/de/mod.rs

View workflow job for this annotation

GitHub Actions / test nightly release ciborium

enum `Reference` is never used
Borrowed(&'b T),
Copied(&'c T),
}

trait ReadSlice<'de>: Read {

Check warning on line 56 in ciborium/src/de/mod.rs

View workflow job for this annotation

GitHub Actions / test nightly debug ciborium

trait `ReadSlice` is never used

Check warning on line 56 in ciborium/src/de/mod.rs

View workflow job for this annotation

GitHub Actions / test nightly release ciborium

trait `ReadSlice` is never used
fn read_slice<'a>(&'a mut self, len: usize) -> Result<Reference<'de, 'a, [u8]>, Self::Error>;
}

/// TODO
pub struct Reader<R> {
r: R,
buf: Vec<u8>,

Check warning on line 63 in ciborium/src/de/mod.rs

View workflow job for this annotation

GitHub Actions / test nightly debug ciborium

field `buf` is never read

Check warning on line 63 in ciborium/src/de/mod.rs

View workflow job for this annotation

GitHub Actions / test nightly release ciborium

field `buf` is never read
}

impl<R> Reader<R> {
fn new(r: R) -> Self {
Self {
r,
buf: Vec::with_capacity(128),
}
}
}

impl<R: Read> Read for Reader<R> {
type Error = R::Error;

fn read_exact(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
self.r.read_exact(data)
}
}

impl<'de, R: Read> ReadSlice<'de> for Reader<R> {
fn read_slice<'a>(&'a mut self, len: usize) -> Result<Reference<'de, 'a, [u8]>, Self::Error> {
self.buf.resize(len, 0);
self.r.read_exact(&mut self.buf)?;
Ok(Reference::Copied(&self.buf[..]))
}
}

/// TODO
pub struct SliceReader<'de> {
_slice: &'de [u8],
buf: &'de [u8],
}

impl<'de> SliceReader<'de> {
fn new(r: &'de [u8]) -> Self {
Self { _slice: r, buf: r }
}
}

impl<'de> Read for SliceReader<'de> {
type Error = <&'de [u8] as ciborium_io::Read>::Error;

fn read_exact(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
self.buf.read_exact(data)
}
}

impl<'de> ReadSlice<'de> for SliceReader<'de> {
fn read_slice<'a>(&'a mut self, len: usize) -> Result<Reference<'de, 'a, [u8]>, Self::Error> {
if len > self.buf.len() {
return Err(eof());
}
let (a, b) = self.buf.split_at(len);
self.buf = b;
Ok(Reference::Borrowed(a))
}
}

/// Deserializer
pub struct Deserializer<'b, R> {
decoder: Decoder<R>,
scratch: &'b mut [u8],
recurse: usize,
}

fn noop(_: u8) {}
impl<'a, R: Read> Deserializer<'a, Reader<R>> {
fn from_reader(r: R, scratch: &'a mut [u8], recurse: usize) -> Self {
Self {
decoder: Reader::new(r).into(),
scratch,
recurse,
}
}
}

impl<'a, 'de> Deserializer<'a, SliceReader<'de>> {
fn from_slice(s: &'de [u8], scratch: &'a mut [u8], recurse: usize) -> Self {
Self {
decoder: SliceReader::new(s).into(),
scratch,
recurse,
}
}
}

impl<'a, R: Read> Deserializer<'a, R>
where
Expand Down Expand Up @@ -148,6 +237,8 @@ where
}
}

fn noop(_: u8) {}

impl<'de, 'a, 'b, R: Read> de::Deserializer<'de> for &'a mut Deserializer<'b, R>
where
R::Error: core::fmt::Debug,
Expand Down Expand Up @@ -874,11 +965,7 @@ pub fn from_reader_with_buffer<T: de::DeserializeOwned, R: Read>(
where
R::Error: core::fmt::Debug,
{
let mut reader = Deserializer {
decoder: reader.into(),
scratch: scratch_buffer,
recurse: 256,
};
let mut reader = Deserializer::from_reader(reader, scratch_buffer, 256);

T::deserialize(&mut reader)
}
Expand All @@ -898,11 +985,7 @@ where
{
let mut scratch = [0; 4096];

let mut reader = Deserializer {
decoder: reader.into(),
scratch: &mut scratch,
recurse: recurse_limit,
};
let mut reader = Deserializer::from_reader(reader, &mut scratch, recurse_limit);

T::deserialize(&mut reader)
}
Expand All @@ -912,15 +995,11 @@ where
pub fn deserializer_from_reader_with_buffer<R: Read>(
reader: R,
scratch_buffer: &mut [u8],
) -> Deserializer<'_, R>
) -> Deserializer<'_, Reader<R>>
where
R::Error: core::fmt::Debug,
{
Deserializer {
decoder: reader.into(),
scratch: scratch_buffer,
recurse: 256,
}
Deserializer::from_reader(reader, scratch_buffer, 256)
}

/// Returns a deserializer with a specified scratch buffer
Expand All @@ -933,13 +1012,42 @@ pub fn deserializer_from_reader_with_buffer_and_recursion_limit<R: Read>(
reader: R,
scratch_buffer: &mut [u8],
recurse_limit: usize,
) -> Deserializer<'_, R>
) -> Deserializer<'_, Reader<R>>
where
R::Error: core::fmt::Debug,
{
Deserializer {
decoder: reader.into(),
scratch: scratch_buffer,
recurse: recurse_limit,
}
Deserializer::from_reader(reader, scratch_buffer, recurse_limit)
}

/// Deserializes as CBOR from a type with [`impl
/// ciborium_io::Read`](ciborium_io::Read) using a 4KB buffer on the stack.
///
/// If you want to deserialize faster at the cost of more memory, consider using
/// [`from_reader_with_buffer`](from_reader_with_buffer) with a larger buffer,
/// for example 64KB.
#[inline]
pub fn from_slice<'de, T: de::Deserialize<'de>>(
reader: &'de [u8],
) -> Result<T, Error<<&'de [u8] as ciborium_io::Read>::Error>>
where
<&'de [u8] as ciborium_io::Read>::Error: core::fmt::Debug,
{
let mut scratch = [0; 4096];
from_slice_with_buffer(reader, &mut scratch)
}

/// Deserializes as CBOR from a type with [`impl
/// ciborium_io::Read`](ciborium_io::Read), using a caller-specific buffer as a
/// temporary scratch space.
#[inline]
pub fn from_slice_with_buffer<'de, T: de::Deserialize<'de>>(
reader: &'de [u8],
scratch_buffer: &mut [u8],
) -> Result<T, Error<<&'de [u8] as ciborium_io::Read>::Error>>
where
<&'de [u8] as ciborium_io::Read>::Error: core::fmt::Debug,
{
let mut reader = Deserializer::from_slice(reader, scratch_buffer, 256);

T::deserialize(&mut reader)
}

0 comments on commit 33235ff

Please sign in to comment.