1extern 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#[derive(Debug)]
39pub struct FabricNet {
40 interface_name: String,
41 bandwidth: u64,
42}
43
44impl FabricNet {
45 pub fn bandwidth(&self) -> u64 {
47 self.bandwidth
48 }
49
50 pub fn interface_name(&self) -> &str {
52 &self.interface_name
53 }
54}
55
56#[derive(Debug)]
61pub struct NetLease {
62 state: SharedLeaseState,
63 net: FabricNet,
64}
65
66impl NetLease {
67 pub fn net(&self) -> &FabricNet {
69 &self.net
70 }
71
72 pub fn info(&self) -> LeaseInfo {
74 lease::info(&self.state)
75 }
76
77 pub fn lease_id(&self) -> u128 {
79 lease::lease_id(&self.state)
80 }
81
82 pub fn created_at_unix_secs(&self) -> u64 {
84 lease::created_at_unix_secs(&self.state)
85 }
86
87 pub fn expires_at_unix_secs(&self) -> u64 {
89 lease::expires_at_unix_secs(&self.state)
90 }
91
92 pub fn status(&self) -> LeaseStatus {
94 lease::status(&self.state)
95 }
96
97 pub fn renew(&self, duration_secs: u64) -> Result<()> {
99 lease::renew(&self.state, duration_secs)
100 }
101
102 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
114pub struct NetBuilder {
130 min_bandwidth: u64,
131 lease_secs: u64,
132}
133
134impl NetBuilder {
135 pub fn new() -> Self {
137 NetBuilder {
138 min_bandwidth: 0,
139 lease_secs: 300,
140 }
141 }
142
143 pub fn min_bandwidth(mut self, bps: u64) -> Self {
145 self.min_bandwidth = bps;
146 self
147 }
148
149 pub fn lease_secs(mut self, secs: u64) -> Self {
151 self.lease_secs = secs.max(1);
152 self
153 }
154
155 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}