Skip to main content

lumen_plugin_abi/
codec.rs

1//! The one bincode call site for both sides of the plugin boundary.
2//!
3//! Mirrors the artifact codec in `lumen_ir::artifact` exactly: plain
4//! `bincode::serialize` to encode, `DefaultOptions` with fixint encoding and
5//! a size limit to decode. `DefaultOptions` alone defaults to *varint*, which
6//! would mis-decode fixint bytes; the limit keeps a corrupt length prefix
7//! from triggering an enormous pre-allocation.
8
9use bincode::Options;
10use serde::Serialize;
11use serde::de::DeserializeOwned;
12
13/// Upper bound on any single payload crossing the boundary. Matches the
14/// artifact body cap in `lumen_ir::artifact`.
15pub const MAX_PAYLOAD: u64 = 512 * 1024 * 1024;
16
17/// Encode a payload for the boundary.
18pub fn encode<T: Serialize>(value: &T) -> Result<Vec<u8>, String> {
19    bincode::serialize(value).map_err(|e| e.to_string())
20}
21
22/// Decode a payload from the boundary.
23pub fn decode<T: DeserializeOwned>(bytes: &[u8]) -> Result<T, String> {
24    bincode::DefaultOptions::new()
25        .with_fixint_encoding()
26        .with_limit(MAX_PAYLOAD)
27        .deserialize(bytes)
28        .map_err(|e| e.to_string())
29}
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34
35    #[test]
36    fn round_trips() {
37        let v: Vec<String> = vec!["a".into(), "bc".into()];
38        let bytes = encode(&v).unwrap();
39        let back: Vec<String> = decode(&bytes).unwrap();
40        assert_eq!(v, back);
41    }
42
43    #[test]
44    fn truncated_is_an_error_not_a_panic() {
45        let bytes = encode(&vec![1u64, 2, 3]).unwrap();
46        let err = decode::<Vec<u64>>(&bytes[..bytes.len() - 3]).unwrap_err();
47        assert!(!err.is_empty());
48    }
49
50    #[test]
51    fn oversized_length_prefix_is_rejected() {
52        // A u64 length prefix claiming more elements than the cap allows.
53        let bytes = (u64::MAX).to_le_bytes().to_vec();
54        assert!(decode::<Vec<u8>>(&bytes).is_err());
55    }
56}