fix: Prevent NullPointerException in BleGattServer.sendResponse#700
fix: Prevent NullPointerException in BleGattServer.sendResponse#700sentry[bot] wants to merge 1 commit intomainfrom
Conversation
Greptile SummaryThis PR fixes a Key changes:
Confidence Score: 3/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant Central as BLE Central (Remote Device)
participant OS as Android BLE Stack
participant Server as BleGattServer
participant GATT as BluetoothGattServer
Central->>OS: ATT Write Request (offset may be null on buggy firmware)
OS->>Server: onCharacteristicWriteRequest(device, requestId, ..., offset, value)
Note over Server: offset may be null-boxed Integer on older Android
Server->>Server: handleCharacteristicWriteRequest(...)
alt responseNeeded == true
Note over Server,GATT: Before fix: passes offset (NPE if null)<br/>After fix: hardcodes 0
Server->>GATT: sendResponse(device, requestId, GATT_SUCCESS, 0, value)
GATT->>Central: ATT Write Response
end
alt Exception thrown
Note over Server: catch(SecurityException) → log only<br/>catch(Exception) → log only<br/>⚠️ CancellationException also caught here
Note over Server: No GATT_FAILURE sent → Central may time out
end
Prompt To Fix All With AIThis is a comment left during a code review.
Path: reticulum/src/main/java/com/lxmf/messenger/reticulum/ble/server/BleGattServer.kt
Line: 839-841
Comment:
**`CancellationException` is swallowed by the generic `catch (e: Exception)` block**
`CancellationException` extends `IllegalStateException → RuntimeException → Exception`, so this new catch will intercept coroutine cancellation signals. Because `handleCharacteristicWriteRequest` runs inside `withContext(Dispatchers.Main)`, cancelling the parent coroutine (e.g. during lifecycle teardown) will throw a `CancellationException` that gets silently swallowed here, logged as "Unexpected error", and the coroutine will appear to have completed normally. This breaks Kotlin structured concurrency and can prevent the BLE server from shutting down cleanly.
The fix is to re-throw `CancellationException` before logging:
```suggestion
} catch (e: Exception) {
if (e is kotlinx.coroutines.CancellationException) throw e
Log.e(TAG, "Unexpected error in handleCharacteristicWriteRequest for ${device.address}", e)
}
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: reticulum/src/main/java/com/lxmf/messenger/reticulum/ble/server/BleGattServer.kt
Line: 837-841
Comment:
**No `sendResponse` in error paths may leave the central hanging**
Both catch blocks (the pre-existing `SecurityException` one and the new `Exception` one) log the error but never call `gattServer?.sendResponse(device, requestId, GATT_FAILURE, 0, null)`. When `responseNeeded` is `true` and an exception is thrown before the response is sent, the requesting central device will time out waiting for an ATT response. On some BLE stacks this leads to connection-level errors or a full disconnect rather than a clean protocol failure.
Consider sending a `GATT_FAILURE` response in the catch blocks when `responseNeeded` is true, guarded by a nested try/catch to handle any permission error from the call itself.
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: "fix: Prevent NullPoi..." |
| } catch (e: Exception) { | ||
| Log.e(TAG, "Unexpected error in handleCharacteristicWriteRequest for ${device.address}", e) | ||
| } |
There was a problem hiding this comment.
CancellationException is swallowed by the generic catch (e: Exception) block
CancellationException extends IllegalStateException → RuntimeException → Exception, so this new catch will intercept coroutine cancellation signals. Because handleCharacteristicWriteRequest runs inside withContext(Dispatchers.Main), cancelling the parent coroutine (e.g. during lifecycle teardown) will throw a CancellationException that gets silently swallowed here, logged as "Unexpected error", and the coroutine will appear to have completed normally. This breaks Kotlin structured concurrency and can prevent the BLE server from shutting down cleanly.
The fix is to re-throw CancellationException before logging:
| } catch (e: Exception) { | |
| Log.e(TAG, "Unexpected error in handleCharacteristicWriteRequest for ${device.address}", e) | |
| } | |
| } catch (e: Exception) { | |
| if (e is kotlinx.coroutines.CancellationException) throw e | |
| Log.e(TAG, "Unexpected error in handleCharacteristicWriteRequest for ${device.address}", e) | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: reticulum/src/main/java/com/lxmf/messenger/reticulum/ble/server/BleGattServer.kt
Line: 839-841
Comment:
**`CancellationException` is swallowed by the generic `catch (e: Exception)` block**
`CancellationException` extends `IllegalStateException → RuntimeException → Exception`, so this new catch will intercept coroutine cancellation signals. Because `handleCharacteristicWriteRequest` runs inside `withContext(Dispatchers.Main)`, cancelling the parent coroutine (e.g. during lifecycle teardown) will throw a `CancellationException` that gets silently swallowed here, logged as "Unexpected error", and the coroutine will appear to have completed normally. This breaks Kotlin structured concurrency and can prevent the BLE server from shutting down cleanly.
The fix is to re-throw `CancellationException` before logging:
```suggestion
} catch (e: Exception) {
if (e is kotlinx.coroutines.CancellationException) throw e
Log.e(TAG, "Unexpected error in handleCharacteristicWriteRequest for ${device.address}", e)
}
```
How can I resolve this? If you propose a fix, please make it concise.| } catch (e: SecurityException) { | ||
| Log.e(TAG, "Permission denied in handleCharacteristicWriteRequest", e) | ||
| } catch (e: Exception) { | ||
| Log.e(TAG, "Unexpected error in handleCharacteristicWriteRequest for ${device.address}", e) | ||
| } |
There was a problem hiding this comment.
No
sendResponse in error paths may leave the central hanging
Both catch blocks (the pre-existing SecurityException one and the new Exception one) log the error but never call gattServer?.sendResponse(device, requestId, GATT_FAILURE, 0, null). When responseNeeded is true and an exception is thrown before the response is sent, the requesting central device will time out waiting for an ATT response. On some BLE stacks this leads to connection-level errors or a full disconnect rather than a clean protocol failure.
Consider sending a GATT_FAILURE response in the catch blocks when responseNeeded is true, guarded by a nested try/catch to handle any permission error from the call itself.
Prompt To Fix With AI
This is a comment left during a code review.
Path: reticulum/src/main/java/com/lxmf/messenger/reticulum/ble/server/BleGattServer.kt
Line: 837-841
Comment:
**No `sendResponse` in error paths may leave the central hanging**
Both catch blocks (the pre-existing `SecurityException` one and the new `Exception` one) log the error but never call `gattServer?.sendResponse(device, requestId, GATT_FAILURE, 0, null)`. When `responseNeeded` is `true` and an exception is thrown before the response is sent, the requesting central device will time out waiting for an ATT response. On some BLE stacks this leads to connection-level errors or a full disconnect rather than a clean protocol failure.
Consider sending a `GATT_FAILURE` response in the catch blocks when `responseNeeded` is true, guarded by a nested try/catch to handle any permission error from the call itself.
How can I resolve this? If you propose a fix, please make it concise.
Fixes COLUMBA-6Q. The issue was that: Android BLE stack passes null offset to GATT callback, causing NullPointerException during
sendResponseunboxing.offsetto0inBluetoothGattServer.sendResponseto preventNullPointerExceptionon older Android devices where the BLE stack might provide a null-boxed Integer for offset.catch (Exception)block tohandleCharacteristicWriteRequestfor improved error logging of unexpected issues.This fix was generated by Seer in Sentry, triggered automatically. 👁️ Run ID: 12135876
Not quite right? Click here to continue debugging with Seer.