Verified findings on peer-to-peer transports between an iPhone and a Mac for aposemati. WiFiAware.framework is unavailable on macOS in every form — which invalidates the architecture in diy-continuity-camera-clone-research — and the widely-repeated claim that “the EU forced Apple to kill AWDL” is false: AWDL is alive and carrying traffic on this machine right now.
The headline result
Wi-Fi Aware cannot be used for anything involving a Mac. Not natively, not via Mac Catalyst. Every public declaration in
WiFiAware.frameworkcarries@available(macOS, unavailable). Verified twice, independently, from the SDK headers in Xcode 26.3.The previous brief called Wi-Fi Aware “the breakthrough” and “the winning path.” That path is dead. Its thesis — the transport is the product — survives.
RESOLVED ON HARDWARE, 2026-08-18 — the candidate chosen here works
NWParameters.includePeerToPeer+ hard interface pinning delivered 339 Mbps beside the router and 156 Mbps at the spot where Camo drops, bothawdl0 — direct, and an iPhone 16 Pro subsequently shot a 48.8 MP still to a Mac over it. See §10 below, aposemati-phase0-field-measurements and aposemati-phase0-phase1-build.
1. Wi-Fi Aware: what it actually is
WiFiAware.framework is real and it is genuinely purpose-built for exactly this use case.
| Fact | Detail |
|---|---|
| Ships in | iOS 26 / iPadOS 26 |
| Introduced | WWDC25 session 228 |
| Standard | Wi-Fi Alliance Wi-Fi Aware / NAN (Neighbor Awareness Networking) |
| Relevant API | WAPerformanceMode with .realtime and .interactiveVideo cases |
| Off-router | Yes — true peer-to-peer data path, no access point |
WAPerformanceMode.interactiveVideo existing at all is the tell: Apple built this for streaming video between devices. It just isn’t for Macs.
2. Why it cannot be used with a Mac — five independent proofs
Every route to macOS is closed
Each of these was checked separately. They all agree.
2.1 Native macOS — explicitly unavailable
Every public declaration in the framework’s swiftinterface carries:
@available(macOS, unavailable)Not “unavailable in this beta.” Unavailable, full stop.
2.2 Mac Catalyst — no module, no interface
error: no such module 'WiFiAware'
There is no -macabi swiftinterface for WiFiAware.
This is not a packaging quirk
Network.frameworkdoes ship a-macabiswiftinterface. So the absence forWiFiAwareis a deliberate exclusion, not an oversight in how the SDK is assembled.
2.3 Absent from the iOSSupport tree
The macOS SDK’s System/iOSSupport tree — which holds 118 other Catalyst-available frameworks — does not contain WiFiAware.
2.4 Xcode’s own provisioning capability record
The WIFI_AWARE capability record states:
"supportedSDKs": [{ "name": "IOS" }]iOS only. Apple’s own tooling agrees.
2.5 The dylib exists but is fully gated
The WiFiAware dylib is present in the macOS 26.5 dyld shared cache with 739 exports. Apple laid the groundwork. But:
- every public API is gated off, and
- there is no NAN interface on macOS to attach to.
So the binary being there proves intent, not usability. Do not read hope into it.
3. What it would take — if a future iPhone↔iPad path is ever wanted
Wi-Fi Aware remains viable iPhone↔iPad. Recorded here so the research does not have to be redone.
Hardware support
| Device | Minimum |
|---|---|
| iPhone | iPhone 12 and later |
| iPad | 10th generation and later |
| iPad Air | 4th generation and later |
| iPad Pro 11” | 3rd generation and later |
| iPad Pro 12.9” | 5th generation and later |
Entitlement
com.apple.developer.wifi-aware
distributionApprovalRequired: false— no Apple approval neededisPublic: true- Requires a paid membership. Not available to free developer accounts.
Info.plist service declaration
Services are static — declared in Info.plist under WiFiAwareServices, DNS-SD style, ≤ 15 characters.
Two ways to crash your own app at launch
- An invalid service name crashes the app.
- A service dict with neither
PublishablenorSubscribablecrashes the app.These are not thrown errors you can catch. Get the plist right.
Pairing
- One-time, system-provided UI, PIN-based, via
DeviceDiscoveryUI. - Needs no Local Network permission prompt — a real UX advantage over Bonjour-based designs.
DeviceDiscoveryUIis also absent from the macOS SDK, which independently corroborates §2.
4. Apple publishes zero throughput numbers
Nobody has measured this — including Apple
A grep across the framework documentation, all 31 Wi-Fi Aware doc pages, the WWDC25 session 228 transcript, and Accessory Design Guidelines chapter 56 found no figure in Mbps or Gbps anywhere. No third-party benchmark exists either.
This is the deeper lesson from the invalidated brief: an unmeasured link is not a plan. See the Phase 0 transport spike in aposemati.
5. Mechanism details worth keeping (ADG ch. 56.6)
Useful even though the Mac path is closed — these characteristics apply to NAN generally.
- Uses 5 GHz when available.
- Bulk mode is duty-cycled on the “social channel.”
- Discovery beacons roughly every 8 seconds → discovery latency is measured in seconds, not milliseconds.
- Addressing is IPv6 link-local derived from the MAC, with NDP disabled.
6. “The EU killed AWDL” is FALSE
Verified live on the target Mac, macOS 26.5.1
AWDL was not removed and not replaced. Wi-Fi Aware ships alongside it.
| Interface | State | Traffic observed |
|---|---|---|
awdl0 | UP, RUNNING, active | 196,141 pkts / 26.8 MB in — 86,930 pkts / 27.3 MB out |
llw0 (low-latency WLAN, same MAC = same radio) | active | 8,300,000 pkts / 11.5 GB inbound |
Supporting daemons all running: sharingd, rapportd, wifip2pd.
But there is still no public API to AWDL
NWInterface.InterfaceSubtype.wifiAWDLexists as a symbol but is SPI and will not compile.
What is public and undeprecated
For Agents — the surviving legal surface
- Multipeer Connectivity is NOT deprecated. Apple’s docs show
deprecated: nullon all six platforms. Apple documents it as using “peer-to-peer Wi-Fi” on both macOS and iOS.NWParameters.includePeerToPeeris public, undeprecated, macOS 10.14+.UNVERIFIED: whether✅ CONFIRMED 2026-08-18 — by Apple DTS (June 2026 forum post), by the EC’s own DMA decision, and by execution: a browse returnedincludePeerToPeerspecifically resolves to AWDL.awdl0among its interfaces,requiredInterfacepinning succeeded, and the accepted connection came from a genuine%awdl0link-local address. There is no preference knob — pinning is the mechanism. See §10.
7. Surviving transport candidates
| Backend | Off-router | Mac support | Status |
|---|---|---|---|
Network.framework includePeerToPeer | Confirmed AWDL | Native, macOS 10.14+ | ✅ CHOSEN AND MEASURED — 339 / 156 Mbps, see aposemati-phase0-field-measurements |
| Multipeer Connectivity | Apple documents “peer-to-peer Wi-Fi” | Native, not deprecated | Fallback; transport not selectable |
| iPhone Personal Hotspot | Yes — the phone is the AP | Native | Crude but certain |
Wired USB (usbmuxd) | No radio involved | Undocumented, entitlement required | Last resort |
| Wi-Fi Aware | Yes | None | ❌ Rejected — see §2 |
Transport selection, as a decision flow
flowchart TD A["Need off-router iPhone → Mac link"] --> B{"Wi-Fi Aware?"} B -->|"@available(macOS, unavailable)"| C["REJECTED"] B -->|no| D["Network.framework<br/>includePeerToPeer"] D --> E{"Phase 0 spike:<br/>throughput + latency<br/>at real range?"} E -->|pass| F["Primary transport"] E -->|fail| G["Multipeer Connectivity"] G --> H{"pass?"} H -->|fail| I["Personal Hotspot<br/><i>crude but certain</i>"] I --> J{"pass?"} H -->|pass| F J -->|pass| F J -->|fail| K["Wired USB / usbmuxd<br/><i>last resort</i>"] style C fill:#3d2020,stroke:#a33,color:#fff style F fill:#264653,stroke:#2a9d8f,color:#fff style K fill:#2d2d2d,stroke:#888,color:#fff
8. The USB bandwidth trap
Newer phone ≠ faster cable
- iPhone 16 — USB 2, 480 Mb/s
- iPhone 17 non-Pro — USB 2, 480 Mb/s
- iPhone 17 Pro — USB 3, 10 Gb/s
Only the Pro gets USB 3. Do not assume a current-generation phone gives you a fast wire.
9. The homelab receiver is out
telep-mainframecannot be a wireless peerIts AX210 has no antenna attached to its u.FL connector. It cannot participate in any wireless peer link — this is independent of, and settles ahead of, the open question about Linux NAN driver support.
See telep-mainframe. The homelab-receiver branch of the original brief is closed.
10. What the Phase 0/1 build settled (2026-08-18)
Everything above is SDK-header research. This section is what execution added, and where it corrected the research.
NWBrowser.Result.interfacesis the only public source of an AWDLNWInterfaceThe research left “how do you actually get an
awdl0interface object” open. The answer:NWPathMonitornever reportsawdl0under any configuration. The browse result is the only door. Pin it withNWParameters.requiredInterface— which takes interface objects, not names.
Never filter by interface type
awdl0reportstype == .wifi.prohibitedInterfaceTypes([.wifi])— the intuitive way to say “not the router” — kills the peer-to-peer path. Discriminate on interface identity, never type.
Also settled:
- QUIC does not support TLS-PSK on Network.framework (works over TCP;
-9858in six QUIC variants). This overturned the spec’s pairing decision mid-build — aposemati-pairing-security-model. .readyproves nothing on a QUIC or UDP listener; handshake failure is deferred.- Multipeer Connectivity phrasing corrected: not deprecated in 26.x, deprecated as of the 27.0 SDK (TN3213). Raw AWDL measures 758 Mbit/s peer-reviewed vs Multipeer’s 0.1–2 MB/s on the same radio — the framework was the bottleneck, not the link.
includePeerToPeerdoes use AWDL (Apple DTS, June 2026; and the EC’s own DMA decision). There is no preference knob — pinning from a browse result is the mechanism that works.
Full list: aposemati-network-framework-gotchas.
Related
- aposemati — project overview, decisions, and phase plan
- diy-continuity-camera-clone-research — the superseded brief this note invalidates
- aposemati-apple-api-measurements — encoding, Core Image, Continuity Camera and CMIO measurements
- camo-studio-teardown — what the incumbent actually ships
- telep-mainframe — homelab box, now out of scope
- aposemati-phase0-field-measurements — the throughput this transport actually delivers
- aposemati-network-framework-gotchas — the API traps found while implementing it
- aposemati-phase0-phase1-build — the working build
Measured 2026-08-26 — cancelling a send tears down the link
Never cancel a
Taskthat is insideNetworkTransport.send
sendwraps its continuation inwithTaskCancellationHandler, and that handler’sonCancelcallstearDown— which is not a per-frame abort. It setsisClosed, shuts down the preview flow, cancels the connection and finishes the arrivals stream. Cancelling one send drops the whole QUIC link.Worse,
withTaskCancellationHandlerfiresonCancelimmediately if the task was already cancelled when the handler is entered — so a send cancelled while still queued tears the transport down the moment it arrives. AndNetworkTransportis afinal class, not an actor, so nothing serialises sends into a safe window.
This was very nearly shipped. An implementation plan specified cancel() as the mechanism for
coalescing rapid crop-region updates during a zoom-slider drag — a plausible-sounding idea that
would have dropped the link on every drag, presenting as “the preview dies when I drag zoom”
and sending anyone looking straight to the preview code.
Coalesce by discarding superseded work before it reaches send, never by cancelling work
already inside it: hold one waiting value and drain it with a single task that is never cancelled.
Regression guard worth copying: the fake sender in the test mimics send’s shape — body wrapped in
withTaskCancellationHandler, onCancel bumping a tally — and the test asserts the tally stays
zero. Reintroducing a cancel() makes it two.
Context: aposemati-crop-and-framing.