text: /*
   * Copyright (c) 2021 Nordic Semiconductor ASA
   *
   * SPDX-License-Identifier: Apache-2.0
   */
  
  #include <zephyr.h>
  #include <stddef.h>
  #include <ztest.h>
  
  #include <bluetooth/bluetooth.h>
  #include <bluetooth/hci.h>
  #include <sys/byteorder.h>
  #include <host/hci_core.h>
  
  #include "util/util.h"
  #include "util/memq.h"
  #include "util/mem.h"
  
  #include "pdu.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;
  	g_sync_set.lll.adv = &adv_set->lll;
  
  	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)
  {
  	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;
  	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, 0);
  
  	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 : 0), 0,
  					 NULL);
  	zassert_equal(err, 0, "Unexpected error during initialization of extended PDU, err: %d",
  		      err);
  
  	/* 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) {
  			ull_adv_sync_pdu_init(pdu_new, ULL_ADV_PDU_HDR_FIELD_AUX_PTR);
  		} else {
  			ull_adv_sync_pdu_init(pdu_new, 0);
  		}
  		/* 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 (type == TEST_PDU_EXT_ADV_SYNC_IND) {
  				zassert_false(
  					ext_hdr->adi,
  					"Unexpected ADI 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_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 (type == TEST_PDU_EXT_ADV_SYNC_IND &&
  			    (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");
  			}
  			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 prepre 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;
  }
  