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}