Volver al blog
Transport19 min read

Diseño del transporte Wi-Fi Aware — descubrimiento de vecinos y rutas de datos

Este artículo explica cómo PlainApp usa Wi-Fi Aware (NAN — Neighbor Awareness Networking) como nivel intermedio de su cadena de fallback de transporte entre pares, entre LAN (HTTPS de misma subred) y BLE (RPC GATT de último recurso). Wi-Fi Aware es lo que permite que dos dispositivos PlainApp hablen cuando están en distintos SSID, VLAN de invitados vs IoT, o sin infraestructura Wi-Fi alguna — sin necesitar jamás una dirección IP de un servidor DHCP.

El artículo cubre el ciclo de vida de sesión Aware exclusivo de Android, el modelo de descubrimiento publish / subscribe, el apretón de manos de dos fases con división de roles que sincroniza requestNetwork en ambos lados dentro de la ventana de ~500 ms del framework, el pool de enlaces por par con barrido de inactividad, el truco de IPv6 + DNS personalizado que permite que un único cliente OkHttp sirva tanto LAN como Aware, y el precalentador que dispara el arranque Aware del par vía BLE.

Para la cadena de fallback más amplia, consulte Arquitectura de Chat. Para el transporte BLE que toma el relevo cuando Aware no está disponible, consulte Transporte BLE. Para saber cómo se establece la clave ChaCha20 compartida reutilizada como PMK de Aware, consulte Flujo de emparejamiento.

Tabla de contenidos

¿Por qué Wi-Fi Aware? {#why-wi-fi-aware}

Wi-Fi Aware (IEEE 802.11bc, antiguamente NAN — Neighbor Awareness Networking) es una certificación de la Wi-Fi Alliance que permite a dos dispositivos descubrirse mutuamente e intercambiar datos sin ninguna infraestructura Wi-Fi — sin AP, sin router, sin DHCP. PlainApp lo usa para dos escenarios que LAN no puede cubrir:

  • Distintos SSID / VLAN. Un teléfono en la red de invitados y un portátil en la VLAN IoT están ambos «en línea» vía Wi-Fi pero no pueden alcanzar la IP del otro. Aware crea una ruta de datos directa dispositivo-a-dispositivo que sortea la infraestructura por completo.
  • Sin infraestructura alguna. Dos dispositivos en la naturaleza con Wi-Fi activado pero sin AP pueden seguir chateando. (BLE también cubre esto, pero Aware es mucho más rápido — ~10 ms de ida y vuelta frente a segundos, y MB/s frente a decenas de KB/s.)

Diagram 1
1

Restricciones de plataforma

Wi-Fi Aware es exclusivo de Android en PlainApp:

  • Android 13 (API 33) es el mínimo — las sobrecargas WifiAwareNetworkSpecifier.Builder con setPort() y setPmk() de las que depende PlainApp requieren isTPlus().
  • iOS no expone Wi-Fi Aware a apps de terceros. iOS PlainApp cae directamente de LAN a BLE; el objeto WifiAwareTransport ni siquiera se compila en el destino iOS (@RequiresApi(Build.VERSION_CODES.S) + source set androidMain).

Por eso PeerTransportRouter.buildList llama a createWifiAwareTransport() — una factoría que devuelve null en iOS.

Dónde se sitúa Aware en la cadena de fallback {#where-aware-sits-in-the-fallback-chain}

El PeerTransportRouter de PlainApp es una lista ordenada. Para cada llamada send o downloadFile, recorre la lista y prueba cada transporte hasta que uno tiene éxito; los fallos se propagan hacia abajo.

Diagram 2
2

¿Por qué Aware es «el del medio» y no «el primero»?

Porque LAN casi siempre es más rápido cuando está disponible. Un salto Wi-Fi de misma subred a través de un AP es un único intercambio de tramas 802.11; una ruta de datos Aware añade una configuración NDP (~5 s en el primer uso) más un segundo contexto de radio Wi-Fi para el enlace dispositivo-a-dispositivo. Si ambos son alcanzables, LAN gana en latencia y rendimiento.

A la inversa, BLE es siempre más lento — pero funciona siempre que ambos dispositivos estén emparejados. Aware está en el medio: más rápido que BLE, más lento que LAN, y solo disponible en dispositivos Android 13+ con Wi-Fi activado.

Ciclo de vida de sesión: Attach → Publish + Subscribe {#session-lifecycle-attach--publish--subscribe}

Una sesión Wi-Fi Aware es a nivel de proceso. Hay exactamente una WifiAwareSession por dispositivo; dentro de ella, PlainApp ejecuta una sesión publish (para que los pares puedan descubrirnos) y una sesión subscribe (para que podamos descubrir pares). Ambas se inician en el momento en que AwareSession.start() completa el callback attach.

Diagram 3
3

¿Por qué publish Y subscribe en el mismo dispositivo?

El modelo de descubrimiento Wi-Fi Aware es asimétrico: un publicador anuncia un servicio, un suscriptor lo busca. Para hacer el descubrimiento simétrico (ambos dispositivos se descubren mutuamente), PlainApp hace ambos a la vez. Sin esto, el dispositivo A tendría que saber de antemano si es el publicador o el suscriptor para un par dado — pero los roles de par se determinan más tarde por comparación de clientId (véase Descubrimiento y asignación de roles).

Publicar y suscribir simultáneamente significa que cada dispositivo ve el onServiceDiscovered del otro (como suscriptor) Y recibe los mensajes hello del otro (como publicador) — ambas direcciones del apretón de manos están siempre disponibles.

Reinicio automático al terminar

Algunas variantes de Android (MIUI en particular) matan sesiones Aware de larga duración para ahorrar batería. PlainApp maneja esto en los callbacks onSessionTerminated: anula la sesión terminada y llama inmediatamente a publishOwnService / subscribeOwnService de nuevo en la WifiAwareSession que sigue attached. La sesión attach en sí no se pierde — solo la sesión de descubrimiento publish/subscribe. Los PeerHandle de antes de la terminación se vuelven obsoletos, por eso awaitPeerHandle comprueba la marca de tiempo discoveredAt y descarta manejadores más antiguos de 30 s.

Descubrimiento y asignación de roles {#discovery--role-assignment}

El protocolo de ruta de datos Wi-Fi Aware requiere que un lado actúe como publicador (servidor) y el otro como suscriptor (cliente). Ambas partes no pueden ser simultáneamente el iniciador — el framework rechaza solicitudes sin una contraparte coincidente.

PlainApp asigna roles deterministamente por par usando una simple comparación lexicográfica de clientIds:

Diagram 4
4

¿Por qué determinista y no negociado?

Un enfoque negociado (p. ej. «la MAC menor es el servidor») requeriría un intercambio de mensajes extra. La comparación lexicográfica es idempotente, simétrica y sin estado: ambos dispositivos calculan el mismo rol para el mismo par sin comunicación alguna. El clientId es un UUID corto de 13 caracteres, así que los empates (clientId == peer.id) solo ocurren al comparar un par consigo mismo — lo cual nunca llega al transporte.

El rol determina dos cosas aguas abajo:

  1. Quién conduce el bucle de reintento. Solo el cliente reintenta requestNetwork; el servidor hace exactamente un intento por cada hello recibido. Esto es crítico para la ventana de 500 ms (siguiente sección).
  2. Quién establece el puerto. El publicador llama a setPort(httpsPort) porque es quien acepta conexiones entrantes en el puerto de su servidor HTTPS. El suscriptor no establece puerto — aprende el puerto del par desde el WifiAwareNetworkInfo tras establecerse la ruta de datos.

El apretón de manos de dos fases (hello + ready) {#the-two-phase-handshake-hello--ready}

La parte más difícil de la configuración de ruta de datos Wi-Fi Aware es el sincronismo. El framework requiere que ambas partes llamen a connectivityManager.requestNetwork dentro de unos 500 ms entre sí — si una parte lo llama antes de que la otra haya registrado su solicitud coincidente, el framework lo rechaza inmediatamente con onUnavailable ("releaseRequestAsUnfulfillableByAnyFactory").

PlainApp lo resuelve con un apretón de manos de dos mensajes en la capa de aplicación que se ejecuta sobre el canal de mensajes L2 de Aware (la misma API sendMessage usada por onServiceDiscovered):

Diagram 5
5

¿Por qué dos mensajes (hello + ready) en vez de solo uno?

El hello por sí solo no basta por la asimetría de dirección. El suscriptor puede enviar hello en el instante en que descubre al publicador (en onServiceDiscovered), pero el publicador no puede iniciar requestNetwork hasta que tenga el PeerHandle del suscriptor, que solo conoce al recibir el hello. Así que el hello sirve para dos propósitos:

  1. Entregar el PeerHandle del suscriptor al publicador. El publicador lo necesita para construir el WifiAwareNetworkSpecifier.
  2. Señalar intención de conectar. Recibir el hello le dice al publicador «el suscriptor está a punto de requestNetwork, así que yo también debería».

El recibo ready existe para la dirección opuesta — para decirle al suscriptor «el publicador ha registrado su requestNetwork». Sin él, el requestNetwork del suscriptor podría adelantarse al del publicador y ser rechazado por el framework. El recibo ready es una señal no bloqueante: el suscriptor no la espera antes de llamar a requestNetwork (eso añadiría una ida y vuelta), pero si llega mientras el suscriptor está en estado IDLE (entre intentos de reintento), el suscriptor puede reintentar inmediatamente sin esperar el hueco de RETRY_DELAY_MS.

La asimetría del bucle de reintento

Esta es la parte más sutil del diseño. Solo el suscriptor reintenta. El publicador hace exactamente un intento de requestNetwork por cada hello recibido. Esto es porque:

  • Si ambas partes reintentaran de forma independiente, sus ciclos de reintento se desfasarían (distintas duraciones de delay(), distintas pausas de GC), y las dos llamadas requestNetwork rara vez coincidirían dentro de la ventana de 500 ms.
  • El bucle de reintento del suscriptor envía un hello fresco en cada intento, lo que re-dispara el buildLink del publicador vía publishHelloListeners. Esto garantiza que el requestNetwork del publicador siempre sigue al hello en ~50 ms, bien dentro de la ventana de 500 ms.

Esto está documentado en detalle en AwarePeerLink.build.

NDP requestNetwork — La ventana de 500 ms {#ndp-requestnetwork--the-500-ms-window}

La llamada requestNetwork es la operación más sensible al sincronismo del transporte Aware. Esto es lo que ocurre en cada lado:

Diagram 6
6

Qué significa el callback onUnavailable

onUnavailable se dispara cuando el framework rechaza el requestNetwork antes de encontrar una solicitud de par coincidente. El PeerHandle en sí sigue siendo válido — solo el emparejamiento NDP (Neighbor Discovery Protocol) falló porque la otra parte aún no había registrado. PlainApp deliberadamente no llama session.invalidatePeerHandle en este caso, porque invalidar el manejador descartaría la única señal de que onServiceDiscovered fue alguna vez llamado (se dispara una vez por par y por vida de la sesión subscribe). Con el manejador preservado, el reintento puede reutilizarlo en vez de esperar un descubrimiento fresco.

Lo mismo aplica al manejador del lado publicador desde onMessageReceived — el publicador mantiene la entrada publishPeerHandles[fromCid] entre intentos fallidos, así que el siguiente hello del suscriptor reutiliza el manejador en caché en vez de ser descartado.

Cada par emparejado obtiene su propio objeto AwarePeerLink, propiedad del AwareLinkPool a nivel de proceso. El pool gestiona eventos de descubrimiento, reutilización de enlaces y expulsión por inactividad.

Diagram 7
7

¿Por qué no auto-build al descubrir?

El pool explícitamente no construye un enlace cuando se dispara onServiceDiscovered. Esta es una decisión crítica: una cafetería concurrida podría tener 100 dispositivos PlainApp publicando todos el servicio «plain-peer». Si cada descubrimiento disparara un requestNetwork, el framework se inundaría de intentos de configuración NDP y la radio Wi-Fi se saturaría.

En su lugar, el pool solo registra el PeerHandle y espera una de:

  1. El usuario local envía un mensajeWifiAwareTransport.sendpool.buildLink(peer) (disparador del lado remitente).
  2. El par remoto envía un helloonPublishHelloReceivedbuildLink(peer) (disparador del lado receptor).
  3. El par remoto envía un readyonSubscribeReadyReceivedbuildLink(peer) (disparador del lado receptor).

Así, los enlaces solo se construyen para pares con los que el usuario está intercambiando realmente mensajes — no para cada dispositivo PlainApp en alcance de radio.

Barrido de inactividad

Cada 10 segundos, el pool recorre todos los enlaces y cierra cualquier cuyo lastActiveAt sea más antiguo de 60 segundos. Cada send y downloadFile llama a link.touch() para refrescar la marca de tiempo. Esto reclama el contexto de radio Wi-Fi y el pool de conexiones OkHttp para los pares con los que el usuario ha dejado de chatear — importante porque Android limita el número de rutas de datos Aware simultáneas a aproximadamente 4–10 (depende del dispositivo).

Direccionamiento IPv6 y el truco DNS de plain-aware-peer {#ipv6-addressing--the-plain-aware-peer-dns-trick}

Las rutas de datos Wi-Fi Aware usan IPv6 link-local únicamente. No hay IPv4, ni servidor DNS, ni DHCP. La dirección IPv6 del par se entrega vía el campo WifiAwareNetworkInfo.peerIpv6Addr en onCapabilitiesChanged — una dirección fe80::... que solo tiene sentido en la interfaz de red Aware.

PlainApp necesita enviar solicitudes HTTPS a esta dirección, pero el analizador de URLs https:// de OkHttp rechaza literales IPv6 puros en un hostname (https://[fe80::abcd]:8443/ funciona, pero enrutarlo a través de un resolver Dns personalizado es más limpio). El truco:

Diagram 8
8

¿Por qué un hostname centinela?

La alternativa — pasar el literal IPv6 directamente en la URL — requeriría que cada sitio de llamada supiera de la dirección link-local. Usando un hostname centinela, la construcción de la URL es idéntica para LAN y Aware: ambas producen una URL https://<host>:<port>/peer_graphql válida que OkHttp puede analizar. La única diferencia es la implementación Dns vinculada al cliente — LAN usa el DNS del sistema, Aware usa awareDns(peerIpv6) que devuelve la dirección link-local cacheada para el hostname centinela y cae a Dns.SYSTEM para cualquier otra cosa.

¿Por qué network.socketFactory?

El objeto Network de Android representa una interfaz de red específica (en este caso, la ruta de datos Aware). Al llamar a network.socketFactory y pasarlo a la configuración socketFactory de OkHttp, forzamos que todos los sockets TCP se creen en la interfaz Aware — no en la interfaz Wi-Fi o celular por defecto. Sin esto, el SO enrutaría la solicitud vía la red por defecto, donde el IPv6 link-local es inalcanzable, y la solicitud fallaría con ENETUNREACH.

Criptografía: derivación PMK y reutilización de ChaCha20 {#cryptography-pmk-derivation--chacha20-reuse}

Wi-Fi Aware soporta un PMK (Pairwise Master Key) opcional para la ruta de datos. Cuando se establece, el propio enlace L2 se cifra con ese PMK — la radio Wi-Fi gestiona el cifrado, sin cripto a nivel de aplicación.

PlainApp deriva el PMK de la misma clave compartida ChaCha20 que LanTransport y BleTransport usan para el cifrado a nivel de aplicación:

Diagram 9
9

¿Por qué truncar a 32 bytes?

El PMK de Wi-Fi Aware debe ser exactamente 32 bytes (256 bits). La clave compartida ChaCha20 del emparejamiento también es de 32 bytes en el caso normal, así que la rama raw.size == 32 es el camino común. El fallback de truncamiento/relleno maneja el caso (teórico) en que la clave se almacenó más corta — rellenar con ceros hasta 32 bytes es una medida defensiva, no algo que ocurra en la práctica con pares correctamente emparejados.

El sobre firmado es idéntico a LAN

Como createCryptoHttpClient es la misma factoría usada por LanTransport, la cripto L7 en Aware es byte a byte idéntica a LAN. El PeerGraphQLService del lado servidor no sabe (ni le importa) qué transporte entregó la solicitud — solo ve una carga útil GraphQL firmada y cifrada y la descifra con la clave compartida del par. Este es el principio «una base de código, muchos transportes» documentado en Arquitectura de Chat.

Ruta de envío de mensajes (de punta a punta) {#message-send-path-end-to-end}

Poniéndolo todo junto — qué ocurre cuando se envía un mensaje de chat por Wi-Fi Aware:

Diagram 10
10

Decisiones de diseño notables

  • Reutilización de conexión. A diferencia de BleTransport, que derriba la conexión GATT tras cada solicitud, WifiAwareTransport reutiliza la ruta de datos Aware para tantas solicitudes como haga el usuario dentro de la ventana de inactividad de 60 s. La primera solicitud paga el apretón de manos de ~400 ms; las solicitudes posteriores son ida y vuelta de ~10 ms.
  • La misma cripto que LAN. El interceptor ChaCha20 y el sobre firmado son byte-idénticos a LAN. El PeerGraphQLService del par no sabe qué transporte entregó la solicitud.
  • Sin apropiación ante fallo de enlace. Si buildLink falla, el transporte lanza TransportUnavailable y el enrutador cae a BLE. No hay reintento dentro de sendAwarePeerLink.build ya hace su propio bucle de reintento interno (con MAX_BUILD_ATTEMPTS = 1 en el cliente, más si el precalentador ha cebado ambos lados).

Ruta de descarga de archivos (de punta a punta) {#file-download-path-end-to-end}

Las descargas de archivos por Aware reutilizan la misma ruta de datos que los mensajes de chat, pero usan un cliente OkHttp separado configurado para streaming de archivos grandes:

Diagram 11
11

¿Por qué un cliente separado para descargas?

El cliente de chat (AwareHttpClientFactory.build) tiene un requestTimeoutMillis de 30 s — apropiado para mutaciones GraphQL pero catastrófico para una descarga de archivo de 100 MB. El cliente de descarga (buildFileDownload) establece:

  • connectTimeoutMillis = 10_000 (más largo que los 5 s del chat, más tolerante con un primer paquete lento en una ruta de datos fresca)
  • readTimeout = 120 s por lectura (frente al implícito por defecto de 10 s)
  • requestTimeoutMillis = 120_000 (2 minutos — suficiente para la mayoría de archivos)
  • retryOnConnectionFailure(true) — una lectura caída a mitad de descarga se reintenta en vez de fallar toda la transferencia

También omite el interceptor ChaCha20. El endpoint /fs sirve bytes de archivo en bruto (no un sobre GraphQL firmado), y el PMK L2 (cuando está presente) ya cifra el enlace de radio. Cifrar dos veces un vídeo de 50 MB con ChaCha20 en software malgastaría CPU y ralentizaría la transferencia.

Streaming, no almacenamiento en búfer

Como la ruta BLE, las descargas Aware hacen streaming del archivo a través de un ByteReadChannel — el archivo se escribe a un archivo temporal a medida que los bytes llegan, no se almacena en búfer en memoria. PeerFileDownloader lee fragmentos de 8 KB y emite eventos de progreso cada segundo. El mismo pipeline DownloadedResponse / PeerFileDownloader / DownloadQueue se reutiliza entre todos los transportes — específico del transporte solo el origen del channel.

Precalentamiento: arranque Aware disparado por BLE {#prewarming-ble-triggered-aware-startup}

La mayor latencia visible para el usuario en la ruta Aware es el primer apretón de manos — si ambas partes aún no han arrancado Aware, el primer mensaje del usuario tiene que esperar:

  1. Attach de la sesión Aware local (~1 s)
  2. Inicio de publish + subscribe local (~1 s)
  3. Arranque Aware del par remoto (~2 s por BLE)
  4. Descubrimiento mutuo (~1 s)
  5. Apretón de manos NDP (~400 ms)

Eso son ~5 segundos antes de enviar el primer byte. Para ocultar esta latencia, PeerTransportPrewarmer se ejecuta al entrar en ChatPage y dispara el arranque Aware del par remoto vía BLE:

Diagram 12
12

El doble papel de BLE

BLE sirve dos propósitos aquí:

  1. Leer el estado Aware actual del par (barato, sin conexión GATT — el byte0 del serviceData en la scan response transporta las banderas Aware).
  2. Disparar al par para que arranque Aware si lo soporta pero no lo está ejecutando actualmente. Esto va por la ruta regular BleTransport.send — una mutación GraphQL startAware cifrada con la clave ChaCha20 compartida, entregada vía RPC GATT al endpoint /peer_graphql del par.

Este es uno de los pocos lugares donde los transportes cooperan en vez de simplemente hacer fallback: BLE se usa para mejorar preemptivamente la sesión al transporte Aware más rápido, antes de que el usuario lo note.

¿Por qué un setAwareRunning(true) optimista?

La mutación startAware devuelve éxito tan pronto como el resolver del par remoto invoca WifiAwareTransport.start() — pero la sesión Aware aún no está realmente attached (onAttached se dispara asíncronamente). PlainApp marca al par como awareRunning = true optimistamente, porque:

  • Si realmente arrancó, el próximo send usará Aware (rápido).
  • Si no (p. ej. el Wi-Fi del par está apagado), el buildLink del próximo send fallará con TransportUnavailable y caerá a BLE naturalmente.
  • El coste de un falso positivo es un tiempo de espera de ~5 s, no un bloqueo permanente — PeerCircuitBreaker registra el fallo pero no abre la pata BLE (BLE solo se abre por sus propios fallos).

¿Por qué limitar a 30 s?

PeerTransportPrewarmer.prewarm(peerId) registra una marca de tiempo por par y se niega a re-ejecutar dentro de 30 s. Esto es porque el usuario navega atrás y adelante entre la lista de chat y la página de chat frecuentemente — sin throttling, cada navegación dispararía un escaneo BLE + mutación startAware, drenando batería y saturando la radio BLE. La ventana de 30 s es lo suficientemente corta para atrapar a un par que acaba de venir en línea (p. ej. el usuario abrió la app en el dispositivo remoto) pero suficientemente larga para evitar re-ejecuciones espurias.

Modos de fallo y la bandera de salto rápido {#failure-modes--the-fast-skip-flag}

Aware tiene más modos de fallo que cualquier otro transporte. La bandera de salto rápido isAwareRunning es la optimización más importante de todo el módulo — sin ella, cada send malgastaría 10 s en buildLink agotando el tiempo de espera antes de caer a BLE.

Diagram 13
13

La bandera isAwareRunning es el pivote

Sin este único booleano, cada send de Aware o bien:

  • Siempre intentaría buildLink → tiempo de espera de 10 s en cada send a un par cuyo Aware no está corriendo.
  • Siempre saltaría Aware → nunca lo usaría incluso cuando ambas partes lo tienen corriendo.

La bandera se refresca desde dos fuentes, en orden de autoridad:

  1. Scan response BLE (barato, sin conexión GATT) — establecida por PeerTransportPrewarmer.refreshAwareFlagFromScan. El par anuncia su estado Aware en la carga útil serviceData de 9 bytes (byte0 bitfield).
  2. Respuesta DISCOVER GATT (autoritativa) — establecida por PairingTransport.scanAndDiscover cuando ocurre un descubrimiento completo. Esto sobreescribe la pista de escaneo.

Cuando es falsa, WifiAwareTransport.send y downloadFile lanzan TransportUnavailable inmediatamente — sin escaneo, sin apretón de manos, sin tiempo de espera. El enrutador cae a BLE en microsegundos.

Referencia de constantes clave {#key-constants-reference}

ConstanteValorDóndePropósito
AwareSession.SERVICE_NAME"plain-peer"DescubrimientoNombre de servicio publicado y suscrito por cada dispositivo PlainApp
AwareSession.PEER_HANDLE_MAX_AGE_MS30 000Caché de PeerHandleDescartar manejadores obsoletos (la sesión publish del par puede haberse reiniciado)
AwareSession.READY_TIMEOUT_MS15 000Apretón de manosEspera del suscriptor del recibo ready del publicador
AwareSession.MSG_HELLO0Apretón de manosID de mensaje Suscriptor → Publicador
AwareSession.MSG_READY1Apretón de manosID de mensaje Publicador → Suscriptor
AwarePeerLink.MAX_BUILD_ATTEMPTS1Apretón de manos (solo cliente)Intento único — era 3, ahora 1 porque el precalentador ceba ambos lados
AwarePeerLink.ATTEMPT_TIMEOUT_MS5 000Apretón de manosTiempo de espera por intento — era 10 s, reducido a la mitad para acelerar el fallback
AwarePeerLink.RETRY_DELAY_MS500Apretón de manosRetardo entre intentos de reintento (solo cliente)
AwarePeerLink.REQUEST_TIMEOUT_MS30 000NDPTiempo de espera de connectivityManager.requestNetwork
AwareLinkPool.IDLE_TIMEOUT_MS60 000Barrido de poolCerrar enlaces inactivos tras 60 s de inactividad
AwareLinkPool.IDLE_SWEEP_INTERVAL_MS10 000Barrido de poolIntervalo de barrido
AwareHttpClientFactory.AWARE_HOST"plain-aware-peer"DNSHostname centinela resuelto al IPv6 del par por un Dns personalizado
build cliente de chatconnectTimeout 5 s, requestTimeout 30 s, interceptor ChaCha20
buildFileDownloadconnectTimeout 10 s, readTimeout 120 s, requestTimeout 120 s, sin cripto
PeerTransportPrewarmer.PREWARM_TTL_MS30 000PrecalentamientoThrottle por par
PeerTransportPrewarmer.BLE_SCAN_TIMEOUT_MS15 000PrecalentamientoTiempo de espera de escaneo BLE para refreshAwareFlagFromScan
PeerCircuitBreaker.WINDOW_MS30 000DisyuntorDuración abierta tras el umbral
PeerCircuitBreaker.MAX_FAILURES2DisyuntorFallos dentro de la ventana para abrir
TempData.httpsPort8443 (por defecto)ServidorPuerto del publicador anunciado vía WifiAwareNetworkSpecifier.setPort
BleServiceData.AWARE_SUPPORTED0x01Scan response BLEBit que indica que el par soporta Wi-Fi Aware
BleServiceData.AWARE_RUNNING0x02Scan response BLEBit que indica que el servicio Aware del par está actualmente corriendo

Resumen de contrapartidas de diseño {#design-trade-offs-recap}

Diagram 14
14

Lecturas adicionales

  • Arquitectura de Chat — cómo encaja WifiAwareTransport en la cadena de fallback LAN → Aware → BLE y el pipeline más amplio de envío/recepción de chat.
  • Transporte BLE — el transporte de último recurso que toma el relevo cuando Aware no está disponible; también el canal usado por el precalentador para disparar el arranque Aware en el par remoto.
  • Flujo de emparejamiento — cómo se establece la clave ChaCha20 compartida reutilizada como PMK de Aware, y cómo se rellenan las banderas de scan response BLE (AWARE_SUPPORTED / AWARE_RUNNING).