Skip to content

Properly set attribute handles and improve dynamic service changes#399

Open
h2zero wants to merge 1 commit intomasterfrom
bugfix/att-handles
Open

Properly set attribute handles and improve dynamic service changes#399
h2zero wants to merge 1 commit intomasterfrom
bugfix/att-handles

Conversation

@h2zero
Copy link
Owner

@h2zero h2zero commented Mar 9, 2026

This changes how attribute handles are set so they can be correctly identified when there is more than one attribute with the same UUID. Instead of reading from the stack by UUID to get the handles this will now use the registration callback to set them correctly.

This also improves handling of dynamic service changes by properly removing characteristics/descriptors when required and resetting the GATT when advertising is started instead of after the last client disconnects.

  • Adds NimBLEUUID constructor overload for ble_uuid_t*.
  • NimBLECharacteristic::getDescriptorByUUID now takes an optional index value to support multiple same-uuid descriptors.

This changes how attribute handles are set so they can be correctly identified when there is more than one attribute with the same UUID.
Instead of reading from the stack by UUID to get the handles this will now use the registration callback to set them correctly.

This also improves handling of dynamic service changes by properly removing characteristics/descriptors when required and resetting the GATT when advertising is started instead of after the last client disconnects.

* Adds NimBLEUUID constructor overload for ble_uuid_t*.
* NimBLECharacteristic::getDescriptorByUUID now takes an optional index value to support multiple same-uuid descriptors.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates how GATT attribute handles are tracked (especially with duplicate UUIDs) by switching to NimBLE’s GATT registration callback, and refines the dynamic service-change/reset flow so the GATT database is rebuilt at server start/advertising time rather than on last disconnect.

Changes:

  • Add NimBLEUUID constructor overload for const ble_uuid_t*.
  • Populate service/characteristic/descriptor handles via ble_hs_cfg.gatts_register_cb registration events instead of UUID-based stack lookups.
  • Extend NimBLECharacteristic::getDescriptorByUUID with an optional index to support multiple descriptors sharing the same UUID.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/NimBLEUUID.h Adds UUID ctor overload from native UUID pointer.
src/NimBLEUUID.cpp Implements pointer-based UUID construction with null handling.
src/NimBLEService.cpp Adjusts service start behavior/logging and handle assignment strategy.
src/NimBLEServer.h Declares GATT registration callback hook.
src/NimBLEServer.cpp Implements registration callback and updates GATT reset/start sequencing.
src/NimBLECharacteristic.h / .cpp Adds indexed descriptor lookup to support duplicate UUID descriptors.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}

NIMBLE_LOGD(LOG_TAG, "Adding %d characteristics for service %s", numChrs, toString().c_str());
NIMBLE_LOGD(LOG_TAG, "Adding %d characteristics for service %s", numChrs, getUUID().toString().c_str());
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

numChrs is a size_t, but this log uses %d. This can trigger format warnings (or errors under -Werror) on some toolchains. Use the correct format specifier or cast to an int explicitly if the value is known to fit.

Suggested change
NIMBLE_LOGD(LOG_TAG, "Adding %d characteristics for service %s", numChrs, getUUID().toString().c_str());
NIMBLE_LOGD(LOG_TAG, "Adding %zu characteristics for service %s", numChrs, getUUID().toString().c_str());

Copilot uses AI. Check for mistakes.
Comment on lines +207 to +211
for (auto i = 0; i < NimBLEDevice::getServer()->m_svcVec.size(); i++) {
auto pSvc = NimBLEDevice::getServer()->getServiceByUUID(uuid, i);
if (pSvc != nullptr && pSvc->m_handle == 0) {
pSvc->m_handle = ctxt->svc.handle;
NIMBLE_LOGD(LOG_TAG, "Service registered: %s, handle=%d", uuid.toString().c_str(), ctxt->svc.handle);
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These for (auto i = 0; i < ...size(); i++) loops deduce i as int and compare to .size() (size_t), which can trigger signed/unsigned comparison warnings. Prefer size_t i = 0 (or a range-based loop) for the index variable.

Copilot uses AI. Check for mistakes.
Comment on lines +202 to +216
void NimBLEServer::gattRegisterCallback(ble_gatt_register_ctxt* ctxt, void* arg) {
gattRegisterCallbackArgs* args = static_cast<gattRegisterCallbackArgs*>(arg);

if (ctxt->op == BLE_GATT_REGISTER_OP_SVC) {
NimBLEUUID uuid(ctxt->svc.svc_def->uuid);
for (auto i = 0; i < NimBLEDevice::getServer()->m_svcVec.size(); i++) {
auto pSvc = NimBLEDevice::getServer()->getServiceByUUID(uuid, i);
if (pSvc != nullptr && pSvc->m_handle == 0) {
pSvc->m_handle = ctxt->svc.handle;
NIMBLE_LOGD(LOG_TAG, "Service registered: %s, handle=%d", uuid.toString().c_str(), ctxt->svc.handle);
// Set the arg to the service so we know that the following
// characteristics and descriptors belong to this service
args->pSvc = pSvc;
break;
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In gattRegisterCallback(), the state in args is not cleared when a service UUID isn’t found. If a BLE_GATT_REGISTER_OP_SVC event doesn’t match any entry in m_svcVec, args->pSvc (and potentially args->pChar) can retain the previous pointers, causing subsequent CHR/DSC registrations to be attributed to the wrong service/characteristic. Reset args->pSvc/args->pChar to nullptr at the start of each SVC op (and consider clearing pChar at each CHR op) before attempting to match.

Copilot uses AI. Check for mistakes.
Comment on lines +283 to +284
gattRegisterCallbackArgs args{};
ble_hs_cfg.gatts_register_arg = &args;
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ble_hs_cfg.gatts_register_arg is set to the address of a stack variable (gattRegisterCallbackArgs args{}) in NimBLEServer::start(). If the host keeps and uses gatts_register_arg after start() returns, this becomes a dangling pointer. Store the callback args with static/member lifetime (or clear ble_hs_cfg.gatts_register_arg immediately after ble_gatts_start() if it is guaranteed synchronous).

Suggested change
gattRegisterCallbackArgs args{};
ble_hs_cfg.gatts_register_arg = &args;
static gattRegisterCallbackArgs s_args{};
ble_hs_cfg.gatts_register_arg = &s_args;

Copilot uses AI. Check for mistakes.
// to update the service/characteristic/descriptor definitions. If characteristics or descriptors
// have been added/removed since the last server start then this service will be started on gatt reset.
if (getServer()->m_svcChanged) {
NIMBLE_LOGW(LOG_TAG, "<< start(): GATT server already started, cannot start service");
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The warning message here is misleading: the condition checks getServer()->m_svcChanged (i.e., services changed after the GATT server started), not simply that the server is already started. Consider updating the log text to indicate a GATT reset/restart is required due to dynamic service changes.

Suggested change
NIMBLE_LOGW(LOG_TAG, "<< start(): GATT server already started, cannot start service");
NIMBLE_LOGW(LOG_TAG, "<< start(): GATT services changed after start; GATT reset/restart required before adding this service");

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants