| /* |
| * |
| * 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 <sys/ioctl.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 "logging.h" |
| #include "textfile.h" |
| |
| #include "hcid.h" |
| #include "sdpd.h" |
| #include "sdp-xml.h" |
| #include "manager.h" |
| #include "adapter.h" |
| #include "device.h" |
| #include "dbus-common.h" |
| #include "dbus-hci.h" |
| #include "error.h" |
| #include "glib-helper.h" |
| #include "agent.h" |
| #include "storage.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 |
| |
| #define check_address(address) bachk(address) |
| |
| static DBusConnection *connection = NULL; |
| static GSList *adapter_drivers = NULL; |
| |
| struct session_req { |
| struct btd_adapter *adapter; |
| DBusConnection *conn; /* Connection reference */ |
| DBusMessage *msg; /* Message reference */ |
| guint id; /* Listener id */ |
| uint8_t mode; /* Requested mode */ |
| int refcount; /* Session refcount */ |
| }; |
| |
| struct service_auth { |
| service_auth_cb cb; |
| void *user_data; |
| }; |
| |
| struct btd_adapter { |
| uint16_t dev_id; |
| int up; |
| char *path; /* adapter object path */ |
| bdaddr_t bdaddr; /* adapter Bluetooth Address */ |
| guint discov_timeout_id; /* discoverable timeout id */ |
| uint32_t discov_timeout; /* discoverable time(msec) */ |
| uint8_t scan_mode; /* scan mode: SCAN_DISABLED, SCAN_PAGE, SCAN_INQUIRY */ |
| uint8_t mode; /* off, connectable, discoverable, limited */ |
| uint8_t global_mode; /* last valid global mode */ |
| int state; /* standard inq, periodic inq, name resloving */ |
| GSList *found_devices; |
| GSList *oor_devices; /* out of range device list */ |
| DBusMessage *discovery_cancel; /* discovery cancel message request */ |
| GSList *passkey_agents; |
| struct agent *agent; /* For the new API */ |
| GSList *active_conn; |
| struct bonding_request_info *bonding; |
| GSList *auth_reqs; /* Received and replied HCI |
| authentication requests */ |
| GSList *devices; /* Devices structure pointers */ |
| GSList *mode_sessions; /* Request Mode sessions */ |
| GSList *disc_sessions; /* Discovery sessions */ |
| guint scheduler_id; /* Scheduler handle */ |
| |
| struct hci_dev dev; /* hci info */ |
| }; |
| |
| 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 DBusHandlerResult error_failed(DBusConnection *conn, |
| DBusMessage *msg, const char * desc) |
| { |
| return error_common_reply(conn, msg, ERROR_INTERFACE ".Failed", desc); |
| } |
| |
| static DBusHandlerResult error_failed_errno(DBusConnection *conn, |
| DBusMessage *msg, int err) |
| { |
| const char *desc = strerror(err); |
| |
| return error_failed(conn, msg, desc); |
| } |
| |
| static DBusHandlerResult error_connection_attempt_failed(DBusConnection *conn, |
| DBusMessage *msg, int err) |
| { |
| return error_common_reply(conn, msg, |
| ERROR_INTERFACE ".ConnectionAttemptFailed", |
| err > 0 ? strerror(err) : "Connection attempt failed"); |
| } |
| |
| static void bonding_request_free(struct bonding_request_info *bonding) |
| { |
| struct btd_device *device; |
| char address[18]; |
| struct agent *agent; |
| |
| if (!bonding) |
| return; |
| |
| if (bonding->msg) |
| dbus_message_unref(bonding->msg); |
| |
| if (bonding->conn) |
| dbus_connection_unref(bonding->conn); |
| |
| if (bonding->io) |
| g_io_channel_unref(bonding->io); |
| |
| ba2str(&bonding->bdaddr, address); |
| |
| device = adapter_find_device(bonding->adapter, address); |
| agent = device_get_agent(device); |
| |
| if (device && agent) { |
| agent_destroy(agent, FALSE); |
| device_set_agent(device, NULL); |
| } |
| |
| g_free(bonding); |
| } |
| |
| static int active_conn_find_by_bdaddr(const void *data, const void *user_data) |
| { |
| const struct active_conn_info *con = data; |
| const bdaddr_t *bdaddr = user_data; |
| |
| return bacmp(&con->bdaddr, bdaddr); |
| } |
| |
| static int active_conn_find_by_handle(const void *data, const void *user_data) |
| { |
| const struct active_conn_info *dev = data; |
| const uint16_t *handle = user_data; |
| |
| if (dev->handle == *handle) |
| return 0; |
| |
| return -1; |
| } |
| |
| static void send_out_of_range(const char *path, GSList *l) |
| { |
| while (l) { |
| const char *peer_addr = l->data; |
| |
| g_dbus_emit_signal(connection, path, |
| ADAPTER_INTERFACE, "DeviceDisappeared", |
| DBUS_TYPE_STRING, &peer_addr, |
| DBUS_TYPE_INVALID); |
| |
| l = l->next; |
| } |
| } |
| |
| static int found_device_cmp(const struct remote_dev_info *d1, |
| const struct remote_dev_info *d2) |
| { |
| int ret; |
| |
| if (bacmp(&d2->bdaddr, BDADDR_ANY)) { |
| ret = bacmp(&d1->bdaddr, &d2->bdaddr); |
| if (ret) |
| return ret; |
| } |
| |
| if (d2->name_status != NAME_ANY) { |
| ret = (d1->name_status - d2->name_status); |
| if (ret) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| 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; |
| } |
| |
| struct pending_auth_info *adapter_find_auth_request(struct btd_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 btd_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 btd_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 btd_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, |
| HCI_REQ_TIMEOUT) < 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 btd_device *device = user_data; |
| struct pending_auth_info *auth; |
| GSList *l; |
| struct btd_adapter *adapter; |
| |
| adapter = device_get_adapter(device); |
| device_set_agent(device, NULL); |
| |
| l = g_slist_find_custom(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 btd_adapter *adapter, |
| const char *address, |
| const char *agent_path, |
| uint8_t capability) |
| { |
| struct bonding_request_info *bonding; |
| struct btd_device *device; |
| const char *name = dbus_message_get_sender(msg); |
| struct agent *agent; |
| char addr[18]; |
| bdaddr_t bdaddr; |
| |
| debug("bonding_request_new(%s)", address); |
| |
| device = adapter_get_device(conn, adapter, address); |
| if (!device) |
| return NULL; |
| |
| device_get_address(device, &bdaddr); |
| ba2str(&bdaddr, addr); |
| |
| agent = agent_create(adapter, name, agent_path, |
| capability, |
| device_agent_removed, |
| device); |
| |
| device_set_agent(device, agent); |
| |
| debug("Temporary agent registered for hci%d/%s at %s:%s", |
| adapter->dev_id, addr, 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; |
| } |
| |
| static 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 get_mode(const bdaddr_t *bdaddr, 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) { |
| char onmode[14], srcaddr[18]; |
| |
| ba2str(bdaddr, srcaddr); |
| if (read_on_mode(srcaddr, onmode, sizeof(onmode)) < 0) |
| return MODE_CONNECTABLE; |
| |
| return get_mode(bdaddr, onmode); |
| } else |
| return MODE_UNKNOWN; |
| } |
| |
| static DBusMessage *set_mode(DBusConnection *conn, DBusMessage *msg, |
| uint8_t new_mode, void *data) |
| { |
| struct btd_adapter *adapter = data; |
| uint8_t scan_enable; |
| uint8_t current_scan = adapter->scan_mode; |
| 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 && |
| (main_opts.offmode == HCID_OFFMODE_NOSCAN || |
| (main_opts.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 && |
| main_opts.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->dev.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, HCI_REQ_TIMEOUT) < 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)) { |
| if (adapter->discov_timeout_id) { |
| g_source_remove(adapter->discov_timeout_id); |
| adapter->discov_timeout_id = 0; |
| } |
| |
| if (!adapter->mode_sessions && !adapter->discov_timeout) |
| adapter_set_discov_timeout(adapter, |
| adapter->discov_timeout * 1000); |
| } |
| } |
| done: |
| write_device_mode(&adapter->bdaddr, mode); |
| |
| hci_close_dev(dd); |
| |
| adapter->mode = new_mode; |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static DBusMessage *set_powered(DBusConnection *conn, DBusMessage *msg, |
| gboolean powered, void *data) |
| { |
| struct btd_adapter *adapter = data; |
| uint8_t mode; |
| |
| mode = powered ? get_mode(&adapter->bdaddr, "on") : MODE_OFF; |
| |
| if (mode == adapter->mode) |
| return dbus_message_new_method_return(msg); |
| |
| return set_mode(conn, msg, mode, data); |
| } |
| |
| static DBusMessage *set_discoverable(DBusConnection *conn, DBusMessage *msg, |
| gboolean discoverable, void *data) |
| { |
| struct btd_adapter *adapter = data; |
| const char *strmode; |
| uint8_t mode; |
| |
| strmode = discoverable ? "discoverable" : "connectable"; |
| mode = get_mode(&adapter->bdaddr, strmode); |
| |
| if (mode == adapter->mode) |
| return dbus_message_new_method_return(msg); |
| |
| return set_mode(conn, msg, mode, data); |
| } |
| |
| static struct session_req *find_session(GSList *list, DBusMessage *msg) |
| { |
| GSList *l; |
| const char *sender = dbus_message_get_sender(msg); |
| |
| for (l = list; l; l = l->next) { |
| struct session_req *req = l->data; |
| const char *name = dbus_message_get_sender(req->msg); |
| |
| if (g_str_equal(name, sender)) |
| return req; |
| } |
| |
| return NULL; |
| } |
| |
| static void session_remove(struct session_req *req) |
| { |
| struct btd_adapter *adapter = req->adapter; |
| |
| if (req->mode) { |
| GSList *l; |
| uint8_t mode = adapter->global_mode; |
| |
| adapter->mode_sessions = g_slist_remove(adapter->mode_sessions, |
| req); |
| |
| for (l = adapter->mode_sessions; l; l = l->next) { |
| struct session_req *req = l->data; |
| |
| if (req->mode > mode) |
| mode = req->mode; |
| } |
| |
| if (mode == adapter->mode) |
| return; |
| |
| debug("Switching to '%s' mode", mode2str(mode)); |
| set_mode(req->conn, req->msg, mode, adapter); |
| } else { |
| adapter->disc_sessions = g_slist_remove(adapter->disc_sessions, |
| req); |
| |
| if (adapter->disc_sessions) |
| return; |
| |
| debug("Stopping discovery", mode2str(adapter->global_mode)); |
| |
| if (adapter->state & STD_INQUIRY) |
| cancel_discovery(adapter); |
| else if (adapter->scheduler_id) |
| g_source_remove(adapter->scheduler_id); |
| else |
| cancel_periodic_discovery(adapter); |
| } |
| |
| } |
| |
| static void session_free(struct session_req *req) |
| { |
| const char *sender = dbus_message_get_sender(req->msg); |
| |
| info("%s session %p with %s deactivated", |
| req->mode ? "Mode" : "Discovery", req, sender); |
| |
| session_remove(req); |
| |
| dbus_message_unref(req->msg); |
| dbus_connection_unref(req->conn); |
| g_free(req); |
| } |
| |
| static struct session_req *session_ref(struct session_req *req) |
| { |
| req->refcount++; |
| |
| debug("session_ref(%p): ref=%d", req, req->refcount); |
| |
| return req; |
| } |
| |
| static void session_unref(struct session_req *req) |
| { |
| req->refcount--; |
| |
| debug("session_unref(%p): ref=%d", req, req->refcount); |
| |
| if (req->refcount) |
| return; |
| |
| if (req->id) |
| g_dbus_remove_watch(req->conn, req->id); |
| |
| session_free(req); |
| } |
| |
| static struct session_req *create_session(struct btd_adapter *adapter, |
| DBusConnection *conn, DBusMessage *msg, |
| uint8_t mode, GDBusWatchFunction cb) |
| { |
| struct session_req *req; |
| const char *sender = dbus_message_get_sender(msg); |
| |
| req = g_new0(struct session_req, 1); |
| req->adapter = adapter; |
| req->conn = dbus_connection_ref(conn); |
| req->msg = dbus_message_ref(msg); |
| req->mode = mode; |
| |
| if (cb) |
| req->id = g_dbus_add_disconnect_watch(conn, |
| dbus_message_get_sender(msg), |
| cb, req, NULL); |
| |
| info("%s session %p with %s activated", |
| req->mode ? "Mode" : "Discovery", req, sender); |
| |
| return session_ref(req); |
| } |
| |
| static void confirm_mode_cb(struct agent *agent, DBusError *err, void *data) |
| { |
| struct session_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 (!find_session(req->adapter->mode_sessions, req->msg)) |
| goto cleanup; |
| |
| return; |
| |
| cleanup: |
| session_unref(req); |
| } |
| |
| static DBusMessage *confirm_mode(DBusConnection *conn, DBusMessage *msg, |
| const char *mode, void *data) |
| { |
| struct btd_adapter *adapter = data; |
| struct session_req *req; |
| int ret; |
| uint8_t umode; |
| |
| if (!adapter->agent) |
| return dbus_message_new_method_return(msg); |
| |
| umode = get_mode(&adapter->bdaddr, mode); |
| |
| req = create_session(adapter, conn, msg, umode, NULL); |
| |
| ret = agent_confirm_mode_change(adapter->agent, mode, confirm_mode_cb, |
| req); |
| if (ret < 0) { |
| session_unref(req); |
| return invalid_args(msg); |
| } |
| |
| return NULL; |
| } |
| |
| static DBusMessage *set_discoverable_timeout(DBusConnection *conn, |
| DBusMessage *msg, |
| uint32_t timeout, |
| void *data) |
| { |
| struct btd_adapter *adapter = data; |
| const char *path; |
| |
| if (adapter->discov_timeout == timeout && timeout == 0) |
| return dbus_message_new_method_return(msg); |
| |
| if (adapter->discov_timeout_id) { |
| g_source_remove(adapter->discov_timeout_id); |
| adapter->discov_timeout_id = 0; |
| } |
| |
| if ((timeout != 0) && (adapter->scan_mode & SCAN_INQUIRY)) |
| adapter_set_discov_timeout(adapter, timeout * 1000); |
| |
| adapter->discov_timeout = timeout; |
| |
| write_discoverable_timeout(&adapter->bdaddr, timeout); |
| |
| path = dbus_message_get_path(msg); |
| |
| dbus_connection_emit_property_changed(conn, path, |
| ADAPTER_INTERFACE, |
| "DiscoverableTimeout", |
| DBUS_TYPE_UINT32, &timeout); |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static void update_ext_inquiry_response(int dd, struct hci_dev *dev) |
| { |
| uint8_t fec = 0, data[240]; |
| |
| if (!(dev->features[6] & LMP_EXT_INQ)) |
| return; |
| |
| memset(data, 0, sizeof(data)); |
| |
| if (dev->ssp_mode > 0) |
| create_ext_inquiry_response((char *) dev->name, data); |
| |
| if (hci_write_ext_inquiry_response(dd, fec, data, |
| HCI_REQ_TIMEOUT) < 0) |
| error("Can't write extended inquiry response: %s (%d)", |
| strerror(errno), errno); |
| } |
| |
| static int adapter_set_name(struct btd_adapter *adapter, const char *name) |
| { |
| struct hci_dev *dev = &adapter->dev; |
| int dd, err; |
| |
| write_local_name(&adapter->bdaddr, (char *) name); |
| |
| if (!adapter->up) |
| return 0; |
| |
| dd = hci_open_dev(adapter->dev_id); |
| if (dd < 0) { |
| err = errno; |
| error("Can't open device hci%d: %s (%d)", |
| adapter->dev_id, strerror(err), err); |
| return -err; |
| } |
| |
| if (hci_write_local_name(dd, name, HCI_REQ_TIMEOUT) < 0) { |
| err = errno; |
| error("Can't write name for hci%d: %s (%d)", |
| adapter->dev_id, strerror(err), err); |
| hci_close_dev(dd); |
| return -err; |
| } |
| |
| strncpy((char *) dev->name, name, 248); |
| |
| update_ext_inquiry_response(dd, dev); |
| |
| hci_close_dev(dd); |
| |
| return 0; |
| } |
| |
| static DBusMessage *set_name(DBusConnection *conn, DBusMessage *msg, |
| const char *name, void *data) |
| { |
| struct btd_adapter *adapter = data; |
| int ecode; |
| const char *path; |
| |
| if (!g_utf8_validate(name, -1, NULL)) { |
| error("Name change failed: the supplied name isn't valid UTF-8"); |
| return invalid_args(msg); |
| } |
| |
| ecode = adapter_set_name(adapter, name); |
| if (ecode < 0) |
| return failed_strerror(msg, -ecode); |
| |
| path = dbus_message_get_path(msg); |
| |
| dbus_connection_emit_property_changed(conn, path, |
| ADAPTER_INTERFACE, |
| "Name", DBUS_TYPE_STRING, |
| &name); |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| 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 btd_device *adapter_find_device(struct btd_adapter *adapter, const char *dest) |
| { |
| struct btd_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 btd_device *adapter_create_device(DBusConnection *conn, |
| struct btd_adapter *adapter, const char *address) |
| { |
| struct btd_device *device; |
| |
| debug("adapter_create_device(%s)", address); |
| |
| device = device_create(conn, adapter, address); |
| if (!device) |
| return NULL; |
| |
| device_set_temporary(device, 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 btd_adapter *adapter = data; |
| struct btd_device *device; |
| char filename[PATH_MAX + 1]; |
| char *str, srcaddr[18]; |
| bdaddr_t dst; |
| GSList *l; |
| int dev, err; |
| gboolean paired; |
| |
| str2ba(address, &dst); |
| ba2str(&adapter->bdaddr, srcaddr); |
| |
| dev = hci_open_dev(adapter->dev_id); |
| if (dev < 0 && msg) |
| return no_such_adapter(msg); |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, |
| "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, HCI_REQ_TIMEOUT); |
| |
| /* 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, |
| HCI_REQ_TIMEOUT) < 0) |
| && msg){ |
| int err = errno; |
| error("Disconnect failed"); |
| hci_close_dev(dev); |
| return failed_strerror(msg, err); |
| } |
| } |
| |
| hci_close_dev(dev); |
| |
| device = adapter_find_device(adapter, address); |
| if (!device) |
| goto proceed; |
| |
| if (paired) { |
| gboolean paired = FALSE; |
| |
| const gchar *dev_path = device_get_path(device); |
| |
| dbus_connection_emit_property_changed(conn, dev_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 btd_adapter *adapter, |
| struct btd_device *device) |
| { |
| bdaddr_t dst; |
| const gchar *dev_path = device_get_path(device); |
| struct agent *agent; |
| char dstaddr[18]; |
| |
| device_get_address(device, &dst); |
| ba2str(&dst, dstaddr); |
| |
| delete_entry(&adapter->bdaddr, "profiles", dstaddr); |
| adapter->devices = g_slist_remove(adapter->devices, device); |
| |
| if (!device_is_temporary(device)) { |
| remove_bonding(conn, NULL, dstaddr, adapter); |
| |
| g_dbus_emit_signal(conn, adapter->path, |
| ADAPTER_INTERFACE, |
| "DeviceRemoved", |
| DBUS_TYPE_OBJECT_PATH, &dev_path, |
| DBUS_TYPE_INVALID); |
| |
| adapter_update_devices(adapter); |
| } |
| |
| agent = device_get_agent(device); |
| |
| if (agent) { |
| agent_destroy(agent, FALSE); |
| device_set_agent(device, NULL); |
| } |
| |
| device_remove(conn, device); |
| } |
| |
| struct btd_device *adapter_get_device(DBusConnection *conn, |
| struct btd_adapter *adapter, const gchar *address) |
| { |
| struct btd_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 btd_adapter *adapter) |
| { |
| struct btd_device *device; |
| char address[18]; |
| |
| ba2str(&adapter->bonding->bdaddr, address); |
| device = adapter_find_device(adapter, address); |
| if (!device) |
| return; |
| |
| if (device_is_temporary(device)) |
| adapter_remove_device(adapter->bonding->conn, adapter, device); |
| } |
| |
| static gboolean create_bonding_conn_complete(GIOChannel *io, GIOCondition cond, |
| struct btd_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); |
| return FALSE; |
| } |
| |
| if (cond & G_IO_NVAL) { |
| DBusMessage *reply; |
| reply = new_authentication_return(adapter->bonding->msg, 0x09); |
| g_dbus_send_message(adapter->bonding->conn, reply); |
| 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) { |
| DBusMessage *reply = no_such_adapter(adapter->bonding->msg); |
| g_dbus_send_message(adapter->bonding->conn, reply); |
| 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, HCI_REQ_TIMEOUT) < 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 cancel_bonding(struct btd_adapter *adapter, gboolean exited) |
| { |
| struct pending_auth_info *auth; |
| struct bonding_request_info *bonding = adapter->bonding; |
| |
| auth = adapter_find_auth_request(adapter, &adapter->bdaddr); |
| if (auth) { |
| cancel_auth_request(auth, adapter->dev_id); |
| if (auth->agent) |
| agent_cancel(auth->agent); |
| adapter_remove_auth_request(adapter, &bonding->bdaddr); |
| } |
| |
| remove_pending_device(adapter); |
| |
| if (bonding->io) |
| g_io_channel_close(bonding->io); |
| |
| if (exited) { |
| if (bonding->io_id) { |
| g_source_remove(bonding->io_id); |
| bonding->io_id = 0; |
| } |
| bonding_request_free(bonding); |
| adapter->bonding = NULL; |
| |
| } else |
| bonding->cancel = TRUE; |
| } |
| |
| static void create_bond_req_exit(void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| debug("CreateConnection requestor exited before bonding was completed"); |
| |
| cancel_bonding(adapter, TRUE); |
| } |
| |
| 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, srcaddr[18]; |
| struct btd_adapter *adapter = data; |
| struct bonding_request_info *bonding; |
| bdaddr_t dst; |
| int sk; |
| |
| str2ba(address, &dst); |
| ba2str(&adapter->bdaddr, srcaddr); |
| |
| /* check if there is a pending discover: requested by D-Bus/non clients */ |
| if (adapter->state & STD_INQUIRY) |
| 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, &dst)) |
| return in_progress(msg, "Bonding in progress"); |
| |
| /* check if a link key already exists */ |
| create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, |
| "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->bdaddr, &dst); |
| 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; |
| } |
| |
| int start_inquiry(struct btd_adapter *adapter) |
| { |
| inquiry_cp cp; |
| evt_cmd_status rp; |
| struct hci_request rq; |
| uint8_t lap[3] = { 0x33, 0x8b, 0x9e }; |
| int dd, err; |
| |
| pending_remote_name_cancel(adapter); |
| |
| dd = hci_open_dev(adapter->dev_id); |
| if (dd < 0) |
| return dd; |
| |
| 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, HCI_REQ_TIMEOUT) < 0) { |
| err = errno; |
| error("Unable to start inquiry: %s (%d)", |
| strerror(err), err); |
| hci_close_dev(dd); |
| return -err; |
| } |
| |
| if (rp.status) { |
| err = bt_error(rp.status); |
| error("HCI_Inquiry command failed with status 0x%02x", |
| rp.status); |
| hci_close_dev(dd); |
| return -err; |
| } |
| |
| hci_close_dev(dd); |
| |
| adapter->state |= RESOLVE_NAME; |
| |
| return 0; |
| } |
| |
| static int start_periodic_inquiry(struct btd_adapter *adapter) |
| { |
| periodic_inquiry_cp cp; |
| struct hci_request rq; |
| uint8_t lap[3] = { 0x33, 0x8b, 0x9e }; |
| uint8_t status; |
| int dd, err; |
| |
| dd = hci_open_dev(adapter->dev_id); |
| if (dd < 0) |
| return dd; |
| |
| 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, HCI_REQ_TIMEOUT) < 0) { |
| err = errno; |
| error("Unable to start periodic inquiry: %s (%d)", |
| strerror(err), err); |
| hci_close_dev(dd); |
| return -err; |
| } |
| |
| if (status) { |
| err = bt_error(status); |
| error("HCI_Periodic_Inquiry_Mode failed with status 0x%02x", |
| status); |
| hci_close_dev(dd); |
| return -err; |
| } |
| |
| hci_close_dev(dd); |
| |
| adapter->state |= RESOLVE_NAME; |
| |
| return 0; |
| } |
| |
| static DBusMessage *adapter_start_discovery(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct session_req *req; |
| struct btd_adapter *adapter = data; |
| int err; |
| |
| if (!adapter->up) |
| return adapter_not_ready(msg); |
| |
| req = find_session(adapter->disc_sessions, msg); |
| if (req) { |
| session_ref(req); |
| return dbus_message_new_method_return(msg); |
| } |
| |
| if (adapter->disc_sessions) |
| goto done; |
| |
| if (main_opts.inqmode) |
| err = start_inquiry(adapter); |
| else |
| err = start_periodic_inquiry(adapter); |
| |
| if (err < 0) |
| return failed_strerror(msg, -err); |
| |
| done: |
| req = create_session(adapter, conn, msg, 0, |
| (GDBusWatchFunction) session_free); |
| |
| adapter->disc_sessions = g_slist_append(adapter->disc_sessions, req); |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static DBusMessage *adapter_stop_discovery(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct btd_adapter *adapter = data; |
| struct session_req *req; |
| |
| if (!adapter->up) |
| return adapter_not_ready(msg); |
| |
| req = find_session(adapter->disc_sessions, msg); |
| if (!req) |
| return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", |
| "Invalid discovery session"); |
| |
| session_unref(req); |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| struct remote_device_list_t { |
| GSList *list; |
| time_t time; |
| }; |
| |
| static DBusMessage *get_properties(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct btd_adapter *adapter = data; |
| const char *property; |
| DBusMessage *reply; |
| DBusMessageIter iter; |
| DBusMessageIter dict; |
| char str[249], srcaddr[18]; |
| gboolean value; |
| char **devices; |
| int i; |
| GSList *l; |
| |
| ba2str(&adapter->bdaddr, srcaddr); |
| |
| if (check_address(srcaddr) < 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 = srcaddr; |
| dbus_message_iter_append_dict_entry(&dict, "Address", |
| DBUS_TYPE_STRING, &property); |
| |
| /* Name */ |
| memset(str, 0, sizeof(str)); |
| strncpy(str, (char *) adapter->dev.name, 248); |
| property = 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); |
| |
| /* Powered */ |
| if (main_opts.offmode == HCID_OFFMODE_DEVDOWN) |
| value = adapter->up ? TRUE : FALSE; |
| else |
| value = adapter->scan_mode == SCAN_DISABLED ? FALSE : TRUE; |
| dbus_message_iter_append_dict_entry(&dict, "Powered", |
| DBUS_TYPE_BOOLEAN, &value); |
| |
| /* Discoverable */ |
| value = adapter->scan_mode & SCAN_INQUIRY ? TRUE : FALSE; |
| dbus_message_iter_append_dict_entry(&dict, "Discoverable", |
| DBUS_TYPE_BOOLEAN, &value); |
| |
| /* DiscoverableTimeout */ |
| dbus_message_iter_append_dict_entry(&dict, "DiscoverableTimeout", |
| DBUS_TYPE_UINT32, &adapter->discov_timeout); |
| |
| if (adapter->state & PERIODIC_INQUIRY || adapter->state & STD_INQUIRY) |
| value = TRUE; |
| else |
| value = FALSE; |
| |
| /* Discovering */ |
| dbus_message_iter_append_dict_entry(&dict, "Discovering", |
| DBUS_TYPE_BOOLEAN, &value); |
| |
| /* Devices */ |
| devices = g_new0(char *, g_slist_length(adapter->devices) + 1); |
| for (i = 0, l = adapter->devices; l; l = l->next, i++) { |
| struct btd_device *dev = l->data; |
| devices[i] = (char *) device_get_path(dev); |
| } |
| dbus_message_iter_append_dict_entry(&dict, "Devices", |
| DBUS_TYPE_ARRAY, &devices); |
| g_free(devices); |
| |
| dbus_message_iter_close_container(&iter, &dict); |
| |
| return reply; |
| } |
| |
| static DBusMessage *set_property(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct btd_adapter *adapter = data; |
| DBusMessageIter iter; |
| DBusMessageIter sub; |
| const char *property; |
| char srcaddr[18]; |
| |
| ba2str(&adapter->bdaddr, srcaddr); |
| |
| 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("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 = get_mode(&adapter->bdaddr, mode); |
| |
| if (adapter->global_mode == adapter->mode) |
| return dbus_message_new_method_return(msg); |
| |
| if (adapter->mode_sessions && adapter->global_mode < adapter->mode) |
| return confirm_mode(conn, msg, mode, data); |
| |
| return set_mode(conn, msg, |
| get_mode(&adapter->bdaddr, mode), data); |
| } else if (g_str_equal("Powered", property)) { |
| gboolean powered; |
| |
| if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN) |
| return invalid_args(msg); |
| |
| dbus_message_iter_get_basic(&sub, &powered); |
| |
| return set_powered(conn, msg, powered, data); |
| } else if (g_str_equal("Discoverable", property)) { |
| gboolean discoverable; |
| |
| if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN) |
| return invalid_args(msg); |
| |
| dbus_message_iter_get_basic(&sub, &discoverable); |
| |
| return set_discoverable(conn, msg, discoverable, 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); |
| } |
| |
| return invalid_args(msg); |
| } |
| |
| static DBusMessage *mode_request(DBusConnection *conn, |
| DBusMessage *msg, const char *mode, |
| void *data) |
| { |
| struct btd_adapter *adapter = data; |
| struct session_req *req; |
| uint8_t new_mode; |
| int ret; |
| char srcaddr[18]; |
| |
| ba2str(&adapter->bdaddr, srcaddr); |
| |
| new_mode = get_mode(&adapter->bdaddr, 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 (!adapter->mode_sessions) |
| adapter->global_mode = adapter->mode; |
| |
| req = find_session(adapter->mode_sessions, msg); |
| if (!req) { |
| req = create_session(adapter, conn, msg, new_mode, |
| (GDBusWatchFunction) session_free); |
| adapter->mode_sessions = g_slist_append(adapter->mode_sessions, |
| req); |
| } else { |
| req->mode = new_mode; |
| adapter->mode_sessions = g_slist_append(adapter->mode_sessions, |
| req); |
| session_remove(req); |
| return dbus_message_new_method_return(msg); |
| } |
| |
| /* 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) { |
| session_unref(req); |
| return invalid_args(msg); |
| } |
| |
| return NULL; |
| } |
| |
| static DBusMessage *request_mode(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| const char *mode; |
| |
| if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &mode, |
| DBUS_TYPE_INVALID)) |
| return invalid_args(msg); |
| |
| return mode_request(conn, msg, mode, data); |
| } |
| |
| static DBusMessage *request_session(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| return mode_request(conn, msg, "connectable", data); |
| } |
| |
| static DBusMessage *release_session(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct btd_adapter *adapter = data; |
| struct session_req *req; |
| |
| req = find_session(adapter->mode_sessions, msg); |
| if (!req) |
| return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", |
| "No Mode to release"); |
| |
| session_unref(req); |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static DBusMessage *list_devices(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct btd_adapter *adapter = data; |
| DBusMessage *reply; |
| GSList *l; |
| DBusMessageIter iter; |
| DBusMessageIter array_iter; |
| const gchar *dev_path; |
| |
| 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 btd_device *device = l->data; |
| |
| if (device_is_temporary(device)) |
| continue; |
| |
| dev_path = device_get_path(device); |
| |
| dbus_message_iter_append_basic(&array_iter, |
| DBUS_TYPE_OBJECT_PATH, &dev_path); |
| } |
| |
| dbus_message_iter_close_container(&iter, &array_iter); |
| |
| return reply; |
| } |
| |
| static DBusMessage *cancel_device_creation(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct btd_adapter *adapter = data; |
| struct bonding_request_info *bonding = adapter->bonding; |
| const gchar *address; |
| bdaddr_t bda; |
| |
| 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); |
| |
| str2ba(address, &bda); |
| |
| if (bonding && !bacmp(&bonding->bdaddr, &bda)) { |
| if (!g_str_equal(dbus_message_get_sender(msg), |
| dbus_message_get_sender(bonding->msg))) |
| return not_authorized(msg); |
| |
| debug("Canceling device creation for %s", address); |
| cancel_bonding(adapter, FALSE); |
| } |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static DBusMessage *create_device(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct btd_adapter *adapter = data; |
| struct btd_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); |
| if (!device) |
| return NULL; |
| |
| device_set_temporary(device, FALSE); |
| |
| device_browse(device, conn, msg, NULL); |
| |
| 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 btd_device *device, const gchar *path) |
| { |
| const gchar *dev_path = device_get_path(device); |
| |
| return strcasecmp(dev_path, path); |
| } |
| |
| static DBusMessage *remove_device(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct btd_adapter *adapter = data; |
| struct btd_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_is_temporary(device) || device_is_busy(device)) |
| 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 btd_adapter *adapter = data; |
| struct btd_device *device; |
| DBusMessage *reply; |
| const gchar *address; |
| GSList *l; |
| const gchar *dev_path; |
| |
| 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_is_temporary(device)) |
| return g_dbus_create_error(msg, |
| ERROR_INTERFACE ".DoesNotExist", |
| "Device creation in progress"); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dev_path = device_get_path(device); |
| |
| dbus_message_append_args(reply, |
| DBUS_TYPE_OBJECT_PATH, &dev_path, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| static void agent_removed(struct agent *agent, struct btd_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 btd_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 btd_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); |
| } |
| |
| /* 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 | |
| G_DBUS_METHOD_FLAG_DEPRECATED}, |
| { "ReleaseMode", "", "", release_session, |
| G_DBUS_METHOD_FLAG_DEPRECATED}, |
| { "RequestSession", "", "", request_session, |
| G_DBUS_METHOD_FLAG_ASYNC}, |
| { "ReleaseSession", "", "", release_session }, |
| { "StartDiscovery", "", "", adapter_start_discovery }, |
| { "StopDiscovery", "", "", adapter_stop_discovery, |
| G_DBUS_METHOD_FLAG_ASYNC}, |
| { "ListDevices", "", "ao", list_devices, |
| G_DBUS_METHOD_FLAG_DEPRECATED}, |
| { "CreateDevice", "s", "o", create_device, |
| G_DBUS_METHOD_FLAG_ASYNC}, |
| { "CreatePairedDevice", "sos", "o", create_paired_device, |
| G_DBUS_METHOD_FLAG_ASYNC}, |
| { "CancelDeviceCreation","s", "", cancel_device_creation }, |
| { "RemoveDevice", "o", "", remove_device }, |
| { "FindDevice", "s", "o", find_device }, |
| { "RegisterAgent", "os", "", register_agent }, |
| { "UnregisterAgent", "o", "", unregister_agent }, |
| { } |
| }; |
| |
| static GDBusSignalTable adapter_signals[] = { |
| { "DeviceCreated", "o" }, |
| { "DeviceRemoved", "o" }, |
| { "DeviceFound", "sa{sv}" }, |
| { "PropertyChanged", "sv" }, |
| { "DeviceDisappeared", "s" }, |
| { } |
| }; |
| |
| static inline uint8_t get_inquiry_mode(struct hci_dev *dev) |
| { |
| if (dev->features[6] & LMP_EXT_INQ) |
| return 2; |
| |
| if (dev->features[3] & LMP_RSSI_INQ) |
| return 1; |
| |
| if (dev->manufacturer == 11 && |
| dev->hci_rev == 0x00 && dev->lmp_subver == 0x0757) |
| return 1; |
| |
| if (dev->manufacturer == 15) { |
| if (dev->hci_rev == 0x03 && dev->lmp_subver == 0x6963) |
| return 1; |
| if (dev->hci_rev == 0x09 && dev->lmp_subver == 0x6963) |
| return 1; |
| if (dev->hci_rev == 0x00 && dev->lmp_subver == 0x6965) |
| return 1; |
| } |
| |
| if (dev->manufacturer == 31 && |
| dev->hci_rev == 0x2005 && dev->lmp_subver == 0x1805) |
| return 1; |
| |
| return 0; |
| } |
| |
| static int adapter_read_bdaddr(uint16_t dev_id, bdaddr_t *bdaddr) |
| { |
| int dd, err; |
| |
| dd = hci_open_dev(dev_id); |
| if (dd < 0) { |
| err = errno; |
| error("Can't open device hci%d: %s (%d)", |
| dev_id, strerror(err), err); |
| return -err; |
| } |
| |
| if (hci_read_bd_addr(dd, bdaddr, HCI_REQ_TIMEOUT) < 0) { |
| err = errno; |
| error("Can't read address for hci%d: %s (%d)", |
| dev_id, strerror(err), err); |
| hci_close_dev(dd); |
| return -err; |
| } |
| |
| hci_close_dev(dd); |
| |
| return 0; |
| } |
| |
| static int adapter_setup(struct btd_adapter *adapter, int dd) |
| { |
| struct hci_dev *dev = &adapter->dev; |
| uint8_t events[8] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x00 }; |
| uint8_t inqmode; |
| int err; |
| char name[249]; |
| |
| if (dev->hci_rev > 1) { |
| if (dev->features[5] & LMP_SNIFF_SUBR) |
| events[5] |= 0x20; |
| |
| if (dev->features[5] & LMP_PAUSE_ENC) |
| events[5] |= 0x80; |
| |
| if (dev->features[6] & LMP_EXT_INQ) |
| events[5] |= 0x40; |
| |
| if (dev->features[6] & LMP_NFLUSH_PKTS) |
| events[7] |= 0x01; |
| |
| if (dev->features[7] & LMP_LSTO) |
| events[6] |= 0x80; |
| |
| if (dev->features[6] & LMP_SIMPLE_PAIR) { |
| events[6] |= 0x01; /* IO Capability Request */ |
| events[6] |= 0x02; /* IO Capability Response */ |
| events[6] |= 0x04; /* User Confirmation Request */ |
| events[6] |= 0x08; /* User Passkey Request */ |
| events[6] |= 0x10; /* Remote OOB Data Request */ |
| events[6] |= 0x20; /* Simple Pairing Complete */ |
| events[7] |= 0x04; /* User Passkey Notification */ |
| events[7] |= 0x08; /* Keypress Notification */ |
| events[7] |= 0x10; /* Remote Host Supported Features Notification */ |
| } |
| |
| hci_send_cmd(dd, OGF_HOST_CTL, OCF_SET_EVENT_MASK, |
| sizeof(events), events); |
| } |
| |
| if (read_local_name(&adapter->bdaddr, name) == 0) { |
| memcpy(dev->name, name, 248); |
| hci_write_local_name(dd, name, HCI_REQ_TIMEOUT); |
| } |
| |
| update_ext_inquiry_response(dd, dev); |
| |
| inqmode = get_inquiry_mode(dev); |
| if (inqmode < 1) |
| return 0; |
| |
| if (hci_write_inquiry_mode(dd, inqmode, HCI_REQ_TIMEOUT) < 0) { |
| err = errno; |
| error("Can't write inquiry mode for %s: %s (%d)", |
| adapter->path, strerror(err), err); |
| hci_close_dev(dd); |
| return -err; |
| } |
| |
| return 0; |
| } |
| |
| static int active_conn_append(GSList **list, bdaddr_t *bdaddr, |
| uint16_t handle) |
| { |
| struct active_conn_info *dev; |
| |
| dev = g_new0(struct active_conn_info, 1); |
| |
| bacpy(&dev->bdaddr, bdaddr); |
| dev->handle = handle; |
| |
| *list = g_slist_append(*list, dev); |
| return 0; |
| } |
| |
| static void create_stored_device_from_profiles(char *key, char *value, |
| void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| GSList *uuids = bt_string2list(value); |
| struct btd_device *device; |
| sdp_list_t *records; |
| bdaddr_t dst; |
| char srcaddr[18], dstaddr[18]; |
| |
| ba2str(&adapter->bdaddr, srcaddr); |
| |
| if (g_slist_find_custom(adapter->devices, |
| key, (GCompareFunc) device_address_cmp)) |
| return; |
| |
| device = device_create(connection, adapter, key); |
| if (!device) |
| return; |
| |
| device_set_temporary(device, FALSE); |
| adapter->devices = g_slist_append(adapter->devices, device); |
| |
| device_get_address(device, &dst); |
| ba2str(&dst, dstaddr); |
| |
| records = read_records(srcaddr, dstaddr); |
| |
| device_probe_drivers(device, uuids, records); |
| |
| if (records) |
| sdp_list_free(records, (sdp_free_func_t) sdp_record_free); |
| |
| g_slist_free(uuids); |
| } |
| |
| static void create_stored_device_from_linkkeys(char *key, char *value, |
| void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| struct btd_device *device; |
| |
| if (g_slist_find_custom(adapter->devices, |
| key, (GCompareFunc) device_address_cmp)) |
| return; |
| |
| device = device_create(connection, adapter, key); |
| if (device) { |
| device_set_temporary(device, FALSE); |
| adapter->devices = g_slist_append(adapter->devices, device); |
| } |
| } |
| |
| static void load_devices(struct btd_adapter *adapter) |
| { |
| char filename[PATH_MAX + 1]; |
| char srcaddr[18]; |
| |
| ba2str(&adapter->bdaddr, srcaddr); |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "profiles"); |
| textfile_foreach(filename, create_stored_device_from_profiles, adapter); |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "linkkeys"); |
| textfile_foreach(filename, create_stored_device_from_linkkeys, adapter); |
| } |
| |
| static void load_drivers(struct btd_adapter *adapter) |
| { |
| GSList *l; |
| |
| for (l = adapter_drivers; l; l = l->next) { |
| struct btd_adapter_driver *driver = l->data; |
| |
| if (driver->probe) |
| driver->probe(adapter); |
| } |
| } |
| |
| static int get_discoverable_timeout(const char *src) |
| { |
| int timeout; |
| |
| if (read_discoverable_timeout(src, &timeout) == 0) |
| return timeout; |
| |
| return main_opts.discovto; |
| } |
| |
| static void adapter_up(struct btd_adapter *adapter, int dd) |
| { |
| struct hci_conn_list_req *cl = NULL; |
| struct hci_conn_info *ci; |
| const char *pmode; |
| char mode[14], srcaddr[18]; |
| int i; |
| gboolean powered; |
| gboolean discoverable; |
| |
| ba2str(&adapter->bdaddr, srcaddr); |
| |
| adapter->up = 1; |
| adapter->discov_timeout = get_discoverable_timeout(srcaddr); |
| adapter->state = DISCOVER_TYPE_NONE; |
| |
| /* Set scan mode */ |
| if (read_device_mode(srcaddr, mode, sizeof(mode)) == 0) { |
| if (!strcmp(mode, "off")) { |
| if (main_opts.offmode == HCID_OFFMODE_NOSCAN) { |
| adapter->mode = MODE_OFF; |
| adapter->scan_mode= SCAN_DISABLED; |
| } else if (main_opts.offmode == HCID_OFFMODE_DEVDOWN) { |
| static gboolean restore_on_mode = FALSE; |
| |
| if (!restore_on_mode) { |
| ioctl(dd, HCIDEVDOWN, adapter->dev_id); |
| restore_on_mode = TRUE; |
| return; |
| } |
| |
| if (read_on_mode(srcaddr, mode, sizeof(mode)) < 0) |
| write_device_mode(&adapter->bdaddr, mode); |
| else |
| write_device_mode(&adapter->bdaddr, "connectable"); |
| |
| adapter_up(adapter, dd); |
| } |
| } else if (!strcmp(mode, "connectable")) { |
| adapter->mode = MODE_CONNECTABLE; |
| adapter->scan_mode = SCAN_PAGE; |
| } else if (!strcmp(mode, "discoverable")) { |
| /* Set discoverable only if timeout is 0 */ |
| if (adapter->discov_timeout == 0) { |
| adapter->mode = MODE_DISCOVERABLE; |
| adapter->scan_mode = SCAN_PAGE | SCAN_INQUIRY; |
| } else { |
| adapter->mode = MODE_CONNECTABLE; |
| adapter->scan_mode = SCAN_PAGE; |
| } |
| } else if (!strcmp(mode, "limited")) { |
| /* Set discoverable only if timeout is 0 */ |
| if (adapter->discov_timeout == 0) { |
| adapter->mode = MODE_LIMITED; |
| adapter->scan_mode = SCAN_PAGE | SCAN_INQUIRY; |
| } else { |
| adapter->mode = MODE_CONNECTABLE; |
| adapter->scan_mode = SCAN_PAGE; |
| |
| } |
| } |
| } |
| |
| hci_send_cmd(dd, OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE, |
| 1, &adapter->scan_mode); |
| |
| if (adapter->mode == MODE_LIMITED) |
| set_limited_discoverable(dd, adapter->dev.class, TRUE); |
| |
| /* |
| * retrieve the active connections: address the scenario where |
| * the are active connections before the daemon've started |
| */ |
| |
| cl = g_malloc0(10 * sizeof(*ci) + sizeof(*cl)); |
| |
| cl->dev_id = adapter->dev_id; |
| cl->conn_num = 10; |
| ci = cl->conn_info; |
| |
| if (ioctl(dd, HCIGETCONNLIST, cl) == 0) { |
| for (i = 0; i < cl->conn_num; i++, ci++) |
| active_conn_append(&adapter->active_conn, |
| &ci->bdaddr, ci->handle); |
| } |
| g_free(cl); |
| |
| pmode = mode2str(adapter->mode); |
| |
| dbus_connection_emit_property_changed(connection, adapter->path, |
| ADAPTER_INTERFACE, "Mode", |
| DBUS_TYPE_STRING, &pmode); |
| |
| powered = adapter->scan_mode == SCAN_DISABLED ? FALSE : TRUE; |
| |
| dbus_connection_emit_property_changed(connection, adapter->path, |
| ADAPTER_INTERFACE, "Powered", |
| DBUS_TYPE_BOOLEAN, &powered); |
| |
| discoverable = adapter->scan_mode == (SCAN_PAGE | SCAN_INQUIRY) ? TRUE |
| : FALSE; |
| |
| dbus_connection_emit_property_changed(connection, adapter->path, |
| ADAPTER_INTERFACE, "Discoverable", |
| DBUS_TYPE_BOOLEAN, &discoverable); |
| |
| load_drivers(adapter); |
| load_devices(adapter); |
| } |
| |
| int adapter_start(struct btd_adapter *adapter) |
| { |
| struct hci_dev *dev = &adapter->dev; |
| struct hci_dev_info di; |
| struct hci_version ver; |
| uint8_t features[8]; |
| int dd, err; |
| char name[249]; |
| |
| if (hci_devinfo(adapter->dev_id, &di) < 0) |
| return -errno; |
| |
| if (hci_test_bit(HCI_RAW, &di.flags)) { |
| dev->ignore = 1; |
| return -1; |
| } |
| |
| if (!bacmp(&di.bdaddr, BDADDR_ANY)) { |
| int err; |
| |
| debug("Adapter %s without an address", adapter->path); |
| |
| err = adapter_read_bdaddr(adapter->dev_id, &di.bdaddr); |
| if (err < 0) |
| return err; |
| } |
| |
| bacpy(&adapter->bdaddr, &di.bdaddr); |
| memcpy(dev->features, di.features, 8); |
| |
| dd = hci_open_dev(adapter->dev_id); |
| if (dd < 0) { |
| err = errno; |
| error("Can't open adapter %s: %s (%d)", |
| adapter->path, strerror(err), err); |
| return -err; |
| } |
| |
| if (hci_read_local_version(dd, &ver, HCI_REQ_TIMEOUT) < 0) { |
| err = errno; |
| error("Can't read version info for %s: %s (%d)", |
| adapter->path, strerror(err), err); |
| hci_close_dev(dd); |
| return -err; |
| } |
| |
| dev->hci_rev = ver.hci_rev; |
| dev->lmp_ver = ver.lmp_ver; |
| dev->lmp_subver = ver.lmp_subver; |
| dev->manufacturer = ver.manufacturer; |
| |
| if (hci_read_local_features(dd, features, HCI_REQ_TIMEOUT) < 0) { |
| err = errno; |
| error("Can't read features for %s: %s (%d)", |
| adapter->path, strerror(err), err); |
| hci_close_dev(dd); |
| return -err; |
| } |
| |
| memcpy(dev->features, features, 8); |
| |
| if (hci_read_class_of_dev(dd, dev->class, HCI_REQ_TIMEOUT) < 0) { |
| err = errno; |
| error("Can't read class of adapter on %s: %s (%d)", |
| adapter->path, strerror(err), err); |
| hci_close_dev(dd); |
| return -err; |
| } |
| |
| if (hci_read_local_name(dd, sizeof(name), name, HCI_REQ_TIMEOUT) < 0) { |
| err = errno; |
| error("Can't read local name on %s: %s (%d)", |
| adapter->path, strerror(err), err); |
| hci_close_dev(dd); |
| return -err; |
| } |
| |
| memcpy(dev->name, name, 248); |
| |
| if (!(features[6] & LMP_SIMPLE_PAIR)) |
| goto setup; |
| |
| if (ioctl(dd, HCIGETAUTHINFO, NULL) < 0 && errno != EINVAL) |
| hci_write_simple_pairing_mode(dd, 0x01, HCI_REQ_TIMEOUT); |
| |
| if (hci_read_simple_pairing_mode(dd, &dev->ssp_mode, |
| HCI_REQ_TIMEOUT) < 0) { |
| err = errno; |
| error("Can't read simple pairing mode on %s: %s (%d)", |
| adapter->path, strerror(err), err); |
| hci_close_dev(dd); |
| return -err; |
| } |
| |
| setup: |
| hci_send_cmd(dd, OGF_LINK_POLICY, |
| OCF_READ_DEFAULT_LINK_POLICY, 0, NULL); |
| |
| if (hci_test_bit(HCI_INQUIRY, &di.flags)) |
| adapter->state |= STD_INQUIRY; |
| else |
| adapter->state &= ~STD_INQUIRY; |
| |
| adapter_setup(adapter, dd); |
| adapter_up(adapter, dd); |
| |
| hci_close_dev(dd); |
| |
| info("Adapter %s has been enabled", adapter->path); |
| |
| return 0; |
| } |
| |
| static void reply_pending_requests(struct btd_adapter *adapter) |
| { |
| DBusMessage *reply; |
| |
| if (!adapter) |
| return; |
| |
| /* pending bonding */ |
| if (adapter->bonding) { |
| reply = new_authentication_return(adapter->bonding->msg, |
| HCI_OE_USER_ENDED_CONNECTION); |
| g_dbus_send_message(connection, reply); |
| remove_pending_device(adapter); |
| |
| g_dbus_remove_watch(adapter->bonding->conn, |
| adapter->bonding->listener_id); |
| |
| if (adapter->bonding->io_id) |
| g_source_remove(adapter->bonding->io_id); |
| if (adapter->bonding->io) |
| g_io_channel_close(adapter->bonding->io); |
| bonding_request_free(adapter->bonding); |
| adapter->bonding = NULL; |
| } |
| |
| if (adapter->state & STD_INQUIRY) { |
| /* Cancel inquiry initiated by D-Bus client */ |
| if (adapter->disc_sessions) |
| cancel_discovery(adapter); |
| } |
| |
| if (adapter->state & PERIODIC_INQUIRY) { |
| /* Stop periodic inquiry initiated by D-Bus client */ |
| if (adapter->disc_sessions) |
| cancel_periodic_discovery(adapter); |
| } |
| } |
| |
| static void unload_drivers(struct btd_adapter *adapter) |
| { |
| GSList *l; |
| |
| for (l = adapter_drivers; l; l = l->next) { |
| struct btd_adapter_driver *driver = l->data; |
| |
| if (driver->remove) |
| driver->remove(adapter); |
| } |
| } |
| |
| int adapter_stop(struct btd_adapter *adapter) |
| { |
| const char *mode = "off"; |
| gboolean powered, discoverable; |
| |
| /* cancel pending timeout */ |
| if (adapter->discov_timeout_id) { |
| g_source_remove(adapter->discov_timeout_id); |
| adapter->discov_timeout_id = 0; |
| } |
| |
| /* check pending requests */ |
| reply_pending_requests(adapter); |
| |
| if (adapter->disc_sessions) { |
| g_slist_foreach(adapter->disc_sessions, (GFunc) session_free, |
| NULL); |
| g_slist_free(adapter->disc_sessions); |
| adapter->disc_sessions = NULL; |
| } |
| |
| if (adapter->found_devices) { |
| g_slist_foreach(adapter->found_devices, (GFunc) g_free, NULL); |
| g_slist_free(adapter->found_devices); |
| adapter->found_devices = NULL; |
| } |
| |
| if (adapter->oor_devices) { |
| g_slist_foreach(adapter->oor_devices, (GFunc) free, NULL); |
| g_slist_free(adapter->oor_devices); |
| adapter->oor_devices = NULL; |
| } |
| |
| if (adapter->auth_reqs) { |
| g_slist_foreach(adapter->auth_reqs, (GFunc) g_free, NULL); |
| g_slist_free(adapter->auth_reqs); |
| adapter->auth_reqs = NULL; |
| } |
| |
| if (adapter->active_conn) { |
| g_slist_foreach(adapter->active_conn, (GFunc) g_free, NULL); |
| g_slist_free(adapter->active_conn); |
| adapter->active_conn = NULL; |
| } |
| |
| dbus_connection_emit_property_changed(connection, adapter->path, |
| ADAPTER_INTERFACE, "Mode", |
| DBUS_TYPE_STRING, &mode); |
| |
| powered = FALSE; |
| |
| dbus_connection_emit_property_changed(connection, adapter->path, |
| ADAPTER_INTERFACE, "Powered", |
| DBUS_TYPE_BOOLEAN, &powered); |
| |
| if (adapter->scan_mode == (SCAN_PAGE | SCAN_INQUIRY)) { |
| discoverable = FALSE; |
| |
| dbus_connection_emit_property_changed(connection, adapter->path, |
| ADAPTER_INTERFACE, "Discoverable", |
| DBUS_TYPE_BOOLEAN, &discoverable); |
| } |
| |
| adapter->up = 0; |
| adapter->scan_mode = SCAN_DISABLED; |
| adapter->mode = MODE_OFF; |
| adapter->state = DISCOVER_TYPE_NONE; |
| |
| unload_drivers(adapter); |
| |
| info("Adapter %s has been disabled", adapter->path); |
| |
| return 0; |
| } |
| |
| int adapter_update(struct btd_adapter *adapter) |
| { |
| struct hci_dev *dev = &adapter->dev; |
| int dd; |
| |
| if (dev->ignore) |
| return 0; |
| |
| dd = hci_open_dev(adapter->dev_id); |
| if (dd < 0) { |
| int err = errno; |
| error("Can't open adapter %s: %s (%d)", |
| adapter->path, strerror(err), err); |
| return -err; |
| } |
| |
| update_ext_inquiry_response(dd, dev); |
| |
| hci_close_dev(dd); |
| |
| return 0; |
| } |
| |
| int adapter_get_class(struct btd_adapter *adapter, uint8_t *cls) |
| { |
| struct hci_dev *dev = &adapter->dev; |
| |
| memcpy(cls, dev->class, 3); |
| |
| return 0; |
| } |
| |
| int adapter_set_class(struct btd_adapter *adapter, uint8_t *cls) |
| { |
| struct hci_dev *dev = &adapter->dev; |
| |
| memcpy(dev->class, cls, 3); |
| |
| return 0; |
| } |
| |
| int adapter_update_ssp_mode(struct btd_adapter *adapter, int dd, uint8_t mode) |
| { |
| struct hci_dev *dev = &adapter->dev; |
| |
| dev->ssp_mode = mode; |
| |
| update_ext_inquiry_response(dd, dev); |
| |
| hci_close_dev(dd); |
| |
| return 0; |
| } |
| |
| static void adapter_free(gpointer user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| agent_destroy(adapter->agent, FALSE); |
| adapter->agent = NULL; |
| |
| g_free(adapter->path); |
| g_free(adapter); |
| |
| return; |
| } |
| |
| struct btd_adapter *adapter_create(DBusConnection *conn, int id) |
| { |
| char path[MAX_PATH_LENGTH]; |
| struct btd_adapter *adapter; |
| |
| if (!connection) |
| connection = conn; |
| |
| snprintf(path, sizeof(path), "%s/hci%d", "/org/bluez", id); |
| |
| adapter = g_try_new0(struct btd_adapter, 1); |
| if (!adapter) { |
| error("Failed to alloc memory to D-Bus path register data (%s)", |
| path); |
| return NULL; |
| } |
| |
| adapter->dev_id = id; |
| adapter->state |= RESOLVE_NAME; |
| adapter->path = g_strdup(path); |
| |
| if (!g_dbus_register_interface(conn, path, ADAPTER_INTERFACE, |
| adapter_methods, adapter_signals, NULL, |
| adapter, adapter_free)) { |
| error("Adapter interface init failed on path %s", path); |
| adapter_free(adapter); |
| return NULL; |
| } |
| |
| return adapter; |
| } |
| |
| void adapter_remove(struct btd_adapter *adapter) |
| { |
| GSList *l; |
| char *path = g_strdup(adapter->path); |
| |
| debug("Removing adapter %s", path); |
| |
| for (l = adapter->devices; l; l = l->next) |
| device_remove(connection, l->data); |
| g_slist_free(adapter->devices); |
| |
| g_dbus_unregister_interface(connection, path, ADAPTER_INTERFACE); |
| |
| g_free(path); |
| } |
| |
| uint16_t adapter_get_dev_id(struct btd_adapter *adapter) |
| { |
| return adapter->dev_id; |
| } |
| |
| const gchar *adapter_get_path(struct btd_adapter *adapter) |
| { |
| if (!adapter) |
| return NULL; |
| |
| return adapter->path; |
| } |
| |
| void adapter_get_address(struct btd_adapter *adapter, bdaddr_t *bdaddr) |
| { |
| bacpy(bdaddr, &adapter->bdaddr); |
| } |
| |
| static gboolean discov_timeout_handler(void *data) |
| { |
| struct btd_adapter *adapter = data; |
| struct hci_request rq; |
| int dd; |
| uint8_t scan_enable = adapter->scan_mode; |
| uint8_t status = 0; |
| gboolean retval = TRUE; |
| uint16_t dev_id = adapter->dev_id; |
| |
| scan_enable &= ~SCAN_INQUIRY; |
| |
| dd = hci_open_dev(dev_id); |
| if (dd < 0) { |
| error("HCI device open failed: hci%d", dev_id); |
| return TRUE; |
| } |
| |
| 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, HCI_REQ_TIMEOUT) < 0) { |
| error("Sending write scan enable command to hci%d failed: %s (%d)", |
| dev_id, strerror(errno), errno); |
| goto failed; |
| } |
| if (status) { |
| error("Setting scan enable failed with status 0x%02x", status); |
| goto failed; |
| } |
| |
| set_limited_discoverable(dd, adapter->dev.class, FALSE); |
| |
| adapter_remove_discov_timeout(adapter); |
| retval = FALSE; |
| |
| failed: |
| if (dd >= 0) |
| hci_close_dev(dd); |
| |
| return retval; |
| } |
| |
| void adapter_set_discov_timeout(struct btd_adapter *adapter, guint interval) |
| { |
| if (!adapter) |
| return; |
| |
| if (adapter->discov_timeout_id) { |
| error("Timeout already added for adapter %s", adapter->path); |
| return; |
| } |
| |
| adapter->discov_timeout_id = g_timeout_add(interval, discov_timeout_handler, adapter); |
| } |
| |
| void adapter_remove_discov_timeout(struct btd_adapter *adapter) |
| { |
| if (!adapter) |
| return; |
| |
| if(adapter->discov_timeout_id == 0) |
| return; |
| |
| g_source_remove(adapter->discov_timeout_id); |
| adapter->discov_timeout_id = 0; |
| } |
| |
| void adapter_set_scan_mode(struct btd_adapter *adapter, uint8_t scan_mode) |
| { |
| if (!adapter) |
| return; |
| |
| adapter->scan_mode = scan_mode; |
| } |
| |
| uint8_t adapter_get_scan_mode(struct btd_adapter *adapter) |
| { |
| return adapter->scan_mode; |
| } |
| |
| void adapter_set_mode(struct btd_adapter *adapter, uint8_t mode) |
| { |
| if (!adapter) |
| return; |
| |
| adapter->mode = mode; |
| } |
| |
| uint8_t adapter_get_mode(struct btd_adapter *adapter) |
| { |
| return adapter->mode; |
| } |
| |
| void adapter_set_state(struct btd_adapter *adapter, int state) |
| { |
| gboolean discov_active = FALSE; |
| const char *path = adapter->path; |
| |
| if (adapter->state == state) |
| return; |
| |
| if (state & PERIODIC_INQUIRY || state & STD_INQUIRY) |
| discov_active = TRUE; |
| else if (adapter->disc_sessions && main_opts.inqmode) |
| adapter->scheduler_id = g_timeout_add(main_opts.inqmode * 1000, |
| (GSourceFunc) start_inquiry, adapter); |
| |
| if (!discov_active && adapter->found_devices) { |
| g_slist_foreach(adapter->found_devices, (GFunc) g_free, NULL); |
| g_slist_free(adapter->found_devices); |
| adapter->found_devices = NULL; |
| } |
| |
| if (!discov_active && adapter->oor_devices) { |
| g_slist_foreach(adapter->oor_devices, (GFunc) g_free, NULL); |
| g_slist_free(adapter->oor_devices); |
| adapter->oor_devices = NULL; |
| } |
| |
| dbus_connection_emit_property_changed(connection, path, |
| ADAPTER_INTERFACE, "Discovering", |
| DBUS_TYPE_BOOLEAN, &discov_active); |
| |
| adapter->state = state; |
| } |
| |
| int adapter_get_state(struct btd_adapter *adapter) |
| { |
| return adapter->state; |
| } |
| |
| struct remote_dev_info *adapter_search_found_devices(struct btd_adapter *adapter, |
| struct remote_dev_info *match) |
| { |
| GSList *l; |
| |
| l = g_slist_find_custom(adapter->found_devices, match, |
| (GCompareFunc) found_device_cmp); |
| if (l) |
| return l->data; |
| |
| return NULL; |
| } |
| |
| int dev_rssi_cmp(struct remote_dev_info *d1, struct remote_dev_info *d2) |
| { |
| int rssi1, rssi2; |
| |
| rssi1 = d1->rssi < 0 ? -d1->rssi : d1->rssi; |
| rssi2 = d2->rssi < 0 ? -d2->rssi : d2->rssi; |
| |
| return rssi1 - rssi2; |
| } |
| |
| int adapter_add_found_device(struct btd_adapter *adapter, bdaddr_t *bdaddr, |
| int8_t rssi, name_status_t name_status) |
| { |
| struct remote_dev_info *dev, match; |
| |
| memset(&match, 0, sizeof(struct remote_dev_info)); |
| bacpy(&match.bdaddr, bdaddr); |
| match.name_status = NAME_ANY; |
| |
| /* ignore repeated entries */ |
| dev = adapter_search_found_devices(adapter, &match); |
| if (dev) { |
| /* device found, update the attributes */ |
| if (rssi != 0) |
| dev->rssi = rssi; |
| |
| /* Get remote name can be received while inquiring. |
| * Keep in mind that multiple inquiry result events can |
| * be received from the same remote device. |
| */ |
| if (name_status != NAME_NOT_REQUIRED) |
| dev->name_status = name_status; |
| |
| adapter->found_devices = g_slist_sort(adapter->found_devices, |
| (GCompareFunc) dev_rssi_cmp); |
| |
| return -EALREADY; |
| } |
| |
| dev = g_new0(struct remote_dev_info, 1); |
| |
| bacpy(&dev->bdaddr, bdaddr); |
| dev->rssi = rssi; |
| dev->name_status = name_status; |
| |
| adapter->found_devices = g_slist_insert_sorted(adapter->found_devices, |
| dev, (GCompareFunc) dev_rssi_cmp); |
| |
| return 0; |
| } |
| |
| int adapter_remove_found_device(struct btd_adapter *adapter, bdaddr_t *bdaddr) |
| { |
| struct remote_dev_info *dev, match; |
| |
| memset(&match, 0, sizeof(struct remote_dev_info)); |
| bacpy(&match.bdaddr, bdaddr); |
| |
| dev = adapter_search_found_devices(adapter, &match); |
| if (!dev) |
| return -1; |
| |
| adapter->found_devices = g_slist_remove(adapter->found_devices, dev); |
| g_free(dev); |
| |
| return 0; |
| } |
| |
| void adapter_update_oor_devices(struct btd_adapter *adapter) |
| { |
| GSList *l = adapter->found_devices; |
| struct remote_dev_info *dev; |
| bdaddr_t tmp; |
| |
| send_out_of_range(adapter->path, adapter->oor_devices); |
| |
| g_slist_foreach(adapter->oor_devices, (GFunc) free, NULL); |
| g_slist_free(adapter->oor_devices); |
| adapter->oor_devices = NULL; |
| |
| while (l) { |
| dev = l->data; |
| baswap(&tmp, &dev->bdaddr); |
| adapter->oor_devices = g_slist_append(adapter->oor_devices, |
| batostr(&tmp)); |
| l = l->next; |
| } |
| } |
| |
| void adapter_remove_oor_device(struct btd_adapter *adapter, char *peer_addr) |
| { |
| GSList *l; |
| |
| l = g_slist_find_custom(adapter->oor_devices, peer_addr, |
| (GCompareFunc) strcmp); |
| if (l) { |
| char *dev = l->data; |
| adapter->oor_devices = g_slist_remove(adapter->oor_devices, |
| dev); |
| g_free(dev); |
| } |
| } |
| |
| void adapter_mode_changed(struct btd_adapter *adapter, uint8_t scan_mode) |
| { |
| const gchar *path = adapter_get_path(adapter); |
| const char *mode; |
| gboolean powered, discoverable; |
| |
| switch (scan_mode) { |
| case SCAN_DISABLED: |
| mode = "off"; |
| adapter_set_mode(adapter, MODE_OFF); |
| powered = FALSE; |
| discoverable = FALSE; |
| break; |
| case SCAN_PAGE: |
| mode = "connectable"; |
| adapter_set_mode(adapter, MODE_CONNECTABLE); |
| powered = TRUE; |
| discoverable = FALSE; |
| break; |
| case (SCAN_PAGE | SCAN_INQUIRY): |
| powered = TRUE; |
| discoverable = TRUE; |
| if (adapter_get_mode(adapter) == MODE_LIMITED) { |
| mode = "limited"; |
| } else { |
| adapter_set_mode(adapter, MODE_DISCOVERABLE); |
| mode = "discoverable"; |
| } |
| if (adapter->discov_timeout != 0) |
| adapter_set_discov_timeout(adapter, |
| adapter->discov_timeout * 1000); |
| break; |
| case SCAN_INQUIRY: |
| /* Address the scenario where a low-level application like |
| * hciconfig changed the scan mode */ |
| if (adapter->discov_timeout != 0) |
| adapter_set_discov_timeout(adapter, |
| adapter->discov_timeout * 1000); |
| |
| /* ignore, this event should not be sent */ |
| default: |
| /* ignore, reserved */ |
| return; |
| } |
| |
| dbus_connection_emit_property_changed(connection, path, |
| ADAPTER_INTERFACE, "Mode", |
| DBUS_TYPE_STRING, &mode); |
| |
| if (powered == FALSE || adapter->scan_mode == SCAN_DISABLED) { |
| dbus_connection_emit_property_changed(connection, path, |
| ADAPTER_INTERFACE, "Powered", |
| DBUS_TYPE_BOOLEAN, &powered); |
| } |
| |
| dbus_connection_emit_property_changed(connection, path, |
| ADAPTER_INTERFACE, "Discoverable", |
| DBUS_TYPE_BOOLEAN, &discoverable); |
| |
| adapter_set_scan_mode(adapter, scan_mode); |
| } |
| |
| struct agent *adapter_get_agent(struct btd_adapter *adapter) |
| { |
| if (!adapter || !adapter->agent) |
| return NULL; |
| |
| return adapter->agent; |
| } |
| |
| void adapter_add_active_conn(struct btd_adapter *adapter, bdaddr_t *bdaddr, |
| uint16_t handle) |
| { |
| struct active_conn_info *dev; |
| |
| if (!adapter) |
| return; |
| |
| dev = g_new0(struct active_conn_info, 1); |
| |
| bacpy(&dev->bdaddr, bdaddr); |
| dev->handle = handle; |
| |
| adapter->active_conn = g_slist_append(adapter->active_conn, dev); |
| } |
| |
| void adapter_remove_active_conn(struct btd_adapter *adapter, |
| struct active_conn_info *dev) |
| { |
| if (!adapter || !adapter->active_conn) |
| return; |
| |
| adapter->active_conn = g_slist_remove(adapter->active_conn, dev); |
| g_free(dev); |
| } |
| |
| struct active_conn_info *adapter_search_active_conn_by_bdaddr(struct btd_adapter *adapter, |
| bdaddr_t *bda) |
| { |
| GSList *l; |
| |
| if (!adapter || !adapter->active_conn) |
| return NULL; |
| |
| l = g_slist_find_custom(adapter->active_conn, &bda, |
| active_conn_find_by_bdaddr); |
| if (l) |
| return l->data; |
| |
| return NULL; |
| } |
| |
| struct active_conn_info *adapter_search_active_conn_by_handle(struct btd_adapter *adapter, |
| uint16_t handle) |
| { |
| GSList *l; |
| |
| if (!adapter || !adapter->active_conn) |
| return NULL; |
| |
| l = g_slist_find_custom(adapter->active_conn, &handle, |
| active_conn_find_by_handle); |
| if (l) |
| return l->data; |
| |
| return NULL; |
| } |
| |
| void adapter_free_bonding_request(struct btd_adapter *adapter) |
| { |
| g_dbus_remove_watch(connection, adapter->bonding->listener_id); |
| |
| if (adapter->bonding->io_id) |
| g_source_remove(adapter->bonding->io_id); |
| |
| if (adapter->bonding->io) |
| g_io_channel_close(adapter->bonding->io); |
| |
| bonding_request_free(adapter->bonding); |
| |
| adapter->bonding = NULL; |
| } |
| |
| struct bonding_request_info *adapter_get_bonding_info(struct btd_adapter *adapter) |
| { |
| if (!adapter) |
| return NULL; |
| |
| return adapter->bonding; |
| } |
| |
| gboolean adapter_has_discov_sessions(struct btd_adapter *adapter) |
| { |
| if (!adapter || !adapter->disc_sessions) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| int btd_register_adapter_driver(struct btd_adapter_driver *driver) |
| { |
| adapter_drivers = g_slist_append(adapter_drivers, driver); |
| |
| return 0; |
| } |
| |
| void btd_unregister_adapter_driver(struct btd_adapter_driver *driver) |
| { |
| adapter_drivers = g_slist_remove(adapter_drivers, driver); |
| } |
| |
| static void agent_auth_cb(struct agent *agent, DBusError *derr, void *user_data) |
| { |
| struct service_auth *auth = user_data; |
| |
| auth->cb(derr, auth->user_data); |
| |
| g_free(auth); |
| } |
| |
| int btd_request_authorization(const bdaddr_t *src, const bdaddr_t *dst, |
| const char *uuid, service_auth_cb cb, void *user_data) |
| { |
| struct service_auth *auth; |
| struct btd_adapter *adapter; |
| struct btd_device *device; |
| struct agent *agent; |
| char address[18]; |
| gboolean trusted; |
| const gchar *dev_path; |
| |
| if (src == NULL || dst == NULL) |
| return -EINVAL; |
| |
| adapter = manager_find_adapter(src); |
| if (!adapter) |
| return -EPERM; |
| |
| /* Device connected? */ |
| if (!g_slist_find_custom(adapter->active_conn, |
| dst, active_conn_find_by_bdaddr)) |
| return -ENOTCONN; |
| |
| ba2str(dst, address); |
| trusted = read_trust(src, address, GLOBAL_TRUST); |
| |
| if (trusted) { |
| cb(NULL, user_data); |
| return 0; |
| } |
| |
| device = adapter_find_device(adapter, address); |
| if (!device) |
| return -EPERM; |
| |
| agent = device_get_agent(device); |
| |
| if (!agent) |
| agent = adapter->agent; |
| |
| if (!agent) |
| return -EPERM; |
| |
| auth = g_try_new0(struct service_auth, 1); |
| if (!auth) |
| return -ENOMEM; |
| |
| auth->cb = cb; |
| auth->user_data = user_data; |
| |
| dev_path = device_get_path(device); |
| |
| return agent_authorize(agent, dev_path, uuid, agent_auth_cb, auth); |
| } |
| |
| int btd_cancel_authorization(const bdaddr_t *src, const bdaddr_t *dst) |
| { |
| struct btd_adapter *adapter = manager_find_adapter(src); |
| struct btd_device *device; |
| struct agent *agent; |
| char address[18]; |
| |
| if (!adapter) |
| return -EPERM; |
| |
| ba2str(dst, address); |
| device = adapter_find_device(adapter, address); |
| if (!device) |
| return -EPERM; |
| |
| /* |
| * FIXME: Cancel fails if authorization is requested to adapter's |
| * agent and in the meanwhile CreatePairedDevice is called. |
| */ |
| |
| agent = device_get_agent(device); |
| |
| if (!agent) |
| agent = adapter->agent; |
| |
| if (!agent) |
| return -EPERM; |
| |
| return agent_cancel(agent); |
| } |
| |
| void adapter_update_devices(struct btd_adapter *adapter) |
| { |
| char **devices; |
| int i; |
| GSList *l; |
| |
| /* Devices */ |
| devices = g_new0(char *, g_slist_length(adapter->devices) + 1); |
| for (i = 0, l = adapter->devices; l; l = l->next, i++) { |
| struct btd_device *dev = l->data; |
| devices[i] = (char *) device_get_path(dev); |
| } |
| |
| dbus_connection_emit_property_changed(connection, adapter->path, |
| ADAPTER_INTERFACE, "Devices", |
| DBUS_TYPE_ARRAY, &devices); |
| g_free(devices); |
| } |