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?
- Dónde se sitúa Aware en la cadena de fallback
- Ciclo de vida de sesión: Attach → Publish + Subscribe
- Descubrimiento y asignación de roles
- El apretón de manos de dos fases (hello + ready)
- NDP requestNetwork — La ventana de 500 ms
- Pool de enlaces por par y barrido de inactividad
- Direccionamiento IPv6 y el truco DNS de
plain-aware-peer - Criptografía: derivación PMK y reutilización de ChaCha20
- Ruta de envío de mensajes (de punta a punta)
- Ruta de descarga de archivos (de punta a punta)
- Precalentamiento: arranque Aware disparado por BLE
- Modos de fallo y la bandera de salto rápido
- Referencia de constantes clave
- Resumen de contrapartidas de diseño
¿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.)
Restricciones de plataforma
Wi-Fi Aware es exclusivo de Android en PlainApp:
- Android 13 (API 33) es el mínimo — las sobrecargas
WifiAwareNetworkSpecifier.BuilderconsetPort()ysetPmk()de las que depende PlainApp requierenisTPlus(). - iOS no expone Wi-Fi Aware a apps de terceros. iOS PlainApp cae directamente
de LAN a BLE; el objeto
WifiAwareTransportni 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.
¿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.
¿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:
¿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:
- 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). - 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 elWifiAwareNetworkInfotras 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):
¿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:
- Entregar el PeerHandle del suscriptor al publicador. El publicador lo
necesita para construir el
WifiAwareNetworkSpecifier. - 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 llamadasrequestNetworkrara 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
buildLinkdel publicador víapublishHelloListeners. Esto garantiza que elrequestNetworkdel 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:
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.
Pool de enlaces por par y barrido de inactividad {#per-peer-link-pool--idle-sweeping}
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.
¿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:
- El usuario local envía un mensaje →
WifiAwareTransport.send→pool.buildLink(peer)(disparador del lado remitente). - El par remoto envía un hello →
onPublishHelloReceived→buildLink(peer)(disparador del lado receptor). - El par remoto envía un ready →
onSubscribeReadyReceived→buildLink(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:
¿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:
¿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:
Decisiones de diseño notables
- Reutilización de conexión. A diferencia de
BleTransport, que derriba la conexión GATT tras cada solicitud,WifiAwareTransportreutiliza 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
PeerGraphQLServicedel par no sabe qué transporte entregó la solicitud. - Sin apropiación ante fallo de enlace. Si
buildLinkfalla, el transporte lanzaTransportUnavailabley el enrutador cae a BLE. No hay reintento dentro desend—AwarePeerLink.buildya hace su propio bucle de reintento interno (conMAX_BUILD_ATTEMPTS = 1en 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:
¿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 spor 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:
- Attach de la sesión Aware local (~1 s)
- Inicio de publish + subscribe local (~1 s)
- Arranque Aware del par remoto (~2 s por BLE)
- Descubrimiento mutuo (~1 s)
- 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:
El doble papel de BLE
BLE sirve dos propósitos aquí:
- Leer el estado Aware actual del par (barato, sin conexión GATT — el
byte0 del
serviceDataen la scan response transporta las banderas Aware). - 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 GraphQLstartAwarecifrada con la clave ChaCha20 compartida, entregada vía RPC GATT al endpoint/peer_graphqldel 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
sendusará Aware (rápido). - Si no (p. ej. el Wi-Fi del par está apagado), el
buildLinkdel próximosendfallará conTransportUnavailabley caerá a BLE naturalmente. - El coste de un falso positivo es un tiempo de espera de ~5 s, no un bloqueo
permanente —
PeerCircuitBreakerregistra 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.
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:
- Scan response BLE (barato, sin conexión GATT) — establecida por
PeerTransportPrewarmer.refreshAwareFlagFromScan. El par anuncia su estado Aware en la carga útilserviceDatade 9 bytes (byte0 bitfield). - Respuesta DISCOVER GATT (autoritativa) — establecida por
PairingTransport.scanAndDiscovercuando 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}
| Constante | Valor | Dónde | Propósito |
|---|---|---|---|
AwareSession.SERVICE_NAME | "plain-peer" | Descubrimiento | Nombre de servicio publicado y suscrito por cada dispositivo PlainApp |
AwareSession.PEER_HANDLE_MAX_AGE_MS | 30 000 | Caché de PeerHandle | Descartar manejadores obsoletos (la sesión publish del par puede haberse reiniciado) |
AwareSession.READY_TIMEOUT_MS | 15 000 | Apretón de manos | Espera del suscriptor del recibo ready del publicador |
AwareSession.MSG_HELLO | 0 | Apretón de manos | ID de mensaje Suscriptor → Publicador |
AwareSession.MSG_READY | 1 | Apretón de manos | ID de mensaje Publicador → Suscriptor |
AwarePeerLink.MAX_BUILD_ATTEMPTS | 1 | Apretón de manos (solo cliente) | Intento único — era 3, ahora 1 porque el precalentador ceba ambos lados |
AwarePeerLink.ATTEMPT_TIMEOUT_MS | 5 000 | Apretón de manos | Tiempo de espera por intento — era 10 s, reducido a la mitad para acelerar el fallback |
AwarePeerLink.RETRY_DELAY_MS | 500 | Apretón de manos | Retardo entre intentos de reintento (solo cliente) |
AwarePeerLink.REQUEST_TIMEOUT_MS | 30 000 | NDP | Tiempo de espera de connectivityManager.requestNetwork |
AwareLinkPool.IDLE_TIMEOUT_MS | 60 000 | Barrido de pool | Cerrar enlaces inactivos tras 60 s de inactividad |
AwareLinkPool.IDLE_SWEEP_INTERVAL_MS | 10 000 | Barrido de pool | Intervalo de barrido |
AwareHttpClientFactory.AWARE_HOST | "plain-aware-peer" | DNS | Hostname centinela resuelto al IPv6 del par por un Dns personalizado |
build cliente de chat | connectTimeout 5 s, requestTimeout 30 s, interceptor ChaCha20 | ||
buildFileDownload | connectTimeout 10 s, readTimeout 120 s, requestTimeout 120 s, sin cripto | ||
PeerTransportPrewarmer.PREWARM_TTL_MS | 30 000 | Precalentamiento | Throttle por par |
PeerTransportPrewarmer.BLE_SCAN_TIMEOUT_MS | 15 000 | Precalentamiento | Tiempo de espera de escaneo BLE para refreshAwareFlagFromScan |
PeerCircuitBreaker.WINDOW_MS | 30 000 | Disyuntor | Duración abierta tras el umbral |
PeerCircuitBreaker.MAX_FAILURES | 2 | Disyuntor | Fallos dentro de la ventana para abrir |
TempData.httpsPort | 8443 (por defecto) | Servidor | Puerto del publicador anunciado vía WifiAwareNetworkSpecifier.setPort |
BleServiceData.AWARE_SUPPORTED | 0x01 | Scan response BLE | Bit que indica que el par soporta Wi-Fi Aware |
BleServiceData.AWARE_RUNNING | 0x02 | Scan response BLE | Bit que indica que el servicio Aware del par está actualmente corriendo |
Resumen de contrapartidas de diseño {#design-trade-offs-recap}
Lecturas adicionales
- Arquitectura de Chat — cómo encaja
WifiAwareTransporten la cadena de fallbackLAN → Aware → BLEy 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).