Skip to main content

concinnity_core/blob/
frame.rs

1// Length-delimited postcard decoding.
2//
3// Every decode the runtime performs on blob bytes reads a frame whose length
4// the container already recorded: the header's `meta_len` for the metadata
5// block, the length prefix serde_bytes writes on a def's `args_bytes`, the same
6// on a resource record's `data_bytes`. postcard is positional and stops as soon
7// as the target type is satisfied, so a frame holding more bytes than the type
8// reads decodes without complaint. Requiring the frame to be consumed exactly
9// turns a type that lost a field since the blob was written into a load error
10// instead of a silent partial decode.
11
12use serde::Deserialize;
13use thiserror::Error;
14
15/// Why a length-delimited postcard frame did not decode.
16#[derive(Debug, Clone, PartialEq, Eq, Error)]
17pub enum FrameError {
18    /// postcard rejected the bytes.
19    #[error("postcard decode failed: {0}")]
20    Decode(postcard::Error),
21    /// The value decoded without reaching the end of its frame, leaving this
22    /// many bytes the type never read.
23    #[error("frame has {0} trailing bytes")]
24    Trailing(usize),
25}
26
27/// Decode `bytes` as a postcard frame of `T`, requiring `T` to consume every
28/// byte of it.
29pub fn decode_exact<'a, T: Deserialize<'a>>(bytes: &'a [u8]) -> Result<T, FrameError> {
30    let (value, rest) = postcard::take_from_bytes(bytes).map_err(FrameError::Decode)?;
31    match rest.len() {
32        0 => Ok(value),
33        n => Err(FrameError::Trailing(n)),
34    }
35}
36
37#[cfg(test)]
38mod tests {
39    use super::*;
40    use alloc::vec::Vec;
41    use serde::Serialize;
42
43    #[derive(Debug, PartialEq, Serialize, Deserialize)]
44    struct Two {
45        a: f32,
46        b: f32,
47    }
48
49    #[derive(Debug, PartialEq, Serialize, Deserialize)]
50    struct Three {
51        a: f32,
52        b: f32,
53        c: f32,
54    }
55
56    fn encode<T: Serialize>(value: &T) -> Vec<u8> {
57        postcard::to_allocvec(value).expect("serialize")
58    }
59
60    #[test]
61    fn an_exact_frame_decodes() {
62        let bytes = encode(&Two { a: 1.0, b: 2.0 });
63        assert_eq!(decode_exact::<Two>(&bytes), Ok(Two { a: 1.0, b: 2.0 }));
64    }
65
66    // The case plain `from_bytes` accepts: the frame was written by a schema
67    // with a field this build no longer has, so the tail goes unread.
68    #[test]
69    fn a_frame_with_a_dropped_field_is_rejected() {
70        let bytes = encode(&Three {
71            a: 1.0,
72            b: 2.0,
73            c: 3.0,
74        });
75        assert!(postcard::from_bytes::<Two>(&bytes).is_ok());
76        assert_eq!(decode_exact::<Two>(&bytes), Err(FrameError::Trailing(4)));
77    }
78
79    // The other direction already fails inside postcard, since the frame ends
80    // before the added field.
81    #[test]
82    fn a_frame_missing_an_added_field_is_rejected() {
83        let bytes = encode(&Two { a: 1.0, b: 2.0 });
84        assert_eq!(
85            decode_exact::<Three>(&bytes),
86            Err(FrameError::Decode(
87                postcard::Error::DeserializeUnexpectedEnd
88            ))
89        );
90    }
91
92    #[test]
93    fn an_empty_frame_is_rejected() {
94        assert!(decode_exact::<Two>(&[]).is_err());
95    }
96}