1#![forbid(unsafe_code)]
4
5use std::collections::BTreeSet;
6
7use iroh_db_core::{AuthorId, CommitId, DomainId};
8
9pub const MAX_CONTROL_FRAME: usize = 1024 * 1024;
11pub const MAX_REFERENCES: usize = 4_096;
13pub const MAX_SESSION_REFERENCES: usize = 65_536;
15const WIRE_FORMAT_VERSION: u16 = 4;
16pub const MIN_PROTOCOL_VERSION: u16 = 4;
18pub const MAX_PROTOCOL_VERSION: u16 = 4;
20const GOSSIP_VERSION: u16 = 1;
21pub const MAX_GOSSIP_HINT: usize = 512;
23pub const MAX_CAPABILITY_CHAIN: usize = 9;
25pub const MAX_CONTROL_CAPABILITIES: usize = MAX_CAPABILITY_CHAIN;
27pub const MAX_CAPABILITY_BYTES: usize = 4_096;
29
30#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct GossipHint {
33 author: AuthorId,
34 commit: CommitId,
35 frontier_digest: [u8; 32],
36 counter: u64,
37 signature: [u8; 64],
38}
39
40impl GossipHint {
41 pub const fn new(
43 author: AuthorId,
44 commit: CommitId,
45 frontier_digest: [u8; 32],
46 counter: u64,
47 signature: [u8; 64],
48 ) -> Self {
49 Self {
50 author,
51 commit,
52 frontier_digest,
53 counter,
54 signature,
55 }
56 }
57
58 pub const fn author(&self) -> AuthorId {
60 self.author
61 }
62
63 pub const fn commit(&self) -> CommitId {
65 self.commit
66 }
67
68 pub const fn frontier_digest(&self) -> [u8; 32] {
70 self.frontier_digest
71 }
72
73 pub const fn counter(&self) -> u64 {
75 self.counter
76 }
77
78 pub const fn signature(&self) -> &[u8; 64] {
80 &self.signature
81 }
82
83 pub fn signing_bytes(&self) -> Result<Vec<u8>, SyncFrameError> {
85 encode_hint_content(self.author, self.commit, self.frontier_digest, self.counter)
86 }
87
88 pub fn encode_canonical(&self) -> Result<Vec<u8>, SyncFrameError> {
90 let bytes = minicbor::to_vec(GossipWire {
91 content: self.signing_bytes()?,
92 signature: self.signature,
93 })
94 .map_err(codec_error)?;
95 if bytes.len() > MAX_GOSSIP_HINT {
96 return Err(SyncFrameError::GossipHintTooLarge(bytes.len()));
97 }
98 Ok(bytes)
99 }
100
101 pub fn decode_canonical(bytes: &[u8]) -> Result<Self, SyncFrameError> {
103 if bytes.len() > MAX_GOSSIP_HINT {
104 return Err(SyncFrameError::GossipHintTooLarge(bytes.len()));
105 }
106 let mut decoder = minicbor::Decoder::new(bytes);
107 let wire: GossipWire = decoder.decode().map_err(codec_error)?;
108 if decoder.position() != bytes.len() {
109 return Err(SyncFrameError::NonCanonical);
110 }
111 let content: GossipContent = decode_exact(&wire.content)?;
112 if content.version != GOSSIP_VERSION {
113 return Err(SyncFrameError::UnsupportedVersion(content.version));
114 }
115 let hint = Self::new(
116 content.author,
117 content.commit,
118 content.frontier_digest.into(),
119 content.counter,
120 wire.signature,
121 );
122 if hint.signing_bytes()? != wire.content || hint.encode_canonical()? != bytes {
123 return Err(SyncFrameError::NonCanonical);
124 }
125 Ok(hint)
126 }
127}
128
129#[derive(Clone, PartialEq, Eq)]
131pub enum SyncFrame {
132 Hello {
134 nonce: [u8; 32],
136 max_frame: u32,
138 min_protocol: u16,
140 max_protocol: u16,
142 },
143 DomainRequest {
145 domain_id: DomainId,
147 epoch: u64,
149 proof: [u8; 32],
151 capability_chain: Vec<Vec<u8>>,
153 },
154 AuthorityState {
156 epoch: u64,
158 sequence: u64,
160 head: Option<[u8; 32]>,
162 },
163 ControlGrant {
165 transition: Vec<u8>,
167 epoch_key: [u8; 32],
169 capability_chain: Vec<Vec<u8>>,
171 },
172 ControlEvidence {
174 transition: Vec<u8>,
176 },
177 Frontier {
179 commits: Vec<CommitId>,
181 },
182 Need {
184 commits: Vec<CommitId>,
186 },
187 Refs {
189 commits: Vec<CommitId>,
191 },
192 Ack {
194 commit: CommitId,
196 },
197 Done,
199 Error {
201 code: u16,
203 },
204 InvitationRequest,
206 Invitation {
208 payload: Vec<u8>,
210 },
211}
212
213impl std::fmt::Debug for SyncFrame {
214 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215 match self {
216 Self::Hello { max_frame, .. } => formatter
217 .debug_struct("Hello")
218 .field("max_frame", max_frame)
219 .finish_non_exhaustive(),
220 Self::DomainRequest {
221 domain_id,
222 epoch,
223 capability_chain,
224 ..
225 } => formatter
226 .debug_struct("DomainRequest")
227 .field("domain_id", domain_id)
228 .field("epoch", epoch)
229 .field("capability_count", &capability_chain.len())
230 .finish_non_exhaustive(),
231 Self::AuthorityState {
232 epoch,
233 sequence,
234 head,
235 } => formatter
236 .debug_struct("AuthorityState")
237 .field("epoch", epoch)
238 .field("sequence", sequence)
239 .field("head", head)
240 .finish(),
241 Self::ControlGrant {
242 capability_chain, ..
243 } => formatter
244 .debug_struct("ControlGrant")
245 .field("capability_count", &capability_chain.len())
246 .field("key_material", &"[redacted]")
247 .finish_non_exhaustive(),
248 Self::ControlEvidence { transition } => formatter
249 .debug_struct("ControlEvidence")
250 .field("transition_len", &transition.len())
251 .finish(),
252 Self::Frontier { commits } => debug_references(formatter, "Frontier", commits),
253 Self::Need { commits } => debug_references(formatter, "Need", commits),
254 Self::Refs { commits } => debug_references(formatter, "Refs", commits),
255 Self::Ack { commit } => formatter
256 .debug_struct("Ack")
257 .field("commit", commit)
258 .finish(),
259 Self::Done => formatter.write_str("Done"),
260 Self::Error { code } => formatter.debug_struct("Error").field("code", code).finish(),
261 Self::InvitationRequest => formatter.write_str("InvitationRequest"),
262 Self::Invitation { payload } => formatter
263 .debug_struct("Invitation")
264 .field("payload_len", &payload.len())
265 .field("payload", &"[redacted]")
266 .finish(),
267 }
268 }
269}
270
271fn debug_references(
272 formatter: &mut std::fmt::Formatter<'_>,
273 name: &str,
274 commits: &[CommitId],
275) -> std::fmt::Result {
276 formatter
277 .debug_struct(name)
278 .field("commit_count", &commits.len())
279 .finish()
280}
281
282impl SyncFrame {
283 pub fn encode_canonical(&self) -> Result<Vec<u8>, SyncFrameError> {
285 validate(self)?;
286 let wire = WireFrame::from(self);
287 let bytes = minicbor::to_vec(wire).map_err(codec_error)?;
288 if bytes.len() > MAX_CONTROL_FRAME {
289 return Err(SyncFrameError::FrameTooLarge(bytes.len()));
290 }
291 Ok(bytes)
292 }
293
294 pub fn decode_canonical(bytes: &[u8]) -> Result<Self, SyncFrameError> {
296 if bytes.len() > MAX_CONTROL_FRAME {
297 return Err(SyncFrameError::FrameTooLarge(bytes.len()));
298 }
299 let mut decoder = minicbor::Decoder::new(bytes);
300 let wire: WireFrame = decoder.decode().map_err(codec_error)?;
301 if decoder.position() != bytes.len() {
302 return Err(SyncFrameError::NonCanonical);
303 }
304 let frame = Self::try_from(wire)?;
305 if frame.encode_canonical()? != bytes {
306 return Err(SyncFrameError::NonCanonical);
307 }
308 Ok(frame)
309 }
310}
311
312pub fn missing_commits(
314 local: &[CommitId],
315 remote: &[CommitId],
316) -> Result<Vec<CommitId>, SyncFrameError> {
317 if local.len() > MAX_SESSION_REFERENCES {
318 return Err(SyncFrameError::TooManyReferences(local.len()));
319 }
320 if remote.len() > MAX_SESSION_REFERENCES {
321 return Err(SyncFrameError::TooManyReferences(remote.len()));
322 }
323 let local = local.iter().copied().collect::<BTreeSet<_>>();
324 Ok(remote
325 .iter()
326 .copied()
327 .collect::<BTreeSet<_>>()
328 .difference(&local)
329 .copied()
330 .collect())
331}
332
333pub fn negotiate_protocol(
335 local_min: u16,
336 local_max: u16,
337 remote_min: u16,
338 remote_max: u16,
339) -> Result<u16, SyncFrameError> {
340 if local_min > local_max {
341 return Err(SyncFrameError::InvalidProtocolRange {
342 min: local_min,
343 max: local_max,
344 });
345 }
346 if remote_min > remote_max {
347 return Err(SyncFrameError::InvalidProtocolRange {
348 min: remote_min,
349 max: remote_max,
350 });
351 }
352 let minimum = local_min.max(remote_min);
353 let maximum = local_max.min(remote_max);
354 if minimum > maximum {
355 return Err(SyncFrameError::NoCommonProtocol {
356 local_min,
357 local_max,
358 remote_min,
359 remote_max,
360 });
361 }
362 Ok(maximum)
363}
364
365#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
367pub enum SyncFrameError {
368 #[error("control frame is too large: {0}")]
370 FrameTooLarge(usize),
371 #[error("control frame contains too many references: {0}")]
373 TooManyReferences(usize),
374 #[error("control-frame references are not canonical")]
376 NonCanonicalReferences,
377 #[error("unsupported sync protocol version: {0}")]
379 UnsupportedVersion(u16),
380 #[error("invalid protocol range {min}..={max}")]
382 InvalidProtocolRange { min: u16, max: u16 },
383 #[error(
385 "no common protocol: local {local_min}..={local_max}, remote {remote_min}..={remote_max}"
386 )]
387 NoCommonProtocol {
388 local_min: u16,
389 local_max: u16,
390 remote_min: u16,
391 remote_max: u16,
392 },
393 #[error("control frame shape is invalid")]
395 InvalidShape,
396 #[error("control frame codec failed: {0}")]
398 Codec(String),
399 #[error("control frame is not canonical")]
401 NonCanonical,
402 #[error("gossip hint is too large: {0}")]
404 GossipHintTooLarge(usize),
405 #[error("invitation payload is too large: {0}")]
407 InvitationTooLarge(usize),
408 #[error("authority message contains too many capability objects: {0}")]
410 TooManyAuthorityObjects(usize),
411 #[error("authority object is too large: {0}")]
413 AuthorityObjectTooLarge(usize),
414}
415
416#[derive(minicbor::Encode, minicbor::Decode)]
417#[cbor(array)]
418struct GossipContent {
419 #[n(0)]
420 version: u16,
421 #[n(1)]
422 author: AuthorId,
423 #[n(2)]
424 commit: CommitId,
425 #[n(3)]
426 frontier_digest: minicbor::bytes::ByteArray<32>,
427 #[n(4)]
428 counter: u64,
429}
430
431#[derive(minicbor::Encode, minicbor::Decode)]
432#[cbor(array)]
433struct GossipWire {
434 #[n(0)]
435 content: Vec<u8>,
436 #[n(1)]
437 signature: [u8; 64],
438}
439
440#[derive(minicbor::Encode, minicbor::Decode)]
441#[cbor(array)]
442struct WireFrame {
443 #[n(0)]
444 version: u16,
445 #[n(1)]
446 kind: u8,
447 #[n(2)]
448 domain_id: Option<DomainId>,
449 #[n(3)]
450 nonce_or_proof: Option<minicbor::bytes::ByteArray<32>>,
451 #[n(4)]
452 commits: Vec<CommitId>,
453 #[n(5)]
454 max_frame: Option<u32>,
455 #[n(6)]
456 commit: Option<CommitId>,
457 #[n(7)]
458 error_code: Option<u16>,
459 #[n(8)]
460 payload: Option<Vec<u8>>,
461 #[n(9)]
462 epoch: Option<u64>,
463 #[n(10)]
464 sequence: Option<u64>,
465 #[n(11)]
466 head: Option<minicbor::bytes::ByteArray<32>>,
467 #[n(12)]
468 payloads: Vec<Vec<u8>>,
469 #[n(13)]
470 epoch_key: Option<minicbor::bytes::ByteArray<32>>,
471 #[n(14)]
472 min_protocol: Option<u16>,
473 #[n(15)]
474 max_protocol: Option<u16>,
475}
476
477impl From<&SyncFrame> for WireFrame {
478 fn from(frame: &SyncFrame) -> Self {
479 let mut wire = Self {
480 version: WIRE_FORMAT_VERSION,
481 kind: 0,
482 domain_id: None,
483 nonce_or_proof: None,
484 commits: Vec::new(),
485 max_frame: None,
486 commit: None,
487 error_code: None,
488 payload: None,
489 epoch: None,
490 sequence: None,
491 head: None,
492 payloads: Vec::new(),
493 epoch_key: None,
494 min_protocol: None,
495 max_protocol: None,
496 };
497 match frame {
498 SyncFrame::Hello {
499 nonce,
500 max_frame,
501 min_protocol,
502 max_protocol,
503 } => {
504 wire.kind = 0;
505 wire.nonce_or_proof = Some((*nonce).into());
506 wire.max_frame = Some(*max_frame);
507 wire.min_protocol = Some(*min_protocol);
508 wire.max_protocol = Some(*max_protocol);
509 }
510 SyncFrame::DomainRequest {
511 domain_id,
512 epoch,
513 proof,
514 capability_chain,
515 } => {
516 wire.kind = 1;
517 wire.domain_id = Some(*domain_id);
518 wire.nonce_or_proof = Some((*proof).into());
519 wire.epoch = Some(*epoch);
520 wire.payloads.clone_from(capability_chain);
521 }
522 SyncFrame::Frontier { commits } => {
523 wire.kind = 2;
524 wire.commits.clone_from(commits);
525 }
526 SyncFrame::Need { commits } => {
527 wire.kind = 3;
528 wire.commits.clone_from(commits);
529 }
530 SyncFrame::Refs { commits } => {
531 wire.kind = 4;
532 wire.commits.clone_from(commits);
533 }
534 SyncFrame::Ack { commit } => {
535 wire.kind = 5;
536 wire.commit = Some(*commit);
537 }
538 SyncFrame::Done => wire.kind = 6,
539 SyncFrame::Error { code } => {
540 wire.kind = 7;
541 wire.error_code = Some(*code);
542 }
543 SyncFrame::InvitationRequest => wire.kind = 8,
544 SyncFrame::Invitation { payload } => {
545 wire.kind = 9;
546 wire.payload = Some(payload.clone());
547 }
548 SyncFrame::AuthorityState {
549 epoch,
550 sequence,
551 head,
552 } => {
553 wire.kind = 10;
554 wire.epoch = Some(*epoch);
555 wire.sequence = Some(*sequence);
556 wire.head = head.map(Into::into);
557 }
558 SyncFrame::ControlGrant {
559 transition,
560 epoch_key,
561 capability_chain,
562 } => {
563 wire.kind = 11;
564 wire.payload = Some(transition.clone());
565 wire.epoch_key = Some((*epoch_key).into());
566 wire.payloads.clone_from(capability_chain);
567 }
568 SyncFrame::ControlEvidence { transition } => {
569 wire.kind = 12;
570 wire.payload = Some(transition.clone());
571 }
572 }
573 wire
574 }
575}
576
577impl TryFrom<WireFrame> for SyncFrame {
578 type Error = SyncFrameError;
579
580 fn try_from(wire: WireFrame) -> Result<Self, SyncFrameError> {
581 if wire.version != WIRE_FORMAT_VERSION {
582 return Err(SyncFrameError::UnsupportedVersion(wire.version));
583 }
584 if wire.kind != 0 && (wire.min_protocol.is_some() || wire.max_protocol.is_some()) {
585 return Err(SyncFrameError::InvalidShape);
586 }
587 if wire.kind >= 10 {
588 let frame = decode_authority_frame(wire)?;
589 validate(&frame)?;
590 return Ok(frame);
591 }
592 let empty_refs = wire.commits.is_empty();
593 let frame = match wire.kind {
594 0 if wire.domain_id.is_none()
595 && empty_refs
596 && wire.commit.is_none()
597 && wire.error_code.is_none()
598 && wire.payload.is_none() =>
599 {
600 Self::Hello {
601 nonce: fixed_proof(wire.nonce_or_proof)?,
602 max_frame: wire.max_frame.ok_or(SyncFrameError::InvalidShape)?,
603 min_protocol: wire.min_protocol.ok_or(SyncFrameError::InvalidShape)?,
604 max_protocol: wire.max_protocol.ok_or(SyncFrameError::InvalidShape)?,
605 }
606 }
607 1 if wire.max_frame.is_none()
608 && empty_refs
609 && wire.commit.is_none()
610 && wire.error_code.is_none()
611 && wire.payload.is_none()
612 && wire.sequence.is_none()
613 && wire.head.is_none()
614 && wire.epoch_key.is_none() =>
615 {
616 Self::DomainRequest {
617 domain_id: wire.domain_id.ok_or(SyncFrameError::InvalidShape)?,
618 epoch: wire.epoch.ok_or(SyncFrameError::InvalidShape)?,
619 proof: fixed_proof(wire.nonce_or_proof)?,
620 capability_chain: wire.payloads,
621 }
622 }
623 2 if unused_scalar_fields(&wire) => Self::Frontier {
624 commits: wire.commits,
625 },
626 3 if unused_scalar_fields(&wire) => Self::Need {
627 commits: wire.commits,
628 },
629 4 if unused_scalar_fields(&wire) => Self::Refs {
630 commits: wire.commits,
631 },
632 5 if wire.domain_id.is_none()
633 && wire.nonce_or_proof.is_none()
634 && empty_refs
635 && wire.max_frame.is_none()
636 && wire.error_code.is_none()
637 && wire.payload.is_none() =>
638 {
639 Self::Ack {
640 commit: wire.commit.ok_or(SyncFrameError::InvalidShape)?,
641 }
642 }
643 6 if unused_scalar_fields(&wire) && empty_refs => Self::Done,
644 7 if wire.domain_id.is_none()
645 && wire.nonce_or_proof.is_none()
646 && empty_refs
647 && wire.max_frame.is_none()
648 && wire.commit.is_none()
649 && wire.payload.is_none() =>
650 {
651 Self::Error {
652 code: wire.error_code.ok_or(SyncFrameError::InvalidShape)?,
653 }
654 }
655 8 if unused_scalar_fields(&wire) && empty_refs => Self::InvitationRequest,
656 9 if unused_scalar_fields_except_payload(&wire) && empty_refs => Self::Invitation {
657 payload: wire.payload.ok_or(SyncFrameError::InvalidShape)?,
658 },
659 _ => return Err(SyncFrameError::InvalidShape),
660 };
661 validate(&frame)?;
662 Ok(frame)
663 }
664}
665
666fn decode_authority_frame(wire: WireFrame) -> Result<SyncFrame, SyncFrameError> {
667 let common_shape = wire.domain_id.is_none()
668 && wire.nonce_or_proof.is_none()
669 && wire.commits.is_empty()
670 && wire.max_frame.is_none()
671 && wire.commit.is_none()
672 && wire.error_code.is_none();
673 match wire.kind {
674 10 if common_shape
675 && wire.payload.is_none()
676 && wire.payloads.is_empty()
677 && wire.epoch_key.is_none() =>
678 {
679 Ok(SyncFrame::AuthorityState {
680 epoch: wire.epoch.ok_or(SyncFrameError::InvalidShape)?,
681 sequence: wire.sequence.ok_or(SyncFrameError::InvalidShape)?,
682 head: wire.head.map(Into::into),
683 })
684 }
685 11 if common_shape
686 && wire.epoch.is_none()
687 && wire.sequence.is_none()
688 && wire.head.is_none() =>
689 {
690 Ok(SyncFrame::ControlGrant {
691 transition: wire.payload.ok_or(SyncFrameError::InvalidShape)?,
692 epoch_key: wire
693 .epoch_key
694 .map(Into::into)
695 .ok_or(SyncFrameError::InvalidShape)?,
696 capability_chain: wire.payloads,
697 })
698 }
699 12 if common_shape
700 && wire.epoch.is_none()
701 && wire.sequence.is_none()
702 && wire.head.is_none()
703 && wire.epoch_key.is_none()
704 && wire.payloads.is_empty() =>
705 {
706 Ok(SyncFrame::ControlEvidence {
707 transition: wire.payload.ok_or(SyncFrameError::InvalidShape)?,
708 })
709 }
710 _ => Err(SyncFrameError::InvalidShape),
711 }
712}
713
714fn unused_scalar_fields(wire: &WireFrame) -> bool {
715 wire.domain_id.is_none()
716 && wire.nonce_or_proof.is_none()
717 && wire.max_frame.is_none()
718 && wire.commit.is_none()
719 && wire.error_code.is_none()
720 && wire.payload.is_none()
721 && wire.epoch.is_none()
722 && wire.sequence.is_none()
723 && wire.head.is_none()
724 && wire.payloads.is_empty()
725 && wire.epoch_key.is_none()
726 && wire.min_protocol.is_none()
727 && wire.max_protocol.is_none()
728}
729
730fn unused_scalar_fields_except_payload(wire: &WireFrame) -> bool {
731 wire.domain_id.is_none()
732 && wire.nonce_or_proof.is_none()
733 && wire.max_frame.is_none()
734 && wire.commit.is_none()
735 && wire.error_code.is_none()
736 && wire.epoch.is_none()
737 && wire.sequence.is_none()
738 && wire.head.is_none()
739 && wire.payloads.is_empty()
740 && wire.epoch_key.is_none()
741 && wire.min_protocol.is_none()
742 && wire.max_protocol.is_none()
743}
744
745fn fixed_proof(value: Option<minicbor::bytes::ByteArray<32>>) -> Result<[u8; 32], SyncFrameError> {
746 value.map(Into::into).ok_or(SyncFrameError::InvalidShape)
747}
748
749fn validate(frame: &SyncFrame) -> Result<(), SyncFrameError> {
750 match frame {
751 SyncFrame::Hello { max_frame, .. }
752 if *max_frame == 0 || *max_frame as usize > MAX_CONTROL_FRAME =>
753 {
754 Err(SyncFrameError::FrameTooLarge(*max_frame as usize))
755 }
756 SyncFrame::Hello {
757 min_protocol,
758 max_protocol,
759 ..
760 } if min_protocol > max_protocol => Err(SyncFrameError::InvalidProtocolRange {
761 min: *min_protocol,
762 max: *max_protocol,
763 }),
764 SyncFrame::AuthorityState {
765 epoch, sequence, ..
766 } if epoch != sequence => Err(SyncFrameError::InvalidShape),
767 SyncFrame::Frontier { commits }
768 | SyncFrame::Need { commits }
769 | SyncFrame::Refs { commits } => validate_references(commits),
770 SyncFrame::Invitation { payload } if payload.len() > 64 * 1024 => {
771 Err(SyncFrameError::InvitationTooLarge(payload.len()))
772 }
773 SyncFrame::DomainRequest {
774 capability_chain, ..
775 } => validate_capability_chain(capability_chain, MAX_CAPABILITY_CHAIN),
776 SyncFrame::ControlGrant {
777 capability_chain, ..
778 } => validate_capability_chain(capability_chain, MAX_CONTROL_CAPABILITIES),
779 _ => Ok(()),
780 }
781}
782
783fn validate_capability_chain(chain: &[Vec<u8>], maximum: usize) -> Result<(), SyncFrameError> {
784 if chain.len() > maximum {
785 return Err(SyncFrameError::TooManyAuthorityObjects(chain.len()));
786 }
787 if let Some(object) = chain
788 .iter()
789 .find(|object| object.len() > MAX_CAPABILITY_BYTES)
790 {
791 return Err(SyncFrameError::AuthorityObjectTooLarge(object.len()));
792 }
793 Ok(())
794}
795
796fn validate_references(commits: &[CommitId]) -> Result<(), SyncFrameError> {
797 if commits.len() > MAX_REFERENCES {
798 return Err(SyncFrameError::TooManyReferences(commits.len()));
799 }
800 if commits.windows(2).any(|pair| pair[0] >= pair[1]) {
801 return Err(SyncFrameError::NonCanonicalReferences);
802 }
803 Ok(())
804}
805
806fn encode_hint_content(
807 author: AuthorId,
808 commit: CommitId,
809 frontier_digest: [u8; 32],
810 counter: u64,
811) -> Result<Vec<u8>, SyncFrameError> {
812 minicbor::to_vec(GossipContent {
813 version: GOSSIP_VERSION,
814 author,
815 commit,
816 frontier_digest: frontier_digest.into(),
817 counter,
818 })
819 .map_err(codec_error)
820}
821
822fn decode_exact(bytes: &[u8]) -> Result<GossipContent, SyncFrameError> {
823 let mut decoder = minicbor::Decoder::new(bytes);
824 let content = decoder.decode().map_err(codec_error)?;
825 if decoder.position() != bytes.len() {
826 return Err(SyncFrameError::NonCanonical);
827 }
828 Ok(content)
829}
830
831#[allow(clippy::needless_pass_by_value)]
832fn codec_error(error: impl std::fmt::Display) -> SyncFrameError {
833 SyncFrameError::Codec(error.to_string())
834}