Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 194 additions & 0 deletions NetworkBackendGuide.md
Original file line number Diff line number Diff line change
@@ -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 <name>` — 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.
40 changes: 3 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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

Expand Down
Loading