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    async fn flush(&mut self) -> Result<(), Self::Error> {
75        embedded_io_async::Write::flush(&mut self.stream)
76            .await
77            .map_err(IntoOsalError::into_osal_error)
78    }
79}
80
81impl veecle_osal_api::net::tcp::TcpSocket for TcpSocket {
82    async fn connect(
83        &mut self,
84        address: SocketAddr,
85    ) -> Result<impl veecle_osal_api::net::tcp::TcpConnection, Error> {
86        let stream = tokio::net::TcpStream::connect(address)
87            .await
88            .map_err(IntoOsalError::into_osal_error)?;
89        Ok(TcpConnection {
90            stream: FromTokio::new(stream),
91            _socket: self,
92        })
93    }
94
95    async fn accept(
96        &mut self,
97        address: SocketAddr,
98    ) -> Result<(impl veecle_osal_api::net::tcp::TcpConnection, SocketAddr), Error> {
99        // Required to match the trait contract.
100        if address.port() == 0 {
101            return Err(Error::InvalidPort);
102        }
103
104        let socket = if address.ip().is_ipv4() {
105            tokio::net::TcpSocket::new_v4().map_err(IntoOsalError::into_osal_error)?
106        } else {
107            tokio::net::TcpSocket::new_v6().map_err(IntoOsalError::into_osal_error)?
108        };
109
110        // The trait contract requires allowing multiple sockets to accept connections on the same address and port.
111        socket
112            .set_reuseaddr(true)
113            .map_err(IntoOsalError::into_osal_error)?;
114        socket
115            .set_reuseport(true)
116            .map_err(IntoOsalError::into_osal_error)?;
117
118        socket
119            .bind(address)
120            .map_err(IntoOsalError::into_osal_error)?;
121
122        let listener = socket.listen(1).map_err(IntoOsalError::into_osal_error)?;
123
124        let (stream, address) = listener
125            .accept()
126            .await
127            .map_err(IntoOsalError::into_osal_error)?;
128        Ok((
129            TcpConnection {
130                stream: FromTokio::new(stream),
131                _socket: self,
132            },
133            address,
134        ))
135    }
136}
137
138impl IntoOsalError<Error> for std::io::Error {
139    fn into_osal_error(self) -> Error {
140        match self.kind() {
141            ErrorKind::PermissionDenied => Error::PermissionDenied,
142            ErrorKind::ConnectionRefused => Error::ConnectionReset,
143            ErrorKind::ConnectionReset => Error::ConnectionReset,
144            ErrorKind::HostUnreachable => Error::NoRoute,
145            ErrorKind::NetworkUnreachable => Error::NoRoute,
146            ErrorKind::ConnectionAborted => Error::ConnectionReset,
147            ErrorKind::AddrInUse => Error::InvalidAddress,
148            ErrorKind::AddrNotAvailable => Error::InvalidAddress,
149            ErrorKind::NetworkDown => Error::NetworkDown,
150            ErrorKind::TimedOut => Error::TimedOut,
151            _ => Error::Other,
152        }
153    }
154}