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 "sync.h"
21 #include "bstests.h"
22 
23 #include <zephyr/logging/log.h>
24 LOG_MODULE_REGISTER(dut, LOG_LEVEL_INF);
25 
26 DEFINE_FLAG(is_connected);
27 DEFINE_FLAG(is_subscribed);
28 DEFINE_FLAG(flag_data_length_updated);
29 
30 static atomic_t notifications;
31 
32 /* Defined in hci_core.c */
33 extern k_tid_t bt_testing_tx_tid_get(void);
34 
35 static struct bt_conn *dconn;
36 
connected(struct bt_conn * conn,uint8_t conn_err)37 static void connected(struct bt_conn *conn, uint8_t conn_err)
38 {
39 	char addr[BT_ADDR_LE_STR_LEN];
40 
41 	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
42 
43 	if (conn_err) {
44 		FAIL("Failed to connect to %s (%u)", addr, conn_err);
45 		return;
46 	}
47 
48 	LOG_INF("%s: %s", __func__, addr);
49 
50 	dconn = bt_conn_ref(conn);
51 	SET_FLAG(is_connected);
52 }
53 
disconnected(struct bt_conn * conn,uint8_t reason)54 static void disconnected(struct bt_conn *conn, uint8_t reason)
55 {
56 	char addr[BT_ADDR_LE_STR_LEN];
57 
58 	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
59 
60 	LOG_INF("%s: %p %s (reason 0x%02x)", __func__, conn, addr, reason);
61 
62 	bt_conn_unref(dconn);
63 	UNSET_FLAG(is_connected);
64 }
65 
data_len_updated(struct bt_conn * conn,struct bt_conn_le_data_len_info * info)66 static void data_len_updated(struct bt_conn *conn,
67 			     struct bt_conn_le_data_len_info *info)
68 {
69 	LOG_DBG("Data length updated: TX %d RX %d",
70 		info->tx_max_len,
71 		info->rx_max_len);
72 	SET_FLAG(flag_data_length_updated);
73 }
74 
do_dlu(void)75 static void do_dlu(void)
76 {
77 	int err;
78 	struct bt_conn_le_data_len_param param;
79 
80 	param.tx_max_len = CONFIG_BT_CTLR_DATA_LENGTH_MAX;
81 	param.tx_max_time = 2500;
82 
83 	err = bt_conn_le_data_len_update(dconn, &param);
84 	ASSERT(err == 0, "Can't update data length (err %d)\n", err);
85 
86 	WAIT_FOR_FLAG(flag_data_length_updated);
87 }
88 
89 BT_CONN_CB_DEFINE(conn_callbacks) = {
90 	.connected = connected,
91 	.disconnected = disconnected,
92 	.le_data_len_updated = data_len_updated,
93 };
94 
device_found(const bt_addr_le_t * addr,int8_t rssi,uint8_t type,struct net_buf_simple * ad)95 static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type,
96 			 struct net_buf_simple *ad)
97 {
98 	char str[BT_ADDR_LE_STR_LEN];
99 	struct bt_le_conn_param *param;
100 	struct bt_conn *conn;
101 	int err;
102 
103 	err = bt_le_scan_stop();
104 	if (err) {
105 		FAIL("Stop LE scan failed (err %d)", err);
106 		return;
107 	}
108 
109 	bt_addr_le_to_str(addr, str, sizeof(str));
110 	LOG_DBG("Connecting to %s", str);
111 
112 	param = BT_LE_CONN_PARAM_DEFAULT;
113 	err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, param, &conn);
114 	if (err) {
115 		FAIL("Create conn failed (err %d)", err);
116 		return;
117 	}
118 }
119 
connect(void)120 static void connect(void)
121 {
122 	int err;
123 	struct bt_le_scan_param scan_param = {
124 		.type = BT_LE_SCAN_TYPE_ACTIVE,
125 		.options = BT_LE_SCAN_OPT_NONE,
126 		.interval = BT_GAP_SCAN_FAST_INTERVAL,
127 		.window = BT_GAP_SCAN_FAST_WINDOW,
128 	};
129 
130 	UNSET_FLAG(is_connected);
131 
132 	err = bt_le_scan_start(&scan_param, device_found);
133 	ASSERT(!err, "Scanning failed to start (err %d)\n", err);
134 
135 	LOG_DBG("Central initiating connection...");
136 	WAIT_FOR_FLAG(is_connected);
137 	LOG_INF("Connected as central");
138 
139 	/* No security support on the tinyhost unfortunately */
140 }
141 
notified(struct bt_conn * conn,struct bt_gatt_subscribe_params * params,const void * data,uint16_t length)142 static uint8_t notified(struct bt_conn *conn, struct bt_gatt_subscribe_params *params,
143 			const void *data, uint16_t length)
144 {
145 	static uint8_t notification[] = NOTIFICATION_PAYLOAD;
146 	static uint8_t indication[] = INDICATION_PAYLOAD;
147 	bool is_nfy;
148 
149 	LOG_HEXDUMP_DBG(data, length, "HVx data");
150 
151 	if (length == 0) {
152 		/* The host's backward way of telling us we are unsubscribed
153 		 * from this characteristic.
154 		 */
155 		LOG_DBG("Unsubscribed");
156 		return BT_GATT_ITER_CONTINUE;
157 	}
158 
159 	ASSERT(length >= sizeof(indication), "Unexpected data\n");
160 	ASSERT(length <= sizeof(notification), "Unexpected data\n");
161 
162 	is_nfy = memcmp(data, notification, length) == 0;
163 
164 	LOG_INF("%s from 0x%x", is_nfy ? "notified" : "indicated",
165 		params->value_handle);
166 
167 	ASSERT(is_nfy, "Unexpected indication\n");
168 
169 	atomic_inc(&notifications);
170 
171 	if (atomic_get(&notifications) == 3) {
172 		LOG_INF("##################### BRB..");
173 		backchannel_sync_send();
174 
175 		/* Make scheduler rotate us in and out multiple times */
176 		for (int i = 0; i < 10; i++) {
177 			LOG_DBG("sleep");
178 			k_sleep(K_MSEC(100));
179 			LOG_DBG("sleep");
180 		}
181 
182 		LOG_INF("##################### ..back to work");
183 	}
184 
185 	return BT_GATT_ITER_CONTINUE;
186 }
187 
subscribed(struct bt_conn * conn,uint8_t err,struct bt_gatt_subscribe_params * params)188 static void subscribed(struct bt_conn *conn,
189 		       uint8_t err,
190 		       struct bt_gatt_subscribe_params *params)
191 {
192 	ASSERT(!err, "Subscribe failed (err %d)\n", err);
193 
194 	ASSERT(params, "params is NULL\n");
195 
196 	SET_FLAG(is_subscribed);
197 	/* spoiler: tester doesn't really have attributes */
198 	LOG_INF("Subscribed to Tester attribute");
199 }
200 
subscribe(void)201 void subscribe(void)
202 {
203 	int err;
204 
205 	/* Handle values don't matter, as long as they match on the tester */
206 	static struct bt_gatt_subscribe_params params = {
207 		.notify = notified,
208 		.subscribe = subscribed,
209 		.value = BT_GATT_CCC_NOTIFY | BT_GATT_CCC_INDICATE,
210 		.value_handle = HVX_HANDLE,
211 		.ccc_handle = (HVX_HANDLE + 1),
212 	};
213 
214 	err = bt_gatt_subscribe(dconn, &params);
215 	ASSERT(!err, "Subscribe failed (err %d)\n", err);
216 
217 	WAIT_FOR_FLAG(is_subscribed);
218 }
219 
test_procedure_0(void)220 void test_procedure_0(void)
221 {
222 	ASSERT(backchannel_init() == 0, "Failed to open backchannel\n");
223 
224 	LOG_DBG("Test start: ATT disconnect protocol");
225 	int err;
226 
227 	err = bt_enable(NULL);
228 	ASSERT(err == 0, "Can't enable Bluetooth (err %d)\n", err);
229 	LOG_DBG("Central: Bluetooth initialized.");
230 
231 	/* Test purpose:
232 	 * Make sure the host handles long blocking in notify callbacks
233 	 * gracefully, especially in the case of a disconnect while waiting.
234 	 *
235 	 * Test procedure:
236 	 *
237 	 * [setup]
238 	 * - connect ACL, DUT is central and GATT client
239 	 * - update data length (tinyhost doens't have recombination)
240 	 * - dut: subscribe to NOTIFY on tester CHRC
241 	 *
242 	 * [procedure]
243 	 * - tester: start periodic notifications
244 	 * - dut: wait 10x 100ms in notification RX callback
245 	 * - tester: disconnect (not gracefully) while DUT is waiting
246 	 *   -> simulates a power or range loss situation
247 	 * - dut: exit notification callback
248 	 * - dut: wait for `disconnected` conn callback
249 	 *
250 	 * [verdict]
251 	 * - The DUT gets the `disconnected` callback, no hanging or timeouts.
252 	 */
253 	connect();
254 	subscribe();
255 
256 	do_dlu();
257 
258 	WAIT_FOR_EXPR(notifications, < 4);
259 
260 	WAIT_FOR_FLAG_UNSET(is_connected);
261 
262 	LOG_INF("##################### END TEST #####################");
263 
264 	PASS("DUT exit\n");
265 }
266 
test_tick(bs_time_t HW_device_time)267 void test_tick(bs_time_t HW_device_time)
268 {
269 	bs_trace_debug_time(0, "Simulation ends now.\n");
270 	if (bst_result != Passed) {
271 		bst_result = Failed;
272 		bs_trace_error("Test did not pass before simulation ended.\n");
273 	}
274 }
275 
test_init(void)276 void test_init(void)
277 {
278 	bst_ticker_set_next_tick_absolute(TEST_TIMEOUT_SIMULATED);
279 	bst_result = In_progress;
280 }
281 
282 static const struct bst_test_instance test_to_add[] = {
283 	{
284 		.test_id = "dut",
285 		.test_pre_init_f = test_init,
286 		.test_tick_f = test_tick,
287 		.test_main_f = test_procedure_0,
288 	},
289 	BSTEST_END_MARKER,
290 };
291 
install(struct bst_test_list * tests)292 static struct bst_test_list *install(struct bst_test_list *tests)
293 {
294 	return bst_add_tests(tests, test_to_add);
295 };
296 
297 bst_test_install_t test_installers[] = {install, NULL};
298 
main(void)299 int main(void)
300 {
301 	bst_main();
302 
303 	return 0;
304 }
305