1 /* Spa hsphfpd backend
2 *
3 * Based on previous work for pulseaudio by: Pali Rohár <pali.rohar@gmail.com>
4 * Copyright © 2020 Collabora Ltd.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice (including the next
14 * paragraph) shall be included in all copies or substantial portions of the
15 * Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 * DEALINGS IN THE SOFTWARE.
24 */
25
26 #include <errno.h>
27 #include <unistd.h>
28 #include <sys/socket.h>
29
30 #include <dbus/dbus.h>
31
32 #include <spa/support/log.h>
33 #include <spa/support/loop.h>
34 #include <spa/support/dbus.h>
35 #include <spa/support/plugin.h>
36 #include <spa/utils/string.h>
37 #include <spa/utils/type.h>
38 #include <spa/param/audio/raw.h>
39
40 #include "defs.h"
41
42 static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.hsphfpd");
43 #undef SPA_LOG_TOPIC_DEFAULT
44 #define SPA_LOG_TOPIC_DEFAULT &log_topic
45
46 struct impl {
47 struct spa_bt_backend this;
48
49 struct spa_bt_monitor *monitor;
50
51 struct spa_log *log;
52 struct spa_loop *main_loop;
53 struct spa_dbus *dbus;
54 DBusConnection *conn;
55
56 const struct spa_bt_quirks *quirks;
57
58 struct spa_list endpoint_list;
59 bool endpoints_listed;
60
61 char *hsphfpd_service_id;
62
63 bool acquire_in_progress;
64
65 unsigned int filters_added:1;
66 unsigned int msbc_supported:1;
67 };
68
69 enum hsphfpd_volume_control {
70 HSPHFPD_VOLUME_CONTROL_NONE = 1,
71 HSPHFPD_VOLUME_CONTROL_LOCAL,
72 HSPHFPD_VOLUME_CONTROL_REMOTE,
73 };
74
75 enum hsphfpd_profile {
76 HSPHFPD_PROFILE_HEADSET = 1,
77 HSPHFPD_PROFILE_HANDSFREE,
78 };
79
80 enum hsphfpd_role {
81 HSPHFPD_ROLE_CLIENT = 1,
82 HSPHFPD_ROLE_GATEWAY,
83 };
84
85 struct hsphfpd_transport_data {
86 char *transport_path;
87 bool rx_soft_volume;
88 bool tx_soft_volume;
89 int rx_volume_gain;
90 int tx_volume_gain;
91 int max_rx_volume_gain;
92 int max_tx_volume_gain;
93 enum hsphfpd_volume_control rx_volume_control;
94 enum hsphfpd_volume_control tx_volume_control;
95 };
96
97 struct hsphfpd_endpoint {
98 struct spa_list link;
99 char *path;
100 bool valid;
101 bool connected;
102 char *remote_address;
103 char *local_address;
104 enum hsphfpd_profile profile;
105 enum hsphfpd_role role;
106 int air_codecs;
107 };
108
109 #define DBUS_INTERFACE_OBJECTMANAGER "org.freedesktop.DBus.ObjectManager"
110
111 #define HSPHFPD_APPLICATION_MANAGER_INTERFACE HSPHFPD_SERVICE ".ApplicationManager"
112 #define HSPHFPD_ENDPOINT_INTERFACE HSPHFPD_SERVICE ".Endpoint"
113 #define HSPHFPD_AUDIO_AGENT_INTERFACE HSPHFPD_SERVICE ".AudioAgent"
114 #define HSPHFPD_AUDIO_TRANSPORT_INTERFACE HSPHFPD_SERVICE ".AudioTransport"
115
116 #define APPLICATION_OBJECT_MANAGER_PATH "/Profile/hsphfpd/manager"
117 #define HSPHFP_AUDIO_CLIENT_PCM_S16LE_8KHZ "/Profile/hsphfpd/pcm_s16le_8khz_agent"
118 #define HSPHFP_AUDIO_CLIENT_MSBC "/Profile/hsphfpd/msbc_agent"
119
120 #define HSPHFP_AIR_CODEC_CVSD "CVSD"
121 #define HSPHFP_AIR_CODEC_MSBC "mSBC"
122 #define HSPHFP_AGENT_CODEC_PCM "PCM_s16le_8kHz"
123 #define HSPHFP_AGENT_CODEC_MSBC "mSBC"
124
125 #define APPLICATION_OBJECT_MANAGER_INTROSPECT_XML \
126 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
127 "<node>\n" \
128 " <interface name=\"" DBUS_INTERFACE_OBJECTMANAGER "\">\n" \
129 " <method name=\"GetManagedObjects\">\n" \
130 " <arg name=\"objects\" direction=\"out\" type=\"a{oa{sa{sv}}}\"/>\n" \
131 " </method>\n" \
132 " <signal name=\"InterfacesAdded\">\n" \
133 " <arg name=\"object\" type=\"o\"/>\n" \
134 " <arg name=\"interfaces\" type=\"a{sa{sv}}\"/>\n" \
135 " </signal>\n" \
136 " <signal name=\"InterfacesRemoved\">\n" \
137 " <arg name=\"object\" type=\"o\"/>\n" \
138 " <arg name=\"interfaces\" type=\"as\"/>\n" \
139 " </signal>\n" \
140 " </interface>\n" \
141 " <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">\n" \
142 " <method name=\"Introspect\">\n" \
143 " <arg name=\"data\" direction=\"out\" type=\"s\"/>\n" \
144 " </method>\n" \
145 " </interface>\n" \
146 "</node>\n"
147
148 #define AUDIO_AGENT_ENDPOINT_INTROSPECT_XML \
149 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
150 "<node>\n" \
151 " <interface name=\"" HSPHFPD_AUDIO_AGENT_INTERFACE "\">\n" \
152 " <method name=\"NewConnection\">\n" \
153 " <arg name=\"audio_transport\" direction=\"in\" type=\"o\"/>\n" \
154 " <arg name=\"fd\" direction=\"in\" type=\"h\"/>\n" \
155 " <arg name=\"properties\" direction=\"in\" type=\"a{sv}\"/>\n" \
156 " </method>\n" \
157 " <property name=\"AgentCodec\" type=\"s\" access=\"read\"/>\n" \
158 " </interface>\n" \
159 " <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">\n" \
160 " <method name=\"Introspect\">\n" \
161 " <arg name=\"data\" type=\"s\" direction=\"out\"/>\n" \
162 " </method>\n" \
163 " </interface>\n" \
164 "</node>\n"
165
166 #define HSPHFPD_ERROR_INVALID_ARGUMENTS HSPHFPD_SERVICE ".Error.InvalidArguments"
167 #define HSPHFPD_ERROR_ALREADY_EXISTS HSPHFPD_SERVICE ".Error.AlreadyExists"
168 #define HSPHFPD_ERROR_DOES_NOT_EXISTS HSPHFPD_SERVICE ".Error.DoesNotExist"
169 #define HSPHFPD_ERROR_NOT_CONNECTED HSPHFPD_SERVICE ".Error.NotConnected"
170 #define HSPHFPD_ERROR_ALREADY_CONNECTED HSPHFPD_SERVICE ".Error.AlreadyConnected"
171 #define HSPHFPD_ERROR_IN_PROGRESS HSPHFPD_SERVICE ".Error.InProgress"
172 #define HSPHFPD_ERROR_IN_USE HSPHFPD_SERVICE ".Error.InUse"
173 #define HSPHFPD_ERROR_NOT_SUPPORTED HSPHFPD_SERVICE ".Error.NotSupported"
174 #define HSPHFPD_ERROR_NOT_AVAILABLE HSPHFPD_SERVICE ".Error.NotAvailable"
175 #define HSPHFPD_ERROR_FAILED HSPHFPD_SERVICE ".Error.Failed"
176 #define HSPHFPD_ERROR_REJECTED HSPHFPD_SERVICE ".Error.Rejected"
177 #define HSPHFPD_ERROR_CANCELED HSPHFPD_SERVICE ".Error.Canceled"
178
endpoint_find(struct impl * backend,const char * path)179 static struct hsphfpd_endpoint *endpoint_find(struct impl *backend, const char *path)
180 {
181 struct hsphfpd_endpoint *d;
182 spa_list_for_each(d, &backend->endpoint_list, link)
183 if (spa_streq(d->path, path))
184 return d;
185 return NULL;
186 }
187
endpoint_free(struct hsphfpd_endpoint * endpoint)188 static void endpoint_free(struct hsphfpd_endpoint *endpoint)
189 {
190 spa_list_remove(&endpoint->link);
191 free(endpoint->path);
192 if (endpoint->local_address)
193 free(endpoint->local_address);
194 if (endpoint->remote_address)
195 free(endpoint->remote_address);
196 }
197
hsphfpd_cmp_transport_path(struct spa_bt_transport * t,const void * data)198 static bool hsphfpd_cmp_transport_path(struct spa_bt_transport *t, const void *data)
199 {
200 struct hsphfpd_transport_data *td = t->user_data;
201 if (spa_streq(td->transport_path, data))
202 return true;
203
204 return false;
205 }
206
check_signature(DBusMessage * m,const char sig[])207 static inline bool check_signature(DBusMessage *m, const char sig[])
208 {
209 return spa_streq(dbus_message_get_signature(m), sig);
210 }
211
set_dbus_property(struct impl * backend,const char * service,const char * path,const char * interface,const char * property,int type,void * value)212 static int set_dbus_property(struct impl *backend,
213 const char *service,
214 const char *path,
215 const char *interface,
216 const char *property,
217 int type,
218 void *value)
219 {
220 DBusMessage *m, *r;
221 DBusMessageIter iter;
222 DBusError err;
223
224 m = dbus_message_new_method_call(HSPHFPD_SERVICE, path, DBUS_INTERFACE_PROPERTIES, "Set");
225 if (m == NULL)
226 return -ENOMEM;
227 dbus_message_append_args(m, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID);
228 dbus_message_iter_init_append(m, &iter);
229 dbus_message_iter_append_basic(&iter, type, value);
230
231 dbus_error_init(&err);
232
233 r = dbus_connection_send_with_reply_and_block(backend->conn, m, -1, &err);
234 dbus_message_unref(m);
235 m = NULL;
236
237 if (r == NULL) {
238 spa_log_error(backend->log, "Transport Set() failed for transport %s (%s)", path, err.message);
239 dbus_error_free(&err);
240 return -EIO;
241 }
242
243 if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
244 spa_log_error(backend->log, "Set() returned error: %s", dbus_message_get_error_name(r));
245 return -EIO;
246 }
247
248 dbus_message_unref(r);
249 return 0;
250 }
251
set_rx_volume_gain_property(const struct spa_bt_transport * transport,uint16_t gain)252 static inline void set_rx_volume_gain_property(const struct spa_bt_transport *transport, uint16_t gain)
253 {
254 struct impl *backend = SPA_CONTAINER_OF(transport->backend, struct impl, this);
255 struct hsphfpd_transport_data *transport_data = transport->user_data;
256
257 if (transport->fd < 0 || transport_data->rx_volume_control <= HSPHFPD_VOLUME_CONTROL_NONE)
258 return;
259 if (set_dbus_property(backend, HSPHFPD_SERVICE, transport_data->transport_path,
260 HSPHFPD_AUDIO_TRANSPORT_INTERFACE, "RxVolumeGain",
261 DBUS_TYPE_UINT16, &gain))
262 spa_log_error(backend->log, "Changing rx volume gain to %u for transport %s failed",
263 (unsigned)gain, transport_data->transport_path);
264 }
265
set_tx_volume_gain_property(const struct spa_bt_transport * transport,uint16_t gain)266 static inline void set_tx_volume_gain_property(const struct spa_bt_transport *transport, uint16_t gain)
267 {
268 struct impl *backend = SPA_CONTAINER_OF(transport->backend, struct impl, this);
269 struct hsphfpd_transport_data *transport_data = transport->user_data;
270
271 if (transport->fd < 0 || transport_data->tx_volume_control <= HSPHFPD_VOLUME_CONTROL_NONE)
272 return;
273 if (set_dbus_property(backend, HSPHFPD_SERVICE, transport_data->transport_path,
274 HSPHFPD_AUDIO_TRANSPORT_INTERFACE, "TxVolumeGain",
275 DBUS_TYPE_UINT16, &gain))
276 spa_log_error(backend->log, "Changing tx volume gain to %u for transport %s failed",
277 (unsigned)gain, transport_data->transport_path);
278 }
279
parse_transport_properties_values(struct impl * backend,const char * transport_path,DBusMessageIter * i,const char ** endpoint_path,const char ** air_codec,enum hsphfpd_volume_control * rx_volume_control,enum hsphfpd_volume_control * tx_volume_control,uint16_t * rx_volume_gain,uint16_t * tx_volume_gain,uint16_t * mtu)280 static void parse_transport_properties_values(struct impl *backend,
281 const char *transport_path,
282 DBusMessageIter *i,
283 const char **endpoint_path,
284 const char **air_codec,
285 enum hsphfpd_volume_control *rx_volume_control,
286 enum hsphfpd_volume_control *tx_volume_control,
287 uint16_t *rx_volume_gain,
288 uint16_t *tx_volume_gain,
289 uint16_t *mtu)
290 {
291 DBusMessageIter element_i;
292
293 spa_assert(i);
294
295 dbus_message_iter_recurse(i, &element_i);
296
297 while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
298 DBusMessageIter dict_i, variant_i;
299 const char *key;
300
301 dbus_message_iter_recurse(&element_i, &dict_i);
302
303 if (dbus_message_iter_get_arg_type(&dict_i) != DBUS_TYPE_STRING) {
304 spa_log_error(backend->log, "Received invalid property for transport %s", transport_path);
305 return;
306 }
307
308 dbus_message_iter_get_basic(&dict_i, &key);
309
310 if (!dbus_message_iter_next(&dict_i)) {
311 spa_log_error(backend->log, "Received invalid property for transport %s", transport_path);
312 return;
313 }
314
315 if (dbus_message_iter_get_arg_type(&dict_i) != DBUS_TYPE_VARIANT) {
316 spa_log_error(backend->log, "Received invalid property for transport %s", transport_path);
317 return;
318 }
319
320 dbus_message_iter_recurse(&dict_i, &variant_i);
321
322 switch (dbus_message_iter_get_arg_type(&variant_i)) {
323 case DBUS_TYPE_STRING:
324 if (spa_streq(key, "RxVolumeControl") || spa_streq(key, "TxVolumeControl")) {
325 const char *value;
326 enum hsphfpd_volume_control volume_control;
327
328 dbus_message_iter_get_basic(&variant_i, &value);
329 if (spa_streq(value, "none"))
330 volume_control = HSPHFPD_VOLUME_CONTROL_NONE;
331 else if (spa_streq(value, "local"))
332 volume_control = HSPHFPD_VOLUME_CONTROL_LOCAL;
333 else if (spa_streq(value, "remote"))
334 volume_control = HSPHFPD_VOLUME_CONTROL_REMOTE;
335 else
336 volume_control = 0;
337
338 if (!volume_control)
339 spa_log_warn(backend->log, "Transport %s received invalid '%s' property value '%s', ignoring", transport_path, key, value);
340 else if (spa_streq(key, "RxVolumeControl"))
341 *rx_volume_control = volume_control;
342 else if (spa_streq(key, "TxVolumeControl"))
343 *tx_volume_control = volume_control;
344 } else if (spa_streq(key, "AirCodec"))
345 dbus_message_iter_get_basic(&variant_i, air_codec);
346 break;
347
348 case DBUS_TYPE_UINT16:
349 if (spa_streq(key, "MTU"))
350 dbus_message_iter_get_basic(&variant_i, mtu);
351 else if (spa_streq(key, "RxVolumeGain"))
352 dbus_message_iter_get_basic(&variant_i, rx_volume_gain);
353 else if (spa_streq(key, "TxVolumeGain"))
354 dbus_message_iter_get_basic(&variant_i, tx_volume_gain);
355 break;
356
357 case DBUS_TYPE_OBJECT_PATH:
358 if (spa_streq(key, "Endpoint"))
359 dbus_message_iter_get_basic(&variant_i, endpoint_path);
360 break;
361 }
362
363 dbus_message_iter_next(&element_i);
364 }
365 }
366
hsphfpd_parse_transport_properties(struct impl * backend,struct spa_bt_transport * transport,DBusMessageIter * i)367 static void hsphfpd_parse_transport_properties(struct impl *backend, struct spa_bt_transport *transport, DBusMessageIter *i)
368 {
369 struct hsphfpd_transport_data *transport_data = transport->user_data;
370 const char *endpoint_path = NULL;
371 const char *air_codec = NULL;
372 enum hsphfpd_volume_control rx_volume_control = 0;
373 enum hsphfpd_volume_control tx_volume_control = 0;
374 uint16_t rx_volume_gain = -1;
375 uint16_t tx_volume_gain = -1;
376 uint16_t mtu = 0;
377 bool rx_volume_gain_changed = false;
378 bool tx_volume_gain_changed = false;
379 bool rx_volume_control_changed = false;
380 bool tx_volume_control_changed = false;
381 bool rx_soft_volume_changed = false;
382 bool tx_soft_volume_changed = false;
383
384 parse_transport_properties_values(backend, transport_data->transport_path, i, &endpoint_path,
385 &air_codec, &rx_volume_control, &tx_volume_control,
386 &rx_volume_gain, &tx_volume_gain, &mtu);
387
388 if (endpoint_path)
389 spa_log_warn(backend->log, "Transport %s received a duplicate '%s' property, ignoring",
390 transport_data->transport_path, "Endpoint");
391
392 if (air_codec)
393 spa_log_warn(backend->log, "Transport %s received a duplicate '%s' property, ignoring",
394 transport_data->transport_path, "AirCodec");
395
396 if (mtu)
397 spa_log_warn(backend->log, "Transport %s received a duplicate '%s' property, ignoring",
398 transport_data->transport_path, "MTU");
399
400 if (rx_volume_control) {
401 if (!!transport_data->rx_soft_volume != !!(rx_volume_control != HSPHFPD_VOLUME_CONTROL_REMOTE)) {
402 spa_log_info(backend->log, "Transport %s changed rx soft volume from %d to %d",
403 transport_data->transport_path, transport_data->rx_soft_volume,
404 (rx_volume_control != HSPHFPD_VOLUME_CONTROL_REMOTE));
405 transport_data->rx_soft_volume = (rx_volume_control != HSPHFPD_VOLUME_CONTROL_REMOTE);
406 rx_soft_volume_changed = true;
407 }
408 if (transport_data->rx_volume_control != rx_volume_control) {
409 transport_data->rx_volume_control = rx_volume_control;
410 rx_volume_control_changed = true;
411 }
412 }
413
414 if (tx_volume_control) {
415 if (!!transport_data->tx_soft_volume != !!(tx_volume_control != HSPHFPD_VOLUME_CONTROL_REMOTE)) {
416 spa_log_info(backend->log, "Transport %s changed tx soft volume from %d to %d",
417 transport_data->transport_path, transport_data->rx_soft_volume,
418 (tx_volume_control != HSPHFPD_VOLUME_CONTROL_REMOTE));
419 transport_data->tx_soft_volume = (tx_volume_control != HSPHFPD_VOLUME_CONTROL_REMOTE);
420 tx_soft_volume_changed = true;
421 }
422 if (transport_data->tx_volume_control != tx_volume_control) {
423 transport_data->tx_volume_control = tx_volume_control;
424 tx_volume_control_changed = true;
425 }
426 }
427
428 if (rx_volume_gain != (uint16_t)-1) {
429 if (transport_data->rx_volume_gain != rx_volume_gain) {
430 spa_log_info(backend->log, "Transport %s changed rx volume gain from %u to %u",
431 transport_data->transport_path, (unsigned)transport_data->rx_volume_gain, (unsigned)rx_volume_gain);
432 transport_data->rx_volume_gain = rx_volume_gain;
433 rx_volume_gain_changed = true;
434 }
435 }
436
437 if (tx_volume_gain != (uint16_t)-1) {
438 if (transport_data->tx_volume_gain != tx_volume_gain) {
439 spa_log_info(backend->log, "Transport %s changed tx volume gain from %u to %u",
440 transport_data->transport_path, (unsigned)transport_data->tx_volume_gain, (unsigned)tx_volume_gain);
441 transport_data->tx_volume_gain = tx_volume_gain;
442 tx_volume_gain_changed = true;
443 }
444 }
445
446 #if 0
447 if (rx_volume_gain_changed || rx_soft_volume_changed)
448 pa_hook_fire(pa_bluetooth_discovery_hook(transport_data->hsphfpd->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_RX_VOLUME_GAIN_CHANGED), transport);
449
450 if (tx_volume_gain_changed || tx_soft_volume_changed)
451 pa_hook_fire(pa_bluetooth_discovery_hook(transport_data->hsphfpd->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_TX_VOLUME_GAIN_CHANGED), transport);
452 #else
453 spa_log_debug(backend->log, "RX volume gain changed: %d, soft volume changed: %d", rx_volume_gain_changed, rx_soft_volume_changed);
454 spa_log_debug(backend->log, "TX volume gain changed: %d, soft volume changed: %d", tx_volume_gain_changed, tx_soft_volume_changed);
455 #endif
456
457 if (rx_volume_control_changed)
458 set_rx_volume_gain_property(transport, transport_data->rx_volume_gain);
459
460 if (tx_volume_control_changed)
461 set_tx_volume_gain_property(transport, transport_data->tx_volume_gain);
462 }
463
audio_agent_get_property(DBusConnection * conn,DBusMessage * m,const char * path,void * userdata)464 static DBusHandlerResult audio_agent_get_property(DBusConnection *conn, DBusMessage *m, const char *path, void *userdata)
465 {
466 const char *interface;
467 const char *property;
468 const char *agent_codec;
469 DBusMessage *r = NULL;
470
471 if (!check_signature(m, "ss")) {
472 r = dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, "Invalid signature in method call");
473 goto fail;
474 }
475
476 if (dbus_message_get_args(m, NULL,
477 DBUS_TYPE_STRING, &interface,
478 DBUS_TYPE_STRING, &property,
479 DBUS_TYPE_INVALID) == FALSE) {
480 r = dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, "Invalid arguments in method call");
481 goto fail;
482 }
483
484 if (!spa_streq(interface, HSPHFPD_AUDIO_AGENT_INTERFACE))
485 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
486
487 if (!spa_streq(property, "AgentCodec")) {
488 r = dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, "Invalid property in method call");
489 goto fail;
490 }
491
492 if (spa_streq(path, HSPHFP_AUDIO_CLIENT_PCM_S16LE_8KHZ))
493 agent_codec = HSPHFP_AGENT_CODEC_PCM;
494 else if (spa_streq(path, HSPHFP_AUDIO_CLIENT_MSBC))
495 agent_codec = HSPHFP_AGENT_CODEC_MSBC;
496 else {
497 r = dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, "Invalid path in method call");
498 goto fail;
499 }
500
501 if ((r = dbus_message_new_method_return(m)) == NULL)
502 return DBUS_HANDLER_RESULT_NEED_MEMORY;
503 if (!dbus_message_append_args(r, DBUS_TYPE_STRING, &agent_codec, DBUS_TYPE_INVALID))
504 return DBUS_HANDLER_RESULT_NEED_MEMORY;
505
506 fail:
507 if (!dbus_connection_send(conn, r, NULL))
508 return DBUS_HANDLER_RESULT_NEED_MEMORY;
509
510 dbus_message_unref(r);
511 return DBUS_HANDLER_RESULT_HANDLED;
512 }
513
audio_agent_getall_properties(DBusConnection * conn,DBusMessage * m,const char * path,void * userdata)514 static DBusHandlerResult audio_agent_getall_properties(DBusConnection *conn, DBusMessage *m, const char *path, void *userdata)
515 {
516 const char *interface;
517 DBusMessageIter iter, array, dict, data;
518 const char *agent_codec_key = "AgentCodec";
519 const char *agent_codec;
520 DBusMessage *r = NULL;
521
522 if (!check_signature(m, "s")) {
523 r = dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, "Invalid signature in method call");
524 goto fail;
525 }
526
527 if (dbus_message_get_args(m, NULL,
528 DBUS_TYPE_STRING, &interface,
529 DBUS_TYPE_INVALID) == FALSE) {
530 r = dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, "Invalid arguments in method call");
531 goto fail;
532 }
533
534 if (spa_streq(path, HSPHFP_AUDIO_CLIENT_PCM_S16LE_8KHZ))
535 agent_codec = HSPHFP_AGENT_CODEC_PCM;
536 else if (spa_streq(path, HSPHFP_AUDIO_CLIENT_MSBC))
537 agent_codec = HSPHFP_AGENT_CODEC_MSBC;
538 else {
539 r = dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, "Invalid path in method call");
540 goto fail;
541 }
542
543 if (!spa_streq(interface, HSPHFPD_AUDIO_AGENT_INTERFACE))
544 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
545
546 if ((r = dbus_message_new_method_return(m)) == NULL)
547 return DBUS_HANDLER_RESULT_NEED_MEMORY;
548 dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &array);
549 dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, NULL, &dict);
550 dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &agent_codec_key);
551 dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, "s", &data);
552 dbus_message_iter_append_basic(&data, DBUS_TYPE_BOOLEAN, &agent_codec);
553 dbus_message_iter_close_container(&dict, &data);
554 dbus_message_iter_close_container(&array, &dict);
555 dbus_message_iter_close_container(&iter, &array);
556
557 fail:
558 if (!dbus_connection_send(conn, r, NULL))
559 return DBUS_HANDLER_RESULT_NEED_MEMORY;
560
561 dbus_message_unref(r);
562 return DBUS_HANDLER_RESULT_HANDLED;
563 }
564
hsphfpd_new_audio_connection(DBusConnection * conn,DBusMessage * m,const char * path,void * userdata)565 static DBusHandlerResult hsphfpd_new_audio_connection(DBusConnection *conn, DBusMessage *m, const char *path, void *userdata)
566 {
567 struct impl *backend = userdata;
568 DBusMessageIter arg_i;
569 const char *transport_path;
570 int fd;
571 const char *sender;
572 const char *endpoint_path = NULL;
573 const char *air_codec = NULL;
574 enum hsphfpd_volume_control rx_volume_control = 0;
575 enum hsphfpd_volume_control tx_volume_control = 0;
576 uint16_t rx_volume_gain = -1;
577 uint16_t tx_volume_gain = -1;
578 uint16_t mtu = 0;
579 unsigned int codec;
580 struct hsphfpd_endpoint *endpoint;
581 struct spa_bt_transport *transport;
582 struct hsphfpd_transport_data *transport_data;
583 DBusMessage *r = NULL;
584
585 if (!check_signature(m, "oha{sv}")) {
586 r = dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, "Invalid signature in method call");
587 goto fail;
588 }
589
590 if (!dbus_message_iter_init(m, &arg_i))
591 return DBUS_HANDLER_RESULT_NEED_MEMORY;
592
593 dbus_message_iter_get_basic(&arg_i, &transport_path);
594 dbus_message_iter_next(&arg_i);
595 dbus_message_iter_get_basic(&arg_i, &fd);
596
597 spa_log_debug(backend->log, "NewConnection %s, fd %d", transport_path, fd);
598
599 sender = dbus_message_get_sender(m);
600 if (!spa_streq(sender, backend->hsphfpd_service_id)) {
601 close(fd);
602 spa_log_error(backend->log, "Sender '%s' is not authorized", sender);
603 r = dbus_message_new_error_printf(m, HSPHFPD_ERROR_REJECTED, "Sender '%s' is not authorized", sender);
604 goto fail;
605 }
606
607 if (spa_streq(path, HSPHFP_AUDIO_CLIENT_PCM_S16LE_8KHZ))
608 codec = HFP_AUDIO_CODEC_CVSD;
609 else if (spa_streq(path, HSPHFP_AUDIO_CLIENT_MSBC))
610 codec = HFP_AUDIO_CODEC_MSBC;
611 else {
612 r = dbus_message_new_error(m, HSPHFPD_ERROR_REJECTED, "Invalid path");
613 goto fail;
614 }
615
616 dbus_message_iter_next(&arg_i);
617 parse_transport_properties_values(backend, transport_path, &arg_i,
618 &endpoint_path, &air_codec,
619 &rx_volume_control, &tx_volume_control,
620 &rx_volume_gain, &tx_volume_gain,
621 &mtu);
622
623 if (!endpoint_path) {
624 close(fd);
625 spa_log_error(backend->log, "Endpoint property was not specified");
626 r = dbus_message_new_error(m, HSPHFPD_ERROR_REJECTED, "Endpoint property was not specified");
627 goto fail;
628 }
629
630 if (!air_codec) {
631 close(fd);
632 spa_log_error(backend->log, "AirCodec property was not specified");
633 r = dbus_message_new_error(m, HSPHFPD_ERROR_REJECTED, "AirCodec property was not specified");
634 goto fail;
635 }
636
637 if (!rx_volume_control) {
638 close(fd);
639 spa_log_error(backend->log, "RxVolumeControl property was not specified");
640 r = dbus_message_new_error(m, HSPHFPD_ERROR_REJECTED, "RxVolumeControl property was not specified");
641 goto fail;
642 }
643
644 if (!tx_volume_control) {
645 close(fd);
646 spa_log_error(backend->log, "TxVolumeControl property was not specified");
647 r = dbus_message_new_error(m, HSPHFPD_ERROR_REJECTED, "TxVolumeControl property was not specified");
648 goto fail;
649 }
650
651 if (rx_volume_control != HSPHFPD_VOLUME_CONTROL_NONE) {
652 if (rx_volume_gain == (uint16_t)-1) {
653 close(fd);
654 spa_log_error(backend->log, "RxVolumeGain property was not specified, but VolumeControl is not none");
655 r = dbus_message_new_error(m, HSPHFPD_ERROR_REJECTED, "RxVolumeGain property was not specified, but VolumeControl is not none");
656 goto fail;
657 }
658 } else {
659 rx_volume_gain = 15; /* No volume control, so set maximal value */
660 }
661
662 if (tx_volume_control != HSPHFPD_VOLUME_CONTROL_NONE) {
663 if (tx_volume_gain == (uint16_t)-1) {
664 close(fd);
665 spa_log_error(backend->log, "TxVolumeGain property was not specified, but VolumeControl is not none");
666 r = dbus_message_new_error(m, HSPHFPD_ERROR_REJECTED, "TxVolumeGain property was not specified, but VolumeControl is not none");
667 goto fail;
668 }
669 } else {
670 tx_volume_gain = 15; /* No volume control, so set maximal value */
671 }
672
673 if (!mtu) {
674 close(fd);
675 spa_log_error(backend->log, "MTU property was not specified");
676 r = dbus_message_new_error(m, HSPHFPD_ERROR_REJECTED, "MTU property was not specified");
677 goto fail;
678 }
679
680 endpoint = endpoint_find(backend, endpoint_path);
681 if (!endpoint) {
682 close(fd);
683 spa_log_error(backend->log, "Endpoint %s does not exist", endpoint_path);
684 r = dbus_message_new_error_printf(m, HSPHFPD_ERROR_REJECTED, "Endpoint %s does not exist", endpoint_path);
685 goto fail;
686 }
687
688 if (!endpoint->valid) {
689 close(fd);
690 spa_log_error(backend->log, "Endpoint %s is not valid", endpoint_path);
691 r = dbus_message_new_error_printf(m, HSPHFPD_ERROR_REJECTED, "Endpoint %s is not valid", endpoint_path);
692 goto fail;
693 }
694
695 transport = spa_bt_transport_find(backend->monitor, endpoint_path);
696 if (!transport) {
697 close(fd);
698 spa_log_error(backend->log, "Endpoint %s is not connected", endpoint_path);
699 r = dbus_message_new_error_printf(m, HSPHFPD_ERROR_REJECTED, "Endpoint %s is not connected", endpoint_path);
700 goto fail;
701 }
702
703 if (transport->codec != codec)
704 spa_log_warn(backend->log, "Expecting codec to be %d, got %d", transport->codec, codec);
705
706 if (transport->fd >= 0) {
707 close(fd);
708 spa_log_error(backend->log, "Endpoint %s has already active transport", endpoint_path);
709 r = dbus_message_new_error_printf(m, HSPHFPD_ERROR_REJECTED, "Endpoint %s has already active transport", endpoint_path);
710 goto fail;
711 }
712
713 transport_data = transport->user_data;
714 transport_data->transport_path = strdup(transport_path);
715 transport_data->rx_soft_volume = (rx_volume_control != HSPHFPD_VOLUME_CONTROL_REMOTE);
716 transport_data->tx_soft_volume = (tx_volume_control != HSPHFPD_VOLUME_CONTROL_REMOTE);
717 transport_data->rx_volume_gain = rx_volume_gain;
718 transport_data->tx_volume_gain = tx_volume_gain;
719 transport_data->rx_volume_control = rx_volume_control;
720 transport_data->tx_volume_control = tx_volume_control;
721
722 #if 0
723 pa_hook_fire(pa_bluetooth_discovery_hook(hsphfpd->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_RX_VOLUME_GAIN_CHANGED), transport);
724 pa_hook_fire(pa_bluetooth_discovery_hook(hsphfpd->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_TX_VOLUME_GAIN_CHANGED), transport);
725 #endif
726
727 transport->read_mtu = mtu;
728 transport->write_mtu = mtu;
729
730 transport->fd = fd;
731
732 if ((r = dbus_message_new_method_return(m)) == NULL)
733 return DBUS_HANDLER_RESULT_NEED_MEMORY;
734
735 fail:
736 if (r) {
737 DBusHandlerResult res = DBUS_HANDLER_RESULT_HANDLED;
738 if (!dbus_connection_send(backend->conn, r, NULL))
739 res = DBUS_HANDLER_RESULT_NEED_MEMORY;
740 dbus_message_unref(r);
741 return res;
742 }
743
744 return DBUS_HANDLER_RESULT_HANDLED;
745 }
746
audio_agent_endpoint_handler(DBusConnection * c,DBusMessage * m,void * userdata)747 static DBusHandlerResult audio_agent_endpoint_handler(DBusConnection *c, DBusMessage *m, void *userdata)
748 {
749 struct impl *backend = userdata;
750 const char *path, *interface, *member;
751 DBusMessage *r;
752 DBusHandlerResult res;
753
754 path = dbus_message_get_path(m);
755 interface = dbus_message_get_interface(m);
756 member = dbus_message_get_member(m);
757
758 spa_log_debug(backend->log, "dbus: path=%s, interface=%s, member=%s", path, interface, member);
759
760 if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
761 const char *xml = AUDIO_AGENT_ENDPOINT_INTROSPECT_XML;
762
763 if ((r = dbus_message_new_method_return(m)) == NULL)
764 return DBUS_HANDLER_RESULT_NEED_MEMORY;
765 if (!dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID))
766 return DBUS_HANDLER_RESULT_NEED_MEMORY;
767 if (!dbus_connection_send(backend->conn, r, NULL))
768 return DBUS_HANDLER_RESULT_NEED_MEMORY;
769
770 dbus_message_unref(r);
771 res = DBUS_HANDLER_RESULT_HANDLED;
772 } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Get"))
773 res = audio_agent_get_property(c, m, path, userdata);
774 else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "GetAll"))
775 res = audio_agent_getall_properties(c, m, path, userdata);
776 else if (dbus_message_is_method_call(m, HSPHFPD_AUDIO_AGENT_INTERFACE, "NewConnection"))
777 res = hsphfpd_new_audio_connection(c, m, path, userdata);
778 else
779 res = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
780
781 return res;
782 }
783
append_audio_agent_object(DBusMessageIter * iter,const char * endpoint,const char * agent_codec)784 static void append_audio_agent_object(DBusMessageIter *iter, const char *endpoint, const char *agent_codec)
785 {
786 const char *interface_name = HSPHFPD_AUDIO_AGENT_INTERFACE;
787 DBusMessageIter object, array, entry, dict, codec, data;
788 char *str = "AgentCodec";
789
790 dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &object);
791 dbus_message_iter_append_basic(&object, DBUS_TYPE_OBJECT_PATH, &endpoint);
792
793 dbus_message_iter_open_container(&object, DBUS_TYPE_ARRAY, "{sa{sv}}", &array);
794
795 dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, NULL, &entry);
796 dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface_name);
797
798 dbus_message_iter_open_container(&entry, DBUS_TYPE_ARRAY, "{sv}", &dict);
799
800 dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &codec);
801 dbus_message_iter_append_basic(&codec, DBUS_TYPE_STRING, &str);
802 dbus_message_iter_open_container(&codec, DBUS_TYPE_VARIANT, "s", &data);
803 dbus_message_iter_append_basic(&data, DBUS_TYPE_STRING, &agent_codec);
804 dbus_message_iter_close_container(&codec, &data);
805 dbus_message_iter_close_container(&dict, &codec);
806
807 dbus_message_iter_close_container(&entry, &dict);
808 dbus_message_iter_close_container(&array, &entry);
809 dbus_message_iter_close_container(&object, &array);
810 dbus_message_iter_close_container(iter, &object);
811 }
812
application_object_manager_handler(DBusConnection * c,DBusMessage * m,void * userdata)813 static DBusHandlerResult application_object_manager_handler(DBusConnection *c, DBusMessage *m, void *userdata)
814 {
815 struct impl *backend = userdata;
816 const char *path, *interface, *member;
817 DBusMessage *r;
818
819 path = dbus_message_get_path(m);
820 interface = dbus_message_get_interface(m);
821 member = dbus_message_get_member(m);
822
823 spa_log_debug(backend->log, "dbus: path=%s, interface=%s, member=%s", path, interface, member);
824
825 if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
826 const char *xml = APPLICATION_OBJECT_MANAGER_INTROSPECT_XML;
827
828 if ((r = dbus_message_new_method_return(m)) == NULL)
829 return DBUS_HANDLER_RESULT_NEED_MEMORY;
830 if (!dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID))
831 return DBUS_HANDLER_RESULT_NEED_MEMORY;
832 } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_OBJECTMANAGER, "GetManagedObjects")) {
833 DBusMessageIter iter, array;
834
835 if ((r = dbus_message_new_method_return(m)) == NULL)
836 return DBUS_HANDLER_RESULT_NEED_MEMORY;
837
838 dbus_message_iter_init_append(r, &iter);
839 dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{oa{sa{sv}}}", &array);
840
841 append_audio_agent_object(&array, HSPHFP_AUDIO_CLIENT_PCM_S16LE_8KHZ, HSPHFP_AGENT_CODEC_PCM);
842 if (backend->msbc_supported)
843 append_audio_agent_object(&array, HSPHFP_AUDIO_CLIENT_MSBC, HSPHFP_AGENT_CODEC_MSBC);
844
845 dbus_message_iter_close_container(&iter, &array);
846 } else
847 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
848
849 if (!dbus_connection_send(backend->conn, r, NULL))
850 return DBUS_HANDLER_RESULT_NEED_MEMORY;
851 dbus_message_unref(r);
852
853 return DBUS_HANDLER_RESULT_HANDLED;
854 }
855
hsphfpd_audio_acquire_reply(DBusPendingCall * pending,void * user_data)856 static void hsphfpd_audio_acquire_reply(DBusPendingCall *pending, void *user_data)
857 {
858 struct impl *backend = user_data;
859 DBusMessage *r;
860 const char *transport_path;
861 const char *service_id;
862 const char *agent_path;
863 DBusError error;
864
865 dbus_error_init(&error);
866
867 backend->acquire_in_progress = false;
868
869 r = dbus_pending_call_steal_reply(pending);
870 if (r == NULL)
871 return;
872
873 if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
874 spa_log_error(backend->log, "RegisterApplication() failed: %s",
875 dbus_message_get_error_name(r));
876 goto finish;
877 }
878
879 if (!spa_streq(dbus_message_get_sender(r), backend->hsphfpd_service_id)) {
880 spa_log_error(backend->log, "Reply for " HSPHFPD_ENDPOINT_INTERFACE ".ConnectAudio() from invalid sender");
881 goto finish;
882 }
883
884 if (!check_signature(r, "oso")) {
885 spa_log_error(backend->log, "Invalid reply signature for " HSPHFPD_ENDPOINT_INTERFACE ".ConnectAudio()");
886 goto finish;
887 }
888
889 if (dbus_message_get_args(r, &error,
890 DBUS_TYPE_OBJECT_PATH, &transport_path,
891 DBUS_TYPE_STRING, &service_id,
892 DBUS_TYPE_OBJECT_PATH, &agent_path,
893 DBUS_TYPE_INVALID) == FALSE) {
894 spa_log_error(backend->log, "Failed to parse " HSPHFPD_ENDPOINT_INTERFACE ".ConnectAudio() reply: %s", error.message);
895 goto finish;
896 }
897
898 if (!spa_streq(service_id, dbus_bus_get_unique_name(backend->conn))) {
899 spa_log_warn(backend->log, HSPHFPD_ENDPOINT_INTERFACE ".ConnectAudio() failed: Other audio application took audio socket");
900 goto finish;
901 }
902
903 spa_log_debug(backend->log, "hsphfpd audio acquired");
904
905 finish:
906 dbus_message_unref(r);
907 dbus_pending_call_unref(pending);
908 }
909
hsphfpd_audio_acquire(void * data,bool optional)910 static int hsphfpd_audio_acquire(void *data, bool optional)
911 {
912 struct spa_bt_transport *transport = data;
913 struct impl *backend = SPA_CONTAINER_OF(transport->backend, struct impl, this);
914 DBusMessage *m;
915 const char *air_codec = HSPHFP_AIR_CODEC_CVSD;
916 const char *agent_codec = HSPHFP_AGENT_CODEC_PCM;
917 DBusPendingCall *call;
918 DBusError err;
919
920 spa_log_debug(backend->log, "transport %p: Acquire %s",
921 transport, transport->path);
922
923 if (backend->acquire_in_progress)
924 return -EINPROGRESS;
925
926 if (transport->codec == HFP_AUDIO_CODEC_MSBC) {
927 air_codec = HSPHFP_AIR_CODEC_MSBC;
928 agent_codec = HSPHFP_AGENT_CODEC_MSBC;
929 }
930
931 m = dbus_message_new_method_call(HSPHFPD_SERVICE,
932 transport->path,
933 HSPHFPD_ENDPOINT_INTERFACE,
934 "ConnectAudio");
935 if (m == NULL)
936 return -ENOMEM;
937 dbus_message_append_args(m, DBUS_TYPE_STRING, &air_codec, DBUS_TYPE_STRING, &agent_codec, DBUS_TYPE_INVALID);
938
939 dbus_error_init(&err);
940
941 dbus_connection_send_with_reply(backend->conn, m, &call, -1);
942 dbus_pending_call_set_notify(call, hsphfpd_audio_acquire_reply, backend, NULL);
943 dbus_message_unref(m);
944
945 /* The ConnectAudio method triggers Introspect and NewConnection calls,
946 which will set the fd to use for the SCO data.
947 We need to run the DBus loop to be able to reply to those method calls */
948 backend->acquire_in_progress = true;
949 while (backend->acquire_in_progress && dbus_connection_read_write_dispatch(backend->conn, -1))
950 ; // empty loop body
951
952 return 0;
953 }
954
hsphfpd_audio_release(void * data)955 static int hsphfpd_audio_release(void *data)
956 {
957 struct spa_bt_transport *transport = data;
958 struct impl *backend = SPA_CONTAINER_OF(transport->backend, struct impl, this);
959 struct hsphfpd_transport_data *transport_data = transport->user_data;
960
961 spa_log_debug(backend->log, "transport %p: Release %s",
962 transport, transport->path);
963
964 if (transport->sco_io) {
965 spa_bt_sco_io_destroy(transport->sco_io);
966 transport->sco_io = NULL;
967 }
968
969 /* shutdown to make sure connection is dropped immediately */
970 shutdown(transport->fd, SHUT_RDWR);
971 close(transport->fd);
972 if (transport_data->transport_path) {
973 free(transport_data->transport_path);
974 transport_data->transport_path = NULL;
975 }
976 transport->fd = -1;
977
978 return 0;
979 }
980
hsphfpd_audio_destroy(void * data)981 static int hsphfpd_audio_destroy(void *data)
982 {
983 struct spa_bt_transport *transport = data;
984 struct hsphfpd_transport_data *transport_data = transport->user_data;
985
986 if (transport_data->transport_path) {
987 free(transport_data->transport_path);
988 transport_data->transport_path = NULL;
989 }
990
991 return 0;
992 }
993
994 static const struct spa_bt_transport_implementation hsphfpd_transport_impl = {
995 SPA_VERSION_BT_TRANSPORT_IMPLEMENTATION,
996 .acquire = hsphfpd_audio_acquire,
997 .release = hsphfpd_audio_release,
998 .destroy = hsphfpd_audio_destroy,
999 };
1000
hsphfpd_parse_endpoint_properties(struct impl * backend,struct hsphfpd_endpoint * endpoint,DBusMessageIter * i)1001 static DBusHandlerResult hsphfpd_parse_endpoint_properties(struct impl *backend, struct hsphfpd_endpoint *endpoint, DBusMessageIter *i)
1002 {
1003 DBusMessageIter element_i;
1004 struct spa_bt_device *d;
1005 struct spa_bt_transport *t;
1006
1007 dbus_message_iter_recurse(i, &element_i);
1008 while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
1009 DBusMessageIter dict_i, value_i;
1010 const char *key;
1011
1012 dbus_message_iter_recurse(&element_i, &dict_i);
1013 dbus_message_iter_get_basic(&dict_i, &key);
1014 dbus_message_iter_next(&dict_i);
1015 dbus_message_iter_recurse(&dict_i, &value_i);
1016 switch (dbus_message_iter_get_arg_type(&value_i)) {
1017 case DBUS_TYPE_STRING:
1018 {
1019 const char *value;
1020 dbus_message_iter_get_basic(&value_i, &value);
1021 if (spa_streq(key, "RemoteAddress"))
1022 endpoint->remote_address = strdup(value);
1023 else if (spa_streq(key, "LocalAddress"))
1024 endpoint->local_address = strdup(value);
1025 else if (spa_streq(key, "Profile")) {
1026 if (endpoint->profile)
1027 spa_log_warn(backend->log, "Endpoint %s received a duplicate '%s' property, ignoring", endpoint->path, key);
1028 else if (spa_streq(value, "headset"))
1029 endpoint->profile = HSPHFPD_PROFILE_HEADSET;
1030 else if (spa_streq(value, "handsfree"))
1031 endpoint->profile = HSPHFPD_PROFILE_HANDSFREE;
1032 else
1033 spa_log_warn(backend->log, "Endpoint %s received invalid '%s' property value '%s', ignoring", endpoint->path, key, value);
1034 } else if (spa_streq(key, "Role")) {
1035 if (endpoint->role)
1036 spa_log_warn(backend->log, "Endpoint %s received a duplicate '%s' property, ignoring", endpoint->path, key);
1037 else if (spa_streq(value, "client"))
1038 endpoint->role = HSPHFPD_ROLE_CLIENT;
1039 else if (spa_streq(value, "gateway"))
1040 endpoint->role = HSPHFPD_ROLE_GATEWAY;
1041 else
1042 spa_log_warn(backend->log, "Endpoint %s received invalid '%s' property value '%s', ignoring", endpoint->path, key, value);
1043 }
1044 spa_log_trace(backend->log, " %s: %s (%p)", key, value, endpoint);
1045 }
1046 break;
1047
1048 case DBUS_TYPE_BOOLEAN:
1049 {
1050 bool value;
1051 dbus_message_iter_get_basic(&value_i, &value);
1052 if (spa_streq(key, "Connected"))
1053 endpoint->connected = value;
1054 spa_log_trace(backend->log, " %s: %d", key, value);
1055 }
1056 break;
1057
1058 case DBUS_TYPE_ARRAY:
1059 {
1060 if (spa_streq(key, "AudioCodecs")) {
1061 DBusMessageIter array_i;
1062 const char *value;
1063
1064 endpoint->air_codecs = 0;
1065 dbus_message_iter_recurse(&value_i, &array_i);
1066 while (dbus_message_iter_get_arg_type(&array_i) != DBUS_TYPE_INVALID) {
1067 dbus_message_iter_get_basic(&array_i, &value);
1068 if (spa_streq(value, HSPHFP_AIR_CODEC_CVSD))
1069 endpoint->air_codecs |= HFP_AUDIO_CODEC_CVSD;
1070 if (spa_streq(value, HSPHFP_AIR_CODEC_MSBC))
1071 endpoint->air_codecs |= HFP_AUDIO_CODEC_MSBC;
1072 dbus_message_iter_next(&array_i);
1073 }
1074 }
1075 }
1076 break;
1077 }
1078
1079 dbus_message_iter_next(&element_i);
1080 }
1081
1082 if (!endpoint->valid && endpoint->local_address && endpoint->remote_address && endpoint->profile && endpoint->role)
1083 endpoint->valid = true;
1084
1085 if (!endpoint->remote_address || !endpoint->local_address) {
1086 spa_log_debug(backend->log, "Missing addresses for %s", endpoint->path);
1087 return DBUS_HANDLER_RESULT_HANDLED;
1088 }
1089
1090 d = spa_bt_device_find_by_address(backend->monitor, endpoint->remote_address, endpoint->local_address);
1091 if (!d) {
1092 spa_log_debug(backend->log, "No device for %s", endpoint->path);
1093 return DBUS_HANDLER_RESULT_HANDLED;
1094 }
1095
1096 if ((t = spa_bt_transport_find(backend->monitor, endpoint->path)) != NULL) {
1097 /* Release transport on disconnection, or when mSBC is supported if there
1098 is an update of the remote codecs */
1099 if (!endpoint->connected || (backend->msbc_supported && (endpoint->air_codecs & HFP_AUDIO_CODEC_MSBC) && t->codec == HFP_AUDIO_CODEC_CVSD)) {
1100 spa_bt_transport_free(t);
1101 spa_bt_device_check_profiles(d, false);
1102 spa_log_debug(backend->log, "Transport released for %s", endpoint->path);
1103 } else {
1104 spa_log_debug(backend->log, "Transport already configured for %s", endpoint->path);
1105 return DBUS_HANDLER_RESULT_HANDLED;
1106 }
1107 }
1108
1109 if (!endpoint->valid || !endpoint->connected)
1110 return DBUS_HANDLER_RESULT_HANDLED;
1111
1112 char *t_path = strdup(endpoint->path);
1113 t = spa_bt_transport_create(backend->monitor, t_path, sizeof(struct hsphfpd_transport_data));
1114 if (t == NULL) {
1115 spa_log_warn(backend->log, "can't create transport: %m");
1116 free(t_path);
1117 return DBUS_HANDLER_RESULT_NEED_MEMORY;
1118 }
1119 spa_bt_transport_set_implementation(t, &hsphfpd_transport_impl, t);
1120
1121 t->device = d;
1122 spa_list_append(&t->device->transport_list, &t->device_link);
1123 t->backend = &backend->this;
1124 t->profile = SPA_BT_PROFILE_NULL;
1125 if (endpoint->profile == HSPHFPD_PROFILE_HEADSET) {
1126 if (endpoint->role == HSPHFPD_ROLE_CLIENT)
1127 t->profile = SPA_BT_PROFILE_HSP_HS;
1128 else if (endpoint->role == HSPHFPD_ROLE_GATEWAY)
1129 t->profile = SPA_BT_PROFILE_HSP_AG;
1130 } else if (endpoint->profile == HSPHFPD_PROFILE_HANDSFREE) {
1131 if (endpoint->role == HSPHFPD_ROLE_CLIENT)
1132 t->profile = SPA_BT_PROFILE_HFP_HF;
1133 else if (endpoint->role == HSPHFPD_ROLE_GATEWAY)
1134 t->profile = SPA_BT_PROFILE_HFP_AG;
1135 }
1136 if (backend->msbc_supported && (endpoint->air_codecs & HFP_AUDIO_CODEC_MSBC))
1137 t->codec = HFP_AUDIO_CODEC_MSBC;
1138 else
1139 t->codec = HFP_AUDIO_CODEC_CVSD;
1140
1141 t->n_channels = 1;
1142 t->channels[0] = SPA_AUDIO_CHANNEL_MONO;
1143
1144 spa_bt_device_add_profile(d, t->profile);
1145 spa_bt_device_connect_profile(t->device, t->profile);
1146
1147 spa_log_debug(backend->log, "Transport %s available for hsphfpd", endpoint->path);
1148
1149 return DBUS_HANDLER_RESULT_HANDLED;
1150 }
1151
hsphfpd_parse_interfaces(struct impl * backend,DBusMessageIter * dict_i)1152 static DBusHandlerResult hsphfpd_parse_interfaces(struct impl *backend, DBusMessageIter *dict_i)
1153 {
1154 DBusMessageIter element_i;
1155 const char *path;
1156
1157 spa_assert(backend);
1158 spa_assert(dict_i);
1159
1160 dbus_message_iter_get_basic(dict_i, &path);
1161 dbus_message_iter_next(dict_i);
1162 dbus_message_iter_recurse(dict_i, &element_i);
1163
1164 while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
1165 DBusMessageIter iface_i;
1166 const char *interface;
1167
1168 dbus_message_iter_recurse(&element_i, &iface_i);
1169 dbus_message_iter_get_basic(&iface_i, &interface);
1170 dbus_message_iter_next(&iface_i);
1171
1172 if (spa_streq(interface, HSPHFPD_ENDPOINT_INTERFACE)) {
1173 struct hsphfpd_endpoint *endpoint;
1174
1175 endpoint = endpoint_find(backend, path);
1176 if (!endpoint) {
1177 endpoint = calloc(1, sizeof(struct hsphfpd_endpoint));
1178 endpoint->path = strdup(path);
1179 spa_list_append(&backend->endpoint_list, &endpoint->link);
1180 spa_log_debug(backend->log, "Found endpoint %s", path);
1181 }
1182 hsphfpd_parse_endpoint_properties(backend, endpoint, &iface_i);
1183 } else
1184 spa_log_debug(backend->log, "Unknown interface %s found, skipping", interface);
1185
1186 dbus_message_iter_next(&element_i);
1187 }
1188
1189 return DBUS_HANDLER_RESULT_HANDLED;
1190 }
1191
hsphfpd_get_endpoints_reply(DBusPendingCall * pending,void * user_data)1192 static void hsphfpd_get_endpoints_reply(DBusPendingCall *pending, void *user_data)
1193 {
1194 struct impl *backend = user_data;
1195 DBusMessage *r;
1196 DBusMessageIter i, array_i;
1197
1198 r = dbus_pending_call_steal_reply(pending);
1199 if (r == NULL)
1200 return;
1201
1202 if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
1203 spa_log_error(backend->log, "Failed to get a list of endpoints from hsphfpd: %s",
1204 dbus_message_get_error_name(r));
1205 goto finish;
1206 }
1207
1208 if (!spa_streq(dbus_message_get_sender(r), backend->hsphfpd_service_id)) {
1209 spa_log_error(backend->log, "Reply for GetManagedObjects() from invalid sender");
1210 goto finish;
1211 }
1212
1213 if (!dbus_message_iter_init(r, &i) || !check_signature(r, "a{oa{sa{sv}}}")) {
1214 spa_log_error(backend->log, "Invalid arguments in GetManagedObjects() reply");
1215 goto finish;
1216 }
1217
1218 dbus_message_iter_recurse(&i, &array_i);
1219 while (dbus_message_iter_get_arg_type(&array_i) != DBUS_TYPE_INVALID) {
1220 DBusMessageIter dict_i;
1221
1222 dbus_message_iter_recurse(&array_i, &dict_i);
1223 hsphfpd_parse_interfaces(backend, &dict_i);
1224 dbus_message_iter_next(&array_i);
1225 }
1226
1227 backend->endpoints_listed = true;
1228
1229 finish:
1230 dbus_message_unref(r);
1231 dbus_pending_call_unref(pending);
1232 }
1233
backend_hsphfpd_register(void * data)1234 static int backend_hsphfpd_register(void *data)
1235 {
1236 struct impl *backend = data;
1237 DBusMessage *m, *r;
1238 const char *path = APPLICATION_OBJECT_MANAGER_PATH;
1239 DBusPendingCall *call;
1240 DBusError err;
1241 int res;
1242
1243 spa_log_debug(backend->log, "Registering to hsphfpd");
1244
1245 m = dbus_message_new_method_call(HSPHFPD_SERVICE, "/",
1246 HSPHFPD_APPLICATION_MANAGER_INTERFACE, "RegisterApplication");
1247 if (m == NULL)
1248 return -ENOMEM;
1249
1250 dbus_message_append_args(m, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID);
1251
1252 dbus_error_init(&err);
1253
1254 r = dbus_connection_send_with_reply_and_block(backend->conn, m, -1, &err);
1255 dbus_message_unref(m);
1256
1257 if (r == NULL) {
1258 if (dbus_error_has_name(&err, "org.freedesktop.DBus.Error.ServiceUnknown")) {
1259 spa_log_info(backend->log, "hsphfpd not available: %s",
1260 err.message);
1261 res = -ENOTSUP;
1262 } else {
1263 spa_log_warn(backend->log, "Registering application %s failed: %s (%s)",
1264 path, err.message, err.name);
1265 res = -EIO;
1266 }
1267 dbus_error_free(&err);
1268 return res;
1269 }
1270
1271 if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
1272 spa_log_error(backend->log, "RegisterApplication() failed: %s",
1273 dbus_message_get_error_name(r));
1274 goto finish;
1275 }
1276 dbus_message_unref(r);
1277
1278 backend->hsphfpd_service_id = strdup(dbus_message_get_sender(r));
1279
1280 spa_log_debug(backend->log, "Registered to hsphfpd");
1281
1282 m = dbus_message_new_method_call(HSPHFPD_SERVICE, "/",
1283 DBUS_INTERFACE_OBJECTMANAGER, "GetManagedObjects");
1284 if (m == NULL)
1285 goto finish;
1286
1287 dbus_connection_send_with_reply(backend->conn, m, &call, -1);
1288 dbus_pending_call_set_notify(call, hsphfpd_get_endpoints_reply, backend, NULL);
1289 dbus_message_unref(m);
1290
1291 return 0;
1292
1293 finish:
1294 dbus_message_unref(r);
1295 return -EIO;
1296 }
1297
backend_hsphfpd_unregistered(void * data)1298 static int backend_hsphfpd_unregistered(void *data)
1299 {
1300 struct impl *backend = data;
1301 struct hsphfpd_endpoint *endpoint;
1302
1303 if (backend->hsphfpd_service_id) {
1304 free(backend->hsphfpd_service_id);
1305 backend->hsphfpd_service_id = NULL;
1306 }
1307 backend->endpoints_listed = false;
1308 spa_list_consume(endpoint, &backend->endpoint_list, link)
1309 endpoint_free(endpoint);
1310
1311 return 0;
1312 }
1313
hsphfpd_filter_cb(DBusConnection * bus,DBusMessage * m,void * user_data)1314 static DBusHandlerResult hsphfpd_filter_cb(DBusConnection *bus, DBusMessage *m, void *user_data)
1315 {
1316 const char *sender;
1317 struct impl *backend = user_data;
1318 DBusError err;
1319
1320 dbus_error_init(&err);
1321
1322 sender = dbus_message_get_sender(m);
1323
1324 if (backend->hsphfpd_service_id && spa_streq(sender, backend->hsphfpd_service_id)) {
1325 if (dbus_message_is_signal(m, DBUS_INTERFACE_OBJECTMANAGER, "InterfacesAdded")) {
1326 DBusMessageIter arg_i;
1327
1328 spa_log_warn(backend->log, "sender: %s", dbus_message_get_sender(m));
1329
1330 if (!backend->endpoints_listed)
1331 goto finish;
1332
1333 if (!dbus_message_iter_init(m, &arg_i) || !check_signature(m, "oa{sa{sv}}")) {
1334 spa_log_error(backend->log, "Invalid signature found in InterfacesAdded");
1335 goto finish;
1336 }
1337
1338 hsphfpd_parse_interfaces(backend, &arg_i);
1339 } else if (dbus_message_is_signal(m, DBUS_INTERFACE_OBJECTMANAGER, "InterfacesRemoved")) {
1340 const char *path;
1341 DBusMessageIter arg_i, element_i;
1342
1343 if (!backend->endpoints_listed)
1344 goto finish;
1345
1346 if (!dbus_message_iter_init(m, &arg_i) || !check_signature(m, "oas")) {
1347 spa_log_error(backend->log, "Invalid signature found in InterfacesRemoved");
1348 goto finish;
1349 }
1350
1351 dbus_message_iter_get_basic(&arg_i, &path);
1352 dbus_message_iter_next(&arg_i);
1353 dbus_message_iter_recurse(&arg_i, &element_i);
1354
1355 while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_STRING) {
1356 const char *iface;
1357
1358 dbus_message_iter_get_basic(&element_i, &iface);
1359
1360 if (spa_streq(iface, HSPHFPD_ENDPOINT_INTERFACE)) {
1361 struct hsphfpd_endpoint *endpoint;
1362 struct spa_bt_transport *transport = spa_bt_transport_find(backend->monitor, path);
1363
1364 if (transport)
1365 spa_bt_transport_free(transport);
1366
1367 spa_log_debug(backend->log, "Remove endpoint %s", path);
1368 endpoint = endpoint_find(backend, path);
1369 if (endpoint)
1370 endpoint_free(endpoint);
1371 }
1372
1373 dbus_message_iter_next(&element_i);
1374 }
1375 } else if (dbus_message_is_signal(m, DBUS_INTERFACE_PROPERTIES, "PropertiesChanged")) {
1376 DBusMessageIter arg_i;
1377 const char *iface;
1378 const char *path;
1379
1380 if (!backend->endpoints_listed)
1381 goto finish;
1382
1383 if (!dbus_message_iter_init(m, &arg_i) || !check_signature(m, "sa{sv}as")) {
1384 spa_log_error(backend->log, "Invalid signature found in PropertiesChanged");
1385 goto finish;
1386 }
1387
1388 dbus_message_iter_get_basic(&arg_i, &iface);
1389 dbus_message_iter_next(&arg_i);
1390
1391 path = dbus_message_get_path(m);
1392
1393 if (spa_streq(iface, HSPHFPD_ENDPOINT_INTERFACE)) {
1394 struct hsphfpd_endpoint *endpoint = endpoint_find(backend, path);
1395 if (!endpoint) {
1396 spa_log_warn(backend->log, "Properties changed on unknown endpoint %s", path);
1397 goto finish;
1398 }
1399 spa_log_debug(backend->log, "Properties changed on endpoint %s", path);
1400 hsphfpd_parse_endpoint_properties(backend, endpoint, &arg_i);
1401 } else if (spa_streq(iface, HSPHFPD_AUDIO_TRANSPORT_INTERFACE)) {
1402 struct spa_bt_transport *transport = spa_bt_transport_find_full(backend->monitor,
1403 hsphfpd_cmp_transport_path,
1404 (const void *)path);
1405 if (!transport) {
1406 spa_log_warn(backend->log, "Properties changed on unknown transport %s", path);
1407 goto finish;
1408 }
1409 spa_log_debug(backend->log, "Properties changed on transport %s", path);
1410 hsphfpd_parse_transport_properties(backend, transport, &arg_i);
1411 }
1412 }
1413 }
1414
1415 finish:
1416 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1417 }
1418
add_filters(void * data)1419 static int add_filters(void *data)
1420 {
1421 struct impl *backend = data;
1422 DBusError err;
1423
1424 if (backend->filters_added)
1425 return 0;
1426
1427 dbus_error_init(&err);
1428
1429 if (!dbus_connection_add_filter(backend->conn, hsphfpd_filter_cb, backend, NULL)) {
1430 spa_log_error(backend->log, "failed to add filter function");
1431 goto fail;
1432 }
1433
1434 dbus_bus_add_match(backend->conn,
1435 "type='signal',sender='" HSPHFPD_SERVICE "',"
1436 "interface='" DBUS_INTERFACE_OBJECTMANAGER "',member='InterfacesAdded'", &err);
1437 dbus_bus_add_match(backend->conn,
1438 "type='signal',sender='" HSPHFPD_SERVICE "',"
1439 "interface='" DBUS_INTERFACE_OBJECTMANAGER "',member='InterfacesRemoved'", &err);
1440 dbus_bus_add_match(backend->conn,
1441 "type='signal',sender='" HSPHFPD_SERVICE "',"
1442 "interface='" DBUS_INTERFACE_PROPERTIES "',member='PropertiesChanged',"
1443 "arg0='" HSPHFPD_ENDPOINT_INTERFACE "'", &err);
1444 dbus_bus_add_match(backend->conn,
1445 "type='signal',sender='" HSPHFPD_SERVICE "',"
1446 "interface='" DBUS_INTERFACE_PROPERTIES "',member='PropertiesChanged',"
1447 "arg0='" HSPHFPD_AUDIO_TRANSPORT_INTERFACE "'", &err);
1448
1449 backend->filters_added = true;
1450
1451 return 0;
1452
1453 fail:
1454 dbus_error_free(&err);
1455 return -EIO;
1456 }
1457
backend_hsphfpd_free(void * data)1458 static int backend_hsphfpd_free(void *data)
1459 {
1460 struct impl *backend = data;
1461 struct hsphfpd_endpoint *endpoint;
1462
1463 if (backend->filters_added) {
1464 dbus_connection_remove_filter(backend->conn, hsphfpd_filter_cb, backend);
1465 backend->filters_added = false;
1466 }
1467
1468 if (backend->msbc_supported)
1469 dbus_connection_unregister_object_path(backend->conn, HSPHFP_AUDIO_CLIENT_MSBC);
1470 dbus_connection_unregister_object_path(backend->conn, HSPHFP_AUDIO_CLIENT_PCM_S16LE_8KHZ);
1471 dbus_connection_unregister_object_path(backend->conn, APPLICATION_OBJECT_MANAGER_PATH);
1472
1473 spa_list_consume(endpoint, &backend->endpoint_list, link)
1474 endpoint_free(endpoint);
1475
1476 free(backend);
1477
1478 return 0;
1479 }
1480
1481 static const struct spa_bt_backend_implementation backend_impl = {
1482 SPA_VERSION_BT_BACKEND_IMPLEMENTATION,
1483 .free = backend_hsphfpd_free,
1484 .register_profiles = backend_hsphfpd_register,
1485 .unregister_profiles = backend_hsphfpd_unregistered,
1486 };
1487
is_available(struct impl * backend)1488 static bool is_available(struct impl *backend)
1489 {
1490 DBusMessage *m, *r;
1491 DBusError err;
1492 bool success = false;
1493
1494 m = dbus_message_new_method_call(HSPHFPD_SERVICE, "/",
1495 DBUS_INTERFACE_INTROSPECTABLE, "Introspect");
1496 if (m == NULL)
1497 return false;
1498
1499 dbus_error_init(&err);
1500 r = dbus_connection_send_with_reply_and_block(backend->conn, m, -1, &err);
1501 dbus_message_unref(m);
1502
1503 if (r && dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_METHOD_RETURN)
1504 success = true;
1505
1506 if (r)
1507 dbus_message_unref(r);
1508 else
1509 dbus_error_free(&err);
1510
1511 return success;
1512 }
1513
backend_hsphfpd_new(struct spa_bt_monitor * monitor,void * dbus_connection,const struct spa_dict * info,const struct spa_bt_quirks * quirks,const struct spa_support * support,uint32_t n_support)1514 struct spa_bt_backend *backend_hsphfpd_new(struct spa_bt_monitor *monitor,
1515 void *dbus_connection,
1516 const struct spa_dict *info,
1517 const struct spa_bt_quirks *quirks,
1518 const struct spa_support *support,
1519 uint32_t n_support)
1520 {
1521 struct impl *backend;
1522 const char *str;
1523 static const DBusObjectPathVTable vtable_application_object_manager = {
1524 .message_function = application_object_manager_handler,
1525 };
1526 static const DBusObjectPathVTable vtable_audio_agent_endpoint = {
1527 .message_function = audio_agent_endpoint_handler,
1528 };
1529
1530 backend = calloc(1, sizeof(struct impl));
1531 if (backend == NULL)
1532 return NULL;
1533
1534 spa_bt_backend_set_implementation(&backend->this, &backend_impl, backend);
1535
1536 backend->this.name = "hsphfpd";
1537 backend->this.exclusive = true;
1538 backend->monitor = monitor;
1539 backend->quirks = quirks;
1540 backend->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log);
1541 backend->dbus = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DBus);
1542 backend->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop);
1543 backend->conn = dbus_connection;
1544 if (info && (str = spa_dict_lookup(info, "bluez5.enable-msbc")))
1545 backend->msbc_supported = spa_atob(str);
1546 else
1547 backend->msbc_supported = false;
1548
1549 spa_log_topic_init(backend->log, &log_topic);
1550
1551 spa_list_init(&backend->endpoint_list);
1552
1553 if (!dbus_connection_register_object_path(backend->conn,
1554 APPLICATION_OBJECT_MANAGER_PATH,
1555 &vtable_application_object_manager, backend)) {
1556 free(backend);
1557 return NULL;
1558 }
1559
1560 if (!dbus_connection_register_object_path(backend->conn,
1561 HSPHFP_AUDIO_CLIENT_PCM_S16LE_8KHZ,
1562 &vtable_audio_agent_endpoint, backend)) {
1563 dbus_connection_unregister_object_path(backend->conn, APPLICATION_OBJECT_MANAGER_PATH);
1564 free(backend);
1565 return NULL;
1566 }
1567
1568 if (backend->msbc_supported && !dbus_connection_register_object_path(backend->conn,
1569 HSPHFP_AUDIO_CLIENT_MSBC,
1570 &vtable_audio_agent_endpoint, backend)) {
1571 dbus_connection_unregister_object_path(backend->conn, HSPHFP_AUDIO_CLIENT_PCM_S16LE_8KHZ);
1572 dbus_connection_unregister_object_path(backend->conn, APPLICATION_OBJECT_MANAGER_PATH);
1573 free(backend);
1574 return NULL;
1575 }
1576
1577 if (add_filters(backend) < 0) {
1578 dbus_connection_unregister_object_path(backend->conn, HSPHFP_AUDIO_CLIENT_MSBC);
1579 dbus_connection_unregister_object_path(backend->conn, HSPHFP_AUDIO_CLIENT_PCM_S16LE_8KHZ);
1580 dbus_connection_unregister_object_path(backend->conn, APPLICATION_OBJECT_MANAGER_PATH);
1581 free(backend);
1582 return NULL;
1583 }
1584
1585 backend->this.available = is_available(backend);
1586
1587 return &backend->this;
1588 }
1589