From 1ff1c6811fae15976b6e6dad7de3eb23eac44938 Mon Sep 17 00:00:00 2001 From: srgg Date: Fri, 17 Jul 2026 15:58:25 -0600 Subject: [PATCH 1/2] feat: Aggregate Format (0x2905) descriptor, reapplied onto 2.5.0 Ports the 0x2905 Characteristic Aggregate Format feature (from feat/aggregate-format-2905 @ dc01636, 2.3.6-based) onto NimBLE-Arduino 2.5.0. Only the feature is carried: NimBLE2905 descriptor class, NimBLECharacteristic::create2905(), the 0x2905 branch in createDescriptor(), the NIMBLE_MAX_AGGREGATE_FORMAT_DESCRIPTORS knob, and the post-start initValue() pass in NimBLEServer::start(). The fork's descriptor-handle-assignment fix (Phase-1 + the ble_gatts_find_dsc dsc_arg change) is intentionally dropped: 2.5.0 already contains upstream #1107 ("Properly set attribute handles"), which owns duplicate-safe handle assignment via the GATT register callback. So the host-C is left untouched. --- src/NimBLE2905.cpp | 82 ++++++++++++++++++++++++++++++++++++ src/NimBLE2905.h | 56 ++++++++++++++++++++++++ src/NimBLECharacteristic.cpp | 14 ++++++ src/NimBLECharacteristic.h | 2 + src/NimBLEServer.cpp | 14 ++++++ src/nimconfig.h | 3 ++ 6 files changed, 171 insertions(+) create mode 100644 src/NimBLE2905.cpp create mode 100644 src/NimBLE2905.h diff --git a/src/NimBLE2905.cpp b/src/NimBLE2905.cpp new file mode 100644 index 000000000..d88c02106 --- /dev/null +++ b/src/NimBLE2905.cpp @@ -0,0 +1,82 @@ +/* + * Copyright 2020-2025 Ryan Powell and + * esp-nimble-cpp, NimBLE-Arduino contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "NimBLE2905.h" +#include "NimBLELog.h" + +#include "NimBLECharacteristic.h" +#if CONFIG_BT_NIMBLE_ENABLED && MYNEWT_VAL(BLE_ROLE_PERIPHERAL) + +// Define default if not already defined +#ifndef NIMBLE_MAX_AGGREGATE_FORMAT_DESCRIPTORS + #define NIMBLE_MAX_AGGREGATE_FORMAT_DESCRIPTORS 5 // Default value +#else + // Ensure the value is within a valid range + #if NIMBLE_MAX_AGGREGATE_FORMAT_DESCRIPTORS < 1 || NIMBLE_MAX_AGGREGATE_FORMAT_DESCRIPTORS > 255 + #error "NIMBLE_MAX_AGGREGATE_FORMAT_DESCRIPTORS must be between 1 and 128" + #endif +#endif + +static const char* LOG_TAG = "NimBLE2905"; + +NimBLE2905::NimBLE2905(NimBLECharacteristic* pChr) + : NimBLEDescriptor(NimBLEUUID(static_cast(0x2905)), BLE_GATT_CHR_F_READ, NIMBLE_MAX_AGGREGATE_FORMAT_DESCRIPTORS * sizeof(uint16_t), pChr) { +} // NimBLE2905 + +void NimBLE2905::initValue() { + const size_t count = m_vAggregatedDescriptors.size(); + uint16_t aggregatedHandles[count]; + + for (size_t i = 0; i < count; ++i) { + auto* desc = m_vAggregatedDescriptors[i]; + uint16_t handle = desc->getHandle(); + + if (handle == 0) { + NIMBLE_LOGE(LOG_TAG, "Failed to initialize value: presentation format descriptor handle is not initialized"); + return; + } + + aggregatedHandles[i] = desc->getHandle(); + } // initValue + + setValue(reinterpret_cast(aggregatedHandles), sizeof(aggregatedHandles)); + + // Presentation formats no longer needed, let's free some memory + m_vAggregatedDescriptors.clear(); + m_vAggregatedDescriptors.shrink_to_fit(); +} // initValue + + +/** + * @brief Add presentation format descriptor. + * @param [in] presentationFormat The 2904 descriptor to aggregate. + */ +void NimBLE2905::add2904Descriptor(const NimBLE2904* presentationFormat) { + if (presentationFormat == nullptr) { + NIMBLE_LOGE(LOG_TAG, "Failed to add presentation format descriptor: nullptr"); + return; + } + + if (m_vAggregatedDescriptors.size() < NIMBLE_MAX_AGGREGATE_FORMAT_DESCRIPTORS) { + m_vAggregatedDescriptors.push_back(presentationFormat); + } else { + NIMBLE_LOGE(LOG_TAG, "Failed to add presentation format descriptor: maximum capacity reached"); + } +} // add2904Descriptor + + +#endif // CONFIG_BT_NIMBLE_ENABLED && MYNEWT_VAL(BLE_ROLE_PERIPHERAL) diff --git a/src/NimBLE2905.h b/src/NimBLE2905.h new file mode 100644 index 000000000..9c1f90762 --- /dev/null +++ b/src/NimBLE2905.h @@ -0,0 +1,56 @@ +/* + * Copyright 2020-2025 Ryan Powell and + * esp-nimble-cpp, NimBLE-Arduino contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NIMBLE_CPP_2905_H_ +#define NIMBLE_CPP_2905_H_ + +#include "syscfg/syscfg.h" +#if CONFIG_BT_NIMBLE_ENABLED && MYNEWT_VAL(BLE_ROLE_PERIPHERAL) + +# include "NimBLEDescriptor.h" + +/** + * @brief Characteristic Aggregate Format descriptor (UUID: 0x2905). + * + * @details Contains an ordered list of handles referencing 0x2904 Presentation Format + * descriptors that define the parent characteristic’s value. + */ +class NimBLE2905 : public NimBLEDescriptor { + public: + NimBLE2905(NimBLECharacteristic* pChr = nullptr); + + void add2904Descriptor(const NimBLE2904* presentationFormat); + private: + + /** + * @brief Descriptor for the Characteristic Aggregate Format (UUID: 0x2905). + * + * @details Contains an ordered list of handles referencing the 0x2904 + * Presentation Format descriptors that define the parent characteristic's value. + * + * @see NimBLEServer::start() + */ + void initValue(); + + friend class NimBLECharacteristic; + friend class NimBLEServer; + + std::vector m_vAggregatedDescriptors; +}; // NimBLE2904 + +#endif // CONFIG_BT_NIMBLE_ENABLED && MYNEWT_VAL(BLE_ROLE_PERIPHERAL) +#endif // NIMBLE_CPP_2904_H_ diff --git a/src/NimBLECharacteristic.cpp b/src/NimBLECharacteristic.cpp index 343d40713..8bde275bd 100644 --- a/src/NimBLECharacteristic.cpp +++ b/src/NimBLECharacteristic.cpp @@ -26,6 +26,7 @@ # endif # include "NimBLE2904.h" +# include "NimBLE2905.h" # include "NimBLEDevice.h" # include "NimBLELog.h" @@ -86,6 +87,9 @@ NimBLEDescriptor* NimBLECharacteristic::createDescriptor(const NimBLEUUID& uuid, if (uuid == NimBLEUUID(static_cast(0x2904))) { NIMBLE_LOGW(LOG_TAG, "0x2904 descriptor should be created with create2904()"); pDescriptor = create2904(); + } else if (uuid == NimBLEUUID(static_cast(0x2905))) { + NIMBLE_LOGW(LOG_TAG, "0x2905 descriptor should be created with create2905()"); + pDescriptor = create2905(); } else { pDescriptor = new NimBLEDescriptor(uuid, properties, maxLen, this); } @@ -104,6 +108,16 @@ NimBLE2904* NimBLECharacteristic::create2904() { return pDescriptor; } // create2904 +/** + * @brief Create a Characteristic Aggregate Format Descriptor for this characteristic. + * @return A pointer to a NimBLE2905 descriptor. + */ +NimBLE2905* NimBLECharacteristic::create2905() { + NimBLE2905* pDescriptor = new NimBLE2905(this); + addDescriptor(pDescriptor); + return pDescriptor; +} // create2905 + /** * @brief Add a descriptor to the characteristic. * @param [in] pDescriptor A pointer to the descriptor to add. diff --git a/src/NimBLECharacteristic.h b/src/NimBLECharacteristic.h index e3c70b63f..b00a589c9 100644 --- a/src/NimBLECharacteristic.h +++ b/src/NimBLECharacteristic.h @@ -26,6 +26,7 @@ class NimBLEService; class NimBLECharacteristic; class NimBLEDescriptor; class NimBLE2904; +class NimBLE2905; # include "NimBLELocalValueAttribute.h" @@ -69,6 +70,7 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute { uint32_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE, uint16_t maxLen = BLE_ATT_ATTR_MAX_LEN); NimBLE2904* create2904(); + NimBLE2905* create2905(); NimBLEDescriptor* getDescriptorByUUID(const char* uuid, uint16_t index = 0) const; NimBLEDescriptor* getDescriptorByUUID(const NimBLEUUID& uuid, uint16_t index = 0) const; NimBLEDescriptor* getDescriptorByHandle(uint16_t handle) const; diff --git a/src/NimBLEServer.cpp b/src/NimBLEServer.cpp index 1cab58ed9..9fd9ccfb5 100644 --- a/src/NimBLEServer.cpp +++ b/src/NimBLEServer.cpp @@ -18,6 +18,7 @@ #include "NimBLEServer.h" #if CONFIG_BT_NIMBLE_ENABLED && MYNEWT_VAL(BLE_ROLE_PERIPHERAL) +# include "NimBLE2905.h" # include "NimBLEDevice.h" # include "NimBLELog.h" @@ -317,6 +318,19 @@ bool NimBLEServer::start() { } # endif + // Populate any Aggregate Format (0x2905) descriptors now that every attribute handle has been + // assigned during ble_gatts_start() (via the GATT register callback). A 0x2905 value is the + // ordered list of its aggregated 0x2904 presentation-format descriptor handles. + for (const auto& svc : m_svcVec) { + for (const auto& chr : svc->getCharacteristics()) { + for (auto& desc : chr->m_vDescriptors) { + if (desc->getUUID() == NimBLEUUID(static_cast(0x2905))) { + static_cast(desc)->initValue(); + } + } + } + } + // If the services have changed indicate it now if (m_svcChanged) { m_svcChanged = false; diff --git a/src/nimconfig.h b/src/nimconfig.h index 387896a7c..a2a64d07b 100644 --- a/src/nimconfig.h +++ b/src/nimconfig.h @@ -5,6 +5,9 @@ * Arduino User Options * **********************************************/ +/** @brief Uncomment to change the maximum number of aggregated presentation format descriptors; 5 by default. */ +// #define NIMBLE_MAX_AGGREGATE_FORMAT_DESCRIPTORS 5 + /** @brief Un-comment to change the number of simultaneous connections (esp controller max is 9) */ // #define MYNEWT_VAL_BLE_MAX_CONNECTIONS 3 From 14f462d1e22cf75bba4797194b1ebc1c4cdb40da Mon Sep 17 00:00:00 2001 From: srgg Date: Mon, 27 Jul 2026 19:02:36 -0600 Subject: [PATCH 2/2] Update src/NimBLE2905.cpp Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/NimBLE2905.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/NimBLE2905.cpp b/src/NimBLE2905.cpp index d88c02106..27959b9f6 100644 --- a/src/NimBLE2905.cpp +++ b/src/NimBLE2905.cpp @@ -38,22 +38,23 @@ NimBLE2905::NimBLE2905(NimBLECharacteristic* pChr) } // NimBLE2905 void NimBLE2905::initValue() { - const size_t count = m_vAggregatedDescriptors.size(); - uint16_t aggregatedHandles[count]; + uint16_t aggregatedHandles[NIMBLE_MAX_AGGREGATE_FORMAT_DESCRIPTORS]; + size_t validCount = 0; - for (size_t i = 0; i < count; ++i) { - auto* desc = m_vAggregatedDescriptors[i]; + for (auto* desc : m_vAggregatedDescriptors) { uint16_t handle = desc->getHandle(); if (handle == 0) { NIMBLE_LOGE(LOG_TAG, "Failed to initialize value: presentation format descriptor handle is not initialized"); - return; + continue; } - aggregatedHandles[i] = desc->getHandle(); - } // initValue + aggregatedHandles[validCount++] = handle; + } - setValue(reinterpret_cast(aggregatedHandles), sizeof(aggregatedHandles)); + if (validCount > 0) { + setValue(reinterpret_cast(aggregatedHandles), validCount * sizeof(uint16_t)); + } // Presentation formats no longer needed, let's free some memory m_vAggregatedDescriptors.clear();