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/conn.h"
14 #include "testlib/scan.h"
15 #include "testlib/log_utils.h"
16
17 #include "babblekit/flags.h"
18 #include "babblekit/sync.h"
19 #include "babblekit/testcase.h"
20
21 /* local includes */
22 #include "data.h"
23
24 LOG_MODULE_REGISTER(dut, LOG_LEVEL_DBG);
25
26 DEFINE_FLAG_STATIC(is_subscribed);
27
28 extern unsigned long runtime_log_level;
29
ccc_changed(const struct bt_gatt_attr * attr,uint16_t value)30 static void ccc_changed(const struct bt_gatt_attr *attr, uint16_t value)
31 {
32 /* assume we only get it for the `test_gatt_service` */
33 if (value != 0) {
34 SET_FLAG(is_subscribed);
35 } else {
36 UNSET_FLAG(is_subscribed);
37 }
38 }
39
40 BT_GATT_SERVICE_DEFINE(test_gatt_service, BT_GATT_PRIMARY_SERVICE(test_service_uuid),
41 BT_GATT_CHARACTERISTIC(test_characteristic_uuid,
42 (BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE |
43 BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_INDICATE),
44 BT_GATT_PERM_READ | BT_GATT_PERM_WRITE, NULL, NULL,
45 NULL),
46 BT_GATT_CCC(ccc_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),);
47
48 /* This is the entrypoint for the DUT.
49 *
50 * This is executed by the `bst_test` framework provided by the zephyr bsim
51 * boards. The framework selects which "main" function to run as entrypoint
52 * depending on the `-testid=` command-line parameter passed to the zephyr
53 * executable.
54 *
55 * In our case, the `testid` is set to "dut" and `entrypoint_dut()` is mapped to
56 * the "dut" ID in `entrypoints[]`.
57 *
58 * In our case we only have two entrypoints, as we only have a single test
59 * involving two devices (so 1x 2 entrypoints). One can define more test cases
60 * with different entrypoints and map them to different test ID strings in
61 * `entrypoints[]`
62 */
entrypoint_dut(void)63 void entrypoint_dut(void)
64 {
65 /* Please leave a comment indicating what the test is supposed to test,
66 * and what is the pass verdict. A nice place is at the beginning of
67 * each test entry point. Something like the following:
68 */
69
70 /* Test purpose:
71 *
72 * Verifies that we are able to send a notification to the peer when
73 * `CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION` is disabled and the peer has
74 * unsubscribed from the characteristic in question.
75 *
76 * Two devices:
77 * - `dut`: tries to send the notification
78 * - `peer`: will receive the notification
79 *
80 * Procedure:
81 * - [dut] establish connection to `peer`
82 * - [peer] discover GATT and subscribe to the test characteristic
83 * - [dut] send notification #1
84 * - [peer] wait for notification
85 * - [peer] unsubscribe
86 * - [dut] send notification #2
87 * - [peer] and [dut] pass test
88 *
89 * [verdict]
90 * - peer receives notifications #1 and #2
91 */
92 int err;
93 bt_addr_le_t peer = {};
94 struct bt_conn *conn = NULL;
95 const struct bt_gatt_attr *attr;
96 uint8_t data[10];
97
98 /* Mark test as in progress. */
99 TEST_START("dut");
100
101 /* Initialize device sync library */
102 bk_sync_init();
103
104 /* Set the log level given by the `log_level` CLI argument */
105 bt_testlib_log_level_set("dut", runtime_log_level);
106
107 /* Initialize Bluetooth */
108 err = bt_enable(NULL);
109 TEST_ASSERT(err == 0, "Can't enable Bluetooth (err %d)", err);
110
111 LOG_DBG("Bluetooth initialized");
112
113 /* Find the address of the peer. In our case, both devices are actually
114 * the same executable (with the same config) but executed with
115 * different arguments. We can then just use CONFIG_BT_DEVICE_NAME which
116 * contains our device name in string form.
117 */
118 err = bt_testlib_scan_find_name(&peer, CONFIG_BT_DEVICE_NAME);
119 TEST_ASSERT(!err, "Failed to start scan (err %d)", err);
120
121 /* Create a connection using that address */
122 err = bt_testlib_connect(&peer, &conn);
123 TEST_ASSERT(!err, "Failed to initiate connection (err %d)", err);
124
125 LOG_DBG("Connected");
126
127 LOG_INF("Wait until peer subscribes");
128 UNSET_FLAG(is_subscribed);
129 WAIT_FOR_FLAG(is_subscribed);
130
131 /* Prepare data for notifications
132 * attrs[0] is our service declaration
133 * attrs[1] is our characteristic declaration
134 * attrs[2] is our characteristic value
135 *
136 * We store a pointer for the characteristic value as that is the
137 * value we want to notify later.
138 *
139 * We could alternatively use `bt_gatt_notify_uuid()`.
140 */
141 attr = &test_gatt_service.attrs[2];
142
143 LOG_INF("Send notification #1");
144 LOG_HEXDUMP_DBG(data, sizeof(data), "Notification payload");
145
146 err = bt_gatt_notify(conn, attr, payload_1, sizeof(payload_1));
147 TEST_ASSERT(!err, "Failed to send notification: err %d", err);
148
149 LOG_INF("Wait until peer unsubscribes");
150 WAIT_FOR_FLAG_UNSET(is_subscribed);
151
152 LOG_INF("Send notification #2");
153 err = bt_gatt_notify(conn, attr, payload_2, sizeof(payload_2));
154 TEST_ASSERT(!err, "Failed to send notification: err %d", err);
155
156 /* We won't be using `conn` anymore */
157 bt_conn_unref(conn);
158
159 /* Wait until the peer has received notification #2.
160 *
161 * This is not strictly necessary, but serves as an example on how to
162 * use the backchannel-based synchronization mechanism between devices
163 * in a simulation.
164 */
165 bk_sync_wait();
166
167 /* Wait for the acknowledge of the DUT. If a device that uses
168 * backchannels exits prematurely (ie before the other side has read the
169 * message it sent), we are in undefined behavior territory.
170 *
171 * The simulation will continue running for its specified length.
172 *
173 * If you don't need backchannels, using `TEST_PASS_AND_EXIT()` is
174 * better as it will make the simulation exit prematurely, saving
175 * computing resources (CI compute time is not free).
176 */
177 TEST_PASS("dut");
178 }
179