secure_serial/resources.rs
1//! Owned queues and buffer pools for one side of a secure-serial link.
2//!
3//! This module is described in detail on [`SecureSerialResources`]; see the crate README for how
4//! tasks and [`crate::SecureSerialSender`] connect to the channels here.
5
6use embassy_sync::{blocking_mutex::raw::RawMutex, channel};
7use embedded_buffer_pool::{BufferGuard, BufferPool, MappedBufferGuard};
8use heapless::Vec;
9
10use crate::protocol::{Ack, CHUNK_LEN_MAX};
11
12/// Queues and pools for [`crate::run_read`], [`crate::run_write`], and [`crate::SecureSerialSender`]
13/// on a single endpoint (one UART side).
14///
15/// This type groups the TX chunk pool, TX/RX [`embassy_sync::channel::Channel`]s, and the RX
16/// reassembly pool so you do not wire six separate static items by hand.
17///
18/// # Sizes
19///
20/// - `N_INFLIGHT`: capacity of the outgoing chunk queue, TX [`BufferPool`], and ACK channels (same as
21/// in-flight chunk/ACK window); must be in 1..=32.
22/// - `N_RX_POOL`: number of parallel RX packet buffers and RX completion queue depth (1..=32).
23/// - `N_BUF`: byte length of each RX reassembly buffer (must fit your largest packet).
24///
25/// [`BufferPool`] and channels require fixed sizes in that range.
26///
27/// # `static` placement
28///
29/// [`embedded_buffer_pool::BufferPool`] only exposes `take` / `try_take` on `&'static` pools.
30/// Put this struct in a `static_cell::StaticCell` (or another `'static` slot) so references
31/// passed to [`crate::SecureSerialSender::new`] and async tasks are valid for the whole program.
32///
33/// # Example (Cortex-M / `no_std`)
34///
35/// Store the bundle once in a `static_cell::StaticCell`, then reborrow it as a shared
36/// `&'static SecureSerialResources<...>` so every task can hold a copy of the pointer (the
37/// mutex-backed channels inside are safe to share). Use [`SecureSerialResourcesDefault`] if the
38/// default sizes (8 TX slots, 4 × 4096-byte RX buffers) fit your link.
39///
40/// ```ignore
41/// use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
42/// use embassy_executor::Spawner;
43/// use static_cell::StaticCell;
44/// use secure_serial::{SecureSerialResources, SecureSerialResourcesDefault};
45///
46/// static SERIAL_RES: StaticCell<SecureSerialResourcesDefault<CriticalSectionRawMutex>> =
47/// StaticCell::new();
48///
49/// fn init_serial_tasks(spawner: Spawner) {
50/// let res_mut = SERIAL_RES.init(SecureSerialResourcesDefault::new());
51/// let res: &'static SecureSerialResourcesDefault<CriticalSectionRawMutex> = res_mut;
52///
53/// // `tx_pool()` and channel endpoints are valid for `'static` because `res` is.
54/// let mut tx_rx = res.tx_chunks_receiver();
55/// let mut acks_out_rx = res.acks_to_wire_receiver();
56/// unwrap!(spawner.spawn(run_write_task(&mut tx_rx, &mut acks_out_rx)));
57/// unwrap!(spawner.spawn(run_read_task(res)));
58/// unwrap!(spawner.spawn(app_task(res)));
59/// }
60///
61/// #[embassy_executor::task]
62/// async fn run_write_task(
63/// tx_rx: &mut /* `Receiver` for DATA chunks from `tx_chunks_receiver` */,
64/// acks_out_rx: &mut /* `Receiver` for ACKs from `acks_to_wire_receiver` */,
65/// ) {
66/// let _ = secure_serial::run_write(&mut /* uart */, tx_rx, acks_out_rx, &mut /* crc */).await;
67/// }
68/// ```
69pub struct SecureSerialResources<
70 M: RawMutex + 'static,
71 const N_INFLIGHT: usize,
72 const N_RX_POOL: usize,
73 const N_BUF: usize,
74> {
75 tx_pool: BufferPool<M, Vec<u8, CHUNK_LEN_MAX>, N_INFLIGHT>,
76 tx_chunks: channel::Channel<M, BufferGuard<M, Vec<u8, CHUNK_LEN_MAX>>, N_INFLIGHT>,
77 acks_to_send: channel::Channel<M, Ack, N_INFLIGHT>,
78 acks_received: channel::Channel<M, Ack, N_INFLIGHT>,
79 rx_pool: BufferPool<M, [u8; N_BUF], N_RX_POOL>,
80 rx_complete: channel::Channel<M, MappedBufferGuard<M, [u8]>, N_RX_POOL>,
81}
82
83/// Common defaults: 8 TX slots, 4 RX buffers, 4096 bytes per RX buffer.
84pub type SecureSerialResourcesDefault<M> = SecureSerialResources<M, 8, 4, 4096>;
85
86impl<M: RawMutex + 'static, const N_INFLIGHT: usize, const N_RX_POOL: usize, const N_BUF: usize>
87 SecureSerialResources<M, N_INFLIGHT, N_RX_POOL, N_BUF>
88{
89 /// Builds pools and channels; all buffers start empty and channels empty.
90 ///
91 /// # Panics
92 ///
93 /// If `N_INFLIGHT` or `N_RX_POOL` is not in `1..=32`.
94 pub fn new() -> Self {
95 assert!(N_INFLIGHT > 0 && N_INFLIGHT <= 32);
96 assert!(N_RX_POOL > 0 && N_RX_POOL <= 32);
97
98 let tx_backing = core::array::from_fn(|_| Vec::new());
99 let tx_pool = BufferPool::new(tx_backing);
100 let tx_chunks = channel::Channel::new();
101 let acks_to_send = channel::Channel::new();
102 let acks_received = channel::Channel::new();
103 let rx_backing = core::array::from_fn(|_| [0u8; N_BUF]);
104 let rx_pool = BufferPool::new(rx_backing);
105 let rx_complete = channel::Channel::new();
106 Self {
107 tx_pool,
108 tx_chunks,
109 acks_to_send,
110 acks_received,
111 rx_pool,
112 rx_complete,
113 }
114 }
115
116 /// Pool for outbound DATA chunks; pass to [`crate::SecureSerialSender::new`] when `self` is `'static`.
117 #[inline]
118 pub fn tx_pool(&self) -> &BufferPool<M, Vec<u8, CHUNK_LEN_MAX>, N_INFLIGHT> {
119 &self.tx_pool
120 }
121
122 /// Sender for encoded DATA chunks consumed by [`crate::run_write`].
123 #[inline]
124 pub fn tx_chunks_sender(
125 &self,
126 ) -> channel::Sender<'_, M, BufferGuard<M, Vec<u8, CHUNK_LEN_MAX>>, N_INFLIGHT> {
127 self.tx_chunks.sender()
128 }
129
130 /// Receiver used by [`crate::run_write`] for pending DATA frames.
131 #[inline]
132 pub fn tx_chunks_receiver(
133 &self,
134 ) -> channel::Receiver<'_, M, BufferGuard<M, Vec<u8, CHUNK_LEN_MAX>>, N_INFLIGHT> {
135 self.tx_chunks.receiver()
136 }
137
138 /// ACKs generated locally that must be written to the wire (feed [`crate::run_write`]).
139 #[inline]
140 pub fn acks_to_wire_sender(&self) -> channel::Sender<'_, M, Ack, N_INFLIGHT> {
141 self.acks_to_send.sender()
142 }
143
144 #[inline]
145 pub fn acks_to_wire_receiver(&self) -> channel::Receiver<'_, M, Ack, N_INFLIGHT> {
146 self.acks_to_send.receiver()
147 }
148
149 /// ACKs received from the peer (feed [`crate::SecureSerialSender::new`] as `rx_confirm`).
150 #[inline]
151 pub fn acks_from_peer_sender(&self) -> channel::Sender<'_, M, Ack, N_INFLIGHT> {
152 self.acks_received.sender()
153 }
154
155 #[inline]
156 pub fn acks_from_peer_receiver(&self) -> channel::Receiver<'_, M, Ack, N_INFLIGHT> {
157 self.acks_received.receiver()
158 }
159
160 /// Pool for partially reassembled RX packets; pass to [`crate::run_read`] when `self` is `'static`.
161 #[inline]
162 pub fn rx_pool(&self) -> &BufferPool<M, [u8; N_BUF], N_RX_POOL> {
163 &self.rx_pool
164 }
165
166 /// Completed packets from [`crate::run_read`].
167 #[inline]
168 pub fn rx_complete_sender(
169 &self,
170 ) -> channel::Sender<'_, M, MappedBufferGuard<M, [u8]>, N_RX_POOL> {
171 self.rx_complete.sender()
172 }
173
174 #[inline]
175 pub fn rx_complete_receiver(
176 &self,
177 ) -> channel::Receiver<'_, M, MappedBufferGuard<M, [u8]>, N_RX_POOL> {
178 self.rx_complete.receiver()
179 }
180}