1 /* btp_hap.c - Bluetooth HAP Tester */
2
3 /*
4 * Copyright (c) 2023 Codecoup
5 *
6 * SPDX-License-Identifier: Apache-2.0
7 */
8
9 #include <errno.h>
10 #include <stdbool.h>
11 #include <stdint.h>
12
13 #include <zephyr/autoconf.h>
14 #include <zephyr/bluetooth/addr.h>
15 #include <zephyr/bluetooth/att.h>
16 #include <zephyr/bluetooth/audio/audio.h>
17 #include <zephyr/bluetooth/audio/bap.h>
18 #include <zephyr/bluetooth/audio/has.h>
19 #include <zephyr/bluetooth/audio/pacs.h>
20 #include <zephyr/bluetooth/bluetooth.h>
21 #include <zephyr/bluetooth/conn.h>
22 #include <zephyr/bluetooth/services/ias.h>
23 #include <zephyr/logging/log.h>
24 #include <zephyr/sys/byteorder.h>
25 #include <zephyr/sys/util.h>
26 #include <zephyr/sys/util_macro.h>
27
28 #include "../bluetooth/audio/has_internal.h"
29 #include "btp/btp.h"
30
31 LOG_MODULE_REGISTER(bttester_hap, CONFIG_BTTESTER_LOG_LEVEL);
32
read_supported_commands(const void * cmd,uint16_t cmd_len,void * rsp,uint16_t * rsp_len)33 static uint8_t read_supported_commands(const void *cmd, uint16_t cmd_len, void *rsp,
34 uint16_t *rsp_len)
35 {
36 struct btp_hap_read_supported_commands_rp *rp = rsp;
37
38 *rsp_len = tester_supported_commands(BTP_SERVICE_ID_HAP, rp->data);
39 *rsp_len += sizeof(*rp);
40
41 return BTP_STATUS_SUCCESS;
42 }
43
ha_init(const void * cmd,uint16_t cmd_len,void * rsp,uint16_t * rsp_len)44 static uint8_t ha_init(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len)
45 {
46 const struct btp_hap_ha_init_cmd *cp = cmd;
47 struct bt_has_features_param params;
48 const uint16_t opts = sys_le16_to_cpu(cp->opts);
49 const bool presets_sync = (opts & BTP_HAP_HA_OPT_PRESETS_SYNC) > 0;
50 const bool presets_independent = (opts & BTP_HAP_HA_OPT_PRESETS_INDEPENDENT) > 0;
51 const bool presets_writable = (opts & BTP_HAP_HA_OPT_PRESETS_WRITABLE) > 0;
52 const bool presets_dynamic = (opts & BTP_HAP_HA_OPT_PRESETS_DYNAMIC) > 0;
53 int err;
54
55 if (!IS_ENABLED(CONFIG_BT_HAS_PRESET_SUPPORT) &&
56 (presets_sync || presets_independent || presets_writable || presets_dynamic)) {
57 return BTP_STATUS_VAL(-ENOTSUP);
58 }
59
60 /* Only dynamic presets are supported */
61 if (!presets_dynamic) {
62 return BTP_STATUS_VAL(-ENOTSUP);
63 }
64
65 /* Preset name writable support mismatch */
66 if (presets_writable != IS_ENABLED(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC)) {
67 return BTP_STATUS_VAL(-ENOTSUP);
68 }
69
70 params.type = cp->type;
71 params.preset_sync_support = presets_sync;
72 params.independent_presets = presets_independent;
73
74 if (cp->type == BT_HAS_HEARING_AID_TYPE_BANDED) {
75 err = bt_pacs_set_location(BT_AUDIO_DIR_SINK, BT_AUDIO_LOCATION_FRONT_LEFT |
76 BT_AUDIO_LOCATION_FRONT_RIGHT);
77 } else {
78 err = bt_pacs_set_location(BT_AUDIO_DIR_SINK, BT_AUDIO_LOCATION_FRONT_LEFT);
79 }
80
81 if (err != 0) {
82 return BTP_STATUS_VAL(err);
83 }
84
85 err = bt_has_register(¶ms);
86 if (err != 0) {
87 return BTP_STATUS_VAL(err);
88 }
89
90 return BTP_STATUS_SUCCESS;
91 }
92
has_client_discover_cb(struct bt_conn * conn,int err,struct bt_has * has,enum bt_has_hearing_aid_type type,enum bt_has_capabilities caps)93 static void has_client_discover_cb(struct bt_conn *conn, int err, struct bt_has *has,
94 enum bt_has_hearing_aid_type type, enum bt_has_capabilities caps)
95 {
96 struct btp_hap_hauc_discovery_complete_ev ev = { 0 };
97
98 LOG_DBG("conn %p err %d", (void *)conn, err);
99
100 bt_addr_le_copy(&ev.address, bt_conn_get_dst(conn));
101 ev.status = BTP_STATUS_VAL(err);
102
103 if (err != 0 && err != BT_ATT_ERR_ATTRIBUTE_NOT_FOUND) {
104 LOG_DBG("Client discovery failed: %d", err);
105 } else {
106 struct bt_has_client *inst = CONTAINER_OF(has, struct bt_has_client, has);
107
108 ev.has_hearing_aid_features_handle = inst->features_subscription.value_handle;
109 ev.has_control_point_handle = inst->control_point_subscription.value_handle;
110 ev.has_active_preset_index_handle = inst->active_index_subscription.value_handle;
111 }
112
113 tester_event(BTP_SERVICE_ID_HAP, BT_HAP_EV_HAUC_DISCOVERY_COMPLETE, &ev, sizeof(ev));
114 }
115
has_client_preset_switch_cb(struct bt_has * has,int err,uint8_t index)116 static void has_client_preset_switch_cb(struct bt_has *has, int err, uint8_t index)
117 {
118
119 }
120
121 static const struct bt_has_client_cb has_client_cb = {
122 .discover = has_client_discover_cb,
123 .preset_switch = has_client_preset_switch_cb,
124 };
125
hauc_init(const void * cmd,uint16_t cmd_len,void * rsp,uint16_t * rsp_len)126 static uint8_t hauc_init(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len)
127 {
128 int err;
129
130 err = bt_has_client_cb_register(&has_client_cb);
131 if (err != 0) {
132 LOG_DBG("Failed to register client callbacks: %d", err);
133 return BTP_STATUS_FAILED;
134 }
135
136 return BTP_STATUS_SUCCESS;
137 }
138
ias_client_discover_cb(struct bt_conn * conn,int err)139 static void ias_client_discover_cb(struct bt_conn *conn, int err)
140 {
141 struct btp_hap_iac_discovery_complete_ev ev;
142 struct bt_conn_info info;
143
144 bt_conn_get_info(conn, &info);
145
146 bt_addr_le_copy(&ev.address, info.le.dst);
147 if (err < 0) {
148 ev.status = BT_ATT_ERR_UNLIKELY;
149 } else {
150 ev.status = (uint8_t)err;
151 }
152
153 tester_event(BTP_SERVICE_ID_HAP, BT_HAP_EV_IAC_DISCOVERY_COMPLETE, &ev, sizeof(ev));
154 }
155
156 static const struct bt_ias_client_cb ias_client_cb = {
157 .discover = ias_client_discover_cb,
158 };
159
iac_init(const void * cmd,uint16_t cmd_len,void * rsp,uint16_t * rsp_len)160 static uint8_t iac_init(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len)
161 {
162 int err;
163
164 err = bt_ias_client_cb_register(&ias_client_cb);
165 if (err != 0) {
166 return BTP_STATUS_VAL(err);
167 }
168
169 return BTP_STATUS_SUCCESS;
170 }
171
iac_discover(const void * cmd,uint16_t cmd_len,void * rsp,uint16_t * rsp_len)172 static uint8_t iac_discover(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len)
173 {
174 const struct btp_hap_iac_discover_cmd *cp = cmd;
175 struct bt_conn *conn;
176 int err;
177
178 conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
179 if (!conn) {
180 LOG_ERR("Unknown connection");
181 return BTP_STATUS_FAILED;
182 }
183
184 err = bt_ias_discover(conn);
185
186 bt_conn_unref(conn);
187
188 return BTP_STATUS_VAL(err);
189 }
190
iac_set_alert(const void * cmd,uint16_t cmd_len,void * rsp,uint16_t * rsp_len)191 static uint8_t iac_set_alert(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len)
192 {
193 const struct btp_hap_iac_set_alert_cmd *cp = cmd;
194 struct bt_conn *conn;
195 int err;
196
197 conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
198 if (!conn) {
199 LOG_ERR("Unknown connection");
200 return BTP_STATUS_FAILED;
201 }
202
203 err = bt_ias_client_alert_write(conn, (enum bt_ias_alert_lvl)cp->alert);
204
205 bt_conn_unref(conn);
206
207 return BTP_STATUS_VAL(err);
208 }
209
hauc_discover(const void * cmd,uint16_t cmd_len,void * rsp,uint16_t * rsp_len)210 static uint8_t hauc_discover(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len)
211 {
212 const struct btp_hap_hauc_discover_cmd *cp = cmd;
213 struct bt_conn *conn;
214 int err;
215
216 conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
217 if (!conn) {
218 LOG_ERR("Unknown connection");
219 return BTP_STATUS_FAILED;
220 }
221
222 err = bt_has_client_discover(conn);
223 if (err != 0) {
224 LOG_DBG("Failed to discover remote HAS: %d", err);
225 }
226
227 bt_conn_unref(conn);
228
229 return BTP_STATUS_VAL(err);
230 }
231
232 static const struct btp_handler hap_handlers[] = {
233 {
234 .opcode = BTP_HAP_READ_SUPPORTED_COMMANDS,
235 .index = BTP_INDEX_NONE,
236 .expect_len = 0,
237 .func = read_supported_commands,
238 },
239 {
240 .opcode = BTP_HAP_HA_INIT,
241 .expect_len = sizeof(struct btp_hap_ha_init_cmd),
242 .func = ha_init,
243 },
244 {
245 .opcode = BTP_HAP_HAUC_INIT,
246 .expect_len = 0,
247 .func = hauc_init,
248 },
249 {
250 .opcode = BTP_HAP_IAC_INIT,
251 .expect_len = 0,
252 .func = iac_init,
253 },
254 {
255 .opcode = BTP_HAP_IAC_DISCOVER,
256 .expect_len = sizeof(struct btp_hap_iac_discover_cmd),
257 .func = iac_discover,
258 },
259 {
260 .opcode = BTP_HAP_IAC_SET_ALERT,
261 .expect_len = sizeof(struct btp_hap_iac_set_alert_cmd),
262 .func = iac_set_alert,
263 },
264 {
265 .opcode = BTP_HAP_HAUC_DISCOVER,
266 .expect_len = sizeof(struct btp_hap_hauc_discover_cmd),
267 .func = hauc_discover,
268 },
269 };
270
tester_init_hap(void)271 uint8_t tester_init_hap(void)
272 {
273 tester_register_command_handlers(BTP_SERVICE_ID_HAP, hap_handlers,
274 ARRAY_SIZE(hap_handlers));
275
276 return BTP_STATUS_SUCCESS;
277 }
278
tester_unregister_hap(void)279 uint8_t tester_unregister_hap(void)
280 {
281 return BTP_STATUS_SUCCESS;
282 }
283