Files
buzz/crates/buzz-relay-mesh/src/lib.rs
T
cls 9dfa06ffee
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
feat: import Chinese-localized Buzz source snapshot
Signed-off-by: cls_宁波本机 <908705107@qq.com>
2026-08-13 18:34:25 +08:00

226 lines
9.1 KiB
Rust

//! buzz-relay-mesh — the inter-relay QUIC mesh.
//!
//! One iroh endpoint per relay runtime (identity = a boot-unique mesh
//! keypair, attested by the relay's signing key — see [`wire::RuntimeId`]),
//! a warm full mesh of authenticated connections, scuttlebutt membership
//! gossip on a control substream, and a fenced wire contract that carries
//! tunnel traffic (reliable streams + realtime datagrams) between pods.
//!
//! The relay consumes this crate exclusively through two seams:
//!
//! - [`RelayMeshMembership`] — "who is alive / draining / dialable?"
//! - [`RelayPeerTransport`] — "move these bytes to that runtime."
//!
//! The seams are what keep single-instance deployments and same-pod sessions
//! mesh-free: when `BUZZ_MESH=off` or no peers exist, the relay never
//! constructs a mesh and the in-process fast path is untouched.
//!
//! **The law:** mesh membership is a hint; the Redis fenced generation is the
//! arbiter. Nothing in this crate grants ownership — see [`wire::FencedHeader`].
pub mod endpoint;
pub mod gossip;
pub mod membership;
pub mod peer;
pub mod registry;
pub mod runtime;
pub mod status;
pub mod wire;
// Lane modules — one owner per file (see the mesh thread for lane map):
// endpoint.rs, peer.rs — Mari (transport core)
// registry.rs, gossip.rs,
// membership.rs, status.rs — Max (membership + /_mesh)
// Session directory + tunnel routing live relay-side (Perci), consuming the
// seams below; huddle fan-out lives in buzz-relay's audio module (Dawn).
use std::future::Future;
use std::pin::Pin;
use bytes::Bytes;
pub use gossip::{GossipDigestEntry, GossipMessage, GossipRecord, GossipState, PhiAccrual};
pub use membership::MeshMembership;
pub use registry::{ReadyHeartbeat, ReadyRecord, ReadyRegistry, RuntimeAttestation};
pub use runtime::MeshRuntime;
pub use status::{ConnectionState, MeshCounters, MeshPeerCounters, MeshPeerStatus, MeshStatus};
pub use wire::{
FencedHeader, GoodbyeReason, MeshDatagram, MeshStreamFrame, Profile, RuntimeId, StreamHello,
StreamRole, ALPN, WIRE_VERSION,
};
/// Mesh configuration, resolved from env by the relay.
#[derive(Clone, Debug)]
pub struct MeshConfig {
/// `BUZZ_MESH` — `on` (default when replicas can exist) | `off` kill
/// switch. When off, the relay must behave exactly like single-instance.
pub enabled: bool,
/// UDP bind for the iroh endpoint (`BUZZ_MESH_BIND_ADDR`, default
/// `0.0.0.0:3478`). Excluded from istio sidecar capture in k8s.
pub bind_addr: std::net::SocketAddr,
/// Ready-registry heartbeat refresh (default 15s; expiry is 3x).
pub registry_refresh: std::time::Duration,
}
#[derive(Debug, thiserror::Error)]
pub enum MeshError {
#[error("frame encode: {0}")]
Encode(#[source] postcard::Error),
#[error("frame decode: {0}")]
Decode(#[source] postcard::Error),
#[error("unknown wire version {0}")]
UnknownWireVersion(u8),
#[error("empty frame")]
EmptyFrame,
#[error("frame exceeds max size ({size} > {max})")]
FrameTooLarge { size: usize, max: usize },
#[error("datagram exceeds connection max_datagram_size ({size} > {max})")]
DatagramTooLarge { size: usize, max: usize },
#[error("peer {0} not connected")]
PeerNotConnected(RuntimeId),
#[error("peer {0} is draining")]
PeerDraining(RuntimeId),
#[error("stale generation for session {session_id}: frame {frame_generation} < known {known_generation}")]
StaleGeneration {
session_id: uuid::Uuid,
frame_generation: u64,
known_generation: u64,
},
// The three variants below complete the fence-rejection taxonomy alongside
// `StaleGeneration` (Wren's chaos-gate ruling: every fence-visible reject
// is a typed variant, never a generic `Transport`, so live kill-9 /
// partition / replay evidence is unambiguous). Counter surface:
// `mesh_fence_rejections_total{reason=...}` with reasons
// `stale_generation` | `no_active_lease` | `owner_mismatch` |
// `future_generation`. None of these are serialized — the wire-level fence
// signal remains `GoodbyeReason::StaleGeneration`.
#[error("no active lease for session {session_id}: frame generation {frame_generation}, known generation {known_generation}, claimed owner {frame_owner_runtime_id}")]
NoActiveLease {
session_id: uuid::Uuid,
frame_generation: u64,
known_generation: u64,
/// The owner the *frame* claimed — there is no current owner by
/// definition when no live lease exists.
frame_owner_runtime_id: RuntimeId,
},
#[error("owner mismatch for session {session_id} generation {generation}: frame owner {frame_owner_runtime_id} != current owner {current_owner_runtime_id}")]
OwnerMismatch {
session_id: uuid::Uuid,
generation: u64,
frame_owner_runtime_id: RuntimeId,
current_owner_runtime_id: RuntimeId,
},
#[error("future generation for session {session_id}: frame {frame_generation} > known {known_generation}")]
FutureGeneration {
session_id: uuid::Uuid,
frame_generation: u64,
known_generation: u64,
},
#[error("mesh is disabled (BUZZ_MESH=off)")]
Disabled,
#[error("transport: {0}")]
Transport(String),
#[error("redis: {0}")]
Redis(#[from] redis::RedisError),
}
/// A peer as membership sees it. Everything here is a routing HINT.
#[derive(Clone, Debug)]
pub struct PeerInfo {
pub runtime_id: RuntimeId,
pub draining: bool,
/// Phi-accrual suspicion; `None` until enough heartbeats observed.
pub phi: Option<f64>,
/// Advisory load factor gossiped by the peer (0.0..).
pub load: f32,
}
/// Boxed future used across the seam traits. Public because implementors of
/// [`StreamSendHalf`]/[`StreamRecvHalf`]/[`RelayPeerTransport`] outside this
/// crate must name it.
pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
/// Seam 1: membership. Answers "who can I route to?" — never "who owns what."
pub trait RelayMeshMembership: Send + Sync + 'static {
/// Live, non-suspect peers (self excluded).
fn peers(&self) -> Vec<PeerInfo>;
/// This runtime's mesh identity.
fn local_runtime_id(&self) -> RuntimeId;
/// Begin drain: gossip `draining=true`, stop accepting new sessions.
fn begin_drain(&self);
}
/// Seam 2: transport. Moves fenced bytes to a specific runtime.
///
/// Implementations perform the datagram-size and wire-version checks; they do
/// NOT perform generation fencing — that belongs to the session layer on both
/// ends (fencing at every hop means every consumer checks, not the pipe).
pub trait RelayPeerTransport: Send + Sync + 'static {
/// Fire-and-forget realtime datagram (drop-on-full, never blocks on old
/// audio). Errors only for disconnected peer / oversize frame.
fn send_datagram(&self, to: RuntimeId, dgram: MeshDatagram) -> Result<(), MeshError>;
/// Open a reliable bi-stream to a peer for a session (`ReliableStream`
/// or `HuddleControl` profile). Sends the `Hello` before returning.
fn open_session_stream(
&self,
to: RuntimeId,
hello: StreamHello,
) -> BoxFuture<'_, Result<MeshStream, MeshError>>;
/// Register the handler invoked for inbound datagrams / session streams.
/// Called once at relay startup.
fn set_inbound(&self, handler: Box<dyn InboundHandler>);
}
/// Inbound mesh traffic, delivered after wire decode + Hello validation.
pub trait InboundHandler: Send + Sync + 'static {
fn on_datagram(&self, from: RuntimeId, dgram: MeshDatagram);
fn on_session_stream(&self, from: RuntimeId, hello: StreamHello, stream: MeshStream);
}
/// A reliable mesh stream: length-delimited `MeshStreamFrame`s over QUIC.
/// Concrete type (not a trait) so lanes share one framing implementation.
pub struct MeshStream {
// Mari: wrap iroh SendStream/RecvStream with the u32-LE length framing
// from `wire`. Placeholder halves keep the seam compilable pre-transport.
pub(crate) send: Box<dyn StreamSendHalf>,
pub(crate) recv: Box<dyn StreamRecvHalf>,
}
pub trait StreamSendHalf: Send + 'static {
fn send_frame(&mut self, frame: MeshStreamFrame) -> BoxFuture<'_, Result<(), MeshError>>;
fn finish(&mut self) -> Result<(), MeshError>;
}
pub trait StreamRecvHalf: Send + 'static {
fn recv_frame(&mut self) -> BoxFuture<'_, Result<Option<MeshStreamFrame>, MeshError>>;
}
impl MeshStream {
pub fn send_frame(&mut self, frame: MeshStreamFrame) -> BoxFuture<'_, Result<(), MeshError>> {
self.send.send_frame(frame)
}
pub fn recv_frame(&mut self) -> BoxFuture<'_, Result<Option<MeshStreamFrame>, MeshError>> {
self.recv.recv_frame()
}
pub fn finish(&mut self) -> Result<(), MeshError> {
self.send.finish()
}
}
/// Raw bytes helper used by transport internals.
pub fn encode_datagram_checked(
dgram: &MeshDatagram,
max_datagram_size: usize,
) -> Result<Bytes, MeshError> {
let bytes = wire::encode(dgram)?;
if bytes.len() > max_datagram_size {
return Err(MeshError::DatagramTooLarge {
size: bytes.len(),
max: max_datagram_size,
});
}
Ok(Bytes::from(bytes))
}