grafos_std/
host.rs

1//! Raw host function bindings for FBMU and FBBU.
2//!
3//! This module provides the low-level interface between grafos-std and the
4//! grafOS runtime. On `wasm32` targets, these functions link to real host
5//! imports (`fabricbios_fbmu_v0` and `fabricbios_fbbu_v0` WASM import
6//! modules). On native targets, they delegate to mock implementations backed
7//! by thread-local storage so unit tests work without a WASM runtime.
8//!
9//! All `unsafe` in the crate is confined to this module. Higher-level code
10//! in [`crate::mem`] and [`crate::block`] wraps these functions with safe
11//! Rust APIs.
12//!
13//! # Mock configuration (native targets only)
14//!
15//! When running on non-WASM targets, the mock state can be configured for
16//! testing:
17//!
18//! ```rust
19//! use grafos_std::host;
20//!
21//! host::reset_mock();
22//! host::mock_set_fbmu_arena_size(8192);
23//! host::mock_set_fbbu_num_blocks(256);
24//! host::mock_set_unix_time_secs(1_700_000_000);
25//!
26//! // Now FabricMem::hello() and FabricBlock::hello() will use these values.
27//! ```
28
29extern crate alloc;
30
31use crate::error::{FabricError, Result};
32use alloc::vec::Vec;
33
34/// Lease status code: lease is active.
35pub const LEASE_STATUS_ACTIVE: u8 = 0;
36/// Lease status code: lease has expired.
37pub const LEASE_STATUS_EXPIRED: u8 = 1;
38/// Lease status code: lease was explicitly revoked/freed.
39pub const LEASE_STATUS_REVOKED: u8 = 2;
40
41/// Host-side FBMU lease metadata returned by v0 lease lifecycle calls.
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub struct FbmuLeaseInfo {
44    pub lease_id: u128,
45    pub expires_at_unix_secs: u64,
46    pub arena_size: u64,
47    pub status: u8,
48}
49
50/// Host-side FBBU lease metadata returned by v0 lease lifecycle calls.
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub struct FbbuLeaseInfo {
53    pub lease_id: u128,
54    pub expires_at_unix_secs: u64,
55    pub num_blocks: u64,
56    pub block_size: u32,
57    pub status: u8,
58}
59
60#[cfg(target_arch = "wasm32")]
61fn split_u128_words(value: u128) -> (i64, i64) {
62    let hi = (value >> 64) as u64;
63    let lo = value as u64;
64    (hi as i64, lo as i64)
65}
66
67#[cfg(target_arch = "wasm32")]
68fn join_u128_words(hi: i64, lo: i64) -> u128 {
69    ((hi as u64 as u128) << 64) | (lo as u64 as u128)
70}
71
72// ---------------------------------------------------------------------------
73// WASM target: real host imports
74// ---------------------------------------------------------------------------
75
76#[cfg(target_arch = "wasm32")]
77mod wasm {
78    #[link(wasm_import_module = "fabricbios_fbmu_v0")]
79    extern "C" {
80        pub fn fbmu_hello() -> i32;
81        pub fn fbmu_write(offset: i64, ptr: i32, len: i32) -> i32;
82        pub fn fbmu_read(offset: i64, len: i32, out_ptr: i32) -> i32;
83        pub fn fbmu_get_arena_size() -> i64;
84        pub fn fbmu_alloc(
85            min_bytes: i64,
86            lease_secs: i64,
87            lease_hi_ptr: i32,
88            lease_lo_ptr: i32,
89            expires_at_ptr: i32,
90            arena_size_ptr: i32,
91        ) -> i32;
92        pub fn fbmu_query(
93            lease_hi: i64,
94            lease_lo: i64,
95            status_ptr: i32,
96            expires_at_ptr: i32,
97            arena_size_ptr: i32,
98        ) -> i32;
99        pub fn fbmu_renew(
100            lease_hi: i64,
101            lease_lo: i64,
102            duration_secs: i64,
103            expires_at_ptr: i32,
104        ) -> i32;
105        pub fn fbmu_free(lease_hi: i64, lease_lo: i64) -> i32;
106        pub fn fbmu_write_lease(
107            lease_hi: i64,
108            lease_lo: i64,
109            offset: i64,
110            ptr: i32,
111            len: i32,
112        ) -> i32;
113        pub fn fbmu_read_lease(
114            lease_hi: i64,
115            lease_lo: i64,
116            offset: i64,
117            len: i32,
118            out_ptr: i32,
119        ) -> i32;
120    }
121
122    #[link(wasm_import_module = "fabricbios_fbbu_v0")]
123    extern "C" {
124        pub fn fbbu_hello() -> i32;
125        pub fn fbbu_write_block(lba: i64, ptr: i32) -> i32;
126        pub fn fbbu_read_block(lba: i64, out_ptr: i32) -> i32;
127        pub fn fbbu_get_num_blocks() -> i64;
128        pub fn fbbu_alloc(
129            min_blocks: i64,
130            lease_secs: i64,
131            lease_hi_ptr: i32,
132            lease_lo_ptr: i32,
133            expires_at_ptr: i32,
134            num_blocks_ptr: i32,
135            block_size_ptr: i32,
136        ) -> i32;
137        pub fn fbbu_query(
138            lease_hi: i64,
139            lease_lo: i64,
140            status_ptr: i32,
141            expires_at_ptr: i32,
142            num_blocks_ptr: i32,
143            block_size_ptr: i32,
144        ) -> i32;
145        pub fn fbbu_renew(
146            lease_hi: i64,
147            lease_lo: i64,
148            duration_secs: i64,
149            expires_at_ptr: i32,
150        ) -> i32;
151        pub fn fbbu_free(lease_hi: i64, lease_lo: i64) -> i32;
152        pub fn fbbu_write_block_lease(lease_hi: i64, lease_lo: i64, lba: i64, ptr: i32) -> i32;
153        pub fn fbbu_read_block_lease(lease_hi: i64, lease_lo: i64, lba: i64, out_ptr: i32) -> i32;
154    }
155}
156
157/// Perform the FBMU HELLO handshake with the host.
158///
159/// Establishes the memory data-plane session. Must be called before any
160/// FBMU read/write operations.
161///
162/// # Errors
163///
164/// Returns [`FabricError::Disconnected`] if the host connection is not
165/// available, or another [`FabricError`] variant based on the host status
166/// code.
167#[cfg(target_arch = "wasm32")]
168pub fn fbmu_hello() -> Result<()> {
169    let status = unsafe { wasm::fbmu_hello() };
170    match FabricError::from_status(status) {
171        None => Ok(()),
172        Some(e) => Err(e),
173    }
174}
175
176/// Write `data` to the FBMU arena at the given byte `offset`.
177///
178/// # Errors
179///
180/// Returns a [`FabricError`] if the host reports a write failure.
181#[cfg(target_arch = "wasm32")]
182pub fn fbmu_write(offset: u64, data: &[u8]) -> Result<()> {
183    let status =
184        unsafe { wasm::fbmu_write(offset as i64, data.as_ptr() as i32, data.len() as i32) };
185    match FabricError::from_status(status) {
186        None => Ok(()),
187        Some(e) => Err(e),
188    }
189}
190
191/// Read from the FBMU arena at `offset` into `buf`.
192///
193/// Returns the number of bytes actually read, which may be less than
194/// `buf.len()` if the arena region is shorter.
195///
196/// # Errors
197///
198/// Returns a [`FabricError`] if the host reports a read failure.
199#[cfg(target_arch = "wasm32")]
200pub fn fbmu_read(offset: u64, buf: &mut [u8]) -> Result<usize> {
201    let n = unsafe { wasm::fbmu_read(offset as i64, buf.len() as i32, buf.as_mut_ptr() as i32) };
202    if n < 0 {
203        Err(FabricError::from_status(n).unwrap_or(FabricError::IoError(n)))
204    } else {
205        Ok(n as usize)
206    }
207}
208
209/// Query the total FBMU arena size in bytes.
210///
211/// # Errors
212///
213/// Returns [`FabricError::IoError`] if the host returns a negative value.
214#[cfg(target_arch = "wasm32")]
215pub fn fbmu_get_arena_size() -> Result<u64> {
216    let size = unsafe { wasm::fbmu_get_arena_size() };
217    if size < 0 {
218        Err(FabricError::IoError(size as i32))
219    } else {
220        Ok(size as u64)
221    }
222}
223
224/// Perform the FBBU HELLO handshake with the host.
225///
226/// Establishes the block data-plane session. Must be called before any
227/// FBBU read/write operations.
228///
229/// # Errors
230///
231/// Returns [`FabricError::Disconnected`] if the host connection is not
232/// available.
233#[cfg(target_arch = "wasm32")]
234pub fn fbbu_hello() -> Result<()> {
235    let status = unsafe { wasm::fbbu_hello() };
236    match FabricError::from_status(status) {
237        None => Ok(()),
238        Some(e) => Err(e),
239    }
240}
241
242/// Write a single 512-byte block at the given logical block address.
243///
244/// # Errors
245///
246/// Returns [`FabricError::CapacityExceeded`] if `lba` is beyond the
247/// device's block count.
248#[cfg(target_arch = "wasm32")]
249pub fn fbbu_write_block(lba: u64, data: &[u8; 512]) -> Result<()> {
250    let status = unsafe { wasm::fbbu_write_block(lba as i64, data.as_ptr() as i32) };
251    match FabricError::from_status(status) {
252        None => Ok(()),
253        Some(e) => Err(e),
254    }
255}
256
257/// Read a single 512-byte block at the given logical block address.
258///
259/// # Errors
260///
261/// Returns [`FabricError::CapacityExceeded`] if `lba` is beyond the
262/// device's block count.
263#[cfg(target_arch = "wasm32")]
264pub fn fbbu_read_block(lba: u64, buf: &mut [u8; 512]) -> Result<()> {
265    let status = unsafe { wasm::fbbu_read_block(lba as i64, buf.as_mut_ptr() as i32) };
266    match FabricError::from_status(status) {
267        None => Ok(()),
268        Some(e) => Err(e),
269    }
270}
271
272/// Query the total number of blocks available on the FBBU device.
273///
274/// # Errors
275///
276/// Returns [`FabricError::IoError`] if the host returns a negative value.
277#[cfg(target_arch = "wasm32")]
278pub fn fbbu_get_num_blocks() -> Result<u64> {
279    let n = unsafe { wasm::fbbu_get_num_blocks() };
280    if n < 0 {
281        Err(FabricError::IoError(n as i32))
282    } else {
283        Ok(n as u64)
284    }
285}
286
287/// Allocate a memory lease from FBMU with the requested minimum capacity and TTL.
288#[cfg(target_arch = "wasm32")]
289pub fn fbmu_alloc(min_bytes: u64, lease_secs: u64) -> Result<FbmuLeaseInfo> {
290    let mut lease_hi: i64 = 0;
291    let mut lease_lo: i64 = 0;
292    let mut expires_at: i64 = 0;
293    let mut arena_size: i64 = 0;
294    let status = unsafe {
295        wasm::fbmu_alloc(
296            min_bytes as i64,
297            lease_secs as i64,
298            &mut lease_hi as *mut i64 as i32,
299            &mut lease_lo as *mut i64 as i32,
300            &mut expires_at as *mut i64 as i32,
301            &mut arena_size as *mut i64 as i32,
302        )
303    };
304    match FabricError::from_status(status) {
305        None => Ok(FbmuLeaseInfo {
306            lease_id: join_u128_words(lease_hi, lease_lo),
307            expires_at_unix_secs: expires_at as u64,
308            arena_size: arena_size as u64,
309            status: LEASE_STATUS_ACTIVE,
310        }),
311        Some(e) => Err(e),
312    }
313}
314
315/// Query FBMU lease status and capacity metadata.
316#[cfg(target_arch = "wasm32")]
317pub fn fbmu_query(lease_id: u128) -> Result<FbmuLeaseInfo> {
318    let (lease_hi, lease_lo) = split_u128_words(lease_id);
319    let mut lease_status: i32 = LEASE_STATUS_REVOKED as i32;
320    let mut expires_at: i64 = 0;
321    let mut arena_size: i64 = 0;
322    let status = unsafe {
323        wasm::fbmu_query(
324            lease_hi,
325            lease_lo,
326            &mut lease_status as *mut i32 as i32,
327            &mut expires_at as *mut i64 as i32,
328            &mut arena_size as *mut i64 as i32,
329        )
330    };
331    match FabricError::from_status(status) {
332        None => Ok(FbmuLeaseInfo {
333            lease_id,
334            expires_at_unix_secs: expires_at as u64,
335            arena_size: arena_size as u64,
336            status: lease_status as u8,
337        }),
338        Some(e) => Err(e),
339    }
340}
341
342/// Renew an FBMU lease and return the updated expiry timestamp.
343#[cfg(target_arch = "wasm32")]
344pub fn fbmu_renew(lease_id: u128, duration_secs: u64) -> Result<u64> {
345    let (lease_hi, lease_lo) = split_u128_words(lease_id);
346    let mut expires_at: i64 = 0;
347    let status = unsafe {
348        wasm::fbmu_renew(
349            lease_hi,
350            lease_lo,
351            duration_secs as i64,
352            &mut expires_at as *mut i64 as i32,
353        )
354    };
355    match FabricError::from_status(status) {
356        None => Ok(expires_at as u64),
357        Some(e) => Err(e),
358    }
359}
360
361/// Free/revoke an FBMU lease.
362#[cfg(target_arch = "wasm32")]
363pub fn fbmu_free(lease_id: u128) -> Result<()> {
364    let (lease_hi, lease_lo) = split_u128_words(lease_id);
365    let status = unsafe { wasm::fbmu_free(lease_hi, lease_lo) };
366    match FabricError::from_status(status) {
367        None => Ok(()),
368        Some(e) => Err(e),
369    }
370}
371
372/// Write to FBMU using an explicit lease id.
373#[cfg(target_arch = "wasm32")]
374pub fn fbmu_write_lease(lease_id: u128, offset: u64, data: &[u8]) -> Result<()> {
375    let (lease_hi, lease_lo) = split_u128_words(lease_id);
376    let status = unsafe {
377        wasm::fbmu_write_lease(
378            lease_hi,
379            lease_lo,
380            offset as i64,
381            data.as_ptr() as i32,
382            data.len() as i32,
383        )
384    };
385    match FabricError::from_status(status) {
386        None => Ok(()),
387        Some(e) => Err(e),
388    }
389}
390
391/// Read from FBMU using an explicit lease id.
392#[cfg(target_arch = "wasm32")]
393pub fn fbmu_read_lease(lease_id: u128, offset: u64, buf: &mut [u8]) -> Result<usize> {
394    let (lease_hi, lease_lo) = split_u128_words(lease_id);
395    let n = unsafe {
396        wasm::fbmu_read_lease(
397            lease_hi,
398            lease_lo,
399            offset as i64,
400            buf.len() as i32,
401            buf.as_mut_ptr() as i32,
402        )
403    };
404    if n < 0 {
405        Err(FabricError::from_status(n).unwrap_or(FabricError::IoError(n)))
406    } else {
407        Ok(n as usize)
408    }
409}
410
411/// Allocate a block lease from FBBU with the requested minimum capacity and TTL.
412#[cfg(target_arch = "wasm32")]
413pub fn fbbu_alloc(min_blocks: u64, lease_secs: u64) -> Result<FbbuLeaseInfo> {
414    let mut lease_hi: i64 = 0;
415    let mut lease_lo: i64 = 0;
416    let mut expires_at: i64 = 0;
417    let mut num_blocks: i64 = 0;
418    let mut block_size: i32 = 0;
419    let status = unsafe {
420        wasm::fbbu_alloc(
421            min_blocks as i64,
422            lease_secs as i64,
423            &mut lease_hi as *mut i64 as i32,
424            &mut lease_lo as *mut i64 as i32,
425            &mut expires_at as *mut i64 as i32,
426            &mut num_blocks as *mut i64 as i32,
427            &mut block_size as *mut i32 as i32,
428        )
429    };
430    match FabricError::from_status(status) {
431        None => Ok(FbbuLeaseInfo {
432            lease_id: join_u128_words(lease_hi, lease_lo),
433            expires_at_unix_secs: expires_at as u64,
434            num_blocks: num_blocks as u64,
435            block_size: block_size as u32,
436            status: LEASE_STATUS_ACTIVE,
437        }),
438        Some(e) => Err(e),
439    }
440}
441
442/// Query FBBU lease status and geometry metadata.
443#[cfg(target_arch = "wasm32")]
444pub fn fbbu_query(lease_id: u128) -> Result<FbbuLeaseInfo> {
445    let (lease_hi, lease_lo) = split_u128_words(lease_id);
446    let mut lease_status: i32 = LEASE_STATUS_REVOKED as i32;
447    let mut expires_at: i64 = 0;
448    let mut num_blocks: i64 = 0;
449    let mut block_size: i32 = 0;
450    let status = unsafe {
451        wasm::fbbu_query(
452            lease_hi,
453            lease_lo,
454            &mut lease_status as *mut i32 as i32,
455            &mut expires_at as *mut i64 as i32,
456            &mut num_blocks as *mut i64 as i32,
457            &mut block_size as *mut i32 as i32,
458        )
459    };
460    match FabricError::from_status(status) {
461        None => Ok(FbbuLeaseInfo {
462            lease_id,
463            expires_at_unix_secs: expires_at as u64,
464            num_blocks: num_blocks as u64,
465            block_size: block_size as u32,
466            status: lease_status as u8,
467        }),
468        Some(e) => Err(e),
469    }
470}
471
472/// Renew an FBBU lease and return the updated expiry timestamp.
473#[cfg(target_arch = "wasm32")]
474pub fn fbbu_renew(lease_id: u128, duration_secs: u64) -> Result<u64> {
475    let (lease_hi, lease_lo) = split_u128_words(lease_id);
476    let mut expires_at: i64 = 0;
477    let status = unsafe {
478        wasm::fbbu_renew(
479            lease_hi,
480            lease_lo,
481            duration_secs as i64,
482            &mut expires_at as *mut i64 as i32,
483        )
484    };
485    match FabricError::from_status(status) {
486        None => Ok(expires_at as u64),
487        Some(e) => Err(e),
488    }
489}
490
491/// Free/revoke an FBBU lease.
492#[cfg(target_arch = "wasm32")]
493pub fn fbbu_free(lease_id: u128) -> Result<()> {
494    let (lease_hi, lease_lo) = split_u128_words(lease_id);
495    let status = unsafe { wasm::fbbu_free(lease_hi, lease_lo) };
496    match FabricError::from_status(status) {
497        None => Ok(()),
498        Some(e) => Err(e),
499    }
500}
501
502/// Write a 512-byte block via FBBU using an explicit lease id.
503#[cfg(target_arch = "wasm32")]
504pub fn fbbu_write_block_lease(lease_id: u128, lba: u64, data: &[u8; 512]) -> Result<()> {
505    let (lease_hi, lease_lo) = split_u128_words(lease_id);
506    let status = unsafe {
507        wasm::fbbu_write_block_lease(lease_hi, lease_lo, lba as i64, data.as_ptr() as i32)
508    };
509    match FabricError::from_status(status) {
510        None => Ok(()),
511        Some(e) => Err(e),
512    }
513}
514
515/// Read a 512-byte block via FBBU using an explicit lease id.
516#[cfg(target_arch = "wasm32")]
517pub fn fbbu_read_block_lease(lease_id: u128, lba: u64, buf: &mut [u8; 512]) -> Result<()> {
518    let (lease_hi, lease_lo) = split_u128_words(lease_id);
519    let status = unsafe {
520        wasm::fbbu_read_block_lease(lease_hi, lease_lo, lba as i64, buf.as_mut_ptr() as i32)
521    };
522    match FabricError::from_status(status) {
523        None => Ok(()),
524        Some(e) => Err(e),
525    }
526}
527
528// ---------------------------------------------------------------------------
529// WASM target: tasklet host imports
530// ---------------------------------------------------------------------------
531
532#[cfg(target_arch = "wasm32")]
533mod wasm_tasklet {
534    #[link(wasm_import_module = "fabricbios_tasklet_v0")]
535    extern "C" {
536        /// Submit WASM bytes for execution. Returns status (0=ok, negative=error).
537        /// On success, exit_code written at exit_code_ptr, output at output_ptr,
538        /// actual output length at output_len_ptr.
539        pub fn tasklet_submit(
540            wasm_ptr: i32,
541            wasm_len: i32,
542            input_ptr: i32,
543            input_len: i32,
544            fuel: i64,
545            exit_code_ptr: i32,
546            output_ptr: i32,
547            output_max_len: i32,
548            output_len_ptr: i32,
549        ) -> i32;
550    }
551}
552
553#[cfg(target_arch = "wasm32")]
554mod wasm_net {
555    #[link(wasm_import_module = "fabricbios_net_v0")]
556    extern "C" {
557        /// Acquire a network interface with minimum bandwidth.
558        /// On success: status=0, interface name written at name_ptr (up to name_max_len),
559        /// actual name length at name_len_ptr, actual bandwidth at bw_ptr.
560        pub fn net_hello(
561            min_bw: i64,
562            name_ptr: i32,
563            name_max_len: i32,
564            name_len_ptr: i32,
565            bw_ptr: i32,
566        ) -> i32;
567    }
568}
569
570#[cfg(target_arch = "wasm32")]
571mod wasm_gpu {
572    #[link(wasm_import_module = "fabricbios_gpu_v0")]
573    extern "C" {
574        /// Allocate a GPU lease. Returns status (0=ok, negative=error).
575        /// On success, lease_id written as two u64 halves at the given pointers.
576        pub fn gpu_lease_alloc(
577            resource_id_lo: i64,
578            resource_id_hi: i64,
579            lease_secs: u32,
580            lease_id_lo_ptr: i32,
581            lease_id_hi_ptr: i32,
582        ) -> i32;
583
584        /// Renew a GPU lease. Returns status (0=ok, negative=error).
585        pub fn gpu_lease_renew(lease_id_lo: i64, lease_id_hi: i64, duration_secs: u32) -> i32;
586
587        /// Free a GPU lease. Returns status (0=ok, negative=error).
588        pub fn gpu_lease_free(lease_id_lo: i64, lease_id_hi: i64) -> i32;
589
590        /// Query GPU lease status. Returns status (0=ok, negative=error).
591        /// On success, lease_status written at status_ptr.
592        pub fn gpu_lease_query(lease_id_lo: i64, lease_id_hi: i64, status_ptr: i32) -> i32;
593    }
594
595    // Phase 48.15 W1 — persistent GPU session ops (0x0601–0x0607).
596    //
597    // These mirror `fabricbios_core::gpu_session` request/response types on
598    // the wire. Lease IDs are split into `(lo, hi)` u64 halves per the v0
599    // calling convention; byte buffers are passed as `(ptr, len)` pairs; and
600    // handle-returning ops take an out-pointer for the handle.
601    //
602    // Return value is a 32-bit status: `0` = success; negative values map to
603    // the transport-level [`FabricError`] variants via
604    // [`FabricError::from_status`]; non-negative non-zero values carry the
605    // raw `SESSION_STATUS_*` code and surface as
606    // [`FabricError::GpuSessionFailed(u8)`].
607    #[link(wasm_import_module = "fabricbios_gpu_v1")]
608    extern "C" {
609        pub fn gpu_session_mem_alloc(
610            lease_lo: i64,
611            lease_hi: i64,
612            size: i64,
613            handle_out_ptr: i32,
614        ) -> i32;
615
616        pub fn gpu_session_mem_write(
617            lease_lo: i64,
618            lease_hi: i64,
619            handle: i64,
620            offset: i64,
621            data_ptr: i32,
622            data_len: i32,
623        ) -> i32;
624
625        pub fn gpu_session_mem_read(
626            lease_lo: i64,
627            lease_hi: i64,
628            handle: i64,
629            offset: i64,
630            dst_ptr: i32,
631            dst_max: i32,
632            out_len_ptr: i32,
633        ) -> i32;
634
635        pub fn gpu_session_mem_free(lease_lo: i64, lease_hi: i64, handle: i64) -> i32;
636
637        pub fn gpu_session_module_load(
638            lease_lo: i64,
639            lease_hi: i64,
640            bin_ptr: i32,
641            bin_len: i32,
642            module_out_ptr: i32,
643        ) -> i32;
644
645        pub fn gpu_session_launch(
646            lease_lo: i64,
647            lease_hi: i64,
648            module_id: i64,
649            kname_ptr: i32,
650            kname_len: i32,
651            gx: u32,
652            gy: u32,
653            gz: u32,
654            bx: u32,
655            by: u32,
656            bz: u32,
657            args_ptr: i32,
658            args_len: i32,
659            arg_sizes_ptr: i32,
660            arg_sizes_len: i32,
661        ) -> i32;
662
663        pub fn gpu_session_sync(lease_lo: i64, lease_hi: i64) -> i32;
664
665        /// Phase 48.16 SDK polish — explicit module unload, the 8th
666        /// `fabricbios_gpu_v1` op. Additive: existing 7 ops unchanged.
667        /// Powers `GpuModule::drop` RAII in `crates/grafos-std/src/gpu.rs`.
668        pub fn gpu_session_module_unload(lease_lo: i64, lease_hi: i64, module_id: i64) -> i32;
669    }
670}
671
672/// Submit a WASM tasklet for execution on a fabric CPU resource.
673///
674/// # Parameters
675///
676/// - `wasm`: The WASM module bytes to execute.
677/// - `input`: Input data passed to the tasklet.
678/// - `fuel`: WASM fuel limit (instruction budget).
679/// - `output_buf`: Buffer for tasklet output.
680///
681/// # Returns
682///
683/// On success, returns `(exit_code, output_len)`.
684///
685/// # Errors
686///
687/// Returns a [`FabricError`] if the submission fails.
688#[cfg(target_arch = "wasm32")]
689pub fn tasklet_submit(
690    wasm: &[u8],
691    input: &[u8],
692    fuel: u64,
693    output_buf: &mut [u8],
694) -> Result<(u64, usize)> {
695    let mut exit_code: u64 = 0;
696    let mut output_len: u32 = 0;
697    let status = unsafe {
698        wasm_tasklet::tasklet_submit(
699            wasm.as_ptr() as i32,
700            wasm.len() as i32,
701            input.as_ptr() as i32,
702            input.len() as i32,
703            fuel as i64,
704            &mut exit_code as *mut u64 as i32,
705            output_buf.as_mut_ptr() as i32,
706            output_buf.len() as i32,
707            &mut output_len as *mut u32 as i32,
708        )
709    };
710    match FabricError::from_status(status) {
711        None => Ok((exit_code, output_len as usize)),
712        Some(e) => Err(e),
713    }
714}
715
716/// Allocate a GPU lease via host import.
717#[cfg(target_arch = "wasm32")]
718pub fn gpu_lease_alloc(resource_id: u128, lease_secs: u32) -> Result<u128> {
719    let res_lo = resource_id as i64;
720    let res_hi = (resource_id >> 64) as i64;
721    let mut lease_id_lo: u64 = 0;
722    let mut lease_id_hi: u64 = 0;
723    let status = unsafe {
724        wasm_gpu::gpu_lease_alloc(
725            res_lo,
726            res_hi,
727            lease_secs,
728            &mut lease_id_lo as *mut u64 as i32,
729            &mut lease_id_hi as *mut u64 as i32,
730        )
731    };
732    match FabricError::from_status(status) {
733        None => Ok((lease_id_hi as u128) << 64 | (lease_id_lo as u128)),
734        Some(e) => Err(e),
735    }
736}
737
738/// Renew a GPU lease via host import.
739#[cfg(target_arch = "wasm32")]
740pub fn gpu_lease_renew(lease_id: u128, duration_secs: u32) -> Result<()> {
741    let lo = lease_id as i64;
742    let hi = (lease_id >> 64) as i64;
743    let status = unsafe { wasm_gpu::gpu_lease_renew(lo, hi, duration_secs) };
744    match FabricError::from_status(status) {
745        None => Ok(()),
746        Some(e) => Err(e),
747    }
748}
749
750/// Free a GPU lease via host import.
751#[cfg(target_arch = "wasm32")]
752pub fn gpu_lease_free(lease_id: u128) -> Result<()> {
753    let lo = lease_id as i64;
754    let hi = (lease_id >> 64) as i64;
755    let status = unsafe { wasm_gpu::gpu_lease_free(lo, hi) };
756    match FabricError::from_status(status) {
757        None => Ok(()),
758        Some(e) => Err(e),
759    }
760}
761
762/// Query GPU lease status via host import.
763/// Returns a status code (0=active, 1=expired, 2=revoked, 3=fenced).
764#[cfg(target_arch = "wasm32")]
765pub fn gpu_lease_query(lease_id: u128) -> Result<u32> {
766    let lo = lease_id as i64;
767    let hi = (lease_id >> 64) as i64;
768    let mut lease_status: u32 = 0;
769    let status = unsafe { wasm_gpu::gpu_lease_query(lo, hi, &mut lease_status as *mut u32 as i32) };
770    match FabricError::from_status(status) {
771        None => Ok(lease_status),
772        Some(e) => Err(e),
773    }
774}
775
776// ---------------------------------------------------------------------------
777// Phase 48.15 W1 — wasm32 bridge for persistent GPU session ops.
778//
779// These lower Rust-typed parameters to the `fabricbios_gpu_v1` wire ABI
780// (lease_id split into lo/hi, byte slices split into (ptr, len), handle
781// out-parameters via pointer) and translate the i32 return:
782//
783//   0                                    → Ok(..)
784//   negative                             → Err(FabricError::from_status(..))
785//   positive (SESSION_STATUS_* from
786//   `fabricbios_core::gpu_session`)      → Err(FabricError::GpuSessionFailed(code))
787//
788// Native equivalents (mock-backed) live further below under
789// `#[cfg(not(target_arch = "wasm32"))]`; both families share the same Rust
790// signatures so `impl GpuSession` compiles on both targets.
791// ---------------------------------------------------------------------------
792
793#[cfg(target_arch = "wasm32")]
794#[inline]
795fn lease_id_split(lease_id: u128) -> (i64, i64) {
796    (lease_id as i64, (lease_id >> 64) as i64)
797}
798
799// Phase 48.15 W2a — `session_status_to_result` is now used by BOTH the
800// wasm32 import-bridge wrappers AND the native mock wrappers. Previously it
801// was wasm32-only and the native wrappers used `FabricError::from_status`
802// directly, which collapses any positive non-zero status into
803// `FabricError::IoError(n)` instead of `FabricError::GpuSessionFailed(n)`.
804// That meant `mock::TestState::gpu.session_error = Some(SESSION_STATUS_*)`
805// produced different error variants under wasm32 vs native, breaking the
806// host-mock parity story for `impl GpuSession`. Sharing the helper across
807// both targets is the fix.
808#[inline]
809fn session_status_to_result(status: i32) -> Result<()> {
810    if status == 0 {
811        Ok(())
812    } else if status < 0 {
813        // Transport/lease-level failure surfaced by the daemon bridge.
814        Err(FabricError::from_status(status).unwrap_or(FabricError::IoError(status)))
815    } else {
816        // Positive, non-zero: a raw SESSION_STATUS_* code from
817        // `fabricbios_core::gpu_session`.
818        Err(FabricError::GpuSessionFailed(status as u8))
819    }
820}
821
822/// Allocate device memory within a GPU session.
823#[cfg(target_arch = "wasm32")]
824pub fn gpu_session_mem_alloc(lease_id: u128, size: u64) -> Result<u64> {
825    let (lo, hi) = lease_id_split(lease_id);
826    let mut handle: u64 = 0;
827    let status = unsafe {
828        wasm_gpu::gpu_session_mem_alloc(lo, hi, size as i64, &mut handle as *mut u64 as i32)
829    };
830    session_status_to_result(status).map(|()| handle)
831}
832
833/// Write data to device memory within a GPU session.
834#[cfg(target_arch = "wasm32")]
835pub fn gpu_session_mem_write(lease_id: u128, handle: u64, offset: u64, data: &[u8]) -> Result<()> {
836    let (lo, hi) = lease_id_split(lease_id);
837    let status = unsafe {
838        wasm_gpu::gpu_session_mem_write(
839            lo,
840            hi,
841            handle as i64,
842            offset as i64,
843            data.as_ptr() as i32,
844            data.len() as i32,
845        )
846    };
847    session_status_to_result(status)
848}
849
850/// Read data from device memory within a GPU session.
851///
852/// The wasm32 ABI uses a caller-supplied output buffer (sized to `size`)
853/// with an out-length pointer, mirroring the `gpu_submit` output pattern.
854/// On success the returned `Vec<u8>` is truncated to the actual bytes read.
855#[cfg(target_arch = "wasm32")]
856pub fn gpu_session_mem_read(
857    lease_id: u128,
858    handle: u64,
859    offset: u64,
860    size: u32,
861) -> Result<alloc::vec::Vec<u8>> {
862    let (lo, hi) = lease_id_split(lease_id);
863    let mut buf: alloc::vec::Vec<u8> = alloc::vec![0u8; size as usize];
864    let mut out_len: u32 = 0;
865    let status = unsafe {
866        wasm_gpu::gpu_session_mem_read(
867            lo,
868            hi,
869            handle as i64,
870            offset as i64,
871            buf.as_mut_ptr() as i32,
872            size as i32,
873            &mut out_len as *mut u32 as i32,
874        )
875    };
876    session_status_to_result(status).map(|()| {
877        buf.truncate(out_len as usize);
878        buf
879    })
880}
881
882/// Free device memory within a GPU session.
883#[cfg(target_arch = "wasm32")]
884pub fn gpu_session_mem_free(lease_id: u128, handle: u64) -> Result<()> {
885    let (lo, hi) = lease_id_split(lease_id);
886    let status = unsafe { wasm_gpu::gpu_session_mem_free(lo, hi, handle as i64) };
887    session_status_to_result(status)
888}
889
890/// Load a GPU module (PTX/cubin) within a GPU session.
891#[cfg(target_arch = "wasm32")]
892pub fn gpu_session_module_load(lease_id: u128, binary: &[u8]) -> Result<u64> {
893    let (lo, hi) = lease_id_split(lease_id);
894    let mut module_id: u64 = 0;
895    let status = unsafe {
896        wasm_gpu::gpu_session_module_load(
897            lo,
898            hi,
899            binary.as_ptr() as i32,
900            binary.len() as i32,
901            &mut module_id as *mut u64 as i32,
902        )
903    };
904    session_status_to_result(status).map(|()| module_id)
905}
906
907/// Launch a kernel from a loaded module within a GPU session.
908#[cfg(target_arch = "wasm32")]
909pub fn gpu_session_launch(
910    lease_id: u128,
911    module_id: u64,
912    kernel_name: &str,
913    grid_dim: [u32; 3],
914    block_dim: [u32; 3],
915    args: &[u8],
916    arg_sizes: &[u32],
917) -> Result<()> {
918    let (lo, hi) = lease_id_split(lease_id);
919    let name_bytes = kernel_name.as_bytes();
920    let status = unsafe {
921        wasm_gpu::gpu_session_launch(
922            lo,
923            hi,
924            module_id as i64,
925            name_bytes.as_ptr() as i32,
926            name_bytes.len() as i32,
927            grid_dim[0],
928            grid_dim[1],
929            grid_dim[2],
930            block_dim[0],
931            block_dim[1],
932            block_dim[2],
933            args.as_ptr() as i32,
934            args.len() as i32,
935            arg_sizes.as_ptr() as i32,
936            arg_sizes.len() as i32,
937        )
938    };
939    session_status_to_result(status)
940}
941
942/// Synchronize a GPU session (wait for all outstanding launches).
943#[cfg(target_arch = "wasm32")]
944pub fn gpu_session_sync(lease_id: u128) -> Result<()> {
945    let (lo, hi) = lease_id_split(lease_id);
946    let status = unsafe { wasm_gpu::gpu_session_sync(lo, hi) };
947    session_status_to_result(status)
948}
949
950/// Unload a GPU module within a GPU session (Phase 48.16 SDK polish).
951#[cfg(target_arch = "wasm32")]
952pub fn gpu_session_module_unload(lease_id: u128, module_id: u64) -> Result<()> {
953    let (lo, hi) = lease_id_split(lease_id);
954    let status = unsafe { wasm_gpu::gpu_session_module_unload(lo, hi, module_id as i64) };
955    session_status_to_result(status)
956}
957
958/// Acquire a network interface with the given minimum bandwidth.
959///
960/// # Parameters
961///
962/// - `min_bw`: Minimum bandwidth in bits per second.
963///
964/// # Returns
965///
966/// On success, returns `(interface_name, actual_bandwidth)`.
967///
968/// # Errors
969///
970/// Returns a [`FabricError`] if the acquisition fails.
971#[cfg(target_arch = "wasm32")]
972pub fn net_hello(min_bw: u64) -> Result<(alloc::string::String, u64)> {
973    let mut name_buf = [0u8; 64];
974    let mut name_len: u32 = 0;
975    let mut actual_bw: u64 = 0;
976    let status = unsafe {
977        wasm_net::net_hello(
978            min_bw as i64,
979            name_buf.as_mut_ptr() as i32,
980            name_buf.len() as i32,
981            &mut name_len as *mut u32 as i32,
982            &mut actual_bw as *mut u64 as i32,
983        )
984    };
985    match FabricError::from_status(status) {
986        None => {
987            let name = core::str::from_utf8(&name_buf[..name_len as usize]).unwrap_or("unknown");
988            Ok((alloc::string::String::from(name), actual_bw))
989        }
990        Some(e) => Err(e),
991    }
992}
993
994// ---------------------------------------------------------------------------
995// Native target: mock implementations for testing
996// ---------------------------------------------------------------------------
997
998#[cfg(not(target_arch = "wasm32"))]
999mod mock {
1000    extern crate alloc;
1001    use super::{LEASE_STATUS_ACTIVE, LEASE_STATUS_EXPIRED, LEASE_STATUS_REVOKED};
1002    use alloc::collections::BTreeMap;
1003    use alloc::vec::Vec;
1004
1005    pub struct MockFbmu {
1006        pub arena: BTreeMap<u64, Vec<u8>>,
1007        pub arena_size: u64,
1008        pub hello_called: bool,
1009    }
1010
1011    impl Default for MockFbmu {
1012        fn default() -> Self {
1013            MockFbmu {
1014                arena: BTreeMap::new(),
1015                arena_size: 65536,
1016                hello_called: false,
1017            }
1018        }
1019    }
1020
1021    pub struct MockFbbu {
1022        pub blocks: BTreeMap<u64, [u8; 512]>,
1023        pub num_blocks: u64,
1024        pub hello_called: bool,
1025    }
1026
1027    impl Default for MockFbbu {
1028        fn default() -> Self {
1029            MockFbbu {
1030                blocks: BTreeMap::new(),
1031                num_blocks: 1024,
1032                hello_called: false,
1033            }
1034        }
1035    }
1036
1037    pub struct MockFbmuLease {
1038        pub status: u8,
1039        pub expires_at_unix_secs: u64,
1040        pub arena_size: u64,
1041        pub arena: BTreeMap<u64, Vec<u8>>,
1042    }
1043
1044    pub struct MockFbbuLease {
1045        pub status: u8,
1046        pub expires_at_unix_secs: u64,
1047        pub block_size: u32,
1048        pub num_blocks: u64,
1049        pub blocks: BTreeMap<u64, [u8; 512]>,
1050    }
1051
1052    #[derive(Default)]
1053    pub struct MockTasklet {
1054        /// Pre-configured exit code for tasklet submissions.
1055        pub exit_code: u64,
1056        /// Pre-configured output for tasklet submissions.
1057        pub output: Vec<u8>,
1058        /// Number of submit calls made.
1059        pub submit_count: u64,
1060    }
1061
1062    #[derive(Default)]
1063    pub struct MockGpu {
1064        /// Session: next allocation handle.
1065        pub session_next_handle: u64,
1066        /// Session: next module_id.
1067        pub session_next_module: u64,
1068        /// Session: number of session ops performed.
1069        pub session_op_count: u64,
1070        /// Session: number of `mem_free` ops performed (Phase 48.16 SDK
1071        /// polish — used by the RAII Drop tests in
1072        /// `crates/grafos-std/src/gpu.rs`).
1073        pub session_mem_free_count: u64,
1074        /// Session: number of `module_unload` ops performed (Phase 48.16
1075        /// SDK polish — used by the GpuModule RAII Drop tests in
1076        /// `crates/grafos-std/src/gpu.rs`).
1077        pub session_module_unload_count: u64,
1078        /// When set, session ops return this error code.
1079        pub session_error: Option<i32>,
1080        /// Lease: next lease_id counter.
1081        pub lease_next_id: u128,
1082        /// Lease: set of active lease IDs.
1083        pub lease_active: BTreeMap<u128, ()>,
1084        /// When set, lease ops return this error code.
1085        pub lease_error: Option<i32>,
1086    }
1087
1088    pub struct MockNet {
1089        /// Pre-configured interface name.
1090        pub interface_name: alloc::string::String,
1091        /// Pre-configured bandwidth in bits per second.
1092        pub bandwidth: u64,
1093    }
1094
1095    impl Default for MockNet {
1096        fn default() -> Self {
1097            MockNet {
1098                interface_name: alloc::string::String::from("eth-mock0"),
1099                bandwidth: 1_000_000_000,
1100            }
1101        }
1102    }
1103
1104    pub struct MockState {
1105        pub fbmu: MockFbmu,
1106        pub fbbu: MockFbbu,
1107        pub fbmu_leases: BTreeMap<u128, MockFbmuLease>,
1108        pub fbbu_leases: BTreeMap<u128, MockFbbuLease>,
1109        pub next_lease_seq: u64,
1110        pub tasklet: MockTasklet,
1111        pub gpu: MockGpu,
1112        pub net: MockNet,
1113        /// Mock unix time used by lease lifecycle logic.
1114        pub now_unix_secs: u64,
1115        /// When set, fbmu_hello returns this error code.
1116        pub fbmu_hello_error: Option<i32>,
1117        /// When set, fbbu_hello returns this error code.
1118        pub fbbu_hello_error: Option<i32>,
1119        /// When set, tasklet_submit returns this error code.
1120        pub tasklet_submit_error: Option<i32>,
1121        /// When set, net_hello returns this error code.
1122        pub net_hello_error: Option<i32>,
1123    }
1124
1125    impl Default for MockState {
1126        fn default() -> Self {
1127            MockState {
1128                fbmu: MockFbmu::default(),
1129                fbbu: MockFbbu::default(),
1130                fbmu_leases: BTreeMap::new(),
1131                fbbu_leases: BTreeMap::new(),
1132                next_lease_seq: 1,
1133                tasklet: MockTasklet::default(),
1134                gpu: MockGpu::default(),
1135                net: MockNet::default(),
1136                now_unix_secs: 1_700_000_000,
1137                fbmu_hello_error: None,
1138                fbbu_hello_error: None,
1139                tasklet_submit_error: None,
1140                net_hello_error: None,
1141            }
1142        }
1143    }
1144
1145    #[cfg(feature = "std")]
1146    thread_local! {
1147        pub static MOCK: std::cell::RefCell<MockState> = std::cell::RefCell::new(MockState::default());
1148    }
1149
1150    // Without std, we use a static mutex via a simple spin lock.
1151    // This is only for native testing — not production.
1152    #[cfg(not(feature = "std"))]
1153    mod nostd_mock {
1154        use super::MockState;
1155        use core::sync::atomic::{AtomicBool, Ordering};
1156
1157        static LOCK: AtomicBool = AtomicBool::new(false);
1158        static mut STATE: Option<MockState> = None;
1159
1160        pub fn with_mock<F, R>(f: F) -> R
1161        where
1162            F: FnOnce(&mut MockState) -> R,
1163        {
1164            while LOCK
1165                .compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed)
1166                .is_err()
1167            {
1168                core::hint::spin_loop();
1169            }
1170            let state = unsafe {
1171                if STATE.is_none() {
1172                    STATE = Some(MockState::default());
1173                }
1174                STATE.as_mut().unwrap()
1175            };
1176            let result = f(state);
1177            LOCK.store(false, Ordering::Release);
1178            result
1179        }
1180    }
1181
1182    pub fn with_mock<F, R>(f: F) -> R
1183    where
1184        F: FnOnce(&mut MockState) -> R,
1185    {
1186        #[cfg(feature = "std")]
1187        {
1188            MOCK.with(|cell| f(&mut cell.borrow_mut()))
1189        }
1190        #[cfg(not(feature = "std"))]
1191        {
1192            nostd_mock::with_mock(f)
1193        }
1194    }
1195
1196    fn next_lease_id(state: &mut MockState, tag: u8) -> u128 {
1197        let seq = state.next_lease_seq as u128;
1198        state.next_lease_seq = state.next_lease_seq.saturating_add(1);
1199        ((state.now_unix_secs as u128) << 64) | ((tag as u128) << 56) | seq
1200    }
1201
1202    fn refresh_fbmu_leases(state: &mut MockState) {
1203        let now = state.now_unix_secs;
1204        for lease in state.fbmu_leases.values_mut() {
1205            if lease.status == LEASE_STATUS_ACTIVE && now >= lease.expires_at_unix_secs {
1206                lease.status = LEASE_STATUS_EXPIRED;
1207            }
1208        }
1209    }
1210
1211    fn refresh_fbbu_leases(state: &mut MockState) {
1212        let now = state.now_unix_secs;
1213        for lease in state.fbbu_leases.values_mut() {
1214            if lease.status == LEASE_STATUS_ACTIVE && now >= lease.expires_at_unix_secs {
1215                lease.status = LEASE_STATUS_EXPIRED;
1216            }
1217        }
1218    }
1219
1220    pub fn fbmu_hello_impl() -> i32 {
1221        with_mock(|s| {
1222            if let Some(err) = s.fbmu_hello_error {
1223                return err;
1224            }
1225            s.fbmu.hello_called = true;
1226            0
1227        })
1228    }
1229
1230    pub fn fbmu_write_impl(offset: u64, data: &[u8]) -> i32 {
1231        with_mock(|s| {
1232            s.fbmu.arena.insert(offset, data.to_vec());
1233            0
1234        })
1235    }
1236
1237    pub fn fbmu_read_impl(offset: u64, buf: &mut [u8]) -> i32 {
1238        with_mock(|s| {
1239            if let Some(data) = s.fbmu.arena.get(&offset) {
1240                let len = buf.len().min(data.len());
1241                buf[..len].copy_from_slice(&data[..len]);
1242                len as i32
1243            } else {
1244                // Return zeroed data of requested length
1245                for b in buf.iter_mut() {
1246                    *b = 0;
1247                }
1248                buf.len() as i32
1249            }
1250        })
1251    }
1252
1253    pub fn fbmu_get_arena_size_impl() -> i64 {
1254        with_mock(|s| s.fbmu.arena_size as i64)
1255    }
1256
1257    pub fn fbmu_alloc_impl(min_bytes: u64, lease_secs: u64) -> (i32, u128, u64, u64) {
1258        with_mock(|s| {
1259            if s.fbmu.arena_size < min_bytes {
1260                return (-4, 0, 0, 0);
1261            }
1262            let lease_id = next_lease_id(s, 0x01);
1263            let expires = s.now_unix_secs.saturating_add(lease_secs.max(1));
1264            let lease = MockFbmuLease {
1265                status: LEASE_STATUS_ACTIVE,
1266                expires_at_unix_secs: expires,
1267                arena_size: s.fbmu.arena_size,
1268                arena: BTreeMap::new(),
1269            };
1270            s.fbmu_leases.insert(lease_id, lease);
1271            (0, lease_id, expires, s.fbmu.arena_size)
1272        })
1273    }
1274
1275    pub fn fbmu_query_impl(lease_id: u128) -> (i32, u8, u64, u64) {
1276        with_mock(|s| {
1277            refresh_fbmu_leases(s);
1278            let Some(lease) = s.fbmu_leases.get(&lease_id) else {
1279                return (-2, LEASE_STATUS_REVOKED, 0, 0);
1280            };
1281            (
1282                0,
1283                lease.status,
1284                lease.expires_at_unix_secs,
1285                lease.arena_size,
1286            )
1287        })
1288    }
1289
1290    pub fn fbmu_renew_impl(lease_id: u128, duration_secs: u64) -> (i32, u64) {
1291        with_mock(|s| {
1292            if duration_secs == 0 {
1293                return (-1, 0);
1294            }
1295            refresh_fbmu_leases(s);
1296            let Some(lease) = s.fbmu_leases.get_mut(&lease_id) else {
1297                return (-2, 0);
1298            };
1299            if lease.status != LEASE_STATUS_ACTIVE {
1300                return (-2, lease.expires_at_unix_secs);
1301            }
1302            let requested_exp = s.now_unix_secs.saturating_add(duration_secs);
1303            if requested_exp > lease.expires_at_unix_secs {
1304                lease.expires_at_unix_secs = requested_exp;
1305            }
1306            (0, lease.expires_at_unix_secs)
1307        })
1308    }
1309
1310    pub fn fbmu_free_impl(lease_id: u128) -> i32 {
1311        with_mock(|s| {
1312            refresh_fbmu_leases(s);
1313            let Some(lease) = s.fbmu_leases.get_mut(&lease_id) else {
1314                return -2;
1315            };
1316            lease.status = LEASE_STATUS_REVOKED;
1317            0
1318        })
1319    }
1320
1321    pub fn fbmu_write_lease_impl(lease_id: u128, offset: u64, data: &[u8]) -> i32 {
1322        with_mock(|s| {
1323            refresh_fbmu_leases(s);
1324            let Some(lease) = s.fbmu_leases.get_mut(&lease_id) else {
1325                return -2;
1326            };
1327            if lease.status != LEASE_STATUS_ACTIVE {
1328                return -2;
1329            }
1330            let end = match offset.checked_add(data.len() as u64) {
1331                Some(v) => v,
1332                None => return -4,
1333            };
1334            if end > lease.arena_size {
1335                return -4;
1336            }
1337            lease.arena.insert(offset, data.to_vec());
1338            0
1339        })
1340    }
1341
1342    pub fn fbmu_read_lease_impl(lease_id: u128, offset: u64, buf: &mut [u8]) -> i32 {
1343        with_mock(|s| {
1344            refresh_fbmu_leases(s);
1345            let Some(lease) = s.fbmu_leases.get_mut(&lease_id) else {
1346                return -2;
1347            };
1348            if lease.status != LEASE_STATUS_ACTIVE {
1349                return -2;
1350            }
1351            if offset >= lease.arena_size {
1352                return -4;
1353            }
1354            let max_len = (lease.arena_size - offset) as usize;
1355            let read_len = buf.len().min(max_len);
1356            for b in buf.iter_mut().take(read_len) {
1357                *b = 0;
1358            }
1359            if let Some(data) = lease.arena.get(&offset) {
1360                let copy_len = read_len.min(data.len());
1361                buf[..copy_len].copy_from_slice(&data[..copy_len]);
1362            }
1363            read_len as i32
1364        })
1365    }
1366
1367    pub fn fbbu_hello_impl() -> i32 {
1368        with_mock(|s| {
1369            if let Some(err) = s.fbbu_hello_error {
1370                return err;
1371            }
1372            s.fbbu.hello_called = true;
1373            0
1374        })
1375    }
1376
1377    pub fn fbbu_write_block_impl(lba: u64, data: &[u8; 512]) -> i32 {
1378        with_mock(|s| {
1379            if lba >= s.fbbu.num_blocks {
1380                return -4; // CapacityExceeded
1381            }
1382            s.fbbu.blocks.insert(lba, *data);
1383            0
1384        })
1385    }
1386
1387    pub fn fbbu_read_block_impl(lba: u64, buf: &mut [u8; 512]) -> i32 {
1388        with_mock(|s| {
1389            if lba >= s.fbbu.num_blocks {
1390                return -4; // CapacityExceeded
1391            }
1392            if let Some(block) = s.fbbu.blocks.get(&lba) {
1393                *buf = *block;
1394            } else {
1395                *buf = [0u8; 512];
1396            }
1397            0
1398        })
1399    }
1400
1401    pub fn fbbu_get_num_blocks_impl() -> i64 {
1402        with_mock(|s| s.fbbu.num_blocks as i64)
1403    }
1404
1405    pub fn fbbu_alloc_impl(min_blocks: u64, lease_secs: u64) -> (i32, u128, u64, u64, u32) {
1406        with_mock(|s| {
1407            if s.fbbu.num_blocks < min_blocks {
1408                return (-4, 0, 0, 0, 0);
1409            }
1410            let lease_id = next_lease_id(s, 0x02);
1411            let expires = s.now_unix_secs.saturating_add(lease_secs.max(1));
1412            let lease = MockFbbuLease {
1413                status: LEASE_STATUS_ACTIVE,
1414                expires_at_unix_secs: expires,
1415                block_size: 512,
1416                num_blocks: s.fbbu.num_blocks,
1417                blocks: BTreeMap::new(),
1418            };
1419            s.fbbu_leases.insert(lease_id, lease);
1420            (0, lease_id, expires, s.fbbu.num_blocks, 512)
1421        })
1422    }
1423
1424    pub fn fbbu_query_impl(lease_id: u128) -> (i32, u8, u64, u64, u32) {
1425        with_mock(|s| {
1426            refresh_fbbu_leases(s);
1427            let Some(lease) = s.fbbu_leases.get(&lease_id) else {
1428                return (-2, LEASE_STATUS_REVOKED, 0, 0, 0);
1429            };
1430            (
1431                0,
1432                lease.status,
1433                lease.expires_at_unix_secs,
1434                lease.num_blocks,
1435                lease.block_size,
1436            )
1437        })
1438    }
1439
1440    pub fn fbbu_renew_impl(lease_id: u128, duration_secs: u64) -> (i32, u64) {
1441        with_mock(|s| {
1442            if duration_secs == 0 {
1443                return (-1, 0);
1444            }
1445            refresh_fbbu_leases(s);
1446            let Some(lease) = s.fbbu_leases.get_mut(&lease_id) else {
1447                return (-2, 0);
1448            };
1449            if lease.status != LEASE_STATUS_ACTIVE {
1450                return (-2, lease.expires_at_unix_secs);
1451            }
1452            let requested_exp = s.now_unix_secs.saturating_add(duration_secs);
1453            if requested_exp > lease.expires_at_unix_secs {
1454                lease.expires_at_unix_secs = requested_exp;
1455            }
1456            (0, lease.expires_at_unix_secs)
1457        })
1458    }
1459
1460    pub fn fbbu_free_impl(lease_id: u128) -> i32 {
1461        with_mock(|s| {
1462            refresh_fbbu_leases(s);
1463            let Some(lease) = s.fbbu_leases.get_mut(&lease_id) else {
1464                return -2;
1465            };
1466            lease.status = LEASE_STATUS_REVOKED;
1467            0
1468        })
1469    }
1470
1471    pub fn fbbu_write_block_lease_impl(lease_id: u128, lba: u64, data: &[u8; 512]) -> i32 {
1472        with_mock(|s| {
1473            refresh_fbbu_leases(s);
1474            let Some(lease) = s.fbbu_leases.get_mut(&lease_id) else {
1475                return -2;
1476            };
1477            if lease.status != LEASE_STATUS_ACTIVE {
1478                return -2;
1479            }
1480            if lba >= lease.num_blocks {
1481                return -4;
1482            }
1483            lease.blocks.insert(lba, *data);
1484            0
1485        })
1486    }
1487
1488    pub fn fbbu_read_block_lease_impl(lease_id: u128, lba: u64, buf: &mut [u8; 512]) -> i32 {
1489        with_mock(|s| {
1490            refresh_fbbu_leases(s);
1491            let Some(lease) = s.fbbu_leases.get_mut(&lease_id) else {
1492                return -2;
1493            };
1494            if lease.status != LEASE_STATUS_ACTIVE {
1495                return -2;
1496            }
1497            if lba >= lease.num_blocks {
1498                return -4;
1499            }
1500            if let Some(block) = lease.blocks.get(&lba) {
1501                *buf = *block;
1502            } else {
1503                *buf = [0u8; 512];
1504            }
1505            0
1506        })
1507    }
1508
1509    pub fn tasklet_submit_impl(
1510        _wasm: &[u8],
1511        _input: &[u8],
1512        _fuel: u64,
1513        output_buf: &mut [u8],
1514    ) -> (i32, u64, usize) {
1515        with_mock(|s| {
1516            if let Some(err) = s.tasklet_submit_error {
1517                return (err, 0, 0);
1518            }
1519            s.tasklet.submit_count += 1;
1520            let out_len = output_buf.len().min(s.tasklet.output.len());
1521            output_buf[..out_len].copy_from_slice(&s.tasklet.output[..out_len]);
1522            (0, s.tasklet.exit_code, out_len)
1523        })
1524    }
1525
1526    // ── GPU session mock impls ──────────────────────────────────────────
1527
1528    pub fn gpu_session_mem_alloc_impl(_lease_id: u128, _size: u64) -> (i32, u64) {
1529        with_mock(|s| {
1530            if let Some(err) = s.gpu.session_error {
1531                return (err, 0);
1532            }
1533            s.gpu.session_next_handle += 1;
1534            s.gpu.session_op_count += 1;
1535            (0, s.gpu.session_next_handle)
1536        })
1537    }
1538
1539    pub fn gpu_session_mem_write_impl(
1540        _lease_id: u128,
1541        _handle: u64,
1542        _offset: u64,
1543        _data: &[u8],
1544    ) -> i32 {
1545        with_mock(|s| {
1546            if let Some(err) = s.gpu.session_error {
1547                return err;
1548            }
1549            s.gpu.session_op_count += 1;
1550            0
1551        })
1552    }
1553
1554    pub fn gpu_session_mem_read_impl(
1555        _lease_id: u128,
1556        _handle: u64,
1557        _offset: u64,
1558        size: u32,
1559    ) -> (i32, Vec<u8>) {
1560        with_mock(|s| {
1561            if let Some(err) = s.gpu.session_error {
1562                return (err, Vec::new());
1563            }
1564            s.gpu.session_op_count += 1;
1565            (0, alloc::vec![0u8; size as usize])
1566        })
1567    }
1568
1569    pub fn gpu_session_mem_free_impl(_lease_id: u128, _handle: u64) -> i32 {
1570        with_mock(|s| {
1571            if let Some(err) = s.gpu.session_error {
1572                return err;
1573            }
1574            s.gpu.session_op_count += 1;
1575            s.gpu.session_mem_free_count += 1;
1576            0
1577        })
1578    }
1579
1580    pub fn gpu_session_module_load_impl(_lease_id: u128, _binary: &[u8]) -> (i32, u64) {
1581        with_mock(|s| {
1582            if let Some(err) = s.gpu.session_error {
1583                return (err, 0);
1584            }
1585            s.gpu.session_next_module += 1;
1586            s.gpu.session_op_count += 1;
1587            (0, s.gpu.session_next_module)
1588        })
1589    }
1590
1591    pub fn gpu_session_launch_impl(
1592        _lease_id: u128,
1593        _module_id: u64,
1594        _kernel_name: &str,
1595        _grid_dim: [u32; 3],
1596        _block_dim: [u32; 3],
1597        _args: &[u8],
1598        _arg_sizes: &[u32],
1599    ) -> i32 {
1600        with_mock(|s| {
1601            if let Some(err) = s.gpu.session_error {
1602                return err;
1603            }
1604            s.gpu.session_op_count += 1;
1605            0
1606        })
1607    }
1608
1609    pub fn gpu_session_sync_impl(_lease_id: u128) -> i32 {
1610        with_mock(|s| {
1611            if let Some(err) = s.gpu.session_error {
1612                return err;
1613            }
1614            s.gpu.session_op_count += 1;
1615            0
1616        })
1617    }
1618
1619    pub fn gpu_session_module_unload_impl(_lease_id: u128, _module_id: u64) -> i32 {
1620        with_mock(|s| {
1621            if let Some(err) = s.gpu.session_error {
1622                return err;
1623            }
1624            s.gpu.session_op_count += 1;
1625            s.gpu.session_module_unload_count += 1;
1626            0
1627        })
1628    }
1629
1630    // ── GPU lease mock impls ────────────────────────────────────────────
1631
1632    pub fn gpu_lease_alloc_impl(_resource_id: u128, _lease_secs: u32) -> (i32, u128) {
1633        with_mock(|s| {
1634            if let Some(err) = s.gpu.lease_error {
1635                return (err, 0);
1636            }
1637            s.gpu.lease_next_id += 1;
1638            let id = s.gpu.lease_next_id;
1639            s.gpu.lease_active.insert(id, ());
1640            (0, id)
1641        })
1642    }
1643
1644    pub fn gpu_lease_renew_impl(lease_id: u128, _duration_secs: u32) -> i32 {
1645        with_mock(|s| {
1646            if let Some(err) = s.gpu.lease_error {
1647                return err;
1648            }
1649            if s.gpu.lease_active.contains_key(&lease_id) {
1650                0
1651            } else {
1652                -1
1653            }
1654        })
1655    }
1656
1657    pub fn gpu_lease_free_impl(lease_id: u128) -> i32 {
1658        with_mock(|s| {
1659            if let Some(err) = s.gpu.lease_error {
1660                return err;
1661            }
1662            s.gpu.lease_active.remove(&lease_id);
1663            0
1664        })
1665    }
1666
1667    pub fn gpu_lease_query_impl(lease_id: u128) -> (i32, u32) {
1668        with_mock(|s| {
1669            if let Some(err) = s.gpu.lease_error {
1670                return (err, 0);
1671            }
1672            if s.gpu.lease_active.contains_key(&lease_id) {
1673                (0, 0) // active
1674            } else {
1675                (0, 1) // expired
1676            }
1677        })
1678    }
1679
1680    pub fn net_hello_impl(_min_bw: u64) -> (i32, alloc::string::String, u64) {
1681        with_mock(|s| {
1682            if let Some(err) = s.net_hello_error {
1683                return (err, alloc::string::String::new(), 0);
1684            }
1685            (0, s.net.interface_name.clone(), s.net.bandwidth)
1686        })
1687    }
1688
1689    /// Phase 48.15 W2a — test helper. Inject a status code that the next
1690    /// `gpu_session_*` mock op will return. Pass `None` to clear. Used by
1691    /// the host-mock parity roundtrip test in
1692    /// `crates/grafos-std/tests/gpu_session_status_translation.rs`.
1693    pub fn _set_gpu_session_error(err: Option<i32>) {
1694        with_mock(|s| s.gpu.session_error = err);
1695    }
1696
1697    /// Phase 48.15 W2a — test helper. Snapshot the current `session_op_count`
1698    /// (incremented on every successful mock session op). Lets the parity
1699    /// test confirm that the happy path actually executed the mock body.
1700    pub fn _gpu_session_op_count() -> u64 {
1701        with_mock(|s| s.gpu.session_op_count)
1702    }
1703
1704    /// Phase 48.16 SDK polish — snapshot the number of `mem_free` ops
1705    /// the mock has observed. Used by the RAII Drop tests in
1706    /// `crates/grafos-std/src/gpu.rs` to verify that handle Drop
1707    /// actually issues a free hostcall.
1708    pub fn _gpu_session_mem_free_count() -> u64 {
1709        with_mock(|s| s.gpu.session_mem_free_count)
1710    }
1711
1712    /// Phase 48.16 SDK polish — snapshot the number of `module_unload`
1713    /// ops the mock has observed. Used by the GpuModule RAII Drop
1714    /// tests in `crates/grafos-std/src/gpu.rs`.
1715    pub fn _gpu_session_module_unload_count() -> u64 {
1716        with_mock(|s| s.gpu.session_module_unload_count)
1717    }
1718}
1719
1720/// Test-only re-exports for the host mock. Hidden from rustdoc; the
1721/// `_`-prefixed names emphasize the test-only contract.
1722#[cfg(not(target_arch = "wasm32"))]
1723#[doc(hidden)]
1724pub mod test_mock {
1725    pub use super::mock::{
1726        _gpu_session_mem_free_count, _gpu_session_module_unload_count, _gpu_session_op_count,
1727        _set_gpu_session_error,
1728    };
1729}
1730
1731/// Perform the FBMU HELLO handshake with the host.
1732///
1733/// Establishes the memory data-plane session. Must be called before any
1734/// FBMU read/write operations.
1735///
1736/// # Errors
1737///
1738/// Returns [`FabricError::Disconnected`] if the host connection is not
1739/// available, or another [`FabricError`] variant based on the host status
1740/// code.
1741#[cfg(not(target_arch = "wasm32"))]
1742pub fn fbmu_hello() -> Result<()> {
1743    let status = mock::fbmu_hello_impl();
1744    match FabricError::from_status(status) {
1745        None => Ok(()),
1746        Some(e) => Err(e),
1747    }
1748}
1749
1750/// Write `data` to the FBMU arena at the given byte `offset`.
1751///
1752/// # Errors
1753///
1754/// Returns a [`FabricError`] if the host reports a write failure.
1755#[cfg(not(target_arch = "wasm32"))]
1756pub fn fbmu_write(offset: u64, data: &[u8]) -> Result<()> {
1757    let status = mock::fbmu_write_impl(offset, data);
1758    match FabricError::from_status(status) {
1759        None => Ok(()),
1760        Some(e) => Err(e),
1761    }
1762}
1763
1764/// Read from the FBMU arena at `offset` into `buf`.
1765///
1766/// Returns the number of bytes actually read, which may be less than
1767/// `buf.len()` if the arena region is shorter.
1768///
1769/// # Errors
1770///
1771/// Returns a [`FabricError`] if the host reports a read failure.
1772#[cfg(not(target_arch = "wasm32"))]
1773pub fn fbmu_read(offset: u64, buf: &mut [u8]) -> Result<usize> {
1774    let n = mock::fbmu_read_impl(offset, buf);
1775    if n < 0 {
1776        Err(FabricError::from_status(n).unwrap_or(FabricError::IoError(n)))
1777    } else {
1778        Ok(n as usize)
1779    }
1780}
1781
1782/// Query the total FBMU arena size in bytes.
1783///
1784/// # Errors
1785///
1786/// Returns [`FabricError::IoError`] if the host returns a negative value.
1787#[cfg(not(target_arch = "wasm32"))]
1788pub fn fbmu_get_arena_size() -> Result<u64> {
1789    let size = mock::fbmu_get_arena_size_impl();
1790    if size < 0 {
1791        Err(FabricError::IoError(size as i32))
1792    } else {
1793        Ok(size as u64)
1794    }
1795}
1796
1797/// Allocate a memory lease from FBMU with the requested minimum capacity and TTL.
1798#[cfg(not(target_arch = "wasm32"))]
1799pub fn fbmu_alloc(min_bytes: u64, lease_secs: u64) -> Result<FbmuLeaseInfo> {
1800    let (status, lease_id, expires_at_unix_secs, arena_size) =
1801        mock::fbmu_alloc_impl(min_bytes, lease_secs);
1802    match FabricError::from_status(status) {
1803        None => Ok(FbmuLeaseInfo {
1804            lease_id,
1805            expires_at_unix_secs,
1806            arena_size,
1807            status: LEASE_STATUS_ACTIVE,
1808        }),
1809        Some(e) => Err(e),
1810    }
1811}
1812
1813/// Query FBMU lease status and capacity metadata.
1814#[cfg(not(target_arch = "wasm32"))]
1815pub fn fbmu_query(lease_id: u128) -> Result<FbmuLeaseInfo> {
1816    let (status, lease_status, expires_at_unix_secs, arena_size) = mock::fbmu_query_impl(lease_id);
1817    match FabricError::from_status(status) {
1818        None => Ok(FbmuLeaseInfo {
1819            lease_id,
1820            expires_at_unix_secs,
1821            arena_size,
1822            status: lease_status,
1823        }),
1824        Some(e) => Err(e),
1825    }
1826}
1827
1828/// Renew an FBMU lease and return the updated expiry timestamp.
1829#[cfg(not(target_arch = "wasm32"))]
1830pub fn fbmu_renew(lease_id: u128, duration_secs: u64) -> Result<u64> {
1831    let (status, expires_at_unix_secs) = mock::fbmu_renew_impl(lease_id, duration_secs);
1832    match FabricError::from_status(status) {
1833        None => Ok(expires_at_unix_secs),
1834        Some(e) => Err(e),
1835    }
1836}
1837
1838/// Free/revoke an FBMU lease.
1839#[cfg(not(target_arch = "wasm32"))]
1840pub fn fbmu_free(lease_id: u128) -> Result<()> {
1841    let status = mock::fbmu_free_impl(lease_id);
1842    match FabricError::from_status(status) {
1843        None => Ok(()),
1844        Some(e) => Err(e),
1845    }
1846}
1847
1848/// Write to FBMU using an explicit lease id.
1849#[cfg(not(target_arch = "wasm32"))]
1850pub fn fbmu_write_lease(lease_id: u128, offset: u64, data: &[u8]) -> Result<()> {
1851    let status = mock::fbmu_write_lease_impl(lease_id, offset, data);
1852    match FabricError::from_status(status) {
1853        None => Ok(()),
1854        Some(e) => Err(e),
1855    }
1856}
1857
1858/// Read from FBMU using an explicit lease id.
1859#[cfg(not(target_arch = "wasm32"))]
1860pub fn fbmu_read_lease(lease_id: u128, offset: u64, buf: &mut [u8]) -> Result<usize> {
1861    let n = mock::fbmu_read_lease_impl(lease_id, offset, buf);
1862    if n < 0 {
1863        Err(FabricError::from_status(n).unwrap_or(FabricError::IoError(n)))
1864    } else {
1865        Ok(n as usize)
1866    }
1867}
1868
1869/// Perform the FBBU HELLO handshake with the host.
1870///
1871/// Establishes the block data-plane session. Must be called before any
1872/// FBBU read/write operations.
1873///
1874/// # Errors
1875///
1876/// Returns [`FabricError::Disconnected`] if the host connection is not
1877/// available.
1878#[cfg(not(target_arch = "wasm32"))]
1879pub fn fbbu_hello() -> Result<()> {
1880    let status = mock::fbbu_hello_impl();
1881    match FabricError::from_status(status) {
1882        None => Ok(()),
1883        Some(e) => Err(e),
1884    }
1885}
1886
1887/// Write a single 512-byte block at the given logical block address.
1888///
1889/// # Errors
1890///
1891/// Returns [`FabricError::CapacityExceeded`] if `lba` is beyond the
1892/// device's block count.
1893#[cfg(not(target_arch = "wasm32"))]
1894pub fn fbbu_write_block(lba: u64, data: &[u8; 512]) -> Result<()> {
1895    let status = mock::fbbu_write_block_impl(lba, data);
1896    match FabricError::from_status(status) {
1897        None => Ok(()),
1898        Some(e) => Err(e),
1899    }
1900}
1901
1902/// Read a single 512-byte block at the given logical block address.
1903///
1904/// # Errors
1905///
1906/// Returns [`FabricError::CapacityExceeded`] if `lba` is beyond the
1907/// device's block count.
1908#[cfg(not(target_arch = "wasm32"))]
1909pub fn fbbu_read_block(lba: u64, buf: &mut [u8; 512]) -> Result<()> {
1910    let status = mock::fbbu_read_block_impl(lba, buf);
1911    match FabricError::from_status(status) {
1912        None => Ok(()),
1913        Some(e) => Err(e),
1914    }
1915}
1916
1917/// Query the total number of blocks available on the FBBU device.
1918///
1919/// # Errors
1920///
1921/// Returns [`FabricError::IoError`] if the host returns a negative value.
1922#[cfg(not(target_arch = "wasm32"))]
1923pub fn fbbu_get_num_blocks() -> Result<u64> {
1924    let n = mock::fbbu_get_num_blocks_impl();
1925    if n < 0 {
1926        Err(FabricError::IoError(n as i32))
1927    } else {
1928        Ok(n as u64)
1929    }
1930}
1931
1932/// Allocate a block lease from FBBU with the requested minimum capacity and TTL.
1933#[cfg(not(target_arch = "wasm32"))]
1934pub fn fbbu_alloc(min_blocks: u64, lease_secs: u64) -> Result<FbbuLeaseInfo> {
1935    let (status, lease_id, expires_at_unix_secs, num_blocks, block_size) =
1936        mock::fbbu_alloc_impl(min_blocks, lease_secs);
1937    match FabricError::from_status(status) {
1938        None => Ok(FbbuLeaseInfo {
1939            lease_id,
1940            expires_at_unix_secs,
1941            num_blocks,
1942            block_size,
1943            status: LEASE_STATUS_ACTIVE,
1944        }),
1945        Some(e) => Err(e),
1946    }
1947}
1948
1949/// Query FBBU lease status and geometry metadata.
1950#[cfg(not(target_arch = "wasm32"))]
1951pub fn fbbu_query(lease_id: u128) -> Result<FbbuLeaseInfo> {
1952    let (status, lease_status, expires_at_unix_secs, num_blocks, block_size) =
1953        mock::fbbu_query_impl(lease_id);
1954    match FabricError::from_status(status) {
1955        None => Ok(FbbuLeaseInfo {
1956            lease_id,
1957            expires_at_unix_secs,
1958            num_blocks,
1959            block_size,
1960            status: lease_status,
1961        }),
1962        Some(e) => Err(e),
1963    }
1964}
1965
1966/// Renew an FBBU lease and return the updated expiry timestamp.
1967#[cfg(not(target_arch = "wasm32"))]
1968pub fn fbbu_renew(lease_id: u128, duration_secs: u64) -> Result<u64> {
1969    let (status, expires_at_unix_secs) = mock::fbbu_renew_impl(lease_id, duration_secs);
1970    match FabricError::from_status(status) {
1971        None => Ok(expires_at_unix_secs),
1972        Some(e) => Err(e),
1973    }
1974}
1975
1976/// Free/revoke an FBBU lease.
1977#[cfg(not(target_arch = "wasm32"))]
1978pub fn fbbu_free(lease_id: u128) -> Result<()> {
1979    let status = mock::fbbu_free_impl(lease_id);
1980    match FabricError::from_status(status) {
1981        None => Ok(()),
1982        Some(e) => Err(e),
1983    }
1984}
1985
1986/// Write a 512-byte block via FBBU using an explicit lease id.
1987#[cfg(not(target_arch = "wasm32"))]
1988pub fn fbbu_write_block_lease(lease_id: u128, lba: u64, data: &[u8; 512]) -> Result<()> {
1989    let status = mock::fbbu_write_block_lease_impl(lease_id, lba, data);
1990    match FabricError::from_status(status) {
1991        None => Ok(()),
1992        Some(e) => Err(e),
1993    }
1994}
1995
1996/// Read a 512-byte block via FBBU using an explicit lease id.
1997#[cfg(not(target_arch = "wasm32"))]
1998pub fn fbbu_read_block_lease(lease_id: u128, lba: u64, buf: &mut [u8; 512]) -> Result<()> {
1999    let status = mock::fbbu_read_block_lease_impl(lease_id, lba, buf);
2000    match FabricError::from_status(status) {
2001        None => Ok(()),
2002        Some(e) => Err(e),
2003    }
2004}
2005
2006/// Submit a WASM tasklet for execution on a fabric CPU resource.
2007///
2008/// # Parameters
2009///
2010/// - `wasm`: The WASM module bytes to execute.
2011/// - `input`: Input data passed to the tasklet.
2012/// - `fuel`: WASM fuel limit (instruction budget).
2013/// - `output_buf`: Buffer for tasklet output.
2014///
2015/// # Returns
2016///
2017/// On success, returns `(exit_code, output_len)`.
2018///
2019/// # Errors
2020///
2021/// Returns a [`FabricError`] if the submission fails.
2022#[cfg(not(target_arch = "wasm32"))]
2023pub fn tasklet_submit(
2024    wasm: &[u8],
2025    input: &[u8],
2026    fuel: u64,
2027    output_buf: &mut [u8],
2028) -> Result<(u64, usize)> {
2029    let (status, exit_code, output_len) = mock::tasklet_submit_impl(wasm, input, fuel, output_buf);
2030    match FabricError::from_status(status) {
2031        None => Ok((exit_code, output_len)),
2032        Some(e) => Err(e),
2033    }
2034}
2035
2036// ---------------------------------------------------------------------------
2037// GPU session host functions (non-WASM mock implementations)
2038// ---------------------------------------------------------------------------
2039
2040/// Allocate device memory within a GPU session.
2041#[cfg(not(target_arch = "wasm32"))]
2042pub fn gpu_session_mem_alloc(lease_id: u128, size: u64) -> Result<u64> {
2043    let (status, handle) = mock::gpu_session_mem_alloc_impl(lease_id, size);
2044    // Phase 48.15 W2a — go through `session_status_to_result` so positive
2045    // SESSION_STATUS_* codes injected via the mock surface as
2046    // `FabricError::GpuSessionFailed(u8)`, matching the wasm32 path.
2047    session_status_to_result(status).map(|()| handle)
2048}
2049
2050/// Write data to device memory within a GPU session.
2051#[cfg(not(target_arch = "wasm32"))]
2052pub fn gpu_session_mem_write(lease_id: u128, handle: u64, offset: u64, data: &[u8]) -> Result<()> {
2053    let status = mock::gpu_session_mem_write_impl(lease_id, handle, offset, data);
2054    session_status_to_result(status)
2055}
2056
2057/// Read data from device memory within a GPU session.
2058#[cfg(not(target_arch = "wasm32"))]
2059pub fn gpu_session_mem_read(
2060    lease_id: u128,
2061    handle: u64,
2062    offset: u64,
2063    size: u32,
2064) -> Result<Vec<u8>> {
2065    let (status, data) = mock::gpu_session_mem_read_impl(lease_id, handle, offset, size);
2066    session_status_to_result(status).map(|()| data)
2067}
2068
2069/// Free device memory within a GPU session.
2070#[cfg(not(target_arch = "wasm32"))]
2071pub fn gpu_session_mem_free(lease_id: u128, handle: u64) -> Result<()> {
2072    let status = mock::gpu_session_mem_free_impl(lease_id, handle);
2073    session_status_to_result(status)
2074}
2075
2076/// Load a GPU module within a GPU session.
2077#[cfg(not(target_arch = "wasm32"))]
2078pub fn gpu_session_module_load(lease_id: u128, binary: &[u8]) -> Result<u64> {
2079    let (status, module_id) = mock::gpu_session_module_load_impl(lease_id, binary);
2080    session_status_to_result(status).map(|()| module_id)
2081}
2082
2083/// Launch a kernel within a GPU session.
2084#[cfg(not(target_arch = "wasm32"))]
2085pub fn gpu_session_launch(
2086    lease_id: u128,
2087    module_id: u64,
2088    kernel_name: &str,
2089    grid_dim: [u32; 3],
2090    block_dim: [u32; 3],
2091    args: &[u8],
2092    arg_sizes: &[u32],
2093) -> Result<()> {
2094    let status = mock::gpu_session_launch_impl(
2095        lease_id,
2096        module_id,
2097        kernel_name,
2098        grid_dim,
2099        block_dim,
2100        args,
2101        arg_sizes,
2102    );
2103    session_status_to_result(status)
2104}
2105
2106/// Synchronize a GPU session.
2107#[cfg(not(target_arch = "wasm32"))]
2108pub fn gpu_session_sync(lease_id: u128) -> Result<()> {
2109    let status = mock::gpu_session_sync_impl(lease_id);
2110    session_status_to_result(status)
2111}
2112
2113/// Unload a GPU module within a GPU session (Phase 48.16 SDK polish).
2114#[cfg(not(target_arch = "wasm32"))]
2115pub fn gpu_session_module_unload(lease_id: u128, module_id: u64) -> Result<()> {
2116    let status = mock::gpu_session_module_unload_impl(lease_id, module_id);
2117    session_status_to_result(status)
2118}
2119
2120// ---------------------------------------------------------------------------
2121// GPU lease lifecycle (native mock)
2122// ---------------------------------------------------------------------------
2123
2124/// Allocate a GPU lease for the given resource.
2125/// Returns an opaque lease_id on success.
2126#[cfg(not(target_arch = "wasm32"))]
2127pub fn gpu_lease_alloc(resource_id: u128, lease_secs: u32) -> Result<u128> {
2128    let (status, lease_id) = mock::gpu_lease_alloc_impl(resource_id, lease_secs);
2129    match FabricError::from_status(status) {
2130        None => Ok(lease_id),
2131        Some(e) => Err(e),
2132    }
2133}
2134
2135/// Renew an existing GPU lease.
2136#[cfg(not(target_arch = "wasm32"))]
2137pub fn gpu_lease_renew(lease_id: u128, duration_secs: u32) -> Result<()> {
2138    let status = mock::gpu_lease_renew_impl(lease_id, duration_secs);
2139    match FabricError::from_status(status) {
2140        None => Ok(()),
2141        Some(e) => Err(e),
2142    }
2143}
2144
2145/// Free/revoke a GPU lease.
2146#[cfg(not(target_arch = "wasm32"))]
2147pub fn gpu_lease_free(lease_id: u128) -> Result<()> {
2148    let status = mock::gpu_lease_free_impl(lease_id);
2149    match FabricError::from_status(status) {
2150        None => Ok(()),
2151        Some(e) => Err(e),
2152    }
2153}
2154
2155/// Query the status of a GPU lease.
2156/// Returns a status code (0=active, 1=expired, 2=revoked, 3=fenced).
2157#[cfg(not(target_arch = "wasm32"))]
2158pub fn gpu_lease_query(lease_id: u128) -> Result<u32> {
2159    let (status, lease_status) = mock::gpu_lease_query_impl(lease_id);
2160    match FabricError::from_status(status) {
2161        None => Ok(lease_status),
2162        Some(e) => Err(e),
2163    }
2164}
2165
2166/// Reset all mock state to defaults.
2167///
2168/// Restores the FBMU arena to 65536 bytes, the FBBU block count to 1024,
2169/// and clears all stored data and error injections. Call this at the start
2170/// of each test to ensure a clean slate.
2171///
2172/// Only available on native (non-WASM) targets.
2173#[cfg(not(target_arch = "wasm32"))]
2174pub fn reset_mock() {
2175    mock::with_mock(|s| {
2176        *s = mock::MockState::default();
2177    });
2178}
2179
2180/// Return the current unix time in seconds used by lease lifecycle logic.
2181///
2182/// On native targets this returns the mock clock value so tests can control
2183/// lease expiry deterministically. On WASM targets this currently returns 0.
2184#[cfg(not(target_arch = "wasm32"))]
2185pub fn unix_time_secs() -> u64 {
2186    mock::with_mock(|s| s.now_unix_secs)
2187}
2188
2189/// Return the current unix time in seconds used by lease lifecycle logic.
2190#[cfg(target_arch = "wasm32")]
2191pub fn unix_time_secs() -> u64 {
2192    0
2193}
2194
2195/// Set the mock FBMU arena size in bytes.
2196///
2197/// Controls the value returned by [`fbmu_get_arena_size`]. Used to test
2198/// capacity checks in [`MemBuilder::acquire`](crate::mem::MemBuilder::acquire).
2199///
2200/// Only available on native (non-WASM) targets.
2201#[cfg(not(target_arch = "wasm32"))]
2202pub fn mock_set_fbmu_arena_size(size: u64) {
2203    mock::with_mock(|s| {
2204        s.fbmu.arena_size = size;
2205    });
2206}
2207
2208/// Set the mock unix time used by lease lifecycle logic.
2209///
2210/// Only available on native (non-WASM) targets.
2211#[cfg(not(target_arch = "wasm32"))]
2212pub fn mock_set_unix_time_secs(secs: u64) {
2213    mock::with_mock(|s| {
2214        s.now_unix_secs = secs;
2215    });
2216}
2217
2218/// Advance the mock unix time by `delta_secs`.
2219///
2220/// Only available on native (non-WASM) targets.
2221#[cfg(not(target_arch = "wasm32"))]
2222pub fn mock_advance_time_secs(delta_secs: u64) {
2223    mock::with_mock(|s| {
2224        s.now_unix_secs = s.now_unix_secs.saturating_add(delta_secs);
2225    });
2226}
2227
2228/// Set the mock FBBU block count.
2229///
2230/// Controls the value returned by [`fbbu_get_num_blocks`] and the upper
2231/// bound for LBA validation. Used to test capacity checks in
2232/// [`BlockBuilder::acquire`](crate::block::BlockBuilder::acquire).
2233///
2234/// Only available on native (non-WASM) targets.
2235#[cfg(not(target_arch = "wasm32"))]
2236pub fn mock_set_fbbu_num_blocks(n: u64) {
2237    mock::with_mock(|s| {
2238        s.fbbu.num_blocks = n;
2239    });
2240}
2241
2242/// Inject an error into the mock FBMU HELLO handshake.
2243///
2244/// When `code` is `Some(status)`, [`fbmu_hello`] will return the
2245/// corresponding [`FabricError`] instead of
2246/// succeeding. Pass `None` to restore normal behavior.
2247///
2248/// Only available on native (non-WASM) targets.
2249#[cfg(not(target_arch = "wasm32"))]
2250pub fn mock_set_fbmu_hello_error(code: Option<i32>) {
2251    mock::with_mock(|s| {
2252        s.fbmu_hello_error = code;
2253    });
2254}
2255
2256/// Inject an error into the mock FBBU HELLO handshake.
2257///
2258/// When `code` is `Some(status)`, [`fbbu_hello`] will return the
2259/// corresponding [`FabricError`] instead of
2260/// succeeding. Pass `None` to restore normal behavior.
2261///
2262/// Only available on native (non-WASM) targets.
2263#[cfg(not(target_arch = "wasm32"))]
2264pub fn mock_set_fbbu_hello_error(code: Option<i32>) {
2265    mock::with_mock(|s| {
2266        s.fbbu_hello_error = code;
2267    });
2268}
2269
2270/// Configure the mock tasklet exit code and output. Only available on native targets.
2271#[cfg(not(target_arch = "wasm32"))]
2272pub fn mock_set_tasklet_result(exit_code: u64, output: &[u8]) {
2273    mock::with_mock(|s| {
2274        s.tasklet.exit_code = exit_code;
2275        s.tasklet.output = output.to_vec();
2276    });
2277}
2278
2279/// Make mock tasklet_submit return an error. Only available on native targets.
2280#[cfg(not(target_arch = "wasm32"))]
2281pub fn mock_set_tasklet_submit_error(code: Option<i32>) {
2282    mock::with_mock(|s| {
2283        s.tasklet_submit_error = code;
2284    });
2285}
2286
2287/// Acquire a network interface with the given minimum bandwidth.
2288///
2289/// # Parameters
2290///
2291/// - `min_bw`: Minimum bandwidth in bits per second.
2292///
2293/// # Returns
2294///
2295/// On success, returns `(interface_name, actual_bandwidth)`.
2296///
2297/// # Errors
2298///
2299/// Returns a [`FabricError`] if the acquisition fails.
2300#[cfg(not(target_arch = "wasm32"))]
2301pub fn net_hello(min_bw: u64) -> Result<(alloc::string::String, u64)> {
2302    let (status, name, bw) = mock::net_hello_impl(min_bw);
2303    match FabricError::from_status(status) {
2304        None => Ok((name, bw)),
2305        Some(e) => Err(e),
2306    }
2307}
2308
2309/// Configure the mock network interface name and bandwidth. Only available on native targets.
2310#[cfg(not(target_arch = "wasm32"))]
2311pub fn mock_set_net_interface(name: &str, bandwidth: u64) {
2312    mock::with_mock(|s| {
2313        s.net.interface_name = alloc::string::String::from(name);
2314        s.net.bandwidth = bandwidth;
2315    });
2316}
2317
2318/// Make mock net_hello return an error. Only available on native targets.
2319#[cfg(not(target_arch = "wasm32"))]
2320pub fn mock_set_net_hello_error(code: Option<i32>) {
2321    mock::with_mock(|s| {
2322        s.net_hello_error = code;
2323    });
2324}