Skip to main content

airbender_guest/
input.rs

1//! Guest input helpers backed by the Airbender codec.
2
3use crate::transport::Transport;
4use airbender_codec::{AirbenderCodecV0, CodecError};
5use airbender_core::wire::FramedReader;
6use core::fmt;
7
8/// Errors that can occur when decoding inputs on the guest.
9#[derive(Debug)]
10pub enum GuestError {
11    Codec(CodecError),
12    UnsupportedTarget,
13}
14
15impl From<CodecError> for GuestError {
16    fn from(err: CodecError) -> Self {
17        GuestError::Codec(err)
18    }
19}
20
21impl fmt::Display for GuestError {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        match self {
24            GuestError::Codec(err) => write!(f, "{err}"),
25            GuestError::UnsupportedTarget => {
26                f.write_str("csr transport is only available on riscv32")
27            }
28        }
29    }
30}
31
32/// Read a single value from the CSR-based transport.
33pub fn read<T: serde::de::DeserializeOwned>() -> Result<T, GuestError> {
34    #[cfg(target_arch = "riscv32")]
35    {
36        let mut transport = crate::transport::CsrTransport;
37        read_with(&mut transport)
38    }
39    #[cfg(not(target_arch = "riscv32"))]
40    {
41        Err(GuestError::UnsupportedTarget)
42    }
43}
44
45/// Read a single value using an explicit transport.
46///
47/// Decodes straight from the framed word transport without first buffering the
48/// serialized blob into a `Vec<u8>`, so peak memory is the decoded value alone
49/// (~1x) rather than blob-plus-value (~2x).
50///
51/// For any input the buffered path could allocate, the accept/reject set is
52/// identical: same bincode config, and a value that does not consume the whole
53/// frame is still a [`CodecError::TrailingBytes`]. Streaming additionally
54/// succeeds on inputs so large the buffered path's up-front `Vec::with_capacity`
55/// would fail — which is the point of the change.
56///
57/// Like the buffered path, the whole frame is consumed from `transport` whether
58/// the decode succeeds or fails, so a subsequent `read_with` stays aligned.
59pub fn read_with<T: serde::de::DeserializeOwned>(
60    transport: &mut impl Transport,
61) -> Result<T, GuestError> {
62    let mut reader = FramedReader::new(|| transport.read_word());
63    let result = AirbenderCodecV0::decode_from_reader(&mut reader);
64    // Drain any words the decoder left behind (on error, or trailing bytes)
65    // before returning, so the transport is positioned at the next frame.
66    reader.discard_rest_of_frame();
67    let value = result.map_err(GuestError::Codec)?;
68    let remaining = reader.remaining();
69    if remaining != 0 {
70        let expected = reader.payload_len();
71        return Err(GuestError::Codec(CodecError::TrailingBytes {
72            expected,
73            read: expected - remaining,
74        }));
75    }
76    Ok(value)
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82    use crate::transport::MockTransport;
83    use airbender_codec::AirbenderCodec; // for `AirbenderCodecV0::encode` in tests
84    use airbender_core::wire::frame_words_from_bytes;
85    use alloc::vec;
86
87    #[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
88    struct Payload {
89        counter: u32,
90        bytes: alloc::vec::Vec<u8>,
91    }
92
93    #[test]
94    fn reads_value_from_transport() {
95        let payload = Payload {
96            counter: 7,
97            bytes: vec![10u8, 20, 30],
98        };
99        let encoded = AirbenderCodecV0::encode(&payload).expect("encode");
100        let words = frame_words_from_bytes(&encoded).expect("frame words");
101        let mut transport = MockTransport::new(words);
102        let decoded: Payload = read_with(&mut transport).expect("read");
103        assert_eq!(decoded, payload);
104    }
105
106    #[test]
107    fn reads_large_multiword_payload() {
108        // Many words plus a big Vec<u8> bincode reads in one chunk — the shape
109        // of a real input, and a non-multiple-of-4 length exercises padding.
110        let payload = Payload {
111            counter: u32::MAX,
112            bytes: (0..10_000u32).map(|i| (i * 31 + 7) as u8).collect(),
113        };
114        let encoded = AirbenderCodecV0::encode(&payload).expect("encode");
115        let words = frame_words_from_bytes(&encoded).expect("frame words");
116        let mut transport = MockTransport::new(words);
117        let decoded: Payload = read_with(&mut transport).expect("read");
118        assert_eq!(decoded, payload);
119    }
120
121    #[test]
122    fn rejects_trailing_bytes_like_the_buffered_codec() {
123        // A frame carrying more bytes than the value consumes must fail with
124        // `TrailingBytes`, matching the slice-based `AirbenderCodecV0::decode`.
125        let payload = Payload {
126            counter: 1,
127            bytes: vec![9u8],
128        };
129        let mut encoded = AirbenderCodecV0::encode(&payload).expect("encode");
130        encoded.extend_from_slice(&[0u8; 5]); // trailing bytes
131        let words = frame_words_from_bytes(&encoded).expect("frame words");
132        let mut transport = MockTransport::new(words);
133        let err = read_with::<Payload>(&mut transport).expect_err("must reject trailing bytes");
134        assert!(matches!(
135            err,
136            GuestError::Codec(CodecError::TrailingBytes { .. })
137        ));
138    }
139
140    #[test]
141    fn rejected_frame_does_not_desync_the_next_frame() {
142        // A frame that fails (trailing bytes) must still be fully drained, so a
143        // second frame on the same transport decodes correctly afterwards.
144        let bad = Payload {
145            counter: 1,
146            bytes: vec![9u8, 8, 7],
147        };
148        let good = Payload {
149            counter: 42,
150            bytes: vec![1u8, 2, 3, 4, 5],
151        };
152
153        let mut bad_encoded = AirbenderCodecV0::encode(&bad).expect("encode");
154        bad_encoded.extend_from_slice(&[0u8; 6]); // trailing bytes -> rejected frame
155        let good_encoded = AirbenderCodecV0::encode(&good).expect("encode");
156
157        let mut words = frame_words_from_bytes(&bad_encoded).expect("frame bad");
158        words.extend(frame_words_from_bytes(&good_encoded).expect("frame good"));
159        let mut transport = MockTransport::new(words);
160
161        let err = read_with::<Payload>(&mut transport).expect_err("first frame rejected");
162        assert!(matches!(
163            err,
164            GuestError::Codec(CodecError::TrailingBytes { .. })
165        ));
166        // The second frame is intact only if the first was fully consumed.
167        let decoded: Payload = read_with(&mut transport).expect("second frame decodes");
168        assert_eq!(decoded, good);
169    }
170
171    #[test]
172    fn mid_decode_failure_does_not_desync_the_next_frame() {
173        // Exercises the `decode == Err -> discard_rest_of_frame` branch (the
174        // trailing-bytes test above hits the `decode == Ok` branch): a frame
175        // that fails *during* decode, with words still unread, must still be
176        // drained so the following frame decodes.
177        #[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
178        struct HasBool {
179            flag: bool,
180            rest: u32,
181        }
182
183        // 0x02 is not a valid bool discriminant, so decode fails after one byte,
184        // leaving the rest of this 8-byte (2-word) frame unread.
185        let bad_frame_bytes = [2u8, 0, 0, 0, 0, 0, 0, 0];
186        let good = Payload {
187            counter: 99,
188            bytes: vec![7u8, 7, 7],
189        };
190        let good_encoded = AirbenderCodecV0::encode(&good).expect("encode");
191
192        let mut words = frame_words_from_bytes(&bad_frame_bytes).expect("frame bad");
193        words.extend(frame_words_from_bytes(&good_encoded).expect("frame good"));
194        let mut transport = MockTransport::new(words);
195
196        let err = read_with::<HasBool>(&mut transport).expect_err("decode must fail");
197        assert!(matches!(err, GuestError::Codec(CodecError::Decode(_))));
198        let decoded: Payload = read_with(&mut transport).expect("second frame decodes");
199        assert_eq!(decoded, good);
200    }
201}