1use core::time::Duration;
8
9use buggy::Bug;
10use heapless::Vec;
11use postcard::Error as PostcardError;
12use serde::{Deserialize, Serialize};
13
14use crate::{
15 Address, Prior,
16 command::{CmdId, Command, Priority},
17 storage::{GraphId, LocatedAddress, Location, MAX_COMMAND_LENGTH, StorageError},
18};
19
20mod requester;
21mod responder;
22pub(crate) mod wire;
23
24pub(crate) use requester::SyncRequestMessage;
25pub use requester::SyncRequester;
26use responder::SyncResponseMessage;
27pub use responder::{PeerCache, SyncResponder};
28use wire::{SubscribeResult, SyncHelloType, SyncType};
29
30pub struct SessionHeads<'a> {
38 pub(crate) cache: &'a PeerCache,
39 pub(crate) session: Option<&'a alloc::collections::BTreeMap<CmdId, Location>>,
40}
41
42impl SessionHeads<'_> {
43 pub(crate) fn session_iter(&self) -> impl Iterator<Item = LocatedAddress> + '_ {
45 self.session
46 .into_iter()
47 .flatten()
48 .map(|(id, loc)| LocatedAddress {
49 id: *id,
50 segment: loc.segment,
51 max_cut: loc.max_cut,
52 })
53 }
54
55 pub(crate) fn cache_heads(&self) -> &[LocatedAddress] {
57 self.cache.heads()
58 }
59}
60
61impl PeerCache {
62 pub fn session_heads(&self) -> SessionHeads<'_> {
64 SessionHeads {
65 cache: self,
66 session: None,
67 }
68 }
69}
70
71pub const PEER_HEAD_MAX: usize = 10;
75
76#[cfg(feature = "low-mem-usage")]
78const COMMAND_SAMPLE_MAX: usize = 20;
79#[cfg(not(feature = "low-mem-usage"))]
80const COMMAND_SAMPLE_MAX: usize = 100;
81
82#[cfg(feature = "low-mem-usage")]
85const REQUEST_MISSING_MAX: usize = 1;
86#[cfg(not(feature = "low-mem-usage"))]
87const REQUEST_MISSING_MAX: usize = 100;
88
89#[cfg(feature = "low-mem-usage")]
91pub const COMMAND_RESPONSE_MAX: usize = 5;
92#[cfg(not(feature = "low-mem-usage"))]
93pub const COMMAND_RESPONSE_MAX: usize = 100;
94
95#[cfg(feature = "low-mem-usage")]
97const SEGMENT_BUFFER_MAX: usize = 10;
98#[cfg(not(feature = "low-mem-usage"))]
99const SEGMENT_BUFFER_MAX: usize = 100;
100
101pub const MAX_SYNC_MESSAGE_SIZE: usize = 1024 + MAX_COMMAND_LENGTH * COMMAND_RESPONSE_MAX;
105
106#[derive(Debug, thiserror::Error)]
108#[non_exhaustive]
109pub enum SyncError {
110 #[error("sync session ID does not match")]
111 SessionMismatch,
112 #[error("missing sync response")]
113 MissingSyncResponse,
114 #[error("syncer state not valid for this message")]
115 SessionState,
116 #[error("syncer not ready for operation")]
117 NotReady,
118 #[error("too many commands sent")]
119 CommandOverflow,
120 #[error("target buffer too small for sync message")]
121 BufferTooSmall,
122 #[error("malformed sync response")]
123 MalformedResponse,
124 #[error("unsupported sync request")]
125 UnsupportedRequest,
126 #[error("storage error: {0}")]
127 Storage(#[from] StorageError),
128 #[error("serialize error: {0}")]
129 Serialize(#[from] PostcardError),
130 #[error(transparent)]
131 Bug(#[from] Bug),
132}
133
134#[derive(Serialize, Deserialize, Debug)]
136pub struct SyncCommand<'a> {
137 priority: Priority,
138 id: CmdId,
139 parent: Prior<Address>,
140 policy: Option<&'a [u8]>,
141 data: &'a [u8],
142}
143
144impl<'a> Command for SyncCommand<'a> {
145 fn priority(&self) -> Priority {
146 self.priority.clone()
147 }
148
149 fn id(&self) -> CmdId {
150 self.id
151 }
152
153 fn parent(&self) -> Prior<Address> {
154 self.parent
155 }
156
157 fn policy(&self) -> Option<&'a [u8]> {
158 self.policy
159 }
160
161 fn bytes(&self) -> &'a [u8] {
162 self.data
163 }
164}
165
166#[allow(clippy::large_enum_variant)]
174pub enum SyncIncoming<'a> {
175 Poll(PollIncoming),
177 Subscribe(SubscribeIncoming),
179 Unsubscribe(UnsubscribeIncoming),
181 Push(PushIncoming<'a>),
183 Hello(SyncHello),
185}
186
187impl<'a> SyncIncoming<'a> {
188 pub fn decode(data: &'a [u8]) -> Result<Self, SyncError> {
190 let (sync_type, remaining) = postcard::take_from_bytes::<SyncType>(data)?;
191 Ok(match sync_type {
192 SyncType::Poll { request } => Self::Poll(PollIncoming {
193 session_id: request.session_id(),
194 message: request,
195 }),
196 SyncType::Subscribe {
197 remain_open,
198 max_bytes,
199 commands,
200 graph_id,
201 } => Self::Subscribe(SubscribeIncoming {
202 graph_id,
203 remain_open,
204 max_bytes,
205 heads: SyncHeads { inner: commands },
206 }),
207 SyncType::Unsubscribe { graph_id } => {
208 Self::Unsubscribe(UnsubscribeIncoming { graph_id })
209 }
210 SyncType::Push { message, graph_id } => Self::Push(PushIncoming {
211 graph_id,
212 session_id: message.session_id(),
213 message,
214 command_data: remaining,
215 }),
216 SyncType::Hello(hello) => Self::Hello(hello.into()),
217 })
218 }
219}
220
221pub struct SyncHeads {
223 inner: Vec<Address, COMMAND_SAMPLE_MAX>,
224}
225
226impl SyncHeads {
227 pub fn as_slice(&self) -> &[Address] {
229 &self.inner
230 }
231
232 pub fn iter(&self) -> impl DoubleEndedIterator<Item = Address> + ExactSizeIterator + '_ {
234 self.inner.iter().copied()
235 }
236}
237
238#[derive(Debug)]
240pub enum SyncHello {
241 Subscribe(HelloSubscribe),
243 Unsubscribe(HelloUnsubscribe),
245 Hello(HelloNotification),
247}
248
249impl From<SyncHelloType> for SyncHello {
250 fn from(t: SyncHelloType) -> Self {
251 match t {
252 SyncHelloType::Subscribe {
253 graph_id,
254 graph_change_delay,
255 duration,
256 schedule_delay,
257 } => Self::Subscribe(HelloSubscribe {
258 graph_id,
259 graph_change_delay,
260 duration,
261 schedule_delay,
262 }),
263 SyncHelloType::Unsubscribe { graph_id } => {
264 Self::Unsubscribe(HelloUnsubscribe { graph_id })
265 }
266 SyncHelloType::Hello { graph_id, head } => {
267 Self::Hello(HelloNotification { graph_id, head })
268 }
269 }
270 }
271}
272
273#[derive(Debug)]
275pub struct HelloSubscribe {
276 graph_id: GraphId,
277 graph_change_delay: Duration,
278 duration: Duration,
279 schedule_delay: Duration,
280}
281
282impl HelloSubscribe {
283 pub fn graph_id(&self) -> GraphId {
285 self.graph_id
286 }
287
288 pub fn graph_change_delay(&self) -> Duration {
290 self.graph_change_delay
291 }
292
293 pub fn duration(&self) -> Duration {
295 self.duration
296 }
297
298 pub fn schedule_delay(&self) -> Duration {
300 self.schedule_delay
301 }
302}
303
304#[derive(Debug)]
306pub struct HelloUnsubscribe {
307 graph_id: GraphId,
308}
309
310impl HelloUnsubscribe {
311 pub fn graph_id(&self) -> GraphId {
313 self.graph_id
314 }
315}
316
317#[derive(Debug)]
319pub struct HelloNotification {
320 graph_id: GraphId,
321 head: Address,
322}
323
324impl HelloNotification {
325 pub fn graph_id(&self) -> GraphId {
327 self.graph_id
328 }
329
330 pub fn head(&self) -> Address {
332 self.head
333 }
334}
335
336pub struct PollIncoming {
339 session_id: u128,
340 pub(crate) message: SyncRequestMessage,
341}
342
343impl PollIncoming {
344 pub fn session_id(&self) -> u128 {
346 self.session_id
347 }
348}
349
350pub struct SubscribeIncoming {
352 graph_id: GraphId,
353 remain_open: u64,
354 max_bytes: u64,
355 heads: SyncHeads,
356}
357
358impl SubscribeIncoming {
359 pub fn graph_id(&self) -> GraphId {
361 self.graph_id
362 }
363
364 pub fn remain_open(&self) -> Duration {
366 Duration::from_secs(self.remain_open)
367 }
368
369 pub fn max_bytes(&self) -> u64 {
371 self.max_bytes
372 }
373
374 pub fn heads(&self) -> &SyncHeads {
376 &self.heads
377 }
378}
379
380pub struct UnsubscribeIncoming {
382 graph_id: GraphId,
383}
384
385impl UnsubscribeIncoming {
386 pub fn graph_id(&self) -> GraphId {
388 self.graph_id
389 }
390}
391
392pub struct PushIncoming<'a> {
395 graph_id: GraphId,
396 session_id: u128,
397 pub(crate) message: SyncResponseMessage,
398 pub(crate) command_data: &'a [u8],
399}
400
401impl PushIncoming<'_> {
402 pub fn graph_id(&self) -> GraphId {
404 self.graph_id
405 }
406
407 pub fn session_id(&self) -> u128 {
409 self.session_id
410 }
411}
412
413#[derive(Debug)]
416pub enum SubscribeResponse {
417 Success,
419 TooManySubscriptions,
421}
422
423impl SubscribeResponse {
424 pub fn encode_to(self, target: &mut [u8]) -> Result<usize, SyncError> {
426 let inner = match self {
427 Self::Success => SubscribeResult::Success,
428 Self::TooManySubscriptions => SubscribeResult::TooManySubscriptions,
429 };
430 Ok(postcard::to_slice(&inner, target)?.len())
431 }
432
433 pub fn decode(data: &[u8]) -> Result<Self, SyncError> {
435 let inner: SubscribeResult = postcard::from_bytes(data)?;
436 Ok(match inner {
437 SubscribeResult::Success => Self::Success,
438 SubscribeResult::TooManySubscriptions => Self::TooManySubscriptions,
439 })
440 }
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446 use crate::command::Priority;
447
448 fn meta(policy_length: u32, length: u32) -> wire::CommandMeta {
449 wire::CommandMeta {
450 id: CmdId::default(),
451 priority: Priority::Basic(0),
452 parent: Prior::None,
453 policy_length,
454 length,
455 }
456 }
457
458 #[test]
461 fn truncated_sync_response_is_rejected() {
462 let session_id = 42;
463 let mut commands: Vec<wire::CommandMeta, COMMAND_RESPONSE_MAX> = Vec::new();
464 commands.push(meta(0, 4096)).expect("push meta");
466 let message = SyncResponseMessage::SyncResponse {
467 session_id,
468 response_index: 0,
469 commands,
470 };
471 let mut buf = [0u8; MAX_SYNC_MESSAGE_SIZE];
472 let len = postcard::to_slice(&message, &mut buf)
473 .expect("serialize")
474 .len();
475
476 let mut requester = SyncRequester::new_session_id(GraphId::default(), session_id);
477 let err = requester.receive(&buf[..len]).expect_err("must not panic");
478 assert!(matches!(err, SyncError::MalformedResponse), "got {err:?}");
479 }
480
481 #[test]
483 fn truncated_policy_is_rejected() {
484 let session_id = 43;
485 let mut commands: Vec<wire::CommandMeta, COMMAND_RESPONSE_MAX> = Vec::new();
486 commands.push(meta(4096, 0)).expect("push meta");
487 let message = SyncResponseMessage::SyncResponse {
488 session_id,
489 response_index: 0,
490 commands,
491 };
492 let mut buf = [0u8; MAX_SYNC_MESSAGE_SIZE];
493 let len = postcard::to_slice(&message, &mut buf)
494 .expect("serialize")
495 .len();
496
497 let mut requester = SyncRequester::new_session_id(GraphId::default(), session_id);
498 let err = requester.receive(&buf[..len]).expect_err("must not panic");
499 assert!(matches!(err, SyncError::MalformedResponse), "got {err:?}");
500 }
501
502 fn poll_bytes(request: SyncRequestMessage, buf: &mut [u8]) -> usize {
503 postcard::to_slice(&SyncType::Poll { request }, buf)
504 .expect("serialize")
505 .len()
506 }
507
508 #[test]
511 fn unimplemented_requests_are_rejected() {
512 let session_id = 44;
513 let unsupported = [
514 SyncRequestMessage::RequestMissing {
515 session_id,
516 indexes: Vec::new(),
517 },
518 SyncRequestMessage::SyncResume {
519 session_id,
520 response_index: 0,
521 max_bytes: 0,
522 },
523 ];
524
525 for request in unsupported {
526 let mut buf = [0u8; MAX_SYNC_MESSAGE_SIZE];
527 let len = poll_bytes(request, &mut buf);
528 let SyncIncoming::Poll(poll) = SyncIncoming::decode(&buf[..len]).expect("decode")
529 else {
530 panic!("expected a poll");
531 };
532
533 let mut responder = SyncResponder::new();
534 let err = responder.receive(poll).expect_err("must not panic");
535 assert!(matches!(err, SyncError::UnsupportedRequest), "got {err:?}");
536 assert!(responder.ready());
538 }
539 }
540}