diff --git a/CODEOWNERS b/CODEOWNERS index d9d36f5305..cc1f3ade93 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -53,6 +53,7 @@ /lib/bluetooth/ble_qwr/ @nrfconnect/ncs-bm /lib/bluetooth/ble_racp/ @nrfconnect/ncs-bm /lib/bluetooth/ble_radio_notification/ @nrfconnect/ncs-bm +/lib/bluetooth/ble_scan/ @nrfconnect/ncs-bm /lib/bluetooth/peer_manager/ @nrfconnect/ncs-bm /lib/bm_buttons/ @nrfconnect/ncs-bm /lib/bm_timer/ @nrfconnect/ncs-bm @@ -98,6 +99,7 @@ /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 +/tests/lib/bluetooth/ble_scan/ @nrfconnect/ncs-bm /tests/subsys/bluetooth/services/ @nrfconnect/ncs-bm @nrfconnect/ncs-bm-test /tests/subsys/fs/bm_zms/ @nrfconnect/ncs-bm @rghaddab /tests/subsys/kmu/ @nrfconnect/ncs-eris diff --git a/doc/nrf-bm/api/api.rst b/doc/nrf-bm/api/api.rst index fc79992291..0f8b3e9648 100644 --- a/doc/nrf-bm/api/api.rst +++ b/doc/nrf-bm/api/api.rst @@ -57,6 +57,15 @@ Bluetooth LE Radio Notification library :inner: :members: +.. _api_ble_scan: + +Bluetooth LE Scan library +========================= + +.. doxygengroup:: ble_scan + :inner: + :members: + .. _api_bm_buttons: Bare Metal Buttons library @@ -154,6 +163,15 @@ Record Access Control Point .. _api_sensorsim: +Scan library +============ + +.. doxygengroup:: ble_scan + :inner: + :members: + +.. _api_ble_scan: + Sensor data simulator library ============================= diff --git a/doc/nrf-bm/libraries/bluetooth/ble_scan.rst b/doc/nrf-bm/libraries/bluetooth/ble_scan.rst new file mode 100644 index 0000000000..40adf1ccbf --- /dev/null +++ b/doc/nrf-bm/libraries/bluetooth/ble_scan.rst @@ -0,0 +1,261 @@ +.. _lib_ble_scan: + +Bluetooth: Scan +############### + +.. contents:: + :local: + :depth: 2 + +The Bluetooth® Low Energy Scan library handles scanning for advertising Bluetooth LE devices. +You can use it to find an advertising device and establish a connection with it. +You can narrow down the scan to a device of a specific type using scan filters or the whitelist. + +Overview +******** + +You can configure this library in one of the following modes: + +* Simple mode without using filters or the whitelist. +* Advanced mode that allows you to use advanced filters and the whitelist. + +The module can automatically establish a connection on a filter match or when identifying a whitelisted device. + +The library registers as a Bluetooth LE event observer using the :c:macro:`NRF_SDH_BLE_OBSERVER` macro and handles the relevant Bluetooth LE events from the SoftDevice. + +Configuration +************* + +To enable the library, set the :kconfig:option:`CONFIG_BLE_SCAN` Kconfig option. + +The library provides the following Kconfig configuration options: + +* :kconfig:option:`CONFIG_BLE_SCAN_BUFFER_SIZE` - Maximum size of an advertising event. +* :kconfig:option:`CONFIG_BLE_SCAN_NAME_MAX_LEN` - Maximum size for the name to search in the advertisement report. +* :kconfig:option:`CONFIG_BLE_SCAN_SHORT_NAME_MAX_LEN` - Maximum size of the short name to search for in the advertisement report. +* :kconfig:option:`CONFIG_BLE_SCAN_FILTER` - Enabling filters for the scanning module. +* :kconfig:option:`CONFIG_BLE_SCAN_NAME_COUNT` - Maximum number of name filters. +* :kconfig:option:`CONFIG_BLE_SCAN_APPEARANCE_COUNT` - Maximum number of appearance filters. +* :kconfig:option:`CONFIG_BLE_SCAN_ADDRESS_COUNT` - Maximum number of address filters. +* :kconfig:option:`CONFIG_BLE_SCAN_SHORT_NAME_COUNT` - Maximum number of short name filters. +* :kconfig:option:`CONFIG_BLE_SCAN_UUID_COUNT` - Maximum number of filters for UUIDs. +* :kconfig:option:`CONFIG_BLE_SCAN_SCAN_INTERVAL` - Determines the scan interval in units of 0.625 millisecond. +* :kconfig:option:`CONFIG_BLE_SCAN_SCAN_DURATION` - Duration of a scanning session in units of 10 ms, if set to 0, the scanning continues until it is explicitly disabled. +* :kconfig:option:`CONFIG_BLE_SCAN_SCAN_WINDOW` - Determines the scanning window in units of 0.625 milliseconds. +* :kconfig:option:`CONFIG_BLE_SCAN_SLAVE_LATENCY` - Determines the slave latency in counts of connection events. +* :kconfig:option:`CONFIG_BLE_SCAN_MIN_CONNECTION_INTERVAL` - Determines the minimum connection interval in units of 1.25 milliseconds. +* :kconfig:option:`CONFIG_BLE_SCAN_MAX_CONNECTION_INTERVAL` - Determines the maximum connection interval in units of 1.25 milliseconds. +* :kconfig:option:`CONFIG_BLE_SCAN_SUPERVISION_TIMEOUT` - Determines the supervision time-out in units of 10 millisecond. + +Initialization +============== + +The module is initialized by calling the :c:func:`ble_scan_init` function. +The application can provide an event handler to reveice events to inform about a filter or whitelist match, or about a connection error during the automatic connection. + +Simple initialization +===================== + +You can use the simple initialization with the default scanning and connection parameters when you want the library to work in the simple mode without filtering and the whitelist. + + +.. code:: c + + BLE_SCAN_DEF(ble_scan); + + void scan_event_handler_func(struct ble_scan_evt const *scan_evt); + + uint32_t nrf_err; + struct ble_scan_config scan_cfg = { + .scan_param = BLE_SCAN_INIT_SCAN_PARAM_DEFAULT, + .conn_param = BLE_SCAN_INIT_CONN_PARAM_DEFAULT, + .evt_handler = scan_event_handler_func, + }; + + nrf_err = ble_scan_init(&ble_scan, &scan_cfg); + if (nrf_err) { + LOG_ERR("Failed to initialie scan module, nrf_error %#x", nrf_err); + } + + +Advanced initialization +======================= + +The advanced initialization provides a larger configuration set for the library. +It is required when using scan filters or the whitelist. + +.. code:: c + + BLE_SCAN_DEF(ble_scan); + + void scan_event_handler_func(struct ble_scan_evt const *scan_evt); + + uint32_t nrf_err; + struct ble_scan_config scan_cfg = { + .scan_param = { + .active = 0x01, + .interval = NRF_BLE_SCAN_SCAN_INTERVAL, + .window = NRF_BLE_SCAN_SCAN_WINDOW, + .filter_policy = BLE_GAP_SCAN_FP_WHITELIST, + .timeout = SCAN_DURATION_WHITELIST, + .scan_phys = BLE_GAP_PHY_1MBPS, + .extended = true, + }, + .conn_param = BLE_SCAN_INIT_CONN_PARAM_DEFAULT, + .connect_if_match = true, + .conn_cfg_tag = APP_BLE_CONN_CFG_TAG, + .evt_handler = scan_event_handler_func, + }; + + nrf_err = ble_scan_init(&ble_scan, &scan_cfg); + if (nrf_err) { + LOG_ERR("Failed to initialie scan module, nrf_error %#x", nrf_err); + } + +.. note:: + + When setting connection-specific configurations with the :c:func:`sd_ble_cfg_set` function, you must create a tag for each configuration. + If your application uses the library with automatic connection, this tag must be provided when calling the :c:func:`sd_ble_gap_scan_start` or the :c:func:`sd_ble_gap_connect` function. + +Usage +***** + +The application can start scanning by calling the :c:func:`ble_scan_start` function. + +The scan parameters can be updated by calling the :c:func:`ble_scan_params_set` function. + +To stop scanning, call the :c:func:`ble_scan_stop` function. + +The library resumes scanning after receiving advertising reports. +Scanning stops if the module establishes a connection automatically, or if the application calls the :c:func:`ble_scan_stop` or the :c:func:`sd_ble_gap_connect` function. + +.. note:: + + When you use the :c:func:`ble_scan_params_set` function during the ongoing scan, scanning is stopped. + To resume scanning, use the :c:func:`ble_scan_start` function. + +Whitelist +========= + +The whitelist stores information about all the device connections and bonding. +If you enable the whitelist, the application receives advertising packets only from the devices that are on the whitelist. +An advertising package from a whitelisted device generates the :c:macro:`NRF_BLE_SCAN_EVT_WHITELIST_ADV_REPORT` event. + +.. note:: + + When using the whitelist, filters are inactive. + +.. caution:: + + If you use the whitelist, you must pass the event handler during the module initialization. + The initial scanning with whitelist generates a :c:macro:`NRF_BLE_SCAN_EVT_WHITELIST_REQUEST` event. + The application must react to this event by either setting up the whitelist or switching off the whitelist scan. + Otherwise, an error is reported when the scan starts. + +Filters +======= + +The module can set scanning filters of different type and mode. +When a filter is matched, it generates a :c:macro:`NRF_BLE_SCAN_EVT_FILTER_MATCH` event to the main application. +If the filter matching is enabled and no filter is matched, a :c:macro:`NRF_BLE_SCAN_EVT_NOT_FOUND` event is generated. + +The available filter types are: + +* **Name** - Filter set to the target name. + The maximum length of the name corresponds to :kconfig:option:`CONFIG_BLE_SCAN_NAME_MAX_LEN`. + The maximum number of filters of this type corresponds to :kconfig:option:`CONFIG_BLE_SCAN_NAME_COUNT`. +* **Short name** - Filter set to the short target name. + The maximum length of the name corresponds to :kconfig:option:`CONFIG_BLE_SCAN_SHORT_NAME_MAX_LEN`. + The maximum number of filters of this type corresponds to :kconfig:option:`CONFIG_BLE_SCAN_SHORT_NAME_COUNT`. +* **Address** - Filter set to the target address. + The maximum number of filters of this type corresponds to :kconfig:option:`CONFIG_BLE_SCAN_ADDRESS_COUNT`. +* **UUID** - Filter set to the target UUID. + The maximum number of filters of this type corresponds to :kconfig:option:`CONFIG_BLE_SCAN_UUID_COUNT`. +* **Appearance** - Filter set to the target appearance. + The maximum number of filters of this type corresponds to :kconfig:option:`CONFIG_BLE_SCAN_APPEARANCE_COUNT`. + +The following two filter modes are available: + +* **Normal** - Only one of the filters set, regardless of the type, must be matched to generate an event. +* **Multifilter** - At least one filter from each filter type you set must be matched to generate an event. + For UUID filters, all specified UUIDs must match in this mode. + Enable multifilter by setting the :c:macro:`match_all` argument to true when calling the :c:func:`ble_scan_filters_enable` function. + +Multifilter example: + +Several filters are set for name, address, UUID, and appearance. +To generate the :c:macro:`NRF_BLE_SCAN_EVT_FILTER_MATCH` event, the following types must match: + +* One of the address filters. +* One of the name filters. +* One of the appearance filters. +* All UUID filters. + +Otherwise, the :c:macro:`NRF_BLE_SCAN_EVT_NOT_FOUND` event is generated. + +You can enable filters by calling the :c:func:`ble_scan_filters_enable` function after initialization. +You can activate filters for one filter type, or for a combination of several filter types. + +.. code:: c + + BLE_SCAN_DEF(ble_scan); + + uint8_t addr[BLE_GAP_ADDR_LEN] = {0xa, 0xd, 0xd, 0x4, 0xe, 0x5}; + char *device_name = "my_device"; + + /* See above code snippet for initialization */ + + /* Enable filter for name and address in normal mode */ + nrf_err = ble_scan_filters_enable(&ble_scan, BLE_SCAN_NAME_FILTER | BLE_SCAN_ADDR_FILTER, false); + if (nrf_err) { + LOG_ERR("Failed to enable scan filters, nrf_error %#x", nrf_err); + } + + /* Add address to scan filter */ + nrf_err = ble_scan_filter_set(&ble_scan, BLE_SCAN_ADDR_FILTER, addr); + if (nrf_err) { + LOG_ERR("Failed to add address scan filter, nrf_error %#x", nrf_err); + } + + /* Add name to scan filter */ + nrf_err = ble_scan_filter_set(&ble_scan, BLE_SCAN_NAME_FILTER, device_name); + if (nrf_err) { + LOG_ERR("Failed to add name scan filter, nrf_error %#x", nrf_err); + } + + /* Start scanning */ + nrf_err = ble_scan_start(&ble_scan); + if (nrf_err) { + LOG_ERR("Failed to start scan, nrf_error %#x", nrf_err); + } + + /* Start scanning */ + ble_scan_stop(&ble_scan); + + /* Disable filters */ + nrf_err = ble_scan_filters_disable(&ble_scan); + if (nrf_err) { + LOG_ERR("Failed to disable scan filters, nrf_error %#x", nrf_err); + } + + /* Remove all scan filters */ + nrf_err = ble_scan_all_filter_remove(&ble_scan); + if (nrf_err) { + LOG_ERR("Failed to remove scan filters, nrf_error %#x", nrf_err); + } + +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_scan.h` +| Source files: :file:`lib/ble_scan/` + +:ref:`Bluetooth LE Scan library API reference ` diff --git a/include/bm/bluetooth/ble_scan.h b/include/bm/bluetooth/ble_scan.h new file mode 100644 index 0000000000..1ac9738027 --- /dev/null +++ b/include/bm/bluetooth/ble_scan.h @@ -0,0 +1,576 @@ +/* + * Copyright (c) 2018 - 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @file + * + * @defgroup ble_scan Scan Library + * @{ + * @brief Library for handling the BLE scanning. + * + * @details The Scan Library handles the BLE scanning for your application. + * The library offers several criteria for filtering the devices available for connection, + * and it can also work in the simple mode without using the filtering. + * If an event handler is provided, your main application can react to a filter match or to + * the need of setting the whitelist. The library can also be configured to automatically connect + * after it matches a filter or a device from the whitelist. + * + * @note The Scan library also supports applications with a multicentral link. + */ + +#ifndef BLE_SCAN_H__ +#define BLE_SCAN_H__ + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Macro for defining a ble_scan instance. + * + * @param _name Name of the instance. + * @hideinitializer + */ +#define BLE_SCAN_DEF(_name) \ + static struct ble_scan _name; \ + extern void ble_scan_on_ble_evt(const ble_evt_t *ble_evt, void *ctx); \ + NRF_SDH_BLE_OBSERVER(_name##_obs, ble_scan_on_ble_evt, &_name, HIGH) + +/** + * @brief Scan events. + * + * @details These events are propagated to the main application if a handler is provided during the + * initialization of the Scan library. + * + * @note @ref BLE_SCAN_EVT_WHITELIST_REQUEST cannot be ignored if whitelist is used. + */ +enum ble_scan_evt_type { + /** + * @brief A filter is matched or all filters are matched in the multifilter mode. + */ + BLE_SCAN_EVT_FILTER_MATCH, + /** + * @brief Request the whitelist from the main application. + * + * For whitelist scanning to work, the whitelist must be set when this event occurs. + */ + BLE_SCAN_EVT_WHITELIST_REQUEST, + /** + * @brief Send notification to the main application when a device from the whitelist is + * found. + */ + BLE_SCAN_EVT_WHITELIST_ADV_REPORT, + /** + * @brief The filter was not matched for the scan data. + */ + BLE_SCAN_EVT_NOT_FOUND, + /** + * @brief Scan timeout. + */ + BLE_SCAN_EVT_SCAN_TIMEOUT, + /** + * @brief Error occurred when establishing the connection. + * + * In this event, an error is passed from the function call @ref sd_ble_gap_connect. + */ + BLE_SCAN_EVT_CONNECTING_ERROR, + /** + * @brief Connected to device. + */ + BLE_SCAN_EVT_CONNECTED, + /** + * @brief Error. + */ + BLE_SCAN_EVT_ERROR, +}; + +/** + * @defgroup BLE_SCAN_FILTER_MODE Filter modes + * @{ + */ + +/* Filters the device name. */ + #define BLE_SCAN_NAME_FILTER (0x01) +/* Filters the device address. */ +#define BLE_SCAN_ADDR_FILTER (0x02) +/* Filters the UUID. */ +#define BLE_SCAN_UUID_FILTER (0x04) +/* Filters the appearance. */ +#define BLE_SCAN_APPEARANCE_FILTER (0x08) +/* Filters the device short name. */ +#define BLE_SCAN_SHORT_NAME_FILTER (0x10) +/* @} + */ + +/** + * @brief Scan short name. + */ +struct ble_scan_short_name { + /** + * @brief Pointer to the short name. + */ + char const *short_name; + /** + * @brief Minimum length of the short name. + */ + uint8_t short_name_min_len; +}; + +/** + * @brief Filter status. + */ +struct ble_scan_filter_match { + /** Set to 1 if name filter is matched. */ + uint8_t name_filter_match: 1; + /** Set to 1 if address filter is matched. */ + uint8_t address_filter_match: 1; + /** Set to 1 if uuid filter is matched. */ + uint8_t uuid_filter_match: 1; + /** Set to 1 if appearance filter is matched. */ + uint8_t appearance_filter_match: 1; + /** Set to 1 if short name filter is matched. */ + uint8_t short_name_filter_match: 1; +}; + +/** + * @brief Scan library event. + * + * @details This is used to send library event data to the main application when an event occurs. + */ +struct ble_scan_evt { + /** Type of event. */ + enum ble_scan_evt_type evt_type; + /** GAP scanning parameters. These parameter are needed to establish connection. */ + ble_gap_scan_params_t const *scan_params; + union { + /** Scan filter match. */ + struct { + /** Event structure for @ref BLE_GAP_EVT_ADV_REPORT. This data + * allows the main application to establish connection. + */ + ble_gap_evt_adv_report_t const *adv_report; + /** Matching filters. Information about matched filters. */ + struct ble_scan_filter_match filter_match; + } filter_match; + /** Timeout event parameters. */ + ble_gap_evt_timeout_t timeout; + /** Advertising report event parameters for whitelist. */ + struct { + /** Advertising report */ + ble_gap_evt_adv_report_t const *report; + } whitelist_adv_report; + /** Advertising report event parameters when filter is not found. */ + struct { + /** Advertising report */ + ble_gap_evt_adv_report_t const *report; + } not_found; + /** Connected event parameters. */ + struct { + /** Connected event parameters. */ + ble_gap_evt_connected_t const *connected; + /** Connection handle of the device on which the event occurred. */ + uint16_t conn_handle; + } connected; + /** Error event when connecting. Propagates the error code returned by + * @ref sd_ble_gap_scan_start. + */ + struct { + /** Indicates success or failure of an API procedure. In case of + * failure, a comprehensive error code indicating the cause or reason + * for failure is provided. + */ + int reason; + } connecting_err; + /** Error event. */ + struct { + /** Error reason */ + uint32_t reason; + } error; + } params; +}; + +/** + * @brief BLE Scan event handler type. + */ +typedef void (*ble_scan_evt_handler_t)(struct ble_scan_evt const *scan_evt); + +#if defined(CONFIG_BLE_SCAN_FILTER) + +#if (CONFIG_BLE_SCAN_NAME_COUNT > 0) +/** Scan name filter */ +struct ble_scan_name_filter { + /** Names that the main application will scan for, + * and that will be advertised by the peripherals. + */ + char target_name[CONFIG_BLE_SCAN_NAME_COUNT][CONFIG_BLE_SCAN_NAME_MAX_LEN]; + /** Number of target names. */ + uint8_t name_cnt; + /** Flag to inform about enabling or disabling this filter. */ + bool name_filter_enabled; +}; +#endif + +#if (CONFIG_BLE_SCAN_SHORT_NAME_COUNT > 0) +/** Scan short name filter */ +struct ble_scan_short_name_filter { + struct { + /** Short names that the main application will scan for, and that will be + * advertised by the peripherals. + */ + char short_target_name[CONFIG_BLE_SCAN_SHORT_NAME_MAX_LEN]; + /** Minimum length of the short name. */ + uint8_t short_name_min_len; + } short_name[CONFIG_BLE_SCAN_SHORT_NAME_COUNT]; + /** Number of short target names. */ + uint8_t name_cnt; + /** Flag to inform about enabling or disabling this filter. */ + bool short_name_filter_enabled; +}; +#endif + +#if (CONFIG_BLE_SCAN_ADDRESS_COUNT > 0) +/** Scan address filter */ +struct ble_scan_addr_filter { + /** Addresses in the same format as the format used by the SoftDevice that the + * main application will scan for, and that will be advertised by the peripherals. + */ + ble_gap_addr_t target_addr[CONFIG_BLE_SCAN_ADDRESS_COUNT]; + /** Number of target addresses. */ + uint8_t addr_cnt; + /** Flag to inform about enabling or disabling this filter. */ + bool addr_filter_enabled; +}; +#endif + +#if (CONFIG_BLE_SCAN_UUID_COUNT > 0) +/** Scan UUID filter */ +struct ble_scan_uuid_filter { + /** UUIDs that the main application will scan for, and that will be advertised by + * the peripherals. + */ + ble_uuid_t uuid[CONFIG_BLE_SCAN_UUID_COUNT]; + /** Number of target UUIDs in list. */ + uint8_t uuid_cnt; + /** Flag to inform about enabling or disabling this filter. */ + bool uuid_filter_enabled; +}; +#endif + +#if (CONFIG_BLE_SCAN_APPEARANCE_COUNT > 0) +/** Scan appearance filter */ +struct ble_scan_appearance_filter { + /** Apperances that the main application will scan for, and that will be advertised by the + * peripherals. + */ + uint16_t appearance[CONFIG_BLE_SCAN_APPEARANCE_COUNT]; + /** Number of appearances in list. */ + uint8_t appearance_cnt; + /** Flag to inform about enabling or disabling this filter.*/ + bool appearance_filter_enabled; +}; +#endif + +/** + * @brief Filter data. + * + * @details This contains all filter data and the information about enabling and disabling + * any type of filters. Flag all_filter_mode informs about the filter mode. If this flag is set, + * then all types of enabled filters must be matched for the library to send a notification to the + * main application. Otherwise, it is enough to match one of the filters to send a notification. + */ +struct ble_scan_filters { +#if (CONFIG_BLE_SCAN_NAME_COUNT > 0) + /** Name filter data. */ + struct ble_scan_name_filter name_filter; +#endif +#if (CONFIG_BLE_SCAN_SHORT_NAME_COUNT > 0) + /** Short name filter data. */ + struct ble_scan_short_name_filter short_name_filter; +#endif +#if (CONFIG_BLE_SCAN_ADDRESS_COUNT > 0) + /** Address filter data. */ + struct ble_scan_addr_filter addr_filter; +#endif +#if (CONFIG_BLE_SCAN_UUID_COUNT > 0) + /** UUID filter data. */ + struct ble_scan_uuid_filter uuid_filter; +#endif +#if (CONFIG_BLE_SCAN_APPEARANCE_COUNT > 0) + /** Appearance filter data. */ + struct ble_scan_appearance_filter appearance_filter; +#endif + /** Filter mode. If true, all set filters must be matched to generate an event. */ + bool all_filters_mode; +}; + +#endif /* CONFIG_BLE_SCAN_FILTER */ + +#define BLE_SCAN_INIT_SCAN_PARAM_DEFAULT \ +{ \ + .active = 1, \ + .interval = CONFG_BLE_SCAN_SCAN_INTERVAL, \ + .window = CONFIG_BLE_SCAN_SCAN_WINDOW, \ + .timeout = CONFIG_BLE_SCAN_SCAN_DURATION, \ + .filter_policy = BLE_GAP_SCAN_FP_ACCEPT_ALL, \ + .scan_phys = BLE_GAP_PHY_1MBPS, \ +} + +#define BLE_SCAN_INIT_CONN_PARAM_DEFAULT \ +{ \ + .conn_sup_timeout = BLE_GAP_CP_CONN_SUP_TIMEOUT_MIN, \ + .min_conn_interval = CONFIG_BLE_SCAN_MIN_CONNECTION_INTERVAL, \ + .max_conn_interval = CONFIG_BLE_SCAN_MAX_CONNECTION_INTERVAL, \ + .slave_latency = (uint16_t)CONFIG_BLE_SCAN_SLAVE_LATENCY, \ +} + +/** + * @brief Scan instance configuration. + */ +struct ble_scan_config { + /* BLE GAP scan parameters required to initialize the module. */ + ble_gap_scan_params_t scan_param; + /* If set to true, the module automatically connects after a filter + * match or successful identification of a device from the whitelist. + */ + bool connect_if_match; + /* Connection parameters. */ + ble_gap_conn_params_t conn_param; + /* Variable to keep track of what connection settings will be used + * if a filer match or a whitelist match results in a connection. + */ + uint8_t conn_cfg_tag; + /** Handler for the scanning events. */ + ble_scan_evt_handler_t evt_handler; +}; + + +/** + * @brief Scan library instance with options for the different scanning modes. + * + * @details This structure stores all library settings. It is used to enable or disable scanning + * modes and to configure filters. + */ +struct ble_scan { +#if defined(CONFIG_BLE_SCAN_FILTER) + /** Filter data. */ + struct ble_scan_filters scan_filters; +#endif + /** If set to true, the library automatically connects after a filter + * match or successful identification of a device from the whitelist. + */ + bool connect_if_match; + /** Connection parameters. */ + ble_gap_conn_params_t conn_params; + /** Variable to keep track of what connection settings will be used + * if a filer match or a whitelist match results in a connection. + */ + uint8_t conn_cfg_tag; + /** GAP scanning parameters. */ + ble_gap_scan_params_t scan_params; + /** Handler for the scanning events. */ + ble_scan_evt_handler_t evt_handler; + /** Buffer where advertising reports will be stored by the SoftDevice. */ + uint8_t scan_buffer_data[CONFIG_BLE_SCAN_BUFFER_SIZE]; + /** Structure-stored pointer to the buffer where advertising + * reports will be stored by the SoftDevice. + */ + ble_data_t scan_buffer; +}; + +/** + * @brief Check if the whitelist is used. + * + * @param[in] scan_ctx Scan library instance. + * + * @return true if whitelist is used. + */ +bool is_whitelist_used(struct ble_scan const *const scan_ctx); + +/** + * @brief Initialize the Scan library. + * + * @param[out] scan Scan library instance. This structure must be supplied + * by the application. It is initialized by this function and is later used to identify this + * particular library instance. + * + * @param[in] config Configuration parameters. + * + * @retval NRF_SUCCESS If initialization was successful. + * @retval NRF_ERROR_NULL When a NULL pointer is passed as input. + */ +int ble_scan_init(struct ble_scan *scan, struct ble_scan_config *config); + +/** + * @brief Start scanning. + * + * @details This function starts the scanning according to the configuration set during the + * initialization. + * + * @param[in] scan_ctx Scan library instance. + * + * @retval NRF_SUCCESS If scanning started. Otherwise, an error code is returned. + * @retval NRF_ERROR_NULL If NULL pointer is passed as input. + * + * @return This API propagates the error code returned by the + * SoftDevice API @ref sd_ble_gap_scan_start. + */ +int ble_scan_start(struct ble_scan const *const scan_ctx); + +/** + * @brief Stop scanning. + */ +void ble_scan_stop(struct ble_scan const *const scan_ctx); + +#if defined(CONFIG_BLE_SCAN_FILTER) + +/** + * @brief Enable filtering. + * + * @details The filters can be combined with each other. For example, you can enable one filter or + * several filters. For example, (BLE_SCAN_NAME_FILTER | BLE_SCAN_UUID_FILTER) + * enables UUID and name filters. + * + * @param[in] mode Filter mode: @c ble_scan_filter_type. + * @param[in] match_all If this flag is set, all types of enabled filters must be + * matched before generating @ref BLE_SCAN_EVT_FILTER_MATCH to the main application. Otherwise, + * it is enough to match one filter to trigger the filter match event. + * @param[in] scan_ctx Scan library instance. + * + * @retval NRF_SUCCESS If the filters are enabled successfully. + * @retval NRF_ERROR_INVALID_PARAM If the filter mode is invalid. + * @retval NRF_ERROR_NULL If a NULL pointer is passed as input. + */ +int ble_scan_filters_enable(struct ble_scan *const scan_ctx, uint8_t mode, bool match_all); + +/** + * @brief Disable filtering. + * + * @details Disable all filters. + * Even if the automatic connection establishing is enabled, + * the connection will not be established with the first device found after + * this function is called. + * + * @param[in] scan_ctx Scan library instance. + * + * @retval NRF_SUCCESS If filters are disabled successfully. + * @retval NRF_ERROR_NULL If a NULL pointer is passed as input. + */ +int ble_scan_filters_disable(struct ble_scan *const scan_ctx); + +/** + * @brief Get filter status. + * + * @details This function returns the filter setting and whether it is enabled or disabled. + + * @param[out] status Filter status. + * @param[in] scan_ctx Scan library instance. + * + * @retval NRF_SUCCESS If filter status is returned. + * @retval NRF_ERROR_NULL If a NULL pointer is passed as input. + */ +int ble_scan_filter_get(struct ble_scan *const scan_ctx, struct ble_scan_filters *status); + +/** + * @brief Add scan filter. + * + * @details This function adds a new filter by type @ref ble_scan_filter_type_t. + * The filter will be added if the number of filters of a given type does not exceed @ref + * CONFIG_BLE_SCAN_UUID_COUNT, @ref CONFIG_BLE_SCAN_NAME_COUNT, @ref + * CONFIG_BLE_SCAN_ADDRESS_COUNT, or @ref CONFIG_BLE_SCAN_APPEARANCE_COUNT, depending on + * the filter type, and if the same filter has not already been set. + * + * @param[in,out] scan_ctx Scan library instance. + * @param[in] type Filter type. + * @param[in] data The filter data to add. + * + * @retval NRF_SUCCESS If the filter is added successfully. + * @retval NRF_ERROR_NULL If a NULL pointer is passed as input. + * @retval NRF_ERROR_DATA_SIZE If the name filter length is too long. Maximum name filter + * length corresponds to @ref NRF_BLE_SCAN_NAME_MAX_LEN. + * @retval NRF_ERROR_NO_MEMORY If the number of available filters is exceeded. + * @retval NRF_ERROR_INVALID_PARAM If the filter type is incorrect. Available filter types: + * @ref ble_scan_filter_type_t. + * @retval BLE_ERROR_GAP_INVALID_BLE_ADDR If the BLE address type is invalid. + */ +int ble_scan_filter_set(struct ble_scan *const scan_ctx, uint8_t type, + void const *data); + +/** + * @brief Remove all filters. + * + * @details The function removes all previously set filters. + * + * @note After using this function the filters are still enabled. + * + * @param[in,out] scan_ctx Scan library instance. + * + * @retval NRF_SUCCESS If all filters are removed successfully. + */ +int ble_scan_all_filter_remove(struct ble_scan *const scan_ctx); + +#endif /* CONFIG_BLE_SCAN_FILTER */ + +/** + * @brief Set the scanning parameters. + * + * @details Use this function to change scanning parameters. During the parameter change + * the scan is stopped. To resume scanning, use @ref ble_scan_start. + * Scanning parameters can be set to NULL. If so, the default static + * configuration is used. For example, use this function when the @ref + * BLE_SCAN_EVT_WHITELIST_ADV_REPORT event is generated. The generation of this + * event means that there is a risk that the whitelist is empty. In such case, + * this function can change the scanning parameters, so that the whitelist is + * not used, and you avoid the error caused by scanning with the whitelist when + * there are no devices on the whitelist. + * + * @param[in,out] scan_ctx Scan library instance. + * @param[in] scan_param GAP scanning parameters. Can be initialized as NULL. + * + * @retval NRF_SUCCESS If parameters are changed successfully. + * @retval NRF_ERROR_NULL If a NULL pointer is passed as input. + */ +int ble_scan_params_set(struct ble_scan *const scan_ctx, ble_gap_scan_params_t const *scan_param); + +/** + * @brief Handler for BLE stack events. + * + * @param[in] ble_evt BLE event. + * @param[in,out] scan Scan library instance. + */ +void ble_scan_on_ble_evt(ble_evt_t const *ble_evt, void *scan); + +/** + * @brief Convert the raw address to the SoftDevice GAP address. + * + * @details This function inverts the byte order in the address. If you enter the address as it is + * displayed (for example, on a phone screen from left to right), you must use + * this function to convert the address to the SoftDevice address type. + * + * @note This function does not decode an address type. + * + * @param[out] gap_addr The Bluetooth Low Energy address. + * @param[in] addr Address to be converted to the SoftDevice address. + * + * @retval NRF_ERROR_NULL If a NULL pointer is passed as input. + * @retval NRF_SUCCESS If the address is copied and converted successfully. + */ +int ble_scan_copy_addr_to_sd_gap_addr(ble_gap_addr_t *gap_addr, + uint8_t const addr[BLE_GAP_ADDR_LEN]); + +#ifdef __cplusplus +} +#endif + +#endif /* BLE_SCAN_H__ */ + +/* @} */ diff --git a/lib/bluetooth/CMakeLists.txt b/lib/bluetooth/CMakeLists.txt index a29e625f37..37fd22b8fc 100644 --- a/lib/bluetooth/CMakeLists.txt +++ b/lib/bluetooth/CMakeLists.txt @@ -10,5 +10,6 @@ add_subdirectory_ifdef(CONFIG_BLE_CONN_STATE ble_conn_state) 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) +add_subdirectory_ifdef(CONFIG_BLE_SCAN ble_scan) add_subdirectory_ifdef(CONFIG_BLE_QWR ble_qwr) add_subdirectory_ifdef(CONFIG_PEER_MANAGER peer_manager) diff --git a/lib/bluetooth/Kconfig b/lib/bluetooth/Kconfig index 4e7e7007f6..7976ac8988 100644 --- a/lib/bluetooth/Kconfig +++ b/lib/bluetooth/Kconfig @@ -11,6 +11,7 @@ rsource "ble_conn_state/Kconfig" rsource "ble_gq/Kconfig" rsource "ble_racp/Kconfig" rsource "ble_radio_notification/Kconfig" +rsource "ble_scan/Kconfig" rsource "ble_qwr/Kconfig" rsource "peer_manager/Kconfig" diff --git a/lib/bluetooth/ble_scan/CMakeLists.txt b/lib/bluetooth/ble_scan/CMakeLists.txt new file mode 100644 index 0000000000..394b629e20 --- /dev/null +++ b/lib/bluetooth/ble_scan/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_scan.c +) diff --git a/lib/bluetooth/ble_scan/Kconfig b/lib/bluetooth/ble_scan/Kconfig new file mode 100644 index 0000000000..142d938a82 --- /dev/null +++ b/lib/bluetooth/ble_scan/Kconfig @@ -0,0 +1,123 @@ +# +# Copyright (c) 2025 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menuconfig BLE_SCAN + bool "BLE Scan" + depends on SOFTDEVICE_CENTRAL + select EXPERIMENTAL + +if BLE_SCAN + +config BLE_SCAN_BUFFER_SIZE + int "Scan buffer size" + default 31 + help + Maximum size for an advertising set. + +config BLE_SCAN_NAME_MAX_LEN + int "Scan name maximum length" + default 32 + help + Maximum size for the name to search in the advertisement report. + +config BLE_SCAN_SHORT_NAME_MAX_LEN + int "Scan short name maximum length" + default 32 + help + Maximum size of the short name to search for in the advertisement report. + +config BLE_SCAN_FILTER + bool "Enable scan filter" + default y + help + Enabling filters for the scanning module. + +if BLE_SCAN_FILTER + +config BLE_SCAN_NAME_COUNT + int "Scan name count" + default 1 + help + Maximum number of name filters. + +config BLE_SCAN_APPEARANCE_COUNT + int "Scan appearance count" + default 0 + help + Maximum number of appearance filters. + +config BLE_SCAN_ADDRESS_COUNT + int "Scan address count" + default 0 + help + Maximum number of address filters. + +config BLE_SCAN_SHORT_NAME_COUNT + int "Scan short name count" + default 0 + help + Maximum number of short name filters. + +config BLE_SCAN_UUID_COUNT + int "Scan UUID count" + default 0 + help + Maximum number of filters for UUIDs. + +endif # BLE_SCAN_FILTER + +config BLE_SCAN_SCAN_INTERVAL + int "Scanning interval" + default 160 + help + Determines the scan interval in units of 0.625 millisecond. + +config BLE_SCAN_SCAN_DURATION + hex "Scan duration" + default 0x0 + range 0x0 0xFFFF + help + Duration of a scanning session in units of 10 ms. + If set to 0, the scanning continues until it is explicitly disabled. + +config BLE_SCAN_SCAN_WINDOW + int "Scanning window" + default 80 + help + Determines the scanning window in units of 0.625 milliseconds. + +config BLE_SCAN_SLAVE_LATENCY + int "Scan slave latency" + default 0 + help + Determines the slave latency in counts of connection events. + +config BLE_SCAN_MIN_CONNECTION_INTERVAL + hex "Minimum connection interval" + default 0x6 + range 0x6 0xC80 + help + Determines the minimum connection interval in units of 1.25 milliseconds. + +config BLE_SCAN_MAX_CONNECTION_INTERVAL + hex "Maximum connection interval" + default 0x18 + range 0x6 0xC80 + help + Determines the maximum connection interval in units of 1.25 milliseconds. + +config BLE_SCAN_SUPERVISION_TIMEOUT + hex "Scan supervision timeout" + default 0xC80 + range 0xA 0xC80 + help + Determines the supervision time-out in units of 10 millisecond. + +module=BLE_SCAN +module-str=BLE Scan +source "$(ZEPHYR_BASE)/subsys/logging/Kconfig.template.log_config" + +endif # BLE_SCAN diff --git a/lib/bluetooth/ble_scan/ble_scan.c b/lib/bluetooth/ble_scan/ble_scan.c new file mode 100644 index 0000000000..855a8b3d7f --- /dev/null +++ b/lib/bluetooth/ble_scan/ble_scan.c @@ -0,0 +1,910 @@ +/* + * Copyright (c) 2015 - 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include + +#include +#include +#include + +LOG_MODULE_REGISTER(ble_scan, CONFIG_BLE_SCAN_LOG_LEVEL); + +static void ble_scan_default_param_set(struct ble_scan *const scan) +{ + /* Set the default parameters. */ + scan->scan_params.active = 1; + scan->scan_params.interval = CONFIG_BLE_SCAN_SCAN_INTERVAL; + scan->scan_params.window = CONFIG_BLE_SCAN_SCAN_WINDOW; + scan->scan_params.timeout = CONFIG_BLE_SCAN_SCAN_DURATION; + scan->scan_params.filter_policy = BLE_GAP_SCAN_FP_ACCEPT_ALL; + scan->scan_params.scan_phys = BLE_GAP_PHY_1MBPS; +} + +int ble_scan_copy_addr_to_sd_gap_addr(ble_gap_addr_t *gap_addr, + const uint8_t addr[BLE_GAP_ADDR_LEN]) +{ + if (!gap_addr) { + return NRF_ERROR_NULL; + } + + for (uint8_t i = 0; i < BLE_GAP_ADDR_LEN; ++i) { + gap_addr->addr[i] = addr[BLE_GAP_ADDR_LEN - (i + 1)]; + } + + return NRF_SUCCESS; +} + +static void ble_scan_connect_with_target(struct ble_scan const *const scan, + ble_gap_evt_adv_report_t const *const adv_report) +{ + uint32_t nrf_err; + struct ble_scan_evt scan_evt = { + .evt_type = BLE_SCAN_EVT_CONNECTING_ERROR, + }; + + /* Return if the automatic connection is disabled. */ + if (!scan->connect_if_match) { + return; + } + + /* Stop scanning. */ + ble_scan_stop(scan); + + /* Establish connection. */ + nrf_err = sd_ble_gap_connect(&adv_report->peer_addr, &scan->scan_params, + &scan->conn_params, scan->conn_cfg_tag); + if (nrf_err) { + LOG_ERR("Connection failed, nrf_error %#x", nrf_err); + if (scan->evt_handler) { + scan_evt.params.connecting_err.reason = nrf_err; + scan->evt_handler(&scan_evt); + } + } +} + +#if CONFIG_BLE_SCAN_FILTER + +#if (CONFIG_BLE_SCAN_ADDRESS_COUNT > 0) +static bool find_peer_addr(ble_gap_evt_adv_report_t const *const adv_report, + ble_gap_addr_t const *addr) +{ + ble_gap_addr_t const *peer_addr = &adv_report->peer_addr; + + /* Compare addresses.*/ + if (memcmp(addr->addr, peer_addr->addr, sizeof(peer_addr->addr)) == 0) { + return true; + } + + return false; +} + +static bool adv_addr_compare(ble_gap_evt_adv_report_t const *const adv_report, + struct ble_scan const *const scan) +{ + ble_gap_addr_t const *addr = scan->scan_filters.addr_filter.target_addr; + + for (uint8_t i = 0; i < scan->scan_filters.addr_filter.addr_cnt; i++) { + /* Search for address. */ + if (find_peer_addr(adv_report, &addr[i])) { + return true; + } + } + + return false; +} + +static int ble_scan_addr_filter_add(struct ble_scan *const scan, uint8_t const *addr) +{ + ble_gap_addr_t *addr_filter = scan->scan_filters.addr_filter.target_addr; + uint8_t *counter = &scan->scan_filters.addr_filter.addr_cnt; + + /* If no memory for filter. */ + if (*counter >= CONFIG_BLE_SCAN_ADDRESS_COUNT) { + return NRF_ERROR_NO_MEM; + } + + /* Check for duplicated filter.*/ + for (uint8_t i = 0; i < CONFIG_BLE_SCAN_ADDRESS_COUNT; i++) { + if (!memcmp(addr_filter[i].addr, addr, BLE_GAP_ADDR_LEN)) { + return NRF_SUCCESS; + } + } + + for (uint8_t i = 0; i < BLE_GAP_ADDR_LEN; i++) { + addr_filter[*counter].addr[i] = addr[i]; + } + + /* Address type is not used so set it to 0. */ + addr_filter[*counter].addr_type = 0; + + LOG_HEXDUMP_DBG(addr_filter[*counter].addr, BLE_GAP_ADDR_LEN, "Filter set on address"); + + /* Increase the address filter counter. */ + *counter += 1; + + return NRF_SUCCESS; +} +#endif /* CONFIG_BLE_SCAN_ADDRESS_COUNT */ + +#if (CONFIG_BLE_SCAN_NAME_COUNT > 0) +static uint16_t advdata_search(uint8_t const *encoded_data, uint16_t data_len, uint16_t *offset, + uint8_t ad_type) +{ + uint16_t i = 0; + uint16_t new_offset; + uint16_t len; + + if (!encoded_data || !offset) { + return 0; + } + + while ((i + 1 < data_len) && ((i < *offset) || (encoded_data[i + 1] != ad_type))) { + /* Jump to next data. */ + i += (encoded_data[i] + 1); + } + + if (i >= data_len) { + return 0; + } + + new_offset = i + 2; + len = encoded_data[i] ? (encoded_data[i] - 1) : 0; + + if (!len || ((new_offset + len) > data_len)) { + /* Malformed. Zero length or extends beyond provided data. */ + return 0; + } + + *offset = new_offset; + + return len; +} + +static bool advdata_name_find(uint8_t const *encoded_data, uint16_t data_len, + char const *target_name) +{ + uint16_t parsed_name_len; + uint8_t const *parsed_name; + uint16_t data_offset = 0; + + if (!target_name) { + return false; + } + + parsed_name_len = advdata_search(encoded_data, data_len, &data_offset, + BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME); + + parsed_name = &encoded_data[data_offset]; + + if ((data_offset != 0) && (parsed_name_len != 0) && + (strlen(target_name) == parsed_name_len) && + (memcmp(target_name, parsed_name, parsed_name_len) == 0)) { + return true; + } + + return false; +} + +static bool adv_name_compare(ble_gap_evt_adv_report_t const *adv_report, + struct ble_scan const *const scan) +{ + struct ble_scan_name_filter const *name_filter = &scan->scan_filters.name_filter; + uint16_t data_len = adv_report->data.len; + + /* Compare the name found with the name filter. */ + for (uint8_t i = 0; i < scan->scan_filters.name_filter.name_cnt; i++) { + if (advdata_name_find(adv_report->data.p_data, data_len, + name_filter->target_name[i])) { + return true; + } + } + + return false; +} + +static int ble_scan_name_filter_add(struct ble_scan *const scan, char const *name) +{ + uint8_t *counter = &scan->scan_filters.name_filter.name_cnt; + uint8_t name_len = strlen(name); + + /* Check the name length. */ + if ((name_len == 0) || (name_len > CONFIG_BLE_SCAN_NAME_MAX_LEN)) { + return NRF_ERROR_DATA_SIZE; + } + + /* If no memory for filter. */ + if (*counter >= CONFIG_BLE_SCAN_NAME_COUNT) { + return NRF_ERROR_NO_MEM; + } + + /* Check for duplicated filter. */ + for (uint8_t i = 0; i < CONFIG_BLE_SCAN_NAME_COUNT; i++) { + if (!strcmp(scan->scan_filters.name_filter.target_name[i], name)) { + return NRF_SUCCESS; + } + } + + /* Add name to filter. */ + memcpy(scan->scan_filters.name_filter.target_name[(*counter)++], name, strlen(name)); + + LOG_DBG("Adding filter on %s name", name); + + return NRF_SUCCESS; +} +#endif /* CONFIG_BLE_SCAN_NAME_COUNT */ + +#if (CONFIG_BLE_SCAN_SHORT_NAME_COUNT > 0) +static bool adv_short_name_compare(ble_gap_evt_adv_report_t const *const adv_report, + struct ble_scan const *const scan) +{ + struct ble_scan_short_name_filter const *name_filter = + &scan->scan_filters.short_name_filter; + uint16_t data_len = adv_report->data.len; + + /* Compare the name found with the name filters. */ + for (uint8_t i = 0; i < scan->scan_filters.short_name_filter.name_cnt; i++) { + if (ble_adv_data_short_name_find( + adv_report->data.p_data, data_len, + name_filter->short_name[i].short_target_name, + name_filter->short_name[i].short_name_min_len)) { + return true; + } + } + + return false; +} + +static int ble_scan_short_name_filter_add(struct ble_scan *const scan, + struct ble_scan_short_name const *short_name) +{ + uint8_t *counter = &scan->scan_filters.short_name_filter.name_cnt; + struct ble_scan_short_name_filter *short_name_filter = + &scan->scan_filters.short_name_filter; + uint8_t name_len = strlen(short_name->short_name); + + /* Check the name length. */ + if ((name_len == 0) || (name_len > CONFIG_BLE_SCAN_SHORT_NAME_MAX_LEN)) { + return NRF_ERROR_DATA_SIZE; + } + + /* If no memory for filter. */ + if (*counter >= CONFIG_BLE_SCAN_SHORT_NAME_COUNT) { + return NRF_ERROR_NO_MEM; + } + + /* Check for duplicated filter. */ + for (uint8_t i = 0; i < CONFIG_BLE_SCAN_SHORT_NAME_COUNT; i++) { + if (!strcmp(short_name_filter->short_name[i].short_target_name, + short_name->short_name)) { + return NRF_SUCCESS; + } + } + + /* Add name to the filter. */ + short_name_filter->short_name[(*counter)].short_name_min_len = + short_name->short_name_min_len; + memcpy(short_name_filter->short_name[(*counter)++].short_target_name, + short_name->short_name, strlen(short_name->short_name)); + + LOG_DBG("Adding filter on %s name", short_name->short_name); + + return NRF_SUCCESS; +} +#endif /* CONFIG_BLE_SCAN_SHORT_NAME_COUNT */ + +#if (CONFIG_BLE_SCAN_UUID_COUNT > 0) +static bool adv_uuid_compare(ble_gap_evt_adv_report_t const *const adv_report, + struct ble_scan const *const scan) +{ + struct ble_scan_uuid_filter const *uuid_filter = &scan->scan_filters.uuid_filter; + bool const all_filters_mode = scan->scan_filters.all_filters_mode; + uint16_t data_len = adv_report->data.len; + uint8_t uuid_match_cnt = 0; + + for (uint8_t i = 0; i < scan->scan_filters.uuid_filter.uuid_cnt; i++) { + + if (ble_adv_data_uuid_find(adv_report->data.p_data, data_len, + &uuid_filter->uuid[i])) { + uuid_match_cnt++; + + /* In the normal filter mode, only one UUID is needed to match. */ + if (!all_filters_mode) { + break; + } + } else if (all_filters_mode) { + break; + } + } + + /* In the multifilter mode, all UUIDs must be found in the advertisement packets. */ + if ((all_filters_mode && (uuid_match_cnt == scan->scan_filters.uuid_filter.uuid_cnt)) || + ((!all_filters_mode) && (uuid_match_cnt > 0))) { + return true; + } + + return false; +} + +static int ble_scan_uuid_filter_add(struct ble_scan *const scan, ble_uuid_t const *uuid) +{ + ble_uuid_t *uuid_filter = scan->scan_filters.uuid_filter.uuid; + uint8_t *counter = &scan->scan_filters.uuid_filter.uuid_cnt; + + /* If no memory. */ + if (*counter >= CONFIG_BLE_SCAN_UUID_COUNT) { + return NRF_ERROR_NO_MEM; + } + + /* Check for duplicated filter.*/ + for (uint8_t i = 0; i < CONFIG_BLE_SCAN_UUID_COUNT; i++) { + if (uuid_filter[i].uuid == uuid->uuid) { + return NRF_SUCCESS; + } + } + + /* Add UUID to the filter. */ + uuid_filter[(*counter)++] = *uuid; + LOG_DBG("Added filter on UUID %#x", uuid->uuid); + + return NRF_SUCCESS; +} +#endif /* CONFIG_BLE_SCAN_UUID_COUNT */ + +#if (CONFIG_BLE_SCAN_APPEARANCE_COUNT) +static bool adv_appearance_compare(ble_gap_evt_adv_report_t const *const adv_report, + struct ble_scan const *const scan) +{ + struct ble_scan_appearance_filter const *appearance_filter = + &scan->scan_filters.appearance_filter; + uint16_t data_len = adv_report->data.len; + + /* Verify if the advertised appearance matches the provided appearance. */ + for (uint8_t i = 0; i < scan->scan_filters.appearance_filter.appearance_cnt; i++) { + if (ble_adv_data_appearance_find(adv_report->data.p_data, data_len, + &appearance_filter->appearance[i])) { + return true; + } + } + return false; +} + +static int ble_scan_appearance_filter_add(struct ble_scan *const scan, uint16_t appearance) +{ + uint16_t *appearance_filter = scan->scan_filters.appearance_filter.appearance; + uint8_t *counter = &scan->scan_filters.appearance_filter.appearance_cnt; + + /* If no memory. */ + if (*counter >= CONFIG_BLE_SCAN_APPEARANCE_COUNT) { + return NRF_ERROR_NO_MEM; + } + + /* Check for duplicated filter. */ + for (uint8_t i = 0; i < CONFIG_BLE_SCAN_APPEARANCE_COUNT; i++) { + if (appearance_filter[i] == appearance) { + return NRF_SUCCESS; + } + } + + /* Add appearance to the filter. */ + appearance_filter[(*counter)++] = appearance; + LOG_DBG("Added filter on appearance %#x", appearance); + return NRF_SUCCESS; +} +#endif /* CONFIG_BLE_SCAN_APPEARANCE_COUNT */ + +int ble_scan_filter_set(struct ble_scan *const scan, uint8_t type, + void const *data) +{ + if (!scan || !data) { + return NRF_ERROR_NULL; + } + + switch (type) { +#if (CONFIG_BLE_SCAN_NAME_COUNT > 0) + case BLE_SCAN_NAME_FILTER: { + char *name = (char *)data; + + return ble_scan_name_filter_add(scan, name); + } +#endif + +#if (CONFIG_BLE_SCAN_SHORT_NAME_COUNT > 0) + case BLE_SCAN_SHORT_NAME_FILTER: { + struct ble_scan_short_name *short_name = (struct ble_scan_short_name *)data; + + return ble_scan_short_name_filter_add(scan, short_name); + } +#endif + +#if (CONFIG_BLE_SCAN_ADDRESS_COUNT > 0) + case BLE_SCAN_ADDR_FILTER: { + uint8_t *addr = (uint8_t *)data; + + return ble_scan_addr_filter_add(scan, addr); + } +#endif + +#if (CONFIG_BLE_SCAN_UUID_COUNT > 0) + case BLE_SCAN_UUID_FILTER: { + ble_uuid_t *uuid = (ble_uuid_t *)data; + + return ble_scan_uuid_filter_add(scan, uuid); + } +#endif + +#if (CONFIG_BLE_SCAN_APPEARANCE_COUNT > 0) + case BLE_SCAN_APPEARANCE_FILTER: { + uint16_t appearance = *((uint16_t *)data); + + return ble_scan_appearance_filter_add(scan, appearance); + } +#endif + + default: + return NRF_ERROR_INVALID_PARAM; + } +} + +int ble_scan_all_filter_remove(struct ble_scan *const scan) +{ +#if (CONFIG_BLE_SCAN_NAME_COUNT > 0) + struct ble_scan_name_filter *name_filter = &scan->scan_filters.name_filter; + + memset(name_filter->target_name, 0, sizeof(name_filter->target_name)); + name_filter->name_cnt = 0; +#endif + +#if (CONFIG_BLE_SCAN_SHORT_NAME_COUNT > 0) + struct ble_scan_short_name_filter *short_name_filter = + &scan->scan_filters.short_name_filter; + + memset(short_name_filter->short_name, 0, sizeof(short_name_filter->short_name)); + short_name_filter->name_cnt = 0; +#endif + +#if (CONFIG_BLE_SCAN_ADDRESS_COUNT > 0) + struct ble_scan_addr_filter *addr_filter = &scan->scan_filters.addr_filter; + + memset(addr_filter->target_addr, 0, sizeof(addr_filter->target_addr)); + addr_filter->addr_cnt = 0; +#endif + +#if (CONFIG_BLE_SCAN_UUID_COUNT > 0) + struct ble_scan_uuid_filter *uuid_filter = &scan->scan_filters.uuid_filter; + + memset(uuid_filter->uuid, 0, sizeof(uuid_filter->uuid)); + uuid_filter->uuid_cnt = 0; +#endif + +#if (CONFIG_BLE_SCAN_APPEARANCE_COUNT > 0) + struct ble_scan_appearance_filter *appearance_filter = + &scan->scan_filters.appearance_filter; + + memset(appearance_filter->appearance, 0, sizeof(appearance_filter->appearance)); + appearance_filter->appearance_cnt = 0; +#endif + + return NRF_SUCCESS; +} + +int ble_scan_filters_enable(struct ble_scan *const scan, uint8_t mode, bool match_all) +{ + int nrf_err; + struct ble_scan_filters *filters; + + if (!scan) { + return NRF_ERROR_NULL; + } + + /* Check if the mode is correct. */ + if ((!(mode & BLE_SCAN_ADDR_FILTER)) && + (!(mode & BLE_SCAN_NAME_FILTER)) && + (!(mode & BLE_SCAN_UUID_FILTER)) && + (!(mode & BLE_SCAN_SHORT_NAME_FILTER)) && + (!(mode & BLE_SCAN_APPEARANCE_FILTER))) { + return NRF_ERROR_INVALID_PARAM; + } + + /* Disable filters.*/ + nrf_err = ble_scan_filters_disable(scan); + if (nrf_err) { + return nrf_err; + } + + filters = &scan->scan_filters; + + /* Turn on the filters of your choice. */ +#if (CONFIG_BLE_SCAN_ADDRESS_COUNT > 0) + if (mode & BLE_SCAN_ADDR_FILTER) { + filters->addr_filter.addr_filter_enabled = true; + } +#endif + +#if (CONFIG_BLE_SCAN_NAME_COUNT > 0) + if (mode & BLE_SCAN_NAME_FILTER) { + filters->name_filter.name_filter_enabled = true; + } +#endif + +#if (CONFIG_BLE_SCAN_SHORT_NAME_COUNT > 0) + if (mode & BLE_SCAN_SHORT_NAME_FILTER) { + filters->short_name_filter.short_name_filter_enabled = true; + } +#endif + +#if (CONFIG_BLE_SCAN_UUID_COUNT > 0) + if (mode & BLE_SCAN_UUID_FILTER) { + filters->uuid_filter.uuid_filter_enabled = true; + } +#endif + +#if (CONFIG_BLE_SCAN_APPEARANCE_COUNT > 0) + if (mode & BLE_SCAN_APPEARANCE_FILTER) { + filters->appearance_filter.appearance_filter_enabled = true; + } +#endif + + /* Select the filter mode. */ + filters->all_filters_mode = match_all; + + return NRF_SUCCESS; +} + +int ble_scan_filters_disable(struct ble_scan *const scan) +{ + if (!scan) { + return NRF_ERROR_NULL; + } + + /* Disable all filters.*/ +#if (CONFIG_BLE_SCAN_NAME_COUNT > 0) + bool *name_filter_enabled = &scan->scan_filters.name_filter.name_filter_enabled; + *name_filter_enabled = false; +#endif + +#if (CONFIG_BLE_SCAN_ADDRESS_COUNT > 0) + bool *addr_filter_enabled = &scan->scan_filters.addr_filter.addr_filter_enabled; + *addr_filter_enabled = false; +#endif + +#if (CONFIG_BLE_SCAN_UUID_COUNT > 0) + bool *uuid_filter_enabled = &scan->scan_filters.uuid_filter.uuid_filter_enabled; + *uuid_filter_enabled = false; +#endif + +#if (CONFIG_BLE_SCAN_APPEARANCE_COUNT > 0) + bool *appearance_filter_enabled = + &scan->scan_filters.appearance_filter.appearance_filter_enabled; + *appearance_filter_enabled = false; +#endif + + return NRF_SUCCESS; +} + +int ble_scan_filter_get(struct ble_scan *const scan, struct ble_scan_filters *status) +{ + if (!scan || !status) { + return NRF_ERROR_NULL; + } + + *status = scan->scan_filters; + + return NRF_SUCCESS; +} + +#endif /* CONFIG_BLE_SCAN_FILTER */ + +bool is_whitelist_used(struct ble_scan const *const scan) +{ + if (scan->scan_params.filter_policy == BLE_GAP_SCAN_FP_WHITELIST || + scan->scan_params.filter_policy == + BLE_GAP_SCAN_FP_WHITELIST_NOT_RESOLVED_DIRECTED) { + return true; + } + + return false; +} + +int ble_scan_init(struct ble_scan *scan, struct ble_scan_config *config) +{ + if (!scan || !config) { + return NRF_ERROR_NULL; + } + + scan->evt_handler = config->evt_handler; + +#if CONFIG_BLE_SCAN_FILTER + /* Disable all scanning filters. */ + memset(&scan->scan_filters, 0, sizeof(scan->scan_filters)); +#endif + + scan->connect_if_match = config->connect_if_match; + scan->conn_cfg_tag = config->conn_cfg_tag; + scan->scan_params = config->scan_param; + scan->conn_params = config->conn_param; + + /* Assign a buffer where the advertising reports are to be stored by the SoftDevice. */ + scan->scan_buffer.p_data = scan->scan_buffer_data; + scan->scan_buffer.len = CONFIG_BLE_SCAN_BUFFER_SIZE; + + return NRF_SUCCESS; +} + +int ble_scan_params_set(struct ble_scan *const scan, + ble_gap_scan_params_t const *const scan_param) +{ + if (!scan) { + return NRF_ERROR_NULL; + } + + ble_scan_stop(scan); + + if (scan_param) { + /* Assign new scanning parameters. */ + scan->scan_params = *scan_param; + } else { + /* If NULL, use the default static configuration. */ + ble_scan_default_param_set(scan); + } + + LOG_DBG("Scanning parameters have been changed successfully"); + + return NRF_SUCCESS; +} + +int ble_scan_start(struct ble_scan const *const scan) +{ + uint32_t nrf_err; + struct ble_scan_evt scan_evt = { + .evt_type = BLE_SCAN_EVT_WHITELIST_REQUEST, + }; + + if (!scan) { + return NRF_ERROR_NULL; + } + + ble_scan_stop(scan); + + /** If the whitelist is used and the event handler is not NULL, send the whitelist request + * to the main application. + */ + if (is_whitelist_used(scan)) { + if (scan->evt_handler) { + scan->evt_handler(&scan_evt); + } + } + + /* Start the scanning. */ + nrf_err = sd_ble_gap_scan_start(&scan->scan_params, &scan->scan_buffer); + + /* It is okay to ignore NRF_ERROR_INVALID_STATE, because the scan stopped earlier. */ + if (nrf_err && (nrf_err != NRF_ERROR_INVALID_STATE)) { + LOG_ERR("sd_ble_gap_scan_start returned nrf_error %#x", nrf_err); + return nrf_err; + } + LOG_DBG("Scanning"); + + return NRF_SUCCESS; +} + +void ble_scan_stop(struct ble_scan const *const scan) +{ + ARG_UNUSED(scan); + /** It is ok to ignore the function return value here, because this function can return + * NRF_SUCCESS or NRF_ERROR_INVALID_STATE, when app is not in the scanning state. + */ + (void)sd_ble_gap_scan_stop(); +} + +static void ble_scan_on_adv_report(struct ble_scan const *const scan, + ble_gap_evt_adv_report_t const *const adv_report) +{ + struct ble_scan_evt scan_evt = { + .scan_params = &scan->scan_params, + }; + +#if CONFIG_BLE_SCAN_FILTER + uint8_t filter_cnt = 0; + uint8_t filter_match_cnt = 0; +#endif + + /* If the whitelist is used, do not check the filters and return. */ + if (is_whitelist_used(scan)) { + scan_evt.evt_type = BLE_SCAN_EVT_WHITELIST_ADV_REPORT; + scan_evt.params.whitelist_adv_report.report = adv_report; + scan->evt_handler(&scan_evt); + + sd_ble_gap_scan_start(NULL, &scan->scan_buffer); + ble_scan_connect_with_target(scan, adv_report); + + return; + } + +#if CONFIG_BLE_SCAN_FILTER + bool const all_filter_mode = scan->scan_filters.all_filters_mode; + bool is_filter_matched = false; + +#if (CONFIG_BLE_SCAN_ADDRESS_COUNT > 0) + bool const addr_filter_enabled = scan->scan_filters.addr_filter.addr_filter_enabled; +#endif + +#if (CONFIG_BLE_SCAN_NAME_COUNT > 0) + bool const name_filter_enabled = scan->scan_filters.name_filter.name_filter_enabled; +#endif + +#if (CONFIG_BLE_SCAN_SHORT_NAME_COUNT > 0) + bool const short_name_filter_enabled = + scan->scan_filters.short_name_filter.short_name_filter_enabled; +#endif + +#if (CONFIG_BLE_SCAN_UUID_COUNT > 0) + bool const uuid_filter_enabled = scan->scan_filters.uuid_filter.uuid_filter_enabled; +#endif + +#if (CONFIG_BLE_SCAN_APPEARANCE_COUNT > 0) + bool const appearance_filter_enabled = + scan->scan_filters.appearance_filter.appearance_filter_enabled; +#endif + +#if (CONFIG_BLE_SCAN_ADDRESS_COUNT > 0) + /* Check the address filter. */ + if (addr_filter_enabled) { + /* Number of active filters. */ + filter_cnt++; + if (adv_addr_compare(adv_report, scan)) { + /* Number of filters matched. */ + filter_match_cnt++; + /* Information about the filters matched. */ + scan_evt.params.filter_match.filter_match.address_filter_match = true; + is_filter_matched = true; + } + } +#endif + +#if (CONFIG_BLE_SCAN_NAME_COUNT > 0) + /* Check the name filter. */ + if (name_filter_enabled) { + filter_cnt++; + if (adv_name_compare(adv_report, scan)) { + filter_match_cnt++; + + /* Information about the filters matched. */ + scan_evt.params.filter_match.filter_match.name_filter_match = true; + is_filter_matched = true; + } + } +#endif + +#if (CONFIG_BLE_SCAN_SHORT_NAME_COUNT > 0) + if (short_name_filter_enabled) { + filter_cnt++; + if (adv_short_name_compare(adv_report, scan)) { + filter_match_cnt++; + + /* Information about the filters matched. */ + scan_evt.params.filter_match.filter_match.short_name_filter_match = true; + is_filter_matched = true; + } + } +#endif + +#if (CONFIG_BLE_SCAN_UUID_COUNT > 0) + /* Check the UUID filter. */ + if (uuid_filter_enabled) { + filter_cnt++; + if (adv_uuid_compare(adv_report, scan)) { + filter_match_cnt++; + /* Information about the filters matched. */ + scan_evt.params.filter_match.filter_match.uuid_filter_match = true; + is_filter_matched = true; + } + } +#endif + +#if (CONFIG_BLE_SCAN_APPEARANCE_COUNT > 0) + /* Check the appearance filter. */ + if (appearance_filter_enabled) { + filter_cnt++; + if (adv_appearance_compare(adv_report, scan)) { + filter_match_cnt++; + /* Information about the filters matched. */ + scan_evt.params.filter_match.filter_match.appearance_filter_match = true; + is_filter_matched = true; + } + } + + scan_evt.evt_type = BLE_SCAN_EVT_NOT_FOUND; +#endif + + scan_evt.params.filter_match.adv_report = adv_report; + + /** In the multifilter mode, the number of the active filters must equal the number of the + * filters matched to generate the notification. + */ + if (all_filter_mode && (filter_match_cnt == filter_cnt)) { + scan_evt.evt_type = BLE_SCAN_EVT_FILTER_MATCH; + ble_scan_connect_with_target(scan, adv_report); + } + /** In the normal filter mode, only one filter match is needed to generate the notification + * to the main application. + */ + else if ((!all_filter_mode) && is_filter_matched) { + scan_evt.evt_type = BLE_SCAN_EVT_FILTER_MATCH; + ble_scan_connect_with_target(scan, adv_report); + } else { + scan_evt.evt_type = BLE_SCAN_EVT_NOT_FOUND; + scan_evt.params.not_found.report = adv_report; + } + + /* If the event handler is not NULL, notify the main application. */ + if (scan->evt_handler) { + scan->evt_handler(&scan_evt); + } + +#endif /* CONFIG_BLE_SCAN_FILTER*/ + + /* Resume the scanning. */ + (void)sd_ble_gap_scan_start(NULL, &scan->scan_buffer); +} + +static void ble_scan_on_timeout(struct ble_scan const *const scan, + ble_gap_evt_t const *const gap) +{ + ble_gap_evt_timeout_t const *timeout = &gap->params.timeout; + struct ble_scan_evt scan_evt = { + .evt_type = BLE_SCAN_EVT_SCAN_TIMEOUT, + .scan_params = &scan->scan_params, + .params.timeout.src = timeout->src, + }; + + if (timeout->src == BLE_GAP_TIMEOUT_SRC_SCAN) { + LOG_DBG("BLE_GAP_SCAN_TIMEOUT"); + if (scan->evt_handler) { + scan->evt_handler(&scan_evt); + } + } +} + +static void ble_scan_on_connected_evt(struct ble_scan const *const scan, + ble_gap_evt_t const *const gap_evt) +{ + struct ble_scan_evt scan_evt = { + .evt_type = BLE_SCAN_EVT_CONNECTED, + .params.connected.connected = &gap_evt->params.connected, + .params.connected.conn_handle = gap_evt->conn_handle, + .scan_params = &scan->scan_params, + }; + + if (scan->evt_handler) { + scan->evt_handler(&scan_evt); + } +} + +void ble_scan_on_ble_evt(ble_evt_t const *ble_evt, void *contex) +{ + struct ble_scan *scan_data = (struct ble_scan *)contex; + ble_gap_evt_adv_report_t const *adv_report = &ble_evt->evt.gap_evt.params.adv_report; + ble_gap_evt_t const *gap_evt = &ble_evt->evt.gap_evt; + + switch (ble_evt->header.evt_id) { + case BLE_GAP_EVT_ADV_REPORT: + ble_scan_on_adv_report(scan_data, adv_report); + break; + + case BLE_GAP_EVT_TIMEOUT: + ble_scan_on_timeout(scan_data, gap_evt); + break; + + case BLE_GAP_EVT_CONNECTED: + ble_scan_on_connected_evt(scan_data, gap_evt); + break; + + default: + break; + } +} diff --git a/tests/lib/bluetooth/ble_scan/CMakeLists.txt b/tests/lib/bluetooth/ble_scan/CMakeLists.txt new file mode 100644 index 0000000000..b1efe22046 --- /dev/null +++ b/tests/lib/bluetooth/ble_scan/CMakeLists.txt @@ -0,0 +1,64 @@ +# +# 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_scan) + +add_compile_definitions( + SVCALL_AS_NORMAL_FUNCTION + SUPPRESS_INLINE_IMPLEMENTATION + NRF54L15_XXAA + CONFIG_BLE_SCAN_BUFFER_SIZE=31 + CONFIG_BLE_SCAN_NAME_MAX_LEN=32 + CONFIG_BLE_SCAN_SHORT_NAME_MAX_LEN=32 + CONFIG_BLE_SCAN_NAME_COUNT=1 + CONFIG_BLE_SCAN_APPEARANCE_COUNT=1 + CONFIG_BLE_SCAN_ADDRESS_COUNT=1 + CONFIG_BLE_SCAN_SHORT_NAME_COUNT=2 + CONFIG_BLE_SCAN_UUID_COUNT=1 + CONFIG_BLE_SCAN_SCAN_INTERVAL=160 + CONFIG_BLE_SCAN_SCAN_DURATION=0x0000 + CONFIG_BLE_SCAN_SCAN_WINDOW=80 + CONFIG_BLE_SCAN_SLAVE_LATENCY=0 + CONFIG_BLE_SCAN_MIN_CONNECTION_INTERVAL=0x0006 + CONFIG_BLE_SCAN_MAX_CONNECTION_INTERVAL=0x0018 + CONFIG_BLE_SCAN_SUPERVISION_TIMEOUT=0x0C80 + CONFIG_BLE_SCAN_FILTER=1 +) + +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_gap.h + WORD_EXCLUDE + "__STATIC_INLINE") +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(${ZEPHYR_NRF_BM_MODULE_DIR}/include/bm/softdevice_handler/nrf_sdh_ble.h) +cmock_handle(${ZEPHYR_NRF_BM_MODULE_DIR}/include/bm/bluetooth/ble_adv_data.h) + +zephyr_linker_sources(SECTIONS evt_obs.ld) + +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) + +test_runner_generate(src/unity_test.c) +target_sources(app PRIVATE src/unity_test.c) + +target_sources(app + PRIVATE + ${ZEPHYR_NRF_BM_MODULE_DIR}/lib/bluetooth/ble_scan/ble_scan.c +) diff --git a/tests/lib/bluetooth/ble_scan/evt_obs.ld b/tests/lib/bluetooth/ble_scan/evt_obs.ld new file mode 100644 index 0000000000..ba91196245 --- /dev/null +++ b/tests/lib/bluetooth/ble_scan/evt_obs.ld @@ -0,0 +1 @@ +ITERABLE_SECTION_ROM(nrf_sdh_ble_evt_observers, 4) diff --git a/tests/lib/bluetooth/ble_scan/prj.conf b/tests/lib/bluetooth/ble_scan/prj.conf new file mode 100644 index 0000000000..a8788847da --- /dev/null +++ b/tests/lib/bluetooth/ble_scan/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_scan/src/unity_test.c b/tests/lib/bluetooth/ble_scan/src/unity_test.c new file mode 100644 index 0000000000..3a81aa516a --- /dev/null +++ b/tests/lib/bluetooth/ble_scan/src/unity_test.c @@ -0,0 +1,986 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "cmock_ble.h" +#include "cmock_ble_gap.h" +#include "cmock_ble_gatts.h" +#include "cmock_ble_gattc.h" +#include "cmock_nrf_sdh_ble.h" +#include "cmock_ble_adv_data.h" + +#define CONN_HANDLE 1 +#define DEVICE_NAME "my_device" + +#define UUID 0x2312 + +BLE_SCAN_DEF(ble_scan); + +static struct ble_scan_evt scan_event; +static struct ble_scan_evt scan_event_prev; + +/* Invoke the BLE event handlers in ble_conn_params, passing BLE event 'evt'. */ +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); + } +} + +void scan_event_handler_func(struct ble_scan_evt const *scan_evt) +{ + scan_event_prev = scan_event; + scan_event = *scan_evt; +} + +void test_ble_scan_init_error_null(void) +{ + uint32_t nrf_err; + + struct ble_scan_config scan_cfg = { + .evt_handler = scan_event_handler_func, + }; + + nrf_err = ble_scan_init(NULL, NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); + + nrf_err = ble_scan_init(&ble_scan, NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); + + nrf_err = ble_scan_init(NULL, &scan_cfg); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); +} + +void test_ble_scan_init(void) +{ + uint32_t nrf_err; + + struct ble_scan_config scan_cfg = { + .evt_handler = scan_event_handler_func, + }; + + struct ble_scan_config scan_cfg_no_handler = { + .evt_handler = scan_event_handler_func, + }; + + struct ble_scan_config scan_cfg_with_params = { + .scan_param = { + .extended = 1, + .report_incomplete_evts = 1, + .active = 1, + .filter_policy = BLE_GAP_SCAN_FP_ACCEPT_ALL, + .scan_phys = BLE_GAP_PHY_1MBPS, + .interval = CONFIG_BLE_SCAN_SCAN_INTERVAL, + .window = CONFIG_BLE_SCAN_SCAN_WINDOW, + .timeout = CONFIG_BLE_SCAN_SCAN_DURATION, + .channel_mask = {1, 1, 1, 1, 1}, + }, + .conn_param = { + .min_conn_interval = 1, + .max_conn_interval = 10, + .slave_latency = CONFIG_BLE_SCAN_SLAVE_LATENCY, + .conn_sup_timeout = BLE_GAP_CP_CONN_SUP_TIMEOUT_MIN, + }, + .evt_handler = scan_event_handler_func, + }; + + nrf_err = ble_scan_init(&ble_scan, &scan_cfg); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + nrf_err = ble_scan_init(&ble_scan, &scan_cfg_no_handler); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + nrf_err = ble_scan_init(&ble_scan, &scan_cfg_with_params); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +void test_ble_scan_start_error_null(void) +{ + uint32_t nrf_err; + + test_ble_scan_init(); + + nrf_err = ble_scan_start(NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); +} + +void test_ble_scan_start(void) +{ + uint32_t nrf_err; + + test_ble_scan_init(); + + __cmock_sd_ble_gap_scan_stop_IgnoreAndReturn(NRF_SUCCESS); + __cmock_sd_ble_gap_scan_start_ExpectAndReturn(&ble_scan.scan_params, + &ble_scan.scan_buffer, NRF_SUCCESS); + nrf_err = ble_scan_start(&ble_scan); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +void test_ble_scan_filters_enable_error_invalid_param(void) +{ + uint32_t nrf_err; + + test_ble_scan_init(); + + nrf_err = ble_scan_filters_enable(&ble_scan, 0x20, true); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_PARAM, nrf_err); +} + +void test_ble_scan_filters_enable_error_null(void) +{ + uint32_t nrf_err; + + test_ble_scan_init(); + + nrf_err = ble_scan_filters_enable(NULL, BLE_SCAN_ADDR_FILTER, true); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); +} + +void test_ble_scan_filters_enable_all(void) +{ + uint32_t nrf_err; + + test_ble_scan_init(); + + nrf_err = ble_scan_filters_enable(&ble_scan, (BLE_SCAN_NAME_FILTER | + BLE_SCAN_SHORT_NAME_FILTER | + BLE_SCAN_ADDR_FILTER | + BLE_SCAN_UUID_FILTER | + BLE_SCAN_APPEARANCE_FILTER), true); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +void test_ble_scan_filters_disable_error_null(void) +{ + uint32_t nrf_err; + + test_ble_scan_init(); + + nrf_err = ble_scan_filters_disable(NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); +} + +void test_ble_scan_filters_disable(void) +{ + uint32_t nrf_err; + + test_ble_scan_init(); + + nrf_err = ble_scan_filters_disable(&ble_scan); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +void test_ble_scan_filter_set_get(void) +{ + uint32_t nrf_err; + + struct ble_scan_filters ble_scan_filter_data = {0}; + char *device_name = "generic_device"; + + test_ble_scan_init(); + + nrf_err = ble_scan_filter_get(&ble_scan, &ble_scan_filter_data); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(0, ble_scan_filter_data.name_filter.name_cnt); + TEST_ASSERT_FALSE(ble_scan_filter_data.name_filter.name_filter_enabled); + + ble_scan_filters_enable(&ble_scan, (BLE_SCAN_NAME_FILTER | + BLE_SCAN_SHORT_NAME_FILTER | + BLE_SCAN_ADDR_FILTER | + BLE_SCAN_UUID_FILTER | + BLE_SCAN_APPEARANCE_FILTER), true); + + nrf_err = ble_scan_filter_get(&ble_scan, &ble_scan_filter_data); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(0, ble_scan_filter_data.name_filter.name_cnt); + TEST_ASSERT_TRUE(ble_scan_filter_data.name_filter.name_filter_enabled); + + ble_scan_filter_set(&ble_scan, BLE_SCAN_NAME_FILTER, device_name); + + nrf_err = ble_scan_filter_get(&ble_scan, &ble_scan_filter_data); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(1, ble_scan_filter_data.name_filter.name_cnt); + TEST_ASSERT_TRUE(ble_scan_filter_data.name_filter.name_filter_enabled); + TEST_ASSERT_EQUAL_MEMORY(device_name, ble_scan_filter_data.name_filter.target_name[0], + sizeof(device_name)); +} + +void test_ble_scan_filter_set_error_null(void) +{ + uint32_t nrf_err; + char *device_name = "generic_device"; + + test_ble_scan_init(); + + ble_scan_filters_enable(&ble_scan, (BLE_SCAN_NAME_FILTER), true); + + nrf_err = ble_scan_filter_set(NULL, (BLE_SCAN_NAME_FILTER), device_name); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); + + nrf_err = ble_scan_filter_set(&ble_scan, (BLE_SCAN_NAME_FILTER), NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); +} + +void test_ble_scan_filter_set_error_invalid_param(void) +{ + uint32_t nrf_err; + char *device_name = DEVICE_NAME; + + test_ble_scan_init(); + + ble_scan_filters_enable(&ble_scan, BLE_SCAN_NAME_FILTER, true); + + nrf_err = ble_scan_filter_set(&ble_scan, 0, device_name); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_PARAM, nrf_err); +} + +void test_ble_scan_filter_set_name(void) +{ + uint32_t nrf_err; + char *device_name = DEVICE_NAME; + + test_ble_scan_init(); + + ble_scan_filters_enable(&ble_scan, BLE_SCAN_NAME_FILTER, true); + + nrf_err = ble_scan_filter_set(&ble_scan, BLE_SCAN_NAME_FILTER, device_name); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +void test_ble_scan_filter_set_addr(void) +{ + uint32_t nrf_err; + uint8_t addr[BLE_GAP_ADDR_LEN] = {0xa, 0xd, 0xd, 0x4, 0xe, 0x5}; + + test_ble_scan_init(); + + ble_scan_filters_enable(&ble_scan, BLE_SCAN_ADDR_FILTER, true); + + nrf_err = ble_scan_filter_set(&ble_scan, BLE_SCAN_ADDR_FILTER, addr); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +void test_ble_scan_filter_set_addr_enomem(void) +{ + uint32_t nrf_err; + uint8_t addr[BLE_GAP_ADDR_LEN] = {0xa, 0xd, 0xd, 0x4, 0xe, 0x5}; + + test_ble_scan_filter_set_addr(); + + nrf_err = ble_scan_filter_set(&ble_scan, BLE_SCAN_ADDR_FILTER, addr); + TEST_ASSERT_EQUAL(NRF_ERROR_NO_MEM, nrf_err); +} + +void test_ble_scan_filter_set_uuid(void) +{ + uint32_t nrf_err; + ble_uuid_t uuid = { + .uuid = UUID, + .type = BLE_UUID_TYPE_BLE, + }; + + test_ble_scan_init(); + + ble_scan_filters_enable(&ble_scan, BLE_SCAN_UUID_FILTER, true); + + nrf_err = ble_scan_filter_set(&ble_scan, BLE_SCAN_UUID_FILTER, &uuid); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +void test_ble_scan_filter_set_uuid_error_no_mem(void) +{ + uint32_t nrf_err; + ble_uuid_t uuid; + + test_ble_scan_filter_set_uuid(); + + nrf_err = ble_scan_filter_set(&ble_scan, BLE_SCAN_UUID_FILTER, &uuid); + TEST_ASSERT_EQUAL(NRF_ERROR_NO_MEM, nrf_err); +} + +void test_ble_scan_filter_set_appearance(void) +{ + uint32_t nrf_err; + uint16_t appearance = 0xa44e; + + test_ble_scan_init(); + + ble_scan_filters_enable(&ble_scan, BLE_SCAN_APPEARANCE_FILTER, true); + + nrf_err = ble_scan_filter_set(&ble_scan, BLE_SCAN_APPEARANCE_FILTER, &appearance); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +void test_ble_scan_filter_set_appearance_error_no_mem(void) +{ + uint32_t nrf_err; + uint16_t appearance = 0xa44e; + + test_ble_scan_filter_set_appearance(); + + nrf_err = ble_scan_filter_set(&ble_scan, BLE_SCAN_APPEARANCE_FILTER, &appearance); + TEST_ASSERT_EQUAL(NRF_ERROR_NO_MEM, nrf_err); +} + +void test_ble_scan_filter_set_short_name(void) +{ + uint32_t nrf_err; + struct ble_scan_short_name short_name = { + .short_name = "dev", + .short_name_min_len = 2, + }; + + test_ble_scan_init(); + + ble_scan_filters_enable(&ble_scan, BLE_SCAN_SHORT_NAME_FILTER, true); + + nrf_err = ble_scan_filter_set(&ble_scan, BLE_SCAN_SHORT_NAME_FILTER, &short_name); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +void test_ble_scan_filter_set_short_name_error_no_mem(void) +{ + uint32_t nrf_err; + struct ble_scan_short_name short_name = { + .short_name = "dev", + .short_name_min_len = 2, + }; + struct ble_scan_short_name short_name2 = { + .short_name = "dev2", + .short_name_min_len = 2, + }; + + test_ble_scan_filter_set_short_name(); + + /* duplicate filter does not increase count, so the next will also succeed. */ + nrf_err = ble_scan_filter_set(&ble_scan, BLE_SCAN_SHORT_NAME_FILTER, &short_name); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + /* We accept two short names so the second will succeed */ + nrf_err = ble_scan_filter_set(&ble_scan, BLE_SCAN_SHORT_NAME_FILTER, &short_name2); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + nrf_err = ble_scan_filter_set(&ble_scan, BLE_SCAN_SHORT_NAME_FILTER, &short_name2); + TEST_ASSERT_EQUAL(NRF_ERROR_NO_MEM, nrf_err); + +} + +void test_is_whitelist_used(void) +{ + bool nrf_err; + + nrf_err = is_whitelist_used(&ble_scan); + TEST_ASSERT_FALSE(nrf_err); + + ble_scan.scan_params.filter_policy = BLE_GAP_SCAN_FP_WHITELIST; + + nrf_err = is_whitelist_used(&ble_scan); + TEST_ASSERT_TRUE(nrf_err); +} + +void test_ble_scan_all_filter_remove(void) +{ + uint32_t nrf_err; + + nrf_err = ble_scan_all_filter_remove(&ble_scan); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +void test_ble_scan_params_set_error_null(void) +{ + uint32_t nrf_err; + + nrf_err = ble_scan_params_set(NULL, NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); +} + +void test_ble_scan_params_set(void) +{ + uint32_t nrf_err; + + __cmock_sd_ble_gap_scan_stop_IgnoreAndReturn(NRF_SUCCESS); + nrf_err = ble_scan_params_set(&ble_scan, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +void test_ble_scan_copy_addr_to_sd_gap_addr_error_null(void) +{ + uint32_t nrf_err; + + nrf_err = ble_scan_copy_addr_to_sd_gap_addr(NULL, 0); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); +} + +void test_ble_scan_copy_addr_to_sd_gap_addr(void) +{ + uint32_t nrf_err; + uint8_t address[BLE_GAP_ADDR_LEN] = {0}; + ble_gap_addr_t gap_address = {0}; + + nrf_err = ble_scan_copy_addr_to_sd_gap_addr(&gap_address, + address); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +void test_ble_scan_on_ble_evt_adv_report_empty(void) +{ + ble_evt_t ble_evt = { + .header.evt_id = BLE_GAP_EVT_ADV_REPORT, + .evt.gap_evt = { + .conn_handle = CONN_HANDLE, + .params.adv_report = {}, + }, + }; + + __cmock_sd_ble_gap_scan_start_ExpectAndReturn( + NULL, &ble_scan.scan_buffer, NRF_SUCCESS); + + ble_evt_send(&ble_evt); + + test_ble_scan_init(); +} + +void test_ble_scan_on_ble_evt_adv_report_device_name_bad_data(void) +{ + char bad_data[] = "baddata"; + ble_evt_t ble_evt = { + .header.evt_id = BLE_GAP_EVT_ADV_REPORT, + .evt.gap_evt = { + .conn_handle = CONN_HANDLE, + .params.adv_report = { + .data = { + .p_data = bad_data, + .len = sizeof(bad_data), + }, + + }, + }, + }; + + test_ble_scan_init(); + test_ble_scan_filter_set_name(); + + __cmock_sd_ble_gap_scan_start_ExpectAndReturn( + NULL, &ble_scan.scan_buffer, NRF_SUCCESS); + + ble_evt_send(&ble_evt); + TEST_ASSERT_EQUAL(BLE_SCAN_EVT_NOT_FOUND, scan_event.evt_type); + +} + +void test_ble_scan_on_ble_evt_adv_report_device_address_not_found(void) +{ + ble_evt_t ble_evt = { + .header.evt_id = BLE_GAP_EVT_ADV_REPORT, + .evt.gap_evt = { + .conn_handle = CONN_HANDLE, + .params.adv_report = { + .peer_addr = { + .addr = {0xb, 0xa, 0xd, 0x4, 0xd, 0xd}, + }, + }, + }, + }; + + test_ble_scan_init(); + test_ble_scan_filter_set_addr(); + + __cmock_sd_ble_gap_scan_start_ExpectAndReturn( + NULL, &ble_scan.scan_buffer, NRF_SUCCESS); + + ble_evt_send(&ble_evt); + TEST_ASSERT_EQUAL(BLE_SCAN_EVT_NOT_FOUND, scan_event.evt_type); +} + +void test_ble_scan_on_ble_evt_adv_report_device_address(void) +{ + ble_evt_t ble_evt = { + .header.evt_id = BLE_GAP_EVT_ADV_REPORT, + .evt.gap_evt = { + .conn_handle = CONN_HANDLE, + .params.adv_report = { + .peer_addr = { + .addr = {0xa, 0xd, 0xd, 0x4, 0xe, 0x5}, + }, + }, + }, + }; + + test_ble_scan_init(); + test_ble_scan_filter_set_addr(); + + __cmock_sd_ble_gap_scan_start_ExpectAndReturn( + NULL, &ble_scan.scan_buffer, NRF_SUCCESS); + + ble_evt_send(&ble_evt); + TEST_ASSERT_EQUAL(BLE_SCAN_EVT_FILTER_MATCH, scan_event.evt_type); + TEST_ASSERT_EQUAL(1, scan_event.params.filter_match.filter_match.address_filter_match); + TEST_ASSERT_EQUAL(0, scan_event.params.filter_match.filter_match.name_filter_match); + TEST_ASSERT_EQUAL(0, scan_event.params.filter_match.filter_match.short_name_filter_match); + TEST_ASSERT_EQUAL(0, scan_event.params.filter_match.filter_match.appearance_filter_match); + TEST_ASSERT_EQUAL(0, scan_event.params.filter_match.filter_match.uuid_filter_match); + TEST_ASSERT_EQUAL_PTR(&ble_evt.evt.gap_evt.params.adv_report, + scan_event.params.filter_match.adv_report); +} + +void test_ble_scan_on_ble_evt_adv_report_device_name_not_found(void) +{ + uint8_t device_name_data[] = { + 10, BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME, + 'n', 'o', 't', '_', 'm', 'y', '_', 'd', 'e', 'v', 'i', 'c', 'e', + }; + ble_evt_t ble_evt = { + .header.evt_id = BLE_GAP_EVT_ADV_REPORT, + .evt.gap_evt = { + .conn_handle = CONN_HANDLE, + .params.adv_report = { + .data = { + .p_data = device_name_data, + .len = sizeof(device_name_data), + }, + }, + }, + }; + + test_ble_scan_init(); + test_ble_scan_filter_set_name(); + + __cmock_sd_ble_gap_scan_start_ExpectAndReturn( + NULL, &ble_scan.scan_buffer, NRF_SUCCESS); + + ble_evt_send(&ble_evt); + TEST_ASSERT_EQUAL(BLE_SCAN_EVT_NOT_FOUND, scan_event.evt_type); +} + +void test_ble_scan_on_ble_evt_adv_report_device_name(void) +{ + uint8_t device_name_data[] = { + 10, BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME, + 'm', 'y', '_', 'd', 'e', 'v', 'i', 'c', 'e', + }; + ble_evt_t ble_evt = { + .header.evt_id = BLE_GAP_EVT_ADV_REPORT, + .evt.gap_evt = { + .conn_handle = CONN_HANDLE, + .params.adv_report = { + .data = { + .p_data = device_name_data, + .len = sizeof(device_name_data), + }, + }, + }, + }; + + test_ble_scan_init(); + test_ble_scan_filter_set_name(); + + __cmock_sd_ble_gap_scan_start_ExpectAndReturn( + NULL, &ble_scan.scan_buffer, NRF_SUCCESS); + + ble_evt_send(&ble_evt); + TEST_ASSERT_EQUAL(BLE_SCAN_EVT_FILTER_MATCH, scan_event.evt_type); + TEST_ASSERT_EQUAL(0, scan_event.params.filter_match.filter_match.address_filter_match); + TEST_ASSERT_EQUAL(1, scan_event.params.filter_match.filter_match.name_filter_match); + TEST_ASSERT_EQUAL(0, scan_event.params.filter_match.filter_match.short_name_filter_match); + TEST_ASSERT_EQUAL(0, scan_event.params.filter_match.filter_match.appearance_filter_match); + TEST_ASSERT_EQUAL(0, scan_event.params.filter_match.filter_match.uuid_filter_match); + TEST_ASSERT_EQUAL_PTR(&ble_evt.evt.gap_evt.params.adv_report, + scan_event.params.filter_match.adv_report); +} + +void test_ble_scan_on_ble_evt_adv_report_device_short_name_not_found(void) +{ + static const char short_name_exp[] = "dev"; + const uint8_t min_len_exp = 2; + uint8_t dummy_data[] = "hello"; + + ble_evt_t ble_evt = { + .header.evt_id = BLE_GAP_EVT_ADV_REPORT, + .evt.gap_evt = { + .conn_handle = CONN_HANDLE, + .params.adv_report = { + .data = { + .p_data = dummy_data, + .len = sizeof(dummy_data), + }, + }, + }, + }; + + test_ble_scan_init(); + test_ble_scan_filter_set_short_name(); + + __cmock_ble_adv_data_short_name_find_ExpectWithArrayAndReturn( + ble_evt.evt.gap_evt.params.adv_report.data.p_data, 1, + ble_evt.evt.gap_evt.params.adv_report.data.len, + short_name_exp, min_len_exp, false); + + __cmock_sd_ble_gap_scan_start_ExpectAndReturn( + NULL, &ble_scan.scan_buffer, NRF_SUCCESS); + + ble_evt_send(&ble_evt); + TEST_ASSERT_EQUAL(BLE_SCAN_EVT_NOT_FOUND, scan_event.evt_type); +} + +void test_ble_scan_on_ble_evt_adv_report_device_short_name(void) +{ + static const char short_name_exp[] = "dev"; + const uint8_t min_len_exp = 2; + uint8_t dummy_data[] = "hello"; + + ble_evt_t ble_evt = { + .header.evt_id = BLE_GAP_EVT_ADV_REPORT, + .evt.gap_evt = { + .conn_handle = CONN_HANDLE, + .params.adv_report = { + .data = { + .p_data = dummy_data, + .len = sizeof(dummy_data), + }, + }, + }, + }; + + test_ble_scan_init(); + test_ble_scan_filter_set_short_name(); + + __cmock_ble_adv_data_short_name_find_ExpectWithArrayAndReturn( + ble_evt.evt.gap_evt.params.adv_report.data.p_data, 1, + ble_evt.evt.gap_evt.params.adv_report.data.len, + short_name_exp, min_len_exp, true); + + __cmock_sd_ble_gap_scan_start_ExpectAndReturn( + NULL, &ble_scan.scan_buffer, NRF_SUCCESS); + + ble_evt_send(&ble_evt); + TEST_ASSERT_EQUAL(BLE_SCAN_EVT_FILTER_MATCH, scan_event.evt_type); + TEST_ASSERT_EQUAL(0, scan_event.params.filter_match.filter_match.address_filter_match); + TEST_ASSERT_EQUAL(0, scan_event.params.filter_match.filter_match.name_filter_match); + TEST_ASSERT_EQUAL(1, scan_event.params.filter_match.filter_match.short_name_filter_match); + TEST_ASSERT_EQUAL(0, scan_event.params.filter_match.filter_match.appearance_filter_match); + TEST_ASSERT_EQUAL(0, scan_event.params.filter_match.filter_match.uuid_filter_match); + TEST_ASSERT_EQUAL_PTR(&ble_evt.evt.gap_evt.params.adv_report, + scan_event.params.filter_match.adv_report); +} + +void test_ble_scan_on_ble_evt_adv_report_device_appearance_not_found(void) +{ + uint16_t appearance_exp = 0xa44e; + uint8_t dummy_data[] = "hello"; + + ble_evt_t ble_evt = { + .header.evt_id = BLE_GAP_EVT_ADV_REPORT, + .evt.gap_evt = { + .conn_handle = CONN_HANDLE, + .params.adv_report = { + .data = { + .p_data = dummy_data, + .len = sizeof(dummy_data), + }, + }, + }, + }; + + test_ble_scan_init(); + test_ble_scan_filter_set_appearance(); + + __cmock_ble_adv_data_appearance_find_ExpectWithArrayAndReturn( + ble_evt.evt.gap_evt.params.adv_report.data.p_data, 1, + ble_evt.evt.gap_evt.params.adv_report.data.len, + &appearance_exp, 1, false); + + __cmock_sd_ble_gap_scan_start_ExpectAndReturn(NULL, &ble_scan.scan_buffer, + NRF_SUCCESS); + + ble_evt_send(&ble_evt); + TEST_ASSERT_EQUAL(BLE_SCAN_EVT_NOT_FOUND, scan_event.evt_type); +} + +void test_ble_scan_on_ble_evt_adv_report_device_appearance(void) +{ + uint16_t appearance_exp = 0xa44e; + uint8_t dummy_data[] = "hello"; + + ble_evt_t ble_evt = { + .header.evt_id = BLE_GAP_EVT_ADV_REPORT, + .evt.gap_evt = { + .conn_handle = CONN_HANDLE, + .params.adv_report = { + .data = { + .p_data = dummy_data, + .len = sizeof(dummy_data), + }, + }, + }, + }; + + test_ble_scan_init(); + test_ble_scan_filter_set_appearance(); + + __cmock_ble_adv_data_appearance_find_ExpectWithArrayAndReturn( + ble_evt.evt.gap_evt.params.adv_report.data.p_data, 1, + ble_evt.evt.gap_evt.params.adv_report.data.len, + &appearance_exp, 1, true); + + __cmock_sd_ble_gap_scan_start_ExpectAndReturn(NULL, &ble_scan.scan_buffer, + NRF_SUCCESS); + + ble_evt_send(&ble_evt); + TEST_ASSERT_EQUAL(BLE_SCAN_EVT_FILTER_MATCH, scan_event.evt_type); + TEST_ASSERT_EQUAL(0, scan_event.params.filter_match.filter_match.address_filter_match); + TEST_ASSERT_EQUAL(0, scan_event.params.filter_match.filter_match.name_filter_match); + TEST_ASSERT_EQUAL(0, scan_event.params.filter_match.filter_match.short_name_filter_match); + TEST_ASSERT_EQUAL(1, scan_event.params.filter_match.filter_match.appearance_filter_match); + TEST_ASSERT_EQUAL(0, scan_event.params.filter_match.filter_match.uuid_filter_match); + TEST_ASSERT_EQUAL_PTR(&ble_evt.evt.gap_evt.params.adv_report, + scan_event.params.filter_match.adv_report); +} + +void test_ble_scan_on_ble_evt_adv_report_device_uuid_not_found(void) +{ + const ble_uuid_t uuid_exp = { + .uuid = UUID, + .type = BLE_UUID_TYPE_BLE, + }; + uint8_t dummy_data[] = "hello"; + ble_evt_t ble_evt = { + .header.evt_id = BLE_GAP_EVT_ADV_REPORT, + .evt.gap_evt = { + .conn_handle = CONN_HANDLE, + .params.adv_report = { + .data = { + .p_data = dummy_data, + .len = sizeof(dummy_data), + }, + }, + }, + }; + + test_ble_scan_init(); + test_ble_scan_filter_set_uuid(); + + __cmock_ble_adv_data_uuid_find_ExpectWithArrayAndReturn( + ble_evt.evt.gap_evt.params.adv_report.data.p_data, 1, + ble_evt.evt.gap_evt.params.adv_report.data.len, + &uuid_exp, 1, false); + /* Size of ble_uuid_t is 4, though only 3 is used, so last byte will fail to compare. */ + __cmock_ble_adv_data_uuid_find_IgnoreArg_uuid(); + + __cmock_sd_ble_gap_scan_start_ExpectAndReturn(NULL, &ble_scan.scan_buffer, + NRF_SUCCESS); + + ble_evt_send(&ble_evt); + TEST_ASSERT_EQUAL(BLE_SCAN_EVT_NOT_FOUND, scan_event.evt_type); +} + +void test_ble_scan_on_ble_evt_adv_report_device_uuid(void) +{ + const ble_uuid_t uuid_exp = { + .uuid = UUID, + .type = BLE_UUID_TYPE_BLE, + }; + uint8_t dummy_data[] = "hello"; + ble_evt_t ble_evt = { + .header.evt_id = BLE_GAP_EVT_ADV_REPORT, + .evt.gap_evt = { + .conn_handle = CONN_HANDLE, + .params.adv_report = { + .data = { + .p_data = dummy_data, + .len = sizeof(dummy_data), + }, + }, + }, + }; + + test_ble_scan_init(); + test_ble_scan_filter_set_uuid(); + + __cmock_ble_adv_data_uuid_find_ExpectWithArrayAndReturn( + ble_evt.evt.gap_evt.params.adv_report.data.p_data, 1, + ble_evt.evt.gap_evt.params.adv_report.data.len, + &uuid_exp, 1, true); + /* Size of ble_uuid_t is 4, though only 3 is used, so last byte will fail to compare. */ + __cmock_ble_adv_data_uuid_find_IgnoreArg_uuid(); + + __cmock_sd_ble_gap_scan_start_ExpectAndReturn(NULL, &ble_scan.scan_buffer, + NRF_SUCCESS); + + ble_evt_send(&ble_evt); + TEST_ASSERT_EQUAL(BLE_SCAN_EVT_FILTER_MATCH, scan_event.evt_type); + TEST_ASSERT_EQUAL(0, scan_event.params.filter_match.filter_match.address_filter_match); + TEST_ASSERT_EQUAL(0, scan_event.params.filter_match.filter_match.name_filter_match); + TEST_ASSERT_EQUAL(0, scan_event.params.filter_match.filter_match.short_name_filter_match); + TEST_ASSERT_EQUAL(0, scan_event.params.filter_match.filter_match.appearance_filter_match); + TEST_ASSERT_EQUAL(1, scan_event.params.filter_match.filter_match.uuid_filter_match); + TEST_ASSERT_EQUAL_PTR(&ble_evt.evt.gap_evt.params.adv_report, + scan_event.params.filter_match.adv_report); +} + +void test_ble_scan_on_ble_evt_adv_report_device_uuid_connect(void) +{ + uint32_t nrf_err; + ble_uuid_t uuid = { + .uuid = UUID, + .type = BLE_UUID_TYPE_BLE, + }; + const ble_uuid_t uuid_exp = { + .uuid = UUID, + .type = BLE_UUID_TYPE_BLE, + }; + uint8_t dummy_data[] = "hello"; + ble_evt_t ble_evt = { + .header.evt_id = BLE_GAP_EVT_ADV_REPORT, + .evt.gap_evt = { + .conn_handle = CONN_HANDLE, + .params.adv_report = { + .data = { + .p_data = dummy_data, + .len = sizeof(dummy_data), + }, + .peer_addr = { + .addr = {0xa, 0xd, 0xd, 0x4, 0xe, 0x5}, + .addr_type = BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE, + } + }, + }, + }; + + struct ble_scan_config scan_cfg_with_params = { + .scan_param = { + .extended = 1, + .report_incomplete_evts = 1, + .active = 1, + .filter_policy = BLE_GAP_SCAN_FP_ACCEPT_ALL, + .scan_phys = BLE_GAP_PHY_1MBPS, + .interval = CONFIG_BLE_SCAN_SCAN_INTERVAL, + .window = CONFIG_BLE_SCAN_SCAN_WINDOW, + .timeout = CONFIG_BLE_SCAN_SCAN_DURATION, + .channel_mask = {1, 1, 1, 1, 1}, + }, + .conn_param = { + .min_conn_interval = 1, + .max_conn_interval = 10, + .slave_latency = CONFIG_BLE_SCAN_SLAVE_LATENCY, + .conn_sup_timeout = BLE_GAP_CP_CONN_SUP_TIMEOUT_MIN, + }, + .evt_handler = scan_event_handler_func, + .connect_if_match = true, + .conn_cfg_tag = 5, + }; + + nrf_err = ble_scan_init(&ble_scan, &scan_cfg_with_params); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + ble_scan_filters_enable(&ble_scan, BLE_SCAN_UUID_FILTER, true); + + nrf_err = ble_scan_filter_set(&ble_scan, BLE_SCAN_UUID_FILTER, &uuid); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + __cmock_ble_adv_data_uuid_find_ExpectWithArrayAndReturn( + ble_evt.evt.gap_evt.params.adv_report.data.p_data, 1, + ble_evt.evt.gap_evt.params.adv_report.data.len, + &uuid_exp, 1, true); + /* Size of ble_uuid_t is 4, though only 3 is used, so last byte will fail to compare. */ + __cmock_ble_adv_data_uuid_find_IgnoreArg_uuid(); + + __cmock_sd_ble_gap_scan_stop_ExpectAndReturn(NRF_SUCCESS); + __cmock_sd_ble_gap_connect_ExpectWithArrayAndReturn( + &ble_evt.evt.gap_evt.params.adv_report.peer_addr, 1, + &scan_cfg_with_params.scan_param, 1, + &scan_cfg_with_params.conn_param, 1, + scan_cfg_with_params.conn_cfg_tag, NRF_SUCCESS); + + __cmock_sd_ble_gap_scan_start_ExpectAndReturn(NULL, &ble_scan.scan_buffer, + NRF_SUCCESS); + + ble_evt_send(&ble_evt); + TEST_ASSERT_EQUAL(BLE_SCAN_EVT_FILTER_MATCH, scan_event.evt_type); + + __cmock_ble_adv_data_uuid_find_ExpectWithArrayAndReturn( + ble_evt.evt.gap_evt.params.adv_report.data.p_data, 1, + ble_evt.evt.gap_evt.params.adv_report.data.len, + &uuid_exp, 1, true); + /* Size of ble_uuid_t is 4, though only 3 is used, so last byte will fail to compare. */ + __cmock_ble_adv_data_uuid_find_IgnoreArg_uuid(); + + __cmock_sd_ble_gap_scan_stop_ExpectAndReturn(NRF_SUCCESS); + __cmock_sd_ble_gap_connect_ExpectWithArrayAndReturn( + &ble_evt.evt.gap_evt.params.adv_report.peer_addr, 1, + &scan_cfg_with_params.scan_param, 1, + &scan_cfg_with_params.conn_param, 1, + scan_cfg_with_params.conn_cfg_tag, NRF_ERROR_BUSY); + + __cmock_sd_ble_gap_scan_start_ExpectAndReturn(NULL, &ble_scan.scan_buffer, + NRF_SUCCESS); + + ble_evt_send(&ble_evt); + TEST_ASSERT_EQUAL(BLE_SCAN_EVT_CONNECTING_ERROR, scan_event_prev.evt_type); + TEST_ASSERT_EQUAL(BLE_SCAN_EVT_FILTER_MATCH, scan_event.evt_type); +} + +void test_ble_scan_on_ble_evt_timeout(void) +{ + ble_evt_t ble_evt = { + .header.evt_id = BLE_GAP_EVT_TIMEOUT, + .evt.gap_evt = { + .conn_handle = CONN_HANDLE, + .params.timeout = { + .src = BLE_GAP_TIMEOUT_SRC_SCAN, + }, + }, + }; + + test_ble_scan_init(); + + ble_evt_send(&ble_evt); + TEST_ASSERT_EQUAL(BLE_SCAN_EVT_SCAN_TIMEOUT, scan_event.evt_type); + TEST_ASSERT_EQUAL(BLE_GAP_TIMEOUT_SRC_SCAN, scan_event.params.timeout.src); +} + +void test_ble_scan_on_ble_evt_connected(void) +{ + ble_evt_t ble_evt = { + .header.evt_id = BLE_GAP_EVT_CONNECTED, + .evt.gap_evt = { + .conn_handle = CONN_HANDLE, + .params.connected = { + .role = 1, + }, + }, + }; + + test_ble_scan_init(); + + ble_evt_send(&ble_evt); + TEST_ASSERT_EQUAL(BLE_SCAN_EVT_CONNECTED, scan_event.evt_type); + TEST_ASSERT_EQUAL(1, scan_event.params.connected.connected->role); + TEST_ASSERT_EQUAL(CONN_HANDLE, scan_event.params.connected.conn_handle); +} + +void setUp(void) +{ + memset(&ble_scan, 0, sizeof(ble_scan)); +} + +void tearDown(void) +{ +} + +extern int unity_main(void); + +int main(void) +{ + return unity_main(); +} diff --git a/tests/lib/bluetooth/ble_scan/testcase.yaml b/tests/lib/bluetooth/ble_scan/testcase.yaml new file mode 100644 index 0000000000..6b52d2e9d9 --- /dev/null +++ b/tests/lib/bluetooth/ble_scan/testcase.yaml @@ -0,0 +1,4 @@ +tests: + lib.ble_scan: + platform_allow: native_sim + tags: unittest