| /* Copyright (c) 2013 The Chromium OS 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) |
| { |
| struct cras_bt_transport *transport; |
| |
| if (!endpoint->transport) |
| return; |
| |
| endpoint->suspend(endpoint, endpoint->transport); |
| |
| transport = endpoint->transport; |
| cras_bt_transport_set_endpoint(transport, NULL); |
| endpoint->transport = NULL; |
| |
| /* |
| * If BT stack has notified this transport interface removed. |
| * Destroy transport now since all related objects has been |
| * cleaned up. |
| */ |
| if (cras_bt_transport_is_removed(transport)) |
| cras_bt_transport_destroy(transport); |
| } |
| |
| 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; |
| } |