/* Copyright (c) 2023 Nordic Semiconductor ASA * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include "testlib/adv.h" #include "testlib/att_read.h" #include "testlib/att_write.h" #include "bs_macro.h" #include "bs_sync.h" #include #include "testlib/log_utils.h" #include "testlib/scan.h" #include "testlib/security.h" /* This test uses system asserts to fail tests. */ BUILD_ASSERT(__ASSERT_ON); #define CENTRAL_DEVICE_NBR 0 #define PERIPHERAL_DEVICE_NBR 1 LOG_MODULE_REGISTER(main, LOG_LEVEL_DBG); #define UUID_1 \ BT_UUID_DECLARE_128(0xdb, 0x1f, 0xe2, 0x52, 0xf3, 0xc6, 0x43, 0x66, 0xb3, 0x92, 0x5d, \ 0xc6, 0xe7, 0xc9, 0x59, 0x9d) #define UUID_2 \ BT_UUID_DECLARE_128(0x3f, 0xa4, 0x7f, 0x44, 0x2e, 0x2a, 0x43, 0x05, 0xab, 0x38, 0x07, \ 0x8d, 0x16, 0xbf, 0x99, 0xf1) static ssize_t read_mtu_validation_chrc(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t buf_len, uint16_t offset) { LOG_DBG("Server side buf_len %u", buf_len); k_msleep(100); LOG_DBG("============================> trigger disconnect"); bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_POWER_OFF); /* We ain't read nothin' */ return 0; } static struct bt_gatt_attr attrs[] = { BT_GATT_PRIMARY_SERVICE(UUID_1), BT_GATT_CHARACTERISTIC(UUID_2, BT_GATT_CHRC_READ, BT_GATT_PERM_READ, read_mtu_validation_chrc, NULL, NULL), }; static struct bt_gatt_service svc = { .attrs = attrs, .attr_count = ARRAY_SIZE(attrs), }; static void find_the_chrc(struct bt_conn *conn, uint16_t *chrc_value_handle) { uint16_t svc_handle; uint16_t svc_end_handle; uint16_t chrc_end_handle; EXPECT_ZERO(bt_testlib_gatt_discover_primary(&svc_handle, &svc_end_handle, conn, UUID_1, 1, 0xffff)); LOG_DBG("svc_handle: %u, svc_end_handle: %u", svc_handle, svc_end_handle); EXPECT_ZERO(bt_testlib_gatt_discover_characteristic(chrc_value_handle, &chrc_end_handle, NULL, conn, UUID_2, (svc_handle + 1), svc_end_handle)); LOG_DBG("chrc_value_handle: %u, chrc_end_handle: %u", *chrc_value_handle, chrc_end_handle); } static void bs_sync_all_log(char *log_msg) { /* Everyone meets here. */ bt_testlib_bs_sync_all(); if (get_device_nbr() == 0) { LOG_WRN("Sync point: %s", log_msg); } /* Everyone waits for d0 to finish logging. */ bt_testlib_bs_sync_all(); } static inline void bt_enable_quiet(void) { bt_testlib_log_level_set("bt_hci_core", LOG_LEVEL_ERR); bt_testlib_log_level_set("bt_id", LOG_LEVEL_ERR); EXPECT_ZERO(bt_enable(NULL)); bt_testlib_log_level_set("bt_hci_core", LOG_LEVEL_INF); bt_testlib_log_level_set("bt_id", LOG_LEVEL_INF); } #define ITERATIONS 20 #define READ_PARAMS_COUNT 20 static struct bt_gatt_read_params closet[READ_PARAMS_COUNT]; static volatile int outstanding; static uint8_t gatt_read_cb(struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, const void *data, uint16_t length) { LOG_DBG("<------------------------- read done: err %d", err); outstanding--; return 0; } void a_test_iteration(int i) { bool central = (get_device_nbr() == CENTRAL_DEVICE_NBR); bool peripheral = (get_device_nbr() == PERIPHERAL_DEVICE_NBR); bt_addr_le_t adva; uint16_t chrc_value_handle = 0; struct bt_conn *conn = NULL; int err; LOG_DBG("############################## start iteration %d", i); bs_sync_all_log("Start iteration"); if (peripheral) { EXPECT_ZERO(bt_set_name("peripheral")); EXPECT_ZERO(bt_testlib_adv_conn(&conn, BT_ID_DEFAULT, bt_get_name())); } if (central) { EXPECT_ZERO(bt_testlib_scan_find_name(&adva, "peripheral")); EXPECT_ZERO(bt_testlib_connect(&adva, &conn)); /* Establish EATT bearers. */ EXPECT_ZERO(bt_testlib_secure(conn, BT_SECURITY_L2)); while (bt_eatt_count(conn) == 0) { k_msleep(100); }; } bs_sync_all_log("Connected"); /* Perform discovery. */ if (central) { find_the_chrc(conn, &chrc_value_handle); } else { /* Peripheral will use handle 0. * * This will return a permission denied from the central's * server. It doesn't matter as the only thing we want as the * peripheral is to also be trying to fill the TX queue with ATT * PDUs. */ } /* Test purpose: verify no allocated resource leaks when disconnecting * abruptly with non-empty queues. * * Test procedure (in a nutshell): * - open channels * - queue up lots of ATT bufs from both sides * - disconnect ACL * - see if anything stalls or leaks * * Run this procedure more times than there are said resources. */ for (int p = 0; p < ARRAY_SIZE(closet); p++) { memset(&closet[p], 0, sizeof(struct bt_gatt_read_params)); closet[p].handle_count = 1; closet[p].single.handle = chrc_value_handle; closet[p].func = gatt_read_cb; /* A disconnected channel (or ACL conn) can end up with * gatt_read returning -ENOMEM instead of -ENOTCONN. sometimes. */ LOG_DBG("-------------------------> gatt_read %d", p); err = bt_gatt_read(conn, &closet[p]); switch (err) { case -ENOMEM: case -ENOTCONN: LOG_DBG("not connected"); break; case 0: outstanding++; break; default: FAIL("unexpected error: %d\n", err); break; } } bt_testlib_wait_disconnected(conn); bt_testlib_conn_unref(&conn); k_msleep(1000); /* beauty rest */ EXPECT_ZERO(outstanding); LOG_DBG("ended iteration %d", i); } void the_test(void) { bool peripheral = (get_device_nbr() == PERIPHERAL_DEVICE_NBR); if (peripheral) { EXPECT_ZERO(bt_gatt_service_register(&svc)); } bt_enable_quiet(); for (int i = 0; i < ITERATIONS; i++) { a_test_iteration(i); } bs_sync_all_log("Test Complete"); PASS("Test complete\n"); }