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 DEFINE_FLAG_STATIC(is_subscribed);
31 DEFINE_FLAG_STATIC(mtu_has_been_exchanged);
32 DEFINE_FLAG_STATIC(conn_recycled);
33 DEFINE_FLAG_STATIC(conn_param_updated);
34 DEFINE_FLAG_STATIC(indicated);
35
36 extern unsigned long runtime_log_level;
37
recycled(void)38 static void recycled(void)
39 {
40 LOG_DBG("");
41 SET_FLAG(conn_recycled);
42 }
43
params_updated(struct bt_conn * conn,uint16_t interval,uint16_t latency,uint16_t timeout)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
ccc_changed(const struct bt_gatt_attr * attr,uint16_t value)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
_mtu_exchanged(struct bt_conn * conn,uint8_t err,struct bt_gatt_exchange_params * params)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
exchange_mtu(struct bt_conn * conn)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, ¶ms);
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
slow_down_conn(struct bt_conn * conn)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
make_peer_go_out_of_range(void)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
make_peer_go_back_in_range(void)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
indicated_cb(struct bt_conn * conn,struct bt_gatt_indicate_params * params,uint8_t err)130 void indicated_cb(struct bt_conn *conn, struct bt_gatt_indicate_params *params, uint8_t err)
131 {
132 SET_FLAG(indicated);
133 }
134
params_struct_freed_cb(struct bt_gatt_indicate_params * params)135 static void params_struct_freed_cb(struct bt_gatt_indicate_params *params)
136 {
137 k_free(params);
138 }
139
send_indication(struct bt_conn * conn,const struct bt_gatt_attr * attr,const void * data,uint16_t len)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
test_iteration(bt_addr_le_t * peer)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
entrypoint_dut(void)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 }
273