diff options
Diffstat (limited to 'src/dec.rs')
-rw-r--r-- | src/dec.rs | 174 |
1 files changed, 174 insertions, 0 deletions
diff --git a/src/dec.rs b/src/dec.rs new file mode 100644 index 0000000..8329634 --- /dev/null +++ b/src/dec.rs @@ -0,0 +1,174 @@ +use super::*; + +use ciborium_io::Read; + +/// An error that occurred while decoding +#[derive(Debug)] +pub enum Error<T> { + /// An error occurred while reading bytes + /// + /// Contains the underlying error reaturned while reading. + Io(T), + + /// An error occurred while parsing bytes + /// + /// Contains the offset into the stream where the syntax error occurred. + Syntax(usize), +} + +impl<T> From<T> for Error<T> { + #[inline] + fn from(value: T) -> Self { + Self::Io(value) + } +} + +/// A decoder for deserializing CBOR items +/// +/// This decoder manages the low-level decoding of CBOR items into `Header` +/// objects. It also contains utility functions for parsing segmented bytes +/// and text inputs. +pub struct Decoder<R: Read> { + reader: R, + offset: usize, + buffer: Option<Title>, +} + +impl<R: Read> From<R> for Decoder<R> { + #[inline] + fn from(value: R) -> Self { + Self { + reader: value, + offset: 0, + buffer: None, + } + } +} + +impl<R: Read> Read for Decoder<R> { + type Error = R::Error; + + #[inline] + fn read_exact(&mut self, data: &mut [u8]) -> Result<(), Self::Error> { + assert!(self.buffer.is_none()); + self.reader.read_exact(data)?; + self.offset += data.len(); + Ok(()) + } +} + +impl<R: Read> Decoder<R> { + #[inline] + fn pull_title(&mut self) -> Result<Title, Error<R::Error>> { + if let Some(title) = self.buffer.take() { + self.offset += title.1.as_ref().len() + 1; + return Ok(title); + } + + let mut prefix = [0u8; 1]; + self.read_exact(&mut prefix[..])?; + + let major = match prefix[0] >> 5 { + 0 => Major::Positive, + 1 => Major::Negative, + 2 => Major::Bytes, + 3 => Major::Text, + 4 => Major::Array, + 5 => Major::Map, + 6 => Major::Tag, + 7 => Major::Other, + _ => unreachable!(), + }; + + let mut minor = match prefix[0] & 0b00011111 { + x if x < 24 => Minor::This(x), + 24 => Minor::Next1([0; 1]), + 25 => Minor::Next2([0; 2]), + 26 => Minor::Next4([0; 4]), + 27 => Minor::Next8([0; 8]), + 31 => Minor::More, + _ => return Err(Error::Syntax(self.offset - 1)), + }; + + self.read_exact(minor.as_mut())?; + Ok(Title(major, minor)) + } + + #[inline] + fn push_title(&mut self, item: Title) { + assert!(self.buffer.is_none()); + self.buffer = Some(item); + self.offset -= item.1.as_ref().len() + 1; + } + + /// Pulls the next header from the input + #[inline] + pub fn pull(&mut self) -> Result<Header, Error<R::Error>> { + let offset = self.offset; + self.pull_title()? + .try_into() + .map_err(|_| Error::Syntax(offset)) + } + + /// Push a single header into the input buffer + /// + /// # Panics + /// + /// This function panics if called while there is already a header in the + /// input buffer. You should take care to call this function only after + /// pulling a header to ensure there is nothing in the input buffer. + #[inline] + pub fn push(&mut self, item: Header) { + self.push_title(Title::from(item)) + } + + /// Gets the current byte offset into the stream + /// + /// The offset starts at zero when the decoder is created. Therefore, if + /// bytes were already read from the reader before the decoder was created, + /// you must account for this. + #[inline] + pub fn offset(&mut self) -> usize { + self.offset + } + + /// Process an incoming bytes item + /// + /// In CBOR, bytes can be segmented. The logic for this can be a bit tricky, + /// so we encapsulate that logic using this function. This function **MUST** + /// be called immediately after first pulling a `Header::Bytes(len)` from + /// the wire and `len` must be provided to this function from that value. + /// + /// The `buf` parameter provides a buffer used when reading in the segmented + /// bytes. A large buffer will result in fewer calls to read incoming bytes + /// at the cost of memory usage. You should consider this trade off when + /// deciding the size of your buffer. + #[inline] + pub fn bytes(&mut self, len: Option<usize>) -> Segments<R, crate::seg::Bytes> { + self.push(Header::Bytes(len)); + Segments::new(self, |header| match header { + Header::Bytes(len) => Ok(len), + _ => Err(()), + }) + } + + /// Process an incoming text item + /// + /// In CBOR, text can be segmented. The logic for this can be a bit tricky, + /// so we encapsulate that logic using this function. This function **MUST** + /// be called immediately after first pulling a `Header::Text(len)` from + /// the wire and `len` must be provided to this function from that value. + /// + /// The `buf` parameter provides a buffer used when reading in the segmented + /// text. A large buffer will result in fewer calls to read incoming bytes + /// at the cost of memory usage. You should consider this trade off when + /// deciding the size of your buffer. + #[inline] + pub fn text(&mut self, len: Option<usize>) -> Segments<R, crate::seg::Text> { + self.push(Header::Text(len)); + Segments::new(self, |header| match header { + Header::Text(len) => Ok(len), + _ => Err(()), + }) + } +} |