Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.os.Build
import androidx.core.app.ActivityCompat
Expand All @@ -42,7 +41,6 @@ import org.radarcns.passive.phone.PhoneBluetoothDeviceScanned
import org.radarcns.passive.phone.PhoneBluetoothDevices
import org.slf4j.LoggerFactory
import java.nio.ByteBuffer
import java.util.concurrent.ThreadLocalRandom
import java.util.concurrent.TimeUnit

class PhoneBluetoothManager(service: PhoneBluetoothService) : AbstractSourceManager<PhoneBluetoothService, BaseSourceState>(service) {
Expand All @@ -52,10 +50,6 @@ class PhoneBluetoothManager(service: PhoneBluetoothService) : AbstractSourceMana

private var bluetoothBroadcastReceiver: BroadcastReceiver? = null
private val hashGenerator: HashGenerator = HashGenerator(service, "bluetooth_devices")
private val preferences: SharedPreferences
get() = service.getSharedPreferences(PhoneBluetoothManager::class.java.name, Context.MODE_PRIVATE)

private var hashSaltReference: Int = 0

init {
name = service.getString(R.string.bluetooth_devices)
Expand All @@ -65,17 +59,6 @@ class PhoneBluetoothManager(service: PhoneBluetoothService) : AbstractSourceMana
requestName = ACTION_SCAN_DEVICES
wake = true
}
preferences.apply {
if (contains(HASH_SALT_REFERENCE)) {
hashSaltReference = getInt(HASH_SALT_REFERENCE, -1)
} else {
val random = ThreadLocalRandom.current()
while (hashSaltReference == 0) {
hashSaltReference = random.nextInt()
}
edit().putInt(HASH_SALT_REFERENCE, hashSaltReference).apply()
}
}
}

override fun start(acceptableIds: Set<String>) {
Expand Down Expand Up @@ -122,32 +105,32 @@ class PhoneBluetoothManager(service: PhoneBluetoothService) : AbstractSourceMana
} ?: return

val macAddress = device.address
val macAddressHash: ByteBuffer = hashGenerator.createHashByteBuffer(macAddress + "$hashSaltReference")

val scannedTopicBuilder = PhoneBluetoothDeviceScanned.newBuilder().apply {
time = currentTime
timeReceived = currentTime
}
val macAddressHash: ByteBuffer = hashGenerator.createHashByteBuffer(macAddress)
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the hash salt makes MAC address hashes deterministic across all devices and time periods. This significantly weakens privacy protection because the hashes can potentially be reversed using rainbow tables or dictionary attacks on known MAC address patterns. The same MAC address will always produce the same hash, making it easier to track devices across different users or time periods. If privacy is a concern, consider keeping the salt or using a more secure hashing approach with proper key derivation.

Copilot uses AI. Check for mistakes.

val pairedDevices: Set<BluetoothDevice> = if (hasConnectPermission) bluetoothAdapter.bondedDevices else emptySet()

pairedDevices.forEach { bd ->
val mac = bd.address
val hash = hashGenerator.createHashByteBuffer(mac + "$hashSaltReference")
val hash = hashGenerator.createHashByteBuffer(mac)
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the hash salt makes MAC address hashes deterministic across all devices and time periods. This significantly weakens privacy protection because the hashes can potentially be reversed using rainbow tables or dictionary attacks on known MAC address patterns. The same MAC address will always produce the same hash, making it easier to track devices across different users or time periods. If privacy is a concern, consider keeping the salt or using a more secure hashing approach with proper key derivation.

Copilot uses AI. Check for mistakes.

val scannedTopicBuilder = PhoneBluetoothDeviceScanned.newBuilder().apply {
time = currentTime
timeReceived = currentTime
}

send(bluetoothScannedTopic, scannedTopicBuilder.apply {
this.macAddressHash = hash
this.pairedState = bd.bondState.toPairedState()
this.hashSaltReference = hashSaltReference
}.build())
Comment on lines 121 to 124
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removal of the hashSaltReference field from PhoneBluetoothDeviceScanned messages represents a breaking API change. Consumers of this data who expect or rely on this field will experience issues when this change is deployed. This change should be coordinated with downstream systems, and the schema should be updated to mark this field as optional or deprecated before removing it from the code, following proper schema evolution practices.

Copilot uses AI. Check for mistakes.
}

send(bluetoothScannedTopic, scannedTopicBuilder.apply {

send(bluetoothScannedTopic, PhoneBluetoothDeviceScanned.newBuilder().apply {
this.time = currentTime
this.timeReceived = currentTime
this.macAddressHash = macAddressHash
this.pairedState = device.bondState.toPairedState()
this.hashSaltReference = hashSaltReference
}.build())
Comment on lines +128 to 133
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removal of the hashSaltReference field from PhoneBluetoothDeviceScanned messages represents a breaking API change. Consumers of this data who expect or rely on this field will experience issues when this change is deployed. This change should be coordinated with downstream systems, and the schema should be updated to mark this field as optional or deprecated before removing it from the code, following proper schema evolution practices.

Copilot uses AI. Check for mistakes.

}

BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> {
Expand Down Expand Up @@ -196,12 +179,11 @@ class PhoneBluetoothManager(service: PhoneBluetoothService) : AbstractSourceMana

private const val SCAN_DEVICES_REQUEST_CODE = 3248902
private const val ACTION_SCAN_DEVICES = "org.radarbase.passive.phone.PhoneBluetoothManager.ACTION_SCAN_DEVICES"
private const val HASH_SALT_REFERENCE = "hash_salt_reference"

private fun Int.toPairedState(): PairedState = when(this) {
10 -> PairedState.NOT_PAIRED
11 -> PairedState.PAIRING
12 -> PairedState.PAIRED
BluetoothDevice.BOND_NONE -> PairedState.NOT_PAIRED
BluetoothDevice.BOND_BONDING -> PairedState.PAIRING
BluetoothDevice.BOND_BONDED -> PairedState.PAIRED
else -> PairedState.UNKNOWN
}
}
Expand Down
Loading