Retour au blog
Transport19 min read

Conception du transport Wi-Fi Aware — Découverte de voisins et chemins de données

Cet article explique comment PlainApp utilise Wi-Fi Aware (NAN — Neighbor Awareness Networking) comme palier intermédiaire de sa chaîne de repli de transport pair, entre LAN (HTTPS même sous-réseau) et BLE (RPC GATT en dernier recours). Wi-Fi Aware est ce qui permet à deux appareils PlainApp de communiquer lorsqu'ils sont sur des SSID différents, des VLAN invité vs IoT, ou sans aucune infrastructure Wi-Fi — sans jamais avoir besoin d'une adresse IP d'un serveur DHCP.

L'article couvre le cycle de vie de session Aware (Android uniquement), le modèle de découverte publish / subscribe, la poignée de main à deux phases avec séparation des rôles qui synchronise requestNetwork des deux côtés dans la fenêtre de ~500 ms du framework, le pool de liens par pair avec balayage d'inactivité, l'astuce IPv6 + DNS personnalisé qui permet à un seul client OkHttp de servir à la fois LAN et Aware, et le préchauffeur qui déclenche le démarrage Aware du pair via BLE.

Pour la chaîne de repli plus large, voir Architecture du Chat. Pour le transport BLE qui prend le relais quand Aware est indisponible, voir Transport BLE. Pour la façon dont la clé ChaCha20 partagée réutilisée comme PMK Aware est établie, voir Flux de jumelage.

Table des matières

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

Wi-Fi Aware (IEEE 802.11bc, anciennement NAN — Neighbor Awareness Networking) est une certification de la Wi-Fi Alliance qui permet à deux appareils de se découvrir et d'échanger des données sans aucune infrastructure Wi-Fi — pas d'AP, pas de routeur, pas de DHCP. PlainApp l'utilise pour deux scénarios que LAN ne peut pas couvrir :

  • SSID/VLAN différents. Un téléphone sur le réseau invité et un laptop sur le VLAN IoT sont tous deux « en ligne » via Wi-Fi mais ne peuvent pas s'atteindre mutuellement par IP. Aware crée un data path direct appareil- à-appareil qui contourne entièrement l'infrastructure.
  • Aucune infrastructure du tout. Deux appareils dans la nature avec le Wi-Fi activé mais sans AP peuvent quand même chatter. (BLE couvre aussi ce cas, mais Aware est beaucoup plus rapide — ~10 ms allers-retours vs secondes, et Mo/s vs dizaines de Ko/s.)

Diagram 1
1

Contraintes de plateforme

Wi-Fi Aware est Android uniquement dans PlainApp :

  • Android 13 (API 33) est le minimum — les surcharges WifiAwareNetworkSpecifier.Builder avec setPort() et setPmk() dont PlainApp dépend exigent isTPlus().
  • iOS n'expose pas Wi-Fi Aware aux applications tierques. iOS PlainApp passe directement de LAN à BLE ; l'objet WifiAwareTransport n'est même pas compilé dans la cible iOS (@RequiresApi(Build.VERSION_CODES.S) + source set androidMain).

C'est pourquoi PeerTransportRouter.buildList appelle createWifiAwareTransport() — une fabrique qui renvoie null sur iOS.

Où Aware se situe dans la chaîne de repli {#where-aware-sits-in-the-fallback-chain}

Le PeerTransportRouter de PlainApp est une liste ordonnée. Pour chaque appel send ou downloadFile, il parcourt la liste et essaie chaque transport jusqu'à ce que l'un réussisse ; les échecs descendent en cascade.

Diagram 2
2

Pourquoi Aware est-il « au milieu » et pas « en premier » ?

Parce que LAN est presque toujours plus rapide lorsqu'il est disponible. Un saut Wi-Fi même sous-réseau via un AP est un seul échange de trames 802.11 ; un data path Aware ajoute une mise en place NDP (~5 s à la première utilisation) plus un second contexte radio Wi-Fi pour la liaison appareil-à- appareil. Si les deux sont atteignables, LAN gagne en latence et en débit.

À l'inverse, BLE est toujours plus lent — mais il fonctionne dès que les deux appareils sont jumelés. Aware est au milieu : plus rapide que BLE, plus lent que LAN, et uniquement disponible sur les appareils Android 13+ avec le Wi-Fi activé.

Cycle de vie de session : Attach → Publish + Subscribe {#session-lifecycle-attach--publish--subscribe}

Une session Wi-Fi Aware est à l'échelle du processus. Il y a exactement une WifiAwareSession par appareil ; à l'intérieur, PlainApp fait tourner une session publish (pour que les pairs puissent nous découvrir) et une session subscribe (pour que nous puissions découvrir les pairs). Les deux sont démarrées dès que AwareSession.start() complète le callback attach.

Diagram 3
3

Pourquoi publish ET subscribe sur le même appareil ?

Le modèle de découverte Wi-Fi Aware est asymétrique : un éditeur publie un service, un abonné le scanne. Pour rendre la découverte symétrique (les deux appareils se découvrent mutuellement), PlainApp fait les deux à la fois. Sans cela, l'appareil A devrait savoir à l'avance s'il est éditeur ou abonné pour un pair donné — mais les rôles de pair sont déterminés ultérieurement par comparaison de clientId (voir Découverte et attribution des rôles).

Publier et s'abonner simultanément signifie que chaque appareil voit le onServiceDiscovered de l'autre (en tant qu'abonné) ET reçoit les messages hello de l'autre (en tant qu'éditeur) — les deux directions de la poignée de main sont toujours disponibles.

Redémarrage automatique sur terminaison

Certaines variantes Android (MIUI en particulier) tuent les sessions Aware longue durée pour économiser la batterie. PlainApp gère cela dans les callbacks onSessionTerminated : il met à null la session terminée et appelle immédiatement publishOwnService / subscribeOwnService à nouveau sur la WifiAwareSession encore attachée. La session attach elle-même n'est pas perdue — seule la session de découverte publish/subscribe. Les PeerHandle d'avant la terminaison deviennent périmés, c'est pourquoi awaitPeerHandle vérifie l'horodatage discoveredAt et rejette les handles de plus de 30 s.

Découverte et attribution des rôles {#discovery--role-assignment}

Le protocole de data-path Wi-Fi Aware exige qu'un côté agisse comme éditeur (serveur) et l'autre comme abonné (client). Les deux côtés ne peuvent pas simultanément être initiateurs — le framework rejette les requêtes sans contrepartie correspondante.

PlainApp attribue les rôles déterministiquement par pair via une simple comparaison lexicographique des clientIds :

Diagram 4
4

Pourquoi déterministe et non négocié ?

Une approche négociée (par ex. « le MAC le plus bas est serveur ») exigerait un échange de messages supplémentaire. La comparaison lexicographique est idempotente, symétrique et sans état : les deux appareils calculent le même rôle pour la même paire sans aucune communication. Le clientId est un short UUID de 13 caractères, donc les égalités (clientId == peer.id) ne se produisent que lors de la comparaison d'un pair avec lui-même — ce qui n'atteint jamais le transport.

Le rôle détermine deux choses en aval :

  1. Qui pilote la boucle de réessai. Seul le client réessaie requestNetwork ; le serveur fait exactement une tentative par hello reçu. C'est critique pour la fenêtre de 500 ms (section suivante).
  2. Qui définit le port. L'éditeur appelle setPort(httpsPort) car c'est lui qui accepte les connexions entrantes sur son port serveur HTTPS. L'abonné ne définit pas de port — il apprend le port du pair depuis le WifiAwareNetworkInfo après l'établissement du data path.

La poignée de main à deux phases (hello + ready) {#the-two-phase-handshake-hello--ready}

La partie la plus difficile de la mise en place d'un data-path Wi-Fi Aware est le timing. Le framework exige que les deux côtés appellent connectivityManager.requestNetwork dans une fenêtre d'environ 500 ms l'un de l'autre — si un côté l'appelle avant que l'autre n'ait enregistré sa requête correspondante, le framework le rejette immédiatement avec onUnavailable (« releaseRequestAsUnfulfillableByAnyFactory »).

PlainApp résout cela par une poignée de main à deux messages au niveau application qui tourne au-dessus du canal de messages L2 Aware (la même API sendMessage utilisée par onServiceDiscovered) :

Diagram 5
5

Pourquoi deux messages (hello + ready) plutôt qu'un seul ?

Le hello seul ne suffit pas à cause de l'asymétrie de direction. L'abonné peut envoyer le hello dès qu'il découvre l'éditeur (dans onServiceDiscovered), mais l'éditeur ne peut pas lancer requestNetwork avant d'avoir le PeerHandle de l'abonné, qu'il n'apprend qu'en recevant le hello. Donc le hello sert deux objectifs :

  1. Livrer le PeerHandle de l'abonné à l'éditeur. L'éditeur en a besoin pour construire le WifiAwareNetworkSpecifier.
  2. Signaler l'intention de se connecter. Recevoir le hello dit à l'éditeur « l'abonné va faire requestNetwork, donc je devrais aussi ».

L'accusé de réception ready existe pour la direction opposée — pour dire à l'abonné « l'éditeur a enregistré son requestNetwork ». Sans lui, le requestNetwork de l'abonné pourrait prendre de l'avance sur celui de l'éditeur et être rejeté par le framework. L'accusé ready est un signal non bloquant : l'abonné ne l'attend pas avant d'appeler requestNetwork (cela ajouterait un aller-retour), mais s'il arrive pendant que l'abonné est en état IDLE (entre tentatives de réessai), l'abonné peut réessayer immédiatement sans attendre le délai RETRY_DELAY_MS.

L'asymétrie de la boucle de réessai

C'est la partie la plus subtile de la conception. Seul l'abonné réessaie. L'éditeur fait exactement une tentative requestNetwork par hello reçu. C'est parce que :

  • Si les deux côtés réessayaient indépendamment, leurs cycles de réessai se déphaseraient (durées delay() différentes, pauses GC différentes), et les deux appels requestNetwork chevaucheraient rarement la fenêtre de 500 ms.
  • La boucle de réessai de l'abonné envoie un nouveau hello à chaque tentative, ce qui redéclenche le buildLink de l'éditeur via publishHelloListeners. Cela garantit que le requestNetwork de l'éditeur suit toujours le hello d'environ 50 ms, bien dans la fenêtre de 500 ms.

Ceci est documenté en détail dans AwarePeerLink.build.

NDP requestNetwork — La fenêtre de 500 ms {#ndp-requestnetwork--the-500-ms-window}

L'appel requestNetwork est l'opération la plus sensible au timing du transport Aware. Voici ce qui se passe de chaque côté :

Diagram 6
6

Ce que le callback onUnavailable signifie

onUnavailable se déclenche quand le framework rejette le requestNetwork avant de trouver une requête de pair correspondante. Le PeerHandle lui- même reste valide — seul l'appairage NDP (Neighbor Discovery Protocol) a échoué car l'autre côté n'avait pas encore enregistré sa requête. PlainApp ne délibérément n'appelle pas session.invalidatePeerHandle dans ce cas, car invalider le handle écarterait le seul signal que onServiceDiscovered ait jamais été appelé (il se déclenche une fois par pair par durée de vie de session subscribe). Avec le handle préservé, le réessai peut le réutiliser au lieu d'attendre une découverte fraîche.

Il en va de même pour le handle côté éditeur depuis onMessageReceived — l'éditeur conserve l'entrée publishPeerHandles[fromCid] à travers les tentatives échouées, de sorte que le prochain hello de l'abonné réutilise le handle en cache au lieu d'être abandonné.

Chaque pair jumelé obtient son propre objet AwarePeerLink, possédé par l'AwareLinkPool à l'échelle du processus. Le pool gère les événements de découverte, la réutilisation des liens et l'éviction des liens inactifs.

Diagram 7
7

Pourquoi pas de construction automatique sur découverte ?

Le pool ne construit pas explicitement de lien lorsque onServiceDiscovered se déclenche. C'est une décision critique : un café animé pourrait avoir 100 appareils PlainApp publiant tous le service « plain-peer ». Si chaque découverte déclenchait un requestNetwork, le framework serait inondé de tentatives de mise en place NDP et la radio Wi-Fi serait saturée.

Au lieu de cela, le pool enregistre uniquement le PeerHandle et attend l'un de ces événements :

  1. L'utilisateur local envoie un messageWifiAwareTransport.sendpool.buildLink(peer) (déclenchement côté émetteur).
  2. Le pair distant envoie un helloonPublishHelloReceivedbuildLink(peer) (déclenchement côté récepteur).
  3. Le pair distant envoie un readyonSubscribeReadyReceivedbuildLink(peer) (déclenchement côté récepteur).

Ainsi, les liens ne sont construits que pour les pairs avec lesquels l'utilisateur échange réellement des messages — pas chaque appareil PlainApp à portée radio.

Balayage d'inactivité

Toutes les 10 secondes, le pool parcourt tous les liens et ferme ceux dont lastActiveAt date de plus de 60 secondes. Chaque send et downloadFile appelle link.touch() pour rafraîchir l'horodatage. Cela récupère le contexte radio Wi-Fi et le pool de connexions OkHttp pour les pairs avec lesquels l'utilisateur a cessé de chatter — important car Android limite le nombre de data paths Aware simultanés à environ 4–10 (selon l'appareil).

Adressage IPv6 et l'astuce DNS plain-aware-peer {#ipv6-addressing--the-plain-aware-peer-dns-trick}

Les data paths Wi-Fi Aware utilisent IPv6 link-local uniquement. Il n'y a ni IPv4, ni serveur DNS, ni DHCP. L'adresse IPv6 du pair est livrée via le champ WifiAwareNetworkInfo.peerIpv6Addr dans onCapabilitiesChanged — une adresse fe80::... qui n'a de sens que sur l'interface réseau Aware.

PlainApp doit envoyer des requêtes HTTPS à cette adresse, mais l'analyse d'URL https:// d'OkHttp refuse les littéraux IPv6 bruts dans un nom d'hôte (https://[fe80::abcd]:8443/ fonctionne, mais le router via un résolveur Dns personnalisé est plus propre). L'astuce :

Diagram 8
8

Pourquoi un nom d'hôte sentinel ?

L'alternative — passer le littéral IPv6 directement dans l'URL — exigerait que chaque site d'appel connaisse l'adresse link-local. En utilisant un nom d'hôte sentinel, la construction de l'URL est identique pour LAN et Aware : les deux produisent une URL https://<host>:<port>/peer_graphql valide qu'OkHttp peut analyser. La seule différence est l'implémentation Dns liée au client — LAN utilise le DNS système, Aware utilise awareDns(peerIpv6) qui renvoie l'adresse link-local en cache pour le nom d'hôte sentinel et retombe sur Dns.SYSTEM pour tout le reste.

Pourquoi network.socketFactory ?

L'objet Network d'Android représente une interface réseau spécifique (en l'occurrence, le data path Aware). En appelant network.socketFactory et en le passant à la config socketFactory d'OkHttp, nous forçons toutes les sockets TCP à être créées sur l'interface Aware — pas l'interface Wi-Fi ou cellulaire par défaut. Sans cela, l'OS routerait la requête via le réseau par défaut, où l'IPv6 link-local est injoignable, et la requête échouerait avec ENETUNREACH.

Cryptographie : dérivation PMK et réutilisation ChaCha20 {#cryptography-pmk-derivation--chacha20-reuse}

Wi-Fi Aware supporte une PMK (Pairwise Master Key) optionnelle pour le data path. Quand elle est définie, le lien L2 lui-même est chiffré avec cette PMK — la radio Wi-Fi gère le chiffrement, nul besoin de crypto au niveau application.

PlainApp dérive la PMK de la même clé partagée ChaCha20 que LanTransport et BleTransport utilisent pour le chiffrement au niveau application :

Diagram 9
9

Pourquoi tronquer à 32 octets ?

La PMK Wi-Fi Aware doit faire exactement 32 octets (256 bits). La clé partagée ChaCha20 issue du jumelage fait aussi 32 octets dans le cas normal, donc la branche raw.size == 32 est le chemin courant. La repli troncature/padding gère le cas (théorique) où la clé aurait été stockée plus courte — remplir de zéros pour atteindre 32 octets est une mesure défensive, pas quelque chose qui arrive en pratique avec des pairs correctement jumelés.

L'enveloppe signée est identique à LAN

Comme createCryptoHttpClient est la même fabrique que celle utilisée par LanTransport, la crypto L7 sur Aware est identique octet pour octet à LAN. Le PeerGraphQLService côté serveur ne sait pas (et n'a pas à le savoir) quel transport a livré la requête — il voit simplement une charge utile GraphQL signée et chiffrée et la déchiffre avec la clé partagée du pair. C'est le principe « une base de code, plusieurs transports » documenté dans Architecture du Chat.

Chemin d'envoi de message (de bout en bout) {#message-send-path-end-to-end}

En assemblant tout — ce qui se passe quand un message de chat est envoyé via Wi-Fi Aware :

Diagram 10
10

Choix de conception notables

  • Réutilisation de connexion. Contrairement à BleTransport, qui démonte la connexion GATT après chaque requête, WifiAwareTransport réutilise le data path Aware pour autant de requêtes que l'utilisateur fait dans la fenêtre d'inactivité de 60 s. La première requête paie la poignée de main de ~400 ms ; les requêtes suivantes font ~10 ms allers-retours.
  • Même crypto que LAN. L'intercepteur ChaCha20 et l'enveloppe signée sont identiques octet pour octet à LAN. Le PeerGraphQLService du pair ne sait pas quel transport a livré la requête.
  • Pas de préemption sur échec de lien. Si buildLink échoue, le transport lève TransportUnavailable et le routeur passe à BLE. Il n'y a pas de réessai dans sendAwarePeerLink.build fait déjà sa propre boucle de réessai interne (avec MAX_BUILD_ATTEMPTS = 1 côté client, plus si le préchauffeur a préparé les deux côtés).

Chemin de téléchargement de fichier (de bout en bout) {#file-download-path-end-to-end}

Les téléchargements de fichiers via Aware réutilisent le même data path que les messages de chat, mais utilisent un client OkHttp séparé configuré pour le streaming de gros fichiers :

Diagram 11
11

Pourquoi un client séparé pour les téléchargements ?

Le client de chat (AwareHttpClientFactory.build) a un requestTimeoutMillis de 30 s — approprié pour des mutations GraphQL mais catastrophique pour un téléchargement de fichier de 100 Mo. Le client de téléchargement (buildFileDownload) définit :

  • connectTimeoutMillis = 10_000 (plus long que les 5 s du chat, plus tolérant pour un premier paquet lent sur un data path frais)
  • readTimeout = 120 s par lecture (vs la valeur par défaut implicite de 10 s)
  • requestTimeoutMillis = 120_000 (2 minutes — suffisant pour la plupart des fichiers)
  • retryOnConnectionFailure(true) — une lecture interrompue en milieu de téléchargement est réessayée au lieu de faire échouer tout le transfert

Il omet aussi l'intercepteur ChaCha20. L'endpoint /fs sert des octets de fichier bruts (pas une enveloppe GraphQL signée), et la PMK L2 (lorsqu'elle est présente) chiffre déjà le lien radio. Double-chiffrer une vidéo de 50 Mo avec ChaCha20 en logiciel gaspillerait du CPU et ralentirait le transfert.

Streaming, pas de mise en mémoire tampon

Comme le chemin BLE, les téléchargements Aware streament le fichier à travers un ByteReadChannel — le fichier est écrit dans un fichier temporaire à mesure que les octets arrivent, pas mis en mémoire tampon. Le PeerFileDownloader lit des morceaux de 8 Ko et émet des événements de progression chaque seconde. Le même pipeline DownloadedResponse / PeerFileDownloader / DownloadQueue est réutilisé à travers tous les transports — seul le channel source est spécifique au transport.

Préchauffage : démarrage Aware déclenché par BLE {#prewarming-ble-triggered-aware-startup}

La plus grande latence visible par l'utilisateur dans le chemin Aware est la première poignée de main — si les deux côtés n'ont pas encore démarré Aware, le premier message de l'utilisateur doit attendre :

  1. Attach de la session Aware locale (~1 s)
  2. Démarrage local publish + subscribe (~1 s)
  3. Démarrage Aware du pair distant (~2 s via BLE)
  4. Découverte mutuelle (~1 s)
  5. Poignée de main NDP (~400 ms)

Cela fait ~5 secondes avant l'envoi du premier octet. Pour masquer cette latence, PeerTransportPrewarmer s'exécute à l'entrée de ChatPage et déclenche le démarrage Aware du pair distant via BLE :

Diagram 12
12

Le double rôle de BLE

BLE sert deux objectifs ici :

  1. Lire l'état Aware courant du pair (bon marché, pas de connexion GATT — le serviceData byte0 de la scan response porte les drapeaux Aware).
  2. Déclencher le démarrage Aware du pair s'il le supporte mais ne le fait pas tourner actuellement. Cela passe par le chemin habituel BleTransport.send — une mutation GraphQL startAware chiffrée avec la clé ChaCha20 partagée, livrée via RPC GATT à l'endpoint /peer_graphql du pair.

C'est l'un des rares endroits où les transports coopèrent plutôt que de se contenter de se replier : BLE est utilisé pour améliorer préventivement la session vers le transport Aware plus rapide, avant même que l'utilisateur ne s'en rende compte.

Pourquoi un setAwareRunning(true) optimiste ?

La mutation startAware renvoie le succès dès que le résolveur du pair distant invoque WifiAwareTransport.start() — mais la session Aware n'est pas encore réellement attachée (onAttached se déclenche de façon asynchrone). PlainApp marque le pair comme awareRunning = true optimistiquement, parce que :

  • Si elle a vraiment démarré, le prochain send utilisera Aware (rapide).
  • Si ce n'est pas le cas (par ex. le Wi-Fi du pair est éteint), le buildLink du prochain send échouera avec TransportUnavailable et retombera sur BLE naturellement.
  • Le coût d'un faux positif est un délai d'attente de ~5 s, pas un blocage permanent — PeerCircuitBreaker enregistre l'échec mais n'ouvre pas la jambe BLE (BLE ne s'ouvre que sur ses propres échecs).

Pourquoi une limitation à 30 s ?

PeerTransportPrewarmer.prewarm(peerId) enregistre un horodatage par pair et refuse de se réexécuter dans les 30 s. C'est parce que l'utilisateur navigue fréquemment entre la liste de chat et la page de chat — sans limitation, chaque navigation déclencherait un scan BLE + une mutation startAware, drainant la batterie et spamme la radio BLE. La fenêtre de 30 s est assez courte pour rattraper un pair qui vient d'arriver en ligne (par ex. l'utilisateur a ouvert l'appareil sur l'appareil distant) mais assez longue pour éviter les réexécutions intempestives.

Modes d'échec et le drapeau de saut rapide {#failure-modes--the-fast-skip-flag}

Aware a plus de modes d'échec que tout autre transport. Le drapeau de saut rapide isAwareRunning est la plus importante optimisation de tout le module — sans lui, chaque send gaspillerait 10 s sur un buildLink expirant avant de retomber sur BLE.

Diagram 13
13

Le drapeau isAwareRunning est le pivot

Sans ce simple booléen, chaque send Aware soit :

  • Toujours tenterait buildLink → délai d'attente de 10 s à chaque envoi vers un pair dont Aware ne tourne pas.
  • Toujours sauterait Aware → ne l'utiliserait jamais même quand les deux côtés le font tourner.

Le drapeau est rafraîchi depuis deux sources, par ordre d'autorité :

  1. Scan response BLE (bon marché, pas de connexion GATT) — défini par PeerTransportPrewarmer.refreshAwareFlagFromScan. Le pair publie son état Aware dans la charge utile serviceData de 9 octets (byte0 bitfield).
  2. Réponse DISCOVER GATT (autorisée) — définie par PairingTransport.scanAndDiscover lorsqu'une découverte complète se fait. Elle écrase l'indice de scan.

Quand il est faux, WifiAwareTransport.send et downloadFile lèvent TransportUnavailable immédiatement — pas de scan, pas de poignée de main, pas de délai d'attente. Le routeur passe à BLE en microsecondes.

Référence des constantes clés {#key-constants-reference}

ConstanteValeurRôle
AwareSession.SERVICE_NAME"plain-peer"DécouverteNom de service publié et abonné par chaque appareil PlainApp
AwareSession.PEER_HANDLE_MAX_AGE_MS30 000Cache PeerHandleRejeter les handles périmés (la session publish du pair a peut-être été redémarrée)
AwareSession.READY_TIMEOUT_MS15 000Poignée de mainAttente de l'abonné pour l'accusé ready de l'éditeur
AwareSession.MSG_HELLO0Poignée de mainID du message Abonné → Éditeur
AwareSession.MSG_READY1Poignée de mainID du message Éditeur → Abonné
AwarePeerLink.MAX_BUILD_ATTEMPTS1Poignée de main (client uniquement)Une seule tentative — était 3, maintenant 1 car le préchauffeur prépare les deux côtés
AwarePeerLink.ATTEMPT_TIMEOUT_MS5 000Poignée de mainDélai par tentative — était 10 s, divisé par 2 pour accélérer le repli
AwarePeerLink.RETRY_DELAY_MS500Poignée de mainDélai entre les tentatives de réessai (client uniquement)
AwarePeerLink.REQUEST_TIMEOUT_MS30 000NDPDélai de connectivityManager.requestNetwork
AwareLinkPool.IDLE_TIMEOUT_MS60 000Balayage du poolFermer les liens inactifs après 60 s d'inactivité
AwareLinkPool.IDLE_SWEEP_INTERVAL_MS10 000Balayage du poolIntervalle de balayage
AwareHttpClientFactory.AWARE_HOST"plain-aware-peer"DNSNom d'hôte sentinel résolu en IPv6 du pair par un Dns personnalisé
build client chatconnectTimeout 5 s, requestTimeout 30 s, intercepteur ChaCha20
buildFileDownloadconnectTimeout 10 s, readTimeout 120 s, requestTimeout 120 s, pas de crypto
PeerTransportPrewarmer.PREWARM_TTL_MS30 000PréchauffageLimitation par pair
PeerTransportPrewarmer.BLE_SCAN_TIMEOUT_MS15 000PréchauffageDélai de scan BLE pour refreshAwareFlagFromScan
PeerCircuitBreaker.WINDOW_MS30 000DisjoncteurDurée d'ouverture après seuil
PeerCircuitBreaker.MAX_FAILURES2DisjoncteurÉchecs dans la fenêtre pour ouvrir
TempData.httpsPort8443 (défaut)ServeurPort de l'éditeur annoncé via WifiAwareNetworkSpecifier.setPort
BleServiceData.AWARE_SUPPORTED0x01Scan response BLEBit indiquant que le pair supporte Wi-Fi Aware
BleServiceData.AWARE_RUNNING0x02Scan response BLEBit indiquant que le service Aware du pair tourne actuellement

Récapitulatif des compromis de conception {#design-trade-offs-recap}

Diagram 14
14

Pour aller plus loin

  • Architecture du Chat — comment WifiAwareTransport s'intègre dans la chaîne de repli LAN → Aware → BLE et le pipeline plus large d'envoi/réception de chat.
  • Transport BLE — le transport de dernier recours qui prend le relais quand Aware est indisponible ; aussi le canal utilisé par le préchauffeur pour déclencher le démarrage Aware sur le pair distant.
  • Flux de jumelage — comment la clé ChaCha20 partagée réutilisée comme PMK Aware est établie, et comment les drapeaux de scan response BLE (AWARE_SUPPORTED / AWARE_RUNNING) sont peuplés. pour les drapeaux isAwareRunning / isAwareSupported en mémoire.