Skip to content

Latest commit

 

History

History
57 lines (39 loc) · 8.1 KB

File metadata and controls

57 lines (39 loc) · 8.1 KB

Flying Carpet

Encrypted, no-internet file transfer between Android, iOS, Linux, macOS, and Windows, over either an ad-hoc Wi-Fi hotspot (one device hosts) or a shared network both devices are already on. Two devices, Wi-Fi (or ethernet), optionally Bluetooth. Port 3290 throughout.

Three implementations, one wire protocol (critical)

All five platforms live in this repo, in three independent implementations of the same wire protocol. A change to the on-the-wire format in one is a breaking change unless mirrored in the other two:

  • Rust (core/) — the reference implementation the other two are tested against. Ships the Tauri desktop app (Flying Carpet/) for Windows + Linux.
  • Kotlin (Android/FlyingCarpet/) — Android.
  • Swift (Apple/) — iOS + macOS. A full port, not a binding over the Rust core. Build only on a Mac; see Apple/CLAUDE.md.

When touching the protocol (discovery bytes, version/mode preamble, Noise handshake, framing), update all three implementations and their cross-platform known-answer tests together.

Where code lives vs. where binaries ship (don't conflate these)

  • The Rust code builds for Windows and Linux only. core/src/ has just windows/ and linux/; lib.rs cfg-selects network/bluetooth on those two target_os values, and there is no target_os = "macos" anywhere in core/. A Tauri/wry/webkit2gtk change therefore affects two desktop platforms, not three — macOS is served by the Swift app in Apple/, which shares no code with core/.
  • The macOS binary is released from this repo's Releases page as a .zip of the .app (macOS_FlyingCarpet_<version>.zip) — not a .dmg. The .dmg format ended at v8.0.1; v9.0.0 switched to .zip. Don't infer from a macOS binary existing that the Rust code targets macOS — it's built from Apple/macOS/. There is also a Homebrew cask (brew install flying-carpet), which is maintained outside this repo.
  • tauri.conf.json still lists icons/icon.icns — genuinely stale, intentionally left alone. Not evidence of macOS support either.
  • The Swift code was developed in a separate FlyingCarpetApple repo and imported without history at the v10 release, so git log on Apple/ starts at the import commit. Commit hashes cited for Swift changes in the older docs (e.g. 4c59af6 in docs/bluetooth-field-guide.md, 4a6b889/b7e9b59 in ARCHITECTURE.md) belong to that repo and won't resolve here.

Layout

  • core/ — Rust core crate flying-carpet-core (v10). Platform-split: core/src/{windows,linux}/ for network/bluetooth/peripheral/central; the bluetooth module is cfg-selected per-OS in lib.rs. Key files: lib.rs (start_transfer entry point), discovery.rs, noise.rs, sending.rs/receiving.rs.
  • Flying Carpet/ — Tauri desktop app. Rust backend in Flying Carpet/src-tauri/ (workspace member), JS/HTML frontend in Flying Carpet/src/ (main.js, index.html). Note the space in the directory name — quote it in shell commands.
  • Android/FlyingCarpet/ — Android app (Kotlin). Noise/discovery ports in app/src/main/java/dev/spiegl/flyingcarpet/.
  • Apple/ — iOS + macOS apps (Swift). Protocol code shared by both in Apple/shared/, per-platform UI in Apple/iOS/ and Apple/macOS/. Read Apple/CLAUDE.md before changing anything under it — the Apple platforms have constraints the others don't (no programmatic hotspot, so Apple-to-Apple requires shared network mode; BLE can't pair iPhone↔Mac).
  • docs/ — design docs (see below). ARCHITECTURE.md — connection role model.

Build & test

Rust is a Cargo workspace (core + Flying Carpet/src-tauri):

  • cargo test — run all Rust tests (includes the Noise/discovery known-answer vectors in core/src/noise.rs and core/src/discovery.rs). cargo build to compile.
  • Desktop app: cargo tauri dev (run) / cargo tauri build (release). Needs the Tauri CLI and the Linux deps listed in README.md.
  • Android: from Android/FlyingCarpet/, ./gradlew assembleDebug / ./gradlew test. Set JAVA_HOME to the Android Studio JBR (the bundled JDK) or Gradle fails.
  • Apple: macOS only, and outside the Cargo workspace — cargo build never touches it. From Apple/, DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcodebuild -project <iOS|macOS>/FlyingCarpet.xcodeproj -scheme "FlyingCarpet" build. DEVELOPMENT_TEAM is blank in both projects, so signing fails until you set your team in Xcode or pass DEVELOPMENT_TEAM=YOURTEAMID. Details in Apple/CLAUDE.md.

Architecture & design docs — read before changing these areas

  • ARCHITECTURE.md — the four independent role axes (transfer direction / BLE peripheral-central / hotspot host-joiner / TCP server-client) and how they map per platform pair. Read before touching connection setup, hosting logic, or BLE.
  • docs/shared-network-crypto.md — the full v10 cryptographic design (the normative reference for the handshake bytes). Read before touching anything crypto, discovery-auth, or the record/framing layer.
  • docs/bluetooth-field-guide.md — read before touching any BLE code on any platform. The four independent axes (advertising / scanning / bonding / GATT services), seven hard-won laws, a per-platform matrix, and a symptom→cause playbook. Bluetooth bugs here are subtle, intermittent, and platform-asymmetric; several have been re-derived from scratch more than once. Chronological investigation logs: docs/windows-ble-gatt-0x8000ffff.md, docs/ble-bond-asymmetries.md.

Load-bearing invariants (don't "simplify" these)

  • v10 = Noise. Every transfer (both modes) runs a Noise_NNpsk0_25519_ChaChaPoly_SHA256 handshake; the PSK is PBKDF2-HMAC-SHA256(password, salt="Flying Carpet v10 shared network PSK", 600_000). Noise is the sole cipher — the old inner per-chunk AES is gone. v10 is a clean break; v9 peers are rejected. If v10 ships before a later Noise wire change, that change must bump to v11.
  • Preamble → prologue binding. Version/mode are negotiated in a plaintext preamble, then every preamble byte is bound into the Noise prologue. Both platforms of any pair must build the prologue identically (build_prologue/buildPrologue). Cross-platform KATs guard this — keep all three in sync (Rust core/src/noise.rs, Kotlin NoiseUnitTest, Swift Apple/macOS/FlyingCarpetTests/FlyingCarpetTests.swift; discovery vector: core/src/discovery.rs test_cross_platform_vector == Android DiscoveryUnitTest.kt == the Swift discovery test in the same file). Note the Swift KATs run only under xcodebuild on a Mac, so cargo test and ./gradlew test passing is not evidence the Swift side still agrees.
  • Passwords: single-use + CSPRNG. The receiver mints a fresh random password per transfer and displays it; never reuse, never user-chosen, never "remember." The entire "offline crack is worthless" security argument depends on this (see the crypto doc §7). The discovery HMAC key is derived from the stretched PSK, so no fast hash of the password goes on the wire.
  • Bluetooth is hotspot-only. Shared network mode exchanges the password manually (display + type/QR); do not re-add BLE to shared mode. Apple-to-Apple can't pair iPhone↔Mac over BLE by design, which is exactly the pair that would need it. Rationale is recorded in ARCHITECTURE.md ("Bluetooth + Shared Network Mode"). Every platform greys out its BT switch in shared network mode; on Apple that goes through bluetoothSwitchShouldBeEnabled(), since restoring the switch on hardware capability alone re-enabled it after a transfer.
  • Receiver is the anchor. In both modes the receiver generates the password and is the TCP server (Noise responder); the sender is the TCP client (Noise initiator).

Conventions & gotchas

  • Android: keep res/layout/ and res/layout-land/ in sync when changing the UI.
  • core/Cargo.toml is pinned to LF line endings (.gitattributes); don't let an editor rewrite it to CRLF.
  • Header-value bounds (file count / filename length / chunk size sanity checks) and filename sanitization apply to values read from the Noise-decrypted stream, not the raw socket.