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