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/bluetooth/bluetooth.h>
9 #include <zephyr/bluetooth/conn.h>
10 #include <zephyr/bluetooth/gatt.h>
11 #include <zephyr/logging/log.h>
12 
13 #include "testlib/att_read.h"
14 #include "testlib/att_write.h"
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 /* local includes */
23 #include "data.h"
24 
25 LOG_MODULE_REGISTER(dut, LOG_LEVEL_DBG);
26 
27 extern unsigned long runtime_log_level;
28 
29 DEFINE_FLAG_STATIC(got_notification);
30 
received_notification(struct bt_conn * conn,struct bt_gatt_subscribe_params * params,const void * data,uint16_t length)31 static uint8_t received_notification(struct bt_conn *conn,
32 				     struct bt_gatt_subscribe_params *params,
33 				     const void *data,
34 				     uint16_t length)
35 {
36 	if (length) {
37 		size_t expected_length = sizeof(NOTIFICATION_PAYLOAD);
38 		bool payload_is_correct = 0 == memcmp(data, NOTIFICATION_PAYLOAD, length);
39 
40 		LOG_INF("Received notification");
41 		LOG_HEXDUMP_DBG(data, length, "payload");
42 
43 		TEST_ASSERT(params->value_handle == GATT_HANDLE,
44 			    "Wrong handle used: expect 0x%x got 0x%x",
45 			    GATT_HANDLE, params->value_handle);
46 		TEST_ASSERT(length == expected_length,
47 			    "Length is incorrect: expect %d got %d",
48 			    expected_length, length);
49 		TEST_ASSERT(payload_is_correct, "Notification contents mismatch");
50 
51 		SET_FLAG(got_notification);
52 	}
53 
54 	return BT_GATT_ITER_CONTINUE;
55 }
56 
57 /* Subscription parameters have the same lifetime as a subscription.
58  * That is the backing struct should stay valid until a call to
59  * `bt_gatt_unsubscribe()` is made. Hence the `static`.
60  */
61 static struct bt_gatt_subscribe_params sub_params;
62 
63 /* Link `cb` to notifications received from `peer` for `handle`. Using
64  * `bt_gatt_resubscribe()` doesn't send anything on-air and just does the
65  * linking in the host.
66  */
fake_subscribe(bt_addr_le_t * peer,uint16_t handle,bt_gatt_notify_func_t cb)67 static void fake_subscribe(bt_addr_le_t *peer,
68 			   uint16_t handle,
69 			   bt_gatt_notify_func_t cb)
70 {
71 	int err;
72 
73 	/* Subscribe to notifications */
74 	sub_params.notify = cb;
75 	sub_params.value = BT_GATT_CCC_NOTIFY;
76 	sub_params.value_handle = handle;
77 
78 	/* Doesn't matter for re-subscribe. */
79 	sub_params.ccc_handle = handle + 2;
80 
81 	err = bt_gatt_resubscribe(0, peer, &sub_params);
82 	TEST_ASSERT(!err, "Subscribe failed (err %d)", err);
83 }
84 
run_test_iteration(bt_addr_le_t * peer)85 static void run_test_iteration(bt_addr_le_t *peer)
86 {
87 	int err;
88 	struct bt_conn *conn = NULL;
89 
90 	/* Create a connection using that address */
91 	err = bt_testlib_connect(peer, &conn);
92 	TEST_ASSERT(!err, "Failed to initiate connection (err %d)", err);
93 
94 	LOG_DBG("Connected");
95 
96 	LOG_DBG("Subscribe to test characteristic: handle 0x%04x", GATT_HANDLE);
97 	fake_subscribe(peer, GATT_HANDLE, received_notification);
98 
99 	WAIT_FOR_FLAG(got_notification);
100 
101 	LOG_DBG("Wait for disconnection from peer");
102 	bt_testlib_wait_disconnected(conn);
103 	bt_testlib_conn_unref(&conn);
104 }
105 
entrypoint_dut(void)106 void entrypoint_dut(void)
107 {
108 	/* Test purpose:
109 	 *
110 	 * Verifies that the Host does not leak resources related to
111 	 * reassembling L2CAP PDUs when operating over an unreliable connection.
112 	 *
113 	 * Two devices:
114 	 * - `peer`: sends long GATT notifications
115 	 * - `dut`: receives long notifications from `peer`
116 	 *
117 	 * To do this, we configure the devices that ensures L2CAP PDUs are
118 	 * fragmented on-air over a long period. That mostly means smallest data
119 	 * length possible combined with a long connection interval.
120 	 *
121 	 * We try to disconnect when a PDU is mid-reassembly. This is slightly
122 	 * tricky to ensure: we rely that the implementation of the controller
123 	 * will forward PDU fragments as soon as they are received on-air.
124 	 *
125 	 * Procedure (loop 20x):
126 	 * - [dut] establish connection to `peer`
127 	 * - [peer] send notification #1
128 	 * - [dut] wait until notification #1 received
129 	 *
130 	 * - [peer] send 2 out of 3 frags of notification #2
131 	 * - [peer] disconnect
132 	 * - [dut] wait for disconnection
133 	 *
134 	 * [verdict]
135 	 * - dut receives notification #1 for all iterations
136 	 */
137 	int err;
138 	bt_addr_le_t peer = {};
139 
140 	/* Mark test as in progress. */
141 	TEST_START("dut");
142 
143 	/* Set the log level given by the `log_level` CLI argument */
144 	bt_testlib_log_level_set("dut", runtime_log_level);
145 
146 	/* Initialize Bluetooth */
147 	err = bt_enable(NULL);
148 	TEST_ASSERT(err == 0, "Can't enable Bluetooth (err %d)", err);
149 
150 	LOG_DBG("Bluetooth initialized");
151 
152 	/* Find the address of the peer, using its advertised name */
153 	err = bt_testlib_scan_find_name(&peer, "peer");
154 	TEST_ASSERT(!err, "Failed to start scan (err %d)", err);
155 
156 	for (size_t i = 0; i < TEST_ITERATIONS; i++) {
157 		LOG_INF("## Iteration %d", i);
158 		run_test_iteration(&peer);
159 	}
160 
161 	TEST_PASS("dut");
162 }
163