diff --git a/src/NimBLECharacteristic.cpp b/src/NimBLECharacteristic.cpp index 343d4071..e8a09861 100644 --- a/src/NimBLECharacteristic.cpp +++ b/src/NimBLECharacteristic.cpp @@ -422,8 +422,8 @@ void NimBLECharacteristic::updatePeerStatus(const NimBLEConnInfo& peerInfo) cons * @brief Handle a read event from a client. * @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info. */ -void NimBLECharacteristic::readEvent(NimBLEConnInfo& connInfo) { - m_pCallbacks->onRead(this, connInfo); +int NimBLECharacteristic::readEvent(NimBLEConnInfo& connInfo) { + return m_pCallbacks->onReadStatus(this, connInfo); } // readEvent /** @@ -432,9 +432,17 @@ void NimBLECharacteristic::readEvent(NimBLEConnInfo& connInfo) { * @param [in] len The length of the data written by the client. * @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info. */ -void NimBLECharacteristic::writeEvent(const uint8_t* val, uint16_t len, NimBLEConnInfo& connInfo) { +int NimBLECharacteristic::writeEvent(const uint8_t* val, uint16_t len, NimBLEConnInfo& connInfo) { + // Commit before the callback so getValue() reflects the new value inside it + // (onWriteStatus defaults to calling the legacy onWrite), but snapshot first + // so a rejected write can be rolled back and never becomes observable state. + NimBLEAttValue previous = m_value; setValue(val, len); - m_pCallbacks->onWrite(this, connInfo); + const int rc = m_pCallbacks->onWriteStatus(this, connInfo); + if (rc != 0) { + m_value = previous; + } + return rc; } // writeEvent /** diff --git a/src/NimBLECharacteristic.h b/src/NimBLECharacteristic.h index e3c70b63..2f5f72f2 100644 --- a/src/NimBLECharacteristic.h +++ b/src/NimBLECharacteristic.h @@ -271,8 +271,8 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute { friend class NimBLEService; void setService(NimBLEService* pService); - void readEvent(NimBLEConnInfo& connInfo) override; - void writeEvent(const uint8_t* val, uint16_t len, NimBLEConnInfo& connInfo) override; + int readEvent(NimBLEConnInfo& connInfo) override; + int writeEvent(const uint8_t* val, uint16_t len, NimBLEConnInfo& connInfo) override; bool sendValue(const uint8_t* value, size_t length, bool is_notification = true, @@ -319,6 +319,35 @@ class NimBLECharacteristicCallbacks { virtual ~NimBLECharacteristicCallbacks() {} virtual void onRead(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo); virtual void onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo); + + /** + * @brief Read request callback that can reject the read with an ATT error. + * @param [in] pCharacteristic The characteristic that is the source of the event. + * @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info. + * @return 0 to accept the read, or a BLE_ATT_ERR_* code to reject it. + * @details Defaults to calling onRead() and accepting. Override this instead of onRead() + * when you need to reject a read from the application. + */ + virtual int onReadStatus(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) { + onRead(pCharacteristic, connInfo); + return 0; + } + + /** + * @brief Write request callback that can reject the write with an ATT error. + * @param [in] pCharacteristic The characteristic that is the source of the event. + * @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info. + * @return 0 to accept the write, or a BLE_ATT_ERR_* code to reject it. + * @details Defaults to calling onWrite() and accepting. Override this instead of onWrite() + * when you need to reject a write from the application. A rejection only reaches the peer + * when it used write-with-response; a write-without-response (ATT Write Command) is + * unacknowledged, so the code is dropped by the stack. + */ + virtual int onWriteStatus(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) { + onWrite(pCharacteristic, connInfo); + return 0; + } + virtual void onStatus(NimBLECharacteristic* pCharacteristic, int code); // deprecated virtual void onStatus(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, int code); virtual void onSubscribe(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue); diff --git a/src/NimBLEDescriptor.cpp b/src/NimBLEDescriptor.cpp index 8f1bb41e..a7d0dc58 100644 --- a/src/NimBLEDescriptor.cpp +++ b/src/NimBLEDescriptor.cpp @@ -120,13 +120,16 @@ std::string NimBLEDescriptor::toString() const { return res; } // toString -void NimBLEDescriptor::readEvent(NimBLEConnInfo& connInfo) { +int NimBLEDescriptor::readEvent(NimBLEConnInfo& connInfo) { + // Descriptor callbacks have no status-returning variant; always accept. m_pCallbacks->onRead(this, connInfo); + return 0; } // readEvent -void NimBLEDescriptor::writeEvent(const uint8_t* val, uint16_t len, NimBLEConnInfo& connInfo) { +int NimBLEDescriptor::writeEvent(const uint8_t* val, uint16_t len, NimBLEConnInfo& connInfo) { setValue(val, len); m_pCallbacks->onWrite(this, connInfo); + return 0; } // writeEvent /** diff --git a/src/NimBLEDescriptor.h b/src/NimBLEDescriptor.h index b6e9b8f2..cf403aad 100644 --- a/src/NimBLEDescriptor.h +++ b/src/NimBLEDescriptor.h @@ -49,8 +49,8 @@ class NimBLEDescriptor : public NimBLELocalValueAttribute { friend class NimBLEService; void setCharacteristic(NimBLECharacteristic* pChar); - void readEvent(NimBLEConnInfo& connInfo) override; - void writeEvent(const uint8_t* val, uint16_t len, NimBLEConnInfo& connInfo) override; + int readEvent(NimBLEConnInfo& connInfo) override; + int writeEvent(const uint8_t* val, uint16_t len, NimBLEConnInfo& connInfo) override; NimBLEDescriptorCallbacks* m_pCallbacks{nullptr}; NimBLECharacteristic* m_pCharacteristic{nullptr}; diff --git a/src/NimBLELocalValueAttribute.h b/src/NimBLELocalValueAttribute.h index 5fd37e55..131a3ece 100644 --- a/src/NimBLELocalValueAttribute.h +++ b/src/NimBLELocalValueAttribute.h @@ -112,8 +112,9 @@ class NimBLELocalValueAttribute : public NimBLELocalAttribute, public NimBLEValu * @brief Callback function to support a read request. * @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info. * @details This function is called by NimBLEServer when a read request is received. + * @return 0 on success, or a BLE_ATT_ERR_* code to reject the read. */ - virtual void readEvent(NimBLEConnInfo& connInfo) = 0; + virtual int readEvent(NimBLEConnInfo& connInfo) = 0; /** * @brief Callback function to support a write request. @@ -121,8 +122,9 @@ class NimBLELocalValueAttribute : public NimBLELocalAttribute, public NimBLEValu * @param [in] len The length of the value. * @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info. * @details This function is called by NimBLEServer when a write request is received. + * @return 0 on success, or a BLE_ATT_ERR_* code to reject the write. */ - virtual void writeEvent(const uint8_t* val, uint16_t len, NimBLEConnInfo& connInfo) = 0; + virtual int writeEvent(const uint8_t* val, uint16_t len, NimBLEConnInfo& connInfo) = 0; /** * @brief Get a pointer to value of the attribute. diff --git a/src/NimBLEServer.cpp b/src/NimBLEServer.cpp index 1cab58ed..29131664 100644 --- a/src/NimBLEServer.cpp +++ b/src/NimBLEServer.cpp @@ -742,7 +742,10 @@ int NimBLEServer::handleGattEvent(uint16_t connHandle, uint16_t attrHandle, ble_ // Don't call readEvent if the buffer len is 0 (this is a follow up to a previous read), // or if this is an internal read (handle is NONE) if (ctxt->om->om_len > 0 && connHandle != BLE_HS_CONN_HANDLE_NONE) { - pAtt->readEvent(peerInfo); + int appRc = pAtt->readEvent(peerInfo); + if (appRc != 0) { + return appRc; // application rejected the read + } } ble_npl_hw_enter_critical(); @@ -773,8 +776,7 @@ int NimBLEServer::handleGattEvent(uint16_t connHandle, uint16_t attrHandle, ble_ next = SLIST_NEXT(next, om_next); } - pAtt->writeEvent(buf, len, peerInfo); - return 0; + return pAtt->writeEvent(buf, len, peerInfo); } default: