blob: 2e1b2b7a7f2181bd700169ecdc6b2fc525ad2372 [file] [log] [blame]
/* main.c - Host long advertising receive */
/*
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/fff.h>
#include <zephyr/kernel.h>
#include <zephyr/ztest.h>
#include <errno.h>
#include <zephyr/tc_util.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/buf.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/drivers/bluetooth/hci_driver.h>
#include <zephyr/sys/byteorder.h>
#define LOG_LEVEL CONFIG_BT_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(host_test_app);
DEFINE_FFF_GLOBALS;
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);
}
FAKE_VALUE_FUNC(struct test_adv_report, get_expected_report);
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 struct test_adv_report expected = get_expected_report();
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;
}
}
}
ZTEST_SUITE(long_adv_rx_tests, NULL, NULL, NULL, NULL, NULL);
ZTEST(long_adv_rx_tests, test_host_long_adv_recv)
{
struct test_adv_report expected_reports[2];
/* 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 */
expected_reports[0] = report_a_combined;
expected_reports[1] = report_b_combined;
SET_RETURN_SEQ(get_expected_report, expected_reports, 2);
send_adv_report(&report_a_1);
send_adv_report(&report_a_2);
send_adv_report(&report_b_1);
send_adv_report(&report_b_2);
zassert_equal(2, get_expected_report_fake.call_count);
RESET_FAKE(get_expected_report);
FFF_RESET_HISTORY();
/* Check that legacy adv reports interleaved with fragmented adv reports work */
expected_reports[0] = report_c;
expected_reports[1] = report_a_combined;
SET_RETURN_SEQ(get_expected_report, expected_reports, 2);
send_adv_report(&report_a_1);
send_adv_report(&report_c); /* Interleaved legacy adv report */
send_adv_report(&report_a_2);
zassert_equal(2, get_expected_report_fake.call_count);
RESET_FAKE(get_expected_report);
FFF_RESET_HISTORY();
/* Check that complete adv reports interleaved with fragmented adv reports work */
expected_reports[0] = report_b_2;
expected_reports[1] = report_a_combined;
SET_RETURN_SEQ(get_expected_report, expected_reports, 2);
send_adv_report(&report_a_1);
send_adv_report(&report_b_2); /* Interleaved short extended adv report */
send_adv_report(&report_a_2);
zassert_equal(2, get_expected_report_fake.call_count);
RESET_FAKE(get_expected_report);
FFF_RESET_HISTORY();
/* Check that fragmented adv reports from one peer are received,
* and that interleaved fragmented adv reports from other peers are discarded
*/
expected_reports[0] = report_a_combined;
expected_reports[1] = report_b_2;
SET_RETURN_SEQ(get_expected_report, expected_reports, 2);
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);
zassert_equal(2, get_expected_report_fake.call_count);
RESET_FAKE(get_expected_report);
FFF_RESET_HISTORY();
/* Check that host discards the data if the controller keeps sending
* incomplete packets.
*/
expected_reports[0] = report_b_combined;
SET_RETURN_SEQ(get_expected_report, expected_reports, 1);
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 */
send_adv_report(&report_b_1);
send_adv_report(&report_b_2);
zassert_equal(1, get_expected_report_fake.call_count);
}