veecle_osal_std/net/
tcp.rs

1//! TCP socket implementation for the std platform.
2
3use crate::IntoOsalError;
4use embedded_io_adapters::tokio_1::FromTokio;
5use std::io::ErrorKind;
6use std::net::SocketAddr;
7use tokio::io::AsyncWriteExt;
8use veecle_osal_api::net::tcp::Error;
9
10/// TCP socket for establishing connections.
11///
12/// This socket can handle one connection at a time.
13/// Create multiple instances for concurrent connections.
14#[derive(Default)]
15pub struct TcpSocket;
16
17impl TcpSocket {
18    /// Creates a new `TcpSocket`.
19    pub fn new() -> Self {
20        Self
21    }
22}
23
24impl core::fmt::Debug for TcpSocket {
25    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
26        f.debug_struct("TcpSocket").finish()
27    }
28}
29
30/// Active TCP connection for reading and writing data.
31///
32/// Implements async I/O operations through `embedded_io_async` traits.
33/// The connection is automatically closed when dropped.
34pub struct TcpConnection<'s> {
35    stream: FromTokio<tokio::net::TcpStream>,
36    // Prevents multiple concurrent connections from the same socket to fulfill trait contract.
37    _socket: &'s mut TcpSocket,
38}
39
40impl<'s> core::fmt::Debug for TcpConnection<'s> {
41    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
42        f.debug_struct("TcpConnection").finish()
43    }
44}
45
46impl<'s> veecle_osal_api::net::tcp::TcpConnection for TcpConnection<'s> {
47    async fn close(self) {
48        // Any error isn't actionable here.
49        let _ = self.stream.into_inner().shutdown().await;
50    }
51}
52
53impl<'s> embedded_io_async::Read for TcpConnection<'s> {
54    async fn read(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error> {
55        self.stream
56            .read(buffer)
57            .await
58            .map_err(IntoOsalError::into_osal_error)
59    }
60}
61
62impl<'s> embedded_io::ErrorType for TcpConnection<'s> {
63    type Error = Error;
64}
65
66impl<'s> embedded_io_async::Write for TcpConnection<'s> {
67    async fn write(&mut self, buffer: &[u8]) -> Result<usize, Self::Error> {
68        self.stream
69            .write(buffer)
70            .await
71            .map_err(IntoOsalError::into_osal_error)
72    }
73}
74
75impl veecle_osal_api::net::tcp::TcpSocket for TcpSocket {
76    async fn connect(
77        &mut self,
78        address: SocketAddr,
79    ) -> Result<impl veecle_osal_api::net::tcp::TcpConnection, Error> {
80        let stream = tokio::net::TcpStream::connect(address)
81            .await
82            .map_err(IntoOsalError::into_osal_error)?;
83        Ok(TcpConnection {
84            stream: FromTokio::new(stream),
85            _socket: self,
86        })
87    }
88
89    async fn accept(
90        &mut self,
91        address: SocketAddr,
92    ) -> Result<(impl veecle_osal_api::net::tcp::TcpConnection, SocketAddr), Error> {
93        // Required to match the trait contract.
94        if address.port() == 0 {
95            return Err(Error::InvalidPort);
96        }
97
98        let socket = if address.ip().is_ipv4() {
99            tokio::net::TcpSocket::new_v4().map_err(IntoOsalError::into_osal_error)?
100        } else {
101            tokio::net::TcpSocket::new_v6().map_err(IntoOsalError::into_osal_error)?
102        };
103
104        // The trait contract requires allowing multiple sockets to accept connections on the same address and port.
105        socket
106            .set_reuseaddr(true)
107            .map_err(IntoOsalError::into_osal_error)?;
108        socket
109            .set_reuseport(true)
110            .map_err(IntoOsalError::into_osal_error)?;
111
112        socket
113            .bind(address)
114            .map_err(IntoOsalError::into_osal_error)?;
115
116        let listener = socket.listen(1).map_err(IntoOsalError::into_osal_error)?;
117
118        let (stream, address) = listener
119            .accept()
120            .await
121            .map_err(IntoOsalError::into_osal_error)?;
122        Ok((
123            TcpConnection {
124                stream: FromTokio::new(stream),
125                _socket: self,
126            },
127            address,
128        ))
129    }
130}
131
132impl IntoOsalError<Error> for std::io::Error {
133    fn into_osal_error(self) -> Error {
134        match self.kind() {
135            ErrorKind::PermissionDenied => Error::PermissionDenied,
136            ErrorKind::ConnectionRefused => Error::ConnectionReset,
137            ErrorKind::ConnectionReset => Error::ConnectionReset,
138            ErrorKind::HostUnreachable => Error::NoRoute,
139            ErrorKind::NetworkUnreachable => Error::NoRoute,
140            ErrorKind::ConnectionAborted => Error::ConnectionReset,
141            ErrorKind::AddrInUse => Error::InvalidAddress,
142            ErrorKind::AddrNotAvailable => Error::InvalidAddress,
143            ErrorKind::NetworkDown => Error::NetworkDown,
144            ErrorKind::TimedOut => Error::TimedOut,
145            _ => Error::Other,
146        }
147    }
148}