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