diff --git a/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionManager.kt b/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionManager.kt index 0e4f29af..69f15294 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionManager.kt @@ -101,6 +101,10 @@ class BluetoothConnectionManager( // Public property for address-peer mapping val addressPeerMap get() = connectionTracker.addressPeerMap + fun noteVerifiedAnnounceReceived(deviceAddress: String) { + connectionTracker.noteAnnounceReceived(deviceAddress) + } + fun observePeerIfCurrent(deviceAddress: String, linkID: String, peerID: String): Boolean = connectionTracker.observePeerIfCurrent(deviceAddress, linkID, peerID) diff --git a/app/src/main/java/com/bitchat/android/mesh/BluetoothGattServerManager.kt b/app/src/main/java/com/bitchat/android/mesh/BluetoothGattServerManager.kt index 038cd960..ab021b43 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothGattServerManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothGattServerManager.kt @@ -283,6 +283,26 @@ class BluetoothGattServerManager( } if (BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE.contentEquals(value)) { + if (!GattNotificationEligibility.maySubscribe( + connectionTracker.hasSeenFirstAnnounce(device.address) + ) + ) { + Log.w( + TAG, + "Rejecting broadcast subscription from ${device.address} before verified ANNOUNCE" + ) + if (responseNeeded) { + gattServer?.sendResponse( + device, + requestId, + BluetoothGatt.GATT_INSUFFICIENT_AUTHORIZATION, + 0, + null + ) + } + return + } + connectionTracker.addSubscribedDevice(device) connectionScope.launch { diff --git a/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt b/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt index 22f17867..2b9ab5ab 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt @@ -579,6 +579,10 @@ class BluetoothMeshService(private val context: Context) : TransportBridgeServic val result = messageHandler.handleAnnounceWithResult(routed) if (result !is AnnounceHandlingResult.Accepted) return false + routed.relayAddress?.let { deviceAddress -> + connectionManager.noteVerifiedAnnounceReceived(deviceAddress) + } + DirectLinkAnnouncementPolicy.observationFor(routed, MAX_TTL)?.let { observation -> if (connectionManager.observePeerIfCurrent( observation.relayAddress, diff --git a/app/src/main/java/com/bitchat/android/mesh/GattNotificationEligibility.kt b/app/src/main/java/com/bitchat/android/mesh/GattNotificationEligibility.kt new file mode 100644 index 00000000..768a0e4f --- /dev/null +++ b/app/src/main/java/com/bitchat/android/mesh/GattNotificationEligibility.kt @@ -0,0 +1,11 @@ +package com.bitchat.android.mesh + +/** + * Whether a BLE central may subscribe to the mesh broadcast notification feed. + * + * Granting notifications before the peer has sent a verified ANNOUNCE would let + * a passive listener harvest the full mesh feed with no participation (#901). + */ +internal object GattNotificationEligibility { + fun maySubscribe(hasVerifiedAnnounce: Boolean): Boolean = hasVerifiedAnnounce +} diff --git a/app/src/test/kotlin/com/bitchat/android/mesh/GattNotificationEligibilityTest.kt b/app/src/test/kotlin/com/bitchat/android/mesh/GattNotificationEligibilityTest.kt new file mode 100644 index 00000000..d295c3fd --- /dev/null +++ b/app/src/test/kotlin/com/bitchat/android/mesh/GattNotificationEligibilityTest.kt @@ -0,0 +1,17 @@ +package com.bitchat.android.mesh + +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class GattNotificationEligibilityTest { + @Test + fun `subscription denied before a verified announce`() { + assertFalse(GattNotificationEligibility.maySubscribe(hasVerifiedAnnounce = false)) + } + + @Test + fun `subscription allowed after a verified announce`() { + assertTrue(GattNotificationEligibility.maySubscribe(hasVerifiedAnnounce = true)) + } +}