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