Skip to main content

safemesh_crdt/
lib.rs

1// SafeMesh — delta-state CRDT convergence, built on crdt-lean.
2// Copyright (C) 2026 Ben Cassie
3// SPDX-License-Identifier: Apache-2.0
4
5//! Delta-state G-Counter and PN-Counter for constrained mesh links.
6//!
7//! This crate is the PRODUCT half of the SafeMesh verification-guided loop:
8//! a thin `no_std + alloc` implementation of exactly the model proven in
9//! `lean/SafeMesh/` (carrier = join-semilattice, merge = join, delta =
10//! single-coordinate bump), held to those proofs by a differential
11//! conformance test (`tests/conformance.rs`) that replays a Lean-emitted
12//! JSON corpus through this code and compares state vectors, sets, read vectors,
13//! numeric values, ownership decisions and optional allocation/sequence results
14//! with the parsed expectations.
15//!
16//! What is PROVEN (in Lean, kernel-checked) vs what is TESTED (here): the
17//! Lean theorems are universal; this crate is checked against them over a
18//! finite corpus. The bridge (Lean compiled eval → JSON → this crate) is the
19//! named trusted component. See the repo README for the full TCB statement.
20
21#![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
103/// State-based merge contract: `merge` is expected to be a semilattice join.
104///
105/// For in-house types, this contract is backed by the Lean proof suite and
106/// differential conformance corpus. For user-defined types, it is a tested
107/// contract enforced by the laws harness, not a proof. Admission is a separate
108/// obligation: user-defined CRDTs must implement [`Crdt::validate_record`].
109pub trait Mergeable {
110    /// Join compatible states, returning an error if their replica domains differ.
111    fn merge(&mut self, other: &Self) -> Result<(), MergeError>;
112}
113
114/// Delta application surface for CRDT product types.
115///
116/// Every implementation, including user-defined types, must explicitly provide
117/// [`Crdt::validate_record`]. The compiler checks its presence; behavioral tests
118/// must check its contract (the merge laws alone do not establish admission).
119/// An implementation in another module cannot inherit permissive admission:
120///
121/// ```compile_fail,E0046
122/// mod downstream {
123///     use safemesh_crdt::{Crdt, Mergeable};
124///     pub struct MissingValidation;
125///     impl Mergeable for MissingValidation {
126///         fn merge(&mut self, _: &Self) -> Result<(), safemesh_crdt::MergeError> {
127///             Ok(())
128///         }
129///     }
130///     impl Crdt for MissingValidation {
131///         type Delta = ();
132///         fn apply_delta(&mut self, _: ()) {}
133///     }
134/// }
135/// ```
136pub trait Crdt: Mergeable {
137    type Delta;
138
139    /// Fixed replica domain, or `None` for CRDTs without a fixed arity.
140    fn replica_count(&self) -> Option<usize> {
141        None
142    }
143
144    /// Validate a decoded record before admission or replay into this carrier.
145    ///
146    /// Refuse records outside the carrier's shape or ownership domain: no
147    /// carrier of that same shape may legitimately apply such a record. For
148    /// example, a fixed-width counter refuses an out-of-range coordinate or a
149    /// coordinate belonging to a different record author.
150    ///
151    /// Accept legitimate replay even when the current state absorbs the delta
152    /// (duplicates, lower tallies, losing writes, or existing tombstones). On a
153    /// fresh carrier of the same shape, it must yield the state the record
154    /// denotes, including lattice bottom for an empty remove. A lack of visible
155    /// change is not evidence of invalidity. Types with a total typed delta
156    /// domain may explicitly return `Ok(())`; they have no domain refusal case.
157    /// This check must not mutate state. Wire decoding and log-shape validation
158    /// are separate checks performed before this method.
159    fn validate_record(&self, id: RecordId, delta: &Self::Delta) -> Result<(), WireError>;
160
161    fn apply_delta(&mut self, delta: Self::Delta);
162}
163
164/// Error raised by full-state merge (`merge` or `try_merge`).
165///
166/// The Lean model fixes the replica set (`Fin n`), so a length mismatch is
167/// unrepresentable there. At the Rust boundary an untrusted peer can hand us a
168/// state vector of a different width; merging it by `zip` would silently drop
169/// the trailing coordinates (WS1 silent state loss). The checked path surfaces
170/// the mismatch as an error instead of corrupting state.
171#[derive(Clone, Copy, Debug, PartialEq, Eq)]
172pub enum MergeError {
173    /// The two counters cover a different number of replicas.
174    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                    // Exercise saturated prefixes without allocating 2^64 records.
226                    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    // Encode conflicting occurrences as inert input to the decoder's collision gate.
364    // The production encoder derives all frame bytes, lengths and checksums.
365    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        // Seed the boundary; materializing all 2^64 - 1 records is infeasible.
593        // This exercises the terminal transition, not a full retained history.
594        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            // Subsequent contiguous, gap, repeated and zero observations agree too.
712            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}