veecle_osal_api/net/
tcp.rs

1//! TCP socket abstractions.
2//!
3//! To get started, see [`TcpSocket`].
4
5use core::fmt::Display;
6use core::fmt::Formatter;
7use core::net::SocketAddr;
8use embedded_io_async::ErrorKind;
9
10/// TCP socket for establishing connections.
11///
12/// A socket can only handle one connection at a time.
13/// For multiple connections, use multiple socket instances.
14/// While no socket is actively listening, incoming connections are rejected via `RST` packet.
15/// This differs from the typical behavior of TCP sockets on Linux.
16///
17/// # Example
18///
19/// ```no_run
20/// use veecle_osal_api::net::tcp::{TcpSocket, TcpConnection};
21/// use core::net::SocketAddr;
22///
23/// async fn connect_example(mut socket: impl TcpSocket)
24/// {
25///     let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
26///     let connection = socket.connect(addr).await.unwrap();
27///     // Use connection for reading/writing.
28///     connection.close().await;
29/// }
30/// ```
31#[expect(async_fn_in_trait)]
32pub trait TcpSocket {
33    /// Connects to a remote TCP server.
34    async fn connect(&mut self, address: SocketAddr) -> Result<impl TcpConnection, Error>;
35
36    /// Accepts an incoming TCP connection.
37    ///
38    /// Binds to the specified address and waits for an incoming connection.
39    /// Returns the connection and the remote peer's address.
40    ///
41    /// Listens on all devices if an all-zero IP is provided.
42    ///
43    /// Does not support using the `0` port to listen on an automatically assigned port.
44    ///
45    /// # Loopback
46    ///
47    /// Depending on the platform, the remote peer's address might be set to `127.0.0.1`,
48    /// regardless of the actual IPv4 used.
49    async fn accept(
50        &mut self,
51        address: SocketAddr,
52    ) -> Result<(impl TcpConnection, SocketAddr), Error>;
53}
54
55/// TCP connection for reading and writing data.
56///
57/// Implements async read and write operations through `embedded_io_async` traits.
58///
59/// # Example
60///
61/// ```no_run
62/// use embedded_io_async::{Read, Write};
63/// use veecle_osal_api::net::tcp::TcpConnection;
64///
65/// async fn echo_server(mut connection: impl TcpConnection)
66/// {
67///     let mut buffer = [0u8; 1024];
68///     loop {
69///         match connection.read(&mut buffer).await {
70///             Ok(0) => break, // Connection closed.
71///             Ok(read) => {
72///                 connection.write_all(&buffer[..read]).await.unwrap();
73///                 connection.flush().await.unwrap();
74///             }
75///             Err(_) => break,
76///         }
77///     }
78///     connection.close().await;
79/// }
80/// ```
81#[expect(async_fn_in_trait)]
82pub trait TcpConnection:
83    core::fmt::Debug
84    + embedded_io_async::Read
85    + embedded_io_async::Write
86    + embedded_io_async::ErrorType<Error = Error>
87{
88    /// Closes the write-half of the TCP connection and flushes all unsent data.
89    async fn close(self);
90}
91
92/// Errors that can occur when using TCP sockets.
93#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Copy, Clone, Hash)]
94pub enum Error {
95    /// The connection was refused or reset by timeout or RST packet.
96    ConnectionReset,
97    /// The socket is in an invalid state.
98    InvalidState,
99    /// The provided port is invalid.
100    InvalidPort,
101    /// The provided address is invalid.
102    ///
103    /// This can occur if the address is already in use or doesn't exist.
104    InvalidAddress,
105    /// The connection timed out.
106    TimedOut,
107    /// No route to host.
108    NoRoute,
109    /// No permission to access the resource.
110    PermissionDenied,
111    /// The network stack is down.
112    NetworkDown,
113    /// Currently unhandled error occurred.
114    /// Please open a bug report if you encounter this error.
115    Other,
116}
117
118impl Display for Error {
119    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
120        match self {
121            Error::ConnectionReset => {
122                write!(f, "The connection was reset by timeout of RST packet.")
123            }
124            Error::InvalidState => {
125                write!(f, "The socket is in an invalid state.")
126            }
127            Error::InvalidPort => {
128                write!(f, "The provided port is invalid.")
129            }
130            Error::TimedOut => {
131                write!(f, "The connection timed out.")
132            }
133            Error::NoRoute => {
134                write!(f, "No route to host.")
135            }
136            Error::Other => {
137                write!(
138                    f,
139                    "Unspecified error, please open a bug report if you encounter this error."
140                )
141            }
142            Error::InvalidAddress => {
143                write!(f, "The provided address is invalid.")
144            }
145            Error::PermissionDenied => {
146                write!(f, "No permission to access the resource.")
147            }
148            Error::NetworkDown => {
149                write!(f, "The network stack is down.")
150            }
151        }
152    }
153}
154
155impl core::error::Error for Error {}
156
157impl embedded_io_async::ErrorType for Error {
158    type Error = Error;
159}
160
161impl embedded_io_async::Error for Error {
162    fn kind(&self) -> ErrorKind {
163        match self {
164            Error::ConnectionReset => ErrorKind::ConnectionReset,
165            Error::InvalidState => ErrorKind::InvalidInput,
166            Error::InvalidPort => ErrorKind::InvalidInput,
167            Error::InvalidAddress => ErrorKind::InvalidInput,
168            Error::TimedOut => ErrorKind::TimedOut,
169            Error::NoRoute => ErrorKind::Other,
170            Error::PermissionDenied => ErrorKind::PermissionDenied,
171            Error::NetworkDown => ErrorKind::NotConnected,
172            Error::Other => ErrorKind::Other,
173        }
174    }
175}
176
177#[doc(hidden)]
178#[cfg(feature = "test-suites")]
179#[cfg_attr(coverage_nightly, coverage(off))]
180pub mod test_suite {
181    #![expect(missing_docs, reason = "tests")]
182    //! Test suite for TCP sockets.
183
184    use crate::net::tcp::{Error, TcpConnection, TcpSocket};
185    use embedded_io_async::{Read, Write};
186    use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
187
188    pub async fn test_connect(
189        mut client: impl TcpSocket,
190        mut server: impl TcpSocket,
191        ip_address: &str,
192    ) {
193        let ip_address = ip_address.parse().unwrap();
194        let server_addr = SocketAddr::new(ip_address, 59001);
195
196        let server_task = async {
197            let (connection, remote_addr) = server.accept(server_addr).await.unwrap();
198
199            // The remote address port should be different from server port.
200            // The IP might be normalized (e.g., 127.3.0.1 -> 127.0.0.1) on some platforms.
201            assert!(remote_addr.ip().is_loopback() || remote_addr.ip() == ip_address);
202            assert_ne!(remote_addr.port(), server_addr.port());
203            assert_ne!(remote_addr.port(), 0);
204
205            connection.close().await;
206        };
207
208        let client_task = async {
209            let connection = loop {
210                if let Ok(connection) = client.connect(server_addr).await {
211                    break connection;
212                }
213            };
214
215            connection.close().await;
216        };
217
218        futures::join!(server_task, client_task);
219    }
220
221    pub async fn test_send_recv(
222        mut client: impl TcpSocket,
223        mut server: impl TcpSocket,
224        ip_address: &str,
225    ) {
226        let ip_address = ip_address.parse().unwrap();
227        let server_addr = SocketAddr::new(ip_address, 59003);
228
229        let server_task = async {
230            let (mut connection, _) = server.accept(server_addr).await.unwrap();
231
232            let mut buffer = [0u8; 256];
233            let read = connection.read(&mut buffer).await.unwrap();
234            assert_eq!(&buffer[..read], b"Test message from client");
235
236            connection.write_all(&buffer[..read]).await.unwrap();
237            connection.flush().await.unwrap();
238
239            let read = connection.read(&mut buffer).await.unwrap();
240            assert_eq!(&buffer[..read], b"Second message");
241
242            connection.write_all(b"Acknowledged").await.unwrap();
243            connection.flush().await.unwrap();
244
245            connection.close().await;
246        };
247
248        let client_task = async {
249            let mut connection = loop {
250                if let Ok(connection) = client.connect(server_addr).await {
251                    break connection;
252                }
253            };
254
255            connection
256                .write_all(b"Test message from client")
257                .await
258                .unwrap();
259            connection.flush().await.unwrap();
260
261            let mut buffer = [0u8; 256];
262            let read = connection.read(&mut buffer).await.unwrap();
263            assert_eq!(&buffer[..read], b"Test message from client");
264
265            connection.write_all(b"Second message").await.unwrap();
266            connection.flush().await.unwrap();
267
268            let read = connection.read(&mut buffer).await.unwrap();
269            assert_eq!(&buffer[..read], b"Acknowledged");
270
271            connection.close().await;
272        };
273
274        futures::join!(server_task, client_task);
275    }
276
277    pub async fn test_connect_refused(mut client: impl TcpSocket, ip_address: &str) {
278        let ip_address = ip_address.parse().unwrap();
279        let server_addr = SocketAddr::new(ip_address, 59900);
280
281        assert_eq!(
282            client.connect(server_addr).await.unwrap_err(),
283            Error::ConnectionReset
284        );
285    }
286
287    pub async fn test_accept_with_zero_port(mut server: impl TcpSocket, ip_address: &str) {
288        let ip_address = ip_address.parse().unwrap();
289        let server_addr = SocketAddr::new(ip_address, 0);
290
291        assert_eq!(
292            server.accept(server_addr).await.unwrap_err(),
293            Error::InvalidPort
294        );
295    }
296
297    pub async fn test_accept_all_zero_ip(
298        mut client: impl TcpSocket,
299        mut server: impl TcpSocket,
300        ip_address: &str,
301    ) {
302        let port = 59910;
303        let ip_address: IpAddr = ip_address.parse().unwrap();
304        let ip_address = SocketAddr::new(ip_address, port);
305        let all_zero_address = if ip_address.is_ipv4() {
306            SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), port)
307        } else {
308            SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)), port)
309        };
310
311        let server_task = async {
312            let (mut connection, _) = server.accept(all_zero_address).await.unwrap();
313
314            let mut buffer = [0u8; 256];
315            let read = connection.read(&mut buffer).await.unwrap();
316            assert_eq!(&buffer[..read], b"Test message from client");
317
318            connection.write_all(&buffer[..read]).await.unwrap();
319            connection.flush().await.unwrap();
320        };
321
322        let client_task = async {
323            let mut connection = loop {
324                if let Ok(connection) = client.connect(ip_address).await {
325                    break connection;
326                }
327            };
328
329            connection
330                .write_all(b"Test message from client")
331                .await
332                .unwrap();
333            connection.flush().await.unwrap();
334
335            connection.close().await;
336        };
337
338        futures::join!(server_task, client_task);
339    }
340
341    pub async fn test_close_connection(
342        mut client: impl TcpSocket,
343        mut server: impl TcpSocket,
344        ip_address: &str,
345    ) {
346        let ip_address = ip_address.parse().unwrap();
347        let server_addr = SocketAddr::new(ip_address, 59004);
348
349        let server_task = async {
350            let (mut connection, _) = server.accept(server_addr).await.unwrap();
351
352            connection.write_all(b"Hello").await.unwrap();
353            connection.flush().await.unwrap();
354
355            connection.close().await;
356        };
357
358        let client_task = async {
359            let mut connection = loop {
360                if let Ok(connection) = client.connect(server_addr).await {
361                    break connection;
362                }
363            };
364
365            let mut buffer = [0u8; 32];
366            let read = connection.read(&mut buffer).await.unwrap();
367            assert_eq!(&buffer[..read], b"Hello");
368
369            let read = connection.read(&mut buffer).await.unwrap();
370            assert_eq!(
371                read, 0,
372                "Expected EOF (0 bytes) after server closed connection"
373            );
374
375            connection.close().await;
376        };
377
378        futures::join!(server_task, client_task);
379    }
380}