1use heapless::{Deque, Vec};
2
3use crate::{
4 wire::WIRE_HEADER_LENGTH, LinkState, MiniError, MiniMessage, MiniResult, MiniStore,
5 MiniTransport, HASH_LENGTH,
6};
7
8const RECENT_REPLAY_SOURCES: usize = 8;
9
10#[derive(Debug, Clone, Copy, Eq, PartialEq)]
11pub struct MiniRuntimeConfig {
12 pub local_identity: [u8; HASH_LENGTH],
13}
14
15#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
16pub struct MiniRuntimeStats {
17 pub queued: u32,
18 pub sent: u32,
19 pub deferred: u32,
20 pub received: u32,
21 pub replay_rejected: u32,
22 pub event_overflows: u32,
23}
24
25#[derive(Debug, Clone, Copy, Eq, PartialEq)]
26pub struct RuntimeStatus {
27 pub outbound_len: usize,
28 pub event_len: usize,
29 pub replay_floor: u64,
30 pub stats: MiniRuntimeStats,
31}
32
33#[derive(Debug, Clone, Copy, Eq, PartialEq)]
34pub enum MiniEvent {
35 Bootstrapped { replay_floor: u64 },
36 MessageQueued { sequence: u64, bytes: usize },
37 FrameSent { sequence: u64, bytes: usize },
38 FrameDeferred { sequence: u64, error: MiniError },
39 MessageReceived { sequence: u64, source: [u8; HASH_LENGTH], bytes: usize },
40 FrameRejected { sequence: u64, error: MiniError },
41}
42
43#[derive(Debug, Clone, Eq, PartialEq)]
44struct OutboundFrame<const FRAME: usize> {
45 sequence: u64,
46 bytes: Vec<u8, FRAME>,
47}
48
49#[derive(Debug, Clone, Copy, Eq, PartialEq)]
50struct ReplaySourceFloor {
51 source: [u8; HASH_LENGTH],
52 floor: u64,
53}
54
55#[derive(Debug, Clone)]
56pub struct MiniNodeRuntime<
57 const TITLE: usize,
58 const CONTENT: usize,
59 const FRAME: usize,
60 const OUTBOUND: usize,
61 const EVENTS: usize,
62> {
63 config: MiniRuntimeConfig,
64 replay_floor: u64,
65 next_sequence: u64,
66 bootstrapped: bool,
67 outbound: Deque<OutboundFrame<FRAME>, OUTBOUND>,
68 events: Deque<MiniEvent, EVENTS>,
69 replay_source_floors: Vec<ReplaySourceFloor, RECENT_REPLAY_SOURCES>,
70 stats: MiniRuntimeStats,
71}
72
73impl<
74 const TITLE: usize,
75 const CONTENT: usize,
76 const FRAME: usize,
77 const OUTBOUND: usize,
78 const EVENTS: usize,
79 > MiniNodeRuntime<TITLE, CONTENT, FRAME, OUTBOUND, EVENTS>
80{
81 pub fn new(config: MiniRuntimeConfig) -> MiniResult<Self> {
82 if config.local_identity == [0; HASH_LENGTH] || OUTBOUND == 0 || EVENTS == 0 {
83 return Err(MiniError::InvalidInput);
84 }
85 Ok(Self {
86 config,
87 replay_floor: 0,
88 next_sequence: 1,
89 bootstrapped: false,
90 outbound: Deque::new(),
91 events: Deque::new(),
92 replay_source_floors: Vec::new(),
93 stats: MiniRuntimeStats::default(),
94 })
95 }
96
97 pub fn queue_message(
98 &mut self,
99 destination: [u8; HASH_LENGTH],
100 title: &[u8],
101 content: &[u8],
102 signature: [u8; 64],
103 timestamp: f64,
104 ) -> MiniResult<u64> {
105 if self.outbound.len() >= OUTBOUND {
106 return Err(MiniError::Backpressure);
107 }
108 self.ensure_event_capacity()?;
109
110 let sequence = self.next_sequence;
111 let message = MiniMessage::<TITLE, CONTENT>::new(
112 destination,
113 self.config.local_identity,
114 signature,
115 timestamp,
116 title,
117 content,
118 )?;
119 let mut bytes = Vec::<u8, FRAME>::new();
120 bytes.resize_default(message.encoded_len()?).map_err(|_| MiniError::CapacityExceeded)?;
121 let written = message.encode(&mut bytes)?;
122 bytes.truncate(written);
123 let queued_bytes = bytes.len();
124 self.outbound
125 .push_back(OutboundFrame { sequence, bytes })
126 .map_err(|_| MiniError::Backpressure)?;
127 self.next_sequence = self.next_sequence.saturating_add(1);
128 self.stats.queued = self.stats.queued.saturating_add(1);
129 self.push_event(MiniEvent::MessageQueued { sequence, bytes: queued_bytes })?;
130 Ok(sequence)
131 }
132
133 pub fn tick<T: MiniTransport, S: MiniStore>(
134 &mut self,
135 transport: &mut T,
136 store: &mut S,
137 scratch: &mut [u8],
138 ) -> MiniResult<()> {
139 if !self.bootstrapped {
140 self.replay_floor = store.load_replay_floor(&self.config.local_identity)?;
141 self.bootstrapped = true;
142 self.push_event(MiniEvent::Bootstrapped { replay_floor: self.replay_floor })?;
143 }
144
145 self.poll_inbound(transport, store, scratch)?;
146 self.flush_outbound(transport)?;
147 Ok(())
148 }
149
150 pub fn poll_event(&mut self) -> Option<MiniEvent> {
151 self.events.pop_front()
152 }
153
154 pub fn status(&self) -> RuntimeStatus {
155 RuntimeStatus {
156 outbound_len: self.outbound.len(),
157 event_len: self.events.len(),
158 replay_floor: self.replay_floor,
159 stats: self.stats,
160 }
161 }
162
163 fn poll_inbound<T: MiniTransport, S: MiniStore>(
164 &mut self,
165 transport: &mut T,
166 store: &mut S,
167 scratch: &mut [u8],
168 ) -> MiniResult<()> {
169 while let Some(len) = transport.poll_frame(scratch)? {
170 let message = MiniMessage::<TITLE, CONTENT>::decode(&scratch[..len])?;
171 let sequence = message_sequence(&message);
172 if sequence <= self.replay_floor_for(&message.source) {
173 self.stats.replay_rejected = self.stats.replay_rejected.saturating_add(1);
174 self.push_event(MiniEvent::FrameRejected {
175 sequence,
176 error: MiniError::ReplayRejected,
177 })?;
178 continue;
179 }
180
181 self.note_replay_floor(message.source, sequence);
182 if sequence > self.replay_floor {
183 self.replay_floor = sequence;
184 store.save_replay_floor(&self.config.local_identity, self.replay_floor)?;
187 }
188 self.stats.received = self.stats.received.saturating_add(1);
189 self.push_event(MiniEvent::MessageReceived {
190 sequence,
191 source: message.source,
192 bytes: message.content.len(),
193 })?;
194 }
195 Ok(())
196 }
197
198 fn flush_outbound<T: MiniTransport>(&mut self, transport: &mut T) -> MiniResult<()> {
199 if transport.link_state() != LinkState::Up {
200 return Ok(());
201 }
202
203 while let Some(frame) = self.outbound.front() {
204 let sequence = frame.sequence;
205 match transport.send_frame(&frame.bytes) {
206 Ok(()) => {
207 let sent = self.outbound.pop_front().ok_or(MiniError::InvalidInput)?;
208 self.stats.sent = self.stats.sent.saturating_add(1);
209 self.push_event(MiniEvent::FrameSent {
210 sequence: sent.sequence,
211 bytes: sent.bytes.len(),
212 })?;
213 }
214 Err(error @ (MiniError::Backpressure | MiniError::Disconnected)) => {
215 self.stats.deferred = self.stats.deferred.saturating_add(1);
216 self.push_event(MiniEvent::FrameDeferred { sequence, error })?;
217 break;
218 }
219 Err(error) => {
220 let dropped = self.outbound.pop_front().ok_or(MiniError::InvalidInput)?;
221 self.stats.deferred = self.stats.deferred.saturating_add(1);
222 self.push_event(MiniEvent::FrameDeferred {
223 sequence: dropped.sequence,
224 error,
225 })?;
226 }
227 }
228 }
229 Ok(())
230 }
231
232 fn ensure_event_capacity(&mut self) -> MiniResult<()> {
233 if self.events.len() >= EVENTS {
234 self.stats.event_overflows = self.stats.event_overflows.saturating_add(1);
235 return Err(MiniError::EventOverflow);
236 }
237 Ok(())
238 }
239
240 fn push_event(&mut self, event: MiniEvent) -> MiniResult<()> {
241 self.ensure_event_capacity()?;
242 self.events.push_back(event).map_err(|_| MiniError::EventOverflow)
243 }
244
245 fn replay_floor_for(&self, source: &[u8; HASH_LENGTH]) -> u64 {
246 self.replay_source_floors
247 .iter()
248 .find(|entry| &entry.source == source)
249 .map(|entry| entry.floor)
250 .unwrap_or(0)
251 }
252
253 fn note_replay_floor(&mut self, source: [u8; HASH_LENGTH], floor: u64) {
254 if let Some(entry) =
255 self.replay_source_floors.iter_mut().find(|entry| entry.source == source)
256 {
257 entry.floor = floor;
258 return;
259 }
260
261 if self.replay_source_floors.len() >= RECENT_REPLAY_SOURCES {
262 self.replay_source_floors.remove(0);
263 }
264 let _ = self.replay_source_floors.push(ReplaySourceFloor { source, floor });
265 }
266}
267
268fn message_sequence<const TITLE: usize, const CONTENT: usize>(
269 message: &MiniMessage<TITLE, CONTENT>,
270) -> u64 {
271 message.timestamp
272}
273
274pub const fn minimum_frame_capacity(title: usize, content: usize) -> usize {
275 WIRE_HEADER_LENGTH + 1 + 9 + 5 + title + 5 + content + 1
276}