A Kotlin Multiplatform (KMP) BLE mesh messaging library for Android, iOS, macOS, Linux, and JVM. MeshLink enables secure, multi-hop peer-to-peer communication over Bluetooth Low Energy with ~85% shared code across platforms.
- Multi-hop mesh routing — Babel-based routing (RFC 8966 adapted for BLE) with proactive route propagation, composite cost metrics, and loop-free convergence
- Two-layer encryption — Hop-by-hop (Noise XX) and end-to-end (Noise K) using
Noise_XX_25519_ChaChaPoly_SHA256 - Zero external crypto dependencies — Pure Kotlin Ed25519/X25519, platform-native SHA-256 and ChaCha20-Poly1305
- Large message transfer — Chunking, selective ACKs (SACK), AIMD congestion control, byte-offset resume
- Power-aware operation — Automatic tuning of scan duty cycle, advertising interval, and chunk sizes based on battery level
- L2CAP support — High-throughput data plane with automatic GATT fallback
- Real-time diagnostics — 22 diagnostic event codes, health snapshots, and Flow-based event streams
- Identity rotation — Rotate keys with signed broadcast announcements and configurable grace periods
flowchart LR
subgraph Sender["Sender Device"]
App1["App"] --> ML1["MeshLink"]
end
subgraph Relay["Relay Peer"]
ML2["MeshLink\nDecrypt hop → Re-encrypt hop\nE2E payload stays sealed"]
end
subgraph Recipient["Recipient Device"]
ML3["MeshLink"] --> App2["App"]
end
ML1 -->|"Noise XX\nhop-by-hop"| ML2
ML2 -->|"Noise XX\nhop-by-hop"| ML3
ML1 -.->|"Noise K\nend-to-end sealed payload"| ML3
style Sender fill:#e8f5e9,stroke:#388e3c
style Relay fill:#fff3e0,stroke:#f57c00
style Recipient fill:#e3f2fd,stroke:#1976d2
Messages are encrypted twice: Noise K seals the payload end-to-end (only the recipient can read it), while Noise XX encrypts each BLE hop independently. Relay peers forward opaque ciphertext — they never see plaintext. Routes are discovered automatically via Babel (RFC 8966). See Design and Diagrams for full details.
| Target | Min Version | Details |
|---|---|---|
| Android | API 26 | Pure Kotlin crypto (API 26–32), JCA (API 33+) |
| iOS | arm64, simulatorArm64 | CoreBluetooth, Keychain |
| macOS | arm64 | CoreBluetooth, Keychain |
| Linux | arm64, x64 | Raw HCI/L2CAP sockets via BlueZ kernel API |
| JVM | Java 21 | Java native JCA crypto |
| Category | Technology | Version |
|---|---|---|
| Language | Kotlin (Multiplatform) | 2.3.20 |
| Build System | Gradle (Kotlin DSL) | 9.4.1 |
| JVM | Java (Zulu recommended) | 21 |
| Android | Android Gradle Plugin | 9.1.1 |
| compileSdk / minSdk | 36 / 26 | |
| Apple | Xcode / Swift (CryptoKit bridge) | 15+ |
| Linux | BlueZ (HCI/L2CAP/D-Bus) | kernel API |
| Concurrency | kotlinx-coroutines | 1.10.2 |
| Encryption | Noise XX (hop-by-hop) + Noise K (end-to-end) | 25519_ChaChaPoly_SHA256 |
| Crypto primitives | Pure-Kotlin Ed25519/X25519, platform-native SHA-256 & ChaCha20-Poly1305 | — |
| Compression | Raw DEFLATE (RFC 1951) | — |
| Wire format | Custom binary protocol (TLV-extensible) | — |
| Linting | detekt | 1.23.8 |
| Testing | kotlin.test + Power Assert plugin | — |
| UI tests | Maestro | — |
| iOS/macOS distribution | XCFramework via Swift Package Manager | — |
| Publishing | Maven Central (Sonatype) | — |
val mesh = MeshLink(
transport = bleTransport,
config = meshLinkConfig { maxMessageSize = 50_000 },
crypto = CryptoProvider(),
cryptoDispatcher = Dispatchers.Default, // offload crypto to background threads
)
mesh.start()See the Integration Guide for complete setup, configuration presets, and platform-specific examples.
MeshLink ships with four presets — smallPayloadLowLatency, largePayloadHighThroughput,
minimalResourceUsage, and minimalOverhead — see the
API Reference for details.
MeshLink uses two-layer Noise protocol encryption — Noise XX
(Noise_XX_25519_ChaChaPoly_SHA256) for hop-by-hop confidentiality between
neighbors and Noise K (Noise_K_25519_ChaChaPoly_SHA256) for end-to-end
authentication between sender and recipient. Identity is Ed25519 with TOFU
(Trust-On-First-Discover) key pinning. See the
Threat Model and
Integration Guide § Encryption & Trust
for details.
MeshLink/
├── meshlink/ # Core KMP library
│ └── src/
│ ├── commonMain/ # ~85% shared code
│ ├── appleMain/ # Shared Apple (CoreBluetooth, Keychain)
│ ├── iosMain/ # iOS-specific (UIKit battery)
│ ├── macosMain/ # macOS-specific (battery stub)
│ ├── linuxMain/ # Linux-specific (POSIX time, battery stub)
│ ├── androidMain/ # Android BLE, storage, services
│ ├── jvmMain/ # JVM crypto provider
│ └── commonTest/ # 1,500+ tests
├── meshlink-sample/
│ ├── android/ # Jetpack Compose reference app
│ ├── ios/ # SwiftUI reference app
│ ├── macos/ # SwiftUI macOS reference app
│ ├── jvm/ # JVM console app
│ └── linux/ # Linux console app (arm64, x64)
├── docs/ # Architecture, API, wire format spec
├── Package.swift # SPM manifest (iOS + macOS)
└── UBIQUITOUS_LANGUAGE.md # Domain glossary
Tutorial · Integration Guide · How to Monitor · Best Practices · Design · Wire Format Spec · API Reference · Threat Model · Diagrams
- Java 21 (e.g., Zulu, Temurin)
- Gradle 9.4+ (wrapper included)
- Android SDK with
compileSdk 36(for Android target) - Xcode 15+ (for iOS/macOS targets, macOS only)
# Run all JVM tests
./gradlew :meshlink:jvmTest
# Run integration tests only (50+ end-to-end scenarios)
./gradlew :meshlink:jvmTest --tests "io.meshlink.MeshIntegrationTest" --parallel
# Compile Android AAR
./gradlew :meshlink:compileAndroidMain
# Run iOS simulator tests (requires macOS + Xcode)
./gradlew :meshlink:iosSimulatorArm64Test
# Run macOS tests
./gradlew :meshlink:macosArm64Test
# Compile Linux targets (cross-compiles from macOS)
./gradlew :meshlink:compileKotlinLinuxX64
./gradlew :meshlink:compileKotlinLinuxArm64
# Build XCFramework for Swift Package Manager (iOS + macOS)
./gradlew :meshlink:assembleMeshLinkXCFramework./gradlew :meshlink-sample:jvm:runWith Android Studio:
- Open the root
MeshLink/directory in Android Studio (File → Open) - Wait for Gradle sync to complete
- In the Run Configurations dropdown, select
meshlink-sample.android - Select a device or emulator (API 26+)
- Click ▶ Run
- Grant Bluetooth and Location permissions when prompted
Tip: If the run configuration doesn't appear, go to Run → Edit Configurations → + → Android App, set module to
MeshLink.meshlink-sample.android.mainand launch activity toio.meshlink.sample.MainActivity.
From the command line:
# Build debug APK
./gradlew :meshlink-sample:android:assembleDebug
# APK location:
# meshlink-sample/android/build/outputs/apk/debug/android-debug.apk
# Install on connected device
adb install meshlink-sample/android/build/outputs/apk/debug/android-debug.apkThe app has four screens: Chat (start/stop mesh, send messages), Mesh Visualizer (peer topology graph), Diagnostics (filterable event log), and Settings (config presets, MTU).
The sample uses the real AndroidBleTransport — it will advertise, scan,
and connect to other MeshLink devices over BLE. Runtime permissions
(Bluetooth Scan/Connect/Advertise + Location) are requested on launch.
Both the Android and iOS sample apps include Maestro UI automation flows covering navigation, lifecycle, messaging, settings, and diagnostics (10 flows each).
# Android (emulator or device required)
maestro test maestro/flows/android/
# iOS (Simulator required)
maestro --platform=ios test maestro/flows/ios/
# Smoke tests only
maestro test maestro/flows/android/ --include-tags=smokeSee maestro/flows/ for the full flow listing and shared subflows. Install
Maestro CLI with curl -fsSL "https://get.maestro.mobile.dev" | bash.
./gradlew :meshlink:assembleMeshLinkXCFramework
open meshlink-sample/ios/MeshLinkSample.xcodeprojBuild the XCFramework, open the pre-configured Xcode project, and hit ⌘R. See meshlink-sample/ios/README.md for details.
./gradlew :meshlink:assembleMeshLinkXCFramework
open meshlink-sample/macos/MeshLinkSample.xcodeprojBuild the XCFramework, open the pre-configured Xcode project, and hit ⌘R. See meshlink-sample/macos/README.md for details.
# Cross-compile from macOS
./gradlew :meshlink-sample:linux:linkReleaseExecutableLinuxX64
./gradlew :meshlink-sample:linux:linkReleaseExecutableLinuxArm64
# Executable locations:
# meshlink-sample/linux/build/bin/linuxX64/releaseExecutable/linux.kexe
# meshlink-sample/linux/build/bin/linuxArm64/releaseExecutable/linux.kexe
# Copy to target Linux machine and run:
# ./linux.kexeTo use real BLE hardware on Linux, edit Main.kt to use LinuxBleTransport instead of DemoTransport. Requires CAP_NET_RAW or root.
GitHub Actions runs on every push and PR (when relevant files change):
- JVM tests (Ubuntu)
- Android compilation (Ubuntu)
- iOS simulator tests (macOS, Apple Silicon)
- macOS native tests (macOS, Apple Silicon)
- Linux native compilation (Ubuntu)
Unlicense — Public domain.