Skip to main content

p2panda_core/
cbor.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! Utility methods to encode or decode values in [CBOR] format.
4//!
5//! As per p2panda specification data-types like operation headers are encoded in the Concise
6//! Binary Object Representation (CBOR) format.
7//!
8//! [CBOR]: https://cbor.io/
9use std::io::Read;
10use std::sync::Arc;
11
12use ciborium::de::Error as DeserializeError;
13use ciborium::ser::Error as SerializeError;
14use serde::{Deserialize, Serialize};
15use thiserror::Error;
16
17/// Serializes a value into CBOR format.
18pub fn encode_cbor<T: Serialize>(value: &T) -> Result<Vec<u8>, EncodeError> {
19    let mut bytes = Vec::new();
20    ciborium::ser::into_writer(value, &mut bytes).map_err(Into::<EncodeError>::into)?;
21    Ok(bytes)
22}
23
24/// Deserializes a value which was formatted in CBOR.
25pub fn decode_cbor<T: for<'a> Deserialize<'a>, R: Read>(reader: R) -> Result<T, DecodeError> {
26    let value = ciborium::from_reader::<T, R>(reader).map_err(Into::<DecodeError>::into)?;
27    Ok(value)
28}
29
30/// An error occurred during CBOR serialization.
31#[derive(Debug, Error)]
32pub enum EncodeError {
33    /// An error occurred while writing bytes.
34    ///
35    /// Contains the underlying error returned while reading.
36    #[error("an error occurred while reading bytes: {0}")]
37    Io(std::io::Error),
38
39    /// An error indicating a value that cannot be serialized.
40    ///
41    /// Contains a description of the problem delivered from serde.
42    #[error("an error occurred while deserializing value: {0}")]
43    Value(String),
44}
45
46impl From<SerializeError<std::io::Error>> for EncodeError {
47    fn from(value: SerializeError<std::io::Error>) -> Self {
48        match value {
49            SerializeError::Io(err) => EncodeError::Io(err),
50            SerializeError::Value(err) => EncodeError::Value(err),
51        }
52    }
53}
54
55/// An error occurred during CBOR deserialization.
56#[derive(Clone, Debug, Error)]
57pub enum DecodeError {
58    /// An error occurred while reading bytes.
59    ///
60    /// Contains the underlying error returned while reading.
61    #[error("an error occurred while reading bytes: {0}")]
62    Io(Arc<std::io::Error>),
63
64    /// An error occurred while parsing bytes.
65    ///
66    /// Contains the offset into the stream where the syntax error occurred.
67    #[error("an error occurred while parsing bytes at position {0}")]
68    Syntax(usize),
69
70    /// An error occurred while processing a parsed value.
71    ///
72    /// Contains a description of the error that occurred and (optionally) the offset into the
73    /// stream indicating the start of the item being processed when the error occurred.
74    #[error("an error occurred while processing a parsed value at position {0:?}: {1}")]
75    Semantic(Option<usize>, String),
76
77    /// The input caused serde to recurse too much.
78    ///
79    /// This error prevents a stack overflow.
80    #[error("recursion limit exceeded while decoding")]
81    RecursionLimitExceeded,
82}
83
84impl From<DeserializeError<std::io::Error>> for DecodeError {
85    fn from(value: DeserializeError<std::io::Error>) -> Self {
86        match value {
87            DeserializeError::Io(err) => DecodeError::Io(Arc::new(err)),
88            DeserializeError::Syntax(offset) => DecodeError::Syntax(offset),
89            DeserializeError::Semantic(offset, description) => {
90                DecodeError::Semantic(offset, description)
91            }
92            DeserializeError::RecursionLimitExceeded => DecodeError::RecursionLimitExceeded,
93        }
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use crate::{Body, Header, SigningKey};
100
101    use super::{DecodeError, decode_cbor, encode_cbor};
102
103    #[test]
104    fn encode_decode() {
105        let signing_key = SigningKey::generate();
106        let body = Body::new(&[1, 2, 3]);
107        let mut header = Header::<()> {
108            verifying_key: signing_key.verifying_key(),
109            payload_size: body.size(),
110            payload_hash: Some(body.hash()),
111            ..Default::default()
112        };
113        header.sign(&signing_key);
114
115        let bytes = encode_cbor(&header).unwrap();
116        let header_again: Header<()> = decode_cbor(&bytes[..]).unwrap();
117
118        assert_eq!(header.hash(), header_again.hash());
119    }
120
121    #[test]
122    fn decode_eof() {
123        // This is an incomplete byte sequence of a header / body tuple.
124        let bytes = hex::decode("828901582014d59877a250").unwrap();
125        let err = decode_cbor::<(Header<()>, Option<Body>), _>(&bytes[..]);
126
127        // We're expecting an "Unexpected EOF" error here. The underlying decoder should be able to
128        // detect that there's bytes missing.
129        assert!(matches!(err, Err(DecodeError::Io(_))));
130    }
131}