@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
package/ios/BleManager.swift
CHANGED
|
@@ -152,15 +152,35 @@ public class BleManager: NSObject, TransportManager {
|
|
|
152
152
|
private var lastPeerCountUpdate: Date?
|
|
153
153
|
private let SCAN_HEARTBEAT_INTERVAL: TimeInterval = 10.0
|
|
154
154
|
private let SCAN_RESTART_INTERVAL: TimeInterval = 30.0
|
|
155
|
+
/// Force a complete BLE stack refresh periodically even when things seem healthy
|
|
156
|
+
private let FORCED_BLE_REFRESH_INTERVAL: TimeInterval = 120.0
|
|
157
|
+
private var lastForcedBleRefresh: Date?
|
|
155
158
|
private var connectionMonitor: DispatchSourceTimer?
|
|
156
159
|
private var connectionAttemptTimestamps: [UUID: Date] = [:]
|
|
160
|
+
private var connectionRetryCount: [UUID: Int] = [:]
|
|
157
161
|
private let CONNECTION_MONITOR_INTERVAL: TimeInterval = 5.0
|
|
158
162
|
private let MIN_RECONNECT_INTERVAL: TimeInterval = 5.0
|
|
163
|
+
private let MAX_RECONNECT_INTERVAL: TimeInterval = 60.0
|
|
164
|
+
private let MAX_CONNECTION_RETRIES = 5
|
|
159
165
|
private var scanRestartCount: Int = 0
|
|
160
166
|
private var lastCentralReset: Date?
|
|
161
167
|
private let MAX_CONSECUTIVE_SCAN_RESTARTS = 3
|
|
162
168
|
private let CENTRAL_RESET_BACKOFF: TimeInterval = 45.0
|
|
163
169
|
private let MINIMUM_RSSI_TO_CONNECT: Int16 = -90
|
|
170
|
+
/// Rate limiting for unknown connectable devices that need GATT verification
|
|
171
|
+
private var unknownDeviceAttempts: [UUID: Date] = [:]
|
|
172
|
+
private let UNKNOWN_DEVICE_RATE_LIMIT: TimeInterval = 5.0 // More aggressive for cross-platform discovery
|
|
173
|
+
private let UNKNOWN_DEVICE_MIN_RSSI: Int16 = -80 // More lenient for cross-platform discovery
|
|
174
|
+
private let MAX_UNKNOWN_DEVICE_ATTEMPTS_PER_MINUTE = 10
|
|
175
|
+
/// Proactive scan refresh interval even when discoveries are occurring
|
|
176
|
+
private let PROACTIVE_SCAN_REFRESH_INTERVAL: TimeInterval = 60.0
|
|
177
|
+
private var lastProactiveScanRefresh: Date?
|
|
178
|
+
/// Tracks recently seen advertisement hashes to avoid duplicate processing
|
|
179
|
+
private var recentAdvertisementHashes: [UUID: (hash: Int, timestamp: Date)] = [:]
|
|
180
|
+
/// Initial aggressive discovery phase duration - more frequent scanning initially
|
|
181
|
+
private let AGGRESSIVE_DISCOVERY_PHASE: TimeInterval = 30.0
|
|
182
|
+
/// Tracks when aggressive discovery phase started
|
|
183
|
+
private var aggressiveDiscoveryStarted: Date?
|
|
164
184
|
|
|
165
185
|
// MARK: - Thread helpers
|
|
166
186
|
@inline(__always)
|
|
@@ -387,10 +407,15 @@ public class BleManager: NSObject, TransportManager {
|
|
|
387
407
|
pendingFragments.removeAll()
|
|
388
408
|
pendingOutboundFragments.removeAll()
|
|
389
409
|
lastSeenMeshAdvertisements.removeAll()
|
|
410
|
+
unknownDeviceAttempts.removeAll()
|
|
411
|
+
recentAdvertisementHashes.removeAll()
|
|
390
412
|
pendingAdvertiseRestart?.cancel()
|
|
391
413
|
pendingAdvertiseRestart = nil
|
|
392
414
|
lastAdvertiseRestartAt = nil
|
|
393
415
|
transportStartAt = nil
|
|
416
|
+
lastProactiveScanRefresh = nil
|
|
417
|
+
lastForcedBleRefresh = nil
|
|
418
|
+
aggressiveDiscoveryStarted = nil
|
|
394
419
|
subscribedCentrals.removeAll()
|
|
395
420
|
|
|
396
421
|
// Clean up managers
|
|
@@ -477,13 +502,27 @@ public class BleManager: NSObject, TransportManager {
|
|
|
477
502
|
scanRestartCount = 0
|
|
478
503
|
}
|
|
479
504
|
|
|
505
|
+
// Scan without service UUID filter for iOS ↔ Android interoperability
|
|
506
|
+
// iOS's scanForPeripherals(withServices:) has known issues recognizing 128-bit
|
|
507
|
+
// service UUIDs from Android advertisements. Scanning with nil allows us to see
|
|
508
|
+
// all peripherals and filter in the discovery callback instead.
|
|
480
509
|
central.scanForPeripherals(
|
|
481
|
-
withServices:
|
|
510
|
+
withServices: nil,
|
|
482
511
|
options: [CBCentralManagerScanOptionAllowDuplicatesKey: true]
|
|
483
512
|
)
|
|
484
513
|
isScanning = true
|
|
485
|
-
|
|
514
|
+
let now = Date()
|
|
515
|
+
scanStartDate = now
|
|
486
516
|
lastDiscoveryDate = scanStartDate
|
|
517
|
+
lastProactiveScanRefresh = now
|
|
518
|
+
// Start aggressive discovery phase for initial faster connection
|
|
519
|
+
if aggressiveDiscoveryStarted == nil {
|
|
520
|
+
aggressiveDiscoveryStarted = now
|
|
521
|
+
print("[BleManager] Starting aggressive discovery phase (\(AGGRESSIVE_DISCOVERY_PHASE)s)")
|
|
522
|
+
emitDiagnostic("info", "Starting aggressive discovery phase", context: [
|
|
523
|
+
"duration": AGGRESSIVE_DISCOVERY_PHASE
|
|
524
|
+
])
|
|
525
|
+
}
|
|
487
526
|
startScanMonitor()
|
|
488
527
|
if logThrottler.shouldLog(key: "scan_started") {
|
|
489
528
|
let context: [String: Any] = [
|
|
@@ -512,6 +551,7 @@ public class BleManager: NSObject, TransportManager {
|
|
|
512
551
|
scanStartDate = nil
|
|
513
552
|
lastDiscoveryDate = nil
|
|
514
553
|
connectionAttemptTimestamps.removeAll()
|
|
554
|
+
connectionRetryCount.removeAll()
|
|
515
555
|
if logThrottler.shouldLog(key: "scan_stopped") {
|
|
516
556
|
print("[BleManager] Stopped scanning (reason: \(reason))")
|
|
517
557
|
}
|
|
@@ -528,12 +568,61 @@ public class BleManager: NSObject, TransportManager {
|
|
|
528
568
|
let now = Date()
|
|
529
569
|
let lastActivity = self.lastDiscoveryDate ?? self.scanStartDate ?? now
|
|
530
570
|
let idleDuration = now.timeIntervalSince(lastActivity)
|
|
571
|
+
|
|
572
|
+
// Check for inactivity-based restart
|
|
531
573
|
if idleDuration >= self.SCAN_RESTART_INTERVAL {
|
|
532
574
|
if self.logThrottler.shouldLog(key: "scan_watchdog", interval: self.SCAN_RESTART_INTERVAL) {
|
|
533
575
|
print("[BleManager] Restarting scan after \(Int(idleDuration))s of inactivity")
|
|
534
576
|
self.emitDiagnostic("warning", "Restarting BLE scan due to inactivity", context: ["idle_seconds": Int(idleDuration)])
|
|
535
577
|
}
|
|
536
578
|
self.restartScanningDueToInactivity()
|
|
579
|
+
return
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Proactive scan refresh even when discoveries are occurring
|
|
583
|
+
// This ensures we don't miss devices due to BLE stack issues
|
|
584
|
+
let lastRefresh = self.lastProactiveScanRefresh ?? self.scanStartDate ?? now
|
|
585
|
+
if now.timeIntervalSince(lastRefresh) >= self.PROACTIVE_SCAN_REFRESH_INTERVAL {
|
|
586
|
+
if self.logThrottler.shouldLog(key: "proactive_scan_refresh", interval: self.PROACTIVE_SCAN_REFRESH_INTERVAL) {
|
|
587
|
+
print("[BleManager] Proactively refreshing BLE scan")
|
|
588
|
+
self.emitDiagnostic("info", "Proactive scan refresh")
|
|
589
|
+
}
|
|
590
|
+
self.lastProactiveScanRefresh = now
|
|
591
|
+
self.restartScanningDueToInactivity()
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Forced complete BLE refresh - more aggressive than proactive refresh
|
|
595
|
+
// This helps recover from edge cases where the BLE stack becomes stuck
|
|
596
|
+
let lastForced = self.lastForcedBleRefresh ?? self.transportStartAt ?? now
|
|
597
|
+
if now.timeIntervalSince(lastForced) >= self.FORCED_BLE_REFRESH_INTERVAL {
|
|
598
|
+
self.lastForcedBleRefresh = now
|
|
599
|
+
if self.logThrottler.shouldLog(key: "forced_ble_refresh", interval: self.FORCED_BLE_REFRESH_INTERVAL) {
|
|
600
|
+
print("[BleManager] Performing forced BLE refresh for reliability")
|
|
601
|
+
self.emitDiagnostic("info", "Forced BLE refresh for reliability", context: [
|
|
602
|
+
"connectedPeers": self.connections.connectedPeripheralCount(),
|
|
603
|
+
"discoveredPeers": self.discoveredPeripherals.count
|
|
604
|
+
])
|
|
605
|
+
}
|
|
606
|
+
// Stop and restart both scanning and advertising
|
|
607
|
+
self.stopScanning(reason: "forced_refresh")
|
|
608
|
+
self.refreshAdvertising(reason: "forced_refresh")
|
|
609
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
|
|
610
|
+
self?.startScanning(reason: "forced_refresh")
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// After aggressive phase ends, do a targeted scan with service UUID filter
|
|
615
|
+
// This can help discover Android devices that might have been missed
|
|
616
|
+
if let started = self.aggressiveDiscoveryStarted,
|
|
617
|
+
now.timeIntervalSince(started) >= self.AGGRESSIVE_DISCOVERY_PHASE,
|
|
618
|
+
now.timeIntervalSince(started) < self.AGGRESSIVE_DISCOVERY_PHASE + self.SCAN_HEARTBEAT_INTERVAL {
|
|
619
|
+
print("[BleManager] Aggressive phase ended, performing targeted service scan")
|
|
620
|
+
self.emitDiagnostic("info", "Aggressive phase ended, performing targeted service scan", context: [
|
|
621
|
+
"discoveredPeers": self.discoveredPeripherals.count,
|
|
622
|
+
"connectedPeers": self.connections.connectedPeripheralCount()
|
|
623
|
+
])
|
|
624
|
+
// Brief targeted scan with service UUID
|
|
625
|
+
self.performTargetedServiceScan()
|
|
537
626
|
}
|
|
538
627
|
}
|
|
539
628
|
timer.resume()
|
|
@@ -558,6 +647,33 @@ public class BleManager: NSObject, TransportManager {
|
|
|
558
647
|
}
|
|
559
648
|
}
|
|
560
649
|
|
|
650
|
+
/// Performs a brief targeted scan with service UUID filter.
|
|
651
|
+
/// This helps discover Android devices that might not advertise our service UUID
|
|
652
|
+
/// in the main advertisement packet but include it in scan response.
|
|
653
|
+
private func performTargetedServiceScan() {
|
|
654
|
+
guard let central = centralManager, central.state == .poweredOn else { return }
|
|
655
|
+
guard isScanning else { return }
|
|
656
|
+
|
|
657
|
+
// Brief stop and restart with service filter
|
|
658
|
+
central.stopScan()
|
|
659
|
+
|
|
660
|
+
// Scan with service UUID filter for 5 seconds
|
|
661
|
+
central.scanForPeripherals(
|
|
662
|
+
withServices: [SERVICE_UUID],
|
|
663
|
+
options: [CBCentralManagerScanOptionAllowDuplicatesKey: true]
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
// After 5 seconds, go back to filterless scanning
|
|
667
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) { [weak self] in
|
|
668
|
+
guard let self = self, self.isScanning else { return }
|
|
669
|
+
self.centralManager?.stopScan()
|
|
670
|
+
self.centralManager?.scanForPeripherals(
|
|
671
|
+
withServices: nil,
|
|
672
|
+
options: [CBCentralManagerScanOptionAllowDuplicatesKey: true]
|
|
673
|
+
)
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
561
677
|
private func evaluateCentralHealthAfterRestart() {
|
|
562
678
|
guard scanRestartCount >= MAX_CONSECUTIVE_SCAN_RESTARTS else { return }
|
|
563
679
|
let now = Date()
|
|
@@ -617,13 +733,22 @@ public class BleManager: NSObject, TransportManager {
|
|
|
617
733
|
private func attemptConnection(to peripheral: CBPeripheral, reason: String, rssi: Int16? = nil, desiredRole: MeshController.MeshRole? = nil) {
|
|
618
734
|
DispatchQueue.main.async { [weak self] in
|
|
619
735
|
guard let self = self else { return }
|
|
736
|
+
|
|
737
|
+
// Atomic check-and-connect: all checks must pass before proceeding
|
|
738
|
+
// This prevents race conditions when multiple discovery callbacks try to connect
|
|
739
|
+
|
|
740
|
+
// 1. Already connected check
|
|
620
741
|
if self.connections.connectedPeripheral(for: peripheral.identifier) != nil {
|
|
621
742
|
return
|
|
622
743
|
}
|
|
744
|
+
|
|
745
|
+
// 2. Recent attempt cooldown check
|
|
623
746
|
let now = Date()
|
|
624
747
|
if let lastAttempt = self.connectionAttemptTimestamps[peripheral.identifier], now.timeIntervalSince(lastAttempt) < self.MIN_RECONNECT_INTERVAL {
|
|
625
748
|
return
|
|
626
749
|
}
|
|
750
|
+
|
|
751
|
+
// 3. RSSI threshold check
|
|
627
752
|
if let effectiveRSSI = rssi ?? self.peripheralRSSI[peripheral.identifier], effectiveRSSI < self.MINIMUM_RSSI_TO_CONNECT {
|
|
628
753
|
if self.logThrottler.shouldLog(key: "rssi_skip_\(peripheral.identifier.uuidString)", interval: 10) {
|
|
629
754
|
self.emitDiagnostic("debug", "Skipping BLE connect due to weak RSSI", context: [
|
|
@@ -634,12 +759,24 @@ public class BleManager: NSObject, TransportManager {
|
|
|
634
759
|
}
|
|
635
760
|
return
|
|
636
761
|
}
|
|
762
|
+
|
|
763
|
+
// 4. Connection capacity check (atomic with the connect call)
|
|
637
764
|
if self.currentConnectionCount() >= self.MAX_CONNECTIONS_PER_DEVICE {
|
|
638
765
|
if self.logThrottler.shouldLog(key: "mesh_conn_cap_ios", interval: 10) {
|
|
639
766
|
print("[BleManager] Connection cap reached, not connecting to \(peripheral.identifier)")
|
|
640
767
|
}
|
|
641
768
|
return
|
|
642
769
|
}
|
|
770
|
+
|
|
771
|
+
// 5. Double-check peripheral state before connecting
|
|
772
|
+
guard peripheral.state != .connecting else {
|
|
773
|
+
if self.logThrottler.shouldLog(key: "already_connecting_\(peripheral.identifier.uuidString)", interval: 5) {
|
|
774
|
+
print("[BleManager] Already connecting to \(peripheral.identifier)")
|
|
775
|
+
}
|
|
776
|
+
return
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// All checks passed - proceed with connection
|
|
643
780
|
self.connectionAttemptTimestamps[peripheral.identifier] = now
|
|
644
781
|
if let desiredRole = desiredRole {
|
|
645
782
|
self.connections.setPendingRole(desiredRole, for: peripheral.identifier)
|
|
@@ -647,6 +784,7 @@ public class BleManager: NSObject, TransportManager {
|
|
|
647
784
|
self.connections.setPendingRole(.member, for: peripheral.identifier)
|
|
648
785
|
}
|
|
649
786
|
peripheral.delegate = self
|
|
787
|
+
|
|
650
788
|
if peripheral.state == .connected {
|
|
651
789
|
self.connections.registerPeripheral(peripheral)
|
|
652
790
|
if let service = peripheral.services?.first(where: { $0.uuid == self.SERVICE_UUID }) {
|
|
@@ -656,6 +794,7 @@ public class BleManager: NSObject, TransportManager {
|
|
|
656
794
|
}
|
|
657
795
|
return
|
|
658
796
|
}
|
|
797
|
+
|
|
659
798
|
self.centralManager?.connect(peripheral, options: nil)
|
|
660
799
|
if self.logThrottler.shouldLog(key: "connect_attempt_\(peripheral.identifier.uuidString)", interval: 10) {
|
|
661
800
|
print("[BleManager] Attempting connection to \(peripheral.identifier) (reason: \(reason))")
|
|
@@ -1026,6 +1165,7 @@ public class BleManager: NSObject, TransportManager {
|
|
|
1026
1165
|
pendingFragments.removeValue(forKey: identifier)
|
|
1027
1166
|
pendingOutboundFragments.removeValue(forKey: deviceId)
|
|
1028
1167
|
connectionAttemptTimestamps.removeValue(forKey: identifier)
|
|
1168
|
+
connectionRetryCount.removeValue(forKey: identifier)
|
|
1029
1169
|
meshController.registerDisconnection(peerId: deviceId)
|
|
1030
1170
|
refreshSelfMetrics()
|
|
1031
1171
|
|
|
@@ -1216,6 +1356,9 @@ public class BleManager: NSObject, TransportManager {
|
|
|
1216
1356
|
|
|
1217
1357
|
private func pruneMeshObservations(now: Date = Date()) {
|
|
1218
1358
|
lastSeenMeshAdvertisements = lastSeenMeshAdvertisements.filter { now.timeIntervalSince($0.value.timestamp) <= MESH_OBSERVATION_TTL }
|
|
1359
|
+
|
|
1360
|
+
// Also clean up stale unknown device rate limiting entries
|
|
1361
|
+
unknownDeviceAttempts = unknownDeviceAttempts.filter { now.timeIntervalSince($0.value) <= UNKNOWN_DEVICE_RATE_LIMIT * 3 }
|
|
1219
1362
|
}
|
|
1220
1363
|
|
|
1221
1364
|
// MARK: - Adaptive Scan Methods
|
|
@@ -1248,6 +1391,13 @@ public class BleManager: NSObject, TransportManager {
|
|
|
1248
1391
|
/// Checks if we should skip this peripheral based on RSSI filtering.
|
|
1249
1392
|
/// Returns true if the signal is too weak and we're in a dense environment.
|
|
1250
1393
|
private func shouldFilterByRssi(_ rssi: Int16) -> Bool {
|
|
1394
|
+
// During aggressive discovery phase, don't apply density-based filtering
|
|
1395
|
+
if let started = aggressiveDiscoveryStarted,
|
|
1396
|
+
Date().timeIntervalSince(started) < AGGRESSIVE_DISCOVERY_PHASE {
|
|
1397
|
+
// Only filter out extremely weak signals during aggressive phase
|
|
1398
|
+
return rssi < MINIMUM_RSSI_TO_CONNECT
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1251
1401
|
// In dense networks, apply stricter RSSI filtering
|
|
1252
1402
|
let threshold: Int16
|
|
1253
1403
|
if estimatedVisiblePeerCount > ADAPTIVE_HIGH_DENSITY_THRESHOLD {
|
|
@@ -1266,19 +1416,34 @@ public class BleManager: NSObject, TransportManager {
|
|
|
1266
1416
|
/// Checks if we should throttle connection attempts based on rate limits.
|
|
1267
1417
|
/// Returns true if we should skip this connection attempt.
|
|
1268
1418
|
private func shouldThrottleConnection(to peripheral: UUID, now: Date) -> Bool {
|
|
1419
|
+
// During aggressive discovery phase, use much shorter cooldowns
|
|
1420
|
+
let isAggressivePhase = aggressiveDiscoveryStarted.map { now.timeIntervalSince($0) < AGGRESSIVE_DISCOVERY_PHASE } ?? false
|
|
1421
|
+
|
|
1269
1422
|
// Prune old entries
|
|
1270
1423
|
let oneMinuteAgo = now.addingTimeInterval(-60.0)
|
|
1271
1424
|
globalConnectionAttempts = globalConnectionAttempts.filter { $0 > oneMinuteAgo }
|
|
1425
|
+
|
|
1426
|
+
let effectiveCooldown: TimeInterval = isAggressivePhase ? 5.0 : ADAPTIVE_COOLDOWN_PER_PERIPHERAL
|
|
1272
1427
|
peripheralConnectionAttempts = peripheralConnectionAttempts.filter {
|
|
1273
|
-
now.timeIntervalSince($0.value) <
|
|
1428
|
+
now.timeIntervalSince($0.value) < effectiveCooldown
|
|
1274
1429
|
}
|
|
1275
1430
|
|
|
1276
1431
|
// Check per-peripheral cooldown
|
|
1277
1432
|
if let lastAttempt = peripheralConnectionAttempts[peripheral],
|
|
1278
|
-
now.timeIntervalSince(lastAttempt) <
|
|
1433
|
+
now.timeIntervalSince(lastAttempt) < effectiveCooldown {
|
|
1279
1434
|
return true
|
|
1280
1435
|
}
|
|
1281
1436
|
|
|
1437
|
+
// During aggressive phase, allow more connection attempts
|
|
1438
|
+
if isAggressivePhase {
|
|
1439
|
+
// Allow up to 3x the normal rate during aggressive phase
|
|
1440
|
+
let maxAttempts = ADAPTIVE_MAX_CONNECTIONS_PER_MINUTE * 3
|
|
1441
|
+
if globalConnectionAttempts.count >= maxAttempts {
|
|
1442
|
+
return true
|
|
1443
|
+
}
|
|
1444
|
+
return false
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1282
1447
|
// In dense networks, apply global rate limiting
|
|
1283
1448
|
if estimatedVisiblePeerCount > ADAPTIVE_LOW_DENSITY_THRESHOLD {
|
|
1284
1449
|
let maxAttempts = ADAPTIVE_MAX_CONNECTIONS_PER_MINUTE
|
|
@@ -1319,6 +1484,124 @@ public class BleManager: NSObject, TransportManager {
|
|
|
1319
1484
|
|
|
1320
1485
|
return normalizedHash < skipProbability
|
|
1321
1486
|
}
|
|
1487
|
+
|
|
1488
|
+
// MARK: - Smart Filtering for iOS ↔ Android Interoperability
|
|
1489
|
+
|
|
1490
|
+
/// Determines if a discovered peripheral should be processed.
|
|
1491
|
+
/// This implements smart filtering since we scan without a service UUID filter
|
|
1492
|
+
/// (required for iOS ↔ Android interoperability).
|
|
1493
|
+
///
|
|
1494
|
+
/// Accepts:
|
|
1495
|
+
/// - Devices advertising our service UUID (iOS devices)
|
|
1496
|
+
/// - Devices with our service data
|
|
1497
|
+
/// - Previously discovered mesh devices
|
|
1498
|
+
/// - Unknown connectable devices (rate-limited, verified via GATT)
|
|
1499
|
+
private func shouldProcessDiscoveredPeripheral(
|
|
1500
|
+
peripheral: CBPeripheral,
|
|
1501
|
+
advertisementData: [String: Any],
|
|
1502
|
+
rssi: Int16,
|
|
1503
|
+
now: Date
|
|
1504
|
+
) -> Bool {
|
|
1505
|
+
// 1. Check if device is advertising our service UUID
|
|
1506
|
+
if let serviceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] {
|
|
1507
|
+
if serviceUUIDs.contains(SERVICE_UUID) {
|
|
1508
|
+
return true
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
// 2. Check for our service data (may come from scan response)
|
|
1513
|
+
if let serviceData = advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data] {
|
|
1514
|
+
if serviceData[SERVICE_UUID] != nil {
|
|
1515
|
+
return true
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
// 2b. Check overflow service UUIDs - Android devices sometimes advertise in overflow area
|
|
1520
|
+
// when the main advertisement packet is full
|
|
1521
|
+
if let overflowUUIDs = advertisementData[CBAdvertisementDataOverflowServiceUUIDsKey] as? [CBUUID] {
|
|
1522
|
+
if overflowUUIDs.contains(SERVICE_UUID) {
|
|
1523
|
+
return true
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
// 2c. Check solicited service UUIDs - some Android devices use this
|
|
1528
|
+
if let solicitedUUIDs = advertisementData[CBAdvertisementDataSolicitedServiceUUIDsKey] as? [CBUUID] {
|
|
1529
|
+
if solicitedUUIDs.contains(SERVICE_UUID) {
|
|
1530
|
+
return true
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
// 3. Check if this is a previously discovered mesh device
|
|
1535
|
+
if lastSeenMeshAdvertisements[peripheral.identifier] != nil {
|
|
1536
|
+
return true
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// 4. Check if we already have this peripheral in our discovered list
|
|
1540
|
+
// (previously connected or successfully verified via GATT)
|
|
1541
|
+
if discoveredPeripherals[peripheral.identifier] != nil {
|
|
1542
|
+
return true
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
// 5. Check if we already have a device ID mapping for this peripheral
|
|
1546
|
+
if connections.peripheralDeviceId(for: peripheral.identifier) != nil ||
|
|
1547
|
+
connections.centralDeviceId(for: peripheral.identifier) != nil {
|
|
1548
|
+
return true
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
// 6. For unknown connectable devices, allow with rate limiting
|
|
1552
|
+
// These will be verified via GATT service discovery after connection
|
|
1553
|
+
// This is CRITICAL for iOS ↔ Android cross-platform discovery since
|
|
1554
|
+
// each platform may not recognize the other's service UUID format in advertisements
|
|
1555
|
+
let isConnectable: Bool
|
|
1556
|
+
if #available(iOS 13.0, *) {
|
|
1557
|
+
isConnectable = (advertisementData[CBAdvertisementDataIsConnectable] as? NSNumber)?.boolValue ?? false
|
|
1558
|
+
} else {
|
|
1559
|
+
isConnectable = true
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
if isConnectable {
|
|
1563
|
+
// Rate limit unknown device connection attempts
|
|
1564
|
+
if let lastAttempt = unknownDeviceAttempts[peripheral.identifier],
|
|
1565
|
+
now.timeIntervalSince(lastAttempt) < UNKNOWN_DEVICE_RATE_LIMIT {
|
|
1566
|
+
return false
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
// Check global rate limit for unknown device attempts
|
|
1570
|
+
let oneMinuteAgo = now.addingTimeInterval(-60.0)
|
|
1571
|
+
let recentUnknownAttempts = unknownDeviceAttempts.values.filter { $0 > oneMinuteAgo }.count
|
|
1572
|
+
if recentUnknownAttempts >= MAX_UNKNOWN_DEVICE_ATTEMPTS_PER_MINUTE {
|
|
1573
|
+
return false
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
// Use a tiered approach: stronger signals get priority
|
|
1577
|
+
let shouldAttempt: Bool
|
|
1578
|
+
if rssi >= -70 {
|
|
1579
|
+
// Strong signal - always try
|
|
1580
|
+
shouldAttempt = true
|
|
1581
|
+
} else if rssi >= UNKNOWN_DEVICE_MIN_RSSI && recentUnknownAttempts < MAX_UNKNOWN_DEVICE_ATTEMPTS_PER_MINUTE / 2 {
|
|
1582
|
+
// Moderate signal and we have budget
|
|
1583
|
+
shouldAttempt = true
|
|
1584
|
+
} else {
|
|
1585
|
+
shouldAttempt = false
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
if shouldAttempt {
|
|
1589
|
+
unknownDeviceAttempts[peripheral.identifier] = now
|
|
1590
|
+
if logThrottler.shouldLog(key: "unknown_connectable_\(peripheral.identifier.uuidString)", interval: 30) {
|
|
1591
|
+
print("[BleManager] Allowing unknown connectable device for GATT verification: \(peripheral.identifier) RSSI=\(rssi)")
|
|
1592
|
+
emitDiagnostic("debug", "Allowing unknown device for GATT verification", context: [
|
|
1593
|
+
"identifier": peripheral.identifier.uuidString,
|
|
1594
|
+
"rssi": rssi,
|
|
1595
|
+
"recentAttempts": recentUnknownAttempts
|
|
1596
|
+
])
|
|
1597
|
+
}
|
|
1598
|
+
return true
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
// Filter out all other devices (not our mesh network)
|
|
1603
|
+
return false
|
|
1604
|
+
}
|
|
1322
1605
|
|
|
1323
1606
|
private func identifierForNodeHash(_ nodeHash: UInt64) -> UUID? {
|
|
1324
1607
|
for (identifier, observation) in lastSeenMeshAdvertisements where observation.advertisement.nodeIdHash == nodeHash {
|
|
@@ -1326,6 +1609,32 @@ public class BleManager: NSObject, TransportManager {
|
|
|
1326
1609
|
}
|
|
1327
1610
|
return nil
|
|
1328
1611
|
}
|
|
1612
|
+
|
|
1613
|
+
/// Computes a hash of the advertisement data for duplicate detection.
|
|
1614
|
+
/// Uses peripheral ID, RSSI bucket, and key advertisement data.
|
|
1615
|
+
private func computeAdvertisementHash(peripheral: CBPeripheral, advertisementData: [String: Any], rssi: Int16) -> Int {
|
|
1616
|
+
var hasher = Hasher()
|
|
1617
|
+
hasher.combine(peripheral.identifier)
|
|
1618
|
+
// Use RSSI buckets of 5 dBm to avoid hash changes from minor signal fluctuations
|
|
1619
|
+
hasher.combine(rssi / 5)
|
|
1620
|
+
|
|
1621
|
+
// Include service UUIDs if present
|
|
1622
|
+
if let serviceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] {
|
|
1623
|
+
for uuid in serviceUUIDs {
|
|
1624
|
+
hasher.combine(uuid.uuidString)
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
// Include service data if present
|
|
1629
|
+
if let serviceData = advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data] {
|
|
1630
|
+
for (uuid, data) in serviceData {
|
|
1631
|
+
hasher.combine(uuid.uuidString)
|
|
1632
|
+
hasher.combine(data)
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
return hasher.finalize()
|
|
1637
|
+
}
|
|
1329
1638
|
|
|
1330
1639
|
private func maybeHandleRebalance(reason: String) {
|
|
1331
1640
|
pruneMeshObservations()
|
|
@@ -1499,6 +1808,37 @@ extension BleManager: CBCentralManagerDelegate {
|
|
|
1499
1808
|
// Adaptive scanning: track discoveries for density estimation
|
|
1500
1809
|
recordDiscoveryForDensity(now: now)
|
|
1501
1810
|
|
|
1811
|
+
// Duplicate advertisement detection - avoid processing identical advertisements
|
|
1812
|
+
// This improves performance in dense networks where the same device may be seen many times
|
|
1813
|
+
let advertHash = computeAdvertisementHash(peripheral: peripheral, advertisementData: advertisementData, rssi: rssiValue)
|
|
1814
|
+
if let cached = recentAdvertisementHashes[peripheral.identifier] {
|
|
1815
|
+
// If we've seen this exact advertisement recently, skip processing
|
|
1816
|
+
if cached.hash == advertHash && now.timeIntervalSince(cached.timestamp) < 1.0 {
|
|
1817
|
+
return
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
recentAdvertisementHashes[peripheral.identifier] = (hash: advertHash, timestamp: now)
|
|
1821
|
+
|
|
1822
|
+
// Prune old advertisement cache entries periodically
|
|
1823
|
+
if recentAdvertisementHashes.count > 100 {
|
|
1824
|
+
let cutoff = now.addingTimeInterval(-30.0)
|
|
1825
|
+
recentAdvertisementHashes = recentAdvertisementHashes.filter { $0.value.timestamp > cutoff }
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
// Smart filtering for iOS ↔ Android interoperability
|
|
1829
|
+
// Since we scan without a service UUID filter (for Android compatibility),
|
|
1830
|
+
// we need to filter discovered peripherals here instead.
|
|
1831
|
+
let shouldProcess = shouldProcessDiscoveredPeripheral(
|
|
1832
|
+
peripheral: peripheral,
|
|
1833
|
+
advertisementData: advertisementData,
|
|
1834
|
+
rssi: rssiValue,
|
|
1835
|
+
now: now
|
|
1836
|
+
)
|
|
1837
|
+
|
|
1838
|
+
if !shouldProcess {
|
|
1839
|
+
return
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1502
1842
|
// Adaptive scanning: early RSSI filtering in dense networks
|
|
1503
1843
|
if shouldFilterByRssi(rssiValue) {
|
|
1504
1844
|
if logThrottler.shouldLog(key: "adaptive_rssi_filter", interval: 10) {
|
|
@@ -1562,11 +1902,25 @@ extension BleManager: CBCentralManagerDelegate {
|
|
|
1562
1902
|
}
|
|
1563
1903
|
|
|
1564
1904
|
// Adaptive scanning: rate limit connection attempts
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1905
|
+
// Skip throttling for first-time discoveries with strong signals for faster connection
|
|
1906
|
+
let notSeenBefore = lastSeenMeshAdvertisements[peripheral.identifier] == nil
|
|
1907
|
+
let notConnected = connections.connectedPeripheral(for: peripheral.identifier) == nil
|
|
1908
|
+
let isFirstDiscovery = notSeenBefore && notConnected
|
|
1909
|
+
let hasStrongSignal = rssiValue >= -70
|
|
1910
|
+
|
|
1911
|
+
if !isFirstDiscovery || !hasStrongSignal {
|
|
1912
|
+
if shouldThrottleConnection(to: peripheral.identifier, now: now) {
|
|
1913
|
+
if logThrottler.shouldLog(key: "adaptive_throttle_\(peripheral.identifier.uuidString)", interval: 30) {
|
|
1914
|
+
print("[BleManager] Adaptive: throttling connection to \(peripheral.identifier)")
|
|
1915
|
+
}
|
|
1916
|
+
return
|
|
1568
1917
|
}
|
|
1569
|
-
|
|
1918
|
+
} else if isFirstDiscovery && hasStrongSignal {
|
|
1919
|
+
print("[BleManager] Fast-tracking first discovery with strong signal: \(peripheral.identifier) RSSI=\(rssiValue)")
|
|
1920
|
+
emitDiagnostic("info", "Fast-tracking first discovery", context: [
|
|
1921
|
+
"identifier": peripheral.identifier.uuidString,
|
|
1922
|
+
"rssi": rssiValue
|
|
1923
|
+
])
|
|
1570
1924
|
}
|
|
1571
1925
|
|
|
1572
1926
|
let desiredRole: MeshController.MeshRole = (decision.intent == .interCluster) ? .bridge : .member
|
|
@@ -1603,6 +1957,7 @@ extension BleManager: CBCentralManagerDelegate {
|
|
|
1603
1957
|
|
|
1604
1958
|
connections.registerPeripheral(peripheral)
|
|
1605
1959
|
connectionAttemptTimestamps.removeValue(forKey: peripheral.identifier)
|
|
1960
|
+
connectionRetryCount.removeValue(forKey: peripheral.identifier) // Reset retry count on successful connection
|
|
1606
1961
|
|
|
1607
1962
|
// Discover services
|
|
1608
1963
|
peripheral.discoverServices([SERVICE_UUID])
|
|
@@ -1610,14 +1965,36 @@ extension BleManager: CBCentralManagerDelegate {
|
|
|
1610
1965
|
|
|
1611
1966
|
public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
|
|
1612
1967
|
print("[BleManager] Failed to connect to peripheral: \(error?.localizedDescription ?? "unknown")")
|
|
1968
|
+
|
|
1969
|
+
// Increment retry count and calculate backoff
|
|
1970
|
+
let retryCount = (connectionRetryCount[peripheral.identifier] ?? 0) + 1
|
|
1971
|
+
connectionRetryCount[peripheral.identifier] = retryCount
|
|
1972
|
+
|
|
1613
1973
|
emitDiagnostic("error", "Failed to connect to BLE peripheral", context: [
|
|
1614
1974
|
"identifier": peripheral.identifier.uuidString,
|
|
1615
|
-
"error": error?.localizedDescription ?? "unknown"
|
|
1975
|
+
"error": error?.localizedDescription ?? "unknown",
|
|
1976
|
+
"retryCount": retryCount
|
|
1616
1977
|
])
|
|
1617
1978
|
connectionAttemptTimestamps.removeValue(forKey: peripheral.identifier)
|
|
1618
1979
|
_ = connections.consumePendingRole(for: peripheral.identifier)
|
|
1619
|
-
|
|
1620
|
-
|
|
1980
|
+
|
|
1981
|
+
// Give up after max retries
|
|
1982
|
+
guard retryCount <= MAX_CONNECTION_RETRIES else {
|
|
1983
|
+
print("[BleManager] Max retries (\(MAX_CONNECTION_RETRIES)) exceeded for \(peripheral.identifier), giving up")
|
|
1984
|
+
emitDiagnostic("warning", "Max connection retries exceeded", context: [
|
|
1985
|
+
"identifier": peripheral.identifier.uuidString,
|
|
1986
|
+
"retryCount": retryCount
|
|
1987
|
+
])
|
|
1988
|
+
connectionRetryCount.removeValue(forKey: peripheral.identifier)
|
|
1989
|
+
return
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
// Exponential backoff: 5s, 10s, 20s, 40s, 60s (capped)
|
|
1993
|
+
let backoffInterval = min(MAX_RECONNECT_INTERVAL, MIN_RECONNECT_INTERVAL * pow(2.0, Double(retryCount - 1)))
|
|
1994
|
+
|
|
1995
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + backoffInterval) { [weak self] in
|
|
1996
|
+
guard let self = self, self.state == .running else { return }
|
|
1997
|
+
self.attemptConnection(to: peripheral, reason: "retry_fail")
|
|
1621
1998
|
}
|
|
1622
1999
|
}
|
|
1623
2000
|
|
|
@@ -1668,8 +2045,34 @@ extension BleManager: CBCentralManagerDelegate {
|
|
|
1668
2045
|
}
|
|
1669
2046
|
}
|
|
1670
2047
|
|
|
1671
|
-
// Attempt reconnection
|
|
1672
|
-
|
|
2048
|
+
// Attempt reconnection with exponential backoff
|
|
2049
|
+
let retryCount = (connectionRetryCount[peripheral.identifier] ?? 0) + 1
|
|
2050
|
+
connectionRetryCount[peripheral.identifier] = retryCount
|
|
2051
|
+
|
|
2052
|
+
// Give up after max retries
|
|
2053
|
+
guard retryCount <= MAX_CONNECTION_RETRIES else {
|
|
2054
|
+
print("[BleManager] Max retries (\(MAX_CONNECTION_RETRIES)) exceeded for \(peripheral.identifier) on disconnect, giving up")
|
|
2055
|
+
connectionRetryCount.removeValue(forKey: peripheral.identifier)
|
|
2056
|
+
// Notify peer lost since we're giving up
|
|
2057
|
+
if let deviceId = connections.peripheralDeviceId(for: peripheral.identifier) {
|
|
2058
|
+
protocolInstance.removeNeighborRoutes(neighborId: deviceId)
|
|
2059
|
+
try? self.protocolInstance.blePeerLost(peerId: deviceId)
|
|
2060
|
+
meshController.registerDisconnection(peerId: deviceId)
|
|
2061
|
+
refreshSelfMetrics()
|
|
2062
|
+
connections.removeConnectionRole(for: deviceId)
|
|
2063
|
+
connections.removePeripheralDeviceId(for: peripheral.identifier)
|
|
2064
|
+
connections.removeCentralDeviceId(for: peripheral.identifier)
|
|
2065
|
+
DispatchQueue.main.async {
|
|
2066
|
+
self.refreshAdvertising(reason: "disconnect_max_retries")
|
|
2067
|
+
}
|
|
2068
|
+
maybeHandleRebalance(reason: "disconnect_max_retries")
|
|
2069
|
+
}
|
|
2070
|
+
return
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
let backoffInterval = min(MAX_RECONNECT_INTERVAL, MIN_RECONNECT_INTERVAL * pow(2.0, Double(retryCount - 1)))
|
|
2074
|
+
|
|
2075
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + backoffInterval) { [weak self] in
|
|
1673
2076
|
guard let self = self else { return }
|
|
1674
2077
|
if self.state == .running && self.discoveredPeripherals[peripheral.identifier] != nil {
|
|
1675
2078
|
self.attemptConnection(to: peripheral, reason: "retry_disconnect")
|
|
@@ -2011,6 +2414,14 @@ extension BleManager: CBPeripheralManagerDelegate {
|
|
|
2011
2414
|
])
|
|
2012
2415
|
return
|
|
2013
2416
|
}
|
|
2417
|
+
|
|
2418
|
+
// Track this central as subscribed for connection count
|
|
2419
|
+
subscribedCentrals.insert(central.identifier)
|
|
2420
|
+
print("[BleManager] Central subscribed: \(central.identifier)")
|
|
2421
|
+
emitDiagnostic("info", "Central subscribed to characteristic", context: [
|
|
2422
|
+
"central": central.identifier.uuidString,
|
|
2423
|
+
"totalSubscribed": subscribedCentrals.count
|
|
2424
|
+
])
|
|
2014
2425
|
|
|
2015
2426
|
if connections.centralDeviceId(for: central.identifier) == nil && connections.peripheralDeviceId(for: central.identifier) == nil {
|
|
2016
2427
|
ensureDeviceId(for: central.identifier)
|
|
@@ -2024,9 +2435,11 @@ extension BleManager: CBPeripheralManagerDelegate {
|
|
|
2024
2435
|
|
|
2025
2436
|
public func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didUnsubscribeFrom characteristic: CBCharacteristic) {
|
|
2026
2437
|
print("[BleManager] Central unsubscribed from characteristic: \(characteristic.uuid)")
|
|
2438
|
+
subscribedCentrals.remove(central.identifier)
|
|
2027
2439
|
emitDiagnostic("info", "Central unsubscribed", context: [
|
|
2028
2440
|
"central": central.identifier.uuidString,
|
|
2029
|
-
"characteristic": characteristic.uuid.uuidString
|
|
2441
|
+
"characteristic": characteristic.uuid.uuidString,
|
|
2442
|
+
"remainingSubscribed": subscribedCentrals.count
|
|
2030
2443
|
])
|
|
2031
2444
|
connections.removeCentralDeviceId(for: central.identifier)
|
|
2032
2445
|
}
|
package/package.json
CHANGED