/* 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); }