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