Overview
- What: two machines on the same network find each other over UDP broadcast and move large files directly over TCP, with no server, cloud or account.
- Hardest problem: resuming a transfer after a crash without ever trusting a corrupt partial file.
- How it is validated: 14 integration tests plus a 400 MiB transfer where the receiver is SIGKILLed mid-transfer and the resumed file matches by hash.
Written in Rust with one dependency (sha2); the discovery protocol, wire format, chunked transfer, crash resumption, flow control and integrity checks are all original.
Architecture
sender receiver
|-- UDP broadcast: discovery ----------------->|
|-- HELLO / OFFER ---------------------------->|
|<-- RESUME (bytes held + SHA-256 of them) ----|
|-- START ------------------------------------>|
|-- CHUNK* (per-chunk digest, bounded window)->| ACK only after chunk hits disk
|-- DONE (whole-file SHA-256) ---------------->| Highlights
- Protocol:
HELLO -> OFFER -> RESUME (bytes held + SHA-256 of exactly those bytes) -> START -> CHUNK* with per-chunk digests and a bounded unacked window -> DONE (whole-file SHA-256); ACKs are sent only after a chunk reaches disk. - Crash-resume proven with a 400 MiB file: receiver SIGKILLed mid-transfer, restarted, and the send resumed at byte 148,897,792 (35% of the file not re-sent) with matching source and received hashes.
- Backpressure holds under a slow disk: peak 3 in-flight against a window of 4, bounded memory of 1,024 KiB.
- Throughput 153.2 MiB/s with 1 MiB chunks on loopback, 77% of a raw-TCP baseline on a single vCPU; the gap is SHA-256 work, analysed in
docs/DESIGN.md. - 14/14 integration tests: corrupt partial files force restart not corruption, bad-hash chunks rejected, filename traversal rejected, oversized/truncated frames rejected, stale peers expire.
What I learned
Verifying the held prefix by hash on resume, rather than trusting its length, is what makes a corrupt partial file restart cleanly instead of corrupting the result. The 77% throughput figure comes with a measured explanation: the gap is SHA-256 work.
Limits
No GUI, no encryption (SHA-256 detects corruption, not an attacker), single files only, IPv4 broadcast only.