/* * Copyright (c) 2023 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include "utils.h" #include "bstests.h" #include LOG_MODULE_REGISTER(dut, LOG_LEVEL_INF); DEFINE_FLAG(is_connected); DEFINE_FLAG(is_subscribed); DEFINE_FLAG(one_indication); DEFINE_FLAG(two_notifications); DEFINE_FLAG(flag_data_length_updated); static atomic_t nwrites; static atomic_t indications; static atomic_t notifications; /* Defined in conn.c */ extern void bt_conn_suspend_tx(bool suspend); static struct bt_conn *dconn; static void connected(struct bt_conn *conn, uint8_t conn_err) { char addr[BT_ADDR_LE_STR_LEN]; bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); if (conn_err) { FAIL("Failed to connect to %s (%u)", addr, conn_err); return; } LOG_DBG("%s", addr); dconn = bt_conn_ref(conn); SET_FLAG(is_connected); } static void disconnected(struct bt_conn *conn, uint8_t reason) { char addr[BT_ADDR_LE_STR_LEN]; bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); LOG_DBG("%p %s (reason 0x%02x)", conn, addr, reason); bt_conn_unref(dconn); UNSET_FLAG(is_connected); } static void data_len_updated(struct bt_conn *conn, struct bt_conn_le_data_len_info *info) { LOG_DBG("Data length updated: TX %d RX %d", info->tx_max_len, info->rx_max_len); SET_FLAG(flag_data_length_updated); } static void do_dlu(void) { int err; struct bt_conn_le_data_len_param param; param.tx_max_len = CONFIG_BT_CTLR_DATA_LENGTH_MAX; param.tx_max_time = 2500; err = bt_conn_le_data_len_update(dconn, ¶m); ASSERT(err == 0, "Can't update data length (err %d)\n", err); WAIT_FOR_FLAG(flag_data_length_updated); } BT_CONN_CB_DEFINE(conn_callbacks) = { .connected = connected, .disconnected = disconnected, .le_data_len_updated = data_len_updated, }; static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, struct net_buf_simple *ad) { char str[BT_ADDR_LE_STR_LEN]; struct bt_le_conn_param *param; struct bt_conn *conn; int err; err = bt_le_scan_stop(); if (err) { FAIL("Stop LE scan failed (err %d)", err); return; } bt_addr_le_to_str(addr, str, sizeof(str)); LOG_DBG("Connecting to %s", str); param = BT_LE_CONN_PARAM_DEFAULT; err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, param, &conn); if (err) { FAIL("Create conn failed (err %d)", err); return; } } static void connect(void) { int err; struct bt_le_scan_param scan_param = { .type = BT_LE_SCAN_TYPE_ACTIVE, .options = BT_LE_SCAN_OPT_NONE, .interval = BT_GAP_SCAN_FAST_INTERVAL, .window = BT_GAP_SCAN_FAST_WINDOW, }; UNSET_FLAG(is_connected); err = bt_le_scan_start(&scan_param, device_found); ASSERT(!err, "Scanning failed to start (err %d)\n", err); LOG_DBG("Central initiating connection..."); WAIT_FOR_FLAG(is_connected); LOG_INF("Connected as central"); /* No security support on the tinyhost unfortunately */ } static ssize_t written_to(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { LOG_INF("written to: handle 0x%x len %d flags 0x%x", attr->handle, len, flags); LOG_HEXDUMP_DBG(buf, len, "Write data"); if (atomic_get(&nwrites) == 0) { /* Suspend on the first write, which is an ATT Request */ LOG_INF("suspending HCI TX thread"); bt_conn_suspend_tx(true); } atomic_inc(&nwrites); return len; } #define test_service_uuid \ BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0xf0debc9a, 0x7856, 0x3412, 0x7856, 0x341278563412)) #define test_characteristic_uuid \ BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0xf2debc9a, 0x7856, 0x3412, 0x7856, 0x341278563412)) BT_GATT_SERVICE_DEFINE(test_gatt_service, BT_GATT_PRIMARY_SERVICE(test_service_uuid), BT_GATT_CHARACTERISTIC(test_characteristic_uuid, (BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_INDICATE), BT_GATT_PERM_READ | BT_GATT_PERM_WRITE, NULL, written_to, NULL), BT_GATT_CCC(NULL, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),); static uint8_t notified(struct bt_conn *conn, struct bt_gatt_subscribe_params *params, const void *data, uint16_t length) { static uint8_t notification[] = NOTIFICATION_PAYLOAD; static uint8_t indication[] = INDICATION_PAYLOAD; bool is_nfy; ASSERT(length >= sizeof(indication), "Unexpected data"); ASSERT(length <= sizeof(notification), "Unexpected data"); LOG_HEXDUMP_DBG(data, length, "HVx data"); is_nfy = memcmp(data, notification, length) == 0; LOG_INF("%s from 0x%x", is_nfy ? "notified" : "indicated", params->value_handle); if (is_nfy) { atomic_inc(¬ifications); } else { atomic_inc(&indications); } return BT_GATT_ITER_CONTINUE; } static void subscribed(struct bt_conn *conn, uint8_t err, struct bt_gatt_subscribe_params *params) { ASSERT(!err, "Subscribe failed (err %d)\n", err); ASSERT(params, "params is NULL\n"); SET_FLAG(is_subscribed); /* spoiler: tester doesn't really have attributes */ LOG_INF("Subscribed to Tester attribute"); } void subscribe(void) { int err; /* Handle values don't matter, as long as they match on the tester */ static struct bt_gatt_subscribe_params params = { .notify = notified, .subscribe = subscribed, .value = BT_GATT_CCC_NOTIFY | BT_GATT_CCC_INDICATE, .value_handle = HVX_HANDLE, .ccc_handle = (HVX_HANDLE + 1), }; err = bt_gatt_subscribe(dconn, ¶ms); ASSERT(!err, "Subscribe failed (err %d)\n", err); WAIT_FOR_FLAG(is_subscribed); } static void send_write_handle(void) { int err; uint16_t handle; uint8_t data[sizeof(handle)]; const struct bt_gatt_attr *attr = &test_gatt_service.attrs[2]; /* Inform tester which handle it should write to */ handle = bt_gatt_attr_get_handle(attr); sys_put_le16(handle, data); err = bt_gatt_notify(dconn, attr, data, sizeof(data)); ASSERT(!err, "Failed to transmit handle for write (err %d)\n", err); } void test_procedure_0(void) { LOG_DBG("Test start: ATT sequential protocol"); int err; err = bt_enable(NULL); ASSERT(err == 0, "Can't enable Bluetooth (err %d)\n", err); LOG_DBG("Central: Bluetooth initialized."); /* Test purpose: * Test Spec V.3 P.F 3.3.2 Sequential protocol * * Verify that a Zephyr host server/client combo can process * concurrently: one Request, one Indication, multiple * Notifications and multiple Commands. * * To do this, the application on the DUT will purposefully stall the * HCI TX thread, ensuring that the responses are not sent until the * tester has finished sending everything. * * Test procedure: * * [setup] * - connect ACL * - update data length (tinyhost doens't have recombination) * - dut: subscribe to INDICATE and NOTIFY on tester CHRC * - dut: send a handle the tester can write to * * [proc] * - tester: send one ATT write request * - tester: send one ATT indication * - tester: send two ATT notifications * - tester: send two ATT commands * * - dut: handle the REQuest, build & put the RSP PDU on the HCI TX queue * - dut: suspend the HCI TX thread * - dut: handle the INDication * - dut: handle the notifications * - dut: handle the (write) commands * - dut: resume the TX thread after a short while * * [verdict] * - all procedures complete successfully, no buffer allocation failures * or timeouts. */ connect(); subscribe(); do_dlu(); send_write_handle(); WAIT_FOR_VAL(indications, 1); WAIT_FOR_VAL(notifications, 2); /* One REQ, two CMDs */ WAIT_FOR_VAL(nwrites, 3); /* Send RSP to LL */ bt_conn_suspend_tx(false); PASS("DUT done\n"); } void test_tick(bs_time_t HW_device_time) { bs_trace_debug_time(0, "Simulation ends now.\n"); if (bst_result != Passed) { bst_result = Failed; bs_trace_error("Test did not pass before simulation ended.\n"); } } void test_init(void) { bst_ticker_set_next_tick_absolute(TEST_TIMEOUT_SIMULATED); bst_result = In_progress; } static const struct bst_test_instance test_to_add[] = { { .test_id = "dut", .test_pre_init_f = test_init, .test_tick_f = test_tick, .test_main_f = test_procedure_0, }, BSTEST_END_MARKER, }; static struct bst_test_list *install(struct bst_test_list *tests) { return bst_add_tests(tests, test_to_add); }; bst_test_install_t test_installers[] = {install, NULL}; int main(void) { bst_main(); return 0; }