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.framework carries @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, both awdl0 — 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.

FactDetail
Ships iniOS 26 / iPadOS 26
IntroducedWWDC25 session 228
StandardWi-Fi Alliance Wi-Fi Aware / NAN (Neighbor Awareness Networking)
Relevant APIWAPerformanceMode with .realtime and .interactiveVideo cases
Off-routerYes — 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.framework does ship a -macabi swiftinterface. So the absence for WiFiAware is 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

DeviceMinimum
iPhoneiPhone 12 and later
iPad10th generation and later
iPad Air4th 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: falseno Apple approval needed
  • isPublic: 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 Publishable nor Subscribable crashes 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.
  • DeviceDiscoveryUI is 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.

InterfaceStateTraffic observed
awdl0UP, RUNNING, active196,141 pkts / 26.8 MB in — 86,930 pkts / 27.3 MB out
llw0 (low-latency WLAN, same MAC = same radio)active8,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.wifiAWDL exists 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: null on all six platforms. Apple documents it as using “peer-to-peer Wi-Fi” on both macOS and iOS.
  • NWParameters.includePeerToPeer is public, undeprecated, macOS 10.14+.
  • UNVERIFIED: whether includePeerToPeer specifically resolves to AWDL.CONFIRMED 2026-08-18 — by Apple DTS (June 2026 forum post), by the EC’s own DMA decision, and by execution: a browse returned awdl0 among its interfaces, requiredInterface pinning succeeded, and the accepted connection came from a genuine %awdl0 link-local address. There is no preference knob — pinning is the mechanism. See §10.

7. Surviving transport candidates

BackendOff-routerMac supportStatus
Network.framework includePeerToPeerConfirmed AWDLNative, macOS 10.14+CHOSEN AND MEASURED — 339 / 156 Mbps, see aposemati-phase0-field-measurements
Multipeer ConnectivityApple documents “peer-to-peer Wi-Fi”Native, not deprecatedFallback; transport not selectable
iPhone Personal HotspotYes — the phone is the APNativeCrude but certain
Wired USB (usbmuxd)No radio involvedUndocumented, entitlement requiredLast resort
Wi-Fi AwareYesNoneRejected — see §2

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-mainframe cannot be a wireless peer

Its 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.interfaces is the only public source of an AWDL NWInterface

The research left “how do you actually get an awdl0 interface object” open. The answer: NWPathMonitor never reports awdl0 under any configuration. The browse result is the only door. Pin it with NWParameters.requiredInterface — which takes interface objects, not names.

Never filter by interface type

awdl0 reports type == .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; -9858 in six QUIC variants). This overturned the spec’s pairing decision mid-build — aposemati-pairing-security-model.
  • .ready proves 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.
  • includePeerToPeer does 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.


Never cancel a Task that is inside NetworkTransport.send

send wraps its continuation in withTaskCancellationHandler, and that handler’s onCancel calls tearDown — which is not a per-frame abort. It sets isClosed, shuts down the preview flow, cancels the connection and finishes the arrivals stream. Cancelling one send drops the whole QUIC link.

Worse, withTaskCancellationHandler fires onCancel immediately 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. And NetworkTransport is a final 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.