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/testcase.h"
22
23 /* local includes */
24 #include "data.h"
25
26 LOG_MODULE_REGISTER(peer, LOG_LEVEL_DBG);
27
28 static DEFINE_FLAG(is_subscribed);
29 static DEFINE_FLAG(got_notification_1);
30
31 extern unsigned long runtime_log_level;
32
find_characteristic(struct bt_conn * conn,const struct bt_uuid * svc,const struct bt_uuid * chrc,uint16_t * chrc_value_handle)33 int find_characteristic(struct bt_conn *conn, const struct bt_uuid *svc, const struct bt_uuid *chrc,
34 uint16_t *chrc_value_handle)
35 {
36 uint16_t svc_handle;
37 uint16_t svc_end_handle;
38 uint16_t chrc_end_handle;
39 int err;
40
41 LOG_DBG("");
42
43 err = bt_testlib_gatt_discover_primary(&svc_handle, &svc_end_handle, conn, svc,
44 BT_ATT_FIRST_ATTRIBUTE_HANDLE,
45 BT_ATT_LAST_ATTRIBUTE_HANDLE);
46 if (err != 0) {
47 LOG_ERR("Failed to discover service: %d", err);
48
49 return err;
50 }
51
52 LOG_DBG("svc_handle: %u, svc_end_handle: %u", svc_handle, svc_end_handle);
53
54 err = bt_testlib_gatt_discover_characteristic(chrc_value_handle, &chrc_end_handle, NULL,
55 conn, chrc, (svc_handle + 1), svc_end_handle);
56 if (err != 0) {
57 LOG_ERR("Failed to get value handle: %d", err);
58
59 return err;
60 }
61
62 LOG_DBG("chrc_value_handle: %u, chrc_end_handle: %u", *chrc_value_handle, chrc_end_handle);
63
64 return err;
65 }
66
received_notification(struct bt_conn * conn,struct bt_gatt_subscribe_params * params,const void * data,uint16_t length)67 static uint8_t received_notification(struct bt_conn *conn, struct bt_gatt_subscribe_params *params,
68 const void *data, uint16_t length)
69 {
70 if (length) {
71 LOG_INF("RX notification");
72 LOG_HEXDUMP_DBG(data, length, "payload");
73 SET_FLAG(got_notification_1);
74
75 TEST_ASSERT(length == GATT_PAYLOAD_SIZE, "Unexpected length: %d", length);
76 }
77
78 return BT_GATT_ITER_CONTINUE;
79 }
80
sub_cb(struct bt_conn * conn,uint8_t err,struct bt_gatt_subscribe_params * params)81 static void sub_cb(struct bt_conn *conn, uint8_t err, struct bt_gatt_subscribe_params *params)
82 {
83 TEST_ASSERT(!err, "Subscribe failed (err %d)", err);
84
85 TEST_ASSERT(params, "params is NULL");
86 TEST_ASSERT(params->value, "Host shouldn't know we have unsubscribed");
87
88 LOG_DBG("Subscribed to handle 0x%04x", params->value_handle);
89 SET_FLAG(is_subscribed);
90 }
91
92 /* Subscription parameters have the same lifetime as a subscription.
93 * That is the backing struct should stay valid until a call to
94 * `bt_gatt_unsubscribe()` is made. Hence the `static`.
95 */
96 static struct bt_gatt_subscribe_params sub_params;
97
98 /* This is "working memory" used by the `CONFIG_BT_GATT_AUTO_DISCOVER_CCC`
99 * feature. It also has to stay valid until the end of the async call.
100 */
101 static struct bt_gatt_discover_params ccc_disc_params;
102
subscribe(struct bt_conn * conn,uint16_t handle,bt_gatt_notify_func_t cb)103 static void subscribe(struct bt_conn *conn, uint16_t handle, bt_gatt_notify_func_t cb)
104 {
105 int err;
106
107 /* Subscribe to notifications */
108 sub_params.notify = cb;
109 sub_params.subscribe = sub_cb;
110 sub_params.value = BT_GATT_CCC_INDICATE;
111 sub_params.value_handle = handle;
112 sub_params.ccc_handle = BT_GATT_AUTO_DISCOVER_CCC_HANDLE;
113 sub_params.disc_params = &ccc_disc_params;
114 sub_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
115
116 err = bt_gatt_subscribe(conn, &sub_params);
117 TEST_ASSERT(!err, "Subscribe failed (err %d)", err);
118
119 WAIT_FOR_FLAG(is_subscribed);
120 }
121
122 static uint16_t g_handle;
123
test_iteration(void)124 static void test_iteration(void)
125 {
126 struct bt_conn *conn;
127 int err;
128
129 err = bt_testlib_adv_conn(&conn, BT_ID_DEFAULT, bt_get_name());
130 TEST_ASSERT(!err, "Failed to start connectable advertising (err %d)", err);
131
132 if (g_handle) {
133 LOG_DBG("Re-use cached characteristic");
134 } else {
135 LOG_DBG("Discover test characteristic");
136 err = find_characteristic(conn, test_service_uuid, test_characteristic_uuid,
137 &g_handle);
138 TEST_ASSERT(!err, "Failed to find characteristic: %d", err);
139 }
140
141 LOG_DBG("Subscribe to test characteristic: handle 0x%04x", g_handle);
142 subscribe(conn, g_handle, received_notification);
143
144 WAIT_FOR_FLAG(got_notification_1);
145
146 bt_testlib_wait_disconnected(conn);
147 bt_testlib_conn_unref(&conn);
148 }
149
150 /* Read the comments on `entrypoint_dut()` first. */
entrypoint_peer(void)151 void entrypoint_peer(void)
152 {
153 int err;
154
155 /* Mark test as in progress. */
156 TEST_START("peer");
157
158 /* Set the log level given by the `log_level` CLI argument */
159 bt_testlib_log_level_set("peer", runtime_log_level);
160
161 /* Initialize Bluetooth */
162 err = bt_enable(NULL);
163 TEST_ASSERT(err == 0, "Can't enable Bluetooth (err %d)", err);
164
165 LOG_DBG("Bluetooth initialized");
166
167 for (int i = 0; i < TEST_ITERATIONS; i++) {
168 LOG_INF("## Iteration %d", i);
169 test_iteration();
170 }
171
172 TEST_PASS_AND_EXIT("peer");
173 }
174