mirror of
https://git.kernel.org/pub/scm/bluetooth/bluez.git
synced 2024-11-29 15:14:31 +08:00
bd3ca4fdf4
This also fix the circular dependency of media.c and a2dp.c Invalid read of size 8 at 0x4EA8CC2: g_slice_free_chain_with_offset (in /lib64/libglib-2.0.so.0.2908.0) by 0x13AF33: path_free (media.c:417) by 0x11EB39: remove_interface (object.c:563) by 0x11F360: g_dbus_unregister_interface (object.c:715) by 0x120C49: media_server_remove (manager.c:1098) by 0x4EA9826: g_slist_foreach (in /lib64/libglib-2.0.so.0.2908.0) by 0x178915: adapter_remove (adapter.c:2326) by 0x17535F: btd_manager_unregister_adapter (manager.c:293) by 0x154081: device_event (hciops.c:2643) by 0x1543C1: io_stack_event (hciops.c:2763) by 0x4E8C88C: g_main_context_dispatch (in /lib64/libglib-2.0.so.0.2908.0) by 0x4E8D087: ??? (in /lib64/libglib-2.0.so.0.2908.0) Address 0x63f6638 is 8 bytes inside a block of size 16 free'd at 0x4A055FE: free (vg_replace_malloc.c:366) by 0x4E938F2: g_free (in /lib64/libglib-2.0.so.0.2908.0) by 0x4EA854E: g_slice_free1 (in /lib64/libglib-2.0.so.0.2908.0) by 0x4EA930C: g_slist_remove (in /lib64/libglib-2.0.so.0.2908.0) by 0x13AE53: media_endpoint_remove (media.c:118) by 0x4EA9826: g_slist_foreach (in /lib64/libglib-2.0.so.0.2908.0) by 0x4EA984A: g_slist_free_full (in /lib64/libglib-2.0.so.0.2908.0) by 0x13AF33: path_free (media.c:417) by 0x11EB39: remove_interface (object.c:563) by 0x11F360: g_dbus_unregister_interface (object.c:715) by 0x120C49: media_server_remove (manager.c:1098) by 0x4EA9826: g_slist_foreach (in /lib64/libglib-2.0.so.0.2908.0)
843 lines
20 KiB
C
843 lines
20 KiB
C
/*
|
|
*
|
|
* BlueZ - Bluetooth protocol stack for Linux
|
|
*
|
|
* Copyright (C) 2006-2007 Nokia Corporation
|
|
* Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org>
|
|
*
|
|
*
|
|
* 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 <config.h>
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
|
|
#include <glib.h>
|
|
#include <gdbus.h>
|
|
|
|
#include "../src/adapter.h"
|
|
#include "../src/dbus-common.h"
|
|
|
|
#include "glib-helper.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;
|
|
GDestroyNotify destroy;
|
|
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);
|
|
|
|
if (request->destroy)
|
|
request->destroy(request->user_data);
|
|
|
|
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_destroy(struct media_endpoint *endpoint)
|
|
{
|
|
struct media_adapter *adapter = endpoint->adapter;
|
|
|
|
DBG("sender=%s path=%s", endpoint->sender, endpoint->path);
|
|
|
|
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_remove(struct media_endpoint *endpoint)
|
|
{
|
|
struct media_adapter *adapter = endpoint->adapter;
|
|
|
|
if (endpoint->sep) {
|
|
a2dp_remove_sep(endpoint->sep);
|
|
return;
|
|
}
|
|
|
|
info("Endpoint unregistered: sender=%s path=%s", endpoint->sender,
|
|
endpoint->path);
|
|
|
|
adapter->endpoints = g_slist_remove(adapter->endpoints, endpoint);
|
|
|
|
media_endpoint_destroy(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 clear_configuration(struct media_endpoint *endpoint)
|
|
{
|
|
DBusConnection *conn;
|
|
DBusMessage *msg;
|
|
const char *path;
|
|
struct media_transport *transport = endpoint->transport;
|
|
|
|
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:
|
|
endpoint->transport = NULL;
|
|
media_transport_destroy(transport);
|
|
}
|
|
|
|
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);
|
|
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,
|
|
GDestroyNotify destroy)
|
|
{
|
|
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->destroy = destroy;
|
|
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;
|
|
}
|
|
|
|
static gboolean select_configuration(struct media_endpoint *endpoint,
|
|
uint8_t *capabilities,
|
|
size_t length,
|
|
media_endpoint_cb_t cb,
|
|
void *user_data,
|
|
GDestroyNotify destroy)
|
|
{
|
|
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,
|
|
destroy);
|
|
}
|
|
|
|
static gboolean set_configuration(struct media_endpoint *endpoint,
|
|
struct audio_device *device,
|
|
uint8_t *configuration, size_t size,
|
|
media_endpoint_cb_t cb,
|
|
void *user_data,
|
|
GDestroyNotify destroy)
|
|
{
|
|
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,
|
|
destroy);
|
|
}
|
|
|
|
static void release_endpoint(struct media_endpoint *endpoint)
|
|
{
|
|
DBusMessage *msg;
|
|
|
|
DBG("sender=%s path=%s", endpoint->sender, endpoint->path);
|
|
|
|
/* already exit */
|
|
if (endpoint->watch == 0)
|
|
goto done;
|
|
|
|
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);
|
|
|
|
done:
|
|
media_endpoint_remove(endpoint);
|
|
}
|
|
|
|
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:
|
|
if (endpoint->transport &&
|
|
media_transport_get_dev(endpoint->transport) == dev) {
|
|
|
|
DBG("Clear endpoint %p", endpoint);
|
|
clear_configuration(endpoint);
|
|
}
|
|
break;
|
|
case HEADSET_STATE_CONNECTING:
|
|
set_configuration(endpoint, dev, NULL, 0, headset_setconf_cb,
|
|
dev, NULL);
|
|
break;
|
|
case HEADSET_STATE_CONNECTED:
|
|
break;
|
|
case HEADSET_STATE_PLAY_IN_PROGRESS:
|
|
break;
|
|
case HEADSET_STATE_PLAYING:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static const char *get_name(struct a2dp_sep *sep, void *user_data)
|
|
{
|
|
struct media_endpoint *endpoint = user_data;
|
|
|
|
return endpoint->sender;
|
|
}
|
|
|
|
static size_t get_capabilities(struct a2dp_sep *sep, uint8_t **capabilities,
|
|
void *user_data)
|
|
{
|
|
struct media_endpoint *endpoint = user_data;
|
|
|
|
*capabilities = endpoint->capabilities;
|
|
return endpoint->size;
|
|
}
|
|
|
|
struct a2dp_config_data {
|
|
guint setup_id;
|
|
a2dp_endpoint_config_t cb;
|
|
};
|
|
|
|
struct a2dp_select_data {
|
|
guint setup_id;
|
|
a2dp_endpoint_select_t cb;
|
|
};
|
|
|
|
static void select_cb(struct media_endpoint *endpoint, void *ret, int size,
|
|
void *user_data)
|
|
{
|
|
struct a2dp_select_data *data = user_data;
|
|
|
|
data->cb(endpoint->sep, data->setup_id, ret, size);
|
|
}
|
|
|
|
static int select_config(struct a2dp_sep *sep, uint8_t *capabilities,
|
|
size_t length, guint setup_id,
|
|
a2dp_endpoint_select_t cb, void *user_data)
|
|
{
|
|
struct media_endpoint *endpoint = user_data;
|
|
struct a2dp_select_data *data;
|
|
|
|
data = g_new0(struct a2dp_select_data, 1);
|
|
data->setup_id = setup_id;
|
|
data->cb = cb;
|
|
|
|
if (select_configuration(endpoint, capabilities, length,
|
|
select_cb, data, g_free) == TRUE)
|
|
return 0;
|
|
|
|
g_free(data);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static void config_cb(struct media_endpoint *endpoint, void *ret, int size,
|
|
void *user_data)
|
|
{
|
|
struct a2dp_config_data *data = user_data;
|
|
|
|
data->cb(endpoint->sep, data->setup_id, ret ? TRUE : FALSE);
|
|
}
|
|
|
|
static int set_config(struct a2dp_sep *sep, struct audio_device *dev,
|
|
uint8_t *configuration, size_t length,
|
|
guint setup_id, a2dp_endpoint_config_t cb,
|
|
void *user_data)
|
|
{
|
|
struct media_endpoint *endpoint = user_data;
|
|
struct a2dp_config_data *data;
|
|
|
|
data = g_new0(struct a2dp_config_data, 1);
|
|
data->setup_id = setup_id;
|
|
data->cb = cb;
|
|
|
|
if (set_configuration(endpoint, dev, configuration, length,
|
|
config_cb, data, g_free) == TRUE)
|
|
return 0;
|
|
|
|
g_free(data);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static void clear_config(struct a2dp_sep *sep, void *user_data)
|
|
{
|
|
struct media_endpoint *endpoint = user_data;
|
|
|
|
clear_configuration(endpoint);
|
|
}
|
|
|
|
static void set_delay(struct a2dp_sep *sep, uint16_t delay, void *user_data)
|
|
{
|
|
struct media_endpoint *endpoint = user_data;
|
|
|
|
if (endpoint->transport == NULL)
|
|
return;
|
|
|
|
media_transport_update_delay(endpoint->transport, delay);
|
|
}
|
|
|
|
static struct a2dp_endpoint a2dp_endpoint = {
|
|
.get_name = get_name,
|
|
.get_capabilities = get_capabilities,
|
|
.select_configuration = select_config,
|
|
.set_configuration = set_config,
|
|
.clear_configuration = clear_config,
|
|
.set_delay = set_delay
|
|
};
|
|
|
|
static void a2dp_destroy_endpoint(void *user_data)
|
|
{
|
|
struct media_endpoint *endpoint = user_data;
|
|
|
|
if (endpoint->transport) {
|
|
media_transport_destroy(endpoint->transport);
|
|
endpoint->transport = NULL;
|
|
}
|
|
|
|
endpoint->sep = NULL;
|
|
release_endpoint(endpoint);
|
|
}
|
|
|
|
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, &a2dp_endpoint,
|
|
endpoint, a2dp_destroy_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_SOURCE, codec,
|
|
delay_reporting, &a2dp_endpoint,
|
|
endpoint, a2dp_destroy_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)
|
|
set_configuration(endpoint, dev, NULL, 0,
|
|
headset_setconf_cb, dev, NULL);
|
|
} 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;
|
|
}
|
|
|
|
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) == NULL) {
|
|
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;
|
|
|
|
while (adapter->endpoints)
|
|
release_endpoint(adapter->endpoints->data);
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|