@offline-protocol/mesh-sdk 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +1 -1
- package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -1
- package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +1 -1
- package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +1 -1
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.s +1 -1
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/inline-functions.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.s +1 -1
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.s +1 -1
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.s +1 -1
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
- package/android/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +0 -0
- package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$AdvertisementCacheEntry.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$MeshObservation.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$PendingFragment.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$fragmentPollingRunnable$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$gattClientCallback$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$gattServerCallback$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$resolveTargetAddress$$inlined$sortedBy$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$routingCleanupRunnable$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$scanWatchdogRunnable$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$startAdvertising$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$startConnectionMonitor$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$startScanning$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/InternetManager$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/InternetManager$messagePollingRunnable$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/InternetManager$pingRunnable$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/InternetManager$webSocketListener$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/InternetManager.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/LogThrottler.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/OfflineProtocolModule$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/OfflineProtocolModule$Constants.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/OfflineProtocolModule$ParsedConfig.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/OfflineProtocolModule$create$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/OfflineProtocolModule$create$3$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/OfflineProtocolModule$create$5$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/OfflineProtocolModule.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/OfflineProtocolPackage.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/TransportException$AlreadyRunning.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/TransportException$InvalidState.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/TransportException$NotAvailable.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/TransportException$NotRunning.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/TransportException$PermissionDenied.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/TransportException$PlatformError.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/TransportException$StartFailed.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/TransportException.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/TransportManager.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/TransportManagerListener.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/TransportState.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/WifiDirectManager$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/WifiDirectManager$messagePollingRunnable$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/WifiDirectManager$p2pReceiver$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/WifiDirectManager$startPeerDiscovery$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/WifiDirectManager$stopPeerDiscovery$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/WifiDirectManager.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/ble/MeshConnectionRegistry.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshAdvertisementData$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshAdvertisementData.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshController$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshController$ConnectionIntent.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshController$MeshConfig.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshController$MeshDecision.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshController$MeshRole.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshController$PeerMetrics.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshController$PeerState.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshController$RebalanceDirective.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshController$RemoteCandidate.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshController$ScoreWeights.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshController.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/AckConfig$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/AckConfig.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/BleFragment$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/BleFragment.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/DedupConfig$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/DedupConfig.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/DedupStats$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/DedupStats.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/Disposable$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/Disposable.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/DorsConfig$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/DorsConfig.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/EventCallback$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/EventCallback.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverter$DefaultImpls.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverter.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterBoolean.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterCallbackInterface.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterFloat.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterOptionalShort.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterOptionalString.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterOptionalTypeBleFragment.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterOptionalTypeFileProgress.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterOptionalTypeInternetMessage.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterOptionalTypeRouteEntry.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterOptionalTypeTransportMetrics.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterOptionalTypeWifiDirectMessage.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterOptionalUByte.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterOptionalULong.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterRustBuffer.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterSequenceString.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterSequenceTypeMessageStats.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterSequenceTypeNetworkLink.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterSequenceTypeNetworkNode.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterSequenceTypeRouteEntry.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterSequenceUByte.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterShort.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterString.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeAckConfig.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeBleFragment.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeDedupConfig.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeDedupStats.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeDorsConfig.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeEventCallback.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeFileProgress.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeGradientRoutingConfig.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeInternetMessage.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeMessagePriority.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeMessageStats.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeNetworkLink.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeNetworkNode.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeNetworkTopology.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeOfflineProtocol.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypePathConfig.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypePeerDevice.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeProtocolConfig.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeProtocolConfigExtended.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeProtocolError.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeProtocolState.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeRelayConfig.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeRelayPriority.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeReliabilityConfig.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeRetryConfig.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeRouteEntry.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeRoutingStats.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeTransportConfig.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeTransportMetrics.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeTransportType.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeWifiDirectMessage.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterUByte.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterUInt.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterULong.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterUShort.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FileProgress$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FileProgress.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ForeignBytes$ByValue.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ForeignBytes.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/GradientRoutingConfig$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/GradientRoutingConfig.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/IntegrityCheckingUniffiLib.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/InternalException.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/InternetMessage$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/InternetMessage.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/JavaLangRefCleanable.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/JavaLangRefCleaner.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/MessagePriority$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/MessagePriority.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/MessageStats$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/MessageStats.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/NetworkLink$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/NetworkLink.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/NetworkNode$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/NetworkNode.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/NetworkTopology$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/NetworkTopology.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/NoHandle.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/OfflineProtocol$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/OfflineProtocol$UniffiCleanAction.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/OfflineProtocol.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/OfflineProtocolInterface$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/OfflineProtocolInterface.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/Offline_protocolKt.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/PathConfig$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/PathConfig.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/PeerDevice$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/PeerDevice.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolConfig$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolConfig.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolConfigExtended$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolConfigExtended.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolException$AlreadyStarted.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolException$ErrorHandler.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolException$InvalidConfiguration.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolException$InvalidState.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolException$NotStarted.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolException$Other.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolException$SendFailed.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolException.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolState$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolState.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RelayConfig$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RelayConfig.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RelayPriority$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RelayPriority.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ReliabilityConfig$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ReliabilityConfig.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RetryConfig$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RetryConfig.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RouteEntry$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RouteEntry.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RoutingStats$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RoutingStats.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RustBuffer$ByReference.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RustBuffer$ByValue.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RustBuffer$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RustBuffer.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/TransportConfig$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/TransportConfig.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/TransportMetrics$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/TransportMetrics.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/TransportType$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/TransportType.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiCallbackInterfaceClone.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiCallbackInterfaceEventCallbackMethod0.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiCallbackInterfaceFree.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiCleaner$Cleanable.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiCleaner$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiCleaner.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureCompleteF32.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureCompleteF64.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureCompleteI16.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureCompleteI32.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureCompleteI64.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureCompleteI8.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureCompleteRustBuffer.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureCompleteU16.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureCompleteU32.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureCompleteU64.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureCompleteU8.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureCompleteVoid.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureDroppedCallback.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureDroppedCallbackStruct$UniffiByValue.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureDroppedCallbackStruct.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultF32$UniffiByValue.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultF32.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultF64$UniffiByValue.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultF64.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultI16$UniffiByValue.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultI16.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultI32$UniffiByValue.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultI32.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultI64$UniffiByValue.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultI64.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultI8$UniffiByValue.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultI8.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultRustBuffer$UniffiByValue.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultRustBuffer.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultU16$UniffiByValue.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultU16.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultU32$UniffiByValue.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultU32.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultU64$UniffiByValue.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultU64.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultU8$UniffiByValue.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultU8.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultVoid$UniffiByValue.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultVoid.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiHandleMap.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiJnaCleanable.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiJnaCleaner.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiLib.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiNullRustCallStatusErrorHandler.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiRustCallStatus$ByValue.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiRustCallStatus$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiRustCallStatus.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiRustCallStatusErrorHandler.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiRustFutureContinuationCallback.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiVTableCallbackInterfaceEventCallback$UniffiByValue.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiVTableCallbackInterfaceEventCallback.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiWithHandle.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/WifiDirectMessage$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/WifiDirectMessage.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/uniffiCallbackInterfaceEventCallback$onEvent.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/uniffiCallbackInterfaceEventCallback$uniffiClone.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/uniffiCallbackInterfaceEventCallback$uniffiFree.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/uniffiCallbackInterfaceEventCallback.class +0 -0
- package/android/src/main/java/com/offlineprotocol/BleManager.kt +571 -34
- package/ios/BleManager.swift +427 -14
- package/package.json +1 -1
- /package/android/build/tmp/kotlin-classes/debug/META-INF/{offlineprotocol_react-native_debug.kotlin_module → offline-protocol_mesh-sdk_debug.kotlin_module} +0 -0
|
@@ -71,16 +71,21 @@ class BleManager(
|
|
|
71
71
|
private const val FRAGMENT_POLL_INTERVAL_MS = 100L // 100ms
|
|
72
72
|
private const val MAX_FRAGMENT_SIZE = 185
|
|
73
73
|
private const val CONNECTION_TIMEOUT_MS = 10000L
|
|
74
|
-
private const val SCAN_WATCHDOG_INTERVAL_MS =
|
|
74
|
+
private const val SCAN_WATCHDOG_INTERVAL_MS = 30000L // Match iOS timing
|
|
75
75
|
private const val SCAN_WATCHDOG_HEARTBEAT_MS = 10000L
|
|
76
76
|
private const val MAX_CONNECTIONS_PER_DEVICE = 4
|
|
77
77
|
private const val ADVERTISE_RESTART_MIN_MS = 200L
|
|
78
78
|
private const val ADVERTISE_RESTART_MAX_MS = 1200L
|
|
79
79
|
private const val MIN_ADVERTISE_INTERVAL_MS = 1500L
|
|
80
|
+
private const val MIN_RECONNECT_INTERVAL_MS = 5_000L // Match iOS timing
|
|
81
|
+
private const val MAX_RECONNECT_INTERVAL_MS = 60_000L
|
|
82
|
+
private const val MAX_CONNECTION_RETRIES = 5
|
|
80
83
|
|
|
81
84
|
// Adaptive Scan Configuration
|
|
82
|
-
/** Minimum RSSI to consider for connection (filter weak signals early) */
|
|
85
|
+
/** Minimum RSSI to consider for connection (filter weak signals early) - matches iOS */
|
|
83
86
|
private const val ADAPTIVE_MIN_RSSI = -85
|
|
87
|
+
/** Absolute minimum RSSI below which we refuse to connect - matches iOS */
|
|
88
|
+
private const val MINIMUM_RSSI_TO_CONNECT = -90
|
|
84
89
|
/** Peer count threshold below which we process all advertisements */
|
|
85
90
|
private const val ADAPTIVE_LOW_DENSITY_THRESHOLD = 10
|
|
86
91
|
/** Peer count threshold above which we apply maximum throttling */
|
|
@@ -91,6 +96,24 @@ class BleManager(
|
|
|
91
96
|
private const val ADAPTIVE_COOLDOWN_PER_DEVICE_MS = 30_000L
|
|
92
97
|
/** Interval for updating visible peer count estimate (ms) */
|
|
93
98
|
private const val ADAPTIVE_PEER_COUNT_WINDOW_MS = 5_000L
|
|
99
|
+
/** Rate limit for unknown device GATT verification attempts (ms) */
|
|
100
|
+
private const val UNKNOWN_DEVICE_RATE_LIMIT_MS = 5_000L // More aggressive for cross-platform discovery
|
|
101
|
+
/** Minimum RSSI for unknown device to be considered for GATT verification */
|
|
102
|
+
private const val UNKNOWN_DEVICE_MIN_RSSI = -80 // More lenient for cross-platform discovery
|
|
103
|
+
/** Maximum unknown device verification attempts per minute */
|
|
104
|
+
private const val MAX_UNKNOWN_DEVICE_ATTEMPTS_PER_MINUTE = 10
|
|
105
|
+
/** Proactive scan refresh interval even when discoveries are occurring (ms) */
|
|
106
|
+
private const val PROACTIVE_SCAN_REFRESH_MS = 60_000L
|
|
107
|
+
/** Force a complete BLE stack refresh periodically even when things seem healthy (ms) */
|
|
108
|
+
private const val FORCED_BLE_REFRESH_MS = 120_000L
|
|
109
|
+
/** Maximum consecutive scan restarts before resetting BLE adapter */
|
|
110
|
+
private const val MAX_CONSECUTIVE_SCAN_RESTARTS = 3
|
|
111
|
+
/** Backoff period after resetting BLE adapter (ms) */
|
|
112
|
+
private const val ADAPTER_RESET_BACKOFF_MS = 45_000L
|
|
113
|
+
/** Connection monitor interval for periodic reconnection attempts (ms) */
|
|
114
|
+
private const val CONNECTION_MONITOR_INTERVAL_MS = 5_000L
|
|
115
|
+
/** Initial aggressive discovery phase duration (ms) - more frequent scanning initially */
|
|
116
|
+
private const val AGGRESSIVE_DISCOVERY_PHASE_MS = 30_000L
|
|
94
117
|
}
|
|
95
118
|
|
|
96
119
|
// MARK: - Properties
|
|
@@ -129,6 +152,7 @@ class BleManager(
|
|
|
129
152
|
private data class MeshObservation(val advertisement: MeshAdvertisementData, val rssi: Int?, val timestamp: Long)
|
|
130
153
|
private val pendingFragments = ConcurrentHashMap<String, MutableList<PendingFragment>>()
|
|
131
154
|
private val PENDING_FRAGMENT_TIMEOUT_MS = 5000L
|
|
155
|
+
private val connectionRetryCount = ConcurrentHashMap<String, Int>()
|
|
132
156
|
private val LOAD_SATURATION_COUNT = 20
|
|
133
157
|
private val MESH_OBSERVATION_TTL_MS = 120_000L
|
|
134
158
|
private val deviceIdResolutionAttempts = ConcurrentHashMap<String, Long>()
|
|
@@ -147,6 +171,23 @@ class BleManager(
|
|
|
147
171
|
/** Last time we updated the peer count estimate */
|
|
148
172
|
@Volatile private var lastPeerCountUpdate: Long = 0L
|
|
149
173
|
@Volatile private var lastMeshAdvertisement: MeshAdvertisementData? = null
|
|
174
|
+
/** Rate limiting for unknown connectable devices that need GATT verification */
|
|
175
|
+
private val unknownDeviceAttempts = ConcurrentHashMap<String, Long>()
|
|
176
|
+
/** Last time we proactively refreshed the scan */
|
|
177
|
+
@Volatile private var lastProactiveScanRefresh: Long = 0L
|
|
178
|
+
/** Last time we performed a forced BLE refresh */
|
|
179
|
+
@Volatile private var lastForcedBleRefresh: Long = 0L
|
|
180
|
+
/** Tracks recently seen advertisements to avoid duplicate processing (hash, timestamp) */
|
|
181
|
+
private data class AdvertisementCacheEntry(val hash: Int, val timestamp: Long)
|
|
182
|
+
private val recentAdvertisementHashes = ConcurrentHashMap<String, AdvertisementCacheEntry>()
|
|
183
|
+
/** Tracks connected centrals (devices that connected to our GATT server) for connection count */
|
|
184
|
+
private val connectedCentrals = ConcurrentHashMap<String, Long>()
|
|
185
|
+
/** Counter for consecutive scan restarts without discoveries */
|
|
186
|
+
@Volatile private var scanRestartCount = 0
|
|
187
|
+
/** Last time we reset the BLE adapter */
|
|
188
|
+
@Volatile private var lastAdapterReset: Long = 0L
|
|
189
|
+
/** Connection monitor runnable for periodic reconnection attempts */
|
|
190
|
+
private var connectionMonitorRunnable: Runnable? = null
|
|
150
191
|
|
|
151
192
|
// Fragment polling
|
|
152
193
|
private val mainHandler = Handler(Looper.getMainLooper())
|
|
@@ -214,13 +255,58 @@ class BleManager(
|
|
|
214
255
|
Log.w(TAG, "Restarting BLE scan after ${idleMs}ms of inactivity")
|
|
215
256
|
emitDiagnostic("warning", "Restarting BLE scan due to inactivity", mapOf("idleMs" to idleMs))
|
|
216
257
|
}
|
|
258
|
+
scanRestartCount++
|
|
217
259
|
restartScanning("watchdog")
|
|
260
|
+
evaluateBleHealthAfterRestart()
|
|
218
261
|
return
|
|
219
262
|
}
|
|
220
263
|
mainHandler.postDelayed(this, SCAN_WATCHDOG_HEARTBEAT_MS)
|
|
221
264
|
}
|
|
222
265
|
}
|
|
223
266
|
|
|
267
|
+
/**
|
|
268
|
+
* Evaluates BLE stack health after consecutive restarts and resets adapter if needed.
|
|
269
|
+
* This mirrors iOS's evaluateCentralHealthAfterRestart mechanism.
|
|
270
|
+
*/
|
|
271
|
+
private fun evaluateBleHealthAfterRestart() {
|
|
272
|
+
if (scanRestartCount < MAX_CONSECUTIVE_SCAN_RESTARTS) {
|
|
273
|
+
return
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
val now = System.currentTimeMillis()
|
|
277
|
+
if (lastAdapterReset > 0 && now - lastAdapterReset < ADAPTER_RESET_BACKOFF_MS) {
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
Log.w(TAG, "Resetting BLE stack due to repeated scan stalls (restartCount=$scanRestartCount)")
|
|
282
|
+
emitDiagnostic("warning", "Resetting BLE stack due to repeated scan stalls", mapOf(
|
|
283
|
+
"restartCount" to scanRestartCount
|
|
284
|
+
))
|
|
285
|
+
|
|
286
|
+
lastAdapterReset = now
|
|
287
|
+
scanRestartCount = 0
|
|
288
|
+
|
|
289
|
+
// Force stop and restart everything
|
|
290
|
+
mainHandler.post {
|
|
291
|
+
if (state == TransportState.RUNNING) {
|
|
292
|
+
stopScanning("ble_reset")
|
|
293
|
+
stopAdvertising()
|
|
294
|
+
|
|
295
|
+
// Re-initialize scanner and advertiser
|
|
296
|
+
bluetoothLeScanner = bluetoothAdapter?.bluetoothLeScanner
|
|
297
|
+
bluetoothLeAdvertiser = bluetoothAdapter?.bluetoothLeAdvertiser
|
|
298
|
+
|
|
299
|
+
// Restart after a brief delay
|
|
300
|
+
mainHandler.postDelayed({
|
|
301
|
+
if (state == TransportState.RUNNING) {
|
|
302
|
+
startScanning("ble_reset")
|
|
303
|
+
startAdvertising("ble_reset")
|
|
304
|
+
}
|
|
305
|
+
}, 1000)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
224
310
|
// Metrics
|
|
225
311
|
private var bytesSent: Long = 0
|
|
226
312
|
private var bytesReceived: Long = 0
|
|
@@ -409,7 +495,15 @@ class BleManager(
|
|
|
409
495
|
pendingFragments.clear()
|
|
410
496
|
pendingOutboundFragments.clear()
|
|
411
497
|
lastSeenMeshAdvertisements.clear()
|
|
498
|
+
unknownDeviceAttempts.clear()
|
|
499
|
+
recentAdvertisementHashes.clear()
|
|
500
|
+
connectionRetryCount.clear()
|
|
501
|
+
connectedCentrals.clear()
|
|
502
|
+
scanRestartCount = 0
|
|
503
|
+
lastAdapterReset = 0L
|
|
412
504
|
transportStartAt = 0L
|
|
505
|
+
lastProactiveScanRefresh = 0L
|
|
506
|
+
lastForcedBleRefresh = 0L
|
|
413
507
|
|
|
414
508
|
// Close GATT server
|
|
415
509
|
gattServer?.close()
|
|
@@ -570,9 +664,11 @@ class BleManager(
|
|
|
570
664
|
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
|
|
571
665
|
.build()
|
|
572
666
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
667
|
+
// CRITICAL FIX: Scan without service UUID filter for iOS ↔ Android interoperability
|
|
668
|
+
// iOS's CoreBluetooth has known issues recognizing 128-bit service UUIDs from Android
|
|
669
|
+
// advertisements, and vice versa. Scanning without filter and filtering in software
|
|
670
|
+
// ensures we discover all mesh devices regardless of platform quirks.
|
|
671
|
+
// We filter in handleScanResult using shouldProcessDiscoveredDevice().
|
|
576
672
|
|
|
577
673
|
scanCallback = object : ScanCallback() {
|
|
578
674
|
override fun onScanResult(callbackType: Int, result: ScanResult) {
|
|
@@ -600,14 +696,28 @@ class BleManager(
|
|
|
600
696
|
}
|
|
601
697
|
}
|
|
602
698
|
|
|
603
|
-
|
|
699
|
+
// Scan without filter - we'll filter in software for cross-platform compatibility
|
|
700
|
+
scanner.startScan(null, scanSettings, scanCallback)
|
|
604
701
|
isScanning = true
|
|
605
|
-
|
|
702
|
+
val now = System.currentTimeMillis()
|
|
703
|
+
lastDiscoveryAt = now
|
|
704
|
+
lastProactiveScanRefresh = now
|
|
705
|
+
// Reset restart count on non-watchdog starts
|
|
706
|
+
if (reason != "restart_watchdog") {
|
|
707
|
+
scanRestartCount = 0
|
|
708
|
+
}
|
|
606
709
|
scheduleScanWatchdog()
|
|
710
|
+
startConnectionMonitor()
|
|
607
711
|
if (logThrottler.shouldLog("scan_started")) {
|
|
608
|
-
Log.i(TAG, "
|
|
609
|
-
emitDiagnostic("info", "BLE scanning started", mapOf(
|
|
712
|
+
Log.i(TAG, "BLE scanning started (no filter, reason: $reason)")
|
|
713
|
+
emitDiagnostic("info", "BLE scanning started", mapOf(
|
|
714
|
+
"reason" to reason,
|
|
715
|
+
"filterless" to true
|
|
716
|
+
))
|
|
610
717
|
}
|
|
718
|
+
|
|
719
|
+
// Rehydrate previously connected devices to avoid waiting for advertisements
|
|
720
|
+
rehydratePreviouslyConnectedDevices()
|
|
611
721
|
} catch (e: SecurityException) {
|
|
612
722
|
Log.e(TAG, "Permission denied while starting scan", e)
|
|
613
723
|
emitDiagnostic("error", "Permission denied while starting scan", mapOf("exception" to e.javaClass.simpleName, "message" to (e.message ?: "unknown")))
|
|
@@ -615,6 +725,28 @@ class BleManager(
|
|
|
615
725
|
}
|
|
616
726
|
}
|
|
617
727
|
|
|
728
|
+
/**
|
|
729
|
+
* Attempts to reconnect to previously known devices without waiting for advertisements.
|
|
730
|
+
* This speeds up rediscovery after app restart or Bluetooth toggle.
|
|
731
|
+
*/
|
|
732
|
+
private fun rehydratePreviouslyConnectedDevices() {
|
|
733
|
+
try {
|
|
734
|
+
val bondedDevices = bluetoothAdapter?.bondedDevices ?: return
|
|
735
|
+
for (device in bondedDevices) {
|
|
736
|
+
val address = device.address
|
|
737
|
+
// Only attempt if we previously had this device in our registry
|
|
738
|
+
if (connections.hasDeviceForAddress(address) && connections.getGatt(address) == null) {
|
|
739
|
+
if (logThrottler.shouldLog("rehydrate_$address", intervalMs = 30_000)) {
|
|
740
|
+
Log.d(TAG, "Rehydrating connection to bonded device: $address")
|
|
741
|
+
}
|
|
742
|
+
connectToDevice(device)
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
} catch (e: SecurityException) {
|
|
746
|
+
Log.w(TAG, "Cannot access bonded devices for rehydration", e)
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
618
750
|
private fun stopScanning(reason: String = "manual") {
|
|
619
751
|
if (!isScanning) return
|
|
620
752
|
|
|
@@ -623,6 +755,7 @@ class BleManager(
|
|
|
623
755
|
scanCallback = null
|
|
624
756
|
isScanning = false
|
|
625
757
|
cancelScanWatchdog()
|
|
758
|
+
cancelConnectionMonitor()
|
|
626
759
|
lastDiscoveryAt = 0L
|
|
627
760
|
discoveryLogTimestamps.clear()
|
|
628
761
|
if (logThrottler.shouldLog("scan_stopped")) {
|
|
@@ -649,6 +782,94 @@ class BleManager(
|
|
|
649
782
|
mainHandler.removeCallbacks(scanWatchdogRunnable)
|
|
650
783
|
}
|
|
651
784
|
|
|
785
|
+
/**
|
|
786
|
+
* Starts the connection monitor that periodically attempts to reconnect to discovered devices.
|
|
787
|
+
* This mirrors iOS's startConnectionMonitor mechanism for more reliable discovery.
|
|
788
|
+
*/
|
|
789
|
+
private fun startConnectionMonitor() {
|
|
790
|
+
cancelConnectionMonitor()
|
|
791
|
+
|
|
792
|
+
connectionMonitorRunnable = object : Runnable {
|
|
793
|
+
override fun run() {
|
|
794
|
+
if (state != TransportState.RUNNING) {
|
|
795
|
+
return
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
val now = System.currentTimeMillis()
|
|
799
|
+
|
|
800
|
+
// Check for discovered devices that aren't connected
|
|
801
|
+
for ((address, observation) in lastSeenMeshAdvertisements) {
|
|
802
|
+
// Skip if already connected
|
|
803
|
+
if (connections.getGatt(address) != null) {
|
|
804
|
+
continue
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Skip if we've hit connection cap
|
|
808
|
+
if (currentConnectionCount() >= MAX_CONNECTIONS_PER_DEVICE) {
|
|
809
|
+
break
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// Skip if observation is too old
|
|
813
|
+
if (now - observation.timestamp > MESH_OBSERVATION_TTL_MS) {
|
|
814
|
+
continue
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// Skip if RSSI too weak
|
|
818
|
+
val rssi = observation.rssi ?: continue
|
|
819
|
+
if (rssi < MINIMUM_RSSI_TO_CONNECT) {
|
|
820
|
+
continue
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Rate limit attempts to this device
|
|
824
|
+
val lastAttempt = deviceConnectionAttempts[address]
|
|
825
|
+
if (lastAttempt != null && now - lastAttempt < MIN_RECONNECT_INTERVAL_MS) {
|
|
826
|
+
continue
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Try to connect
|
|
830
|
+
try {
|
|
831
|
+
val device = bluetoothAdapter?.getRemoteDevice(address) ?: continue
|
|
832
|
+
recordConnectionAttempt(address, now)
|
|
833
|
+
connectToDevice(device)
|
|
834
|
+
} catch (e: Exception) {
|
|
835
|
+
Log.w(TAG, "Connection monitor: failed to get remote device $address", e)
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// Also check for pending fragments that need device ID resolution
|
|
840
|
+
for (address in pendingFragments.keys) {
|
|
841
|
+
if (connections.deviceIdForAddress(address) != null) {
|
|
842
|
+
continue
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
val lastAttempt = deviceIdResolutionAttempts[address]
|
|
846
|
+
if (lastAttempt != null && now - lastAttempt < MIN_RECONNECT_INTERVAL_MS) {
|
|
847
|
+
continue
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
deviceIdResolutionAttempts[address] = now
|
|
851
|
+
try {
|
|
852
|
+
val device = bluetoothAdapter?.getRemoteDevice(address)
|
|
853
|
+
if (device != null && connections.getGatt(address) == null) {
|
|
854
|
+
connectToDevice(device)
|
|
855
|
+
}
|
|
856
|
+
} catch (e: Exception) {
|
|
857
|
+
Log.w(TAG, "Connection monitor: failed to resolve device ID for $address", e)
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
mainHandler.postDelayed(this, CONNECTION_MONITOR_INTERVAL_MS)
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
mainHandler.postDelayed(connectionMonitorRunnable!!, CONNECTION_MONITOR_INTERVAL_MS)
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
private fun cancelConnectionMonitor() {
|
|
869
|
+
connectionMonitorRunnable?.let { mainHandler.removeCallbacks(it) }
|
|
870
|
+
connectionMonitorRunnable = null
|
|
871
|
+
}
|
|
872
|
+
|
|
652
873
|
private fun startAdvertising(reason: String = "manual") {
|
|
653
874
|
if (isAdvertising) return
|
|
654
875
|
|
|
@@ -674,7 +895,7 @@ class BleManager(
|
|
|
674
895
|
|
|
675
896
|
advertiseCallback = object : AdvertiseCallback() {
|
|
676
897
|
override fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
|
|
677
|
-
Log.i(TAG, "
|
|
898
|
+
Log.i(TAG, "BLE advertising started successfully (reason=$reason)")
|
|
678
899
|
isAdvertising = true
|
|
679
900
|
lastAdvertiseRestartAt = System.currentTimeMillis()
|
|
680
901
|
emitDiagnostic("info", "BLE advertising started", mapOf("reason" to reason))
|
|
@@ -698,7 +919,11 @@ class BleManager(
|
|
|
698
919
|
}
|
|
699
920
|
}
|
|
700
921
|
|
|
701
|
-
|
|
922
|
+
// Include scan response with service UUID for iOS compatibility
|
|
923
|
+
// iOS's CoreBluetooth actively queries for scan responses and has known issues
|
|
924
|
+
// recognizing 128-bit service UUIDs from Android's main advertisement packet
|
|
925
|
+
val scanResponse = buildScanResponse()
|
|
926
|
+
bluetoothLeAdvertiser?.startAdvertising(settings, advertiseData, scanResponse, advertiseCallback)
|
|
702
927
|
|
|
703
928
|
// Reduced logging
|
|
704
929
|
} catch (e: SecurityException) {
|
|
@@ -762,6 +987,21 @@ class BleManager(
|
|
|
762
987
|
.build()
|
|
763
988
|
}
|
|
764
989
|
|
|
990
|
+
/**
|
|
991
|
+
* Builds the scan response data for BLE advertising.
|
|
992
|
+
*
|
|
993
|
+
* iOS's CoreBluetooth actively queries for scan responses during BLE scanning.
|
|
994
|
+
* Including the service UUID in the scan response makes Android devices more
|
|
995
|
+
* reliably visible to iOS devices, which have known issues recognizing 128-bit
|
|
996
|
+
* service UUIDs from Android's main advertisement packet format.
|
|
997
|
+
*/
|
|
998
|
+
private fun buildScanResponse(): AdvertiseData {
|
|
999
|
+
return AdvertiseData.Builder()
|
|
1000
|
+
.setIncludeDeviceName(false)
|
|
1001
|
+
.addServiceUuid(ParcelUuid(SERVICE_UUID))
|
|
1002
|
+
.build()
|
|
1003
|
+
}
|
|
1004
|
+
|
|
765
1005
|
private fun handleScanResult(result: ScanResult) {
|
|
766
1006
|
val device = result.device
|
|
767
1007
|
val rssi = result.rssi
|
|
@@ -769,9 +1009,42 @@ class BleManager(
|
|
|
769
1009
|
val now = System.currentTimeMillis()
|
|
770
1010
|
lastDiscoveryAt = now
|
|
771
1011
|
|
|
1012
|
+
// Duplicate advertisement detection - avoid processing identical advertisements
|
|
1013
|
+
// This improves performance in dense networks
|
|
1014
|
+
val advertHash = computeAdvertisementHash(result)
|
|
1015
|
+
val cached = recentAdvertisementHashes[address]
|
|
1016
|
+
if (cached != null && cached.hash == advertHash && now - cached.timestamp < 1000L) {
|
|
1017
|
+
return // Skip duplicate advertisement
|
|
1018
|
+
}
|
|
1019
|
+
recentAdvertisementHashes[address] = AdvertisementCacheEntry(advertHash, now)
|
|
1020
|
+
|
|
1021
|
+
// Prune old advertisement cache entries periodically
|
|
1022
|
+
if (recentAdvertisementHashes.size > 100) {
|
|
1023
|
+
val cutoff = now - 30_000L
|
|
1024
|
+
val iterator = recentAdvertisementHashes.entries.iterator()
|
|
1025
|
+
while (iterator.hasNext()) {
|
|
1026
|
+
if (iterator.next().value.timestamp < cutoff) {
|
|
1027
|
+
iterator.remove()
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
772
1032
|
// Adaptive scanning: track discoveries for density estimation
|
|
773
1033
|
recordDiscoveryForDensity(now)
|
|
774
1034
|
|
|
1035
|
+
// CRITICAL: Software-based filtering for iOS ↔ Android interoperability
|
|
1036
|
+
// Since we scan without a service UUID filter, we filter here instead.
|
|
1037
|
+
val scanRecord = result.scanRecord
|
|
1038
|
+
val isConnectable = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
1039
|
+
result.isConnectable
|
|
1040
|
+
} else {
|
|
1041
|
+
true // Assume connectable on older Android
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
if (!shouldProcessDiscoveredDevice(address, scanRecord, rssi, isConnectable, now)) {
|
|
1045
|
+
return
|
|
1046
|
+
}
|
|
1047
|
+
|
|
775
1048
|
// Adaptive scanning: early RSSI filtering in dense networks
|
|
776
1049
|
if (shouldFilterByRssi(rssi)) {
|
|
777
1050
|
if (logThrottler.shouldLog("adaptive_rssi_filter", intervalMs = 10000)) {
|
|
@@ -786,11 +1059,6 @@ class BleManager(
|
|
|
786
1059
|
}
|
|
787
1060
|
|
|
788
1061
|
val lastLog = discoveryLogTimestamps[address]
|
|
789
|
-
val isConnectable = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
790
|
-
result.isConnectable
|
|
791
|
-
} else {
|
|
792
|
-
null
|
|
793
|
-
}
|
|
794
1062
|
if (lastLog == null || now - lastLog > 30000) {
|
|
795
1063
|
discoveryLogTimestamps[address] = now
|
|
796
1064
|
Log.d(TAG, "Discovered device $address RSSI=$rssi (density: $estimatedVisiblePeerCount)")
|
|
@@ -800,14 +1068,13 @@ class BleManager(
|
|
|
800
1068
|
mapOf(
|
|
801
1069
|
"address" to address,
|
|
802
1070
|
"rssi" to rssi,
|
|
803
|
-
"connectable" to
|
|
1071
|
+
"connectable" to isConnectable,
|
|
804
1072
|
"visiblePeers" to estimatedVisiblePeerCount
|
|
805
1073
|
)
|
|
806
1074
|
)
|
|
807
1075
|
}
|
|
808
1076
|
lastSeenRssi[address] = rssi.toShort()
|
|
809
1077
|
|
|
810
|
-
val scanRecord = result.scanRecord
|
|
811
1078
|
val serviceData = scanRecord?.getServiceData(ParcelUuid(SERVICE_UUID))
|
|
812
1079
|
val meshMetadata = MeshAdvertisementData.decode(serviceData)
|
|
813
1080
|
meshMetadata?.let {
|
|
@@ -837,11 +1104,23 @@ class BleManager(
|
|
|
837
1104
|
}
|
|
838
1105
|
|
|
839
1106
|
// Adaptive scanning: rate limit connection attempts
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
1107
|
+
// Skip throttling for first-time discoveries with strong signals for faster connection
|
|
1108
|
+
val isFirstDiscovery = !lastSeenMeshAdvertisements.containsKey(address) && connections.getGatt(address) == null
|
|
1109
|
+
val hasStrongSignal = rssi >= -70
|
|
1110
|
+
|
|
1111
|
+
if (!isFirstDiscovery || !hasStrongSignal) {
|
|
1112
|
+
if (shouldThrottleConnection(address, now)) {
|
|
1113
|
+
if (logThrottler.shouldLog("adaptive_throttle_$address", intervalMs = 30000)) {
|
|
1114
|
+
Log.d(TAG, "Adaptive: throttling connection to $address")
|
|
1115
|
+
}
|
|
1116
|
+
return
|
|
843
1117
|
}
|
|
844
|
-
|
|
1118
|
+
} else if (isFirstDiscovery && hasStrongSignal) {
|
|
1119
|
+
Log.d(TAG, "Fast-tracking first discovery with strong signal: $address RSSI=$rssi")
|
|
1120
|
+
emitDiagnostic("info", "Fast-tracking first discovery", mapOf(
|
|
1121
|
+
"address" to address,
|
|
1122
|
+
"rssi" to rssi
|
|
1123
|
+
))
|
|
845
1124
|
}
|
|
846
1125
|
|
|
847
1126
|
if (!meshController.connectionBudgetAvailable() && decision.evictPeerId != null) {
|
|
@@ -879,19 +1158,209 @@ class BleManager(
|
|
|
879
1158
|
}
|
|
880
1159
|
|
|
881
1160
|
maybeHandleRebalance("scan")
|
|
1161
|
+
|
|
1162
|
+
// Check if we should proactively refresh the scan
|
|
1163
|
+
maybeProactivelyRefreshScan(now)
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
/**
|
|
1167
|
+
* Determines if a discovered device should be processed.
|
|
1168
|
+
* Implements smart filtering since we scan without a service UUID filter
|
|
1169
|
+
* (required for iOS ↔ Android interoperability).
|
|
1170
|
+
*
|
|
1171
|
+
* Accepts:
|
|
1172
|
+
* - Devices advertising our service UUID
|
|
1173
|
+
* - Devices with our service data
|
|
1174
|
+
* - Previously discovered mesh devices
|
|
1175
|
+
* - Unknown connectable devices (rate-limited, verified via GATT)
|
|
1176
|
+
*/
|
|
1177
|
+
private fun shouldProcessDiscoveredDevice(
|
|
1178
|
+
address: String,
|
|
1179
|
+
scanRecord: android.bluetooth.le.ScanRecord?,
|
|
1180
|
+
rssi: Int,
|
|
1181
|
+
isConnectable: Boolean,
|
|
1182
|
+
now: Long
|
|
1183
|
+
): Boolean {
|
|
1184
|
+
// 1. Check if device is advertising our service UUID
|
|
1185
|
+
val serviceUuids = scanRecord?.serviceUuids
|
|
1186
|
+
if (serviceUuids != null) {
|
|
1187
|
+
for (uuid in serviceUuids) {
|
|
1188
|
+
if (uuid.uuid == SERVICE_UUID) {
|
|
1189
|
+
return true
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
// 2. Check for our service data
|
|
1195
|
+
val serviceData = scanRecord?.getServiceData(ParcelUuid(SERVICE_UUID))
|
|
1196
|
+
if (serviceData != null) {
|
|
1197
|
+
return true
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// 3. Check if this is a previously discovered mesh device
|
|
1201
|
+
if (lastSeenMeshAdvertisements.containsKey(address)) {
|
|
1202
|
+
return true
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// 4. Check if we already have a device ID mapping for this device
|
|
1206
|
+
if (connections.deviceIdForAddress(address) != null) {
|
|
1207
|
+
return true
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// 5. Check if we have an active GATT connection to this device
|
|
1211
|
+
if (connections.getGatt(address) != null) {
|
|
1212
|
+
return true
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// 6. For unknown connectable devices, allow with rate limiting
|
|
1216
|
+
// These will be verified via GATT service discovery after connection
|
|
1217
|
+
// This is CRITICAL for iOS ↔ Android cross-platform discovery since
|
|
1218
|
+
// each platform may not recognize the other's service UUID format in advertisements
|
|
1219
|
+
if (isConnectable) {
|
|
1220
|
+
// Rate limit unknown device connection attempts
|
|
1221
|
+
val lastAttempt = unknownDeviceAttempts[address]
|
|
1222
|
+
if (lastAttempt != null && now - lastAttempt < UNKNOWN_DEVICE_RATE_LIMIT_MS) {
|
|
1223
|
+
return false
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
// Check global rate limit for unknown device attempts
|
|
1227
|
+
val recentUnknownAttempts = unknownDeviceAttempts.values.count {
|
|
1228
|
+
now - it < 60_000L
|
|
1229
|
+
}
|
|
1230
|
+
if (recentUnknownAttempts >= MAX_UNKNOWN_DEVICE_ATTEMPTS_PER_MINUTE) {
|
|
1231
|
+
return false
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// Only process signals above minimum threshold for unknown devices
|
|
1235
|
+
// Use a tiered approach: stronger signals get priority
|
|
1236
|
+
val shouldAttempt = when {
|
|
1237
|
+
rssi >= -70 -> true // Strong signal - always try
|
|
1238
|
+
rssi >= UNKNOWN_DEVICE_MIN_RSSI && recentUnknownAttempts < MAX_UNKNOWN_DEVICE_ATTEMPTS_PER_MINUTE / 2 -> true
|
|
1239
|
+
else -> false
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
if (shouldAttempt) {
|
|
1243
|
+
unknownDeviceAttempts[address] = now
|
|
1244
|
+
if (logThrottler.shouldLog("unknown_connectable_$address", intervalMs = 30_000)) {
|
|
1245
|
+
Log.d(TAG, "Allowing unknown connectable device for GATT verification: $address RSSI=$rssi")
|
|
1246
|
+
emitDiagnostic("debug", "Allowing unknown device for GATT verification", mapOf(
|
|
1247
|
+
"address" to address,
|
|
1248
|
+
"rssi" to rssi,
|
|
1249
|
+
"recentAttempts" to recentUnknownAttempts
|
|
1250
|
+
))
|
|
1251
|
+
}
|
|
1252
|
+
return true
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// Filter out all other devices (not our mesh network)
|
|
1257
|
+
return false
|
|
882
1258
|
}
|
|
883
1259
|
|
|
1260
|
+
/**
|
|
1261
|
+
* Proactively refreshes the scan periodically to ensure we don't miss devices
|
|
1262
|
+
* due to BLE stack issues or cached state.
|
|
1263
|
+
*/
|
|
1264
|
+
private fun maybeProactivelyRefreshScan(now: Long) {
|
|
1265
|
+
if (now - lastProactiveScanRefresh >= PROACTIVE_SCAN_REFRESH_MS) {
|
|
1266
|
+
lastProactiveScanRefresh = now
|
|
1267
|
+
if (logThrottler.shouldLog("proactive_scan_refresh", intervalMs = PROACTIVE_SCAN_REFRESH_MS)) {
|
|
1268
|
+
Log.d(TAG, "Proactively refreshing BLE scan")
|
|
1269
|
+
emitDiagnostic("info", "Proactive scan refresh")
|
|
1270
|
+
}
|
|
1271
|
+
restartScanning("proactive_refresh")
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
// Forced complete BLE refresh - more aggressive than proactive refresh
|
|
1275
|
+
// This helps recover from edge cases where the BLE stack becomes stuck
|
|
1276
|
+
val lastForced = if (lastForcedBleRefresh == 0L) transportStartAt else lastForcedBleRefresh
|
|
1277
|
+
if (now - lastForced >= FORCED_BLE_REFRESH_MS) {
|
|
1278
|
+
lastForcedBleRefresh = now
|
|
1279
|
+
if (logThrottler.shouldLog("forced_ble_refresh", intervalMs = FORCED_BLE_REFRESH_MS)) {
|
|
1280
|
+
Log.i(TAG, "Performing forced BLE refresh for reliability")
|
|
1281
|
+
emitDiagnostic("info", "Forced BLE refresh for reliability", mapOf(
|
|
1282
|
+
"connectedPeers" to connections.connectionCount(),
|
|
1283
|
+
"discoveredPeers" to connections.discoveredPeerCount()
|
|
1284
|
+
))
|
|
1285
|
+
}
|
|
1286
|
+
// Stop and restart both scanning and advertising
|
|
1287
|
+
stopScanning("forced_refresh")
|
|
1288
|
+
refreshAdvertising("forced_refresh")
|
|
1289
|
+
mainHandler.postDelayed({
|
|
1290
|
+
if (state == TransportState.RUNNING) {
|
|
1291
|
+
startScanning("forced_refresh")
|
|
1292
|
+
}
|
|
1293
|
+
}, 500)
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
/**
|
|
1298
|
+
* Computes a hash of the advertisement data for duplicate detection.
|
|
1299
|
+
* Uses device address, RSSI bucket, and key advertisement data.
|
|
1300
|
+
*/
|
|
1301
|
+
private fun computeAdvertisementHash(result: ScanResult): Int {
|
|
1302
|
+
var hash = result.device.address.hashCode()
|
|
1303
|
+
// Use RSSI buckets of 5 dBm to avoid hash changes from minor signal fluctuations
|
|
1304
|
+
hash = 31 * hash + (result.rssi / 5)
|
|
1305
|
+
|
|
1306
|
+
val scanRecord = result.scanRecord
|
|
1307
|
+
if (scanRecord != null) {
|
|
1308
|
+
// Include service UUIDs
|
|
1309
|
+
scanRecord.serviceUuids?.forEach { uuid ->
|
|
1310
|
+
hash = 31 * hash + uuid.hashCode()
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
// Include service data
|
|
1314
|
+
val serviceData = scanRecord.getServiceData(ParcelUuid(SERVICE_UUID))
|
|
1315
|
+
if (serviceData != null) {
|
|
1316
|
+
hash = 31 * hash + serviceData.contentHashCode()
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
return hash
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
/** Lock for atomic connection count check and connect operations */
|
|
1324
|
+
private val connectionLock = Any()
|
|
1325
|
+
|
|
884
1326
|
private fun connectToDevice(device: BluetoothDevice) {
|
|
885
1327
|
try {
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1328
|
+
// Atomic check-and-connect to prevent race conditions
|
|
1329
|
+
synchronized(connectionLock) {
|
|
1330
|
+
// Check RSSI threshold - don't connect to devices with weak signals
|
|
1331
|
+
val rssi = lastSeenRssi[device.address]?.toInt() ?: -60
|
|
1332
|
+
if (rssi < MINIMUM_RSSI_TO_CONNECT) {
|
|
1333
|
+
if (logThrottler.shouldLog("rssi_skip_${device.address}", intervalMs = 10000)) {
|
|
1334
|
+
Log.d(TAG, "Skipping connection to ${device.address} due to weak RSSI ($rssi < $MINIMUM_RSSI_TO_CONNECT)")
|
|
1335
|
+
emitDiagnostic("debug", "Skipping BLE connect due to weak RSSI", mapOf(
|
|
1336
|
+
"address" to device.address,
|
|
1337
|
+
"rssi" to rssi,
|
|
1338
|
+
"threshold" to MINIMUM_RSSI_TO_CONNECT
|
|
1339
|
+
))
|
|
1340
|
+
}
|
|
1341
|
+
connections.consumePendingRole(device.address)
|
|
1342
|
+
return
|
|
889
1343
|
}
|
|
890
|
-
|
|
891
|
-
|
|
1344
|
+
|
|
1345
|
+
if (currentConnectionCount() >= MAX_CONNECTIONS_PER_DEVICE) {
|
|
1346
|
+
if (logThrottler.shouldLog("mesh_conn_cap", intervalMs = 10000)) {
|
|
1347
|
+
Log.d(TAG, "Connection cap reached, not connecting to ${device.address}")
|
|
1348
|
+
}
|
|
1349
|
+
connections.consumePendingRole(device.address)
|
|
1350
|
+
return
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// Double-check we don't already have a connection to this device
|
|
1354
|
+
if (connections.getGatt(device.address) != null) {
|
|
1355
|
+
if (logThrottler.shouldLog("already_connecting_${device.address}", intervalMs = 5000)) {
|
|
1356
|
+
Log.d(TAG, "Already have GATT client for ${device.address}")
|
|
1357
|
+
}
|
|
1358
|
+
return
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
val gatt = device.connectGatt(context, false, gattClientCallback, BluetoothDevice.TRANSPORT_LE)
|
|
1362
|
+
connections.registerGatt(device.address, gatt)
|
|
892
1363
|
}
|
|
893
|
-
val gatt = device.connectGatt(context, false, gattClientCallback, BluetoothDevice.TRANSPORT_LE)
|
|
894
|
-
connections.registerGatt(device.address, gatt)
|
|
895
1364
|
|
|
896
1365
|
Log.i(TAG, "Connecting to device: ${device.address}")
|
|
897
1366
|
emitDiagnostic("info", "Connecting to BLE device", mapOf("address" to device.address))
|
|
@@ -902,7 +1371,7 @@ class BleManager(
|
|
|
902
1371
|
}
|
|
903
1372
|
}
|
|
904
1373
|
|
|
905
|
-
private fun currentConnectionCount(): Int = connections.connectionCount()
|
|
1374
|
+
private fun currentConnectionCount(): Int = connections.connectionCount() + connectedCentrals.size
|
|
906
1375
|
|
|
907
1376
|
private fun refreshSelfMetrics() {
|
|
908
1377
|
val rssiValues = lastSeenRssi.values.map { it.toInt() }
|
|
@@ -971,6 +1440,7 @@ class BleManager(
|
|
|
971
1440
|
pendingFragments.remove(address)
|
|
972
1441
|
pendingOutboundFragments.remove(peerId)
|
|
973
1442
|
deviceIdResolutionAttempts.remove(address)
|
|
1443
|
+
connectionRetryCount.remove(address)
|
|
974
1444
|
meshController.registerDisconnection(peerId)
|
|
975
1445
|
refreshSelfMetrics()
|
|
976
1446
|
|
|
@@ -1362,6 +1832,13 @@ class BleManager(
|
|
|
1362
1832
|
|
|
1363
1833
|
/** Checks if we should skip this device based on RSSI filtering. */
|
|
1364
1834
|
private fun shouldFilterByRssi(rssi: Int): Boolean {
|
|
1835
|
+
// During aggressive discovery phase, don't apply density-based filtering
|
|
1836
|
+
val now = System.currentTimeMillis()
|
|
1837
|
+
if (transportStartAt > 0 && now - transportStartAt < AGGRESSIVE_DISCOVERY_PHASE_MS) {
|
|
1838
|
+
// Only filter out extremely weak signals during aggressive phase
|
|
1839
|
+
return rssi < MINIMUM_RSSI_TO_CONNECT
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1365
1842
|
// In dense networks, apply stricter RSSI filtering
|
|
1366
1843
|
val threshold = when {
|
|
1367
1844
|
estimatedVisiblePeerCount > ADAPTIVE_HIGH_DENSITY_THRESHOLD -> -70
|
|
@@ -1373,21 +1850,36 @@ class BleManager(
|
|
|
1373
1850
|
|
|
1374
1851
|
/** Checks if we should throttle connection attempts based on rate limits. */
|
|
1375
1852
|
private fun shouldThrottleConnection(address: String, now: Long): Boolean {
|
|
1853
|
+
// During aggressive discovery phase, use much shorter cooldowns
|
|
1854
|
+
val isAggressivePhase = transportStartAt > 0 && now - transportStartAt < AGGRESSIVE_DISCOVERY_PHASE_MS
|
|
1855
|
+
|
|
1376
1856
|
// Prune old entries
|
|
1377
1857
|
val oneMinuteAgo = now - 60_000L
|
|
1378
1858
|
synchronized(globalConnectionAttempts) {
|
|
1379
1859
|
globalConnectionAttempts.removeAll { it < oneMinuteAgo }
|
|
1380
1860
|
}
|
|
1861
|
+
|
|
1862
|
+
val effectiveCooldown = if (isAggressivePhase) 5_000L else ADAPTIVE_COOLDOWN_PER_DEVICE_MS
|
|
1381
1863
|
deviceConnectionAttempts.entries.removeIf {
|
|
1382
|
-
now - it.value >=
|
|
1864
|
+
now - it.value >= effectiveCooldown
|
|
1383
1865
|
}
|
|
1384
1866
|
|
|
1385
1867
|
// Check per-device cooldown
|
|
1386
1868
|
val lastAttempt = deviceConnectionAttempts[address]
|
|
1387
|
-
if (lastAttempt != null && now - lastAttempt <
|
|
1869
|
+
if (lastAttempt != null && now - lastAttempt < effectiveCooldown) {
|
|
1388
1870
|
return true
|
|
1389
1871
|
}
|
|
1390
1872
|
|
|
1873
|
+
// During aggressive phase, allow more connection attempts
|
|
1874
|
+
if (isAggressivePhase) {
|
|
1875
|
+
// Allow up to 3x the normal rate during aggressive phase
|
|
1876
|
+
val maxAttempts = ADAPTIVE_MAX_CONNECTIONS_PER_MINUTE * 3
|
|
1877
|
+
if (globalConnectionAttempts.size >= maxAttempts) {
|
|
1878
|
+
return true
|
|
1879
|
+
}
|
|
1880
|
+
return false
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1391
1883
|
// In dense networks, apply global rate limiting
|
|
1392
1884
|
if (estimatedVisiblePeerCount > ADAPTIVE_LOW_DENSITY_THRESHOLD) {
|
|
1393
1885
|
val currentAttempts = globalConnectionAttempts.size
|
|
@@ -1518,12 +2010,19 @@ class BleManager(
|
|
|
1518
2010
|
gattServer?.cancelConnection(device)
|
|
1519
2011
|
return
|
|
1520
2012
|
}
|
|
2013
|
+
// Check connection capacity
|
|
2014
|
+
if (currentConnectionCount() >= MAX_CONNECTIONS_PER_DEVICE) {
|
|
2015
|
+
Log.w(TAG, "Rejecting inbound connection from ${device.address}: connection cap reached")
|
|
2016
|
+
gattServer?.cancelConnection(device)
|
|
2017
|
+
return
|
|
2018
|
+
}
|
|
1521
2019
|
val role = when (decision.intent) {
|
|
1522
2020
|
ConnectionIntent.INTER_CLUSTER -> MeshRole.BRIDGE
|
|
1523
2021
|
ConnectionIntent.INTRA_CLUSTER, ConnectionIntent.REJECTED -> MeshRole.MEMBER
|
|
1524
2022
|
}
|
|
1525
2023
|
connections.trackServerConnection(device.address)
|
|
1526
2024
|
connections.setPendingRole(device.address, role)
|
|
2025
|
+
connectedCentrals[device.address] = System.currentTimeMillis()
|
|
1527
2026
|
Log.i(TAG, "GATT server: Device connected: ${device.address} (role=$role)")
|
|
1528
2027
|
emitDiagnostic("info", "Device connected to GATT server", mapOf("address" to device.address))
|
|
1529
2028
|
}
|
|
@@ -1531,6 +2030,7 @@ class BleManager(
|
|
|
1531
2030
|
val address = device.address
|
|
1532
2031
|
connections.untrackServerConnection(address)
|
|
1533
2032
|
connections.consumePendingRole(address)
|
|
2033
|
+
connectedCentrals.remove(address)
|
|
1534
2034
|
// Don't immediately remove - connection might be re-established
|
|
1535
2035
|
// Only remove if it's a permanent error (status != 0)
|
|
1536
2036
|
if (status != 0 && status != 19) { // Not normal disconnect or connection timeout
|
|
@@ -1657,7 +2157,43 @@ class BleManager(
|
|
|
1657
2157
|
|
|
1658
2158
|
// Try to reconnect if we were connected and state is still running
|
|
1659
2159
|
if (wasConnected && state == TransportState.RUNNING) {
|
|
1660
|
-
//
|
|
2160
|
+
// Increment retry count and calculate backoff
|
|
2161
|
+
val retryCount = (connectionRetryCount[address] ?: 0) + 1
|
|
2162
|
+
connectionRetryCount[address] = retryCount
|
|
2163
|
+
|
|
2164
|
+
// Give up after max retries
|
|
2165
|
+
if (retryCount > MAX_CONNECTION_RETRIES) {
|
|
2166
|
+
Log.w(TAG, "Max retries ($MAX_CONNECTION_RETRIES) exceeded for $address on disconnect, giving up")
|
|
2167
|
+
emitDiagnostic("warning", "Max connection retries exceeded", mapOf(
|
|
2168
|
+
"address" to address,
|
|
2169
|
+
"retryCount" to retryCount
|
|
2170
|
+
))
|
|
2171
|
+
connectionRetryCount.remove(address)
|
|
2172
|
+
// Notify peer lost since we're giving up
|
|
2173
|
+
connections.deviceIdForAddress(address)?.let { peerId ->
|
|
2174
|
+
protocol.removeNeighborRoutes(peerId)
|
|
2175
|
+
try {
|
|
2176
|
+
protocol.blePeerLost(peerId)
|
|
2177
|
+
} catch (e: Exception) {
|
|
2178
|
+
Log.e(TAG, "Error notifying peer lost", e)
|
|
2179
|
+
}
|
|
2180
|
+
meshController.registerDisconnection(peerId)
|
|
2181
|
+
refreshSelfMetrics()
|
|
2182
|
+
connections.removeIdentifiersForAddress(address)
|
|
2183
|
+
connections.removeConnectionRole(peerId)
|
|
2184
|
+
mainHandler.post {
|
|
2185
|
+
if (state == TransportState.RUNNING) {
|
|
2186
|
+
refreshAdvertising("disconnect_max_retries")
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
maybeHandleRebalance("disconnect_max_retries")
|
|
2190
|
+
}
|
|
2191
|
+
return@onConnectionStateChange
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
// Exponential backoff: 5s, 10s, 20s, 40s, 60s (capped)
|
|
2195
|
+
val backoffInterval = minOf(MAX_RECONNECT_INTERVAL_MS, MIN_RECONNECT_INTERVAL_MS * (1L shl (retryCount - 1)))
|
|
2196
|
+
|
|
1661
2197
|
mainHandler.postDelayed({
|
|
1662
2198
|
if (state == TransportState.RUNNING && connections.hasDeviceForAddress(address)) {
|
|
1663
2199
|
try {
|
|
@@ -1669,7 +2205,7 @@ class BleManager(
|
|
|
1669
2205
|
Log.e(TAG, "Error reconnecting to device", e)
|
|
1670
2206
|
}
|
|
1671
2207
|
}
|
|
1672
|
-
},
|
|
2208
|
+
}, backoffInterval)
|
|
1673
2209
|
} else {
|
|
1674
2210
|
// Notify protocol of peer loss only if we're not reconnecting
|
|
1675
2211
|
connections.deviceIdForAddress(address)?.let { deviceId ->
|
|
@@ -1760,6 +2296,7 @@ class BleManager(
|
|
|
1760
2296
|
if (deviceIdValue != null) {
|
|
1761
2297
|
Log.i(TAG, "📝 Mapping ${gatt.device.address} -> $deviceIdValue")
|
|
1762
2298
|
connections.setDeviceIdentifier(gatt.device.address, deviceIdValue)
|
|
2299
|
+
connectionRetryCount.remove(gatt.device.address) // Reset retry count on successful connection
|
|
1763
2300
|
|
|
1764
2301
|
val role = connections.consumePendingRole(gatt.device.address) ?: MeshRole.MEMBER
|
|
1765
2302
|
meshController.registerConnection(deviceIdValue, role)
|