নিবন্ধটি Android-কে-শুধু Aware সেশন লাইফসাইকেল, publish / subscribe ডিসকভারি মডেল, two-phase role-split হ্যান্ডশেক যা ফ্রেমওয়ার্কের ~500 ms উইন্ডোর মধ্যে উভয় পক্ষে requestNetwork সিঙ্ক্রোনাইজ করে, আইডল-সুইপিং সহ প্রতি-পিয়ার লিংক পুল, IPv6 + কাস্টম-DNS ট্রিক যা একটিমাত্র OkHttp ক্লায়েন্টকে LAN এবং Aware উভয় পরিসেবা দিতে দেয়, এবং প্রিওয়ার্মার যা 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 রাউন্ড ট্রিপ এবং কয়েক ডজন KB/s-এর বিপরীতে MB/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 সোর্স সেট)।
এটাই কারণ যে 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 s) এবং ডিভাইস-থেকে-ডিভাইস লিংকের জন্য একটি দ্বিতীয় Wi-Fi রেডিও কন্টেক্সট যোগ করে। উভয়ই রিচেবল হলে, 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 ডিসকভারি মডেল অসিমেট্রিক: একটি পাবলিশার একটি সার্ভিস অ্যাডভার্টাইজ করে,
একটি সাবস্ক্রাইবার সেটির জন্য স্ক্যান করে। ডিসকভারিকে সিমেট্রিক করতে (উভয়
ডিভাইস একে অপরকে আবিষ্কার করে), PlainApp একসাথে উভয়টি করে। এটি ছাড়া,
ডিভাইস A-কে আগেই জানতে হতো যে এটি একটি নির্দিষ্ট পিয়ারের জন্য পাবলিশার নাকি
সাবস্ক্রাইবার — কিন্তু পিয়ার রোলগুলো পরে clientId তুলনা দ্বারা নির্ধারিত হয়
(ডিসকভারি ও রোল অ্যাসাইনমেন্ট দেখুন)।
একই সাথে পাবলিশ এবং সাবস্ক্রাইব করার অর্থ প্রতিটি ডিভাইস অন্যটির
onServiceDiscovered (সাবস্ক্রাইবার হিসেবে) দেখে এবং অন্যটির
hello বার্তাগুলো (পাবলিশার হিসেবে) গ্রহণ করে — হ্যান্ডশেকের উভয় দিক
সবসময় উপলব্ধ থাকে।
টার্মিনেশনে অটো-রিস্টার্ট
কিছু Android ভ্যারিয়েন্ট (বিশেষত MIUI) ব্যাটারি বাঁচাতে দীর্ঘস্থায়ী Aware
সেশন কিল করে। PlainApp এটি onSessionTerminated
কলব্যাকগুলোতে হ্যান্ডেল করে: এটি টার্মিনেটেড সেশনটিকে null করে এবং সাথে সাথে
এখনও-অ্যাটাচড WifiAwareSession-এ আবার publishOwnService / subscribeOwnService কল করে।
অ্যাটাচ সেশনটি নিজে হারায় না — শুধুমাত্র publish/subscribe ডিসকভারি সেশনটি।
টার্মিনেশনের আগের পিয়ার হ্যান্ডেলগুলো পুরোনো হয়ে যায়, যা কারণ awaitPeerHandlediscoveredAt টাইমস্ট্যাম্প চেক করে এবং 30 s-এর পুরোনো হ্যান্ডেল বাতিল করে।
ডিসকভারি ও রোল অ্যাসাইনমেন্ট {#discovery--role-assignment}
Wi-Fi Aware ডেটা-পাথ প্রোটোকলের প্রয়োজন এক পক্ষের পাবলিশার (সার্ভার) হিসেবে কাজ করা এবং অন্যটিকে সাবস্ক্রাইবার (ক্লায়েন্ট) হিসেবে। উভয় পক্ষ একই সাথে ইনিশিয়েটর হতে পারে না — ফ্রেমওয়ার্ক ম্যাচিং কাউন্টারপার্ট ছাড়া রিকোয়েস্ট রিজেক্ট করে।
PlainApp রোলগুলো প্রতি-পিয়ার ডিটারমিনিস্টিকভাবে clientIds-এর একটি সাধারণ লেক্সিকোগ্রাফিক তুলনা ব্যবহার করে অ্যাসাইন করে:
কেন ডিটারমিনিস্টিক এবং নেগোশিয়েটেড নয়?
একটি নেগোশিয়েটেড অ্যাপ্রোচ (যেমন "নিম্ন MAC হলো সার্ভার") একটি
অতিরিক্ত বার্তা এক্সচেঞ্জ প্রয়োজন হবে। লেক্সিকোগ্রাফিক তুলনা idempotent,
symmetric, এবং stateless: উভয় ডিভাইস কোনো কমিউনিকেশন ছাড়াই একই জোড়ের জন্য
একই রোল কম্পিউট করে। 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 ডেটা-পাথ সেটআপের সবচেয়ে কঠিন অংশ হলো টাইমিং।
ফ্রেমওয়ার্ক উভয় পক্ষের জন্য connectivityManager.requestNetwork কল করার প্রয়োজন
একে অপরের থেকে আনুমানিক 500 ms-এর মধ্যে — যদি এক পক্ষ অন্যটির
ম্যাচিং রিকোয়েস্ট রেজিস্টার করার আগে এটি কল করে, ফ্রেমওয়ার্ক সাথে সাথে
onUnavailable ("releaseRequestAsUnfulfillableByAnyFactory") দিয়ে রিজেক্ট করে।
PlainApp এটি একটি টু-মেসেজ অ্যাপ্লিকেশন-লেয়ার হ্যান্ডশেক দিয়ে সমাধান করে
যা Aware L2 মেসেজ চ্যানেলের উপরে চলে (একই sendMessage
API যা onServiceDiscovered ব্যবহার করে):
কেন শুধু একটির বদলে দুটি বার্তা (hello + ready)?
hello একাই যথেষ্ট নয় কারণ ডিরেকশন অ্যাসিমেট্রি রয়েছে।
সাবস্ক্রাইবার পাবলিশার আবিষ্কার করার সাথে সাথে hello পাঠাতে পারে
(onServiceDiscovered-এ), কিন্তু পাবলিশার 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 কল করে না, কারণ
হ্যান্ডেল ইনভ্যালিডেট করলে একমাত্র সিগন্যালটি বাতিল হয়ে যাবে যা
onServiceDiscovered কখনো কল করা হয়েছিল (এটি প্রতি সাবস্ক্রাইব সেশন লাইফটাইমে প্রতি পিয়ারে একবার ফায়ার করে)। হ্যান্ডেল সংরক্ষণের সাথে, রিট্রাই একটি
নতুন ডিসকভারির জন্য অপেক্ষা না করে এটি পুনঃব্যবহার করতে পারে।
একই কথা onMessageReceived থেকে পাবলিশার-সাইড হ্যান্ডেলের ক্ষেত্রেও প্রযোজ্য —
পাবলিশার ব্যর্থ চেষ্টা জুড়ে publishPeerHandles[fromCid] এন্ট্রি রাখে,
তাই সাবস্ক্রাইবারের পরবর্তী hello ক্যাশড হ্যান্ডেল পুনঃব্যবহার করে
বাদ পড়ার বদলে।
প্রতি-পিয়ার লিংক পুল ও আইডল সুইপিং {#per-peer-link-pool--idle-sweeping}
প্রতিটি পেয়ার্ড পিয়ার তার নিজস্ব AwarePeerLink অবজেক্ট পায়, যা
প্রসেস-ওয়াইড AwareLinkPool দ্বারা মালিকানাধীন। পুল ডিসকভারি ইভেন্ট, লিংক
পুনঃব্যবহার, এবং আইডল ইভিকশন হ্যান্ডেল করে।
কেন ডিসকভারিতে অটো-বিল্ড নেই?
পুল স্পষ্টভাবে লিংক বিল্ড করে না যখন
onServiceDiscovered ফায়ার করে। এটি একটি গুরুত্বপূর্ণ সিদ্ধান্ত: একটি ব্যস্ত কফি
শপে ১০০টি 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 ট্রিক
Wi-Fi Aware ডেটা পাথ শুধুমাত্র link-local IPv6 ব্যবহার করে। এখানে কোনো IPv4 নেই, কোনো
DNS সার্ভার নেই, কোনো DHCP নেই। পিয়ারের IPv6 অ্যাড্রেস
WifiAwareNetworkInfo.peerIpv6Addr ফিল্ডের মাধ্যমে
onCapabilitiesChanged-এ ডেলিভার করা হয় — একটি fe80::... অ্যাড্রেস যা শুধুমাত্র
Aware নেটওয়ার্ক ইন্টারফেসে অর্থপূর্ণ।
PlainApp-কে এই অ্যাড্রেসে HTTPS রিকোয়েস্ট পাঠাতে হবে, কিন্তু OkHttp-এর
https:// URL পার্সিং একটি hostname-এ র IPv6 লিটারেল রিফিউজ করে
(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) ব্যবহার করে যা
সেন্টিনেল hostname-এর জন্য ক্যাশড link-local অ্যাড্রেস রিটার্ন করে এবং
অন্য কিছুর জন্য Dns.SYSTEM-এ ফল-থ্রু করে।
কেন network.socketFactory?
Android-এর Network অবজেক্ট একটি নির্দিষ্ট নেটওয়ার্ক ইন্টারফেস উপস্থাপন করে (এই
ক্ষেত্রে, Aware ডেটা পাথ)। network.socketFactory কল করে এবং
এটি OkHttp-এর socketFactory কনফিগে পাস করে, আমরা সব TCP সকেট
Aware ইন্টারফেসে তৈরি হতে বাধ্য করি — ডিফল্ট Wi-Fi বা
সেলুলার ইন্টারফেসে নয়। এটি ছাড়া, OS রিকোয়েস্টটি
ডিফল্ট নেটওয়ার্কের মাধ্যমে রাউট করবে, যেখানে link-local IPv6 আনরিচেবল, এবং
রিকোয়েস্ট ENETUNREACH দিয়ে ব্যর্থ হবে।
ক্রিপ্টোগ্রাফি: PMK ডেরিভেশন ও ChaCha20 পুনঃব্যবহার
Wi-Fi Aware ডেটা পাথের জন্য একটি ঐচ্ছিক PMK (Pairwise Master Key) সাপোর্ট করে। সেট করা হলে, L2 লিংক নিজেই সেই PMK দিয়ে এনক্রিপ্ট করা হয় — Wi-Fi রেডিও এনক্রিপশন হ্যান্ডেল করে, কোনো অ্যাপ্লিকেশন-লেয়ার ক্রিপ্টো প্রয়োজন নেই।
PlainApp PMK একই ChaCha20 শেয়ার্ড কী থেকে ডেরাইভ করে যা
LanTransport এবং BleTransport অ্যাপ্লিকেশন-লেয়ার এনক্রিপশনের জন্য ব্যবহার করে:
কেন 32 বাইটে ট্রাঙ্কেট করা হয়?
Wi-Fi Aware PMK ঠিক 32 বাইট (256 বিট) হতে হবে। পেয়ারিং থেকে ChaCha20
শেয়ার্ড কী সাধারণ ক্ষেত্রেও 32 বাইট, তাই
raw.size == 32 ব্রাঞ্চ হলো সাধারণ পাথ। ট্রাঙ্কেশন/প্যাডিং
ফলব্যাক (তাত্ত্বিক) ক্ষেত্রে হ্যান্ডেল করে যেখানে কী ছোট সংরক্ষণ করা হয়েছিল
— জিরো দিয়ে 32 বাইটে প্যাডিং একটি ডিফেন্সিভ ব্যবস্থা, এমন কিছু নয়
যা সঠিকভাবে পেয়ার্ড পিয়ারের সাথে বাস্তবে ঘটে।
সাইনড এনভেলপ LAN-এর অভিন্ন
যেহেতু createCryptoHttpClient একই ফ্যাক্টরি যা
LanTransport ব্যবহার করে, Aware-এ L7 ক্রিপ্টো LAN-এর সাথে বাইট-ফর-বাইট অভিন্ন।
সার্ভার-সাইড PeerGraphQLService জানে না (বা পরোয়া করে না) কোন
ট্রান্সপোর্ট রিকোয়েস্ট ডেলিভার করেছে — এটি শুধু একটি সাইনড, এনক্রিপ্টেড
GraphQL পেলোড দেখে এবং পিয়ারের শেয়ার্ড কী দিয়ে ডিক্রিপ্ট করে। এটিই
"এক কোডবেস, অনেক ট্রান্সপোর্ট" নীতি যা
Chat Architecture-এ ডকুমেন্ট করা আছে।
বার্তা সেন্ড পাথ (এন্ড-টু-এন্ড)
সব একসাথে রাখলে — Wi-Fi Aware-এর মাধ্যমে একটি চ্যাট বার্তা পাঠানোর সময় কী ঘটে:
উল্লেখযোগ্য ডিজাইন চয়েস
- কানেকশন পুনঃব্যবহার।
BleTransport-এর বিপরীতে, যা প্রতিটি রিকোয়েস্টের পরে GATT কানেকশন ভেঙে দেয়,WifiAwareTransportAware ডেটা পাথ পুনঃব্যবহার করে 60 s আইল উইন্ডোর মধ্যে ইউজার যত রিকোয়েস্ট করে ততগুলোর জন্য। প্রথম রিকোয়েস্ট ~400 ms হ্যান্ডশেক পরিশোধ করে; পরবর্তী রিকোয়েস্টগুলো ~10 ms রাউন্ড ট্রিপ। - LAN-এর মতো একই ক্রিপ্টো। ChaCha20 ইন্টারসেপ্টর এবং সাইনড এনভেলপ
LAN-এর বাইট-অভিন্ন। পিয়ারের
PeerGraphQLServiceজানে না কোন ট্রান্সপোর্ট রিকোয়েস্ট ডেলিভার করেছে। - লিংক ফেইলিওরে কোনো প্রিএম্পশন নেই। যদি
buildLinkব্যর্থ হয়, ট্রান্সপোর্টTransportUnavailableথ্রো করে এবং রাউটার BLE-এ ফল-থ্রু করে।send-এর ভেতরে কোনো রিট্রাই নেই —AwarePeerLink.buildইতিমধ্যেই তার নিজস্ব ইন্টারনাল রিট্রাই লুপ করে (ক্লায়েন্টেMAX_BUILD_ATTEMPTS = 1, আরও যদি প্রিওয়ার্মার উভয় পক্ষকে প্রাইম করে থাকে)।
ফাইল ডাউনলোড পাথ (এন্ড-টু-এন্ড)
Aware-এর মাধ্যমে ফাইল ডাউনলোড চ্যাট বার্তার মতো একই ডেটা পাথ পুনঃব্যবহার করে, কিন্তু বড় ফাইল স্ট্রিমিংয়ের জন্য কনফিগার করা একটি আলাদা OkHttp ক্লায়েন্ট ব্যবহার করে:
ডাউনলোডের জন্য কেন আলাদা ক্লায়েন্ট?
চ্যাট ক্লায়েন্টের (AwareHttpClientFactory.build) একটি 30 s
requestTimeoutMillis আছে — GraphQL মিউটেশনের জন্য উপযুক্ত কিন্তু
একটি 100 MB ফাইল ডাউনলোডের জন্য বিপর্যয়কর। ডাউনলোড ক্লায়েন্ট
(buildFileDownload) সেট করে:
connectTimeoutMillis = 10_000(চ্যাটের 5 s-এর চেয়ে বেশি, একটি নতুন ডেটা পাথে ধীর ফার্স্ট-প্যাকেটের প্রতি বেশি টলারেন্ট)- প্রতি রিডে
readTimeout = 120 s(অন্তর্নিহিত ডিফল্ট 10 s-এর বিপরীতে) 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 স্টার্টআপ
Aware পাথে সবচেয়ে বড় ইউজার-ভিজিবল লেটেন্সি হলো প্রথম হ্যান্ডশেক — যদি উভয় পক্ষ এখনও Aware শুরু না করে থাকে, ইউজারের প্রথম বার্তার জন্য অপেক্ষা করতে হবে:
- লোকাল Aware সেশন অ্যাটাচ (~1 s)
- লোকাল publish + subscribe শুরু (~1 s)
- রিমোট পিয়ারের Aware স্টার্টআপ (~2 s BLE-এ)
- মিউচুয়াল ডিসকভারি (~1 s)
- NDP হ্যান্ডশেক (~400 ms)
প্রথম বাইট পাঠানোর আগে ~5 সেকেন্ড। এই লেটেন্সি লুকাতে,
PeerTransportPrewarmer ChatPage এন্ট্রিতে চলে এবং রিমোট
পিয়ারের Aware স্টার্টআপ BLE-এর মাধ্যমে ট্রিগার করে:
BLE-এর দ্বৈত রোল
BLE এখানে দুটি উদ্দেশ্যে কাজ করে:
- পিয়ারের বর্তমান Aware স্টেট পড়ুন (সস্তা, কোনো GATT কানেক্ট নেই —
স্ক্যান রেসপন্সের
serviceDatabyte0 Aware ফ্ল্যাগ বহন করে)। - পিয়ারকে Aware শুরু করতে ট্রিগার করুন যদি এটি সাপোর্ট করে কিন্তু বর্তমানে
এটি চালায় না। এটি নিয়মিত
BleTransport.sendপাথের মাধ্যমে যায় — একটিstartAwareGraphQL মিউটেশন শেয়ার্ড ChaCha20 কী দিয়ে এনক্রিপ্ট করা, GATT RPC-এর মাধ্যমে পিয়ারের/peer_graphqlএন্ডপয়েন্টে ডেলিভার করা।
এটি সেই কয়েকটি জায়গার একটি যেখানে ট্রান্সপোর্টগুলো সহযোগিতা করে বনাম শুধু ফলব্যাক করে: BLE ব্যবহার করা হয় সেশনকে দ্রুততর Aware ট্রান্সপোর্টে প্রি-এম্পটিভলি আপগ্রেড করতে, ইউজার খেয়াল করার আগেই।
কেন অপটিমিস্টিক setAwareRunning(true)?
startAware মিউটেশন সাফল্য রিটার্ন করে যখন রিমোট পিয়ারের
রিজলভার WifiAwareTransport.start() ইনভোক করে — কিন্তু Aware সেশন
আসলে অ্যাটাচ হয়নি (onAttached অ্যাসিঙ্ক্রোনাসভাবে ফায়ার করে)। PlainApp
পিয়ারকে অপটিমিস্টিকভাবে awareRunning = true চিহ্নিত করে, কারণ:
- যদি এটি আসলে শুরু হয়, পরবর্তী
sendAware ব্যবহার করবে (দ্রুত)। - যদি না হয় (যেমন পিয়ারের Wi-Fi বন্ধ), পরবর্তী
send-এরbuildLinkTransportUnavailableদিয়ে ব্যর্থ হবে এবং স্বাভাবিকভাবে BLE-এ ফলব্যাক করবে। - একটি ভুল পজিটিভের খরচ একটি ~5 s টাইমআউট, স্থায়ী
ব্লক নয় —
PeerCircuitBreakerফেইলিওর রেকর্ড করে কিন্তু BLE লেগ খোলে না (BLE শুধুমাত্র তার নিজস্ব ফেইলিওরে খোলে)।
কেন 30 s-এ থ্রোটল?
PeerTransportPrewarmer.prewarm(peerId) প্রতি পিয়ারের জন্য একটি টাইমস্ট্যাম্প রেকর্ড করে
এবং 30 s-এর মধ্যে পুনরায় চালানো প্রত্যাখ্যান করে। এটি কারণ ইউজার চ্যাট লিস্ট এবং
চ্যাট পেজের মধ্যে ঘন ঘন নেভিগেট করে — থ্রোটলিং ছাড়া,
প্রতিটি নেভিগেশন একটি BLE স্ক্যান + startAware ট্রিগার করবে,
ব্যাটারি নষ্ট করবে এবং BLE রেডিও স্প্যাম করবে। 30 s উইন্ডো
একটি পিয়ারকে ধরতে যথেষ্ট ছোট যে সে এইমাত্র অনলাইন এসেছে (যেমন ইউজার
রিমোট ডিভাইসে অ্যাপ খোলেছে) কিন্তু ভুয়া পুনরায় চালানো এড়াতে যথেষ্ট দীর্ঘ।
ফেইলিওর মোড ও ফাস্ট-স্কিপ ফ্ল্যাগ
Aware-এর অন্য যেকোনো ট্রান্সপোর্টের চেয়ে বেশি ফেইলিওর মোড রয়েছে।
isAwareRunning ফাস্ট-স্কিপ ফ্ল্যাগ পুরো মডিউলের সবচেয়ে গুরুত্বপূর্ণ
অপটিমাইজেশন — এটি ছাড়া, প্রতিটি send BLE-এ ফল ব্যাক করার আগে buildLink টাইমআউটে 10 s নষ্ট করবে।
isAwareRunning ফ্ল্যাগ হলো লিঞ্চপিন
এই একক বুলিয়ান ছাড়া, প্রতিটি Aware send হয়:
- সবসময়
buildLinkচেষ্টা করবে → এমন একটি পিয়ারে প্রতিটি সেন্ডে 10 s টাইমআউট যার Aware চলছে না। - সবসময় Aware স্কিপ করবে → এটি কখনো ব্যবহার করবে না এমনকি যখন উভয় পক্ষ এটি চালায়।
ফ্ল্যাগ দুটি সোর্স থেকে, অথরিটির ক্রমানুসারে রিফ্রেশ করা হয়:
- BLE স্ক্যান রেসপন্স (সস্তা, কোনো GATT কানেক্ট নেই) —
PeerTransportPrewarmer.refreshAwareFlagFromScanদ্বারা সেট। পিয়ার তার Aware স্টেট 9-বাইটserviceDataপেলোডে (byte0 বিটফিল্ড) অ্যাডভার্টাইজ করে। - GATT DISCOVER রিপ্লাই (অথরিটেটিভ) —
PairingTransport.scanAndDiscoverদ্বারা সেট যখন একটি পূর্ণ ডিসকভারি ঘটে। এটি স্ক্যান হিন্টকে ওভাররাইট করে।
ফলস হলে, WifiAwareTransport.send এবং downloadFile থ্রো করে
TransportUnavailable সাথে সাথে — কোনো স্ক্যান নেই, কোনো হ্যান্ডশেক নেই, কোনো
টাইমআউট নেই। রাউটার মাইক্রোসেকেন্ডে BLE-এ ফল-থ্রু করে।
কী কনস্ট্যান্ট রেফারেন্স
| কনস্ট্যান্ট | ভ্যালু | কোথায় | উদ্দেশ্য |
|---|---|---|---|
AwareSession.SERVICE_NAME | "plain-peer" | ডিসকভারি | প্রতিটি PlainApp ডিভাইস দ্বারা পাবলিশড এবং সাবস্ক্রাইবড সার্ভিস নাম |
AwareSession.PEER_HANDLE_MAX_AGE_MS | 30 000 | PeerHandle ক্যাশ | পুরোনো হ্যান্ডেল বাতিল করুন (পিয়ারের পাবলিশ সেশন পুনরায় চালু হয়ে থাকতে পারে) |
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 s ছিল, ফাস্ট ফলব্যাকের জন্য অর্ধেক করা হয়েছে |
AwarePeerLink.RETRY_DELAY_MS | 500 | হ্যান্ডশেক | রিট্রাই চেষ্টার মধ্যে বিলম্ব (শুধুমাত্র ক্লায়েন্ট) |
AwarePeerLink.REQUEST_TIMEOUT_MS | 30 000 | NDP | connectivityManager.requestNetwork টাইমআউট |
AwareLinkPool.IDLE_TIMEOUT_MS | 60 000 | পুল সুইপ | 60 s নিষ্ক্রিয়তার পরে আইল লিংক বন্ধ করুন |
AwareLinkPool.IDLE_SWEEP_INTERVAL_MS | 10 000 | পুল সুইপ | সুইপ ইন্টারভ্যাল |
AwareHttpClientFactory.AWARE_HOST | "plain-aware-peer" | DNS | কাস্টম Dns দ্বারা পিয়ার IPv6-এ রিজল্ভ করা সেন্টিনেল hostname |
build চ্যাট ক্লায়েন্ট | connectTimeout 5 s, requestTimeout 30 s, ChaCha20 ইন্টারসেপ্টর | ||
buildFileDownload | connectTimeout 10 s, readTimeout 120 s, requestTimeout 120 s, কোনো ক্রিপ্টো নেই | ||
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 সার্ভিস বর্তমানে চলছে তা নির্দেশকারী বিট |
ডিজাইন ট্রেড-অফ সারাংশ
আরও পড়ার জন্য
- Chat Architecture — কীভাবে
WifiAwareTransportLAN → Aware → BLEফলব্যাক চেইন এবং বৃহত্তর চ্যাট সেন্ড/রিসিভ পাইপলাইনে ফিট করে। - BLE Transport — শেষ-অবলম্বন ট্রান্সপোর্ট যা Aware অনুপলব্ধ হলে দায়িত্ব নেয়; এটিও চ্যানেল যা প্রিওয়ার্মার দ্বারা রিমোট পিয়ারে Aware স্টার্টআপ ট্রিগার করতে ব্যবহৃত হয়।
- Pairing Flow — শেয়ার্ড ChaCha20 কী কীভাবে
Aware PMK হিসেবে পুনঃব্যবহৃত হয় তা স্থাপন করা হয়, এবং BLE স্ক্যান রেসপন্স ফ্ল্যাগ
(
AWARE_SUPPORTED/AWARE_RUNNING) কীভাবে পপুলেট হয়।