Bluetooth: Host: Reassemble extended advertising reports
The host reassembles fragmented advertising reports from the controller. Non-complete advertising reports from different advertisers may not be interleaved. If non-complete advertising reports from an advertiser is received while advertising reports from another advertiser is reassembled, an error message is logged and the advertising report is discarded. Future scan results may be incomplete. Advertising reports from legacy PDUs or complete extended advertising reports may be interleaved as these do not require reassembly. If the controller sends more advertising data than fits in the reassembly buffer, the data is truncated. Further advertising reports from the advertiser are discarded until the final complete advertising report is received and discarded. Signed-off-by: Herman Berget <herman.berget@nordicsemi.no>
This commit is contained in:
parent
821f49fbb4
commit
6ede31428d
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <bluetooth/iso.h>
|
||||
#include <bluetooth/buf.h>
|
||||
#include <bluetooth/direction.h>
|
||||
#include <bluetooth/addr.h>
|
||||
|
||||
#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));
|
||||
}
|
||||
|
|
8
tests/bluetooth/host_long_adv_recv/CMakeLists.txt
Normal file
8
tests/bluetooth/host_long_adv_recv/CMakeLists.txt
Normal file
|
@ -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)
|
19
tests/bluetooth/host_long_adv_recv/prj.conf
Normal file
19
tests/bluetooth/host_long_adv_recv/prj.conf
Normal file
|
@ -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
|
493
tests/bluetooth/host_long_adv_recv/src/main.c
Normal file
493
tests/bluetooth/host_long_adv_recv/src/main.c
Normal file
|
@ -0,0 +1,493 @@
|
|||
/* main.c - Host long advertising receive */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2021 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <tc_util.h>
|
||||
#include <ztest.h>
|
||||
|
||||
#include <bluetooth/hci.h>
|
||||
#include <bluetooth/buf.h>
|
||||
#include <bluetooth/bluetooth.h>
|
||||
#include <drivers/bluetooth/hci_driver.h>
|
||||
#include <sys/byteorder.h>
|
||||
|
||||
#define LOG_LEVEL CONFIG_BT_LOG_LEVEL
|
||||
#include <logging/log.h>
|
||||
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);
|
||||
}
|
4
tests/bluetooth/host_long_adv_recv/testcase.yaml
Normal file
4
tests/bluetooth/host_long_adv_recv/testcase.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
tests:
|
||||
bluetooth.host_long_adv_recv:
|
||||
platform_allow: native_posix native_posix_64
|
||||
tags: bluetooth host
|
Loading…
Reference in a new issue