Skip to main content

byteflow/bytecode/
format.rs

1use super::chunk::{Chunk, FunctionDef, ABI_VERSION, MAGIC};
2use super::instruction::Instruction;
3use super::opcode::Opcode;
4use super::value::Value;
5use super::verify::TrustLevel;
6
7const MAX_NAME: u32 = 64 * 1024;
8const MAX_ITEMS: u32 = 1_000_000;
9/// Max length for [`Value::Str`] / [`Value::Bytes`] constant payloads.
10const MAX_BLOB: u32 = 1_048_576;
11/// Nested `Message.payload` depth (defense against hostile `.bf` files).
12const MAX_VALUE_DEPTH: u32 = 16;
13
14/// Why a `.bf` buffer failed to decode.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum FormatError {
17    BadMagic { found: [u8; 4] },
18    UnsupportedAbi(u32),
19    Truncated,
20    LimitExceeded { what: &'static str, got: u32 },
21    BadUtf8,
22    UnknownValueTag(u8),
23    UnknownOpcode { at: usize, byte: u8 },
24    ForbiddenConstant(super::verify::ConstantKind),
25    ValueTooNested,
26}
27
28impl std::fmt::Display for FormatError {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        match self {
31            FormatError::BadMagic { found } => {
32                write!(f, "bad magic {found:?}, expected {:?}", MAGIC)
33            }
34            FormatError::UnsupportedAbi(v) => write!(f, "unsupported ABI version {v}"),
35            FormatError::Truncated => write!(f, "truncated .bf module"),
36            FormatError::LimitExceeded { what, got } => {
37                write!(f, "{what} count {got} exceeds decoder limit")
38            }
39            FormatError::BadUtf8 => write!(f, "name is not valid UTF-8"),
40            FormatError::UnknownValueTag(t) => write!(f, "unknown value tag 0x{t:02X}"),
41            FormatError::UnknownOpcode { at, byte } => {
42                write!(f, "unknown opcode 0x{byte:02X} at instruction {at}")
43            }
44            FormatError::ForbiddenConstant(kind) => {
45                write!(f, "untrusted module must not embed {kind} in the constant pool")
46            }
47            FormatError::ValueTooNested => write!(f, "value nesting exceeds decoder limit"),
48        }
49    }
50}
51
52impl std::error::Error for FormatError {}
53
54/// Encode `chunk` as a BFV0 module (little-endian). Pure data — no I/O.
55pub fn encode(chunk: &Chunk) -> Vec<u8> {
56    let mut out = Vec::new();
57    out.extend_from_slice(&MAGIC);
58    out.extend_from_slice(&ABI_VERSION.to_le_bytes());
59    write_string(&mut out, &chunk.name);
60    out.extend_from_slice(&(chunk.constants.len() as u32).to_le_bytes());
61    for value in &chunk.constants {
62        write_value(&mut out, value);
63    }
64    out.extend_from_slice(&(chunk.functions.len() as u32).to_le_bytes());
65    for def in &chunk.functions {
66        write_string(&mut out, &def.name);
67        out.extend_from_slice(&def.entry.to_le_bytes());
68        out.push(def.arity);
69        out.push(def.num_registers);
70    }
71    out.extend_from_slice(&(chunk.code.len() as u32).to_le_bytes());
72    for instr in &chunk.code {
73        out.push(instr.op.as_u8());
74        out.push(instr.a);
75        out.push(instr.b);
76        out.push(instr.c);
77        out.extend_from_slice(&instr.imm.to_le_bytes());
78    }
79    out
80}
81
82/// Decode a BFV0 module. Untrusted by default: Cap / Pid / Message constants
83/// are rejected (see [`decode_with`]).
84pub fn decode(bytes: &[u8]) -> Result<Chunk, FormatError> {
85    decode_with(bytes, TrustLevel::Untrusted)
86}
87
88/// Decode with an explicit trust level. Trusted decode is for host-packed
89/// modules that may embed authority tags in the constant pool.
90pub fn decode_with(bytes: &[u8], trust: TrustLevel) -> Result<Chunk, FormatError> {
91    let mut r = Reader { data: bytes, pos: 0 };
92    let magic = r.read_array::<4>()?;
93    if magic != MAGIC {
94        return Err(FormatError::BadMagic { found: magic });
95    }
96    let abi = r.read_u32()?;
97    if abi != ABI_VERSION {
98        return Err(FormatError::UnsupportedAbi(abi));
99    }
100    let name = r.read_string()?;
101    let n_const = r.read_count("constants")?;
102    let mut constants = Vec::with_capacity(n_const as usize);
103    for _ in 0..n_const {
104        constants.push(r.read_value(trust, 0)?);
105    }
106    let n_fn = r.read_count("functions")?;
107    let mut functions = Vec::with_capacity(n_fn as usize);
108    for _ in 0..n_fn {
109        functions.push(FunctionDef {
110            name: r.read_string()?,
111            entry: r.read_u32()?,
112            arity: r.read_u8()?,
113            num_registers: r.read_u8()?,
114        });
115    }
116    let n_code = r.read_count("code")?;
117    let mut code = Vec::with_capacity(n_code as usize);
118    for at in 0..n_code as usize {
119        let byte = r.read_u8()?;
120        let op = Opcode::from_u8(byte).ok_or(FormatError::UnknownOpcode { at, byte })?;
121        code.push(Instruction {
122            op,
123            a: r.read_u8()?,
124            b: r.read_u8()?,
125            c: r.read_u8()?,
126            imm: r.read_i32()?,
127        });
128    }
129    Ok(Chunk {
130        name,
131        constants,
132        code,
133        functions,
134    })
135}
136
137fn write_string(out: &mut Vec<u8>, s: &str) {
138    let bytes = s.as_bytes();
139    out.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
140    out.extend_from_slice(bytes);
141}
142
143fn write_value(out: &mut Vec<u8>, value: &Value) {
144    match value {
145        Value::Unit => out.push(0),
146        Value::Bool(b) => {
147            out.push(1);
148            out.push(u8::from(*b));
149        }
150        Value::Int(i) => {
151            out.push(2);
152            out.extend_from_slice(&i.to_le_bytes());
153        }
154        Value::Float(x) => {
155            out.push(3);
156            out.extend_from_slice(&x.to_le_bytes());
157        }
158        Value::Pid(p) => {
159            out.push(4);
160            out.extend_from_slice(&p.to_le_bytes());
161        }
162        Value::Message(m) => {
163            // Tag 5 — ABI v5 layout (reply_cap is 128-bit, payload is nested).
164            out.push(5);
165            out.extend_from_slice(&m.sender.to_le_bytes());
166            out.extend_from_slice(&m.reply_cap.as_u128().to_le_bytes());
167            out.extend_from_slice(&m.request_id.to_le_bytes());
168            out.extend_from_slice(&m.tag.to_le_bytes());
169            write_value(out, m.payload.as_ref());
170        }
171        Value::Cap(c) => {
172            out.push(6);
173            out.extend_from_slice(&c.as_u128().to_le_bytes());
174        }
175        Value::Str(s) => {
176            out.push(7);
177            write_blob(out, s.as_bytes());
178        }
179        Value::Bytes(b) => {
180            out.push(8);
181            write_blob(out, b);
182        }
183    }
184}
185
186fn write_blob(out: &mut Vec<u8>, bytes: &[u8]) {
187    out.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
188    out.extend_from_slice(bytes);
189}
190
191struct Reader<'a> {
192    data: &'a [u8],
193    pos: usize,
194}
195
196impl<'a> Reader<'a> {
197    fn rest(&self) -> usize {
198        self.data.len().saturating_sub(self.pos)
199    }
200
201    fn take(&mut self, n: usize) -> Result<&'a [u8], FormatError> {
202        if self.rest() < n {
203            return Err(FormatError::Truncated);
204        }
205        let slice = &self.data[self.pos..self.pos + n];
206        self.pos += n;
207        Ok(slice)
208    }
209
210    fn read_u8(&mut self) -> Result<u8, FormatError> {
211        Ok(self.take(1)?[0])
212    }
213
214    fn read_array<const N: usize>(&mut self) -> Result<[u8; N], FormatError> {
215        let slice = self.take(N)?;
216        let mut arr = [0u8; N];
217        arr.copy_from_slice(slice);
218        Ok(arr)
219    }
220
221    fn read_u32(&mut self) -> Result<u32, FormatError> {
222        Ok(u32::from_le_bytes(self.read_array()?))
223    }
224
225    fn read_u16(&mut self) -> Result<u16, FormatError> {
226        Ok(u16::from_le_bytes(self.read_array()?))
227    }
228
229    fn read_i32(&mut self) -> Result<i32, FormatError> {
230        Ok(i32::from_le_bytes(self.read_array()?))
231    }
232
233    fn read_i64(&mut self) -> Result<i64, FormatError> {
234        Ok(i64::from_le_bytes(self.read_array()?))
235    }
236
237    fn read_u64(&mut self) -> Result<u64, FormatError> {
238        Ok(u64::from_le_bytes(self.read_array()?))
239    }
240
241    fn read_u128(&mut self) -> Result<u128, FormatError> {
242        Ok(u128::from_le_bytes(self.read_array()?))
243    }
244
245    fn read_f64(&mut self) -> Result<f64, FormatError> {
246        Ok(f64::from_le_bytes(self.read_array()?))
247    }
248
249    fn read_count(&mut self, what: &'static str) -> Result<u32, FormatError> {
250        let n = self.read_u32()?;
251        if n > MAX_ITEMS {
252            return Err(FormatError::LimitExceeded { what, got: n });
253        }
254        Ok(n)
255    }
256
257    fn read_string(&mut self) -> Result<String, FormatError> {
258        let len = self.read_u32()?;
259        if len > MAX_NAME {
260            return Err(FormatError::LimitExceeded {
261                what: "name",
262                got: len,
263            });
264        }
265        let bytes = self.take(len as usize)?;
266        String::from_utf8(bytes.to_vec()).map_err(|_| FormatError::BadUtf8)
267    }
268
269    fn read_value(&mut self, trust: TrustLevel, depth: u32) -> Result<Value, FormatError> {
270        if depth > MAX_VALUE_DEPTH {
271            return Err(FormatError::ValueTooNested);
272        }
273        match self.read_u8()? {
274            0 => Ok(Value::Unit),
275            1 => Ok(Value::Bool(self.read_u8()? != 0)),
276            2 => Ok(Value::Int(self.read_i64()?)),
277            3 => Ok(Value::Float(self.read_f64()?)),
278            4 => {
279                if trust == TrustLevel::Untrusted {
280                    return Err(FormatError::ForbiddenConstant(
281                        super::verify::ConstantKind::ProcessId,
282                    ));
283                }
284                Ok(Value::Pid(self.read_u64()?))
285            }
286            5 => {
287                if trust == TrustLevel::Untrusted {
288                    return Err(FormatError::ForbiddenConstant(
289                        super::verify::ConstantKind::Message,
290                    ));
291                }
292                let sender = self.read_u64()?;
293                let reply_cap = super::cap::CapId::from_raw(self.read_u128()?);
294                let request_id = self.read_u64()?;
295                let tag = self.read_u16()?;
296                let payload = self.read_value(trust, depth + 1)?;
297                Ok(Value::Message(
298                    super::value::Message::new(sender, request_id, tag, payload)
299                        .authenticate(sender, reply_cap),
300                ))
301            }
302            6 => {
303                if trust == TrustLevel::Untrusted {
304                    return Err(FormatError::ForbiddenConstant(
305                        super::verify::ConstantKind::Capability,
306                    ));
307                }
308                Ok(Value::Cap(super::cap::CapId::from_raw(self.read_u128()?)))
309            }
310            7 => {
311                let bytes = self.read_blob("str")?;
312                let s = std::str::from_utf8(bytes).map_err(|_| FormatError::BadUtf8)?;
313                Ok(Value::str(s))
314            }
315            8 => Ok(Value::bytes(self.read_blob("bytes")?)),
316            tag => Err(FormatError::UnknownValueTag(tag)),
317        }
318    }
319
320    fn read_blob(&mut self, what: &'static str) -> Result<&'a [u8], FormatError> {
321        let len = self.read_u32()?;
322        if len > MAX_BLOB {
323            return Err(FormatError::LimitExceeded { what, got: len });
324        }
325        self.take(len as usize)
326    }
327}
328
329#[cfg(test)]
330mod tests {
331    use super::*;
332    use crate::bytecode::builder::ChunkBuilder;
333    use crate::bytecode::opcode::Opcode;
334    use crate::bytecode::verify::{verify, TrustLevel};
335
336
337    #[test]
338    fn roundtrip_preserves_chunk() -> Result<(), FormatError> {
339        let mut b = ChunkBuilder::new("roundtrip");
340        b.begin_function("main", 0, 2);
341        let k = b.const_(Value::Int(9));
342        b.emit_load_const(0, k);
343        b.emit_load_imm(1, 1);
344        b.emit_binop(Opcode::Add, 0, 0, 1);
345        b.emit_return(0);
346        let original = b.finish();
347        let bytes = encode(&original);
348        assert_eq!(&bytes[..4], &MAGIC);
349        let decoded = decode(&bytes)?;
350        assert!(verify(&decoded).is_ok());
351        assert_eq!(decoded.name, original.name);
352        assert_eq!(decoded.constants, original.constants);
353        assert_eq!(decoded.code, original.code);
354        assert_eq!(decoded.functions, original.functions);
355        Ok(())
356    }
357
358    #[test]
359    fn roundtrip_preserves_message_constant() -> Result<(), FormatError> {
360        use super::super::value::Message;
361        let mut b = ChunkBuilder::new("msg-const");
362        b.begin_function("main", 0, 1);
363        let k = b.const_(Value::Message(Message::new(1, 2, 3, 4u64)));
364        b.emit_load_const(0, k);
365        b.emit_return(0);
366        let original = b.finish();
367        let decoded = decode_with(&encode(&original), TrustLevel::Trusted)?;
368        assert_eq!(decoded.constants, original.constants);
369        let got = decoded.constants[0]
370            .as_message()
371            .ok_or(FormatError::Truncated)?;
372        assert_eq!(got.sender, 1);
373        assert_eq!(got.request_id, 2);
374        assert_eq!(got.tag, 3);
375        assert_eq!(got.payload.as_ref(), &Value::Int(4));
376        Ok(())
377    }
378
379    #[test]
380    fn untrusted_decode_rejects_cap_constant() {
381        use crate::bytecode::cap::CapId;
382        let mut b = ChunkBuilder::new("cap-const");
383        b.begin_function("main", 0, 1);
384        let k = b.const_(Value::Cap(CapId::from_raw(1)));
385        b.emit_load_const(0, k);
386        b.emit_return(0);
387        let bytes = encode(&b.finish());
388        assert!(matches!(
389            decode(&bytes),
390            Err(FormatError::ForbiddenConstant(_))
391        ));
392        assert!(decode_with(&bytes, TrustLevel::Trusted).is_ok());
393    }
394
395    #[test]
396    fn roundtrip_preserves_str_and_bytes_constants() -> Result<(), FormatError> {
397        let mut b = ChunkBuilder::new("blob-const");
398        b.begin_function("main", 0, 2);
399        let ks = b.const_(Value::str("olá"));
400        let kb = b.const_(Value::bytes([0u8, 255, 7]));
401        b.emit_load_const(0, ks);
402        b.emit_load_const(1, kb);
403        b.emit_return(0);
404        let original = b.finish();
405        let decoded = decode(&encode(&original))?;
406        assert_eq!(decoded.constants, original.constants);
407        assert_eq!(decoded.constants[0].as_str(), Some("olá"));
408        assert_eq!(decoded.constants[1].as_bytes(), Some(&[0, 255, 7][..]));
409        Ok(())
410    }
411
412    #[test]
413    fn rejects_bad_magic() {
414        assert!(matches!(
415            decode(b"XXXX"),
416            Err(FormatError::BadMagic { .. })
417        ));
418    }
419}