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    async fn flush(&mut self) -> Result<(), Self::Error> {
84        embedded_io_async::Write::flush(self.socket)
85            .await
86            .map_err(IntoOsalError::into_osal_error)
87    }
88}
89
90impl<'a, 's> veecle_osal_api::net::tcp::TcpConnection for TcpConnection<'a, 's> {
91    async fn close(self) {
92        self.socket.close();
93        // We need to wait until the socket has been flushed to be able to reuse it.
94        // The only error that can occur is `ConnectionReset`, which isn't actionable in `close`.
95        let _ = self.socket.flush().await;
96    }
97}
98
99impl<'a> veecle_osal_api::net::tcp::TcpSocket for TcpSocket<'a> {
100    async fn connect(
101        &mut self,
102        address: SocketAddr,
103    ) -> Result<impl veecle_osal_api::net::tcp::TcpConnection, Error> {
104        self.socket
105            .connect(address)
106            .await
107            .map_err(IntoOsalError::into_osal_error)?;
108        Ok(TcpConnection {
109            socket: &mut self.socket,
110        })
111    }
112
113    async fn accept(
114        &mut self,
115        address: SocketAddr,
116    ) -> Result<(impl veecle_osal_api::net::tcp::TcpConnection, SocketAddr), Error> {
117        // smoltcp treats an all-zero address as invalid, so we need to convert it to `None`.
118        let listen_endpoint = if address.ip().is_unspecified() {
119            IpListenEndpoint {
120                addr: None,
121                port: address.port(),
122            }
123        } else {
124            address.into()
125        };
126
127        self.socket
128            .accept(listen_endpoint)
129            .await
130            .map_err(IntoOsalError::into_osal_error)?;
131        let IpEndpoint {
132            addr: address,
133            port,
134        } = self
135            .socket
136            .remote_endpoint()
137            .expect("The endpoint should be set after accepting a connection.");
138
139        let address = match address {
140            IpAddress::Ipv4(address) => IpAddr::V4(address),
141            IpAddress::Ipv6(address) => IpAddr::V6(address),
142        };
143        let address: SocketAddr = SocketAddr::new(address, port);
144        Ok((
145            TcpConnection {
146                socket: &mut self.socket,
147            },
148            address,
149        ))
150    }
151}
152
153impl IntoOsalError<Error> for AcceptError {
154    fn into_osal_error(self) -> Error {
155        match self {
156            AcceptError::ConnectionReset => Error::ConnectionReset,
157            AcceptError::InvalidState => Error::InvalidState,
158            AcceptError::InvalidPort => Error::InvalidPort,
159        }
160    }
161}
162
163impl IntoOsalError<Error> for ConnectError {
164    fn into_osal_error(self) -> Error {
165        match self {
166            ConnectError::InvalidState => Error::InvalidState,
167            ConnectError::ConnectionReset => Error::ConnectionReset,
168            ConnectError::TimedOut => Error::TimedOut,
169            ConnectError::NoRoute => Error::NoRoute,
170        }
171    }
172}
173
174impl IntoOsalError<Error> for embassy_net::tcp::Error {
175    fn into_osal_error(self) -> Error {
176        match self {
177            embassy_net::tcp::Error::ConnectionReset => Error::ConnectionReset,
178        }
179    }
180}