Skip to main content

klukai_agent/api/peer/
mod.rs

1use std::cmp;
2use std::collections::{HashMap, VecDeque};
3use std::net::SocketAddr;
4use std::sync::Arc;
5use std::time::{Duration, Instant};
6
7use antithesis_sdk::assert_sometimes;
8
9use bytes::{BufMut, BytesMut};
10use futures::stream::FuturesUnordered;
11use futures::{Future, Stream, TryFutureExt, TryStreamExt};
12use itertools::Itertools;
13use klukai_types::actor::ClusterId;
14use klukai_types::agent::{Agent, SplitPool};
15use klukai_types::base::{CrsqlDbVersion, CrsqlDbVersionRange, CrsqlSeq, CrsqlSeqRange};
16use klukai_types::broadcast::{
17    BiPayload, BiPayloadV1, ChangeSource, ChangeV1, Changeset, Timestamp,
18};
19use klukai_types::change::{Change, ChunkedChanges, row_to_change};
20use klukai_types::config::{GossipConfig, TlsClientConfig};
21use klukai_types::sync::{
22    SyncMessage, SyncMessageEncodeError, SyncMessageV1, SyncNeedV1, SyncRejectionV1, SyncRequestV1,
23    SyncStateV1, SyncTraceContextV1, generate_sync,
24};
25use metrics::counter;
26use quinn::crypto::rustls::{QuicClientConfig, QuicServerConfig};
27use quinn::{RecvStream, SendStream, WriteError};
28use rangemap::RangeInclusiveSet;
29use rusqlite::{Connection, named_params};
30use speedy::Writable;
31use std::string::String;
32use tokio::io::AsyncWriteExt;
33use tokio::sync::mpsc::{self, Sender, unbounded_channel};
34use tokio::task::block_in_place;
35use tokio::time::timeout;
36use tokio_stream::StreamExt as TokioStreamExt;
37use tokio_stream::wrappers::{ReceiverStream, UnboundedReceiverStream};
38// use tokio_stream::StreamExt as TokioStreamExt;
39use tokio_util::codec::{Encoder, FramedRead, LengthDelimitedCodec};
40use tracing::{Instrument, debug, error, info, info_span, trace, warn};
41use tracing_opentelemetry::OpenTelemetrySpanExt;
42
43use crate::agent::SyncRecvError;
44use crate::quinn_plaintext;
45use crate::transport::{Transport, TransportError};
46
47use klukai_types::{actor::ActorId, agent::Bookie};
48
49#[derive(Debug, thiserror::Error)]
50pub enum SyncError {
51    #[error(transparent)]
52    Send(#[from] SyncSendError),
53    #[error(transparent)]
54    BiPayloadSend(#[from] BiPayloadSendError),
55    #[error(transparent)]
56    Recv(#[from] SyncRecvError),
57    #[error(transparent)]
58    Rejection(#[from] SyncRejectionV1),
59    #[error(transparent)]
60    Transport(#[from] TransportError),
61}
62
63#[derive(Debug, thiserror::Error)]
64pub enum SyncSendError {
65    #[error(transparent)]
66    Io(#[from] std::io::Error),
67
68    #[error(transparent)]
69    Write(#[from] quinn::WriteError),
70
71    #[error(transparent)]
72    Rusqlite(#[from] rusqlite::Error),
73
74    #[error("expected a column, there were none")]
75    NoMoreColumns,
76
77    #[error("expected column '{0}', got none")]
78    MissingColumn(&'static str),
79
80    #[error("expected column '{0}', got '{1}'")]
81    WrongColumn(&'static str, String),
82
83    #[error("unexpected column '{0}'")]
84    UnexpectedColumn(String),
85
86    #[error("unknown resource state")]
87    UnknownResourceState,
88
89    #[error("unknown resource type")]
90    UnknownResourceType,
91
92    #[error("wrong ip byte len: {0}")]
93    WrongIpByteLength(usize),
94
95    #[error("could not encode message: {0}")]
96    Encode(#[from] SyncMessageEncodeError),
97
98    #[error("sync send channel is closed")]
99    ChannelClosed,
100}
101
102#[derive(Debug, thiserror::Error)]
103pub enum BiPayloadSendError {
104    #[error("could not encode payload: {0}")]
105    Encode(#[from] BiPayloadEncodeError),
106    #[error(transparent)]
107    Io(#[from] std::io::Error),
108    #[error(transparent)]
109    Write(#[from] quinn::WriteError),
110}
111
112#[derive(Debug, thiserror::Error)]
113pub enum BiPayloadEncodeError {
114    #[error(transparent)]
115    Encode(#[from] speedy::Error),
116    #[error(transparent)]
117    Io(#[from] std::io::Error),
118}
119
120fn build_quinn_transport_config(config: &GossipConfig) -> quinn::TransportConfig {
121    let mut transport_config = quinn::TransportConfig::default();
122
123    // max idle timeout
124    transport_config.max_idle_timeout(Some(
125        Duration::from_secs(config.idle_timeout_secs as u64)
126            .try_into()
127            .unwrap(),
128    ));
129
130    // max concurrent bidirectional streams
131    transport_config.max_concurrent_bidi_streams(32u32.into());
132
133    // max concurrent unidirectional streams
134    transport_config.max_concurrent_uni_streams(256u32.into());
135
136    if let Some(max_mtu) = config.max_mtu {
137        info!("Setting maximum MTU for QUIC at {max_mtu}");
138        transport_config.initial_mtu(max_mtu);
139        transport_config.min_mtu(max_mtu);
140        // disable discovery
141        transport_config.mtu_discovery_config(None);
142    }
143
144    if config.disable_gso {
145        transport_config.enable_segmentation_offload(false);
146    }
147
148    transport_config
149}
150
151async fn build_quinn_server_config(config: &GossipConfig) -> eyre::Result<quinn::ServerConfig> {
152    let mut server_config = if config.plaintext {
153        quinn_plaintext::server_config()
154    } else {
155        let tls = config
156            .tls
157            .as_ref()
158            .ok_or_else(|| eyre::eyre!("either plaintext or a tls config is required"))?;
159
160        let key = tokio::fs::read(&tls.key_file).await?;
161        let key = if tls.key_file.extension() == Some("der") {
162            rustls_pki_types::PrivateKeyDer::Pkcs8(rustls_pki_types::PrivatePkcs8KeyDer::from(key))
163        } else {
164            let pkcs8 = {
165                let result: Result<Vec<_>, _> =
166                    rustls_pemfile::pkcs8_private_keys(&mut &*key).collect();
167                result?
168            };
169            match pkcs8.into_iter().next() {
170                Some(x) => rustls_pki_types::PrivateKeyDer::Pkcs8(x),
171                None => {
172                    let rsa = {
173                        let result: Result<Vec<_>, _> =
174                            rustls_pemfile::rsa_private_keys(&mut &*key).collect();
175                        result?
176                    };
177                    match rsa.into_iter().next() {
178                        Some(x) => rustls_pki_types::PrivateKeyDer::Pkcs1(x),
179                        None => {
180                            eyre::bail!("no private keys found");
181                        }
182                    }
183                }
184            }
185        };
186
187        let certs = tokio::fs::read(&tls.cert_file).await?;
188        let certs = if tls.cert_file.extension() == Some("der") {
189            vec![rustls_pki_types::CertificateDer::from(certs)]
190        } else {
191            {
192                let result: Result<Vec<_>, _> = rustls_pemfile::certs(&mut &*certs).collect();
193                result?
194            }
195            .into_iter()
196            .map(|x| rustls_pki_types::CertificateDer::from(x.as_ref().to_vec()))
197            .collect()
198        };
199
200        let server_crypto = rustls::ServerConfig::builder();
201
202        let server_crypto = if tls.client.is_some() {
203            let ca_file = match &tls.ca_file {
204                None => {
205                    eyre::bail!(
206                        "ca_file required in tls config for server client cert auth verification"
207                    );
208                }
209                Some(ca_file) => ca_file,
210            };
211
212            let ca_certs = tokio::fs::read(&ca_file).await?;
213            let ca_certs = if ca_file.extension() == Some("der") {
214                vec![rustls_pki_types::CertificateDer::from(ca_certs)]
215            } else {
216                {
217                    let result: Result<Vec<_>, _> =
218                        rustls_pemfile::certs(&mut &*ca_certs).collect();
219                    result?
220                }
221                .into_iter()
222                .map(|x| rustls_pki_types::CertificateDer::from(x.as_ref().to_vec()))
223                .collect()
224            };
225
226            let mut root_store = rustls::RootCertStore::empty();
227
228            for cert in ca_certs {
229                root_store.add(cert)?;
230            }
231
232            server_crypto.with_client_cert_verifier(
233                rustls::server::WebPkiClientVerifier::builder(Arc::new(root_store))
234                    .build()
235                    .unwrap(),
236            )
237        } else {
238            server_crypto.with_no_client_auth()
239        };
240
241        quinn::ServerConfig::with_crypto(Arc::new(QuicServerConfig::try_from(
242            server_crypto.with_single_cert(certs, key)?,
243        )?))
244    };
245
246    let transport_config = build_quinn_transport_config(config);
247
248    server_config.transport_config(Arc::new(transport_config));
249
250    Ok(server_config)
251}
252
253pub async fn gossip_server_endpoint(config: &GossipConfig) -> eyre::Result<quinn::Endpoint> {
254    let server_config = build_quinn_server_config(config).await?;
255
256    Ok(quinn::Endpoint::server(server_config, config.bind_addr)?)
257}
258
259fn client_cert_auth(
260    config: &TlsClientConfig,
261) -> eyre::Result<(
262    Vec<rustls_pki_types::CertificateDer<'_>>,
263    rustls_pki_types::PrivateKeyDer<'_>,
264)> {
265    let mut cert_file = std::io::BufReader::new(
266        std::fs::OpenOptions::new()
267            .read(true)
268            .open(&config.cert_file)?,
269    );
270    let certs = {
271        let result: Result<Vec<_>, _> = rustls_pemfile::certs(&mut cert_file).collect();
272        result?
273    }
274    .into_iter()
275    .map(|x| rustls_pki_types::CertificateDer::from(x.as_ref().to_vec()))
276    .collect();
277
278    let mut key_file = std::io::BufReader::new(
279        std::fs::OpenOptions::new()
280            .read(true)
281            .open(&config.key_file)?,
282    );
283    let key = {
284        let result: Result<Vec<_>, _> = rustls_pemfile::pkcs8_private_keys(&mut key_file).collect();
285        result?
286    }
287    .into_iter()
288    .map(rustls_pki_types::PrivateKeyDer::Pkcs8)
289    .next()
290    .ok_or_else(|| eyre::eyre!("could not find client tls key"))?;
291
292    Ok((certs, key))
293}
294
295async fn build_quinn_client_config(config: &GossipConfig) -> eyre::Result<quinn::ClientConfig> {
296    let mut client_config = if config.plaintext {
297        quinn_plaintext::client_config()
298    } else {
299        let tls = config
300            .tls
301            .as_ref()
302            .ok_or_else(|| eyre::eyre!("tls config required"))?;
303
304        let client_crypto = rustls::ClientConfig::builder();
305
306        let client_crypto = if let Some(ca_file) = &tls.ca_file {
307            let ca_certs = tokio::fs::read(&ca_file).await?;
308            let ca_certs = if ca_file.extension() == Some("der") {
309                vec![rustls_pki_types::CertificateDer::from(ca_certs)]
310            } else {
311                {
312                    let result: Result<Vec<_>, _> =
313                        rustls_pemfile::certs(&mut &*ca_certs).collect();
314                    result?
315                }
316                .into_iter()
317                .map(|x| rustls_pki_types::CertificateDer::from(x.as_ref().to_vec()))
318                .collect()
319            };
320
321            let mut root_store = rustls::RootCertStore::empty();
322
323            for cert in ca_certs {
324                root_store.add(cert)?;
325            }
326
327            let client_crypto = client_crypto.with_root_certificates(root_store);
328
329            if let Some(client_config) = &tls.client {
330                let (certs, key) = client_cert_auth(client_config)?;
331                let certs: Vec<rustls_pki_types::CertificateDer<'static>> =
332                    certs.into_iter().map(|c| c.into_owned()).collect();
333                let key: rustls_pki_types::PrivateKeyDer<'static> = key.clone_key();
334                client_crypto.with_client_auth_cert(certs, key)?
335            } else {
336                client_crypto.with_no_client_auth()
337            }
338        } else {
339            if !tls.insecure {
340                eyre::bail!(
341                    "insecure setting needs to be explicitly true if no ca_file is provided"
342                );
343            }
344            let client_crypto = client_crypto
345                .dangerous()
346                .with_custom_certificate_verifier(SkipServerVerification::new());
347
348            if let Some(client_config) = &tls.client {
349                let (certs, key) = client_cert_auth(client_config)?;
350                let certs: Vec<rustls_pki_types::CertificateDer<'static>> =
351                    certs.into_iter().map(|c| c.into_owned()).collect();
352                let key: rustls_pki_types::PrivateKeyDer<'static> = key.clone_key();
353                client_crypto.with_client_auth_cert(certs, key)?
354            } else {
355                client_crypto.with_no_client_auth()
356            }
357        };
358
359        quinn::ClientConfig::new(Arc::new(QuicClientConfig::try_from(client_crypto)?))
360    };
361
362    let mut transport_config = build_quinn_transport_config(config);
363
364    // client should send keepalives!
365    transport_config.keep_alive_interval(Some(Duration::from_secs(
366        config.idle_timeout_secs as u64 / 2,
367    )));
368
369    client_config.transport_config(Arc::new(transport_config));
370
371    Ok(client_config)
372}
373
374pub async fn gossip_client_endpoint(config: &GossipConfig) -> eyre::Result<quinn::Endpoint> {
375    let client_config = build_quinn_client_config(config).await?;
376
377    let mut client = quinn::Endpoint::client(config.client_addr)?;
378
379    client.set_default_client_config(client_config);
380    Ok(client)
381}
382
383/// Dummy certificate verifier that treats any certificate as valid.
384/// NOTE, such verification is vulnerable to MITM attacks, but convenient for testing.
385#[derive(Debug)]
386struct SkipServerVerification;
387
388impl SkipServerVerification {
389    fn new() -> Arc<Self> {
390        Arc::new(Self)
391    }
392}
393
394impl rustls::client::danger::ServerCertVerifier for SkipServerVerification {
395    fn verify_server_cert(
396        &self,
397        _end_entity: &rustls_pki_types::CertificateDer,
398        _intermediates: &[rustls_pki_types::CertificateDer],
399        _server_name: &rustls_pki_types::ServerName,
400        _ocsp_response: &[u8],
401        _now: rustls_pki_types::UnixTime,
402    ) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
403        Ok(rustls::client::danger::ServerCertVerified::assertion())
404    }
405
406    fn verify_tls12_signature(
407        &self,
408        _message: &[u8],
409        _cert: &rustls_pki_types::CertificateDer,
410        _dss: &rustls::DigitallySignedStruct,
411    ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
412        Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
413    }
414
415    fn verify_tls13_signature(
416        &self,
417        _message: &[u8],
418        _cert: &rustls_pki_types::CertificateDer,
419        _dss: &rustls::DigitallySignedStruct,
420    ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
421        Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
422    }
423
424    fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
425        vec![
426            rustls::SignatureScheme::RSA_PKCS1_SHA1,
427            rustls::SignatureScheme::ECDSA_SHA1_Legacy,
428            rustls::SignatureScheme::RSA_PKCS1_SHA256,
429            rustls::SignatureScheme::ECDSA_NISTP256_SHA256,
430            rustls::SignatureScheme::RSA_PKCS1_SHA384,
431            rustls::SignatureScheme::ECDSA_NISTP384_SHA384,
432            rustls::SignatureScheme::RSA_PKCS1_SHA512,
433            rustls::SignatureScheme::ECDSA_NISTP521_SHA512,
434            rustls::SignatureScheme::RSA_PSS_SHA256,
435            rustls::SignatureScheme::RSA_PSS_SHA384,
436            rustls::SignatureScheme::RSA_PSS_SHA512,
437            rustls::SignatureScheme::ED25519,
438            rustls::SignatureScheme::ED448,
439        ]
440    }
441}
442
443const MAX_CHANGES_BYTES_PER_MESSAGE: usize = 8 * 1024;
444const MIN_CHANGES_BYTES_PER_MESSAGE: usize = 1024;
445
446const ADAPT_CHUNK_SIZE_THRESHOLD: Duration = Duration::from_millis(500);
447
448#[allow(clippy::too_many_arguments)]
449fn handle_need(
450    conn: &mut Connection,
451    actor_id: ActorId,
452    need: SyncNeedV1,
453    sender: &Sender<SyncMessage>,
454) -> eyre::Result<()> {
455    debug!(%actor_id, "handle known versions! need: {need:?}");
456
457    let mut empties: RangeInclusiveSet<CrsqlDbVersion> = RangeInclusiveSet::new();
458
459    assert_sometimes!(true, "Corrosion handles sync requests from other nodes");
460    // this is a read transaction!
461    let tx = conn.transaction()?;
462
463    let mut prepped = tx.prepare_cached(
464        "
465        SELECT db_version, MAX(seq) AS last_seq, MAX(ts) AS ts
466            FROM crsql_changes
467            WHERE site_id = :actor_id
468              AND db_version BETWEEN :start AND :end
469            GROUP BY db_version
470            ORDER BY db_version DESC, seq ASC
471    ",
472    )?;
473
474    match need {
475        SyncNeedV1::Full { versions } => {
476            let mut rows = prepped.query(named_params! {
477                ":actor_id": actor_id,
478                ":start": versions.start(),
479                ":end": versions.end(),
480            })?;
481
482            let mut unprocessed = RangeInclusiveSet::new();
483            unprocessed.insert(versions.into());
484
485            loop {
486                let row = match rows.next()? {
487                    Some(row) => row,
488                    None => {
489                        break;
490                    }
491                };
492
493                let version: CrsqlDbVersion = row.get(0)?;
494
495                unprocessed.remove(version..=version);
496
497                let last_seq: CrsqlSeq = row.get(1)?;
498                let ts: Timestamp = row.get(2)?;
499                debug!(%actor_id, ?version, %ts, "not empty");
500
501                let mut prepped = tx.prepare_cached(
502                    r#"
503                        SELECT "table", pk, cid, val, col_version, db_version, seq, site_id, cl, ts
504                            FROM crsql_changes
505                            WHERE site_id = :actor_id
506                              AND db_version = :version
507                            ORDER BY seq ASC
508                    "#,
509                )?;
510
511                let rows = prepped.query_map(
512                    named_params! {
513                        ":actor_id": actor_id,
514                        ":version": version
515                    },
516                    row_to_change,
517                )?;
518
519                debug!(%actor_id, ?version, ?last_seq, "not empty");
520
521                send_change_chunks(
522                    sender,
523                    ChunkedChanges::new(rows, CrsqlSeq(0), last_seq, MAX_CHANGES_BYTES_PER_MESSAGE),
524                    actor_id,
525                    version,
526                    last_seq,
527                    ts,
528                )?;
529            }
530
531            // now process the last unprocessed in case we have partials
532            for versions in unprocessed {
533                for version in CrsqlDbVersionRange::from(versions) {
534                    let (in_gaps, buffered): (bool, bool) = tx
535                        .prepare_cached(
536                            "
537                        SELECT
538                            EXISTS(
539                                SELECT 1
540                                FROM __corro_bookkeeping_gaps
541                                    WHERE actor_id = :actor_id
542                                      AND :version BETWEEN start AND end
543                            ) AS in_gaps,
544                            EXISTS(
545                                SELECT 1
546                                FROM __corro_buffered_changes
547                                    WHERE site_id = :actor_id
548                                      AND db_version = :version
549                            ) AS buffered",
550                        )?
551                        .query_row(
552                            named_params! {
553                                ":actor_id": actor_id,
554                                ":version": version
555                            },
556                            |row| Ok((row.get(0)?, row.get(1)?)),
557                        )?;
558
559                    if !buffered {
560                        if !in_gaps {
561                            // this is an empty!
562                            empties.insert(version..=version);
563                        }
564                        continue;
565                    }
566
567                    let seqs = tx
568                        .prepare_cached("
569                        SELECT start_seq, end_seq, last_seq, ts FROM __corro_seq_bookkeeping WHERE site_id = :actor_id AND db_version = :db_version
570                        ")?.query_map(named_params!{
571                            ":actor_id": actor_id,
572                            ":db_version": version
573                        },|row| Ok((CrsqlSeqRange::new(row.get(0)?, row.get(1)?), row.get(2)?, row.get(3)?)))?.collect::<rusqlite::Result<Vec<(CrsqlSeqRange, CrsqlSeq, Timestamp)>>>()?;
574
575                    for (range_needed, last_seq, ts) in seqs {
576                        let mut prepped = tx.prepare_cached(
577                            r#"
578                                SELECT "table", pk, cid, val, col_version, db_version, seq, site_id, cl
579                                    FROM __corro_buffered_changes
580                                    WHERE site_id = :actor_id
581                                        AND db_version = :db_version
582                                        AND seq BETWEEN :start_seq AND :end_seq
583                                    ORDER BY seq ASC
584                            "#,
585                        )?;
586
587                        let start_seq = range_needed.start();
588                        let end_seq = range_needed.end();
589
590                        let rows = prepped.query_map(
591                            named_params! {
592                                ":actor_id": actor_id,
593                                ":db_version": version,
594                                ":start_seq": start_seq,
595                                ":end_seq": end_seq
596                            },
597                            row_to_change,
598                        )?;
599
600                        send_change_chunks(
601                            sender,
602                            ChunkedChanges::new(
603                                rows,
604                                start_seq,
605                                end_seq,
606                                MAX_CHANGES_BYTES_PER_MESSAGE,
607                            ),
608                            actor_id,
609                            version,
610                            last_seq,
611                            ts,
612                        )?;
613                    }
614                }
615            }
616        }
617        SyncNeedV1::Partial { version, seqs } => {
618            let mut rows = prepped.query(named_params! {
619                ":actor_id": actor_id,
620                ":start": version,
621                ":end": version,
622            })?;
623
624            trace!(%version, "looking up from crsql_changes!");
625
626            match rows.next()? {
627                Some(row) => {
628                    let version: CrsqlDbVersion = row.get(0)?;
629                    let last_seq: CrsqlSeq = row.get(1)?;
630                    let ts: Timestamp = row.get(2)?;
631                    trace!(%version, %last_seq, "got a row from crsql_change!");
632
633                    for range_needed in seqs {
634                        let mut prepped = tx.prepare_cached(
635                            r#"
636                                SELECT "table", pk, cid, val, col_version, db_version, seq, site_id, cl
637                                    FROM crsql_changes
638                                    WHERE site_id = :actor_id
639                                      AND db_version = :version
640                                      AND seq BETWEEN :start AND :end
641                                    ORDER BY seq ASC
642                            "#,
643                        )?;
644
645                        let rows = prepped.query_map(
646                            named_params! {
647                                ":actor_id": actor_id,
648                                ":version": version,
649                                ":start": range_needed.start(),
650                                ":end": range_needed.end(),
651                            },
652                            row_to_change,
653                        )?;
654
655                        send_change_chunks(
656                            sender,
657                            ChunkedChanges::new(
658                                rows,
659                                range_needed.start(),
660                                range_needed.end(),
661                                MAX_CHANGES_BYTES_PER_MESSAGE,
662                            ),
663                            actor_id,
664                            version,
665                            last_seq,
666                            ts,
667                        )?;
668                    }
669                }
670                None => {
671                    trace!(%version, ?seqs, "no rows in crsql_changes, checking partials and gaps...");
672
673                    let (in_gaps, buffered): (bool, bool) = tx
674                        .prepare_cached(
675                            "
676                        SELECT
677                            EXISTS(
678                                SELECT 1
679                                FROM __corro_bookkeeping_gaps
680                                    WHERE actor_id = :actor_id
681                                      AND :version BETWEEN start AND end
682                            ) AS in_gaps,
683                            EXISTS(
684                                SELECT 1
685                                FROM __corro_buffered_changes
686                                    WHERE site_id = :actor_id
687                                      AND db_version = :version
688                            ) AS buffered",
689                        )?
690                        .query_row(
691                            named_params! {
692                                ":actor_id": actor_id,
693                                ":version": version
694                            },
695                            |row| Ok((row.get(0)?, row.get(1)?)),
696                        )?;
697
698                    if !buffered && !in_gaps {
699                        // this is an empty!
700                        empties.insert(version..=version);
701                    }
702
703                    if buffered {
704                        for seqs_range in seqs {
705                            let seqs = tx
706                                .prepare_cached(
707                                    "
708                                SELECT start_seq, end_seq, last_seq, ts
709                                    FROM __corro_seq_bookkeeping
710                                    WHERE
711                                        site_id = :actor_id AND
712                                        db_version = :db_version AND
713                                        (
714                                            -- [:start]---[start_seq]---[:end]
715                                            ( start_seq BETWEEN :start AND :end ) OR
716
717                                            -- [start_seq]---[:start]---[:end]---[end_seq]
718                                            ( start_seq <= :start AND end_seq >= :end ) OR
719
720                                            -- [:start]---[start_seq]---[:end]---[end_seq]
721                                            ( start_seq <= :end AND end_seq >= :end ) OR
722
723                                            -- [:start]---[end_seq]---[:end]
724                                            ( end_seq BETWEEN :start AND :end )
725                                        )
726                                ",
727                                )?
728                                .query_map(
729                                    named_params! {
730                                        ":actor_id": actor_id,
731                                        ":db_version": version,
732                                        ":start": seqs_range.start(),
733                                        ":end": seqs_range.end(),
734                                    },
735                                    |row| {
736                                        Ok((
737                                            CrsqlSeqRange::new(row.get(0)?, row.get(1)?),
738                                            row.get(2)?,
739                                            row.get(3)?,
740                                        ))
741                                    },
742                                )?
743                                .collect::<rusqlite::Result<
744                                    Vec<(CrsqlSeqRange, CrsqlSeq, Timestamp)>,
745                                >>()?;
746
747                            trace!(%version, ?seqs, "got some partial seqs!");
748
749                            for (range_needed, last_seq, ts) in seqs {
750                                let mut prepped = tx.prepare_cached(
751                                r#"
752                                    SELECT "table", pk, cid, val, col_version, db_version, seq, site_id, cl
753                                        FROM __corro_buffered_changes
754                                        WHERE site_id = :actor_id
755                                            AND db_version = :version
756                                            AND seq BETWEEN :start_seq AND :end_seq
757                                        ORDER BY seq ASC
758                                "#,
759                            )?;
760
761                                // scope query to only the sequences we have
762                                let start_seq = cmp::max(range_needed.start(), seqs_range.start());
763                                let end_seq = cmp::min(range_needed.end(), seqs_range.end());
764
765                                trace!(%version, %start_seq, %end_seq, "getting seq range from crsql_changes");
766
767                                let rows = prepped.query_map(
768                                    named_params! {
769                                        ":actor_id": actor_id,
770                                        ":version": version,
771                                        ":start_seq": start_seq,
772                                        ":end_seq": end_seq
773                                    },
774                                    row_to_change,
775                                )?;
776
777                                send_change_chunks(
778                                    sender,
779                                    ChunkedChanges::new(
780                                        rows,
781                                        start_seq,
782                                        end_seq,
783                                        MAX_CHANGES_BYTES_PER_MESSAGE,
784                                    ),
785                                    actor_id,
786                                    version,
787                                    last_seq,
788                                    ts,
789                                )?;
790                            }
791                        }
792                    }
793                }
794            }
795        }
796        SyncNeedV1::Empty { .. } => {
797            // NOTE: no more empties in the new reality
798        }
799    }
800
801    if !empties.is_empty() {
802        for versions in empties {
803            sender.blocking_send(SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
804                actor_id,
805                changeset: Changeset::Empty {
806                    versions: versions.into(),
807                    ts: None,
808                },
809            })))?;
810        }
811    }
812
813    Ok(())
814}
815
816fn send_change_chunks<I: Iterator<Item = rusqlite::Result<Change>>>(
817    sender: &Sender<SyncMessage>,
818    mut chunked: ChunkedChanges<I>,
819    actor_id: ActorId,
820    version: CrsqlDbVersion,
821    last_seq: CrsqlSeq,
822    ts: Timestamp,
823) -> eyre::Result<()> {
824    let mut max_buf_size = chunked.max_buf_size();
825    loop {
826        if sender.is_closed() {
827            eyre::bail!("sync message sender channel is closed");
828        }
829        match chunked.next() {
830            Some(Ok((changes, seqs))) => {
831                let start = Instant::now();
832
833                if changes.is_empty() && seqs.start() == CrsqlSeq(0) && seqs.end() == last_seq {
834                    warn!(%actor_id, %version, "got an empty changes we should've had");
835                    return Ok(());
836                } else {
837                    sender.blocking_send(SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
838                        actor_id,
839                        changeset: Changeset::FullV2 {
840                            actor_id,
841                            version,
842                            changes,
843                            seqs,
844                            last_seq,
845                            ts,
846                        },
847                    })))?;
848                }
849
850                let elapsed = start.elapsed();
851
852                if elapsed > Duration::from_secs(5) {
853                    eyre::bail!("time out: peer is too slow");
854                }
855
856                if elapsed > ADAPT_CHUNK_SIZE_THRESHOLD {
857                    if max_buf_size <= MIN_CHANGES_BYTES_PER_MESSAGE {
858                        eyre::bail!("time out: peer is too slow even after reducing throughput");
859                    }
860
861                    max_buf_size /= 2;
862                    debug!("adapting max chunk size to {max_buf_size} bytes");
863
864                    chunked.set_max_buf_size(max_buf_size);
865                }
866            }
867            Some(Err(e)) => {
868                error!(%actor_id, %version, "could not process changes to send via sync: {e}");
869                break;
870            }
871            None => {
872                break;
873            }
874        }
875    }
876
877    Ok(())
878}
879
880async fn process_sync(
881    pool: SplitPool,
882    bookie: Bookie,
883    sender: Sender<SyncMessage>,
884    recv: mpsc::Receiver<SyncRequestV1>,
885) -> eyre::Result<()> {
886    let chunked_reqs = ReceiverStream::new(recv).chunks_timeout(10, Duration::from_millis(500));
887    tokio::pin!(chunked_reqs);
888
889    let (job_tx, job_rx) = unbounded_channel();
890
891    let mut buf =
892        futures::StreamExt::buffer_unordered(
893            UnboundedReceiverStream::<
894                std::pin::Pin<Box<dyn Future<Output = eyre::Result<()>> + Send>>,
895            >::new(job_rx),
896            6,
897        );
898    loop {
899        enum Branch {
900            Reqs(Vec<Vec<(ActorId, Vec<SyncNeedV1>)>>),
901        }
902
903        let branch = tokio::select! {
904            maybe_reqs = chunked_reqs.next() => match maybe_reqs {
905                Some(reqs) => {
906                    Branch::Reqs(reqs)
907                },
908                None => break
909            },
910            Some(res) = buf.next() => {
911                res?;
912                continue;
913            },
914            else => {
915                break;
916            }
917        };
918
919        match branch {
920            Branch::Reqs(reqs) => {
921                let agg = reqs
922                    .into_iter()
923                    .flatten()
924                    .chunk_by(|req| req.0)
925                    .into_iter()
926                    .map(|(actor_id, reqs)| (actor_id, reqs.flat_map(|(_, reqs)| reqs).collect()))
927                    .collect::<Vec<(ActorId, Vec<SyncNeedV1>)>>();
928
929                for (actor_id, needs) in agg {
930                    let booked = bookie
931                        .read::<&str, _>("process_sync get actor", None)
932                        .await
933                        .get(&actor_id)
934                        .cloned();
935                    let booked = match booked {
936                        Some(b) => b,
937                        None => continue,
938                    };
939                    let booked_read = booked
940                        .read::<&str, _>("process_sync check needs", None)
941                        .await;
942
943                    for need in needs {
944                        match &need {
945                            SyncNeedV1::Full { versions } => {
946                                if versions.clone().all(|v| {
947                                    booked_read.needed().contains(&v)
948                                        || booked_read.last().is_some_and(|max| v > max)
949                                }) {
950                                    continue;
951                                }
952                            }
953                            SyncNeedV1::Partial { version, .. } => {
954                                if booked_read.needed().contains(version)
955                                    || booked_read.last().is_some_and(|max| *version > max)
956                                {
957                                    continue;
958                                }
959                            }
960                            SyncNeedV1::Empty { ts } => {
961                                debug!("received empty need with ts: {ts:?}");
962                            }
963                        }
964
965                        let pool = pool.clone();
966                        let sender = sender.clone();
967
968                        let fut = Box::pin(async move {
969                            let mut conn = pool.read().await?;
970
971                            block_in_place(|| handle_need(&mut conn, actor_id, need, &sender))?;
972
973                            Ok(())
974                        });
975
976                        if job_tx.send(fut).is_err() {
977                            eyre::bail!("could not send into job channel");
978                        }
979                    }
980                }
981            }
982        }
983    }
984    debug!("done w/ sync server loop");
985
986    drop(job_tx);
987
988    let _: () = buf.try_collect().await?;
989
990    debug!("done processing sync state");
991
992    Ok(())
993}
994
995fn encode_sync_msg(
996    codec: &mut LengthDelimitedCodec,
997    encode_buf: &mut BytesMut,
998    send_buf: &mut BytesMut,
999    msg: SyncMessage,
1000) -> Result<(), SyncSendError> {
1001    msg.write_to_stream(encode_buf.writer())
1002        .map_err(SyncMessageEncodeError::from)?;
1003
1004    let data = encode_buf.split().freeze();
1005    trace!("encoded sync message, len: {}", data.len());
1006    codec.encode(data, send_buf)?;
1007    Ok(())
1008}
1009
1010pub async fn encode_write_bipayload_msg(
1011    codec: &mut LengthDelimitedCodec,
1012    encode_buf: &mut BytesMut,
1013    send_buf: &mut BytesMut,
1014    msg: BiPayload,
1015    write: &mut SendStream,
1016) -> Result<(), BiPayloadSendError> {
1017    encode_bipayload_msg(codec, encode_buf, send_buf, msg)?;
1018
1019    write_buf(send_buf, write)
1020        .await
1021        .map_err(BiPayloadSendError::from)
1022}
1023
1024fn encode_bipayload_msg(
1025    codec: &mut LengthDelimitedCodec,
1026    encode_buf: &mut BytesMut,
1027    send_buf: &mut BytesMut,
1028    msg: BiPayload,
1029) -> Result<(), BiPayloadEncodeError> {
1030    msg.write_to_stream(encode_buf.writer())
1031        .map_err(BiPayloadEncodeError::from)?;
1032
1033    codec.encode(encode_buf.split().freeze(), send_buf)?;
1034    Ok(())
1035}
1036
1037async fn encode_write_sync_msg(
1038    codec: &mut LengthDelimitedCodec,
1039    encode_buf: &mut BytesMut,
1040    send_buf: &mut BytesMut,
1041    msg: SyncMessage,
1042    write: &mut SendStream,
1043) -> Result<(), SyncSendError> {
1044    encode_sync_msg(codec, encode_buf, send_buf, msg)?;
1045
1046    write_buf(send_buf, write)
1047        .await
1048        .map_err(SyncSendError::from)
1049}
1050
1051#[tracing::instrument(skip_all, fields(buf_size = send_buf.len()), err)]
1052async fn write_buf(send_buf: &mut BytesMut, write: &mut SendStream) -> Result<(), WriteError> {
1053    let len = send_buf.len();
1054    write.write_chunk(send_buf.split().freeze()).await?;
1055    counter!("corro.sync.chunk.sent.bytes").increment(len as u64);
1056
1057    Ok(())
1058}
1059
1060#[tracing::instrument(skip(read), fields(buf_size = tracing::field::Empty), err)]
1061pub async fn read_sync_msg<R: Stream<Item = std::io::Result<BytesMut>> + Unpin>(
1062    read: &mut R,
1063) -> Result<Option<SyncMessage>, SyncRecvError> {
1064    match read.next().await {
1065        Some(buf_res) => match buf_res {
1066            Ok(mut buf) => {
1067                counter!("corro.sync.chunk.recv.bytes").increment(buf.len() as u64);
1068                tracing::Span::current().record("buf_size", buf.len());
1069                match SyncMessage::from_buf(&mut buf) {
1070                    Ok(msg) => Ok(Some(msg)),
1071                    Err(e) => Err(SyncRecvError::from(e)),
1072                }
1073            }
1074            Err(e) => Err(SyncRecvError::from(e)),
1075        },
1076        None => Ok(None),
1077    }
1078}
1079
1080#[tracing::instrument(skip_all, err)]
1081pub async fn parallel_sync(
1082    agent: &Agent,
1083    transport: &Transport,
1084    members: Vec<(ActorId, SocketAddr)>,
1085    our_sync_state: SyncStateV1,
1086) -> Result<usize, SyncError> {
1087    trace!(
1088        self_actor_id = %agent.actor_id(),
1089        "parallel syncing w/ {}",
1090        members
1091            .iter()
1092            .map(|(actor_id, _)| actor_id.to_string())
1093            .collect::<Vec<_>>()
1094            .join(", ")
1095    );
1096
1097    let mut trace_ctx = SyncTraceContextV1::default();
1098    opentelemetry::global::get_text_map_propagator(|prop| {
1099        prop.inject_context(&tracing::Span::current().context(), &mut trace_ctx)
1100    });
1101
1102    let results = FuturesUnordered::from_iter(members.iter().map(|(actor_id, addr)| {
1103        let trace_ctx = trace_ctx.clone();
1104        async {
1105            (
1106                *actor_id,
1107                *addr,
1108                async {
1109                    let mut codec = LengthDelimitedCodec::builder().max_frame_length(100 * 1_024 * 1_024).new_codec();
1110                    let mut send_buf = BytesMut::new();
1111                    let mut encode_buf = BytesMut::new();
1112
1113                    let actor_id = *actor_id;
1114                    let (mut tx, rx) = transport.open_bi(*addr).await?;
1115                    let mut read = FramedRead::new(rx, LengthDelimitedCodec::builder().max_frame_length(100 * 1_024 * 1_024).new_codec());
1116
1117                    encode_write_bipayload_msg(
1118                        &mut codec,
1119                        &mut encode_buf,
1120                        &mut send_buf,
1121                        BiPayload::V1 {data: BiPayloadV1::SyncStart {actor_id: agent.actor_id(), trace_ctx}, cluster_id: agent.cluster_id()},
1122                        &mut tx,
1123                    ).instrument(info_span!("write_sync_start"))
1124                    .await?;
1125
1126                    trace!(%actor_id, self_actor_id = %agent.actor_id(), "sent start payload");
1127
1128                    encode_write_sync_msg(
1129                        &mut codec,
1130                        &mut encode_buf,
1131                        &mut send_buf,
1132                        SyncMessage::V1(SyncMessageV1::Clock(agent.clock().new_timestamp().into())),
1133                        &mut tx,
1134                    ).instrument(info_span!("write_sync_clock"))
1135                    .await?;
1136
1137                    trace!(%actor_id, self_actor_id = %agent.actor_id(), "sent clock payload");
1138                    tx.flush().instrument(info_span!("quic_flush")).await.map_err(SyncSendError::from)?;
1139
1140                    trace!(%actor_id, self_actor_id = %agent.actor_id(), "flushed sync payloads");
1141
1142                    let their_sync_state = match timeout(Duration::from_secs(2), read_sync_msg(&mut read)).instrument(info_span!("read_sync_state")).await.map_err(SyncRecvError::from)?? {
1143                        Some(SyncMessage::V1(SyncMessageV1::State(state))) => state,
1144                        Some(SyncMessage::V1(SyncMessageV1::Rejection(rejection))) => {
1145                            return Err(rejection.into())
1146                        }
1147                        Some(_) => return Err(SyncRecvError::ExpectedSyncState.into()),
1148                        None => return Err(SyncRecvError::UnexpectedEndOfStream.into()),
1149                    };
1150                    trace!(%actor_id, self_actor_id = %agent.actor_id(), "read state payload: {their_sync_state:?}");
1151
1152                    match timeout(Duration::from_secs(2), read_sync_msg(&mut read)).instrument(info_span!("read_sync_clock")).await.map_err(SyncRecvError::from)??  {
1153                        Some(SyncMessage::V1(SyncMessageV1::Clock(ts))) => {
1154                            match agent.update_clock_with_timestamp(actor_id, ts) {
1155                                Ok(()) => (),
1156                                Err(e) => {
1157                                    warn!("could not update clock from actor {actor_id}: {e}");
1158                                }
1159                            }
1160                        },
1161                        Some(_) => return Err(SyncRecvError::ExpectedClockMessage.into()),
1162                        None => return Err(SyncRecvError::UnexpectedEndOfStream.into()),
1163                    }
1164                    trace!(%actor_id, self_actor_id = %agent.actor_id(), "read clock payload");
1165
1166                    counter!("corro.sync.client.member", "id" => actor_id.to_string(), "addr" => addr.to_string()).increment(1);
1167
1168                    let needs = our_sync_state.compute_available_needs(&their_sync_state);
1169
1170                    debug!(%actor_id, self_actor_id = %agent.actor_id(), "computed needs: {:?}, their_sync_state: {:?}", needs, their_sync_state);
1171
1172                    Ok::<_, SyncError>((needs, tx, read))
1173                }.await
1174            )
1175        }.instrument(info_span!("sync_client_handshake", %actor_id, %addr))
1176    }))
1177    .collect::<Vec<(ActorId, SocketAddr, Result<_, SyncError>)>>()
1178    .await;
1179
1180    debug!("collected member needs and such!");
1181
1182    assert_sometimes!(true, "Corrosion initializes sync with other nodes");
1183    #[allow(clippy::manual_try_fold)]
1184    let syncers = results
1185        .into_iter()
1186        .fold(Ok(vec![]), |agg, (actor_id, addr, res)| match res {
1187            Ok((needs, tx, read)) => {
1188                let mut v = agg.unwrap_or_default();
1189                v.push((actor_id, addr, needs, tx, read));
1190                Ok(v)
1191            }
1192            Err(e) => {
1193                counter!(
1194                    "corro.sync.client.handshake.errors",
1195                    "actor_id" => actor_id.to_string(),
1196                    "addr" => addr.to_string(),
1197                    "error" => e.to_string()
1198                )
1199                .increment(1);
1200                match agg {
1201                    Ok(v) if !v.is_empty() => Ok(v),
1202                    _ => Err(e),
1203                }
1204            }
1205        })?;
1206
1207    let len = syncers.len();
1208
1209    let (readers, mut servers) = {
1210        syncers.into_iter().fold(
1211            (Vec::with_capacity(len), Vec::with_capacity(len)),
1212            |(mut readers, mut servers), (actor_id, addr, needs, tx, read)| {
1213                if needs.is_empty() {
1214                    trace!(%actor_id, "no needs!");
1215                    return (readers, servers);
1216                }
1217                readers.push((actor_id, read));
1218
1219                trace!(%actor_id, "needs: {needs:?}");
1220
1221
1222                debug!(%actor_id, %addr, "needs len: {}", needs.values().map(|needs| needs.iter().map(|need| match need {
1223                    SyncNeedV1::Full {versions} => versions.len(),
1224                    SyncNeedV1::Partial {..} => 0,
1225                    SyncNeedV1::Empty {..} => 0,
1226                }).sum::<usize>()).sum::<usize>());
1227
1228                let actor_needs = needs
1229                    .into_iter()
1230                    .flat_map(|(actor_id, needs)| {
1231                        let needs: Vec<_> = needs
1232                            .into_iter()
1233                            .flat_map(|need| match need {
1234                                // chunk the versions, sometimes it's 0..=1000000 and that's far too big for a chunk!
1235                                SyncNeedV1::Full { versions } => CrsqlDbVersion::chunked_iter(versions, 10)
1236                                    .map(|versions| SyncNeedV1::Full { versions })
1237                                    .collect(),
1238
1239                                need => vec![need],
1240                            })
1241                            .collect();
1242
1243                        needs
1244                            .into_iter()
1245                            .map(|need| (actor_id, need))
1246                            .collect::<Vec<_>>()
1247                    })
1248                    .collect::<VecDeque<_>>();
1249
1250                servers.push((
1251                    actor_id,
1252                    addr,
1253                    actor_needs,
1254                    tx,
1255                ));
1256
1257                (readers, servers)
1258            },
1259        )
1260    };
1261
1262    if readers.is_empty() && servers.is_empty() {
1263        return Ok(0);
1264    }
1265
1266    tokio::spawn(async move {
1267        // reusable buffers and constructs
1268        let mut codec = LengthDelimitedCodec::builder().max_frame_length(100 * 1_024 * 1_024).new_codec();
1269        let mut send_buf = BytesMut::new();
1270        let mut encode_buf = BytesMut::new();
1271
1272        // already requested full versions
1273        let mut req_full: HashMap<ActorId, RangeInclusiveSet<CrsqlDbVersion>> = HashMap::new();
1274
1275        // already requested partial version sequences
1276        let mut req_partials: HashMap<(ActorId, CrsqlDbVersion), RangeInclusiveSet<CrsqlSeq>> = HashMap::new();
1277
1278        let start = Instant::now();
1279
1280        loop {
1281            if servers.is_empty() {
1282                break;
1283            }
1284            let mut next_servers = Vec::with_capacity(servers.len());
1285            'servers: for (server_actor_id, addr, mut needs, mut tx) in servers {
1286                if needs.is_empty() {
1287                    continue;
1288                }
1289
1290                let mut drained = 0;
1291
1292                while drained < 10 {
1293                    let (actor_id, need) = match needs.pop_back() {
1294                        Some(popped) => popped,
1295                        None => {
1296                            break;
1297                        }
1298                    };
1299
1300                    drained += 1;
1301
1302                    let actual_needs = match need {
1303                        SyncNeedV1::Full { versions } => {
1304                            let range = req_full.entry(actor_id).or_default();
1305
1306                            let mut new_versions =
1307                                RangeInclusiveSet::from_iter(std::iter::once(versions.into()));
1308
1309                            // check if we've already requested
1310                            for overlap in range.overlapping(&versions.into()) {
1311                                new_versions.remove(overlap.clone());
1312                            }
1313
1314                            if new_versions.is_empty() {
1315                                continue;
1316                            }
1317
1318                            new_versions
1319                                .into_iter()
1320                                .map(|versions| {
1321                                    range.insert(versions.clone());
1322                                    SyncNeedV1::Full { versions: versions.into() }
1323                                })
1324                                .collect()
1325                        }
1326                        SyncNeedV1::Partial { version, seqs } => {
1327                            let range = req_partials.entry((actor_id, version)).or_default();
1328                            let mut new_seqs =
1329                                RangeInclusiveSet::from_iter(seqs.iter().map(|s| s.into()));
1330
1331                            for seqs in seqs {
1332                                for overlap in range.overlapping(&seqs.into()) {
1333                                    new_seqs.remove(overlap.clone());
1334                                }
1335                            }
1336
1337                            if new_seqs.is_empty() {
1338                                continue;
1339                            }
1340
1341                            vec![SyncNeedV1::Partial {
1342                                version,
1343                                seqs: new_seqs
1344                                    .into_iter()
1345                                    .map(|seqs| {
1346                                        range.insert(seqs.clone());
1347                                        CrsqlSeqRange::from(seqs)
1348                                    })
1349                                    .collect(),
1350                            }]
1351                        }
1352                        need => {vec![need]},
1353                    };
1354
1355                    if actual_needs.is_empty() {
1356                        warn!(%server_actor_id, %actor_id, %addr, "nothing to send!");
1357                        continue;
1358                    }
1359
1360                    let req_len = actual_needs.len();
1361
1362                    if let Err(e) = encode_sync_msg(
1363                        &mut codec,
1364                        &mut encode_buf,
1365                        &mut send_buf,
1366                        SyncMessage::V1(SyncMessageV1::Request(vec![(actor_id, actual_needs)])),
1367                    ) {
1368                        error!(%server_actor_id, %actor_id, %addr, "could not encode sync request: {e} (elapsed: {:?})", start.elapsed());
1369                        continue 'servers;
1370                    }
1371
1372                    counter!("corro.sync.client.req.sent", "actor_id" => server_actor_id.to_string()).increment(req_len as u64);
1373                }
1374
1375                if !send_buf.is_empty() {
1376                    if let Err(e) = write_buf(&mut send_buf, &mut tx).await {
1377                        error!(%server_actor_id, %addr, "could not write sync requests: {e} (elapsed: {:?})", start.elapsed());
1378                        continue;
1379                    }
1380                } else {
1381                    // give some reprieve
1382                    tokio::task::yield_now().await;
1383                }
1384
1385                if needs.is_empty() {
1386                    if let Err(e) = tx.finish() {
1387                        warn!("could not finish stream while sending sync requests: {e}");
1388                    }
1389                    debug!(%server_actor_id, %addr, "done trying to sync w/ actor after {:?}", start.elapsed());
1390                    continue;
1391                }
1392
1393                next_servers.push((server_actor_id, addr, needs, tx));
1394            }
1395            servers = next_servers;
1396        }
1397    }.instrument(info_span!("send_sync_requests")));
1398
1399    // now handle receiving changesets!
1400    let counts = FuturesUnordered::from_iter(readers.into_iter().map(|(actor_id, mut read)| {
1401        let tx_changes = agent.tx_changes().clone();
1402
1403        async move {
1404            let mut count = 0;
1405            loop {
1406                match read_sync_msg(&mut read).await {
1407                    Ok(None) => {
1408                        break;
1409                    }
1410                    Err(e) => {
1411                        error!(%actor_id, "sync recv error: {e}");
1412                        break;
1413                    }
1414                    Ok(Some(msg)) => match msg {
1415                        SyncMessage::V1(SyncMessageV1::Changeset(change)) => {
1416                            let changes_len = cmp::max(change.len(), 1);
1417                            // tracing::Span::current().record("changes_len", changes_len);
1418                            count += changes_len;
1419                            counter!("corro.sync.changes.recv", "actor_id" => actor_id.to_string())
1420                                .increment(changes_len as u64);
1421
1422                            debug!(
1423                                "handling versions: {:?}, seqs: {:?}, len: {changes_len} (is_empty: {}) from {actor_id}",
1424                                change.versions(),
1425                                change.seqs(),
1426                                change.is_empty()
1427                            );
1428                            // only accept emptyset that's from the same node that's syncing
1429                            if change.is_empty_set() {
1430                                warn!("received empty set from {actor_id}, ignoring");
1431                                continue;
1432                            }
1433
1434                            tx_changes
1435                                .send((change, ChangeSource::Sync))
1436                                .await
1437                                .map_err(|_| SyncRecvError::ChangesChannelClosed)?;
1438                        }
1439                        SyncMessage::V1(SyncMessageV1::Request(_)) => {
1440                            warn!("received sync request message unexpectedly, ignoring");
1441                            continue;
1442                        }
1443                        SyncMessage::V1(SyncMessageV1::State(_)) => {
1444                            warn!("received sync state message unexpectedly, ignoring");
1445                            continue;
1446                        }
1447                        SyncMessage::V1(SyncMessageV1::Clock(_)) => {
1448                            warn!("received sync clock message unexpectedly, ignoring");
1449                            continue;
1450                        }
1451                        SyncMessage::V1(SyncMessageV1::Rejection(rejection)) => {
1452                            return Err(rejection.into())
1453                        }
1454                    },
1455                }
1456            }
1457
1458            debug!(%actor_id, %count, "done reading sync messages");
1459
1460            Ok((actor_id, count))
1461        }
1462        .instrument(info_span!("read_sync_requests_responses", %actor_id))
1463    }))
1464    .collect::<Vec<Result<(ActorId, usize), SyncError>>>()
1465    .await;
1466
1467    let mut members = agent.members().write();
1468    let ts = Timestamp::from(agent.clock().new_timestamp());
1469    for res in counts.iter() {
1470        match res {
1471            Err(e) => error!("could not properly recv from peer: {e}"),
1472            Ok((actor_id, _)) => {
1473                members.update_sync_ts(actor_id, ts);
1474            }
1475        };
1476    }
1477
1478    Ok(counts
1479        .into_iter()
1480        .flat_map(|res| res.map(|i| i.1))
1481        .sum::<usize>())
1482}
1483
1484#[tracing::instrument(skip(agent, bookie, their_actor_id, read, write), fields(actor_id = %their_actor_id), err)]
1485pub async fn serve_sync(
1486    agent: &Agent,
1487    bookie: &Bookie,
1488    their_actor_id: ActorId,
1489    trace_ctx: SyncTraceContextV1,
1490    cluster_id: ClusterId,
1491    mut read: FramedRead<RecvStream, LengthDelimitedCodec>,
1492    mut write: SendStream,
1493) -> Result<usize, SyncError> {
1494    let context =
1495        opentelemetry::global::get_text_map_propagator(|propagator| propagator.extract(&trace_ctx));
1496    tracing::Span::current().set_parent(context);
1497
1498    debug!(actor_id = %their_actor_id, self_actor_id = %agent.actor_id(), "received sync request");
1499    let mut codec = LengthDelimitedCodec::builder()
1500        .max_frame_length(100 * 1_024 * 1_024)
1501        .new_codec();
1502    let mut send_buf = BytesMut::new();
1503    let mut encode_buf = BytesMut::new();
1504
1505    if cluster_id != agent.cluster_id() {
1506        encode_write_sync_msg(
1507            &mut codec,
1508            &mut encode_buf,
1509            &mut send_buf,
1510            SyncMessage::V1(SyncMessageV1::Rejection(SyncRejectionV1::DifferentCluster)),
1511            &mut write,
1512        )
1513        .instrument(info_span!("write_rejection_cluster_id"))
1514        .await?;
1515        return Ok(0);
1516    }
1517
1518    // read the clock
1519    match read_sync_msg(&mut read)
1520        .instrument(info_span!("read_peer_clock"))
1521        .await?
1522    {
1523        Some(SyncMessage::V1(SyncMessageV1::Clock(ts))) => match their_actor_id.try_into() {
1524            Ok(id) => {
1525                if let Err(e) = agent
1526                    .clock()
1527                    .update_with_timestamp(&uhlc::Timestamp::new(ts.to_ntp64(), id))
1528                {
1529                    warn!("could not update clock from actor {their_actor_id}: {e}");
1530                }
1531            }
1532            Err(e) => {
1533                error!("could not convert ActorId to uhlc ID: {e}");
1534            }
1535        },
1536        Some(_) => return Err(SyncRecvError::ExpectedClockMessage.into()),
1537        None => return Err(SyncRecvError::UnexpectedEndOfStream.into()),
1538    }
1539
1540    trace!(actor_id = %their_actor_id, self_actor_id = %agent.actor_id(), "read clock");
1541
1542    let _permit = match agent.limits().sync.try_acquire() {
1543        Ok(permit) => permit,
1544        Err(_) => {
1545            // no permits!
1546            encode_write_sync_msg(
1547                &mut codec,
1548                &mut encode_buf,
1549                &mut send_buf,
1550                SyncMessage::V1(SyncMessageV1::Rejection(
1551                    SyncRejectionV1::MaxConcurrencyReached,
1552                )),
1553                &mut write,
1554            )
1555            .instrument(info_span!("write_sync_rejection"))
1556            .await?;
1557            return Ok(0);
1558        }
1559    };
1560
1561    let sync_state = generate_sync(bookie, agent.actor_id()).await;
1562
1563    // first, send the current sync state
1564    encode_write_sync_msg(
1565        &mut codec,
1566        &mut encode_buf,
1567        &mut send_buf,
1568        SyncMessage::V1(SyncMessageV1::State(sync_state)),
1569        &mut write,
1570    )
1571    .instrument(info_span!("write_sync_state"))
1572    .await?;
1573
1574    trace!(actor_id = %their_actor_id, self_actor_id = %agent.actor_id(), "sent sync state");
1575
1576    // then the current clock's timestamp
1577    encode_write_sync_msg(
1578        &mut codec,
1579        &mut encode_buf,
1580        &mut send_buf,
1581        SyncMessage::V1(SyncMessageV1::Clock(agent.clock().new_timestamp().into())),
1582        &mut write,
1583    )
1584    .instrument(info_span!("write_sync_clock"))
1585    .await?;
1586    trace!(actor_id = %their_actor_id, self_actor_id = %agent.actor_id(), "sent clock");
1587
1588    // ensure we flush here so the data gets there fast. clock needs to be fresh!
1589    write
1590        .flush()
1591        .instrument(info_span!("quic_flush"))
1592        .await
1593        .map_err(SyncSendError::from)?;
1594    trace!(actor_id = %their_actor_id, self_actor_id = %agent.actor_id(), "flushed sync messages");
1595
1596    let (tx_need, rx_need) = mpsc::channel(1024);
1597    let (tx, mut rx) = mpsc::channel::<SyncMessage>(256);
1598
1599    tokio::spawn(
1600        process_sync(agent.pool().clone(), bookie.clone(), tx, rx_need)
1601            .instrument(info_span!("process_sync"))
1602            .inspect_err(|e| error!("could not process sync request: {e}")),
1603    );
1604
1605    let (send_res, recv_res) = tokio::join!(
1606        async move {
1607            let mut count = 0;
1608
1609            let mut check_buf = tokio::time::interval(Duration::from_secs(1));
1610
1611            let mut stopped = false;
1612
1613            loop {
1614                tokio::select! {
1615                    biased;
1616
1617                    stopped_res = write.stopped() => {
1618                        match stopped_res {
1619                            Ok(code) => {
1620                                debug!(actor_id = %their_actor_id, "send stream was stopped by peer, code: {code:?}");
1621                            },
1622                            Err(e) => {
1623                                warn!(actor_id = %their_actor_id, "error waiting for stop from stream: {e}");
1624                            }
1625                        }
1626                        stopped = true;
1627                        break;
1628                    },
1629
1630                    maybe_msg = rx.recv() => match maybe_msg {
1631                        Some(msg) => {
1632                            if let SyncMessage::V1(SyncMessageV1::Changeset(change)) = &msg {
1633                                count += change.len();
1634                            }
1635                            encode_sync_msg(&mut codec, &mut encode_buf, &mut send_buf, msg)?;
1636
1637                            if send_buf.len() >= 16 * 1024 {
1638                                write_buf(&mut send_buf, &mut write).await.map_err(SyncSendError::from)?;
1639                            }
1640                        },
1641                        None => {
1642                            break;
1643                        }
1644                    },
1645
1646                    _ = check_buf.tick() => {
1647                        if !send_buf.is_empty() {
1648                            write_buf(&mut send_buf, &mut write).await.map_err(SyncSendError::from)?;
1649                        }
1650                    }
1651                }
1652            }
1653
1654            if !stopped {
1655                if !send_buf.is_empty() {
1656                    write_buf(&mut send_buf, &mut write).await.map_err(SyncSendError::from)?;
1657                }
1658
1659                if let Err(e) = write.finish() {
1660                    warn!("could not properly finish QUIC send stream: {e}");
1661                }
1662            }
1663
1664            debug!(actor_id = %agent.actor_id(), "done writing sync messages (count: {count})");
1665
1666            counter!("corro.sync.changes.sent", "actor_id" => their_actor_id.to_string()).increment(count as u64);
1667
1668            Ok::<_, SyncError>(count)
1669        }.instrument(info_span!("process_versions_to_send")),
1670        async move {
1671            let mut count = 0;
1672
1673            loop {
1674                match read_sync_msg(&mut read).await {
1675                    Ok(None) => {
1676                        break;
1677                    }
1678                    Err(e) => {
1679                        error!("sync recv error: {e}");
1680                        break;
1681                    }
1682                    Ok(Some(msg)) => match msg {
1683                        SyncMessage::V1(SyncMessageV1::Request(req)) => {
1684                            trace!(actor_id = %their_actor_id, self_actor_id = %agent.actor_id(), "read req: {req:?}");
1685                            count += req
1686                                .iter()
1687                                .map(|(_, needs)| {
1688                                    needs.iter().map(|need| need.count()).sum::<usize>()
1689                                })
1690                                .sum::<usize>();
1691                            tx_need
1692                                .send(req)
1693                                .await
1694                                .map_err(|_| SyncRecvError::RequestsChannelClosed)?;
1695                        }
1696                        SyncMessage::V1(SyncMessageV1::Changeset(_)) => {
1697                            warn!(actor_id = %their_actor_id, "received sync changeset message unexpectedly, ignoring");
1698                            continue;
1699                        }
1700                        SyncMessage::V1(SyncMessageV1::State(_)) => {
1701                            warn!(actor_id = %their_actor_id, "received sync state message unexpectedly, ignoring");
1702                            continue;
1703                        }
1704                        SyncMessage::V1(SyncMessageV1::Clock(_)) => {
1705                            warn!(actor_id = %their_actor_id, "received sync clock message more than once, ignoring");
1706                            continue;
1707                        }
1708                        SyncMessage::V1(SyncMessageV1::Rejection(rejection)) => {
1709                            return Err(rejection.into())
1710                        }
1711                    },
1712                }
1713            }
1714
1715            debug!(actor_id = %agent.actor_id(), "done reading sync messages");
1716
1717            counter!("corro.sync.requests.recv", "actor_id" => their_actor_id.to_string()).increment(count as u64);
1718
1719            Ok(count)
1720        }.instrument(info_span!("process_version_requests"))
1721    );
1722
1723    if let Err(e) = send_res {
1724        error!(actor_id = %their_actor_id, "could not complete serving sync due to a send side error: {e}");
1725    }
1726
1727    recv_res
1728}
1729
1730#[cfg(test)]
1731mod tests {
1732    use crate::api::public::api_v1_transactions;
1733    use axum::{Extension, Json};
1734    use camino::Utf8PathBuf;
1735    use hyper::StatusCode;
1736    use klukai_tests::TEST_SCHEMA;
1737    use klukai_tests::launch_test_agent;
1738    use klukai_types::api::Statement;
1739    use klukai_types::base::CrsqlDbVersion;
1740    use klukai_types::tripwire::Tripwire;
1741    use klukai_types::{
1742        api::{ColumnName, TableName},
1743        broadcast::ChangesetPerTable,
1744        config::{Config, DEFAULT_GOSSIP_CLIENT_ADDR, TlsConfig},
1745        dbsr, dbvr,
1746        pubsub::pack_columns,
1747        tls::{generate_ca, generate_client_cert, generate_server_cert},
1748    };
1749    use rand::{Rng, RngCore};
1750    use tempfile::TempDir;
1751
1752    use crate::{
1753        agent::{process_multiple_changes, setup},
1754        api::public::{TimeoutParams, api_v1_db_schema},
1755    };
1756
1757    use super::*;
1758
1759    #[tokio::test(flavor = "multi_thread")]
1760    async fn test_sync_changes_order() -> eyre::Result<()> {
1761        _ = tracing_subscriber::fmt::try_init();
1762
1763        let (tripwire, _tripwire_worker, _tripwire_tx) = Tripwire::new_simple();
1764
1765        let ta1 = launch_test_agent(|conf| conf.build(), tripwire.clone()).await?;
1766
1767        // create versions on the first node
1768        let versions_range = 1..=100;
1769        for i in versions_range.clone() {
1770            let (status_code, body) = api_v1_transactions(
1771                Extension(ta1.agent.clone()),
1772                axum::extract::Query(TimeoutParams { timeout: None }),
1773                axum::Json(vec![Statement::WithParams(
1774                    "INSERT OR REPLACE INTO testsblob (id,text) VALUES (?,?)".into(),
1775                    vec![format!("service-id-{i}").into(), "service-name".into()],
1776                )]),
1777            )
1778            .await;
1779            assert_eq!(status_code, StatusCode::OK);
1780
1781            let version = body.0.version.unwrap();
1782            assert_eq!(version, i);
1783        }
1784
1785        let dir = tempfile::tempdir()?;
1786
1787        let (ta2_agent, mut ta2_opts) = setup(
1788            Config::builder()
1789                .db_path(dir.path().join("corrosion.db").display().to_string())
1790                .gossip_addr("127.0.0.1:0".parse()?)
1791                .api_addr("127.0.0.1:0".parse()?)
1792                .build()?,
1793            tripwire,
1794        )
1795        .await?;
1796
1797        let members = vec![(ta1.agent.actor_id(), ta1.agent.gossip_addr())];
1798        let _ = parallel_sync(&ta2_agent, &ta2_opts.transport, members, Default::default()).await?;
1799
1800        for i in versions_range.rev() {
1801            let changes = tokio::time::timeout(Duration::from_secs(5), ta2_opts.rx_changes.recv())
1802                .await?
1803                .unwrap();
1804            assert_eq!(
1805                changes.0.versions(),
1806                (CrsqlDbVersion(i)..=CrsqlDbVersion(i)).into()
1807            );
1808        }
1809
1810        Ok(())
1811    }
1812
1813    #[tokio::test(flavor = "multi_thread")]
1814    async fn test_handle_need() -> eyre::Result<()> {
1815        _ = tracing_subscriber::fmt::try_init();
1816
1817        let (tripwire, _tripwire_worker, _tripwire_tx) = Tripwire::new_simple();
1818        let tx_timeout = Duration::from_secs(60);
1819
1820        let dir = tempfile::tempdir()?;
1821
1822        let (agent, _agent_options) = setup(
1823            Config::builder()
1824                .db_path(dir.path().join("corrosion.db").display().to_string())
1825                .gossip_addr("127.0.0.1:0".parse()?)
1826                .api_addr("127.0.0.1:0".parse()?)
1827                .build()?,
1828            tripwire,
1829        )
1830        .await?;
1831
1832        let (status_code, _res) =
1833            api_v1_db_schema(Extension(agent.clone()), Json(vec![TEST_SCHEMA.to_owned()])).await;
1834
1835        assert_eq!(status_code, StatusCode::OK);
1836
1837        let actor_id = ActorId(uuid::Uuid::new_v4());
1838
1839        let ts = agent.clock().new_timestamp().into();
1840
1841        let change1 = Change {
1842            table: TableName("tests".into()),
1843            pk: pack_columns(&[1i64.into()])?,
1844            cid: ColumnName("text".into()),
1845            val: "one".into(),
1846            col_version: 1,
1847            db_version: CrsqlDbVersion(1),
1848            seq: CrsqlSeq(0),
1849            site_id: actor_id.to_bytes(),
1850            cl: 1,
1851        };
1852
1853        let change2 = Change {
1854            table: TableName("tests".into()),
1855            pk: pack_columns(&[2i64.into()])?,
1856            cid: ColumnName("text".into()),
1857            val: "two".into(),
1858            col_version: 1,
1859            db_version: CrsqlDbVersion(2),
1860            seq: CrsqlSeq(0),
1861            site_id: actor_id.to_bytes(),
1862            cl: 1,
1863        };
1864        let changes_two: ChangesetPerTable = changeset_with_changes(vec![change2.clone()]);
1865
1866        let bookie = Bookie::new(Default::default());
1867
1868        process_multiple_changes(
1869            agent.clone(),
1870            bookie.clone(),
1871            vec![
1872                (
1873                    ChangeV1 {
1874                        actor_id,
1875                        changeset: Changeset::Full {
1876                            version: CrsqlDbVersion(1),
1877                            changes: vec![change1.clone()],
1878                            seqs: dbsr!(0, 0),
1879                            last_seq: CrsqlSeq(0),
1880                            ts,
1881                        },
1882                    },
1883                    ChangeSource::Sync,
1884                    Instant::now(),
1885                ),
1886                (
1887                    ChangeV1 {
1888                        actor_id,
1889                        changeset: Changeset::Full {
1890                            version: CrsqlDbVersion(2),
1891                            changes: vec![change2.clone()],
1892                            seqs: dbsr!(0, 0),
1893                            last_seq: CrsqlSeq(0),
1894                            ts,
1895                        },
1896                    },
1897                    ChangeSource::Sync,
1898                    Instant::now(),
1899                ),
1900            ],
1901            tx_timeout,
1902        )
1903        .await?;
1904
1905        let booked = bookie
1906            .read::<&str, _>("test", None)
1907            .await
1908            .get(&actor_id)
1909            .cloned()
1910            .unwrap();
1911
1912        {
1913            let read = booked.read::<&str, _>("test", None).await;
1914
1915            assert!(read.contains_version(&CrsqlDbVersion(1)));
1916            assert!(read.contains_version(&CrsqlDbVersion(2)));
1917        }
1918
1919        {
1920            let (tx, mut rx) = mpsc::channel(5);
1921            let mut conn = agent.pool().read().await?;
1922
1923            {
1924                let mut prepped = conn.prepare("SELECT * FROM crsql_changes;")?;
1925                let mut rows = prepped.query([])?;
1926
1927                loop {
1928                    let row = rows.next()?;
1929                    if row.is_none() {
1930                        break;
1931                    }
1932
1933                    println!("ROW: {row:?}");
1934                }
1935            }
1936
1937            block_in_place(|| {
1938                handle_need(
1939                    &mut conn,
1940                    actor_id,
1941                    SyncNeedV1::Full {
1942                        versions: dbvr!(1, 1),
1943                    },
1944                    &tx,
1945                )
1946            })?;
1947
1948            let changes_one = changeset_with_changes(vec![change1.clone()]);
1949
1950            let msg = rx.recv().await.unwrap();
1951            assert_eq!(
1952                msg,
1953                SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
1954                    actor_id,
1955                    changeset: Changeset::FullV2 {
1956                        actor_id,
1957                        version: CrsqlDbVersion(1),
1958                        changes: changes_one,
1959                        seqs: dbsr!(0, 0),
1960                        last_seq: CrsqlSeq(0),
1961                        ts,
1962                    }
1963                }))
1964            );
1965
1966            println!("gonna handle a partial need...");
1967            block_in_place(|| {
1968                handle_need(
1969                    &mut conn,
1970                    actor_id,
1971                    SyncNeedV1::Partial {
1972                        version: CrsqlDbVersion(2),
1973                        seqs: vec![dbsr!(0, 0)],
1974                    },
1975                    &tx,
1976                )
1977            })?;
1978
1979            let msg = rx.recv().await.unwrap();
1980            assert_eq!(
1981                msg,
1982                SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
1983                    actor_id,
1984                    changeset: Changeset::FullV2 {
1985                        actor_id,
1986                        version: CrsqlDbVersion(2),
1987                        changes: changes_two.clone(),
1988                        seqs: dbsr!(0, 0),
1989                        last_seq: CrsqlSeq(0),
1990                        ts,
1991                    }
1992                }))
1993            );
1994        }
1995
1996        let change3 = Change {
1997            table: TableName("tests".into()),
1998            pk: pack_columns(&[1i64.into()])?,
1999            cid: ColumnName("text".into()),
2000            val: "one override".into(),
2001            col_version: 2,
2002            db_version: CrsqlDbVersion(3),
2003            seq: CrsqlSeq(0),
2004            site_id: actor_id.to_bytes(),
2005            cl: 1,
2006        };
2007        let changes_three = changeset_with_changes(vec![change3.clone()]);
2008
2009        process_multiple_changes(
2010            agent.clone(),
2011            bookie.clone(),
2012            vec![(
2013                ChangeV1 {
2014                    actor_id,
2015                    changeset: Changeset::Full {
2016                        version: CrsqlDbVersion(3),
2017                        changes: vec![change3.clone()],
2018                        seqs: dbsr!(0, 0),
2019                        last_seq: CrsqlSeq(0),
2020                        ts,
2021                    },
2022                },
2023                ChangeSource::Sync,
2024                Instant::now(),
2025            )],
2026            tx_timeout,
2027        )
2028        .await?;
2029
2030        {
2031            let (tx, mut rx) = mpsc::channel(5);
2032            let mut conn = agent.pool().read().await?;
2033
2034            {
2035                let mut prepped = conn.prepare("SELECT * FROM crsql_changes;")?;
2036                let mut rows = prepped.query([])?;
2037
2038                loop {
2039                    let row = rows.next()?;
2040                    if row.is_none() {
2041                        break;
2042                    }
2043
2044                    println!("ROW: {row:?}");
2045                }
2046            }
2047
2048            block_in_place(|| {
2049                handle_need(
2050                    &mut conn,
2051                    actor_id,
2052                    SyncNeedV1::Partial {
2053                        version: CrsqlDbVersion(1),
2054                        seqs: vec![dbsr!(0, 0)],
2055                    },
2056                    &tx,
2057                )
2058            })?;
2059
2060            let msg = rx.recv().await.unwrap();
2061            if let SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
2062                actor_id: actor,
2063                changeset,
2064                ..
2065            })) = msg
2066            {
2067                assert_eq!(actor_id, actor);
2068                assert!(changeset.is_empty());
2069                assert_eq!(changeset.versions(), dbvr!(1, 1));
2070            } else {
2071                panic!("{msg:?} doesn't contain an empty changeset");
2072            }
2073        }
2074
2075        {
2076            let (tx, mut rx) = mpsc::channel(5);
2077            let mut conn = agent.pool().read().await?;
2078
2079            block_in_place(|| {
2080                handle_need(
2081                    &mut conn,
2082                    actor_id,
2083                    SyncNeedV1::Full {
2084                        versions: dbvr!(1, 6),
2085                    },
2086                    &tx,
2087                )
2088            })?;
2089
2090            let changes_three = changeset_with_changes(vec![change3.clone()]);
2091            let msg = rx.recv().await.unwrap();
2092            assert_eq!(
2093                msg,
2094                SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
2095                    actor_id,
2096                    changeset: Changeset::FullV2 {
2097                        actor_id,
2098                        version: CrsqlDbVersion(3),
2099                        changes: changes_three,
2100                        seqs: dbsr!(0, 0),
2101                        last_seq: CrsqlSeq(0),
2102                        ts,
2103                    }
2104                }))
2105            );
2106
2107            let msg = rx.recv().await.unwrap();
2108            assert_eq!(
2109                msg,
2110                SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
2111                    actor_id,
2112                    changeset: Changeset::FullV2 {
2113                        actor_id,
2114                        version: CrsqlDbVersion(2),
2115                        changes: changes_two,
2116                        seqs: dbsr!(0, 0),
2117                        last_seq: CrsqlSeq(0),
2118                        ts,
2119                    }
2120                }))
2121            );
2122
2123            let msg = rx.recv().await.unwrap();
2124            if let SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
2125                actor_id: actor,
2126                changeset,
2127                ..
2128            })) = msg
2129            {
2130                assert_eq!(actor_id, actor);
2131                assert!(changeset.is_empty());
2132                assert_eq!(changeset.versions(), dbvr!(1, 1));
2133            } else {
2134                panic!("{msg:?} doesn't contain an empty changeset");
2135            }
2136        }
2137
2138        // overwrite v2
2139        let change4 = Change {
2140            table: TableName("tests".into()),
2141            pk: pack_columns(&[2i64.into()])?,
2142            cid: ColumnName("text".into()),
2143            val: "two override".into(),
2144            col_version: 2,
2145            db_version: CrsqlDbVersion(4),
2146            seq: CrsqlSeq(0),
2147            site_id: actor_id.to_bytes(),
2148            cl: 1,
2149        };
2150
2151        process_multiple_changes(
2152            agent.clone(),
2153            bookie.clone(),
2154            vec![(
2155                ChangeV1 {
2156                    actor_id,
2157                    changeset: Changeset::Full {
2158                        version: CrsqlDbVersion(4),
2159                        changes: vec![change4.clone()],
2160                        seqs: dbsr!(0, 0),
2161                        last_seq: CrsqlSeq(0),
2162                        ts,
2163                    },
2164                },
2165                ChangeSource::Sync,
2166                Instant::now(),
2167            )],
2168            tx_timeout,
2169        )
2170        .await?;
2171
2172        let mut rng = rand::rng();
2173
2174        let len = 10u64;
2175        let mut last_seq = 0u64;
2176
2177        let changes = (0u64..len)
2178            .map(|i| {
2179                let mut b = vec![0u8; 16];
2180                rng.fill_bytes(&mut b);
2181
2182                let int: i64 = rng.sample(rand::distr::StandardUniform);
2183                let float: f64 = rng.sample(rand::distr::StandardUniform);
2184                [
2185                    ("int", int.into()),
2186                    ("float", float.into()),
2187                    ("blob", b.into()),
2188                ]
2189                .into_iter()
2190                .map(|(col, val)| {
2191                    let c = Change {
2192                        table: TableName("wide".into()),
2193                        pk: pack_columns(&[i.to_be_bytes().to_vec().into(), i.to_string().into()])
2194                            .unwrap(),
2195                        cid: ColumnName(col.into()),
2196                        val,
2197                        col_version: 1,
2198                        db_version: CrsqlDbVersion(5),
2199                        seq: CrsqlSeq(last_seq),
2200                        site_id: actor_id.to_bytes(),
2201                        cl: 1,
2202                    };
2203                    last_seq += 1;
2204                    c
2205                })
2206                .collect::<Vec<_>>()
2207            })
2208            .collect::<Vec<_>>();
2209
2210        last_seq -= 1;
2211        let last_seq = CrsqlSeq(last_seq);
2212
2213        let changes_v1 = changes
2214            .iter()
2215            .map(|changes| {
2216                let seqs = changes.first().unwrap().seq..=changes.last().unwrap().seq;
2217                ChangeV1 {
2218                    actor_id,
2219                    changeset: Changeset::Full {
2220                        version: CrsqlDbVersion(5),
2221                        changes: changes.clone(),
2222                        seqs: seqs.into(),
2223                        last_seq,
2224                        ts,
2225                    },
2226                }
2227            })
2228            .collect::<Vec<_>>();
2229
2230        process_multiple_changes(
2231            agent.clone(),
2232            bookie.clone(),
2233            changes_v1
2234                .iter()
2235                .map(|change| (change.clone(), ChangeSource::Sync, Instant::now()))
2236                .collect(),
2237            tx_timeout,
2238        )
2239        .await?;
2240
2241        {
2242            let (tx, mut rx) = mpsc::channel(5);
2243            let mut conn = agent.pool().read().await?;
2244
2245            block_in_place(|| {
2246                handle_need(
2247                    &mut conn,
2248                    actor_id,
2249                    SyncNeedV1::Full {
2250                        versions: dbvr!(1, 1000),
2251                    },
2252                    &tx,
2253                )
2254            })?;
2255
2256            let msg = rx.recv().await.unwrap();
2257            let changes_four = changeset_with_changes(vec![change4.clone()]);
2258            assert_eq!(
2259                msg,
2260                SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
2261                    actor_id,
2262                    changeset: Changeset::FullV2 {
2263                        actor_id,
2264                        version: CrsqlDbVersion(4),
2265                        changes: changes_four,
2266                        seqs: dbsr!(0, 0),
2267                        last_seq: CrsqlSeq(0),
2268                        ts,
2269                    }
2270                }))
2271            );
2272
2273            let msg = rx.recv().await.unwrap();
2274            assert_eq!(
2275                msg,
2276                SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
2277                    actor_id,
2278                    changeset: Changeset::FullV2 {
2279                        actor_id,
2280                        version: CrsqlDbVersion(3),
2281                        changes: changes_three,
2282                        seqs: dbsr!(0, 0),
2283                        last_seq: CrsqlSeq(0),
2284                        ts,
2285                    }
2286                }))
2287            );
2288
2289            let changes_five = changeset_with_changes(changes.iter().flatten().cloned().collect());
2290            let msg = rx.recv().await.unwrap();
2291            assert_eq!(
2292                msg,
2293                SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
2294                    actor_id,
2295                    changeset: Changeset::FullV2 {
2296                        actor_id,
2297                        version: CrsqlDbVersion(5),
2298                        changes: changes_five,
2299                        seqs: (CrsqlSeq(0)..=last_seq).into(),
2300                        last_seq,
2301                        ts,
2302                    }
2303                }))
2304            );
2305
2306            let msg = rx.recv().await.unwrap();
2307            if let SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
2308                actor_id: actor,
2309                changeset,
2310                ..
2311            })) = msg
2312            {
2313                assert_eq!(actor_id, actor);
2314                assert!(changeset.is_empty());
2315                assert_eq!(changeset.versions(), dbvr!(1, 2));
2316            } else {
2317                panic!("{msg:?} doesn't contain an empty changeset");
2318            }
2319        }
2320
2321        {
2322            let (tx, mut rx) = mpsc::channel(5);
2323            let mut conn = agent.pool().read().await?;
2324
2325            block_in_place(|| {
2326                handle_need(
2327                    &mut conn,
2328                    actor_id,
2329                    SyncNeedV1::Partial {
2330                        version: CrsqlDbVersion(5),
2331                        seqs: vec![dbsr!(4, 7)],
2332                    },
2333                    &tx,
2334                )
2335            })?;
2336
2337            let filtered_changes = changes
2338                .iter()
2339                .flatten()
2340                .enumerate()
2341                .filter_map(|(i, c)| {
2342                    if (4..=7).contains(&i) {
2343                        Some(c.clone())
2344                    } else {
2345                        None
2346                    }
2347                })
2348                .collect();
2349            let changes_five_partial = changeset_with_changes(filtered_changes);
2350            let msg = rx.recv().await.unwrap();
2351            assert_eq!(
2352                msg,
2353                SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
2354                    actor_id,
2355                    changeset: Changeset::FullV2 {
2356                        actor_id,
2357                        version: CrsqlDbVersion(5),
2358                        changes: changes_five_partial,
2359                        seqs: dbsr!(4, 7),
2360                        last_seq,
2361                        ts,
2362                    }
2363                }))
2364            );
2365
2366            block_in_place(|| {
2367                handle_need(
2368                    &mut conn,
2369                    actor_id,
2370                    SyncNeedV1::Partial {
2371                        version: CrsqlDbVersion(5),
2372                        seqs: vec![dbsr!(2, 2), dbsr!(15, 24)],
2373                    },
2374                    &tx,
2375                )
2376            })?;
2377
2378            let filtered_changes = changes
2379                .iter()
2380                .flatten()
2381                .enumerate()
2382                .filter_map(|(i, c)| if i == 2 { Some(c.clone()) } else { None })
2383                .collect();
2384            let changes_five_partial2 = changeset_with_changes(filtered_changes);
2385            let msg = rx.recv().await.unwrap();
2386            assert_eq!(
2387                msg,
2388                SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
2389                    actor_id,
2390                    changeset: Changeset::FullV2 {
2391                        actor_id,
2392                        version: CrsqlDbVersion(5),
2393                        changes: changes_five_partial2,
2394                        seqs: dbsr!(2, 2),
2395                        last_seq,
2396                        ts,
2397                    }
2398                }))
2399            );
2400
2401            let filtered_changes = changes
2402                .iter()
2403                .flatten()
2404                .enumerate()
2405                .filter_map(|(i, c)| {
2406                    if (15..=24).contains(&i) {
2407                        Some(c.clone())
2408                    } else {
2409                        None
2410                    }
2411                })
2412                .collect();
2413            let changes_five_partial3 = changeset_with_changes(filtered_changes);
2414            let msg = rx.recv().await.unwrap();
2415            assert_eq!(
2416                msg,
2417                SyncMessage::V1(SyncMessageV1::Changeset(ChangeV1 {
2418                    actor_id,
2419                    changeset: Changeset::FullV2 {
2420                        actor_id,
2421                        version: CrsqlDbVersion(5),
2422                        changes: changes_five_partial3,
2423                        seqs: dbsr!(15, 24),
2424                        last_seq,
2425                        ts,
2426                    }
2427                }))
2428            );
2429        }
2430
2431        Ok(())
2432    }
2433
2434    fn changeset_with_changes(changes: Vec<Change>) -> ChangesetPerTable {
2435        let mut changeset = ChangesetPerTable::default();
2436        for change in changes {
2437            changeset.insert(change);
2438        }
2439        changeset
2440    }
2441
2442    #[tokio::test]
2443    async fn test_mutual_tls() -> eyre::Result<()> {
2444        let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
2445
2446        let (ca_cert, ca_key_pair) = generate_ca()?;
2447        let (_server_cert, server_cert_signed, server_key_pair) = generate_server_cert(
2448            &ca_cert.pem(),
2449            &ca_key_pair.serialize_pem(),
2450            "127.0.0.1".parse()?,
2451        )?;
2452
2453        let (_client_cert, client_cert_signed, client_key_pair) =
2454            generate_client_cert(&ca_cert.pem(), &ca_key_pair.serialize_pem())?;
2455
2456        let tmpdir = TempDir::new()?;
2457        let base_path = Utf8PathBuf::from(tmpdir.path().display().to_string());
2458
2459        let cert_file = base_path.join("cert.pem");
2460        let key_file = base_path.join("cert.key");
2461        let ca_file = base_path.join("ca.pem");
2462
2463        let client_cert_file = base_path.join("client-cert.pem");
2464        let client_key_file = base_path.join("client-cert.key");
2465
2466        tokio::fs::write(&cert_file, &server_cert_signed).await?;
2467        tokio::fs::write(&key_file, server_key_pair.serialize_pem()).await?;
2468
2469        tokio::fs::write(&ca_file, ca_cert.pem()).await?;
2470
2471        tokio::fs::write(&client_cert_file, &client_cert_signed).await?;
2472        tokio::fs::write(&client_key_file, client_key_pair.serialize_pem()).await?;
2473
2474        let gossip_config = GossipConfig {
2475            bind_addr: "127.0.0.1:0".parse()?,
2476            client_addr: DEFAULT_GOSSIP_CLIENT_ADDR,
2477            external_addr: None,
2478            bootstrap: vec![],
2479            tls: Some(TlsConfig {
2480                cert_file,
2481                key_file,
2482                ca_file: Some(ca_file),
2483                client: Some(TlsClientConfig {
2484                    cert_file: client_cert_file,
2485                    key_file: client_key_file,
2486                }),
2487                insecure: false,
2488            }),
2489            idle_timeout_secs: 30,
2490            plaintext: false,
2491            max_mtu: None,
2492            disable_gso: false,
2493        };
2494
2495        let server = gossip_server_endpoint(&gossip_config).await?;
2496        let addr = server.local_addr()?;
2497
2498        let client = gossip_client_endpoint(&gossip_config).await?;
2499
2500        let res = tokio::try_join!(
2501            async {
2502                Ok(client
2503                    .connect(addr, &addr.ip().to_string())
2504                    .unwrap()
2505                    .await
2506                    .unwrap())
2507            },
2508            async {
2509                let conn = match server.accept().await {
2510                    None => eyre::bail!("None accept!"),
2511                    Some(connecting) => connecting.await.unwrap(),
2512                };
2513                Ok(conn)
2514            }
2515        )?;
2516
2517        let client_conn = res.0;
2518
2519        let client_conn_peer_id = client_conn
2520            .peer_identity()
2521            .unwrap()
2522            .downcast::<Vec<rustls_pki_types::CertificateDer>>()
2523            .unwrap();
2524
2525        let mut server_cert_signed_buf =
2526            std::io::Cursor::new(server_cert_signed.as_bytes().to_vec());
2527
2528        assert_eq!(
2529            client_conn_peer_id[0].as_ref(),
2530            {
2531                let result: Result<Vec<_>, _> =
2532                    rustls_pemfile::certs(&mut server_cert_signed_buf).collect();
2533                result?
2534            }[0]
2535            .as_ref()
2536        );
2537
2538        let server_conn = res.1;
2539
2540        let server_conn_peer_id = server_conn
2541            .peer_identity()
2542            .unwrap()
2543            .downcast::<Vec<rustls_pki_types::CertificateDer>>()
2544            .unwrap();
2545
2546        let mut client_cert_signed_buf =
2547            std::io::Cursor::new(client_cert_signed.as_bytes().to_vec());
2548
2549        assert_eq!(
2550            server_conn_peer_id[0].as_ref(),
2551            {
2552                let result: Result<Vec<_>, _> =
2553                    rustls_pemfile::certs(&mut client_cert_signed_buf).collect();
2554                result?
2555            }[0]
2556            .as_ref()
2557        );
2558
2559        Ok(())
2560    }
2561}