espressif/wifi_aware

0.1.0

Latest
uploaded 18 hours ago
Wi-Fi Aware (NAN) based Network Service Discovery component

Readme

# wifi_aware

## Introduction

Wi-Fi Aware (also referred to as NAN - Neighbor Awareness Networking) is a protocol that allows Wi-Fi devices to discover services in their proximity. NAN uses direct device-to-device communication and does not require any Internet or AP connection.
Multiple NAN devices which are in the vicinity will form a cluster which allows them to communicate with each other. Devices within the cluster can advertise or look for services and establish a peer-to-peer datapath with or without security.

This component is a DNS-SD-shaped API on top of ESP-IDF's NAN driver: advertise or discover a service, optionally pair, then get an IPv6 link-local datapath. There is no access point, no DHCP, and no internet connection involved.

Peer-platform compatibility (Default, iOS, Android) is selected component-wide in menuconfig → Wi-Fi Aware Configuration (`CONFIG_WIFI_AWARE_COMPATIBILITY_DEFAULT`, `_IOS`, or `_ANDROID`). NAN pairing support follows `CONFIG_ESP_WIFI_NAN_PAIRING`; whether a session uses pairing is the application's per-session security config (the examples expose that as `CONFIG_EXAMPLE_WA_PAIRING`).

## How to use

Wi-Fi is owned by the application: call `esp_wifi_init()` before
`wifi_aware_init()`.

### Publisher (Advertiser)

```c
#include "wifi_aware.h"

const wa_init_config_t init_cfg = {
    .use_nvs_for_caching = true,
};
wifi_aware_init(&init_cfg);

const wa_dp_security_cfg_t security = {
    .mode = WA_SEC_PIN_PAIRING,
    .pairing = { .caching_enabled = true },
};
const wa_publish_cfg_t pub_cfg = {
    .instance_name = "My Service",     /* advertised in the SSI only in Android compatibility mode
                                          (peer apps list services by name there); Default and
                                          iOS modes keep it local per the Apple guideline */
    .service_type  = "myservice",
    .proto         = WA_PROTO_UDP,
    .port          = 3333,             /* port the service listens on; the subscriber receives
                                          it as wa_endpoint_t.port when the datapath comes up */
    .security      = &security,
};
wa_session_handle_t session;
wifi_aware_advertise(&pub_cfg, &session);

/* WA_EVENT_BOOTSTRAP_INDICATION -> wifi_aware_pairing_bootstrap_response(peer, true)
 *                                + wifi_aware_pairing_set_credentials(peer, &cred);
 * WA_EVENT_DATAPATH_CONNECTED   -> client is connected. */
```

### Subscriber (Discoverer)

```c
#include "wifi_aware.h"

static void on_match(wa_session_handle_t session, wa_peer_handle_t peer,
                     const wa_peer_info_t *info, void *user_ctx)
{
    /* copy the handle; defer blocking work to your own task */
}

const wa_dp_security_cfg_t security = {   /* must match the publisher's mode */
    .mode = WA_SEC_PIN_PAIRING,
    .pairing = { .caching_enabled = true },
};
const wa_subscribe_cfg_t sub_cfg = {
    .service_type = "myservice",
    .proto        = WA_PROTO_UDP,
    .ttl_sec      = 10,
    .stop_on_first_match = true,
    .security     = &security,
    .callback     = on_match,
};
wa_session_handle_t session;
wifi_aware_discover(&sub_cfg, &session);

/* First encounter: bootstrap + pairing_start; already_paired: pairing_verify.
 * WA_EVENT_PAIRING_CONFIRMED arrives on the event loop — do NOT connect from
 * the handler; hand the peer handle to a worker task: */
static void worker_task(void *arg)
{
    wa_peer_handle_t peer = (wa_peer_handle_t)(uintptr_t)arg; /* from the event */
    wa_endpoint_t ep;
    if (wifi_aware_connect(peer, 0, &ep) == ESP_OK) {
        /* sendto(ep.addr, ep.port) with sin6_scope_id = ep.netif_index */
    }
}
```

`wifi_aware_connect_async()` is the non-blocking alternative: completion is
signalled by `WA_EVENT_DATAPATH_CONNECTED`, rejection by
`WA_EVENT_DATAPATH_DISCONNECTED`. Peer attributes (names, `already_paired`,
DCEA capability bits) are available any time via
`wifi_aware_peer_get_info(peer, &info)`.

## Examples

- [udp_server](examples/udp_server/) — advertises a UDP echo
  service, responds to pairing, serves clients
- [udp_client](examples/udp_client/) — discovers, pairs
  (or verifies), connects and exchanges UDP datagrams

## Supported Targets

ESP32, ESP32-S2, ESP32-S31, ESP32-C5, ESP32-C61 (requires SoC with Wi-Fi NAN support).

## Supported ESP-IDF versions

The component needs the driver's NAN extended-SSI support, which was
backported to the maintained v5.x release branches. The build detects it and
fails with a clear error on releases that predate it.

| ESP-IDF release | Supported from | What works |
| --- | --- | --- |
| v6.1 and later | every release | Everything: discovery, secured NDP (passphrase), NAN pairing, iOS and Android compatibility |
| v6.0 | every release | Discovery + open datapaths |
| v5.5 | every release | Discovery + open datapaths |
| v5.4 | v5.4.2 | Discovery + open datapaths |
| v5.3 | v5.3.4 | Discovery + open datapaths |
| v5.2 and older | not supported | — |

"Discovery + open datapaths" is the full flow minus security: DNS-SD
service matching, the record exchange (including the peer's port) and the
resolved IPv6 endpoint all work; the datapath is simply unencrypted.
Secured NDP (`CONFIG_ESP_WIFI_NAN_SECURITY`), NAN pairing
(`CONFIG_ESP_WIFI_NAN_PAIRING`), NDPE and protected NAN group frames all
arrived in v6.1.

Peer platforms before v6.1: iOS is not reachable at all (iPhones connect
exclusively via NAN pairing), and the iOS compatibility choice is not
offered. The Android compatibility mode can be selected: the
component-level behaviors (unsolicited publish, identity in the SSI,
pairing defaulted off) apply on any supported release, while the
driver-level compatibility tweaks exist only from v6.1 — so Android peers
over an open datapath are possible but best-effort there.

## Requirements

- A supported ESP-IDF release (table above)
- Wi-Fi NAN enabled: `CONFIG_ESP_WIFI_NAN_SYNC_ENABLE=y` on v6.x
  (`CONFIG_ESP_WIFI_NAN_ENABLE=y` on v5.x)
- IPv6 enabled (`CONFIG_LWIP_IPV6=y`). The component always selects static
  ND6 neighbor entries (`CONFIG_WIFI_AWARE_STATIC_ND6`) so iOS peers, which
  do not run Neighbor Discovery, can reach the datapath.

## Identity model

Everything is keyed by two opaque handles:

- `wa_peer_handle_t` — one discovered/pairing/connected peer. A handle is
  valid from the moment the peer is reported (discovery callback, or
  `WA_EVENT_BOOTSTRAP_INDICATION` for an inbound request) until
  `WA_EVENT_PEER_LOST` for that handle, which announces every invalidation
  (session stop, eviction under peer-pool pressure — the pool is
  `CONFIG_WIFI_AWARE_MAX_PEER_RECORDS`, default 8) except
  `wifi_aware_deinit()`, which invalidates everything silently. The same
  handle is used across the whole lifecycle: discovery → pairing →
  connection → teardown. Handles are never persisted; after the peer rotates
  its randomized MAC or either side restarts, rediscovery yields a new handle.
- `wa_session_handle_t` — one advertise (publish) or discover (subscribe)
  instance, returned by `wifi_aware_advertise()` / `wifi_aware_discover()`
  and used to stop it. `WA_EVENT_SESSION_STOPPED` fires on explicit stop.
  `ttl_sec` is meant to bound the on-air publish/subscribe activity but is
  not yet implemented (the driver ignores the TTL in synchronized
  discovery); today every session runs until explicitly stopped.

One service name can be registered only once per device: advertising *and*
discovering the same `service_type`/`proto` simultaneously is rejected by the
NAN stack.

## Service names and matching

Three different "names" exist, at three layers. Knowing which layer does
what avoids most confusion about discovery:

1. **`service_type` + `proto` (API input)** — what the application configures:
   a bare label like `"ESP-Demo"` plus `WA_PROTO_UDP`/`WA_PROTO_TCP`.
2. **DNS-SD service name (composed string)** — the component joins them as
   `_<service_type>._<proto>`, e.g. `"_ESP-Demo._udp"` (RFC 6763 form). This
   string is what gets handed to the Wi-Fi driver.
3. **NAN Service ID (what the radio matches on)** — the driver computes the
   first 6 bytes of SHA-256 over the *lowercased* service name (Wi-Fi Aware
   spec v4.0 §5.1.5) and puts that ID in the Service Descriptor Attribute of
   every publish/subscribe frame. Discovery matching is a byte-equality check
   on this 6-byte ID — nothing else. Case-insensitivity comes from the
   lowercasing (`"_ESP-Demo._udp"` and `"_esp-demo._udp"` hash identically);
   the protocol participates in matching only because `._udp`/`._tcp` is part
   of the hashed string.

Everything textual that appears in the service-specific info (SSI) — the
GSP Service Name sub-attribute, the instance name, TXT records — is data
*about* an already-matched service, for applications and humans. None of it
is consulted for matching.

DNS-SD subtypes (RFC 6763 §7.1) are not supported: the RFC requires a
subtyped instance to remain visible to plain base-type browsers as well,
and hash-equality matching cannot express that (one publish produces one
Service ID). Rather than offer the field without its RFC semantics, the
API has no subtype parameter.

Peer records are kept per (session, device): if one peer device matches
several concurrently running discover sessions, each session gets its own
handle addressing that service instance on the peer — connecting through one
handle never targets the other session's service. NAN pairing remains
device-level (one cached credential serves all of a device's handles).

Durable identity exists only for peers that support NPK/NIK caching. If the
same device is reported again later, it gets a new handle — but after pairing
once, such a peer is re-identified via the NAN Identity Resolution Attribute
(NIRA) and delivered with `already_paired`
set — reconnect with `wifi_aware_pairing_verify()` + `wifi_aware_connect()`,
no names involved. Names (`instance_name`, `pairingName`) are read-only
attributes in `wa_peer_info_t`, never lookup keys.

## Lifecycle

```
subscriber (Discoverer, initiator)          publisher (Advertiser, responder)
----------------------                      ---------------------
wifi_aware_init()                           wifi_aware_init()
wifi_aware_discover(cfg, &session)          wifi_aware_advertise(cfg, &session)
  -> discovery callback(peer, info)
     first time:                              WA_EVENT_BOOTSTRAP_INDICATION(peer)
       pairing_bootstrap_request(peer)          pairing_bootstrap_response(peer, true)
       WA_EVENT_BOOTSTRAP_COMPLETED(peer)       pairing_set_credentials(peer, &cred)
       pairing_start(peer, &cred)
     already_paired:
       pairing_verify(peer)
     WA_EVENT_PAIRING_CONFIRMED(peer)         WA_EVENT_PAIRING_CONFIRMED(peer)
wifi_aware_connect(peer, 0, &endpoint)        WA_EVENT_DATAPATH_CONNECTED(peer, endpoint)
  ... UDP/TCP via endpoint ...                ... serve on advertised port ...
wifi_aware_disconnect(peer)                   WA_EVENT_DATAPATH_DISCONNECTED(peer)
wifi_aware_discover_stop(session)           wifi_aware_advertise_stop(session)
wifi_aware_deinit()                         wifi_aware_deinit()
```

All events arrive on the default event loop under the `WA_EVENT` base with
fixed-size payloads that lead with the relevant handle.

**Do not call blocking APIs from event context.** WA_EVENT handlers and the
discovery callback run on event-loop tasks that this component itself depends
on; calling `wifi_aware_connect()` there blocks the NDP confirm from being
dispatched and the connect always times out. Copy the handle, signal your own
task (semaphore/queue), and connect from there — both examples show the
pattern.

## Pairing window

Whether an advertise session accepts new pairing follows its publish config:

- **Pairable** (advertised with `WA_SEC_PIN_PAIRING` or
  `WA_SEC_BOTH_PWD_PIN_PAIRING` — plug-and-pair for headless devices): the
  device info from `wa_publish_cfg_t.pairing_info` (`vendor_name`,
  `model_name`, `pairing_name`) is carried in the service-specific info
  (SSI), the Pairing Setup bit of the Device Capability Extension Attribute
  (DCEA) is set, and new peers may send bootstrap requests.
- **Non-pairable**: no pairing TXT info on the air (in Android compatibility mode the
  instance name is still advertised so peer apps can list services),
  Pairing Setup = 0,
  and the component rejects incoming bootstrap requests itself and raises no
  `WA_EVENT_BOOTSTRAP_INDICATION`, so an application that did not opt into
  pairing never sees one. Previously paired peers still
  reconnect via NIRA recognition + `wifi_aware_pairing_verify()`, since
  NPK/NIK caching stays advertised.

Toggling the window at runtime on a live session (Apple's temporary pairing
mode) is planned future work; today the window state is fixed for the
session's lifetime.

Links

Supports all targets

To add this component to your project, run:

idf.py add-dependency "espressif/wifi_aware^0.1.0"

download archive

Stats

  • Archive size
    Archive size ~ 95.35 KB
  • Downloaded in total
    Downloaded in total 6 times
  • Weekly Downloads Weekly Downloads (All Versions)
  • Downloaded this version
    This version: 0 times

Badge

espressif/wifi_aware version: 0.1.0
|