diff --git a/CODEOWNERS b/CODEOWNERS index d9d36f5305..6369a85792 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -49,6 +49,7 @@ /lib/bluetooth/ble_adv/ @nrfconnect/ncs-bm /lib/bluetooth/ble_conn_params/ @nrfconnect/ncs-bm /lib/bluetooth/ble_conn_state/ @nrfconnect/ncs-bm +/lib/bluetooth/ble_db_discovery/ @nrfconnect/ncs-bm /lib/bluetooth/ble_gq/ @nrfconnect/ncs-bm /lib/bluetooth/ble_qwr/ @nrfconnect/ncs-bm /lib/bluetooth/ble_racp/ @nrfconnect/ncs-bm @@ -95,6 +96,7 @@ # Tests /tests/lib/bluetooth/ble_adv/ @nrfconnect/ncs-bm-test /tests/lib/bluetooth/ble_conn_state/ @nrfconnect/ncs-bm +/tests/lib/bluetooth/ble_db_discovery/ @nrfconnect/ncs-bm /tests/lib/bluetooth/ble_qwr/ @nrfconnect/ncs-bm /tests/lib/bluetooth/ble_racp/ @nrfconnect/ncs-bm /tests/lib/bluetooth/ble_radio_notif/ @nrfconnect/ncs-bm diff --git a/doc/nrf-bm/api/api.rst b/doc/nrf-bm/api/api.rst index fc79992291..ecb07703be 100644 --- a/doc/nrf-bm/api/api.rst +++ b/doc/nrf-bm/api/api.rst @@ -48,6 +48,15 @@ Bluetooth LE Connection State library :inner: :members: +.. _api_ble_db_discovery: + +Bluetooth LE Database Discovery library +======================================= + +.. doxygengroup:: ble_db_discovery + :inner: + :members: + .. _api_ble_radio_notif: Bluetooth LE Radio Notification library diff --git a/doc/nrf-bm/libraries/bluetooth/ble_db_discovery.rst b/doc/nrf-bm/libraries/bluetooth/ble_db_discovery.rst new file mode 100644 index 0000000000..1fc2bfd429 --- /dev/null +++ b/doc/nrf-bm/libraries/bluetooth/ble_db_discovery.rst @@ -0,0 +1,58 @@ +.. _lib_ble_db_discovery: + +Bluetooth: Database Discovery library +############################## + +.. contents:: + :local: + :depth: 2 + +This module contains the APIs and types exposed by the Database Discovery module. These APIs and types can be used by the application to perform discovery of a service and its characteristics at the peer server. This module can also be used to discover the desired services in multiple remote devices. + +Overview +******** + +The library allows registration of callbacks that trigger upon discoveering selected services and characteristics in the peer device. + +.. _lib_ble_db_discovery_configure: + +Configuration +************* + +The library is enabled and configured using the Kconfig system: + +* :kconfig:option:`CONFIG_BLE_DB_DISCOVERY` - Enables the database discovery library. +* :kconfig:option:`CONFIG_BLE_GATT_DB_MAX_CHARS` - Sets the maximum number of database characteristics. +* :kconfig:option:`CONFIG_BLE_DB_DISCOVERY_MAX_SRV`- Sets the maximum number of services that can be discovered. +* :kconfig:option:`SRV_DISC_START_HANDLE`- Sets the start value used durning discovery. + + +Initialization +============== + +The library is initialized by calling the :c:func:`ble_db_discovery_init` function. +See the :c:struct:`ble_db_discovery_config` struct for configuration details. + +Usage +***** + +After initialization, use :c:func:`ble_db_discovery_evt_register` to register a callback that triggers when a service is discovered with a specific UUID. +To start discovering from a peer, you must use :c:func:`ble_db_discovery_start` with the connection handle of the peer device. +For example, you can call :c:func:`ble_db_discovery_start` when the ble event :c:enum:`BLE_GAP_EVT_CONNECTED` is triggered and use the connection handle from the event as the second argument in :c:func:`ble_db_discovery_start`. + + +Dependencies +************ + +This library uses the following |BMshort| libraries: + +* SoftDevice - :kconfig:option:`CONFIG_SOFTDEVICE` +* SoftDevice handler - :kconfig:option:`CONFIG_NRF_SDH` + +API documentation +***************** + +| Header file: :file:`include/bm/bluetooth/ble_db_discovery.h` +| Source files: :file:`lib/bluetooth/ble_db_discovery/` + +:ref:`Bluetooth LE Database Discovery library API reference ` diff --git a/include/bm/bluetooth/ble_db_discovery.h b/include/bm/bluetooth/ble_db_discovery.h new file mode 100644 index 0000000000..ee7e976097 --- /dev/null +++ b/include/bm/bluetooth/ble_db_discovery.h @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2013 - 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** @file + * + * @defgroup ble_db_discovery BLE Nordic database discovery library + * @{ + * @brief Library for discovery of a service and its characteristics at the peer server. + */ + +#include +#include +#include +#include +#include + +#ifndef BLE_DB_DISCOVERY_H__ +#define BLE_DB_DISCOVERY_H__ + +/** + * @brief Macro for defining a ble_db_discovery instance. + * + * @param _name Name of the instance. + */ +#define BLE_DB_DISCOVERY_DEF(_name) \ + static struct ble_db_discovery _name = {.discovery_in_progress = 0, \ + .conn_handle = BLE_CONN_HANDLE_INVALID}; \ + NRF_SDH_BLE_OBSERVER(_name##_obs, ble_db_discovery_on_ble_evt, &_name, HIGH); + +/** + * @brief BLE database discovery event type. + */ +enum ble_db_discovery_evt_type { + /** + * @brief Event indicating that the discovery of one service is complete. + */ + BLE_DB_DISCOVERY_COMPLETE, + /** + * @brief Event indicating that an internal error has occurred in the DB Discovery module. + * + * This could typically be because of the SoftDevice API returning an error code during the + * DB discover. + */ + BLE_DB_DISCOVERY_ERROR, + /** + * @brief Event indicating that the service was not found at the peer. + */ + BLE_DB_DISCOVERY_SRV_NOT_FOUND, + /** + * @brief Event indicating that the DB discovery instance is available. + */ + BLE_DB_DISCOVERY_AVAILABLE +}; + +/** + * @brief BLE database discovery event. + */ +struct ble_db_discovery_evt { + /** + * @brief Type of event. + */ + enum ble_db_discovery_evt_type evt_type; + /** + * @brief Handle of the connection for which this event has occurred. + */ + uint16_t conn_handle; + union { + /** + * @brief Structure containing the information about the GATT Database at the + * server. + * + * This will be filled when the event type is @ref BLE_DB_DISCOVERY_COMPLETE. The + * UUID field of this will be filled when the event type is @ref + * BLE_DB_DISCOVERY_SRV_NOT_FOUND. + */ + struct ble_gatt_db_srv discovered_db; + /** + * @brief Pointer to DB discovery instance @ref ble_db_discovery_t, indicating + * availability to the new discovery process. + * + * This will be filled when the event type is @ref BLE_DB_DISCOVERY_AVAILABLE. + */ + void const *db_instance; + /** + * @brief nRF Error code indicating the type of error which occurred in the DB + * Discovery module. + * + * This will be filled when the event type is @ref BLE_DB_DISCOVERY_ERROR. + */ + struct { + /** + * @brief Error reason. + */ + uint32_t reason; + } error; + } params; +}; + +struct ble_db_discovery; + +typedef void (*ble_db_discovery_evt_handler)(struct ble_db_discovery *db_discovery, + struct ble_db_discovery_evt *evt); + +/** + * @brief BLE database discovery configuration. + */ +struct ble_db_discovery_config { + /** + * @brief Event handler to be called by the DB Discovery module. + */ + ble_db_discovery_evt_handler evt_handler; + /** + * @brief Pointer to BLE GATT Queue instance. + */ + const struct ble_gq *gatt_queue; +}; + +/** + * @brief BLE database discovery user event. + */ +struct ble_db_discovery_user_evt { + /** + * @brief Pending event. + */ + struct ble_db_discovery_evt evt; + /** + * @brief Event handler which should be called to raise this event. + */ + ble_db_discovery_evt_handler evt_handler; +}; + +/** + * @brief BLE database discovery. + */ +struct ble_db_discovery { + /** + * @brief Information related to the current service being discovered. + * + * This is intended for internal use during service discovery. + */ + struct ble_gatt_db_srv services[CONFIG_BLE_DB_DISCOVERY_MAX_SRV]; + /** + * @brief UUID of registered handlers + */ + ble_uuid_t registered_handlers[CONFIG_BLE_DB_DISCOVERY_MAX_SRV]; + /** + * @brief Instance event handler. + */ + ble_db_discovery_evt_handler evt_handler; + /** + * @brief Pointer to BLE GATT Queue instance. + */ + const struct ble_gq *gatt_queue; + /** + * @brief The number of handlers registered with the DB Discovery module. + */ + uint32_t num_of_handlers_reg; + /** + * @brief Number of services at the peer's GATT database. + */ + uint8_t srv_count; + /** + * @brief Index of the current characteristic being discovered. + * + * This is intended for internal use during service discovery. + */ + uint8_t curr_char_ind; + /** + * @brief Index of the current service being discovered. + * + * This is intended for internal use during service discovery. + */ + uint8_t curr_srv_ind; + /** + * @brief Number of service discoveries made, both successful and unsuccessful. + */ + uint8_t discoveries_count; + /** + * @brief Variable to indicate whether there is a service discovery in progress. + */ + bool discovery_in_progress; + /** + * @brief Connection handle on which the discovery is started. + */ + uint16_t conn_handle; + /** + * @brief The index to the pending user event array, pointing to the last added pending user + * event. + */ + uint32_t pending_usr_evt_index; + /** + * @brief Whenever a discovery related event is to be raised to a user module, it is stored + * in this array first. + * + * When all expected services have been discovered, all pending events are sent to the + * corresponding user modules. + */ + struct ble_db_discovery_user_evt pending_usr_evts[CONFIG_BLE_DB_DISCOVERY_MAX_SRV]; +}; + +/** + * @brief Function for initializing the DB Discovery module. + * + * @param[in] db_discovery BLE DB discovery instance + * @param[in] db_config Pointer to DB discovery initialization structure. + * + * @retval NRF_SUCCESS On successful initialization. + * @retval NRF_ERROR_NULL If the initialization structure was NULL or + * the structure content is empty. + */ +uint32_t ble_db_discovery_init(struct ble_db_discovery *db_discovery, + struct ble_db_discovery_config *db_config); + +/** + * @brief Function for starting the discovery of the GATT database at the server. + * + * @param[out] db_discovery Pointer to the DB Discovery structure. + * @param[in] conn_handle The handle of the connection for which the discovery should be + * started. + * + * @retval NRF_SUCCESS Operation success. + * @retval NRF_ERROR_NULL When a NULL pointer is passed as input. + * @retval NRF_ERROR_INVALID_STATE If this function is called without calling the + * @ref ble_db_discovery_init, or without calling + * @ref ble_db_discovery_evt_register. + * @retval NRF_ERROR_BUSY If a discovery is already in progress using + * @p db_discovery. Use a different @ref struct ble_db_discovery + * structure, or wait for a DB Discovery event before retrying. + * @return This API propagates the error code returned by functions: + * @ref nrf_ble_gq_conn_handle_register and @ref + * nrf_ble_gq_item_add. + */ +uint32_t ble_db_discovery_start(struct ble_db_discovery *db_discovery, uint16_t conn_handle); + +/** + * @brief Function for registering with the DB Discovery module. + * + * @details The application can use this function to inform which service it is interested in + * discovering at the server. + * + * @param[in] db_discovery Pointer to the DB Discovery structure. + * @param[in] uuid Pointer to the UUID of the service to be discovered at the server. + * + * @note The total number of services that can be discovered by this module is @ref + * CONFIG_BLE_DB_DISCOVERY_MAX_SRV. This effectively means that the maximum number of + * registrations possible is equal to the @ref CONFIG_BLE_DB_DISCOVERY_MAX_SRV. + * + * @retval NRF_SUCCESS Operation success. + * @retval NRF_ERROR_NULL When a NULL pointer is passed as input. + * @retval NRF_ERROR_INVALID_STATE If this function is called without calling the + * @ref ble_db_discovery_init. + * @retval NRF_ERROR_NO_MEM The maximum number of registrations allowed by this module + * has been reached. + */ +uint32_t ble_db_discovery_evt_register(struct ble_db_discovery *db_discovery, + const ble_uuid_t *const uuid); + +/** + * @brief Function for handling the Application's BLE Stack events. + * + * @param[in] ble_evt Pointer to the BLE event received. + * @param[in,out] context Pointer to the DB Discovery structure. + */ +void ble_db_discovery_on_ble_evt(ble_evt_t const *ble_evt, void *context); + +#endif /* BLE_DB_DISCOVERY_H__ */ + +/** @} */ diff --git a/include/bm/bluetooth/peer_manager/ble_gatt_db.h b/include/bm/bluetooth/ble_gatt_db.h similarity index 100% rename from include/bm/bluetooth/peer_manager/ble_gatt_db.h rename to include/bm/bluetooth/ble_gatt_db.h diff --git a/include/bm/bluetooth/peer_manager/peer_manager_types.h b/include/bm/bluetooth/peer_manager/peer_manager_types.h index 8219c6edfa..5fffb973db 100644 --- a/include/bm/bluetooth/peer_manager/peer_manager_types.h +++ b/include/bm/bluetooth/peer_manager/peer_manager_types.h @@ -20,7 +20,7 @@ #include #include #include -#include +#include #ifdef __cplusplus extern "C" { diff --git a/lib/bluetooth/CMakeLists.txt b/lib/bluetooth/CMakeLists.txt index a29e625f37..a3c7cac791 100644 --- a/lib/bluetooth/CMakeLists.txt +++ b/lib/bluetooth/CMakeLists.txt @@ -7,6 +7,7 @@ add_subdirectory_ifdef(CONFIG_BLE_ADV ble_adv) add_subdirectory_ifdef(CONFIG_BLE_CONN_PARAMS ble_conn_params) add_subdirectory_ifdef(CONFIG_BLE_CONN_STATE ble_conn_state) +add_subdirectory_ifdef(CONFIG_BLE_DB_DISCOVERY ble_db_discovery) add_subdirectory_ifdef(CONFIG_BLE_GATT_QUEUE ble_gq) add_subdirectory_ifdef(CONFIG_BLE_RACP ble_racp) add_subdirectory_ifdef(CONFIG_BLE_RADIO_NOTIFICATION ble_radio_notification) diff --git a/lib/bluetooth/Kconfig b/lib/bluetooth/Kconfig index 4e7e7007f6..7a53abf74a 100644 --- a/lib/bluetooth/Kconfig +++ b/lib/bluetooth/Kconfig @@ -8,6 +8,7 @@ menu "Bluetooth libraries" rsource "ble_adv/Kconfig" rsource "ble_conn_params/Kconfig" rsource "ble_conn_state/Kconfig" +rsource "ble_db_discovery/Kconfig" rsource "ble_gq/Kconfig" rsource "ble_racp/Kconfig" rsource "ble_radio_notification/Kconfig" diff --git a/lib/bluetooth/ble_db_discovery/CMakeLists.txt b/lib/bluetooth/ble_db_discovery/CMakeLists.txt new file mode 100644 index 0000000000..e333a0936d --- /dev/null +++ b/lib/bluetooth/ble_db_discovery/CMakeLists.txt @@ -0,0 +1,9 @@ +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +zephyr_library() +zephyr_library_sources( + ble_db_discovery.c +) diff --git a/lib/bluetooth/ble_db_discovery/Kconfig b/lib/bluetooth/ble_db_discovery/Kconfig new file mode 100644 index 0000000000..09f8a222a5 --- /dev/null +++ b/lib/bluetooth/ble_db_discovery/Kconfig @@ -0,0 +1,41 @@ +# +# Copyright (c) 2025 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menuconfig BLE_DB_DISCOVERY + bool "BLE DB discovery" + depends on SOFTDEVICE + select EXPERIMENTAL + help + database discovery. + +if BLE_DB_DISCOVERY + +config BLE_GATT_DB_MAX_CHARS + int "Database max characteristics" + range 1 6 + default 6 + help + The maximum number of database characteristics. + +config BLE_DB_DISCOVERY_MAX_SRV + int "Max discovered services" + range 1 6 + default 6 + help + The total number of services that can be discovered by this module. + +config SRV_DISC_START_HANDLE + hex "Start handle value during discovery" + range 0x0000 0x0EFF + default 0x0001 + help + The start handle value used during service discovery. + +module=BLE_DB_DISCOVERY +module-str=BLE DB Discovery +source "$(ZEPHYR_BASE)/subsys/logging/Kconfig.template.log_config" + +endif # BLE_DB_DISCOVERY diff --git a/lib/bluetooth/ble_db_discovery/ble_db_discovery.c b/lib/bluetooth/ble_db_discovery/ble_db_discovery.c new file mode 100644 index 0000000000..c848bdac07 --- /dev/null +++ b/lib/bluetooth/ble_db_discovery/ble_db_discovery.c @@ -0,0 +1,752 @@ +/* + * Copyright (c) 2013 - 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(ble_db_disc, CONFIG_BLE_DB_DISCOVERY_LOG_LEVEL); + +static ble_db_discovery_evt_handler registered_handler_get(struct ble_db_discovery *db_discovery, + ble_uuid_t const *srv_uuid) +{ + for (uint32_t i = 0; i < db_discovery->num_of_handlers_reg; i++) { + if (BLE_UUID_EQ(&(db_discovery->registered_handlers[i]), srv_uuid)) { + return db_discovery->evt_handler; + } + } + + return NULL; +} + +static uint32_t registered_handler_set(struct ble_db_discovery *db_discovery, + ble_uuid_t const *srv_uuid) +{ + if (registered_handler_get(db_discovery, srv_uuid) != NULL) { + return NRF_SUCCESS; + } + + if (db_discovery->num_of_handlers_reg < CONFIG_BLE_DB_DISCOVERY_MAX_SRV) { + db_discovery->registered_handlers[db_discovery->num_of_handlers_reg] = *srv_uuid; + db_discovery->num_of_handlers_reg++; + + return NRF_SUCCESS; + } else { + return NRF_ERROR_NO_MEM; + } +} + +static void pending_user_evts_send(struct ble_db_discovery *db_discovery) +{ + for (uint32_t i = 0; i < db_discovery->num_of_handlers_reg; i++) { + /* Pass the event to the corresponding event handler. */ + db_discovery->pending_usr_evts[i].evt_handler( + db_discovery, &(db_discovery->pending_usr_evts[i].evt)); + } + + db_discovery->pending_usr_evt_index = 0; +} + +static void discovery_available_evt_trigger(struct ble_db_discovery *const db_discovery, + uint16_t const conn_handle) +{ + struct ble_db_discovery_evt evt = {0}; + + evt.conn_handle = conn_handle; + evt.evt_type = BLE_DB_DISCOVERY_AVAILABLE; + evt.params.db_instance = (void *)db_discovery; + + if (db_discovery->evt_handler) { + db_discovery->evt_handler(db_discovery, &evt); + } +} + +static void discovery_error_evt_trigger(struct ble_db_discovery *db_discovery, uint32_t err_code, + uint16_t conn_handle) +{ + ble_db_discovery_evt_handler evt_handler; + struct ble_gatt_db_srv *srv_being_discovered; + + srv_being_discovered = &(db_discovery->services[db_discovery->curr_srv_ind]); + + evt_handler = registered_handler_get(db_discovery, &(srv_being_discovered->srv_uuid)); + + if (evt_handler != NULL) { + struct ble_db_discovery_evt evt = { + .conn_handle = conn_handle, + .evt_type = BLE_DB_DISCOVERY_ERROR, + .params.error.reason = err_code, + }; + + db_discovery->evt_handler(db_discovery, &evt); + } +} + +static void discovery_error_handler(uint16_t conn_handle, uint32_t nrf_error, void *ctx) +{ + struct ble_db_discovery *db_discovery = (struct ble_db_discovery *)ctx; + + db_discovery->discovery_in_progress = false; + + discovery_error_evt_trigger(db_discovery, nrf_error, conn_handle); + discovery_available_evt_trigger(db_discovery, conn_handle); +} + +static void discovery_event_handler(const struct ble_gq_req *req, struct ble_gq_evt *evt) +{ + if (evt->evt_type != BLE_GQ_EVT_ERROR) { + return; + } + + discovery_error_handler(evt->conn_handle, evt->error.reason, req->ctx); +} + +static void discovery_complete_evt_trigger(struct ble_db_discovery *db_discovery, bool is_srv_found, + uint16_t conn_handle) +{ + ble_db_discovery_evt_handler evt_handler; + struct ble_gatt_db_srv *srv_being_discovered; + + srv_being_discovered = &(db_discovery->services[db_discovery->curr_srv_ind]); + + evt_handler = registered_handler_get(db_discovery, &(srv_being_discovered->srv_uuid)); + + if (evt_handler != NULL) { + if (db_discovery->pending_usr_evt_index < CONFIG_BLE_DB_DISCOVERY_MAX_SRV) { + /* Insert an event into the pending event list. */ + db_discovery->pending_usr_evts[db_discovery->pending_usr_evt_index] + .evt.conn_handle = conn_handle; + db_discovery->pending_usr_evts[db_discovery->pending_usr_evt_index] + .evt.params.discovered_db = *srv_being_discovered; + + if (is_srv_found) { + db_discovery->pending_usr_evts[db_discovery->pending_usr_evt_index] + .evt.evt_type = BLE_DB_DISCOVERY_COMPLETE; + } else { + db_discovery->pending_usr_evts[db_discovery->pending_usr_evt_index] + .evt.evt_type = BLE_DB_DISCOVERY_SRV_NOT_FOUND; + } + + db_discovery->pending_usr_evts[db_discovery->pending_usr_evt_index] + .evt_handler = evt_handler; + db_discovery->pending_usr_evt_index++; + + if (db_discovery->pending_usr_evt_index == + db_discovery->num_of_handlers_reg) { + /* All registered modules have pending events. Send all pending + * events to the user modules. + */ + pending_user_evts_send(db_discovery); + } else { + /* Too many events pending. Do nothing. (Ideally this should not + * happen.) + */ + } + } + } +} + +static void on_srv_disc_completion(struct ble_db_discovery *db_discovery, uint16_t conn_handle) +{ + struct ble_gq_req db_srv_disc_req = {0}; + + db_discovery->discoveries_count++; + + /* Check if more services need to be discovered. */ + if (db_discovery->discoveries_count < db_discovery->num_of_handlers_reg) { + /* Reset the current characteristic index since a new service discovery is about to + * start. + */ + db_discovery->curr_char_ind = 0; + + /* Initiate discovery of the next service. */ + db_discovery->curr_srv_ind++; + + struct ble_gatt_db_srv *srv_being_discovered; + + srv_being_discovered = &(db_discovery->services[db_discovery->curr_srv_ind]); + + srv_being_discovered->srv_uuid = + db_discovery->registered_handlers[db_discovery->curr_srv_ind]; + + /* Reset the characteristic count in the current service to zero since a new + * service discovery is about to start. + */ + srv_being_discovered->char_count = 0; + + LOG_DBG("Starting discovery of service with UUID 0x%x on connection handle 0x%x.", + srv_being_discovered->srv_uuid.uuid, conn_handle); + + uint32_t err_code; + + db_srv_disc_req.type = BLE_GQ_REQ_SRV_DISCOVERY; + db_srv_disc_req.gattc_srv_disc.start_handle = CONFIG_SRV_DISC_START_HANDLE; + db_srv_disc_req.gattc_srv_disc.srvc_uuid = srv_being_discovered->srv_uuid; + db_srv_disc_req.ctx = db_discovery; + db_srv_disc_req.evt_handler = discovery_event_handler; + + err_code = ble_gq_item_add(db_discovery->gatt_queue, &db_srv_disc_req, conn_handle); + + if (err_code != NRF_SUCCESS) { + discovery_error_handler(conn_handle, err_code, db_discovery); + + return; + } + } else { + /* No more service discovery is needed. */ + db_discovery->discovery_in_progress = false; + + discovery_available_evt_trigger(db_discovery, conn_handle); + } +} + +static bool is_char_discovery_reqd(struct ble_db_discovery *db_discovery, + ble_gattc_char_t *after_char) +{ + if (after_char->handle_value < + db_discovery->services[db_discovery->curr_srv_ind].handle_range.end_handle) { + /* Handle value of the characteristic being discovered is less than the end handle + * of the service being discovered. There is a possibility of more characteristics + * being present. Hence a characteristic discovery is required. + */ + return true; + } + + return false; +} + +static bool is_desc_discovery_reqd(struct ble_db_discovery *db_discovery, + struct ble_gatt_db_char *curr_char, + struct ble_gatt_db_char *next_char, + ble_gattc_handle_range_t *handle_range) +{ + if (next_char == NULL) { + /* Current characteristic is the last characteristic in the service. Check if the + * value handle of the current characteristic is equal to the service end handle. + */ + if (curr_char->characteristic.handle_value == + db_discovery->services[db_discovery->curr_srv_ind].handle_range.end_handle) { + /* No descriptors can be present for the current characteristic. curr_char + * is the last characteristic with no descriptors. + */ + return false; + } + + handle_range->start_handle = curr_char->characteristic.handle_value + 1; + + /* Since the current characteristic is the last characteristic in the service, the + * end handle should be the end handle of the service. + */ + handle_range->end_handle = + db_discovery->services[db_discovery->curr_srv_ind].handle_range.end_handle; + + return true; + } + + /* next_char != NULL. Check for existence of descriptors between the current and the next + * characteristic. + */ + if ((curr_char->characteristic.handle_value + 1) == next_char->characteristic.handle_decl) { + /* No descriptors can exist between the two characteristic. */ + return false; + } + + handle_range->start_handle = curr_char->characteristic.handle_value + 1; + handle_range->end_handle = next_char->characteristic.handle_decl - 1; + + return true; +} + +static uint32_t characteristics_discover(struct ble_db_discovery *db_discovery, + uint16_t conn_handle) +{ + struct ble_gatt_db_srv *srv_being_discovered; + ble_gattc_handle_range_t handle_range = {0}; + struct ble_gq_req db_char_disc_req = {0}; + + srv_being_discovered = &(db_discovery->services[db_discovery->curr_srv_ind]); + + if (db_discovery->curr_char_ind != 0) { + /* This is not the first characteristic being discovered. Hence the 'start handle' + * to be used must be computed using the handle_value of the previous + * characteristic. + */ + ble_gattc_char_t *prev_char; + uint8_t prev_char_ind = db_discovery->curr_char_ind - 1; + + srv_being_discovered = &(db_discovery->services[db_discovery->curr_srv_ind]); + + prev_char = &(srv_being_discovered->charateristics[prev_char_ind].characteristic); + + handle_range.start_handle = prev_char->handle_value + 1; + } else { + /* This is the first characteristic of this service being discovered. */ + handle_range.start_handle = srv_being_discovered->handle_range.start_handle; + } + + handle_range.end_handle = srv_being_discovered->handle_range.end_handle; + + db_char_disc_req.type = BLE_GQ_REQ_CHAR_DISCOVERY; + db_char_disc_req.gattc_char_disc = handle_range; + db_char_disc_req.ctx = db_discovery; + db_char_disc_req.evt_handler = discovery_event_handler; + + return ble_gq_item_add(db_discovery->gatt_queue, &db_char_disc_req, conn_handle); +} + +static uint32_t descriptors_discover(struct ble_db_discovery *db_discovery, + bool *raise_discov_complete, uint16_t conn_handle) +{ + ble_gattc_handle_range_t handle_range; + struct ble_gatt_db_char *curr_char_being_discovered; + struct ble_gatt_db_srv *srv_being_discovered; + struct ble_gq_req db_desc_disc_req = {0}; + bool is_discovery_reqd = false; + + srv_being_discovered = &(db_discovery->services[db_discovery->curr_srv_ind]); + + curr_char_being_discovered = + &(srv_being_discovered->charateristics[db_discovery->curr_char_ind]); + + if ((db_discovery->curr_char_ind + 1) == srv_being_discovered->char_count) { + /* This is the last characteristic of this service. */ + is_discovery_reqd = is_desc_discovery_reqd(db_discovery, curr_char_being_discovered, + NULL, &handle_range); + } else { + uint8_t i; + struct ble_gatt_db_char *next_char; + + for (i = db_discovery->curr_char_ind; i < srv_being_discovered->char_count; i++) { + if (i == (srv_being_discovered->char_count - 1)) { + /* The current characteristic is the last characteristic in the + * service. + */ + next_char = NULL; + } else { + next_char = &(srv_being_discovered->charateristics[i + 1]); + } + + /* Check if it is possible for the current characteristic to have a + * descriptor. + */ + if (is_desc_discovery_reqd(db_discovery, curr_char_being_discovered, + next_char, &handle_range)) { + is_discovery_reqd = true; + break; + } + /* No descriptors can exist. */ + curr_char_being_discovered = next_char; + db_discovery->curr_char_ind++; + } + } + + if (!is_discovery_reqd) { + /* No more descriptor discovery required. Discovery is complete. + * This informs the caller that a discovery complete event can be triggered. + */ + *raise_discov_complete = true; + + return NRF_SUCCESS; + } + + *raise_discov_complete = false; + + db_desc_disc_req.type = BLE_GQ_REQ_DESC_DISCOVERY; + db_desc_disc_req.gattc_desc_disc = handle_range; + db_desc_disc_req.ctx = db_discovery; + db_desc_disc_req.evt_handler = discovery_event_handler; + + return ble_gq_item_add(db_discovery->gatt_queue, &db_desc_disc_req, conn_handle); +} + +static void on_primary_srv_discovery_rsp(struct ble_db_discovery *db_discovery, + ble_gattc_evt_t const *ble_gattc_evt) +{ + struct ble_gatt_db_srv *srv_being_discovered; + + srv_being_discovered = &(db_discovery->services[db_discovery->curr_srv_ind]); + + if (ble_gattc_evt->conn_handle != db_discovery->conn_handle) { + return; + } + + if (ble_gattc_evt->gatt_status == BLE_GATT_STATUS_SUCCESS) { + uint32_t err_code; + ble_gattc_evt_prim_srvc_disc_rsp_t const *prim_srvc_disc_rsp_evt; + + LOG_DBG("Found service UUID 0x%x.", srv_being_discovered->srv_uuid.uuid); + + prim_srvc_disc_rsp_evt = &(ble_gattc_evt->params.prim_srvc_disc_rsp); + + srv_being_discovered->handle_range = + prim_srvc_disc_rsp_evt->services[0].handle_range; + + /* Number of services, previously discovered. */ + uint8_t num_srv_previous_disc = db_discovery->srv_count; + + /* Number of services, currently discovered. */ + uint8_t current_srv_disc = prim_srvc_disc_rsp_evt->count; + + if ((num_srv_previous_disc + current_srv_disc) <= CONFIG_BLE_DB_DISCOVERY_MAX_SRV) { + db_discovery->srv_count += current_srv_disc; + } else { + db_discovery->srv_count = CONFIG_BLE_DB_DISCOVERY_MAX_SRV; + + LOG_WRN("Not enough space for services."); + LOG_WRN("Increase CONFIG_BLE_DB_DISCOVERY_MAX_SRV to be able to store more " + "services!"); + } + + err_code = characteristics_discover(db_discovery, ble_gattc_evt->conn_handle); + + if (err_code != NRF_SUCCESS) { + discovery_error_handler(ble_gattc_evt->conn_handle, err_code, db_discovery); + } + } else { + LOG_DBG("Service UUID 0x%x not found.", srv_being_discovered->srv_uuid.uuid); + /* Trigger Service Not Found event to the application. */ + discovery_complete_evt_trigger(db_discovery, false, ble_gattc_evt->conn_handle); + on_srv_disc_completion(db_discovery, ble_gattc_evt->conn_handle); + } +} + +static void on_characteristic_discovery_rsp(struct ble_db_discovery *db_discovery, + ble_gattc_evt_t const *ble_gattc_evt) +{ + uint32_t err_code; + struct ble_gatt_db_srv *srv_being_discovered; + bool perform_desc_discov = false; + + if (ble_gattc_evt->conn_handle != db_discovery->conn_handle) { + return; + } + + srv_being_discovered = &(db_discovery->services[db_discovery->curr_srv_ind]); + + if (ble_gattc_evt->gatt_status == BLE_GATT_STATUS_SUCCESS) { + ble_gattc_evt_char_disc_rsp_t const *char_disc_rsp_evt; + + char_disc_rsp_evt = &(ble_gattc_evt->params.char_disc_rsp); + + /* Find out the number of characteristics that were previously discovered (in + * earlier characteristic discovery responses, if any). + */ + uint8_t num_chars_prev_disc = srv_being_discovered->char_count; + + /* Find out the number of characteristics that are currently discovered (in the + * characteristic discovery response being handled). + */ + uint8_t num_chars_curr_disc = char_disc_rsp_evt->count; + + /* Check if the total number of discovered characteristics are supported by this + * module. + */ + if ((num_chars_prev_disc + num_chars_curr_disc) <= CONFIG_BLE_GATT_DB_MAX_CHARS) { + /* Update the characteristics count. */ + srv_being_discovered->char_count += num_chars_curr_disc; + } else { + /* The number of characteristics discovered at the peer is more than the + * supported maximum. This module will store only the characteristics found + * up to this point. + */ + srv_being_discovered->char_count = CONFIG_BLE_GATT_DB_MAX_CHARS; + LOG_WRN("Not enough space for characteristics associated with " + "service 0x%04X !", + srv_being_discovered->srv_uuid.uuid); + LOG_WRN("Increase CONFIG_BLE_GATT_DB_MAX_CHARS to be able to store more " + "characteristics for each service!"); + } + + uint32_t i; + uint32_t j; + + for (i = num_chars_prev_disc, j = 0; i < srv_being_discovered->char_count; + i++, j++) { + srv_being_discovered->charateristics[i].characteristic = + char_disc_rsp_evt->chars[j]; + + srv_being_discovered->charateristics[i].cccd_handle = + BLE_GATT_HANDLE_INVALID; + srv_being_discovered->charateristics[i].ext_prop_handle = + BLE_GATT_HANDLE_INVALID; + srv_being_discovered->charateristics[i].user_desc_handle = + BLE_GATT_HANDLE_INVALID; + srv_being_discovered->charateristics[i].report_ref_handle = + BLE_GATT_HANDLE_INVALID; + } + + ble_gattc_char_t *last_known_char; + + last_known_char = &(srv_being_discovered->charateristics[i - 1].characteristic); + + /* If no more characteristic discovery is required, or if the maximum number of + * supported characteristic per service has been reached, descriptor discovery will + * be performed. + */ + if (!is_char_discovery_reqd(db_discovery, last_known_char) || + (srv_being_discovered->char_count == CONFIG_BLE_GATT_DB_MAX_CHARS)) { + perform_desc_discov = true; + } else { + /* Update the current characteristic index. */ + db_discovery->curr_char_ind = srv_being_discovered->char_count; + + /* Perform another round of characteristic discovery. */ + err_code = + characteristics_discover(db_discovery, ble_gattc_evt->conn_handle); + + if (err_code != NRF_SUCCESS) { + discovery_error_handler(ble_gattc_evt->conn_handle, err_code, + db_discovery); + + return; + } + } + } else { + /* The previous characteristic discovery resulted in no characteristics. + * descriptor discovery should be performed. + */ + perform_desc_discov = true; + } + + if (perform_desc_discov) { + bool raise_discov_complete; + + db_discovery->curr_char_ind = 0; + + err_code = descriptors_discover(db_discovery, &raise_discov_complete, + ble_gattc_evt->conn_handle); + + if (err_code != NRF_SUCCESS) { + discovery_error_handler(ble_gattc_evt->conn_handle, err_code, db_discovery); + + return; + } + if (raise_discov_complete) { + /* No more characteristics and descriptors need to be discovered. Discovery + * is complete. Send a discovery complete event to the user application. + */ + LOG_DBG("Discovery of service with UUID 0x%x completed with success" + " on connection handle 0x%x.", + srv_being_discovered->srv_uuid.uuid, ble_gattc_evt->conn_handle); + + discovery_complete_evt_trigger(db_discovery, true, + ble_gattc_evt->conn_handle); + on_srv_disc_completion(db_discovery, ble_gattc_evt->conn_handle); + } + } +} + +static void on_descriptor_discovery_rsp(struct ble_db_discovery *const db_discovery, + const ble_gattc_evt_t *const ble_gattc_evt) +{ + const ble_gattc_evt_desc_disc_rsp_t *desc_disc_rsp_evt; + struct ble_gatt_db_srv *srv_being_discovered; + + if (ble_gattc_evt->conn_handle != db_discovery->conn_handle) { + return; + } + + srv_being_discovered = &(db_discovery->services[db_discovery->curr_srv_ind]); + + desc_disc_rsp_evt = &(ble_gattc_evt->params.desc_disc_rsp); + + struct ble_gatt_db_char *char_being_discovered = + &(srv_being_discovered->charateristics[db_discovery->curr_char_ind]); + + if (ble_gattc_evt->gatt_status == BLE_GATT_STATUS_SUCCESS) { + /* The descriptor was found at the peer. + * Iterate through and collect CCCD, Extended Properties, + * User Description & Report Reference descriptor handles. + */ + for (uint32_t i = 0; i < desc_disc_rsp_evt->count; i++) { + switch (desc_disc_rsp_evt->descs[i].uuid.uuid) { + case BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG: + char_being_discovered->cccd_handle = + desc_disc_rsp_evt->descs[i].handle; + break; + + case BLE_UUID_DESCRIPTOR_CHAR_EXT_PROP: + char_being_discovered->ext_prop_handle = + desc_disc_rsp_evt->descs[i].handle; + break; + + case BLE_UUID_DESCRIPTOR_CHAR_USER_DESC: + char_being_discovered->user_desc_handle = + desc_disc_rsp_evt->descs[i].handle; + break; + + case BLE_UUID_REPORT_REF_DESCR: + char_being_discovered->report_ref_handle = + desc_disc_rsp_evt->descs[i].handle; + break; + } + + /* Break if we've found all the descriptors we are looking for. */ + if (char_being_discovered->cccd_handle != BLE_GATT_HANDLE_INVALID && + char_being_discovered->ext_prop_handle != BLE_GATT_HANDLE_INVALID && + char_being_discovered->user_desc_handle != BLE_GATT_HANDLE_INVALID && + char_being_discovered->report_ref_handle != BLE_GATT_HANDLE_INVALID) { + break; + } + } + } + + bool raise_discov_complete = false; + + if ((db_discovery->curr_char_ind + 1) == srv_being_discovered->char_count) { + /* No more characteristics and descriptors need to be discovered. Discovery is + * complete. Send a discovery complete event to the user application. + */ + raise_discov_complete = true; + } else { + /* Begin discovery of descriptors for the next characteristic.*/ + uint32_t err_code; + + db_discovery->curr_char_ind++; + + err_code = descriptors_discover(db_discovery, &raise_discov_complete, + ble_gattc_evt->conn_handle); + + if (err_code != NRF_SUCCESS) { + discovery_error_handler(ble_gattc_evt->conn_handle, err_code, db_discovery); + + return; + } + } + + if (raise_discov_complete) { + LOG_DBG("Discovery of service with UUID 0x%x completed with success" + " on connection handle 0x%x.", + srv_being_discovered->srv_uuid.uuid, ble_gattc_evt->conn_handle); + + discovery_complete_evt_trigger(db_discovery, true, ble_gattc_evt->conn_handle); + on_srv_disc_completion(db_discovery, ble_gattc_evt->conn_handle); + } +} + +static uint32_t discovery_start(struct ble_db_discovery *const db_discovery, uint16_t conn_handle) +{ + int err_code; + struct ble_gatt_db_srv *srv_being_discovered; + struct ble_gq_req db_srv_disc_req = {0}; + + err_code = ble_gq_conn_handle_register(db_discovery->gatt_queue, conn_handle); + if (err_code != NRF_SUCCESS) { + return err_code; + } + + db_discovery->conn_handle = conn_handle; + + srv_being_discovered = &(db_discovery->services[db_discovery->curr_srv_ind]); + srv_being_discovered->srv_uuid = + db_discovery->registered_handlers[db_discovery->curr_srv_ind]; + + LOG_DBG("Starting discovery of service with UUID 0x%x on connection handle 0x%x.", + srv_being_discovered->srv_uuid.uuid, conn_handle); + + db_srv_disc_req.type = BLE_GQ_REQ_SRV_DISCOVERY; + db_srv_disc_req.gattc_srv_disc.start_handle = CONFIG_SRV_DISC_START_HANDLE; + db_srv_disc_req.gattc_srv_disc.srvc_uuid = srv_being_discovered->srv_uuid; + db_srv_disc_req.ctx = db_discovery; + db_srv_disc_req.evt_handler = discovery_event_handler; + + err_code = ble_gq_item_add(db_discovery->gatt_queue, &db_srv_disc_req, conn_handle); + + if (err_code == NRF_SUCCESS) { + db_discovery->discovery_in_progress = true; + } + + return err_code; +} + +uint32_t ble_db_discovery_init(struct ble_db_discovery *db_discovery, + struct ble_db_discovery_config *db_config) +{ + if (!db_discovery || !db_config || !(db_config->evt_handler) || !(db_config->gatt_queue)) { + return NRF_ERROR_NULL; + } + + db_discovery->num_of_handlers_reg = 0; + db_discovery->evt_handler = db_config->evt_handler; + db_discovery->gatt_queue = db_config->gatt_queue; + + return NRF_SUCCESS; +} + +uint32_t ble_db_discovery_start(struct ble_db_discovery *const db_discovery, uint16_t conn_handle) +{ + if (!db_discovery) { + return NRF_ERROR_NULL; + } + if (!db_discovery->gatt_queue) { + return NRF_ERROR_INVALID_STATE; + } + + if (db_discovery->num_of_handlers_reg == 0) { + /* No user modules were registered. There are no services to discover. */ + return NRF_ERROR_INVALID_STATE; + } + + if (db_discovery->discovery_in_progress) { + return NRF_ERROR_BUSY; + } + + return discovery_start(db_discovery, conn_handle); +} + +uint32_t ble_db_discovery_evt_register(struct ble_db_discovery *db_discovery, + ble_uuid_t const *uuid) +{ + if (!db_discovery || !uuid) { + return NRF_ERROR_NULL; + } + if (!db_discovery->gatt_queue) { + return NRF_ERROR_INVALID_STATE; + } + + return registered_handler_set(db_discovery, uuid); +} + +static void on_disconnected(struct ble_db_discovery *db_discovery, ble_gap_evt_t const *evt) +{ + if (evt->conn_handle == db_discovery->conn_handle) { + db_discovery->discovery_in_progress = false; + db_discovery->conn_handle = BLE_CONN_HANDLE_INVALID; + } +} + +void ble_db_discovery_on_ble_evt(ble_evt_t const *ble_evt, void *context) +{ + struct ble_db_discovery *db_discovery = (struct ble_db_discovery *)context; + + if (!ble_evt || !context || !db_discovery->gatt_queue) { + return; + } + + switch (ble_evt->header.evt_id) { + case BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP: + on_primary_srv_discovery_rsp(db_discovery, &(ble_evt->evt.gattc_evt)); + break; + + case BLE_GATTC_EVT_CHAR_DISC_RSP: + on_characteristic_discovery_rsp(db_discovery, &(ble_evt->evt.gattc_evt)); + break; + + case BLE_GATTC_EVT_DESC_DISC_RSP: + on_descriptor_discovery_rsp(db_discovery, &(ble_evt->evt.gattc_evt)); + break; + + case BLE_GAP_EVT_DISCONNECTED: + on_disconnected(db_discovery, &(ble_evt->evt.gap_evt)); + break; + + default: + break; + } +} diff --git a/tests/lib/bluetooth/ble_db_discovery/CMakeLists.txt b/tests/lib/bluetooth/ble_db_discovery/CMakeLists.txt new file mode 100644 index 0000000000..00e09f3499 --- /dev/null +++ b/tests/lib/bluetooth/ble_db_discovery/CMakeLists.txt @@ -0,0 +1,59 @@ +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(unit_test_ble_db_discovery) + +set(SOFTDEVICE_VARIANT "s145") +set(SOFTDEVICE_INCLUDE_DIR "${ZEPHYR_NRF_BM_MODULE_DIR}/components/softdevice/${SOFTDEVICE_VARIANT}/${SOFTDEVICE_VARIANT}_API/include") + +cmock_handle(${SOFTDEVICE_INCLUDE_DIR}/ble.h) +cmock_handle(${SOFTDEVICE_INCLUDE_DIR}/ble_gatts.h + WORD_EXCLUDE + "__STATIC_INLINE" +) +cmock_handle(${SOFTDEVICE_INCLUDE_DIR}/ble_gattc.h + WORD_EXCLUDE + "__STATIC_INLINE" +) +cmock_handle(${SOFTDEVICE_INCLUDE_DIR}/ble_gap.h) +cmock_handle(${ZEPHYR_NRF_BM_MODULE_DIR}/include/bm/bluetooth/ble_gq.h) +cmock_handle(${ZEPHYR_NRF_BM_MODULE_DIR}/include/bm/softdevice_handler/nrf_sdh_ble.h) + +zephyr_linker_sources(SECTIONS evt_obs.ld) + +add_compile_definitions( + SVCALL_AS_NORMAL_FUNCTION=1 + NRF54L15_XXAA) + +target_compile_definitions( app PRIVATE + CONFIG_NRF_SDH_BLE_TOTAL_LINK_COUNT=1 + CONFIG_BLE_DB_DISCOVERY_MAX_SRV=6 + CONFIG_BLE_GQ_QUEUE_SIZE=8 + CONFIG_BLE_GQ_HEAP_SIZE=256 + CONFIG_BLE_GQ_MAX_CONNECTIONS=1 + CONFIG_SRV_DISC_START_HANDLE=1 + CONFIG_BLE_GATT_DB_MAX_CHARS=6 +) + +# Generate and add test file +test_runner_generate(src/unity_test.c) +target_sources(app PRIVATE src/unity_test.c) + +target_include_directories(app PRIVATE ${ZEPHYR_NRF_BM_MODULE_DIR}/include) +target_include_directories(app PRIVATE ${SOFTDEVICE_INCLUDE_DIR}) +target_include_directories(app PRIVATE ${ZEPHYR_HAL_NORDIC_MODULE_DIR}/nrfx/mdk) +target_include_directories(app PRIVATE ${ZEPHYR_CMSIS_MODULE_DIR}/CMSIS/Core/Include) + +target_sources(app + PRIVATE + ${ZEPHYR_NRF_BM_MODULE_DIR}/lib/bluetooth/ble_db_discovery/ble_db_discovery.c +) + +zephyr_include_directories(${ZEPHYR_NRF_BM_MODULE_DIR}/include) diff --git a/tests/lib/bluetooth/ble_db_discovery/evt_obs.ld b/tests/lib/bluetooth/ble_db_discovery/evt_obs.ld new file mode 100644 index 0000000000..ba91196245 --- /dev/null +++ b/tests/lib/bluetooth/ble_db_discovery/evt_obs.ld @@ -0,0 +1 @@ +ITERABLE_SECTION_ROM(nrf_sdh_ble_evt_observers, 4) diff --git a/tests/lib/bluetooth/ble_db_discovery/prj.conf b/tests/lib/bluetooth/ble_db_discovery/prj.conf new file mode 100644 index 0000000000..a8788847da --- /dev/null +++ b/tests/lib/bluetooth/ble_db_discovery/prj.conf @@ -0,0 +1,6 @@ +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +CONFIG_UNITY=y diff --git a/tests/lib/bluetooth/ble_db_discovery/src/unity_test.c b/tests/lib/bluetooth/ble_db_discovery/src/unity_test.c new file mode 100644 index 0000000000..b621548b3f --- /dev/null +++ b/tests/lib/bluetooth/ble_db_discovery/src/unity_test.c @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include + +#include "cmock_ble.h" +#include "cmock_ble_gap.h" +#include "cmock_ble_gattc.h" +#include "cmock_ble_gq.h" + +BLE_GQ_DEF(ble_gatt_queue); +BLE_DB_DISCOVERY_DEF(db_discovery) + +static struct ble_db_discovery_evt db_evt; +static struct ble_db_discovery_evt db_evt_prev; + +void ble_evt_send(const ble_evt_t *evt) +{ + TYPE_SECTION_FOREACH(struct nrf_sdh_ble_evt_observer, nrf_sdh_ble_evt_observers, obs) + { + obs->handler(evt, obs->context); + } +} + +static void db_discovery_evt_handler(struct ble_db_discovery *db_discovery, + struct ble_db_discovery_evt *evt) +{ + db_evt_prev = db_evt; + db_evt = *evt; +} + +void test_ble_adv_init_error_null(void) +{ + struct ble_db_discovery_config config = { + .gatt_queue = &ble_gatt_queue, + .evt_handler = db_discovery_evt_handler, + }; + int ret; + + ret = ble_db_discovery_init(NULL, &config); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, ret); + ret = ble_db_discovery_init(&db_discovery, NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, ret); + config.evt_handler = NULL; + ret = ble_db_discovery_init(&db_discovery, &config); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, ret); +} + +void test_ble_db_discovery_init(void) +{ + int ret; + + struct ble_db_discovery_config config = { + .gatt_queue = &ble_gatt_queue, + .evt_handler = db_discovery_evt_handler, + }; + + ret = ble_db_discovery_init(&db_discovery, &config); + TEST_ASSERT_EQUAL(NRF_SUCCESS, ret); + TEST_ASSERT_EQUAL(0, db_discovery.num_of_handlers_reg); + TEST_ASSERT_NOT_EQUAL(NULL, db_discovery.evt_handler); + TEST_ASSERT_NOT_EQUAL(NULL, db_discovery.gatt_queue); +} + +void test_ble_db_discovery_evt_register_null(void) +{ + uint32_t ret; + + struct ble_db_discovery db_discovery = {0}; + struct ble_db_discovery_config config = { + .gatt_queue = &ble_gatt_queue, + .evt_handler = db_discovery_evt_handler, + }; + + ble_uuid_t hrs_uuid = {.type = BLE_UUID_TYPE_BLE, .uuid = BLE_UUID_HEART_RATE_SERVICE}; + + ret = ble_db_discovery_init(&db_discovery, &config); + TEST_ASSERT_EQUAL(NRF_SUCCESS, ret); + + ret = ble_db_discovery_evt_register(&db_discovery, NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, ret); + ret = ble_db_discovery_evt_register(NULL, &hrs_uuid); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, ret); +} + +void test_ble_db_discovery_evt_register_invalid_state(void) +{ + + ble_uuid_t hrs_uuid = {.type = BLE_UUID_TYPE_BLE, .uuid = BLE_UUID_HEART_RATE_SERVICE}; + + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, + ble_db_discovery_evt_register(&db_discovery, &hrs_uuid)); +} + +void test_ble_db_discovery_evt_register_no_mem(void) +{ + struct ble_db_discovery_config config = { + .gatt_queue = &ble_gatt_queue, + .evt_handler = db_discovery_evt_handler, + }; + + TEST_ASSERT_EQUAL(NRF_SUCCESS, ble_db_discovery_init(&db_discovery, &config)); + + ble_uuid_t hrs_uuid = {.type = BLE_UUID_TYPE_BLE, .uuid = BLE_UUID_IMMEDIATE_ALERT_SERVICE}; + + for (size_t i = 0; i < CONFIG_BLE_DB_DISCOVERY_MAX_SRV; ++i) { + TEST_ASSERT_EQUAL(NRF_SUCCESS, + ble_db_discovery_evt_register(&db_discovery, &hrs_uuid)); + hrs_uuid.uuid++; + } + TEST_ASSERT_EQUAL(CONFIG_BLE_DB_DISCOVERY_MAX_SRV, db_discovery.num_of_handlers_reg); + TEST_ASSERT_EQUAL(NRF_ERROR_NO_MEM, + ble_db_discovery_evt_register(&db_discovery, &hrs_uuid)); +} + +void test_ble_db_discovery_evt_register(void) +{ + struct ble_db_discovery_config config = { + .gatt_queue = &ble_gatt_queue, + .evt_handler = db_discovery_evt_handler, + }; + + TEST_ASSERT_EQUAL(NRF_SUCCESS, ble_db_discovery_init(&db_discovery, &config)); + + ble_uuid_t hrs_uuid = {.type = BLE_UUID_TYPE_BLE, .uuid = BLE_UUID_HEART_RATE_SERVICE}; + + TEST_ASSERT_EQUAL(NRF_SUCCESS, ble_db_discovery_evt_register(&db_discovery, &hrs_uuid)); +} + +void test_ble_db_discovery_start_null(void) +{ + struct ble_db_discovery_config config = { + .gatt_queue = &ble_gatt_queue, + .evt_handler = db_discovery_evt_handler, + }; + + TEST_ASSERT_EQUAL(NRF_SUCCESS, ble_db_discovery_init(&db_discovery, &config)); + + ble_uuid_t hrs_uuid = {.type = BLE_UUID_TYPE_BLE, .uuid = BLE_UUID_HEART_RATE_SERVICE}; + + TEST_ASSERT_EQUAL(NRF_SUCCESS, ble_db_discovery_evt_register(&db_discovery, &hrs_uuid)); + + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, ble_db_discovery_start(NULL, 0)); +} + +void test_ble_db_discovery_start_invalid_state(void) +{ + struct ble_db_discovery_config config = { + .gatt_queue = &ble_gatt_queue, + .evt_handler = db_discovery_evt_handler, + }; + + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, ble_db_discovery_start(&db_discovery, 0)); + + TEST_ASSERT_EQUAL(NRF_SUCCESS, ble_db_discovery_init(&db_discovery, &config)); + + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, ble_db_discovery_start(&db_discovery, 0)); +} + +void test_ble_db_discovery_start_busy(void) +{ + struct ble_db_discovery_config config = { + .gatt_queue = &ble_gatt_queue, + .evt_handler = db_discovery_evt_handler, + }; + + TEST_ASSERT_EQUAL(NRF_SUCCESS, ble_db_discovery_init(&db_discovery, &config)); + + ble_uuid_t hrs_uuid = {.type = BLE_UUID_TYPE_BLE, .uuid = BLE_UUID_HEART_RATE_SERVICE}; + + TEST_ASSERT_EQUAL(NRF_SUCCESS, ble_db_discovery_evt_register(&db_discovery, &hrs_uuid)); + + __cmock_ble_gq_conn_handle_register_ExpectAnyArgsAndReturn(NRF_SUCCESS); + __cmock_ble_gq_item_add_ExpectAnyArgsAndReturn(NRF_SUCCESS); + TEST_ASSERT_EQUAL(NRF_SUCCESS, ble_db_discovery_start(&db_discovery, 0)); + + TEST_ASSERT_EQUAL(NRF_ERROR_BUSY, ble_db_discovery_start(&db_discovery, 0)); +} + +void test_ble_db_discovery_start_no_mem(void) +{ + struct ble_db_discovery_config config = { + .gatt_queue = &ble_gatt_queue, + .evt_handler = db_discovery_evt_handler, + }; + + TEST_ASSERT_EQUAL(NRF_SUCCESS, ble_db_discovery_init(&db_discovery, &config)); + + ble_uuid_t hrs_uuid = {.type = BLE_UUID_TYPE_BLE, .uuid = BLE_UUID_HEART_RATE_SERVICE}; + + TEST_ASSERT_EQUAL(NRF_SUCCESS, ble_db_discovery_evt_register(&db_discovery, &hrs_uuid)); + + __cmock_ble_gq_conn_handle_register_ExpectAndReturn(&ble_gatt_queue, 8, NRF_ERROR_NO_MEM); + TEST_ASSERT_EQUAL(NRF_ERROR_NO_MEM, ble_db_discovery_start(&db_discovery, 8)); +} + +void test_ble_db_discovery_start(void) +{ + struct ble_db_discovery_config config = { + .gatt_queue = &ble_gatt_queue, + .evt_handler = db_discovery_evt_handler, + }; + + TEST_ASSERT_EQUAL(NRF_SUCCESS, ble_db_discovery_init(&db_discovery, &config)); + + ble_uuid_t hrs_uuid = {.type = BLE_UUID_TYPE_BLE, .uuid = BLE_UUID_HEART_RATE_SERVICE}; + + TEST_ASSERT_EQUAL(NRF_SUCCESS, ble_db_discovery_evt_register(&db_discovery, &hrs_uuid)); + + __cmock_ble_gq_conn_handle_register_ExpectAndReturn(&ble_gatt_queue, 8, NRF_SUCCESS); + __cmock_ble_gq_item_add_ExpectAnyArgsAndReturn(NRF_SUCCESS); + TEST_ASSERT_EQUAL(NRF_SUCCESS, ble_db_discovery_start(&db_discovery, 8)); +} + +void test_ble_db_discovery_on_ble_evt(void) +{ + struct ble_db_discovery_config config = { + .gatt_queue = &ble_gatt_queue, + .evt_handler = db_discovery_evt_handler, + }; + ble_evt_t evt; + + __cmock_ble_gq_conn_handle_register_ExpectAndReturn(&ble_gatt_queue, 8, NRF_SUCCESS); + __cmock_ble_gq_item_add_ExpectAnyArgsAndReturn(NRF_SUCCESS); + __cmock_ble_gq_item_add_ExpectAnyArgsAndReturn(NRF_SUCCESS); + __cmock_ble_gq_item_add_ExpectAnyArgsAndReturn(NRF_SUCCESS); + __cmock_ble_gq_item_add_ExpectAnyArgsAndReturn(NRF_SUCCESS); + + TEST_ASSERT_EQUAL(NRF_SUCCESS, ble_db_discovery_init(&db_discovery, &config)); + + ble_uuid_t hrs_uuid = {.type = BLE_UUID_TYPE_BLE, .uuid = BLE_UUID_HEART_RATE_SERVICE}; + TEST_ASSERT_EQUAL(NRF_SUCCESS, ble_db_discovery_evt_register(&db_discovery, &hrs_uuid)); + + hrs_uuid.uuid = BLE_UUID_HEALTH_THERMOMETER_SERVICE; + TEST_ASSERT_EQUAL(NRF_SUCCESS, ble_db_discovery_evt_register(&db_discovery, &hrs_uuid)); + + TEST_ASSERT_EQUAL(NRF_SUCCESS, ble_db_discovery_start(&db_discovery, 8)); + + __cmock_ble_gq_on_ble_evt_ExpectAnyArgs(); + evt.header.evt_id = BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP; + evt.evt.gattc_evt.conn_handle = 8; + evt.evt.gattc_evt.params.prim_srvc_disc_rsp.services[0].uuid.uuid = + BLE_UUID_HEART_RATE_SERVICE; + evt.evt.gattc_evt.gatt_status = BLE_GATT_STATUS_SUCCESS; + evt.evt.gattc_evt.params.prim_srvc_disc_rsp.services[0].uuid.type = BLE_UUID_TYPE_BLE; + evt.evt.gattc_evt.params.prim_srvc_disc_rsp.count = 1; + ble_evt_send(&evt); + TEST_ASSERT_NOT_EQUAL(BLE_DB_DISCOVERY_ERROR, db_evt.evt_type); + + __cmock_ble_gq_on_ble_evt_ExpectAnyArgs(); + evt.evt.gattc_evt.gatt_status = BLE_GATT_STATUS_UNKNOWN; + ble_evt_send(&evt); + TEST_ASSERT_NOT_EQUAL(BLE_DB_DISCOVERY_ERROR, db_evt.evt_type); + + __cmock_ble_gq_on_ble_evt_ExpectAnyArgs(); + evt.header.evt_id = BLE_GATTC_EVT_CHAR_DISC_RSP; + evt.evt.gattc_evt.conn_handle = 8; + evt.evt.gattc_evt.gatt_status = BLE_GATT_STATUS_SUCCESS; + evt.evt.gattc_evt.params.char_disc_rsp.count = 1; + evt.evt.gattc_evt.params.char_disc_rsp.chars[0].uuid.uuid = + BLE_UUID_HEART_RATE_MEASUREMENT_CHAR; + evt.evt.gattc_evt.params.char_disc_rsp.chars[0].uuid.type = BLE_UUID_TYPE_BLE; + ble_evt_send(&evt); + TEST_ASSERT_NOT_EQUAL(BLE_DB_DISCOVERY_ERROR, db_evt.evt_type); + + __cmock_ble_gq_item_add_ExpectAnyArgsAndReturn(NRF_SUCCESS); + __cmock_ble_gq_on_ble_evt_ExpectAnyArgs(); + evt.evt.gattc_evt.params.char_disc_rsp.chars[0].uuid.uuid = + BLE_UUID_HEART_RATE_CONTROL_POINT_CHAR; + ble_evt_send(&evt); + TEST_ASSERT_NOT_EQUAL(BLE_DB_DISCOVERY_ERROR, db_evt.evt_type); + + __cmock_ble_gq_item_add_ExpectAnyArgsAndReturn(NRF_SUCCESS); + __cmock_ble_gq_on_ble_evt_ExpectAnyArgs(); + evt.evt.gattc_evt.gatt_status = BLE_GATT_STATUS_UNKNOWN; + ble_evt_send(&evt); + TEST_ASSERT_NOT_EQUAL(BLE_DB_DISCOVERY_ERROR, db_evt.evt_type); + + __cmock_ble_gq_item_add_ExpectAnyArgsAndReturn(NRF_SUCCESS); + __cmock_ble_gq_on_ble_evt_ExpectAnyArgs(); + evt.header.evt_id = BLE_GATTC_EVT_DESC_DISC_RSP; + evt.evt.gattc_evt.conn_handle = 8; + evt.evt.gattc_evt.gatt_status = BLE_GATT_STATUS_SUCCESS; + evt.evt.gattc_evt.params.desc_disc_rsp.count = 1; + evt.evt.gattc_evt.params.desc_disc_rsp.descs[0].handle = 8; + evt.evt.gattc_evt.params.desc_disc_rsp.descs[0].uuid.uuid = + BLE_UUID_DESCRIPTOR_CHAR_USER_DESC; + evt.evt.gattc_evt.params.desc_disc_rsp.descs[0].uuid.type = BLE_UUID_TYPE_BLE; + ble_evt_send(&evt); + TEST_ASSERT_NOT_EQUAL(BLE_DB_DISCOVERY_ERROR, db_evt.evt_type); + + __cmock_ble_gq_on_ble_evt_ExpectAnyArgs(); + evt.evt.gattc_evt.params.desc_disc_rsp.descs[0].uuid.uuid = + BLE_UUID_DESCRIPTOR_CHAR_EXT_PROP; + ble_evt_send(&evt); + TEST_ASSERT_NOT_EQUAL(BLE_DB_DISCOVERY_ERROR, db_evt.evt_type); + + __cmock_ble_gq_on_ble_evt_ExpectAnyArgs(); + evt.evt.gattc_evt.params.desc_disc_rsp.descs[0].uuid.uuid = + BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG; + ble_evt_send(&evt); + TEST_ASSERT_NOT_EQUAL(BLE_DB_DISCOVERY_ERROR, db_evt.evt_type); + + __cmock_ble_gq_on_ble_evt_ExpectAnyArgs(); + evt.evt.gattc_evt.params.desc_disc_rsp.descs[0].uuid.uuid = BLE_UUID_REPORT_REF_DESCR; + ble_evt_send(&evt); + TEST_ASSERT_NOT_EQUAL(BLE_DB_DISCOVERY_ERROR, db_evt.evt_type); + + __cmock_ble_gq_on_ble_evt_ExpectAnyArgs(); + evt.header.evt_id = BLE_GAP_EVT_DISCONNECTED; + evt.evt.gap_evt.params.disconnected.reason = BLE_HCI_CONNECTION_TIMEOUT; + ble_evt_send(&evt); + TEST_ASSERT_NOT_EQUAL(BLE_DB_DISCOVERY_ERROR, db_evt.evt_type); +} + +static uint32_t ble_gq_item_add_no_mem_stub(const struct ble_gq *gatt_queue, struct ble_gq_req *req, + uint16_t conn_handle, int cmock_num_calls) +{ + struct ble_gq_evt evt = {.evt_type = BLE_GQ_EVT_ERROR, .error.reason = NRF_ERROR_NO_MEM}; + req->evt_handler(req, &evt); + + return NRF_ERROR_NO_MEM; +} + +void test_ble_db_discovery_on_ble_evt_no_mem(void) +{ + struct ble_db_discovery_config config = { + .gatt_queue = &ble_gatt_queue, + .evt_handler = db_discovery_evt_handler, + }; + ble_evt_t evt; + + __cmock_ble_gq_conn_handle_register_ExpectAndReturn(&ble_gatt_queue, 4, NRF_SUCCESS); + __cmock_ble_gq_item_add_ExpectAnyArgsAndReturn(NRF_SUCCESS); + + TEST_ASSERT_EQUAL(NRF_SUCCESS, ble_db_discovery_init(&db_discovery, &config)); + + ble_uuid_t hrs_uuid = {.type = BLE_UUID_TYPE_BLE, .uuid = BLE_UUID_HEART_RATE_SERVICE}; + TEST_ASSERT_EQUAL(NRF_SUCCESS, ble_db_discovery_evt_register(&db_discovery, &hrs_uuid)); + + TEST_ASSERT_EQUAL(NRF_SUCCESS, ble_db_discovery_start(&db_discovery, 4)); + + __cmock_ble_gq_item_add_ExpectAnyArgsAndReturn(NRF_SUCCESS); + __cmock_ble_gq_item_add_StubWithCallback(ble_gq_item_add_no_mem_stub); + + __cmock_ble_gq_on_ble_evt_ExpectAnyArgs(); + evt.header.evt_id = BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP; + evt.evt.gattc_evt.conn_handle = 4; + evt.evt.gattc_evt.gatt_status = BLE_GATT_STATUS_SUCCESS; + evt.evt.gattc_evt.params.prim_srvc_disc_rsp.services[0].uuid.uuid = + BLE_UUID_HEART_RATE_SERVICE; + evt.evt.gattc_evt.params.prim_srvc_disc_rsp.services[0].uuid.type = BLE_UUID_TYPE_BLE; + evt.evt.gattc_evt.params.prim_srvc_disc_rsp.count = 1; + ble_evt_send(&evt); + TEST_ASSERT_EQUAL(BLE_DB_DISCOVERY_ERROR, db_evt_prev.evt_type); + TEST_ASSERT_EQUAL(NRF_ERROR_NO_MEM, db_evt_prev.params.error.reason); + + __cmock_ble_gq_on_ble_evt_ExpectAnyArgs(); + evt.header.evt_id = BLE_GATTC_EVT_CHAR_DISC_RSP; + evt.evt.gattc_evt.conn_handle = 8; + evt.evt.gattc_evt.gatt_status = BLE_GATT_STATUS_SUCCESS; + evt.evt.gattc_evt.params.char_disc_rsp.count = 1; + evt.evt.gattc_evt.params.char_disc_rsp.chars[0].uuid.uuid = BLE_UUID_BATTERY_LEVEL_CHAR; + evt.evt.gattc_evt.params.char_disc_rsp.chars[0].uuid.type = BLE_UUID_TYPE_BLE; + ble_evt_send(&evt); + TEST_ASSERT_EQUAL(BLE_DB_DISCOVERY_ERROR, db_evt_prev.evt_type); + TEST_ASSERT_EQUAL(NRF_ERROR_NO_MEM, db_evt_prev.params.error.reason); +} + +void setUp(void) +{ + memset(&db_discovery, 0, sizeof(db_discovery)); +} +void tearDown(void) +{ +} + +extern int unity_main(void); + +int main(void) +{ + return unity_main(); +} diff --git a/tests/lib/bluetooth/ble_db_discovery/testcase.yaml b/tests/lib/bluetooth/ble_db_discovery/testcase.yaml new file mode 100644 index 0000000000..f37b29eb5e --- /dev/null +++ b/tests/lib/bluetooth/ble_db_discovery/testcase.yaml @@ -0,0 +1,4 @@ +tests: + lib.ble_db_discovery: + platform_allow: native_sim + tags: unittest