Skip to content

Commit fcdf2c9

Browse files
zephyr: net: Add TCP implementation
Add TcpListener and TcpStream structs which work much like those in the Rust std library. Also add a basic test case to confirm that the TCP abstractions have the basic functions working. Signed-off-by: Matt Rodgers <[email protected]>
1 parent 464fdfb commit fcdf2c9

File tree

8 files changed

+277
-20
lines changed

8 files changed

+277
-20
lines changed

tests/net/tcp/CMakeLists.txt

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
cmake_minimum_required(VERSION 3.20.0)
4+
5+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
6+
project(tcp_rust)
7+
8+
rust_cargo_application()

tests/net/tcp/Cargo.toml

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright (c) 2025 Witekio
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
[package]
5+
# This must be rustapp for now.
6+
name = "rustapp"
7+
version = "0.1.0"
8+
edition = "2021"
9+
description = "Tests of TCP sockets"
10+
license = "Apache-2.0 or MIT"
11+
12+
[lib]
13+
crate-type = ["staticlib"]
14+
15+
[dependencies]
16+
zephyr = "0.1.0"

tests/net/tcp/prj.conf

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Copyright (c) 2025 Witekio
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
CONFIG_RUST=y
5+
CONFIG_RUST_ALLOC=y # For threads
6+
CONFIG_MAIN_STACK_SIZE=2048
7+
8+
# Networking config
9+
CONFIG_NETWORKING=y
10+
CONFIG_NET_IPV4=y
11+
CONFIG_NET_IPV6=y
12+
CONFIG_NET_TCP=y
13+
CONFIG_NET_SOCKETS=y
14+
CONFIG_NET_PKT_RX_COUNT=16
15+
CONFIG_NET_PKT_TX_COUNT=16
16+
17+
# Networking drivers
18+
CONFIG_NET_DRIVERS=y
19+
CONFIG_NET_TEST=y
20+
CONFIG_NET_LOOPBACK=y
21+
CONFIG_TEST_RANDOM_GENERATOR=y
22+

tests/net/tcp/src/lib.rs

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) 2025 Witekio
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
#![no_std]
5+
6+
use zephyr::net::{TcpListener, TcpStream};
7+
use zephyr::printkln;
8+
9+
#[no_mangle]
10+
extern "C" fn rust_main() {
11+
let tx_data: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
12+
let mut server_recv_buf: [u8; 64] = [0; 64];
13+
let mut client_recv_buf: [u8; 64] = [0; 64];
14+
15+
// The test creates client and server TcpStreams, and echo's some data between them to verify
16+
// that the TCP connection is functioning
17+
printkln!("Starting tests");
18+
19+
let server_sockaddr = "0.0.0.0:4242"
20+
.parse()
21+
.expect("Failed to parse server sockaddr");
22+
23+
let connect_sockaddr = "127.0.0.1:4242"
24+
.parse()
25+
.expect("Failed to parse connect sockaddr");
26+
27+
let mut listener = TcpListener::bind(&server_sockaddr).expect("Failed to create TcpListener");
28+
29+
let mut client_stream =
30+
TcpStream::connect(&connect_sockaddr).expect("Failed to create client TcpStream");
31+
32+
let sent = client_stream
33+
.send(&tx_data)
34+
.expect("Client failed to send data");
35+
36+
assert_eq!(sent, tx_data.len());
37+
38+
let (mut server_stream, _) = listener.accept().expect("Failed to accept connection");
39+
40+
let recvd = server_stream
41+
.recv(&mut server_recv_buf)
42+
.expect("Server failed to receive data");
43+
44+
assert_eq!(recvd, tx_data.len());
45+
assert_eq!(server_recv_buf[0..recvd], tx_data);
46+
47+
let sent = server_stream
48+
.send(&server_recv_buf[0..recvd])
49+
.expect("Failed to send data");
50+
51+
assert_eq!(sent, recvd);
52+
53+
let recvd = client_stream
54+
.recv(&mut client_recv_buf)
55+
.expect("Client failed to receive data");
56+
57+
assert_eq!(recvd, sent);
58+
assert_eq!(client_recv_buf[0..recvd], tx_data);
59+
60+
printkln!("All tests passed");
61+
}

tests/net/tcp/testcase.yaml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
common:
2+
filter: CONFIG_RUST_SUPPORTED
3+
platform_allow:
4+
- qemu_cortex_m0
5+
- qemu_cortex_m3
6+
- qemu_riscv32
7+
- qemu_riscv64
8+
- nrf52840dk/nrf52840
9+
tests:
10+
test.rust.tcp:
11+
harness: console
12+
harness_config:
13+
type: one_line
14+
regex:
15+
- "All tests passed"

tests/net/udp/prj.conf

-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,3 @@ CONFIG_NET_TEST=y
1717
CONFIG_NET_LOOPBACK=y
1818
CONFIG_TEST_RANDOM_GENERATOR=y
1919

20-
# Avoid tests blocking forever if something goes wrong
21-
CONFIG_NET_CONTEXT_SNDTIMEO=y
22-
CONFIG_NET_CONTEXT_RCVTIMEO=y

zephyr/src/net/mod.rs

+75-6
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod socket;
1010

1111
use crate::error::Result;
1212
use crate::net::socket::{Domain, Protocol, SockType, Socket};
13+
use core::ffi::c_int;
1314
use core::net::SocketAddr;
1415

1516
/// UDP socket.
@@ -26,18 +27,16 @@ pub struct UdpSocket {
2627
impl UdpSocket {
2728
/// Create a UDP socket bound to the provided socket address.
2829
pub fn bind(addr: &SocketAddr) -> Result<Self> {
29-
let domain = match addr {
30-
SocketAddr::V4(_) => Domain::AfInet,
31-
SocketAddr::V6(_) => Domain::AfInet6,
32-
};
30+
let domain = Domain::from_socket_addr(addr);
3331

34-
let mut sock = Socket::new(domain, SockType::Dgram, Protocol::IpprotoUdp)?;
32+
let mut sock = Socket::new(domain, SockType::Dgram, Protocol::IpProtoUdp)?;
3533
sock.bind(addr)?;
3634

3735
Ok(Self { sock })
3836
}
3937

40-
/// Send data to the specified socket address.
38+
/// Send data to the specified socket address, returning the number of bytes that were actually
39+
/// sent.
4140
pub fn send_to(&self, buf: &[u8], addr: &SocketAddr) -> Result<usize> {
4241
self.sock.send_to(buf, addr)
4342
}
@@ -47,3 +46,73 @@ impl UdpSocket {
4746
self.sock.recv_from(buf)
4847
}
4948
}
49+
50+
/// TCP Listener
51+
///
52+
/// A TCP listener can be created by binding to a local socket address. It represents a server
53+
/// socket which is ready to accept incoming connections.
54+
///
55+
/// Connections can be accepted by calling [`accept`] on the TcpListener.
56+
///
57+
/// [`accept`]: TcpListener::accept
58+
pub struct TcpListener {
59+
sock: Socket,
60+
}
61+
62+
impl TcpListener {
63+
/// Create a TCP listener bound to and listening on the provided socket address.
64+
pub fn bind(addr: &SocketAddr) -> Result<Self> {
65+
let domain = Domain::from_socket_addr(addr);
66+
67+
let mut sock = Socket::new(domain, SockType::Stream, Protocol::IpProtoTcp)?;
68+
sock.bind(addr)?;
69+
sock.listen()?;
70+
71+
Ok(Self { sock })
72+
}
73+
74+
/// Accept a single connection from the TCP listener.
75+
pub fn accept(&mut self) -> Result<(TcpStream, SocketAddr)> {
76+
let (sock, peer) = self.sock.accept()?;
77+
Ok((TcpStream { sock }, peer))
78+
}
79+
}
80+
81+
/// TCP Stream
82+
///
83+
/// This represents a connected TCP socket, which may be either a client or server socket. Data can
84+
/// be sent and received over the stream using the [`send`] and [`recv`] methods.
85+
///
86+
/// There are two ways to get a `TcpStream`:
87+
/// - Call [`accept`] on a [`TcpListener`] to get a connected stream as a server.
88+
/// - Use [`connect`] to get a connected stream as a client
89+
///
90+
/// [`accept`]: TcpListener::accept
91+
/// [`connect`]: TcpStream::connect
92+
/// [`send`]: TcpStream::send
93+
/// [`recv`]: TcpStream::recv
94+
pub struct TcpStream {
95+
sock: Socket,
96+
}
97+
98+
impl TcpStream {
99+
/// Create a TCP stream by connecting to the provided socket address.
100+
pub fn connect(addr: &SocketAddr) -> Result<Self> {
101+
let domain = Domain::from_socket_addr(addr);
102+
103+
let mut sock = Socket::new(domain, SockType::Stream, Protocol::IpProtoTcp)?;
104+
sock.connect(addr)?;
105+
106+
Ok(Self { sock })
107+
}
108+
109+
/// Send data over the TCP stream, returning the number of bytes that were actually sent.
110+
pub fn send(&mut self, buf: &[u8]) -> Result<usize> {
111+
self.sock.send(buf)
112+
}
113+
114+
/// Receive data from the TCP stream, returning the number of bytes received.
115+
pub fn recv(&mut self, buf: &mut [u8]) -> Result<usize> {
116+
self.sock.recv(buf)
117+
}
118+
}

zephyr/src/net/socket.rs

+80-11
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@
55
66
use crate::error::{to_result_errno, Result};
77
use crate::net::ipaddr::{try_sockaddr_from_c, try_sockaddr_to_c};
8-
use crate::raw::{
9-
sockaddr, socklen_t, zsock_bind, zsock_close, zsock_recvfrom, zsock_sendto, zsock_socket,
10-
};
8+
use crate::raw::{self, sockaddr, socklen_t};
119
use core::ffi::{c_int, c_void};
1210
use core::mem::MaybeUninit;
1311
use core::net::SocketAddr;
@@ -18,16 +16,31 @@ pub(crate) enum Domain {
1816
AfInet6 = crate::raw::AF_INET6 as isize,
1917
}
2018

19+
impl Domain {
20+
/// Get the Domain associated with a SocketAddr
21+
pub(crate) fn from_socket_addr(addr: &SocketAddr) -> Self {
22+
match addr {
23+
SocketAddr::V4(_) => Self::AfInet,
24+
SocketAddr::V6(_) => Self::AfInet6,
25+
}
26+
}
27+
}
28+
2129
#[derive(Debug, Copy, Clone)]
2230
pub(crate) enum SockType {
2331
Dgram = crate::raw::net_sock_type_SOCK_DGRAM as isize,
32+
Stream = crate::raw::net_sock_type_SOCK_STREAM as isize,
2433
}
2534

2635
#[derive(Debug, Copy, Clone)]
2736
pub(crate) enum Protocol {
28-
IpprotoUdp = crate::raw::net_ip_protocol_IPPROTO_UDP as isize,
37+
IpProtoUdp = crate::raw::net_ip_protocol_IPPROTO_UDP as isize,
38+
IpProtoTcp = crate::raw::net_ip_protocol_IPPROTO_TCP as isize,
2939
}
3040

41+
// We could make the backlog configurable, but for now just pick a sensible default value
42+
const LISTEN_BACKLOG: c_int = 10;
43+
3144
/// Socket type implementing minimal safe wrappers around Zephyr's C socket API
3245
pub(crate) struct Socket {
3346
fd: c_int,
@@ -38,7 +51,8 @@ impl Socket {
3851
///
3952
/// This is a minimal wrapper around zsock_socket
4053
pub(crate) fn new(domain: Domain, sock_type: SockType, protocol: Protocol) -> Result<Socket> {
41-
let res = unsafe { zsock_socket(domain as c_int, sock_type as c_int, protocol as c_int) };
54+
let res =
55+
unsafe { raw::zsock_socket(domain as c_int, sock_type as c_int, protocol as c_int) };
4256

4357
let fd = to_result_errno(res)?;
4458

@@ -51,12 +65,52 @@ impl Socket {
5165
pub(crate) fn bind(&mut self, addr: &SocketAddr) -> Result<()> {
5266
let (sockaddr, socklen) = try_sockaddr_to_c(addr)?;
5367

54-
let res = unsafe { zsock_bind(self.fd, &sockaddr as *const sockaddr, socklen) };
68+
let res = unsafe { raw::zsock_bind(self.fd, &sockaddr as *const sockaddr, socklen) };
5569

5670
let _ = to_result_errno(res)?;
5771
Ok(())
5872
}
5973

74+
/// Connect a socket to a peer socket address
75+
///
76+
/// This is a minimal wrapper around zsock_connect
77+
pub(crate) fn connect(&mut self, peer: &SocketAddr) -> Result<()> {
78+
let (sa, socklen) = try_sockaddr_to_c(peer)?;
79+
80+
let res = unsafe { raw::zsock_connect(self.fd, &sa as *const sockaddr, socklen) };
81+
let _ = to_result_errno(res)?;
82+
Ok(())
83+
}
84+
85+
/// Listen for incoming connections on a socket
86+
///
87+
/// This is a minimal wrapper around zsock_listen
88+
pub(crate) fn listen(&mut self) -> Result<()> {
89+
let res = unsafe { raw::zsock_listen(self.fd, LISTEN_BACKLOG) };
90+
let _ = to_result_errno(res)?;
91+
Ok(())
92+
}
93+
94+
/// Accept a connection on a listening socket
95+
///
96+
/// This is a minimal wrapper around zsock_accept
97+
pub(crate) fn accept(&mut self) -> Result<(Socket, SocketAddr)> {
98+
let mut sa = MaybeUninit::<sockaddr>::uninit();
99+
let mut socklen: socklen_t = core::mem::size_of::<sockaddr>();
100+
101+
let res =
102+
unsafe { raw::zsock_accept(self.fd, sa.as_mut_ptr(), &mut socklen as *mut socklen_t) };
103+
104+
let new_fd = to_result_errno(res)?;
105+
let new_sock = Socket { fd: new_fd };
106+
107+
// SAFETY: `zsock_accept` returned a success code, so it has populated the sockaddr.
108+
let sa = unsafe { sa.assume_init() };
109+
110+
let peer_sa = try_sockaddr_from_c(&sa, socklen)?;
111+
Ok((new_sock, peer_sa))
112+
}
113+
60114
/// Receive from the socket, returning the data length and peer address it was received from
61115
///
62116
/// This is a minimal wrapper around zsock_recvfrom
@@ -65,7 +119,7 @@ impl Socket {
65119
let mut socklen: socklen_t = core::mem::size_of::<sockaddr>();
66120

67121
let res = unsafe {
68-
zsock_recvfrom(
122+
raw::zsock_recvfrom(
69123
self.fd,
70124
buf.as_mut_ptr() as *mut c_void,
71125
buf.len(),
@@ -87,11 +141,11 @@ impl Socket {
87141
/// Send data to the specified socket address.
88142
///
89143
/// This is a minimal wrapper around zsock_sendto
90-
pub fn send_to(&self, buf: &[u8], addr: &SocketAddr) -> Result<usize> {
91-
let (sa, socklen) = try_sockaddr_to_c(addr)?;
144+
pub fn send_to(&self, buf: &[u8], peer: &SocketAddr) -> Result<usize> {
145+
let (sa, socklen) = try_sockaddr_to_c(peer)?;
92146

93147
let res = unsafe {
94-
zsock_sendto(
148+
raw::zsock_sendto(
95149
self.fd,
96150
buf.as_ptr() as *const c_void,
97151
buf.len(),
@@ -104,12 +158,27 @@ impl Socket {
104158
let sent = to_result_errno(res)?;
105159
Ok(sent as usize)
106160
}
161+
162+
/// Send data on a socket
163+
pub fn send(&mut self, buf: &[u8]) -> Result<usize> {
164+
let res = unsafe { raw::zsock_send(self.fd, buf.as_ptr() as *const c_void, buf.len(), 0) };
165+
let sent = to_result_errno(res)?;
166+
Ok(sent as usize)
167+
}
168+
169+
/// Receive data from a socket
170+
pub fn recv(&mut self, buf: &mut [u8]) -> Result<usize> {
171+
let res =
172+
unsafe { raw::zsock_recv(self.fd, buf.as_mut_ptr() as *mut c_void, buf.len(), 0) };
173+
let recvd = to_result_errno(res)?;
174+
Ok(recvd as usize)
175+
}
107176
}
108177

109178
impl Drop for Socket {
110179
fn drop(&mut self) {
111180
unsafe {
112-
let _ = zsock_close(self.fd);
181+
let _ = raw::zsock_close(self.fd);
113182
}
114183
}
115184
}

0 commit comments

Comments
 (0)