Free · ISC · Rust sender · Flutter receiver

The QR codes are the wire.

Porter moves a file between two machines that share no network. A terminal displays it as a slideshow of QR codes, a phone camera reads them back, and fountain coding means the receiver never has to catch any particular frame.
cd rust-sender
cargo build --release

./target/release/porter-sender myfile.pdf --fountain
# Point the receiver app at the terminal. That's the whole protocol.

A link with no back-channel

The receiver cannot ask for a frame again. A dropped frame — bad angle, motion blur, a reflection at exactly the wrong moment — is simply gone, and every interesting decision in Porter follows from that.
  • Sequential
    Frame n is byte range n. Miss one and you wait for the sender to loop all the way back to that index.
  • Fountain
    Each frame XORs a random subset of blocks, picked from its sequence number by a PRNG both sides run. Any sufficient pile rebuilds the file.
  • So nothing is special
    No individual frame matters, which means a persistently missed one costs nothing at all.

One binary on the sending machine

Porter is an air-gapped tool, so the sending machine is often not the one you keep a runtime on. The sender compiles to a single static binary — no Node.js, no Python, nothing to install alongside it.
  • porter-sender <file>
    A ratatui TUI — QR grid, sidebar, scrubbing and gap-fill. Runs on the offline machine.
  • Receiver app
    Flutter, on Android and macOS. Scans, decodes, verifies, and resumes from disk if it is killed.
  • porter-sender serve
    An HTTP receiver on axum, for when a network turns out to exist after all.
  • porter-sender join
    Reassembles the .partaa and .partab files a receiver wrote, with a SHA-256 check.

What Porter does not do

The security story is short, which is the point. No account, no server, no telemetry, and by default no trace left on disk.
  • No network path
    The sender opens no sockets on the QR path. The transport is a camera pointed at a screen, so there is nothing to intercept in transit.
  • No disk trace by default
    Slideshow position persists only under --resume. Without the flag, nothing is written.
  • No telemetry
    No analytics, no crash reporting, no phone-home. Both binaries do exactly what the flags say.
  • Verified on arrival
    Fountain transfers always carry a CHECKSUM frame. The receiver checks SHA-256 before saving.
  • Resumable receiver
    Chunks land on disk as they decode, so a killed app rehydrates from the files rather than re-scanning.
  • Decisions on the record
    The worker isolate, fountain coding, disk hydration and the Rust move are all written up as ADRs.

Honest limits, and an ISC licence

QR is a slow, lossy, line-of-sight link, and no amount of coding changes that. A 109 MB file is roughly 26,000 chunks — a real transfer that has been run end to end, and also a long time holding a phone at a screen. Porter is for the case where two machines genuinely cannot talk - no NIC, an isolated network, a device you will not plug anything into.
Copyright © 2026