Skip to content

Commit e489ec2

Browse files
jori-nordiccarlescufi
authored andcommitted
Bluetooth: L2CAP: Add re-assembly stress test
Add a test that verifies no resources are leaked when re-assembling L2CAP PDUs over an unreliable channel (ie. lots of disconnects). Signed-off-by: Jonathan Rico <[email protected]>
1 parent 6d01073 commit e489ec2

File tree

12 files changed

+1045
-0
lines changed

12 files changed

+1045
-0
lines changed

tests/bsim/bluetooth/host/l2cap/compile.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ app=tests/bsim/bluetooth/host/l2cap/stress conf_file=prj_nofrag.conf compile
2020
app=tests/bsim/bluetooth/host/l2cap/stress conf_file=prj_syswq.conf compile
2121
app=tests/bsim/bluetooth/host/l2cap/split/dut compile
2222
app=tests/bsim/bluetooth/host/l2cap/split/tester compile
23+
app=tests/bsim/bluetooth/host/l2cap/reassembly/dut compile
24+
app=tests/bsim/bluetooth/host/l2cap/reassembly/peer compile
2325
app=tests/bsim/bluetooth/host/l2cap/ecred/dut compile
2426
app=tests/bsim/bluetooth/host/l2cap/ecred/peer compile
2527
app=tests/bsim/bluetooth/host/l2cap/credits compile
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* Copyright (c) 2024 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#ifndef ZEPHYR_TESTS_BSIM_BLUETOOTH_HOST_L2CAP_REASSEMBLY_SRC_DATA_H_
8+
#define ZEPHYR_TESTS_BSIM_BLUETOOTH_HOST_L2CAP_REASSEMBLY_SRC_DATA_H_
9+
10+
#define GATT_HANDLE 0x1337
11+
#define PEER_NAME "peer"
12+
#define TEST_ITERATIONS 20
13+
#define NOTIFICATION_PAYLOAD "the !ification"
14+
15+
#endif /* ZEPHYR_TESTS_BSIM_BLUETOOTH_HOST_L2CAP_REASSEMBLY_SRC_DATA_H_ */
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
cmake_minimum_required(VERSION 3.20.0)
4+
5+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
6+
7+
project(reassembly)
8+
9+
add_subdirectory(${ZEPHYR_BASE}/tests/bluetooth/common/testlib testlib)
10+
target_link_libraries(app PRIVATE testlib)
11+
12+
add_subdirectory(${ZEPHYR_BASE}/tests/bsim/babblekit babblekit)
13+
target_link_libraries(app PRIVATE babblekit)
14+
15+
zephyr_include_directories(
16+
../
17+
${BSIM_COMPONENTS_PATH}/libUtilv1/src/
18+
${BSIM_COMPONENTS_PATH}/libPhyComv1/src/
19+
)
20+
21+
target_sources(app PRIVATE
22+
src/main.c
23+
src/dut.c
24+
)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
CONFIG_BT=y
2+
CONFIG_BT_DEVICE_NAME="reassembly"
3+
CONFIG_BT_CENTRAL=y
4+
5+
# Dependency of testlib/adv and testlib/scan.
6+
CONFIG_BT_EXT_ADV=y
7+
8+
CONFIG_BT_GATT_CLIENT=y
9+
10+
CONFIG_ASSERT=y
11+
12+
CONFIG_LOG=y
13+
CONFIG_LOG_RUNTIME_FILTERING=y
14+
15+
CONFIG_THREAD_NAME=y
16+
CONFIG_LOG_THREAD_ID_PREFIX=y
17+
18+
CONFIG_ASSERT_ON_ERRORS=y
19+
CONFIG_ARCH_POSIX_TRAP_ON_FATAL=y
20+
21+
# Disable auto-initiated procedures so they don't
22+
# mess with the test's execution.
23+
CONFIG_BT_AUTO_PHY_UPDATE=n
24+
CONFIG_BT_AUTO_DATA_LEN_UPDATE=n
25+
CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS=n
26+
27+
# Since the test's purpose is to test for leaks in the ACL
28+
# RX buffer pool, it is a good idea to constrain said buffer
29+
# pool.
30+
CONFIG_BT_MAX_CONN=1
31+
CONFIG_BT_BUF_ACL_RX_COUNT=6
32+
CONFIG_BT_BUF_EVT_RX_COUNT=6
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
* Copyright (c) 2024 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/kernel.h>
8+
#include <zephyr/bluetooth/bluetooth.h>
9+
#include <zephyr/bluetooth/conn.h>
10+
#include <zephyr/bluetooth/gatt.h>
11+
#include <zephyr/logging/log.h>
12+
13+
#include "testlib/att_read.h"
14+
#include "testlib/att_write.h"
15+
#include "testlib/conn.h"
16+
#include "testlib/scan.h"
17+
#include "testlib/log_utils.h"
18+
19+
#include "babblekit/flags.h"
20+
#include "babblekit/testcase.h"
21+
22+
/* local includes */
23+
#include "data.h"
24+
25+
LOG_MODULE_REGISTER(dut, LOG_LEVEL_DBG);
26+
27+
extern unsigned long runtime_log_level;
28+
29+
static DEFINE_FLAG(got_notification);
30+
31+
static uint8_t received_notification(struct bt_conn *conn,
32+
struct bt_gatt_subscribe_params *params,
33+
const void *data,
34+
uint16_t length)
35+
{
36+
if (length) {
37+
size_t expected_length = sizeof(NOTIFICATION_PAYLOAD);
38+
bool payload_is_correct = 0 == memcmp(data, NOTIFICATION_PAYLOAD, length);
39+
40+
LOG_INF("Received notification");
41+
LOG_HEXDUMP_DBG(data, length, "payload");
42+
43+
TEST_ASSERT(params->value_handle == GATT_HANDLE,
44+
"Wrong handle used: expect 0x%x got 0x%x",
45+
GATT_HANDLE, params->value_handle);
46+
TEST_ASSERT(length == expected_length,
47+
"Length is incorrect: expect %d got %d",
48+
expected_length, length);
49+
TEST_ASSERT(payload_is_correct, "Notification contents mismatch");
50+
51+
SET_FLAG(got_notification);
52+
}
53+
54+
return BT_GATT_ITER_CONTINUE;
55+
}
56+
57+
/* Subscription parameters have the same lifetime as a subscription.
58+
* That is the backing struct should stay valid until a call to
59+
* `bt_gatt_unsubscribe()` is made. Hence the `static`.
60+
*/
61+
static struct bt_gatt_subscribe_params sub_params;
62+
63+
/* Link `cb` to notifications received from `peer` for `handle`. Using
64+
* `bt_gatt_resubscribe()` doesn't send anything on-air and just does the
65+
* linking in the host.
66+
*/
67+
static void fake_subscribe(bt_addr_le_t *peer,
68+
uint16_t handle,
69+
bt_gatt_notify_func_t cb)
70+
{
71+
int err;
72+
73+
/* Subscribe to notifications */
74+
sub_params.notify = cb;
75+
sub_params.value = BT_GATT_CCC_NOTIFY;
76+
sub_params.value_handle = handle;
77+
78+
/* Doesn't matter for re-subscribe. */
79+
sub_params.ccc_handle = handle + 2;
80+
81+
err = bt_gatt_resubscribe(0, peer, &sub_params);
82+
TEST_ASSERT(!err, "Subscribe failed (err %d)", err);
83+
}
84+
85+
static void run_test_iteration(bt_addr_le_t *peer)
86+
{
87+
int err;
88+
struct bt_conn *conn = NULL;
89+
90+
/* Create a connection using that address */
91+
err = bt_testlib_connect(peer, &conn);
92+
TEST_ASSERT(!err, "Failed to initiate connection (err %d)", err);
93+
94+
LOG_DBG("Connected");
95+
96+
LOG_DBG("Subscribe to test characteristic: handle 0x%04x", GATT_HANDLE);
97+
fake_subscribe(peer, GATT_HANDLE, received_notification);
98+
99+
WAIT_FOR_FLAG(got_notification);
100+
101+
LOG_DBG("Wait for disconnection from peer");
102+
bt_testlib_wait_disconnected(conn);
103+
bt_testlib_conn_unref(&conn);
104+
}
105+
106+
void entrypoint_dut(void)
107+
{
108+
/* Test purpose:
109+
*
110+
* Verifies that the Host does not leak resources related to
111+
* reassembling L2CAP PDUs when operating over an unreliable connection.
112+
*
113+
* Two devices:
114+
* - `peer`: sends long GATT notifications
115+
* - `dut`: receives long notifications from `peer`
116+
*
117+
* To do this, we configure the devices that ensures L2CAP PDUs are
118+
* fragmented on-air over a long period. That mostly means smallest data
119+
* length possible combined with a long connection interval.
120+
*
121+
* We try to disconnect when a PDU is mid-reassembly. This is slightly
122+
* tricky to ensure: we rely that the implementation of the controller
123+
* will forward PDU fragments as soon as they are received on-air.
124+
*
125+
* Procedure (loop 20x):
126+
* - [dut] establish connection to `peer`
127+
* - [peer] send notification #1
128+
* - [dut] wait until notification #1 received
129+
*
130+
* - [peer] send 2 out of 3 frags of notification #2
131+
* - [peer] disconnect
132+
* - [dut] wait for disconnection
133+
*
134+
* [verdict]
135+
* - dut receives notification #1 for all iterations
136+
*/
137+
int err;
138+
bt_addr_le_t peer = {};
139+
140+
/* Mark test as in progress. */
141+
TEST_START("dut");
142+
143+
/* Set the log level given by the `log_level` CLI argument */
144+
bt_testlib_log_level_set("dut", runtime_log_level);
145+
146+
/* Initialize Bluetooth */
147+
err = bt_enable(NULL);
148+
TEST_ASSERT(err == 0, "Can't enable Bluetooth (err %d)", err);
149+
150+
LOG_DBG("Bluetooth initialized");
151+
152+
/* Find the address of the peer, using its advertised name */
153+
err = bt_testlib_scan_find_name(&peer, "peer");
154+
TEST_ASSERT(!err, "Failed to start scan (err %d)", err);
155+
156+
for (size_t i = 0; i < TEST_ITERATIONS; i++) {
157+
LOG_INF("## Iteration %d", i);
158+
run_test_iteration(&peer);
159+
}
160+
161+
TEST_PASS("dut");
162+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright (c) 2024 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/kernel.h>
8+
9+
#include "bs_tracing.h"
10+
#include "bstests.h"
11+
#include "babblekit/testcase.h"
12+
#include "testlib/log_utils.h"
13+
14+
extern void entrypoint_dut(void);
15+
extern enum bst_result_t bst_result;
16+
17+
unsigned long runtime_log_level = LOG_LEVEL_INF;
18+
19+
static void test_args(int argc, char *argv[])
20+
{
21+
size_t argn = 0;
22+
const char *arg = argv[argn];
23+
24+
if (strcmp(arg, "log_level") == 0) {
25+
26+
runtime_log_level = strtoul(argv[++argn], NULL, 10);
27+
28+
if (runtime_log_level >= LOG_LEVEL_NONE &&
29+
runtime_log_level <= LOG_LEVEL_DBG){
30+
TEST_PRINT("Runtime log level configuration: %d", runtime_log_level);
31+
} else {
32+
TEST_FAIL("Invalid arguments to set log level: %d", runtime_log_level);
33+
}
34+
} else {
35+
TEST_PRINT("Default runtime log level configuration: INFO");
36+
}
37+
}
38+
39+
static void test_end_cb(void)
40+
{
41+
if (bst_result != Passed) {
42+
TEST_PRINT("Test has not passed.");
43+
}
44+
}
45+
46+
static const struct bst_test_instance entrypoints[] = {
47+
{
48+
.test_id = "dut",
49+
.test_delete_f = test_end_cb,
50+
.test_main_f = entrypoint_dut,
51+
.test_args_f = test_args,
52+
},
53+
BSTEST_END_MARKER,
54+
};
55+
56+
static struct bst_test_list *install(struct bst_test_list *tests)
57+
{
58+
return bst_add_tests(tests, entrypoints);
59+
};
60+
61+
bst_test_install_t test_installers[] = {install, NULL};
62+
63+
int main(void)
64+
{
65+
bst_main();
66+
67+
return 0;
68+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
cmake_minimum_required(VERSION 3.20.0)
4+
5+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
6+
project(l2cap_reassembly_peer)
7+
8+
add_subdirectory(${ZEPHYR_BASE}/tests/bsim/babblekit babblekit)
9+
target_link_libraries(app PRIVATE babblekit)
10+
11+
target_sources(app PRIVATE
12+
src/main.c
13+
src/peer.c
14+
)
15+
16+
zephyr_include_directories(
17+
../
18+
${ZEPHYR_BASE}/subsys/bluetooth/common/
19+
${BSIM_COMPONENTS_PATH}/libUtilv1/src/
20+
${BSIM_COMPONENTS_PATH}/libPhyComv1/src/
21+
)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
CONFIG_LOG=y
2+
CONFIG_ASSERT=y
3+
4+
CONFIG_BT=y
5+
CONFIG_BT_HCI_RAW=y
6+
CONFIG_BT_MAX_CONN=1
7+
8+
CONFIG_BT_BUF_CMD_TX_COUNT=10
9+
CONFIG_BT_BUF_ACL_TX_COUNT=20
10+
11+
CONFIG_BT_BUF_ACL_RX_SIZE=255
12+
CONFIG_BT_BUF_CMD_TX_SIZE=255
13+
CONFIG_BT_BUF_EVT_DISCARDABLE_SIZE=255
14+
15+
# Allow whole L2CAP PDUs to fit on-air
16+
CONFIG_BT_BUF_ACL_TX_SIZE=251
17+
CONFIG_BT_BUF_ACL_RX_SIZE=251
18+
CONFIG_BT_DATA_LEN_UPDATE=y
19+
CONFIG_BT_CTLR_DATA_LENGTH_MAX=251

0 commit comments

Comments
 (0)