ब्लॉग पर वापस जाएँ
Transport19 min read

Wi-Fi Aware ट्रांसपोर्ट डिज़ाइन — पड़ोसी डिस्कवरी और डेटा पथ

यह लेख बताता है कि PlainApp Wi-Fi Aware (NAN — Neighbor Awareness Networking) का उपयोग अपनी पीयर ट्रांसपोर्ट फ़ॉलबैक श्रृंखला के मध्य स्तर के रूप में कैसे करता है, LAN (समान-सबनेट HTTPS) और BLE (अंतिम-उपाय GATT RPC) के बीच। Wi-Fi Aware वही है जो दो PlainApp डिवाइस को तब बात करने देता है जब वे अलग SSID पर, गेस्ट बनाम IoT VLAN पर, या बिना किसी Wi-Fi इंफ़्रास्ट्रक्चर के हों — बिना कभी किसी DHCP सर्वर से IP पते की ज़रूरत के।

यह लेख केवल-Android Aware सत्र जीवनचक्र, publish / subscribe डिस्कवरी मॉडल, दो-चरण भूमिका-विभाजन हैंडशेक जो फ़्रेमवर्क की ~500 ms विंडो के भीतर दोनों पक्षों पर requestNetwork को सिंक्रोनाइज़ करता है, निष्क्रिय स्वीपिंग के साथ प्रति-पीयर लिंक पूल, IPv6 + कस्टम-DNS ट्रिक जो एक एकल OkHttp क्लाइंट को LAN और Aware दोनों की सेवा देने देता है, और प्रीवार्मर जो BLE के माध्यम से पीयर Aware स्टार्टअप को ट्रिगर करता है, कवर करता है।

व्यापक फ़ॉलबैक श्रृंखला के लिए देखें Chat Architecture। BLE ट्रांसपोर्ट के लिए जो Aware अनुपलब्ध होने पर काबू लेता है, देखें BLE Transport। साझा ChaCha20 कुंजी जो Aware PMK के रूप में पुनः उपयोग होती है, कैसे स्थापित होती है, उसके लिए देखें Pairing Flow।

विषय-सूची

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 राउंड ट्रिप, और दसियों KB/s के बजाय MB/s)।

Diagram 1
1

प्लेटफ़ॉर्म बाधाएँ

Wi-Fi Aware PlainApp में केवल-Android है:

  • Android 13 (API 33) न्यूनतम है — WifiAwareNetworkSpecifier.BuildersetPort() और setPmk() ओवरलोड के साथ जिन पर PlainApp निर्भर है, उन्हें 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 कॉल के लिए, यह सूची पर चलता है और तब तक हर ट्रांसपोर्ट आज़माता है जब तक एक सफल नहीं होता; विफलताएँ नीचे की ओर बहती हैं।

Diagram 2
2

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 कॉलबैक पूरा करता है।

Diagram 3
3

एक ही डिवाइस पर publish और subscribe दोनों क्यों?

Wi-Fi Aware डिस्कवरी मॉडल असममित है: एक पब्लिशर एक सेवा विज्ञापित करता है, एक सब्सक्राइबर इसके लिए स्कैन करता है। डिस्कवरी को सममित बनाने के लिए (दोनों डिवाइस एक-दूसरे को खोजें), PlainApp दोनों एक बार करता है। इसके बिना, डिवाइस A को पहले से जानना होगा कि वह दिए गए पीयर के लिए पब्लिशर है या सब्सक्राइबर — लेकिन पीयर भूमिकाएँ बाद में clientId तुलना द्वारा तय होती हैं (देखें Discovery & Role Assignment)।

एक साथ publish और subscribe करने का अर्थ है कि हर डिवाइस दूसरे के onServiceDiscovered (सब्सक्राइबर के रूप में) देखता है और दूसरे के hello संदेश (पब्लिशर के रूप में) प्राप्त करता है — हैंडशेक के दोनों दिशाएँ हमेशा उपलब्ध रहती हैं।

समाप्ति पर स्वतः-पुनःप्रारंभ

कुछ Android वेरिएंट (विशेष रूप से MIUI) बैटरी बचाने के लिए लंबे समय तक चलने वाले Aware सत्र मार देते हैं। PlainApp इसे onSessionTerminated कॉलबैक में संभालता है: यह समाप्त सत्र को शून्य कर देता है और तुरंत अभी-भी-attached WifiAwareSession पर फिर से publishOwnService / subscribeOwnService को कॉल करता है। Attach सत्र स्वयं खोया नहीं — केवल publish/subscribe डिस्कवरी सत्र। समाप्ति से पहले के पीयर हैंडल पुराने हो जाते हैं, जो कारण है कि awaitPeerHandle discoveredAt टाइमस्टैम्प जाँचता है और 30 सेकंड से पुराने हैंडल त्याग देता है।

डिस्कवरी और भूमिका असाइनमेंट {#discovery--role-assignment}

Wi-Fi Aware डेटा-पथ प्रोटोकॉल को एक पक्ष के पब्लिशर (सर्वर) के रूप में कार्य करने और दूसरे को सब्सक्राइबर (क्लाइंट) के रूप में अनिवार्य करता है। दोनों पक्ष एक साथ प्रारंभकर्ता नहीं हो सकते — फ़्रेमवर्क बिना मिलान विरोधी के अनुरोधों को अस्वीकार करता है।

PlainApp clientIds की एक सरल लेक्सिकोग्राफ़िक तुलना का उपयोग करके प्रति-पीयर निर्धारक रूप से भूमिकाएँ असाइन करता है:

Diagram 4
4

निर्धारक क्यों, निगोशिएटेड नहीं?

एक निगोशिएटेड दृष्टिकोण (उदा. "निचला MAC सर्वर है") को एक अतिरिक्त संदेश आदान-प्रदान की आवश्यकता होगी। लेक्सिकोग्राफ़िक तुलना आइडेम्पोटेंट, सममित, और स्टेटलेस है: दोनों डिवाइस बिना किसी संचार के एक ही जोड़े के लिए एक ही भूमिका गणना करते हैं। clientId एक 13-अक्षर छोटा UUID है, इसलिए टाई (clientId == peer.id) केवल तब होती है जब किसी पीयर की खुद से तुलना हो — जो कभी ट्रांसपोर्ट तक नहीं पहुँचता।

भूमिका डाउनस्ट्रीम दो चीज़ें तय करती है:

  1. कौन पुनः प्रयास लूप चलाता है। केवल क्लाइंट requestNetwork पुनः प्रयास करता है; सर्वर प्राप्त हर hello पर ठीक एक प्रयास करता है। यह 500 ms विंडो के लिए महत्वपूर्ण है (अगला अनुभाग)।
  2. कौन पोर्ट सेट करता है। पब्लिशर setPort(httpsPort) को कॉल करता है क्योंकि यह अपने HTTPS सर्वर पोर्ट पर इनबाउंड कनेक्शन स्वीकार करने वाला है। सब्सक्राइबर पोर्ट सेट नहीं करता — यह डेटा पथ स्थापित होने के बाद पीयर का पोर्ट WifiAwareNetworkInfo से जानता है।

दो-चरण हैंडशेक (hello + ready) {#the-two-phase-handshake-hello--ready}

Wi-Fi Aware डेटा-पथ सेटअप का सबसे कठिन हिस्सा टाइमिंग है। फ़्रेमवर्क को दोनों पक्षों को एक-दूसरे के ~500 ms के भीतर connectivityManager.requestNetwork कॉल करने की आवश्यकता है — यदि एक पक्ष दूसरे के मिलान अनुरोध पंजीकृत करने से पहले इसे कॉल करता है, तो फ़्रेमवर्क इसे तुरंत onUnavailable ("releaseRequestAsUnfulfillableByAnyFactory") के साथ अस्वीकार करता है।

PlainApp इसे एक दो-संदेश एप्लिकेशन-लेयर हैंडशेक से हल करता है जो Aware L2 संदेश चैनल (वही sendMessage API जो onServiceDiscovered द्वारा उपयोग होता है) पर चलता है:

Diagram 5
5

केवल एक के बजाय दो संदेश (hello + ready) क्यों?

केवल hello पर्याप्त नहीं है दिशा असममितता के कारण। सब्सक्राइबर hello तब भेज सकता है जब वह पब्लिशर खोजता है (in onServiceDiscovered), लेकिन पब्लिशर requestNetwork तब तक शुरू नहीं कर सकता जब तक उसके पास सब्सक्राइबर का PeerHandle न हो, जो वह hello प्राप्त करके जानता है। इसलिए hello दो उद्देश्यों की सेवा करता है:

  1. सब्सक्राइबर का PeerHandle पब्लिशर को दें। पब्लिशर को इसकी ज़रूरत WifiAwareNetworkSpecifier बनाने के लिए है।
  2. कनेक्ट करने के इरादे का संकेत। hello प्राप्त करना पब्लिशर को बताता है "सब्सक्राइबर requestNetwork करने वाला है, इसलिए मुझे भी करना चाहिए।"

ready रसीद विपरीत दिशा के लिए मौजूद है — सब्सक्राइबर को बताने के लिए "पब्लिशर ने अपना requestNetwork पंजीकृत कर लिया है।" इसके बिना, सब्सक्राइबर का requestNetwork पब्लिशर के आगे दौड़ सकता है और फ़्रेमवर्क द्वारा अस्वीकार हो सकता है। ready रसीद एक नॉन-ब्लॉकिंग संकेत है: सब्सक्राइबर requestNetwork कॉल करने से पहले इसकी प्रतीक्षा नहीं करता (वह एक राउंड ट्रिप जोड़ता), लेकिन यदि यह तब आता है जब सब्सक्राइबर IDLE स्थिति में है (पुनः प्रयास के बीच), तो सब्सक्राइबर RETRY_DELAY_MS अंतराल की प्रतीक्षा किए बिना तुरंत पुनः प्रयास कर सकता है।

पुनः प्रयास-लूप असममितता

यह डिज़ाइन का सबसे सूक्ष्म हिस्सा है। केवल सब्सक्राइबर पुनः प्रयास करता है। पब्लिशर प्राप्त हर hello पर ठीक एक requestNetwork प्रयास करता है। ऐसा इसलिए है क्योंकि:

  • यदि दोनों पक्ष स्वतंत्र रूप से पुनः प्रयास करते, तो उनके पुनः प्रयास चक्र phase से बाहर हो जाते (अलग delay() अवधि, अलग GC pauses), और दोनों 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 ट्रांसपोर्ट में सबसे टाइमिंग-संवेदनशील संक्रिया है। यहाँ हर पक्ष पर क्या होता है:

Diagram 6
6

onUnavailable कॉलबैक का अर्थ

onUnavailable तब फायर होता है जब फ़्रेमवर्क मिलान पीयर अनुरोध खोजने से पहले requestNetwork को अस्वीकार करता है। PeerHandle स्वयं अभी भी मान्य है — केवल NDP (Neighbor Discovery Protocol) जोड़ीदारी विफल हो गई क्योंकि दूसरे पक्ष ने अभी तक पंजीकृत नहीं किया था। PlainApp इस मामले में जानबूझकर session.invalidatePeerHandle को कॉल नहीं करता, क्योंकि हैंडल को अमान्य करना वही एकमात्र संकेत त्याग देगा कि onServiceDiscovered कभी कॉल हुआ था (यह प्रति-सब्सक्राइब सत्र जीवनकाल में प्रति-पीयर एक बार फायर होता है)। हैंडल संरक्षित रहने पर, पुनः प्रयास इसे ताज़ा डिस्कवरी की प्रतीक्षा किए बिना पुनः उपयोग कर सकता है।

यह onMessageReceived से पब्लिशर-साइड हैंडल पर भी लागू होता है — पब्लिशर विफल प्रयासों में publishPeerHandles[fromCid] प्रविष्टि रखता है, इसलिए सब्सक्राइबर का अगला hello कैश्ड हैंडल का पुनः उपयोग करता है बजाय गिरा दिए जाने के।

हर पेयर्ड पीयर को अपना AwarePeerLink ऑब्जेक्ट मिलता है, जो प्रोसेस-व्यापी AwareLinkPool का स्वामित्व है। पूल डिस्कवरी घटनाओं, लिंक पुनः उपयोग, और निष्क्रिय निष्कासन को संभालता है।

Diagram 7
7

डिस्कवरी पर कोई ऑटो-बिल्ड क्यों नहीं?

पूल स्पष्ट रूप से नहीं बनाता जब onServiceDiscovery फायर होता है। यह एक महत्वपूर्ण निर्णय है: एक व्यस्त कॉफ़ी शॉप में 100 PlainApp डिवाइस सभी "plain-peer" सेवा प्रकाशित कर रहे हो सकते हैं। यदि हर डिस्कवरी requestNetwork को ट्रिगर करती, तो फ़्रेमवर्क NDP सेटअप प्रयासों से भर जाता और Wi-Fi रेडियो संतृप्त हो जाता।

इसके बजाय, पूल केवल PeerHandle रिकॉर्ड करता है और इनमें से एक की प्रतीक्षा करता है:

  1. स्थानीय उपयोगकर्ता संदेश भेजता है → WifiAwareTransport.send → pool.buildLink(peer) (प्रेषक-साइड ट्रिगर)।
  2. दूरस्थ पीयर hello भेजता है → onPublishHelloReceived → buildLink(peer) (रिसीवर-साइड ट्रिगर)।
  3. दूरस्थ पीयर 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 डेटा पथ केवल लिंक-लोकल IPv6 का उपयोग करते हैं। कोई IPv4 नहीं, कोई DNS सर्वर नहीं, कोई DHCP नहीं। पीयर का IPv6 पता onCapabilitiesChanged में WifiAwareNetworkInfo.peerIpv6Addr फ़ील्ड के माध्यम से वितरित होता है — एक fe80::... पता जो केवल Aware नेटवर्क इंटरफ़ेस पर सार्थक है।

PlainApp को इस पते पर HTTPS अनुरोध भेजने चाहिए, लेकिन OkHttp का https:// URL पार्सिंग होस्टनाम में कच्चे IPv6 लिटरल से इनकार करती है (https://[fe80::abcd]:8443/ काम करता है, लेकिन इसे कस्टम Dns रिज़ॉल्वर के माध्यम से रूट करना साफ़ है)। ट्रिक:

Diagram 8
8

सेंटिनल होस्टनाम क्यों?

विकल्प — IPv6 लिटरल सीधे URL में पास करना — हर कॉल साइट को लिंक-लोकल पते के बारे में जानने की आवश्यकता होगी। सेंटिनल होस्टनाम का उपयोग करके, URL निर्माण LAN और Aware के लिए समान है: दोनों एक मान्य https://<host>:<port>/peer_graphql URL तैयार करते हैं जिसे OkHttp पार्स कर सकता है। एकमात्र अंतर क्लाइंट से बँधा Dns कार्यान्वयन है — LAN सिस्टम DNS उपयोग करता है, Aware awareDns(peerIpv6) उपयोग करता है जो सेंटिनल होस्टनाम के लिए कैश्ड लिंक-लोकल पता लौटाता है और बाकी के लिए Dns.SYSTEM पर गिरता है।

network.socketFactory क्यों?

Android का Network ऑब्जेक्ट एक विशिष्ट नेटवर्क इंटरफ़ेस (इस मामले में, Aware डेटा पथ) का प्रतिनिधित्व करता है। network.socketFactory को कॉल करके और इसे OkHttp के socketFactory कॉन्फ़िग में पास करके, हम सभी TCP सॉकेट को Aware इंटरफ़ेस पर बनाने के लिए मजबूर करते हैं — नहीं डिफ़ॉल्ट Wi-Fi या सेलुलर इंटरफ़ेस। इसके बिना, OS अनुरोध को डिफ़ॉल्ट नेटवर्क के माध्यम से रूट करता, जहाँ लिंक-लोकल IPv6 पहुँच योग्य नहीं, और अनुरोध ENETUNREACH से विफल होता।

क्रिप्टोग्राफी: PMK डेरिवेशन और ChaCha20 पुनः उपयोग {#cryptography-pmk-derivation--chacha20-reuse}

Wi-Fi Aware डेटा पथ के लिए एक वैकल्पिक PMK (Pairwise Master Key) का समर्थन करता है। जब सेट हो, तो L2 लिंक स्वयं उस PMK से कूटलेखित होता है — Wi-Fi रेडियो एन्क्रिप्शन संभालता है, किसी एप्लिकेशन-लेयर क्रिप्टो की ज़रूरत नहीं।

PlainApp PMK को उसी ChaCha20 साझा कुंजी से व्युत्पन्न करता है जिसका उपयोग LanTransport और BleTransport एप्लिकेशन-लेयर एन्क्रिप्शन के लिए करते हैं:

Diagram 9
9

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 पर भेजा जाता है तो क्या होता है:

Diagram 10
10

उल्लेखनीय डिज़ाइन चुनाव

  • कनेक्शन पुनः उपयोग। BleTransport के विपरीत जो हर अनुरोध के बाद GATT कनेक्शन तोड़ता है, WifiAwareTransport 60 सेकंड निष्क्रिय विंडो के भीतर जितने अनुरोध उपयोगकर्ता करता है उतने के लिए Aware डेटा पथ पुनः उपयोग करता है। पहला अनुरोध ~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 क्लाइंट का उपयोग करते हैं:

Diagram 11
11

डाउनलोड के लिए अलग क्लाइंट क्यों?

चैट क्लाइंट (AwareHttpClientFactory.build) में 30 सेकंड requestTimeoutMillis है — GraphQL mutations के लिए उपयुक्त लेकिन 100 MB फ़ाइल डाउनलोड के लिए आपत्तिजनक। डाउनलोड क्लाइंट (buildFileDownload) सेट करता है:

  • connectTimeoutMillis = 10_000 (चैट के 5 सेकंड से अधिक, ताज़ा डेटा पथ पर धीमे पहले-पैकेट के प्रति अधिक सहिष्णु)
  • प्रति रीड readTimeout = 120 सेकंड (10 सेकंड के अंतर्निहित डिफ़ॉल्ट के विपरीत)
  • requestTimeoutMillis = 120_000 (2 मिनट — अधिकांश फ़ाइलों के लिए पर्याप्त)
  • retryOnConnectionFailure(true) — एक ड्रॉप हुआ मिड-डाउनलोड रीड पूरे स्थानांतरण को विफल करने के बजाय पुनः प्रयास होता है

यह ChaCha20 इंटरसेप्टर को भी छोड़ता है। /fs एंडपॉइंट कच्चे फ़ाइल बाइट्स परोसता है (हस्ताक्षरित GraphQL एनवलप नहीं), और L2 PMK (जब मौजूद) पहले से रेडियो लिंक कूटलेखित करता है। 50 MB वीडियो को सॉफ़्टवेयर में ChaCha20 से दोहरी-कूटलेखन CPU बर्बाद करेगा और स्थानांतरण धीमा करेगा।

स्ट्रीमिंग, बफ़रिंग नहीं

BLE पथ की तरह, Aware डाउनलोड फ़ाइल को एक ByteReadChannel के माध्यम से स्ट्रीम करते हैं — फ़ाइल बाइट्स आते ही एक टेम्प फ़ाइल में लिखी जाती है, मेमोरी में बफ़र नहीं। PeerFileDownloader 8 KB चंक्स पढ़ता है और हर सेकंड प्रोग्रेस घटनाएँ उत्सर्जित करता है। वही DownloadedResponse / PeerFileDownloader / DownloadQueue पाइपलाइन सभी ट्रांसपोर्ट में पुनः उपयोग होती है — ट्रांसपोर्ट-विशिष्ट केवल channel स्रोत है।

प्रीवार्मिंग: BLE-ट्रिगर्ड Aware स्टार्टअप {#prewarming-ble-triggered-aware-startup}

Aware पथ में सबसे बड़ी उपयोगकर्ता-दृश्य लेटेंसी पहला हैंडशेक है — यदि दोनों पक्षों ने अभी तक Aware शुरू नहीं किया है, तो उपयोगकर्ता के पहले संदेश को इसकी प्रतीक्षा करनी पड़ती है:

  1. स्थानीय Aware सत्र attach (~1 सेकंड)
  2. स्थानीय publish + subscribe प्रारंभ (~1 सेकंड)
  3. दूरस्थ पीयर का Aware स्टार्टअप (~2 सेकंड BLE पर)
  4. पारस्परिक डिस्कवरी (~1 सेकंड)
  5. NDP हैंडशेक (~400 ms)

पहला बाइट भेजे जाने से पहले ~5 सेकंड। इस लेटेंसी को छिपाने के लिए, PeerTransportPrewarmer ChatPage प्रवेश पर चलता है और दूरस्थ पीयर के Aware स्टार्टअप को BLE के माध्यम से ट्रिगर करता है:

Diagram 12
12

BLE की दोहरी भूमिका

BLE यहाँ दो उद्देश्यों की सेवा करता है:

  1. पीयर की वर्तमान Aware स्थिति पढ़ें (सस्ता, कोई GATT कनेक्ट नहीं — स्कैन प्रतिक्रिया का serviceData byte0 Aware फ़्लैग्स ले जाता है)।
  2. पीयर को Aware शुरू करने के लिए ट्रिगर करें यदि यह समर्थन करता है लेकिन वर्तमान में इसे नहीं चला रहा। यह नियमित BleTransport.send पथ से गुज़रता है — एक startAware GraphQL mutation साझा ChaCha20 कुंजी से कूटलेखित, GATT RPC के माध्यम से पीयर के /peer_graphql एंडपॉइंट पर वितरित।

यह कुछ जगहों में से एक है जहाँ ट्रांसपोर्ट केवल गिरने के बजाय सहयोग करते हैं: BLE का उपयोग सत्र को तेज़ Aware ट्रांसपोर्ट में प्री-एम्पटिवली अपग्रेड करने के लिए होता है, इससे पहले कि उपयोगकर्ता नोटिस करे।

आशावादी setAwareRunning(true) क्यों?

startAware mutation तब सफलता लौटाता है जब दूरस्थ पीयर का रिज़ॉल्वर WifiAwareTransport.start() को लागू करता है — लेकिन Aware सत्र वास्तव में अभी तक attached नहीं होता (onAttached अतुल्यकालिक फायर होता है)। PlainApp पीयर को आशावादी रूप से awareRunning = true चिह्नित करता है, क्योंकि:

  • यदि यह वास्तव में शुरू हो गया, तो अगला send Aware उपयोग करेगा (तेज़)।
  • यदि नहीं (उदा. पीयर का Wi-Fi बंद है), तो अगला send का buildLinkTransportUnavailable के साथ विफल होगा और स्वाभाविक रूप से BLE पर गिरेगा।
  • एक ग़लत सकारात्मक की कीमत एक ~5 सेकंड टाइमआउट है, स्थायी ब्लॉक नहीं — PeerCircuitBreaker विफलता दर्ज करता है लेकिन BLE लेग नहीं खोलता (BLE केवल अपनी विफलताओं पर खुलता है)।

30 सेकंड तक थ्रॉटल क्यों?

PeerTransportPrewarmer.prewarm(peerId) प्रति-पीयर एक टाइमस्टैम्प रिकॉर्ड करता है और 30 सेकंड के भीतर पुनः-चलने से इनकार करता है। ऐसा इसलिए है क्योंकि उपयोगकर्ता चैट सूची और चैट पृष्ठ के बीच बार-बार नेविगेट करता है — थ्रॉटलिंग के बिना, हर नेविगेशन एक BLE स्कैन + startAware mutation ट्रिगर करता, बैटरी निकालता और BLE रेडियो स्पैम करता। 30 सेकंड की विंडो पीयर को पकड़ने के लिए पर्याप्त छोटी है जो अभी ऑनलाइन आया (उदा. उपयोगकर्ता ने दूरस्थ डिवाइस पर ऐप खोला) लेकिन स्पुरियस पुनः-चलने से बचने के लिए पर्याप्त लंबी।

विफलता मोड और फास्ट-स्किप फ़्लैग {#failure-modes--the-fast-skip-flag}

Aware के किसी भी अन्य ट्रांसपोर्ट से अधिक विफलता मोड हैं। isAwareRunning फास्ट-स्किप फ़्लैग पूरे मॉड्यूल में सबसे महत्वपूर्ण अनुकूलन है — इसके बिना, हर send BLE पर गिरने से पहले buildLink टाइमआउट पर 10 सेकंड बर्बाद करता।

Diagram 13
13

isAwareRunning फ़्लैग कीलक है

इस एकल बूलियन के बिना, हर Aware send या तो:

  • हमेशा buildLink का प्रयास करता → उस पीयर के लिए हर send पर 10 सेकंड टाइमआउट जिसका Aware नहीं चल रहा।
  • हमेशा Aware छोड़ता → कभी उपयोग नहीं करता जब दोनों पक्षों में यह चल रहा हो।

फ़्लैग दो स्रोतों से, अधिकार के क्रम में रिफ़्रेश होता है:

  1. BLE स्कैन प्रतिक्रिया (सस्ता, कोई GATT कनेक्ट नहीं) — PeerTransportPrewarmer.refreshAwareFlagFromScan द्वारा सेट। पीयर अपनी Aware स्थिति 9-बाइट serviceData पेलोड (byte0 बिटफ़ील्ड) में विज्ञापित करता है।
  2. GATT DISCOVER रिप्लाई (अधिकारशाली) — PairingTransport.scanAndDiscover द्वारा सेट जब एक पूर्ण डिस्कवरी होती है। यह स्कैन संकेत को अधिलेखित करता है।

जब ग़लत, WifiAwareTransport.send और downloadFileतुरंत TransportUnavailable फेंकते हैं — कोई स्कैन नहीं, कोई हैंडशेक नहीं, कोई टाइमआउट नहीं। राउटर माइक्रोसेकंड में BLE पर गिरता है।

मुख्य स्थिरांक संदर्भ {#key-constants-reference}

ConstantValueWherePurpose
AwareSession.SERVICE_NAME"plain-peer"डिस्कवरीहर PlainApp डिवाइस द्वारा प्रकाशित और सब्सक्राइब किया गया सेवा नाम
AwareSession.PEER_HANDLE_MAX_AGE_MS30 000PeerHandle कैशपुराने हैंडल त्यागें (पीयर का publish सत्र पुनःप्रारंभ हो गया होगा)
AwareSession.READY_TIMEOUT_MS15 000हैंडशेकपब्लिशर की ready रसीद के लिए सब्सक्राइबर प्रतीक्षा
AwareSession.MSG_HELLO0हैंडशेकसब्सक्राइबर → पब्लिशर संदेश ID
AwareSession.MSG_READY1हैंडशेकपब्लिशर → सब्सक्राइबर संदेश ID
AwarePeerLink.MAX_BUILD_ATTEMPTS1हैंडशेक (केवल क्लाइंट)एकल प्रयास — पहले 3 था, अब 1 क्योंकि प्रीवार्मर दोनों पक्षों को प्राइम करता है
AwarePeerLink.ATTEMPT_TIMEOUT_MS5 000हैंडशेकप्रति-प्रयास टाइमआउट — पहले 10 सेकंड था, फ़ॉलबैक गति के लिए आधा किया
AwarePeerLink.RETRY_DELAY_MS500हैंडशेकपुनः प्रयास के बीच विलंब (केवल क्लाइंट)
AwarePeerLink.REQUEST_TIMEOUT_MS30 000NDPconnectivityManager.requestNetwork टाइमआउट
AwareLinkPool.IDLE_TIMEOUT_MS60 000पूल स्वीप60 सेकंड निष्क्रियता के बाद निष्क्रिय लिंक बंद करें
AwareLinkPool.IDLE_SWEEP_INTERVAL_MS10 000पूल स्वीपस्वीप अंतराल
AwareHttpClientFactory.AWARE_HOST"plain-aware-peer"DNSसेंटिनल होस्टनाम कस्टम Dns द्वारा पीयर IPv6 में हल
build चैट क्लाइंटconnectTimeout 5 सेकंड, requestTimeout 30 सेकंड, ChaCha20 इंटरसेप्टर
buildFileDownloadconnectTimeout 10 सेकंड, readTimeout 120 सेकंड, requestTimeout 120 सेकंड, कोई क्रिप्टो नहीं
PeerTransportPrewarmer.PREWARM_TTL_MS30 000प्रीवार्मप्रति-पीयर थ्रॉटल
PeerTransportPrewarmer.BLE_SCAN_TIMEOUT_MS15 000प्रीवार्मrefreshAwareFlagFromScan के लिए BLE स्कैन टाइमआउट
PeerCircuitBreaker.WINDOW_MS30 000सर्किट ब्रेकरथ्रेशोल्ड के बाद खुली अवधि
PeerCircuitBreaker.MAX_FAILURES2सर्किट ब्रेकरखोलने के लिए विंडो के भीतर विफलताएँ
TempData.httpsPort8443 (डिफ़ॉल्ट)सर्वरWifiAwareNetworkSpecifier.setPort के माध्यम से विज्ञापित पब्लिशर का पोर्ट
BleServiceData.AWARE_SUPPORTED0x01BLE स्कैन प्रतिक्रियाबिट जो दर्शाता है कि पीयर Wi-Fi Aware समर्थन करता है
BleServiceData.AWARE_RUNNING0x02BLE स्कैन प्रतिक्रियाबिट जो दर्शाता है कि पीयर की Aware सेवा वर्तमान में चल रही है

डिज़ाइन ट्रेड-ऑफ़ सारांश {#design-trade-offs-recap}

Diagram 14
14

आगे पढ़ने के लिए

  • Chat Architecture — WifiAwareTransportLAN → Aware → BLE फ़ॉलबैक श्रृंखला और व्यापक चैट भेजना/प्राप्त करना पाइपलाइन में कैसे फिट बैठता है।
  • BLE Transport — अंतिम-उपाय ट्रांसपोर्ट जो Aware अनुपलब्ध होने पर काबू लेता है; वही चैनल जो प्रीवार्मर दूरस्थ पीयर पर Aware स्टार्टअप ट्रिगर करने के लिए उपयोग करता है।
  • Pairing Flow — साझा ChaCha20 कुंजी जो Aware PMK के रूप में पुनः उपयोग होती है, कैसे स्थापित होती है, और BLE स्कैन प्रतिक्रिया फ़्लैग्स (AWARE_SUPPORTED / AWARE_RUNNING) कैसे आबाद होते हैं।