Skip to content

Commit 043de7d

Browse files
jori-nordicfabiobaltieri
authored andcommitted
Bluetooth: Host: Add disconnection during TX fragmentation test
Verifies that we don't leak connection references when the peer goes out of range whilst we are fragmenting and sending data to the controller. Signed-off-by: Jonathan Rico <[email protected]>
1 parent 9347887 commit 043de7d

File tree

9 files changed

+660
-0
lines changed

9 files changed

+660
-0
lines changed

tests/bsim/bluetooth/host/compile.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ run_in_background ${ZEPHYR_BASE}/tests/bsim/bluetooth/host/misc/hfc/compile.sh
3131
run_in_background ${ZEPHYR_BASE}/tests/bsim/bluetooth/host/misc/hfc_multilink/compile.sh
3232
app=tests/bsim/bluetooth/host/misc/unregister_conn_cb compile
3333
run_in_background ${ZEPHYR_BASE}/tests/bsim/bluetooth/host/misc/sample_test/compile.sh
34+
run_in_background ${ZEPHYR_BASE}/tests/bsim/bluetooth/host/misc/acl_tx_frag/compile.sh
3435

3536
run_in_background ${ZEPHYR_BASE}/tests/bsim/bluetooth/host/privacy/central/compile.sh
3637
run_in_background ${ZEPHYR_BASE}/tests/bsim/bluetooth/host/privacy/peripheral/compile.sh
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(acl_tx_frag)
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+
${BSIM_COMPONENTS_PATH}/libUtilv1/src/
17+
${BSIM_COMPONENTS_PATH}/libPhyComv1/src/
18+
)
19+
20+
target_sources(app PRIVATE
21+
src/main.c
22+
src/dut.c
23+
src/peer.c
24+
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env bash
2+
# Copyright 2023 Nordic Semiconductor ASA
3+
# SPDX-License-Identifier: Apache-2.0
4+
set -eu
5+
: "${ZEPHYR_BASE:?ZEPHYR_BASE must be defined}"
6+
7+
INCR_BUILD=1
8+
9+
source ${ZEPHYR_BASE}/tests/bsim/compile.source
10+
11+
app="$(guess_test_relpath)" compile
12+
13+
wait_for_background_jobs
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
CONFIG_BT=y
2+
CONFIG_BT_DEVICE_NAME="acl_tx_frag"
3+
CONFIG_BT_PERIPHERAL=y
4+
CONFIG_BT_CENTRAL=y
5+
6+
# Dependency of testlib/adv and testlib/scan.
7+
CONFIG_BT_EXT_ADV=y
8+
9+
CONFIG_BT_GATT_CLIENT=y
10+
CONFIG_BT_GATT_AUTO_DISCOVER_CCC=y
11+
12+
CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION=n
13+
14+
CONFIG_ASSERT=y
15+
CONFIG_LOG=y
16+
CONFIG_LOG_RUNTIME_FILTERING=y
17+
CONFIG_THREAD_NAME=y
18+
CONFIG_LOG_THREAD_ID_PREFIX=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+
CONFIG_BT_MAX_CONN=1
28+
29+
# We don't want to be constrained on the number of TX
30+
# contexts, rather on the number of LL TX buffers.
31+
CONFIG_BT_CONN_TX_MAX=10
32+
33+
# Outgoing ATT buffers
34+
CONFIG_BT_ATT_TX_COUNT=1
35+
36+
# Allow big ATT MTU
37+
CONFIG_BT_BUF_ACL_RX_SIZE=100
38+
CONFIG_BT_L2CAP_TX_MTU=100
39+
40+
# Controller buffers
41+
CONFIG_BT_BUF_ACL_TX_COUNT=1
42+
CONFIG_BT_BUF_ACL_TX_SIZE=27
43+
44+
# If we don't define this, it will inherit
45+
# CONFIG_BT_BUF_ACL_TX_COUNT and fail a build assert that
46+
# expects >= 3.
47+
CONFIG_BT_L2CAP_TX_BUF_COUNT=3
48+
49+
# For indication param structs. It's fine, we run on native.
50+
CONFIG_HEAP_MEM_POOL_SIZE=1024
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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_MISC_ACL_TX_FRAG_SRC_DATA_H_
8+
#define ZEPHYR_TESTS_BSIM_BLUETOOTH_HOST_MISC_ACL_TX_FRAG_SRC_DATA_H_
9+
10+
#include <zephyr/bluetooth/uuid.h>
11+
12+
#define TEST_ITERATIONS 3
13+
14+
/* overhead: opcode + ATT handle + L2CAP PDU header */
15+
#define GATT_PAYLOAD_SIZE (CONFIG_BT_L2CAP_TX_MTU - 1 - 2 - 4)
16+
17+
#define test_service_uuid \
18+
BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0xf0debc9a, 0x7856, 0x3412, 0x7856, 0x341278563412))
19+
#define test_characteristic_uuid \
20+
BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0xf2debc9a, 0x7856, 0x3412, 0x7856, 0x341278563412))
21+
22+
#endif /* ZEPHYR_TESTS_BSIM_BLUETOOTH_HOST_MISC_ACL_TX_FRAG_SRC_DATA_H_ */
Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
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/sys/byteorder.h>
9+
#include <zephyr/bluetooth/bluetooth.h>
10+
#include <zephyr/bluetooth/conn.h>
11+
#include <zephyr/bluetooth/l2cap.h>
12+
#include <zephyr/bluetooth/gatt.h>
13+
#include <zephyr/logging/log.h>
14+
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+
/* For the radio shenanigans */
23+
#include "hw_testcheat_if.h"
24+
25+
/* local includes */
26+
#include "data.h"
27+
28+
LOG_MODULE_REGISTER(dut, LOG_LEVEL_DBG);
29+
30+
static DEFINE_FLAG(is_subscribed);
31+
static DEFINE_FLAG(mtu_has_been_exchanged);
32+
static DEFINE_FLAG(conn_recycled);
33+
static DEFINE_FLAG(conn_param_updated);
34+
static DEFINE_FLAG(indicated);
35+
36+
extern unsigned long runtime_log_level;
37+
38+
static void recycled(void)
39+
{
40+
LOG_DBG("");
41+
SET_FLAG(conn_recycled);
42+
}
43+
44+
static void params_updated(struct bt_conn *conn, uint16_t interval, uint16_t latency,
45+
uint16_t timeout)
46+
{
47+
LOG_DBG("");
48+
SET_FLAG(conn_param_updated);
49+
}
50+
51+
static struct bt_conn_cb conn_cbs = {
52+
.recycled = recycled,
53+
.le_param_updated = params_updated,
54+
};
55+
56+
static void ccc_changed(const struct bt_gatt_attr *attr, uint16_t value)
57+
{
58+
/* assume we only get it for the `test_gatt_service` */
59+
if (value != 0) {
60+
SET_FLAG(is_subscribed);
61+
} else {
62+
UNSET_FLAG(is_subscribed);
63+
}
64+
}
65+
66+
BT_GATT_SERVICE_DEFINE(test_gatt_service, BT_GATT_PRIMARY_SERVICE(test_service_uuid),
67+
BT_GATT_CHARACTERISTIC(test_characteristic_uuid,
68+
(BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE |
69+
BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_INDICATE),
70+
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE, NULL, NULL,
71+
NULL),
72+
BT_GATT_CCC(ccc_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE));
73+
74+
static void _mtu_exchanged(struct bt_conn *conn, uint8_t err,
75+
struct bt_gatt_exchange_params *params)
76+
{
77+
LOG_DBG("MTU exchanged");
78+
SET_FLAG(mtu_has_been_exchanged);
79+
}
80+
81+
static void exchange_mtu(struct bt_conn *conn)
82+
{
83+
int err;
84+
struct bt_gatt_exchange_params params = {
85+
.func = _mtu_exchanged,
86+
};
87+
88+
UNSET_FLAG(mtu_has_been_exchanged);
89+
90+
err = bt_gatt_exchange_mtu(conn, &params);
91+
TEST_ASSERT(!err, "Failed MTU exchange (err %d)", err);
92+
93+
WAIT_FOR_FLAG(mtu_has_been_exchanged);
94+
}
95+
96+
#define UPDATE_PARAM_INTERVAL_MIN 500
97+
#define UPDATE_PARAM_INTERVAL_MAX 500
98+
#define UPDATE_PARAM_LATENCY 1
99+
#define UPDATE_PARAM_TIMEOUT 1000
100+
101+
static struct bt_le_conn_param update_params = {
102+
.interval_min = UPDATE_PARAM_INTERVAL_MIN,
103+
.interval_max = UPDATE_PARAM_INTERVAL_MAX,
104+
.latency = UPDATE_PARAM_LATENCY,
105+
.timeout = UPDATE_PARAM_TIMEOUT,
106+
};
107+
108+
void slow_down_conn(struct bt_conn *conn)
109+
{
110+
int err;
111+
112+
UNSET_FLAG(conn_param_updated);
113+
err = bt_conn_le_param_update(conn, &update_params);
114+
TEST_ASSERT(!err, "Parameter update failed (err %d)", err);
115+
WAIT_FOR_FLAG(conn_param_updated);
116+
}
117+
118+
static void make_peer_go_out_of_range(void)
119+
{
120+
hw_radio_testcheat_set_tx_power_gain(-300);
121+
hw_radio_testcheat_set_rx_power_gain(-300);
122+
}
123+
124+
static void make_peer_go_back_in_range(void)
125+
{
126+
hw_radio_testcheat_set_tx_power_gain(+300);
127+
hw_radio_testcheat_set_rx_power_gain(+300);
128+
}
129+
130+
void indicated_cb(struct bt_conn *conn, struct bt_gatt_indicate_params *params, uint8_t err)
131+
{
132+
SET_FLAG(indicated);
133+
}
134+
135+
static void params_struct_freed_cb(struct bt_gatt_indicate_params *params)
136+
{
137+
k_free(params);
138+
}
139+
140+
static int send_indication(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *data,
141+
uint16_t len)
142+
{
143+
struct bt_gatt_indicate_params *params = k_malloc(sizeof(struct bt_gatt_indicate_params));
144+
145+
params->attr = attr;
146+
params->func = indicated_cb;
147+
params->destroy = params_struct_freed_cb;
148+
params->data = data;
149+
params->len = len;
150+
151+
return bt_gatt_indicate(conn, params);
152+
}
153+
154+
static const uint8_t notification_data[GATT_PAYLOAD_SIZE];
155+
156+
static void test_iteration(bt_addr_le_t *peer)
157+
{
158+
int err;
159+
struct bt_conn *conn = NULL;
160+
const struct bt_gatt_attr *attr;
161+
162+
/* Create a connection using that address */
163+
err = bt_testlib_connect(peer, &conn);
164+
TEST_ASSERT(!err, "Failed to initiate connection (err %d)", err);
165+
166+
LOG_DBG("Connected");
167+
168+
LOG_INF("Wait until peer subscribes");
169+
UNSET_FLAG(is_subscribed);
170+
WAIT_FOR_FLAG(is_subscribed);
171+
172+
/* Prepare data for notifications
173+
* attrs[0] is our service declaration
174+
* attrs[1] is our characteristic declaration
175+
* attrs[2] is our characteristic value
176+
*
177+
* We store a pointer for the characteristic value as that is the
178+
* value we want to notify later.
179+
*
180+
* We could alternatively use `bt_gatt_notify_uuid()`.
181+
*/
182+
attr = &test_gatt_service.attrs[2];
183+
184+
exchange_mtu(conn);
185+
186+
slow_down_conn(conn);
187+
LOG_DBG("Updated params");
188+
189+
LOG_INF("Send indication #1");
190+
UNSET_FLAG(indicated);
191+
err = send_indication(conn, attr, notification_data, sizeof(notification_data));
192+
TEST_ASSERT(!err, "Failed to send notification: err %d", err);
193+
LOG_DBG("Wait until peer confirms our first indication");
194+
WAIT_FOR_FLAG(indicated);
195+
196+
LOG_INF("Send indication #2");
197+
UNSET_FLAG(indicated);
198+
err = send_indication(conn, attr, notification_data, sizeof(notification_data));
199+
TEST_ASSERT(!err, "Failed to send notification: err %d", err);
200+
201+
LOG_DBG("Simulate RF connection loss");
202+
UNSET_FLAG(conn_recycled);
203+
make_peer_go_out_of_range();
204+
205+
/* We will not access conn after this: give back our initial ref. */
206+
bt_testlib_conn_unref(&conn);
207+
WAIT_FOR_FLAG(conn_recycled);
208+
209+
LOG_DBG("Connection object has been destroyed as expected");
210+
make_peer_go_back_in_range();
211+
}
212+
213+
void entrypoint_dut(void)
214+
{
215+
/* Test purpose:
216+
*
217+
* Verifies that we don't leak resources or mess up host state when a
218+
* disconnection happens whilst the host is transmitting ACL fragments.
219+
*
220+
* To achieve that, we use the BabbleSim magic modem (see run.sh) to cut
221+
* the RF link before we have sent all the ACL fragments the peer. We do
222+
* want to send multiple fragments to the controller though, the
223+
* important part is that the peer does not acknowledge them, so that
224+
* the disconnection happens while the controller has its TX buffers
225+
* full.
226+
*
227+
* Two devices:
228+
* - `dut`: the device whose host we are testing
229+
* - `peer`: anime side-character. not important.
230+
*
231+
* Procedure (for n iterations):
232+
* - [dut] establish connection to `peer`
233+
* - [peer] discover GATT and subscribe to the test characteristic
234+
* - [dut] send long indication
235+
* - [peer] wait for confirmation of indication
236+
* - [dut] send another long indication
237+
* - [dut] disconnect
238+
*
239+
* [verdict]
240+
* - All test cycles complete
241+
*/
242+
int err;
243+
bt_addr_le_t peer = {};
244+
245+
/* Mark test as in progress. */
246+
TEST_START("dut");
247+
248+
/* Set the log level given by the `log_level` CLI argument */
249+
bt_testlib_log_level_set("dut", runtime_log_level);
250+
251+
/* Initialize Bluetooth */
252+
bt_conn_cb_register(&conn_cbs);
253+
err = bt_enable(NULL);
254+
TEST_ASSERT(err == 0, "Can't enable Bluetooth (err %d)", err);
255+
256+
LOG_DBG("Bluetooth initialized");
257+
258+
/* Find the address of the peer. In our case, both devices are actually
259+
* the same executable (with the same config) but executed with
260+
* different arguments. We can then just use CONFIG_BT_DEVICE_NAME which
261+
* contains our device name in string form.
262+
*/
263+
err = bt_testlib_scan_find_name(&peer, CONFIG_BT_DEVICE_NAME);
264+
TEST_ASSERT(!err, "Failed to start scan (err %d)", err);
265+
266+
for (int i = 0; i < TEST_ITERATIONS; i++) {
267+
LOG_INF("## Iteration %d", i);
268+
test_iteration(&peer);
269+
}
270+
271+
TEST_PASS("dut");
272+
}

0 commit comments

Comments
 (0)