GhostDrop is an open-source, AirDrop-like nearby transfer system built on public Apple APIs only. It targets iOS 26.0+ and macOS 26.0+, and focuses on realistic offline file exchange over BLE with transport negotiation:
- Prefer L2CAP Credit-Based Channels when available.
- Fallback to GATT with framing, sliding window, and retransmission.
- Use a unified frame transport API through
GhostTransport.
GhostDropKit is the reusable Swift package. Apps/GhostDropiOS and Apps/GhostDropmacOS contain SwiftUI demo app sources.
BLE transfers between Apple devices are practical but nuanced:
- L2CAP availability is device/state dependent.
- GATT MTU and write flow control impose tight constraints.
- Real-world transfer UX needs state machines, verification, and resume support.
GhostDrop demonstrates a production-oriented architecture that works within public CoreBluetooth limitations.
- Open the repo in Xcode 26+.
- Add
GhostDropKitas a local package dependency. - Create two app targets from
Apps/GhostDropiOSandApps/GhostDropmacOSsource files. - Add required Bluetooth/Local Network privacy keys (see Info.plist & Entitlements Notes).
- Run one device in Receive mode and the other in Send mode.
- Connect, compare SAS code, confirm, then transfer a file.
+-----------------------------+ +-----------------------------+
| SwiftUI App | | SwiftUI App |
| (iOS/macOS MainActor UI) | | (iOS/macOS MainActor UI) |
+--------------+--------------+ +--------------+--------------+
| AsyncStream<GhostEvent> |
v v
+--------------------------------------------------------------------------+
| SessionActor |
| handshake, SAS verification, transfer state machine, resume integration |
+------------------------+---------------------------+----------------------+
| |
v v
+----------------+ +----------------+
| TransportActor | | ResumeStore |
| negotiate path | | persisted state|
+-------+--------+ +----------------+
|
+-------------+-------------------+
| |
v v
+-----------------------+ +-----------------------+
| L2CAPTransport | | GATTTransport |
| stream read/write API | | fragment/reassemble |
| backpressure + cancel | | window + retransmit |
+-----------+-----------+ +-----------+-----------+
| |
+------------- CoreBluetooth -------+
BLECentralActor / BLEPeripheralActor
Sources/GhostDropKit/BLE: CoreBluetooth roles and capability advertisement/parsing.Sources/GhostDropKit/Transport:GhostTransport, L2CAP and GATT transports, sliding window.Sources/GhostDropKit/Protocol: typed frames + binary envelope codec.Sources/GhostDropKit/Security: ECDH/HKDF handshake, SAS derivation, AES-GCM context.Sources/GhostDropKit/Storage: persisted resume states and inbound file storage.Sources/GhostDropKit/SessionActor.swift: transfer/session state machine and event bus.
- Ephemeral P-256 ECDH key exchange.
- Shared secret expanded via HKDF-SHA256 into encryption/auth key material.
- Transcript hash binds both peer public keys, nonces, and session id.
- 6-digit SAS is derived deterministically from transcript hash.
- Both devices display the same code.
- Transfer proceeds only when user explicitly confirms “Codes match”.
- Data chunks are encrypted with AES.GCM using sequence-derived nonces.
- Control frames can be wrapped as encrypted envelopes post-verification.
SessionCryptoContextexposes additional HMAC support for authenticated control framing extensions.
metadata: filename, size, mime type, SHA256, chunk size.data: sequence + payload (AES-GCM protected after verification).ack: cumulative ack + selective NACK bitmap.resume: receiver reports last confirmed sequence after reconnect.complete: sender final digest; receiver verifies final SHA256.cancel: explicit cancellation.
Receiver persists resume state in Application Support (GhostDrop/Resume) and continues from lastConfirmed + 1 on reconnect.
- Receive mode (advertising status).
- Send mode (scan + nearby list + connect).
- Pairing screen with SAS code and confirm/cancel.
- Transfer panel with progress, throughput, ETA, selected transport.
- Live log viewer and NDJSON log export.
- File picker wrappers:
- iOS:
UIDocumentPickerViewController - macOS:
NSOpenPanel
- iOS:
- Launch both iOS apps.
- Device A: Receive mode.
- Device B: Send mode, pick Device A.
- Verify SAS code on both devices.
- Send file.
- Launch both macOS apps.
- Same steps as above.
- Put either side in Receive mode.
- Other side in Send mode, connect.
- Confirm SAS and transfer.
- No private APIs are used.
- BLE discovery/connection behavior depends on platform radio conditions and user permissions.
- Background scan/advertise reliability is intentionally not guaranteed.
- Effective throughput varies by MTU, write-without-response availability, and link quality.
- L2CAP support can vary by peer/device state; automatic GATT fallback is required.
- Ensure Bluetooth is enabled on both devices.
- Verify app privacy keys are present and permissions granted.
- Keep devices close and unlocked during pairing/transfer.
- If discovery is intermittent, restart scan/advertise and relaunch one peer.
- If transfers stall on GATT, reduce chunk size and window size in capabilities.
Run unit tests:
swift testCovers:
- frame codec round-trip + fuzz-ish data tests
- handshake transcript/SAS determinism
- crypto context sealing/opening
- resume persistence
- GATT sliding window ACK/NACK/timeout behavior
Add these keys for both demo apps:
NSBluetoothAlwaysUsageDescription: “GhostDrop uses Bluetooth to discover nearby devices and transfer files.”NSBluetoothPeripheralUsageDescription: “GhostDrop advertises and receives nearby file transfers.”
Recommended for iOS demo UX (if needed by your file flow):
UISupportsDocumentBrowser(optional, if using document browser model)
For hardened macOS targets, ensure Bluetooth capability is enabled in Signing & Capabilities if your template requires it.
GhostDrop/
Package.swift
Sources/GhostDropKit/
Sources/GhostDropKit/BLE/
Sources/GhostDropKit/Transport/
Sources/GhostDropKit/Protocol/
Sources/GhostDropKit/Security/
Sources/GhostDropKit/Storage/
Tests/GhostDropKitTests/
Apps/GhostDropiOS/
Apps/GhostDropmacOS/
README.md