veecle_osal_embassy/net/
tcp.rs

1//! TCP socket implementation for Embassy.
2
3use crate::IntoOsalError;
4use core::net::IpAddr;
5use core::net::SocketAddr;
6use embassy_net::tcp::{AcceptError, ConnectError, State};
7use embassy_net::{IpAddress, IpEndpoint, IpListenEndpoint};
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.
14pub struct TcpSocket<'a> {
15    socket: embassy_net::tcp::TcpSocket<'a>,
16}
17
18impl<'a> core::fmt::Debug for TcpSocket<'a> {
19    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
20        f.debug_struct("TcpSocket").finish()
21    }
22}
23
24impl<'a> TcpSocket<'a> {
25    /// Creates a new `TcpSocket`.
26    ///
27    /// The socket must be closed.
28    pub fn new(socket: embassy_net::tcp::TcpSocket<'a>) -> Result<Self, Error> {
29        if socket.state() != State::Closed {
30            return Err(Error::InvalidState);
31        }
32        Ok(Self { socket })
33    }
34}
35
36/// Active TCP connection for reading and writing data.
37///
38/// Implements async I/O operations through `embedded_io_async` traits.
39///
40/// The connection is automatically closed when dropped.
41/// On drop, the remote endpoint may or may not be informed about the connection terminating.
42/// To cleanly close a connection, use [`veecle_osal_api::net::tcp::TcpConnection::close`].
43pub struct TcpConnection<'a, 's> {
44    socket: &'s mut embassy_net::tcp::TcpSocket<'a>,
45}
46
47impl<'a, 's> core::fmt::Debug for TcpConnection<'a, 's> {
48    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
49        f.debug_struct("TcpConnection").finish()
50    }
51}
52
53impl Drop for TcpConnection<'_, '_> {
54    fn drop(&mut self) {
55        if self.socket.state() != State::Closed {
56            self.socket.close();
57            self.socket.abort();
58        }
59    }
60}
61
62impl<'a, 's> embedded_io_async::Read for TcpConnection<'a, 's> {
63    async fn read(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error> {
64        self.socket
65            .read(buffer)
66            .await
67            .map_err(IntoOsalError::into_osal_error)
68    }
69}
70
71impl<'a, 's> embedded_io::ErrorType for TcpConnection<'a, 's> {
72    type Error = Error;
73}
74
75impl<'a, 's> embedded_io_async::Write for TcpConnection<'a, 's> {
76    async fn write(&mut self, buffer: &[u8]) -> Result<usize, Self::Error> {
77        self.socket
78            .write(buffer)
79            .await
80            .map_err(IntoOsalError::into_osal_error)
81    }
82}
83
84impl<'a, 's> veecle_osal_api::net::tcp::TcpConnection for TcpConnection<'a, 's> {
85    async fn close(self) {
86        self.socket.close();
87        // We need to wait until the socket has been flushed to be able to reuse it.
88        // The only error that can occur is `ConnectionReset`, which isn't actionable in `close`.
89        let _ = self.socket.flush().await;
90    }
91}
92
93impl<'a> veecle_osal_api::net::tcp::TcpSocket for TcpSocket<'a> {
94    async fn connect(
95        &mut self,
96        address: SocketAddr,
97    ) -> Result<impl veecle_osal_api::net::tcp::TcpConnection, Error> {
98        self.socket
99            .connect(address)
100            .await
101            .map_err(IntoOsalError::into_osal_error)?;
102        Ok(TcpConnection {
103            socket: &mut self.socket,
104        })
105    }
106
107    async fn accept(
108        &mut self,
109        address: SocketAddr,
110    ) -> Result<(impl veecle_osal_api::net::tcp::TcpConnection, SocketAddr), Error> {
111        // smoltcp treats an all-zero address as invalid, so we need to convert it to `None`.
112        let listen_endpoint = if address.ip().is_unspecified() {
113            IpListenEndpoint {
114                addr: None,
115                port: address.port(),
116            }
117        } else {
118            address.into()
119        };
120
121        self.socket
122            .accept(listen_endpoint)
123            .await
124            .map_err(IntoOsalError::into_osal_error)?;
125        let IpEndpoint {
126            addr: address,
127            port,
128        } = self
129            .socket
130            .remote_endpoint()
131            .expect("The endpoint should be set after accepting a connection.");
132
133        let address = match address {
134            IpAddress::Ipv4(address) => IpAddr::V4(address),
135            IpAddress::Ipv6(address) => IpAddr::V6(address),
136        };
137        let address: SocketAddr = SocketAddr::new(address, port);
138        Ok((
139            TcpConnection {
140                socket: &mut self.socket,
141            },
142            address,
143        ))
144    }
145}
146
147impl IntoOsalError<Error> for AcceptError {
148    fn into_osal_error(self) -> Error {
149        match self {
150            AcceptError::ConnectionReset => Error::ConnectionReset,
151            AcceptError::InvalidState => Error::InvalidState,
152            AcceptError::InvalidPort => Error::InvalidPort,
153        }
154    }
155}
156
157impl IntoOsalError<Error> for ConnectError {
158    fn into_osal_error(self) -> Error {
159        match self {
160            ConnectError::InvalidState => Error::InvalidState,
161            ConnectError::ConnectionReset => Error::ConnectionReset,
162            ConnectError::TimedOut => Error::TimedOut,
163            ConnectError::NoRoute => Error::NoRoute,
164        }
165    }
166}
167
168impl IntoOsalError<Error> for embassy_net::tcp::Error {
169    fn into_osal_error(self) -> Error {
170        match self {
171            embassy_net::tcp::Error::ConnectionReset => Error::ConnectionReset,
172        }
173    }
174}