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::{
62    FrameKind, HeaderSeq, Key, ProtocolError,
63    nash::NameHash,
64    wire_frames::{self, CommonHeader},
65};
66use cordyceps::{Linked, list::Links};
67use postcard::ser_flavors;
68use serde::Serialize;
69
70pub mod borrow;
71pub mod owned;
72pub mod raw_owned;
73
74#[derive(Debug)]
75pub struct Attributes {
76    pub kind: FrameKind,
77    // If true: participates in service discovery and responds to ANY delivery.
78    // if false: is not included in service discovery, and only responds to specific port addressing.
79    pub discoverable: bool,
80}
81
82#[derive(Debug)]
83pub struct SocketHeader {
84    pub(crate) links: Links<SocketHeader>,    // 2 ptrs (8/16 bytes)
85    pub(crate) vtable: &'static SocketVTable, // 4 ptrs (16/32 bytes)
86    pub(crate) key: Key,                      // 8 bytes
87    pub(crate) nash: Option<NameHash>,        // 4 bytes
88    pub(crate) attrs: Attributes,             // 2 bytes
89    pub(crate) port: u8,                      // 1 byte
90                                              // ====================
91                                              // 39 bytes / 63 bytes
92}
93
94#[derive(Debug, PartialEq, Eq)]
95#[non_exhaustive]
96pub enum SocketSendError {
97    NoSpace,
98    DeserFailed,
99    TypeMismatch,
100    WhatTheHell,
101}
102
103#[derive(Debug, Clone)]
104pub struct SocketVTable {
105    pub(crate) recv_owned: Option<RecvOwned>,
106    pub(crate) recv_bor: Option<RecvBorrowed>,
107    pub(crate) recv_raw: RecvRaw,
108    pub(crate) recv_err: Option<RecvError>,
109    // NOTE: We do *not* have a `drop` impl here, because the list
110    // doesn't ACTUALLY own the nodes, so it is not responsible for dropping
111    // them. They are naturally destroyed by their true owner.
112}
113
114#[derive(Debug)]
115pub struct HeaderMessage<T> {
116    pub hdr: HeaderSeq,
117    pub t: T,
118}
119
120pub type Response<T> = Result<HeaderMessage<T>, HeaderMessage<ProtocolError>>;
121
122// TODO: replace with header and handle kind and stuff right!
123
124// Morally: &T, TypeOf<T>, src, dst
125// If return OK: the type has been moved OUT of the source
126// May serialize, or may be just moved.
127pub type RecvOwned = fn(
128    // The socket ptr
129    NonNull<()>,
130    // The T ptr
131    NonNull<()>,
132    // the header
133    HeaderSeq,
134    // The T ty
135    &TypeId,
136) -> Result<(), SocketSendError>;
137// Morally: &T, src, dst
138// Always a serialize
139pub type RecvBorrowed = fn(
140    // The socket ptr
141    NonNull<()>,
142    // The T ptr
143    NonNull<()>,
144    // the header
145    HeaderSeq,
146    // the ser fn
147    fn(NonNull<()>, HeaderSeq, &mut [u8]) -> Result<usize, SocketSendError>,
148) -> Result<(), SocketSendError>;
149// Morally: it's a packet
150// Never a serialize, sometimes a deserialize
151pub type RecvRaw = fn(
152    // The socket ptr
153    NonNull<()>,
154    // The packet
155    &[u8],
156    // the header
157    HeaderSeq,
158    // the raw header
159    &[u8],
160) -> Result<(), SocketSendError>;
161
162pub type RecvError = fn(
163    // The socket ptr
164    NonNull<()>,
165    // the header
166    HeaderSeq,
167    // The Error
168    ProtocolError,
169);
170
171pub(crate) fn borser<T: Serialize>(
172    that: NonNull<()>,
173    hdr: HeaderSeq,
174    out: &mut [u8],
175) -> Result<usize, SocketSendError> {
176    let that = that.cast::<T>();
177    let that: &T = unsafe { that.as_ref() };
178    let ser = ser_flavors::Slice::new(out);
179
180    let chdr = CommonHeader {
181        src: hdr.src,
182        dst: hdr.dst,
183        seq_no: hdr.seq_no,
184        kind: hdr.kind,
185        ttl: hdr.ttl,
186    };
187
188    let Ok(used) = wire_frames::encode_frame_ty(ser, &chdr, hdr.any_all.as_ref(), that) else {
189        log::trace!("BOOP");
190        return Err(SocketSendError::NoSpace);
191    };
192
193    Ok(used.len())
194}
195
196// --------------------------------------------------------------------------
197// impl SocketHeader
198// --------------------------------------------------------------------------
199
200unsafe impl Linked<Links<SocketHeader>> for SocketHeader {
201    type Handle = NonNull<SocketHeader>;
202
203    fn into_ptr(r: Self::Handle) -> core::ptr::NonNull<Self> {
204        r
205    }
206
207    unsafe fn from_ptr(ptr: core::ptr::NonNull<Self>) -> Self::Handle {
208        ptr
209    }
210
211    unsafe fn links(target: NonNull<Self>) -> NonNull<Links<SocketHeader>> {
212        // Safety: using `ptr::addr_of!` avoids creating a temporary
213        // reference, which stacked borrows dislikes.
214        let node = unsafe { ptr::addr_of_mut!((*target.as_ptr()).links) };
215        unsafe { NonNull::new_unchecked(node) }
216    }
217}
218
219impl SocketSendError {
220    pub fn to_error(&self) -> ProtocolError {
221        match self {
222            SocketSendError::NoSpace => ProtocolError::SSE_NO_SPACE,
223            SocketSendError::DeserFailed => ProtocolError::SSE_DESER_FAILED,
224            SocketSendError::TypeMismatch => ProtocolError::SSE_TYPE_MISMATCH,
225            SocketSendError::WhatTheHell => ProtocolError::SSE_WHAT_THE_HELL,
226        }
227    }
228}