mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 05:45:19 +00:00
feat: implement GATT client identity signaling and connection deduplication
Implements a mechanism for GATT clients to signal their Peer ID to the server immediately after connection, allowing for robust deduplication of redundant connections. Includes: - New IDENTITY_CHARACTERISTIC_UUID in AppConstants - Server-side handling of identity writes and deduplication logic - Client-side active signaling of Peer ID upon service discovery - Updated BluetoothConnectionTracker to map devices to Peer IDs - Comprehensive specification document in docs/PEERID_GATT_SERVER_CLIENT_SPEC.md
This commit is contained in:
@@ -76,7 +76,7 @@ class BluetoothConnectionManager(
|
||||
context, connectionScope, connectionTracker, permissionManager, powerManager, componentDelegate, myPeerID
|
||||
)
|
||||
private val clientManager = BluetoothGattClientManager(
|
||||
context, connectionScope, connectionTracker, permissionManager, powerManager, componentDelegate
|
||||
context, connectionScope, connectionTracker, permissionManager, powerManager, componentDelegate, myPeerID
|
||||
)
|
||||
|
||||
// Service state
|
||||
|
||||
@@ -167,6 +167,19 @@ class BluetoothConnectionTracker(
|
||||
return connectedDevices.containsKey(deviceAddress)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update device peer ID binding
|
||||
*/
|
||||
fun setDevicePeerID(deviceAddress: String, peerID: String) {
|
||||
connectedDevices[deviceAddress]?.let { deviceConn ->
|
||||
val updatedConn = deviceConn.copy(peerID = peerID)
|
||||
connectedDevices[deviceAddress] = updatedConn
|
||||
// Also update the address map
|
||||
addressPeerMap[deviceAddress] = peerID
|
||||
Log.d(TAG, "Bound device $deviceAddress to peerID $peerID")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a peer is already connected (by PeerID)
|
||||
*/
|
||||
|
||||
@@ -27,7 +27,8 @@ class BluetoothGattClientManager(
|
||||
private val connectionTracker: BluetoothConnectionTracker,
|
||||
private val permissionManager: BluetoothPermissionManager,
|
||||
private val powerManager: PowerManager,
|
||||
private val delegate: BluetoothConnectionManagerDelegate?
|
||||
private val delegate: BluetoothConnectionManagerDelegate?,
|
||||
private val myPeerID: String
|
||||
) {
|
||||
|
||||
companion object {
|
||||
@@ -480,6 +481,20 @@ class BluetoothGattClientManager(
|
||||
connectionTracker.updateDeviceConnection(deviceAddress, updatedConn)
|
||||
Log.d(TAG, "Client: Updated device connection with characteristic for $deviceAddress")
|
||||
}
|
||||
|
||||
// OPTIONAL: Signal our identity to the server (if they support it)
|
||||
val identityChar = service.getCharacteristic(AppConstants.Mesh.Gatt.IDENTITY_CHARACTERISTIC_UUID)
|
||||
if (identityChar != null) {
|
||||
try {
|
||||
val idBytes = myPeerID.chunked(2).map { it.toInt(16).toByte() }.toByteArray().take(8).toByteArray()
|
||||
identityChar.value = idBytes
|
||||
identityChar.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
|
||||
gatt.writeCharacteristic(identityChar)
|
||||
Log.d(TAG, "Client: Signaled identity to server $deviceAddress")
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Client: Failed to write identity: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
gatt.setCharacteristicNotification(characteristic, true)
|
||||
val descriptor = characteristic.getDescriptor(AppConstants.Mesh.Gatt.DESCRIPTOR_UUID)
|
||||
|
||||
@@ -231,6 +231,42 @@ class BluetoothGattServerManager(
|
||||
Log.w(TAG, "Server: Packet data: ${value.joinToString(" ") { "%02x".format(it) }}")
|
||||
}
|
||||
|
||||
if (responseNeeded) {
|
||||
gattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null)
|
||||
}
|
||||
} else if (characteristic.uuid == AppConstants.Mesh.Gatt.IDENTITY_CHARACTERISTIC_UUID) {
|
||||
val peerID = value.joinToString("") { "%02x".format(it) }
|
||||
Log.i(TAG, "Server: Received Identity signal from ${device.address}: $peerID")
|
||||
|
||||
if (value.size >= 8) {
|
||||
// 1. Update tracker with the declared identity
|
||||
connectionTracker.setDevicePeerID(device.address, peerID)
|
||||
|
||||
// 2. Check for duplicates (same peerID, different MAC)
|
||||
// Note: If we just set it above, we look for *others*
|
||||
val duplicate = connectionTracker.getConnectedDevices().values.firstOrNull {
|
||||
it.peerID == peerID && it.device.address != device.address
|
||||
}
|
||||
|
||||
if (duplicate != null) {
|
||||
Log.w(TAG, "Server: Deduplication - Peer $peerID is already connected via ${duplicate.device.address}. Rejecting new connection ${device.address}.")
|
||||
|
||||
// Send success response first to be polite before killing the connection?
|
||||
// Or fail it?
|
||||
if (responseNeeded) {
|
||||
gattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null)
|
||||
}
|
||||
|
||||
// Disconnect the NEW connection (this one)
|
||||
disconnectDevice(device)
|
||||
return
|
||||
} else {
|
||||
Log.d(TAG, "Server: Identity accepted for $peerID at ${device.address}")
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "Server: Invalid Identity length from ${device.address}: ${value.size}")
|
||||
}
|
||||
|
||||
if (responseNeeded) {
|
||||
gattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null)
|
||||
}
|
||||
@@ -310,6 +346,15 @@ class BluetoothGattServerManager(
|
||||
|
||||
val service = BluetoothGattService(AppConstants.Mesh.Gatt.SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY)
|
||||
service.addCharacteristic(characteristic)
|
||||
|
||||
// Create identity characteristic for direct client ID signaling
|
||||
val identityCharacteristic = BluetoothGattCharacteristic(
|
||||
AppConstants.Mesh.Gatt.IDENTITY_CHARACTERISTIC_UUID,
|
||||
BluetoothGattCharacteristic.PROPERTY_WRITE or
|
||||
BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE,
|
||||
BluetoothGattCharacteristic.PERMISSION_WRITE
|
||||
)
|
||||
service.addCharacteristic(identityCharacteristic)
|
||||
|
||||
gattServer?.addService(service)
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ object AppConstants {
|
||||
object Gatt {
|
||||
val SERVICE_UUID: UUID = UUID.fromString("F47B5E2D-4A9E-4C5A-9B3F-8E1D2C3A4B5C")
|
||||
val CHARACTERISTIC_UUID: UUID = UUID.fromString("A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D")
|
||||
val IDENTITY_CHARACTERISTIC_UUID: UUID = UUID.fromString("A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5E")
|
||||
val DESCRIPTOR_UUID: UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user