1 /*
2  * Copyright (c) 2023 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <stdio.h>
8 
9 #include <zephyr/kernel.h>
10 #include <zephyr/sys/byteorder.h>
11 #include <zephyr/bluetooth/bluetooth.h>
12 #include <zephyr/bluetooth/hci.h>
13 #include <zephyr/bluetooth/l2cap.h>
14 #include <zephyr/bluetooth/att.h>
15 #include <zephyr/bluetooth/gatt.h>
16 #include <zephyr/bluetooth/uuid.h>
17 #include <zephyr/bluetooth/conn.h>
18 
19 #include "utils.h"
20 #include "bstests.h"
21 
22 #include <zephyr/logging/log.h>
23 LOG_MODULE_REGISTER(dut, LOG_LEVEL_INF);
24 
25 DEFINE_FLAG(is_connected);
26 DEFINE_FLAG(is_subscribed);
27 DEFINE_FLAG(one_indication);
28 DEFINE_FLAG(two_notifications);
29 DEFINE_FLAG(flag_data_length_updated);
30 
31 static atomic_t nwrites;
32 static atomic_t indications;
33 static atomic_t notifications;
34 
35 /* Defined in conn.c */
36 extern void bt_conn_suspend_tx(bool suspend);
37 
38 static struct bt_conn *dconn;
39 
connected(struct bt_conn * conn,uint8_t conn_err)40 static void connected(struct bt_conn *conn, uint8_t conn_err)
41 {
42 	char addr[BT_ADDR_LE_STR_LEN];
43 
44 	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
45 
46 	if (conn_err) {
47 		FAIL("Failed to connect to %s (%u)", addr, conn_err);
48 		return;
49 	}
50 
51 	LOG_DBG("%s", addr);
52 
53 	dconn = bt_conn_ref(conn);
54 	SET_FLAG(is_connected);
55 }
56 
disconnected(struct bt_conn * conn,uint8_t reason)57 static void disconnected(struct bt_conn *conn, uint8_t reason)
58 {
59 	char addr[BT_ADDR_LE_STR_LEN];
60 
61 	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
62 
63 	LOG_DBG("%p %s (reason 0x%02x)", conn, addr, reason);
64 
65 	bt_conn_unref(dconn);
66 	UNSET_FLAG(is_connected);
67 }
68 
data_len_updated(struct bt_conn * conn,struct bt_conn_le_data_len_info * info)69 static void data_len_updated(struct bt_conn *conn,
70 			     struct bt_conn_le_data_len_info *info)
71 {
72 	LOG_DBG("Data length updated: TX %d RX %d",
73 		info->tx_max_len,
74 		info->rx_max_len);
75 	SET_FLAG(flag_data_length_updated);
76 }
77 
do_dlu(void)78 static void do_dlu(void)
79 {
80 	int err;
81 	struct bt_conn_le_data_len_param param;
82 
83 	param.tx_max_len = CONFIG_BT_CTLR_DATA_LENGTH_MAX;
84 	param.tx_max_time = 2500;
85 
86 	err = bt_conn_le_data_len_update(dconn, &param);
87 	ASSERT(err == 0, "Can't update data length (err %d)\n", err);
88 
89 	WAIT_FOR_FLAG(flag_data_length_updated);
90 }
91 
92 BT_CONN_CB_DEFINE(conn_callbacks) = {
93 	.connected = connected,
94 	.disconnected = disconnected,
95 	.le_data_len_updated = data_len_updated,
96 };
97 
device_found(const bt_addr_le_t * addr,int8_t rssi,uint8_t type,struct net_buf_simple * ad)98 static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type,
99 			 struct net_buf_simple *ad)
100 {
101 	char str[BT_ADDR_LE_STR_LEN];
102 	struct bt_le_conn_param *param;
103 	struct bt_conn *conn;
104 	int err;
105 
106 	err = bt_le_scan_stop();
107 	if (err) {
108 		FAIL("Stop LE scan failed (err %d)", err);
109 		return;
110 	}
111 
112 	bt_addr_le_to_str(addr, str, sizeof(str));
113 	LOG_DBG("Connecting to %s", str);
114 
115 	param = BT_LE_CONN_PARAM_DEFAULT;
116 	err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, param, &conn);
117 	if (err) {
118 		FAIL("Create conn failed (err %d)", err);
119 		return;
120 	}
121 }
122 
connect(void)123 static void connect(void)
124 {
125 	int err;
126 	struct bt_le_scan_param scan_param = {
127 		.type = BT_LE_SCAN_TYPE_ACTIVE,
128 		.options = BT_LE_SCAN_OPT_NONE,
129 		.interval = BT_GAP_SCAN_FAST_INTERVAL,
130 		.window = BT_GAP_SCAN_FAST_WINDOW,
131 	};
132 
133 	UNSET_FLAG(is_connected);
134 
135 	err = bt_le_scan_start(&scan_param, device_found);
136 	ASSERT(!err, "Scanning failed to start (err %d)\n", err);
137 
138 	LOG_DBG("Central initiating connection...");
139 	WAIT_FOR_FLAG(is_connected);
140 	LOG_INF("Connected as central");
141 
142 	/* No security support on the tinyhost unfortunately */
143 }
144 
written_to(struct bt_conn * conn,const struct bt_gatt_attr * attr,const void * buf,uint16_t len,uint16_t offset,uint8_t flags)145 static ssize_t written_to(struct bt_conn *conn,
146 			  const struct bt_gatt_attr *attr,
147 			  const void *buf,
148 			  uint16_t len,
149 			  uint16_t offset,
150 			  uint8_t flags)
151 {
152 	LOG_INF("written to: handle 0x%x len %d flags 0x%x",
153 		attr->handle,
154 		len,
155 		flags);
156 
157 	LOG_HEXDUMP_DBG(buf, len, "Write data");
158 
159 	if (atomic_get(&nwrites) == 0) {
160 		/* Suspend on the first write, which is an ATT Request */
161 		LOG_INF("suspending HCI TX thread");
162 		bt_conn_suspend_tx(true);
163 	}
164 
165 	atomic_inc(&nwrites);
166 
167 	return len;
168 }
169 
170 #define test_service_uuid                                                                          \
171 	BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0xf0debc9a, 0x7856, 0x3412, 0x7856, 0x341278563412))
172 #define test_characteristic_uuid                                                                   \
173 	BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0xf2debc9a, 0x7856, 0x3412, 0x7856, 0x341278563412))
174 
175 BT_GATT_SERVICE_DEFINE(test_gatt_service, BT_GATT_PRIMARY_SERVICE(test_service_uuid),
176 		       BT_GATT_CHARACTERISTIC(test_characteristic_uuid,
177 					      (BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE |
178 					       BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_INDICATE),
179 					      BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
180 					      NULL, written_to, NULL),
181 		       BT_GATT_CCC(NULL, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),);
182 
notified(struct bt_conn * conn,struct bt_gatt_subscribe_params * params,const void * data,uint16_t length)183 static uint8_t notified(struct bt_conn *conn, struct bt_gatt_subscribe_params *params,
184 			const void *data, uint16_t length)
185 {
186 	static uint8_t notification[] = NOTIFICATION_PAYLOAD;
187 	static uint8_t indication[] = INDICATION_PAYLOAD;
188 	bool is_nfy;
189 
190 	ASSERT(length >= sizeof(indication), "Unexpected data");
191 	ASSERT(length <= sizeof(notification), "Unexpected data");
192 
193 	LOG_HEXDUMP_DBG(data, length, "HVx data");
194 
195 	is_nfy = memcmp(data, notification, length) == 0;
196 
197 	LOG_INF("%s from 0x%x", is_nfy ? "notified" : "indicated",
198 		params->value_handle);
199 
200 	if (is_nfy) {
201 		atomic_inc(&notifications);
202 	} else {
203 		atomic_inc(&indications);
204 	}
205 
206 	return BT_GATT_ITER_CONTINUE;
207 }
208 
subscribed(struct bt_conn * conn,uint8_t err,struct bt_gatt_subscribe_params * params)209 static void subscribed(struct bt_conn *conn,
210 		       uint8_t err,
211 		       struct bt_gatt_subscribe_params *params)
212 {
213 	ASSERT(!err, "Subscribe failed (err %d)\n", err);
214 
215 	ASSERT(params, "params is NULL\n");
216 
217 	SET_FLAG(is_subscribed);
218 	/* spoiler: tester doesn't really have attributes */
219 	LOG_INF("Subscribed to Tester attribute");
220 }
221 
subscribe(void)222 void subscribe(void)
223 {
224 	int err;
225 
226 	/* Handle values don't matter, as long as they match on the tester */
227 	static struct bt_gatt_subscribe_params params = {
228 		.notify = notified,
229 		.subscribe = subscribed,
230 		.value = BT_GATT_CCC_NOTIFY | BT_GATT_CCC_INDICATE,
231 		.value_handle = HVX_HANDLE,
232 		.ccc_handle = (HVX_HANDLE + 1),
233 	};
234 
235 	err = bt_gatt_subscribe(dconn, &params);
236 	ASSERT(!err, "Subscribe failed (err %d)\n", err);
237 
238 	WAIT_FOR_FLAG(is_subscribed);
239 }
240 
send_write_handle(void)241 static void send_write_handle(void)
242 {
243 	int err;
244 	uint16_t handle;
245 	uint8_t data[sizeof(handle)];
246 	const struct bt_gatt_attr *attr = &test_gatt_service.attrs[2];
247 
248 	/* Inform tester which handle it should write to */
249 	handle = bt_gatt_attr_get_handle(attr);
250 	sys_put_le16(handle, data);
251 
252 	err = bt_gatt_notify(dconn, attr, data, sizeof(data));
253 	ASSERT(!err, "Failed to transmit handle for write (err %d)\n", err);
254 }
255 
test_procedure_0(void)256 void test_procedure_0(void)
257 {
258 	LOG_DBG("Test start: ATT sequential protocol");
259 	int err;
260 
261 	err = bt_enable(NULL);
262 	ASSERT(err == 0, "Can't enable Bluetooth (err %d)\n", err);
263 	LOG_DBG("Central: Bluetooth initialized.");
264 
265 	/* Test purpose:
266 	 * Test Spec V.3 P.F 3.3.2 Sequential protocol
267 	 *
268 	 * Verify that a Zephyr host server/client combo can process
269 	 * concurrently: one Request, one Indication, multiple
270 	 * Notifications and multiple Commands.
271 	 *
272 	 * To do this, the application on the DUT will purposefully stall the
273 	 * HCI TX thread, ensuring that the responses are not sent until the
274 	 * tester has finished sending everything.
275 	 *
276 	 * Test procedure:
277 	 *
278 	 * [setup]
279 	 * - connect ACL
280 	 * - update data length (tinyhost doens't have recombination)
281 	 * - dut: subscribe to INDICATE and NOTIFY on tester CHRC
282 	 * - dut: send a handle the tester can write to
283 	 *
284 	 * [proc]
285 	 * - tester: send one ATT write request
286 	 * - tester: send one ATT indication
287 	 * - tester: send two ATT notifications
288 	 * - tester: send two ATT commands
289 	 *
290 	 * - dut: handle the REQuest, build & put the RSP PDU on the HCI TX queue
291 	 * - dut: suspend the HCI TX thread
292 	 * - dut: handle the INDication
293 	 * - dut: handle the notifications
294 	 * - dut: handle the (write) commands
295 	 * - dut: resume the TX thread after a short while
296 	 *
297 	 * [verdict]
298 	 * - all procedures complete successfully, no buffer allocation failures
299 	 *   or timeouts.
300 	 */
301 	connect();
302 	subscribe();
303 
304 	do_dlu();
305 
306 	send_write_handle();
307 
308 	WAIT_FOR_VAL(indications, 1);
309 	WAIT_FOR_VAL(notifications, 2);
310 	/* One REQ, two CMDs */
311 	WAIT_FOR_VAL(nwrites, 3);
312 
313 	/* Send RSP to LL */
314 	bt_conn_suspend_tx(false);
315 
316 	PASS("DUT done\n");
317 }
318 
test_tick(bs_time_t HW_device_time)319 void test_tick(bs_time_t HW_device_time)
320 {
321 	bs_trace_debug_time(0, "Simulation ends now.\n");
322 	if (bst_result != Passed) {
323 		bst_result = Failed;
324 		bs_trace_error("Test did not pass before simulation ended.\n");
325 	}
326 }
327 
test_init(void)328 void test_init(void)
329 {
330 	bst_ticker_set_next_tick_absolute(TEST_TIMEOUT_SIMULATED);
331 	bst_result = In_progress;
332 }
333 
334 static const struct bst_test_instance test_to_add[] = {
335 	{
336 		.test_id = "dut",
337 		.test_pre_init_f = test_init,
338 		.test_tick_f = test_tick,
339 		.test_main_f = test_procedure_0,
340 	},
341 	BSTEST_END_MARKER,
342 };
343 
install(struct bst_test_list * tests)344 static struct bst_test_list *install(struct bst_test_list *tests)
345 {
346 	return bst_add_tests(tests, test_to_add);
347 };
348 
349 bst_test_install_t test_installers[] = {install, NULL};
350 
main(void)351 int main(void)
352 {
353 	bst_main();
354 
355 	return 0;
356 }
357