Files
clawsync/crates/clawsync-transport/README.md
osobhandClaude Sonnet 4.6 6d2eb5a1db feat: QUIC backend tests, diff command, clippy/doc fixes, QUIC push race fix
- 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]>
2026-04-06 22:40:31 -05:00

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.