| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2006-2010 Nokia Corporation |
| * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> |
| * Copyright 2024 NXP |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #define _GNU_SOURCE |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <stdbool.h> |
| #include <errno.h> |
| #include <dirent.h> |
| #include <time.h> |
| #include <limits.h> |
| #include <sys/stat.h> |
| |
| #include <glib.h> |
| #include <dbus/dbus.h> |
| |
| #include "bluetooth/bluetooth.h" |
| #include "bluetooth/sdp.h" |
| #include "bluetooth/sdp_lib.h" |
| #include "bluetooth/uuid.h" |
| |
| #include "gdbus/gdbus.h" |
| |
| #include "log.h" |
| #include "src/shared/util.h" |
| #include "src/shared/att.h" |
| #include "src/shared/queue.h" |
| #include "src/shared/gatt-db.h" |
| #include "src/shared/gatt-client.h" |
| #include "src/shared/gatt-server.h" |
| #include "src/shared/ad.h" |
| #include "src/shared/timeout.h" |
| #include "btio/btio.h" |
| #include "bluetooth/mgmt.h" |
| #include "attrib/att.h" |
| #include "btd.h" |
| #include "adapter.h" |
| #include "gatt-database.h" |
| #include "attrib/gattrib.h" |
| #include "device.h" |
| #include "gatt-client.h" |
| #include "profile.h" |
| #include "service.h" |
| #include "dbus-common.h" |
| #include "error.h" |
| #include "uuid-helper.h" |
| #include "sdp-client.h" |
| #include "attrib/gatt.h" |
| #include "agent.h" |
| #include "textfile.h" |
| #include "storage.h" |
| #include "eir.h" |
| #include "settings.h" |
| #include "set.h" |
| #include "bearer.h" |
| |
| #define DISCONNECT_TIMER 2 |
| #define DISCOVERY_TIMER 1 |
| #define INVALID_FLAGS 0xff |
| |
| #ifndef MIN |
| #define MIN(a, b) ((a) < (b) ? (a) : (b)) |
| #endif |
| |
| #define RSSI_THRESHOLD 8 |
| #define AUTH_FAILURES_THRESHOLD 3 |
| |
| static DBusConnection *dbus_conn = NULL; |
| static 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; |
| uint8_t bdaddr_type; |
| 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; |
| uint8_t addr_type; |
| uint32_t passkey; |
| char *pincode; |
| gboolean secure; |
| }; |
| |
| enum { |
| BROWSE_SDP, |
| BROWSE_GATT |
| }; |
| |
| struct browse_req { |
| DBusMessage *msg; |
| struct btd_device *device; |
| uint8_t type; |
| GSList *match_uuids; |
| GSList *profiles_added; |
| sdp_list_t *records; |
| int search_uuid; |
| int reconnect_attempt; |
| guint listener_id; |
| uint16_t sdp_flags; |
| }; |
| |
| struct included_search { |
| struct browse_req *req; |
| GSList *services; |
| GSList *current; |
| }; |
| |
| struct svc_callback { |
| unsigned int id; |
| guint idle_id; |
| struct btd_device *dev; |
| device_svc_cb_t func; |
| void *user_data; |
| }; |
| |
| /* Per-bearer (LE or BR/EDR) device state */ |
| struct bearer_state { |
| bool prefer; |
| bool paired; |
| bool bonded; |
| bool connected; |
| bool svc_resolved; |
| bool initiator; |
| bool connectable; |
| time_t last_seen; |
| time_t last_used; |
| }; |
| |
| struct ltk_info { |
| uint8_t key[16]; |
| bool central; |
| uint8_t enc_size; |
| }; |
| |
| struct csrk_info { |
| uint8_t key[16]; |
| uint32_t counter; |
| bool auth; |
| }; |
| |
| struct sirk_info { |
| struct btd_device_set *set; |
| uint8_t encrypted; |
| uint8_t key[16]; |
| uint8_t size; |
| uint8_t rank; |
| }; |
| |
| enum { |
| WAKE_FLAG_DEFAULT = 0, |
| WAKE_FLAG_ENABLED, |
| WAKE_FLAG_DISABLED, |
| }; |
| |
| enum { |
| PREFER_LAST_USED = 0, |
| PREFER_LE, |
| PREFER_BREDR, |
| PREFER_LAST_SEEN, |
| }; |
| |
| struct btd_device { |
| int ref_count; |
| |
| bdaddr_t conn_bdaddr; |
| uint8_t conn_bdaddr_type; |
| bdaddr_t bdaddr; |
| uint8_t bdaddr_type; |
| bool privacy; |
| uint8_t *irk; |
| char *path; |
| struct btd_bearer *bredr; |
| struct btd_bearer *le; |
| bool pending_paired; /* "Paired" waiting for SDP */ |
| bool svc_refreshed; |
| bool refresh_discovery; |
| |
| /* Manage whether this device can wake the system from suspend. |
| * - wake_support: Requires a profile that supports wake (i.e. HID) |
| * - wake_allowed: Is wake currently allowed? |
| * - pending_wake_allowed - Wake flag sent via set_device_flags |
| * - wake_override - User configured wake setting |
| */ |
| bool wake_support; |
| bool wake_allowed; |
| bool pending_wake_allowed; |
| uint8_t wake_override; |
| GDBusPendingPropertySet wake_id; |
| |
| uint32_t supported_flags; |
| uint32_t pending_flags; |
| uint32_t current_flags; |
| GSList *svc_callbacks; |
| GSList *eir_uuids; |
| struct bt_ad *ad; |
| uint8_t ad_flags[1]; |
| 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 */ |
| bool temporary; |
| bool connectable; |
| bool cable_pairing; |
| unsigned int disconn_timer; |
| unsigned int discov_timer; |
| unsigned int temporary_timer; /* Temporary/disappear timer */ |
| struct browse_req *browse; /* service discover request */ |
| struct bonding_req *bonding; |
| struct authentication_req *authr; /* authentication request */ |
| uint8_t bonding_status; |
| GSList *disconnects; /* disconnects message */ |
| DBusMessage *connect; /* connect message */ |
| DBusMessage *disconnect; /* disconnect message */ |
| GAttrib *attrib; |
| |
| struct bt_att *att; /* The new ATT transport */ |
| uint16_t att_mtu; /* The ATT MTU */ |
| unsigned int att_disconn_id; |
| |
| /* |
| * TODO: For now, device creates and owns the client-role gatt_db, but |
| * this needs to be persisted in a more central place so that proper |
| * attribute cache support can be built. |
| */ |
| struct gatt_db *db; /* GATT db cache */ |
| unsigned int db_id; |
| struct bt_gatt_client *client; /* GATT client instance */ |
| struct bt_gatt_server *server; /* GATT server instance */ |
| unsigned int gatt_ready_id; |
| |
| struct btd_gatt_client *client_dbus; |
| |
| uint8_t prefer_bearer; |
| struct bearer_state bredr_state; |
| struct bearer_state le_state; |
| |
| struct csrk_info *local_csrk; |
| struct csrk_info *remote_csrk; |
| struct ltk_info *ltk; |
| struct queue *sirks; |
| |
| sdp_list_t *tmp_records; |
| |
| bool trusted; |
| gboolean blocked; |
| gboolean auto_connect; |
| gboolean disable_auto_connect; |
| gboolean general_connect; |
| |
| bool legacy; |
| int8_t rssi; |
| int8_t tx_power; |
| |
| GIOChannel *att_io; |
| guint store_id; |
| |
| time_t name_resolve_failed_time; |
| |
| int8_t volume; |
| |
| uint32_t auth_failures; |
| guint auth_retry_id; |
| }; |
| |
| static const uint16_t uuid_list[] = { |
| L2CAP_UUID, |
| PNP_INFO_SVCLASS_ID, |
| PUBLIC_BROWSE_GROUP, |
| 0 |
| }; |
| |
| static int device_browse_gatt(struct btd_device *device, DBusMessage *msg); |
| static int device_browse_sdp(struct btd_device *device, DBusMessage *msg); |
| |
| static struct bearer_state *get_state(struct btd_device *dev, |
| uint8_t bdaddr_type) |
| { |
| if (bdaddr_type == BDADDR_BREDR) |
| return &dev->bredr_state; |
| else |
| return &dev->le_state; |
| } |
| |
| bool btd_device_is_initiator(struct btd_device *dev) |
| { |
| if (dev->le_state.connected) |
| return dev->le_state.initiator; |
| else if (dev->bredr_state.connected) |
| return dev->bredr_state.initiator; |
| else if (dev->bonding) |
| return true; |
| |
| return dev->att_io ? true : false; |
| } |
| |
| 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 GSList *find_service_with_uuid(GSList *list, char *uuid) |
| { |
| GSList *l; |
| |
| for (l = list; l != NULL; l = g_slist_next(l)) { |
| struct btd_service *service = l->data; |
| struct btd_profile *profile = btd_service_get_profile(service); |
| |
| if (bt_uuid_strcmp(profile->remote_uuid, uuid) == 0) |
| return l; |
| } |
| |
| return NULL; |
| } |
| |
| static const char *device_prefer_bearer_str(struct btd_device *device) |
| { |
| /* Check if both BR/EDR and LE bearer are supported */ |
| if (!device->bredr || !device->le) |
| return NULL; |
| |
| switch (device->prefer_bearer) { |
| case PREFER_LAST_USED: |
| return "last-used"; |
| case PREFER_LE: |
| return "le"; |
| case PREFER_BREDR: |
| return "bredr"; |
| case PREFER_LAST_SEEN: |
| return "last-seen"; |
| } |
| |
| return NULL; |
| } |
| |
| static bool device_set_prefer_bearer(struct btd_device *device, uint8_t bearer) |
| { |
| switch (bearer) { |
| case PREFER_LAST_USED: |
| device->prefer_bearer = PREFER_LAST_USED; |
| return true; |
| case PREFER_LE: |
| device->prefer_bearer = PREFER_LE; |
| device->le_state.prefer = true; |
| device->bredr_state.prefer = false; |
| return true; |
| case PREFER_BREDR: |
| device->prefer_bearer = PREFER_BREDR; |
| device->bredr_state.prefer = true; |
| device->le_state.prefer = false; |
| return true; |
| case PREFER_LAST_SEEN: |
| device->prefer_bearer = PREFER_LAST_SEEN; |
| device->bredr_state.prefer = false; |
| device->le_state.prefer = false; |
| return true; |
| default: |
| error("Unknown preferred bearer: %d", bearer); |
| return false; |
| } |
| } |
| |
| static bool device_set_prefer_bearer_str(struct btd_device *device, |
| const char *str) |
| { |
| if (!strcmp(str, "last-used")) |
| return device_set_prefer_bearer(device, PREFER_LAST_USED); |
| else if (!strcmp(str, "le")) |
| return device_set_prefer_bearer(device, PREFER_LE); |
| else if (!strcmp(str, "bredr")) |
| return device_set_prefer_bearer(device, PREFER_BREDR); |
| else if (!strcmp(str, "last-seen")) |
| return device_set_prefer_bearer(device, PREFER_LAST_SEEN); |
| |
| error("Unknown preferred bearer: %s", str); |
| return false; |
| } |
| |
| static void update_technologies(GKeyFile *file, struct btd_device *dev) |
| { |
| const char *list[2]; |
| size_t len = 0; |
| const char *bearer; |
| |
| if (dev->bredr) |
| list[len++] = "BR/EDR"; |
| |
| if (dev->le) { |
| const char *type; |
| |
| if (dev->bdaddr_type == BDADDR_LE_PUBLIC) |
| type = "public"; |
| else |
| type = "static"; |
| |
| g_key_file_set_string(file, "General", "AddressType", type); |
| |
| list[len++] = "LE"; |
| } |
| |
| g_key_file_set_string_list(file, "General", "SupportedTechnologies", |
| list, len); |
| |
| /* Store the PreferredBearer in case of dual-mode devices */ |
| bearer = device_prefer_bearer_str(dev); |
| if (bearer) { |
| g_key_file_set_string(file, "General", "PreferredBearer", |
| bearer); |
| if (dev->prefer_bearer == PREFER_LAST_USED) { |
| g_key_file_set_string(file, "General", "LastUsedBearer", |
| dev->le_state.prefer ? |
| "le" : "bredr"); |
| } |
| } |
| } |
| |
| static void store_csrk(struct csrk_info *csrk, GKeyFile *key_file, |
| const char *group) |
| { |
| char key[33]; |
| int i; |
| |
| for (i = 0; i < 16; i++) |
| sprintf(key + (i * 2), "%2.2X", csrk->key[i]); |
| |
| g_key_file_set_string(key_file, group, "Key", key); |
| g_key_file_set_integer(key_file, group, "Counter", csrk->counter); |
| g_key_file_set_boolean(key_file, group, "Authenticated", csrk->auth); |
| } |
| |
| static void store_sirk(struct sirk_info *sirk, GKeyFile *key_file, |
| uint8_t index) |
| { |
| char group[28]; |
| char key[33]; |
| int i; |
| |
| sprintf(group, "SetIdentityResolvingKey#%u", index); |
| |
| for (i = 0; i < 16; i++) |
| sprintf(key + (i * 2), "%2.2X", sirk->key[i]); |
| |
| g_key_file_set_boolean(key_file, group, "Encrypted", sirk->encrypted); |
| g_key_file_set_string(key_file, group, "Key", key); |
| g_key_file_set_integer(key_file, group, "Size", sirk->size); |
| g_key_file_set_integer(key_file, group, "Rank", sirk->rank); |
| } |
| |
| static gboolean store_device_info_cb(gpointer user_data) |
| { |
| struct btd_device *device = user_data; |
| GKeyFile *key_file; |
| GError *gerr = NULL; |
| char filename[PATH_MAX]; |
| char device_addr[18]; |
| char *str; |
| char class[9]; |
| char **uuids = NULL; |
| gsize length = 0; |
| |
| device->store_id = 0; |
| |
| ba2str(&device->bdaddr, device_addr); |
| create_filename(filename, PATH_MAX, "/%s/%s/info", |
| btd_adapter_get_storage_dir(device->adapter), |
| device_addr); |
| create_file(filename, 0600); |
| |
| key_file = g_key_file_new(); |
| if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { |
| error("Unable to load key file from %s: (%s)", filename, |
| gerr->message); |
| g_error_free(gerr); |
| g_key_file_free(key_file); |
| return FALSE; |
| } |
| |
| 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 & 0xffffff); |
| 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); |
| } |
| |
| update_technologies(key_file, device); |
| |
| g_key_file_set_boolean(key_file, "General", "Trusted", |
| device->trusted); |
| |
| g_key_file_set_boolean(key_file, "General", "Blocked", |
| device->blocked); |
| |
| g_key_file_set_boolean(key_file, "General", "CablePairing", |
| device->cable_pairing); |
| |
| if (device->wake_override != WAKE_FLAG_DEFAULT) { |
| g_key_file_set_boolean(key_file, "General", "WakeAllowed", |
| device->wake_override == |
| WAKE_FLAG_ENABLED); |
| } |
| |
| 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); |
| } |
| |
| if (device->local_csrk) |
| store_csrk(device->local_csrk, key_file, "LocalSignatureKey"); |
| |
| if (device->remote_csrk) |
| store_csrk(device->remote_csrk, key_file, "RemoteSignatureKey"); |
| |
| if (!queue_isempty(device->sirks)) { |
| const struct queue_entry *entry; |
| int i; |
| |
| for (entry = queue_get_entries(device->sirks), i = 0; entry; |
| entry = entry->next, i++) { |
| struct sirk_info *sirk = entry->data; |
| |
| store_sirk(sirk, key_file, i); |
| } |
| } |
| |
| str = g_key_file_to_data(key_file, &length, NULL); |
| if (!g_file_set_contents(filename, str, length, &gerr)) { |
| error("Unable set contents for %s: (%s)", filename, |
| gerr->message); |
| g_error_free(gerr); |
| } |
| |
| g_free(str); |
| |
| g_key_file_free(key_file); |
| g_free(uuids); |
| |
| return FALSE; |
| } |
| |
| 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)) { |
| DBG("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]; |
| char d_addr[18]; |
| GKeyFile *key_file; |
| GError *gerr = NULL; |
| char *data; |
| char *data_old; |
| gsize length = 0; |
| gsize length_old = 0; |
| |
| if (device_address_is_private(dev)) { |
| DBG("Can't store name for private addressed device %s", |
| dev->path); |
| return; |
| } |
| |
| ba2str(&dev->bdaddr, d_addr); |
| create_filename(filename, PATH_MAX, "/%s/cache/%s", |
| btd_adapter_get_storage_dir(dev->adapter), d_addr); |
| create_file(filename, 0600); |
| |
| key_file = g_key_file_new(); |
| if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { |
| error("Unable to load key file from %s: (%s)", filename, |
| gerr->message); |
| g_clear_error(&gerr); |
| } |
| |
| data_old = g_key_file_to_data(key_file, &length_old, NULL); |
| |
| g_key_file_set_string(key_file, "General", "Name", name); |
| |
| data = g_key_file_to_data(key_file, &length, NULL); |
| |
| if ((length != length_old) || (memcmp(data, data_old, length))) { |
| if (!g_file_set_contents(filename, data, length, &gerr)) { |
| error("Unable set contents for %s: (%s)", filename, |
| gerr->message); |
| g_clear_error(&gerr); |
| } |
| } |
| g_free(data); |
| g_free(data_old); |
| |
| g_key_file_free(key_file); |
| } |
| |
| static void device_store_cached_name_resolve(struct btd_device *dev) |
| { |
| char filename[PATH_MAX]; |
| char d_addr[18]; |
| GKeyFile *key_file; |
| GError *gerr = NULL; |
| char *data; |
| char *data_old; |
| gsize length = 0; |
| gsize length_old = 0; |
| uint64_t failed_time; |
| |
| if (device_address_is_private(dev)) { |
| DBG("Can't store name resolve for private addressed device %s", |
| dev->path); |
| return; |
| } |
| |
| ba2str(&dev->bdaddr, d_addr); |
| create_filename(filename, PATH_MAX, "/%s/cache/%s", |
| btd_adapter_get_storage_dir(dev->adapter), d_addr); |
| create_file(filename, 0600); |
| |
| key_file = g_key_file_new(); |
| if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { |
| error("Unable to load key file from %s: (%s)", filename, |
| gerr->message); |
| g_clear_error(&gerr); |
| } |
| |
| failed_time = (uint64_t) dev->name_resolve_failed_time; |
| |
| data_old = g_key_file_to_data(key_file, &length_old, NULL); |
| |
| g_key_file_set_uint64(key_file, "NameResolving", "FailedTime", |
| failed_time); |
| |
| data = g_key_file_to_data(key_file, &length, NULL); |
| |
| if ((length != length_old) || (memcmp(data, data_old, length))) { |
| if (!g_file_set_contents(filename, data, length, &gerr)) { |
| error("Unable set contents for %s: (%s)", filename, |
| gerr->message); |
| g_error_free(gerr); |
| } |
| } |
| |
| g_free(data); |
| g_free(data_old); |
| |
| g_key_file_free(key_file); |
| } |
| |
| static void browse_request_free(struct browse_req *req) |
| { |
| struct btd_device *device = req->device; |
| |
| if (device->browse == req) |
| device->browse = NULL; |
| |
| 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 bool gatt_cache_is_enabled(struct btd_device *device) |
| { |
| switch (btd_opts.gatt_cache) { |
| case BT_GATT_CACHE_YES: |
| return device_is_paired(device, device->bdaddr_type); |
| case BT_GATT_CACHE_NO: |
| return false; |
| case BT_GATT_CACHE_ALWAYS: |
| default: |
| return true; |
| } |
| } |
| |
| static void gatt_cache_cleanup(struct btd_device *device) |
| { |
| if (gatt_cache_is_enabled(device)) |
| return; |
| |
| bt_gatt_client_cancel_all(device->client); |
| gatt_db_clear(device->db); |
| device->le_state.svc_resolved = false; |
| } |
| |
| static void gatt_client_cleanup(struct btd_device *device) |
| { |
| if (!device->client) |
| return; |
| |
| gatt_cache_cleanup(device); |
| bt_gatt_client_set_service_changed(device->client, NULL, NULL, NULL); |
| |
| if (device->gatt_ready_id > 0) { |
| bt_gatt_client_ready_unregister(device->client, |
| device->gatt_ready_id); |
| device->gatt_ready_id = 0; |
| } |
| |
| bt_gatt_client_unref(device->client); |
| device->client = NULL; |
| } |
| |
| static void gatt_server_cleanup(struct btd_device *device) |
| { |
| if (!device->server) |
| return; |
| |
| btd_gatt_database_att_disconnected( |
| btd_adapter_get_database(device->adapter), device); |
| |
| bt_gatt_server_unref(device->server); |
| device->server = NULL; |
| } |
| |
| static void attio_cleanup(struct btd_device *device) |
| { |
| if (device->att_disconn_id) |
| bt_att_unregister_disconnect(device->att, |
| device->att_disconn_id); |
| |
| if (device->att_io) { |
| g_io_channel_shutdown(device->att_io, FALSE, NULL); |
| g_io_channel_unref(device->att_io); |
| device->att_io = NULL; |
| } |
| |
| gatt_client_cleanup(device); |
| gatt_server_cleanup(device); |
| |
| if (device->att) { |
| bt_att_unref(device->att); |
| device->att = 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; |
| |
| DBG(""); |
| |
| bt_cancel_discovery(btd_adapter_get_address(adapter), &device->bdaddr); |
| |
| attio_cleanup(device); |
| |
| 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; |
| |
| btd_gatt_client_destroy(device->client_dbus); |
| device->client_dbus = NULL; |
| |
| g_slist_free_full(device->uuids, g_free); |
| g_slist_free_full(device->primaries, g_free); |
| g_slist_free_full(device->svc_callbacks, svc_dev_remove); |
| |
| /* Reset callbacks since the device is going to be freed */ |
| gatt_db_unregister(device->db, device->db_id); |
| |
| attio_cleanup(device); |
| |
| gatt_db_unref(device->db); |
| |
| bt_ad_unref(device->ad); |
| |
| if (device->tmp_records) |
| sdp_list_free(device->tmp_records, |
| (sdp_free_func_t) sdp_record_free); |
| |
| if (device->disconn_timer) |
| timeout_remove(device->disconn_timer); |
| |
| if (device->discov_timer) |
| timeout_remove(device->discov_timer); |
| |
| if (device->temporary_timer) |
| timeout_remove(device->temporary_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); |
| |
| queue_destroy(device->sirks, free); |
| |
| btd_bearer_destroy(device->bredr); |
| btd_bearer_destroy(device->le); |
| |
| g_free(device->local_csrk); |
| g_free(device->remote_csrk); |
| free(device->ltk); |
| g_free(device->path); |
| g_free(device->alias); |
| free(device->modalias); |
| g_free(device); |
| } |
| |
| bool device_is_paired(struct btd_device *device, uint8_t bdaddr_type) |
| { |
| struct bearer_state *state = get_state(device, bdaddr_type); |
| |
| return state->paired; |
| } |
| |
| bool device_is_bonded(struct btd_device *device, uint8_t bdaddr_type) |
| { |
| struct bearer_state *state = get_state(device, bdaddr_type); |
| |
| return state->bonded; |
| } |
| |
| bool btd_device_is_trusted(struct btd_device *device) |
| { |
| return device->trusted; |
| } |
| |
| bool device_is_cable_pairing(struct btd_device *device) |
| { |
| return device->cable_pairing; |
| } |
| |
| 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 property_get_address_type(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *user_data) |
| { |
| struct btd_device *device = user_data; |
| const char *str; |
| |
| if (device->le && device->bdaddr_type == BDADDR_LE_RANDOM) |
| str = "random"; |
| else |
| str = "public"; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str); |
| |
| return TRUE; |
| } |
| |
| static gboolean dev_property_get_name(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct btd_device *device = data; |
| const char *ptr = device->name; |
| |
| 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; |
| } |
| |
| const char *btd_device_get_icon(struct btd_device *device) |
| { |
| const char *icon = NULL; |
| |
| if (device->class != 0) |
| icon = class_to_icon(device->class); |
| else if (device->appearance != 0) |
| icon = gap_appearance_to_icon(device->appearance); |
| |
| return icon; |
| } |
| |
| static gboolean dev_property_exists_icon( |
| const GDBusPropertyTable *property, void *data) |
| { |
| return btd_device_get_icon(data) != NULL; |
| } |
| |
| static gboolean dev_property_get_icon(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| const char *icon; |
| |
| icon = btd_device_get_icon(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 *dev = data; |
| dbus_bool_t val; |
| |
| if (dev->bredr_state.paired || dev->le_state.paired) |
| val = TRUE; |
| else |
| val = FALSE; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val); |
| |
| return TRUE; |
| } |
| |
| static gboolean dev_property_get_bonded(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct btd_device *dev = data; |
| dbus_bool_t val; |
| |
| if (dev->bredr_state.bonded || dev->le_state.bonded) |
| val = TRUE; |
| else |
| val = FALSE; |
| |
| 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_cable_pairing(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct btd_device *device = data; |
| dbus_bool_t val = device->cable_pairing; |
| |
| 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_tx_power(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct btd_device *dev = data; |
| dbus_int16_t val = dev->tx_power; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_INT16, &val); |
| |
| return TRUE; |
| } |
| |
| static gboolean dev_property_exists_tx_power(const GDBusPropertyTable *property, |
| void *data) |
| { |
| struct btd_device *dev = data; |
| |
| if (dev->tx_power == 127) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| dev_property_get_svc_resolved(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct btd_device *device = data; |
| gboolean val = device->svc_refreshed; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val); |
| |
| return TRUE; |
| } |
| |
| static gboolean dev_property_flags_exist(const GDBusPropertyTable *property, |
| void *data) |
| { |
| struct btd_device *device = data; |
| |
| return device->ad_flags[0] != INVALID_FLAGS; |
| } |
| |
| static gboolean |
| dev_property_get_flags(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct btd_device *device = data; |
| uint8_t *flags = device->ad_flags; |
| DBusMessageIter array; |
| |
| dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, |
| DBUS_TYPE_BYTE_AS_STRING, &array); |
| dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, |
| &flags, sizeof(device->ad_flags)); |
| dbus_message_iter_close_container(iter, &array); |
| |
| return TRUE; |
| } |
| |
| static gboolean dev_property_get_trusted(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct btd_device *device = data; |
| gboolean val = btd_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; |
| |
| btd_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 reject list 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 *dev = data; |
| dbus_bool_t connected; |
| |
| if (dev->bredr_state.connected || dev->le_state.connected) |
| connected = TRUE; |
| else |
| connected = FALSE; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &connected); |
| |
| return TRUE; |
| } |
| |
| static gboolean dev_property_get_uuids(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct btd_device *dev = data; |
| DBusMessageIter entry; |
| GSList *l; |
| |
| dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, |
| DBUS_TYPE_STRING_AS_STRING, &entry); |
| |
| if (dev->bredr_state.svc_resolved || dev->le_state.svc_resolved) |
| l = dev->uuids; |
| else if (dev->eir_uuids) |
| l = dev->eir_uuids; |
| else |
| l = dev->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 void append_manufacturer_data(void *data, void *user_data) |
| { |
| struct bt_ad_manufacturer_data *md = data; |
| DBusMessageIter *dict = user_data; |
| |
| g_dbus_dict_append_basic_array(dict, |
| DBUS_TYPE_UINT16, &md->manufacturer_id, |
| DBUS_TYPE_BYTE, &md->data, md->len); |
| } |
| |
| static gboolean |
| dev_property_get_manufacturer_data(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct btd_device *device = data; |
| DBusMessageIter dict; |
| |
| dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, |
| DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING |
| DBUS_TYPE_UINT16_AS_STRING |
| DBUS_TYPE_VARIANT_AS_STRING |
| DBUS_DICT_ENTRY_END_CHAR_AS_STRING, |
| &dict); |
| |
| bt_ad_foreach_manufacturer_data(device->ad, append_manufacturer_data, |
| &dict); |
| |
| dbus_message_iter_close_container(iter, &dict); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| dev_property_manufacturer_data_exist(const GDBusPropertyTable *property, |
| void *data) |
| { |
| struct btd_device *device = data; |
| |
| return bt_ad_has_manufacturer_data(device->ad, NULL); |
| } |
| |
| static void append_service_data(void *data, void *user_data) |
| { |
| struct bt_ad_service_data *sd = data; |
| DBusMessageIter *dict = user_data; |
| char uuid_str[MAX_LEN_UUID_STR]; |
| |
| bt_uuid_to_string(&sd->uuid, uuid_str, sizeof(uuid_str)); |
| |
| dict_append_array(dict, uuid_str, DBUS_TYPE_BYTE, &sd->data, sd->len); |
| } |
| |
| static gboolean |
| dev_property_get_service_data(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct btd_device *device = data; |
| DBusMessageIter dict; |
| |
| dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, |
| DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING |
| DBUS_TYPE_STRING_AS_STRING |
| DBUS_TYPE_VARIANT_AS_STRING |
| DBUS_DICT_ENTRY_END_CHAR_AS_STRING, |
| &dict); |
| |
| bt_ad_foreach_service_data(device->ad, append_service_data, &dict); |
| |
| dbus_message_iter_close_container(iter, &dict); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| dev_property_service_data_exist(const GDBusPropertyTable *property, |
| void *data) |
| { |
| struct btd_device *device = data; |
| |
| return bt_ad_has_service_data(device->ad, NULL); |
| } |
| |
| static void append_advertising_data(void *data, void *user_data) |
| { |
| struct bt_ad_data *ad = data; |
| DBusMessageIter *dict = user_data; |
| |
| g_dbus_dict_append_basic_array(dict, |
| DBUS_TYPE_BYTE, &ad->type, |
| DBUS_TYPE_BYTE, &ad->data, ad->len); |
| } |
| |
| static gboolean |
| dev_property_get_advertising_data(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct btd_device *device = data; |
| DBusMessageIter dict; |
| |
| dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, |
| DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING |
| DBUS_TYPE_BYTE_AS_STRING |
| DBUS_TYPE_VARIANT_AS_STRING |
| DBUS_DICT_ENTRY_END_CHAR_AS_STRING, |
| &dict); |
| |
| bt_ad_foreach_data(device->ad, append_advertising_data, &dict); |
| |
| dbus_message_iter_close_container(iter, &dict); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| dev_property_advertising_data_exist(const GDBusPropertyTable *property, |
| void *data) |
| { |
| struct btd_device *device = data; |
| |
| return bt_ad_has_data(device->ad, NULL) ? TRUE : FALSE; |
| } |
| |
| static bool device_get_wake_support(struct btd_device *device) |
| { |
| return device->wake_support; |
| } |
| |
| void device_set_wake_support(struct btd_device *device, bool wake_support) |
| { |
| device->wake_support = wake_support; |
| |
| if (device->wake_support) |
| device->supported_flags |= DEVICE_FLAG_REMOTE_WAKEUP; |
| else |
| device->supported_flags &= ~DEVICE_FLAG_REMOTE_WAKEUP; |
| |
| /* If there is not override set, set the default the same as |
| * support value. |
| */ |
| if (device->wake_override == WAKE_FLAG_DEFAULT) |
| device_set_wake_override(device, wake_support); |
| |
| /* Set wake_allowed according to the override value. |
| * Limit this to bonded device to avoid trying to set it |
| * to new devices that are simply in range. |
| */ |
| if (device_is_bonded(device, device->bdaddr_type)) |
| device_set_wake_allowed(device, |
| device->wake_override == |
| WAKE_FLAG_ENABLED, |
| -1U); |
| } |
| |
| static bool device_get_wake_allowed(struct btd_device *device) |
| { |
| return device->wake_allowed; |
| } |
| |
| void device_set_wake_override(struct btd_device *device, bool wake_override) |
| { |
| if (wake_override) |
| device->wake_override = WAKE_FLAG_ENABLED; |
| else |
| device->wake_override = WAKE_FLAG_DISABLED; |
| } |
| |
| static void device_set_wake_allowed_complete(struct btd_device *device) |
| { |
| if (device->wake_id != -1U) { |
| g_dbus_pending_property_success(device->wake_id); |
| device->wake_id = -1U; |
| } |
| |
| device->wake_allowed = device->pending_wake_allowed; |
| g_dbus_emit_property_changed(dbus_conn, device->path, |
| DEVICE_INTERFACE, "WakeAllowed"); |
| |
| store_device_info(device); |
| } |
| |
| static void set_wake_allowed_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_rp_set_device_flags *rp = param; |
| struct btd_device *dev = user_data; |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| error("Set device flags return status: %s", |
| mgmt_errstr(status)); |
| if (dev->wake_id != -1U) { |
| g_dbus_pending_property_error(dev->wake_id, |
| ERROR_INTERFACE ".Failed", |
| mgmt_errstr(status)); |
| dev->wake_id = -1U; |
| } |
| dev->pending_wake_allowed = FALSE; |
| dev->pending_flags = 0; |
| return; |
| } |
| |
| if (length < sizeof(*rp)) { |
| error("Too small Set Device Flags complete event: %d", length); |
| return; |
| } |
| |
| btd_device_flags_changed(dev, dev->supported_flags, dev->pending_flags); |
| } |
| |
| void device_set_wake_allowed(struct btd_device *device, bool wake_allowed, |
| GDBusPendingPropertySet id) |
| { |
| uint32_t flags; |
| |
| /* Pending and current value are the same unless there is a change in |
| * progress. Only update wake allowed if pending value doesn't match the |
| * new value. |
| */ |
| if (device->wake_id != -1U && id != -1U) { |
| g_dbus_pending_property_error(id, ERROR_INTERFACE ".Busy", |
| "Property change in progress"); |
| return; |
| } |
| |
| device->wake_id = id; |
| device->pending_wake_allowed = wake_allowed; |
| |
| flags = device->current_flags; |
| |
| /* Include the pending flags, or they may get overwritten. */ |
| flags |= device->pending_flags; |
| |
| if (wake_allowed) |
| flags |= DEVICE_FLAG_REMOTE_WAKEUP; |
| else |
| flags &= ~DEVICE_FLAG_REMOTE_WAKEUP; |
| |
| adapter_set_device_flags(device->adapter, device, flags, |
| set_wake_allowed_complete, device); |
| } |
| |
| static void set_past_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_rp_set_device_flags *rp = param; |
| struct btd_device *dev = user_data; |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| error("Set device flags return status: %s", |
| mgmt_errstr(status)); |
| return; |
| } |
| |
| if (length < sizeof(*rp)) { |
| error("Too small Set Device Flags complete event: %d", length); |
| return; |
| } |
| |
| btd_device_flags_changed(dev, dev->supported_flags, dev->pending_flags); |
| } |
| |
| void device_set_past_support(struct btd_device *device, bool value) |
| { |
| uint32_t flags; |
| int err; |
| |
| if (!device) |
| return; |
| |
| if (btd_device_flags_enabled(device, DEVICE_FLAG_PAST) == value) |
| return; |
| |
| DBG("value %s", value ? "true" : "false"); |
| |
| flags = device->current_flags; |
| |
| /* Include the pending flags, or they may get overwritten. */ |
| flags |= device->pending_flags; |
| |
| if (value) |
| flags |= DEVICE_FLAG_PAST; |
| else |
| flags &= ~DEVICE_FLAG_PAST; |
| |
| err = adapter_set_device_flags(device->adapter, device, flags, |
| set_past_complete, device); |
| |
| if (err) |
| error("Failed to set past support: %s", strerror(-err)); |
| } |
| |
| static gboolean |
| dev_property_get_wake_allowed(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct btd_device *device = data; |
| dbus_bool_t wake_allowed = device_get_wake_allowed(device); |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &wake_allowed); |
| |
| return TRUE; |
| } |
| |
| static void dev_property_set_wake_allowed(const GDBusPropertyTable *property, |
| DBusMessageIter *value, |
| GDBusPendingPropertySet id, void *data) |
| { |
| struct btd_device *device = 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; |
| } |
| |
| if (device->temporary) { |
| g_dbus_pending_property_error(id, |
| ERROR_INTERFACE ".Unsupported", |
| "Cannot set property while temporary"); |
| return; |
| } |
| |
| dbus_message_iter_get_basic(value, &b); |
| |
| /* Emit busy or success depending on current value. */ |
| if (b == device->pending_wake_allowed) { |
| if (device->wake_allowed == device->pending_wake_allowed) |
| g_dbus_pending_property_success(id); |
| else |
| g_dbus_pending_property_error( |
| id, ERROR_INTERFACE ".Busy", |
| "Property change in progress"); |
| |
| return; |
| } |
| |
| device_set_wake_override(device, b); |
| device_set_wake_allowed(device, b, id); |
| } |
| |
| static gboolean dev_property_wake_allowed_exist( |
| const GDBusPropertyTable *property, void *data) |
| { |
| struct btd_device *device = data; |
| |
| return device_get_wake_support(device); |
| } |
| |
| static void append_set(void *data, void *user_data) |
| { |
| struct sirk_info *info = data; |
| const char *path; |
| DBusMessageIter *iter = user_data; |
| DBusMessageIter entry, dict; |
| |
| if (!info->set) |
| return; |
| |
| path = btd_set_get_path(info->set); |
| |
| dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, |
| &entry); |
| |
| dbus_message_iter_append_basic(&entry, DBUS_TYPE_OBJECT_PATH, &path); |
| |
| dbus_message_iter_open_container(&entry, DBUS_TYPE_ARRAY, |
| DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING |
| DBUS_TYPE_STRING_AS_STRING |
| DBUS_TYPE_VARIANT_AS_STRING |
| DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); |
| |
| g_dbus_dict_append_entry(&dict, "Rank", DBUS_TYPE_BYTE, &info->rank); |
| |
| dbus_message_iter_close_container(&entry, &dict); |
| dbus_message_iter_close_container(iter, &entry); |
| } |
| |
| static gboolean dev_property_get_set(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct btd_device *device = data; |
| DBusMessageIter array; |
| |
| dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, |
| DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING |
| DBUS_TYPE_OBJECT_PATH_AS_STRING |
| DBUS_TYPE_ARRAY_AS_STRING |
| DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING |
| DBUS_TYPE_STRING_AS_STRING |
| DBUS_TYPE_VARIANT_AS_STRING |
| DBUS_DICT_ENTRY_END_CHAR_AS_STRING |
| DBUS_DICT_ENTRY_END_CHAR_AS_STRING, |
| &array); |
| |
| queue_foreach(device->sirks, append_set, &array); |
| |
| dbus_message_iter_close_container(iter, &array); |
| |
| return TRUE; |
| } |
| |
| static gboolean dev_property_set_exists(const GDBusPropertyTable *property, |
| void *data) |
| { |
| struct btd_device *device = data; |
| |
| return !queue_isempty(device->sirks); |
| } |
| |
| static bool disconnect_all(gpointer user_data) |
| { |
| struct btd_device *device = user_data; |
| |
| device->disconn_timer = 0; |
| |
| if (device->bredr_state.connected) |
| btd_adapter_disconnect_device(device->adapter, &device->bdaddr, |
| BDADDR_BREDR); |
| |
| if (device->le_state.connected) |
| 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->disconn_timer > 0) |
| timeout_remove(device->disconn_timer); |
| |
| disconnect_all(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) { |
| if (device->le) |
| err = btd_adapter_block_address(device->adapter, |
| &device->bdaddr, |
| device->bdaddr_type); |
| if (!err && device->bredr) |
| err = btd_adapter_block_address(device->adapter, |
| &device->bdaddr, |
| BDADDR_BREDR); |
| } |
| |
| if (err < 0) |
| return err; |
| |
| device->blocked = TRUE; |
| |
| store_device_info(device); |
| |
| btd_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) { |
| if (device->le) |
| err = btd_adapter_unblock_address(device->adapter, |
| &device->bdaddr, |
| device->bdaddr_type); |
| if (!err && device->bredr) |
| err = btd_adapter_unblock_address(device->adapter, |
| &device->bdaddr, |
| BDADDR_BREDR); |
| } |
| |
| 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 browse_request_exit(DBusConnection *conn, void *user_data) |
| { |
| struct browse_req *req = user_data; |
| |
| DBG("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_disconnect_watches_callback(struct btd_device *device) |
| { |
| if (!device || !device->watches) |
| return; |
| |
| 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); |
| } |
| } |
| |
| 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->att_io) { |
| g_io_channel_shutdown(device->att_io, FALSE, NULL); |
| g_io_channel_unref(device->att_io); |
| device->att_io = NULL; |
| } |
| |
| if (device->connect) { |
| const char *err_str; |
| DBusMessage *reply; |
| |
| if (device->bonding_status == MGMT_STATUS_AUTH_FAILED) |
| err_str = ERR_BREDR_CONN_KEY_MISSING; |
| else |
| err_str = ERR_BREDR_CONN_CANCELED; |
| reply = btd_error_failed(device->connect, err_str); |
| g_dbus_send_message(dbus_conn, reply); |
| dbus_message_unref(device->connect); |
| device->bonding_status = 0; |
| device->connect = NULL; |
| } |
| |
| if (btd_device_is_connected(device) && 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; |
| |
| device_disconnect_watches_callback(device); |
| |
| if (!btd_device_is_connected(device)) { |
| if (msg) |
| g_dbus_send_reply(dbus_conn, msg, DBUS_TYPE_INVALID); |
| return; |
| } |
| |
| device->disconn_timer = timeout_add_seconds(DISCONNECT_TIMER, |
| disconnect_all, |
| device, NULL); |
| } |
| |
| bool device_is_disconnecting(struct btd_device *device) |
| { |
| return device->disconn_timer > 0; |
| } |
| |
| bool device_is_connecting(struct btd_device *device) |
| { |
| return device->connect != NULL; |
| } |
| |
| static void add_set(void *data, void *user_data) |
| { |
| struct sirk_info *sirk = data; |
| struct btd_device *device = user_data; |
| struct btd_device_set *set; |
| |
| if (!sirk->encrypted) |
| return; |
| |
| set = btd_set_add_device(device, device->ltk->key, sirk->key, |
| sirk->size); |
| if (!set) |
| return; |
| |
| if (sirk->set != set) { |
| sirk->set = set; |
| g_dbus_emit_property_changed(dbus_conn, device->path, |
| DEVICE_INTERFACE, "Sets"); |
| } |
| } |
| |
| void device_set_ltk(struct btd_device *device, const uint8_t val[16], |
| bool central, uint8_t enc_size) |
| { |
| if (!device->ltk) |
| device->ltk = new0(struct ltk_info, 1); |
| |
| memcpy(device->ltk->key, val, sizeof(device->ltk->key)); |
| device->ltk->central = central; |
| device->ltk->enc_size = enc_size; |
| bt_att_set_enc_key_size(device->att, enc_size); |
| |
| /* Check if there is any set/sirk that needs decryption */ |
| queue_foreach(device->sirks, add_set, device); |
| } |
| |
| bool btd_device_get_ltk(struct btd_device *device, uint8_t key[16], |
| bool *central, uint8_t *enc_size) |
| { |
| if (!device || !device->ltk || !key) |
| return false; |
| |
| memcpy(key, device->ltk->key, sizeof(device->ltk->key)); |
| |
| if (central) |
| *central = device->ltk->central; |
| |
| if (enc_size) |
| *enc_size = device->ltk->enc_size; |
| |
| return true; |
| } |
| |
| void device_set_csrk(struct btd_device *device, const uint8_t val[16], |
| uint32_t counter, uint8_t type, |
| bool store_hint) |
| { |
| struct csrk_info **handle; |
| struct csrk_info *csrk; |
| bool auth; |
| |
| switch (type) { |
| case 0x00: |
| handle = &device->local_csrk; |
| auth = FALSE; |
| break; |
| case 0x01: |
| handle = &device->remote_csrk; |
| auth = FALSE; |
| break; |
| case 0x02: |
| handle = &device->local_csrk; |
| auth = TRUE; |
| break; |
| case 0x03: |
| handle = &device->remote_csrk; |
| auth = TRUE; |
| break; |
| default: |
| warn("Unsupported CSRK type %u", type); |
| return; |
| } |
| |
| if (!*handle) |
| *handle = g_new0(struct csrk_info, 1); |
| |
| csrk = *handle; |
| memcpy(csrk->key, val, sizeof(csrk->key)); |
| csrk->counter = counter; |
| csrk->auth = auth; |
| |
| if (!store_hint) |
| return; |
| |
| store_device_info(device); |
| |
| btd_device_set_temporary(device, false); |
| } |
| |
| static bool match_sirk(const void *data, const void *match_data) |
| { |
| const struct sirk_info *sirk = data; |
| const uint8_t *key = match_data; |
| |
| return !memcmp(sirk->key, key, sizeof(sirk->key)); |
| } |
| |
| static struct sirk_info *device_add_sirk_info(struct btd_device *device, |
| bool encrypted, uint8_t key[16], |
| uint8_t size, uint8_t rank) |
| { |
| struct sirk_info *sirk; |
| |
| sirk = queue_find(device->sirks, match_sirk, key); |
| if (sirk) |
| return sirk; |
| |
| sirk = new0(struct sirk_info, 1); |
| sirk->encrypted = encrypted; |
| memcpy(sirk->key, key, sizeof(sirk->key)); |
| sirk->size = size; |
| sirk->rank = rank; |
| |
| queue_push_tail(device->sirks, sirk); |
| store_device_info(device); |
| |
| return sirk; |
| } |
| |
| bool btd_device_add_set(struct btd_device *device, bool encrypted, |
| uint8_t key[16], uint8_t size, uint8_t rank) |
| { |
| struct btd_device_set *set; |
| struct sirk_info *sirk; |
| |
| if (encrypted && !device->ltk) |
| return false; |
| |
| sirk = device_add_sirk_info(device, encrypted, key, size, rank); |
| if (!sirk) |
| return false; |
| |
| set = btd_set_add_device(device, encrypted ? device->ltk->key : NULL, |
| key, size); |
| if (!set) |
| return false; |
| |
| if (sirk->set != set) { |
| sirk->set = set; |
| g_dbus_emit_property_changed(dbus_conn, device->path, |
| DEVICE_INTERFACE, "Sets"); |
| } |
| |
| return true; |
| } |
| |
| static void device_set_auto_connect(struct btd_device *device, gboolean enable) |
| { |
| char addr[18]; |
| const char *bearer; |
| |
| if (!device || !device->le) |
| return; |
| |
| ba2str(&device->bdaddr, addr); |
| |
| DBG("%s auto connect: %d", addr, enable); |
| |
| if (device->auto_connect == enable) |
| return; |
| |
| device->auto_connect = enable; |
| |
| /* Disabling auto connect */ |
| if (enable == FALSE) { |
| adapter_connect_list_remove(device->adapter, device); |
| adapter_auto_connect_remove(device->adapter, device); |
| return; |
| } |
| |
| if (device_address_is_private(device)) |
| return; |
| |
| /* Inhibit auto connect if BR/EDR bearer is preferred */ |
| bearer = device_prefer_bearer_str(device); |
| if (bearer && !strcasecmp(bearer, "bredr")) |
| return; |
| |
| /* Enabling auto connect */ |
| adapter_auto_connect_add(device->adapter, device); |
| |
| if (device->attrib) { |
| DBG("Already connected"); |
| return; |
| } |
| |
| adapter_connect_list_add(device->adapter, device); |
| } |
| |
| static DBusMessage *dev_disconnect(DBusConnection *conn, DBusMessage *msg, |
| void *user_data) |
| { |
| struct btd_device *device = user_data; |
| |
| /* |
| * If device is not trusted disable connections through passive |
| * scanning until Device1.Connect is called |
| */ |
| if (device->auto_connect && !device->trusted) { |
| device->disable_auto_connect = TRUE; |
| device_set_auto_connect(device, FALSE); |
| } |
| |
| 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; |
| |
| err = btd_service_connect(service); |
| if (!err) |
| 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) |
| btd_device_set_temporary(dev, false); |
| |
| if (dev->pending == NULL) |
| goto done; |
| |
| if (!btd_device_is_connected(dev)) { |
| switch (-err) { |
| case EHOSTDOWN: /* page timeout */ |
| case EHOSTUNREACH: /* adapter not powered */ |
| case ECONNABORTED: /* adapter powered down */ |
| 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: |
| g_slist_free(dev->pending); |
| dev->pending = NULL; |
| |
| if (!dev->connect) |
| return; |
| |
| if (dbus_message_is_method_call(dev->connect, DEVICE_INTERFACE, |
| "Connect")) { |
| if (!err) |
| dev->general_connect = TRUE; |
| else if (find_service_with_state(dev->services, |
| BTD_SERVICE_STATE_CONNECTED)) |
| /* Reset error if there are services connected */ |
| err = 0; |
| } else if (dbus_message_is_method_call(dev->connect, |
| BTD_BEARER_BREDR_INTERFACE, |
| "Connect")) { |
| /* Send D-Bus reply to bredr.connect() */ |
| if (err) { |
| btd_bearer_connected(dev->bredr, err); |
| goto unref; |
| } |
| } |
| |
| DBG("returning response to %s", dbus_message_get_sender(dev->connect)); |
| |
| if (err) { |
| /* Fallback to LE bearer if supported */ |
| if (err == -EHOSTDOWN && dev->le && !dev->le_state.connected) { |
| err = device_connect_le(dev); |
| if (err == 0) |
| return; |
| } |
| |
| g_dbus_send_message(dbus_conn, |
| btd_error_bredr_errno(dev->connect, err)); |
| } else { |
| /* Start passive SDP discovery to update known services */ |
| if (dev->bredr && !dev->svc_refreshed && dev->refresh_discovery) |
| device_browse_sdp(dev, NULL); |
| g_dbus_send_reply(dbus_conn, dev->connect, DBUS_TYPE_INVALID); |
| } |
| |
| unref: |
| dbus_message_unref(dev->connect); |
| dev->connect = NULL; |
| } |
| |
| void device_add_eir_uuids(struct btd_device *dev, GSList *uuids) |
| { |
| GSList *l; |
| GSList *added = NULL; |
| |
| if (dev->bredr_state.svc_resolved || dev->le_state.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 = g_slist_append(added, (void *)str); |
| dev->eir_uuids = g_slist_append(dev->eir_uuids, g_strdup(str)); |
| } |
| |
| device_probe_profiles(dev, added); |
| g_slist_free(added); |
| } |
| |
| static void add_manufacturer_data(void *data, void *user_data) |
| { |
| struct eir_msd *msd = data; |
| struct btd_device *dev = user_data; |
| |
| if (!bt_ad_add_manufacturer_data(dev->ad, msd->company, msd->data, |
| msd->data_len)) |
| return; |
| |
| g_dbus_emit_property_changed(dbus_conn, dev->path, |
| DEVICE_INTERFACE, "ManufacturerData"); |
| } |
| |
| void device_set_manufacturer_data(struct btd_device *dev, GSList *list, |
| bool duplicate) |
| { |
| if (duplicate) |
| bt_ad_clear_manufacturer_data(dev->ad); |
| |
| g_slist_foreach(list, add_manufacturer_data, dev); |
| } |
| |
| static void add_service_data(void *data, void *user_data) |
| { |
| struct eir_sd *sd = data; |
| struct btd_device *dev = user_data; |
| bt_uuid_t uuid; |
| GSList *l; |
| |
| if (bt_string_to_uuid(&uuid, sd->uuid) < 0) |
| return; |
| |
| if (!bt_ad_add_service_data(dev->ad, &uuid, sd->data, sd->data_len)) |
| return; |
| |
| l = g_slist_append(NULL, sd->uuid); |
| device_add_eir_uuids(dev, l); |
| g_slist_free(l); |
| |
| g_dbus_emit_property_changed(dbus_conn, dev->path, |
| DEVICE_INTERFACE, "ServiceData"); |
| } |
| |
| void device_set_service_data(struct btd_device *dev, GSList *list, |
| bool duplicate) |
| { |
| if (duplicate) |
| bt_ad_clear_service_data(dev->ad); |
| |
| g_slist_foreach(list, add_service_data, dev); |
| } |
| |
| static void add_data(void *data, void *user_data) |
| { |
| struct eir_ad *ad = data; |
| struct btd_device *dev = user_data; |
| |
| if (!bt_ad_add_data(dev->ad, ad->type, ad->data, ad->len)) |
| return; |
| |
| if (ad->type == EIR_TRANSPORT_DISCOVERY) |
| g_dbus_emit_property_changed(dbus_conn, dev->path, |
| DEVICE_INTERFACE, |
| "AdvertisingData"); |
| } |
| |
| void device_set_data(struct btd_device *dev, GSList *list, |
| bool duplicate) |
| { |
| if (duplicate) |
| bt_ad_clear_data(dev->ad); |
| |
| g_slist_foreach(list, add_data, dev); |
| } |
| |
| 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; |
| } |
| |
| bool btd_device_all_services_allowed(struct btd_device *dev) |
| { |
| GSList *l; |
| struct btd_adapter *adapter = dev->adapter; |
| struct btd_service *service; |
| struct btd_profile *profile; |
| |
| for (l = dev->services; l != NULL; l = g_slist_next(l)) { |
| service = l->data; |
| profile = btd_service_get_profile(service); |
| |
| if (!profile || !profile->auto_connect) |
| continue; |
| |
| if (!btd_adapter_is_uuid_allowed(adapter, profile->remote_uuid)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void btd_device_update_allowed_services(struct btd_device *dev) |
| { |
| struct btd_adapter *adapter = dev->adapter; |
| struct btd_service *service; |
| struct btd_profile *profile; |
| GSList *l; |
| bool is_allowed; |
| char addr[18]; |
| |
| /* If service discovery is ongoing, let the service discovery complete |
| * callback call this function. |
| */ |
| if (dev->browse) { |
| ba2str(&dev->bdaddr, addr); |
| DBG("service discovery of %s is ongoing. Skip updating allowed " |
| "services", addr); |
| return; |
| } |
| |
| for (l = dev->services; l != NULL; l = g_slist_next(l)) { |
| service = l->data; |
| profile = btd_service_get_profile(service); |
| |
| is_allowed = btd_adapter_is_uuid_allowed(adapter, |
| profile->remote_uuid); |
| btd_service_set_allowed(service, is_allowed); |
| } |
| } |
| |
| static const struct btd_profile *get_service_profile(void *data, |
| void *user_data) |
| { |
| return btd_service_get_profile(data); |
| } |
| |
| 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 dev->pending; |
| |
| if (btd_service_is_allowed(service)) |
| return g_slist_prepend(dev->pending, service); |
| |
| info("service %s is blocked", uuid); |
| 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 (!btd_service_is_allowed(service)) { |
| info("service %s is blocked", p->remote_uuid); |
| continue; |
| } |
| |
| if (g_slist_find(dev->pending, service)) |
| continue; |
| |
| if (btd_service_get_state(service) != |
| BTD_SERVICE_STATE_DISCONNECTED) |
| continue; |
| |
| dev->pending = g_slist_append(dev->pending, service); |
| } |
| |
| /* Connect in priority order */ |
| dev->pending = btd_profile_sort_list(dev->pending, get_service_profile, |
| NULL); |
| |
| return dev->pending; |
| } |
| |
| #define NVAL_TIME ((time_t) -1) |
| #define SEEN_TRESHHOLD 300 |
| |
| static uint8_t select_conn_bearer(struct btd_device *dev) |
| { |
| time_t bredr_last = NVAL_TIME, le_last = NVAL_TIME; |
| time_t current = time(NULL); |
| |
| /* Use preferred bearer or bonded bearer in case only one is bonded */ |
| if (dev->bredr_state.prefer || |
| (dev->bredr_state.bonded && !dev->le_state.bonded)) |
| return BDADDR_BREDR; |
| else if (dev->le_state.prefer || |
| (!dev->bredr_state.bonded && dev->le_state.bonded)) |
| return dev->bdaddr_type; |
| |
| /* If the address is random it can only be connected over LE */ |
| if (dev->bdaddr_type == BDADDR_LE_RANDOM) |
| return dev->bdaddr_type; |
| |
| if (dev->bredr_state.connectable && dev->bredr_state.last_seen) { |
| bredr_last = current - dev->bredr_state.last_seen; |
| if (bredr_last > SEEN_TRESHHOLD) |
| bredr_last = NVAL_TIME; |
| } |
| |
| if (dev->le_state.connectable && dev->le_state.last_seen) { |
| le_last = current - dev->le_state.last_seen; |
| if (le_last > SEEN_TRESHHOLD) |
| le_last = NVAL_TIME; |
| } |
| |
| if (le_last == NVAL_TIME && bredr_last == NVAL_TIME) |
| return dev->bdaddr_type; |
| |
| if (dev->bredr && (!dev->le || le_last == NVAL_TIME)) |
| return BDADDR_BREDR; |
| |
| if (dev->le && (!dev->bredr || bredr_last == NVAL_TIME)) |
| return dev->bdaddr_type; |
| |
| /* |
| * Prefer BR/EDR if time is the same since it might be from an |
| * advertisement with BR/EDR flag set. |
| */ |
| if (bredr_last <= le_last && btd_adapter_get_bredr(dev->adapter)) |
| return BDADDR_BREDR; |
| |
| return dev->bdaddr_type; |
| } |
| |
| static void device_browse_cb(struct btd_device *dev, int err, void *user_data) |
| { |
| DBG("err %d (%s)", err, strerror(-err)); |
| |
| /* If there are not errors with discovery proceed conecting services */ |
| if (!err) { |
| dev->pending = create_pending_list(dev, NULL); |
| connect_next(dev); |
| } |
| } |
| |
| int btd_device_connect_services(struct btd_device *dev, GSList *services) |
| { |
| GSList *l; |
| uint8_t bdaddr_type; |
| |
| if (dev->pending || dev->connect || dev->browse) |
| return -EBUSY; |
| |
| if (!btd_adapter_get_powered(dev->adapter)) |
| return -ENETDOWN; |
| |
| bdaddr_type = select_conn_bearer(dev); |
| if (bdaddr_type != BDADDR_BREDR) { |
| if (dev->le_state.connected) |
| return -EALREADY; |
| |
| return device_connect_le(dev); |
| } |
| |
| if (!dev->bredr_state.svc_resolved) { |
| int err; |
| |
| err = device_discover_services(dev); |
| if (err) |
| return err; |
| |
| /* Wait for service discovery to complete before connecting to |
| * profiles. |
| */ |
| device_wait_for_svc_complete(dev, device_browse_cb, NULL); |
| |
| return 0; |
| } |
| |
| if (services) { |
| for (l = services; l; l = g_slist_next(l)) { |
| struct btd_service *service = l->data; |
| |
| dev->pending = g_slist_append(dev->pending, service); |
| } |
| } else { |
| dev->pending = create_pending_list(dev, NULL); |
| } |
| |
| return connect_next(dev); |
| } |
| |
| DBusMessage *device_connect_profiles(struct btd_device *dev, |
| uint8_t bdaddr_type, DBusMessage *msg, const char *uuid) |
| { |
| struct bearer_state *state = get_state(dev, bdaddr_type); |
| 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_str(msg, ERR_BREDR_CONN_BUSY); |
| |
| if (!btd_adapter_get_powered(dev->adapter)) { |
| return btd_error_not_ready_str(msg, |
| ERR_BREDR_CONN_ADAPTER_NOT_POWERED); |
| } |
| |
| btd_device_set_temporary(dev, false); |
| |
| if (!state->svc_resolved) |
| goto resolve_services; |
| |
| dev->pending = create_pending_list(dev, uuid); |
| if (!dev->pending) { |
| if (dev->svc_refreshed) { |
| if (dbus_message_is_method_call(msg, DEVICE_INTERFACE, |
| "Connect") && |
| find_service_with_state(dev->services, |
| BTD_SERVICE_STATE_CONNECTED)) { |
| return dbus_message_new_method_return(msg); |
| } else { |
| return btd_error_profile_unavailable(msg); |
| } |
| } |
| |
| goto resolve_services; |
| } |
| |
| err = connect_next(dev); |
| if (err < 0) { |
| if (err == -EALREADY) |
| return dbus_message_new_method_return(msg); |
| return btd_error_bredr_errno(msg, err); |
| } |
| |
| dev->connect = dbus_message_ref(msg); |
| |
| return NULL; |
| |
| resolve_services: |
| DBG("Resolving services for %s", dev->path); |
| |
| if (bdaddr_type == BDADDR_BREDR) |
| err = device_browse_sdp(dev, msg); |
| else |
| err = device_browse_gatt(dev, msg); |
| if (err < 0) { |
| return btd_error_failed(msg, bdaddr_type == BDADDR_BREDR ? |
| ERR_BREDR_CONN_SDP_SEARCH : ERR_LE_CONN_GATT_BROWSE); |
| } |
| |
| return NULL; |
| } |
| |
| static DBusMessage *dev_connect(DBusConnection *conn, DBusMessage *msg, |
| void *user_data) |
| { |
| struct btd_device *dev = user_data; |
| uint8_t bdaddr_type; |
| |
| if (dev->bonding) |
| return btd_error_in_progress(msg); |
| |
| if (dev->bredr_state.connected) { |
| /* |
| * Check if services have been resolved and there is at least |
| * one connected before switching to connect LE. |
| */ |
| if (dev->bredr_state.svc_resolved && |
| find_service_with_state(dev->services, |
| BTD_SERVICE_STATE_CONNECTED)) |
| bdaddr_type = dev->bdaddr_type; |
| else |
| bdaddr_type = BDADDR_BREDR; |
| } else if (dev->le_state.connected && dev->bredr) |
| bdaddr_type = BDADDR_BREDR; |
| else |
| bdaddr_type = select_conn_bearer(dev); |
| |
| dev->auth_failures = 0; |
| |
| if (bdaddr_type != BDADDR_BREDR) { |
| int err; |
| |
| if (dev->connect) |
| return btd_error_in_progress(msg); |
| |
| if (dev->le_state.connected) |
| return dbus_message_new_method_return(msg); |
| |
| btd_device_set_temporary(dev, false); |
| |
| if (dev->disable_auto_connect) { |
| dev->disable_auto_connect = FALSE; |
| device_set_auto_connect(dev, TRUE); |
| } |
| |
| err = device_connect_le(dev); |
| if (err < 0) |
| return btd_error_failed(msg, strerror(-err)); |
| |
| dev->connect = dbus_message_ref(msg); |
| |
| return NULL; |
| } |
| |
| return device_connect_profiles(dev, bdaddr_type, 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_str(msg, |
| ERR_BREDR_CONN_INVALID_ARGUMENTS); |
| } |
| |
| uuid = bt_name2string(pattern); |
| if (uuid == NULL) |
| return btd_error_invalid_args_str(msg, |
| ERR_BREDR_CONN_INVALID_ARGUMENTS); |
| |
| reply = device_connect_profiles(dev, BDADDR_BREDR, msg, uuid); |
| 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); |
| free(uuid); |
| |
| if (!service) |
| return btd_error_invalid_args(msg); |
| |
| if (dev->disconnect) |
| return btd_error_in_progress(msg); |
| |
| if (btd_service_get_state(service) == BTD_SERVICE_STATE_DISCONNECTED) |
| return dbus_message_new_method_return(msg); |
| |
| dev->disconnect = dbus_message_ref(msg); |
| |
| err = btd_service_disconnect(service); |
| if (err == 0) |
| return NULL; |
| |
| dbus_message_unref(dev->disconnect); |
| dev->disconnect = NULL; |
| |
| if (err == -ENOTSUP) |
| return btd_error_not_supported(msg); |
| else if (err == -EALREADY) |
| return dbus_message_new_method_return(msg); |
| |
| return btd_error_failed(msg, strerror(-err)); |
| } |
| |
| static void store_services(struct btd_device *device) |
| { |
| char filename[PATH_MAX]; |
| char dst_addr[18]; |
| uuid_t uuid; |
| char *prim_uuid; |
| GKeyFile *key_file; |
| GError *gerr = NULL; |
| GSList *l; |
| char *data; |
| gsize length = 0; |
| |
| if (device_address_is_private(device)) { |
| DBG("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(&device->bdaddr, dst_addr); |
| |
| create_filename(filename, PATH_MAX, "/%s/%s/attributes", |
| btd_adapter_get_storage_dir(device->adapter), |
| dst_addr); |
| 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, 0600); |
| if (!g_file_set_contents(filename, data, length, &gerr)) { |
| error("Unable set contents for %s: (%s)", filename, |
| gerr->message); |
| g_error_free(gerr); |
| } |
| } |
| |
| free(prim_uuid); |
| g_free(data); |
| g_key_file_free(key_file); |
| } |
| |
| static void store_gatt_db(struct btd_device *device) |
| { |
| char filename[PATH_MAX]; |
| char dst_addr[18]; |
| |
| if (device_address_is_private(device)) { |
| DBG("Can't store GATT db for private addressed device %s", |
| device->path); |
| return; |
| } |
| |
| if (!gatt_cache_is_enabled(device)) |
| return; |
| |
| ba2str(&device->bdaddr, dst_addr); |
| |
| create_filename(filename, PATH_MAX, "/%s/cache/%s", |
| btd_adapter_get_storage_dir(device->adapter), |
| dst_addr); |
| create_file(filename, 0600); |
| |
| btd_settings_gatt_db_store(device->db, filename); |
| } |
| |
| static void browse_request_complete(struct browse_req *req, uint8_t type, |
| uint8_t bdaddr_type, int err) |
| { |
| struct btd_device *dev = req->device; |
| DBusMessage *reply = NULL; |
| DBusMessage *msg; |
| |
| if (req->type != type) |
| return; |
| |
| if (!req->msg) |
| goto done; |
| |
| if (dbus_message_is_method_call(req->msg, DEVICE_INTERFACE, "Pair")) { |
| if (!device_is_paired(dev, bdaddr_type)) { |
| reply = btd_error_failed(req->msg, "Not paired"); |
| goto done; |
| } |
| |
| if (dev->pending_paired) { |
| if (bdaddr_type == BDADDR_BREDR) |
| btd_bearer_paired(dev->bredr); |
| else |
| btd_bearer_paired(dev->le); |
| |
| g_dbus_emit_property_changed(dbus_conn, dev->path, |
| DEVICE_INTERFACE, "Paired"); |
| dev->pending_paired = false; |
| } |
| |
| /* Disregard browse errors in case of Pair */ |
| reply = g_dbus_create_reply(req->msg, DBUS_TYPE_INVALID); |
| goto done; |
| } else if (dbus_message_is_method_call(req->msg, |
| BTD_BEARER_BREDR_INTERFACE, "Connect")) { |
| /* Send D-Bus reply to bredr.connect() */ |
| if (err && dev->bredr) { |
| btd_bearer_connected(dev->bredr, err); |
| goto done; |
| } |
| } |
| |
| if (err) { |
| /* Fallback to LE bearer if supported */ |
| if (err == -EHOSTDOWN && bdaddr_type == BDADDR_BREDR && |
| dev->le && !dev->le_state.connected) { |
| err = device_connect_le(dev); |
| if (err == 0) |
| goto done; |
| } |
| |
| if (bdaddr_type == BDADDR_BREDR) |
| reply = btd_error_bredr_errno(req->msg, err); |
| else |
| reply = btd_error_le_errno(req->msg, err); |
| |
| goto done; |
| } |
| |
| /* if successfully resolved services we need to free browsing request |
| * before passing message back to connect functions, otherwise |
| * device->browse is set and "InProgress" error is returned instead |
| * of actually connecting services |
| */ |
| msg = dbus_message_ref(req->msg); |
| browse_request_free(req); |
| req = NULL; |
| |
| if (dbus_message_is_method_call(msg, DEVICE_INTERFACE, "Connect")) |
| reply = dev_connect(dbus_conn, msg, dev); |
| else if (dbus_message_is_method_call(msg, DEVICE_INTERFACE, |
| "ConnectProfile")) |
| reply = connect_profile(dbus_conn, msg, dev); |
| else |
| reply = g_dbus_create_reply(msg, DBUS_TYPE_INVALID); |
| |
| dbus_message_unref(msg); |
| |
| done: |
| if (reply) |
| g_dbus_send_message(dbus_conn, reply); |
| |
| if (req) |
| browse_request_free(req); |
| } |
| |
| void device_set_refresh_discovery(struct btd_device *dev, bool refresh) |
| { |
| dev->refresh_discovery = refresh; |
| } |
| |
| static void device_set_svc_refreshed(struct btd_device *device, bool value) |
| { |
| if (device->svc_refreshed == value) |
| return; |
| |
| device->svc_refreshed = value; |
| |
| g_dbus_emit_property_changed(dbus_conn, device->path, |
| DEVICE_INTERFACE, "ServicesResolved"); |
| } |
| |
| static void device_svc_resolved(struct btd_device *dev, uint8_t browse_type, |
| uint8_t bdaddr_type, int err) |
| { |
| struct bearer_state *state = get_state(dev, bdaddr_type); |
| struct browse_req *req = dev->browse; |
| |
| DBG("%s err %d", dev->path, err); |
| |
| state->svc_resolved = true; |
| |
| /* Disconnection notification can happen before this function |
| * gets called, so don't set svc_refreshed for a disconnected |
| * device. |
| */ |
| if (state->connected) |
| device_set_svc_refreshed(dev, true); |
| |
| g_slist_free_full(dev->eir_uuids, g_free); |
| dev->eir_uuids = NULL; |
| |
| if (dev->pending_paired) { |
| if (bdaddr_type == BDADDR_BREDR) |
| btd_bearer_paired(dev->bredr); |
| else |
| btd_bearer_paired(dev->le); |
| |
| g_dbus_emit_property_changed(dbus_conn, dev->path, |
| DEVICE_INTERFACE, "Paired"); |
| dev->pending_paired = false; |
| } |
| |
| if (!dev->temporary) { |
| store_device_info(dev); |
| |
| if (bdaddr_type != BDADDR_BREDR && err == 0) |
| store_services(dev); |
| } |
| |
| if (req) |
| browse_request_complete(req, browse_type, bdaddr_type, err); |
| |
| 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); |
| } |
| |
| btd_device_update_allowed_services(dev); |
| device_resolved_drivers(dev->adapter, dev); |
| } |
| |
| static struct bonding_req *bonding_request_new(DBusMessage *msg, |
| struct btd_device *device, |
| uint8_t bdaddr_type, |
| 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->bdaddr_type = bdaddr_type; |
| |
| 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 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 DBusMessage *pair_device(DBusConnection *conn, DBusMessage *msg, |
| void *data) |
| { |
| struct btd_device *device = data; |
| struct btd_adapter *adapter = device->adapter; |
| struct bearer_state *state; |
| uint8_t bdaddr_type; |
| const char *sender; |
| struct agent *agent; |
| struct bonding_req *bonding; |
| enum mgmt_io_capability io_cap; |
| int err; |
| |
| btd_device_set_temporary(device, false); |
| |
| if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_INVALID)) |
| return btd_error_invalid_args(msg); |
| |
| if (device->bonding || device->connect) |
| return btd_error_in_progress(msg); |
| |
| /* Only use this selection algorithms when device is combo |
| * chip. Otherwise, it will use the wrong bearer to establish |
| * a connection if the device is already paired, which will |
| * stall the pairing procedure. For example, for a BLE only |
| * device, if the device is already paired, and upper layer |
| * issue the pair device again, it will set bdaddr_type to |
| * BDADDR_BREDR since LE is bonded, then it goes with BR/EDR |
| * bearer. |
| */ |
| if (device->bredr && device->le) { |
| if (device->bredr_state.bonded) |
| bdaddr_type = device->bdaddr_type; |
| else if (device->le_state.bonded) |
| bdaddr_type = BDADDR_BREDR; |
| else |
| bdaddr_type = select_conn_bearer(device); |
| } else { |
| bdaddr_type = device->bdaddr_type; |
| } |
| |
| state = get_state(device, bdaddr_type); |
| |
| if (state->bonded) |
| 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 = MGMT_IO_CAPABILITY_NOINPUTNOOUTPUT; |
| |
| bonding = bonding_request_new(msg, device, bdaddr_type, 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 (bdaddr_type != BDADDR_BREDR) { |
| if (device->disable_auto_connect) { |
| device->disable_auto_connect = FALSE; |
| device_set_auto_connect(device, TRUE); |
| } |
| |
| if (!state->connected && btd_le_connect_before_pairing()) |
| err = device_connect_le(device); |
| else if (!state->connected || !bt_att_set_security(device->att, |
| BT_ATT_SECURITY_MEDIUM)) |
| err = adapter_create_bonding(adapter, &device->bdaddr, |
| device->bdaddr_type, |
| io_cap); |
| else |
| err = 0; |
| } else { |
| err = adapter_create_bonding(adapter, &device->bdaddr, |
| BDADDR_BREDR, io_cap); |
| } |
| |
| if (err < 0) { |
| bonding_request_free(device->bonding); |
| 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"); |
| case MGMT_STATUS_ALREADY_PAIRED: |
| return dbus_message_new_error(msg, |
| ERROR_INTERFACE ".AlreadyExists", |
| "Already Paired"); |
| default: |
| return dbus_message_new_error(msg, |
| ERROR_INTERFACE ".AuthenticationFailed", |
| "Authentication Failed"); |
| } |
| } |
| |
| 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) { |
| btd_adapter_remove_bonding(device->adapter, &device->bdaddr, |
| device->bdaddr_type); |
| return btd_error_does_not_exist(msg); |
| } |
| |
| device_cancel_bonding(device, MGMT_STATUS_CANCELLED); |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static sdp_list_t *read_device_records(struct btd_device *device); |
| |
| static DBusMessage *get_service_records(DBusConnection *conn, DBusMessage *msg, |
| void *data) |
| { |
| DBusMessage *reply; |
| DBusMessageIter records_arr, record; |
| struct btd_device *device = data; |
| sdp_list_t *cur; |
| |
| if (!btd_adapter_get_powered(device->adapter)) |
| return btd_error_not_ready(msg); |
| |
| if (!btd_device_is_connected(device)) |
| return btd_error_not_connected(msg); |
| |
| if (!device->bredr_state.svc_resolved) |
| return btd_error_not_ready(msg); |
| |
| if (!device->tmp_records) { |
| device->tmp_records = read_device_records(device); |
| if (!device->tmp_records) |
| return btd_error_does_not_exist(msg); |
| } |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return btd_error_failed(msg, "Could not create method reply"); |
| |
| dbus_message_iter_init_append(reply, &records_arr); |
| if (!dbus_message_iter_open_container(&records_arr, DBUS_TYPE_ARRAY, |
| "ay", &record)) { |
| dbus_message_unref(reply); |
| return btd_error_failed(msg, "Could not initialize iterator"); |
| } |
| |
| for (cur = device->tmp_records; cur; cur = cur->next) { |
| DBusMessageIter record_bytes; |
| sdp_record_t *rec = cur->data; |
| sdp_buf_t buf; |
| int result; |
| |
| result = sdp_gen_record_pdu(rec, &buf); |
| if (result) { |
| dbus_message_iter_abandon_container(&records_arr, |
| &record); |
| dbus_message_unref(reply); |
| return btd_error_failed( |
| msg, "Could not marshal service record"); |
| } |
| if (!dbus_message_iter_open_container(&record, DBUS_TYPE_ARRAY, |
| "y", &record_bytes)) { |
| bt_free(buf.data); |
| dbus_message_iter_abandon_container(&records_arr, |
| &record); |
| dbus_message_unref(reply); |
| return btd_error_failed( |
| msg, "Could not initialize iterator"); |
| } |
| if (!dbus_message_iter_append_fixed_array( |
| &record_bytes, DBUS_TYPE_BYTE, &buf.data, |
| buf.data_size)) { |
| bt_free(buf.data); |
| dbus_message_iter_abandon_container(&record, |
| &record_bytes); |
| dbus_message_iter_abandon_container(&records_arr, |
| &record); |
| dbus_message_unref(reply); |
| return btd_error_failed( |
| msg, "Could not append record data to reply"); |
| } |
| dbus_message_iter_close_container(&record, &record_bytes); |
| bt_free(buf.data); |
| } |
| |
| dbus_message_iter_close_container(&records_arr, &record); |
| |
| return reply; |
| } |
| |
| 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) }, |
| { GDBUS_EXPERIMENTAL_METHOD("GetServiceRecords", NULL, |
| GDBUS_ARGS({ "Records", "aay" }), |
| get_service_records) }, |
| { } |
| }; |
| |
| static const GDBusSignalTable device_signals[] = { |
| { GDBUS_SIGNAL("Disconnected", |
| GDBUS_ARGS({ "name", "s" }, { "message", "s" })) }, |
| { } |
| }; |
| |
| static gboolean |
| dev_property_get_prefer_bearer(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct btd_device *device = data; |
| const char *str = device_prefer_bearer_str(device); |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str); |
| |
| return TRUE; |
| } |
| |
| static void |
| dev_property_set_prefer_bearer(const GDBusPropertyTable *property, |
| DBusMessageIter *value, |
| GDBusPendingPropertySet id, void *data) |
| { |
| struct btd_device *device = data; |
| const char *str; |
| |
| 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, &str); |
| |
| /* Check if current preferred bearer is the same */ |
| if (!strcasecmp(device_prefer_bearer_str(device), str)) |
| goto done; |
| |
| if (!device_set_prefer_bearer_str(device, str)) { |
| g_dbus_pending_property_error(id, |
| ERROR_INTERFACE ".InvalidArguments", |
| "Invalid arguments in method call"); |
| return; |
| } |
| |
| switch (device->prefer_bearer) { |
| case PREFER_BREDR: |
| /* Remove device from auto-connect list so the kernel does not |
| * attempt to auto-connect to it in case it starts advertising. |
| */ |
| device_set_auto_connect(device, FALSE); |
| break; |
| |
| case PREFER_LE: |
| /* Add device to auto-connect list */ |
| device_set_auto_connect(device, TRUE); |
| break; |
| } |
| |
| store_device_info(device); |
| |
| g_dbus_emit_property_changed(dbus_conn, device->path, |
| DEVICE_INTERFACE, "PreferredBearer"); |
| |
| done: |
| g_dbus_pending_property_success(id); |
| } |
| |
| static gboolean |
| dev_property_prefer_bearer_exists(const GDBusPropertyTable *property, |
| void *data) |
| { |
| struct btd_device *device = data; |
| |
| return device_prefer_bearer_str(device) != NULL; |
| } |
| |
| static const GDBusPropertyTable device_properties[] = { |
| { "Address", "s", dev_property_get_address }, |
| { "AddressType", "s", property_get_address_type }, |
| { "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 }, |
| { "Bonded", "b", dev_property_get_bonded }, |
| { "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 }, |
| { "CablePairing", "b", dev_property_get_cable_pairing }, |
| { "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 }, |
| { "ManufacturerData", "a{qv}", dev_property_get_manufacturer_data, |
| NULL, dev_property_manufacturer_data_exist }, |
| { "ServiceData", "a{sv}", dev_property_get_service_data, |
| NULL, dev_property_service_data_exist }, |
| { "TxPower", "n", dev_property_get_tx_power, NULL, |
| dev_property_exists_tx_power }, |
| { "ServicesResolved", "b", dev_property_get_svc_resolved, NULL, NULL }, |
| { "AdvertisingFlags", "ay", dev_property_get_flags, NULL, |
| dev_property_flags_exist }, |
| { "AdvertisingData", "a{yv}", dev_property_get_advertising_data, |
| NULL, dev_property_advertising_data_exist }, |
| { "WakeAllowed", "b", dev_property_get_wake_allowed, |
| dev_property_set_wake_allowed, |
| dev_property_wake_allowed_exist }, |
| { "Sets", "a{oa{sv}}", dev_property_get_set, NULL, |
| dev_property_set_exists }, |
| { "PreferredBearer", "s", dev_property_get_prefer_bearer, |
| dev_property_set_prefer_bearer, |
| dev_property_prefer_bearer_exists, |
| G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, |
| { } |
| }; |
| |
| uint8_t btd_device_get_bdaddr_type(struct btd_device *dev) |
| { |
| return dev->bdaddr_type; |
| } |
| |
| bool btd_device_is_connected(struct btd_device *dev) |
| { |
| if (btd_device_bearer_is_connected(dev)) |
| return true; |
| |
| return find_service_with_state(dev->services, |
| BTD_SERVICE_STATE_CONNECTED); |
| } |
| |
| bool btd_device_bearer_is_connected(struct btd_device *dev) |
| { |
| return dev->bredr_state.connected || dev->le_state.connected; |
| } |
| |
| bool btd_device_bdaddr_type_connected(struct btd_device *dev, uint8_t type) |
| { |
| if (type == BDADDR_BREDR) |
| return dev->bredr_state.connected; |
| |
| return dev->le_state.connected; |
| } |
| |
| static void clear_temporary_timer(struct btd_device *dev) |
| { |
| if (dev->temporary_timer) { |
| timeout_remove(dev->temporary_timer); |
| dev->temporary_timer = 0; |
| } |
| } |
| |
| static void device_update_last_used(struct btd_device *device, |
| uint8_t bdaddr_type) |
| { |
| struct bearer_state *state; |
| |
| state = get_state(device, bdaddr_type); |
| state->last_used = time(NULL); |
| |
| if (device->prefer_bearer != PREFER_LAST_USED) |
| return; |
| |
| /* If current policy is to prefer last used bearer update the state. */ |
| state->prefer = true; |
| if (bdaddr_type == BDADDR_BREDR) { |
| if (device->le_state.prefer) { |
| device->le_state.prefer = false; |
| /* Remove device from auto-connect list so the kernel |
| * does not attempt to auto-connect to it in case it |
| * starts advertising. |
| */ |
| device_set_auto_connect(device, FALSE); |
| } |
| } else if (device->bredr_state.prefer) { |
| device->bredr_state.prefer = false; |
| /* Add device to auto-connect list */ |
| device_set_auto_connect(device, TRUE); |
| } |
| |
| store_device_info(device); |
| } |
| |
| void device_add_connection(struct btd_device *dev, uint8_t bdaddr_type, |
| uint32_t flags) |
| { |
| struct bearer_state *state = get_state(dev, bdaddr_type); |
| |
| if (dev->auth_retry_id) { |
| dev->auth_retry_id = 0; |
| timeout_remove(dev->auth_retry_id); |
| dev->auth_failures = 0; |
| } |
| |
| device_update_last_seen(dev, bdaddr_type, true); |
| device_update_last_used(dev, bdaddr_type); |
| |
| if (state->connected) { |
| char addr[18]; |
| ba2str(&dev->bdaddr, addr); |
| error("Device %s is already connected", addr); |
| return; |
| } |
| |
| bacpy(&dev->conn_bdaddr, &dev->bdaddr); |
| dev->conn_bdaddr_type = dev->bdaddr_type; |
| |
| /* If this is the first connection over this bearer */ |
| if (bdaddr_type == BDADDR_BREDR) { |
| device_set_bredr_support(dev); |
| btd_bearer_connected(dev->bredr, 0); |
| } else { |
| device_set_le_support(dev, bdaddr_type); |
| btd_bearer_connected(dev->le, 0); |
| } |
| |
| state->connected = true; |
| state->initiator = flags & BIT(3); |
| |
| if (dev->le_state.connected && dev->bredr_state.connected) |
| return; |
| |
| /* Remove temporary timer while connected */ |
| clear_temporary_timer(dev); |
| |
| g_dbus_emit_property_changed(dbus_conn, dev->path, DEVICE_INTERFACE, |
| "Connected"); |
| } |
| |
| static bool device_service_connected(struct btd_device *dev) |
| { |
| if (find_service_with_state(dev->services, |
| BTD_SERVICE_STATE_CONNECTING)) |
| return true; |
| |
| return find_service_with_state(dev->services, |
| BTD_SERVICE_STATE_CONNECTED); |
| } |
| |
| static bool device_disappeared(gpointer user_data) |
| { |
| struct btd_device *dev = user_data; |
| |
| /* If there are services connected restart the timer to give more time |
| * for the service to either complete the connection or disconnect. |
| */ |
| if (device_service_connected(dev)) |
| return TRUE; |
| |
| dev->temporary_timer = 0; |
| |
| btd_adapter_remove_device(dev->adapter, dev); |
| |
| return FALSE; |
| } |
| |
| static void set_temporary_timer(struct btd_device *dev, unsigned int timeout) |
| { |
| clear_temporary_timer(dev); |
| |
| if (!timeout) |
| return; |
| |
| dev->temporary_timer = timeout_add_seconds(timeout, device_disappeared, |
| dev, NULL); |
| } |
| |
| static void device_disconnected(struct btd_device *device, uint8_t reason) |
| { |
| const char *name; |
| const char *message; |
| |
| switch (reason) { |
| case MGMT_DEV_DISCONN_UNKNOWN: |
| name = "org.bluez.Reason.Unknown"; |
| message = "Unspecified"; |
| break; |
| case MGMT_DEV_DISCONN_TIMEOUT: |
| name = "org.bluez.Reason.Timeout"; |
| message = "Connection timeout"; |
| break; |
| case MGMT_DEV_DISCONN_LOCAL_HOST: |
| name = "org.bluez.Reason.Local"; |
| message = "Connection terminated by local host"; |
| break; |
| case MGMT_DEV_DISCONN_REMOTE: |
| name = "org.bluez.Reason.Remote"; |
| message = "Connection terminated by remote user"; |
| break; |
| case MGMT_DEV_DISCONN_AUTH_FAILURE: |
| name = "org.bluez.Reason.Authentication"; |
| message = "Connection terminated due to authentication failure"; |
| break; |
| case MGMT_DEV_DISCONN_LOCAL_HOST_SUSPEND: |
| name = "org.bluez.Reason.Suspend"; |
| message = "Connection terminated by local host for suspend"; |
| break; |
| default: |
| warn("Unknown disconnection value: %u", reason); |
| name = "org.bluez.Reason.Unknown"; |
| message = "Unspecified"; |
| } |
| |
| g_dbus_emit_signal(dbus_conn, device->path, DEVICE_INTERFACE, |
| "Disconnected", |
| DBUS_TYPE_STRING, &name, |
| DBUS_TYPE_STRING, &message, |
| DBUS_TYPE_INVALID); |
| } |
| |
| static bool device_auth_failure_retry(gpointer user_data) |
| { |
| struct btd_device *device = user_data; |
| |
| device_set_auto_connect(device, TRUE); |
| |
| return false; |
| } |
| |
| void device_remove_connection(struct btd_device *device, uint8_t bdaddr_type, |
| bool *remove, |
| uint8_t reason) |
| { |
| struct bearer_state *state = get_state(device, bdaddr_type); |
| DBusMessage *reply; |
| bool remove_device = false; |
| bool paired_status_updated = false; |
| |
| if (!state->connected) |
| return; |
| |
| state->connected = false; |
| state->initiator = false; |
| device->general_connect = FALSE; |
| |
| device_set_svc_refreshed(device, false); |
| |
| if (device->disconn_timer > 0) { |
| timeout_remove(device->disconn_timer); |
| device->disconn_timer = 0; |
| } |
| |
| /* This could be executed while the client is waiting for Connect() but |
| * att_connect_cb has not been invoked. |
| * In that case reply the client that the connection failed. |
| */ |
| if (device->connect) { |
| DBG("connection removed while Connect() is waiting reply"); |
| reply = btd_error_failed(device->connect, |
| ERR_BREDR_CONN_CANCELED); |
| g_dbus_send_message(dbus_conn, reply); |
| dbus_message_unref(device->connect); |
| device->connect = NULL; |
| } |
| |
| /* Update bearer interface */ |
| if (bdaddr_type == BDADDR_BREDR) |
| btd_bearer_disconnected(device->bredr, reason); |
| else |
| btd_bearer_disconnected(device->le, reason); |
| |
| /* Check paired status of both bearers since it's possible to be |
| * paired but not connected via link key to LTK conversion. |
| */ |
| if (!device->bredr_state.connected && device->bredr_state.paired && |
| !device->bredr_state.bonded) { |
| btd_adapter_remove_bonding(device->adapter, |
| &device->bdaddr, |
| BDADDR_BREDR); |
| device->bredr_state.paired = false; |
| paired_status_updated = true; |
| btd_bearer_paired(device->bredr); |
| } |
| |
| if (!device->le_state.connected && device->le_state.paired && |
| !device->le_state.bonded) { |
| btd_adapter_remove_bonding(device->adapter, |
| &device->bdaddr, |
| device->bdaddr_type); |
| device->le_state.paired = false; |
| paired_status_updated = true; |
| btd_bearer_paired(device->le); |
| } |
| |
| /* report change only if both bearers are unpaired */ |
| if (!device->bredr_state.paired && !device->le_state.paired && |
| paired_status_updated) |
| g_dbus_emit_property_changed(dbus_conn, device->path, |
| DEVICE_INTERFACE, |
| "Paired"); |
| |
| if (device->bredr_state.connected || device->le_state.connected) |
| return; |
| |
| device_update_last_seen(device, bdaddr_type, true); |
| |
| g_slist_free_full(device->eir_uuids, g_free); |
| device->eir_uuids = NULL; |
| |
| /* |
| * Check if device is marked for auto_connect before attempting to limit |
| * the number of retries. |
| */ |
| if (device->auto_connect && reason == MGMT_DEV_DISCONN_AUTH_FAILURE) { |
| /* |
| * In case of an auth failure, implement an |
| * exponential backoff retry logic. We disable auto |
| * connect immediately to prevent us from retrying to |
| * connect to this device again and start an 1, 2, 4 |
| * second timers to re-enable that knob in hopes that |
| * subsequent retires will be more successul |
| */ |
| device_set_auto_connect(device, FALSE); |
| if (device->auth_failures < AUTH_FAILURES_THRESHOLD) { |
| uint8_t timer = 1 << device->auth_failures; |
| |
| device->auth_retry_id = timeout_add_seconds(timer, |
| device_auth_failure_retry, |
| device, NULL); |
| DBG("Auth failure, retrying in %d seconds", timer); |
| device->auth_failures++; |
| } else { |
| DBG("Disabling auto connect due to too many auth " |
| "failures"); |
| device->auth_failures = 0; |
| } |
| } |
| |
| device_disconnected(device, reason); |
| |
| g_dbus_emit_property_changed(dbus_conn, device->path, |
| DEVICE_INTERFACE, "Connected"); |
| |
| /* remove device only if both bearers are disconnected */ |
| while (device->disconnects) { |
| DBusMessage *msg = device->disconnects->data; |
| |
| if (dbus_message_is_method_call(msg, ADAPTER_INTERFACE, |
| "RemoveDevice")) |
| remove_device = true; |
| |
| g_dbus_send_reply(dbus_conn, msg, DBUS_TYPE_INVALID); |
| device->disconnects = g_slist_remove(device->disconnects, msg); |
| dbus_message_unref(msg); |
| } |
| |
| if (remove_device) |
| *remove = remove_device; |
| } |
| |
| 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]; |
| GKeyFile *key_file; |
| char *str = NULL; |
| int len; |
| |
| if (device_address_is_private(device)) |
| return NULL; |
| |
| create_filename(filename, PATH_MAX, "/%s/cache/%s", local, peer); |
| |
| 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_cached_name_resolve(struct btd_device *device, |
| const char *local, const char *peer) |
| { |
| char filename[PATH_MAX]; |
| GKeyFile *key_file; |
| uint64_t failed_time; |
| |
| if (device_address_is_private(device)) |
| return; |
| |
| create_filename(filename, PATH_MAX, "/%s/cache/%s", local, peer); |
| |
| key_file = g_key_file_new(); |
| |
| if (!g_key_file_load_from_file(key_file, filename, 0, NULL)) |
| goto failed; |
| |
| failed_time = g_key_file_get_uint64(key_file, "NameResolving", |
| "FailedTime", NULL); |
| |
| device->name_resolve_failed_time = failed_time; |
| |
| failed: |
| g_key_file_free(key_file); |
| } |
| |
| static struct csrk_info *load_csrk(GKeyFile *key_file, const char *group) |
| { |
| struct csrk_info *csrk; |
| char *str; |
| int i; |
| |
| str = g_key_file_get_string(key_file, group, "Key", NULL); |
| if (!str) |
| return NULL; |
| |
| csrk = g_new0(struct csrk_info, 1); |
| |
| for (i = 0; i < 16; i++) { |
| if (sscanf(str + (i * 2), "%2hhx", &csrk->key[i]) != 1) |
| goto fail; |
| } |
| |
| /* |
| * In case of older storage this will return 0 which is fine since it |
| * didn't support signing at that point the counter should never have |
| * been used. |
| */ |
| csrk->counter = g_key_file_get_integer(key_file, group, "Counter", |
| NULL); |
| g_free(str); |
| |
| return csrk; |
| |
| fail: |
| g_free(str); |
| g_free(csrk); |
| return NULL; |
| } |
| |
| static struct sirk_info *load_sirk(GKeyFile *key_file, uint8_t index) |
| { |
| char group[28]; |
| struct sirk_info *sirk; |
| char *str; |
| int i; |
| |
| sprintf(group, "SetIdentityResolvingKey#%u", index); |
| |
| str = g_key_file_get_string(key_file, group, "Key", NULL); |
| if (!str) |
| return NULL; |
| |
| sirk = g_new0(struct sirk_info, 1); |
| |
| for (i = 0; i < 16; i++) { |
| if (sscanf(str + (i * 2), "%2hhx", &sirk->key[i]) != 1) |
| goto fail; |
| } |
| |
| |
| sirk->encrypted = g_key_file_get_boolean(key_file, group, "Encrypted", |
| NULL); |
| sirk->size = g_key_file_get_integer(key_file, group, "Size", NULL); |
| sirk->rank = g_key_file_get_integer(key_file, group, "Rank", NULL); |
| g_free(str); |
| |
| return sirk; |
| |
| fail: |
| g_free(str); |
| g_free(sirk); |
| return NULL; |
| } |
| |
| static void load_sirks(struct btd_device *device, GKeyFile *key_file) |
| { |
| struct sirk_info *sirk; |
| uint8_t i; |
| |
| for (i = 0; i < UINT8_MAX; i++) { |
| sirk = load_sirk(key_file, i); |
| if (!sirk) |
| break; |
| |
| queue_push_tail(device->sirks, sirk); |
| |
| /* Only add DeviceSet object if sirk does need |
| * decryption otherwise it has to wait for the LTK in |
| * order to decrypt. |
| */ |
| if (!sirk->encrypted) |
| btd_set_add_device(device, NULL, sirk->key, |
| sirk->size); |
| } |
| } |
| |
| static void load_services(struct btd_device *device, char **uuids) |
| { |
| char **uuid; |
| |
| for (uuid = uuids; *uuid; uuid++) { |
| if (g_slist_find_custom(device->uuids, *uuid, bt_uuid_strcmp)) |
| continue; |
| |
| device->uuids = g_slist_insert_sorted(device->uuids, |
| g_strdup(*uuid), |
| bt_uuid_strcmp); |
| } |
| |
| g_strfreev(uuids); |
| } |
| |
| static void convert_info(struct btd_device *device, GKeyFile *key_file) |
| { |
| char filename[PATH_MAX]; |
| char adapter_addr[18]; |
| char device_addr[18]; |
| char **uuids; |
| char *str; |
| gsize length = 0; |
| GError *gerr = NULL; |
| |
| /* Load device profile list from legacy properties */ |
| uuids = g_key_file_get_string_list(key_file, "General", "SDPServices", |
| NULL, NULL); |
| if (uuids) |
| load_services(device, uuids); |
| |
| uuids = g_key_file_get_string_list(key_file, "General", "GATTServices", |
| NULL, NULL); |
| if (uuids) |
| load_services(device, uuids); |
| |
| if (!device->uuids) |
| return; |
| |
| /* Remove old entries so they are not loaded again */ |
| g_key_file_remove_key(key_file, "General", "SDPServices", NULL); |
| g_key_file_remove_key(key_file, "General", "GATTServices", NULL); |
| |
| ba2str(btd_adapter_get_address(device->adapter), adapter_addr); |
| ba2str(&device->bdaddr, device_addr); |
| create_filename(filename, PATH_MAX, "/%s/%s/info", adapter_addr, |
| device_addr); |
| |
| str = g_key_file_to_data(key_file, &length, NULL); |
| if (!g_file_set_contents(filename, str, length, &gerr)) { |
| error("Unable set contents for %s: (%s)", filename, |
| gerr->message); |
| g_error_free(gerr); |
| } |
| g_free(str); |
| |
| store_device_info(device); |
| } |
| |
| static void load_info(struct btd_device *device, const char *local, |
| const char *peer, GKeyFile *key_file) |
| { |
| GError *gerr = NULL; |
| char *str; |
| gboolean store_needed = FALSE; |
| gboolean blocked; |
| gboolean wake_allowed; |
| char **uuids; |
| int source, vendor, product, version; |
| char **techno, **t; |
| |
| /* 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")) |
| device->bredr = btd_bearer_new(device, BDADDR_BREDR); |
| else if (g_str_equal(*t, "LE")) |
| device->le = btd_bearer_new(device, BDADDR_LE_PUBLIC); |
| else |
| error("Unknown device technology"); |
| } |
| |
| if (!device->le) { |
| device->bdaddr_type = BDADDR_BREDR; |
| } else { |
| 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); |
| |
| device->local_csrk = load_csrk(key_file, "LocalSignatureKey"); |
| device->remote_csrk = load_csrk(key_file, "RemoteSignatureKey"); |
| |
| load_sirks(device, key_file); |
| } |
| |
| g_strfreev(techno); |
| |
| /* Load preferred bearer */ |
| str = g_key_file_get_string(key_file, "General", "PreferredBearer", |
| NULL); |
| if (str) { |
| device_set_prefer_bearer_str(device, str); |
| g_free(str); |
| |
| /* Load last used bearer */ |
| str = g_key_file_get_string(key_file, "General", |
| "LastUsedBearer", NULL); |
| if (str) { |
| device_update_last_used(device, !strcmp(str, "le") ? |
| device->bdaddr_type : |
| BDADDR_BREDR); |
| g_free(str); |
| } |
| } |
| |
| 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); |
| |
| device->cable_pairing = g_key_file_get_boolean(key_file, "General", |
| "CablePairing", NULL); |
| |
| /* Load device profile list */ |
| uuids = g_key_file_get_string_list(key_file, "General", "Services", |
| NULL, NULL); |
| if (uuids) { |
| char filename[PATH_MAX]; |
| char device_addr[18]; |
| struct stat st; |
| GKeyFile *key_file = g_key_file_new(); |
| GError *gerr = NULL; |
| |
| load_services(device, uuids); |
| |
| ba2str(&device->bdaddr, device_addr); |
| create_filename(filename, PATH_MAX, "/%s/cache/%s", |
| btd_adapter_get_storage_dir(device->adapter), |
| device_addr); |
| |
| /* Check if ServiceRecords cached group exists */ |
| if (stat(filename, &st) < 0) { |
| DBG("Missing cache file for ServiceRecords"); |
| device->bredr_state.svc_resolved = false; |
| } else if (!g_key_file_load_from_file(key_file, filename, |
| 0, &gerr)) { |
| DBG("Unable to load key file from %s: (%s)", filename, |
| gerr->message); |
| g_clear_error(&gerr); |
| device->bredr_state.svc_resolved = false; |
| } else if (!g_key_file_has_group(key_file, "ServiceRecords")) { |
| DBG("Missing ServiceRecords from cache file"); |
| device->bredr_state.svc_resolved = false; |
| } else { |
| /* Discovered services restored from storage */ |
| device->bredr_state.svc_resolved = true; |
| } |
| g_key_file_free(key_file); |
| } |
| |
| /* 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); |
| } |
| |
| /* Wake allowed is only configured and stored if user changed it. |
| * Otherwise, we enable if profile supports it. |
| */ |
| wake_allowed = g_key_file_get_boolean(key_file, "General", |
| "WakeAllowed", &gerr); |
| if (!gerr) { |
| device_set_wake_override(device, wake_allowed); |
| } else { |
| g_error_free(gerr); |
| gerr = NULL; |
| } |
| |
| 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]; |
| struct stat st; |
| GKeyFile *key_file; |
| GError *gerr = NULL; |
| char *prim_uuid, *str; |
| char **groups, **handle, *service_uuid; |
| struct gatt_primary *prim; |
| uuid_t uuid; |
| char tmp[3]; |
| int i; |
| |
| create_filename(filename, PATH_MAX, "/%s/%s/attributes", local, peer); |
| |
| /* Check if attributes file exists */ |
| if (stat(filename, &st) < 0) |
| return; |
| |
| key_file = g_key_file_new(); |
| if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { |
| error("Unable to load key file from %s: (%s)", filename, |
| gerr->message); |
| g_clear_error(&gerr); |
| } |
| groups = g_key_file_get_groups(key_file, NULL); |
| |
| sdp_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); |
| prim_uuid = bt_uuid2string(&uuid); |
| |
| 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); |
| free(service_uuid); |
| g_free(str); |
| |
| device->primaries = g_slist_append(device->primaries, prim); |
| } |
| |
| g_strfreev(groups); |
| g_key_file_free(key_file); |
| free(prim_uuid); |
| } |
| |
| 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 add_primary(struct gatt_db_attribute *attr, void *user_data) |
| { |
| GSList **new_services = user_data; |
| struct gatt_primary *prim; |
| bt_uuid_t uuid; |
| |
| prim = g_new0(struct gatt_primary, 1); |
| if (!prim) { |
| DBG("Failed to allocate gatt_primary structure"); |
| return; |
| } |
| |
| gatt_db_attribute_get_service_handles(attr, &prim->range.start, |
| &prim->range.end); |
| gatt_db_attribute_get_service_uuid(attr, &uuid); |
| bt_uuid_to_string(&uuid, prim->uuid, sizeof(prim->uuid)); |
| |
| *new_services = g_slist_append(*new_services, prim); |
| } |
| |
| static void load_gatt_db(struct btd_device *device, const char *local, |
| const char *peer) |
| { |
| char filename[PATH_MAX]; |
| int err; |
| |
| if (!gatt_cache_is_enabled(device)) |
| return; |
| |
| DBG("Restoring %s gatt database from file", peer); |
| |
| create_filename(filename, PATH_MAX, "/%s/cache/%s", local, peer); |
| |
| err = btd_settings_gatt_db_load(device->db, filename); |
| if (err < 0) { |
| if (err == -ENOENT) |
| return; |
| |
| warn("Error loading db from cache for %s: %s (%d)", peer, |
| strerror(-err), err); |
| } |
| |
| g_slist_free_full(device->primaries, g_free); |
| device->primaries = NULL; |
| gatt_db_foreach_service(device->db, NULL, add_primary, |
| &device->primaries); |
| } |
| |
| static void device_add_uuids(struct btd_device *device, GSList *uuids) |
| { |
| GSList *l; |
| bool changed = false; |
| |
| 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; |
| |
| changed = true; |
| device->uuids = g_slist_insert_sorted(device->uuids, |
| g_strdup(l->data), |
| bt_uuid_strcmp); |
| } |
| |
| if (changed) |
| g_dbus_emit_property_changed(dbus_conn, device->path, |
| DEVICE_INTERFACE, "UUIDs"); |
| } |
| |
| static bool device_match_profile(struct btd_device *device, |
| struct btd_profile *profile, |
| GSList *uuids) |
| { |
| GSList *l; |
| |
| if (profile->remote_uuid == NULL) |
| return false; |
| |
| l = g_slist_find_custom(uuids, profile->remote_uuid, bt_uuid_strcmp); |
| if (!l) |
| return false; |
| |
| return true; |
| } |
| |
| static void add_gatt_service(struct gatt_db_attribute *attr, void *user_data) |
| { |
| struct btd_device *device = user_data; |
| struct btd_service *service; |
| struct btd_profile *profile; |
| bt_uuid_t uuid; |
| char uuid_str[MAX_LEN_UUID_STR]; |
| GSList *l; |
| |
| gatt_db_attribute_get_service_uuid(attr, &uuid); |
| bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); |
| |
| /* Check if service was already probed */ |
| l = find_service_with_uuid(device->services, uuid_str); |
| if (l) |
| goto done; |
| |
| /* Add UUID and probe service */ |
| btd_device_add_uuid(device, uuid_str); |
| |
| /* Check if service was probed */ |
| l = find_service_with_uuid(device->services, uuid_str); |
| if (!l) |
| return; |
| |
| done: |
| /* Mark service as active to skip discovering it again */ |
| gatt_db_service_set_active(attr, true); |
| |
| service = l->data; |
| profile = btd_service_get_profile(service); |
| |
| /* Claim attributes of internal profiles */ |
| if (!profile->external) { |
| /* Mark the service as claimed by the existing profile. */ |
| gatt_db_service_set_claimed(attr, true); |
| } |
| |
| /* Notify driver about the new connection */ |
| service_accept(service, btd_device_is_initiator(device)); |
| } |
| |
| |
| static const struct btd_profile *get_gatt_profile(void *data, void *user_data) |
| { |
| struct gatt_db_attribute *attr = data; |
| struct btd_profile *profile; |
| bt_uuid_t uuid; |
| struct btd_profile *dummy_profile = user_data; |
| char *uuid_str = (char *)dummy_profile->remote_uuid; |
| |
| gatt_db_attribute_get_service_uuid(attr, &uuid); |
| bt_uuid_to_string(&uuid, uuid_str, MAX_LEN_UUID_STR); |
| |
| profile = btd_profile_find_remote_uuid(uuid_str); |
| if (!profile) |
| profile = dummy_profile; |
| |
| return profile; |
| } |
| |
| static void get_gatt_attrs(struct gatt_db_attribute *attr, |
| void *user_data) |
| { |
| GSList **list = user_data; |
| |
| *list = g_slist_append(*list, attr); |
| } |
| |
| static void device_add_gatt_services(struct btd_device *device) |
| { |
| char addr[18]; |
| GSList *attrs = NULL; |
| GSList *entry; |
| char uuid_str[MAX_LEN_UUID_STR]; |
| struct btd_profile dummy_profile = { |
| .remote_uuid = uuid_str, |
| }; |
| |
| ba2str(&device->bdaddr, addr); |
| |
| if (device->blocked) { |
| DBG("Skipping profiles for blocked device %s", addr); |
| return; |
| } |
| |
| /* Probe and accept in profile priority order */ |
| gatt_db_foreach_service(device->db, NULL, get_gatt_attrs, &attrs); |
| |
| attrs = btd_profile_sort_list(attrs, get_gatt_profile, |
| &dummy_profile); |
| |
| for (entry = attrs; entry; entry = g_slist_next(entry)) |
| add_gatt_service(entry->data, device); |
| |
| g_slist_free(attrs); |
| } |
| |
| static void device_accept_gatt_profiles(struct btd_device *device) |
| { |
| GSList *l; |
| bool initiator = btd_device_is_initiator(device); |
| GSList *services; |
| |
| DBG("initiator %s", initiator ? "true" : "false"); |
| |
| /* Accept in profile priority order */ |
| services = g_slist_copy(device->services); |
| services = btd_profile_sort_list(services, get_service_profile, NULL); |
| |
| for (l = services; l != NULL; l = g_slist_next(l)) |
| service_accept(l->data, initiator); |
| |
| g_slist_free(services); |
| } |
| |
| static void device_remove_gatt_service(struct btd_device *device, |
| struct gatt_db_attribute *attr) |
| { |
| struct btd_service *service; |
| bt_uuid_t uuid; |
| char uuid_str[MAX_LEN_UUID_STR]; |
| GSList *l; |
| |
| gatt_db_attribute_get_service_uuid(attr, &uuid); |
| bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); |
| |
| l = find_service_with_uuid(device->services, uuid_str); |
| if (!l) |
| return; |
| |
| service = l->data; |
| device->services = g_slist_delete_link(device->services, l); |
| device->pending = g_slist_remove(device->pending, service); |
| service_remove(service); |
| } |
| |
| static gboolean gatt_services_changed(gpointer user_data) |
| { |
| struct btd_device *device = user_data; |
| |
| store_gatt_db(device); |
| |
| return FALSE; |
| } |
| |
| static void gatt_service_added(struct gatt_db_attribute *attr, void *user_data) |
| { |
| struct btd_device *device = user_data; |
| GSList *new_service = NULL; |
| uint16_t start, end; |
| |
| if (!bt_gatt_client_is_ready(device->client)) |
| return; |
| |
| gatt_db_attribute_get_service_data(attr, &start, &end, NULL, NULL); |
| |
| DBG("start: 0x%04x, end: 0x%04x", start, end); |
| |
| /* |
| * TODO: Remove the primaries list entirely once all profiles use |
| * shared/gatt. |
| */ |
| add_primary(attr, &new_service); |
| if (!new_service) |
| return; |
| |
| device_register_primaries(device, new_service, -1); |
| |
| add_gatt_service(attr, device); |
| |
| btd_gatt_client_service_added(device->client_dbus, attr); |
| |
| gatt_services_changed(device); |
| } |
| |
| static gint prim_attr_cmp(gconstpointer a, gconstpointer b) |
| { |
| const struct gatt_primary *prim = a; |
| const struct gatt_db_attribute *attr = b; |
| uint16_t start, end; |
| |
| gatt_db_attribute_get_service_handles(attr, &start, &end); |
| |
| return !(prim->range.start == start && prim->range.end == end); |
| } |
| |
| static gint prim_uuid_cmp(gconstpointer a, gconstpointer b) |
| { |
| const struct gatt_primary *prim = a; |
| const char *uuid = b; |
| |
| return bt_uuid_strcmp(prim->uuid, uuid); |
| } |
| |
| static void gatt_service_removed(struct gatt_db_attribute *attr, |
| void *user_data) |
| { |
| struct btd_device *device = user_data; |
| GSList *l; |
| struct gatt_primary *prim; |
| uint16_t start, end; |
| |
| /* |
| * NOTE: shared/gatt-client clears the database in case of failure. This |
| * triggers the service_removed callback for all affected services. |
| * Hence, this function will be called in the following cases: |
| * |
| * 1. When a GATT service gets removed due to "Service Changed". |
| * |
| * 2. When a GATT service gets removed when the database get cleared |
| * upon disconnection with a non-bonded device. |
| * |
| * 3. When a GATT service gets removed when the database get cleared |
| * by shared/gatt-client when its initialization procedure fails, |
| * e.g. due to an ATT protocol error or an unexpected disconnect. |
| * In this case the gatt-client will not be ready. |
| */ |
| |
| gatt_db_attribute_get_service_handles(attr, &start, &end); |
| |
| DBG("start: 0x%04x, end: 0x%04x", start, end); |
| |
| /* Remove the corresponding gatt_primary */ |
| l = g_slist_find_custom(device->primaries, attr, prim_attr_cmp); |
| if (!l) |
| return; |
| |
| prim = l->data; |
| device->primaries = g_slist_delete_link(device->primaries, l); |
| |
| /* |
| * Remove the corresponding UUIDs entry and profile, only if this is |
| * the last service with this UUID. |
| */ |
| l = g_slist_find_custom(device->uuids, prim->uuid, bt_uuid_strcmp); |
| |
| if (l && !g_slist_find_custom(device->primaries, prim->uuid, |
| prim_uuid_cmp)) { |
| /* |
| * If this happened since the db was cleared for a non-bonded |
| * device, then don't remove the btd_service just yet. We do |
| * this so that we can avoid re-probing the profile if the same |
| * GATT service is found on the device on re-connection. |
| * However, if the device is marked as temporary, then we |
| * remove it anyway. |
| */ |
| if (device->client || device->temporary == TRUE) |
| device_remove_gatt_service(device, attr); |
| |
| g_free(l->data); |
| device->uuids = g_slist_delete_link(device->uuids, l); |
| g_dbus_emit_property_changed(dbus_conn, device->path, |
| DEVICE_INTERFACE, "UUIDs"); |
| } |
| |
| g_free(prim); |
| |
| store_device_info(device); |
| |
| btd_gatt_client_service_removed(device->client_dbus, attr); |
| |
| gatt_services_changed(device); |
| } |
| |
| 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; |
| |
| device->tx_power = 127; |
| device->volume = -1; |
| device->wake_id = -1U; |
| |
| device->db = gatt_db_new(); |
| if (!device->db) { |
| g_free(device); |
| return NULL; |
| } |
| |
| memset(device->ad_flags, INVALID_FLAGS, sizeof(device->ad_flags)); |
| |
| device->ad = bt_ad_new(); |
| if (!device->ad) { |
| device_free(device); |
| 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); |
| |
| str2ba(address, &device->bdaddr); |
| |
| device->client_dbus = btd_gatt_client_new(device); |
| if (!device->client_dbus) { |
| error("Failed to create btd_gatt_client"); |
| device_free(device); |
| return NULL; |
| } |
| |
| DBG("Creating device %s", device->path); |
| |
| if (g_dbus_register_interface(dbus_conn, |
| device->path, DEVICE_INTERFACE, |
| device_methods, device_signals, |
| device_properties, device, |
| device_free) == FALSE) { |
| error("Unable to register device interface for %s", address); |
| device_free(device); |
| return NULL; |
| } |
| |
| device->adapter = adapter; |
| device->sirks = queue_new(); |
| device->temporary = true; |
| |
| device->db_id = gatt_db_register(device->db, gatt_service_added, |
| gatt_service_removed, device, NULL); |
| |
| device->refresh_discovery = btd_opts.refresh_discovery; |
| |
| 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 char *src_dir; |
| |
| DBG("address %s", address); |
| |
| device = device_new(adapter, address); |
| if (device == NULL) |
| return NULL; |
| |
| convert_info(device, key_file); |
| |
| src_dir = btd_adapter_get_storage_dir(adapter); |
| |
| load_info(device, src_dir, address, key_file); |
| load_att_info(device, src_dir, address); |
| |
| return device; |
| } |
| |
| struct btd_device *device_create(struct btd_adapter *adapter, |
| const bdaddr_t *bdaddr, uint8_t bdaddr_type) |
| { |
| struct btd_device *device; |
| char dst[18]; |
| char *str; |
| const char *storage_dir; |
| |
| ba2str(bdaddr, dst); |
| DBG("dst %s", dst); |
| |
| device = device_new(adapter, dst); |
| if (device == NULL) |
| return NULL; |
| |
| device->bdaddr_type = bdaddr_type; |
| |
| if (bdaddr_type == BDADDR_BREDR) |
| device->bredr = btd_bearer_new(device, BDADDR_BREDR); |
| else |
| device->le = btd_bearer_new(device, BDADDR_LE_PUBLIC); |
| |
| storage_dir = btd_adapter_get_storage_dir(adapter); |
| str = load_cached_name(device, storage_dir, dst); |
| if (str) { |
| strcpy(device->name, str); |
| g_free(str); |
| } |
| |
| load_cached_name_resolve(device, storage_dir, dst); |
| |
| return device; |
| } |
| |
| char *btd_device_get_storage_path(struct btd_device *device, const char *name) |
| { |
| char filename[PATH_MAX]; |
| char dstaddr[18]; |
| |
| if (device_address_is_private(device)) { |
| warn("Refusing storage path for private addressed device %s", |
| device->path); |
| return NULL; |
| } |
| |
| ba2str(&device->bdaddr, dstaddr); |
| |
| if (!name) |
| create_filename(filename, PATH_MAX, "/%s/%s", |
| btd_adapter_get_storage_dir(device->adapter), |
| dstaddr); |
| else |
| create_filename(filename, PATH_MAX, "/%s/%s/%s", |
| btd_adapter_get_storage_dir(device->adapter), |
| dstaddr, name); |
| |
| return strdup(filename); |
| } |
| |
| void btd_device_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) |
| { |
| if (name != NULL && len > 0) { |
| strncpy(name, device->name, len - 1); |
| name[len - 1] = '\0'; |
| } |
| } |
| |
| bool device_name_known(struct btd_device *device) |
| { |
| return device->name[0] != '\0'; |
| } |
| |
| bool device_is_name_resolve_allowed(struct btd_device *device) |
| { |
| struct timespec now; |
| |
| if (!device) |
| return false; |
| |
| clock_gettime(CLOCK_MONOTONIC, &now); |
| |
| /* failed_time is empty (0), meaning no prior failure. */ |
| if (device->name_resolve_failed_time == 0) |
| return true; |
| |
| /* now < failed_time, meaning the clock has somehow turned back, |
| * possibly because of system restart. Allow just to be safe. |
| */ |
| if (now.tv_sec < device->name_resolve_failed_time) |
| return true; |
| |
| /* now >= failed_time + name_request_retry_delay, meaning the |
| * period of not sending name request is over. |
| */ |
| if (now.tv_sec >= (time_t)(device->name_resolve_failed_time + |
| btd_opts.name_request_retry_delay)) |
| return true; |
| |
| return false; |
| } |
| |
| void device_name_resolve_fail(struct btd_device *device) |
| { |
| struct timespec now; |
| |
| if (!device) |
| return; |
| |
| clock_gettime(CLOCK_MONOTONIC, &now); |
| device->name_resolve_failed_time = now.tv_sec; |
| device_store_cached_name_resolve(device); |
| } |
| |
| 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"); |
| g_dbus_emit_property_changed(dbus_conn, device->path, |
| DEVICE_INTERFACE, "Icon"); |
| } |
| |
| void device_set_privacy(struct btd_device *device, bool value, |
| const uint8_t *irk) |
| { |
| device->privacy = value; |
| |
| free(device->irk); |
| |
| if (irk) |
| device->irk = util_memdup(irk, 16); |
| else |
| device->irk = NULL; |
| } |
| |
| bool device_get_privacy(struct btd_device *device) |
| { |
| if (device->privacy) |
| return true; |
| |
| return device_address_is_private(device); |
| } |
| |
| void device_update_addr(struct btd_device *device, const bdaddr_t *bdaddr, |
| uint8_t bdaddr_type, const uint8_t *irk) |
| { |
| bool auto_connect = device->auto_connect; |
| |
| device_set_privacy(device, true, irk); |
| |
| if (!bacmp(bdaddr, &device->bdaddr) && |
| bdaddr_type == device->bdaddr_type) |
| return; |
| |
| /* Since this function is only used for LE SMP Identity |
| * Resolving purposes we can now assume LE is supported. |
| */ |
| if (!device->le) |
| device->le = btd_bearer_new(device, BDADDR_LE_PUBLIC); |
| |
| /* Remove old address from accept/auto-connect list since its address |
| * will be changed. |
| */ |
| if (auto_connect) |
| device_set_auto_connect(device, FALSE); |
| |
| bacpy(&device->bdaddr, bdaddr); |
| device->bdaddr_type = bdaddr_type; |
| |
| if (device->temporary) |
| btd_device_set_temporary(device, false); |
| else |
| store_device_info(device); |
| |
| g_dbus_emit_property_changed(dbus_conn, device->path, |
| DEVICE_INTERFACE, "Address"); |
| g_dbus_emit_property_changed(dbus_conn, device->path, |
| DEVICE_INTERFACE, "AddressType"); |
| |
| if (auto_connect) |
| device_set_auto_connect(device, TRUE); |
| } |
| |
| void device_set_bredr_support(struct btd_device *device) |
| { |
| if (btd_opts.mode == BT_MODE_LE || device->bredr) |
| return; |
| |
| if (!device->bredr) |
| device->bredr = btd_bearer_new(device, BDADDR_BREDR); |
| |
| if (device->le) |
| g_dbus_emit_property_changed(dbus_conn, device->path, |
| DEVICE_INTERFACE, "PreferredBearer"); |
| |
| store_device_info(device); |
| } |
| |
| void device_set_le_support(struct btd_device *device, uint8_t bdaddr_type) |
| { |
| if (btd_opts.mode == BT_MODE_BREDR || device->le) |
| return; |
| |
| if (!device->le) |
| device->le = btd_bearer_new(device, BDADDR_LE_PUBLIC); |
| |
| device->bdaddr_type = bdaddr_type; |
| |
| g_dbus_emit_property_changed(dbus_conn, device->path, |
| DEVICE_INTERFACE, "AddressType"); |
| |
| if (device->bredr) |
| g_dbus_emit_property_changed(dbus_conn, device->path, |
| DEVICE_INTERFACE, "PreferredBearer"); |
| |
| store_device_info(device); |
| } |
| |
| void device_update_last_seen(struct btd_device *device, uint8_t bdaddr_type, |
| bool connectable) |
| { |
| struct bearer_state *state; |
| |
| state = get_state(device, bdaddr_type); |
| |
| state->last_seen = time(NULL); |
| state->connectable = connectable; |
| |
| if (!device_is_temporary(device)) |
| return; |
| |
| /* Restart temporary timer */ |
| set_temporary_timer(device, btd_opts.tmpto); |
| } |
| |
| void btd_device_set_connectable(struct btd_device *device, bool connectable) |
| { |
| device_update_last_seen(device, device->bdaddr_type, connectable); |
| } |
| |
| /* It is possible that we have two device objects for the same device in |
| * case it has first been discovered over BR/EDR and has a private |
| * address when discovered over LE for the first time. In such a case we |
| * need to inherit critical values from the duplicate so that we don't |
| * overwrite them when writing to storage. The next time bluetoothd |
| * starts the device will show up as a single instance. |
| */ |
| void device_merge_duplicate(struct btd_device *dev, struct btd_device *dup) |
| { |
| GSList *l; |
| |
| DBG(""); |
| |
| dev->bredr = dup->bredr; |
| |
| dev->trusted = dup->trusted; |
| dev->blocked = dup->blocked; |
| |
| for (l = dup->uuids; l; l = g_slist_next(l)) |
| dev->uuids = g_slist_append(dev->uuids, g_strdup(l->data)); |
| |
| if (dev->name[0] == '\0') |
| strcpy(dev->name, dup->name); |
| |
| if (!dev->alias) |
| dev->alias = g_strdup(dup->alias); |
| |
| dev->class = dup->class; |
| |
| dev->vendor_src = dup->vendor_src; |
| dev->vendor = dup->vendor; |
| dev->product = dup->product; |
| dev->version = dup->version; |
| } |
| |
| 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]; |
| |
| 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; |
| |
| if (entry->d_type == DT_UNKNOWN) |
| entry->d_type = util_get_dt(dirname, entry->d_name); |
| |
| snprintf(filename, PATH_MAX, "%s/%s", dirname, |
| entry->d_name); |
| |
| if (entry->d_type == DT_DIR) |
| delete_folder_tree(filename); |
| else |
| unlink(filename); |
| } |
| closedir(dir); |
| |
| rmdir(dirname); |
| } |
| |
| void device_remove_bonding(struct btd_device *device, uint8_t bdaddr_type) |
| { |
| if (bdaddr_type == BDADDR_BREDR) |
| device->bredr_state.bonded = false; |
| else |
| device->le_state.bonded = false; |
| |
| if (!device->bredr_state.bonded && !device->le_state.bonded) |
| btd_device_set_temporary(device, true); |
| |
| btd_adapter_remove_bonding(device->adapter, &device->bdaddr, |
| bdaddr_type); |
| } |
| |
| static void device_remove_stored(struct btd_device *device) |
| { |
| char device_addr[18]; |
| char filename[PATH_MAX]; |
| GKeyFile *key_file; |
| GError *gerr = NULL; |
| char *data; |
| gsize length = 0; |
| |
| if (device->bredr_state.bonded) |
| device_remove_bonding(device, BDADDR_BREDR); |
| |
| if (device->le_state.bonded) |
| device_remove_bonding(device, device->bdaddr_type); |
| |
| device->bredr_state.paired = false; |
| device->le_state.paired = false; |
| |
| if (device->blocked) |
| device_unblock(device, TRUE, FALSE); |
| |
| ba2str(&device->bdaddr, device_addr); |
| |
| create_filename(filename, PATH_MAX, "/%s/%s", |
| btd_adapter_get_storage_dir(device->adapter), |
| device_addr); |
| delete_folder_tree(filename); |
| |
| create_filename(filename, PATH_MAX, "/%s/cache/%s", |
| btd_adapter_get_storage_dir(device->adapter), |
| device_addr); |
| |
| key_file = g_key_file_new(); |
| if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { |
| g_error_free(gerr); |
| g_key_file_free(key_file); |
| return; |
| } |
| g_key_file_remove_group(key_file, "ServiceRecords", NULL); |
| g_key_file_remove_group(key_file, "Attributes", NULL); |
| g_key_file_remove_group(key_file, "Endpoints", NULL); |
| |
| data = g_key_file_to_data(key_file, &length, NULL); |
| if (length > 0) { |
| create_file(filename, 0600); |
| if (!g_file_set_contents(filename, data, length, &gerr)) { |
| error("Unable set contents for %s: (%s)", filename, |
| gerr->message); |
| g_error_free(gerr); |
| } |
| } |
| |
| 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->auth_retry_id) { |
| device->auth_retry_id = 0; |
| timeout_remove(device->auth_retry_id); |
| } |
| |
| if (device->auto_connect) { |
| device->disable_auto_connect = TRUE; |
| device_set_auto_connect(device, FALSE); |
| } |
| |
| if (device->bonding) { |
| uint8_t status; |
| |
| if (device->bredr_state.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 (btd_device_is_connected(device)) { |
| if (device->disconn_timer > 0) |
| timeout_remove(device->disconn_timer); |
| disconnect_all(device); |
| } |
| |
| clear_temporary_timer(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 bool addr_is_public(uint8_t addr_type) |
| { |
| if (addr_type == BDADDR_BREDR || addr_type == BDADDR_LE_PUBLIC) |
| return true; |
| |
| return false; |
| } |
| |
| static bool addr_is_resolvable(const bdaddr_t *bdaddr, uint8_t addr_type) |
| { |
| if (addr_type != BDADDR_LE_RANDOM) |
| return false; |
| |
| switch (bdaddr->b[5] >> 6) { |
| case 0x01: /* Private resolvable */ |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static int device_irk_cmp(const struct btd_device *device, |
| const struct device_addr_type *addr) |
| { |
| struct bt_crypto *crypto; |
| uint8_t hash[3]; |
| |
| if (!device->irk) |
| return -1; |
| |
| crypto = bt_crypto_new(); |
| if (!crypto) |
| return -1; |
| |
| bt_crypto_ah(crypto, device->irk, addr->bdaddr.b + 3, hash); |
| |
| bt_crypto_unref(crypto); |
| |
| return memcmp(addr, hash, 3); |
| } |
| |
| int device_addr_type_cmp(gconstpointer a, gconstpointer b) |
| { |
| const struct btd_device *dev = a; |
| const struct device_addr_type *addr = b; |
| int cmp; |
| |
| cmp = bacmp(&dev->bdaddr, &addr->bdaddr); |
| |
| /* |
| * Address matches and both old and new are public addresses |
| * (doesn't matter whether LE or BR/EDR, then consider this a |
| * match. |
| */ |
| if (!cmp && addr_is_public(addr->bdaddr_type) && |
| addr_is_public(dev->bdaddr_type)) |
| return 0; |
| |
| if (addr->bdaddr_type == BDADDR_BREDR) { |
| if (!dev->bredr) |
| return -1; |
| |
| return cmp; |
| } |
| |
| if (!dev->le) |
| return -1; |
| |
| if (addr->bdaddr_type != dev->bdaddr_type) { |
| if (dev->privacy && addr_is_resolvable(&addr->bdaddr, |
| addr->bdaddr_type)) |
| return device_irk_cmp(dev, addr); |
| |
| if (addr->bdaddr_type == dev->conn_bdaddr_type) |
| return bacmp(&dev->conn_bdaddr, &addr->bdaddr); |
| |
| return -1; |
| } |
| |
| return cmp; |
| } |
| |
| 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); |
| |
| free(uuid); |
| |
| if (ret == 0) |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| GSList *btd_device_get_uuids(struct btd_device *device) |
| { |
| return device->uuids; |
| } |
| |
| bool btd_device_has_uuid(struct btd_device *device, const char *uuid) |
| { |
| return g_slist_find_custom(device->uuids, uuid, |
| (GCompareFunc)strcasecmp); |
| } |
| |
| struct probe_data { |
| struct btd_device *dev; |
| GSList *uuids; |
| }; |
| |
| static struct btd_service *probe_service(struct btd_device *device, |
| struct btd_profile *profile, |
| GSList *uuids) |
| { |
| GSList *l; |
| struct btd_service *service; |
| |
| if (profile->device_probe == NULL) |
| return NULL; |
| |
| if (!device_match_profile(device, profile, uuids)) |
| return NULL; |
| |
| l = find_service_with_profile(device->services, profile); |
| /* If the service already exists, return NULL so that it won't be added |
| * to the device->services. |
| */ |
| if (l) |
| return NULL; |
| |
| service = service_create(device, profile); |
| |
| if (service_probe(service)) { |
| btd_service_unref(service); |
| return NULL; |
| } |
| |
| /* Only set auto connect if profile has set the flag and can really |
| * accept connections. |
| */ |
| if (profile->auto_connect && profile->accept) { |
| /* If temporary mark auto_connect as disabled so when the |
| * device is connected it attempts to enable it. |
| */ |
| if (device->temporary) |
| device->disable_auto_connect = TRUE; |
| else |
| device_set_auto_connect(device, TRUE); |
| } |
| |
| return service; |
| } |
| |
| static void dev_probe(struct btd_profile *p, void *user_data) |
| { |
| struct probe_data *d = user_data; |
| struct btd_service *service; |
| |
| service = probe_service(d->dev, p, d->uuids); |
| if (!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; |
| |
| service = probe_service(device, profile, device->uuids); |
| if (!service) |
| return; |
| |
| device->services = g_slist_append(device->services, service); |
| |
| if (!profile->auto_connect || (!btd_device_is_connected(device) && |
| !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 }; |
| char addr[18]; |
| |
| if (!uuids) |
| return; |
| |
| ba2str(&device->bdaddr, addr); |
| |
| if (device->blocked) { |
| DBG("Skipping profiles for blocked device %s", addr); |
| goto add_uuids; |
| } |
| |
| btd_profile_foreach(dev_probe, &d); |
| |
| add_uuids: |
| device_add_uuids(device, 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: |
| free(prim_uuid); |
| 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]; |
| char att_file[PATH_MAX]; |
| GKeyFile *sdp_key_file; |
| GKeyFile *att_key_file; |
| GError *gerr = NULL; |
| |
| char *data; |
| gsize length = 0; |
| |
| ba2str(btd_adapter_get_address(device->adapter), srcaddr); |
| ba2str(&device->bdaddr, dstaddr); |
| |
| create_filename(sdp_file, PATH_MAX, "/%s/cache/%s", srcaddr, dstaddr); |
| create_file(sdp_file, 0600); |
| |
| sdp_key_file = g_key_file_new(); |
| if (!g_key_file_load_from_file(sdp_key_file, sdp_file, 0, &gerr)) { |
| error("Unable to load key file from %s: (%s)", sdp_file, |
| gerr->message); |
| g_clear_error(&gerr); |
| g_key_file_free(sdp_key_file); |
| sdp_key_file = NULL; |
| } |
| |
| create_filename(att_file, PATH_MAX, "/%s/%s/attributes", srcaddr, |
| dstaddr); |
| create_file(att_file, 0600); |
| |
| att_key_file = g_key_file_new(); |
| if (!g_key_file_load_from_file(att_key_file, att_file, 0, &gerr)) { |
| error("Unable to load key file from %s: (%s)", att_file, |
| gerr->message); |
| g_clear_error(&gerr); |
| g_key_file_free(att_key_file); |
| att_key_file = NULL; |
| } |
| |
| for (seq = recs; seq; seq = seq->next) { |
| sdp_record_t *rec = (sdp_record_t *) seq->data; |
| char *profile_uuid; |
| |
| if (!rec) |
| break; |
| |
| /* If service class attribute is missing, svclass will be all |
| * zero and the resulting uuid string will be NULL. |
| */ |
| profile_uuid = bt_uuid2string(&rec->svclass); |
| |
| if (!profile_uuid) |
| 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: |
| free(profile_uuid); |
| } |
| |
| if (sdp_key_file) { |
| data = g_key_file_to_data(sdp_key_file, &length, NULL); |
| if (length > 0) { |
| if (!g_file_set_contents(sdp_file, data, length, |
| &gerr)) { |
| error("Unable set contents for %s: (%s)", |
| sdp_file, gerr->message); |
| g_clear_error(&gerr); |
| } |
| } |
| |
| 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) { |
| if (!g_file_set_contents(att_file, data, length, |
| &gerr)) { |
| error("Unable set contents for %s: (%s)", |
| att_file, gerr->message); |
| g_clear_error(&gerr); |
| } |
| } |
| |
| 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_uuids(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); |
| } |
| |
| free(att_uuid); |
| |
| return 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; |
| DBusMessage *reply; |
| 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, BT_ATT_PSM); |
| |
| /* |
| * TODO: The btd_service instances for GATT services need to be |
| * initialized with the service handles. Eventually this code should |
| * perform ATT protocol service discovery over the ATT PSM to obtain |
| * the full list of services and populate a client-role gatt_db over |
| * BR/EDR. |
| */ |
| 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: |
| /* If SDP search failed during an ongoing connection request, we should |
| * reply to D-Bus method call. |
| */ |
| if (err < 0 && device->connect) { |
| DBG("SDP failed during connection"); |
| reply = btd_error_failed(device->connect, strerror(-err)); |
| g_dbus_send_message(dbus_conn, reply); |
| dbus_message_unref(device->connect); |
| device->connect = NULL; |
| } |
| |
| device_svc_resolved(device, BROWSE_SDP, BDADDR_BREDR, err); |
| } |
| |
| 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(btd_adapter_get_address(adapter), |
| &device->bdaddr, &uuid, |
| browse_cb, user_data, NULL, |
| req->sdp_flags); |
| return; |
| } |
| |
| done: |
| search_cb(recs, err, user_data); |
| } |
| |
| static bool device_get_auto_connect(struct btd_device *device) |
| { |
| if (device->disable_auto_connect) |
| return false; |
| |
| return device->auto_connect; |
| } |
| |
| static void disconnect_gatt_service(gpointer data, gpointer user_data) |
| { |
| struct btd_service *service = data; |
| struct btd_profile *profile = btd_service_get_profile(service); |
| |
| /* Ignore if profile cannot accept connections */ |
| if (!profile->accept) |
| return; |
| |
| btd_service_disconnect(service); |
| } |
| |
| static void att_disconnected_cb(int err, void *user_data) |
| { |
| struct btd_device *device = user_data; |
| |
| DBG(""); |
| |
| if (device->browse) |
| goto done; |
| |
| DBG("%s (%d)", strerror(err), err); |
| |
| g_slist_foreach(device->services, disconnect_gatt_service, NULL); |
| |
| btd_gatt_client_disconnected(device->client_dbus); |
| |
| 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); |
| } |
| |
| static void register_gatt_services(struct btd_device *device) |
| { |
| struct browse_req *req = device->browse; |
| GSList *services = NULL; |
| |
| if (!bt_gatt_client_is_ready(device->client)) |
| return; |
| |
| /* |
| * TODO: Remove the primaries list entirely once all profiles use |
| * shared/gatt. |
| */ |
| gatt_db_foreach_service(device->db, NULL, add_primary, &services); |
| |
| btd_device_set_temporary(device, false); |
| |
| if (req) |
| update_gatt_uuids(req, device->primaries, services); |
| |
| g_slist_free_full(device->primaries, g_free); |
| device->primaries = NULL; |
| |
| device_register_primaries(device, services, -1); |
| |
| device_add_gatt_services(device); |
| } |
| |
| static void gatt_client_init(struct btd_device *device); |
| |
| static void gatt_client_ready_cb(bool success, uint8_t att_ecode, |
| void *user_data) |
| { |
| struct btd_device *device = user_data; |
| |
| DBG("status: %s, error: %u", success ? "success" : "failed", att_ecode); |
| |
| if (!success) { |
| device_svc_resolved(device, BROWSE_GATT, device->bdaddr_type, |
| -EIO); |
| return; |
| } |
| |
| register_gatt_services(device); |
| |
| btd_gatt_client_ready(device->client_dbus); |
| |
| device_svc_resolved(device, BROWSE_GATT, device->bdaddr_type, 0); |
| |
| store_gatt_db(device); |
| } |
| |
| static void gatt_client_service_changed(uint16_t start_handle, |
| uint16_t end_handle, |
| void *user_data) |
| { |
| struct btd_device *device = user_data; |
| |
| DBG("start 0x%04x, end: 0x%04x", start_handle, end_handle); |
| |
| store_gatt_db(device); |
| } |
| |
| static void gatt_debug(const char *str, void *user_data) |
| { |
| DBG_IDX(0xffff, "%s", str); |
| } |
| |
| static void gatt_client_init(struct btd_device *device) |
| { |
| uint8_t features; |
| |
| gatt_client_cleanup(device); |
| |
| if (!btd_device_is_initiator(device) && !btd_opts.reverse_discovery) { |
| DBG("Reverse service discovery disabled: skipping GATT client"); |
| return; |
| } |
| |
| if (!btd_device_is_initiator(device) && !btd_opts.gatt_client) { |
| DBG("GATT client disabled: skipping GATT client"); |
| return; |
| } |
| |
| features = BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING |
| | BT_GATT_CHRC_CLI_FEAT_NFY_MULTI; |
| if (btd_opts.gatt_channels > 1) |
| features |= BT_GATT_CHRC_CLI_FEAT_EATT; |
| |
| if (!btd_opts.gatt_seclevel && device->bonding) { |
| DBG("Elevating security level since bonding is in progress"); |
| bt_att_set_security(device->att, BT_ATT_SECURITY_MEDIUM); |
| } |
| |
| device->client = bt_gatt_client_new(device->db, device->att, |
| device->att_mtu, features); |
| if (!device->client) { |
| DBG("Failed to initialize"); |
| return; |
| } |
| |
| bt_gatt_client_set_debug(device->client, gatt_debug, NULL, NULL); |
| g_attrib_attach_client(device->attrib, device->client); |
| |
| /* |
| * If we have cache, notify existing service about the new connection |
| * so they can react to notifications while discovering services |
| */ |
| if (!gatt_db_isempty(device->db)) |
| device_accept_gatt_profiles(device); |
| |
| device->gatt_ready_id = bt_gatt_client_ready_register(device->client, |
| gatt_client_ready_cb, |
| device, NULL); |
| if (!device->gatt_ready_id) { |
| DBG("Failed to register GATT ready callback"); |
| gatt_client_cleanup(device); |
| return; |
| } |
| |
| if (!bt_gatt_client_set_service_changed(device->client, |
| gatt_client_service_changed, |
| device, NULL)) { |
| DBG("Failed to set service changed handler"); |
| gatt_client_cleanup(device); |
| return; |
| } |
| |
| btd_gatt_client_connected(device->client_dbus); |
| |
| /* Only initiate EATT connection when acting as initiator, as acceptor |
| * it shall be triggered only when ready to avoid possible clashes where |
| * both sides attempt to connection at same time. |
| */ |
| if (btd_device_is_initiator(device)) |
| btd_gatt_client_eatt_connect(device->client_dbus); |
| } |
| |
| static void gatt_server_init(struct btd_device *device, |
| struct btd_gatt_database *database) |
| { |
| struct gatt_db *db = btd_gatt_database_get_db(database); |
| |
| if (!db) { |
| error("No local GATT database exists for this adapter"); |
| return; |
| } |
| |
| gatt_server_cleanup(device); |
| |
| device->server = bt_gatt_server_new(db, device->att, device->att_mtu, |
| btd_opts.key_size); |
| if (!device->server) { |
| error("Failed to initialize bt_gatt_server"); |
| return; |
| } |
| |
| if (device->ltk) |
| bt_att_set_enc_key_size(device->att, device->ltk->enc_size); |
| |
| if (btd_opts.gatt_seclevel == BT_ATT_SECURITY_LOW) |
| bt_gatt_server_set_permissions(device->server, false); |
| |
| bt_gatt_server_set_debug(device->server, gatt_debug, NULL, NULL); |
| |
| btd_gatt_database_server_connected(database, device->server); |
| } |
| |
| static bool local_counter(uint32_t *sign_cnt, void *user_data) |
| { |
| struct btd_device *dev = user_data; |
| |
| if (!dev->local_csrk) |
| return false; |
| |
| *sign_cnt = dev->local_csrk->counter++; |
| |
| store_device_info(dev); |
| |
| return true; |
| } |
| |
| static bool remote_counter(uint32_t *sign_cnt, void *user_data) |
| { |
| struct btd_device *dev = user_data; |
| |
| if (!dev->remote_csrk || *sign_cnt < dev->remote_csrk->counter) |
| return false; |
| |
| dev->remote_csrk->counter = *sign_cnt; |
| |
| store_device_info(dev); |
| |
| return true; |
| } |
| |
| bool device_attach_att(struct btd_device *dev, GIOChannel *io) |
| { |
| GError *gerr = NULL; |
| GAttrib *attrib; |
| BtIOSecLevel sec_level; |
| uint16_t mtu; |
| uint16_t cid; |
| struct btd_gatt_database *database; |
| const bdaddr_t *dst; |
| char dstaddr[18]; |
| |
| bt_io_get(io, &gerr, BT_IO_OPT_SEC_LEVEL, &sec_level, |
| BT_IO_OPT_IMTU, &mtu, |
| BT_IO_OPT_CID, &cid, |
| BT_IO_OPT_INVALID); |
| |
| if (gerr) { |
| error("bt_io_get: %s", gerr->message); |
| g_error_free(gerr); |
| return false; |
| } |
| |
| if (dev->att) { |
| if (btd_opts.gatt_channels == bt_att_get_channels(dev->att)) { |
| DBG("EATT channel limit reached"); |
| return false; |
| } |
| |
| if (!bt_att_attach_fd(dev->att, g_io_channel_unix_get_fd(io))) { |
| DBG("EATT channel connected"); |
| g_io_channel_set_close_on_unref(io, FALSE); |
| return true; |
| } |
| |
| error("Failed to attach EATT channel"); |
| return false; |
| } |
| |
| if (!btd_opts.gatt_seclevel && sec_level == BT_IO_SEC_LOW && |
| dev->le_state.paired) { |
| DBG("Elevating security level since LTK is available"); |
| |
| sec_level = BT_IO_SEC_MEDIUM; |
| bt_io_set(io, &gerr, BT_IO_OPT_SEC_LEVEL, sec_level, |
| BT_IO_OPT_INVALID); |
| if (gerr) { |
| error("bt_io_set: %s", gerr->message); |
| g_error_free(gerr); |
| return false; |
| } |
| } |
| |
| dev->att_mtu = MIN(mtu, btd_opts.gatt_mtu); |
| attrib = g_attrib_new(io, cid == BT_ATT_CID ? BT_ATT_DEFAULT_LE_MTU : |
| dev->att_mtu, false); |
| if (!attrib) { |
| error("Unable to create new GAttrib instance"); |
| return false; |
| } |
| |
| dev->attrib = attrib; |
| dev->att = g_attrib_get_att(attrib); |
| |
| bt_att_ref(dev->att); |
| |
| bt_att_set_debug(dev->att, BT_ATT_DEBUG, gatt_debug, NULL, NULL); |
| |
| dev->att_disconn_id = bt_att_register_disconnect(dev->att, |
| att_disconnected_cb, dev, NULL); |
| bt_att_set_close_on_unref(dev->att, true); |
| |
| if (dev->local_csrk) |
| bt_att_set_local_key(dev->att, dev->local_csrk->key, |
| local_counter, dev); |
| |
| if (dev->remote_csrk) |
| bt_att_set_remote_key(dev->att, dev->remote_csrk->key, |
| remote_counter, dev); |
| |
| /* Force security level if it has been set */ |
| if (btd_opts.gatt_seclevel) |
| bt_att_set_security(dev->att, btd_opts.gatt_seclevel); |
| |
| database = btd_adapter_get_database(dev->adapter); |
| |
| dst = device_get_address(dev); |
| ba2str(dst, dstaddr); |
| |
| if (gatt_db_isempty(dev->db)) |
| load_gatt_db(dev, btd_adapter_get_storage_dir(dev->adapter), |
| dstaddr); |
| |
| gatt_client_init(dev); |
| gatt_server_init(dev, database); |
| |
| /* |
| * 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(dev->adapter, dev); |
| |
| return true; |
| } |
| |
| static void att_connect_cb(GIOChannel *io, GError *gerr, gpointer user_data) |
| { |
| struct btd_device *device = user_data; |
| DBusMessage *reply; |
| enum mgmt_io_capability io_cap; |
| int err = 0; |
| |
| g_io_channel_unref(device->att_io); |
| device->att_io = NULL; |
| |
| if (gerr) { |
| DBG("%s", gerr->message); |
| |
| if (g_error_matches(gerr, BT_IO_ERROR, ECONNABORTED)) |
| goto done; |
| |
| if (device_get_auto_connect(device)) { |
| DBG("Enabling automatic connections"); |
| adapter_connect_list_add(device->adapter, device); |
| } |
| |
| if (device->browse) |
| browse_request_complete(device->browse, |
| BROWSE_GATT, |
| device->bdaddr_type, |
| -ECONNABORTED); |
| |
| err = -ECONNABORTED; |
| goto done; |
| } |
| |
| /* Update connected state */ |
| device->le_state.connected = true; |
| |
| if (!device_attach_att(device, io)) |
| goto done; |
| |
| if (!device->bonding) |
| goto done; |
| |
| if (device->bonding->agent) |
| io_cap = agent_get_io_capability(device->bonding->agent); |
| else |
| io_cap = MGMT_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 (!err) |
| device_browse_gatt(device, NULL); |
| |
| /* Send D-Bus reply to le.connect() */ |
| if (err && device->le) |
| btd_bearer_connected(device->le, err); |
| |
| if (device->connect) { |
| if (err < 0) |
| reply = btd_error_le_errno(device->connect, 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; |
| } |
| } |
| |
| int device_connect_le(struct btd_device *dev) |
| { |
| struct btd_adapter *adapter = dev->adapter; |
| BtIOSecLevel sec_level; |
| GIOChannel *io; |
| GError *gerr = NULL; |
| char addr[18]; |
| |
| /* There is one connection attempt going on */ |
| if (dev->att_io || dev->att) |
| return -EALREADY; |
| |
| ba2str(&dev->bdaddr, addr); |
| |
| DBG("Connection attempt to: %s", addr); |
| |
| /* Set as initiator */ |
| dev->le_state.initiator = true; |
| |
| if (btd_opts.gatt_seclevel) |
| sec_level = btd_opts.gatt_seclevel; |
| else if (dev->le_state.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, dev, NULL, &gerr, |
| BT_IO_OPT_SOURCE_BDADDR, |
| btd_adapter_get_address(adapter), |
| BT_IO_OPT_SOURCE_TYPE, |
| btd_adapter_get_address_type(adapter), |
| BT_IO_OPT_DEST_BDADDR, &dev->bdaddr, |
| BT_IO_OPT_DEST_TYPE, dev->bdaddr_type, |
| BT_IO_OPT_CID, BT_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); |
| return -EIO; |
| } |
| |
| /* Keep this, so we can cancel the connection */ |
| dev->att_io = io; |
| |
| /* Restart temporary timer to give it time to connect/pair, etc. */ |
| if (dev->temporary) |
| set_temporary_timer(dev, btd_opts.tmpto); |
| |
| return 0; |
| } |
| |
| static struct browse_req *browse_request_new(struct btd_device *device, |
| uint8_t type, |
| DBusMessage *msg) |
| { |
| struct browse_req *req; |
| |
| if (device->browse) |
| return NULL; |
| |
| req = g_new0(struct browse_req, 1); |
| req->device = device; |
| req->type = type; |
| |
| device->browse = req; |
| |
| if (!msg) |
| return req; |
| |
| 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, |
| dbus_message_get_sender(msg), |
| browse_request_exit, |
| req, NULL); |
| |
| return req; |
| } |
| |
| static int device_browse_gatt(struct btd_device *device, DBusMessage *msg) |
| { |
| struct btd_adapter *adapter = device->adapter; |
| struct browse_req *req; |
| |
| req = browse_request_new(device, BROWSE_GATT, msg); |
| if (!req) |
| return -EBUSY; |
| |
| if (device->client) { |
| /* |
| * If discovery has not yet completed, then wait for gatt-client |
| * to become ready. |
| */ |
| if (!bt_gatt_client_is_ready(device->client)) |
| return 0; |
| |
| /* |
| * Services have already been discovered, so signal this browse |
| * request as resolved. |
| */ |
| device_svc_resolved(device, BROWSE_GATT, device->bdaddr_type, |
| 0); |
| return 0; |
| } |
| |
| device->att_io = bt_io_connect(att_connect_cb, |
| device, NULL, NULL, |
| BT_IO_OPT_SOURCE_BDADDR, |
| btd_adapter_get_address(adapter), |
| BT_IO_OPT_SOURCE_TYPE, |
| btd_adapter_get_address_type(adapter), |
| BT_IO_OPT_DEST_BDADDR, &device->bdaddr, |
| BT_IO_OPT_DEST_TYPE, device->bdaddr_type, |
| BT_IO_OPT_CID, BT_ATT_CID, |
| BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, |
| BT_IO_OPT_INVALID); |
| |
| if (device->att_io == NULL) { |
| browse_request_free(req); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static uint16_t get_sdp_flags(struct btd_device *device) |
| { |
| uint16_t vid, pid; |
| |
| vid = btd_device_get_vendor(device); |
| pid = btd_device_get_product(device); |
| |
| /* Sony DualShock 4 is not respecting negotiated L2CAP MTU. This might |
| * results in SDP response being dropped by kernel. Workaround this by |
| * forcing SDP code to use bigger MTU while connecting. |
| */ |
| if (vid == 0x054c && pid == 0x05c4) |
| return SDP_LARGE_MTU; |
| |
| if (btd_adapter_ssp_enabled(device->adapter)) |
| return 0; |
| |
| /* if no EIR try matching Sony DualShock 4 with name and class */ |
| if (!strncmp(device->name, "Wireless Controller", MAX_NAME_LENGTH) && |
| device->class == 0x2508) |
| return SDP_LARGE_MTU; |
| |
| 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; |
| |
| req = browse_request_new(device, BROWSE_SDP, msg); |
| if (!req) |
| return -EBUSY; |
| |
| sdp_uuid16_create(&uuid, uuid_list[req->search_uuid++]); |
| |
| req->sdp_flags = get_sdp_flags(device); |
| |
| err = bt_search(btd_adapter_get_address(adapter), |
| &device->bdaddr, &uuid, browse_cb, req, NULL, |
| req->sdp_flags); |
| if (err < 0) { |
| browse_request_free(req); |
| return err; |
| } |
| |
| return err; |
| } |
| |
| static gboolean device_is_browsing(struct btd_device *device, |
| uint8_t bdaddr_type) |
| { |
| if (!device->browse) |
| return FALSE; |
| |
| if (bdaddr_type == BDADDR_BREDR && device->browse->type == BROWSE_SDP) |
| return TRUE; |
| |
| if (bdaddr_type != BDADDR_BREDR && device->browse->type == BROWSE_GATT) |
| return TRUE; |
| |
| return FALSE; |
| } |
| |
| void device_cancel_browse(struct btd_device *device, uint8_t bdaddr_type) |
| { |
| struct btd_adapter *adapter = device->adapter; |
| |
| DBG(""); |
| |
| if (!device_is_browsing(device, bdaddr_type)) |
| return; |
| |
| if (bdaddr_type == BDADDR_BREDR) |
| bt_cancel_discovery(btd_adapter_get_address(adapter), |
| &device->bdaddr); |
| else |
| attio_cleanup(device); |
| |
| browse_request_free(device->browse); |
| } |
| |
| int device_discover_services(struct btd_device *device) |
| { |
| int err; |
| |
| if (device->bredr) |
| err = device_browse_sdp(device, NULL); |
| else |
| err = device_browse_gatt(device, NULL); |
| |
| if (err == 0 && device->discov_timer) { |
| timeout_remove(device->discov_timer); |
| device->discov_timer = 0; |
| } |
| |
| 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; |
| } |
| uint8_t device_get_le_address_type(struct btd_device *device) |
| { |
| return device->bdaddr_type; |
| } |
| |
| 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 btd_device_set_temporary(struct btd_device *device, bool temporary) |
| { |
| if (!device) |
| return; |
| |
| if (device->temporary == temporary) |
| return; |
| |
| if (device_address_is_private(device)) |
| return; |
| |
| DBG("temporary %d", temporary); |
| |
| device->temporary = temporary; |
| |
| if (temporary) { |
| if (device->bredr) |
| adapter_accept_list_remove(device->adapter, device); |
| adapter_connect_list_remove(device->adapter, device); |
| if (device->auto_connect) { |
| device->disable_auto_connect = TRUE; |
| device_set_auto_connect(device, FALSE); |
| } |
| set_temporary_timer(device, btd_opts.tmpto); |
| return; |
| } else |
| clear_temporary_timer(device); |
| |
| if (device->bredr) |
| adapter_accept_list_add(device->adapter, device); |
| |
| store_device_info(device); |
| |
| /* attributes were not stored when resolved if device was temporary */ |
| if (device->bdaddr_type != BDADDR_BREDR && |
| device->le_state.svc_resolved && |
| g_slist_length(device->primaries) != 0) |
| store_services(device); |
| } |
| |
| void btd_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, uint8_t bdaddr_type) |
| { |
| struct bearer_state *state; |
| |
| if (!device) |
| return; |
| |
| state = get_state(device, bdaddr_type); |
| |
| if (state->bonded) |
| return; |
| |
| DBG("setting bonded for device to true"); |
| |
| state->bonded = true; |
| |
| if (bdaddr_type == BDADDR_BREDR) |
| btd_bearer_bonded(device->bredr); |
| else |
| btd_bearer_bonded(device->le); |
| |
| btd_device_set_temporary(device, false); |
| |
| /* If the other bearer state was already true we don't need to |
| * send any property signals. |
| */ |
| if (device->bredr_state.bonded == device->le_state.bonded) |
| return; |
| |
| g_dbus_emit_property_changed(dbus_conn, device->path, |
| DEVICE_INTERFACE, "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_cable_pairing(struct btd_device *device, bool cable_pairing) |
| { |
| if (!device) |
| return; |
| |
| if (device->cable_pairing == cable_pairing) |
| return; |
| |
| DBG("setting cable pairing %d", cable_pairing); |
| |
| device->cable_pairing = cable_pairing; |
| |
| g_dbus_emit_property_changed(dbus_conn, device->path, |
| DEVICE_INTERFACE, "CablePairing"); |
| } |
| |
| void device_store_svc_chng_ccc(struct btd_device *device, uint8_t bdaddr_type, |
| uint16_t value) |
| { |
| char filename[PATH_MAX]; |
| char device_addr[18]; |
| GKeyFile *key_file; |
| GError *gerr = NULL; |
| uint16_t old_value; |
| gsize length = 0; |
| char *str; |
| |
| ba2str(&device->bdaddr, device_addr); |
| create_filename(filename, PATH_MAX, "/%s/%s/info", |
| btd_adapter_get_storage_dir(device->adapter), |
| device_addr); |
| |
| key_file = g_key_file_new(); |
| if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { |
| error("Unable to load key file from %s: (%s)", filename, |
| gerr->message); |
| g_clear_error(&gerr); |
| } |
| |
| /* for bonded devices this is done on every connection so limit writes |
| * to storage if no change needed |
| */ |
| if (bdaddr_type == BDADDR_BREDR) { |
| old_value = g_key_file_get_integer(key_file, "ServiceChanged", |
| "CCC_BR/EDR", NULL); |
| if (old_value == value) |
| goto done; |
| |
| g_key_file_set_integer(key_file, "ServiceChanged", "CCC_BR/EDR", |
| value); |
| } else { |
| old_value = g_key_file_get_integer(key_file, "ServiceChanged", |
| "CCC_LE", NULL); |
| if (old_value == value) |
| goto done; |
| |
| g_key_file_set_integer(key_file, "ServiceChanged", "CCC_LE", |
| value); |
| } |
| |
| create_file(filename, 0600); |
| |
| str = g_key_file_to_data(key_file, &length, NULL); |
| if (!g_file_set_contents(filename, str, length, &gerr)) { |
| error("Unable set contents for %s: (%s)", filename, |
| gerr->message); |
| g_error_free(gerr); |
| } |
| g_free(str); |
| |
| done: |
| g_key_file_free(key_file); |
| } |
| void device_load_svc_chng_ccc(struct btd_device *device, uint16_t *ccc_le, |
| uint16_t *ccc_bredr) |
| { |
| char filename[PATH_MAX]; |
| char device_addr[18]; |
| GKeyFile *key_file; |
| GError *gerr = NULL; |
| |
| ba2str(&device->bdaddr, device_addr); |
| create_filename(filename, PATH_MAX, "/%s/%s/info", |
| btd_adapter_get_storage_dir(device->adapter), |
| device_addr); |
| |
| key_file = g_key_file_new(); |
| if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { |
| error("Unable to load key file from %s: (%s)", filename, |
| gerr->message); |
| g_error_free(gerr); |
| } |
| |
| if (!g_key_file_has_group(key_file, "ServiceChanged")) { |
| if (ccc_le) |
| *ccc_le = 0x0000; |
| if (ccc_bredr) |
| *ccc_bredr = 0x0000; |
| g_key_file_free(key_file); |
| return; |
| } |
| |
| if (ccc_le) |
| *ccc_le = g_key_file_get_integer(key_file, "ServiceChanged", |
| "CCC_LE", NULL); |
| |
| if (ccc_bredr) |
| *ccc_bredr = g_key_file_get_integer(key_file, "ServiceChanged", |
| "CCC_BR/EDR", NULL); |
| |
| g_key_file_free(key_file); |
| } |
| |
| void device_set_rssi_with_delta(struct btd_device *device, int8_t rssi, |
| int8_t delta_threshold) |
| { |
| 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 delta_threshold dBm or more */ |
| if (delta < delta_threshold) |
| return; |
| |
| DBG("rssi %d delta %d", rssi, delta); |
| |
| device->rssi = rssi; |
| } |
| |
| g_dbus_emit_property_changed(dbus_conn, device->path, |
| DEVICE_INTERFACE, "RSSI"); |
| } |
| |
| void device_set_rssi(struct btd_device *device, int8_t rssi) |
| { |
| device_set_rssi_with_delta(device, rssi, RSSI_THRESHOLD); |
| } |
| |
| void device_set_tx_power(struct btd_device *device, int8_t tx_power) |
| { |
| if (!device) |
| return; |
| |
| if (device->tx_power == tx_power) |
| return; |
| |
| DBG("tx_power %d", tx_power); |
| |
| device->tx_power = tx_power; |
| |
| g_dbus_emit_property_changed(dbus_conn, device->path, |
| DEVICE_INTERFACE, "TxPower"); |
| } |
| |
| void device_set_flags(struct btd_device *device, uint8_t flags) |
| { |
| if (!device) |
| return; |
| |
| DBG("flags %d", flags); |
| |
| if (device->ad_flags[0] == flags) |
| return; |
| |
| device->ad_flags[0] = flags; |
| |
| g_dbus_emit_property_changed(dbus_conn, device->path, |
| DEVICE_INTERFACE, "AdvertisingFlags"); |
| } |
| |
| bool device_is_connectable(struct btd_device *device) |
| { |
| struct bearer_state *state; |
| |
| if (!device) |
| return false; |
| |
| if (device->bredr) |
| return true; |
| |
| state = get_state(device, device->bdaddr_type); |
| |
| return state->connectable; |
| } |
| |
| static bool start_discovery(gpointer user_data) |
| { |
| struct btd_device *device = user_data; |
| |
| if (device->bredr) |
| device_browse_sdp(device, NULL); |
| else |
| device_browse_gatt(device, NULL); |
| |
| device->discov_timer = 0; |
| |
| return FALSE; |
| } |
| |
| void device_set_paired(struct btd_device *dev, uint8_t bdaddr_type) |
| { |
| struct bearer_state *state = get_state(dev, bdaddr_type); |
| |
| if (state->paired) |
| return; |
| |
| state->paired = true; |
| |
| if (bdaddr_type == BDADDR_BREDR) |
| btd_bearer_paired(dev->bredr); |
| else |
| btd_bearer_paired(dev->le); |
| |
| /* If the other bearer state was already true we don't need to |
| * send any property signals. |
| */ |
| if (dev->bredr_state.paired == dev->le_state.paired) |
| return; |
| |
| if (!state->svc_resolved) { |
| dev->pending_paired = true; |
| return; |
| } |
| |
| g_dbus_emit_property_changed(dbus_conn, dev->path, |
| DEVICE_INTERFACE, "Paired"); |
| } |
| |
| void device_set_unpaired(struct btd_device *dev, uint8_t bdaddr_type) |
| { |
| struct bearer_state *state = get_state(dev, bdaddr_type); |
| |
| if (!state->paired) |
| return; |
| |
| state->paired = false; |
| |
| if (bdaddr_type == BDADDR_BREDR) |
| btd_bearer_paired(dev->bredr); |
| else |
| btd_bearer_paired(dev->le); |
| |
| /* |
| * If the other bearer state is still true we don't need to |
| * send any property signals or remove device. |
| */ |
| if (dev->bredr_state.paired != dev->le_state.paired) { |
| /* TODO disconnect only unpaired bearer */ |
| if (state->connected) |
| device_request_disconnect(dev, NULL); |
| |
| return; |
| } |
| |
| g_dbus_emit_property_changed(dbus_conn, dev->path, |
| DEVICE_INTERFACE, "Paired"); |
| |
| btd_device_set_temporary(dev, true); |
| |
| if (btd_device_is_connected(dev)) |
| device_request_disconnect(dev, NULL); |
| else |
| btd_adapter_remove_device(dev->adapter, dev); |
| } |
| |
| static void device_auth_req_free(struct btd_device *device) |
| { |
| struct authentication_req *authr = device->authr; |
| |
| if (!authr) |
| return; |
| |
| if (authr->agent) |
| agent_unref(authr->agent); |
| |
| g_free(authr->pincode); |
| g_free(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 bdaddr_type, |
| uint8_t status) |
| { |
| struct bonding_req *bonding = device->bonding; |
| struct authentication_req *auth = device->authr; |
| struct bearer_state *state = get_state(device, bdaddr_type); |
| |
| DBG("bonding %p status 0x%02x", bonding, status); |
| |
| device->bonding_status = status; |
| if (status == MGMT_STATUS_AUTH_FAILED) |
| device_request_disconnect(device, NULL); |
| |
| if (auth && auth->agent) |
| agent_cancel(auth->agent); |
| |
| if (status) { |
| device_cancel_authentication(device, TRUE); |
| |
| /* Put the device back to the temporary state so that it will be |
| * treated as a newly discovered device. |
| */ |
| if (!btd_device_bearer_is_connected(device) && |
| !device_is_paired(device, bdaddr_type) && |
| !btd_device_is_trusted(device)) |
| btd_device_set_temporary(device, true); |
| |
| device_bonding_failed(device, status); |
| return; |
| } |
| |
| device_auth_req_free(device); |
| |
| /* Enable the wake_allowed property if required */ |
| if (device->wake_override == WAKE_FLAG_ENABLED) |
| device_set_wake_allowed(device, true, -1U); |
| |
| /* If we're already paired nothing more is needed */ |
| if (state->paired) |
| return; |
| |
| device_set_paired(device, bdaddr_type); |
| |
| /* If services are already resolved just reply to the pairing |
| * request |
| */ |
| if (state->svc_resolved && bonding) { |
| /* Attempt to store services for this device failed because it |
| * was not paired. Now that we're paired retry. */ |
| store_gatt_db(device); |
| |
| 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) { |
| timeout_remove(device->discov_timer); |
| device->discov_timer = 0; |
| } |
| |
| if (bdaddr_type == BDADDR_BREDR) |
| device_browse_sdp(device, bonding->msg); |
| else |
| device_browse_gatt(device, bonding->msg); |
| |
| bonding_request_free(bonding); |
| } else if (!state->svc_resolved) { |
| if (!device->browse && !device->discov_timer && |
| btd_opts.reverse_discovery) { |
| /* 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 = timeout_add_seconds( |
| DISCOVERY_TIMER, |
| start_discovery, |
| device, NULL); |
| } |
| } |
| } |
| |
| 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) |
| { |
| /* This API is only used for BR/EDR (for now) */ |
| struct bearer_state *state = &dev->bredr_state; |
| 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 (state->svc_resolved || !btd_opts.reverse_discovery) |
| cb->idle_id = g_idle_add(svc_idle_cb, cb); |
| else { |
| if (dev->discov_timer > 0) |
| timeout_remove(dev->discov_timer); |
| |
| dev->discov_timer = timeout_add_seconds( |
| 0, |
| start_discovery, |
| dev, NULL); |
| } |
| |
| 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; |
| enum mgmt_io_capability io_cap; |
| int err; |
| |
| if (!bonding) |
| return FALSE; |
| |
| DBG("retrying bonding"); |
| bonding->retry_timer = 0; |
| |
| /* Restart the bonding timer to the beginning 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 = MGMT_IO_CAPABILITY_NOINPUTNOOUTPUT; |
| |
| err = adapter_bonding_attempt(adapter, &device->bdaddr, |
| device->bdaddr_type, io_cap); |
| if (err < 0) |
| device_bonding_complete(device, bonding->bdaddr_type, |
| 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, |
| auth->addr_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, |
| auth->addr_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, |
| uint8_t addr_type, |
| 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->addr_type = addr_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, BDADDR_BREDR, 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, uint8_t type) |
| { |
| struct authentication_req *auth; |
| int err; |
| |
| auth = new_auth(device, type, 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, uint8_t type, |
| int32_t passkey, uint8_t confirm_hint) |
| { |
| struct authentication_req *auth; |
| int err; |
| |
| /* Just-Works repairing policy */ |
| if (confirm_hint && device_is_paired(device, type)) { |
| if (btd_opts.jw_repairing == JW_REPAIRING_NEVER) { |
| btd_adapter_confirm_reply(device->adapter, |
| &device->bdaddr, |
| type, FALSE); |
| return 0; |
| } else if (btd_opts.jw_repairing == JW_REPAIRING_ALWAYS) { |
| btd_adapter_confirm_reply(device->adapter, |
| &device->bdaddr, |
| type, TRUE); |
| return 0; |
| } |
| } |
| |
| auth = new_auth(device, type, AUTH_TYPE_CONFIRM, FALSE); |
| if (!auth) |
| return -EPERM; |
| |
| auth->passkey = passkey; |
| |
| if (confirm_hint) { |
| if (device->bonding != NULL) { |
| /* We know the client has indicated the intent to pair |
| * with the peer device, so we can auto-accept. |
| */ |
| btd_adapter_confirm_reply(device->adapter, |
| &device->bdaddr, |
| type, TRUE); |
| return 0; |
| } |
| |
| 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) { |
| if (err == -EINPROGRESS) { |
| /* Already in progress */ |
| confirm_cb(auth->agent, NULL, auth); |
| return 0; |
| } |
| |
| error("Failed requesting authentication"); |
| device_auth_req_free(device); |
| } |
| |
| return err; |
| } |
| |
| int device_notify_passkey(struct btd_device *device, uint8_t type, |
| 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, type, 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, BDADDR_BREDR, 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) { |
| if (err == -EINPROGRESS) { |
| /* Already in progress */ |
| display_pincode_cb(auth->agent, NULL, auth); |
| return 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 (device->adapter) |
| btd_adapter_cancel_service_auth(device->adapter, device); |
| |
| 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; |
| } |
| |
| struct gatt_db *btd_device_get_gatt_db(struct btd_device *device) |
| { |
| if (!device) |
| return NULL; |
| |
| return device->db; |
| } |
| |
| bool btd_device_set_gatt_db(struct btd_device *device, struct gatt_db *db) |
| { |
| struct gatt_db *clone; |
| |
| if (!device) |
| return false; |
| |
| clone = gatt_db_clone(db); |
| if (clone) |
| return false; |
| |
| gatt_db_unregister(device->db, device->db_id); |
| gatt_db_unref(device->db); |
| |
| device->db = clone; |
| device->db_id = gatt_db_register(device->db, gatt_service_added, |
| gatt_service_removed, device, NULL); |
| |
| return true; |
| } |
| |
| struct bt_gatt_client *btd_device_get_gatt_client(struct btd_device *device) |
| { |
| if (!device) |
| return NULL; |
| |
| return device->client; |
| } |
| |
| void *btd_device_get_attrib(struct btd_device *device) |
| { |
| if (!device) |
| return NULL; |
| |
| return device->attrib; |
| } |
| |
| struct bt_gatt_server *btd_device_get_gatt_server(struct btd_device *device) |
| { |
| if (!device) |
| return NULL; |
| |
| return device->server; |
| } |
| |
| void btd_device_gatt_set_service_changed(struct btd_device *device, |
| uint16_t start, uint16_t end) |
| { |
| /* |
| * TODO: Remove this function and handle service changed via |
| * gatt-client. |
| */ |
| } |
| |
| void btd_device_add_uuid(struct btd_device *device, const char *uuid) |
| { |
| GSList *uuid_list; |
| char *new_uuid; |
| |
| 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); |
| } |
| |
| static sdp_list_t *read_device_records(struct btd_device *device) |
| { |
| char local[18], peer[18]; |
| char filename[PATH_MAX]; |
| GKeyFile *key_file; |
| GError *gerr = NULL; |
| char **keys, **handle; |
| char *str; |
| sdp_list_t *recs = NULL; |
| sdp_record_t *rec; |
| |
| ba2str(btd_adapter_get_address(device->adapter), local); |
| ba2str(&device->bdaddr, peer); |
| |
| create_filename(filename, PATH_MAX, "/%s/cache/%s", local, peer); |
| |
| key_file = g_key_file_new(); |
| if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { |
| error("Unable to load key file from %s: (%s)", filename, |
| gerr->message); |
| g_error_free(gerr); |
| } |
| 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; |
| } |
| |
| void btd_device_set_record(struct btd_device *device, const char *uuid, |
| const char *record) |
| { |
| /* This API is only used for BR/EDR */ |
| struct bearer_state *state = &device->bredr_state; |
| struct browse_req *req; |
| sdp_list_t *recs = NULL; |
| sdp_record_t *rec; |
| |
| if (!record) |
| return; |
| |
| req = browse_request_new(device, BROWSE_SDP, NULL); |
| if (!req) |
| return; |
| |
| rec = record_from_string(record); |
| recs = sdp_list_append(recs, rec); |
| update_bredr_services(req, recs); |
| sdp_list_free(recs, NULL); |
| |
| device->svc_refreshed = true; |
| state->svc_resolved = true; |
| |
| device_probe_profiles(device, req->profiles_added); |
| |
| /* Propagate services changes */ |
| g_dbus_emit_property_changed(dbus_conn, req->device->path, |
| DEVICE_INTERFACE, "UUIDs"); |
| |
| device_svc_resolved(device, BROWSE_SDP, device->bdaddr_type, 0); |
| } |
| |
| const sdp_record_t *btd_device_get_record(struct btd_device *device, |
| const char *uuid) |
| { |
| /* Load records from storage if there is nothing in cache */ |
| if (!device->tmp_records) { |
| 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; |
| } |
| |
| static void remove_sirk_info(void *data, void *user_data) |
| { |
| struct sirk_info *info = data; |
| struct btd_device *device = user_data; |
| |
| btd_set_remove_device(info->set, 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; |
| } |
| |
| if (!queue_isempty(device->sirks)) |
| queue_foreach(device->sirks, remove_sirk_info, device); |
| |
| 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); |
| |
| if (device->appearance == value) |
| return; |
| |
| 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); |
| } |
| |
| void btd_device_set_pnpid(struct btd_device *device, uint16_t source, |
| uint16_t vendor, uint16_t product, uint16_t version) |
| { |
| if (device->vendor_src == source && device->version == version && |
| device->vendor == vendor && device->product == product) |
| return; |
| |
| device->vendor_src = source; |
| device->vendor = vendor; |
| device->product = product; |
| device->version = version; |
| |
| 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); |
| } |
| |
| bool btd_device_flags_enabled(struct btd_device *dev, uint32_t flags) |
| { |
| const char *ll_privacy = "15c0a148-c273-11ea-b3de-0242ac130004"; |
| |
| if (!dev) |
| return false; |
| |
| if (dev->current_flags & flags) |
| return true; |
| |
| /* For backward compatibility check for LL Privacy experimental UUID |
| * since that shall be equivalent to DEVICE_FLAG_ADDRESS_RESOLUTION on |
| * older kernels. |
| */ |
| if ((flags & DEVICE_FLAG_ADDRESS_RESOLUTION) && |
| btd_kernel_experimental_enabled(ll_privacy)) |
| return true; |
| |
| return false; |
| } |
| |
| uint32_t btd_device_get_current_flags(struct btd_device *dev) |
| { |
| return dev->current_flags; |
| } |
| |
| uint32_t btd_device_get_supported_flags(struct btd_device *dev) |
| { |
| return dev->supported_flags; |
| } |
| |
| void btd_device_set_pending_flags(struct btd_device *dev, uint32_t flags) |
| { |
| if (!dev) |
| return; |
| |
| dev->pending_flags = flags; |
| } |
| |
| uint32_t btd_device_get_pending_flags(struct btd_device *dev) |
| { |
| if (!dev) |
| return 0; |
| |
| return dev->pending_flags; |
| } |
| |
| /* This event is sent immediately after add device on all mgmt sockets. |
| * Afterwards, it is only sent to mgmt sockets other than the one which called |
| * set_device_flags. |
| */ |
| void btd_device_flags_changed(struct btd_device *dev, uint32_t supported_flags, |
| uint32_t current_flags) |
| { |
| const uint32_t changed_flags = dev->current_flags ^ current_flags; |
| bool flag_value; |
| |
| dev->supported_flags = supported_flags; |
| dev->current_flags = current_flags; |
| dev->pending_flags &= ~current_flags; |
| |
| if (!changed_flags) |
| return; |
| |
| if (changed_flags & DEVICE_FLAG_REMOTE_WAKEUP && dev->wake_support) { |
| flag_value = !!(current_flags & DEVICE_FLAG_REMOTE_WAKEUP); |
| dev->pending_wake_allowed = flag_value; |
| |
| /* If an override exists and doesn't match the current state, |
| * apply it. This logic will run after Add Device only and will |
| * enable wake for previously paired devices. |
| */ |
| if (dev->wake_override != WAKE_FLAG_DEFAULT) { |
| bool wake_allowed = |
| dev->wake_override == WAKE_FLAG_ENABLED; |
| if (flag_value != wake_allowed) |
| device_set_wake_allowed(dev, wake_allowed, -1U); |
| else |
| device_set_wake_allowed_complete(dev); |
| } else { |
| device_set_wake_allowed_complete(dev); |
| } |
| } |
| } |
| |
| 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); |
| } |
| |
| void btd_device_set_volume(struct btd_device *device, int8_t volume) |
| { |
| device->volume = volume; |
| } |
| |
| int8_t btd_device_get_volume(struct btd_device *device) |
| { |
| return device->volume; |
| } |
| |
| void btd_device_foreach_ad(struct btd_device *dev, bt_ad_func_t func, |
| void *data) |
| { |
| bt_ad_foreach_data(dev->ad, func, data); |
| } |
| |
| void btd_device_set_conn_param(struct btd_device *device, uint16_t min_interval, |
| uint16_t max_interval, uint16_t latency, |
| uint16_t timeout) |
| { |
| /* Attempt to load the new connection parameters, in case it is |
| * successful the MGMT_EV_NEW_CONN_PARAM will be generated which will |
| * then trigger btd_adapter_store_conn_param. |
| */ |
| btd_adapter_load_conn_param(device->adapter, &device->bdaddr, |
| device->bdaddr_type, min_interval, |
| max_interval, latency, |
| timeout); |
| } |
| |
| void btd_device_foreach_service_data(struct btd_device *dev, bt_ad_func_t func, |
| void *data) |
| { |
| bt_ad_foreach_service_data(dev->ad, func, data); |
| } |
| |
| |
| void btd_device_foreach_service(struct btd_device *dev, |
| btd_device_service_func_t func, |
| void *user_data) |
| { |
| GSList *l; |
| |
| for (l = dev->services; l; l = l->next) |
| func(l->data, user_data); |
| } |
| |
| void device_remove_pending_services(struct btd_device *dev, |
| uint8_t bdaddr_type) |
| { |
| GSList *l = dev->pending; |
| GSList *next; |
| struct btd_service *service; |
| struct btd_profile *profile; |
| |
| while (l) { |
| next = l->next; |
| service = l->data; |
| |
| profile = btd_service_get_profile(service); |
| if (!profile) |
| goto next; |
| |
| if (bdaddr_type == BDADDR_BREDR) { |
| if (profile->bearer == BTD_PROFILE_BEARER_LE) |
| goto next; |
| } else { |
| if (profile->bearer == BTD_PROFILE_BEARER_BREDR) |
| goto next; |
| } |
| |
| /* Matched: remove from pending list */ |
| dev->pending = g_slist_remove(dev->pending, service); |
| |
| next: |
| l = next; |
| } |
| } |