Skip to main content

holger_plugin_abi/
codec.rs

1//! The byte codec both surfaces share. **One writer, one reader** — the host
2//! links these functions and so does every guest, so a field cannot be encoded
3//! one way and decoded another (LAW 5, by construction rather than by a guard
4//! watching two copies agree).
5//!
6//! # Why not JSON
7//!
8//! znippy's `wasm_loader::parse_json_to_row` splits on `,` and `:` *before*
9//! stripping quotes, so a value containing either separator silently corrupts
10//! the row, and it has no null representation at all — an absent column and an
11//! empty one are the same bytes on the wire. Both are properties of the ad-hoc
12//! format, not bugs that can be patched out of the parser.
13//!
14//! This codec is length-prefixed, so a value is copied by length and its
15//! contents are never scanned for structure: `,`, `:`, `"`, a NUL and an
16//! arbitrary UTF-8 sequence all survive a round trip. `Option` carries an
17//! explicit 1-byte tag, so `None` and `Some("")` are distinct on the wire.
18//! `codec_tests.rs` asserts exactly those two properties.
19//!
20//! # Layout
21//!
22//! | type | bytes |
23//! |---|---|
24//! | `u32` / `u16` / `u64` / `i64` | little-endian, fixed width |
25//! | `bool` | 1 byte, `0` or `1` |
26//! | bytes / `String` | `u32` length, then that many bytes |
27//! | `Option<T>` | `0u8`, or `1u8` then `T` |
28//! | `Vec<T>` | `u32` count, then that many `T` |
29//! | `Result<T, String>` | `0u8` then `T`, or `1u8` then the message |
30
31use std::string::ToString;
32
33/// Why a byte string could not be decoded. Every variant names the field being
34/// read, because a decode failure on the host side is otherwise indistinguishable
35/// from a plugin that returned nothing.
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub enum DecodeError {
38    /// Ran off the end of the buffer while reading `field`.
39    Truncated { field: &'static str, need: usize, have: usize },
40    /// A length prefix exceeded what the remaining buffer can hold.
41    LengthOverflow { field: &'static str, len: u64, remaining: usize },
42    /// A tag byte was not one of the values the type defines.
43    BadTag { field: &'static str, tag: u8 },
44    /// A length-prefixed string was not valid UTF-8.
45    NotUtf8 { field: &'static str },
46    /// Bytes were left over after the value was fully decoded — the two sides
47    /// disagree about the shape, which is never benign.
48    TrailingBytes { field: &'static str, left: usize },
49}
50
51impl core::fmt::Display for DecodeError {
52    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
53        match self {
54            DecodeError::Truncated { field, need, have } => write!(
55                f,
56                "ABI decode: truncated reading `{field}` — need {need} bytes, {have} left"
57            ),
58            DecodeError::LengthOverflow { field, len, remaining } => write!(
59                f,
60                "ABI decode: `{field}` declares {len} bytes but only {remaining} remain"
61            ),
62            DecodeError::BadTag { field, tag } => {
63                write!(f, "ABI decode: `{field}` carries invalid tag byte {tag}")
64            }
65            DecodeError::NotUtf8 { field } => write!(f, "ABI decode: `{field}` is not UTF-8"),
66            DecodeError::TrailingBytes { field, left } => write!(
67                f,
68                "ABI decode: {left} trailing byte(s) after `{field}` — encoder and decoder \
69                 disagree about the shape"
70            ),
71        }
72    }
73}
74
75/// Append-only byte writer.
76#[derive(Default)]
77pub struct Writer {
78    buf: Vec<u8>,
79}
80
81impl Writer {
82    pub fn new() -> Self {
83        Self { buf: Vec::new() }
84    }
85
86    pub fn finish(self) -> Vec<u8> {
87        self.buf
88    }
89
90    pub fn u8(&mut self, v: u8) -> &mut Self {
91        self.buf.push(v);
92        self
93    }
94
95    pub fn u16(&mut self, v: u16) -> &mut Self {
96        self.buf.extend_from_slice(&v.to_le_bytes());
97        self
98    }
99
100    pub fn u32(&mut self, v: u32) -> &mut Self {
101        self.buf.extend_from_slice(&v.to_le_bytes());
102        self
103    }
104
105    pub fn u64(&mut self, v: u64) -> &mut Self {
106        self.buf.extend_from_slice(&v.to_le_bytes());
107        self
108    }
109
110    pub fn i64(&mut self, v: i64) -> &mut Self {
111        self.buf.extend_from_slice(&v.to_le_bytes());
112        self
113    }
114
115    pub fn bool(&mut self, v: bool) -> &mut Self {
116        self.buf.push(u8::from(v));
117        self
118    }
119
120    /// Length-prefixed bytes. The payload is copied verbatim — no escaping, no
121    /// separator scan, so any byte sequence survives.
122    pub fn bytes(&mut self, v: &[u8]) -> &mut Self {
123        self.u32(v.len() as u32);
124        self.buf.extend_from_slice(v);
125        self
126    }
127
128    pub fn str(&mut self, v: &str) -> &mut Self {
129        self.bytes(v.as_bytes())
130    }
131
132    /// `None` and `Some("")` differ by the tag byte, not by length.
133    pub fn opt_str(&mut self, v: Option<&str>) -> &mut Self {
134        match v {
135            None => self.u8(0),
136            Some(s) => self.u8(1).str(s),
137        }
138    }
139
140    pub fn opt_bytes(&mut self, v: Option<&[u8]>) -> &mut Self {
141        match v {
142            None => self.u8(0),
143            Some(b) => self.u8(1).bytes(b),
144        }
145    }
146}
147
148/// Cursor over a byte string.
149pub struct Reader<'a> {
150    buf: &'a [u8],
151    pos: usize,
152}
153
154impl<'a> Reader<'a> {
155    pub fn new(buf: &'a [u8]) -> Self {
156        Self { buf, pos: 0 }
157    }
158
159    pub fn remaining(&self) -> usize {
160        self.buf.len() - self.pos
161    }
162
163    /// Assert the value consumed the whole buffer. Called at the end of every
164    /// top-level decode: a decoder that stops early is reading a *different*
165    /// shape than the encoder wrote, and silently ignoring the tail is how an
166    /// ABI skew turns into wrong values instead of an error.
167    pub fn expect_end(&self, field: &'static str) -> Result<(), DecodeError> {
168        if self.remaining() == 0 {
169            Ok(())
170        } else {
171            Err(DecodeError::TrailingBytes { field, left: self.remaining() })
172        }
173    }
174
175    fn take(&mut self, n: usize, field: &'static str) -> Result<&'a [u8], DecodeError> {
176        if self.remaining() < n {
177            return Err(DecodeError::Truncated { field, need: n, have: self.remaining() });
178        }
179        let out = &self.buf[self.pos..self.pos + n];
180        self.pos += n;
181        Ok(out)
182    }
183
184    pub fn u8(&mut self, field: &'static str) -> Result<u8, DecodeError> {
185        Ok(self.take(1, field)?[0])
186    }
187
188    pub fn u16(&mut self, field: &'static str) -> Result<u16, DecodeError> {
189        let b = self.take(2, field)?;
190        Ok(u16::from_le_bytes([b[0], b[1]]))
191    }
192
193    pub fn u32(&mut self, field: &'static str) -> Result<u32, DecodeError> {
194        let b = self.take(4, field)?;
195        Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
196    }
197
198    pub fn u64(&mut self, field: &'static str) -> Result<u64, DecodeError> {
199        let b = self.take(8, field)?;
200        let mut a = [0u8; 8];
201        a.copy_from_slice(b);
202        Ok(u64::from_le_bytes(a))
203    }
204
205    pub fn i64(&mut self, field: &'static str) -> Result<i64, DecodeError> {
206        Ok(self.u64(field)? as i64)
207    }
208
209    pub fn bool(&mut self, field: &'static str) -> Result<bool, DecodeError> {
210        match self.u8(field)? {
211            0 => Ok(false),
212            1 => Ok(true),
213            tag => Err(DecodeError::BadTag { field, tag }),
214        }
215    }
216
217    pub fn bytes(&mut self, field: &'static str) -> Result<Vec<u8>, DecodeError> {
218        let len = self.u32(field)? as usize;
219        if len > self.remaining() {
220            return Err(DecodeError::LengthOverflow {
221                field,
222                len: len as u64,
223                remaining: self.remaining(),
224            });
225        }
226        Ok(self.take(len, field)?.to_vec())
227    }
228
229    pub fn str(&mut self, field: &'static str) -> Result<String, DecodeError> {
230        let b = self.bytes(field)?;
231        String::from_utf8(b).map_err(|_| DecodeError::NotUtf8 { field })
232    }
233
234    pub fn opt_str(&mut self, field: &'static str) -> Result<Option<String>, DecodeError> {
235        match self.u8(field)? {
236            0 => Ok(None),
237            1 => Ok(Some(self.str(field)?)),
238            tag => Err(DecodeError::BadTag { field, tag }),
239        }
240    }
241
242    pub fn opt_bytes(&mut self, field: &'static str) -> Result<Option<Vec<u8>>, DecodeError> {
243        match self.u8(field)? {
244            0 => Ok(None),
245            1 => Ok(Some(self.bytes(field)?)),
246            tag => Err(DecodeError::BadTag { field, tag }),
247        }
248    }
249
250    /// Read a `u32` count and check it against the bytes actually left, so a
251    /// corrupt count cannot make the caller pre-allocate an absurd `Vec`.
252    pub fn count(&mut self, field: &'static str) -> Result<usize, DecodeError> {
253        let n = self.u32(field)? as usize;
254        if n > self.remaining() {
255            return Err(DecodeError::LengthOverflow {
256                field,
257                len: n as u64,
258                remaining: self.remaining(),
259            });
260        }
261        Ok(n)
262    }
263}
264
265/// Encode a `Result<T, String>`: tag `0` = ok, tag `1` = the error message.
266pub fn write_result<T>(w: &mut Writer, v: &Result<T, String>, ok: impl FnOnce(&mut Writer, &T)) {
267    match v {
268        Ok(t) => {
269            w.u8(0);
270            ok(w, t);
271        }
272        Err(e) => {
273            w.u8(1);
274            w.str(e);
275        }
276    }
277}
278
279/// Decode a `Result<T, String>` written by [`write_result`].
280pub fn read_result<T>(
281    r: &mut Reader<'_>,
282    field: &'static str,
283    ok: impl FnOnce(&mut Reader<'_>) -> Result<T, DecodeError>,
284) -> Result<Result<T, String>, DecodeError> {
285    match r.u8(field)? {
286        0 => Ok(Ok(ok(r)?)),
287        1 => Ok(Err(r.str(field)?)),
288        tag => Err(DecodeError::BadTag { field, tag }),
289    }
290}
291
292impl DecodeError {
293    /// Render for a `Result<_, String>` boundary (the ABI carries messages, not types).
294    pub fn message(&self) -> String {
295        self.to_string()
296    }
297}