Skip to main content

truthlinked_axiom/
bytecode.rs

1//! Truthlinked Axiom Src Bytecode
2//!
3//! Owns bytecode encoding, decoding, and validation boundaries.
4//! VM and bytecode changes are consensus-sensitive and must remain deterministic across platforms.
5
6use crate::error::AxiomError;
7use alloc::vec::Vec;
8
9/// Magic bytes identifying Axiom bytecode: ASCII "AXIO"
10pub const MAGIC: [u8; 4] = [0x41, 0x58, 0x49, 0x4F];
11pub const VERSION: u8 = 2;
12
13/// Maximum sizes enforced at decode time.
14pub const MAX_CONST_POOL_ENTRIES: usize = 65_535;
15pub const MAX_CONST_ENTRY_BYTES: usize = 65_536; // 64KB per constant
16pub const MAX_CODE_BYTES: usize = 1_048_576; // 1MB bytecode
17
18/// A decoded cell ready for execution.
19pub struct CellBytecode {
20    /// Constant pool: arbitrary byte blobs referenced by index.
21    pub const_pool: Vec<Vec<u8>>,
22    /// Raw encoded instruction stream.
23    pub code: Vec<u8>,
24}
25
26impl CellBytecode {
27    /// Decode from raw bytes. Validates magic, version, and size limits.
28    pub fn decode(raw: &[u8]) -> Result<Self, AxiomError> {
29        let mut pos = 0;
30
31        // Magic
32        if raw.len() < 6 {
33            return Err(AxiomError::InvalidMagic);
34        }
35        if &raw[0..4] != &MAGIC {
36            return Err(AxiomError::InvalidMagic);
37        }
38        pos += 4;
39
40        // Version
41        if raw[pos] != VERSION {
42            return Err(AxiomError::InvalidBytecode);
43        }
44        pos += 1;
45
46        // Reserved byte (must be 0)
47        if raw[pos] != 0 {
48            return Err(AxiomError::InvalidBytecode);
49        }
50        pos += 1;
51
52        // Const pool count (u16 LE)
53        if pos + 2 > raw.len() {
54            return Err(AxiomError::InvalidBytecode);
55        }
56        let pool_count = u16::from_le_bytes([raw[pos], raw[pos + 1]]) as usize;
57        pos += 2;
58        if pool_count > MAX_CONST_POOL_ENTRIES {
59            return Err(AxiomError::InvalidBytecode);
60        }
61
62        // Const pool entries: each prefixed with u32 LE length
63        let mut const_pool = Vec::with_capacity(pool_count);
64        for _ in 0..pool_count {
65            if pos + 4 > raw.len() {
66                return Err(AxiomError::InvalidBytecode);
67            }
68            let entry_len =
69                u32::from_le_bytes([raw[pos], raw[pos + 1], raw[pos + 2], raw[pos + 3]]) as usize;
70            pos += 4;
71            if entry_len > MAX_CONST_ENTRY_BYTES {
72                return Err(AxiomError::InvalidBytecode);
73            }
74            if pos + entry_len > raw.len() {
75                return Err(AxiomError::InvalidBytecode);
76            }
77            const_pool.push(raw[pos..pos + entry_len].to_vec());
78            pos += entry_len;
79        }
80
81        // Code length (u32 LE)
82        if pos + 4 > raw.len() {
83            return Err(AxiomError::InvalidBytecode);
84        }
85        let code_len =
86            u32::from_le_bytes([raw[pos], raw[pos + 1], raw[pos + 2], raw[pos + 3]]) as usize;
87        pos += 4;
88        if code_len > MAX_CODE_BYTES {
89            return Err(AxiomError::InvalidBytecode);
90        }
91        if pos + code_len > raw.len() {
92            return Err(AxiomError::InvalidBytecode);
93        }
94
95        let code = raw[pos..pos + code_len].to_vec();
96
97        Ok(Self { const_pool, code })
98    }
99
100    /// Encode to bytes.
101    pub fn encode(&self) -> Vec<u8> {
102        let mut out = Vec::new();
103        out.extend_from_slice(&MAGIC);
104        out.push(VERSION);
105        out.push(0); // reserved
106        out.extend_from_slice(&(self.const_pool.len() as u16).to_le_bytes());
107        for entry in &self.const_pool {
108            out.extend_from_slice(&(entry.len() as u32).to_le_bytes());
109            out.extend_from_slice(entry);
110        }
111        out.extend_from_slice(&(self.code.len() as u32).to_le_bytes());
112        out.extend_from_slice(&self.code);
113        out
114    }
115
116    /// Returns true if raw bytes start with Axiom magic.
117    pub fn is_axiom(raw: &[u8]) -> bool {
118        raw.len() >= 4 && &raw[0..4] == &MAGIC
119    }
120}