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, &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 
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