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