ergot_base/socket/
mod.rs

1//! The "Sockets"
2//!
3//! Ergot is oriented around type-safe sockets. Rather than TCP/IP sockets,
4//! which provide users with either streams or frames of bytes (e.g. `[u8]`),
5//! Ergot sockets are always of a certain Rust data type, such as structs or
6//! enums. They provide an API very similar to "channels", a common way of
7//! passing data around within Rust programs.
8//!
9//! When messages are received from outside of the current application/firmware,
10//! messages are deserialized using the `postcard` serialization format, a
11//! compact, non-self-describing, binary format.
12//!
13//! When messages are sent locally within a device, no serialization or
14//! deserialization occurs, meaning that fundamentally sending data to an
15//! Ergot socket locally has no cost over using a normal channel.
16//!
17//! In general: Sockets **receive**, and the NetStack **sends**.
18//!
19//! ### Non-stateful sockets
20//!
21//! Currently, sockets in Ergot are not stateful, meaning that they only serve
22//! to receive messages. Replies may be made by sending a response to the
23//! source address of the received message, using the [`NetStack`] to send
24//! the response.
25//!
26//! Conceptually, this makes Ergot sockets similar to UDP sockets: delivery
27//! is not guaranteed.
28//!
29//! ### A variety of sockets
30//!
31//! Ergot allows for different implementations of what a "socket" is, with
32//! a common subset of functionality. Normally, this might sound like just the
33//! problem to solve with a Rust `trait`, however as we would like to store
34//! all of these items in a single intrusive linked list, this becomes
35//! problematic.
36//!
37//! Instead, the pinned sockets all feature a common socket header, which
38//! includes a hand-crafted vtable used to interact with the socket. This allows
39//! us to have the moral equivalent to `List<dyn Socket>`, but in a way that
40//! is easier to support on embedded devices without an allocator.
41//!
42//! This indirection allows us to be flexible both in intent of a socket, for
43//! example a socket that expects a single one-shot response, or a socket that
44//! expects a stream of requests; as well as flexible in the means of storage
45//! of a socket, for example using stackful bounded queues of message on
46//! embedded systems, or heapful unbounded queues of messages on systems with
47//! an allocator.
48//!
49//! This approach of using a linked list, common header, and vtable, is NEARLY
50//! IDENTICAL to how most async executors operate in Rust, particularly how
51//! Tasks containing differently-typed Futures are handled by the executor
52//! itself when it comes to polling or dropping a Task.
53//!
54//! [`NetStack`]: crate::NetStack
55
56use core::{
57    any::TypeId,
58    ptr::{self, NonNull},
59};
60
61use crate::{FrameKind, HeaderSeq, Key, ProtocolError};
62use cordyceps::{Linked, list::Links};
63
64pub mod owned;
65pub mod std_bounded;
66
67#[derive(Debug)]
68pub struct Attributes {
69    pub kind: FrameKind,
70    // If true: participates in service discovery and responds to ANY delivery.
71    // if false: is not included in service discovery, and only responds to specific port addressing.
72    pub discoverable: bool,
73}
74
75#[derive(Debug)]
76pub struct SocketHeader {
77    pub(crate) links: Links<SocketHeader>,
78    pub(crate) vtable: &'static SocketVTable,
79    pub(crate) key: Key,
80    pub(crate) attrs: Attributes,
81    pub(crate) port: u8,
82}
83
84// TODO: Way of signaling "socket consumed"?
85#[derive(Debug, PartialEq, Eq)]
86#[non_exhaustive]
87pub enum SocketSendError {
88    NoSpace,
89    DeserFailed,
90    TypeMismatch,
91    WhatTheHell,
92}
93
94#[derive(Debug, Clone)]
95pub struct SocketVTable {
96    pub(crate) recv_owned: Option<RecvOwned>,
97    pub(crate) recv_bor: Option<RecvBorrowed>,
98    pub(crate) recv_raw: RecvRaw,
99    pub(crate) recv_err: Option<RecvError>,
100    // NOTE: We do *not* have a `drop` impl here, because the list
101    // doesn't ACTUALLY own the nodes, so it is not responsible for dropping
102    // them. They are naturally destroyed by their true owner.
103}
104
105#[derive(Debug)]
106pub struct OwnedMessage<T: 'static> {
107    pub hdr: HeaderSeq,
108    pub t: T,
109}
110
111pub enum Contents<T: 'static> {
112    Mesg(OwnedMessage<T>),
113    Err(OwnedMessage<ProtocolError>),
114    None,
115}
116
117pub type Response<T> = Result<OwnedMessage<T>, OwnedMessage<ProtocolError>>;
118
119// TODO: replace with header and handle kind and stuff right!
120
121// Morally: &T, TypeOf<T>, src, dst
122// If return OK: the type has been moved OUT of the source
123// May serialize, or may be just moved.
124pub type RecvOwned = fn(
125    // The socket ptr
126    NonNull<()>,
127    // The T ptr
128    NonNull<()>,
129    // the header
130    HeaderSeq,
131    // The T ty
132    &TypeId,
133) -> Result<(), SocketSendError>;
134// Morally: &T, src, dst
135// Always a serialize
136pub type RecvBorrowed = fn(
137    // The socket ptr
138    NonNull<()>,
139    // The T ptr
140    NonNull<()>,
141    // the header
142    HeaderSeq,
143) -> Result<(), SocketSendError>;
144// Morally: it's a packet
145// Never a serialize, sometimes a deserialize
146pub type RecvRaw = fn(
147    // The socket ptr
148    NonNull<()>,
149    // The packet
150    &[u8],
151    // the header
152    HeaderSeq,
153) -> Result<(), SocketSendError>;
154
155pub type RecvError = fn(
156    // The socket ptr
157    NonNull<()>,
158    // the header
159    HeaderSeq,
160    // The Error
161    ProtocolError,
162);
163
164// --------------------------------------------------------------------------
165// impl SocketHeader
166// --------------------------------------------------------------------------
167
168unsafe impl Linked<Links<SocketHeader>> for SocketHeader {
169    type Handle = NonNull<SocketHeader>;
170
171    fn into_ptr(r: Self::Handle) -> std::ptr::NonNull<Self> {
172        r
173    }
174
175    unsafe fn from_ptr(ptr: std::ptr::NonNull<Self>) -> Self::Handle {
176        ptr
177    }
178
179    unsafe fn links(target: NonNull<Self>) -> NonNull<Links<SocketHeader>> {
180        // Safety: using `ptr::addr_of!` avoids creating a temporary
181        // reference, which stacked borrows dislikes.
182        let node = unsafe { ptr::addr_of_mut!((*target.as_ptr()).links) };
183        unsafe { NonNull::new_unchecked(node) }
184    }
185}
186
187impl SocketSendError {
188    pub fn to_error(&self) -> ProtocolError {
189        match self {
190            SocketSendError::NoSpace => ProtocolError::SSE_NO_SPACE,
191            SocketSendError::DeserFailed => ProtocolError::SSE_DESER_FAILED,
192            SocketSendError::TypeMismatch => ProtocolError::SSE_TYPE_MISMATCH,
193            SocketSendError::WhatTheHell => ProtocolError::SSE_WHAT_THE_HELL,
194        }
195    }
196}