
Authenticated peer discovery, messaging, and durable streaming file transfer over local networks with Noise-based mutual authentication, persistent identities, and SHA-256 verified commits.
P2pKit is a Kotlin Multiplatform library for authenticated peer discovery, messaging, and durable file transfer over a reachable local network. It supports Android, JVM/Desktop, and iOS through one transport-independent API.
Development version: 0.7.0-SNAPSHOT.
Latest published version: 0.7.0-rc3.
0.7.0-rc3 is available from Maven Central under
io.github.apdelrahman1911. It is a release candidate: the automated release
gates are extensive, but campaign status is governed by the
validation authority.
RC3's immutable tag and artifacts are historical release evidence. Each new
campaign must explicitly freeze its approved commit and artifact set under
that handbook; results from later main snapshots must not be reported as
RC3 evidence.
iosArm64,
iosSimulatorArm64, and iosX64.Noise_XX_25519_ChaChaPoly_SHA256 and persistent X25519 identities.P2pKit does not provide internet signaling, NAT traversal, relays, accounts, rooms, or application-level authorization. Both peers must already be mutually reachable on the LAN. Guest/enterprise Wi-Fi may block multicast or peer TCP.
Use mavenCentral() and keep all P2pKit modules on the same version.
Supported consumer languages are Kotlin (Android, JVM/Desktop, and KMP) and
Swift through the source-built XCFramework. Java is not supported as an
end-to-end SDK integration; see the Java interop limitations.
| Published module | Purpose |
|---|---|
io.github.apdelrahman1911:p2p-core:0.7.0-rc3 |
Public API, protocol, security, sessions, and file transfer |
io.github.apdelrahman1911:p2p-transport-lan:0.7.0-rc3 |
Multiplatform LAN discovery and TCP transport |
io.github.apdelrahman1911:p2p-network-provisioning-android:0.7.0-rc3 |
Optional Android provisioning sidecar |
io.github.apdelrahman1911:p2p-network-provisioning-desktop:0.7.0-rc3 |
Optional JVM/Desktop manual-endpoint sidecar |
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.apdelrahman1911:p2p-core:0.7.0-rc3")
implementation("io.github.apdelrahman1911:p2p-transport-lan:0.7.0-rc3")
}
}
}dependencies {
implementation("io.github.apdelrahman1911:p2p-transport-lan-android:0.7.0-rc3")
// Optional hotspot/Wi-Fi provisioning:
implementation("io.github.apdelrahman1911:p2p-network-provisioning-android-android:0.7.0-rc3")
}dependencies {
implementation("io.github.apdelrahman1911:p2p-transport-lan-jvm:0.7.0-rc3")
// Optional manual-endpoint provisioning:
implementation("io.github.apdelrahman1911:p2p-network-provisioning-desktop:0.7.0-rc3")
}Kotlin Multiplatform root coordinates select their platform variants. The
complete 15-coordinate publication set is recorded in the
0.7.0-rc3 release record.
The Maven publications serve Kotlin Multiplatform consumers. A direct Swift
application builds P2pKitShared.xcframework from this repository:
./gradlew :p2p-transport-lan:assembleP2pKitSharedReleaseXCFrameworkThe maintained Swift sample and provenance-checked integration live under
samples/iosApp.
Current development builds with Kotlin 2.4.10 while retaining the iOS 14 library floor explicitly. Kotlin Multiplatform applications that link their own Apple binary must apply the iOS 14 Kotlin/Native override documented in the compatibility policy; repository-built XCFrameworks apply it and verify every Mach-O slice automatically.
Authenticated v2 is the default and fails closed. Exchange the full pairing QR or fingerprint through a trusted channel, then pin the expected identity:
import dev.p2pkit.core.AppId
import dev.p2pkit.core.P2pKit
import dev.p2pkit.core.P2pMessage
import dev.p2pkit.transport.lan.lan
import kotlinx.coroutines.flow.first
val kit = P2pKit.create {
appId = AppId("com.example.transfer")
deviceName = "My device"
transports { lan() } // Android: lan(applicationContext)
}
kit.start()
kit.startAdvertising()
kit.startDiscovery()
val expectedFingerprint =
requireNotNull(kit.parsePeerPairingQr(qrFromTrustedChannel))
val peer = kit.peers.first { it.isNotEmpty() }.first()
val session = kit.connect(peer, expectedFingerprint)
session.send(P2pMessage.Text("hello"))For an executable discovered-peer example, the CLI's pairing and
connect-pinned <peer-alias> <full-pairing-QR> commands implement this exchange;
see the sample pairing walkthrough.
The interactive samples' incoming admission remains development-only, not an
allowlist. The KMP sample separately demonstrates PinnedOnly admission.
Subscribe to incomingSessions before advertising and attach each
session.incoming collector promptly; these are hot event streams. send()
confirms a local transport write, not remote application processing. Add
domain-level IDs, ordering, deduplication, acknowledgements, and repair where
your application requires them.
Initialize secure identity storage once from Application.onCreate() before
constructing a kit:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
P2pKitAndroid.initialize(this)
}
}Declare the base LAN permissions:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />For an app targeting SDK 37+, raw LAN additionally needs the dangerous
android.permission.ACCESS_LOCAL_NETWORK grant on Android 17/API 37+.
Declare it in that final app (not in apps targeting SDK 36 or lower):
<uses-permission android:name="android.permission.ACCESS_LOCAL_NETWORK" />The current development source reports this live grant as
P2pPermission.LocalNetwork through kit.permissions; this is not a claim
about older published binaries. The app requests access using Android's
permission APIs and rechecks the live result before retrying its intended
operation. Denial/revocation must not trigger automatic start or prompt loops.
Preflight direct start()/connect() calls too; only the advertising/discovery
feature entry points perform the SDK permission gate. A preflight cannot prevent
a later OS revocation, so handle operation failures and keep cleanup available.
Android 16's explicit local-network compatibility opt-in is a separate test
mode, not ordinary API 36 behavior. See the
Android policy.
The optional network-provisioning sidecar has separate runtime permission and system-state requirements. Query its permission manager immediately before a provisioning operation; do not gate the base LAN transport on those permissions.
The final application Info.plist must contain a nonblank local-network reason and the secure-v2 Bonjour service:
<key>NSLocalNetworkUsageDescription</key>
<string>Find and connect to nearby devices on your local network.</string>
<key>NSBonjourServices</key>
<array>
<string>_p2pkit2._tcp</string>
</array>Keep these values in the source that generates the final plist. The sample uses
samples/iosApp/project.yml. Add the legacy
_p2pkit._tcp service only for an explicitly configured deprecated plaintext-v1
build; v1 and v2 do not interoperate or downgrade.
Core intentionally has no plaintext secure-identity default. Install a durable, confidential, integrity-protected operating-system-backed store:
val kit = P2pKit.create {
appId = AppId("com.example.transfer")
deviceName = "Desktop"
jvmSecureIdentityStore(protectedIdentityStore)
transports { lan() }
}putIfAbsent must be atomic across processes and durable before returning.
The samples' in-memory stores are development-only.
| Directory / project | Purpose |
|---|---|
library/p2p-core / :p2p-core
|
API, protocol, security, sessions, file transfer |
library/p2p-transport-lan / :p2p-transport-lan
|
JmDNS/Bonjour and TCP transport |
library/p2p-network-provisioning-android |
Optional Android network provisioning |
library/p2p-network-provisioning-desktop |
Optional JVM manual-endpoint provisioning |
samples/ |
Android, JVM CLI, Desktop UI, KMP, iOS, and shared diagnostics samples |
buildSrc/ |
Build provenance and canonical publication metadata logic |
scripts/ |
Release, security, publication, consumer, and repository gates |
See the architecture overview and current specification.
Discovery TXT records, names, peer IDs, and AppId are untrusted. The default
RejectUnknown policy requires an exact trusted identity. The explicit-risk
AcceptAnyAuthenticatedSameApp policy encrypts and authenticates key possession
but does not identify a person/device; it requires application-level admission.
There is no automatic fallback to plaintext.
Public collection models are snapshot values. Text/binary messages are capped at 4 MiB. File transfer is streaming and completes only after the authenticated receiver verifies the sender's prepared SHA-256 snapshot and completes the destination's platform durability contract. The JVM destination fsyncs file content and atomically publishes it; POSIX hosts also fsync the parent directory. The public JDK exposes no equivalent parent-directory barrier on Windows, so sudden power loss may lose the renamed directory entry there.
Use the operational limits reference for current admission, receive-backlog, framing, discovery and timeout policies, their observable failures, and the settings applications can configure.
Read the security model, compatibility policy, and 0.6-to-0.7 migration guide.
The 0.7.0-rc3 release record records the
historical automated gate, ABI, strict Dokka, signing/provenance, Swift
warnings-as-errors build and XCFramework checks for that exact release commit.
Its macOS module gate covered JVM/Android-host and runnable iOS simulator tests,
not Android ART, physical devices or every published Kotlin target. Those
results do not validate the later 0.7.0-SNAPSHOT source.
Prepublication artifact-shape and isolated-consumer checks use locally built Maven artifacts. The SBOM is generated from the configured resolved library dependency graphs; its content gate is not a scan of distributed binary bytes. Separately, RC3's publication workflow verified remote Maven Central bytes against its signed bundle and compiled fresh remote consumer fixtures after publication, as the release record documents. That genuine remote evidence is historical, not a current-snapshot or runtime-consumer result. None of these checks replaces external campaign evidence.
Current CI reports actual Kotlin test-task execution and skipped targets;
check success alone is not an all-runtime result. The published iosX64
slice cannot be natively tested on the default arm64 runner: a weekly/manual
Intel simulator job is configured, but its passing run must be recorded for
the tested commit. iosArm64 device execution needs external hardware.
One focused API37 permission/recreation/TCP instrumentation case is authored;
its execution must be recorded separately, and it is not a general device suite.
Android host JVM tests (including Robolectric shadows) are not ART or physical-device evidence.
The canonical six-area status table records external campaign progress and links each execution handbook. In particular, the CLI/Desktop row's partial automated coverage is not a completed fault-injection or real-display campaign. See automated scope and history for the distinct host/target evidence. Do not treat this release candidate as fully production validated or independently audited.
main)P2pKit is licensed under the Apache License 2.0.
P2pKit is a Kotlin Multiplatform library for authenticated peer discovery, messaging, and durable file transfer over a reachable local network. It supports Android, JVM/Desktop, and iOS through one transport-independent API.
Development version: 0.7.0-SNAPSHOT.
Latest published version: 0.7.0-rc3.
0.7.0-rc3 is available from Maven Central under
io.github.apdelrahman1911. It is a release candidate: the automated release
gates are extensive, but campaign status is governed by the
validation authority.
RC3's immutable tag and artifacts are historical release evidence. Each new
campaign must explicitly freeze its approved commit and artifact set under
that handbook; results from later main snapshots must not be reported as
RC3 evidence.
iosArm64,
iosSimulatorArm64, and iosX64.Noise_XX_25519_ChaChaPoly_SHA256 and persistent X25519 identities.P2pKit does not provide internet signaling, NAT traversal, relays, accounts, rooms, or application-level authorization. Both peers must already be mutually reachable on the LAN. Guest/enterprise Wi-Fi may block multicast or peer TCP.
Use mavenCentral() and keep all P2pKit modules on the same version.
Supported consumer languages are Kotlin (Android, JVM/Desktop, and KMP) and
Swift through the source-built XCFramework. Java is not supported as an
end-to-end SDK integration; see the Java interop limitations.
| Published module | Purpose |
|---|---|
io.github.apdelrahman1911:p2p-core:0.7.0-rc3 |
Public API, protocol, security, sessions, and file transfer |
io.github.apdelrahman1911:p2p-transport-lan:0.7.0-rc3 |
Multiplatform LAN discovery and TCP transport |
io.github.apdelrahman1911:p2p-network-provisioning-android:0.7.0-rc3 |
Optional Android provisioning sidecar |
io.github.apdelrahman1911:p2p-network-provisioning-desktop:0.7.0-rc3 |
Optional JVM/Desktop manual-endpoint sidecar |
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.apdelrahman1911:p2p-core:0.7.0-rc3")
implementation("io.github.apdelrahman1911:p2p-transport-lan:0.7.0-rc3")
}
}
}dependencies {
implementation("io.github.apdelrahman1911:p2p-transport-lan-android:0.7.0-rc3")
// Optional hotspot/Wi-Fi provisioning:
implementation("io.github.apdelrahman1911:p2p-network-provisioning-android-android:0.7.0-rc3")
}dependencies {
implementation("io.github.apdelrahman1911:p2p-transport-lan-jvm:0.7.0-rc3")
// Optional manual-endpoint provisioning:
implementation("io.github.apdelrahman1911:p2p-network-provisioning-desktop:0.7.0-rc3")
}Kotlin Multiplatform root coordinates select their platform variants. The
complete 15-coordinate publication set is recorded in the
0.7.0-rc3 release record.
The Maven publications serve Kotlin Multiplatform consumers. A direct Swift
application builds P2pKitShared.xcframework from this repository:
./gradlew :p2p-transport-lan:assembleP2pKitSharedReleaseXCFrameworkThe maintained Swift sample and provenance-checked integration live under
samples/iosApp.
Current development builds with Kotlin 2.4.10 while retaining the iOS 14 library floor explicitly. Kotlin Multiplatform applications that link their own Apple binary must apply the iOS 14 Kotlin/Native override documented in the compatibility policy; repository-built XCFrameworks apply it and verify every Mach-O slice automatically.
Authenticated v2 is the default and fails closed. Exchange the full pairing QR or fingerprint through a trusted channel, then pin the expected identity:
import dev.p2pkit.core.AppId
import dev.p2pkit.core.P2pKit
import dev.p2pkit.core.P2pMessage
import dev.p2pkit.transport.lan.lan
import kotlinx.coroutines.flow.first
val kit = P2pKit.create {
appId = AppId("com.example.transfer")
deviceName = "My device"
transports { lan() } // Android: lan(applicationContext)
}
kit.start()
kit.startAdvertising()
kit.startDiscovery()
val expectedFingerprint =
requireNotNull(kit.parsePeerPairingQr(qrFromTrustedChannel))
val peer = kit.peers.first { it.isNotEmpty() }.first()
val session = kit.connect(peer, expectedFingerprint)
session.send(P2pMessage.Text("hello"))For an executable discovered-peer example, the CLI's pairing and
connect-pinned <peer-alias> <full-pairing-QR> commands implement this exchange;
see the sample pairing walkthrough.
The interactive samples' incoming admission remains development-only, not an
allowlist. The KMP sample separately demonstrates PinnedOnly admission.
Subscribe to incomingSessions before advertising and attach each
session.incoming collector promptly; these are hot event streams. send()
confirms a local transport write, not remote application processing. Add
domain-level IDs, ordering, deduplication, acknowledgements, and repair where
your application requires them.
Initialize secure identity storage once from Application.onCreate() before
constructing a kit:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
P2pKitAndroid.initialize(this)
}
}Declare the base LAN permissions:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />For an app targeting SDK 37+, raw LAN additionally needs the dangerous
android.permission.ACCESS_LOCAL_NETWORK grant on Android 17/API 37+.
Declare it in that final app (not in apps targeting SDK 36 or lower):
<uses-permission android:name="android.permission.ACCESS_LOCAL_NETWORK" />The current development source reports this live grant as
P2pPermission.LocalNetwork through kit.permissions; this is not a claim
about older published binaries. The app requests access using Android's
permission APIs and rechecks the live result before retrying its intended
operation. Denial/revocation must not trigger automatic start or prompt loops.
Preflight direct start()/connect() calls too; only the advertising/discovery
feature entry points perform the SDK permission gate. A preflight cannot prevent
a later OS revocation, so handle operation failures and keep cleanup available.
Android 16's explicit local-network compatibility opt-in is a separate test
mode, not ordinary API 36 behavior. See the
Android policy.
The optional network-provisioning sidecar has separate runtime permission and system-state requirements. Query its permission manager immediately before a provisioning operation; do not gate the base LAN transport on those permissions.
The final application Info.plist must contain a nonblank local-network reason and the secure-v2 Bonjour service:
<key>NSLocalNetworkUsageDescription</key>
<string>Find and connect to nearby devices on your local network.</string>
<key>NSBonjourServices</key>
<array>
<string>_p2pkit2._tcp</string>
</array>Keep these values in the source that generates the final plist. The sample uses
samples/iosApp/project.yml. Add the legacy
_p2pkit._tcp service only for an explicitly configured deprecated plaintext-v1
build; v1 and v2 do not interoperate or downgrade.
Core intentionally has no plaintext secure-identity default. Install a durable, confidential, integrity-protected operating-system-backed store:
val kit = P2pKit.create {
appId = AppId("com.example.transfer")
deviceName = "Desktop"
jvmSecureIdentityStore(protectedIdentityStore)
transports { lan() }
}putIfAbsent must be atomic across processes and durable before returning.
The samples' in-memory stores are development-only.
| Directory / project | Purpose |
|---|---|
library/p2p-core / :p2p-core
|
API, protocol, security, sessions, file transfer |
library/p2p-transport-lan / :p2p-transport-lan
|
JmDNS/Bonjour and TCP transport |
library/p2p-network-provisioning-android |
Optional Android network provisioning |
library/p2p-network-provisioning-desktop |
Optional JVM manual-endpoint provisioning |
samples/ |
Android, JVM CLI, Desktop UI, KMP, iOS, and shared diagnostics samples |
buildSrc/ |
Build provenance and canonical publication metadata logic |
scripts/ |
Release, security, publication, consumer, and repository gates |
See the architecture overview and current specification.
Discovery TXT records, names, peer IDs, and AppId are untrusted. The default
RejectUnknown policy requires an exact trusted identity. The explicit-risk
AcceptAnyAuthenticatedSameApp policy encrypts and authenticates key possession
but does not identify a person/device; it requires application-level admission.
There is no automatic fallback to plaintext.
Public collection models are snapshot values. Text/binary messages are capped at 4 MiB. File transfer is streaming and completes only after the authenticated receiver verifies the sender's prepared SHA-256 snapshot and completes the destination's platform durability contract. The JVM destination fsyncs file content and atomically publishes it; POSIX hosts also fsync the parent directory. The public JDK exposes no equivalent parent-directory barrier on Windows, so sudden power loss may lose the renamed directory entry there.
Use the operational limits reference for current admission, receive-backlog, framing, discovery and timeout policies, their observable failures, and the settings applications can configure.
Read the security model, compatibility policy, and 0.6-to-0.7 migration guide.
The 0.7.0-rc3 release record records the
historical automated gate, ABI, strict Dokka, signing/provenance, Swift
warnings-as-errors build and XCFramework checks for that exact release commit.
Its macOS module gate covered JVM/Android-host and runnable iOS simulator tests,
not Android ART, physical devices or every published Kotlin target. Those
results do not validate the later 0.7.0-SNAPSHOT source.
Prepublication artifact-shape and isolated-consumer checks use locally built Maven artifacts. The SBOM is generated from the configured resolved library dependency graphs; its content gate is not a scan of distributed binary bytes. Separately, RC3's publication workflow verified remote Maven Central bytes against its signed bundle and compiled fresh remote consumer fixtures after publication, as the release record documents. That genuine remote evidence is historical, not a current-snapshot or runtime-consumer result. None of these checks replaces external campaign evidence.
Current CI reports actual Kotlin test-task execution and skipped targets;
check success alone is not an all-runtime result. The published iosX64
slice cannot be natively tested on the default arm64 runner: a weekly/manual
Intel simulator job is configured, but its passing run must be recorded for
the tested commit. iosArm64 device execution needs external hardware.
One focused API37 permission/recreation/TCP instrumentation case is authored;
its execution must be recorded separately, and it is not a general device suite.
Android host JVM tests (including Robolectric shadows) are not ART or physical-device evidence.
The canonical six-area status table records external campaign progress and links each execution handbook. In particular, the CLI/Desktop row's partial automated coverage is not a completed fault-injection or real-display campaign. See automated scope and history for the distinct host/target evidence. Do not treat this release candidate as fully production validated or independently audited.
main)P2pKit is licensed under the Apache License 2.0.