Skip to main content

leodos_libcfs/os/
net.rs

1//! Safe, idiomatic wrappers for OSAL networking APIs (sockets).
2
3use crate::error::{CfsError, OsalError, Result};
4use crate::ffi;
5use crate::os::id::OsalId;
6use crate::cstring;
7use crate::string_from_c_buf;
8use crate::status::check;
9use core::fmt::Write;
10use core::future::Future;
11use core::mem::MaybeUninit;
12use core::task::Poll;
13use heapless::String;
14
15#[cfg(not(nos3_cfe))]
16use crate::os::time::OsTime;
17
18/// A wrapper for a CFE/OSAL socket address.
19#[derive(Clone)]
20#[repr(transparent)]
21pub struct SocketAddr(ffi::OS_SockAddr_t);
22
23/// Defines how to shut down a TCP stream.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25#[repr(u32)]
26pub enum SocketShutdownMode {
27    /// Disable future reading.
28    Read = ffi::OS_SocketShutdownMode_t_OS_SocketShutdownMode_SHUT_READ,
29    /// Disable future writing.
30    Write = ffi::OS_SocketShutdownMode_t_OS_SocketShutdownMode_SHUT_WRITE,
31    /// Disable future reading and writing.
32    ReadWrite = ffi::OS_SocketShutdownMode_t_OS_SocketShutdownMode_SHUT_READWRITE,
33}
34
35impl SocketAddr {
36    /// Creates a new socket address.
37    pub fn new_ipv4(ip_addr: &str, port: u16) -> Result<Self> {
38        let mut addr_uninit = MaybeUninit::uninit();
39
40        // 1. Initialize for the correct domain
41        check(unsafe {
42            ffi::OS_SocketAddrInit(addr_uninit.as_mut_ptr(), SocketDomain::IPv4.into())
43        })?;
44
45        // 2. Set the IP address from a string
46        let c_ip = cstring::<{ ffi::OS_MAX_PATH_LEN as usize }>(ip_addr)?;
47        check(unsafe {
48            ffi::OS_SocketAddrFromString(addr_uninit.as_mut_ptr(), c_ip.as_ptr())
49        })?;
50
51        // 3. Set the port
52        check(unsafe { ffi::OS_SocketAddrSetPort(addr_uninit.as_mut_ptr(), port) })?;
53
54        Ok(Self(unsafe { addr_uninit.assume_init() }))
55    }
56
57    /// Gets the port number from a socket address.
58    pub fn port(&self) -> Result<u16> {
59        let mut port = MaybeUninit::uninit();
60        check(unsafe { ffi::OS_SocketAddrGetPort(port.as_mut_ptr(), &self.0) })?;
61        Ok(unsafe { port.assume_init() })
62    }
63
64    /// Gets a string representation of the host address (e.g., "127.0.0.1").
65    pub fn to_string(&self) -> Result<String<{ ffi::OS_MAX_PATH_LEN as usize }>> {
66        let mut buffer = [0u8; { ffi::OS_MAX_PATH_LEN as usize }];
67        check(unsafe {
68            ffi::OS_SocketAddrToString(
69                buffer.as_mut_ptr() as *mut libc::c_char,
70                buffer.len(),
71                &self.0,
72            )
73        })?;
74        let len = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len());
75        let vec = heapless::Vec::from_slice(&buffer[..len]).map_err(|_| CfsError::Osal(OsalError::NameTooLong))?;
76        let s = String::from_utf8(vec).map_err(|_| CfsError::InvalidString)?;
77        Ok(s)
78    }
79}
80
81impl TryFrom<core::net::SocketAddr> for SocketAddr {
82    type Error = CfsError;
83
84    fn try_from(addr: core::net::SocketAddr) -> Result<Self> {
85        let mut addr_uninit = MaybeUninit::uninit();
86
87        let domain = match addr {
88            core::net::SocketAddr::V4(_) => SocketDomain::IPv4,
89            core::net::SocketAddr::V6(_) => SocketDomain::IPv6,
90        };
91
92        check(unsafe { ffi::OS_SocketAddrInit(addr_uninit.as_mut_ptr(), domain.into()) })?;
93
94        // Format IP to C-String for OSAL
95        let mut ip_buf: String<{ ffi::OS_MAX_PATH_LEN as usize }> = String::new();
96        write!(ip_buf, "{}", addr.ip()).map_err(|_| CfsError::Osal(OsalError::NameTooLong))?;
97        let c_ip = cstring::<{ ffi::OS_MAX_PATH_LEN as usize }>(&ip_buf)?;
98
99        check(unsafe {
100            ffi::OS_SocketAddrFromString(addr_uninit.as_mut_ptr(), c_ip.as_ptr())
101        })?;
102
103        check(unsafe { ffi::OS_SocketAddrSetPort(addr_uninit.as_mut_ptr(), addr.port()) })?;
104
105        Ok(Self(unsafe { addr_uninit.assume_init() }))
106    }
107}
108
109/// A raw socket handle that ensures `OS_close` is called on drop.
110#[derive(Debug)]
111#[repr(transparent)]
112pub struct Socket(ffi::osal_id_t);
113
114impl Drop for Socket {
115    fn drop(&mut self) {
116        if self.0 != 0 {
117            let _ = unsafe { ffi::OS_close(self.0) };
118        }
119    }
120}
121
122impl Socket {
123    /// Binds a socket to a given local address.
124    pub fn bind_address(&self, addr: &SocketAddr) -> Result<()> {
125        check(unsafe { ffi::OS_SocketBindAddress(self.0, &addr.0) })?;
126        Ok(())
127    }
128}
129
130/// A UDP socket.
131#[derive(Debug)]
132#[repr(transparent)]
133pub struct UdpSocket(Socket);
134
135/// Timeout options for socket operations.
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
137pub enum Timeout {
138    /// Wait indefinitely.
139    Pend,
140    /// Do not wait at all.
141    Poll,
142    /// Wait for the specified number of milliseconds.
143    Milliseconds(i32),
144}
145
146impl From<Timeout> for i32 {
147    fn from(timeout: Timeout) -> Self {
148        match timeout {
149            Timeout::Pend => ffi::OS_PEND,
150            Timeout::Poll => ffi::OS_CHECK as i32,
151            Timeout::Milliseconds(ms) => ms,
152        }
153    }
154}
155
156impl UdpSocket {
157    /// Returns the OSAL id for this socket, suitable for passing
158    /// to `OS_SelectMultiple` via the runtime reactor.
159    pub fn id(&self) -> OsalId {
160        OsalId((self.0).0)
161    }
162
163    /// Creates a new UDP socket bound to the specified address.
164    pub fn bind(addr: SocketAddr) -> Result<UdpSocket> {
165        let mut sock_id = MaybeUninit::uninit();
166        check(unsafe {
167            ffi::OS_SocketOpen(
168                sock_id.as_mut_ptr(),
169                SocketDomain::IPv4.into(),
170                ffi::OS_SocketType_t_OS_SocketType_DATAGRAM,
171            )
172        })?;
173        let sock_id = unsafe { sock_id.assume_init() };
174
175        check(unsafe { ffi::OS_SocketBind(sock_id, &addr.0) })?;
176
177        Ok(UdpSocket(Socket(sock_id)))
178    }
179
180    /// Receives a single datagram message on the socket.
181    pub fn recv_from<'a>(
182        &self,
183        buf: &'a mut [u8],
184        timeout: Timeout,
185    ) -> Result<(usize, SocketAddr)> {
186        let mut remote_addr_uninit = MaybeUninit::<ffi::OS_SockAddr_t>::uninit();
187
188        let num_bytes = unsafe {
189            ffi::OS_SocketRecvFrom(
190                (self.0).0,
191                buf.as_mut_ptr() as *mut _,
192                buf.len(),
193                remote_addr_uninit.as_mut_ptr(),
194                timeout.into(),
195            )
196        };
197
198        if num_bytes < 0 {
199            Err(CfsError::from(num_bytes))
200        } else {
201            let remote_addr = unsafe { remote_addr_uninit.assume_init() };
202            Ok((num_bytes as usize, SocketAddr(remote_addr)))
203        }
204    }
205
206    /// Sends data on the socket to the given address.
207    pub fn send_to(&self, buf: &[u8], target: &SocketAddr) -> Result<usize> {
208        let num_bytes = unsafe {
209            ffi::OS_SocketSendTo((self.0).0, buf.as_ptr() as *const _, buf.len(), &target.0)
210        };
211        if num_bytes < 0 {
212            Err(CfsError::from(num_bytes))
213        } else {
214            Ok(num_bytes as usize)
215        }
216    }
217
218    /// Receives a datagram with an absolute timeout.
219    #[cfg(not(nos3_cfe))]
220    pub fn recv_from_abs<'a>(
221        &self,
222        buf: &'a mut [u8],
223        abstime: OsTime,
224    ) -> Result<(usize, SocketAddr)> {
225        let mut remote_addr_uninit = MaybeUninit::<ffi::OS_SockAddr_t>::uninit();
226        let num_bytes = unsafe {
227            ffi::OS_SocketRecvFromAbs(
228                (self.0).0,
229                buf.as_mut_ptr() as *mut _,
230                buf.len(),
231                remote_addr_uninit.as_mut_ptr(),
232                abstime.0,
233            )
234        };
235
236        if num_bytes < 0 {
237            Err(CfsError::from(num_bytes))
238        } else {
239            let remote_addr = SocketAddr(unsafe { remote_addr_uninit.assume_init() });
240            Ok((num_bytes as usize, remote_addr))
241        }
242    }
243}
244
245/// A TCP socket server, listening for connections.
246#[derive(Debug)]
247#[repr(transparent)]
248pub struct TcpListener(Socket);
249
250/// The domain of a socket.
251pub enum SocketDomain {
252    /// IPv4 (Inet)
253    IPv4,
254    /// IPv6 (Inet6)
255    IPv6,
256}
257
258impl Into<ffi::OS_SocketDomain_t> for SocketDomain {
259    fn into(self) -> ffi::OS_SocketDomain_t {
260        match self {
261            SocketDomain::IPv4 => ffi::OS_SocketDomain_t_OS_SocketDomain_INET,
262            SocketDomain::IPv6 => ffi::OS_SocketDomain_t_OS_SocketDomain_INET6,
263        }
264    }
265}
266
267impl TcpListener {
268    /// Creates a new `TcpListener` which will be bound to the specified address.
269    pub fn bind(addr: SocketAddr) -> Result<Self> {
270        let mut sock_id = MaybeUninit::uninit();
271        check(unsafe {
272            ffi::OS_SocketOpen(
273                sock_id.as_mut_ptr(),
274                SocketDomain::IPv4.into(),
275                ffi::OS_SocketType_t_OS_SocketType_STREAM,
276            )
277        })?;
278        let sock_id = unsafe { sock_id.assume_init() };
279
280        check(unsafe { ffi::OS_SocketBind(sock_id, &addr.0) })?;
281
282        Ok(TcpListener(Socket(sock_id)))
283    }
284
285    /// Accepts a new incoming connection from this listener.
286    /// This function will block until a new connection is established.
287    pub fn accept(&self, timeout: Timeout) -> Result<(TcpStream, SocketAddr)> {
288        let mut remote_addr_uninit = MaybeUninit::<ffi::OS_SockAddr_t>::uninit();
289        let mut conn_sock_id = MaybeUninit::uninit();
290        check(unsafe {
291            ffi::OS_SocketAccept(
292                (self.0).0,
293                conn_sock_id.as_mut_ptr(),
294                remote_addr_uninit.as_mut_ptr(),
295                timeout.into(),
296            )
297        })?;
298
299        let stream = TcpStream(Socket(unsafe { conn_sock_id.assume_init() }));
300        let remote_addr = SocketAddr(unsafe { remote_addr_uninit.assume_init() });
301        Ok((stream, remote_addr))
302    }
303
304    /// Places the socket into a listening state for incoming connections.
305    ///
306    /// This is typically called after `bind`.
307    pub fn listen(&self) -> Result<()> {
308        check(unsafe { ffi::OS_SocketListen(self.0 .0) })?;
309        Ok(())
310    }
311}
312
313/// A TCP stream between a local and a remote socket.
314#[derive(Debug)]
315#[repr(transparent)]
316pub struct TcpStream(Socket);
317
318impl TcpStream {
319    /// Opens a TCP connection to a remote host.
320    pub fn connect(addr: SocketAddr, domain: SocketDomain) -> Result<Self> {
321        let mut sock_id = MaybeUninit::uninit();
322        check(unsafe {
323            ffi::OS_SocketOpen(
324                sock_id.as_mut_ptr(),
325                domain.into(),
326                ffi::OS_SocketType_t_OS_SocketType_STREAM,
327            )
328        })?;
329        let sock_id = unsafe { sock_id.assume_init() };
330
331        check(unsafe { ffi::OS_SocketConnect(sock_id, &addr.0, ffi::OS_PEND as i32) })?;
332
333        Ok(TcpStream(Socket(sock_id)))
334    }
335
336    /// Reads some bytes from the stream into the specified buffer.
337    pub fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
338        let bytes_read = unsafe { ffi::OS_read(self.0 .0, buf.as_mut_ptr() as *mut _, buf.len()) };
339        if bytes_read < 0 {
340            Err(CfsError::from(bytes_read))
341        } else {
342            Ok(bytes_read as usize)
343        }
344    }
345
346    /// Writes a buffer to the stream.
347    pub fn write(&mut self, buf: &[u8]) -> Result<usize> {
348        let bytes_written =
349            unsafe { ffi::OS_write(self.0 .0, buf.as_ptr() as *const _, buf.len()) };
350        if bytes_written < 0 {
351            Err(CfsError::from(bytes_written))
352        } else {
353            Ok(bytes_written as usize)
354        }
355    }
356
357    /// Accepts a new connection with an absolute timeout.
358    #[cfg(not(nos3_cfe))]
359    pub fn accept_abs(&self, abstime: OsTime) -> Result<(TcpStream, SocketAddr)> {
360        let mut remote_addr_uninit = MaybeUninit::<ffi::OS_SockAddr_t>::uninit();
361        let mut conn_sock_id = MaybeUninit::uninit();
362        check(unsafe {
363            ffi::OS_SocketAcceptAbs(
364                self.0 .0,
365                conn_sock_id.as_mut_ptr(),
366                remote_addr_uninit.as_mut_ptr(),
367                abstime.0,
368            )
369        })?;
370
371        let stream = TcpStream(Socket(unsafe { conn_sock_id.assume_init() }));
372        let remote_addr = SocketAddr(unsafe { remote_addr_uninit.assume_init() });
373        Ok((stream, remote_addr))
374    }
375
376    /// Opens a TCP connection to a remote host with an absolute timeout.
377    #[cfg(not(nos3_cfe))]
378    pub fn connect_abs(addr: SocketAddr, domain: SocketDomain, abstime: OsTime) -> Result<Self> {
379        let mut sock_id = MaybeUninit::uninit();
380        check(unsafe {
381            ffi::OS_SocketOpen(
382                sock_id.as_mut_ptr(),
383                domain.into(),
384                ffi::OS_SocketType_t_OS_SocketType_STREAM,
385            )
386        })?;
387        let sock_id = unsafe { sock_id.assume_init() };
388
389        check(unsafe { ffi::OS_SocketConnectAbs(sock_id, &addr.0, abstime.0) })?;
390
391        Ok(TcpStream(Socket(sock_id)))
392    }
393
394    /// Gracefully shuts down the read, write, or both halves of the connection.
395    pub fn shutdown(&self, how: SocketShutdownMode) -> Result<()> {
396        check(unsafe { ffi::OS_SocketShutdown(self.0 .0, how as ffi::OS_SocketShutdownMode_t) })?;
397        Ok(())
398    }
399}
400
401/// Gets the OSAL-specific network ID of the local machine.
402pub fn get_network_id() -> i32 {
403    unsafe { ffi::OS_NetworkGetID() }
404}
405
406/// Gets the local machine's network host name.
407pub fn get_host_name() -> Result<String<{ ffi::OS_MAX_PATH_LEN as usize }>> {
408    let mut buffer = [0u8; { ffi::OS_MAX_PATH_LEN as usize }];
409    check(unsafe {
410        ffi::OS_NetworkGetHostName(buffer.as_mut_ptr() as *mut libc::c_char, buffer.len())
411    })?;
412    let len = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len());
413    let vec = heapless::Vec::from_slice(&buffer[..len]).map_err(|_| CfsError::Osal(OsalError::NameTooLong))?;
414    let s = String::from_utf8(vec).map_err(|_| CfsError::InvalidString)?;
415    Ok(s)
416}
417
418/// A set of file descriptors, for use with `select`.
419#[derive(Debug, Clone, Copy)]
420pub struct FdSet(ffi::OS_FdSet);
421
422impl FdSet {
423    /// Creates a new, empty file descriptor set.
424    pub fn new() -> Self {
425        let mut set = MaybeUninit::uninit();
426        // Should not fail.
427        unsafe { ffi::OS_SelectFdZero(set.as_mut_ptr()) };
428        Self(unsafe { set.assume_init() })
429    }
430
431    /// Clears all file descriptors from the set.
432    pub fn zero(&mut self) {
433        let _ = unsafe { ffi::OS_SelectFdZero(&mut self.0) };
434    }
435
436    /// Adds a file descriptor to the set.
437    pub fn add(&mut self, id: OsalId) {
438        let _ = unsafe { ffi::OS_SelectFdAdd(&mut self.0, id.0) };
439    }
440
441    /// Removes a file descriptor from the set.
442    pub fn clear(&mut self, id: OsalId) {
443        let _ = unsafe { ffi::OS_SelectFdClear(&mut self.0, id.0) };
444    }
445
446    /// Checks if a file descriptor is a member of the set.
447    pub fn is_set(&self, id: OsalId) -> bool {
448        unsafe { ffi::OS_SelectFdIsSet(&self.0, id.0) }
449    }
450}
451
452impl Default for FdSet {
453    fn default() -> Self {
454        Self::new()
455    }
456}
457
458/// Waits for events on a single file handle with a relative timeout.
459pub fn select_single(id: OsalId, state: &mut u32, timeout_ms: i32) -> Result<()> {
460    check(unsafe { ffi::OS_SelectSingle(id.0, state, timeout_ms) })?;
461    Ok(())
462}
463
464/// Waits for events on a single file handle with an absolute timeout.
465    #[cfg(not(nos3_cfe))]
466pub fn select_single_abs(id: OsalId, state: &mut u32, abstime: OsTime) -> Result<()> {
467    check(unsafe { ffi::OS_SelectSingleAbs(id.0, state, abstime.0) })?;
468    Ok(())
469}
470
471/// Waits for events across multiple file handles with a relative timeout.
472pub fn select_multiple(
473    read_set: Option<&mut FdSet>,
474    write_set: Option<&mut FdSet>,
475    timeout_ms: i32,
476) -> Result<()> {
477    let read_ptr = read_set.map_or(core::ptr::null_mut(), |s| &mut s.0);
478    let write_ptr = write_set.map_or(core::ptr::null_mut(), |s| &mut s.0);
479    check(unsafe { ffi::OS_SelectMultiple(read_ptr, write_ptr, timeout_ms) })?;
480    Ok(())
481}
482
483/// Waits for events across multiple file handles with an absolute timeout.
484    #[cfg(not(nos3_cfe))]
485pub fn select_multiple_abs(
486    read_set: Option<&mut FdSet>,
487    write_set: Option<&mut FdSet>,
488    abstime: OsTime,
489) -> Result<()> {
490    let read_ptr = read_set.map_or(core::ptr::null_mut(), |s| &mut s.0);
491    let write_ptr = write_set.map_or(core::ptr::null_mut(), |s| &mut s.0);
492    check(unsafe { ffi::OS_SelectMultipleAbs(read_ptr, write_ptr, abstime.0) })?;
493    Ok(())
494}
495
496/// Properties of an OSAL socket.
497#[derive(Debug, Clone)]
498pub struct SocketProp {
499    /// The registered name of the socket.
500    pub name: String<{ ffi::OS_MAX_API_NAME as usize }>,
501    /// The OSAL ID of the task that created the socket.
502    pub creator: OsalId,
503}
504
505/// Finds an existing socket ID by its name.
506pub fn get_socket_id_by_name(name: &str) -> Result<OsalId> {
507    let c_name = cstring::<{ ffi::OS_MAX_API_NAME as usize }>(name)?;
508    let mut sock_id = MaybeUninit::uninit();
509    check(unsafe { ffi::OS_SocketGetIdByName(sock_id.as_mut_ptr(), c_name.as_ptr()) })?;
510    Ok(OsalId(unsafe { sock_id.assume_init() }))
511}
512
513/// Retrieves information about a socket.
514pub fn get_socket_info(sock_id: OsalId) -> Result<SocketProp> {
515    let mut prop = MaybeUninit::<ffi::OS_socket_prop_t>::uninit();
516    check(unsafe { ffi::OS_SocketGetInfo(sock_id.0, prop.as_mut_ptr()) })?;
517    let prop = unsafe { prop.assume_init() };
518
519    Ok(SocketProp {
520        name: string_from_c_buf(&prop.name)?,
521        creator: OsalId(prop.creator),
522    })
523}
524
525impl UdpSocket {
526    /// Asynchronously receives a single datagram message on the socket.
527    pub fn recv<'a>(
528        &'a self,
529        buf: &'a mut [u8],
530    ) -> impl Future<Output = Result<(usize, SocketAddr)>> + use<'a> {
531        core::future::poll_fn(|cx| {
532            let recv_future = self.recv_from(buf, Timeout::Poll);
533            match recv_future {
534                Err(CfsError::Osal(OsalError::Timeout | OsalError::QueueEmpty)) => {
535                    crate::runtime::reactor::register_read(cx.waker(), self.id());
536                    Poll::Pending
537                }
538                Ok(result) => Poll::Ready(Ok(result)),
539                Err(e) => Poll::Ready(Err(e)),
540            }
541        })
542    }
543
544    /// Asynchronously sends data on the socket to the given address.
545    pub fn send<'a>(
546        &'a self,
547        buf: &'a [u8],
548        target: &'a SocketAddr,
549    ) -> impl Future<Output = Result<usize>> + use<'a> {
550        core::future::poll_fn(|cx| {
551            match self.send_to(buf, target) {
552                Err(CfsError::Osal(OsalError::Timeout | OsalError::QueueEmpty)) => {
553                    crate::runtime::reactor::register_write(cx.waker(), self.id());
554                    Poll::Pending
555                }
556                Ok(result) => Poll::Ready(Ok(result)),
557                Err(e) => Poll::Ready(Err(e)),
558            }
559        })
560    }
561}