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/att.h>
11 #include <zephyr/bluetooth/gatt.h>
12 #include <zephyr/logging/log.h>
13
14 #include "testlib/adv.h"
15 #include "testlib/att_read.h"
16 #include "testlib/att_write.h"
17 #include "testlib/conn.h"
18 #include "testlib/log_utils.h"
19
20 #include "babblekit/flags.h"
21 #include "babblekit/sync.h"
22 #include "babblekit/testcase.h"
23
24 /* local includes */
25 #include "data.h"
26
27 LOG_MODULE_REGISTER(peer, LOG_LEVEL_DBG);
28
29 static DEFINE_FLAG(is_subscribed);
30 static DEFINE_FLAG(got_notification_1);
31 static DEFINE_FLAG(got_notification_2);
32
33 extern unsigned long runtime_log_level;
34
find_characteristic(struct bt_conn * conn,const struct bt_uuid * svc,const struct bt_uuid * chrc,uint16_t * chrc_value_handle)35 int find_characteristic(struct bt_conn *conn,
36 const struct bt_uuid *svc,
37 const struct bt_uuid *chrc,
38 uint16_t *chrc_value_handle)
39 {
40 uint16_t svc_handle;
41 uint16_t svc_end_handle;
42 uint16_t chrc_end_handle;
43 int err;
44
45 LOG_DBG("");
46
47 err = bt_testlib_gatt_discover_primary(&svc_handle, &svc_end_handle, conn, svc,
48 BT_ATT_FIRST_ATTRIBUTE_HANDLE,
49 BT_ATT_LAST_ATTRIBUTE_HANDLE);
50 if (err != 0) {
51 LOG_ERR("Failed to discover service: %d", err);
52
53 return err;
54 }
55
56 LOG_DBG("svc_handle: %u, svc_end_handle: %u", svc_handle, svc_end_handle);
57
58 err = bt_testlib_gatt_discover_characteristic(chrc_value_handle, &chrc_end_handle,
59 NULL, conn, chrc, (svc_handle + 1),
60 svc_end_handle);
61 if (err != 0) {
62 LOG_ERR("Failed to get value handle: %d", err);
63
64 return err;
65 }
66
67 LOG_DBG("chrc_value_handle: %u, chrc_end_handle: %u", *chrc_value_handle, chrc_end_handle);
68
69 return err;
70 }
71
received_notification(struct bt_conn * conn,struct bt_gatt_subscribe_params * params,const void * data,uint16_t length)72 static uint8_t received_notification(struct bt_conn *conn,
73 struct bt_gatt_subscribe_params *params,
74 const void *data,
75 uint16_t length)
76 {
77 if (length) {
78 LOG_INF("RX notification");
79 LOG_HEXDUMP_DBG(data, length, "payload");
80
81 TEST_ASSERT(length == sizeof(payload_1), "Unexpected length: %d", length);
82
83 if (!memcmp(payload_1, data, length)) {
84 SET_FLAG(got_notification_1);
85 } else if (!memcmp(payload_2, data, length)) {
86 SET_FLAG(got_notification_2);
87 }
88 }
89
90 return BT_GATT_ITER_CONTINUE;
91 }
92
sub_cb(struct bt_conn * conn,uint8_t err,struct bt_gatt_subscribe_params * params)93 static void sub_cb(struct bt_conn *conn,
94 uint8_t err,
95 struct bt_gatt_subscribe_params *params)
96 {
97 TEST_ASSERT(!err, "Subscribe failed (err %d)", err);
98
99 TEST_ASSERT(params, "params is NULL");
100 TEST_ASSERT(params->value, "Host shouldn't know we have unsubscribed");
101
102 LOG_DBG("Subscribed to handle 0x%04x", params->value_handle);
103 SET_FLAG(is_subscribed);
104 }
105
106 /* Subscription parameters have the same lifetime as a subscription.
107 * That is the backing struct should stay valid until a call to
108 * `bt_gatt_unsubscribe()` is made. Hence the `static`.
109 */
110 static struct bt_gatt_subscribe_params sub_params;
111
112 /* This is "working memory" used by the `CONFIG_BT_GATT_AUTO_DISCOVER_CCC`
113 * feature. It also has to stay valid until the end of the async call.
114 */
115 static struct bt_gatt_discover_params ccc_disc_params;
116
subscribe(struct bt_conn * conn,uint16_t handle,bt_gatt_notify_func_t cb)117 static void subscribe(struct bt_conn *conn,
118 uint16_t handle,
119 bt_gatt_notify_func_t cb)
120 {
121 int err;
122
123 /* Subscribe to notifications */
124 sub_params.notify = cb;
125 sub_params.subscribe = sub_cb;
126 sub_params.value = BT_GATT_CCC_NOTIFY;
127 sub_params.value_handle = handle;
128 sub_params.ccc_handle = BT_GATT_AUTO_DISCOVER_CCC_HANDLE;
129 sub_params.disc_params = &ccc_disc_params;
130 sub_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
131
132 err = bt_gatt_subscribe(conn, &sub_params);
133 TEST_ASSERT(!err, "Subscribe failed (err %d)", err);
134
135 WAIT_FOR_FLAG(is_subscribed);
136 }
137
unsubscribe_but_not_really(struct bt_conn * conn,uint16_t handle)138 static void unsubscribe_but_not_really(struct bt_conn *conn, uint16_t handle)
139 {
140 /* Here we do something slightly different:
141 *
142 * Since we want to still be able to receive the notification, we don't
143 * actually want to unsubscribe. We only want to make the server *think*
144 * we have unsubscribed in order to test that
145 * CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION works properly.
146 *
147 * So we just write a 0 to the CCC handle, that should do the trick.
148 */
149 uint8_t data[1] = {0};
150
151 int err = bt_testlib_att_write(conn, BT_ATT_CHAN_OPT_NONE,
152 sub_params.ccc_handle, data, sizeof(data));
153
154 TEST_ASSERT(!err, "Unsubscribe failed: err %d", err);
155 }
156
157 /* Read the comments on `entrypoint_dut()` first. */
entrypoint_peer(void)158 void entrypoint_peer(void)
159 {
160 int err;
161 struct bt_conn *conn;
162 uint16_t handle;
163
164 /* Mark test as in progress. */
165 TEST_START("peer");
166
167 /* Initialize device sync library */
168 bk_sync_init();
169
170 /* Set the log level given by the `log_level` CLI argument */
171 bt_testlib_log_level_set("peer", runtime_log_level);
172
173 /* Initialize Bluetooth */
174 err = bt_enable(NULL);
175 TEST_ASSERT(err == 0, "Can't enable Bluetooth (err %d)", err);
176
177 LOG_DBG("Bluetooth initialized");
178
179 err = bt_testlib_adv_conn(&conn, BT_ID_DEFAULT, bt_get_name());
180 TEST_ASSERT(!err, "Failed to start connectable advertising (err %d)", err);
181
182 LOG_DBG("Discover test characteristic");
183 err = find_characteristic(conn, test_service_uuid, test_characteristic_uuid, &handle);
184 TEST_ASSERT(!err, "Failed to find characteristic: %d", err);
185
186 LOG_DBG("Subscribe to test characteristic: handle 0x%04x", handle);
187 subscribe(conn, handle, received_notification);
188
189 WAIT_FOR_FLAG(got_notification_1);
190
191 LOG_DBG("Unsubscribe from test characteristic: handle 0x%04x", handle);
192 unsubscribe_but_not_really(conn, handle);
193
194 WAIT_FOR_FLAG(got_notification_2);
195 bk_sync_send();
196
197 /* Disconnect and destroy connection object */
198 LOG_DBG("Disconnect");
199 err = bt_testlib_disconnect(&conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
200 TEST_ASSERT(!err, "Failed to disconnect (err %d)", err);
201
202 TEST_PASS("peer");
203 }
204