calimero_runtime/logic.rs
1#![allow(single_use_lifetimes, unused_lifetimes, reason = "False positive")]
2#![allow(clippy::mem_forget, reason = "Safe for now")]
3
4use core::mem::MaybeUninit;
5use core::num::NonZeroU64;
6use core::{fmt, slice};
7use std::borrow::Cow;
8use std::collections::{BTreeMap, HashMap};
9use std::vec;
10
11use calimero_node_primitives::client::NodeClient;
12use calimero_sys as sys;
13use ouroboros::self_referencing;
14use serde::Serialize;
15
16use crate::constants::{DIGEST_SIZE, ONE_GIB, ONE_KIB, ONE_MIB};
17use crate::constraint::{Constrained, MaxU64};
18use crate::errors::{FunctionCallError, HostError, Location, PanicContext};
19use crate::store::Storage;
20use crate::Constraint;
21
22mod errors;
23mod host_functions;
24mod imports;
25mod registers;
26
27pub use errors::VMLogicError;
28pub use host_functions::*;
29use registers::Registers;
30
31/// A specialized `Result` type for VMLogic operations.
32pub type VMLogicResult<T, E = VMLogicError> = Result<T, E>;
33
34/// Encapsulates the context for a single VM execution.
35///
36/// This struct holds all the necessary information about the current execution environment,
37/// such as the input data, the context ID, and the executor's public key.
38#[derive(Debug)]
39#[non_exhaustive]
40pub struct VMContext<'a> {
41 /// The input data for the context.
42 pub input: Cow<'a, [u8]>,
43 /// The unique ID for the current execution context.
44 pub context_id: [u8; DIGEST_SIZE],
45 /// The public key of the entity executing the function call/transaction.
46 pub executor_public_key: [u8; DIGEST_SIZE],
47}
48
49impl<'a> VMContext<'a> {
50 /// Creates a new `VMContext`.
51 ///
52 /// # Arguments
53 ///
54 /// * `input` - The input data for the context.
55 /// * `context_id` - The unique ID for the execution context.
56 /// * `executor_public_key` - The public key of the executor.
57 #[must_use]
58 pub const fn new(
59 input: Cow<'a, [u8]>,
60 context_id: [u8; DIGEST_SIZE],
61 executor_public_key: [u8; DIGEST_SIZE],
62 ) -> Self {
63 Self {
64 input,
65 context_id,
66 executor_public_key,
67 }
68 }
69}
70
71/// Defines the resource limits for a VM instance.
72///
73/// This struct is used to configure constraints on various VM operations to prevent
74/// excessive resource consumption.
75#[derive(Debug, Clone, Copy)]
76pub struct VMLimits {
77 /// The maximum number of memory pages allowed.
78 pub max_memory_pages: u32,
79 /// The maximum stack size in bytes.
80 pub max_stack_size: usize,
81 /// The maximum number of registers that can be used.
82 pub max_registers: u64,
83 /// The maximum size of a single register's data in bytes.
84 /// constrained to be less than u64::MAX
85 /// because register_len returns u64::MAX if the register is not found
86 pub max_register_size: Constrained<u64, MaxU64<{ u64::MAX - 1 }>>,
87 /// The total capacity across all registers in bytes.
88 pub max_registers_capacity: u64, // todo! must not be less than max_register_size
89 /// The maximum number of log entries that can be created.
90 pub max_logs: u64,
91 /// The maximum size of a single log message in bytes.
92 pub max_log_size: u64,
93 /// The maximum number of events that can be emitted.
94 pub max_events: u64,
95 /// The maximum size of an event's "kind" string in bytes.
96 pub max_event_kind_size: u64,
97 /// The maximum size of an event's data payload in bytes.
98 pub max_event_data_size: u64,
99 /// The maximum size of a storage key in bytes.
100 pub max_storage_key_size: NonZeroU64,
101 /// The maximum size of a storage value in bytes.
102 pub max_storage_value_size: NonZeroU64,
103 /// The maximum number of blob handles that can exist.
104 pub max_blob_handles: u64,
105 /// The maximum size of a single chunk when writing to or reading from a blob.
106 pub max_blob_chunk_size: u64,
107}
108
109impl Default for VMLimits {
110 fn default() -> Self {
111 #[inline(always)]
112 fn is_valid<T, E: fmt::Debug>(t: Result<T, E>) -> T {
113 t.expect("is valid")
114 }
115
116 Self {
117 max_memory_pages: ONE_KIB, // 1 KiB
118 max_stack_size: 200 * ONE_KIB as usize, // 200 KiB
119 max_registers: 100, //
120 max_register_size: is_valid((100 * ONE_MIB as u64).validate()), // 100 MiB
121 max_registers_capacity: ONE_GIB as u64, // 1 GiB
122 max_logs: 100, //
123 max_log_size: 16 * ONE_KIB as u64, // 16 KiB
124 max_events: 100, //
125 max_event_kind_size: 100, //
126 max_event_data_size: 16 * ONE_KIB as u64, // 16 KiB
127 max_storage_key_size: is_valid((ONE_MIB as u64).try_into()), // 1 MiB
128 max_storage_value_size: is_valid((10 * ONE_MIB as u64).try_into()), // 10 MiB
129 max_blob_handles: 100, //
130 max_blob_chunk_size: 10 * ONE_MIB as u64, // 10 MiB
131 }
132 }
133}
134
135/// The core logic controller for the VM.
136///
137/// This struct manages the state of an execution, including storage,
138/// memory, registers, and the results of the execution (logs, events, return values).
139#[expect(
140 missing_debug_implementations,
141 reason = "storage and node_client can't impl Debug"
142)]
143pub struct VMLogic<'a> {
144 /// A mutable reference to the storage.
145 storage: &'a mut dyn Storage,
146 /// The Wasmer memory instance associated with the guest module.
147 memory: Option<wasmer::Memory>,
148 /// The execution context for the current call.
149 context: VMContext<'a>,
150 /// The VM resource limits applied to this execution.
151 limits: &'a VMLimits,
152 /// A collection of registers for temporary data exchange between host and guest.
153 registers: Registers,
154 /// The optional final result of the execution, which can be a success (`Ok`) or an error (`Err`).
155 returns: Option<VMLogicResult<Vec<u8>, Vec<u8>>>,
156 /// A list of log messages generated during execution.
157 logs: Vec<String>,
158 /// A list of events emitted during execution.
159 events: Vec<Event>,
160 /// The root hash of the state after a successful commit.
161 root_hash: Option<[u8; DIGEST_SIZE]>,
162 /// A binary artifact produced by the execution.
163 artifact: Vec<u8>,
164 /// A map of proposals created during execution, having proposal ID as a key.
165 proposals: BTreeMap<[u8; DIGEST_SIZE], Vec<u8>>,
166 /// A list of approvals submitted during execution.
167 approvals: Vec<[u8; DIGEST_SIZE]>,
168
169 // Blob functionality
170 /// An optional client for interacting with the node's blob storage.
171 node_client: Option<NodeClient>,
172 /// A map of active blob handles, having blob's file descriptor as a key.
173 blob_handles: HashMap<u64, BlobHandle>,
174 /// The next available file descriptor for a new blob handle.
175 next_blob_fd: u64,
176}
177
178impl<'a> VMLogic<'a> {
179 /// Creates a new `VMLogic` instance.
180 ///
181 /// # Arguments
182 ///
183 /// * `storage` - A mutable reference to the storage implementation.
184 /// * `context` - The execution context for the VM.
185 /// * `limits` - The VM resource limits to enforce.
186 /// * `node_client` - An optional client for blob storage operations.
187 pub fn new(
188 storage: &'a mut dyn Storage,
189 context: VMContext<'a>,
190 limits: &'a VMLimits,
191 node_client: Option<NodeClient>,
192 ) -> Self {
193 VMLogic {
194 storage,
195 memory: None,
196 context,
197 limits,
198 registers: Registers::default(),
199 returns: None,
200 logs: vec![],
201 events: vec![],
202 root_hash: None,
203 artifact: vec![],
204 proposals: BTreeMap::new(),
205 approvals: vec![],
206
207 // Blob functionality
208 node_client,
209 blob_handles: HashMap::new(),
210 next_blob_fd: 1,
211 }
212 }
213
214 /// Associates a Wasmer memory instance with this `VMLogic`.
215 ///
216 /// This method should be called after the guest module is instantiated but before
217 /// any host functions are called.
218 ///
219 /// # Arguments
220 ///
221 /// * `memory` - The `wasmer::Memory` instance from the instantiated guest module.
222 pub fn with_memory(&mut self, memory: wasmer::Memory) -> &mut Self {
223 self.memory = Some(memory);
224 self
225 }
226
227 /// Creates a `VMHostFunctions` instance to be imported by the guest module.
228 ///
229 /// This method builds the self-referential struct that provides guest code
230 /// with access to host capabilities.
231 ///
232 /// # Panics
233 ///
234 /// Panics if `with_memory` has not been called first.
235 ///
236 /// # Arguments
237 ///
238 /// * `store` - A mutable view of the Wasmer store.
239 pub fn host_functions(&'a mut self, store: wasmer::StoreMut<'a>) -> VMHostFunctions<'a> {
240 // TODO: review the `clone()` and figure out if the function should be a one-time call only.
241 let memory = self.memory.clone().expect("VM Memory not initialized");
242
243 VMHostFunctionsBuilder {
244 logic: self,
245 store,
246
247 memory_builder: |store| memory.view(store),
248 }
249 .build()
250 }
251}
252
253/// Represents the final outcome of a VM execution.
254///
255/// This struct aggregates all the results and side effects of function calls,
256/// such as the return value, logs, events, and state changes.
257#[derive(Debug, Serialize)]
258#[non_exhaustive]
259pub struct Outcome {
260 /// The result of the execution. `Ok(Some(value))` for a successful return,
261 /// `Ok(None)` for no return, and `Err` for a trap or execution error.
262 pub returns: VMLogicResult<Option<Vec<u8>>, FunctionCallError>,
263 /// All log messages generated during the execution.
264 pub logs: Vec<String>,
265 /// All events emitted during the execution.
266 pub events: Vec<Event>,
267 /// The new state root hash if there were commits during the execution.
268 pub root_hash: Option<[u8; DIGEST_SIZE]>,
269 /// The binary artifact produced if there were commits during the execution.
270 //TODO: why the artifact is not an Option?
271 pub artifact: Vec<u8>,
272 /// A map of proposals created during execution, having proposal ID as a key.
273 pub proposals: BTreeMap<[u8; DIGEST_SIZE], Vec<u8>>,
274 /// A list of approvals submitted during execution.
275 pub approvals: Vec<[u8; DIGEST_SIZE]>,
276 //TODO: execution runtime (???).
277 //TODO: current storage usage of the app (???).
278}
279
280impl VMLogic<'_> {
281 /// Consumes the `VMLogic` instance and produces the final `Outcome`.
282 ///
283 /// This method should be called after the guest function has finished executing
284 /// or has trapped. It packages the final state of the `VMLogic` into an
285 /// `Outcome` struct.
286 ///
287 /// # Arguments
288 ///
289 /// * `err` - An optional `FunctionCallError` that occurred during execution (e.g., a trap).
290 /// If `None`, the outcome is determined by the `returns` field.
291 #[must_use]
292 pub fn finish(self, err: Option<FunctionCallError>) -> Outcome {
293 let returns = match err {
294 Some(err) => Err(err),
295 None => self
296 .returns
297 .map(|t| t.map_err(FunctionCallError::ExecutionError))
298 .transpose(),
299 };
300
301 Outcome {
302 returns,
303 logs: self.logs,
304 events: self.events,
305 root_hash: self.root_hash,
306 artifact: self.artifact,
307 proposals: self.proposals,
308 approvals: self.approvals,
309 }
310 }
311}
312
313/// A self-referential struct that holds the `VMLogic`, the Wasmer `StoreMut`,
314/// and a `MemoryView` derived from them.
315///
316/// This structure is necessary to safely provide guest access to host functions,
317/// hold the reference to guest memory (`MemoryView`) simultaneously. The `ouroboros`
318/// crate ensures that the lifetimes are managed correctly.
319#[self_referencing]
320pub struct VMHostFunctions<'a> {
321 logic: &'a mut VMLogic<'a>,
322 store: wasmer::StoreMut<'a>,
323
324 #[covariant]
325 #[borrows(store)]
326 memory: wasmer::MemoryView<'this>,
327}
328
329// Private helper functions for memory access.
330impl VMHostFunctions<'_> {
331 /// Reads an IMMUTABLE slice of guest memory.
332 /// This should be used when the host needs to READ from a buffer
333 /// provided by the guest.
334 ///
335 /// # Arguments
336 ///
337 /// * `slice` - A `sys::Buffer` descriptor pointing to the buffer location and length in guest memory.
338 ///
339 /// # Returns
340 ///
341 /// * Immutable slice of the guest memory contents.
342 fn read_guest_memory_slice(&self, slice: &sys::Buffer<'_>) -> &[u8] {
343 let ptr = slice.ptr().value().as_usize();
344 let len = slice.len() as usize;
345
346 unsafe { &self.borrow_memory().data_unchecked()[ptr..ptr + len] }
347 }
348
349 /// Reads a MUTABLE slice of guest memory.
350 /// This should only be used when the host needs to WRITE into a buffer
351 /// provided by the guest.
352 ///
353 /// # Arguments
354 ///
355 /// * `slice` - A `sys::Buffer` descriptor pointing to the buffer location and length in guest memory.
356 ///
357 /// # Returns
358 ///
359 /// * Mutable slice of the guest memory contents.
360 #[allow(
361 clippy::mut_from_ref,
362 reason = "We are not modifying the self explicitly, only the underlying slice of the guest memory.\
363 Meantime we are required to have an immutable reference to self, hence the exception"
364 )]
365 fn read_guest_memory_slice_mut(&self, slice: &sys::BufferMut<'_>) -> &mut [u8] {
366 let ptr = slice.ptr().value().as_usize();
367 let len = slice.len() as usize;
368
369 unsafe { &mut self.borrow_memory().data_unchecked_mut()[ptr..ptr + len] }
370 }
371
372 /// Reads an immutable UTF-8 string slice from guest memory.
373 ///
374 /// # Arguments
375 ///
376 /// * `slice` - A `sys::Buffer` descriptor pointing to the buffer location and length in guest memory.
377 ///
378 /// # Returns
379 ///
380 /// * `VMLogicResult(Ok(utf8_slice))`
381 ///
382 /// # Errors
383 ///
384 /// Returns `VMLogicError::HostError(HostError::BadUTF8)` if the memory slice
385 /// does not contain valid UTF-8 data.
386 fn read_guest_memory_str(&self, slice: &sys::Buffer<'_>) -> VMLogicResult<&str> {
387 let buf = self.read_guest_memory_slice(slice);
388
389 std::str::from_utf8(buf).map_err(|_| HostError::BadUTF8.into())
390 }
391
392 /// Reads a fixed-size IMMUTABLE array slice from guest memory.
393 ///
394 /// # Arguments
395 ///
396 /// * `slice` - A `sys::Buffer` descriptor pointing to the buffer location and length in guest memory.
397 ///
398 /// # Errors
399 ///
400 /// Returns `VMLogicError::HostError(HostError::InvalidMemoryAccess)` if the buffer
401 /// length in guest memory does not exactly match the requested array size `N`.
402 fn read_guest_memory_sized<const N: usize>(
403 &self,
404 slice: &sys::Buffer<'_>,
405 ) -> VMLogicResult<&[u8; N]> {
406 let buf = self.read_guest_memory_slice(slice);
407
408 buf.try_into()
409 .map_err(|_| HostError::InvalidMemoryAccess.into())
410 }
411
412 /// Reads a sized type from guest memory.
413 /// Reads a `Sized` type `T` from a specified location in guest memory.
414 ///
415 /// # Arguments
416 ///
417 /// * `ptr` - The memory address in the guest's linear memory where the type `T` is located.
418 ///
419 /// # Errors
420 ///
421 /// Can return a `Host::InvalidMemoryAccesssError` if the read operation goes out of bounds.
422 ///
423 /// # Safety
424 ///
425 /// This function is unsafe because:
426 /// 1. It reads raw bytes from guest memory and interprets them as type `T`. The caller
427 /// must ensure that the bytes at `ptr` represent a valid instance of `T`.
428 /// 2. It relies on `ptr` being a valid, aligned pointer within the guest memory.
429 //TODO: refactor to use `sys::Buffer` instead of `ptr`.
430 unsafe fn read_guest_memory_typed<T>(&self, ptr: u64) -> VMLogicResult<T> {
431 let mut value = MaybeUninit::<T>::uninit();
432
433 let raw = slice::from_raw_parts_mut(value.as_mut_ptr().cast::<u8>(), size_of::<T>());
434
435 self.borrow_memory().read(ptr, raw)?;
436
437 Ok(value.assume_init())
438 }
439}
440
441#[cfg(test)]
442mod tests {
443 use super::*;
444 use crate::store::{Key, Value};
445 use core::ops::Deref;
446 use wasmer::{AsStoreMut, Store};
447
448 // The descriptor has the size of 16-bytes with the layout `{ ptr: u64, len: u64 }`
449 // See below: [`prepare_guest_buf_descriptor`]
450 pub const DESCRIPTOR_SIZE: usize = u64::BITS as usize / 8 * 2;
451
452 // This implementation is more suitable for testing host-side components
453 // in comparison to `store::MockedStorage` which is a better for guest-side
454 // tests - e.g. testing Calimero application contracts.
455 // This version minimally satisfies the `Storage` trait without introducing
456 // the global state, guaranteed having a proper test isolation and not having a risk
457 // to collide with other tests due to developer error, too (i.e. developer accidentally
458 // used the same scope for the global state of `store::MockedStorage` in two different
459 // tests).
460 pub struct SimpleMockStorage {
461 data: HashMap<Vec<u8>, Vec<u8>>,
462 }
463
464 impl SimpleMockStorage {
465 pub fn new() -> Self {
466 Self {
467 data: HashMap::new(),
468 }
469 }
470 }
471
472 impl Storage for SimpleMockStorage {
473 fn get(&self, key: &Key) -> Option<Value> {
474 self.data.get(key).cloned()
475 }
476
477 fn set(&mut self, key: Key, value: Value) -> Option<Value> {
478 self.data.insert(key, value)
479 }
480
481 fn remove(&mut self, key: &Key) -> Option<Value> {
482 self.data.remove(key)
483 }
484
485 fn has(&self, key: &Key) -> bool {
486 self.data.contains_key(key)
487 }
488 }
489
490 /// A macro to set up the VM environment within a test.
491 /// It takes references to storage and limits, which are owned by the test function,
492 /// ensuring that all lifetimes are valid.
493 macro_rules! setup_vm {
494 ($storage:expr, $limits:expr, $input:expr) => {{
495 let context =
496 VMContext::new(Cow::Owned($input), [0u8; DIGEST_SIZE], [0u8; DIGEST_SIZE]);
497 let mut store = Store::default();
498 let memory =
499 wasmer::Memory::new(&mut store, wasmer::MemoryType::new(1, None, false)).unwrap();
500 let mut logic = VMLogic::new($storage, context, $limits, None);
501 let _ = logic.with_memory(memory);
502 (logic, store)
503 }};
504 }
505
506 // Export macros to the module level, so it can be reused in other host functions' tests.
507 pub(super) use setup_vm;
508
509 /// Helper to write a similar to `sys::Buffer` struct representation to memory.
510 /// Simulates a WASM guest preparing a memory descriptor for a host call.
511 ///
512 /// # Why this is necessary
513 /// When a WASM guest needs the host to read/write a slice of its memory, it cannot
514 /// pass a slice directly. Instead, it must pass a pointer to a "descriptor" structure
515 /// that exists within guest's memory. This descriptor tells the host where the
516 /// actual data is (`ptr`) and how long it is (`len`). This function simulates the guest
517 /// writing that descriptor into the mock memory.
518 ///
519 /// # Parameters
520 /// - `host`: A reference to the `VMHostFunctions` to get access to the guest memory view.
521 /// - `offset`: The address of the descriptor struct itself in the guest memory.
522 /// This is the pointer that the guest would pass to the host function.
523 /// - `ptr`: The address of the actual data payload (e.g., a string or byte array) in the
524 /// guest memory. This value is written inside the descriptor structure.
525 /// - `len`: The length of the data payload. This value is also written inside the
526 /// descriptor structure.
527 ///
528 /// # ABI and Memory Layout
529 /// Although the guest is `wasm32` and uses `u32` pointers internally, the host-guest
530 /// ABI often standardizes on `u64` for all pointers and lengths for consistency and
531 /// forward-compatibility with `wasm64`. Therefore, this function writes both `ptr` and `len`
532 /// as `u64`, creating a 16-byte descriptor in memory with the layout `{ ptr: u64, len: u64 }`.
533 /// All values are little-endian, as required by the WebAssembly specification.
534 pub fn prepare_guest_buf_descriptor(
535 host: &VMHostFunctions<'_>,
536 offset: u64,
537 ptr: u64,
538 len: u64,
539 ) {
540 let data: Vec<u8> = [ptr.to_le_bytes(), len.to_le_bytes()].concat();
541
542 host.borrow_memory()
543 .write(offset, &data)
544 .expect("Failed to write buffer");
545 }
546
547 /// A test helper to write a string slice directly into the guest's mock memory.
548 ///
549 /// This simulates the guest having string data (e.g., a log message, a storage key)
550 /// in its linear memory, making it available for the host to read.
551 ///
552 /// # Parameters
553 /// - `host`: A reference to the `VMHostFunctions` to get access to the guest memory view.
554 /// - `offset`: The memory address where the string's byte data will be written.
555 /// - `s`: The string slice to write into the guest's memory.
556 pub fn write_str(host: &VMHostFunctions<'_>, offset: u64, s: &str) {
557 host.borrow_memory()
558 .write(offset, s.as_bytes())
559 .expect("Failed to write string");
560 }
561
562 /// A simple sanity check to ensure the default `VMLimits` are configured as expected.
563 /// This test helps prevent accidental changes to the default limits.
564 #[test]
565 fn test_default_limits() {
566 let limits = VMLimits::default();
567 assert_eq!(limits.max_memory_pages, 1 << 10);
568 assert_eq!(limits.max_stack_size, 200 << 10);
569 assert_eq!(limits.max_registers, 100);
570 assert_eq!(*limits.max_register_size.deref(), 100 << 20);
571 assert_eq!(limits.max_registers_capacity, 1 << 30); // 1 GiB
572 assert_eq!(limits.max_logs, 100);
573 assert_eq!(limits.max_log_size, 16 << 10); // 16 KiB
574 assert_eq!(limits.max_events, 100);
575 assert_eq!(limits.max_event_kind_size, 100);
576 assert_eq!(limits.max_event_data_size, 16 << 10); // 16 KiB
577 assert_eq!(limits.max_storage_key_size.get(), 1 << 20); // 1 MiB
578 assert_eq!(limits.max_storage_value_size.get(), 10 << 20); // 10 MiB
579 assert_eq!(limits.max_blob_handles, 100);
580 assert_eq!(limits.max_blob_chunk_size, 10 << 20); // 10 MiB
581 }
582
583 /// A smoke test for the successful path of the `finish` method.
584 ///
585 /// This test simulates a VM execution that successfully finished by
586 /// calling `finish(None)` and asserts that the `returns` field in
587 /// the final `Outcome` is an `Ok`, ensuring the error is propagated correctly.
588 #[test]
589 fn test_smoke_finish() {
590 let mut storage = SimpleMockStorage::new();
591 let limits = VMLimits::default();
592 let (logic, _) = setup_vm!(&mut storage, &limits, vec![1, 2, 3]);
593 let outcome = logic.finish(None);
594
595 assert!(outcome.returns.is_ok());
596 }
597
598 /// A smoke test for the error-handling path of the `finish` method.
599 ///
600 /// This test simulates a VM execution that failed by calling `finish(Some(Error))`
601 /// and asserts that the `returns` field in the final `Outcome` is an `Err`,
602 /// ensuring the error is propagated correctly.
603 #[test]
604 fn test_smoke_finish_with_error() {
605 let mut storage = SimpleMockStorage::new();
606 let limits = VMLimits::default();
607 let (logic, _) = setup_vm!(&mut storage, &limits, vec![]);
608 let outcome = logic.finish(Some(FunctionCallError::ExecutionError(vec![])));
609 assert!(outcome.returns.is_err());
610 }
611
612 // ===========================================================================
613 // Tests for private functions
614 // ===========================================================================
615
616 /// Verifies the success path of the private `read_guest_memory_slice` and `read_guest_memory_str` functions.
617 #[test]
618 fn test_private_read_guest_memory_slice_and_str_success() {
619 let mut storage = SimpleMockStorage::new();
620 let limits = VMLimits::default();
621 let (mut logic, mut store) = setup_vm!(&mut storage, &limits, vec![]);
622 let host = logic.host_functions(store.as_store_mut());
623
624 let expected_str = "hello world";
625 let data_ptr = 100u64;
626 // Write msg to guest memory.
627 write_str(&host, data_ptr, expected_str);
628 let buf_ptr = 16u64;
629 // Guest: prepare the descriptor for the destination buffer so host can access it.
630 prepare_guest_buf_descriptor(&host, buf_ptr, data_ptr, expected_str.len() as u64);
631
632 // Use `read_guest_memory_typed` to get a `sys::Buffer` instance,
633 // just like public host functions do internally.
634 let buffer = unsafe {
635 host.read_guest_memory_typed::<sys::Buffer<'_>>(buf_ptr)
636 .unwrap()
637 };
638
639 // Guest: ask host to read str from the `buffer` located in guest memory.
640 let result_str = host.read_guest_memory_str(&buffer).unwrap();
641 assert_eq!(result_str, expected_str);
642
643 // Guest: ask host to read slice from the `buffer` located in guest memory.
644 let result_slice = host.read_guest_memory_slice(&buffer);
645 assert_eq!(result_slice, expected_str.as_bytes());
646 }
647
648 /// Verifies the `read_guest_memory_slice` function can't modify the guest buffer.
649 #[test]
650 fn test_private_read_guest_memory_slice_unmutable() {
651 let mut storage = SimpleMockStorage::new();
652 let limits = VMLimits::default();
653 let (mut logic, mut store) = setup_vm!(&mut storage, &limits, vec![]);
654 let host = logic.host_functions(store.as_store_mut());
655
656 let expected_str = "hello world";
657 let data_ptr = 100u64;
658 // Write msg to guest memory.
659 write_str(&host, data_ptr, expected_str);
660 let buf_ptr = 16u64;
661 // Guest: prepare the descriptor for the destination buffer so host can access it.
662 prepare_guest_buf_descriptor(&host, buf_ptr, data_ptr, expected_str.len() as u64);
663
664 // Use `read_guest_memory_typed` to get a `sys::Buffer` instance,
665 // just like public host functions do internally.
666 let buffer = unsafe {
667 host.read_guest_memory_typed::<sys::Buffer<'_>>(buf_ptr)
668 .unwrap()
669 };
670
671 // Guest: ask host to read slice from the `buffer` located in guest memory.
672 let result_slice = host.read_guest_memory_slice(&buffer);
673 assert_eq!(result_slice, expected_str.as_bytes());
674
675 // Now, this code won't be compilable as we get an immutable ref.
676 // Host: modify the memory (this could happen accidentally and not intended).
677 // ```compile_fail
678 // for value in result_slice.iter() {
679 // }
680 // ```
681
682 // Guest: ask host to read str from the `buffer` located in guest memory.
683 let result_str = host.read_guest_memory_str(&buffer).unwrap();
684 assert_eq!(result_str, expected_str);
685 }
686
687 /// Verifies the error handling of the private `read_guest_memory_str` function for invalid UTF-8.
688 #[test]
689 fn test_private_read_guest_memory_str_invalid_utf8() {
690 let mut storage = SimpleMockStorage::new();
691 let limits = VMLimits::default();
692 let (mut logic, mut store) = setup_vm!(&mut storage, &limits, vec![]);
693 let host = logic.host_functions(store.as_store_mut());
694
695 let invalid_utf8: &[u8] = &[0, 159, 146, 150];
696 let data_ptr = 100u64;
697 // Write invalid utf8 buffer to the guest memory
698 host.borrow_memory().write(data_ptr, invalid_utf8).unwrap();
699 let buf_ptr = 16u64;
700 // Guest: prepare the descriptor for the destination buffer so host can access it.
701 prepare_guest_buf_descriptor(&host, buf_ptr, data_ptr, invalid_utf8.len() as u64);
702
703 // Use `read_guest_memory_typed` to get a `sys::Buffer` instance,
704 // just like public host functions do internally.
705 let buffer = unsafe {
706 host.read_guest_memory_typed::<sys::Buffer<'_>>(buf_ptr)
707 .unwrap()
708 };
709
710 // Test that `read_guest_memory_str` fails as expected.
711 let err = host.read_guest_memory_str(&buffer).unwrap_err();
712 assert!(matches!(err, VMLogicError::HostError(HostError::BadUTF8)));
713 }
714
715 /// Verifies the success and failure paths of the private `read_guest_memory_sized` function.
716 #[test]
717 fn test_private_read_guest_memory_sized() {
718 let mut storage = SimpleMockStorage::new();
719 let limits = VMLimits::default();
720 let (mut logic, mut store) = setup_vm!(&mut storage, &limits, vec![]);
721 let host = logic.host_functions(store.as_store_mut());
722
723 // Test success case.
724 let correct_data = [42u8; DIGEST_SIZE];
725 let data_ptr_ok = 100u64;
726 // Write correct data to guest memory.
727 host.borrow_memory()
728 .write(data_ptr_ok, &correct_data)
729 .unwrap();
730 let buf_ptr_ok = 16u64;
731 // Guest: prepare the descriptor for the destination buffer so host can access it.
732 prepare_guest_buf_descriptor(&host, buf_ptr_ok, data_ptr_ok, correct_data.len() as u64);
733
734 // Use `read_guest_memory_typed` to get a `sys::Buffer` instance,
735 // just like public host functions do internally.
736 let buffer_ok = unsafe {
737 host.read_guest_memory_typed::<sys::Buffer<'_>>(buf_ptr_ok)
738 .unwrap()
739 };
740 let result_sized_ok = host
741 .read_guest_memory_sized::<DIGEST_SIZE>(&buffer_ok)
742 .unwrap();
743 assert_eq!(result_sized_ok, &correct_data);
744
745 // Test failure case (incorrect length).
746 let incorrect_data = [1u8; 31];
747 let data_ptr_err = 300u64;
748 // Write incorrect data to guest memory.
749 host.borrow_memory()
750 .write(data_ptr_err, &incorrect_data)
751 .unwrap();
752 let buf_ptr_err = 32u64;
753 // Guest: prepare the descriptor for the destination buffer so host can access it.
754 prepare_guest_buf_descriptor(
755 &host,
756 buf_ptr_err,
757 data_ptr_err,
758 incorrect_data.len() as u64,
759 );
760
761 // Use `read_guest_memory_typed` to get a `sys::Buffer` instance,
762 // just like public host functions do internally.
763 let buffer_err = unsafe {
764 host.read_guest_memory_typed::<sys::Buffer<'_>>(buf_ptr_err)
765 .unwrap()
766 };
767 // Guest: ask host to read the guest memory sized.
768 let err = host
769 .read_guest_memory_sized::<DIGEST_SIZE>(&buffer_err)
770 .unwrap_err();
771 assert!(matches!(
772 err,
773 VMLogicError::HostError(HostError::InvalidMemoryAccess)
774 ));
775 }
776}