ove/
net.rs

1// Copyright (C) 2026 Kamil Lulko <kamil.lulko@gmail.com>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4//
5// This file is part of oveRTOS.
6
7//! Networking primitives for oveRTOS.
8//!
9//! Provides safe wrappers for BSD-like sockets (TCP and UDP), network interface
10//! management, DNS resolution, and socket addresses.  All types use inline
11//! storage and implement `Send + Sync`.
12
13use core::fmt;
14
15use crate::bindings;
16use crate::error::{Error, Result};
17
18// ---------------------------------------------------------------------------
19// Address
20// ---------------------------------------------------------------------------
21
22/// Generic socket address (IPv4 or IPv6).
23///
24/// Wraps [`ove_sockaddr_t`](bindings::ove_sockaddr_t) transparently so it can
25/// be passed directly to the C API without conversion.
26#[derive(Clone, Copy)]
27#[repr(transparent)]
28pub struct Address(bindings::ove_sockaddr_t);
29
30impl Address {
31    /// Create an IPv4 address from four octets and a port number.
32    pub fn ipv4(a: u8, b: u8, c: u8, d: u8, port: u16) -> Self {
33        let mut inner: bindings::ove_sockaddr_t = unsafe { core::mem::zeroed() };
34        unsafe { bindings::ove_sockaddr_ipv4(&mut inner, a, b, c, d, port) };
35        Self(inner)
36    }
37
38    /// Get the port number in host byte order.
39    pub fn port(&self) -> u16 {
40        self.0.port
41    }
42
43    /// Set the port number in host byte order.
44    pub fn set_port(&mut self, port: u16) {
45        self.0.port = port;
46    }
47
48    /// Get the first four bytes of the address (the IPv4 octets).
49    pub fn octets(&self) -> [u8; 4] {
50        [self.0.addr[0], self.0.addr[1], self.0.addr[2], self.0.addr[3]]
51    }
52
53    /// Get a const pointer to the inner sockaddr for passing to C APIs.
54    pub(crate) fn as_ptr(&self) -> *const bindings::ove_sockaddr_t {
55        &self.0
56    }
57
58    /// Get a mutable pointer to the inner sockaddr for receiving from C APIs.
59    pub(crate) fn as_mut_ptr(&mut self) -> *mut bindings::ove_sockaddr_t {
60        &mut self.0
61    }
62
63    /// Borrow the inner C struct.
64    fn as_inner(&self) -> &bindings::ove_sockaddr_t {
65        &self.0
66    }
67}
68
69impl Default for Address {
70    fn default() -> Self {
71        Self(unsafe { core::mem::zeroed() })
72    }
73}
74
75impl fmt::Debug for Address {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        let o = self.octets();
78        f.debug_struct("Address")
79            .field("ip", &format_args!("{}.{}.{}.{}", o[0], o[1], o[2], o[3]))
80            .field("port", &self.0.port)
81            .finish()
82    }
83}
84
85// ---------------------------------------------------------------------------
86// NetIfConfig
87// ---------------------------------------------------------------------------
88
89/// Builder for network interface configuration.
90///
91/// Wraps [`ove_netif_config_t`](bindings::ove_netif_config_t) and provides a
92/// fluent API for configuring DHCP, static IP, and DNS.
93pub struct NetIfConfig(bindings::ove_netif_config_t);
94
95impl NetIfConfig {
96    /// Create a zeroed configuration (no DHCP, no static IP, no DNS).
97    pub fn new() -> Self {
98        Self(unsafe { core::mem::zeroed() })
99    }
100
101    /// Configure a static IP, subnet mask, and gateway.
102    ///
103    /// Disables DHCP.
104    pub fn static_ip(mut self, ip: Address, mask: Address, gw: Address) -> Self {
105        self.0.use_dhcp = 0;
106        self.0.static_ip = *ip.as_inner();
107        self.0.netmask = *mask.as_inner();
108        self.0.gateway = *gw.as_inner();
109        self
110    }
111
112    /// Enable DHCP for automatic address assignment.
113    pub fn dhcp(mut self) -> Self {
114        self.0.use_dhcp = 1;
115        self
116    }
117
118    /// Set the DNS server address.
119    pub fn dns(mut self, dns: Address) -> Self {
120        self.0.dns = *dns.as_inner();
121        self
122    }
123}
124
125impl Default for NetIfConfig {
126    fn default() -> Self {
127        Self::new()
128    }
129}
130
131// ---------------------------------------------------------------------------
132// NetIf
133// ---------------------------------------------------------------------------
134
135/// RAII wrapper around a network interface.
136///
137/// Uses inline storage so no heap/zero-heap split is needed.
138/// The handle is derived from `&self.storage` at each use to avoid
139/// dangling self-referential pointers after Rust moves the struct.
140pub struct NetIf {
141    storage: bindings::ove_netif_storage_t,
142    init: bool,
143}
144
145impl NetIf {
146    /// Derive the C handle from inline storage.
147    fn handle(&self) -> bindings::ove_netif_t {
148        &self.storage as *const _ as bindings::ove_netif_t
149    }
150
151    /// Initialise a network interface using inline storage.
152    ///
153    /// # Errors
154    /// Returns an error if the underlying RTOS rejects the initialisation.
155    pub fn new() -> Result<Self> {
156        let mut netif = Self {
157            storage: unsafe { core::mem::zeroed() },
158            init: false,
159        };
160        let mut handle: bindings::ove_netif_t = core::ptr::null_mut();
161        let rc = unsafe {
162            bindings::ove_netif_init(&mut handle, &mut netif.storage)
163        };
164        Error::from_code(rc)?;
165        netif.init = true;
166        Ok(netif)
167    }
168
169    /// Bring the interface up with the given configuration.
170    ///
171    /// # Errors
172    /// Returns an error if the RTOS cannot bring the interface up (e.g.
173    /// invalid configuration or hardware failure).
174    pub fn up(&self, cfg: &NetIfConfig) -> Result<()> {
175        let rc = unsafe { bindings::ove_netif_up(self.handle(), &cfg.0) };
176        Error::from_code(rc)
177    }
178
179    /// Tear down the network interface.
180    pub fn down(&self) {
181        unsafe { bindings::ove_netif_down(self.handle()) }
182    }
183
184    /// Query current addresses of the network interface.
185    ///
186    /// Returns `(ip, gateway, netmask)` on success.
187    ///
188    /// # Errors
189    /// Returns an error if the RTOS cannot retrieve the addresses.
190    pub fn get_addr(&self) -> Result<(Address, Address, Address)> {
191        let mut ip = core::mem::MaybeUninit::<bindings::ove_sockaddr_t>::zeroed();
192        let mut gw = core::mem::MaybeUninit::<bindings::ove_sockaddr_t>::zeroed();
193        let mut nm = core::mem::MaybeUninit::<bindings::ove_sockaddr_t>::zeroed();
194        let rc = unsafe {
195            bindings::ove_netif_get_addr(
196                self.handle(),
197                ip.as_mut_ptr(),
198                gw.as_mut_ptr(),
199                nm.as_mut_ptr(),
200            )
201        };
202        Error::from_code(rc)?;
203        unsafe {
204            Ok((
205                Address(ip.assume_init()),
206                Address(gw.assume_init()),
207                Address(nm.assume_init()),
208            ))
209        }
210    }
211}
212
213impl fmt::Debug for NetIf {
214    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215        f.debug_struct("NetIf")
216            .field("handle", &format_args!("{:p}", self.handle()))
217            .finish()
218    }
219}
220
221impl Drop for NetIf {
222    fn drop(&mut self) {
223        if !self.init {
224            return;
225        }
226        unsafe { bindings::ove_netif_deinit(self.handle()) }
227    }
228}
229
230// SAFETY: Wraps a ove handle backed by inline storage. The RTOS net calls are
231// thread-safe. Create/destroy are single-threaded (lifecycle guarantee).
232unsafe impl Send for NetIf {}
233unsafe impl Sync for NetIf {}
234
235// ---------------------------------------------------------------------------
236// TcpStream
237// ---------------------------------------------------------------------------
238
239/// RAII wrapper around a TCP (stream) socket.
240///
241/// Uses inline storage for the socket backend, so no heap/zero-heap split is
242/// needed.  The handle is derived from `&self.storage` at each call to
243/// avoid dangling self-referential pointers after Rust moves the struct.
244pub struct TcpStream {
245    storage: bindings::ove_socket_storage_t,
246    open: bool,
247}
248
249impl TcpStream {
250    /// Derive the C handle from inline storage.
251    fn handle(&self) -> bindings::ove_socket_t {
252        &self.storage as *const _ as bindings::ove_socket_t
253    }
254
255    /// Open a new TCP socket.
256    ///
257    /// # Errors
258    /// Returns an error if the RTOS cannot open the socket.
259    pub fn new() -> Result<Self> {
260        let mut sock = Self {
261            storage: unsafe { core::mem::zeroed() },
262            open: false,
263        };
264        let mut handle: bindings::ove_socket_t = core::ptr::null_mut();
265        let rc = unsafe {
266            bindings::ove_socket_open(
267                &mut handle,
268                &mut sock.storage,
269                bindings::OVE_AF_INET,
270                bindings::OVE_SOCK_STREAM,
271            )
272        };
273        Error::from_code(rc)?;
274        sock.open = true;
275        Ok(sock)
276    }
277
278    /// Connect to a remote address with a timeout.
279    ///
280    /// # Errors
281    /// Returns an error if the connection fails or times out.
282    pub fn connect(&self, addr: &Address, timeout_ms: u32) -> Result<()> {
283        let rc = unsafe {
284            bindings::ove_socket_connect(self.handle(), addr.as_ptr(), timeout_ms)
285        };
286        Error::from_code(rc)
287    }
288
289    /// Send data on the connected socket.
290    ///
291    /// Returns the number of bytes actually sent.
292    ///
293    /// # Errors
294    /// Returns an error if the send fails.
295    pub fn send(&self, data: &[u8]) -> Result<usize> {
296        let mut sent: usize = 0;
297        let rc = unsafe {
298            bindings::ove_socket_send(
299                self.handle(),
300                data.as_ptr() as *const _,
301                data.len(),
302                &mut sent,
303            )
304        };
305        Error::from_code(rc)?;
306        Ok(sent)
307    }
308
309    /// Receive data from the connected socket.
310    ///
311    /// Returns the number of bytes received.
312    ///
313    /// # Errors
314    /// Returns an error if the receive fails or times out.
315    pub fn recv(&self, buf: &mut [u8], timeout_ms: u32) -> Result<usize> {
316        let mut received: usize = 0;
317        let rc = unsafe {
318            bindings::ove_socket_recv(
319                self.handle(),
320                buf.as_mut_ptr() as *mut _,
321                buf.len(),
322                &mut received,
323                timeout_ms,
324            )
325        };
326        Error::from_code(rc)?;
327        Ok(received)
328    }
329}
330
331impl fmt::Debug for TcpStream {
332    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333        f.debug_struct("TcpStream")
334            .field("handle", &format_args!("{:p}", self.handle()))
335            .field("open", &self.open)
336            .finish()
337    }
338}
339
340impl Drop for TcpStream {
341    fn drop(&mut self) {
342        if self.open {
343            unsafe { bindings::ove_socket_close(self.handle()) }
344        }
345    }
346}
347
348// SAFETY: Wraps a ove handle backed by inline storage. The RTOS socket calls
349// are thread-safe. Open/close are single-threaded (lifecycle guarantee).
350unsafe impl Send for TcpStream {}
351unsafe impl Sync for TcpStream {}
352
353// ---------------------------------------------------------------------------
354// UdpSocket
355// ---------------------------------------------------------------------------
356
357/// RAII wrapper around a UDP (datagram) socket.
358///
359/// Uses inline storage for the socket backend, so no heap/zero-heap split is
360/// needed.  The handle is derived from `&self.storage` at each call to
361/// avoid dangling self-referential pointers after Rust moves the struct.
362pub struct UdpSocket {
363    storage: bindings::ove_socket_storage_t,
364    open: bool,
365}
366
367impl UdpSocket {
368    /// Derive the C handle from inline storage.
369    fn handle(&self) -> bindings::ove_socket_t {
370        &self.storage as *const _ as bindings::ove_socket_t
371    }
372
373    /// Open a new UDP socket.
374    ///
375    /// # Errors
376    /// Returns an error if the RTOS cannot open the socket.
377    pub fn new() -> Result<Self> {
378        let mut sock = Self {
379            storage: unsafe { core::mem::zeroed() },
380            open: false,
381        };
382        let mut handle: bindings::ove_socket_t = core::ptr::null_mut();
383        let rc = unsafe {
384            bindings::ove_socket_open(
385                &mut handle,
386                &mut sock.storage,
387                bindings::OVE_AF_INET,
388                bindings::OVE_SOCK_DGRAM,
389            )
390        };
391        Error::from_code(rc)?;
392        sock.open = true;
393        Ok(sock)
394    }
395
396    /// Bind the socket to a local address.
397    ///
398    /// # Errors
399    /// Returns an error if the bind fails (e.g. address already in use).
400    pub fn bind(&self, addr: &Address) -> Result<()> {
401        let rc = unsafe { bindings::ove_socket_bind(self.handle(), addr.as_ptr()) };
402        Error::from_code(rc)
403    }
404
405    /// Send a datagram to a specific destination.
406    ///
407    /// Returns the number of bytes actually sent.
408    ///
409    /// # Errors
410    /// Returns an error if the send fails.
411    pub fn send_to(&self, data: &[u8], dest: &Address) -> Result<usize> {
412        let mut sent: usize = 0;
413        let rc = unsafe {
414            bindings::ove_socket_sendto(
415                self.handle(),
416                data.as_ptr() as *const _,
417                data.len(),
418                &mut sent,
419                dest.as_ptr(),
420            )
421        };
422        Error::from_code(rc)?;
423        Ok(sent)
424    }
425
426    /// Receive a datagram and the sender's address.
427    ///
428    /// Returns the number of bytes received and the source address.
429    ///
430    /// # Errors
431    /// Returns an error if the receive fails or times out.
432    pub fn recv_from(
433        &self,
434        buf: &mut [u8],
435        timeout_ms: u32,
436    ) -> Result<(usize, Address)> {
437        let mut received: usize = 0;
438        let mut src = Address::default();
439        let rc = unsafe {
440            bindings::ove_socket_recvfrom(
441                self.handle(),
442                buf.as_mut_ptr() as *mut _,
443                buf.len(),
444                &mut received,
445                src.as_mut_ptr(),
446                timeout_ms,
447            )
448        };
449        Error::from_code(rc)?;
450        Ok((received, src))
451    }
452}
453
454impl fmt::Debug for UdpSocket {
455    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
456        f.debug_struct("UdpSocket")
457            .field("handle", &format_args!("{:p}", self.handle()))
458            .field("open", &self.open)
459            .finish()
460    }
461}
462
463impl Drop for UdpSocket {
464    fn drop(&mut self) {
465        if self.open {
466            unsafe { bindings::ove_socket_close(self.handle()) }
467        }
468    }
469}
470
471// SAFETY: Wraps a ove handle backed by inline storage. The RTOS socket calls
472// are thread-safe. Open/close are single-threaded (lifecycle guarantee).
473unsafe impl Send for UdpSocket {}
474unsafe impl Sync for UdpSocket {}
475
476// ---------------------------------------------------------------------------
477// DNS
478// ---------------------------------------------------------------------------
479
480/// Resolve a hostname to an address.
481///
482/// The `hostname` byte slice must be null-terminated (e.g. `b"example.com\0"`).
483///
484/// # Errors
485/// Returns an error if name resolution fails or times out.
486pub fn dns_resolve(hostname: &[u8], timeout_ms: u32) -> Result<Address> {
487    let mut addr = Address::default();
488    let rc = unsafe {
489        bindings::ove_dns_resolve(
490            hostname.as_ptr() as *const _,
491            addr.as_mut_ptr(),
492            timeout_ms,
493        )
494    };
495    Error::from_code(rc)?;
496    Ok(addr)
497}