safemesh_crdt/
ownership.rs1use crate::{GCounterDelta, OrSetDelta, RecordId};
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub struct WriterConfig {
9 pub writers: u64,
10 pub writer: u64,
11}
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub struct Refused;
15
16impl WriterConfig {
17 pub fn validate(self) -> Result<(), Refused> {
18 if self.writer < self.writers {
19 Ok(())
20 } else {
21 Err(Refused)
22 }
23 }
24}
25
26#[derive(Clone, Copy, Debug)]
29pub struct WriteContext {
30 pub config: WriterConfig,
31 pub held: bool,
32 pub generation: u64,
33 pub current_generation: u64,
34 pub local: bool,
35}
36
37#[derive(Clone, Copy, Debug)]
40pub enum OwnedPayload {
41 Counter(u64),
42 Add(u64),
43 Remove,
44}
45
46pub fn next_sequence(last: u64) -> Option<u64> {
47 last.checked_add(1)
48}
49
50pub fn allocate_token(writers: u64, author: u64, sequence: u64) -> Option<u64> {
53 if author >= writers || sequence == 0 {
54 return None;
55 }
56 sequence.checked_mul(writers)?.checked_add(author)
57}
58
59pub fn refuses(ctx: WriteContext, id: RecordId, payload: OwnedPayload) -> bool {
62 !(ctx.config.writer < ctx.config.writers
63 && ctx.held
64 && ctx.generation > 0
65 && ctx.generation == ctx.current_generation
66 && id.replica < ctx.config.writers
67 && id.sequence > 0
68 && (!ctx.local || id.replica == ctx.config.writer)
69 && match payload {
70 OwnedPayload::Counter(coordinate) => coordinate == id.replica,
71 OwnedPayload::Add(token) => {
72 allocate_token(ctx.config.writers, id.replica, id.sequence) == Some(token)
73 }
74 OwnedPayload::Remove => true,
75 })
76}
77
78pub trait OwnedDelta {
80 fn owned_payload(&self) -> OwnedPayload;
81}
82impl OwnedDelta for GCounterDelta {
83 fn owned_payload(&self) -> OwnedPayload {
84 OwnedPayload::Counter(self.replica as u64)
85 }
86}
87impl<T> OwnedDelta for OrSetDelta<T, u64> {
88 fn owned_payload(&self) -> OwnedPayload {
89 match self {
90 Self::Add { token, .. } => OwnedPayload::Add(*token),
91 Self::Remove { .. } => OwnedPayload::Remove,
92 }
93 }
94}
95
96pub fn check_counter_record(
99 writers: usize,
100 id: RecordId,
101 delta: &GCounterDelta,
102) -> Result<(), Refused> {
103 let ctx = WriteContext {
104 config: WriterConfig {
105 writers: writers as u64,
106 writer: 0,
107 },
108 held: true,
109 generation: 1,
110 current_generation: 1,
111 local: false,
112 };
113 if refuses(ctx, id, delta.owned_payload()) {
114 Err(Refused)
115 } else {
116 Ok(())
117 }
118}