From 276f936fc8730504666fbf1c3c5b2942f9be9016 Mon Sep 17 00:00:00 2001 From: tv Date: Fri, 23 Oct 2026 22:12:20 +0200 Subject: enrich keepalive content Add ready and syncs_with_you flags as well as actual heads, so the peer - ready: same meaning as in the sync message - syncs_with_you: sender counts recipient as member and in same epoch - heads: allows to tell who's behind or if both sides have changes --- src/transport/mod.rs | 49 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) (limited to 'src/transport/mod.rs') diff --git a/src/transport/mod.rs b/src/transport/mod.rs index eeba47f..aee3ee3 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -1,6 +1,8 @@ use anyhow::Result; use async_trait::async_trait; +use automerge::ChangeHash; use automerge::sync::Message; +use sha2::{Digest, Sha256}; use std::boxed::Box; use std::future::Future; use std::pin::Pin; @@ -10,13 +12,54 @@ pub use crate::document::replica::Epoch; pub mod firewall; pub mod mycelium; -pub type HeadsDigest = [u8; 32]; +/// Most heads listed in a keepalive; more are sent as a digest. 16 heads are about +/// 520 bytes, well within one Mycelium chunk (1300 bytes). +pub const MAX_LISTED_HEADS: usize = 16; + +/// A document's heads, compactly: the heads themselves while there are few, otherwise a +/// digest of them. Either way, two summaries are equal exactly when the heads are (the +/// number of heads decides the form, so equal heads always take the same one). +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum HeadsSummary { + /// Sorted. + Heads(Vec), + /// SHA-256 over the sorted heads. + Digest([u8; 32]), +} + +impl HeadsSummary { + pub fn of(mut heads: Vec) -> Self { + heads.sort_unstable(); + if heads.len() <= MAX_LISTED_HEADS { + return Self::Heads(heads); + } + let mut hasher = Sha256::new(); + for head in &heads { + hasher.update(head.0); + } + Self::Digest(hasher.finalize().into()) + } + /// The heads, if listed. + pub fn heads(&self) -> Option<&[ChangeHash]> { + match self { + Self::Heads(heads) => Some(heads), + Self::Digest(_) => None, + } + } +} + +/// PING and PONG. Addressed to one peer: `syncs_with_you` is about the recipient. #[derive(Debug, Clone)] pub struct Keepalive { - pub heads_digest: HeadsDigest, - /// The digest is only comparable between nodes on the same epoch. + /// Only comparable between nodes on the same epoch. + pub heads: HeadsSummary, pub epoch: Epoch, + /// The sender is ready (same meaning as `SyncEnvelope::ready`). + pub ready: bool, + /// The sender would sync with the recipient: counts it as an active member on its + /// own epoch. + pub syncs_with_you: bool, } #[derive(Debug)] -- cgit v1.3.1