1use std::{cmp, collections::HashMap, io, ops::RangeInclusive};
2
3use bytes::BytesMut;
4use opentelemetry::propagation::{Extractor, Injector};
5use rangemap::RangeInclusiveSet;
6use serde::{Deserialize, Serialize};
7use speedy::{Readable, Writable};
8use tokio_util::codec::{Decoder, LengthDelimitedCodec};
9use tracing::warn;
10
11use crate::{
12 actor::ActorId,
13 agent::{Booked, Bookie},
14 base::{CrsqlDbVersion, CrsqlSeq},
15 broadcast::{ChangeV1, Timestamp},
16};
17
18#[derive(Debug, Clone, PartialEq, Readable, Writable)]
19pub enum SyncMessage {
20 V1(SyncMessageV1),
21}
22
23#[derive(Debug, Clone, PartialEq, Readable, Writable)]
24pub enum SyncMessageV1 {
25 State(SyncStateV1),
26 Changeset(ChangeV1),
27 Clock(Timestamp),
28 Rejection(SyncRejectionV1),
29 Request(SyncRequestV1),
30}
31
32#[derive(Debug, Default, Clone, PartialEq, Readable, Writable)]
33pub struct SyncTraceContextV1 {
34 pub traceparent: Option<String>,
35 pub tracestate: Option<String>,
36}
37
38impl Injector for SyncTraceContextV1 {
39 fn set(&mut self, key: &str, value: String) {
40 match key {
41 "traceparent" if !value.is_empty() => self.traceparent = Some(value),
42 "tracestate" if !value.is_empty() => self.tracestate = Some(value),
43 _ => {}
44 }
45 }
46}
47
48impl Extractor for SyncTraceContextV1 {
49 fn get(&self, key: &str) -> Option<&str> {
50 match key {
51 "traceparent" => self.traceparent.as_deref(),
52 "tracestate" => self.tracestate.as_deref(),
53 _ => None,
54 }
55 }
56
57 fn keys(&self) -> Vec<&str> {
58 let mut v = Vec::with_capacity(2);
59 if self.traceparent.is_some() {
60 v.push("traceparent");
61 }
62 if self.tracestate.is_some() {
63 v.push("tracestate");
64 }
65 v
66 }
67}
68
69pub type SyncRequestV1 = Vec<(ActorId, Vec<SyncNeedV1>)>;
70
71#[derive(Debug, thiserror::Error, Clone, PartialEq, Readable, Writable)]
72pub enum SyncRejectionV1 {
73 #[error("max concurrency reached")]
74 MaxConcurrencyReached,
75 #[error("different cluster")]
76 DifferentCluster,
77}
78
79#[derive(Debug, Default, Clone, PartialEq, Readable, Writable, Serialize, Deserialize)]
80pub struct SyncStateV1 {
81 pub actor_id: ActorId,
82 pub heads: HashMap<ActorId, CrsqlDbVersion>,
83 pub need: HashMap<ActorId, Vec<RangeInclusive<CrsqlDbVersion>>>,
84 pub partial_need: HashMap<ActorId, HashMap<CrsqlDbVersion, Vec<RangeInclusive<CrsqlSeq>>>>,
85 #[speedy(default_on_eof)]
86 pub last_cleared_ts: Option<Timestamp>,
87}
88
89impl SyncStateV1 {
90 pub fn need_len(&self) -> u64 {
91 self.need
92 .values()
93 .flat_map(|v| v.iter().map(|range| (range.end().0 - range.start().0) + 1))
94 .sum::<u64>()
95 + (
96 self.partial_need
97 .values()
98 .flat_map(|partials| {
99 partials.values().flat_map(|ranges| {
100 ranges
101 .iter()
102 .map(|range| (range.end().0 - range.start().0) + 1)
103 })
104 })
105 .sum::<u64>()
106 / 50
107 )
109 }
110
111 pub fn need_len_for_actor(&self, actor_id: &ActorId) -> u64 {
112 self.need
113 .get(actor_id)
114 .map(|v| {
115 v.iter()
116 .map(|range| (range.end().0 - range.start().0) + 1)
117 .sum()
118 })
119 .unwrap_or(0)
120 + self
121 .partial_need
122 .get(actor_id)
123 .map(|partials| partials.len() as u64)
124 .unwrap_or(0)
125 }
126
127 pub fn compute_available_needs(
128 &self,
129 other: &SyncStateV1,
130 ) -> HashMap<ActorId, Vec<SyncNeedV1>> {
131 let mut needs: HashMap<ActorId, Vec<SyncNeedV1>> = HashMap::new();
132
133 for (actor_id, head) in other.heads.iter() {
134 if *actor_id == self.actor_id {
135 continue;
136 }
137 if *head == CrsqlDbVersion(0) {
138 warn!(actor_id = %other.actor_id, "sent a 0 head version for actor id {}", actor_id);
139 continue;
140 }
141 let other_haves = {
142 let mut haves =
143 RangeInclusiveSet::from_iter([(CrsqlDbVersion(1)..=*head)].into_iter());
144
145 if let Some(other_need) = other.need.get(actor_id) {
147 for need in other_need.iter() {
148 haves.remove(need.clone());
150 }
151 }
152
153 if let Some(other_partials) = other.partial_need.get(actor_id) {
155 for (v, _) in other_partials.iter() {
156 haves.remove(*v..=*v);
157 }
158 }
159
160 haves
162 };
163
164 if let Some(our_need) = self.need.get(actor_id) {
165 for range in our_need.iter() {
166 for overlap in other_haves.overlapping(range) {
167 let start = cmp::max(range.start(), overlap.start());
168 let end = cmp::min(range.end(), overlap.end());
169 needs.entry(*actor_id).or_default().push(SyncNeedV1::Full {
170 versions: *start..=*end,
171 })
172 }
173 }
174 }
175
176 if let Some(our_partials) = self.partial_need.get(actor_id) {
177 for (v, seqs) in our_partials.iter() {
178 if other_haves.contains(v) {
179 needs
180 .entry(*actor_id)
181 .or_default()
182 .push(SyncNeedV1::Partial {
183 version: *v,
184 seqs: seqs.clone(),
185 });
186 } else if let Some(other_seqs) = other
187 .partial_need
188 .get(actor_id)
189 .and_then(|versions| versions.get(v))
190 {
191 let max_other_seq = other_seqs.iter().map(|range| *range.end()).max();
192 let max_our_seq = seqs.iter().map(|range| *range.end()).max();
193
194 let end_seq = cmp::max(max_other_seq, max_our_seq);
195
196 if let Some(end) = end_seq {
197 let mut other_seqs_haves =
198 RangeInclusiveSet::from_iter([CrsqlSeq(0)..=end]);
199
200 for seqs in other_seqs.iter() {
201 other_seqs_haves.remove(seqs.clone());
202 }
203
204 let seqs = seqs
205 .iter()
206 .flat_map(|range| {
207 other_seqs_haves
208 .overlapping(range)
209 .map(|overlap| {
210 let start = cmp::max(range.start(), overlap.start());
211 let end = cmp::min(range.end(), overlap.end());
212 *start..=*end
213 })
214 .collect::<Vec<RangeInclusive<CrsqlSeq>>>()
215 })
216 .collect::<Vec<RangeInclusive<CrsqlSeq>>>();
217
218 if !seqs.is_empty() {
219 needs
220 .entry(*actor_id)
221 .or_default()
222 .push(SyncNeedV1::Partial { version: *v, seqs });
223 }
224 }
225 }
226 }
227 }
228
229 let missing = match self.heads.get(actor_id) {
230 Some(our_head) => {
231 if head > our_head {
232 Some((*our_head + 1)..=*head)
233 } else {
234 None
235 }
236 }
237 None => Some(CrsqlDbVersion(1)..=*head),
238 };
239
240 if let Some(missing) = missing {
241 needs
242 .entry(*actor_id)
243 .or_default()
244 .push(SyncNeedV1::Full { versions: missing });
245 }
246 }
247
248 needs
249 }
250}
251
252#[derive(Debug, Clone, PartialEq, Readable, Writable)]
253pub enum SyncNeedV1 {
254 Full {
255 versions: RangeInclusive<CrsqlDbVersion>,
256 },
257 Partial {
258 version: CrsqlDbVersion,
259 seqs: Vec<RangeInclusive<CrsqlSeq>>,
260 },
261 Empty {
262 ts: Option<Timestamp>,
263 },
264}
265
266impl SyncNeedV1 {
267 pub fn count(&self) -> usize {
268 match self {
269 SyncNeedV1::Full { versions } => (versions.end().0 - versions.start().0) as usize + 1,
270 SyncNeedV1::Partial { .. } => 1,
271 SyncNeedV1::Empty { .. } => 1,
272 }
273 }
274}
275
276impl From<SyncStateV1> for SyncMessage {
277 fn from(value: SyncStateV1) -> Self {
278 SyncMessage::V1(SyncMessageV1::State(value))
279 }
280}
281
282#[tracing::instrument(skip_all, level = "debug")]
284pub async fn generate_sync(bookie: &Bookie, self_actor_id: ActorId) -> SyncStateV1 {
285 let mut state = SyncStateV1 {
286 actor_id: self_actor_id,
287 ..Default::default()
288 };
289
290 let actors: Vec<(ActorId, Booked)> = {
291 bookie
292 .read::<&str, _>("generate_sync", None)
293 .await
294 .iter()
295 .map(|(k, v)| (*k, v.clone()))
296 .collect()
297 };
298
299 for (actor_id, booked) in actors {
300 let bookedr = booked.read("generate_sync", actor_id.as_simple()).await;
301
302 let last_version = match bookedr.last() {
303 None => continue,
304 Some(v) => v,
305 };
306
307 let need: Vec<_> = bookedr.needed().iter().cloned().collect();
308
309 if !need.is_empty() {
310 state.need.insert(actor_id, need);
311 }
312
313 {
314 for (v, partial) in bookedr
315 .partials
316 .iter()
317 .filter(|(_, partial)| !partial.is_complete())
319 {
320 state.partial_need.entry(actor_id).or_default().insert(
321 *v,
322 partial
323 .seqs
324 .gaps(&(CrsqlSeq(0)..=partial.last_seq))
325 .collect(),
326 );
327 }
328 }
329 state.heads.insert(actor_id, last_version);
330 }
331
332 state
333}
334
335#[derive(Debug, thiserror::Error)]
336pub enum SyncMessageEncodeError {
337 #[error(transparent)]
338 Encode(#[from] speedy::Error),
339 #[error(transparent)]
340 Io(#[from] io::Error),
341}
342
343#[derive(Debug, thiserror::Error)]
344pub enum SyncMessageDecodeError {
345 #[error(transparent)]
346 Decode(#[from] speedy::Error),
347 #[error("corrupted message, crc mismatch (got: {0}, expected {1})")]
348 Corrupted(u32, u32),
349 #[error(transparent)]
350 Io(#[from] io::Error),
351}
352
353impl SyncMessage {
354 pub fn state(&self) -> Option<&SyncStateV1> {
355 match self {
356 SyncMessage::V1(SyncMessageV1::State(state)) => Some(state),
357 _ => None,
358 }
359 }
360
361 pub fn from_slice<S: AsRef<[u8]>>(slice: S) -> Result<Self, speedy::Error> {
362 Self::read_from_buffer(slice.as_ref())
363 }
364
365 pub fn from_buf(buf: &mut BytesMut) -> Result<Self, SyncMessageDecodeError> {
366 Ok(Self::from_slice(buf)?)
367 }
368
369 pub fn decode(
370 codec: &mut LengthDelimitedCodec,
371 buf: &mut BytesMut,
372 ) -> Result<Option<Self>, SyncMessageDecodeError> {
373 Ok(match codec.decode(buf)? {
374 Some(mut buf) => Some(Self::from_buf(&mut buf)?),
375 None => None,
376 })
377 }
378}
379
380#[cfg(test)]
381mod tests {
382 use uuid::Uuid;
383
384 use super::*;
385
386 #[test]
387 fn test_compute_available_needs() {
388 let actor1 = ActorId(Uuid::new_v4());
389
390 let mut our_state = SyncStateV1::default();
391 our_state.heads.insert(actor1, CrsqlDbVersion(10));
392
393 let mut other_state = SyncStateV1::default();
394 other_state.heads.insert(actor1, CrsqlDbVersion(13));
395
396 assert_eq!(
397 our_state.compute_available_needs(&other_state),
398 [(
399 actor1,
400 vec![SyncNeedV1::Full {
401 versions: CrsqlDbVersion(11)..=CrsqlDbVersion(13)
402 }]
403 )]
404 .into()
405 );
406
407 our_state
408 .need
409 .entry(actor1)
410 .or_default()
411 .push(CrsqlDbVersion(2)..=CrsqlDbVersion(5));
412 our_state
413 .need
414 .entry(actor1)
415 .or_default()
416 .push(CrsqlDbVersion(7)..=CrsqlDbVersion(7));
417
418 assert_eq!(
419 our_state.compute_available_needs(&other_state),
420 [(
421 actor1,
422 vec![
423 SyncNeedV1::Full {
424 versions: CrsqlDbVersion(2)..=CrsqlDbVersion(5)
425 },
426 SyncNeedV1::Full {
427 versions: CrsqlDbVersion(7)..=CrsqlDbVersion(7)
428 },
429 SyncNeedV1::Full {
430 versions: CrsqlDbVersion(11)..=CrsqlDbVersion(13)
431 }
432 ]
433 )]
434 .into()
435 );
436
437 our_state.partial_need.insert(
438 actor1,
439 [(
440 CrsqlDbVersion(9),
441 vec![CrsqlSeq(100)..=CrsqlSeq(120), CrsqlSeq(130)..=CrsqlSeq(132)],
442 )]
443 .into(),
444 );
445
446 assert_eq!(
447 our_state.compute_available_needs(&other_state),
448 [(
449 actor1,
450 vec![
451 SyncNeedV1::Full {
452 versions: CrsqlDbVersion(2)..=CrsqlDbVersion(5)
453 },
454 SyncNeedV1::Full {
455 versions: CrsqlDbVersion(7)..=CrsqlDbVersion(7)
456 },
457 SyncNeedV1::Partial {
458 version: CrsqlDbVersion(9),
459 seqs: vec![CrsqlSeq(100)..=CrsqlSeq(120), CrsqlSeq(130)..=CrsqlSeq(132)]
460 },
461 SyncNeedV1::Full {
462 versions: CrsqlDbVersion(11)..=CrsqlDbVersion(13)
463 }
464 ]
465 )]
466 .into()
467 );
468
469 other_state.partial_need.insert(
470 actor1,
471 [(
472 CrsqlDbVersion(9),
473 vec![CrsqlSeq(100)..=CrsqlSeq(110), CrsqlSeq(130)..=CrsqlSeq(130)],
474 )]
475 .into(),
476 );
477
478 assert_eq!(
479 our_state.compute_available_needs(&other_state),
480 [(
481 actor1,
482 vec![
483 SyncNeedV1::Full {
484 versions: CrsqlDbVersion(2)..=CrsqlDbVersion(5)
485 },
486 SyncNeedV1::Full {
487 versions: CrsqlDbVersion(7)..=CrsqlDbVersion(7)
488 },
489 SyncNeedV1::Partial {
490 version: CrsqlDbVersion(9),
491 seqs: vec![CrsqlSeq(111)..=CrsqlSeq(120), CrsqlSeq(131)..=CrsqlSeq(132)]
492 },
493 SyncNeedV1::Full {
494 versions: CrsqlDbVersion(11)..=CrsqlDbVersion(13)
495 }
496 ]
497 )]
498 .into()
499 );
500 }
501}