Skip to main content

wasm_pvm/
spi.rs

1// SPI encoding uses u32 lengths but writes u24. Truncation is checked or expected.
2#![allow(clippy::cast_possible_truncation)]
3
4use crate::pvm::ProgramBlob;
5
6pub struct SpiProgram {
7    metadata: Vec<u8>,
8    ro_data: Vec<u8>,
9    rw_data: Vec<u8>,
10    heap_pages: u16,
11    stack_size: u32,
12    code: ProgramBlob,
13}
14
15impl SpiProgram {
16    #[must_use]
17    pub fn new(code: ProgramBlob) -> Self {
18        Self {
19            metadata: Vec::new(),
20            ro_data: Vec::new(),
21            rw_data: Vec::new(),
22            heap_pages: 16,
23            stack_size: 64 * 1024,
24            code,
25        }
26    }
27
28    #[must_use]
29    pub fn with_stack_size(mut self, size: u32) -> Self {
30        self.stack_size = size;
31        self
32    }
33
34    #[must_use]
35    pub fn with_heap_pages(mut self, pages: u16) -> Self {
36        self.heap_pages = pages;
37        self
38    }
39
40    #[must_use]
41    pub fn with_ro_data(mut self, data: Vec<u8>) -> Self {
42        self.ro_data = data;
43        self
44    }
45
46    #[must_use]
47    pub fn with_rw_data(mut self, data: Vec<u8>) -> Self {
48        self.rw_data = data;
49        self
50    }
51
52    #[must_use]
53    pub fn with_metadata(mut self, data: Vec<u8>) -> Self {
54        self.metadata = data;
55        self
56    }
57
58    #[must_use]
59    pub fn code(&self) -> &ProgramBlob {
60        &self.code
61    }
62
63    #[must_use]
64    pub fn ro_data(&self) -> &[u8] {
65        &self.ro_data
66    }
67
68    #[must_use]
69    pub fn rw_data(&self) -> &[u8] {
70        &self.rw_data
71    }
72
73    #[must_use]
74    pub fn heap_pages(&self) -> u16 {
75        self.heap_pages
76    }
77
78    #[must_use]
79    pub fn metadata(&self) -> &[u8] {
80        &self.metadata
81    }
82
83    /// Encode the SPI program with metadata prefix.
84    ///
85    /// Format: `[varint: metadata_len][metadata_bytes][SPI header + data + code]`
86    #[must_use]
87    pub fn encode(&self) -> Vec<u8> {
88        let code_blob = self.code.encode();
89
90        let mut output = Vec::new();
91
92        // Metadata prefix
93        output.extend(crate::pvm::encode_var_u32(self.metadata.len() as u32));
94        output.extend(&self.metadata);
95
96        // SPI header
97        output.extend(encode_u24(self.ro_data.len() as u32));
98        output.extend(encode_u24(self.rw_data.len() as u32));
99        output.extend(self.heap_pages.to_le_bytes());
100        output.extend(encode_u24(self.stack_size));
101        output.extend(&self.ro_data);
102        output.extend(&self.rw_data);
103        output.extend((code_blob.len() as u32).to_le_bytes());
104        output.extend(code_blob);
105
106        output
107    }
108}
109
110fn encode_u24(value: u32) -> [u8; 3] {
111    let bytes = value.to_le_bytes();
112    [bytes[0], bytes[1], bytes[2]]
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    use crate::pvm::Instruction;
119
120    #[test]
121    fn test_spi_encode_minimal() {
122        let code = ProgramBlob::new(vec![Instruction::Trap]);
123        let spi = SpiProgram::new(code);
124        let encoded = spi.encode();
125
126        // First byte is varint-encoded metadata length (0 = empty metadata).
127        assert_eq!(encoded[0], 0, "metadata length varint should be 0");
128
129        // SPI header starts at offset 1.
130        assert_eq!(&encoded[1..4], &[0, 0, 0], "ro_data_len");
131        assert_eq!(&encoded[4..7], &[0, 0, 0], "rw_data_len");
132        assert_eq!(&encoded[7..9], &16u16.to_le_bytes(), "heap_pages");
133        let stack_bytes = encode_u24(64 * 1024);
134        assert_eq!(&encoded[9..12], &stack_bytes, "stack_size");
135    }
136
137    #[test]
138    fn test_spi_encode_with_metadata() {
139        let code = ProgramBlob::new(vec![Instruction::Trap]);
140        let metadata = vec![0xDE, 0xAD, 0xBE, 0xEF];
141        let spi = SpiProgram::new(code).with_metadata(metadata.clone());
142        let encoded = spi.encode();
143
144        // Metadata length varint: 4 encodes as [4].
145        assert_eq!(encoded[0], 4, "metadata length varint should be 4");
146
147        // Metadata bytes follow.
148        assert_eq!(&encoded[1..5], &metadata, "metadata content");
149
150        // SPI header starts after metadata.
151        assert_eq!(&encoded[5..8], &[0, 0, 0], "ro_data_len after metadata");
152    }
153
154    #[test]
155    fn test_spi_encode_with_string_metadata() {
156        let code = ProgramBlob::new(vec![Instruction::Trap]);
157        let metadata_str = "test.wasm (wasm-pvm 0.1.0)";
158        let spi = SpiProgram::new(code).with_metadata(metadata_str.as_bytes().to_vec());
159        let encoded = spi.encode();
160
161        let meta_len = metadata_str.len() as u32;
162        let varint = crate::pvm::encode_var_u32(meta_len);
163        assert_eq!(&encoded[..varint.len()], &varint, "metadata length varint");
164        assert_eq!(
165            &encoded[varint.len()..varint.len() + meta_len as usize],
166            metadata_str.as_bytes(),
167            "metadata should contain the string"
168        );
169    }
170}