/* 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 <unistd.h>
#include "cras_bt_constants.h"
#include "cras_bt_device.h"
#include "cras_bt_profile.h"
#include "cras_dbus_util.h"
#include "utlist.h"
#define PROFILE_INTROSPECT_XML \
DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
"<node>\n" \
" <interface name=\"org.bluez.Profile1\">\n" \
" <method name=\"Release\">\n" \
" </method>\n" \
" <method name=\"NewConnection\">\n" \
" <arg name=\"device\" type=\"o\" direction=\"in\">\n" \
" <arg name=\"fd\" type=\"h\" direction=\"in\">\n" \
" <arg name=\"fd_properties\" type=\"a{sv}\" direction=\"in\">\n"\
" </method>\n" \
" <method name=\"RequestDisconnection\">\n" \
" <arg name=\"device\" type=\"o\" direction=\"in\">\n" \
" </method>\n" \
" <method name=\"Cancel\">\n" \
" </method>\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"
/* Profiles */
static struct cras_bt_profile *profiles;
static DBusHandlerResult cras_bt_profile_handle_release(
DBusConnection *conn,
DBusMessage *message,
void *arg)
{
DBusMessage *reply;
const char *profile_path;
struct cras_bt_profile *profile;
profile_path = dbus_message_get_path(message);
profile = cras_bt_profile_get(profile_path);
if (!profile)
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
profile->release(profile);
reply = dbus_message_new_method_return(message);
if (!reply)
return DBUS_HANDLER_RESULT_NEED_MEMORY;
if (!dbus_connection_send(conn, reply, NULL)) {
dbus_message_unref(reply);
return DBUS_HANDLER_RESULT_NEED_MEMORY;
}
dbus_message_unref(reply);
return DBUS_HANDLER_RESULT_HANDLED;
}
static DBusHandlerResult cras_bt_profile_handle_new_connection(
DBusConnection *conn,
DBusMessage *message,
void *arg)
{
DBusMessageIter message_iter;
DBusMessage *reply;
const char *profile_path, *object_path;
int fd = -1;
int err;
struct cras_bt_profile *profile;
struct cras_bt_device *device;
profile_path = dbus_message_get_path(message);
dbus_message_iter_init(message, &message_iter);
dbus_message_iter_get_basic(&message_iter, &object_path);
dbus_message_iter_next(&message_iter);
if (dbus_message_iter_get_arg_type(&message_iter)
!= DBUS_TYPE_UNIX_FD) {
syslog(LOG_ERR, "Argument not a valid unix file descriptor");
goto invalid;
}
dbus_message_iter_get_basic(&message_iter, &fd);
dbus_message_iter_next(&message_iter);
if (fd < 0)
goto invalid;
profile = cras_bt_profile_get(profile_path);
if (!profile)
goto invalid;
device = cras_bt_device_get(object_path);
if (!device) {
syslog(LOG_ERR, "Device %s not found at %s new connection",
object_path, profile_path);
device = cras_bt_device_create(conn, object_path);
}
err = profile->new_connection(conn, profile, device, fd);
if (err) {
syslog(LOG_INFO, "%s new connection rejected", profile->name);
close(fd);
reply = dbus_message_new_error(message,
"org.chromium.Cras.Error.RejectNewConnection",
"Possibly another headset already in use");
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_connection_send(conn, reply, NULL)) {
dbus_message_unref(reply);
return DBUS_HANDLER_RESULT_NEED_MEMORY;
}
dbus_message_unref(reply);
return DBUS_HANDLER_RESULT_HANDLED;
invalid:
if (fd >= 0)
close(fd);
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
static DBusHandlerResult cras_bt_profile_handle_request_disconnection(
DBusConnection *conn,
DBusMessage *message,
void *arg)
{
DBusMessageIter message_iter;
DBusMessage *reply;
const char *prpofile_path, *object_path;
struct cras_bt_profile *profile;
struct cras_bt_device *device;
prpofile_path = dbus_message_get_path(message);
dbus_message_iter_init(message, &message_iter);
dbus_message_iter_get_basic(&message_iter, &object_path);
dbus_message_iter_next(&message_iter);
profile = cras_bt_profile_get(prpofile_path);
if (!profile)
goto invalid;
device = cras_bt_device_get(object_path);
if (!device)
goto invalid;
profile->request_disconnection(profile, device);
reply = dbus_message_new_method_return(message);
if (!reply)
return DBUS_HANDLER_RESULT_NEED_MEMORY;
if (!dbus_connection_send(conn, reply, NULL)) {
dbus_message_unref(reply);
return DBUS_HANDLER_RESULT_NEED_MEMORY;
}
dbus_message_unref(reply);
return DBUS_HANDLER_RESULT_HANDLED;
invalid:
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
static DBusHandlerResult cras_bt_profile_handle_cancel(
DBusConnection *conn,
DBusMessage *message,
void *arg)
{
DBusMessage *reply;
const char *profile_path;
struct cras_bt_profile *profile;
profile_path = dbus_message_get_path(message);
profile = cras_bt_profile_get(profile_path);
if (!profile)
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
profile->cancel(profile);
reply = dbus_message_new_method_return(message);
if (!reply)
return DBUS_HANDLER_RESULT_NEED_MEMORY;
if (!dbus_connection_send(conn, reply, NULL)) {
dbus_message_unref(reply);
return DBUS_HANDLER_RESULT_NEED_MEMORY;
}
dbus_message_unref(reply);
return DBUS_HANDLER_RESULT_HANDLED;
}
static DBusHandlerResult cras_bt_handle_profile_messages(DBusConnection *conn,
DBusMessage *message,
void *arg)
{
if (dbus_message_is_method_call(message,
DBUS_INTERFACE_INTROSPECTABLE,
"Introspect")) {
DBusMessage *reply;
const char *xml = PROFILE_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)) {
dbus_message_unref(reply);
return DBUS_HANDLER_RESULT_NEED_MEMORY;
}
if (!dbus_connection_send(conn, reply, NULL)) {
dbus_message_unref(reply);
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_PROFILE,
"Release")) {
return cras_bt_profile_handle_release(conn, message, arg);
} else if (dbus_message_is_method_call(message,
BLUEZ_INTERFACE_PROFILE,
"NewConnection")) {
return cras_bt_profile_handle_new_connection(conn, message, arg);
} else if (dbus_message_is_method_call(message,
BLUEZ_INTERFACE_PROFILE,
"RequestDisconnection")) {
return cras_bt_profile_handle_request_disconnection(conn,
message,
arg);
} else if (dbus_message_is_method_call(message,
BLUEZ_INTERFACE_PROFILE,
"Cancel")) {
return cras_bt_profile_handle_cancel(conn, message, arg);
} else {
syslog(LOG_ERR, "Unknown Profile message");
}
return DBUS_HANDLER_RESULT_HANDLED;
}
static void cras_bt_on_register_profile(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_ERR, "RegisterProfile returned error: %s",
dbus_message_get_error_name(reply));
dbus_message_unref(reply);
}
int cras_bt_register_profile(DBusConnection *conn,
struct cras_bt_profile *profile)
{
DBusMessage *method_call;
DBusMessageIter message_iter;
DBusMessageIter properties_array_iter;
DBusPendingCall *pending_call;
method_call = dbus_message_new_method_call(BLUEZ_SERVICE,
PROFILE_MANAGER_OBJ_PATH,
BLUEZ_PROFILE_MGMT_INTERFACE,
"RegisterProfile");
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,
&profile->object_path);
dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_STRING,
&profile->uuid);
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);
if (!append_key_value(&properties_array_iter, "Name", DBUS_TYPE_STRING,
DBUS_TYPE_STRING_AS_STRING, &profile->name)) {
dbus_message_unref(method_call);
return -ENOMEM;
}
if (profile->record &&
!append_key_value(&properties_array_iter, "ServiceRecord",
DBUS_TYPE_STRING, DBUS_TYPE_STRING_AS_STRING,
&profile->record)) {
dbus_message_unref(method_call);
return -ENOMEM;
}
if (!append_key_value(&properties_array_iter, "Version",
DBUS_TYPE_UINT16,
DBUS_TYPE_UINT16_AS_STRING, &profile->version)) {
dbus_message_unref(method_call);
return -ENOMEM;
}
if (profile->role && !append_key_value(&properties_array_iter, "Role",
DBUS_TYPE_STRING,
DBUS_TYPE_STRING_AS_STRING,
&profile->role)) {
dbus_message_unref(method_call);
return -ENOMEM;
}
if (profile->features && !append_key_value(&properties_array_iter,
"Features",
DBUS_TYPE_UINT16,
DBUS_TYPE_UINT16_AS_STRING,
&profile->features)) {
dbus_message_unref(method_call);
return -ENOMEM;
}
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_profile,
NULL, NULL)) {
dbus_pending_call_cancel(pending_call);
dbus_pending_call_unref(pending_call);
syslog(LOG_ERR, "register profile fail on set notify");
return -ENOMEM;
}
return 0;
}
int cras_bt_register_profiles(DBusConnection *conn)
{
struct cras_bt_profile *profile;
int err;
DL_FOREACH(profiles, profile) {
err = cras_bt_register_profile(conn, profile);
if (err)
return err;
}
return 0;
}
int cras_bt_add_profile(DBusConnection *conn,
struct cras_bt_profile *profile)
{
static const DBusObjectPathVTable profile_vtable = {
NULL,
cras_bt_handle_profile_messages,
NULL, NULL, NULL, NULL
};
DBusError dbus_error;
dbus_error_init(&dbus_error);
if (!dbus_connection_register_object_path(conn,
profile->object_path,
&profile_vtable,
&dbus_error)) {
syslog(LOG_ERR, "Could not register BT profile %s: %s",
profile->object_path, dbus_error.message);
dbus_error_free(&dbus_error);
return -ENOMEM;
}
DL_APPEND(profiles, profile);
return 0;
}
void cras_bt_profile_reset()
{
struct cras_bt_profile *profile;
DL_FOREACH(profiles, profile)
profile->release(profile);
}
struct cras_bt_profile *cras_bt_profile_get(const char *path)
{
struct cras_bt_profile *profile;
DL_FOREACH(profiles, profile) {
if (strcmp(profile->object_path, path) == 0)
return profile;
}
return NULL;
}
void cras_bt_profile_on_device_disconnected(struct cras_bt_device *device)
{
struct cras_bt_profile *profile;
DL_FOREACH(profiles, profile)
profile->request_disconnection(profile, device);
}