/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * * 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 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "log.h" #include "lib/uuid.h" #include "lib/mgmt.h" #include "attrib/att.h" #include "hcid.h" #include "adapter.h" #include "attrib/gattrib.h" #include "attio.h" #include "device.h" #include "profile.h" #include "service.h" #include "dbus-common.h" #include "error.h" #include "glib-helper.h" #include "sdp-client.h" #include "attrib/gatt.h" #include "agent.h" #include "sdp-xml.h" #include "storage.h" #include "attrib-server.h" #define IO_CAPABILITY_NOINPUTNOOUTPUT 0x03 #define DISCONNECT_TIMER 2 #define DISCOVERY_TIMER 1 static DBusConnection *dbus_conn = NULL; unsigned service_state_cb_id; struct btd_disconnect_data { guint id; disconnect_watch watch; void *user_data; GDestroyNotify destroy; }; struct bonding_req { DBusMessage *msg; guint listener_id; struct btd_device *device; struct agent *agent; struct btd_adapter_pin_cb_iter *cb_iter; uint8_t status; guint retry_timer; struct timespec attempt_start_time; long last_attempt_duration_ms; }; typedef enum { AUTH_TYPE_PINCODE, AUTH_TYPE_PASSKEY, AUTH_TYPE_CONFIRM, AUTH_TYPE_NOTIFY_PASSKEY, AUTH_TYPE_NOTIFY_PINCODE, } auth_type_t; struct authentication_req { auth_type_t type; struct agent *agent; struct btd_device *device; uint32_t passkey; char *pincode; gboolean secure; }; struct browse_req { DBusMessage *msg; struct btd_device *device; GSList *match_uuids; GSList *profiles_added; sdp_list_t *records; int search_uuid; int reconnect_attempt; guint listener_id; }; struct included_search { struct browse_req *req; GSList *services; GSList *current; }; struct attio_data { guint id; attio_connect_cb cfunc; attio_disconnect_cb dcfunc; gpointer user_data; }; struct svc_callback { unsigned int id; guint idle_id; struct btd_device *dev; device_svc_cb_t func; void *user_data; }; typedef void (*attio_error_cb) (const GError *gerr, gpointer user_data); typedef void (*attio_success_cb) (gpointer user_data); struct att_callbacks { attio_error_cb error; /* Callback for error */ attio_success_cb success; /* Callback for success */ gpointer user_data; }; struct btd_device { int ref_count; bdaddr_t bdaddr; uint8_t bdaddr_type; char *path; bool pending_paired; /* "Paired" waiting for SDP */ bool svc_resolved; bool svc_refreshed; GSList *svc_callbacks; GSList *eir_uuids; char name[MAX_NAME_LENGTH + 1]; char *alias; uint32_t class; uint16_t vendor_src; uint16_t vendor; uint16_t product; uint16_t version; uint16_t appearance; char *modalias; struct btd_adapter *adapter; GSList *uuids; GSList *primaries; /* List of primary services */ GSList *services; /* List of btd_service */ GSList *pending; /* Pending services */ GSList *watches; /* List of disconnect_data */ gboolean temporary; guint disconn_timer; guint discov_timer; struct browse_req *browse; /* service discover request */ struct bonding_req *bonding; struct authentication_req *authr; /* authentication request */ GSList *disconnects; /* disconnects message */ DBusMessage *connect; /* connect message */ DBusMessage *disconnect; /* disconnect message */ GAttrib *attrib; GSList *attios; GSList *attios_offline; guint attachid; /* Attrib server attach */ gboolean connected; sdp_list_t *tmp_records; gboolean trusted; gboolean paired; gboolean blocked; gboolean bonded; gboolean auto_connect; gboolean disable_auto_connect; gboolean general_connect; bool legacy; int8_t rssi; GIOChannel *att_io; guint cleanup_id; guint store_id; }; static const uint16_t uuid_list[] = { L2CAP_UUID, PNP_INFO_SVCLASS_ID, PUBLIC_BROWSE_GROUP, 0 }; static int device_browse_primary(struct btd_device *device, DBusMessage *msg); static int device_browse_sdp(struct btd_device *device, DBusMessage *msg); static GSList *find_service_with_profile(GSList *list, struct btd_profile *p) { GSList *l; for (l = list; l != NULL; l = g_slist_next(l)) { struct btd_service *service = l->data; if (btd_service_get_profile(service) == p) return l; } return NULL; } static GSList *find_service_with_state(GSList *list, btd_service_state_t state) { GSList *l; for (l = list; l != NULL; l = g_slist_next(l)) { struct btd_service *service = l->data; if (btd_service_get_state(service) == state) return l; } return NULL; } static gboolean store_device_info_cb(gpointer user_data) { struct btd_device *device = user_data; GKeyFile *key_file; char filename[PATH_MAX + 1]; char adapter_addr[18]; char device_addr[18]; char *str; char class[9]; char **uuids = NULL; gsize length = 0; device->store_id = 0; ba2str(adapter_get_address(device->adapter), adapter_addr); ba2str(&device->bdaddr, device_addr); snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", adapter_addr, device_addr); filename[PATH_MAX] = '\0'; key_file = g_key_file_new(); g_key_file_load_from_file(key_file, filename, 0, NULL); g_key_file_set_string(key_file, "General", "Name", device->name); if (device->alias != NULL) g_key_file_set_string(key_file, "General", "Alias", device->alias); else g_key_file_remove_key(key_file, "General", "Alias", NULL); if (device->class) { sprintf(class, "0x%6.6x", device->class); g_key_file_set_string(key_file, "General", "Class", class); } else { g_key_file_remove_key(key_file, "General", "Class", NULL); } if (device->appearance) { sprintf(class, "0x%4.4x", device->appearance); g_key_file_set_string(key_file, "General", "Appearance", class); } else { g_key_file_remove_key(key_file, "General", "Appearance", NULL); } switch (device->bdaddr_type) { case BDADDR_BREDR: g_key_file_set_string(key_file, "General", "SupportedTechnologies", "BR/EDR"); g_key_file_remove_key(key_file, "General", "AddressType", NULL); break; case BDADDR_LE_PUBLIC: g_key_file_set_string(key_file, "General", "SupportedTechnologies", "LE"); g_key_file_set_string(key_file, "General", "AddressType", "public"); break; case BDADDR_LE_RANDOM: g_key_file_set_string(key_file, "General", "SupportedTechnologies", "LE"); g_key_file_set_string(key_file, "General", "AddressType", "static"); break; default: g_key_file_remove_key(key_file, "General", "SupportedTechnologies", NULL); g_key_file_remove_key(key_file, "General", "AddressType", NULL); } g_key_file_set_boolean(key_file, "General", "Trusted", device->trusted); g_key_file_set_boolean(key_file, "General", "Blocked", device->blocked); if (device->uuids) { GSList *l; int i; uuids = g_new0(char *, g_slist_length(device->uuids) + 1); for (i = 0, l = device->uuids; l; l = g_slist_next(l), i++) uuids[i] = l->data; g_key_file_set_string_list(key_file, "General", "Services", (const char **)uuids, i); } else { g_key_file_remove_key(key_file, "General", "Services", NULL); } if (device->vendor_src) { g_key_file_set_integer(key_file, "DeviceID", "Source", device->vendor_src); g_key_file_set_integer(key_file, "DeviceID", "Vendor", device->vendor); g_key_file_set_integer(key_file, "DeviceID", "Product", device->product); g_key_file_set_integer(key_file, "DeviceID", "Version", device->version); } else { g_key_file_remove_group(key_file, "DeviceID", NULL); } create_file(filename, S_IRUSR | S_IWUSR); str = g_key_file_to_data(key_file, &length, NULL); g_file_set_contents(filename, str, length, NULL); g_free(str); g_key_file_free(key_file); g_free(uuids); return FALSE; } static bool device_address_is_private(struct btd_device *dev) { if (dev->bdaddr_type != BDADDR_LE_RANDOM) return false; switch (dev->bdaddr.b[5] >> 6) { case 0x00: /* Private non-resolvable */ case 0x01: /* Private resolvable */ return true; default: return false; } } static void store_device_info(struct btd_device *device) { if (device->temporary || device->store_id > 0) return; if (device_address_is_private(device)) { warn("Can't store info for private addressed device %s", device->path); return; } device->store_id = g_idle_add(store_device_info_cb, device); } void device_store_cached_name(struct btd_device *dev, const char *name) { char filename[PATH_MAX + 1]; char s_addr[18], d_addr[18]; GKeyFile *key_file; char *data; gsize length = 0; if (device_address_is_private(dev)) { warn("Can't store name for private addressed device %s", dev->path); return; } ba2str(adapter_get_address(dev->adapter), s_addr); ba2str(&dev->bdaddr, d_addr); snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", s_addr, d_addr); filename[PATH_MAX] = '\0'; create_file(filename, S_IRUSR | S_IWUSR); key_file = g_key_file_new(); g_key_file_load_from_file(key_file, filename, 0, NULL); g_key_file_set_string(key_file, "General", "Name", name); data = g_key_file_to_data(key_file, &length, NULL); g_file_set_contents(filename, data, length, NULL); g_free(data); g_key_file_free(key_file); } static void browse_request_free(struct browse_req *req) { if (req->listener_id) g_dbus_remove_watch(dbus_conn, req->listener_id); if (req->msg) dbus_message_unref(req->msg); g_slist_free_full(req->profiles_added, g_free); if (req->records) sdp_list_free(req->records, (sdp_free_func_t) sdp_record_free); g_free(req); } static void attio_cleanup(struct btd_device *device) { if (device->attachid) { attrib_channel_detach(device->attrib, device->attachid); device->attachid = 0; } if (device->cleanup_id) { g_source_remove(device->cleanup_id); device->cleanup_id = 0; } if (device->att_io) { g_io_channel_shutdown(device->att_io, FALSE, NULL); g_io_channel_unref(device->att_io); device->att_io = NULL; } if (device->attrib) { GAttrib *attrib = device->attrib; device->attrib = NULL; g_attrib_cancel_all(attrib); g_attrib_unref(attrib); } } static void browse_request_cancel(struct browse_req *req) { struct btd_device *device = req->device; struct btd_adapter *adapter = device->adapter; bt_cancel_discovery(adapter_get_address(adapter), &device->bdaddr); attio_cleanup(device); device->browse = NULL; browse_request_free(req); } static void svc_dev_remove(gpointer user_data) { struct svc_callback *cb = user_data; if (cb->idle_id > 0) g_source_remove(cb->idle_id); cb->func(cb->dev, -ENODEV, cb->user_data); g_free(cb); } static void device_free(gpointer user_data) { struct btd_device *device = user_data; g_slist_free_full(device->uuids, g_free); g_slist_free_full(device->primaries, g_free); g_slist_free_full(device->attios, g_free); g_slist_free_full(device->attios_offline, g_free); g_slist_free_full(device->svc_callbacks, svc_dev_remove); attio_cleanup(device); if (device->tmp_records) sdp_list_free(device->tmp_records, (sdp_free_func_t) sdp_record_free); if (device->disconn_timer) g_source_remove(device->disconn_timer); if (device->discov_timer) g_source_remove(device->discov_timer); if (device->connect) dbus_message_unref(device->connect); if (device->disconnect) dbus_message_unref(device->disconnect); DBG("%p", device); if (device->authr) { if (device->authr->agent) agent_unref(device->authr->agent); g_free(device->authr->pincode); g_free(device->authr); } if (device->eir_uuids) g_slist_free_full(device->eir_uuids, g_free); g_free(device->path); g_free(device->alias); g_free(device->modalias); g_free(device); } gboolean device_is_bredr(struct btd_device *device) { return (device->bdaddr_type == BDADDR_BREDR); } gboolean device_is_le(struct btd_device *device) { return (device->bdaddr_type != BDADDR_BREDR); } gboolean device_is_paired(struct btd_device *device) { return device->paired; } gboolean device_is_bonded(struct btd_device *device) { return device->bonded; } gboolean device_is_trusted(struct btd_device *device) { return device->trusted; } static gboolean dev_property_get_address(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; char dstaddr[18]; const char *ptr = dstaddr; ba2str(&device->bdaddr, dstaddr); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ptr); return TRUE; } static gboolean dev_property_get_name(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; const char *empty = "", *ptr; ptr = device->name ?: empty; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ptr); return TRUE; } static gboolean dev_property_exists_name(const GDBusPropertyTable *property, void *data) { struct btd_device *dev = data; return device_name_known(dev); } static gboolean dev_property_get_alias(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; char dstaddr[18]; const char *ptr; /* Alias (fallback to name or address) */ if (device->alias != NULL) ptr = device->alias; else if (strlen(device->name) > 0) { ptr = device->name; } else { ba2str(&device->bdaddr, dstaddr); g_strdelimit(dstaddr, ":", '-'); ptr = dstaddr; } dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ptr); return TRUE; } static void set_alias(GDBusPendingPropertySet id, const char *alias, void *data) { struct btd_device *device = data; /* No change */ if ((device->alias == NULL && g_str_equal(alias, "")) || g_strcmp0(device->alias, alias) == 0) { g_dbus_pending_property_success(id); return; } g_free(device->alias); device->alias = g_str_equal(alias, "") ? NULL : g_strdup(alias); store_device_info(device); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Alias"); g_dbus_pending_property_success(id); } static void dev_property_set_alias(const GDBusPropertyTable *property, DBusMessageIter *value, GDBusPendingPropertySet id, void *data) { const char *alias; if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_STRING) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); return; } dbus_message_iter_get_basic(value, &alias); set_alias(id, alias, data); } static gboolean dev_property_exists_class(const GDBusPropertyTable *property, void *data) { struct btd_device *device = data; return device->class != 0; } static gboolean dev_property_get_class(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; if (device->class == 0) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &device->class); return TRUE; } static gboolean get_appearance(const GDBusPropertyTable *property, void *data, uint16_t *appearance) { struct btd_device *device = data; if (dev_property_exists_class(property, data)) return FALSE; if (device->appearance) { *appearance = device->appearance; return TRUE; } return FALSE; } static gboolean dev_property_exists_appearance( const GDBusPropertyTable *property, void *data) { uint16_t appearance; return get_appearance(property, data, &appearance); } static gboolean dev_property_get_appearance(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { uint16_t appearance; if (!get_appearance(property, data, &appearance)) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &appearance); return TRUE; } static const char *get_icon(const GDBusPropertyTable *property, void *data) { struct btd_device *device = data; const char *icon = NULL; uint16_t appearance; if (device->class != 0) icon = class_to_icon(device->class); else if (get_appearance(property, data, &appearance)) icon = gap_appearance_to_icon(appearance); return icon; } static gboolean dev_property_exists_icon( const GDBusPropertyTable *property, void *data) { return get_icon(property, data) != NULL; } static gboolean dev_property_get_icon(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { const char *icon; icon = get_icon(property, data); if (icon == NULL) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &icon); return TRUE; } static gboolean dev_property_get_paired(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; gboolean val = device_is_paired(device); dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val); return TRUE; } static gboolean dev_property_get_legacy(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; dbus_bool_t val = device->legacy; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val); return TRUE; } static gboolean dev_property_get_rssi(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *dev = data; dbus_int16_t val = dev->rssi; dbus_message_iter_append_basic(iter, DBUS_TYPE_INT16, &val); return TRUE; } static gboolean dev_property_exists_rssi(const GDBusPropertyTable *property, void *data) { struct btd_device *dev = data; if (dev->rssi == 0) return FALSE; return TRUE; } static gboolean dev_property_get_trusted(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; gboolean val = device_is_trusted(device); dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val); return TRUE; } static void set_trust(GDBusPendingPropertySet id, gboolean value, void *data) { struct btd_device *device = data; device_set_trusted(device, value); g_dbus_pending_property_success(id); } static void dev_property_set_trusted(const GDBusPropertyTable *property, DBusMessageIter *value, GDBusPendingPropertySet id, void *data) { dbus_bool_t b; if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); return; } dbus_message_iter_get_basic(value, &b); set_trust(id, b, data); } static gboolean dev_property_get_blocked(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &device->blocked); return TRUE; } static void set_blocked(GDBusPendingPropertySet id, gboolean value, void *data) { struct btd_device *device = data; int err; if (value) err = device_block(device, FALSE); else err = device_unblock(device, FALSE, FALSE); switch (-err) { case 0: g_dbus_pending_property_success(id); break; case EINVAL: g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed", "Kernel lacks blacklist support"); break; default: g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed", strerror(-err)); break; } } static void dev_property_set_blocked(const GDBusPropertyTable *property, DBusMessageIter *value, GDBusPendingPropertySet id, void *data) { dbus_bool_t b; if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); return; } dbus_message_iter_get_basic(value, &b); set_blocked(id, b, data); } static gboolean dev_property_get_connected(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &device->connected); return TRUE; } static gboolean dev_property_get_uuids(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; DBusMessageIter entry; GSList *l; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &entry); if (device->svc_resolved) l = device->uuids; else if (device->eir_uuids) l = device->eir_uuids; else l = device->uuids; for (; l != NULL; l = l->next) dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &l->data); dbus_message_iter_close_container(iter, &entry); return TRUE; } static gboolean dev_property_get_modalias(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; if (!device->modalias) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &device->modalias); return TRUE; } static gboolean dev_property_exists_modalias(const GDBusPropertyTable *property, void *data) { struct btd_device *device = data; return device->modalias ? TRUE : FALSE; } static gboolean dev_property_get_adapter(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; const char *str = adapter_get_path(device->adapter); dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &str); return TRUE; } static gboolean do_disconnect(gpointer user_data) { struct btd_device *device = user_data; device->disconn_timer = 0; btd_adapter_disconnect_device(device->adapter, &device->bdaddr, device->bdaddr_type); return FALSE; } int device_block(struct btd_device *device, gboolean update_only) { int err = 0; if (device->blocked) return 0; if (device->connected) do_disconnect(device); while (device->services != NULL) { struct btd_service *service = device->services->data; device->services = g_slist_remove(device->services, service); service_remove(service); } if (!update_only) err = btd_adapter_block_address(device->adapter, &device->bdaddr, device->bdaddr_type); if (err < 0) return err; device->blocked = TRUE; store_device_info(device); device_set_temporary(device, FALSE); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Blocked"); return 0; } int device_unblock(struct btd_device *device, gboolean silent, gboolean update_only) { int err = 0; if (!device->blocked) return 0; if (!update_only) err = btd_adapter_unblock_address(device->adapter, &device->bdaddr, device->bdaddr_type); if (err < 0) return err; device->blocked = FALSE; store_device_info(device); if (!silent) { g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Blocked"); device_probe_profiles(device, device->uuids); } return 0; } static void discover_services_req_exit(DBusConnection *conn, void *user_data) { struct browse_req *req = user_data; DBG("DiscoverServices requestor exited"); browse_request_cancel(req); } static void bonding_request_cancel(struct bonding_req *bonding) { struct btd_device *device = bonding->device; struct btd_adapter *adapter = device->adapter; adapter_cancel_bonding(adapter, &device->bdaddr, device->bdaddr_type); } static void dev_disconn_service(gpointer a, gpointer b) { btd_service_disconnect(a); } void device_request_disconnect(struct btd_device *device, DBusMessage *msg) { if (device->bonding) bonding_request_cancel(device->bonding); if (device->browse) browse_request_cancel(device->browse); if (device->connect) { DBusMessage *reply = btd_error_failed(device->connect, "Cancelled"); g_dbus_send_message(dbus_conn, reply); dbus_message_unref(device->connect); device->connect = NULL; } if (device->connected && msg) device->disconnects = g_slist_append(device->disconnects, dbus_message_ref(msg)); if (device->disconn_timer) return; g_slist_foreach(device->services, dev_disconn_service, NULL); g_slist_free(device->pending); device->pending = NULL; while (device->watches) { struct btd_disconnect_data *data = device->watches->data; if (data->watch) /* temporary is set if device is going to be removed */ data->watch(device, device->temporary, data->user_data); /* Check if the watch has been removed by callback function */ if (!g_slist_find(device->watches, data)) continue; device->watches = g_slist_remove(device->watches, data); g_free(data); } if (!device->connected) { g_dbus_send_reply(dbus_conn, msg, DBUS_TYPE_INVALID); return; } device->disconn_timer = g_timeout_add_seconds(DISCONNECT_TIMER, do_disconnect, device); } static DBusMessage *dev_disconnect(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_device *device = user_data; /* * Disable connections through passive scanning until * Device1.Connect is called */ if (device->auto_connect) device->disable_auto_connect = TRUE; device_request_disconnect(device, msg); return NULL; } static int connect_next(struct btd_device *dev) { struct btd_service *service; int err = -ENOENT; while (dev->pending) { service = dev->pending->data; if (btd_service_connect(service) == 0) return 0; dev->pending = g_slist_delete_link(dev->pending, dev->pending); } return err; } static void device_profile_connected(struct btd_device *dev, struct btd_profile *profile, int err) { struct btd_service *pending; GSList *l; DBG("%s %s (%d)", profile->name, strerror(-err), -err); if (!err) device_set_temporary(dev, FALSE); if (dev->pending == NULL) return; if (!dev->connected && err == -EHOSTDOWN) goto done; pending = dev->pending->data; l = find_service_with_profile(dev->pending, profile); if (l != NULL) dev->pending = g_slist_delete_link(dev->pending, l); /* Only continue connecting the next profile if it matches the first * pending, otherwise it will trigger another connect to the same * profile */ if (profile != btd_service_get_profile(pending)) return; if (connect_next(dev) == 0) return; done: if (!dev->connect) return; if (!err && dbus_message_is_method_call(dev->connect, DEVICE_INTERFACE, "Connect")) dev->general_connect = TRUE; DBG("returning response to %s", dbus_message_get_sender(dev->connect)); l = find_service_with_state(dev->services, BTD_SERVICE_STATE_CONNECTED); if (err && l == NULL) g_dbus_send_message(dbus_conn, btd_error_failed(dev->connect, strerror(-err))); else { /* Start passive SDP discovery to update known services */ if (device_is_bredr(dev) && !dev->svc_refreshed) device_browse_sdp(dev, NULL); g_dbus_send_reply(dbus_conn, dev->connect, DBUS_TYPE_INVALID); } g_slist_free(dev->pending); dev->pending = NULL; dbus_message_unref(dev->connect); dev->connect = NULL; } void device_add_eir_uuids(struct btd_device *dev, GSList *uuids) { GSList *l; bool added = false; if (dev->svc_resolved) return; for (l = uuids; l != NULL; l = l->next) { const char *str = l->data; if (g_slist_find_custom(dev->eir_uuids, str, bt_uuid_strcmp)) continue; added = true; dev->eir_uuids = g_slist_append(dev->eir_uuids, g_strdup(str)); } if (added) g_dbus_emit_property_changed(dbus_conn, dev->path, DEVICE_INTERFACE, "UUIDs"); } static struct btd_service *find_connectable_service(struct btd_device *dev, const char *uuid) { GSList *l; for (l = dev->services; l != NULL; l = g_slist_next(l)) { struct btd_service *service = l->data; struct btd_profile *p = btd_service_get_profile(service); if (!p->connect || !p->remote_uuid) continue; if (strcasecmp(uuid, p->remote_uuid) == 0) return service; } return NULL; } static int service_prio_cmp(gconstpointer a, gconstpointer b) { struct btd_profile *p1 = btd_service_get_profile(a); struct btd_profile *p2 = btd_service_get_profile(b); return p2->priority - p1->priority; } static GSList *create_pending_list(struct btd_device *dev, const char *uuid) { struct btd_service *service; struct btd_profile *p; GSList *l; if (uuid) { service = find_connectable_service(dev, uuid); if (service) return g_slist_prepend(dev->pending, service); return dev->pending; } for (l = dev->services; l != NULL; l = g_slist_next(l)) { service = l->data; p = btd_service_get_profile(service); if (!p->auto_connect) continue; if (g_slist_find(dev->pending, service)) continue; if (btd_service_get_state(service) != BTD_SERVICE_STATE_DISCONNECTED) continue; dev->pending = g_slist_insert_sorted(dev->pending, service, service_prio_cmp); } return dev->pending; } static DBusMessage *connect_profiles(struct btd_device *dev, DBusMessage *msg, const char *uuid) { int err; DBG("%s %s, client %s", dev->path, uuid ? uuid : "(all)", dbus_message_get_sender(msg)); if (dev->pending || dev->connect || dev->browse) return btd_error_in_progress(msg); device_set_temporary(dev, FALSE); if (!dev->svc_resolved) goto resolve_services; dev->pending = create_pending_list(dev, uuid); if (!dev->pending) { if (dev->svc_refreshed) { if (find_service_with_state(dev->services, BTD_SERVICE_STATE_CONNECTED)) return dbus_message_new_method_return(msg); else return btd_error_not_available(msg); } goto resolve_services; } err = connect_next(dev); if (err < 0) return btd_error_failed(msg, strerror(-err)); dev->connect = dbus_message_ref(msg); return NULL; resolve_services: DBG("Resolving services for %s", dev->path); if (device_is_bredr(dev)) err = device_browse_sdp(dev, msg); else err = device_browse_primary(dev, msg); if (err < 0) return btd_error_failed(msg, strerror(-err)); return NULL; } static DBusMessage *dev_connect(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_device *dev = user_data; if (device_is_le(dev)) { int err; if (device_is_connected(dev)) return dbus_message_new_method_return(msg); device_set_temporary(dev, FALSE); dev->disable_auto_connect = FALSE; err = device_connect_le(dev); if (err < 0) return btd_error_failed(msg, strerror(-err)); dev->connect = dbus_message_ref(msg); return NULL; } return connect_profiles(dev, msg, NULL); } static DBusMessage *connect_profile(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_device *dev = user_data; const char *pattern; char *uuid; DBusMessage *reply; if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &pattern, DBUS_TYPE_INVALID)) return btd_error_invalid_args(msg); uuid = bt_name2string(pattern); reply = connect_profiles(dev, msg, uuid); g_free(uuid); return reply; } static void device_profile_disconnected(struct btd_device *dev, struct btd_profile *profile, int err) { if (!dev->disconnect) return; if (err) g_dbus_send_message(dbus_conn, btd_error_failed(dev->disconnect, strerror(-err))); else g_dbus_send_reply(dbus_conn, dev->disconnect, DBUS_TYPE_INVALID); dbus_message_unref(dev->disconnect); dev->disconnect = NULL; } static DBusMessage *disconnect_profile(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_device *dev = user_data; struct btd_service *service; const char *pattern; char *uuid; int err; if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &pattern, DBUS_TYPE_INVALID)) return btd_error_invalid_args(msg); uuid = bt_name2string(pattern); if (uuid == NULL) return btd_error_invalid_args(msg); service = find_connectable_service(dev, uuid); g_free(uuid); if (!service) return btd_error_invalid_args(msg); err = btd_service_disconnect(service); if (err == 0) { dev->disconnect = dbus_message_ref(msg); return NULL; } if (err == -ENOTSUP) return btd_error_not_supported(msg); return btd_error_failed(msg, strerror(-err)); } static void device_svc_resolved(struct btd_device *dev, int err) { DBusMessage *reply; struct browse_req *req = dev->browse; DBG("%s err %d", dev->path, err); dev->svc_resolved = true; dev->browse = NULL; /* Disconnection notification can happen before this function * gets called, so don't set svc_refreshed for a disconnected * device. */ if (dev->connected) dev->svc_refreshed = true; g_slist_free_full(dev->eir_uuids, g_free); dev->eir_uuids = NULL; if (dev->pending_paired) { g_dbus_emit_property_changed(dbus_conn, dev->path, DEVICE_INTERFACE, "Paired"); dev->pending_paired = false; } while (dev->svc_callbacks) { struct svc_callback *cb = dev->svc_callbacks->data; if (cb->idle_id > 0) g_source_remove(cb->idle_id); cb->func(dev, err, cb->user_data); dev->svc_callbacks = g_slist_delete_link(dev->svc_callbacks, dev->svc_callbacks); g_free(cb); } if (!req || !req->msg) return; if (dbus_message_is_method_call(req->msg, DEVICE_INTERFACE, "Pair")) { g_dbus_send_reply(dbus_conn, req->msg, DBUS_TYPE_INVALID); return; } if (err) { reply = btd_error_failed(req->msg, strerror(-err)); g_dbus_send_message(dbus_conn, reply); return; } if (dbus_message_is_method_call(req->msg, DEVICE_INTERFACE, "Connect")) reply = dev_connect(dbus_conn, req->msg, dev); else if (dbus_message_is_method_call(req->msg, DEVICE_INTERFACE, "ConnectProfile")) reply = connect_profile(dbus_conn, req->msg, dev); else return; dbus_message_unref(req->msg); req->msg = NULL; if (reply) g_dbus_send_message(dbus_conn, reply); } static struct bonding_req *bonding_request_new(DBusMessage *msg, struct btd_device *device, struct agent *agent) { struct bonding_req *bonding; char addr[18]; ba2str(&device->bdaddr, addr); DBG("Requesting bonding for %s", addr); bonding = g_new0(struct bonding_req, 1); bonding->msg = dbus_message_ref(msg); bonding->cb_iter = btd_adapter_pin_cb_iter_new(device->adapter); /* Marks the bonding start time for the first attempt on request * construction. The following attempts will be updated on * device_bonding_retry. */ clock_gettime(CLOCK_MONOTONIC, &bonding->attempt_start_time); if (agent) bonding->agent = agent_ref(agent); return bonding; } void device_bonding_restart_timer(struct btd_device *device) { if (!device || !device->bonding) return; clock_gettime(CLOCK_MONOTONIC, &device->bonding->attempt_start_time); } static void bonding_request_stop_timer(struct bonding_req *bonding) { struct timespec current; clock_gettime(CLOCK_MONOTONIC, ¤t); /* Compute the time difference in ms. */ bonding->last_attempt_duration_ms = (current.tv_sec - bonding->attempt_start_time.tv_sec) * 1000L + (current.tv_nsec - bonding->attempt_start_time.tv_nsec) / 1000000L; } /* Returns the duration of the last bonding attempt in milliseconds. The * duration is measured starting from the latest of the following three * events and finishing when the Command complete event is received for the * authentication request: * - MGMT_OP_PAIR_DEVICE is sent, * - MGMT_OP_PIN_CODE_REPLY is sent and * - Command complete event is received for the sent MGMT_OP_PIN_CODE_REPLY. */ long device_bonding_last_duration(struct btd_device *device) { struct bonding_req *bonding = device->bonding; if (!bonding) return 0; return bonding->last_attempt_duration_ms; } static void create_bond_req_exit(DBusConnection *conn, void *user_data) { struct btd_device *device = user_data; char addr[18]; ba2str(&device->bdaddr, addr); DBG("%s: requestor exited before bonding was completed", addr); if (device->authr) device_cancel_authentication(device, FALSE); if (device->bonding) { device->bonding->listener_id = 0; device_request_disconnect(device, NULL); } } static DBusMessage *pair_device(DBusConnection *conn, DBusMessage *msg, void *data) { struct btd_device *device = data; struct btd_adapter *adapter = device->adapter; const char *sender; struct agent *agent; struct bonding_req *bonding; uint8_t io_cap; int err; device_set_temporary(device, FALSE); if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_INVALID)) return btd_error_invalid_args(msg); if (device->bonding) return btd_error_in_progress(msg); if (device_is_bonded(device)) return btd_error_already_exists(msg); sender = dbus_message_get_sender(msg); agent = agent_get(sender); if (agent) io_cap = agent_get_io_capability(agent); else io_cap = IO_CAPABILITY_NOINPUTNOOUTPUT; bonding = bonding_request_new(msg, device, agent); if (agent) agent_unref(agent); bonding->listener_id = g_dbus_add_disconnect_watch(dbus_conn, sender, create_bond_req_exit, device, NULL); device->bonding = bonding; bonding->device = device; /* Due to a bug in the kernel we might loose out on ATT commands * that arrive during the SMP procedure, so connect the ATT * channel first and only then start pairing (there's code for * this in the ATT connect callback) */ if (device_is_le(device) && !device_is_connected(device)) err = device_connect_le(device); else err = adapter_create_bonding(adapter, &device->bdaddr, device->bdaddr_type, io_cap); if (err < 0) return btd_error_failed(msg, strerror(-err)); return NULL; } static DBusMessage *new_authentication_return(DBusMessage *msg, uint8_t status) { switch (status) { case MGMT_STATUS_SUCCESS: return dbus_message_new_method_return(msg); case MGMT_STATUS_CONNECT_FAILED: return dbus_message_new_error(msg, ERROR_INTERFACE ".ConnectionAttemptFailed", "Page Timeout"); case MGMT_STATUS_TIMEOUT: return dbus_message_new_error(msg, ERROR_INTERFACE ".AuthenticationTimeout", "Authentication Timeout"); case MGMT_STATUS_BUSY: case MGMT_STATUS_REJECTED: return dbus_message_new_error(msg, ERROR_INTERFACE ".AuthenticationRejected", "Authentication Rejected"); case MGMT_STATUS_CANCELLED: case MGMT_STATUS_NO_RESOURCES: case MGMT_STATUS_DISCONNECTED: return dbus_message_new_error(msg, ERROR_INTERFACE ".AuthenticationCanceled", "Authentication Canceled"); default: return dbus_message_new_error(msg, ERROR_INTERFACE ".AuthenticationFailed", "Authentication Failed"); } } static void bonding_request_free(struct bonding_req *bonding) { if (!bonding) return; if (bonding->listener_id) g_dbus_remove_watch(dbus_conn, bonding->listener_id); if (bonding->msg) dbus_message_unref(bonding->msg); if (bonding->cb_iter) g_free(bonding->cb_iter); if (bonding->agent) { agent_cancel(bonding->agent); agent_unref(bonding->agent); bonding->agent = NULL; } if (bonding->retry_timer) g_source_remove(bonding->retry_timer); if (bonding->device) bonding->device->bonding = NULL; g_free(bonding); } static void device_cancel_bonding(struct btd_device *device, uint8_t status) { struct bonding_req *bonding = device->bonding; DBusMessage *reply; char addr[18]; if (!bonding) return; ba2str(&device->bdaddr, addr); DBG("Canceling bonding request for %s", addr); if (device->authr) device_cancel_authentication(device, FALSE); reply = new_authentication_return(bonding->msg, status); g_dbus_send_message(dbus_conn, reply); bonding_request_cancel(bonding); bonding_request_free(bonding); } static DBusMessage *cancel_pairing(DBusConnection *conn, DBusMessage *msg, void *data) { struct btd_device *device = data; struct bonding_req *req = device->bonding; DBG(""); if (!req) return btd_error_does_not_exist(msg); device_cancel_bonding(device, MGMT_STATUS_CANCELLED); return dbus_message_new_method_return(msg); } static const GDBusMethodTable device_methods[] = { { GDBUS_ASYNC_METHOD("Disconnect", NULL, NULL, dev_disconnect) }, { GDBUS_ASYNC_METHOD("Connect", NULL, NULL, dev_connect) }, { GDBUS_ASYNC_METHOD("ConnectProfile", GDBUS_ARGS({ "UUID", "s" }), NULL, connect_profile) }, { GDBUS_ASYNC_METHOD("DisconnectProfile", GDBUS_ARGS({ "UUID", "s" }), NULL, disconnect_profile) }, { GDBUS_ASYNC_METHOD("Pair", NULL, NULL, pair_device) }, { GDBUS_METHOD("CancelPairing", NULL, NULL, cancel_pairing) }, { } }; static const GDBusPropertyTable device_properties[] = { { "Address", "s", dev_property_get_address }, { "Name", "s", dev_property_get_name, NULL, dev_property_exists_name }, { "Alias", "s", dev_property_get_alias, dev_property_set_alias }, { "Class", "u", dev_property_get_class, NULL, dev_property_exists_class }, { "Appearance", "q", dev_property_get_appearance, NULL, dev_property_exists_appearance }, { "Icon", "s", dev_property_get_icon, NULL, dev_property_exists_icon }, { "Paired", "b", dev_property_get_paired }, { "Trusted", "b", dev_property_get_trusted, dev_property_set_trusted }, { "Blocked", "b", dev_property_get_blocked, dev_property_set_blocked }, { "LegacyPairing", "b", dev_property_get_legacy }, { "RSSI", "n", dev_property_get_rssi, NULL, dev_property_exists_rssi }, { "Connected", "b", dev_property_get_connected }, { "UUIDs", "as", dev_property_get_uuids }, { "Modalias", "s", dev_property_get_modalias, NULL, dev_property_exists_modalias }, { "Adapter", "o", dev_property_get_adapter }, { } }; gboolean device_is_connected(struct btd_device *device) { return device->connected; } void device_add_connection(struct btd_device *device) { if (device->connected) { char addr[18]; ba2str(&device->bdaddr, addr); error("Device %s is already connected", addr); return; } device->connected = TRUE; g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Connected"); } void device_remove_connection(struct btd_device *device) { if (!device->connected) { char addr[18]; ba2str(&device->bdaddr, addr); error("Device %s isn't connected", addr); return; } device->connected = FALSE; device->general_connect = FALSE; device->svc_refreshed = false; if (device->disconn_timer > 0) { g_source_remove(device->disconn_timer); device->disconn_timer = 0; } while (device->disconnects) { DBusMessage *msg = device->disconnects->data; g_dbus_send_reply(dbus_conn, msg, DBUS_TYPE_INVALID); device->disconnects = g_slist_remove(device->disconnects, msg); dbus_message_unref(msg); } if (device_is_paired(device) && !device_is_bonded(device)) device_set_paired(device, FALSE); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Connected"); } guint device_add_disconnect_watch(struct btd_device *device, disconnect_watch watch, void *user_data, GDestroyNotify destroy) { struct btd_disconnect_data *data; static guint id = 0; data = g_new0(struct btd_disconnect_data, 1); data->id = ++id; data->watch = watch; data->user_data = user_data; data->destroy = destroy; device->watches = g_slist_append(device->watches, data); return data->id; } void device_remove_disconnect_watch(struct btd_device *device, guint id) { GSList *l; for (l = device->watches; l; l = l->next) { struct btd_disconnect_data *data = l->data; if (data->id == id) { device->watches = g_slist_remove(device->watches, data); if (data->destroy) data->destroy(data->user_data); g_free(data); return; } } } static char *load_cached_name(struct btd_device *device, const char *local, const char *peer) { char filename[PATH_MAX + 1]; GKeyFile *key_file; char *str = NULL; int len; snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", local, peer); filename[PATH_MAX] = '\0'; key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, NULL)) goto failed; str = g_key_file_get_string(key_file, "General", "Name", NULL); if (str) { len = strlen(str); if (len > HCI_MAX_NAME_LENGTH) str[HCI_MAX_NAME_LENGTH] = '\0'; } failed: g_key_file_free(key_file); return str; } static void load_info(struct btd_device *device, const char *local, const char *peer, GKeyFile *key_file) { char *str; gboolean store_needed = FALSE; gboolean blocked; char **uuids; int source, vendor, product, version; char **techno, **t; gboolean bredr = FALSE; gboolean le = FALSE; /* Load device name from storage info file, if that fails fall back to * the cache. */ str = g_key_file_get_string(key_file, "General", "Name", NULL); if (str == NULL) { str = load_cached_name(device, local, peer); if (str) store_needed = TRUE; } if (str) { strcpy(device->name, str); g_free(str); } /* Load alias */ device->alias = g_key_file_get_string(key_file, "General", "Alias", NULL); /* Load class */ str = g_key_file_get_string(key_file, "General", "Class", NULL); if (str) { uint32_t class; if (sscanf(str, "%x", &class) == 1) device->class = class; g_free(str); } /* Load appearance */ str = g_key_file_get_string(key_file, "General", "Appearance", NULL); if (str) { device->appearance = strtol(str, NULL, 16); g_free(str); } /* Load device technology */ techno = g_key_file_get_string_list(key_file, "General", "SupportedTechnologies", NULL, NULL); if (!techno) goto next; for (t = techno; *t; t++) { if (g_str_equal(*t, "BR/EDR")) bredr = TRUE; else if (g_str_equal(*t, "LE")) le = TRUE; else error("Unknown device technology"); } if (bredr && le) { /* TODO: Add correct type for dual mode device */ } else if (bredr) { device->bdaddr_type = BDADDR_BREDR; } else if (le) { str = g_key_file_get_string(key_file, "General", "AddressType", NULL); if (str && g_str_equal(str, "public")) device->bdaddr_type = BDADDR_LE_PUBLIC; else if (str && g_str_equal(str, "static")) device->bdaddr_type = BDADDR_LE_RANDOM; else error("Unknown LE device technology"); g_free(str); } g_strfreev(techno); next: /* Load trust */ device->trusted = g_key_file_get_boolean(key_file, "General", "Trusted", NULL); /* Load device blocked */ blocked = g_key_file_get_boolean(key_file, "General", "Blocked", NULL); if (blocked) device_block(device, FALSE); /* Load device profile list */ uuids = g_key_file_get_string_list(key_file, "General", "Services", NULL, NULL); if (uuids) { char **uuid; for (uuid = uuids; *uuid; uuid++) { GSList *match; match = g_slist_find_custom(device->uuids, *uuid, bt_uuid_strcmp); if (match) continue; device->uuids = g_slist_insert_sorted(device->uuids, g_strdup(*uuid), bt_uuid_strcmp); } g_strfreev(uuids); /* Discovered services restored from storage */ device->svc_resolved = true; } /* Load device id */ source = g_key_file_get_integer(key_file, "DeviceID", "Source", NULL); if (source) { vendor = g_key_file_get_integer(key_file, "DeviceID", "Vendor", NULL); product = g_key_file_get_integer(key_file, "DeviceID", "Product", NULL); version = g_key_file_get_integer(key_file, "DeviceID", "Version", NULL); btd_device_set_pnpid(device, source, vendor, product, version); } if (store_needed) store_device_info(device); } static void load_att_info(struct btd_device *device, const char *local, const char *peer) { char filename[PATH_MAX + 1]; GKeyFile *key_file; char *prim_uuid, *str; char **groups, **handle, *service_uuid; struct gatt_primary *prim; uuid_t uuid; char tmp[3]; int i; sdp_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); prim_uuid = bt_uuid2string(&uuid); snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/attributes", local, peer); filename[PATH_MAX] = '\0'; key_file = g_key_file_new(); g_key_file_load_from_file(key_file, filename, 0, NULL); groups = g_key_file_get_groups(key_file, NULL); for (handle = groups; *handle; handle++) { gboolean uuid_ok; int end; str = g_key_file_get_string(key_file, *handle, "UUID", NULL); if (!str) continue; uuid_ok = g_str_equal(str, prim_uuid); g_free(str); if (!uuid_ok) continue; str = g_key_file_get_string(key_file, *handle, "Value", NULL); if (!str) continue; end = g_key_file_get_integer(key_file, *handle, "EndGroupHandle", NULL); if (end == 0) { g_free(str); continue; } prim = g_new0(struct gatt_primary, 1); prim->range.start = atoi(*handle); prim->range.end = end; switch (strlen(str)) { case 4: uuid.type = SDP_UUID16; sscanf(str, "%04hx", &uuid.value.uuid16); break; case 8: uuid.type = SDP_UUID32; sscanf(str, "%08x", &uuid.value.uuid32); break; case 32: uuid.type = SDP_UUID128; memset(tmp, 0, sizeof(tmp)); for (i = 0; i < 16; i++) { memcpy(tmp, str + (i * 2), 2); uuid.value.uuid128.data[i] = (uint8_t) strtol(tmp, NULL, 16); } break; default: g_free(str); g_free(prim); continue; } service_uuid = bt_uuid2string(&uuid); memcpy(prim->uuid, service_uuid, MAX_LEN_UUID_STR); g_free(service_uuid); g_free(str); device->primaries = g_slist_append(device->primaries, prim); } g_strfreev(groups); g_key_file_free(key_file); g_free(prim_uuid); } static struct btd_device *device_new(struct btd_adapter *adapter, const char *address) { char *address_up; struct btd_device *device; const char *adapter_path = adapter_get_path(adapter); DBG("address %s", address); device = g_try_malloc0(sizeof(struct btd_device)); if (device == NULL) return NULL; address_up = g_ascii_strup(address, -1); device->path = g_strdup_printf("%s/dev_%s", adapter_path, address_up); g_strdelimit(device->path, ":", '_'); g_free(address_up); DBG("Creating device %s", device->path); if (g_dbus_register_interface(dbus_conn, device->path, DEVICE_INTERFACE, device_methods, NULL, device_properties, device, device_free) == FALSE) { error("Unable to register device interface for %s", address); device_free(device); return NULL; } str2ba(address, &device->bdaddr); device->adapter = adapter; return btd_device_ref(device); } struct btd_device *device_create_from_storage(struct btd_adapter *adapter, const char *address, GKeyFile *key_file) { struct btd_device *device; const bdaddr_t *src; char srcaddr[18]; DBG("address %s", address); device = device_new(adapter, address); if (device == NULL) return NULL; src = adapter_get_address(adapter); ba2str(src, srcaddr); load_info(device, srcaddr, address, key_file); load_att_info(device, srcaddr, address); return device; } struct btd_device *device_create(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t bdaddr_type) { struct btd_device *device; const bdaddr_t *sba; char src[18], dst[18]; char *str; ba2str(bdaddr, dst); DBG("dst %s", dst); device = device_new(adapter, dst); if (device == NULL) return NULL; device->bdaddr_type = bdaddr_type; sba = adapter_get_address(adapter); ba2str(sba, src); str = load_cached_name(device, src, dst); if (str) { strcpy(device->name, str); g_free(str); } return device; } char *btd_device_get_storage_path(struct btd_device *device, const char *filename) { char srcaddr[18], dstaddr[18]; if (device_address_is_private(device)) { warn("Refusing storage path for private addressed device %s", device->path); return NULL; } ba2str(adapter_get_address(device->adapter), srcaddr); ba2str(&device->bdaddr, dstaddr); if (!filename) return g_strdup_printf(STORAGEDIR "/%s/%s", srcaddr, dstaddr); return g_strdup_printf(STORAGEDIR "/%s/%s/%s", srcaddr, dstaddr, filename); } void device_set_name(struct btd_device *device, const char *name) { if (strncmp(name, device->name, MAX_NAME_LENGTH) == 0) return; DBG("%s %s", device->path, name); strncpy(device->name, name, MAX_NAME_LENGTH); store_device_info(device); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Name"); if (device->alias != NULL) return; g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Alias"); } void device_get_name(struct btd_device *device, char *name, size_t len) { strncpy(name, device->name, len); } bool device_name_known(struct btd_device *device) { return device->name[0] != '\0'; } void device_set_class(struct btd_device *device, uint32_t class) { if (device->class == class) return; DBG("%s 0x%06X", device->path, class); device->class = class; store_device_info(device); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Class"); } uint32_t btd_device_get_class(struct btd_device *device) { return device->class; } uint16_t btd_device_get_vendor(struct btd_device *device) { return device->vendor; } uint16_t btd_device_get_vendor_src(struct btd_device *device) { return device->vendor_src; } uint16_t btd_device_get_product(struct btd_device *device) { return device->product; } uint16_t btd_device_get_version(struct btd_device *device) { return device->version; } static void delete_folder_tree(const char *dirname) { DIR *dir; struct dirent *entry; char filename[PATH_MAX + 1]; dir = opendir(dirname); if (dir == NULL) return; while ((entry = readdir(dir)) != NULL) { if (g_str_equal(entry->d_name, ".") || g_str_equal(entry->d_name, "..")) continue; snprintf(filename, PATH_MAX, "%s/%s", dirname, entry->d_name); filename[PATH_MAX] = '\0'; if (entry->d_type == DT_DIR) delete_folder_tree(filename); else unlink(filename); } closedir(dir); rmdir(dirname); } static void device_remove_stored(struct btd_device *device) { const bdaddr_t *src = adapter_get_address(device->adapter); uint8_t dst_type = device->bdaddr_type; char adapter_addr[18]; char device_addr[18]; char filename[PATH_MAX + 1]; GKeyFile *key_file; char *data; gsize length = 0; if (device_is_bonded(device)) { device_set_bonded(device, FALSE); device->paired = FALSE; btd_adapter_remove_bonding(device->adapter, &device->bdaddr, dst_type); } if (device->blocked) device_unblock(device, TRUE, FALSE); ba2str(src, adapter_addr); ba2str(&device->bdaddr, device_addr); snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s", adapter_addr, device_addr); filename[PATH_MAX] = '\0'; delete_folder_tree(filename); snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", adapter_addr, device_addr); filename[PATH_MAX] = '\0'; key_file = g_key_file_new(); g_key_file_load_from_file(key_file, filename, 0, NULL); g_key_file_remove_group(key_file, "ServiceRecords", NULL); data = g_key_file_to_data(key_file, &length, NULL); if (length > 0) { create_file(filename, S_IRUSR | S_IWUSR); g_file_set_contents(filename, data, length, NULL); } g_free(data); g_key_file_free(key_file); } void device_remove(struct btd_device *device, gboolean remove_stored) { DBG("Removing device %s", device->path); if (device->bonding) { uint8_t status; if (device->connected) status = MGMT_STATUS_DISCONNECTED; else status = MGMT_STATUS_CONNECT_FAILED; device_cancel_bonding(device, status); } if (device->browse) browse_request_cancel(device->browse); while (device->services != NULL) { struct btd_service *service = device->services->data; device->services = g_slist_remove(device->services, service); service_remove(service); } g_slist_free(device->pending); device->pending = NULL; if (device->connected) do_disconnect(device); if (device->store_id > 0) { g_source_remove(device->store_id); device->store_id = 0; if (!remove_stored) store_device_info_cb(device); } if (remove_stored) device_remove_stored(device); btd_device_unref(device); } int device_address_cmp(gconstpointer a, gconstpointer b) { const struct btd_device *device = a; const char *address = b; char addr[18]; ba2str(&device->bdaddr, addr); return strcasecmp(addr, address); } int device_bdaddr_cmp(gconstpointer a, gconstpointer b) { const struct btd_device *device = a; const bdaddr_t *bdaddr = b; return bacmp(&device->bdaddr, bdaddr); } static gboolean record_has_uuid(const sdp_record_t *rec, const char *profile_uuid) { sdp_list_t *pat; for (pat = rec->pattern; pat != NULL; pat = pat->next) { char *uuid; int ret; uuid = bt_uuid2string(pat->data); if (!uuid) continue; ret = strcasecmp(uuid, profile_uuid); g_free(uuid); if (ret == 0) return TRUE; } return FALSE; } GSList *device_get_uuids(struct btd_device *device) { return device->uuids; } static bool device_match_profile(struct btd_device *device, struct btd_profile *profile, GSList *uuids) { if (profile->remote_uuid == NULL) return false; if (g_slist_find_custom(uuids, profile->remote_uuid, bt_uuid_strcmp) == NULL) return false; return true; } struct probe_data { struct btd_device *dev; GSList *uuids; char addr[18]; }; static void dev_probe(struct btd_profile *p, void *user_data) { struct probe_data *d = user_data; struct btd_service *service; if (p->device_probe == NULL) return; if (!device_match_profile(d->dev, p, d->uuids)) return; service = service_create(d->dev, p); if (service_probe(service) < 0) { btd_service_unref(service); return; } d->dev->services = g_slist_append(d->dev->services, service); } void device_probe_profile(gpointer a, gpointer b) { struct btd_device *device = a; struct btd_profile *profile = b; struct btd_service *service; if (profile->device_probe == NULL) return; if (!device_match_profile(device, profile, device->uuids)) return; service = service_create(device, profile); if (service_probe(service) < 0) { btd_service_unref(service); return; } device->services = g_slist_append(device->services, service); if (!profile->auto_connect || !device->general_connect) return; device->pending = g_slist_append(device->pending, service); if (g_slist_length(device->pending) == 1) connect_next(device); } void device_remove_profile(gpointer a, gpointer b) { struct btd_device *device = a; struct btd_profile *profile = b; struct btd_service *service; GSList *l; l = find_service_with_profile(device->services, profile); if (l == NULL) return; service = l->data; device->services = g_slist_delete_link(device->services, l); device->pending = g_slist_remove(device->pending, service); service_remove(service); } void device_probe_profiles(struct btd_device *device, GSList *uuids) { struct probe_data d = { device, uuids }; GSList *l; ba2str(&device->bdaddr, d.addr); if (device->blocked) { DBG("Skipping profiles for blocked device %s", d.addr); goto add_uuids; } DBG("Probing profiles for device %s", d.addr); btd_profile_foreach(dev_probe, &d); add_uuids: for (l = uuids; l != NULL; l = g_slist_next(l)) { GSList *match = g_slist_find_custom(device->uuids, l->data, bt_uuid_strcmp); if (match) continue; device->uuids = g_slist_insert_sorted(device->uuids, g_strdup(l->data), bt_uuid_strcmp); } g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "UUIDs"); } static void store_sdp_record(GKeyFile *key_file, sdp_record_t *rec) { char handle_str[11]; sdp_buf_t buf; int size, i; char *str; sprintf(handle_str, "0x%8.8X", rec->handle); if (sdp_gen_record_pdu(rec, &buf) < 0) return; size = buf.data_size; str = g_malloc0(size*2+1); for (i = 0; i < size; i++) sprintf(str + (i * 2), "%02X", buf.data[i]); g_key_file_set_string(key_file, "ServiceRecords", handle_str, str); free(buf.data); g_free(str); } static void store_primaries_from_sdp_record(GKeyFile *key_file, sdp_record_t *rec) { uuid_t uuid; char *att_uuid, *prim_uuid; uint16_t start = 0, end = 0, psm = 0; char handle[6], uuid_str[33]; int i; sdp_uuid16_create(&uuid, ATT_UUID); att_uuid = bt_uuid2string(&uuid); sdp_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); prim_uuid = bt_uuid2string(&uuid); if (!record_has_uuid(rec, att_uuid)) goto done; if (!gatt_parse_record(rec, &uuid, &psm, &start, &end)) goto done; sprintf(handle, "%hu", start); switch (uuid.type) { case SDP_UUID16: sprintf(uuid_str, "%4.4X", uuid.value.uuid16); break; case SDP_UUID32: sprintf(uuid_str, "%8.8X", uuid.value.uuid32); break; case SDP_UUID128: for (i = 0; i < 16; i++) sprintf(uuid_str + (i * 2), "%2.2X", uuid.value.uuid128.data[i]); break; default: uuid_str[0] = '\0'; } g_key_file_set_string(key_file, handle, "UUID", prim_uuid); g_key_file_set_string(key_file, handle, "Value", uuid_str); g_key_file_set_integer(key_file, handle, "EndGroupHandle", end); done: g_free(prim_uuid); g_free(att_uuid); } static int rec_cmp(const void *a, const void *b) { const sdp_record_t *r1 = a; const sdp_record_t *r2 = b; return r1->handle - r2->handle; } static int update_record(struct browse_req *req, const char *uuid, sdp_record_t *rec) { GSList *l; /* Check for duplicates */ if (sdp_list_find(req->records, rec, rec_cmp)) return -EALREADY; /* Copy record */ req->records = sdp_list_append(req->records, sdp_copy_record(rec)); /* Check if UUID is duplicated */ l = g_slist_find_custom(req->device->uuids, uuid, bt_uuid_strcmp); if (l == NULL) { l = g_slist_find_custom(req->profiles_added, uuid, bt_uuid_strcmp); if (l != NULL) return 0; req->profiles_added = g_slist_append(req->profiles_added, g_strdup(uuid)); } return 0; } static void update_bredr_services(struct browse_req *req, sdp_list_t *recs) { struct btd_device *device = req->device; sdp_list_t *seq; char srcaddr[18], dstaddr[18]; char sdp_file[PATH_MAX + 1]; char att_file[PATH_MAX + 1]; GKeyFile *sdp_key_file = NULL; GKeyFile *att_key_file = NULL; char *data; gsize length = 0; ba2str(adapter_get_address(device->adapter), srcaddr); ba2str(&device->bdaddr, dstaddr); if (!device->temporary) { snprintf(sdp_file, PATH_MAX, STORAGEDIR "/%s/cache/%s", srcaddr, dstaddr); sdp_file[PATH_MAX] = '\0'; sdp_key_file = g_key_file_new(); g_key_file_load_from_file(sdp_key_file, sdp_file, 0, NULL); snprintf(att_file, PATH_MAX, STORAGEDIR "/%s/%s/attributes", srcaddr, dstaddr); att_file[PATH_MAX] = '\0'; att_key_file = g_key_file_new(); g_key_file_load_from_file(att_key_file, att_file, 0, NULL); } for (seq = recs; seq; seq = seq->next) { sdp_record_t *rec = (sdp_record_t *) seq->data; sdp_list_t *svcclass = NULL; char *profile_uuid; if (!rec) break; if (sdp_get_service_classes(rec, &svcclass) < 0) continue; /* Check for empty service classes list */ if (svcclass == NULL) { DBG("Skipping record with no service classes"); continue; } /* Extract the first element and skip the remainning */ profile_uuid = bt_uuid2string(svcclass->data); if (!profile_uuid) { sdp_list_free(svcclass, free); continue; } if (bt_uuid_strcmp(profile_uuid, PNP_UUID) == 0) { uint16_t source, vendor, product, version; sdp_data_t *pdlist; pdlist = sdp_data_get(rec, SDP_ATTR_VENDOR_ID_SOURCE); source = pdlist ? pdlist->val.uint16 : 0x0000; pdlist = sdp_data_get(rec, SDP_ATTR_VENDOR_ID); vendor = pdlist ? pdlist->val.uint16 : 0x0000; pdlist = sdp_data_get(rec, SDP_ATTR_PRODUCT_ID); product = pdlist ? pdlist->val.uint16 : 0x0000; pdlist = sdp_data_get(rec, SDP_ATTR_VERSION); version = pdlist ? pdlist->val.uint16 : 0x0000; if (source || vendor || product || version) btd_device_set_pnpid(device, source, vendor, product, version); } if (update_record(req, profile_uuid, rec) < 0) goto next; if (sdp_key_file) store_sdp_record(sdp_key_file, rec); if (att_key_file) store_primaries_from_sdp_record(att_key_file, rec); next: g_free(profile_uuid); sdp_list_free(svcclass, free); } if (sdp_key_file) { data = g_key_file_to_data(sdp_key_file, &length, NULL); if (length > 0) { create_file(sdp_file, S_IRUSR | S_IWUSR); g_file_set_contents(sdp_file, data, length, NULL); } g_free(data); g_key_file_free(sdp_key_file); } if (att_key_file) { data = g_key_file_to_data(att_key_file, &length, NULL); if (length > 0) { create_file(att_file, S_IRUSR | S_IWUSR); g_file_set_contents(att_file, data, length, NULL); } g_free(data); g_key_file_free(att_key_file); } } static int primary_cmp(gconstpointer a, gconstpointer b) { return memcmp(a, b, sizeof(struct gatt_primary)); } static void update_gatt_services(struct browse_req *req, GSList *current, GSList *found) { GSList *l, *lmatch; /* Added Profiles */ for (l = found; l; l = g_slist_next(l)) { struct gatt_primary *prim = l->data; /* Entry found ? */ lmatch = g_slist_find_custom(current, prim, primary_cmp); if (lmatch) continue; /* New entry */ req->profiles_added = g_slist_append(req->profiles_added, g_strdup(prim->uuid)); DBG("UUID Added: %s", prim->uuid); } } static GSList *device_services_from_record(struct btd_device *device, GSList *profiles) { GSList *l, *prim_list = NULL; char *att_uuid; uuid_t proto_uuid; sdp_uuid16_create(&proto_uuid, ATT_UUID); att_uuid = bt_uuid2string(&proto_uuid); for (l = profiles; l; l = l->next) { const char *profile_uuid = l->data; const sdp_record_t *rec; struct gatt_primary *prim; uint16_t start = 0, end = 0, psm = 0; uuid_t prim_uuid; rec = btd_device_get_record(device, profile_uuid); if (!rec) continue; if (!record_has_uuid(rec, att_uuid)) continue; if (!gatt_parse_record(rec, &prim_uuid, &psm, &start, &end)) continue; prim = g_new0(struct gatt_primary, 1); prim->range.start = start; prim->range.end = end; sdp_uuid2strn(&prim_uuid, prim->uuid, sizeof(prim->uuid)); prim_list = g_slist_append(prim_list, prim); } g_free(att_uuid); return prim_list; } static void device_register_primaries(struct btd_device *device, GSList *prim_list, int psm) { device->primaries = g_slist_concat(device->primaries, prim_list); } static void search_cb(sdp_list_t *recs, int err, gpointer user_data) { struct browse_req *req = user_data; struct btd_device *device = req->device; GSList *primaries; char addr[18]; ba2str(&device->bdaddr, addr); if (err < 0) { error("%s: error updating services: %s (%d)", addr, strerror(-err), -err); goto send_reply; } update_bredr_services(req, recs); if (device->tmp_records) sdp_list_free(device->tmp_records, (sdp_free_func_t) sdp_record_free); device->tmp_records = req->records; req->records = NULL; if (!req->profiles_added) { DBG("%s: No service update", addr); goto send_reply; } primaries = device_services_from_record(device, req->profiles_added); if (primaries) device_register_primaries(device, primaries, ATT_PSM); device_probe_profiles(device, req->profiles_added); /* Propagate services changes */ g_dbus_emit_property_changed(dbus_conn, req->device->path, DEVICE_INTERFACE, "UUIDs"); send_reply: device_svc_resolved(device, err); if (!device->temporary) store_device_info(device); browse_request_free(req); } static void browse_cb(sdp_list_t *recs, int err, gpointer user_data) { struct browse_req *req = user_data; struct btd_device *device = req->device; struct btd_adapter *adapter = device->adapter; uuid_t uuid; /* If we have a valid response and req->search_uuid == 2, then L2CAP * UUID & PNP searching was successful -- we are done */ if (err < 0 || (req->search_uuid == 2 && req->records)) { if (err == -ECONNRESET && req->reconnect_attempt < 1) { req->search_uuid--; req->reconnect_attempt++; } else goto done; } update_bredr_services(req, recs); /* Search for mandatory uuids */ if (uuid_list[req->search_uuid]) { sdp_uuid16_create(&uuid, uuid_list[req->search_uuid++]); bt_search_service(adapter_get_address(adapter), &device->bdaddr, &uuid, browse_cb, user_data, NULL); return; } done: search_cb(recs, err, user_data); } static void store_services(struct btd_device *device) { struct btd_adapter *adapter = device->adapter; char filename[PATH_MAX + 1]; char src_addr[18], dst_addr[18]; uuid_t uuid; char *prim_uuid; GKeyFile *key_file; GSList *l; char *data; gsize length = 0; if (device_address_is_private(device)) { warn("Can't store services for private addressed device %s", device->path); return; } sdp_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); prim_uuid = bt_uuid2string(&uuid); if (prim_uuid == NULL) return; ba2str(adapter_get_address(adapter), src_addr); ba2str(&device->bdaddr, dst_addr); snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/attributes", src_addr, dst_addr); filename[PATH_MAX] = '\0'; key_file = g_key_file_new(); for (l = device->primaries; l; l = l->next) { struct gatt_primary *primary = l->data; char handle[6], uuid_str[33]; int i; sprintf(handle, "%hu", primary->range.start); bt_string2uuid(&uuid, primary->uuid); sdp_uuid128_to_uuid(&uuid); switch (uuid.type) { case SDP_UUID16: sprintf(uuid_str, "%4.4X", uuid.value.uuid16); break; case SDP_UUID32: sprintf(uuid_str, "%8.8X", uuid.value.uuid32); break; case SDP_UUID128: for (i = 0; i < 16; i++) sprintf(uuid_str + (i * 2), "%2.2X", uuid.value.uuid128.data[i]); break; default: uuid_str[0] = '\0'; } g_key_file_set_string(key_file, handle, "UUID", prim_uuid); g_key_file_set_string(key_file, handle, "Value", uuid_str); g_key_file_set_integer(key_file, handle, "EndGroupHandle", primary->range.end); } data = g_key_file_to_data(key_file, &length, NULL); if (length > 0) { create_file(filename, S_IRUSR | S_IWUSR); g_file_set_contents(filename, data, length, NULL); } g_free(prim_uuid); g_free(data); g_key_file_free(key_file); } static bool device_get_auto_connect(struct btd_device *device) { if (device->disable_auto_connect) return false; return device->auto_connect; } static void attio_connected(gpointer data, gpointer user_data) { struct attio_data *attio = data; GAttrib *attrib = user_data; if (attio->cfunc) attio->cfunc(attrib, attio->user_data); } static void attio_disconnected(gpointer data, gpointer user_data) { struct attio_data *attio = data; if (attio->dcfunc) attio->dcfunc(attio->user_data); } static gboolean attrib_disconnected_cb(GIOChannel *io, GIOCondition cond, gpointer user_data) { struct btd_device *device = user_data; int sock, err = 0; socklen_t len; if (device->browse) goto done; sock = g_io_channel_unix_get_fd(io); len = sizeof(err); getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &len); DBG("%s (%d)", strerror(err), err); g_slist_foreach(device->attios, attio_disconnected, NULL); if (!device_get_auto_connect(device)) { DBG("Automatic connection disabled"); goto done; } /* * Keep scanning/re-connection active if disconnection reason * is connection timeout, remote user terminated connection or local * initiated disconnection. */ if (err == ETIMEDOUT || err == ECONNRESET || err == ECONNABORTED) adapter_connect_list_add(device->adapter, device); done: attio_cleanup(device); return FALSE; } static void register_all_services(struct browse_req *req, GSList *services) { struct btd_device *device = req->device; device_set_temporary(device, FALSE); update_gatt_services(req, device->primaries, services); g_slist_free_full(device->primaries, g_free); device->primaries = NULL; device_register_primaries(device, g_slist_copy(services), -1); device_probe_profiles(device, req->profiles_added); if (device->attios == NULL && device->attios_offline == NULL) attio_cleanup(device); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "UUIDs"); device_svc_resolved(device, 0); store_services(device); browse_request_free(req); } static int service_by_range_cmp(gconstpointer a, gconstpointer b) { const struct gatt_primary *prim = a; const struct att_range *range = b; return memcmp(&prim->range, range, sizeof(*range)); } static void find_included_cb(GSList *includes, uint8_t status, gpointer user_data) { struct included_search *search = user_data; struct btd_device *device = search->req->device; struct gatt_primary *prim; GSList *l; if (device->attrib == NULL) { error("Disconnected while doing included discovery"); g_slist_free(search->services); g_free(search); return; } if (status != 0) { error("Find included services failed: %s (%d)", att_ecode2str(status), status); goto done; } if (includes == NULL) goto done; for (l = includes; l; l = l->next) { struct gatt_included *incl = l->data; if (g_slist_find_custom(search->services, &incl->range, service_by_range_cmp)) continue; prim = g_new0(struct gatt_primary, 1); memcpy(prim->uuid, incl->uuid, sizeof(prim->uuid)); memcpy(&prim->range, &incl->range, sizeof(prim->range)); search->services = g_slist_append(search->services, prim); } done: search->current = search->current->next; if (search->current == NULL) { register_all_services(search->req, search->services); g_slist_free(search->services); g_free(search); return; } prim = search->current->data; gatt_find_included(device->attrib, prim->range.start, prim->range.end, find_included_cb, search); } static void find_included_services(struct browse_req *req, GSList *services) { struct btd_device *device = req->device; struct included_search *search; struct gatt_primary *prim; if (services == NULL) return; search = g_new0(struct included_search, 1); search->req = req; search->services = g_slist_copy(services); search->current = search->services; prim = search->current->data; gatt_find_included(device->attrib, prim->range.start, prim->range.end, find_included_cb, search); } static void primary_cb(GSList *services, guint8 status, gpointer user_data) { struct browse_req *req = user_data; if (status) { struct btd_device *device = req->device; if (req->msg) { DBusMessage *reply; reply = btd_error_failed(req->msg, att_ecode2str(status)); g_dbus_send_message(dbus_conn, reply); } device->browse = NULL; browse_request_free(req); return; } find_included_services(req, services); } static void att_connect_cb(GIOChannel *io, GError *gerr, gpointer user_data) { struct att_callbacks *attcb = user_data; struct btd_device *device = attcb->user_data; DBusMessage *reply; uint8_t io_cap; GAttrib *attrib; int err = 0; g_io_channel_unref(device->att_io); device->att_io = NULL; if (gerr) { DBG("%s", gerr->message); if (attcb->error) attcb->error(gerr, user_data); err = -ECONNABORTED; goto done; } attrib = g_attrib_new(io); device->attachid = attrib_channel_attach(attrib); if (device->attachid == 0) error("Attribute server attach failure!"); device->attrib = attrib; device->cleanup_id = g_io_add_watch(io, G_IO_HUP, attrib_disconnected_cb, device); if (attcb->success) attcb->success(user_data); if (!device->bonding) goto done; if (device->bonding->agent) io_cap = agent_get_io_capability(device->bonding->agent); else io_cap = IO_CAPABILITY_NOINPUTNOOUTPUT; err = adapter_create_bonding(device->adapter, &device->bdaddr, device->bdaddr_type, io_cap); done: if (device->bonding && err < 0) { reply = btd_error_failed(device->bonding->msg, strerror(-err)); g_dbus_send_message(dbus_conn, reply); bonding_request_cancel(device->bonding); bonding_request_free(device->bonding); } if (device->connect) { if (!device->svc_resolved) device_browse_primary(device, NULL); if (err < 0) reply = btd_error_failed(device->connect, strerror(-err)); else reply = dbus_message_new_method_return(device->connect); g_dbus_send_message(dbus_conn, reply); dbus_message_unref(device->connect); device->connect = NULL; } g_free(attcb); } static void att_error_cb(const GError *gerr, gpointer user_data) { struct att_callbacks *attcb = user_data; struct btd_device *device = attcb->user_data; if (g_error_matches(gerr, BT_IO_ERROR, ECONNABORTED)) return; if (device_get_auto_connect(device)) { DBG("Enabling automatic connections"); adapter_connect_list_add(device->adapter, device); } } static void att_success_cb(gpointer user_data) { struct att_callbacks *attcb = user_data; struct btd_device *device = attcb->user_data; if (device->attios == NULL) return; /* * Remove the device from the connect_list and give the passive * scanning another chance to be restarted in case there are * other devices in the connect_list. */ adapter_connect_list_remove(device->adapter, device); g_slist_foreach(device->attios, attio_connected, device->attrib); } int device_connect_le(struct btd_device *dev) { struct btd_adapter *adapter = dev->adapter; struct att_callbacks *attcb; BtIOSecLevel sec_level; GIOChannel *io; GError *gerr = NULL; char addr[18]; /* There is one connection attempt going on */ if (dev->att_io) return -EALREADY; ba2str(&dev->bdaddr, addr); DBG("Connection attempt to: %s", addr); attcb = g_new0(struct att_callbacks, 1); attcb->error = att_error_cb; attcb->success = att_success_cb; attcb->user_data = dev; if (dev->paired) sec_level = BT_IO_SEC_MEDIUM; else sec_level = BT_IO_SEC_LOW; /* * This connection will help us catch any PDUs that comes before * pairing finishes */ io = bt_io_connect(att_connect_cb, attcb, NULL, &gerr, BT_IO_OPT_SOURCE_BDADDR, adapter_get_address(adapter), BT_IO_OPT_SOURCE_TYPE, BDADDR_LE_PUBLIC, BT_IO_OPT_DEST_BDADDR, &dev->bdaddr, BT_IO_OPT_DEST_TYPE, dev->bdaddr_type, BT_IO_OPT_CID, ATT_CID, BT_IO_OPT_SEC_LEVEL, sec_level, BT_IO_OPT_INVALID); if (io == NULL) { if (dev->bonding) { DBusMessage *reply = btd_error_failed( dev->bonding->msg, gerr->message); g_dbus_send_message(dbus_conn, reply); bonding_request_cancel(dev->bonding); bonding_request_free(dev->bonding); } error("ATT bt_io_connect(%s): %s", addr, gerr->message); g_error_free(gerr); g_free(attcb); return -EIO; } /* Keep this, so we can cancel the connection */ dev->att_io = io; return 0; } static void att_browse_error_cb(const GError *gerr, gpointer user_data) { struct att_callbacks *attcb = user_data; struct btd_device *device = attcb->user_data; struct browse_req *req = device->browse; if (req->msg) { DBusMessage *reply; reply = btd_error_failed(req->msg, gerr->message); g_dbus_send_message(dbus_conn, reply); } device->browse = NULL; browse_request_free(req); } static void att_browse_cb(gpointer user_data) { struct att_callbacks *attcb = user_data; struct btd_device *device = attcb->user_data; gatt_discover_primary(device->attrib, NULL, primary_cb, device->browse); } static int device_browse_primary(struct btd_device *device, DBusMessage *msg) { struct btd_adapter *adapter = device->adapter; struct att_callbacks *attcb; struct browse_req *req; if (device->browse) return -EBUSY; req = g_new0(struct browse_req, 1); req->device = device; device->browse = req; if (device->attrib) { gatt_discover_primary(device->attrib, NULL, primary_cb, req); goto done; } attcb = g_new0(struct att_callbacks, 1); attcb->error = att_browse_error_cb; attcb->success = att_browse_cb; attcb->user_data = device; device->att_io = bt_io_connect(att_connect_cb, attcb, NULL, NULL, BT_IO_OPT_SOURCE_BDADDR, adapter_get_address(adapter), BT_IO_OPT_DEST_BDADDR, &device->bdaddr, BT_IO_OPT_DEST_TYPE, device->bdaddr_type, BT_IO_OPT_CID, ATT_CID, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, BT_IO_OPT_INVALID); if (device->att_io == NULL) { device->browse = NULL; browse_request_free(req); g_free(attcb); return -EIO; } done: if (msg) { const char *sender = dbus_message_get_sender(msg); req->msg = dbus_message_ref(msg); /* Track the request owner to cancel it * automatically if the owner exits */ req->listener_id = g_dbus_add_disconnect_watch(dbus_conn, sender, discover_services_req_exit, req, NULL); } return 0; } static int device_browse_sdp(struct btd_device *device, DBusMessage *msg) { struct btd_adapter *adapter = device->adapter; struct browse_req *req; uuid_t uuid; int err; if (device->browse) return -EBUSY; req = g_new0(struct browse_req, 1); req->device = device; sdp_uuid16_create(&uuid, uuid_list[req->search_uuid++]); err = bt_search_service(adapter_get_address(adapter), &device->bdaddr, &uuid, browse_cb, req, NULL); if (err < 0) { browse_request_free(req); return err; } device->browse = req; if (msg) { const char *sender = dbus_message_get_sender(msg); req->msg = dbus_message_ref(msg); /* Track the request owner to cancel it * automatically if the owner exits */ req->listener_id = g_dbus_add_disconnect_watch(dbus_conn, sender, discover_services_req_exit, req, NULL); } return err; } struct btd_adapter *device_get_adapter(struct btd_device *device) { if (!device) return NULL; return device->adapter; } const bdaddr_t *device_get_address(struct btd_device *device) { return &device->bdaddr; } const char *device_get_path(const struct btd_device *device) { if (!device) return NULL; return device->path; } gboolean device_is_temporary(struct btd_device *device) { return device->temporary; } void device_set_temporary(struct btd_device *device, gboolean temporary) { if (!device) return; if (device->temporary == temporary) return; DBG("temporary %d", temporary); if (temporary) adapter_connect_list_remove(device->adapter, device); device->temporary = temporary; } void device_set_trusted(struct btd_device *device, gboolean trusted) { if (!device) return; if (device->trusted == trusted) return; DBG("trusted %d", trusted); device->trusted = trusted; store_device_info(device); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Trusted"); } void device_set_bonded(struct btd_device *device, gboolean bonded) { if (!device) return; DBG("bonded %d", bonded); device->bonded = bonded; } void device_set_legacy(struct btd_device *device, bool legacy) { if (!device) return; DBG("legacy %d", legacy); if (device->legacy == legacy) return; device->legacy = legacy; g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "LegacyPairing"); } void device_set_rssi(struct btd_device *device, int8_t rssi) { if (!device) return; if (rssi == 0 || device->rssi == 0) { if (device->rssi == rssi) return; DBG("rssi %d", rssi); device->rssi = rssi; } else { int delta; if (device->rssi > rssi) delta = device->rssi - rssi; else delta = rssi - device->rssi; /* only report changes of 8 dBm or more */ if (delta < 8) return; DBG("rssi %d delta %d", rssi, delta); device->rssi = rssi; } g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "RSSI"); } static void device_set_auto_connect(struct btd_device *device, gboolean enable) { char addr[18]; if (!device) return; ba2str(&device->bdaddr, addr); DBG("%s auto connect: %d", addr, enable); device->auto_connect = enable; /* Disabling auto connect */ if (enable == FALSE) { adapter_connect_list_remove(device->adapter, device); return; } if (device->attrib) { DBG("Already connected"); return; } /* Enabling auto connect */ adapter_connect_list_add(device->adapter, device); } static gboolean start_discovery(gpointer user_data) { struct btd_device *device = user_data; if (device_is_bredr(device)) device_browse_sdp(device, NULL); else device_browse_primary(device, NULL); device->discov_timer = 0; return FALSE; } void device_set_paired(struct btd_device *device, gboolean value) { if (device->paired == value) return; if (!value) btd_adapter_remove_bonding(device->adapter, &device->bdaddr, device->bdaddr_type); device->paired = value; if (device->paired && !device->svc_resolved) device->pending_paired = true; else g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Paired"); } static void device_auth_req_free(struct btd_device *device) { if (device->authr) g_free(device->authr->pincode); g_free(device->authr); device->authr = NULL; } bool device_is_retrying(struct btd_device *device) { struct bonding_req *bonding = device->bonding; return bonding && bonding->retry_timer > 0; } void device_bonding_complete(struct btd_device *device, uint8_t status) { struct bonding_req *bonding = device->bonding; struct authentication_req *auth = device->authr; DBG("bonding %p status 0x%02x", bonding, status); if (auth && auth->agent) agent_cancel(auth->agent); if (status) { device_cancel_authentication(device, TRUE); device_bonding_failed(device, status); return; } device_auth_req_free(device); /* If we're already paired nothing more is needed */ if (device->paired) return; device_set_paired(device, TRUE); /* If services are already resolved just reply to the pairing * request */ if (device->svc_resolved && bonding) { g_dbus_send_reply(dbus_conn, bonding->msg, DBUS_TYPE_INVALID); bonding_request_free(bonding); return; } /* If we were initiators start service discovery immediately. * However if the other end was the initator wait a few seconds * before SDP. This is due to potential IOP issues if the other * end starts doing SDP at the same time as us */ if (bonding) { DBG("Proceeding with service discovery"); /* If we are initiators remove any discovery timer and just * start discovering services directly */ if (device->discov_timer) { g_source_remove(device->discov_timer); device->discov_timer = 0; } if (device_is_bredr(device)) device_browse_sdp(device, bonding->msg); else device_browse_primary(device, bonding->msg); bonding_request_free(bonding); } else if (!device->svc_resolved) { if (!device->browse && !device->discov_timer && main_opts.reverse_sdp) { /* If we are not initiators and there is no currently * active discovery or discovery timer, set discovery * timer */ DBG("setting timer for reverse service discovery"); device->discov_timer = g_timeout_add_seconds( DISCOVERY_TIMER, start_discovery, device); } } } static gboolean svc_idle_cb(gpointer user_data) { struct svc_callback *cb = user_data; struct btd_device *dev = cb->dev; dev->svc_callbacks = g_slist_remove(dev->svc_callbacks, cb); cb->func(cb->dev, 0, cb->user_data); g_free(cb); return FALSE; } unsigned int device_wait_for_svc_complete(struct btd_device *dev, device_svc_cb_t func, void *user_data) { static unsigned int id = 0; struct svc_callback *cb; cb = g_new0(struct svc_callback, 1); cb->func = func; cb->user_data = user_data; cb->dev = dev; cb->id = ++id; dev->svc_callbacks = g_slist_prepend(dev->svc_callbacks, cb); if (dev->svc_resolved || !main_opts.reverse_sdp) cb->idle_id = g_idle_add(svc_idle_cb, cb); else if (dev->discov_timer > 0) { g_source_remove(dev->discov_timer); dev->discov_timer = g_idle_add(start_discovery, dev); } return cb->id; } bool device_remove_svc_complete_callback(struct btd_device *dev, unsigned int id) { GSList *l; for (l = dev->svc_callbacks; l != NULL; l = g_slist_next(l)) { struct svc_callback *cb = l->data; if (cb->id != id) continue; if (cb->idle_id > 0) g_source_remove(cb->idle_id); dev->svc_callbacks = g_slist_remove(dev->svc_callbacks, cb); g_free(cb); return true; } return false; } gboolean device_is_bonding(struct btd_device *device, const char *sender) { struct bonding_req *bonding = device->bonding; if (!device->bonding) return FALSE; if (!sender) return TRUE; return g_str_equal(sender, dbus_message_get_sender(bonding->msg)); } static gboolean device_bonding_retry(gpointer data) { struct btd_device *device = data; struct btd_adapter *adapter = device_get_adapter(device); struct bonding_req *bonding = device->bonding; uint8_t io_cap; int err; if (!bonding) return FALSE; DBG("retrying bonding"); bonding->retry_timer = 0; /* Restart the bonding timer to the begining of the pairing. If not * pincode request/reply occurs during this retry, * device_bonding_last_duration() will return a consistent value from * this point. */ device_bonding_restart_timer(device); if (bonding->agent) io_cap = agent_get_io_capability(bonding->agent); else io_cap = IO_CAPABILITY_NOINPUTNOOUTPUT; err = adapter_bonding_attempt(adapter, &device->bdaddr, device->bdaddr_type, io_cap); if (err < 0) device_bonding_complete(device, bonding->status); return FALSE; } int device_bonding_attempt_retry(struct btd_device *device) { struct bonding_req *bonding = device->bonding; /* Ignore other failure events while retrying */ if (device_is_retrying(device)) return 0; if (!bonding) return -EINVAL; /* Mark the end of a bonding attempt to compute the delta for the * retry. */ bonding_request_stop_timer(bonding); if (btd_adapter_pin_cb_iter_end(bonding->cb_iter)) return -EINVAL; DBG("scheduling retry"); bonding->retry_timer = g_timeout_add(3000, device_bonding_retry, device); return 0; } void device_bonding_failed(struct btd_device *device, uint8_t status) { struct bonding_req *bonding = device->bonding; DBusMessage *reply; DBG("status %u", status); if (!bonding) return; if (device->authr) device_cancel_authentication(device, FALSE); reply = new_authentication_return(bonding->msg, status); g_dbus_send_message(dbus_conn, reply); bonding_request_free(bonding); } struct btd_adapter_pin_cb_iter *device_bonding_iter(struct btd_device *device) { if (device->bonding == NULL) return NULL; return device->bonding->cb_iter; } static void pincode_cb(struct agent *agent, DBusError *err, const char *pin, void *data) { struct authentication_req *auth = data; struct btd_device *device = auth->device; /* No need to reply anything if the authentication already failed */ if (auth->agent == NULL) return; btd_adapter_pincode_reply(device->adapter, &device->bdaddr, pin, pin ? strlen(pin) : 0); agent_unref(device->authr->agent); device->authr->agent = NULL; } static void confirm_cb(struct agent *agent, DBusError *err, void *data) { struct authentication_req *auth = data; struct btd_device *device = auth->device; /* No need to reply anything if the authentication already failed */ if (auth->agent == NULL) return; btd_adapter_confirm_reply(device->adapter, &device->bdaddr, device->bdaddr_type, err ? FALSE : TRUE); agent_unref(device->authr->agent); device->authr->agent = NULL; } static void passkey_cb(struct agent *agent, DBusError *err, uint32_t passkey, void *data) { struct authentication_req *auth = data; struct btd_device *device = auth->device; /* No need to reply anything if the authentication already failed */ if (auth->agent == NULL) return; if (err) passkey = INVALID_PASSKEY; btd_adapter_passkey_reply(device->adapter, &device->bdaddr, device->bdaddr_type, passkey); agent_unref(device->authr->agent); device->authr->agent = NULL; } static void display_pincode_cb(struct agent *agent, DBusError *err, void *data) { struct authentication_req *auth = data; struct btd_device *device = auth->device; pincode_cb(agent, err, auth->pincode, auth); g_free(device->authr->pincode); device->authr->pincode = NULL; } static struct authentication_req *new_auth(struct btd_device *device, auth_type_t type, gboolean secure) { struct authentication_req *auth; struct agent *agent; char addr[18]; ba2str(&device->bdaddr, addr); DBG("Requesting agent authentication for %s", addr); if (device->authr) { error("Authentication already requested for %s", addr); return NULL; } if (device->bonding && device->bonding->agent) agent = agent_ref(device->bonding->agent); else agent = agent_get(NULL); if (!agent) { error("No agent available for request type %d", type); return NULL; } auth = g_new0(struct authentication_req, 1); auth->agent = agent; auth->device = device; auth->type = type; auth->secure = secure; device->authr = auth; return auth; } int device_request_pincode(struct btd_device *device, gboolean secure) { struct authentication_req *auth; int err; auth = new_auth(device, AUTH_TYPE_PINCODE, secure); if (!auth) return -EPERM; err = agent_request_pincode(auth->agent, device, pincode_cb, secure, auth, NULL); if (err < 0) { error("Failed requesting authentication"); device_auth_req_free(device); } return err; } int device_request_passkey(struct btd_device *device) { struct authentication_req *auth; int err; auth = new_auth(device, AUTH_TYPE_PASSKEY, FALSE); if (!auth) return -EPERM; err = agent_request_passkey(auth->agent, device, passkey_cb, auth, NULL); if (err < 0) { error("Failed requesting authentication"); device_auth_req_free(device); } return err; } int device_confirm_passkey(struct btd_device *device, uint32_t passkey, uint8_t confirm_hint) { struct authentication_req *auth; int err; auth = new_auth(device, AUTH_TYPE_CONFIRM, FALSE); if (!auth) return -EPERM; auth->passkey = passkey; if (confirm_hint) err = agent_request_authorization(auth->agent, device, confirm_cb, auth, NULL); else err = agent_request_confirmation(auth->agent, device, passkey, confirm_cb, auth, NULL); if (err < 0) { error("Failed requesting authentication"); device_auth_req_free(device); } return err; } int device_notify_passkey(struct btd_device *device, uint32_t passkey, uint8_t entered) { struct authentication_req *auth; int err; if (device->authr) { auth = device->authr; if (auth->type != AUTH_TYPE_NOTIFY_PASSKEY) return -EPERM; } else { auth = new_auth(device, AUTH_TYPE_NOTIFY_PASSKEY, FALSE); if (!auth) return -EPERM; } err = agent_display_passkey(auth->agent, device, passkey, entered); if (err < 0) { error("Failed requesting authentication"); device_auth_req_free(device); } return err; } int device_notify_pincode(struct btd_device *device, gboolean secure, const char *pincode) { struct authentication_req *auth; int err; auth = new_auth(device, AUTH_TYPE_NOTIFY_PINCODE, secure); if (!auth) return -EPERM; auth->pincode = g_strdup(pincode); err = agent_display_pincode(auth->agent, device, pincode, display_pincode_cb, auth, NULL); if (err < 0) { error("Failed requesting authentication"); device_auth_req_free(device); } return err; } static void cancel_authentication(struct authentication_req *auth) { struct agent *agent; DBusError err; if (!auth || !auth->agent) return; agent = auth->agent; auth->agent = NULL; dbus_error_init(&err); dbus_set_error_const(&err, ERROR_INTERFACE ".Canceled", NULL); switch (auth->type) { case AUTH_TYPE_PINCODE: pincode_cb(agent, &err, NULL, auth); break; case AUTH_TYPE_CONFIRM: confirm_cb(agent, &err, auth); break; case AUTH_TYPE_PASSKEY: passkey_cb(agent, &err, 0, auth); break; case AUTH_TYPE_NOTIFY_PASSKEY: /* User Notify doesn't require any reply */ break; case AUTH_TYPE_NOTIFY_PINCODE: pincode_cb(agent, &err, NULL, auth); break; } dbus_error_free(&err); } void device_cancel_authentication(struct btd_device *device, gboolean aborted) { struct authentication_req *auth = device->authr; char addr[18]; if (!auth) return; ba2str(&device->bdaddr, addr); DBG("Canceling authentication request for %s", addr); if (auth->agent) agent_cancel(auth->agent); if (!aborted) cancel_authentication(auth); device_auth_req_free(device); } gboolean device_is_authenticating(struct btd_device *device) { return (device->authr != NULL); } struct gatt_primary *btd_device_get_primary(struct btd_device *device, const char *uuid) { GSList *match; match = g_slist_find_custom(device->primaries, uuid, bt_uuid_strcmp); if (match) return match->data; return NULL; } GSList *btd_device_get_primaries(struct btd_device *device) { return device->primaries; } void btd_device_gatt_set_service_changed(struct btd_device *device, uint16_t start, uint16_t end) { GSList *l; for (l = device->primaries; l; l = g_slist_next(l)) { struct gatt_primary *prim = l->data; if (start <= prim->range.end && end >= prim->range.start) prim->changed = TRUE; } device_browse_primary(device, NULL); } void btd_device_add_uuid(struct btd_device *device, const char *uuid) { GSList *uuid_list; char *new_uuid; if (g_slist_find_custom(device->uuids, uuid, bt_uuid_strcmp)) return; new_uuid = g_strdup(uuid); uuid_list = g_slist_append(NULL, new_uuid); device_probe_profiles(device, uuid_list); g_free(new_uuid); g_slist_free(uuid_list); store_device_info(device); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "UUIDs"); } static sdp_list_t *read_device_records(struct btd_device *device) { char local[18], peer[18]; char filename[PATH_MAX + 1]; GKeyFile *key_file; char **keys, **handle; char *str; sdp_list_t *recs = NULL; sdp_record_t *rec; ba2str(adapter_get_address(device->adapter), local); ba2str(&device->bdaddr, peer); snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", local, peer); filename[PATH_MAX] = '\0'; key_file = g_key_file_new(); g_key_file_load_from_file(key_file, filename, 0, NULL); keys = g_key_file_get_keys(key_file, "ServiceRecords", NULL, NULL); for (handle = keys; handle && *handle; handle++) { str = g_key_file_get_string(key_file, "ServiceRecords", *handle, NULL); if (!str) continue; rec = record_from_string(str); recs = sdp_list_append(recs, rec); g_free(str); } g_strfreev(keys); g_key_file_free(key_file); return recs; } const sdp_record_t *btd_device_get_record(struct btd_device *device, const char *uuid) { if (device->tmp_records) { const sdp_record_t *record; record = find_record_in_list(device->tmp_records, uuid); if (record != NULL) return record; sdp_list_free(device->tmp_records, (sdp_free_func_t) sdp_record_free); device->tmp_records = NULL; } device->tmp_records = read_device_records(device); if (!device->tmp_records) return NULL; return find_record_in_list(device->tmp_records, uuid); } struct btd_device *btd_device_ref(struct btd_device *device) { __sync_fetch_and_add(&device->ref_count, 1); return device; } void btd_device_unref(struct btd_device *device) { if (__sync_sub_and_fetch(&device->ref_count, 1)) return; if (!device->path) { error("freeing device without an object path"); return; } DBG("Freeing device %s", device->path); g_dbus_unregister_interface(dbus_conn, device->path, DEVICE_INTERFACE); } int device_get_appearance(struct btd_device *device, uint16_t *value) { if (device->appearance == 0) return -1; if (value) *value = device->appearance; return 0; } void device_set_appearance(struct btd_device *device, uint16_t value) { const char *icon = gap_appearance_to_icon(value); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Appearance"); if (icon) g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Icon"); device->appearance = value; store_device_info(device); } static gboolean notify_attios(gpointer user_data) { struct btd_device *device = user_data; if (device->attrib == NULL) return FALSE; g_slist_foreach(device->attios_offline, attio_connected, device->attrib); device->attios = g_slist_concat(device->attios, device->attios_offline); device->attios_offline = NULL; return FALSE; } guint btd_device_add_attio_callback(struct btd_device *device, attio_connect_cb cfunc, attio_disconnect_cb dcfunc, gpointer user_data) { struct attio_data *attio; static guint attio_id = 0; DBG("%p registered ATT connection callback", device); attio = g_new0(struct attio_data, 1); attio->id = ++attio_id; attio->cfunc = cfunc; attio->dcfunc = dcfunc; attio->user_data = user_data; device_set_auto_connect(device, TRUE); if (device->attrib && cfunc) { device->attios_offline = g_slist_append(device->attios_offline, attio); g_idle_add(notify_attios, device); return attio->id; } device->attios = g_slist_append(device->attios, attio); return attio->id; } static int attio_id_cmp(gconstpointer a, gconstpointer b) { const struct attio_data *attio = a; guint id = GPOINTER_TO_UINT(b); return attio->id - id; } gboolean btd_device_remove_attio_callback(struct btd_device *device, guint id) { struct attio_data *attio; GSList *l; l = g_slist_find_custom(device->attios, GUINT_TO_POINTER(id), attio_id_cmp); if (l) { attio = l->data; device->attios = g_slist_remove(device->attios, attio); } else { l = g_slist_find_custom(device->attios_offline, GUINT_TO_POINTER(id), attio_id_cmp); if (!l) return FALSE; attio = l->data; device->attios_offline = g_slist_remove(device->attios_offline, attio); } g_free(attio); if (device->attios != NULL || device->attios_offline != NULL) return TRUE; attio_cleanup(device); return TRUE; } void btd_device_set_pnpid(struct btd_device *device, uint16_t source, uint16_t vendor, uint16_t product, uint16_t version) { device->vendor_src = source; device->vendor = vendor; device->product = product; device->version = version; g_free(device->modalias); device->modalias = bt_modalias(source, vendor, product, version); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Modalias"); store_device_info(device); } static void service_state_changed(struct btd_service *service, btd_service_state_t old_state, btd_service_state_t new_state, void *user_data) { struct btd_profile *profile = btd_service_get_profile(service); struct btd_device *device = btd_service_get_device(service); int err = btd_service_get_error(service); if (new_state == BTD_SERVICE_STATE_CONNECTING || new_state == BTD_SERVICE_STATE_DISCONNECTING) return; if (old_state == BTD_SERVICE_STATE_CONNECTING) device_profile_connected(device, profile, err); else if (old_state == BTD_SERVICE_STATE_DISCONNECTING) device_profile_disconnected(device, profile, err); } struct btd_service *btd_device_get_service(struct btd_device *dev, const char *remote_uuid) { GSList *l; for (l = dev->services; l != NULL; l = g_slist_next(l)) { struct btd_service *service = l->data; struct btd_profile *p = btd_service_get_profile(service); if (g_str_equal(p->remote_uuid, remote_uuid)) return service; } return NULL; } void btd_device_init(void) { dbus_conn = btd_get_dbus_connection(); service_state_cb_id = btd_service_add_state_cb( service_state_changed, NULL); } void btd_device_cleanup(void) { btd_service_remove_state_cb(service_state_cb_id); }