1 /* Copyright (c) 2023 Nordic Semiconductor ASA
2  * SPDX-License-Identifier: Apache-2.0
3  */
4 
5 #include <stddef.h>
6 #include <stdint.h>
7 #include <zephyr/bluetooth/bluetooth.h>
8 #include <zephyr/bluetooth/conn.h>
9 #include <zephyr/bluetooth/gatt.h>
10 #include <zephyr/bluetooth/l2cap.h>
11 #include <zephyr/bluetooth/uuid.h>
12 #include <zephyr/kernel.h>
13 #include <zephyr/logging/log_core.h>
14 #include <zephyr/logging/log.h>
15 #include <zephyr/sys/__assert.h>
16 
17 LOG_MODULE_REGISTER(bt_testlib_security, LOG_LEVEL_INF);
18 
19 struct testlib_security_ctx {
20 	enum bt_security_err result;
21 	struct bt_conn *conn;
22 	bt_security_t new_minimum;
23 	struct k_condvar done;
24 };
25 
26 /* Context pool (with capacity of one). */
27 static K_SEM_DEFINE(g_ctx_free, 1, 1);
28 static K_MUTEX_DEFINE(g_ctx_lock);
29 static struct testlib_security_ctx *g_ctx;
30 
security_changed(struct bt_conn * conn,bt_security_t level,enum bt_security_err err)31 static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_security_err err)
32 {
33 	LOG_INF("conn %u level %d err %d", bt_conn_index(conn), level, err);
34 
35 	/* Mutex operations establish a happens-before relationship. This
36 	 * ensures variables have the expected values despite non-atomic
37 	 * accesses.
38 	 */
39 	k_mutex_lock(&g_ctx_lock, K_FOREVER);
40 
41 	if (g_ctx && (g_ctx->conn == conn)) {
42 		g_ctx->result = err;
43 		/* Assumption: A security error means there will be further
44 		 * security changes for this connection.
45 		 */
46 		if (err || level >= g_ctx->new_minimum) {
47 			k_condvar_signal(&g_ctx->done);
48 		}
49 	}
50 
51 	k_mutex_unlock(&g_ctx_lock);
52 }
53 
54 BT_CONN_CB_DEFINE(conn_callbacks) = {
55 	.security_changed = security_changed,
56 };
57 
bt_testlib_secure(struct bt_conn * conn,bt_security_t new_minimum)58 int bt_testlib_secure(struct bt_conn *conn, bt_security_t new_minimum)
59 {
60 	int api_err = 0;
61 	struct testlib_security_ctx ctx = {
62 		.conn = conn,
63 		.new_minimum = new_minimum,
64 	};
65 
66 	k_condvar_init(&ctx.done);
67 
68 	/* The semaphore allocates `g_ctx` to this invocation of
69 	 * `bt_testlib_secure`, in case this function is called from multiple
70 	 * threads in parallel.
71 	 */
72 	k_sem_take(&g_ctx_free, K_FOREVER);
73 	/* The mutex synchronizes this function with `security_changed()`. */
74 	k_mutex_lock(&g_ctx_lock, K_FOREVER);
75 
76 	/* Do the thing. */
77 	api_err = bt_conn_set_security(conn, new_minimum);
78 
79 	/* Holding the mutex will pause any thread entering
80 	 * `security_changed_cb`, delaying it until `k_condvar_wait`. This
81 	 * ensures that the condition variable is signaled while this thread is
82 	 * in `k_condvar_wait`, even if the event happens before, e.g. between
83 	 * `bt_conn_get_security` and `k_condvar_wait`.
84 	 *
85 	 * If the security level is already satisfied, there is no point in
86 	 * waiting, and it would deadlock if security was already satisfied
87 	 * before the mutex was taken, `bt_conn_set_security` will result in no
88 	 * operation.
89 	 */
90 	if (!api_err && bt_conn_get_security(conn) < new_minimum) {
91 		/* Waiting on a condvar releases the mutex and waits for a
92 		 * signal on the condvar, atomically, without a gap between the
93 		 * release and wait. The mutex is locked again before returning.
94 		 */
95 		g_ctx = &ctx;
96 		k_condvar_wait(&ctx.done, &g_ctx_lock, K_FOREVER);
97 		g_ctx = NULL;
98 	}
99 
100 	k_mutex_unlock(&g_ctx_lock);
101 	k_sem_give(&g_ctx_free);
102 
103 	if (api_err) {
104 		__ASSERT_NO_MSG(api_err < 0);
105 		return api_err;
106 	}
107 
108 	__ASSERT_NO_MSG(ctx.result >= 0);
109 	return ctx.result;
110 }
111