Skip to content

GATT server write path cannot propagate application errors to the client (handleGattEvent always returns 0) #441

Description

@jie-meng

Problem

When an application rejects a write inside NimBLECharacteristicCallbacks::onWrite (e.g. device busy, validation failure), the BLE client still receives a successful Write Response. There is no way to signal an ATT Error Response for a rejected write.

Root cause

The NimBLE C stack supports ATT Error Responses from the ble_gatt_access_fn callback (its return value is int), and a characteristic with the WRITE property uses Write Requests (opcode 0x12), which per the Bluetooth spec support error responses. The capability is lost in the C++ wrapper, in three places:

  1. NimBLECharacteristicCallbacks::onWrite is void.
  2. NimBLECharacteristic::writeEvent is void.
  3. NimBLEServer::handleGattEvent (src/NimBLEServer.cpp, the OP_WRITE_DSC/OP_WRITE_CHR block) ends with a hardcoded return 0; right after pAtt->writeEvent(buf, len, peerInfo);.

So business-layer results (which are computed synchronously inside the ATT transaction context by applications) are silently discarded, and every write is ACKed as success.

Constraint

writeEvent cannot simply be changed to return int: it is a pure virtual in NimBLELocalValueAttribute (virtual void writeEvent(...) = 0) and overridden by both NimBLECharacteristic and NimBLEDescriptor. Changing its signature is a breaking API change.

Proposed direction

Keep all signatures unchanged and add a per-write error slot on NimBLECharacteristic:

  • void setWriteError(uint8_t attError) — called by the application inside onWrite(); 0 accepts, vendor range 0x800x9F for application-defined errors.
  • int getWriteError() const — read by handleGattEvent's write branch, which returns it instead of 0.

writeEvent() resets the slot to 0 before invoking onWrite, and the OP_WRITE_DSC path keeps returning 0 (descriptors carry no slot). Fully backwards compatible: applications that never call setWriteError see identical behavior.

PR

Implemented in #442. Feedback on the design (especially whether you'd prefer a different mechanism, e.g. a new virtual with a default implementation) is welcome.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions