Braid docs

Everything to get Braid running on your computer, add a phone with Braid for Android, drive it from the command line, and build or change either app.

Install

Download the installer for your platform from the Braid releases. Every package installs two programs: braid, the desktop app, and dl, the same engine on the command line.

The current release is Braid 0.1.0 beta 3.

PlatformFileHow
macOS 11+, Apple silicon and IntelBraid-0.1.0-macos.dmgDrag to Applications
Windows 10+ x86_64braid-0.1.0-x86_64.msiDouble-click
Windows on ARMbraid-0.1.0-aarch64.msiDouble-click
Debian, Ubuntu x86_64braid_0.1.0-1_amd64.debsudo apt install ./braid_*.deb
Debian, Ubuntu ARM64braid_0.1.0-1_arm64.debsudo apt install ./braid_*.deb
Fedora, RHEL x86_64braid-0.1.0-1.x86_64.rpmsudo dnf install ./braid-*.rpm
Fedora, RHEL ARM64braid-0.1.0-1.aarch64.rpmsudo dnf install ./braid-*.rpm

Nothing is code signed yet. On macOS, the first launch needs a right-click on the app, then Open, then confirm once. Windows SmartScreen warns for the same reason. If that is not acceptable, build from source.

On macOS, Braid registers as the handler for magnet: links and .torrent files, so clicking one in a browser opens it in Braid.

Your first download

  1. Press + in the toolbar.
  2. Paste a URL, a magnet link, or the path to a .torrent file.
  3. For HTTP, Braid asks the server first: the size, whether ranges really work, and whether there is a validator to resume against.
  4. Pick where to save it, how many connections to open (8 by default), and which interfaces to use. All means every interface you have ticked in settings.
  5. If the site publishes a checksum, switch on Verify checksum and paste it.
  6. Press Add.

The bar under the transfer fills in the colour of each interface that carried a chunk, and the sidebar shows each interface's live speed. Select a transfer and open the inspector (the panel button in the toolbar) for Info, Pieces, Connections and File.

Quitting is safe at any point. Each transfer comes back in the state you left it, and an unfinished one resumes from its journal.

Use every network

Braid only gains speed when your paths are really different roads to the internet.

SetupWhat carries the fileNeeds the Android app
Computer aloneEvery interface you selectNo
Plain USB tetheringThe phone shows up as one more network cardNo
Phone on your networkYour connection plus the phone's mobile dataYes
Phone on a cableYour connection plus its mobile data plus its Wi-FiYes
Several phonesEvery network of every phone, each weighted by its speedYes

Open Settings → Network to choose which interfaces Braid may use. Each row shows its address, the Binding mechanism actually in force, and an optional per-interface Limit. Unticked interfaces are never used, and a VPN tunnel shows as excluded.

The Binding column is worth a glance. IP_BOUND_IF on macOS, SO_BINDTODEVICE on Linux or IP_UNICAST_IF on Windows means the connection is truly pinned to that interface. A fall back to a plain source-address bind still works, but the traffic is not guaranteed to leave by that interface, so any aggregation you see may be extra connections over one route.

Resolve hostnames on under DNS chooses which interface looks up the server's name. Fastest interface is the default.

A transfer's paths are fixed when it starts. Plug in the cable or pair the phone first, then start the download.

Add a phone

Braid for Android lends a phone's networks to Braid on your computer. You need Braid for Android on the phone, and Android 8.0 or newer.

Install the app

Download braid-android-0.1.1.apk (Braid for Android 0.1.1, 511 KiB) from the Braid for Android releases and open it on the phone. The first time, Android asks you to allow installs from your browser or file manager. The same APK runs on every processor (arm64-v8a, armeabi-v7a, x86_64, x86), because it has no native code.

Pair by code

  1. On the phone, open Braid, tap Start sharing, and switch on Mobile data.
  2. In Braid on your computer, press Add phone at the foot of the interfaces list, then Show code. The code is good for two minutes.
  3. On the phone, tap Add to a computer by code, point the camera at the screen, check the computer's name, and tap Add.

The phone appears in Braid's sidebar with its own speed. No Google Play services on the phone? Point the phone's own camera app at the code instead.

Pair without a camera

  • In the same sheet on the computer, press Scan. Braid looks for phones on your network and down any cable, and the phone asks you to Allow the computer.
  • Or type the phone's address, shown at the bottom of the app, into Or add one by address. It looks like 192.168.1.40:8710 or [2409:…]:8710.

Choose what the phone lends

Each network on the phone has its own switch. Mobile data is the useful one when the phone is on the same Wi-Fi as your computer:

A phone on your Wi-Fi is usually not a second path. It reaches the internet through the same router you do. Braid compares the public address each path leaves from and marks the duplicate Not used, rather than pretending.

A USB cable is the fastest way to connect: the phone's hop to your computer then does not share airtime with your computer's own Wi-Fi, and both the phone's mobile data and its Wi-Fi can be real paths.

Set a limit under Mobile data caps what the phone will spend in a session. When it is reached, sharing on that network stops. Sharing keeps working with the screen off, as a foreground service with a notification that shows what has been used.

Forget a computer

Tap Forget beside a computer on the phone. Its key is deleted and it is cut off at once. Pairing keys are stored on the phone only as SHA-256 hashes.

Verify the APK

Every release is signed with the same InfoDive Labs certificate. An APK that does not match it did not come from us.

apksigner verify --print-certs braid-android-0.1.1.apk

The certificate's SHA-256 fingerprint is:

04:C0:0D:53:6A:D1:DB:AD:25:90:C8:43:3C:41:6B:EA:6A:66:4C:9E:2B:4C:B6:E5:49:C2:4F:80:B5:08:77:E2

Limits and schedules

Settings → Bandwidth sets a download and an upload limit, each Unlimited or Custom. Switch on Limit bandwidth on a schedule and paint the hours you want capped on the week grid. Outside the painted windows Braid runs unlimited, and a change reaches transfers that are already running.

Ignore limits for transfers I start manually lets a download you start by hand run at full speed while queued and scheduled ones stay capped.

A per-interface limit in Settings → Network is a ceiling on that link, shared by every connection over it. Use it for a metered tether.

Integrity and durability

Every chunk is hashed with BLAKE3 as it lands. A chunk that fails its hash is fetched again on its own, so a single bad chunk costs its size, not the whole download.

Under Settings → Integrity:

  • Verify every completed transfer checks the whole file once it finishes.
  • Checksum algorithm is BLAKE3, SHA-256 or MD5 for an expected digest.
  • Re-fetch only the damaged part keeps the rest of a file whose check fails.

Durability modes

Braid writes chunks into one preallocated .part file beside the destination, with a .dlmeta journal next to it. The rule that never changes:

Chunk data is made durable before the journal claims the chunk.

The modes only change how often that pair happens:

ModeFlushesJournal
Safeevery chunkwaits for the drive
Balanced (default)every 5 s or 64 MiBwaits for the drive
Fastevery 30 s or 512 MiBleft to the OS

A looser mode never risks a wrong file, only more re-downloading.

What Braid refuses

These produce an error rather than a file:

  • a server that advertises Range and then ignores it
  • a 200 where a 206 was asked for, or a changed Content-Range total
  • a ranged response with a non-identity Content-Encoding
  • an ETag that changes mid-download
  • a login or error page served with a plausible Content-Length

Pre-signed links from S3, cloud storage and CDNs are re-resolved before they lapse. Sixteen connections hitting an expired link produce one refresh, not sixteen. A link signed for one source address is resolved once per interface, because each path leaves from a different address.

On the command line, --refresh-command names a program that prints a fresh URL, either bare or as {"url": ..., "headers": {...}}. It is off unless you ask for it, and it runs directly, never through a shell:

dl add "$URL" --refresh-command "yt-dlp -g https://example.com/watch?v=..."

Torrents

Paste a magnet link or a .torrent file into Add. A torrent expands in the list to show its files, each with its own progress, and reports peers while it runs. Torrents obey the same limits and schedule as HTTP transfers.

Keep seeding after a torrent completes is under Settings. Off, a torrent stops when the last piece arrives.

Seeding is publication. Joining a swarm announces your address to every peer and tracker in it, and binding a transfer to one interface is not a privacy control.

Braid is a client, not an index. There is no search and no bundled tracker list. A torrent currently uses one interface at a time.

The dl command

dl is the same engine without the window. It prints a single-line progress bar on a terminal, and Ctrl-C pauses rather than kills: run the same command again and it resumes from the journal.

dl interfaces                  # every interface and the binding in force
dl probe <url>                 # size, ranges, validator, without downloading
dl add <url> [options]         # download

Common recipes

# Spread over two interfaces and verify against a published SHA-256
dl add https://releases.ubuntu.com/26.04/ubuntu-26.04-desktop-amd64.iso \
  --interfaces en0,en5 --verify sha256:9d3f...
 
# Every interface that has a gateway, 16 connections
dl add "$URL" --all-interfaces -n 16
 
# Cap the metered tether, and lift a daytime cap overnight
dl add "$URL" --all-interfaces --limit-interface en5=5M \
  --limit 20M --at 23:00-07:00=off
 
# Two mirrors of the same file, each a lane of its own
dl add https://mirror-a.example/file.iso --mirror https://mirror-b.example/file.iso

dl add options

OptionWhat it does
-o, --output <path>Where to write. For a torrent, the folder its files land in.
-n, --connections <n>Parallel connections. Default 8.
--interfaces <names>Spread across these interfaces, e.g. en0,en5. A misspelled name is an error.
--all-interfacesSpread across every interface that has a gateway.
--limit <rate>Cap this download, e.g. 500K, 2M.
--limit-interface <name=rate>Cap one interface. Repeatable.
--at <HH:MM-HH:MM=rate>Apply a limit only in a window; =off means unlimited. Repeatable.
--verify <digest>sha256:<hex>, blake3:<hex>, md5:<hex>, or a bare BLAKE3 digest.
--durability <mode>safe, balanced (default) or fast.
--chunk-size <size>Chunk size. Chosen from file size and connections if omitted.
--verify-existingOn resume, re-read existing chunks and re-fetch any that fail.
--mirror <url>Another URL for the same file. Must agree on length and validator.
--refresh-command <cmd>A program that prints a fresh URL when the link expires.
--head <size>Fetch only the first N bytes, for testing large files.
--no-resumeOne stream, no journal, nothing to resume.
--forceOverwrite the destination if it exists.

A bare 64-character digest is read as BLAKE3, because that is what dl prints. Paste a SHA-256 from a release page with its sha256: prefix.

Troubleshooting

The phone's Wi-Fi says Not used. Working as intended: the phone is on the same network as your computer, so its Wi-Fi is not a second path. Use its mobile data, or connect it by cable.

The phone does not appear after pairing. A phone joins the next transfer, not one already running. Start a new download. If it still does not appear, check that sharing is on and at least one network is switched on in the app.

Scanning finds nothing. Some networks drop the multicast discovery uses. Use Show code instead: it needs no discovery at all.

A path shows a speed but the total does not rise. Open Settings → Network and check the Binding column. A source-address fall back means the connection may not be leaving by that interface.

macOS says the app cannot be opened. It is not code signed yet. Right-click it, choose Open, and confirm.

The download failed with a range or encoding error. The server did something that would have produced a wrong file. Try again later or use a mirror; it is not something a retry inside Braid can fix safely.

Build Braid from source

git clone https://github.com/InfoDiveLabs/braid && cd braid
cargo build --release            # both binaries
cargo test --workspace           # 507 tests
cargo run -p xtask -- package    # the host platform's installer

Rust 1.92 or newer. Linux also needs libxkbcommon-dev and libfontconfig-dev. Packaging builds only the host platform: .dmg on macOS (stock hdiutil and iconutil), .deb and .rpm on Linux (cargo-deb, cargo-generate-rpm), .msi on Windows (cargo-wix).

How the code is laid out

CrateRole
dl-coreThe engine: scheduling, chunking, crash-safe storage, link refresh. No UI, no sockets.
dl-netThe only crate that knows about sockets and NICs: enumeration, per-interface binding, HTTP, phone pairing.
dl-torrentBitTorrent over librqbit.
dl-testkitA misbehaving origin server, fault injection and the adversarial scenario catalogue.
dl-cliThe dl binary.
dl-guiThe braid binary, a Slint UI.
xtaskScreenshots, size checks, packaging.

dl-core reaches the network only through ByteSource and LaneSet, and the disk only through Storage, so the engine runs against a lying server and a failing drive with no sockets involved. dl-gui never depends on dl-net directly.

Rules worth knowing before a change

  • Journal ordering. Journal::flush is the only place the durable-before-claimed order is expressed. Read it before touching dl-core/src/store.
  • Accept-Encoding: identity on every ranged request. Ranges address encoded bytes.
  • Interface.name is the identity. It keys the socket option, saved selections and limits. The display name is presentation only.
  • One writer. Windows seek_write moves the file cursor, so chunks go through a single writer task.
  • One UI, two token files. ui/platform/{macos,generic}.slint hold per-platform values. A token added to one must be added to the other, or that target stops compiling.

Try the misbehaving server

cargo run -p dl-cli --features devtools -- dev scenarios
cargo run -p dl-cli --features devtools -- dev serve <scenario>

Each scenario serves a file the way a broken server would, and prints the URL and the expected BLAKE3 so you can point dl add at it.

Check other platforms from a Mac

Linux, full test suite in Docker:

docker run --rm -v "$PWD":/src:ro -v /tmp:/tmp rust:1.92-bookworm bash -c '
  apt-get update -qq && apt-get install -y -qq libxkbcommon-dev libfontconfig1-dev pkg-config
  cd /src && export RUSTFLAGS="-D warnings" CARGO_TARGET_DIR=/tmp/lt SLINT_BACKEND=headless
  cargo test --workspace --all-features'

DL_UI_PLATFORM=generic cargo run -p xtask -- screenshots --out artifacts/generic renders the Windows and Linux look from any host.

Build Braid for Android

git clone https://github.com/InfoDiveLabs/braid-android && cd braid-android
./gradlew :relay:test          # 99 JVM tests
./gradlew :app:assembleDebug   # debug APK in app/build/outputs/apk/debug
./gradlew :app:dist            # signed release APK in dist/

JDK 17 and the Android SDK with platform 35. Point local.properties at the SDK with sdk.dir=..., or set ANDROID_HOME.

The project is two modules. relay/ is plain JVM Kotlin with no Android dependency: the proxy on port 8710, request parsing, pairing, lane routing and egress checks, all testable without a phone. app/ is the Android app around it: binding each lane to an Android Network, the foreground service, the screen and pairing.

The rule the proxy lives by:

A request leaves by the network it names, or not at all.

A lane that cannot serve answers 503, and nothing substitutes another lane. The proxy never adds Accept-Encoding, decompresses, caches or retries: the bytes are not the phone's to touch.

Checks that need a real phone, over adb with both devices on one network:

curl -x "http://cell:$KEY@[phone]:8710" https://api.ipify.org   # leaves from cell's egress
curl -x "http://cell:$KEY@[phone]:8710" -r 100-199 <url>         # 206, exactly 100 bytes
adb shell svc data disable                                       # cell becomes 503, never Wi-Fi

Release builds read the signing key from .env or the environment: BRAID_KEYSTORE, BRAID_KEYSTORE_PASSWORD, BRAID_KEY_ALIAS, BRAID_KEY_PASSWORD, or BRAID_KEYSTORE_BASE64 for CI. Pushing a v* tag builds, signs and attaches the APK to a release.

Contributing

Open an issue first for anything beyond a bug fix. Every change must leave these green:

cargo fmt --all
cargo clippy --workspace --all-targets     # silent, warnings included
cargo test --workspace
  • Tests describe the property. a_run_to_midnight_does_not_wrap, not test_schedule_2.
  • New behaviour needs a test that fails without it, especially in storage and networking, where failures are silent.
  • Nothing in the UI may claim to do something it does not. A control that does nothing is worse than an absent one.
  • Per-interface binding changes say which platforms they ran on and how you confirmed the socket option took effect.
  • Pull requests adding search, tracker lists or any content directory are declined.

The most useful contribution right now needs no Rust: if you have a Windows or Linux machine, install Braid and report what happens.

What is not verified

  • Windows at runtime. CI builds and tests it; nobody has installed and used it. IP_UNICAST_IF binding is written from Microsoft's documentation.
  • The Linux desktop. The .desktop file and xdg-mime registration have not been run on a Linux desktop.
  • Real multi-NIC hardware. CI proves binding with virtual interfaces, not two real uplinks.
  • Braid for Android beyond one phone. Tested on a Pixel 7 Pro on Jio, an IPv6-only carrier. Other makers, IPv4 carriers and dual-SIM phones have not been tried, and the app is not on Google Play.

Licences

Braid is free software under the GNU General Public License version 3. Braid for Android is source-available under the PolyForm Noncommercial License 1.0.0: free for any noncommercial use, and commercial use needs a licence from InfoDive Labs.