/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2007 Nokia Corporation * Copyright (C) 2004-2009 Marcel Holtmann * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "../src/adapter.h" #include "../src/dbus-common.h" #include "log.h" #include "error.h" #include "device.h" #include "avdtp.h" #include "media.h" #include "transport.h" #include "a2dp.h" #include "headset.h" #include "manager.h" #ifndef DBUS_TYPE_UNIX_FD #define DBUS_TYPE_UNIX_FD -1 #endif #define MEDIA_INTERFACE "org.bluez.Media" #define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint" #define REQUEST_TIMEOUT (3 * 1000) /* 3 seconds */ struct media_adapter { bdaddr_t src; /* Adapter address */ char *path; /* Adapter path */ DBusConnection *conn; /* Adapter connection */ GSList *endpoints; /* Endpoints list */ }; struct endpoint_request { DBusMessage *msg; DBusPendingCall *call; media_endpoint_cb_t cb; void *user_data; }; struct media_endpoint { struct a2dp_sep *sep; char *sender; /* Endpoint DBus bus id */ char *path; /* Endpoint object path */ char *uuid; /* Endpoint property UUID */ uint8_t codec; /* Endpoint codec */ uint8_t *capabilities; /* Endpoint property capabilities */ size_t size; /* Endpoint capabilities size */ guint hs_watch; guint watch; struct endpoint_request *request; struct media_transport *transport; struct media_adapter *adapter; }; static GSList *adapters = NULL; static void endpoint_request_free(struct endpoint_request *request) { if (request->call) dbus_pending_call_unref(request->call); dbus_message_unref(request->msg); g_free(request); } static void media_endpoint_cancel(struct media_endpoint *endpoint) { struct endpoint_request *request = endpoint->request; if (request->call) dbus_pending_call_cancel(request->call); endpoint_request_free(request); endpoint->request = NULL; } static void media_endpoint_remove(struct media_endpoint *endpoint) { struct media_adapter *adapter = endpoint->adapter; if (g_slist_find(adapter->endpoints, endpoint) == NULL) return; info("Endpoint unregistered: sender=%s path=%s", endpoint->sender, endpoint->path); adapter->endpoints = g_slist_remove(adapter->endpoints, endpoint); if (endpoint->sep) a2dp_remove_sep(endpoint->sep); if (endpoint->hs_watch) headset_remove_state_cb(endpoint->hs_watch); if (endpoint->request) media_endpoint_cancel(endpoint); if (endpoint->transport) media_transport_destroy(endpoint->transport); g_dbus_remove_watch(adapter->conn, endpoint->watch); g_free(endpoint->capabilities); g_free(endpoint->sender); g_free(endpoint->path); g_free(endpoint->uuid); g_free(endpoint); } static void media_endpoint_exit(DBusConnection *connection, void *user_data) { struct media_endpoint *endpoint = user_data; endpoint->watch = 0; media_endpoint_remove(endpoint); } static void headset_setconf_cb(struct media_endpoint *endpoint, void *ret, int size, void *user_data) { struct audio_device *dev = user_data; if (ret != NULL) return; headset_set_state(dev, HEADSET_STATE_DISCONNECTED); } static void headset_state_changed(struct audio_device *dev, headset_state_t old_state, headset_state_t new_state, void *user_data) { struct media_endpoint *endpoint = user_data; DBG(""); switch (new_state) { case HEADSET_STATE_DISCONNECTED: media_endpoint_clear_configuration(endpoint); break; case HEADSET_STATE_CONNECTING: media_endpoint_set_configuration(endpoint, dev, NULL, 0, headset_setconf_cb, dev); break; case HEADSET_STATE_CONNECTED: break; case HEADSET_STATE_PLAY_IN_PROGRESS: break; case HEADSET_STATE_PLAYING: break; } } static struct media_endpoint *media_endpoint_create(struct media_adapter *adapter, const char *sender, const char *path, const char *uuid, gboolean delay_reporting, uint8_t codec, uint8_t *capabilities, int size, int *err) { struct media_endpoint *endpoint; endpoint = g_new0(struct media_endpoint, 1); endpoint->sender = g_strdup(sender); endpoint->path = g_strdup(path); endpoint->uuid = g_strdup(uuid); endpoint->codec = codec; if (size > 0) { endpoint->capabilities = g_new(uint8_t, size); memcpy(endpoint->capabilities, capabilities, size); endpoint->size = size; } endpoint->adapter = adapter; if (strcasecmp(uuid, A2DP_SOURCE_UUID) == 0) { endpoint->sep = a2dp_add_sep(&adapter->src, AVDTP_SEP_TYPE_SOURCE, codec, delay_reporting, endpoint, err); if (endpoint->sep == NULL) goto failed; } else if (strcasecmp(uuid, A2DP_SINK_UUID) == 0) { endpoint->sep = a2dp_add_sep(&adapter->src, AVDTP_SEP_TYPE_SINK, codec, delay_reporting, endpoint, err); if (endpoint->sep == NULL) goto failed; } else if (strcasecmp(uuid, HFP_AG_UUID) == 0 || g_strcmp0(uuid, HSP_AG_UUID) == 0) { struct audio_device *dev; endpoint->hs_watch = headset_add_state_cb(headset_state_changed, endpoint); dev = manager_find_device(NULL, &adapter->src, BDADDR_ANY, AUDIO_HEADSET_INTERFACE, TRUE); if (dev) media_endpoint_set_configuration(endpoint, dev, NULL, 0, headset_setconf_cb, dev); } else { if (err) *err = -EINVAL; goto failed; } endpoint->watch = g_dbus_add_disconnect_watch(adapter->conn, sender, media_endpoint_exit, endpoint, NULL); adapter->endpoints = g_slist_append(adapter->endpoints, endpoint); info("Endpoint registered: sender=%s path=%s", sender, path); if (err) *err = 0; return endpoint; failed: g_free(endpoint); return NULL; } static struct media_endpoint *media_adapter_find_endpoint( struct media_adapter *adapter, const char *sender, const char *path, const char *uuid) { GSList *l; for (l = adapter->endpoints; l; l = l->next) { struct media_endpoint *endpoint = l->data; if (sender && g_strcmp0(endpoint->sender, sender) != 0) continue; if (path && g_strcmp0(endpoint->path, path) != 0) continue; if (uuid && g_strcmp0(endpoint->uuid, uuid) != 0) continue; return endpoint; } return NULL; } const char *media_endpoint_get_sender(struct media_endpoint *endpoint) { return endpoint->sender; } static int parse_properties(DBusMessageIter *props, const char **uuid, gboolean *delay_reporting, uint8_t *codec, uint8_t **capabilities, int *size) { gboolean has_uuid = FALSE; gboolean has_codec = FALSE; while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) { const char *key; DBusMessageIter value, entry; int var; dbus_message_iter_recurse(props, &entry); dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); dbus_message_iter_recurse(&entry, &value); var = dbus_message_iter_get_arg_type(&value); if (strcasecmp(key, "UUID") == 0) { if (var != DBUS_TYPE_STRING) return -EINVAL; dbus_message_iter_get_basic(&value, uuid); has_uuid = TRUE; } else if (strcasecmp(key, "Codec") == 0) { if (var != DBUS_TYPE_BYTE) return -EINVAL; dbus_message_iter_get_basic(&value, codec); has_codec = TRUE; } else if (strcasecmp(key, "DelayReporting") == 0) { if (var != DBUS_TYPE_BOOLEAN) return -EINVAL; dbus_message_iter_get_basic(&value, delay_reporting); } else if (strcasecmp(key, "Capabilities") == 0) { DBusMessageIter array; if (var != DBUS_TYPE_ARRAY) return -EINVAL; dbus_message_iter_recurse(&value, &array); dbus_message_iter_get_fixed_array(&array, capabilities, size); } dbus_message_iter_next(props); } return (has_uuid && has_codec) ? 0 : -EINVAL; } static DBusMessage *register_endpoint(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_adapter *adapter = data; DBusMessageIter args, props; const char *sender, *path, *uuid; gboolean delay_reporting = FALSE; uint8_t codec; uint8_t *capabilities; int size = 0; int err; sender = dbus_message_get_sender(msg); dbus_message_iter_init(msg, &args); dbus_message_iter_get_basic(&args, &path); dbus_message_iter_next(&args); if (media_adapter_find_endpoint(adapter, sender, path, NULL) != NULL) return btd_error_already_exists(msg); dbus_message_iter_recurse(&args, &props); if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY) return btd_error_invalid_args(msg); if (parse_properties(&props, &uuid, &delay_reporting, &codec, &capabilities, &size) < 0) return btd_error_invalid_args(msg); if (media_endpoint_create(adapter, sender, path, uuid, delay_reporting, codec, capabilities, size, &err) == FALSE) { if (err == -EPROTONOSUPPORT) return btd_error_not_supported(msg); else return btd_error_invalid_args(msg); } return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static DBusMessage *unregister_endpoint(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_adapter *adapter = data; struct media_endpoint *endpoint; const char *sender, *path; if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) return NULL; sender = dbus_message_get_sender(msg); endpoint = media_adapter_find_endpoint(adapter, sender, path, NULL); if (endpoint == NULL) return btd_error_does_not_exist(msg); media_endpoint_remove(endpoint); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static GDBusMethodTable media_methods[] = { { "RegisterEndpoint", "oa{sv}", "", register_endpoint }, { "UnregisterEndpoint", "o", "", unregister_endpoint }, { }, }; static void path_free(void *data) { struct media_adapter *adapter = data; g_slist_foreach(adapter->endpoints, (GFunc) media_endpoint_release, NULL); g_slist_free(adapter->endpoints); dbus_connection_unref(adapter->conn); adapters = g_slist_remove(adapters, adapter); g_free(adapter->path); g_free(adapter); } int media_register(DBusConnection *conn, const char *path, const bdaddr_t *src) { struct media_adapter *adapter; if (DBUS_TYPE_UNIX_FD < 0) return -EPERM; adapter = g_new0(struct media_adapter, 1); adapter->conn = dbus_connection_ref(conn); bacpy(&adapter->src, src); adapter->path = g_strdup(path); if (!g_dbus_register_interface(conn, path, MEDIA_INTERFACE, media_methods, NULL, NULL, adapter, path_free)) { error("D-Bus failed to register %s path", path); path_free(adapter); return -1; } adapters = g_slist_append(adapters, adapter); return 0; } void media_unregister(const char *path) { GSList *l; for (l = adapters; l; l = l->next) { struct media_adapter *adapter = l->data; if (g_strcmp0(path, adapter->path) == 0) { g_dbus_unregister_interface(adapter->conn, path, MEDIA_INTERFACE); return; } } } size_t media_endpoint_get_capabilities(struct media_endpoint *endpoint, uint8_t **capabilities) { *capabilities = endpoint->capabilities; return endpoint->size; } static void endpoint_reply(DBusPendingCall *call, void *user_data) { struct media_endpoint *endpoint = user_data; struct endpoint_request *request = endpoint->request; DBusMessage *reply; DBusError err; gboolean value; void *ret = NULL; int size = -1; /* steal_reply will always return non-NULL since the callback * is only called after a reply has been received */ reply = dbus_pending_call_steal_reply(call); dbus_error_init(&err); if (dbus_set_error_from_message(&err, reply)) { error("Endpoint replied with an error: %s", err.name); /* Clear endpoint configuration in case of NO_REPLY error */ if (dbus_error_has_name(&err, DBUS_ERROR_NO_REPLY)) { if (request->cb) request->cb(endpoint, NULL, size, request->user_data); media_endpoint_clear_configuration(endpoint); dbus_message_unref(reply); dbus_error_free(&err); return; } dbus_error_free(&err); goto done; } dbus_error_init(&err); if (dbus_message_is_method_call(request->msg, MEDIA_ENDPOINT_INTERFACE, "SelectConfiguration")) { DBusMessageIter args, array; uint8_t *configuration; dbus_message_iter_init(reply, &args); dbus_message_iter_recurse(&args, &array); dbus_message_iter_get_fixed_array(&array, &configuration, &size); ret = configuration; goto done; } else if (!dbus_message_get_args(reply, &err, DBUS_TYPE_INVALID)) { error("Wrong reply signature: %s", err.message); dbus_error_free(&err); goto done; } size = 1; value = TRUE; ret = &value; done: dbus_message_unref(reply); if (request->cb) request->cb(endpoint, ret, size, request->user_data); endpoint_request_free(request); endpoint->request = NULL; } static gboolean media_endpoint_async_call(DBusConnection *conn, DBusMessage *msg, struct media_endpoint *endpoint, media_endpoint_cb_t cb, void *user_data) { struct endpoint_request *request; if (endpoint->request) return FALSE; request = g_new0(struct endpoint_request, 1); /* Timeout should be less than avdtp request timeout (4 seconds) */ if (dbus_connection_send_with_reply(conn, msg, &request->call, REQUEST_TIMEOUT) == FALSE) { error("D-Bus send failed"); g_free(request); return FALSE; } dbus_pending_call_set_notify(request->call, endpoint_reply, endpoint, NULL); request->msg = msg; request->cb = cb; request->user_data = user_data; endpoint->request = request; DBG("Calling %s: name = %s path = %s", dbus_message_get_member(msg), dbus_message_get_destination(msg), dbus_message_get_path(msg)); return TRUE; } gboolean media_endpoint_set_configuration(struct media_endpoint *endpoint, struct audio_device *device, uint8_t *configuration, size_t size, media_endpoint_cb_t cb, void *user_data) { DBusConnection *conn; DBusMessage *msg; const char *path; DBusMessageIter iter; if (endpoint->transport != NULL || endpoint->request != NULL) return FALSE; conn = endpoint->adapter->conn; endpoint->transport = media_transport_create(conn, endpoint, device, configuration, size); if (endpoint->transport == NULL) return FALSE; msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, MEDIA_ENDPOINT_INTERFACE, "SetConfiguration"); if (msg == NULL) { error("Couldn't allocate D-Bus message"); return FALSE; } dbus_message_iter_init_append(msg, &iter); path = media_transport_get_path(endpoint->transport); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path); transport_get_properties(endpoint->transport, &iter); return media_endpoint_async_call(conn, msg, endpoint, cb, user_data); } gboolean media_endpoint_select_configuration(struct media_endpoint *endpoint, uint8_t *capabilities, size_t length, media_endpoint_cb_t cb, void *user_data) { DBusConnection *conn; DBusMessage *msg; if (endpoint->request != NULL) return FALSE; conn = endpoint->adapter->conn; msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, MEDIA_ENDPOINT_INTERFACE, "SelectConfiguration"); if (msg == NULL) { error("Couldn't allocate D-Bus message"); return FALSE; } dbus_message_append_args(msg, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &capabilities, length, DBUS_TYPE_INVALID); return media_endpoint_async_call(conn, msg, endpoint, cb, user_data); } void media_endpoint_clear_configuration(struct media_endpoint *endpoint) { DBusConnection *conn; DBusMessage *msg; const char *path; if (endpoint->transport == NULL) return; if (endpoint->request) media_endpoint_cancel(endpoint); conn = endpoint->adapter->conn; msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, MEDIA_ENDPOINT_INTERFACE, "ClearConfiguration"); if (msg == NULL) { error("Couldn't allocate D-Bus message"); goto done; } path = media_transport_get_path(endpoint->transport); dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID); g_dbus_send_message(conn, msg); done: media_transport_destroy(endpoint->transport); endpoint->transport = NULL; } void media_endpoint_release(struct media_endpoint *endpoint) { DBusMessage *msg; DBG("sender=%s path=%s", endpoint->sender, endpoint->path); /* already exit */ if (endpoint->watch == 0) return; msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, MEDIA_ENDPOINT_INTERFACE, "Release"); if (msg == NULL) { error("Couldn't allocate D-Bus message"); return; } g_dbus_send_message(endpoint->adapter->conn, msg); media_endpoint_remove(endpoint); } struct a2dp_sep *media_endpoint_get_sep(struct media_endpoint *endpoint) { return endpoint->sep; } const char *media_endpoint_get_uuid(struct media_endpoint *endpoint) { return endpoint->uuid; } uint8_t media_endpoint_get_codec(struct media_endpoint *endpoint) { return endpoint->codec; } struct media_transport *media_endpoint_get_transport( struct media_endpoint *endpoint) { return endpoint->transport; }