p2panda_core/
cbor.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
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;
10
11use ciborium::de::Error as DeserializeError;
12use ciborium::ser::Error as SerializeError;
13use serde::{Deserialize, Serialize};
14use thiserror::Error;
15
16/// Serializes a value into CBOR format.
17pub fn encode_cbor<T: Serialize>(value: &T) -> Result<Vec<u8>, EncodeError> {
18    let mut bytes = Vec::new();
19    ciborium::ser::into_writer(value, &mut bytes).map_err(Into::<EncodeError>::into)?;
20    Ok(bytes)
21}
22
23/// Deserializes a value which was formatted in CBOR.
24pub fn decode_cbor<T: for<'a> Deserialize<'a>, R: Read>(reader: R) -> Result<T, DecodeError> {
25    let value = ciborium::from_reader::<T, R>(reader).map_err(Into::<DecodeError>::into)?;
26    Ok(value)
27}
28
29/// An error occurred during CBOR serialization.
30#[derive(Debug, Error)]
31pub enum EncodeError {
32    /// An error occurred while writing bytes.
33    ///
34    /// Contains the underlying error returned while reading.
35    #[error("an error occurred while reading bytes: {0}")]
36    Io(std::io::Error),
37
38    /// An error indicating a value that cannot be serialized.
39    ///
40    /// Contains a description of the problem delivered from serde.
41    #[error("an error occurred while deserializing value: {0}")]
42    Value(String),
43}
44
45impl From<SerializeError<std::io::Error>> for EncodeError {
46    fn from(value: SerializeError<std::io::Error>) -> Self {
47        match value {
48            SerializeError::Io(err) => EncodeError::Io(err),
49            SerializeError::Value(err) => EncodeError::Value(err),
50        }
51    }
52}
53
54/// An error occurred during CBOR deserialization.
55#[derive(Debug, Error)]
56pub enum DecodeError {
57    /// An error occurred while reading bytes.
58    ///
59    /// Contains the underlying error returned while reading.
60    #[error("an error occurred while reading bytes: {0}")]
61    Io(std::io::Error),
62
63    /// An error occurred while parsing bytes.
64    ///
65    /// Contains the offset into the stream where the syntax error occurred.
66    #[error("an error occurred while parsing bytes at position {0}")]
67    Syntax(usize),
68
69    /// An error occurred while processing a parsed value.
70    ///
71    /// Contains a description of the error that occurred and (optionally) the offset into the
72    /// stream indicating the start of the item being processed when the error occurred.
73    #[error("an error occurred while processing a parsed value at position {0:?}: {1}")]
74    Semantic(Option<usize>, String),
75
76    /// The input caused serde to recurse too much.
77    ///
78    /// This error prevents a stack overflow.
79    #[error("recursion limit exceeded while decoding")]
80    RecursionLimitExceeded,
81}
82
83impl From<DeserializeError<std::io::Error>> for DecodeError {
84    fn from(value: DeserializeError<std::io::Error>) -> Self {
85        match value {
86            DeserializeError::Io(err) => DecodeError::Io(err),
87            DeserializeError::Syntax(offset) => DecodeError::Syntax(offset),
88            DeserializeError::Semantic(offset, description) => {
89                DecodeError::Semantic(offset, description)
90            }
91            DeserializeError::RecursionLimitExceeded => DecodeError::RecursionLimitExceeded,
92        }
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use crate::{Body, Header, PrivateKey};
99
100    use super::{decode_cbor, encode_cbor};
101
102    #[test]
103    fn encode_decode() {
104        let private_key = PrivateKey::new();
105        let body = Body::new(&[1, 2, 3]);
106        let mut header = Header::<()> {
107            public_key: private_key.public_key(),
108            payload_size: body.size(),
109            payload_hash: Some(body.hash()),
110            ..Default::default()
111        };
112        header.sign(&private_key);
113
114        let bytes = encode_cbor(&header).unwrap();
115        let header_again: Header<()> = decode_cbor(&bytes[..]).unwrap();
116
117        assert_eq!(header.hash(), header_again.hash());
118    }
119}