calimero_runtime/logic/host_functions/
utility.rs

1use borsh::from_slice as from_borsh_slice;
2use rand::RngCore;
3use std::time::{SystemTime, UNIX_EPOCH};
4
5use crate::{
6    errors::HostError,
7    logic::{sys, VMHostFunctions, VMLogicResult},
8};
9
10impl VMHostFunctions<'_> {
11    /// Fetches data from a URL.
12    ///
13    /// Performs an HTTP request. This is a synchronous, blocking operation.
14    /// The response body is placed into the specified host register.
15    ///
16    /// # Arguments
17    ///
18    /// * `src_url_ptr` - Pointer to the URL string source-buffer in guest memory.
19    /// * `src_method_ptr` - Pointer to the HTTP method string source-buffer (e.g., "GET", "POST")
20    /// in guest memory.
21    /// * `src_headers_ptr` - Pointer to a borsh-serialized `Vec<(String, String)>` source-buffer of
22    /// headers in guest memory.
23    /// * `src_body_ptr` - Pointer to the request body source-buffer in guest memory.
24    /// * `dest_register_id` - The ID of the destination register in host memory where to store
25    /// the response body.
26    ///
27    /// # Returns
28    ///
29    /// * Returns `0` on success (HTTP 2xx)
30    /// * Returns `1` on failure.
31    /// The response body or error message is placed in the host register.
32    ///
33    /// # Errors
34    ///
35    /// * `HostError::DeserializationError` if the headers cannot be deserialized.
36    /// * `HostError::InvalidMemoryAccess` if memory access fails for descriptor buffers.
37    pub fn fetch(
38        &mut self,
39        src_url_ptr: u64,
40        src_method_ptr: u64,
41        src_headers_ptr: u64,
42        src_body_ptr: u64,
43        dest_register_id: u64,
44    ) -> VMLogicResult<u32> {
45        let url = unsafe { self.read_guest_memory_typed::<sys::Buffer<'_>>(src_url_ptr)? };
46        let method = unsafe { self.read_guest_memory_typed::<sys::Buffer<'_>>(src_method_ptr)? };
47        let headers = unsafe { self.read_guest_memory_typed::<sys::Buffer<'_>>(src_headers_ptr)? };
48        let body = unsafe { self.read_guest_memory_typed::<sys::Buffer<'_>>(src_body_ptr)? };
49
50        let url = self.read_guest_memory_str(&url)?;
51        let method = self.read_guest_memory_str(&method)?;
52
53        let headers = self.read_guest_memory_slice(&headers);
54        let body = self.read_guest_memory_slice(&body);
55
56        // TODO: clarify why the `fetch` function cannot be directly called by applications.
57        // Note: The `fetch` function cannot be directly called by applications.
58        // Therefore, the headers are generated exclusively by our code, ensuring
59        // that it is safe to deserialize them.
60        let headers: Vec<(String, String)> =
61            from_borsh_slice(headers).map_err(|_| HostError::DeserializationError)?;
62
63        let mut request = ureq::request(&method, &url);
64
65        for (key, value) in &headers {
66            request = request.set(key, value);
67        }
68
69        let response = if body.is_empty() {
70            request.call()
71        } else {
72            request.send_bytes(body)
73        };
74
75        let (status, data) = match response {
76            Ok(response) => {
77                let mut buffer = vec![];
78                match response.into_reader().read_to_end(&mut buffer) {
79                    Ok(_) => (0, buffer),
80                    Err(_) => (1, "Failed to read the response body.".into()),
81                }
82            }
83            Err(e) => (1, e.to_string().into_bytes()),
84        };
85
86        self.with_logic_mut(|logic| logic.registers.set(logic.limits, dest_register_id, data))?;
87        Ok(status)
88    }
89
90    /// Fills a guest memory buffer with random bytes.
91    ///
92    /// # Arguments
93    ///
94    /// * `dest_ptr` - A destination pointer to a `sys::BufferMut` in guest memory to be filled.
95    ///
96    /// # Errors
97    ///
98    /// * `HostError::InvalidMemoryAccess` if memory access fails for a descriptor buffer.
99    pub fn random_bytes(&mut self, dest_ptr: u64) -> VMLogicResult<()> {
100        let dest_buf = unsafe { self.read_guest_memory_typed::<sys::BufferMut<'_>>(dest_ptr)? };
101
102        rand::thread_rng().fill_bytes(self.read_guest_memory_slice_mut(&dest_buf));
103
104        Ok(())
105    }
106
107    /// Gets the current Unix timestamp in nanoseconds.
108    ///
109    /// This function obtains the current time as a nanosecond timestamp, as
110    /// [`SystemTime`] is not available inside the guest runtime. Therefore the
111    /// guest needs to request this from the host.
112    ///
113    /// The result is written into a guest buffer as a `u64`.
114    ///
115    /// # Arguments
116    ///
117    /// * `dest_ptr` - A pointer to an 8-byte destination buffer `sys::BufferMut`
118    /// in guest memory where the `u64` timestamp will be written.
119    ///
120    /// # Errors
121    ///
122    /// * `HostError::InvalidMemoryAccess` if the provided buffer is not exactly 8 bytes long
123    /// or if memory access fails for a descriptor buffer.
124    #[expect(
125        clippy::cast_possible_truncation,
126        reason = "Impossible to overflow in normal circumstances"
127    )]
128    #[expect(
129        clippy::expect_used,
130        clippy::unwrap_in_result,
131        reason = "Effectively infallible here"
132    )]
133    pub fn time_now(&mut self, dest_ptr: u64) -> VMLogicResult<()> {
134        let guest_time_ptr =
135            unsafe { self.read_guest_memory_typed::<sys::BufferMut<'_>>(dest_ptr)? };
136
137        if guest_time_ptr.len() != 8 {
138            return Err(HostError::InvalidMemoryAccess.into());
139        }
140
141        let now = SystemTime::now()
142            .duration_since(UNIX_EPOCH)
143            .expect("Time went backwards to before the Unix epoch!")
144            .as_nanos() as u64;
145
146        // Record the time into the guest memory buffer
147        let guest_time_out_buf: &mut [u8] = self.read_guest_memory_slice_mut(&guest_time_ptr);
148        guest_time_out_buf.copy_from_slice(&now.to_le_bytes());
149
150        Ok(())
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    use crate::logic::{
159        tests::{prepare_guest_buf_descriptor, setup_vm, SimpleMockStorage},
160        Cow, VMContext, VMLimits, VMLogic, DIGEST_SIZE,
161    };
162    use wasmer::{AsStoreMut, Store};
163
164    #[test]
165    fn test_random_bytes() {
166        let mut storage = SimpleMockStorage::new();
167        let limits = VMLimits::default();
168        let (mut logic, mut store) = setup_vm!(&mut storage, &limits, vec![]);
169        let mut host = logic.host_functions(store.as_store_mut());
170
171        let buf_ptr = 10u64;
172        let data_ptr = 200u64;
173        let data_len = 32u64;
174
175        // Explicitly fill the memory with some pattern before the host call.
176        // This makes the test deterministic (for CI) and ensures it fails
177        // correctly if the function under this test does not write to the buffer.
178        let initial_pattern = vec![0xAB; data_len as usize];
179        host.borrow_memory()
180            .write(data_ptr, &initial_pattern)
181            .unwrap();
182
183        // Guest: prepare the descriptor for the destination buffer so host can write there.
184        prepare_guest_buf_descriptor(&host, buf_ptr, data_ptr, data_len);
185
186        // Guest: ask host to fill the buffer with random bytes
187        host.random_bytes(buf_ptr).unwrap();
188
189        // Host: perform a priveleged read of the contents of guest's memory to extract the buffer
190        // back.
191        let mut random_data = vec![0u8; data_len as usize];
192        host.borrow_memory()
193            .read(data_ptr, &mut random_data)
194            .unwrap();
195
196        // Assert that the memory content has changed from our initial pattern.
197        assert_ne!(
198            random_data, initial_pattern,
199            "The data buffer should have been overwritten with random bytes, but it was not."
200        );
201    }
202
203    /// Tests the `time_now()` host function.
204    #[test]
205    fn test_time_now() {
206        let mut storage = SimpleMockStorage::new();
207        let limits = VMLimits::default();
208        let (mut logic, mut store) = setup_vm!(&mut storage, &limits, vec![]);
209        let mut host = logic.host_functions(store.as_store_mut());
210
211        let buf_ptr = 16u64;
212        let time_data_ptr = 200u64;
213        // The `time_now()` function expects an 8-byte buffer to write the u64 timestamp.
214        let time_data_len = u64::BITS as u64 / 8;
215        // Guest: prepare the descriptor for the destination buffer so host can write there.
216        prepare_guest_buf_descriptor(&host, buf_ptr, time_data_ptr, time_data_len);
217
218        // Record the host's system time before the host-function call.
219        let time_before = SystemTime::now()
220            .duration_since(UNIX_EPOCH)
221            .unwrap()
222            .as_nanos() as u64;
223
224        // Guest: ask the host to provide the current timestamp and write it to the buffer.
225        host.time_now(buf_ptr).unwrap();
226
227        // Record the host's system time after the host-function call.
228        let time_after = SystemTime::now()
229            .duration_since(UNIX_EPOCH)
230            .unwrap()
231            .as_nanos() as u64;
232
233        // Host: read the timestamp back from guest memory.
234        let mut time_buffer = [0u8; 8];
235        host.borrow_memory()
236            .read(time_data_ptr, &mut time_buffer)
237            .unwrap();
238        let timestamp_from_host = u64::from_le_bytes(time_buffer);
239
240        // Verify the timestamp is current and valid (within the before-after range).
241        assert!(timestamp_from_host >= time_before);
242        assert!(timestamp_from_host <= time_after);
243    }
244}