Статья описывает только для Android жизненный цикл Aware-сессии, модель
обнаружения publish / subscribe, двухфазное рукопожатие с разделением
ролей, синхронизирующее requestNetwork на обеих сторонах в ~500 мс окне
фреймворка, пул per-peer линков с очисткой простаивающих, трюк с IPv6 +
кастомным DNS, позволяющий одному OkHttp-клиенту обслуживать и LAN, и Aware,
и prewarmer, запускающий Aware пира через BLE.
Более широкая цепочка fallback'а — см. Chat Architecture. BLE-транспорт, вступающий при недоступности Aware, — см. BLE Transport. О том, как устанавливается общий ключ ChaCha20, переиспользуемый как Aware PMK, — см. Pairing Flow.
Содержание
- Почему Wi-Fi Aware?
- Где Aware находится в цепочке fallback'а
- Жизненный цикл сессии: Attach → Publish + Subscribe
- Обнаружение и назначение ролей
- Двухфазное рукопожатие (hello + ready)
- NDP requestNetwork — окно 500 мс
- Пул per-peer линков и очистка простаивающих
- IPv6-адресация и DNS-трюк
plain-aware-peer - Криптография: вывод PMK и переиспользование ChaCha20
- Путь отправки сообщения (end-to-end)
- Путь загрузки файла (end-to-end)
- Prewarming: запуск Aware через BLE
- Отказы и флаг fast-skip
- Справочник ключевых констант
- Сводка проектных компромиссов
Почему Wi-Fi Aware? {#why-wi-fi-aware}
Wi-Fi Aware (IEEE 802.11bc, ранее NAN — Neighbor Awareness Networking) — сертификация Wi-Fi Alliance, позволяющая двум устройствам обнаруживать друг друга и обмениваться данными без какой-либо Wi-Fi-инфраструктуры — без AP, без роутера, без DHCP. PlainApp использует его в двух сценариях, которые не покрывает LAN:
- Разные SSID / VLAN. Телефон в гостевой сети и ноутбук в IoT VLAN оба «онлайн» по Wi-Fi, но не могут достучаться до IP друг друга. Aware создаёт прямой device-to-device data path, полностью обходящий инфраструктуру.
- Инфраструктуры нет вообще. Два устройства в глуши с включённым Wi-Fi, но без AP, всё ещё могут общаться. (BLE это тоже покрывает, но Aware намного быстрее — ~10 мс round trip против секунд, и MB/s против десятков KB/s.)
Платформенные ограничения
Wi-Fi Aware в PlainApp — только Android:
- Android 13 (API 33) — минимум; перегрузки
WifiAwareNetworkSpecifier.BuilderсsetPort()иsetPmk(), от которых зависит PlainApp, требуютisTPlus(). - iOS не предоставляет Wi-Fi Aware сторонним приложениям. iOS PlainApp падает
напрямую с LAN на BLE; объект
WifiAwareTransportдаже не компилируется в iOS-таргет (@RequiresApi(Build.VERSION_CODES.S)+ androidMain source set).
Именно поэтому PeerTransportRouter.buildList вызывает
createWifiAwareTransport() — фабрику, возвращающую null на iOS.
Где Aware находится в цепочке fallback'а {#where-aware-sits-in-the-fallback-chain}
PeerTransportRouter PlainApp — упорядоченный список. На каждый вызов send
или downloadFile он обходит список и пробует каждый транспорт, пока один не
успеет; сбои каскадом спускаются ниже.
Почему Aware «в середине», а не «первый»?
Потому что LAN почти всегда быстрее, когда доступен. Один Wi-Fi hop внутри одной подсети через AP — это один обмен 802.11-кадрами; Aware data path добавляет NDP setup (~5 с при первом использовании) плюс второй контекст Wi-Fi-радио для device-to-device линка. Если оба достижимы, LAN выигрывает по задержке и пропускной способности.
И наоборот, BLE всегда медленнее — но он работает, когда оба устройства сопряжены. Aware посередине: быстрее BLE, медленнее LAN, и доступен только на Android 13+ с включённым Wi-Fi.
Жизненный цикл сессии: Attach → Publish + Subscribe {#session-lifecycle-attach--publish--subscribe}
Wi-Fi Aware сессия — процесс-глобальная. На устройстве ровно одна
WifiAwareSession; внутри неё PlainApp запускает одну publish-сессию
(чтобы пиры могли нас обнаружить) и одну subscribe-сессию (чтобы мы могли
обнаруживать пиров). Обе стартуют сразу, как только AwareSession.start()
завершает колбэк attach.
Зачем publish И subscribe на одном устройстве?
Модель обнаружения Wi-Fi Aware асимметрична: publisher рекламирует сервис,
subscriber его сканирует. Чтобы сделать обнаружение симметричным (оба
устройства обнаруживают друг друга), PlainApp делает обе роли сразу. Без
этого устройство A должно было бы заранее знать, является ли оно publisher или
subscriber для данного пира — но роли пиров определяются позже сравнением
clientId (см. Обнаружение и назначение ролей).
Одновременный publish и subscribe означает, что каждое устройство видит
onServiceDiscovered другого (как subscriber) И получает hello-сообщения
другого (как publisher) — оба направления рукопожатия всегда доступны.
Авторестарт при завершении
Некоторые Android-варианты (особенно MIUI) убивают длительные Aware-сессии
для экономии батареи. PlainApp обрабатывает это в колбэках
onSessionTerminated: он зануляет завершённую сессию и немедленно снова
вызывает publishOwnService / subscribeOwnService на всё ещё
подключённой WifiAwareSession. Сама attach-сессия не теряется — только
publish/subscribe discovery-сессия. Peer handle'ы до завершения становятся
устаревшими, поэтому awaitPeerHandle проверяет метку времени
discoveredAt и отбрасывает handle'ы старше 30 с.
Обнаружение и назначение ролей {#discovery--role-assignment}
Протокол Wi-Fi Aware data-path требует одну сторону в роли publisher (server) и другую в роли subscriber (client). Обе стороны не могут одновременно быть инициаторами — фреймворк отклоняет запросы без соответствующей counterpart'и.
PlainApp назначает роли детерминированно per peer простым лексикографическим сравнением clientId:
Почему детерминированно, а не через согласование?
Согласованный подход (например, «меньший MAC — сервер») потребовал бы лишнего
обмена сообщениями. Лексикографическое сравнение идемпотентно, симметрично
и stateless: оба устройства вычисляют одну и ту же роль для одной пары без
всякого общения. clientId — 13-символьный короткий UUID, поэтому совпадения
(clientId == peer.id) случаются только при сравнении пира с самим собой —
что никогда не доходит до транспорта.
Роль определяет далее две вещи:
- Кто ведёт цикл повторных попыток. Только клиент повторяет
requestNetwork; сервер делает ровно одну попытку на каждый полученный hello. Это критично для окна 500 мс (следующий раздел). - Кто задаёт порт. Publisher вызывает
setPort(httpsPort), поскольку именно он принимает входящие соединения на порту своего HTTPS-сервера. Subscriber порт не задаёт — он узнаёт порт пира изWifiAwareNetworkInfoпосле установки data path.
Двухфазное рукопожатие (hello + ready) {#the-two-phase-handshake-hello--ready}
Самая сложная часть настройки Wi-Fi Aware data-path — тайминг. Фреймворк
требует, чтобы обе стороны вызвали connectivityManager.requestNetwork в
течение примерно 500 мс друг от друга — если одна сторона вызовет его до
того, как другая зарегистрирует соответствующий запрос, фреймворк немедленно
отклоняет его onUnavailable
("releaseRequestAsUnfulfillableByAnyFactory").
PlainApp решает это двухсообщенийным прикладным рукопожатием, поверх
Aware L2 message-канала (тот же sendMessage API, что используется в
onServiceDiscovered):
Почему два сообщения (hello + ready), а не одно?
Только hello недостаточно из-за асимметрии направлений. Subscriber может
отправить hello сразу, как обнаружит publisher (в onServiceDiscovered), но
publisher не может запустить requestNetwork, пока не получит PeerHandle
subscriber'а, который он узнаёт только получив hello. Так что hello служит
двум целям:
- Доставляет PeerHandle subscriber'а publisher'у. Publisher нужен он,
чтобы построить
WifiAwareNetworkSpecifier. - Сигнализирует намерение подключиться. Получение hello говорит publisher'у «subscriber сейчас вызовет requestNetwork, поэтому мне тоже надо».
Квитанция ready существует для противоположного направления — сказать
subscriber'у «publisher зарегистрировал свой requestNetwork». Без неё
requestNetwork subscriber'а мог бы обогнать publisher'а и быть отвергнутым
фреймворком. Квитанция ready — неблокирующий сигнал: subscriber не ждёт
его перед вызовом requestNetwork (это добавило бы round trip), но если она
приходит, пока subscriber в состоянии IDLE (между попытками), он может
немедленно повторить, не дожидаясь паузы RETRY_DELAY_MS.
Асимметрия цикла повторов
Это самая тонкая часть дизайна. Повторяет только subscriber. Publisher
делает ровно одну попытку requestNetwork на каждый полученный hello. Это
связано с тем, что:
- Если бы обе стороны повторяли независимо, их циклы повторов разошлись бы
по фазе (разные
delay()длительности, разные GC-паузы), и два вызоваrequestNetworkредко совпали бы в окне 500 мс. - Цикл повторов subscriber'а отправляет свежий hello на каждой попытке, что
повторно триггерит
buildLinkpublisher'а черезpublishHelloListeners. Это гарантирует, чтоrequestNetworkpublisher'а всегда следует за hello с отставанием ~50 мс, хорошо внутри окна 500 мс.
Подробно это задокументировано в
AwarePeerLink.build.
NDP requestNetwork — окно 500 мс {#ndp-requestnetwork--the-500-ms-window}
Вызов requestNetwork — самая чувствительная ко времени операция в Aware
транспорте. Вот что происходит на каждой стороне:
Что означает колбэк onUnavailable
onUnavailable срабатывает, когда фреймворк отклоняет requestNetwork, не
найдя соответствующего peer-запроса. Сам PeerHandle всё ещё валиден —
неудачна только NDP (Neighbor Discovery Protocol)-связка, поскольку другая
сторона ещё не зарегистрировалась. PlainApp намеренно не вызывает
session.invalidatePeerHandle в этом случае, поскольку инвалидация handle'а
отбросила бы единственный сигнал, что onServiceDiscovered вообще был вызван
(он срабатывает один раз за пир за время жизни subscribe-сессии). Сохранив
handle, повторная попытка может переиспользовать его, не ожидая свежего
обнаружения.
То же относится к publisher-стороне handle из onMessageReceived —
publisher хранит запись publishPeerHandles[fromCid] между неудачными
попытками, поэтому следующий hello subscriber'а переиспользует
закэшированный handle, а не теряется.
Пул per-peer линков и очистка простаивающих {#per-peer-link-pool--idle-sweeping}
Каждый сопряжённый пир получает собственный объект AwarePeerLink,
принадлежащий процесс-глобальному AwareLinkPool. Пул обрабатывает события
обнаружения, переиспользование линка и выселение простаивающих.
Почему не авто-сборка при обнаружении?
Пул явно не строит линк при срабатывании onServiceDiscovered. Это
критическое решение: в шумной кофейне может быть 100 устройств PlainApp, все
публикующие сервис «plain-peer». Если бы каждое обнаружение триггерило
requestNetwork, фреймворк был бы затоплен попытками NDP setup, а Wi-Fi-радио
было бы насыщено.
Вместо этого пул только записывает PeerHandle и ждёт одного из:
- Локальный пользователь отправил сообщение →
WifiAwareTransport.send→pool.buildLink(peer)(триггер со стороны отправителя). - Удалённый пир прислал hello →
onPublishHelloReceived→buildLink(peer)(триггер со стороны приёмника). - Удалённый пир прислал ready →
onSubscribeReadyReceived→buildLink(peer)(триггер со стороны приёмника).
Таким образом, линки строятся только для пиров, с которыми пользователь реально обменивается сообщениями, — а не для каждого PlainApp-устройства в зоне действия радио.
Очистка простаивающих
Каждые 10 секунд пул обходит все линки и закрывает те, чей lastActiveAt
старше 60 секунд. Каждый send и downloadFile вызывает link.touch() для
обновления метки. Это возвращает контекст Wi-Fi-радио и пул OkHttp-соединений
для пиров, с которыми пользователь перестал общаться, — что важно, поскольку
Android ограничивает число одновременных Aware data paths примерно до 4–10
(зависит от устройства).
IPv6-адресация и DNS-трюк plain-aware-peer {#ipv6-addressing--the-plain-aware-peer-dns-trick}
Wi-Fi Aware data paths используют только link-local IPv6. IPv4 нет, DNS-
сервера нет, DHCP нет. IPv6-адрес пира доставляется через поле
WifiAwareNetworkInfo.peerIpv6Addr в onCapabilitiesChanged — адрес
fe80::..., осмысленный только на сетевом интерфейсе Aware.
PlainApp должен отправлять HTTPS-запросы на этот адрес, но парсинг URL
https:// в OkHttp отказывается принимать сырые IPv6-литералы в hostname
(https://[fe80::abcd]:8443/ работает, но маршрутизировать через кастомный
Dns-резолвер чище). Трюк:
Зачем сентинельный hostname?
Альтернатива — передача IPv6-литерала напрямую в URL — потребовала бы, чтобы
каждая точка вызова знала о link-local-адресе. Использование сентинельного
hostname делает конструирование URL одинаковым для LAN и Aware: оба
производят валидный https://<host>:<port>/peer_graphql URL, который OkHttp
может распарсить. Единственное отличие — Dns-реализация, привязанная к
клиенту: LAN использует системный DNS, Aware использует awareDns(peerIpv6),
возвращающий закэшированный link-local-адрес для сентинельного hostname и
проваливающийся к Dns.SYSTEM для всего остального.
Зачем network.socketFactory?
Объект Network Android представляет конкретный сетевой интерфейс (в данном
случае Aware data path). Вызывая network.socketFactory и передавая его в
конфиг socketFactory OkHttp, мы форсим создание всех TCP-сокетов на
интерфейсе Aware — не на дефолтном Wi-Fi или сотовом интерфейсе. Без
этого ОС маршрутизирует запрос через сеть по умолчанию, где link-local IPv6
недостижим, и запрос упадёт с ENETUNREACH.
Криптография: вывод PMK и переиспользование ChaCha20 {#cryptography-pmk-derivation--chacha20-reuse}
Wi-Fi Aware поддерживает опциональный PMK (Pairwise Master Key) для data path. Когда он установлен, сам L2-линк шифруется этим PMK — Wi-Fi-радио обрабатывает шифрование, прикладная криптография не нужна.
PlainApp выводит PMK из того же общего ключа ChaCha20, который
LanTransport и BleTransport используют для прикладного шифрования:
Зачем усекать до 32 байт?
Wi-Fi Aware PMK должен быть ровно 32 байта (256 бит). Общий ключ ChaCha20 из
сопряжения тоже 32 байта в обычном случае, поэтому ветка raw.size == 32 —
общий путь. Fallback с усечением/паддингом обрабатывает (теоретический)
случай, когда ключ сохранён короче — паддинг нулями до 32 байт это
оборонительная мера, не что-то, что случается на практике с корректно
сопряжёнными пирами.
Подписанный конверт идентичен LAN
Поскольку createCryptoHttpClient — та же фабрика, что использует
LanTransport, L7-криптография на Aware байт-в-байт идентична LAN.
Серверный PeerGraphQLService не знает (и не заботится), какой транспорт
доставил запрос — он просто видит подписанную, зашифрованную GraphQL-полезную
нагрузку и расшифровывает её общим ключом пира. Это принцип «одна кодовая
база, много транспортов», задокументированный в
Chat Architecture.
Путь отправки сообщения (end-to-end) {#message-send-path-end-to-end}
Сводя всё вместе — что происходит при отправке сообщения чата через Wi-Fi Aware:
Примечательные проектные решения
- Переиспользование соединения. В отличие от
BleTransport, разрывающего GATT-соединение после каждого запроса,WifiAwareTransportпереиспользует Aware data path для стольких запросов, сколько пользователь делает в пределах 60-секундного окна простоя. Первый запрос оплачивает ~400 мс рукопожатие; последующие — ~10 мс round trip. - Та же криптография, что у LAN. ChaCha20-интерсептор и подписанный
конверт байт-идентичны LAN.
PeerGraphQLServiceпира не знает, какой транспорт доставил запрос. - Без preemption при сбое линка. Если
buildLinkпадает, транспорт выбрасываетTransportUnavailable, и маршрутизатор проваливается к BLE. Повторных попыток внутриsendнет —AwarePeerLink.buildуже выполняет собственный внутренний цикл повторов (сMAX_BUILD_ATTEMPTS = 1на клиенте, больше, если prewarmer «прогрел» обе стороны).
Путь загрузки файла (end-to-end) {#file-download-path-end-to-end}
Загрузки файлов через Aware переиспользуют тот же data path, что и сообщения чата, но используют отдельный OkHttp-клиент, настроенный на стриминг больших файлов:
Зачем отдельный клиент для загрузок?
Чат-клиент (AwareHttpClientFactory.build) имеет 30-секундный
requestTimeoutMillis — уместно для GraphQL-мутаций, но катастрофично для
100 MB загрузки файла. Клиент загрузок (buildFileDownload) ставит:
connectTimeoutMillis = 10_000(дольше 5 с чата, более толерантен к медленному первому пакету на свежем data path)readTimeout = 120 sна чтение (vs неявный дефолт 10 с)requestTimeoutMillis = 120_000(2 минуты — достаточно для большинства файлов)retryOnConnectionFailure(true)— упавшее посреди загрузки чтение повторяется, а не валит всю передачу
Также он опускает ChaCha20-интерсептор. Endpoint /fs отдаёт сырые
байты файла (не подписанный GraphQL-конверт), а L2 PMK (когда есть) уже
шифрует радио-линк. Двойное шифрование 50 MB видео ChaCha20 в ПО потратило
бы CPU и замедлило передачу.
Стриминг, не буферизация
Как и BLE-путь, загрузки Aware стримят файл через ByteReadChannel — файл
пишется во временный файл по мере поступления байт, не буферизуется в памяти.
PeerFileDownloader читает чанки 8 KB и эмитит события прогресса каждую
секунду. Тот же конвейер DownloadedResponse / PeerFileDownloader /
DownloadQueue переиспользуется между всеми транспортами —
транспортно-специфичен только источник channel.
Prewarming: запуск Aware через BLE {#prewarming-ble-triggered-aware-startup}
Самая большая видимая пользователю задержка в Aware-пути — первое рукопожатие — если обе стороны ещё не запустили Aware, первое сообщение пользователя должно ждать:
- Local Aware session attach (~1 с)
- Local publish + subscribe start (~1 с)
- Aware-старт удалённого пира (~2 с через BLE)
- Взаимное обнаружение (~1 с)
- NDP-рукопожатие (~400 мс)
Это ~5 секунд до отправки первого байта. Чтобы скрыть эту задержку,
PeerTransportPrewarmer запускается при входе на ChatPage и триггерит
Aware-старт удалённого пира через BLE:
Двойная роль BLE
BLE здесь служит двум целям:
- Прочитать текущее Aware-состояние пира (дёшево, без GATT-подключения —
serviceDatabyte0 scan-response несёт Aware-флаги). - Триггернуть пир запустить Aware, если он поддерживает, но сейчас не
запущен. Это идёт через обычный путь
BleTransport.send— мутация GraphQLstartAware, зашифрованная общим ключом ChaCha20, доставленная через GATT RPC на endpoint/peer_graphqlпира.
Это одно из немногих мест, где транспорты сотрудничают, а не просто проваливаются: BLE используется, чтобы превентивно апгрейдить сессию до более быстрого Aware-транспорта, прежде чем пользователь вообще заметит.
Зачем оптимистичный setAwareRunning(true)?
Мутация startAware возвращает успех, как только резолвер удалённого пира
вызывает WifiAwareTransport.start() — но Aware-сессия ещё не присоединена
(onAttached срабатывает асинхронно). PlainApp помечает пир как
awareRunning = true оптимистично, потому что:
- Если она действительно стартовала, следующий
sendиспользует Aware (быстро). - Если нет (например, Wi-Fi пира выключен), следующий
sendупадёт вbuildLinkсTransportUnavailableи естественно откатится к BLE. - Цена ложного позитива — один ~5-секундный тайм-аут, не постоянный блок —
PeerCircuitBreakerзаписывает сбой, но не открывает BLE-ветвь (BLE открывается только по собственным сбоям).
Зачем троттлинг 30 с?
PeerTransportPrewarmer.prewarm(peerId) записывает метку времени per peer и
отказывается перезапускаться в течение 30 с. Это потому, что пользователь
часто ходит туда-сюда между списком чатов и страницей чата — без троттлинга
каждая навигация триггерила бы BLE-скан + startAware-мутацию, высаживая
батарею и спамя BLE-радио. Окно 30 с достаточно короткое, чтобы поймать пира,
только что ставшего онлайн (например, пользователь открыл приложение на
удалённом устройстве), но достаточно длинное, чтобы избежать спуртовых
перезапусков.
Отказы и флаг fast-skip {#failure-modes--the-fast-skip-flag}
У Aware больше режимов отказа, чем у любого другого транспорта. Флаг fast-skip
isAwareRunning — единственная самая важная оптимизация во всём модуле: без
него каждый send тратил бы 10 с на тайм-аут buildLink перед откатом к BLE.
Флаг isAwareRunning — краеугольный камень
Без этого единственного булева флага каждый Aware-send либо:
- Всегда пытался бы
buildLink→ 10-секундный тайм-аут на каждом send пиру, чей Aware не запущен. - Всегда пропускал бы Aware → никогда не использовал бы его, даже когда обе стороны его запускают.
Флаг обновляется из двух источников, в порядке авторитетности:
- BLE scan response (дёшево, без GATT-подключения) — ставится
PeerTransportPrewarmer.refreshAwareFlagFromScan. Пир рекламирует своё Aware-состояние в 9-байтной полезной нагрузкеserviceData(byte0 битполе). - GATT DISCOVER reply (авторитетно) — ставится
PairingTransport.scanAndDiscoverпри полном обнаружении. Перезаписывает scan-подсказку.
Когда false, WifiAwareTransport.send и downloadFile выбрасывают
TransportUnavailable немедленно — без сканирования, без рукопожатия,
без тайм-аута. Маршрутизатор проваливается к BLE за микросекунды.
Справочник ключевых констант {#key-constants-reference}
| Константа | Значение | Где | Назначение |
|---|---|---|---|
AwareSession.SERVICE_NAME | "plain-peer" | Обнаружение | Имя сервиса, публикуемое и подписываемое каждым устройством PlainApp |
AwareSession.PEER_HANDLE_MAX_AGE_MS | 30 000 | Кэш PeerHandle | Отбрасывать устаревшие handle'ы (publish-сессия пира могла быть перезапущена) |
AwareSession.READY_TIMEOUT_MS | 15 000 | Рукопожатие | Ожидание subscriber'ом квитанции ready publisher'а |
AwareSession.MSG_HELLO | 0 | Рукопожатие | ID сообщения Subscriber → Publisher |
AwareSession.MSG_READY | 1 | Рукопожатие | ID сообщения Publisher → Subscriber |
AwarePeerLink.MAX_BUILD_ATTEMPTS | 1 | Рукопожатие (только клиент) | Одна попытка — было 3, теперь 1, потому что prewarmer прогревает обе стороны |
AwarePeerLink.ATTEMPT_TIMEOUT_MS | 5 000 | Рукопожатие | Тайм-аут на попытку — было 10 с, вдвое уменьшено для ускорения fallback |
AwarePeerLink.RETRY_DELAY_MS | 500 | Рукопожатие | Задержка между попытками (только клиент) |
AwarePeerLink.REQUEST_TIMEOUT_MS | 30 000 | NDP | Тайм-аут connectivityManager.requestNetwork |
AwareLinkPool.IDLE_TIMEOUT_MS | 60 000 | Очистка пула | Закрывать простаивающие линки после 60 с неактивности |
AwareLinkPool.IDLE_SWEEP_INTERVAL_MS | 10 000 | Очистка пула | Интервал очистки |
AwareHttpClientFactory.AWARE_HOST | "plain-aware-peer" | DNS | Сентинельный hostname, резолвящийся в IPv6 пира через кастомный Dns |
build чат-клиент | connectTimeout 5 с, requestTimeout 30 с, ChaCha20-интерсептор | ||
buildFileDownload | connectTimeout 10 с, readTimeout 120 с, requestTimeout 120 с, без крипто | ||
PeerTransportPrewarmer.PREWARM_TTL_MS | 30 000 | Prewarm | Троттлинг per peer |
PeerTransportPrewarmer.BLE_SCAN_TIMEOUT_MS | 15 000 | Prewarm | Тайм-аут BLE-сканирования для refreshAwareFlagFromScan |
PeerCircuitBreaker.WINDOW_MS | 30 000 | Circuit breaker | Длительность открытия после порога |
PeerCircuitBreaker.MAX_FAILURES | 2 | Circuit breaker | Сбоев в окне для открытия |
TempData.httpsPort | 8443 (default) | Сервер | Порт publisher'а, рекламируемый через WifiAwareNetworkSpecifier.setPort |
BleServiceData.AWARE_SUPPORTED | 0x01 | BLE scan response | Бит: пир поддерживает Wi-Fi Aware |
BleServiceData.AWARE_RUNNING | 0x02 | BLE scan response | Бит: Aware-сервис пира сейчас запущен |
Сводка проектных компромиссов {#design-trade-offs-recap}
Дополнительная литература
- Chat Architecture — как
WifiAwareTransportвписывается в цепочку fallback'овLAN → Aware → BLEи более широкий конвейер отправки/получения чата. - BLE Transport — транспорт последней инстанции, вступающий при недоступности Aware; также канал, используемый prewarmer'ом для триггера Aware-старта на удалённом пире.
- Pairing Flow — как устанавливается общий ключ
ChaCha20, переиспользуемый как Aware PMK, и как заполняются флаги BLE
scan response (
AWARE_SUPPORTED/AWARE_RUNNING).