本文涵蓋僅限 Android 的 Aware 工作階段生命週期、publish /
subscribe 探索模型、在框架約 500 ms 視窗內同步雙方
requestNetwork 的兩階段角色切分握手、具閒置清掃的每對端連結集區、讓單一 OkHttp 用戶端同時服務 LAN 與 Aware 的 IPv6 + 自訂 DNS 技巧,
以及透過 BLE 觸發對端 Aware 啟動的預熱器。
關於更廣泛的備援鏈,請參見 Chat Architecture。 關於 Aware 不可用時接手的 BLE 傳輸層,請參見 BLE Transport。關於作為 Aware PMK 重用的共享 ChaCha20 金鑰如何建立,請參見 Pairing Flow。
目錄
- 為什麼選擇 Wi-Fi Aware?
- Aware 在備援鏈中的位置
- 工作階段生命週期:Attach → Publish + Subscribe
- 探索與角色指派
- 兩階段握手(hello + ready)
- NDP requestNetwork —— 500 ms 視窗
- 每對端連結集區與閒置清掃
- IPv6 定址與
plain-aware-peerDNS 技巧 - 密碼學:PMK 推導與 ChaCha20 重用
- 訊息傳送路徑(端對端)
- 檔案下載路徑(端對端)
- 預熱:BLE 觸發的 Aware 啟動
- 失敗模式與快速跳過旗標
- 關鍵常數參考
- 設計取捨回顧
為什麼選擇 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 建立一條直接的裝置對裝置資料路徑,完全繞過 基礎設施。
- 完全沒有基礎設施。 兩台處於荒野、開啟 Wi-Fi 但無 AP 的裝置 仍能聊天。(BLE 也涵蓋此情境,但 Aware 快得多 ——約 10 ms 往返 vs 數秒,以及 MB/s vs 數十 KB/s。)
平台限制
Wi-Fi Aware 在 PlainApp 中僅限 Android:
- Android 13(API 33)為最低版本——PlainApp 依賴的
WifiAwareNetworkSpecifier.Builder的setPort()與setPmk()多載需要isTPlus()。 - iOS 未向第三方應用程式公開 Wi-Fi Aware。iOS PlainApp 直接
從 LAN 退到 BLE;
WifiAwareTransport物件甚至未 編譯進 iOS 目標(@RequiresApi(Build.VERSION_CODES.S)+ androidMain 原始碼集)。
這就是為什麼 PeerTransportRouter.buildList 呼叫
createWifiAwareTransport() —— 一個在 iOS 上傳回 null 的工廠。
Aware 在備援鏈中的位置 {#where-aware-sits-in-the-fallback-chain}
PlainApp 的 PeerTransportRouter 是一個有序清單。對於每次 send 或
downloadFile 呼叫,它走訪清單並嘗試每個傳輸層直到成功;失敗向下層疊。
為什麼 Aware 是「中間」而非「第一個」?
因為** LAN 在可用時幾乎總是更快**。透過 AP 的同子網 Wi-Fi 跳接是單一次 802.11 框架交換;Aware 資料路徑 增加了 NDP 設定(首次約 5 秒)加上用於裝置對裝置連結的第二個 Wi-Fi 無線電情境。若兩者皆可達,LAN 在延遲 與吞吐量上勝出。
相對地,BLE 永遠較慢——但只要兩台裝置 已配對,它就能運作。Aware 居中:比 BLE 快,比 LAN 慢, 且僅在具備 Wi-Fi 的 Android 13+ 裝置上可用。
工作階段生命週期:Attach → Publish + Subscribe {#session-lifecycle-attach--publish--subscribe}
Wi-Fi Aware 工作階段是行程層級的。每台裝置恰好有一個
WifiAwareSession;在其中,PlainApp 執行一個 publish
工作階段(讓對端能發現我們)與一個 subscribe 工作階段(讓我們
能發現對端)。兩者都在 AwareSession.start() 完成attach 回呼的瞬間啟動
。
為什麼同一台裝置同時 publish 與 subscribe?
Wi-Fi Aware 探索模型是不對稱的:發布者廣告
一個服務,訂閱者掃描它。為讓探索對稱(兩台
裝置都發現彼此),PlainApp 同時做兩者。否則,
裝置 A 必須事先知道對於某個對端它是發布者還是
訂閱者——但對端角色稍後才由
clientId 比較決定(見 探索與角色指派)。
同時 publish 與 subscribe 意味著每台裝置都會看到
對方的 onServiceDiscovered(作為訂閱者)並接收對方的
hello 訊息(作為發布者)——握手的兩個方向
始終可用。
終止時自動重啟
某些 Android 變體(尤其是 MIUI)會終止長時間執行的 Aware
工作階段以節省電力。PlainApp 在 onSessionTerminated
回呼中處理此情況:它將終止的工作階段設為 null,並立即在仍 attached 的
WifiAwareSession 上再次呼叫
publishOwnService / subscribeOwnService。Attach 工作階段本身不會遺失——只有
publish/subscribe 探索工作階段。終止之前的 PeerHandle 會變得過時,這就是為什麼 awaitPeerHandle 會檢查
discoveredAt 時間戳並丟棄超過 30 秒的 PeerHandle。
探索與角色指派 {#discovery--role-assignment}
Wi-Fi Aware 資料路徑協定要求一方擔任 發布者(伺服器),另一方擔任訂閱者(用戶端)。雙方 不能同時擔任發起者——框架會拒絕 沒有匹配對應方的請求。
PlainApp 使用簡單的 clientId 字典序比較,按對端確定性地指派角色:
為什麼是確定性而非協商?
協商方式(例如「較小 MAC 為伺服器」)需要
額外的訊息交換。字典序比較是冪等、
對稱且無狀態的:雙方無需任何通訊即可為
同一對計算出相同角色。clientId 是 13 字元的短
UUID,因此平手(clientId == peer.id)只發生於與
自身比較時——這永遠不會到達傳輸層。
角色決定了下游兩件事:
- 誰驅動重試迴圈。 只有用戶端重試
requestNetwork;伺服器對每個收到的 hello 恰好嘗試一次。 這對 500 ms 視窗至關重要(下一節)。 - 誰設定連接埠。 發布者呼叫
setPort(httpsPort),因為 它是在自己的 HTTPS 伺服器連接埠上接受入境連線的一方。 訂閱者不設定連接埠——它從 資料路徑建立後的WifiAwareNetworkInfo學習對端的連接埠。
兩階段握手(hello + ready) {#the-two-phase-handshake-hello--ready}
Wi-Fi Aware 資料路徑設定最困難的部分是時序。
框架要求雙方在彼此約 500 ms 內呼叫 connectivityManager.requestNetwork
——若一方在另一方註冊其匹配請求之前呼叫,框架會立即以 onUnavailable 拒絕("releaseRequestAsUnfulfillableByAnyFactory")。
PlainApp 透過一個兩訊息的應用層握手解決此問題,
該握手執行於 Aware L2 訊息通道之上(與 onServiceDiscovered 使用的相同 sendMessage
API):
為什麼是兩則訊息(hello + ready)而非一則?
單有 hello 不夠,因為方向不對稱。
訂閱者可以在發現發布者的瞬間(在
onServiceDiscovered 中)傳送 hello,但發布者
無法啟動requestNetwork,直到它擁有訂閱者的 PeerHandle,而這只能透過接收 hello 得知。因此 hello 有兩個用途:
- 將訂閱者的 PeerHandle 傳遞給發布者。 發布者
需要它來建構
WifiAwareNetworkSpecifier。 - 表示連線意圖。 接收 hello 告訴發布者 「訂閱者即將 requestNetwork,所以我也應該這樣做。」
ready 回條是為相反方向而存在——告訴
訂閱者「發布者已註冊其 requestNetwork」。沒有它,
訂閱者的 requestNetwork 可能跑到發布者之前
並被框架拒絕。ready 回條是一個非阻塞
訊號:訂閱者不會在呼叫
requestNetwork 之前等待它(那會增加一次往返),但若它抵達時
訂閱者正處於 IDLE 狀態(重試嘗試之間),訂閱者
可立即重試而無需等待 RETRY_DELAY_MS 間隔。
重試迴圈不對稱
這是設計中最微妙的部分。只有訂閱者
重試。 發布者對每個收到的 hello 恰好嘗試一次 requestNetwork。這是因為:
- 若雙方獨立重試,它們的重試週期會
失去同步(不同的
delay()持續時間、不同的 GC 暫停), 兩次requestNetwork呼叫鮮少在 500 ms 視窗內重疊。 - 訂閱者的重試迴圈在每次嘗試時傳送新鮮的 hello,
透過
publishHelloListeners重新觸發發布者的buildLink。 這保證發布者的requestNetwork永遠在 hello 之後約 50 ms,遠在 500 ms 視窗內。
相關細節記載於
AwarePeerLink.build。
NDP requestNetwork —— 500 ms 視窗 {#ndp-requestnetwork--the-500-ms-window}
requestNetwork 呼叫是 Aware 傳輸層中對時序最敏感的操作。以下是雙方發生的事:
onUnavailable 回呼代表什麼
onUnavailable 在框架找到匹配對端請求之前拒絕 requestNetwork
時觸發。PeerHandle 本身仍然
有效——只有 NDP(Neighbor Discovery Protocol)配對失敗,
因為另一方尚未註冊。PlainApp 在此情況下刻意不呼叫 session.invalidatePeerHandle,因為
使 PeerHandle 失效會丟棄 onServiceDiscovered 曾被呼叫過的唯一訊號(它在每個訂閱工作階段生命週期內對每個對端觸發一次)。保留 PeerHandle 後,重試可
重用它,而不必等待新鮮的探索。
對於從 onMessageReceived 取得的發布者端 PeerHandle 也是如此——
發布者跨失敗嘗試保留 publishPeerHandles[fromCid] 項目,
因此訂閱者的下一個 hello 會重用快取的 PeerHandle,而非
被丟棄。
每對端連結集區與閒置清掃 {#per-peer-link-pool--idle-sweeping}
每個已配對的對端都擁有自己的 AwarePeerLink 物件,由行程層級的
AwareLinkPool 擁有。集區處理探索事件、連結
重用與閒置驅逐。
為什麼探索時不自動建連結?
集區在
onServiceDiscovered 觸發時明確不建立連結。這是一個關鍵決策:繁忙的咖啡廳
可能有 100 台 PlainApp 裝置同時發布「plain-peer」
服務。若每次探索都觸發 requestNetwork,框架
會被 NDP 設定嘗試淹沒,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 資料路徑數量限制為約 4–10 個(因裝置而異)。
IPv6 定址與 plain-aware-peer DNS 技巧 {#ipv6-addressing--the-plain-aware-peer-dns-trick}
Wi-Fi Aware 資料路徑僅使用 link-local IPv6。沒有 IPv4,沒有
DNS 伺服器,沒有 DHCP。對端的 IPv6 位址透過
onCapabilitiesChanged 中的 WifiAwareNetworkInfo.peerIpv6Addr 欄位傳遞——一個只在
Aware 網路介面上有意義的 fe80::... 位址。
PlainApp 需要對此位址傳送 HTTPS 請求,但 OkHttp 的
https:// URL 解析拒絕主機名稱中的原始 IPv6 字面值
(https://[fe80::abcd]:8443/ 可運作,但透過自訂
Dns 解析器路由更為乾淨)。訣竅:
為什麼用哨兵主機名稱?
替代方案——直接在 URL 中傳遞 IPv6 字面值——會
要求每個呼叫端都知道 link-local 位址。透過使用
哨兵主機名稱,URL 建構對 LAN 與 Aware 完全相同:
兩者都產生有效的 https://<host>:<port>/peer_graphql URL,讓 OkHttp
可解析。唯一差異在於繫結至
用戶端的 Dns 實作——LAN 使用系統 DNS,Aware 使用 awareDns(peerIpv6),它對哨兵主機名稱傳回
快取的 link-local 位址,對其他任何主機名稱則
退到 Dns.SYSTEM。
為什麼用 network.socketFactory?
Android 的 Network 物件代表特定的網路介面(在本
例中為 Aware 資料路徑)。透過呼叫 network.socketFactory 並
將它傳遞給 OkHttp 的 socketFactory 設定,我們強制所有 TCP socket
在 Aware 介面上建立——而非預設的 Wi-Fi 或
行動網路介面。若不這麼做,作業系統會透過
預設網路路由請求,而 link-local IPv6 在那裡無法觸及,請求
會以 ENETUNREACH 失敗。
密碼學:PMK 推導與 ChaCha20 重用 {#cryptography-pmk-derivation--chacha20-reuse}
Wi-Fi Aware 支援資料路徑的選用 PMK(Pairwise Master Key)。設定後, L2 連結本身以該 PMK 加密——Wi-Fi 無線電 處理加密,無需應用層密碼學。
PlainApp 從 LanTransport 與 BleTransport 用於應用層加密的相同 ChaCha20 共享金鑰推導出 PMK:
為什麼截斷為 32 位元組?
Wi-Fi Aware PMK 必須恰好 32 位元組(256 位元)。配對得到的
ChaCha20 共享金鑰在正常情況下也是 32 位元組,因此
raw.size == 32 分支是常見路徑。截斷/填補
備援處理金鑰儲存較短的(理論)情況
——以零填補到 32 位元組是防禦性措施,在
正確配對的對端上實務上不會發生。
已簽章封套與 LAN 相同
由於 createCryptoHttpClient 是 LanTransport 使用的相同工廠,
Aware 上的 L7 加密與 LAN 位元對位元相同。
伺服器端的 PeerGraphQLService 不知道(也不在乎)哪個
傳輸層送達請求——它只看到一個已簽章、加密的
GraphQL 負載,並以對端的共享金鑰解密。這就是
Chat Architecture 中記載的
「一份程式碼庫,多種傳輸層」原則。
訊息傳送路徑(端對端) {#message-send-path-end-to-end}
將一切組合起來——當一則聊天訊息透過 Wi-Fi Aware 傳送時發生什麼事:
值得注意的設計選擇
- 連線重用。 與
BleTransport(在每次請求後拆除 GATT 連線)不同,WifiAwareTransport重用 Aware 資料路徑 用於使用者在 60 秒閒置 視窗內發出的所有請求。首次請求支付約 400 ms 握手;後續 請求約 10 ms 往返。 - 與 LAN 相同的密碼學。 ChaCha20 攔截器與已簽章封套與
LAN 位元相同。對端的
PeerGraphQLService不知道 哪個傳輸層送達請求。 - 連結失敗時不搶佔。 若
buildLink失敗,傳輸層 拋出TransportUnavailable,路由器退到 BLE。send內部不重試——AwarePeerLink.build已執行自己的 內部重試迴圈(用戶端MAX_BUILD_ATTEMPTS = 1,若 預熱器已預熱雙方則更多)。
檔案下載路徑(端對端) {#file-download-path-end-to-end}
透過 Aware 的檔案下載重用與聊天訊息相同的資料路徑,但 使用獨立的 OkHttp 用戶端,為串流大檔案設定:
為什麼下載用獨立用戶端?
聊天用戶端(AwareHttpClientFactory.build)有 30 秒的
requestTimeoutMillis——適用於 GraphQL 變更,但
對 100 MB 檔案下載是災難性的。下載用戶端
(buildFileDownload)設定:
connectTimeoutMillis = 10_000(比聊天的 5 秒長,對 全新資料路徑上慢的首封包更寬容)- 每次讀取
readTimeout = 120 s(對比隱含預設的 10 秒) requestTimeoutMillis = 120_000(2 分鐘——對大多數檔案足夠)retryOnConnectionFailure(true)——下載中斷的讀取會 重試,而非讓整個傳輸失敗
它也省略 ChaCha20 攔截器。/fs 端點提供原始
檔案位元組(非已簽章的 GraphQL 封套),而 L2 PMK(存在時)
已加密無線電連結。以 ChaCha20 軟體雙重加密 50 MB 影片
會浪費 CPU 並拖慢傳輸。
串流,而非緩衝
與 BLE 路徑一樣,Aware 下載將檔案透過
ByteReadChannel 串流——檔案在位元組到達時寫入暫存檔,
而非緩衝在記憶體中。PeerFileDownloader 讀取 8 KB 分塊並每秒發出
進度事件。相同的 DownloadedResponse /
PeerFileDownloader / DownloadQueue 管線跨所有
傳輸層重用——只有 channel 來源是傳輸層特定的。
預熱:BLE 觸發的 Aware 啟動 {#prewarming-ble-triggered-aware-startup}
Aware 路徑中最大的使用者可見延遲是首次 握手——若雙方都尚未啟動 Aware,使用者的第一則 訊息必須等待:
- 本地 Aware 工作階段 attach(約 1 秒)
- 本地 publish + subscribe 啟動(約 1 秒)
- 遠端對端的 Aware 啟動(透過 BLE 約 2 秒)
- 相互探索(約 1 秒)
- NDP 握手(約 400 ms)
這是在第一個位元組傳送前約 5 秒。為隱藏此延遲,
PeerTransportPrewarmer 在進入 ChatPage 時執行,並
透過 BLE 觸發遠端對端的 Aware 啟動:
BLE 的雙重角色
BLE 在此有兩個用途:
- 讀取對端當前的 Aware 狀態(便宜,無需 GATT 連線——
掃描回應的
serviceDatabyte0 帶有 Aware 旗標)。 - 觸發對端啟動 Aware(若它支援但目前
未運行)。這透過常規
BleTransport.send路徑進行—— 一個以共享 ChaCha20 金鑰加密的startAwareGraphQL 變更, 透過 GATT RPC 送達對端的/peer_graphql端點。
這是少數幾處傳輸層是合作而非單純備援的地方:BLE 用於 預先搶佔升級工作階段到更快的 Aware 傳輸層, 在使用者察覺之前。
為什麼樂觀地 setAwareRunning(true)?
startAware 變更在遠端對端的解析器呼叫 WifiAwareTransport.start() 後立即傳回成功——但 Aware 工作階段
尚未實際 attached(onAttached 非同步觸發)。PlainApp
樂觀地將對端標記為 awareRunning = true,因為:
- 若它確實啟動了,下一次
send會使用 Aware(快)。 - 若沒有(例如對端的 Wi-Fi 關閉),下一次
send的buildLink會以TransportUnavailable失敗並自然退到 BLE。 - 偽陽性的代價是一次約 5 秒的逾時,而非永久
阻塞——
PeerCircuitBreaker記錄失敗但不開啟 BLE 那一腿(BLE 只在自身的失敗時開啟)。
為什麼節流為 30 秒?
PeerTransportPrewarmer.prewarm(peerId) 對每個對端記錄一個時間戳,
並拒絕在 30 秒內重新執行。這是因為使用者頻繁地在
聊天清單與聊天頁面之間來回導航——若不
節流,每次導航都會觸發 BLE 掃描 + startAware
變更,耗電並對 BLE 無線電發出垃圾請求。30 秒視窗
短到足以捕捉剛上線的對端(例如使用者在
遠端裝置上開啟應用程式),但長到足以避免虛假的重跑。
失敗模式與快速跳過旗標 {#failure-modes--the-fast-skip-flag}
Aware 比任何其他傳輸層有更多失敗模式。
isAwareRunning 快速跳過旗標是整個模組中最重要的單一
最佳化——若沒有它,每次 send 都會
浪費 10 秒在 buildLink 逾時後才退回 BLE。
isAwareRunning 旗標是關鍵
若沒有這個單一布林值,每次 Aware send 都會:
- 永遠嘗試
buildLink→ 對 Aware 未運行的對端每次 send 都 10 秒逾時。 - 永遠跳過 Aware → 即使雙方都運行也永遠不用它。
該旗標從兩個來源重新整理,依權威排序:
- BLE 掃描回應(便宜,無需 GATT 連線)——由
PeerTransportPrewarmer.refreshAwareFlagFromScan設定。對端在 9 位元組的serviceData負載(byte0 位元欄位)中廣告 其 Aware 狀態。 - GATT DISCOVER 回覆(權威)——由
PairingTransport.scanAndDiscover在完整探索發生時設定。這會 覆寫掃描提示。
為 false 時,WifiAwareTransport.send 與 downloadFile 拋出
TransportUnavailable 立即——無掃描、無握手、無
逾時。路由器在微秒內退到 BLE。
關鍵常數參考 {#key-constants-reference}
| 常數 | 值 | 所在位置 | 用途 |
|---|---|---|---|
AwareSession.SERVICE_NAME | "plain-peer" | 探索 | 每台 PlainApp 裝置 publish 與 subscribe 的服務名稱 |
AwareSession.PEER_HANDLE_MAX_AGE_MS | 30 000 | PeerHandle 快取 | 捨棄過時 PeerHandle(對端的 publish 工作階段可能已重啟) |
AwareSession.READY_TIMEOUT_MS | 15 000 | 握手 | 訂閱者等待發布者 ready 回條 |
AwareSession.MSG_HELLO | 0 | 握手 | 訂閱者 → 發布者 訊息 ID |
AwareSession.MSG_READY | 1 | 握手 | 發布者 → 訂閱者 訊息 ID |
AwarePeerLink.MAX_BUILD_ATTEMPTS | 1 | 握手(僅用戶端) | 單次嘗試——原本 3,因預熱器預熱雙方現改為 1 |
AwarePeerLink.ATTEMPT_TIMEOUT_MS | 5 000 | 握手 | 每次嘗試逾時——原本 10 秒,減半以加速備援 |
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 | 由自訂 Dns 解析為對端 IPv6 的哨兵主機名稱 |
build 聊天用戶端 | connectTimeout 5 秒, requestTimeout 30 秒, ChaCha20 攔截器 | ||
buildFileDownload | connectTimeout 10 秒, readTimeout 120 秒, requestTimeout 120 秒, 無加密 | ||
PeerTransportPrewarmer.PREWARM_TTL_MS | 30 000 | 預熱 | 每對端節流 |
PeerTransportPrewarmer.BLE_SCAN_TIMEOUT_MS | 15 000 | 預熱 | refreshAwareFlagFromScan 的 BLE 掃描逾時 |
PeerCircuitBreaker.WINDOW_MS | 30 000 | 斷路器 | 達到閾值後的開啟持續時間 |
PeerCircuitBreaker.MAX_FAILURES | 2 | 斷路器 | 視窗內開啟所需的失敗次數 |
TempData.httpsPort | 8443 (預設) | 伺服器 | 透過 WifiAwareNetworkSpecifier.setPort 廣告的發布者連接埠 |
BleServiceData.AWARE_SUPPORTED | 0x01 | BLE 掃描回應 | 表示對端支援 Wi-Fi Aware 的位元 |
BleServiceData.AWARE_RUNNING | 0x02 | BLE 掃描回應 | 表示對端的 Aware 服務目前正在運行的位元 |
設計取捨回顧 {#design-trade-offs-recap}
延伸閱讀
- Chat Architecture ——
WifiAwareTransport如何融入LAN → Aware → BLE備援鏈與更宏觀的聊天 傳送/接收管線。 - BLE Transport —— Aware 不可用時 接手的最後手段傳輸層;也是預熱器用來觸發 遠端對端 Aware 啟動的通道。
- Pairing Flow —— 作為 Aware PMK 重用的共享 ChaCha20 金鑰如何建立,
以及 BLE 掃描回應旗標
(
AWARE_SUPPORTED/AWARE_RUNNING)如何填入。