1#![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 #[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 output.extend(crate::pvm::encode_var_u32(self.metadata.len() as u32));
94 output.extend(&self.metadata);
95
96 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 assert_eq!(encoded[0], 0, "metadata length varint should be 0");
128
129 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 assert_eq!(encoded[0], 4, "metadata length varint should be 4");
146
147 assert_eq!(&encoded[1..5], &metadata, "metadata content");
149
150 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}