| /* |
| * Copyright (c) 2021 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/kernel.h> |
| #include <stddef.h> |
| #include <zephyr/ztest.h> |
| |
| #include <zephyr/bluetooth/bluetooth.h> |
| #include <zephyr/bluetooth/hci.h> |
| #include <zephyr/sys/byteorder.h> |
| #include <host/hci_core.h> |
| |
| #include "util/util.h" |
| #include "util/memq.h" |
| #include "util/mem.h" |
| #include "util/dbuf.h" |
| |
| #include "pdu_df.h" |
| #include "lll/pdu_vendor.h" |
| #include "pdu.h" |
| |
| #include "hal/ccm.h" |
| |
| #include "lll.h" |
| #include "lll/lll_adv_types.h" |
| #include "lll_adv.h" |
| #include "lll/lll_adv_pdu.h" |
| #include "lll_adv_sync.h" |
| #include "lll/lll_df_types.h" |
| |
| #include "ull_adv_types.h" |
| #include "ull_adv_internal.h" |
| |
| #include "ll.h" |
| #include "common.h" |
| |
| #define PDU_PAULOAD_BUFF_SIZE 100 |
| #define TEST_CTE_LENGTH 20 |
| |
| /* Controller code uses static function sync_acquire to get adv. sync. |
| * For test purposes it is used as global variable to avoid complete |
| * creation of advertising set. |
| */ |
| static struct ll_adv_sync_set g_sync_set; |
| static struct lll_df_adv_cfg g_df_cfg; |
| |
| static void common_pdu_adv_data_set(struct pdu_adv *pdu, const uint8_t *data, uint8_t len); |
| |
| /* |
| * @brief Helper function to create advertising set. |
| * |
| * The function creates advertising set to an extent required to test adding CTE to periodic |
| * advertising chains. The function returns handle to advertising set that may be used |
| * in calls to ULL functions related with advertising. |
| * |
| * @param hci_handle equivalent of a handle received from HCI command. |
| * |
| * @return Handle to created advertising set. |
| */ |
| struct ll_adv_set *common_create_adv_set(uint8_t hci_handle) |
| { |
| struct lll_adv_sync *lll_sync; |
| struct ll_adv_set *adv_set; |
| uint8_t handle; |
| uint8_t err; |
| |
| zassert_true(hci_handle < BT_HCI_LE_ADV_HANDLE_MAX, |
| "Advertising set handle: %" PRIu8 " exceeds maximum handles value %" PRIu8, |
| hci_handle, BT_HCI_LE_ADV_HANDLE_MAX); |
| err = ll_adv_set_by_hci_handle_get_or_new(hci_handle, &handle); |
| zassert_equal(err, 0, "Unexpected error while create new advertising set, err: %" PRIu8, |
| err); |
| |
| adv_set = ull_adv_set_get(handle); |
| zassert_not_null(adv_set, 0, "Unexpectedly advertising set is NULL"); |
| /* Note: there is single lll_adv_sync instance. If created more than one advertising set |
| * all will have reference to the same lll_adv_sync instance. |
| */ |
| lll_sync = &g_sync_set.lll; |
| adv_set->lll.sync = &g_sync_set.lll; |
| lll_hdr_init(&adv_set->lll, adv_set); |
| g_sync_set.lll.adv = &adv_set->lll; |
| lll_hdr_init(lll_sync, &g_sync_set); |
| |
| err = lll_adv_init(); |
| zassert_equal(err, 0, "Unexpected error while initialization advertising set, err: %d", |
| err); |
| |
| lll_hdr_init(lll_sync, &g_sync_set); |
| |
| lll_adv_data_reset(&lll_sync->data); |
| err = lll_adv_data_init(&lll_sync->data); |
| zassert_equal(err, 0, |
| "Unexpected error while initialization advertising data init, err: %d", err); |
| |
| adv_set->is_created = 1U; |
| |
| return adv_set; |
| } |
| |
| /* |
| * @brief Release advertising set. |
| * |
| * @param adv_set Pointer to advertising set to be released. |
| */ |
| void common_release_adv_set(struct ll_adv_set *adv_set) |
| { |
| struct ll_adv_sync_set *sync; |
| |
| if (adv_set->lll.sync) { |
| sync = HDR_LLL2ULL(adv_set->lll.sync); |
| if (sync) { |
| sync->is_started = 0U; |
| } |
| |
| lll_adv_data_reset(&sync->lll.data); |
| } |
| adv_set->lll.sync = NULL; |
| if (adv_set->df_cfg->is_enabled) { |
| adv_set->df_cfg->is_enabled = 0U; |
| } |
| adv_set->df_cfg = NULL; |
| adv_set->is_created = 0U; |
| } |
| |
| /* |
| * @brief Helper function that creates periodic advertising chain. |
| * |
| * The function creates periodic advertising chain with provided number of PDUs @p pdu_count. |
| * The created chain is enqueued in provided advertising set. Number of requested PDUs includes |
| * head of a chain AUX_SYNC_IND. |
| * Each created PDU will hold payload data in following pattern: |
| * "test%d test%d test%d", where '%d' is substituted by PDU index. |
| * |
| * @param adv_set Pointer to advertising set to enqueue created chain. |
| * @param pdu_count Requested number of PDUs in a chain. |
| */ |
| void common_create_per_adv_chain(struct ll_adv_set *adv_set, uint8_t pdu_count) |
| { |
| uint8_t hdr_data[ULL_ADV_HDR_DATA_LEN_SIZE + |
| ULL_ADV_HDR_DATA_AUX_PTR_PTR_SIZE]; |
| struct pdu_adv *pdu_prev, *pdu, *pdu_new; |
| char pdu_buff[PDU_PAULOAD_BUFF_SIZE]; |
| void *extra_data_prev, *extra_data; |
| struct lll_adv_sync *lll_sync; |
| bool adi_in_sync_ind; |
| uint8_t err, pdu_idx; |
| |
| lll_sync = adv_set->lll.sync; |
| pdu = lll_adv_sync_data_peek(lll_sync, NULL); |
| ull_adv_sync_pdu_init(pdu, 0U, 0U, 0U, NULL); |
| |
| err = ull_adv_sync_pdu_alloc(adv_set, ULL_ADV_PDU_EXTRA_DATA_ALLOC_IF_EXIST, &pdu_prev, |
| &pdu, &extra_data_prev, &extra_data, &pdu_idx); |
| zassert_equal(err, 0, "Unexpected error while PDU allocation, err: %d", err); |
| |
| if (extra_data) { |
| ull_adv_sync_extra_data_set_clear(extra_data_prev, extra_data, 0, 0, NULL); |
| } |
| |
| /* Create AUX_SYNC_IND PDU as a head of chain */ |
| err = ull_adv_sync_pdu_set_clear(lll_sync, pdu_prev, pdu, |
| (pdu_count > 1 ? ULL_ADV_PDU_HDR_FIELD_AUX_PTR : |
| ULL_ADV_PDU_HDR_FIELD_NONE), |
| ULL_ADV_PDU_HDR_FIELD_NONE, hdr_data); |
| zassert_equal(err, 0, "Unexpected error during initialization of extended PDU, err: %d", |
| err); |
| |
| if (IS_ENABLED(CONFIG_BT_CTLR_ADV_PERIODIC_ADI_SUPPORT)) { |
| adi_in_sync_ind = ull_adv_sync_pdu_had_adi(pdu); |
| } |
| |
| /* Add some AD for testing */ |
| snprintf(pdu_buff, ARRAY_SIZE(pdu_buff), "test%" PRIu8 " test%" PRIu8 " test%" PRIu8 "", 0, |
| 0, 0); |
| common_pdu_adv_data_set(pdu, pdu_buff, strlen(pdu_buff)); |
| /* Create AUX_CHAIN_IND PDUs. Start from 1, AUX_SYNC_IND is first PDU. */ |
| for (uint8_t idx = 1; idx < pdu_count; ++idx) { |
| snprintf(pdu_buff, ARRAY_SIZE(pdu_buff), |
| "test%" PRIu8 " test%" PRIu8 " test%" PRIu8 "", idx, idx, idx); |
| /* Allocate new PDU */ |
| pdu_new = lll_adv_pdu_alloc_pdu_adv(); |
| zassert_not_null(pdu_new, "Cannot allocate new PDU."); |
| /* Initialize new empty PDU. Last AUX_CHAIN_IND may not include AuxPtr. */ |
| if (idx < pdu_count - 1) { |
| if (IS_ENABLED(CONFIG_BT_CTLR_ADV_PERIODIC_ADI_SUPPORT) && |
| adi_in_sync_ind) { |
| ull_adv_sync_pdu_init(pdu_new, |
| ULL_ADV_PDU_HDR_FIELD_AUX_PTR | |
| ULL_ADV_PDU_HDR_FIELD_ADI, |
| lll_sync->adv->phy_s, |
| lll_sync->adv->phy_flags, NULL); |
| } else { |
| ull_adv_sync_pdu_init(pdu_new, |
| ULL_ADV_PDU_HDR_FIELD_AUX_PTR, |
| lll_sync->adv->phy_s, |
| lll_sync->adv->phy_flags, NULL); |
| } |
| } else { |
| if (IS_ENABLED(CONFIG_BT_CTLR_ADV_PERIODIC_ADI_SUPPORT) && |
| adi_in_sync_ind) { |
| ull_adv_sync_pdu_init(pdu_new, |
| ULL_ADV_PDU_HDR_FIELD_ADI, |
| 0U, 0U, NULL); |
| } else { |
| ull_adv_sync_pdu_init(pdu_new, |
| ULL_ADV_PDU_HDR_FIELD_NONE, |
| 0U, 0U, NULL); |
| } |
| } |
| /* Add some AD for testing */ |
| common_pdu_adv_data_set(pdu_new, pdu_buff, strlen(pdu_buff)); |
| /* Link to previous PDU */ |
| lll_adv_pdu_linked_append(pdu_new, pdu); |
| pdu = pdu_new; |
| } |
| |
| lll_adv_sync_data_enqueue(lll_sync, pdu_idx); |
| } |
| |
| /* |
| * @brief Helper function to release periodic advertising chain that was enqueued for |
| * advertising set. |
| * |
| * @param adv_set Pointer to advertising set to release a PDUs chain. |
| */ |
| void common_release_per_adv_chain(struct ll_adv_set *adv_set) |
| { |
| struct lll_adv_sync *lll_sync; |
| struct pdu_adv *pdu; |
| |
| lll_sync = adv_set->lll.sync; |
| pdu = lll_adv_sync_data_peek(lll_sync, NULL); |
| if (pdu != NULL) { |
| lll_adv_pdu_linked_release_all(pdu); |
| } |
| |
| pdu = (void *)lll_sync->data.pdu[lll_sync->data.first]; |
| if (pdu != NULL) { |
| lll_adv_pdu_linked_release_all(pdu); |
| } |
| } |
| |
| /* |
| * @brief Helper function that validates content of periodic advertising PDU. |
| * |
| * The function verifies if content of periodic advertising PDU as as expected. The function |
| * verifies two types of PDUs: AUX_SYNC_IND and AUX_CHAIN_IND. AUX_CHAIN_IND is validated |
| * as if its superior PDU is AUX_SYNC_IND only. |
| * |
| * Expected fields in extended advertising header are provided by @p exp_ext_hdr_flags. |
| * |
| * Note: The function expects that there is no ACAD data in the PDU. |
| * |
| * @param pdu Pointer to PDU to be verified. |
| * @param type Type of the PDU. |
| * @param exp_ext_hdr_flags Bitfield with expected extended header flags. |
| */ |
| void common_validate_per_adv_pdu(struct pdu_adv *pdu, enum test_pdu_ext_adv_type type, |
| uint16_t exp_ext_hdr_flags) |
| { |
| struct pdu_adv_com_ext_adv *com_hdr; |
| struct pdu_adv_ext_hdr *ext_hdr; |
| uint8_t ext_hdr_len; |
| uint8_t *dptr; |
| |
| if (pdu->len > 1) { |
| com_hdr = &pdu->adv_ext_ind; |
| /* Non-connectable and Non-scannable adv mode */ |
| zassert_equal(com_hdr->adv_mode, 0U, |
| "Unexpected mode of extended advertising PDU: %" PRIu8, |
| com_hdr->adv_mode); |
| |
| ext_hdr = &com_hdr->ext_hdr; |
| dptr = ext_hdr->data; |
| |
| if (com_hdr->ext_hdr_len > 0) { |
| zassert_false(ext_hdr->adv_addr, |
| "Unexpected AdvA field in extended advertising header"); |
| zassert_false(ext_hdr->tgt_addr, |
| "Unexpected TargetA field in extended advertising header"); |
| if (exp_ext_hdr_flags & ULL_ADV_PDU_HDR_FIELD_CTE_INFO) { |
| zassert_true( |
| ext_hdr->cte_info, |
| "Missing expected CteInfo field in extended advertising header"); |
| dptr += sizeof(struct pdu_cte_info); |
| } else { |
| zassert_false( |
| ext_hdr->cte_info, |
| "Unexpected CteInfo field in extended advertising header"); |
| } |
| if (exp_ext_hdr_flags & ULL_ADV_PDU_HDR_FIELD_ADI) { |
| zassert_true( |
| ext_hdr->adi, |
| "Missing expected ADI field in extended advertising header"); |
| dptr += sizeof(struct pdu_adv_adi); |
| } else { |
| zassert_false( |
| ext_hdr->adi, |
| "Unexpected ADI field in extended advertising header"); |
| } |
| if (exp_ext_hdr_flags & ULL_ADV_PDU_HDR_FIELD_AUX_PTR) { |
| zassert_true( |
| ext_hdr->aux_ptr, |
| "Missing expected AuxPtr field in extended advertising header"); |
| dptr += sizeof(struct pdu_adv_aux_ptr); |
| } else { |
| zassert_false( |
| ext_hdr->aux_ptr, |
| "Unexpected AuxPtr field in extended advertising header"); |
| } |
| zassert_false(ext_hdr->sync_info, |
| "Unexpected SyncInfo field in extended advertising header"); |
| if (exp_ext_hdr_flags & ULL_ADV_PDU_HDR_FIELD_TX_POWER) { |
| zassert_true( |
| ext_hdr->tx_pwr, |
| "Missing expected CteInfo field in extended advertising header"); |
| dptr += sizeof(uint8_t); |
| } else { |
| zassert_false( |
| ext_hdr->tx_pwr, |
| "Unexpected CteInfo field in extended advertising header"); |
| } |
| |
| /* Calculate extended header len, ACAD should not be available. |
| * ull_adv_aux_hdr_len_calc returns ext hdr length without it. |
| */ |
| ext_hdr_len = ull_adv_aux_hdr_len_calc(com_hdr, &dptr); |
| zassert_equal(com_hdr->ext_hdr_len, |
| ext_hdr_len - PDU_AC_EXT_HEADER_SIZE_MIN, |
| "Extended header length: %" PRIu8 |
| " different than expected %" PRIu8, |
| ext_hdr_len, com_hdr->ext_hdr_len); |
| |
| if (exp_ext_hdr_flags & ULL_ADV_PDU_HDR_FIELD_AD_DATA) { |
| zassert_true((pdu->len - ext_hdr_len) > 0, |
| "Missing expected advertising data in PDU"); |
| } else { |
| zassert_equal(pdu->len - ext_hdr_len, 0, |
| "Unexpected advertising data in PDU"); |
| } |
| } else { |
| zassert_equal(exp_ext_hdr_flags, ULL_ADV_PDU_HDR_FIELD_AD_DATA, |
| "Unexpected extended header flags: %" PRIu16, |
| exp_ext_hdr_flags); |
| } |
| } |
| } |
| |
| /* |
| * @brief Helper function to prepare CTE configuration for a given advertising set. |
| * |
| * Note: There is a single instance of CTE configuration. In case there is a need |
| * to use multiple advertising sets at once, all will use the same CTE configuration. |
| * |
| * @param adv Pointer to advertising set |
| * @param cte_count Requested number of CTEs to be send |
| */ |
| void common_prepare_df_cfg(struct ll_adv_set *adv, uint8_t cte_count) |
| { |
| /* Prepare CTE configuration */ |
| g_df_cfg.cte_count = cte_count; |
| g_df_cfg.cte_length = TEST_CTE_LENGTH; |
| g_df_cfg.cte_type = BT_HCI_LE_AOD_CTE_2US; |
| |
| adv->df_cfg = &g_df_cfg; |
| } |
| |
| /* |
| * @brief Helper function that validates correctness of periodic advertising chain. |
| * |
| * The function expects that all periodic advertising chain PDUs will have advertising data. |
| * |
| * @param adv Pointer to advertising set |
| * @param pdu_count Number of PDUs in a chain |
| */ |
| void common_validate_per_adv_chain(struct ll_adv_set *adv, uint8_t pdu_count) |
| { |
| uint16_t ext_hdr_flags; |
| struct pdu_adv *pdu; |
| |
| pdu = lll_adv_sync_data_peek(adv->lll.sync, NULL); |
| |
| /* Validate AUX_SYNC_IND */ |
| if (pdu_count > 1) { |
| ext_hdr_flags = ULL_ADV_PDU_HDR_FIELD_AUX_PTR | ULL_ADV_PDU_HDR_FIELD_AD_DATA; |
| } else { |
| ext_hdr_flags = ULL_ADV_PDU_HDR_FIELD_AD_DATA; |
| } |
| |
| common_validate_per_adv_pdu(pdu, TEST_PDU_EXT_ADV_SYNC_IND, ext_hdr_flags); |
| pdu = lll_adv_pdu_linked_next_get(pdu); |
| if (pdu_count > 1) { |
| zassert_not_null(pdu, "Expected PDU in periodic advertising chain is NULL"); |
| } else { |
| zassert_is_null(pdu, "Unexpected PDU in a single PDU periodic advertising chain"); |
| } |
| /* Validate AUX_CHAIN_IND PDUs in a periodic advertising chain. Start from 1, because |
| * first PDU is AUX_SYNC_IND. |
| */ |
| for (uint8_t idx = 1; idx < pdu_count; ++idx) { |
| if (idx != (pdu_count - 1)) { |
| ext_hdr_flags = |
| ULL_ADV_PDU_HDR_FIELD_AUX_PTR | ULL_ADV_PDU_HDR_FIELD_AD_DATA; |
| } else { |
| ext_hdr_flags = ULL_ADV_PDU_HDR_FIELD_AD_DATA; |
| } |
| |
| common_validate_per_adv_pdu(pdu, TEST_PDU_EXT_ADV_CHAIN_IND, ext_hdr_flags); |
| pdu = lll_adv_pdu_linked_next_get(pdu); |
| if (idx != (pdu_count - 1)) { |
| zassert_not_null(pdu, "Expected PDU in periodic advertising chain is NULL"); |
| } else { |
| zassert_is_null(pdu, "Unexpected PDU at end of periodic advertising chain"); |
| } |
| } |
| } |
| |
| /* |
| * @brief Helper function that validates correctness of periodic advertising chain including CTE |
| * |
| * The number of PDUs including advertising data or CTE is provided by appropriate function |
| * arguments. PUDs including CTE are always first #N PDUs. The same rule applies to PDUs including |
| * advertising data. So maximum number of PDUs in a chain is maximum value from pair @p cte_count |
| * and @p ad_data_count. |
| * |
| * @param adv Pointer to advertising set |
| * @param cte_count Number of PDUs including CTE |
| * @param ad_data_pdu_count Number of PDUs including advertising data |
| */ |
| void common_validate_chain_with_cte(struct ll_adv_set *adv, uint8_t cte_count, |
| uint8_t ad_data_pdu_count) |
| { |
| uint16_t ext_hdr_flags; |
| struct pdu_adv *pdu; |
| uint8_t pdu_count; |
| |
| pdu = lll_adv_sync_data_peek(adv->lll.sync, NULL); |
| if (cte_count > 1) { |
| ext_hdr_flags = ULL_ADV_PDU_HDR_FIELD_AUX_PTR | ULL_ADV_PDU_HDR_FIELD_CTE_INFO; |
| |
| } else { |
| ext_hdr_flags = ULL_ADV_PDU_HDR_FIELD_CTE_INFO; |
| } |
| if (ad_data_pdu_count > 0) { |
| ext_hdr_flags |= ULL_ADV_PDU_HDR_FIELD_AD_DATA; |
| } |
| |
| common_validate_per_adv_pdu(pdu, TEST_PDU_EXT_ADV_SYNC_IND, ext_hdr_flags); |
| |
| pdu_count = MAX(cte_count, ad_data_pdu_count); |
| |
| pdu = lll_adv_pdu_linked_next_get(pdu); |
| if (pdu_count > 1) { |
| zassert_not_null(pdu, "Expected PDU in periodic advertising chain is NULL"); |
| } else { |
| zassert_is_null(pdu, "Unexpected PDU in a single PDU periodic advertising chain"); |
| } |
| |
| for (uint8_t idx = 1; idx < pdu_count; ++idx) { |
| if (idx < pdu_count - 1) { |
| ext_hdr_flags = ULL_ADV_PDU_HDR_FIELD_AUX_PTR; |
| } else { |
| ext_hdr_flags = 0U; |
| } |
| if (idx < cte_count) { |
| ext_hdr_flags |= ULL_ADV_PDU_HDR_FIELD_CTE_INFO; |
| } |
| if (idx < ad_data_pdu_count) { |
| ext_hdr_flags |= ULL_ADV_PDU_HDR_FIELD_AD_DATA; |
| } |
| |
| common_validate_per_adv_pdu(pdu, TEST_PDU_EXT_ADV_CHAIN_IND, ext_hdr_flags); |
| |
| pdu = lll_adv_pdu_linked_next_get(pdu); |
| if (idx < (pdu_count - 1)) { |
| zassert_not_null(pdu, "Expected PDU in periodic advertising chain is NULL"); |
| } else { |
| zassert_is_null(pdu, "Unexpected PDU at end of periodic advertising chain"); |
| } |
| } |
| } |
| |
| /* |
| * @brief Helper function to cleanup after test case end. |
| * |
| * @param adv Pointer to advertising set |
| */ |
| void common_teardown(struct ll_adv_set *adv) |
| { |
| common_release_per_adv_chain(adv); |
| common_release_adv_set(adv); |
| lll_adv_init(); |
| } |
| /* |
| * @brief Helper function to add payload data to extended advertising PDU. |
| * |
| * @param pdu Pointer to extended advertising PDU. |
| * @param data Pointer to data to be added. |
| * @param len Length of the data. |
| */ |
| static void common_pdu_adv_data_set(struct pdu_adv *pdu, const uint8_t *data, uint8_t len) |
| { |
| struct pdu_adv_com_ext_adv *com_hdr; |
| uint8_t len_max; |
| uint8_t *dptr; |
| |
| com_hdr = &pdu->adv_ext_ind; |
| |
| dptr = &com_hdr->ext_hdr_adv_data[com_hdr->ext_hdr_len]; |
| |
| len_max = PDU_AC_PAYLOAD_SIZE_MAX - (dptr - pdu->payload); |
| zassert_false(len > len_max, |
| "Provided data length exceeds maximum supported payload length: %" PRIu8, |
| len_max); |
| |
| memcpy(dptr, data, len); |
| dptr += len; |
| |
| pdu->len = dptr - pdu->payload; |
| } |