Voltar ao blog
Transport19 min read

Design do Transporte Wi-Fi Aware — Descoberta de Vizinhos e Caminhos de Dados

Este artigo explica como o PlainApp usa Wi-Fi Aware (NAN — Neighbor Awareness Networking) como a camada intermediária da sua cadeia de fallback de transporte entre pares, entre a LAN (HTTPS na mesma sub-rede) e o BLE (RPC GATT de último recurso). O Wi-Fi Aware é o que faz dois dispositivos PlainApp conversarem quando estão em SSIDs diferentes, VLAN de visitantes vs IoT, ou sem qualquer infraestrutura Wi-Fi — sem jamais precisar de um endereço IP de um servidor DHCP.

O artigo cobre o ciclo de vida da sessão Aware (apenas Android), o modelo de descoberta publish / subscribe, o handshake de duas fases com divisão de papéis que sincroniza o requestNetwork em ambos os lados dentro da janela de ~500 ms do framework, o pool de links por par com limpeza de ociosos, o truque do IPv6 + DNS customizado que permite a um único cliente OkHttp servir tanto LAN quanto Aware, e o prewarmer que dispara a inicialização do Aware do par via BLE.

Para a cadeia de fallback mais ampla, consulte Arquitetura de Chat. Para o transporte BLE que assume quando o Aware está indisponível, consulte Transporte BLE. Para saber como a chave ChaCha20 compartilhada reutilizada como PMK do Aware é estabelecida, consulte Fluxo de Pareamento.

Sumário

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

O Wi-Fi Aware (IEEE 802.11bc, antigamente NAN — Neighbor Awareness Networking) é uma certificação da Wi-Fi Alliance que permite a dois dispositivos se descobrirem e trocar dados sem qualquer infraestrutura Wi-Fi — sem AP, sem router, sem DHCP. O PlainApp o usa em dois cenários que a LAN não consegue cobrir:

  • SSIDs / VLANs diferentes. Um telefone na rede de visitantes e um laptop na VLAN IoT estão ambos "online" via Wi-Fi, mas não conseguem alcançar o IP um do outro. O Aware cria um caminho de dados direto dispositivo-a-dispositivo que ignora completamente a infraestrutura.
  • Sem infraestrutura alguma. Dois dispositivos no meio do nada com o Wi-Fi ligado, mas sem AP, ainda assim conseguem conversar. (O BLE também cobre isso, mas o Aware é muito mais rápido — ~10 ms de ida e volta vs segundos, e MB/s vs dezenas de KB/s.)

Diagram 1
1

Restrições de plataforma

O Wi-Fi Aware é apenas Android no PlainApp:

  • Android 13 (API 33) é o mínimo — as sobrecargas de WifiAwareNetworkSpecifier.Builder com setPort() e setPmk() das quais o PlainApp depende requerem isTPlus().
  • O iOS não expõe o Wi-Fi Aware para apps de terceiros. O PlainApp no iOS cai diretamente da LAN para o BLE; o objeto WifiAwareTransport nem é compilado para o target iOS (@RequiresApi(Build.VERSION_CODES.S) + source set androidMain).

É por isso que o PeerTransportRouter.buildList chama createWifiAwareTransport() — uma fábrica que retorna null no iOS.

Onde o Aware Fica na Cadeia de Fallback {#where-aware-sits-in-the-fallback-chain}

O PeerTransportRouter do PlainApp é uma lista ordenada. Para cada chamada de send ou downloadFile, ele percorre a lista e tenta cada transporte até um deles ter sucesso; falhas cascateiam para baixo.

Diagram 2
2

Por que o Aware é "o meio" e não "o primeiro"?

Porque a LAN é quase sempre mais rápida quando disponível. Um salto Wi-Fi na mesma sub-rede por um AP é uma única troca de quadro 802.11; um data path do Aware adiciona um setup de NDP (~5 s no primeiro uso) mais um segundo contexto de rádio Wi-Fi para o link dispositivo-a-dispositivo. Se ambos são alcançáveis, a LAN vence em latência e vazão.

Por outro lado, o BLE é sempre mais lento — mas funciona sempre que ambos os dispositivos estão pareados. O Aware fica no meio: mais rápido que o BLE, mais lento que a LAN, e disponível apenas em dispositivos Android 13+ com o Wi-Fi ligado.

Ciclo de Vida da Sessão: Attach → Publish + Subscribe {#session-lifecycle-attach--publish--subscribe}

Uma sessão Wi-Fi Aware é de processo inteiro. Há exatamente um WifiAwareSession por dispositivo; dentro dela, o PlainApp roda uma sessão de publish (para os pares nos descobrirem) e uma sessão de subscribe (para descobrirmos os pares). Ambas são iniciadas no momento em que AwareSession.start() completa o callback de attach.

Diagram 3
3

Por que publicar E subscrever no mesmo dispositivo?

O modelo de descoberta do Wi-Fi Aware é assimétrico: um publisher anuncia um serviço, um subscriber procura por ele. Para tornar a descoberta simétrica (ambos os dispositivos se descobrem), o PlainApp faz ambos ao mesmo tempo. Sem isso, o dispositivo A teria que saber de antemão se é o publisher ou o subscriber para um dado par — mas os papéis dos pares são determinados depois pela comparação de clientId (consulte Descoberta e Atribuição de Papéis).

Publicar e subscrever simultaneamente significa que cada dispositivo vê o onServiceDiscovered do outro (como subscriber) E recebe as mensagens hello do outro (como publisher) — ambas as direções do handshake estão sempre disponíveis.

Reinício automático no término

Algumas variantes do Android (MIUI em particular) matam sessões Aware de longa duração para economizar bateria. O PlainApp trata isso nos callbacks de onSessionTerminated: ele anula a sessão terminada e chama imediatamente publishOwnService / subscribeOwnService de novo na WifiAwareSession que ainda está attached. A sessão de attach em si não é perdida — apenas a sessão de descoberta publish/subscribe. Peer handles de antes do término ficam obsoletos, por isso o awaitPeerHandle verifica o timestamp de discoveredAt e descarta handles mais velhos que 30 s.

Descoberta e Atribuição de Papéis {#discovery--role-assignment}

O protocolo de data path do Wi-Fi Aware exige que um lado atue como publisher (servidor) e o outro como subscriber (cliente). Ambos os lados não podem ser, simultaneamente, o initiator — o framework rejeita requisições sem uma contraparte correspondente.

O PlainApp atribui papéis deterministicamente por par usando uma simples comparação lexicográfica de clientIds:

Diagram 4
4

Por que determinístico e não negociado?

Uma abordagem negociada (por exemplo, "MAC menor é o servidor") exigiria uma troca de mensagens extra. A comparação lexicográfica é idempotente, simétrica e stateless: ambos os dispositivos computam o mesmo papel para o mesmo par sem qualquer comunicação. O clientId é um UUID curto de 13 caracteres, então empates (clientId == peer.id) só acontecem ao comparar um par com ele mesmo — o que nunca chega ao transporte.

O papel determina duas coisas downstream:

  1. Quem conduz o loop de retry. Apenas o cliente retenta o requestNetwork; o servidor faz exatamente uma tentativa por hello recebido. Isso é crítico para a janela de 500 ms (próxima seção).
  2. Quem define a porta. O publisher chama setPort(httpsPort) porque é ele quem aceita conexões de entrada na porta do seu servidor HTTPS. O subscriber não define porta — ele aprende a porta do par a partir do WifiAwareNetworkInfo depois que o data path é estabelecido.

O Handshake de Duas Fases (hello + ready) {#the-two-phase-handshake-hello--ready}

A parte mais difícil do setup de data path do Wi-Fi Aware é o timing. O framework exige que ambos os lados chamem connectivityManager.requestNetwork dentro de aproximadamente 500 ms um do outro — se um lado chamar antes de o outro ter registrado sua requisição correspondente, o framework a rejeita imediatamente com onUnavailable ("releaseRequestAsUnfulfillableByAnyFactory").

O PlainApp resolve isso com um handshake de duas mensagens em camada de aplicação que roda sobre o canal de mensagens L2 do Aware (a mesma API sendMessage usada por onServiceDiscovered):

Diagram 5
5

Por que duas mensagens (hello + ready) em vez de apenas uma?

O hello sozinho não basta por causa da assimetria de direção. O subscriber pode enviar o hello no instante em que descobre o publisher (em onServiceDiscovered), mas o publisher não consegue iniciar o requestNetwork até ter o PeerHandle do subscriber, que só aprende ao receber o hello. Então o hello serve a dois propósitos:

  1. Entregar o PeerHandle do subscriber ao publisher. O publisher precisa dele para construir o WifiAwareNetworkSpecifier.
  2. Sinalizar intenção de conectar. Receber o hello diz ao publisher "o subscriber está prestes a fazer requestNetwork, então eu deveria também."

O recibo ready existe para a direção oposta — para dizer ao subscriber "o publisher registrou seu requestNetwork." Sem ele, o requestNetwork do subscriber poderia correr à frente do publisher e ser rejeitado pelo framework. O recibo ready é um sinal não bloqueante: o subscriber não espera por ele antes de chamar requestNetwork (isso adicionaria uma ida e volta), mas se ele chegar enquanto o subscriber está no estado IDLE (entre tentativas de retry), o subscriber pode repetir imediatamente sem esperar o intervalo de RETRY_DELAY_MS.

A assimetria do loop de retry

Essa é a parte mais sutil do design. Apenas o subscriber retenta. O publisher faz exatamente uma tentativa de requestNetwork por hello recebido. Isso porque:

  • Se ambos os lados retentassem independentemente, seus ciclos de retry sairiam de fase (durações diferentes de delay(), pausas diferentes de GC), e as duas chamadas de requestNetwork raramente se sobrepõoriam dentro da janela de 500 ms.
  • O loop de retry do subscriber envia um hello novo a cada tentativa, o que reativa o buildLink do publisher via publishHelloListeners. Isso garante que o requestNetwork do publisher sempre siga o hello em ~50 ms, bem dentro da janela de 500 ms.

Isso está documentado em detalhes em AwarePeerLink.build.

NDP requestNetwork — A Janela de 500 ms {#ndp-requestnetwork--the-500-ms-window}

A chamada requestNetwork é a operação mais sensível a timing no transporte Aware. Veja o que acontece em cada lado:

Diagram 6
6

O que o callback onUnavailable significa

onUnavailable dispara quando o framework rejeita o requestNetwork antes de encontrar uma requisição de par correspondente. O PeerHandle em si ainda é válido — apenas o pareamento NDP (Neighbor Discovery Protocol) falhou porque o outro lado ainda não tinha registrado. O PlainApp deliberadamente não chama session.invalidatePeerHandle nesse caso, porque invalidar o handle descartaria o único sinal de que onServiceDiscovered foi chamado (ele dispara uma vez por par por tempo de vida da sessão de subscribe). Com o handle preservado, o retry pode reutilizá-lo em vez de esperar por uma descoberta nova.

O mesmo vale para o handle do lado do publisher vindo de onMessageReceived — o publisher mantém a entrada publishPeerHandles[fromCid] entre tentativas falhadas, de modo que o próximo hello do subscriber reutiliza o handle em cache em vez de ser descartado.

Cada par pareado tem seu próprio objeto AwarePeerLink, pertencente ao AwareLinkPool de processo inteiro. O pool trata eventos de descoberta, reuso de link e expulsão de ociosos.

Diagram 7
7

Por que não auto-build na descoberta?

O pool explicitamente não constrói um link quando onServiceDiscovered dispara. Essa é uma decisão crítica: uma cafeteria movimentada pode ter 100 dispositivos PlainApp publicando o serviço "plain-peer". Se cada descoberta disparasse um requestNetwork, o framework seria inundado de tentativas de setup NDP e o rádio Wi-Fi seria saturado.

Em vez disso, o pool apenas registra o PeerHandle e espera por um de:

  1. O usuário local envia uma mensagem → WifiAwareTransport.send → pool.buildLink(peer) (gatilho do lado remetente).
  2. O par remoto envia um hello → onPublishHelloReceived → buildLink(peer) (gatilho do lado receptor).
  3. O par remoto envia um ready → onSubscribeReadyReceived → buildLink(peer) (gatilho do lado receptor).

Assim, links são construídos apenas para pares com os quais o usuário está efetivamente trocando mensagens — não para todo dispositivo PlainApp no alcance do rádio.

Limpeza de ociosos

A cada 10 segundos, o pool percorre todos os links e fecha qualquer um cujo lastActiveAt seja mais velho que 60 segundos. Cada send e downloadFile chama link.touch() para atualizar o timestamp. Isso recupera o contexto de rádio Wi-Fi e o pool de conexões OkHttp para pares com os quais o usuário parou de conversar — importante porque o Android limita o número de data paths Aware simultâneos a aproximadamente 4–10 (depende do dispositivo).

Endereçamento IPv6 e o Truque de DNS plain-aware-peer {#ipv6-addressing--the-plain-aware-peer-dns-trick}

Data paths do Wi-Fi Aware usam apenas IPv6 link-local. Não há IPv4, nem servidor DNS, nem DHCP. O endereço IPv6 do par é entregue via o campo WifiAwareNetworkInfo.peerIpv6Addr em onCapabilitiesChanged — um endereço fe80::... que só é significativo na interface de rede do Aware.

O PlainApp precisa enviar requisições HTTPS para esse endereço, mas o parsing de URLs https:// do OkHttp recusa literais IPv6 brutos como hostname (https://[fe80::abcd]:8443/ funciona, mas roteá-lo por um resolver de Dns customizado é mais limpo). O truque:

Diagram 8
8

Por que um hostname sentinela?

A alternativa — passar o literal IPv6 direto na URL — exigiria que todo ponto de chamada soubesse do endereço link-local. Ao usar um hostname sentinela, a construção da URL é idêntica para LAN e Aware: ambas produzem uma URL https://<host>:<port>/peer_graphql válida que o OkHttp consegue fazer parse. A única diferença é a implementação de Dns vinculada ao cliente — a LAN usa o DNS do sistema, o Aware usa awareDns(peerIpv6) que retorna o endereço link-local em cache para o hostname sentinela e cai para Dns.SYSTEM para qualquer outra coisa.

Por que network.socketFactory?

O objeto Network do Android representa uma interface de rede específica (neste caso, o data path do Aware). Ao chamar network.socketFactory e passá-lo para a configuração socketFactory do OkHttp, forçamos todos os sockets TCP a serem criados na interface do Aware — não na interface Wi-Fi ou celular padrão. Sem isso, o SO rotearia a requisição pela rede padrão, onde o IPv6 link-local é inalcançável, e a requisição falharia com ENETUNREACH.

Criptografia: Derivação de PMK e Reuso de ChaCha20 {#cryptography-pmk-derivation--chacha20-reuse}

O Wi-Fi Aware suporta um PMK (Pairwise Master Key) opcional para o data path. Quando definido, o link L2 em si é criptografado com aquele PMK — o rádio Wi-Fi trata da criptografia, sem crypto em camada de aplicação.

O PlainApp deriva o PMK da mesma chave compartilhada ChaCha20 que LanTransport e BleTransport usam para criptografia em camada de aplicação:

Diagram 9
9

Por que truncar para 32 bytes?

O PMK do Wi-Fi Aware deve ser exatamente 32 bytes (256 bits). A chave ChaCha20 compartilhada do pareamento também é 32 bytes no caso normal, então o branch raw.size == 32 é o caminho comum. O fallback de truncamento/padding trata o caso (teórico) em que a chave foi armazenada mais curta — preencher com zeros até 32 bytes é uma medida defensiva, não algo que acontece na prática com pares devidamente pareados.

O envelope assinado é idêntico ao da LAN

Como createCryptoHttpClient é a mesma fábrica usada por LanTransport, a crypto L7 no Aware é byte a byte idêntica à LAN. O PeerGraphQLService do lado servidor não sabe (nem se importa) qual transporte entregou a requisição — ele apenas vê um payload GraphQL assinado e criptografado e o descriptografa com a chave compartilhada do par. Esse é o princípio "uma base de código, muitos transportes" documentado em Arquitetura de Chat.

Caminho de Envio de Mensagens (Ponta a Ponta) {#message-send-path-end-to-end}

Juntando tudo — o que acontece quando uma mensagem de chat é enviada via Wi-Fi Aware:

Diagram 10
10

Escolhas de design notáveis

  • Reuso de conexão. Ao contrário do BleTransport, que encerra a conexão GATT após cada requisição, o WifiAwareTransport reutiliza o data path do Aware para tantas requisições quanto o usuário fizer dentro da janela de ociosidade de 60 s. A primeira requisição paga o handshake de ~400 ms; requisições subsequentes são ~10 ms de ida e volta.
  • Mesma crypto da LAN. O interceptador ChaCha20 e o envelope assinado são byte a byte idênticos à LAN. O PeerGraphQLService do par não sabe qual transporte entregou a requisição.
  • Sem preempção em falha de link. Se buildLink falhar, o transporte lança TransportUnavailable e o router cai para o BLE. Não há retry dentro de send — AwarePeerLink.build já faz seu próprio loop interno de retry (com MAX_BUILD_ATTEMPTS = 1 no cliente, mais se o prewarmer tiver preparado ambos os lados).

Caminho de Download de Arquivos (Ponta a Ponta) {#file-download-path-end-to-end}

Downloads de arquivos via Aware reutilizam o mesmo data path das mensagens de chat, mas usam um cliente OkHttp separado configurado para streaming de arquivos grandes:

Diagram 11
11

Por que um cliente separado para downloads?

O cliente de chat (AwareHttpClientFactory.build) tem um requestTimeoutMillis de 30 s — apropriado para mutações GraphQL, mas catastrófico para um download de arquivo de 100 MB. O cliente de download (buildFileDownload) define:

  • connectTimeoutMillis = 10_000 (maior que os 5 s do chat, mais tolerante a um primeiro pacote lento num data path novo)
  • readTimeout = 120 s por leitura (vs o padrão implícito de 10 s)
  • requestTimeoutMillis = 120_000 (2 minutos — suficiente para a maioria dos arquivos)
  • retryOnConnectionFailure(true) — uma leitura interrompida no meio do download é repetida em vez de falhar a transferência inteira

Ele também omite o interceptador ChaCha20. O endpoint /fs serve bytes de arquivo brutos (não um envelope GraphQL assinado), e o PMK L2 (quando presente) já criptografa o link de rádio. Criptografar duas vezes um vídeo de 50 MB com ChaCha20 em software desperdiçaria CPU e lentificaria a transferência.

Streaming, sem bufferização

Como no caminho BLE, downloads Aware fazem streaming do arquivo por um ByteReadChannel — o arquivo é gravado em um arquivo temporário conforme os bytes chegam, não bufferizado em memória. O PeerFileDownloader lê chunks de 8 KB e emite eventos de progresso a cada segundo. O mesmo pipeline DownloadedResponse / PeerFileDownloader / DownloadQueue é reutilizado entre todos os transportes — específico do transporte é apenas a fonte do channel.

Prewarming: Inicialização do Aware Disparada por BLE {#prewarming-ble-triggered-aware-startup}

A maior latência visível ao usuário no caminho Aware é o primeiro handshake — se ambos os lados ainda não iniciaram o Aware, a primeira mensagem do usuário precisa esperar por:

  1. Attach da sessão Aware local (~1 s)
  2. Início do publish + subscribe local (~1 s)
  3. Inicialização do Aware no par remoto (~2 s via BLE)
  4. Descoberta mútua (~1 s)
  5. Handshake NDP (~400 ms)

São ~5 segundos antes de o primeiro byte ser enviado. Para esconder essa latência, o PeerTransportPrewarmer roda na entrada do ChatPage e dispara a inicialização do Aware do par remoto via BLE:

Diagram 12
12

O papel duplo do BLE

O BLE tem dois papéis aqui:

  1. Ler o estado atual do Aware do par (barato, sem GATT connect — o serviceData da scan response no byte0 carrega as flags do Aware).
  2. Disparar o par para iniciar o Aware, se ele suporta mas não está rodando no momento. Isso passa pelo caminho regular de BleTransport.send — uma mutação GraphQL startAware criptografada com a chave ChaCha20 compartilhada, entregue via RPC GATT ao endpoint /peer_graphql do par.

Este é um dos poucos lugares em que os transportes cooperam em vez de apenas cair para o próximo: o BLE é usado para preemptivamente atualizar a sessão para o transporte Aware mais rápido, antes mesmo de o usuário perceber.

Por que setAwareRunning(true) otimista?

A mutação startAware retorna sucesso assim que o resolver do par remoto invoca WifiAwareTransport.start() — mas a sessão Aware ainda não está attached de fato (onAttached dispara assincronamente). O PlainApp marca o par como awareRunning = true otimisticamente, porque:

  • Se ela realmente iniciou, o próximo send usará o Aware (rápido).
  • Se não (por exemplo, o Wi-Fi do par está desligado), o buildLink do próximo send falhará com TransportUnavailable e cairá para o BLE naturalmente.
  • O custo de um falso positivo é um timeout de ~5 s, não um bloqueio permanente — o PeerCircuitBreaker registra a falha, mas não abre a perna do BLE (o BLE só abre com suas próprias falhas).

Por que throttling para 30 s?

PeerTransportPrewarmer.prewarm(peerId) registra um timestamp por par e se recusa a re-executar dentro de 30 s. Isso é porque o usuário navega para frente e para trás entre a lista de chats e a página de chat com frequência — sem throttling, toda navegação dispararia um scan BLE + mutação startAware, drenando bateria e bombardeando o rádio BLE. A janela de 30 s é curta o suficiente para capturar um par que acabou de ficar online (por exemplo, o usuário abriu o app no dispositivo remoto), mas longa o suficiente para evitar re-execuções espúrias.

Modos de Falha e a Flag de Skip Rápido {#failure-modes--the-fast-skip-flag}

O Aware tem mais modos de falha do que qualquer outro transporte. A flag de skip rápido isAwareRunning é a otimização mais importante de todo o módulo — sem ela, todo send desperdiçaria 10 s em buildLink dando timeout antes de cair para o BLE.

Diagram 13
13

A flag isAwareRunning é o pilar

Sem esse único booleano, todo send do Aware ou:

  • Sempre tentaria buildLink → 10 s de timeout em todo send para um par cujo Aware não está rodando.
  • Sempre pularia o Aware → nunca o usaria, mesmo quando ambos os lados o têm rodando.

A flag é atualizada a partir de duas fontes, em ordem de autoridade:

  1. Scan response do BLE (barato, sem GATT connect) — definida por PeerTransportPrewarmer.refreshAwareFlagFromScan. O par anuncia seu estado Aware no payload serviceData de 9 bytes (bitfield no byte0).
  2. Resposta DISCOVER via GATT (autoritativa) — definida por PairingTransport.scanAndDiscover quando uma descoberta completa acontece. Isso sobrescreve a dica de scan.

Quando falsa, WifiAwareTransport.send e downloadFile lançam TransportUnavailable imediatamente — sem scan, sem handshake, sem timeout. O router cai para o BLE em microssegundos.

Referência de Constantes-Chave {#key-constants-reference}

ConstanteValorOndePropósito
AwareSession.SERVICE_NAME"plain-peer"DescobertaNome do serviço publicado e subscrito por todo dispositivo PlainApp
AwareSession.PEER_HANDLE_MAX_AGE_MS30 000Cache de PeerHandleDescarta handles obsoletos (a sessão de publish do par pode ter reiniciado)
AwareSession.READY_TIMEOUT_MS15 000HandshakeEspera do subscriber pelo recibo ready do publisher
AwareSession.MSG_HELLO0HandshakeID de mensagem Subscriber → Publisher
AwareSession.MSG_READY1HandshakeID de mensagem Publisher → Subscriber
AwarePeerLink.MAX_BUILD_ATTEMPTS1Handshake (apenas cliente)Uma única tentativa — era 3, agora 1 porque o prewarmer prepara ambos os lados
AwarePeerLink.ATTEMPT_TIMEOUT_MS5 000HandshakeTimeout por tentativa — era 10 s, cortado pela metade para acelerar o fallback
AwarePeerLink.RETRY_DELAY_MS500HandshakeAtraso entre tentativas de retry (apenas cliente)
AwarePeerLink.REQUEST_TIMEOUT_MS30 000NDPTimeout de connectivityManager.requestNetwork
AwareLinkPool.IDLE_TIMEOUT_MS60 000Limpeza do poolFecha links ociosos após 60 s de inatividade
AwareLinkPool.IDLE_SWEEP_INTERVAL_MS10 000Limpeza do poolIntervalo da varredura
AwareHttpClientFactory.AWARE_HOST"plain-aware-peer"DNSHostname sentinela resolvido para o IPv6 do par por Dns customizado
build chat clientconnectTimeout 5 s, requestTimeout 30 s, interceptador ChaCha20
buildFileDownloadconnectTimeout 10 s, readTimeout 120 s, requestTimeout 120 s, sem crypto
PeerTransportPrewarmer.PREWARM_TTL_MS30 000PrewarmThrottle por par
PeerTransportPrewarmer.BLE_SCAN_TIMEOUT_MS15 000PrewarmTimeout de scan BLE para refreshAwareFlagFromScan
PeerCircuitBreaker.WINDOW_MS30 000Circuit breakerDuração aberto após threshold
PeerCircuitBreaker.MAX_FAILURES2Circuit breakerFalhas dentro da janela para abrir
TempData.httpsPort8443 (padrão)ServidorPorta do publisher anunciada via WifiAwareNetworkSpecifier.setPort
BleServiceData.AWARE_SUPPORTED0x01Scan response do BLEBit indicando que o par suporta Wi-Fi Aware
BleServiceData.AWARE_RUNNING0x02Scan response do BLEBit indicando que o serviço Aware do par está rodando no momento

Recapitulação das Trocas de Design {#design-trade-offs-recap}

Diagram 14
14

Leitura Adicional

  • Arquitetura de Chat — como o WifiAwareTransport se encaixa na cadeia de fallback LAN → Aware → BLE e no pipeline mais amplo de envio/recebimento de chat.
  • Transporte BLE — o transporte de último recurso que assume quando o Aware está indisponível; também é o canal usado pelo prewarmer para disparar a inicialização do Aware no par remoto.
  • Fluxo de Pareamento — como a chave ChaCha20 compartilhada reutilizada como PMK do Aware é estabelecida, e como as flags da scan response do BLE (AWARE_SUPPORTED / AWARE_RUNNING) são preenchidas.