- Add QuicSyncBackend + quic_scheduler; fix push stream/close race by waiting for peer close instead of calling close_and_drain() on the client side (SyncComplete stream was racing CONNECTION_CLOSE) - Add PipeWriteHalf::shutdown_push() for client-sends-last QUIC paths; use it in cmd_push so the server can process SyncComplete before the connection tears down - Add SshSyncBackend + ssh_scheduler with subprocess integration tests - Add clawsync diff command with --porcelain flag and subprocess tests - Add QUIC subprocess push/pull integration tests - Fix clippy --tests violations across 5 crates - Fix broken intra-doc links (reader.rs, lib.rs, scheduler.rs) - Rewrite crate READMEs; update BENCHMARKS.md with clawsync-fs CDC row 653 tests, 0 failures. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
3.1 KiB
clawsync-transport
ClawSync transport layer: TCP, QUIC, mmap ring-buffer, and SSH/pipe.
Overview
clawsync-transport provides the network and IPC layer for ClawSync. All
backends share the same SyncMessage wire protocol (rkyv-serialized,
length-prefixed) and the SyncPeer abstraction.
Backends
TCP (tcp)
Async tokio TCP transport. Each SyncMessage is framed as a 4-byte LE length
prefix followed by an rkyv-serialized body. Suitable for LAN and WAN sync.
// Server
let server = TcpServer::bind("0.0.0.0:9999".parse()?).await?;
let (conn, _addr) = server.accept().await?;
let msg = conn.recv().await?;
// Client
let client = TcpConnection::connect("10.0.0.1:9999".parse()?).await?;
client.send(&SyncMessage::IbltRequest { .. }).await?;
QUIC (quic)
Quinn-based QUIC transport with TLS 1.3. One unidirectional stream per
SyncMessage; W=16 concurrent streams for pipelined push.
// Server (self-signed cert for dev)
let server = QuicServer::bind(addr, QuicConfig::self_signed()?).await?;
let conn = server.accept().await?;
// Client (insecure — skip cert verification; for dev/same-LAN)
let conn = quic_connect(addr, "localhost", QuicConfig::insecure()?).await?;
After sending the last message, callers should use conn.wait_for_peer_close()
rather than dropping immediately, to avoid racing the in-flight stream with
CONNECTION_CLOSE.
mmap (mmap)
Zero-copy same-node sync via a shared memory-mapped file ring buffer. For co-located processes on the same machine.
SSH / pipe (peer, StreamPeer)
StreamPeer::from_child(child) wraps a spawned process's stdin/stdout as a
SyncPeer. Used by SshSyncBackend to pipe through SSH without a pre-running
daemon.
let child = Command::new("ssh")
.args(["user@host", "clawsync", "serve", "/data/agent.claws", "--stdio"])
.stdin(Stdio::piped()).stdout(Stdio::piped())
.spawn()?;
let peer = SyncPeer::Stream(StreamPeer::from_child(child));
Protocol
IBLT push (default)
Client Server
│── IbltRequest { sketch } ──────▶│ IBLT pre-flight: O(diff) wire cost
│◀─ IbltResponse { missing } ──────│
│── LayerPacket (rev K+1) ───────▶│ W=16 pipelined; Ack back
│── LayerPacket (rev K+2) ───────▶│
│ ... │
│── SyncComplete ─────────────────▶│
Flat-manifest pull
Client Server
│── ManifestRequest ──────────────▶│
│◀─ ManifestResponse ──────────────│
│◀─ LayerPacket (rev N+1) ─────────│
│── Ack { revision: N+1 } ────────▶│
│ ... │
│◀─ SyncComplete ──────────────────│
SyncMessage has 22 variants (discriminants append-only; never reorder).
License
MIT — see repository root.