Bu taşımayı kullanan daha geniş sohbet mimarisi için Sohbet Mimarisi makalesine bakın. İki cihazın her BLE yükünü şifrelemek için kullanılan paylaşılan ChaCha20 anahtarını nasıl elde ettiğini görmek için Eşleştirme Akışı makalesine bakın.
İçindekiler
- Neden Bir BLE Taşıması?
- GATT Hizmet Düzeni
- Eş Tanımlama: shortId, MAC değil
- İki Katmanlı Parçalama Tasarımı
- RPC İlkesi:
BleDeviceApi.requestAsync - Kablo Zarf Biçimi
- Mesaj Gönderme Yolu (Uçtan Uca)
- Dosya İndirme Yolu (Uçtan Uca)
- Önceliklendirme: Sohbet Pratikte Dosyaları Nasıl Geçer
- Eşzamanlılık Kontrolü ve Statik GATT Kuyruğu
- Bağlantı Yaşam Döngüsü ve MTU Müzakeresi
- Bildirimler için Akış Kontrolü
- Hata Yönetimi: TransportUnavailable ve Gerçek Başarısızlık
- Anahtar Sabitler Referansı
- Tasarım Takasları Özeti
Neden Bir BLE Taşıması? {#why-a-ble-transport-at-all}
PlainApp sunucusuz ve çevrimdışı önceliklidir. Taşıma katmanı, sıralı bir
yedek zinciridir: LAN → Wi-Fi Aware → BLE. LAN mutlu yoldur (Wi-Fi üzerinden
HTTPS, ~10 ms gidiş-dönüş). Wi-Fi Aware, alt ağ ötesi eşleri kapsar (farklı
SSID'ler, misafir ve IoT VLAN'ları). Her ikisi de bir tür IP bağlantısı
gerektirir. BLE, şu durumlarda çalışan tek taşımadır:
- Cihazlar aynı IP ağında hiç değilse.
- Wi-Fi kapalıysa veya uçak modundaysa (BLE radyosu ayrıdır).
- Wi-Fi Aware desteklenmiyorsa (Android < 13, PlainApp'in tüm iOS varyantları).
BLE yavaştır — saniyede onlarca KB, istek başına saniyelerce gecikme — ancak
herhangi bir eşleştirilmiş eş için garanti edilmiştir, çünkü ihtiyaç duyduğu
tek şey, BLE tarama yanıtında her zaman yayınlanan eşin clientId'sidir.
GATT Hizmet Düzeni {#gatt-service-layout}
PlainApp, iki karakteristiğe sahip tek bir özel GATT hizmeti tanıtır.
Kayıtlı 16 bitlik UUID yoktur — hizmet, sondaki baytları ASCII olarak
plpai\x01'e çözülen 128 bitlik bir UUID kullanır:
Neden iki karakteristik?
İki protokolün tamamen farklı güven modelleri ve yük şekilleri vardır:
- NEARBY, eşleştirme mesajlarını taşır. Bunlar, eş henüz eşleştirilmeden önce gelir (henüz paylaşılan anahtar yok), bu nedenle kendi önek yönlendirmesiyle kendi Ed25519 imzalı JSON yüklerini kullanırlar. Gövde düz bir metindir.
- HTTP, eşleştirme sonrası tüm trafiği (sohbet, dosyalar, varlık) taşır.
Her zaman paylaşılan anahtarla ChaCha20 ile şifrelidir ve LAN Ktor
sunucusuyla aynı
HttpRouteRegistry'i kullanır, böylece rota işleyicileri (/peer_graphql,/fs,/peer_status) bir kez yazılır ve her iki taşıma için yeniden kullanılır.
Neden okuma yerine bildirimler?
BLE ATT protokolü, tek bir öznitelik okumasını 512 bayt ile sınırlar. Bir
GraphQL yanıtı veya 16 KB'lık bir dosya parçası çok daha büyük olabilir.
PlainApp, gerçek veriler için asla readCharacteristic kullanmayarak bunu
çözer — sunucunun onCharacteristicReadRequest'i GATT_SUCCESS ile boş bir
yük döndürür. Bunun yerine, istemci isteğini karakteristiğe yazar ve sunucu,
istemcinin yeniden birleştirdiği bir parçalı bildirim dizisi göndererek
yanıt verir. Bu, BleDeviceApi.requestAsync, BleServerProtocol.handleWrite
ve AndroidBleGattServer.sendChunkedResponse içinde belgelenmiştir.
Eş Tanımlama: shortId, MAC değil {#peer-identification-shortid-not-mac}
BLE reklam paketleri küçüktür (31 bayt) ve BLE MAC adresi Android tarafından
yaklaşık 15 dakikada bir rastgeleleştirilir — bu nedenle kararlı bir
tanımlayıcı olarak kullanılamaz. PlainApp bunun yerine tarama yanıtında 9
baytlık bir serviceData yükü yayınlar:
Neden tam clientId yerine kırpılmış bir hash?
13 karakterlik bir clientId 13 bayta sığar, ancak PlainApp iki nedenle 8 baytlık kırpılmış bir SHA-256 seçer:
- Kararlı bayt bütçesi. Toplam 9 bayt, 31 baytlık reklam yüküne hizmet UUID'si (16 bayt), uzunluk ve tür alanlarıyla rahatça sığar (~27 bayt kullanıldı, 4 bayt boşluk).
- Gizlilik. BLE taraması yapan pasif bir gözlemci, shortId'den clientId'yi kurtaramaz (bir SHA-256 hash'inin 8 baytlık öneki pratikte geri döndürülemez). Yalnızca aynı shortId'yi daha önce yayınladığını gördükleri bir eşi tanıyabilirler — PlainApp kullanıcılarını sayamazlar.
Tam clientId, yalnızca GATT üzerinden gerçekten bağlanmış ve bir
DDiscoverReply almış bir eşe — yani kullanıcının etkileşimde bulunmayı
zaten seçtiği bir eşe açıklanır.
İki Katmanlı Parçalama Tasarımı {#two-layer-chunking-design}
Bu, BLE taşımasının en ince kısmıdır ve her iki katmanı da anlamak esaslidir çünkü tamamen farklı boyutlara ve amaçlara sahiptirler:
Neden 380 karakter?
Müzakere edilen ATT MTU, Android'de 517 bayttır (requestMtu(517) — BLE
spesifikasyonunun izin verdiği maksimum) ve iOS'ta ~185+ (CoreBluetooth tarafından
otomatik müzakere edilir). ATT başlığı (~3 bayt) ve BleSegmentData'nın JSON
sarmalayıcı yükü ({"d":"...","s":N} ~12 bayt ekler) çıkarıldığında, 380
karakterlik yük her iki platformda tek bir ATT MTU'ya rahatça sığar. Değer
simetriktir (hem istemci istek parçaları hem de sunucu bildirim parçaları
380 kullanır), bu da kodu basit tutar.
Neden dosya parçaları için 16 KiB?
16 KiB'lik bir dosya parçası base64 ile ~22 KiB JSON'a kodlanır, bu da ~58
GATT bildirim segmentine parçalanır. BLE üzerinden her requestAsync
gidiş-dönüşü saniyeler sürer, bu nedenle daha az ama daha büyük parçalar
parça başına yükü azaltır. Çok daha büyütmek, BLE RPC zaman aşımlarına
çarpmak ve kötü ilerleme geri bildirimi üretmek riskini doğurur (kullanıcı
ilerlemeyi parça başına yalnızca bir kez güncellenmiş görür). 16 KiB,
ampirik olarak ayarlanmış tatlı noktadır — verimlilik için yeterince büyük,
yanıt veren ilerleme UI'si için yeterince küçük.
RPC İlkesi: BleDeviceApi.requestAsync {#the-rpc-primitive-bledeviceapirequestasync}
Her BLE sohbet mesajı ve her dosya parçası, bir BleResult döndüren bir
suspend fonksiyonu olan BleDeviceApi.requestAsync(service, requestData)
için bir çağrıdır. Çağırıcı açısından senkroniktir: bir istek → tamamen
yeniden birleştirilmiş bir yanıt, ardışık işleme yok.
Anahtar değişmezler
- Bir istek → bir yanıt.
requestAsync, çağırıcı açısından senkroniktir — yalnızca tam yanıt yeniden birleştirildikten sonra döner. Ardışık işleme yoktur. - Çağrı başına bildirimler etkin. İstemci, her
requestAsync'in başında CCCD'yi yazar ve sonunda devre dışı bırakır. Bu israflıdır (çağrı başına iki ekstra GATT yazması) ancak protokolü durumsuz tutar — sunucu hangi istemcilerin «dinlediğini» takip etmek zorunda değildir. - RPC içinde yeniden deneme yok. Tek bir
writeCharacteristiczaman aşımına uğrarsa (5 s), tüm RPC iptal edilir. YalnızcaensureConnectedyeniden dener (bağlantı başarısızlığında 3 deneme). Kaba taşıma düzeyinde geri çekilmePeerCircuitBreakertarafından sağlanır, RPC katmanı tarafından değil.
Kablo Zarf Biçimi {#wire-envelope-format}
A katmanı segmentlerinin içindeki yük, iç içe geçmiş bir JSON zarfıdır. Parçalamayı çıkarırsak, mantıksal yapı şöyledir:
Yanıt şekli
Yanıt, aynı A katmanı parçalaması aracılığıyla ters yönde akar, ancak iç
JSON üç alana sahip bir BleHttpResponse'dur: s (HTTP durum kodu), h
(yanıt başlıkları haritası) ve b (gövde). Gövde, BleHttpCall.encodeResponse()
tarafından her zaman base64 olarak kodlanır, boş olduğunda bile — yanıt
ikili olabilir (şifreli GraphQL baytları, ham /fs dosya baytları) ve BLE
taşıması yalnızca dize olduğu için, aynı JSON zarfı hem metin hem de ikili
yükleri taşır.
Mesaj Gönderme Yolu (Uçtan Uca) {#message-send-path-end-to-end}
Hepsini bir araya getirirsek — BLE üzerinden bir sohbet mesajı gönderildiğinde ne olur:
Önemli tasarım seçimleri
- LAN ile aynı anahtar. Eşleştirmeden gelen ChaCha20 paylaşılan anahtarı
BLE için yeniden kullanılır — ayrı bir BLE anahtarı yoktur.
LanTransporttarafından kullanılan OkHttp şifreleme interceptor'ü veBleTransport'teki manuelchaCha20Encrypt/chaCha20Decryptaynı ilkedir, sadece farklı şekilde çağrılır. - LAN ile aynı rota işleyicileri.
BleHttpRequest, Ktor LAN sunucusunun kullandığı aynı kayıt defteri olanHttpRouteRegistry.matchRoute(path)üzerinden gönderilir. Bu nedenle/peer_graphql,/fs,/peer_statusvb. tam olarak bir kez uygulanır ve her iki taşıma üzerinden aynı şekilde çalışır. - Bağlantı yeniden kullanımı yok.
finally { scanner.teardownConnection(client) }bloğu her zaman çalışır. Her mesaj tam bağlan→servisleriKeşfet→MTU maliyetini öder (~saniyeler). Bu kasıtlı bir takastır — bkz. Tasarım Takasları.
Dosya İndirme Yolu (Uçtan Uca) {#file-download-path-end-to-end}
BLE üzerinden indirmeler akışlıdır — dosya 16 KiB parçalar halinde okunur
ve geldikçe geçici bir dosyaya yazılır, böylece 10 MB'lık bir dosya 10 MB RAM
gerektirmez. Püf noktası, her parçanın RPC'sinin ayrı bir requestAsync
çağrısı olması ve parçaların tüketici tarafından eşzamanlı olarak okunan bir
ByteChannel'a itilmesidir.
Neden tek büyük RPC yerine akış?
Tek bir RPC olarak gönderilen 10 MB'lık bir dosya, ~280 000 bildirim segmenti anlamına gelir, hepsi yanıttan önce bile her iki tarafta bellekte tutulur — ve tüm transferin herhangi bir ilerleme bildirilmeden önce başarılı olması gerekir. Daha kötüsü, ortada tek bir düşen bildirim tümünü bozabilir.
Parçalı tasarımın üç kazancı vardır:
- Sabit bellek. Aynı anda yalnızca bir 16 KiB'lik parça yoldadır.
- Canlı ilerleme.
DownloadQueue.notifyProgressUpdate()her saniye çalışır ve UI bir indirme çubuğu gösterir. - Dayanıklılık. Başarısız bir parça bağımsız olarak yeniden denenebilir
(
DownloadQueuegörev düzeyinde duraklat/devam et/yeniden dene destekler; akış ortasında bir başarısızlık kısmi geçici dosyayı bırakır, ancak şu anda indirici başarısızlıkta onu siler — takaslara bakın).
onClose neden indirme işini iptal eder
DownloadedResponse.onClose callback'i downloadJob.cancel() çağırır. Bu
esaslidir çünkü indirme döngüsü, tüketici kanalı erken terk ederse (örneğin
kullanıcı Duraklat'a dokunduysa) aksi takdirde sonsuza kadar çalışmaya
devam edecek bir alt coroutine'de çalışır. DownloadedResponse üzerindeki
AutoCloseable sözleşmesi, tüketicinin use { ... } bloğunun çıkışta
otomatik olarak onClose'u çağırması, BLE indirme coroutine'ini iptal etmesi
ve coroutine'in finally bloğunda GATT bağlantısını yıkması anlamına gelir.
Önceliklendirme: Sohbet Pratikte Dosyaları Nasıl Geçer {#prioritization-how-chat-beats-files-in-practice}
Bu, herhangi bir sohbet uygulaması için en önemli sorudur: yavaş bir BLE dosya indirmesi devam ederken, yeni bir sohbet mesajı önüne geçebilir mi?
Dürüst cevap: açık bir öncelik şeması yoktur
BLE kodunda veya indirme kuyruğunda öncelik alanı, öncelik kuyruğu, önleme
yoktur. Bunu kapsamlı grep ile doğruladım — shared/src'deki tek priority
eşleşmeleri günlük öncelik seviyeleri ve EXIF meta verileridir, mesaj ve
indirme sıralamasıyla ilgili hiçbir şey.
Bunun yerine var olan, istenen davranışı ortaya çıkan bir özellik olarak üreten bir dizi mimari ayrımdır:
Neden pratikte çalışır
Sohbeti «öncelikli hissettiren» ayrım yapısal:
- Sohbet gönderimleri
DownloadQueue'dan geçmez. DoğrudanPeerGraphQLClient→PeerTransportRouter→BleTransport.sendtarafından verilir. Bu nedenle bir sohbet mesajı asla bir dosya indirme kuyruğunun arkasında oturmaz. - Her
BleTransportçağrısı kendi GATT bağlantısını açar. Bir bağlantıyı tutan uzun süren bir indirme, bir sohbet gönderiminin aynı eşe ikinci bir bağlantı açmasını engellemez. Android, eşzamanlı birden çok GATT bağlantısını destekler. - Sohbet RPC'leri kısadır. Tek bir sohbet mesajı bir
requestAsyncgidiş-dönüşüdür (bağlantıdan sonra ~1 s). Radyo bir indirmeyle meşgul olsa bile, sohbet gönderimi birkaç saniye içinde tamamlanır.
Tasarımın yetersiz kaldığı yerler
«Açık öncelik yok»un takasları:
- Bağlantı gecikmesi. Hem sohbet hem de indirme, bağlantılar yeniden kullanılmadığından her seferinde bağlan→keşfet→MTU maliyetini (~saniyeler) öder. Bir indirme sırasında gelen bir sohbet mesajı, indirmenin mevcut bağlantısına ortak olamaz — yeni bir tane açar.
- Android'de statik kuyruk.
AndroidBleGattClient'teki süreç genelioperationQueue, GATT işlemlerini tüm eşler ve tüm bağlantılar arasında seri hale getirir. Bu nedenle iki GATT bağlantısı bir arada var olsa da, yazma/okuma/bildirim işlemleri kuyruk düzeyinde iç içe geçer. Pratikte bu iyidir (her işlem ~ms'dir) ancak yüksek eşzamanlılık altında ince bir küresel darboğazdır. - Önleme yok. Devam eden bir indirme, bir sohbet mesajının geçmesine izin için duraklatılamaz. Sohbet gönderimi basitçe eşzamanlı çalışır ve radyo süresi için yarışır.
Gelecekteki bir iyileştirme, BleTransport.send ve downloadFile etrafında
eş başına bir Mutex ve kuyrukta bir öncelik alanı olabilir — ancak mevcut
tasarım, sohbet RPC'lerinin çekişmenin nadiren kullanıcı tarafından görünür
olacak kadar kısa olduğu gerçeğine güvenir.
Eşzamanlılık Kontrolü ve Statik GATT Kuyruğu {#concurrency-control--the-static-gatt-queue}
Bu, kendi bölümünü hak eder çünkü Android BLE uygulamasının en ince yönüdür.
Neden statik (süreç geneli)?
Android BLE yığını, tek bir BluetoothGatt örneğinde eşzamanlı GATT
işlemlerine izin vermez — başka bir yazma devam ederken
writeCharacteristic çağrısı false döndürür ve ikinci yazmayı sessizce
bırakır. Standart geçici çözüm, BluetoothGatt başına bir kuyruktur. PlainApp
bir adım ileri gider ve companion object'te süreç geneli bir kuyruk
kullanır, bu aşırı muhafazakardır ama doğrudur: uygulamadaki hiçbir iki GATT
işleminin aynı anda çalışmamasını garanti eder.
Maliyeti, uzun bir BLE dosya indirmesinin yazma/okuma/bildirim işlemlerinin başka herhangi bir eşin GATT işlemlerinin arkasına sıralanmasıdır (ve onlar da arkasına sıralanır). Her bir işlem ~ms olduğundan, bu nadiren kullanıcı tarafından görülebilen bir darboğazdır — ancak birden çok eşe ağır eşzamanlı BLE trafiği altında bir tane olabilir.
Taşıma katmanında eş başına kilit yok
BleDeviceApi.requestAsync, mutex'i, kuyruğu, eş başına serileştirme olmayan
düz bir suspend fun'dur. Aynı eş için BleTransport.send'e yapılan iki
eşzamanlı çağrı, her biri kendi GATT bağlantısını açar ve bağımsız olarak
ilerler. Serileştirme, GATT işlem düzeyinde dolaylı olarak gerçekleşir
(Android'de statik kuyruk aracılığıyla veya iOS'ta ardışık await aracılığıyla).
Bağlantı Yaşam Döngüsü ve MTU Müzakeresi {#connection-lifecycle--mtu-negotiation}
Neden requestMtu(517)?
Varsayılan ATT MTU 23 bayttır (3 baytlık ATT başlığından sonra yalnızca 20 bayt yük). Varsayılan MTU ile, her 380 karakterlik segment ~19 GATT yazması yerine 1 gerektirir — 19× yavaşlama. BLE spesifikasyonunun izin verdiği maksimum MTU'yu (517 bayt) istemek, 380 karakterlik segmentlerin tek bir ATT işlemine sığmasını sağlar, verimliliği önemli ölçüde artırır.
iOS, açık bir MTU isteme API'si sunmaz — CoreBluetooth, bağlantı sırasında çevre birimle otomatik olarak müzakere eder. Modern iOS cihazları tipik olarak ~185 bayt müzakere eder, bu da yine 380 karakterlik segmentleri rahatça sığdırır (ATT başlığı + JSON sarmalayıcı yükü çıkarıldıktan sonra).
Bildirimler için Akış Kontrolü {#flow-control-for-notifications}
Sunucu, yanıt parçalarını bildirim olarak gönderir, ancak BLE bildirimlerinin yerleşik akış kontrolü yoktur — sunucu, denetleyicinin iletebileceğinden daha hızlı bildirim gönderirse, bunlar sessizce bırakılır. PlainApp, açık onay tabanlı akış kontrolü uygular:
Bu akış kontrolü olmadan, arka arkaya bildirimler, BLE denetleyicisinin iç
gönderme kuyruğu dolduğunda BleGattServer arayüzü yorumlarında belgelenen
iyi bilinen bir Android BLE sorunu olarak sessizce bırakılır. Cihaz başına
tek-yolda kuralı, her bildirimin ya iletildiğini ya da bir zaman aşımı
tetiklediğini (daha sonra bir taşıma başarısızlığı olarak ele alınır)
garanti eder.
Hata Yönetimi: TransportUnavailable ve Gerçek Başarısızlık {#error-handling-transportunavailable-vs-real-failure}
TransportUnavailable, PeerTransportRouter'a bir sonraki taşımaya geçmesini
söyleyen sinyaldir. Başka her şey, çağırıcıya dönen gerçek bir başarısızlıktır.
İndirme başarısızlığı inceliği
BleTransport.downloadFile, hemen DownloadedResponse(200, channel, onClose)
döndürür — parçalı indirme döngüsü, kanala yazan bir arka plan coroutine'de
çalışır. Bir parça RPC'si akış ortasında başarısız olursa, döngü
channel.close(TransportUnavailable(...)) çağırır, bu da tüketici
(PeerFileDownloader.downloadAsync) hatayı channel.readAvailable(buf)'den
bir fırlatılan istisna olarak görür.
Bu, PeerTransportRouter.downloadFile çağrısının kendisinin başarılı olduğu
(bir DownloadedResponse döndürdüğü) anlamına gelir, bu nedenle devre kesici,
akış ortası indirme hataları için bir başarısızlık kaydetmez. Yalnızca
bağlantı zamanı ve tarama zamanı başarısızlıkları yönlendirici tarafından
yakalanır. Bu kasıtlı bir tasarım seçimidir — akış ortası bir başarısızlık,
o eş için BLE'yi kalıcı olarak devre dışı bırakmamalıdır (eş yalnızca geçici
olarak menzil dışına çıkmış olabilir).
Anahtar Sabitler Referansı {#key-constants-reference}
| Sabit | Değer | Nerede | Amaç |
|---|---|---|---|
BleDeviceApi.CHUNK_SIZE | 380 | GATT segment parçalama | Her BleSegmentData.data'nın boyutu (JSON yükünden sonra ATT MTU içine sığar) |
BleTransport.CHUNK_SIZE | 16 384 (16 KiB) | Dosya indirme bayt aralığı | Her /fs parça isteğinin boyutu |
BleTransport.SCAN_TIMEOUT_MS | 10 000 | BLE tarama | scanner.findOne için zaman aşımı |
BleDeviceApi.NOTIFY_TIMEOUT_MS | 15 000 | RPC yanıtı | requestAsync'de bildirim başına bekleme |
AndroidBleGattClient MTU | 517 | Bağlantı kurulumu | requestMtu(517) — BLE spesifikasyonunun izin verdiği maksimum |
AndroidBleGattClient bağlantı zaman aşımı | 10 000 | Bağlantı kurulumu | STATE_CONNECTED için bekleme |
AndroidBleGattClient MTU zaman aşımı | 5 000 | Bağlantı kurulumu | onMtuChanged için bekleme |
AndroidBleGattClient yazma zaman aşımı | 5 000 | GATT yazma | onCharacteristicWrite için bekleme |
AndroidBleGattClient okuma zaman aşımı | 10 000 | GATT okuma | onCharacteristicRead için bekleme (gerçek veriler için kullanılmaz) |
AndroidBleGattClient bildirim-durumu zaman aşımı | 5 000 | CCCD yazma | CCCD deskriptör yazması için bekleme |
ensureConnected yeniden deneme | 3 | Bağlantı kurulumu | Toplam 4 denemeye kadar (0..3) |
AndroidBleGattServer.NOTIFY_ACK_TIMEOUT_MS | 10 000 | Bildirim akış kontrolü | onNotificationSent için bekleme |
AndroidBleGattServer notifyChunkSize | 380 | Yanıt parçalama | BleDeviceApi.CHUNK_SIZE ile aynı |
IosBleGattServer yeniden deneme sınırı | 10 | Bildirim akış kontrolü | Vazgeçmeden önce maksimum updateValue yeniden deneme |
PeerCircuitBreaker.WINDOW_MS | 30 000 | Taşıma devre kesici | Eşik sonrası açık süre |
PeerCircuitBreaker.MAX_FAILURES | 2 | Taşıma devre kesici | Açmak için pencere içinde başarısızlık |
DownloadQueue.MAX_CONCURRENT | 3 | İndirme worker havuzu | Eşzamanlı indirme coroutine'leri |
BleServiceData.SHORT_ID_BYTES | 8 | Eş tanımlama | Kırpılmış SHA256 önek baytları |
BleServiceData.PAYLOAD_BYTES | 9 | Eş tanımlama | 1 bayrak baytı + 8 shortId baytı |
BleSegmentData.STATE_START_BIT | 1 | A katmanı EOF sinyali | Çok segmentli mesajın ilk segmenti |
BleSegmentData.STATE_END_BIT | 2 | A katmanı EOF sinyali | Son segment (veya tek segment) |
Tasarım Takasları Özeti {#design-trade-offs-recap}
Daha Fazla Okuma
- Sohbet Mimarisi —
BleTransport'inLAN → Aware → BLEyedek zincirine ve daha geniş sohbet gönderme/alma ardışık düzenine nasıl uyduğu. - Eşleştirme Akışı — her BLE yükü tarafından kullanılan paylaşılan ChaCha20 anahtarının nasıl kurulduğu ve eşleştirme el sıkışması için NEARBY karakteristiğinin nasıl kullanıldığı. için.