diff --git a/drivers/bluetooth/hci/h4.c b/drivers/bluetooth/hci/h4.c index b8c77282b49..9b145be82de 100644 --- a/drivers/bluetooth/hci/h4.c +++ b/drivers/bluetooth/hci/h4.c @@ -164,8 +164,7 @@ static inline void get_evt_hdr(void) if (!rx.remaining) { if (rx.evt.evt == BT_HCI_EVT_LE_META_EVENT && - (rx.hdr[sizeof(*hdr)] == BT_HCI_EVT_LE_ADVERTISING_REPORT || - rx.hdr[sizeof(*hdr)] == BT_HCI_EVT_LE_EXT_ADVERTISING_REPORT)) { + (rx.hdr[sizeof(*hdr)] == BT_HCI_EVT_LE_ADVERTISING_REPORT)) { BT_DBG("Marking adv report as discardable"); rx.discardable = true; } diff --git a/drivers/bluetooth/hci/hci_esp32.c b/drivers/bluetooth/hci/hci_esp32.c index 70da2163da9..0a866e2199f 100644 --- a/drivers/bluetooth/hci/hci_esp32.c +++ b/drivers/bluetooth/hci/hci_esp32.c @@ -41,8 +41,6 @@ static bool is_hci_event_discardable(const uint8_t *evt_data) switch (subevt_type) { case BT_HCI_EVT_LE_ADVERTISING_REPORT: return true; - case BT_HCI_EVT_LE_EXT_ADVERTISING_REPORT: - return true; default: return false; } diff --git a/drivers/bluetooth/hci/ipm_stm32wb.c b/drivers/bluetooth/hci/ipm_stm32wb.c index fb0c6a1f494..a487b8b0193 100644 --- a/drivers/bluetooth/hci/ipm_stm32wb.c +++ b/drivers/bluetooth/hci/ipm_stm32wb.c @@ -181,8 +181,7 @@ static void bt_ipm_rx_thread(void) default: mev = (void *)&hcievt->evtserial.evt.payload; if (hcievt->evtserial.evt.evtcode == BT_HCI_EVT_LE_META_EVENT && - (mev->subevent == BT_HCI_EVT_LE_ADVERTISING_REPORT || - mev->subevent == BT_HCI_EVT_LE_EXT_ADVERTISING_REPORT)) { + (mev->subevent == BT_HCI_EVT_LE_ADVERTISING_REPORT)) { discardable = true; timeout = K_NO_WAIT; } diff --git a/drivers/bluetooth/hci/rpmsg.c b/drivers/bluetooth/hci/rpmsg.c index 1f702089ab8..281a77d90e9 100644 --- a/drivers/bluetooth/hci/rpmsg.c +++ b/drivers/bluetooth/hci/rpmsg.c @@ -41,8 +41,6 @@ static bool is_hci_event_discardable(const uint8_t *evt_data) switch (subevt_type) { case BT_HCI_EVT_LE_ADVERTISING_REPORT: return true; - case BT_HCI_EVT_LE_EXT_ADVERTISING_REPORT: - return true; default: return false; } diff --git a/drivers/bluetooth/hci/spi.c b/drivers/bluetooth/hci/spi.c index 642c3799c24..359667aad69 100644 --- a/drivers/bluetooth/hci/spi.c +++ b/drivers/bluetooth/hci/spi.c @@ -370,8 +370,7 @@ static void bt_spi_rx_thread(void) continue; default: if (rxmsg[1] == BT_HCI_EVT_LE_META_EVENT && - (rxmsg[3] == BT_HCI_EVT_LE_ADVERTISING_REPORT || - rxmsg[3] == BT_HCI_EVT_LE_EXT_ADVERTISING_REPORT)) { + (rxmsg[3] == BT_HCI_EVT_LE_ADVERTISING_REPORT)) { discardable = true; timeout = K_NO_WAIT; } diff --git a/drivers/bluetooth/hci/userchan.c b/drivers/bluetooth/hci/userchan.c index 1dc5a9f5dc5..d856f89e94c 100644 --- a/drivers/bluetooth/hci/userchan.c +++ b/drivers/bluetooth/hci/userchan.c @@ -64,8 +64,7 @@ static struct net_buf *get_rx(const uint8_t *buf) switch (buf[0]) { case H4_EVT: if (buf[1] == BT_HCI_EVT_LE_META_EVENT && - (buf[3] == BT_HCI_EVT_LE_ADVERTISING_REPORT || - buf[3] == BT_HCI_EVT_LE_EXT_ADVERTISING_REPORT)) { + (buf[3] == BT_HCI_EVT_LE_ADVERTISING_REPORT)) { discardable = true; timeout = K_NO_WAIT; } diff --git a/subsys/bluetooth/host/Kconfig b/subsys/bluetooth/host/Kconfig index 3468a6d462d..d9ba7824064 100644 --- a/subsys/bluetooth/host/Kconfig +++ b/subsys/bluetooth/host/Kconfig @@ -485,6 +485,17 @@ config BT_BACKGROUND_SCAN_WINDOW int "Scan window used for background scanning in 0.625 ms units" default 18 range 4 16384 + +config BT_EXT_SCAN_BUF_SIZE + int "Maximum advertisement report size" + depends on BT_EXT_ADV + range 1 1650 + default 229 + help + Maximum size of an advertisement report in octets. If the advertisement + provided by the controller is larger than this buffer size, + the remaining data will be discarded. + endif # BT_OBSERVER config BT_SCAN_WITH_IDENTITY diff --git a/subsys/bluetooth/host/scan.c b/subsys/bluetooth/host/scan.c index 033c9ca9cad..84124863244 100644 --- a/subsys/bluetooth/host/scan.c +++ b/subsys/bluetooth/host/scan.c @@ -13,6 +13,7 @@ #include #include #include +#include #include "hci_core.h" #include "conn_internal.h" @@ -27,6 +28,42 @@ static bt_le_scan_cb_t *scan_dev_found_cb; static sys_slist_t scan_cbs = SYS_SLIST_STATIC_INIT(&scan_cbs); #if defined(CONFIG_BT_EXT_ADV) +/* A buffer used to reassemble advertisement data from the controller. */ +NET_BUF_SIMPLE_DEFINE(ext_scan_buf, CONFIG_BT_EXT_SCAN_BUF_SIZE); + +struct fragmented_advertiser { + bt_addr_le_t addr; + uint8_t sid; + enum { + FRAG_ADV_INACTIVE, + FRAG_ADV_REASSEMBLING, + FRAG_ADV_DISCARDING, + } state; +}; + +static struct fragmented_advertiser reassembling_advertiser; + +static bool fragmented_advertisers_equal(const struct fragmented_advertiser *a, + const bt_addr_le_t *addr, uint8_t sid) +{ + /* Two advertisers are equal if they are the same adv set from the same device */ + return a->sid == sid && bt_addr_le_cmp(&a->addr, addr) == 0; +} + +/* Sets the address and sid of the advertiser to be reassembled. */ +static void init_reassembling_advertiser(const bt_addr_le_t *addr, uint8_t sid) +{ + bt_addr_le_copy(&reassembling_advertiser.addr, addr); + reassembling_advertiser.sid = sid; + reassembling_advertiser.state = FRAG_ADV_REASSEMBLING; +} + +static void reset_reassembling_advertiser(void) +{ + net_buf_simple_reset(&ext_scan_buf); + reassembling_advertiser.state = FRAG_ADV_INACTIVE; +} + #if defined(CONFIG_BT_PER_ADV_SYNC) static struct bt_le_per_adv_sync *get_pending_per_adv_sync(void); static struct bt_le_per_adv_sync per_adv_sync_pool[CONFIG_BT_PER_ADV_SYNC_MAX]; @@ -37,6 +74,9 @@ static sys_slist_t pa_sync_cbs = SYS_SLIST_STATIC_INIT(&pa_sync_cbs); void bt_scan_reset(void) { scan_dev_found_cb = NULL; +#if defined(CONFIG_BT_EXT_ADV) + reset_reassembling_advertiser(); +#endif } static int set_le_ext_scan_enable(uint8_t enable, uint16_t duration) @@ -392,7 +432,7 @@ static uint8_t get_adv_props(uint8_t evt_type) } static void le_adv_recv(bt_addr_le_t *addr, struct bt_le_scan_recv_info *info, - struct net_buf *buf, uint8_t len) + struct net_buf_simple *buf, uint16_t len) { struct bt_le_scan_cb *listener, *next; struct net_buf_simple_state state; @@ -423,23 +463,22 @@ static void le_adv_recv(bt_addr_le_t *addr, struct bt_le_scan_recv_info *info, info->addr = &id_addr; if (scan_dev_found_cb) { - net_buf_simple_save(&buf->b, &state); + net_buf_simple_save(buf, &state); buf->len = len; - scan_dev_found_cb(&id_addr, info->rssi, info->adv_type, - &buf->b); + scan_dev_found_cb(&id_addr, info->rssi, info->adv_type, buf); - net_buf_simple_restore(&buf->b, &state); + net_buf_simple_restore(buf, &state); } SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&scan_cbs, listener, next, node) { if (listener->recv) { - net_buf_simple_save(&buf->b, &state); + net_buf_simple_save(buf, &state); buf->len = len; - listener->recv(info, &buf->b); + listener->recv(info, buf); - net_buf_simple_restore(&buf->b, &state); + net_buf_simple_restore(buf, &state); } } @@ -507,15 +546,34 @@ static uint8_t get_adv_type(uint8_t evt_type) } } +static void create_ext_adv_info(struct bt_hci_evt_le_ext_advertising_info const *const evt, + struct bt_le_scan_recv_info *const scan_info) +{ + scan_info->primary_phy = bt_get_phy(evt->prim_phy); + scan_info->secondary_phy = bt_get_phy(evt->sec_phy); + scan_info->tx_power = evt->tx_power; + scan_info->rssi = evt->rssi; + scan_info->sid = evt->sid; + scan_info->interval = sys_le16_to_cpu(evt->interval); + scan_info->adv_type = get_adv_type(evt->evt_type); + /* Convert "Legacy" property to Extended property. */ + scan_info->adv_props = evt->evt_type ^ BT_HCI_LE_ADV_PROP_LEGACY; + scan_info->adv_props &= BIT_MASK(5); +} + void bt_hci_le_adv_ext_report(struct net_buf *buf) { uint8_t num_reports = net_buf_pull_u8(buf); - BT_DBG("Adv number of reports %u", num_reports); + BT_DBG("Adv number of reports %u", num_reports); while (num_reports--) { struct bt_hci_evt_le_ext_advertising_info *evt; - struct bt_le_scan_recv_info adv_info; + struct bt_le_scan_recv_info scan_info; + uint16_t data_status; + bool is_report_complete; + bool more_to_come; + bool is_new_advertiser; if (buf->len < sizeof(*evt)) { BT_ERR("Unexpected end of buffer"); @@ -523,34 +581,99 @@ void bt_hci_le_adv_ext_report(struct net_buf *buf) } evt = net_buf_pull_mem(buf, sizeof(*evt)); + data_status = BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS(evt->evt_type); + is_report_complete = data_status == BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS_COMPLETE; + more_to_come = data_status == BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS_PARTIAL; - adv_info.primary_phy = bt_get_phy(evt->prim_phy); - adv_info.secondary_phy = bt_get_phy(evt->sec_phy); - adv_info.tx_power = evt->tx_power; - adv_info.rssi = evt->rssi; - adv_info.sid = evt->sid; - adv_info.interval = sys_le16_to_cpu(evt->interval); - - adv_info.adv_type = get_adv_type(evt->evt_type); - /* Convert "Legacy" property to Extended property. */ - adv_info.adv_props = evt->evt_type ^ BT_HCI_LE_ADV_PROP_LEGACY; - - if (BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS(evt->evt_type) == - BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS_PARTIAL) { - /* Handling of incomplete reports is currently not - * handled in the host. The remaining advertising - * reports may therefore contain partial data. + if (evt->evt_type & BT_HCI_LE_ADV_EVT_TYPE_LEGACY) { + /* Legacy advertising reports are complete. + * Create event immediately. */ - BT_WARN("Incomplete adv report"); + create_ext_adv_info(evt, &scan_info); + le_adv_recv(&evt->addr, &scan_info, &buf->b, evt->length); + continue; } - le_adv_recv(&evt->addr, &adv_info, buf, evt->length); + is_new_advertiser = reassembling_advertiser.state == FRAG_ADV_INACTIVE || + !fragmented_advertisers_equal(&reassembling_advertiser, + &evt->addr, evt->sid); + + if (is_new_advertiser && is_report_complete) { + /* Only advertising report from this advertiser. + * Create event immediately. + */ + create_ext_adv_info(evt, &scan_info); + le_adv_recv(&evt->addr, &scan_info, &buf->b, evt->length); + continue; + } + + if (is_new_advertiser && reassembling_advertiser.state == FRAG_ADV_REASSEMBLING) { + BT_WARN("Received an incomplete advertising report while reassembling " + "advertising reports from a different advertiser. The advertising " + "report is discarded and future scan results may be incomplete. " + "Interleaving of fragmented advertising reports from different " + "advertisers is not yet supported."); + (void)net_buf_pull_mem(buf, evt->length); + continue; + } + + if (data_status == BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS_INCOMPLETE) { + /* Controller truncated, no more data will come. + * We do not need to keep track of this advertiser. + * Discard this report. + */ + (void)net_buf_pull_mem(buf, evt->length); + reset_reassembling_advertiser(); + continue; + } + + if (is_new_advertiser) { + /* We are not reassembling reports from an advertiser and + * this is the first report from the new advertiser. + * Initialize the new advertiser. + */ + __ASSERT_NO_MSG(reassembling_advertiser.state == FRAG_ADV_INACTIVE); + init_reassembling_advertiser(&evt->addr, evt->sid); + } + + if (evt->length + ext_scan_buf.len > ext_scan_buf.size) { + /* The report does not fit in the reassemby buffer + * Discard this and future reports from the advertiser. + */ + reassembling_advertiser.state = FRAG_ADV_DISCARDING; + } + + if (reassembling_advertiser.state == FRAG_ADV_DISCARDING) { + (void)net_buf_pull_mem(buf, evt->length); + if (!more_to_come) { + /* We do no longer need to keep track of this advertiser as + * all the expected data is received. + */ + reset_reassembling_advertiser(); + } + continue; + } + + net_buf_simple_add_mem(&ext_scan_buf, buf->data, evt->length); + if (more_to_come) { + /* The controller will send additional reports to be reassembled */ + continue; + } + + /* No more data coming from the controller. + * Create event. + */ + __ASSERT_NO_MSG(is_report_complete); + create_ext_adv_info(evt, &scan_info); + le_adv_recv(&evt->addr, &scan_info, &ext_scan_buf, ext_scan_buf.len); + + /* We do no longer need to keep track of this advertiser. */ + reset_reassembling_advertiser(); net_buf_pull(buf, evt->length); } } - #if defined(CONFIG_BT_PER_ADV_SYNC) static void per_adv_sync_delete(struct bt_le_per_adv_sync *per_adv_sync) { @@ -991,7 +1114,7 @@ void bt_hci_le_adv_report(struct net_buf *buf) adv_info.adv_type = evt->evt_type; adv_info.adv_props = get_adv_props(evt->evt_type); - le_adv_recv(&evt->addr, &adv_info, buf, evt->length); + le_adv_recv(&evt->addr, &adv_info, &buf->b, evt->length); net_buf_pull(buf, evt->length + sizeof(adv_info.rssi)); } diff --git a/tests/bluetooth/host_long_adv_recv/CMakeLists.txt b/tests/bluetooth/host_long_adv_recv/CMakeLists.txt new file mode 100644 index 00000000000..da5b449c986 --- /dev/null +++ b/tests/bluetooth/host_long_adv_recv/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(host_long_adv_recv) + +target_sources(app PRIVATE src/main.c) diff --git a/tests/bluetooth/host_long_adv_recv/prj.conf b/tests/bluetooth/host_long_adv_recv/prj.conf new file mode 100644 index 00000000000..c7b3600e999 --- /dev/null +++ b/tests/bluetooth/host_long_adv_recv/prj.conf @@ -0,0 +1,19 @@ +CONFIG_TEST=y +CONFIG_ZTEST=y +CONFIG_ZTEST_MOCKING=y + +CONFIG_BT=y +CONFIG_BT_CTLR=n +CONFIG_BT_HCI=n +CONFIG_BT_HCI_RAW=n +CONFIG_BT_OBSERVER=y +CONFIG_BT_NO_DRIVER=y +CONFIG_BT_RECV_IS_RX_THREAD=y +CONFIG_BT_EXT_ADV=y + +CONFIG_BT_DEBUG_LOG=y +CONFIG_BT_DEBUG_HCI_CORE=n +CONFIG_BT_DEBUG_HCI_DRIVER=n +CONFIG_BT_LOG_LEVEL_DBG=y + +CONFIG_BT_EXT_SCAN_BUF_SIZE=91 diff --git a/tests/bluetooth/host_long_adv_recv/src/main.c b/tests/bluetooth/host_long_adv_recv/src/main.c new file mode 100644 index 00000000000..14164705f7d --- /dev/null +++ b/tests/bluetooth/host_long_adv_recv/src/main.c @@ -0,0 +1,493 @@ +/* main.c - Host long advertising receive */ + +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#define LOG_LEVEL CONFIG_BT_LOG_LEVEL +#include +LOG_MODULE_REGISTER(host_test_app); + +struct test_adv_report { + uint8_t data[CONFIG_BT_EXT_SCAN_BUF_SIZE]; + uint8_t length; + uint16_t evt_prop; + bt_addr_le_t addr; +}; + +#define COMPLETE BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS_COMPLETE << 5 +#define MORE_TO_COME BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS_PARTIAL << 5 +#define TRUNCATED BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS_INCOMPLETE << 5 + +/* Command handler structure for cmd_handle(). */ +struct cmd_handler { + uint16_t opcode; /* HCI command opcode */ + uint8_t len; /* HCI command response length */ + void (*handler)(struct net_buf *buf, struct net_buf **evt, uint8_t len, uint16_t opcode); +}; + +/* Add event to net_buf. */ +static void evt_create(struct net_buf *buf, uint8_t evt, uint8_t len) +{ + struct bt_hci_evt_hdr *hdr; + + hdr = net_buf_add(buf, sizeof(*hdr)); + hdr->evt = evt; + hdr->len = len; +} + +static void le_meta_evt_create(struct bt_hci_evt_le_meta_event *evt, uint8_t subevent) +{ + evt->subevent = subevent; +} + +static void adv_info_create(struct bt_hci_evt_le_ext_advertising_info *evt, uint16_t evt_type, + const bt_addr_le_t *const addr, uint8_t length) +{ + evt->evt_type = evt_type; + bt_addr_le_copy(&evt->addr, addr); + evt->prim_phy = 0; + evt->sec_phy = 0; + evt->sid = 0; + evt->tx_power = 0; + evt->rssi = 0; + evt->interval = 0; + bt_addr_le_copy(&evt->direct_addr, BT_ADDR_LE_NONE); + evt->length = length; +} + +/* Create a command complete event. */ +static void *cmd_complete(struct net_buf **buf, uint8_t plen, uint16_t opcode) +{ + struct bt_hci_evt_cmd_complete *cc; + + *buf = bt_buf_get_evt(BT_HCI_EVT_CMD_COMPLETE, false, K_FOREVER); + evt_create(*buf, BT_HCI_EVT_CMD_COMPLETE, sizeof(*cc) + plen); + cc = net_buf_add(*buf, sizeof(*cc)); + cc->ncmd = 1U; + cc->opcode = sys_cpu_to_le16(opcode); + + return net_buf_add(*buf, plen); +} + +/* Loop over handlers to try to handle the command given by opcode. */ +static int cmd_handle_helper(uint16_t opcode, struct net_buf *cmd, struct net_buf **evt, + const struct cmd_handler *handlers, size_t num_handlers) +{ + for (size_t i = 0; i < num_handlers; i++) { + const struct cmd_handler *handler = &handlers[i]; + + if (handler->opcode != opcode) { + continue; + } + + if (handler->handler) { + handler->handler(cmd, evt, handler->len, opcode); + + return 0; + } + } + + zassert_unreachable("opcode %X failed", opcode); + + return -EINVAL; +} + +/* Lookup the command opcode and invoke handler. */ +static int cmd_handle(struct net_buf *cmd, const struct cmd_handler *handlers, size_t num_handlers) +{ + struct net_buf *evt = NULL; + struct bt_hci_evt_cc_status *ccst; + struct bt_hci_cmd_hdr *chdr; + uint16_t opcode; + int err; + + chdr = net_buf_pull_mem(cmd, sizeof(*chdr)); + opcode = sys_le16_to_cpu(chdr->opcode); + + err = cmd_handle_helper(opcode, cmd, &evt, handlers, num_handlers); + + if (err == -EINVAL) { + ccst = cmd_complete(&evt, sizeof(*ccst), opcode); + ccst->status = BT_HCI_ERR_UNKNOWN_CMD; + } + + if (evt) { + bt_recv_prio(evt); + } + + return err; +} + +/* Generic command complete with success status. */ +static void generic_success(struct net_buf *buf, struct net_buf **evt, uint8_t len, uint16_t opcode) +{ + struct bt_hci_evt_cc_status *ccst; + + ccst = cmd_complete(evt, len, opcode); + + /* Fill any event parameters with zero */ + (void)memset(ccst, 0, len); + + ccst->status = BT_HCI_ERR_SUCCESS; +} + +/* Bogus handler for BT_HCI_OP_READ_LOCAL_FEATURES. */ +static void read_local_features(struct net_buf *buf, struct net_buf **evt, uint8_t len, + uint16_t opcode) +{ + struct bt_hci_rp_read_local_features *rp; + + rp = cmd_complete(evt, sizeof(*rp), opcode); + rp->status = 0x00; + (void)memset(rp->features, 0xFF, sizeof(rp->features)); +} + +/* Bogus handler for BT_HCI_OP_READ_SUPPORTED_COMMANDS. */ +static void read_supported_commands(struct net_buf *buf, struct net_buf **evt, uint8_t len, + uint16_t opcode) +{ + struct bt_hci_rp_read_supported_commands *rp; + + rp = cmd_complete(evt, sizeof(*rp), opcode); + (void)memset(rp->commands, 0xFF, sizeof(rp->commands)); + rp->status = 0x00; +} + +/* Bogus handler for BT_HCI_OP_LE_READ_LOCAL_FEATURES. */ +static void le_read_local_features(struct net_buf *buf, struct net_buf **evt, uint8_t len, + uint16_t opcode) +{ + struct bt_hci_rp_le_read_local_features *rp; + + rp = cmd_complete(evt, sizeof(*rp), opcode); + rp->status = 0x00; + (void)memset(rp->features, 0xFF, sizeof(rp->features)); +} + +/* Bogus handler for BT_HCI_OP_LE_READ_SUPP_STATES. */ +static void le_read_supp_states(struct net_buf *buf, struct net_buf **evt, uint8_t len, + uint16_t opcode) +{ + struct bt_hci_rp_le_read_supp_states *rp; + + rp = cmd_complete(evt, sizeof(*rp), opcode); + rp->status = 0x00; + (void)memset(&rp->le_states, 0xFF, sizeof(rp->le_states)); +} + +/* Setup handlers needed for bt_enable to function. */ +static const struct cmd_handler cmds[] = { + { BT_HCI_OP_READ_LOCAL_VERSION_INFO, sizeof(struct bt_hci_rp_read_local_version_info), + generic_success }, + { BT_HCI_OP_READ_SUPPORTED_COMMANDS, sizeof(struct bt_hci_rp_read_supported_commands), + read_supported_commands }, + { BT_HCI_OP_READ_LOCAL_FEATURES, sizeof(struct bt_hci_rp_read_local_features), + read_local_features }, + { BT_HCI_OP_READ_BD_ADDR, sizeof(struct bt_hci_rp_read_bd_addr), generic_success }, + { BT_HCI_OP_SET_EVENT_MASK, sizeof(struct bt_hci_evt_cc_status), generic_success }, + { BT_HCI_OP_LE_SET_EVENT_MASK, sizeof(struct bt_hci_evt_cc_status), generic_success }, + { BT_HCI_OP_LE_READ_LOCAL_FEATURES, sizeof(struct bt_hci_rp_le_read_local_features), + le_read_local_features }, + { BT_HCI_OP_LE_READ_SUPP_STATES, sizeof(struct bt_hci_rp_le_read_supp_states), + le_read_supp_states }, + { BT_HCI_OP_LE_RAND, sizeof(struct bt_hci_rp_le_rand), generic_success }, + { BT_HCI_OP_LE_SET_RANDOM_ADDRESS, sizeof(struct bt_hci_cp_le_set_random_address), + generic_success }, + { BT_HCI_OP_RESET, 0, generic_success }, +}; + +/* HCI driver open. */ +static int driver_open(void) +{ + return 0; +} + +/* HCI driver send. */ +static int driver_send(struct net_buf *buf) +{ + zassert_true(cmd_handle(buf, cmds, ARRAY_SIZE(cmds)) == 0, "Unknown HCI command"); + + net_buf_unref(buf); + + return 0; +} + +/* HCI driver structure. */ +static const struct bt_hci_driver drv = { + .name = "test", + .bus = BT_HCI_DRIVER_BUS_VIRTUAL, + .open = driver_open, + .send = driver_send, + .quirks = 0, +}; + +struct bt_recv_job_data { + struct k_work work; /* Work item */ + struct k_sem *sync; /* Semaphore to synchronize with */ + struct net_buf *buf; /* Net buffer to be passed to bt_recv() */ +} job_data[CONFIG_BT_BUF_EVT_RX_COUNT]; + +#define job(buf) (&job_data[net_buf_id(buf)]) + +/* Work item handler for bt_recv() jobs. */ +static void bt_recv_job_cb(struct k_work *item) +{ + struct bt_recv_job_data *data = CONTAINER_OF(item, struct bt_recv_job_data, work); + + /* Send net buffer to host */ + bt_recv(data->buf); + + /* Wake up bt_recv_job_submit */ + k_sem_give(job(data->buf)->sync); +} + +/* Prepare a job to call bt_recv() to be submitted to the system workqueue. */ +static void bt_recv_job_submit(struct net_buf *buf) +{ + struct k_sem sync_sem; + + /* Store the net buffer to be passed to bt_recv */ + job(buf)->buf = buf; + + /* Initialize job work item/semaphore */ + k_work_init(&job(buf)->work, bt_recv_job_cb); + k_sem_init(&sync_sem, 0, 1); + job(buf)->sync = &sync_sem; + + /* Make sure the buffer stays around until the command completes */ + buf = net_buf_ref(buf); + + /* Submit the work item */ + k_work_submit(&job(buf)->work); + + /* Wait for bt_recv_job_cb to be done */ + k_sem_take(&sync_sem, K_FOREVER); + + net_buf_unref(buf); +} + +/* Semaphore to test if the prop callback was called. */ +static K_SEM_DEFINE(prop_cb_sem, 0, 1); + +static void *adv_report_evt(struct net_buf *buf, uint8_t data_len, uint16_t evt_type, + const bt_addr_le_t *const addr) +{ + struct bt_hci_evt_le_meta_event *meta_evt; + struct bt_hci_evt_le_ext_advertising_info *evt; + + evt_create(buf, BT_HCI_EVT_LE_META_EVENT, sizeof(*meta_evt) + sizeof(*evt) + data_len + 1); + meta_evt = net_buf_add(buf, sizeof(*meta_evt)); + le_meta_evt_create(meta_evt, BT_HCI_EVT_LE_EXT_ADVERTISING_REPORT); + net_buf_add_u8(buf, 1); /* Number of reports */ + evt = net_buf_add(buf, sizeof(*evt)); + adv_info_create(evt, evt_type, addr, data_len); + + return net_buf_add(buf, data_len); +} + +/* Send a prop event report wit the given data. */ +static void send_adv_report(const struct test_adv_report *report) +{ + LOG_DBG("Sending adv report"); + struct net_buf *buf; + uint8_t *adv_data; + + buf = bt_buf_get_rx(BT_BUF_EVT, K_FOREVER); + adv_data = adv_report_evt(buf, report->length, report->evt_prop, &report->addr); + memcpy(adv_data, &report->data, report->length); + + /* Submit job */ + bt_recv_job_submit(buf); +} + +static uint16_t get_expected_length(void) +{ + return ztest_get_return_value(); +} + +static uint8_t *get_expected_data(void) +{ + return ztest_get_return_value_ptr(); +} + +static void scan_recv_cb(const struct bt_le_scan_recv_info *info, struct net_buf_simple *buf) +{ + ARG_UNUSED(info); + + LOG_DBG("Received event with length %u", buf->len); + + const uint16_t expected_length = get_expected_length(); + const uint8_t *expected_data = get_expected_data(); + + zassert_equal(buf->len, expected_length, "Lengths should be equal"); + zassert_mem_equal(buf->data, expected_data, buf->len, "Data should be equal"); +} + +static void scan_timeout_cb(void) +{ + zassert_unreachable("Timeout should not happen"); +} + +static void generate_sequence(uint8_t *dest, uint16_t len, uint8_t range_start, uint8_t range_end) +{ + uint16_t written = 0; + uint8_t value = range_start; + + while (written < len) { + *dest++ = value++; + written++; + if (value > range_end) { + value = range_start; + } + } +} + +/* Test. */ +static void test_host_long_adv_recv(void) +{ + /* Register the test HCI driver */ + bt_hci_driver_register(&drv); + + /* Go! Wait until Bluetooth initialization is done */ + zassert_true((bt_enable(NULL) == 0), "bt_enable failed"); + + static struct bt_le_scan_cb scan_callbacks = { .recv = scan_recv_cb, + .timeout = scan_timeout_cb }; + bt_le_scan_cb_register(&scan_callbacks); + bt_addr_le_t addr_a; + bt_addr_le_t addr_b; + bt_addr_le_t addr_c; + bt_addr_le_t addr_d; + + bt_addr_le_create_static(&addr_a); + bt_addr_le_create_static(&addr_b); + bt_addr_le_create_static(&addr_c); + bt_addr_le_create_static(&addr_d); + + struct test_adv_report report_a_1 = { .length = 30, .evt_prop = MORE_TO_COME }; + struct test_adv_report report_a_2 = { .length = 30, .evt_prop = COMPLETE }; + + bt_addr_le_copy(&report_a_1.addr, &addr_a); + bt_addr_le_copy(&report_a_2.addr, &addr_a); + + struct test_adv_report report_b_1 = { .length = 30, .evt_prop = MORE_TO_COME }; + struct test_adv_report report_b_2 = { .length = 30, .evt_prop = COMPLETE }; + + bt_addr_le_copy(&report_b_1.addr, &addr_b); + bt_addr_le_copy(&report_b_2.addr, &addr_b); + + struct test_adv_report report_c = { .length = 30, + .evt_prop = COMPLETE | BT_HCI_LE_ADV_EVT_TYPE_LEGACY }; + + bt_addr_le_copy(&report_c.addr, &addr_c); + + struct test_adv_report report_d = { .length = 30, .evt_prop = TRUNCATED }; + + bt_addr_le_copy(&report_c.addr, &addr_c); + + struct test_adv_report report_a_combined = { .length = report_a_1.length + + report_a_2.length }; + + struct test_adv_report report_a_1_repeated = { .length = CONFIG_BT_EXT_SCAN_BUF_SIZE }; + + struct test_adv_report report_b_combined = { .length = report_b_1.length + + report_b_2.length }; + + generate_sequence(report_a_combined.data, report_a_combined.length, 'A', 'Z'); + generate_sequence(report_b_combined.data, report_b_combined.length, 'a', 'z'); + generate_sequence(report_c.data, report_c.length, '0', '9'); + + (void)memcpy(report_a_1.data, report_a_combined.data, report_a_1.length); + (void)memcpy(report_a_2.data, &report_a_combined.data[report_a_1.length], + report_a_2.length); + + for (int i = 0; i < report_a_1_repeated.length; i += report_a_1.length) { + memcpy(&report_a_1_repeated.data[i], report_a_1.data, + MIN(report_a_1.length, (report_a_1_repeated.length - i))); + } + + (void)memcpy(report_b_1.data, report_b_combined.data, report_b_1.length); + (void)memcpy(report_b_2.data, &report_b_combined.data[report_b_1.length], + report_b_2.length); + + /* Check that non-interleaved fragmented adv reports work */ + ztest_returns_value(get_expected_data, report_a_combined.data); + ztest_returns_value(get_expected_length, report_a_combined.length); /* Expect a */ + ztest_returns_value(get_expected_data, report_b_combined.data); + ztest_returns_value(get_expected_length, report_b_combined.length); /* Then b */ + send_adv_report(&report_a_1); + send_adv_report(&report_a_2); + send_adv_report(&report_b_1); + send_adv_report(&report_b_2); + + /* Check that legacy adv reports interleaved with fragmented adv reports work */ + ztest_returns_value(get_expected_data, report_c.data); + ztest_returns_value(get_expected_length, report_c.length); /* Expect c */ + ztest_returns_value(get_expected_data, report_a_combined.data); + ztest_returns_value(get_expected_length, report_a_combined.length); /* Then a */ + send_adv_report(&report_a_1); + send_adv_report(&report_c); /* Interleaved legacy adv report */ + send_adv_report(&report_a_2); + + /* Check that complete adv reports interleaved with fragmented adv reports work */ + ztest_returns_value(get_expected_data, report_b_2.data); + ztest_returns_value(get_expected_length, report_b_2.length); /* Expect b */ + ztest_returns_value(get_expected_data, report_a_combined.data); + ztest_returns_value(get_expected_length, report_a_combined.length); /* Then a */ + send_adv_report(&report_a_1); + send_adv_report(&report_b_2); /* Interleaved short extended adv report */ + send_adv_report(&report_a_2); + + /* Check that fragmented adv reports from one peer are received, + * and that interleaved fragmented adv reports from other peers are discarded + */ + ztest_returns_value(get_expected_data, report_a_combined.data); + ztest_returns_value(get_expected_length, report_a_combined.length); /* Expect a */ + ztest_returns_value(get_expected_data, report_b_2.data); + ztest_returns_value(get_expected_length, + report_b_2.length); /* Then b, INCOMPLETE REPORT */ + send_adv_report(&report_a_1); + send_adv_report(&report_b_1); /* Interleaved fragmented adv report, NOT SUPPORTED */ + send_adv_report(&report_a_2); + send_adv_report(&report_b_2); + + /* Check that host discards the data if the controller keeps sending + * incomplete packets. + */ + for (int i = 0; i < (2 + (CONFIG_BT_EXT_SCAN_BUF_SIZE / report_a_1.length)); i++) { + send_adv_report(&report_a_1); + } + send_adv_report(&report_a_2); + + /* Check that controller truncated reports do not generate events */ + send_adv_report(&report_d); + + /* Check that reports from a different advertiser works after truncation */ + ztest_returns_value(get_expected_data, report_b_combined.data); + ztest_returns_value(get_expected_length, report_b_combined.length); /* Expect b */ + send_adv_report(&report_b_1); + send_adv_report(&report_b_2); +} + +/* test case main entry */ +void test_main(void) +{ + ztest_test_suite(test_host_long_adv_recv, ztest_unit_test(test_host_long_adv_recv)); + + ztest_run_test_suite(test_host_long_adv_recv); +} diff --git a/tests/bluetooth/host_long_adv_recv/testcase.yaml b/tests/bluetooth/host_long_adv_recv/testcase.yaml new file mode 100644 index 00000000000..54f11d81b09 --- /dev/null +++ b/tests/bluetooth/host_long_adv_recv/testcase.yaml @@ -0,0 +1,4 @@ +tests: + bluetooth.host_long_adv_recv: + platform_allow: native_posix native_posix_64 + tags: bluetooth host