mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-09-19 04:59:59 +00:00
Writing the CCCD is the one GATT operation a peer can repeat freely on an open connection: no authentication, no Noise session, no announcement, and the Bluetooth spec puts no limit on how often a client may rewrite the descriptor. onDescriptorWriteRequest attached two non-idempotent effects to it. Each ENABLE_NOTIFICATION write appended to subscribedDevices with no contains() check, so N writes put N entries in the list one address owns. broadcastSinglePacketInternal iterates that list and notifies per entry, so every broadcast the mesh produces is then transmitted to that address N times. Each write also scheduled onDeviceConnected, which calls sendBroadcastAnnounce(). That is unthrottled and floods at maxTtl, so a peer could drive a mesh-wide ANNOUNCE at whatever rate it wrote the descriptor. Subscribe by address and return whether the device was newly subscribed, run the peer-connect side effects only on that transition, and honour DISABLE_NOTIFICATION_VALUE instead of keeping a peer on the broadcast feed until it disconnects. Removal now matches on address like the connection cleanup paths, so an unsubscribe cannot miss because the framework handed us a different BluetoothDevice instance for the same address. Refs #901
5.9 KiB
5.9 KiB
Device Monitoring Manager — Design and Integration
This change introduces a lean DeviceMonitoringManager to strictly manage BLE device connections while keeping the existing code structure intact.
Goals
- Maintain a blocklist of device MAC addresses to deny incoming/outgoing connections.
- Drop and block connections that never ANNOUNCE within 15 seconds of establishment.
- Drop and block connections that go silent (no packets) for over 60 seconds.
- Block devices that experience 5 error disconnects within a 5-minute window.
- Auto-unblock devices after 15 minutes.
Implementation Overview
File: app/src/main/java/com/bitchat/android/mesh/DeviceMonitoringManager.kt
- Thread-safe maps with coroutine-based timers.
- Minimal surface area: a few clearly named entry points to hook into existing flows.
- Callbacks to perform disconnects without coupling to GATT APIs.
Key logic:
isBlocked(address): check if a MAC is blocked (auto-clears on expiry).block(address, reason): add MAC to blocklist (15m), disconnect via callback, auto-unblock later.onConnectionEstablished(address): start 15s “first ANNOUNCE” timer and a 60s inactivity timer.onAnnounceReceived(address): cancel the 15s ANNOUNCE timer for that device.onAnyPacketReceived(address): refresh 60s inactivity timer.onDeviceDisconnected(address, status): track error disconnects and block on 5 within 5 minutes.
Timers:
- ANNOUNCE timer: 15 seconds from connection establishment.
- Inactivity timer: resets on any packet; fires after 60 seconds of silence.
- Blocklist TTL: 15 minutes per device (auto-unblock job per entry).
Wiring Points (Minimal Changes)
- Connection Manager
- File:
BluetoothConnectionManager.kt - Added a
DeviceMonitoringManagerinstance and provided adisconnectCallbackthat:- disconnects client GATT connections via
BluetoothConnectionTracker. - cancels server connections via
BluetoothGattServer.cancelConnection.
- disconnects client GATT connections via
- Updated
componentDelegate.onPacketReceivedto notify per-device activity to the monitor.
- GATT Client
- File:
BluetoothGattClientManager.kt - Constructor now receives
deviceMonitor. - Before attempting any outgoing connection (from scan or direct connect), deny if blocked.
- On connection setup complete (after CCCD enable), call
deviceMonitor.onConnectionEstablished(addr). - On incoming packet (
onCharacteristicChanged), calldeviceMonitor.onAnyPacketReceived(addr). - On disconnect, call
deviceMonitor.onDeviceDisconnected(addr, status)to track error bursts.
- GATT Server
- File:
BluetoothGattServerManager.kt - Constructor now receives
deviceMonitor. - On incoming connection, immediately deny (cancelConnection) if blocked, before tracking it.
- On connection setup complete (descriptor enable) and also after initial connect, start monitoring via
onConnectionEstablished(addr). - On packet write, call
deviceMonitor.onAnyPacketReceived(addr). - On disconnect, call
deviceMonitor.onDeviceDisconnected(addr, status). - Broadcast-notification subscription is keyed by device address and is
idempotent. A CCCD write needs no authentication and the Bluetooth spec puts
no limit on how often a client may repeat it, so the server must treat a
repeat enable as a no-op: exactly one entry per address in the notification
fan-out list, and peer-connect side effects (including the announce broadcast
onDeviceConnectedtriggers) run only on the transition into the subscribed state. A CCCD write ofDISABLE_NOTIFICATION_VALUEunsubscribes the address; it is not deferred to disconnect.
- ANNOUNCE Binding
- File:
BluetoothMeshService.kt(in the ANNOUNCE handler where we first map device → peer)
Behavior Summary
-
Blocked devices:
- Outgoing: client will not initiate connections.
- Incoming: server cancels the connection immediately.
- Existing connection: monitor disconnects instantly and blocks for 15 minutes.
-
No ANNOUNCE within 15s of connection:
- Connection is dropped and device is blocked for 15 minutes.
-
No packets for >60s:
- Connection is dropped and device is blocked for 15 minutes.
-
=5 error disconnects within 5 minutes:
- Device is blocked for 15 minutes.
-
Auto-unblock:
- Every block entry automatically expires after 15 minutes.
Debug Logging
- The manager emits chat-visible debug messages through
DebugSettingsManager(SystemMessage), e.g.:- Blocking decisions and reasons
- Auto-unblock events
- ANNOUNCE wait start/cancel
- Inactivity timer set and inactivity-triggered blocks
- Burst error disconnect threshold reached
- Additional enforcement logs are added in GATT client/server when a blocked device is denied.
- Logs appear in the chat when verbose logging is enabled in Debug settings.
Panic Triple-Tap
- Triple-tapping the title now also clears the device blocklist and all device tracking:
- Calls
BluetoothMeshService.clearAllInternalData()which triggersBluetoothConnectionManager.clearDeviceMonitoringAndTracking(). - This disconnects active connections, clears the monitor’s blocklist and timers, and resets the
BluetoothConnectionTrackerstate.
- Calls
Notes and Rationale
- The monitoring manager is intentionally decoupled from GATT specifics via a disconnect callback. This keeps responsibilities separate and avoids plumbing GATT instances through unrelated classes.
- Packet activity is captured in both client and server data paths as early as possible to ensure the inactivity timer is accurate even before higher-level processing.
- The “first ANNOUNCE” check uses the same mapping event that sets
addressPeerMapto avoid false positives on unverified announces.
Touched Files
- Added:
mesh/DeviceMonitoringManager.kt - Updated:
mesh/BluetoothConnectionManager.kt - Updated:
mesh/BluetoothGattClientManager.kt - Updated:
mesh/BluetoothGattServerManager.kt - Updated:
mesh/BluetoothMeshService.kt
These changes are small, local, and respect existing structure without broad refactors.