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