diff --git a/.github/workflows/build_samples.yml b/.github/workflows/build_samples.yml index 2d4cfc69e3..fe6cf6e3f1 100644 --- a/.github/workflows/build_samples.yml +++ b/.github/workflows/build_samples.yml @@ -9,9 +9,7 @@ on: jobs: build_samples_job: name: Build all samples within the project - runs-on: - - runs-on=${{ github.run_id }} - - runner=64cpu-linux-x64 + runs-on: ubuntu-22.04 # Keep aligned with target NCS version container: ghcr.io/nrfconnect/sdk-nrf-toolchain:v2.9.1 defaults: @@ -45,7 +43,7 @@ jobs: - name: Build samples working-directory: nrf-bm run: | - west twister -T samples --build-only -v --inline-logs --integration + west twister -T samples/bluetooth/ble_hrs --build-only -v --inline-logs --integration - name: upload-logs uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 diff --git a/.github/workflows/twister.yml b/.github/workflows/twister.yml index ce872824f7..94a6316489 100644 --- a/.github/workflows/twister.yml +++ b/.github/workflows/twister.yml @@ -9,9 +9,7 @@ on: jobs: twister_job: name: Run Twister patch series (PR) - runs-on: - - runs-on=${{ github.run_id }} - - runner=4cpu-linux-x64 + runs-on: ubuntu-22.04 # Keep aligned with target NCS version container: ghcr.io/nrfconnect/sdk-nrf-toolchain:v2.9.1 defaults: diff --git a/CODEOWNERS b/CODEOWNERS index ea8a3dc8ec..84e7a5ce06 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -46,6 +46,7 @@ /lib/bm_zms/ @nrfconnect/ncs-bm @rghaddab /lib/ble_adv/ @nrfconnect/ncs-bm /lib/ble_conn_params/ @nrfconnect/ncs-bm +/lib/ble_conn_state/ @nrfconnect/ncs-bm /lib/ble_gq/ @nrfconnect/ncs-bm /lib/ble_qwr/ @nrfconnect/ncs-bm /lib/ble_racp/ @nrfconnect/ncs-bm @@ -54,6 +55,7 @@ /lib/bm_timer/ @nrfconnect/ncs-bm /lib/boot_banner/ @nrfconnect/ncs-bm /lib/event_scheduler/ @nrfconnect/ncs-bm +/lib/peer_manager/ @nrfconnect/ncs-bm /lib/sensorsim/ @nrfconnect/ncs-bm /lib/zephyr_queue/ @nrfconnect/ncs-pluto diff --git a/include/ble_conn_state.h b/include/ble_conn_state.h new file mode 100644 index 0000000000..6963097de2 --- /dev/null +++ b/include/ble_conn_state.h @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2015 - 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @file + * + * @defgroup ble_conn_state Connection state + * @ingroup ble_sdk_lib + * @{ + * @brief Module for storing data on BLE connections. + * + * @details This module stores certain states for each connection, which can be queried by + * connection handle. The module uses BLE events to keep the states updated. + * + * In addition to the preprogrammed states, this module can also keep track of a number of + * binary user states, or user flags. These are reset to 0 for new connections, but + * otherwise not touched by this module. + * + * This module uses atomics to make the flag operations thread-safe. + * + * @note A connection handle is not immediately invalidated when it is disconnected. Certain states, + * such as the role, can still be queried until the next time a new connection is established + * to any device. + * + */ + +#ifndef BLE_CONN_STATE_H__ +#define BLE_CONN_STATE_H__ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Connection handle statuses. */ +enum ble_conn_state_status { + /** The connection handle is invalid. */ + BLE_CONN_STATUS_INVALID, + /** The connection handle refers to a connection that has been disconnected, + * but not yet invalidated. + */ + BLE_CONN_STATUS_DISCONNECTED, + /** The connection handle refers to an active connection. */ + BLE_CONN_STATUS_CONNECTED, +}; + +/** The maximum number of connections supported. */ +#define BLE_CONN_STATE_MAX_CONNECTIONS BLE_GAP_ROLE_COUNT_COMBINED_MAX + +/** @brief Type used to present a list of conn_handles. */ +struct ble_conn_state_conn_handle_list { + /** The length of the list. */ + uint32_t len; + /** The list of handles. */ + uint16_t conn_handles[BLE_CONN_STATE_MAX_CONNECTIONS]; +}; + +/** + * @brief Function to be called when a flag ID is set. + * + * See @ref ble_conn_state_for_each_set_user_flag. + * + * @param[in] conn_handle The connection the flag is set for. + * @param[in] ctx Arbitrary pointer provided by the caller of + * @ref ble_conn_state_for_each_set_user_flag. + */ +typedef void (*ble_conn_state_user_function_t)(uint16_t conn_handle, void *ctx); + +/** + * @defgroup ble_conn_state_functions BLE connection state functions + * @{ + */ + +/** + * @brief Initialize or reset the module. + * + * @details This function sets all states to their default, + * removing all records of connection handles. + */ +void ble_conn_state_init(void); + +/** + * @brief Check whether a connection handle represents a valid connection. + * + * @details A connection might be valid and have a BLE_CONN_STATUS_DISCONNECTED status. + * Those connections are invalidated after a new connection occurs. + * + * @param[in] conn_handle Handle of the connection. + * + * @retval true If @c conn_handle represents a valid connection, thus a connection for which + we have a record. + * @retval false If @c conn_handle is @ref BLE_GAP_ROLE_INVALID, or if it has never been recorded. + */ +bool ble_conn_state_valid(uint16_t conn_handle); + +/** + * @brief Get the role of the local device in a connection. + * + * @param[in] conn_handle Handle of the connection to get the role for. + * + * @return The role of the local device in the connection (see @ref BLE_GAP_ROLES). + * If conn_handle is not valid, the function returns @ref BLE_GAP_ROLE_INVALID. + */ +uint8_t ble_conn_state_role(uint16_t conn_handle); + +/** + * @brief Get the status of a connection. + * + * @param[in] conn_handle Handle of the connection. + * + * @return The status of the connection. + * If conn_handle is not valid, the function returns @ref BLE_CONN_STATE_INVALID. + */ +enum ble_conn_state_status ble_conn_state_status(uint16_t conn_handle); + +/** + * @brief Check whether a connection is encrypted. + * + * @param[in] conn_handle Handle of connection to get the encryption state for. + * + * @retval true If the connection is encrypted. + * @retval false If the connection is not encrypted or conn_handle is invalid. + */ +bool ble_conn_state_encrypted(uint16_t conn_handle); + +/** + * @brief Check whether a connection encryption is protected from Man in the Middle + * (MITM) attacks. + * + * @param[in] conn_handle Handle of connection to get the MITM state for. + * + * @retval true If the connection is encrypted with MITM protection. + * @retval false If the connection is not encrypted, or encryption is not MITM protected, or + * conn_handle is invalid. + */ +bool ble_conn_state_mitm_protected(uint16_t conn_handle); + +/** + * @brief Check whether a connection was bonded using LE Secure Connections (LESC). + * + * The connection must currently be encrypted. + * + * @note This function will report false if bonded, and the LESC bonding was unauthenticated + * ("Just Works") and happened in a previous connection. To detect such cases as well, check + * the stored bonding key, e.g. in Peer Manager, which has a LESC flag associated with it. + * + * @param[in] conn_handle Handle of connection to get the LESC state for. + * + * @retval true If the connection was bonded using LESC. + * @retval false If the connection has not been bonded using LESC, or @c conn_handle is invalid. + */ +bool ble_conn_state_lesc(uint16_t conn_handle); + +/** + * @brief Get the total number of connections. + * + * @return The total number of valid connections for which the module has a record. + */ +uint32_t ble_conn_state_conn_count(void); + +/** + * @brief Get the total number of connections in which the role of the local + * device is @ref BLE_GAP_ROLE_CENTRAL. + * + * @return The number of connections in which the role of the local device is + * @ref BLE_GAP_ROLE_CENTRAL. + */ +uint32_t ble_conn_state_central_conn_count(void); + +/** + * @brief Get the total number of connections in which the role of the local + * device is @ref BLE_GAP_ROLE_PERIPH. + * + * @return The number of connections in which the role of the local device is + * @ref BLE_GAP_ROLE_PERIPH. + */ +uint32_t ble_conn_state_peripheral_conn_count(void); + +/** + * @brief Get a list of all connection handles for which the module has a record. + * + * @details This function takes into account connections whose state is + * @ref BLE_CONN_STATUS_DISCONNECTED. + * + * @return A list of all valid connection handles for which the module has a record. + */ +struct ble_conn_state_conn_handle_list ble_conn_state_conn_handles(void); + +/** + * @brief Get a list of connection handles in which the role of the local + * device is @ref BLE_GAP_ROLE_CENTRAL. + * + * @details This function takes into account connections whose state is + * @ref BLE_CONN_STATUS_DISCONNECTED. + * + * @return A list of all valid connection handles for which the module has a record and in which + * the role of local device is @ref BLE_GAP_ROLE_CENTRAL. + */ +struct ble_conn_state_conn_handle_list ble_conn_state_central_handles(void); + +/** + * @brief Get the handle for the connection in which the role of the local device + * is @ref BLE_GAP_ROLE_PERIPH. + * + * @details This function takes into account connections whose state is + * @ref BLE_CONN_STATUS_DISCONNECTED. + * + * @return A list of all valid connection handles for which the module has a record and in which + * the role of local device is @ref BLE_GAP_ROLE_PERIPH. + */ +struct ble_conn_state_conn_handle_list ble_conn_state_periph_handles(void); + +/** + * @brief Translate a connection handle to a value that can be used as an array index. + * + * @details Function for mapping connection handles onto the range <0 - MAX_CONNECTIONS>. + * + * @note The index will be the same as long as a connection is invalid. A subsequent connection with + * the same connection handle might have a different index. + * + * @param[in] conn_handle The connection for which to retrieve an index. + * + * @return An index unique to this connection. Or @ref BLE_CONN_STATE_MAX_CONNECTIONS if + * @c conn_handle refers to an invalid connection. + */ +uint16_t ble_conn_state_conn_idx(uint16_t conn_handle); + +/** + * @brief Obtain exclusive access to one of the user flag collections. + * + * @details The acquired collection contains one flag for each connection. These flags can be set + * and read individually for each connection. + * + * The state of user flags will not be modified by the connection state module, except to + * set it to 0 for a connection when that connection is invalidated. + * + * @return The index of the acquired flag, or -1 if none are available. + */ +int ble_conn_state_user_flag_acquire(void); + +/** + * @brief Read the value of a user flag. + * + * @param[in] conn_handle Handle of connection to get the flag state for. + * @param[in] flag_index Which flag to get the state for. + * + * @return The state of the flag. If @c conn_handle is invalid, the function returns false. + */ +bool ble_conn_state_user_flag_get(uint16_t conn_handle, uint16_t flag_index); + +/** + * @brief Set the value of a user flag. + * + * @param[in] conn_handle Handle of connection to set the flag state for. + * @param[in] flag_id Which flag to set the state for. + * @param[in] value Value to set the flag state to. + */ +void ble_conn_state_user_flag_set(uint16_t conn_handle, uint16_t flag_index, bool value); + +/** + * @brief Run a function for each active connection. + * + * @param[in] user_function The function to run for each connection. + * @param[in] ctx Arbitrary context to be passed to @p user_function. + * + * @return The number of times @p user_function was run. + */ +uint32_t ble_conn_state_for_each_connected(ble_conn_state_user_function_t user_function, + void *ctx); + +/** + * @brief Run a function for each connection that has a user flag set. + * + * @param[in] flag_index Which flag to check. + * @param[in] user_function The function to run when a flag is set. + * @param[in] ctx Arbitrary context to be passed to @p user_function. + * + * @return The number of times @p user_function was run. + */ +uint32_t ble_conn_state_for_each_set_user_flag(uint16_t flag_index, + ble_conn_state_user_function_t user_function, + void *ctx); + +/** @} */ +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* BLE_CONN_STATE_H__ */ diff --git a/include/ble_gatt_db.h b/include/ble_gatt_db.h new file mode 100644 index 0000000000..0e4787776b --- /dev/null +++ b/include/ble_gatt_db.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2015-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @file + * + * @defgroup ble_sdk_lib_gatt_db GATT Database Service Structure + * @{ + * @ingroup ble_sdk_lib + */ + +#ifndef BLE_GATT_DB_H__ +#define BLE_GATT_DB_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief The maximum number of characteristics present in a service record. */ +#define BLE_GATT_DB_MAX_CHARS 6 + +/**@brief Structure for holding the characteristic and the handle of its CCCD present on a server. + */ +typedef struct { + /** @brief Structure containing information about the characteristic. */ + ble_gattc_char_t characteristic; + /** + * @brief CCCD Handle value for this characteristic. This will be set to + * BLE_GATT_HANDLE_INVALID if a CCCD is not present at the server. + */ + uint16_t cccd_handle; + /** + * @brief Extended Properties Handle value for this characteristic. + * This will be set to BLE_GATT_HANDLE_INVALID if an Extended + * Properties descriptor is not present at the server. + */ + uint16_t ext_prop_handle; + /** + * @brief User Description Handle value for this characteristic. This + * will be set to BLE_GATT_HANDLE_INVALID if a User Description + * descriptor is not present at the server. + */ + uint16_t user_desc_handle; + /** + * @brief Report Reference Handle value for this characteristic. This + * will be set to BLE_GATT_HANDLE_INVALID if a Report Reference + * descriptor is not present at the server. + */ + uint16_t report_ref_handle; +} ble_gatt_db_char_t; + +/** + * @brief Structure for holding information about the service and the characteristics present on a + * server. + */ +typedef struct { + /** @brief UUID of the service. */ + ble_uuid_t srv_uuid; + /** @brief Number of characteristics present in the service. */ + uint8_t char_count; + /** @brief Service Handle Range. */ + ble_gattc_handle_range_t handle_range; + /** + * @brief Array of information related to the characteristics present in the service. + * This list can extend further than one. + */ + ble_gatt_db_char_t charateristics[BLE_GATT_DB_MAX_CHARS]; +} ble_gatt_db_srv_t; + +#ifdef __cplusplus +} +#endif + +#endif /* BLE_GATT_DB_H__ */ + +/** @} */ diff --git a/include/bluetooth/peer_manager/peer_manager.h b/include/bluetooth/peer_manager/peer_manager.h new file mode 100644 index 0000000000..fbf39a9ec5 --- /dev/null +++ b/include/bluetooth/peer_manager/peer_manager.h @@ -0,0 +1,825 @@ +/* + * Copyright (c) 2015-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @file peer_manager.h + * + * @defgroup peer_manager Peer Manager + * @ingroup ble_sdk_lib + * @{ + * @brief Module for managing BLE bonding, which includes controlling encryption and pairing + * procedures as well as persistently storing different pieces of data that must be stored + * when bonded. + * + * @details The API consists of functions for configuring the pairing and encryption behavior of the + * device and functions for manipulating the stored data. + * + * This module uses ZMS to interface with persistent storage. (todo) The + * Peer Manager needs exclusive use of certain FDS file IDs and record keys. See + * @ref lib_fds_functionality_keys for more information. + */ + +#ifndef PEER_MANAGER_H__ +#define PEER_MANAGER_H__ + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Peer list filtrations. They determine which peer ID will be added to list. */ +typedef enum { + /** @brief Add all peers. */ + PM_PEER_ID_LIST_ALL_ID, + /** @brief Add only peers with an ID address (static address). */ + PM_PEER_ID_LIST_SKIP_NO_ID_ADDR = 1 << 0, + /** + * @brief Add only peers with a valid IRK. This implies @ref + * PM_PEER_ID_LIST_SKIP_NO_ID_ADDR, since all peers + * with IRKs have ID addresses. + */ + PM_PEER_ID_LIST_SKIP_NO_IRK = 1 << 1, + /** @brief Add only peers with Central Address Resolution characteristic set to 0. */ + PM_PEER_ID_LIST_SKIP_NO_CAR = 1 << 2, + /** @brief All above filters applied. */ + PM_PEER_ID_LIST_SKIP_ALL = PM_PEER_ID_LIST_SKIP_NO_IRK | PM_PEER_ID_LIST_SKIP_NO_CAR +} pm_peer_id_list_skip_t; + +/** + * @brief Function for initializing the Peer Manager. + * + * @details You must initialize the Peer Manager before you can call any other Peer Manager + * functions. + * + * @retval NRF_SUCCESS If initialization was successful. + * @retval NRF_ERROR_INTERNAL If an internal error occurred. + */ +uint32_t pm_init(void); + +/** + * @brief Function for registering an event handler with the Peer Manager. + * + * @param[in] event_handler Callback for events from the @ref peer_manager module. @p event_handler + * is called for every event that the Peer Manager sends after this + * function is called. + * + * @retval NRF_SUCCESS If initialization was successful. + * @retval NRF_ERROR_NULL If @p event_handler was NULL. + * @retval NRF_ERROR_NO_MEM If no more registrations can happen. + * @retval NRF_ERROR_INVALID_STATE If the Peer Manager is not initialized. + */ +uint32_t pm_register(pm_evt_handler_t event_handler); + +/** + * @brief Function for providing pairing and bonding parameters to use for pairing procedures. + * + * @details Until this function is called, all bonding procedures that are initiated by the + * peer are rejected. + * + * This function can be called multiple times with different parameters, even with NULL as + * @p p_sec_params, in which case the Peer Manager starts rejecting all procedures again. + * + * @param[in] p_sec_params Security parameters to be used for subsequent security procedures. + * + * @retval NRF_SUCCESS If the parameters were set successfully. + * @retval NRF_ERROR_INVALID_PARAM If the combination of parameters is invalid. + * @retval NRF_ERROR_INVALID_STATE If the Peer Manager is not initialized. + * @retval NRF_ERROR_INTERNAL If an internal error occurred. + */ +uint32_t pm_sec_params_set(ble_gap_sec_params_t *p_sec_params); + +/** + * @brief Function for establishing encryption on a connection, and optionally establishing a bond. + * + * @details This function attempts to secure the link that is specified by @p conn_handle. It uses + * the parameters that were previously provided in a call to @ref pm_sec_params_set. + * + * If the connection is a master connection, calling this function starts a security + * procedure on the link. If we have keys from a previous bonding procedure with this peer + * and the keys meet the security requirements in the currently active security parameters, + * the function attempts to establish encryption with the existing keys. If no key exists, + * the function attempts to perform pairing and bonding according to the currently active + * security parameters. + * + * If the function completes successfully, a @ref PM_EVT_CONN_SEC_START event is sent. + * The procedure might be queued, in which case the @ref PM_EVT_CONN_SEC_START event is + * delayed until the procedure is initiated in the SoftDevice. + * + * If the connection is a slave connection, the function sends a security request to + * the peer (master). It is up to the peer then to initiate pairing or encryption. + * If the peer ignores the request, a @ref BLE_GAP_EVT_AUTH_STATUS event occurs + * with the status @ref BLE_GAP_SEC_STATUS_TIMEOUT. Otherwise, the peer initiates + * security, in which case things happen as if the peer had initiated security itself. + * See @ref PM_EVT_CONN_SEC_START for information about peer-initiated security. + * + * @param[in] conn_handle Connection handle of the link as provided by the SoftDevice. + * @param[in] force_repairing Whether to force a pairing procedure even if there is an existing + * encryption key. This argument is relevant only for + * the central role. Recommended value: false. + * + * @retval NRF_SUCCESS If the operation completed successfully. + * @retval NRF_ERROR_BUSY If a security procedure is already in progress on the + * link, or if the link is disconnecting or disconnected. + * @retval NRF_ERROR_TIMEOUT If there was an SMP time-out, so that no more security + * operations can be performed on this link. + * @retval BLE_ERROR_INVALID_CONN_HANDLE If the connection handle is invalid. + * @retval NRF_ERROR_NOT_FOUND If the security parameters have not been set, either by + * @ref pm_sec_params_set or by @ref + * pm_conn_sec_params_reply. + * @retval NRF_ERROR_INVALID_DATA If the peer is bonded, but no LTK was found in the stored + * bonding data. Repairing was not requested. + * @retval NRF_ERROR_INVALID_STATE If the Peer Manager is not initialized. + * @retval NRF_ERROR_INTERNAL If an internal error occurred. + */ +uint32_t pm_conn_secure(uint16_t conn_handle, bool force_repairing); + +/** + * @brief Function for excluding a connection from the BLE event flow that is handled inside + * the Peer Manager. + * + * @details This function is optional, and must be called in reply to a @ref PM_EVT_CONN_CONFIG_REQ + * event, before the Peer Manager event handler returns. If it is not called in time, + * BLE events for a connection handle passed in the @ref PM_EVT_CONN_CONFIG_REQ event will + * be normally handled by the Peer Manager. + * + * @param[in] conn_handle The connection to be excluded. + * @param[in] p_context The context found in the request event that this function replies to. + * + * @retval NRF_SUCCESS Successful reply. + * @retval NRF_ERROR_NULL p_context was null. + */ +uint32_t pm_conn_exclude(uint16_t conn_handle, void const *p_context); + +/** + * @brief Function for providing security configuration for a link. + * + * @details This function is optional, and must be called in reply to a @ref + * PM_EVT_CONN_SEC_CONFIG_REQ event, before the Peer Manager event handler returns. If it + * is not called in time, a default configuration is used. See @ref pm_conn_sec_config_t + * for the value of the default. + * + * @param[in] conn_handle The connection to set the configuration for. + * @param[in] p_conn_sec_config The configuration. + */ +void pm_conn_sec_config_reply(uint16_t conn_handle, pm_conn_sec_config_t *p_conn_sec_config); + +/** + * @brief Function for providing security parameters for a link. + * + * @details This function is optional, and must be called in reply to a @ref + * PM_EVT_CONN_SEC_PARAMS_REQ event, before the Peer Manager event handler returns. If it + * is not called in time, the parameters given in @ref pm_sec_params_set are used. + * + * @param[in] conn_handle The connection to set the parameters for. + * @param[in] p_sec_params The parameters. If NULL, the security procedure is rejected. + * @param[in] p_context The context found in the request event that this function replies to. + * + * @retval NRF_SUCCESS Successful reply. + * @retval NRF_ERROR_NULL p_sec_params or p_context was null. + * @retval NRF_ERROR_INVALID_PARAM Value of p_sec_params was invalid. + * @retval NRF_ERROR_INVALID_STATE This module is not initialized. + */ +uint32_t pm_conn_sec_params_reply(uint16_t conn_handle, ble_gap_sec_params_t *p_sec_params, + void const *p_context); + +/** + * @brief Function for manually informing that the local database has changed. + * + * @details This function sends a service changed indication to all bonded and/or connected peers + * that subscribe to this indication. If a bonded peer is not connected, the indication is + * sent when it reconnects. Every time an indication is sent, a @ref + * PM_EVT_SERVICE_CHANGED_IND_SENT event occurs, followed by a @ref + * PM_EVT_SERVICE_CHANGED_IND_CONFIRMED when the peer sends its confirmation. Peers that + * are not subscribed to the service changed indication when this function is called do not + * receive an indication, and no events are sent to the user. Likewise, if the service + * changed characteristic is not present in the local database, or if the @ref + * PM_SERVICE_CHANGED_ENABLED is set to 0, no indications are sent peers, and no events are + * sent to the user. + */ +void pm_local_database_has_changed(void); + +/** + * @brief Function for getting the security status of a connection. + * + * @param[in] conn_handle Connection handle of the link as provided by the SoftDevice. + * @param[out] p_conn_sec_status Security status of the link. + * + * @retval NRF_SUCCESS If pairing was initiated successfully. + * @retval BLE_ERROR_INVALID_CONN_HANDLE If the connection handle is invalid. + * @retval NRF_ERROR_NULL If @p p_conn_sec_status was NULL. + * @retval NRF_ERROR_INVALID_STATE If the Peer Manager is not initialized. + */ +uint32_t pm_conn_sec_status_get(uint16_t conn_handle, pm_conn_sec_status_t *p_conn_sec_status); + +/** + * @brief Function for comparing the security status of a connection against a baseline. + * + * @param[in] conn_handle Connection handle of the link as provided by the SoftDevice. + * @param[out] p_sec_status_req Target baseline security status to compare against. + * + * @retval true If the security status of the connection matches or exceeds the baseline on all + * points. + * @retval false If the security status of the connection does not fulfil the baseline, or could + * not be retrieved. + */ +bool pm_sec_is_sufficient(uint16_t conn_handle, pm_conn_sec_status_t *p_sec_status_req); + +/** + * @brief Experimental function for specifying the public key to use for LESC operations. + * + * @details This function can be called multiple times. The specified public key will be used for + * all subsequent LESC (LE Secure Connections) operations until the next time this function + * is called. + * + * @note The key must continue to reside in application memory as it is not copied by Peer Manager. + * + * @note This function is deprecated. LESC keys are now handled internally if @ref PM_LESC_ENABLED + * is true. If @ref PM_LESC_ENABLED is false, this function works as before. + * + * @param[in] p_public_key The public key to use for all subsequent LESC operations. + * + * @retval NRF_SUCCESS If pairing was initiated successfully. + * @retval NRF_ERROR_INVALID_STATE If the Peer Manager is not initialized. + * @retval NRF_ERROR_FORBIDDEN If LESC module support is enabled (see @ref + * PM_LESC_ENABLED). + */ +uint32_t pm_lesc_public_key_set(ble_gap_lesc_p256_pk_t *p_public_key); + +/** + * @brief Function for setting or clearing the whitelist. + * + * When using the S13x SoftDevice v3.x, this function sets or clears the whitelist. + * When using the S13x SoftDevice v2.x, this function caches a list of + * peers that can be retrieved later by @ref pm_whitelist_get to pass to the @ref + * lib_ble_advertising. + * + * To clear the current whitelist, pass either NULL as @p p_peers or zero as @p peer_cnt. + * + * @param[in] p_peers The peers to add to the whitelist. Pass NULL to clear the current whitelist. + * @param[in] peer_cnt The number of peers to add to the whitelist. The number must not be greater + * than + * @ref BLE_GAP_WHITELIST_ADDR_MAX_COUNT. Pass zero to clear the current + * whitelist. + * + * @retval NRF_SUCCESS If the whitelist was successfully set or cleared. + * @retval BLE_GAP_ERROR_WHITELIST_IN_USE If a whitelist is already in use and cannot be set. + * @retval BLE_ERROR_GAP_INVALID_BLE_ADDR If a peer in @p p_peers has an address that cannot + * be used for whitelisting. + * @retval NRF_ERROR_NOT_FOUND If any of the peers in @p p_peers cannot be found. + * @retval NRF_ERROR_DATA_SIZE If @p peer_cnt is greater than + * @ref BLE_GAP_WHITELIST_ADDR_MAX_COUNT. + * @retval NRF_ERROR_INVALID_STATE If the Peer Manager is not initialized. + */ +uint32_t pm_whitelist_set(pm_peer_id_t const *p_peers, uint32_t peer_cnt); + +/** + * @brief Function for retrieving the previously set whitelist. + * + * The function retrieves the whitelist of GAP addresses and IRKs that was + * previously set by @ref pm_whitelist_set. + * + * To retrieve only GAP addresses or only IRKs, provide only one of the + * buffers. If a buffer is provided, its size must be specified. + * + * @param[out] p_addrs The buffer where to store GAP addresses. Pass NULL to retrieve + * only IRKs (in that case, @p p_irks must not be NULL). + * @param[in,out] p_addr_cnt In: The size of the @p p_addrs buffer. + * May be NULL if and only if @p p_addrs is NULL. + * Out: The number of GAP addresses copied into the buffer. + * If @p p_addrs is NULL, this parameter remains unchanged. + * @param[out] p_irks The buffer where to store IRKs. Pass NULL to retrieve + * only GAP addresses (in that case, @p p_addrs must not NULL). + * @param[in,out] p_irk_cnt In: The size of the @p p_irks buffer. + * May be NULL if and only if @p p_irks is NULL. + * Out: The number of IRKs copied into the buffer. + * If @p p_irks is NULL, this parameter remains unchanged. + * + * @retval NRF_SUCCESS If the whitelist was successfully retrieved. + * @retval BLE_ERROR_GAP_INVALID_BLE_ADDR If a peer has an address that cannot be used for + * whitelisting (this error can occur only + * when using the S13x SoftDevice v2.x). + * @retval NRF_ERROR_NULL If a required parameter is NULL. + * @retval NRF_ERROR_NO_MEM If the provided buffers are too small. + * @retval NRF_ERROR_NOT_FOUND If the data for any of the cached whitelisted peers + * cannot be found. It might have been deleted. + * @retval NRF_ERROR_INVALID_STATE If the Peer Manager is not initialized. + */ +uint32_t pm_whitelist_get(ble_gap_addr_t *p_addrs, uint32_t *p_addr_cnt, ble_gap_irk_t *p_irks, + uint32_t *p_irk_cnt); + +/** + * @brief Function for setting and clearing the device identities list. + * + * @note When entering directed advertising, make sure the active identities list does not contain + * peers that have no Central Address Resolution. See @ref pm_peer_id_list with skip_id + * @ref PM_PEER_ID_LIST_SKIP_NO_CAR. + * + * @param[in] p_peers The peers to add to the device identities list. Pass NULL to clear + * the device identities list. + * @param[in] peer_cnt The number of peers. Pass zero to clear the device identities list. + * + * @retval NRF_SUCCESS If the device identities list was successfully + * set or cleared. + * @retval NRF_ERROR_NOT_FOUND If a peer is invalid or its data could not + * be found in flash. + * @retval BLE_ERROR_GAP_INVALID_BLE_ADDR If a peer has an address that cannot be + * used for whitelisting. + * @retval BLE_ERROR_GAP_DEVICE_IDENTITIES_IN_USE If the device identities list is in use and + * cannot be set. + * @retval NRF_ERROR_INVALID_STATE If the Peer Manager is not initialized. + * @retval NRF_ERROR_NOT_SUPPORTED If using a SoftDevice that does not support + * device identities, e.g. S130 v2.0. + */ +uint32_t pm_device_identities_list_set(pm_peer_id_t const *p_peers, uint32_t peer_cnt); + +/** + * @brief Function for setting the local Bluetooth identity address. + * + * @details The local Bluetooth identity address is the address that identifies the device + * to other peers. The address type must be either @ref BLE_GAP_ADDR_TYPE_PUBLIC or @ref + * BLE_GAP_ADDR_TYPE_RANDOM_STATIC. The identity address cannot be changed while roles are running. + * + * The SoftDevice sets a default address of type @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC when it is + * enabled. This default address is a random number that is populated during the IC manufacturing + * process. It remains unchanged for the lifetime of each IC, but the application can use this + * function to assign a different identity address. + * + * The identity address is distributed to the peer during bonding. Changing the identity address + * means bonded devices might not recognize us. + * + * @note The SoftDevice functions @ref sd_ble_gap_addr_set and @ref sd_ble_gap_privacy_set must not + * be called when using the Peer Manager. Use the Peer Manager equivalents instead. + * + * @param[in] p_addr The GAP address to be set. + * + * @retval NRF_SUCCESS If the identity address was set successfully. + * @retval NRF_ERROR_NULL If @p p_addr is NULL. + * @retval NRF_ERROR_INVALID_ADDR If the @p p_addr pointer is invalid. + * @retval BLE_ERROR_GAP_INVALID_BLE_ADDR If the BLE address is invalid. + * @retval NRF_ERROR_BUSY If the SoftDevice was busy. Process SoftDevice events + * and retry. + * @retval NRF_ERROR_INVALID_STATE If the Peer Manager is not initialized or if this + * function was called while advertising, scanning, or while connected. + * @retval NRF_ERROR_INTERNAL If an internal error occurred. + */ +uint32_t pm_id_addr_set(ble_gap_addr_t const *p_addr); + +/** + * @brief Function for retrieving the local Bluetooth identity address. + * + * This function always returns the identity address, irrespective of the privacy settings. + * This means that the address type will always be either @ref BLE_GAP_ADDR_TYPE_PUBLIC or @ref + * BLE_GAP_ADDR_TYPE_RANDOM_STATIC. + * + * @param[out] p_addr Pointer to the address structure to be filled in. + * + * @retval NRF_SUCCESS If the address was retrieved successfully. + * @retval NRF_ERROR_NULL If @p p_addr is NULL. + * @retval NRF_ERROR_INVALID_STATE If the Peer Manager is not initialized. + */ +uint32_t pm_id_addr_get(ble_gap_addr_t *p_addr); + +/** + * @brief Function for configuring privacy settings. + * + * The privacy settings cannot be configured while advertising, scanning, or while in a connection. + * + * @note The SoftDevice functions @ref sd_ble_gap_addr_set + * and @ref sd_ble_gap_privacy_set must not be called when using the Peer Manager. + * Use this function instead. + * + * @param[in] p_privacy_params Privacy settings. + * + * @retval NRF_SUCCESS If the privacy settings were configured successfully. + * @retval NRF_ERROR_NULL If @p p_privacy_params is NULL. + * @retval NRF_ERROR_BUSY If the operation could not be performed at this time. + * Process SoftDevice events and retry. + * @retval NRF_ERROR_INVALID_PARAM If the address type is invalid. + * @retval NRF_ERROR_INVALID_STATE If this function is called while BLE roles using + * privacy are enabled. + * @retval NRF_ERROR_INVALID_STATE If the Peer Manager is not initialized. + */ +uint32_t pm_privacy_set(pm_privacy_params_t const *p_privacy_params); + +/** + * @brief Function for retrieving privacy settings. + * + * The privacy settings that are returned include the current IRK as well. + * + * @param[out] p_privacy_params Privacy settings. + * + * @retval NRF_SUCCESS If the privacy settings were retrieved successfully. + * @retval NRF_ERROR_NULL If @p p_privacy_params or @p p_privacy_params->p_device_irk is + * NULL. + * @retval NRF_ERROR_INVALID_STATE If the Peer Manager is not initialized. + */ +uint32_t pm_privacy_get(pm_privacy_params_t *p_privacy_params); + +/** + * @brief Function for resolving a resolvable address with an identity resolution key (IRK). + * + * @param[in] p_addr A private random resolvable address. + * @param[in] p_irk An identity resolution key (IRK). + * + * @retval true The IRK used matched the one used to create the address. + * @retval false The IRK used did not match the one used to create the address, or an argument was + * NULL or invalid. + */ +bool pm_address_resolve(ble_gap_addr_t const *p_addr, ble_gap_irk_t const *p_irk); + +/** + * @brief Function for getting the connection handle of the connection with a bonded peer. + * + * @param[in] peer_id The peer ID of the bonded peer. + * @param[out] p_conn_handle Connection handle, or @ref BLE_CONN_HANDLE_INVALID if none could be + * resolved. The conn_handle can refer to a recently disconnected + * connection. + * + * @retval NRF_SUCCESS If the connection handle was retrieved successfully. + * @retval NRF_ERROR_NULL If @p p_conn_handle was NULL. + * @retval NRF_ERROR_INVALID_STATE If the Peer Manager is not initialized. + */ +uint32_t pm_conn_handle_get(pm_peer_id_t peer_id, uint16_t *p_conn_handle); + +/** + * @brief Function for retrieving the ID of a peer, given its connection handle. + * + * @param[in] conn_handle The connection handle of the peer. + * @param[out] p_peer_id The peer ID, or @ref PM_PEER_ID_INVALID if the peer is not bonded or + * @p conn_handle does not refer to a valid connection. + * + * @retval NRF_SUCCESS If the peer ID was retrieved successfully. + * @retval NRF_ERROR_NULL If @p p_peer_id was NULL. + * @retval NRF_ERROR_INVALID_STATE If the Peer Manager is not initialized. + */ +uint32_t pm_peer_id_get(uint16_t conn_handle, pm_peer_id_t *p_peer_id); + +/** + * @brief Function for retrieving a filtered list of peer IDs. + * + * @details This function starts searching from @p first_peer_id. IDs ordering + * is the same as for @ref pm_next_peer_id_get(). If the first_peer_id + * is @ref PM_PEER_ID_INVALID, the function starts searching from the first ID. + * The function looks for the ID's number specified by @p p_list_size. Only those IDs that + * match @p skip_id are added to the list. The number of returned elements is determined + * by @p p_list_size. + * + * @warning The size of the @p p_peer_list buffer must be equal or greater than @p p_list_size. + * + * @param[out] p_peer_list Pointer to peer IDs list buffer. + * @param[in,out] p_list_size The amount of IDs to return / The number of returned IDs. + * @param[in] first_peer_id The first ID from which the search begins. IDs ordering + * is the same as for @ref pm_next_peer_id_get() + * @param[in] skip_id It determines which peer ID will be added to list. + * + * @retval NRF_SUCCESS If the ID list has been filled out. + * @retval NRF_ERROR_INVALID_PARAM If @p skip_id was invalid. + * @retval NRF_ERROR_NULL If peer_list or list_size was NULL. + * @retval NRF_ERROR_INVALID_STATE If the Peer Manager is not initialized. + */ +uint32_t pm_peer_id_list(pm_peer_id_t *p_peer_list, uint32_t *const p_list_size, + pm_peer_id_t first_peer_id, pm_peer_id_list_skip_t skip_id); + +/** + * @brief Function for getting the next peer ID in the sequence of all used peer IDs. + * + * @details This function can be used to loop through all used peer IDs. The order in which + * peer IDs are returned should be considered unpredictable. @ref PM_PEER_ID_INVALID + * is considered to be before the first and after the last used peer ID. + * + * @details To loop through all peer IDs exactly once, use the following constuct: + * @code{c} + * pm_peer_id_t current_peer_id = pm_next_peer_id_get(PM_PEER_ID_INVALID); + * while (current_peer_id != PM_PEER_ID_INVALID) + * { + * // Do something with current_peer_id. + * current_peer_id = pm_next_peer_id_get(current_peer_id) + * } + * @endcode + * + * @note This function does not report peer IDs that are pending deletion. + * + * @param[in] prev_peer_id The previous peer ID. + * + * @return The next peer ID. If @p prev_peer_id was @ref PM_PEER_ID_INVALID, the + * next peer ID is the first used peer ID. If @p prev_peer_id was the last + * used peer ID, the function returns @ref PM_PEER_ID_INVALID. + */ +pm_peer_id_t pm_next_peer_id_get(pm_peer_id_t prev_peer_id); + +/** + * @brief Function for querying the number of valid peer IDs that are available. + * + * @details This function returns the number of peers for which there is data in persistent storage. + * + * @return The number of valid peer IDs. + */ +uint32_t pm_peer_count(void); + +/** + * @anchor PM_PEER_DATA_FUNCTIONS + * @name Functions (Peer Data) + * Functions for manipulating peer data. + * @{ + */ + +/** + * @{ + */ + +/** + * @brief Function for retrieving stored data of a peer. + * + * @note The length of the provided buffer must be a multiple of 4. + * + * @param[in] peer_id Peer ID to get data for. + * @param[in] data_id Which type of data to read. + * @param[out] p_data Where to put the retrieved data. The documentation for + * @ref pm_peer_data_id_t specifies what data type each data ID is stored + * as. + * @param[in,out] p_len In: The length in bytes of @p p_data. + * Out: The length in bytes of the read data, if the read was successful. + * + * @retval NRF_SUCCESS If the data was read successfully. + * @retval NRF_ERROR_INVALID_PARAM If the data type or the peer ID was invalid or unallocated. + * @retval NRF_ERROR_NULL If a pointer parameter was NULL. + * @retval NRF_ERROR_NOT_FOUND If no stored data was found for this peer ID/data ID + * combination. + * @retval NRF_ERROR_DATA_SIZE If the provided buffer was not large enough. The data is still + * copied, filling the provided buffer. Note that this error can + * occur even if loading the same size as was stored, because the + * underlying layers round the length up to the nearest word (4 + * bytes) when storing. + * @retval NRF_ERROR_INVALID_STATE If the Peer Manager is not initialized. + */ +uint32_t pm_peer_data_load(pm_peer_id_t peer_id, pm_peer_data_id_t data_id, void *p_data, + uint32_t *p_len); + +/** + * @brief Function for reading a peer's bonding data (@ref PM_PEER_DATA_ID_BONDING). + * @details See @ref pm_peer_data_load for parameters and return values. + */ +uint32_t pm_peer_data_bonding_load(pm_peer_id_t peer_id, pm_peer_data_bonding_t *p_data); + +/** + * @brief Function for reading a peer's remote DB values. (@ref PM_PEER_DATA_ID_GATT_REMOTE). + * @details See @ref pm_peer_data_load for parameters and return values. + */ +uint32_t pm_peer_data_remote_db_load(pm_peer_id_t peer_id, ble_gatt_db_srv_t *p_data, + uint32_t *p_len); + +/** + * @brief Function for reading a peer's application data. (@ref PM_PEER_DATA_ID_APPLICATION). + * @details See @ref pm_peer_data_load for parameters and return values. + */ +uint32_t pm_peer_data_app_data_load(pm_peer_id_t peer_id, void *p_data, uint32_t *p_len); +/** @}*/ + +/** + * @{ + */ + +/** + * @brief Function for setting or updating stored data of a peer. + * + * @note Writing the data to persistent storage happens asynchronously. Therefore, the buffer + * that contains the data must be kept alive until the operation has completed. + * + * @note The data written using this function might later be overwritten as a result of internal + * operations in the Peer Manager. A Peer Manager event is sent each time data is updated, + * regardless of whether the operation originated internally or from action by the user. + * Data with @p data_id @ref PM_PEER_DATA_ID_GATT_REMOTE @ref PM_PEER_DATA_ID_APPLICATION is + * never (over)written internally. + * + * @param[in] peer_id Peer ID to set data for. + * @param[in] data_id Which type of data to set. + * @param[in] p_data New value to set. The documentation for @ref pm_peer_data_id_t specifies + * what data type each data ID should be stored as. + * @param[in] len The length in bytes of @p p_data. + * @param[out] p_token A token that identifies this particular store operation. The token can be + * used to identify events that pertain to this operation. This parameter can + * be NULL. + * + * @retval NRF_SUCCESS If the data is scheduled to be written to persistent storage. + * @retval NRF_ERROR_NULL If @p p_data is NULL. + * @retval NRF_ERROR_NOT_FOUND If no peer was found for the peer ID. + * @retval NRF_ERROR_INVALID_ADDR If @p p_data is not word-aligned (4 bytes). + * @retval NRF_ERROR_BUSY If the underlying flash handler is busy with other flash + * operations. Try again after receiving a Peer Manager event. + * @retval NRF_ERROR_RESOURCES If there is not enough space in persistent storage. + * @retval NRF_ERROR_FORBIDDEN If data ID is @ref PM_PEER_DATA_ID_BONDING and the new bonding + * data also corresponds to another bonded peer. No data is written + * so duplicate entries are avoided. + * @retval NRF_ERROR_INVALID_STATE If the Peer Manager is not initialized. + */ +uint32_t pm_peer_data_store(pm_peer_id_t peer_id, pm_peer_data_id_t data_id, void const *p_data, + uint32_t len, pm_store_token_t *p_token); + +/** + * @brief Function for setting or updating a peer's bonding data (@ref PM_PEER_DATA_ID_BONDING). + * @details See @ref pm_peer_data_store for parameters and return values. + */ +uint32_t pm_peer_data_bonding_store(pm_peer_id_t peer_id, pm_peer_data_bonding_t const *p_data, + pm_store_token_t *p_token); + +/** + * @brief Function for setting or updating a peer's remote DB values. (@ref + * PM_PEER_DATA_ID_GATT_REMOTE). + * @details See @ref pm_peer_data_store for parameters and return values. + */ +uint32_t pm_peer_data_remote_db_store(pm_peer_id_t peer_id, ble_gatt_db_srv_t const *p_data, + uint32_t len, pm_store_token_t *p_token); + +/** + * @brief Function for setting or updating a peer's application data. + * (@ref PM_PEER_DATA_ID_APPLICATION). + * @details See @ref pm_peer_data_store for parameters and return values. + */ +uint32_t pm_peer_data_app_data_store(pm_peer_id_t peer_id, void const *p_data, uint32_t len, + pm_store_token_t *p_token); +/** @}*/ + +/** + * @{ + */ + +/** + * @brief Function for deleting a peer's stored pieces of data. + * + * @details This function deletes specific data that is stored for a peer. Note that bonding data + * cannot be cleared separately. + * + * To delete all data for a peer (including bonding data), use @ref pm_peer_delete. + * + * @note Clearing data in persistent storage happens asynchronously. + * + * @param[in] peer_id Peer ID to clear data for. + * @param[in] data_id Which data to clear. + * + * @retval NRF_SUCCESS If the clear procedure was initiated successfully. + * @retval NRF_ERROR_INVALID_PARAM If @p data_id was PM_PEER_DATA_ID_BONDING or invalid, or + * @p peer_id was invalid. + * @retval NRF_ERROR_NOT_FOUND If there was no data to clear for this peer ID/data ID + * combination. + * @retval NRF_ERROR_BUSY If the underlying flash handler is busy with other flash + * operations. Try again after receiving a Peer Manager event. + * @retval NRF_ERROR_INVALID_STATE If the Peer Manager is not initialized. + * @retval NRF_ERROR_INTERNAL If an internal error occurred. + */ +uint32_t pm_peer_data_delete(pm_peer_id_t peer_id, pm_peer_data_id_t data_id); + +/** + * @brief Function for manually adding a peer to the persistent storage. + * + * @details This function allocates a new peer ID and stores bonding data for the new peer. The + * bonding data is necessary to prevent ambiguity/inconsistency in peer data. + * + * @param[in] p_bonding_data The bonding data of the new peer (must contain a public/static + * address or a non-zero IRK). + * @param[out] p_new_peer_id Peer ID for the new peer, or an existing peer if a match was found. + * @param[out] p_token A token that identifies this particular store operation (storing the + * bonding data). The token can be used to identify events that pertain + * to this operation. This parameter can be NULL. + * + * @retval NRF_SUCCESS If the store operation for bonding data was initiated + * successfully. + * @retval NRF_ERROR_NULL If @p p_bonding_data or @p p_new_peer_id is NULL. + * @retval NRF_ERROR_INVALID_ADDR If @p p_bonding_data is not word-aligned (4 bytes). + * @retval NRF_ERROR_RESOURCES If there is not enough space in persistent storage. + * @retval NRF_ERROR_NO_MEM If there are no more available peer IDs. + * @retval NRF_ERROR_BUSY If the underlying flash filesystem is busy with other flash + * operations. Try again after receiving a Peer Manager event. + * @retval NRF_ERROR_INVALID_STATE If the Peer Manager is not initialized. + * @retval NRF_ERROR_INTERNAL If an internal error occurred. + */ +uint32_t pm_peer_new(pm_peer_id_t *p_new_peer_id, pm_peer_data_bonding_t *p_bonding_data, + pm_store_token_t *p_token); + +/** + * @brief Function for freeing persistent storage for a peer. + * + * @details This function deletes every piece of data that is associated with the specified peer and + * frees the peer ID to be used for another peer. The deletion happens asynchronously, and + * the peer ID is not freed until the data is deleted. When the operation finishes, a @ref + * PM_EVT_PEER_DELETE_SUCCEEDED or @ref PM_EVT_PEER_DELETE_FAILED event is sent. + * + * @warning Use this function only when not connected to or connectable for the peer that is being + * deleted. If the peer is or becomes connected or data is manually written in flash during + * this procedure (until the success or failure event happens), the behavior is undefined. + * + * @param[in] peer_id Peer ID to be freed and have all associated data deleted. + * + * @retval NRF_SUCCESS If the operation was initiated successfully. + * @retval NRF_ERROR_INVALID_PARAM If the peer ID was not valid. + * @retval NRF_ERROR_INVALID_STATE If the Peer Manager is not initialized. + */ +uint32_t pm_peer_delete(pm_peer_id_t peer_id); + +/** + * @brief Function for deleting all data stored for all peers. + * + * @details This function sends either a @ref PM_EVT_PEERS_DELETE_SUCCEEDED or a @ref + * PM_EVT_PEERS_DELETE_FAILED event. In addition, a @ref PM_EVT_PEER_DELETE_SUCCEEDED or + * @ref PM_EVT_PEER_DELETE_FAILED event is sent for each deleted peer. + * + * @note When there is no peer data in flash the @ref PM_EVT_PEER_DELETE_SUCCEEDED event is sent + * synchronously. + * + * @warning Use this function only when not connected or connectable. If a peer is or becomes + * connected or a @ref PM_PEER_DATA_FUNCTIONS function is used during this procedure (until + * the success or failure event happens), the behavior is undefined. + * + * @retval NRF_SUCCESS If the deletion process was initiated successfully. + * @retval NRF_ERROR_INVALID_STATE If the Peer Manager is not initialized. + * @retval NRF_ERROR_INTERNAL If an internal error occurred. + */ +uint32_t pm_peers_delete(void); +/** @}*/ + +/** + * @{ + */ + +/** + * @brief Function for finding the highest and lowest ranked peers. + * + * @details The rank is saved in persistent storage under the data ID @ref + * PM_PEER_DATA_ID_PEER_RANK. + * + * @details The interpretation of rank is up to the user, because the rank is only updated by + * calling @ref pm_peer_rank_highest or by manipulating the value using a @ref + * PM_PEER_DATA_FUNCTIONS function. + * + * @note Peers with no stored rank are not considered. + * @note Any argument that is NULL is ignored. + * + * @param[out] p_highest_ranked_peer The peer ID with the highest rank of all peers, for example, + * the most recently used peer. + * @param[out] p_highest_rank The highest rank. + * @param[out] p_lowest_ranked_peer The peer ID with the lowest rank of all peers, for example, + * the least recently used peer. + * @param[out] p_lowest_rank The lowest rank. + * + * @retval NRF_SUCCESS If the operation completed successfully. + * @retval NRF_ERROR_NOT_FOUND If no peer with stored peer rank was found. + * @retval NRF_ERROR_INVALID_STATE If the Peer Manager is not initialized. + * @retval NRF_ERROR_INTERNAL If an internal error occurred. + * @retval NRF_ERROR_NOT_SUPPORTED If peer rank functionality has been disabled via the @ref + * PM_PEER_RANKS_ENABLED configuration option. + */ +uint32_t pm_peer_ranks_get(pm_peer_id_t *p_highest_ranked_peer, uint32_t *p_highest_rank, + pm_peer_id_t *p_lowest_ranked_peer, uint32_t *p_lowest_rank); + +/** + * @brief Function for updating the rank of a peer to be highest among all stored peers. + * + * @details If this function returns @ref NRF_SUCCESS, either a @ref + * PM_EVT_PEER_DATA_UPDATE_SUCCEEDED or a + * @ref PM_EVT_PEER_DATA_UPDATE_FAILED event is sent with a @ref + * PM_STORE_TOKEN_INVALID store token when the operation is complete. Until the operation + * is complete, this function returns @ref NRF_ERROR_BUSY. + * + * When the operation is complete, the peer is the highest ranked peer as reported by + * @ref pm_peer_ranks_get. + * + * @note The @ref PM_EVT_PEER_DATA_UPDATE_SUCCEEDED event can arrive before the function returns if + * the peer is already ranked highest. In this case, the @ref + * pm_peer_data_update_succeeded_evt_t::flash_changed flag in the event will be false. + * + * @param[in] peer_id The peer to rank highest. + * + * @retval NRF_SUCCESS If the peer's rank is, or will be updated to be highest. + * @retval NRF_ERROR_INVALID_PARAM If @p peer_id is invalid, or doesn't exist in flash. + * @retval NRF_ERROR_RESOURCES If there is not enough space in persistent storage. + * @retval NRF_ERROR_BUSY If the underlying flash handler is busy with other flash + * operations, or if a previous call to this function has not + * completed. Try again after receiving a Peer Manager event. + * @retval NRF_ERROR_INVALID_STATE If the Peer Manager is not initialized. + * @retval NRF_ERROR_DATA_SIZE If the highest rank is UINT32_MAX, so the new rank would wrap + * around to 0. To fix this, manually update all ranks to smaller + * values, while still keeping their order. + * @retval NRF_ERROR_INTERNAL If an internal error occurred. + * @retval NRF_ERROR_NOT_SUPPORTED If peer rank functionality has been disabled via the @ref + * PM_PEER_RANKS_ENABLED configuration option. + */ +uint32_t pm_peer_rank_highest(pm_peer_id_t peer_id); + +/** @}*/ + +/** @} */ + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* PEER_MANAGER_H__ */ diff --git a/include/bluetooth/peer_manager/peer_manager_types.h b/include/bluetooth/peer_manager/peer_manager_types.h new file mode 100644 index 0000000000..155f7f98d3 --- /dev/null +++ b/include/bluetooth/peer_manager/peer_manager_types.h @@ -0,0 +1,568 @@ +/* + * Copyright (c) 2015-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @file peer_manager_types.h + * + * @addtogroup peer_manager + * @{ + */ + +#ifndef PEER_MANAGER_TYPES_H__ +#define PEER_MANAGER_TYPES_H__ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Handle to uniquely identify a peer for which we have persistently stored data. */ +typedef uint16_t pm_peer_id_t; + +/** @brief Type that is used to hold a reference to a stored item in flash. */ +typedef uint32_t pm_store_token_t; + +/** + * @brief Errors from security procedures in Peer Manager. + * + * @details Possible values are defined in @ref PM_SEC_ERRORS and @ref BLE_GAP_SEC_STATUS. + */ +typedef uint16_t pm_sec_error_code_t; + +/** @brief Invalid value for Peer ID. */ +#define PM_PEER_ID_INVALID 0xFFFF +/** @brief Invalid value for store token. */ +#define PM_STORE_TOKEN_INVALID 0 +/** @brief The number of available peer IDs. */ +#define PM_PEER_ID_N_AVAILABLE_IDS 256 +/** @brief The static-length part of the local GATT data struct. */ +#define PM_LOCAL_DB_LEN_OVERHEAD_BYTES offsetof(pm_peer_data_local_gatt_db_t, data) +/** + * @brief The base for Peer Manager defined errors. See @ref PM_SEC_ERRORS and + * @ref pm_sec_error_code_t. + */ +#define PM_CONN_SEC_ERROR_BASE 0x1000 + +/** + * @defgroup PM_SEC_ERRORS Peer Manager defined security errors + * + * @details The first 256 numbers, from PM_CONN_SEC_ERROR_BASE to (PM_CONN_SEC_ERROR_BASE + 0xFF), + * correspond to the status codes in @ref BLE_HCI_STATUS_CODES. + * @{ + */ +/** + * @brief Encryption failed because the peripheral has lost the LTK for this + * bond. See also @ref BLE_HCI_STATUS_CODE_PIN_OR_KEY_MISSING and Table 3.7 + * ("Pairing Failed Reason Codes") in the Bluetooth Core Specification 4.2, + * section 3.H.3.5.5 (@linkBLEcore). + */ +#define PM_CONN_SEC_ERROR_PIN_OR_KEY_MISSING (PM_CONN_SEC_ERROR_BASE + 0x06) +/** + * @brief Encryption ended with disconnection because of mismatching keys or a + * stray packet during a procedure. See the SoftDevice GAP Message Sequence Charts + * on encryption (@linkBLEMSCgap), the Bluetooth Core Specification 4.2, + * sections 6.B.5.1.3.1 and 3.H.3.5.5 (@linkBLEcore), and + * @ref BLE_HCI_CONN_TERMINATED_DUE_TO_MIC_FAILURE. + */ +#define PM_CONN_SEC_ERROR_MIC_FAILURE (PM_CONN_SEC_ERROR_BASE + 0x3D) +/** + * @brief Pairing or encryption did not finish before + * the link disconnected for an unrelated reason. + */ +#define PM_CONN_SEC_ERROR_DISCONNECT (PM_CONN_SEC_ERROR_BASE + 0x100) +/** + * @brief Pairing/bonding could not start because an SMP time-out has already + * happened on this link. This means that no more pairing or bonding can happen on + * this link. To be able to pair or bond, the link must be disconnected and then + * reconnected. See Bluetooth Core Specification 4.2 section 3.H.3.4 (@linkBLEcore). + */ +#define PM_CONN_SEC_ERROR_SMP_TIMEOUT (PM_CONN_SEC_ERROR_BASE + 0x101) +/** @} */ + +/** + * @defgroup PM_PEER_ID_VERSIONS All versions of Peer IDs. + * @brief The data ID for each iteration of the data formats in flash. + * @details Each time the format (in flash) of a piece of peer data changes, the data ID will also + * be updated. This list of defines is a record of each data ID that has ever existed, and + * code that caters to legacy formats can find the relevant IDs here. + * @{ + */ +/** @brief The smallest data ID. */ +#define PM_PEER_DATA_ID_FIRST_VX 0 +/** @brief The data ID of the first version of bonding data. */ +#define PM_PEER_DATA_ID_BONDING_V1 0 +/** @brief The data ID of the second version of bonding data. */ +#define PM_PEER_DATA_ID_BONDING_V2 7 +/** @brief The data ID of the first version of the service changed pending flag. */ +#define PM_PEER_DATA_ID_SERVICE_CHANGED_PENDING_V1 1 +/** @brief The data ID of the first version of local GATT data. */ +#define PM_PEER_DATA_ID_GATT_LOCAL_V1 2 +/** @brief The data ID of the second version of local GATT data. */ +#define PM_PEER_DATA_ID_GATT_LOCAL_V2 8 +/** @brief The data ID of the first version of remote GATT data. */ +#define PM_PEER_DATA_ID_GATT_REMOTE_V1 3 +/** @brief The data ID of the first version of application data. */ +#define PM_PEER_DATA_ID_APPLICATION_V1 4 +/** @brief The data ID of the second version of remote GATT data. */ +#define PM_PEER_DATA_ID_GATT_REMOTE_V2 5 +/** @brief The data ID of the first version of the rank. */ +#define PM_PEER_DATA_ID_PEER_RANK_V1 6 +/** @brief The data ID of the first version of central address resolution. */ +#define PM_PEER_DATA_ID_CENTRAL_ADDR_RES_V1 9 +/** @brief The data ID after the last valid one. */ +#define PM_PEER_DATA_ID_LAST_VX 10 +/** @brief A data ID guaranteed to be invalid. */ +#define PM_PEER_DATA_ID_INVALID_VX 0xFF +/**@}*/ + +/** + * @brief The different types of data associated with a peer. + */ +typedef enum { + /** @brief The smallest data ID. */ + PM_PEER_DATA_ID_FIRST = PM_PEER_DATA_ID_FIRST_VX, + /** @brief The data ID for bonding data. Type: @ref pm_peer_data_bonding_t. */ + PM_PEER_DATA_ID_BONDING = PM_PEER_DATA_ID_BONDING_V2, + /** @brief The data ID for service changed state. Type: bool. */ + PM_PEER_DATA_ID_SERVICE_CHANGED_PENDING = PM_PEER_DATA_ID_SERVICE_CHANGED_PENDING_V1, + /** + * @brief The data ID for local GATT data (sys attributes). + * Type: @ref pm_peer_data_local_gatt_db_t. + */ + PM_PEER_DATA_ID_GATT_LOCAL = PM_PEER_DATA_ID_GATT_LOCAL_V2, + /** @brief The data ID for remote GATT data. Type: uint8_t array. */ + PM_PEER_DATA_ID_GATT_REMOTE = PM_PEER_DATA_ID_GATT_REMOTE_V2, + /** @brief The data ID for peer rank. See @ref pm_peer_rank_highest. Type: uint32_t. */ + PM_PEER_DATA_ID_PEER_RANK = PM_PEER_DATA_ID_PEER_RANK_V1, + /** + * @brief The data ID for central address resolution. See @ref pm_peer_id_list. + * Type: uint32_t. + */ + PM_PEER_DATA_ID_CENTRAL_ADDR_RES = PM_PEER_DATA_ID_CENTRAL_ADDR_RES_V1, + /** @brief The data ID for application data. Type: uint8_t array. */ + PM_PEER_DATA_ID_APPLICATION = PM_PEER_DATA_ID_APPLICATION_V1, + /** @brief One more than the highest data ID. */ + PM_PEER_DATA_ID_LAST = PM_PEER_DATA_ID_LAST_VX, + /** @brief A data ID guaranteed to be invalid. */ + PM_PEER_DATA_ID_INVALID = PM_PEER_DATA_ID_INVALID_VX, +} pm_peer_data_id_t; + +/** @brief Different procedures that can lead to an encrypted link. */ +typedef enum { + /** + * @brief Using an LTK that was shared during a previous bonding procedure to encrypt the + * link. + */ + PM_CONN_SEC_PROCEDURE_ENCRYPTION, + /** @brief A pairing procedure, followed by a bonding procedure. */ + PM_CONN_SEC_PROCEDURE_BONDING, + /** @brief A pairing procedure with no bonding. */ + PM_CONN_SEC_PROCEDURE_PAIRING, +} pm_conn_sec_procedure_t; + +/** @brief Configuration of a security procedure. */ +typedef struct { + /** + * @brief Whether to allow the peer to pair if it wants to, but is + * already bonded. If this is false, the procedure is rejected, and no + * more events are sent. Default: false. + */ + bool allow_repairing; +} pm_conn_sec_config_t; + +/** @brief Data associated with a bond to a peer. */ +typedef struct { + /** @brief The BLE role of the local device during bonding. See @ref BLE_GAP_ROLES. */ + uint8_t own_role; + /** @brief The peer's Bluetooth address and identity resolution key (IRK). */ + ble_gap_id_key_t peer_ble_id; + /** @brief The peer's long-term encryption key (LTK) and master ID. */ + ble_gap_enc_key_t peer_ltk; + /** + * @brief Locally generated long-term encryption key (LTK) and master ID, distributed to + * the peer. + */ + ble_gap_enc_key_t own_ltk; +} pm_peer_data_bonding_t; + +/** @brief Data on a local GATT database. */ +typedef struct { + /** @brief Flags that describe the database attributes. */ + uint32_t flags; + /** @brief Size of the attribute array. */ + uint16_t len; + /** @brief Array to hold the database attributes. */ + uint8_t data[1]; +} pm_peer_data_local_gatt_db_t; + +/** + * @brief Device Privacy. + * + * The privacy feature provides a way for the device to avoid being tracked over a period of + * time. The privacy feature, when enabled, hides the local device identity and replaces it + * with a private address that is automatically refreshed at a specified interval. + * + * If a device still wants to be recognized by other peers, it needs to share it's Identity + * Resolving Key (IRK). With this key, a device can generate a random private address that + * can only be recognized by peers in possession of that key, and devices can establish + * connections without revealing their real identities. + * + * @note If the device IRK is updated, the new IRK becomes the one to be distributed in all + * bonding procedures performed after @ref sd_ble_gap_privacy_set returns. + * The IRK distributed during bonding procedure is the device IRK that is active when @ref + * sd_ble_gap_sec_params_reply is called. + */ +typedef ble_gap_privacy_params_t pm_privacy_params_t; + +/** @brief Security status of a connection. */ +typedef struct { + /** @brief The connection is active (not disconnected). */ + uint8_t connected: 1; + /** @brief The communication on this link is encrypted. */ + uint8_t encrypted: 1; + /** + * @brief The encrypted communication is also protected against man-in-the-middle + * attacks. + */ + uint8_t mitm_protected: 1; + /** @brief The peer is bonded. */ + uint8_t bonded: 1; + /** @brief The peer is paired using LESC. */ + uint8_t lesc: 1; + /** @brief Reserved for future use. */ + uint8_t reserved: 3; +} pm_conn_sec_status_t; + +/** @brief Types of events that can come from the @ref peer_manager module. */ +typedef enum { + /** + * @brief A connected peer has been identified as one with + * which we have a bond. When performing bonding with a peer + * for the first time, this event will not be sent until a new + * connection is established with the peer. When we are + * central, this event is always sent when the Peer Manager + * receives the @ref BLE_GAP_EVT_CONNECTED event. When we are + * peripheral, this event might in rare cases arrive later. + */ + PM_EVT_BONDED_PEER_CONNECTED, + /** + * @brief A new connection has been established. This event is a + * wrapper for @ref BLE_GAP_EVT_CONNECTED event and contains its + * parameters. Reply with @ref pm_conn_exclude before the event + * handler returns to exclude BLE events targeting this connection + * from being handled by the Peer Manager + */ + PM_EVT_CONN_CONFIG_REQ, + /** + * @brief A security procedure has started on a link, initiated + * either locally or remotely. The security procedure is using the + * last parameters provided via @ref pm_sec_params_set. This event is + * always followed by either a @ref PM_EVT_CONN_SEC_SUCCEEDED or a + * @ref PM_EVT_CONN_SEC_FAILED event. This is an informational event; + * no action is needed for the procedure to proceed. + */ + PM_EVT_CONN_SEC_START, + /** + * @brief A link has been encrypted, either as a result of a + * call to @ref pm_conn_secure or a result of an action by th + * peer. The event structure contains more information about the + * circumstances. This event might contain a peer ID with the + * value @ref PM_PEER_ID_INVALID, which means that the peer + * (central) used an address that could not be identified, but it + * used an encryption key (LTK) that is present in the database. + */ + PM_EVT_CONN_SEC_SUCCEEDED, + /** + * @brief A pairing or encryption procedure has failed. In some + * cases, this means that security is not possible on this link + * (temporarily or permanently). How to handle this error depends on + * the application. + */ + PM_EVT_CONN_SEC_FAILED, + /** + * @brief The peer (central) has requested pairing, but a bond + * already exists with that peer. Reply by calling @ref + * pm_conn_sec_config_reply before the event handler returns. If + * no reply is sent, a default is used. + */ + PM_EVT_CONN_SEC_CONFIG_REQ, + /** + * @brief Security parameters (@ref ble_gap_sec_params_t) are + * needed for an ongoing security procedure. Reply with @ref + * pm_conn_sec_params_reply before the event handler returns. If + * no reply is sent, the parameters given in @ref + * pm_sec_params_set are used. If a peripheral connection, the + * central's sec_params will be available in the event. + */ + PM_EVT_CONN_SEC_PARAMS_REQ, + /** + * @brief There is no more room for peer data in flash storage. To + * solve this problem, delete data that is not needed anymore and run a + * garbage collection procedure in FDS. + */ + PM_EVT_STORAGE_FULL, + /** + * @brief An unrecoverable error happened inside Peer Manager. An + * operation failed with the provided error. + */ + PM_EVT_ERROR_UNEXPECTED, + /** + * @brief A piece of peer data was stored, updated, or + * cleared in flash storage. This event is sent for all + * successful changes to peer data, also those initiated + * internally in Peer Manager. To identify an operation, + * compare the store token in the event with the store + * token received during the initiating function call. + * Events from internally initiated changes might have + * invalid store tokens. + */ + PM_EVT_PEER_DATA_UPDATE_SUCCEEDED, + /** + * @brief A piece of peer data could not be stored, + * updated, or cleared in flash storage. This event is sent + * instead of @ref PM_EVT_PEER_DATA_UPDATE_SUCCEEDED for the + * failed operation. + */ + PM_EVT_PEER_DATA_UPDATE_FAILED, + /** + * @brief A peer was cleared from flash storage, for example + * because a call to @ref pm_peer_delete succeeded. This event + * can also be sent as part of a call to @ref pm_peers_delete + * or internal cleanup. + */ + PM_EVT_PEER_DELETE_SUCCEEDED, + /** + * @brief A peer could not be cleared from flash storage. This + * event is sent instead of @ref PM_EVT_PEER_DELETE_SUCCEEDED for + * the failed operation. + */ + PM_EVT_PEER_DELETE_FAILED, + /** + * @brief A call to @ref pm_peers_delete has completed + * successfully. Flash storage now contains no peer data. + */ + PM_EVT_PEERS_DELETE_SUCCEEDED, + /** + * @brief A call to @ref pm_peers_delete has failed, which + * means that at least one of the peers could not be deleted. + * Other peers might have been deleted, or might still be queued + * to be deleted. No more @ref PM_EVT_PEERS_DELETE_SUCCEEDED or + * @ref PM_EVT_PEERS_DELETE_FAILED events are sent until the + * next time @ref pm_peers_delete is called. + */ + PM_EVT_PEERS_DELETE_FAILED, + /** + * @brief Local database values for a peer (taken from + * flash storage) have been provided to the SoftDevice. + */ + PM_EVT_LOCAL_DB_CACHE_APPLIED, + /** + * @brief Local database values for a peer (taken from + * flash storage) were rejected by the SoftDevice, which + * means that either the database has changed or the + * user has manually set the local database to an + * invalid value (using @ref pm_peer_data_store). + */ + PM_EVT_LOCAL_DB_CACHE_APPLY_FAILED, + /** + * @brief A service changed indication has been sent to a + * peer, as a result of a call to @ref + * pm_local_database_has_changed. This event will be + * followed by a @ref PM_EVT_SERVICE_CHANGED_IND_CONFIRMED + * event if the peer acknowledges the indication. + */ + PM_EVT_SERVICE_CHANGED_IND_SENT, + /** + * @brief A service changed indication that was sent + * has been confirmed by a peer. The peer can now be + * considered aware that the local database has + * changed. + */ + PM_EVT_SERVICE_CHANGED_IND_CONFIRMED, + /** @brief The peer (peripheral) has requested link encryption, which has been enabled. */ + PM_EVT_SLAVE_SECURITY_REQ, + /** @brief The flash has been garbage collected (By FDS), possibly freeing up space. */ + PM_EVT_FLASH_GARBAGE_COLLECTED, + /** @brief Garbage collection was attempted but failed. */ + PM_EVT_FLASH_GARBAGE_COLLECTION_FAILED, +} pm_evt_id_t; + +/** @brief Parameters specific to the @ref PM_EVT_CONN_CONFIG_REQ event. */ +typedef struct { + /** @brief Connected Event parameters. */ + ble_gap_evt_connected_t const *p_peer_params; + /** + * @brief This pointer must be provided in the reply if the reply function takes a + * p_context argument. + */ + void const *p_context; +} pm_conn_config_req_evt_t; + +/** @brief Events parameters specific to the @ref PM_EVT_CONN_SEC_START event. */ +typedef struct { + /** @brief The procedure that has started. */ + pm_conn_sec_procedure_t procedure; +} pm_conn_sec_start_evt_t; + +/** @brief Parameters specific to the @ref PM_EVT_CONN_SEC_SUCCEEDED event. */ +typedef struct { + /** @brief The procedure that led to securing the link. */ + pm_conn_sec_procedure_t procedure; + /** + * @brief Whether bonding data was successfully requested to be stored. + * This is false if: No bonding happened, or an internal error occurred + * when trying to store the data, or if the data was rejected via @ref + * pm_conn_sec_config_reply. + */ + bool data_stored; +} pm_conn_secured_evt_t; + +/** @brief Parameters specific to the @ref PM_EVT_CONN_SEC_FAILED event. */ +typedef struct { + /** @brief The procedure that failed. */ + pm_conn_sec_procedure_t procedure; + /** @brief An error code that describes the failure. */ + pm_sec_error_code_t error; + /** @brief The party that raised the error, see @ref BLE_GAP_SEC_STATUS_SOURCES. */ + uint8_t error_src; +} pm_conn_secure_failed_evt_t; + +/** @brief Parameters specific to the @ref PM_EVT_CONN_SEC_PARAMS_REQ event. */ +typedef struct { + /** @brief Peer security parameters, if role is peripheral. Otherwise, this is NULL. */ + ble_gap_sec_params_t const *p_peer_params; + /** + * @brief This pointer must be provided in the reply if the reply function takes a + * p_context argument. + */ + void const *p_context; +} pm_conn_sec_params_req_evt_t; + +/** @brief Actions that can be performed to peer data in persistent storage. */ +typedef enum { + /** @brief Writing or overwriting the data. */ + PM_PEER_DATA_OP_UPDATE, + /** @brief Removing the data. */ + PM_PEER_DATA_OP_DELETE, +} pm_peer_data_op_t; + +/** @brief Parameters specific to the @ref PM_EVT_PEER_DATA_UPDATE_SUCCEEDED event. */ +typedef struct { + /** @brief The type of the data that was changed. */ + pm_peer_data_id_t data_id; + /** @brief What happened to the data. */ + pm_peer_data_op_t action; + /** + * @brief Token that identifies the operation. For @ref + * PM_PEER_DATA_OP_DELETE actions, this token can be disregarded. For @ref + * PM_PEER_DATA_OP_UPDATE actions, compare this token with the token that is + * received from a call to a @ref PM_PEER_DATA_FUNCTIONS function. + */ + uint32_t token; + /** + * @brief If this is false, no operation was done in flash, + * because the value was already what it should be. Please note + * that in certain scenarios, this flag will be true even if the + * new value is the same as the old. + */ + uint8_t flash_changed: 1; +} pm_peer_data_update_succeeded_evt_t; + +/** @brief Parameters specific to the @ref PM_EVT_PEER_DATA_UPDATE_FAILED event. */ +typedef struct { + /** @brief The type of the data that was supposed to be changed. */ + pm_peer_data_id_t data_id; + /** @brief The action that failed. */ + pm_peer_data_op_t action; + /** + * @brief Token that identifies the operation. For @ref + * PM_PEER_DATA_OP_DELETE actions, this token can be disregarded. For @ref + * PM_PEER_DATA_OP_UPDATE actions, compare this token with the token that is + * received from a call to a @ref PM_PEER_DATA_FUNCTIONS function. + */ + uint32_t token; + /** @brief An error code that describes the failure. */ + uint32_t error; +} pm_peer_data_update_failed_t; + +/** @brief Standard parameters for failure events. */ +typedef struct { + /** @brief The error that occurred. */ + uint32_t error; +} pm_failure_evt_t; + +/** + * @brief An event from the @ref peer_manager module. + * + * @details The structure contains both standard parameters and parameters that are specific to some + * events. + */ +typedef struct { + /** @brief The type of the event. */ + pm_evt_id_t evt_id; + /** @brief The connection that this event pertains to, or @ref BLE_CONN_HANDLE_INVALID. */ + uint16_t conn_handle; + /** @brief The bonded peer that this event pertains to, or @ref PM_PEER_ID_INVALID. */ + uint16_t peer_id; + union { + /** @brief Parameters specific to the @ref PM_EVT_CONN_CONFIG_REQ event. */ + pm_conn_config_req_evt_t conn_config_req; + /** @brief Parameters specific to the @ref PM_EVT_CONN_SEC_START event. */ + pm_conn_sec_start_evt_t conn_sec_start; + /** @brief Parameters specific to the @ref PM_EVT_CONN_SEC_SUCCEEDED event. */ + pm_conn_secured_evt_t conn_sec_succeeded; + /** @brief Parameters specific to the @ref PM_EVT_CONN_SEC_FAILED event. */ + pm_conn_secure_failed_evt_t conn_sec_failed; + /** @brief Parameters specific to the @ref PM_EVT_CONN_SEC_PARAMS_REQ event. */ + pm_conn_sec_params_req_evt_t conn_sec_params_req; + /** + * @brief Parameters specific to the @ref PM_EVT_PEER_DATA_UPDATE_SUCCEEDED + * event. + */ + pm_peer_data_update_succeeded_evt_t peer_data_update_succeeded; + /** @brief Parameters specific to the @ref PM_EVT_PEER_DATA_UPDATE_FAILED event. */ + pm_peer_data_update_failed_t peer_data_update_failed; + /** @brief Parameters specific to the @ref PM_EVT_PEER_DELETE_FAILED event. */ + pm_failure_evt_t peer_delete_failed; + /** @brief Parameters specific to the @ref PM_EVT_PEERS_DELETE_FAILED event. */ + pm_failure_evt_t peers_delete_failed_evt; + /** @brief Parameters specific to the @ref PM_EVT_ERROR_UNEXPECTED event. */ + pm_failure_evt_t error_unexpected; +#ifdef CONFIG_SOFTDEVICE_CENTRAL + /** @brief Parameters specific to the @ref PM_EVT_SLAVE_SECURITY_REQ event. */ + ble_gap_evt_sec_request_t slave_security_req; + /** + * @brief Parameters specific to the @ref PM_EVT_FLASH_GARBAGE_COLLECTION_FAILED + * event. + */ +#endif + pm_failure_evt_t garbage_collection_failed; + } params; +} pm_evt_t; + +/** + * @brief Event handler for events from the @ref peer_manager module. + * + * @sa pm_register + * + * @param[in] p_event The event that has occurred. + */ +typedef void (*pm_evt_handler_t)(pm_evt_t const *p_event); + +#ifdef __cplusplus +} +#endif + +#endif /* PEER_MANAGER_TYPES_H__ */ + +/** @} */ diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 7cd28e8ee8..f330201142 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -8,6 +8,7 @@ add_subdirectory_ifdef(CONFIG_BLE_ADV ble_adv) add_subdirectory_ifdef(CONFIG_BLE_CONN_PARAMS ble_conn_params) add_subdirectory_ifdef(CONFIG_BLE_GATT_QUEUE ble_gq) add_subdirectory_ifdef(CONFIG_BLE_RACP ble_racp) +add_subdirectory_ifdef(CONFIG_BLE_CONN_STATE ble_conn_state) add_subdirectory_ifdef(CONFIG_EVENT_SCHEDULER event_scheduler) add_subdirectory_ifdef(CONFIG_BM_BUTTONS bm_buttons) add_subdirectory_ifdef(CONFIG_BM_STORAGE bm_storage) @@ -17,3 +18,4 @@ add_subdirectory_ifdef(CONFIG_SENSORSIM sensorsim) add_subdirectory_ifdef(CONFIG_NCS_BARE_METAL_BOOT_BANNER boot_banner) add_subdirectory_ifdef(CONFIG_ZEPHYR_QUEUE zephyr_queue) add_subdirectory_ifdef(CONFIG_BM_ZMS bm_zms) +add_subdirectory_ifdef(CONFIG_PEER_MANAGER peer_manager) diff --git a/lib/Kconfig b/lib/Kconfig index a1fe1029d0..10317a89f2 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -9,6 +9,7 @@ rsource "ble_adv/Kconfig" rsource "ble_conn_params/Kconfig" rsource "ble_gq/Kconfig" rsource "ble_racp/Kconfig" +rsource "ble_conn_state/Kconfig" rsource "event_scheduler/Kconfig" rsource "bm_buttons/Kconfig" rsource "bm_storage/Kconfig" @@ -18,5 +19,6 @@ rsource "sensorsim/Kconfig" rsource "boot_banner/Kconfig" rsource "zephyr_queue/Kconfig" rsource "bm_zms/Kconfig" +rsource "peer_manager/Kconfig" endmenu diff --git a/lib/ble_conn_state/CMakeLists.txt b/lib/ble_conn_state/CMakeLists.txt new file mode 100644 index 0000000000..26089e31a5 --- /dev/null +++ b/lib/ble_conn_state/CMakeLists.txt @@ -0,0 +1,7 @@ +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +zephyr_library() +zephyr_library_sources(ble_conn_state.c) diff --git a/lib/ble_conn_state/Kconfig b/lib/ble_conn_state/Kconfig new file mode 100644 index 0000000000..a613463689 --- /dev/null +++ b/lib/ble_conn_state/Kconfig @@ -0,0 +1,31 @@ +# +# Copyright (c) 2025 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +menuconfig BLE_CONN_STATE + bool "Event scheduler" + help + A library to defer event processing to main(). + +if BLE_CONN_STATE + +config BLE_CONN_STATE_USER_FLAG_COUNT + int "User flag count" + range 0 24 + default 24 + help + The number of available user flags. + +config BLE_CONN_STATE_DEFAULT_FLAG_COLLECTION_COUNT + int "Default flag collection count" + default 6 + help + The number of flags kept for each connection, excluding user flags. + +module=BLE_CONN_STATE +module-dep=LOG +module-str=BLE Connection state +source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" + +endif diff --git a/lib/ble_conn_state/ble_conn_state.c b/lib/ble_conn_state/ble_conn_state.c new file mode 100644 index 0000000000..c4acb721a7 --- /dev/null +++ b/lib/ble_conn_state/ble_conn_state.c @@ -0,0 +1,463 @@ +/* + * Copyright (c) 2015 - 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(ble_bms, CONFIG_BLE_CONN_STATE_LOG_LEVEL); + +/** The number of flags kept for each connection, including user flags. */ +#define TOTAL_FLAG_COLLECTION_COUNT (CONFIG_BLE_CONN_STATE_DEFAULT_FLAG_COLLECTION_COUNT + \ + CONFIG_BLE_CONN_STATE_USER_FLAG_COUNT) + +/** Connection handles in the same order as they are indexed in the atomics */ +struct ble_conn_state_conn_handle_list connections; + +/** + * @brief Structure containing all the flag collections maintained by the Connection State module. + */ +struct ble_conn_state_flag_collections { + /** Flags indicating which connection handles are valid. */ + atomic_t valid_flags; + /** Flags indicating which connections are connected, since disconnected connection handles + * will not immediately be invalidated. + */ + atomic_t connected_flags; + /** Flags indicating in which connections the local device is the central. */ + atomic_t central_flags; + /** Flags indicating which connections are encrypted. */ + atomic_t encrypted_flags; + /** Flags indicating which connections have encryption with protection from + * man-in-the-middle attacks. + */ + atomic_t mitm_protected_flags; + /** Flags indicating which connections have bonded using LE Secure Connections (LESC). */ + atomic_t lesc_flags; + /** Flags that can be reserved by the user. The flags will be cleared when a connection is + * invalidated, otherwise, the user is wholly responsible for the flag states. + */ + atomic_t user_flags[CONFIG_BLE_CONN_STATE_USER_FLAG_COUNT]; +}; + +/** @brief Structure containing the internal state of the Connection State module. */ +struct ble_conn_state { + /** Bitmap for keeping track of which user flags have been acquired. */ + atomic_t acquired_flags; + union { + /** Flag collection kept by the Connections State module. */ + struct ble_conn_state_flag_collections flags; + /** Flag collections as array to allow iterating over all flag collections. */ + atomic_t flag_array[TOTAL_FLAG_COLLECTION_COUNT]; + }; +}; + +/** Instantiation of the internal state. */ +static struct ble_conn_state bcs = {0}; + +static int bcs_atomic_find_and_set_flag(atomic_t *flags) +{ + for (int i = 0; i < (sizeof(atomic_t) * 8); i++) { + if (atomic_test_and_set_bit(flags, i) == false) { + /* Bit was not set before */ + return i; + } + } + + return -1; +} + +/** @brief Function for resetting all internal memory to the values it had at initialization. */ +static inline void bcs_internal_state_reset(void) +{ + memset(&bcs, 0, sizeof(struct ble_conn_state)); +} + +static struct ble_conn_state_conn_handle_list conn_handle_list_get(atomic_t flags) +{ + struct ble_conn_state_conn_handle_list conn_handle_list = { + .len = 0 + }; + + if (flags) { + for (uint32_t i = 0; i < BLE_CONN_STATE_MAX_CONNECTIONS; i++) { + if (atomic_test_bit(&flags, i)) { + conn_handle_list.conn_handles[conn_handle_list.len++] = i; + } + } + } + + return conn_handle_list; +} + +static uint32_t active_flag_count(atomic_t flags) +{ + uint32_t set_flag_count = 0; + + for (uint32_t i = 0; i < BLE_CONN_STATE_MAX_CONNECTIONS; i++) { + if (atomic_test_bit(&flags, i)) { + set_flag_count += 1; + } + } + + return set_flag_count; +} + +/** + * @brief Function for activating a connection record. + * + * @param conn_handle The connection handle to copy into the record. + * + * @return whether the record was activated successfully. + */ +static bool record_activate(uint16_t conn_handle) +{ + if (conn_handle >= BLE_CONN_STATE_MAX_CONNECTIONS) { + return false; + } + + atomic_set_bit(&bcs.flags.connected_flags, conn_handle); + atomic_set_bit(&bcs.flags.valid_flags, conn_handle); + return true; +} + +/** + * @brief Function for marking a connection record as invalid and resetting the values. + * + * @param conn_handle The connection to invalidate. + */ +static void record_invalidate(uint16_t conn_handle) +{ + for (uint32_t i = 0; i < TOTAL_FLAG_COLLECTION_COUNT; i++) { + atomic_clear_bit(&bcs.flag_array[i], conn_handle); + } +} + +/** + * @brief Function for marking a connection as disconnected. See @ref BLE_CONN_STATUS_DISCONNECTED. + * + * @param conn_handle The connection to set as disconnected. + */ +static void record_set_disconnected(uint16_t conn_handle) +{ + atomic_clear_bit(&bcs.flags.connected_flags, conn_handle); +} + +/** + * @brief Function for invalidating records with a @ref BLE_CONN_STATUS_DISCONNECTED + * connection status + */ +static void record_purge_disconnected(void) +{ + atomic_t disconnected_flags = ~bcs.flags.connected_flags; + struct ble_conn_state_conn_handle_list disconnected_list; + + (void)atomic_and(&disconnected_flags, bcs.flags.valid_flags); + disconnected_list = conn_handle_list_get(disconnected_flags); + + for (uint32_t i = 0; i < disconnected_list.len; i++) { + record_invalidate(disconnected_list.conn_handles[i]); + } +} + +/** + * @brief Function for checking if a user flag has been acquired. + * + * @param[in] flag_index Which flag to check. + * + * @return Whether the flag has been acquired. + */ +static bool user_flag_is_acquired(uint16_t flag_index) +{ + return atomic_test_bit(&bcs.acquired_flags, flag_index); +} + +void ble_conn_state_init(void) +{ + bcs_internal_state_reset(); +} + +static void flag_toggle(atomic_t *flags, uint16_t conn_handle, bool value) +{ + if (value) { + atomic_set_bit(flags, conn_handle); + } else { + atomic_clear_bit(flags, conn_handle); + } +} + +int conn_id_get(uint8_t conn_idx) +{ + return connections.conn_handles[conn_idx]; +} + +int conn_idx_get(uint32_t conn_id) +{ + for (int i = 0; i < connections.len; i++) { + if (connections.conn_handles[i] == conn_id) { + return i; + } + } + + /* conn_id not found in list */ + + for (int i = 0; i < BLE_CONN_STATE_MAX_CONNECTIONS; i++) { + if (atomic_test_bit(&bcs.flags.connected_flags, i) == false) { + record_invalidate(i); + connections.conn_handles[i] = conn_id; + return i; + } + } + + return -1; +} + +/** + * @brief Function for handling BLE events. + * + * @param[in] ble_evt Event received from the BLE stack. + * @param[in] ctx Context. + */ +static void ble_evt_handler(ble_evt_t const *ble_evt, void *ctx) +{ + uint16_t conn_handle = ble_evt->evt.gap_evt.conn_handle; + + switch (ble_evt->header.evt_id) { + case BLE_GAP_EVT_CONNECTED: + record_purge_disconnected(); + + if (!record_activate(conn_handle)) { + /* No more records available. Should not happen. */ + LOG_ERR("No more records available"); + __ASSERT(false, "No more records available"); +#ifdef CONFIG_BLE_GAP_ROLE_CENTRAL + } else if (ble_evt->evt.gap_evt.params.connected.role == BLE_GAP_ROLE_CENTRAL) { + /* Central */ + atomic_set_bit(&bcs.flags.central_flags, conn_handle); +#endif + } else { + /* No implementation required. */ + } + + break; + case BLE_GAP_EVT_DISCONNECTED: + record_set_disconnected(conn_handle); + break; + case BLE_GAP_EVT_CONN_SEC_UPDATE: + uint8_t sec_lv = ble_evt->evt.gap_evt.params.conn_sec_update.conn_sec.sec_mode.lv; + + /* Set/unset flags based on security level. */ + flag_toggle(&bcs.flags.lesc_flags, conn_handle, sec_lv >= 4); + flag_toggle(&bcs.flags.mitm_protected_flags, conn_handle, sec_lv >= 3); + flag_toggle(&bcs.flags.encrypted_flags, conn_handle, sec_lv >= 2); + break; + case BLE_GAP_EVT_AUTH_STATUS: + if (ble_evt->evt.gap_evt.params.auth_status.auth_status == + BLE_GAP_SEC_STATUS_SUCCESS) { + bool lesc = ble_evt->evt.gap_evt.params.auth_status.lesc; + + flag_toggle(&bcs.flags.lesc_flags, conn_handle, lesc); + } + + break; + } +} + +NRF_SDH_BLE_OBSERVER(ble_evt_observer, ble_evt_handler, NULL, BLE_CONN_STATE_BLE_OBSERVER_PRIO); + +bool ble_conn_state_valid(uint16_t conn_handle) +{ + if (conn_handle >= BLE_CONN_STATE_MAX_CONNECTIONS) { + return false; + } + + return atomic_test_bit(&bcs.flags.valid_flags, conn_handle); +} + +uint8_t ble_conn_state_role(uint16_t conn_handle) +{ + uint8_t role = BLE_GAP_ROLE_INVALID; + + if (ble_conn_state_valid(conn_handle)) { +#if defined(CONFIG_SOFTDEVICE_PERIPHERAL) && defined(CONFIG_SOFTDEVICE_CENTRAL) + bool central; + + central = atomic_test_bit(&bcs.flags.central_flags, conn_handle); + role = central ? BLE_GAP_ROLE_CENTRAL : BLE_GAP_ROLE_PERIPH; +#elif defined(CONFIG_SOFTDEVICE_CENTRAL) + role = BLE_GAP_ROLE_CENTRAL; +#else + role = BLE_GAP_ROLE_PERIPH; +#endif + } + + return role; +} + +enum ble_conn_state_status ble_conn_state_status(uint16_t conn_handle) +{ + bool connected; + enum ble_conn_state_status conn_status = BLE_CONN_STATUS_INVALID; + + if (ble_conn_state_valid(conn_handle)) { + connected = atomic_test_bit(&bcs.flags.connected_flags, conn_handle); + conn_status = connected ? BLE_CONN_STATUS_CONNECTED : BLE_CONN_STATUS_DISCONNECTED; + } + + return conn_status; +} + +bool ble_conn_state_encrypted(uint16_t conn_handle) +{ + if (ble_conn_state_valid(conn_handle)) { + return atomic_test_bit(&bcs.flags.encrypted_flags, conn_handle); + } + + return false; +} + +bool ble_conn_state_mitm_protected(uint16_t conn_handle) +{ + if (ble_conn_state_valid(conn_handle)) { + return atomic_test_bit(&bcs.flags.mitm_protected_flags, conn_handle); + } + + return false; +} + +bool ble_conn_state_lesc(uint16_t conn_handle) +{ + if (ble_conn_state_valid(conn_handle)) { + return atomic_test_bit(&bcs.flags.lesc_flags, conn_handle); + } + + return false; +} + +uint32_t ble_conn_state_conn_count(void) +{ + return active_flag_count(bcs.flags.connected_flags); +} + +uint32_t ble_conn_state_central_conn_count(void) +{ + atomic_t central_conn_flags; + + central_conn_flags = bcs.flags.central_flags; + (void)atomic_and(¢ral_conn_flags, bcs.flags.connected_flags); + + return active_flag_count(central_conn_flags); +} + +uint32_t ble_conn_state_peripheral_conn_count(void) +{ + atomic_t peripheral_conn_flags; + + peripheral_conn_flags = ~bcs.flags.central_flags; + (void)atomic_and(&peripheral_conn_flags, bcs.flags.connected_flags); + + return active_flag_count(peripheral_conn_flags); +} + +struct ble_conn_state_conn_handle_list ble_conn_state_conn_handles(void) +{ + return conn_handle_list_get(bcs.flags.valid_flags); +} + +struct ble_conn_state_conn_handle_list ble_conn_state_central_handles(void) +{ + atomic_t central_conn_flags; + + central_conn_flags = bcs.flags.central_flags; + (void)atomic_and(¢ral_conn_flags, bcs.flags.connected_flags); + + return conn_handle_list_get(central_conn_flags); +} + +struct ble_conn_state_conn_handle_list ble_conn_state_periph_handles(void) +{ + atomic_t peripheral_conn_flags; + + peripheral_conn_flags = ~bcs.flags.central_flags; + (void)atomic_and(&peripheral_conn_flags, bcs.flags.connected_flags); + + return conn_handle_list_get(peripheral_conn_flags); +} + +uint16_t ble_conn_state_conn_idx(uint16_t conn_handle) +{ + if (ble_conn_state_valid(conn_handle)) { + return conn_handle; + } else { + return BLE_CONN_STATE_MAX_CONNECTIONS; + } +} + +int ble_conn_state_user_flag_acquire(void) +{ + return bcs_atomic_find_and_set_flag(&bcs.acquired_flags); +} + +bool ble_conn_state_user_flag_get(uint16_t conn_handle, uint16_t flag_index) +{ + if (user_flag_is_acquired(flag_index) && ble_conn_state_valid(conn_handle)) { + return atomic_test_bit(&bcs.flags.user_flags[flag_index], conn_handle); + } else { + return false; + } +} + + +void ble_conn_state_user_flag_set(uint16_t conn_handle, uint16_t flag_index, bool value) +{ + if (user_flag_is_acquired(flag_index) && ble_conn_state_valid(conn_handle)) { + flag_toggle(&bcs.flags.user_flags[flag_index], conn_handle, value); + } +} + +static uint32_t for_each_set_flag(atomic_t flags, + ble_conn_state_user_function_t user_function, void *ctx) +{ + uint32_t call_count = 0; + + if (!user_function) { + return 0; + } + + if (flags) { + for (uint32_t i = 0; i < BLE_CONN_STATE_MAX_CONNECTIONS; i++) { + if (atomic_test_bit(&flags, i)) { + user_function(i, ctx); + call_count += 1; + } + } + } + + return call_count; +} + +uint32_t ble_conn_state_for_each_connected(ble_conn_state_user_function_t user_function, void *ctx) +{ + return for_each_set_flag(bcs.flags.connected_flags, user_function, ctx); +} + + +uint32_t ble_conn_state_for_each_set_user_flag(uint16_t flag_index, + ble_conn_state_user_function_t user_function, + void *ctx) +{ + if (!user_flag_is_acquired(flag_index)) { + return 0; + } + + return for_each_set_flag(bcs.flags.user_flags[flag_index], user_function, ctx); +} diff --git a/lib/peer_manager/CMakeLists.txt b/lib/peer_manager/CMakeLists.txt new file mode 100644 index 0000000000..4aa6973140 --- /dev/null +++ b/lib/peer_manager/CMakeLists.txt @@ -0,0 +1,12 @@ +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +zephyr_library() +zephyr_library_include_directories(include) +zephyr_library_sources(peer_manager.c) +zephyr_library_sources(nrf_strerror.c) + +add_subdirectory(modules) diff --git a/lib/peer_manager/Kconfig b/lib/peer_manager/Kconfig new file mode 100644 index 0000000000..913b9bb10a --- /dev/null +++ b/lib/peer_manager/Kconfig @@ -0,0 +1,120 @@ +# +# Copyright (c) 2025 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menuconfig PEER_MANAGER + bool "Peer Manager" + help + Peer Manager + +if PEER_MANAGER + +config PM_BLE_OBSERVER_PRIO + int "Priority with which BLE events are dispatched to the Peer Manager module." + default 1 + help + Priority with which BLE events are dispatched to the Peer Manager module. + +config PM_MAX_REGISTRANTS + int "Number of event handlers that can be registered." + default 3 + help + Number of event handlers that can be registered. + +config PM_FLASH_BUFFERS + int "Number of internal buffers for flash operations." + default 4 + help + Number of internal buffers for flash operations. + Decrease this value to lower RAM usage. + +config PM_SERVICE_CHANGED_ENABLED + bool "Enable/disable the service changed management for GATT server in Peer Manager." + default y + help + Enable/disable the service changed management for GATT server in Peer Manager. + If not using a GATT server, or using a server without a service changed characteristic, + disable this to save code space. + +config PM_PEER_RANKS_ENABLED + bool "Enable/disable the peer rank management in Peer Manager." + default y + help + Enable/disable the peer rank management in Peer Manager. + Set this to false to save code space if not using the peer rank API. + +config PM_LESC_ENABLED + bool "Enable/disable LESC support in Peer Manager." + default n + depends on PSA_WANT_ALG_ECDH + depends on PSA_WANT_GENERATE_RANDOM + depends on PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_GENERATE + depends on PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT + depends on PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_EXPORT + depends on PSA_WANT_ECC_SECP_R1_256 + depends on (MBEDTLS_PSA_STATIC_KEY_SLOTS && MBEDTLS_PSA_KEY_SLOT_COUNT >= 1) || \ + MBEDTLS_ENABLE_HEAP + + help + Enable/disable LESC support in Peer Manager. + If set to true, you need to call nrf_ble_lesc_request_handler() in the main loop to + respond to LESC-related BLE events. If LESC support is not required, set this to false to + save code space. + +if PM_LESC_ENABLED + +config PM_LESC_GENERATE_NEW_KEYS + bool "Generate new LESC key pair after every pairing attempt" + default n + help + New LESC keys are generated on the auth status event. + +endif # PM_LESC_ENABLED + +config PM_RA_PROTECTION_ENABLED + bool "Enable/disable protection against repeated pairing attempts in Peer Manager." + default n + help + Enable/disable protection against repeated pairing attempts in Peer Manager. + +config PM_RA_PROTECTION_TRACKED_PEERS_NUM + int "Maximum number of peers whose authorization status can be tracked." + default 8 + help + Maximum number of peers whose authorization status can be tracked. + +config PM_RA_PROTECTION_MIN_WAIT_INTERVAL + int "Minimum waiting interval (in ms) before a new pairing attempt can be initiated." + default 4000 + help + Minimum waiting interval (in ms) before a new pairing attempt can be initiated. + +config PM_RA_PROTECTION_MAX_WAIT_INTERVAL + int "Maximum waiting interval (in ms) before a new pairing attempt can be initiated." + default 64000 + help + Maximum waiting interval (in ms) before a new pairing attempt can be initiated. + +config PM_RA_PROTECTION_REWARD_PERIOD + int "Reward period (in ms)." + default 10000 + help + Reward period (in ms). + The waiting interval is gradually decreased when no new failed pairing attempts are made + during reward period. + +config PM_HANDLER_SEC_DELAY_MS + int "Delay before starting security." + default 0 + help + Delay before starting security. + This might be necessary for interoperability reasons, especially as peripheral. + +module=PEER_MANAGER +module-dep=LOG +module-str=Peer Manager +source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" + +endif # PEER_MANAGER diff --git a/lib/peer_manager/include/modules/auth_status_tracker.h b/lib/peer_manager/include/modules/auth_status_tracker.h new file mode 100644 index 0000000000..30072e8dfe --- /dev/null +++ b/lib/peer_manager/include/modules/auth_status_tracker.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @defgroup auth_status_tracker Authorization Status Tracker + * @ingroup peer_manager + * @{ + * @brief An internal module of @ref peer_manager. A module for tracking peers with failed + * authorization attempts. It uses tracking policy, which is described in Bluetooth + * Core Specification v5.0, Vol 3, Part H, Section 2.3.6. + */ + +#ifndef AUTH_STATUS_TRACKER_H__ +#define AUTH_STATUS_TRACKER_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Function for initializing the Authorization Status Tracker module. + * + * @retval NRF_SUCCESS Initialization was successful. + * @retval Other Other error codes might be returned by the @ref app_timer_create function. + */ +uint32_t ast_init(void); + +/** + * @brief Function for notifying about failed authorization attempts. + * + * @param[in] conn_handle Connection handle on which authorization attempt has failed. + */ +void ast_auth_error_notify(uint16_t conn_handle); + +/** + * @brief Function for checking if pairing request must be rejected. + * + * @param[in] conn_handle Connection handle on which this check must be performed. + * + * @retval true If the connected peer is blacklisted. + * @retval false If the connected peer is not blacklisted. + */ +bool ast_peer_blacklisted(uint16_t conn_handle); + +#ifdef __cplusplus +} +#endif + +#endif /* AUTH_STATUS_TRACKER_H__ */ + +/** @} */ diff --git a/lib/peer_manager/include/modules/gatt_cache_manager.h b/lib/peer_manager/include/modules/gatt_cache_manager.h new file mode 100644 index 0000000000..a2964d3036 --- /dev/null +++ b/lib/peer_manager/include/modules/gatt_cache_manager.h @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2015-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @defgroup gatt_cache_manager GATT Cache Manager + * @ingroup peer_manager + * @{ + * @brief An internal module of @ref peer_manager. A module for managing persistent storing of GATT + * attributes. + */ + +#ifndef GATT_CACHE_MANAGER_H__ +#define GATT_CACHE_MANAGER_H__ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Function for initializing the GATT Cache Manager module. + * + * @retval NRF_SUCCESS Initialization was successful. + * @retval NRF_ERROR_INTERNAL If an internal error occurred. + */ +uint32_t gcm_init(void); + +/** + * @brief Function for dispatching SoftDevice events to the GATT Cache Manager module. + * + * @param[in] p_ble_evt The SoftDevice event. + */ +void gcm_ble_evt_handler(ble_evt_t const *p_ble_evt); + +/** + * @brief Function for triggering local GATT database data to be stored persistently. + * + * @details Values are retrieved from SoftDevice and written to persistent storage. + * + * @note This operation happens asynchronously, so any errors are reported as events. + * + * @note This function is only needed when you want to override the regular functionality of the + * module, e.g. to immediately store to flash instead of waiting for the native logic to + * perform the update. + * + * @param[in] conn_handle Connection handle to perform update on. + * + * @retval NRF_SUCCESS Store operation started. + */ +uint32_t gcm_local_db_cache_update(uint16_t conn_handle); + +/** + * @brief Function for manually informing that the local database has changed. + * + * @details This causes a service changed notification to be sent to all bonded peers that + * subscribe to it. + */ +void gcm_local_database_has_changed(void); + +#ifdef __cplusplus +} +#endif + +#endif /* GATT_CACHE_MANAGER_H__ */ + +/** @} */ diff --git a/lib/peer_manager/include/modules/gatts_cache_manager.h b/lib/peer_manager/include/modules/gatts_cache_manager.h new file mode 100644 index 0000000000..891d54c0c3 --- /dev/null +++ b/lib/peer_manager/include/modules/gatts_cache_manager.h @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2015-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @defgroup gatts_cache_manager GATT Server Cache Manager + * @ingroup peer_manager + * @{ + * @brief An internal module of @ref peer_manager. A module for managing persistent storing of GATT + * attributes pertaining to the GATT server role of the local device. + */ + +#ifndef GATTS_CACHE_MANAGER_H__ +#define GATTS_CACHE_MANAGER_H__ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Function for initializing the GATT Server Cache Manager module. + * + * @retval NRF_SUCCESS Initialization was successful. + * @retval NRF_ERROR_INTERNAL If an internal error occurred. + */ +uint32_t gscm_init(void); + +/** + * @brief Function for triggering local GATT database data to be stored persistently. Values are + * retrieved from the SoftDevice and written to persistent storage. + * + * @param[in] conn_handle Connection handle to perform update on. + * + * @retval NRF_SUCCESS Store operation started. + * @retval BLE_ERROR_INVALID_CONN_HANDLE conn_handle does not refer to an active connection with a + * bonded peer. + * @retval NRF_ERROR_INVALID_DATA Refused to update because the GATT database is already up + * to date. + * @retval NRF_ERROR_BUSY Unable to perform operation at this time. Reattempt later. + * @retval NRF_ERROR_DATA_SIZE Write buffer not large enough. Call will never work with + * this GATT database. + * @retval NRF_ERROR_RESOURCES No room in persistent_storage. Free up space; the + * operation will be automatically reattempted after the + * next FDS garbage collection procedure. + */ +uint32_t gscm_local_db_cache_update(uint16_t conn_handle); + +/** + * @brief Function for applying stored local GATT database data to the SoftDevice. Values are + * retrieved from persistent storage and given to the SoftDevice. + * + * @param[in] conn_handle Connection handle to apply values to. + * + * @retval NRF_SUCCESS Store operation started. + * @retval BLE_ERROR_INVALID_CONN_HANDLE conn_handle does not refer to an active connection with a + * bonded peer. + * @retval NRF_ERROR_INVALID_DATA The stored data was rejected by the SoftDevice, which + * probably means that the local database has changed. The + * system part of the sys_attributes was attempted applied, + * so service changed indications can be sent to subscribers. + * @retval NRF_ERROR_BUSY Unable to perform operation at this time. Reattempt later. + * @retval NRF_ERROR_INTERNAL An unexpected error happened. + */ +uint32_t gscm_local_db_cache_apply(uint16_t conn_handle); + +/** + * @brief Function for storing the fact that the local database has changed, for all currently + * bonded peers. + * + * @note This will cause a later call to @ref gscm_service_changed_ind_needed to return true for + * a connection with a currently bonded peer. + */ +void gscm_local_database_has_changed(void); + +/** + * @brief Function for checking if a service changed indication should be sent. + * + * @param[in] conn_handle The connection to check. + * + * @return true if a service changed indication should be sent, false if not. + */ +bool gscm_service_changed_ind_needed(uint16_t conn_handle); + +/** + * @brief Function for sending a service changed indication to a connected peer. + * + * @param[in] conn_handle The connection to send the indication on. + * + * @retval NRF_SUCCESS Indication sent or not needed. + * @retval BLE_ERROR_INVALID_CONN_HANDLE conn_handle does not refer to an active connection. + * @retval NRF_ERROR_BUSY Unable to send indication at this time. Reattempt + * later. + * @retval BLE_ERROR_GATTS_SYS_ATTR_MISSING Information missing. Apply local cache, then reattempt. + * @retval NRF_ERROR_INVALID_PARAM From @ref sd_ble_gatts_service_changed. Unexpected. + * @retval NRF_ERROR_NOT_SUPPORTED Service changed characteristic is not present. + * @retval NRF_ERROR_INVALID_STATE Service changed cannot be indicated to this peer + * because the peer has not subscribed to it. + * @retval NRF_ERROR_INTERNAL An unexpected error happened. + */ +uint32_t gscm_service_changed_ind_send(uint16_t conn_handle); + +/** + * @brief Function for specifying that a peer has been made aware of the latest local database + * change. + * + * @note After calling this, a later call to @ref gscm_service_changed_ind_needed will to return + * false for this peer unless @ref gscm_local_database_has_changed is called again. + * + * @param[in] peer_id The connection to send the indication on. + */ +void gscm_db_change_notification_done(pm_peer_id_t peer_id); + +#ifdef __cplusplus +} +#endif + +#endif /* GATTS_CACHE_MANAGER_H__ */ + +/** @} */ diff --git a/lib/peer_manager/include/modules/id_manager.h b/lib/peer_manager/include/modules/id_manager.h new file mode 100644 index 0000000000..3b52030d26 --- /dev/null +++ b/lib/peer_manager/include/modules/id_manager.h @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2015-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @defgroup id_manager ID Manager + * @ingroup peer_manager + * @{ + * @brief An internal module of @ref peer_manager. A module for keeping track of peer identities + * (IRK and peer address). + */ + +#ifndef PEER_ID_MANAGER_H__ +#define PEER_ID_MANAGER_H__ + +#include +#include +#include +#include +#include "peer_manager_internal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Function for dispatching SoftDevice events to the ID Manager module. + * + * @param[in] p_ble_evt The SoftDevice event. + */ +void im_ble_evt_handler(ble_evt_t const *p_ble_evt); + +/** + * @brief Function for getting the corresponding peer ID from a connection handle. + * + * @param[in] conn_handle The connection handle. + * + * @return The corresponding peer ID, or @ref PM_PEER_ID_INVALID if none could be resolved. + */ +pm_peer_id_t im_peer_id_get_by_conn_handle(uint16_t conn_handle); + +/** + * @brief Function for getting the corresponding peer ID from a master ID (EDIV and rand). + * + * @param[in] p_master_id The master ID. + * + * @return The corresponding peer ID, or @ref PM_PEER_ID_INVALID if none could be resolved. + */ +pm_peer_id_t im_peer_id_get_by_master_id(ble_gap_master_id_t const *p_master_id); + +/** + * @brief Function for getting the corresponding connection handle from a peer ID. + * + * @param[in] peer_id The peer ID. + * + * @return The corresponding connection handle, or @ref BLE_CONN_HANDLE_INVALID if none could be + * resolved. The conn_handle can refer to a recently disconnected connection. + */ +uint16_t im_conn_handle_get(pm_peer_id_t peer_id); + +/** + * @brief Function for comparing two master ids + * @note Two invalid master IDs will not match. + * + * @param[in] p_master_id1 First master id for comparison + * @param[in] p_master_id2 Second master id for comparison + * + * @return True if the input matches, false if it does not. + */ +bool im_master_ids_compare(ble_gap_master_id_t const *p_master_id1, + ble_gap_master_id_t const *p_master_id2); + +/** + * @brief Function for getting the BLE address used by the peer when connecting. + * + * @param[in] conn_handle The connection handle. + * @param[out] p_ble_addr The BLE address used by the peer when the connection specified by + * conn_handle was established. Cannot be NULL. + * + * @retval NRF_SUCCESS The address was found and copied. + * @retval BLE_ERROR_INVALID_CONN_HANDLE conn_handle does not refer to an active connection. + */ +uint32_t im_ble_addr_get(uint16_t conn_handle, ble_gap_addr_t *p_ble_addr); + +/** + * @brief Function for checking if a master ID is valid or invalid + * + * @param[in] p_master_id The master ID. + * + * @retval true The master id is valid. + * @retval false The master id is invalid (i.e. all zeros). + */ +bool im_master_id_is_valid(ble_gap_master_id_t const *p_master_id); + +/** + * @brief Function for checking if two pieces of bonding data correspond to the same peer. + * + * @param[in] p_bonding_data1 The first piece of bonding data to check. + * @param[in] p_bonding_data2 The second piece of bonding data to check. + * + * @retval true The bonding data correspond to the same peer. + * @retval false The bonding data do not correspond to the same peer. + */ +bool im_is_duplicate_bonding_data(pm_peer_data_bonding_t const *p_bonding_data1, + pm_peer_data_bonding_t const *p_bonding_data2); + +/** + * @brief Function for finding if we are already bonded to a peer. + * + * @param[in] p_bonding_data The bonding data to check. + * @param[in] peer_id_skip Optional peer to ignore when searching for duplicates. + * + * @return An existing peer ID for the peer, or PM_PEER_ID_INVALID if none was found. + */ +pm_peer_id_t im_find_duplicate_bonding_data(pm_peer_data_bonding_t const *p_bonding_data, + pm_peer_id_t peer_id_skip); + +/** + * @brief Function for reporting that a new peer ID has been allocated for a specified connection. + * + * @param[in] conn_handle The connection. + * @param[in] peer_id The new peer ID. + */ +void im_new_peer_id(uint16_t conn_handle, pm_peer_id_t peer_id); + +/** + * @brief Function for deleting all of a peer's data from flash and disassociating it from any + * connection handles it is associated with. + * + * @param[in] peer_id The peer to free. + * + * @return Any error code returned by @ref pdb_peer_free. + */ +uint32_t im_peer_free(pm_peer_id_t peer_id); + +/** + * @brief Function to set the local Bluetooth identity address. + * + * @details The local Bluetooth identity address is the address that identifies this device to other + * peers. The address type must be either @ref BLE_GAP_ADDR_TYPE_PUBLIC or @ref + * BLE_GAP_ADDR_TYPE_RANDOM_STATIC. The identity address cannot be changed while roles are + * running. + * + * @note This address will be distributed to the peer during bonding. + * If the address changes, the address stored in the peer device will not be valid and the + * ability to reconnect using the old address will be lost. + * + * @note By default the SoftDevice will set an address of type @ref BLE_GAP_ADDR_TYPE_RANDOM_STATIC + * upon being enabled. The address is a random number populated during the IC manufacturing + * process and remains unchanged for the lifetime of each IC. + * + * @param[in] p_addr Pointer to address structure. + * + * @retval NRF_SUCCESS Address successfully set. + * @retval BLE_ERROR_GAP_INVALID_BLE_ADDR If the GAP address is invalid. + * @retval NRF_ERROR_BUSY Could not process at this time. Process SoftDevice events + * and retry. + * @retval NRF_ERROR_INVALID_STATE The identity address cannot be changed while advertising, + * scanning, or while in a connection. + * @retval NRF_ERROR_INTERNAL If an internal error occurred. + */ +uint32_t im_id_addr_set(ble_gap_addr_t const *p_addr); + +/** + * @brief Function to get the local Bluetooth identity address. + * + * @note This will always return the identity address irrespective of the privacy settings, + * i.e. the address type will always be either @ref BLE_GAP_ADDR_TYPE_PUBLIC or @ref + * BLE_GAP_ADDR_TYPE_RANDOM_STATIC. + * + * @param[out] p_addr Pointer to address structure to be filled in. + * + * @retval NRF_SUCCESS If the address was successfully retrieved. + */ +uint32_t im_id_addr_get(ble_gap_addr_t *p_addr); + +/** + * @brief Function to set privacy settings. + * + * @details Privacy settings cannot be set while advertising, scanning, or while in a connection. + * + * @param[in] p_privacy_params Privacy settings. + * + * @retval NRF_SUCCESS If privacy options were set successfully. + * @retval NRF_ERROR_NULL If @p p_privacy_params is NULL. + * @retval NRF_ERROR_INVALID_PARAM If the address type is not valid. + * @retval NRF_ERROR_BUSY If the request could not be processed at this time. + * Process SoftDevice events and retry. + * @retval NRF_ERROR_INVALID_STATE Privacy settings cannot be changed while BLE roles using + * privacy are enabled. + */ +uint32_t im_privacy_set(pm_privacy_params_t const *p_privacy_params); + +/** + * @brief Function to retrieve the current privacy settings. + * + * @details The privacy settings returned include the current device irk as well. + * + * @param[in] p_privacy_params Privacy settings. + * + * @retval NRF_SUCCESS Successfully retrieved privacy settings. + * @retval NRF_ERROR_NULL @c p_privacy_params is NULL. + * @retval NRF_ERROR_INTERNAL If an internal error occurred. + */ +uint32_t im_privacy_get(pm_privacy_params_t *p_privacy_params); + +/** + * @brief Function for resolving a resolvable address with an identity resolution key (IRK). + * + * @details This function will use the ECB peripheral to resolve a resolvable address. + * This can be used to resolve the identity of a device distributing a random + * resolvable address based on any IRKs you have received earlier. If an address is + * resolved by an IRK, the device distributing the address must also know the IRK. + * + * @param[in] p_addr A random resolvable address. + * @param[in] p_irk An identity resolution key (IRK). + * + * @retval true The irk used matched the one used to create the address. + * @retval false The irk used did not match the one used to create the address, or an argument was + * NULL. + */ +bool im_address_resolve(ble_gap_addr_t const *p_addr, ble_gap_irk_t const *p_irk); + +/** + * @brief Function for setting / clearing the whitelist. + * + * @param p_peers The peers to whitelist. Pass NULL to clear the whitelist. + * @param peer_cnt The number of peers to whitelist. Pass zero to clear the whitelist. + * + * @retval NRF_SUCCESS If the whitelist was successfully set or cleared. + * @retval BLE_GAP_ERROR_WHITELIST_IN_USE If a whitelist is in use. + * @retval BLE_ERROR_GAP_INVALID_BLE_ADDR If any peer has an address which can not be used + * for whitelisting. + * @retval NRF_ERROR_NOT_FOUND If any peer or its data could not be found. + * @retval NRF_ERROR_DATA_SIZE If @p peer_cnt is greater than + * @ref BLE_GAP_WHITELIST_ADDR_MAX_COUNT. + */ +uint32_t im_whitelist_set(pm_peer_id_t const *p_peers, uint32_t const peer_cnt); + +/** + * @brief Retrieves the current whitelist, set by a previous call to @ref im_whitelist_set. + * + * @param[out] A buffer where to copy the GAP addresses. + * @param[inout] In: the size of the @p p_addrs buffer. + * Out: the number of address copied into the buffer. + * @param[out] A buffer where to copy the IRKs. + * @param[inout] In: the size of the @p p_irks buffer. + * Out: the number of IRKs copied into the buffer. + * + * @retval NRF_SUCCESS If the whitelist was successfully retrieved. + * @retval BLE_ERROR_GAP_INVALID_BLE_ADDR If any peer has an address which can not be used for + * whitelisting. + * @retval NRF_ERROR_NOT_FOUND If the data for any of the cached whitelisted peers + * can not be found anymore. It might have been deleted in + * the meanwhile. + * @retval NRF_ERROR_NO_MEM If the provided buffers are too small. + */ +uint32_t im_whitelist_get(ble_gap_addr_t *p_addrs, uint32_t *p_addr_cnt, ble_gap_irk_t *p_irks, + uint32_t *p_irk_cnt); + +/** @brief Set the device identities list. */ +uint32_t im_device_identities_list_set(pm_peer_id_t const *p_peers, uint32_t peer_cnt); + +#ifdef __cplusplus +} +#endif + +#endif /* PEER_ID_MANAGER_H__ */ + +/** @} */ diff --git a/lib/peer_manager/include/modules/nrf_ble_lesc.h b/lib/peer_manager/include/modules/nrf_ble_lesc.h new file mode 100644 index 0000000000..a9da213fa2 --- /dev/null +++ b/lib/peer_manager/include/modules/nrf_ble_lesc.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2018-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** @file + * + * @defgroup nrf_ble_lesc LESC module + * @{ + * @ingroup peer_manager + * @brief Module for handling LESC related events. + */ + +#ifndef NRF_BLE_LESC_H__ +#define NRF_BLE_LESC_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Peer OOB Data handler prototype. */ +typedef ble_gap_lesc_oob_data_t *(*nrf_ble_lesc_peer_oob_data_handler)(uint16_t conn_handle); + +/** + * @brief Function for initializing the LESC module. + * + * @details This function initializes the PSA crypto for ECC and ECDH calculations, which are + * required to handle LESC authentication procedures. + * + * @retval NRF_SUCCESS If the operation was successful. + * @retval NRF_ERROR_INTERNAL If @ref psa_crypto_init failed. + * @returns Other error codes might be returned by the @ref nrf_ble_lesc_keypair_generate function. + */ +uint32_t nrf_ble_lesc_init(void); + +/** + * @brief Function for generating ECC keypair used for the LESC procedure. + * + * @details This function generates an ECC key pair, which consists of a private and public key. + * Keys are generated using ECC and are used to create LESC DH key during authentication procedures. + * + * @retval NRF_SUCCESS If the operation was successful. + * @retval NRF_ERROR_BUSY If any pending request needs to be processed by @ref + * nrf_ble_lesc_request_handler. + * @retval NRF_ERROR_INTERNAL If @ref psa_generate_key, or @ref psa_export_public_key failed. + * @returns Other error codes might be returned. + */ +uint32_t nrf_ble_lesc_keypair_generate(void); + +/** + * @brief Function for generating LESC OOB data. + * + * @details This function generates LESC OOB data, which can be transmitted Out-Of-Band to the peer + * device and used during LESC procedure. It is required to generate ECC keypair with @ref + * nrf_ble_lesc_keypair_generate before calling this function. + * + * @retval NRF_SUCCESS If the operation was successful. + * @retval NRF_ERROR_INVALID_STATE If the ECC keypair hasn't been generated or is currently + * being generated. + */ +uint32_t nrf_ble_lesc_own_oob_data_generate(void); + +/** + * @brief Function for accessing the ECC public key used for LESC DH key generation. + * + * @details This function can be used to access the ECC public key, which is required to generate a + * LESC DH key at the peer side. + * + * @return Pointer to the generated public key or NULL if the key has not been generated yet. + */ +ble_gap_lesc_p256_pk_t *nrf_ble_lesc_public_key_get(void); + +/** + * @brief Function for accessing LESC OOB data. + * + * @details This function can be used to access LESC OOB data that is associated with this device. + * It is required to regenerate LESC OOB data with @ref nrf_ble_lesc_own_oob_data_generate, + * after each change of ECC keypair with @ref nrf_ble_lesc_keypair_generate. + * + * @return Pointer to the LESC OOB data or NULL if the data has not been generated yet or is no + * no longer valid. + */ +ble_gap_lesc_oob_data_t *nrf_ble_lesc_own_oob_data_get(void); + +/** + * @brief Function for setting the handler used to retrieve peer OOB data. + * + * @param[in] handler Function to retrieve peer OOB data. + */ +void nrf_ble_lesc_peer_oob_data_handler_set(nrf_ble_lesc_peer_oob_data_handler handler); + +/** + * @brief Function for responding to a DH key requests. + * + * @details This function calculates DH keys and supplies them to the SoftDevice if there are any + * pending requests for keys. + * + * @note This function should be called systematically (e.g. in the main application loop) to handle + * any pending DH key requests. + * + * @retval NRF_SUCCESS If the operation was successful. + * @retval NRF_ERROR_INTERNAL If the LESC module encountered an internal error. The only way to + * recover from this type of error is to reset the application. + * @returns Other error codes might be returned by the @ref sd_ble_gap_lesc_dhkey_reply function. + */ +uint32_t nrf_ble_lesc_request_handler(void); + +/** + * @brief Function for handling BLE stack events. + * + * @details This function handles events from the BLE stack that are of interest to the module. + * + * @param[in] p_ble_evt Event received from the BLE stack. + */ +void nrf_ble_lesc_on_ble_evt(ble_evt_t const *p_ble_evt); + +#ifdef __cplusplus +} +#endif + +#endif /* NRF_BLE_LESC_H__ */ + +/** @} */ diff --git a/lib/peer_manager/include/modules/peer_data_storage.h b/lib/peer_manager/include/modules/peer_data_storage.h new file mode 100644 index 0000000000..e56095bb7e --- /dev/null +++ b/lib/peer_manager/include/modules/peer_data_storage.h @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2015-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @defgroup peer_data_storage Peer Data Storage + * @ingroup peer_manager + * @{ + * @brief An internal module of @ref peer_manager. This module provides a Peer Manager-specific API + * to the persistent storage. + * + * @details This module uses Flash Data Storage (FDS) to interface with persistent storage. + */ + +#ifndef PEER_DATA_STORAGE_H__ +#define PEER_DATA_STORAGE_H__ + +#include +#include +#include +#include "peer_manager_internal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief The beginning of the range of file IDs reserved for Peer Manager. */ +#define PDS_FIRST_RESERVED_FILE_ID (0xC000) +/** @brief The end of the range of file IDs reserved for Peer Manager. */ +#define PDS_LAST_RESERVED_FILE_ID (0xFFFE) +/** @brief The beginning of the range of record keys reserved for Peer Manager. */ +#define PDS_FIRST_RESERVED_RECORD_KEY (0xC000) +/** @brief The end of the range of record keys reserved for Peer Manager. */ +#define PDS_LAST_RESERVED_RECORD_KEY (0xFFFE) + +/** @brief Macro for converting a @ref pm_peer_id_t to an FDS file ID. */ +#define PEER_ID_TO_FILE_ID (PDS_FIRST_RESERVED_FILE_ID) +/** @brief Macro for converting an FDS file ID to a @ref pm_peer_id_t. */ +#define FILE_ID_TO_PEER_ID (-PDS_FIRST_RESERVED_FILE_ID) +/** @brief Macro for converting a @ref pm_peer_data_id_t to an FDS record ID. */ +#define DATA_ID_TO_RECORD_KEY (PDS_FIRST_RESERVED_RECORD_KEY) +/** @brief Macro for converting an FDS record ID to a @ref pm_peer_data_id_t. */ +#define RECORD_KEY_TO_DATA_ID (-PDS_FIRST_RESERVED_RECORD_KEY) + +/** + * @brief Function for initializing the module. + * + * @retval NRF_SUCCESS If initialization was successful. + * @retval NRF_ERROR_RESOURCES If no flash pages were available for use. + * @retval NRF_ERROR_INTERNAL If the module couldn't register with the flash filesystem. + */ +uint32_t pds_init(void); + +/** + * @brief Function for reading peer data in flash. + * + * @param[in] peer_id The peer the data belongs to. + * @param[in] data_id The data to retrieve. + * @param[out] p_data The peer data. May not be @c NULL. p_data.length_words and p_data.data_id + * are ignored. p_data.p_all_data is ignored if @p p_buf_len is @c NULL. + * @param[in] p_buf_len Length of the provided buffer, in bytes. Pass @c NULL to only copy + * a pointer to the data in flash. + * + * @retval NRF_SUCCESS If the operation was successful. + * @retval NRF_ERROR_INVALID_PARAM If @p peer_id or @p data_id are invalid. + * @retval NRF_ERROR_NOT_FOUND If the data was not found in flash. + * @retval NRF_ERROR_DATA_SIZE If the provided buffer is too small. The data is still copied, + * filling the provided buffer. + */ +uint32_t pds_peer_data_read(pm_peer_id_t peer_id, pm_peer_data_id_t data_id, + pm_peer_data_t *const p_data, uint32_t const *const p_buf_len); + +/** + * @brief Function to prepare iterating over peer data in flash using @ref pds_peer_data_iterate. + * Call this function once each time before iterating using @ref pds_peer_data_iterate. + */ +void pds_peer_data_iterate_prepare(void); + +/** + * @brief Function for iterating peers' data in flash. + * Always call @ref pds_peer_data_iterate_prepare before starting iterating. + * + * @param[in] data_id The peer data to iterate over. + * @param[out] p_peer_id The peer the data belongs to. + * @param[out] p_data The peer data in flash. + * + * @retval true If the operation was successful. + * @retval false If the data was not found in flash, or another error occurred. + */ +bool pds_peer_data_iterate(pm_peer_data_id_t data_id, pm_peer_id_t *const p_peer_id, + pm_peer_data_flash_t *const p_data); + +/** + * @brief Function for storing peer data in flash. If the same piece of data already exists for the + * given peer, it will be updated. This operation is asynchronous. + * Expect a @ref PM_EVT_PEER_DATA_UPDATE_SUCCEEDED or @ref PM_EVT_PEER_DATA_UPDATE_FAILED + * event. + * + * @param[in] peer_id The peer the data belongs to. + * @param[in] p_peer_data The peer data. May not be @c NULL. + * @param[out] p_store_token A token identifying this particular store operation. The token can be + * used to identify events pertaining to this operation. Pass @p NULL + * if not used. + * + * @retval NRF_SUCCESS If the operation was initiated successfully. + * @retval NRF_ERROR_INVALID_PARAM If @p peer_id or the data ID in @p_peer_data are invalid. + * @retval NRF_ERROR_INVALID_ADDR If @p p_peer_data is not word-aligned. + * @retval NRF_ERROR_RESOURCES If no space is available in flash. + * @retval NRF_ERROR_BUSY If the flash filesystem was busy. + * @retval NRF_ERROR_INTERNAL If an unexpected error occurred. + */ +uint32_t pds_peer_data_store(pm_peer_id_t peer_id, pm_peer_data_const_t const *p_peer_data, + pm_store_token_t *p_store_token); + +/** + * @brief Function for deleting peer data in flash. This operation is asynchronous. + * Expect a @ref PM_EVT_PEER_DATA_UPDATE_SUCCEEDED or @ref PM_EVT_PEER_DATA_UPDATE_FAILED + * event. + * + * @param[in] peer_id The peer the data belongs to + * @param[in] data_id The data to delete. + * + * @retval NRF_SUCCESS If the operation was initiated successfully. + * @retval NRF_ERROR_INVALID_PARAM If @p peer_id or @p data_id are invalid. + * @retval NRF_ERROR_NOT_FOUND If data was not found in flash. + * @retval NRF_ERROR_BUSY If the flash filesystem was busy. + * @retval NRF_ERROR_INTERNAL If an unexpected error occurred. + */ +uint32_t pds_peer_data_delete(pm_peer_id_t peer_id, pm_peer_data_id_t data_id); + +/** + * @brief Function for claiming an unused peer ID. + * + * @retval PM_PEER_ID_INVALID If no peer ID was available. + */ +pm_peer_id_t pds_peer_id_allocate(void); + +/** + * @brief Function for freeing a peer ID and deleting all data associated with it in flash. + * + * @param[in] peer_id The ID of the peer to free. + * + * @retval NRF_SUCCESS The operation was initiated successfully. + * @retval NRF_ERROR_INVALID_PARAM If @p peer_id is invalid. + */ +uint32_t pds_peer_id_free(pm_peer_id_t peer_id); + +/** + * @brief Function for finding out whether a peer ID is in use. + * + * @param[in] peer_id The peer ID to inquire about. + * + * @retval true @p peer_id is in use. + * @retval false @p peer_id is free. + */ +bool pds_peer_id_is_allocated(pm_peer_id_t peer_id); + +/** + * @brief Function for finding out whether a peer ID is marked for deletion. + * + * @param[in] peer_id The peer ID to inquire about. + * + * @retval true @p peer_id is marked for deletion. + * @retval false @p peer_id is not marked for deletion. + */ +bool pds_peer_id_is_deleted(pm_peer_id_t peer_id); + +/** + * @brief Function for getting the next peer ID in the sequence of all used peer IDs. Can be + * used to loop through all used peer IDs. + * + * @note @ref PM_PEER_ID_INVALID is considered to be before the first and after the last ordinary + * peer ID. + * + * @param[in] prev_peer_id The previous peer ID. + * + * @return The first ordinary peer ID If @p prev_peer_id is @ref PM_PEER_ID_INVALID. + * @retval PM_PEER_ID_INVALID If @p prev_peer_id is the last ordinary peer ID or the + * module is not initialized. + */ +pm_peer_id_t pds_next_peer_id_get(pm_peer_id_t prev_peer_id); + +/** + * @brief Function for getting the next peer ID in the sequence of all peer IDs pending deletion. + * Can be used to loop through all used peer IDs. + * + * @note @ref PM_PEER_ID_INVALID is considered to be before the first and after the last ordinary + * peer ID. + * + * @param[in] prev_peer_id The previous peer ID. + * + * @return The next peer ID pending deletion. + * @return The first ordinary peer ID if prev_peer_id was @ref PM_PEER_ID_INVALID. + * @retval PM_PEER_ID_INVALID if prev_peer_id was the last ordinary peer ID or the module + * is not initialized. + */ +pm_peer_id_t pds_next_deleted_peer_id_get(pm_peer_id_t prev_peer_id); + +/** + * @brief Function for querying the number of valid peer IDs available. I.E the number of peers + * in persistent storage. + * + * @return The number of valid peer IDs. + */ +uint32_t pds_peer_count_get(void); + +#ifdef __cplusplus +} +#endif + +#endif /* PEER_DATA_STORAGE_H__ */ + +/** @} */ diff --git a/lib/peer_manager/include/modules/peer_database.h b/lib/peer_manager/include/modules/peer_database.h new file mode 100644 index 0000000000..dd42311362 --- /dev/null +++ b/lib/peer_manager/include/modules/peer_database.h @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2015-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @defgroup peer_database Peer Database + * @ingroup peer_manager + * @{ + * @brief An internal module of @ref peer_manager. A module for simple management of reading and + * writing of peer data into persistent storage. + * + */ + +#ifndef PEER_DATABASE_H__ +#define PEER_DATABASE_H__ + +#include +#include +#include "peer_manager_internal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief The size (in bytes) of each block in the internal buffer accessible via + * @ref pdb_write_buf_get. + */ +#define PDB_WRITE_BUF_SIZE (sizeof(pm_peer_data_bonding_t)) + +/** + * @brief Macro for creating a peer ID value from a connection handle. + * + * Use this macro with pdb_write_buf_get() or pdb_write_buf_store(). It allows to create a write + * buffer even if you do not yet know the proper peer ID the data will be stored for. + * + * @return @p conn_handle + @ref PM_PEER_ID_N_AVAILABLE_IDS. + */ +#define PDB_TEMP_PEER_ID(conn_handle) (PM_PEER_ID_N_AVAILABLE_IDS + conn_handle) + +/** + * @brief Function for initializing the module. + * + * @retval NRF_SUCCESS If initialization was successful. + * @retval NRF_ERROR_INTERNAL An unexpected error happened. + */ +uint32_t pdb_init(void); + +/** + * @brief Function for freeing a peer's persistent bond storage. + * + * @note This function will call @ref pdb_write_buf_release on the data for this peer. + * + * @param[in] peer_id ID to be freed. + * + * @retval NRF_SUCCESS Peer ID was released and clear operation was initiated + * successfully. + * @retval NRF_ERROR_INVALID_PARAM Peer ID was invalid. + */ +uint32_t pdb_peer_free(pm_peer_id_t peer_id); + +/** + * @brief Function for retrieving a pointer to peer data in flash (read-only). + * + * @note Dereferencing this pointer is not the safest thing to do if interrupts are enabled, + * because Flash Data Storage garbage collection might move the data around. Either disable + * interrupts while using the data, or use @ref pdb_peer_data_load. + * + * @param[in] peer_id The peer the data belongs to. + * @param[in] data_id The data to read. + * @param[out] p_peer_data The peer data, read-only. + * + * @retval NRF_SUCCESS If the pointer to the data was retrieved successfully. + * @retval NRF_ERROR_INVALID_PARAM If either @p peer_id or @p data_id are invalid. + * @retval NRF_ERROR_NOT_FOUND If data was not found in flash. + */ +uint32_t pdb_peer_data_ptr_get(pm_peer_id_t peer_id, pm_peer_data_id_t data_id, + pm_peer_data_flash_t *const p_peer_data); + +/** + * @brief Function for retrieving pointers to a write buffer for peer data. + * + * @details This function will provide pointers to a buffer of the data. The data buffer will not be + * written to persistent storage until @ref pdb_write_buf_store is called. The buffer is + * released by calling either @ref pdb_write_buf_release, @ref pdb_write_buf_store, or + * @ref pdb_peer_free. + * + * When the data_id refers to a variable length data type, the available size is written + * to the data, both the top-level, and any internal length fields. + * + * @note Calling this function on a peer_id/data_id pair that already has a buffer created will + * give the same buffer, not create a new one. If n_bufs was increased since last time, the + * buffer might be relocated to be able to provide additional room. In this case, the data + * will be copied. If n_bufs was increased since last time, this function might return @ref + * NRF_ERROR_BUSY. In that case, the buffer is automatically released. + * + * @param[in] peer_id ID of the peer to get a write buffer for. If @p peer_id is larger than + * @ref PM_PEER_ID_N_AVAILABLE_IDS, it is interpreted as pertaining to + * the connection with connection handle peer_id - + * PM_PEER_ID_N_AVAILABLE_IDS. See @ref PDB_TEMP_PEER_ID. + * @param[in] data_id The piece of data to get. + * @param[in] n_bufs Number of contiguous buffers needed. + * @param[out] p_peer_data Pointers to mutable peer data. + * + * @retval NRF_SUCCESS Data retrieved successfully. + * @retval NRF_ERROR_INVALID_PARAM @p data_id was invalid, or @p n_bufs was 0 or more than the + * total available buffers. + * @retval NRF_ERROR_FORBIDDEN n_bufs was higher or lower than the existing buffer. If needed, + * release the existing buffer with @ref pdb_write_buf_release, and + * call this function again. + * @retval NRF_ERROR_NULL p_peer_data was NULL. + * @retval NRF_ERROR_BUSY Not enough buffer(s) available. + * @retval NRF_ERROR_INTERNAL Unexpected internal error. + */ +uint32_t pdb_write_buf_get(pm_peer_id_t peer_id, pm_peer_data_id_t data_id, uint32_t n_bufs, + pm_peer_data_t *p_peer_data); + +/** + * @brief Function for freeing a write buffer allocated with @ref pdb_write_buf_get. + * + * @note This function will not write peer data to persistent memory. Data in released buffer will + * be lost. + * + * @param[in] peer_id ID of peer to release buffer for. + * @param[in] data_id Which piece of data to release buffer for. + * + * @retval NRF_SUCCESS Successfully released buffer. + * @retval NRF_ERROR_NOT_FOUND No buffer was allocated for this peer ID/data ID pair. + */ +uint32_t pdb_write_buf_release(pm_peer_id_t peer_id, pm_peer_data_id_t data_id); + +/** + * @brief Function for writing data into persistent storage. Writing happens asynchronously. + * + * @note This will unlock the data after it has been written. + * + * @param[in] peer_id The ID used to address the write buffer. + * @param[in] data_id The piece of data to store. + * @param[in] new_peer_id The ID to put in flash. This is usually the same as peer_id, but + * must be valid, i.e. allocated (and smaller than @ref + * PM_PEER_ID_N_AVAILABLE_IDS). + * + * @retval NRF_SUCCESS Data storing was successfully started. + * @retval NRF_ERROR_RESOURCES No space available in persistent storage. Please clear some + * space, the operation will be reattempted after the next compress + * procedure. + * @retval NRF_ERROR_INVALID_PARAM @p data_id or @p new_peer_id was invalid. + * @retval NRF_ERROR_NOT_FOUND No buffer has been allocated for this peer ID/data ID pair. + * @retval NRF_ERROR_INTERNAL Unexpected internal error. + */ +uint32_t pdb_write_buf_store(pm_peer_id_t peer_id, pm_peer_data_id_t data_id, + pm_peer_id_t new_peer_id); + +#ifdef __cplusplus +} +#endif + +#endif /* PEER_DATABASE_H__ */ + +/** @} */ diff --git a/lib/peer_manager/include/modules/peer_id.h b/lib/peer_manager/include/modules/peer_id.h new file mode 100644 index 0000000000..6353b80440 --- /dev/null +++ b/lib/peer_manager/include/modules/peer_id.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2015-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @defgroup peer_id Peer IDs + * @ingroup peer_manager + * @{ + * @brief An internal module of @ref peer_manager. This module keeps track of which peer IDs are in + * use and which are free. + */ + +#ifndef PEER_ID_H__ +#define PEER_ID_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Function for initializing the module. */ +void peer_id_init(void); + +/** + * @brief Function for claiming an unused peer ID. + * + * @param peer_id The peer ID to allocate. If this is @ref PM_PEER_ID_INVALID, the first available + * will be allocated. + * + * @return The allocated peer ID. + * @retval PM_PEER_ID_INVALID If no peer ID could be allocated or module is not initialized. + */ +pm_peer_id_t peer_id_allocate(pm_peer_id_t peer_id); + +/** + * @brief Function for marking a peer ID for deletion. + * + * @param peer_id The peer ID to delete. + * + * @retval true Deletion was successful. + * @retval false Peer ID already marked for deletion, peer_id was PM_PEER_ID_INVALID, or module is + * not initialized. + */ +bool peer_id_delete(pm_peer_id_t peer_id); + +/** + * @brief Function for freeing a peer ID and clearing all data associated with it in persistent + * storage. + * + * @param[in] peer_id Peer ID to free. + */ +void peer_id_free(pm_peer_id_t peer_id); + +/** + * @brief Function for finding out whether a peer ID is marked for deletion. + * + * @param[in] peer_id The peer ID to inquire about. + * + * @retval true peer_id is in marked for deletion. + * @retval false peer_id is not marked for deletion, or the module is not initialized. + */ +bool peer_id_is_deleted(pm_peer_id_t peer_id); + +/** + * @brief Function for finding out whether a peer ID is in use. + * + * @param[in] peer_id The peer ID to inquire about. + * + * @retval true peer_id is in use. + * @retval false peer_id is free, or the module is not initialized. + */ +bool peer_id_is_allocated(pm_peer_id_t peer_id); + +/** + * @brief Function for getting the next peer ID in the sequence of all used peer IDs. Can be + * used to loop through all used peer IDs. + * + * @note @ref PM_PEER_ID_INVALID is considered to be before the first and after the last ordinary + * peer ID. + * + * @param[in] prev_peer_id The previous peer ID. + * + * @return The next peer ID. + * @return The first used peer ID if prev_peer_id was @ref PM_PEER_ID_INVALID. + * @retval PM_PEER_ID_INVALID if prev_peer_id was the last ordinary peer ID or the module is + * not initialized. + */ +pm_peer_id_t peer_id_get_next_used(pm_peer_id_t prev_peer_id); + +/** + * @brief Function for getting the next peer ID in the sequence of all peer IDs marked for deletion. + * Can be used to loop through all peer IDs marked for deletion. + * + * @note @ref PM_PEER_ID_INVALID is considered to be before the first and after the last ordinary + * peer ID. + * + * @param[in] prev_peer_id The previous peer ID. + * + * @return The next peer ID. + * @return The first used peer ID if prev_peer_id was @ref PM_PEER_ID_INVALID. + * @retval PM_PEER_ID_INVALID if prev_peer_id was the last ordinary peer ID or the module is + * not initialized. + */ +pm_peer_id_t peer_id_get_next_deleted(pm_peer_id_t prev_peer_id); + +/** + * @brief Function for querying the number of valid peer IDs available. I.E the number of peers + * in persistent storage. + * + * @return The number of valid peer IDs, or 0 if module is not initialized. + */ +uint32_t peer_id_n_ids(void); + +#ifdef __cplusplus +} +#endif + +#endif /* PEER_ID_H__ */ + +/** @} */ diff --git a/lib/peer_manager/include/modules/peer_manager_handler.h b/lib/peer_manager/include/modules/peer_manager_handler.h new file mode 100644 index 0000000000..fd28686fa6 --- /dev/null +++ b/lib/peer_manager/include/modules/peer_manager_handler.h @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2018-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @file peer_manager_handler.h + * + * @defgroup pm_handler Peer Manager Standard Event Handlers + * @ingroup peer_manager + * @{ + * @brief Standard event handlers implementing some best practices for BLE security. + */ + +#ifndef PEER_MANAGER_HANDLER_H__ +#define PEER_MANAGER_HANDLER_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Standard function for making Peer Manager calls based on Peer Manager events. + * + * This function does the following: + * - Logs all PM events using @ref nrf_log, at different severity levels. + * - Starts encryption if connected to an already bonded peer. This is affected by @ref + * PM_HANDLER_SEC_DELAY_MS. + * - Calls @ref app_error on fatal errors. + * + * @note In normal circumstances, this function should be called for every Peer Manager event. + * + * @param[in] p_pm_evt Peer Manager event to handle. + */ +void pm_handler_on_pm_evt(pm_evt_t const *p_pm_evt); + +/** + * @brief Auxiliary standard function for logging Peer Manager events. + * + * This function logs all PM events using @ref nrf_log, at different severity levels. The + * @ref PM_LOG_ENABLED and other @c PM_LOG_* configs control these log messages. + * + * @note This function is called internally by @ref pm_handler_on_pm_evt. + * + * @param[in] p_pm_evt Peer Manager event to log. + */ +void pm_handler_pm_evt_log(pm_evt_t const *p_pm_evt); + +/** + * @brief Auxiliary standard function for maintaining room in flash based on Peer Manager events. + * + * This function does the following: + * - Ranks peers by when they last connected. + * - Garbage collects the flash when needed. + * - Deletes the lowest ranked peer(s) when garbage collection is insufficient. + * + * @note See also @ref pm_handler_flash_clean_on_return. + * @note In normal circumstances, this function should be called for every Peer Manager event. + * @note This function is a supplement to @ref pm_handler_on_pm_evt, not its replacement. + * + * @param[in] p_pm_evt Peer Manager event to handle. + */ +void pm_handler_flash_clean(pm_evt_t const *p_pm_evt); + +/** + * @brief Function to call when a Peer Manager function returns @ref NRF_ERROR_RESOURCES. + * + * @note This should only be used if @ref pm_handler_flash_clean is also used. + */ +void pm_handler_flash_clean_on_return(void); + +/** + * @brief Auxiliary standard function for disconnecting when the connection could not be secured. + * + * This function disconnects whenever connection security fails, i.e. whenever it receives a + * @ref PM_EVT_CONN_SEC_FAILED. + * + * @note In normal circumstances, this function should be called for every Peer Manager event. + * @note This function is a supplement to @ref pm_handler_on_pm_evt, not its replacement. + * + * @param[in] p_pm_evt Peer Manager event to handle. + */ +void pm_handler_disconnect_on_sec_failure(pm_evt_t const *p_pm_evt); + +/** + * @brief Auxiliary standard function for disconnecting on insufficient connection security. + * + * This function disconnects whenever the connection security succeeds, that is whenever it + * receives a @ref PM_EVT_CONN_SEC_SUCCEEDED, but the established security does not fulfill the + * provided criteria. + * + * @note In normal circumstances, this function should be called for every Peer Manager event. + * @note This function is a supplement to @ref pm_handler_on_pm_evt, not its replacement. + * + * @param[in] p_pm_evt Peer Manager event to handle. + * @param[in] p_min_conn_sec Minimum security status below which to disconnect the link. + */ +void pm_handler_disconnect_on_insufficient_sec(pm_evt_t const *p_pm_evt, + pm_conn_sec_status_t *p_min_conn_sec); + +/** + * @brief Function for securing a connection when it is established. + * + * This function starts security when receiving a @ref BLE_GAP_EVT_CONNECTED event. This is + * affected by @ref PM_HANDLER_SEC_DELAY_MS. + * + * @note In normal circumstances, this function should be called for every BLE event. + * + * @param[in] p_ble_evt BLE event to handle. + */ +void pm_handler_secure_on_connection(ble_evt_t const *p_ble_evt); + +/** + * @brief Function for securing a connection if a GATT read or write operation lacks security. + * + * This function starts pairing if a GATTC procedure fails with insufficient encryption + * or insufficient authentication. This is meant to delay performing pairing/bonding until + * it is actually needed to access resources. This is affected by @ref PM_HANDLER_SEC_DELAY_MS. + * + * @note When using this handler, the failed GATTC operation must be retried by the user. + * @note This does not work when using Write Without Response (@ref BLE_GATT_OP_WRITE_CMD) because + * the server does not send any response, even on error. Instead, the write will be + * silently dropped by the server. + * @note In normal circumstances, this function should be called for every BLE event. + * + * @param[in] p_ble_evt BLE event to handle. + */ +void pm_handler_secure_on_error(ble_evt_t const *p_ble_evt); + +#ifdef __cplusplus +} +#endif + +#endif /* PEER_MANAGER_HANDLER_H__ */ + +/** @} */ diff --git a/lib/peer_manager/include/modules/peer_manager_internal.h b/lib/peer_manager/include/modules/peer_manager_internal.h new file mode 100644 index 0000000000..3f17b61b5e --- /dev/null +++ b/lib/peer_manager/include/modules/peer_manager_internal.h @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2015-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @file peer_manager_types.h + * + * @addtogroup peer_manager + * @brief File containing definitions used solely inside the Peer Manager's modules. + * @{ + */ + +#ifndef PEER_MANAGER_INTERNAL_H__ +#define PEER_MANAGER_INTERNAL_H__ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief One piece of data associated with a peer, together with its type. + * + * @note This type is deprecated. + */ +typedef struct { + /** @brief The length of the data in words. */ + uint16_t length_words; + /** + * @brief ID that specifies the type of data (defines which member of the union is + * used). + */ + pm_peer_data_id_t data_id; + /** @brief The data. */ + union { + /** @brief The exchanged bond information in addition to metadata of the bonding. */ + pm_peer_data_bonding_t *p_bonding_data; + /** + * @brief A value locally assigned to this peer. Its + * interpretation is up to the user. The rank is not set + * automatically by the Peer Manager, but it is assigned by + * the user using either @ref pm_peer_rank_highest or a @ref + * PM_PEER_DATA_FUNCTIONS function. + */ + uint32_t *p_peer_rank; + /** @brief Value of peer's Central Address Resolution characteristic. */ + uint32_t *p_central_addr_res; + /** @brief Whether a service changed indication should be sent to the peer. */ + bool *p_service_changed_pending; + /** @brief Persistent information pertaining to a peer GATT client. */ + pm_peer_data_local_gatt_db_t *p_local_gatt_db; + /** @brief Persistent information pertaining to a peer GATT server. */ + ble_gatt_db_srv_t *p_remote_gatt_db; + /** + * @brief Arbitrary data to associate with the peer. This data can be freely used + * by the application. + */ + uint8_t *p_application_data; + /** + * @brief Generic access pointer to the data. It is used only to + * handle the data without regard to type. + */ + void *p_all_data; + }; +} pm_peer_data_t; + +/** + * @brief Immutable version of @ref pm_peer_data_t. + * + * @note This type is deprecated. + */ +typedef struct { + /** @brief The length of the data in words. */ + uint16_t length_words; + /** + * @brief ID that specifies the type of data (defines which member of the union is + * used). + */ + pm_peer_data_id_t data_id; + /** @brief The data. */ + union { + /** @brief Immutable @ref pm_peer_data_t::p_bonding_data. */ + pm_peer_data_bonding_t const *p_bonding_data; + /** @brief Immutable @ref pm_peer_data_t::p_peer_rank. */ + uint32_t const *p_peer_rank; + /** @brief Immutable @ref pm_peer_data_t::p_central_addr_res. */ + uint32_t const *p_central_addr_res; + /** @brief Immutable @ref pm_peer_data_t::p_service_changed_pending. */ + bool const *p_service_changed_pending; + /** @brief Immutable @ref pm_peer_data_t::p_local_gatt_db. */ + pm_peer_data_local_gatt_db_t const *p_local_gatt_db; + /** @brief Immutable @ref pm_peer_data_t::p_remote_gatt_db. */ + ble_gatt_db_srv_t const *p_remote_gatt_db; + /** @brief Immutable @ref pm_peer_data_t::p_application_data. */ + uint8_t const *p_application_data; + /** @brief Immutable @ref pm_peer_data_t::p_all_data. */ + void const *p_all_data; + }; +} pm_peer_data_const_t; + +/** + * @brief Version of @ref pm_peer_data_t that reflects the structure of peer data in flash. + * + * @note This type is deprecated. + */ +typedef pm_peer_data_const_t pm_peer_data_flash_t; + +/** + * @brief Event handler for events from the @ref peer_manager module. + * + * @sa pm_register + * + * @param[in] p_event The event that has occurred. + */ +typedef void (*pm_evt_handler_internal_t)(pm_evt_t *p_event); + +/** @brief The number of bytes in a word. */ +#define BYTES_PER_WORD (4) + +/** + * @brief Macro for calculating the flash size of bonding data. + * + * @return The number of words that the data takes in flash. + */ +#define PM_BONDING_DATA_N_WORDS() BYTES_TO_WORDS(sizeof(pm_peer_data_bonding_t)) + +/** + * @brief Macro for calculating the flash size of service changed pending state. + * + * @return The number of words that the data takes in flash. + */ +#define PM_SC_STATE_N_WORDS() BYTES_TO_WORDS(sizeof(bool)) + +/** + * @brief Macro for calculating the flash size of local GATT database data. + * + * @param[in] local_db_len The length, in bytes, of the database as reported by the SoftDevice. + * + * @return The number of words that the data takes in flash. + */ +#define PM_LOCAL_DB_N_WORDS(local_db_len) \ + BYTES_TO_WORDS((local_db_len) + PM_LOCAL_DB_LEN_OVERHEAD_BYTES) + +/** + * @brief Macro for calculating the length of a local GATT database attribute array. + * + * @param[in] n_words The number of words that the data takes in flash. + * + * @return The length of the database attribute array. + */ +#define PM_LOCAL_DB_LEN(n_words) (((n_words)*BYTES_PER_WORD) - PM_LOCAL_DB_LEN_OVERHEAD_BYTES) + +/** + * @brief Macro for calculating the flash size of remote GATT database data. + * + * @param[in] service_count The number of services in the service array. + * + * @return The number of words that the data takes in flash. + */ +#define PM_REMOTE_DB_N_WORDS(service_count) \ + BYTES_TO_WORDS(sizeof(ble_gatt_db_srv_t) * (service_count)) + +/** + * @brief Macro for calculating the flash size of remote GATT database data. + * + * @param[in] n_words The length in number of words. + * + * @return The number of words that the data takes in flash. + */ +#define PM_REMOTE_DB_N_SERVICES(n_words) (((n_words)*BYTES_PER_WORD) / sizeof(ble_gatt_db_srv_t)) + +/** + * @brief Function for calculating the flash size of the usage index. + * + * @return The number of words that the data takes in flash. + */ +#define PM_USAGE_INDEX_N_WORDS() BYTES_TO_WORDS(sizeof(uint32_t)) + +#ifdef NRF_PM_DEBUG + +#define NRF_PM_DEBUG_CHECK(condition) \ + if (!(condition)) { \ + __asm("bkpt #0"); \ + } + +#else + +/* Prevent "variable set but never used" compiler warnings. */ +#define NRF_PM_DEBUG_CHECK(condition) (void)(condition) + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* PEER_MANAGER_INTERNAL_H__ */ + +/** @} */ diff --git a/lib/peer_manager/include/modules/pm_buffer.h b/lib/peer_manager/include/modules/pm_buffer.h new file mode 100644 index 0000000000..846c85c9a7 --- /dev/null +++ b/lib/peer_manager/include/modules/pm_buffer.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2015-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @defgroup pm_buffer Buffer + * @ingroup peer_manager + * @{ + * @brief An internal module of @ref peer_manager. This module provides a simple buffer. + */ + +#ifndef BUFFER_H__ +#define BUFFER_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Invalid buffer block ID. */ +#define PM_BUFFER_INVALID_ID 0xFF + +/** + * @brief Convenience macro for declaring memory and initializing a buffer instance. + * + * @param[out] p_buffer The buffer instance to initialize. + * @param[in] n_blocks The desired number of blocks in the buffer. + * @param[in] block_size The desired block size of the buffer. + * @param[out] err_code The return code from @ref pm_buffer_init. + */ +#define PM_BUFFER_INIT(p_buffer, n_blocks, block_size, err_code) \ + do { \ + __ALIGN(4) static uint8_t buffer_memory[(n_blocks) * (block_size)]; \ + static atomic_t mutex_memory[(n_blocks - 1) / (sizeof(atomic_t) * 8) + 1]; \ + err_code = pm_buffer_init((p_buffer), buffer_memory, (n_blocks) * (block_size), \ + mutex_memory, (n_blocks), (block_size)); \ + } while (0) + +typedef struct { + /** + * @brief The storage for all buffer entries. The size of the buffer must be + * n_blocks*block_size. + */ + uint8_t *p_memory; + /** @brief A mutex group with one mutex for each buffer entry. */ + atomic_t *p_mutex; + /** @brief The number of allocatable blocks in the buffer. */ + uint32_t n_blocks; + /** @brief The size of each block in the buffer. */ + uint32_t block_size; +} pm_buffer_t; + +/** + * @brief Function for initializing a buffer instance. + * + * @param[out] p_buffer The buffer instance to initialize. + * @param[in] p_buffer_memory The memory this buffer will use. + * @param[in] buffer_memory_size The size of p_buffer_memory. This must be at least + * n_blocks*block_size. + * @param[in] p_mutex_memory The memory for the mutexes. This must be at least + * @ref NRF_ATFLAGS_ARRAY_LEN(n_blocks). + * @param[in] n_blocks The number of blocks in the buffer. + * @param[in] block_size The size of each block. + * + * @retval NRF_SUCCESS Successfully initialized buffer instance. + * @retval NRF_ERROR_INVALID_PARAM A parameter was 0 or NULL or a size was too small. + */ +uint32_t pm_buffer_init(pm_buffer_t *p_buffer, uint8_t *p_buffer_memory, + uint32_t buffer_memory_size, atomic_t *p_mutex_memory, + uint32_t n_blocks, uint32_t block_size); + +/** + * @brief Function for acquiring a buffer block in a buffer. + * + * @param[in] p_buffer The buffer instance acquire from. + * @param[in] n_blocks The number of contiguous blocks to acquire. + * + * @return The id of the acquired block, if successful. + * @retval PM_BUFFER_INVALID_ID If unsuccessful. + */ +uint8_t pm_buffer_block_acquire(pm_buffer_t *p_buffer, uint32_t n_blocks); + +/** + * @brief Function for getting a pointer to a specific buffer block. + * + * @param[in] p_buffer The buffer instance get from. + * @param[in] id The id of the buffer to get the pointer for. + * + * @return A pointer to the buffer for the specified id, if the id is valid. + * @retval NULL If the id is invalid. + */ +uint8_t *pm_buffer_ptr_get(pm_buffer_t *p_buffer, uint8_t id); + +/** + * @brief Function for releasing a buffer block. + * + * @param[in] p_buffer The buffer instance containing the block to release. + * @param[in] id The id of the block to release. + */ +void pm_buffer_release(pm_buffer_t *p_buffer, uint8_t id); + +#ifdef __cplusplus +} +#endif + +#endif /* BUFFER_H__ */ + +/** @} */ diff --git a/lib/peer_manager/include/modules/security_dispatcher.h b/lib/peer_manager/include/modules/security_dispatcher.h new file mode 100644 index 0000000000..b13055ca3b --- /dev/null +++ b/lib/peer_manager/include/modules/security_dispatcher.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2015-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @defgroup security_dispatcher Security Dispatcher + * @ingroup peer_manager + * @{ + * @brief An internal module of @ref peer_manager. A module for streamlining pairing, bonding, and + * encryption, including flash storage of shared data. + * + */ + +#ifndef SECURITY_DISPATCHER_H__ +#define SECURITY_DISPATCHER_H__ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Function for initializing the Security Dispatcher module. + * + * @retval NRF_SUCCESS Initialization was successful. + * @retval NRF_ERROR_INTERNAL An unexpected fatal error occurred. + */ +uint32_t smd_init(void); + +/** + * @brief Function for dispatching SoftDevice events to the Security Dispatcher module. + * + * @param[in] ble_evt The SoftDevice event. + */ +void smd_ble_evt_handler(ble_evt_t const *ble_evt); + +/** + * @brief Function for providing security configuration for a link. + * + * @details This function is optional, and must be called in reply to a @ref + * PM_EVT_CONN_SEC_CONFIG_REQ event, before the Peer Manager event handler returns. If it + * is not called in time, a default configuration is used. See @ref pm_conn_sec_config_t + * for the value of the default. + * + * @param[in] conn_handle The connection to set the configuration for. + * @param[in] p_conn_sec_config The configuration. + */ +void smd_conn_sec_config_reply(uint16_t conn_handle, pm_conn_sec_config_t *p_conn_sec_config); + +/** + * @brief Function for providing pairing and bonding parameters to use for the current pairing + * procedure on a connection. + * + * @note If this function returns an @ref NRF_ERROR_NULL, @ref NRF_ERROR_INVALID_PARAM, @ref + * BLE_ERROR_INVALID_CONN_HANDLE, or @ref NRF_ERROR_RESOURCES, this function can be called + * again after corrective action. + * + * @note To reject a request, call this function with NULL p_sec_params. + * + * @param[in] conn_handle The connection handle of the connection the pairing is happening on. + * @param[in] p_sec_params The security parameters to use for this link. + * @param[in] p_public_key A pointer to the public key to use if using LESC, or NULL. + * + * @retval NRF_SUCCESS Success. + * @retval NRF_ERROR_INVALID_STATE No parameters have been requested on that conn_handle, or + * the link is disconnecting. + * @retval NRF_ERROR_INVALID_PARAM Invalid combination of parameters (not including + * conn_handle). + * @retval NRF_ERROR_TIMEOUT There has been an SMP timeout, so no more SMP operations + * can be performed on this link. + * @retval BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle. + * @retval NRF_ERROR_BUSY No write buffer. Reattempt later. + * @retval NRF_ERROR_INTERNAL A fatal error occurred. + */ +uint32_t smd_params_reply(uint16_t conn_handle, ble_gap_sec_params_t *p_sec_params, + ble_gap_lesc_p256_pk_t *p_public_key); + +/** + * @brief Function for initiating security on the link, with the specified parameters. + * + * @note If the connection is a peripheral connection, this will send a security request to the + * master, but the master is not obligated to initiate pairing or encryption in response. + * @note If the connection is a central connection and a key is available, the parameters will be + * used to determine whether to re-pair or to encrypt using the existing key. If no key is + * available, pairing will be started. + * + * @param[in] conn_handle Handle of the connection to initiate pairing on. + * @param[in] p_sec_params The security parameters to use for this link. As a central, this can + * be NULL to reject a slave security request. + * @param[in] force_repairing Whether to force a pairing procedure to happen regardless of whether + * an encryption key already exists. This argument is only relevant for + * the central role. Recommended value: false + * + * @retval NRF_SUCCESS Success. + * @retval NRF_ERROR_NULL p_sec_params was NULL (peripheral only). + * @retval NRF_ERROR_INVALID_STATE A security procedure is already in progress on the link, + * or the link is disconnecting. + * @retval NRF_ERROR_INVALID_PARAM Invalid combination of parameters (not including + * conn_handle). + * @retval NRF_ERROR_INVALID_DATA Peer is bonded, but no LTK was found, and repairing was + * not requested. + * @retval NRF_ERROR_BUSY Unable to initiate procedure at this time. + * @retval NRF_ERROR_TIMEOUT There has been an SMP timeout, so no more SMP operations + * can be performed on this link. + * @retval BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle. + * @retval NRF_ERROR_INTERNAL No more available peer IDs. + */ +uint32_t smd_link_secure(uint16_t conn_handle, ble_gap_sec_params_t *p_sec_params, + bool force_repairing); + +#ifdef __cplusplus +} +#endif + +#endif /* SECURITY_DISPATCHER_H__ */ + +/** @} */ diff --git a/lib/peer_manager/include/modules/security_manager.h b/lib/peer_manager/include/modules/security_manager.h new file mode 100644 index 0000000000..9cc41c1891 --- /dev/null +++ b/lib/peer_manager/include/modules/security_manager.h @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2015-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @defgroup security_manager Security Manager + * @ingroup peer_manager + * @{ + * @brief An internal module of @ref peer_manager. A module for streamlining pairing, bonding, and + * encryption, including flash storage of shared data. + */ + +#ifndef SECURITY_MANAGER_H__ +#define SECURITY_MANAGER_H__ + +#include +#include +#include +#include +#include "security_dispatcher.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Function for initializing the Security Manager module. + * + * @retval NRF_SUCCESS If initialization was successful. + * @retval NRF_ERROR_INTERNAL If an unexpected error occurred. + */ +uint32_t sm_init(void); + +/** + * @brief Function for dispatching SoftDevice events to the Security Manager module. + * + * @param[in] ble_evt The SoftDevice event. + */ +void sm_ble_evt_handler(ble_evt_t const *ble_evt); + +/** + * @brief Function for providing pairing and bonding parameters to use for pairing procedures. + * + * @details Until this is called, all bonding procedures initiated by the peer will be rejected. + * This function can be called multiple times, even with NULL p_sec_params, in which case + * it will go back to rejecting all procedures. + * + * @param[in] p_sec_params The security parameters to use for this link. Can be NULL to reject + * all pairing procedures. + * + * @retval NRF_SUCCESS Success. + * @retval NRF_ERROR_INVALID_PARAM Invalid combination of parameters. + */ +uint32_t sm_sec_params_set(ble_gap_sec_params_t *p_sec_params); + +/** + * @brief Function for providing security configuration for a link. + * + * @details This function is optional, and must be called in reply to a @ref + * PM_EVT_CONN_SEC_CONFIG_REQ event, before the Peer Manager event handler returns. If it + * is not called in time, a default configuration is used. See @ref pm_conn_sec_config_t + * for the value of the default. + * + * @param[in] conn_handle The connection to set the configuration for. + * @param[in] p_conn_sec_config The configuration. + */ +void sm_conn_sec_config_reply(uint16_t conn_handle, pm_conn_sec_config_t *p_conn_sec_config); + +/** + * @brief Function for providing security parameters for a link. + * + * @details This function is optional, and must be called in reply to a @ref + * PM_EVT_CONN_SEC_PARAMS_REQ event, before the Security Manager event handler returns. If + * it is not called in time, the parameters given in @ref sm_sec_params_set are used. See + * @ref pm_conn_sec_config_t for the value of the default. + * + * @param[in] conn_handle The connection to set the parameters for. + * @param[in] p_sec_params The parameters. If NULL, the security procedure is rejected. + * @param[in] p_context The context found in the request event that this function replies to. + * + * @retval NRF_SUCCESS Successful reply. + * @retval NRF_ERROR_NULL p_context was null. + * @retval NRF_ERROR_INVALID_PARAM Value of p_sec_params was invalid. + * @retval NRF_ERROR_INVALID_STATE This module is not initialized. + */ +uint32_t sm_sec_params_reply(uint16_t conn_handle, ble_gap_sec_params_t *p_sec_params, + void const *p_context); + +/** + * @brief Experimental function for specifying the public key to use for LESC operations. + * + * @details This function can be called multiple times. The specified public key will be used for + * all subsequent LESC (LE Secure Connections) operations until the next time this function + * is called. + * + * @note The key must continue to reside in application memory as it is not copied by Peer Manager. + * + * @param[in] p_public_key The public key to use for all subsequent LESC operations. + * + * @retval NRF_SUCCESS Pairing initiated successfully. + * @retval NRF_ERROR_INVALID_STATE This module is not initialized. + */ +uint32_t sm_lesc_public_key_set(ble_gap_lesc_p256_pk_t *p_public_key); + +/** + * @brief Function for getting the security status of a connection. + * + * @param[in] conn_handle Connection handle of the link as provided by the SoftDevice. + * @param[out] p_conn_sec_status Security status of the link. + * + * @retval NRF_SUCCESS If pairing was initiated successfully. + * @retval BLE_ERROR_INVALID_CONN_HANDLE If the connection handle is invalid. + * @retval NRF_ERROR_NULL If @p p_conn_sec_status was NULL. + */ +uint32_t sm_conn_sec_status_get(uint16_t conn_handle, pm_conn_sec_status_t *p_conn_sec_status); + +/** + * @brief Function for comparing the security status of a connection against a baseline. + * + * @param[in] conn_handle Connection handle of the link as provided by the SoftDevice. + * @param[out] p_sec_status_req Target baseline security status to compare against. + * + * @retval true If the security status of the connection matches or exceeds the baseline on all + * points. + * @retval false If the security status of the connection does not fulfill the baseline, or could + * not be retrieved. + */ +bool sm_sec_is_sufficient(uint16_t conn_handle, pm_conn_sec_status_t *p_sec_status_req); + +/** + * @brief Function for initiating security on the link, with the specified parameters. + * + * @note If the connection is a peripheral connection, this will send a security request to the + * master, but the master is not obligated to initiate pairing or encryption in response. + * @note If the connection is a central connection and a key is available, the parameters will be + * used to determine whether to re-pair or to encrypt using the existing key. If no key is + * available, pairing will be started. + * + * @param[in] conn_handle Handle of the connection to initiate pairing on. + * @param[in] force_repairing Whether to force a pairing procedure to happen regardless of whether + * an encryption key already exists. This argument is only relevant for + * the central role. Recommended value: false + * + * @retval NRF_SUCCESS Success. + * @retval NRF_ERROR_TIMEOUT There has been an SMP timeout, so no more SMP operations + * can be performed on this link. + * @retval BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle. + * @retval NRF_ERROR_INVALID_DATA Peer is bonded, but no LTK was found, and repairing was + * not requested. + * @retval NRF_ERROR_NOT_FOUND Security parameters have not been set. + * @retval NRF_ERROR_INVALID_STATE A security procedure is already in progress on the link, + * or the link is disconnecting. + * @retval NRF_ERROR_INTERNAL An unexpected error occurred. + */ +uint32_t sm_link_secure(uint16_t conn_handle, bool force_repairing); + +#ifdef __cplusplus +} +#endif + +#endif /* SECURITY_MANAGER_H__ */ + +/** @} */ diff --git a/lib/peer_manager/include/nordic_common.h b/lib/peer_manager/include/nordic_common.h new file mode 100644 index 0000000000..a4db927de0 --- /dev/null +++ b/lib/peer_manager/include/nordic_common.h @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2008-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** @file + * @brief Common defines and macros for firmware developed by Nordic Semiconductor. + */ + +#ifndef NORDIC_COMMON_H__ +#define NORDIC_COMMON_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Check if selected module is enabled + * + * This is save function for driver enable checking. + * Correct from Lint point of view (not using default of undefined value). + * + * Usage: + * @code + #if NRF_MODULE_ENABLED(UART) + ... + #endif + * @endcode + * + * @param module The module name. + * + * @retval 1 The macro _ENABLE is defined and is non-zero. + * @retval 0 The macro _ENABLE is not defined or it equals zero. + * + * @note + * This macro intentionally does not implement second expansion level. + * The name of the module to be checked has to be given directly as a parameter. + * And given parameter would be connected with @c _ENABLED postfix directly + * without evaluating its value. + */ +#ifdef NRF_MODULE_ENABLE_ALL +#warning "Do not use NRF_MODULE_ENABLE_ALL for real builds." +#define NRF_MODULE_ENABLED(module) 1 +#else +#define NRF_MODULE_ENABLED(module) ((defined(module##_ENABLED) && (module##_ENABLED)) ? 1 : 0) +#endif +/** The upper 8 bits of a 32 bit value */ +#define MSB_32(a) (((a)&0xFF000000) >> 24) +/** The lower 8 bits (of a 32 bit value) */ +#define LSB_32(a) ((a)&0x000000FF) + +/** The upper 8 bits of a 16 bit value */ +#define MSB_16(a) (((a)&0xFF00) >> 8) +/** The lower 8 bits (of a 16 bit value) */ +#define LSB_16(a) ((a)&0x00FF) + +/** Leaves the minimum of the two 32-bit arguments */ +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +/** Leaves the maximum of the two 32-bit arguments */ +#define MAX(a, b) ((a) < (b) ? (b) : (a)) + +/**@brief Concatenates two parameters. + * + * It realizes two level expansion to make it sure that all the parameters + * are actually expanded before gluing them together. + * + * @param p1 First parameter to concatenating + * @param p2 Second parameter to concatenating + * + * @return Two parameters glued together. + * They have to create correct C mnemonic in other case + * preprocessor error would be generated. + * + * @sa CONCAT_3 + */ +#define CONCAT_2(p1, p2) CONCAT_2_(p1, p2) +/** Auxiliary macro used by @ref CONCAT_2 */ +#define CONCAT_2_(p1, p2) p1##p2 + +/**@brief Concatenates three parameters. + * + * It realizes two level expansion to make it sure that all the parameters + * are actually expanded before gluing them together. + * + * @param p1 First parameter to concatenating + * @param p2 Second parameter to concatenating + * @param p3 Third parameter to concatenating + * + * @return Three parameters glued together. + * They have to create correct C mnemonic in other case + * preprocessor error would be generated. + * + * @sa CONCAT_2 + */ +#define CONCAT_3(p1, p2, p3) CONCAT_3_(p1, p2, p3) +/** Auxiliary macro used by @ref CONCAT_3 */ +#define CONCAT_3_(p1, p2, p3) p1##p2##p3 + +#define STRINGIFY_(val) #val +/** Converts a macro argument into a character constant. + */ +#define STRINGIFY(val) STRINGIFY_(val) + +/** Counts number of elements inside the array + */ +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +/**@brief Set a bit in the uint32 word. + * + * @param[in] W Word whose bit is being set. + * @param[in] B Bit number in the word to be set. + */ +#define SET_BIT(W, B) ((W) |= (uint32_t)(1U << (B))) + +/**@brief Clears a bit in the uint32 word. + * + * @param[in] W Word whose bit is to be cleared. + * @param[in] B Bit number in the word to be cleared. + */ +#define CLR_BIT(W, B) ((W) &= (~(uint32_t)(1U << (B)))) + +/**@brief Checks if a bit is set. + * + * @param[in] W Word whose bit is to be checked. + * @param[in] B Bit number in the word to be checked. + * + * @retval 1 if bit is set. + * @retval 0 if bit is not set. + */ +#define IS_SET(W, B) (((W) >> (B)) & 1) + +#define BIT_0 0x01 /**< The value of bit 0 */ +#define BIT_1 0x02 /**< The value of bit 1 */ +#define BIT_2 0x04 /**< The value of bit 2 */ +#define BIT_3 0x08 /**< The value of bit 3 */ +#define BIT_4 0x10 /**< The value of bit 4 */ +#define BIT_5 0x20 /**< The value of bit 5 */ +#define BIT_6 0x40 /**< The value of bit 6 */ +#define BIT_7 0x80 /**< The value of bit 7 */ +#define BIT_8 0x0100 /**< The value of bit 8 */ +#define BIT_9 0x0200 /**< The value of bit 9 */ +#define BIT_10 0x0400 /**< The value of bit 10 */ +#define BIT_11 0x0800 /**< The value of bit 11 */ +#define BIT_12 0x1000 /**< The value of bit 12 */ +#define BIT_13 0x2000 /**< The value of bit 13 */ +#define BIT_14 0x4000 /**< The value of bit 14 */ +#define BIT_15 0x8000 /**< The value of bit 15 */ +#define BIT_16 0x00010000 /**< The value of bit 16 */ +#define BIT_17 0x00020000 /**< The value of bit 17 */ +#define BIT_18 0x00040000 /**< The value of bit 18 */ +#define BIT_19 0x00080000 /**< The value of bit 19 */ +#define BIT_20 0x00100000 /**< The value of bit 20 */ +#define BIT_21 0x00200000 /**< The value of bit 21 */ +#define BIT_22 0x00400000 /**< The value of bit 22 */ +#define BIT_23 0x00800000 /**< The value of bit 23 */ +#define BIT_24 0x01000000 /**< The value of bit 24 */ +#define BIT_25 0x02000000 /**< The value of bit 25 */ +#define BIT_26 0x04000000 /**< The value of bit 26 */ +#define BIT_27 0x08000000 /**< The value of bit 27 */ +#define BIT_28 0x10000000 /**< The value of bit 28 */ +#define BIT_29 0x20000000 /**< The value of bit 29 */ +#define BIT_30 0x40000000 /**< The value of bit 30 */ +#define BIT_31 0x80000000 /**< The value of bit 31 */ + +#define UNUSED_VARIABLE(X) ((void)(X)) +#define UNUSED_PARAMETER(X) UNUSED_VARIABLE(X) +#define UNUSED_RETURN_VALUE(X) UNUSED_VARIABLE(X) + +/** + * @brief Macro for calculating the number of words that are needed to hold a number of bytes. + * + * @details Adds 3 and divides by 4. + * + * @param[in] n_bytes The number of bytes. + * + * @return The number of words that @p n_bytes take up (rounded up). + */ +#define BYTES_TO_WORDS(n_bytes) (((n_bytes) + 3) >> 2) + +#ifdef __cplusplus +} +#endif + +#endif /* NORDIC_COMMON_H__ */ diff --git a/lib/peer_manager/include/nrf_strerror.h b/lib/peer_manager/include/nrf_strerror.h new file mode 100644 index 0000000000..de6ecaf797 --- /dev/null +++ b/lib/peer_manager/include/nrf_strerror.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2017-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @defgroup nrf_strerror Error code to string converter + * @ingroup app_common + * + * @brief Module for converting error code into a printable string. + * @{ + */ +#ifndef NRF_STRERROR_H__ +#define NRF_STRERROR_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Function for getting a printable error string. + * + * @param code Error code to convert. + * + * @note This function cannot fail. + * For the function that may fail with error translation, see @ref nrf_strerror_find. + * + * @return Pointer to the printable string. + * If the string is not found, + * it returns a simple string that says that the error is unknown. + */ +char const *nrf_strerror_get(uint32_t code); + +/** + * @brief Function for finding a printable error string. + * + * This function gets the error string in the same way as @ref nrf_strerror_get, + * but if the string is not found, it returns NULL. + * + * @param code Error code to convert. + * @return Pointer to the printable string. + * If the string is not found, NULL is returned. + */ +char const *nrf_strerror_find(uint32_t code); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* NRF_STRERROR_H__ */ diff --git a/lib/peer_manager/include/sdk_macros.h b/lib/peer_manager/include/sdk_macros.h new file mode 100644 index 0000000000..5631d3e6fc --- /dev/null +++ b/lib/peer_manager/include/sdk_macros.h @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2015-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @file + * + * @defgroup sdk_common_macros SDK Common Header + * @ingroup app_common + * @brief Macros for parameter checking and similar tasks + * @{ + */ + +#ifndef SDK_MACROS_H__ +#define SDK_MACROS_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Macro for parameter checking. + * + * If @p _cond evaluates to true, does nothing. Otherwise, + * if @p _module ## _PARAM_CHECK_DISABLED is @e not set (default), prints an error message + * if @p _printfn is provided, and returns from the calling function context with code @p _err. + * If @p _module ## _PARAM_CHECK_DISABLED is set, behaves like the ASSERT macro. + * + * Parameter checking implemented using this macro can be optionally turned off for release code. + * Only disable runtime parameter checks if size if a major concern. + * + * @param _module The module name. + * @param _cond The condition to be evaluated. + * @param _err The error to be returned. + * @param _printfn A printf-compatible function used to log the error. + * Leave empty if no logging is needed. + * + * @hideinitializer + */ +#define NRF_PARAM_CHECK(_module, _cond, _err, _printfn) \ + do { \ + if ((_cond)) { \ + /* Do nothing. */ \ + } else if (!(_module##_PARAM_CHECK_DISABLED)) { \ + _printfn("%s check failed in %s() with value 0x%x.", #_cond, __func__, \ + _err); \ + return _err; \ + } else { \ + __ASSERT((_cond)); \ + } \ + } while (0) + +/** + * @brief Macro for verifying statement to be true. It will cause the exterior function to return + * err_code if the statement is not true. + * + * @param[in] statement Statement to test. + * @param[in] err_code Error value to return if test was invalid. + * + * @retval nothing, but will cause the exterior function to return @p err_code if @p statement + * is false. + */ +#define VERIFY_TRUE(statement, err_code) \ + do { \ + if (!(statement)) { \ + return err_code; \ + } \ + } while (0) + +/** + * @brief Macro for verifying statement to be true. It will cause the exterior function to return + * if the statement is not true. + * + * @param[in] statement Statement to test. + */ +#define VERIFY_TRUE_VOID(statement) VERIFY_TRUE((statement),) + +/** + * @brief Macro for verifying statement to be false. It will cause the exterior function to return + * err_code if the statement is not false. + * + * @param[in] statement Statement to test. + * @param[in] err_code Error value to return if test was invalid. + * + * @retval nothing, but will cause the exterior function to return @p err_code if @p statement + * is true. + */ +#define VERIFY_FALSE(statement, err_code) \ + do { \ + if ((statement)) { \ + return err_code; \ + } \ + } while (0) + +/** + * @brief Macro for verifying statement to be false. It will cause the exterior function to return + * if the statement is not false. + * + * @param[in] statement Statement to test. + */ +#define VERIFY_FALSE_VOID(statement) VERIFY_FALSE((statement),) + +/** + * @brief Macro for verifying that a function returned NRF_SUCCESS. It will cause the exterior + * function to return error code of statement if it is not @ref NRF_SUCCESS. + * + * @param[in] statement Statement to check against NRF_SUCCESS. + */ +#define VERIFY_SUCCESS(statement) \ + do { \ + uint32_t _err_code = (uint32_t)(statement); \ + if (_err_code != NRF_SUCCESS) { \ + return _err_code; \ + } \ + } while (0) + +/** + * @brief Macro for verifying that a function returned NRF_SUCCESS. It will cause the exterior + * function to return if the err_code is not @ref NRF_SUCCESS. + * + * @param[in] err_code The error code to check. + */ +#define VERIFY_SUCCESS_VOID(err_code) VERIFY_TRUE_VOID((err_code) == NRF_SUCCESS) + +/** + * @brief Macro for verifying that the module is initialized. It will cause the exterior function to + * return @ref NRF_ERROR_INVALID_STATE if not. + * + * @note MODULE_INITIALIZED must be defined in each module using this macro. MODULE_INITIALIZED + * should be true if the module is initialized, false if not. + */ +#define VERIFY_MODULE_INITIALIZED() VERIFY_TRUE((MODULE_INITIALIZED), NRF_ERROR_INVALID_STATE) + +/** + * @brief Macro for verifying that the module is initialized. It will cause the exterior function to + * return if not. + * + * @note MODULE_INITIALIZED must be defined in each module using this macro. MODULE_INITIALIZED + * should be true if the module is initialized, false if not. + */ +#define VERIFY_MODULE_INITIALIZED_VOID() VERIFY_TRUE_VOID((MODULE_INITIALIZED)) + +/** + * @brief Macro for verifying that the module is initialized. It will cause the exterior function to + * return false if not. + * + * @note MODULE_INITIALIZED must be defined in each module using this macro. MODULE_INITIALIZED + * should be true if the module is initialized, false if not. + */ +#define VERIFY_MODULE_INITIALIZED_BOOL() VERIFY_TRUE((MODULE_INITIALIZED), false) + +/** + * @brief Macro for verifying that the module is initialized. It will cause the exterior function to + * return if not. + * + * @param[in] param The variable to check if is NULL. + */ +#define VERIFY_PARAM_NOT_NULL(param) VERIFY_FALSE(((param) == NULL), NRF_ERROR_NULL) + +/** + * @brief Macro for verifying that the module is initialized. It will cause the exterior function to + * return if not. + * + * @param[in] param The variable to check if is NULL. + */ +#define VERIFY_PARAM_NOT_NULL_VOID(param) VERIFY_FALSE_VOID(((param) == NULL)) + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* SDK_MACROS_H__ */ diff --git a/lib/peer_manager/modules/CMakeLists.txt b/lib/peer_manager/modules/CMakeLists.txt new file mode 100644 index 0000000000..2b437230dd --- /dev/null +++ b/lib/peer_manager/modules/CMakeLists.txt @@ -0,0 +1,18 @@ +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +zephyr_library_sources(auth_status_tracker.c) +zephyr_library_sources(gatt_cache_manager.c) +zephyr_library_sources(gatts_cache_manager.c) +zephyr_library_sources(id_manager.c) +zephyr_library_sources_ifdef(CONFIG_PM_LESC_ENABLED nrf_ble_lesc.c) +zephyr_library_sources(peer_data_storage.c) +zephyr_library_sources(peer_database.c) +zephyr_library_sources(peer_id.c) +zephyr_library_sources(peer_manager_handler.c) +zephyr_library_sources(pm_buffer.c) +zephyr_library_sources(security_dispatcher.c) +zephyr_library_sources(security_manager.c) diff --git a/lib/peer_manager/modules/auth_status_tracker.c b/lib/peer_manager/modules/auth_status_tracker.c new file mode 100644 index 0000000000..bd57e64d01 --- /dev/null +++ b/lib/peer_manager/modules/auth_status_tracker.c @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2018-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(peer_manager, CONFIG_PEER_MANAGER_LOG_LEVEL); + +/* Assume that waiting interval doubles with each failed authentication. */ +#define PAIR_REWARD_TICKS BM_TIMER_MS_TO_TICKS(CONFIG_PM_RA_PROTECTION_REWARD_PERIOD) +#define PENALITY_LVL_TO_PENALITY_MS(_lvl) (CONFIG_PM_RA_PROTECTION_MIN_WAIT_INTERVAL * (1 << _lvl)) +#define PENALITY_LVL_TO_PENALITY_TICKS(_lvl) BM_TIMER_MS_TO_TICKS(PENALITY_LVL_TO_PENALITY_MS(_lvl)) +#define PENALITY_LVL_NEXT_SET(_lvl) \ + _lvl = (PENALITY_LVL_TO_PENALITY_MS(_lvl) >= (CONFIG_PM_RA_PROTECTION_MAX_WAIT_INTERVAL)) \ + ? (_lvl) \ + : (_lvl + 1) + +/** @brief Tracked peer state. */ +typedef struct { + /** @brief BLE address, used to identify peer. */ + ble_gap_addr_t peer_addr; + /** + * @brief Accumulated reward ticks, used to decrease penality level after achieving certain + * threshold. + */ + uint32_t reward_ticks; + /** + * @brief Accumulated penality ticks, used to determine remaining time + * in which pairing attempts should be rejected. + */ + uint32_t penality_ticks; + /** + * @brief Accumulated penality level, used to determine waiting interval + * after failed authorization attempt. + */ + uint8_t penality_lvl; + /** + * @brief Flag indicating that the waiting interval for this peer has not + * passed yet. + */ + bool is_active; + /** @brief Flag indicating that this entry is valid in the peer blacklist. */ + bool is_valid; +} blacklisted_peer_t; + +static struct bm_timer m_pairing_attempt_timer; +static blacklisted_peer_t m_blacklisted_peers[CONFIG_PM_RA_PROTECTION_TRACKED_PEERS_NUM]; +static uint64_t m_ticks_cnt; + +/** + * @brief Function for updating the state of blacklisted peers after timer has been stopped or + * timed out. + * + * @param[in] ticks_passed The number of ticks since the timer has started. + */ +static uint32_t blacklisted_peers_state_update(uint32_t ticks_passed) +{ + uint32_t minimal_ticks = UINT32_MAX; + + for (uint32_t id = 0; id < ARRAY_SIZE(m_blacklisted_peers); id++) { + blacklisted_peer_t *p_bl_peer = &m_blacklisted_peers[id]; + + if (p_bl_peer->is_valid) { + if (p_bl_peer->is_active) { + if (p_bl_peer->penality_ticks > ticks_passed) { + p_bl_peer->penality_ticks -= ticks_passed; + minimal_ticks = + MIN(minimal_ticks, p_bl_peer->penality_ticks); + } else { + p_bl_peer->is_active = false; + + if (p_bl_peer->penality_lvl == 0) { + p_bl_peer->is_valid = false; + LOG_DBG("Peer has been removed from the " + "blacklist, its address:"); + LOG_HEXDUMP_DBG(p_bl_peer->peer_addr.addr, + sizeof(p_bl_peer->peer_addr.addr), ""); + } else { + minimal_ticks = + MIN(minimal_ticks, PAIR_REWARD_TICKS); + } + + LOG_DBG("Pairing waiting interval has expired for:"); + LOG_HEXDUMP_DBG(p_bl_peer->peer_addr.addr, + sizeof(p_bl_peer->peer_addr.addr), ""); + } + } else { + if (p_bl_peer->penality_lvl == 0) { + p_bl_peer->is_valid = false; + LOG_DBG("Peer has been removed from the blacklist, " + "its address:"); + LOG_HEXDUMP_DBG(p_bl_peer->peer_addr.addr, + sizeof(p_bl_peer->peer_addr.addr), ""); + } else { + p_bl_peer->reward_ticks += ticks_passed; + if (p_bl_peer->reward_ticks >= PAIR_REWARD_TICKS) { + p_bl_peer->penality_lvl--; + p_bl_peer->reward_ticks -= PAIR_REWARD_TICKS; + LOG_DBG("Peer penality level has decreased " + "to %d for device:", + p_bl_peer->penality_lvl); + LOG_HEXDUMP_DBG(p_bl_peer->peer_addr.addr, + sizeof(p_bl_peer->peer_addr.addr), ""); + } + + minimal_ticks = + MIN(minimal_ticks, + (PAIR_REWARD_TICKS - p_bl_peer->reward_ticks)); + } + } + } + } + + return minimal_ticks; +} + +/** + * @brief Function for handling state transition of blacklisted peers. + * + * @param[in] context Context containing the number of ticks since the timer has started. + */ +static void blacklisted_peers_state_transition_handle(void *context) +{ + int err; + uint32_t minimal_ticks; + uint32_t ticks_passed = (uint32_t)context; + + minimal_ticks = blacklisted_peers_state_update(ticks_passed); + m_ticks_cnt = k_uptime_ticks(); + + if (minimal_ticks != UINT32_MAX) { + err = bm_timer_start(&m_pairing_attempt_timer, minimal_ticks, + (void *)minimal_ticks); + if (err) { + LOG_WRN("bm_timer_start() returned %d", err); + } + LOG_DBG("Restarting the timer"); + } +} + +uint32_t ast_init(void) +{ + int err_code = bm_timer_init(&m_pairing_attempt_timer, BM_TIMER_MODE_SINGLE_SHOT, + blacklisted_peers_state_transition_handle); + if (err_code) { + return NRF_ERROR_INTERNAL; + } + + return NRF_SUCCESS; +} + +void ast_auth_error_notify(uint16_t conn_handle) +{ + int err; + uint32_t err_code; + ble_gap_addr_t peer_addr; + uint32_t new_timeout; + uint32_t free_id = ARRAY_SIZE(m_blacklisted_peers); + bool new_bl_entry = true; + + /* Get the peer address associated with connection handle. */ + err_code = im_ble_addr_get(conn_handle, &peer_addr); + if (err_code != NRF_SUCCESS) { + LOG_WRN("im_ble_addr_get() returned %s. conn_handle: %d. " + "Link was likely disconnected.", + nrf_strerror_get(err_code), conn_handle); + return; + } + + /* Stop the timer and update the state of all blacklisted peers. */ + err = bm_timer_stop(&m_pairing_attempt_timer); + if (err) { + LOG_WRN("bm_timer_stop() returned %d", err); + return; + } + + new_timeout = blacklisted_peers_state_update((uint32_t)(k_uptime_ticks() - m_ticks_cnt)); + m_ticks_cnt = k_uptime_ticks(); + + /* Check if authorization has failed for already blacklisted peer. */ + for (uint32_t id = 0; id < ARRAY_SIZE(m_blacklisted_peers); id++) { + blacklisted_peer_t *p_bl_peer = &m_blacklisted_peers[id]; + + if (p_bl_peer->is_valid) { + if (memcmp(peer_addr.addr, p_bl_peer->peer_addr.addr, BLE_GAP_ADDR_LEN) == + 0) { + uint8_t lvl = p_bl_peer->penality_lvl; + + PENALITY_LVL_NEXT_SET(lvl); + p_bl_peer->penality_lvl = lvl; + p_bl_peer->reward_ticks = 0; + p_bl_peer->penality_ticks = PENALITY_LVL_TO_PENALITY_TICKS(lvl); + + new_timeout = MIN(new_timeout, p_bl_peer->penality_ticks); + + p_bl_peer->is_active = true; + new_bl_entry = false; + + LOG_DBG("Pairing waiting interval has been renewed. " + "Penality level: %d for device:", + lvl); + LOG_HEXDUMP_DBG(p_bl_peer->peer_addr.addr, + sizeof(p_bl_peer->peer_addr.addr), ""); + } + } else { + free_id = id; + } + } + + /* Add a new peer to the blacklist. */ + if (new_bl_entry) { + if (free_id < ARRAY_SIZE(m_blacklisted_peers)) { + blacklisted_peer_t *p_bl_peer = &m_blacklisted_peers[free_id]; + + memcpy(&p_bl_peer->peer_addr, &peer_addr, sizeof(peer_addr)); + + p_bl_peer->penality_lvl = 0; + p_bl_peer->reward_ticks = 0; + p_bl_peer->penality_ticks = + PENALITY_LVL_TO_PENALITY_TICKS(p_bl_peer->penality_lvl); + + new_timeout = MIN(new_timeout, p_bl_peer->penality_ticks); + + p_bl_peer->is_active = true; + p_bl_peer->is_valid = true; + LOG_DBG("New peer has been added to the blacklist:"); + LOG_HEXDUMP_DBG(p_bl_peer->peer_addr.addr, + sizeof(p_bl_peer->peer_addr.addr), ""); + } else { + LOG_WRN("No space to blacklist another peer ID"); + } + } + + /* Restart the timer. */ + if (new_timeout != UINT32_MAX) { + err = bm_timer_start(&m_pairing_attempt_timer, new_timeout, (void *)new_timeout); + if (err) { + LOG_WRN("bm_timer_start() returned %d", err); + } + } +} + +bool ast_peer_blacklisted(uint16_t conn_handle) +{ + uint32_t err_code; + ble_gap_addr_t peer_addr; + + err_code = im_ble_addr_get(conn_handle, &peer_addr); + if (err_code != NRF_SUCCESS) { + LOG_WRN("im_ble_addr_get() returned %s. conn_handle: %d. " + "Link was likely disconnected.", + nrf_strerror_get(err_code), conn_handle); + return true; + } + + for (uint32_t id = 0; id < ARRAY_SIZE(m_blacklisted_peers); id++) { + blacklisted_peer_t *p_bl_peer = &m_blacklisted_peers[id]; + + if (p_bl_peer->is_valid) { + if ((memcmp(peer_addr.addr, p_bl_peer->peer_addr.addr, BLE_GAP_ADDR_LEN) == + 0) && + (p_bl_peer->is_active)) { + return true; + } + } + } + + return false; +} diff --git a/lib/peer_manager/modules/gatt_cache_manager.c b/lib/peer_manager/modules/gatt_cache_manager.c new file mode 100644 index 0000000000..f63c9cd016 --- /dev/null +++ b/lib/peer_manager/modules/gatt_cache_manager.c @@ -0,0 +1,807 @@ +/* + * Copyright (c) 2015-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 + +LOG_MODULE_DECLARE(peer_manager, CONFIG_PEER_MANAGER_LOG_LEVEL); + +#define NRF_MTX_LOCKED 1 +#define NRF_MTX_UNLOCKED 0 + +typedef atomic_t nrf_mtx_t; + +/** + * @brief Initialize mutex. + * + * This function _must_ be called before nrf_mtx_trylock() and nrf_mtx_unlock() functions. + * + * @param[in, out] p_mtx The mutex to be initialized. + */ +__STATIC_INLINE void nrf_mtx_init(nrf_mtx_t *p_mtx) +{ + __ASSERT(p_mtx != NULL, ""); + + atomic_set(p_mtx, NRF_MTX_UNLOCKED); +} + +/** + * @brief Try to lock a mutex. + * + * If the mutex is already held by another context, this function will return immediately. + * + * @param[in, out] p_mtx The mutex to be locked. + * @return true if lock was acquired, false if not + */ +__STATIC_INLINE bool nrf_mtx_trylock(nrf_mtx_t *p_mtx) +{ + __ASSERT(p_mtx != NULL, ""); + + return atomic_cas(p_mtx, NRF_MTX_UNLOCKED, NRF_MTX_LOCKED); +} + +/** + * @brief Unlock a mutex. + * + * This function _must_ only be called when holding the lock. Unlocking a mutex which you do not + * hold will give undefined behavior. + * + * @note Unlock must happen from the same context as the one used to lock the mutex. + * + * @param[in, out] p_mtx The mutex to be unlocked. + */ +__STATIC_INLINE void nrf_mtx_unlock(nrf_mtx_t *p_mtx) +{ + __ASSERT(p_mtx != NULL, ""); + __ASSERT(*p_mtx == NRF_MTX_LOCKED, ""); + + atomic_set(p_mtx, NRF_MTX_UNLOCKED); +} + +/* The number of registered event handlers. */ +#define GCM_EVENT_HANDLERS_CNT ARRAY_SIZE(m_evt_handlers) + +/* GATT Cache Manager event handler in Peer Manager. */ +extern void pm_gcm_evt_handler(pm_evt_t *p_gcm_evt); + +/** + * @brief GATT Cache Manager events' handlers. + * The number of elements in this array is GCM_EVENT_HANDLERS_CNT. + */ +static pm_evt_handler_internal_t m_evt_handlers[] = {pm_gcm_evt_handler}; + +static bool m_module_initialized; +/** @brief Mutex indicating whether a local DB write operation is ongoing. */ +static nrf_mtx_t m_db_update_in_progress_mutex; +/** + * @brief Flag ID for flag collection to keep track of which connections need a local DB update + * procedure. + */ +static int m_flag_local_db_update_pending; +/** + * @brief Flag ID for flag collection to keep track of which connections need a local DB apply + * procedure. + */ +static int m_flag_local_db_apply_pending; +/** + * @brief Flag ID for flag collection to keep track of which connections need to be sent a service + * changed indication. + */ +static int m_flag_service_changed_pending; +/** + * @brief Flag ID for flag collection to keep track of which connections have been sent a service + * changed indication and are waiting for a handle value confirmation. + */ +static int m_flag_service_changed_sent; +/** + * @brief Flag ID for flag collection to keep track of which connections need to have their Central + * Address Resolution value stored. + */ +static int m_flag_car_update_pending; +/** + * @brief Flag ID for flag collection to keep track of which connections are pending Central + * Address Resolution handle reply. + */ +static int m_flag_car_handle_queried; +/** + * @brief Flag ID for flag collection to keep track of which connections are pending Central + * Address Resolution value reply. + */ +static int m_flag_car_value_queried; + +#ifdef CONFIG_PM_SERVICE_CHANGED_ENABLED +BUILD_ASSERT(IS_ENABLED(CONFIG_PM_SERVICE_CHANGED_ENABLED) || + !IS_ENABLED(CONFIG_NRF_SDH_BLE_SERVICE_CHANGED), + "CONFIG_PM_SERVICE_CHANGED_ENABLED should be enabled " + "if NRF_SDH_BLE_SERVICE_CHANGED is enabled."); +#else +#define CONFIG_PM_SERVICE_CHANGED_ENABLED 1 +#endif + +/** + * @brief Function for resetting the module variable(s) of the GSCM module. + * + * @param[out] The instance to reset. + */ +static void internal_state_reset(void) +{ + m_module_initialized = false; +} + +static void evt_send(pm_evt_t *p_gcm_evt) +{ + p_gcm_evt->peer_id = im_peer_id_get_by_conn_handle(p_gcm_evt->conn_handle); + + for (uint32_t i = 0; i < GCM_EVENT_HANDLERS_CNT; i++) { + m_evt_handlers[i](p_gcm_evt); + } +} + +/** + * @brief Function for checking a write event for whether a CCCD was written during the write + * operation. + * + * @param[in] p_write_evt The parameters of the write event. + * + * @return Whether the write was on a CCCD. + */ +static bool cccd_written(ble_gatts_evt_write_t const *p_write_evt) +{ + return ((p_write_evt->op == BLE_GATTS_OP_WRITE_REQ) && + (p_write_evt->uuid.type == BLE_UUID_TYPE_BLE) && + (p_write_evt->uuid.uuid == BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG)); +} + +/** + * @brief Function for sending an PM_EVT_ERROR_UNEXPECTED event. + * + * @param[in] conn_handle The connection handle the event pertains to. + * @param[in] err_code The unexpected error that occurred. + */ +static void send_unexpected_error(uint16_t conn_handle, uint32_t err_code) +{ + pm_evt_t error_evt = {.evt_id = PM_EVT_ERROR_UNEXPECTED, + .conn_handle = conn_handle, + .params = {.error_unexpected = {.error = err_code}}}; + + evt_send(&error_evt); +} + +/** + * @brief Function for performing the local DB update procedure in an event context, where no return + * code can be given. + * + * @details This function will do the procedure, and check the result, set a flag if needed, and + * send an event if needed. + * + * @param[in] conn_handle The connection to perform the procedure on. + */ +static void local_db_apply_in_evt(uint16_t conn_handle) +{ + bool set_procedure_as_pending = false; + uint32_t err_code; + pm_evt_t event = { + .conn_handle = conn_handle, + }; + + if (conn_handle == BLE_CONN_HANDLE_INVALID) { + return; + } + + err_code = gscm_local_db_cache_apply(conn_handle); + + switch (err_code) { + case NRF_SUCCESS: + event.evt_id = PM_EVT_LOCAL_DB_CACHE_APPLIED; + + evt_send(&event); + break; + + case NRF_ERROR_BUSY: + set_procedure_as_pending = true; + break; + + case NRF_ERROR_INVALID_DATA: + event.evt_id = PM_EVT_LOCAL_DB_CACHE_APPLY_FAILED; + + LOG_WRN("The local database has changed, so some subscriptions to notifications " + "and indications could not be restored for conn_handle %d", + conn_handle); + evt_send(&event); + break; + + case BLE_ERROR_INVALID_CONN_HANDLE: + /* Do nothing */ + break; + + default: + LOG_ERR("gscm_local_db_cache_apply() returned %s which should not happen. " + "conn_handle: %d", + nrf_strerror_get(err_code), conn_handle); + send_unexpected_error(conn_handle, err_code); + break; + } + + ble_conn_state_user_flag_set(conn_handle, m_flag_local_db_apply_pending, + set_procedure_as_pending); +} + +/** + * @brief Function for asynchronously starting a DB update procedure. + * + * @note This procedure can only be started asynchronously. + * + * @param[in] conn_handle The connection to perform the procedure on. + * @param[in] update Whether to perform the procedure. + */ +static __INLINE void local_db_update(uint16_t conn_handle, bool update) +{ + ble_conn_state_user_flag_set(conn_handle, m_flag_local_db_update_pending, update); +} + +/** + * @brief Function for performing the local DB update procedure in an event context, where no return + * code can be given. + * + * @details This function will do the procedure, and check the result, set a flag if needed, and + * send an event if needed. + * + * @param[in] conn_handle The connection to perform the procedure on. + */ +static bool local_db_update_in_evt(uint16_t conn_handle) +{ + bool set_procedure_as_pending = false; + bool success = false; + uint32_t err_code = gscm_local_db_cache_update(conn_handle); + + switch (err_code) { + case NRF_SUCCESS: + success = true; + break; + + case NRF_ERROR_INVALID_DATA: + /* Fallthrough */ + case BLE_ERROR_INVALID_CONN_HANDLE: + /* Do nothing */ + break; + + case NRF_ERROR_BUSY: + set_procedure_as_pending = true; + break; + + case NRF_ERROR_RESOURCES: { + pm_evt_t event = { + .evt_id = PM_EVT_STORAGE_FULL, + .conn_handle = conn_handle, + }; + + LOG_WRN("Flash full. Could not store data for conn_handle: %d", conn_handle); + evt_send(&event); + break; + } + + default: + LOG_ERR("gscm_local_db_cache_update() returned %s for conn_handle: %d", + nrf_strerror_get(err_code), conn_handle); + send_unexpected_error(conn_handle, err_code); + break; + } + + local_db_update(conn_handle, set_procedure_as_pending); + + return success; +} + +#if CONFIG_PM_SERVICE_CHANGED_ENABLED + +/** + * @brief Function for getting the value of the CCCD for the service changed characteristic. + * + * @details This function will search all system handles consecutively. + * + * @param[in] conn_handle The connection to check. + * @param[out] p_cccd The CCCD value of the service changed characteristic for this link. + * + * @return Any error from @ref sd_ble_gatts_value_get or @ref sd_ble_gatts_attr_get. + */ +static uint32_t service_changed_cccd(uint16_t conn_handle, uint16_t *p_cccd) +{ + bool sc_found = false; + uint16_t end_handle; + + uint32_t err_code = sd_ble_gatts_initial_user_handle_get(&end_handle); + + __ASSERT(err_code == NRF_SUCCESS, ""); + + for (uint16_t handle = 1; handle < end_handle; handle++) { + ble_uuid_t uuid; + ble_gatts_value_t value = {.p_value = (uint8_t *)&uuid.uuid, .len = 2, .offset = 0}; + + err_code = sd_ble_gatts_attr_get(handle, &uuid, NULL); + if (err_code != NRF_SUCCESS) { + return err_code; + } else if (!sc_found && + (uuid.uuid == BLE_UUID_GATT_CHARACTERISTIC_SERVICE_CHANGED)) { + sc_found = true; + } else if (sc_found && (uuid.uuid == BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG)) { + value.p_value = (uint8_t *)p_cccd; + return sd_ble_gatts_value_get(conn_handle, handle, &value); + } + } + return NRF_ERROR_NOT_FOUND; +} + +/** + * @brief Function for sending a service changed indication in an event context, where no return + * code can be given. + * + * @details This function will do the procedure, and check the result, set a flag if needed, and + * send an event if needed. + * + * @param[in] conn_handle The connection to perform the procedure on. + */ +static void service_changed_send_in_evt(uint16_t conn_handle) +{ + bool sc_pending_state = true; + bool sc_sent_state = false; + uint32_t err_code = gscm_service_changed_ind_send(conn_handle); + + switch (err_code) { + case NRF_SUCCESS: { + pm_evt_t event = { + .evt_id = PM_EVT_SERVICE_CHANGED_IND_SENT, + .conn_handle = conn_handle, + }; + + sc_sent_state = true; + + evt_send(&event); + break; + } + + case NRF_ERROR_BUSY: + /* Do nothing. */ + break; + + case NRF_ERROR_INVALID_STATE: { + uint16_t cccd; + + err_code = service_changed_cccd(conn_handle, &cccd); + if ((err_code == NRF_SUCCESS) && cccd) { + /* Possible ATT_MTU exchange ongoing. */ + /* Do nothing, treat as busy. */ + break; + } + if (err_code != NRF_SUCCESS) { + LOG_DBG("Unexpected error when looking for service changed " + "CCCD: %s", + nrf_strerror_get(err_code)); + } + /* CCCDs not enabled or an error happened. Drop indication. */ + /* Fallthrough. */ + } + /* Sometimes fallthrough. */ + case NRF_ERROR_NOT_SUPPORTED: + /* Service changed not supported. Drop indication. */ + sc_pending_state = false; + gscm_db_change_notification_done(im_peer_id_get_by_conn_handle(conn_handle)); + break; + + case BLE_ERROR_GATTS_SYS_ATTR_MISSING: + local_db_apply_in_evt(conn_handle); + break; + + case BLE_ERROR_INVALID_CONN_HANDLE: + /* Do nothing. */ + break; + + default: + LOG_ERR("gscm_service_changed_ind_send() returned %s for conn_handle: %d", + nrf_strerror_get(err_code), conn_handle); + send_unexpected_error(conn_handle, err_code); + break; + } + + ble_conn_state_user_flag_set(conn_handle, m_flag_service_changed_pending, sc_pending_state); + ble_conn_state_user_flag_set(conn_handle, m_flag_service_changed_sent, sc_sent_state); +} +#endif + +static void apply_pending_handle(uint16_t conn_handle, void *p_context) +{ + UNUSED_PARAMETER(p_context); + local_db_apply_in_evt(conn_handle); +} + +static __INLINE void apply_pending_flags_check(void) +{ + UNUSED_RETURN_VALUE(ble_conn_state_for_each_set_user_flag(m_flag_local_db_apply_pending, + apply_pending_handle, NULL)); +} + +static void db_update_pending_handle(uint16_t conn_handle, void *p_context) +{ + UNUSED_PARAMETER(p_context); + if (nrf_mtx_trylock(&m_db_update_in_progress_mutex)) { + if (local_db_update_in_evt(conn_handle)) { + /* Successfully started writing to flash. */ + return; + } + + nrf_mtx_unlock(&m_db_update_in_progress_mutex); + } +} + +#if CONFIG_PM_SERVICE_CHANGED_ENABLED +static void sc_send_pending_handle(uint16_t conn_handle, void *p_context) +{ + UNUSED_PARAMETER(p_context); + if (!ble_conn_state_user_flag_get(conn_handle, m_flag_service_changed_sent)) { + service_changed_send_in_evt(conn_handle); + } +} + +static __INLINE void service_changed_pending_flags_check(void) +{ + UNUSED_RETURN_VALUE(ble_conn_state_for_each_set_user_flag(m_flag_service_changed_pending, + sc_send_pending_handle, NULL)); +} + +static void service_changed_needed(uint16_t conn_handle) +{ + if (gscm_service_changed_ind_needed(conn_handle)) { + ble_conn_state_user_flag_set(conn_handle, m_flag_service_changed_pending, true); + } +} +#endif + +static void car_update_pending_handle(uint16_t conn_handle, void *p_context) +{ + UNUSED_PARAMETER(p_context); + + ble_uuid_t car_uuid; + + memset(&car_uuid, 0, sizeof(ble_uuid_t)); + car_uuid.uuid = BLE_UUID_GAP_CHARACTERISTIC_CAR; + car_uuid.type = BLE_UUID_TYPE_BLE; + + ble_gattc_handle_range_t const car_handle_range = {1, 0xFFFF}; + + uint32_t err_code = + sd_ble_gattc_char_value_by_uuid_read(conn_handle, &car_uuid, &car_handle_range); + + if (err_code == NRF_SUCCESS) { + ble_conn_state_user_flag_set(conn_handle, m_flag_car_handle_queried, true); + } +} + +static void car_update_needed(uint16_t conn_handle) +{ + pm_peer_data_t peer_data; + + if (pds_peer_data_read(im_peer_id_get_by_conn_handle(conn_handle), + PM_PEER_DATA_ID_CENTRAL_ADDR_RES, &peer_data, + NULL) == NRF_ERROR_NOT_FOUND) { + ble_conn_state_user_flag_set(conn_handle, m_flag_car_update_pending, true); + } +} + +static __INLINE void update_pending_flags_check(void) +{ + uint32_t count = ble_conn_state_for_each_set_user_flag(m_flag_local_db_update_pending, + db_update_pending_handle, NULL); + if (count == 0) { + count = ble_conn_state_for_each_set_user_flag(m_flag_car_update_pending, + car_update_pending_handle, NULL); + UNUSED_RETURN_VALUE(count); + } +} + +/** + * @brief Callback function for events from the ID Manager module. + * This function is registered in the ID Manager module. + * + * @param[in] p_event The event from the ID Manager module. + */ +void gcm_im_evt_handler(pm_evt_t *p_event) +{ + switch (p_event->evt_id) { + case PM_EVT_BONDED_PEER_CONNECTED: + local_db_apply_in_evt(p_event->conn_handle); +#if (CONFIG_PM_SERVICE_CHANGED_ENABLED == 1) + service_changed_needed(p_event->conn_handle); +#endif + car_update_needed(p_event->conn_handle); + update_pending_flags_check(); + break; + default: + break; + } +} + +/** + * @brief Callback function for events from the Peer Database module. + * This handler is extern in Peer Database. + * + * @param[in] p_event The event from the Security Dispatcher module. + */ +void gcm_pdb_evt_handler(pm_evt_t *p_event) +{ + if (p_event->evt_id == PM_EVT_PEER_DATA_UPDATE_SUCCEEDED && + p_event->params.peer_data_update_succeeded.action == PM_PEER_DATA_OP_UPDATE) { + switch (p_event->params.peer_data_update_succeeded.data_id) { + case PM_PEER_DATA_ID_BONDING: { + uint16_t conn_handle = im_conn_handle_get(p_event->peer_id); + + if (conn_handle != BLE_CONN_HANDLE_INVALID) { + local_db_update(conn_handle, true); + car_update_needed(conn_handle); + } + break; + } + +#if CONFIG_PM_SERVICE_CHANGED_ENABLED + case PM_PEER_DATA_ID_SERVICE_CHANGED_PENDING: { + uint32_t err_code; + pm_peer_data_flash_t peer_data; + + err_code = pdb_peer_data_ptr_get(p_event->peer_id, + PM_PEER_DATA_ID_SERVICE_CHANGED_PENDING, + &peer_data); + + if (err_code == NRF_SUCCESS) { + if (*peer_data.p_service_changed_pending) { + uint16_t conn_handle = im_conn_handle_get(p_event->peer_id); + + if (conn_handle != BLE_CONN_HANDLE_INVALID) { + ble_conn_state_user_flag_set( + conn_handle, m_flag_service_changed_pending, + true); + service_changed_pending_flags_check(); + } + } + } + break; + } +#endif + + case PM_PEER_DATA_ID_GATT_LOCAL: + if (m_db_update_in_progress_mutex == NRF_MTX_LOCKED) { + nrf_mtx_unlock(&m_db_update_in_progress_mutex); + } + + /* Expecting a call to update_pending_flags_check() immediately. */ + break; + + default: + /* No action */ + break; + } + } + + update_pending_flags_check(); +} + +uint32_t gcm_init(void) +{ + NRF_PM_DEBUG_CHECK(!m_module_initialized); + + internal_state_reset(); + + m_flag_local_db_update_pending = ble_conn_state_user_flag_acquire(); + m_flag_local_db_apply_pending = ble_conn_state_user_flag_acquire(); + m_flag_service_changed_pending = ble_conn_state_user_flag_acquire(); + m_flag_service_changed_sent = ble_conn_state_user_flag_acquire(); + m_flag_car_update_pending = ble_conn_state_user_flag_acquire(); + m_flag_car_handle_queried = ble_conn_state_user_flag_acquire(); + m_flag_car_value_queried = ble_conn_state_user_flag_acquire(); + + if ((m_flag_local_db_update_pending == CONFIG_BLE_CONN_STATE_USER_FLAG_COUNT) || + (m_flag_local_db_apply_pending == CONFIG_BLE_CONN_STATE_USER_FLAG_COUNT) || + (m_flag_service_changed_pending == CONFIG_BLE_CONN_STATE_USER_FLAG_COUNT) || + (m_flag_service_changed_sent == CONFIG_BLE_CONN_STATE_USER_FLAG_COUNT) || + (m_flag_car_update_pending == CONFIG_BLE_CONN_STATE_USER_FLAG_COUNT) || + (m_flag_car_handle_queried == CONFIG_BLE_CONN_STATE_USER_FLAG_COUNT) || + (m_flag_car_value_queried == CONFIG_BLE_CONN_STATE_USER_FLAG_COUNT)) { + LOG_ERR("Could not acquire conn_state user flags. Increase " + "BLE_CONN_STATE_USER_FLAG_COUNT in the ble_conn_state module."); + return NRF_ERROR_INTERNAL; + } + + nrf_mtx_init(&m_db_update_in_progress_mutex); + + m_module_initialized = true; + + return NRF_SUCCESS; +} + +void store_car_value(uint16_t conn_handle, bool car_value) +{ + /* Use a uint32_t to enforce 4-byte alignment. */ + static const uint32_t car_value_true = true; + static const uint32_t car_value_false; + + pm_peer_data_const_t peer_data = { + .data_id = PM_PEER_DATA_ID_CENTRAL_ADDR_RES, + .length_words = 1, + }; + + ble_conn_state_user_flag_set(conn_handle, m_flag_car_update_pending, false); + peer_data.p_central_addr_res = car_value ? &car_value_true : &car_value_false; + uint32_t err_code = + pds_peer_data_store(im_peer_id_get_by_conn_handle(conn_handle), &peer_data, NULL); + if (err_code != NRF_SUCCESS) { + LOG_WRN("CAR char value couldn't be stored (error: %s). Reattempt will " + "happen on the next connection.", + nrf_strerror_get(err_code)); + } +} + +/** + * @brief Callback function for BLE events from the SoftDevice. + * + * @param[in] p_ble_evt The BLE event from the SoftDevice. + */ +void gcm_ble_evt_handler(ble_evt_t const *p_ble_evt) +{ + uint16_t conn_handle = p_ble_evt->evt.gatts_evt.conn_handle; + + switch (p_ble_evt->header.evt_id) { + case BLE_GATTS_EVT_SYS_ATTR_MISSING: + local_db_apply_in_evt(conn_handle); + break; + +#if CONFIG_PM_SERVICE_CHANGED_ENABLED + case BLE_GATTS_EVT_SC_CONFIRM: { + pm_evt_t event = { + .evt_id = PM_EVT_SERVICE_CHANGED_IND_CONFIRMED, + .peer_id = im_peer_id_get_by_conn_handle(conn_handle), + .conn_handle = conn_handle, + }; + + gscm_db_change_notification_done(event.peer_id); + + ble_conn_state_user_flag_set(conn_handle, m_flag_service_changed_sent, false); + ble_conn_state_user_flag_set(conn_handle, m_flag_service_changed_pending, false); + evt_send(&event); + break; + } +#endif + + case BLE_GATTS_EVT_WRITE: + if (cccd_written(&p_ble_evt->evt.gatts_evt.params.write)) { + local_db_update(conn_handle, true); + update_pending_flags_check(); + } + break; + + case BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP: { + bool handle_found = false; + + conn_handle = p_ble_evt->evt.gattc_evt.conn_handle; + const ble_gattc_evt_char_val_by_uuid_read_rsp_t *p_val = + &p_ble_evt->evt.gattc_evt.params.char_val_by_uuid_read_rsp; + + if (!ble_conn_state_user_flag_get(conn_handle, m_flag_car_handle_queried)) { + break; + } + + ble_conn_state_user_flag_set(conn_handle, m_flag_car_handle_queried, false); + + if (p_ble_evt->evt.gattc_evt.gatt_status == + BLE_GATT_STATUS_ATTERR_ATTRIBUTE_NOT_FOUND) { + /* Store 0. */ + } else if (p_ble_evt->evt.gattc_evt.gatt_status != BLE_GATT_STATUS_SUCCESS) { + LOG_WRN("Unexpected GATT status while getting CAR char value: 0x%x", + p_ble_evt->evt.gattc_evt.gatt_status); + /* Store 0. */ + } else { + if (p_val->count != 1) { + LOG_WRN("Multiple (%d) CAR characteristics found, using the first.", + p_val->count); + } + + if (p_val->value_len != 1) { + LOG_WRN("Unexpected CAR characteristic value length (%d), store 0.", + p_val->value_len); + /* Store 0. */ + } else { + uint32_t err_code = sd_ble_gattc_read( + conn_handle, *(uint16_t *)p_val->handle_value, 0); + + if (err_code == NRF_SUCCESS) { + handle_found = true; + ble_conn_state_user_flag_set( + conn_handle, m_flag_car_value_queried, true); + } + } + } + + if (!handle_found) { + store_car_value(conn_handle, false); + } + break; + } + + case BLE_GATTC_EVT_READ_RSP: { + bool car_value = false; + + conn_handle = p_ble_evt->evt.gattc_evt.conn_handle; + const ble_gattc_evt_read_rsp_t *p_val = &p_ble_evt->evt.gattc_evt.params.read_rsp; + + if (!ble_conn_state_user_flag_get(conn_handle, m_flag_car_value_queried)) { + break; + } + + ble_conn_state_user_flag_set(conn_handle, m_flag_car_value_queried, false); + + if (p_ble_evt->evt.gattc_evt.gatt_status != BLE_GATT_STATUS_SUCCESS) { + LOG_WRN("Unexpected GATT status while getting CAR char value: 0x%x", + p_ble_evt->evt.gattc_evt.gatt_status); + /* Store 0. */ + } else { + if (p_val->len != 1) { + LOG_WRN("Unexpected CAR characteristic value length (%d), store 0.", + p_val->len); + /* Store 0. */ + } else { + car_value = *p_val->data; + } + } + + store_car_value(conn_handle, car_value); + } + } + + apply_pending_flags_check(); +#if CONFIG_PM_SERVICE_CHANGED_ENABLED + service_changed_pending_flags_check(); +#endif +} + +uint32_t gcm_local_db_cache_update(uint16_t conn_handle) +{ + NRF_PM_DEBUG_CHECK(m_module_initialized); + + local_db_update(conn_handle, true); + update_pending_flags_check(); + + return NRF_SUCCESS; +} + +#if CONFIG_PM_SERVICE_CHANGED_ENABLED +void gcm_local_database_has_changed(void) +{ + gscm_local_database_has_changed(); + + struct ble_conn_state_conn_handle_list conn_handles = ble_conn_state_conn_handles(); + + for (uint16_t i = 0; i < conn_handles.len; i++) { + if (im_peer_id_get_by_conn_handle(conn_handles.conn_handles[i]) == + PM_PEER_ID_INVALID) { + ble_conn_state_user_flag_set(conn_handles.conn_handles[i], + m_flag_service_changed_pending, true); + } + } + + service_changed_pending_flags_check(); +} +#endif diff --git a/lib/peer_manager/modules/gatts_cache_manager.c b/lib/peer_manager/modules/gatts_cache_manager.c new file mode 100644 index 0000000000..152cd2cc61 --- /dev/null +++ b/lib/peer_manager/modules/gatts_cache_manager.c @@ -0,0 +1,374 @@ +/* + * Copyright (c) 2015-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(peer_manager, CONFIG_PEER_MANAGER_LOG_LEVEL); + +#if !defined(CONFIG_PM_SERVICE_CHANGED_ENABLED) || (CONFIG_PM_SERVICE_CHANGED_ENABLED == 1) + +/* The number of registered event handlers. */ +#define GSCM_EVENT_HANDLERS_CNT ARRAY_SIZE(m_evt_handlers) + +/* GATTS Cache Manager event handler in Peer Manager. */ +extern void pm_gscm_evt_handler(pm_evt_t *p_gcm_evt); + +/* GATTS Cache Manager events' handlers. + * The number of elements in this array is GSCM_EVENT_HANDLERS_CNT. + */ +static pm_evt_handler_internal_t m_evt_handlers[] = {pm_gscm_evt_handler}; +#endif + +/* Syntactic sugar, two spoons. */ +#define SYS_ATTR_SYS (BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS) +#define SYS_ATTR_USR (BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS) +#define SYS_ATTR_BOTH (SYS_ATTR_SYS | SYS_ATTR_USR) + +static bool m_module_initialized; +static pm_peer_id_t m_current_sc_store_peer_id; + +/** @brief Function for resetting the module variable(s) of the GSCM module. */ +static void internal_state_reset(void) +{ + m_module_initialized = false; + m_current_sc_store_peer_id = PM_PEER_ID_INVALID; + + /* If CONFIG_PM_SERVICE_CHANGED_ENABLED is 0, this variable is unused. */ + UNUSED_VARIABLE(m_current_sc_store_peer_id); +} + +#if !defined(CONFIG_PM_SERVICE_CHANGED_ENABLED) || (CONFIG_PM_SERVICE_CHANGED_ENABLED == 1) +static void evt_send(pm_evt_t *p_gscm_evt) +{ + p_gscm_evt->conn_handle = im_conn_handle_get(p_gscm_evt->peer_id); + + for (uint32_t i = 0; i < GSCM_EVENT_HANDLERS_CNT; i++) { + m_evt_handlers[i](p_gscm_evt); + } +} + +/** + * @brief Function for storing service_changed_pending = true to flash for all peers, in sequence. + * + * This function aborts if it gets @ref NRF_ERROR_BUSY when trying to store. A subsequent call will + * continue where the last call was aborted. + */ +static void service_changed_pending_set(void) +{ + NRF_PM_DEBUG_CHECK(m_module_initialized); + + uint32_t err_code; + /* Use a uint32_t to enforce 4-byte alignment. */ + static const uint32_t service_changed_pending = true; + + pm_peer_data_const_t peer_data = { + .data_id = PM_PEER_DATA_ID_SERVICE_CHANGED_PENDING, + .length_words = PM_SC_STATE_N_WORDS(), + .p_service_changed_pending = (bool *)&service_changed_pending, + }; + + while (m_current_sc_store_peer_id != PM_PEER_ID_INVALID) { + err_code = pds_peer_data_store(m_current_sc_store_peer_id, &peer_data, NULL); + if (err_code != NRF_SUCCESS) { + pm_evt_t evt = {.peer_id = m_current_sc_store_peer_id}; + + if (err_code == NRF_ERROR_BUSY) { + /* Do nothing. */ + } else if (err_code == NRF_ERROR_RESOURCES) { + evt.evt_id = PM_EVT_STORAGE_FULL; + evt_send(&evt); + } else { + LOG_ERR("pds_peer_data_store() returned %s while storing " + "service changed " + "state for peer id %d.", + nrf_strerror_get(err_code), + m_current_sc_store_peer_id); + evt.evt_id = PM_EVT_ERROR_UNEXPECTED; + evt.params.error_unexpected.error = err_code; + evt_send(&evt); + } + break; + } + + m_current_sc_store_peer_id = pds_next_peer_id_get(m_current_sc_store_peer_id); + } +} + +/** + * @brief Event handler for events from the Peer Database module. + * This function is extern in Peer Database. + * + * @param[in] p_event The event that has happened with peer id and flags. + */ +void gscm_pdb_evt_handler(pm_evt_t *p_event) +{ + if (m_current_sc_store_peer_id != PM_PEER_ID_INVALID) { + service_changed_pending_set(); + } +} +#endif + +uint32_t gscm_init(void) +{ + NRF_PM_DEBUG_CHECK(!m_module_initialized); + + internal_state_reset(); + m_module_initialized = true; + + return NRF_SUCCESS; +} + +uint32_t gscm_local_db_cache_update(uint16_t conn_handle) +{ + NRF_PM_DEBUG_CHECK(m_module_initialized); + + pm_peer_id_t peer_id = im_peer_id_get_by_conn_handle(conn_handle); + uint32_t err_code; + + if (peer_id == PM_PEER_ID_INVALID) { + return BLE_ERROR_INVALID_CONN_HANDLE; + } + + pm_peer_data_t peer_data; + uint16_t n_bufs = 1; + bool retry_with_bigger_buffer = false; + + do { + retry_with_bigger_buffer = false; + + err_code = pdb_write_buf_get(peer_id, PM_PEER_DATA_ID_GATT_LOCAL, n_bufs++, + &peer_data); + if (err_code == NRF_SUCCESS) { + pm_peer_data_local_gatt_db_t *p_local_gatt_db = + peer_data.p_local_gatt_db; + + p_local_gatt_db->flags = SYS_ATTR_BOTH; + + err_code = sd_ble_gatts_sys_attr_get( + conn_handle, &p_local_gatt_db->data[0], + &p_local_gatt_db->len, p_local_gatt_db->flags); + + if (err_code == NRF_SUCCESS) { + pm_peer_data_flash_t curr_peer_data; + + err_code = pdb_peer_data_ptr_get(peer_id, + PM_PEER_DATA_ID_GATT_LOCAL, + &curr_peer_data); + + if ((err_code != NRF_SUCCESS) && + (err_code != NRF_ERROR_NOT_FOUND)) { + LOG_ERR("pdb_peer_data_ptr_get() returned %s " + "for conn_handle: %d", + nrf_strerror_get(err_code), + conn_handle); + return NRF_ERROR_INTERNAL; + } + + if ((err_code == NRF_ERROR_NOT_FOUND) || + (p_local_gatt_db->len != + curr_peer_data.p_local_gatt_db->len) || + (memcmp(p_local_gatt_db->data, + curr_peer_data.p_local_gatt_db->data, + p_local_gatt_db->len) != 0)) { + err_code = pdb_write_buf_store( + peer_id, PM_PEER_DATA_ID_GATT_LOCAL, + peer_id); + } else { + LOG_DBG("Local db is already up to date, " + "skipping write."); + uint32_t err_code_release = pdb_write_buf_release( + peer_id, PM_PEER_DATA_ID_GATT_LOCAL); + + if (err_code_release == NRF_SUCCESS) { + err_code = NRF_ERROR_INVALID_DATA; + } else { + LOG_ERR("Did another thread manipulate " + "PM_PEER_DATA_ID_GATT_LOCAL for " + "peer_id %d at the same time? " + "pdb_write_buf_release() returned " + "%s.", + peer_id, + nrf_strerror_get(err_code_release)); + err_code = NRF_ERROR_INTERNAL; + } + } + } else { + if (err_code == NRF_ERROR_DATA_SIZE) { + /* The sys attributes are bigger than the requested + * write buffer. + */ + retry_with_bigger_buffer = true; + } else if (err_code == NRF_ERROR_NOT_FOUND) { + /* There are no sys attributes in the GATT db, so + * nothing needs to be stored. + */ + err_code = NRF_SUCCESS; + } + + uint32_t err_code_release = pdb_write_buf_release( + peer_id, PM_PEER_DATA_ID_GATT_LOCAL); + + if (err_code_release != NRF_SUCCESS) { + LOG_ERR("Did another thread manipulate " + "PM_PEER_DATA_ID_GATT_LOCAL for " + "peer_id %d at the same time? " + "pdb_write_buf_release() returned %s.", + peer_id, + nrf_strerror_get(err_code_release)); + err_code = NRF_ERROR_INTERNAL; + } + } + } else if (err_code == NRF_ERROR_INVALID_PARAM) { + /* The sys attributes are bigger than the entire write buffer. */ + err_code = NRF_ERROR_DATA_SIZE; + } + } while (retry_with_bigger_buffer); + + return err_code; +} + +uint32_t gscm_local_db_cache_apply(uint16_t conn_handle) +{ + NRF_PM_DEBUG_CHECK(m_module_initialized); + + pm_peer_id_t peer_id = im_peer_id_get_by_conn_handle(conn_handle); + uint32_t err_code; + pm_peer_data_flash_t peer_data; + uint8_t const *p_sys_attr_data = NULL; + uint16_t sys_attr_len = 0; + uint32_t sys_attr_flags = (SYS_ATTR_BOTH); + bool all_attributes_applied = true; + + if (peer_id != PM_PEER_ID_INVALID) { + err_code = pdb_peer_data_ptr_get(peer_id, PM_PEER_DATA_ID_GATT_LOCAL, &peer_data); + if (err_code == NRF_SUCCESS) { + pm_peer_data_local_gatt_db_t const *p_local_gatt_db; + + p_local_gatt_db = peer_data.p_local_gatt_db; + p_sys_attr_data = p_local_gatt_db->data; + sys_attr_len = p_local_gatt_db->len; + sys_attr_flags = p_local_gatt_db->flags; + } + } + + do { + err_code = sd_ble_gatts_sys_attr_set(conn_handle, p_sys_attr_data, sys_attr_len, + sys_attr_flags); + + if (err_code == NRF_ERROR_NO_MEM) { + err_code = NRF_ERROR_BUSY; + } else if (err_code == NRF_ERROR_INVALID_STATE) { + err_code = NRF_SUCCESS; + } else if (err_code == NRF_ERROR_INVALID_DATA) { + all_attributes_applied = false; + + if (sys_attr_flags & SYS_ATTR_USR) { + /* Try setting only system attributes. */ + sys_attr_flags = SYS_ATTR_SYS; + } else if (p_sys_attr_data || sys_attr_len) { + /* Try reporting that none exist. */ + p_sys_attr_data = NULL; + sys_attr_len = 0; + sys_attr_flags = SYS_ATTR_BOTH; + } else { + LOG_ERR("sd_ble_gatts_sys_attr_set() returned " + "NRF_ERROR_INVALID_DATA for NULL " + "pointer which should never happen. conn_handle: %d", + conn_handle); + err_code = NRF_ERROR_INTERNAL; + } + } + } while (err_code == NRF_ERROR_INVALID_DATA); + + if (!all_attributes_applied) { + err_code = NRF_ERROR_INVALID_DATA; + } + + return err_code; +} + +#if !defined(CONFIG_PM_SERVICE_CHANGED_ENABLED) || (CONFIG_PM_SERVICE_CHANGED_ENABLED == 1) +void gscm_local_database_has_changed(void) +{ + NRF_PM_DEBUG_CHECK(m_module_initialized); + m_current_sc_store_peer_id = pds_next_peer_id_get(PM_PEER_ID_INVALID); + service_changed_pending_set(); +} + +bool gscm_service_changed_ind_needed(uint16_t conn_handle) +{ + uint32_t err_code; + bool service_changed_state; + pm_peer_data_flash_t peer_data; + + peer_data.p_service_changed_pending = &service_changed_state; + pm_peer_id_t peer_id = im_peer_id_get_by_conn_handle(conn_handle); + + err_code = + pdb_peer_data_ptr_get(peer_id, PM_PEER_DATA_ID_SERVICE_CHANGED_PENDING, &peer_data); + + if (err_code != NRF_SUCCESS) { + return false; + } + + return *peer_data.p_service_changed_pending; +} + +uint32_t gscm_service_changed_ind_send(uint16_t conn_handle) +{ + static uint16_t start_handle; + const uint16_t end_handle = 0xFFFF; + uint32_t err_code; + + err_code = sd_ble_gatts_initial_user_handle_get(&start_handle); + + if (err_code != NRF_SUCCESS) { + LOG_ERR("sd_ble_gatts_initial_user_handle_get() returned %s which should not " + "happen.", + nrf_strerror_get(err_code)); + return NRF_ERROR_INTERNAL; + } + + do { + err_code = sd_ble_gatts_service_changed(conn_handle, start_handle, end_handle); + if (err_code == BLE_ERROR_INVALID_ATTR_HANDLE) { + start_handle += 1; + } + } while (err_code == BLE_ERROR_INVALID_ATTR_HANDLE); + + return err_code; +} + +void gscm_db_change_notification_done(pm_peer_id_t peer_id) +{ + NRF_PM_DEBUG_CHECK(m_module_initialized); + + /* Use a uint32_t to enforce 4-byte alignment. */ + static const uint32_t service_changed_pending; + + pm_peer_data_const_t peer_data = { + .data_id = PM_PEER_DATA_ID_SERVICE_CHANGED_PENDING, + .length_words = PM_SC_STATE_N_WORDS(), + .p_service_changed_pending = (bool *)&service_changed_pending, + }; + + /* Don't need to check return code, because all error conditions can be ignored. */ + (void)pds_peer_data_store(peer_id, &peer_data, NULL); +} +#endif diff --git a/lib/peer_manager/modules/id_manager.c b/lib/peer_manager/modules/id_manager.c new file mode 100644 index 0000000000..14cac437e9 --- /dev/null +++ b/lib/peer_manager/modules/id_manager.c @@ -0,0 +1,645 @@ +/* + * Copyright (c) 2015-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(peer_manager, CONFIG_PEER_MANAGER_LOG_LEVEL); + +#define IM_MAX_CONN_HANDLES (20) +#define IM_NO_INVALID_CONN_HANDLES (0xFF) +#define IM_ADDR_CLEARTEXT_LENGTH (3) +#define IM_ADDR_CIPHERTEXT_LENGTH (3) + +/* The number of registered event handlers. */ +#define IM_EVENT_HANDLERS_CNT ARRAY_SIZE(m_evt_handlers) + +/* Identity Manager event handlers in Peer Manager and GATT Cache Manager. */ +extern void pm_im_evt_handler(pm_evt_t *p_event); +extern void gcm_im_evt_handler(pm_evt_t *p_event); + +/* Identity Manager events' handlers. + * The number of elements in this array is IM_EVENT_HANDLERS_CNT. + */ +static pm_evt_handler_internal_t const m_evt_handlers[] = {pm_im_evt_handler, gcm_im_evt_handler}; + +typedef struct { + pm_peer_id_t peer_id; + ble_gap_addr_t peer_address; +} im_connection_t; + +static im_connection_t m_connections[IM_MAX_CONN_HANDLES]; +static uint8_t m_wlisted_peer_cnt; +static pm_peer_id_t m_wlisted_peers[BLE_GAP_WHITELIST_ADDR_MAX_COUNT]; + +/** + * @brief Function for sending an event to all registered event handlers. + * + * @param[in] p_event The event to distribute. + */ +static void evt_send(pm_evt_t *p_event) +{ + for (uint32_t i = 0; i < IM_EVENT_HANDLERS_CNT; i++) { + m_evt_handlers[i](p_event); + } +} + +/** + * @brief Function checking the validity of an IRK + * + * @detail An all-zero IRK is not valid. This function will check if a given IRK is valid. + * + * @param[in] p_irk The IRK for which the validity is going to be checked. + * + * @retval true The IRK is valid. + * @retval false The IRK is invalid. + */ +bool is_valid_irk(ble_gap_irk_t const *p_irk) +{ + NRF_PM_DEBUG_CHECK(p_irk != NULL); + + for (uint32_t i = 0; i < BLE_GAP_SEC_KEY_LEN; i++) { + if (p_irk->irk[i] != 0) { + return true; + } + } + return false; +} + +/** + * @brief Function for comparing two addresses to determine if they are identical + * + * @note The address type need to be identical, as well as every bit in the address itself. + * + * @param[in] p_addr1 The first address to be compared. + * @param[in] p_addr2 The second address to be compared. + * + * @retval true The addresses are identical. + * @retval false The addresses are not identical. + */ +bool addr_compare(ble_gap_addr_t const *p_addr1, ble_gap_addr_t const *p_addr2) +{ + if ((p_addr1 == NULL) || (p_addr2 == NULL)) { + return false; + } + + /* Check that the addr type is identical, return false if it is not */ + if (p_addr1->addr_type != p_addr2->addr_type) { + return false; + } + + /* Check if the addr bytes are is identical */ + return (memcmp(p_addr1->addr, p_addr2->addr, BLE_GAP_ADDR_LEN) == 0); +} + +void im_ble_evt_handler(ble_evt_t const *ble_evt) +{ + ble_gap_evt_t gap_evt; + pm_peer_id_t bonded_matching_peer_id; + + if (ble_evt->header.evt_id != BLE_GAP_EVT_CONNECTED) { + /* Nothing to do. */ + return; + } + + gap_evt = ble_evt->evt.gap_evt; + bonded_matching_peer_id = PM_PEER_ID_INVALID; + + if (gap_evt.params.connected.peer_addr.addr_type != + BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_NON_RESOLVABLE) { + /* Search the database for bonding data matching the one that triggered the event. + * Public and static addresses can be matched on address alone, while resolvable + * random addresses can be resolved agains known IRKs. Non-resolvable random + * addresses are never matching because they are not longterm form of + * identification. + */ + + pm_peer_id_t peer_id; + pm_peer_data_flash_t peer_data; + + pds_peer_data_iterate_prepare(); + + switch (gap_evt.params.connected.peer_addr.addr_type) { + case BLE_GAP_ADDR_TYPE_PUBLIC: + case BLE_GAP_ADDR_TYPE_RANDOM_STATIC: { + while (pds_peer_data_iterate(PM_PEER_DATA_ID_BONDING, &peer_id, + &peer_data)) { + if (addr_compare( + &gap_evt.params.connected.peer_addr, + &peer_data.p_bonding_data->peer_ble_id.id_addr_info)) { + bonded_matching_peer_id = peer_id; + break; + } + } + } break; + + case BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE: { + while (pds_peer_data_iterate(PM_PEER_DATA_ID_BONDING, &peer_id, + &peer_data)) { + if (im_address_resolve( + &gap_evt.params.connected.peer_addr, + &peer_data.p_bonding_data->peer_ble_id.id_info)) { + bonded_matching_peer_id = peer_id; + break; + } + } + } break; + + default: + NRF_PM_DEBUG_CHECK(false); + break; + } + } + + m_connections[gap_evt.conn_handle].peer_id = bonded_matching_peer_id; + m_connections[gap_evt.conn_handle].peer_address = gap_evt.params.connected.peer_addr; + + if (bonded_matching_peer_id != PM_PEER_ID_INVALID) { + /* Send a bonded peer event */ + pm_evt_t im_evt; + + im_evt.conn_handle = gap_evt.conn_handle; + im_evt.peer_id = bonded_matching_peer_id; + im_evt.evt_id = PM_EVT_BONDED_PEER_CONNECTED; + evt_send(&im_evt); + } +} + +/** + * @brief Function to compare two sets of bonding data to check if they belong to the same device. + * @note Invalid irks will never match even though they are identical. + * + * @param[in] p_bonding_data1 First bonding data for comparison + * @param[in] p_bonding_data2 Second bonding data for comparison + * + * @return True if the input matches, false if it does not. + */ +bool im_is_duplicate_bonding_data(pm_peer_data_bonding_t const *p_bonding_data1, + pm_peer_data_bonding_t const *p_bonding_data2) +{ + NRF_PM_DEBUG_CHECK(p_bonding_data1 != NULL); + NRF_PM_DEBUG_CHECK(p_bonding_data2 != NULL); + + ble_gap_addr_t const *p_addr1 = &p_bonding_data1->peer_ble_id.id_addr_info; + ble_gap_addr_t const *p_addr2 = &p_bonding_data2->peer_ble_id.id_addr_info; + + bool duplicate_irk = + ((memcmp(p_bonding_data1->peer_ble_id.id_info.irk, + p_bonding_data2->peer_ble_id.id_info.irk, BLE_GAP_SEC_KEY_LEN) == 0) && + is_valid_irk(&p_bonding_data1->peer_ble_id.id_info) && + is_valid_irk(&p_bonding_data2->peer_ble_id.id_info)); + + bool duplicate_addr = addr_compare(p_addr1, p_addr2); + + bool id_addrs = ((p_addr1->addr_type != BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE) && + (p_addr1->addr_type != BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_NON_RESOLVABLE) && + (p_addr2->addr_type != BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE) && + (p_addr2->addr_type != BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_NON_RESOLVABLE)); + + return (duplicate_addr && id_addrs) || (duplicate_irk && !id_addrs); +} + +pm_peer_id_t im_find_duplicate_bonding_data(pm_peer_data_bonding_t const *p_bonding_data, + pm_peer_id_t peer_id_skip) +{ + pm_peer_id_t peer_id; + pm_peer_data_flash_t peer_data_duplicate; + + NRF_PM_DEBUG_CHECK(p_bonding_data != NULL); + + pds_peer_data_iterate_prepare(); + + while (pds_peer_data_iterate(PM_PEER_DATA_ID_BONDING, &peer_id, &peer_data_duplicate)) { + if ((peer_id != peer_id_skip) && + im_is_duplicate_bonding_data(p_bonding_data, + peer_data_duplicate.p_bonding_data)) { + return peer_id; + } + } + return PM_PEER_ID_INVALID; +} + +pm_peer_id_t im_peer_id_get_by_conn_handle(uint16_t conn_handle) +{ + if ((conn_handle >= IM_MAX_CONN_HANDLES) || !ble_conn_state_valid(conn_handle)) { + return PM_PEER_ID_INVALID; + } + + return m_connections[conn_handle].peer_id; +} + +uint32_t im_ble_addr_get(uint16_t conn_handle, ble_gap_addr_t *p_ble_addr) +{ + NRF_PM_DEBUG_CHECK(p_ble_addr != NULL); + + if ((conn_handle >= IM_MAX_CONN_HANDLES) || !ble_conn_state_valid(conn_handle)) { + return BLE_ERROR_INVALID_CONN_HANDLE; + } + + *p_ble_addr = m_connections[conn_handle].peer_address; + return NRF_SUCCESS; +} + +bool im_master_ids_compare(ble_gap_master_id_t const *p_master_id1, + ble_gap_master_id_t const *p_master_id2) +{ + NRF_PM_DEBUG_CHECK(p_master_id1 != NULL); + NRF_PM_DEBUG_CHECK(p_master_id2 != NULL); + + if (!im_master_id_is_valid(p_master_id1)) { + return false; + } + + if (p_master_id1->ediv != p_master_id2->ediv) { + return false; + } + + return (memcmp(p_master_id1->rand, p_master_id2->rand, BLE_GAP_SEC_RAND_LEN) == 0); +} + +pm_peer_id_t im_peer_id_get_by_master_id(ble_gap_master_id_t const *p_master_id) +{ + pm_peer_id_t peer_id; + pm_peer_data_flash_t peer_data; + + NRF_PM_DEBUG_CHECK(p_master_id != NULL); + + pds_peer_data_iterate_prepare(); + + /* For each stored peer, check if the master_id matches p_master_id */ + while (pds_peer_data_iterate(PM_PEER_DATA_ID_BONDING, &peer_id, &peer_data)) { + if (im_master_ids_compare(p_master_id, + &peer_data.p_bonding_data->own_ltk.master_id) || + im_master_ids_compare(p_master_id, + &peer_data.p_bonding_data->peer_ltk.master_id)) { + /* If a matching master ID is found then return the peer ID. */ + return peer_id; + } + } + + /* If no matching master ID is found return PM_PEER_ID_INVALID. */ + return PM_PEER_ID_INVALID; +} + +uint16_t im_conn_handle_get(pm_peer_id_t peer_id) +{ + if (peer_id == PM_PEER_ID_INVALID) { + return BLE_CONN_HANDLE_INVALID; + } + + for (uint16_t conn_handle = 0; conn_handle < IM_MAX_CONN_HANDLES; conn_handle++) { + if ((m_connections[conn_handle].peer_id == peer_id) && + ble_conn_state_valid(conn_handle)) { + return conn_handle; + } + } + return BLE_CONN_HANDLE_INVALID; +} + +bool im_master_id_is_valid(ble_gap_master_id_t const *p_master_id) +{ + if (p_master_id->ediv != 0) { + return true; + } + + for (uint32_t i = 0; i < BLE_GAP_SEC_RAND_LEN; i++) { + if (p_master_id->rand[i] != 0) { + return true; + } + } + return false; +} + +void im_new_peer_id(uint16_t conn_handle, pm_peer_id_t peer_id) +{ + if (conn_handle < IM_MAX_CONN_HANDLES) { + m_connections[conn_handle].peer_id = peer_id; + } +} + +uint32_t im_peer_free(pm_peer_id_t peer_id) +{ + uint16_t conn_handle; + uint32_t ret; + + conn_handle = im_conn_handle_get(peer_id); + ret = pdb_peer_free(peer_id); + + if (ret == NRF_SUCCESS && (conn_handle < IM_MAX_CONN_HANDLES)) { + m_connections[conn_handle].peer_id = PM_PEER_ID_INVALID; + } + return ret; +} + +/** @brief Given a list of peers, loads their GAP address and IRK into the provided buffers. */ +static uint32_t peers_id_keys_get(pm_peer_id_t const *p_peers, uint32_t peer_cnt, + ble_gap_addr_t *p_gap_addrs, uint32_t *p_addr_cnt, + ble_gap_irk_t *p_gap_irks, uint32_t *p_irk_cnt) +{ + uint32_t ret; + + pm_peer_data_bonding_t bond_data; + pm_peer_data_t peer_data; + + uint32_t const buf_size = sizeof(bond_data); + + bool copy_addrs = false; + bool copy_irks = false; + + NRF_PM_DEBUG_CHECK(p_peers != NULL); + + /* One of these two has to be provided. */ + NRF_PM_DEBUG_CHECK((p_gap_addrs != NULL) || (p_gap_irks != NULL)); + + if ((p_gap_addrs != NULL) && (p_addr_cnt != NULL)) { + NRF_PM_DEBUG_CHECK((*p_addr_cnt) >= peer_cnt); + + copy_addrs = true; + *p_addr_cnt = 0; + } + + if ((p_gap_irks != NULL) && (p_irk_cnt != NULL)) { + NRF_PM_DEBUG_CHECK((*p_irk_cnt) >= peer_cnt); + + copy_irks = true; + *p_irk_cnt = 0; + } + + memset(&peer_data, 0x00, sizeof(peer_data)); + peer_data.p_bonding_data = &bond_data; + + /* Read through flash memory and look for peers ID keys. */ + + for (uint32_t i = 0; i < peer_cnt; i++) { + memset(&bond_data, 0x00, sizeof(bond_data)); + + /* Read peer data from flash. */ + ret = pds_peer_data_read(p_peers[i], PM_PEER_DATA_ID_BONDING, &peer_data, + &buf_size); + + if ((ret == NRF_ERROR_NOT_FOUND) || (ret == NRF_ERROR_INVALID_PARAM)) { + /* Peer data coulnd't be found in flash or peer ID is not valid. */ + return NRF_ERROR_NOT_FOUND; + } + + uint8_t const addr_type = bond_data.peer_ble_id.id_addr_info.addr_type; + + if ((addr_type != BLE_GAP_ADDR_TYPE_PUBLIC) && + (addr_type != BLE_GAP_ADDR_TYPE_RANDOM_STATIC)) { + /* The address shared by the peer during bonding can't be used for + * whitelisting. + */ + return BLE_ERROR_GAP_INVALID_BLE_ADDR; + } + + /* Copy the GAP address. */ + if (copy_addrs) { + memcpy(&p_gap_addrs[i], &bond_data.peer_ble_id.id_addr_info, + sizeof(ble_gap_addr_t)); + (*p_addr_cnt)++; + } + + /* Copy the IRK. */ + if (copy_irks) { + memcpy(&p_gap_irks[i], bond_data.peer_ble_id.id_info.irk, + BLE_GAP_SEC_KEY_LEN); + (*p_irk_cnt)++; + } + } + + return NRF_SUCCESS; +} + +uint32_t im_device_identities_list_set(pm_peer_id_t const *p_peers, uint32_t peer_cnt) +{ + uint32_t ret; + pm_peer_data_t peer_data; + pm_peer_data_bonding_t bond_data; + + ble_gap_id_key_t keys[BLE_GAP_DEVICE_IDENTITIES_MAX_COUNT]; + ble_gap_id_key_t const *key_ptrs[BLE_GAP_DEVICE_IDENTITIES_MAX_COUNT]; + + if (peer_cnt > BLE_GAP_DEVICE_IDENTITIES_MAX_COUNT) { + return NRF_ERROR_INVALID_PARAM; + } + + if ((p_peers == NULL) || (peer_cnt == 0)) { + /* Clear the device identities list. */ + return sd_ble_gap_device_identities_set(NULL, NULL, 0); + } + + peer_data.p_bonding_data = &bond_data; + uint32_t const buf_size = sizeof(bond_data); + + memset(keys, 0x00, sizeof(keys)); + for (uint32_t i = 0; i < BLE_GAP_DEVICE_IDENTITIES_MAX_COUNT; i++) { + key_ptrs[i] = &keys[i]; + } + + for (uint32_t i = 0; i < peer_cnt; i++) { + memset(&bond_data, 0x00, sizeof(bond_data)); + + /* Read peer data from flash. */ + ret = pds_peer_data_read(p_peers[i], PM_PEER_DATA_ID_BONDING, &peer_data, + &buf_size); + + if ((ret == NRF_ERROR_NOT_FOUND) || (ret == NRF_ERROR_INVALID_PARAM)) { + LOG_WRN("peer id %d: Peer data could not be found in flash. Remove " + "the peer ID " + "from the peer list and try again.", + p_peers[i]); + return NRF_ERROR_NOT_FOUND; + } + + uint8_t const addr_type = bond_data.peer_ble_id.id_addr_info.addr_type; + + if ((addr_type != BLE_GAP_ADDR_TYPE_PUBLIC) && + (addr_type != BLE_GAP_ADDR_TYPE_RANDOM_STATIC)) { + LOG_WRN("peer id %d: The address shared by the peer during bonding cannot " + "be " + "whitelisted. Remove the peer ID from the peer list and try again.", + p_peers[i]); + return BLE_ERROR_GAP_INVALID_BLE_ADDR; + } + + /* Copy data to the buffer. */ + memcpy(&keys[i], &bond_data.peer_ble_id, sizeof(ble_gap_id_key_t)); + } + + return sd_ble_gap_device_identities_set(key_ptrs, NULL, peer_cnt); +} + +uint32_t im_id_addr_set(ble_gap_addr_t const *p_addr) +{ + return sd_ble_gap_addr_set(p_addr); +} + +uint32_t im_id_addr_get(ble_gap_addr_t *p_addr) +{ + NRF_PM_DEBUG_CHECK(p_addr != NULL); + + return sd_ble_gap_addr_get(p_addr); +} + +uint32_t im_privacy_set(pm_privacy_params_t const *p_privacy_params) +{ + return sd_ble_gap_privacy_set(p_privacy_params); +} + +uint32_t im_privacy_get(pm_privacy_params_t *p_privacy_params) +{ + return sd_ble_gap_privacy_get(p_privacy_params); +} + +/* Create a whitelist for the user using the cached list of peers. + * This whitelist is meant to be provided by the application to the Advertising module. + */ +uint32_t im_whitelist_get(ble_gap_addr_t *p_addrs, uint32_t *p_addr_cnt, ble_gap_irk_t *p_irks, + uint32_t *p_irk_cnt) +{ + /* One of the two buffers has to be provided. */ + NRF_PM_DEBUG_CHECK((p_addrs != NULL) || (p_irks != NULL)); + NRF_PM_DEBUG_CHECK((p_addr_cnt != NULL) || (p_irk_cnt != NULL)); + + if (((p_addr_cnt != NULL) && (m_wlisted_peer_cnt > *p_addr_cnt)) || + ((p_irk_cnt != NULL) && (m_wlisted_peer_cnt > *p_irk_cnt))) { + /* The size of the cached list of peers is larger than the provided buffers. */ + return NRF_ERROR_NO_MEM; + } + + /* NRF_SUCCESS or + * NRF_ERROR_NOT_FOUND, if a peer or its data were not found. + * BLE_ERROR_GAP_INVALID_BLE_ADDR, if a peer address can not be used for whitelisting. + */ + return peers_id_keys_get(m_wlisted_peers, m_wlisted_peer_cnt, p_addrs, p_addr_cnt, p_irks, + p_irk_cnt); +} + +/* Copies the peers to whitelist into a local cache. + * The cached list will be used by im_whitelist_get() to retrieve the active whitelist. + * For SoftDevices 3x, also loads the peers' GAP addresses and whitelists them using + * sd_ble_gap_whitelist_set(). + */ +uint32_t im_whitelist_set(pm_peer_id_t const *p_peers, uint32_t peer_cnt) +{ + /* Clear the cache of whitelisted peers. */ + memset(m_wlisted_peers, 0x00, sizeof(m_wlisted_peers)); + + if ((p_peers == NULL) || (peer_cnt == 0)) { + /* Clear the current whitelist. */ + m_wlisted_peer_cnt = 0; + + /* NRF_SUCCESS, or + * BLE_GAP_ERROR_WHITELIST_IN_USE + */ + return sd_ble_gap_whitelist_set(NULL, 0); + } + + /* Copy the new whitelisted peers. */ + m_wlisted_peer_cnt = peer_cnt; + memcpy(m_wlisted_peers, p_peers, sizeof(pm_peer_id_t) * peer_cnt); + + uint32_t ret; + uint32_t wlist_addr_cnt = 0; + + ble_gap_addr_t const *addr_ptrs[BLE_GAP_WHITELIST_ADDR_MAX_COUNT]; + ble_gap_addr_t addrs[BLE_GAP_WHITELIST_ADDR_MAX_COUNT]; + + memset(addrs, 0x00, sizeof(addrs)); + + /* Fetch GAP addresses for these peers, but don't fetch IRKs. */ + ret = peers_id_keys_get(p_peers, peer_cnt, addrs, &wlist_addr_cnt, NULL, NULL); + + if (ret != NRF_SUCCESS) { + /* NRF_ERROR_NOT_FOUND, if a peer or its data were not found. + * BLE_ERROR_GAP_INVALID_BLE_ADDR, if a peer address can not be used for + * whitelisting. + */ + return ret; + } + + for (uint32_t i = 0; i < BLE_GAP_WHITELIST_ADDR_MAX_COUNT; i++) { + addr_ptrs[i] = &addrs[i]; + } + + /* NRF_ERROR_DATA_SIZE, if peer_cnt > BLE_GAP_WHITELIST_ADDR_MAX_COUNT. + * BLE_ERROR_GAP_WHITELIST_IN_USE, if a whitelist is in use. + */ + return sd_ble_gap_whitelist_set(addr_ptrs, peer_cnt); +} + +/** + * @brief Function for calculating the ah() hash function described in Bluetooth core specification + * 4.2 section 3.H.2.2.2. + * + * @detail BLE uses a hash function to calculate the first half of a resolvable address + * from the second half of the address and an irk. This function will use the ECB + * periferal to hash these data according to the Bluetooth core specification. + * + * @note The ECB expect little endian input and output. + * This function expect big endian and will reverse the data as necessary. + * + * @param[in] p_k The key used in the hash function. + * For address resolution this is should be the irk. + * The array must have a length of 16. + * @param[in] p_r The rand used in the hash function. For generating a new address + * this would be a random number. For resolving a resolvable address + * this would be the last half of the address being resolved. + * The array must have a length of 3. + * @param[out] p_local_hash The result of the hash operation. For address resolution this + * will match the first half of the address being resolved if and only + * if the irk used in the hash function is the same one used to generate + * the address. + * The array must have a length of 16. + */ +void ah(uint8_t const *p_k, uint8_t const *p_r, uint8_t *p_local_hash) +{ + nrf_ecb_hal_data_t ecb_hal_data; + + for (uint32_t i = 0; i < SOC_ECB_KEY_LENGTH; i++) { + ecb_hal_data.key[i] = p_k[SOC_ECB_KEY_LENGTH - 1 - i]; + } + + memset(ecb_hal_data.cleartext, 0, SOC_ECB_KEY_LENGTH - IM_ADDR_CLEARTEXT_LENGTH); + + for (uint32_t i = 0; i < IM_ADDR_CLEARTEXT_LENGTH; i++) { + ecb_hal_data.cleartext[SOC_ECB_KEY_LENGTH - 1 - i] = p_r[i]; + } + + /* Can only return NRF_SUCCESS. */ + (void)sd_ecb_block_encrypt(&ecb_hal_data); + + for (uint32_t i = 0; i < IM_ADDR_CIPHERTEXT_LENGTH; i++) { + p_local_hash[i] = ecb_hal_data.ciphertext[SOC_ECB_KEY_LENGTH - 1 - i]; + } +} + +bool im_address_resolve(ble_gap_addr_t const *p_addr, ble_gap_irk_t const *p_irk) +{ + uint8_t hash[IM_ADDR_CIPHERTEXT_LENGTH]; + uint8_t local_hash[IM_ADDR_CIPHERTEXT_LENGTH]; + uint8_t prand[IM_ADDR_CLEARTEXT_LENGTH]; + + if (p_addr->addr_type != BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE) { + return false; + } + + memcpy(hash, p_addr->addr, IM_ADDR_CIPHERTEXT_LENGTH); + memcpy(prand, &p_addr->addr[IM_ADDR_CIPHERTEXT_LENGTH], IM_ADDR_CLEARTEXT_LENGTH); + ah(p_irk->irk, prand, local_hash); + + return (memcmp(hash, local_hash, IM_ADDR_CIPHERTEXT_LENGTH) == 0); +} diff --git a/lib/peer_manager/modules/nrf_ble_lesc.c b/lib/peer_manager/modules/nrf_ble_lesc.c new file mode 100644 index 0000000000..e14c504801 --- /dev/null +++ b/lib/peer_manager/modules/nrf_ble_lesc.c @@ -0,0 +1,430 @@ +/* + * Copyright (c) 2018-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +LOG_MODULE_DECLARE(peer_manager, CONFIG_PEER_MANAGER_LOG_LEVEL); + +/** @brief Descriptor of the peer public key. */ +typedef struct { + /** @brief Peer public key. Stored in little-endian. */ + uint8_t value[BLE_GAP_LESC_P256_PK_LEN]; + /** @brief Flag indicating that the public key has been requested to compute DH key. */ + bool is_requested; + /** @brief Flag indicating that the public key is valid. */ + bool is_valid; + /** @brief Flag indicating that the passkey key has been requested. */ + bool passkey_requested; + /** @brief Flag indicating that the passkey display event has been received. */ + bool passkey_displayed; +} nrf_ble_lesc_peer_pub_key_t; + +/** + * @brief The maximum number of peripheral and central connections combined. + * This value is based on what the SoftDevice handler module uses. + */ +#define NRF_BLE_LESC_LINK_COUNT CONFIG_NRF_SDH_BLE_TOTAL_LINK_COUNT + +/** LESC ECC Public Key. */ +__ALIGN(4) static ble_gap_lesc_p256_pk_t m_lesc_public_key; +/** LESC ECC DH Key. */ +__ALIGN(4) static ble_gap_lesc_dhkey_t m_lesc_dh_key; + +/** Flag indicating that the module encountered an internal error. */ +static bool m_ble_lesc_internal_error; +/** Flag indicating that the local ECDH key pair was generated. */ +static bool m_keypair_generated; + +/** ID of ECC private/public key pair. */ +static psa_key_id_t m_keypair_id; +/** Array of pointers to peer public keys, used for LESC DH generation. */ +static nrf_ble_lesc_peer_pub_key_t m_peer_keys[NRF_BLE_LESC_LINK_COUNT]; + +static bool m_lesc_oobd_own_generated; +/** LESC OOB data used in LESC OOB pairing mode. */ +static ble_gap_lesc_oob_data_t m_ble_lesc_oobd_own; +static nrf_ble_lesc_peer_oob_data_handler m_lesc_oobd_peer_handler; + +#define ECC_PUB_KEY_UNCOMPRESSED_FORMAT_MARKER 0x04 +#define ECC_PUB_KEY_EXPORT_SIZE \ + PSA_EXPORT_PUBLIC_KEY_OUTPUT_SIZE(PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1), 256) +#define COORD_SIZE (BLE_GAP_LESC_P256_PK_LEN / 2) + +/* Convert an ECC (secp256r1) public key from between big-endian and little-endian. + * The two coordinates are converted individually. + */ +static void ecc_public_key_byte_order_invert(const uint8_t *raw_key_in, uint8_t *raw_key_out) +{ + sys_memcpy_swap(raw_key_out, raw_key_in, COORD_SIZE); + sys_memcpy_swap(&raw_key_out[COORD_SIZE], &raw_key_in[COORD_SIZE], COORD_SIZE); +} + +uint32_t nrf_ble_lesc_init(void) +{ + psa_status_t status; + + memset((void *)m_peer_keys, 0, sizeof(m_peer_keys)); + + /* Ensure that psa_crypto has been initialized. */ + status = psa_crypto_init(); + if (status != PSA_SUCCESS) { + LOG_ERR("psa_crypto_init() returned status %d", status); + return NRF_ERROR_INTERNAL; + } + + LOG_DBG("Initialized nrf_ble_lesc."); + + /* Reset module state. */ + m_ble_lesc_internal_error = false; + m_keypair_generated = false; + + /* Generate ECC key pair. Only one key pair is automatically generated by this module. */ + return nrf_ble_lesc_keypair_generate(); +} + +uint32_t nrf_ble_lesc_keypair_generate(void) +{ + psa_status_t status; + uint8_t pub_key[ECC_PUB_KEY_EXPORT_SIZE]; + size_t pub_key_len = 0; + + /* Check if any DH computation is pending */ + for (uint16_t i = 0; i < ARRAY_SIZE(m_peer_keys); i++) { + if (m_peer_keys[i].is_valid) { + return NRF_ERROR_BUSY; + } + } + + /* Update flag to indicate that there is no valid private key. */ + m_keypair_generated = false; + m_lesc_oobd_own_generated = false; + + /* Destroy the previous key pair (if any), to free up memory. */ + status = psa_destroy_key(m_keypair_id); + if (status != PSA_SUCCESS && status != PSA_ERROR_INVALID_HANDLE) { + LOG_ERR("psa_destroy_key returned status %d", status); + } else { + m_keypair_id = 0; + } + + LOG_DBG("Generating ECC key pair"); + + psa_key_attributes_t key_attributes = PSA_KEY_ATTRIBUTES_INIT; + + psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_DERIVE); + psa_set_key_lifetime(&key_attributes, PSA_KEY_LIFETIME_VOLATILE); + psa_set_key_algorithm(&key_attributes, PSA_ALG_ECDH); + psa_set_key_type(&key_attributes, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); + psa_set_key_bits(&key_attributes, 256); + + status = psa_generate_key(&key_attributes, &m_keypair_id); + if (status != PSA_SUCCESS) { + LOG_ERR("psa_generate_key() returned status %d", status); + return NRF_ERROR_INTERNAL; + } + + /* Export the raw representation of the public key. */ + status = psa_export_public_key(m_keypair_id, pub_key, sizeof(pub_key), &pub_key_len); + if (status != PSA_SUCCESS) { + LOG_ERR("psa_export_public_key() returned status %d", status); + return NRF_ERROR_INTERNAL; + } + /* Convert from big-endian to little-endian. + * Drop the first byte indicating the serialization format. + */ + ecc_public_key_byte_order_invert(&pub_key[1], m_lesc_public_key.pk); + + /* Set the flag to indicate that there is a valid ECDH key pair generated. */ + m_keypair_generated = true; + + return NRF_SUCCESS; +} + +uint32_t nrf_ble_lesc_own_oob_data_generate(void) +{ + uint32_t err_code = NRF_ERROR_INVALID_STATE; + + m_lesc_oobd_own_generated = false; + + if (m_keypair_generated) { + err_code = sd_ble_gap_lesc_oob_data_get(BLE_CONN_HANDLE_INVALID, &m_lesc_public_key, + &m_ble_lesc_oobd_own); + if (err_code == NRF_SUCCESS) { + m_lesc_oobd_own_generated = true; + } + } + + return err_code; +} + +ble_gap_lesc_p256_pk_t *nrf_ble_lesc_public_key_get(void) +{ + ble_gap_lesc_p256_pk_t *p_lesc_pk = NULL; + + if (m_keypair_generated) { + p_lesc_pk = &m_lesc_public_key; + } else { + LOG_ERR("Trying to access LESC public key that has not been generated yet."); + } + + return p_lesc_pk; +} + +ble_gap_lesc_oob_data_t *nrf_ble_lesc_own_oob_data_get(void) +{ + ble_gap_lesc_oob_data_t *p_lesc_oobd_own = NULL; + + if (m_lesc_oobd_own_generated) { + p_lesc_oobd_own = &m_ble_lesc_oobd_own; + } else { + LOG_ERR("Trying to access LESC OOB data that have not been generated yet."); + } + + return p_lesc_oobd_own; +} + +void nrf_ble_lesc_peer_oob_data_handler_set(nrf_ble_lesc_peer_oob_data_handler handler) +{ + m_lesc_oobd_peer_handler = handler; +} + +/** + * @brief Function for calculating a DH key and responding to the DH key request on a given + * connection handle. + * + * @param[in] p_peer_public_key ECC peer public key, used to compute shared secret. + * @param[in] conn_handle Connection handle. + * + * @retval NRF_SUCCESS If the operation was successful. + * @retval NRF_ERROR_INTERNAL If @ref psa_raw_key_agreement, or @ref psa_generate_random failed. + * @returns Other error codes might be returned by @ref sd_ble_gap_lesc_dhkey_reply. + * functions. + */ +static uint32_t compute_and_give_dhkey(nrf_ble_lesc_peer_pub_key_t *p_peer_public_key, + uint16_t conn_handle) +{ + psa_status_t status = PSA_ERROR_BAD_STATE; + uint8_t *p_shared_secret = m_lesc_dh_key.key; + size_t shared_secret_size; + uint8_t pub_key[ECC_PUB_KEY_EXPORT_SIZE]; + + /* Check if there is a valid generated and set a local ECDH public key. */ + if (!m_keypair_generated) { + return NRF_ERROR_INTERNAL; + } + + /* Check if the public_key is valid */ + if (p_peer_public_key->is_valid) { + /* Add the uncompressed format marker. */ + pub_key[0] = ECC_PUB_KEY_UNCOMPRESSED_FORMAT_MARKER; + + /* Convert from little-endian to big-endian. */ + ecc_public_key_byte_order_invert(p_peer_public_key->value, &pub_key[1]); + + status = psa_raw_key_agreement(PSA_ALG_ECDH, m_keypair_id, + pub_key, sizeof(pub_key), + p_shared_secret, BLE_GAP_LESC_DHKEY_LEN, &shared_secret_size); + } + + if ((status == PSA_SUCCESS) && (shared_secret_size == BLE_GAP_LESC_DHKEY_LEN)) { + /* Convert secret from big-endian to little-endian. */ + sys_mem_swap(p_shared_secret, BLE_GAP_LESC_DHKEY_LEN); + } else { + LOG_ERR("psa_raw_key_agreement() returned status %d", status); + + LOG_WRN("Creating invalid shared secret to make LESC fail."); + status = psa_generate_random(p_shared_secret, BLE_GAP_LESC_DHKEY_LEN); + if (status != PSA_SUCCESS) { + LOG_ERR("psa_generate_random() returned status %d", status); + return NRF_ERROR_INTERNAL; + } + } + + LOG_INF("Calling sd_ble_gap_lesc_dhkey_reply on conn_handle: %d", conn_handle); + return sd_ble_gap_lesc_dhkey_reply(conn_handle, &m_lesc_dh_key); +} + +uint32_t nrf_ble_lesc_request_handler(void) +{ + uint32_t err_code = NRF_SUCCESS; + + /* If the LESC module is in an invalid state, a restart is required. */ + if (m_ble_lesc_internal_error) { + return NRF_ERROR_INTERNAL; + } + + for (uint16_t i = 0; i < NRF_BLE_LESC_LINK_COUNT; i++) { + if (m_peer_keys[i].is_requested) { + err_code = compute_and_give_dhkey(&m_peer_keys[i], i); + m_peer_keys[i].is_requested = false; + m_peer_keys[i].is_valid = false; + m_peer_keys[i].passkey_requested = false; + m_peer_keys[i].passkey_displayed = false; + + VERIFY_SUCCESS(err_code); + } + } + + return err_code; +} + +/** + * @brief Function for handling a DH key request event. + * + * @param[in] conn_handle Connection handle. + * @param[in] idx Data index assigned to the connection handle. + * @param[in] p_dhkey_request DH key request descriptor. + * + * @retval NRF_SUCCESS If the operation was successful. + * @returns Other error codes might be returned by @ref sd_ble_gap_auth_key_reply, and + * @ref sd_ble_gap_disconnect. + */ +static uint32_t on_dhkey_request(uint16_t conn_handle, int idx, + ble_gap_evt_lesc_dhkey_request_t const *p_dhkey_request) +{ + uint32_t err_code = NRF_SUCCESS; + const uint8_t *const p_public_raw = p_dhkey_request->p_pk_peer->pk; + + /* Don't allow to pair with remote peer which uses the same public key. + * Compare only X cordinate of the public key, bytes from 0 to 31. + */ + if (memcmp(m_lesc_public_key.pk, p_public_raw, BLE_GAP_LESC_P256_PK_LEN / 2) == 0) { + LOG_WRN("Remote peer is using identical public key."); + m_peer_keys[idx].is_valid = false; + + /* In case when we have gotten passkey requested then we will respond to it with the + * "NONE" key type to prevent us from going through Authentication Stage 1. + */ + if (m_peer_keys[idx].passkey_requested) { + m_peer_keys[idx].passkey_requested = false; + + err_code = sd_ble_gap_auth_key_reply(conn_handle, + BLE_GAP_AUTH_KEY_TYPE_NONE, NULL); + + return err_code; + + } + /* In case we have gotten passkey display event then we need to disconnect a link + * to prevent us from going through Authentication Stage 1. + */ + else if (m_peer_keys[idx].passkey_displayed) { + m_peer_keys[idx].passkey_displayed = false; + + err_code = sd_ble_gap_disconnect(conn_handle, + BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION); + if (err_code != NRF_SUCCESS && err_code != NRF_ERROR_INVALID_STATE) { + return err_code; + } + + return NRF_SUCCESS; + } + } else { + memcpy(m_peer_keys[idx].value, p_public_raw, BLE_GAP_LESC_P256_PK_LEN); + m_peer_keys[idx].is_valid = true; + } + + m_peer_keys[idx].is_requested = true; + + return NRF_SUCCESS; +} + +/** + * @brief Function for setting LESC OOB data. + * + * @param[in] conn_handle Connection handle. + * + * @retval NRF_SUCCESS If the operation was successful. + * @returns Other error codes might be returned by @ref sd_ble_gap_lesc_oob_data_set. + */ +static uint32_t lesc_oob_data_set(uint16_t conn_handle) +{ + uint32_t err_code; + ble_gap_lesc_oob_data_t *p_lesc_oobd_own; + ble_gap_lesc_oob_data_t *p_lesc_oobd_peer; + + p_lesc_oobd_own = (m_lesc_oobd_own_generated) ? &m_ble_lesc_oobd_own : NULL; + p_lesc_oobd_peer = + (m_lesc_oobd_peer_handler != NULL) ? m_lesc_oobd_peer_handler(conn_handle) : NULL; + + err_code = sd_ble_gap_lesc_oob_data_set(conn_handle, p_lesc_oobd_own, p_lesc_oobd_peer); + return err_code; +} + +void nrf_ble_lesc_on_ble_evt(ble_evt_t const *p_ble_evt) +{ + uint32_t err_code = NRF_SUCCESS; + const uint16_t conn_handle = p_ble_evt->evt.gap_evt.conn_handle; + const int idx = nrf_sdh_ble_idx_get(conn_handle); + + __ASSERT(idx >= 0, "Invalid idx %d for conn_handle %#x, evt_id %#x", + idx, conn_handle, p_ble_evt->header.evt_id); + + switch (p_ble_evt->header.evt_id) { + case BLE_GAP_EVT_DISCONNECTED: + m_peer_keys[idx].is_valid = false; + m_peer_keys[idx].is_requested = false; + m_peer_keys[idx].passkey_requested = false; + m_peer_keys[idx].passkey_displayed = false; + + break; + + case BLE_GAP_EVT_AUTH_KEY_REQUEST: + if (p_ble_evt->evt.gap_evt.params.auth_key_request.key_type == + BLE_GAP_AUTH_KEY_TYPE_PASSKEY) { + m_peer_keys[idx].passkey_requested = true; + } + + break; + + case BLE_GAP_EVT_PASSKEY_DISPLAY: + m_peer_keys[idx].passkey_displayed = true; + + break; + + case BLE_GAP_EVT_LESC_DHKEY_REQUEST: + LOG_DBG("BLE_GAP_EVT_LESC_DHKEY_REQUEST"); + + if (p_ble_evt->evt.gap_evt.params.lesc_dhkey_request.oobd_req) { + err_code = lesc_oob_data_set(conn_handle); + if (err_code != NRF_SUCCESS) { + LOG_ERR("sd_ble_gap_lesc_oob_data_set() returned error 0x%x.", + err_code); + m_ble_lesc_internal_error = true; + } + } + + err_code = on_dhkey_request(conn_handle, idx, + &p_ble_evt->evt.gap_evt.params.lesc_dhkey_request); + if (err_code != NRF_SUCCESS) { + m_ble_lesc_internal_error = true; + } + break; + +#if defined(CONFIG_PM_LESC_GENERATE_NEW_KEYS) + case BLE_GAP_EVT_AUTH_STATUS: + /* Generate new pairing keys. */ + err_code = nrf_ble_lesc_keypair_generate(); + if (err_code != NRF_SUCCESS) { + m_ble_lesc_internal_error = true; + } + + break; +#endif /* CONFIG_PM_LESC_GENERATE_NEW_KEYS */ + + default: + break; + } +} diff --git a/lib/peer_manager/modules/peer_data_storage.c b/lib/peer_manager/modules/peer_data_storage.c new file mode 100644 index 0000000000..2e19f22c3c --- /dev/null +++ b/lib/peer_manager/modules/peer_data_storage.c @@ -0,0 +1,541 @@ +/* + * Copyright (c) 2015-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(peer_manager, CONFIG_PEER_MANAGER_LOG_LEVEL); + +/* Macro for verifying that the peer id is within a valid range. */ +#define VERIFY_PEER_ID_IN_RANGE(id) \ + VERIFY_FALSE((id >= PM_PEER_ID_N_AVAILABLE_IDS), NRF_ERROR_INVALID_PARAM) + +/* Macro for verifying that the peer data id is within a valid range. */ +#define VERIFY_PEER_DATA_ID_IN_RANGE(id) \ + VERIFY_TRUE(peer_data_id_is_valid(id), NRF_ERROR_INVALID_PARAM) + +/* The number of registered event handlers. */ +#define PDS_EVENT_HANDLERS_CNT ARRAY_SIZE(m_evt_handlers) + +/* Peer Data Storage event handler in Peer Database. */ +extern void pdb_pds_evt_handler(pm_evt_t *evt); + +/* Peer Data Storage events' handlers. + * The number of elements in this array is PDS_EVENT_HANDLERS_CNT. + */ +static pm_evt_handler_internal_t const m_evt_handlers[] = { + pdb_pds_evt_handler, +}; + +static bool m_module_initialized; +static volatile bool m_peer_delete_deferred; + +/* A token used for Flash Data Storage searches. */ +static fds_find_token_t m_fds_ftok; + +/* Function for dispatching events to all registered event handlers. */ +static void pds_evt_send(pm_evt_t *p_event) +{ + p_event->conn_handle = BLE_CONN_HANDLE_INVALID; + + for (uint32_t i = 0; i < PDS_EVENT_HANDLERS_CNT; i++) { + m_evt_handlers[i](p_event); + } +} + +/* Function to convert peer IDs to file IDs. */ +static uint16_t peer_id_to_file_id(pm_peer_id_t peer_id) +{ + return (uint16_t)(peer_id + PEER_ID_TO_FILE_ID); +} + +/* Function to convert peer data id to type id. */ +static pm_peer_id_t file_id_to_peer_id(uint16_t file_id) +{ + return (pm_peer_id_t)(file_id + FILE_ID_TO_PEER_ID); +} + +/* Function to convert peer data IDs to record keys. */ +static uint16_t peer_data_id_to_record_key(pm_peer_data_id_t peer_data_id) +{ + return (uint16_t)(peer_data_id + DATA_ID_TO_RECORD_KEY); +} + +/* Function to convert record keys to peer data IDs. */ +static pm_peer_data_id_t record_key_to_peer_data_id(uint16_t record_key) +{ + return (pm_peer_data_id_t)(record_key + RECORD_KEY_TO_DATA_ID); +} + +/* Function for checking whether a file ID is relevant for the Peer Manager. */ +static bool file_id_within_pm_range(uint16_t file_id) +{ + return ((PDS_FIRST_RESERVED_FILE_ID <= file_id) && (file_id <= PDS_LAST_RESERVED_FILE_ID)); +} + +/* Function for checking whether a record key is relevant for the Peer Manager. */ +static bool record_key_within_pm_range(uint16_t record_key) +{ + return ((PDS_FIRST_RESERVED_RECORD_KEY <= record_key) && + (record_key <= PDS_LAST_RESERVED_RECORD_KEY)); +} + +static bool peer_data_id_is_valid(pm_peer_data_id_t data_id) +{ + return ((data_id == PM_PEER_DATA_ID_BONDING) || + (data_id == PM_PEER_DATA_ID_SERVICE_CHANGED_PENDING) || + (data_id == PM_PEER_DATA_ID_GATT_LOCAL) || + (data_id == PM_PEER_DATA_ID_GATT_REMOTE) || + (data_id == PM_PEER_DATA_ID_PEER_RANK) || + (data_id == PM_PEER_DATA_ID_CENTRAL_ADDR_RES) || + (data_id == PM_PEER_DATA_ID_APPLICATION)); +} + +/** + * @brief Function for sending a PM_EVT_ERROR_UNEXPECTED event. + * + * @param[in] peer_id The peer the event pertains to. + * @param[in] err_code The unexpected error that occurred. + */ +static void send_unexpected_error(pm_peer_id_t peer_id, uint32_t err_code) +{ + pm_evt_t error_evt = {.evt_id = PM_EVT_ERROR_UNEXPECTED, + .peer_id = peer_id, + .params = {.error_unexpected = { + .error = err_code, + }}}; + pds_evt_send(&error_evt); +} + +/* Function for deleting all data beloning to a peer. + * These operations will be sent to FDS one at a time. + */ +static void peer_data_delete_process(void) +{ + uint32_t ret; + pm_peer_id_t peer_id; + uint16_t file_id; + fds_record_desc_t desc; + fds_find_token_t ftok; + + m_peer_delete_deferred = false; + + memset(&ftok, 0x00, sizeof(fds_find_token_t)); + peer_id = peer_id_get_next_deleted(PM_PEER_ID_INVALID); + + while ((peer_id != PM_PEER_ID_INVALID) && + (fds_record_find_in_file(peer_id_to_file_id(peer_id), &desc, &ftok) == + FDS_ERR_NOT_FOUND)) { + peer_id_free(peer_id); + peer_id = peer_id_get_next_deleted(peer_id); + } + + if (peer_id != PM_PEER_ID_INVALID) { + file_id = peer_id_to_file_id(peer_id); + ret = fds_file_delete(file_id); + + if (ret == FDS_ERR_NO_SPACE_IN_QUEUES) { + m_peer_delete_deferred = true; + } else if (ret != NRF_SUCCESS) { + LOG_ERR("Could not delete peer data. fds_file_delete() returned 0x%x " + "for peer_id: %d", + ret, peer_id); + send_unexpected_error(peer_id, ret); + } + } +} + +static uint32_t peer_data_find(pm_peer_id_t peer_id, pm_peer_data_id_t data_id, + fds_record_desc_t *const p_desc) +{ + uint32_t ret; + fds_find_token_t ftok; + + NRF_PM_DEBUG_CHECK(peer_id < PM_PEER_ID_N_AVAILABLE_IDS); + NRF_PM_DEBUG_CHECK(peer_data_id_is_valid(data_id)); + NRF_PM_DEBUG_CHECK(p_desc != NULL); + + memset(&ftok, 0x00, sizeof(fds_find_token_t)); + + uint16_t file_id = peer_id_to_file_id(peer_id); + uint16_t record_key = peer_data_id_to_record_key(data_id); + + ret = fds_record_find(file_id, record_key, p_desc, &ftok); + + if (ret != NRF_SUCCESS) { + return NRF_ERROR_NOT_FOUND; + } + + return NRF_SUCCESS; +} + +static void peer_ids_load(void) +{ + fds_record_desc_t record_desc; + fds_flash_record_t record; + fds_find_token_t ftok; + + memset(&ftok, 0x00, sizeof(fds_find_token_t)); + + uint16_t const record_key = peer_data_id_to_record_key(PM_PEER_DATA_ID_BONDING); + + while (fds_record_find_by_key(record_key, &record_desc, &ftok) == NRF_SUCCESS) { + pm_peer_id_t peer_id; + + /* It is safe to ignore the return value since the descriptor was + * just obtained and also 'record' is different from NULL. + */ + (void)fds_record_open(&record_desc, &record); + peer_id = file_id_to_peer_id(record.p_header->file_id); + (void)fds_record_close(&record_desc); + + (void)peer_id_allocate(peer_id); + } +} + +static void fds_evt_handler(fds_evt_t const *const p_fds_evt) +{ + pm_evt_t pds_evt = {.peer_id = file_id_to_peer_id(p_fds_evt->write.file_id)}; + + switch (p_fds_evt->id) { + case FDS_EVT_WRITE: + case FDS_EVT_UPDATE: + case FDS_EVT_DEL_RECORD: + if (file_id_within_pm_range(p_fds_evt->write.file_id) || + record_key_within_pm_range(p_fds_evt->write.record_key)) { + pds_evt.params.peer_data_update_succeeded.data_id = + record_key_to_peer_data_id(p_fds_evt->write.record_key); + pds_evt.params.peer_data_update_succeeded.action = + (p_fds_evt->id == FDS_EVT_DEL_RECORD) ? PM_PEER_DATA_OP_DELETE + : PM_PEER_DATA_OP_UPDATE; + pds_evt.params.peer_data_update_succeeded.token = + p_fds_evt->write.record_id; + + if (p_fds_evt->result == NRF_SUCCESS) { + pds_evt.evt_id = PM_EVT_PEER_DATA_UPDATE_SUCCEEDED; + pds_evt.params.peer_data_update_succeeded.flash_changed = true; + } else { + pds_evt.evt_id = PM_EVT_PEER_DATA_UPDATE_FAILED; + pds_evt.params.peer_data_update_failed.error = p_fds_evt->result; + } + + pds_evt_send(&pds_evt); + } + break; + + case FDS_EVT_DEL_FILE: + if (file_id_within_pm_range(p_fds_evt->del.file_id) && + (p_fds_evt->del.record_key == FDS_RECORD_KEY_DIRTY)) { + if (p_fds_evt->result == NRF_SUCCESS) { + pds_evt.evt_id = PM_EVT_PEER_DELETE_SUCCEEDED; + peer_id_free(pds_evt.peer_id); + } else { + pds_evt.evt_id = PM_EVT_PEER_DELETE_FAILED; + pds_evt.params.peer_delete_failed.error = p_fds_evt->result; + } + + /* Trigger remaining deletes. */ + m_peer_delete_deferred = true; + + pds_evt_send(&pds_evt); + } + break; + + case FDS_EVT_GC: + if (p_fds_evt->result == NRF_SUCCESS) { + pds_evt.evt_id = PM_EVT_FLASH_GARBAGE_COLLECTED; + } else { + pds_evt.evt_id = PM_EVT_FLASH_GARBAGE_COLLECTION_FAILED; + pds_evt.params.garbage_collection_failed.error = p_fds_evt->result; + } + pds_evt.peer_id = PM_PEER_ID_INVALID; + pds_evt_send(&pds_evt); + break; + + default: + /* No action. */ + break; + } + + if (m_peer_delete_deferred) { + peer_data_delete_process(); + } +} + +uint32_t pds_init(void) +{ + uint32_t ret; + + /* Check for re-initialization if debugging. */ + NRF_PM_DEBUG_CHECK(!m_module_initialized); + + ret = fds_register(fds_evt_handler); + if (ret != NRF_SUCCESS) { + LOG_ERR("Could not initialize flash storage. fds_register() returned 0x%x.", ret); + return NRF_ERROR_INTERNAL; + } + + ret = fds_init(); + if (ret != NRF_SUCCESS) { + LOG_ERR("Could not initialize flash storage. fds_init() returned 0x%x.", ret); + return NRF_ERROR_RESOURCES; + } + + peer_id_init(); + peer_ids_load(); + + m_module_initialized = true; + + return NRF_SUCCESS; +} + +uint32_t pds_peer_data_read(pm_peer_id_t peer_id, pm_peer_data_id_t data_id, + pm_peer_data_t *const p_data, uint32_t const *const p_buf_len) +{ + uint32_t ret; + fds_record_desc_t rec_desc; + fds_flash_record_t rec_flash; + + NRF_PM_DEBUG_CHECK(m_module_initialized); + NRF_PM_DEBUG_CHECK(p_data != NULL); + + VERIFY_PEER_ID_IN_RANGE(peer_id); + VERIFY_PEER_DATA_ID_IN_RANGE(data_id); + + ret = peer_data_find(peer_id, data_id, &rec_desc); + + if (ret != NRF_SUCCESS) { + return NRF_ERROR_NOT_FOUND; + } + + /* Shouldn't fail, unless the record was deleted in the meanwhile or the CRC check has + * failed. + */ + ret = fds_record_open(&rec_desc, &rec_flash); + + if (ret != NRF_SUCCESS) { + return NRF_ERROR_NOT_FOUND; + } + + p_data->data_id = data_id; + p_data->length_words = rec_flash.p_header->length_words; + + /* If p_buf_len is NULL, provide a pointer to data in flash, otherwise, + * check that the buffer is large enough and copy the data in flash into the buffer. + */ + if (p_buf_len == NULL) { + /* The cast is necessary because if no buffer is provided, we just copy the pointer, + * but in that case it should be considered a pointer to const data by the caller, + * since it is a pointer to data in flash. + */ + p_data->p_all_data = (void *)rec_flash.p_data; + } else { + uint32_t const data_len_bytes = (p_data->length_words * sizeof(uint32_t)); + uint32_t const copy_len_bytes = + MIN((*p_buf_len), (p_data->length_words * sizeof(uint32_t))); + + memcpy(p_data->p_all_data, rec_flash.p_data, copy_len_bytes); + + if (copy_len_bytes < data_len_bytes) { + return NRF_ERROR_DATA_SIZE; + } + } + + /* Shouldn't fail unless the record was already closed, in which case it can be ignored. */ + (void)fds_record_close(&rec_desc); + + return NRF_SUCCESS; +} + +void pds_peer_data_iterate_prepare(void) +{ + memset(&m_fds_ftok, 0x00, sizeof(fds_find_token_t)); +} + +bool pds_peer_data_iterate(pm_peer_data_id_t data_id, pm_peer_id_t *const p_peer_id, + pm_peer_data_flash_t *const p_data) +{ + uint32_t ret; + uint16_t rec_key; + fds_record_desc_t rec_desc; + fds_flash_record_t rec_flash; + + NRF_PM_DEBUG_CHECK(m_module_initialized); + NRF_PM_DEBUG_CHECK(p_peer_id != NULL); + NRF_PM_DEBUG_CHECK(p_data != NULL); + + VERIFY_PEER_DATA_ID_IN_RANGE(data_id); + + rec_key = peer_data_id_to_record_key(data_id); + + if (fds_record_find_by_key(rec_key, &rec_desc, &m_fds_ftok) != NRF_SUCCESS) { + return false; + } + + ret = fds_record_open(&rec_desc, &rec_flash); + + if (ret != NRF_SUCCESS) { + /* It can only happen if the record was deleted after the call to + * fds_record_find_by_key(), before we could open it, or if CRC support was enabled + * in Flash Data Storage at compile time and the CRC check failed. + */ + return false; + } + + p_data->data_id = data_id; + p_data->length_words = rec_flash.p_header->length_words; + p_data->p_all_data = rec_flash.p_data; + + *p_peer_id = file_id_to_peer_id(rec_flash.p_header->file_id); + + (void)fds_record_close(&rec_desc); + + return true; +} + +uint32_t pds_peer_data_store(pm_peer_id_t peer_id, pm_peer_data_const_t const *p_peer_data, + pm_store_token_t *p_store_token) +{ + uint32_t ret; + fds_record_t rec; + fds_record_desc_t rec_desc; + + NRF_PM_DEBUG_CHECK(m_module_initialized); + NRF_PM_DEBUG_CHECK(p_peer_data != NULL); + + VERIFY_PEER_ID_IN_RANGE(peer_id); + VERIFY_PEER_DATA_ID_IN_RANGE(p_peer_data->data_id); + + /* Prepare the record to be stored in flash. */ + rec.file_id = peer_id_to_file_id(peer_id); + rec.key = peer_data_id_to_record_key(p_peer_data->data_id); + rec.data.p_data = (void *)p_peer_data->p_all_data; + rec.data.length_words = p_peer_data->length_words; + + ret = peer_data_find(peer_id, p_peer_data->data_id, &rec_desc); + + if (ret == NRF_ERROR_NOT_FOUND) { + ret = fds_record_write(&rec_desc, &rec); + } else { + /* Update existing record. */ + ret = fds_record_update(&rec_desc, &rec); + } + + switch (ret) { + case NRF_SUCCESS: + if (p_store_token != NULL) { + /* Update the store token. */ + (void)fds_record_id_from_desc(&rec_desc, (uint32_t *)p_store_token); + } + return NRF_SUCCESS; + + case FDS_ERR_BUSY: + case FDS_ERR_NO_SPACE_IN_QUEUES: + return NRF_ERROR_BUSY; + + case FDS_ERR_NO_SPACE_IN_FLASH: + return NRF_ERROR_RESOURCES; + + case FDS_ERR_UNALIGNED_ADDR: + return NRF_ERROR_INVALID_ADDR; + + default: + LOG_ERR("Could not write data to flash. fds_record_{write|update}() returned 0x%x. " + "peer_id: %d", + ret, peer_id); + return NRF_ERROR_INTERNAL; + } +} + +uint32_t pds_peer_data_delete(pm_peer_id_t peer_id, pm_peer_data_id_t data_id) +{ + uint32_t ret; + fds_record_desc_t record_desc; + + NRF_PM_DEBUG_CHECK(m_module_initialized); + + VERIFY_PEER_ID_IN_RANGE(peer_id); + VERIFY_PEER_DATA_ID_IN_RANGE(data_id); + + ret = peer_data_find(peer_id, data_id, &record_desc); + + if (ret != NRF_SUCCESS) { + return NRF_ERROR_NOT_FOUND; + } + + ret = fds_record_delete(&record_desc); + + switch (ret) { + case NRF_SUCCESS: + return NRF_SUCCESS; + + case FDS_ERR_NO_SPACE_IN_QUEUES: + return NRF_ERROR_BUSY; + + default: + LOG_ERR("Could not delete peer. fds_record_delete() returned 0x%x. peer_id: %d, " + "data_id: %d.", + ret, peer_id, data_id); + return NRF_ERROR_INTERNAL; + } +} + +pm_peer_id_t pds_peer_id_allocate(void) +{ + NRF_PM_DEBUG_CHECK(m_module_initialized); + return peer_id_allocate(PM_PEER_ID_INVALID); +} + +uint32_t pds_peer_id_free(pm_peer_id_t peer_id) +{ + NRF_PM_DEBUG_CHECK(m_module_initialized); + VERIFY_PEER_ID_IN_RANGE(peer_id); + + (void)peer_id_delete(peer_id); + peer_data_delete_process(); + + return NRF_SUCCESS; +} + +bool pds_peer_id_is_allocated(pm_peer_id_t peer_id) +{ + NRF_PM_DEBUG_CHECK(m_module_initialized); + return peer_id_is_allocated(peer_id); +} + +bool pds_peer_id_is_deleted(pm_peer_id_t peer_id) +{ + NRF_PM_DEBUG_CHECK(m_module_initialized); + return peer_id_is_deleted(peer_id); +} + +pm_peer_id_t pds_next_peer_id_get(pm_peer_id_t prev_peer_id) +{ + NRF_PM_DEBUG_CHECK(m_module_initialized); + return peer_id_get_next_used(prev_peer_id); +} + +pm_peer_id_t pds_next_deleted_peer_id_get(pm_peer_id_t prev_peer_id) +{ + NRF_PM_DEBUG_CHECK(m_module_initialized); + return peer_id_get_next_deleted(prev_peer_id); +} + +uint32_t pds_peer_count_get(void) +{ + NRF_PM_DEBUG_CHECK(m_module_initialized); + return peer_id_n_ids(); +} diff --git a/lib/peer_manager/modules/peer_database.c b/lib/peer_manager/modules/peer_database.c new file mode 100644 index 0000000000..cf701ce5c2 --- /dev/null +++ b/lib/peer_manager/modules/peer_database.c @@ -0,0 +1,647 @@ +/* + * Copyright (c) 2015-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(peer_manager, CONFIG_PEER_MANAGER_LOG_LEVEL); + +/** + * @brief Macro for verifying that the data ID is among the values eligible for using the write + * buffer. + * + * @param[in] data_id The data ID to verify. + */ +#define VERIFY_DATA_ID_WRITE_BUF(data_id) \ + do { \ + if (((data_id) != PM_PEER_DATA_ID_BONDING) && \ + ((data_id) != PM_PEER_DATA_ID_GATT_LOCAL)) { \ + return NRF_ERROR_INVALID_PARAM; \ + } \ + } while (0) + +/* The number of registered event handlers. */ +#define PDB_EVENT_HANDLERS_CNT ARRAY_SIZE(m_evt_handlers) + +/* Peer Database event handlers in other Peer Manager submodules. */ +extern void pm_pdb_evt_handler(pm_evt_t *p_event); +extern void sm_pdb_evt_handler(pm_evt_t *p_event); +#if !defined(CONFIG_PM_SERVICE_CHANGED_ENABLED) || (CONFIG_PM_SERVICE_CHANGED_ENABLED == 1) +extern void gscm_pdb_evt_handler(pm_evt_t *p_event); +#endif +extern void gcm_pdb_evt_handler(pm_evt_t *p_event); + +/** + * @brief Peer Database events' handlers. + * The number of elements in this array is PDB_EVENT_HANDLERS_CNT. + */ +static pm_evt_handler_internal_t const m_evt_handlers[] = { + pm_pdb_evt_handler, + sm_pdb_evt_handler, +#if !defined(CONFIG_PM_SERVICE_CHANGED_ENABLED) || (CONFIG_PM_SERVICE_CHANGED_ENABLED == 1) + gscm_pdb_evt_handler, +#endif + gcm_pdb_evt_handler, +}; + +/** + * @brief Struct for keeping track of one write buffer, from allocation, until it is fully written + * or cancelled. + */ +typedef struct { + /** @brief The peer ID this buffer belongs to. */ + pm_peer_id_t peer_id; + /** @brief The data ID this buffer belongs to. */ + pm_peer_data_id_t data_id; + /** + * @brief Token given by Peer Data Storage when a flash write has been + * successfully requested. This is used as the check for whether such + * an operation has been successfully requested. + */ + pm_store_token_t store_token; + /** @brief The number of buffer blocks containing peer data. */ + uint8_t n_bufs; + /** @brief The index of the first (or only) buffer block containing peer data. */ + uint8_t buffer_block_id; + /** + * @brief Flag indicating that the buffer was attempted written to + * flash, but a flash full error was returned and the operation + * should be retried after room has been made. + */ + uint8_t store_flash_full: 1; + /** + * @brief Flag indicating that the buffer was attempted written to flash, + * but a busy error was returned and the operation should be retried. + */ + uint8_t store_busy: 1; +} pdb_buffer_record_t; + +static bool m_module_initialized; +/** @brief The internal states of the write buffer. */ +static pm_buffer_t m_write_buffer; +/** @brief The available write buffer records. */ +static pdb_buffer_record_t m_write_buffer_records[CONFIG_PM_FLASH_BUFFERS]; +/** + * @brief Whether there are any pending (Not yet successfully requested in Peer Data + * Storage) store operations. This flag is for convenience only. The real bookkeeping + * is in the records (@ref m_write_buffer_records). + */ +static bool m_pending_store; + +/** + * @brief Function for invalidating a record of a write buffer allocation. + * + * @param[in] p_record The record to invalidate. + */ +static void write_buffer_record_invalidate(pdb_buffer_record_t *p_record) +{ + p_record->peer_id = PM_PEER_ID_INVALID; + p_record->data_id = PM_PEER_DATA_ID_INVALID; + p_record->buffer_block_id = PM_BUFFER_INVALID_ID; + p_record->store_busy = false; + p_record->store_flash_full = false; + p_record->n_bufs = 0; + p_record->store_token = PM_STORE_TOKEN_INVALID; +} + +/** + * @brief Function for finding a record of a write buffer allocation. + * + * @param[in] peer_id The peer ID in the record. + * @param[inout] p_index In: The starting index, out: The index of the record + * + * @return A pointer to the matching record, or NULL if none was found. + */ +static pdb_buffer_record_t *write_buffer_record_find_next(pm_peer_id_t peer_id, uint32_t *p_index) +{ + for (uint32_t i = *p_index; i < CONFIG_PM_FLASH_BUFFERS; i++) { + if (m_write_buffer_records[i].peer_id == peer_id) { + *p_index = i; + return &m_write_buffer_records[i]; + } + } + return NULL; +} + +/** + * @brief Function for finding a record of a write buffer allocation. + * + * @param[in] peer_id The peer ID in the record. + * @param[in] data_id The data ID in the record. + * + * @return A pointer to the matching record, or NULL if none was found. + */ +static pdb_buffer_record_t *write_buffer_record_find(pm_peer_id_t peer_id, + pm_peer_data_id_t data_id) +{ + uint32_t index = 0; + pdb_buffer_record_t *p_record = write_buffer_record_find_next(peer_id, &index); + + while ((p_record != NULL) && ((p_record->data_id != data_id) || (p_record->store_busy) || + (p_record->store_flash_full) || + (p_record->store_token != PM_STORE_TOKEN_INVALID))) { + index++; + p_record = write_buffer_record_find_next(peer_id, &index); + } + + return p_record; +} + +/** + * @brief Function for finding a record of a write buffer allocation that has been sent to be + * stored. + * + * @param[in] store_token The store token received when store was called for the record. + * + * @return A pointer to the matching record, or NULL if none was found. + */ +static pdb_buffer_record_t *write_buffer_record_find_stored(pm_store_token_t store_token) +{ + for (int i = 0; i < CONFIG_PM_FLASH_BUFFERS; i++) { + if (m_write_buffer_records[i].store_token == store_token) { + return &m_write_buffer_records[i]; + } + } + return NULL; +} + +/** + * @brief Function for finding an available record for write buffer allocation. + * + * @return A pointer to the available record, or NULL if none was found. + */ +static pdb_buffer_record_t *write_buffer_record_find_unused(void) +{ + return write_buffer_record_find(PM_PEER_ID_INVALID, PM_PEER_DATA_ID_INVALID); +} + +/** + * @brief Function for gracefully deactivating a write buffer record. + * + * @details This function will first release any buffers, then invalidate the record. + * + * @param[inout] p_write_buffer_record The record to release. + * + * @return A pointer to the matching record, or NULL if none was found. + */ +static void write_buffer_record_release(pdb_buffer_record_t *p_write_buffer_record) +{ + for (uint32_t i = 0; i < p_write_buffer_record->n_bufs; i++) { + pm_buffer_release(&m_write_buffer, p_write_buffer_record->buffer_block_id + i); + } + + write_buffer_record_invalidate(p_write_buffer_record); +} + +/** + * @brief Function for claiming and activating a write buffer record. + * + * @param[out] pp_write_buffer_record The claimed record. + * @param[in] peer_id The peer ID this record should have. + * @param[in] data_id The data ID this record should have. + */ +static void write_buffer_record_acquire(pdb_buffer_record_t **pp_write_buffer_record, + pm_peer_id_t peer_id, pm_peer_data_id_t data_id) +{ + if (pp_write_buffer_record == NULL) { + return; + } + *pp_write_buffer_record = write_buffer_record_find_unused(); + if (*pp_write_buffer_record == NULL) { + /* This also means the buffer is full. */ + return; + } + (*pp_write_buffer_record)->peer_id = peer_id; + (*pp_write_buffer_record)->data_id = data_id; +} + +/** + * @brief Function for dispatching outbound events to all registered event handlers. + * + * @param[in] p_event The event to dispatch. + */ +static void pdb_evt_send(pm_evt_t *p_event) +{ + for (uint32_t i = 0; i < PDB_EVENT_HANDLERS_CNT; i++) { + m_evt_handlers[i](p_event); + } +} + +/** + * @brief Function for resetting the internal state of the Peer Database module. + * + * @param[out] p_event The event to dispatch. + */ +static void internal_state_reset(void) +{ + for (uint32_t i = 0; i < CONFIG_PM_FLASH_BUFFERS; i++) { + write_buffer_record_invalidate(&m_write_buffer_records[i]); + } +} + +static void peer_data_point_to_buffer(pm_peer_data_t *p_peer_data, pm_peer_data_id_t data_id, + uint8_t *p_buffer_memory, uint16_t n_bufs) +{ + uint16_t n_bytes = n_bufs * PDB_WRITE_BUF_SIZE; + + p_peer_data->data_id = data_id; + + p_peer_data->p_all_data = (pm_peer_data_bonding_t *)p_buffer_memory; + p_peer_data->length_words = BYTES_TO_WORDS(n_bytes); +} + +static void peer_data_const_point_to_buffer(pm_peer_data_const_t *p_peer_data, + pm_peer_data_id_t data_id, uint8_t *p_buffer_memory, + uint32_t n_bufs) +{ + peer_data_point_to_buffer((pm_peer_data_t *)p_peer_data, data_id, p_buffer_memory, n_bufs); +} + +static void write_buf_length_words_set(pm_peer_data_const_t *p_peer_data) +{ + switch (p_peer_data->data_id) { + case PM_PEER_DATA_ID_BONDING: + p_peer_data->length_words = PM_BONDING_DATA_N_WORDS(); + break; + case PM_PEER_DATA_ID_SERVICE_CHANGED_PENDING: + p_peer_data->length_words = PM_SC_STATE_N_WORDS(); + break; + case PM_PEER_DATA_ID_PEER_RANK: + p_peer_data->length_words = PM_USAGE_INDEX_N_WORDS(); + break; + case PM_PEER_DATA_ID_GATT_LOCAL: + p_peer_data->length_words = PM_LOCAL_DB_N_WORDS(p_peer_data->p_local_gatt_db->len); + break; + default: + /* No action needed. */ + break; + } +} + +/** + * @brief Function for writing data into persistent storage. Writing happens asynchronously. + * + * @note This will unlock the data after it has been written. + * + * @param[in] p_write_buffer_record The write buffer record to write into persistent storage. + * + * @retval NRF_SUCCESS Data storing was successfully started. + * @retval NRF_ERROR_RESOURCES No space available in persistent storage. Please clear some + * space, the operation will be reattempted after the next compress + * procedure. + * @retval NRF_ERROR_INVALID_PARAM Data ID was invalid. + * @retval NRF_ERROR_INVALID_STATE Module is not initialized. + * @retval NRF_ERROR_INTERNAL Unexpected internal error. + */ +uint32_t write_buf_store(pdb_buffer_record_t *p_write_buffer_record) +{ + uint32_t err_code = NRF_SUCCESS; + pm_peer_data_const_t peer_data = {.data_id = p_write_buffer_record->data_id}; + uint8_t *p_buffer_memory; + + p_buffer_memory = + pm_buffer_ptr_get(&m_write_buffer, p_write_buffer_record->buffer_block_id); + if (p_buffer_memory == NULL) { + LOG_ERR("pm_buffer_ptr_get() could not retrieve RAM buffer. block_id: %d", + p_write_buffer_record->buffer_block_id); + return NRF_ERROR_INTERNAL; + } + + peer_data_const_point_to_buffer(&peer_data, p_write_buffer_record->data_id, p_buffer_memory, + p_write_buffer_record->n_bufs); + write_buf_length_words_set(&peer_data); + + err_code = pds_peer_data_store(p_write_buffer_record->peer_id, &peer_data, + &p_write_buffer_record->store_token); + + switch (err_code) { + case NRF_SUCCESS: + p_write_buffer_record->store_busy = false; + p_write_buffer_record->store_flash_full = false; + break; + + case NRF_ERROR_BUSY: + p_write_buffer_record->store_busy = true; + p_write_buffer_record->store_flash_full = false; + m_pending_store = true; + + err_code = NRF_SUCCESS; + break; + + case NRF_ERROR_RESOURCES: + p_write_buffer_record->store_busy = false; + p_write_buffer_record->store_flash_full = true; + m_pending_store = true; + break; + + case NRF_ERROR_INVALID_PARAM: + /* No action. */ + break; + + default: + LOG_ERR("pds_peer_data_store() returned %s. peer_id: %d", + nrf_strerror_get(err_code), p_write_buffer_record->peer_id); + err_code = NRF_ERROR_INTERNAL; + break; + } + + return err_code; +} + +/** + * @brief This calls @ref write_buf_store and sends events based on the return value. + * + * See @ref write_buf_store for more info. + * + * @return Whether or not the store operation succeeded. + */ +static bool write_buf_store_in_event(pdb_buffer_record_t *p_write_buffer_record) +{ + uint32_t err_code; + pm_evt_t event; + + err_code = write_buf_store(p_write_buffer_record); + if (err_code != NRF_SUCCESS) { + event.conn_handle = BLE_CONN_HANDLE_INVALID; + event.peer_id = p_write_buffer_record->peer_id; + + if (err_code == NRF_ERROR_RESOURCES) { + event.evt_id = PM_EVT_STORAGE_FULL; + } else { + event.evt_id = PM_EVT_ERROR_UNEXPECTED; + event.params.error_unexpected.error = err_code; + + LOG_ERR("Some peer data was not properly written to flash. " + "write_buf_store() " + "returned %s for peer_id: %d", + nrf_strerror_get(err_code), p_write_buffer_record->peer_id); + } + + pdb_evt_send(&event); + + return false; + } + + return true; +} + +/** + * @brief This reattempts store operations on write buffers, that previously failed because of @ref + * NRF_ERROR_BUSY or @ref NRF_ERROR_RESOURCES errors. + * + * param[in] retry_flash_full Whether to retry operations that failed because of an + * @ref NRF_ERROR_RESOURCES error. + */ +static void reattempt_previous_operations(bool retry_flash_full) +{ + if (!m_pending_store) { + return; + } + + m_pending_store = false; + + for (uint32_t i = 0; i < CONFIG_PM_FLASH_BUFFERS; i++) { + if ((m_write_buffer_records[i].store_busy) || + (m_write_buffer_records[i].store_flash_full)) { + m_pending_store = true; + + if (m_write_buffer_records[i].store_busy || retry_flash_full) { + if (!write_buf_store_in_event(&m_write_buffer_records[i])) { + return; + } + } + } + } +} + +/** + * @brief Function for handling events from the Peer Data Storage module. + * This function is extern in Peer Data Storage. + * + * @param[in] p_event The event to handle. + */ +void pdb_pds_evt_handler(pm_evt_t *p_event) +{ + pdb_buffer_record_t *p_write_buffer_record; + bool evt_send = true; + bool retry_flash_full = false; + + p_write_buffer_record = + write_buffer_record_find_stored(p_event->params.peer_data_update_succeeded.token); + + switch (p_event->evt_id) { + case PM_EVT_PEER_DATA_UPDATE_SUCCEEDED: + if ((p_event->params.peer_data_update_succeeded.action == PM_PEER_DATA_OP_UPDATE) && + (p_write_buffer_record != NULL)) { + /* The write came from PDB. */ + write_buffer_record_release(p_write_buffer_record); + } + break; + + case PM_EVT_PEER_DATA_UPDATE_FAILED: + if ((p_event->params.peer_data_update_succeeded.action == PM_PEER_DATA_OP_UPDATE) && + (p_write_buffer_record != NULL)) { + /* The write came from PDB, retry. */ + p_write_buffer_record->store_token = PM_STORE_TOKEN_INVALID; + p_write_buffer_record->store_busy = true; + m_pending_store = true; + evt_send = false; + } + break; + + case PM_EVT_FLASH_GARBAGE_COLLECTED: + retry_flash_full = true; + break; + + default: + break; + } + + if (evt_send) { + /* Forward the event to all registered Peer Database event handlers. */ + pdb_evt_send(p_event); + } + + reattempt_previous_operations(retry_flash_full); +} + +uint32_t pdb_init(void) +{ + uint32_t err_code; + + NRF_PM_DEBUG_CHECK(!m_module_initialized); + + internal_state_reset(); + + PM_BUFFER_INIT(&m_write_buffer, CONFIG_PM_FLASH_BUFFERS, PDB_WRITE_BUF_SIZE, err_code); + + if (err_code != NRF_SUCCESS) { + LOG_ERR("PM_BUFFER_INIT() returned %s.", nrf_strerror_get(err_code)); + return NRF_ERROR_INTERNAL; + } + + m_module_initialized = true; + + return NRF_SUCCESS; +} + +uint32_t pdb_peer_free(pm_peer_id_t peer_id) +{ + uint32_t err_code; + + NRF_PM_DEBUG_CHECK(m_module_initialized); + + uint32_t index = 0; + pdb_buffer_record_t *p_record = write_buffer_record_find_next(peer_id, &index); + + while (p_record != NULL) { + err_code = pdb_write_buf_release(peer_id, p_record->data_id); + /* All return values are acceptable. */ + UNUSED_VARIABLE(err_code); + + if ((err_code != NRF_SUCCESS) && (err_code != NRF_ERROR_NOT_FOUND)) { + LOG_ERR("pdb_write_buf_release() returned %s which should not " + "happen. peer_id: %d, " + "data_id: %d", + nrf_strerror_get(err_code), peer_id, p_record->data_id); + return NRF_ERROR_INTERNAL; + } + + index++; + p_record = write_buffer_record_find_next(peer_id, &index); + } + + err_code = pds_peer_id_free(peer_id); + + if ((err_code == NRF_SUCCESS) || (err_code == NRF_ERROR_INVALID_PARAM)) { + return err_code; + } + + LOG_ERR("Peer ID %d was not properly released. pds_peer_id_free() returned %s. " + "peer_id: %d", + peer_id, nrf_strerror_get(err_code)); + + return NRF_ERROR_INTERNAL; +} + +uint32_t pdb_peer_data_ptr_get(pm_peer_id_t peer_id, pm_peer_data_id_t data_id, + pm_peer_data_flash_t *const p_peer_data) +{ + NRF_PM_DEBUG_CHECK(m_module_initialized); + NRF_PM_DEBUG_CHECK(p_peer_data != NULL); + + /* Pass NULL to only retrieve a pointer. */ + return pds_peer_data_read(peer_id, data_id, (pm_peer_data_t *)p_peer_data, NULL); +} + +uint32_t pdb_write_buf_get(pm_peer_id_t peer_id, pm_peer_data_id_t data_id, uint32_t n_bufs, + pm_peer_data_t *p_peer_data) +{ + NRF_PM_DEBUG_CHECK(m_module_initialized); + + VERIFY_PARAM_NOT_NULL(p_peer_data); + VERIFY_DATA_ID_WRITE_BUF(data_id); + + if ((n_bufs == 0) || (n_bufs > CONFIG_PM_FLASH_BUFFERS)) { + return NRF_ERROR_INVALID_PARAM; + } + + pdb_buffer_record_t *p_write_buffer_record; + uint8_t *p_buffer_memory; + bool new_record = false; + + p_write_buffer_record = write_buffer_record_find(peer_id, data_id); + + if (p_write_buffer_record == NULL) { + /* No buffer exists. */ + write_buffer_record_acquire(&p_write_buffer_record, peer_id, data_id); + if (p_write_buffer_record == NULL) { + return NRF_ERROR_BUSY; + } + } else if (p_write_buffer_record->n_bufs != n_bufs) { + /* Buffer exists with a different n_bufs from what was requested. */ + return NRF_ERROR_FORBIDDEN; + } + + if (p_write_buffer_record->buffer_block_id == PM_BUFFER_INVALID_ID) { + p_write_buffer_record->buffer_block_id = + pm_buffer_block_acquire(&m_write_buffer, n_bufs); + + if (p_write_buffer_record->buffer_block_id == PM_BUFFER_INVALID_ID) { + write_buffer_record_invalidate(p_write_buffer_record); + return NRF_ERROR_BUSY; + } + + new_record = true; + } + + p_write_buffer_record->n_bufs = n_bufs; + + p_buffer_memory = + pm_buffer_ptr_get(&m_write_buffer, p_write_buffer_record->buffer_block_id); + + if (p_buffer_memory == NULL) { + LOG_ERR("Cannot store data to flash because pm_buffer_ptr_get() could not retrieve " + "RAM buffer. Is block_id %d not allocated?", + p_write_buffer_record->buffer_block_id); + return NRF_ERROR_INTERNAL; + } + + peer_data_point_to_buffer(p_peer_data, data_id, p_buffer_memory, n_bufs); + if (new_record && (data_id == PM_PEER_DATA_ID_GATT_LOCAL)) { + p_peer_data->p_local_gatt_db->len = PM_LOCAL_DB_LEN(p_peer_data->length_words); + } + + return NRF_SUCCESS; +} + +uint32_t pdb_write_buf_release(pm_peer_id_t peer_id, pm_peer_data_id_t data_id) +{ + NRF_PM_DEBUG_CHECK(m_module_initialized); + + pdb_buffer_record_t *p_write_buffer_record; + + p_write_buffer_record = write_buffer_record_find(peer_id, data_id); + + if (p_write_buffer_record == NULL) { + return NRF_ERROR_NOT_FOUND; + } + + write_buffer_record_release(p_write_buffer_record); + + return NRF_SUCCESS; +} + +uint32_t pdb_write_buf_store(pm_peer_id_t peer_id, pm_peer_data_id_t data_id, + pm_peer_id_t new_peer_id) +{ + NRF_PM_DEBUG_CHECK(m_module_initialized); + + VERIFY_DATA_ID_WRITE_BUF(data_id); + + if (!pds_peer_id_is_allocated(new_peer_id)) { + return NRF_ERROR_INVALID_PARAM; + } + + pdb_buffer_record_t *p_write_buffer_record = write_buffer_record_find(peer_id, data_id); + + if (p_write_buffer_record == NULL) { + return NRF_ERROR_NOT_FOUND; + } + + p_write_buffer_record->peer_id = new_peer_id; + p_write_buffer_record->data_id = data_id; + return write_buf_store(p_write_buffer_record); +} diff --git a/lib/peer_manager/modules/peer_id.c b/lib/peer_manager/modules/peer_id.c new file mode 100644 index 0000000000..e11fdad555 --- /dev/null +++ b/lib/peer_manager/modules/peer_id.c @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2015-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#define FLAGS_PER_ELEMENT (sizeof(atomic_t) * CHAR_BIT) +#define FLAGS_ARRAY_LEN(flag_count) (((flag_count - 1) / FLAGS_PER_ELEMENT) + 1) +#define ATOMIC_BITMAP(name) atomic_t name[FLAGS_ARRAY_LEN((PM_PEER_ID_N_AVAILABLE_IDS))] + +typedef struct { + /** Bitmap designating which peer IDs are in use. */ + ATOMIC_BITMAP(used_peer_ids); + /** Bitmap designating which peer IDs are marked for deletion. */ + ATOMIC_BITMAP(deleted_peer_ids); +} pi_t; + +static pi_t m_pi = {{0}, {0}}; + +static void internal_state_reset(pi_t *p_pi) +{ + memset(p_pi, 0, sizeof(pi_t)); +} + +void peer_id_init(void) +{ + internal_state_reset(&m_pi); +} + +static uint32_t find_and_set_flag(atomic_t *p_flags, uint32_t flag_count) +{ + for (uint32_t i = 0; i < FLAGS_ARRAY_LEN(flag_count); i++) { + uint32_t word = atomic_get(&p_flags[i]); + uint32_t inverted = ~word; + + while (inverted) { + /* Find lowest zero bit */ + uint32_t first_zero = NRF_CTZ(inverted); + uint32_t first_zero_global = first_zero + (i * 32); + + if (first_zero_global >= flag_count) { + break; + } + if (!atomic_test_and_set_bit(p_flags, first_zero_global)) { + return first_zero_global; + } + inverted &= ~(1u << first_zero); + } + } + return flag_count; +} + +static pm_peer_id_t claim(pm_peer_id_t peer_id, atomic_t *p_peer_id_flags) +{ + pm_peer_id_t allocated_peer_id = PM_PEER_ID_INVALID; + + if (peer_id == PM_PEER_ID_INVALID) { + allocated_peer_id = + find_and_set_flag(p_peer_id_flags, PM_PEER_ID_N_AVAILABLE_IDS); + if (allocated_peer_id == PM_PEER_ID_N_AVAILABLE_IDS) { + allocated_peer_id = PM_PEER_ID_INVALID; + } + } else if (peer_id < PM_PEER_ID_N_AVAILABLE_IDS) { + bool lock_success = !atomic_test_and_set_bit(p_peer_id_flags, peer_id); + + allocated_peer_id = lock_success ? peer_id : PM_PEER_ID_INVALID; + } + return allocated_peer_id; +} + +static void release(pm_peer_id_t peer_id, atomic_t *p_peer_id_flags) +{ + if (peer_id < PM_PEER_ID_N_AVAILABLE_IDS) { + atomic_clear_bit(p_peer_id_flags, peer_id); + } +} + +pm_peer_id_t peer_id_allocate(pm_peer_id_t peer_id) +{ + return claim(peer_id, m_pi.used_peer_ids); +} + +bool peer_id_delete(pm_peer_id_t peer_id) +{ + pm_peer_id_t deleted_peer_id; + + if (peer_id == PM_PEER_ID_INVALID) { + return false; + } + + deleted_peer_id = claim(peer_id, m_pi.deleted_peer_ids); + + return (deleted_peer_id == peer_id); +} + +void peer_id_free(pm_peer_id_t peer_id) +{ + release(peer_id, m_pi.used_peer_ids); + release(peer_id, m_pi.deleted_peer_ids); +} + +bool peer_id_is_allocated(pm_peer_id_t peer_id) +{ + if (peer_id < PM_PEER_ID_N_AVAILABLE_IDS) { + return atomic_test_bit(m_pi.used_peer_ids, peer_id); + } + return false; +} + +bool peer_id_is_deleted(pm_peer_id_t peer_id) +{ + if (peer_id < PM_PEER_ID_N_AVAILABLE_IDS) { + return atomic_test_bit(m_pi.deleted_peer_ids, peer_id); + } + return false; +} + +pm_peer_id_t next_id_get(pm_peer_id_t prev_peer_id, atomic_t *p_peer_id_flags) +{ + pm_peer_id_t i = (prev_peer_id == PM_PEER_ID_INVALID) ? 0 : (prev_peer_id + 1); + + for (; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + if (atomic_test_bit(p_peer_id_flags, i)) { + return i; + } + } + + return PM_PEER_ID_INVALID; +} + +pm_peer_id_t peer_id_get_next_used(pm_peer_id_t peer_id) +{ + peer_id = next_id_get(peer_id, m_pi.used_peer_ids); + + while (peer_id != PM_PEER_ID_INVALID) { + if (!peer_id_is_deleted(peer_id)) { + return peer_id; + } + + peer_id = next_id_get(peer_id, m_pi.used_peer_ids); + } + + return peer_id; +} + +pm_peer_id_t peer_id_get_next_deleted(pm_peer_id_t prev_peer_id) +{ + return next_id_get(prev_peer_id, m_pi.deleted_peer_ids); +} + +uint32_t peer_id_n_ids(void) +{ + uint32_t n_ids = 0; + + for (pm_peer_id_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + n_ids += atomic_test_bit(m_pi.used_peer_ids, i); + } + + return n_ids; +} diff --git a/lib/peer_manager/modules/peer_manager_handler.c b/lib/peer_manager/modules/peer_manager_handler.c new file mode 100644 index 0000000000..24c5537174 --- /dev/null +++ b/lib/peer_manager/modules/peer_manager_handler.c @@ -0,0 +1,603 @@ +/* + * Copyright (c) 2018-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if CONFIG_PM_HANDLER_SEC_DELAY_MS > 0 +#include +#endif + +#include + +#define APP_ERROR_CHECK + +LOG_MODULE_DECLARE(peer_manager, CONFIG_PEER_MANAGER_LOG_LEVEL); + +static const char * const m_roles_str[] = { + "Invalid Role", + "Peripheral", + "Central", +}; + +static const char * const m_sec_procedure_str[] = { + "Encryption", + "Bonding", + "Pairing", +}; + +#define PM_EVT_STR(_name) [_name] = STRINGIFY(_name) + +static const char * const m_event_str[] = { + PM_EVT_STR(PM_EVT_BONDED_PEER_CONNECTED), + PM_EVT_STR(PM_EVT_CONN_CONFIG_REQ), + PM_EVT_STR(PM_EVT_CONN_SEC_START), + PM_EVT_STR(PM_EVT_CONN_SEC_SUCCEEDED), + PM_EVT_STR(PM_EVT_CONN_SEC_FAILED), + PM_EVT_STR(PM_EVT_CONN_SEC_CONFIG_REQ), + PM_EVT_STR(PM_EVT_CONN_SEC_PARAMS_REQ), + PM_EVT_STR(PM_EVT_STORAGE_FULL), + PM_EVT_STR(PM_EVT_ERROR_UNEXPECTED), + PM_EVT_STR(PM_EVT_PEER_DATA_UPDATE_SUCCEEDED), + PM_EVT_STR(PM_EVT_PEER_DATA_UPDATE_FAILED), + PM_EVT_STR(PM_EVT_PEER_DELETE_SUCCEEDED), + PM_EVT_STR(PM_EVT_PEER_DELETE_FAILED), + PM_EVT_STR(PM_EVT_PEERS_DELETE_SUCCEEDED), + PM_EVT_STR(PM_EVT_PEERS_DELETE_FAILED), + PM_EVT_STR(PM_EVT_LOCAL_DB_CACHE_APPLIED), + PM_EVT_STR(PM_EVT_LOCAL_DB_CACHE_APPLY_FAILED), + PM_EVT_STR(PM_EVT_SERVICE_CHANGED_IND_SENT), + PM_EVT_STR(PM_EVT_SERVICE_CHANGED_IND_CONFIRMED), + PM_EVT_STR(PM_EVT_SLAVE_SECURITY_REQ), + PM_EVT_STR(PM_EVT_FLASH_GARBAGE_COLLECTED), + PM_EVT_STR(PM_EVT_FLASH_GARBAGE_COLLECTION_FAILED), +}; + +static const char * const m_data_id_str[] = { + "Outdated (0)", "Service changed pending flag", + "Outdated (2)", "Outdated (3)", + "Application data", "Remote database", + "Peer rank", "Bonding data", + "Local database", "Central address resolution", +}; + +static const char * const m_data_action_str[] = {"Update", "Delete"}; + +#define PM_SEC_ERR_STR(_name) \ + { \ + .error = _name, .error_str = #_name \ + } + +typedef struct { + pm_sec_error_code_t error; + const char *error_str; +} sec_err_str_t; + +static const sec_err_str_t m_pm_sec_error_str[] = { + PM_SEC_ERR_STR(PM_CONN_SEC_ERROR_PIN_OR_KEY_MISSING), + PM_SEC_ERR_STR(PM_CONN_SEC_ERROR_MIC_FAILURE), + PM_SEC_ERR_STR(PM_CONN_SEC_ERROR_DISCONNECT), + PM_SEC_ERR_STR(PM_CONN_SEC_ERROR_SMP_TIMEOUT), +}; + +static const char *sec_err_string_get(pm_sec_error_code_t error) +{ + static char errstr[30]; + + for (uint32_t i = 0; i < (sizeof(m_pm_sec_error_str) / sizeof(sec_err_str_t)); i++) { + if (m_pm_sec_error_str[i].error == error) { + return m_pm_sec_error_str[i].error_str; + } + } + + int len = snprintf(errstr, sizeof(errstr), "%s 0x%hx", + (error < PM_CONN_SEC_ERROR_BASE) ? "BLE_GAP_SEC_STATUS" + : "PM_CONN_SEC_ERROR", + error); + UNUSED_VARIABLE(len); + return errstr; +} + +static void _conn_secure(uint16_t conn_handle, bool force) +{ + uint32_t err_code; + + if (!force) { + pm_conn_sec_status_t status; + + err_code = pm_conn_sec_status_get(conn_handle, &status); + if (err_code != BLE_ERROR_INVALID_CONN_HANDLE) { + APP_ERROR_CHECK(err_code); + } + + /* If the link is already secured, don't initiate security procedure. */ + if (status.encrypted) { + LOG_DBG("Already encrypted, skipping security."); + return; + } + } + + err_code = pm_conn_secure(conn_handle, false); + + if ((err_code == NRF_SUCCESS) || (err_code == NRF_ERROR_BUSY)) { + /* Success. */ + } else if (err_code == NRF_ERROR_TIMEOUT) { + LOG_WRN("pm_conn_secure() failed because an SMP timeout is preventing security on " + "the link. Disconnecting conn_handle %d.", + conn_handle); + + err_code = sd_ble_gap_disconnect(conn_handle, + BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION); + if (err_code != NRF_SUCCESS) { + LOG_WRN("sd_ble_gap_disconnect() returned %s on conn_handle %d.", + nrf_strerror_get(err_code), conn_handle); + } + } else if (err_code == NRF_ERROR_INVALID_DATA) { + LOG_WRN("pm_conn_secure() failed because the stored data for conn_handle %d does " + "not have a valid key.", + conn_handle); + } else if (err_code == BLE_ERROR_INVALID_CONN_HANDLE) { + LOG_WRN("pm_conn_secure() failed because conn_handle %d is not a valid connection.", + conn_handle); + } else { + LOG_ERR("Asserting. pm_conn_secure() returned %s on conn_handle %d.", + nrf_strerror_get(err_code), conn_handle); + APP_ERROR_CHECK(err_code); + } +} + +#if CONFIG_PM_HANDLER_SEC_DELAY_MS > 0 +static struct bm_timer secure_delay_timer; + +typedef union { + struct { + uint16_t conn_handle; + bool force; + } values; + void *p_void; +} conn_secure_context_t; + +STATIC_ASSERT(sizeof(conn_secure_context_t) <= sizeof(void *), + "conn_secure_context_t is too large."); + +static void _conn_secure(uint16_t conn_handle, bool force); + +static void delayed_conn_secure(void *context) +{ + conn_secure_context_t sec_context = {.p_void = context}; + + _conn_secure(sec_context.values.conn_handle, sec_context.values.force); +} + +static void conn_secure(uint16_t conn_handle, bool force) +{ + int err; + static bool created; + + if (!created) { + err = bm_timer_init(&secure_delay_timer, BM_TIMER_MODE_SINGLE_SHOT, + delayed_conn_secure); + APP_ERROR_CHECK(err); + created = true; + } + + conn_secure_context_t sec_context = {0}; + + sec_context.values.conn_handle = conn_handle; + sec_context.values.force = force; + + err = bm_timer_start(secure_delay_timer, + BM_TIMER_MS_TO_TICKS(CONFIG_PM_HANDLER_SEC_DELAY_MS), + sec_context.p_void); + APP_ERROR_CHECK(err); +} + +#else +static void conn_secure(uint16_t conn_handle, bool force) +{ + _conn_secure(conn_handle, force); +} +#endif + +void pm_handler_on_pm_evt(pm_evt_t const *p_pm_evt) +{ + pm_handler_pm_evt_log(p_pm_evt); + + if (p_pm_evt->evt_id == PM_EVT_BONDED_PEER_CONNECTED) { + conn_secure(p_pm_evt->conn_handle, false); + } else if (p_pm_evt->evt_id == PM_EVT_ERROR_UNEXPECTED) { + LOG_ERR("Asserting."); + APP_ERROR_CHECK(p_pm_evt->params.error_unexpected.error); + } +} + +void pm_handler_flash_clean_on_return(void) +{ + /* Trigger the mechanism to make more room in flash. */ + pm_evt_t storage_full_evt = {.evt_id = PM_EVT_STORAGE_FULL}; + + pm_handler_flash_clean(&storage_full_evt); +} + +static void rank_highest(pm_peer_id_t peer_id) +{ + /* Trigger a pm_peer_rank_highest() with internal bookkeeping. */ + pm_evt_t connected_evt = {.evt_id = PM_EVT_BONDED_PEER_CONNECTED, .peer_id = peer_id}; + + pm_handler_flash_clean(&connected_evt); +} + +void pm_handler_flash_clean(pm_evt_t const *p_pm_evt) +{ + uint32_t err_code; + /* Indicates whether garbage collection is currently being run. */ + static bool flash_cleaning; + /* Indicates whether a successful write happened after the last garbage + * collection. If this is false when flash is full, it means just a + * garbage collection won't work, so some data should be deleted. + */ + static bool flash_write_after_gc = true; + +/* Size of rank_queue. */ +#define RANK_QUEUE_SIZE 8 +/* Initial value of rank_queue. */ +#define RANK_QUEUE_INIT PM_PEER_ID_INVALID, + + /* Queue of rank_highest calls that failed because of full flash. */ + static pm_peer_id_t rank_queue[8] = {[0 ... RANK_QUEUE_SIZE - 1] = RANK_QUEUE_INIT}; + /* Write pointer for rank_queue. */ + static int rank_queue_wr; + + switch (p_pm_evt->evt_id) { + case PM_EVT_BONDED_PEER_CONNECTED: + err_code = pm_peer_rank_highest(p_pm_evt->peer_id); + if ((err_code == NRF_ERROR_RESOURCES) || (err_code == NRF_ERROR_BUSY)) { + /* Queue pm_peer_rank_highest() call and attempt to clean flash. */ + rank_queue[rank_queue_wr] = p_pm_evt->peer_id; + rank_queue_wr = (rank_queue_wr + 1) % RANK_QUEUE_SIZE; + pm_handler_flash_clean_on_return(); + } else if ((err_code != NRF_ERROR_NOT_SUPPORTED) && + (err_code != NRF_ERROR_INVALID_PARAM) && + (err_code != NRF_ERROR_DATA_SIZE)) { + APP_ERROR_CHECK(err_code); + } else { + LOG_DBG("pm_peer_rank_highest() returned %s for peer id %d", + nrf_strerror_get(err_code), p_pm_evt->peer_id); + } + break; + + case PM_EVT_CONN_SEC_START: + break; + + case PM_EVT_CONN_SEC_SUCCEEDED: + /* PM_CONN_SEC_PROCEDURE_ENCRYPTION in case peer was not recognized at connection + * time. + */ + if ((p_pm_evt->params.conn_sec_succeeded.procedure == + PM_CONN_SEC_PROCEDURE_BONDING) || + (p_pm_evt->params.conn_sec_succeeded.procedure == + PM_CONN_SEC_PROCEDURE_ENCRYPTION)) { + rank_highest(p_pm_evt->peer_id); + } + break; + + case PM_EVT_CONN_SEC_FAILED: + case PM_EVT_CONN_SEC_CONFIG_REQ: + case PM_EVT_CONN_SEC_PARAMS_REQ: + break; + + case PM_EVT_STORAGE_FULL: + if (!flash_cleaning) { + err_code = NRF_SUCCESS; + LOG_INF("Attempting to clean flash."); + if (!flash_write_after_gc) { + /* Check whether another user of FDS has deleted a record that can + * be GCed. + */ + fds_stat_t fds_stats; + + err_code = fds_stat(&fds_stats); + APP_ERROR_CHECK(err_code); + flash_write_after_gc = (fds_stats.dirty_records > 0); + } + if (!flash_write_after_gc) { + pm_peer_id_t peer_id_to_delete; + + err_code = pm_peer_ranks_get(NULL, NULL, &peer_id_to_delete, NULL); + if (err_code == NRF_SUCCESS) { + LOG_INF("Deleting lowest ranked peer (peer_id: %d)", + peer_id_to_delete); + err_code = pm_peer_delete(peer_id_to_delete); + APP_ERROR_CHECK(err_code); + flash_write_after_gc = true; + } + if (err_code == NRF_ERROR_NOT_FOUND) { + LOG_ERR("There are no peers to delete."); + } else if (err_code == NRF_ERROR_NOT_SUPPORTED) { + LOG_WRN("Peer ranks functionality is disabled, so " + "no peers are deleted."); + } else { + APP_ERROR_CHECK(err_code); + } + } + if (err_code == NRF_SUCCESS) { + err_code = fds_gc(); + if (err_code == NRF_SUCCESS) { + LOG_DBG("Running flash garbage collection."); + flash_cleaning = true; + } else if (err_code != FDS_ERR_NO_SPACE_IN_QUEUES) { + APP_ERROR_CHECK(err_code); + } + } + } + break; + + case PM_EVT_ERROR_UNEXPECTED: + break; + + case PM_EVT_PEER_DATA_UPDATE_SUCCEEDED: + flash_write_after_gc = true; + break; + + case PM_EVT_PEER_DATA_UPDATE_FAILED: + break; + + case PM_EVT_PEER_DELETE_SUCCEEDED: + flash_write_after_gc = true; + break; + + case PM_EVT_PEER_DELETE_FAILED: + case PM_EVT_PEERS_DELETE_SUCCEEDED: + case PM_EVT_PEERS_DELETE_FAILED: + case PM_EVT_LOCAL_DB_CACHE_APPLIED: + case PM_EVT_LOCAL_DB_CACHE_APPLY_FAILED: + case PM_EVT_SERVICE_CHANGED_IND_SENT: + case PM_EVT_SERVICE_CHANGED_IND_CONFIRMED: + case PM_EVT_SLAVE_SECURITY_REQ: + break; + + case PM_EVT_FLASH_GARBAGE_COLLECTED: + flash_cleaning = false; + flash_write_after_gc = false; + { + /* Reattempt queued pm_peer_rank_highest() calls. */ + int rank_queue_rd = rank_queue_wr; + + for (int i = 0; i < RANK_QUEUE_SIZE; i++) { + pm_peer_id_t peer_id = + rank_queue[(i + rank_queue_rd) % RANK_QUEUE_SIZE]; + if (peer_id != PM_PEER_ID_INVALID) { + rank_queue[(i + rank_queue_rd) % RANK_QUEUE_SIZE] = + PM_PEER_ID_INVALID; + rank_highest(peer_id); + } + } + } + break; + + case PM_EVT_FLASH_GARBAGE_COLLECTION_FAILED: + flash_cleaning = false; + + if (p_pm_evt->params.garbage_collection_failed.error == FDS_ERR_BUSY || + p_pm_evt->params.garbage_collection_failed.error == FDS_ERR_OPERATION_TIMEOUT) { + /* Retry immediately if error is transient. */ + pm_handler_flash_clean_on_return(); + } + break; + + default: + break; + } +} + +void pm_handler_pm_evt_log(pm_evt_t const *p_pm_evt) +{ + LOG_DBG("Event %s", m_event_str[p_pm_evt->evt_id]); + + switch (p_pm_evt->evt_id) { + case PM_EVT_BONDED_PEER_CONNECTED: + LOG_DBG("Previously bonded peer connected: role: %s, conn_handle: %d, peer_id: %d", + m_roles_str[ble_conn_state_role(p_pm_evt->conn_handle)], + p_pm_evt->conn_handle, p_pm_evt->peer_id); + break; + + case PM_EVT_CONN_CONFIG_REQ: + LOG_DBG("Connection configuration request"); + break; + + case PM_EVT_CONN_SEC_START: + LOG_DBG("Connection security procedure started: role: %s, conn_handle: %d, " + "procedure: %s", + m_roles_str[ble_conn_state_role(p_pm_evt->conn_handle)], + p_pm_evt->conn_handle, + m_sec_procedure_str[p_pm_evt->params.conn_sec_start.procedure]); + break; + + case PM_EVT_CONN_SEC_SUCCEEDED: + LOG_INF("Connection secured: role: %s, conn_handle: %d, procedure: %s", + m_roles_str[ble_conn_state_role(p_pm_evt->conn_handle)], + p_pm_evt->conn_handle, + m_sec_procedure_str[p_pm_evt->params.conn_sec_start.procedure]); + break; + + case PM_EVT_CONN_SEC_FAILED: + LOG_INF("Connection security failed: role: %s, conn_handle: 0x%x, procedure: " + "%s, error: %d", + m_roles_str[ble_conn_state_role(p_pm_evt->conn_handle)], + p_pm_evt->conn_handle, + m_sec_procedure_str[p_pm_evt->params.conn_sec_start.procedure], + p_pm_evt->params.conn_sec_failed.error); + LOG_DBG("Error (decoded): %s", + sec_err_string_get(p_pm_evt->params.conn_sec_failed.error)); + break; + + case PM_EVT_CONN_SEC_CONFIG_REQ: + LOG_DBG("Security configuration request"); + break; + + case PM_EVT_CONN_SEC_PARAMS_REQ: + LOG_DBG("Security parameter request"); + break; + + case PM_EVT_STORAGE_FULL: + LOG_WRN("Flash storage is full"); + break; + + case PM_EVT_ERROR_UNEXPECTED: + LOG_ERR("Unexpected fatal error occurred: error: %s", + nrf_strerror_get(p_pm_evt->params.error_unexpected.error)); + break; + + case PM_EVT_PEER_DATA_UPDATE_SUCCEEDED: + LOG_INF("Peer data updated in flash: peer_id: %d, data_id: %s, action: %s%s", + p_pm_evt->peer_id, + m_data_id_str[p_pm_evt->params.peer_data_update_succeeded.data_id], + m_data_action_str[p_pm_evt->params.peer_data_update_succeeded.action], + p_pm_evt->params.peer_data_update_succeeded.flash_changed + ? "" + : ", no change"); + break; + + case PM_EVT_PEER_DATA_UPDATE_FAILED: + /* This can happen if the SoftDevice is too busy with BLE operations. */ + LOG_WRN("Peer data updated failed: peer_id: %d, data_id: %s, action: %s, error: %s", + p_pm_evt->peer_id, + m_data_id_str[p_pm_evt->params.peer_data_update_failed.data_id], + m_data_action_str[p_pm_evt->params.peer_data_update_succeeded.action], + nrf_strerror_get(p_pm_evt->params.peer_data_update_failed.error)); + break; + + case PM_EVT_PEER_DELETE_SUCCEEDED: + LOG_ERR("Peer deleted successfully: peer_id: %d", p_pm_evt->peer_id); + break; + + case PM_EVT_PEER_DELETE_FAILED: + LOG_ERR("Peer deletion failed: peer_id: %d, error: %s", p_pm_evt->peer_id, + nrf_strerror_get(p_pm_evt->params.peer_delete_failed.error)); + break; + + case PM_EVT_PEERS_DELETE_SUCCEEDED: + LOG_INF("All peers deleted."); + break; + + case PM_EVT_PEERS_DELETE_FAILED: + LOG_ERR("All peer deletion failed: error: %s", + nrf_strerror_get(p_pm_evt->params.peers_delete_failed_evt.error)); + break; + + case PM_EVT_LOCAL_DB_CACHE_APPLIED: + LOG_DBG("Previously stored local DB applied: conn_handle: %d, peer_id: %d", + p_pm_evt->conn_handle, p_pm_evt->peer_id); + break; + + case PM_EVT_LOCAL_DB_CACHE_APPLY_FAILED: + /* This can happen when the local DB has changed. */ + LOG_WRN("Local DB could not be applied: conn_handle: %d, peer_id: %d", + p_pm_evt->conn_handle, p_pm_evt->peer_id); + break; + + case PM_EVT_SERVICE_CHANGED_IND_SENT: + LOG_DBG("Sending Service Changed indication."); + break; + + case PM_EVT_SERVICE_CHANGED_IND_CONFIRMED: + LOG_DBG("Service Changed indication confirmed."); + break; + + case PM_EVT_SLAVE_SECURITY_REQ: + LOG_DBG("Security Request received from peer."); + break; + + case PM_EVT_FLASH_GARBAGE_COLLECTED: + LOG_DBG("Flash garbage collection complete."); + break; + + case PM_EVT_FLASH_GARBAGE_COLLECTION_FAILED: + LOG_WRN("Flash garbage collection failed with error %s.", + nrf_strerror_get(p_pm_evt->params.garbage_collection_failed.error)); + break; + + default: + LOG_WRN("Unexpected PM event ID: 0x%x.", p_pm_evt->evt_id); + break; + } +} + +void pm_handler_disconnect_on_sec_failure(pm_evt_t const *p_pm_evt) +{ + uint32_t err_code; + + if (p_pm_evt->evt_id == PM_EVT_CONN_SEC_FAILED) { + LOG_WRN("Disconnecting conn_handle %d.", p_pm_evt->conn_handle); + err_code = sd_ble_gap_disconnect(p_pm_evt->conn_handle, + BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION); + if ((err_code != NRF_ERROR_INVALID_STATE) && + (err_code != BLE_ERROR_INVALID_CONN_HANDLE)) { + APP_ERROR_CHECK(err_code); + } + } +} + +void pm_handler_disconnect_on_insufficient_sec(pm_evt_t const *p_pm_evt, + pm_conn_sec_status_t *p_min_conn_sec) +{ + if (p_pm_evt->evt_id == PM_EVT_CONN_SEC_SUCCEEDED) { + if (!pm_sec_is_sufficient(p_pm_evt->conn_handle, p_min_conn_sec)) { + LOG_WRN("Connection security is insufficient, disconnecting."); + uint32_t err_code = sd_ble_gap_disconnect( + p_pm_evt->conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION); + APP_ERROR_CHECK(err_code); + } + } +} + +void pm_handler_secure_on_connection(ble_evt_t const *p_ble_evt) +{ + switch (p_ble_evt->header.evt_id) { + case BLE_GAP_EVT_CONNECTED: + LOG_DBG("Connected, securing connection. conn_handle: %d", + p_ble_evt->evt.gap_evt.conn_handle); + conn_secure(p_ble_evt->evt.gap_evt.conn_handle, false); + break; + +#if CONFIG_PM_HANDLER_SEC_DELAY_MS > 0 + case BLE_GAP_EVT_DISCONNECTED: { + int err = bm_timer_stop(&secure_delay_timer); + + APP_ERROR_CHECK(err); + } break; +#endif + + default: + break; + } +} + +void pm_handler_secure_on_error(ble_evt_t const *p_ble_evt) +{ + if ((p_ble_evt->header.evt_id >= BLE_GATTC_EVT_BASE) && + (p_ble_evt->header.evt_id <= BLE_GATTC_EVT_LAST)) { + if ((p_ble_evt->evt.gattc_evt.gatt_status == + BLE_GATT_STATUS_ATTERR_INSUF_ENCRYPTION) || + (p_ble_evt->evt.gattc_evt.gatt_status == + BLE_GATT_STATUS_ATTERR_INSUF_AUTHENTICATION)) { + LOG_INF("GATTC procedure (evt id 0x%x) failed because it needs " + "encryption. Bonding: conn_handle=%d", + p_ble_evt->header.evt_id, + p_ble_evt->evt.gattc_evt.conn_handle); + conn_secure(p_ble_evt->evt.gattc_evt.conn_handle, true); + } + } +} diff --git a/lib/peer_manager/modules/pm_buffer.c b/lib/peer_manager/modules/pm_buffer.c new file mode 100644 index 0000000000..bbc642040e --- /dev/null +++ b/lib/peer_manager/modules/pm_buffer.c @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2015-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + + +#include +#include +#include +#include +#include + +#define BUFFER_IS_VALID(p_buffer) \ + ((p_buffer != NULL) && (p_buffer->p_memory != NULL) && (p_buffer->p_mutex != NULL)) + +static bool mutex_lock(atomic_t *mutex, int index) +{ + return !atomic_test_and_set_bit(mutex, index); +} + +static void mutex_unlock(atomic_t *mutex, int index) +{ + atomic_clear_bit(mutex, index); +} + +static bool mutex_lock_status_get(atomic_t *mutex, int index) +{ + return atomic_test_bit(mutex, index); +} + +uint32_t pm_buffer_init(pm_buffer_t *p_buffer, uint8_t *p_buffer_memory, + uint32_t buffer_memory_size, atomic_t *p_mutex_memory, + uint32_t n_blocks, uint32_t block_size) +{ + if ((p_buffer != NULL) && (p_buffer_memory != NULL) && (p_mutex_memory != NULL) && + (buffer_memory_size >= (n_blocks * block_size)) && (n_blocks != 0) && + (block_size != 0)) { + p_buffer->p_memory = p_buffer_memory; + p_buffer->p_mutex = p_mutex_memory; + p_buffer->n_blocks = n_blocks; + p_buffer->block_size = block_size; + + return NRF_SUCCESS; + } else { + return NRF_ERROR_INVALID_PARAM; + } +} + +uint8_t pm_buffer_block_acquire(pm_buffer_t *p_buffer, uint32_t n_blocks) +{ + if (!BUFFER_IS_VALID(p_buffer)) { + return PM_BUFFER_INVALID_ID; + } + + uint8_t first_locked_mutex = PM_BUFFER_INVALID_ID; + + for (uint8_t i = 0; i < p_buffer->n_blocks; i++) { + if (mutex_lock(p_buffer->p_mutex, i)) { + if (first_locked_mutex == PM_BUFFER_INVALID_ID) { + first_locked_mutex = i; + } + if ((i - first_locked_mutex + 1U) == n_blocks) { + return first_locked_mutex; + } + } else if (first_locked_mutex != PM_BUFFER_INVALID_ID) { + for (uint8_t j = first_locked_mutex; j < i; j++) { + pm_buffer_release(p_buffer, j); + } + first_locked_mutex = PM_BUFFER_INVALID_ID; + } + } + + return (PM_BUFFER_INVALID_ID); +} + +uint8_t *pm_buffer_ptr_get(pm_buffer_t *p_buffer, uint8_t id) +{ + if (!BUFFER_IS_VALID(p_buffer)) { + return NULL; + } + + if ((id != PM_BUFFER_INVALID_ID) && mutex_lock_status_get(p_buffer->p_mutex, id)) { + return &p_buffer->p_memory[id * p_buffer->block_size]; + } else { + return NULL; + } +} + +void pm_buffer_release(pm_buffer_t *p_buffer, uint8_t id) +{ + if (BUFFER_IS_VALID(p_buffer) && (id != PM_BUFFER_INVALID_ID) && + mutex_lock_status_get(p_buffer->p_mutex, id)) { + mutex_unlock(p_buffer->p_mutex, id); + } +} diff --git a/lib/peer_manager/modules/security_dispatcher.c b/lib/peer_manager/modules/security_dispatcher.c new file mode 100644 index 0000000000..4bc5bb7065 --- /dev/null +++ b/lib/peer_manager/modules/security_dispatcher.c @@ -0,0 +1,917 @@ +/* + * Copyright (c) 2015-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 +#if CONFIG_PM_RA_PROTECTION_ENABLED +#include +#endif /* CONFIG_PM_RA_PROTECTION_ENABLED */ + +#include + +LOG_MODULE_DECLARE(peer_manager, CONFIG_PEER_MANAGER_LOG_LEVEL); + +/* The number of registered event handlers. */ +#define SMD_EVENT_HANDLERS_CNT ARRAY_SIZE(m_evt_handlers) + +/* Security Dispacher event handlers in Security Manager and GATT Cache Manager. */ +extern void sm_smd_evt_handler(pm_evt_t *p_event); + +/* Security Dispatcher events' handlers. + * The number of elements in this array is SMD_EVENT_HANDLERS_CNT. + */ +static pm_evt_handler_internal_t const m_evt_handlers[] = {sm_smd_evt_handler}; + +static bool m_module_initialized; + +static int m_flag_sec_proc = CONFIG_BLE_CONN_STATE_USER_FLAG_COUNT; +static int m_flag_sec_proc_pairing = CONFIG_BLE_CONN_STATE_USER_FLAG_COUNT; +static int m_flag_sec_proc_bonding = CONFIG_BLE_CONN_STATE_USER_FLAG_COUNT; +static int m_flag_allow_repairing = CONFIG_BLE_CONN_STATE_USER_FLAG_COUNT; + +static ble_gap_lesc_p256_pk_t m_peer_pk; + +static __INLINE bool sec_procedure(uint16_t conn_handle) +{ + return ble_conn_state_user_flag_get(conn_handle, m_flag_sec_proc); +} + +static __INLINE bool pairing(uint16_t conn_handle) +{ + return ble_conn_state_user_flag_get(conn_handle, m_flag_sec_proc_pairing); +} + +static __INLINE bool bonding(uint16_t conn_handle) +{ + return ble_conn_state_user_flag_get(conn_handle, m_flag_sec_proc_bonding); +} + +static __INLINE bool allow_repairing(uint16_t conn_handle) +{ + return ble_conn_state_user_flag_get(conn_handle, m_flag_allow_repairing); +} + +/** + * @brief Function for sending an SMD event to all event handlers. + * + * @param[in] p_event The event to pass to all event handlers. + */ +static void evt_send(pm_evt_t *p_event) +{ + p_event->peer_id = im_peer_id_get_by_conn_handle(p_event->conn_handle); + + for (uint32_t i = 0; i < SMD_EVENT_HANDLERS_CNT; i++) { + m_evt_handlers[i](p_event); + } +} + +/** + * @brief Function for sending a PM_EVT_CONN_SEC_START event. + * + * @param[in] conn_handle The connection handle the event pertains to. + * @param[in] procedure The procedure that has started on the connection. + */ +static void sec_start_send(uint16_t conn_handle, pm_conn_sec_procedure_t procedure) +{ + pm_evt_t evt = {.evt_id = PM_EVT_CONN_SEC_START, + .conn_handle = conn_handle, + .params = {.conn_sec_start = {.procedure = procedure}}}; + + evt_send(&evt); +} + +/** + * @brief Function for sending a PM_EVT_ERROR_UNEXPECTED event. + * + * @param[in] conn_handle The connection handle the event pertains to. + * @param[in] err_code The unexpected error that occurred. + */ +static void send_unexpected_error(uint16_t conn_handle, uint32_t err_code) +{ + pm_evt_t error_evt = {.evt_id = PM_EVT_ERROR_UNEXPECTED, + .conn_handle = conn_handle, + .params = {.error_unexpected = { + .error = err_code, + }}}; + + evt_send(&error_evt); +} + +/** + * @brief Function for sending a PM_EVT_STORAGE_FULL event. + * + * @param[in] conn_handle The connection handle the event pertains to. + */ +static void send_storage_full_evt(uint16_t conn_handle) +{ + pm_evt_t evt = {.evt_id = PM_EVT_STORAGE_FULL, .conn_handle = conn_handle}; + + evt_send(&evt); +} + +/** + * @brief Function for cleaning up after a failed security procedure. + * + * @param[in] conn_handle The handle of the connection the security procedure happens on. + * @param[in] procedure The procedure that failed. + * @param[in] error The error the procedure failed with. + * @param[in] error_src The party that raised the error. See @ref BLE_GAP_SEC_STATUS_SOURCES. + */ +static void conn_sec_failure(uint16_t conn_handle, pm_conn_sec_procedure_t procedure, + pm_sec_error_code_t error, uint8_t error_src) +{ + pm_evt_t evt = {.evt_id = PM_EVT_CONN_SEC_FAILED, + .conn_handle = conn_handle, + .params = {.conn_sec_failed = { + .procedure = procedure, + .error = error, + .error_src = error_src, + }}}; + + ble_conn_state_user_flag_set(conn_handle, m_flag_sec_proc, false); + + evt_send(&evt); +} + +/** + * @brief Function for cleaning up after a failed pairing procedure. + * + * @param[in] conn_handle The handle of the connection the pairing procedure happens on. + * @param[in] error The error the procedure failed with. + * @param[in] error_src The source of the error (local or remote). See @ref + * BLE_GAP_SEC_STATUS_SOURCES. + */ +static void pairing_failure(uint16_t conn_handle, pm_sec_error_code_t error, uint8_t error_src) +{ + uint32_t err_code = NRF_SUCCESS; + pm_conn_sec_procedure_t procedure = bonding(conn_handle) ? PM_CONN_SEC_PROCEDURE_BONDING + : PM_CONN_SEC_PROCEDURE_PAIRING; + + err_code = pdb_write_buf_release(PDB_TEMP_PEER_ID(conn_handle), PM_PEER_DATA_ID_BONDING); + if ((err_code != NRF_SUCCESS) && + (err_code != NRF_ERROR_NOT_FOUND /* No buffer was allocated */)) { + LOG_ERR("Could not clean up after failed bonding procedure. " + "pdb_write_buf_release() returned %s. conn_handle: %d.", + nrf_strerror_get(err_code), conn_handle); + send_unexpected_error(conn_handle, err_code); + } + + conn_sec_failure(conn_handle, procedure, error, error_src); +} + +/** + * @brief Function for cleaning up after a failed encryption procedure. + * + * @param[in] conn_handle The handle of the connection the encryption procedure happens on. + * @param[in] error The error the procedure failed with. + * @param[in] error_src The party that raised the error. See @ref BLE_GAP_SEC_STATUS_SOURCES. + */ +static __INLINE void encryption_failure(uint16_t conn_handle, pm_sec_error_code_t error, + uint8_t error_src) +{ + conn_sec_failure(conn_handle, PM_CONN_SEC_PROCEDURE_ENCRYPTION, error, error_src); +} + +/** + * @brief Function for possibly cleaning up after a failed pairing or encryption procedure. + * + * @param[in] conn_handle The handle of the connection the pairing procedure happens on. + * @param[in] error The error the procedure failed with. + * @param[in] error_src The party that raised the error. See @ref BLE_GAP_SEC_STATUS_SOURCES. + */ +static void link_secure_failure(uint16_t conn_handle, pm_sec_error_code_t error, uint8_t error_src) +{ + if (sec_procedure(conn_handle)) { + if (pairing(conn_handle)) { + pairing_failure(conn_handle, error, error_src); + } else { + encryption_failure(conn_handle, error, error_src); + } + } +} + +/** + * @brief Function for administrative actions to be taken when a security process has started. + * + * @param[in] conn_handle The connection the security process was attempted on. + * @param[in] success Whether the procedure was started successfully. + * @param[in] procedure The procedure that was started. + */ +static void sec_proc_start(uint16_t conn_handle, bool success, pm_conn_sec_procedure_t procedure) +{ + ble_conn_state_user_flag_set(conn_handle, m_flag_sec_proc, success); + if (success) { + ble_conn_state_user_flag_set(conn_handle, m_flag_sec_proc_pairing, + (procedure != PM_CONN_SEC_PROCEDURE_ENCRYPTION)); + ble_conn_state_user_flag_set(conn_handle, m_flag_sec_proc_bonding, + (procedure == PM_CONN_SEC_PROCEDURE_BONDING)); + sec_start_send(conn_handle, procedure); + } +} + +#ifdef CONFIG_SOFTDEVICE_CENTRAL +/** + * @brief Function for initiating encryption as a central. See @ref smd_link_secure for more + * info. + */ +static uint32_t link_secure_central_encryption(uint16_t conn_handle, pm_peer_id_t peer_id) +{ + pm_peer_data_flash_t peer_data; + uint32_t err_code; + ble_gap_enc_key_t const *p_existing_key = NULL; + bool lesc = false; + + err_code = pdb_peer_data_ptr_get(peer_id, PM_PEER_DATA_ID_BONDING, &peer_data); + + if (err_code == NRF_SUCCESS) { + /* Use peer's key since they are peripheral. */ + p_existing_key = &(peer_data.p_bonding_data->peer_ltk); + + lesc = peer_data.p_bonding_data->own_ltk.enc_info.lesc; + /* LESC was used during bonding. */ + if (lesc) { + /* For LESC, always use own key. */ + p_existing_key = &(peer_data.p_bonding_data->own_ltk); + } + } + + if ((err_code != NRF_SUCCESS) && (err_code != NRF_ERROR_NOT_FOUND)) { + if (err_code != NRF_ERROR_BUSY) { + NRF_LOG_ERROR("Could not retrieve stored bond. pdb_peer_data_ptr_get() " + "returned %s. " + "peer_id: %d", + nrf_strerror_get(err_code), peer_id); + err_code = NRF_ERROR_INTERNAL; + } + /* There is no bonding data stored. This means that a + * bonding procedure is in ongoing, or that the records + * in flash are in a bad state. + */ + } else if (p_existing_key == NULL) { + err_code = NRF_ERROR_BUSY; + } else if (!lesc && !im_master_id_is_valid(&(p_existing_key->master_id))) { + /* No LTK to encrypt with. */ + err_code = NRF_ERROR_INVALID_DATA; + } else { + /* Encrypt with existing LTK. */ + err_code = sd_ble_gap_encrypt(conn_handle, &(p_existing_key->master_id), + &(p_existing_key->enc_info)); + } + + sec_proc_start(conn_handle, err_code == NRF_SUCCESS, PM_CONN_SEC_PROCEDURE_ENCRYPTION); + + return err_code; +} + +/** @brief Function for intiating security as a central. See @ref smd_link_secure for more info. */ +static uint32_t link_secure_central(uint16_t conn_handle, ble_gap_sec_params_t *p_sec_params, + bool force_repairing) +{ + uint32_t err_code; + pm_peer_id_t peer_id; + + if (p_sec_params == NULL) { + return link_secure_authenticate(conn_handle, NULL); + } + + /* Set the default value for allowing repairing at the start of the sec proc. + * (for central) + */ + ble_conn_state_user_flag_set(conn_handle, m_flag_allow_repairing, force_repairing); + + peer_id = im_peer_id_get_by_conn_handle(conn_handle); + + if ((peer_id != PM_PEER_ID_INVALID) && !force_repairing) { + /* There is already data in flash for this peer, and repairing has not been + * requested, so the link will be encrypted with the existing keys. + */ + err_code = link_secure_central_encryption(conn_handle, peer_id); + } else { + /* There are no existing keys, or repairing has been explicitly requested, so + * pairing (possibly including bonding) will be performed to encrypt the link. + */ + err_code = link_secure_authenticate(conn_handle, p_sec_params); + pm_conn_sec_procedure_t procedure = (p_sec_params && p_sec_params->bond) + ? PM_CONN_SEC_PROCEDURE_BONDING + : PM_CONN_SEC_PROCEDURE_PAIRING; + sec_proc_start(conn_handle, err_code == NRF_SUCCESS, procedure); + } + + return err_code; +} + +/** + * @brief Function for processing the @ref BLE_GAP_EVT_SEC_REQUEST event from the SoftDevice. + * + * @param[in] p_gap_evt The event from the SoftDevice. + */ +static void sec_request_process(ble_gap_evt_t const *p_gap_evt) +{ + if (sec_procedure(p_gap_evt->conn_handle)) { + /* Ignore request as per spec. */ + return; + } + + pm_evt_t evt = {.evt_id = PM_EVT_SLAVE_SECURITY_REQ, .conn_handle = p_gap_evt->conn_handle}; + + memcpy(&evt.params.slave_security_req, &p_gap_evt->params.sec_request, + sizeof(ble_gap_evt_sec_request_t)); + + evt_send(&evt); +} +#endif /* CONFIG_SOFTDEVICE_CENTRAL */ + +#ifdef BLE_GAP_ROLE_PERIPH +/** @brief Function for processing the @ref BLE_GAP_EVT_SEC_INFO_REQUEST event from the SoftDevice. + * + * @param[in] p_gap_evt The event from the SoftDevice. + */ +static void sec_info_request_process(ble_gap_evt_t const *p_gap_evt) +{ + uint32_t err_code; + ble_gap_enc_info_t const *p_enc_info = NULL; + pm_peer_data_flash_t peer_data; + pm_peer_id_t peer_id = + im_peer_id_get_by_master_id(&p_gap_evt->params.sec_info_request.master_id); + + if (peer_id == PM_PEER_ID_INVALID) { + peer_id = im_peer_id_get_by_conn_handle(p_gap_evt->conn_handle); + } else { + /* The peer might have been unrecognized until now (since connecting). E.g. if using + * a random non-resolvable advertising address. Report the discovered peer ID just + * in case. + */ + im_new_peer_id(p_gap_evt->conn_handle, peer_id); + } + + sec_proc_start(p_gap_evt->conn_handle, true, PM_CONN_SEC_PROCEDURE_ENCRYPTION); + + if (peer_id != PM_PEER_ID_INVALID) { + err_code = pdb_peer_data_ptr_get(peer_id, PM_PEER_DATA_ID_BONDING, &peer_data); + + if (err_code == NRF_SUCCESS) { + /* There is stored bonding data for this peer. */ + ble_gap_enc_key_t const *p_existing_key = + &peer_data.p_bonding_data->own_ltk; + + if (p_gap_evt->params.sec_info_request.enc_info && + (p_existing_key->enc_info.lesc || + im_master_ids_compare( + &p_existing_key->master_id, + &p_gap_evt->params.sec_info_request.master_id))) { + p_enc_info = &p_existing_key->enc_info; + } + } + } + + err_code = sd_ble_gap_sec_info_reply(p_gap_evt->conn_handle, p_enc_info, NULL, NULL); + + if (err_code == NRF_ERROR_INVALID_STATE) { + /* Do nothing. If disconnecting, it will be caught later by the handling of the + * DISCONNECTED event. If there is no SEC_INFO_REQ pending, there is either a logic + * error, or the user is also calling sd_ble_gap_sec_info_reply(), but there is no + * way for the present code to detect which one is the case. + */ + LOG_WRN("sd_ble_gap_sec_info_reply() returned NRF_ERROR_INVALID_STATE, which is an " + "error unless the link is disconnecting."); + } else if (err_code != NRF_SUCCESS) { + LOG_ERR("Could not complete encryption procedure. sd_ble_gap_sec_info_reply() " + "returned %s. conn_handle: %d, peer_id: %d.", + nrf_strerror_get(err_code), p_gap_evt->conn_handle, peer_id); + send_unexpected_error(p_gap_evt->conn_handle, err_code); + } else if (p_gap_evt->params.sec_info_request.enc_info && (p_enc_info == NULL)) { + encryption_failure(p_gap_evt->conn_handle, PM_CONN_SEC_ERROR_PIN_OR_KEY_MISSING, + BLE_GAP_SEC_STATUS_SOURCE_LOCAL); + } +} +#endif /* BLE_GAP_ROLE_PERIPH */ + +/** + * @brief Function for sending a CONFIG_REQ event. + * + * @param[in] conn_handle The connection the sec parameters are needed for. + */ +static void send_config_req(uint16_t conn_handle) +{ + pm_evt_t evt; + + memset(&evt, 0, sizeof(evt)); + + evt.evt_id = PM_EVT_CONN_SEC_CONFIG_REQ; + evt.conn_handle = conn_handle; + + evt_send(&evt); +} + +void smd_conn_sec_config_reply(uint16_t conn_handle, pm_conn_sec_config_t *p_conn_sec_config) +{ + NRF_PM_DEBUG_CHECK(m_module_initialized); + NRF_PM_DEBUG_CHECK(p_conn_sec_config != NULL); + + ble_conn_state_user_flag_set(conn_handle, m_flag_allow_repairing, + p_conn_sec_config->allow_repairing); +} + +/** + * @brief Function for processing the @ref BLE_GAP_EVT_DISCONNECT event from the SoftDevice. + * + * @param[in] p_gap_evt The event from the SoftDevice. + */ +static void disconnect_process(ble_gap_evt_t const *p_gap_evt) +{ + pm_sec_error_code_t error = (p_gap_evt->params.disconnected.reason == + BLE_HCI_CONN_TERMINATED_DUE_TO_MIC_FAILURE) + ? PM_CONN_SEC_ERROR_MIC_FAILURE + : PM_CONN_SEC_ERROR_DISCONNECT; + + link_secure_failure(p_gap_evt->conn_handle, error, BLE_GAP_SEC_STATUS_SOURCE_LOCAL); +} + +/** + * @brief Function for sending a PARAMS_REQ event. + * + * @param[in] conn_handle The connection the security parameters are needed for. + * @param[in] p_peer_params The security parameters from the peer. Can be NULL if the peer's + * parameters are not yet available. + */ +static void send_params_req(uint16_t conn_handle, ble_gap_sec_params_t const *p_peer_params) +{ + pm_evt_t evt = { + .evt_id = PM_EVT_CONN_SEC_PARAMS_REQ, + .conn_handle = conn_handle, + .params = { + .conn_sec_params_req = {.p_peer_params = p_peer_params}, + }, + }; + + evt_send(&evt); +} + +/** + * @brief Function for processing the @ref BLE_GAP_EVT_SEC_PARAMS_REQUEST event from the SoftDevice. + * + * @param[in] p_gap_evt The event from the SoftDevice. + */ +static void sec_params_request_process(ble_gap_evt_t const *p_gap_evt) +{ +#ifdef BLE_GAP_ROLE_PERIPH + if (ble_conn_state_role(p_gap_evt->conn_handle) == BLE_GAP_ROLE_PERIPH) { + sec_proc_start(p_gap_evt->conn_handle, true, + p_gap_evt->params.sec_params_request.peer_params.bond + ? PM_CONN_SEC_PROCEDURE_BONDING + : PM_CONN_SEC_PROCEDURE_PAIRING); + } +#endif /* BLE_GAP_ROLE_PERIPH */ + + send_params_req(p_gap_evt->conn_handle, &p_gap_evt->params.sec_params_request.peer_params); +} + +/** + * @brief Function for sending a Peer Manager event indicating that pairing has succeeded. + * + * @param[in] p_gap_evt The AUTH_STATUS event from the SoftDevice that triggered this. + * @param[in] data_stored Whether bonding data was stored. + */ +static void pairing_success_evt_send(ble_gap_evt_t const *p_gap_evt, bool data_stored) +{ + pm_evt_t pairing_success_evt; + + pairing_success_evt.evt_id = PM_EVT_CONN_SEC_SUCCEEDED; + pairing_success_evt.conn_handle = p_gap_evt->conn_handle; + pairing_success_evt.params.conn_sec_succeeded.procedure = + p_gap_evt->params.auth_status.bonded ? PM_CONN_SEC_PROCEDURE_BONDING + : PM_CONN_SEC_PROCEDURE_PAIRING; + pairing_success_evt.params.conn_sec_succeeded.data_stored = data_stored; + + evt_send(&pairing_success_evt); +} + +/** + * @brief Function for processing the @ref BLE_GAP_EVT_AUTH_STATUS event from the SoftDevice, when + * the auth_status is success. + * + * @param[in] p_gap_evt The event from the SoftDevice. + */ +static void auth_status_success_process(ble_gap_evt_t const *p_gap_evt) +{ + uint32_t err_code; + uint16_t conn_handle = p_gap_evt->conn_handle; + pm_peer_id_t peer_id; + pm_peer_data_t peer_data; + bool new_peer_id = false; + + ble_conn_state_user_flag_set(conn_handle, m_flag_sec_proc, false); + + if (!p_gap_evt->params.auth_status.bonded) { + pairing_success_evt_send(p_gap_evt, false); + return; + } + + err_code = pdb_write_buf_get(PDB_TEMP_PEER_ID(conn_handle), PM_PEER_DATA_ID_BONDING, 1, + &peer_data); + if (err_code != NRF_SUCCESS) { + LOG_ERR("RAM buffer for new bond was unavailable. pdb_write_buf_get() " + "returned %s. conn_handle: %d.", + nrf_strerror_get(err_code), conn_handle); + send_unexpected_error(conn_handle, err_code); + pairing_success_evt_send(p_gap_evt, false); + return; + } + + peer_id = im_peer_id_get_by_conn_handle(conn_handle); + + if (peer_id == PM_PEER_ID_INVALID) { + peer_id = im_find_duplicate_bonding_data(peer_data.p_bonding_data, + PM_PEER_ID_INVALID); + + if (peer_id != PM_PEER_ID_INVALID) { + /* The peer has been identified as someone we have already bonded with. */ + im_new_peer_id(conn_handle, peer_id); + + /* If the flag is true, the configuration has been requested before. */ + if (!allow_repairing(conn_handle)) { + send_config_req(conn_handle); + if (!allow_repairing(conn_handle)) { + pairing_success_evt_send(p_gap_evt, false); + return; + } + } + } + } + + if (peer_id == PM_PEER_ID_INVALID) { + peer_id = pds_peer_id_allocate(); + if (peer_id == PM_PEER_ID_INVALID) { + LOG_ERR("Could not allocate new peer_id for incoming bond."); + send_unexpected_error(conn_handle, NRF_ERROR_NO_MEM); + pairing_success_evt_send(p_gap_evt, false); + return; + } + im_new_peer_id(conn_handle, peer_id); + new_peer_id = true; + } + + err_code = pdb_write_buf_store(PDB_TEMP_PEER_ID(conn_handle), PM_PEER_DATA_ID_BONDING, + peer_id); + + if (err_code == NRF_SUCCESS) { + pairing_success_evt_send(p_gap_evt, true); + } else if (err_code == NRF_ERROR_RESOURCES) { + send_storage_full_evt(conn_handle); + pairing_success_evt_send(p_gap_evt, true); + } else { + /* Unexpected error */ + LOG_ERR("Could not store bond. pdb_write_buf_store() returned %s. " + "conn_handle: %d, peer_id: %d", + nrf_strerror_get(err_code), conn_handle, peer_id); + send_unexpected_error(conn_handle, err_code); + pairing_success_evt_send(p_gap_evt, false); + if (new_peer_id) { + UNUSED_RETURN_VALUE( + /* We are already in a bad state. */ + im_peer_free(peer_id)); + } + } +} + +/** + * @brief Function for processing the @ref BLE_GAP_EVT_AUTH_STATUS event from the SoftDevice, when + * the auth_status is failure. + * + * @param[in] p_gap_evt The event from the SoftDevice. + */ +static void auth_status_failure_process(ble_gap_evt_t const *p_gap_evt) +{ + link_secure_failure(p_gap_evt->conn_handle, p_gap_evt->params.auth_status.auth_status, + p_gap_evt->params.auth_status.error_src); +} + +/** + * @brief Function for processing the @ref BLE_GAP_EVT_AUTH_STATUS event from the SoftDevice. + * + * @param[in] p_gap_evt The event from the SoftDevice. + */ +static void auth_status_process(ble_gap_evt_t const *p_gap_evt) +{ + switch (p_gap_evt->params.auth_status.auth_status) { + case BLE_GAP_SEC_STATUS_SUCCESS: + auth_status_success_process(p_gap_evt); + break; + + default: + auth_status_failure_process(p_gap_evt); +#if CONFIG_PM_RA_PROTECTION_ENABLED + ast_auth_error_notify(p_gap_evt->conn_handle); +#endif /* CONFIG_PM_RA_PROTECTION_ENABLED */ + break; + } +} + +/** + * @brief Function for processing the @ref BLE_GAP_EVT_CONN_SEC_UPDATE event from the SoftDevice. + * + * @param[in] p_gap_evt The event from the SoftDevice. + */ +static void conn_sec_update_process(ble_gap_evt_t const *p_gap_evt) +{ + if (!pairing(p_gap_evt->conn_handle)) { + /* This is an encryption procedure (not pairing), so this event marks the end of the + * procedure. + */ + if (!ble_conn_state_encrypted(p_gap_evt->conn_handle)) { + encryption_failure(p_gap_evt->conn_handle, + PM_CONN_SEC_ERROR_PIN_OR_KEY_MISSING, + BLE_GAP_SEC_STATUS_SOURCE_REMOTE); + } else { + ble_conn_state_user_flag_set(p_gap_evt->conn_handle, m_flag_sec_proc, + false); + + pm_evt_t evt; + + evt.evt_id = PM_EVT_CONN_SEC_SUCCEEDED; + evt.conn_handle = p_gap_evt->conn_handle; + evt.params.conn_sec_succeeded.procedure = PM_CONN_SEC_PROCEDURE_ENCRYPTION; + evt.params.conn_sec_succeeded.data_stored = false; + + evt_send(&evt); + } + } +} + +/** + * @brief Function for initializing a BLE Connection State user flag. + * + * @param[out] flag_id The flag to initialize. + */ +static void flag_id_init(int *p_flag_id) +{ + if (*p_flag_id == CONFIG_BLE_CONN_STATE_USER_FLAG_COUNT) { + *p_flag_id = ble_conn_state_user_flag_acquire(); + } +} + +uint32_t smd_init(void) +{ + NRF_PM_DEBUG_CHECK(!m_module_initialized); + + flag_id_init(&m_flag_sec_proc); + flag_id_init(&m_flag_sec_proc_pairing); + flag_id_init(&m_flag_sec_proc_bonding); + flag_id_init(&m_flag_allow_repairing); + + if ((m_flag_sec_proc == CONFIG_BLE_CONN_STATE_USER_FLAG_COUNT) || + (m_flag_sec_proc_pairing == CONFIG_BLE_CONN_STATE_USER_FLAG_COUNT) || + (m_flag_sec_proc_bonding == CONFIG_BLE_CONN_STATE_USER_FLAG_COUNT) || + (m_flag_allow_repairing == CONFIG_BLE_CONN_STATE_USER_FLAG_COUNT)) { + LOG_ERR("Could not acquire conn_state user flags. Increase " + "BLE_CONN_STATE_USER_FLAG_COUNT in the ble_conn_state module."); + return NRF_ERROR_INTERNAL; + } + +#if CONFIG_PM_RA_PROTECTION_ENABLED + uint32_t err_code = ast_init(); + + if (err_code != NRF_SUCCESS) { + return err_code; + } +#endif /* CONFIG_PM_RA_PROTECTION_ENABLED */ + + m_module_initialized = true; + + return NRF_SUCCESS; +} + +/** + * @brief Function for putting retrieving a buffer and putting pointers into a @ref + * ble_gap_sec_keyset_t. + * + * @param[in] conn_handle The connection the security procedure is happening on. + * @param[in] role Our role in the connection. + * @param[in] p_public_key Pointer to a buffer holding the public key, or NULL. + * @param[out] p_sec_keyset Pointer to the keyset to be filled. + * + * @retval NRF_SUCCESS Success. + * @retval NRF_ERROR_BUSY Could not process request at this time. Reattempt later. + * @retval NRF_ERROR_INVALID_PARAM Data ID or Peer ID was invalid or unallocated. + * @retval NRF_ERROR_INVALID_STATE The link is disconnected. + * @retval NRF_ERROR_INTERNAL Fatal error. + */ +static uint32_t sec_keyset_fill(uint16_t conn_handle, uint8_t role, + ble_gap_lesc_p256_pk_t *p_public_key, + ble_gap_sec_keyset_t *p_sec_keyset) +{ + uint32_t err_code; + pm_peer_data_t peer_data; + + if (p_sec_keyset == NULL) { + LOG_ERR("Internal error: %s received NULL for p_sec_keyset.", __func__); + return NRF_ERROR_INTERNAL; + } + + /* Acquire a memory buffer to receive bonding data into. */ + err_code = pdb_write_buf_get(PDB_TEMP_PEER_ID(conn_handle), PM_PEER_DATA_ID_BONDING, 1, + &peer_data); + + if (err_code == NRF_ERROR_BUSY) { + /* No action. */ + } else if (err_code != NRF_SUCCESS) { + LOG_ERR("Could not retrieve RAM buffer for incoming bond. pdb_write_buf_get() " + "returned %s. conn_handle: %d", + nrf_strerror_get(err_code), conn_handle); + err_code = NRF_ERROR_INTERNAL; + } else { + memset(peer_data.p_bonding_data, 0, sizeof(pm_peer_data_bonding_t)); + + peer_data.p_bonding_data->own_role = role; + + p_sec_keyset->keys_own.p_enc_key = &peer_data.p_bonding_data->own_ltk; + p_sec_keyset->keys_own.p_pk = p_public_key; + p_sec_keyset->keys_peer.p_enc_key = &peer_data.p_bonding_data->peer_ltk; + p_sec_keyset->keys_peer.p_id_key = &peer_data.p_bonding_data->peer_ble_id; + p_sec_keyset->keys_peer.p_pk = &m_peer_pk; + + /* Retrieve the address the peer used during connection establishment. + * This address will be overwritten if ID is shared. Should not fail. + */ + err_code = im_ble_addr_get(conn_handle, + &peer_data.p_bonding_data->peer_ble_id.id_addr_info); + if (err_code != NRF_SUCCESS) { + LOG_WRN("im_ble_addr_get() returned %s. conn_handle: %d. Link was " + "likely disconnected.", + nrf_strerror_get(err_code), conn_handle); + return NRF_ERROR_INVALID_STATE; + } + } + + return err_code; +} + +uint32_t smd_params_reply(uint16_t conn_handle, ble_gap_sec_params_t *p_sec_params, + ble_gap_lesc_p256_pk_t *p_public_key) +{ + NRF_PM_DEBUG_CHECK(m_module_initialized); + + uint8_t role = ble_conn_state_role(conn_handle); + uint32_t err_code = NRF_SUCCESS; + uint8_t sec_status = BLE_GAP_SEC_STATUS_SUCCESS; + ble_gap_sec_keyset_t sec_keyset; + + memset(&sec_keyset, 0, sizeof(ble_gap_sec_keyset_t)); +#ifdef BLE_GAP_ROLE_PERIPH + if (role == BLE_GAP_ROLE_PERIPH) { + /* Set the default value for allowing repairing at the start of the sec proc. (for + * peripheral) + */ + ble_conn_state_user_flag_set(conn_handle, m_flag_allow_repairing, false); + } +#endif /* BLE_GAP_ROLE_PERIPH */ + + if (role == BLE_GAP_ROLE_INVALID) { + return BLE_ERROR_INVALID_CONN_HANDLE; + } + +#if CONFIG_PM_RA_PROTECTION_ENABLED + /* Check for repeated attempts here. */ + if (ast_peer_blacklisted(conn_handle)) { + sec_status = BLE_GAP_SEC_STATUS_REPEATED_ATTEMPTS; + } else +#endif /* CONFIG_PM_RA_PROTECTION_ENABLED */ + if (p_sec_params == NULL) { + /* NULL params means reject pairing. */ + sec_status = BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP; + } else { +#ifdef BLE_GAP_ROLE_PERIPH + if ((im_peer_id_get_by_conn_handle(conn_handle) != PM_PEER_ID_INVALID) && + (role == BLE_GAP_ROLE_PERIPH) && !allow_repairing(conn_handle)) { + /* Bond already exists. Reject the pairing request if the user + * doesn't intervene. + */ + send_config_req(conn_handle); + if (!allow_repairing(conn_handle)) { + /* Reject pairing. */ + sec_status = BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP; + } + } +#endif /* BLE_GAP_ROLE_PERIPH */ + + if (!p_sec_params->bond) { + /* Pairing, no bonding. */ + sec_keyset.keys_own.p_pk = p_public_key; + sec_keyset.keys_peer.p_pk = &m_peer_pk; + } else if (sec_status != BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP) { + /* Bonding is to be performed, prepare to receive bonding data. */ + err_code = sec_keyset_fill(conn_handle, role, p_public_key, + &sec_keyset); + } + } + + if (err_code == NRF_SUCCESS) { + /* Everything OK, reply to SoftDevice. If an error happened, the user is given an + * opportunity to change the parameters and retry the call. + */ + ble_gap_sec_params_t *p_aux_sec_params = NULL; +#ifdef BLE_GAP_ROLE_PERIPH + p_aux_sec_params = (role == BLE_GAP_ROLE_PERIPH) ? p_sec_params : NULL; +#endif /* BLE_GAP_ROLE_PERIPH */ + + err_code = sd_ble_gap_sec_params_reply(conn_handle, sec_status, p_aux_sec_params, + &sec_keyset); + } + + return err_code; +} + +/** + * @brief Function for initiating pairing as a central, or all security as a periheral. + * + * See @ref smd_link_secure and @ref sd_ble_gap_authenticate for more information. + */ +static uint32_t link_secure_authenticate(uint16_t conn_handle, ble_gap_sec_params_t *p_sec_params) +{ + uint32_t err_code = sd_ble_gap_authenticate(conn_handle, p_sec_params); + + if (err_code == NRF_ERROR_NO_MEM) { + /* sd_ble_gap_authenticate() returned NRF_ERROR_NO_MEM. Too many other sec + * procedures running. + */ + err_code = NRF_ERROR_BUSY; + } + + return err_code; +} + +#ifdef BLE_GAP_ROLE_PERIPH +/** @brief Function for asking the central to secure the link. See @ref smd_link_secure for more + * info. + */ +static uint32_t link_secure_peripheral(uint16_t conn_handle, ble_gap_sec_params_t *p_sec_params) +{ + uint32_t err_code = NRF_SUCCESS; + + if (p_sec_params != NULL) { + err_code = link_secure_authenticate(conn_handle, p_sec_params); + } + + return err_code; +} +#endif + +uint32_t smd_link_secure(uint16_t conn_handle, ble_gap_sec_params_t *p_sec_params, + bool force_repairing) +{ + NRF_PM_DEBUG_CHECK(m_module_initialized); + + uint8_t role = ble_conn_state_role(conn_handle); + + switch (role) { +#ifdef BLE_GAP_ROLE_PERIPH + case BLE_GAP_ROLE_PERIPH: + return link_secure_peripheral(conn_handle, p_sec_params); +#endif /* BLE_GAP_ROLE_PERIPH */ + + default: + return BLE_ERROR_INVALID_CONN_HANDLE; + } +} + +void smd_ble_evt_handler(ble_evt_t const *p_ble_evt) +{ + switch (p_ble_evt->header.evt_id) { + case BLE_GAP_EVT_DISCONNECTED: + disconnect_process(&(p_ble_evt->evt.gap_evt)); + break; + + case BLE_GAP_EVT_SEC_PARAMS_REQUEST: + sec_params_request_process(&(p_ble_evt->evt.gap_evt)); + break; + +#ifdef BLE_GAP_ROLE_PERIPH + case BLE_GAP_EVT_SEC_INFO_REQUEST: + sec_info_request_process(&(p_ble_evt->evt.gap_evt)); + break; +#endif /* BLE_GAP_ROLE_PERIPH */ + + case BLE_GAP_EVT_AUTH_STATUS: + auth_status_process(&(p_ble_evt->evt.gap_evt)); + break; + + case BLE_GAP_EVT_CONN_SEC_UPDATE: + conn_sec_update_process(&(p_ble_evt->evt.gap_evt)); + break; + }; +} diff --git a/lib/peer_manager/modules/security_manager.c b/lib/peer_manager/modules/security_manager.c new file mode 100644 index 0000000000..1d723bda64 --- /dev/null +++ b/lib/peer_manager/modules/security_manager.c @@ -0,0 +1,679 @@ +/* + * Copyright (c) 2015-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if CONFIG_PM_LESC_ENABLED +#include +#endif + +LOG_MODULE_DECLARE(peer_manager, CONFIG_PEER_MANAGER_LOG_LEVEL); + +/* The number of registered event handlers. */ +#define SM_EVENT_HANDLERS_CNT ARRAY_SIZE(m_evt_handlers) + +/* Security Manager event handler in Peer Manager. */ +extern void pm_sm_evt_handler(pm_evt_t *p_sm_evt); + +/* Security Manager events' handlers. + * The number of elements in this array is SM_EVENT_HANDLERS_CNT. + */ +static pm_evt_handler_internal_t const m_evt_handlers[] = {pm_sm_evt_handler}; + +/* The context type that is used in PM_EVT_CONN_SEC_PARAMS_REQ events and in calls to + * sm_sec_params_reply(). + */ +typedef struct { + /* The security parameters to use in the call to the security_dispatcher */ + ble_gap_sec_params_t *p_sec_params; + /* The buffer for holding the security parameters. */ + ble_gap_sec_params_t sec_params_mem; + /* Whether @ref sm_sec_params_reply has been called for this context instance. */ + bool params_reply_called; +} sec_params_reply_context_t; + +/* Whether the Security Manager module has been initialized. */ +static bool m_module_initialized; + +/** The buffer for the default security parameters set by @ref sm_sec_params_set. */ +static ble_gap_sec_params_t m_sec_params; +/** The default security parameters set by @ref sm_sec_params_set. */ +static ble_gap_sec_params_t *mp_sec_params; +/** Whether @ref sm_sec_params_set has been called. */ +static bool m_sec_params_set; + +#if CONFIG_PM_LESC_ENABLED == 0 +/* Pointer, provided by the user, to the public key to use for LESC procedures. */ +static ble_gap_lesc_p256_pk_t *m_p_public_key; +#endif + +/* User flag indicating whether a connection has a pending call to @ref sm_link_secure because it + * returned @ref NRF_ERROR_BUSY. + */ +static int m_flag_link_secure_pending_busy = CONFIG_BLE_CONN_STATE_USER_FLAG_COUNT; +/* User flag indicating whether a pending call to @ref sm_link_secure should be called with true for + * the force_repairing parameter. + */ +static int m_flag_link_secure_force_repairing = CONFIG_BLE_CONN_STATE_USER_FLAG_COUNT; +/* User flag indicating whether a pending call to @ref sm_link_secure should be called with NULL + * security parameters. + */ +static int m_flag_link_secure_null_params = CONFIG_BLE_CONN_STATE_USER_FLAG_COUNT; +/* User flag indicating whether a connection has a pending call to @ref sm_sec_params_reply because + * it returned @ref NRF_ERROR_BUSY. + */ +static int m_flag_params_reply_pending_busy = CONFIG_BLE_CONN_STATE_USER_FLAG_COUNT; + +/** + * @brief Function for sending an SM event to all registered event handlers. + * + * @param[in] p_event The event to send. + */ +static void evt_send(pm_evt_t *p_event) +{ + for (uint32_t i = 0; i < SM_EVENT_HANDLERS_CNT; i++) { + m_evt_handlers[i](p_event); + } +} + +/** + * @brief Function for setting or clearing user flags based on error codes returned from @ref + * smd_link_secure or @ref smd_params_reply. + * + * @param[in] conn_handle The connection the call pertained to. + * @param[in] err_code The error code returned from @ref smd_link_secure or + * @ref smd_params_reply. + * @param[in] params_reply Whether the call was to @ref smd_params_reply. + */ +static void flags_set_from_err_code(uint16_t conn_handle, uint32_t err_code, bool params_reply) +{ + bool flag_value_busy = false; + + if (err_code == NRF_ERROR_BUSY) { + flag_value_busy = true; + } else { + flag_value_busy = false; + } + + if (params_reply) { + ble_conn_state_user_flag_set(conn_handle, m_flag_params_reply_pending_busy, + flag_value_busy); + ble_conn_state_user_flag_set(conn_handle, m_flag_link_secure_pending_busy, false); + } else { + ble_conn_state_user_flag_set(conn_handle, m_flag_link_secure_pending_busy, + flag_value_busy); + } +} + +static inline pm_evt_t new_evt(pm_evt_id_t evt_id, uint16_t conn_handle) +{ + pm_evt_t evt = {.evt_id = evt_id, + .conn_handle = conn_handle, + .peer_id = im_peer_id_get_by_conn_handle(conn_handle)}; + return evt; +} + +/** + * @brief Function for sending a PM_EVT_ERROR_UNEXPECTED event. + * + * @param[in] conn_handle The connection handle the event pertains to. + * @param[in] err_code The unexpected error that occurred. + */ +static void send_unexpected_error(uint16_t conn_handle, uint32_t err_code) +{ + pm_evt_t error_evt = new_evt(PM_EVT_ERROR_UNEXPECTED, conn_handle); + + error_evt.params.error_unexpected.error = err_code; + evt_send(&error_evt); +} + +/** + * @brief Returns whether the LTK came from LESC bonding. + * + * @param[in] peer_id The peer to check. + * + * @return Whether the key is LESC or not. + */ +static bool key_is_lesc(pm_peer_id_t peer_id) +{ + pm_peer_data_flash_t peer_data; + uint32_t err_code; + + err_code = pdb_peer_data_ptr_get(peer_id, PM_PEER_DATA_ID_BONDING, &peer_data); + + return (err_code == NRF_SUCCESS) && (peer_data.p_bonding_data->own_ltk.enc_info.lesc); +} + +/** + * @brief Function for sending an event based on error codes returned from @ref smd_link_secure or + * @ref smd_params_reply. + * + * @param[in] conn_handle The connection the event pertains to. + * @param[in] err_code The error code returned from @ref smd_link_secure or + * @ref smd_params_reply. + * @param[in] p_sec_params The security parameters attempted to pass in the call to + * @ref smd_link_secure or @ref smd_params_reply. + */ +static void events_send_from_err_code(uint16_t conn_handle, uint32_t err_code, + ble_gap_sec_params_t *p_sec_params) +{ + if ((err_code != NRF_SUCCESS) && (err_code != NRF_ERROR_BUSY) && + (err_code != NRF_ERROR_INVALID_STATE)) { + if (err_code == NRF_ERROR_TIMEOUT) { + LOG_WRN("Cannot secure link because a previous security procedure " + "ended in timeout. " + "Disconnect and retry. smd_params_reply() or " + "smd_link_secure() returned " + "NRF_ERROR_TIMEOUT. conn_handle: %d", + conn_handle); + + pm_evt_t evt = new_evt(PM_EVT_CONN_SEC_FAILED, conn_handle); + + evt.params.conn_sec_failed.procedure = + ((p_sec_params != NULL) && p_sec_params->bond) + ? PM_CONN_SEC_PROCEDURE_BONDING + : PM_CONN_SEC_PROCEDURE_PAIRING; + evt.params.conn_sec_failed.error_src = BLE_GAP_SEC_STATUS_SOURCE_LOCAL; + evt.params.conn_sec_failed.error = PM_CONN_SEC_ERROR_SMP_TIMEOUT; + evt_send(&evt); + } else { + LOG_ERR("Could not perform security procedure. smd_params_reply() or " + "smd_link_secure() returned %s. conn_handle: %d", + nrf_strerror_get(err_code), conn_handle); + send_unexpected_error(conn_handle, err_code); + } + } +} + +/** + * @brief Function for sending an PM_EVT_CONN_SEC_PARAMS_REQ event. + * + * @param[in] conn_handle The connection the event pertains to. + * @param[in] p_peer_params The peer's security parameters to include in the event. Can be NULL. + * @param[in] p_context Pointer to a context that the user must include in the call to @ref + * sm_sec_params_reply(). + */ +static void params_req_send(uint16_t conn_handle, ble_gap_sec_params_t const *p_peer_params, + sec_params_reply_context_t *p_context) +{ + pm_evt_t evt = new_evt(PM_EVT_CONN_SEC_PARAMS_REQ, conn_handle); + + evt.params.conn_sec_params_req.p_peer_params = p_peer_params; + evt.params.conn_sec_params_req.p_context = p_context; + + evt_send(&evt); +} + +/** + * @brief Function for creating a new @ref sec_params_reply_context_t with the correct initial + * values. + * + * @return The new context. + */ +static sec_params_reply_context_t new_context_get(void) +{ + sec_params_reply_context_t new_context = {.p_sec_params = mp_sec_params, + .params_reply_called = false}; + return new_context; +} + +/** + * @brief Internal function corresponding to @ref sm_link_secure. + * + * @param[in] conn_handle The connection to secure. + * @param[in] null_params Whether to pass NULL security parameters to the security_dispatcher. + * @param[in] force_repairing Whether to force rebonding if peer exists. + * @param[in] send_events Whether to send events based on the result of @ref smd_link_secure. + * + * @return Same return codes as @ref sm_link_secure. + */ +static uint32_t link_secure(uint16_t conn_handle, bool null_params, bool force_repairing, + bool send_events) +{ + uint32_t err_code; + uint32_t return_err_code; + ble_gap_sec_params_t *p_sec_params; + + if (null_params) { + p_sec_params = NULL; + } else { + sec_params_reply_context_t context = new_context_get(); + + params_req_send(conn_handle, NULL, &context); + p_sec_params = context.p_sec_params; + + if (!m_sec_params_set && !context.params_reply_called) { + /* Security parameters have not been set. */ + return NRF_ERROR_NOT_FOUND; + } + } + + err_code = smd_link_secure(conn_handle, p_sec_params, force_repairing); + + flags_set_from_err_code(conn_handle, err_code, false); + + switch (err_code) { + case NRF_ERROR_BUSY: + ble_conn_state_user_flag_set(conn_handle, m_flag_link_secure_null_params, + null_params); + ble_conn_state_user_flag_set(conn_handle, m_flag_link_secure_force_repairing, + force_repairing); + return_err_code = NRF_SUCCESS; + break; + case NRF_SUCCESS: + case NRF_ERROR_TIMEOUT: + case BLE_ERROR_INVALID_CONN_HANDLE: + case NRF_ERROR_INVALID_STATE: + case NRF_ERROR_INVALID_DATA: + return_err_code = err_code; + break; + default: + LOG_ERR("Could not perform security procedure. smd_link_secure() returned %s. " + "conn_handle: %d", + nrf_strerror_get(err_code), conn_handle); + return_err_code = NRF_ERROR_INTERNAL; + break; + } + + if (send_events) { + events_send_from_err_code(conn_handle, err_code, p_sec_params); + } + + return return_err_code; +} + +/** + * @brief Function for requesting security parameters from the user and passing them to the + * security_dispatcher. + * + * @param[in] conn_handle The connection that needs security parameters. + * @param[in] p_peer_params The peer's security parameters if present. Otherwise NULL. + */ +static void smd_params_reply_perform(uint16_t conn_handle, + ble_gap_sec_params_t const *p_peer_params) +{ + uint32_t err_code; + ble_gap_lesc_p256_pk_t *p_public_key; + sec_params_reply_context_t context = new_context_get(); + + params_req_send(conn_handle, p_peer_params, &context); + +#if CONFIG_PM_LESC_ENABLED + p_public_key = nrf_ble_lesc_public_key_get(); +#else + p_public_key = m_p_public_key; +#endif /* CONFIG_PM_LESC_ENABLED */ + err_code = smd_params_reply(conn_handle, context.p_sec_params, p_public_key); + + flags_set_from_err_code(conn_handle, err_code, true); + events_send_from_err_code(conn_handle, err_code, context.p_sec_params); +} + +/** + * @brief Function for handling @ref PM_EVT_CONN_SEC_PARAMS_REQ events. + * + * @param[in] p_event The @ref PM_EVT_CONN_SEC_PARAMS_REQ event. + */ +static __INLINE void params_req_process(pm_evt_t const *p_event) +{ + smd_params_reply_perform(p_event->conn_handle, + p_event->params.conn_sec_params_req.p_peer_params); +} + +uint32_t sm_conn_sec_status_get(uint16_t conn_handle, pm_conn_sec_status_t *p_conn_sec_status) +{ + VERIFY_PARAM_NOT_NULL(p_conn_sec_status); + + int status = ble_conn_state_status(conn_handle); + + if (status == BLE_CONN_STATUS_INVALID) { + return BLE_ERROR_INVALID_CONN_HANDLE; + } + + pm_peer_id_t peer_id = im_peer_id_get_by_conn_handle(conn_handle); + + p_conn_sec_status->connected = (status == BLE_CONN_STATUS_CONNECTED); + p_conn_sec_status->bonded = (peer_id != PM_PEER_ID_INVALID); + p_conn_sec_status->encrypted = ble_conn_state_encrypted(conn_handle); + p_conn_sec_status->mitm_protected = ble_conn_state_mitm_protected(conn_handle); + p_conn_sec_status->lesc = ble_conn_state_lesc(conn_handle) || + (ble_conn_state_encrypted(conn_handle) && key_is_lesc(peer_id)); + return NRF_SUCCESS; +} + +bool sm_sec_is_sufficient(uint16_t conn_handle, pm_conn_sec_status_t *p_sec_status_req) +{ + /* Set all bits in reserved to 1 so they are ignored in subsequent logic. */ + pm_conn_sec_status_t sec_status = {.reserved = ~0}; + uint32_t err_code = sm_conn_sec_status_get(conn_handle, &sec_status); + + __ASSERT(sizeof(pm_conn_sec_status_t) == sizeof(uint8_t), ""); + + uint8_t unmet_reqs = (~(*((uint8_t *)&sec_status)) & *((uint8_t *)p_sec_status_req)); + + return (err_code == NRF_SUCCESS) && !unmet_reqs; +} + +#ifdef CONFIG_SOFTDEVICE_CENTRAL +/** + * @brief Function for handling @ref PM_EVT_SLAVE_SECURITY_REQ events. + * + * @param[in] p_event The @ref PM_EVT_SLAVE_SECURITY_REQ event. + */ +static void sec_req_process(pm_evt_t const *p_event) +{ + uint32_t err_code; + bool null_params = false; + bool force_repairing = false; + + if (mp_sec_params == NULL) { + null_params = true; + } else if (ble_conn_state_encrypted(p_event->conn_handle)) { + pm_conn_sec_status_t sec_status_req = { + .bonded = p_event->params.slave_security_req.bond, + .mitm_protected = p_event->params.slave_security_req.mitm, + .lesc = p_event->params.slave_security_req.lesc, + }; + + force_repairing = !sm_sec_is_sufficient(p_event->conn_handle, &sec_status_req); + } + + err_code = link_secure(p_event->conn_handle, null_params, force_repairing, true); + /* The error code has been properly handled inside link_secure(). */ + UNUSED_VARIABLE(err_code); +} +#endif + +/** + * @brief Function for translating an SMD event to an SM event and passing it on to SM event + * handlers. + * + * @param[in] p_event The event to forward. + */ +static void evt_forward(pm_evt_t *p_event) +{ + evt_send(p_event); +} + +/** + * @brief Event handler for events from the Security Dispatcher module. + * This handler is extern in Security Dispatcher. + * + * @param[in] p_event The event that has happened. + */ +void sm_smd_evt_handler(pm_evt_t *p_event) +{ + switch (p_event->evt_id) { + case PM_EVT_CONN_SEC_PARAMS_REQ: + params_req_process(p_event); + break; + case PM_EVT_SLAVE_SECURITY_REQ: +#ifdef CONFIG_SOFTDEVICE_CENTRAL + sec_req_process(p_event); +#endif + /* fallthrough */ + default: + /* Forward the event to all registered Security Manager event handlers. */ + evt_forward(p_event); + break; + } +} + +/** @brief Function handling a pending params_reply. See @ref ble_conn_state_user_function_t. */ +static void params_reply_pending_handle(uint16_t conn_handle, void *p_context) +{ + UNUSED_PARAMETER(p_context); + smd_params_reply_perform(conn_handle, NULL); +} + +/** @brief Function handling a pending link_secure. See @ref ble_conn_state_user_function_t. */ +static void link_secure_pending_handle(uint16_t conn_handle, void *p_context) +{ + UNUSED_PARAMETER(p_context); + + bool force_repairing = + ble_conn_state_user_flag_get(conn_handle, m_flag_link_secure_force_repairing); + bool null_params = + ble_conn_state_user_flag_get(conn_handle, m_flag_link_secure_null_params); + + /* If this fails, it will be automatically retried. */ + uint32_t err_code = link_secure(conn_handle, null_params, force_repairing, true); + + UNUSED_VARIABLE(err_code); +} + +/** + * @brief Event handler for events from the Peer Database module. + * This handler is extern in Peer Database. + * + * @param[in] p_event The event that has happened. + */ +void sm_pdb_evt_handler(pm_evt_t *p_event) +{ + switch (p_event->evt_id) { + case PM_EVT_FLASH_GARBAGE_COLLECTED: + case PM_EVT_PEER_DATA_UPDATE_SUCCEEDED: + case PM_EVT_PEER_DATA_UPDATE_FAILED: + case PM_EVT_PEER_DELETE_SUCCEEDED: + case PM_EVT_PEER_DELETE_FAILED: + (void)ble_conn_state_for_each_set_user_flag(m_flag_params_reply_pending_busy, + params_reply_pending_handle, NULL); + (void)ble_conn_state_for_each_set_user_flag(m_flag_link_secure_pending_busy, + link_secure_pending_handle, NULL); + break; + default: + /* Do nothing. */ + break; + } +} + +/** + * @brief Function for initializing a BLE Connection State user flag. + * + * @param[out] flag_id The flag to initialize. + */ +static void flag_id_init(int *p_flag_id) +{ + if (*p_flag_id == CONFIG_BLE_CONN_STATE_USER_FLAG_COUNT) { + *p_flag_id = ble_conn_state_user_flag_acquire(); + } +} + +uint32_t sm_init(void) +{ + NRF_PM_DEBUG_CHECK(!m_module_initialized); + +#if CONFIG_PM_LESC_ENABLED + uint32_t err_code = nrf_ble_lesc_init(); + + if (err_code != NRF_SUCCESS) { + return err_code; + } +#endif + + flag_id_init(&m_flag_link_secure_pending_busy); + flag_id_init(&m_flag_link_secure_force_repairing); + flag_id_init(&m_flag_link_secure_null_params); + flag_id_init(&m_flag_params_reply_pending_busy); + + if (m_flag_params_reply_pending_busy == CONFIG_BLE_CONN_STATE_USER_FLAG_COUNT) { + LOG_ERR("Could not acquire conn_state user flags. Increase " + "BLE_CONN_STATE_USER_FLAG_COUNT in the ble_conn_state module."); + return NRF_ERROR_INTERNAL; + } + + m_module_initialized = true; + + return NRF_SUCCESS; +} + +void sm_ble_evt_handler(ble_evt_t const *p_ble_evt) +{ + NRF_PM_DEBUG_CHECK(p_ble_evt != NULL); + + smd_ble_evt_handler(p_ble_evt); +#if CONFIG_PM_LESC_ENABLED + nrf_ble_lesc_on_ble_evt(p_ble_evt); +#endif + (void)ble_conn_state_for_each_set_user_flag(m_flag_params_reply_pending_busy, + params_reply_pending_handle, NULL); + (void)ble_conn_state_for_each_set_user_flag(m_flag_link_secure_pending_busy, + link_secure_pending_handle, NULL); +} + +/** + * @brief Function for checking whether security parameters are valid. + * + * @param[out] p_sec_params The security parameters to verify. + * + * @return Whether the security parameters are valid. + */ +static bool sec_params_verify(ble_gap_sec_params_t *p_sec_params) +{ + /* NULL check. */ + if (p_sec_params == NULL) { + return false; + } + + /* OOB not allowed unless MITM. */ + if (!p_sec_params->mitm && p_sec_params->oob) { + return false; + } + + /* IO Capabilities must be one of the valid values from @ref BLE_GAP_IO_CAPS. */ + if (p_sec_params->io_caps > BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY) { + return false; + } + + /* Must have either IO capabilities or OOB if MITM. */ + if (p_sec_params->mitm && (p_sec_params->io_caps == BLE_GAP_IO_CAPS_NONE) && + !p_sec_params->oob) { + return false; + } + + /* Minimum key size cannot be larger than maximum key size. */ + if (p_sec_params->min_key_size > p_sec_params->max_key_size) { + return false; + } + + /* Key size cannot be below 7 bytes. */ + if (p_sec_params->min_key_size < 7) { + return false; + } + + /* Key size cannot be above 16 bytes. */ + if (p_sec_params->max_key_size > 16) { + return false; + } + + /* Signing is not supported. */ + if (p_sec_params->kdist_own.sign || p_sec_params->kdist_peer.sign) { + return false; + } + + /* link bit must be 0. */ + if (p_sec_params->kdist_own.link || p_sec_params->kdist_peer.link) { + return false; + } + + /* If bonding is not enabled, no keys can be distributed. */ + if (!p_sec_params->bond && (p_sec_params->kdist_own.enc || p_sec_params->kdist_own.id || + p_sec_params->kdist_peer.enc || p_sec_params->kdist_peer.id)) { + return false; + } + + /* If bonding is enabled, one or more keys must be distributed. */ + if (p_sec_params->bond && !p_sec_params->kdist_own.enc && !p_sec_params->kdist_own.id && + !p_sec_params->kdist_peer.enc && !p_sec_params->kdist_peer.id) { + return false; + } + + return true; +} + +uint32_t sm_sec_params_set(ble_gap_sec_params_t *p_sec_params) +{ + NRF_PM_DEBUG_CHECK(m_module_initialized); + + if (p_sec_params == NULL) { + mp_sec_params = NULL; + m_sec_params_set = true; + return NRF_SUCCESS; + } else if (sec_params_verify(p_sec_params)) { + m_sec_params = *p_sec_params; + mp_sec_params = &m_sec_params; + m_sec_params_set = true; + return NRF_SUCCESS; + } else { + return NRF_ERROR_INVALID_PARAM; + } +} + +void sm_conn_sec_config_reply(uint16_t conn_handle, pm_conn_sec_config_t *p_conn_sec_config) +{ + NRF_PM_DEBUG_CHECK(m_module_initialized); + NRF_PM_DEBUG_CHECK(p_conn_sec_config != NULL); + + smd_conn_sec_config_reply(conn_handle, p_conn_sec_config); +} + +uint32_t sm_sec_params_reply(uint16_t conn_handle, ble_gap_sec_params_t *p_sec_params, + void const *p_context) +{ + NRF_PM_DEBUG_CHECK(m_module_initialized); + VERIFY_PARAM_NOT_NULL(p_context); + + sec_params_reply_context_t *p_sec_params_reply_context = + (sec_params_reply_context_t *)p_context; + if (p_sec_params == NULL) { + /* Set the store pointer to NULL, so that NULL is passed to the SoftDevice. */ + p_sec_params_reply_context->p_sec_params = NULL; + } else if (sec_params_verify(p_sec_params)) { + /* Copy the provided sec_params into the store. */ + p_sec_params_reply_context->sec_params_mem = *p_sec_params; + p_sec_params_reply_context->p_sec_params = + &p_sec_params_reply_context->sec_params_mem; + } else { + return NRF_ERROR_INVALID_PARAM; + } + p_sec_params_reply_context->params_reply_called = true; + + return NRF_SUCCESS; +} + +uint32_t sm_lesc_public_key_set(ble_gap_lesc_p256_pk_t *p_public_key) +{ + NRF_PM_DEBUG_CHECK(m_module_initialized); + +#if CONFIG_PM_LESC_ENABLED + return NRF_ERROR_FORBIDDEN; +#else + m_p_public_key = p_public_key; + + return NRF_SUCCESS; +#endif /* CONFIG_PM_LESC_ENABLED */ +} + +uint32_t sm_link_secure(uint16_t conn_handle, bool force_repairing) +{ + uint32_t ret; + + NRF_PM_DEBUG_CHECK(m_module_initialized); + + ret = link_secure(conn_handle, false, force_repairing, false); + return ret; +} diff --git a/lib/peer_manager/nrf_strerror.c b/lib/peer_manager/nrf_strerror.c new file mode 100644 index 0000000000..19cc67b131 --- /dev/null +++ b/lib/peer_manager/nrf_strerror.c @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2011-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include + +/** + * @brief Macro for adding an entity to the description array. + * + * Macro that helps to create a single entity in the description array. + */ +#define NRF_STRERROR_ENTITY(mnemonic) \ + { \ + .code = mnemonic, .name = #mnemonic \ + } + +/** + * @brief Array entity element that describes an error. + */ +typedef struct { + uint32_t code; /**< Error code. */ + char const *name; /**< Descriptive name (the same as the internal error mnemonic). */ +} nrf_strerror_desc_t; + +/** + * @brief Unknown error code. + * + * The constant string used by @ref nrf_strerror_get when the error description was not found. + */ +static char const m_unknown_str[] = "Unknown error code"; + +/** + * @brief Array with error codes. + * + * Array that describes error codes. + * + * @note It is required for this array to have error codes placed in ascending order. + * This condition is checked in automatic unit test before the release. + */ +static nrf_strerror_desc_t const nrf_strerror_array[] = { + NRF_STRERROR_ENTITY(NRF_SUCCESS), NRF_STRERROR_ENTITY(NRF_ERROR_SVC_HANDLER_MISSING), + NRF_STRERROR_ENTITY(NRF_ERROR_SOFTDEVICE_NOT_ENABLED), + NRF_STRERROR_ENTITY(NRF_ERROR_INTERNAL), NRF_STRERROR_ENTITY(NRF_ERROR_NO_MEM), + NRF_STRERROR_ENTITY(NRF_ERROR_NOT_FOUND), NRF_STRERROR_ENTITY(NRF_ERROR_NOT_SUPPORTED), + NRF_STRERROR_ENTITY(NRF_ERROR_INVALID_PARAM), NRF_STRERROR_ENTITY(NRF_ERROR_INVALID_STATE), + NRF_STRERROR_ENTITY(NRF_ERROR_INVALID_LENGTH), NRF_STRERROR_ENTITY(NRF_ERROR_INVALID_FLAGS), + NRF_STRERROR_ENTITY(NRF_ERROR_INVALID_DATA), NRF_STRERROR_ENTITY(NRF_ERROR_DATA_SIZE), + NRF_STRERROR_ENTITY(NRF_ERROR_TIMEOUT), NRF_STRERROR_ENTITY(NRF_ERROR_NULL), + NRF_STRERROR_ENTITY(NRF_ERROR_FORBIDDEN), NRF_STRERROR_ENTITY(NRF_ERROR_INVALID_ADDR), + NRF_STRERROR_ENTITY(NRF_ERROR_BUSY), +#ifdef NRF_ERROR_CONN_COUNT + NRF_STRERROR_ENTITY(NRF_ERROR_CONN_COUNT), +#endif +#ifdef NRF_ERROR_RESOURCES + NRF_STRERROR_ENTITY(NRF_ERROR_RESOURCES), +#endif +}; + +char const *nrf_strerror_get(uint32_t code) +{ + char const *p_ret = nrf_strerror_find(code); + + return (p_ret == NULL) ? m_unknown_str : p_ret; +} + +char const *nrf_strerror_find(uint32_t code) +{ + nrf_strerror_desc_t const *p_start; + nrf_strerror_desc_t const *p_end; + + p_start = nrf_strerror_array; + p_end = nrf_strerror_array + ARRAY_SIZE(nrf_strerror_array); + + while (p_start < p_end) { + nrf_strerror_desc_t const *p_mid = p_start + ((p_end - p_start) / 2); + uint32_t mid_c = p_mid->code; + + if (mid_c > code) { + p_end = p_mid; + } else if (mid_c < code) { + p_start = p_mid + 1; + } else { + return p_mid->name; + } + } + return NULL; +} diff --git a/lib/peer_manager/peer_manager.c b/lib/peer_manager/peer_manager.c new file mode 100644 index 0000000000..4b29d1622c --- /dev/null +++ b/lib/peer_manager/peer_manager.c @@ -0,0 +1,1052 @@ +/* + * Copyright (c) 2015-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 + +LOG_MODULE_REGISTER(peer_manager, CONFIG_PEER_MANAGER_LOG_LEVEL); + +/** Macro indicating whether the module has been initialized properly. */ +#define MODULE_INITIALIZED (m_module_initialized) +#define VERIFY_MODULE_INITIALIZED() VERIFY_TRUE((MODULE_INITIALIZED), NRF_ERROR_INVALID_STATE) + +/** Whether or not @ref pm_init has been called successfully. */ +static bool m_module_initialized; +/** Whether or not @ref rank_init has been called successfully. */ +static bool m_peer_rank_initialized; +/** True from when @ref pm_peers_delete is called until all peers have been deleted. */ +static bool m_deleting_all; +/** The store token of an ongoing peer rank update via a call to @ref pm_peer_rank_highest. If @ref + * PM_STORE_TOKEN_INVALID, there is no ongoing update. + */ +static pm_store_token_t m_peer_rank_token; +/** The current highest peer rank. Used by @ref pm_peer_rank_highest. */ +static uint32_t m_current_highest_peer_rank; +/** The peer with the highest peer rank. Used by @ref pm_peer_rank_highest. */ +static pm_peer_id_t m_highest_ranked_peer; +/** The subscribers to Peer Manager events, as registered through @ref pm_register. */ +static pm_evt_handler_t m_evt_handlers[CONFIG_PM_MAX_REGISTRANTS]; +/** The number of event handlers registered through @ref pm_register. */ +static uint8_t m_n_registrants; + +/** User flag indicating whether a connection is excluded from being handled by the Peer Manager. */ +static int m_flag_conn_excluded = CONFIG_BLE_CONN_STATE_USER_FLAG_COUNT; + +/** + * @brief Function for sending a Peer Manager event to all subscribers. + * + * @param[in] p_pm_evt The event to send. + */ +static void evt_send(pm_evt_t const *p_pm_evt) +{ + for (int i = 0; i < m_n_registrants; i++) { + m_evt_handlers[i](p_pm_evt); + } +} + +#if CONFIG_PM_PEER_RANKS_ENABLED == 1 +/** @brief Function for initializing peer rank static variables. */ +static void rank_vars_update(void) +{ + uint32_t err_code = + pm_peer_ranks_get(&m_highest_ranked_peer, &m_current_highest_peer_rank, NULL, NULL); + + if (err_code == NRF_ERROR_NOT_FOUND) { + m_highest_ranked_peer = PM_PEER_ID_INVALID; + m_current_highest_peer_rank = 0; + } + + m_peer_rank_initialized = ((err_code == NRF_SUCCESS) || (err_code == NRF_ERROR_NOT_FOUND)); +} +#endif + +/** + * @brief Event handler for events from the Peer Database module. + * This handler is extern in the Peer Database module. + * + * @param[in] p_pdb_evt The incoming Peer Database event. + */ +void pm_pdb_evt_handler(pm_evt_t *p_pdb_evt) +{ + bool send_evt = true; + + p_pdb_evt->conn_handle = im_conn_handle_get(p_pdb_evt->peer_id); + + switch (p_pdb_evt->evt_id) { +#if CONFIG_PM_PEER_RANKS_ENABLED == 1 + case PM_EVT_PEER_DATA_UPDATE_SUCCEEDED: + if (p_pdb_evt->params.peer_data_update_succeeded.action == PM_PEER_DATA_OP_UPDATE) { + if ((m_peer_rank_token != PM_STORE_TOKEN_INVALID) && + (m_peer_rank_token == + p_pdb_evt->params.peer_data_update_succeeded.token)) { + m_peer_rank_token = PM_STORE_TOKEN_INVALID; + m_highest_ranked_peer = p_pdb_evt->peer_id; + + p_pdb_evt->params.peer_data_update_succeeded.token = + PM_STORE_TOKEN_INVALID; + } else if (m_peer_rank_initialized && + (p_pdb_evt->peer_id == m_highest_ranked_peer) && + (p_pdb_evt->params.peer_data_update_succeeded.data_id == + PM_PEER_DATA_ID_PEER_RANK)) { + /* Update peer rank variable if highest ranked peer has changed its + * rank. + */ + rank_vars_update(); + } + } else if (p_pdb_evt->params.peer_data_update_succeeded.action == + PM_PEER_DATA_OP_DELETE) { + if (m_peer_rank_initialized && + (p_pdb_evt->peer_id == m_highest_ranked_peer) && + (p_pdb_evt->params.peer_data_update_succeeded.data_id == + PM_PEER_DATA_ID_PEER_RANK)) { + /* Update peer rank variable if highest ranked peer has deleted its + * rank. + */ + rank_vars_update(); + } + } + break; + + case PM_EVT_PEER_DATA_UPDATE_FAILED: + if (p_pdb_evt->params.peer_data_update_succeeded.action == PM_PEER_DATA_OP_UPDATE) { + if ((m_peer_rank_token != PM_STORE_TOKEN_INVALID) && + (m_peer_rank_token == + p_pdb_evt->params.peer_data_update_failed.token)) { + m_peer_rank_token = PM_STORE_TOKEN_INVALID; + m_current_highest_peer_rank -= 1; + + p_pdb_evt->params.peer_data_update_succeeded.token = + PM_STORE_TOKEN_INVALID; + } + } + break; +#endif + + case PM_EVT_PEER_DELETE_SUCCEEDED: + /* Check that no peers marked for deletion are left. */ + if (m_deleting_all && + (pds_next_peer_id_get(PM_PEER_ID_INVALID) == PM_PEER_ID_INVALID) && + (pds_next_deleted_peer_id_get(PM_PEER_ID_INVALID) == PM_PEER_ID_INVALID)) { + /* pm_peers_delete() has been called and this is the last peer to be + * deleted. + */ + m_deleting_all = false; + + pm_evt_t pm_delete_all_evt; + + memset(&pm_delete_all_evt, 0, sizeof(pm_evt_t)); + pm_delete_all_evt.evt_id = PM_EVT_PEERS_DELETE_SUCCEEDED; + pm_delete_all_evt.peer_id = PM_PEER_ID_INVALID; + pm_delete_all_evt.conn_handle = BLE_CONN_HANDLE_INVALID; + + send_evt = false; + + /* Forward the event to all registered Peer Manager event handlers. + * Ensure that PEER_DELETE_SUCCEEDED arrives before PEERS_DELETE_SUCCEEDED. + */ + evt_send(p_pdb_evt); + evt_send(&pm_delete_all_evt); + } + +#if CONFIG_PM_PEER_RANKS_ENABLED == 1 + if (m_peer_rank_initialized && (p_pdb_evt->peer_id == m_highest_ranked_peer)) { + /* Update peer rank variable if highest ranked peer has been deleted. */ + rank_vars_update(); + } +#endif + break; + + case PM_EVT_PEER_DELETE_FAILED: + if (m_deleting_all) { + /* pm_peers_delete() was called and has thus failed. */ + + m_deleting_all = false; + + pm_evt_t pm_delete_all_evt; + + memset(&pm_delete_all_evt, 0, sizeof(pm_evt_t)); + pm_delete_all_evt.evt_id = PM_EVT_PEERS_DELETE_FAILED; + pm_delete_all_evt.peer_id = PM_PEER_ID_INVALID; + pm_delete_all_evt.conn_handle = BLE_CONN_HANDLE_INVALID; + pm_delete_all_evt.params.peers_delete_failed_evt.error = + p_pdb_evt->params.peer_delete_failed.error; + + send_evt = false; + + /* Forward the event to all registered Peer Manager event handlers. + * Ensure that PEER_DELETE_FAILED arrives before PEERS_DELETE_FAILED. + */ + evt_send(p_pdb_evt); + evt_send(&pm_delete_all_evt); + } + break; + + default: + /* Do nothing. */ + break; + } + + if (send_evt) { + /* Forward the event to all registered Peer Manager event handlers. */ + evt_send(p_pdb_evt); + } +} + +/** + * @brief Event handler for events from the Security Manager module. + * This handler is extern in the Security Manager module. + * + * @param[in] p_sm_evt The incoming Security Manager event. + */ +void pm_sm_evt_handler(pm_evt_t *p_sm_evt) +{ + VERIFY_PARAM_NOT_NULL_VOID(p_sm_evt); + + /* Forward the event to all registered Peer Manager event handlers. */ + evt_send(p_sm_evt); +} + +/** + * @brief Event handler for events from the GATT Cache Manager module. + * This handler is extern in GATT Cache Manager. + * + * @param[in] p_gcm_evt The incoming GATT Cache Manager event. + */ +void pm_gcm_evt_handler(pm_evt_t *p_gcm_evt) +{ + /* Forward the event to all registered Peer Manager event handlers. */ + evt_send(p_gcm_evt); +} + +/** + * @brief Event handler for events from the GATTS Cache Manager module. + * This handler is extern in GATTS Cache Manager. + * + * @param[in] p_gscm_evt The incoming GATTS Cache Manager event. + */ +void pm_gscm_evt_handler(pm_evt_t *p_gscm_evt) +{ + /* Forward the event to all registered Peer Manager event handlers. */ + evt_send(p_gscm_evt); +} + +/** + * @brief Event handler for events from the ID Manager module. + * This function is registered in the ID Manager. + * + * @param[in] p_im_evt The incoming ID Manager event. + */ +void pm_im_evt_handler(pm_evt_t *p_im_evt) +{ + /* Forward the event to all registered Peer Manager event handlers. */ + evt_send(p_im_evt); +} + +static bool is_conn_handle_excluded(ble_evt_t const *p_ble_evt) +{ + uint16_t conn_handle = p_ble_evt->evt.gap_evt.conn_handle; + + switch (p_ble_evt->header.evt_id) { + case BLE_GAP_EVT_CONNECTED: { + pm_evt_t pm_conn_config_req_evt; + bool is_excluded = false; + + memset(&pm_conn_config_req_evt, 0, sizeof(pm_evt_t)); + pm_conn_config_req_evt.evt_id = PM_EVT_CONN_CONFIG_REQ; + pm_conn_config_req_evt.peer_id = PM_PEER_ID_INVALID; + pm_conn_config_req_evt.conn_handle = conn_handle; + + pm_conn_config_req_evt.params.conn_config_req.p_peer_params = + &p_ble_evt->evt.gap_evt.params.connected; + pm_conn_config_req_evt.params.conn_config_req.p_context = &is_excluded; + + evt_send(&pm_conn_config_req_evt); + ble_conn_state_user_flag_set(conn_handle, m_flag_conn_excluded, is_excluded); + + return is_excluded; + } + + default: + return ble_conn_state_user_flag_get(conn_handle, m_flag_conn_excluded); + } +} + +/** + * @brief Function for handling BLE events. + * + * @param[in] p_ble_evt Event received from the BLE stack. + * @param[in] p_context Context. + */ +static void ble_evt_handler(ble_evt_t const *p_ble_evt, void *p_context) +{ + VERIFY_MODULE_INITIALIZED_VOID(); + + if (is_conn_handle_excluded(p_ble_evt)) { + LOG_DBG("Filtering BLE event with ID: 0x%04X targeting 0x%04X connection handle", + p_ble_evt->header.evt_id, p_ble_evt->evt.gap_evt.conn_handle); + return; + } + + im_ble_evt_handler(p_ble_evt); + sm_ble_evt_handler(p_ble_evt); + gcm_ble_evt_handler(p_ble_evt); +} + +NRF_SDH_BLE_OBSERVER(m_ble_evt_observer, ble_evt_handler, NULL, CONFIG_PM_BLE_OBSERVER_PRIO); + +/** @brief Function for resetting the internal state of this module. */ +static void internal_state_reset(void) +{ + m_highest_ranked_peer = PM_PEER_ID_INVALID; + m_peer_rank_token = PM_STORE_TOKEN_INVALID; +} + +uint32_t pm_init(void) +{ + uint32_t err_code; + + err_code = pds_init(); + if (err_code != NRF_SUCCESS) { + LOG_ERR("%s failed because pds_init() returned %s.", __func__, + nrf_strerror_get(err_code)); + return NRF_ERROR_INTERNAL; + } + + err_code = pdb_init(); + if (err_code != NRF_SUCCESS) { + LOG_ERR("%s failed because pdb_init() returned %s.", __func__, + nrf_strerror_get(err_code)); + return NRF_ERROR_INTERNAL; + } + + err_code = sm_init(); + if (err_code != NRF_SUCCESS) { + LOG_ERR("%s failed because sm_init() returned %s.", __func__, + nrf_strerror_get(err_code)); + return NRF_ERROR_INTERNAL; + } + + err_code = smd_init(); + if (err_code != NRF_SUCCESS) { + LOG_ERR("%s failed because smd_init() returned %s.", __func__, + nrf_strerror_get(err_code)); + return NRF_ERROR_INTERNAL; + } + + err_code = gcm_init(); + if (err_code != NRF_SUCCESS) { + LOG_ERR("%s failed because gcm_init() returned %s.", __func__, + nrf_strerror_get(err_code)); + return NRF_ERROR_INTERNAL; + } + + err_code = gscm_init(); + if (err_code != NRF_SUCCESS) { + LOG_ERR("%s failed because gscm_init() returned %s.", __func__, + nrf_strerror_get(err_code)); + return NRF_ERROR_INTERNAL; + } + + internal_state_reset(); + + m_peer_rank_initialized = false; + m_module_initialized = true; + + m_flag_conn_excluded = ble_conn_state_user_flag_acquire(); + + /* If CONFIG_PM_PEER_RANKS_ENABLED is 0, these variables are unused. */ + UNUSED_VARIABLE(m_peer_rank_initialized); + UNUSED_VARIABLE(m_peer_rank_token); + UNUSED_VARIABLE(m_current_highest_peer_rank); + UNUSED_VARIABLE(m_highest_ranked_peer); + + return NRF_SUCCESS; +} + +uint32_t pm_register(pm_evt_handler_t event_handler) +{ + VERIFY_MODULE_INITIALIZED(); + + if (m_n_registrants >= CONFIG_PM_MAX_REGISTRANTS) { + return NRF_ERROR_NO_MEM; + } + + m_evt_handlers[m_n_registrants] = event_handler; + m_n_registrants += 1; + + return NRF_SUCCESS; +} + +uint32_t pm_sec_params_set(ble_gap_sec_params_t *p_sec_params) +{ + VERIFY_MODULE_INITIALIZED(); + + uint32_t err_code; + + err_code = sm_sec_params_set(p_sec_params); + + /* NRF_ERROR_INVALID_PARAM if parameters are invalid, + * NRF_SUCCESS otherwise. + */ + return err_code; +} + +uint32_t pm_conn_secure(uint16_t conn_handle, bool force_repairing) +{ + VERIFY_MODULE_INITIALIZED(); + + uint32_t err_code; + + err_code = sm_link_secure(conn_handle, force_repairing); + + if (err_code == NRF_ERROR_INVALID_STATE) { + err_code = NRF_ERROR_BUSY; + } + + return err_code; +} + +uint32_t pm_conn_exclude(uint16_t conn_handle, void const *p_context) +{ + VERIFY_PARAM_NOT_NULL(p_context); + + bool *p_is_conn_excluded = (bool *)p_context; + + *p_is_conn_excluded = true; + + return NRF_SUCCESS; +} + +void pm_conn_sec_config_reply(uint16_t conn_handle, pm_conn_sec_config_t *p_conn_sec_config) +{ + if (p_conn_sec_config != NULL) { + sm_conn_sec_config_reply(conn_handle, p_conn_sec_config); + } +} + +uint32_t pm_conn_sec_params_reply(uint16_t conn_handle, ble_gap_sec_params_t *p_sec_params, + void const *p_context) +{ + VERIFY_MODULE_INITIALIZED(); + + return sm_sec_params_reply(conn_handle, p_sec_params, p_context); +} + +void pm_local_database_has_changed(void) +{ +#if !defined(CONFIG_PM_SERVICE_CHANGED_ENABLED) || (CONFIG_PM_SERVICE_CHANGED_ENABLED == 1) + VERIFY_MODULE_INITIALIZED_VOID(); + + gcm_local_database_has_changed(); +#endif +} + +uint32_t pm_id_addr_set(ble_gap_addr_t const *p_addr) +{ + VERIFY_MODULE_INITIALIZED(); + return im_id_addr_set(p_addr); +} + +uint32_t pm_id_addr_get(ble_gap_addr_t *p_addr) +{ + VERIFY_MODULE_INITIALIZED(); + VERIFY_PARAM_NOT_NULL(p_addr); + return im_id_addr_get(p_addr); +} + +uint32_t pm_privacy_set(pm_privacy_params_t const *p_privacy_params) +{ + VERIFY_MODULE_INITIALIZED(); + VERIFY_PARAM_NOT_NULL(p_privacy_params); + return im_privacy_set(p_privacy_params); +} + +uint32_t pm_privacy_get(pm_privacy_params_t *p_privacy_params) +{ + VERIFY_MODULE_INITIALIZED(); + VERIFY_PARAM_NOT_NULL(p_privacy_params); + VERIFY_PARAM_NOT_NULL(p_privacy_params->p_device_irk); + return im_privacy_get(p_privacy_params); +} + +bool pm_address_resolve(ble_gap_addr_t const *p_addr, ble_gap_irk_t const *p_irk) +{ + VERIFY_MODULE_INITIALIZED(); + + if ((p_addr == NULL) || (p_irk == NULL)) { + return false; + } else { + return im_address_resolve(p_addr, p_irk); + } +} + +uint32_t pm_whitelist_set(pm_peer_id_t const *p_peers, uint32_t peer_cnt) +{ + VERIFY_MODULE_INITIALIZED(); + return im_whitelist_set(p_peers, peer_cnt); +} + +uint32_t pm_whitelist_get(ble_gap_addr_t *p_addrs, uint32_t *p_addr_cnt, ble_gap_irk_t *p_irks, + uint32_t *p_irk_cnt) +{ + VERIFY_MODULE_INITIALIZED(); + + if (((p_addrs == NULL) && (p_irks == NULL)) || + ((p_addrs != NULL) && (p_addr_cnt == NULL)) || + ((p_irks != NULL) && (p_irk_cnt == NULL))) { + /* The buffers can't be both NULL, and if a buffer is provided its size must be + * specified. + */ + return NRF_ERROR_NULL; + } + + return im_whitelist_get(p_addrs, p_addr_cnt, p_irks, p_irk_cnt); +} + +uint32_t pm_device_identities_list_set(pm_peer_id_t const *p_peers, uint32_t peer_cnt) +{ + VERIFY_MODULE_INITIALIZED(); + return im_device_identities_list_set(p_peers, peer_cnt); +} + +uint32_t pm_conn_sec_status_get(uint16_t conn_handle, pm_conn_sec_status_t *p_conn_sec_status) +{ + VERIFY_MODULE_INITIALIZED(); + return sm_conn_sec_status_get(conn_handle, p_conn_sec_status); +} + +bool pm_sec_is_sufficient(uint16_t conn_handle, pm_conn_sec_status_t *p_sec_status_req) +{ + VERIFY_MODULE_INITIALIZED_BOOL(); + return sm_sec_is_sufficient(conn_handle, p_sec_status_req); +} + +uint32_t pm_lesc_public_key_set(ble_gap_lesc_p256_pk_t *p_public_key) +{ + VERIFY_MODULE_INITIALIZED(); + return sm_lesc_public_key_set(p_public_key); +} + +uint32_t pm_conn_handle_get(pm_peer_id_t peer_id, uint16_t *p_conn_handle) +{ + VERIFY_MODULE_INITIALIZED(); + VERIFY_PARAM_NOT_NULL(p_conn_handle); + *p_conn_handle = im_conn_handle_get(peer_id); + return NRF_SUCCESS; +} + +uint32_t pm_peer_id_get(uint16_t conn_handle, pm_peer_id_t *p_peer_id) +{ + VERIFY_MODULE_INITIALIZED(); + VERIFY_PARAM_NOT_NULL(p_peer_id); + *p_peer_id = im_peer_id_get_by_conn_handle(conn_handle); + return NRF_SUCCESS; +} + +uint32_t pm_peer_count(void) +{ + if (!MODULE_INITIALIZED) { + return 0; + } + return pds_peer_count_get(); +} + +pm_peer_id_t pm_next_peer_id_get(pm_peer_id_t prev_peer_id) +{ + pm_peer_id_t next_peer_id = prev_peer_id; + + if (!MODULE_INITIALIZED) { + return PM_PEER_ID_INVALID; + } + + do { + next_peer_id = pds_next_peer_id_get(next_peer_id); + } while (pds_peer_id_is_deleted(next_peer_id)); + + return next_peer_id; +} + +/** + * @brief Function for checking if the peer has a valid Identity Resolving Key. + * + * @param[in] p_irk Pointer to the Identity Resolving Key. + */ +static bool peer_is_irk(ble_gap_irk_t const *const p_irk) +{ + for (uint32_t i = 0; i < ARRAY_SIZE(p_irk->irk); i++) { + if (p_irk->irk[i] != 0) { + return true; + } + } + + return false; +} + +uint32_t pm_peer_id_list(pm_peer_id_t *p_peer_list, uint32_t *const p_list_size, + pm_peer_id_t first_peer_id, pm_peer_id_list_skip_t skip_id) +{ + VERIFY_MODULE_INITIALIZED(); + VERIFY_PARAM_NOT_NULL(p_list_size); + VERIFY_PARAM_NOT_NULL(p_peer_list); + + uint32_t err_code; + uint32_t size = *p_list_size; + uint32_t current_size = 0; + pm_peer_data_t pm_car_data; + pm_peer_data_t pm_bond_data; + pm_peer_id_t current_peer_id = first_peer_id; + ble_gap_addr_t const *p_gap_addr; + bool skip_no_addr = skip_id & PM_PEER_ID_LIST_SKIP_NO_ID_ADDR; + bool skip_no_irk = skip_id & PM_PEER_ID_LIST_SKIP_NO_IRK; + bool skip_no_car = skip_id & PM_PEER_ID_LIST_SKIP_NO_CAR; + + if ((*p_list_size < 1) || + (skip_id > (PM_PEER_ID_LIST_SKIP_NO_ID_ADDR | PM_PEER_ID_LIST_SKIP_ALL))) { + return NRF_ERROR_INVALID_PARAM; + } + + *p_list_size = 0; + + if (current_peer_id == PM_PEER_ID_INVALID) { + current_peer_id = pm_next_peer_id_get(current_peer_id); + + if (current_peer_id == PM_PEER_ID_INVALID) { + return NRF_SUCCESS; + } + } + + memset(&pm_car_data, 0, sizeof(pm_peer_data_t)); + memset(&pm_bond_data, 0, sizeof(pm_peer_data_t)); + + while (current_peer_id != PM_PEER_ID_INVALID) { + bool skip = false; + + if (skip_no_addr || skip_no_irk) { + /* Get data */ + pm_bond_data.p_bonding_data = NULL; + + err_code = pds_peer_data_read(current_peer_id, PM_PEER_DATA_ID_BONDING, + &pm_bond_data, NULL); + + if (err_code == NRF_ERROR_NOT_FOUND) { + skip = true; + } else { + VERIFY_SUCCESS(err_code); + } + + /* Check data */ + if (skip_no_addr) { + p_gap_addr = &pm_bond_data.p_bonding_data->peer_ble_id.id_addr_info; + + if ((p_gap_addr->addr_type != BLE_GAP_ADDR_TYPE_PUBLIC) && + (p_gap_addr->addr_type != BLE_GAP_ADDR_TYPE_RANDOM_STATIC)) { + skip = true; + } + } + if (skip_no_irk) { + if (!peer_is_irk( + &pm_bond_data.p_bonding_data->peer_ble_id.id_info)) { + skip = true; + } + } + } + + if (skip_no_car) { + /* Get data */ + pm_car_data.p_central_addr_res = NULL; + + err_code = pds_peer_data_read(current_peer_id, + PM_PEER_DATA_ID_CENTRAL_ADDR_RES, + &pm_car_data, NULL); + + if (err_code == NRF_ERROR_NOT_FOUND) { + skip = true; + } else { + VERIFY_SUCCESS(err_code); + } + + /* Check data */ + if (*pm_car_data.p_central_addr_res == 0) { + skip = true; + } + } + + if (!skip) { + p_peer_list[current_size++] = current_peer_id; + + if (current_size >= size) { + break; + } + } + + current_peer_id = pm_next_peer_id_get(current_peer_id); + } + + *p_list_size = current_size; + + return NRF_SUCCESS; +} + +uint32_t pm_peer_data_load(pm_peer_id_t peer_id, pm_peer_data_id_t data_id, void *p_data, + uint32_t *p_length) +{ + VERIFY_MODULE_INITIALIZED(); + VERIFY_PARAM_NOT_NULL(p_data); + VERIFY_PARAM_NOT_NULL(p_length); + + pm_peer_data_t peer_data; + + memset(&peer_data, 0, sizeof(peer_data)); + peer_data.p_all_data = p_data; + + return pds_peer_data_read(peer_id, data_id, &peer_data, p_length); +} + +uint32_t pm_peer_data_bonding_load(pm_peer_id_t peer_id, pm_peer_data_bonding_t *p_data) +{ + uint32_t length = sizeof(pm_peer_data_bonding_t); + + return pm_peer_data_load(peer_id, PM_PEER_DATA_ID_BONDING, p_data, &length); +} + +uint32_t pm_peer_data_remote_db_load(pm_peer_id_t peer_id, ble_gatt_db_srv_t *p_data, + uint32_t *p_length) +{ + return pm_peer_data_load(peer_id, PM_PEER_DATA_ID_GATT_REMOTE, p_data, p_length); +} + +uint32_t pm_peer_data_app_data_load(pm_peer_id_t peer_id, void *p_data, uint32_t *p_length) +{ + return pm_peer_data_load(peer_id, PM_PEER_DATA_ID_APPLICATION, p_data, p_length); +} + +uint32_t pm_peer_data_store(pm_peer_id_t peer_id, pm_peer_data_id_t data_id, void const *p_data, + uint32_t length, pm_store_token_t *p_token) +{ + VERIFY_MODULE_INITIALIZED(); + VERIFY_PARAM_NOT_NULL(p_data); + if (ALIGN_NUM(4, length) != length) { + return NRF_ERROR_INVALID_PARAM; + } + + if (data_id == PM_PEER_DATA_ID_BONDING) { + pm_peer_id_t dupl_peer_id; + + dupl_peer_id = + im_find_duplicate_bonding_data((pm_peer_data_bonding_t *)p_data, peer_id); + + if (dupl_peer_id != PM_PEER_ID_INVALID) { + return NRF_ERROR_FORBIDDEN; + } + } + + pm_peer_data_flash_t peer_data; + + memset(&peer_data, 0, sizeof(peer_data)); + peer_data.length_words = BYTES_TO_WORDS(length); + peer_data.data_id = data_id; + peer_data.p_all_data = p_data; + + return pds_peer_data_store(peer_id, &peer_data, p_token); +} + +uint32_t pm_peer_data_bonding_store(pm_peer_id_t peer_id, pm_peer_data_bonding_t const *p_data, + pm_store_token_t *p_token) +{ + return pm_peer_data_store(peer_id, PM_PEER_DATA_ID_BONDING, p_data, + ALIGN_NUM(4, sizeof(pm_peer_data_bonding_t)), p_token); +} + +uint32_t pm_peer_data_remote_db_store(pm_peer_id_t peer_id, ble_gatt_db_srv_t const *p_data, + uint32_t length, pm_store_token_t *p_token) +{ + return pm_peer_data_store(peer_id, PM_PEER_DATA_ID_GATT_REMOTE, p_data, length, p_token); +} + +uint32_t pm_peer_data_app_data_store(pm_peer_id_t peer_id, void const *p_data, uint32_t length, + pm_store_token_t *p_token) +{ + return pm_peer_data_store(peer_id, PM_PEER_DATA_ID_APPLICATION, p_data, length, p_token); +} + +uint32_t pm_peer_data_delete(pm_peer_id_t peer_id, pm_peer_data_id_t data_id) +{ + VERIFY_MODULE_INITIALIZED(); + + if (data_id == PM_PEER_DATA_ID_BONDING) { + return NRF_ERROR_INVALID_PARAM; + } + + return pds_peer_data_delete(peer_id, data_id); +} + +uint32_t pm_peer_new(pm_peer_id_t *p_new_peer_id, pm_peer_data_bonding_t *p_bonding_data, + pm_store_token_t *p_token) +{ + uint32_t err_code; + pm_peer_id_t peer_id; + pm_peer_data_flash_t peer_data; + + VERIFY_MODULE_INITIALIZED(); + VERIFY_PARAM_NOT_NULL(p_bonding_data); + VERIFY_PARAM_NOT_NULL(p_new_peer_id); + + memset(&peer_data, 0, sizeof(pm_peer_data_flash_t)); + + /* Search through existing bonds to look for a duplicate. */ + pds_peer_data_iterate_prepare(); + + /* @note This check is not thread safe since data is not copied while iterating. */ + while (pds_peer_data_iterate(PM_PEER_DATA_ID_BONDING, &peer_id, &peer_data)) { + if (im_is_duplicate_bonding_data(p_bonding_data, peer_data.p_bonding_data)) { + *p_new_peer_id = peer_id; + return NRF_SUCCESS; + } + } + + /* If no duplicate data is found, prepare to write a new bond to flash. */ + + *p_new_peer_id = pds_peer_id_allocate(); + + if (*p_new_peer_id == PM_PEER_ID_INVALID) { + return NRF_ERROR_NO_MEM; + } + + memset(&peer_data, 0, sizeof(pm_peer_data_flash_t)); + + peer_data.data_id = PM_PEER_DATA_ID_BONDING; + peer_data.p_bonding_data = p_bonding_data; + peer_data.length_words = BYTES_TO_WORDS(sizeof(pm_peer_data_bonding_t)); + + err_code = pds_peer_data_store(*p_new_peer_id, &peer_data, p_token); + + if (err_code != NRF_SUCCESS) { + uint32_t err_code_free = im_peer_free(*p_new_peer_id); + + if (err_code_free != NRF_SUCCESS) { + LOG_ERR("Fatal error during cleanup of a failed call to %s. im_peer_free() " + "returned %s. peer_id: %d", + __func__, nrf_strerror_get(err_code_free), *p_new_peer_id); + return NRF_ERROR_INTERNAL; + } + + /* NRF_ERROR_RESOURCES, if no space in flash. + * NRF_ERROR_BUSY, if flash filesystem was busy. + * NRF_ERROR_INVALID_ADDR, if bonding data is unaligned. + * NRF_ERROR_INTENRAL, on internal error. + */ + return err_code; + } + + return NRF_SUCCESS; +} + +uint32_t pm_peer_delete(pm_peer_id_t peer_id) +{ + VERIFY_MODULE_INITIALIZED(); + + return im_peer_free(peer_id); +} + +uint32_t pm_peers_delete(void) +{ + VERIFY_MODULE_INITIALIZED(); + + m_deleting_all = true; + + pm_peer_id_t current_peer_id = pds_next_peer_id_get(PM_PEER_ID_INVALID); + + if (current_peer_id == PM_PEER_ID_INVALID) { + /* No peers bonded. */ + m_deleting_all = false; + + pm_evt_t pm_delete_all_evt; + + memset(&pm_delete_all_evt, 0, sizeof(pm_evt_t)); + pm_delete_all_evt.evt_id = PM_EVT_PEERS_DELETE_SUCCEEDED; + pm_delete_all_evt.peer_id = PM_PEER_ID_INVALID; + pm_delete_all_evt.conn_handle = BLE_CONN_HANDLE_INVALID; + + evt_send(&pm_delete_all_evt); + } + + while (current_peer_id != PM_PEER_ID_INVALID) { + uint32_t err_code = pm_peer_delete(current_peer_id); + + if (err_code != NRF_SUCCESS) { + LOG_ERR("%s() failed because a call to pm_peer_delete() returned %s. " + "peer_id: %d", + __func__, nrf_strerror_get(err_code), current_peer_id); + return NRF_ERROR_INTERNAL; + } + + current_peer_id = pds_next_peer_id_get(current_peer_id); + } + + return NRF_SUCCESS; +} + +uint32_t pm_peer_ranks_get(pm_peer_id_t *p_highest_ranked_peer, uint32_t *p_highest_rank, + pm_peer_id_t *p_lowest_ranked_peer, uint32_t *p_lowest_rank) +{ +#if CONFIG_PM_PEER_RANKS_ENABLED == 0 + return NRF_ERROR_NOT_SUPPORTED; +#else + VERIFY_MODULE_INITIALIZED(); + + pm_peer_id_t peer_id = pds_next_peer_id_get(PM_PEER_ID_INVALID); + uint32_t peer_rank = 0; + uint32_t length = sizeof(peer_rank); + pm_peer_data_t peer_data = {.p_peer_rank = &peer_rank}; + uint32_t err_code = + pds_peer_data_read(peer_id, PM_PEER_DATA_ID_PEER_RANK, &peer_data, &length); + uint32_t highest_rank = 0; + uint32_t lowest_rank = 0xFFFFFFFF; + pm_peer_id_t highest_ranked_peer = PM_PEER_ID_INVALID; + pm_peer_id_t lowest_ranked_peer = PM_PEER_ID_INVALID; + + if (err_code == NRF_ERROR_INVALID_PARAM) { + /* No peer IDs exist. */ + return NRF_ERROR_NOT_FOUND; + } + + while ((err_code == NRF_SUCCESS) || (err_code == NRF_ERROR_NOT_FOUND)) { + if (err_code == NRF_SUCCESS) { + if (peer_rank >= highest_rank) { + highest_rank = peer_rank; + highest_ranked_peer = peer_id; + } + if (peer_rank < lowest_rank) { + lowest_rank = peer_rank; + lowest_ranked_peer = peer_id; + } + } + peer_id = pds_next_peer_id_get(peer_id); + err_code = + pds_peer_data_read(peer_id, PM_PEER_DATA_ID_PEER_RANK, &peer_data, &length); + } + if (peer_id == PM_PEER_ID_INVALID) { + if ((highest_ranked_peer == PM_PEER_ID_INVALID) || + (lowest_ranked_peer == PM_PEER_ID_INVALID)) { + err_code = NRF_ERROR_NOT_FOUND; + } else { + err_code = NRF_SUCCESS; + } + + if (p_highest_ranked_peer != NULL) { + *p_highest_ranked_peer = highest_ranked_peer; + } + if (p_highest_rank != NULL) { + *p_highest_rank = highest_rank; + } + if (p_lowest_ranked_peer != NULL) { + *p_lowest_ranked_peer = lowest_ranked_peer; + } + if (p_lowest_rank != NULL) { + *p_lowest_rank = lowest_rank; + } + } else { + LOG_ERR("Could not retrieve ranks. pdb_peer_data_load() returned %s. peer_id: %d", + nrf_strerror_get(err_code), peer_id); + err_code = NRF_ERROR_INTERNAL; + } + return err_code; +#endif +} + +#if CONFIG_PM_PEER_RANKS_ENABLED == 1 +/** @brief Function for initializing peer rank functionality. */ +static void rank_init(void) +{ + rank_vars_update(); +} +#endif + +uint32_t pm_peer_rank_highest(pm_peer_id_t peer_id) +{ +#if CONFIG_PM_PEER_RANKS_ENABLED == 0 + return NRF_ERROR_NOT_SUPPORTED; +#else + VERIFY_MODULE_INITIALIZED(); + + uint32_t err_code; + pm_peer_data_flash_t peer_data = { + .length_words = BYTES_TO_WORDS(sizeof(m_current_highest_peer_rank)), + .data_id = PM_PEER_DATA_ID_PEER_RANK, + .p_peer_rank = &m_current_highest_peer_rank}; + + if (!m_peer_rank_initialized) { + rank_init(); + } + + if (!m_peer_rank_initialized || (m_peer_rank_token != PM_STORE_TOKEN_INVALID)) { + err_code = NRF_ERROR_BUSY; + } else { + if ((peer_id == m_highest_ranked_peer) && (m_current_highest_peer_rank > 0)) { + pm_evt_t pm_evt; + + /* The reported peer is already regarded as highest (provided it has an + * index at all) + */ + err_code = NRF_SUCCESS; + + memset(&pm_evt, 0, sizeof(pm_evt)); + pm_evt.evt_id = PM_EVT_PEER_DATA_UPDATE_SUCCEEDED; + pm_evt.conn_handle = im_conn_handle_get(peer_id); + pm_evt.peer_id = peer_id; + pm_evt.params.peer_data_update_succeeded.data_id = + PM_PEER_DATA_ID_PEER_RANK; + pm_evt.params.peer_data_update_succeeded.action = PM_PEER_DATA_OP_UPDATE; + pm_evt.params.peer_data_update_succeeded.token = PM_STORE_TOKEN_INVALID; + pm_evt.params.peer_data_update_succeeded.flash_changed = false; + + evt_send(&pm_evt); + } else { + if (m_current_highest_peer_rank == UINT32_MAX) { + err_code = NRF_ERROR_DATA_SIZE; + } else { + m_current_highest_peer_rank += 1; + err_code = pds_peer_data_store(peer_id, &peer_data, + &m_peer_rank_token); + if (err_code != NRF_SUCCESS) { + m_peer_rank_token = PM_STORE_TOKEN_INVALID; + m_current_highest_peer_rank -= 1; + /* Assume INVALID_PARAM + * refers to peer_id, not + * data_id. + */ + if ((err_code != NRF_ERROR_BUSY) && + (err_code != NRF_ERROR_RESOURCES) && + (err_code != NRF_ERROR_INVALID_PARAM)) { + LOG_ERR("Could not update rank. " + "pdb_raw_store() returned %s. " + "peer_id: %d", + nrf_strerror_get(err_code), peer_id); + err_code = NRF_ERROR_INTERNAL; + } + } + } + } + } + return err_code; +#endif +} diff --git a/samples/bluetooth/ble_hrs/prj.conf b/samples/bluetooth/ble_hrs/prj.conf index ef28cda77c..e663003b9a 100644 --- a/samples/bluetooth/ble_hrs/prj.conf +++ b/samples/bluetooth/ble_hrs/prj.conf @@ -9,6 +9,16 @@ 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_HRS" CONFIG_BLE_ADV_EXTENDED_ADVERTISING=n @@ -33,3 +43,9 @@ CONFIG_BLE_HRS=y # To simulate measurements CONFIG_SENSORSIM=y CONFIG_BM_TIMER=y + +# Peer Manager +CONFIG_PEER_MANAGER=y +CONFIG_PM_LESC_ENABLED=y +CONFIG_BLE_CONN_STATE=y +CONFIG_BM_ZMS=y diff --git a/samples/bluetooth/ble_hrs/src/main.c b/samples/bluetooth/ble_hrs/src/main.c index 76341fa858..ecfc6a5b2e 100644 --- a/samples/bluetooth/ble_hrs/src/main.c +++ b/samples/bluetooth/ble_hrs/src/main.c @@ -19,10 +19,29 @@ #include #include +#include + LOG_MODULE_REGISTER(app, CONFIG_BLE_HRS_SAMPLE_LOG_LEVEL); #define CONN_TAG 1 +/* 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 0 +/* No I/O capabilities. */ +#define SEC_PARAM_IO_CAPABILITIES BLE_GAP_IO_CAPS_NONE +/* 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_ADV_DEF(ble_adv); /* BLE advertising instance */ BLE_BAS_DEF(ble_bas); /* BLE battery service instance */ BLE_HRS_DEF(ble_hrs); /* BLE heart rate service instance */ @@ -313,6 +332,88 @@ static void ble_hrs_evt_handler(struct ble_hrs *hrs, const struct ble_hrs_evt *e } } +static void delete_bonds(void) +{ + uint32_t err; + + printk("Erase bonds!\n"); + + err = pm_peers_delete(); + if (err) { + printk("Failed to delete peers, err %d\n", err); + } +} + +static void advertising_start(bool erase_bonds) +{ + int err; + + if (erase_bonds) { + delete_bonds(); + } else { + err = ble_adv_start(&ble_adv, BLE_ADV_MODE_FAST); + if (err) { + printk("Failed to start advertising, err %d\n", err); + } + } +} + +static void pm_evt_handler(pm_evt_t const *p_evt) +{ + pm_handler_on_pm_evt(p_evt); + pm_handler_disconnect_on_sec_failure(p_evt); + pm_handler_flash_clean(p_evt); + + switch (p_evt->evt_id) { + case PM_EVT_PEERS_DELETE_SUCCEEDED: + advertising_start(false); + break; + default: + break; + } +} + +static void peer_manager_init(void) +{ + ble_gap_sec_params_t sec_param; + int err; + + err = pm_init(); + if (err) { + return; + } + + 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, + }; + + err = pm_sec_params_set(&sec_param); + if (err) { + printk("pm_sec_params_set() failed, err: %d\n", err); + return; + } + + err = pm_register(pm_evt_handler); + if (err) { + printk("pm_register() failed, err: %d\n", err); + return; + } +} + int main(void) { int err; @@ -369,6 +470,8 @@ int main(void) LOG_INF("Bluetooth enabled"); + peer_manager_init(); + err = ble_hrs_init(&ble_hrs, &hrs_cfg); if (err) { LOG_ERR("Failed to initialize heart rate service, err %d", err);