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:
NimBLECharacteristicCallbacks::onWrite is void.
NimBLECharacteristic::writeEvent is void.
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 0x80–0x9F 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.
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_fncallback (its return value isint), and a characteristic with theWRITEproperty uses Write Requests (opcode 0x12), which per the Bluetooth spec support error responses. The capability is lost in the C++ wrapper, in three places:NimBLECharacteristicCallbacks::onWriteisvoid.NimBLECharacteristic::writeEventisvoid.NimBLEServer::handleGattEvent(src/NimBLEServer.cpp, theOP_WRITE_DSC/OP_WRITE_CHRblock) ends with a hardcodedreturn 0;right afterpAtt->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
writeEventcannot simply be changed to returnint: it is a pure virtual inNimBLELocalValueAttribute(virtual void writeEvent(...) = 0) and overridden by bothNimBLECharacteristicandNimBLEDescriptor. 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 insideonWrite();0accepts, vendor range0x80–0x9Ffor application-defined errors.int getWriteError() const— read byhandleGattEvent's write branch, which returns it instead of0.writeEvent()resets the slot to0before invokingonWrite, and the OP_WRITE_DSC path keeps returning0(descriptors carry no slot). Fully backwards compatible: applications that never callsetWriteErrorsee 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.