Back to Flyingcarpet

FlyingCarpet Connection Architecture

ARCHITECTURE.md

10.0.411.7 KB
Original Source

FlyingCarpet Connection Architecture

Overview

A transfer involves four independent role axes. Each device takes one role on each axis. The roles are determined by a combination of which device is sending/receiving and the peer's operating system.

AxisRole ARole B
Transfer directionSenderReceiver
BLEPeripheral (advertiser)Central (scanner)
WiFi hotspotHostJoiner
TCPServer (listener)Client (connector)

These four axes are not all aligned — the mapping depends on the platform pair and connection mode.


Bluetooth Roles

BLE role is determined solely by transfer direction, the same on every platform:

Transfer DirectionBLE Role
SenderPeripheral — advertises GATT service, exposes characteristics
ReceiverCentral — scans for peripheral, connects, reads/writes characteristics

Source:

  • Linux: core/src/linux/bluetooth.rsMode::Send → peripheral branch (line 85), else → central branch (line 156)
  • Windows: core/src/windows/bluetooth.rsMode::Send → peripheral branch (line 74), else → central branch (line 144)
  • Android: MainActivity.ktMode.Sendingadvertise(), Mode.Receivingscan() (lines 83-88)

BLE Data Flow

The GATT service exposes three characteristics: OS, SSID, Password.

Data flow direction depends on who is hosting the hotspot, not who is the BLE peripheral/central:

Hosting device is...SSID/Password flow
Peripheral (sender is hosting)Peripheral populates GATT; central reads
Central (receiver is hosting)Central writes SSID/password to peripheral's GATT

The OS characteristic always flows both ways: each side needs to know the other's OS.


Hotspot Hosting

The hotspot host generates the WiFi password, creates the hotspot, and acts as TCP server. The joiner connects to the hotspot and acts as TCP client.

Linux is_hosting(peer, mode)

Peer OSLinux Hosts?
AndroidAlways
iOSAlways
macOSAlways
WindowsNever
LinuxOnly if Receiving

Source: core/src/linux/network.rs lines 11-20

Windows is_hosting(peer, mode)

Peer OSWindows Hosts?
AndroidAlways
iOSAlways
macOSAlways
LinuxAlways
WindowsOnly if Receiving

Source: core/src/windows/network.rs lines 674-683

Android isHosting()

Peer OSAndroid Hosts?
iOSAlways
macOSAlways
AndroidOnly if Receiving
LinuxNever (Linux hosts)
WindowsNever (Windows hosts)

Source: MainViewModel.kt lines 112-116

iOS / macOS

iOS and macOS never host hotspots — they lack a public hotspot API. The peer always hosts.


Peer OS Selection — why it exists, and when it's hidden

The "Select Peer OS" control (desktop radio buttons, Android spinner, macOS peerSwitch) exists to answer one question the app can't otherwise answer before a connection exists: who hosts the hotspot? is_hosting(peer, mode) is a pure function of this device's OS, the peer's OS, and the transfer direction (see the tables above). This device knows its own OS and direction; the missing input is the peer's OS.

There are three ways to learn the peer's OS, and the UI control is only the third:

  1. Shared network mode — nobody hosts, so is_hosting is never called. Roles are fixed by direction (receiver = TCP server + password generator) and the peer is found by discovery over IP. Peer OS is irrelevant. → control hidden.
  2. Hotspot mode with Bluetooth — the BLE GATT OS characteristic carries each side's OS automatically (it always flows both ways, see BLE Data Flow). Peer OS is learned over the air. → control hidden.
  3. Hotspot mode without Bluetooth — there is no channel to exchange OS before the hotspot exists (chicken-and-egg), so the user supplies it. → control shown. This is the only case where it's needed.

Every platform gates the control on exactly this condition. The desktop's checkStatus() hides peerBox when connectionMode === 'shared_network' || usingBluetooth; macOS hides peerSwitch in the same two cases; iOS omits the control entirely (see below).

Beyond the hosting decision, knowing the peer OS in hotspot-without-BT also drives two secondary behaviors:

  • Android's SSID is OS-assigned, not derivable from the password, so an Android peer means the joiner must be told the SSID separately (every other peer derives the SSID from the password). This is why the desktop/macOS prompt for SSID only when the peer is Android.
  • Fast-fail on impossible pairs. Selecting a macOS/iOS peer in hotspot mode is invalid (neither Apple device can host), so the app can reject it up front with "use Shared Network mode" instead of failing deep in the join.

Could it be removed?

Not on host-capable platforms (Windows/Linux/Android) in hotspot-without-BT: the hosting decision genuinely needs the peer OS, and there's no earlier channel to negotiate it. The control is effectively the manual stand-in for the BLE OS exchange. The realistic ways to shrink it are to lean harder on the two automatic paths (Bluetooth or shared network) rather than to drop the manual fallback. On Apple platforms the picture is different — see Apple/CLAUDE.md, since Apple never hosts and so never needs the peer OS to decide hosting at all.


TCP Roles (Hotspot Mode)

TCP role follows directly from hotspot role:

Hotspot RoleTCP Role
HostServer — binds 0.0.0.0:3290, calls accept()
JoinerClient — connects to host's gateway IP on port 3290

Source:

  • Rust: core/src/lib.rs start_tcp()PeerResource::WifiClient → connect, otherwise → bind+accept
  • Android: MainViewModel.kt startTCP()isHosting()ServerSocket(3290).accept(), else → Socket(peerIP, 3290)

Password Generation & Sharing

The hotspot host generates the password. It is shared with the peer via one of:

  1. Bluetooth — delivered through GATT characteristics (direction depends on who's hosting, see BLE Data Flow above)
  2. QR code — host displays QR code, joiner scans it (for mobile peers)
  3. Manual entry — host displays password as text, joiner types it in

The desktop app's needPassword() function:

  • Returns false → this device is hosting → generates and displays the password
  • Returns true → this device is joining → user must enter the host's password

Complete Platform Pair Matrix (Hotspot Mode)

"A → B" means A is sending to B.

ScenarioBLE: PeripheralBLE: CentralHotspot HostTCP ServerPassword Generator
Linux → AndroidLinuxAndroidLinuxLinuxLinux
Android → LinuxAndroidLinuxLinuxLinuxLinux
Linux → WindowsLinuxWindowsWindowsWindowsWindows
Windows → LinuxWindowsLinuxWindowsWindowsWindows
Linux → iOS/macOSLinuxiOS/macOSLinuxLinuxLinux
Windows → iOS/macOSWindowsiOS/macOSWindowsWindowsWindows
Android → iOS/macOSAndroidiOS/macOSAndroidAndroidAndroid
Linux → LinuxSenderReceiverReceiverReceiverReceiver
Windows → WindowsSenderReceiverReceiverReceiverReceiver
Android → AndroidSenderReceiverReceiverReceiverReceiver
Windows → AndroidWindowsAndroidWindowsWindowsWindows
Android → WindowsAndroidWindowsWindowsWindowsWindows

Key pattern: Hotspot Host = TCP Server = Password Generator. BLE Peripheral = Sender, BLE Central = Receiver. These two groupings are independent.

In shared network mode, there is no hotspot host. The Receiver takes over as TCP Server and Password Generator — consistent with the same-platform hotspot convention. See the Shared Network Mode section below.


Shared Network Mode

No hotspot is created. Both devices are already on the same LAN.

Discovery

Both devices simultaneously:

  1. Send HMAC-signed announcements via UDP multicast (239.255.73.67:3290) and unicast subnet scan
  2. Listen for announcements from the peer
  3. Validate: magic bytes, HMAC (using password-derived key), timestamp window, opposite role

TCP in Shared Network Mode

Transfer DirectionTCP Role
ReceiverServer — binds TCP listener on port 3290 before discovery starts
SenderClient — connects to receiver's IP after discovery completes

This is consistent with same-platform hotspot mode, where the receiver always hosts and is the TCP server.

Password in Shared Network Mode (without Bluetooth)

  • Receiver generates the password and displays it
  • Sender enters the password manually

Consistent with hotspot mode: the receiver is always the "anchor" role (host in hotspot, server + password generator in shared network).

The password is stretched with PBKDF2-HMAC-SHA256 into the Noise pre-shared key (PSK); the discovery HMAC key is derived from that PSK, and the same PSK authenticates the Noise handshake that encrypts the whole transfer with ChaCha20-Poly1305. There is no separate password-derived AES key — v10 removed the old inner per-chunk AES and Noise is the sole cipher. See docs/shared-network-crypto.md.

Bluetooth + Shared Network Mode

The current BLE protocol is tightly coupled to the hotspot flow — it exchanges peer OS, SSID, and password, and uses is_hosting() to determine BLE data flow direction. In shared network mode, peer OS and SSID are irrelevant, and is_hosting() doesn't apply.

Decision: Bluetooth is hotspot-only. Shared network mode exchanges the password manually (receiver displays it; sender types it or scans a QR code). This is deliberate, not a missing feature — do not re-add BLE to shared network mode.

Rationale. The only reason to want BLE in shared mode is to spare users from typing the password, and the users who most need that are Apple-to-Apple pairs: Apple devices cannot host a hotspot (no public API), so they are forced into shared network mode and always type. But two Apple devices cannot complete the BLE exchange at all. Apple does not support Bluetooth pairing between an iPhone and a Mac — by design; Continuity/AirDrop use BLE only for discovery and move data over Wi-Fi/AWDL, never forming a classic bond — and Flying Carpet's GATT characteristics all require an encrypted, bonded link (.readEncryptionRequired / .writeEncryptionRequired). No bond is possible, so the encryption-required characteristics can never be read: the exchange fails for exactly the pairing that motivates it.

Every other pairing that could do BLE (non-Apple↔non-Apple, or Apple↔non-Apple) can also almost always use hotspot mode instead, where BLE already works. So the residual benefit of shared-mode BLE is a thin slice of transfers, bought at the cost of a new GATT flow across three codebases (Rust core, Android, Apple) plus manual-entry fallbacks. The Apple repo (FlyingCarpetApple) actually implemented shared-mode BLE in commit 4a6b889 and then removed it in b7e9b59 for these reasons; this note records that conclusion for the desktop/Android side so it isn't rediscovered. If reducing password typing ever becomes a priority, improve the out-of-band password UX (QR) that works on every platform pair — not BLE.