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}