1use alloc::boxed::Box;
35use alloc::vec::Vec;
36
37use crate::cap::{Cap, CapHashOrRef, NUM_REGS, TypeCap};
38use crate::cnode::CNodeCap;
39use crate::data::{DataCap, DataContent};
40use crate::image_cap::{EndpointDef, ImageCap, ImageSlotEntry, MemoryMapping};
41use crate::instance::{InstanceCap, RwOverlay};
42use crate::slot::SlotIdx;
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum WireConvertError {
48 CapHasRef,
52 PagedData,
55 CNodeMissingSlot,
58}
59
60#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
65pub enum WireCap {
66 Instance(WireInstanceCap),
67 Image(WireImageCap),
68 Data(WireDataCap),
69 CNode(WireCNodeCap),
70 Type(WireTypeCap),
71}
72
73#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
76pub struct WireInstanceCap {
77 pub image_hash_chain: [u8; 32],
78 pub image_hash: [u8; 32],
79 pub root_cnode_hash: [u8; 32],
80 pub rw_overlays: Vec<WireRwOverlay>,
81 pub mem_size: u32,
82 pub regs: [u64; NUM_REGS],
83 pub pc: u64,
84 pub gas_remaining: u64,
85}
86
87#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
88pub struct WireRwOverlay {
89 pub start: u32,
90 pub bytes: Vec<u8>,
91}
92
93#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
96pub struct WireImageCap {
97 pub code: Vec<u8>,
98 pub bitmask: Vec<u8>,
99 pub jump_table: Vec<u32>,
100 pub endpoints: Vec<WireEndpointDef>,
101 pub mappings: Vec<WireMemoryMapping>,
102 pub pinned: Vec<WireImageSlotEntry>,
103 pub initial: Vec<WireImageSlotEntry>,
104 pub yield_marker_slot: Option<u32>,
105}
106
107#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
108pub struct WireEndpointDef {
109 pub entry_pc: u64,
110 pub stack_top: u64,
111 pub arg_cnode_slot: u32,
112 pub arg_cnode_size: u8,
113 pub initial_regs: [u64; NUM_REGS],
114}
115
116#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
117pub struct WireMemoryMapping {
118 pub start: u64,
119 pub size: u64,
120 pub source_path: Vec<u32>,
121 pub source_path_len: u8,
122}
123
124#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
125pub struct WireImageSlotEntry {
126 pub slot: u32,
127 pub cap_hash: [u8; 32],
128}
129
130#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
132pub struct WireDataCap {
133 pub bytes: Vec<u8>,
134}
135
136#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
139pub struct WireCNodeCap {
140 pub size_log: u8,
141 pub slots: Vec<WireCNodeSlot>,
142}
143
144#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
145pub struct WireCNodeSlot {
146 pub slot: u32,
147 pub cap_hash: [u8; 32],
148}
149
150#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
151pub struct WireTypeCap {
152 pub image_hash_chain: [u8; 32],
153}
154
155impl WireCap {
158 pub fn from_cap(cap: &Cap) -> Result<Self, WireConvertError> {
161 Ok(match cap {
162 Cap::Instance(i) => WireCap::Instance(WireInstanceCap::from_instance(i)?),
163 Cap::Image(i) => WireCap::Image(WireImageCap::from_image(i)),
164 Cap::Data(d) => WireCap::Data(WireDataCap::from_data(d)?),
165 Cap::CNode(c) => WireCap::CNode(WireCNodeCap::from_cnode(c)?),
166 Cap::Type(t) => WireCap::Type(WireTypeCap {
167 image_hash_chain: t.image_hash_chain,
168 }),
169 })
170 }
171
172 pub fn into_cap(self) -> Result<Cap, WireConvertError> {
175 Ok(match self {
176 WireCap::Instance(i) => Cap::Instance(i.into_instance()),
177 WireCap::Image(i) => Cap::Image(i.into_image()),
178 WireCap::Data(d) => Cap::Data(d.into_data()),
179 WireCap::CNode(c) => Cap::CNode(c.into_cnode()?),
180 WireCap::Type(t) => Cap::Type(TypeCap {
181 image_hash_chain: t.image_hash_chain,
182 }),
183 })
184 }
185}
186
187impl WireInstanceCap {
188 fn from_instance(inst: &InstanceCap) -> Result<Self, WireConvertError> {
189 let root_cnode_hash = match inst.root_cnode {
190 CapHashOrRef::Hash(h) => h,
191 CapHashOrRef::Ref(_) => return Err(WireConvertError::CapHasRef),
192 };
193 let rw_overlays = inst
194 .rw_overlays
195 .iter()
196 .map(|ov| WireRwOverlay {
197 start: ov.start,
198 bytes: ov.bytes.clone(),
199 })
200 .collect();
201 Ok(Self {
202 image_hash_chain: inst.image_hash_chain,
203 image_hash: inst.image_hash,
204 root_cnode_hash,
205 rw_overlays,
206 mem_size: inst.mem_size,
207 regs: inst.regs,
208 pc: inst.pc,
209 gas_remaining: inst.gas_remaining,
210 })
211 }
212
213 fn into_instance(self) -> InstanceCap {
214 let rw_overlays = self
215 .rw_overlays
216 .into_iter()
217 .map(|w| RwOverlay {
218 start: w.start,
219 bytes: w.bytes,
220 })
221 .collect();
222 InstanceCap {
223 image_hash_chain: self.image_hash_chain,
224 image_hash: self.image_hash,
225 root_cnode: CapHashOrRef::Hash(self.root_cnode_hash),
226 rw_overlays,
227 mem_size: self.mem_size,
228 regs: self.regs,
229 pc: self.pc,
230 gas_remaining: self.gas_remaining,
231 }
232 }
233}
234
235impl WireImageCap {
236 fn from_image(img: &ImageCap) -> Self {
237 let endpoints = img
238 .endpoints
239 .iter()
240 .map(|e| WireEndpointDef {
241 entry_pc: e.entry_pc,
242 stack_top: e.stack_top,
243 arg_cnode_slot: e.arg_cnode_slot.get(),
244 arg_cnode_size: e.arg_cnode_size,
245 initial_regs: e.initial_regs,
246 })
247 .collect();
248 let mappings = img
249 .mappings
250 .iter()
251 .map(|m| WireMemoryMapping {
252 start: m.start,
253 size: m.size,
254 source_path: m.source_path.iter().map(|s| s.get()).collect(),
255 source_path_len: m.source_path_len,
256 })
257 .collect();
258 let pinned = img
259 .pinned
260 .iter()
261 .map(|e| WireImageSlotEntry {
262 slot: e.slot.get(),
263 cap_hash: e.cap_hash,
264 })
265 .collect();
266 let initial = img
267 .initial
268 .iter()
269 .map(|e| WireImageSlotEntry {
270 slot: e.slot.get(),
271 cap_hash: e.cap_hash,
272 })
273 .collect();
274 Self {
275 code: img.code.clone(),
276 bitmask: img.bitmask.clone(),
277 jump_table: img.jump_table.clone(),
278 endpoints,
279 mappings,
280 pinned,
281 initial,
282 yield_marker_slot: img.yield_marker_slot.map(|s| s.get()),
283 }
284 }
285
286 fn into_image(self) -> ImageCap {
287 let endpoints = self
288 .endpoints
289 .into_iter()
290 .map(|w| EndpointDef {
291 entry_pc: w.entry_pc,
292 stack_top: w.stack_top,
293 arg_cnode_slot: SlotIdx(w.arg_cnode_slot),
294 arg_cnode_size: w.arg_cnode_size,
295 initial_regs: w.initial_regs,
296 })
297 .collect();
298 let mappings = self
299 .mappings
300 .into_iter()
301 .map(|w| {
302 let mut source_path = [SlotIdx(0); crate::cap::MAX_SOURCE_DEPTH];
303 for (i, v) in w.source_path.iter().enumerate() {
304 if i >= crate::cap::MAX_SOURCE_DEPTH {
305 break;
306 }
307 source_path[i] = SlotIdx(*v);
308 }
309 MemoryMapping {
310 start: w.start,
311 size: w.size,
312 source_path,
313 source_path_len: w.source_path_len,
314 }
315 })
316 .collect();
317 let pinned = self
318 .pinned
319 .into_iter()
320 .map(|w| ImageSlotEntry {
321 slot: SlotIdx(w.slot),
322 cap_hash: w.cap_hash,
323 })
324 .collect();
325 let initial = self
326 .initial
327 .into_iter()
328 .map(|w| ImageSlotEntry {
329 slot: SlotIdx(w.slot),
330 cap_hash: w.cap_hash,
331 })
332 .collect();
333 ImageCap {
334 code: self.code,
335 bitmask: self.bitmask,
336 jump_table: self.jump_table,
337 endpoints,
338 mappings,
339 pinned,
340 initial,
341 yield_marker_slot: self.yield_marker_slot.map(SlotIdx),
342 }
343 }
344}
345
346impl WireDataCap {
347 fn from_data(d: &DataCap) -> Result<Self, WireConvertError> {
348 match &d.content {
349 DataContent::Inline(bytes) => Ok(Self {
350 bytes: bytes.clone(),
351 }),
352 DataContent::Paged { .. } => Err(WireConvertError::PagedData),
353 }
354 }
355
356 fn into_data(self) -> DataCap {
357 let bytes = self.bytes;
361 let mut buf = crate::data::alloc_page_aligned_zeroed(bytes.len());
362 let copy_len = bytes.len().min(buf.len());
363 buf[..copy_len].copy_from_slice(&bytes[..copy_len]);
364 DataCap {
365 content: DataContent::Inline(buf),
366 }
367 }
368}
369
370impl WireCNodeCap {
371 fn from_cnode(cn: &CNodeCap) -> Result<Self, WireConvertError> {
372 let mut slots = Vec::new();
373 for (idx, entry) in cn.slots.iter() {
374 match entry {
375 ssz::MissingOr::Materialized(CapHashOrRef::Hash(h)) => {
376 slots.push(WireCNodeSlot {
377 slot: idx as u32,
378 cap_hash: *h,
379 });
380 }
381 ssz::MissingOr::Materialized(CapHashOrRef::Ref(_)) => {
382 return Err(WireConvertError::CapHasRef);
383 }
384 ssz::MissingOr::Missing(_) => {
385 return Err(WireConvertError::CNodeMissingSlot);
386 }
387 }
388 }
389 Ok(Self {
390 size_log: cn.size_log,
391 slots,
392 })
393 }
394
395 fn into_cnode(self) -> Result<CNodeCap, WireConvertError> {
396 let mut cn =
399 CNodeCap::new(self.size_log).map_err(|_| WireConvertError::CNodeMissingSlot)?;
400 for entry in self.slots {
401 cn.set(
402 SlotIdx(entry.slot),
403 Some(CapHashOrRef::Hash(entry.cap_hash)),
404 )
405 .map_err(|_| WireConvertError::CNodeMissingSlot)?;
406 }
407 Ok(cn)
408 }
409}
410
411pub fn box_from_wire(wire: WireCap) -> Result<Box<Cap>, WireConvertError> {
417 wire.into_cap().map(Box::new)
418}
419
420impl core::fmt::Display for WireConvertError {
424 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
425 match self {
426 WireConvertError::CapHasRef => {
427 f.write_str("cap holds a CapHashOrRef::Ref target; refs are unsupported on the wire")
428 }
429 WireConvertError::PagedData => {
430 f.write_str("DataContent::Paged is not supported on the wire (V0 inline-only)")
431 }
432 WireConvertError::CNodeMissingSlot => {
433 f.write_str("CNodeCap slot is unrepresentable on the wire (missing placeholder or oversized cnode)")
434 }
435 }
436 }
437}
438
439#[cfg(feature = "std")]
440impl std::error::Error for WireConvertError {}
441
442#[cfg(test)]
443mod tests {
444 use super::*;
445 use crate::cap_hash::cap_hash;
446
447 #[test]
448 fn type_cap_roundtrip_preserves_hash() {
449 let cap = Cap::Type(TypeCap {
450 image_hash_chain: [0xAB; 32],
451 });
452 let wire = WireCap::from_cap(&cap).expect("from_cap");
453 let recovered = wire.into_cap().expect("into_cap");
454 assert_eq!(cap_hash(&cap), cap_hash(&recovered));
455 }
456
457 #[test]
458 fn empty_cnode_roundtrip_preserves_hash() {
459 let cap = Cap::CNode(CNodeCap::new(0).expect("cnode"));
460 let wire = WireCap::from_cap(&cap).expect("from_cap");
461 let recovered = wire.into_cap().expect("into_cap");
462 assert_eq!(cap_hash(&cap), cap_hash(&recovered));
463 }
464
465 #[test]
466 fn inline_data_roundtrip_preserves_hash() {
467 let cap = Cap::data_inline(b"hello-rkyv");
468 let wire = WireCap::from_cap(&cap).expect("from_cap");
469 let recovered = wire.into_cap().expect("into_cap");
470 assert_eq!(cap_hash(&cap), cap_hash(&recovered));
471 }
472
473 #[test]
474 fn rkyv_archive_roundtrip_data_cap() {
475 let cap = Cap::data_inline(b"archive me");
476 let wire = WireCap::from_cap(&cap).expect("from_cap");
477 let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(&wire).expect("rkyv encode");
478 let mut aligned = rkyv::util::AlignedVec::<16>::with_capacity(bytes.len());
479 aligned.extend_from_slice(&bytes);
480 let decoded: WireCap =
481 rkyv::from_bytes::<WireCap, rkyv::rancor::Error>(&aligned).expect("rkyv decode");
482 let recovered = decoded.into_cap().expect("into_cap");
483 assert_eq!(cap_hash(&cap), cap_hash(&recovered));
484 }
485
486 #[test]
487 fn image_cap_roundtrip_preserves_hash() {
488 let mut img = crate::image::Image::empty();
491 img.code = alloc::vec![0u8, 10u8, 42];
492 img.packed_bitmask = alloc::vec![0b011u8];
493 let mut endpoints = alloc::collections::BTreeMap::new();
494 endpoints.insert(
495 0u8,
496 crate::image::EndpointDef {
497 entry_pc: 1,
498 arg_registers: 0,
499 arg_cnode_size: 0,
500 initial_regs: alloc::collections::BTreeMap::new(),
501 },
502 );
503 img.endpoints = endpoints;
504 let cap = Cap::image_with_slots(&img, &[], &[]).expect("image_with_slots");
505 let wire = WireCap::from_cap(&cap).expect("from_cap");
506 let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(&wire).expect("rkyv encode");
507 let mut aligned = rkyv::util::AlignedVec::<16>::with_capacity(bytes.len());
508 aligned.extend_from_slice(&bytes);
509 let decoded: WireCap =
510 rkyv::from_bytes::<WireCap, rkyv::rancor::Error>(&aligned).expect("rkyv decode");
511 let recovered = decoded.into_cap().expect("into_cap");
512 assert_eq!(cap_hash(&cap), cap_hash(&recovered));
513 }
514
515 #[test]
516 fn instance_cap_roundtrip_preserves_hash() {
517 let cap = Cap::instance_with_overlays(
518 [0u8; 32],
519 [0xAA; 32],
520 [0xBB; 32],
521 &[],
522 4096,
523 [0u64; NUM_REGS],
524 0,
525 0,
526 );
527 let wire = WireCap::from_cap(&cap).expect("from_cap");
528 let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(&wire).expect("rkyv encode");
529 let mut aligned = rkyv::util::AlignedVec::<16>::with_capacity(bytes.len());
530 aligned.extend_from_slice(&bytes);
531 let decoded: WireCap =
532 rkyv::from_bytes::<WireCap, rkyv::rancor::Error>(&aligned).expect("rkyv decode");
533 let recovered = decoded.into_cap().expect("into_cap");
534 assert_eq!(cap_hash(&cap), cap_hash(&recovered));
535 }
536}