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}