/* Copyright (c) 2013 The Chromium Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include <dbus/dbus.h> #include <errno.h> #include <stdlib.h> #include <string.h> #include <syslog.h> #include "cras_bt_constants.h" #include "cras_bt_adapter.h" #include "cras_bt_endpoint.h" #include "cras_bt_transport.h" #include "utlist.h" /* Defined by doc/media-api.txt in the BlueZ source */ #define ENDPOINT_INTROSPECT_XML \ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ "<node>\n" \ " <interface name=\"org.bluez.MediaEndpoint\">\n" \ " <method name=\"SetConfiguration\">\n" \ " <arg name=\"transport\" type=\"o\" direction=\"in\"/>\n" \ " <arg name=\"configuration\" type=\"a{sv}\" direction=\"in\"/>\n"\ " </method>\n" \ " <method name=\"SelectConfiguration\">\n" \ " <arg name=\"capabilities\" type=\"ay\" direction=\"in\"/>\n"\ " <arg name=\"configuration\" type=\"ay\" direction=\"out\"/>\n"\ " </method>\n" \ " <method name=\"ClearConfiguration\">\n" \ " </method>\n" \ " <method name=\"Release\">\n" \ " </method>\n" \ " </interface>\n" \ " <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">\n" \ " <method name=\"Introspect\">\n" \ " <arg name=\"data\" type=\"s\" direction=\"out\"/>\n" \ " </method>\n" \ " </interface>\n" \ "</node>\n" static void cras_bt_endpoint_suspend(struct cras_bt_endpoint *endpoint) { if (!endpoint->transport) return; endpoint->suspend(endpoint, endpoint->transport); cras_bt_transport_set_endpoint(endpoint->transport, NULL); endpoint->transport = NULL; } static DBusHandlerResult cras_bt_endpoint_set_configuration( DBusConnection *conn, DBusMessage *message, void *arg) { DBusMessageIter message_iter, properties_array_iter; const char *endpoint_path, *transport_path; struct cras_bt_endpoint *endpoint; struct cras_bt_transport *transport; DBusMessage *reply; syslog(LOG_DEBUG, "SetConfiguration: %s", dbus_message_get_path(message)); endpoint_path = dbus_message_get_path(message); endpoint = cras_bt_endpoint_get(endpoint_path); if (!endpoint) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; if (!dbus_message_has_signature(message, "oa{sv}")) { syslog(LOG_WARNING, "Bad SetConfiguration message received."); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } dbus_message_iter_init(message, &message_iter); dbus_message_iter_get_basic(&message_iter, &transport_path); dbus_message_iter_next(&message_iter); dbus_message_iter_recurse(&message_iter, &properties_array_iter); transport = cras_bt_transport_get(transport_path); if (transport) { cras_bt_transport_update_properties(transport, &properties_array_iter, NULL); } else { transport = cras_bt_transport_create(conn, transport_path); if (transport) { cras_bt_transport_update_properties( transport, &properties_array_iter, NULL); syslog(LOG_INFO, "Bluetooth Transport: %s added", cras_bt_transport_object_path(transport)); } } if (!cras_bt_transport_device(transport)) { syslog(LOG_ERR, "Do device found for transport %s", cras_bt_transport_object_path(transport)); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } cras_bt_transport_set_endpoint(transport, endpoint); endpoint->transport = transport; endpoint->set_configuration(endpoint, transport); reply = dbus_message_new_method_return(message); if (!reply) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_connection_send(conn, reply, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; dbus_message_unref(reply); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult cras_bt_endpoint_select_configuration( DBusConnection *conn, DBusMessage *message, void *arg) { DBusError dbus_error; const char *endpoint_path; struct cras_bt_endpoint *endpoint; char buf[4]; void *capabilities, *configuration = buf; int len; DBusMessage *reply; syslog(LOG_DEBUG, "SelectConfiguration: %s", dbus_message_get_path(message)); endpoint_path = dbus_message_get_path(message); endpoint = cras_bt_endpoint_get(endpoint_path); if (!endpoint) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; dbus_error_init(&dbus_error); if (!dbus_message_get_args(message, &dbus_error, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &capabilities, &len, DBUS_TYPE_INVALID)) { syslog(LOG_WARNING, "Bad SelectConfiguration method call: %s", dbus_error.message); dbus_error_free(&dbus_error); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } if (len > sizeof(configuration) || endpoint->select_configuration(endpoint, capabilities, len, configuration) < 0) { reply = dbus_message_new_error( message, "org.chromium.Cras.Error.UnsupportedConfiguration", "Unable to select configuration from capabilities"); if (!dbus_connection_send(conn, reply, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; dbus_message_unref(reply); return DBUS_HANDLER_RESULT_HANDLED; } reply = dbus_message_new_method_return(message); if (!reply) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_message_append_args(reply, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &configuration, len, DBUS_TYPE_INVALID)) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_connection_send(conn, reply, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; dbus_message_unref(reply); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult cras_bt_endpoint_clear_configuration( DBusConnection *conn, DBusMessage *message, void *arg) { DBusError dbus_error; const char *endpoint_path, *transport_path; struct cras_bt_endpoint *endpoint; struct cras_bt_transport *transport; DBusMessage *reply; syslog(LOG_DEBUG, "ClearConfiguration: %s", dbus_message_get_path(message)); endpoint_path = dbus_message_get_path(message); endpoint = cras_bt_endpoint_get(endpoint_path); if (!endpoint) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; dbus_error_init(&dbus_error); if (!dbus_message_get_args(message, &dbus_error, DBUS_TYPE_OBJECT_PATH, &transport_path, DBUS_TYPE_INVALID)) { syslog(LOG_WARNING, "Bad ClearConfiguration method call: %s", dbus_error.message); dbus_error_free(&dbus_error); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } transport = cras_bt_transport_get(transport_path); if (transport == endpoint->transport) cras_bt_endpoint_suspend(endpoint); reply = dbus_message_new_method_return(message); if (!reply) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_connection_send(conn, reply, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; dbus_message_unref(reply); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult cras_bt_endpoint_release(DBusConnection *conn, DBusMessage *message, void *arg) { const char *endpoint_path; struct cras_bt_endpoint *endpoint; DBusMessage *reply; syslog(LOG_DEBUG, "Release: %s", dbus_message_get_path(message)); endpoint_path = dbus_message_get_path(message); endpoint = cras_bt_endpoint_get(endpoint_path); if (!endpoint) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; cras_bt_endpoint_suspend(endpoint); reply = dbus_message_new_method_return(message); if (!reply) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_connection_send(conn, reply, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; dbus_message_unref(reply); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult cras_bt_handle_endpoint_message(DBusConnection *conn, DBusMessage *message, void *arg) { syslog(LOG_DEBUG, "Endpoint message: %s %s %s", dbus_message_get_path(message), dbus_message_get_interface(message), dbus_message_get_member(message)); if (dbus_message_is_method_call(message, DBUS_INTERFACE_INTROSPECTABLE, "Introspect")) { DBusMessage *reply; const char *xml = ENDPOINT_INTROSPECT_XML; reply = dbus_message_new_method_return(message); if (!reply) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_connection_send(conn, reply, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; dbus_message_unref(reply); return DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_method_call(message, BLUEZ_INTERFACE_MEDIA_ENDPOINT, "SetConfiguration")) { return cras_bt_endpoint_set_configuration(conn, message, arg); } else if (dbus_message_is_method_call(message, BLUEZ_INTERFACE_MEDIA_ENDPOINT, "SelectConfiguration")) { return cras_bt_endpoint_select_configuration( conn, message, arg); } else if (dbus_message_is_method_call(message, BLUEZ_INTERFACE_MEDIA_ENDPOINT, "ClearConfiguration")) { return cras_bt_endpoint_clear_configuration(conn, message, arg); } else if (dbus_message_is_method_call(message, BLUEZ_INTERFACE_MEDIA_ENDPOINT, "Release")) { return cras_bt_endpoint_release(conn, message, arg); } else { syslog(LOG_DEBUG, "%s: %s.%s: Unknown MediaEndpoint message", dbus_message_get_path(message), dbus_message_get_interface(message), dbus_message_get_member(message)); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } } static void cras_bt_on_register_endpoint(DBusPendingCall *pending_call, void *data) { DBusMessage *reply; reply = dbus_pending_call_steal_reply(pending_call); dbus_pending_call_unref(pending_call); if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) { syslog(LOG_WARNING, "RegisterEndpoint returned error: %s", dbus_message_get_error_name(reply)); dbus_message_unref(reply); return; } dbus_message_unref(reply); } int cras_bt_register_endpoint(DBusConnection *conn, const struct cras_bt_adapter *adapter, struct cras_bt_endpoint *endpoint) { const char *adapter_path, *key; DBusMessage *method_call; DBusMessageIter message_iter; DBusMessageIter properties_array_iter, properties_dict_iter; DBusMessageIter variant_iter, bytes_iter; DBusPendingCall *pending_call; char buf[4]; void *capabilities = buf; int len = sizeof(buf); int error; error = endpoint->get_capabilities(endpoint, capabilities, &len); if (error < 0) return error; adapter_path = cras_bt_adapter_object_path(adapter); method_call = dbus_message_new_method_call(BLUEZ_SERVICE, adapter_path, BLUEZ_INTERFACE_MEDIA, "RegisterEndpoint"); if (!method_call) return -ENOMEM; dbus_message_iter_init_append(method_call, &message_iter); dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_OBJECT_PATH, &endpoint->object_path); dbus_message_iter_open_container(&message_iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &properties_array_iter); key = "UUID"; dbus_message_iter_open_container(&properties_array_iter, DBUS_TYPE_DICT_ENTRY, NULL, &properties_dict_iter); dbus_message_iter_append_basic(&properties_dict_iter, DBUS_TYPE_STRING, &key); dbus_message_iter_open_container(&properties_dict_iter, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &variant_iter); dbus_message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &endpoint->uuid); dbus_message_iter_close_container(&properties_dict_iter, &variant_iter); dbus_message_iter_close_container(&properties_array_iter, &properties_dict_iter); key = "Codec"; dbus_message_iter_open_container(&properties_array_iter, DBUS_TYPE_DICT_ENTRY, NULL, &properties_dict_iter); dbus_message_iter_append_basic(&properties_dict_iter, DBUS_TYPE_STRING, &key); dbus_message_iter_open_container(&properties_dict_iter, DBUS_TYPE_VARIANT, DBUS_TYPE_BYTE_AS_STRING, &variant_iter); dbus_message_iter_append_basic(&variant_iter, DBUS_TYPE_BYTE, &endpoint->codec); dbus_message_iter_close_container(&properties_dict_iter, &variant_iter); dbus_message_iter_close_container(&properties_array_iter, &properties_dict_iter); key = "Capabilities"; dbus_message_iter_open_container(&properties_array_iter, DBUS_TYPE_DICT_ENTRY, NULL, &properties_dict_iter); dbus_message_iter_append_basic(&properties_dict_iter, DBUS_TYPE_STRING, &key); dbus_message_iter_open_container(&properties_dict_iter, DBUS_TYPE_VARIANT, DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_BYTE_AS_STRING, &variant_iter); dbus_message_iter_open_container(&variant_iter, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE_AS_STRING, &bytes_iter); dbus_message_iter_append_fixed_array(&bytes_iter, DBUS_TYPE_BYTE, &capabilities, len); dbus_message_iter_close_container(&variant_iter, &bytes_iter); dbus_message_iter_close_container(&properties_dict_iter, &variant_iter); dbus_message_iter_close_container(&properties_array_iter, &properties_dict_iter); dbus_message_iter_close_container(&message_iter, &properties_array_iter); if (!dbus_connection_send_with_reply(conn, method_call, &pending_call, DBUS_TIMEOUT_USE_DEFAULT)) { dbus_message_unref(method_call); return -ENOMEM; } dbus_message_unref(method_call); if (!pending_call) return -EIO; if (!dbus_pending_call_set_notify(pending_call, cras_bt_on_register_endpoint, NULL, NULL)) { dbus_pending_call_cancel(pending_call); dbus_pending_call_unref(pending_call); return -ENOMEM; } return 0; } static void cras_bt_on_unregister_endpoint(DBusPendingCall *pending_call, void *data) { DBusMessage *reply; reply = dbus_pending_call_steal_reply(pending_call); dbus_pending_call_unref(pending_call); if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) { syslog(LOG_WARNING, "UnregisterEndpoint returned error: %s", dbus_message_get_error_name(reply)); dbus_message_unref(reply); return; } dbus_message_unref(reply); } int cras_bt_unregister_endpoint(DBusConnection *conn, const struct cras_bt_adapter *adapter, struct cras_bt_endpoint *endpoint) { const char *adapter_path; DBusMessage *method_call; DBusPendingCall *pending_call; adapter_path = cras_bt_adapter_object_path(adapter); method_call = dbus_message_new_method_call(BLUEZ_SERVICE, adapter_path, BLUEZ_INTERFACE_MEDIA, "UnregisterEndpoint"); if (!method_call) return -ENOMEM; if (!dbus_message_append_args(method_call, DBUS_TYPE_OBJECT_PATH, &endpoint->object_path, DBUS_TYPE_INVALID)) return -ENOMEM; if (!dbus_connection_send_with_reply(conn, method_call, &pending_call, DBUS_TIMEOUT_USE_DEFAULT)) { dbus_message_unref(method_call); return -ENOMEM; } dbus_message_unref(method_call); if (!pending_call) return -EIO; if (!dbus_pending_call_set_notify(pending_call, cras_bt_on_unregister_endpoint, NULL, NULL)) { dbus_pending_call_cancel(pending_call); dbus_pending_call_unref(pending_call); return -ENOMEM; } return 0; } /* Available endpoints */ static struct cras_bt_endpoint *endpoints; int cras_bt_register_endpoints(DBusConnection *conn, const struct cras_bt_adapter *adapter) { struct cras_bt_endpoint *endpoint; DL_FOREACH(endpoints, endpoint) cras_bt_register_endpoint(conn, adapter, endpoint); return 0; } int cras_bt_endpoint_add(DBusConnection *conn, struct cras_bt_endpoint *endpoint) { static const DBusObjectPathVTable endpoint_vtable = { .message_function = cras_bt_handle_endpoint_message }; DBusError dbus_error; struct cras_bt_adapter **adapters; size_t num_adapters, i; DL_APPEND(endpoints, endpoint); dbus_error_init(&dbus_error); if (!dbus_connection_register_object_path(conn, endpoint->object_path, &endpoint_vtable, &dbus_error)) { syslog(LOG_WARNING, "Couldn't register Bluetooth endpoint: %s: %s", endpoint->object_path, dbus_error.message); dbus_error_free(&dbus_error); return -ENOMEM; } num_adapters = cras_bt_adapter_get_list(&adapters); for (i = 0; i < num_adapters; ++i) cras_bt_register_endpoint(conn, adapters[i], endpoint); free(adapters); return 0; } void cras_bt_endpoint_rm(DBusConnection *conn, struct cras_bt_endpoint *endpoint) { struct cras_bt_adapter **adapters; size_t num_adapters, i; num_adapters = cras_bt_adapter_get_list(&adapters); for (i = 0; i < num_adapters; ++i) cras_bt_unregister_endpoint(conn, adapters[i], endpoint); free(adapters); dbus_connection_unregister_object_path(conn, endpoint->object_path); DL_DELETE(endpoints, endpoint); } void cras_bt_endpoint_reset() { struct cras_bt_endpoint *endpoint; DL_FOREACH(endpoints, endpoint) cras_bt_endpoint_suspend(endpoint); } struct cras_bt_endpoint *cras_bt_endpoint_get(const char *object_path) { struct cras_bt_endpoint *endpoint; DL_FOREACH(endpoints, endpoint) { if (strcmp(endpoint->object_path, object_path) == 0) return endpoint; } return NULL; }