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