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(&params);
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