Skip to main content

sacp_cbor/
canonical.rs

1use crate::{CborError, DecodeLimits, ErrorCode};
2
3/// A validated canonical SACP-CBOR/1 data item borrowed from an input buffer.
4///
5/// This is the primary "hot-path" product of [`crate::validate_canonical`]. The bytes are guaranteed to:
6///
7/// - represent exactly one SACP-CBOR/1 CBOR data item, and
8/// - already be in canonical form.
9///
10/// Therefore, for protocol purposes, these bytes can be treated as the stable canonical representation.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct CanonicalCborRef<'a> {
13    bytes: &'a [u8],
14}
15
16impl<'a> CanonicalCborRef<'a> {
17    #[inline]
18    pub(crate) const fn new(bytes: &'a [u8]) -> Self {
19        Self { bytes }
20    }
21
22    /// Return the canonical bytes.
23    #[inline]
24    #[must_use]
25    pub const fn as_bytes(self) -> &'a [u8] {
26        self.bytes
27    }
28
29    /// Wrap canonical bytes without validation.
30    ///
31    /// # Safety
32    ///
33    /// The caller must guarantee the bytes are a single canonical SACP-CBOR/1 item.
34    #[cfg(feature = "unsafe")]
35    #[cfg_attr(docsrs, doc(cfg(feature = "unsafe")))]
36    #[inline]
37    #[must_use]
38    pub const unsafe fn from_canonical(bytes: &'a [u8]) -> Self {
39        Self { bytes }
40    }
41
42    /// Length in bytes of the canonical representation.
43    #[inline]
44    #[must_use]
45    pub const fn len(self) -> usize {
46        self.bytes.len()
47    }
48
49    /// Returns `true` iff the canonical encoding is empty (this never happens for a valid item).
50    #[inline]
51    #[must_use]
52    pub const fn is_empty(self) -> bool {
53        self.bytes.is_empty()
54    }
55
56    /// Compute the SHA-256 digest of the canonical bytes.
57    #[cfg(feature = "sha2")]
58    #[cfg_attr(docsrs, doc(cfg(feature = "sha2")))]
59    #[must_use]
60    pub fn sha256(self) -> [u8; 32] {
61        use sha2::{Digest, Sha256};
62        let mut h = Sha256::new();
63        h.update(self.bytes);
64        let out = h.finalize();
65        let mut digest = [0u8; 32];
66        digest.copy_from_slice(out.as_slice());
67        digest
68    }
69
70    /// Copy into an owned [`CanonicalCbor`].
71    ///
72    /// This method is available with the `alloc` feature.
73    #[cfg(feature = "alloc")]
74    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
75    ///
76    /// # Errors
77    ///
78    /// Returns `CborError` on allocation failure.
79    pub fn to_owned(self) -> Result<CanonicalCbor, CborError> {
80        use crate::alloc_util::try_vec_from_slice;
81
82        Ok(CanonicalCbor {
83            bytes: try_vec_from_slice(self.bytes, 0)?,
84        })
85    }
86
87    /// Compare canonical bytes for equality.
88    #[inline]
89    #[must_use]
90    pub fn bytes_eq(self, other: Self) -> bool {
91        self.bytes == other.bytes
92    }
93}
94
95impl AsRef<[u8]> for CanonicalCborRef<'_> {
96    fn as_ref(&self) -> &[u8] {
97        self.bytes
98    }
99}
100
101/// A validated canonical CBOR-encoded text-string key.
102///
103/// This wraps the exact canonical encoding bytes for a CBOR text string.
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub struct EncodedTextKey<'a> {
106    bytes: &'a [u8],
107}
108
109impl<'a> EncodedTextKey<'a> {
110    #[cfg(feature = "alloc")]
111    #[inline]
112    pub(crate) const fn new_unchecked(bytes: &'a [u8]) -> Self {
113        Self { bytes }
114    }
115
116    /// Validate and wrap an encoded text key.
117    ///
118    /// # Errors
119    ///
120    /// Returns `CborError` if `bytes` are not a canonical CBOR text string.
121    pub fn parse(bytes: &'a [u8]) -> Result<Self, CborError> {
122        if bytes.is_empty() {
123            return Err(CborError::new(ErrorCode::UnexpectedEof, 0));
124        }
125        let limits = DecodeLimits::for_bytes(bytes.len());
126        crate::validate_canonical(bytes, limits)?;
127        if bytes[0] >> 5 != 3 {
128            return Err(CborError::new(ErrorCode::MapKeyMustBeText, 0));
129        }
130        Ok(Self { bytes })
131    }
132
133    /// Return the canonical encoded bytes.
134    #[inline]
135    #[must_use]
136    pub const fn as_bytes(self) -> &'a [u8] {
137        self.bytes
138    }
139}
140
141impl AsRef<[u8]> for EncodedTextKey<'_> {
142    fn as_ref(&self) -> &[u8] {
143        self.bytes
144    }
145}
146
147#[cfg(feature = "alloc")]
148use alloc::vec::Vec;
149
150/// An owned canonical SACP-CBOR/1 data item.
151///
152/// This type is useful for durable storage of canonical CBOR (e.g., protocol state).
153#[cfg(feature = "alloc")]
154#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
155#[derive(Debug, Clone, PartialEq, Eq)]
156pub struct CanonicalCbor {
157    bytes: Vec<u8>,
158}
159
160#[cfg(feature = "alloc")]
161impl CanonicalCbor {
162    #[inline]
163    pub(crate) const fn new_unchecked(bytes: Vec<u8>) -> Self {
164        Self { bytes }
165    }
166
167    /// Compare canonical bytes for equality.
168    #[inline]
169    #[must_use]
170    pub fn bytes_eq(&self, other: &Self) -> bool {
171        self.bytes == other.bytes
172    }
173
174    /// Validate and copy `bytes` into an owned canonical representation.
175    ///
176    /// # Errors
177    ///
178    /// Returns an error if `bytes` are not a canonical SACP-CBOR/1 data item.
179    pub fn from_slice(bytes: &[u8], limits: DecodeLimits) -> Result<Self, CborError> {
180        let canon = crate::validate_canonical(bytes, limits)?;
181        canon.to_owned()
182    }
183
184    /// Validate and wrap an owned canonical CBOR buffer without copying.
185    ///
186    /// # Errors
187    ///
188    /// Returns an error if `bytes` are not a canonical SACP-CBOR/1 data item.
189    pub fn from_vec(bytes: Vec<u8>, limits: DecodeLimits) -> Result<Self, CborError> {
190        crate::validate_canonical(&bytes, limits)?;
191        Ok(Self { bytes })
192    }
193
194    /// Validate and wrap an owned canonical CBOR buffer using default limits.
195    ///
196    /// # Errors
197    ///
198    /// Returns an error if `bytes` are not a canonical SACP-CBOR/1 data item.
199    pub fn from_vec_default_limits(bytes: Vec<u8>) -> Result<Self, CborError> {
200        let limits = DecodeLimits::for_bytes(bytes.len());
201        Self::from_vec(bytes, limits)
202    }
203
204    /// Borrow the canonical bytes.
205    #[inline]
206    #[must_use]
207    pub fn as_bytes(&self) -> &[u8] {
208        &self.bytes
209    }
210
211    /// Borrow the canonical bytes as a validated reference wrapper.
212    #[inline]
213    #[must_use]
214    pub fn as_ref(&self) -> CanonicalCborRef<'_> {
215        CanonicalCborRef::new(self.as_bytes())
216    }
217
218    /// Consume and return the canonical bytes.
219    #[inline]
220    #[must_use]
221    pub fn into_bytes(self) -> Vec<u8> {
222        self.bytes
223    }
224
225    /// Compute the SHA-256 digest of the canonical bytes.
226    #[cfg(feature = "sha2")]
227    #[cfg_attr(docsrs, doc(cfg(feature = "sha2")))]
228    #[must_use]
229    pub fn sha256(&self) -> [u8; 32] {
230        CanonicalCborRef::new(&self.bytes).sha256()
231    }
232}
233
234#[cfg(feature = "alloc")]
235impl AsRef<[u8]> for CanonicalCbor {
236    fn as_ref(&self) -> &[u8] {
237        &self.bytes
238    }
239}
240
241#[cfg(feature = "serde")]
242mod serde_impls {
243    use super::{CanonicalCbor, CanonicalCborRef};
244    use crate::{validate_canonical, DecodeLimits};
245    use serde::de::{Error as DeError, Visitor};
246    use serde::{Deserialize, Deserializer, Serialize, Serializer};
247
248    impl Serialize for CanonicalCborRef<'_> {
249        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
250            serializer.serialize_bytes(self.as_bytes())
251        }
252    }
253
254    #[cfg(feature = "alloc")]
255    impl Serialize for CanonicalCbor {
256        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
257            serializer.serialize_bytes(self.as_bytes())
258        }
259    }
260
261    /// Deserializes from a CBOR byte string (or any serde bytes source) into an owned canonical
262    /// wrapper. Validation uses `DecodeLimits::for_bytes(len)`.
263    #[cfg(feature = "alloc")]
264    impl<'de> Deserialize<'de> for CanonicalCbor {
265        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
266            struct V;
267
268            impl<'de> Visitor<'de> for V {
269                type Value = CanonicalCbor;
270
271                fn expecting(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
272                    write!(f, "canonical CBOR bytes")
273                }
274
275                fn visit_byte_buf<E: DeError>(
276                    self,
277                    v: alloc::vec::Vec<u8>,
278                ) -> Result<Self::Value, E> {
279                    let limits = DecodeLimits::for_bytes(v.len());
280                    validate_canonical(&v, limits).map_err(E::custom)?;
281                    Ok(CanonicalCbor::new_unchecked(v))
282                }
283
284                fn visit_borrowed_bytes<E: DeError>(self, v: &'de [u8]) -> Result<Self::Value, E> {
285                    let limits = DecodeLimits::for_bytes(v.len());
286                    validate_canonical(v, limits).map_err(E::custom)?;
287                    let out = crate::alloc_util::try_vec_from_slice(v, 0).map_err(E::custom)?;
288                    Ok(CanonicalCbor::new_unchecked(out))
289                }
290            }
291
292            deserializer.deserialize_bytes(V)
293        }
294    }
295}