வலைப்பதிவுக்குத் திரும்பு
Transport15 min read

BLE போக்குவரத்து வடிவமைப்பு — செய்திகள் & கோப்பு பதிவிறக்கங்கள்

LAN அல்லது Wi-Fi Aware இரண்டும் கிடைக்காதபோது PlainApp எப்படி Bluetooth Low Energy வழியாக அரட்டைச் செய்திகளைத் தள்ளுகிறது மற்றும் கோப்புகளைப் பதிவிறக்குகிறது என்பதை இக்கட்டுரை விளக்குகிறது. BLE உத்தரவாதமான fallback: மெதுவானது, ஆனால் எந்த IP இணைப்பும் இல்லாமலேயே வேலை செய்கிறது. இக்கட்டுரை wire வடிவம், இரண்டு-அடுக்கு chunking வடிவமைப்பு, ஒரே நேரத்தில் நிகழும் போக்குவரத்து எப்படி முன்னுரிமைப்படுத்தப்படுகிறது (மற்றும் செய்யப்படுவதில்லை), மற்றும் ஒவ்வொரு கோரிக்கைக்குப் பிறகும் ஒவ்வொரு இணைப்பும் ஏன் இடிக்கப்படுகிறது ஆகியவற்றை உள்ளடக்கியது.

இந்தப் போக்குவரத்தைப் பயன்படுத்தும் பரந்த அரட்டைக் கட்டமைப்பிற்கு, அரட்டைக் கட்டமைப்பு-ஐப் பார்க்கவும். ஒவ்வொரு BLE சுமையையும் மறைகுறியாக்கப் பயன்படுத்தப்படும் பகிரப்பட்ட ChaCha20 விசையை இரு சாதனங்கள் எப்படிப் பெறுகின்றன என்பதற்கு பெயரிங் பாய்வு-ஐப் பார்க்கவும்.

பொருளடக்கம்

BLE போக்குவரத்து ஏன் கொஞ்சமும்? {#why-a-ble-transport-at-all}

PlainApp சேவையகமற்றது மற்றும் ஆஃப்லைன்-முதல். போக்குவரத்து அடுக்கு ஒரு வரிசையான fallback சங்கிலி: LAN → Wi-Fi Aware → BLE. LAN சிறந்த பாதை (Wi-Fi வழியாக HTTPS, ~10 ms ரவுண்ட் ட்ரிப்கள்). Wi-Fi Aware குறுக்கு-சப்நெட் பியர்களை (வெவ்வேறு SSID-கள், guest vs IoT VLAN-கள்) உள்ளடக்குகிறது. இரண்டும் ஏதோ ஒருவகை IP இணைப்பு தேவை. BLE மட்டுமே வேலை செய்யும் போக்குவரத்து:

  • சாதனங்கள் ஒரே IP நெட்வொர்க்கில் இல்லாதபோது.
  • Wi-Fi ஆஃப் செய்யப்பட்டோ அல்லது விமான பயன்முறையிலோ இருக்கும்போது (BLE ரேடியோ தனி).
  • Wi-Fi Aware ஆதரிக்கப்படாதபோது (Android < 13, PlainApp-ன் அனைத்து iOS மாறுபாடுகள்).

BLE மெதுவானது — பத்துகள் KB/s, ஒவ்வொரு கோரிக்கைக்கும் விநாடிகள் தாமதம் — ஆனால் எந்தப் பெயர் செய்த பியருக்கும் உத்தரவாதம், ஏனெனில் அதற்குத் தேவையான ஒன்றே பியரின் clientId, இது எப்போதும் BLE ஸ்கேன் பதிலில் ஒளிபரப்பப்படுகிறது.

Diagram 1
1

GATT சேவை அமைப்பு

PlainApp இரண்டு பண்புகளுடன் ஒரே ஒரு தனிப்பயன் GATT சேவையை விளம்பரப்படுத்துகிறது. பதிவுசெய்யப்பட்ட 16-பிட் UUID இல்லை — சேவை 128-பிட் UUID-ஐப் பயன்படுத்துகிறது, அதன் பின் வரும் பைட்டுகள் ASCII-ஆக plpai\x01-ஆக டிகோட் செய்யப்படுகின்றன:

Diagram 2
2

ஏன் இரண்டு பண்புகள்?

இரண்டு நெறிமுறைகளும் முற்றிலும் வேறுபட்ட நம்பிக்கை மாதிரிகள் மற்றும் சுமை வடிவங்களைக் கொண்டுள்ளன:

  • NEARBY பெயரிங் செய்திகளைக் கொண்டு செல்கிறது. அவை பியர் பெயர் செய்யப்படுவதற்கு முன்பு வருகின்றன (இன்னும் பகிரப்பட்ட விசை இல்லை), எனவே அவை தங்கள் சொந்த Ed25519-கையொப்ப JSON சுமைகளையும் தங்கள் சொந்த முன்னொட்ட ரூட்டிங்கையும் பயன்படுத்துகின்றன. உடல் வெறும் சரம்.
  • HTTP பெயரிங்கிற்குப் பின்னரான எல்லா போக்குவரத்தையும் கொண்டு செல்கிறது (அரட்டை, கோப்புகள், presence). இது எப்போதும் பகிரப்பட்ட விசையுடன் ChaCha20-ஆல் மறைகுறியாக்கப்பட்டு, LAN Ktor சேவையகத்தின் அதே HttpRouteRegistry-ஐப் பயன்படுத்துகிறது, எனவே ரூட் கையாளுநர்கள் (/peer_graphql, /fs, /peer_status) ஒருமுறை எழுதப்பட்டு இரு போக்குவரத்துகளுக்கும் மீண்டும் பயன்படுத்தப்படுகின்றன.

வாசிப்புகளுக்குப் பதிலாக அறிவிப்புகள் ஏன்?

BLE ATT நெறிமுறை ஒரு பண்பு வாசிப்பை 512 பைட்டுகளுக்கு வரம்பிடுகிறது. ஒரு GraphQL பதில் அல்லது 16 KB கோப்புத் துண்டு மிகப் பெரியதாக இருக்கலாம். PlainApp இதை உண்மையான தரவுக்கு readCharacteristic-ஐ ஒருபோதும் பயன்படுத்தாமல் தவிர்க்கிறது — சேவையகத்தின் onCharacteristicReadRequest GATT_SUCCESS-உடன் வெற்றுச் சுமையைத் திருப்பி அனுப்புகிறது. அதற்குப் பதிலாக, கிளையன்ட் தனது கோரிக்கையைப் பண்புக்கு எழுதுகிறது, சேவையகம் துண்டாக்கப்பட்ட அறிவிப்புகளின் தொடரை அனுப்பி கிளையன்ட் மீண்டும் இணைப்பதன் மூலம் பதிலளிக்கிறது. இது BleDeviceApi.requestAsync, BleServerProtocol.handleWrite, மற்றும் AndroidBleGattServer.sendChunkedResponse-இல் ஆவணப்படுத்தப்பட்டுள்ளது.

பியர் அடையாளம்: shortId, MAC அல்ல

BLE விளம்பரப் பாக்கெட்டுகள் சிறியவை (31 பைட்டுகள்) மற்றும் BLE MAC முகவரி Android-ஆல் ஒவ்வொரு ~15 நிமிடங்களுக்கும் சீரற்றதாக்கப்படுகிறது — எனவே அதை நிலையான அடையாளங்காட்டியாகப் பயன்படுத்த முடியாது. PlainApp அதற்குப் பதிலாக ஸ்கேன் பதிலில் 9-பைட் serviceData சுமையை ஒளிபரப்புகிறது:

Diagram 3
3

முழு clientId-க்குப் பதிலாக துண்டிக்கப்பட்ட hash ஏன்?

13-எழுத்து clientId 13 பைட்டுகளில் பொருந்தும், ஆனால் PlainApp இரண்டு காரணங்களுக்காக 8-பைட் துண்டிக்கப்பட்ட SHA-256-ஐத் தேர்ந்தெடுக்கிறது:

  1. நிலையான பைட் பட்ஜெட். மொத்தம் 9 பைட்டுகள் 31-பைட் விளம்பரச் சுமையில் சேவை UUID (16 பைட்டுகள்), நீளம் மற்றும் வகைப் புலங்களுடன் (~27 பைட்டுகள் பயன்படுத்தப்பட்டது, 4 பைட்டுகள் headroom) வசதியாகப் பொருந்துகிறது.
  2. தனியுரிமை. BLE-ஐ ஸ்கேன் செய்யும் செயலற்ற பார்வையாளரால் shortId-இலிருந்து clientId-ஐ மீட்டெடுக்க முடியாது (SHA-256 hash-ன் 8-பைட் முன்னொட்டம் நடைமுறையில் மீளமுடியாதது). அவர்களால் ஏற்கனவே அதே shortId-ஐ விளம்பரப்படுத்திய பியரை அடையாளம் காண மட்டுமே முடியும் — PlainApp பயனர்களைப் பட்டியலிட முடியாது.

முழு clientId GATT வழியாக இணைந்து DDiscoverReply-ஐப் பரிமாறிய பியருக்கு மட்டுமே வெளிப்படுத்தப்படுகிறது — அதாவது பயனர் ஏற்கனவே தொடர்பு கொள்ளத் தேர்ந்தெடுத்த பியருக்கு.

இரண்டு-அடுக்கு Chunking வடிவமைப்பு

இது BLE போக்குவரத்தின் மிகச் சூக்ஷ்மமான பகுதி, இரண்டு அடுக்குகளையும் புரிந்துகொள்வது அவசியம், ஏனெனில் அவை முற்றிலும் வேறுபட்ட அளவுகள் மற்றும் நோக்கங்களைக் கொண்டுள்ளன:

Diagram 4
4

ஏன் 380 எழுத்துகள்?

பேச்சுவார்த்தை செய்யப்பட்ட ATT MTU Android-இல் 517 பைட்டுகள் (requestMtu(517) — BLE விவரக்குறிப்பால் அனுமதிக்கப்பட்ட அதிகபட்சம்) மற்றும் iOS-இல் ~185+ (CoreBluetooth-ஆல் தானாக-பேச்சுவார்த்தை செய்யப்பட்டது). ATT தலைப்பு (~3 பைட்டுகள்) மற்றும் BleSegmentData-ன் JSON உறை மேலதிகச் சுமையைக் ({"d":"...","s":N} ~12 பைட்டுகளைச் சேர்க்கிறது) கழித்தால், 380 எழுத்துகள் சுமை இரு தளங்களிலும் ஒரே ATT MTU-க்குள் வசதியாகப் பொருந்துகிறது. மதிப்பு சமச்சீர் (கிளையன்ட் கோரிக்கைத் துண்டுகளும் சேவையக அறிவிப்புத் துண்டுகளும் 380-ஐப் பயன்படுத்துகின்றன), இது குறியீட்டை எளிமையாக்குகிறது.

கோப்புத் துண்டுகளுக்கு ஏன் 16 KiB?

16 KiB கோப்புத் துண்டு base64-ஆக ~22 KiB JSON-ஆக குறியாக்கப்படுகிறது, இது ~58 GATT அறிவிப்புத் துண்டுகளாகப் பிரிக்கப்படுகிறது. ஒவ்வொரு requestAsync ரவுண்ட்-ட்ரிப்பும் BLE வழியாக விநாடிகள் எடுக்கும், எனவே குறைவான-ஆனால்-பெரிய துண்டுகள் ஒவ்வொரு துண்டின் மேலதிகச் சுமையைக் குறைக்கின்றன. மேலும் பெரியதாகச் செல்வது BLE RPC நேரமுடிவுகளை அடைய ஆபத்தை ஏற்படுத்தும் மற்றும் மோசமான முன்னேற்றக் கருத்தைத் தரும் (பயனர் முன்னேற்றத்தை ஒரு துண்டுக்கு ஒருமுறை மட்டுமே பார்க்கிறார்). 16 KiB என்பது அனுபவரீதியாக வரையறுக்கப்பட்ட சிறந்த இடம் — செயல்திறனுக்குப் போதுமான பெரியது, பதிலளிக்கும் முன்னேற்ற UI-க்குப் போதுமான சிறியது.

RPC முதன்மை: BleDeviceApi.requestAsync {#the-rpc-primitive-bledeviceapirequestasync}

ஒவ்வொரு BLE அரட்டைச் செய்தியும் ஒவ்வொரு கோப்புத் துண்டும் ஒரு BleResult-ஐத் திருப்பி அளிக்கும் BleDeviceApi.requestAsync(service, requestData)-க்கான ஒரு அழைப்பு — ஒரு suspend செயல்பாடு. இது அழைப்பவரின் கண்ணோட்டத்திலிருந்து ஒருங்கிணைந்தது: ஒரு கோரிக்கை → ஒரு முழுமையாக மீண்டும் இணைக்கப்பட்ட பதில், pipelining இல்லை.

Diagram 5
5

முக்கிய மாறாத நிலைகள்

  1. ஒரு கோரிக்கை → ஒரு பதில். requestAsync அழைப்பவரின் கண்ணோட்டத்திலிருந்து ஒருங்கிணைந்தது — முழு பதிலும் மீண்டும் இணைக்கப்பட்ட பிறகு மட்டுமே திருப்பி அளிக்கிறது. Pipelining இல்லை.
  2. ஒரு அழைப்புக்கு அறிவிப்புகள் இயக்கப்பட்டது. கிளையன்ட் ஒவ்வொரு requestAsync-ன் தொடக்கத்திலும் CCCD-ஐ எழுதி, முடிவில் முடக்குகிறது. இது வீண் (ஒரு அழைப்புக்கு இரண்டு கூடுதல் GATT எழுதுதல்கள்) ஆனால் நெறிமுறையை நிலையற்றதாக வைத்திருக்கிறது — சேவையகம் எந்தக் கிளையன்ட்கள் "கேட்டுக்கொண்டிருக்கிறார்கள்" என்பதைக் கண்காணிக்க வேண்டியதில்லை.
  3. ஒரு RPC-க்குள் மறுமுயற்சி இல்லை. எந்தத் தனிப்பட்ட writeCharacteristic நேரமுடிவாகினால் (5 வி), முழு RPCம் கைவிடப்படுகிறது. ensureConnected மட்டுமே மறுமுயற்சி செய்கிறது (இணைப்புத் தோல்வியில் 3 முயற்சிகள்). கரடுமுரடான போக்குவரத்து-நிலை backoff PeerCircuitBreaker-ஆல் வழங்கப்படுகிறது, RPC அடுக்கால் அல்ல.

Wire உறை வடிவம் {#wire-envelope-format}

Layer A துண்டுகளுக்குள் உள்ள சுமை ஒரு உள்ளமைக்கப்பட்ட JSON உறை. துண்டாக்கலை நீக்கினால், தர்க்கரீதியான அமைப்பு:

Diagram 6
6

பதில் வடிவம்

பதில் அதே Layer A துண்டாக்கல் வழியாக எதிர் திசையில் ஓடுகிறது, ஆனால் உள்ள JSON மூன்று புலங்களுடன் ஒரு BleHttpResponse: s (HTTP நிலைக் குறியீடு), h (பதில் தலைப்புகள் வரைபடம்), மற்றும் b (உடல்). உடல் BleHttpCall.encodeResponse()-ஆல் எப்போதும் base64-குறியாக்கப்படுகிறது, வெறுமையாக இருந்தாலும் கூட — பதில் பைனரியாக இருக்கலாம் (மறைகுறியாக்கப்பட்ட GraphQL பைட்டுகள், மூல /fs கோப்புப் பைட்டுகள்) மற்றும் BLE போக்குவரத்து சரம்-மட்டுமே, எனவே அதே JSON உறை உரை மற்றும் பைனரி சுமைகள் இரண்டையும் கொண்டு செல்கிறது.

செய்தி அனுப்பல் பாதை (முழுமையான) {#message-send-path-end-to-end}

எல்லாவற்றையும் ஒன்றிணைத்தால் — ஒரு அரட்டைச் செய்தி BLE வழியாக அனுப்பப்படும்போது என்ன நிகழ்கிறது:

Diagram 7
7

குறிப்பிடத்தக்க வடிவமைப்புத் தேர்வுகள்

  • LAN-ன் அதே விசை. பெயரிங்கிலிருந்து ChaCha20 பகிரப்பட்ட விசை BLE-க்கும் மீண்டும் பயன்படுத்தப்படுகிறது — தனி BLE விசை இல்லை. LanTransport-ஆல் பயன்படுத்தப்படும் OkHttp மறைகுறியாக்க இடைமறிப்பானும் BleTransport-இல் கைமுறையான chaCha20Encrypt/chaCha20Decrypt-உம் ஒரே முதன்மை, வெவ்வேறாக அழைக்கப்படுகின்றன.
  • LAN-ன் அதே ரூட் கையாளுநர்கள். BleHttpRequest HttpRouteRegistry.matchRoute(path)-உடன் ஒப்புவிக்கப்படுகிறது, இது Ktor LAN சேவையகம் பயன்படுத்தும் அதே பதிவேடு. எனவே /peer_graphql, /fs, /peer_status முதலியன ஒரே முறை செயல்படுத்தப்பட்டு இரு போக்குவரத்துகளிலும் ஒரே மாதிரி வேலை செய்கின்றன.
  • இணைப்பு மறுபயன்பாடு இல்லை. finally { scanner.teardownConnection(client) } தொகுதி எப்போதும் இயங்குகிறது. ஒவ்வொரு செய்தியும் முழு connect→discoverServices→MTU செலவையும் (~விநாடிகள்) செலுத்துகிறது. இது வேண்டுமென்றே செய்யப்பட்ட பரிமாற்றம் — வடிவமைப்பு பரிமாற்றங்கள்-ஐப் பார்க்கவும்.

கோப்பு பதிவிறக்கப் பாதை (முழுமையான) {#file-download-path-end-to-end}

BLE வழியாகப் பதிவிறக்கங்கள் ஸ்ட்ரீமிங் — கோப்பு 16 KiB துண்டுகளாகப் படிக்கப்பட்டு, வரும்போது தற்காலிகக் கோப்பில் எழுதப்படுகிறது, எனவே 10 MB கோப்புக்கு 10 MB RAM தேவையில்லை. தந்திரம் என்னவென்றால், ஒவ்வொரு துண்டின் RPC-உம் ஒரு தனி requestAsync அழைப்பு, மற்றும் துண்டுகள் நுகர்வோர் ஒரே நேரத்தில் படிக்கும் ஒரு ByteChannel-இல் தள்ளப்படுகின்றன.

Diagram 8
8

ஒரு பெரிய RPC-க்குப் பதிலாக ஏன் ஸ்ட்ரீமிங்?

ஒரு 10 MB கோப்பு ஒரே RPC-ஆக அனுப்பப்பட்டால் ~280 000 அறிவிப்புத் துண்டுகள் தேவை, அனைத்தும் பதில் தொடங்குவதற்கு முன்பே இரு தரப்பிலும் நினைவகத்தில் வைக்கப்படும் — மேலும் எந்த முன்னேற்றமும் அறிவிக்கப்படுவதற்கு முன்பு முழு பரிமாற்றமும் வெற்றிபெற வேண்டும். மோசமாக, நடுவில் ஒரு விடப்பட்ட அறிவிப்பு முழுவதையும் சேதப்படுத்தும்.

துண்டாக்கப்பட்ட வடிவமைப்பில் மூன்று வெற்றிகள்:

  1. மாறாத நினைவகம். ஒரு நேரத்தில் ஒரே ஒரு 16 KiB துண்டு மட்டுமே பயணத்தில் உள்ளது.
  2. நேரடி முன்னேற்றம். DownloadQueue.notifyProgressUpdate() ஒவ்வொரு விநாடியும் இயங்குகிறது, UI ஒரு பதிவிறக்கப் பட்டையைக் காட்டுகிறது.
  3. பின்னடைவு. ஒரு தோல்வியடைந்த துண்டு தனித்தனியாக மறுமுயற்சி செய்யப்படலாம் (DownloadQueue பணி நிலையில் இடைநிறுத்தம்/தொடர்/மறுமுயற்சியை ஆதரிக்கிறது; நடு-ஸ்ட்ரீம் தோல்வி பகுதி தற்காலிகக் கோப்பை விட்டுச்செல்கிறது, இருப்பினும் தற்போது பதிவிறக்கி தோல்வியில் அதை நீக்குகிறது — பரிமாற்றங்களைப் பார்க்கவும்).

onClose ஏன் பதிவிறக்க வேலையை ரத்து செய்கிறது

DownloadedResponse.onClose கால்பேக் downloadJob.cancel()-ஐ அழைக்கிறது. இது அவசியம், ஏனெனில் பதிவிறக்க வளையம் ஒரு குழந்தை coroutine-இல் இயங்குகிறது, நுகர்வோர் சேனலை சீக்கிரமாகக் கைவிட்டால் (உ.ம். பயனர் இடைநிறுத்தம் என்பதைத் தட்டினால்) அது நிரந்தரமாக இயங்கிக்கொண்டே இருக்கும். DownloadedResponse-இல் AutoCloseable ஒப்பந்தம் என்பது நுகர்வோரின் use { ... } தொகுதி வெளியேறும்போது தானாகவே onClose-ஐ இயக்குகிறது, BLE பதிவிறக்க coroutine-ஐ ரத்து செய்து, coroutine-ன் finally தொகுதியில் GATT இணைப்பை இடிக்கிறது.

முன்னுரிமைப்படுத்தல்: செயல்பாட்டில் அரட்டை கோப்புகளை எப்படி வெல்கிறது {#prioritization-how-chat-beats-files-in-practice}

இது எந்த அரட்டைச் செயலிக்கும் மிக முக்கியமான கேள்வி: மெதுவான BLE கோப்புப் பதிவிறக்கம் நிகழ்ந்துகொண்டிருக்கும்போது, ஒரு புதிய அரட்டைச் செய்தி அதற்கு முன்பாகத் தாண்ட முடியுமா?

நேர்மையான பதில்: வெளிப்படையான முன்னுரிமைத் திட்டம் எதுவும் இல்லை

BLE குறியீடு அல்லது பதிவிறக்க வரிசையில் முன்னுரிமைப் புலம் இல்லை, முன்னுரிமை வரிசை இல்லை, preemption இல்லை. இதை நான் முழுமையான grep-ஆல் சரிபார்த்தேன் — shared/src-இல் priority பொருந்துகள் log-priority நிலைகள் மற்றும் EXIF மெட்டாடேட்டா மட்டுமே, செய்தி-vs-பதிவிறக்க வரிசையுடன் தொடர்பில்லாதது.

அதற்குப் பதிலாக இருப்பது விரும்பிய நடத்தையை ஒரு வெளிப்படையான பண்பாக உருவாக்கும் கட்டமைப்பு பிரிவினைகளின் தொகுப்பு:

Diagram 9
9

செயல்பாட்டில் ஏன் வேலை செய்கிறது

அரட்டை "முன்னுரிமைப்படுத்தப்பட்டதாக" உணரச் செய்யும் பிரிவினை கட்டமைப்பு ரீதியானது:

  1. அரட்டை அனுப்பல்கள் DownloadQueue வழியாகச் செல்வதில்லை. அவை PeerGraphQLClient → PeerTransportRouter → BleTransport.send-ஆல் நேரடியாக வழங்கப்படுகின்றன. எனவே ஒரு அரட்டைச் செய்தி ஒருபோதும் கோப்புப் பதிவிறக்கங்களின் வரிசைக்குப் பின்பு அமர்வதில்லை.
  2. ஒவ்வொரு BleTransport அழைப்பும் தனது சொந்த GATT இணைப்பைத் திறக்கிறது. ஒரு நீண்ட-கால பதிவிறக்கம் ஒரு இணைப்பை வைத்திருப்பது ஒரு அரட்டை அனுப்பல் அதே பியருக்கு இரண்டாவது இணைப்பைத் திறப்பதைத் தடுப்பதில்லை. Android பல ஒரே நேர GATT இணைப்புகளை ஆதரிக்கிறது.
  3. அரட்டை RPC-கள் குறுகியவை. ஒரு அரட்டைச் செய்தி ஒரு requestAsync ரவுண்ட் ட்ரிப் (~1 வி இணைப்பிற்குப் பிறகு). ரேடியோ பதிவிறக்கத்துடன் பிஸியாக இருந்தாலும், அரட்டை அனுப்பல் சில விநாடிகளுக்குள் முடிகிறது.

வடிவமைப்பு எங்கு குறைகிறது

"வெளிப்படையான முன்னுரிமை இல்லை"-ன் பரிமாற்றங்கள்:

  • இணைப்பு தாமதம். அரட்டையும் பதிவிறக்கமும் connect→discover→MTU செலவை (~விநாடிகள்) ஒவ்வொரு முறையும் செலுத்துகின்றன, ஏனெனில் இணைப்புகள் மறுபயன்பாடு செய்யப்படவில்லை. பதிவிறக்கத்தின்போது வரும் அரட்டைச் செய்தி பதிவிறக்கத்தின் ஏற்கனவே இருக்கும் இணைப்பில் piggyback செய்ய முடியாது — அது புதியதைத் திறக்கிறது.
  • Android-இல் நிலையான வரிசை. AndroidBleGattClient-இல் செயல்முறை-அளவு operationQueue எல்லா பியர்கள் மற்றும் எல்லா இணைப்புகளிலும் GATT செயல்பாடுகளை வரிசைப்படுத்துகிறது. எனவே இரண்டு GATT இணைப்புகள் இணைந்திருக்கலாம் என்றாலும், அவற்றின் write/read/notify செயல்பாடுகள் வரிசை நிலையில் இடைமறிக்கப்படுகின்றன. நடைமுறையில் இது சரி (ஒவ்வொரு op-ம் ~ms) ஆனால் அதிக ஒரே நேரத்தில் நிகழும்போது இது ஒரு சூக்ஷ்மமான உலகளாவிய தடை.
  • Preemption இல்லை. நிகழ்ந்துகொண்டிருக்கும் பதிவிறக்கத்தை ஒரு அரட்டைச் செய்தியை விடுவிக்க இடைநிறுத்த முடியாது. அரட்டை அனுப்பல் வெறுமனே ஒரே நேரத்தில் இயங்கி, ரேடியோ நேரத்திற்காகப் போட்டியிடுகிறது.

எதிர்கால மேம்பாடு BleTransport.send மற்றும் downloadFile-ஐச் சுற்றி ஒரு பியர்-வாரியான Mutex-உம் வரிசையில் ஒரு முன்னுரிமைப் புலமும் இருக்கலாம் — ஆனால் தற்போதைய வடிவமைப்பு அரட்டை RPC-கள் போதுமான அளவு குறுகியவை, போட்டி அரிதாகவே பயனர்-காணக்கூடியது என்பதை நம்புகிறது.

ஒரே நேரக் கட்டுப்பாடு & நிலையான GATT வரிசை {#concurrency-control--the-static-gatt-queue}

இது Android BLE செயலாக்கத்தின் மிகச் சூக்ஷ்மமான அம்சம் என்பதால் இதற்குத் தனி பிரிவு தேவை.

Diagram 10
10

ஏன் நிலையானது (செயல்முறை-அளவு)?

Android BLE ஸ்டாக் ஒரு தனி BluetoothGatt நிகழ்வில் ஒரே நேர GATT செயல்பாடுகளை அனுமதிப்பதில்லை — மற்றொரு எழுத்தல் பயணத்தில் இருக்கும்போது writeCharacteristic-ஐ அழைப்பது false-ஐத் திருப்பி அனுப்பி, இரண்டாவது எழுத்தலை அமைதியாகக் கைவிடுகிறது. தரநிலை தீர்வு ஒரு ஒரு-BluetoothGatt வரிசை. PlainApp ஒரு படி மேலே சென்று ஒரு செயல்முறை-அளவு வரிசையை (companion object-இல்) பயன்படுத்துகிறது, இது அதிக பாதுகாப்பானது ஆனால் சரியானது: செயலியில் எங்கும் எந்த இரண்டு GATT செயல்பாடுகளும் ஒரே நேரத்தில் இயங்காது என்பதை உத்தரவாதம் செய்கிறது.

இதன் செலவு, ஒரு நீண்ட BLE கோப்புப் பதிவிறக்கத்தின் write/read/notify செயல்பாடுகள் வேறு எந்தப் பியரின் GATT செயல்பாடுகளுக்கும் பின்பும் (மற்றும் பின்பும்) வரிசைப்படுத்தப்படுகின்றன. ஒவ்வொரு தனிப்பட்ட op-உம் ~ms என்பதால், இது அரிதாகவே பயனர்-காணக்கூடிய தடை — ஆனால் பல பியர்களுக்கு கனரக ஒரே நேர BLE போக்குவரத்தின்கீழ், இது ஒன்றாகலாம்.

போக்குவரத்து அடுக்கில் பியர்-வாரிப் பூட்டு இல்லை

BleDeviceApi.requestAsync ஒரு சாதாரண suspend fun, mutex இல்லை, வரிசை இல்லை, பியர்-வாரி வரிசைப்படுத்தல் இல்லை. ஒரே பியருக்கு இரண்டு ஒரே நேர BleTransport.send அழைப்புகள் ஒவ்வொன்றும் தங்கள் சொந்த GATT இணைப்பைத் திறந்து சுயாதீனமாகத் தொடரும். வரிசைப்படுத்தல் GATT செயல்பாட்டு நிலையில் மறைமுகமாக நிகழ்கிறது (Android-இல் நிலையான வரிசை வழியாக, அல்லது iOS-இல் வரிசையான await வழியாக).

இணைப்பு வாழ்க்கைச்சுழற்சி & MTU பேச்சுவார்த்தை

Diagram 11
11

ஏன் requestMtu(517)?

இயல்புநிலை ATT MTU 23 பைட்டுகள் (3-பைட் ATT தலைப்பிற்குப் பிறகு சுமை 20 பைட்டுகள் மட்டுமே). இயல்புநிலை MTU-உடன், ஒவ்வொரு 380-எழுத்துத் துண்டும் 1-க்குப் பதிலாக ~19 GATT எழுத்தல்களைத் தேவைப்படும் — 19× மெதுவாக்கம். BLE விவரக்குறிப்பால் அனுமதிக்கப்பட்ட அதிகபட்ச MTU-ஐ (517 பைட்டுகள்) கோருவது 380-எழுத்துத் துண்டுகள் ஒரே ATT செயல்பாட்டில் பொருந்த அனுமதித்து, செயல்திறனை நாடகரீதியாக மேம்படுத்துகிறது.

iOS வெளிப்படையான MTU கோரிக்கை API-ஐ வெளிப்படுத்துவதில்லை — CoreBluetooth இணைப்பின்போது அதை peripheral-உடன் தானாகப் பேச்சுவார்த்தை நடத்துகிறது. நவீன iOS சாதனங்கள் பொதுவாக ~185 பைட்டுகளைப் பேச்சுவார்த்தை செய்கின்றன, இது 380-எழுத்துத் துண்டுகளை (ATT தலைப்பு + JSON உறை மேலதிகச் சுமையைக் கழித்த பிறகு) வசதியாகப் பொருந்த வைக்கிறது.

அறிவிப்புகளுக்கான ஓட்டக் கட்டுப்பாடு {#flow-control-for-notifications}

சேவையகம் பதில் துண்டுகளை அறிவிப்புகளாக அனுப்புகிறது, ஆனால் BLE அறிவிப்புகளுக்கு உள்ளமைக்கப்பட்ட ஓட்டக் கட்டுப்பாடு இல்லை — சேவையகம் கட்டுப்பாட்டாளரால் பரப்ப முடிந்ததை விட வேகமாக அறிவிப்புகளை அனுப்பினால், அவை அமைதியாகக் கைவிடப்படுகின்றன. PlainApp வெளிப்படையான ack-அடிப்படையிலான ஓட்டக் கட்டுப்பாட்டைச் செயல்படுத்துகிறது:

Diagram 12
12

இந்த ஓட்டக் கட்டுப்பாடு இல்லாவிட்டால், பின்-தொடர்-பின் தொடர் அறிவிப்புகள், BLE கட்டுப்பாட்டாளரின் உள்ளமை அனுப்பு வரிசை நிரம்பும்போது அமைதியாகக் கைவிடப்படும் — ஒரு நன்கு அறியப்பட்ட Android BLE பிரச்சினை, BleGattServer இடைமுகக் கருத்துகளில் ஆவணப்படுத்தப்பட்டுள்ளது. பியர்-வாரியான ஒரு-பயணத்தில்-ஒன்று விதி ஒவ்வொரு அறிவிப்பும் பரப்பப்படுகிறது அல்லது ஒரு நேரமுடிவைத் தூண்டுகிறது (இது பின்னர் போக்குவரத்துத் தோல்வியாகக் கருதப்படுகிறது) என்பதை உத்தரவாதம் செய்கிறது.

பிழைக் கையாளுதல்: TransportUnavailable vs உண்மையான தோல்வி

TransportUnavailable என்பது PeerTransportRouter-ஐ அடுத்த போக்குவரத்திற்கு விழச் சொல்லும் சமிக்ஞை. மற்றவை அனைத்தும் அழைப்பவருக்குத் திருப்பி அனுப்பப்பட்ட உண்மையான தோல்விகள்.

Diagram 13
13

பதிவிறக்கத் தோல்வி சூக்ஷ்மம்

BleTransport.downloadFile ஒரு DownloadedResponse(200, channel, onClose)-ஐ உடனடியாகத் திருப்பி அனுப்புகிறது — துண்டாக்கப்பட்ட பதிவிறக்க வளையம் சேனலுக்கு எழுதும் ஒரு பின்னணி coroutine-இல் இயங்குகிறது. ஒரு துண்டு RPC நடு-ஸ்ட்ரீமில் தோல்வியடைந்தால், வளையம் channel.close(TransportUnavailable(...))-ஐ அழைக்கிறது, அதாவது நுகர்வோர் (PeerFileDownloader.downloadAsync) பிழையை channel.readAvailable(buf)-இலிருந்து வீசப்பட்ட விதிவிலக்காகக் காண்கிறார்.

இதன் பொருள் PeerTransportRouter.downloadFile அழைப்பு வெற்றிபெற்றது (ஒரு DownloadedResponse-ஐத் திருப்பி அனுப்பியது), எனவே சுற்றுச் சுற்று-உடைப்பான் நடு-ஸ்ட்ரீம் பதிவிறக்கப் பிழைகளுக்கு ஒரு தோல்வியைப் பதிவு செய்வதில்லை. இணைப்பு-நேரம் மற்றும் ஸ்கேன்-நேரத் தோல்விகள் மட்டுமே ரூட்டரால் பிடிக்கப்படுகின்றன. இது வேண்டுமென்றே செய்யப்பட்ட வடிவமைப்புத் தேர்வு — நடு-ஸ்ட்ரீம் தோல்வி அந்தப் பியருக்கு BLE-ஐ நிரந்தரமாக முடக்கக் கூடாது (பியர் வெறுமனே தற்காலிகமாக வரம்பிற்கு வெளியே சென்றிருக்கலாம்).

முக்கிய மாறிலிகள் குறிப்பு

மாறிலிமதிப்புஎங்கேநோக்கம்
BleDeviceApi.CHUNK_SIZE380GATT துண்டு துண்டாக்கல்ஒவ்வொரு BleSegmentData.data-ன் அளவு (JSON மேலதிகச் சுமைக்குப் பிறகு ATT MTU-க்குள் பொருந்தும்)
BleTransport.CHUNK_SIZE16 384 (16 KiB)கோப்பு-பதிவிறக்க byte-வரம்புஒவ்வொரு /fs துண்டு கோரிக்கையின் அளவு
BleTransport.SCAN_TIMEOUT_MS10 000BLE ஸ்கேன்scanner.findOne-க்கான நேரமுடிவு
BleDeviceApi.NOTIFY_TIMEOUT_MS15 000RPC பதில்requestAsync-இல் ஒரு-அறிவிப்பு காத்திருப்பு
AndroidBleGattClient MTU517இணைப்பு அமைப்புrequestMtu(517) — BLE விவரக்குறிப்பால் அதிகபட்சம்
AndroidBleGattClient connect timeout10 000இணைப்பு அமைப்புSTATE_CONNECTED-க்காக காத்திருப்பு
AndroidBleGattClient MTU timeout5 000இணைப்பு அமைப்புonMtuChanged-க்காக காத்திருப்பு
AndroidBleGattClient write timeout5 000GATT எழுத்தல்onCharacteristicWrite-க்காக காத்திருப்பு
AndroidBleGattClient read timeout10 000GATT வாசிப்புonCharacteristicRead-க்காக காத்திருப்பு (உண்மையான தரவுக்குப் பயன்படுத்தப்படவில்லை)
AndroidBleGattClient notify-state timeout5 000CCCD எழுத்தல்CCCD விளக்க எழுத்தலுக்காக காத்திருப்பு
ensureConnected retries3இணைப்பு அமைப்புமொத்தம் 4 முயற்சிகள் வரை (0..3)
AndroidBleGattServer.NOTIFY_ACK_TIMEOUT_MS10 000அறிவிப்பு ஓட்டக் கட்டுப்பாடுonNotificationSent-க்காக காத்திருப்பு
AndroidBleGattServer notifyChunkSize380பதில் துண்டாக்கல்BleDeviceApi.CHUNK_SIZE-ஐப் போலவே
IosBleGattServer retry cap10அறிவிப்பு ஓட்டக் கட்டுப்பாடுகைவிடுவதற்கு முன் அதிகபட்ச updateValue மறுமுயற்சிகள்
PeerCircuitBreaker.WINDOW_MS30 000போக்குவரத்து சுற்று-உடைப்பான்வாசலுக்குப் பிறகு திறக்கும் காலம்
PeerCircuitBreaker.MAX_FAILURES2போக்குவரத்து சுற்று-உடைப்பான்திறக்க வாசலுக்குள் தோல்விகள்
DownloadQueue.MAX_CONCURRENT3பதிவிறக்க வேலையாள் குளம்ஒரே நேர பதிவிறக்க coroutines
BleServiceData.SHORT_ID_BYTES8பியர் அடையாளம்துண்டிக்கப்பட்ட SHA256 முன்னொட்ட பைட்டுகள்
BleServiceData.PAYLOAD_BYTES9பியர் அடையாளம்1 கொடி பைட் + 8 shortId பைட்டுகள்
BleSegmentData.STATE_START_BIT1Layer A EOF சமிக்ஞைபல-துண்டு செய்தியின் முதல் துண்டு
BleSegmentData.STATE_END_BIT2Layer A EOF சமிக்ஞைகடைசி துண்டு (அல்லது ஒற்றைத் துண்டு)

வடிவமைப்பு பரிமாற்றங்கள் சுருக்கம் {#design-trade-offs-recap}

Diagram 14
14

மேலும் படிப்பதற்கு

  • அரட்டைக் கட்டமைப்பு — BleTransport எப்படி LAN → Aware → BLE fallback சங்கிலி மற்றும் பரந்த அரட்டை அனுப்பு/பெறு பைப்லைனுக்குள் பொருந்துகிறது.
  • பெயரிங் பாய்வு — ஒவ்வொரு BLE சுமையாலும் பயன்படுத்தப்படும் பகிரப்பட்ட ChaCha20 விசை எப்படி நிறுவப்படுகிறது, மற்றும் NEARBY பண்பு பெயரிங் handshake-க்கு எப்படிப் பயன்படுத்தப்படுகிறது.