1use alloc::vec::Vec;
9
10use crate::slot::SlotIdx;
11
12use super::cap::{CapHash, MAX_ENDPOINTS, MAX_SOURCE_DEPTH, NUM_REGS};
13
14#[derive(Clone, Debug, ssz_derive::HashTreeRoot)]
15pub struct ImageCap {
16 pub code: Vec<u8>,
18 pub bitmask: Vec<u8>,
21 pub jump_table: Vec<u32>,
23 pub endpoints: Vec<EndpointDef>,
27 pub mappings: Vec<MemoryMapping>,
29 pub pinned: Vec<ImageSlotEntry>,
33 pub initial: Vec<ImageSlotEntry>,
35 pub yield_marker_slot: Option<SlotIdx>,
37}
38
39#[derive(
44 Clone,
45 Copy,
46 Debug,
47 PartialEq,
48 Eq,
49 ssz_derive::Encode,
50 ssz_derive::Decode,
51 ssz_derive::HashTreeRoot,
52)]
53pub struct EndpointDef {
54 pub entry_pc: u64,
55 pub stack_top: u64,
56 pub arg_cnode_slot: SlotIdx,
57 pub arg_cnode_size: u8,
58 pub initial_regs: [u64; NUM_REGS],
59}
60
61impl EndpointDef {
62 pub const fn empty() -> Self {
66 Self {
67 entry_pc: 0,
68 stack_top: 0,
69 arg_cnode_slot: SlotIdx(0),
70 arg_cnode_size: 0,
71 initial_regs: [0; NUM_REGS],
72 }
73 }
74}
75
76#[derive(Clone, Copy, Debug, PartialEq, Eq)]
89pub struct MemoryMapping {
90 pub start: u64,
91 pub size: u64,
92 pub source_path: [SlotIdx; MAX_SOURCE_DEPTH],
93 pub source_path_len: u8,
94}
95
96impl MemoryMapping {
97 const SSZ_LEN: usize = 8 + 8 + MAX_SOURCE_DEPTH * 4 + 1;
99}
100
101impl ssz::Encode for MemoryMapping {
102 fn is_ssz_fixed_len() -> bool {
103 true
104 }
105 fn ssz_fixed_len() -> usize {
106 Self::SSZ_LEN
107 }
108 fn ssz_bytes_len(&self) -> usize {
109 Self::SSZ_LEN
110 }
111 fn ssz_append(&self, buf: &mut alloc::vec::Vec<u8>) {
112 buf.extend_from_slice(&self.start.to_le_bytes());
113 buf.extend_from_slice(&self.size.to_le_bytes());
114 for s in &self.source_path {
115 buf.extend_from_slice(&s.get().to_le_bytes());
116 }
117 buf.push(self.source_path_len);
118 }
119}
120
121impl ssz::Decode for MemoryMapping {
122 fn is_ssz_fixed_len() -> bool {
123 true
124 }
125 fn ssz_fixed_len() -> usize {
126 Self::SSZ_LEN
127 }
128 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
129 if bytes.len() != Self::SSZ_LEN {
130 return Err(ssz::DecodeError::UnexpectedEof {
131 expected: Self::SSZ_LEN,
132 actual: bytes.len(),
133 });
134 }
135 let start = u64::from_le_bytes(bytes[0..8].try_into().expect("len checked"));
136 let size = u64::from_le_bytes(bytes[8..16].try_into().expect("len checked"));
137 let mut source_path = [SlotIdx(0); MAX_SOURCE_DEPTH];
138 for (i, slot) in source_path.iter_mut().enumerate() {
139 let s = 16 + i * 4;
140 let arr: [u8; 4] = bytes[s..s + 4].try_into().expect("len checked");
141 *slot = SlotIdx(u32::from_le_bytes(arr));
142 }
143 let source_path_len = bytes[16 + MAX_SOURCE_DEPTH * 4];
144 Ok(Self {
145 start,
146 size,
147 source_path,
148 source_path_len,
149 })
150 }
151}
152
153impl ssz::HashTreeRoot for MemoryMapping {
154 fn hash_tree_root<D: ::ssz::digest::Digest<OutputSize = ::ssz::digest::typenum::U32>>(
155 &self,
156 ) -> [u8; 32] {
157 let path_root = {
160 let mut buf: alloc::vec::Vec<u8> = alloc::vec::Vec::with_capacity(MAX_SOURCE_DEPTH * 4);
164 for s in &self.source_path {
165 buf.extend_from_slice(&s.get().to_le_bytes());
166 }
167 let chunks = ssz::pack_bytes(&buf);
168 let limit = (MAX_SOURCE_DEPTH * 4).div_ceil(32).max(1);
169 ssz::merkleize::<D>(&chunks, limit)
170 };
171 let roots = [
172 ssz::HashTreeRoot::hash_tree_root::<D>(&self.start),
173 ssz::HashTreeRoot::hash_tree_root::<D>(&self.size),
174 path_root,
175 ssz::HashTreeRoot::hash_tree_root::<D>(&self.source_path_len),
176 ];
177 ssz::merkleize::<D>(&roots, 4)
178 }
179}
180
181impl MemoryMapping {
182 pub fn path(&self) -> &[SlotIdx] {
184 &self.source_path[..self.source_path_len as usize]
185 }
186}
187
188#[derive(
191 Clone,
192 Copy,
193 Debug,
194 PartialEq,
195 Eq,
196 ssz_derive::Encode,
197 ssz_derive::Decode,
198 ssz_derive::HashTreeRoot,
199)]
200pub struct ImageSlotEntry {
201 pub slot: SlotIdx,
202 pub cap_hash: CapHash,
203}
204
205#[derive(Debug, thiserror::Error)]
211pub enum ImageConvertError {
212 #[error("memory mapping source path empty")]
213 SourcePathEmpty,
214 #[error("memory mapping source path too deep (steps={0} > MAX_SOURCE_DEPTH)")]
215 SourcePathTooDeep(usize),
216 #[error("endpoint index {0} >= MAX_ENDPOINTS")]
217 EndpointIndexOutOfRange(u8),
218 #[error("register index {0} >= NUM_REGS")]
219 RegisterIndexOutOfRange(u8),
220}
221
222pub fn image_cap(
243 image: &crate::image::Image,
244 pinned_hashes: &[(SlotIdx, CapHash)],
245 initial_hashes: &[(SlotIdx, CapHash)],
246) -> Result<ImageCap, ImageConvertError> {
247 let mut code = Vec::with_capacity(image.code.len());
248 code.extend_from_slice(&image.code);
249
250 let mut bitmask = Vec::with_capacity(image.packed_bitmask.len());
251 bitmask.extend_from_slice(&image.packed_bitmask);
252
253 let mut jump_table = Vec::with_capacity(image.jump_table.len());
254 for &j in &image.jump_table {
255 jump_table.push(j);
256 }
257
258 let mut endpoints = Vec::with_capacity(MAX_ENDPOINTS);
261 for _ in 0..MAX_ENDPOINTS {
262 endpoints.push(EndpointDef::empty());
263 }
264 for (&idx, ep) in &image.endpoints {
265 if (idx as usize) >= MAX_ENDPOINTS {
266 return Err(ImageConvertError::EndpointIndexOutOfRange(idx));
267 }
268 let mut initial_regs = [0u64; NUM_REGS];
269 for (®_idx, &val) in &ep.initial_regs {
270 if (reg_idx as usize) >= NUM_REGS {
271 return Err(ImageConvertError::RegisterIndexOutOfRange(reg_idx));
272 }
273 initial_regs[reg_idx as usize] = val;
274 }
275 let stack_top = ep.initial_regs.get(&1).copied().unwrap_or(0);
277 endpoints[idx as usize] = EndpointDef {
278 entry_pc: ep.entry_pc,
279 stack_top,
280 arg_cnode_slot: SlotIdx(0),
281 arg_cnode_size: ep.arg_cnode_size,
282 initial_regs,
283 };
284 }
285
286 let mut mappings = Vec::with_capacity(image.memory_mappings.len());
287 for m in &image.memory_mappings {
288 let steps = &m.source.steps;
289 if steps.is_empty() {
290 return Err(ImageConvertError::SourcePathEmpty);
291 }
292 if steps.len() > MAX_SOURCE_DEPTH {
293 return Err(ImageConvertError::SourcePathTooDeep(steps.len()));
294 }
295 let mut source_path = [SlotIdx(0); MAX_SOURCE_DEPTH];
296 for (i, s) in steps.iter().enumerate() {
297 source_path[i] = *s;
298 }
299 mappings.push(MemoryMapping {
300 start: m.start,
301 size: m.size,
302 source_path,
303 source_path_len: steps.len() as u8,
304 });
305 }
306
307 let pinned = build_image_slot_vec(pinned_hashes);
308 let initial = build_image_slot_vec(initial_hashes);
309
310 Ok(ImageCap {
311 code,
312 bitmask,
313 jump_table,
314 endpoints,
315 mappings,
316 pinned,
317 initial,
318 yield_marker_slot: image.yield_marker_slot,
319 })
320}
321
322fn build_image_slot_vec(pairs: &[(SlotIdx, CapHash)]) -> Vec<ImageSlotEntry> {
323 let mut sorted: Vec<(SlotIdx, CapHash)> = pairs.to_vec();
324 sorted.sort_by_key(|(s, _)| *s);
325 let mut out = Vec::with_capacity(sorted.len());
326 for (slot, cap_hash) in &sorted {
327 out.push(ImageSlotEntry {
328 slot: *slot,
329 cap_hash: *cap_hash,
330 });
331 }
332 out
333}