1use umsh_core::PublicKey;
9
10use crate::model::{ConversationKey, SenderScope};
11
12pub fn serial_delta(last: u8, id: u8) -> u8 {
16 id.wrapping_sub(last)
17}
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub enum SerialClass {
22 Baseline,
24 Newer(u8),
26 Older(u8),
28 Ambiguous,
30}
31
32pub fn classify(last: u8, id: u8) -> SerialClass {
33 match serial_delta(last, id) {
34 0 => SerialClass::Baseline,
35 delta @ 1..=127 => SerialClass::Newer(delta),
36 128 => SerialClass::Ambiguous,
37 delta => SerialClass::Older(delta.wrapping_neg()),
38 }
39}
40
41#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
43pub struct MessageHandle(pub u32);
44
45#[derive(Clone, Debug, Default)]
48pub struct RefRing {
49 entries: heapless::Vec<(u8, MessageHandle), 16>,
50}
51
52impl RefRing {
53 pub fn clear(&mut self) {
54 self.entries.clear();
55 }
56
57 pub fn record(&mut self, id: u8, handle: MessageHandle) {
60 self.entries.retain(|(entry_id, _)| *entry_id != id);
61 if self.entries.is_full() {
62 self.entries.remove(0);
63 }
64 let _ = self.entries.push((id, handle));
65 }
66
67 pub fn lookup(&self, id: u8) -> Option<MessageHandle> {
68 self.entries
69 .iter()
70 .find(|(entry_id, _)| *entry_id == id)
71 .map(|(_, handle)| *handle)
72 }
73
74 pub fn lookup_handle(&self, handle: MessageHandle) -> Option<u8> {
75 self.entries
76 .iter()
77 .find(|(_, entry)| *entry == handle)
78 .map(|(id, _)| *id)
79 }
80
81 pub fn retire(&mut self, id: u8) {
83 self.entries.retain(|(entry_id, _)| *entry_id != id);
84 }
85}
86
87#[derive(Clone, Debug, Default)]
95pub struct EditRing {
96 entries: heapless::Vec<(u8, u8), 8>,
97}
98
99impl EditRing {
100 pub fn clear(&mut self) {
101 self.entries.clear();
102 }
103
104 pub fn record(&mut self, edit_id: u8, original_id: u8) {
107 let original_id = self.resolve(original_id);
108 if edit_id == original_id {
109 return;
110 }
111 self.entries.retain(|(entry_id, _)| *entry_id != edit_id);
112 if self.entries.is_full() {
113 self.entries.remove(0);
114 }
115 let _ = self.entries.push((edit_id, original_id));
116 }
117
118 pub fn resolve(&self, id: u8) -> u8 {
120 self.entries
121 .iter()
122 .find(|(entry_id, _)| *entry_id == id)
123 .map(|(_, original)| *original)
124 .unwrap_or(id)
125 }
126
127 pub fn retire(&mut self, id: u8) {
129 self.entries
130 .retain(|(edit_id, original_id)| *edit_id != id && *original_id != id);
131 }
132}
133
134#[derive(Clone, Debug, Default)]
136pub struct SeenWindow {
137 bits: [u32; 8],
138}
139
140impl SeenWindow {
141 pub fn clear(&mut self) {
142 self.bits = [0; 8];
143 }
144
145 pub fn contains(&self, id: u8) -> bool {
146 self.bits[(id >> 5) as usize] & (1 << (id & 31)) != 0
147 }
148
149 pub fn insert(&mut self, id: u8) {
150 self.bits[(id >> 5) as usize] |= 1 << (id & 31);
151 }
152
153 fn remove(&mut self, id: u8) {
154 self.bits[(id >> 5) as usize] &= !(1 << (id & 31));
155 }
156
157 pub fn advance(&mut self, last: u8, delta: u8) {
161 for step in 1..=delta {
162 self.remove(last.wrapping_add(step));
163 }
164 }
165}
166
167#[derive(Clone, Copy, Debug, PartialEq, Eq)]
169pub struct PendingRepair {
170 pub message_id: u8,
171 pub fragment: Option<u8>,
173 pub deadline_ms: u64,
175 pub attempts: u8,
176 pub handle: Option<MessageHandle>,
180}
181
182#[derive(Clone, Debug)]
184pub struct InboundStream {
185 pub epoch: u16,
188 pub baseline: Option<u8>,
190 pub seen: SeenWindow,
191 pub refs: RefRing,
192 pub edit_refs: EditRing,
194 pub pending: heapless::Vec<PendingRepair, 8>,
195 pub sender_key: Option<PublicKey>,
198 pub collided: bool,
201 pub last_request_ms: u64,
202 pub last_active_ms: u64,
203}
204
205impl InboundStream {
206 pub fn new(now_ms: u64) -> Self {
207 Self {
208 epoch: 0,
209 baseline: None,
210 seen: SeenWindow::default(),
211 refs: RefRing::default(),
212 edit_refs: EditRing::default(),
213 pending: heapless::Vec::new(),
214 sender_key: None,
215 collided: false,
216 last_request_ms: 0,
217 last_active_ms: now_ms,
218 }
219 }
220
221 pub fn reset_epoch(&mut self, new_baseline: Option<u8>) {
224 self.epoch = self.epoch.wrapping_add(1);
225 self.baseline = new_baseline;
226 self.seen.clear();
227 self.refs.clear();
228 self.edit_refs.clear();
229 self.pending.clear();
230 if let Some(id) = new_baseline {
231 self.seen.insert(id);
232 }
233 }
234
235 pub fn cancel_pending(&mut self, message_id: u8, fragment: Option<u8>) {
240 self.pending.retain(|pending| {
241 if pending.message_id != message_id {
242 return true;
243 }
244 match (fragment, pending.fragment) {
245 (None, _) | (Some(_), None) => false,
246 (Some(arrived), Some(wanted)) => wanted != arrived,
247 }
248 });
249 }
250}
251
252#[derive(Clone, Debug)]
254pub struct OutboundStream {
255 pub next_id: u8,
256 pub epoch: u16,
257 pub announce_reset: bool,
259 pub refs: RefRing,
260 pub edit_refs: EditRing,
263 pub last_active_ms: u64,
264}
265
266impl OutboundStream {
267 pub fn fresh(now_ms: u64) -> Self {
268 Self {
269 next_id: 0,
270 epoch: 0,
271 announce_reset: true,
272 refs: RefRing::default(),
273 edit_refs: EditRing::default(),
274 last_active_ms: now_ms,
275 }
276 }
277
278 pub fn allocate(&mut self) -> u8 {
280 let id = self.next_id;
281 self.next_id = self.next_id.wrapping_add(1);
282 self.refs.retire(id);
283 self.edit_refs.retire(id);
284 id
285 }
286}
287
288#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
290pub struct StreamKey {
291 pub conversation: ConversationKey,
292 pub sender: SenderScope,
293}