| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2006-2007 Nokia Corporation |
| * Copyright (C) 2004-2008 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 |
| |
| #define _GNU_SOURCE |
| #include <stdio.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <time.h> |
| #include <sys/param.h> |
| #include <sys/ioctl.h> |
| #include <sys/socket.h> |
| |
| #include <bluetooth/bluetooth.h> |
| #include <bluetooth/hci.h> |
| #include <bluetooth/hci_lib.h> |
| #include <bluetooth/l2cap.h> |
| #include <bluetooth/sdp.h> |
| #include <bluetooth/sdp_lib.h> |
| |
| #include <glib.h> |
| #include <dbus/dbus.h> |
| #include <gdbus.h> |
| |
| #include "hcid.h" |
| |
| #include "adapter.h" |
| #include "device.h" |
| |
| #include "textfile.h" |
| #include "oui.h" |
| #include "dbus-common.h" |
| #include "dbus-hci.h" |
| #include "dbus-sdp.h" |
| #include "dbus-database.h" |
| #include "dbus-service.h" |
| #include "dbus-security.h" |
| #include "dbus-error.h" |
| #include "error.h" |
| #include "glib-helper.h" |
| #include "logging.h" |
| #include "agent.h" |
| |
| #define NUM_ELEMENTS(table) (sizeof(table)/sizeof(const char *)) |
| |
| #define IO_CAPABILITY_DISPLAYONLY 0x00 |
| #define IO_CAPABILITY_DISPLAYYESNO 0x01 |
| #define IO_CAPABILITY_KEYBOARDONLY 0x02 |
| #define IO_CAPABILITY_NOINPUTOUTPUT 0x03 |
| #define IO_CAPABILITY_INVALID 0xFF |
| |
| struct mode_req { |
| struct adapter *adapter; |
| DBusConnection *conn; /* Connection reference */ |
| DBusMessage *msg; /* Message reference */ |
| uint8_t mode; /* Requested mode */ |
| guint id; /* Listener id */ |
| }; |
| |
| static const char *service_cls[] = { |
| "positioning", |
| "networking", |
| "rendering", |
| "capturing", |
| "object transfer", |
| "audio", |
| "telephony", |
| "information" |
| }; |
| |
| static const char *major_cls[] = { |
| "miscellaneous", |
| "computer", |
| "phone", |
| "access point", |
| "audio/video", |
| "peripheral", |
| "imaging", |
| "wearable", |
| "toy", |
| "uncategorized" |
| }; |
| |
| static const char *computer_minor_cls[] = { |
| "uncategorized", |
| "desktop", |
| "server", |
| "laptop", |
| "handheld", |
| "palm", |
| "wearable" |
| }; |
| |
| static const char *phone_minor_cls[] = { |
| "uncategorized", |
| "cellular", |
| "cordless", |
| "smart phone", |
| "modem", |
| "isdn" |
| }; |
| |
| static const char *access_point_minor_cls[] = { |
| "fully", |
| "1-17 percent", |
| "17-33 percent", |
| "33-50 percent", |
| "50-67 percent", |
| "67-83 percent", |
| "83-99 percent", |
| "not available" |
| }; |
| |
| static const char *audio_video_minor_cls[] = { |
| "uncategorized", |
| "headset", |
| "handsfree", |
| "unknown", |
| "microphone", |
| "loudspeaker", |
| "headphones", |
| "portable audio", |
| "car audio", |
| "set-top box", |
| "hifi audio", |
| "vcr", |
| "video camera", |
| "camcorder", |
| "video monitor", |
| "video display and loudspeaker", |
| "video conferencing", |
| "unknown", |
| "gaming/toy" |
| }; |
| |
| static const char *peripheral_minor_cls[] = { |
| "uncategorized", |
| "keyboard", |
| "pointing", |
| "combo" |
| }; |
| |
| #if 0 |
| static const char *peripheral_2_minor_cls[] = { |
| "uncategorized", |
| "joystick", |
| "gamepad", |
| "remote control", |
| "sensing", |
| "digitizer tablet", |
| "card reader" |
| }; |
| #endif |
| |
| static const char *imaging_minor_cls[] = { |
| "display", |
| "camera", |
| "scanner", |
| "printer" |
| }; |
| |
| static const char *wearable_minor_cls[] = { |
| "wrist watch", |
| "pager", |
| "jacket", |
| "helmet", |
| "glasses" |
| }; |
| |
| static const char *toy_minor_cls[] = { |
| "robot", |
| "vehicle", |
| "doll", |
| "controller", |
| "game" |
| }; |
| |
| static inline DBusMessage *invalid_args(DBusMessage *msg) |
| { |
| return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments", |
| "Invalid arguments in method call"); |
| } |
| |
| static inline DBusMessage *not_available(DBusMessage *msg) |
| { |
| return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAvailable", |
| "Not Available"); |
| } |
| |
| static inline DBusMessage *adapter_not_ready(DBusMessage *msg) |
| { |
| return g_dbus_create_error(msg, ERROR_INTERFACE ".NotReady", |
| "Adapter is not ready"); |
| } |
| |
| static inline DBusMessage *no_such_adapter(DBusMessage *msg) |
| { |
| return g_dbus_create_error(msg, ERROR_INTERFACE ".NoSuchAdapter", |
| "No such adapter"); |
| } |
| |
| static inline DBusMessage *failed_strerror(DBusMessage *msg, int err) |
| { |
| return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", |
| strerror(err)); |
| } |
| |
| static inline DBusMessage *in_progress(DBusMessage *msg, const char *str) |
| { |
| return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress", str); |
| } |
| |
| static inline DBusMessage *not_in_progress(DBusMessage *msg, const char *str) |
| { |
| return g_dbus_create_error(msg, ERROR_INTERFACE ".NotInProgress", str); |
| } |
| |
| static inline DBusMessage *not_authorized(DBusMessage *msg) |
| { |
| return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAuthorized", |
| "Not authorized"); |
| } |
| |
| static inline DBusMessage *unsupported_major_class(DBusMessage *msg) |
| { |
| return g_dbus_create_error(msg, |
| ERROR_INTERFACE ".UnsupportedMajorClass", |
| "Unsupported Major Class"); |
| } |
| |
| static int auth_req_cmp(const void *p1, const void *p2) |
| { |
| const struct pending_auth_info *pb1 = p1; |
| const bdaddr_t *bda = p2; |
| |
| return bda ? bacmp(&pb1->bdaddr, bda) : -1; |
| } |
| |
| void adapter_auth_request_replied(struct adapter *adapter, bdaddr_t *dba) |
| { |
| GSList *l; |
| struct pending_auth_info *auth; |
| |
| l = g_slist_find_custom(adapter->auth_reqs, dba, auth_req_cmp); |
| if (!l) |
| return; |
| |
| auth = l->data; |
| |
| auth->replied = TRUE; |
| } |
| |
| struct pending_auth_info *adapter_find_auth_request(struct adapter *adapter, |
| bdaddr_t *dba) |
| { |
| GSList *l; |
| |
| l = g_slist_find_custom(adapter->auth_reqs, dba, auth_req_cmp); |
| if (l) |
| return l->data; |
| |
| return NULL; |
| } |
| |
| void adapter_remove_auth_request(struct adapter *adapter, bdaddr_t *dba) |
| { |
| GSList *l; |
| struct pending_auth_info *auth; |
| |
| l = g_slist_find_custom(adapter->auth_reqs, dba, auth_req_cmp); |
| if (!l) |
| return; |
| |
| auth = l->data; |
| |
| adapter->auth_reqs = g_slist_remove(adapter->auth_reqs, auth); |
| |
| g_free(auth); |
| } |
| |
| struct pending_auth_info *adapter_new_auth_request(struct adapter *adapter, |
| bdaddr_t *dba, |
| auth_type_t type) |
| { |
| struct pending_auth_info *info; |
| |
| debug("hcid_dbus_new_auth_request"); |
| |
| info = g_new0(struct pending_auth_info, 1); |
| |
| bacpy(&info->bdaddr, dba); |
| info->type = type; |
| adapter->auth_reqs = g_slist_append(adapter->auth_reqs, info); |
| |
| if (adapter->bonding && !bacmp(dba, &adapter->bonding->bdaddr)) |
| adapter->bonding->auth_active = 1; |
| |
| return info; |
| } |
| |
| int pending_remote_name_cancel(struct adapter *adapter) |
| { |
| struct remote_dev_info *dev, match; |
| GSList *l; |
| int dd, err = 0; |
| |
| /* find the pending remote name request */ |
| memset(&match, 0, sizeof(struct remote_dev_info)); |
| bacpy(&match.bdaddr, BDADDR_ANY); |
| match.name_status = NAME_REQUESTED; |
| |
| l = g_slist_find_custom(adapter->found_devices, &match, |
| (GCompareFunc) found_device_cmp); |
| if (!l) /* no pending request */ |
| return 0; |
| |
| dd = hci_open_dev(adapter->dev_id); |
| if (dd < 0) |
| return -ENODEV; |
| |
| dev = l->data; |
| |
| if (hci_read_remote_name_cancel(dd, &dev->bdaddr, 1000) < 0) { |
| error("Remote name cancel failed: %s(%d)", strerror(errno), errno); |
| err = -errno; |
| } |
| |
| /* free discovered devices list */ |
| g_slist_foreach(adapter->found_devices, (GFunc) g_free, NULL); |
| g_slist_free(adapter->found_devices); |
| adapter->found_devices = NULL; |
| |
| hci_close_dev(dd); |
| return err; |
| } |
| |
| static int auth_info_agent_cmp(const void *a, const void *b) |
| { |
| const struct pending_auth_info *auth = a; |
| const struct agent *agent = b; |
| |
| if (auth->agent == agent) |
| return 0; |
| |
| return -1; |
| } |
| |
| static void device_agent_removed(struct agent *agent, void *user_data) |
| { |
| struct device *device = user_data; |
| struct pending_auth_info *auth; |
| GSList *l; |
| |
| device->agent = NULL; |
| |
| l = g_slist_find_custom(device->adapter->auth_reqs, agent, |
| auth_info_agent_cmp); |
| if (!l) |
| return; |
| |
| auth = l->data; |
| auth->agent = NULL; |
| } |
| |
| static struct bonding_request_info *bonding_request_new(DBusConnection *conn, |
| DBusMessage *msg, |
| struct adapter *adapter, |
| const char *address, |
| const char *agent_path, |
| uint8_t capability) |
| { |
| struct bonding_request_info *bonding; |
| struct device *device; |
| |
| debug("bonding_request_new(%s)", address); |
| |
| if (hcid_dbus_use_experimental() && agent_path) { |
| const char *name = dbus_message_get_sender(msg); |
| |
| device = adapter_get_device(conn, adapter, address); |
| if (!device) |
| return NULL; |
| |
| device->agent = agent_create(adapter, name, agent_path, |
| capability, |
| device_agent_removed, |
| device); |
| debug("Temporary agent registered for hci%d/%s at %s:%s", |
| adapter->dev_id, device->address, name, |
| agent_path); |
| } |
| |
| bonding = g_new0(struct bonding_request_info, 1); |
| |
| bonding->conn = dbus_connection_ref(conn); |
| bonding->msg = dbus_message_ref(msg); |
| bonding->adapter = adapter; |
| |
| str2ba(address, &bonding->bdaddr); |
| |
| return bonding; |
| } |
| |
| const char *mode2str(uint8_t mode) |
| { |
| switch(mode) { |
| case MODE_OFF: |
| return "off"; |
| case MODE_CONNECTABLE: |
| return "connectable"; |
| case MODE_DISCOVERABLE: |
| return "discoverable"; |
| case MODE_LIMITED: |
| return "limited"; |
| default: |
| return "unknown"; |
| } |
| } |
| |
| static uint8_t on_mode(const char *addr) |
| { |
| char mode[14]; |
| bdaddr_t sba; |
| |
| str2ba(addr, &sba); |
| |
| if (read_on_mode(&sba, mode, sizeof(mode)) < 0) |
| return MODE_CONNECTABLE; |
| |
| return str2mode(addr, mode); |
| } |
| |
| uint8_t str2mode(const char *addr, const char *mode) |
| { |
| if (strcasecmp("off", mode) == 0) |
| return MODE_OFF; |
| else if (strcasecmp("connectable", mode) == 0) |
| return MODE_CONNECTABLE; |
| else if (strcasecmp("discoverable", mode) == 0) |
| return MODE_DISCOVERABLE; |
| else if (strcasecmp("limited", mode) == 0) |
| return MODE_LIMITED; |
| else if (strcasecmp("on", mode) == 0) |
| return on_mode(addr); |
| else |
| return MODE_UNKNOWN; |
| } |
| |
| static DBusMessage *adapter_get_info(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| const char *property; |
| DBusMessage *reply; |
| DBusMessageIter iter; |
| DBusMessageIter dict; |
| bdaddr_t ba; |
| char str[249]; |
| uint8_t cls[3]; |
| |
| if (check_address(adapter->address) < 0) |
| return adapter_not_ready(msg); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_iter_init_append(reply, &iter); |
| |
| dbus_message_iter_open_container(&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, &dict); |
| |
| property = adapter->address; |
| dbus_message_iter_append_dict_entry(&dict, "address", |
| DBUS_TYPE_STRING, &property); |
| |
| memset(str, 0, sizeof(str)); |
| property = str; |
| str2ba(adapter->address, &ba); |
| |
| if (!read_local_name(&ba, str)) |
| dbus_message_iter_append_dict_entry(&dict, "name", |
| DBUS_TYPE_STRING, &property); |
| |
| get_device_version(adapter->dev_id, str, sizeof(str)); |
| dbus_message_iter_append_dict_entry(&dict, "version", |
| DBUS_TYPE_STRING, &property); |
| |
| get_device_revision(adapter->dev_id, str, sizeof(str)); |
| dbus_message_iter_append_dict_entry(&dict, "revision", |
| DBUS_TYPE_STRING, &property); |
| |
| get_device_manufacturer(adapter->dev_id, str, sizeof(str)); |
| dbus_message_iter_append_dict_entry(&dict, "manufacturer", |
| DBUS_TYPE_STRING, &property); |
| |
| get_device_company(adapter->dev_id, str, sizeof(str)); |
| dbus_message_iter_append_dict_entry(&dict, "company", |
| DBUS_TYPE_STRING, &property); |
| |
| property = mode2str(adapter->mode); |
| |
| dbus_message_iter_append_dict_entry(&dict, "mode", |
| DBUS_TYPE_STRING, &property); |
| |
| dbus_message_iter_append_dict_entry(&dict, "discoverable_timeout", |
| DBUS_TYPE_UINT32, &adapter->discov_timeout); |
| |
| if (!read_local_class(&ba, cls)) { |
| uint32_t class; |
| |
| memcpy(&class, cls, 3); |
| dbus_message_iter_append_dict_entry(&dict, "class", |
| DBUS_TYPE_UINT32, &class); |
| |
| property = major_class_str(class); |
| dbus_message_iter_append_dict_entry(&dict, "major_class", |
| DBUS_TYPE_STRING, &property); |
| |
| property = minor_class_str(class); |
| dbus_message_iter_append_dict_entry(&dict, "minor_class", |
| DBUS_TYPE_STRING, &property); |
| } |
| |
| dbus_message_iter_close_container(&iter, &dict); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_get_address(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| const char *paddr = adapter->address; |
| DBusMessage *reply; |
| |
| if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) |
| return invalid_args(msg); |
| |
| if (check_address(paddr) < 0) |
| return adapter_not_ready(msg); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_STRING, &paddr, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_get_version(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| DBusMessage *reply; |
| char str[20], *str_ptr = str; |
| int err; |
| |
| if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) |
| return invalid_args(msg); |
| |
| err = get_device_version(adapter->dev_id, str, sizeof(str)); |
| if (err < 0) |
| return failed_strerror(msg, -err); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_get_revision(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| DBusMessage *reply; |
| char str[64], *str_ptr = str; |
| int err; |
| |
| if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) |
| return invalid_args(msg); |
| |
| err = get_device_revision(adapter->dev_id, str, sizeof(str)); |
| if (err < 0) |
| return failed_strerror(msg, -err); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_get_manufacturer(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| DBusMessage *reply; |
| char str[64], *str_ptr = str; |
| int err; |
| |
| if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) |
| return invalid_args(msg); |
| |
| err = get_device_manufacturer(adapter->dev_id, str, sizeof(str)); |
| if (err < 0) |
| return failed_strerror(msg, -err); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_get_company(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| DBusMessage *reply; |
| char str[64], *str_ptr = str; |
| int err; |
| |
| if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) |
| return invalid_args(msg); |
| |
| err = get_device_company(adapter->dev_id, str, sizeof(str)); |
| if (err < 0) |
| return failed_strerror(msg, -err); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_list_modes(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| DBusMessage *reply; |
| DBusMessageIter iter; |
| DBusMessageIter array_iter; |
| const char *mode_ptr[] = { "off", "connectable", "discoverable", "limited" }; |
| int i; |
| |
| if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) |
| return invalid_args(msg); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_iter_init_append(reply, &iter); |
| dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, |
| DBUS_TYPE_STRING_AS_STRING, &array_iter); |
| for (i = 0; i < 4; i++) |
| dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_STRING, |
| &mode_ptr[i]); |
| |
| dbus_message_iter_close_container(&iter, &array_iter); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_get_mode(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| const struct adapter *adapter = data; |
| DBusMessage *reply = NULL; |
| const char *mode; |
| |
| if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) |
| return invalid_args(msg); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| mode = mode2str(adapter->mode); |
| |
| dbus_message_append_args(reply, DBUS_TYPE_STRING, &mode, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static DBusMessage *set_mode(DBusConnection *conn, DBusMessage *msg, |
| uint8_t new_mode, void *data) |
| { |
| struct adapter *adapter = data; |
| uint8_t scan_enable; |
| uint8_t current_scan = adapter->scan_enable; |
| bdaddr_t local; |
| gboolean limited; |
| int err, dd; |
| const char *mode; |
| |
| switch(new_mode) { |
| case MODE_OFF: |
| scan_enable = SCAN_DISABLED; |
| break; |
| case MODE_CONNECTABLE: |
| scan_enable = SCAN_PAGE; |
| break; |
| case MODE_DISCOVERABLE: |
| case MODE_LIMITED: |
| scan_enable = (SCAN_PAGE | SCAN_INQUIRY); |
| break; |
| default: |
| return invalid_args(msg); |
| } |
| |
| /* Do reverse resolution in case of "on" mode */ |
| mode = mode2str(new_mode); |
| |
| dd = hci_open_dev(adapter->dev_id); |
| if (dd < 0) |
| return no_such_adapter(msg); |
| |
| if (!adapter->up && |
| (hcid.offmode == HCID_OFFMODE_NOSCAN || |
| (hcid.offmode == HCID_OFFMODE_DEVDOWN && |
| scan_enable != SCAN_DISABLED))) { |
| /* Start HCI device */ |
| if (ioctl(dd, HCIDEVUP, adapter->dev_id) == 0) |
| goto done; /* on success */ |
| |
| if (errno != EALREADY) { |
| err = errno; |
| error("Can't init device hci%d: %s (%d)\n", |
| adapter->dev_id, strerror(errno), errno); |
| |
| hci_close_dev(dd); |
| return failed_strerror(msg, err); |
| } |
| } |
| |
| if (adapter->up && scan_enable == SCAN_DISABLED && |
| hcid.offmode == HCID_OFFMODE_DEVDOWN) { |
| if (ioctl(dd, HCIDEVDOWN, adapter->dev_id) < 0) { |
| hci_close_dev(dd); |
| return failed_strerror(msg, errno); |
| } |
| |
| goto done; |
| } |
| |
| limited = (new_mode == MODE_LIMITED ? TRUE : FALSE); |
| err = set_limited_discoverable(dd, adapter->class, limited); |
| if (err < 0) { |
| hci_close_dev(dd); |
| return failed_strerror(msg, -err); |
| } |
| |
| if (current_scan != scan_enable) { |
| struct hci_request rq; |
| uint8_t status = 0; |
| |
| memset(&rq, 0, sizeof(rq)); |
| rq.ogf = OGF_HOST_CTL; |
| rq.ocf = OCF_WRITE_SCAN_ENABLE; |
| rq.cparam = &scan_enable; |
| rq.clen = sizeof(scan_enable); |
| rq.rparam = &status; |
| rq.rlen = sizeof(status); |
| rq.event = EVT_CMD_COMPLETE; |
| |
| if (hci_send_req(dd, &rq, 1000) < 0) { |
| err = errno; |
| error("Sending write scan enable command failed: %s (%d)", |
| strerror(errno), errno); |
| hci_close_dev(dd); |
| return failed_strerror(msg, err); |
| } |
| |
| if (status) { |
| error("Setting scan enable failed with status 0x%02x", |
| status); |
| hci_close_dev(dd); |
| return failed_strerror(msg, bt_error(status)); |
| } |
| } else { |
| /* discoverable or limited */ |
| if ((scan_enable & SCAN_INQUIRY) && (new_mode != adapter->mode)) { |
| g_dbus_emit_signal(conn, |
| dbus_message_get_path(msg), |
| ADAPTER_INTERFACE, |
| "ModeChanged", |
| DBUS_TYPE_STRING, &mode, |
| DBUS_TYPE_INVALID); |
| |
| if (adapter->timeout_id) |
| g_source_remove(adapter->timeout_id); |
| |
| if (!adapter->sessions && !adapter->discov_timeout) |
| adapter->timeout_id = g_timeout_add(adapter->discov_timeout * 1000, |
| discov_timeout_handler, adapter); |
| } |
| } |
| done: |
| str2ba(adapter->address, &local); |
| write_device_mode(&local, mode); |
| |
| hci_close_dev(dd); |
| |
| adapter->mode = new_mode; |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| gint find_session(struct mode_req *req, DBusMessage *msg) |
| { |
| const char *name = dbus_message_get_sender(req->msg); |
| const char *sender = dbus_message_get_sender(msg); |
| |
| return strcmp(name, sender); |
| } |
| |
| static void confirm_mode_cb(struct agent *agent, DBusError *err, void *data) |
| { |
| struct mode_req *req = data; |
| DBusMessage *reply; |
| |
| if (err && dbus_error_is_set(err)) { |
| reply = dbus_message_new_error(req->msg, err->name, err->message); |
| dbus_connection_send(req->conn, reply, NULL); |
| dbus_message_unref(reply); |
| goto cleanup; |
| } |
| |
| reply = set_mode(req->conn, req->msg, req->mode, req->adapter); |
| dbus_connection_send(req->conn, reply, NULL); |
| dbus_message_unref(reply); |
| |
| if (!g_slist_find_custom(req->adapter->sessions, req->msg, |
| (GCompareFunc) find_session)) |
| goto cleanup; |
| |
| return; |
| |
| cleanup: |
| dbus_message_unref(req->msg); |
| if (req->id) |
| g_dbus_remove_watch(req->conn, req->id); |
| dbus_connection_unref(req->conn); |
| g_free(req); |
| } |
| |
| static DBusMessage *confirm_mode(DBusConnection *conn, DBusMessage *msg, |
| const char *mode, void *data) |
| { |
| struct adapter *adapter = data; |
| struct mode_req *req; |
| int ret; |
| |
| if (!adapter->agent) |
| return dbus_message_new_method_return(msg); |
| |
| req = g_new0(struct mode_req, 1); |
| req->adapter = adapter; |
| req->conn = dbus_connection_ref(conn); |
| req->msg = dbus_message_ref(msg); |
| req->mode = str2mode(adapter->address, mode); |
| |
| ret = agent_confirm_mode_change(adapter->agent, mode, confirm_mode_cb, |
| req); |
| if (ret < 0) { |
| dbus_connection_unref(req->conn); |
| dbus_message_unref(req->msg); |
| g_free(req); |
| return invalid_args(msg); |
| } |
| |
| return NULL; |
| } |
| |
| static DBusMessage *adapter_set_mode(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| const char *mode; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &mode, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| if (!mode) |
| return invalid_args(msg); |
| |
| adapter->global_mode = str2mode(adapter->address, mode); |
| |
| if (adapter->global_mode == adapter->mode) |
| return dbus_message_new_method_return(msg); |
| |
| if (adapter->sessions && adapter->global_mode < adapter->mode) |
| return confirm_mode(conn, msg, mode, data); |
| |
| return set_mode(conn, msg, str2mode(adapter->address, mode), data); |
| } |
| |
| static DBusMessage *adapter_get_discoverable_to(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| const struct adapter *adapter = data; |
| DBusMessage *reply; |
| |
| if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) |
| return invalid_args(msg); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_UINT32, &adapter->discov_timeout, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static void resolve_paths(DBusMessage *msg, char **old_path, char **new_path) |
| { |
| const char *path = dbus_message_get_path(msg); |
| |
| if (!path) |
| return; |
| |
| if (old_path) |
| *old_path = NULL; |
| |
| if (new_path) |
| *new_path = NULL; |
| |
| /* old path calls */ |
| if (g_str_has_prefix(path, BASE_PATH)) { |
| if (old_path) |
| *old_path = g_strdup(path); |
| |
| if (hcid_dbus_use_experimental() && new_path) |
| *new_path = g_strdup(path + ADAPTER_PATH_INDEX); |
| |
| return; |
| } |
| |
| if (old_path) |
| *old_path = g_strconcat(BASE_PATH, path, NULL); |
| |
| if (new_path) |
| *new_path = g_strdup(path); |
| } |
| |
| static DBusMessage *set_discoverable_timeout(DBusConnection *conn, |
| DBusMessage *msg, |
| uint32_t timeout, |
| void *data) |
| { |
| struct adapter *adapter = data; |
| bdaddr_t bdaddr; |
| char *old_path, *new_path; |
| |
| if (adapter->timeout_id) { |
| g_source_remove(adapter->timeout_id); |
| adapter->timeout_id = 0; |
| } |
| |
| if ((timeout != 0) && (adapter->scan_enable & SCAN_INQUIRY)) |
| adapter->timeout_id = g_timeout_add(timeout * 1000, |
| discov_timeout_handler, |
| adapter); |
| |
| adapter->discov_timeout = timeout; |
| |
| str2ba(adapter->address, &bdaddr); |
| write_discoverable_timeout(&bdaddr, timeout); |
| |
| resolve_paths(msg, &old_path, &new_path); |
| |
| g_dbus_emit_signal(conn, old_path, |
| ADAPTER_INTERFACE, |
| "DiscoverableTimeoutChanged", |
| DBUS_TYPE_UINT32, &timeout, |
| DBUS_TYPE_INVALID); |
| if (new_path) { |
| dbus_connection_emit_property_changed(conn, new_path, |
| ADAPTER_INTERFACE, |
| "DiscoverableTimeout", |
| DBUS_TYPE_UINT32, &timeout); |
| } |
| |
| g_free(old_path); |
| g_free(new_path); |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static DBusMessage *adapter_set_discoverable_to(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| uint32_t timeout; |
| |
| if (!adapter->up) |
| return adapter_not_ready(msg); |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_UINT32, &timeout, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| return set_discoverable_timeout(conn, msg, timeout, data); |
| } |
| |
| static DBusMessage *adapter_is_connectable(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| const struct adapter *adapter = data; |
| DBusMessage *reply; |
| const uint8_t scan_enable = adapter->scan_enable; |
| dbus_bool_t connectable = FALSE; |
| |
| if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) |
| return invalid_args(msg); |
| |
| if (scan_enable & SCAN_PAGE) |
| connectable = TRUE; |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connectable, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_is_discoverable(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| const struct adapter *adapter = data; |
| DBusMessage *reply; |
| const uint8_t scan_enable = adapter->scan_enable; |
| dbus_bool_t discoverable = FALSE; |
| |
| if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) |
| return invalid_args(msg); |
| |
| if (scan_enable & SCAN_INQUIRY) |
| discoverable = TRUE; |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &discoverable, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_is_connected(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| DBusMessage *reply; |
| dbus_bool_t connected = FALSE; |
| |
| struct adapter *adapter = data; |
| GSList *l = adapter->active_conn; |
| |
| const char *peer_addr; |
| bdaddr_t peer_bdaddr; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &peer_addr, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| if (check_address(peer_addr) < 0) |
| return invalid_args(msg); |
| |
| str2ba(peer_addr, &peer_bdaddr); |
| |
| l = g_slist_find_custom(l, &peer_bdaddr, active_conn_find_by_bdaddr); |
| if (l) |
| connected = TRUE; |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_list_connections(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| DBusMessage *reply; |
| DBusMessageIter iter; |
| DBusMessageIter array_iter; |
| struct adapter *adapter = data; |
| GSList *l = adapter->active_conn; |
| |
| if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) |
| return invalid_args(msg); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_iter_init_append(reply, &iter); |
| dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, |
| DBUS_TYPE_STRING_AS_STRING, &array_iter); |
| |
| while (l) { |
| char peer_addr[18]; |
| const char *paddr = peer_addr; |
| struct active_conn_info *dev = l->data; |
| |
| ba2str(&dev->bdaddr, peer_addr); |
| |
| dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_STRING, |
| &paddr); |
| |
| l = l->next; |
| } |
| |
| dbus_message_iter_close_container(&iter, &array_iter); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_get_major_class(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| const struct adapter *adapter = data; |
| DBusMessage *reply; |
| const char *str_ptr = "computer"; |
| |
| if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) |
| return invalid_args(msg); |
| |
| /* FIXME: Currently, only computer major class is supported */ |
| if ((adapter->class[1] & 0x1f) != 1) |
| return unsupported_major_class(msg); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_list_minor_classes(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| const struct adapter *adapter = data; |
| DBusMessage *reply; |
| DBusMessageIter iter; |
| DBusMessageIter array_iter; |
| const char **minor_ptr; |
| uint8_t major_class; |
| int size, i; |
| |
| if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) |
| return invalid_args(msg); |
| |
| major_class = adapter->class[1] & 0x1F; |
| |
| switch (major_class) { |
| case 1: /* computer */ |
| minor_ptr = computer_minor_cls; |
| size = sizeof(computer_minor_cls) / sizeof(*computer_minor_cls); |
| break; |
| case 2: /* phone */ |
| minor_ptr = phone_minor_cls; |
| size = sizeof(phone_minor_cls) / sizeof(*phone_minor_cls); |
| break; |
| default: |
| return unsupported_major_class(msg); |
| } |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_iter_init_append(reply, &iter); |
| dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, |
| DBUS_TYPE_STRING_AS_STRING, &array_iter); |
| for (i = 0; i < size; i++) |
| dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_STRING, |
| &minor_ptr[i]); |
| |
| dbus_message_iter_close_container(&iter, &array_iter); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_get_minor_class(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| DBusMessage *reply; |
| const char *str_ptr = ""; |
| uint8_t minor_class; |
| |
| if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) |
| return invalid_args(msg); |
| |
| /* FIXME: Currently, only computer major class is supported */ |
| if ((adapter->class[1] & 0x1f) != 1) |
| return unsupported_major_class(msg); |
| |
| minor_class = adapter->class[0] >> 2; |
| |
| /* Validate computer minor class */ |
| if (minor_class > (sizeof(computer_minor_cls) / sizeof(*computer_minor_cls))) |
| goto failed; |
| |
| str_ptr = computer_minor_cls[minor_class]; |
| |
| failed: |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_set_minor_class(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| const char *minor; |
| uint32_t dev_class = 0xFFFFFFFF; |
| int i, dd; |
| |
| if (!adapter->up) |
| return adapter_not_ready(msg); |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &minor, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| if (!minor) |
| return invalid_args(msg); |
| |
| dd = hci_open_dev(adapter->dev_id); |
| if (dd < 0) |
| return no_such_adapter(msg); |
| |
| /* Currently, only computer major class is supported */ |
| if ((adapter->class[1] & 0x1f) != 1) { |
| hci_close_dev(dd); |
| return unsupported_major_class(msg); |
| } |
| for (i = 0; i < sizeof(computer_minor_cls) / sizeof(*computer_minor_cls); i++) |
| if (!strcasecmp(minor, computer_minor_cls[i])) { |
| /* Remove the format type */ |
| dev_class = i << 2; |
| break; |
| } |
| |
| /* Check if it's a valid minor class */ |
| if (dev_class == 0xFFFFFFFF) { |
| hci_close_dev(dd); |
| return invalid_args(msg); |
| } |
| |
| /* set the service class and major class */ |
| dev_class |= (adapter->class[2] << 16) | (adapter->class[1] << 8); |
| |
| if (hci_write_class_of_dev(dd, dev_class, 2000) < 0) { |
| int err = errno; |
| error("Can't write class of device on hci%d: %s(%d)", |
| adapter->dev_id, strerror(errno), errno); |
| hci_close_dev(dd); |
| return failed_strerror(msg, err); |
| } |
| |
| g_dbus_emit_signal(conn, dbus_message_get_path(msg), |
| ADAPTER_INTERFACE, "MinorClassChanged", |
| DBUS_TYPE_STRING, &minor, |
| DBUS_TYPE_INVALID); |
| |
| hci_close_dev(dd); |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static DBusMessage *adapter_get_service_classes(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| DBusMessage *reply; |
| DBusMessageIter iter; |
| DBusMessageIter array_iter; |
| const char *str_ptr; |
| int i; |
| |
| if (!adapter->up) |
| return adapter_not_ready(msg); |
| |
| if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) |
| return invalid_args(msg); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_iter_init_append(reply, &iter); |
| |
| dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, |
| DBUS_TYPE_STRING_AS_STRING, &array_iter); |
| |
| for (i = 0; i < (sizeof(service_cls) / sizeof(*service_cls)); i++) { |
| if (adapter->class[2] & (1 << i)) { |
| str_ptr = service_cls[i]; |
| dbus_message_iter_append_basic(&array_iter, |
| DBUS_TYPE_STRING, &str_ptr); |
| } |
| } |
| |
| dbus_message_iter_close_container(&iter, &array_iter); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_get_name(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| DBusMessage *reply; |
| char str[249], *str_ptr = str; |
| int err; |
| bdaddr_t ba; |
| |
| if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) |
| return invalid_args(msg); |
| |
| str2ba(adapter->address, &ba); |
| |
| err = read_local_name(&ba, str); |
| if (err < 0) { |
| if (!adapter->up) |
| return adapter_not_ready(msg); |
| |
| err = get_device_name(adapter->dev_id, str, sizeof(str)); |
| if (err < 0) |
| return failed_strerror(msg, -err); |
| } |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static DBusMessage *set_name(DBusConnection *conn, DBusMessage *msg, |
| const char *name, void *data) |
| { |
| struct adapter *adapter = data; |
| bdaddr_t bdaddr; |
| int ecode; |
| char *new_path; |
| |
| if (!g_utf8_validate(name, -1, NULL)) { |
| error("Name change failed: the supplied name isn't valid UTF-8"); |
| return invalid_args(msg); |
| } |
| |
| str2ba(adapter->address, &bdaddr); |
| |
| write_local_name(&bdaddr, (char *) name); |
| |
| if (!adapter->up) |
| goto done; |
| |
| ecode = set_device_name(adapter->dev_id, name); |
| if (ecode < 0) |
| return failed_strerror(msg, -ecode); |
| done: |
| resolve_paths(msg, NULL, &new_path); |
| |
| if (new_path) { |
| dbus_connection_emit_property_changed(conn, new_path, |
| ADAPTER_INTERFACE, |
| "Name", DBUS_TYPE_STRING, |
| &name); |
| } |
| |
| g_free(new_path); |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static DBusMessage *adapter_set_name(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| char *str_ptr; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &str_ptr, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| return set_name(conn, msg, str_ptr, data); |
| } |
| |
| static DBusMessage *adapter_get_remote_info(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| DBusMessage *reply; |
| DBusMessageIter iter; |
| DBusMessageIter dict; |
| bdaddr_t src, dst; |
| const char *addr_ptr; |
| char filename[PATH_MAX + 1]; |
| char buf[64]; |
| const char *ptr; |
| char *str; |
| dbus_bool_t boolean; |
| uint32_t class; |
| int compid, ver, subver; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &addr_ptr, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| if (check_address(addr_ptr) < 0) |
| return invalid_args(msg); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_iter_init_append(reply, &iter); |
| |
| dbus_message_iter_open_container(&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, &dict); |
| |
| /* Name */ |
| create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "names"); |
| str = textfile_caseget(filename, addr_ptr); |
| if (str) { |
| dbus_message_iter_append_dict_entry(&dict, "name", |
| DBUS_TYPE_STRING, &str); |
| free(str); |
| } |
| |
| str2ba(adapter->address, &src); |
| str2ba(addr_ptr, &dst); |
| |
| /* Remote device class */ |
| if (read_remote_class(&src, &dst, &class) == 0) { |
| |
| dbus_message_iter_append_dict_entry(&dict, "class", |
| DBUS_TYPE_UINT32, &class); |
| |
| ptr = major_class_str(class); |
| dbus_message_iter_append_dict_entry(&dict, "major_class", |
| DBUS_TYPE_STRING, &ptr); |
| |
| ptr = minor_class_str(class); |
| dbus_message_iter_append_dict_entry(&dict, "minor_class", |
| DBUS_TYPE_STRING, &ptr); |
| } |
| |
| /* Alias */ |
| if (get_device_alias(adapter->dev_id, &dst, buf, sizeof(buf)) > 0) { |
| ptr = buf; |
| dbus_message_iter_append_dict_entry(&dict, "alias", |
| DBUS_TYPE_STRING, &ptr); |
| } |
| |
| /* Bonded */ |
| create_name(filename, PATH_MAX, STORAGEDIR, |
| adapter->address, "linkkeys"); |
| str = textfile_caseget(filename, addr_ptr); |
| if (str) { |
| boolean = TRUE; |
| free(str); |
| } else { |
| boolean = FALSE; |
| } |
| |
| dbus_message_iter_append_dict_entry(&dict, "bonded", |
| DBUS_TYPE_BOOLEAN, &boolean); |
| |
| /* Trusted */ |
| boolean = read_trust(&src, addr_ptr, GLOBAL_TRUST); |
| dbus_message_iter_append_dict_entry(&dict, "trusted", |
| DBUS_TYPE_BOOLEAN, &boolean); |
| |
| /* Connected */ |
| if (g_slist_find_custom(adapter->active_conn, &dst, |
| active_conn_find_by_bdaddr)) |
| boolean = TRUE; |
| else |
| boolean = FALSE; |
| |
| dbus_message_iter_append_dict_entry(&dict, "connected", |
| DBUS_TYPE_BOOLEAN, &boolean); |
| |
| /* HCI Revision/Manufacturer/Version */ |
| create_name(filename, PATH_MAX, STORAGEDIR, |
| adapter->address, "manufacturers"); |
| |
| str = textfile_caseget(filename, addr_ptr); |
| if (!str) |
| goto done; |
| |
| if (sscanf(str, "%d %d %d", &compid, &ver, &subver) != 3) { |
| /* corrupted file data */ |
| free(str); |
| goto done; |
| } |
| |
| free(str); |
| |
| ptr = buf; |
| snprintf(buf, 16, "HCI 0x%X", subver); |
| dbus_message_iter_append_dict_entry(&dict, "revision", |
| DBUS_TYPE_STRING, &ptr); |
| |
| ptr = bt_compidtostr(compid); |
| dbus_message_iter_append_dict_entry(&dict, "manufacturer", |
| DBUS_TYPE_STRING, &ptr); |
| |
| str = lmp_vertostr(ver); |
| snprintf(buf, 64, "Bluetooth %s", str); |
| bt_free(str); |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, |
| adapter->address, "features"); |
| |
| str = textfile_caseget(filename, addr_ptr); |
| if (str) { |
| if (strlen(str) == 16) { |
| uint8_t features; |
| /* Getting the third byte */ |
| features = ((str[6] - 48) << 4) | (str[7] - 48); |
| if (features & (LMP_EDR_ACL_2M | LMP_EDR_ACL_3M)) |
| snprintf(buf, 64, "Bluetooth %s + EDR", ptr); |
| |
| } |
| free(str); |
| } |
| ptr = buf; |
| dbus_message_iter_append_dict_entry(&dict, "version", |
| DBUS_TYPE_STRING, &ptr); |
| |
| done: |
| dbus_message_iter_close_container(&iter, &dict); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_get_remote_svc(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| return get_remote_svc_rec(conn, msg, data, SDP_FORMAT_BINARY); |
| } |
| |
| static DBusMessage *adapter_get_remote_svc_xml(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| return get_remote_svc_rec(conn, msg, data, SDP_FORMAT_XML); |
| } |
| |
| static DBusMessage *adapter_get_remote_svc_handles(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| return get_remote_svc_handles(conn, msg, data); |
| } |
| |
| static DBusMessage *adapter_get_remote_svc_identifiers(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| return get_remote_svc_identifiers(conn, msg, data); |
| } |
| |
| static DBusMessage *adapter_finish_sdp_transact(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| return finish_remote_svc_transact(conn, msg, data); |
| } |
| |
| static DBusMessage *adapter_get_remote_version(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| DBusMessage *reply; |
| char filename[PATH_MAX + 1]; |
| char *addr_ptr, *str; |
| char *str_ver = NULL; |
| char info_array[64], *info = info_array; |
| int compid, ver, subver; |
| |
| memset(info_array, 0, 64); |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &addr_ptr, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| if (check_address(addr_ptr) < 0) |
| return invalid_args(msg); |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, |
| "manufacturers"); |
| |
| str = textfile_caseget(filename, addr_ptr); |
| if (!str) |
| return not_available(msg); |
| |
| if (sscanf(str, "%d %d %d", &compid, &ver, &subver) != 3) { |
| /* corrupted file data */ |
| free(str); |
| goto failed; |
| } |
| |
| free(str); |
| |
| str_ver = lmp_vertostr(ver); |
| |
| /* Default value */ |
| snprintf(info, 64, "Bluetooth %s", str_ver); |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, |
| "features"); |
| |
| str = textfile_caseget(filename, addr_ptr); |
| if (!str) |
| goto failed; |
| |
| /* Check if the data is not corrupted */ |
| if (strlen(str) == 16) { |
| uint8_t features; |
| /* Getting the third byte */ |
| features = ((str[6] - 48) << 4) | (str[7] - 48); |
| if (features & (LMP_EDR_ACL_2M | LMP_EDR_ACL_3M)) |
| snprintf(info, 64, "Bluetooth %s + EDR", str_ver); |
| } |
| |
| free(str); |
| |
| failed: |
| if (str_ver) |
| bt_free(str_ver); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_STRING, &info, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_get_remote_revision(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| DBusMessage *reply; |
| char filename[PATH_MAX + 1]; |
| char *addr_ptr, *str; |
| char info_array[16], *info = info_array; |
| int compid, ver, subver; |
| |
| memset(info_array, 0, 16); |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &addr_ptr, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| if (check_address(addr_ptr) < 0) |
| return invalid_args(msg); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, |
| "manufacturers"); |
| |
| str = textfile_caseget(filename, addr_ptr); |
| if (!str) |
| return not_available(msg); |
| |
| if (sscanf(str, "%d %d %d", &compid, &ver, &subver) == 3) |
| snprintf(info, 16, "HCI 0x%X", subver); |
| |
| free(str); |
| |
| dbus_message_append_args(reply, DBUS_TYPE_STRING, &info, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_get_remote_manufacturer(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| DBusMessage *reply; |
| char filename[PATH_MAX + 1]; |
| char *addr_ptr, *str; |
| char info_array[64], *info = info_array; |
| int compid, ver, subver; |
| |
| memset(info_array, 0, 64); |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &addr_ptr, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| if (check_address(addr_ptr) < 0) |
| return invalid_args(msg); |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, |
| "manufacturers"); |
| |
| str = textfile_caseget(filename, addr_ptr); |
| if (!str) |
| return not_available(msg); |
| |
| if (sscanf(str, "%d %d %d", &compid, &ver, &subver) == 3) |
| info = bt_compidtostr(compid); |
| |
| free(str); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_STRING, &info, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_get_remote_company(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| DBusMessage *reply; |
| bdaddr_t bdaddr; |
| char oui[9], *str_bdaddr, *tmp; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &str_bdaddr, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| str2ba(str_bdaddr, &bdaddr); |
| ba2oui(&bdaddr, oui); |
| |
| tmp = ouitocomp(oui); |
| if (!tmp) |
| return not_available(msg); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) { |
| free(tmp); |
| return NULL; |
| } |
| |
| dbus_message_append_args(reply, DBUS_TYPE_STRING, &tmp, |
| DBUS_TYPE_INVALID); |
| |
| free(tmp); |
| |
| return reply; |
| } |
| |
| static DBusMessage *get_remote_class(DBusConnection *conn, DBusMessage *msg, |
| void *data, uint32_t *class) |
| { |
| struct adapter *adapter = data; |
| char *addr_peer; |
| bdaddr_t local, peer; |
| int ecode; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &addr_peer, |
| DBUS_TYPE_INVALID)) { |
| return invalid_args(msg); |
| } |
| |
| if (check_address(addr_peer) < 0) |
| return invalid_args(msg); |
| |
| str2ba(addr_peer, &peer); |
| str2ba(adapter->address, &local); |
| |
| ecode = read_remote_class(&local, &peer, class); |
| if (ecode < 0) |
| return not_available(msg); |
| |
| return NULL; |
| } |
| |
| static DBusMessage *adapter_get_remote_major_class(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| DBusMessage *reply; |
| const char *major_class; |
| uint32_t class; |
| |
| reply = get_remote_class(conn, msg, data, &class); |
| if (reply) |
| return reply; |
| |
| major_class = major_class_str(class); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_STRING, &major_class, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_get_remote_minor_class(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| DBusMessage *reply; |
| const char *minor_class; |
| uint32_t class; |
| |
| reply = get_remote_class(conn, msg, data, &class); |
| if (reply) |
| return reply; |
| |
| minor_class = minor_class_str(class); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_STRING, &minor_class, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static void append_class_string(const char *class, DBusMessageIter *iter) |
| { |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &class); |
| } |
| |
| static DBusMessage *adapter_get_remote_service_cls(DBusConnection *conn, |
| DBusMessage *msg, |
| void *data) |
| { |
| DBusMessage *reply; |
| DBusMessageIter iter, array_iter; |
| GSList *service_classes; |
| uint32_t class; |
| |
| reply = get_remote_class(conn, msg, data, &class); |
| if (reply) |
| return reply; |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| service_classes = service_classes_str(class); |
| |
| dbus_message_iter_init_append(reply, &iter); |
| dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, |
| DBUS_TYPE_STRING_AS_STRING, &array_iter); |
| |
| g_slist_foreach(service_classes, (GFunc) append_class_string, |
| &array_iter); |
| |
| dbus_message_iter_close_container(&iter, &array_iter); |
| |
| g_slist_free(service_classes); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_get_remote_class(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| DBusMessage *reply; |
| uint32_t class; |
| |
| reply = get_remote_class(conn, msg, data, &class); |
| if (reply) |
| return reply; |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_UINT32, &class, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_get_remote_features(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| char filename[PATH_MAX + 1]; |
| struct adapter *adapter = data; |
| DBusMessage *reply = NULL; |
| DBusMessageIter iter, array_iter; |
| uint8_t features[8], *ptr = features; |
| const char *addr; |
| char *str; |
| int i; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &addr, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| if (check_address(addr) < 0) |
| return invalid_args(msg); |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "features"); |
| |
| str = textfile_caseget(filename, addr); |
| if (!str) |
| return not_available(msg); |
| |
| memset(features, 0, sizeof(features)); |
| for (i = 0; i < sizeof(features); i++) { |
| char tmp[3]; |
| |
| memcpy(tmp, str + (i * 2), 2); |
| tmp[2] = '\0'; |
| |
| features[i] = (uint8_t) strtol(tmp, NULL, 16); |
| } |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) { |
| free(str); |
| return NULL; |
| } |
| |
| dbus_message_iter_init_append(reply, &iter); |
| dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, |
| DBUS_TYPE_BYTE_AS_STRING, &array_iter); |
| |
| dbus_message_iter_append_fixed_array(&array_iter, |
| DBUS_TYPE_BYTE, &ptr, sizeof(features)); |
| |
| dbus_message_iter_close_container(&iter, &array_iter); |
| |
| free(str); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_get_remote_name(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| char filename[PATH_MAX + 1]; |
| struct adapter *adapter = data; |
| DBusMessage *reply = NULL; |
| const char *peer_addr; |
| bdaddr_t peer_bdaddr; |
| char *str; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &peer_addr, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| if (check_address(peer_addr) < 0) |
| return invalid_args(msg); |
| |
| /* check if it is in the cache */ |
| create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "names"); |
| |
| str = textfile_caseget(filename, peer_addr); |
| |
| if (str) { |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) { |
| free(str); |
| return NULL; |
| } |
| |
| /* send the cached name */ |
| dbus_message_append_args(reply, DBUS_TYPE_STRING, &str, |
| DBUS_TYPE_INVALID); |
| |
| free(str); |
| return reply; |
| } |
| |
| if (!adapter->up) |
| return adapter_not_ready(msg); |
| |
| /* If the discover process is not running, return an error */ |
| if (!adapter->discov_active && !adapter->pdiscov_active) |
| return not_available(msg); |
| |
| /* Queue the request when there is a discovery running */ |
| str2ba(peer_addr, &peer_bdaddr); |
| found_device_add(&adapter->found_devices, &peer_bdaddr, 0, NAME_REQUIRED); |
| |
| return g_dbus_create_error(msg, |
| ERROR_INTERFACE ".RequestDeferred", |
| "Request Deferred"); |
| } |
| |
| static DBusMessage *adapter_get_remote_alias(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| DBusMessage *reply; |
| char str[249], *str_ptr = str, *addr_ptr; |
| bdaddr_t bdaddr; |
| int ecode; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &addr_ptr, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| if (check_address(addr_ptr) < 0) |
| return invalid_args(msg); |
| |
| str2ba(addr_ptr, &bdaddr); |
| |
| ecode = get_device_alias(adapter->dev_id, &bdaddr, str, sizeof(str)); |
| if (ecode < 0) |
| return not_available(msg); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_set_remote_alias(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| char *alias, *addr, *old_path, *new_path; |
| bdaddr_t bdaddr; |
| int ecode; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &addr, |
| DBUS_TYPE_STRING, &alias, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| if ((strlen(alias) == 0) || (check_address(addr) < 0)) { |
| error("Alias change failed: Invalid parameter"); |
| return invalid_args(msg); |
| } |
| |
| str2ba(addr, &bdaddr); |
| |
| ecode = set_device_alias(adapter->dev_id, &bdaddr, alias); |
| if (ecode < 0) |
| return failed_strerror(msg, -ecode); |
| |
| resolve_paths(msg, &old_path, &new_path); |
| |
| g_dbus_emit_signal(conn, old_path, |
| ADAPTER_INTERFACE, "RemoteAliasChanged", |
| DBUS_TYPE_STRING, &addr, |
| DBUS_TYPE_STRING, &alias, |
| DBUS_TYPE_INVALID); |
| |
| if (new_path) { |
| struct device *device; |
| |
| device = adapter_find_device(adapter, addr); |
| if (device) { |
| dbus_connection_emit_property_changed(conn, |
| device->path, DEVICE_INTERFACE, |
| "Alias", DBUS_TYPE_STRING, &alias); |
| } |
| } |
| |
| g_free(old_path); |
| g_free(new_path); |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static DBusMessage *adapter_clear_remote_alias(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| char *addr_ptr; |
| bdaddr_t bdaddr; |
| int ecode, had_alias = 1; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &addr_ptr, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| if (check_address(addr_ptr) < 0) { |
| error("Alias clear failed: Invalid parameter"); |
| return invalid_args(msg); |
| } |
| |
| str2ba(addr_ptr, &bdaddr); |
| |
| ecode = get_device_alias(adapter->dev_id, &bdaddr, NULL, 0); |
| if (ecode == -ENXIO) |
| had_alias = 0; |
| |
| ecode = set_device_alias(adapter->dev_id, &bdaddr, NULL); |
| if (ecode < 0) |
| return failed_strerror(msg, -ecode); |
| |
| if (had_alias) |
| g_dbus_emit_signal(conn, dbus_message_get_path(msg), |
| ADAPTER_INTERFACE, |
| "RemoteAliasCleared", |
| DBUS_TYPE_STRING, &addr_ptr, |
| DBUS_TYPE_INVALID); |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static DBusMessage *adapter_last_seen(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| DBusMessage *reply; |
| char filename[PATH_MAX + 1]; |
| char *addr_ptr, *str; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &addr_ptr, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| if (check_address(addr_ptr) < 0) |
| return invalid_args(msg); |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, |
| "lastseen"); |
| |
| str = textfile_caseget(filename, addr_ptr); |
| if (!str) |
| return not_available(msg); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) { |
| free(str); |
| return NULL; |
| } |
| |
| dbus_message_append_args(reply, DBUS_TYPE_STRING, &str, |
| DBUS_TYPE_INVALID); |
| |
| free(str); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_last_used(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| DBusMessage *reply; |
| char filename[PATH_MAX + 1]; |
| char *addr_ptr, *str; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &addr_ptr, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| if (check_address(addr_ptr) < 0) |
| return invalid_args(msg); |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, |
| "lastused"); |
| |
| str = textfile_caseget(filename, addr_ptr); |
| if (!str) |
| return not_available(msg); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) { |
| free(str); |
| return NULL; |
| } |
| |
| dbus_message_append_args(reply, DBUS_TYPE_STRING, &str, |
| DBUS_TYPE_INVALID); |
| |
| free(str); |
| |
| return reply; |
| } |
| |
| gboolean dc_pending_timeout_handler(void *data) |
| { |
| int dd; |
| struct adapter *adapter = data; |
| struct pending_dc_info *pending_dc = adapter->pending_dc; |
| DBusMessage *reply; |
| |
| dd = hci_open_dev(adapter->dev_id); |
| |
| if (dd < 0) { |
| error_no_such_adapter(pending_dc->conn, |
| pending_dc->msg); |
| dc_pending_timeout_cleanup(adapter); |
| return FALSE; |
| } |
| |
| /* Send the HCI disconnect command */ |
| if (hci_disconnect(dd, htobs(pending_dc->conn_handle), |
| HCI_OE_USER_ENDED_CONNECTION, |
| 500) < 0) { |
| int err = errno; |
| error("Disconnect failed"); |
| error_failed_errno(pending_dc->conn, pending_dc->msg, err); |
| } else { |
| reply = dbus_message_new_method_return(pending_dc->msg); |
| if (reply) { |
| dbus_connection_send(pending_dc->conn, reply, NULL); |
| dbus_message_unref(reply); |
| } else |
| error("Failed to allocate disconnect reply"); |
| } |
| |
| hci_close_dev(dd); |
| dc_pending_timeout_cleanup(adapter); |
| |
| return FALSE; |
| } |
| |
| void dc_pending_timeout_cleanup(struct adapter *adapter) |
| { |
| dbus_connection_unref(adapter->pending_dc->conn); |
| dbus_message_unref(adapter->pending_dc->msg); |
| g_free(adapter->pending_dc); |
| adapter->pending_dc = NULL; |
| } |
| |
| static DBusMessage *adapter_dc_remote_device(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| GSList *l = adapter->active_conn; |
| const char *peer_addr; |
| bdaddr_t peer_bdaddr; |
| |
| if (!adapter->up) |
| return adapter_not_ready(msg); |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &peer_addr, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| if (check_address(peer_addr) < 0) |
| return invalid_args(msg); |
| |
| str2ba(peer_addr, &peer_bdaddr); |
| |
| l = g_slist_find_custom(l, &peer_bdaddr, active_conn_find_by_bdaddr); |
| if (!l) |
| return g_dbus_create_error(msg, |
| ERROR_INTERFACE ".NotConnected", |
| "Device not connected"); |
| |
| if (adapter->pending_dc) |
| return in_progress(msg, "Disconnection in progress"); |
| |
| adapter->pending_dc = g_new0(struct pending_dc_info, 1); |
| |
| /* Start waiting... */ |
| adapter->pending_dc->timeout_id = |
| g_timeout_add(DC_PENDING_TIMEOUT, |
| dc_pending_timeout_handler, |
| adapter); |
| |
| if (!adapter->pending_dc->timeout_id) { |
| g_free(adapter->pending_dc); |
| adapter->pending_dc = NULL; |
| return NULL; |
| } |
| |
| adapter->pending_dc->conn = dbus_connection_ref(conn); |
| adapter->pending_dc->msg = dbus_message_ref(msg); |
| adapter->pending_dc->conn_handle = |
| ((struct active_conn_info *) l->data)->handle; |
| |
| g_dbus_emit_signal(conn, dbus_message_get_path(msg), |
| ADAPTER_INTERFACE, |
| "RemoteDeviceDisconnectRequested", |
| DBUS_TYPE_STRING, &peer_addr, |
| DBUS_TYPE_INVALID); |
| |
| return NULL; |
| } |
| |
| static void reply_authentication_failure(struct bonding_request_info *bonding) |
| { |
| DBusMessage *reply; |
| int status; |
| |
| status = bonding->hci_status ? |
| bonding->hci_status : HCI_AUTHENTICATION_FAILURE; |
| |
| reply = new_authentication_return(bonding->msg, status); |
| if (reply) { |
| dbus_connection_send(bonding->conn, reply, NULL); |
| dbus_message_unref(reply); |
| } |
| } |
| |
| struct device *adapter_find_device(struct adapter *adapter, const char *dest) |
| { |
| struct device *device; |
| GSList *l; |
| |
| if (!adapter) |
| return NULL; |
| |
| l = g_slist_find_custom(adapter->devices, |
| dest, (GCompareFunc) device_address_cmp); |
| if (!l) |
| return NULL; |
| |
| device = l->data; |
| |
| return device; |
| } |
| |
| struct device *adapter_create_device(DBusConnection *conn, |
| struct adapter *adapter, const char *address) |
| { |
| struct device *device; |
| |
| debug("adapter_create_device(%s)", address); |
| |
| device = device_create(conn, adapter, address, NULL); |
| if (!device) |
| return NULL; |
| |
| device->temporary = TRUE; |
| |
| adapter->devices = g_slist_append(adapter->devices, device); |
| |
| return device; |
| } |
| |
| static DBusMessage *remove_bonding(DBusConnection *conn, DBusMessage *msg, |
| const char *address, void *data) |
| { |
| struct adapter *adapter = data; |
| struct device *device; |
| char path[MAX_PATH_LENGTH], filename[PATH_MAX + 1]; |
| char *str; |
| bdaddr_t src, dst; |
| GSList *l; |
| int dev, err; |
| gboolean paired; |
| |
| str2ba(adapter->address, &src); |
| str2ba(address, &dst); |
| |
| dev = hci_open_dev(adapter->dev_id); |
| if (dev < 0 && msg) |
| return no_such_adapter(msg); |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, |
| "linkkeys"); |
| |
| /* textfile_del doesn't return an error when the key is not found */ |
| str = textfile_caseget(filename, address); |
| paired = str ? TRUE : FALSE; |
| g_free(str); |
| |
| if (!paired && msg) { |
| hci_close_dev(dev); |
| return g_dbus_create_error(msg, |
| ERROR_INTERFACE ".DoesNotExist", |
| "Bonding does not exist"); |
| } |
| |
| /* Delete the link key from storage */ |
| if (textfile_casedel(filename, address) < 0 && msg) { |
| hci_close_dev(dev); |
| err = errno; |
| return failed_strerror(msg, err); |
| } |
| |
| /* Delete the link key from the Bluetooth chip */ |
| hci_delete_stored_link_key(dev, &dst, 0, 1000); |
| |
| /* find the connection */ |
| l = g_slist_find_custom(adapter->active_conn, &dst, |
| active_conn_find_by_bdaddr); |
| if (l) { |
| struct active_conn_info *con = l->data; |
| /* Send the HCI disconnect command */ |
| if ((hci_disconnect(dev, htobs(con->handle), |
| HCI_OE_USER_ENDED_CONNECTION, 500) < 0) |
| && msg){ |
| int err = errno; |
| error("Disconnect failed"); |
| hci_close_dev(dev); |
| return failed_strerror(msg, err); |
| } |
| } |
| |
| hci_close_dev(dev); |
| |
| if (paired) { |
| snprintf(path, MAX_PATH_LENGTH, BASE_PATH "/hci%d", |
| adapter->dev_id); |
| g_dbus_emit_signal(conn, path, |
| ADAPTER_INTERFACE, "BondingRemoved", |
| DBUS_TYPE_STRING, &address, |
| DBUS_TYPE_INVALID); |
| } |
| |
| device = adapter_find_device(adapter, address); |
| if (!device) |
| goto proceed; |
| |
| if (paired) { |
| gboolean paired = FALSE; |
| dbus_connection_emit_property_changed(conn, device->path, |
| DEVICE_INTERFACE, "Paired", |
| DBUS_TYPE_BOOLEAN, &paired); |
| } |
| |
| proceed: |
| if(!msg) |
| goto done; |
| |
| return dbus_message_new_method_return(msg); |
| |
| done: |
| return NULL; |
| } |
| |
| |
| void adapter_remove_device(DBusConnection *conn, struct adapter *adapter, |
| struct device *device) |
| { |
| bdaddr_t src; |
| char path[MAX_PATH_LENGTH]; |
| |
| str2ba(adapter->address, &src); |
| delete_entry(&src, "profiles", device->address); |
| |
| remove_bonding(conn, NULL, device->address, adapter); |
| |
| if (!device->temporary) { |
| snprintf(path, MAX_PATH_LENGTH, "/hci%d", adapter->dev_id); |
| g_dbus_emit_signal(conn, path, |
| ADAPTER_INTERFACE, |
| "DeviceRemoved", |
| DBUS_TYPE_OBJECT_PATH, &device->path, |
| DBUS_TYPE_INVALID); |
| } |
| |
| if (device->agent) { |
| agent_destroy(device->agent, FALSE); |
| device->agent = NULL; |
| } |
| |
| adapter->devices = g_slist_remove(adapter->devices, device); |
| |
| device_remove(conn, device); |
| } |
| |
| struct device *adapter_get_device(DBusConnection *conn, |
| struct adapter *adapter, const gchar *address) |
| { |
| struct device *device; |
| |
| debug("adapter_get_device(%s)", address); |
| |
| if (!adapter) |
| return NULL; |
| |
| device = adapter_find_device(adapter, address); |
| if (device) |
| return device; |
| |
| return adapter_create_device(conn, adapter, address); |
| } |
| |
| void remove_pending_device(struct adapter *adapter) |
| { |
| struct device *device; |
| char address[18]; |
| |
| ba2str(&adapter->bonding->bdaddr, address); |
| device = adapter_find_device(adapter, address); |
| if (!device) |
| return; |
| |
| if (device->temporary) |
| adapter_remove_device(adapter->bonding->conn, adapter, device); |
| } |
| |
| static gboolean create_bonding_conn_complete(GIOChannel *io, GIOCondition cond, |
| struct adapter *adapter) |
| { |
| struct hci_request rq; |
| auth_requested_cp cp; |
| evt_cmd_status rp; |
| struct l2cap_conninfo cinfo; |
| socklen_t len; |
| int sk, dd, ret; |
| |
| if (!adapter->bonding) { |
| /* If we come here it implies a bug somewhere */ |
| debug("create_bonding_conn_complete: no pending bonding!"); |
| g_io_channel_close(io); |
| g_io_channel_unref(io); |
| return FALSE; |
| } |
| |
| if (cond & G_IO_NVAL) { |
| error_authentication_canceled(adapter->bonding->conn, |
| adapter->bonding->msg); |
| goto cleanup; |
| } |
| |
| if (cond & (G_IO_HUP | G_IO_ERR)) { |
| debug("Hangup or error on bonding IO channel"); |
| |
| if (!adapter->bonding->auth_active) |
| error_connection_attempt_failed(adapter->bonding->conn, |
| adapter->bonding->msg, |
| ENETDOWN); |
| else |
| reply_authentication_failure(adapter->bonding); |
| |
| goto failed; |
| } |
| |
| sk = g_io_channel_unix_get_fd(io); |
| |
| len = sizeof(ret); |
| if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &ret, &len) < 0) { |
| error("Can't get socket error: %s (%d)", |
| strerror(errno), errno); |
| error_failed_errno(adapter->bonding->conn, adapter->bonding->msg, |
| errno); |
| goto failed; |
| } |
| |
| if (ret != 0) { |
| if (adapter->bonding->auth_active) |
| reply_authentication_failure(adapter->bonding); |
| else |
| error_connection_attempt_failed(adapter->bonding->conn, |
| adapter->bonding->msg, |
| ret); |
| goto failed; |
| } |
| |
| len = sizeof(cinfo); |
| if (getsockopt(sk, SOL_L2CAP, L2CAP_CONNINFO, &cinfo, &len) < 0) { |
| error("Can't get connection info: %s (%d)", |
| strerror(errno), errno); |
| error_failed_errno(adapter->bonding->conn, adapter->bonding->msg, |
| errno); |
| goto failed; |
| } |
| |
| dd = hci_open_dev(adapter->dev_id); |
| if (dd < 0) { |
| error_no_such_adapter(adapter->bonding->conn, |
| adapter->bonding->msg); |
| goto failed; |
| } |
| |
| memset(&rp, 0, sizeof(rp)); |
| |
| memset(&cp, 0, sizeof(cp)); |
| cp.handle = htobs(cinfo.hci_handle); |
| |
| memset(&rq, 0, sizeof(rq)); |
| rq.ogf = OGF_LINK_CTL; |
| rq.ocf = OCF_AUTH_REQUESTED; |
| rq.cparam = &cp; |
| rq.clen = AUTH_REQUESTED_CP_SIZE; |
| rq.rparam = &rp; |
| rq.rlen = EVT_CMD_STATUS_SIZE; |
| rq.event = EVT_CMD_STATUS; |
| |
| if (hci_send_req(dd, &rq, 500) < 0) { |
| error("Unable to send HCI request: %s (%d)", |
| strerror(errno), errno); |
| error_failed_errno(adapter->bonding->conn, adapter->bonding->msg, |
| errno); |
| hci_close_dev(dd); |
| goto failed; |
| } |
| |
| if (rp.status) { |
| error("HCI_Authentication_Requested failed with status 0x%02x", |
| rp.status); |
| error_failed_errno(adapter->bonding->conn, adapter->bonding->msg, |
| bt_error(rp.status)); |
| hci_close_dev(dd); |
| goto failed; |
| } |
| |
| hci_close_dev(dd); |
| |
| adapter->bonding->auth_active = 1; |
| |
| adapter->bonding->io_id = g_io_add_watch(io, |
| G_IO_NVAL | G_IO_HUP | G_IO_ERR, |
| (GIOFunc) create_bonding_conn_complete, |
| adapter); |
| |
| return FALSE; |
| |
| failed: |
| g_io_channel_close(io); |
| remove_pending_device(adapter); |
| |
| cleanup: |
| g_dbus_remove_watch(adapter->bonding->conn, |
| adapter->bonding->listener_id); |
| bonding_request_free(adapter->bonding); |
| adapter->bonding = NULL; |
| |
| return FALSE; |
| } |
| |
| static void cancel_auth_request(struct pending_auth_info *auth, int dev_id) |
| { |
| int dd; |
| |
| if (auth->replied) |
| return; |
| |
| dd = hci_open_dev(dev_id); |
| if (dd < 0) { |
| error("hci_open_dev: %s (%d)", strerror(errno), errno); |
| return; |
| } |
| |
| switch (auth->type) { |
| case AUTH_TYPE_PINCODE: |
| hci_send_cmd(dd, OGF_LINK_CTL, OCF_PIN_CODE_NEG_REPLY, |
| 6, &auth->bdaddr); |
| break; |
| case AUTH_TYPE_CONFIRM: |
| hci_send_cmd(dd, OGF_LINK_CTL, OCF_USER_CONFIRM_NEG_REPLY, |
| 6, &auth->bdaddr); |
| break; |
| case AUTH_TYPE_PASSKEY: |
| hci_send_cmd(dd, OGF_LINK_CTL, OCF_USER_PASSKEY_NEG_REPLY, |
| 6, &auth->bdaddr); |
| break; |
| case AUTH_TYPE_NOTIFY: |
| /* User Notify doesn't require any reply */ |
| break; |
| } |
| |
| auth->replied = TRUE; |
| |
| hci_close_dev(dd); |
| } |
| |
| static void create_bond_req_exit(void *user_data) |
| { |
| struct adapter *adapter = user_data; |
| struct pending_auth_info *auth; |
| char path[MAX_PATH_LENGTH]; |
| |
| snprintf(path, sizeof(path), "%s/hci%d", BASE_PATH, adapter->dev_id); |
| |
| debug("CreateConnection requestor exited before bonding was completed"); |
| |
| cancel_passkey_agent_requests(adapter->passkey_agents, path, |
| &adapter->bonding->bdaddr); |
| release_passkey_agents(adapter, &adapter->bonding->bdaddr); |
| |
| auth = adapter_find_auth_request(adapter, &adapter->bonding->bdaddr); |
| if (auth) { |
| cancel_auth_request(auth, adapter->dev_id); |
| if (auth->agent) |
| agent_cancel(auth->agent); |
| adapter_remove_auth_request(adapter, &adapter->bonding->bdaddr); |
| } |
| |
| remove_pending_device(adapter); |
| |
| g_io_channel_close(adapter->bonding->io); |
| if (adapter->bonding->io_id) |
| g_source_remove(adapter->bonding->io_id); |
| bonding_request_free(adapter->bonding); |
| adapter->bonding = NULL; |
| } |
| |
| static DBusMessage *create_bonding(DBusConnection *conn, DBusMessage *msg, |
| const char *address, const char *agent_path, |
| uint8_t capability, void *data) |
| { |
| char filename[PATH_MAX + 1]; |
| char *str; |
| struct adapter *adapter = data; |
| struct bonding_request_info *bonding; |
| bdaddr_t bdaddr; |
| int sk; |
| |
| str2ba(address, &bdaddr); |
| |
| /* check if there is a pending discover: requested by D-Bus/non clients */ |
| if (adapter->discov_active || (adapter->pdiscov_active && !adapter->pinq_idle)) |
| return in_progress(msg, "Discover in progress"); |
| |
| pending_remote_name_cancel(adapter); |
| |
| if (adapter->bonding) |
| return in_progress(msg, "Bonding in progress"); |
| |
| if (adapter_find_auth_request(adapter, &bdaddr)) |
| return in_progress(msg, "Bonding in progress"); |
| |
| /* check if a link key already exists */ |
| create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, |
| "linkkeys"); |
| |
| str = textfile_caseget(filename, address); |
| if (str) { |
| free(str); |
| return g_dbus_create_error(msg, |
| ERROR_INTERFACE ".AlreadyExists", |
| "Bonding already exists"); |
| } |
| |
| sk = l2raw_connect(adapter->address, &bdaddr); |
| if (sk < 0) |
| return g_dbus_create_error(msg, |
| ERROR_INTERFACE ".ConnectionAttemptFailed", |
| "Connection attempt failed"); |
| |
| bonding = bonding_request_new(conn, msg, adapter, address, agent_path, |
| capability); |
| if (!bonding) { |
| close(sk); |
| return NULL; |
| } |
| |
| bonding->io = g_io_channel_unix_new(sk); |
| bonding->io_id = g_io_add_watch(bonding->io, |
| G_IO_OUT | G_IO_NVAL | G_IO_HUP | G_IO_ERR, |
| (GIOFunc) create_bonding_conn_complete, |
| adapter); |
| |
| bonding->listener_id = g_dbus_add_disconnect_watch(conn, |
| dbus_message_get_sender(msg), |
| create_bond_req_exit, adapter, |
| NULL); |
| |
| adapter->bonding = bonding; |
| |
| return NULL; |
| } |
| |
| static DBusMessage *adapter_create_bonding(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| char *address; |
| |
| if (!adapter->up) |
| return adapter_not_ready(msg); |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &address, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| if (check_address(address) < 0) |
| return invalid_args(msg); |
| |
| return create_bonding(conn, msg, address, NULL, |
| IO_CAPABILITY_INVALID, data); |
| } |
| static DBusMessage *adapter_cancel_bonding(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| const char *address; |
| bdaddr_t bdaddr; |
| struct bonding_request_info *bonding = adapter->bonding; |
| struct pending_auth_info *auth_req; |
| |
| if (!adapter->up) |
| return adapter_not_ready(msg); |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &address, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| if (check_address(address) < 0) |
| return invalid_args(msg); |
| |
| str2ba(address, &bdaddr); |
| if (!bonding || bacmp(&bonding->bdaddr, &bdaddr)) |
| return not_in_progress(msg, "Bonding is not in progress"); |
| |
| if (strcmp(dbus_message_get_sender(adapter->bonding->msg), |
| dbus_message_get_sender(msg))) |
| return not_authorized(msg); |
| |
| adapter->bonding->cancel = 1; |
| |
| auth_req = adapter_find_auth_request(adapter, &bdaddr); |
| if (auth_req) { |
| if (auth_req->replied) { |
| /* |
| * If disconnect can't be applied and the PIN code |
| * request was already replied it doesn't make sense |
| * cancel the remote passkey: return not authorized. |
| */ |
| g_io_channel_close(adapter->bonding->io); |
| return not_authorized(msg); |
| } |
| |
| cancel_auth_request(auth_req, adapter->dev_id); |
| if (auth_req->agent) |
| agent_cancel(auth_req->agent); |
| adapter_remove_auth_request(adapter, &bdaddr); |
| } |
| |
| g_io_channel_close(adapter->bonding->io); |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static DBusMessage *adapter_remove_bonding(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| char *address; |
| |
| if (!adapter->up) |
| return adapter_not_ready(msg); |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &address, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| if (check_address(address) < 0) |
| return invalid_args(msg); |
| |
| return remove_bonding(conn, msg, address, data); |
| } |
| |
| static DBusMessage *adapter_has_bonding(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| DBusMessage *reply; |
| char filename[PATH_MAX + 1]; |
| char *addr_ptr, *str; |
| dbus_bool_t result; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &addr_ptr, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| if (check_address(addr_ptr) < 0) |
| return invalid_args(msg); |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, |
| "linkkeys"); |
| |
| str = textfile_caseget(filename, addr_ptr); |
| if (str) { |
| result = TRUE; |
| free(str); |
| } else |
| result = FALSE; |
| |
| reply = dbus_message_new_method_return(msg); |
| |
| dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &result, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static void list_bondings_do_append(char *key, char *value, void *data) |
| { |
| DBusMessageIter *iter = data; |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &key); |
| } |
| |
| static DBusMessage *adapter_list_bondings(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| DBusMessageIter iter; |
| DBusMessageIter array_iter; |
| DBusMessage *reply; |
| char filename[PATH_MAX + 1]; |
| |
| if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) |
| return invalid_args(msg); |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, |
| "linkkeys"); |
| |
| reply = dbus_message_new_method_return(msg); |
| |
| dbus_message_iter_init_append(reply, &iter); |
| |
| dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, |
| DBUS_TYPE_STRING_AS_STRING, &array_iter); |
| |
| textfile_foreach(filename, list_bondings_do_append, &array_iter); |
| |
| dbus_message_iter_close_container(&iter, &array_iter); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_get_pin_code_length(DBusConnection *conn, |
| DBusMessage *msg, |
| void *data) |
| { |
| struct adapter *adapter = data; |
| DBusMessage *reply; |
| bdaddr_t local, peer; |
| char *addr_ptr; |
| uint8_t length; |
| int len; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &addr_ptr, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| if (check_address(addr_ptr) < 0) |
| return invalid_args(msg); |
| |
| str2ba(adapter->address, &local); |
| |
| str2ba(addr_ptr, &peer); |
| |
| len = read_pin_length(&local, &peer); |
| if (len < 0) |
| return g_dbus_create_error(msg, |
| ERROR_INTERFACE ".DoesNotExist", |
| "Record does not exist"); |
| |
| reply = dbus_message_new_method_return(msg); |
| |
| length = len; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_BYTE, &length, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_get_encryption_key_size(DBusConnection *conn, |
| DBusMessage *msg, |
| void *data) |
| { |
| struct adapter *adapter = data; |
| DBusMessage *reply; |
| bdaddr_t bdaddr; |
| char *addr_ptr; |
| uint8_t size; |
| int val; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &addr_ptr, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| if (check_address(addr_ptr) < 0) |
| return invalid_args(msg); |
| |
| str2ba(addr_ptr, &bdaddr); |
| |
| val = get_encryption_key_size(adapter->dev_id, &bdaddr); |
| if (val < 0) |
| return failed_strerror(msg, -val); |
| |
| reply = dbus_message_new_method_return(msg); |
| |
| size = val; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_BYTE, &size, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static void periodic_discover_req_exit(void *user_data) |
| { |
| struct adapter *adapter = user_data; |
| |
| debug("PeriodicDiscovery requestor exited"); |
| |
| /* Cleanup the discovered devices list and send the cmd to exit from |
| * periodic inquiry or cancel remote name request. The return value can |
| * be ignored. */ |
| |
| cancel_periodic_discovery(adapter); |
| } |
| |
| static DBusMessage *adapter_start_periodic(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| periodic_inquiry_cp cp; |
| struct hci_request rq; |
| struct adapter *adapter = data; |
| uint8_t lap[3] = { 0x33, 0x8b, 0x9e }; |
| uint8_t status; |
| int dd; |
| |
| if (!adapter->up) |
| return adapter_not_ready(msg); |
| |
| if (dbus_message_is_method_call(msg, ADAPTER_INTERFACE, |
| "StartPeriodicDiscovery")) { |
| if (!dbus_message_has_signature(msg, |
| DBUS_TYPE_INVALID_AS_STRING)) |
| return invalid_args(msg); |
| } |
| |
| if (adapter->discov_active || adapter->pdiscov_active) |
| return in_progress(msg, "Discover in progress"); |
| |
| pending_remote_name_cancel(adapter); |
| |
| dd = hci_open_dev(adapter->dev_id); |
| if (dd < 0) |
| return no_such_adapter(msg); |
| |
| memset(&cp, 0, sizeof(cp)); |
| memcpy(&cp.lap, lap, 3); |
| cp.max_period = htobs(24); |
| cp.min_period = htobs(16); |
| cp.length = 0x08; |
| cp.num_rsp = 0x00; |
| |
| memset(&rq, 0, sizeof(rq)); |
| rq.ogf = OGF_LINK_CTL; |
| rq.ocf = OCF_PERIODIC_INQUIRY; |
| rq.cparam = &cp; |
| rq.clen = PERIODIC_INQUIRY_CP_SIZE; |
| rq.rparam = &status; |
| rq.rlen = sizeof(status); |
| rq.event = EVT_CMD_COMPLETE; |
| |
| if (hci_send_req(dd, &rq, 1000) < 0) { |
| int err = errno; |
| error("Unable to start periodic inquiry: %s (%d)", |
| strerror(errno), errno); |
| hci_close_dev(dd); |
| return failed_strerror(msg, err); |
| } |
| |
| if (status) { |
| error("HCI_Periodic_Inquiry_Mode failed with status 0x%02x", |
| status); |
| hci_close_dev(dd); |
| return failed_strerror(msg, bt_error(status)); |
| } |
| |
| adapter->pdiscov_requestor = g_strdup(dbus_message_get_sender(msg)); |
| |
| if (adapter->pdiscov_resolve_names) |
| adapter->discov_type = PERIODIC_INQUIRY | RESOLVE_NAME; |
| else |
| adapter->discov_type = PERIODIC_INQUIRY; |
| |
| hci_close_dev(dd); |
| |
| /* track the request owner to cancel it automatically if the owner |
| * exits */ |
| adapter->pdiscov_listener = g_dbus_add_disconnect_watch(conn, |
| dbus_message_get_sender(msg), |
| periodic_discover_req_exit, |
| adapter, NULL); |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static DBusMessage *adapter_stop_periodic(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| int err; |
| |
| if (!adapter->up) |
| return adapter_not_ready(msg); |
| |
| if (dbus_message_is_method_call(msg, ADAPTER_INTERFACE, |
| "StopPeriodicDiscovery")) { |
| if (!dbus_message_has_signature(msg, |
| DBUS_TYPE_INVALID_AS_STRING)) |
| return invalid_args(msg); |
| } |
| |
| if (!adapter->pdiscov_active) |
| return g_dbus_create_error(msg, |
| ERROR_INTERFACE ".NotAuthorized", |
| "Not authorized"); |
| /* |
| * Cleanup the discovered devices list and send the cmd to exit |
| * from periodic inquiry mode or cancel remote name request. |
| */ |
| err = cancel_periodic_discovery(adapter); |
| if (err < 0) { |
| if (err == -ENODEV) |
| return no_such_adapter(msg); |
| |
| else |
| return failed_strerror(msg, -err); |
| } |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static DBusMessage *adapter_is_periodic(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| DBusMessage *reply; |
| struct adapter *adapter = data; |
| dbus_bool_t active = adapter->pdiscov_active; |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &active, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_set_pdiscov_resolve(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| dbus_bool_t resolve; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_BOOLEAN, &resolve, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| debug("SetPeriodicDiscoveryNameResolving(%s)", |
| resolve ? "TRUE" : "FALSE"); |
| |
| adapter->pdiscov_resolve_names = resolve; |
| |
| if (adapter->pdiscov_active) { |
| if (resolve) |
| adapter->discov_type |= RESOLVE_NAME; |
| else |
| adapter->discov_type &= ~RESOLVE_NAME; |
| } |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static DBusMessage *adapter_get_pdiscov_resolve(DBusConnection *conn, |
| DBusMessage *msg, |
| void *data) |
| { |
| DBusMessage *reply; |
| struct adapter *adapter = data; |
| dbus_bool_t resolve = adapter->pdiscov_resolve_names; |
| |
| if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) |
| return invalid_args(msg); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &resolve, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static void discover_devices_req_exit(void *user_data) |
| { |
| struct adapter *adapter = user_data; |
| |
| debug("DiscoverDevices requestor exited"); |
| |
| /* Cleanup the discovered devices list and send the command to cancel |
| * inquiry or cancel remote name request. The return can be ignored. */ |
| cancel_discovery(adapter); |
| } |
| |
| static DBusMessage *adapter_discover_devices(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| const char *method; |
| inquiry_cp cp; |
| evt_cmd_status rp; |
| struct hci_request rq; |
| struct adapter *adapter = data; |
| uint8_t lap[3] = { 0x33, 0x8b, 0x9e }; |
| int dd; |
| |
| if (!adapter->up) |
| return adapter_not_ready(msg); |
| |
| if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) |
| return invalid_args(msg); |
| |
| if (adapter->discov_active) |
| return in_progress(msg, "Discover in progress"); |
| |
| pending_remote_name_cancel(adapter); |
| |
| if (adapter->bonding) |
| return in_progress(msg, "Bonding in progress"); |
| |
| dd = hci_open_dev(adapter->dev_id); |
| if (dd < 0) |
| return no_such_adapter(msg); |
| |
| memset(&cp, 0, sizeof(cp)); |
| memcpy(&cp.lap, lap, 3); |
| cp.length = 0x08; |
| cp.num_rsp = 0x00; |
| |
| memset(&rq, 0, sizeof(rq)); |
| rq.ogf = OGF_LINK_CTL; |
| rq.ocf = OCF_INQUIRY; |
| rq.cparam = &cp; |
| rq.clen = INQUIRY_CP_SIZE; |
| rq.rparam = &rp; |
| rq.rlen = EVT_CMD_STATUS_SIZE; |
| rq.event = EVT_CMD_STATUS; |
| |
| if (hci_send_req(dd, &rq, 500) < 0) { |
| int err = errno; |
| error("Unable to start inquiry: %s (%d)", |
| strerror(errno), errno); |
| hci_close_dev(dd); |
| return failed_strerror(msg, err); |
| } |
| |
| if (rp.status) { |
| error("HCI_Inquiry command failed with status 0x%02x", |
| rp.status); |
| hci_close_dev(dd); |
| return failed_strerror(msg, bt_error(rp.status)); |
| } |
| |
| method = dbus_message_get_member(msg); |
| if (strcmp("DiscoverDevicesWithoutNameResolving", method) == 0) |
| adapter->discov_type |= STD_INQUIRY; |
| else |
| adapter->discov_type |= (STD_INQUIRY | RESOLVE_NAME); |
| |
| adapter->discov_requestor = g_strdup(dbus_message_get_sender(msg)); |
| |
| |
| hci_close_dev(dd); |
| |
| /* track the request owner to cancel it automatically if the owner |
| * exits */ |
| adapter->discov_listener = g_dbus_add_disconnect_watch(conn, |
| dbus_message_get_sender(msg), |
| discover_devices_req_exit, |
| adapter, NULL); |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static DBusMessage *adapter_cancel_discovery(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| int err; |
| |
| if (!adapter->up) |
| return adapter_not_ready(msg); |
| |
| if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) |
| return invalid_args(msg); |
| |
| /* is there discover pending? or discovery cancel was requested |
| * previously */ |
| if (!adapter->discov_active || adapter->discovery_cancel) |
| return g_dbus_create_error(msg, |
| ERROR_INTERFACE ".NotAuthorized", |
| "Not Authorized"); |
| |
| /* only the discover requestor can cancel the inquiry process */ |
| if (!adapter->discov_requestor || |
| strcmp(adapter->discov_requestor, dbus_message_get_sender(msg))) |
| return g_dbus_create_error(msg, |
| ERROR_INTERFACE ".NotAuthorized", |
| "Not Authorized"); |
| |
| /* Cleanup the discovered devices list and send the cmd to cancel |
| * inquiry or cancel remote name request */ |
| err = cancel_discovery(adapter); |
| if (err < 0) { |
| if (err == -ENODEV) |
| return no_such_adapter(msg); |
| else |
| return failed_strerror(msg, -err); |
| } |
| |
| /* Reply before send DiscoveryCompleted */ |
| adapter->discovery_cancel = dbus_message_ref(msg); |
| |
| return NULL; |
| } |
| |
| struct remote_device_list_t { |
| GSList *list; |
| time_t time; |
| }; |
| |
| static void list_remote_devices_do_append(char *key, char *value, void *data) |
| { |
| struct remote_device_list_t *param = data; |
| char *address; |
| struct tm date; |
| |
| if (g_slist_find_custom(param->list, key, (GCompareFunc) strcasecmp)) |
| return; |
| |
| if (param->time){ |
| strptime(value, "%Y-%m-%d %H:%M:%S %Z", &date); |
| if (difftime(mktime(&date), param->time) < 0) |
| return; |
| } |
| |
| address = g_strdup(key); |
| |
| param->list = g_slist_append(param->list, address); |
| } |
| |
| static void remote_devices_do_append(void *data, void *user_data) |
| { |
| DBusMessageIter *iter = user_data; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &data); |
| } |
| |
| static DBusMessage *adapter_list_remote_devices(DBusConnection *conn, |
| DBusMessage *msg, |
| void *data) |
| { |
| struct adapter *adapter = data; |
| DBusMessageIter iter; |
| DBusMessageIter array_iter; |
| DBusMessage *reply; |
| char filename[PATH_MAX + 1]; |
| struct remote_device_list_t param = { NULL, 0 }; |
| |
| if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) |
| return invalid_args(msg); |
| |
| /* Add Bonded devices to the list */ |
| create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "linkkeys"); |
| textfile_foreach(filename, list_remote_devices_do_append, ¶m); |
| |
| /* Add Trusted devices to the list */ |
| create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "trusts"); |
| textfile_foreach(filename, list_remote_devices_do_append, ¶m); |
| |
| /* Add Last Used devices to the list */ |
| create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "lastused"); |
| textfile_foreach(filename, list_remote_devices_do_append, ¶m); |
| |
| reply = dbus_message_new_method_return(msg); |
| |
| dbus_message_iter_init_append(reply, &iter); |
| |
| dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, |
| DBUS_TYPE_STRING_AS_STRING, &array_iter); |
| |
| g_slist_foreach(param.list, remote_devices_do_append, &array_iter); |
| |
| g_slist_foreach(param.list, (GFunc) free, NULL); |
| g_slist_free(param.list); |
| |
| dbus_message_iter_close_container(&iter, &array_iter); |
| |
| return reply; |
| } |
| |
| static void append_connected(struct active_conn_info *dev, GSList *list) |
| { |
| char address[18]; |
| |
| ba2str(&dev->bdaddr, address); |
| if (g_slist_find_custom(list, address, (GCompareFunc) strcasecmp)) |
| return; |
| |
| list = g_slist_append(list, g_strdup(address)); |
| } |
| |
| static DBusMessage *adapter_list_recent_remote_devices(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| struct tm date; |
| const char *string; |
| DBusMessageIter iter; |
| DBusMessageIter array_iter; |
| DBusMessage *reply; |
| char filename[PATH_MAX + 1]; |
| struct remote_device_list_t param = { NULL, 0 }; |
| int len; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &string, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| /* Date format is "YYYY-MM-DD HH:MM:SS GMT" */ |
| len = strlen(string); |
| if (len && (strptime(string, "%Y-%m-%d %H:%M:%S", &date) == NULL)) |
| return invalid_args(msg); |
| |
| /* Bonded and trusted: mandatory entries(no matter the date/time) */ |
| create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "linkkeys"); |
| textfile_foreach(filename, list_remote_devices_do_append, ¶m); |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "trusts"); |
| textfile_foreach(filename, list_remote_devices_do_append, ¶m); |
| |
| /* Last seen/used: append devices since the date informed */ |
| if (len) |
| param.time = mktime(&date); |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "lastseen"); |
| textfile_foreach(filename, list_remote_devices_do_append, ¶m); |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "lastused"); |
| textfile_foreach(filename, list_remote_devices_do_append, ¶m); |
| |
| /* connected: force appending connected devices, lastused might not match */ |
| g_slist_foreach(adapter->active_conn, (GFunc) append_connected, param.list); |
| |
| reply = dbus_message_new_method_return(msg); |
| |
| dbus_message_iter_init_append(reply, &iter); |
| |
| dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, |
| DBUS_TYPE_STRING_AS_STRING, &array_iter); |
| |
| g_slist_foreach(param.list, remote_devices_do_append, &array_iter); |
| |
| g_slist_foreach(param.list, (GFunc) free, NULL); |
| g_slist_free(param.list); |
| |
| dbus_message_iter_close_container(&iter, &array_iter); |
| |
| return reply; |
| } |
| |
| |
| static DBusMessage *adapter_set_trusted(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| bdaddr_t local; |
| const char *address; |
| char *old_path, *new_path; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &address, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| if (check_address(address) < 0) |
| return invalid_args(msg); |
| |
| str2ba(adapter->address, &local); |
| |
| write_trust(&local, address, GLOBAL_TRUST, TRUE); |
| |
| resolve_paths(msg, &old_path, &new_path); |
| |
| g_dbus_emit_signal(conn, old_path, |
| ADAPTER_INTERFACE, "TrustAdded", |
| DBUS_TYPE_STRING, &address, |
| DBUS_TYPE_INVALID); |
| |
| if (new_path) { |
| struct device *device; |
| gboolean trust = TRUE; |
| |
| device = adapter_find_device(adapter, address); |
| if (device) { |
| dbus_connection_emit_property_changed(conn, |
| device->path, DEVICE_INTERFACE, |
| "Trusted", DBUS_TYPE_BOOLEAN, &trust); |
| } |
| } |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static DBusMessage *adapter_is_trusted(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| DBusMessage *reply; |
| const char *address; |
| dbus_bool_t trusted; |
| bdaddr_t local; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &address, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| if (check_address(address) < 0) |
| return invalid_args(msg); |
| |
| str2ba(adapter->address, &local); |
| |
| trusted = read_trust(&local, address, GLOBAL_TRUST); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_append_args(reply, |
| DBUS_TYPE_BOOLEAN, &trusted, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static DBusMessage *adapter_remove_trust(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| const char *address; |
| bdaddr_t local; |
| char *old_path, *new_path; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &address, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| if (check_address(address) < 0) |
| return invalid_args(msg); |
| |
| str2ba(adapter->address, &local); |
| |
| write_trust(&local, address, GLOBAL_TRUST, FALSE); |
| |
| resolve_paths(msg, &old_path, &new_path); |
| |
| g_dbus_emit_signal(conn, old_path, |
| ADAPTER_INTERFACE, "TrustRemoved", |
| DBUS_TYPE_STRING, &address, |
| DBUS_TYPE_INVALID); |
| |
| if (new_path) { |
| struct device *device; |
| gboolean trust = FALSE; |
| |
| device = adapter_find_device(adapter, address); |
| if (device) { |
| dbus_connection_emit_property_changed(conn, |
| device->path, DEVICE_INTERFACE, |
| "Trusted", DBUS_TYPE_BOOLEAN, &trust); |
| } |
| } |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static DBusMessage *adapter_list_trusts(DBusConnection *conn, |
| DBusMessage *msg, |
| void *data) |
| { |
| struct adapter *adapter = data; |
| DBusMessage *reply; |
| GSList *trusts, *l; |
| char **addrs; |
| bdaddr_t local; |
| int len; |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| str2ba(adapter->address, &local); |
| |
| trusts = list_trusts(&local, GLOBAL_TRUST); |
| |
| addrs = g_new(char *, g_slist_length(trusts)); |
| |
| for (l = trusts, len = 0; l; l = l->next, len++) |
| addrs[len] = l->data; |
| |
| dbus_message_append_args(reply, |
| DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, |
| &addrs, len, |
| DBUS_TYPE_INVALID); |
| |
| g_free(addrs); |
| g_slist_foreach(trusts, (GFunc) g_free, NULL); |
| g_slist_free(trusts); |
| |
| return reply; |
| } |
| |
| static DBusMessage *get_properties(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| const char *property; |
| DBusMessage *reply; |
| DBusMessageIter iter; |
| DBusMessageIter dict; |
| bdaddr_t ba; |
| char str[249]; |
| |
| if (check_address(adapter->address) < 0) |
| return adapter_not_ready(msg); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_iter_init_append(reply, &iter); |
| |
| dbus_message_iter_open_container(&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, &dict); |
| |
| /* Address */ |
| property = adapter->address; |
| dbus_message_iter_append_dict_entry(&dict, "Address", |
| DBUS_TYPE_STRING, &property); |
| |
| /* Name */ |
| memset(str, 0, sizeof(str)); |
| property = str; |
| str2ba(adapter->address, &ba); |
| |
| if (!read_local_name(&ba, str)) |
| dbus_message_iter_append_dict_entry(&dict, "Name", |
| DBUS_TYPE_STRING, &property); |
| |
| /* Mode */ |
| property = mode2str(adapter->mode); |
| |
| dbus_message_iter_append_dict_entry(&dict, "Mode", |
| DBUS_TYPE_STRING, &property); |
| |
| /* DiscoverableTimeout */ |
| dbus_message_iter_append_dict_entry(&dict, "DiscoverableTimeout", |
| DBUS_TYPE_UINT32, &adapter->discov_timeout); |
| |
| /* PeriodicDiscovery */ |
| dbus_message_iter_append_dict_entry(&dict, "PeriodicDiscovery", |
| DBUS_TYPE_BOOLEAN, &adapter->pdiscov_active); |
| |
| dbus_message_iter_close_container(&iter, &dict); |
| |
| return reply; |
| } |
| |
| static DBusMessage *set_property(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| DBusMessageIter iter; |
| DBusMessageIter sub; |
| const char *property; |
| |
| if (!dbus_message_iter_init(msg, &iter)) |
| return invalid_args(msg); |
| |
| if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) |
| return invalid_args(msg); |
| |
| dbus_message_iter_get_basic(&iter, &property); |
| dbus_message_iter_next(&iter); |
| |
| if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) |
| return invalid_args(msg); |
| dbus_message_iter_recurse(&iter, &sub); |
| |
| if (g_str_equal("Name", property)) { |
| const char *name; |
| |
| if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) |
| return invalid_args(msg); |
| dbus_message_iter_get_basic(&sub, &name); |
| |
| return set_name(conn, msg, name, data); |
| } else if (g_str_equal("DiscoverableTimeout", property)) { |
| uint32_t timeout; |
| |
| if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT32) |
| return invalid_args(msg); |
| dbus_message_iter_get_basic(&sub, &timeout); |
| |
| return set_discoverable_timeout(conn, msg, timeout, data); |
| } else if (g_str_equal("PeriodicDiscovery", property)) { |
| dbus_bool_t value; |
| |
| if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN) |
| return invalid_args(msg); |
| dbus_message_iter_get_basic(&sub, &value); |
| |
| if (value) |
| return adapter_start_periodic(conn, msg, data); |
| else |
| return adapter_stop_periodic(conn, msg, data); |
| } else if (g_str_equal("Mode", property)) { |
| const char *mode; |
| |
| if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) |
| return invalid_args(msg); |
| |
| dbus_message_iter_get_basic(&sub, &mode); |
| |
| adapter->global_mode = str2mode(adapter->address, mode); |
| |
| if (adapter->global_mode == adapter->mode) |
| return dbus_message_new_method_return(msg); |
| |
| if (adapter->sessions && adapter->global_mode < adapter->mode) |
| return confirm_mode(conn, msg, mode, data); |
| |
| return set_mode(conn, msg, str2mode(adapter->address, mode), |
| data); |
| } |
| |
| return invalid_args(msg); |
| } |
| |
| static void session_exit(void *data) |
| { |
| struct mode_req *req = data; |
| struct adapter *adapter = req->adapter; |
| |
| adapter->sessions = g_slist_remove(adapter->sessions, req); |
| |
| if (!adapter->sessions) { |
| debug("Falling back to '%s' mode", mode2str(adapter->global_mode)); |
| /* FIXME: fallback to previous mode |
| set_mode(req->conn, req->msg, adapter->global_mode, adapter); |
| */ |
| } |
| dbus_connection_unref(req->conn); |
| dbus_message_unref(req->msg); |
| g_free(req); |
| } |
| |
| static DBusMessage *request_mode(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| const char *mode; |
| struct adapter *adapter = data; |
| struct mode_req *req; |
| uint8_t new_mode; |
| int ret; |
| |
| if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &mode, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| new_mode = str2mode(adapter->address, mode); |
| if (new_mode != MODE_CONNECTABLE && new_mode != MODE_DISCOVERABLE) |
| return invalid_args(msg); |
| |
| if (!adapter->agent) |
| return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", |
| "No agent registered"); |
| |
| if (g_slist_find_custom(adapter->sessions, msg, |
| (GCompareFunc) find_session)) |
| return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", |
| "Mode already requested"); |
| |
| req = g_new0(struct mode_req, 1); |
| req->adapter = adapter; |
| req->conn = dbus_connection_ref(conn); |
| req->msg = dbus_message_ref(msg); |
| req->mode = new_mode; |
| req->id = g_dbus_add_disconnect_watch(conn, |
| dbus_message_get_sender(msg), |
| session_exit, req, NULL); |
| |
| if (!adapter->sessions) |
| adapter->global_mode = adapter->mode; |
| adapter->sessions = g_slist_append(adapter->sessions, req); |
| |
| /* No need to change mode */ |
| if (adapter->mode >= new_mode) |
| return dbus_message_new_method_return(msg); |
| |
| ret = agent_confirm_mode_change(adapter->agent, mode, confirm_mode_cb, |
| req); |
| if (ret < 0) { |
| dbus_message_unref(req->msg); |
| g_dbus_remove_watch(req->conn, req->id); |
| dbus_connection_unref(req->conn); |
| g_free(req); |
| return invalid_args(msg); |
| } |
| |
| return NULL; |
| } |
| |
| static DBusMessage *release_mode(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| GSList *l; |
| |
| l = g_slist_find_custom(adapter->sessions, msg, |
| (GCompareFunc) find_session); |
| if (!l) |
| return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", |
| "No Mode to release"); |
| |
| session_exit(l->data); |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static DBusMessage *list_devices(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| DBusMessage *reply; |
| GSList *l; |
| DBusMessageIter iter; |
| DBusMessageIter array_iter; |
| |
| if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) |
| return invalid_args(msg); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_iter_init_append(reply, &iter); |
| dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, |
| DBUS_TYPE_OBJECT_PATH_AS_STRING, &array_iter); |
| |
| for (l = adapter->devices; l; l = l->next) { |
| struct device *device = l->data; |
| |
| if (device->temporary) |
| continue; |
| |
| dbus_message_iter_append_basic(&array_iter, |
| DBUS_TYPE_OBJECT_PATH, &device->path); |
| } |
| |
| dbus_message_iter_close_container(&iter, &array_iter); |
| |
| return reply; |
| } |
| |
| static DBusMessage *create_device(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| struct device *device; |
| const gchar *address; |
| |
| if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address, |
| DBUS_TYPE_INVALID) == FALSE) |
| return invalid_args(msg); |
| |
| if (check_address(address) < 0) |
| return invalid_args(msg); |
| |
| if (adapter_find_device(adapter, address)) |
| return g_dbus_create_error(msg, |
| ERROR_INTERFACE ".AlreadyExists", |
| "Device already exists"); |
| |
| debug("create_device(%s)", address); |
| |
| device = device_create(conn, adapter, address, NULL); |
| if (!device) |
| return NULL; |
| |
| device->temporary = FALSE; |
| |
| device_browse(device, conn, msg, FALSE, 0); |
| |
| adapter->devices = g_slist_append(adapter->devices, device); |
| |
| return NULL; |
| } |
| |
| static uint8_t parse_io_capability(const char *capability) |
| { |
| if (g_str_equal(capability, "")) |
| return IO_CAPABILITY_DISPLAYYESNO; |
| if (g_str_equal(capability, "DisplayOnly")) |
| return IO_CAPABILITY_DISPLAYONLY; |
| if (g_str_equal(capability, "DisplayYesNo")) |
| return IO_CAPABILITY_DISPLAYYESNO; |
| if (g_str_equal(capability, "KeyboardOnly")) |
| return IO_CAPABILITY_KEYBOARDONLY; |
| if (g_str_equal(capability, "NoInputOutput")) |
| return IO_CAPABILITY_NOINPUTOUTPUT; |
| return IO_CAPABILITY_INVALID; |
| } |
| |
| static DBusMessage *create_paired_device(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| const gchar *address, *agent_path, *capability; |
| uint8_t cap; |
| |
| if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address, |
| DBUS_TYPE_OBJECT_PATH, &agent_path, |
| DBUS_TYPE_STRING, &capability, |
| DBUS_TYPE_INVALID) == FALSE) |
| return invalid_args(msg); |
| |
| if (check_address(address) < 0) |
| return invalid_args(msg); |
| |
| cap = parse_io_capability(capability); |
| if (cap == IO_CAPABILITY_INVALID) |
| return invalid_args(msg); |
| |
| return create_bonding(conn, msg, address, agent_path, cap, data); |
| } |
| |
| static gint device_path_cmp(struct device *device, const gchar *path) |
| { |
| return strcasecmp(device->path, path); |
| } |
| |
| static DBusMessage *remove_device(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| struct device *device; |
| const char *path; |
| GSList *l; |
| |
| if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, |
| DBUS_TYPE_INVALID) == FALSE) |
| return invalid_args(msg); |
| |
| l = g_slist_find_custom(adapter->devices, |
| path, (GCompareFunc) device_path_cmp); |
| if (!l) |
| return g_dbus_create_error(msg, |
| ERROR_INTERFACE ".DoesNotExist", |
| "Device does not exist"); |
| device = l->data; |
| |
| if (device->temporary || device->discov_active) |
| return g_dbus_create_error(msg, |
| ERROR_INTERFACE ".DoesNotExist", |
| "Device creation in progress"); |
| |
| adapter_remove_device(conn, adapter, device); |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static DBusMessage *find_device(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| struct device *device; |
| DBusMessage *reply; |
| const gchar *address; |
| GSList *l; |
| |
| if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| l = g_slist_find_custom(adapter->devices, |
| address, (GCompareFunc) device_address_cmp); |
| if (!l) |
| return g_dbus_create_error(msg, |
| ERROR_INTERFACE ".DoesNotExist", |
| "Device does not exist"); |
| |
| device = l->data; |
| |
| if (device->temporary) |
| return g_dbus_create_error(msg, |
| ERROR_INTERFACE ".DoesNotExist", |
| "Device creation in progress"); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_append_args(reply, |
| DBUS_TYPE_OBJECT_PATH, &device->path, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static void agent_removed(struct agent *agent, struct adapter *adapter) |
| { |
| struct pending_auth_info *auth; |
| GSList *l; |
| |
| adapter->agent = NULL; |
| |
| l = g_slist_find_custom(adapter->auth_reqs, agent, |
| auth_info_agent_cmp); |
| if (!l) |
| return; |
| |
| auth = l->data; |
| auth->agent = NULL; |
| } |
| |
| static DBusMessage *register_agent(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| const char *path, *name, *capability; |
| struct agent *agent; |
| struct adapter *adapter = data; |
| uint8_t cap; |
| |
| if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, |
| DBUS_TYPE_STRING, &capability, DBUS_TYPE_INVALID)) |
| return NULL; |
| |
| if (adapter->agent) |
| return g_dbus_create_error(msg, |
| ERROR_INTERFACE ".AlreadyExists", |
| "Agent already exists"); |
| |
| cap = parse_io_capability(capability); |
| if (cap == IO_CAPABILITY_INVALID) |
| return invalid_args(msg); |
| |
| name = dbus_message_get_sender(msg); |
| |
| agent = agent_create(adapter, name, path, cap, |
| (agent_remove_cb) agent_removed, adapter); |
| if (!agent) |
| return g_dbus_create_error(msg, |
| ERROR_INTERFACE ".Failed", |
| "Failed to create a new agent"); |
| |
| adapter->agent = agent; |
| |
| debug("Agent registered for hci%d at %s:%s", adapter->dev_id, name, |
| path); |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static DBusMessage *unregister_agent(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| const char *path, *name; |
| struct adapter *adapter = data; |
| |
| if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, |
| DBUS_TYPE_INVALID)) |
| return NULL; |
| |
| name = dbus_message_get_sender(msg); |
| |
| if (!adapter->agent || !agent_matches(adapter->agent, name, path)) |
| return g_dbus_create_error(msg, |
| ERROR_INTERFACE ".DoesNotExist", |
| "No such agent"); |
| |
| agent_destroy(adapter->agent, FALSE); |
| adapter->agent = NULL; |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static DBusMessage *add_service_record(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| DBusMessage *reply; |
| const char *sender, *record; |
| dbus_uint32_t handle; |
| bdaddr_t src; |
| int err; |
| |
| if (dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &record, DBUS_TYPE_INVALID) == FALSE) |
| return NULL; |
| |
| sender = dbus_message_get_sender(msg); |
| str2ba(adapter->address, &src); |
| err = add_xml_record(conn, sender, &src, record, &handle); |
| if (err < 0) |
| return failed_strerror(msg, err); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_UINT32, &handle, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static DBusMessage *update_service_record(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| bdaddr_t src; |
| |
| str2ba(adapter->address, &src); |
| |
| return update_xml_record(conn, msg, &src); |
| } |
| |
| static DBusMessage *remove_service_record(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| dbus_uint32_t handle; |
| const char *sender; |
| |
| if (dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &handle, |
| DBUS_TYPE_INVALID) == FALSE) |
| return NULL; |
| |
| sender = dbus_message_get_sender(msg); |
| |
| if (remove_record(conn, sender, handle) < 0) |
| return not_available(msg); |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static DBusMessage *request_authorization(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| /* FIXME implement the request */ |
| |
| return NULL; |
| } |
| |
| static DBusMessage *cancel_authorization(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| /* FIXME implement cancel request */ |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| /* BlueZ 4.0 API */ |
| static GDBusMethodTable adapter_methods[] = { |
| { "GetProperties", "", "a{sv}",get_properties }, |
| { "SetProperty", "sv", "", set_property, |
| G_DBUS_METHOD_FLAG_ASYNC}, |
| { "RequestMode", "s", "", request_mode, |
| G_DBUS_METHOD_FLAG_ASYNC}, |
| { "ReleaseMode", "", "", release_mode }, |
| { "DiscoverDevices", "", "", adapter_discover_devices}, |
| { "CancelDiscovery", "", "", adapter_cancel_discovery, |
| G_DBUS_METHOD_FLAG_ASYNC}, |
| { "ListDevices", "", "ao", list_devices }, |
| { "CreateDevice", "s", "o", create_device, |
| G_DBUS_METHOD_FLAG_ASYNC}, |
| { "CreatePairedDevice", "sos", "o", create_paired_device, |
| G_DBUS_METHOD_FLAG_ASYNC}, |
| { "RemoveDevice", "o", "", remove_device }, |
| { "FindDevice", "s", "o", find_device }, |
| { "RegisterAgent", "os", "", register_agent }, |
| { "UnregisterAgent", "o", "", unregister_agent }, |
| { "AddServiceRecord", "s", "u", add_service_record }, |
| { "UpdateServiceRecord","us", "", update_service_record }, |
| { "RemoveServiceRecord","u", "", remove_service_record }, |
| { "RequestAuthorization","su", "", request_authorization, |
| G_DBUS_METHOD_FLAG_ASYNC}, |
| { "CancelAuthorization","", "", cancel_authorization }, |
| { } |
| }; |
| |
| /* Deprecated */ |
| static GDBusMethodTable old_adapter_methods[] = { |
| { "GetInfo", "", "a{sv}", |
| adapter_get_info }, |
| { "GetAddress", "", "s", |
| adapter_get_address }, |
| { "GetVersion", "", "s", |
| adapter_get_version }, |
| { "GetRevision", "", "s", |
| adapter_get_revision }, |
| { "GetManufacturer", "", "s", |
| adapter_get_manufacturer }, |
| { "GetCompany", "", "s", |
| adapter_get_company }, |
| { "ListAvailableModes", "", "as", |
| adapter_list_modes }, |
| { "GetMode", "", "s", |
| adapter_get_mode }, |
| { "SetMode", "s", "", |
| adapter_set_mode }, |
| { "GetDiscoverableTimeout", "", "u", |
| adapter_get_discoverable_to }, |
| { "SetDiscoverableTimeout", "u", "", |
| adapter_set_discoverable_to }, |
| { "IsConnectable", "", "b", |
| adapter_is_connectable }, |
| { "IsDiscoverable", "", "b", |
| adapter_is_discoverable }, |
| { "IsConnected", "s", "b", |
| adapter_is_connected }, |
| { "ListConnections", "", "as", |
| adapter_list_connections }, |
| { "GetMajorClass", "", "s", |
| adapter_get_major_class }, |
| { "ListAvailableMinorClasses", "", "as", |
| adapter_list_minor_classes }, |
| { "GetMinorClass", "", "s", |
| adapter_get_minor_class }, |
| { "SetMinorClass", "s", "", |
| adapter_set_minor_class }, |
| { "GetServiceClasses", "", "as", |
| adapter_get_service_classes }, |
| { "GetName", "", "s", |
| adapter_get_name }, |
| { "SetName", "s", "", |
| adapter_set_name }, |
| |
| { "GetRemoteInfo", "s", "a{sv}", |
| adapter_get_remote_info }, |
| { "GetRemoteServiceRecord", "su", "ay", |
| adapter_get_remote_svc, G_DBUS_METHOD_FLAG_ASYNC }, |
| { "GetRemoteServiceRecordAsXML", "su", "s", |
| adapter_get_remote_svc_xml, G_DBUS_METHOD_FLAG_ASYNC }, |
| { "GetRemoteServiceHandles", "ss", "au", |
| adapter_get_remote_svc_handles, G_DBUS_METHOD_FLAG_ASYNC }, |
| { "GetRemoteServiceIdentifiers", "s", "as", |
| adapter_get_remote_svc_identifiers, G_DBUS_METHOD_FLAG_ASYNC }, |
| { "FinishRemoteServiceTransaction", "s", "", |
| adapter_finish_sdp_transact }, |
| { "GetRemoteVersion", "s", "s", |
| adapter_get_remote_version }, |
| { "GetRemoteRevision", "s", "s", |
| adapter_get_remote_revision }, |
| { "GetRemoteManufacturer", "s", "s", |
| adapter_get_remote_manufacturer }, |
| { "GetRemoteCompany", "s", "s", |
| adapter_get_remote_company }, |
| { "GetRemoteMajorClass", "s", "s", |
| adapter_get_remote_major_class }, |
| { "GetRemoteMinorClass", "s", "s", |
| adapter_get_remote_minor_class }, |
| { "GetRemoteServiceClasses", "s", "as", |
| adapter_get_remote_service_cls }, |
| { "GetRemoteClass", "s", "u", |
| adapter_get_remote_class }, |
| { "GetRemoteFeatures", "s", "ay", |
| adapter_get_remote_features }, |
| { "GetRemoteName", "s", "s", |
| adapter_get_remote_name }, |
| { "GetRemoteAlias", "s", "s", |
| adapter_get_remote_alias }, |
| { "SetRemoteAlias", "ss", "", |
| adapter_set_remote_alias }, |
| { "ClearRemoteAlias", "s", "", |
| adapter_clear_remote_alias }, |
| |
| { "LastSeen", "s", "s", |
| adapter_last_seen }, |
| { "LastUsed", "s", "s", |
| adapter_last_used }, |
| |
| { "DisconnectRemoteDevice", "s", "", |
| adapter_dc_remote_device, G_DBUS_METHOD_FLAG_ASYNC}, |
| |
| { "CreateBonding", "s", "", |
| adapter_create_bonding, G_DBUS_METHOD_FLAG_ASYNC}, |
| { "CancelBondingProcess", "s", "", |
| adapter_cancel_bonding }, |
| { "RemoveBonding", "s", "", |
| adapter_remove_bonding }, |
| { "HasBonding", "s", "b", |
| adapter_has_bonding }, |
| { "ListBondings", "", "as", |
| adapter_list_bondings }, |
| { "GetPinCodeLength", "s", "y", |
| adapter_get_pin_code_length }, |
| { "GetEncryptionKeySize", "s", "y", |
| adapter_get_encryption_key_size }, |
| |
| { "StartPeriodicDiscovery", "", "", |
| adapter_start_periodic }, |
| { "StopPeriodicDiscovery", "", "", |
| adapter_stop_periodic }, |
| { "IsPeriodicDiscovery", "", "b", |
| adapter_is_periodic }, |
| { "SetPeriodicDiscoveryNameResolving", "b", "", |
| adapter_set_pdiscov_resolve }, |
| { "GetPeriodicDiscoveryNameResolving", "", "b", |
| adapter_get_pdiscov_resolve }, |
| { "DiscoverDevices", "", "", |
| adapter_discover_devices }, |
| { "CancelDiscovery", "", "", |
| adapter_cancel_discovery, G_DBUS_METHOD_FLAG_ASYNC }, |
| { "DiscoverDevicesWithoutNameResolving","", "", |
| adapter_discover_devices }, |
| { "ListRemoteDevices", "", "as", |
| adapter_list_remote_devices }, |
| { "ListRecentRemoteDevices", "s", "as", |
| adapter_list_recent_remote_devices}, |
| |
| { "SetTrusted", "s", "", |
| adapter_set_trusted }, |
| { "IsTrusted", "s", "b", |
| adapter_is_trusted }, |
| { "RemoveTrust", "s", "", |
| adapter_remove_trust }, |
| { "ListTrusts", "", "as", |
| adapter_list_trusts }, |
| |
| { } |
| }; |
| |
| /* BlueZ 4.X */ |
| static GDBusSignalTable adapter_signals[] = { |
| { "DiscoveryStarted", "" }, |
| { "DiscoveryCompleted", "" }, |
| { "DeviceCreated", "o" }, |
| { "DeviceRemoved", "o" }, |
| { "DeviceFound", "sa{sv}" }, |
| { "PropertyChanged", "sv" }, |
| { "DeviceDisappeared", "s" }, |
| { } |
| }; |
| |
| /* Deprecated */ |
| static GDBusSignalTable old_adapter_signals[] = { |
| { "DiscoveryStarted", "" }, |
| { "DiscoveryCompleted", "" }, |
| { "ModeChanged", "s" }, |
| { "DiscoverableTimeoutChanged", "u" }, |
| { "MinorClassChanged", "s" }, |
| { "NameChanged", "s" }, |
| { "PeriodicDiscoveryStarted", "" }, |
| { "PeriodicDiscoveryStopped", "" }, |
| { "RemoteDeviceFound", "sun" }, |
| { "RemoteDeviceDisappeared", "s" }, |
| { "RemoteClassUpdated", "su" }, |
| { "RemoteNameUpdated", "ss" }, |
| { "RemoteNameFailed", "s" }, |
| { "RemoteNameRequested", "s" }, |
| { "RemoteAliasChanged", "ss" }, |
| { "RemoteAliasCleared", "s" }, |
| { "RemoteDeviceConnected", "s" }, |
| { "RemoteDeviceDisconnectRequested", "s" }, |
| { "RemoteDeviceDisconnected", "s" }, |
| { "RemoteIdentifiersUpdated", "sas" }, |
| { "BondingCreated", "s" }, |
| { "BondingRemoved", "s" }, |
| { "TrustAdded", "s" }, |
| { "TrustRemoved", "s" }, |
| { } |
| }; |
| |
| dbus_bool_t adapter_init(DBusConnection *conn, |
| const char *path, struct adapter *adapter) |
| { |
| if (hcid_dbus_use_experimental()) |
| g_dbus_register_interface(conn, path + ADAPTER_PATH_INDEX, |
| ADAPTER_INTERFACE, adapter_methods, |
| adapter_signals, NULL, adapter, NULL); |
| |
| return g_dbus_register_interface(conn, |
| path, ADAPTER_INTERFACE, |
| old_adapter_methods, old_adapter_signals, |
| NULL, adapter, NULL); |
| } |
| |
| dbus_bool_t adapter_cleanup(DBusConnection *conn, const char *path) |
| { |
| return g_dbus_unregister_interface(conn, path, ADAPTER_INTERFACE); |
| } |
| |
| const char *major_class_str(uint32_t class) |
| { |
| uint8_t index = (class >> 8) & 0x1F; |
| |
| if (index > 8) |
| return major_cls[9]; /* uncategorized */ |
| |
| return major_cls[index]; |
| } |
| |
| const char *minor_class_str(uint32_t class) |
| { |
| uint8_t major_index = (class >> 8) & 0x1F; |
| uint8_t minor_index; |
| |
| switch (major_index) { |
| case 1: /* computer */ |
| minor_index = (class >> 2) & 0x3F; |
| if (minor_index < NUM_ELEMENTS(computer_minor_cls)) |
| return computer_minor_cls[minor_index]; |
| else |
| return ""; |
| case 2: /* phone */ |
| minor_index = (class >> 2) & 0x3F; |
| if (minor_index < NUM_ELEMENTS(phone_minor_cls)) |
| return phone_minor_cls[minor_index]; |
| return ""; |
| case 3: /* access point */ |
| minor_index = (class >> 5) & 0x07; |
| if (minor_index < NUM_ELEMENTS(access_point_minor_cls)) |
| return access_point_minor_cls[minor_index]; |
| else |
| return ""; |
| case 4: /* audio/video */ |
| minor_index = (class >> 2) & 0x3F; |
| if (minor_index < NUM_ELEMENTS(audio_video_minor_cls)) |
| return audio_video_minor_cls[minor_index]; |
| else |
| return ""; |
| case 5: /* peripheral */ |
| minor_index = (class >> 6) & 0x03; |
| if (minor_index < NUM_ELEMENTS(peripheral_minor_cls)) |
| return peripheral_minor_cls[minor_index]; |
| else |
| return ""; |
| case 6: /* imaging */ |
| { |
| uint8_t shift_minor = 0; |
| |
| minor_index = (class >> 4) & 0x0F; |
| while (shift_minor < (sizeof(imaging_minor_cls) / sizeof(*imaging_minor_cls))) { |
| if (((minor_index >> shift_minor) & 0x01) == 0x01) |
| return imaging_minor_cls[shift_minor]; |
| shift_minor++; |
| } |
| } |
| break; |
| case 7: /* wearable */ |
| minor_index = (class >> 2) & 0x3F; |
| if (minor_index < NUM_ELEMENTS(wearable_minor_cls)) |
| return wearable_minor_cls[minor_index]; |
| else |
| return ""; |
| case 8: /* toy */ |
| minor_index = (class >> 2) & 0x3F; |
| if (minor_index < NUM_ELEMENTS(toy_minor_cls)) |
| return toy_minor_cls[minor_index]; |
| else |
| return ""; |
| } |
| |
| return ""; |
| } |
| |
| GSList *service_classes_str(uint32_t class) |
| { |
| uint8_t services = class >> 16; |
| GSList *l = NULL; |
| int i; |
| |
| for (i = 0; i < (sizeof(service_cls) / sizeof(*service_cls)); i++) { |
| if (!(services & (1 << i))) |
| continue; |
| |
| l = g_slist_append(l, (void *) service_cls[i]); |
| } |
| |
| return l; |
| } |