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