- 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]>
97 lines
3.1 KiB
Markdown
97 lines
3.1 KiB
Markdown
# 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.
|
|
|
|
```rust
|
|
// 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.
|
|
|
|
```rust
|
|
// 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.
|
|
|
|
```rust
|
|
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)
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
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.
|