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}