Skip to main content

airbender_codec/
lib.rs

1#![doc = include_str!("../README.md")]
2#![no_std]
3
4extern crate alloc;
5
6use airbender_core::wire::FramedRead;
7use alloc::vec::Vec;
8use core::fmt;
9
10#[cfg(test)]
11extern crate std;
12
13/// Stable codec version for host/guest communication.
14pub const AIRBENDER_CODEC_V0: u32 = 0;
15
16/// A stable, versioned serializer used by Airbender host and guest programs.
17pub trait AirbenderCodec {
18    /// Version identifier baked into manifests and tooling.
19    const VERSION: u32;
20
21    /// Serialize a value into a byte payload.
22    fn encode<T: serde::Serialize>(value: &T) -> Result<Vec<u8>, CodecError>;
23
24    /// Deserialize a value from a byte payload.
25    fn decode<T: serde::de::DeserializeOwned>(bytes: &[u8]) -> Result<T, CodecError>;
26}
27
28/// Initial codec based on `bincode` v2 with a fixed configuration.
29pub struct AirbenderCodecV0;
30
31impl AirbenderCodec for AirbenderCodecV0 {
32    const VERSION: u32 = AIRBENDER_CODEC_V0;
33
34    fn encode<T: serde::Serialize>(value: &T) -> Result<Vec<u8>, CodecError> {
35        bincode::serde::encode_to_vec(value, bincode::config::standard())
36            .map_err(CodecError::Encode)
37    }
38
39    fn decode<T: serde::de::DeserializeOwned>(bytes: &[u8]) -> Result<T, CodecError> {
40        let (decoded, read_len) =
41            bincode::serde::decode_from_slice(bytes, bincode::config::standard())
42                .map_err(CodecError::Decode)?;
43        if read_len != bytes.len() {
44            return Err(CodecError::TrailingBytes {
45                expected: bytes.len(),
46                read: read_len,
47            });
48        }
49        Ok(decoded)
50    }
51}
52
53/// Bridges a [`FramedRead`] source (owned by `airbender-core`, bincode-free) to
54/// [`bincode`]'s reader trait, so the framing layer stays decoupled from the
55/// serializer. Maps frame exhaustion to bincode's `UnexpectedEnd`.
56struct FramedBincodeReader<'a, R: FramedRead>(&'a mut R);
57
58impl<R: FramedRead> bincode::de::read::Reader for FramedBincodeReader<'_, R> {
59    fn read(&mut self, bytes: &mut [u8]) -> Result<(), bincode::error::DecodeError> {
60        self.0
61            .read(bytes)
62            .map_err(|end| bincode::error::DecodeError::UnexpectedEnd {
63                additional: end.shortfall,
64            })
65    }
66}
67
68impl AirbenderCodecV0 {
69    /// Decode a value from a streaming [`FramedRead`] source using the same
70    /// configuration as [`AirbenderCodec::decode`].
71    ///
72    /// Unlike the slice-based `decode`, the end-of-input check is left to the
73    /// caller: a reader knows the frame bounds, bincode does not. Callers that
74    /// need the codec's trailing-byte strictness should verify the reader is
75    /// fully consumed and raise [`CodecError::TrailingBytes`] themselves (see
76    /// `airbender_core::wire::FramedReader::remaining`).
77    ///
78    /// For `DeserializeOwned` types this consumes exactly the same bytes with
79    /// the same validation as `decode`. One failure-mode difference: a
80    /// `deserialize_bytes` visitor (e.g. `serde_bytes`) is served zero-copy by
81    /// the slice path but goes through `Vec<u8>::decode` here, which allocates
82    /// the claimed length up front — so a bogus inner length fails as an
83    /// allocation abort rather than a clean `DecodeError`. Ordinary `Vec<u8>`
84    /// and `String` fields are unaffected (both paths allocate up front).
85    pub fn decode_from_reader<T, R>(reader: &mut R) -> Result<T, CodecError>
86    where
87        T: serde::de::DeserializeOwned,
88        R: FramedRead,
89    {
90        bincode::serde::decode_from_reader(FramedBincodeReader(reader), bincode::config::standard())
91            .map_err(CodecError::Decode)
92    }
93}
94
95#[derive(Debug)]
96pub enum CodecError {
97    Encode(bincode::error::EncodeError),
98    Decode(bincode::error::DecodeError),
99    TrailingBytes { expected: usize, read: usize },
100}
101
102impl fmt::Display for CodecError {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        match self {
105            CodecError::Encode(_) => f.write_str("failed to encode value"),
106            CodecError::Decode(_) => f.write_str("failed to decode value"),
107            CodecError::TrailingBytes { expected, read } => {
108                write!(f, "decoded {read} bytes but expected {expected}")
109            }
110        }
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117    use alloc::vec;
118
119    #[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
120    struct Sample {
121        value: u32,
122        payload: alloc::vec::Vec<u8>,
123    }
124
125    #[test]
126    fn codec_roundtrip() {
127        let sample = Sample {
128            value: 42,
129            payload: vec![1u8, 2, 3, 4, 5],
130        };
131        let encoded = AirbenderCodecV0::encode(&sample).expect("encode");
132        let decoded: Sample = AirbenderCodecV0::decode(&encoded).expect("decode");
133        assert_eq!(decoded, sample);
134    }
135}