diff --git a/doc/nrf-bm/api/api.rst b/doc/nrf-bm/api/api.rst index 27e92641dc..4f74c488ed 100644 --- a/doc/nrf-bm/api/api.rst +++ b/doc/nrf-bm/api/api.rst @@ -191,6 +191,15 @@ Battery Service :inner: :members: +.. _api_ble_bms: + +Bond Management Service +======================= + +.. doxygengroup:: ble_bms + :inner: + :members: + .. _api_ble_cgms: Continuous Glucose Monitoring Service diff --git a/doc/nrf-bm/libraries/bluetooth/services/ble_bms.rst b/doc/nrf-bm/libraries/bluetooth/services/ble_bms.rst new file mode 100644 index 0000000000..1fb51aec4b --- /dev/null +++ b/doc/nrf-bm/libraries/bluetooth/services/ble_bms.rst @@ -0,0 +1,73 @@ +.. _lib_ble_service_bms: + +Bond Management Service (BMS) +############################# + +.. contents:: + :local: + :depth: 2 + +Overview +******** + +This library implements the Bond Management Service with the corresponding set of characteristics defined in the `Bond Management Service Specification`_. + +You can configure the service to support your desired feature set of bond management operations. +All the BMS features in the "LE transport only" mode are supported: + + * Delete the bond of the requesting device. + * Delete all bonds on the server. + * Delete all bonds on the server except the one of the requesting device. + +You can enable each feature when initializing the library. + +Authorization +============= + +You can require authorization to access each BMS feature. + +When required, the client's request to execute a bond management operation must contain the authorization code. +The server compares the code with its local version and accepts the request only if the codes match. + +If you use at least one BMS feature that requires authorization, you need to provide a callback with comparison logic for the authorization codes. +You can set this callback when initializing the library. + +Deleting the bonds +================== + +The server deletes bonding information on client's request right away when there is no active Bluetooth® Low Energy connection associated with a bond. +Otherwise, the server removes the bond for a given peer when it disconnects. + +Configuration +************* + +Set the :kconfig:option:`CONFIG_BLE_BMS` Kconfig option to enable the service. + +Initialization +============== + +The service instance is declared using the :c:macro:`BLE_BMS_DEF` macro, specifying the name of the instance. +The service is initialized by calling the :c:func:`ble_bms_init` function. +See the :c:struct:`ble_bms_config` struct for configuration details, in addition to the BMS specification. + +Usage +***** + +Events from the service are forwarded through the event handler specified during initialization. +For a full list of events see the :c:enum:`ble_bms_evt_type` enum. + +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/bluetooth/services/ble_bms.h` +| Source files: :file:`subsys/bluetooth/services/ble_bms/` + +:ref:`Bond Management Service API reference ` diff --git a/include/bm/bluetooth/ble_qwr.h b/include/bm/bluetooth/ble_qwr.h index 249c65e906..c31ddcfce2 100644 --- a/include/bm/bluetooth/ble_qwr.h +++ b/include/bm/bluetooth/ble_qwr.h @@ -225,8 +225,7 @@ uint32_t ble_qwr_attr_register(struct ble_qwr *qwr, uint16_t attr_handle); * @retval NRF_ERROR_NULL If @p qwr, @p mem or @p len is @c NULL. * @retval NRF_ERROR_INVALID_STATE If the given @p qwr instance has not been initialized. */ -uint32_t ble_qwr_value_get( - struct ble_qwr *qwr, uint16_t attr_handle, uint8_t *mem, uint16_t *len); +uint32_t ble_qwr_value_get(struct ble_qwr *qwr, uint16_t attr_handle, uint8_t *mem, uint16_t *len); #endif /* (CONFIG_BLE_QWR_MAX_ATTR > 0) */ #ifdef __cplusplus diff --git a/include/bm/bluetooth/services/ble_bms.h b/include/bm/bluetooth/services/ble_bms.h new file mode 100644 index 0000000000..b4d51c4441 --- /dev/null +++ b/include/bm/bluetooth/services/ble_bms.h @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2012 - 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** @file + * + * @defgroup ble_bms Bond Management Service + * @{ + * + * @brief Bond Management Service (BMS) module. + * + * @details This module implements the Bond Management Service (BMS). + * By writing to the Bond Management Control Point, the connected peer can request the + * deletion of bond information from the device. + * If authorization is configured, the application must supply an event handler for + * receiving Bond Management Service events. Using this handler, the service requests + * authorization when a procedure is requested by writing to the + * Bond Management Control Point. + */ + +#ifndef NRFBM_BLE_BMS_H__ +#define NRFBM_BLE_BMS_H__ + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Macro for defining a ble_bms instance. + * + * @param _name Name of the instance. + * @hideinitializer + */ +#define BLE_BMS_DEF(_name) \ +static struct ble_bms _name; \ + NRF_SDH_BLE_OBSERVER(_name ## _obs, \ + ble_bms_on_ble_evt, &_name, \ + HIGH) + +/** Maximum length of the Bond Management Control Point Characteristic (in bytes). */ +#define BLE_BMS_CTRLPT_MAX_LEN 128 +/** Maximum length of the Bond Management Control Point Authorization Code (in bytes). */ +#define BLE_BMS_AUTH_CODE_MAX_LEN BLE_BMS_CTRLPT_MAX_LEN - 1 + +/** @defgroup BLE_BMS_FEATURES BMS feature bits + * @{ + */ +/** Delete bond of the requesting device (BR/EDR and LE). */ +#define BLE_BMS_REQUESTING_DEVICE_BR_LE (0x01 << 0) +/** Delete bond of the requesting device (BR/EDR and LE) with an authorization code. */ +#define BLE_BMS_REQUESTING_DEVICE_BR_LE_AUTH_CODE (0x01 << 1) +/** Delete bond of the requesting device (BR/EDR transport only). */ +#define BLE_BMS_REQUESTING_DEVICE_BR (0x01 << 2) +/** Delete bond of the requesting device (BR/EDR transport only) with an authorization code. */ +#define BLE_BMS_REQUESTING_DEVICE_BR_AUTH_CODE (0x01 << 3) +/** Delete bond of the requesting device (LE transport only). */ +#define BLE_BMS_REQUESTING_DEVICE_LE (0x01 << 4) +/** Delete bond of the requesting device (LE transport only) with an authorization code. */ +#define BLE_BMS_REQUESTING_DEVICE_LE_AUTH_CODE (0x01 << 5) +/** Delete all bonds on the device (BR/EDR and LE). */ +#define BLE_BMS_ALL_BONDS_BR_LE (0x01 << 6) +/** Delete all bonds on the device (BR/EDR and LE) with an authorization code. */ +#define BLE_BMS_ALL_BONDS_BR_LE_AUTH_CODE (0x01 << 7) +/** Delete all bonds on the device (BR/EDR transport only). */ +#define BLE_BMS_ALL_BONDS_BR (0x01 << 8) +/** Delete all bonds on the device (BR/EDR transport only) with an authorization code. */ +#define BLE_BMS_ALL_BONDS_BR_AUTH_CODE (0x01 << 9) +/** Delete all bonds on the device (LE transport only). */ +#define BLE_BMS_ALL_BONDS_LE (0x01 << 10) +/** Delete all bonds on the device (LE transport only) with an authorization code. */ +#define BLE_BMS_ALL_BONDS_LE_AUTH_CODE (0x01 << 11) +/** Delete all bonds on the device except for the bond of the requesting device + * (BR/EDR and LE). + */ +#define BLE_BMS_ALL_EXCEPT_REQUESTING_DEVICE_BR_LE (0x01 << 12) +/** Delete all bonds on the device except for the bond of the requesting device + * (BR/EDR and LE) with an authorization code. + */ +#define BLE_BMS_ALL_EXCEPT_REQUESTING_DEVICE_BR_LE_AUTH_CODE (0x01 << 13) +/** Delete all bonds on the device except for the bond of the requesting device + * (BR/EDR transport only). + */ +#define BLE_BMS_ALL_EXCEPT_REQUESTING_DEVICE_BR (0x01 << 14) +/** Delete all bonds on the device except for the bond of the requesting device + * (BR/EDR transport only) with an authorization code. + */ +#define BLE_BMS_ALL_EXCEPT_REQUESTING_DEVICE_BR_AUTH_CODE (0x01 << 15) +/** Delete all bonds on the device except for the bond of the requesting device + * (LE transport only). + */ +#define BLE_BMS_ALL_EXCEPT_REQUESTING_DEVICE_LE (0x01 << 16) +/** Delete all bonds on the device except for the bond of the requesting device + * (LE transport only) with an authorization code. + */ +#define BLE_BMS_ALL_EXCEPT_REQUESTING_DEVICE_LE_AUTH_CODE (0x01 << 17) +/** @} */ + +/** Error sent back when receiving a control point write with an unsupported opcode. */ +#define BLE_BMS_OPCODE_NOT_SUPPORTED 0x80 +/** Error sent back when a control point operation fails. */ +#define BLE_BMS_OPERATION_FAILED 0x81 + +/** @brief Supported features. */ +struct ble_bms_features { + /** Indicates whether the application wants to support the operation to delete all bonds. */ + bool delete_all : 1; + /** Indicates whether the application wants to support the operation to delete all bonds + * with authorization code. + */ + bool delete_all_auth : 1; + /** Indicates whether the application wants to support the operation to delete the bonds of + * the requesting device. + */ + bool delete_requesting : 1; + /** Indicates whether the application wants to support the operation to delete the bonds of + * therequesting device with authorization code. + */ + bool delete_requesting_auth : 1; + /** Indicates whether the application wants to support the operation to delete all bonds + * except for the bond of the requesting device. + */ + bool delete_all_but_requesting : 1; + /** Indicates whether the application wants to support the operation to delete all bonds + * except for the bond of the requesting device with authorization code. + */ + bool delete_all_but_requesting_auth : 1; +}; + +/** @brief BMS Control Point opcodes. */ +enum ble_bms_op { + /** Initiates the procedure to delete the bond of the requesting device on + * BR/EDR and LE transports. + */ + BLE_BMS_OP_DEL_BOND_REQ_DEVICE_BR_LE = 0x01, + /** Initiates the procedure to delete the bond of the requesting device on BR/EDR transport. + */ + BLE_BMS_OP_DEL_BOND_REQ_DEVICE_BR_ONLY = 0x02, + /** Initiates the procedure to delete the bond of the requesting device on LE transport. */ + BLE_BMS_OP_DEL_BOND_REQ_DEVICE_LE_ONLY = 0x03, + /** Initiates the procedure to delete all bonds on the device on BR/EDR and LE transports. + */ + BLE_BMS_OP_DEL_ALL_BONDS_ON_SERVER_BR_LE = 0x04, + /** Initiates the procedure to delete all bonds on the device on BR/EDR transport. */ + BLE_BMS_OP_DEL_ALL_BONDS_ON_SERVER_BR_ONLY = 0x05, + /** Initiates the procedure to delete all bonds on the device on LE transport. */ + BLE_BMS_OP_DEL_ALL_BONDS_ON_SERVER_LE_ONLY = 0x06, + /** Initiates the procedure to delete all bonds except for the one of the requesting device + * on BR/EDR and LE transports. + */ + BLE_BMS_OP_DEL_ALL_BUT_ACTIVE_BOND_BR_LE = 0x07, + /** Initiates the procedure to delete all bonds except for the one of the requesting device + * on BR/EDR transport. + */ + BLE_BMS_OP_DEL_ALL_BUT_ACTIVE_BOND_BR_ONLY = 0x08, + /** Initiates the procedure to delete all bonds except for the one of the requesting device + * on LE transport. + */ + BLE_BMS_OP_DEL_ALL_BUT_ACTIVE_BOND_LE_ONLY = 0x09, + /** Indicates an invalid opcode or no pending opcode. */ + BLE_BMS_OP_NONE = 0xFF +}; + +/** @brief Authorization status values. */ +enum ble_bms_auth_status { + /** Authorization is granted. */ + BLE_BMS_AUTH_STATUS_ALLOWED, + /** Authorization is denied. */ + BLE_BMS_AUTH_STATUS_DENIED, + /** Authorization is pending. */ + BLE_BMS_AUTH_STATUS_PENDING +}; + +/** @brief Received authorization codes. */ +struct ble_bms_auth_code { + /** Authorization code. */ + uint8_t code[BLE_BMS_AUTH_CODE_MAX_LEN]; + /** Length of the authorization code. */ + uint16_t len; +}; + +/** @brief BMS event types. */ +enum ble_bms_evt_type { + /** Error event. */ + BLE_BMS_EVT_ERROR, + /** Event that indicates that the application shall verify the supplied + * authentication code. + */ + BLE_BMS_EVT_AUTH, + /** Request to delete the bond of the requesting device. */ + BLE_BMS_EVT_BOND_DELETE_REQUESTING, + /** Request to delete all bonds. */ + BLE_BMS_EVT_BOND_DELETE_ALL, + /** Request to delete all bonds except for the requesting device. */ + BLE_BMS_EVT_BOND_DELETE_ALL_EXCEPT_REQUESTING, +}; + +/** @brief BMS events. */ +struct ble_bms_evt { + /** Type of event. */ + enum ble_bms_evt_type evt_type; + union { + /** @ref BLE_BMS_EVT_ERROR event data. */ + struct { + /* Error reason */ + uint32_t reason; + } error; + /** @ref BLE_BMS_EVT_AUTH event data. */ + struct { + /** Received authorization code. */ + struct ble_bms_auth_code auth_code; + } auth; + }; +}; + +/** @brief BMS control points. */ +struct ble_bms_ctrlpt { + /** Control Point Op Code. */ + enum ble_bms_op op_code; + /** Control Point Authorization Code. */ + struct ble_bms_auth_code auth_code; +}; + +/* Forward declaration of the struct ble_bms. */ +struct ble_bms; + +/** @brief BMS event handler type. + * + * The event handler returns a @ref BLE_GATT_STATUS_CODES "BLE GATT status code". + */ +typedef void (*ble_bms_evt_handler_t)(struct ble_bms *bms, struct ble_bms_evt *evt); + +/** @brief BMS initialization structure with all information needed to initialize the service. */ +struct ble_bms_config { + /** Event handler to be called for handling events in the Bond Management Service. */ + ble_bms_evt_handler_t evt_handler; + /** Initial value for features of the service. */ + struct ble_bms_features feature; + /** Initial security level for the Feature characteristic. */ + ble_gap_conn_sec_mode_t feature_sec; + /** Initial security level for the Control Point characteristic. */ + ble_gap_conn_sec_mode_t ctrlpt_sec; + /** Pointer to the initialized Queued Write contexts. */ + struct ble_qwr *qwr; + /** Initialized Queue Write contexts count. */ + uint8_t qwr_count; +}; + +/**@brief Status information for the service. */ +struct ble_bms { + /** Handle of the Bond Management Service (as provided by the BLE stack). */ + uint16_t service_handle; + /** Handle of the current connection (as provided by the BLE stack). + * @ref BLE_CONN_HANDLE_INVALID if not in a connection. + */ + uint16_t conn_handle; + /** Event handler to be called for handling events in the Bond Management Service. */ + ble_bms_evt_handler_t evt_handler; + /** Value for features of the service (see @ref BLE_BMS_FEATURES). */ + struct ble_bms_features feature; + /** Handles related to the Bond Management Feature characteristic. */ + ble_gatts_char_handles_t feature_handles; + /** Handles related to the Bond Management Control Point characteristic. */ + ble_gatts_char_handles_t ctrlpt_handles; + /** Authorization status. */ + enum ble_bms_auth_status auth_status; +}; + +/** + * @brief Respond to an authorization request. + * + * @details Call this function when receiving the @ref BLE_BMS_EVT_AUTH event to + * respond to the service with an authorization result. + * + * @param[in] bms BMS structure. + * @param[in] authorize Authorization response. True if the authorization is considered successful. + * + * @retval NRF_ERROR_NULL If @p bms was NULL. + * @retval NRF_ERROR_INVALID_STATE If no authorization request was pending. + * @retval NRF_SUCCESS If the response was received successfully. + */ +uint32_t ble_bms_auth_response(struct ble_bms *bms, bool authorize); + +/** + * @brief Initialize the Bond Management Service. + * + * @param[out] bms BMS structure. + * @param[in] bms_config Information needed to initialize the service. + * + * @retval NRF_ERROR_NULL If @p bms or @p bms_config was NULL. + * @retval NRF_SUCCESS If the service was initialized successfully. + * Otherwise, an error code is returned. + */ +uint32_t ble_bms_init(struct ble_bms *bms, struct ble_bms_config *bms_config); + +/** + * @brief Handle Bond Management BLE stack events. + * + * @details This function handles all events from the BLE stack that are of interest to the + * Bond Management Service. + * + * @param[in] ble_evt Event received from the BLE stack. + * @param[in] context BMS structure. + */ +void ble_bms_on_ble_evt(ble_evt_t const *ble_evt, void *context); + +/** + * @brief Handle events from the @ref nrf_ble_qwr. + * + * @param[in] bms BMS structure. + * @param[in] qwr Queued Write structure. + * @param[in] evt Event received from the Queued Writes module. + * + * @retval BLE_GATT_STATUS_SUCCESS If the received event is accepted. + * @retval NRF_BLE_QWR_REJ_REQUEST_ERR_CODE If the received event is not relevant for any of this + * module's attributes. + * @retval BLE_BMS_OPCODE_NOT_SUPPORTED If the received opcode is not supported. + * @retval BLE_GATT_STATUS_ATTERR_INSUF_AUTHORIZATION If the application handler returns that the + * authorization code is not valid. + */ +uint16_t ble_bms_on_qwr_evt(struct ble_bms *bms, struct ble_qwr *qwr, + const struct ble_qwr_evt *evt); + +#ifdef __cplusplus +} +#endif + +#endif /* NRFBM_BLE_BMS_H__ */ + +/** @} */ diff --git a/samples/bluetooth/ble_bms/CMakeLists.txt b/samples/bluetooth/ble_bms/CMakeLists.txt new file mode 100644 index 0000000000..e8734b8bcd --- /dev/null +++ b/samples/bluetooth/ble_bms/CMakeLists.txt @@ -0,0 +1,12 @@ +# +# 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(ble_bms) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/bluetooth/ble_bms/Kconfig b/samples/bluetooth/ble_bms/Kconfig new file mode 100644 index 0000000000..d505963cf0 --- /dev/null +++ b/samples/bluetooth/ble_bms/Kconfig @@ -0,0 +1,23 @@ +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menu "BLE BMS sample" + +config APP_QWR_MEM_BUFF_SIZE + int "QWR memory buffer size" + default 512 + +config APP_BLE_BMS_PEERS_TO_DELETE_ON_DISCONNECT_MAX + int "Maximum number of peers to delete upon a peer disconnect" + default 10 + +module=APP_BLE_BMS +module-str=BLE Bond Management Service Sample +source "$(ZEPHYR_BASE)/subsys/logging/Kconfig.template.log_config" + +endmenu # "BLE BMS sample" + +source "Kconfig.zephyr" diff --git a/samples/bluetooth/ble_bms/README.rst b/samples/bluetooth/ble_bms/README.rst new file mode 100644 index 0000000000..f4e21fb2a3 --- /dev/null +++ b/samples/bluetooth/ble_bms/README.rst @@ -0,0 +1,101 @@ +.. _ble_bms_sample: + +Bluetooth: Bond Management Service (BMS) +######################################## + +.. contents:: + :local: + :depth: 2 + +This sample demonstrates how to use the Bond Management Service (BMS). + +Requirements +************ + +The sample supports the following development kits: + +.. list-table:: + :header-rows: 1 + + * - Hardware platform + - PCA + - SoftDevice + - Board target + * - `nRF54L15 DK`_ + - PCA10156 + - S115 + - bm_nrf54l15dk/nrf54l15/cpuapp/s115_softdevice + * - `nRF54L15 DK`_ (emulating nRF54L10) + - PCA10156 + - S115 + - bm_nrf54l15dk/nrf54l10/cpuapp/s115_softdevice + * - `nRF54L15 DK`_ (emulating nRF54L05) + - PCA10156 + - S115 + - bm_nrf54l15dk/nrf54l05/cpuapp/s115_softdevice + +Overview +******** + +When connected, the sample waits for the client's requests to perform any bond-deleting operation. + +User interface +************** + +LED 0: + Lit when the device is advertising. + +LED 1: + Lit when a device is connected. + +Programming the S115 SoftDevice +******************************* + +.. include:: /includes/softdevice_flash.txt + +.. _ble_bms_sample_testing: + +Building and running +******************** + +This sample can be found under :file:`samples/bluetooth/ble_bms/` in the |BMshort| folder structure. + +.. include:: /includes/create_sample.txt + +.. include:: /includes/configure_and_build_sample.txt + +.. include:: /includes/program_sample.txt + +Testing +======= + +1. Compile and program the application. +#. In the Serial Terminal, observe that the ``BLE BMS sample started`` message is printed. +#. Observe that the ``Advertising as nRF_BM_BMS`` message is printed. +#. Bind with the device: + + a. Click the :guilabel:`Settings` button for the device in the app. + #. Select :guilabel:`Pair`. + #. Select :guilabel:`Keyboard and display` in the IO capabilities setting. + #. Select :guilabel:`Perform Bonding`. + #. Click :guilabel:`Pair`. + +#. Check the logs to verify that the connection security is updated. +#. Disconnect the device in the app. +#. Reconnect again and verify that the connection security is updated automatically. +#. Verify that the Feature Characteristic of the Bond Management Service displays ``10 08 02``. + This means that the following features are supported: + + * Deletion of the bonds for the current connection of the requesting device. + * Deletion of all bonds on the Server with the Authorization Code. + * Deletion of all bonds on the Server except the ones of the requesting device with the Authorization Code. + +#. Write ``03`` to the Bond Management Service Control Point Characteristic. + ``03`` is the command to delete the current bond. +#. Disconnect the device to trigger the bond deletion procedures. +#. Reconnect the devices and verify that the connection security is not updated. +#. Bond both devices again. +#. Write ``06 41 42 43 44`` to the Bond Management Service Control Point Characteristic. + ``06`` is the command to delete all bonds on the server, followed by the authorization code ``ABCD``. +#. Disconnect the device to trigger the bond deletion procedures. +#. Reconnect the devices again and verify that the connection security is not updated. diff --git a/samples/bluetooth/ble_bms/prj.conf b/samples/bluetooth/ble_bms/prj.conf new file mode 100644 index 0000000000..c4926fa9db --- /dev/null +++ b/samples/bluetooth/ble_bms/prj.conf @@ -0,0 +1,50 @@ +CONFIG_LOG=y +CONFIG_LOG_BACKEND_BM_UARTE=y + +CONFIG_SOFTDEVICE=y +CONFIG_NRF_SDH=y + +# Enable RNG +CONFIG_NRF_SECURITY=y +CONFIG_MBEDTLS_PSA_CRYPTO_C=y +CONFIG_PSA_WANT_GENERATE_RANDOM=y + +# Enable Crypto functionality required by LE Secure Connection +CONFIG_PSA_WANT_ALG_ECDH=y +CONFIG_PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_GENERATE=y +CONFIG_PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT=y +CONFIG_PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_EXPORT=y +CONFIG_PSA_WANT_ECC_SECP_R1_256=y +CONFIG_MBEDTLS_PSA_STATIC_KEY_SLOTS=y +CONFIG_MBEDTLS_PSA_KEY_SLOT_COUNT=1 +CONFIG_MBEDTLS_PSA_STATIC_KEY_SLOT_BUFFER_SIZE=65 + +CONFIG_BLE_ADV=y +CONFIG_BLE_ADV_NAME="nRF_BM_BMS" +CONFIG_BLE_ADV_EXTENDED_ADVERTISING=n +CONFIG_BLE_ADV_DIRECTED_ADVERTISING=n + +# BLE connection parameter +CONFIG_BLE_CONN_PARAMS=y + +# Device information service +CONFIG_BLE_DIS=y +CONFIG_BLE_DIS_SERIAL_NUMBER="ABCD" +CONFIG_BLE_DIS_HW_REVISION="hw 54.15.0" +CONFIG_BLE_DIS_FW_REVISION="fw 17.2.0" +CONFIG_BLE_DIS_SW_REVISION="sw 1.0.0" + +# Nordic LED button service +CONFIG_BLE_QWR=y +CONFIG_BLE_QWR_MAX_ATTR=1 +CONFIG_BLE_BMS=y + +# Buttons and timer +CONFIG_BM_BUTTONS=y +CONFIG_BM_TIMER=y + +# Peer Manager +CONFIG_PEER_MANAGER=y +CONFIG_PM_LESC=y +CONFIG_BLE_CONN_STATE=y +CONFIG_BM_ZMS=y diff --git a/samples/bluetooth/ble_bms/sample.yaml b/samples/bluetooth/ble_bms/sample.yaml new file mode 100644 index 0000000000..8a969cecc3 --- /dev/null +++ b/samples/bluetooth/ble_bms/sample.yaml @@ -0,0 +1,21 @@ +sample: + name: Bluetooth LE Bond Management Service Sample +tests: + sample.ble_bms_service: + sysbuild: true + build_only: true + integration_platforms: + - bm_nrf54l15dk/nrf54l05/cpuapp/s115_softdevice + - bm_nrf54l15dk/nrf54l10/cpuapp/s115_softdevice + - bm_nrf54l15dk/nrf54l15/cpuapp/s115_softdevice + - bm_nrf54l15dk/nrf54l05/cpuapp/s115_softdevice/mcuboot + - bm_nrf54l15dk/nrf54l10/cpuapp/s115_softdevice/mcuboot + - bm_nrf54l15dk/nrf54l15/cpuapp/s115_softdevice/mcuboot + platform_allow: + - bm_nrf54l15dk/nrf54l05/cpuapp/s115_softdevice + - bm_nrf54l15dk/nrf54l10/cpuapp/s115_softdevice + - bm_nrf54l15dk/nrf54l15/cpuapp/s115_softdevice + - bm_nrf54l15dk/nrf54l05/cpuapp/s115_softdevice/mcuboot + - bm_nrf54l15dk/nrf54l10/cpuapp/s115_softdevice/mcuboot + - bm_nrf54l15dk/nrf54l15/cpuapp/s115_softdevice/mcuboot + tags: ci_build diff --git a/samples/bluetooth/ble_bms/src/main.c b/samples/bluetooth/ble_bms/src/main.c new file mode 100644 index 0000000000..21bb0ee71f --- /dev/null +++ b/samples/bluetooth/ble_bms/src/main.c @@ -0,0 +1,740 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* FIFO for keeping track of peers that cannot be deleted immediately. */ +RING_BUF_DECLARE(peers_to_delete_on_disconnect, + CONFIG_APP_BLE_BMS_PEERS_TO_DELETE_ON_DISCONNECT_MAX * sizeof(uint16_t)); + +LOG_MODULE_REGISTER(app, CONFIG_APP_BLE_BMS_LOG_LEVEL); + +/* Perform bonding. */ +#define SEC_PARAM_BOND 1 +/* Man In The Middle protection not required. */ +#define SEC_PARAM_MITM 0 +/* LE Secure Connections enabled. */ +#define SEC_PARAM_LESC 1 +/* Keypress notifications not enabled. */ +#define SEC_PARAM_KEYPRESS 1 +/* No I/O capabilities. */ +#define SEC_PARAM_IO_CAPABILITIES BLE_GAP_IO_CAPS_DISPLAY_YESNO +/* Out Of Band data not available. */ +#define SEC_PARAM_OOB 0 +/* Minimum encryption key size. */ +#define SEC_PARAM_MIN_KEY_SIZE 7 +/* Maximum encryption key size. */ +#define SEC_PARAM_MAX_KEY_SIZE 16 + +/* BLE Advertising library instance */ +BLE_ADV_DEF(ble_adv); +/* BLE QWR instance */ +BLE_QWR_DEF(ble_qwr); +/* BLE Bond Management Service instance */ +BLE_BMS_DEF(ble_bms); + +/* BLE Connection handle */ +static uint16_t conn_handle = BLE_CONN_HANDLE_INVALID; +/* Peer ID */ +static uint16_t peer_id; +/* Flag for ongoing authentication request */ +static bool auth_key_request; + +/* Write buffer for the Queued Write module. */ +static uint8_t qwr_mem[CONFIG_APP_QWR_MEM_BUFF_SIZE]; + +/* Forward declaration */ +static void identities_set(enum pm_peer_id_list_skip skip); + +static void delete_disconnected_bonds(void); + +static void on_ble_evt(const ble_evt_t *evt, void *ctx) +{ + uint32_t nrf_err; + + switch (evt->header.evt_id) { + case BLE_GAP_EVT_CONNECTED: + LOG_INF("Peer connected, conn handle %d", evt->evt.gap_evt.conn_handle); + conn_handle = evt->evt.gap_evt.conn_handle; + + nrf_err = ble_qwr_conn_handle_assign(&ble_qwr, conn_handle); + if (nrf_err) { + LOG_ERR("Failed to assign qwr handle, nrf_error %#x", nrf_err); + return; + } + nrf_gpio_pin_write(BOARD_PIN_LED_0, !BOARD_LED_ACTIVE_STATE); + nrf_gpio_pin_write(BOARD_PIN_LED_1, BOARD_LED_ACTIVE_STATE); + break; + + case BLE_GAP_EVT_DISCONNECTED: + LOG_INF("Peer disconnected, reason %#x", + evt->evt.gap_evt.params.disconnected.reason); + + if (conn_handle == evt->evt.gap_evt.conn_handle) { + conn_handle = BLE_CONN_HANDLE_INVALID; + } + delete_disconnected_bonds(); + nrf_gpio_pin_write(BOARD_PIN_LED_1, !BOARD_LED_ACTIVE_STATE); + break; + + case BLE_GAP_EVT_PASSKEY_DISPLAY: + LOG_INF("Passkey: %.*s", BLE_GAP_PASSKEY_LEN, + evt->evt.gap_evt.params.passkey_display.passkey); + if (evt->evt.gap_evt.params.passkey_display.match_request) { + LOG_INF("Pairing request, press button 0 to accept or button 1 to reject."); + auth_key_request = true; + } + break; + + case BLE_GAP_EVT_AUTH_KEY_REQUEST: + LOG_INF("Pairing request, press button 0 to accept or button 1 to reject."); + auth_key_request = true; + break; + + default: + break; + } +} +NRF_SDH_BLE_OBSERVER(sdh_ble, on_ble_evt, NULL, USER_LOW); + +static void ble_adv_evt_handler(struct ble_adv *ble_adv, const struct ble_adv_evt *evt) +{ + uint32_t nrf_err; + ble_gap_addr_t *peer_addr; + ble_gap_addr_t allow_list_addrs[BLE_GAP_WHITELIST_ADDR_MAX_COUNT]; + ble_gap_irk_t allow_list_irks[BLE_GAP_WHITELIST_ADDR_MAX_COUNT]; + uint32_t addr_cnt = BLE_GAP_WHITELIST_ADDR_MAX_COUNT; + uint32_t irk_cnt = BLE_GAP_WHITELIST_ADDR_MAX_COUNT; + + switch (evt->evt_type) { + case BLE_ADV_EVT_ERROR: + LOG_ERR("Advertising failure, nrf_error %#x", evt->error.reason); + break; + case BLE_ADV_EVT_DIRECTED_HIGH_DUTY: + case BLE_ADV_EVT_DIRECTED: + case BLE_ADV_EVT_FAST: + case BLE_ADV_EVT_SLOW: + case BLE_ADV_EVT_FAST_ALLOW_LIST: + case BLE_ADV_EVT_SLOW_ALLOW_LIST: + nrf_gpio_pin_write(BOARD_PIN_LED_0, BOARD_LED_ACTIVE_STATE); + break; + case BLE_ADV_EVT_IDLE: + nrf_gpio_pin_write(BOARD_PIN_LED_0, !BOARD_LED_ACTIVE_STATE); + break; + case BLE_ADV_EVT_ALLOW_LIST_REQUEST: + nrf_err = pm_allow_list_get(allow_list_addrs, &addr_cnt, + allow_list_irks, &irk_cnt); + if (nrf_err) { + LOG_ERR("Failed to get allow list, nrf_error %#x", nrf_err); + } + LOG_DBG("pm_allow_list_get returns %d addr in allow list and %d irk allow list", + addr_cnt, irk_cnt); + + /* Set the correct identities list + * (no excluding peers with no Central Address Resolution). + */ + identities_set(PM_PEER_ID_LIST_SKIP_NO_IRK); + + nrf_err = ble_adv_allow_list_reply(ble_adv, allow_list_addrs, addr_cnt, + allow_list_irks, irk_cnt); + if (nrf_err) { + LOG_ERR("Failed to set allow_list, nrf_error %#x", nrf_err); + } + break; + + case BLE_ADV_EVT_PEER_ADDR_REQUEST: + struct pm_peer_data_bonding peer_bonding_data; + + /* Only Give peer address if we have a handle to the bonded peer. */ + if (peer_id != PM_PEER_ID_INVALID) { + nrf_err = pm_peer_data_bonding_load(peer_id, &peer_bonding_data); + if (nrf_err != NRF_ERROR_NOT_FOUND) { + if (nrf_err) { + LOG_ERR("Failed to load bonding data, nrf_error %#x", + nrf_err); + } + + /* Manipulate identities to exclude peers with no + * Central Address Resolution. + */ + identities_set(PM_PEER_ID_LIST_SKIP_ALL); + + peer_addr = &(peer_bonding_data.peer_ble_id.id_addr_info); + nrf_err = ble_adv_peer_addr_reply(ble_adv, peer_addr); + if (nrf_err) { + LOG_ERR("Failed to reply peer address, nrf_error %#x", + nrf_err); + } + } + } + break; + default: + break; + } +} + +static void num_comp_reply(uint16_t conn_handle, bool accept) +{ + uint8_t key_type; + uint32_t nrf_err; + + if (accept) { + LOG_INF("Numeric Match. Conn handle: %d", conn_handle); + key_type = BLE_GAP_AUTH_KEY_TYPE_PASSKEY; + } else { + LOG_INF("Numeric REJECT. Conn handle: %d", conn_handle); + key_type = BLE_GAP_AUTH_KEY_TYPE_NONE; + } + + nrf_err = sd_ble_gap_auth_key_reply(conn_handle, key_type, NULL); + if (nrf_err) { + LOG_ERR("Failed to reply auth request, nrf_error %#x", nrf_err); + } +} + +static void button_handler(uint8_t pin, uint8_t action) +{ + if (conn_handle == BLE_CONN_HANDLE_INVALID) { + return; + } + + if (auth_key_request) { + switch (pin) { + case BOARD_PIN_BTN_0: + if (action == BM_BUTTONS_PRESS) { + num_comp_reply(conn_handle, true); + } else { + auth_key_request = false; + } + break; + case BOARD_PIN_BTN_1: + if (action == BM_BUTTONS_PRESS) { + num_comp_reply(conn_handle, false); + } else { + auth_key_request = false; + } + break; + } + return; + } +} + +static void allow_list_set(enum pm_peer_id_list_skip skip) +{ + uint32_t nrf_err; + uint16_t peer_ids[BLE_GAP_WHITELIST_ADDR_MAX_COUNT]; + uint32_t peer_id_count = BLE_GAP_WHITELIST_ADDR_MAX_COUNT; + + nrf_err = pm_peer_id_list(peer_ids, &peer_id_count, PM_PEER_ID_INVALID, skip); + if (nrf_err) { + LOG_ERR("Failed to get peer id list, nrf_error %#x", nrf_err); + } + + LOG_INF("allow-listed peers: %d, max %d", + peer_id_count, BLE_GAP_WHITELIST_ADDR_MAX_COUNT); + + nrf_err = pm_allow_list_set(peer_ids, peer_id_count); + if (nrf_err) { + LOG_ERR("Failed to set allow list, nrf_error %#x", nrf_err); + } +} + +static void identities_set(enum pm_peer_id_list_skip skip) +{ + uint32_t nrf_err; + uint16_t peer_ids[BLE_GAP_DEVICE_IDENTITIES_MAX_COUNT]; + uint32_t peer_id_count = BLE_GAP_DEVICE_IDENTITIES_MAX_COUNT; + + nrf_err = pm_peer_id_list(peer_ids, &peer_id_count, PM_PEER_ID_INVALID, skip); + if (nrf_err) { + LOG_ERR("Failed to get peer id list, nrf_error %#x", nrf_err); + } + + nrf_err = pm_device_identities_list_set(peer_ids, peer_id_count); + if (nrf_err) { + LOG_ERR("Failed to set peer manager identity list, nrf_error %#x", nrf_err); + } +} + +static void delete_bonds(void) +{ + uint32_t nrf_err; + + LOG_INF("Erasing bonds"); + + nrf_err = pm_peers_delete(); + if (nrf_err) { + LOG_ERR("Failed to delete peers, nrf_error %#x", nrf_err); + } +} + +static void bond_delete(uint16_t peer_id, void *ctx) +{ + uint32_t nrf_err; + uint16_t peer_conn_handle; + + LOG_DBG("Attempting to delete bond."); + if (peer_id != PM_PEER_ID_INVALID) { + + nrf_err = pm_conn_handle_get(peer_id, &peer_conn_handle); + if (nrf_err) { + LOG_ERR("Failed to get connection handle for peer %d, nrf_error %#x", + peer_id, nrf_err); + } + + if (peer_conn_handle == conn_handle) { + NRFX_CRITICAL_SECTION_ENTER(); + (void)ring_buf_put(&peers_to_delete_on_disconnect, + (void *)&peer_id, sizeof(uint16_t)); + NRFX_CRITICAL_SECTION_EXIT(); + return; + } + + nrf_err = pm_peer_delete(peer_id); + if (nrf_err) { + LOG_ERR("Failed to delete peer, nrf_error %#x", nrf_err); + } + } +} + +static void delete_disconnected_bonds(void) +{ + uint16_t peer_id; + uint32_t nrf_err; + bool peer_to_delete; + + while (!ring_buf_is_empty(&peers_to_delete_on_disconnect)) { + NRFX_CRITICAL_SECTION_ENTER(); + if (!ring_buf_is_empty(&peers_to_delete_on_disconnect)) { + (void)ring_buf_get(&peers_to_delete_on_disconnect, + (uint8_t *)&peer_id, sizeof(uint16_t)); + peer_to_delete = true; + } + NRFX_CRITICAL_SECTION_EXIT(); + + if (!peer_to_delete) { + return; + } + + LOG_INF("delete bond, peer id %d", peer_id); + nrf_err = pm_peer_delete(peer_id); + if (nrf_err) { + LOG_ERR("Failed to delete peer, nrf_error %#x", nrf_err); + } + } +} + +static void delete_requesting_bond(const struct ble_bms *bms) +{ + uint32_t nrf_err; + uint16_t peer_id; + + LOG_INF("Client requested that bond to current device deleted"); + nrf_err = pm_peer_id_get(bms->conn_handle, &peer_id); + if (nrf_err) { + LOG_ERR("Failed to get peer id, nrf_error %#x", nrf_err); + return; + } + + LOG_INF("Adding peer id %d to list to delete", peer_id); + + NRFX_CRITICAL_SECTION_ENTER(); + (void)ring_buf_put(&peers_to_delete_on_disconnect, (void *)&peer_id, sizeof(uint16_t)); + NRFX_CRITICAL_SECTION_EXIT(); +} + +static void delete_all_bonds(const struct ble_bms *bms) +{ + uint16_t peer_id; + + LOG_INF("Client requested that all bonds be deleted"); + + peer_id = pm_next_peer_id_get(PM_PEER_ID_INVALID); + while (peer_id != PM_PEER_ID_INVALID) { + bond_delete(peer_id, NULL); + + peer_id = pm_next_peer_id_get(peer_id); + } +} + +static void delete_all_except_requesting_bond(const struct ble_bms *bms) +{ + uint32_t nrf_err; + uint16_t peer_conn_handle; + uint16_t peer_id; + + LOG_INF("Client requested that all bonds except current bond be deleted"); + + peer_id = pm_next_peer_id_get(PM_PEER_ID_INVALID); + while (peer_id != PM_PEER_ID_INVALID) { + nrf_err = pm_conn_handle_get(peer_id, &peer_conn_handle); + if (nrf_err) { + LOG_ERR("Failed to get connection handle, nrf_error %#x", nrf_err); + } + + /* Do nothing if this is our own bond. */ + if (peer_conn_handle != bms->conn_handle) { + if (peer_conn_handle == conn_handle) { + NRFX_CRITICAL_SECTION_ENTER(); + (void)ring_buf_put(&peers_to_delete_on_disconnect, + (void *)&peer_id, sizeof(uint16_t)); + NRFX_CRITICAL_SECTION_EXIT(); + } else { + bond_delete(peer_id, NULL); + } + + } + + peer_id = pm_next_peer_id_get(peer_id); + } +} + +void bms_evt_handler(struct ble_bms *bms, struct ble_bms_evt *evt) +{ + uint32_t nrf_err; + bool is_authorized = true; + + switch (evt->evt_type) { + case BLE_BMS_EVT_ERROR: + LOG_ERR("BMS error event, nrf_error %#x", evt->error.reason); + break; + case BLE_BMS_EVT_AUTH: + LOG_DBG("Authorization request."); +#if defined(CONFIG_BLE_BMS_USE_AUTHORIZATION_CODE) + if ((evt->auth.auth_code.len != strlen(CONFIG_BLE_BMS_AUTHORIZATION_CODE)) || + (memcmp(CONFIG_BLE_BMS_AUTHORIZATION_CODE, evt->auth.auth_code.code, + strlen(CONFIG_BLE_BMS_AUTHORIZATION_CODE)) != 0)) { + is_authorized = false; + } +#endif + nrf_err = ble_bms_auth_response(&ble_bms, is_authorized); + if (nrf_err) { + LOG_ERR("BMS auth response failed, nrf_error %#x", nrf_err); + } + break; + case BLE_BMS_EVT_BOND_DELETE_REQUESTING: + delete_requesting_bond(bms); + break; + case BLE_BMS_EVT_BOND_DELETE_ALL: + delete_all_bonds(bms); + break; + case BLE_BMS_EVT_BOND_DELETE_ALL_EXCEPT_REQUESTING: + delete_all_except_requesting_bond(bms); + break; + } +} + +static int advertising_start(bool erase_bonds) +{ + int nrf_err = 0; + + if (erase_bonds) { + delete_bonds(); + } else { + allow_list_set(PM_PEER_ID_LIST_SKIP_NO_ID_ADDR); + + nrf_err = ble_adv_start(&ble_adv, BLE_ADV_MODE_FAST); + if (nrf_err) { + LOG_ERR("Failed to start advertising, nrf_error %x", nrf_err); + } + } + + return nrf_err; +} + +static void pm_evt_handler(struct pm_evt const *evt) +{ + pm_handler_on_pm_evt(evt); + pm_handler_disconnect_on_sec_failure(evt); + pm_handler_flash_clean(evt); + + switch (evt->evt_id) { + case PM_EVT_CONN_SEC_SUCCEEDED: + peer_id = evt->peer_id; + break; + + case PM_EVT_PEERS_DELETE_SUCCEEDED: + advertising_start(false); + break; + + case PM_EVT_PEER_DATA_UPDATE_SUCCEEDED: + if (evt->params.peer_data_update_succeeded.flash_changed && + (evt->params.peer_data_update_succeeded.data_id == PM_PEER_DATA_ID_BONDING)) { + LOG_INF("New bond, add the peer to the allow list if possible"); + /* Note: You should check on what kind of allow list policy your + * application should use. + */ + allow_list_set(PM_PEER_ID_LIST_SKIP_NO_ID_ADDR); + } + break; + + default: + break; + } +} + +static uint32_t peer_manager_init(void) +{ + ble_gap_sec_params_t sec_param; + int nrf_err; + + nrf_err = pm_init(); + if (nrf_err) { + return nrf_err; + } + + memset(&sec_param, 0, sizeof(ble_gap_sec_params_t)); + + /* Security parameters to be used for all security procedures. */ + sec_param = (ble_gap_sec_params_t) { + .bond = SEC_PARAM_BOND, + .mitm = SEC_PARAM_MITM, + .lesc = SEC_PARAM_LESC, + .keypress = SEC_PARAM_KEYPRESS, + .io_caps = SEC_PARAM_IO_CAPABILITIES, + .oob = SEC_PARAM_OOB, + .min_key_size = SEC_PARAM_MIN_KEY_SIZE, + .max_key_size = SEC_PARAM_MAX_KEY_SIZE, + .kdist_own.enc = 1, + .kdist_own.id = 1, + .kdist_peer.enc = 1, + .kdist_peer.id = 1, + }; + + nrf_err = pm_sec_params_set(&sec_param); + if (nrf_err) { + LOG_ERR("pm_sec_params_set() failed, nrf_error %#x", nrf_err); + return nrf_err; + } + + nrf_err = pm_register(pm_evt_handler); + if (nrf_err) { + LOG_ERR("pm_register() failed, nrf_error %#x", nrf_err); + return nrf_err; + } + + return NRF_SUCCESS; +} + +uint16_t ble_qwr_evt_handler(struct ble_qwr *qwr, const struct ble_qwr_evt *qwr_evt) +{ + switch (qwr_evt->evt_type) { + case BLE_QWR_EVT_ERROR: + LOG_ERR("QWR error event, nrf_error 0x%x", qwr_evt->error.reason); + break; + case BLE_QWR_EVT_EXECUTE_WRITE: + LOG_INF("QWR execute write event"); + break; + case BLE_QWR_EVT_AUTH_REQUEST: + LOG_INF("QWR auth request event"); + break; + } + + return ble_bms_on_qwr_evt(&ble_bms, qwr, qwr_evt); +} + +int main(void) +{ + int err; + uint32_t nrf_err; + bool erase_bonds; + ble_uuid_t adv_uuid_list[] = { + { + .uuid = BLE_UUID_BMS_SERVICE, + .type = BLE_UUID_TYPE_BLE + }, + }; + + struct ble_adv_config ble_adv_cfg = { + .conn_cfg_tag = CONFIG_NRF_SDH_BLE_CONN_TAG, + .evt_handler = ble_adv_evt_handler, + .adv_data = { + .name_type = BLE_ADV_DATA_FULL_NAME, + .flags = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE, + + }, + .sr_data.uuid_lists.complete = { + .uuid = &adv_uuid_list[0], + .len = ARRAY_SIZE(adv_uuid_list), + } + }; + + struct ble_bms_config bms_cfg = { + .evt_handler = bms_evt_handler, +#if defined(CONFIG_BLE_BMS_USE_AUTHORIZATION_CODE) + /* Do not require auth to delete requesting. */ + .feature.delete_requesting = true, + .feature.delete_all_auth = true, + .feature.delete_all_but_requesting_auth = true, +#else + .feature.delete_requesting = true, + .feature.delete_all = true, + .feature.delete_all_but_requesting = true, +#endif + + .qwr = &ble_qwr, + .ctrlpt_sec = BLE_GAP_CONN_SEC_MODE_ENC_NO_MITM, + .feature_sec = BLE_GAP_CONN_SEC_MODE_ENC_NO_MITM, + }; + + struct ble_qwr_config qwr_config = { + .evt_handler = ble_qwr_evt_handler, + .mem_buffer.len = ARRAY_SIZE(qwr_mem), + .mem_buffer.p_mem = qwr_mem, + }; + + struct bm_buttons_config configs[4] = { + { + .pin_number = BOARD_PIN_BTN_0, + .active_state = BM_BUTTONS_ACTIVE_LOW, + .pull_config = BM_BUTTONS_PIN_PULLUP, + .handler = button_handler, + }, + { + .pin_number = BOARD_PIN_BTN_1, + .active_state = BM_BUTTONS_ACTIVE_LOW, + .pull_config = BM_BUTTONS_PIN_PULLUP, + .handler = button_handler, + }, + { + .pin_number = BOARD_PIN_BTN_2, + .active_state = BM_BUTTONS_ACTIVE_LOW, + .pull_config = BM_BUTTONS_PIN_PULLUP, + .handler = button_handler, + }, + { + .pin_number = BOARD_PIN_BTN_3, + .active_state = BM_BUTTONS_ACTIVE_LOW, + .pull_config = BM_BUTTONS_PIN_PULLUP, + .handler = button_handler, + }, + }; + struct ble_dis_config dis_config = { + .sec_mode = BLE_DIS_CONFIG_SEC_MODE_DEFAULT, + }; + + + LOG_INF("BLE BMS sample started"); + + nrf_gpio_cfg_output(BOARD_PIN_LED_0); + nrf_gpio_cfg_output(BOARD_PIN_LED_1); + + err = bm_buttons_init(configs, ARRAY_SIZE(configs), BM_BUTTONS_DETECTION_DELAY_MIN_US); + if (err) { + LOG_ERR("Failed to initialize buttons, err %d", err); + goto idle; + } + + err = bm_buttons_enable(); + if (err) { + LOG_ERR("Failed to enable buttons, err %d", err); + goto idle; + } + + erase_bonds = bm_buttons_is_pressed(BOARD_PIN_BTN_1); + + err = nrf_sdh_enable_request(); + if (err) { + LOG_ERR("Failed to enable SoftDevice, err %d", err); + goto idle; + } + + LOG_INF("SoftDevice enabled"); + + err = nrf_sdh_ble_enable(CONFIG_NRF_SDH_BLE_CONN_TAG); + if (err) { + LOG_ERR("Failed to enable BLE, err %d", err); + goto idle; + } + + LOG_INF("Bluetooth is enabled!"); + + nrf_err = peer_manager_init(); + if (nrf_err) { + LOG_ERR("Failed to initialize Peer Manager, nrf_error %#x", nrf_err); + goto idle; + } + + nrf_err = ble_qwr_init(&ble_qwr, &qwr_config); + if (nrf_err) { + LOG_ERR("ble_qwr_init failed, nrf_error %#x", nrf_err); + goto idle; + } + + nrf_err = ble_dis_init(&dis_config); + if (nrf_err) { + LOG_ERR("Failed to initialize device information service, nrf_error %#x", nrf_err); + goto idle; + } + + nrf_err = ble_bms_init(&ble_bms, &bms_cfg); + if (nrf_err) { + LOG_ERR("Failed to initialize BMS service, nrf_error %#x", nrf_err); + goto idle; + } + + nrf_err = sd_ble_gap_appearance_set(BLE_APPEARANCE_UNKNOWN); + if (nrf_err) { + LOG_ERR("Failed to sd_ble_gap_appearance_set, nrf_error %#x", nrf_err); + goto idle; + } + + nrf_err = ble_adv_init(&ble_adv, &ble_adv_cfg); + if (nrf_err) { + LOG_ERR("Failed to initialize BLE advertising, nrf_error %#x", nrf_err); + goto idle; + } + + nrf_err = advertising_start(erase_bonds); + if (nrf_err) { + LOG_ERR("Failed to start advertising, nrf_error %#x", nrf_err); + goto idle; + } + + LOG_INF("Advertising as %s", CONFIG_BLE_ADV_NAME); + +idle: + while (true) { + (void)nrf_ble_lesc_request_handler(); + + while (LOG_PROCESS()) { + /* Empty. */ + } + + /* Wait for an event. */ + __WFE(); + + /* Clear Event Register */ + __SEV(); + __WFE(); + } + + return 0; +} diff --git a/subsys/bluetooth/services/CMakeLists.txt b/subsys/bluetooth/services/CMakeLists.txt index 14f6fa3fc6..1fde1e320d 100644 --- a/subsys/bluetooth/services/CMakeLists.txt +++ b/subsys/bluetooth/services/CMakeLists.txt @@ -4,6 +4,7 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # add_subdirectory_ifdef(CONFIG_BLE_BAS ble_bas) +add_subdirectory_ifdef(CONFIG_BLE_BMS ble_bms) add_subdirectory_ifdef(CONFIG_BLE_CGMS ble_cgms) add_subdirectory_ifdef(CONFIG_BLE_DIS ble_dis) add_subdirectory_ifdef(CONFIG_BLE_HIDS ble_hids) diff --git a/subsys/bluetooth/services/Kconfig b/subsys/bluetooth/services/Kconfig index ee7c6264e0..932c49c503 100644 --- a/subsys/bluetooth/services/Kconfig +++ b/subsys/bluetooth/services/Kconfig @@ -4,6 +4,7 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # rsource "ble_bas/Kconfig" +rsource "ble_bms/Kconfig" rsource "ble_cgms/Kconfig" rsource "ble_dis/Kconfig" rsource "ble_hids/Kconfig" diff --git a/subsys/bluetooth/services/ble_bms/CMakeLists.txt b/subsys/bluetooth/services/ble_bms/CMakeLists.txt new file mode 100644 index 0000000000..cb0704bb6c --- /dev/null +++ b/subsys/bluetooth/services/ble_bms/CMakeLists.txt @@ -0,0 +1,6 @@ +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +zephyr_sources(bms.c) diff --git a/subsys/bluetooth/services/ble_bms/Kconfig b/subsys/bluetooth/services/ble_bms/Kconfig new file mode 100644 index 0000000000..f89b963f5a --- /dev/null +++ b/subsys/bluetooth/services/ble_bms/Kconfig @@ -0,0 +1,26 @@ +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +menuconfig BLE_BMS + bool "Bond management service [EXPERIMENTAL]" + select EXPERIMENTAL + depends on SOFTDEVICE_PERIPHERAL + depends on BLE_QWR + +if BLE_BMS + +config BLE_BMS_USE_AUTHORIZATION_CODE + bool "Use authorization code" + default y + +config BLE_BMS_AUTHORIZATION_CODE + string "Authorization code" + default "ABCD" + +module=BLE_BMS +module-str=BLE_BMS +source "$(ZEPHYR_BASE)/subsys/logging/Kconfig.template.log_config" + +endif diff --git a/subsys/bluetooth/services/ble_bms/bms.c b/subsys/bluetooth/services/ble_bms/bms.c new file mode 100644 index 0000000000..4d17225239 --- /dev/null +++ b/subsys/bluetooth/services/ble_bms/bms.c @@ -0,0 +1,514 @@ +/* + * Copyright (c) 2016 - 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/** Length of the Feature Characteristic (in bytes). */ +#define BLE_BMS_FEATURE_LEN 3 +/** Minimum length of the Bond Management Control Point Characteristic (in bytes). */ +#define BLE_BMS_CTRLPT_MIN_LEN 1 + +LOG_MODULE_REGISTER(ble_bms, CONFIG_BLE_BMS_LOG_LEVEL); + +static uint32_t ctrlpt_char_add(struct ble_bms *bms, struct ble_bms_config const *bms_config) +{ + uint32_t nrf_err; + ble_uuid_t char_uuid = { + .type = BLE_UUID_TYPE_BLE, + .uuid = BLE_UUID_BMS_CTRLPT, + }; + ble_gatts_char_md_t char_md = { + .char_props = { + .write = true, + }, + .char_ext_props = { + .reliable_wr = bms_config->qwr ? true : false, + }, + }; + ble_gatts_attr_md_t attr_md = { + .vloc = BLE_GATTS_VLOC_STACK, + .wr_auth = true, + .vlen = true, + .write_perm = bms_config->ctrlpt_sec, + }; + ble_gatts_attr_t attr_char_value = { + .p_uuid = &char_uuid, + .p_attr_md = &attr_md, + .p_value = NULL, + .init_len = 0, + .max_len = BLE_BMS_CTRLPT_MAX_LEN, + }; + + nrf_err = sd_ble_gatts_characteristic_add(bms->service_handle, &char_md, &attr_char_value, + &bms->ctrlpt_handles); + if (nrf_err) { + LOG_ERR("Failed to add GATT CGMS SST characteristic, nrf_error %#x", nrf_err); + return nrf_err; + } + + return NRF_SUCCESS; +} + +static uint8_t feature_encode(struct ble_bms_features const *feature, uint8_t *encoded_feature) +{ + uint32_t data = 0; + + if (feature->delete_all_auth) { + data |= BLE_BMS_ALL_BONDS_LE_AUTH_CODE; + } + + if (feature->delete_all_but_requesting_auth) { + data |= BLE_BMS_ALL_EXCEPT_REQUESTING_DEVICE_LE_AUTH_CODE; + } + + if (feature->delete_all_but_requesting) { + data |= BLE_BMS_ALL_EXCEPT_REQUESTING_DEVICE_LE; + } + + if (feature->delete_all) { + data |= BLE_BMS_ALL_BONDS_LE; + } + + if (feature->delete_requesting_auth) { + data |= BLE_BMS_REQUESTING_DEVICE_LE_AUTH_CODE; + } + + if (feature->delete_requesting) { + data |= BLE_BMS_REQUESTING_DEVICE_LE; + } + + sys_put_le24(data, encoded_feature); + return 3; +} + +static uint32_t feature_char_add(struct ble_bms *bms, struct ble_bms_config const *bms_config) +{ + uint32_t nrf_err; + uint8_t encoded_feature[BLE_BMS_FEATURE_LEN]; + uint8_t init_value_len; + + ble_uuid_t char_uuid = { + .type = BLE_UUID_TYPE_BLE, + .uuid = BLE_UUID_BMS_FEATURE, + }; + ble_gatts_char_md_t char_md = { + .char_props = { + .read = true, + }, + }; + ble_gatts_attr_md_t attr_md = { + .vloc = BLE_GATTS_VLOC_STACK, + }; + ble_gatts_attr_t attr_char_value = { + .p_uuid = &char_uuid, + .p_attr_md = &attr_md, + .p_value = encoded_feature, + }; + + __ASSERT_NO_MSG(bms != NULL); + __ASSERT_NO_MSG(bms_config != NULL); + + init_value_len = feature_encode(&bms->feature, encoded_feature); + attr_char_value.init_len = init_value_len; + attr_char_value.max_len = init_value_len; + attr_md.read_perm = bms_config->feature_sec; + + nrf_err = sd_ble_gatts_characteristic_add(bms->service_handle, &char_md, &attr_char_value, + &bms->feature_handles); + if (nrf_err) { + LOG_ERR("Failed to add GATT CGMS SST characteristic, nrf_error %#x", nrf_err); + return nrf_err; + } + + return NRF_SUCCESS; +} + +static void ctrlpt_auth(struct ble_bms *bms, struct ble_bms_ctrlpt *ctrlpt) +{ + __ASSERT_NO_MSG(bms != NULL); + __ASSERT_NO_MSG(ctrlpt != NULL); + + bms->auth_status = BLE_BMS_AUTH_STATUS_ALLOWED; + + /* Check if the authorization feature is enabled for this op code. */ + if (((bms->feature.delete_requesting_auth) && + (ctrlpt->op_code == BLE_BMS_OP_DEL_BOND_REQ_DEVICE_LE_ONLY)) || + ((bms->feature.delete_all_auth) && + (ctrlpt->op_code == BLE_BMS_OP_DEL_ALL_BONDS_ON_SERVER_LE_ONLY)) || + ((bms->feature.delete_all_but_requesting_auth) && + (ctrlpt->op_code == BLE_BMS_OP_DEL_ALL_BUT_ACTIVE_BOND_LE_ONLY))) { + if (bms->evt_handler != NULL) { + struct ble_bms_evt bms_evt = { + .evt_type = BLE_BMS_EVT_AUTH, + .auth.auth_code.len = ctrlpt->auth_code.len, + }; + + memcpy(bms_evt.auth.auth_code.code, + ctrlpt->auth_code.code, ctrlpt->auth_code.len); + + bms->auth_status = BLE_BMS_AUTH_STATUS_PENDING; + + bms->evt_handler(bms, &bms_evt); + } else { + bms->auth_status = BLE_BMS_AUTH_STATUS_DENIED; + } + } +} + +static uint32_t ctrlpt_decode(uint8_t const *rcvd_val, uint16_t len, struct ble_bms_ctrlpt *ctrlpt) +{ + uint16_t pos = 0; + + __ASSERT_NO_MSG(rcvd_val != NULL); + __ASSERT_NO_MSG(ctrlpt != NULL); + + if (len < BLE_BMS_CTRLPT_MIN_LEN || len > BLE_BMS_CTRLPT_MAX_LEN) { + return NRF_ERROR_INVALID_LENGTH; + } + + ctrlpt->op_code = (enum ble_bms_op)rcvd_val[pos++]; + ctrlpt->auth_code.len = (len - pos); + memcpy(ctrlpt->auth_code.code, &(rcvd_val[pos]), ctrlpt->auth_code.len); + + return NRF_SUCCESS; +} + +static void ctrlpt_execute(struct ble_bms *bms, enum ble_bms_op op_code) +{ + __ASSERT_NO_MSG(bms != NULL); + + struct ble_bms_evt evt = {}; + + switch (op_code) { + case BLE_BMS_OP_DEL_BOND_REQ_DEVICE_LE_ONLY: + /* Delete single bond */ + evt.evt_type = BLE_BMS_EVT_BOND_DELETE_REQUESTING; + break; + case BLE_BMS_OP_DEL_ALL_BONDS_ON_SERVER_LE_ONLY: + /* Delete all bonds */ + evt.evt_type = BLE_BMS_EVT_BOND_DELETE_ALL; + break; + case BLE_BMS_OP_DEL_ALL_BUT_ACTIVE_BOND_LE_ONLY: + /* Delete all but current bond */ + evt.evt_type = BLE_BMS_EVT_BOND_DELETE_ALL_EXCEPT_REQUESTING; + break; + default: + /* No event, return. */ + return; + } + + bms->evt_handler(bms, &evt); +} + +static bool ctrlpt_validate(struct ble_bms_ctrlpt *ctrlpt, struct ble_bms_features *feature) +{ + __ASSERT_NO_MSG(ctrlpt != NULL); + __ASSERT_NO_MSG(feature != NULL); + + switch (ctrlpt->op_code) { + case BLE_BMS_OP_DEL_BOND_REQ_DEVICE_LE_ONLY: + if (feature->delete_requesting || feature->delete_requesting_auth) { + return true; + } + break; + case BLE_BMS_OP_DEL_ALL_BONDS_ON_SERVER_LE_ONLY: + if (feature->delete_all || feature->delete_all_auth) { + return true; + } + break; + case BLE_BMS_OP_DEL_ALL_BUT_ACTIVE_BOND_LE_ONLY: + if (feature->delete_all_but_requesting || feature->delete_all_but_requesting_auth) { + return true; + } + break; + default: + /* No implementation needed. */ + break; + } + + return false; +} + +static uint16_t ctrlpt_process(struct ble_bms *bms, uint8_t const *rcvd_val, uint16_t len, + struct ble_bms_ctrlpt *ctrlpt) +{ + uint32_t nrf_err; + + __ASSERT_NO_MSG(bms != NULL); + __ASSERT_NO_MSG(rcvd_val != NULL); + __ASSERT_NO_MSG(ctrlpt != NULL); + + /* Decode operation */ + nrf_err = ctrlpt_decode(rcvd_val, len, ctrlpt); + if (nrf_err) { + LOG_ERR("Control point write: Operation failed."); + return BLE_BMS_OPERATION_FAILED; + } + + /* Verify that the operation is allowed. */ + if (!ctrlpt_validate(ctrlpt, &bms->feature)) { + LOG_ERR("Control point write: Invalid op code."); + return BLE_BMS_OPCODE_NOT_SUPPORTED; + } + + /* Request authorization */ + ctrlpt_auth(bms, ctrlpt); + if (bms->auth_status != BLE_BMS_AUTH_STATUS_ALLOWED) { + LOG_ERR("Control point long write: Invalid auth."); + return BLE_GATT_STATUS_ATTERR_INSUF_AUTHORIZATION; + } + + return BLE_GATT_STATUS_SUCCESS; +} + +static void on_ctrlpt_write(struct ble_bms *bms, ble_gatts_evt_write_t const *evt_write, + ble_gatts_authorize_params_t *auth_params) +{ + uint32_t nrf_err; + struct ble_bms_ctrlpt ctrlpt; + + __ASSERT_NO_MSG(bms != NULL); + __ASSERT_NO_MSG(evt_write != NULL); + __ASSERT_NO_MSG(auth_params != NULL); + + nrf_err = ctrlpt_process(bms, evt_write->data, evt_write->len, &ctrlpt); + if (nrf_err) { + auth_params->gatt_status = nrf_err; + auth_params->update = 0; + + return; + } + + auth_params->gatt_status = BLE_GATT_STATUS_SUCCESS; + auth_params->update = 1; + + LOG_INF("Control point write: Success"); + + /* Execute the requested operation. */ + ctrlpt_execute(bms, ctrlpt.op_code); +} + +static void on_rw_auth_req(struct ble_bms *bms, ble_gatts_evt_t const *gatts_evt) +{ + uint32_t nrf_err; + + __ASSERT_NO_MSG(bms != NULL); + __ASSERT_NO_MSG(gatts_evt != NULL); + + ble_gatts_rw_authorize_reply_params_t auth_reply = { + .type = BLE_GATTS_AUTHORIZE_TYPE_WRITE, + }; + + ble_gatts_evt_rw_authorize_request_t const *auth_req = + &gatts_evt->params.authorize_request; + + if ((auth_req->type == BLE_GATTS_AUTHORIZE_TYPE_WRITE) && + (auth_req->request.write.op == BLE_GATTS_OP_WRITE_REQ) && + (auth_req->request.write.handle == bms->ctrlpt_handles.value_handle)) { + on_ctrlpt_write(bms, &auth_req->request.write, &auth_reply.params.write); + + /* Send authorization reply */ + nrf_err = sd_ble_gatts_rw_authorize_reply(bms->conn_handle, &auth_reply); + if (nrf_err) { + struct ble_bms_evt bms_evt = { + .evt_type = BLE_BMS_EVT_ERROR, + .error.reason = nrf_err, + }; + + bms->evt_handler(bms, &bms_evt); + } + } +} + +static uint16_t on_qwr_auth_req(struct ble_bms *bms, struct ble_qwr *qwr, + const struct ble_qwr_evt *evt) +{ + uint32_t nrf_err; + uint16_t len = BLE_BMS_CTRLPT_MAX_LEN; + uint8_t mem_buffer[BLE_BMS_CTRLPT_MAX_LEN]; + struct ble_bms_ctrlpt ctrlpt; + + __ASSERT_NO_MSG(bms != NULL); + __ASSERT_NO_MSG(qwr != NULL); + __ASSERT_NO_MSG(evt != NULL); + + nrf_err = ble_qwr_value_get(qwr, evt->auth_req.attr_handle, mem_buffer, &len); + if (nrf_err) { + LOG_ERR("Control point write: Operation failed."); + return BLE_BMS_OPERATION_FAILED; + } + + return ctrlpt_process(bms, mem_buffer, len, &ctrlpt); +} + +static uint16_t on_qwr_exec_write(struct ble_bms *bms, struct ble_qwr *qwr, + const struct ble_qwr_evt *evt) +{ + uint32_t nrf_err; + uint16_t len = BLE_BMS_CTRLPT_MAX_LEN; + uint8_t mem_buffer[BLE_BMS_CTRLPT_MAX_LEN]; + struct ble_bms_ctrlpt ctrlpt; + ble_gatts_value_t ctrlpt_value = { + .len = BLE_BMS_CTRLPT_MAX_LEN, + .offset = 0, + .p_value = mem_buffer, + }; + + __ASSERT_NO_MSG(bms != NULL); + __ASSERT_NO_MSG(qwr != NULL); + __ASSERT_NO_MSG(evt != NULL); + + const uint16_t ctrlpt_handle = bms->ctrlpt_handles.value_handle; + + nrf_err = sd_ble_gatts_value_get(bms->conn_handle, ctrlpt_handle, &ctrlpt_value); + if (nrf_err) { + LOG_ERR("Control point write: Operation failed."); + return BLE_BMS_OPERATION_FAILED; + } + + /* Decode operation */ + nrf_err = ctrlpt_decode(ctrlpt_value.p_value, len, &ctrlpt); + if (nrf_err) { + LOG_ERR("Control point write: Operation failed."); + return BLE_BMS_OPERATION_FAILED; + } + + /* Execute the requested operation. */ + ctrlpt_execute(bms, ctrlpt.op_code); + + /* Reset authorization status */ + bms->auth_status = BLE_BMS_AUTH_STATUS_DENIED; + + return BLE_GATT_STATUS_SUCCESS; +} + +uint16_t ble_bms_on_qwr_evt(struct ble_bms *bms, struct ble_qwr *qwr, + const struct ble_qwr_evt *evt) +{ + if (!bms || !qwr || !evt) { + return BLE_QWR_REJ_REQUEST_ERR_CODE; + } + + if (evt->auth_req.attr_handle != bms->ctrlpt_handles.value_handle) { + return BLE_QWR_REJ_REQUEST_ERR_CODE; + } + + bms->conn_handle = qwr->conn_handle; + + if (evt->evt_type == BLE_QWR_EVT_AUTH_REQUEST) { + return on_qwr_auth_req(bms, qwr, evt); + } else if ((evt->evt_type == BLE_QWR_EVT_EXECUTE_WRITE) && + (bms->auth_status == BLE_BMS_AUTH_STATUS_ALLOWED)) { + return on_qwr_exec_write(bms, qwr, evt); + } + + return BLE_GATT_STATUS_SUCCESS; +} + +void ble_bms_on_ble_evt(ble_evt_t const *ble_evt, void *context) +{ + struct ble_bms *bms; + + if (!context || !ble_evt) { + return; + } + + bms = (struct ble_bms *)context; + + switch (ble_evt->header.evt_id) { + case BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST: + bms->conn_handle = ble_evt->evt.gatts_evt.conn_handle; + on_rw_auth_req(bms, &ble_evt->evt.gatts_evt); + break; + default: + break; + } +} + +uint32_t ble_bms_init(struct ble_bms *bms, struct ble_bms_config *bms_config) +{ + uint32_t nrf_err; + ble_uuid_t ble_uuid; + + if (!bms || !bms_config) { + return NRF_ERROR_NULL; + } + + if (!bms_config->evt_handler) { + return NRF_ERROR_INVALID_ADDR; + } + + /* Add service */ + BLE_UUID_BLE_ASSIGN(ble_uuid, BLE_UUID_BMS_SERVICE); + + bms->evt_handler = bms_config->evt_handler; + bms->feature = bms_config->feature; + bms->conn_handle = BLE_CONN_HANDLE_INVALID; + + nrf_err = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, + &ble_uuid, &bms->service_handle); + if (nrf_err) { + return nrf_err; + } + + nrf_err = feature_char_add(bms, bms_config); + if (nrf_err) { + return nrf_err; + } + + nrf_err = ctrlpt_char_add(bms, bms_config); + if (nrf_err) { + return nrf_err; + } + + /* Allow this for backward compatibility */ + if (bms_config->qwr && (bms_config->qwr_count == 0)) { + nrf_err = ble_qwr_attr_register(bms_config->qwr, bms->ctrlpt_handles.value_handle); + if (nrf_err) { + return nrf_err; + } + } else if (bms_config->qwr && (bms_config->qwr_count > 0)) { + for (uint32_t i = 0; i < bms_config->qwr_count; i++) { + nrf_err = ble_qwr_attr_register(&bms_config->qwr[i], + bms->ctrlpt_handles.value_handle); + if (nrf_err) { + return nrf_err; + } + } + } else { + /* Do nothing */ + } + + return NRF_SUCCESS; +} + +uint32_t ble_bms_auth_response(struct ble_bms *bms, bool authorize) +{ + if (!bms) { + return NRF_ERROR_NULL; + } + + if (bms->auth_status != BLE_BMS_AUTH_STATUS_PENDING) { + return NRF_ERROR_INVALID_STATE; + } + + if (authorize) { + bms->auth_status = BLE_BMS_AUTH_STATUS_ALLOWED; + } else { + bms->auth_status = BLE_BMS_AUTH_STATUS_DENIED; + } + + return NRF_SUCCESS; +} diff --git a/tests/subsys/bluetooth/services/ble_bms/CMakeLists.txt b/tests/subsys/bluetooth/services/ble_bms/CMakeLists.txt new file mode 100644 index 0000000000..b7fd7243df --- /dev/null +++ b/tests/subsys/bluetooth/services/ble_bms/CMakeLists.txt @@ -0,0 +1,34 @@ +# +# 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_bms) + +set(SOFTDEVICE_VARIANT "s115") +set(SOFTDEVICE_INCLUDE_DIR "${ZEPHYR_NRF_BM_MODULE_DIR}/components/softdevice/\ +${SOFTDEVICE_VARIANT}/${SOFTDEVICE_VARIANT}_API/include") + +cmock_handle(${SOFTDEVICE_INCLUDE_DIR}/ble_gatts.h) +cmock_handle(${ZEPHYR_NRF_BM_MODULE_DIR}/include/bm/bluetooth/ble_qwr.h) + +zephyr_compile_definitions( + NRF54L15_XXAA + SVCALL_AS_NORMAL_FUNCTION + SUPPRESS_INLINE_IMPLEMENTATION +) + +zephyr_include_directories( + ${SOFTDEVICE_INCLUDE_DIR} + ${ZEPHYR_NRF_BM_MODULE_DIR}/include + ${ZEPHYR_HAL_NORDIC_MODULE_DIR}/nrfx/mdk +) + +# Generate and add test file +test_runner_generate(src/unity_test.c) +target_sources(app PRIVATE src/unity_test.c) diff --git a/tests/subsys/bluetooth/services/ble_bms/Kconfig b/tests/subsys/bluetooth/services/ble_bms/Kconfig new file mode 100644 index 0000000000..e5a35bc501 --- /dev/null +++ b/tests/subsys/bluetooth/services/ble_bms/Kconfig @@ -0,0 +1,14 @@ +# Clear dependencies for BLE_BMS and enable it to allow +# testing the features without enabling the library. +config BLE_BMS + default y + +# Redefine Kconfigs used by the tested module that is defined in +# other modules we do not want to enable. +config BLE_QWR_MAX_ATTR + default 1 + +config NRF_SDH_BLE_TOTAL_LINK_COUNT + default 1 + +source "Kconfig.zephyr" diff --git a/tests/subsys/bluetooth/services/ble_bms/prj.conf b/tests/subsys/bluetooth/services/ble_bms/prj.conf new file mode 100644 index 0000000000..9cdbb56cb9 --- /dev/null +++ b/tests/subsys/bluetooth/services/ble_bms/prj.conf @@ -0,0 +1,9 @@ +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +CONFIG_UNITY=y + +CONFIG_NRF_SDH_BLE_TOTAL_LINK_COUNT=1 +CONFIG_BLE_QWR_MAX_ATTR=1 diff --git a/tests/subsys/bluetooth/services/ble_bms/src/unity_test.c b/tests/subsys/bluetooth/services/ble_bms/src/unity_test.c new file mode 100644 index 0000000000..4f2d72d138 --- /dev/null +++ b/tests/subsys/bluetooth/services/ble_bms/src/unity_test.c @@ -0,0 +1,775 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cmock_ble_gatts.h" +#include "cmock_ble_qwr.h" + +#define SERVICE_HANDLE 0x1234 + +#define FEATURE_SEC_LV 1 +#define FEATURE_SEC_SM 2 +#define CTRLPT_SEC_LV 1 +#define CTRLPT_SEC_SM 3 + +#define BLE_BMS_FEATURE_LEN 3 +#define CTRLPT_VALUE_HANDLE 2 + +#define CONN_HANDLE 10 + +BLE_BMS_DEF(ble_bms); +BLE_QWR_DEF(ble_qwr); + +#define AUTH_CODE_VALID "abc" + +struct ble_bms_evt last_evt; + +void bms_evt_handler(struct ble_bms *bms, struct ble_bms_evt *evt) +{ + uint32_t nrf_err; + bool is_authorized = true; + + last_evt = *evt; + + switch (evt->evt_type) { + case BLE_BMS_EVT_ERROR: + TEST_ASSERT_EQUAL(NRF_ERROR_BUSY, evt->error.reason); + break; + case BLE_BMS_EVT_AUTH: + if ((evt->auth.auth_code.len != strlen(AUTH_CODE_VALID)) || + (memcmp(AUTH_CODE_VALID, evt->auth.auth_code.code, + strlen(AUTH_CODE_VALID)) != 0)) { + is_authorized = false; + } + nrf_err = ble_bms_auth_response(&ble_bms, is_authorized); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + break; + case BLE_BMS_EVT_BOND_DELETE_REQUESTING: + break; + case BLE_BMS_EVT_BOND_DELETE_ALL: + break; + case BLE_BMS_EVT_BOND_DELETE_ALL_EXCEPT_REQUESTING: + break; + } +} + +uint32_t stub_sd_ble_gatts_service_add(uint8_t type, ble_uuid_t const *p_uuid, uint16_t *p_handle, + int cmock_calls) +{ + TEST_ASSERT_EQUAL(BLE_GATTS_SRVC_TYPE_PRIMARY, type); + TEST_ASSERT_EQUAL(BLE_UUID_TYPE_BLE, p_uuid->type); + TEST_ASSERT_EQUAL(BLE_UUID_BMS_SERVICE, p_uuid->uuid); + + *p_handle = SERVICE_HANDLE; + + return NRF_SUCCESS; +} + +uint32_t stub_sd_ble_gatts_characteristic_add_feature_char_error_no_mem( + uint16_t service_handle, ble_gatts_char_md_t const *p_char_md, + ble_gatts_attr_t const *p_attr_char_value, ble_gatts_char_handles_t *p_handles, + int cmock_calls) +{ + /* Encoded expected feature of delete_all_auth, delete_requesting and + * delete_all_but_requesting_auth. + */ + const uint8_t encoded_feature_expected[BLE_BMS_FEATURE_LEN] = { 0x10, 0x08, 0x02 }; + + TEST_ASSERT_EQUAL(SERVICE_HANDLE, service_handle); + TEST_ASSERT_TRUE(p_char_md->char_props.read); + + TEST_ASSERT_EQUAL(BLE_UUID_TYPE_BLE, p_attr_char_value->p_uuid->type); + TEST_ASSERT_EQUAL(BLE_UUID_BMS_FEATURE, p_attr_char_value->p_uuid->uuid); + TEST_ASSERT_EQUAL(BLE_GATTS_VLOC_STACK, p_attr_char_value->p_attr_md->vloc); + TEST_ASSERT_EQUAL(FEATURE_SEC_LV, p_attr_char_value->p_attr_md->read_perm.lv); + TEST_ASSERT_EQUAL(FEATURE_SEC_SM, p_attr_char_value->p_attr_md->read_perm.sm); + TEST_ASSERT_EQUAL(BLE_BMS_FEATURE_LEN, p_attr_char_value->init_len); + TEST_ASSERT_EQUAL_MEMORY(encoded_feature_expected, p_attr_char_value->p_value, + p_attr_char_value->init_len); + + TEST_ASSERT_EQUAL(BLE_BMS_FEATURE_LEN, p_attr_char_value->max_len); + + return NRF_ERROR_NO_MEM; +} + +uint32_t stub_sd_ble_gatts_characteristic_add_ctrlpt_char_error_no_mem( + uint16_t service_handle, ble_gatts_char_md_t const *p_char_md, + ble_gatts_attr_t const *p_attr_char_value, ble_gatts_char_handles_t *p_handles, + int cmock_calls) +{ + if (cmock_calls < 1) { + /* Earlier calls verified by other tests, return successfully. */ + return NRF_SUCCESS; + } + + TEST_ASSERT_EQUAL(SERVICE_HANDLE, service_handle); + TEST_ASSERT_TRUE(p_char_md->char_props.write); + TEST_ASSERT_FALSE(p_char_md->char_ext_props.reliable_wr); + + TEST_ASSERT_EQUAL(BLE_UUID_TYPE_BLE, p_attr_char_value->p_uuid->type); + TEST_ASSERT_EQUAL(BLE_UUID_BMS_CTRLPT, p_attr_char_value->p_uuid->uuid); + TEST_ASSERT_EQUAL(BLE_GATTS_VLOC_STACK, p_attr_char_value->p_attr_md->vloc); + TEST_ASSERT_TRUE(p_attr_char_value->p_attr_md->wr_auth); + TEST_ASSERT_TRUE(p_attr_char_value->p_attr_md->vlen); + TEST_ASSERT_EQUAL(CTRLPT_SEC_LV, p_attr_char_value->p_attr_md->write_perm.lv); + TEST_ASSERT_EQUAL(CTRLPT_SEC_SM, p_attr_char_value->p_attr_md->write_perm.sm); + TEST_ASSERT_EQUAL(0, p_attr_char_value->init_len); + TEST_ASSERT_NULL(p_attr_char_value->p_value); + TEST_ASSERT_EQUAL(BLE_BMS_CTRLPT_MAX_LEN, p_attr_char_value->max_len); + + return NRF_ERROR_NO_MEM; +} + +uint32_t stub_sd_ble_gatts_characteristic_add( + uint16_t service_handle, ble_gatts_char_md_t const *p_char_md, + ble_gatts_attr_t const *p_attr_char_value, ble_gatts_char_handles_t *p_handles, + int cmock_calls) +{ + if (cmock_calls < 1) { + /* Earlier calls verified by other tests, return successfully. */ + return NRF_SUCCESS; + } + + p_handles->value_handle = CTRLPT_VALUE_HANDLE; + + return NRF_SUCCESS; +} + +uint32_t stub_sd_ble_gatts_rw_authorize_reply_accepted( + uint16_t conn_handle, + ble_gatts_rw_authorize_reply_params_t const *p_rw_authorize_reply_params, int cmock_calls) +{ + TEST_ASSERT_EQUAL(1, p_rw_authorize_reply_params->params.write.update); + + return NRF_SUCCESS; +} + +uint32_t stub_sd_ble_gatts_rw_authorize_reply_rejected( + uint16_t conn_handle, + ble_gatts_rw_authorize_reply_params_t const *p_rw_authorize_reply_params, int cmock_calls) +{ + TEST_ASSERT_EQUAL(0, p_rw_authorize_reply_params->params.write.update); + + return NRF_SUCCESS; +} + +uint32_t stub_sd_ble_gatts_rw_authorize_reply_error( + uint16_t conn_handle, + ble_gatts_rw_authorize_reply_params_t const *p_rw_authorize_reply_params, int cmock_calls) +{ + return NRF_ERROR_BUSY; +} + +uint32_t stub_ble_qwr_value_get_auth_req_accepted( + struct ble_qwr *qwr, uint16_t attr_handle, uint8_t *mem, uint16_t *len, int cmock_calls) +{ + TEST_ASSERT_EQUAL_PTR(&ble_qwr, qwr); + TEST_ASSERT_EQUAL(CTRLPT_VALUE_HANDLE, attr_handle); + + uint8_t data_val[] = { BLE_BMS_OP_DEL_ALL_BONDS_ON_SERVER_LE_ONLY, 'a', 'b', 'c' }; + + memcpy(mem, data_val, sizeof(data_val)); + *len = sizeof(data_val); + + return NRF_SUCCESS; +} + +uint32_t stub_ble_qwr_value_get_auth_req_no_data( + struct ble_qwr *qwr, uint16_t attr_handle, uint8_t *mem, uint16_t *len, int cmock_calls) +{ + TEST_ASSERT_EQUAL_PTR(&ble_qwr, qwr); + TEST_ASSERT_EQUAL(CTRLPT_VALUE_HANDLE, attr_handle); + + *len = 0; + + return NRF_SUCCESS; +} + +uint32_t stub_ble_qwr_value_get_auth_req_rejected( + struct ble_qwr *qwr, uint16_t attr_handle, uint8_t *mem, uint16_t *len, int cmock_calls) +{ + TEST_ASSERT_EQUAL_PTR(&ble_qwr, qwr); + TEST_ASSERT_EQUAL(CTRLPT_VALUE_HANDLE, attr_handle); + + uint8_t data_inval[] = { BLE_BMS_OP_DEL_ALL_BONDS_ON_SERVER_LE_ONLY, 'b', 'a', 'd' }; + + memcpy(mem, data_inval, sizeof(data_inval)); + *len = sizeof(data_inval); + + return NRF_SUCCESS; +} + +uint32_t stub_ble_qwr_value_get_error_no_mem( + struct ble_qwr *qwr, uint16_t attr_handle, uint8_t *mem, uint16_t *len, int cmock_calls) +{ + return NRF_ERROR_NO_MEM; +} + +uint32_t stub_sd_ble_gatts_value_get_accepted(uint16_t conn_handle, uint16_t handle, + ble_gatts_value_t *p_value, int cmock_calls) +{ + TEST_ASSERT_EQUAL(CONN_HANDLE, conn_handle); + TEST_ASSERT_EQUAL(CTRLPT_VALUE_HANDLE, handle); + + uint8_t data_val[] = { BLE_BMS_OP_DEL_ALL_BONDS_ON_SERVER_LE_ONLY, 'a', 'b', 'c' }; + + memcpy(p_value->p_value, data_val, sizeof(data_val)); + p_value->len = sizeof(data_val); + + return NRF_SUCCESS; +} + +uint32_t stub_sd_ble_gatts_value_get_no_data(uint16_t conn_handle, uint16_t handle, + ble_gatts_value_t *p_value, int cmock_calls) +{ + p_value->len = 0; + + return NRF_SUCCESS; +} + +uint32_t stub_sd_ble_gatts_value_get_error_not_found(uint16_t conn_handle, uint16_t handle, + ble_gatts_value_t *p_value, int cmock_calls) +{ + return NRF_ERROR_NOT_FOUND; +} + +void test_ble_bms_init_error_null(void) +{ + uint32_t nrf_err; + struct ble_bms_config bms_cfg = {0}; + + nrf_err = ble_bms_init(NULL, &bms_cfg); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); + + nrf_err = ble_bms_init(&ble_bms, NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); +} + +void test_ble_bms_init_error_invalid_addr(void) +{ + uint32_t nrf_err; + struct ble_bms_config bms_cfg = {0}; + + nrf_err = ble_bms_init(&ble_bms, &bms_cfg); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_ADDR, nrf_err); +} + +void test_ble_bms_init_error_no_mem(void) +{ + uint32_t nrf_err; + struct ble_bms_config bms_cfg = { + .evt_handler = bms_evt_handler, + }; + + __cmock_sd_ble_gatts_service_add_ExpectAndReturn(BLE_GATTS_SRVC_TYPE_PRIMARY, + NULL, &ble_bms.service_handle, + NRF_ERROR_NO_MEM); + __cmock_sd_ble_gatts_service_add_IgnoreArg_p_uuid(); + + nrf_err = ble_bms_init(&ble_bms, &bms_cfg); + TEST_ASSERT_EQUAL(NRF_ERROR_NO_MEM, nrf_err); +} + +void test_ble_bms_init_error_feature_add_no_mem(void) +{ + uint32_t nrf_err; + struct ble_bms_config bms_cfg = { + .evt_handler = bms_evt_handler, + .feature_sec.lv = FEATURE_SEC_LV, + .feature_sec.sm = FEATURE_SEC_SM, + .feature = { + .delete_all_auth = true, + .delete_requesting = true, + .delete_all_but_requesting_auth = true, + }, + }; + + __cmock_sd_ble_gatts_service_add_Stub(stub_sd_ble_gatts_service_add); + + __cmock_sd_ble_gatts_characteristic_add_Stub( + stub_sd_ble_gatts_characteristic_add_feature_char_error_no_mem); + + nrf_err = ble_bms_init(&ble_bms, &bms_cfg); + TEST_ASSERT_EQUAL(NRF_ERROR_NO_MEM, nrf_err); +} + +void test_ble_bms_init_error_ctrlpt_add_no_mem(void) +{ + uint32_t nrf_err; + struct ble_bms_config bms_cfg = { + .evt_handler = bms_evt_handler, + .feature_sec.lv = FEATURE_SEC_LV, + .feature_sec.sm = FEATURE_SEC_SM, + .ctrlpt_sec.lv = CTRLPT_SEC_LV, + .ctrlpt_sec.sm = CTRLPT_SEC_SM, + .feature = { + .delete_all_auth = true, + .delete_requesting = true, + .delete_all_but_requesting_auth = true, + }, + }; + + __cmock_sd_ble_gatts_service_add_Stub(stub_sd_ble_gatts_service_add); + + __cmock_sd_ble_gatts_characteristic_add_Stub( + stub_sd_ble_gatts_characteristic_add_ctrlpt_char_error_no_mem); + + nrf_err = ble_bms_init(&ble_bms, &bms_cfg); + TEST_ASSERT_EQUAL(NRF_ERROR_NO_MEM, nrf_err); +} + +void test_ble_bms_init_error_qwr_attr_register_no_mem(void) +{ + uint32_t nrf_err; + struct ble_bms_config bms_cfg = { + .evt_handler = bms_evt_handler, + .feature_sec.lv = FEATURE_SEC_LV, + .feature_sec.sm = FEATURE_SEC_SM, + .ctrlpt_sec.lv = CTRLPT_SEC_LV, + .ctrlpt_sec.sm = CTRLPT_SEC_SM, + .feature = { + .delete_all_auth = true, + .delete_requesting = true, + .delete_all_but_requesting_auth = true, + }, + .qwr = &ble_qwr, + }; + + __cmock_sd_ble_gatts_service_add_Stub(stub_sd_ble_gatts_service_add); + + __cmock_sd_ble_gatts_characteristic_add_Stub(stub_sd_ble_gatts_characteristic_add); + + __cmock_ble_qwr_attr_register_ExpectAndReturn( + &ble_qwr, CTRLPT_VALUE_HANDLE, NRF_ERROR_NO_MEM); + + nrf_err = ble_bms_init(&ble_bms, &bms_cfg); + TEST_ASSERT_EQUAL(NRF_ERROR_NO_MEM, nrf_err); + + bms_cfg.qwr_count = 1; + + /* TODO reset stub */ + + __cmock_sd_ble_gatts_service_add_Stub(stub_sd_ble_gatts_service_add); + + __cmock_sd_ble_gatts_characteristic_add_Stub(stub_sd_ble_gatts_characteristic_add); + + __cmock_ble_qwr_attr_register_ExpectAndReturn( + &ble_qwr, CTRLPT_VALUE_HANDLE, NRF_ERROR_NO_MEM); + + nrf_err = ble_bms_init(&ble_bms, &bms_cfg); + TEST_ASSERT_EQUAL(NRF_ERROR_NO_MEM, nrf_err); +} + +void test_ble_bms_init(void) +{ + uint32_t nrf_err; + struct ble_bms_config bms_cfg = { + .evt_handler = bms_evt_handler, + .feature_sec.lv = FEATURE_SEC_LV, + .feature_sec.sm = FEATURE_SEC_SM, + .ctrlpt_sec.lv = CTRLPT_SEC_LV, + .ctrlpt_sec.sm = CTRLPT_SEC_SM, + .feature = { + .delete_all_auth = true, + .delete_requesting = true, + .delete_all_but_requesting_auth = true, + }, + .qwr = &ble_qwr, + }; + + __cmock_sd_ble_gatts_service_add_Stub(stub_sd_ble_gatts_service_add); + + __cmock_sd_ble_gatts_characteristic_add_Stub(stub_sd_ble_gatts_characteristic_add); + + __cmock_ble_qwr_attr_register_ExpectAndReturn( + &ble_qwr, CTRLPT_VALUE_HANDLE, NRF_SUCCESS); + + nrf_err = ble_bms_init(&ble_bms, &bms_cfg); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +void test_ble_bms_init_support_none(void) +{ + uint32_t nrf_err; + struct ble_bms_config bms_cfg = { + .evt_handler = bms_evt_handler, + .feature_sec.lv = FEATURE_SEC_LV, + .feature_sec.sm = FEATURE_SEC_SM, + .ctrlpt_sec.lv = CTRLPT_SEC_LV, + .ctrlpt_sec.sm = CTRLPT_SEC_SM, + .feature = { + }, + .qwr = &ble_qwr, + }; + + __cmock_sd_ble_gatts_service_add_Stub(stub_sd_ble_gatts_service_add); + + __cmock_sd_ble_gatts_characteristic_add_Stub(stub_sd_ble_gatts_characteristic_add); + + __cmock_ble_qwr_attr_register_ExpectAndReturn( + &ble_qwr, CTRLPT_VALUE_HANDLE, NRF_SUCCESS); + + nrf_err = ble_bms_init(&ble_bms, &bms_cfg); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +void test_ble_bms_init_support_all(void) +{ + uint32_t nrf_err; + struct ble_bms_config bms_cfg = { + .evt_handler = bms_evt_handler, + .feature_sec.lv = FEATURE_SEC_LV, + .feature_sec.sm = FEATURE_SEC_SM, + .ctrlpt_sec.lv = CTRLPT_SEC_LV, + .ctrlpt_sec.sm = CTRLPT_SEC_SM, + .feature = { + .delete_all = true, + .delete_all_auth = true, + .delete_requesting = true, + .delete_requesting_auth = true, + .delete_all_but_requesting = true, + .delete_all_but_requesting_auth = true, + }, + .qwr = &ble_qwr, + }; + + __cmock_sd_ble_gatts_service_add_Stub(stub_sd_ble_gatts_service_add); + + __cmock_sd_ble_gatts_characteristic_add_Stub(stub_sd_ble_gatts_characteristic_add); + + __cmock_ble_qwr_attr_register_ExpectAndReturn( + &ble_qwr, CTRLPT_VALUE_HANDLE, NRF_SUCCESS); + + nrf_err = ble_bms_init(&ble_bms, &bms_cfg); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +void test_ble_bms_auth_response_error_null(void) +{ + uint32_t nrf_err; + + nrf_err = ble_bms_auth_response(NULL, true); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); +} + +void test_ble_bms_auth_response_error_invalid_state(void) +{ + uint32_t nrf_err; + + nrf_err = ble_bms_auth_response(&ble_bms, true); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); +} + +void test_ble_bms_on_ble_evt_rw_authorize_req_uninitialized(void) +{ + ble_evt_t evt = { + .header = { + .evt_id = BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST, + }, + .evt.gatts_evt = { + .conn_handle = CONN_HANDLE, + .params.authorize_request = { + .type = BLE_GATTS_AUTHORIZE_TYPE_WRITE, + .request.write = { + .op = BLE_GATTS_OP_WRITE_REQ, + .handle = CTRLPT_VALUE_HANDLE, + .len = 4, + }, + }, + }, + }; + + /* evt.evt.gatts_evt.params.authorize_request.request.write.data is set as an array of + * length 1, so the variable data cannot be set above. + */ + uint8_t data[] = { BLE_BMS_OP_DEL_ALL_BONDS_ON_SERVER_LE_ONLY, 'a', 'b', 'c' }; + + memcpy(evt.evt.gatts_evt.params.authorize_request.request.write.data, + data, sizeof(data)); + + /* These should return immediately. */ + ble_bms_on_ble_evt(&evt, NULL); + ble_bms_on_ble_evt(NULL, &ble_bms); + + /* Unhandled as ctrlpt handle is not registered. */ + ble_bms_on_ble_evt(&evt, &ble_bms); +} + +void test_ble_bms_on_ble_evt_rw_authorize_req_error(void) +{ + ble_evt_t evt = { + .header = { + .evt_id = BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST, + }, + .evt.gatts_evt = { + .conn_handle = CONN_HANDLE, + .params.authorize_request = { + .type = BLE_GATTS_AUTHORIZE_TYPE_WRITE, + .request.write = { + .op = BLE_GATTS_OP_WRITE_REQ, + .handle = CTRLPT_VALUE_HANDLE, + }, + }, + }, + }; + + test_ble_bms_init(); + + /* evt.evt.gatts_evt.params.authorize_request.request.write.data is set as an array of + * length 1, so the variable data cannot be set above. + */ + uint8_t data[] = { BLE_BMS_OP_DEL_ALL_BONDS_ON_SERVER_LE_ONLY, 'a', 'b', 'c' }; + + memcpy(evt.evt.gatts_evt.params.authorize_request.request.write.data, + data, sizeof(data)); + evt.evt.gatts_evt.params.authorize_request.request.write.len = sizeof(data); + + __cmock_sd_ble_gatts_rw_authorize_reply_Stub(stub_sd_ble_gatts_rw_authorize_reply_error); + + ble_bms_on_ble_evt(&evt, &ble_bms); + + TEST_ASSERT_EQUAL(BLE_BMS_EVT_ERROR, last_evt.evt_type); +} + +void test_ble_bms_on_ble_evt_rw_authorize_req(void) +{ + ble_evt_t evt = { + .header = { + .evt_id = BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST, + }, + .evt.gatts_evt = { + .conn_handle = CONN_HANDLE, + .params.authorize_request = { + .type = BLE_GATTS_AUTHORIZE_TYPE_WRITE, + .request.write = { + .op = BLE_GATTS_OP_WRITE_REQ, + .handle = CTRLPT_VALUE_HANDLE, + }, + }, + }, + }; + + test_ble_bms_init(); + + /* These should return immediately. */ + ble_bms_on_ble_evt(&evt, NULL); + ble_bms_on_ble_evt(NULL, &ble_bms); + + /* Empty data is rejected */ + __cmock_sd_ble_gatts_rw_authorize_reply_Stub(stub_sd_ble_gatts_rw_authorize_reply_rejected); + ble_bms_on_ble_evt(&evt, &ble_bms); + + /* evt.evt.gatts_evt.params.authorize_request.request.write.data is set as an array of + * length 1, so the variable data cannot be set above. + */ + uint8_t data_bad_op[] = { 0xba, 'a', 'b', 'c' }; + + memcpy(evt.evt.gatts_evt.params.authorize_request.request.write.data, + data_bad_op, sizeof(data_bad_op)); + evt.evt.gatts_evt.params.authorize_request.request.write.len = sizeof(data_bad_op); + + __cmock_sd_ble_gatts_rw_authorize_reply_Stub(stub_sd_ble_gatts_rw_authorize_reply_rejected); + + ble_bms_on_ble_evt(&evt, &ble_bms); + + /* evt.evt.gatts_evt.params.authorize_request.request.write.data is set as an array of + * length 1, so the variable data cannot be set above. + */ + uint8_t data[] = { BLE_BMS_OP_DEL_ALL_BONDS_ON_SERVER_LE_ONLY, 'a', 'b', 'c' }; + + memcpy(evt.evt.gatts_evt.params.authorize_request.request.write.data, + data, sizeof(data)); + evt.evt.gatts_evt.params.authorize_request.request.write.len = sizeof(data); + + __cmock_sd_ble_gatts_rw_authorize_reply_Stub(stub_sd_ble_gatts_rw_authorize_reply_accepted); + + ble_bms_on_ble_evt(&evt, &ble_bms); + + __cmock_sd_ble_gatts_rw_authorize_reply_Stub(stub_sd_ble_gatts_rw_authorize_reply_accepted); + + ble_bms_on_ble_evt(&evt, &ble_bms); + + /* This is allowed without valid passkey in init config */ + uint8_t data_device_only[] = { BLE_BMS_OP_DEL_BOND_REQ_DEVICE_LE_ONLY }; + + memcpy(evt.evt.gatts_evt.params.authorize_request.request.write.data, + data_device_only, sizeof(data_device_only)); + evt.evt.gatts_evt.params.authorize_request.request.write.len = sizeof(data); + + __cmock_sd_ble_gatts_rw_authorize_reply_Stub(stub_sd_ble_gatts_rw_authorize_reply_accepted); + + ble_bms_on_ble_evt(&evt, &ble_bms); + + uint8_t data_other_only[] = { BLE_BMS_OP_DEL_ALL_BUT_ACTIVE_BOND_LE_ONLY, 'a', 'b', 'c'}; + + memcpy(evt.evt.gatts_evt.params.authorize_request.request.write.data, + data_other_only, sizeof(data_other_only)); + evt.evt.gatts_evt.params.authorize_request.request.write.len = sizeof(data); + __cmock_sd_ble_gatts_rw_authorize_reply_Stub(stub_sd_ble_gatts_rw_authorize_reply_accepted); + + ble_bms_on_ble_evt(&evt, &ble_bms); + + uint8_t data_inval[] = { BLE_BMS_OP_DEL_ALL_BONDS_ON_SERVER_LE_ONLY, 'b', 'a', 'd' }; + + memcpy(evt.evt.gatts_evt.params.authorize_request.request.write.data, + data_inval, sizeof(data_inval)); + evt.evt.gatts_evt.params.authorize_request.request.write.len = sizeof(data); + __cmock_sd_ble_gatts_rw_authorize_reply_Stub(stub_sd_ble_gatts_rw_authorize_reply_rejected); + + ble_bms_on_ble_evt(&evt, &ble_bms); +} + +void test_ble_bms_on_qwr_evt_authorize_req_error_op_failed(void) +{ + uint32_t nrf_err; + struct ble_qwr_evt evt = { + .evt_type = BLE_QWR_EVT_AUTH_REQUEST, + .auth_req = { + .attr_handle = CTRLPT_VALUE_HANDLE, + }, + }; + + ble_qwr.conn_handle = CONN_HANDLE; + + test_ble_bms_init(); + + __cmock_ble_qwr_value_get_Stub(stub_ble_qwr_value_get_error_no_mem); + + nrf_err = ble_bms_on_qwr_evt(&ble_bms, &ble_qwr, &evt); + TEST_ASSERT_EQUAL(BLE_BMS_OPERATION_FAILED, nrf_err); +} + +void test_ble_bms_on_qwr_evt_authorize_req_error(void) +{ + uint32_t nrf_err; + struct ble_qwr_evt evt = { + .evt_type = BLE_QWR_EVT_AUTH_REQUEST, + .auth_req = { + .attr_handle = CTRLPT_VALUE_HANDLE, + }, + }; + + ble_qwr.conn_handle = CONN_HANDLE; + + test_ble_bms_init(); + + evt.evt_type = BLE_QWR_EVT_EXECUTE_WRITE; + + __cmock_sd_ble_gatts_value_get_Stub(stub_sd_ble_gatts_value_get_error_not_found); + + nrf_err = ble_bms_on_qwr_evt(&ble_bms, &ble_qwr, &evt); + TEST_ASSERT_EQUAL(BLE_BMS_OPERATION_FAILED, nrf_err); + + evt.evt_type = BLE_QWR_EVT_AUTH_REQUEST; + + __cmock_sd_ble_gatts_value_get_Stub(stub_sd_ble_gatts_value_get_accepted); + __cmock_ble_qwr_value_get_Stub(stub_ble_qwr_value_get_auth_req_no_data); + + nrf_err = ble_bms_on_qwr_evt(&ble_bms, &ble_qwr, &evt); + TEST_ASSERT_EQUAL(BLE_BMS_OPERATION_FAILED, nrf_err); + + /* Successful request before execute write*/ + __cmock_ble_qwr_value_get_Stub(stub_ble_qwr_value_get_auth_req_accepted); + + nrf_err = ble_bms_on_qwr_evt(&ble_bms, &ble_qwr, &evt); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + evt.evt_type = BLE_QWR_EVT_EXECUTE_WRITE; + + __cmock_sd_ble_gatts_value_get_Stub(stub_sd_ble_gatts_value_get_accepted); + + nrf_err = ble_bms_on_qwr_evt(&ble_bms, &ble_qwr, &evt); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +void test_ble_bms_on_qwr_evt_error_failed(void) +{ + uint32_t nrf_err; + struct ble_qwr_evt evt = { + .evt_type = BLE_QWR_EVT_AUTH_REQUEST, + .auth_req = { + .attr_handle = CTRLPT_VALUE_HANDLE, + }, + }; + + ble_qwr.conn_handle = CONN_HANDLE; + + test_ble_bms_init(); + + __cmock_ble_qwr_value_get_Stub(stub_ble_qwr_value_get_auth_req_accepted); + + nrf_err = ble_bms_on_qwr_evt(&ble_bms, &ble_qwr, &evt); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + evt.evt_type = BLE_QWR_EVT_EXECUTE_WRITE; + + __cmock_sd_ble_gatts_value_get_Stub(stub_sd_ble_gatts_value_get_error_not_found); + + nrf_err = ble_bms_on_qwr_evt(&ble_bms, &ble_qwr, &evt); + TEST_ASSERT_EQUAL(BLE_BMS_OPERATION_FAILED, nrf_err); +} + +void test_ble_bms_on_qwr_evt_authorize_req(void) +{ + uint32_t nrf_err; + struct ble_qwr_evt evt = { + .evt_type = BLE_QWR_EVT_AUTH_REQUEST, + .auth_req = { + .attr_handle = CTRLPT_VALUE_HANDLE, + }, + }; + + ble_qwr.conn_handle = CONN_HANDLE; + + test_ble_bms_init(); + + /* These should return immediately. */ + nrf_err = ble_bms_on_qwr_evt(NULL, &ble_qwr, &evt); + TEST_ASSERT_EQUAL(BLE_QWR_REJ_REQUEST_ERR_CODE, nrf_err); + nrf_err = ble_bms_on_qwr_evt(&ble_bms, NULL, &evt); + TEST_ASSERT_EQUAL(BLE_QWR_REJ_REQUEST_ERR_CODE, nrf_err); + nrf_err = ble_bms_on_qwr_evt(&ble_bms, &ble_qwr, NULL); + TEST_ASSERT_EQUAL(BLE_QWR_REJ_REQUEST_ERR_CODE, nrf_err); + + __cmock_ble_qwr_value_get_Stub(stub_ble_qwr_value_get_auth_req_accepted); + + nrf_err = ble_bms_on_qwr_evt(&ble_bms, &ble_qwr, &evt); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + evt.evt_type = BLE_QWR_EVT_EXECUTE_WRITE; + + __cmock_sd_ble_gatts_value_get_Stub(stub_sd_ble_gatts_value_get_accepted); + __cmock_sd_ble_gatts_rw_authorize_reply_Stub(stub_sd_ble_gatts_rw_authorize_reply_accepted); + + nrf_err = ble_bms_on_qwr_evt(&ble_bms, &ble_qwr, &evt); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +void setUp(void) +{ + memset(&ble_bms, 0, sizeof(struct ble_bms)); + memset(&last_evt, 0, sizeof(struct ble_bms_evt)); + last_evt.evt_type = 100; /* 0 is a valid event type */ +} + +extern int unity_main(void); + +int main(void) +{ + return unity_main(); +} diff --git a/tests/subsys/bluetooth/services/ble_bms/testcase.yaml b/tests/subsys/bluetooth/services/ble_bms/testcase.yaml new file mode 100644 index 0000000000..21def991f1 --- /dev/null +++ b/tests/subsys/bluetooth/services/ble_bms/testcase.yaml @@ -0,0 +1,4 @@ +tests: + bluetooth.ble_bms: + platform_allow: native_sim + tags: unittest