summaryrefslogtreecommitdiff
path: root/src/dec.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/dec.rs')
-rw-r--r--src/dec.rs174
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(()),
+ })
+ }
+}