grafos_net/socket.rs
1//! Fabric-aware socket wrapping a leased network interface.
2
3use std::io;
4use std::net::{SocketAddr, UdpSocket};
5
6use grafos_std::net::{NetBuilder, NetLease};
7
8/// A network socket bound to a leased fabric network interface.
9///
10/// Wraps a UDP socket with the bandwidth guarantee from the underlying
11/// [`NetLease`]. On native targets, delegates to `std::net::UdpSocket`.
12///
13/// # Example
14///
15/// ```rust
16/// use grafos_net::FabricSocket;
17/// use grafos_std::net::NetBuilder;
18///
19/// # grafos_std::host::reset_mock();
20/// # grafos_std::host::mock_set_net_interface("eth0", 1_000_000_000);
21/// let lease = NetBuilder::new().min_bandwidth(1_000_000_000).acquire()?;
22/// let sock = FabricSocket::new(lease)?;
23/// assert_eq!(sock.bandwidth(), 1_000_000_000);
24/// assert_eq!(sock.interface_name(), "eth0");
25/// # Ok::<(), Box<dyn std::error::Error>>(())
26/// ```
27pub struct FabricSocket {
28 inner: UdpSocket,
29 bandwidth: u64,
30 lease: NetLease,
31}
32
33impl FabricSocket {
34 /// Create a new socket bound to the leased interface.
35 ///
36 /// Binds to an ephemeral port on `0.0.0.0`. The socket is associated
37 /// with the given [`NetLease`] for lifetime tracking.
38 ///
39 /// # Errors
40 ///
41 /// Returns an I/O error if the bind fails.
42 pub fn new(lease: NetLease) -> io::Result<Self> {
43 let bandwidth = lease.net().bandwidth();
44 let inner = UdpSocket::bind("0.0.0.0:0")?;
45 Ok(FabricSocket {
46 inner,
47 bandwidth,
48 lease,
49 })
50 }
51
52 /// Create a connected socket using a default (zero-bandwidth) lease.
53 ///
54 /// Acquires a network lease with no bandwidth constraint and connects
55 /// to the given address.
56 ///
57 /// # Errors
58 ///
59 /// Returns an I/O error if lease acquisition or the connect fails.
60 ///
61 /// # Example
62 ///
63 /// ```rust
64 /// use grafos_net::FabricSocket;
65 /// use std::net::SocketAddr;
66 ///
67 /// # grafos_std::host::reset_mock();
68 /// # grafos_std::host::mock_set_net_interface("lo", 1_000_000_000);
69 /// let addr: SocketAddr = "127.0.0.1:9999".parse().unwrap();
70 /// let sock = FabricSocket::connect(addr)?;
71 /// assert!(sock.bandwidth() > 0);
72 /// # Ok::<(), Box<dyn std::error::Error>>(())
73 /// ```
74 pub fn connect(addr: SocketAddr) -> io::Result<Self> {
75 let lease = NetBuilder::new().acquire().map_err(io::Error::other)?;
76 let bandwidth = lease.net().bandwidth();
77 let inner = UdpSocket::bind("0.0.0.0:0")?;
78 inner.connect(addr)?;
79 Ok(FabricSocket {
80 inner,
81 bandwidth,
82 lease,
83 })
84 }
85
86 /// Create a connected socket with a minimum bandwidth guarantee.
87 ///
88 /// Acquires a network lease with at least `min_bw` bits per second,
89 /// then connects to the given address.
90 ///
91 /// # Errors
92 ///
93 /// Returns an I/O error if the bandwidth requirement cannot be met
94 /// or if the connect fails.
95 ///
96 /// # Example
97 ///
98 /// ```rust
99 /// use grafos_net::FabricSocket;
100 /// use std::net::SocketAddr;
101 ///
102 /// # grafos_std::host::reset_mock();
103 /// # grafos_std::host::mock_set_net_interface("eth0", 10_000_000_000);
104 /// let addr: SocketAddr = "127.0.0.1:9999".parse().unwrap();
105 /// let sock = FabricSocket::connect_with_bandwidth(addr, 1_000_000_000)?;
106 /// assert!(sock.bandwidth() >= 1_000_000_000);
107 /// # Ok::<(), Box<dyn std::error::Error>>(())
108 /// ```
109 pub fn connect_with_bandwidth(addr: SocketAddr, min_bw: u64) -> io::Result<Self> {
110 let lease = NetBuilder::new()
111 .min_bandwidth(min_bw)
112 .acquire()
113 .map_err(io::Error::other)?;
114 let bandwidth = lease.net().bandwidth();
115 let inner = UdpSocket::bind("0.0.0.0:0")?;
116 inner.connect(addr)?;
117 Ok(FabricSocket {
118 inner,
119 bandwidth,
120 lease,
121 })
122 }
123
124 /// Connect this socket to a remote address.
125 ///
126 /// After connecting, [`send`](FabricSocket::send) and
127 /// [`recv`](FabricSocket::recv) operate on the connected peer.
128 ///
129 /// # Errors
130 ///
131 /// Returns an I/O error if the connect fails.
132 pub fn connect_to(&self, addr: SocketAddr) -> io::Result<()> {
133 self.inner.connect(addr)
134 }
135
136 /// Send data through the socket.
137 ///
138 /// The socket must be connected first via [`connect`](FabricSocket::connect).
139 ///
140 /// # Errors
141 ///
142 /// Returns an I/O error if the send fails.
143 pub fn send(&self, data: &[u8]) -> io::Result<usize> {
144 self.inner.send(data)
145 }
146
147 /// Receive data from the socket.
148 ///
149 /// The socket must be connected first via [`connect`](FabricSocket::connect).
150 ///
151 /// # Errors
152 ///
153 /// Returns an I/O error if the recv fails.
154 pub fn recv(&self, buf: &mut [u8]) -> io::Result<usize> {
155 self.inner.recv(buf)
156 }
157
158 /// Returns the local address this socket is bound to.
159 ///
160 /// # Errors
161 ///
162 /// Returns an I/O error if the address cannot be retrieved.
163 pub fn local_addr(&self) -> io::Result<SocketAddr> {
164 self.inner.local_addr()
165 }
166
167 /// Returns the guaranteed bandwidth in bits per second.
168 pub fn bandwidth(&self) -> u64 {
169 self.bandwidth
170 }
171
172 /// Returns the leased interface name.
173 pub fn interface_name(&self) -> &str {
174 self.lease.net().interface_name()
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use grafos_std::host;
182 use grafos_std::net::NetBuilder;
183
184 #[test]
185 fn socket_creates_and_reports_bandwidth() {
186 host::reset_mock();
187 host::mock_set_net_interface("eth-fab0", 1_000_000_000);
188
189 let lease = NetBuilder::new()
190 .min_bandwidth(1_000_000_000)
191 .acquire()
192 .expect("acquire");
193 let sock = FabricSocket::new(lease).expect("socket");
194 assert_eq!(sock.bandwidth(), 1_000_000_000);
195 assert_eq!(sock.interface_name(), "eth-fab0");
196 assert!(sock.local_addr().is_ok());
197 }
198
199 #[test]
200 fn socket_send_recv_loopback() {
201 host::reset_mock();
202 host::mock_set_net_interface("lo", 10_000_000_000);
203
204 let lease_a = NetBuilder::new().acquire().expect("acquire a");
205 let sock_a = FabricSocket::new(lease_a).expect("socket a");
206 let addr_a = sock_a.local_addr().expect("local addr a");
207
208 let lease_b = NetBuilder::new().acquire().expect("acquire b");
209 let sock_b = FabricSocket::new(lease_b).expect("socket b");
210
211 sock_b.connect_to(addr_a).expect("connect");
212 sock_b.send(b"hello fabric").expect("send");
213
214 let mut buf = [0u8; 64];
215 let n = sock_a.inner.recv_from(&mut buf).expect("recv_from").0;
216 assert_eq!(&buf[..n], b"hello fabric");
217 }
218
219 #[test]
220 fn connect_acquires_lease_and_connects() {
221 host::reset_mock();
222 host::mock_set_net_interface("lo", 1_000_000_000);
223
224 // Bind a target socket so connect() has somewhere to point.
225 let target = UdpSocket::bind("127.0.0.1:0").expect("bind target");
226 let target_addr = target.local_addr().expect("target addr");
227
228 let sock = FabricSocket::connect(target_addr).expect("connect");
229 assert_eq!(sock.bandwidth(), 1_000_000_000);
230 assert_eq!(sock.interface_name(), "lo");
231 }
232
233 #[test]
234 fn connect_with_bandwidth_enforces_minimum() {
235 host::reset_mock();
236 host::mock_set_net_interface("eth0", 10_000_000_000);
237
238 let target = UdpSocket::bind("127.0.0.1:0").expect("bind target");
239 let target_addr = target.local_addr().expect("target addr");
240
241 let sock =
242 FabricSocket::connect_with_bandwidth(target_addr, 1_000_000_000).expect("connect");
243 assert!(sock.bandwidth() >= 1_000_000_000);
244 }
245
246 #[test]
247 fn connect_with_bandwidth_rejects_insufficient() {
248 host::reset_mock();
249 host::mock_set_net_interface("eth0", 100_000_000);
250
251 let target = UdpSocket::bind("127.0.0.1:0").expect("bind target");
252 let target_addr = target.local_addr().expect("target addr");
253
254 let result = FabricSocket::connect_with_bandwidth(target_addr, 1_000_000_000);
255 assert!(result.is_err());
256 }
257}