1#![no_std]
22#![deny(unsafe_code)]
23
24extern crate alloc;
25#[cfg(all(feature = "local-writer", target_os = "linux"))]
26extern crate std;
27
28#[cfg(all(feature = "local-writer", target_os = "linux"))]
29pub mod local;
30pub mod ownership;
31pub mod wire;
32
33#[path = "crdt/gcounter.rs"]
34mod gcounter;
35pub use gcounter::{CoordinateError, GCounter, GCounterDelta};
36
37#[path = "crdt/gset.rs"]
38mod gset;
39pub use gset::GSet;
40
41#[path = "crdt/pncounter.rs"]
42mod pncounter;
43pub use pncounter::{PnCounter, PnCounterDelta};
44
45#[path = "crdt/orset.rs"]
46mod orset;
47pub use orset::{OrSet, OrSetDelta};
48
49#[path = "crdt/rga.rs"]
50mod rga;
51pub use rga::{Rga, RgaDelta};
52
53#[path = "crdt/enable_wins_flag.rs"]
54mod enable_wins_flag;
55pub use enable_wins_flag::{EnableWinsFlag, EnableWinsFlagDelta};
56
57#[path = "crdt/lww_register.rs"]
58mod lww_register;
59pub use lww_register::{LwwDot, LwwEntry, LwwRegister, LwwRegisterDelta};
60
61#[path = "crdt/lww_map.rs"]
62mod lww_map;
63pub use lww_map::{LwwMap, LwwMapDelta};
64
65mod record;
66pub use record::{Record, RecordId};
67
68mod version_vector;
69pub use version_vector::{
70 VersionVector, VersionVectorError, VersionVectorLimitError, VersionVectorLimits,
71};
72
73mod event_log;
74pub use event_log::{Admission, AppendError, EventLog};
75
76mod replica;
77pub use replica::{Replica, ReplicaError};
78
79mod collision;
80pub use collision::{CollisionReport, CollisionVerdict, RecordCollision};
81
82mod transport;
83pub use transport::{
84 anti_entropy, queue_anti_entropy, InMemoryTransport, TransportAdapter, TransportEnvelope,
85 TransportError,
86};
87
88mod codec;
89pub use codec::{
90 write_bytes, write_len, write_u32, write_u64, write_u8, CollectionLimits, DecodeError,
91 DecodeLimits, LegacyFrame, WireCursor, WireDecode, WireEncode, WireError, WireSchema,
92};
93
94use codec::*;
95
96#[cfg(test)]
97use alloc::{
98 collections::{BTreeMap, BTreeSet},
99 vec,
100 vec::Vec,
101};
102
103pub trait Mergeable {
110 fn merge(&mut self, other: &Self) -> Result<(), MergeError>;
112}
113
114pub trait Crdt: Mergeable {
137 type Delta;
138
139 fn replica_count(&self) -> Option<usize> {
141 None
142 }
143
144 fn validate_record(&self, id: RecordId, delta: &Self::Delta) -> Result<(), WireError>;
160
161 fn apply_delta(&mut self, delta: Self::Delta);
162}
163
164#[derive(Clone, Copy, Debug, PartialEq, Eq)]
172pub enum MergeError {
173 ReplicaCountMismatch { own: usize, other: usize },
175}
176
177impl core::fmt::Display for MergeError {
178 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
179 match self {
180 MergeError::ReplicaCountMismatch { own, other } => write!(
181 f,
182 "replica-count mismatch: own={own}, other={other} (fixed replica set violated)"
183 ),
184 }
185 }
186}
187
188impl core::error::Error for MergeError {}
189
190#[cfg(feature = "laws")]
191pub mod laws;
192
193#[cfg(test)]
194mod frame_tests {
195 use super::*;
196
197 #[test]
198 fn seqzero_saturated_prefixes() {
199 extern crate std;
200 let mut cases = 0;
201 let mut mismatches = 0;
202 for author in [0, 1, u64::MAX] {
203 for sequence in [0, 1, 2, 3, u64::MAX - 1, u64::MAX] {
204 for prefix in [u64::MAX - 1, u64::MAX] {
205 let mut log = EventLog::new();
206 let r = Record {
207 id: RecordId {
208 replica: author,
209 sequence,
210 },
211 delta: 7u64,
212 };
213 assert_eq!(
214 log.insert_record(&GSet::<u64>::new(), r.clone()),
215 if sequence == 0 {
216 Admission::Invalid(WireError::ZeroSequenceRecord {
217 replica: author,
218 kind: "G-Set",
219 })
220 } else {
221 Admission::Accepted
222 }
223 );
224 let mut version = VersionVector::new();
225 version.set(author, prefix);
227 let expected = if sequence > 0 && sequence > prefix {
228 vec![r]
229 } else {
230 vec![]
231 };
232 mismatches += usize::from(log.since(&version) != expected);
233 cases += 1;
234 }
235 }
236 }
237 std::println!("SATURATED CASES {cases} DISAGREE {mismatches}");
238 assert_eq!(mismatches, 0);
239 }
240
241 use alloc::vec;
242
243 #[test]
244 fn legacy_integrity_frames_name_the_unshaped_frame() {
245 for records in [vec![], vec![record(1, 5)]] {
246 let mut body = Vec::new();
247 write_len(&mut body, records.len()).unwrap();
248 for record in records {
249 write_bytes(&mut body, &record.to_wire_bytes().unwrap()).unwrap();
250 }
251 let mut bytes = vec![TAG_EVENT_LOG];
252 let len = u32::try_from(body.len()).unwrap();
253 write_u32(&mut bytes, len);
254 write_u32(&mut bytes, !len);
255 bytes.extend(body);
256 let crc = frame_crc32(&bytes[1..]);
257 write_u32(&mut bytes, crc);
258 assert_eq!(
259 EventLog::<GCounterDelta>::from_wire_bytes(&bytes),
260 Err(WireError::LegacyEventLogFrame {
261 found: LegacyFrame::Tag03Unshaped
262 })
263 );
264 }
265 }
266
267 #[test]
268 fn crc32_matches_standard_check_vector() {
269 assert_eq!(frame_crc32(b"123456789"), 0xcbf4_3926);
270 }
271
272 #[test]
273 fn binding_collision_fixtures_use_product_encoder() {
274 extern crate std;
275 fn fixture<D: WireEncode + WireDecode + WireSchema + PartialEq>(
276 name: &str,
277 deltas: [D; 2],
278 ) {
279 let shape = D::REQUIRES_ARITY.then_some(2);
280 let records: Vec<_> = deltas
281 .into_iter()
282 .map(|delta| Record {
283 id: RecordId {
284 replica: 1,
285 sequence: 1,
286 },
287 delta,
288 })
289 .collect();
290 let mut bytes = Vec::new();
291 EventLog::encode_records(shape, &records, &mut bytes).unwrap();
292 assert!(matches!(
293 EventLog::<D>::from_wire_bytes(&bytes),
294 Err(WireError::RecordCollision)
295 ));
296 if let Some(directory) = std::env::var_os("SAFEMESH_FRAME_FIXTURE_DIR") {
297 std::fs::write(std::path::Path::new(&directory).join(name), bytes).unwrap();
298 }
299 }
300 fixture(
301 "gcounter-collision.bin",
302 [
303 GCounterDelta {
304 replica: 1,
305 tally: 5,
306 },
307 GCounterDelta {
308 replica: 1,
309 tally: 9,
310 },
311 ],
312 );
313 fixture(
314 "flag-collision.bin",
315 [
316 EnableWinsFlagDelta::Enable { token: 5u64 },
317 EnableWinsFlagDelta::Enable { token: 9u64 },
318 ],
319 );
320 fixture(
321 "register-collision.bin",
322 [
323 LwwRegisterDelta {
324 timestamp: 1,
325 replica: 1,
326 value: 5u64,
327 },
328 LwwRegisterDelta {
329 timestamp: 2,
330 replica: 1,
331 value: 9u64,
332 },
333 ],
334 );
335 fixture(
336 "map-collision.bin",
337 [
338 LwwMapDelta::Set {
339 key: 1u64,
340 timestamp: 1,
341 replica: 1,
342 value: 5u64,
343 },
344 LwwMapDelta::Remove {
345 key: 1u64,
346 timestamp: 2,
347 replica: 1,
348 },
349 ],
350 );
351 }
352
353 fn record(sequence: u64, tally: u64) -> Record<GCounterDelta> {
354 Record {
355 id: RecordId {
356 replica: 1,
357 sequence,
358 },
359 delta: GCounterDelta { replica: 1, tally },
360 }
361 }
362
363 fn wire_log(records: &[Record<GCounterDelta>]) -> Vec<u8> {
366 let mut bytes = Vec::new();
367 EventLog::encode_records(Some(2), records, &mut bytes).unwrap();
368 bytes
369 }
370 #[test]
371 fn decoder_surfaces_conflicts_before_deduplication() {
372 for records in [
373 vec![record(1, 5), record(1, 9)],
374 vec![record(1, 9), record(1, 5)],
375 ] {
376 assert_eq!(
377 EventLog::<GCounterDelta>::from_wire_bytes(&wire_log(&records)),
378 Err(WireError::RecordCollision)
379 );
380 }
381 let decoded =
382 EventLog::<GCounterDelta>::from_wire_bytes(&wire_log(&[record(1, 5), record(1, 5)]))
383 .unwrap();
384 assert_eq!(decoded.records(), &[record(1, 5)]);
385 for records in [
386 vec![record(1, 5), record(1, 5), record(1, 5)],
387 vec![record(1, 5), record(2, 7), record(1, 5)],
388 ] {
389 let bytes = wire_log(&records);
390 let state = GCounter::new(2);
391 let plain = EventLog::<GCounterDelta>::from_wire_bytes(&bytes).unwrap();
392 let shaped = EventLog::<GCounterDelta>::from_wire_bytes_for(&bytes, &state).unwrap();
393 assert_eq!(plain, shaped);
394 let expected = if records[1] == records[0] {
395 &records[..1]
396 } else {
397 &records[..2]
398 };
399 assert_eq!(plain.records(), expected);
400 assert_eq!(plain.version(), shaped.version());
401 assert_eq!(plain.to_wire_bytes(), shaped.to_wire_bytes());
402 assert_eq!(
403 EventLog::<GCounterDelta>::records_from_wire_bytes_for(&bytes, &state).unwrap(),
404 records
405 );
406 let mut trailing = bytes;
407 trailing.push(0);
408 assert_eq!(
409 EventLog::<GCounterDelta>::records_from_wire_bytes_for(&trailing, &state),
410 Err(WireError::TrailingBytes)
411 );
412 }
413 }
414}
415
416#[cfg(test)]
417mod version_vector_tests {
418 #[test]
419 fn planted_peer_collection_budgets_are_enforced_independently() {
420 let entries = BTreeMap::from([(1, 1), (2, u64::MAX), (3, 7)]);
421 let zeros = BTreeSet::from([4, 5]);
422 for (max_authors, max_zero_replicas, expected) in [
423 (
424 Some(2),
425 None,
426 VersionVectorLimitError::AuthorLimitExceeded { max_authors: 2 },
427 ),
428 (
429 None,
430 Some(1),
431 VersionVectorLimitError::ZeroReplicaLimitExceeded {
432 max_zero_replicas: 1,
433 },
434 ),
435 (
436 Some(2),
437 Some(1),
438 VersionVectorLimitError::AuthorLimitExceeded { max_authors: 2 },
439 ),
440 ] {
441 assert_eq!(
442 VersionVector::from_peer_prefixes_with_limits(
443 &entries,
444 &zeros,
445 VersionVectorLimits {
446 max_authors,
447 max_zero_replicas
448 }
449 ),
450 Err(expected)
451 );
452 }
453 let unbounded = VersionVector::from_peer_prefixes(&entries, &zeros).unwrap();
454 assert_eq!(unbounded.entries(), &entries);
455 assert_eq!(unbounded.zero_replicas(), &zeros);
456 for limits in [
457 VersionVectorLimits::default(),
458 VersionVectorLimits {
459 max_authors: Some(3),
460 max_zero_replicas: Some(2),
461 },
462 ] {
463 assert_eq!(
464 VersionVector::from_peer_prefixes_with_limits(&entries, &zeros, limits),
465 Ok(unbounded.clone())
466 );
467 }
468 }
469
470 #[test]
471 fn peer_budgets_allow_empty_and_precede_prefix_validation() {
472 let no_entries = BTreeMap::new();
473 let no_zeros = BTreeSet::new();
474 let limits = VersionVectorLimits {
475 max_authors: Some(0),
476 max_zero_replicas: Some(0),
477 };
478 assert_eq!(
479 VersionVector::from_peer_prefixes_with_limits(&no_entries, &no_zeros, limits),
480 Ok(VersionVector::new())
481 );
482 let invalid = BTreeMap::from([(7, 0)]);
483 assert_eq!(
484 VersionVector::from_peer_prefixes_with_limits(&invalid, &no_zeros, limits),
485 Err(VersionVectorLimitError::AuthorLimitExceeded { max_authors: 0 })
486 );
487 let zeros = BTreeSet::from([7]);
488 assert_eq!(
489 VersionVector::from_peer_prefixes_with_limits(
490 &invalid,
491 &zeros,
492 VersionVectorLimits {
493 max_authors: None,
494 max_zero_replicas: Some(0)
495 }
496 ),
497 Err(VersionVectorLimitError::ZeroReplicaLimitExceeded {
498 max_zero_replicas: 0
499 })
500 );
501 assert_eq!(
502 VersionVector::from_peer_prefixes_with_limits(
503 &invalid,
504 &no_zeros,
505 VersionVectorLimits::default()
506 ),
507 Err(VersionVectorLimitError::Prefix(
508 VersionVectorError::ZeroPrefix { replica: 7 }
509 ))
510 );
511 }
512
513 #[test]
514 fn large_peer_prefixes_are_copied_directly() {
515 for (authors, prefix) in [
516 (1024, 1000),
517 (1024, 1_000_000),
518 (1024, u64::MAX),
519 (2048, 1_000_000),
520 (2048, u64::MAX),
521 ] {
522 let entries = (0..authors).map(|r| (r, prefix)).collect();
523 let zeros = (0..authors).collect();
524 let vector = VersionVector::from_peer_prefixes(&entries, &zeros).unwrap();
525 assert_eq!(vector.entries(), &entries);
526 assert_eq!(vector.zero_replicas(), &zeros);
527 }
528 }
529
530 use super::*;
531
532 #[test]
533 fn large_zero_acknowledgement_set_is_copied_directly() {
534 let zeros: BTreeSet<u64> = (0..=1_000_000).collect();
535 let vector = VersionVector::from_peer_prefixes(&BTreeMap::new(), &zeros).unwrap();
536 assert_eq!(vector.zero_replicas().len(), 1_000_001);
537 assert_eq!(vector.zero_replicas(), &zeros);
538 assert!(vector.entries().is_empty());
539 }
540
541 #[test]
542 fn planted_zero_prefix_is_rejected() {
543 let entries = BTreeMap::from([(7, 0)]);
544 let zeros = BTreeSet::new();
545 assert_eq!(
546 VersionVector::from_peer_prefixes(&entries, &zeros),
547 Err(VersionVectorError::ZeroPrefix { replica: 7 })
548 );
549 }
550
551 #[test]
552 fn all_positive_u64_prefixes_are_accepted() {
553 for prefix in [1, 999_999, 1_000_000, 1_000_001, u64::MAX - 1, u64::MAX] {
554 let entries = BTreeMap::from([(0, prefix)]);
555 let zeros = BTreeSet::from([0]);
556 let vector = VersionVector::from_peer_prefixes(&entries, &zeros).unwrap();
557 assert_eq!(vector.entries(), &entries);
558 assert_eq!(vector.zero_replicas(), &zeros);
559 assert!(vector.includes(RecordId {
560 replica: 0,
561 sequence: prefix
562 }));
563 }
564 }
565
566 #[test]
567 fn million_plus_one_record_log_version_is_reconstructible() {
568 let mut log = EventLog::new();
569 for sequence in 1..=1_000_001 {
570 assert_eq!(
571 log.append(&mut GSet::<u64>::new(), 0, sequence).unwrap(),
572 RecordId {
573 replica: 0,
574 sequence
575 }
576 );
577 }
578 assert_eq!(log.records().len(), 1_000_001);
579 assert_eq!(log.version().get(0), 1_000_001);
580 let rebuilt = VersionVector::from_peer_prefixes(
581 log.version().entries(),
582 log.version().zero_replicas(),
583 )
584 .unwrap();
585 assert_eq!(&rebuilt, log.version());
586 assert!(log.since(&rebuilt).is_empty());
587 }
588
589 #[test]
590 fn terminal_log_prefix_is_reconstructible_without_overflow() {
591 let mut log = EventLog::new();
592 log.version.set(0, u64::MAX - 1);
595 let last = RecordId {
596 replica: 0,
597 sequence: u64::MAX,
598 };
599 assert_eq!(
600 log.insert_record(
601 &GSet::<u64>::new(),
602 Record {
603 id: last,
604 delta: 7u64
605 }
606 ),
607 Admission::Accepted
608 );
609 assert_eq!(log.version().get(0), u64::MAX);
610 let mut rebuilt = VersionVector::from_peer_prefixes(
611 log.version().entries(),
612 log.version().zero_replicas(),
613 )
614 .unwrap();
615 assert_eq!(&rebuilt, log.version());
616 assert!(rebuilt.includes(last));
617 assert!(log.since(&rebuilt).is_empty());
618 assert!(!rebuilt.includes(RecordId {
619 replica: 0,
620 sequence: 0
621 }));
622 rebuilt.observe(RecordId {
623 replica: 0,
624 sequence: 0,
625 });
626 assert_eq!(rebuilt.get(0), u64::MAX);
627 assert!(rebuilt.includes(RecordId {
628 replica: 0,
629 sequence: 0
630 }));
631 assert_eq!(
632 log.append_with(&mut GSet::<u64>::new(), 0, 8, |_, _| {}),
633 Err(AppendError::SequenceExhausted)
634 );
635 }
636
637 #[test]
638 fn legitimate_peer_maps_construct_and_match_observe() {
639 let cases = [
640 (BTreeMap::new(), BTreeSet::new()),
641 (BTreeMap::from([(1, 1)]), BTreeSet::new()),
642 (BTreeMap::new(), BTreeSet::from([2])),
643 (BTreeMap::from([(3, 1_000_000)]), BTreeSet::new()),
644 (BTreeMap::from([(4, 2)]), BTreeSet::from([4, 5])),
645 (BTreeMap::from([(0, 1)]), BTreeSet::new()),
646 (BTreeMap::from([(u64::MAX, 3)]), BTreeSet::new()),
647 (
648 (1..=32).map(|replica| (replica, replica)).collect(),
649 BTreeSet::new(),
650 ),
651 (BTreeMap::from([(0, 5)]), BTreeSet::from([0])),
652 (BTreeMap::new(), BTreeSet::from([0, u64::MAX])),
653 (
654 BTreeMap::from([(11, 1_000_000), (12, 1_000_000)]),
655 BTreeSet::new(),
656 ),
657 (BTreeMap::from([(8, 1_000_000 - 1)]), BTreeSet::new()),
658 (BTreeMap::from([(1, 4), (3, 2)]), BTreeSet::from([2, 9])),
659 (BTreeMap::from([(1, 2), (1, 4)]), BTreeSet::new()),
660 ];
661 let mut constructed = 0;
662 for (entries, zeros) in cases {
663 let actual = VersionVector::from_peer_prefixes(&entries, &zeros).unwrap();
664 let mut expected = VersionVector::new();
665 for (&replica, &prefix) in &entries {
666 for sequence in 1..=prefix {
667 expected.observe(RecordId { replica, sequence });
668 }
669 }
670 for &replica in &zeros {
671 expected.observe(RecordId {
672 replica,
673 sequence: 0,
674 });
675 }
676 assert_eq!(actual, expected);
677 assert_eq!(actual.entries(), &entries);
678 assert_eq!(actual.zero_replicas(), &zeros);
679 let mut local = EventLog::new();
680 let replicas: BTreeSet<_> = entries
681 .keys()
682 .chain(zeros.iter())
683 .copied()
684 .chain([0, 99, u64::MAX])
685 .collect();
686 for replica in replicas {
687 let prefix = entries.get(&replica).copied().unwrap_or(0);
688 let sequences = BTreeSet::from([0, 1, prefix, prefix + 1, u64::MAX]);
689 for sequence in sequences {
690 let id = RecordId { replica, sequence };
691 assert_eq!(
692 local.insert_record(
693 &GSet::<u64>::new(),
694 Record {
695 id,
696 delta: sequence
697 }
698 ),
699 if sequence == 0 {
700 Admission::Invalid(WireError::ZeroSequenceRecord {
701 replica,
702 kind: "G-Set",
703 })
704 } else {
705 Admission::Accepted
706 }
707 );
708 }
709 }
710 assert_eq!(local.since(&actual), local.since(&expected));
711 let mut actual_next = actual.clone();
713 let mut expected_next = expected.clone();
714 for sequence in [0, 1, 3, 2, 2, 0] {
715 let id = RecordId {
716 replica: 99,
717 sequence,
718 };
719 actual_next.observe(id);
720 expected_next.observe(id);
721 assert_eq!(actual_next, expected_next);
722 }
723 constructed += 1;
724 }
725 assert_eq!(constructed, 14);
726 }
727
728 #[test]
729 fn refused_peer_map_leaves_since_on_previous_version_unchanged() {
730 let mut local = EventLog::new();
731 let acknowledged = local.append(&mut GSet::<u64>::new(), 7, 10u64).unwrap();
732 let missing = local.append(&mut GSet::<u64>::new(), 7, 20u64).unwrap();
733 let remote_version =
734 VersionVector::from_peer_prefixes(&BTreeMap::from([(7, 1)]), &BTreeSet::new()).unwrap();
735 let before = local.since(&remote_version);
736 {
737 let (prefix, error) = (0, VersionVectorError::ZeroPrefix { replica: 7 });
738 assert_eq!(
739 VersionVector::from_peer_prefixes(&BTreeMap::from([(7, prefix)]), &BTreeSet::new()),
740 Err(error)
741 );
742 let after = local.since(&remote_version);
743 assert_eq!(after, before);
744 assert_eq!(after.len(), 1);
745 assert_eq!(after[0].id, missing);
746 assert!(remote_version.includes(acknowledged));
747 assert!(!remote_version.includes(missing));
748 }
749 assert_eq!(local.since(&VersionVector::new()).len(), 2);
750 }
751
752 #[test]
753 fn accepted_peer_vector_drives_since_and_anti_entropy() {
754 let mut local = EventLog::new();
755 for id in [
756 RecordId {
757 replica: 1,
758 sequence: 0,
759 },
760 RecordId {
761 replica: 1,
762 sequence: 1,
763 },
764 RecordId {
765 replica: 1,
766 sequence: 2,
767 },
768 RecordId {
769 replica: 1,
770 sequence: 3,
771 },
772 RecordId {
773 replica: 2,
774 sequence: 0,
775 },
776 RecordId {
777 replica: 2,
778 sequence: 1,
779 },
780 RecordId {
781 replica: 2,
782 sequence: 2,
783 },
784 ] {
785 assert_eq!(
786 local.insert_record(&GSet::<u64>::new(), Record { id, delta: 7u64 }),
787 if id.sequence == 0 {
788 Admission::Invalid(WireError::ZeroSequenceRecord {
789 replica: id.replica,
790 kind: "G-Set",
791 })
792 } else {
793 Admission::Accepted
794 }
795 );
796 }
797 let peer =
798 VersionVector::from_peer_prefixes(&BTreeMap::from([(1, 2)]), &BTreeSet::from([2]))
799 .unwrap();
800 assert_eq!(local.since(&peer).len(), 3);
801
802 let malicious =
803 VersionVector::from_peer_prefixes(&BTreeMap::from([(1, u64::MAX)]), &BTreeSet::new())
804 .unwrap();
805 let mut transport = InMemoryTransport::new();
806 transport.subscribe(1);
807 transport.subscribe(2);
808 queue_anti_entropy(&mut transport, 1, 2, &local, &malicious).unwrap();
809 assert_eq!(transport.pending_len(), 1);
810 assert_eq!(transport.drain(2)[0].records.len(), 2);
811 }
812}
813
814#[cfg(test)]
815mod wire_helper_tests {
816 use super::*;
817 #[derive(Debug, PartialEq)]
818 struct Stranger {
819 replica: u64,
820 tally: u64,
821 }
822 impl WireDecode for Stranger {
823 fn decode_wire(cursor: &mut WireCursor<'_>) -> Result<Self, WireError> {
824 if cursor.read_u8()? != 0x10 {
825 return Err(WireError::InvalidTag);
826 }
827 Ok(Self {
828 replica: cursor.read_u64()?,
829 tally: cursor.read_u64()?,
830 })
831 }
832 }
833
834 #[test]
835 fn external_length_extremes_are_checked() {
836 for len in [0, 1, u32::MAX] {
837 let mut bytes = Vec::new();
838 write_u32(&mut bytes, len);
839 let mut cursor = WireCursor::new(&bytes);
840 let expected = usize::try_from(len).map_err(|_| WireError::LengthOverflow);
841 assert_eq!(cursor.read_len(), expected);
842 assert!(cursor.is_empty());
843 }
844 if let Ok(too_large) = usize::try_from(u64::from(u32::MAX) + 1) {
845 let mut bytes = vec![99];
846 assert_eq!(
847 write_len(&mut bytes, too_large),
848 Err(WireError::LengthOverflow)
849 );
850 assert_eq!(bytes, vec![99]);
851 }
852 }
853
854 #[test]
855 fn external_tag_and_trailing_bytes_are_checked() {
856 let mut cursor = WireCursor::new(&[3, 4]);
857 assert_eq!(read_tag(&mut cursor, 2), Err(WireError::InvalidTag));
858 assert_eq!(read_tag(&mut cursor, 4), Ok(()));
859 assert_eq!(read_tag(&mut cursor, 4), Err(WireError::UnexpectedEof));
860 assert!(cursor.is_empty());
861 let mut bytes = GCounterDelta {
862 replica: 2,
863 tally: 7,
864 }
865 .to_wire_bytes()
866 .unwrap();
867 bytes.push(0);
868 assert_eq!(
869 Stranger::from_wire_bytes(&bytes),
870 Err(WireError::TrailingBytes)
871 );
872 }
873}