From fd1aa565a1ee9b0fbd5f49d51369d29cdb9cad06 Mon Sep 17 00:00:00 2001 From: Reid Chatham Date: Fri, 7 Aug 2026 12:22:48 -0700 Subject: [PATCH] docs: add Network backend guide --- NetworkBackendGuide.md | 194 +++++++++++++++++++++++++++++++++++++++++ README.md | 40 +-------- 2 files changed, 197 insertions(+), 37 deletions(-) create mode 100644 NetworkBackendGuide.md diff --git a/NetworkBackendGuide.md b/NetworkBackendGuide.md new file mode 100644 index 0000000..396db59 --- /dev/null +++ b/NetworkBackendGuide.md @@ -0,0 +1,194 @@ +# Network Backend Guide + +PeerConnectivity is migrating toward Apple's Network framework while preserving the existing MultipeerConnectivity-backed public API. The Network backend is available as an explicit opt-in migration path; the default backend remains `.multipeerConnectivity`. + +## Release posture + +The Network backend is not the default runtime path yet. Treat it as an experimental/beta backend for apps that can validate behavior in their own topology and OS/device matrix. + +Use it when you need to evaluate the Network framework migration path for reliable local peer messaging. Continue using the default MultipeerConnectivity backend when you need browser UI, stream transfer, resource transfer, or proven production parity. + +## Requirements + +- `.networkFramework` requires iOS 13.0+ or macOS 10.15+. +- iOS apps that use Bonjour/local-network discovery should include local network privacy entries in `Info.plist`: + - `NSLocalNetworkUsageDescription` + - `NSBonjourServices`, including the DNS-SD form of your service type, for example `_local._tcp`. +- Service types passed to `PeerConnectionManager` remain bare PeerConnectivity service names such as `"local"`; the Network backend maps them to Bonjour service names internally. + +## Opt in + +Create the manager with `backend: .networkFramework`: + +```swift +import Foundation +import PeerConnectivity + +let secret = Data("replace-with-an-app-managed-secret".utf8) +let manager = PeerConnectionManager(serviceType: "local", + connectionType: .automatic, + displayName: "Alice", + backend: .networkFramework, + networkSecurity: .preSharedKey(secret)) + +manager.start() +``` + +If `.networkFramework` is requested on an unsupported OS, initialization fails with a programmer-error `fatalError` instead of silently falling back to MultipeerConnectivity. + +## Security model + +Prefer `.preSharedKey` for every Network-backed app session: + +```swift +let security = PeerConnectionNetworkSecurity.preSharedKey(secret) +let manager = PeerConnectionManager(serviceType: "local", + backend: .networkFramework, + networkSecurity: security) +``` + +`PeerConnectionNetworkSecurity.preSharedKey(_:)` configures TLS with a pre-shared key. Peers must use the same non-empty key to complete the TLS handshake. + +Guidance for app-managed secrets: + +- Use high-entropy key material, not a human-readable demo string. +- Store and rotate the secret according to your app's threat model. +- Use the same secret only for peers that should be allowed into the same local mesh. +- Treat Bonjour TXT metadata (`pc-id`, `pc-name`, `pc-v`) as routing/discovery metadata only. It is not a trust assertion. + +`.unauthenticated` is plaintext TCP. It remains available only for migration compatibility and diagnostics and must not be used for sensitive data. + +Current limitation: TLS-PSK authenticates membership in the shared-key group; it does not yet bind a long-term public peer identity to a certificate or pinned key. If multiple devices share the same PSK, any member of that group can advertise a display name. Apps that need stronger identity guarantees should keep the Network backend opt-in until a stricter trust model is added. + +## Connection modes + +### `.automatic` + +Supported. Peers advertise and browse for the same service type, then attempt to connect automatically. + +### `.custom` + +Supported for app-owned peer selection. Observe `.foundPeer` and `.lostPeer`, then call `invitePeer` for the selected peer: + +```swift +var discoveredPeers : [Peer] = [] + +manager.listenOn({ event in + switch event { + case .foundPeer(let peer): + // Add `peer` to app UI. + discoveredPeers.append(peer) + case .lostPeer(let peer): + // Remove `peer` from app UI. + discoveredPeers.removeAll { $0 == peer } + default: + break + } +}, withKey: "network-browser") + +// Later, after user/app approval: +if let selectedPeer = discoveredPeers.first { + manager.invitePeer(selectedPeer) +} +``` + +For Network-backed managers, `invitePeer(_:withContext:timeout:)` uses the discovered peer endpoint. The `context` and `timeout` parameters are currently ignored. + +### `.inviteOnly` + +The built-in MultipeerConnectivity advertiser assistant/browser UI is not available for the Network backend. Network-backed apps should provide their own UI using `.foundPeer`, `.lostPeer`, and `invitePeer`. + +`PeerConnectivityUI.browserViewController` returns `nil` for Network-backed managers. + +## API support matrix + +| API / behavior | MultipeerConnectivity backend | Network backend | +|---|---:|---:| +| Default backend | ✅ | ❌ opt-in only | +| `.automatic` discovery/connect | ✅ | ✅ | +| `.custom` + app-owned `invitePeer` | ✅ | ✅ | +| `.inviteOnly` built-in browser UI | ✅ | ❌ app-owned UI required | +| `sendData` | ✅ | ✅ | +| `sendMessage` / `observeMessages` | ✅ | ✅ | +| Large framed messages | ✅ via MC | ✅ via TCP framing | +| Multi-peer broadcast | ✅ | ✅ bounded local E2E coverage | +| Disconnect/reconnect after peer restart | ✅ | ✅ bounded local E2E coverage | +| `sendDataStream` | ✅ | ❌ unsupported-operation error | +| `sendResourceAtURL` | ✅ | ❌ unsupported-operation error | +| Stream/resource receive events | ✅ | ❌ not implemented | +| `multipeerSession` | ✅ | ❌ programmer error | +| TLS-PSK transport security | MC-managed | ✅ with `.preSharedKey` | + +## Sending data and messages + +The Network backend supports reliable `Data` and typed `PeerMessage` exchange: + +```swift +struct ChatMessage : PeerMessage, Codable, Equatable { + let text : String +} + +manager.observeMessages(ofType: ChatMessage.self, forKey: "chat") { message, peer in + print("received \(message.text) from \(peer.displayName)") +} + +let message = ChatMessage(text: "hello") +manager.sendMessage(message, toPeers: manager.connectedPeers) +``` + +Resource transfer and stream APIs are intentionally unsupported for the Network backend in the current migration stack. Calls fail explicitly instead of silently degrading behavior. + +## Demo app + +The demo app can be launched with arguments to exercise the Network backend: + +- `PCNetworkBackend` — use `.networkFramework` instead of the default MultipeerConnectivity backend. +- `PCAutoStart` — start the manager on launch. +- `PCDisplayName ` — set a deterministic display name such as `Alice` or `Bob`. + +Example simulator launch arguments: + +```text +PCNetworkBackend PCAutoStart PCDisplayName Alice +PCNetworkBackend PCAutoStart PCDisplayName Bob +``` + +The demo path is intended for local validation while the backend remains opt-in. + +## Current validation coverage + +The Network backend currently has local loopback tests for: + +- discovery/connect/message exchange +- bidirectional typed messages +- large typed payloads +- wrong-service isolation +- mismatched PSK rejection +- multi-peer broadcast +- disconnect/reconnect after peer restart + +It also has mock-backed coordinator/unit tests for connection caps, duplicate connection handling, and handshake timeout behavior. + +Run the focused Network tests with: + +```sh +swift test --filter NetworkPeerLoopbackTests +``` + +Full local verification for the migration stack: + +```sh +swift test +xcodebuild test -project PeerConnectivity.xcodeproj \ + -scheme PeerConnectivity \ + -destination 'platform=iOS Simulator,name=iPhone 16,OS=18.3.1' \ + -configuration Debug +``` + +## Known follow-ups + +- Decide whether connection policy values such as handshake timeout, maximum pending connections, and maximum connected peers should become public configuration. +- Add a Network-native peer browser UI/model for apps that need built-in selection UI. +- Decide whether to implement Network equivalents for streams and resource transfer or document them as MultipeerConnectivity-only long term. +- Strengthen identity binding beyond shared-key group membership for apps that require per-peer authentication. +- Continue monitoring Bonjour/Network.framework E2E behavior in CI and split or gate slow tests if they become flaky. diff --git a/README.md b/README.md index e077688..4935bd8 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,9 @@ The staged migration toward Apple's Network framework is tracked in [NetworkFram ## Experimental Network framework backend -`PeerConnectionManager` can be explicitly initialized with `backend: .networkFramework` on supported OS versions. This backend is still a migration/testing path and lacks stream/resource/UI parity. +`PeerConnectionManager` can be explicitly initialized with `backend: .networkFramework` on supported OS versions. The default backend remains `.multipeerConnectivity`. -Use a shared secret to require TLS-PSK authenticated encryption between Network-backed peers: +Use `.preSharedKey` with high-entropy app-managed key material for authenticated encrypted Network sessions: ```swift let secret = Data("replace-with-an-app-managed-secret".utf8) @@ -57,41 +57,7 @@ let pcm = PeerConnectionManager(serviceType: "local", The default `networkSecurity: .unauthenticated` mode is plaintext TCP, remains available only for source compatibility and diagnostics, and must not be used for sensitive data. -The default backend remains `.multipeerConnectivity`. - -### Network backend support matrix - -| API / behavior | MultipeerConnectivity | Network framework | -|---|---:|---:| -| Default backend | ✅ | ❌ opt-in only | -| `.automatic` discovery/connect | ✅ | ✅ | -| `.custom` manual `invitePeer` connection | ✅ | ✅ | -| `.inviteOnly` advertiser assistant / browser UI | ✅ | ❌ use app UI with `.foundPeer` / `.lostPeer` | -| `sendData` | ✅ | ✅ | -| `sendMessage` / `observeMessages` | ✅ | ✅ | -| `sendDataStream` | ✅ | ❌ throws unsupported-operation error | -| `sendResourceAtURL` | ✅ | ❌ reports unsupported-operation error | -| `multipeerSession` | ✅ | ❌ programmer error | -| TLS-PSK transport security | N/A | ✅ with `.preSharedKey` | - -For Network-backed peer selection, configure `.preSharedKey` for authenticated encrypted sessions, then observe discovered peers and call `invitePeer` from app-owned UI: - -```swift -pcm.listenOn({ event in - switch event { - case .foundPeer(let peer): - pcm.invitePeer(peer) - case .lostPeer(let peer): - break - default: - break - } -}, withKey: "network-browser") -``` - - -`networkSecurity` is used only by `.networkFramework`; MultipeerConnectivity keeps its own `MCSession` security behavior. - +See [NetworkBackendGuide.md](NetworkBackendGuide.md) for the full migration guide, security model, support matrix, demo launch arguments, validation coverage, and known limitations. ## Creating/Stopping/Starting