Skip to main content

freenet_stdlib/
delegate_host.rs

1//! Host function API for delegates.
2//!
3//! This module provides synchronous access to delegate context and secrets
4//! via host functions, eliminating the need for message round-trips.
5//!
6//! # Example
7//!
8//! ```ignore
9//! use freenet_stdlib::prelude::*;
10//!
11//! #[delegate]
12//! impl DelegateInterface for MyDelegate {
13//!     fn process(
14//!         ctx: &mut DelegateCtx,
15//!         _params: Parameters<'static>,
16//!         _attested: Option<&'static [u8]>,
17//!         message: InboundDelegateMsg,
18//!     ) -> Result<Vec<OutboundDelegateMsg>, DelegateError> {
19//!         // Read/write temporary context
20//!         let data = ctx.read();
21//!         ctx.write(b"new state");
22//!
23//!         // Access persistent secrets
24//!         if let Some(key) = ctx.get_secret(b"private_key") {
25//!             // use key...
26//!         }
27//!         ctx.set_secret(b"new_secret", b"value");
28//!
29//!         Ok(vec![])
30//!     }
31//! }
32//! ```
33//!
34//! # Context vs Secrets
35//!
36//! - **Context** (`read`/`write`): Temporary state within a single message batch.
37//!   Reset between separate runtime calls. Use for intermediate processing state.
38//!
39//! - **Secrets** (`get_secret`/`set_secret`): Persistent encrypted storage.
40//!   Survives across all delegate invocations. Use for private keys, tokens, etc.
41//!
42//! # Error Codes
43//!
44//! Host functions return negative values to indicate errors:
45//!
46//! | Code | Meaning |
47//! |------|---------|
48//! | 0    | Success |
49//! | -1   | Called outside process() context |
50//! | -2   | Secret not found |
51//! | -3   | Storage operation failed |
52//! | -4   | Invalid parameter (e.g., negative length) |
53//! | -5   | Context too large (exceeds i32::MAX) |
54//! | -6   | Buffer too small |
55//!
56//! The wrapper methods in [`DelegateCtx`] handle these error codes and present
57//! a more ergonomic API.
58
59/// Error codes returned by host functions.
60///
61/// Negative values indicate errors, non-negative values indicate success
62/// (usually the number of bytes read/written).
63pub mod error_codes {
64    /// Operation succeeded.
65    pub const SUCCESS: i32 = 0;
66    /// Called outside of a process() context.
67    pub const ERR_NOT_IN_PROCESS: i32 = -1;
68    /// Secret not found.
69    pub const ERR_SECRET_NOT_FOUND: i32 = -2;
70    /// Storage operation failed.
71    pub const ERR_STORAGE_FAILED: i32 = -3;
72    /// Invalid parameter (e.g., negative length).
73    pub const ERR_INVALID_PARAM: i32 = -4;
74    /// Context too large (exceeds i32::MAX).
75    pub const ERR_CONTEXT_TOO_LARGE: i32 = -5;
76    /// Buffer too small to hold the data.
77    pub const ERR_BUFFER_TOO_SMALL: i32 = -6;
78}
79
80// ============================================================================
81// Host function declarations (WASM only)
82// ============================================================================
83
84#[cfg(target_family = "wasm")]
85#[link(wasm_import_module = "freenet_delegate_ctx")]
86extern "C" {
87    /// Returns the current context length in bytes, or negative error code.
88    fn __frnt__delegate__ctx_len() -> i32;
89    /// Reads context into the buffer at `ptr` (max `len` bytes). Returns bytes written, or negative error code.
90    fn __frnt__delegate__ctx_read(ptr: i64, len: i32) -> i32;
91    /// Writes `len` bytes from `ptr` into the context, replacing existing content. Returns 0 on success, or negative error code.
92    fn __frnt__delegate__ctx_write(ptr: i64, len: i32) -> i32;
93}
94
95#[cfg(target_family = "wasm")]
96#[link(wasm_import_module = "freenet_delegate_secrets")]
97extern "C" {
98    /// Get a secret. Returns bytes written to `out_ptr`, or negative error code.
99    fn __frnt__delegate__get_secret(key_ptr: i64, key_len: i32, out_ptr: i64, out_len: i32) -> i32;
100    /// Get secret length without fetching value. Returns length, or negative error code.
101    fn __frnt__delegate__get_secret_len(key_ptr: i64, key_len: i32) -> i32;
102    /// Store a secret. Returns 0 on success, or negative error code.
103    fn __frnt__delegate__set_secret(key_ptr: i64, key_len: i32, val_ptr: i64, val_len: i32) -> i32;
104    /// Check if a secret exists. Returns 1 if yes, 0 if no, or negative error code.
105    fn __frnt__delegate__has_secret(key_ptr: i64, key_len: i32) -> i32;
106    /// Remove a secret. Returns 0 on success, or negative error code.
107    fn __frnt__delegate__remove_secret(key_ptr: i64, key_len: i32) -> i32;
108}
109
110// ============================================================================
111// DelegateCtx - Unified handle to context and secrets
112// ============================================================================
113
114/// Opaque handle to the delegate's execution environment.
115///
116/// Provides access to both:
117/// - **Temporary context**: State shared within a single message batch (reset between calls)
118/// - **Persistent secrets**: Encrypted storage that survives across all invocations
119///
120/// # Context Methods
121/// - [`read`](Self::read), [`write`](Self::write), [`len`](Self::len), [`clear`](Self::clear)
122///
123/// # Secret Methods
124/// - [`get_secret`](Self::get_secret), [`set_secret`](Self::set_secret),
125///   [`has_secret`](Self::has_secret), [`remove_secret`](Self::remove_secret)
126#[derive(Default)]
127#[repr(transparent)]
128pub struct DelegateCtx {
129    _private: (),
130}
131
132impl DelegateCtx {
133    /// Creates the context handle.
134    ///
135    /// # Safety
136    ///
137    /// This should only be called by macro-generated code when the runtime
138    /// has set up the delegate execution environment.
139    #[doc(hidden)]
140    pub unsafe fn __new() -> Self {
141        Self { _private: () }
142    }
143
144    // ========================================================================
145    // Context methods (temporary state within a batch)
146    // ========================================================================
147
148    /// Returns the current context length in bytes.
149    #[inline]
150    pub fn len(&self) -> usize {
151        #[cfg(target_family = "wasm")]
152        {
153            let len = unsafe { __frnt__delegate__ctx_len() };
154            if len < 0 {
155                0
156            } else {
157                len as usize
158            }
159        }
160        #[cfg(not(target_family = "wasm"))]
161        {
162            0
163        }
164    }
165
166    /// Returns `true` if the context is empty.
167    #[inline]
168    pub fn is_empty(&self) -> bool {
169        self.len() == 0
170    }
171
172    /// Read the current context bytes.
173    ///
174    /// Returns an empty `Vec` if no context has been written.
175    pub fn read(&self) -> Vec<u8> {
176        #[cfg(target_family = "wasm")]
177        {
178            let len = unsafe { __frnt__delegate__ctx_len() };
179            if len <= 0 {
180                return Vec::new();
181            }
182            let mut buf = vec![0u8; len as usize];
183            let read = unsafe { __frnt__delegate__ctx_read(buf.as_mut_ptr() as i64, len) };
184            buf.truncate(read.max(0) as usize);
185            buf
186        }
187        #[cfg(not(target_family = "wasm"))]
188        {
189            Vec::new()
190        }
191    }
192
193    /// Read context into a provided buffer.
194    ///
195    /// Returns the number of bytes actually read.
196    pub fn read_into(&self, buf: &mut [u8]) -> usize {
197        #[cfg(target_family = "wasm")]
198        {
199            let read =
200                unsafe { __frnt__delegate__ctx_read(buf.as_mut_ptr() as i64, buf.len() as i32) };
201            read.max(0) as usize
202        }
203        #[cfg(not(target_family = "wasm"))]
204        {
205            let _ = buf;
206            0
207        }
208    }
209
210    /// Write new context bytes, replacing any existing content.
211    ///
212    /// Returns `true` on success, `false` on error.
213    pub fn write(&mut self, data: &[u8]) -> bool {
214        #[cfg(target_family = "wasm")]
215        {
216            let result =
217                unsafe { __frnt__delegate__ctx_write(data.as_ptr() as i64, data.len() as i32) };
218            result == 0
219        }
220        #[cfg(not(target_family = "wasm"))]
221        {
222            let _ = data;
223            false
224        }
225    }
226
227    /// Clear the context.
228    #[inline]
229    pub fn clear(&mut self) {
230        self.write(&[]);
231    }
232
233    // ========================================================================
234    // Secret methods (persistent encrypted storage)
235    // ========================================================================
236
237    /// Get the length of a secret without retrieving its value.
238    ///
239    /// Returns `None` if the secret does not exist.
240    pub fn get_secret_len(&self, key: &[u8]) -> Option<usize> {
241        #[cfg(target_family = "wasm")]
242        {
243            let result =
244                unsafe { __frnt__delegate__get_secret_len(key.as_ptr() as i64, key.len() as i32) };
245            if result < 0 {
246                None
247            } else {
248                Some(result as usize)
249            }
250        }
251        #[cfg(not(target_family = "wasm"))]
252        {
253            let _ = key;
254            None
255        }
256    }
257
258    /// Get a secret by key.
259    ///
260    /// Returns `None` if the secret does not exist.
261    pub fn get_secret(&self, key: &[u8]) -> Option<Vec<u8>> {
262        #[cfg(target_family = "wasm")]
263        {
264            // First get the length to allocate the right buffer size
265            let len = self.get_secret_len(key)?;
266
267            if len == 0 {
268                return Some(Vec::new());
269            }
270
271            let mut out = vec![0u8; len];
272            let result = unsafe {
273                __frnt__delegate__get_secret(
274                    key.as_ptr() as i64,
275                    key.len() as i32,
276                    out.as_mut_ptr() as i64,
277                    out.len() as i32,
278                )
279            };
280            if result < 0 {
281                None
282            } else {
283                out.truncate(result as usize);
284                Some(out)
285            }
286        }
287        #[cfg(not(target_family = "wasm"))]
288        {
289            let _ = key;
290            None
291        }
292    }
293
294    /// Store a secret.
295    ///
296    /// Returns `true` on success, `false` on error.
297    pub fn set_secret(&mut self, key: &[u8], value: &[u8]) -> bool {
298        #[cfg(target_family = "wasm")]
299        {
300            let result = unsafe {
301                __frnt__delegate__set_secret(
302                    key.as_ptr() as i64,
303                    key.len() as i32,
304                    value.as_ptr() as i64,
305                    value.len() as i32,
306                )
307            };
308            result == 0
309        }
310        #[cfg(not(target_family = "wasm"))]
311        {
312            let _ = (key, value);
313            false
314        }
315    }
316
317    /// Check if a secret exists.
318    pub fn has_secret(&self, key: &[u8]) -> bool {
319        #[cfg(target_family = "wasm")]
320        {
321            let result =
322                unsafe { __frnt__delegate__has_secret(key.as_ptr() as i64, key.len() as i32) };
323            result == 1
324        }
325        #[cfg(not(target_family = "wasm"))]
326        {
327            let _ = key;
328            false
329        }
330    }
331
332    /// Remove a secret.
333    ///
334    /// Returns `true` if the secret was removed, `false` if it didn't exist.
335    pub fn remove_secret(&mut self, key: &[u8]) -> bool {
336        #[cfg(target_family = "wasm")]
337        {
338            let result =
339                unsafe { __frnt__delegate__remove_secret(key.as_ptr() as i64, key.len() as i32) };
340            result == 0
341        }
342        #[cfg(not(target_family = "wasm"))]
343        {
344            let _ = key;
345            false
346        }
347    }
348}
349
350impl std::fmt::Debug for DelegateCtx {
351    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
352        f.debug_struct("DelegateCtx")
353            .field("context_len", &self.len())
354            .finish_non_exhaustive()
355    }
356}