From eb8e79c8a341dc2d1143201600ccb2cdf965eb2f Mon Sep 17 00:00:00 2001 From: Andreas Moltumyr Date: Mon, 4 May 2026 10:49:56 +0200 Subject: [PATCH 01/12] fs: bm_zms: remove unused __ALIGN macro and add header for __aligned The __ALIGN macro defined in bm_zms.c is not used. Remove it and add header for the __aligned macro, which is used in the bm_zms.c file. Signed-off-by: Andreas Moltumyr --- subsys/fs/bm_zms/bm_zms.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/subsys/fs/bm_zms/bm_zms.c b/subsys/fs/bm_zms/bm_zms.c index 544e54a18f..e20e8b915d 100644 --- a/subsys/fs/bm_zms/bm_zms.c +++ b/subsys/fs/bm_zms/bm_zms.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -23,10 +24,6 @@ LOG_MODULE_REGISTER(bm_zms, CONFIG_BM_ZMS_LOG_LEVEL); -#if defined(CONFIG_ZTEST) && defined(CONFIG_BOARD_NATIVE_SIM) -#define __ALIGN(x) __aligned(x) -#endif - static zms_op_t cur_op; /* Current bm_zms operation. */ static zms_op_t *p_cur_op; static atomic_t cur_op_result = ATOMIC_INIT(0); From 9694912f9ed6c8f6585649638ea5cef0f8dabb9a Mon Sep 17 00:00:00 2001 From: Andreas Moltumyr Date: Mon, 4 May 2026 10:46:29 +0200 Subject: [PATCH 02/12] lib: peer_manager: use __aligned macro instead of __ALIGN Swap nrf mdk __ALIGN macro for the zephyr __aligned macro to align code. Signed-off-by: Andreas Moltumyr --- lib/bluetooth/peer_manager/include/modules/pm_buffer.h | 3 ++- lib/bluetooth/peer_manager/modules/nrf_ble_lesc.c | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/bluetooth/peer_manager/include/modules/pm_buffer.h b/lib/bluetooth/peer_manager/include/modules/pm_buffer.h index 78142db821..79e9820817 100644 --- a/lib/bluetooth/peer_manager/include/modules/pm_buffer.h +++ b/lib/bluetooth/peer_manager/include/modules/pm_buffer.h @@ -15,6 +15,7 @@ #define BUFFER_H__ #include +#include #ifdef __cplusplus extern "C" { @@ -33,7 +34,7 @@ extern "C" { */ #define PM_BUFFER_INIT(buffer, n_blocks, block_size, nrf_err) \ do { \ - __ALIGN(4) static uint8_t buffer_memory[(n_blocks) * (block_size)]; \ + __aligned(4) static uint8_t buffer_memory[(n_blocks) * (block_size)]; \ static atomic_t mutex_memory[(n_blocks - 1) / (sizeof(atomic_t) * 8) + 1]; \ nrf_err = pm_buffer_init((buffer), buffer_memory, (n_blocks) * (block_size), \ mutex_memory, (n_blocks), (block_size)); \ diff --git a/lib/bluetooth/peer_manager/modules/nrf_ble_lesc.c b/lib/bluetooth/peer_manager/modules/nrf_ble_lesc.c index 04467f57cb..6b345093e0 100644 --- a/lib/bluetooth/peer_manager/modules/nrf_ble_lesc.c +++ b/lib/bluetooth/peer_manager/modules/nrf_ble_lesc.c @@ -14,6 +14,7 @@ #include #include #include +#include #include @@ -36,9 +37,9 @@ struct lesc_peer_pub_key { #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 lesc_public_key; +__aligned(4) static ble_gap_lesc_p256_pk_t lesc_public_key; /** LESC ECC DH Key. */ -__ALIGN(4) static ble_gap_lesc_dhkey_t lesc_dh_key; +__aligned(4) static ble_gap_lesc_dhkey_t lesc_dh_key; /** Flag indicating that the module encountered an internal error. */ static bool ble_lesc_internal_error; From 56714816170e80981d5328411459af89add59057 Mon Sep 17 00:00:00 2001 From: Andreas Moltumyr Date: Mon, 4 May 2026 15:01:46 +0200 Subject: [PATCH 03/12] lib: peer_manager: no need to align size when storing bonding data No need to round up to nearest word when storing bonding data. The underlying storage now handles this. Side note: The size of struct pm_peer_data_bonding in bytes is currently divisible by 4 and therefore aligned. Therefore, the round up macro currently does nothing here. Signed-off-by: Andreas Moltumyr --- lib/bluetooth/peer_manager/peer_manager.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bluetooth/peer_manager/peer_manager.c b/lib/bluetooth/peer_manager/peer_manager.c index 4a16b13f11..6bd659673c 100644 --- a/lib/bluetooth/peer_manager/peer_manager.c +++ b/lib/bluetooth/peer_manager/peer_manager.c @@ -853,7 +853,7 @@ uint32_t pm_peer_data_bonding_store(uint16_t peer_id, const struct pm_peer_data_ uint32_t *token) { return pm_peer_data_store(peer_id, PM_PEER_DATA_ID_BONDING, data, - ROUND_UP(sizeof(struct pm_peer_data_bonding), 4), token); + sizeof(struct pm_peer_data_bonding), token); } uint32_t pm_peer_data_remote_db_store(uint16_t peer_id, const struct ble_gatt_db_srv *data, From 5dd3c5b830989cb4445fd91894c80ed241fba9e1 Mon Sep 17 00:00:00 2001 From: Andreas Moltumyr Date: Mon, 13 Apr 2026 12:55:36 +0200 Subject: [PATCH 04/12] lib: peer_manager: use inline instead of __INLINE Use the standard inline keyword instead of the cmsis defined __INLINE. Signed-off-by: Andreas Moltumyr --- .../peer_manager/modules/gatt_cache_manager.c | 8 ++++---- .../peer_manager/modules/security_dispatcher.c | 10 +++++----- lib/bluetooth/peer_manager/modules/security_manager.c | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/bluetooth/peer_manager/modules/gatt_cache_manager.c b/lib/bluetooth/peer_manager/modules/gatt_cache_manager.c index 4819012062..254f3394b6 100644 --- a/lib/bluetooth/peer_manager/modules/gatt_cache_manager.c +++ b/lib/bluetooth/peer_manager/modules/gatt_cache_manager.c @@ -200,7 +200,7 @@ static void local_db_apply_in_evt(uint16_t conn_handle) * @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) +static inline void local_db_update(uint16_t conn_handle, bool update) { pm_conn_state_user_flag_set(conn_handle, flag_local_db_update_pending, update); } @@ -379,7 +379,7 @@ static void apply_pending_handle(uint16_t conn_handle, void *context) local_db_apply_in_evt(conn_handle); } -static __INLINE void apply_pending_flags_check(void) +static inline void apply_pending_flags_check(void) { (void)pm_conn_state_for_each_set_user_flag(flag_local_db_apply_pending, apply_pending_handle, NULL); @@ -410,7 +410,7 @@ static void sc_send_pending_handle(uint16_t conn_handle, void *context) } } -static __INLINE void service_changed_pending_flags_check(void) +static inline void service_changed_pending_flags_check(void) { (void)(pm_conn_state_for_each_set_user_flag(flag_service_changed_pending, sc_send_pending_handle, NULL)); @@ -459,7 +459,7 @@ static void car_update_needed(uint16_t conn_handle) } } -static __INLINE void update_pending_flags_check(void) +static inline void update_pending_flags_check(void) { uint32_t count = pm_conn_state_for_each_set_user_flag(flag_local_db_update_pending, db_update_pending_handle, NULL); diff --git a/lib/bluetooth/peer_manager/modules/security_dispatcher.c b/lib/bluetooth/peer_manager/modules/security_dispatcher.c index 21fe8fc22d..91c9a89e60 100644 --- a/lib/bluetooth/peer_manager/modules/security_dispatcher.c +++ b/lib/bluetooth/peer_manager/modules/security_dispatcher.c @@ -48,22 +48,22 @@ static int flag_allow_repairing = PM_CONN_STATE_USER_FLAG_INVALID; static ble_gap_lesc_p256_pk_t peer_pk; -static __INLINE bool sec_procedure(uint16_t conn_handle) +static inline bool sec_procedure(uint16_t conn_handle) { return pm_conn_state_user_flag_get(conn_handle, flag_sec_proc); } -static __INLINE bool pairing(uint16_t conn_handle) +static inline bool pairing(uint16_t conn_handle) { return pm_conn_state_user_flag_get(conn_handle, flag_sec_proc_pairing); } -static __INLINE bool bonding(uint16_t conn_handle) +static inline bool bonding(uint16_t conn_handle) { return pm_conn_state_user_flag_get(conn_handle, flag_sec_proc_bonding); } -static __INLINE bool allow_repairing(uint16_t conn_handle) +static inline bool allow_repairing(uint16_t conn_handle) { return pm_conn_state_user_flag_get(conn_handle, flag_allow_repairing); } @@ -196,7 +196,7 @@ static void pairing_failure(uint16_t conn_handle, uint16_t error, uint8_t error_ * @param[in] error The error the procedure failed with. See @ref PM_SEC_ERRORS. * @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, uint16_t error, uint8_t error_src) +static inline void encryption_failure(uint16_t conn_handle, uint16_t error, uint8_t error_src) { conn_sec_failure(conn_handle, PM_CONN_SEC_PROCEDURE_ENCRYPTION, error, error_src); } diff --git a/lib/bluetooth/peer_manager/modules/security_manager.c b/lib/bluetooth/peer_manager/modules/security_manager.c index 3ff8cde7b6..b65088bb77 100644 --- a/lib/bluetooth/peer_manager/modules/security_manager.c +++ b/lib/bluetooth/peer_manager/modules/security_manager.c @@ -338,7 +338,7 @@ static void smd_params_reply_perform(uint16_t conn_handle, * * @param[in] event The @ref PM_EVT_CONN_SEC_PARAMS_REQ event. */ -static __INLINE void params_req_process(const struct pm_evt *event) +static inline void params_req_process(const struct pm_evt *event) { smd_params_reply_perform(event->conn_handle, event->conn_sec_params_req.peer_params); From 32548ba42d04080429827940865a80ba1a7e253d Mon Sep 17 00:00:00 2001 From: Andreas Moltumyr Date: Fri, 24 Oct 2025 17:48:26 +0200 Subject: [PATCH 05/12] lib: peer_manager: place peer ranks module vars behind PM_PEER_RANKS The module variables used by the peer ranks functionality existed even when CONFIG_PM_PEER_RANKS was unset and nothing was using them. Restructure the names to group them and place them behind the CONFIG_PM_PEER_RANKS Kconfig option. Signed-off-by: Andreas Moltumyr --- lib/bluetooth/peer_manager/peer_manager.c | 121 ++++++++++------------ 1 file changed, 57 insertions(+), 64 deletions(-) diff --git a/lib/bluetooth/peer_manager/peer_manager.c b/lib/bluetooth/peer_manager/peer_manager.c index 6bd659673c..2588112ecd 100644 --- a/lib/bluetooth/peer_manager/peer_manager.c +++ b/lib/bluetooth/peer_manager/peer_manager.c @@ -29,23 +29,28 @@ LOG_MODULE_REGISTER(peer_manager, CONFIG_PEER_MANAGER_LOG_LEVEL); /** Whether or not @ref pm_init has been called successfully. */ static bool module_initialized; -/** Whether or not @ref rank_init has been called successfully. */ -static bool peer_rank_initialized; /** True from when @ref pm_peers_delete is called until all peers have been deleted. */ static bool 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 uint32_t peer_rank_token; -/** The current highest peer rank. Used by @ref pm_peer_rank_highest. */ -static uint32_t current_highest_peer_rank; -/** The peer with the highest peer rank. Used by @ref pm_peer_rank_highest. */ -static uint16_t highest_ranked_peer; /** The subscribers to Peer Manager events, as registered through @ref pm_register. */ static pm_evt_handler_t evt_handlers[CONFIG_PM_MAX_REGISTRANTS]; /** The number of event handlers registered through @ref pm_register. */ static uint8_t n_registrants; +#if defined(CONFIG_PM_PEER_RANKS) +static struct { + /** Whether or not @ref rank_init has been called successfully. */ + bool initialized; + /** The peer with the highest peer rank. Used by @ref pm_peer_rank_highest. */ + uint16_t highest_peer; + /** The current highest peer rank. Used by @ref pm_peer_rank_highest. */ + uint32_t highest_rank; + /** 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. + */ + uint32_t token; +} peer_ranks; +#endif + /** User flag indicating whether a connection is excluded from being handled by the Peer Manager. */ static int flag_conn_excluded = PM_CONN_STATE_USER_FLAG_INVALID; @@ -66,14 +71,14 @@ static void evt_send(const struct pm_evt *pm_evt) static void rank_vars_update(void) { uint32_t nrf_err = - pm_peer_ranks_get(&highest_ranked_peer, ¤t_highest_peer_rank, NULL, NULL); + pm_peer_ranks_get(&peer_ranks.highest_peer, &peer_ranks.highest_rank, NULL, NULL); if (nrf_err == NRF_ERROR_NOT_FOUND) { - highest_ranked_peer = PM_PEER_ID_INVALID; - current_highest_peer_rank = 0; + peer_ranks.highest_peer = PM_PEER_ID_INVALID; + peer_ranks.highest_rank = 0; } - peer_rank_initialized = ((nrf_err == NRF_SUCCESS) || (nrf_err == NRF_ERROR_NOT_FOUND)); + peer_ranks.initialized = ((nrf_err == NRF_SUCCESS) || (nrf_err == NRF_ERROR_NOT_FOUND)); } #endif @@ -93,15 +98,14 @@ void pm_pdb_evt_handler(struct pm_evt *pdb_evt) #if defined(CONFIG_PM_PEER_RANKS) case PM_EVT_PEER_DATA_UPDATE_SUCCEEDED: if (pdb_evt->peer_data_update_succeeded.action == PM_PEER_DATA_OP_UPDATE) { - if ((peer_rank_token != PM_STORE_TOKEN_INVALID) && - (peer_rank_token == pdb_evt->peer_data_update_succeeded.token)) { - peer_rank_token = PM_STORE_TOKEN_INVALID; - highest_ranked_peer = pdb_evt->peer_id; - - pdb_evt->peer_data_update_succeeded.token = - PM_STORE_TOKEN_INVALID; - } else if (peer_rank_initialized && - (pdb_evt->peer_id == highest_ranked_peer) && + if ((peer_ranks.token != PM_STORE_TOKEN_INVALID) && + (peer_ranks.token == pdb_evt->peer_data_update_succeeded.token)) { + peer_ranks.token = PM_STORE_TOKEN_INVALID; + peer_ranks.highest_peer = pdb_evt->peer_id; + + pdb_evt->peer_data_update_succeeded.token = PM_STORE_TOKEN_INVALID; + } else if (peer_ranks.initialized && + (pdb_evt->peer_id == peer_ranks.highest_peer) && (pdb_evt->peer_data_update_succeeded.data_id == PM_PEER_DATA_ID_PEER_RANK)) { /* Update peer rank variable if highest ranked peer has changed its @@ -109,10 +113,9 @@ void pm_pdb_evt_handler(struct pm_evt *pdb_evt) */ rank_vars_update(); } - } else if (pdb_evt->peer_data_update_succeeded.action == - PM_PEER_DATA_OP_DELETE) { - if (peer_rank_initialized && (pdb_evt->peer_id == highest_ranked_peer) && - (pdb_evt->peer_data_update_succeeded.data_id == + } else if (pdb_evt->peer_data_update_succeeded.action == PM_PEER_DATA_OP_DELETE) { + if (peer_ranks.initialized && (pdb_evt->peer_id == peer_ranks.highest_peer) + && (pdb_evt->peer_data_update_succeeded.data_id == PM_PEER_DATA_ID_PEER_RANK)) { /* Update peer rank variable if highest ranked peer has deleted its * rank. @@ -124,10 +127,10 @@ void pm_pdb_evt_handler(struct pm_evt *pdb_evt) case PM_EVT_PEER_DATA_UPDATE_FAILED: if (pdb_evt->peer_data_update_succeeded.action == PM_PEER_DATA_OP_UPDATE) { - if ((peer_rank_token != PM_STORE_TOKEN_INVALID) && - (peer_rank_token == pdb_evt->peer_data_update_failed.token)) { - peer_rank_token = PM_STORE_TOKEN_INVALID; - current_highest_peer_rank -= 1; + if ((peer_ranks.token != PM_STORE_TOKEN_INVALID) && + (peer_ranks.token == pdb_evt->peer_data_update_failed.token)) { + peer_ranks.token = PM_STORE_TOKEN_INVALID; + peer_ranks.highest_rank -= 1; pdb_evt->peer_data_update_succeeded.token = PM_STORE_TOKEN_INVALID; @@ -163,7 +166,7 @@ void pm_pdb_evt_handler(struct pm_evt *pdb_evt) } #if defined(CONFIG_PM_PEER_RANKS) - if (peer_rank_initialized && (pdb_evt->peer_id == highest_ranked_peer)) { + if (peer_ranks.initialized && (pdb_evt->peer_id == peer_ranks.highest_peer)) { /* Update peer rank variable if highest ranked peer has been deleted. */ rank_vars_update(); } @@ -312,13 +315,6 @@ static void ble_evt_handler(const ble_evt_t *ble_evt, void *context) NRF_SDH_BLE_OBSERVER(ble_evt_observer, ble_evt_handler, NULL, HIGH); -/** @brief Function for resetting the internal state of this module. */ -static void internal_state_reset(void) -{ - highest_ranked_peer = PM_PEER_ID_INVALID; - peer_rank_token = PM_STORE_TOKEN_INVALID; -} - uint32_t pm_init(void) { uint32_t nrf_err; @@ -367,19 +363,16 @@ uint32_t pm_init(void) return NRF_ERROR_INTERNAL; } - internal_state_reset(); +#if defined(CONFIG_PM_PEER_RANKS) + peer_ranks.initialized = false; + peer_ranks.highest_peer = PM_PEER_ID_INVALID; + peer_ranks.token = PM_STORE_TOKEN_INVALID; +#endif /* CONFIG_PM_PEER_RANKS */ - peer_rank_initialized = false; module_initialized = true; flag_conn_excluded = pm_conn_state_user_flag_acquire(); - /* If CONFIG_PM_PEER_RANKS is 0, these variables are unused. */ - UNUSED_VARIABLE(peer_rank_initialized); - UNUSED_VARIABLE(peer_rank_token); - UNUSED_VARIABLE(current_highest_peer_rank); - UNUSED_VARIABLE(highest_ranked_peer); - return NRF_SUCCESS; } @@ -1010,9 +1003,9 @@ uint32_t pm_peer_ranks_get(uint16_t *highest_ranked_peer, uint32_t *highest_rank } uint16_t peer_id = pds_next_peer_id_get(PM_PEER_ID_INVALID); - uint32_t peer_rank = 0; - uint32_t length = sizeof(peer_rank); - struct pm_peer_data peer_data = {.peer_rank = &peer_rank}; + uint32_t rank_val = 0; + uint32_t length = sizeof(rank_val); + struct pm_peer_data peer_data = {.peer_rank = &rank_val}; uint32_t nrf_err = pds_peer_data_read(peer_id, PM_PEER_DATA_ID_PEER_RANK, &peer_data, &length); struct { @@ -1034,12 +1027,12 @@ uint32_t pm_peer_ranks_get(uint16_t *highest_ranked_peer, uint32_t *highest_rank while ((nrf_err == NRF_SUCCESS) || (nrf_err == NRF_ERROR_NOT_FOUND)) { if (nrf_err == NRF_SUCCESS) { - if (peer_rank >= rank.highest) { - rank.highest = peer_rank; + if (rank_val >= rank.highest) { + rank.highest = rank_val; rank.highest_peer = peer_id; } - if (peer_rank < rank.lowest) { - rank.lowest = peer_rank; + if (rank_val < rank.lowest) { + rank.lowest = rank_val; rank.lowest_peer = peer_id; } } @@ -1095,18 +1088,18 @@ uint32_t pm_peer_rank_highest(uint16_t peer_id) uint32_t nrf_err; struct pm_peer_data_const peer_data = { - .length = sizeof(current_highest_peer_rank), + .length = sizeof(peer_ranks.highest_rank), .data_id = PM_PEER_DATA_ID_PEER_RANK, - .peer_rank = ¤t_highest_peer_rank}; + .peer_rank = &peer_ranks.highest_rank}; - if (!peer_rank_initialized) { + if (!peer_ranks.initialized) { rank_init(); } - if (!peer_rank_initialized || (peer_rank_token != PM_STORE_TOKEN_INVALID)) { + if (!peer_ranks.initialized || (peer_ranks.token != PM_STORE_TOKEN_INVALID)) { nrf_err = NRF_ERROR_BUSY; } else { - if ((peer_id == highest_ranked_peer) && (current_highest_peer_rank > 0)) { + if ((peer_id == peer_ranks.highest_peer) && (peer_ranks.highest_rank > 0)) { struct pm_evt pm_evt; /* The reported peer is already regarded as highest (provided it has an @@ -1126,15 +1119,15 @@ uint32_t pm_peer_rank_highest(uint16_t peer_id) evt_send(&pm_evt); } else { - if (current_highest_peer_rank == UINT32_MAX) { + if (peer_ranks.highest_rank == UINT32_MAX) { nrf_err = NRF_ERROR_DATA_SIZE; } else { - current_highest_peer_rank += 1; + peer_ranks.highest_rank += 1; nrf_err = pds_peer_data_store(peer_id, &peer_data, - &peer_rank_token); + &peer_ranks.token); if (nrf_err) { - peer_rank_token = PM_STORE_TOKEN_INVALID; - current_highest_peer_rank -= 1; + peer_ranks.token = PM_STORE_TOKEN_INVALID; + peer_ranks.highest_rank -= 1; /* Assume INVALID_PARAM * refers to peer_id, not * data_id. From bbe3d6cfa56063713fe8523acacba1ba155047cf Mon Sep 17 00:00:00 2001 From: Andreas Moltumyr Date: Fri, 24 Oct 2025 17:57:30 +0200 Subject: [PATCH 06/12] lib: peer_manager: clear the registered event handlers on pm_init Calling pm_init() for a second time would not clear the registered handler functions registered with pm_register(). Signed-off-by: Andreas Moltumyr --- lib/bluetooth/peer_manager/peer_manager.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/bluetooth/peer_manager/peer_manager.c b/lib/bluetooth/peer_manager/peer_manager.c index 2588112ecd..283824478e 100644 --- a/lib/bluetooth/peer_manager/peer_manager.c +++ b/lib/bluetooth/peer_manager/peer_manager.c @@ -369,6 +369,7 @@ uint32_t pm_init(void) peer_ranks.token = PM_STORE_TOKEN_INVALID; #endif /* CONFIG_PM_PEER_RANKS */ + n_registrants = 0; module_initialized = true; flag_conn_excluded = pm_conn_state_user_flag_acquire(); From c253412836b75c5fba15cfd1417c0da3d2a2429f Mon Sep 17 00:00:00 2001 From: Andreas Moltumyr Date: Mon, 1 Jun 2026 17:45:23 +0200 Subject: [PATCH 07/12] lib: peer_manager: fix issue with flag indices and multi pm_init calls Fix an issue where calling pm_init two or more times would reset the flag data in conn_state.c but not all flags in the peer manager sub modules would be re-acquire correctly due to a check against PM_CONN_STATE_USER_FLAG_INVALID. Fix this and align how the flags are initialized. Issue appeared and has existed since an internal copy of ble_conn_state was added to peer_manager. Signed-off-by: Andreas Moltumyr --- .../peer_manager/modules/gatt_cache_manager.c | 14 ++++----- .../modules/security_dispatcher.c | 20 +++---------- .../peer_manager/modules/security_manager.c | 30 ++++++++----------- lib/bluetooth/peer_manager/peer_manager.c | 6 ++++ 4 files changed, 29 insertions(+), 41 deletions(-) diff --git a/lib/bluetooth/peer_manager/modules/gatt_cache_manager.c b/lib/bluetooth/peer_manager/modules/gatt_cache_manager.c index 254f3394b6..5e973c47f6 100644 --- a/lib/bluetooth/peer_manager/modules/gatt_cache_manager.c +++ b/lib/bluetooth/peer_manager/modules/gatt_cache_manager.c @@ -48,37 +48,37 @@ static atomic_t 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 flag_local_db_update_pending; +static int flag_local_db_update_pending = PM_CONN_STATE_USER_FLAG_INVALID; /** * @brief Flag ID for flag collection to keep track of which connections need a local DB apply * procedure. */ -static int flag_local_db_apply_pending; +static int flag_local_db_apply_pending = PM_CONN_STATE_USER_FLAG_INVALID; /** * @brief Flag ID for flag collection to keep track of which connections need to be sent a service * changed indication. */ -static int flag_service_changed_pending; +static int flag_service_changed_pending = PM_CONN_STATE_USER_FLAG_INVALID; /** * @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 flag_service_changed_sent; +static int flag_service_changed_sent = PM_CONN_STATE_USER_FLAG_INVALID; /** * @brief Flag ID for flag collection to keep track of which connections need to have their Central * Address Resolution value stored. */ -static int flag_car_update_pending; +static int flag_car_update_pending = PM_CONN_STATE_USER_FLAG_INVALID; /** * @brief Flag ID for flag collection to keep track of which connections are pending Central * Address Resolution handle reply. */ -static int flag_car_handle_queried; +static int flag_car_handle_queried = PM_CONN_STATE_USER_FLAG_INVALID; /** * @brief Flag ID for flag collection to keep track of which connections are pending Central * Address Resolution value reply. */ -static int flag_car_value_queried; +static int flag_car_value_queried = PM_CONN_STATE_USER_FLAG_INVALID; /** * @brief Function for resetting the module variable(s) of the GSCM module. diff --git a/lib/bluetooth/peer_manager/modules/security_dispatcher.c b/lib/bluetooth/peer_manager/modules/security_dispatcher.c index 91c9a89e60..3dbf04d639 100644 --- a/lib/bluetooth/peer_manager/modules/security_dispatcher.c +++ b/lib/bluetooth/peer_manager/modules/security_dispatcher.c @@ -716,26 +716,14 @@ static void conn_sec_update_process(const ble_gap_evt_t *gap_evt) } } -/** - * @brief Function for initializing a Bluetooth LE Connection State user flag. - * - * @param[out] flag_id The flag to initialize. - */ -static void flag_id_init(int *flag_id) -{ - if (*flag_id == PM_CONN_STATE_USER_FLAG_INVALID) { - *flag_id = pm_conn_state_user_flag_acquire(); - } -} - uint32_t smd_init(void) { __ASSERT_NO_MSG(!module_initialized); - flag_id_init(&flag_sec_proc); - flag_id_init(&flag_sec_proc_pairing); - flag_id_init(&flag_sec_proc_bonding); - flag_id_init(&flag_allow_repairing); + flag_sec_proc = pm_conn_state_user_flag_acquire(); + flag_sec_proc_pairing = pm_conn_state_user_flag_acquire(); + flag_sec_proc_bonding = pm_conn_state_user_flag_acquire(); + flag_allow_repairing = pm_conn_state_user_flag_acquire(); if ((flag_sec_proc == PM_CONN_STATE_USER_FLAG_INVALID) || (flag_sec_proc_pairing == PM_CONN_STATE_USER_FLAG_INVALID) || diff --git a/lib/bluetooth/peer_manager/modules/security_manager.c b/lib/bluetooth/peer_manager/modules/security_manager.c index b65088bb77..d2cf34ea7a 100644 --- a/lib/bluetooth/peer_manager/modules/security_manager.c +++ b/lib/bluetooth/peer_manager/modules/security_manager.c @@ -489,18 +489,6 @@ void sm_pdb_evt_handler(struct pm_evt *event) } } -/** - * @brief Function for initializing a Bluetooth LE Connection State user flag. - * - * @param[out] flag_id The flag to initialize. - */ -static void flag_id_init(int *flag_id) -{ - if (*flag_id == PM_CONN_STATE_USER_FLAG_INVALID) { - *flag_id = pm_conn_state_user_flag_acquire(); - } -} - uint32_t sm_init(void) { __ASSERT_NO_MSG(!module_initialized); @@ -513,12 +501,18 @@ uint32_t sm_init(void) } #endif - flag_id_init(&flag_link_secure_pending_busy); - flag_id_init(&flag_link_secure_force_repairing); - flag_id_init(&flag_link_secure_null_params); - flag_id_init(&flag_params_reply_pending_busy); - - if (flag_params_reply_pending_busy == PM_CONN_STATE_USER_FLAG_INVALID) { + flag_link_secure_pending_busy = pm_conn_state_user_flag_acquire(); + flag_link_secure_force_repairing = pm_conn_state_user_flag_acquire(); + flag_link_secure_null_params = pm_conn_state_user_flag_acquire(); + flag_params_reply_pending_busy = pm_conn_state_user_flag_acquire(); + + /* If successfully acquiring the last set of flags, + * all sets of flags have been successfully acquired. + */ + if ((flag_link_secure_pending_busy == PM_CONN_STATE_USER_FLAG_INVALID) || + (flag_link_secure_force_repairing == PM_CONN_STATE_USER_FLAG_INVALID) || + (flag_link_secure_null_params == PM_CONN_STATE_USER_FLAG_INVALID) || + (flag_params_reply_pending_busy == PM_CONN_STATE_USER_FLAG_INVALID)) { LOG_ERR("Could not acquire conn_state user flags. Increase " "PM_CONN_STATE_USER_FLAG_COUNT in the pm_conn_state module."); return NRF_ERROR_INTERNAL; diff --git a/lib/bluetooth/peer_manager/peer_manager.c b/lib/bluetooth/peer_manager/peer_manager.c index 283824478e..3c60b145a8 100644 --- a/lib/bluetooth/peer_manager/peer_manager.c +++ b/lib/bluetooth/peer_manager/peer_manager.c @@ -374,6 +374,12 @@ uint32_t pm_init(void) flag_conn_excluded = pm_conn_state_user_flag_acquire(); + if (flag_conn_excluded == PM_CONN_STATE_USER_FLAG_INVALID) { + LOG_ERR("Could not acquire conn_state user flags. Increase " + "PM_CONN_STATE_USER_FLAG_COUNT in the pm_conn_state module."); + return NRF_ERROR_INTERNAL; + } + return NRF_SUCCESS; } From 89e9b1044ef2bc5d8904c6ca8c4c3ff0323df9d6 Mon Sep 17 00:00:00 2001 From: Andreas Moltumyr Date: Fri, 29 May 2026 15:34:28 +0200 Subject: [PATCH 08/12] lib: peer_manager: fix pm_address_resolve module not inited return The pm_address_resolve function should not return an nrf_error. Change the value returned when the peer manager module is not initialized to false. Signed-off-by: Andreas Moltumyr --- doc/nrf-bm/release_notes/release_notes_changelog.rst | 4 ++++ lib/bluetooth/peer_manager/peer_manager.c | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/nrf-bm/release_notes/release_notes_changelog.rst b/doc/nrf-bm/release_notes/release_notes_changelog.rst index 6e6ef0adff..002a88cd65 100644 --- a/doc/nrf-bm/release_notes/release_notes_changelog.rst +++ b/doc/nrf-bm/release_notes/release_notes_changelog.rst @@ -81,6 +81,10 @@ Libraries * An issue where the :c:func:`ble_conn_params_phy_radio_mode_get` function would incorrectly return the PHY mode mask of a pending update rather than the currently active PHY mode if a PHY update initiated by the :c:func:`ble_conn_params_phy_radio_mode_set` function was still in progress. * An issue where the SoftDevice define :c:macro:`BLE_GAP_PHYS_SUPPORTED` was used instead of the PHY preferences set with Kconfig when initiating or responding to a PHY update procedure. +* :ref:`lib_peer_manager` library: + + * Fixed the :c:func:`pm_address_resolve` function to return ``false`` instead of ``NRF_ERROR_INVALID_STATE`` when Peer Manager is not initialized. + Bluetooth LE Services --------------------- diff --git a/lib/bluetooth/peer_manager/peer_manager.c b/lib/bluetooth/peer_manager/peer_manager.c index 3c60b145a8..a306c21eb8 100644 --- a/lib/bluetooth/peer_manager/peer_manager.c +++ b/lib/bluetooth/peer_manager/peer_manager.c @@ -517,7 +517,7 @@ uint32_t pm_privacy_get(ble_gap_privacy_params_t *privacy_params) bool pm_address_resolve(const ble_gap_addr_t *addr, const ble_gap_irk_t *irk) { if (!module_initialized) { - return NRF_ERROR_INVALID_STATE; + return false; } if ((addr == NULL) || (irk == NULL)) { From 5fcc9a8ff69d8201a907a212304d3d37ed191ec0 Mon Sep 17 00:00:00 2001 From: Andreas Moltumyr Date: Tue, 2 Jun 2026 15:40:59 +0200 Subject: [PATCH 09/12] lib: peer_manager: fix storage read buffer in gscm_local_db_cache_apply Fix an issue where the local_gatt_db_buf (in the gscm_local_db_cache_apply function) was used after going out of scope. This was not caught earlier because nothing was allocated on the stack that could corrupt the data in local_gatt_db_buf before it was used by the sd_ble_gatts_sys_attr_set SVC. Signed-off-by: Andreas Moltumyr --- .../modules/gatts_cache_manager.c | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/lib/bluetooth/peer_manager/modules/gatts_cache_manager.c b/lib/bluetooth/peer_manager/modules/gatts_cache_manager.c index 14c0b0f055..b3690d064e 100644 --- a/lib/bluetooth/peer_manager/modules/gatts_cache_manager.c +++ b/lib/bluetooth/peer_manager/modules/gatts_cache_manager.c @@ -255,29 +255,24 @@ uint32_t gscm_local_db_cache_apply(uint16_t conn_handle) { __ASSERT_NO_MSG(module_initialized); - uint16_t peer_id = im_peer_id_get_by_conn_handle(conn_handle); uint32_t nrf_err; - struct pm_peer_data peer_data; + uint32_t sys_attr_flags = (SYS_ATTR_BOTH); const uint8_t *sys_attr_data = NULL; uint16_t sys_attr_len = 0; - uint32_t sys_attr_flags = (SYS_ATTR_BOTH); + uint16_t peer_id = im_peer_id_get_by_conn_handle(conn_handle); + uint8_t local_gatt_db_buf[PM_PEER_DATA_LOCAL_GATT_DB_MAX_SIZE] = {0}; + uint32_t local_gatt_db_size = PM_PEER_DATA_LOCAL_GATT_DB_MAX_SIZE; + struct pm_peer_data peer_data; bool all_attributes_applied = true; if (peer_id != PM_PEER_ID_INVALID) { - uint8_t local_gatt_db_buf[PM_PEER_DATA_LOCAL_GATT_DB_MAX_SIZE] = { 0 }; - uint32_t local_gatt_db_size = PM_PEER_DATA_LOCAL_GATT_DB_MAX_SIZE; - struct pm_peer_data_local_gatt_db *curr_local_gatt_db = - (struct pm_peer_data_local_gatt_db *)local_gatt_db_buf; peer_data.all_data = &local_gatt_db_buf; nrf_err = pds_peer_data_read(peer_id, PM_PEER_DATA_ID_GATT_LOCAL, &peer_data, &local_gatt_db_size); if (nrf_err == NRF_SUCCESS) { - const struct pm_peer_data_local_gatt_db *local_gatt_db; - - local_gatt_db = curr_local_gatt_db; - sys_attr_data = local_gatt_db->data; - sys_attr_len = local_gatt_db->len; - sys_attr_flags = local_gatt_db->flags; + sys_attr_flags = peer_data.local_gatt_db->flags; + sys_attr_len = peer_data.local_gatt_db->len; + sys_attr_data = peer_data.local_gatt_db->data; } } From 379e12120278c269669fd5c4ed4777d5835b2aec Mon Sep 17 00:00:00 2001 From: Andreas Moltumyr Date: Tue, 2 Sep 2025 14:51:06 +0200 Subject: [PATCH 10/12] tests: unit: peer_manager: add nrf_ble_lesc module unit tests Add unit tests for the nrf_ble_lesc submodule of the peer_manager library. Signed-off-by: Andreas Moltumyr --- .../lib/bluetooth/nrf_ble_lesc/CMakeLists.txt | 31 + tests/unit/lib/bluetooth/nrf_ble_lesc/Kconfig | 11 + .../nrf_ble_lesc/include/psa/crypto.h | 57 ++ .../unit/lib/bluetooth/nrf_ble_lesc/prj.conf | 1 + .../bluetooth/nrf_ble_lesc/src/unity_test.c | 792 ++++++++++++++++++ .../lib/bluetooth/nrf_ble_lesc/testcase.yaml | 4 + 6 files changed, 896 insertions(+) create mode 100644 tests/unit/lib/bluetooth/nrf_ble_lesc/CMakeLists.txt create mode 100644 tests/unit/lib/bluetooth/nrf_ble_lesc/Kconfig create mode 100644 tests/unit/lib/bluetooth/nrf_ble_lesc/include/psa/crypto.h create mode 100644 tests/unit/lib/bluetooth/nrf_ble_lesc/prj.conf create mode 100644 tests/unit/lib/bluetooth/nrf_ble_lesc/src/unity_test.c create mode 100644 tests/unit/lib/bluetooth/nrf_ble_lesc/testcase.yaml diff --git a/tests/unit/lib/bluetooth/nrf_ble_lesc/CMakeLists.txt b/tests/unit/lib/bluetooth/nrf_ble_lesc/CMakeLists.txt new file mode 100644 index 0000000000..a99ba5a2b1 --- /dev/null +++ b/tests/unit/lib/bluetooth/nrf_ble_lesc/CMakeLists.txt @@ -0,0 +1,31 @@ +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(unit_test_peer_manager_nrf_ble_lesc) + +include(${ZEPHYR_NRF_BM_MODULE_DIR}/cmake/unity/unity_softdevice_setup.cmake) +unity_softdevice_header_setup(VARIANT "s145") + +cmock_handle(${SOFTDEVICE_INCLUDE_DIR}/ble_gap.h) +cmock_handle(${ZEPHYR_NRF_BM_MODULE_DIR}/include/bm/softdevice_handler/nrf_sdh_ble.h) +cmock_handle(include/psa/crypto.h) # Local definitions for mocking of psa/crypto.h with CMock + +zephyr_include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/include # Include test specific headers +) + +# Generate and add test file +test_runner_generate(src/unity_test.c) +target_sources(app PRIVATE src/unity_test.c) + +# Unit under test +target_sources(app PRIVATE + ${ZEPHYR_NRF_BM_MODULE_DIR}/lib/bluetooth/peer_manager/modules/nrf_ble_lesc.c +) diff --git a/tests/unit/lib/bluetooth/nrf_ble_lesc/Kconfig b/tests/unit/lib/bluetooth/nrf_ble_lesc/Kconfig new file mode 100644 index 0000000000..0a392a5131 --- /dev/null +++ b/tests/unit/lib/bluetooth/nrf_ble_lesc/Kconfig @@ -0,0 +1,11 @@ +# Clear dependencies for PM_LESC and enable it to allow +# testing the features without enabling the peer manager library. +config PM_LESC + default y + +# Redefine Kconfigs used by the tested module that is defined in +# other modules we do not want to enable. +config NRF_SDH_BLE_TOTAL_LINK_COUNT + default 2 + +source "Kconfig.zephyr" diff --git a/tests/unit/lib/bluetooth/nrf_ble_lesc/include/psa/crypto.h b/tests/unit/lib/bluetooth/nrf_ble_lesc/include/psa/crypto.h new file mode 100644 index 0000000000..96f2e0f21d --- /dev/null +++ b/tests/unit/lib/bluetooth/nrf_ble_lesc/include/psa/crypto.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef PSA_CRYPTO_FOR_MOCKING_H__ +#define PSA_CRYPTO_FOR_MOCKING_H__ + +#include +#include + +/* Header for mocking the required psa/crypto.h functionality with CMock. + * For use in nrf_ble_lesc unit tests. + */ + +typedef uint32_t psa_key_id_t; +typedef psa_key_id_t mbedtls_svc_key_id_t; +typedef int32_t psa_status_t; +typedef uint16_t psa_key_type_t; + +typedef int psa_key_attributes_t; +typedef uint32_t psa_key_usage_t; +typedef uint32_t psa_key_lifetime_t; +typedef uint32_t psa_algorithm_t; +typedef uint8_t psa_ecc_family_t; + +#define PSA_KEY_ATTRIBUTES_INIT 0x00C0FFEE + +#define PSA_EXPORT_PUBLIC_KEY_OUTPUT_SIZE(key_type, key_bits) (65) +#define PSA_KEY_TYPE_ECC_KEY_PAIR(curve) 0 +#define PSA_ECC_FAMILY_SECP_R1 ((psa_ecc_family_t)0x12) + +#define PSA_KEY_USAGE_DERIVE ((psa_key_usage_t)0x00004000) +#define PSA_KEY_LIFETIME_VOLATILE ((psa_key_lifetime_t)0x00000000) +#define PSA_ALG_ECDH ((psa_algorithm_t)0x09020000) + +#define PSA_SUCCESS ((psa_status_t)0) +#define PSA_ERROR_INVALID_HANDLE ((psa_status_t)-136) +#define PSA_ERROR_BAD_STATE ((psa_status_t)-137) + +psa_status_t psa_crypto_init(void); +psa_status_t psa_destroy_key(mbedtls_svc_key_id_t key); +void psa_set_key_usage_flags(psa_key_attributes_t *attributes, psa_key_usage_t usage_flags); +void psa_set_key_lifetime(psa_key_attributes_t *attributes, psa_key_lifetime_t lifetime); +void psa_set_key_algorithm(psa_key_attributes_t *attributes, psa_algorithm_t alg); +void psa_set_key_type(psa_key_attributes_t *attributes, psa_key_type_t type); +void psa_set_key_bits(psa_key_attributes_t *attributes, size_t bits); +psa_status_t psa_generate_key(const psa_key_attributes_t *attributes, mbedtls_svc_key_id_t *key); +psa_status_t psa_export_public_key(mbedtls_svc_key_id_t key, uint8_t *data, size_t data_size, + size_t *data_length); +psa_status_t psa_raw_key_agreement(psa_algorithm_t alg, mbedtls_svc_key_id_t private_key, + const uint8_t *peer_key, size_t peer_key_length, + uint8_t *output, size_t output_size, size_t *output_length); +psa_status_t psa_generate_random(uint8_t *output, size_t output_size); + +#endif /* PSA_CRYPTO_FOR_MOCKING_H__ */ diff --git a/tests/unit/lib/bluetooth/nrf_ble_lesc/prj.conf b/tests/unit/lib/bluetooth/nrf_ble_lesc/prj.conf new file mode 100644 index 0000000000..3fb209dc63 --- /dev/null +++ b/tests/unit/lib/bluetooth/nrf_ble_lesc/prj.conf @@ -0,0 +1 @@ +CONFIG_UNITY=y diff --git a/tests/unit/lib/bluetooth/nrf_ble_lesc/src/unity_test.c b/tests/unit/lib/bluetooth/nrf_ble_lesc/src/unity_test.c new file mode 100644 index 0000000000..8b6b4c0c52 --- /dev/null +++ b/tests/unit/lib/bluetooth/nrf_ble_lesc/src/unity_test.c @@ -0,0 +1,792 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include + +#include "cmock_ble_gap.h" +#include "cmock_crypto.h" +#include "cmock_nrf_sdh_ble.h" + +#include + +#define PTR_IGNORE NULL +#define VAL_IGNORE 0 + +static psa_key_attributes_t *key_attrs; +static const psa_key_attributes_t key_attrs_expected = PSA_KEY_ATTRIBUTES_INIT; + +static ble_gap_lesc_oob_data_t *oobd; + +static const mbedtls_svc_key_id_t key_pair_id = 0x2A; + +static uint8_t own_test_pub_key_psa[] = { + 0x04, + + 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, + 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, + 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, + 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, + + 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, + 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, + 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, + 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, +}; + +static uint8_t own_test_pub_key_sd[] = { + 0xCF, 0xCE, 0xCD, 0xCC, 0xCB, 0xCA, 0xC9, 0xC8, + 0xC7, 0xC6, 0xC5, 0xC4, 0xC3, 0xC2, 0xC1, 0xC0, + 0xBF, 0xBE, 0xBD, 0xBC, 0xBB, 0xBA, 0xB9, 0xB8, + 0xB7, 0xB6, 0xB5, 0xB4, 0xB3, 0xB2, 0xB1, 0xB0, + + 0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8, + 0xE7, 0xE6, 0xE5, 0xE4, 0xE3, 0xE2, 0xE1, 0xE0, + 0xDF, 0xDE, 0xDD, 0xDC, 0xDB, 0xDA, 0xD9, 0xD8, + 0xD7, 0xD6, 0xD5, 0xD4, 0xD3, 0xD2, 0xD1, 0xD0, +}; + +static uint8_t peer_test_pub_key_psa[] = { + 0x04, + + 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, + 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, + + 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, +}; + +static uint8_t peer_test_pub_key_sd[] = { + 0xA7, 0xA6, 0xA5, 0xA4, 0xA3, 0xA2, 0xA1, 0xA0, + 0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0xAA, 0xA9, 0xA8, + 0x97, 0x96, 0x95, 0x94, 0x93, 0x92, 0x91, 0x90, + 0x9F, 0x9E, 0x9D, 0x9C, 0x9B, 0x9A, 0x99, 0x98, + + 0x87, 0x86, 0x85, 0x84, 0x83, 0x82, 0x81, 0x80, + 0x8F, 0x8E, 0x8D, 0x8C, 0x8B, 0x8A, 0x89, 0x88, + 0x77, 0x76, 0x75, 0x74, 0x73, 0x72, 0x71, 0x70, + 0x7F, 0x7E, 0x7D, 0x7C, 0x7B, 0x7A, 0x79, 0x78, +}; + +static uint8_t test_pub_key_sd_cleared[] = { + 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, + 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, + 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, + 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, + + 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, + 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, + 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, + 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, +}; + +static uint8_t test_secret_psa[] = { + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, + 0x0F, 0x1E, 0x2D, 0x3C, 0x4B, 0x5A, 0x69, 0x78, + 0x87, 0x96, 0xA5, 0xB4, 0xC3, 0xD2, 0xE1, 0xF0, +}; + +static uint8_t test_secret_sd[] = { + 0xF0, 0xE1, 0xD2, 0xC3, 0xB4, 0xA5, 0x96, 0x87, + 0x78, 0x69, 0x5A, 0x4B, 0x3C, 0x2D, 0x1E, 0x0F, + 0x00, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, + 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, +}; + +BUILD_ASSERT(sizeof(own_test_pub_key_psa) == sizeof(uint8_t) + BLE_GAP_LESC_P256_PK_LEN); +BUILD_ASSERT(sizeof(own_test_pub_key_sd) == BLE_GAP_LESC_P256_PK_LEN); +BUILD_ASSERT(sizeof(peer_test_pub_key_psa) == sizeof(uint8_t) + BLE_GAP_LESC_P256_PK_LEN); +BUILD_ASSERT(sizeof(peer_test_pub_key_sd) == BLE_GAP_LESC_P256_PK_LEN); +BUILD_ASSERT(sizeof(test_secret_psa) == BLE_GAP_LESC_DHKEY_LEN); +BUILD_ASSERT(sizeof(test_secret_sd) == BLE_GAP_LESC_DHKEY_LEN); + +#define KEY_ATTRS_CHECK(_attributes) \ + do { \ + if (key_attrs != NULL) { \ + TEST_ASSERT_EQUAL_PTR(key_attrs, _attributes); \ + } else { \ + TEST_ASSERT_EQUAL_MEMORY(&key_attrs_expected, _attributes, \ + sizeof(key_attrs_expected)); \ + key_attrs = _attributes; \ + } \ + } while (0) + +static void stub_psa_set_key_usage_flags( + psa_key_attributes_t *attributes, psa_key_usage_t usage_flags, int cmock_num_calls) +{ + ARG_UNUSED(cmock_num_calls); + + TEST_ASSERT_NOT_NULL(attributes); + KEY_ATTRS_CHECK(attributes); + + TEST_ASSERT_EQUAL(PSA_KEY_USAGE_DERIVE, usage_flags); +} + +static void stub_psa_set_key_lifetime( + psa_key_attributes_t *attributes, psa_key_lifetime_t lifetime, int cmock_num_calls) +{ + ARG_UNUSED(cmock_num_calls); + + TEST_ASSERT_NOT_NULL(attributes); + KEY_ATTRS_CHECK(attributes); + + TEST_ASSERT_EQUAL(PSA_KEY_LIFETIME_VOLATILE, lifetime); +} + +static void stub_psa_set_key_algorithm( + psa_key_attributes_t *attributes, psa_algorithm_t alg, int cmock_num_calls) +{ + ARG_UNUSED(cmock_num_calls); + + TEST_ASSERT_NOT_NULL(attributes); + KEY_ATTRS_CHECK(attributes); + + TEST_ASSERT_EQUAL(PSA_ALG_ECDH, alg); +} + +static void stub_psa_set_key_type( + psa_key_attributes_t *attributes, psa_key_type_t type, int cmock_num_calls) +{ + ARG_UNUSED(cmock_num_calls); + + TEST_ASSERT_NOT_NULL(attributes); + KEY_ATTRS_CHECK(attributes); + + TEST_ASSERT_EQUAL(PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1), type); +} + + +static void stub_psa_set_key_bits( + psa_key_attributes_t *attributes, size_t bits, int cmock_num_calls) +{ + ARG_UNUSED(cmock_num_calls); + + TEST_ASSERT_NOT_NULL(attributes); + KEY_ATTRS_CHECK(attributes); + + TEST_ASSERT_EQUAL(256, bits); +} + +static psa_status_t stub_psa_generate_key_success( + const psa_key_attributes_t *attributes, mbedtls_svc_key_id_t *key, int cmock_num_calls) +{ + ARG_UNUSED(cmock_num_calls); + + TEST_ASSERT_NOT_NULL(attributes); + TEST_ASSERT_EQUAL_PTR(key_attrs, attributes); + + TEST_ASSERT_NOT_NULL(key); + *key = key_pair_id; + + return PSA_SUCCESS; +} + +static psa_status_t stub_psa_generate_key_failure( + const psa_key_attributes_t *attributes, mbedtls_svc_key_id_t *key, int cmock_num_calls) +{ + ARG_UNUSED(cmock_num_calls); + + TEST_ASSERT_NOT_NULL(attributes); + TEST_ASSERT_EQUAL_PTR(key_attrs, attributes); + + TEST_ASSERT_NOT_NULL(key); + + return PSA_ERROR_BAD_STATE; +} + +static psa_status_t stub_psa_export_public_key_success(mbedtls_svc_key_id_t key, + uint8_t *data, size_t data_size, size_t *data_length, int cmock_num_calls) +{ + ARG_UNUSED(cmock_num_calls); + + TEST_ASSERT_EQUAL(key_pair_id, key); + TEST_ASSERT_EQUAL(sizeof(own_test_pub_key_psa), data_size); + + TEST_ASSERT_NOT_NULL(data); + memcpy(data, own_test_pub_key_psa, sizeof(own_test_pub_key_psa)); + + TEST_ASSERT_NOT_NULL(data_length); + *data_length = sizeof(own_test_pub_key_psa); + + return PSA_SUCCESS; +} + +static psa_status_t stub_psa_export_public_key_failure(mbedtls_svc_key_id_t key, + uint8_t *data, size_t data_size, size_t *data_length, int cmock_num_calls) +{ + ARG_UNUSED(cmock_num_calls); + + TEST_ASSERT_EQUAL(key_pair_id, key); + TEST_ASSERT_EQUAL(sizeof(own_test_pub_key_psa), data_size); + + TEST_ASSERT_NOT_NULL(data); + TEST_ASSERT_NOT_NULL(data_length); + + return PSA_ERROR_BAD_STATE; +} + +static void reinitialize(void) +{ + uint32_t err; + + /* Stubs have priority. Clear stubs so that Expect functions are used. */ + __cmock_psa_crypto_init_Stub(NULL); + __cmock_psa_set_key_usage_flags_Stub(NULL); + __cmock_psa_set_key_lifetime_Stub(NULL); + __cmock_psa_set_key_algorithm_Stub(NULL); + __cmock_psa_set_key_type_Stub(NULL); + __cmock_psa_set_key_bits_Stub(NULL); + __cmock_psa_destroy_key_Stub(NULL); + __cmock_psa_generate_key_Stub(NULL); + + /* Set Expect functions. */ + __cmock_psa_crypto_init_ExpectAndReturn(PSA_SUCCESS); + __cmock_psa_set_key_usage_flags_ExpectAnyArgs(); + __cmock_psa_set_key_lifetime_ExpectAnyArgs(); + __cmock_psa_set_key_algorithm_ExpectAnyArgs(); + __cmock_psa_set_key_type_ExpectAnyArgs(); + __cmock_psa_set_key_bits_ExpectAnyArgs(); + __cmock_psa_destroy_key_ExpectAnyArgsAndReturn(PSA_ERROR_INVALID_HANDLE); + __cmock_psa_generate_key_ExpectAnyArgsAndReturn(PSA_ERROR_BAD_STATE); + + err = nrf_ble_lesc_init(); + TEST_ASSERT_EQUAL(NRF_ERROR_INTERNAL, err); +} + +static void generate_key_pair(void) +{ + uint32_t err; + + __cmock_psa_destroy_key_ExpectAnyArgsAndReturn(PSA_ERROR_INVALID_HANDLE); + + __cmock_psa_set_key_usage_flags_Stub(stub_psa_set_key_usage_flags); + __cmock_psa_set_key_lifetime_Stub(stub_psa_set_key_lifetime); + __cmock_psa_set_key_algorithm_Stub(stub_psa_set_key_algorithm); + __cmock_psa_set_key_type_Stub(stub_psa_set_key_type); + __cmock_psa_set_key_bits_Stub(stub_psa_set_key_bits); + + __cmock_psa_generate_key_Stub(stub_psa_generate_key_success); + __cmock_psa_export_public_key_Stub(stub_psa_export_public_key_success); + + err = nrf_ble_lesc_keypair_generate(); + TEST_ASSERT_EQUAL(NRF_SUCCESS, err); + + /* Stubs have priority. Reset stubs so they are unset when returning from this function. */ + __cmock_psa_set_key_usage_flags_Stub(NULL); + __cmock_psa_set_key_lifetime_Stub(NULL); + __cmock_psa_set_key_algorithm_Stub(NULL); + __cmock_psa_set_key_type_Stub(NULL); + __cmock_psa_set_key_bits_Stub(NULL); + __cmock_psa_destroy_key_Stub(NULL); + __cmock_psa_generate_key_Stub(NULL); +} + +void test_nrf_ble_lesc_init_success(void) +{ + uint32_t err; + + __cmock_psa_crypto_init_ExpectAndReturn(PSA_SUCCESS); + __cmock_psa_destroy_key_ExpectAnyArgsAndReturn(PSA_ERROR_INVALID_HANDLE); + + __cmock_psa_set_key_usage_flags_Stub(stub_psa_set_key_usage_flags); + __cmock_psa_set_key_lifetime_Stub(stub_psa_set_key_lifetime); + __cmock_psa_set_key_algorithm_Stub(stub_psa_set_key_algorithm); + __cmock_psa_set_key_type_Stub(stub_psa_set_key_type); + __cmock_psa_set_key_bits_Stub(stub_psa_set_key_bits); + + __cmock_psa_generate_key_Stub(stub_psa_generate_key_success); + __cmock_psa_export_public_key_Stub(stub_psa_export_public_key_success); + + err = nrf_ble_lesc_init(); + TEST_ASSERT_EQUAL(NRF_SUCCESS, err); +} + +void test_nrf_ble_lesc_init_error_internal(void) +{ + uint32_t err; + + __cmock_psa_crypto_init_ExpectAndReturn(PSA_ERROR_BAD_STATE); + + err = nrf_ble_lesc_init(); + TEST_ASSERT_EQUAL_UINT32(NRF_ERROR_INTERNAL, err); +} + +void test_nrf_ble_lesc_keypair_generate_and_public_key_get_success(void) +{ + uint32_t err; + ble_gap_lesc_p256_pk_t *pub_key; + + __cmock_psa_destroy_key_ExpectAnyArgsAndReturn(PSA_SUCCESS); + + __cmock_psa_set_key_usage_flags_Stub(stub_psa_set_key_usage_flags); + __cmock_psa_set_key_lifetime_Stub(stub_psa_set_key_lifetime); + __cmock_psa_set_key_algorithm_Stub(stub_psa_set_key_algorithm); + __cmock_psa_set_key_type_Stub(stub_psa_set_key_type); + __cmock_psa_set_key_bits_Stub(stub_psa_set_key_bits); + + __cmock_psa_generate_key_Stub(stub_psa_generate_key_success); + __cmock_psa_export_public_key_Stub(stub_psa_export_public_key_success); + + err = nrf_ble_lesc_keypair_generate(); + TEST_ASSERT_EQUAL(NRF_SUCCESS, err); + + pub_key = nrf_ble_lesc_public_key_get(); + TEST_ASSERT_NOT_NULL(pub_key); + TEST_ASSERT_EQUAL_MEMORY(own_test_pub_key_sd, pub_key->pk, sizeof(own_test_pub_key_sd)); +} + +void test_nrf_ble_lesc_keypair_generate_and_public_key_get_error_internal(void) +{ + uint32_t err; + ble_gap_lesc_p256_pk_t *pub_key; + + __cmock_psa_set_key_usage_flags_Stub(stub_psa_set_key_usage_flags); + __cmock_psa_set_key_lifetime_Stub(stub_psa_set_key_lifetime); + __cmock_psa_set_key_algorithm_Stub(stub_psa_set_key_algorithm); + __cmock_psa_set_key_type_Stub(stub_psa_set_key_type); + __cmock_psa_set_key_bits_Stub(stub_psa_set_key_bits); + + /* Generate key error. */ + __cmock_psa_destroy_key_ExpectAnyArgsAndReturn(PSA_ERROR_INVALID_HANDLE); + + __cmock_psa_generate_key_Stub(stub_psa_generate_key_failure); + + err = nrf_ble_lesc_keypair_generate(); + TEST_ASSERT_EQUAL(NRF_ERROR_INTERNAL, err); + + pub_key = nrf_ble_lesc_public_key_get(); + TEST_ASSERT_NULL(pub_key); + + key_attrs = NULL; + + /* Export key error. */ + __cmock_psa_destroy_key_ExpectAnyArgsAndReturn(PSA_ERROR_INVALID_HANDLE); + + __cmock_psa_generate_key_Stub(stub_psa_generate_key_success); + __cmock_psa_export_public_key_Stub(stub_psa_export_public_key_failure); + + err = nrf_ble_lesc_keypair_generate(); + TEST_ASSERT_EQUAL(NRF_ERROR_INTERNAL, err); + + pub_key = nrf_ble_lesc_public_key_get(); + TEST_ASSERT_NULL(pub_key); +} + +static uint32_t stub_sd_ble_gap_lesc_oob_data_get_success( + uint16_t conn_handle, ble_gap_lesc_p256_pk_t const *p_pk_own, + ble_gap_lesc_oob_data_t *p_oobd_own, int cmock_num_calls) +{ + TEST_ASSERT_EQUAL(BLE_CONN_HANDLE_INVALID, conn_handle); + + TEST_ASSERT_NOT_NULL(p_pk_own); + TEST_ASSERT_EQUAL_MEMORY(own_test_pub_key_sd, p_pk_own->pk, sizeof(own_test_pub_key_sd)); + + TEST_ASSERT_NOT_NULL(p_oobd_own); + oobd = p_oobd_own; + + return NRF_SUCCESS; +} + +void test_nrf_ble_lesc_own_oob_data_generation_and_get_success(void) +{ + uint32_t err; + ble_gap_lesc_oob_data_t *lesc_oob_data; + + generate_key_pair(); + + __cmock_sd_ble_gap_lesc_oob_data_get_Stub(stub_sd_ble_gap_lesc_oob_data_get_success); + + err = nrf_ble_lesc_own_oob_data_generate(); + TEST_ASSERT_EQUAL(NRF_SUCCESS, err); + + lesc_oob_data = nrf_ble_lesc_own_oob_data_get(); + TEST_ASSERT_NOT_NULL(lesc_oob_data); + TEST_ASSERT_EQUAL_PTR(oobd, lesc_oob_data); +} + +void test_nrf_ble_lesc_own_oob_data_generate_error_invalid_state(void) +{ + uint32_t err; + ble_gap_lesc_oob_data_t *lesc_oob_data; + + err = nrf_ble_lesc_own_oob_data_generate(); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, err); + + lesc_oob_data = nrf_ble_lesc_own_oob_data_get(); + TEST_ASSERT_NULL(lesc_oob_data); +} + +static psa_status_t stub_psa_raw_key_agreement_success(psa_algorithm_t alg, + mbedtls_svc_key_id_t private_key, const uint8_t *peer_key, size_t peer_key_length, + uint8_t *output, size_t output_size, size_t *output_length, int cmock_num_calls) +{ + ARG_UNUSED(cmock_num_calls); + + TEST_ASSERT_EQUAL(PSA_ALG_ECDH, alg); + TEST_ASSERT_EQUAL(key_pair_id, private_key); + + TEST_ASSERT_NOT_NULL(peer_key); + TEST_ASSERT_EQUAL(sizeof(peer_test_pub_key_psa), peer_key_length); + TEST_ASSERT_EQUAL_MEMORY(peer_test_pub_key_psa, peer_key, sizeof(peer_test_pub_key_psa)); + + TEST_ASSERT_EQUAL(sizeof(test_secret_psa), output_size); + TEST_ASSERT_NOT_NULL(output); + memcpy(output, test_secret_psa, sizeof(test_secret_psa)); + TEST_ASSERT_NOT_NULL(output_length); + *output_length = sizeof(test_secret_psa); + + return PSA_SUCCESS; +} + +static psa_status_t stub_psa_raw_key_agreement_failure(psa_algorithm_t alg, + mbedtls_svc_key_id_t private_key, const uint8_t *peer_key, size_t peer_key_length, + uint8_t *output, size_t output_size, size_t *output_length, int cmock_num_calls) +{ + ARG_UNUSED(cmock_num_calls); + + TEST_ASSERT_EQUAL(PSA_ALG_ECDH, alg); + TEST_ASSERT_EQUAL(key_pair_id, private_key); + + TEST_ASSERT_NOT_NULL(peer_key); + TEST_ASSERT_EQUAL(sizeof(peer_test_pub_key_psa), peer_key_length); + + TEST_ASSERT_EQUAL(sizeof(test_secret_psa), output_size); + TEST_ASSERT_NOT_NULL(output); + TEST_ASSERT_NOT_NULL(output_length); + + return PSA_ERROR_BAD_STATE; +} + +static uint32_t callback_sd_ble_gap_lesc_dhkey_reply_success(uint16_t conn_handle, + uint8_t sec_status, ble_gap_lesc_dhkey_t const *p_dhkey, int cmock_num_calls) +{ + ARG_UNUSED(cmock_num_calls); + ARG_UNUSED(conn_handle); + + if (sec_status == BLE_GAP_SEC_STATUS_SUCCESS) { + TEST_ASSERT_EQUAL_MEMORY(test_secret_sd, p_dhkey->key, sizeof(test_secret_sd)); + } else { + TEST_ASSERT_NULL(p_dhkey); + } + + return NRF_SUCCESS; +} + +void test_nrf_ble_lesc_compute_and_give_dhkey_success(void) +{ + uint32_t err; + const uint16_t conn_handle = 0x32; + const int peer_pub_key_idx = 1; + ble_gap_lesc_p256_pk_t peer_lesc_key; + ble_evt_t evt = { + .header = { + .evt_id = BLE_GAP_EVT_LESC_DHKEY_REQUEST, + }, + .evt.gap_evt = { + .conn_handle = conn_handle, + .params.lesc_dhkey_request.p_pk_peer = &peer_lesc_key, + }, + }; + memcpy(peer_lesc_key.pk, peer_test_pub_key_sd, sizeof(peer_test_pub_key_sd)); + + generate_key_pair(); + + __cmock_nrf_sdh_ble_idx_get_ExpectAndReturn(conn_handle, peer_pub_key_idx); + + /* Invoke on_dhkey_request(). */ + nrf_ble_lesc_on_ble_evt(&evt); + + __cmock_psa_raw_key_agreement_Stub(stub_psa_raw_key_agreement_success); + __cmock_sd_ble_gap_lesc_dhkey_reply_ExpectAndReturn( + conn_handle, BLE_GAP_SEC_STATUS_SUCCESS, PTR_IGNORE, VAL_IGNORE); + __cmock_sd_ble_gap_lesc_dhkey_reply_IgnoreArg_p_dhkey(); + __cmock_sd_ble_gap_lesc_dhkey_reply_AddCallback( + callback_sd_ble_gap_lesc_dhkey_reply_success); + + /* Invoke compute_and_give_dhkey(). */ + err = nrf_ble_lesc_request_handler(); + TEST_ASSERT_EQUAL(NRF_SUCCESS, err); +} + +void test_nrf_ble_lesc_compute_and_give_dhkey_without_keypair_generation(void) +{ + uint32_t err; + const uint16_t conn_handle = 0x32; + const int peer_pub_key_idx = 1; + ble_gap_lesc_p256_pk_t peer_lesc_key; + ble_evt_t evt = { + .header = { + .evt_id = BLE_GAP_EVT_LESC_DHKEY_REQUEST, + }, + .evt.gap_evt = { + .conn_handle = conn_handle, + .params.lesc_dhkey_request.p_pk_peer = &peer_lesc_key, + }, + }; + memcpy(peer_lesc_key.pk, peer_test_pub_key_sd, sizeof(peer_test_pub_key_sd)); + + __cmock_nrf_sdh_ble_idx_get_ExpectAndReturn(conn_handle, peer_pub_key_idx); + + /* Invoke on_dhkey_request(). */ + nrf_ble_lesc_on_ble_evt(&evt); + + /* Invoke compute_and_give_dhkey(). */ + err = nrf_ble_lesc_request_handler(); + TEST_ASSERT_EQUAL(NRF_ERROR_INTERNAL, err); +} + +static psa_status_t stub_psa_generate_random_success( + uint8_t *output, size_t output_size, int cmock_num_calls) +{ + ARG_UNUSED(cmock_num_calls); + + TEST_ASSERT_EQUAL(sizeof(test_secret_sd), output_size); + TEST_ASSERT_NOT_NULL(output); + memcpy(output, test_secret_sd, sizeof(test_secret_sd)); + + return PSA_SUCCESS; +} + +void test_nrf_ble_lesc_compute_and_give_dhkey_with_invalid_peer_key(void) +{ + uint32_t err; + const uint16_t conn_handle = 0x32; + const int peer_pub_key_idx = 1; + ble_gap_lesc_p256_pk_t peer_lesc_key; + ble_evt_t evt = { + .header = { + .evt_id = BLE_GAP_EVT_LESC_DHKEY_REQUEST, + }, + .evt.gap_evt = { + .conn_handle = conn_handle, + .params.lesc_dhkey_request.p_pk_peer = &peer_lesc_key, + }, + }; + memcpy(peer_lesc_key.pk, peer_test_pub_key_sd, sizeof(peer_test_pub_key_sd)); + + generate_key_pair(); + + __cmock_nrf_sdh_ble_idx_get_ExpectAndReturn(conn_handle, peer_pub_key_idx); + + /* Invoke on_dhkey_request(). */ + nrf_ble_lesc_on_ble_evt(&evt); + + __cmock_psa_raw_key_agreement_Stub(stub_psa_raw_key_agreement_failure); + __cmock_psa_generate_random_Stub(stub_psa_generate_random_success); + + __cmock_sd_ble_gap_lesc_dhkey_reply_ExpectAndReturn( + conn_handle, BLE_GAP_SEC_STATUS_DHKEY_FAILURE, PTR_IGNORE, VAL_IGNORE); + __cmock_sd_ble_gap_lesc_dhkey_reply_IgnoreArg_p_dhkey(); + __cmock_sd_ble_gap_lesc_dhkey_reply_AddCallback( + callback_sd_ble_gap_lesc_dhkey_reply_success); + + /* Invoke compute_and_give_dhkey(). */ + err = nrf_ble_lesc_request_handler(); + TEST_ASSERT_EQUAL(NRF_SUCCESS, err); +} + +void test_nrf_ble_lesc_compute_and_give_dhkey_with_oob_data_own(void) +{ + uint32_t err; + const uint16_t conn_handle = 0x32; + const int peer_pub_key_idx = 1; + ble_gap_lesc_oob_data_t *lesc_oob_data; + ble_gap_lesc_p256_pk_t peer_lesc_key; + ble_evt_t evt = { + .header = { + .evt_id = BLE_GAP_EVT_LESC_DHKEY_REQUEST, + }, + .evt.gap_evt = { + .conn_handle = conn_handle, + .params.lesc_dhkey_request.p_pk_peer = &peer_lesc_key, + .params.lesc_dhkey_request.oobd_req = 1, + }, + }; + memcpy(peer_lesc_key.pk, peer_test_pub_key_sd, sizeof(peer_test_pub_key_sd)); + + generate_key_pair(); + + __cmock_sd_ble_gap_lesc_oob_data_get_Stub(stub_sd_ble_gap_lesc_oob_data_get_success); + + /* Prepare own OOB data. */ + err = nrf_ble_lesc_own_oob_data_generate(); + TEST_ASSERT_EQUAL(NRF_SUCCESS, err); + + lesc_oob_data = nrf_ble_lesc_own_oob_data_get(); + TEST_ASSERT_NOT_NULL(lesc_oob_data); + TEST_ASSERT_EQUAL_PTR(oobd, lesc_oob_data); + + __cmock_nrf_sdh_ble_idx_get_ExpectAndReturn(conn_handle, peer_pub_key_idx); + __cmock_sd_ble_gap_lesc_oob_data_set_ExpectAndReturn(conn_handle, lesc_oob_data, NULL, + NRF_SUCCESS); + + /* Invoke on_dhkey_request(). */ + nrf_ble_lesc_on_ble_evt(&evt); + + __cmock_psa_raw_key_agreement_Stub(stub_psa_raw_key_agreement_success); + __cmock_sd_ble_gap_lesc_dhkey_reply_ExpectAndReturn( + conn_handle, BLE_GAP_SEC_STATUS_SUCCESS, PTR_IGNORE, VAL_IGNORE); + __cmock_sd_ble_gap_lesc_dhkey_reply_IgnoreArg_p_dhkey(); + __cmock_sd_ble_gap_lesc_dhkey_reply_AddCallback( + callback_sd_ble_gap_lesc_dhkey_reply_success); + + /* Invoke compute_and_give_dhkey(). */ + err = nrf_ble_lesc_request_handler(); + TEST_ASSERT_EQUAL(NRF_SUCCESS, err); +} + +static ble_gap_lesc_oob_data_t nfc_oob_data; + +static ble_gap_lesc_oob_data_t *nfc_peer_oob_data_get(uint16_t conn_handle) +{ + ARG_UNUSED(conn_handle); + + return &nfc_oob_data; +} + +void test_nrf_ble_lesc_compute_and_give_dhkey_with_oob_data_peer(void) +{ + uint32_t err; + const uint16_t conn_handle = 0x32; + const int peer_pub_key_idx = 1; + ble_gap_lesc_p256_pk_t peer_lesc_key; + ble_evt_t evt = { + .header = { + .evt_id = BLE_GAP_EVT_LESC_DHKEY_REQUEST, + }, + .evt.gap_evt = { + .conn_handle = conn_handle, + .params.lesc_dhkey_request.p_pk_peer = &peer_lesc_key, + .params.lesc_dhkey_request.oobd_req = 1, + }, + }; + memcpy(peer_lesc_key.pk, peer_test_pub_key_sd, sizeof(peer_test_pub_key_sd)); + + generate_key_pair(); + + nrf_ble_lesc_peer_oob_data_handler_set(nfc_peer_oob_data_get); + + __cmock_nrf_sdh_ble_idx_get_ExpectAndReturn(conn_handle, peer_pub_key_idx); + __cmock_sd_ble_gap_lesc_oob_data_set_ExpectAndReturn(conn_handle, NULL, &nfc_oob_data, + NRF_SUCCESS); + + /* Invoke on_dhkey_request(). */ + nrf_ble_lesc_on_ble_evt(&evt); + + __cmock_psa_raw_key_agreement_Stub(stub_psa_raw_key_agreement_success); + __cmock_sd_ble_gap_lesc_dhkey_reply_ExpectAndReturn( + conn_handle, BLE_GAP_SEC_STATUS_SUCCESS, PTR_IGNORE, VAL_IGNORE); + __cmock_sd_ble_gap_lesc_dhkey_reply_IgnoreArg_p_dhkey(); + __cmock_sd_ble_gap_lesc_dhkey_reply_AddCallback( + callback_sd_ble_gap_lesc_dhkey_reply_success); + + /* Invoke compute_and_give_dhkey(). */ + err = nrf_ble_lesc_request_handler(); + TEST_ASSERT_EQUAL(NRF_SUCCESS, err); +} + +void test_nrf_ble_lesc_compute_and_give_dhkey_with_oob_data_fail(void) +{ + uint32_t err; + const uint16_t conn_handle = 0x32; + const int peer_pub_key_idx = 1; + ble_gap_lesc_p256_pk_t peer_lesc_key; + ble_evt_t evt = { + .header = { + .evt_id = BLE_GAP_EVT_LESC_DHKEY_REQUEST, + }, + .evt.gap_evt = { + .conn_handle = conn_handle, + .params.lesc_dhkey_request.p_pk_peer = &peer_lesc_key, + .params.lesc_dhkey_request.oobd_req = 1, + }, + }; + memcpy(peer_lesc_key.pk, peer_test_pub_key_sd, sizeof(peer_test_pub_key_sd)); + + generate_key_pair(); + + nrf_ble_lesc_peer_oob_data_handler_set(nfc_peer_oob_data_get); + + __cmock_nrf_sdh_ble_idx_get_ExpectAndReturn(conn_handle, peer_pub_key_idx); + __cmock_sd_ble_gap_lesc_oob_data_set_ExpectAndReturn(conn_handle, NULL, &nfc_oob_data, + NRF_ERROR_INVALID_STATE); + + /* Invoke on_dhkey_request(). */ + nrf_ble_lesc_on_ble_evt(&evt); + + err = nrf_ble_lesc_request_handler(); + TEST_ASSERT_EQUAL(NRF_ERROR_INTERNAL, err); +} + +void test_nrf_ble_lesc_compute_and_give_dhkey_with_disconnect(void) +{ + uint32_t err; + const uint16_t conn_handle = 0x32; + const int peer_pub_key_idx = 1; + ble_gap_lesc_p256_pk_t peer_lesc_key; + ble_evt_t evt = { + .header = { + .evt_id = BLE_GAP_EVT_LESC_DHKEY_REQUEST, + }, + .evt.gap_evt = { + .conn_handle = conn_handle, + .params.lesc_dhkey_request.p_pk_peer = &peer_lesc_key, + }, + }; + memcpy(peer_lesc_key.pk, peer_test_pub_key_sd, sizeof(peer_test_pub_key_sd)); + + generate_key_pair(); + + __cmock_nrf_sdh_ble_idx_get_ExpectAndReturn(conn_handle, peer_pub_key_idx); + + /* Invoke on_dhkey_request(). */ + nrf_ble_lesc_on_ble_evt(&evt); + + /* Disconnect before trying to invoke compute_and_give_dhkey(). */ + const ble_evt_t evt_disconnect = { + .header = { + .evt_id = BLE_GAP_EVT_DISCONNECTED, + }, + .evt.gap_evt.conn_handle = conn_handle, + }; + + __cmock_nrf_sdh_ble_idx_get_ExpectAndReturn(conn_handle, peer_pub_key_idx); + + nrf_ble_lesc_on_ble_evt(&evt_disconnect); + + /* Try to invoke compute_and_give_dhkey(), but nothing to do. */ + err = nrf_ble_lesc_request_handler(); + TEST_ASSERT_EQUAL(NRF_SUCCESS, err); +} + +void tearDown(void) +{ + key_attrs = NULL; + oobd = NULL; + + /* Clear the internal exported public key. */ + ble_gap_lesc_p256_pk_t *pub_key = nrf_ble_lesc_public_key_get(); + + if (pub_key != NULL) { + memcpy(pub_key->pk, test_pub_key_sd_cleared, sizeof(test_pub_key_sd_cleared)); + } + + /* Reset generated key state. */ + reinitialize(); +} + +extern int unity_main(void); + +int main(void) +{ + return unity_main(); +} diff --git a/tests/unit/lib/bluetooth/nrf_ble_lesc/testcase.yaml b/tests/unit/lib/bluetooth/nrf_ble_lesc/testcase.yaml new file mode 100644 index 0000000000..a0357bc406 --- /dev/null +++ b/tests/unit/lib/bluetooth/nrf_ble_lesc/testcase.yaml @@ -0,0 +1,4 @@ +tests: + lib.peer_manager.nrf_ble_lesc: + platform_allow: native_sim + tags: unittest From dd5960c307c8648a4ecdd61f5709b03811297993 Mon Sep 17 00:00:00 2001 From: Andreas Moltumyr Date: Wed, 15 Oct 2025 16:27:51 +0200 Subject: [PATCH 11/12] tests: unit: peer_manager: add peer manager unit tests Add first set of unit tests for peer manager library. Signed-off-by: Andreas Moltumyr --- .../peer_manager/modules/peer_data_storage.c | 4 +- lib/bluetooth/peer_manager/modules/peer_id.c | 8 +- .../peer_manager/modules/security_manager.c | 7 +- lib/bluetooth/peer_manager/peer_manager.c | 4 + subsys/softdevice_handler/nrf_sdh_ble.c | 6 +- .../lib/bluetooth/peer_manager/CMakeLists.txt | 35 + tests/unit/lib/bluetooth/peer_manager/Kconfig | 26 + .../peer_manager/boards/native_sim.overlay | 4 + .../peer_manager/include/psa/crypto.h | 57 + .../unit/lib/bluetooth/peer_manager/prj.conf | 10 + .../bluetooth/peer_manager/src/unity_test.c | 1615 +++++++++++++++++ .../lib/bluetooth/peer_manager/testcase.yaml | 4 + 12 files changed, 1773 insertions(+), 7 deletions(-) create mode 100644 tests/unit/lib/bluetooth/peer_manager/CMakeLists.txt create mode 100644 tests/unit/lib/bluetooth/peer_manager/Kconfig create mode 100644 tests/unit/lib/bluetooth/peer_manager/boards/native_sim.overlay create mode 100644 tests/unit/lib/bluetooth/peer_manager/include/psa/crypto.h create mode 100644 tests/unit/lib/bluetooth/peer_manager/prj.conf create mode 100644 tests/unit/lib/bluetooth/peer_manager/src/unity_test.c create mode 100644 tests/unit/lib/bluetooth/peer_manager/testcase.yaml diff --git a/lib/bluetooth/peer_manager/modules/peer_data_storage.c b/lib/bluetooth/peer_manager/modules/peer_data_storage.c index a72bf4ca83..0552d2b066 100644 --- a/lib/bluetooth/peer_manager/modules/peer_data_storage.c +++ b/lib/bluetooth/peer_manager/modules/peer_data_storage.c @@ -179,7 +179,7 @@ static void peer_ids_load(void) peer_data.all_data = peer_data_buffer; - /* Search through existing bonds to look for a duplicate. */ + /* Allocate peer IDs for already stored bonds. */ pds_peer_data_iterate_prepare(&peer_id_iter); while (pds_peer_data_iterate(PM_PEER_DATA_ID_BONDING, &peer_id, &peer_data, @@ -282,7 +282,9 @@ static void bm_zms_evt_handler(const struct bm_zms_evt *evt) static void wait_for_init(void) { while (!fs.init_flags.initialized) { +#if !defined(CONFIG_UNITY) k_cpu_idle(); +#endif } } diff --git a/lib/bluetooth/peer_manager/modules/peer_id.c b/lib/bluetooth/peer_manager/modules/peer_id.c index 2a285a67cd..344785afe9 100644 --- a/lib/bluetooth/peer_manager/modules/peer_id.c +++ b/lib/bluetooth/peer_manager/modules/peer_id.c @@ -9,7 +9,9 @@ #include #include #include -#include +#if !defined(CONFIG_UNITY) +#include +#endif #include #include @@ -40,7 +42,11 @@ static uint32_t find_and_set_flag(atomic_t *pi_flags, uint32_t flag_count) while (inverted) { /* Find lowest zero bit */ +#if defined(CONFIG_UNITY) + uint32_t first_zero = __builtin_ctz(inverted); +#else uint32_t first_zero = NRF_CTZ(inverted); +#endif uint32_t first_zero_global = first_zero + (i * 32); if (first_zero_global >= flag_count) { diff --git a/lib/bluetooth/peer_manager/modules/security_manager.c b/lib/bluetooth/peer_manager/modules/security_manager.c index d2cf34ea7a..360503529b 100644 --- a/lib/bluetooth/peer_manager/modules/security_manager.c +++ b/lib/bluetooth/peer_manager/modules/security_manager.c @@ -406,7 +406,7 @@ static void sec_req_process(const struct pm_evt *event) (void)link_secure(event->conn_handle, null_params, force_repairing, true); /* The error code has been properly handled inside link_secure(). */ } -#endif +#endif /* CONFIG_SOFTDEVICE_CENTRAL */ /** * @brief Function for translating an SMD event to an SM event and passing it on to SM event @@ -434,7 +434,7 @@ void sm_smd_evt_handler(struct pm_evt *event) case PM_EVT_PERIPHERAL_SECURITY_REQ: #if defined(CONFIG_SOFTDEVICE_CENTRAL) sec_req_process(event); -#endif +#endif /* CONFIG_SOFTDEVICE_CENTRAL */ /* fallthrough */ default: /* Forward the event to all registered Security Manager event handlers. */ @@ -518,6 +518,9 @@ uint32_t sm_init(void) return NRF_ERROR_INTERNAL; } + default_sec_params = NULL; + default_sec_params_set = false; + module_initialized = true; return NRF_SUCCESS; diff --git a/lib/bluetooth/peer_manager/peer_manager.c b/lib/bluetooth/peer_manager/peer_manager.c index a306c21eb8..c105fe4a59 100644 --- a/lib/bluetooth/peer_manager/peer_manager.c +++ b/lib/bluetooth/peer_manager/peer_manager.c @@ -389,6 +389,10 @@ uint32_t pm_register(pm_evt_handler_t event_handler) return NRF_ERROR_INVALID_STATE; } + if (!event_handler) { + return NRF_ERROR_NULL; + } + if (n_registrants >= CONFIG_PM_MAX_REGISTRANTS) { return NRF_ERROR_NO_MEM; } diff --git a/subsys/softdevice_handler/nrf_sdh_ble.c b/subsys/softdevice_handler/nrf_sdh_ble.c index d74464d890..bfcc537bbb 100644 --- a/subsys/softdevice_handler/nrf_sdh_ble.c +++ b/subsys/softdevice_handler/nrf_sdh_ble.c @@ -62,7 +62,7 @@ const char *nrf_sdh_ble_evt_to_str(uint32_t evt) return "BLE_GAP_EVT_SEC_REQUEST"; case BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST: return "BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST"; -#endif +#endif /* CONFIG_SOFTDEVICE_CENTRAL */ case BLE_GAP_EVT_SCAN_REQ_REPORT: return "BLE_GAP_EVT_SCAN_REQ_REPORT"; case BLE_GAP_EVT_PHY_UPDATE_REQUEST: @@ -175,13 +175,13 @@ static int default_cfg_set(void) ble_cfg.gap_cfg.role_count_cfg.adv_set_count = BLE_GAP_ADV_SET_COUNT_DEFAULT; #endif -#if CONFIG_SOFTDEVICE_CENTRAL +#if defined(CONFIG_SOFTDEVICE_CENTRAL) ble_cfg.gap_cfg.role_count_cfg.central_role_count = CONFIG_NRF_SDH_BLE_CENTRAL_LINK_COUNT; ble_cfg.gap_cfg.role_count_cfg.central_sec_count = MIN(CONFIG_NRF_SDH_BLE_CENTRAL_LINK_COUNT, BLE_GAP_ROLE_COUNT_CENTRAL_SEC_DEFAULT); -#endif +#endif /* CONFIG_SOFTDEVICE_CENTRAL */ err = sd_ble_cfg_set(BLE_GAP_CFG_ROLE_COUNT, &ble_cfg, app_ram_start); if (err) { diff --git a/tests/unit/lib/bluetooth/peer_manager/CMakeLists.txt b/tests/unit/lib/bluetooth/peer_manager/CMakeLists.txt new file mode 100644 index 0000000000..669079923c --- /dev/null +++ b/tests/unit/lib/bluetooth/peer_manager/CMakeLists.txt @@ -0,0 +1,35 @@ +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(unit_test_peer_manager) + +include(${ZEPHYR_NRF_BM_MODULE_DIR}/cmake/unity/unity_softdevice_setup.cmake) +unity_softdevice_header_setup(VARIANT "s145") +unity_softdevice_event_setup() + +cmock_handle(${SOFTDEVICE_INCLUDE_DIR}/ble_gap.h) +cmock_handle(${SOFTDEVICE_INCLUDE_DIR}/ble_gattc.h) +cmock_handle(${SOFTDEVICE_INCLUDE_DIR}/ble_gatts.h) +cmock_handle(${SOFTDEVICE_INCLUDE_DIR}/nrf_soc.h) +cmock_handle(${ZEPHYR_NRF_BM_MODULE_DIR}/include/bm/bm_timer.h) +cmock_handle(${ZEPHYR_NRF_BM_MODULE_DIR}/include/bm/fs/bm_zms.h) +cmock_handle(${ZEPHYR_NRF_BM_MODULE_DIR}/include/bm/softdevice_handler/nrf_sdh_ble.h) +cmock_handle(include/psa/crypto.h) # Local definitions for mocking of psa/crypto.h with CMock + +# Increase the memory size for CMock. The default of 32768 is not sufficient for the tests. +zephyr_compile_definitions(CMOCK_MEM_SIZE=65536) + +zephyr_include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/include # Include test specific headers +) + +# Generate and add test file +test_runner_generate(src/unity_test.c) +target_sources(app PRIVATE src/unity_test.c) diff --git a/tests/unit/lib/bluetooth/peer_manager/Kconfig b/tests/unit/lib/bluetooth/peer_manager/Kconfig new file mode 100644 index 0000000000..6459a9106d --- /dev/null +++ b/tests/unit/lib/bluetooth/peer_manager/Kconfig @@ -0,0 +1,26 @@ +# Clear dependencies for PEER_MANAGER, PM_SERVICE_CHANGED, and PM_LESC, +# then enable them to allow testing the features. +config PEER_MANAGER + default y + +config PM_SERVICE_CHANGED + default y + +config PM_LESC + default y + +# Redefine Kconfigs used by the tested module that is defined in +# other modules we do not want to enable. +config NRF_SDH_BLE_TOTAL_LINK_COUNT + default 2 + +config SOFTDEVICE_PERIPHERAL + default y + +config SOFTDEVICE_CENTRAL + default y + +config BM_STORAGE_BACKEND_SD + default y + +source "Kconfig.zephyr" diff --git a/tests/unit/lib/bluetooth/peer_manager/boards/native_sim.overlay b/tests/unit/lib/bluetooth/peer_manager/boards/native_sim.overlay new file mode 100644 index 0000000000..cbd2f9e38e --- /dev/null +++ b/tests/unit/lib/bluetooth/peer_manager/boards/native_sim.overlay @@ -0,0 +1,4 @@ +/* Mock values for PEER_MANAGER_NODE, PEER_MANAGER_PARTITION_OFFSET and + * PEER_MANAGER_PARTITION_SIZE in peer manager file peer_data_storage.c. + */ +peer_manager_partition: &storage_partition {}; diff --git a/tests/unit/lib/bluetooth/peer_manager/include/psa/crypto.h b/tests/unit/lib/bluetooth/peer_manager/include/psa/crypto.h new file mode 100644 index 0000000000..96f2e0f21d --- /dev/null +++ b/tests/unit/lib/bluetooth/peer_manager/include/psa/crypto.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef PSA_CRYPTO_FOR_MOCKING_H__ +#define PSA_CRYPTO_FOR_MOCKING_H__ + +#include +#include + +/* Header for mocking the required psa/crypto.h functionality with CMock. + * For use in nrf_ble_lesc unit tests. + */ + +typedef uint32_t psa_key_id_t; +typedef psa_key_id_t mbedtls_svc_key_id_t; +typedef int32_t psa_status_t; +typedef uint16_t psa_key_type_t; + +typedef int psa_key_attributes_t; +typedef uint32_t psa_key_usage_t; +typedef uint32_t psa_key_lifetime_t; +typedef uint32_t psa_algorithm_t; +typedef uint8_t psa_ecc_family_t; + +#define PSA_KEY_ATTRIBUTES_INIT 0x00C0FFEE + +#define PSA_EXPORT_PUBLIC_KEY_OUTPUT_SIZE(key_type, key_bits) (65) +#define PSA_KEY_TYPE_ECC_KEY_PAIR(curve) 0 +#define PSA_ECC_FAMILY_SECP_R1 ((psa_ecc_family_t)0x12) + +#define PSA_KEY_USAGE_DERIVE ((psa_key_usage_t)0x00004000) +#define PSA_KEY_LIFETIME_VOLATILE ((psa_key_lifetime_t)0x00000000) +#define PSA_ALG_ECDH ((psa_algorithm_t)0x09020000) + +#define PSA_SUCCESS ((psa_status_t)0) +#define PSA_ERROR_INVALID_HANDLE ((psa_status_t)-136) +#define PSA_ERROR_BAD_STATE ((psa_status_t)-137) + +psa_status_t psa_crypto_init(void); +psa_status_t psa_destroy_key(mbedtls_svc_key_id_t key); +void psa_set_key_usage_flags(psa_key_attributes_t *attributes, psa_key_usage_t usage_flags); +void psa_set_key_lifetime(psa_key_attributes_t *attributes, psa_key_lifetime_t lifetime); +void psa_set_key_algorithm(psa_key_attributes_t *attributes, psa_algorithm_t alg); +void psa_set_key_type(psa_key_attributes_t *attributes, psa_key_type_t type); +void psa_set_key_bits(psa_key_attributes_t *attributes, size_t bits); +psa_status_t psa_generate_key(const psa_key_attributes_t *attributes, mbedtls_svc_key_id_t *key); +psa_status_t psa_export_public_key(mbedtls_svc_key_id_t key, uint8_t *data, size_t data_size, + size_t *data_length); +psa_status_t psa_raw_key_agreement(psa_algorithm_t alg, mbedtls_svc_key_id_t private_key, + const uint8_t *peer_key, size_t peer_key_length, + uint8_t *output, size_t output_size, size_t *output_length); +psa_status_t psa_generate_random(uint8_t *output, size_t output_size); + +#endif /* PSA_CRYPTO_FOR_MOCKING_H__ */ diff --git a/tests/unit/lib/bluetooth/peer_manager/prj.conf b/tests/unit/lib/bluetooth/peer_manager/prj.conf new file mode 100644 index 0000000000..3bfe1271c8 --- /dev/null +++ b/tests/unit/lib/bluetooth/peer_manager/prj.conf @@ -0,0 +1,10 @@ +CONFIG_UNITY=y + +# Enable generation of new LESC key pair after every pairing attempt +# CONFIG_PM_LESC_GENERATE_NEW_KEYS=y + +# Repeated Attempts Protection feature +CONFIG_PM_RA_PROTECTION=y + +# Include sec delay code +CONFIG_PM_HANDLER_SEC_DELAY_MS=100 diff --git a/tests/unit/lib/bluetooth/peer_manager/src/unity_test.c b/tests/unit/lib/bluetooth/peer_manager/src/unity_test.c new file mode 100644 index 0000000000..4e0b0c4e69 --- /dev/null +++ b/tests/unit/lib/bluetooth/peer_manager/src/unity_test.c @@ -0,0 +1,1615 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + + +#include "cmock_ble_gap.h" +#include "cmock_ble_gatts.h" +#include "cmock_nrf_sdh_ble.h" +#include "cmock_bm_zms.h" +#include "cmock_crypto.h" +#include "cmock_bm_timer.h" + +#include + +#include + +#define PTR_IGNORE NULL +#define VAL_IGNORE 0 +#define RET_IGNORE 0 + +#ifndef ARG_UNUSED +#define ARG_UNUSED(arg) (void)(arg) +#endif + +#define PM_PEER_DATA_MAX_SIZE 128 + +#define CONN_HANDLE_1 (uint16_t)(0x0003) + +#define ADDRESS_PUBLIC_1 \ + (ble_gap_addr_t) { \ + .addr_id_peer = 0, .addr_type = BLE_GAP_ADDR_TYPE_PUBLIC, \ + .addr = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66}, \ + } + +#define LTK_1 \ + (ble_gap_enc_key_t) { \ + .enc_info = { \ + .ltk = {0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, \ + 0x58, 0x59, 0x60, 0x61}, \ + .lesc = 1, .auth = 1, .ltk_len = 12, \ + }, \ + .master_id = { \ + .rand = {0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x48, 0x65, 0x69}, \ + .ediv = 0x96, \ + }, \ + } + +/* Helper union to allocate 'struct pm_peer_data_local_gatt_db' with memory for the + * flexible array member 'data' at the end of 'struct pm_peer_data_local_gatt_db'. + */ +union local_gatt_db_with_data { + struct { + uint8_t header[offsetof(struct pm_peer_data_local_gatt_db, data)]; + uint8_t payload[32]; + }; + struct pm_peer_data_local_gatt_db data; +}; + +#define STORED_GATT_DATA_1 \ + (union local_gatt_db_with_data) { \ + .data = {.flags = 0, .len = 13}, \ + .payload = {0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, \ + 0x99, 0x9A, 0x9B, 0x9C, 0x9D}, \ + } + +/* Helper union for converting between storage entry ID and peer ID + data ID. */ +union pm_entry_id { + struct { + uint32_t data_id : 16; + uint32_t peer_id : 16; + }; + uint32_t id; +}; + +uint8_t bonding_data_A[PM_PEER_DATA_MAX_SIZE] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + +static int stub_nrf_sdh_ble_idx_get(uint16_t conn_handle, int cmock_num_calls) +{ + int idx = -1; + + ARG_UNUSED(cmock_num_calls); + + switch (conn_handle) { + case CONN_HANDLE_1: + idx = 0; + break; + default: + TEST_FAIL(); + } + + return idx; +} + +static uint16_t stub_nrf_sdh_ble_conn_handle_get(int idx, int cmock_num_calls) +{ + ARG_UNUSED(cmock_num_calls); + + switch (idx) { + case 0: + return CONN_HANDLE_1; + default: + return BLE_CONN_HANDLE_INVALID; + } +} + +/* Hold reference to bm_zms instance to check that all bm_zms API calls use the same instance. */ +static struct bm_zms_fs *zms_fs; +static bm_zms_evt_handler_t zms_handler; + +/* Mock for storage API used by peer manager. */ +const struct bm_storage_api bm_storage_sd_api = {0}; + +static int stub_bm_zms_mount_success(struct bm_zms_fs *fs, const struct bm_zms_fs_config *config, + int cmock_num_calls) +{ + ARG_UNUSED(cmock_num_calls); + TEST_ASSERT_NOT_NULL(fs); + + TEST_ASSERT_NOT_NULL(config); + TEST_ASSERT_EQUAL(DT_REG_ADDR(DT_NODELABEL(peer_manager_partition)), config->offset); + TEST_ASSERT_EQUAL(CONFIG_PM_BM_ZMS_SECTOR_SIZE, config->sector_size); + TEST_ASSERT_EQUAL( + DT_REG_SIZE(DT_NODELABEL(peer_manager_partition)) / CONFIG_PM_BM_ZMS_SECTOR_SIZE, + config->sector_count); + TEST_ASSERT_NOT_NULL(config->evt_handler); + TEST_ASSERT_EQUAL_PTR(&bm_storage_sd_api, config->storage_api); + + /* Store address of bm_zms file system structure so it can be checked against the + * file system structure of later calls to bm_zms. + */ + zms_fs = fs; + + /* Store address of the handler function so it can be invoked in later tests. */ + zms_handler = config->evt_handler; + + /* Signal that the fs is initialized. This is usually done asynchronously by bm_zms, + * but this will not happen when mocking the bm_zms API, so signal it here. + */ + fs->init_flags.initialized = true; + + return 0; +} + +static ssize_t stub_bm_zms_read_pm_init(struct bm_zms_fs *fs, uint32_t id, void *data, size_t len, + int cmock_num_calls) +{ + /* On pm_init, all n = PM_PEER_ID_N_AVAILABLE_IDS 'peer bonding data' entries + * are read through, looking for existing bonding data. + */ + + int ret; + union pm_entry_id entry = {.id = id}; + + TEST_ASSERT_NOT_NULL(fs); + TEST_ASSERT_EQUAL_PTR(zms_fs, fs); + + if (cmock_num_calls < PM_PEER_ID_N_AVAILABLE_IDS) { + entry.peer_id = cmock_num_calls; + + TEST_ASSERT_EQUAL(cmock_num_calls, entry.peer_id); + TEST_ASSERT_EQUAL(PM_PEER_DATA_ID_BONDING, entry.data_id); + TEST_ASSERT_NOT_NULL(data); + TEST_ASSERT_EQUAL(PM_PEER_DATA_MAX_SIZE, len); + + /* Return -ENOENT to signal that no data exists for this entry_id. */ + ret = -ENOENT; + } else { + TEST_FAIL(); + } + + return ret; +} + +#define GEN_MATERIAL(i, start, step) ((start) + (step) * (i)) + +static uint8_t own_test_pub_key_psa[] = { + 0x04, + LISTIFY(32, GEN_MATERIAL, (,), 0xB0, 1), + LISTIFY(32, GEN_MATERIAL, (,), 0xD0, 1), +}; +static uint8_t own_test_pub_key_sd[] = { + LISTIFY(32, GEN_MATERIAL, (,), 0xCF, -1), + LISTIFY(32, GEN_MATERIAL, (,), 0xEF, -1), +}; + +static uint8_t peer_test_pub_key_psa[] = { + 0x04, + LISTIFY(32, GEN_MATERIAL, (,), 0x60, 1), + LISTIFY(32, GEN_MATERIAL, (,), 0x80, 1), +}; +static uint8_t peer_test_pub_key_sd[] = { + LISTIFY(32, GEN_MATERIAL, (,), 0x7F, -1), + LISTIFY(32, GEN_MATERIAL, (,), 0x9F, -1), +}; + +static uint8_t test_secret_psa[] = { + LISTIFY(32, GEN_MATERIAL, (,), 0x20, 1), +}; +static uint8_t test_secret_sd[] = { + LISTIFY(32, GEN_MATERIAL, (,), 0x3F, -1), +}; + +BUILD_ASSERT(sizeof(own_test_pub_key_sd) == BLE_GAP_LESC_P256_PK_LEN); +BUILD_ASSERT(sizeof(peer_test_pub_key_sd) == BLE_GAP_LESC_P256_PK_LEN); +BUILD_ASSERT(sizeof(test_secret_sd) == BLE_GAP_LESC_DHKEY_LEN); + +static const mbedtls_svc_key_id_t key_pair_id = 0x2A; + +#define PSA_GENERATE_ECDH_KEYS_AND_EXPORT_PUBLIC_KEY_MOCKS(_key_pair_id) \ + psa_key_attributes_t key_attributes_mock = PSA_KEY_ATTRIBUTES_INIT; \ + \ + __cmock_psa_crypto_init_ExpectAndReturn(PSA_SUCCESS); \ + __cmock_psa_destroy_key_ExpectAnyArgsAndReturn(PSA_ERROR_INVALID_HANDLE); \ + \ + __cmock_psa_set_key_usage_flags_ExpectWithArray( \ + &(psa_key_attributes_t){key_attributes_mock}, 1, PSA_KEY_USAGE_DERIVE); \ + __cmock_psa_set_key_usage_flags_ReturnMemThruPtr_attributes( \ + &(psa_key_attributes_t){(++key_attributes_mock)}, sizeof(key_attributes_mock)); \ + \ + __cmock_psa_set_key_lifetime_ExpectWithArray( \ + &(psa_key_attributes_t){key_attributes_mock}, 1, PSA_KEY_LIFETIME_VOLATILE); \ + __cmock_psa_set_key_lifetime_ReturnMemThruPtr_attributes( \ + &(psa_key_attributes_t){(++key_attributes_mock)}, sizeof(key_attributes_mock)); \ + \ + __cmock_psa_set_key_algorithm_ExpectWithArray( \ + &(psa_key_attributes_t){key_attributes_mock}, 1, PSA_ALG_ECDH); \ + __cmock_psa_set_key_algorithm_ReturnMemThruPtr_attributes( \ + &(psa_key_attributes_t){(++key_attributes_mock)}, sizeof(key_attributes_mock)); \ + \ + __cmock_psa_set_key_type_ExpectWithArray( \ + &(psa_key_attributes_t){key_attributes_mock}, 1, \ + PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); \ + __cmock_psa_set_key_type_ReturnMemThruPtr_attributes( \ + &(psa_key_attributes_t){(++key_attributes_mock)}, sizeof(key_attributes_mock)); \ + \ + __cmock_psa_set_key_bits_ExpectWithArray( \ + &(psa_key_attributes_t){key_attributes_mock}, 1, 256); \ + __cmock_psa_set_key_bits_ReturnMemThruPtr_attributes( \ + &(psa_key_attributes_t){(++key_attributes_mock)}, sizeof(key_attributes_mock)); \ + \ + __cmock_psa_generate_key_ExpectWithArrayAndReturn( \ + &(psa_key_attributes_t){key_attributes_mock}, 1, PTR_IGNORE, 0, PSA_SUCCESS); \ + __cmock_psa_generate_key_IgnoreArg_key(); \ + __cmock_psa_generate_key_ReturnMemThruPtr_key( \ + (mbedtls_svc_key_id_t *)(&(_key_pair_id)), sizeof(_key_pair_id)); \ + \ + __cmock_psa_export_public_key_ExpectAndReturn( \ + (_key_pair_id), PTR_IGNORE, sizeof(own_test_pub_key_psa), PTR_IGNORE, PSA_SUCCESS);\ + __cmock_psa_export_public_key_IgnoreArg_data(); \ + __cmock_psa_export_public_key_IgnoreArg_data_length(); \ + __cmock_psa_export_public_key_ReturnMemThruPtr_data( \ + own_test_pub_key_psa, sizeof(own_test_pub_key_psa)); \ + __cmock_psa_export_public_key_ReturnMemThruPtr_data_length( \ + &(size_t){sizeof(own_test_pub_key_psa)}, sizeof(size_t)) + +static bm_timer_timeout_handler_t blacklisted_peers_update_handler; +static struct bm_timer *ast_timer; + +int stub_bm_timer_init_ast(struct bm_timer *timer, enum bm_timer_mode mode, + bm_timer_timeout_handler_t timeout_handler, int cmock_num_calls) +{ + ARG_UNUSED(cmock_num_calls); + + TEST_ASSERT_NOT_NULL(timer); + TEST_ASSERT_EQUAL(BM_TIMER_MODE_SINGLE_SHOT, mode); + TEST_ASSERT_NOT_NULL(timeout_handler); + + /* Store address of bm_timer structure so it can be checked against the + * bm_timer structure of later auth status tracker related calls to bm_timer. + */ + ast_timer = timer; + + /* Store address of the handler function so it can be invoked in later tests. */ + blacklisted_peers_update_handler = timeout_handler; + + return 0; +} + +void peer_manager_initialize_success(void) +{ + uint32_t nrf_err; + + __cmock_bm_zms_mount_Stub(stub_bm_zms_mount_success); + __cmock_bm_zms_read_Stub(stub_bm_zms_read_pm_init); + + PSA_GENERATE_ECDH_KEYS_AND_EXPORT_PUBLIC_KEY_MOCKS(key_pair_id); + + __cmock_bm_timer_init_Stub(stub_bm_timer_init_ast); + + nrf_err = pm_init(); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + /* Stubs have priority. Clear stubs so that Expect functions can be used from now on. */ + __cmock_bm_zms_mount_Stub(NULL); + __cmock_bm_zms_read_Stub(NULL); + + __cmock_bm_timer_init_Stub(NULL); +} + +void test_pm_init_success(void) +{ + uint32_t nrf_err; + + __cmock_bm_zms_mount_Stub(stub_bm_zms_mount_success); + __cmock_bm_zms_read_Stub(stub_bm_zms_read_pm_init); + + PSA_GENERATE_ECDH_KEYS_AND_EXPORT_PUBLIC_KEY_MOCKS(key_pair_id); + + __cmock_bm_timer_init_Stub(stub_bm_timer_init_ast); + + nrf_err = pm_init(); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +void test_pm_init_zms_error(void) +{ + uint32_t nrf_err; + + __cmock_bm_zms_mount_ExpectAnyArgsAndReturn(-EINVAL); + + nrf_err = pm_init(); + TEST_ASSERT_EQUAL(NRF_ERROR_INTERNAL, nrf_err); +} + +void test_pm_init_lesc_error(void) +{ + uint32_t nrf_err; + + __cmock_bm_zms_mount_Stub(stub_bm_zms_mount_success); + __cmock_bm_zms_read_IgnoreAndReturn(-ENOENT); + + __cmock_psa_crypto_init_ExpectAndReturn(PSA_ERROR_BAD_STATE); + + nrf_err = pm_init(); + TEST_ASSERT_EQUAL(NRF_ERROR_INTERNAL, nrf_err); + + __cmock_bm_zms_mount_ExpectAnyArgsAndReturn(0); + __cmock_bm_zms_read_IgnoreAndReturn(-ENOENT); + + __cmock_psa_crypto_init_ExpectAndReturn(PSA_SUCCESS); + __cmock_psa_destroy_key_ExpectAnyArgsAndReturn(PSA_ERROR_INVALID_HANDLE); + __cmock_psa_set_key_usage_flags_ExpectAnyArgs(); + __cmock_psa_set_key_lifetime_ExpectAnyArgs(); + __cmock_psa_set_key_algorithm_ExpectAnyArgs(); + __cmock_psa_set_key_type_ExpectAnyArgs(); + __cmock_psa_set_key_bits_ExpectAnyArgs(); + __cmock_psa_generate_key_ExpectAnyArgsAndReturn(PSA_ERROR_BAD_STATE); + + nrf_err = pm_init(); + TEST_ASSERT_EQUAL(NRF_ERROR_INTERNAL, nrf_err); + + __cmock_bm_zms_mount_ExpectAnyArgsAndReturn(0); + __cmock_bm_zms_read_IgnoreAndReturn(-ENOENT); + + __cmock_psa_crypto_init_ExpectAndReturn(PSA_SUCCESS); + __cmock_psa_destroy_key_ExpectAnyArgsAndReturn(PSA_ERROR_INVALID_HANDLE); + __cmock_psa_set_key_usage_flags_ExpectAnyArgs(); + __cmock_psa_set_key_lifetime_ExpectAnyArgs(); + __cmock_psa_set_key_algorithm_ExpectAnyArgs(); + __cmock_psa_set_key_type_ExpectAnyArgs(); + __cmock_psa_set_key_bits_ExpectAnyArgs(); + __cmock_psa_generate_key_ExpectAnyArgsAndReturn(PSA_SUCCESS); + __cmock_psa_export_public_key_ExpectAnyArgsAndReturn(PSA_ERROR_BAD_STATE); + + nrf_err = pm_init(); + TEST_ASSERT_EQUAL(NRF_ERROR_INTERNAL, nrf_err); +} + +/* Helper context for modifying behavior of the on_pm_evt handler function. */ +static struct { + struct { + bool exclude_connection; + } conn_config_req; +} on_pm_evt_test_ctx; + +static void on_pm_evt(const struct pm_evt *pm_evt) +{ + uint32_t nrf_err; + + TEST_ASSERT_NOT_NULL(pm_evt); + + switch (pm_evt->evt_id) { + case PM_EVT_CONN_CONFIG_REQ: + if (on_pm_evt_test_ctx.conn_config_req.exclude_connection) { + nrf_err = pm_conn_exclude(pm_evt->conn_handle, + pm_evt->conn_config_req.context); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + } + break; + default: + break; + } +} + +void test_pm_register_success(void) +{ + uint32_t nrf_err; + + peer_manager_initialize_success(); + + /* Try registering up to the maximum number of event handlers. */ + for (uint32_t i = 0; i < CONFIG_PM_MAX_REGISTRANTS; i++) { + nrf_err = pm_register(on_pm_evt); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + } +} + +void test_pm_register_null(void) +{ + uint32_t nrf_err; + + peer_manager_initialize_success(); + + nrf_err = pm_register(NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); +} + +void test_pm_register_no_mem(void) +{ + uint32_t nrf_err; + + peer_manager_initialize_success(); + + for (uint32_t i = 0; i < CONFIG_PM_MAX_REGISTRANTS; i++) { + nrf_err = pm_register(on_pm_evt); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + } + + nrf_err = pm_register(on_pm_evt); + TEST_ASSERT_EQUAL(NRF_ERROR_NO_MEM, nrf_err); +} + +void test_pm_sec_params_set_success(void) +{ + uint32_t nrf_err; + ble_gap_sec_params_t sec_param; + + /* Bonding with MITM protection. */ + sec_param = (ble_gap_sec_params_t) { + .bond = 1, .mitm = 1, .lesc = 1, .keypress = 1, + .io_caps = BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY, .oob = 1, + + .min_key_size = 7, .max_key_size = 16, + .kdist_own = {.enc = 1, .id = 1}, + .kdist_peer = {.enc = 1, .id = 1}, + }; + + nrf_err = pm_sec_params_set(&sec_param); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + /* Bonding without MTIM protection. */ + sec_param = (ble_gap_sec_params_t) { + .bond = 1, .mitm = 0, .lesc = 0, .keypress = 1, + .io_caps = BLE_GAP_IO_CAPS_NONE, .oob = 0, + + .min_key_size = 11, .max_key_size = 11, + .kdist_own = {.enc = 1, .id = 1}, + .kdist_peer = {.enc = 1, .id = 1}, + }; + + nrf_err = pm_sec_params_set(&sec_param); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + /* Passing NULL is a valid input where all bonding procedures are rejected. */ + nrf_err = pm_sec_params_set(NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + /* Bonding Not supported. Reject all bonding procedures. */ + sec_param = (ble_gap_sec_params_t) { + .bond = 0, .mitm = 0, .lesc = 0, .keypress = 0, + .io_caps = BLE_GAP_IO_CAPS_NONE, .oob = 0, + + .min_key_size = 11, .max_key_size = 11, + .kdist_own = {.enc = 0, .id = 0}, + .kdist_peer = {.enc = 0, .id = 0}, + }; + + nrf_err = pm_sec_params_set(&sec_param); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +void test_pm_sec_params_set_invalid_param(void) +{ + uint32_t nrf_err; + ble_gap_sec_params_t sec_param; + + /* Error if OOB flag is set without MITM flag. */ + sec_param = (ble_gap_sec_params_t) { + .bond = 1, .mitm = 0, .lesc = 1, .keypress = 1, + .io_caps = BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY, .oob = 1, + + .min_key_size = 7, .max_key_size = 16, + .kdist_own = {.enc = 1, .id = 1}, + .kdist_peer = {.enc = 1, .id = 1}, + }; + + nrf_err = pm_sec_params_set(&sec_param); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_PARAM, nrf_err); + + /* Error if IO capabilities are outside of valid values. */ + sec_param = (ble_gap_sec_params_t) { + .bond = 1, .mitm = 1, .lesc = 1, .keypress = 1, + .io_caps = -1, .oob = 1, + + .min_key_size = 7, .max_key_size = 16, + .kdist_own = {.enc = 1, .id = 1}, + .kdist_peer = {.enc = 1, .id = 1}, + }; + + nrf_err = pm_sec_params_set(&sec_param); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_PARAM, nrf_err); + + /* Error if MITM flag is set and no Out-Of-Band (IO caps or OOB) capability is set. */ + sec_param = (ble_gap_sec_params_t) { + .bond = 1, .mitm = 1, .lesc = 1, .keypress = 1, + .io_caps = BLE_GAP_IO_CAPS_NONE, .oob = 0, + + .min_key_size = 7, .max_key_size = 16, + .kdist_own = {.enc = 1, .id = 1}, + .kdist_peer = {.enc = 1, .id = 1}, + }; + + nrf_err = pm_sec_params_set(&sec_param); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_PARAM, nrf_err); + + /* Error if min key size is larger than max key size. */ + sec_param = (ble_gap_sec_params_t) { + .bond = 1, .mitm = 1, .lesc = 1, .keypress = 1, + .io_caps = BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY, .oob = 1, + + .min_key_size = 12, .max_key_size = 11, + .kdist_own = {.enc = 1, .id = 1}, + .kdist_peer = {.enc = 1, .id = 1}, + }; + + nrf_err = pm_sec_params_set(&sec_param); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_PARAM, nrf_err); + + /* Error if minimum key size is lower than 7 bytes. */ + sec_param = (ble_gap_sec_params_t) { + .bond = 1, .mitm = 1, .lesc = 1, .keypress = 1, + .io_caps = BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY, .oob = 1, + + .min_key_size = 6, .max_key_size = 16, + .kdist_own = {.enc = 1, .id = 1}, + .kdist_peer = {.enc = 1, .id = 1}, + }; + + nrf_err = pm_sec_params_set(&sec_param); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_PARAM, nrf_err); + + /* Error if maximum key size is greater than 16 bytes. */ + sec_param = (ble_gap_sec_params_t) { + .bond = 1, .mitm = 1, .lesc = 1, .keypress = 1, + .io_caps = BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY, .oob = 1, + + .min_key_size = 7, .max_key_size = 17, + .kdist_own = {.enc = 1, .id = 1}, + .kdist_peer = {.enc = 1, .id = 1}, + }; + + nrf_err = pm_sec_params_set(&sec_param); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_PARAM, nrf_err); + + /* Error if key distribution flags are set but bonding is set to unsupported. */ + sec_param = (ble_gap_sec_params_t) { + .bond = 0, .mitm = 1, .lesc = 1, .keypress = 1, + .io_caps = BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY, .oob = 1, + + .min_key_size = 7, .max_key_size = 16, + .kdist_own = {.enc = 1, .id = 1}, + .kdist_peer = {.enc = 1, .id = 1}, + }; + + nrf_err = pm_sec_params_set(&sec_param); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_PARAM, nrf_err); + + sec_param.kdist_own = (ble_gap_sec_kdist_t) {.enc = 1}; + sec_param.kdist_peer = (ble_gap_sec_kdist_t) {.enc = 0}; + + nrf_err = pm_sec_params_set(&sec_param); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_PARAM, nrf_err); + + sec_param.kdist_own = (ble_gap_sec_kdist_t) {.enc = 0}; + sec_param.kdist_peer = (ble_gap_sec_kdist_t) {.enc = 1}; + + nrf_err = pm_sec_params_set(&sec_param); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_PARAM, nrf_err); + + sec_param.kdist_own = (ble_gap_sec_kdist_t) {.id = 1}; + sec_param.kdist_peer = (ble_gap_sec_kdist_t) {.id = 0}; + + nrf_err = pm_sec_params_set(&sec_param); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_PARAM, nrf_err); + + sec_param.kdist_own = (ble_gap_sec_kdist_t) {.id = 0}; + sec_param.kdist_peer = (ble_gap_sec_kdist_t) {.id = 1}; + + nrf_err = pm_sec_params_set(&sec_param); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_PARAM, nrf_err); + + /* Error if bonding is set to supported, but no key distribution flags are set. */ + sec_param = (ble_gap_sec_params_t) { + .bond = 1, .mitm = 1, .lesc = 1, .keypress = 1, + .io_caps = BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY, .oob = 1, + + .min_key_size = 7, .max_key_size = 16, + .kdist_own = {.enc = 0, .id = 0}, + .kdist_peer = {.enc = 0, .id = 0}, + }; + + nrf_err = pm_sec_params_set(&sec_param); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_PARAM, nrf_err); +} + +void test_pm_ble_event_connected_not_bonded_success(void) +{ + union pm_entry_id entry; + ble_evt_t evt = {.evt.gap_evt.conn_handle = CONN_HANDLE_1}; + + peer_manager_initialize_success(); + + __cmock_nrf_sdh_ble_idx_get_Stub(stub_nrf_sdh_ble_idx_get); + __cmock_nrf_sdh_ble_conn_handle_get_Stub(stub_nrf_sdh_ble_conn_handle_get); + + /* Expect the bonding data storage to be iterated to find out if the connected peer have + * previously bonded. However, no bonding data is found for a peer with the given address. + */ + entry.data_id = PM_PEER_DATA_ID_BONDING; + entry.peer_id = 0; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + + entry.peer_id++; + } + + evt.header.evt_id = BLE_GAP_EVT_CONNECTED; + evt.evt.gap_evt.params.connected = (ble_gap_evt_connected_t) { + .peer_addr = ADDRESS_PUBLIC_1, + .role = BLE_GAP_ROLE_PERIPH, + }; + + sdh_evt_dispatch_ble(&evt); +} + +void test_pm_ble_event_connected_already_bonded_success(void) +{ + union pm_entry_id entry; + ble_evt_t evt = {.evt.gap_evt.conn_handle = CONN_HANDLE_1}; + const struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + .own_ltk = LTK_1, + }; + union local_gatt_db_with_data local_gatt_db = STORED_GATT_DATA_1; + + peer_manager_initialize_success(); + + __cmock_nrf_sdh_ble_idx_get_Stub(stub_nrf_sdh_ble_idx_get); + __cmock_nrf_sdh_ble_conn_handle_get_Stub(stub_nrf_sdh_ble_conn_handle_get); + + /* Expect the bonding data storage to be iterated to find out if the connected peer have + * previously bonded and if so, what peer ID is associated with the peer. + */ + entry.data_id = PM_PEER_DATA_ID_BONDING; + entry.peer_id = 0; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + entry.peer_id++; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, PM_PEER_DATA_MAX_SIZE); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data((void *)&bonding_data, + sizeof(struct pm_peer_data_bonding)); + + /* Expect a storage read looking for local gatt data for the connected peer, + * followed by a restoration of the local gatt database in the SoftDevice. + */ + entry.data_id = PM_PEER_DATA_ID_GATT_LOCAL; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, PM_PEER_DATA_MAX_SIZE); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data((void *)&local_gatt_db, sizeof(local_gatt_db)); + + __cmock_sd_ble_gatts_sys_attr_set_ExpectAndReturn(CONN_HANDLE_1, local_gatt_db.data.data, + local_gatt_db.data.len, + local_gatt_db.data.flags, NRF_SUCCESS); + + /* Expect a storage read looking for service changed state for the connected peer. */ + entry.data_id = PM_PEER_DATA_ID_SERVICE_CHANGED_PENDING; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + sizeof(bool), PM_PEER_DATA_MAX_SIZE); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data((void *)&(bool){0}, sizeof(bool)); + + /* Expect a storage read looking for central address resolution data for the connected + * peer. + */ + entry.data_id = PM_PEER_DATA_ID_CENTRAL_ADDR_RES; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + sizeof(uint32_t), PM_PEER_DATA_MAX_SIZE); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data((void *)&(uint32_t){1}, sizeof(uint32_t)); + + evt.header.evt_id = BLE_GAP_EVT_CONNECTED; + evt.evt.gap_evt.params.connected = (ble_gap_evt_connected_t) { + .peer_addr = ADDRESS_PUBLIC_1, + .role = BLE_GAP_ROLE_PERIPH, + }; + + sdh_evt_dispatch_ble(&evt); +} + +void test_pm_conn_secure_peripheral_pair_success(void) +{ + uint32_t nrf_err; + ble_gap_sec_params_t sec_param = { + .bond = 0, .mitm = 1, .lesc = 1, .keypress = 1, + .io_caps = BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY, .oob = 0, + + .min_key_size = 7, .max_key_size = 16, + .kdist_own = {.enc = 0, .id = 0}, + .kdist_peer = {.enc = 0, .id = 0}, + }; + union pm_entry_id entry; + ble_evt_t evt = {.evt.gap_evt.conn_handle = CONN_HANDLE_1}; + + peer_manager_initialize_success(); + + nrf_err = pm_sec_params_set(&sec_param); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + __cmock_nrf_sdh_ble_idx_get_Stub(stub_nrf_sdh_ble_idx_get); + __cmock_nrf_sdh_ble_conn_handle_get_Stub(stub_nrf_sdh_ble_conn_handle_get); + + /* Expect the bonding data storage to be iterated to find out if the connected peer have + * previously bonded. However, no bonding data is found for a peer with the given address. + */ + entry.data_id = PM_PEER_DATA_ID_BONDING; + entry.peer_id = 0; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + + entry.peer_id++; + } + + evt.header.evt_id = BLE_GAP_EVT_CONNECTED; + evt.evt.gap_evt.params.connected = (ble_gap_evt_connected_t) { + .peer_addr = ADDRESS_PUBLIC_1, + .role = BLE_GAP_ROLE_PERIPH, + }; + + sdh_evt_dispatch_ble(&evt); + + __cmock_sd_ble_gap_authenticate_ExpectWithArrayAndReturn(CONN_HANDLE_1, &sec_param, 1, + NRF_SUCCESS); + + nrf_err = pm_conn_secure(CONN_HANDLE_1, false); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + /* On a BLE_GAP_EVT_SEC_PARAMS_REQUEST event, expect a reply with the sec_param values that + * were set during Peer Manager initialization/configuration. + */ + __cmock_sd_ble_gap_sec_params_reply_ExpectWithArrayAndReturn(CONN_HANDLE_1, + BLE_GAP_SEC_STATUS_SUCCESS, + &sec_param, 1, PTR_IGNORE, 0, + NRF_SUCCESS); + __cmock_sd_ble_gap_sec_params_reply_IgnoreArg_p_sec_keyset(); + + evt.header.evt_id = BLE_GAP_EVT_SEC_PARAMS_REQUEST; + evt.evt.gap_evt.params.sec_params_request = (ble_gap_evt_sec_params_request_t) { + .peer_params = { + /* Security parameters for central (peer) side. */ + .bond = 0, .mitm = 1, .lesc = 1, .keypress = 1, + .io_caps = BLE_GAP_IO_CAPS_DISPLAY_ONLY, .oob = 1, + + .min_key_size = 7, .max_key_size = 16, + .kdist_own = {.enc = 0, .id = 0}, + .kdist_peer = {.enc = 0, .id = 0}, + }, + }; + + sdh_evt_dispatch_ble(&evt); + + /* Here, the SoftDevice would send a BLE_GAP_EVT_AUTH_KEY_REQUEST event and it must be + * handled by the application by invoking sd_ble_gap_auth_key_reply. + * Peer Manager will not handle this event (except for an edge case with LESC and + * identical keys), so nothing to test here. + */ + + /* On a BLE_GAP_EVT_LESC_DHKEY_REQUEST event, a flag is set to defer the computation + * of the DH key (key agreement) to the nrf_ble_lesc_request_handler() function and have + * that reply with sd_ble_gap_lesc_dhkey_reply() at a later time (usually from main loop). + */ + ble_gap_lesc_p256_pk_t peer_pk; + + memcpy(peer_pk.pk, peer_test_pub_key_sd, BLE_GAP_LESC_P256_PK_LEN); + + evt.header.evt_id = BLE_GAP_EVT_LESC_DHKEY_REQUEST; + evt.evt.gap_evt.params.lesc_dhkey_request = (ble_gap_evt_lesc_dhkey_request_t) { + .p_pk_peer = &peer_pk, .oobd_req = 0, + }; + + sdh_evt_dispatch_ble(&evt); + + /* Expect a call to nrf_ble_lesc_request_handler() to compute DH key (key agreement) + * with psa_raw_key_agreement() and reply to the SoftDevice with + * sd_ble_gap_lesc_dhkey_reply() to complete the BLE_GAP_EVT_LESC_DHKEY_REQUEST request. + */ + __cmock_psa_raw_key_agreement_ExpectWithArrayAndReturn( + PSA_ALG_ECDH, key_pair_id, + peer_test_pub_key_psa, sizeof(peer_test_pub_key_psa), sizeof(peer_test_pub_key_psa), + PTR_IGNORE, 0, sizeof(test_secret_psa), PTR_IGNORE, 0, PSA_SUCCESS); + __cmock_psa_raw_key_agreement_IgnoreArg_output(); + __cmock_psa_raw_key_agreement_IgnoreArg_output_length(); + __cmock_psa_raw_key_agreement_ReturnMemThruPtr_output( + test_secret_psa, sizeof(test_secret_psa)); + __cmock_psa_raw_key_agreement_ReturnMemThruPtr_output_length( + &(size_t){sizeof(test_secret_psa)}, sizeof(size_t)); + + ble_gap_lesc_dhkey_t secret; + + memcpy(secret.key, test_secret_sd, sizeof(test_secret_sd)); + __cmock_sd_ble_gap_lesc_dhkey_reply_ExpectWithArrayAndReturn( + CONN_HANDLE_1, BLE_GAP_SEC_STATUS_SUCCESS, &secret, 1, NRF_SUCCESS); + + nrf_err = nrf_ble_lesc_request_handler(); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + /* On a BLE_GAP_EVT_CONN_SEC_UPDATE, expect the connection security state to change. */ + evt.header.evt_id = BLE_GAP_EVT_CONN_SEC_UPDATE; + evt.evt.gap_evt.params.conn_sec_update = (ble_gap_evt_conn_sec_update_t) { + .conn_sec.sec_mode = {.sm = 1, .lv = 4}, + .conn_sec.encr_key_size = 16, + }; + struct pm_conn_sec_status conn_sec_status; + + nrf_err = pm_conn_sec_status_get(CONN_HANDLE_1, &conn_sec_status); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_TRUE(conn_sec_status.connected); + TEST_ASSERT_FALSE(conn_sec_status.bonded); + TEST_ASSERT_FALSE(conn_sec_status.encrypted); + TEST_ASSERT_FALSE(conn_sec_status.mitm_protected); + TEST_ASSERT_FALSE(conn_sec_status.lesc); + + sdh_evt_dispatch_ble(&evt); + + nrf_err = pm_conn_sec_status_get(CONN_HANDLE_1, &conn_sec_status); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_TRUE(conn_sec_status.connected); + TEST_ASSERT_FALSE(conn_sec_status.bonded); + TEST_ASSERT_TRUE(conn_sec_status.encrypted); + TEST_ASSERT_TRUE(conn_sec_status.mitm_protected); + TEST_ASSERT_TRUE(conn_sec_status.lesc); + + /* On a BLE_GAP_EVT_AUTH_STATUS, expect peer manager to finish up the pairing request. + * Not much is done in response to this event when simply pairing (no bonding). + */ + evt.header.evt_id = BLE_GAP_EVT_AUTH_STATUS; + evt.evt.gap_evt.params.auth_status = (ble_gap_evt_auth_status_t) { + .auth_status = BLE_GAP_SEC_STATUS_SUCCESS, + .bonded = false, + .lesc = true, + .sm1_levels = {.lv1 = 1, .lv2 = 1, .lv3 = 1, .lv4 = 1}, + .kdist_own = {.enc = 0, .id = 0}, + .kdist_peer = {.enc = 0, .id = 0}, + }; + + sdh_evt_dispatch_ble(&evt); + + TEST_ASSERT_EQUAL(0, pm_peer_count()); +} + +void test_pm_conn_secure_peripheral_pair_and_bond_success(void) +{ + uint32_t nrf_err; + ble_gap_sec_params_t sec_param = { + .bond = 1, .mitm = 1, .lesc = 1, .keypress = 1, + .io_caps = BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY, .oob = 0, + + .min_key_size = 7, .max_key_size = 16, + .kdist_own = {.enc = 1, .id = 1}, + .kdist_peer = {.enc = 1, .id = 1}, + }; + union pm_entry_id entry; + ble_evt_t evt = {.evt.gap_evt.conn_handle = CONN_HANDLE_1}; + + peer_manager_initialize_success(); + + nrf_err = pm_sec_params_set(&sec_param); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + __cmock_nrf_sdh_ble_idx_get_Stub(stub_nrf_sdh_ble_idx_get); + __cmock_nrf_sdh_ble_conn_handle_get_Stub(stub_nrf_sdh_ble_conn_handle_get); + + /* Expect the bonding data storage to be iterated to find out if the connected peer have + * previously bonded. However, no bonding data is found for a peer with the given address. + */ + entry.data_id = PM_PEER_DATA_ID_BONDING; + entry.peer_id = 0; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + + entry.peer_id++; + } + + evt.header.evt_id = BLE_GAP_EVT_CONNECTED; + evt.evt.gap_evt.params.connected = (ble_gap_evt_connected_t) { + .peer_addr = ADDRESS_PUBLIC_1, + .role = BLE_GAP_ROLE_PERIPH, + }; + + sdh_evt_dispatch_ble(&evt); + + __cmock_sd_ble_gap_authenticate_ExpectWithArrayAndReturn(CONN_HANDLE_1, &sec_param, 1, + NRF_SUCCESS); + + nrf_err = pm_conn_secure(CONN_HANDLE_1, false); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + /* On a BLE_GAP_EVT_SEC_PARAMS_REQUEST event, expect a reply with the sec_param values that + * were set during Peer Manager initialization/configuration. + */ + __cmock_sd_ble_gap_sec_params_reply_ExpectWithArrayAndReturn(CONN_HANDLE_1, + BLE_GAP_SEC_STATUS_SUCCESS, + &sec_param, 1, PTR_IGNORE, 0, + NRF_SUCCESS); + __cmock_sd_ble_gap_sec_params_reply_IgnoreArg_p_sec_keyset(); + + evt.header.evt_id = BLE_GAP_EVT_SEC_PARAMS_REQUEST; + evt.evt.gap_evt.params.sec_params_request = (ble_gap_evt_sec_params_request_t) { + .peer_params = { + /* Security parameters for central (peer) side. */ + .bond = 1, .mitm = 1, .lesc = 1, .keypress = 1, + .io_caps = BLE_GAP_IO_CAPS_DISPLAY_ONLY, .oob = 1, + + .min_key_size = 7, .max_key_size = 16, + .kdist_own = {.enc = 1, .id = 1}, + .kdist_peer = {.enc = 1, .id = 1}, + }, + }; + + sdh_evt_dispatch_ble(&evt); + + /* Here, the SoftDevice would send a BLE_GAP_EVT_AUTH_KEY_REQUEST event and it must be + * handled by the application by invoking sd_ble_gap_auth_key_reply. + * Peer Manager will not handle this event (except for an edge case with LESC and + * identical keys), so nothing to test here. + */ + + /* On a BLE_GAP_EVT_LESC_DHKEY_REQUEST event, a flag is set to defer the computation + * of the DH key (key agreement) to the nrf_ble_lesc_request_handler() function and have + * that reply with sd_ble_gap_lesc_dhkey_reply() at a later time (usually from main loop). + */ + ble_gap_lesc_p256_pk_t peer_pk; + + memcpy(peer_pk.pk, peer_test_pub_key_sd, BLE_GAP_LESC_P256_PK_LEN); + + evt.header.evt_id = BLE_GAP_EVT_LESC_DHKEY_REQUEST; + evt.evt.gap_evt.params.lesc_dhkey_request = (ble_gap_evt_lesc_dhkey_request_t) { + .p_pk_peer = &peer_pk, .oobd_req = 0, + }; + + sdh_evt_dispatch_ble(&evt); + + /* Expect a call to nrf_ble_lesc_request_handler() to compute DH key (key agreement) + * with psa_raw_key_agreement() and reply to the SoftDevice with + * sd_ble_gap_lesc_dhkey_reply() to complete the BLE_GAP_EVT_LESC_DHKEY_REQUEST request. + */ + __cmock_psa_raw_key_agreement_ExpectWithArrayAndReturn( + PSA_ALG_ECDH, key_pair_id, + peer_test_pub_key_psa, sizeof(peer_test_pub_key_psa), sizeof(peer_test_pub_key_psa), + PTR_IGNORE, 0, sizeof(test_secret_psa), PTR_IGNORE, 0, PSA_SUCCESS); + __cmock_psa_raw_key_agreement_IgnoreArg_output(); + __cmock_psa_raw_key_agreement_IgnoreArg_output_length(); + __cmock_psa_raw_key_agreement_ReturnMemThruPtr_output( + test_secret_psa, sizeof(test_secret_psa)); + __cmock_psa_raw_key_agreement_ReturnMemThruPtr_output_length( + &(size_t){sizeof(test_secret_psa)}, sizeof(size_t)); + + ble_gap_lesc_dhkey_t secret; + + memcpy(secret.key, test_secret_sd, sizeof(test_secret_sd)); + __cmock_sd_ble_gap_lesc_dhkey_reply_ExpectWithArrayAndReturn( + CONN_HANDLE_1, BLE_GAP_SEC_STATUS_SUCCESS, &secret, 1, NRF_SUCCESS); + + nrf_err = nrf_ble_lesc_request_handler(); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + /* On a BLE_GAP_EVT_CONN_SEC_UPDATE, expect the connection security state to change. */ + evt.header.evt_id = BLE_GAP_EVT_CONN_SEC_UPDATE; + evt.evt.gap_evt.params.conn_sec_update = (ble_gap_evt_conn_sec_update_t) { + .conn_sec.sec_mode = {.sm = 1, .lv = 4}, + .conn_sec.encr_key_size = 16, + }; + struct pm_conn_sec_status conn_sec_status; + + nrf_err = pm_conn_sec_status_get(CONN_HANDLE_1, &conn_sec_status); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_TRUE(conn_sec_status.connected); + TEST_ASSERT_FALSE(conn_sec_status.bonded); + TEST_ASSERT_FALSE(conn_sec_status.encrypted); + TEST_ASSERT_FALSE(conn_sec_status.mitm_protected); + TEST_ASSERT_FALSE(conn_sec_status.lesc); + + sdh_evt_dispatch_ble(&evt); + + nrf_err = pm_conn_sec_status_get(CONN_HANDLE_1, &conn_sec_status); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_TRUE(conn_sec_status.connected); + TEST_ASSERT_FALSE(conn_sec_status.bonded); + TEST_ASSERT_TRUE(conn_sec_status.encrypted); + TEST_ASSERT_TRUE(conn_sec_status.mitm_protected); + TEST_ASSERT_TRUE(conn_sec_status.lesc); + + /* On a BLE_GAP_EVT_AUTH_STATUS, expect peer manager to finish up the pairing request. + * + * Expect the bonding data storage to be iterated to check if the paired peer already have + * bonding data. No duplicate bonding data is to be found. + * Next, expect new bonding data to be stored. + */ + entry.data_id = PM_PEER_DATA_ID_BONDING; + entry.peer_id = 0; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + + entry.peer_id++; + } + + entry.data_id = PM_PEER_DATA_ID_BONDING; + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + sizeof(struct pm_peer_data_bonding), 0); + __cmock_bm_zms_write_IgnoreArg_data(); + + evt.header.evt_id = BLE_GAP_EVT_AUTH_STATUS; + evt.evt.gap_evt.params.auth_status = (ble_gap_evt_auth_status_t) { + .auth_status = BLE_GAP_SEC_STATUS_SUCCESS, + .bonded = true, + .lesc = true, + .sm1_levels = {.lv1 = 1, .lv2 = 1, .lv3 = 1, .lv4 = 1}, + .kdist_own = {.enc = 1, .id = 1}, + .kdist_peer = {.enc = 1, .id = 1}, + }; + + sdh_evt_dispatch_ble(&evt); + + TEST_ASSERT_EQUAL(1, pm_peer_count()); + + nrf_err = pm_conn_sec_status_get(CONN_HANDLE_1, &conn_sec_status); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_TRUE(conn_sec_status.connected); + TEST_ASSERT_TRUE(conn_sec_status.bonded); + TEST_ASSERT_TRUE(conn_sec_status.encrypted); + TEST_ASSERT_TRUE(conn_sec_status.mitm_protected); + TEST_ASSERT_TRUE(conn_sec_status.lesc); +} + +void test_pm_conn_secure_peripheral_with_bonding_data_success(void) +{ + uint32_t nrf_err; + ble_gap_sec_params_t sec_param = { + .bond = 1, .mitm = 1, .lesc = 1, .keypress = 1, + .io_caps = BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY, .oob = 0, + + .min_key_size = 7, .max_key_size = 16, + .kdist_own = {.enc = 1, .id = 1}, + .kdist_peer = {.enc = 1, .id = 1}, + }; + union pm_entry_id entry; + ble_evt_t evt = {.evt.gap_evt.conn_handle = CONN_HANDLE_1}; + const struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + .own_ltk = LTK_1, + }; + union local_gatt_db_with_data local_gatt_db = STORED_GATT_DATA_1; + + peer_manager_initialize_success(); + + nrf_err = pm_sec_params_set(&sec_param); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + __cmock_nrf_sdh_ble_idx_get_Stub(stub_nrf_sdh_ble_idx_get); + __cmock_nrf_sdh_ble_conn_handle_get_Stub(stub_nrf_sdh_ble_conn_handle_get); + + /* Expect the bonding data storage to be iterated to find out if the connected peer have + * previously bonded and if so, what peer ID is associated with the peer. + */ + entry.data_id = PM_PEER_DATA_ID_BONDING; + entry.peer_id = 0; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + entry.peer_id++; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, PM_PEER_DATA_MAX_SIZE); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data((void *)&bonding_data, + sizeof(struct pm_peer_data_bonding)); + + /* Expect a storage read looking for local gatt data for the connected peer, + * followed by a restoration of the local gatt database in the SoftDevice. + */ + entry.data_id = PM_PEER_DATA_ID_GATT_LOCAL; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, PM_PEER_DATA_MAX_SIZE); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data((void *)&local_gatt_db, sizeof(local_gatt_db)); + + __cmock_sd_ble_gatts_sys_attr_set_ExpectAndReturn(CONN_HANDLE_1, local_gatt_db.data.data, + local_gatt_db.data.len, + local_gatt_db.data.flags, NRF_SUCCESS); + + /* Expect a storage read looking for service changed state for the connected peer. */ + entry.data_id = PM_PEER_DATA_ID_SERVICE_CHANGED_PENDING; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + sizeof(bool), PM_PEER_DATA_MAX_SIZE); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data((void *)&(bool){0}, sizeof(bool)); + + /* Expect a storage read looking for central address resolution data for the connected + * peer. + */ + entry.data_id = PM_PEER_DATA_ID_CENTRAL_ADDR_RES; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + sizeof(uint32_t), PM_PEER_DATA_MAX_SIZE); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data((void *)&(uint32_t){1}, sizeof(uint32_t)); + + evt.header.evt_id = BLE_GAP_EVT_CONNECTED; + evt.evt.gap_evt.params.connected = (ble_gap_evt_connected_t) { + .peer_addr = ADDRESS_PUBLIC_1, + .role = BLE_GAP_ROLE_PERIPH, + }; + + sdh_evt_dispatch_ble(&evt); + + /* On a BLE_GAP_EVT_SEC_INFO_REQUEST event, expect the bonding data storage to be iterated + * in search for a matching master ID. None found. Peer ID is found based on conn_handle. + * Next, expect a read of the stored bonding data based on the peer ID. + */ + entry = (union pm_entry_id) {.data_id = PM_PEER_DATA_ID_BONDING, .peer_id = 0}; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + if (i == 1) { + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, + PM_PEER_DATA_MAX_SIZE); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data( + (void *)&bonding_data, sizeof(struct pm_peer_data_bonding)); + } else { + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + + entry.peer_id++; + } + + entry = (union pm_entry_id) {.data_id = PM_PEER_DATA_ID_BONDING, .peer_id = 1}; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + sizeof(struct pm_peer_data_bonding), + sizeof(struct pm_peer_data_bonding)); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data((void *)&bonding_data, + sizeof(struct pm_peer_data_bonding)); + + /* Expect reply to the BLE_GAP_EVT_SEC_INFO_REQUEST event with ltk. */ + __cmock_sd_ble_gap_sec_info_reply_ExpectWithArrayAndReturn( + CONN_HANDLE_1, &bonding_data.own_ltk.enc_info, 1, NRF_SUCCESS); + + evt.header.evt_id = BLE_GAP_EVT_SEC_INFO_REQUEST; + evt.evt.gap_evt.params.sec_info_request = (ble_gap_evt_sec_info_request_t) { + .peer_addr = ADDRESS_PUBLIC_1, + .master_id = {0}, + .enc_info = 1, + .id_info = 0, + }; + + sdh_evt_dispatch_ble(&evt); +} + +void test_pm_conn_secure_central_pair_success(void) +{ + uint32_t nrf_err; + ble_gap_sec_params_t sec_param = { + .bond = 0, .mitm = 1, .lesc = 1, .keypress = 1, + .io_caps = BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY, .oob = 0, + + .min_key_size = 7, .max_key_size = 16, + .kdist_own = {.enc = 0, .id = 0}, + .kdist_peer = {.enc = 0, .id = 0}, + }; + union pm_entry_id entry; + ble_evt_t evt = {.evt.gap_evt.conn_handle = CONN_HANDLE_1}; + + peer_manager_initialize_success(); + + nrf_err = pm_sec_params_set(&sec_param); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + __cmock_nrf_sdh_ble_idx_get_Stub(stub_nrf_sdh_ble_idx_get); + __cmock_nrf_sdh_ble_conn_handle_get_Stub(stub_nrf_sdh_ble_conn_handle_get); + + /* Expect the bonding data storage to be iterated to find out if the connected peer have + * previously bonded. However, no bonding data is found for a peer with the given address. + */ + entry.data_id = PM_PEER_DATA_ID_BONDING; + entry.peer_id = 0; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + + entry.peer_id++; + } + + evt.header.evt_id = BLE_GAP_EVT_CONNECTED; + evt.evt.gap_evt.params.connected = (ble_gap_evt_connected_t) { + .peer_addr = ADDRESS_PUBLIC_1, + .role = BLE_GAP_ROLE_CENTRAL, + }; + + sdh_evt_dispatch_ble(&evt); + + __cmock_sd_ble_gap_authenticate_ExpectWithArrayAndReturn(CONN_HANDLE_1, &sec_param, 1, + NRF_SUCCESS); + + nrf_err = pm_conn_secure(CONN_HANDLE_1, false); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + /* On a BLE_GAP_EVT_SEC_PARAMS_REQUEST event, expect a reply with sec_param == NULL. + * The central's sec_params was passed to the SoftDevice with the call to + * sd_ble_gap_authenticate() so no reason to repeat it. + */ + __cmock_sd_ble_gap_sec_params_reply_ExpectWithArrayAndReturn(CONN_HANDLE_1, + BLE_GAP_SEC_STATUS_SUCCESS, + NULL, 0, PTR_IGNORE, 0, + NRF_SUCCESS); + __cmock_sd_ble_gap_sec_params_reply_IgnoreArg_p_sec_keyset(); + + evt.header.evt_id = BLE_GAP_EVT_SEC_PARAMS_REQUEST; + evt.evt.gap_evt.params.sec_params_request = (ble_gap_evt_sec_params_request_t) { + .peer_params = { + /* Security parameters for peripheral (peer) side. */ + .bond = 0, .mitm = 1, .lesc = 1, .keypress = 1, + .io_caps = BLE_GAP_IO_CAPS_DISPLAY_ONLY, .oob = 1, + + .min_key_size = 7, .max_key_size = 16, + .kdist_own = {.enc = 0, .id = 0}, + .kdist_peer = {.enc = 0, .id = 0}, + }, + }; + + sdh_evt_dispatch_ble(&evt); + + /* Here, the SoftDevice would send a BLE_GAP_EVT_AUTH_KEY_REQUEST event and it must be + * handled by the application by invoking sd_ble_gap_auth_key_reply. + * Peer Manager will not handle this event (except for an edge case with LESC and + * identical keys), so nothing to test here. + */ + + /* On a BLE_GAP_EVT_LESC_DHKEY_REQUEST event, a flag is set to defer the computation + * of the DH key (key agreement) to the nrf_ble_lesc_request_handler() function and have + * that reply with sd_ble_gap_lesc_dhkey_reply() at a later time (usually from main loop). + */ + ble_gap_lesc_p256_pk_t peer_pk; + + memcpy(peer_pk.pk, peer_test_pub_key_sd, BLE_GAP_LESC_P256_PK_LEN); + + evt.header.evt_id = BLE_GAP_EVT_LESC_DHKEY_REQUEST; + evt.evt.gap_evt.params.lesc_dhkey_request = (ble_gap_evt_lesc_dhkey_request_t) { + .p_pk_peer = &peer_pk, .oobd_req = 0, + }; + + sdh_evt_dispatch_ble(&evt); + + /* Expect a call to nrf_ble_lesc_request_handler() to compute DH key (key agreement) + * with psa_raw_key_agreement() and reply to the SoftDevice with + * sd_ble_gap_lesc_dhkey_reply() to complete the BLE_GAP_EVT_LESC_DHKEY_REQUEST request. + */ + __cmock_psa_raw_key_agreement_ExpectWithArrayAndReturn( + PSA_ALG_ECDH, key_pair_id, + peer_test_pub_key_psa, sizeof(peer_test_pub_key_psa), sizeof(peer_test_pub_key_psa), + PTR_IGNORE, 0, sizeof(test_secret_psa), PTR_IGNORE, 0, PSA_SUCCESS); + __cmock_psa_raw_key_agreement_IgnoreArg_output(); + __cmock_psa_raw_key_agreement_IgnoreArg_output_length(); + __cmock_psa_raw_key_agreement_ReturnMemThruPtr_output( + test_secret_psa, sizeof(test_secret_psa)); + __cmock_psa_raw_key_agreement_ReturnMemThruPtr_output_length( + &(size_t){sizeof(test_secret_psa)}, sizeof(size_t)); + + ble_gap_lesc_dhkey_t secret; + + memcpy(secret.key, test_secret_sd, sizeof(test_secret_sd)); + __cmock_sd_ble_gap_lesc_dhkey_reply_ExpectWithArrayAndReturn( + CONN_HANDLE_1, BLE_GAP_SEC_STATUS_SUCCESS, &secret, 1, NRF_SUCCESS); + + nrf_err = nrf_ble_lesc_request_handler(); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + /* On a BLE_GAP_EVT_CONN_SEC_UPDATE, expect the connection security state to change. */ + evt.header.evt_id = BLE_GAP_EVT_CONN_SEC_UPDATE; + evt.evt.gap_evt.params.conn_sec_update = (ble_gap_evt_conn_sec_update_t) { + .conn_sec.sec_mode = {.sm = 1, .lv = 4}, + .conn_sec.encr_key_size = 16, + }; + struct pm_conn_sec_status conn_sec_status; + + nrf_err = pm_conn_sec_status_get(CONN_HANDLE_1, &conn_sec_status); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_TRUE(conn_sec_status.connected); + TEST_ASSERT_FALSE(conn_sec_status.bonded); + TEST_ASSERT_FALSE(conn_sec_status.encrypted); + TEST_ASSERT_FALSE(conn_sec_status.mitm_protected); + TEST_ASSERT_FALSE(conn_sec_status.lesc); + + sdh_evt_dispatch_ble(&evt); + + nrf_err = pm_conn_sec_status_get(CONN_HANDLE_1, &conn_sec_status); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_TRUE(conn_sec_status.connected); + TEST_ASSERT_FALSE(conn_sec_status.bonded); + TEST_ASSERT_TRUE(conn_sec_status.encrypted); + TEST_ASSERT_TRUE(conn_sec_status.mitm_protected); + TEST_ASSERT_TRUE(conn_sec_status.lesc); + + /* On a BLE_GAP_EVT_AUTH_STATUS, expect peer manager to finish up the pairing request. + * Not much is done in response to this event when simply pairing (no bonding). + */ + evt.header.evt_id = BLE_GAP_EVT_AUTH_STATUS; + evt.evt.gap_evt.params.auth_status = (ble_gap_evt_auth_status_t) { + .auth_status = BLE_GAP_SEC_STATUS_SUCCESS, + .bonded = false, + .lesc = true, + .sm1_levels = {.lv1 = 1, .lv2 = 1, .lv3 = 1, .lv4 = 1}, + .kdist_own = {.enc = 0, .id = 0}, + .kdist_peer = {.enc = 0, .id = 0}, + }; + + sdh_evt_dispatch_ble(&evt); + + TEST_ASSERT_EQUAL(0, pm_peer_count()); +} + +void test_pm_conn_secure_central_pair_and_bond_success(void) +{ + uint32_t nrf_err; + ble_gap_sec_params_t sec_param = { + .bond = 1, .mitm = 1, .lesc = 1, .keypress = 1, + .io_caps = BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY, .oob = 0, + + .min_key_size = 7, .max_key_size = 16, + .kdist_own = {.enc = 1, .id = 1}, + .kdist_peer = {.enc = 1, .id = 1}, + }; + union pm_entry_id entry; + ble_evt_t evt = {.evt.gap_evt.conn_handle = CONN_HANDLE_1}; + + peer_manager_initialize_success(); + + nrf_err = pm_sec_params_set(&sec_param); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + __cmock_nrf_sdh_ble_idx_get_Stub(stub_nrf_sdh_ble_idx_get); + __cmock_nrf_sdh_ble_conn_handle_get_Stub(stub_nrf_sdh_ble_conn_handle_get); + + /* Expect the bonding data storage to be iterated to find out if the connected peer have + * previously bonded. However, no bonding data is found for a peer with the given address. + */ + entry.data_id = PM_PEER_DATA_ID_BONDING; + entry.peer_id = 0; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + + entry.peer_id++; + } + + evt.header.evt_id = BLE_GAP_EVT_CONNECTED; + evt.evt.gap_evt.params.connected = (ble_gap_evt_connected_t) { + .peer_addr = ADDRESS_PUBLIC_1, + .role = BLE_GAP_ROLE_CENTRAL, + }; + + sdh_evt_dispatch_ble(&evt); + + __cmock_sd_ble_gap_authenticate_ExpectWithArrayAndReturn(CONN_HANDLE_1, &sec_param, 1, + NRF_SUCCESS); + + nrf_err = pm_conn_secure(CONN_HANDLE_1, false); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + /* On a BLE_GAP_EVT_SEC_PARAMS_REQUEST event, expect a reply with sec_param == NULL. + * The central's sec_params was passed to the SoftDevice with the call to + * sd_ble_gap_authenticate() so no reason to repeat it. + */ + __cmock_sd_ble_gap_sec_params_reply_ExpectWithArrayAndReturn(CONN_HANDLE_1, + BLE_GAP_SEC_STATUS_SUCCESS, + NULL, 0, PTR_IGNORE, 0, + NRF_SUCCESS); + __cmock_sd_ble_gap_sec_params_reply_IgnoreArg_p_sec_keyset(); + + evt.header.evt_id = BLE_GAP_EVT_SEC_PARAMS_REQUEST; + evt.evt.gap_evt.params.sec_params_request = (ble_gap_evt_sec_params_request_t) { + .peer_params = { + /* Security parameters for peripheral (peer) side. */ + .bond = 1, .mitm = 1, .lesc = 1, .keypress = 1, + .io_caps = BLE_GAP_IO_CAPS_DISPLAY_ONLY, .oob = 1, + + .min_key_size = 7, .max_key_size = 16, + .kdist_own = {.enc = 1, .id = 1}, + .kdist_peer = {.enc = 1, .id = 1}, + }, + }; + + sdh_evt_dispatch_ble(&evt); + + /* Here, the SoftDevice would send a BLE_GAP_EVT_AUTH_KEY_REQUEST event and it must be + * handled by the application by invoking sd_ble_gap_auth_key_reply. + * Peer Manager will not handle this event (except for an edge case with LESC and + * identical keys), so nothing to test here. + */ + + /* On a BLE_GAP_EVT_LESC_DHKEY_REQUEST event, a flag is set to defer the computation + * of the DH key (key agreement) to the nrf_ble_lesc_request_handler() function and have + * that reply with sd_ble_gap_lesc_dhkey_reply() at a later time (usually from main loop). + */ + ble_gap_lesc_p256_pk_t peer_pk; + + memcpy(peer_pk.pk, peer_test_pub_key_sd, BLE_GAP_LESC_P256_PK_LEN); + + evt.header.evt_id = BLE_GAP_EVT_LESC_DHKEY_REQUEST; + evt.evt.gap_evt.params.lesc_dhkey_request = (ble_gap_evt_lesc_dhkey_request_t) { + .p_pk_peer = &peer_pk, .oobd_req = 0, + }; + + sdh_evt_dispatch_ble(&evt); + + /* Expect a call to nrf_ble_lesc_request_handler() to compute DH key (key agreement) + * with psa_raw_key_agreement() and reply to the SoftDevice with + * sd_ble_gap_lesc_dhkey_reply() to complete the BLE_GAP_EVT_LESC_DHKEY_REQUEST request. + */ + __cmock_psa_raw_key_agreement_ExpectWithArrayAndReturn( + PSA_ALG_ECDH, key_pair_id, + peer_test_pub_key_psa, sizeof(peer_test_pub_key_psa), sizeof(peer_test_pub_key_psa), + PTR_IGNORE, 0, sizeof(test_secret_psa), PTR_IGNORE, 0, PSA_SUCCESS); + __cmock_psa_raw_key_agreement_IgnoreArg_output(); + __cmock_psa_raw_key_agreement_IgnoreArg_output_length(); + __cmock_psa_raw_key_agreement_ReturnMemThruPtr_output( + test_secret_psa, sizeof(test_secret_psa)); + __cmock_psa_raw_key_agreement_ReturnMemThruPtr_output_length( + &(size_t){sizeof(test_secret_psa)}, sizeof(size_t)); + + ble_gap_lesc_dhkey_t secret; + + memcpy(secret.key, test_secret_sd, sizeof(test_secret_sd)); + __cmock_sd_ble_gap_lesc_dhkey_reply_ExpectWithArrayAndReturn( + CONN_HANDLE_1, BLE_GAP_SEC_STATUS_SUCCESS, &secret, 1, NRF_SUCCESS); + + nrf_err = nrf_ble_lesc_request_handler(); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + /* On a BLE_GAP_EVT_CONN_SEC_UPDATE, expect the connection security state to change. */ + evt.header.evt_id = BLE_GAP_EVT_CONN_SEC_UPDATE; + evt.evt.gap_evt.params.conn_sec_update = (ble_gap_evt_conn_sec_update_t) { + .conn_sec.sec_mode = {.sm = 1, .lv = 4}, + .conn_sec.encr_key_size = 16, + }; + struct pm_conn_sec_status conn_sec_status; + + nrf_err = pm_conn_sec_status_get(CONN_HANDLE_1, &conn_sec_status); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_TRUE(conn_sec_status.connected); + TEST_ASSERT_FALSE(conn_sec_status.bonded); + TEST_ASSERT_FALSE(conn_sec_status.encrypted); + TEST_ASSERT_FALSE(conn_sec_status.mitm_protected); + TEST_ASSERT_FALSE(conn_sec_status.lesc); + + sdh_evt_dispatch_ble(&evt); + + nrf_err = pm_conn_sec_status_get(CONN_HANDLE_1, &conn_sec_status); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_TRUE(conn_sec_status.connected); + TEST_ASSERT_FALSE(conn_sec_status.bonded); + TEST_ASSERT_TRUE(conn_sec_status.encrypted); + TEST_ASSERT_TRUE(conn_sec_status.mitm_protected); + TEST_ASSERT_TRUE(conn_sec_status.lesc); + + /* On a BLE_GAP_EVT_AUTH_STATUS, expect peer manager to finish up the pairing request. + * Not much is done in response to this event when simply pairing (no bonding). + */ + evt.header.evt_id = BLE_GAP_EVT_AUTH_STATUS; + evt.evt.gap_evt.params.auth_status = (ble_gap_evt_auth_status_t) { + .auth_status = BLE_GAP_SEC_STATUS_SUCCESS, + .bonded = false, + .lesc = true, + .sm1_levels = {.lv1 = 1, .lv2 = 1, .lv3 = 1, .lv4 = 1}, + .kdist_own = {.enc = 0, .id = 0}, + .kdist_peer = {.enc = 0, .id = 0}, + }; + + sdh_evt_dispatch_ble(&evt); + + TEST_ASSERT_EQUAL(0, pm_peer_count()); +} + +void test_pm_conn_exclude_success(void) +{ + uint32_t nrf_err; + + peer_manager_initialize_success(); + + __cmock_nrf_sdh_ble_idx_get_Stub(stub_nrf_sdh_ble_idx_get); + + nrf_err = pm_register(on_pm_evt); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + on_pm_evt_test_ctx.conn_config_req.exclude_connection = true; + + const ble_evt_t evt = { + .header.evt_id = BLE_GAP_EVT_CONNECTED, + .evt.gap_evt = { + .conn_handle = CONN_HANDLE_1, + .params.connected = { + .peer_addr = ADDRESS_PUBLIC_1, + .role = BLE_GAP_ROLE_PERIPH, + }, + }, + }; + + /* Expect peer_manager to ignore the connection handle. + * No more function calls are expected in response to the connected event. + */ + sdh_evt_dispatch_ble(&evt); +} + +void test_pm_conn_exclude_null(void) +{ + uint32_t nrf_err; + + peer_manager_initialize_success(); + + nrf_err = pm_conn_exclude(CONN_HANDLE_1, NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); +} + +void setUp(void) +{ +} + +void tearDown(void) +{ + uint32_t nrf_err; + + /* Reset behavior for on_pm_evt event handler function. */ + memset(&on_pm_evt_test_ctx, 0, sizeof(on_pm_evt_test_ctx)); + + if (zms_fs != NULL) { + zms_fs->init_flags.initialized = false; + zms_fs = NULL; + } + zms_handler = NULL; + + ble_gap_lesc_p256_pk_t *pub_key = nrf_ble_lesc_public_key_get(); + + if (pub_key != NULL) { + memset(pub_key->pk, 0, sizeof(pub_key->pk)); + } + + /* Set Expect functions. */ + __cmock_psa_crypto_init_ExpectAndReturn(PSA_SUCCESS); + __cmock_psa_set_key_usage_flags_ExpectAnyArgs(); + __cmock_psa_set_key_lifetime_ExpectAnyArgs(); + __cmock_psa_set_key_algorithm_ExpectAnyArgs(); + __cmock_psa_set_key_type_ExpectAnyArgs(); + __cmock_psa_set_key_bits_ExpectAnyArgs(); + __cmock_psa_destroy_key_ExpectAnyArgsAndReturn(PSA_ERROR_INVALID_HANDLE); + __cmock_psa_generate_key_ExpectAnyArgsAndReturn(PSA_ERROR_BAD_STATE); + + nrf_err = nrf_ble_lesc_init(); + TEST_ASSERT_EQUAL(NRF_ERROR_INTERNAL, nrf_err); +} + +extern int unity_main(void); + +int main(void) +{ + return unity_main(); +} diff --git a/tests/unit/lib/bluetooth/peer_manager/testcase.yaml b/tests/unit/lib/bluetooth/peer_manager/testcase.yaml new file mode 100644 index 0000000000..a700de39f4 --- /dev/null +++ b/tests/unit/lib/bluetooth/peer_manager/testcase.yaml @@ -0,0 +1,4 @@ +tests: + lib.peer_manager: + platform_allow: native_sim + tags: unittest From d46dbb9ccae199f5cf921ee6b1739c9e121e7c85 Mon Sep 17 00:00:00 2001 From: Andreas Moltumyr Date: Fri, 29 May 2026 17:11:09 +0200 Subject: [PATCH 12/12] tests: unit: peer_manager: add more unit tests Add more unit tests for peer manager. These have been assisted with AI. Signed-off-by: Andreas Moltumyr --- .../bluetooth/peer_manager/src/unity_test.c | 2717 ++++++++++++++++- 1 file changed, 2644 insertions(+), 73 deletions(-) diff --git a/tests/unit/lib/bluetooth/peer_manager/src/unity_test.c b/tests/unit/lib/bluetooth/peer_manager/src/unity_test.c index 4e0b0c4e69..2a547f252d 100644 --- a/tests/unit/lib/bluetooth/peer_manager/src/unity_test.c +++ b/tests/unit/lib/bluetooth/peer_manager/src/unity_test.c @@ -20,6 +20,7 @@ #include "cmock_ble_gap.h" #include "cmock_ble_gatts.h" #include "cmock_nrf_sdh_ble.h" +#include "cmock_nrf_soc.h" #include "cmock_bm_zms.h" #include "cmock_crypto.h" #include "cmock_bm_timer.h" @@ -46,6 +47,26 @@ .addr = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66}, \ } +#define ADDRESS_PUBLIC_2 \ + (ble_gap_addr_t) { \ + .addr_id_peer = 0, .addr_type = BLE_GAP_ADDR_TYPE_PUBLIC, \ + .addr = {0xAA, 0xBB, 0x33, 0x44, 0x55, 0x66}, \ + } + +#define ADDRESS_RANDOM_PRIVATE \ + (ble_gap_addr_t) { \ + .addr_id_peer = 0, \ + .addr_type = BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE, \ + .addr = {0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6}, \ + } + +#define ADDRESS_RANDOM_STATIC \ + (ble_gap_addr_t) { \ + .addr_id_peer = 0, \ + .addr_type = BLE_GAP_ADDR_TYPE_RANDOM_STATIC, \ + .addr = {0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6}, \ + } + #define LTK_1 \ (ble_gap_enc_key_t) { \ .enc_info = { \ @@ -290,6 +311,61 @@ int stub_bm_timer_init_ast(struct bm_timer *timer, enum bm_timer_mode mode, return 0; } +static void invoke_zms_write_success(uint32_t entry_id) +{ + struct bm_zms_evt evt = { + .evt_type = BM_ZMS_EVT_WRITE, + .id = entry_id, + .result = 0, + }; + + TEST_ASSERT_NOT_NULL(zms_handler); + zms_handler(&evt); +} + +static void invoke_zms_delete_success(uint32_t entry_id) +{ + struct bm_zms_evt evt = { + .evt_type = BM_ZMS_EVT_DELETE, + .id = entry_id, + .result = 0, + }; + + TEST_ASSERT_NOT_NULL(zms_handler); + zms_handler(&evt); +} + +static int stub_bm_zms_delete_invoke_handler(struct bm_zms_fs *fs, uint32_t id, int cmock_num_calls) +{ + ARG_UNUSED(fs); + ARG_UNUSED(cmock_num_calls); + + invoke_zms_delete_success(id); + + return 0; +} + +/* Mirrors security_manager.c sec_params_reply_context for pm_conn_sec_params_reply(). */ +struct test_sec_params_reply_context { + ble_gap_sec_params_t *sec_params; + ble_gap_sec_params_t sec_params_mem; + bool params_reply_called; +}; + +static uint32_t stub_sd_ecb_block_encrypt_match_prand(nrf_ecb_hal_data_t *p_ecb_data, + int cmock_num_calls) +{ + ARG_UNUSED(cmock_num_calls); + + /* Echo cleartext prand bytes into ciphertext so pm_address_resolve() succeeds. */ + for (uint32_t i = 0; i < 3; i++) { + p_ecb_data->ciphertext[SOC_ECB_KEY_LENGTH - 1 - i] = + p_ecb_data->cleartext[SOC_ECB_KEY_LENGTH - 1 - i]; + } + + return NRF_SUCCESS; +} + void peer_manager_initialize_success(void) { uint32_t nrf_err; @@ -311,6 +387,214 @@ void peer_manager_initialize_success(void) __cmock_bm_timer_init_Stub(NULL); } +#define PM_EVT_CAPTURE_MAX 16 + +/* Helper context for modifying behavior of the on_pm_evt handler function. */ +static struct { + struct { + bool exclude_connection; + } conn_config_req; + struct { + bool allow_repairing; + } conn_sec_config_req; + struct { + bool enabled; + uint32_t count; + struct pm_evt events[PM_EVT_CAPTURE_MAX]; + } capture; +} on_pm_evt_test_ctx; + +static void on_pm_evt_capture_restart(void) +{ + on_pm_evt_test_ctx.capture.count = 0; + on_pm_evt_test_ctx.capture.enabled = true; +} + +static const struct pm_evt *on_pm_evt_find_last(enum pm_evt_id evt_id) +{ + const struct pm_evt *found = NULL; + + for (uint32_t i = 0; i < on_pm_evt_test_ctx.capture.count; i++) { + if (on_pm_evt_test_ctx.capture.events[i].evt_id == evt_id) { + found = &on_pm_evt_test_ctx.capture.events[i]; + } + } + + return found; +} + +static void on_pm_evt(const struct pm_evt *pm_evt) +{ + uint32_t nrf_err; + + TEST_ASSERT_NOT_NULL(pm_evt); + + switch (pm_evt->evt_id) { + case PM_EVT_CONN_CONFIG_REQ: + if (on_pm_evt_test_ctx.conn_config_req.exclude_connection) { + nrf_err = pm_conn_exclude(pm_evt->conn_handle, + pm_evt->conn_config_req.context); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + } + break; + case PM_EVT_CONN_SEC_CONFIG_REQ: + if (on_pm_evt_test_ctx.conn_sec_config_req.allow_repairing) { + struct pm_conn_sec_config sec_config = {.allow_repairing = true}; + + pm_conn_sec_config_reply(pm_evt->conn_handle, &sec_config); + } + break; + default: + break; + } + + if (on_pm_evt_test_ctx.capture.enabled && + (on_pm_evt_test_ctx.capture.count < PM_EVT_CAPTURE_MAX)) { + on_pm_evt_test_ctx.capture.events[on_pm_evt_test_ctx.capture.count++] = *pm_evt; + } +} + +/* Runs before test_pm_init_* (name sort order) while Peer Manager is still + * uninitialized. Checks that public APIs return NRF_ERROR_INVALID_STATE, or + * the documented special-case values, when called before pm_init(). + */ +void test_pm_0_module_not_initialized(void) +{ + uint32_t nrf_err; + uint16_t peer_id; + uint16_t conn_handle; + uint16_t next_peer_id; + uint16_t peer_list[1]; + uint32_t list_size = 1; + uint32_t peer_count; + uint32_t data_len = sizeof(uint32_t); + uint8_t data[4] __aligned(4); + ble_gap_addr_t addr; + ble_gap_sec_params_t sec_param; + struct pm_conn_sec_status sec_status; + struct pm_peer_data_bonding bonding_data; + struct ble_gatt_db_srv remote_db __aligned(4); + ble_gap_privacy_params_t privacy; + ble_gap_irk_t irk; + bool excluded; + bool address_resolved; + bool sec_sufficient; + + nrf_err = pm_register(on_pm_evt); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_sec_params_set(&sec_param); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_conn_secure(CONN_HANDLE_1, false); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_conn_sec_params_reply(CONN_HANDLE_1, &sec_param, &excluded); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_id_addr_set(&addr); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_id_addr_get(&addr); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_privacy_set(&privacy); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_privacy_get(&privacy); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_allow_list_set(NULL, 0); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_allow_list_get(&addr, &list_size, NULL, NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_device_identities_list_set(NULL, 0); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_conn_sec_status_get(CONN_HANDLE_1, &sec_status); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_lesc_public_key_set(NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + sec_sufficient = pm_sec_is_sufficient(CONN_HANDLE_1, &sec_status); + TEST_ASSERT_FALSE(sec_sufficient); + + address_resolved = pm_address_resolve(&addr, &irk); + TEST_ASSERT_FALSE(address_resolved); + + nrf_err = pm_conn_handle_get(0, &conn_handle); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_peer_id_get(CONN_HANDLE_1, &peer_id); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_peer_id_list(peer_list, &list_size, PM_PEER_ID_INVALID, + PM_PEER_ID_LIST_ALL_ID); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + peer_count = pm_peer_count(); + TEST_ASSERT_EQUAL(0, peer_count); + + next_peer_id = pm_next_peer_id_get(PM_PEER_ID_INVALID); + TEST_ASSERT_EQUAL(PM_PEER_ID_INVALID, next_peer_id); + + nrf_err = pm_peer_data_load(0, PM_PEER_DATA_ID_APPLICATION, data, &data_len); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_peer_data_bonding_load(0, &bonding_data); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_peer_data_remote_db_load(0, &remote_db, &data_len); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_peer_data_app_data_load(0, data, &data_len); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_peer_data_store(0, PM_PEER_DATA_ID_APPLICATION, data, data_len, NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_peer_data_bonding_store(0, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_peer_data_remote_db_store(0, &remote_db, sizeof(remote_db), NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_peer_data_app_data_store(0, data, sizeof(data), NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_peer_data_delete(0, PM_PEER_DATA_ID_APPLICATION); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_peer_new(&peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_peer_delete(0); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_peers_delete(); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + +#if defined(CONFIG_PM_PEER_RANKS) + nrf_err = pm_peer_ranks_get(&peer_id, &data_len, NULL, NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); + + nrf_err = pm_peer_rank_highest(0); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_STATE, nrf_err); +#else + nrf_err = pm_peer_ranks_get(&peer_id, &data_len, NULL, NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_NOT_SUPPORTED, nrf_err); + + nrf_err = pm_peer_rank_highest(0); + TEST_ASSERT_EQUAL(NRF_ERROR_NOT_SUPPORTED, nrf_err); +#endif + + /* No-op when not initialized; must not crash. */ + pm_local_database_has_changed(); +} + void test_pm_init_success(void) { uint32_t nrf_err; @@ -380,32 +664,6 @@ void test_pm_init_lesc_error(void) TEST_ASSERT_EQUAL(NRF_ERROR_INTERNAL, nrf_err); } -/* Helper context for modifying behavior of the on_pm_evt handler function. */ -static struct { - struct { - bool exclude_connection; - } conn_config_req; -} on_pm_evt_test_ctx; - -static void on_pm_evt(const struct pm_evt *pm_evt) -{ - uint32_t nrf_err; - - TEST_ASSERT_NOT_NULL(pm_evt); - - switch (pm_evt->evt_id) { - case PM_EVT_CONN_CONFIG_REQ: - if (on_pm_evt_test_ctx.conn_config_req.exclude_connection) { - nrf_err = pm_conn_exclude(pm_evt->conn_handle, - pm_evt->conn_config_req.context); - TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); - } - break; - default: - break; - } -} - void test_pm_register_success(void) { uint32_t nrf_err; @@ -740,10 +998,15 @@ void test_pm_conn_secure_peripheral_pair_success(void) .kdist_peer = {.enc = 0, .id = 0}, }; union pm_entry_id entry; + const struct pm_evt *pm_evt; ble_evt_t evt = {.evt.gap_evt.conn_handle = CONN_HANDLE_1}; peer_manager_initialize_success(); + nrf_err = pm_register(&on_pm_evt); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + on_pm_evt_capture_restart(); + nrf_err = pm_sec_params_set(&sec_param); TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); @@ -771,12 +1034,23 @@ void test_pm_conn_secure_peripheral_pair_success(void) sdh_evt_dispatch_ble(&evt); + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_CONFIG_REQ); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(CONN_HANDLE_1, pm_evt->conn_handle); + TEST_ASSERT_EQUAL(1, on_pm_evt_test_ctx.capture.count); + on_pm_evt_capture_restart(); + __cmock_sd_ble_gap_authenticate_ExpectWithArrayAndReturn(CONN_HANDLE_1, &sec_param, 1, NRF_SUCCESS); nrf_err = pm_conn_secure(CONN_HANDLE_1, false); TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_SEC_PARAMS_REQ); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(1, on_pm_evt_test_ctx.capture.count); + on_pm_evt_capture_restart(); + /* On a BLE_GAP_EVT_SEC_PARAMS_REQUEST event, expect a reply with the sec_param values that * were set during Peer Manager initialization/configuration. */ @@ -801,6 +1075,14 @@ void test_pm_conn_secure_peripheral_pair_success(void) sdh_evt_dispatch_ble(&evt); + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_SEC_START); + TEST_ASSERT_NOT_NULL(pm_evt); + + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_SEC_PARAMS_REQ); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(2, on_pm_evt_test_ctx.capture.count); + on_pm_evt_capture_restart(); + /* Here, the SoftDevice would send a BLE_GAP_EVT_AUTH_KEY_REQUEST event and it must be * handled by the application by invoking sd_ble_gap_auth_key_reply. * Peer Manager will not handle this event (except for an edge case with LESC and @@ -888,6 +1170,11 @@ void test_pm_conn_secure_peripheral_pair_success(void) sdh_evt_dispatch_ble(&evt); TEST_ASSERT_EQUAL(0, pm_peer_count()); + + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_SEC_SUCCEEDED); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(PM_CONN_SEC_PROCEDURE_PAIRING, pm_evt->conn_sec_succeeded.procedure); + TEST_ASSERT_EQUAL(1, on_pm_evt_test_ctx.capture.count); } void test_pm_conn_secure_peripheral_pair_and_bond_success(void) @@ -902,10 +1189,15 @@ void test_pm_conn_secure_peripheral_pair_and_bond_success(void) .kdist_peer = {.enc = 1, .id = 1}, }; union pm_entry_id entry; + const struct pm_evt *pm_evt; ble_evt_t evt = {.evt.gap_evt.conn_handle = CONN_HANDLE_1}; peer_manager_initialize_success(); + nrf_err = pm_register(&on_pm_evt); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + on_pm_evt_capture_restart(); + nrf_err = pm_sec_params_set(&sec_param); TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); @@ -933,12 +1225,23 @@ void test_pm_conn_secure_peripheral_pair_and_bond_success(void) sdh_evt_dispatch_ble(&evt); + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_CONFIG_REQ); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(CONN_HANDLE_1, pm_evt->conn_handle); + TEST_ASSERT_EQUAL(1, on_pm_evt_test_ctx.capture.count); + on_pm_evt_capture_restart(); + __cmock_sd_ble_gap_authenticate_ExpectWithArrayAndReturn(CONN_HANDLE_1, &sec_param, 1, NRF_SUCCESS); nrf_err = pm_conn_secure(CONN_HANDLE_1, false); TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_SEC_PARAMS_REQ); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(1, on_pm_evt_test_ctx.capture.count); + on_pm_evt_capture_restart(); + /* On a BLE_GAP_EVT_SEC_PARAMS_REQUEST event, expect a reply with the sec_param values that * were set during Peer Manager initialization/configuration. */ @@ -963,6 +1266,14 @@ void test_pm_conn_secure_peripheral_pair_and_bond_success(void) sdh_evt_dispatch_ble(&evt); + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_SEC_START); + TEST_ASSERT_NOT_NULL(pm_evt); + + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_SEC_PARAMS_REQ); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(2, on_pm_evt_test_ctx.capture.count); + on_pm_evt_capture_restart(); + /* Here, the SoftDevice would send a BLE_GAP_EVT_AUTH_KEY_REQUEST event and it must be * handled by the application by invoking sd_ble_gap_auth_key_reply. * Peer Manager will not handle this event (except for an edge case with LESC and @@ -1077,6 +1388,11 @@ void test_pm_conn_secure_peripheral_pair_and_bond_success(void) TEST_ASSERT_TRUE(conn_sec_status.encrypted); TEST_ASSERT_TRUE(conn_sec_status.mitm_protected); TEST_ASSERT_TRUE(conn_sec_status.lesc); + + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_SEC_SUCCEEDED); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(PM_CONN_SEC_PROCEDURE_BONDING, pm_evt->conn_sec_succeeded.procedure); + TEST_ASSERT_EQUAL(1, on_pm_evt_test_ctx.capture.count); } void test_pm_conn_secure_peripheral_with_bonding_data_success(void) @@ -1091,6 +1407,7 @@ void test_pm_conn_secure_peripheral_with_bonding_data_success(void) .kdist_peer = {.enc = 1, .id = 1}, }; union pm_entry_id entry; + const struct pm_evt *pm_evt; ble_evt_t evt = {.evt.gap_evt.conn_handle = CONN_HANDLE_1}; const struct pm_peer_data_bonding bonding_data = { .own_role = BLE_GAP_ROLE_PERIPH, @@ -1101,6 +1418,10 @@ void test_pm_conn_secure_peripheral_with_bonding_data_success(void) peer_manager_initialize_success(); + nrf_err = pm_register(&on_pm_evt); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + on_pm_evt_capture_restart(); + nrf_err = pm_sec_params_set(&sec_param); TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); @@ -1159,6 +1480,18 @@ void test_pm_conn_secure_peripheral_with_bonding_data_success(void) sdh_evt_dispatch_ble(&evt); + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_CONFIG_REQ); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(CONN_HANDLE_1, pm_evt->conn_handle); + + pm_evt = on_pm_evt_find_last(PM_EVT_BONDED_PEER_CONNECTED); + TEST_ASSERT_NOT_NULL(pm_evt); + + pm_evt = on_pm_evt_find_last(PM_EVT_LOCAL_DB_CACHE_APPLIED); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(3, on_pm_evt_test_ctx.capture.count); + on_pm_evt_capture_restart(); + /* On a BLE_GAP_EVT_SEC_INFO_REQUEST event, expect the bonding data storage to be iterated * in search for a matching master ID. None found. Peer ID is found based on conn_handle. * Next, expect a read of the stored bonding data based on the peer ID. @@ -1202,24 +1535,39 @@ void test_pm_conn_secure_peripheral_with_bonding_data_success(void) }; sdh_evt_dispatch_ble(&evt); + + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_SEC_START); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(1, on_pm_evt_test_ctx.capture.count); } -void test_pm_conn_secure_central_pair_success(void) +void test_pm_conn_secure_peripheral_pair_again_with_bonding_data_present_success(void) { uint32_t nrf_err; ble_gap_sec_params_t sec_param = { - .bond = 0, .mitm = 1, .lesc = 1, .keypress = 1, + .bond = 1, .mitm = 1, .lesc = 1, .keypress = 0, .io_caps = BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY, .oob = 0, .min_key_size = 7, .max_key_size = 16, - .kdist_own = {.enc = 0, .id = 0}, - .kdist_peer = {.enc = 0, .id = 0}, + .kdist_own = {.enc = 1, .id = 1}, + .kdist_peer = {.enc = 1, .id = 1}, }; union pm_entry_id entry; + const struct pm_evt *pm_evt; ble_evt_t evt = {.evt.gap_evt.conn_handle = CONN_HANDLE_1}; + const struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + .own_ltk = LTK_1, + }; + union local_gatt_db_with_data local_gatt_db = STORED_GATT_DATA_1; peer_manager_initialize_success(); + nrf_err = pm_register(&on_pm_evt); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + on_pm_evt_capture_restart(); + nrf_err = pm_sec_params_set(&sec_param); TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); @@ -1227,35 +1575,269 @@ void test_pm_conn_secure_central_pair_success(void) __cmock_nrf_sdh_ble_conn_handle_get_Stub(stub_nrf_sdh_ble_conn_handle_get); /* Expect the bonding data storage to be iterated to find out if the connected peer have - * previously bonded. However, no bonding data is found for a peer with the given address. + * previously bonded and if so, what peer ID is associated with the peer. */ entry.data_id = PM_PEER_DATA_ID_BONDING; entry.peer_id = 0; - for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { - __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, - PM_PEER_DATA_MAX_SIZE, -ENOENT); - __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + entry.peer_id++; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, PM_PEER_DATA_MAX_SIZE); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data((void *)&bonding_data, + sizeof(struct pm_peer_data_bonding)); - entry.peer_id++; - } + /* Expect a storage read looking for local gatt data for the connected peer, + * followed by a restoration of the local gatt database in the SoftDevice. + */ + entry.data_id = PM_PEER_DATA_ID_GATT_LOCAL; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, PM_PEER_DATA_MAX_SIZE); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data((void *)&local_gatt_db, sizeof(local_gatt_db)); + + __cmock_sd_ble_gatts_sys_attr_set_ExpectAndReturn(CONN_HANDLE_1, local_gatt_db.data.data, + local_gatt_db.data.len, + local_gatt_db.data.flags, NRF_SUCCESS); + + /* Expect a storage read looking for service changed state for the connected peer. */ + entry.data_id = PM_PEER_DATA_ID_SERVICE_CHANGED_PENDING; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + sizeof(bool), PM_PEER_DATA_MAX_SIZE); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data((void *)&(bool){0}, sizeof(bool)); + + /* Expect a storage read looking for central address resolution data for the connected + * peer. + */ + entry.data_id = PM_PEER_DATA_ID_CENTRAL_ADDR_RES; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + sizeof(uint32_t), PM_PEER_DATA_MAX_SIZE); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data((void *)&(uint32_t){1}, sizeof(uint32_t)); evt.header.evt_id = BLE_GAP_EVT_CONNECTED; evt.evt.gap_evt.params.connected = (ble_gap_evt_connected_t) { .peer_addr = ADDRESS_PUBLIC_1, - .role = BLE_GAP_ROLE_CENTRAL, + .role = BLE_GAP_ROLE_PERIPH, }; sdh_evt_dispatch_ble(&evt); - __cmock_sd_ble_gap_authenticate_ExpectWithArrayAndReturn(CONN_HANDLE_1, &sec_param, 1, - NRF_SUCCESS); + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_CONFIG_REQ); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(CONN_HANDLE_1, pm_evt->conn_handle); - nrf_err = pm_conn_secure(CONN_HANDLE_1, false); - TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + pm_evt = on_pm_evt_find_last(PM_EVT_BONDED_PEER_CONNECTED); + TEST_ASSERT_NOT_NULL(pm_evt); - /* On a BLE_GAP_EVT_SEC_PARAMS_REQUEST event, expect a reply with sec_param == NULL. - * The central's sec_params was passed to the SoftDevice with the call to - * sd_ble_gap_authenticate() so no reason to repeat it. + pm_evt = on_pm_evt_find_last(PM_EVT_LOCAL_DB_CACHE_APPLIED); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(3, on_pm_evt_test_ctx.capture.count); + on_pm_evt_capture_restart(); + + /* On a BLE_GAP_EVT_SEC_PARAMS_REQUEST event with a bonded peer, expect + * PM_EVT_CONN_SEC_CONFIG_REQ and allow repairing, then reply with the configured sec_param + * values to start re-pairing. + */ + __cmock_sd_ble_gap_sec_params_reply_ExpectWithArrayAndReturn(CONN_HANDLE_1, + BLE_GAP_SEC_STATUS_SUCCESS, + &sec_param, 1, PTR_IGNORE, 0, + NRF_SUCCESS); + __cmock_sd_ble_gap_sec_params_reply_IgnoreArg_p_sec_keyset(); + + evt.header.evt_id = BLE_GAP_EVT_SEC_PARAMS_REQUEST; + evt.evt.gap_evt.params.sec_params_request = (ble_gap_evt_sec_params_request_t) { + .peer_params = { + /* Security parameters for central (peer) side. */ + .bond = 0, .mitm = 1, .lesc = 1, .keypress = 1, + .io_caps = BLE_GAP_IO_CAPS_DISPLAY_ONLY, .oob = 1, + + .min_key_size = 7, .max_key_size = 16, + .kdist_own = {.enc = 1, .id = 1}, + .kdist_peer = {.enc = 1, .id = 1}, + }, + }; + + on_pm_evt_test_ctx.conn_sec_config_req.allow_repairing = true; + + sdh_evt_dispatch_ble(&evt); + + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_SEC_CONFIG_REQ); + TEST_ASSERT_NOT_NULL(pm_evt); + + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_SEC_PARAMS_REQ); + TEST_ASSERT_NOT_NULL(pm_evt); + + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_SEC_START); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(3, on_pm_evt_test_ctx.capture.count); + on_pm_evt_capture_restart(); + + /* Here, the SoftDevice would send a BLE_GAP_EVT_AUTH_KEY_REQUEST event and it must be + * handled by the application by invoking sd_ble_gap_auth_key_reply. + * Peer Manager will not handle this event (except for an edge case with LESC and + * identical keys), so nothing to test here. + */ + + /* On a BLE_GAP_EVT_LESC_DHKEY_REQUEST event, a flag is set to defer the computation + * of the DH key (key agreement) to the nrf_ble_lesc_request_handler() function and have + * that reply with sd_ble_gap_lesc_dhkey_reply() at a later time (usually from main loop). + */ + ble_gap_lesc_p256_pk_t peer_pk; + + memcpy(peer_pk.pk, peer_test_pub_key_sd, BLE_GAP_LESC_P256_PK_LEN); + + evt.header.evt_id = BLE_GAP_EVT_LESC_DHKEY_REQUEST; + evt.evt.gap_evt.params.lesc_dhkey_request = (ble_gap_evt_lesc_dhkey_request_t) { + .p_pk_peer = &peer_pk, .oobd_req = 0, + }; + + sdh_evt_dispatch_ble(&evt); + + /* Expect a call to nrf_ble_lesc_request_handler() to compute DH key (key agreement) + * with psa_raw_key_agreement() and reply to the SoftDevice with + * sd_ble_gap_lesc_dhkey_reply() to complete the BLE_GAP_EVT_LESC_DHKEY_REQUEST request. + */ + __cmock_psa_raw_key_agreement_ExpectWithArrayAndReturn( + PSA_ALG_ECDH, key_pair_id, + peer_test_pub_key_psa, sizeof(peer_test_pub_key_psa), sizeof(peer_test_pub_key_psa), + PTR_IGNORE, 0, sizeof(test_secret_psa), PTR_IGNORE, 0, PSA_SUCCESS); + __cmock_psa_raw_key_agreement_IgnoreArg_output(); + __cmock_psa_raw_key_agreement_IgnoreArg_output_length(); + __cmock_psa_raw_key_agreement_ReturnMemThruPtr_output( + test_secret_psa, sizeof(test_secret_psa)); + __cmock_psa_raw_key_agreement_ReturnMemThruPtr_output_length( + &(size_t){sizeof(test_secret_psa)}, sizeof(size_t)); + + ble_gap_lesc_dhkey_t secret; + + memcpy(secret.key, test_secret_sd, sizeof(test_secret_sd)); + __cmock_sd_ble_gap_lesc_dhkey_reply_ExpectWithArrayAndReturn( + CONN_HANDLE_1, BLE_GAP_SEC_STATUS_SUCCESS, &secret, 1, NRF_SUCCESS); + + nrf_err = nrf_ble_lesc_request_handler(); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + /* On a BLE_GAP_EVT_CONN_SEC_UPDATE, expect the connection security state to change. */ + evt.header.evt_id = BLE_GAP_EVT_CONN_SEC_UPDATE; + evt.evt.gap_evt.params.conn_sec_update = (ble_gap_evt_conn_sec_update_t) { + .conn_sec.sec_mode = {.sm = 1, .lv = 4}, + .conn_sec.encr_key_size = 16, + }; + struct pm_conn_sec_status conn_sec_status; + + nrf_err = pm_conn_sec_status_get(CONN_HANDLE_1, &conn_sec_status); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_TRUE(conn_sec_status.connected); + TEST_ASSERT_TRUE(conn_sec_status.bonded); + TEST_ASSERT_FALSE(conn_sec_status.encrypted); + TEST_ASSERT_FALSE(conn_sec_status.mitm_protected); + TEST_ASSERT_FALSE(conn_sec_status.lesc); + + sdh_evt_dispatch_ble(&evt); + + nrf_err = pm_conn_sec_status_get(CONN_HANDLE_1, &conn_sec_status); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_TRUE(conn_sec_status.connected); + TEST_ASSERT_TRUE(conn_sec_status.bonded); + TEST_ASSERT_TRUE(conn_sec_status.encrypted); + TEST_ASSERT_TRUE(conn_sec_status.mitm_protected); + TEST_ASSERT_TRUE(conn_sec_status.lesc); + + /* On a BLE_GAP_EVT_AUTH_STATUS, expect peer manager to finish up the pairing request. + * Not much is done in response to this event when simply pairing (no bonding). + */ + evt.header.evt_id = BLE_GAP_EVT_AUTH_STATUS; + evt.evt.gap_evt.params.auth_status = (ble_gap_evt_auth_status_t) { + .auth_status = BLE_GAP_SEC_STATUS_SUCCESS, + .bonded = false, + .lesc = true, + .sm1_levels = {.lv1 = 1, .lv2 = 1, .lv3 = 1, .lv4 = 1}, + .kdist_own = {.enc = 0, .id = 0}, + .kdist_peer = {.enc = 0, .id = 0}, + }; + + sdh_evt_dispatch_ble(&evt); + + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_SEC_SUCCEEDED); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(PM_CONN_SEC_PROCEDURE_PAIRING, pm_evt->conn_sec_succeeded.procedure); + TEST_ASSERT_EQUAL(1, on_pm_evt_test_ctx.capture.count); +} +void test_pm_conn_secure_central_pair_success(void) +{ + uint32_t nrf_err; + ble_gap_sec_params_t sec_param = { + .bond = 0, .mitm = 1, .lesc = 1, .keypress = 1, + .io_caps = BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY, .oob = 0, + + .min_key_size = 7, .max_key_size = 16, + .kdist_own = {.enc = 0, .id = 0}, + .kdist_peer = {.enc = 0, .id = 0}, + }; + union pm_entry_id entry; + const struct pm_evt *pm_evt; + ble_evt_t evt = {.evt.gap_evt.conn_handle = CONN_HANDLE_1}; + + peer_manager_initialize_success(); + + nrf_err = pm_register(&on_pm_evt); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + on_pm_evt_capture_restart(); + + nrf_err = pm_sec_params_set(&sec_param); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + __cmock_nrf_sdh_ble_idx_get_Stub(stub_nrf_sdh_ble_idx_get); + __cmock_nrf_sdh_ble_conn_handle_get_Stub(stub_nrf_sdh_ble_conn_handle_get); + + /* Expect the bonding data storage to be iterated to find out if the connected peer have + * previously bonded. However, no bonding data is found for a peer with the given address. + */ + entry.data_id = PM_PEER_DATA_ID_BONDING; + entry.peer_id = 0; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + + entry.peer_id++; + } + + evt.header.evt_id = BLE_GAP_EVT_CONNECTED; + evt.evt.gap_evt.params.connected = (ble_gap_evt_connected_t) { + .peer_addr = ADDRESS_PUBLIC_1, + .role = BLE_GAP_ROLE_CENTRAL, + }; + + sdh_evt_dispatch_ble(&evt); + + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_CONFIG_REQ); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(CONN_HANDLE_1, pm_evt->conn_handle); + TEST_ASSERT_EQUAL(1, on_pm_evt_test_ctx.capture.count); + on_pm_evt_capture_restart(); + + __cmock_sd_ble_gap_authenticate_ExpectWithArrayAndReturn(CONN_HANDLE_1, &sec_param, 1, + NRF_SUCCESS); + + nrf_err = pm_conn_secure(CONN_HANDLE_1, false); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_SEC_PARAMS_REQ); + TEST_ASSERT_NOT_NULL(pm_evt); + + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_SEC_START); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(2, on_pm_evt_test_ctx.capture.count); + on_pm_evt_capture_restart(); + + /* On a BLE_GAP_EVT_SEC_PARAMS_REQUEST event, expect a reply with sec_param == NULL. + * The central's sec_params was passed to the SoftDevice with the call to + * sd_ble_gap_authenticate() so no reason to repeat it. */ __cmock_sd_ble_gap_sec_params_reply_ExpectWithArrayAndReturn(CONN_HANDLE_1, BLE_GAP_SEC_STATUS_SUCCESS, @@ -1278,6 +1860,11 @@ void test_pm_conn_secure_central_pair_success(void) sdh_evt_dispatch_ble(&evt); + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_SEC_PARAMS_REQ); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(1, on_pm_evt_test_ctx.capture.count); + on_pm_evt_capture_restart(); + /* Here, the SoftDevice would send a BLE_GAP_EVT_AUTH_KEY_REQUEST event and it must be * handled by the application by invoking sd_ble_gap_auth_key_reply. * Peer Manager will not handle this event (except for an edge case with LESC and @@ -1365,6 +1952,11 @@ void test_pm_conn_secure_central_pair_success(void) sdh_evt_dispatch_ble(&evt); TEST_ASSERT_EQUAL(0, pm_peer_count()); + + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_SEC_SUCCEEDED); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(PM_CONN_SEC_PROCEDURE_PAIRING, pm_evt->conn_sec_succeeded.procedure); + TEST_ASSERT_EQUAL(1, on_pm_evt_test_ctx.capture.count); } void test_pm_conn_secure_central_pair_and_bond_success(void) @@ -1379,10 +1971,15 @@ void test_pm_conn_secure_central_pair_and_bond_success(void) .kdist_peer = {.enc = 1, .id = 1}, }; union pm_entry_id entry; + const struct pm_evt *pm_evt; ble_evt_t evt = {.evt.gap_evt.conn_handle = CONN_HANDLE_1}; peer_manager_initialize_success(); + nrf_err = pm_register(&on_pm_evt); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + on_pm_evt_capture_restart(); + nrf_err = pm_sec_params_set(&sec_param); TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); @@ -1410,12 +2007,26 @@ void test_pm_conn_secure_central_pair_and_bond_success(void) sdh_evt_dispatch_ble(&evt); + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_CONFIG_REQ); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(CONN_HANDLE_1, pm_evt->conn_handle); + TEST_ASSERT_EQUAL(1, on_pm_evt_test_ctx.capture.count); + on_pm_evt_capture_restart(); + __cmock_sd_ble_gap_authenticate_ExpectWithArrayAndReturn(CONN_HANDLE_1, &sec_param, 1, NRF_SUCCESS); nrf_err = pm_conn_secure(CONN_HANDLE_1, false); TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_SEC_PARAMS_REQ); + TEST_ASSERT_NOT_NULL(pm_evt); + + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_SEC_START); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(2, on_pm_evt_test_ctx.capture.count); + on_pm_evt_capture_restart(); + /* On a BLE_GAP_EVT_SEC_PARAMS_REQUEST event, expect a reply with sec_param == NULL. * The central's sec_params was passed to the SoftDevice with the call to * sd_ble_gap_authenticate() so no reason to repeat it. @@ -1441,6 +2052,11 @@ void test_pm_conn_secure_central_pair_and_bond_success(void) sdh_evt_dispatch_ble(&evt); + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_SEC_PARAMS_REQ); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(1, on_pm_evt_test_ctx.capture.count); + on_pm_evt_capture_restart(); + /* Here, the SoftDevice would send a BLE_GAP_EVT_AUTH_KEY_REQUEST event and it must be * handled by the application by invoking sd_ble_gap_auth_key_reply. * Peer Manager will not handle this event (except for an edge case with LESC and @@ -1528,47 +2144,2002 @@ void test_pm_conn_secure_central_pair_and_bond_success(void) sdh_evt_dispatch_ble(&evt); TEST_ASSERT_EQUAL(0, pm_peer_count()); + + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_SEC_SUCCEEDED); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(PM_CONN_SEC_PROCEDURE_PAIRING, pm_evt->conn_sec_succeeded.procedure); + TEST_ASSERT_EQUAL(1, on_pm_evt_test_ctx.capture.count); } -void test_pm_conn_exclude_success(void) +void test_pm_conn_secure_central_with_bonding_data_success(void) { uint32_t nrf_err; + ble_gap_sec_params_t sec_param = { + .bond = 1, .mitm = 1, .lesc = 0, .keypress = 0, + .io_caps = BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY, .oob = 0, + .min_key_size = 7, .max_key_size = 16, + .kdist_own = {.enc = 1, .id = 1}, + .kdist_peer = {.enc = 1, .id = 1}, + }; + union pm_entry_id entry; + const struct pm_evt *pm_evt; + ble_evt_t evt = {.evt.gap_evt.conn_handle = CONN_HANDLE_1}; + const struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_CENTRAL, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + .peer_ltk = LTK_1, + }; + union local_gatt_db_with_data local_gatt_db = STORED_GATT_DATA_1; peer_manager_initialize_success(); - __cmock_nrf_sdh_ble_idx_get_Stub(stub_nrf_sdh_ble_idx_get); - - nrf_err = pm_register(on_pm_evt); + nrf_err = pm_register(&on_pm_evt); TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + on_pm_evt_capture_restart(); - on_pm_evt_test_ctx.conn_config_req.exclude_connection = true; + nrf_err = pm_sec_params_set(&sec_param); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); - const ble_evt_t evt = { - .header.evt_id = BLE_GAP_EVT_CONNECTED, - .evt.gap_evt = { - .conn_handle = CONN_HANDLE_1, - .params.connected = { - .peer_addr = ADDRESS_PUBLIC_1, - .role = BLE_GAP_ROLE_PERIPH, - }, - }, - }; + __cmock_nrf_sdh_ble_idx_get_Stub(stub_nrf_sdh_ble_idx_get); + __cmock_nrf_sdh_ble_conn_handle_get_Stub(stub_nrf_sdh_ble_conn_handle_get); - /* Expect peer_manager to ignore the connection handle. - * No more function calls are expected in response to the connected event. - */ - sdh_evt_dispatch_ble(&evt); -} + /* Find stored bonding for the connected peer address (peer id 1). */ + entry.data_id = PM_PEER_DATA_ID_BONDING; + entry.peer_id = 0; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + entry.peer_id = 1; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, PM_PEER_DATA_MAX_SIZE); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data((void *)&bonding_data, + sizeof(struct pm_peer_data_bonding)); -void test_pm_conn_exclude_null(void) -{ - uint32_t nrf_err; + /* PM_EVT_BONDED_PEER_CONNECTED restores local GATT data and checks SC/CAR state. */ + entry.data_id = PM_PEER_DATA_ID_GATT_LOCAL; + entry.peer_id = 1; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, PM_PEER_DATA_MAX_SIZE); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data((void *)&local_gatt_db, sizeof(local_gatt_db)); - peer_manager_initialize_success(); + __cmock_sd_ble_gatts_sys_attr_set_ExpectAndReturn(CONN_HANDLE_1, local_gatt_db.data.data, + local_gatt_db.data.len, + local_gatt_db.data.flags, NRF_SUCCESS); - nrf_err = pm_conn_exclude(CONN_HANDLE_1, NULL); - TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); -} + entry.data_id = PM_PEER_DATA_ID_SERVICE_CHANGED_PENDING; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + sizeof(bool), PM_PEER_DATA_MAX_SIZE); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data((void *)&(bool){0}, sizeof(bool)); + + entry.data_id = PM_PEER_DATA_ID_CENTRAL_ADDR_RES; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + sizeof(uint32_t), PM_PEER_DATA_MAX_SIZE); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data((void *)&(uint32_t){1}, sizeof(uint32_t)); + + evt.header.evt_id = BLE_GAP_EVT_CONNECTED; + evt.evt.gap_evt.params.connected = (ble_gap_evt_connected_t) { + .peer_addr = ADDRESS_PUBLIC_1, + .role = BLE_GAP_ROLE_CENTRAL, + }; + + sdh_evt_dispatch_ble(&evt); + + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_CONFIG_REQ); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(CONN_HANDLE_1, pm_evt->conn_handle); + + pm_evt = on_pm_evt_find_last(PM_EVT_BONDED_PEER_CONNECTED); + TEST_ASSERT_NOT_NULL(pm_evt); + + pm_evt = on_pm_evt_find_last(PM_EVT_LOCAL_DB_CACHE_APPLIED); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(3, on_pm_evt_test_ctx.capture.count); + on_pm_evt_capture_restart(); + + /* Central re-encrypts with stored peer LTK (own_ltk.lesc is zero). */ + entry.data_id = PM_PEER_DATA_ID_BONDING; + entry.peer_id = 1; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + sizeof(struct pm_peer_data_bonding), + sizeof(bonding_data)); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data((void *)&bonding_data, + sizeof(bonding_data)); + + __cmock_sd_ble_gap_encrypt_ExpectWithArrayAndReturn( + CONN_HANDLE_1, &bonding_data.peer_ltk.master_id, 1, + &bonding_data.peer_ltk.enc_info, 1, NRF_SUCCESS); + + nrf_err = pm_conn_secure(CONN_HANDLE_1, false); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_SEC_PARAMS_REQ); + TEST_ASSERT_NOT_NULL(pm_evt); + + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_SEC_START); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(2, on_pm_evt_test_ctx.capture.count); +} + +void test_pm_conn_exclude_success(void) +{ + uint32_t nrf_err; + const struct pm_evt *pm_evt; + + peer_manager_initialize_success(); + + __cmock_nrf_sdh_ble_idx_get_Stub(stub_nrf_sdh_ble_idx_get); + + nrf_err = pm_register(on_pm_evt); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + on_pm_evt_capture_restart(); + + on_pm_evt_test_ctx.conn_config_req.exclude_connection = true; + + const ble_evt_t evt = { + .header.evt_id = BLE_GAP_EVT_CONNECTED, + .evt.gap_evt = { + .conn_handle = CONN_HANDLE_1, + .params.connected = { + .peer_addr = ADDRESS_PUBLIC_1, + .role = BLE_GAP_ROLE_PERIPH, + }, + }, + }; + + /* Expect peer_manager to ignore the connection handle. + * No more function calls are expected in response to the connected event. + */ + sdh_evt_dispatch_ble(&evt); + + pm_evt = on_pm_evt_find_last(PM_EVT_CONN_CONFIG_REQ); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(CONN_HANDLE_1, pm_evt->conn_handle); + + pm_evt = on_pm_evt_find_last(PM_EVT_BONDED_PEER_CONNECTED); + TEST_ASSERT_NULL(pm_evt); + TEST_ASSERT_EQUAL(1, on_pm_evt_test_ctx.capture.count); +} + +void test_pm_conn_exclude_null(void) +{ + uint32_t nrf_err; + + peer_manager_initialize_success(); + + nrf_err = pm_conn_exclude(CONN_HANDLE_1, NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); +} + +void test_pm_conn_sec_config_reply_success(void) +{ + struct pm_conn_sec_config sec_config = {.allow_repairing = true}; + + peer_manager_initialize_success(); + + __cmock_nrf_sdh_ble_idx_get_Stub(stub_nrf_sdh_ble_idx_get); + + pm_conn_sec_config_reply(CONN_HANDLE_1, &sec_config); +} + +void test_pm_conn_sec_config_reply_null(void) +{ + peer_manager_initialize_success(); + + /* NULL config is a no-op. */ + pm_conn_sec_config_reply(CONN_HANDLE_1, NULL); +} + +void test_pm_conn_sec_params_reply_success(void) +{ + uint32_t nrf_err; + struct test_sec_params_reply_context context = {0}; + ble_gap_sec_params_t sec_param = { + .bond = 1, .mitm = 1, .lesc = 1, .keypress = 0, + .io_caps = BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY, .oob = 1, + .min_key_size = 7, .max_key_size = 16, + .kdist_own = {.enc = 1, .id = 1}, + .kdist_peer = {.enc = 1, .id = 1}, + }; + + peer_manager_initialize_success(); + + nrf_err = pm_conn_sec_params_reply(CONN_HANDLE_1, &sec_param, &context); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_TRUE(context.params_reply_called); + TEST_ASSERT_EQUAL(1, context.sec_params_mem.bond); +} + +void test_pm_conn_sec_params_reply_null_context(void) +{ + uint32_t nrf_err; + ble_gap_sec_params_t sec_param = { + .bond = 1, .mitm = 1, .lesc = 1, .keypress = 0, + .io_caps = BLE_GAP_IO_CAPS_KEYBOARD_DISPLAY, .oob = 1, + .min_key_size = 7, .max_key_size = 16, + .kdist_own = {.enc = 1, .id = 1}, + .kdist_peer = {.enc = 1, .id = 1}, + }; + + peer_manager_initialize_success(); + + nrf_err = pm_conn_sec_params_reply(CONN_HANDLE_1, &sec_param, NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); +} + +void test_pm_conn_sec_status_get_null(void) +{ + uint32_t nrf_err; + + peer_manager_initialize_success(); + + nrf_err = pm_conn_sec_status_get(CONN_HANDLE_1, NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); +} + +void test_pm_conn_sec_status_get_invalid_conn_handle(void) +{ + uint32_t nrf_err; + struct pm_conn_sec_status conn_sec_status; + + peer_manager_initialize_success(); + + __cmock_nrf_sdh_ble_idx_get_ExpectAndReturn(BLE_CONN_HANDLE_INVALID, -1); + + nrf_err = pm_conn_sec_status_get(BLE_CONN_HANDLE_INVALID, &conn_sec_status); + TEST_ASSERT_EQUAL(BLE_ERROR_INVALID_CONN_HANDLE, nrf_err); + + +} + +void test_pm_sec_is_sufficient_invalid_conn_handle(void) +{ + bool sec_sufficient; + struct pm_conn_sec_status sec_status_req = {0}; + + peer_manager_initialize_success(); + + __cmock_nrf_sdh_ble_idx_get_ExpectAndReturn(BLE_CONN_HANDLE_INVALID, -1); + + sec_sufficient = pm_sec_is_sufficient(BLE_CONN_HANDLE_INVALID, &sec_status_req); + TEST_ASSERT_FALSE(sec_sufficient); +} + +void test_pm_sec_is_sufficient_connected_unencrypted(void) +{ + bool sec_sufficient; + union pm_entry_id entry; + const ble_evt_t evt = { + .header.evt_id = BLE_GAP_EVT_CONNECTED, + .evt.gap_evt = { + .conn_handle = CONN_HANDLE_1, + .params.connected = { + .peer_addr = ADDRESS_PUBLIC_1, + .role = BLE_GAP_ROLE_PERIPH, + }, + }, + }; + struct pm_conn_sec_status sec_status_req = {.connected = true, .encrypted = true}; + + peer_manager_initialize_success(); + + __cmock_nrf_sdh_ble_idx_get_Stub(stub_nrf_sdh_ble_idx_get); + __cmock_nrf_sdh_ble_conn_handle_get_Stub(stub_nrf_sdh_ble_conn_handle_get); + + entry.data_id = PM_PEER_DATA_ID_BONDING; + entry.peer_id = 0; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + entry.peer_id++; + } + + sdh_evt_dispatch_ble(&evt); + + sec_sufficient = pm_sec_is_sufficient(CONN_HANDLE_1, &sec_status_req); + TEST_ASSERT_FALSE(sec_sufficient); + + sec_status_req.encrypted = false; + + sec_sufficient = pm_sec_is_sufficient(CONN_HANDLE_1, &sec_status_req); + TEST_ASSERT_TRUE(sec_sufficient); +} + +void test_pm_lesc_public_key_set_forbidden(void) +{ + uint32_t nrf_err; + ble_gap_lesc_p256_pk_t public_key = {0}; + + peer_manager_initialize_success(); + + nrf_err = pm_lesc_public_key_set(&public_key); + TEST_ASSERT_EQUAL(NRF_ERROR_FORBIDDEN, nrf_err); +} + +void test_pm_allow_list_clear(void) +{ + uint32_t nrf_err; + + peer_manager_initialize_success(); + + __cmock_sd_ble_gap_whitelist_set_ExpectAndReturn(NULL, 0, NRF_SUCCESS); + + nrf_err = pm_allow_list_set(NULL, 0); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +void test_pm_allow_list_get_empty(void) +{ + uint32_t nrf_err; + ble_gap_addr_t addrs[BLE_GAP_WHITELIST_ADDR_MAX_COUNT]; + uint32_t addr_cnt = ARRAY_SIZE(addrs); + + peer_manager_initialize_success(); + + nrf_err = pm_allow_list_get(addrs, &addr_cnt, NULL, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(0, addr_cnt); +} + +void test_pm_allow_list_get_null_buffers(void) +{ + uint32_t nrf_err; + + peer_manager_initialize_success(); + + nrf_err = pm_allow_list_get(NULL, NULL, NULL, NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); +} + +void test_pm_device_identities_list_clear(void) +{ + uint32_t nrf_err; + + peer_manager_initialize_success(); + + __cmock_sd_ble_gap_device_identities_set_ExpectAndReturn(NULL, NULL, 0, NRF_SUCCESS); + + nrf_err = pm_device_identities_list_set(NULL, 0); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +void test_pm_id_addr_get_null(void) +{ + uint32_t nrf_err; + + peer_manager_initialize_success(); + + nrf_err = pm_id_addr_get(NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); +} + +void test_pm_id_addr_set_get_success(void) +{ + uint32_t nrf_err; + ble_gap_addr_t addr_set = ADDRESS_PUBLIC_1; + ble_gap_addr_t addr_get; + + peer_manager_initialize_success(); + + __cmock_sd_ble_gap_addr_set_ExpectWithArrayAndReturn(&addr_set, 1, NRF_SUCCESS); + nrf_err = pm_id_addr_set(&addr_set); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + __cmock_sd_ble_gap_addr_get_ExpectAndReturn(&addr_get, NRF_SUCCESS); + __cmock_sd_ble_gap_addr_get_IgnoreArg_p_addr(); + __cmock_sd_ble_gap_addr_get_ReturnMemThruPtr_p_addr(&addr_set, sizeof(addr_set)); + + nrf_err = pm_id_addr_get(&addr_get); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL_MEMORY(&addr_set, &addr_get, sizeof(addr_set)); +} + +void test_pm_privacy_set_null(void) +{ + uint32_t nrf_err; + + peer_manager_initialize_success(); + + nrf_err = pm_privacy_set(NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); +} + +void test_pm_privacy_get_null(void) +{ + uint32_t nrf_err; + ble_gap_privacy_params_t privacy_params = {0}; + + peer_manager_initialize_success(); + + nrf_err = pm_privacy_get(&privacy_params); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); + + nrf_err = pm_privacy_get(NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); +} + +void test_pm_address_resolve_null(void) +{ + ble_gap_addr_t addr = ADDRESS_PUBLIC_1; + ble_gap_irk_t irk = {0}; + + peer_manager_initialize_success(); + + TEST_ASSERT_FALSE(pm_address_resolve(NULL, &irk)); + TEST_ASSERT_FALSE(pm_address_resolve(&addr, NULL)); +} + +void test_pm_address_resolve_wrong_addr_type(void) +{ + ble_gap_addr_t addr = ADDRESS_PUBLIC_1; + ble_gap_irk_t irk = {.irk = {1}}; + + peer_manager_initialize_success(); + + TEST_ASSERT_FALSE(pm_address_resolve(&addr, &irk)); +} + +void test_pm_address_resolve_success(void) +{ + const uint8_t prand[3] = {0x11, 0x22, 0x33}; + ble_gap_addr_t addr = { + .addr_type = BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE, + .addr = {prand[0], prand[1], prand[2], prand[0], prand[1], prand[2]}, + }; + ble_gap_irk_t irk = {.irk = {0x01, 0x02, 0x03, 0x04}}; + + peer_manager_initialize_success(); + + __cmock_sd_ecb_block_encrypt_Stub(stub_sd_ecb_block_encrypt_match_prand); + + TEST_ASSERT_TRUE(pm_address_resolve(&addr, &irk)); +} + +void test_pm_address_resolve_mismatch(void) +{ + const uint8_t prand[3] = {0x11, 0x22, 0x33}; + ble_gap_addr_t addr = { + .addr_type = BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE, + .addr = {0xAA, 0xBB, 0xCC, prand[0], prand[1], prand[2]}, + }; + ble_gap_irk_t irk = {.irk = {0x01, 0x02, 0x03, 0x04}}; + + peer_manager_initialize_success(); + + __cmock_sd_ecb_block_encrypt_Stub(stub_sd_ecb_block_encrypt_match_prand); + + TEST_ASSERT_FALSE(pm_address_resolve(&addr, &irk)); +} + +void test_pm_conn_handle_get_null(void) +{ + uint32_t nrf_err; + + peer_manager_initialize_success(); + + nrf_err = pm_conn_handle_get(0, NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); +} + +void test_pm_peer_id_get_null(void) +{ + uint32_t nrf_err; + + peer_manager_initialize_success(); + + nrf_err = pm_peer_id_get(CONN_HANDLE_1, NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); +} + +void test_pm_conn_handle_get_and_peer_id_get_not_connected(void) +{ + uint32_t nrf_err; + uint16_t conn_handle; + uint16_t peer_id; + + peer_manager_initialize_success(); + + /* Use a peer ID that is not associated with any connection. */ + nrf_err = pm_conn_handle_get(1, &conn_handle); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(BLE_CONN_HANDLE_INVALID, conn_handle); + + __cmock_nrf_sdh_ble_idx_get_ExpectAndReturn(CONN_HANDLE_1, -1); + + nrf_err = pm_peer_id_get(CONN_HANDLE_1, &peer_id); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(PM_PEER_ID_INVALID, peer_id); +} + +void test_pm_peer_count_empty(void) +{ + peer_manager_initialize_success(); + + TEST_ASSERT_EQUAL(0, pm_peer_count()); +} + +void test_pm_next_peer_id_get_empty(void) +{ + peer_manager_initialize_success(); + + TEST_ASSERT_EQUAL(PM_PEER_ID_INVALID, pm_next_peer_id_get(PM_PEER_ID_INVALID)); +} + +void test_pm_peer_id_list_null(void) +{ + uint32_t nrf_err; + uint32_t list_size = 1; + uint16_t peer_list[1]; + + peer_manager_initialize_success(); + + nrf_err = pm_peer_id_list(NULL, &list_size, PM_PEER_ID_INVALID, PM_PEER_ID_LIST_ALL_ID); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); + + nrf_err = pm_peer_id_list(peer_list, NULL, PM_PEER_ID_INVALID, PM_PEER_ID_LIST_ALL_ID); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); +} + +void test_pm_peer_id_list_empty(void) +{ + uint32_t nrf_err; + uint32_t list_size = 4; + uint16_t peer_list[4] = {0}; + + peer_manager_initialize_success(); + + nrf_err = pm_peer_id_list(peer_list, &list_size, PM_PEER_ID_INVALID, + PM_PEER_ID_LIST_ALL_ID); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(0, list_size); +} + +void test_pm_peer_id_list_invalid_param(void) +{ + uint32_t nrf_err; + uint32_t list_size = 0; + uint16_t peer_list[1]; + + peer_manager_initialize_success(); + + nrf_err = pm_peer_id_list(peer_list, &list_size, PM_PEER_ID_INVALID, + PM_PEER_ID_LIST_ALL_ID); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_PARAM, nrf_err); +} + +void test_pm_peer_data_load_null(void) +{ + uint32_t nrf_err; + uint32_t len = PM_PEER_DATA_MAX_SIZE; + uint8_t data[PM_PEER_DATA_MAX_SIZE]; + + peer_manager_initialize_success(); + + nrf_err = pm_peer_data_load(0, PM_PEER_DATA_ID_APPLICATION, NULL, &len); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); + + nrf_err = pm_peer_data_load(0, PM_PEER_DATA_ID_APPLICATION, data, NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); +} + +void test_pm_peer_data_load_not_found(void) +{ + uint32_t nrf_err; + union pm_entry_id entry = {.peer_id = 0, .data_id = PM_PEER_DATA_ID_APPLICATION}; + uint32_t len = PM_PEER_DATA_MAX_SIZE; + uint8_t data[PM_PEER_DATA_MAX_SIZE]; + + peer_manager_initialize_success(); + + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, len, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + + nrf_err = pm_peer_data_load(entry.peer_id, entry.data_id, data, &len); + TEST_ASSERT_EQUAL(NRF_ERROR_NOT_FOUND, nrf_err); +} + +void test_pm_peer_data_delete_bonding_invalid(void) +{ + uint32_t nrf_err; + + peer_manager_initialize_success(); + + nrf_err = pm_peer_data_delete(0, PM_PEER_DATA_ID_BONDING); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_PARAM, nrf_err); +} + +void test_pm_peer_new_null(void) +{ + uint32_t nrf_err; + uint16_t new_peer_id; + struct pm_peer_data_bonding bonding_data = {0}; + + peer_manager_initialize_success(); + + nrf_err = pm_peer_new(NULL, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); + + nrf_err = pm_peer_new(&new_peer_id, NULL, NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); +} + +void test_pm_peer_new_success(void) +{ + uint32_t nrf_err; + uint16_t new_peer_id; + union pm_entry_id entry; + struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + }; + + peer_manager_initialize_success(); + + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + + entry.peer_id = 0; + entry.data_id = PM_PEER_DATA_ID_BONDING; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_data, + sizeof(bonding_data), sizeof(bonding_data)); + + nrf_err = pm_peer_new(&new_peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(0, new_peer_id); + TEST_ASSERT_EQUAL(1, pm_peer_count()); +} + +void test_pm_peers_delete_empty(void) +{ + uint32_t nrf_err; + + peer_manager_initialize_success(); + + nrf_err = pm_peers_delete(); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +void test_pm_peer_ranks_get_not_supported(void) +{ + uint32_t nrf_err; + uint16_t highest_peer; + uint32_t highest_rank; + + peer_manager_initialize_success(); + + nrf_err = pm_peer_ranks_get(&highest_peer, &highest_rank, NULL, NULL); +#if defined(CONFIG_PM_PEER_RANKS) + TEST_ASSERT_EQUAL(NRF_ERROR_NOT_FOUND, nrf_err); +#else + TEST_ASSERT_EQUAL(NRF_ERROR_NOT_SUPPORTED, nrf_err); +#endif +} + +void test_pm_local_database_has_changed(void) +{ + peer_manager_initialize_success(); + + pm_local_database_has_changed(); +} + +void test_pm_peer_data_bonding_load_not_found(void) +{ + uint32_t nrf_err; + union pm_entry_id entry = {.peer_id = 0, .data_id = PM_PEER_DATA_ID_BONDING}; + struct pm_peer_data_bonding bonding_data; + + peer_manager_initialize_success(); + + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + sizeof(bonding_data), -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + + nrf_err = pm_peer_data_bonding_load(entry.peer_id, &bonding_data); + TEST_ASSERT_EQUAL(NRF_ERROR_NOT_FOUND, nrf_err); +} + +void test_pm_peer_data_store_null(void) +{ + uint32_t nrf_err; + uint8_t data[4] __aligned(4) = {0}; + + peer_manager_initialize_success(); + + nrf_err = pm_peer_data_store(0, PM_PEER_DATA_ID_APPLICATION, NULL, sizeof(data), NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_NULL, nrf_err); +} + +void test_pm_peer_delete_invalid_peer_id(void) +{ + uint32_t nrf_err; + + peer_manager_initialize_success(); + + __cmock_nrf_sdh_ble_idx_get_IgnoreAndReturn(-1); + + nrf_err = pm_peer_delete(PM_PEER_ID_INVALID); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_PARAM, nrf_err); +} + +void test_pm_peer_data_store_forwards_update_succeeded_event(void) +{ + uint32_t nrf_err; + const struct pm_evt *pm_evt; + union pm_entry_id entry = {.peer_id = 0, .data_id = PM_PEER_DATA_ID_APPLICATION}; + uint8_t app_data[4] __aligned(4) = {0x01, 0x02, 0x03, 0x04}; + + peer_manager_initialize_success(); + + nrf_err = pm_register(on_pm_evt); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + on_pm_evt_capture_restart(); + + __cmock_nrf_sdh_ble_idx_get_IgnoreAndReturn(-1); + + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, app_data, sizeof(app_data), + sizeof(app_data)); + nrf_err = pm_peer_data_app_data_store(entry.peer_id, app_data, sizeof(app_data), NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + invoke_zms_write_success(entry.id); + + pm_evt = on_pm_evt_find_last(PM_EVT_PEER_DATA_UPDATE_SUCCEEDED); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(PM_EVT_PEER_DATA_UPDATE_SUCCEEDED, pm_evt->evt_id); + TEST_ASSERT_EQUAL(0, pm_evt->peer_id); + TEST_ASSERT_EQUAL(PM_PEER_DATA_ID_APPLICATION, + pm_evt->peer_data_update_succeeded.data_id); + TEST_ASSERT_EQUAL(PM_PEER_DATA_OP_UPDATE, + pm_evt->peer_data_update_succeeded.action); + TEST_ASSERT_EQUAL(1, on_pm_evt_test_ctx.capture.count); +} + +#if defined(CONFIG_PM_SERVICE_CHANGED) +void test_pm_local_database_has_changed_forwards_unexpected_error(void) +{ + uint32_t nrf_err; + uint16_t new_peer_id; + union pm_entry_id entry; + const struct pm_evt *pm_evt; + struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + }; + + peer_manager_initialize_success(); + + nrf_err = pm_register(on_pm_evt); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + on_pm_evt_capture_restart(); + + __cmock_nrf_sdh_ble_idx_get_IgnoreAndReturn(-1); + + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_data, + sizeof(bonding_data), sizeof(bonding_data)); + nrf_err = pm_peer_new(&new_peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + entry.data_id = PM_PEER_DATA_ID_SERVICE_CHANGED_PENDING; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, sizeof(uint32_t), -EIO); + __cmock_bm_zms_write_IgnoreArg_data(); + + pm_local_database_has_changed(); + + pm_evt = on_pm_evt_find_last(PM_EVT_ERROR_UNEXPECTED); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(PM_EVT_ERROR_UNEXPECTED, pm_evt->evt_id); + TEST_ASSERT_EQUAL(0, pm_evt->peer_id); + TEST_ASSERT_EQUAL(NRF_ERROR_INTERNAL, pm_evt->error_unexpected.error); + TEST_ASSERT_EQUAL(1, on_pm_evt_test_ctx.capture.count); +} +#endif /* CONFIG_PM_SERVICE_CHANGED */ + +void test_pm_privacy_set_get_success(void) +{ + uint32_t nrf_err; + ble_gap_irk_t device_irk = {.irk = {0x01, 0x02, 0x03, 0x04}}; + ble_gap_privacy_params_t privacy_set = { + .privacy_mode = BLE_GAP_PRIVACY_MODE_OFF, + .private_addr_type = BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE, + .p_device_irk = &device_irk, + }; + ble_gap_privacy_params_t privacy_get = { + .p_device_irk = &device_irk, + }; + + peer_manager_initialize_success(); + + __cmock_sd_ble_gap_privacy_set_ExpectAndReturn(&privacy_set, NRF_SUCCESS); + nrf_err = pm_privacy_set(&privacy_set); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + __cmock_sd_ble_gap_privacy_get_ExpectAndReturn(&privacy_get, NRF_SUCCESS); + nrf_err = pm_privacy_get(&privacy_get); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +void test_pm_peer_data_store_and_load_app_data_success(void) +{ + uint32_t nrf_err; + union pm_entry_id entry = {.peer_id = 0, .data_id = PM_PEER_DATA_ID_APPLICATION}; + uint8_t app_data[8] __aligned(4) = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + uint8_t read_buf[8] __aligned(4); + uint32_t len = sizeof(app_data); + + peer_manager_initialize_success(); + + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, app_data, sizeof(app_data), + sizeof(app_data)); + nrf_err = pm_peer_data_app_data_store(entry.peer_id, app_data, sizeof(app_data), NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, len, len); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data(app_data, sizeof(app_data)); + + nrf_err = pm_peer_data_app_data_load(entry.peer_id, read_buf, &len); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL_MEMORY(app_data, read_buf, sizeof(app_data)); +} + +void test_pm_peer_data_remote_db_store_and_load_success(void) +{ + uint32_t nrf_err; + union pm_entry_id entry = {.peer_id = 0, .data_id = PM_PEER_DATA_ID_GATT_REMOTE}; + struct ble_gatt_db_srv remote_db __aligned(4) = { + .srv_uuid = { + .uuid = 0x1800, + .type = BLE_UUID_TYPE_BLE, + }, + .char_count = 0, + .handle_range = { + .start_handle = 0x0001, + .end_handle = 0x000A, + }, + }; + struct ble_gatt_db_srv read_db __aligned(4); + uint32_t len = sizeof(remote_db); + + peer_manager_initialize_success(); + + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &remote_db, len, len); + nrf_err = pm_peer_data_remote_db_store(entry.peer_id, &remote_db, len, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, len, len); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data(&remote_db, sizeof(remote_db)); + + nrf_err = pm_peer_data_remote_db_load(entry.peer_id, &read_db, &len); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL_MEMORY(&remote_db, &read_db, sizeof(remote_db)); +} + +void test_pm_peer_data_remote_db_load_not_found(void) +{ + uint32_t nrf_err; + union pm_entry_id entry = {.peer_id = 0, .data_id = PM_PEER_DATA_ID_GATT_REMOTE}; + struct ble_gatt_db_srv read_db __aligned(4); + uint32_t len = sizeof(read_db); + + peer_manager_initialize_success(); + + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, len, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + + nrf_err = pm_peer_data_remote_db_load(entry.peer_id, &read_db, &len); + TEST_ASSERT_EQUAL(NRF_ERROR_NOT_FOUND, nrf_err); +} + +void test_pm_peer_data_store_unaligned(void) +{ + uint32_t nrf_err; + uint8_t buffer[8] __aligned(4); + const void *unaligned = &buffer[1]; + + peer_manager_initialize_success(); + + nrf_err = pm_peer_data_store(0, PM_PEER_DATA_ID_APPLICATION, unaligned, + sizeof(buffer) - 1, NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_ADDR, nrf_err); +} + +void test_pm_peer_data_delete_app_data_success(void) +{ + uint32_t nrf_err; + union pm_entry_id entry = {.peer_id = 0, .data_id = PM_PEER_DATA_ID_APPLICATION}; + + peer_manager_initialize_success(); + + __cmock_bm_zms_delete_ExpectAndReturn(zms_fs, entry.id, 0); + nrf_err = pm_peer_data_delete(entry.peer_id, entry.data_id); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +void test_pm_peer_new_duplicate_returns_existing_peer(void) +{ + uint32_t nrf_err; + uint16_t new_peer_id; + union pm_entry_id entry; + struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + }; + + peer_manager_initialize_success(); + + /* Create the first peer. */ + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_data, + sizeof(bonding_data), sizeof(bonding_data)); + nrf_err = pm_peer_new(&new_peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(0, new_peer_id); + + /* Search finds the existing bond at peer 0; no new write. */ + entry.peer_id = 0; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, PM_PEER_DATA_MAX_SIZE, + sizeof(bonding_data)); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data(&bonding_data, sizeof(bonding_data)); + nrf_err = pm_peer_new(&new_peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(0, new_peer_id); +} + +void test_pm_peer_id_list_with_stored_peer(void) +{ + uint32_t nrf_err; + uint16_t new_peer_id; + uint32_t list_size = 2; + uint16_t peer_list[2]; + union pm_entry_id entry; + struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + }; + + peer_manager_initialize_success(); + + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_data, + sizeof(bonding_data), sizeof(bonding_data)); + nrf_err = pm_peer_new(&new_peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + nrf_err = pm_peer_id_list(peer_list, &list_size, PM_PEER_ID_INVALID, + PM_PEER_ID_LIST_ALL_ID); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(1, list_size); + TEST_ASSERT_EQUAL(0, peer_list[0]); + TEST_ASSERT_EQUAL(1, pm_peer_count()); +} + +void test_pm_peers_delete_with_stored_peer(void) +{ + uint32_t nrf_err; + uint16_t new_peer_id; + union pm_entry_id entry; + struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + }; + + peer_manager_initialize_success(); + __cmock_nrf_sdh_ble_idx_get_IgnoreAndReturn(-1); + + /* Add a bonded peer. */ + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_data, + sizeof(bonding_data), sizeof(bonding_data)); + nrf_err = pm_peer_new(&new_peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(1, pm_peer_count()); + + /* pm_peers_delete() scans data IDs 0..7 until bonding is found, then deletes it. */ + entry.peer_id = 0; + for (uint32_t data_id = 0; data_id <= PM_PEER_DATA_ID_BONDING; data_id++) { + entry.data_id = data_id; + if (data_id == PM_PEER_DATA_ID_BONDING) { + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, + sizeof(bonding_data)); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data(&bonding_data, + sizeof(bonding_data)); + } else { + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + } + entry.data_id = PM_PEER_DATA_ID_BONDING; + __cmock_bm_zms_read_IgnoreAndReturn(-ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_delete_Stub(stub_bm_zms_delete_invoke_handler); + + nrf_err = pm_peers_delete(); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(0, pm_peer_count()); +} + +void test_pm_peer_data_bonding_store_duplicate_forbidden(void) +{ + uint32_t nrf_err; + uint16_t new_peer_id; + union pm_entry_id entry; + struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + }; + + peer_manager_initialize_success(); + + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_data, + sizeof(bonding_data), sizeof(bonding_data)); + nrf_err = pm_peer_new(&new_peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + entry.peer_id = 0; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, PM_PEER_DATA_MAX_SIZE, + sizeof(bonding_data)); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data(&bonding_data, sizeof(bonding_data)); + + nrf_err = pm_peer_data_bonding_store(1, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_ERROR_FORBIDDEN, nrf_err); +} + +void test_pm_peer_data_bonding_store_success(void) +{ + uint32_t nrf_err; + uint16_t new_peer_id; + union pm_entry_id entry; + struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + }; + + peer_manager_initialize_success(); + + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_data, + sizeof(bonding_data), sizeof(bonding_data)); + nrf_err = pm_peer_new(&new_peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + entry.peer_id = 0; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, PM_PEER_DATA_MAX_SIZE, + sizeof(bonding_data)); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data(&bonding_data, sizeof(bonding_data)); + for (uint32_t i = 1; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_data, + sizeof(bonding_data), sizeof(bonding_data)); + nrf_err = pm_peer_data_bonding_store(0, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +void test_pm_peer_id_list_skip_no_irk_filters_peer(void) +{ + uint32_t nrf_err; + uint16_t new_peer_id; + uint32_t list_size = 1; + uint16_t peer_list[1]; + union pm_entry_id entry; + struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + }; + + peer_manager_initialize_success(); + + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_data, + sizeof(bonding_data), sizeof(bonding_data)); + nrf_err = pm_peer_new(&new_peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + entry.peer_id = 0; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + sizeof(struct pm_peer_data_bonding), + sizeof(bonding_data)); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data(&bonding_data, sizeof(bonding_data)); + + nrf_err = pm_peer_id_list(peer_list, &list_size, PM_PEER_ID_INVALID, + PM_PEER_ID_LIST_SKIP_NO_IRK); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(0, list_size); +} + +void test_pm_peer_id_list_includes_peer_with_irk(void) +{ + uint32_t nrf_err; + uint16_t new_peer_id; + uint32_t list_size = 1; + uint16_t peer_list[1]; + union pm_entry_id entry; + struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + .peer_ble_id.id_info.irk = {0x01, 0x02, 0x03}, + }; + + peer_manager_initialize_success(); + + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_data, + sizeof(bonding_data), sizeof(bonding_data)); + nrf_err = pm_peer_new(&new_peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + entry.peer_id = 0; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + sizeof(struct pm_peer_data_bonding), + sizeof(bonding_data)); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data(&bonding_data, sizeof(bonding_data)); + + nrf_err = pm_peer_id_list(peer_list, &list_size, PM_PEER_ID_INVALID, + PM_PEER_ID_LIST_SKIP_NO_IRK); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(1, list_size); + TEST_ASSERT_EQUAL(0, peer_list[0]); +} + +void test_pm_peer_id_list_invalid_skip_flags(void) +{ + uint32_t nrf_err; + uint32_t list_size = 1; + uint16_t peer_list[1]; + + peer_manager_initialize_success(); + + nrf_err = pm_peer_id_list(peer_list, &list_size, PM_PEER_ID_INVALID, 8); + TEST_ASSERT_EQUAL(NRF_ERROR_INVALID_PARAM, nrf_err); +} + +void test_pm_peer_id_list_skip_no_id_addr_filters_non_static_address(void) +{ + uint32_t nrf_err; + uint16_t new_peer_id; + uint32_t list_size = 1; + uint16_t peer_list[1]; + union pm_entry_id entry; + struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_RANDOM_PRIVATE, + }; + + peer_manager_initialize_success(); + + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_data, + sizeof(bonding_data), sizeof(bonding_data)); + nrf_err = pm_peer_new(&new_peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + entry.peer_id = 0; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + sizeof(struct pm_peer_data_bonding), + sizeof(bonding_data)); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data(&bonding_data, sizeof(bonding_data)); + + nrf_err = pm_peer_id_list(peer_list, &list_size, PM_PEER_ID_INVALID, + PM_PEER_ID_LIST_SKIP_NO_ID_ADDR); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(0, list_size); +} + +void test_pm_peer_id_list_skip_no_id_addr_includes_public_address(void) +{ + uint32_t nrf_err; + uint16_t new_peer_id; + uint32_t list_size = 1; + uint16_t peer_list[1]; + union pm_entry_id entry; + struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + }; + + peer_manager_initialize_success(); + + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_data, + sizeof(bonding_data), sizeof(bonding_data)); + nrf_err = pm_peer_new(&new_peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + entry.peer_id = 0; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + sizeof(struct pm_peer_data_bonding), + sizeof(bonding_data)); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data(&bonding_data, sizeof(bonding_data)); + + nrf_err = pm_peer_id_list(peer_list, &list_size, PM_PEER_ID_INVALID, + PM_PEER_ID_LIST_SKIP_NO_ID_ADDR); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(1, list_size); + TEST_ASSERT_EQUAL(0, peer_list[0]); +} + +void test_pm_peer_id_list_skip_no_car_filters_zero(void) +{ + uint32_t nrf_err; + uint16_t new_peer_id; + uint32_t list_size = 1; + uint16_t peer_list[1]; + union pm_entry_id entry; + uint32_t car_zero = 0; + struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + }; + + peer_manager_initialize_success(); + + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_data, + sizeof(bonding_data), sizeof(bonding_data)); + nrf_err = pm_peer_new(&new_peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + entry.data_id = PM_PEER_DATA_ID_CENTRAL_ADDR_RES; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &car_zero, sizeof(car_zero), + sizeof(car_zero)); + nrf_err = pm_peer_data_store(0, PM_PEER_DATA_ID_CENTRAL_ADDR_RES, &car_zero, + sizeof(car_zero), NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + entry.data_id = PM_PEER_DATA_ID_CENTRAL_ADDR_RES; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, sizeof(uint32_t), + sizeof(car_zero)); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data(&car_zero, sizeof(car_zero)); + + nrf_err = pm_peer_id_list(peer_list, &list_size, PM_PEER_ID_INVALID, + PM_PEER_ID_LIST_SKIP_NO_CAR); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(0, list_size); +} + +void test_pm_peer_id_list_skip_no_car_includes_nonzero(void) +{ + uint32_t nrf_err; + uint16_t new_peer_id; + uint32_t list_size = 1; + uint16_t peer_list[1]; + union pm_entry_id entry; + uint32_t car_one = 1; + struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + }; + + peer_manager_initialize_success(); + + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_data, + sizeof(bonding_data), sizeof(bonding_data)); + nrf_err = pm_peer_new(&new_peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + entry.data_id = PM_PEER_DATA_ID_CENTRAL_ADDR_RES; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &car_one, sizeof(car_one), + sizeof(car_one)); + nrf_err = pm_peer_data_store(0, PM_PEER_DATA_ID_CENTRAL_ADDR_RES, &car_one, + sizeof(car_one), NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + entry.data_id = PM_PEER_DATA_ID_CENTRAL_ADDR_RES; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, sizeof(uint32_t), + sizeof(car_one)); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data(&car_one, sizeof(car_one)); + + nrf_err = pm_peer_id_list(peer_list, &list_size, PM_PEER_ID_INVALID, + PM_PEER_ID_LIST_SKIP_NO_CAR); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(1, list_size); + TEST_ASSERT_EQUAL(0, peer_list[0]); +} + +void test_pm_peer_id_list_starts_from_first_peer_id(void) +{ + uint32_t nrf_err; + uint16_t peer_id_0; + uint16_t peer_id_1; + uint32_t list_size = 2; + uint16_t peer_list[2]; + union pm_entry_id entry; + struct pm_peer_data_bonding bonding_0 = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + }; + struct pm_peer_data_bonding bonding_1 = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_2, + }; + + peer_manager_initialize_success(); + + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_0, sizeof(bonding_0), + sizeof(bonding_0)); + nrf_err = pm_peer_new(&peer_id_0, &bonding_0, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + entry.peer_id = 0; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, PM_PEER_DATA_MAX_SIZE, + sizeof(bonding_0)); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data(&bonding_0, sizeof(bonding_0)); + for (uint32_t i = 1; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 1; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_1, sizeof(bonding_1), + sizeof(bonding_1)); + nrf_err = pm_peer_new(&peer_id_1, &bonding_1, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(1, peer_id_1); + + nrf_err = pm_peer_id_list(peer_list, &list_size, 1, PM_PEER_ID_LIST_ALL_ID); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(1, list_size); + TEST_ASSERT_EQUAL(1, peer_list[0]); +} + +void test_pm_peer_id_list_list_size_limits_output(void) +{ + uint32_t nrf_err; + uint16_t peer_id_0; + uint16_t peer_id_1; + uint32_t list_size = 1; + uint16_t peer_list[2]; + union pm_entry_id entry; + struct pm_peer_data_bonding bonding_0 = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + }; + struct pm_peer_data_bonding bonding_1 = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_2, + }; + + peer_manager_initialize_success(); + + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_0, sizeof(bonding_0), + sizeof(bonding_0)); + nrf_err = pm_peer_new(&peer_id_0, &bonding_0, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + entry.peer_id = 0; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, PM_PEER_DATA_MAX_SIZE, + sizeof(bonding_0)); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data(&bonding_0, sizeof(bonding_0)); + for (uint32_t i = 1; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 1; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_1, sizeof(bonding_1), + sizeof(bonding_1)); + nrf_err = pm_peer_new(&peer_id_1, &bonding_1, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + nrf_err = pm_peer_id_list(peer_list, &list_size, PM_PEER_ID_INVALID, + PM_PEER_ID_LIST_ALL_ID); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(1, list_size); + TEST_ASSERT_EQUAL(0, peer_list[0]); +} + +void test_pm_peer_id_list_bonding_read_error(void) +{ + uint32_t nrf_err; + uint16_t new_peer_id; + uint32_t list_size = 1; + uint16_t peer_list[1]; + union pm_entry_id entry; + struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + }; + + peer_manager_initialize_success(); + + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_data, + sizeof(bonding_data), sizeof(bonding_data)); + nrf_err = pm_peer_new(&new_peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + entry.peer_id = 0; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + sizeof(struct pm_peer_data_bonding), -EIO); + __cmock_bm_zms_read_IgnoreArg_data(); + + nrf_err = pm_peer_id_list(peer_list, &list_size, PM_PEER_ID_INVALID, + PM_PEER_ID_LIST_SKIP_NO_IRK); + TEST_ASSERT_EQUAL(NRF_ERROR_INTERNAL, nrf_err); +} + +void test_pm_peer_id_list_skip_bonding_not_found(void) +{ + uint32_t nrf_err; + uint16_t new_peer_id; + uint32_t list_size = 1; + uint16_t peer_list[1]; + union pm_entry_id entry; + struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + }; + + peer_manager_initialize_success(); + + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_data, + sizeof(bonding_data), sizeof(bonding_data)); + nrf_err = pm_peer_new(&new_peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + entry.peer_id = 0; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + sizeof(struct pm_peer_data_bonding), -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + + nrf_err = pm_peer_id_list(peer_list, &list_size, PM_PEER_ID_INVALID, + PM_PEER_ID_LIST_SKIP_NO_IRK); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(0, list_size); +} + +void test_pm_peer_id_list_skip_no_id_addr_includes_random_static(void) +{ + uint32_t nrf_err; + uint16_t new_peer_id; + uint32_t list_size = 1; + uint16_t peer_list[1]; + union pm_entry_id entry; + struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_RANDOM_STATIC, + }; + + peer_manager_initialize_success(); + + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_data, + sizeof(bonding_data), sizeof(bonding_data)); + nrf_err = pm_peer_new(&new_peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + entry.peer_id = 0; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + sizeof(struct pm_peer_data_bonding), + sizeof(bonding_data)); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data(&bonding_data, sizeof(bonding_data)); + + nrf_err = pm_peer_id_list(peer_list, &list_size, PM_PEER_ID_INVALID, + PM_PEER_ID_LIST_SKIP_NO_ID_ADDR); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(1, list_size); + TEST_ASSERT_EQUAL(0, peer_list[0]); +} + +void test_pm_peer_id_list_skip_car_not_found(void) +{ + uint32_t nrf_err; + uint16_t new_peer_id; + uint32_t list_size = 1; + uint16_t peer_list[1]; + union pm_entry_id entry; + struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + }; + + peer_manager_initialize_success(); + + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_data, + sizeof(bonding_data), sizeof(bonding_data)); + nrf_err = pm_peer_new(&new_peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + entry.peer_id = 0; + entry.data_id = PM_PEER_DATA_ID_CENTRAL_ADDR_RES; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, sizeof(uint32_t), + -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + + nrf_err = pm_peer_id_list(peer_list, &list_size, PM_PEER_ID_INVALID, + PM_PEER_ID_LIST_SKIP_NO_CAR); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(0, list_size); +} + +void test_pm_peer_id_list_car_read_error(void) +{ + uint32_t nrf_err; + uint16_t new_peer_id; + uint32_t list_size = 1; + uint16_t peer_list[1]; + union pm_entry_id entry; + struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + }; + + peer_manager_initialize_success(); + + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_data, + sizeof(bonding_data), sizeof(bonding_data)); + nrf_err = pm_peer_new(&new_peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + entry.peer_id = 0; + entry.data_id = PM_PEER_DATA_ID_CENTRAL_ADDR_RES; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, sizeof(uint32_t), -EIO); + __cmock_bm_zms_read_IgnoreArg_data(); + + nrf_err = pm_peer_id_list(peer_list, &list_size, PM_PEER_ID_INVALID, + PM_PEER_ID_LIST_SKIP_NO_CAR); + TEST_ASSERT_EQUAL(NRF_ERROR_INTERNAL, nrf_err); +} + +void test_pm_peers_delete_async_sends_peers_delete_succeeded(void) +{ + uint32_t nrf_err; + uint16_t new_peer_id; + union pm_entry_id entry; + const struct pm_evt *pm_evt; + struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + }; + + peer_manager_initialize_success(); + + nrf_err = pm_register(on_pm_evt); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + on_pm_evt_capture_restart(); + + __cmock_nrf_sdh_ble_idx_get_IgnoreAndReturn(-1); + + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_data, + sizeof(bonding_data), sizeof(bonding_data)); + nrf_err = pm_peer_new(&new_peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + entry.peer_id = 0; + for (uint32_t data_id = 0; data_id <= PM_PEER_DATA_ID_BONDING; data_id++) { + entry.data_id = data_id; + if (data_id == PM_PEER_DATA_ID_BONDING) { + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, + sizeof(bonding_data)); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data(&bonding_data, + sizeof(bonding_data)); + } else { + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + } + entry.data_id = PM_PEER_DATA_ID_BONDING; + __cmock_bm_zms_read_IgnoreAndReturn(-ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_delete_Stub(stub_bm_zms_delete_invoke_handler); + + nrf_err = pm_peers_delete(); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(0, pm_peer_count()); + + pm_evt = on_pm_evt_find_last(PM_EVT_PEERS_DELETE_SUCCEEDED); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(PM_EVT_PEERS_DELETE_SUCCEEDED, pm_evt->evt_id); + TEST_ASSERT_EQUAL(2, on_pm_evt_test_ctx.capture.count); +} + +void test_pm_peer_ranks_get_success_with_stored_rank(void) +{ + uint32_t nrf_err; + uint16_t new_peer_id; + uint16_t highest_peer; + uint32_t highest_rank; + union pm_entry_id entry; + uint32_t rank_val = 1; + struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + }; + + peer_manager_initialize_success(); + + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_data, + sizeof(bonding_data), sizeof(bonding_data)); + nrf_err = pm_peer_new(&new_peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + entry.data_id = PM_PEER_DATA_ID_PEER_RANK; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, sizeof(uint32_t), + -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, sizeof(uint32_t), + sizeof(uint32_t)); + __cmock_bm_zms_write_IgnoreArg_data(); + nrf_err = pm_peer_rank_highest(0); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + entry.data_id = PM_PEER_DATA_ID_PEER_RANK; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, sizeof(uint32_t), + sizeof(rank_val)); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_read_ReturnMemThruPtr_data(&rank_val, sizeof(rank_val)); + + nrf_err = pm_peer_ranks_get(&highest_peer, &highest_rank, NULL, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + TEST_ASSERT_EQUAL(0, highest_peer); + TEST_ASSERT_EQUAL(1, highest_rank); +} + +void test_pm_peer_rank_highest_success(void) +{ + uint32_t nrf_err; + uint16_t new_peer_id; + union pm_entry_id entry; + struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + }; + + peer_manager_initialize_success(); + + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + entry.data_id = PM_PEER_DATA_ID_BONDING; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_data, + sizeof(bonding_data), sizeof(bonding_data)); + nrf_err = pm_peer_new(&new_peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + entry.data_id = PM_PEER_DATA_ID_PEER_RANK; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, sizeof(uint32_t), + -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, sizeof(uint32_t), + sizeof(uint32_t)); + __cmock_bm_zms_write_IgnoreArg_data(); + + nrf_err = pm_peer_rank_highest(0); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); +} + +#if defined(CONFIG_PM_PEER_RANKS) +void test_pm_peer_rank_highest_busy_while_store_pending(void) +{ + uint32_t nrf_err; + uint16_t new_peer_id; + union pm_entry_id entry; + struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + }; + + peer_manager_initialize_success(); + + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_data, + sizeof(bonding_data), sizeof(bonding_data)); + nrf_err = pm_peer_new(&new_peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + entry.data_id = PM_PEER_DATA_ID_PEER_RANK; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, sizeof(uint32_t), + -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, sizeof(uint32_t), + sizeof(uint32_t)); + __cmock_bm_zms_write_IgnoreArg_data(); + nrf_err = pm_peer_rank_highest(0); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + /* Store is still in progress (token not cleared). */ + nrf_err = pm_peer_rank_highest(0); + TEST_ASSERT_EQUAL(NRF_ERROR_BUSY, nrf_err); +} + +void test_pm_peer_rank_highest_already_highest_sends_event(void) +{ + uint32_t nrf_err; + uint16_t new_peer_id; + union pm_entry_id entry; + const struct pm_evt *pm_evt; + struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + }; + + peer_manager_initialize_success(); + + nrf_err = pm_register(on_pm_evt); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + on_pm_evt_capture_restart(); + + __cmock_nrf_sdh_ble_idx_get_IgnoreAndReturn(-1); + + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_data, + sizeof(bonding_data), sizeof(bonding_data)); + nrf_err = pm_peer_new(&new_peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + entry.data_id = PM_PEER_DATA_ID_PEER_RANK; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, sizeof(uint32_t), + -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, sizeof(uint32_t), + sizeof(uint32_t)); + __cmock_bm_zms_write_IgnoreArg_data(); + nrf_err = pm_peer_rank_highest(0); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + /* Complete the async rank store so peer 0 is recorded as highest. */ + invoke_zms_write_success(entry.id); + + nrf_err = pm_peer_rank_highest(0); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + pm_evt = on_pm_evt_find_last(PM_EVT_PEER_DATA_UPDATE_SUCCEEDED); + TEST_ASSERT_NOT_NULL(pm_evt); + TEST_ASSERT_EQUAL(PM_EVT_PEER_DATA_UPDATE_SUCCEEDED, pm_evt->evt_id); + TEST_ASSERT_EQUAL(0, pm_evt->peer_id); + TEST_ASSERT_EQUAL(PM_PEER_DATA_ID_PEER_RANK, + pm_evt->peer_data_update_succeeded.data_id); + TEST_ASSERT_FALSE(pm_evt->peer_data_update_succeeded.flash_changed); + TEST_ASSERT_EQUAL(2, on_pm_evt_test_ctx.capture.count); +} + +void test_pm_peer_rank_highest_store_failure(void) +{ + uint32_t nrf_err; + uint16_t new_peer_id; + union pm_entry_id entry; + struct pm_peer_data_bonding bonding_data = { + .own_role = BLE_GAP_ROLE_PERIPH, + .peer_ble_id.id_addr_info = ADDRESS_PUBLIC_1, + }; + + peer_manager_initialize_success(); + + entry.data_id = PM_PEER_DATA_ID_BONDING; + for (uint32_t i = 0; i < PM_PEER_ID_N_AVAILABLE_IDS; i++) { + entry.peer_id = i; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, + PM_PEER_DATA_MAX_SIZE, -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + } + entry.peer_id = 0; + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, &bonding_data, + sizeof(bonding_data), sizeof(bonding_data)); + nrf_err = pm_peer_new(&new_peer_id, &bonding_data, NULL); + TEST_ASSERT_EQUAL(NRF_SUCCESS, nrf_err); + + entry.data_id = PM_PEER_DATA_ID_PEER_RANK; + __cmock_bm_zms_read_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, sizeof(uint32_t), + -ENOENT); + __cmock_bm_zms_read_IgnoreArg_data(); + __cmock_bm_zms_write_ExpectAndReturn(zms_fs, entry.id, PTR_IGNORE, sizeof(uint32_t), -EIO); + __cmock_bm_zms_write_IgnoreArg_data(); + nrf_err = pm_peer_rank_highest(0); + TEST_ASSERT_EQUAL(NRF_ERROR_INTERNAL, nrf_err); +} + +#endif /* CONFIG_PM_PEER_RANKS */ void setUp(void) { @@ -1578,7 +4149,7 @@ void tearDown(void) { uint32_t nrf_err; - /* Reset behavior for on_pm_evt event handler function. */ + /* Reset behavior for on_pm_evt event handler function and pm_evt capture. */ memset(&on_pm_evt_test_ctx, 0, sizeof(on_pm_evt_test_ctx)); if (zms_fs != NULL) {