Sockets
This module defines a minimal socket abstraction for a custom no_std
networking stack, supporting UDP and TCP over IPv4. It’s designed to be lightweight and embedded-friendly, leveraging async operations and simple data structures like SPSC queues for buffering.
SocketAddr
A SocketAddr
identifies a network endpoint (IPv4 address + port):
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct SocketAddr {
pub addr: Ipv4Address,
pub port: u16,
}
DNS Resolution
SocketAddr::resolve
uses a UDP socket to send a DNS query and waits for a response:
pub async fn resolve(host: &str, port: u16) -> Self
Internally, this:
- Binds a new UDP socket to port 53.
- Sends a serialized DNS query to the configured DNS server.
- Receives and parses the response to extract the resolved
Ipv4Address
.
Socket Lifecycle Operations
All socket file descriptors (socketfd
) are managed via a global interface’s sockets
map.
Ephemeral Port Management
An AtomicU16
is used to track the next ephemeral port:
pub static NEXT_EPHEMERAL: AtomicU16 = AtomicU16::new(32768);
send_to
Queues a packet to be sent from a socket:
pub async fn send_to(socketfd: u16, payload: Vec<u8>, saddr: SocketAddr)
- Verifies socket existence.
- Auto-binds if not already bound.
- Enqueues the packet for transmission.
recv_from
Blocking receive from a socket’s receive queue:
pub async fn recv_from(socketfd: u16) -> Result<(Vec<u8>, SocketAddr)>
connect
, listen
, accept
, close
These follow conventional TCP socket behavior but are stubbed out for UDP:
pub async fn connect(socketfd: u16, saddr: SocketAddr)
pub async fn listen(socketfd: u16, num_requests: usize)
pub async fn accept(socketfd: u16) -> Result<u16>
pub async fn close(socketfd: u16)
Internal Representation: TaggedSocket
All sockets are wrapped in an enum that tags their type:
pub enum TaggedSocket {
Udp(UdpSocket),
Tcp(TcpSocket),
}
Each variant implements methods like:
bind(port)
recv()
/send_enqueue(payload, saddr)
listen()
/accept()
/connect()
close()
SPSC Queues for Asynchronous Communication
Internally, sockets use SPSC (Single-Producer Single-Consumer) ring buffers from crate::ringbuffer
to implement async send/receive queues.
Each socket has:
- A
Sender<BUFFER_LEN, (Vec<u8>, SocketAddr)>
for the send queue. - A
Receiver<BUFFER_LEN, (Vec<u8>, SocketAddr)>
for the receive queue.
This avoids the need for locking and is ideal for interrupt-safe or cooperative multitasking environments. In the future, this should be expanded to use a MPSC queue.
Error Handling
All socket functions return a Result<T>
with custom Error
types such as:
Error::InvalidSocket(socketfd)
Error::BindingInUse(addr)
Error::Ignored
(for operations likeconnect
on UDP)
Example Flow
Here’s how a typical TCP/HTTP send might look:
let host = "neverssl.com";
let saddr = SocketAddr::resolve(host, 80).await;
let s = TcpSocket::new();
match connect(s, saddr).await {
Ok(_) => println!("initiated connection"),
Err(_) => println!("couldn't connect to address"),
};
let path = "/";
let http_req = HttpPacket::new(HttpMethod::Get, host, path);
match send_to(s, http_req.serialize(), saddr).await {
Ok(_) => println!("sent http request"),
Err(_) => println!("couldn't send"),
};
let (resp, sender) = recv_from(s).await.unwrap();
close(s).await;
Binding Behavior
Binding a socket will:
- Fail if another socket is already bound to the same
SocketAddr
. - Succeed otherwise and update the socket’s internal bind state.
- An ephemeral socket will be used if a bind is needed and none has been provided.