Torna al blog
Transport19 min read

Design del Trasporto Wi-Fi Aware — Neighbor Discovery e Data Path

Questo articolo spiega come PlainApp usi Wi-Fi Aware (NAN — Neighbor Awareness Networking) come livello intermedio della sua catena di fallback del peer transport, tra LAN (HTTPS same-subnet) e BLE (RPC GATT di ultima istanza). Wi-Fi Aware è ciò che permette a due dispositivi PlainApp di parlarsi quando sono su SSID diversi, guest vs IoT VLAN, o senza alcuna infrastruttura Wi-Fi — senza mai aver bisogno di un indirizzo IP da un server DHCP.

L'articolo tratta il ciclo di vita della sessione Aware (solo Android), il modello di discovery publish / subscribe, l'handshake a due fasi con role-split che sincronizza requestNetwork su entrambi i lati entro la finestra di ~500 ms del framework, il pool di link per-peer con idle sweeping, il trucco IPv6 + DNS custom che consente a un singolo client OkHttp di servire sia LAN sia Aware, e il prewarmer che innesca l'avvio di Aware sul peer via BLE.

Per la catena di fallback più ampia, si veda Chat Architecture. Per il trasporto BLE che subentra quando Aware non è disponibile, si veda BLE Transport. Per come viene stabilita la chiave ChaCha20 condivisa riusata come PMK di Aware, si veda Pairing Flow.

Sommario

Perché Wi-Fi Aware? {#why-wi-fi-aware}

Wi-Fi Aware (IEEE 802.11bc, formalmente NAN — Neighbor Awareness Networking) è una certificazione Wi-Fi Alliance che permette a due dispositivi di scoprirsi a vicenda e scambiare dati senza alcuna infrastruttura Wi-Fi — nessun AP, nessun router, nessun DHCP. PlainApp lo usa per due scenari che LAN non può coprire:

  • SSID / VLAN diversi. Un telefono sulla rete guest e un laptop sulla VLAN IoT sono entrambi «online» via Wi-Fi ma non possono raggiungere l'IP dell'altro. Aware crea un data path diretto device-to-device che bypassa completamente l'infrastruttura.
  • Nessuna infrastruttura. Due dispositivi in mezzo al nulla con il Wi-Fi acceso ma senza AP possono comunque chattare. (BLE copre questo, ma Aware è molto più veloce — ~10 ms di round trip contro secondi, e MB/s contro decine di KB/s.)

Diagram 1
1

Vincoli di piattaforma

Wi-Fi Aware è solo Android in PlainApp:

  • Android 13 (API 33) è il minimo — gli overload di WifiAwareNetworkSpecifier.Builder con setPort() e setPmk() da cui PlainApp dipende richiedono isTPlus().
  • iOS non espone Wi-Fi Aware alle app di terze parti. iOS PlainApp cade direttamente da LAN a BLE; l'oggetto WifiAwareTransport non è nemmeno compilato nel target iOS (@RequiresApi(Build.VERSION_CODES.S) + source set androidMain).

Ecco perché PeerTransportRouter.buildList chiama createWifiAwareTransport() — una factory che ritorna null su iOS.

Dove si posiziona Aware nella catena di fallback {#where-aware-sits-in-the-fallback-chain}

Il PeerTransportRouter di PlainApp è una lista ordinata. Per ogni chiamata send o downloadFile, percorre la lista e prova ogni trasporto finché uno non ha successo; i fallimenti si propagano in basso.

Diagram 2
2

Perché Aware è «il mezzo» e non «il primo»?

Perché LAN è quasi sempre più veloce quando disponibile. Un salto Wi-Fi stessa subnet attraverso un AP è un singolo scambio di frame 802.11; un data path Aware aggiunge un setup NDP (~5 s al primo uso) più un secondo contesto radio Wi-Fi per il link device-to-device. Se entrambi sono raggiungibili, LAN vince su latenza e throughput.

Viceversa, BLE è sempre più lento — ma funziona ogni volta che entrambi i dispositivi sono paired. Aware sta in mezzo: più veloce di BLE, più lento di LAN, e disponibile solo su dispositivi Android 13+ con il Wi-Fi acceso.

Ciclo di vita della sessione: Attach → Publish + Subscribe {#session-lifecycle-attach--publish--subscribe}

Una sessione Wi-Fi Aware è process-wide. C'è esattamente una WifiAwareSession per dispositivo; al suo interno, PlainApp fa girare una sessione di publish (così i peer possono scoprirci) e una sessione di subscribe (così possiamo scoprire i peer). Entrambe vengono avviate non appena AwareSession.start() completa la callback di attach.

Diagram 3
3

Perché publish AND subscribe sullo stesso dispositivo?

Il modello di discovery di Wi-Fi Aware è asimmetrico: un publisher publicizza un servizio, un subscriber lo scansiona. Per rendere simmetrico il discovery (entrambi i dispositivi si scoprono a vicenda), PlainApp fa entrambe le cose in una volta. Senza questo, il dispositivo A dovrebbe sapere in anticipo se è il publisher o il subscriber per un dato peer — ma i ruoli dei peer sono determinati più tardi dal confronto di clientId (si veda Discovery & Role Assignment).

Pubblicare e fare subscribe simultaneamente significa che ciascun dispositivo vede l'onServiceDiscovered dell'altro (come subscriber) E riceve i messaggi hello dell'altro (come publisher) — entrambe le direzioni dell'handshake sono sempre disponibili.

Auto-restart su terminazione

Alcune varianti Android (MIUI in particolare) uccidono le sessioni Aware di lunga durata per risparmiare batteria. PlainApp lo gestisce nelle callback di onSessionTerminated: mette a null la sessione terminata e richiama immediatamente publishOwnService / subscribeOwnService sulla WifiAwareSession ancora attached. La sessione di attach stessa non viene persa — solo la sessione di discovery publish/subscribe. I peer handle da prima della terminazione diventano stale, ecco perché awaitPeerHandle controlla il timestamp discoveredAt e scarta gli handle più vecchi di 30 s.

Discovery e assegnazione dei ruoli {#discovery--role-assignment}

Il protocollo data-path di Wi-Fi Aware richiede che un lato agisca come publisher (server) e l'altro come subscriber (client). Entrambe le parti non possono essere simultaneamente l'initiator — il framework rifiuta le richieste senza una controparte corrispondente.

PlainApp assegna i ruoli deterministicamente per peer usando un semplice confronto lessicografico dei clientIds:

Diagram 4
4

Perché deterministico e non negoziato?

Un approccio negoziato (ad es. «il MAC più basso è il server») richiederebbe uno scambio di messaggi extra. Il confronto lessicografico è idempotente, simmetrico e stateless: entrambi i dispositivi calcolano lo stesso ruolo per la stessa coppia senza alcuna comunicazione. Il clientId è uno short UUID di 13 caratteri, quindi i pareggi (clientId == peer.id) avvengono solo confrontando un peer con sé stesso — il che non raggiunge mai il trasporto.

Il ruolo determina due cose a valle:

  1. Chi guida il retry loop. Solo il client ritenta requestNetwork; il server fa esattamente un tentativo per ogni hello ricevuto. Questo è critico per la finestra di 500 ms (sezione successiva).
  2. Chi imposta la porta. Il publisher chiama setPort(httpsPort) perché è lui che accetta connessioni inbound sulla porta del proprio server HTTPS. Il subscriber non imposta una porta — apprende la porta del peer dalla WifiAwareNetworkInfo dopo che il data path è stabilito.

L'handshake a due fasi (hello + ready) {#the-two-phase-handshake-hello--ready}

La parte più difficile del setup del data path Wi-Fi Aware è il timing. Il framework richiede che entrambe le parti chiamino connectivityManager.requestNetwork entro circa 500 ms l'una dall'altra — se un lato la chiama prima che l'altro abbia registrato la propria richiesta corrispondente, il framework la rifiuta immediatamente con onUnavailable ("releaseRequestAsUnfulfillableByAnyFactory").

PlainApp risolve questo con un handshake a due messaggi a livello applicativo che gira sopra il canale di messaggi L2 di Aware (la stessa API sendMessage usata da onServiceDiscovered):

Diagram 5
5

Perché due messaggi (hello + ready) invece di uno solo?

L'hello da solo non basta a causa dell'asimmetria di direzione. Il subscriber può inviare l'hello non appena scopre il publisher (in onServiceDiscovered), ma il publisher non può iniziare requestNetwork finché non ha il PeerHandle del subscriber, che apprende solo ricevendo l'hello. Quindi l'hello serve a due scopi:

  1. Consegnare il PeerHandle del subscriber al publisher. Il publisher ne ha bisogno per costruire il WifiAwareNetworkSpecifier.
  2. Segnalare l'intento di connettersi. Ricevere l'hello dice al publisher «il subscriber sta per fare requestNetwork, quindi dovrei farlo anch'io».

La ricevuta ready esiste per la direzione opposta — per dire al subscriber «il publisher ha registrato il proprio requestNetwork». Senza di essa, il requestNetwork del subscriber potrebbe arrivare prima di quello del publisher ed essere rifiutato dal framework. La ricevuta ready è un segnale non bloccante: il subscriber non la attende prima di chiamare requestNetwork (aggiungerebbe un round trip), ma se arriva mentre il subscriber è in stato IDLE (tra tentativi di retry), può ritentare immediatamente senza attendere il gap RETRY_DELAY_MS.

L'asimmetria del retry loop

Questa è la parte più sottile del design. Solo il subscriber ritenta. Il publisher fa esattamente un tentativo di requestNetwork per ogni hello ricevuto. Questo perché:

  • Se entrambe le parti ritentassero indipendentemente, i loro cicli di retry andrebbero fuori fase (diverse durate delay(), diverse pause GC), e le due chiamate requestNetwork si sovrapporrebbero raramente dentro la finestra di 500 ms.
  • Il retry loop del subscriber invia un hello fresco a ogni tentativo, che re-innesca il buildLink del publisher via publishHelloListeners. Questo garantisce che il requestNetwork del publisher segua sempre l'hello di ~50 ms, ben dentro la finestra di 500 ms.

Questo è documentato in dettaglio in AwarePeerLink.build.

NDP requestNetwork — la finestra di 500 ms {#ndp-requestnetwork--the-500-ms-window}

La chiamata requestNetwork è l'operazione più timing-sensitive nel trasporto Aware. Ecco cosa succede su ciascun lato:

Diagram 6
6

Cosa significa la callback onUnavailable

onUnavailable scatta quando il framework rifiuta il requestNetwork prima di trovare una richiesta peer corrispondente. Il PeerHandle stesso è ancora valido — solo l'accoppiamento NDP (Neighbor Discovery Protocol) è fallito perché l'altro lato non aveva ancora registrato. PlainApp deliberatamente non chiama session.invalidatePeerHandle in questo caso, perché invalidare l'handle scarterebbe l'unico segnale che onServiceDiscovered sia mai stato chiamato (scatta una volta per peer per la durata della sessione di subscribe). Con l'handle preservato, il retry può riusarlo invece di attendere una discovery fresca.

Lo stesso vale per l'handle lato publisher proveniente da onMessageReceived — il publisher mantiene l'entry publishPeerHandles[fromCid] attraverso i tentativi falliti, così l'hello successivo del subscriber riusa l'handle in cache invece di essere scartato.

Ogni peer paired ottiene il proprio oggetto AwarePeerLink, posseduto dall'AwareLinkPool process-wide. Il pool gestisce gli eventi di discovery, il riuso dei link e l'evizione degli idle.

Diagram 7
7

Perché nessun auto-build su discovery?

Il pool esplicitamente non costruisce un link quando onServiceDiscovered scatta. Questa è una decisione critica: uno coffee shop affollato potrebbe avere 100 dispositivi PlainApp che pubblicano tutti il servizio «plain-peer». Se ogni discovery innesca un requestNetwork, il framework verrebbe inondato di tentativi di setup NDP e la radio Wi-Fi sarebbe saturata.

Invece, il pool registra solo il PeerHandle e attende uno tra:

  1. L'utente locale invia un messaggioWifiAwareTransport.sendpool.buildLink(peer) (trigger lato sender).
  2. Il peer remoto invia un helloonPublishHelloReceivedbuildLink(peer) (trigger lato receiver).
  3. Il peer remoto invia un readyonSubscribeReadyReceivedbuildLink(peer) (trigger lato receiver).

In questo modo, i link sono costruiti solo per i peer con cui l'utente sta effettivamente scambiando messaggi — non per ogni dispositivo PlainApp nel raggio radio.

Idle sweep

Ogni 10 secondi, il pool percorre tutti i link e chiude quelli il cui lastActiveAt è più vecchio di 60 secondi. Ogni send e downloadFile chiama link.touch() per aggiornare il timestamp. Questo recupera il contesto radio Wi-Fi e il pool di connessioni OkHttp per i peer con cui l'utente ha smesso di chattare — importante perché Android limita il numero di data path Aware simultanei a circa 4–10 (dipendentemente dal dispositivo).

Indirizzamento IPv6 e il trucco DNS plain-aware-peer {#ipv6-addressing--the-plain-aware-peer-dns-trick}

I data path Wi-Fi Aware usano solo IPv6 link-local. Non c'è IPv4, nessun server DNS, nessun DHCP. L'indirizzo IPv6 del peer è consegnato via il campo WifiAwareNetworkInfo.peerIpv6Addr in onCapabilitiesChanged — un indirizzo fe80::... che ha senso solo sull'interfaccia di rete Aware.

PlainApp deve inviare richieste HTTPS a questo indirizzo, ma il parsing URL https:// di OkHttp rifiuta gli IPv6 letterali raw in un hostname (https://[fe80::abcd]:8443/ funziona, ma instradarlo attraverso un resolver Dns custom è più pulito). Il trucco:

Diagram 8
8

Perché un hostname sentinel?

L'alternativa — passare il letterale IPv6 direttamente nell'URL — richiederebbe che ogni call site sappia dell'indirizzo link-local. Usando un hostname sentinel, la costruzione dell'URL è identica per LAN e Aware: entrambi producono un URL https://<host>:<port>/peer_graphql valido che OkHttp può parsare. L'unica differenza è l'implementazione Dns collegata al client — LAN usa il DNS di sistema, Aware usa awareDns(peerIpv6) che ritorna l'indirizzo link-local in cache per l'hostname sentinel e ricade su Dns.SYSTEM per qualsiasi altra cosa.

Perché network.socketFactory?

L'oggetto Network di Android rappresenta una specifica interfaccia di rete (in questo caso, il data path Aware). Chiamando network.socketFactory e passandolo alla config socketFactory di OkHttp, forziamo tutti i socket TCP ad essere creati sull'interfaccia Aware — non sull'interfaccia Wi-Fi o cellulare di default. Senza questo, l'OS instraderebbe la richiesta via il network di default, dove l'IPv6 link-local è irraggiungibile, e la richiesta fallirebbe con ENETUNREACH.

Crittografia: derivazione PMK e riuso di ChaCha20 {#cryptography-pmk-derivation--chacha20-reuse}

Wi-Fi Aware supporta una PMK (Pairwise Master Key) opzionale per il data path. Quando impostata, il link L2 stesso è cifrato con quella PMK — la radio Wi-Fi gestisce la cifratura, nessuna crittografia a livello applicativo richiesta.

PlainApp deriva la PMK dalla stessa chiave condivisa ChaCha20 che LanTransport e BleTransport usano per la cifratura a livello applicativo:

Diagram 9
9

Perché troncare a 32 byte?

La PMK di Wi-Fi Aware deve essere esattamente 32 byte (256 bit). La chiave condivisa ChaCha20 del pairing è anch'essa 32 byte nel caso normale, quindi il branch raw.size == 32 è il path comune. Il fallback di troncamento/padding gestisce il caso (teorico) in cui la chiave fosse memorizzata più corta — il padding con zeri a 32 byte è una misura difensiva, non qualcosa che accade nella pratica con peer correttamente paired.

L'envelope firmata è identica a LAN

Poiché createCryptoHttpClient è la stessa factory usata da LanTransport, la crittografia L7 su Aware è byte-per-byte identica a LAN. Il PeerGraphQLService lato server non sa (né gli importa) quale trasporto abbia consegnato la richiesta — vede semplicemente un payload GraphQL firmato e cifrato e lo decifra con la chiave condivisa del peer. Questo è il principio «una codebase, molti trasporti» documentato in Chat Architecture.

Percorso di invio messaggi (end-to-end) {#message-send-path-end-to-end}

Mettendo tutto insieme — cosa succede quando un messaggio di chat viene inviato via Wi-Fi Aware:

Diagram 10
10

Scelte di design notevoli

  • Riuso della connessione. A differenza di BleTransport, che abbatte la connessione GATT dopo ogni richiesta, WifiAwareTransport riusa il data path Aware per quante richieste l'utente fa entro la finestra idle di 60 s. La prima richiesta paga l'handshake di ~400 ms; le richieste successive sono ~10 ms di round trip.
  • Stessa crittografia di LAN. L'interceptor ChaCha20 e l'envelope firmata sono byte-identical a LAN. Il PeerGraphQLService del peer non sa quale trasporto abbia consegnato la richiesta.
  • Nessuna preemption su fallimento del link. Se buildLink fallisce, il trasporto lancia TransportUnavailable e il router ricade su BLE. Non c'è retry dentro sendAwarePeerLink.build fa già il proprio loop di retry interno (con MAX_BUILD_ATTEMPTS = 1 sul client, di più se il prewarmer ha preparato entrambi i lati).

Percorso di download file (end-to-end) {#file-download-path-end-to-end}

I download file via Aware riusano lo stesso data path dei messaggi di chat, ma usano un client OkHttp separato configurato per lo streaming di file grandi:

Diagram 11
11

Perché un client separato per i download?

Il client di chat (AwareHttpClientFactory.build) ha una requestTimeoutMillis di 30 s — appropriata per mutation GraphQL ma catastrofica per un download di file da 100 MB. Il client di download (buildFileDownload) imposta:

  • connectTimeoutMillis = 10_000 (più lungo dei 5 s della chat, più tollerante per il first-packet lento su un data path fresco)
  • readTimeout = 120 s per read (vs il default implicito di 10 s)
  • requestTimeoutMillis = 120_000 (2 minuti — sufficiente per la maggior parte dei file)
  • retryOnConnectionFailure(true) — un read abbandonato a metà download è ritentato invece di fallire l'intero trasferimento

Omette anche l'interceptor ChaCha20. L'endpoint /fs serve byte di file grezzi (non un'envelope GraphQL firmata), e la PMK L2 (quando presente) cifra già il link radio. Doppia-cifrare un video da 50 MB con ChaCha20 in software sprecherebbe CPU e rallenterebbe il trasferimento.

Streaming, non buffering

Come il path BLE, i download Aware streammano il file attraverso un ByteReadChannel — il file è scritto su un file temp man mano che i byte arrivano, non buffered in memoria. PeerFileDownloader legge chunk da 8 KB ed emette eventi di progresso ogni secondo. La stessa pipeline DownloadedResponse / PeerFileDownloader / DownloadQueue è riusata attraverso tutti i trasporti — transport-specific solo la sorgente channel.

Prewarming: avvio di Aware innescato da BLE {#prewarming-ble-triggered-aware-startup}

La più grande latenza visibile all'utente nel path Aware è il primo handshake — se entrambi i lati non hanno ancora avviato Aware, il primo messaggio dell'utente deve attendere:

  1. Attach della sessione Aware locale (~1 s)
  2. Avvio di publish + subscribe locale (~1 s)
  3. Avvio di Aware del peer remoto (~2 s via BLE)
  4. Discovery reciproco (~1 s)
  5. Handshake NDP (~400 ms)

Sono ~5 secondi prima che il primo byte sia inviato. Per nascondere questa latenza, PeerTransportPrewarmer gira all'ingresso in ChatPage e innesca l'avvio di Aware del peer remoto via BLE:

Diagram 12
12

Il duplice ruolo di BLE

BLE serve a due scopi qui:

  1. Leggere lo stato Aware corrente del peer (economico, nessuna connessione GATT — il serviceData byte0 della scan response porta i flag Aware).
  2. Innescare il peer per avviare Aware se lo supporta ma non lo sta attualmente eseguendo. Questo passa attraverso il path BleTransport.send regolare — una mutation GraphQL startAware cifrata con la chiave ChaCha20 condivisa, consegnata via RPC GATT all'endpoint /peer_graphql del peer.

Questo è uno dei pochi punti in cui i trasporti cooperano invece di cadere semplicemente l'uno sull'altro: BLE è usato per pre-emptively upgrade la sessione al trasporto Aware più veloce, prima che l'utente anche se ne accorga.

Perché setAwareRunning(true) ottimistico?

La mutation startAware ritorna success non appena il resolver del peer remoto invoca WifiAwareTransport.start() — ma la sessione Aware non è ancora effettivamente attached (onAttached scatta asincronamente). PlainApp marca il peer come awareRunning = true ottimisticamente, perché:

  • Se si è avviata davvero, il prossimo send userà Aware (veloce).
  • Se non è successo (ad es. il Wi-Fi del peer è spento), il buildLink del prossimo send fallirà con TransportUnavailable e ricadrà su BLE naturalmente.
  • Il costo di un falso positivo è un timeout di ~5 s, non un blocco permanente — PeerCircuitBreaker registra il fallimento ma non apre la gamba BLE (BLE si apre solo sui propri fallimenti).

Perché throttle a 30 s?

PeerTransportPrewarmer.prewarm(peerId) registra un timestamp per peer e si rifiuta di girare di nuovo entro 30 s. Questo perché l'utente naviga avanti e indietro tra lista chat e pagina chat frequentemente — senza throttling, ogni navigazione innescerebbe uno scan BLE + mutation startAware, scaricando la batteria e saturando la radio BLE. La finestra di 30 s è abbastanza corta da intercettare un peer appena venuto online (ad es. l'utente ha aperto l'app sul dispositivo remoto) ma abbastanza lunga da evitare re-run spuri.

Modalità di fallimento e il flag fast-skip {#failure-modes--the-fast-skip-flag}

Aware ha più modalità di fallimento di qualsiasi altro trasporto. Il flag fast-skip isAwareRunning è la singola ottimizzazione più importante dell'intero modulo — senza di esso, ogni send sprecherebbe 10 s su buildLink che va in timeout prima di ricadere su BLE.

Diagram 13
13

Il flag isAwareRunning è il perno

Senza questo singolo booleano, ogni send Aware o:

  • Tenterebbe sempre buildLink → timeout di 10 s su ogni invio a un peer il cui Aware non è in esecuzione.
  • Saltarebbe sempre Aware → non lo userebbe mai anche quando entrambi i lati lo hanno in esecuzione.

Il flag è aggiornato da due sorgenti, in ordine di autorità:

  1. Scan response BLE (economico, nessuna connessione GATT) — impostato da PeerTransportPrewarmer.refreshAwareFlagFromScan. Il peer pubblicizza il proprio stato Aware nel payload serviceData di 9 byte (byte0 bitfield).
  2. Risposta GATT DISCOVER (autoritativa) — impostata da PairingTransport.scanAndDiscover quando avviene una discovery completa. Questo sovrascrive l'hint di scan.

Quando false, WifiAwareTransport.send e downloadFile lanciano TransportUnavailable immediatamente — nessuno scan, nessun handshake, nessun timeout. Il router ricade su BLE in microsecondi.

Riferimento delle costanti chiave {#key-constants-reference}

CostanteValoreDoveScopo
AwareSession.SERVICE_NAME"plain-peer"DiscoveryNome servizio pubblicato & sottoscritto da ogni dispositivo PlainApp
AwareSession.PEER_HANDLE_MAX_AGE_MS30 000Cache PeerHandleScarta handle stale (la sessione di publish del peer potrebbe essere stata riavviata)
AwareSession.READY_TIMEOUT_MS15 000HandshakeAttesa subscriber per la ricevuta ready del publisher
AwareSession.MSG_HELLO0HandshakeSubscriber → Publisher message ID
AwareSession.MSG_READY1HandshakePublisher → Subscriber message ID
AwarePeerLink.MAX_BUILD_ATTEMPTS1Handshake (solo client)Singolo tentativo — era 3, ora 1 perché il prewarmer prepara entrambi i lati
AwarePeerLink.ATTEMPT_TIMEOUT_MS5 000HandshakeTimeout per-tentativo — era 10 s, dimezzato per accelerare il fallback
AwarePeerLink.RETRY_DELAY_MS500HandshakeRitardo tra tentativi di retry (solo client)
AwarePeerLink.REQUEST_TIMEOUT_MS30 000NDPTimeout di connectivityManager.requestNetwork
AwareLinkPool.IDLE_TIMEOUT_MS60 000Pool sweepChiude link idle dopo 60 s di inattività
AwareLinkPool.IDLE_SWEEP_INTERVAL_MS10 000Pool sweepIntervallo di sweep
AwareHttpClientFactory.AWARE_HOST"plain-aware-peer"DNSHostname sentinel risolto nell'IPv6 del peer da un Dns custom
build client chatconnectTimeout 5 s, requestTimeout 30 s, interceptor ChaCha20
buildFileDownloadconnectTimeout 10 s, readTimeout 120 s, requestTimeout 120 s, no crypto
PeerTransportPrewarmer.PREWARM_TTL_MS30 000PrewarmThrottle per peer
PeerTransportPrewarmer.BLE_SCAN_TIMEOUT_MS15 000PrewarmTimeout scan BLE per refreshAwareFlagFromScan
PeerCircuitBreaker.WINDOW_MS30 000Circuit breakerDurata di apertura dopo soglia
PeerCircuitBreaker.MAX_FAILURES2Circuit breakerFallimenti entro finestra per aprire
TempData.httpsPort8443 (default)ServerPorta del publisher pubblicizzata via WifiAwareNetworkSpecifier.setPort
BleServiceData.AWARE_SUPPORTED0x01Scan response BLEBit che indica che il peer supporta Wi-Fi Aware
BleServiceData.AWARE_RUNNING0x02Scan response BLEBit che indica che il servizio Aware del peer è attualmente in esecuzione

Riepilogo dei compromessi di design {#design-trade-offs-recap}

Diagram 14
14

Letture aggiuntive

  • Chat Architecture — come WifiAwareTransport si inserisce nella catena di fallback LAN → Aware → BLE e nella pipeline più ampia di send/receive della chat.
  • BLE Transport — il trasporto di ultima istanza che subentra quando Aware non è disponibile; anche il canale usato dal prewarmer per innescare l'avvio di Aware sul peer remoto.
  • Pairing Flow — come viene stabilita la chiave ChaCha20 condivisa riusata come PMK di Aware, e come i flag di scan response BLE (AWARE_SUPPORTED / AWARE_RUNNING) vengono popolati.