Zephyr GATT UUID Notifications: A Search Start Does Not Limit the Match to One Service

Passing an attribute from service A to bt_gatt_notify_uuid() does not restrict its UUID search to service A. In Zephyr v4.4.0, that attribute supplies a starting handle; the search continues through 0xffff. If A lacks the requested value and a later service B contains it, the helper can select B’s attribute instead of reporting that A lacks the value.

The problem can occur in one unchanged, valid server database. It needs neither an OTA update nor a stale client cache. This analysis uses one explicit connected peer and a hypothetical static database. No service was registered, notification transmitted or interoperability test executed here.

Construct the missing-in-A counterexample

Suppose the current database manifest places service A at handles 0x0020 through 0x0023, followed by service B at 0x0024 through 0x0027. These are illustrative handles, not addresses to hard-code. A has a characteristic value with UUID V. B has a notifiable characteristic value with the desired custom UUID U at 0x0026. U does not occur between the chosen start and B’s value.

An A producer supplies A’s primary-service attribute as the start and U as the UUID. The requested application meaning is “notify A’s U value, or fail if A has none.” That service-scoped requirement is stronger than the helper’s actual contract.

The public header describes the attribute as a search starting point and selects the first occurrence from there. It accepts no ending handle or service object. A pointer being valid and belonging to A establishes where the search starts, not where it stops.

A fixed hypothetical attribute table has service A at handles 0x0020 through 0x0023 with value V and service B at 0x0024 through 0x0027 with value U. A UUID search starting in A continues into B and selects U at 0x0026.
Original source-derived GATT lookup diagram. A lacks U and B contains it; illustrative handles show selection, not transmitted notifications or measured behavior.

Follow the selected handle through the implementation

The inline wrapper zero-initializes notification parameters, supplies both uuid and attr, and calls bt_gatt_notify_cb(). The tagged implementation obtains the start through bt_gatt_attr_get_handle(), then uses its internal gatt_find_by_uuid() when a UUID was supplied.

That internal helper invokes bt_gatt_foreach_attr_type() with the inclusive starting handle, upper bound 0xffff and a maximum of one match. Its iterator checks the handle range and compares the requested UUID with each attribute’s UUID. Traversing the static service array does not stop simply because one service ends.

On a match, match_uuid() stores the matching attribute and handle. The notification path also replaces params->attr with that result. In the constructed database, the selected pair belongs to B at 0x0026. With the explicit connection supplied, that selected handle proceeds to gatt_notify().

The characteristic macro explains why the match is the value. BT_GATT_CHARACTERISTIC emits a characteristic-declaration attribute with type BT_UUID_GATT_CHRC, followed by a value attribute whose UUID is the supplied U or V. Searching for U compares attribute UUIDs; it does not match a service merely because its declaration or metadata refers to related functionality.

A correct subscription does not prove correct selection

Assume the peer has completed the necessary security and has a valid subscription to B’s notifiable value. Those conditions make it possible for subsequent notification processing to proceed; they do not tell the server that the payload producer intended A.

The selected handle and payload are separate inputs to notification construction. Perfectly encoded A data can therefore be associated with B’s value handle if the application chose this unbounded search incorrectly. Inspecting payload bytes alone will miss the instance-selection mistake.

Selection is the source-derived conclusion, not guaranteed delivery. Connection state, security, configuration-dependent subscription enforcement, ATT resources and other transmission constraints can still produce errors. Record the selected handle before interpreting the eventual return. An error later in the send path does not make the UUID lookup service-scoped.

Keep the producer’s service boundary explicit

When the application owns the service definition, retain a deliberate reference to its current characteristic value or declaration and use bt_gatt_notify() without the UUID search. The direct helper supports either characteristic form; the implementation adjusts a declaration to its value handle. Tie the reference to the actual definition and manifest rather than an unexplained array index copied from another build.

For the counterexample, A has no U attribute. A producer requiring U should fail its local setup or reject that operation. Substituting B’s reference would preserve the very mistake being diagnosed.

If discovery within the local database is genuinely needed, derive the intended service’s actual inclusive handle range and enforce both bounds. The iterator accepts explicit start and end handles. Verify that a match is inside the range and is the intended notifiable characteristic before using it. The separate bt_gatt_find_by_uuid() helper has count/end-handle semantics that need their own review; its similar name does not make it automatically service-scoped.

Make absence a regression case

A proposed lookup fixture should compare target present in A, target absent from A but present later in B, and target absent from both. Capture the selected attribute and handle before any transmission. The middle case is decisive: the service-scoped application contract must reject it even though the UUID helper can find B.

Use direct notification of B as a separate control, and keep discovery, security and subscriptions constant if an authorized radio test follows. No such fixture was run here. Acceptance requires that each producer’s selected current attribute belongs to its intended service, including when the requested value is absent.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *