| /* 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; |
| |
| syslog(LOG_ERR, "Profile %s released by bluetoothd", profile->name); |
| 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_unregister_profile(DBusConnection *conn, |
| struct cras_bt_profile *profile) |
| { |
| DBusMessage *method_call; |
| DBusMessageIter message_iter; |
| DBusError dbus_error; |
| DBusMessage *reply; |
| |
| method_call = dbus_message_new_method_call(BLUEZ_SERVICE, |
| PROFILE_MANAGER_OBJ_PATH, |
| BLUEZ_PROFILE_MGMT_INTERFACE, |
| "UnregisterProfile"); |
| |
| if (!method_call) |
| return -ENOMEM; |
| dbus_error_init(&dbus_error); |
| dbus_message_iter_init_append(method_call, &message_iter); |
| dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_OBJECT_PATH, |
| &profile->object_path); |
| reply = dbus_connection_send_with_reply_and_block( |
| conn, method_call, DBUS_TIMEOUT_USE_DEFAULT, &dbus_error); |
| |
| if (!reply) { |
| dbus_error_free(&dbus_error); |
| dbus_message_unref(method_call); |
| return -EIO; |
| } |
| |
| dbus_message_unref(method_call); |
| |
| if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) { |
| syslog(LOG_ERR, "Unregister profile returned error: %s", |
| dbus_message_get_error_name(reply)); |
| dbus_message_unref(reply); |
| return -EIO; |
| } |
| dbus_message_unref(reply); |
| 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; |
| } |
| |
| int cras_bt_rm_profile(DBusConnection *conn, struct cras_bt_profile *profile) |
| { |
| DL_DELETE(profiles, profile); |
| |
| if (!dbus_connection_unregister_object_path(conn, |
| profile->object_path)) { |
| syslog(LOG_ERR, "Could not unregister BT profile %s", |
| profile->object_path); |
| return -ENOMEM; |
| } |
| 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); |
| } |