grafos_std/
net.rs

1//! Network resource module.
2//!
3//! Provides typed access to fabric network resources via NET host
4//! functions. Programs request leased network interfaces with optional
5//! bandwidth guarantees. The underlying NET_ALLOC control op (0x0700)
6//! is implemented in fabricbiosd with macvlan/SR-IOV VF allocation.
7//! On `wasm32` targets, the host functions link to real imports
8//! (`fabricbios_net_v0`). On native targets, mock implementations
9//! allow testing without network hardware.
10//!
11//! # Example
12//!
13//! ```rust
14//! use grafos_std::net::{NetBuilder, FabricNet};
15//!
16//! # grafos_std::host::reset_mock();
17//! # grafos_std::host::mock_set_net_interface("eth-fab0", 1_000_000_000);
18//! let lease = NetBuilder::new().min_bandwidth(1_000_000_000).acquire()?;
19//! assert_eq!(lease.net().bandwidth(), 1_000_000_000);
20//! assert_eq!(lease.net().interface_name(), "eth-fab0");
21//! # Ok::<(), grafos_std::FabricError>(())
22//! ```
23
24extern crate alloc;
25use alloc::string::String;
26
27use crate::error::{FabricError, Result};
28use crate::host;
29use crate::lease::{self, LeaseInfo, LeaseStatus, SharedLeaseState};
30
31const LEASE_TAG_NET: u8 = 0x03;
32
33/// Safe handle to a leased fabric network interface.
34///
35/// Provides access to the interface name and guaranteed bandwidth
36/// of a leased NIC (macvlan or SR-IOV VF). Created via
37/// [`NetBuilder::acquire`].
38#[derive(Debug)]
39pub struct FabricNet {
40    interface_name: String,
41    bandwidth: u64,
42}
43
44impl FabricNet {
45    /// Returns the guaranteed bandwidth in bits per second.
46    pub fn bandwidth(&self) -> u64 {
47        self.bandwidth
48    }
49
50    /// Returns the name of the leased network interface.
51    pub fn interface_name(&self) -> &str {
52        &self.interface_name
53    }
54}
55
56/// A network lease that auto-frees on drop.
57///
58/// Wraps a [`FabricNet`] handle with RAII lifecycle management.
59/// Created via [`NetBuilder::acquire`].
60#[derive(Debug)]
61pub struct NetLease {
62    state: SharedLeaseState,
63    net: FabricNet,
64}
65
66impl NetLease {
67    /// Access the underlying [`FabricNet`] handle.
68    pub fn net(&self) -> &FabricNet {
69        &self.net
70    }
71
72    /// Lease metadata snapshot (id, creation time, expiry, and status).
73    pub fn info(&self) -> LeaseInfo {
74        lease::info(&self.state)
75    }
76
77    /// Unique lease identifier.
78    pub fn lease_id(&self) -> u128 {
79        lease::lease_id(&self.state)
80    }
81
82    /// Lease creation timestamp (unix seconds).
83    pub fn created_at_unix_secs(&self) -> u64 {
84        lease::created_at_unix_secs(&self.state)
85    }
86
87    /// Lease expiry timestamp (unix seconds).
88    pub fn expires_at_unix_secs(&self) -> u64 {
89        lease::expires_at_unix_secs(&self.state)
90    }
91
92    /// Current lease status.
93    pub fn status(&self) -> LeaseStatus {
94        lease::status(&self.state)
95    }
96
97    /// Renew the lease TTL by `duration_secs`.
98    pub fn renew(&self, duration_secs: u64) -> Result<()> {
99        lease::renew(&self.state, duration_secs)
100    }
101
102    /// Explicitly revoke/free this lease.
103    pub fn free(&self) {
104        lease::free(&self.state);
105    }
106}
107
108impl Drop for NetLease {
109    fn drop(&mut self) {
110        lease::free(&self.state);
111    }
112}
113
114/// Builder for acquiring a fabric network lease.
115///
116/// Allows specifying minimum bandwidth requirements.
117///
118/// # Examples
119///
120/// ```rust
121/// use grafos_std::net::NetBuilder;
122///
123/// # grafos_std::host::reset_mock();
124/// # grafos_std::host::mock_set_net_interface("eth-fab0", 10_000_000_000);
125/// let lease = NetBuilder::new().min_bandwidth(1_000_000_000).acquire()?;
126/// assert!(lease.net().bandwidth() >= 1_000_000_000);
127/// # Ok::<(), grafos_std::FabricError>(())
128/// ```
129pub struct NetBuilder {
130    min_bandwidth: u64,
131    lease_secs: u64,
132}
133
134impl NetBuilder {
135    /// Create a new builder with no bandwidth constraint.
136    pub fn new() -> Self {
137        NetBuilder {
138            min_bandwidth: 0,
139            lease_secs: 300,
140        }
141    }
142
143    /// Set the minimum bandwidth required in bits per second.
144    pub fn min_bandwidth(mut self, bps: u64) -> Self {
145        self.min_bandwidth = bps;
146        self
147    }
148
149    /// Set the lease TTL in seconds.
150    pub fn lease_secs(mut self, secs: u64) -> Self {
151        self.lease_secs = secs.max(1);
152        self
153    }
154
155    /// Acquire a network lease.
156    ///
157    /// Calls the host `net_hello` function with the requested minimum
158    /// bandwidth. The host allocates a NIC (macvlan or SR-IOV VF) and
159    /// returns the interface name and actual bandwidth.
160    ///
161    /// # Errors
162    ///
163    /// Returns [`FabricError::CapacityExceeded`] if the host cannot
164    /// satisfy the bandwidth requirement, or [`FabricError::Disconnected`]
165    /// if the host connection fails.
166    pub fn acquire(self) -> Result<NetLease> {
167        let state = lease::new_shared_lease(LEASE_TAG_NET, self.lease_secs);
168        let (interface_name, actual_bandwidth) = host::net_hello(self.min_bandwidth)?;
169        if actual_bandwidth < self.min_bandwidth {
170            return Err(FabricError::CapacityExceeded);
171        }
172        Ok(NetLease {
173            state,
174            net: FabricNet {
175                interface_name,
176                bandwidth: actual_bandwidth,
177            },
178        })
179    }
180}
181
182impl Default for NetBuilder {
183    fn default() -> Self {
184        Self::new()
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191    use crate::error::FabricError;
192    use crate::host;
193
194    #[test]
195    fn net_acquire_succeeds_with_mock() {
196        host::reset_mock();
197        host::mock_set_net_interface("eth-fab0", 1_000_000_000);
198
199        let lease = NetBuilder::new()
200            .min_bandwidth(1_000_000_000)
201            .acquire()
202            .expect("acquire");
203        assert_eq!(lease.net().interface_name(), "eth-fab0");
204        assert_eq!(lease.net().bandwidth(), 1_000_000_000);
205    }
206
207    #[test]
208    fn net_acquire_rejects_insufficient_bandwidth() {
209        host::reset_mock();
210        host::mock_set_net_interface("eth-fab0", 100_000_000);
211
212        let result = NetBuilder::new().min_bandwidth(1_000_000_000).acquire();
213        assert_eq!(result.unwrap_err(), FabricError::CapacityExceeded);
214    }
215
216    #[test]
217    fn net_acquire_no_constraint() {
218        host::reset_mock();
219        host::mock_set_net_interface("eth-fab1", 10_000_000_000);
220
221        let lease = NetBuilder::new().acquire().expect("acquire");
222        assert_eq!(lease.net().interface_name(), "eth-fab1");
223        assert_eq!(lease.net().bandwidth(), 10_000_000_000);
224    }
225
226    #[test]
227    fn net_hello_error_propagates() {
228        host::reset_mock();
229        host::mock_set_net_hello_error(Some(-1));
230
231        let result = NetBuilder::new().acquire();
232        assert_eq!(result.unwrap_err(), FabricError::Disconnected);
233
234        host::mock_set_net_hello_error(None);
235    }
236
237    #[test]
238    fn net_builder_default() {
239        let builder = NetBuilder::default();
240        assert_eq!(builder.min_bandwidth, 0);
241    }
242
243    #[test]
244    fn net_bandwidth_reported_correctly() {
245        host::reset_mock();
246        host::mock_set_net_interface("macvlan0", 25_000_000_000);
247
248        let lease = NetBuilder::new()
249            .min_bandwidth(10_000_000_000)
250            .acquire()
251            .expect("acquire");
252        assert_eq!(lease.net().bandwidth(), 25_000_000_000);
253    }
254
255    #[test]
256    fn net_lease_status_and_renew() {
257        host::reset_mock();
258        host::mock_set_net_interface("eth-fab0", 1_000_000_000);
259        host::mock_set_unix_time_secs(3_000);
260
261        let lease = NetBuilder::new().lease_secs(10).acquire().expect("acquire");
262        assert_eq!(lease.status(), LeaseStatus::Active);
263        assert_eq!(lease.expires_at_unix_secs(), 3_010);
264        lease.renew(30).expect("renew");
265        assert_eq!(lease.expires_at_unix_secs(), 3_030);
266    }
267}