// 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 * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "lib/bluetooth.h" #include "lib/sdp.h" #include "lib/sdp_lib.h" #include "lib/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 "lib/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 "attrib-server.h" #include "eir.h" #ifdef TIZEN_FEATURE_BLUEZ_MODIFY #include "sdp-xml.h" #ifdef TIZEN_FEATURE_BLUEZ_BATTERY_WATCH #include #endif /* TIZEN_FEATURE_BLUEZ_BATTERY_WATCH */ #endif #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 GATT_PRIM_SVC_UUID_STR "2800" #define GATT_SND_SVC_UUID_STR "2801" #define GATT_INCLUDE_UUID_STR "2802" #define GATT_CHARAC_UUID_STR "2803" #ifdef TIZEN_FEATURE_BLUEZ_MODIFY #define DEV_MAX_MANUFACTURER_DATA_LEN 248 #endif 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; }; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY struct le_adv_report_info { uint8_t flags; char manufacturer_data[DEV_MAX_MANUFACTURER_DATA_LEN]; uint8_t manufacturer_data_len; }; #endif /* Per-bearer (LE or BR/EDR) device state */ struct bearer_state { bool paired; bool bonded; bool connected; bool svc_resolved; }; struct csrk_info { uint8_t key[16]; uint32_t counter; }; enum { WAKE_FLAG_DEFAULT = 0, WAKE_FLAG_ENABLED, WAKE_FLAG_DISABLED, }; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY typedef enum { DEV_PAIRED_NONE = 0, DEV_PAIRED_BREDR = 1, DEV_PAIRED_LE, DEV_PAIRED_BREDR_LE, } dev_paired_state; typedef enum { DEV_CONNECTED_NONE = 0, DEV_CONNECTED_BREDR = 1, DEV_CONNECTED_LE, DEV_CONNECTED_BREDR_LE, } dev_connected_state; struct trusted_profile_t { uint32_t pbap:2; uint32_t map:2; uint32_t sap:2; uint32_t hfp_hs:2; uint32_t a2dp:2; } __packed; #endif struct btd_device { int ref_count; bdaddr_t conn_bdaddr; uint8_t conn_bdaddr_type; bdaddr_t bdaddr; uint8_t bdaddr_type; char *path; bool bredr; bool 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 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; 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 */ 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; struct bearer_state bredr_state; struct bearer_state le_state; struct csrk_info *local_csrk; struct csrk_info *remote_csrk; uint8_t ltk_enc_size; sdp_list_t *tmp_records; time_t bredr_seen; time_t le_seen; gboolean trusted; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY struct trusted_profile_t trusted_profiles; #endif 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; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY bool legacy_pairing; char *manufacturer_data; int manufacturer_data_len; struct le_adv_report_info le_adv_data; int remote_feature_flags; guint attio_id; gboolean gatt_connected; uint16_t auth_payload_timeout; uint8_t disc_reason; uint8_t last_bdaddr_type; uint8_t auth_bdaddr_type; gboolean ipsp_connected; /* IPSP Connection state */ char if_name[16 + 1]; /* BT interface UP after IPSP connection */ uint8_t rpa_res_support; /* RPA Resolution capability of device */ uint16_t max_tx_octets; uint16_t max_tx_time; uint16_t max_rx_octets; uint16_t max_rx_time; bdaddr_t *rpa; DBusMessage *req_att_mtu; /* Attribute MTU request message */ uint8_t irk_val[16]; bool pending_conn_update; bool le_connectable; #endif }; static const uint16_t uuid_list[] = { L2CAP_UUID, PNP_INFO_SVCLASS_ID, PUBLIC_BROWSE_GROUP, 0 }; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY typedef enum { SHOW_AUTHORIZATION = 0x0, /* 0b00 */ SUPPORTED_BLOCKED = 0x1, /* 0b01 */ SUPPORTED_TRUSTED= 0x2, /* 0b10 */ } bt_profile_trusted_states; #define PBAP_SHIFT_OFFSET 0 #define MAP_SHIFT_OFFSET 2 #define SAP_SHIFT_OFFSET 4 #define HFP_HS_SHIFT_OFFSET 6 #define A2DP_SHIFT_OFFSET 8 #define PROFILE_SUPPORTED 0x3 /* This corresponds to binary 0b11*/ #endif #ifdef TIZEN_FEATURE_BLUEZ_MODIFY #define OTP_PSM 0x0025 /* OTP Client */ #define BT_OTC_SERVICE_NAME "org.otp.client" #define BT_OTC_OBJECT_PATH "/org/otp/client" #define BT_OTC_INTERFACE_NAME "org.otp.otc_channel" /* OTP Server */ #define BT_OTS_SERVICE_NAME "org.projectx.otp" #define BT_OTS_OBJECT_PATH "/org/projectx/otp" #define BT_OTS_INTERFACE_NAME "org.projectx.otp_service" typedef enum { BT_OTP_CLIENT_ROLE = 0x00, BT_OTP_SERVER_ROLE, } bt_otp_role_e; struct otc_conn_info { const char *dev_path; bt_otp_role_e role; GIOChannel *io; bool otc_connected; }; GSList *otc_connection_list = NULL; #endif #ifdef TIZEN_FEATURE_BLUEZ_MODIFY #define BT_L2CAP_LE_INTERFACE_NAME "org.bluez.l2cap_le" #define BTD_L2CAP_LE_PSM_MAX 0xFFFF #define UUID_LEN 37 typedef enum { BT_L2CAP_LE_CLIENT_ROLE = 0x00, BT_L2CAP_LE_SERVER_ROLE, } bt_l2cap_le_role_e; struct l2cap_le_conn_info { struct l2cap_le_profile_info *profile_info; GIOChannel *io; guint io_id; bool connected; const char *dev_path; int psm; guint auth_id; char *auth_uuid; }; struct l2cap_le_profile_info { char *name; char *owner; char *path; bt_l2cap_le_role_e role; int psm; BtIOSecLevel sec_level; bool authorize; guint id; struct l2cap_le_conn_info *server; GSList *conn; }; GSList *l2cap_le_socket_list = NULL; #endif 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; } 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 void update_technologies(GKeyFile *file, struct btd_device *dev) { const char *list[2]; size_t len = 0; 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); } 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); } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY static char *manufacturer_data2str(char *data, int size) { char str[DEV_MAX_MANUFACTURER_DATA_LEN * 3 + 1]; char tmp[5]; int i; str[0] = '\0'; for(i = 0; i < size; i++) { snprintf(tmp, sizeof(tmp), "%d ", data[i]); g_strlcat(str, tmp, sizeof(str)); } return g_strdup(str); } static void load_manufacturer_data_2digit(char *data, int len, char *buf) { int i; char **split; split = g_strsplit(data, " ", 0); for (i = 0; i < len; i++) { if (split[i] == NULL) break; buf[i] = (char)g_ascii_strtoull(split[i], NULL, 10); } g_strfreev(split); return; } #endif 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; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY gboolean svc_change_regd = false; #endif device->store_id = 0; ba2str(&device->bdaddr, device_addr); #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (device->rpa) ba2str(device->rpa, device_addr); #endif snprintf(filename, PATH_MAX, STORAGEDIR "/%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); } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (device->rpa_res_support) { g_key_file_set_integer(key_file, "General", "RPAResSupport", device->rpa_res_support); } else { g_key_file_remove_key(key_file, "General", "RPAResSupport", NULL); } #endif update_technologies(key_file, device); g_key_file_set_boolean(key_file, "General", "Trusted", device->trusted); #ifdef TIZEN_FEATURE_BLUEZ_MODIFY struct trusted_profile_t trust_profile = device->trusted_profiles; int trusted_profiles = (trust_profile.pbap << PBAP_SHIFT_OFFSET) | (trust_profile.map << MAP_SHIFT_OFFSET) | (trust_profile.sap << SAP_SHIFT_OFFSET) | (trust_profile.hfp_hs << HFP_HS_SHIFT_OFFSET) | (trust_profile.a2dp << A2DP_SHIFT_OFFSET); DBG("Storing TrustedProfiles %d", trusted_profiles); g_key_file_set_integer(key_file, "General", "TrustedProfiles", trusted_profiles); #endif g_key_file_set_boolean(key_file, "General", "Blocked", device->blocked); 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); } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (device->le_adv_data.flags) { g_key_file_set_integer(key_file, "General", "Flags", device->le_adv_data.flags); } else { g_key_file_remove_key(key_file, "General", "Flags", NULL); } if (device->manufacturer_data) { str = manufacturer_data2str(device->manufacturer_data, device->manufacturer_data_len); g_key_file_set_string(key_file, "General", "LegacyManufacturerData", str); g_free(str); g_key_file_set_integer(key_file, "General", "LegacyManufacturerDataLen", device->manufacturer_data_len); } else { g_key_file_remove_key(key_file, "General", "LegacyManufacturerData", NULL); g_key_file_remove_key(key_file, "General", "LegacyManufacturerDataLen", NULL); } if (device->rpa) { char irk_addr[18]; ba2str(&device->bdaddr, irk_addr); g_key_file_set_string(key_file, "General", "IdentityAddress", irk_addr); } else { g_key_file_remove_key(key_file, "General", "IdentityAddress", NULL); } #endif 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); } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY svc_change_regd = bt_att_get_svc_changed_indication_registered(device->att); g_key_file_set_boolean(key_file, "Att", "SvcChangeRegd", svc_change_regd); #endif if (device->local_csrk) store_csrk(device->local_csrk, key_file, "LocalSignatureKey"); if (device->remote_csrk) store_csrk(device->remote_csrk, key_file, "RemoteSignatureKey"); 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; } static bool device_address_is_private(struct btd_device *dev) { if (dev->bdaddr_type != BDADDR_LE_RANDOM) return false; switch (dev->bdaddr.b[5] >> 6) { case 0x00: /* Private non-resolvable */ case 0x01: /* Private resolvable */ return true; default: return false; } } static void store_device_info(struct btd_device *device) { if (device->temporary || device->store_id > 0) return; if (device_address_is_private(device)) { 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); #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (dev->rpa) ba2str(dev->rpa, d_addr); #endif snprintf(filename, PATH_MAX, STORAGEDIR "/%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_error_free(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_error_free(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); snprintf(filename, PATH_MAX, STORAGEDIR "/%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_error_free(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) { #ifdef TIZEN_FEATURE_BLUEZ_MODIFY DBG(""); #endif 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); #ifdef TIZEN_FEATURE_BLUEZ_MODIFY g_free(device->rpa); #endif g_free(device->local_csrk); g_free(device->remote_csrk); g_free(device->path); g_free(device->alias); free(device->modalias); g_free(device); } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY void device_set_remote_feature_flag(struct btd_device *device, int flags) { device->remote_feature_flags = flags; } gboolean device_is_bredrle(struct btd_device *device) { return (device->remote_feature_flags & (EIR_CONTROLLER | EIR_SIM_HOST)); } #endif 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; } gboolean device_is_trusted(struct btd_device *device) { return device->trusted; } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY gboolean device_is_profile_trusted(struct btd_device *device, const char *uuid) { if (g_strcmp0(uuid, OBEX_PSE_UUID) == 0) { if (device->trusted_profiles.pbap == SUPPORTED_TRUSTED) return TRUE; } else if (g_strcmp0(uuid, OBEX_MAS_UUID) == 0) { if (device->trusted_profiles.map == SUPPORTED_TRUSTED) return TRUE; } else if (g_strcmp0(uuid, SAP_UUID) == 0) { if (device->trusted_profiles.sap == SUPPORTED_TRUSTED) return TRUE; } else if (g_strcmp0(uuid, HFP_HS_UUID) == 0) { if (device->trusted_profiles.hfp_hs == SUPPORTED_TRUSTED) return TRUE; } else if (g_strcmp0(uuid, A2DP_SINK_UUID) == 0) { if (device->trusted_profiles.a2dp == SUPPORTED_TRUSTED) return TRUE; } return FALSE; } gboolean device_is_profile_blocked(struct btd_device *device, const char *uuid) { if (g_strcmp0(uuid, OBEX_PSE_UUID) == 0) { if (device->trusted_profiles.pbap == SUPPORTED_BLOCKED) return TRUE; } else if (g_strcmp0(uuid, OBEX_MAS_UUID) == 0) { if (device->trusted_profiles.map == SUPPORTED_BLOCKED) return TRUE; } else if (g_strcmp0(uuid, SAP_UUID) == 0) { if (device->trusted_profiles.sap == SUPPORTED_BLOCKED) return TRUE; } else if (g_strcmp0(uuid, HFP_HS_UUID) == 0) { if (device->trusted_profiles.hfp_hs == SUPPORTED_BLOCKED) return TRUE; } else if (g_strcmp0(uuid, A2DP_SINK_UUID) == 0) { if (device->trusted_profiles.a2dp == SUPPORTED_BLOCKED) return TRUE; } return FALSE; } #endif 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; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (device->rpa) ba2str(device->rpa, dstaddr); else #endif 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); #ifndef TIZEN_FEATURE_BLUEZ_MODIFY g_strdelimit(dstaddr, ":", '-'); #endif ptr = dstaddr; } dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ptr); return TRUE; } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY static gboolean dev_property_get_alias_set(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; dbus_bool_t val; if (device->alias != NULL) val = TRUE; else val = FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val); return TRUE; } #endif static void set_alias(GDBusPendingPropertySet id, const char *alias, void *data) { struct btd_device *device = data; /* No change */ if ((device->alias == NULL && g_str_equal(alias, "")) || g_strcmp0(device->alias, alias) == 0) { g_dbus_pending_property_success(id); return; } g_free(device->alias); device->alias = g_str_equal(alias, "") ? NULL : g_strdup(alias); store_device_info(device); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Alias"); g_dbus_pending_property_success(id); } static void dev_property_set_alias(const GDBusPropertyTable *property, DBusMessageIter *value, GDBusPendingPropertySet id, void *data) { const char *alias; if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_STRING) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); return; } dbus_message_iter_get_basic(value, &alias); set_alias(id, alias, data); } static gboolean dev_property_exists_class(const GDBusPropertyTable *property, void *data) { struct btd_device *device = data; return device->class != 0; } static gboolean dev_property_get_class(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; if (device->class == 0) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &device->class); return TRUE; } static gboolean get_appearance(const GDBusPropertyTable *property, void *data, uint16_t *appearance) { struct btd_device *device = data; if (dev_property_exists_class(property, data)) return FALSE; if (device->appearance) { *appearance = device->appearance; return TRUE; } return FALSE; } static gboolean dev_property_exists_appearance( const GDBusPropertyTable *property, void *data) { uint16_t appearance; return get_appearance(property, data, &appearance); } static gboolean dev_property_get_appearance(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { uint16_t appearance; if (!get_appearance(property, data, &appearance)) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &appearance); return TRUE; } static const char *get_icon(const GDBusPropertyTable *property, void *data) { struct btd_device *device = data; const char *icon = NULL; uint16_t appearance; if (device->class != 0) icon = class_to_icon(device->class); else if (get_appearance(property, data, &appearance)) icon = gap_appearance_to_icon(appearance); return icon; } static gboolean dev_property_exists_icon( const GDBusPropertyTable *property, void *data) { return get_icon(property, data) != NULL; } static gboolean dev_property_get_icon(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { const char *icon; icon = get_icon(property, data); if (icon == NULL) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &icon); return TRUE; } static gboolean dev_property_get_paired(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *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_legacy(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; dbus_bool_t val = device->legacy; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val); return TRUE; } static gboolean dev_property_get_rssi(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *dev = data; dbus_int16_t val = dev->rssi; dbus_message_iter_append_basic(iter, DBUS_TYPE_INT16, &val); return TRUE; } static gboolean dev_property_exists_rssi(const GDBusPropertyTable *property, void *data) { struct btd_device *dev = data; if (dev->rssi == 0) return FALSE; return TRUE; } static gboolean dev_property_get_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 = 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); } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY static gboolean dev_property_get_trusted_profiles(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; uint32_t pbap = device->trusted_profiles.pbap; uint32_t map = device->trusted_profiles.map; uint32_t sap = device->trusted_profiles.sap; uint32_t hfp_hs = device->trusted_profiles.hfp_hs; uint32_t a2dp = device->trusted_profiles.a2dp; unsigned int val = (pbap << PBAP_SHIFT_OFFSET) | (map << MAP_SHIFT_OFFSET) | (sap << SAP_SHIFT_OFFSET) | (hfp_hs << HFP_HS_SHIFT_OFFSET) | (a2dp << A2DP_SHIFT_OFFSET); dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &val); return TRUE; } #endif 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); } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY static uint8_t device_get_connected_state(struct btd_device *device) { if (device->bredr_state.connected && device->le_state.connected) return DEV_CONNECTED_BREDR_LE; else if (device->bredr_state.connected) return DEV_CONNECTED_BREDR; else if (device->le_state.connected) return DEV_CONNECTED_LE; else return DEV_CONNECTED_NONE; } static gboolean dev_property_get_payload(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *dev = data; dbus_uint16_t payload_timeout = dev->auth_payload_timeout; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &payload_timeout); return TRUE; } static gboolean dev_property_get_last_addr_type(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *dev = data; uint8_t last_addr_type = dev->last_bdaddr_type; dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &last_addr_type); return TRUE; } static gboolean dev_property_get_att_mtu(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; dbus_uint16_t mtu = bt_gatt_client_get_mtu(device->client); dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &mtu); return TRUE; } static gboolean dev_property_get_gatt_connected(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; dbus_bool_t gatt_connected; if (device->gatt_connected) gatt_connected = TRUE; else gatt_connected = FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &gatt_connected); return TRUE; } static gboolean dev_property_get_ipsp_conn_state(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *dev = data; dbus_bool_t ipsp_connected; if (dev->ipsp_connected) ipsp_connected = TRUE; else ipsp_connected = FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &ipsp_connected); return TRUE; } static gboolean dev_property_get_ipsp_conn_bt_iface_name(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *dev = data; char *ptr = g_strdup(dev->if_name); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, (const char **)&ptr); g_free(ptr); return TRUE; } #endif static gboolean dev_property_get_connected(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *dev = data; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY uint8_t connected = device_get_connected_state(dev); dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &connected); #else 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); #endif 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; } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY static gboolean property_get_manufacturer_data_len(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct btd_device *device = user_data; dbus_uint16_t val = device->manufacturer_data_len; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &val); return TRUE; } static gboolean property_get_manufacturer_data(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct btd_device *device = user_data; char str[DEV_MAX_MANUFACTURER_DATA_LEN] = {0}; DBusMessageIter array; memset(str, 0, DEV_MAX_MANUFACTURER_DATA_LEN); if (device->manufacturer_data_len) memcpy(str, device->manufacturer_data, device->manufacturer_data_len); 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, &device->manufacturer_data, device->manufacturer_data_len); dbus_message_iter_close_container(iter, &array); return TRUE; } gboolean device_get_gatt_connected(const struct btd_device *device) { return device->gatt_connected; } #endif 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); } 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 wake configuration has not been made yet, set the initial * configuration. */ if (device->wake_override == WAKE_FLAG_DEFAULT) { device_set_wake_override(device, wake_support); device_set_wake_allowed(device, wake_support, -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; device->current_flags |= DEVICE_FLAG_REMOTE_WAKEUP; } else { device->wake_override = WAKE_FLAG_DISABLED; device->current_flags &= ~DEVICE_FLAG_REMOTE_WAKEUP; } } 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)); return; } if (length < sizeof(*rp)) { error("Too small Set Device Flags complete event: %d", length); return; } device_set_wake_allowed_complete(dev); } 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 (wake_allowed == device->pending_wake_allowed) return; device->wake_id = id; device->pending_wake_allowed = wake_allowed; flags = device->current_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 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; } /* 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; } dbus_message_iter_get_basic(value, &b); 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 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_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) { DBusMessage *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; } 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; while (device->watches) { struct btd_disconnect_data *data = device->watches->data; if (data->watch) /* temporary is set if device is going to be removed */ data->watch(device, device->temporary, data->user_data); /* Check if the watch has been removed by callback function */ if (!g_slist_find(device->watches, data)) continue; device->watches = g_slist_remove(device->watches, data); g_free(data); } if (!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; } void device_set_ltk_enc_size(struct btd_device *device, uint8_t enc_size) { device->ltk_enc_size = enc_size; bt_att_set_enc_key_size(device->att, device->ltk_enc_size); } static void device_set_auto_connect(struct btd_device *device, gboolean enable) { char addr[18]; 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; } /* 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; } 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_failed(dev->connect, btd_error_bredr_conn_from_errno(err))); } else { #ifdef TIZEN_FEATURE_BLUEZ_MODIFY /* SDP is not required for Samsung TV Power on */ if (g_strcmp0(profile->name, "hid-device") == 0) { DBG("Skip SDP discovery."); } else { #endif /* Start passive SDP discovery to update known services */ if (dev->bredr && !dev->svc_refreshed && dev->refresh_discovery) device_browse_sdp(dev, NULL); #ifdef TIZEN_FEATURE_BLUEZ_MODIFY } #endif g_dbus_send_reply(dbus_conn, dev->connect, DBUS_TYPE_INVALID); } dbus_message_unref(dev->connect); dev->connect = NULL; } void device_add_eir_uuids(struct btd_device *dev, GSList *uuids) { GSList *l; bool added = false; if (dev->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 = true; dev->eir_uuids = g_slist_append(dev->eir_uuids, g_strdup(str)); } if (added) g_dbus_emit_property_changed(dbus_conn, dev->path, DEVICE_INTERFACE, "UUIDs"); } static 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; 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; 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; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY struct btd_service *s = NULL; #endif 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; #ifndef TIZEN_FEATURE_BLUEZ_MODIFY if (strcasecmp(uuid, p->remote_uuid) == 0) return service; #else if (strcasecmp(uuid, p->remote_uuid) == 0) { s = service; if (ext_profile_is_registered_as_client_role(p) == TRUE) { return service; } else { continue; } } #endif } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (s) return s; #endif return NULL; } static int service_prio_cmp(gconstpointer a, gconstpointer b) { struct btd_profile *p1 = btd_service_get_profile(a); struct btd_profile *p2 = btd_service_get_profile(b); return p2->priority - p1->priority; } 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 GSList *create_pending_list(struct btd_device *dev, const char *uuid) { struct btd_service *service; struct btd_profile *p; GSList *l; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY bool hs_hf_verify = FALSE; #endif 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); #ifdef TIZEN_FEATURE_BLUEZ_MODIFY else if (g_strcmp0(uuid, HFP_HS_UUID) == 0) { DBG("HFP service not found check for HSP service"); service = find_connectable_service(dev, HSP_HS_UUID); if (service) return g_slist_prepend(dev->pending, service); } else if (g_strcmp0(uuid, HID_UUID) == 0) { DBG("HID service not found, add HID service"); btd_device_add_uuid(dev, uuid); service = find_connectable_service(dev, HID_UUID); if (service) return g_slist_prepend(dev->pending, service); } #endif return dev->pending; } for (l = dev->services; l != NULL; l = g_slist_next(l)) { service = l->data; p = btd_service_get_profile(service); #ifdef TIZEN_FEATURE_BLUEZ_MODIFY DBG("profile uuid %s", p->remote_uuid); if (g_strcmp0(p->remote_uuid, HSP_HS_UUID) == 0) { DBG("HSP service is found check for HFP service"); struct btd_service *service; struct btd_profile *p; GSList *h; for (h = dev->services; h != NULL; h = g_slist_next(h)) { service = h->data; p = btd_service_get_profile(service); if (g_strcmp0(p->remote_uuid, HFP_HS_UUID) == 0) { DBG("HFP found,ignore HSP "); hs_hf_verify = TRUE; break; } } if (hs_hf_verify) continue; } #endif 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_insert_sorted(dev->pending, service, service_prio_cmp); } return dev->pending; } int btd_device_connect_services(struct btd_device *dev, GSList *services) { GSList *l; if (dev->pending || dev->connect || dev->browse) return -EBUSY; if (!btd_adapter_get_powered(dev->adapter)) return -ENETDOWN; if (!dev->bredr_state.svc_resolved) return -ENOENT; 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); } static DBusMessage *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)); #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (dev->pending || dev->connect) return btd_error_in_progress_str(msg, ERR_BREDR_CONN_BUSY); #else if (dev->pending || dev->connect || dev->browse) return btd_error_in_progress_str(msg, ERR_BREDR_CONN_BUSY); #endif 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) { #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (!uuid && dbus_message_is_method_call(msg, DEVICE_INTERFACE, "Connect") && find_service_with_state(dev->services, BTD_SERVICE_STATE_CONNECTED)) { #else if (dbus_message_is_method_call(msg, DEVICE_INTERFACE, "Connect") && find_service_with_state(dev->services, BTD_SERVICE_STATE_CONNECTED)) { #endif return dbus_message_new_method_return(msg); } else { return btd_error_not_available_str(msg, ERR_BREDR_CONN_PROFILE_UNAVAILABLE); } } goto resolve_services; } err = connect_next(dev); if (err < 0) { if (err == -EALREADY) #ifdef TIZEN_FEATURE_BLUEZ_MODIFY return btd_error_already_connected(msg); #else return dbus_message_new_method_return(msg); #endif return btd_error_failed(msg, btd_error_bredr_conn_from_errno(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; } #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); /* Prefer bonded bearer in case only one is bonded */ if (dev->bredr_state.bonded && !dev->le_state.bonded ) return BDADDR_BREDR; else if (!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_seen) { bredr_last = current - dev->bredr_seen; if (bredr_last > SEEN_TRESHHOLD) bredr_last = NVAL_TIME; } if (dev->le_seen) { le_last = current - dev->le_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 DBusMessage *dev_connect(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_device *dev = user_data; uint8_t bdaddr_type; 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); if (bdaddr_type != BDADDR_BREDR) { int err; 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 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); reply = 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); #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if ((service == NULL) && (g_strcmp0(uuid, HFP_HS_UUID) == 0)) { DBG("HFP service is not found check for HSP service"); service = find_connectable_service(dev, HSP_HS_UUID); } #endif 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; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (dev->disconnect) #endif 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)); } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY static DBusMessage *disconnect_ext_profile(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_device *dev = user_data; struct btd_profile *profile; struct btd_service *service; const char *sender, *path; GSList *l; int err; sender = dbus_message_get_sender(msg); DBG("sender %s", sender); if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) return btd_error_invalid_args(msg); profile = btd_profile_find_ext(sender, path); if (!profile) return btd_error_invalid_args(msg); l = find_service_with_profile(dev->services, profile); if (!l) return btd_error_invalid_args(msg); service = l->data; if (dev->disconnect) return btd_error_in_progress(msg); dev->disconnect = dbus_message_ref(msg); err = btd_service_disconnect(service); if (err == 0) return NULL; if (dev->disconnect) dbus_message_unref(dev->disconnect); dev->disconnect = NULL; if (err == -ENOTSUP) return btd_error_not_supported(msg); return btd_error_failed(msg, strerror(-err)); } #endif 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); #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (device->rpa) ba2str(device->rpa, dst_addr); #endif snprintf(filename, PATH_MAX, STORAGEDIR "/%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); } struct gatt_saver { struct btd_device *device; uint16_t ext_props; GKeyFile *key_file; }; static void db_hash_read_value_cb(struct gatt_db_attribute *attrib, int err, const uint8_t *value, size_t length, void *user_data) { const uint8_t **hash = user_data; if (err || (length != 16)) return; *hash = value; } static void store_desc(struct gatt_db_attribute *attr, void *user_data) { struct gatt_saver *saver = user_data; GKeyFile *key_file = saver->key_file; char handle[6], value[100], uuid_str[MAX_LEN_UUID_STR]; const bt_uuid_t *uuid; bt_uuid_t ext_uuid; uint16_t handle_num; handle_num = gatt_db_attribute_get_handle(attr); sprintf(handle, "%04hx", handle_num); uuid = gatt_db_attribute_get_type(attr); bt_uuid_to_string(uuid, uuid_str, sizeof(uuid_str)); bt_uuid16_create(&ext_uuid, GATT_CHARAC_EXT_PROPER_UUID); if (!bt_uuid_cmp(uuid, &ext_uuid) && saver->ext_props) sprintf(value, "%04hx:%s", saver->ext_props, uuid_str); else sprintf(value, "%s", uuid_str); g_key_file_set_string(key_file, "Attributes", handle, value); } static void store_chrc(struct gatt_db_attribute *attr, void *user_data) { struct gatt_saver *saver = user_data; GKeyFile *key_file = saver->key_file; char handle[6], value[100], uuid_str[MAX_LEN_UUID_STR]; uint16_t handle_num, value_handle; uint8_t properties; bt_uuid_t uuid, hash_uuid; if (!gatt_db_attribute_get_char_data(attr, &handle_num, &value_handle, &properties, &saver->ext_props, &uuid)) { warn("Error storing characteristic - can't get data"); return; } sprintf(handle, "%04hx", handle_num); bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); /* Store Database Hash value if available */ bt_uuid16_create(&hash_uuid, GATT_CHARAC_DB_HASH); if (!bt_uuid_cmp(&uuid, &hash_uuid)) { const uint8_t *hash = NULL; attr = gatt_db_get_attribute(saver->device->db, value_handle); gatt_db_attribute_read(attr, 0, BT_ATT_OP_READ_REQ, NULL, db_hash_read_value_cb, &hash); if (hash) sprintf(value, GATT_CHARAC_UUID_STR ":%04hx:%02hhx:" "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx" "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx" "%02hhx%02hhx:%s", value_handle, properties, hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7], hash[8], hash[9], hash[10], hash[11], hash[12], hash[13], hash[14], hash[15], uuid_str); else sprintf(value, GATT_CHARAC_UUID_STR ":%04hx:%02hhx:%s", value_handle, properties, uuid_str); } else sprintf(value, GATT_CHARAC_UUID_STR ":%04hx:%02hhx:%s", value_handle, properties, uuid_str); g_key_file_set_string(key_file, "Attributes", handle, value); gatt_db_service_foreach_desc(attr, store_desc, saver); } static void store_incl(struct gatt_db_attribute *attr, void *user_data) { struct gatt_saver *saver = user_data; GKeyFile *key_file = saver->key_file; struct gatt_db_attribute *service; char handle[6], value[100], uuid_str[MAX_LEN_UUID_STR]; uint16_t handle_num, start, end; bt_uuid_t uuid; if (!gatt_db_attribute_get_incl_data(attr, &handle_num, &start, &end)) { warn("Error storing included service - can't get data"); return; } service = gatt_db_get_attribute(saver->device->db, start); if (!service) { warn("Error storing included service - can't find it"); return; } sprintf(handle, "%04hx", handle_num); gatt_db_attribute_get_service_uuid(service, &uuid); bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); sprintf(value, GATT_INCLUDE_UUID_STR ":%04hx:%04hx:%s", start, end, uuid_str); g_key_file_set_string(key_file, "Attributes", handle, value); } static void store_service(struct gatt_db_attribute *attr, void *user_data) { struct gatt_saver *saver = user_data; GKeyFile *key_file = saver->key_file; char uuid_str[MAX_LEN_UUID_STR], handle[6], value[256]; uint16_t start, end; bt_uuid_t uuid; bool primary; char *type; if (!gatt_db_attribute_get_service_data(attr, &start, &end, &primary, &uuid)) { warn("Error storing service - can't get data"); return; } sprintf(handle, "%04hx", start); bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); if (primary) type = GATT_PRIM_SVC_UUID_STR; else type = GATT_SND_SVC_UUID_STR; sprintf(value, "%s:%04hx:%s", type, end, uuid_str); g_key_file_set_string(key_file, "Attributes", handle, value); gatt_db_service_foreach_incl(attr, store_incl, saver); gatt_db_service_foreach_char(attr, store_chrc, saver); } static void store_gatt_db(struct btd_device *device) { char filename[PATH_MAX]; char dst_addr[18]; GKeyFile *key_file; GError *gerr = NULL; char *data; gsize length = 0; struct gatt_saver saver; 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); snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", btd_adapter_get_storage_dir(device->adapter), dst_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); } /* Remove current attributes since it might have changed */ g_key_file_remove_group(key_file, "Attributes", NULL); saver.key_file = key_file; saver.device = device; gatt_db_foreach_service(device->db, NULL, store_service, &saver); data = g_key_file_to_data(key_file, &length, NULL); 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); } 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) { 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; } 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; } reply = btd_error_failed(req->msg, bdaddr_type == BDADDR_BREDR ? btd_error_bredr_conn_from_errno(err) : btd_error_le_conn_from_errno(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; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY DBG("%s bdaddr_type %d err %d", dev->path, bdaddr_type, err); #else DBG("%s err %d", dev->path, err); #endif #ifndef TIZEN_FEATURE_BLUEZ_MODIFY state->svc_resolved = true; #else if (err == 0) state->svc_resolved = true; #endif /* 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) { 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); } #ifndef TIZEN_FEATURE_BLUEZ_MODIFY if (req) browse_request_complete(req, browse_type, bdaddr_type, err); #endif 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); } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (!req) return; /* If bdaddr_type is LE but req is for SDP, don't complete browse req. */ if (bdaddr_type != BDADDR_BREDR && req->search_uuid) { DBG("Discover comp. is for LE but browse req. is for SDP."); return; } browse_request_complete(req, browse_type, bdaddr_type, err); #endif 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; #ifndef TIZEN_FEATURE_BLUEZ_MODIFY struct bearer_state *state; #endif uint8_t bdaddr_type; const char *sender; struct agent *agent; struct bonding_req *bonding; uint8_t io_cap; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY uint8_t conn_type; bool connect_le = FALSE; uint8_t link_type = DEV_CONNECTED_NONE; #endif int err; btd_device_set_temporary(device, false); #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (dbus_message_get_args(msg, NULL, DBUS_TYPE_BYTE, &conn_type, DBUS_TYPE_INVALID) == FALSE) #else if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_INVALID)) #endif return btd_error_invalid_args(msg); if (device->bonding) return btd_error_in_progress(msg); #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (conn_type == DEV_CONN_DEFAULT) { link_type = device_get_connected_state(device); if (link_type == DEV_CONNECTED_BREDR) { if (device_is_bonded(device, DEV_CONN_BREDR)) return btd_error_already_exists(msg); conn_type = DEV_CONN_BREDR; } else if (link_type == DEV_CONNECTED_LE) { if (device_is_bonded(device, DEV_CONN_LE)) return btd_error_already_exists(msg); conn_type = DEV_CONN_LE; } else { if (device_is_bonded(device, DEV_CONN_BREDR)) return btd_error_already_exists(msg); else if (device_is_bonded(device, DEV_CONN_LE)) return btd_error_already_exists(msg); if (device->bredr) conn_type = DEV_CONN_BREDR; else if (device->le) conn_type = DEV_CONN_LE; else conn_type = DEV_CONN_BREDR; } } else { if (device_is_bonded(device, conn_type)) return btd_error_already_exists(msg); } bdaddr_type = device->bdaddr_type; #else 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); state = get_state(device, bdaddr_type); if (state->bonded) return btd_error_already_exists(msg); #endif #ifdef TIZEN_FEATURE_BLUEZ_MODIFY DBG("conn_type %d, link_type %d, bdaddr_type %d, device->bredr %d", conn_type, link_type, bdaddr_type, device->bredr); if (conn_type == DEV_CONN_LE && (device_is_bredrle(device) || bdaddr_type != BDADDR_BREDR)) { DBG("LE Connect request"); connect_le = TRUE; } #endif sender = dbus_message_get_sender(msg); agent = agent_get(sender); if (agent) io_cap = agent_get_io_capability(agent); else io_cap = IO_CAPABILITY_NOINPUTNOOUTPUT; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if ((conn_type == DEV_CONN_LE && bdaddr_type != BDADDR_BREDR) || connect_le) bonding = bonding_request_new(msg, device, bdaddr_type, agent); else bonding = bonding_request_new(msg, device, BDADDR_BREDR, agent); #else bonding = bonding_request_new(msg, device, bdaddr_type, agent); #endif 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) */ #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (((conn_type == DEV_CONN_LE && bdaddr_type != BDADDR_BREDR) || (connect_le)) && !device->le_state.connected) err = device_connect_le(device); else if (connect_le) /* Send bonding request if LE is already connected*/ err = adapter_create_bonding(adapter, &device->bdaddr, bdaddr_type, io_cap); else err = adapter_create_bonding(adapter, &device->bdaddr, BDADDR_BREDR, io_cap); #else if (bdaddr_type != BDADDR_BREDR) { if (!state->connected && btd_le_connect_before_pairing()) err = device_connect_le(device); else err = adapter_create_bonding(adapter, &device->bdaddr, device->bdaddr_type, io_cap); } else { err = adapter_create_bonding(adapter, &device->bdaddr, BDADDR_BREDR, io_cap); } #endif 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: #ifndef TIZEN_FEATURE_BLUEZ_MODIFY case MGMT_STATUS_DISCONNECTED: #endif 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"); } } static void device_cancel_bonding(struct btd_device *device, uint8_t status) { struct bonding_req *bonding = device->bonding; DBusMessage *reply; char addr[18]; if (!bonding) return; ba2str(&device->bdaddr, addr); DBG("Canceling bonding request for %s", addr); if (device->authr) device_cancel_authentication(device, FALSE); reply = new_authentication_return(bonding->msg, status); g_dbus_send_message(dbus_conn, reply); bonding_request_cancel(bonding); bonding_request_free(bonding); } static DBusMessage *cancel_pairing(DBusConnection *conn, DBusMessage *msg, void *data) { struct btd_device *device = data; struct bonding_req *req = device->bonding; DBG(""); if (!req) return btd_error_does_not_exist(msg); device_cancel_bonding(device, MGMT_STATUS_CANCELLED); return dbus_message_new_method_return(msg); } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY static DBusMessage *discover_services(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_device *device = user_data; const char *pattern; int err; if (device->browse) return btd_error_in_progress(msg); if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &pattern, DBUS_TYPE_INVALID) == FALSE) return btd_error_invalid_args(msg); err = device_browse_sdp(device, msg); if (err < 0) goto fail; return NULL; fail: return btd_error_failed(msg, "Unable to search the SDP services"); } static const char *browse_request_get_requestor(struct browse_req *req) { if (!req->msg) return NULL; return dbus_message_get_sender(req->msg); } static void iter_append_record(DBusMessageIter *dict, uint32_t handle, const char *record) { DBusMessageIter entry; dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_UINT32, &handle); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &record); dbus_message_iter_close_container(dict, &entry); } static void discover_services_reply(struct browse_req *req, int err, sdp_list_t *recs) { DBusMessage *reply; DBusMessageIter iter, dict; sdp_list_t *seq; if (!req->msg) return; if (err) { const char *err_if; if (err == -EHOSTDOWN) err_if = ERROR_INTERFACE ".ConnectionAttemptFailed"; else err_if = ERROR_INTERFACE ".Failed"; reply = dbus_message_new_error(req->msg, err_if, strerror(-err)); g_dbus_send_message(dbus_conn, reply); return; } reply = dbus_message_new_method_return(req->msg); if (!reply) return; dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_UINT32_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); for (seq = recs; seq; seq = seq->next) { sdp_record_t *rec = (sdp_record_t *) seq->data; GString *result; if (!rec) break; result = g_string_new(NULL); convert_sdp_record_to_xml(rec, result, (void *) g_string_append); if (result->len) { if (!g_utf8_validate(result->str, -1, NULL)) { gchar *ptr = NULL; DBG("UTF8 invalid string, make valid"); ptr = g_utf8_make_valid(result->str, -1); iter_append_record(&dict, rec->handle, ptr); g_free(ptr); } else { iter_append_record(&dict, rec->handle, result->str); } } g_string_free(result, TRUE); } dbus_message_iter_close_container(&iter, &dict); g_dbus_send_message(dbus_conn, reply); } static DBusMessage *cancel_discover(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_device *device = user_data; const char *sender = dbus_message_get_sender(msg); const char *requestor; if (!device->browse) return btd_error_does_not_exist(msg); if (!dbus_message_is_method_call(device->browse->msg, DEVICE_INTERFACE, "DiscoverServices")) return btd_error_not_authorized(msg); requestor = browse_request_get_requestor(device->browse); /* only the discover requestor can cancel the inquiry process */ if (!requestor || !g_str_equal(requestor, sender)) return btd_error_not_authorized(msg); discover_services_reply(device->browse, -ECANCELED, NULL); if (device->browse) browse_request_cancel(device->browse); return dbus_message_new_method_return(msg); } void device_set_gatt_connected(struct btd_device *device, gboolean connected) { if (device == NULL) { error("device is NULL"); return; } if (device->gatt_connected == connected) { error("same state change for gatt_connected : %d", connected); return; } DBG("GattConnected %d", connected); device->gatt_connected = connected; g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "GattConnected"); } static DBusMessage *connect_le(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_device *dev = user_data; dbus_bool_t auto_connect = FALSE; int err; if (!dev->le) { /* * If a LE connection is requested without device discovery, * we try to get device object. Here, technology can be updated * if there is matched device object. Or, a new device object * will be created. */ dev = btd_adapter_get_device(dev->adapter, &dev->bdaddr, BDADDR_LE_PUBLIC); if (dev == NULL) { error("Unable to get device object"); return btd_error_not_supported(msg); } } if (dev->le_state.connected) return dbus_message_new_method_return(msg); if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_BOOLEAN, &auto_connect, DBUS_TYPE_INVALID)) return btd_error_invalid_args(msg); btd_device_set_temporary(dev, false); if (auto_connect) { DBG("Start BLE auto connection"); dev->disable_auto_connect = FALSE; device_set_auto_connect(dev, TRUE); return dbus_message_new_method_return(msg); } err = device_connect_le(dev); if (err < 0) return btd_error_failed(msg, strerror(-err)); dev->connect = dbus_message_ref(msg); return NULL; } static DBusMessage *disconnect_le(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_device *dev = user_data; if (!dev->le) return btd_error_not_supported(msg); /* * Disable connections through passive sccanning */ if (dev->auto_connect) { DBG("Stop BLE auto connection"); dev->disable_auto_connect = FALSE; device_set_auto_connect(dev, FALSE); if (!dev->le_state.connected) { g_dbus_send_reply(dbus_conn, msg, DBUS_TYPE_INVALID); return NULL; } } device_request_disconnect(dev, msg); return NULL; } static DBusMessage *connect_ipsp(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_device *device = user_data; DBG("bdaddr_type %d", device->bdaddr_type); if (device->bdaddr_type == BDADDR_BREDR) { if(device->le) device->bdaddr_type = BDADDR_LE_PUBLIC; else { device = btd_adapter_get_device(device->adapter, &device->bdaddr, BDADDR_LE_PUBLIC); if (device == NULL) return btd_error_no_such_adapter(msg); } } if (device->ipsp_connected) return btd_error_already_connected(msg); /* Initiate Connection for 6Lowan*/ if (btd_adapter_connect_ipsp(device->adapter, &device->bdaddr, device->bdaddr_type) != 0) return btd_error_failed(msg, "ConnectFailed"); return dbus_message_new_method_return(msg);; } static DBusMessage *disconnect_ipsp(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_device *device = user_data; DBG("bdaddr_type %d", device->bdaddr_type); if (device->bdaddr_type == BDADDR_BREDR) return btd_error_not_supported(msg); if (!device->ipsp_connected) return btd_error_not_connected(msg); /* Disconnect the 6Lowpan connection */ if (btd_adapter_disconnect_ipsp(device->adapter, &device->bdaddr, device->bdaddr_type) != 0) return btd_error_failed(msg, "DisconnectFailed"); /* TODO: Handle disconnection of GATT connection, If the connection * is established as part of IPSP connection. */ return dbus_message_new_method_return(msg);; } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY static GSList *find_otc_conn_info(GSList *list, const char *path) { GSList *l; for (l = list; l != NULL; l = g_slist_next(l)) { struct otc_conn_info *info = l->data; if (g_strcmp0(info->dev_path, path) == 0) return l; } return NULL; } static gboolean otc_disconnected_cb(GIOChannel *chan, GIOCondition cond, gpointer user_data) { struct otc_conn_info *conn_info; const char *dev_path = (const char *) user_data; GSList *l; DBG("OTC Disconnected"); l = find_otc_conn_info(otc_connection_list, dev_path); if (!l) return FALSE; conn_info = l->data; conn_info->otc_connected = false; g_dbus_emit_signal(dbus_conn, dev_path, DEVICE_INTERFACE, "OtcDisconnected", DBUS_TYPE_INVALID); if (conn_info->io) { g_io_channel_shutdown(conn_info->io, TRUE, NULL); g_io_channel_unref(conn_info->io); } otc_connection_list = g_slist_remove(otc_connection_list, conn_info); g_free(conn_info); return FALSE; } static void otc_connect_cb(GIOChannel *chan, GError *gerr, gpointer user_data) { const char *dev_path; GSList *l; struct otc_conn_info *conn_info = NULL; DBusMessage *msg = NULL; DBusMessageIter iter; int fd; if (gerr) return; dev_path = (const char *) user_data; l = find_otc_conn_info(otc_connection_list, dev_path); if (!l) return; conn_info = l->data; conn_info->otc_connected = true; fd = g_io_channel_unix_get_fd(chan); DBG("OTC Connected fd [%d], role [%s]", fd, conn_info->role ? "server" : "client"); DBG("dev_path [%s]", dev_path); g_io_add_watch(chan, G_IO_HUP | G_IO_ERR | G_IO_NVAL, otc_disconnected_cb, (gpointer) dev_path); if (conn_info->role == BT_OTP_CLIENT_ROLE) { msg = dbus_message_new_method_call(BT_OTC_SERVICE_NAME, BT_OTC_OBJECT_PATH, BT_OTC_INTERFACE_NAME, "NewConnection"); } else if (conn_info->role == BT_OTP_SERVER_ROLE) { msg = dbus_message_new_method_call(BT_OTS_SERVICE_NAME, BT_OTS_OBJECT_PATH, BT_OTS_INTERFACE_NAME, "NewConnection"); } if (!msg) { error("Unable to create NewConnection call"); return; } dbus_message_iter_init_append(msg, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &dev_path); dbus_message_iter_append_basic(&iter, DBUS_TYPE_UNIX_FD, &fd); if (!g_dbus_send_message(dbus_conn, msg)) { error("sending NewConnection failed"); dbus_message_unref(msg); } } int btd_adapter_connect_otc(struct btd_device *device) { struct btd_adapter *adapter = device_get_adapter(device); const bdaddr_t *src = btd_adapter_get_address(adapter); uint8_t src_type = btd_adapter_get_le_address_type(adapter); const bdaddr_t *dest = device_get_address(device); uint8_t dest_type = device->bdaddr_type; struct otc_conn_info *conn_info = NULL; const char *dev_path = device_get_path(device); GError *gerr = NULL; conn_info = g_malloc0(sizeof(struct otc_conn_info)); conn_info->io = bt_io_connect(otc_connect_cb, (gpointer) dev_path, NULL, &gerr, BT_IO_OPT_SOURCE_BDADDR, src, BT_IO_OPT_SOURCE_TYPE, src_type, BT_IO_OPT_DEST_BDADDR, dest, BT_IO_OPT_DEST_TYPE, dest_type, BT_IO_OPT_PSM, OTP_PSM, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, BT_IO_OPT_INVALID); if (!conn_info->io) { error("OTC Connect failed : %s", gerr->message); g_error_free(gerr); g_free(conn_info); return -EIO; } conn_info->dev_path = dev_path; conn_info->role = BT_OTP_CLIENT_ROLE; conn_info->otc_connected = false; g_io_channel_set_close_on_unref(conn_info->io, FALSE); otc_connection_list = g_slist_append(otc_connection_list, conn_info); return 0; } static DBusMessage *connect_otc(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_device *device = user_data; GSList *l; if (device == NULL) return btd_error_invalid_args(msg); l = find_otc_conn_info(otc_connection_list, device_get_path(device)); if (l) { struct otc_conn_info *info = l->data; if (info->otc_connected) return btd_error_already_connected(msg); else return btd_error_busy(msg); } if (btd_adapter_connect_otc(device) != 0) return btd_error_failed(msg, "ConnectFailed"); return dbus_message_new_method_return(msg); } static DBusMessage *disconnect_otc(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_device *device = user_data; struct otc_conn_info *info = NULL; GSList *l; int sock; l = find_otc_conn_info(otc_connection_list, device_get_path(device)); if (!l) return btd_error_does_not_exist(msg); info = l->data; if (!info->otc_connected) return btd_error_not_connected(msg); sock = g_io_channel_unix_get_fd(info->io); shutdown(sock, SHUT_RDWR); g_io_channel_shutdown(info->io, FALSE, NULL); g_io_channel_unref(info->io); info->io = NULL; return dbus_message_new_method_return(msg); } int btd_adapter_listen_otc(struct btd_device *device) { struct btd_adapter *adapter = device_get_adapter(device); const bdaddr_t *src = btd_adapter_get_address(adapter); uint8_t type = btd_adapter_get_le_address_type(adapter); struct otc_conn_info *conn_info = NULL; const char *dev_path = device_get_path(device); GError *gerr = NULL; conn_info = g_malloc0(sizeof(struct otc_conn_info)); conn_info->io = bt_io_listen(otc_connect_cb, NULL, (gpointer) dev_path, NULL, &gerr, BT_IO_OPT_SOURCE_BDADDR, src, BT_IO_OPT_SOURCE_TYPE, type, BT_IO_OPT_PSM, OTP_PSM, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, BT_IO_OPT_INVALID); if (!conn_info->io) { error("OTC Listen failed : %s", gerr->message); g_error_free(gerr); g_free(conn_info); return -EIO; } conn_info->dev_path = dev_path; conn_info->otc_connected = false; conn_info->role = BT_OTP_SERVER_ROLE; g_io_channel_set_close_on_unref(conn_info->io, FALSE); otc_connection_list = g_slist_append(otc_connection_list, conn_info); return 0; } static DBusMessage *listen_otc(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_device *device = user_data; GSList *l; if (device == NULL) return btd_error_invalid_args(msg); l = find_otc_conn_info(otc_connection_list, device_get_path(device)); if (l) { struct otc_conn_info *info = l->data; if (info->otc_connected) return btd_error_already_connected(msg); else return btd_error_busy(msg); } if (btd_adapter_listen_otc(device) != 0) return btd_error_failed(msg, "ListenFailed"); return dbus_message_new_method_return(msg); } #endif #ifdef TIZEN_FEATURE_BLUEZ_MODIFY /********************* L2CAP LE SOCKET IMPLEMENTATION *************************/ static GSList *find_l2cap_le_profile_info(GSList *list, const char *owner, const char *path) { GSList *l; for (l = list; l != NULL; l = g_slist_next(l)) { struct l2cap_le_profile_info *info = l->data; if (!info) continue; if (g_strcmp0(info->owner, owner)) continue; if (!g_strcmp0(info->path, path)) return l; } return NULL; } static int parse_l2cap_le_opt(struct l2cap_le_profile_info *info, const char *key, DBusMessageIter *value) { int type = dbus_message_iter_get_arg_type(value); dbus_bool_t b; if (strcasecmp(key, "RequireAuthentication") == 0) { if (type != DBUS_TYPE_BOOLEAN) return -EINVAL; dbus_message_iter_get_basic(value, &b); if (b) info->sec_level = BT_IO_SEC_MEDIUM; else info->sec_level = BT_IO_SEC_LOW; } else if (strcasecmp(key, "RequireAuthorization") == 0) { if (type != DBUS_TYPE_BOOLEAN) return -EINVAL; dbus_message_iter_get_basic(value, &b); info->authorize = b; } return 0; } static struct l2cap_le_profile_info *create_l2cap_le_socket(const char *owner, const char *path, int psm, DBusMessageIter *opts) { struct l2cap_le_profile_info *info = NULL; if (psm < 0 || psm > BTD_L2CAP_LE_PSM_MAX) return NULL; info = g_malloc0(sizeof(struct l2cap_le_profile_info)); info->owner = g_strdup(owner); info->path = g_strdup(path); info->psm = psm; while (dbus_message_iter_get_arg_type(opts) == DBUS_TYPE_DICT_ENTRY) { DBusMessageIter value, entry; const char *key; dbus_message_iter_recurse(opts, &entry); dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); dbus_message_iter_recurse(&entry, &value); if (parse_l2cap_le_opt(info, key, &value) < 0) error("Invalid value for l2cap_le_socket option %s", key); dbus_message_iter_next(opts); } if (!info->name) info->name = g_strdup_printf("%s%s/%d", owner, path, psm); return info; } static void l2cap_le_io_destroy(gpointer p) { struct l2cap_le_conn_info *conn = p; if (conn->io_id > 0) g_source_remove(conn->io_id); if (conn->io) { g_io_channel_shutdown(conn->io, FALSE, NULL); g_io_channel_unref(conn->io); } if (conn->auth_id != 0) btd_cancel_authorization(conn->auth_id); if (conn->auth_uuid) free(conn->auth_uuid); g_free(conn); } static gboolean l2cap_le_disconnected_cb(GIOChannel *chan, GIOCondition cond, gpointer user_data) { struct l2cap_le_conn_info *conn = user_data; struct l2cap_le_profile_info *info = conn->profile_info; DBG(" L2CAP LE socket disconnected role [%s] ", info->role == BT_L2CAP_LE_SERVER_ROLE ? "server" : "client"); g_dbus_emit_signal(dbus_conn, conn->dev_path, DEVICE_INTERFACE, "L2CAPLEDisconnected", DBUS_TYPE_INVALID); conn->connected = false; info->conn = g_slist_remove(info->conn, conn); l2cap_le_io_destroy(conn); return FALSE; } static void l2cap_le_connect_cb(GIOChannel *chan, GError *gerr, gpointer user_data) { struct l2cap_le_conn_info *conn = (struct l2cap_le_conn_info *) user_data; struct l2cap_le_profile_info *info = conn->profile_info; DBusMessage *msg = NULL; DBusMessageIter iter; const char *dev_path; int fd; GError *io_err = NULL; char addr[18]; if (!bt_io_get(chan, &io_err, BT_IO_OPT_DEST, addr, BT_IO_OPT_INVALID)) { if (gerr) { error("%s failed %s", info->name, gerr->message); g_error_free(io_err); io_err = NULL; } else { error("Unable to get connect data for %s: %s", info->name, io_err->message); gerr = io_err; } goto drop; } if (gerr != NULL) { error("%s failed to connect to %s: %s", info->name, addr, gerr->message); goto drop; } conn->connected = true; fd = g_io_channel_unix_get_fd(chan); if (conn->io_id == 0) conn->io_id = g_io_add_watch(chan, G_IO_HUP | G_IO_ERR | G_IO_NVAL, l2cap_le_disconnected_cb, conn); msg = dbus_message_new_method_call(info->owner, info->path, BT_L2CAP_LE_INTERFACE_NAME, "NewConnection"); if (!msg) { error("Unable to create NewConnection call"); goto drop; } dev_path = conn->dev_path; dbus_message_iter_init_append(msg, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &dev_path); dbus_message_iter_append_basic(&iter, DBUS_TYPE_UNIX_FD, &fd); if (!g_dbus_send_message(dbus_conn, msg)) { error("sending NewConnection failed"); dbus_message_unref(msg); goto drop; } DBG("L2CAP_LE Connected fd [%d] addr [%s], role [%s]",fd, addr, info->role == BT_L2CAP_LE_SERVER_ROLE ? "server" : "client"); return; drop: if (io_err) g_error_free(io_err); info->conn = g_slist_remove(info->conn, conn); l2cap_le_io_destroy(conn); } static struct l2cap_le_conn_info *create_l2cap_le_conn( struct l2cap_le_conn_info *server, GIOChannel *io, bdaddr_t *src, bdaddr_t *dst, uint8_t dst_type) { struct l2cap_le_conn_info *conn; GIOCondition cond; struct btd_device *device; struct btd_adapter *adapter = adapter_find(src); if (!adapter) { error("could not find adapter"); return NULL; } device = btd_adapter_get_device(adapter, dst, dst_type); if (!device) { error("could not find Device"); return NULL; } conn = g_new0(struct l2cap_le_conn_info, 1); conn->io = g_io_channel_ref(io); conn->profile_info = server->profile_info; conn->dev_path = device_get_path(device); cond = G_IO_HUP | G_IO_ERR | G_IO_NVAL; conn->io_id = g_io_add_watch(io, cond, l2cap_le_disconnected_cb, conn); return conn; } static void l2cap_le_auth(DBusError *err, void *user_data) { struct l2cap_le_conn_info *conn = user_data; struct l2cap_le_profile_info *info = conn->profile_info; GError *gerr = NULL; char addr[18]; conn->auth_id = 0; bt_io_get(conn->io, &gerr, BT_IO_OPT_DEST, addr, BT_IO_OPT_INVALID); if (gerr != NULL) { error("Unable to get connect data for %s: %s", info->name, gerr->message); g_error_free(gerr); goto drop; } if (err && dbus_error_is_set(err)) { error("%s rejected %s: %s", info->name, addr, err->message); goto drop; } if (!bt_io_accept(conn->io, l2cap_le_connect_cb, conn, NULL, &gerr)) { error("bt_io_accept: %s", gerr->message); g_error_free(gerr); goto drop; } DBG("%s authorized to connect to %s", addr, info->name); return; drop: info->conn = g_slist_remove(info->conn, conn); l2cap_le_io_destroy(conn); } static void l2cap_le_confirm(GIOChannel *io, gpointer user_data) { struct l2cap_le_conn_info *server = user_data; struct l2cap_le_profile_info *info = server->profile_info; struct l2cap_le_conn_info *conn; GError *gerr = NULL; uint8_t dst_type; bdaddr_t src, dst; char addr[18]; char *uuid = g_malloc0(UUID_LEN * sizeof(char)); bt_io_get(io, &gerr, BT_IO_OPT_SOURCE_BDADDR, &src, BT_IO_OPT_DEST_BDADDR, &dst, BT_IO_OPT_DEST_TYPE, &dst_type, BT_IO_OPT_DEST, addr, BT_IO_OPT_INVALID); if (gerr != NULL) { error("%s failed to get connect data: %s", info->name, gerr->message); g_error_free(gerr); return; } DBG("incoming connect from %s for authorization", addr); conn = create_l2cap_le_conn(server, io, &src, &dst, dst_type); if (conn == NULL) return; /* Use custom uuid of the form "FFFFFFFF-FFFF-FFFF-FFFF-" * for authorizing l2cap_le socket using existing flow. * This custom uuid will be used in the BT-OAL layer to identify the * l2cap_le socket authorization and extracting psm from it. */ snprintf(uuid, UUID_LEN, "%s%012d", L2CAP_LE_UUID_SUBSTR, server->psm); conn->auth_uuid = uuid; conn->auth_id = btd_request_authorization(&src, &dst, (const char *)conn->auth_uuid, l2cap_le_auth, conn); if (conn->auth_id == 0) { error("%s authorization failure", info->name); l2cap_le_io_destroy(conn); return; } info->conn = g_slist_append(info->conn, conn); DBG("%s authorizing connection from %s", info->name, addr); } static void l2cap_le_direct_connect(GIOChannel *io, GError *err, gpointer user_data) { struct l2cap_le_conn_info *server = user_data; struct l2cap_le_profile_info *info = server->profile_info; struct l2cap_le_conn_info *conn; GError *gerr = NULL; uint8_t dst_type; bdaddr_t src, dst; bt_io_get(io, &gerr, BT_IO_OPT_SOURCE_BDADDR, &src, BT_IO_OPT_DEST_BDADDR, &dst, BT_IO_OPT_DEST_TYPE, &dst_type, BT_IO_OPT_INVALID); if (gerr != NULL) { error("%s failed to get connect data: %s", info->name, gerr->message); g_error_free(gerr); return; } conn = create_l2cap_le_conn(server, io, &src, &dst, dst_type); if (conn == NULL) return; info->conn = g_slist_append(info->conn, conn); l2cap_le_connect_cb(io, err, conn); } static void _remove_l2cap_le_socket(struct l2cap_le_profile_info *info) { l2cap_le_socket_list = g_slist_remove(l2cap_le_socket_list, info); DBG("Removed \"%s\"", info->name); if (info->server) l2cap_le_io_destroy((gpointer)info->server); g_slist_free_full(info->conn, l2cap_le_io_destroy); g_free(info->name); g_free(info->owner); g_free(info->path); g_free(info); } static void l2cap_le_socket_exited(DBusConnection *conn, void *user_data) { struct l2cap_le_profile_info *info = user_data; DBG("l2cap le server \"%s\" exited", info->name); _remove_l2cap_le_socket(info); } static int _connect_l2cap_le(struct btd_device *device, struct l2cap_le_profile_info *info) { struct btd_adapter *adapter = device_get_adapter(device); const bdaddr_t *src = btd_adapter_get_address(adapter); uint8_t src_type = btd_adapter_get_le_address_type(adapter); const bdaddr_t *dest = device_get_address(device); uint8_t dest_type = device->bdaddr_type; const char *dev_path = device_get_path(device); GError *gerr = NULL; struct l2cap_le_conn_info *conn = NULL; conn = g_malloc0(sizeof(struct l2cap_le_conn_info)); info->conn = g_slist_append(info->conn, conn); conn->io = bt_io_connect(l2cap_le_connect_cb, conn, NULL, &gerr, BT_IO_OPT_SOURCE_BDADDR, src, BT_IO_OPT_SOURCE_TYPE, src_type, BT_IO_OPT_DEST_BDADDR, dest, BT_IO_OPT_DEST_TYPE, dest_type, BT_IO_OPT_PSM, info->psm, BT_IO_OPT_SEC_LEVEL, info->sec_level, BT_IO_OPT_INVALID); if (!conn->io) { error("L2CAP_LE Connect failed : %s", gerr->message); g_error_free(gerr); _remove_l2cap_le_socket(info); return -EIO; } info->role = BT_L2CAP_LE_CLIENT_ROLE; conn->dev_path = dev_path; conn->profile_info = info; conn->connected = false; g_io_channel_set_close_on_unref(conn->io, FALSE); l2cap_le_socket_list = g_slist_append(l2cap_le_socket_list, info); return 0; } static int _listen_l2cap_le(struct btd_adapter *adapter, struct l2cap_le_profile_info *info) { const bdaddr_t *src = btd_adapter_get_address(adapter); uint8_t type = btd_adapter_get_le_address_type(adapter); GError *gerr = NULL; struct l2cap_le_conn_info *conn = NULL; BtIOConfirm confirm; BtIOConnect connect; int psm = 0; if (info->authorize) { confirm = l2cap_le_confirm; connect = NULL; } else { confirm = NULL; connect = l2cap_le_direct_connect; } conn = g_malloc0(sizeof(struct l2cap_le_conn_info)); info->server = conn; conn->io = bt_io_listen(connect, confirm, (gpointer)conn, NULL, &gerr, BT_IO_OPT_SOURCE_BDADDR, src, BT_IO_OPT_SOURCE_TYPE, type, BT_IO_OPT_PSM, info->psm, BT_IO_OPT_SEC_LEVEL, info->sec_level, BT_IO_OPT_INVALID); if (!conn->io) { error("L2cap_LE Listen failed : %s", gerr->message); g_error_free(gerr); _remove_l2cap_le_socket(info); return -EIO; } bt_io_get(conn->io, &gerr, BT_IO_OPT_PSM, &psm, BT_IO_OPT_INVALID); DBG("L2CAP LE Socket listen to PSM %d successful", psm); conn->connected = false; info->role = BT_L2CAP_LE_SERVER_ROLE; conn->psm = psm; conn->profile_info = info; g_io_channel_set_close_on_unref(conn->io, FALSE); l2cap_le_socket_list = g_slist_append(l2cap_le_socket_list, info); return 0; } static DBusMessage *connect_l2cap_le_socket(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_device *device = user_data; GSList *l; const char *path, *sender; dbus_int32_t psm; DBusMessageIter args, opts; struct l2cap_le_profile_info *info = NULL; if (device == NULL) return btd_error_invalid_args(msg); sender = dbus_message_get_sender(msg); dbus_message_iter_init(msg, &args); dbus_message_iter_get_basic(&args, &path); dbus_message_iter_next(&args); dbus_message_iter_get_basic(&args, &psm); dbus_message_iter_next(&args); l = find_l2cap_le_profile_info(l2cap_le_socket_list, sender, path); if (l) { struct l2cap_le_profile_info *info = l->data; if (info->conn) { struct l2cap_le_conn_info *conn_info = info->conn->data; if (conn_info && conn_info->connected) return btd_error_already_connected(msg); else return btd_error_busy(msg); } else { error("Something is wrong!!!"); return btd_error_failed(msg, "ConnectFailed due to some internal error"); } } if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY) return btd_error_invalid_args(msg); dbus_message_iter_recurse(&args, &opts); info = create_l2cap_le_socket(sender, path, psm, &opts); if (!info) return btd_error_invalid_args(msg); info->role = BT_L2CAP_LE_CLIENT_ROLE; DBG("connect l2cap l2 socket channel %d", (int)psm); if (_connect_l2cap_le(device, info) != 0) return btd_error_failed(msg, "ConnectFailed"); info->id = g_dbus_add_disconnect_watch(conn, sender, l2cap_le_socket_exited, info, NULL); return dbus_message_new_method_return(msg); } DBusMessage *listen_l2cap_le_socket(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_adapter *adapter = user_data; GSList *l; const char *path, *sender; dbus_int32_t psm; DBusMessageIter args, opts; struct l2cap_le_profile_info *info = NULL; sender = dbus_message_get_sender(msg); dbus_message_iter_init(msg, &args); dbus_message_iter_get_basic(&args, &path); dbus_message_iter_next(&args); dbus_message_iter_get_basic(&args, &psm); dbus_message_iter_next(&args); l = find_l2cap_le_profile_info(l2cap_le_socket_list, sender, path); if (l) return btd_error_already_exists(msg); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY) return btd_error_invalid_args(msg); dbus_message_iter_recurse(&args, &opts); info = create_l2cap_le_socket(sender, path, psm, &opts); if (!info) return btd_error_invalid_args(msg); info->role = BT_L2CAP_LE_SERVER_ROLE; DBG("listen l2cap_le socket to psm %d", (int)psm); if (_listen_l2cap_le(adapter, info) != 0) return btd_error_failed(msg, "ListenFailed"); info->id = g_dbus_add_disconnect_watch(conn, sender, l2cap_le_socket_exited, info, NULL); return dbus_message_new_method_return(msg); } DBusMessage *get_psm_l2cap_le(DBusConnection *conn, DBusMessage *msg) { const char *path, *sender; int psm; GSList *l; DBusMessage *reply; DBusMessageIter args; struct l2cap_le_profile_info *info; sender = dbus_message_get_sender(msg); DBG("sender %s", sender); dbus_message_iter_init(msg, &args); dbus_message_iter_get_basic(&args, &path); dbus_message_iter_next(&args); l = find_l2cap_le_profile_info(l2cap_le_socket_list, sender, path); if (!l) { DBG("L2cap LE socket not exist"); return btd_error_does_not_exist(msg); } info = l->data; if(info->server) psm = info->server->psm; else return btd_error_does_not_exist(msg); reply = dbus_message_new_method_return(msg); if (!reply) return btd_error_failed(msg, "Failed to create reply."); dbus_message_append_args(reply, DBUS_TYPE_UINT32, &psm, DBUS_TYPE_INVALID); return reply; } DBusMessage *remove_l2cap_le_socket(DBusConnection *conn, DBusMessage *msg) { const char *path, *sender; struct l2cap_le_profile_info *info = NULL; GSList *l; sender = dbus_message_get_sender(msg); if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) return btd_error_invalid_args(msg); DBG("remove socket sender %s path %s", sender, path); l = find_l2cap_le_profile_info(l2cap_le_socket_list, sender, path); if (!l) { DBG("L2cap LE socket not exist"); return btd_error_does_not_exist(msg); } info = l->data; g_dbus_remove_watch(conn, info->id); _remove_l2cap_le_socket(info); return dbus_message_new_method_return(msg); } #endif static DBusMessage *le_set_data_length( DBusConnection *conn, DBusMessage *msg, void *user_data) { dbus_uint16_t max_tx_octets; dbus_uint16_t max_tx_time; struct btd_device *device = user_data; int status; char addr[18]; if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT16, &max_tx_octets, DBUS_TYPE_UINT16, &max_tx_time, DBUS_TYPE_INVALID)) { DBG("error in retrieving values"); return btd_error_invalid_args(msg); } if (device->bdaddr_type == BDADDR_BREDR) return btd_error_not_supported(msg); ba2str(&device->bdaddr, addr); DBG("Remote device address: %s", addr); DBG("Max tx octets: %u, Max tx time: %u", max_tx_octets, max_tx_time); status = btd_adapter_le_set_data_length(device->adapter, &device->bdaddr, max_tx_octets, max_tx_time); if (status != 0) return btd_error_failed(msg, "Unable to set le data length values"); else return dbus_message_new_method_return(msg); } static DBusMessage *set_trusted_profile(DBusConnection *conn, DBusMessage *msg, void *data) { struct btd_device *dev = data; dbus_bool_t profile_trusted; const char *pattern; char *uuid; uint32_t pbap = dev->trusted_profiles.pbap; uint32_t map = dev->trusted_profiles.map; uint32_t sap = dev->trusted_profiles.sap; uint32_t hfp_hs = dev->trusted_profiles.hfp_hs; uint32_t a2dp = dev->trusted_profiles.a2dp; if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &pattern, DBUS_TYPE_BOOLEAN, &profile_trusted, DBUS_TYPE_INVALID)) return btd_error_invalid_args(msg); DBG("Pattern : %s", pattern); uuid = bt_name2string(pattern); DBG("UUID : %s", uuid); DBG("profile Trusted : %d %d %d", dev->trusted_profiles.pbap, dev->trusted_profiles.map, dev->trusted_profiles.sap); DBG("profile Restricted : %d %d", dev->trusted_profiles.hfp_hs, dev->trusted_profiles.a2dp); if (g_strcmp0(uuid, OBEX_PBAP_UUID) == 0) { if (profile_trusted) pbap = SUPPORTED_TRUSTED; else pbap = SUPPORTED_BLOCKED; } else if (g_strcmp0(uuid, OBEX_MAP_UUID) == 0) { if (profile_trusted) map = SUPPORTED_TRUSTED; else map = SUPPORTED_BLOCKED; } else if (g_strcmp0(uuid, SAP_UUID) == 0) { if (profile_trusted) sap = SUPPORTED_TRUSTED; else sap = SUPPORTED_BLOCKED; } else if (g_strcmp0(uuid, HFP_HS_UUID) == 0) { if (profile_trusted) hfp_hs = SUPPORTED_TRUSTED; else hfp_hs = SUPPORTED_BLOCKED; } else if (g_strcmp0(uuid, A2DP_SINK_UUID) == 0) { if (profile_trusted) a2dp = SUPPORTED_TRUSTED; else a2dp = SUPPORTED_BLOCKED; } else { free(uuid); return btd_error_invalid_args(msg); } free(uuid); btd_device_set_trusted_profiles(dev, pbap, map, sap, hfp_hs, a2dp); return dbus_message_new_method_return(msg); } static DBusMessage *is_connected_profile(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_device *dev = user_data; struct btd_service *service; btd_service_state_t state; const char *pattern; char *uuid; DBusMessage *reply; dbus_bool_t val; if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &pattern, DBUS_TYPE_INVALID)) return btd_error_invalid_args(msg); reply = dbus_message_new_method_return(msg); if (!reply) return btd_error_invalid_args(reply); uuid = bt_name2string(pattern); DBG("is_connected_profile_uuid : %s", uuid); service = btd_device_get_service(dev, uuid); if ((service == NULL) && (g_strcmp0(uuid, HFP_HS_UUID) == 0)) { DBG("HFP service is not found check for HSP service"); service = btd_device_get_service(dev, HSP_HS_UUID); } if (uuid) free(uuid); if (!service) return btd_error_not_connected(msg); state = btd_service_get_state(service); DBG("Connected State : %d", state); if (state == BTD_SERVICE_STATE_CONNECTED) val = TRUE; else val = FALSE; dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &val, DBUS_TYPE_INVALID); return reply; } static DBusMessage *update_le_conn_parm(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_device *device = user_data; GIOChannel *io; int fd; struct le_conn_param param = {0, 0, 0, 0}; uint32_t min, max, latency, to_multiplier; DBG(""); if (device == NULL) { error("device is NULL"); return btd_error_invalid_args(msg); } if (!device->le) { error("le is not supported"); return btd_error_not_supported(msg); } if (!device->gatt_connected || !device->attrib) return btd_error_not_connected(msg); io = g_attrib_get_channel(device->attrib); if (!io) return btd_error_not_connected(msg); fd = g_io_channel_unix_get_fd(io); if (fd < 0) return btd_error_not_connected(msg); if (device_get_conn_update_state(device)) return btd_error_in_progress(msg); else device_set_conn_update_state(device, true); if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &min, DBUS_TYPE_UINT32, &max, DBUS_TYPE_UINT32, &latency, DBUS_TYPE_UINT32, &to_multiplier, DBUS_TYPE_INVALID)) { error("Invalid args"); return btd_error_invalid_args(msg); } if (min > UINT16_MAX || max > UINT16_MAX || latency > UINT16_MAX || to_multiplier > UINT16_MAX) { error("Invalid args"); return btd_error_invalid_args(msg); } param.min = (uint16_t)min; param.max = (uint16_t)max; param.latency = (uint16_t)latency; param.to_multiplier = (uint16_t)to_multiplier; if (setsockopt(fd, SOL_BLUETOOTH, BT_LE_CONN_PARAM, ¶m, sizeof(param)) < 0) { error("Can't Update LE conn param : %s (%d)", strerror(errno), errno); return btd_error_failed(msg, strerror(errno)); } return dbus_message_new_method_return(msg); } static void device_request_att_mtu_reponse_cb(bool success, uint8_t att_ecode, void *user_data) { struct btd_device *device = user_data; DBusMessage *reply; DBusMessageIter iter; uint16_t mtu; if (!device->req_att_mtu) return; mtu = bt_gatt_client_get_mtu(device->client); if (!success) { const char *err_if; err_if = ERROR_INTERFACE ".Failed"; reply = dbus_message_new_error(device->req_att_mtu, err_if, "Request Att MTU failed"); g_dbus_send_message(dbus_conn, reply); return; } DBG("MTU exchange complete, with MTU: %u", mtu); reply = dbus_message_new_method_return(device->req_att_mtu); if (!reply) return; dbus_message_iter_init_append(reply, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT16, &mtu); dbus_message_iter_append_basic(&iter, DBUS_TYPE_BYTE, &att_ecode); g_dbus_send_message(dbus_conn, reply); dbus_message_unref(device->req_att_mtu); device->req_att_mtu = NULL; } static DBusMessage *request_att_mtu(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_device *device = user_data; uint16_t mtu; DBG(""); if (device == NULL) { error("device is NULL"); return btd_error_invalid_args(msg); } if (!device->le) { error("le is not supported"); return btd_error_not_supported(msg); } if (!device->gatt_connected || !device->attrib) return btd_error_not_connected(msg); if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT16, &mtu, DBUS_TYPE_INVALID)) { error("Invalid args"); return btd_error_invalid_args(msg); } DBG("MTU %d", mtu); if (!bt_gatt_request_att_mtu(device->client, mtu, device_request_att_mtu_reponse_cb, device)) return btd_error_failed(msg, "Unable to Request MTU"); device->req_att_mtu = dbus_message_ref(msg); return NULL; } static DBusMessage *device_get_ida(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_device *device = user_data; char device_idaddr[18] = { 0 }; DBusMessage *reply; const gchar *id_address = device_idaddr; DBG(""); if (device == NULL) return btd_error_invalid_args(msg); if (!device->le) { error("It doesn't support LE"); return btd_error_not_supported(msg); } if (device->rpa) { // There is the first RPA. So it's paired device. if (device->bredr) ba2str(device->rpa, device_idaddr); else ba2str(&device->bdaddr, device_idaddr); } else if (device->bdaddr_type != BDADDR_LE_RANDOM) { // device->bdaddr is identity address. ba2str(&device->bdaddr, device_idaddr); } else if ((device->bdaddr.b[5] >> 6) == 0x01) { // RPA but it's not paired return btd_error_does_not_exist(msg); } else if ((device->bdaddr.b[5] >> 6) == 0x03) { // Static Random address ba2str(&device->bdaddr, device_idaddr); } else { // Non-RPA case return btd_error_not_supported(msg); } reply = dbus_message_new_method_return(msg); if (!reply) return NULL; dbus_message_append_args(reply, DBUS_TYPE_STRING, &id_address, DBUS_TYPE_INVALID); return reply; } void device_set_conn_update_state(struct btd_device *device, bool state) { if (!device) return; device->pending_conn_update = state; } bool device_get_conn_update_state(struct btd_device *device) { return device->pending_conn_update; } #endif 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) }, #ifdef TIZEN_FEATURE_BLUEZ_MODIFY { GDBUS_ASYNC_METHOD("DisconnectExtProfile", GDBUS_ARGS({ "profile", "o" }), NULL, disconnect_ext_profile) }, { GDBUS_ASYNC_METHOD("Pair", GDBUS_ARGS({ "conn_type", "y" }), NULL, pair_device) }, #else { GDBUS_ASYNC_METHOD("Pair", NULL, NULL, pair_device) }, #endif { GDBUS_METHOD("CancelPairing", NULL, NULL, cancel_pairing) }, #ifdef TIZEN_FEATURE_BLUEZ_MODIFY { GDBUS_ASYNC_METHOD("ConnectLE", GDBUS_ARGS({ "auto_connect", "b"}), NULL, connect_le) }, { GDBUS_ASYNC_METHOD("DisconnectLE", NULL, NULL, disconnect_le) }, { GDBUS_METHOD("IsConnectedProfile", GDBUS_ARGS({ "UUID", "s" }), GDBUS_ARGS({ "IsConnected", "b" }), is_connected_profile)}, { GDBUS_METHOD("LeConnUpdate", GDBUS_ARGS({ "interval_min", "u" }, { "interval_max", "u" }, { "latency", "u" }, { "time_out", "u" }), NULL, update_le_conn_parm) }, { GDBUS_ASYNC_METHOD("DiscoverServices", GDBUS_ARGS({ "pattern", "s" }), NULL, discover_services) }, { GDBUS_METHOD("CancelDiscovery", NULL, NULL, cancel_discover) }, { GDBUS_ASYNC_METHOD("ConnectIpsp", NULL, NULL, connect_ipsp) }, { GDBUS_ASYNC_METHOD("DisconnectIpsp", NULL, NULL, disconnect_ipsp) }, #ifdef TIZEN_FEATURE_BLUEZ_MODIFY { GDBUS_METHOD("ConnectOtc", NULL, NULL, connect_otc) }, { GDBUS_METHOD("DisconnectOtc", NULL, NULL, disconnect_otc) }, { GDBUS_METHOD("ListenOtc", NULL, NULL, listen_otc) }, { GDBUS_ASYNC_METHOD("ConnectL2capLESocket", GDBUS_ARGS({ "path", "o"}, { "psm", "i" }, { "options", "a{sv}" }), NULL, connect_l2cap_le_socket) }, #endif { GDBUS_ASYNC_METHOD("LESetDataLength", GDBUS_ARGS({"max_tx_octets", "q" }, { "max_tx_time", "q" }), NULL, le_set_data_length)}, { GDBUS_ASYNC_METHOD("RequestAttMtu", GDBUS_ARGS({ "mtu", "q" }), GDBUS_ARGS({ "mtu", "q" }, { "status", "y"}), request_att_mtu) }, { GDBUS_METHOD("GetIDAddress", NULL, GDBUS_ARGS({ "IDAdress", "s" }), device_get_ida) }, { GDBUS_METHOD("SetTrustedProfile", GDBUS_ARGS({ "uuid", "s"}, { "trusted", "b"}), NULL, set_trusted_profile) }, #endif { } }; 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 }, { "Trusted", "b", dev_property_get_trusted, dev_property_set_trusted }, { "Blocked", "b", dev_property_get_blocked, dev_property_set_blocked }, { "LegacyPairing", "b", dev_property_get_legacy }, { "RSSI", "n", dev_property_get_rssi, NULL, dev_property_exists_rssi }, #ifdef TIZEN_FEATURE_BLUEZ_MODIFY {"IsAliasSet", "b", dev_property_get_alias_set }, { "Connected", "y", dev_property_get_connected }, #else { "Connected", "b", dev_property_get_connected }, #endif { "UUIDs", "as", dev_property_get_uuids }, { "Modalias", "s", dev_property_get_modalias, NULL, dev_property_exists_modalias }, { "Adapter", "o", dev_property_get_adapter }, #ifdef TIZEN_FEATURE_BLUEZ_MODIFY /* To handle Failed Legacy Pairing when initiated from Remote device*/ { "LegacyPaired", "b", dev_property_get_paired }, { "LegacyManufacturerDataLen", "q", property_get_manufacturer_data_len }, { "LegacyManufacturerData", "ay", property_get_manufacturer_data }, { "GattConnected", "b", dev_property_get_gatt_connected }, { "PayloadTimeout", "q", dev_property_get_payload}, { "LastAddrType", "y", dev_property_get_last_addr_type}, { "IpspConnected", "b", dev_property_get_ipsp_conn_state }, { "IpspBtInterfaceInfo", "s", dev_property_get_ipsp_conn_bt_iface_name }, { "AttMtu", "q", dev_property_get_att_mtu }, { "TrustedProfiles", "u", dev_property_get_trusted_profiles}, #endif { "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, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL}, { "AdvertisingData", "a{yv}", dev_property_get_advertising_data, NULL, dev_property_advertising_data_exist, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { "WakeAllowed", "b", dev_property_get_wake_allowed, dev_property_set_wake_allowed, dev_property_wake_allowed_exist }, { } }; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY static const GDBusSignalTable device_signals[] = { { GDBUS_SIGNAL("Disconnected", GDBUS_ARGS({ "bdaddr_type", "y" }, { "reason", "y" }, { "name", "s" })) }, { GDBUS_SIGNAL("DeviceConnected", GDBUS_ARGS({ "bdaddr_type", "y"})) }, { GDBUS_SIGNAL("ProfileStateChanged", GDBUS_ARGS({ "profile", "s"}, {"state", "i"})) }, { GDBUS_SIGNAL("AdvReport", GDBUS_ARGS({"Address","s"}, { "Address Type", "y" }, { "Adv Type", "y"}, { "RSSI", "i"}, { "AdvDataLen", "i"}, { "AdvData", "ay"})) }, { GDBUS_SIGNAL("LEDataLengthChanged", GDBUS_ARGS({"max_tx_octets","q"}, { "max_tx_time", "q" }, { "max_rx_octets", "q"}, { "max_rx_time", "q"}))}, { GDBUS_SIGNAL("IpspStateChanged", GDBUS_ARGS({"connected","b"}, {"if_name","s"}))}, { GDBUS_SIGNAL("OtcDisconnected", NULL)}, { GDBUS_SIGNAL("AttMtuChanged", GDBUS_ARGS({"mtu", "q"})) }, { } }; #endif uint8_t btd_device_get_bdaddr_type(struct btd_device *dev) { return dev->bdaddr_type; } bool btd_device_is_connected(struct btd_device *dev) { return dev->bredr_state.connected || 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; } } void device_add_connection(struct btd_device *dev, uint8_t bdaddr_type) { struct bearer_state *state = get_state(dev, bdaddr_type); device_update_last_seen(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); else device_set_le_support(dev, bdaddr_type); state->connected = true; #ifndef TIZEN_FEATURE_BLUEZ_MODIFY 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"); #else #ifdef TIZEN_FEATURE_BLUEZ_BATTERY_WATCH if (bdaddr_type == BDADDR_BREDR && get_charging_state(dev->adapter) == WIRELESS_CHARGING) { int br_pkt_type = ACL_PTYPE_MASK | HCI_2DH1 | HCI_2DH3 | HCI_2DH5 | HCI_3DH1 | HCI_3DH3 | HCI_3DH5; DBG("During wireless charging... Change packet type"); device_change_pkt_type(dev, (gpointer)br_pkt_type); } #endif /* TIZEN_FEATURE_BLUEZ_BATTERY_WATCH */ g_dbus_emit_signal(dbus_conn, dev->path, DEVICE_INTERFACE, "DeviceConnected", DBUS_TYPE_BYTE, &bdaddr_type, DBUS_TYPE_INVALID); #endif } void device_remove_connection(struct btd_device *device, uint8_t bdaddr_type) { struct bearer_state *state = get_state(device, bdaddr_type); DBusMessage *reply; #ifndef TIZEN_FEATURE_BLUEZ_MODIFY bool remove_device = false; #endif bool paired_status_updated = false; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY char *dev_name = device->name; #endif if (!state->connected) return; state->connected = 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; } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (device->browse) { struct browse_req *req = device->browse; if ((bdaddr_type == BDADDR_BREDR && req->search_uuid != 0) || (bdaddr_type != BDADDR_BREDR && req->search_uuid == 0)) device->browse = NULL; else DBG("device->browse is for other link"); } #endif /* 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; } while (device->disconnects) { DBusMessage *msg = device->disconnects->data; #ifndef TIZEN_FEATURE_BLUEZ_MODIFY if (dbus_message_is_method_call(msg, ADAPTER_INTERFACE, "RemoveDevice")) remove_device = true; #endif g_dbus_send_reply(dbus_conn, msg, DBUS_TYPE_INVALID); device->disconnects = g_slist_remove(device->disconnects, msg); dbus_message_unref(msg); } /* 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; } 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; } /* 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"); #ifndef TIZEN_FEATURE_BLUEZ_MODIFY if (device->bredr_state.connected || device->le_state.connected) return; device_update_last_seen(device, bdaddr_type); g_slist_free_full(device->eir_uuids, g_free); device->eir_uuids = NULL; g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Connected"); if (remove_device) btd_adapter_remove_device(device->adapter, device); #else g_dbus_emit_signal(dbus_conn, device->path, DEVICE_INTERFACE, "Disconnected", DBUS_TYPE_BYTE, &bdaddr_type, DBUS_TYPE_BYTE, &device->disc_reason, DBUS_TYPE_STRING, &dev_name, DBUS_TYPE_INVALID); #endif } 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; snprintf(filename, PATH_MAX, STORAGEDIR "/%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; snprintf(filename, PATH_MAX, STORAGEDIR "/%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 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); snprintf(filename, PATH_MAX, STORAGEDIR "/%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; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY gboolean svc_change_regd; char buf[DEV_MAX_MANUFACTURER_DATA_LEN] = { 0, }; #endif /* 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); } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY /* Load RPA Resolution Support value */ device->rpa_res_support = g_key_file_get_integer(key_file, "General", "RPAResSupport", NULL); str = g_key_file_get_string(key_file, "General", "LegacyManufacturerDataLen", NULL); if (str) { device->manufacturer_data_len = strtol(str, NULL, 10); g_free(str); if (0 > device->manufacturer_data_len) { error("Invalid manufacturer_data_len: %d", device->manufacturer_data_len); device->manufacturer_data_len = 0; } str = g_key_file_get_string(key_file, "General", "LegacyManufacturerData", NULL); if (str) { if (device->manufacturer_data_len < DEV_MAX_MANUFACTURER_DATA_LEN) { load_manufacturer_data_2digit(str, device->manufacturer_data_len, buf); device->manufacturer_data = g_memdup(buf, device->manufacturer_data_len); } g_free(str); } } str = g_key_file_get_string(key_file, "General", "IdentityAddress", NULL); if (str) { device->rpa = g_malloc0(sizeof(bdaddr_t)); bacpy(device->rpa, &device->bdaddr); str2ba(str, &device->bdaddr); g_free(str); } #endif /* 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 = true; else if (g_str_equal(*t, "LE")) device->le = true; 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"); } g_strfreev(techno); next: /* Load trust */ device->trusted = g_key_file_get_boolean(key_file, "General", "Trusted", NULL); #ifdef TIZEN_FEATURE_BLUEZ_MODIFY /* Load Trusted Profiles*/ int trusted_profiles = g_key_file_get_integer(key_file, "General", "TrustedProfiles", NULL); DBG("Loading TrustedProfiles %d", trusted_profiles); device->trusted_profiles.pbap = ((trusted_profiles & (PROFILE_SUPPORTED << PBAP_SHIFT_OFFSET)) >> PBAP_SHIFT_OFFSET); device->trusted_profiles.map = ((trusted_profiles & (PROFILE_SUPPORTED << MAP_SHIFT_OFFSET)) >> MAP_SHIFT_OFFSET); device->trusted_profiles.sap = ((trusted_profiles & (PROFILE_SUPPORTED << SAP_SHIFT_OFFSET)) >> SAP_SHIFT_OFFSET); device->trusted_profiles.hfp_hs = ((trusted_profiles & (PROFILE_SUPPORTED << HFP_HS_SHIFT_OFFSET)) >> HFP_HS_SHIFT_OFFSET); device->trusted_profiles.a2dp = ((trusted_profiles & (PROFILE_SUPPORTED << A2DP_SHIFT_OFFSET)) >> A2DP_SHIFT_OFFSET); #endif /* Load device blocked */ blocked = g_key_file_get_boolean(key_file, "General", "Blocked", NULL); if (blocked) device_block(device, FALSE); /* Load device profile list */ uuids = g_key_file_get_string_list(key_file, "General", "Services", NULL, NULL); if (uuids) { load_services(device, uuids); /* Discovered services restored from storage */ device->bredr_state.svc_resolved = true; } /* Load device id */ source = g_key_file_get_integer(key_file, "DeviceID", "Source", NULL); if (source) { vendor = g_key_file_get_integer(key_file, "DeviceID", "Vendor", NULL); product = g_key_file_get_integer(key_file, "DeviceID", "Product", NULL); version = g_key_file_get_integer(key_file, "DeviceID", "Version", NULL); btd_device_set_pnpid(device, source, vendor, product, version); } /* 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; } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY /* Load Service changed Registered flag */ svc_change_regd = g_key_file_get_boolean(key_file, "Att", "SvcChangeRegd", NULL); bt_att_set_svc_changed_indication_registered(device->att, svc_change_regd); #endif 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; snprintf(filename, PATH_MAX, STORAGEDIR "/%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_error_free(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_desc_value(struct gatt_db_attribute *attrib, int err, void *user_data) { if (err) warn("loading descriptor value to db failed"); } static ssize_t str2val(const char *str, uint8_t *val, size_t len) { const char *pos = str; size_t i; for (i = 0; i < len; i++) { if (sscanf(pos, "%2hhx", &val[i]) != 1) break; pos += 2; } return i; } static int load_desc(char *handle, char *value, struct gatt_db_attribute *service) { char uuid_str[MAX_LEN_UUID_STR]; struct gatt_db_attribute *att; uint16_t handle_int; uint16_t val; bt_uuid_t uuid, ext_uuid; if (sscanf(handle, "%04hx", &handle_int) != 1) return -EIO; /* Check if there is any value stored, otherwise it is just the UUID */ if (sscanf(value, "%04hx:%s", &val, uuid_str) != 2) { if (sscanf(value, "%s", uuid_str) != 1) return -EIO; val = 0; } DBG("loading descriptor handle: 0x%04x, value: 0x%04x, value uuid: %s", handle_int, val, uuid_str); bt_string_to_uuid(&uuid, uuid_str); bt_uuid16_create(&ext_uuid, GATT_CHARAC_EXT_PROPER_UUID); /* If it is CEP then it must contain the value */ if (!bt_uuid_cmp(&uuid, &ext_uuid) && !val) { warn("cannot load CEP descriptor without value"); return -EIO; } att = gatt_db_service_insert_descriptor(service, handle_int, &uuid, 0, NULL, NULL, NULL); if (!att || gatt_db_attribute_get_handle(att) != handle_int) { warn("loading descriptor to db failed"); return -EIO; } if (val) { if (!gatt_db_attribute_write(att, 0, (uint8_t *)&val, sizeof(val), 0, NULL, load_desc_value, NULL)) return -EIO; } return 0; } static int load_chrc(char *handle, char *value, struct gatt_db_attribute *service) { uint16_t properties, value_handle, handle_int; char uuid_str[MAX_LEN_UUID_STR]; struct gatt_db_attribute *att; char val_str[32]; uint8_t val[16]; size_t val_len; bt_uuid_t uuid; if (sscanf(handle, "%04hx", &handle_int) != 1) return -EIO; /* Check if there is any value stored */ if (sscanf(value, GATT_CHARAC_UUID_STR ":%04hx:%02hx:%32s:%s", &value_handle, &properties, val_str, uuid_str) != 4) { if (sscanf(value, GATT_CHARAC_UUID_STR ":%04hx:%02hx:%s", &value_handle, &properties, uuid_str) != 3) return -EIO; val_len = 0; } else val_len = str2val(val_str, val, sizeof(val)); bt_string_to_uuid(&uuid, uuid_str); /* Log debug message. */ DBG("loading characteristic handle: 0x%04x, value handle: 0x%04x," " properties 0x%04x value: %s uuid: %s", handle_int, value_handle, properties, val_len ? val_str : "", uuid_str); att = gatt_db_service_insert_characteristic(service, value_handle, &uuid, 0, properties, NULL, NULL, NULL); if (!att || gatt_db_attribute_get_handle(att) != value_handle) { warn("loading characteristic to db failed"); return -EIO; } if (val_len) { if (!gatt_db_attribute_write(att, 0, val, val_len, 0, NULL, load_desc_value, NULL)) return -EIO; } return 0; } static int load_incl(struct gatt_db *db, char *handle, char *value, struct gatt_db_attribute *service) { char uuid_str[MAX_LEN_UUID_STR]; struct gatt_db_attribute *att; uint16_t start, end; if (sscanf(handle, "%04hx", &start) != 1) return -EIO; if (sscanf(value, GATT_INCLUDE_UUID_STR ":%04hx:%04hx:%s", &start, &end, uuid_str) != 3) return -EIO; /* Log debug message. */ DBG("loading included service: 0x%04x, end: 0x%04x, uuid: %s", start, end, uuid_str); att = gatt_db_get_attribute(db, start); if (!att) { warn("loading included service to db failed - no such service"); return -EIO; } att = gatt_db_service_add_included(service, att); if (!att) { warn("loading included service to db failed"); return -EIO; } return 0; } static int load_service(struct gatt_db *db, char *handle, char *value) { struct gatt_db_attribute *att; uint16_t start, end; char type[MAX_LEN_UUID_STR], uuid_str[MAX_LEN_UUID_STR]; bt_uuid_t uuid; bool primary; if (sscanf(handle, "%04hx", &start) != 1) return -EIO; if (sscanf(value, "%[^:]:%04hx:%s", type, &end, uuid_str) != 3) return -EIO; if (g_str_equal(type, GATT_PRIM_SVC_UUID_STR)) primary = true; else if (g_str_equal(type, GATT_SND_SVC_UUID_STR)) primary = false; else return -EIO; bt_string_to_uuid(&uuid, uuid_str); /* Log debug message. */ DBG("loading service: 0x%04x, end: 0x%04x, uuid: %s", start, end, uuid_str); att = gatt_db_insert_service(db, start, &uuid, primary, end - start + 1); if (!att) { error("Unable load service into db!"); return -EIO; } return 0; } static int load_gatt_db_impl(GKeyFile *key_file, char **keys, struct gatt_db *db) { struct gatt_db_attribute *current_service; char **handle, *value, type[MAX_LEN_UUID_STR]; int ret; /* first load service definitions */ for (handle = keys; *handle; handle++) { value = g_key_file_get_string(key_file, "Attributes", *handle, NULL); if (sscanf(value, "%[^:]:", type) != 1) { warn("Missing Type in attribute definition"); g_free(value); return -EIO; } if (g_str_equal(type, GATT_PRIM_SVC_UUID_STR) || g_str_equal(type, GATT_SND_SVC_UUID_STR)) { ret = load_service(db, *handle, value); if (ret) { g_free(value); return ret; } } g_free(value); } current_service = NULL; /* then fill them with data*/ for (handle = keys; *handle; handle++) { value = g_key_file_get_string(key_file, "Attributes", *handle, NULL); if (sscanf(value, "%[^:]:", type) != 1) { warn("Missing Type in attribute definition"); g_free(value); return -EIO; } if (g_str_equal(type, GATT_PRIM_SVC_UUID_STR) || g_str_equal(type, GATT_SND_SVC_UUID_STR)) { uint16_t tmp; uint16_t start, end; bool primary; bt_uuid_t uuid; char uuid_str[MAX_LEN_UUID_STR]; if (sscanf(*handle, "%04hx", &tmp) != 1) { warn("Unable to parse attribute handle"); g_free(value); return -EIO; } if (current_service) gatt_db_service_set_active(current_service, true); current_service = gatt_db_get_attribute(db, tmp); gatt_db_attribute_get_service_data(current_service, &start, &end, &primary, &uuid); bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); } else if (g_str_equal(type, GATT_INCLUDE_UUID_STR)) { ret = load_incl(db, *handle, value, current_service); } else if (g_str_equal(type, GATT_CHARAC_UUID_STR)) { ret = load_chrc(*handle, value, current_service); } else { ret = load_desc(*handle, value, current_service); } g_free(value); if (ret) { gatt_db_clear(db); return ret; } } if (current_service) gatt_db_service_set_active(current_service, true); return 0; } static void load_gatt_db(struct btd_device *device, const char *local, const char *peer) { char **keys, filename[PATH_MAX]; GKeyFile *key_file; GError *gerr = NULL; if (!gatt_cache_is_enabled(device)) return; DBG("Restoring %s gatt database from file", peer); snprintf(filename, PATH_MAX, STORAGEDIR "/%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, "Attributes", NULL, NULL); if (!keys) { warn("No cache for %s", peer); g_key_file_free(key_file); return; } if (load_gatt_db_impl(key_file, keys, device->db)) warn("Unable to load gatt db from file for %s", peer); g_strfreev(keys); g_key_file_free(key_file); 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) { if (profile->remote_uuid == NULL) return false; if (g_slist_find_custom(uuids, profile->remote_uuid, bt_uuid_strcmp) == NULL) { #ifdef TIZEN_BT_HID_DEVICE_ENABLE if (strcmp(profile->name, "hid-device") == 0) return true; #endif 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); } static void device_add_gatt_services(struct btd_device *device) { char addr[18]; ba2str(&device->bdaddr, addr); if (device->blocked) { DBG("Skipping profiles for blocked device %s", addr); return; } gatt_db_foreach_service(device->db, NULL, add_gatt_service, device); } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY static void accept_gatt_service(struct gatt_db_attribute *attr, void *user_data) { struct btd_device *device = user_data; GSList *l; bt_uuid_t uuid; char uuid_str[MAX_LEN_UUID_STR]; 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_accept(l->data); } #endif static void device_accept_gatt_profiles(struct btd_device *device) { #ifndef TIZEN_FEATURE_BLUEZ_MODIFY GSList *l; for (l = device->services; l != NULL; l = g_slist_next(l)) service_accept(l->data); #else gatt_db_foreach_service(device->db, NULL, accept_gatt_service, device); #endif } 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 happend 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); #ifndef TIZEN_FEATURE_BLUEZ_MODIFY DBG("address %s", address); #endif device = g_try_malloc0(sizeof(struct btd_device)); if (device == NULL) return NULL; device->tx_power = 127; 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, #ifdef TIZEN_FEATURE_BLUEZ_MODIFY device_methods, device_signals, #else device_methods, NULL, #endif device_properties, device, device_free) == FALSE) { error("Unable to register device interface for %s", address); device_free(device); return NULL; } device->adapter = adapter; 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); } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY void device_print_addr(struct btd_device *dev) { char ida[18]; char rpa[18]; ba2str(&dev->bdaddr, ida); if (dev->rpa) { ba2str(dev->rpa, rpa); DBG("IDA %s [%d] : RPA [%s], BREDR [%d], LE [%d]", ida, dev->bdaddr_type, rpa, dev->bredr ? 1 : 0, dev->le ? 1 : 0); } else { DBG("ADDR %s [%d] : BREDR [%d], LE [%d]", ida, dev->bdaddr_type, dev->bredr ? 1 : 0, dev->le ? 1 : 0); } } #endif 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 = true; else device->le = true; 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 *filename) { 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); #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (device->rpa) ba2str(device->rpa, dstaddr); #endif if (!filename) return g_strdup_printf(STORAGEDIR "/%s/%s", btd_adapter_get_storage_dir(device->adapter), dstaddr); return g_strdup_printf(STORAGEDIR "/%s/%s/%s", btd_adapter_get_storage_dir(device->adapter), dstaddr, 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); /* If now < failed_time, it means the clock has somehow turned back, * possibly because of system restart. Allow name request in this case. */ return now.tv_sec < device->name_resolve_failed_time || now.tv_sec >= device->name_resolve_failed_time + btd_opts.name_request_retry_delay; } 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_update_addr(struct btd_device *device, const bdaddr_t *bdaddr, uint8_t bdaddr_type) { bool auto_connect = device->auto_connect; 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. */ device->le = true; /* 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; 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) { #ifdef TIZEN_FEATURE_BLUEZ_MODIFY char addr_str[18]; if (device->rpa) { ba2str(device->rpa, addr_str); error("Cannot set bredr support to RPA device [%s]", addr_str); return; } if (device->bdaddr_type == BDADDR_LE_RANDOM) { ba2str(&device->bdaddr, addr_str); error("Cannot set bredr support to LE random device [%s]", addr_str); return; } #endif if (device->bredr) return; device->bredr = true; store_device_info(device); } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY void device_set_rpa(struct btd_device *device, const bdaddr_t *rpa) { if (device->rpa == NULL) { device->rpa = g_malloc0(sizeof(bdaddr_t)); bacpy(device->rpa, rpa); } else error("RPA is already set"); } void device_set_irk_value(struct btd_device *device, const uint8_t *val) { memcpy(&device->irk_val, val, sizeof(device->irk_val)); } #endif void device_set_le_support(struct btd_device *device, uint8_t bdaddr_type) { if (device->le) return; device->le = true; device->bdaddr_type = bdaddr_type; store_device_info(device); } static bool device_disappeared(gpointer user_data) { struct btd_device *dev = user_data; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (dev->le_state.connected) { DBG("GATT connection exists, don't remove the device"); return FALSE; } #endif 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); } void device_update_last_seen(struct btd_device *device, uint8_t bdaddr_type) { if (bdaddr_type == BDADDR_BREDR) device->bredr_seen = time(NULL); else device->le_seen = time(NULL); if (!device_is_temporary(device)) return; /* Restart temporary timer */ set_temporary_timer(device, btd_opts.tmpto); } /* 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 * ovewrite 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); #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (device->rpa) ba2str(device->rpa, device_addr); #endif snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s", btd_adapter_get_storage_dir(device->adapter), device_addr); delete_folder_tree(filename); snprintf(filename, PATH_MAX, STORAGEDIR "/%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); 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); } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY void device_unpair(struct btd_device *device, gboolean remove_stored) { DBG("+"); DBG("Unpairing device %s", device->path); 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)) disconnect_all(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); gatt_db_clear(device->db); if (device->rpa) { bacpy(&device->bdaddr, device->rpa); device->bdaddr_type = BDADDR_LE_RANDOM; g_free(device->rpa); device->rpa = NULL; } device->bredr_state.paired = 0; device->le_state.paired = 0; device->bredr_state.svc_resolved = false; device->trusted = false; device->trusted_profiles.pbap = SHOW_AUTHORIZATION; device->trusted_profiles.map = SHOW_AUTHORIZATION; device->trusted_profiles.sap = SHOW_AUTHORIZATION; device->trusted_profiles.hfp_hs = SUPPORTED_TRUSTED; device->trusted_profiles.a2dp = SUPPORTED_TRUSTED; if (device->alias != NULL) { /* Remove alias name because * In UG if we rename and then unpair device and * initiates connection without scanning then paired * list will have alias name as first preference is * given to alias name. */ DBG("Freeing device alias name"); g_free(device->alias); device->alias = NULL; } g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Paired"); // btd_device_unref(device); DBG("-"); } void device_remove_stored_folder(struct btd_device *device) { const bdaddr_t *src = btd_adapter_get_address(device->adapter); char adapter_addr[18]; char device_addr[18]; char filename[PATH_MAX]; ba2str(src, adapter_addr); ba2str(&device->bdaddr, device_addr); snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s", adapter_addr, device_addr); delete_folder_tree(filename); } #endif void device_remove(struct btd_device *device, gboolean remove_stored) { DBG("Removing device %s", device->path); 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); } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY int device_rpa_cmp(gconstpointer a, gconstpointer b) { const struct btd_device *device = a; const char *address = b; char addr[18]; if (!device->rpa) return -1; ba2str(device->rpa, addr); return strcasecmp(addr, address); } int device_addr_cmp(gconstpointer a, gconstpointer b) { const struct btd_device *device = a; const bdaddr_t *bdaddr = b; return bacmp(&device->bdaddr, bdaddr); } int device_rpa_ida_cmp(gconstpointer a, gconstpointer b) { const struct btd_device *device = a; const char *address = b; char addr[18]; if (!device->rpa || device->le == false) return -1; ba2str(&device->bdaddr, addr); return strcasecmp(addr, address); } #endif 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; } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY int device_addr_type_strict_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); if (addr->bdaddr_type == BDADDR_BREDR) { if (!dev->bredr) return -1; return cmp; } if (!dev->le) return -1; if (cmp && dev->rpa && addr->bdaddr_type == BDADDR_LE_RANDOM && (addr->bdaddr.b[5] >> 6) == 0x01) return bacmp(dev->rpa, &addr->bdaddr); if (addr->bdaddr_type != dev->bdaddr_type) return -1; return cmp; } #endif 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)) #ifdef TIZEN_FEATURE_BLUEZ_MODIFY { if (dev->rpa && addr->bdaddr_type == BDADDR_BREDR) { char addr_str[18]; ba2str(&dev->bdaddr, addr_str); DBG("Don't match. LE Only device [%s]", addr_str); return -1; } return 0; } #else return 0; #endif 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 (addr->bdaddr_type == dev->conn_bdaddr_type) return bacmp(&dev->conn_bdaddr, &addr->bdaddr); return -1; } return cmp; } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY #ifdef TIZEN_FEATURE_BLUEZ_BATTERY_WATCH void device_change_pkt_type(gpointer data, gpointer user_data) { int pkt_type = (int)user_data; struct btd_device *device = data; struct hci_conn_info_req *cr; set_conn_ptype_cp cp; char addr[18]; int hdev = 0; int err = 0; /* Change a packet type only for Phone device */ if ((device->class & 0x00001F00) >> 8 != 0x02) return; if (!device->bredr_state.connected) return; hdev = hci_open_dev(0); if (hdev < 0) { error("Cannot open hdev"); return; } cr = g_malloc0(sizeof(*cr) + sizeof(struct hci_conn_info)); if (cr == NULL) { error("Out of memory"); return; } cr->type = ACL_LINK; bacpy(&cr->bdaddr, &device->bdaddr); err = ioctl(hdev, HCIGETCONNINFO, cr); if (err < 0) { error("Fail to get HCIGETCOINFO"); g_free(cr); hci_close_dev(hdev); return; } cp.handle = cr->conn_info->handle; g_free(cr); cp.pkt_type = cpu_to_le16((uint16_t)pkt_type); ba2str(&device->bdaddr, addr); DBG("Handle %d, Addr %s", cp.handle, addr); DBG("Send Change pkt type request : 0x%X", pkt_type); if (hci_send_cmd(hdev, OGF_LINK_CTL, OCF_SET_CONN_PTYPE, SET_CONN_PTYPE_CP_SIZE, &cp) < 0) { error("hci_send_cmd is failed"); hci_close_dev(hdev); return; } hci_close_dev(hdev); return; } #endif /* TIZEN_FEATURE_BLUEZ_BATTERY_WATCH */ #endif 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; } 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. */ #ifndef TIZEN_FEATURE_BLUEZ_MODIFY if (profile->auto_connect && profile->accept) device_set_auto_connect(device, TRUE); #endif return service; } static void dev_probe(struct btd_profile *p, void *user_data) { struct probe_data *d = user_data; struct btd_service *service; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (find_service_with_profile(d->dev->services, p)) { DBG("%s is already probed.(UUID:%s)", p->name, p->remote_uuid); return; } #endif 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 || !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); #ifdef TIZEN_BT_HID_DEVICE_ENABLE if (l == NULL) { if (g_strcmp0(profile->local_uuid , HID_DEVICE_UUID) == 0) { l = find_service_with_uuid(device->services, HID_DEVICE_UUID); if (l == NULL) return; service = l->data; if (btd_service_get_state(service) == BTD_SERVICE_STATE_CONNECTED) { int err; err = btd_service_disconnect(service); if (err) error("error: %s", strerror(-err)); } } return; } #else if (l == NULL) return; #endif 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]; ba2str(&device->bdaddr, addr); if (device->blocked) { DBG("Skipping profiles for blocked device %s", addr); goto add_uuids; } DBG("Probing profiles for device %s", addr); 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); snprintf(sdp_file, PATH_MAX, STORAGEDIR "/%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; } snprintf(att_file, PATH_MAX, STORAGEDIR "/%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, 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: #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (!req->msg) goto done; /* since no new services are found, UUID signal is not emitted, ** so send a reply to the framework with the existing services */ if (dbus_message_is_method_call(req->msg, DEVICE_INTERFACE, "DiscoverServices")) discover_services_reply(req, err, device->tmp_records); done: #endif /* 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: #ifdef TIZEN_FEATURE_BLUEZ_MODIFY device_set_gatt_connected(device, FALSE); #endif attio_cleanup(device); } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY static void att_mtu_changed(uint16_t mtu, void *user_data) { struct btd_device *device = user_data; DBG("att mtu changed %d", mtu); g_dbus_emit_signal(dbus_conn, device->path, DEVICE_INTERFACE, "AttMtuChanged", DBUS_TYPE_UINT16, &mtu, DBUS_TYPE_INVALID); } #endif 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); #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (req) { if (req->search_uuid) DBG("browse req. is for SDP. Ignore it."); else update_gatt_uuids(req, device->primaries, services); } #else if (req) update_gatt_uuids(req, device->primaries, services); #endif #ifndef TIZEN_FEATURE_BLUEZ_MODIFY /* do not delete existing primary list, * just append the new primary uuids, * the list will be modifed when service changed * indication is received during connection */ g_slist_free_full(device->primaries, g_free); device->primaries = NULL; #endif 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; } #ifndef TIZEN_FEATURE_BLUEZ_MODIFY /* Register the services after setting the client is ready * and exporting all the services and characteristics paths. */ register_gatt_services(device); #endif btd_gatt_client_ready(device->client_dbus); #ifdef TIZEN_FEATURE_BLUEZ_MODIFY register_gatt_services(device); #endif device_svc_resolved(device, BROWSE_GATT, device->bdaddr_type, 0); store_gatt_db(device); #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (device->name[0] == '\0') { char *name = NULL; name = bt_gatt_client_get_gap_device_name(device->client); if (name) strncpy(device->name, name, MAX_NAME_LENGTH); } #endif } static void gatt_client_service_changed(uint16_t start_handle, uint16_t end_handle, void *user_data) { DBG("start 0x%04x, end: 0x%04x", start_handle, end_handle); } static void gatt_debug(const char *str, void *user_data) { DBG("%s", str); } static void gatt_client_init(struct btd_device *device) { gatt_client_cleanup(device); if (!device->connect && !btd_opts.reverse_discovery) { DBG("Reverse service discovery disabled: skipping GATT client"); return; } device->client = bt_gatt_client_new(device->db, device->att, device->att_mtu, 0); if (!device->client) { DBG("Failed to initialize"); return; } bt_gatt_client_set_debug(device->client, gatt_debug, NULL, NULL); /* * Notify notify existing service about the new connection so they can * react to notifications while discovering services */ 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 (device->connect) 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; } bt_att_set_enc_key_size(device->att, device->ltk_enc_size); bt_gatt_server_set_debug(device->server, gatt_debug, NULL, NULL); #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (!bt_gatt_server_set_mtu_changed(device->server, att_mtu_changed, device, NULL)) { DBG("Failed to set mtu changed handler"); return; } #endif } 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; } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY static bool load_svc_change_indication_status(struct btd_device *device, const char *local, const char *peer) { char filename[PATH_MAX]; GKeyFile *key_file; gboolean svc_change_regd = false; snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", local, peer); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, NULL)) goto failed; /* Load Service changed Registered flag */ svc_change_regd = g_key_file_get_boolean(key_file, "Att", "SvcChangeRegd", NULL); bt_att_set_svc_changed_indication_registered(device->att, svc_change_regd); failed: g_key_file_free(key_file); return svc_change_regd; } #endif 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]; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY uint8_t dst_type = BDADDR_BREDR; char srcaddr[18]; const bdaddr_t *src; #endif bt_io_get(io, &gerr, BT_IO_OPT_SEC_LEVEL, &sec_level, BT_IO_OPT_IMTU, &mtu, BT_IO_OPT_CID, &cid, #ifdef TIZEN_FEATURE_BLUEZ_MODIFY BT_IO_OPT_DEST_TYPE, &dst_type, #endif 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; } #ifndef TIZEN_FEATURE_BLUEZ_MODIFY if (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; } } #endif dev->att_mtu = MIN(mtu, btd_opts.gatt_mtu); attrib = g_attrib_new(io, cid == 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); #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (dst_type != BDADDR_BREDR && device_get_rpa_exist(dev) == true) { bt_att_set_remote_addr(dev->att, device_get_rpa(dev), BDADDR_LE_RANDOM); } else { bt_att_set_remote_addr(dev->att, &dev->bdaddr, dev->bdaddr_type); } #endif 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); #ifdef TIZEN_FEATURE_BLUEZ_MODIFY src = btd_adapter_get_address(dev->adapter); ba2str(src, srcaddr); /* load the service changed indication status on connection */ load_svc_change_indication_status(dev, srcaddr, dstaddr); #endif return true; } static void att_connect_cb(GIOChannel *io, GError *gerr, gpointer user_data) { struct btd_device *device = user_data; DBusMessage *reply; uint8_t 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; } 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 = 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); if (device->connect) { if (err < 0) reply = btd_error_failed(device->connect, btd_error_le_conn_from_errno(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) return -EALREADY; ba2str(&dev->bdaddr, addr); DBG("Connection attempt to: %s", addr); 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, 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; 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; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY DBG(""); #endif 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, 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; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY DBG(""); #endif 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) { #ifdef TIZEN_FEATURE_BLUEZ_MODIFY device->browse = NULL; #endif browse_request_free(req); return err; } return err; } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY void device_set_last_addr_type(struct btd_device *device, uint8_t type) { if (!device) return; //DBG("Last addr type %d", type); device->last_bdaddr_type = type; } gboolean device_is_ipsp_connected(struct btd_device * device) { return device->ipsp_connected; } void device_set_ipsp_connected(struct btd_device *device, gboolean connected, const unsigned char *ifname) { char *iface_name = NULL; if (device == NULL) { error("device is NULL"); return; } if (device->ipsp_connected == connected) return; device->ipsp_connected = connected; memset(device->if_name, 0, sizeof(device->if_name)); memcpy(device->if_name, ifname, 16); iface_name = device->if_name; DBG("ipsp_connected %d", connected); DBG("ipsp_iface: %s is Up !", iface_name); g_dbus_emit_signal(dbus_conn, device->path, DEVICE_INTERFACE, "IpspStateChanged", DBUS_TYPE_BOOLEAN, &connected, DBUS_TYPE_STRING, &iface_name, DBUS_TYPE_INVALID); } void device_le_data_length_changed(struct btd_device *device, uint16_t max_tx_octets, uint16_t max_tx_time, uint16_t max_rx_octets, uint16_t max_rx_time) { if (device == NULL) { error("device is NULL"); return; } device->max_tx_octets = max_tx_octets; device->max_tx_time = max_tx_time; device->max_rx_octets = max_rx_octets; device->max_rx_time = max_rx_time; DBG("data length changed values :max_tx_octets: %d max_tx_time: %d max_rx_octets: %d max_rx_time: %d", max_tx_octets, max_tx_time, max_rx_octets, max_rx_time); g_dbus_emit_signal(dbus_conn, device->path, DEVICE_INTERFACE, "LEDataLengthChanged", DBUS_TYPE_UINT16, &max_tx_octets, DBUS_TYPE_UINT16, &max_tx_time, DBUS_TYPE_UINT16, &max_rx_octets, DBUS_TYPE_UINT16, &max_rx_time, DBUS_TYPE_INVALID); } const bdaddr_t *device_get_rpa(struct btd_device *device) { return device->rpa; } const uint8_t *device_get_irk_value(struct btd_device *device) { return device->irk_val; } bool device_get_rpa_exist(struct btd_device *device) { return device->rpa ? true : false; } void device_set_auth_addr_type(struct btd_device *device, uint8_t type) { if (!device) return; DBG("Auth addr type %d", type); device->auth_bdaddr_type = type; } void device_get_tizen_addr(struct btd_device *device, uint8_t type, struct device_addr_type *addr) { if (!device || !addr) return; if (type == BDADDR_BREDR) { bacpy(&addr->bdaddr, &device->bdaddr); addr->bdaddr_type = BDADDR_BREDR; return; } if (device->rpa) { bacpy(&addr->bdaddr, device->rpa); addr->bdaddr_type = BDADDR_LE_RANDOM; return; } bacpy(&addr->bdaddr, &device->bdaddr); addr->bdaddr_type = device->bdaddr_type; } #endif 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"); } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY void device_disconnect_blocked(struct btd_device *device, char *uuid) { struct btd_service *service; GSList *l; if (!device || !uuid) return; l = find_service_with_uuid(device->services, uuid); if (l == NULL) return; service = l->data; if (btd_service_get_state(service) == BTD_SERVICE_STATE_CONNECTED) { int err; err = btd_service_disconnect(service); if (err) error("error: %s", strerror(-err)); } } void btd_device_set_trusted_profiles(struct btd_device *device, uint32_t pbap, uint32_t map, uint32_t sap, uint32_t hfp_hs, uint32_t a2dp) { if (!device) return; DBG("TrustedProfiles Parameters: [PBAP %d] [MAP %d] [SAP %d] [HFP %d] [A2DP %d]", pbap, map, sap, hfp_hs, a2dp); if (device->trusted_profiles.pbap == pbap && device->trusted_profiles.map == map && device->trusted_profiles.sap == sap && device->trusted_profiles.hfp_hs == hfp_hs && device->trusted_profiles.a2dp == a2dp) return; /* Disconnect OBEX based profiles if connected */ if (device->trusted_profiles.pbap != pbap) { device->trusted_profiles.pbap = pbap; if (pbap == SUPPORTED_BLOCKED) device_disconnect_blocked(device, OBEX_PSE_UUID); } else if (device->trusted_profiles.map != map) { device->trusted_profiles.map = map; if (map == SUPPORTED_BLOCKED) device_disconnect_blocked(device, OBEX_MAP_UUID); } else if (device->trusted_profiles.sap != sap) { device->trusted_profiles.sap = sap; if (sap == SUPPORTED_BLOCKED) device_disconnect_blocked(device, SAP_UUID); } else if (device->trusted_profiles.hfp_hs != hfp_hs) { device->trusted_profiles.hfp_hs = hfp_hs; } else if (device->trusted_profiles.a2dp != a2dp) { device->trusted_profiles.a2dp = a2dp; } store_device_info(device); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "TrustedProfiles"); } #endif void device_set_bonded(struct btd_device *device, uint8_t bdaddr_type) { if (!device) return; DBG(""); if (bdaddr_type == BDADDR_BREDR) device->bredr_state.bonded = true; else device->le_state.bonded = true; btd_device_set_temporary(device, false); } 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_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); snprintf(filename, PATH_MAX, STORAGEDIR "/%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); } /* 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); snprintf(filename, PATH_MAX, STORAGEDIR "/%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; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (rssi == 0 || device->rssi == 0) { if (device->rssi == rssi) return; } device->rssi = rssi; DBG("rssi %d", rssi); #else 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; } #endif 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; #ifndef TIZEN_FEATURE_BLUEZ_MODIFY DBG("flags %d", flags); #endif if (device->ad_flags[0] == flags) return; device->ad_flags[0] = flags; g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "AdvertisingFlags"); } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY void device_set_le_connectable(struct btd_device *device, uint8_t adv_type) { if (!device) return; if (device->le_connectable) return; if (adv_type == ADV_TYPE_IND || adv_type == ADV_TYPE_DIRECT_IND) device->le_connectable = true; } #endif bool device_is_connectable(struct btd_device *device) { if (!device) return false; if (device->bredr) return true; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY /* Check the ADV type. Some beacon device can be connectable regardless adv flags */ return device->le_connectable; #else /* Check if either Limited or General discoverable are set */ return (device->ad_flags[0] & 0x03); #endif } 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 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; } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (dev->bonding == NULL) #endif 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 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); 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 (!device_is_paired(device, bdaddr_type) && !device_is_trusted(device)) btd_device_set_temporary(device, true); device_bonding_failed(device, status); #ifdef TIZEN_FEATURE_BLUEZ_MODIFY device->legacy_pairing = false; #endif return; } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY device->legacy_pairing = false; #endif device_auth_req_free(device); /* If we're already paired nothing more is needed */ if (state->paired) { #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (bdaddr_type == BDADDR_BREDR && state->svc_resolved) { DBG("Link key has been changed. Report it"); if (!device->rpa) g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Paired"); else DBG("Just overwrite Link key"); } else if (bdaddr_type == BDADDR_LE_RANDOM || bdaddr_type == BDADDR_LE_PUBLIC) { DBG("Long Term Key has been changed. Report it"); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Paired"); } #endif /* TIZEN_FEATURE_BLUEZ_MODIFY */ return; } device_set_paired(device, bdaddr_type); /* If services are already resolved just reply to the pairing * request */ if (state->svc_resolved && bonding) { /* Attept 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); } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY else if (!dev->browse) { DBG("Service is not going on. Start discovery"); dev->discov_timer = timeout_add_seconds( 0, start_discovery, dev, NULL); } else DBG("Wait for service discovery"); #endif return cb->id; } bool device_remove_svc_complete_callback(struct btd_device *dev, unsigned int id) { GSList *l; for (l = dev->svc_callbacks; l != NULL; l = g_slist_next(l)) { struct svc_callback *cb = l->data; if (cb->id != id) continue; if (cb->idle_id > 0) g_source_remove(cb->idle_id); dev->svc_callbacks = g_slist_remove(dev->svc_callbacks, cb); g_free(cb); return true; } return false; } gboolean device_is_bonding(struct btd_device *device, const char *sender) { struct bonding_req *bonding = device->bonding; if (!device->bonding) return FALSE; if (!sender) return TRUE; return g_str_equal(sender, dbus_message_get_sender(bonding->msg)); } static gboolean device_bonding_retry(gpointer data) { struct btd_device *device = data; struct btd_adapter *adapter = device_get_adapter(device); struct bonding_req *bonding = device->bonding; uint8_t io_cap; int err; if (!bonding) return FALSE; DBG("retrying bonding"); bonding->retry_timer = 0; /* Restart the bonding timer to the begining of the pairing. If not * pincode request/reply occurs during this retry, * device_bonding_last_duration() will return a consistent value from * this point. */ device_bonding_restart_timer(device); if (bonding->agent) io_cap = agent_get_io_capability(bonding->agent); else io_cap = IO_CAPABILITY_NOINPUTNOOUTPUT; err = adapter_bonding_attempt(adapter, &device->bdaddr, device->bdaddr_type, io_cap); if (err < 0) device_bonding_complete(device, bonding->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) { #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (device->legacy_pairing) { DBG("Emit LegacyPaired"); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "LegacyPaired"); } #endif return; } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY btd_device_set_temporary(device, TRUE); #endif 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; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY btd_adapter_confirm_reply(device->adapter, &device->bdaddr, auth->addr_type, err ? FALSE : TRUE); device_set_auth_addr_type(device, BDADDR_BREDR); #else btd_adapter_confirm_reply(device->adapter, &device->bdaddr, auth->addr_type, err ? FALSE : TRUE); #endif 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; #ifdef TIZEN_FEATURE_BLUEZ_MODIFY btd_adapter_passkey_reply(device->adapter, &device->bdaddr, auth->addr_type, passkey); device_set_auth_addr_type(device, BDADDR_BREDR); #else btd_adapter_passkey_reply(device->adapter, &device->bdaddr, auth->addr_type, passkey); #endif 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; #ifndef TIZEN_FEATURE_BLUEZ_CONFIRM_ONLY 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 #endif 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 (!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); #ifdef TIZEN_FEATURE_BLUEZ_MODIFY device_set_auth_addr_type(device, BDADDR_BREDR); #endif } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY gboolean device_is_authenticating(struct btd_device *dev, uint8_t bdaddr_type) { return (dev->auth_bdaddr_type == bdaddr_type && dev->authr != NULL); } #else gboolean device_is_authenticating(struct btd_device *device) { return (device->authr != NULL); } #endif 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; } 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; if (g_slist_find_custom(device->uuids, uuid, bt_uuid_strcmp)) return; new_uuid = g_strdup(uuid); uuid_list = g_slist_append(NULL, new_uuid); device_probe_profiles(device, uuid_list); g_free(new_uuid); g_slist_free(uuid_list); store_device_info(device); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "UUIDs"); } static sdp_list_t *read_device_records(struct btd_device *device) { char local[18], peer[18]; char filename[PATH_MAX]; 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); #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (device->rpa) ba2str(device->rpa, peer); #endif snprintf(filename, PATH_MAX, STORAGEDIR "/%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; } void btd_device_unref(struct btd_device *device) { if (__sync_sub_and_fetch(&device->ref_count, 1)) return; if (!device->path) { error("freeing device without an object path"); return; } DBG("Freeing device %s", device->path); g_dbus_unregister_interface(dbus_conn, device->path, DEVICE_INTERFACE); } int device_get_appearance(struct btd_device *device, uint16_t *value) { if (device->appearance == 0) return -1; if (value) *value = device->appearance; return 0; } void device_set_appearance(struct btd_device *device, uint16_t value) { const char *icon = gap_appearance_to_icon(value); 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); } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY int device_get_rpa_res_char_value(struct btd_device *device) { return device->rpa_res_support; } /* Store the RPA Resolution Characteristic Value of remote device. * This value would be checked before start directed advertising using RPA. */ void device_set_rpa_res_char_value(struct btd_device *device, uint8_t value) { if (device->rpa_res_support == value) return; device->rpa_res_support = value; store_device_info(device); } void device_set_manufacturer_info(struct btd_device *device, struct eir_data *eir) { if (!device) return; if (eir->manufacturer_data_len == 0) return; device->manufacturer_data = g_memdup(eir->manufacturer_data, eir->manufacturer_data_len); device->manufacturer_data_len = eir->manufacturer_data_len; store_device_info(device); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "LegacyManufacturerDataLen"); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "LegacyManufacturerData"); } void device_set_adv_report_info(struct btd_device *device, void *data, uint8_t data_len, uint8_t adv_type, int8_t rssi) { if (!device) return; char peer_addr[18]; const char *paddr = peer_addr; dbus_int32_t rssi_val = rssi; int adv_len = data_len; uint8_t addr_type; ba2str(&device->bdaddr, peer_addr); /* Replace address type for paired RPA device since IDA passed from controller */ if (device->rpa) { ba2str(device->rpa, peer_addr); addr_type = BDADDR_LE_RANDOM; } else addr_type = device->bdaddr_type; g_dbus_emit_signal(dbus_conn, device->path, DEVICE_INTERFACE, "AdvReport", DBUS_TYPE_STRING, &paddr, DBUS_TYPE_BYTE, &addr_type, DBUS_TYPE_BYTE, &adv_type, DBUS_TYPE_INT32, &rssi_val, DBUS_TYPE_INT32, &adv_len, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &data, data_len, DBUS_TYPE_INVALID); } void device_set_payload_timeout(struct btd_device *device, uint16_t payload_timeout) { if (!device) return; if (device->auth_payload_timeout == payload_timeout) return; DBG("Payload timeout %d", payload_timeout); device->auth_payload_timeout = payload_timeout; g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "PayloadTimeout"); } void device_set_disconnect_reason(struct btd_device *device, uint8_t reason) { device->disc_reason = reason; } void btd_device_disconnect(struct btd_device *device) { char dst[18]; struct btd_service *service; btd_service_state_t state; ba2str(&device->bdaddr, dst); DBG(""); if (device->bredr_state.connected == false) return; service = btd_device_get_service(device, HFP_HS_UUID); if (!service) return; state = btd_service_get_state(service); DBG("Connected State : %d", state); if (state == BTD_SERVICE_STATE_DISCONNECTED) { btd_adapter_disconnect_device(device->adapter, &device->bdaddr, BDADDR_BREDR); } return; } #endif 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); } uint32_t btd_device_get_current_flags(struct btd_device *dev) { return dev->current_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; if (!changed_flags) return; if (changed_flags & DEVICE_FLAG_REMOTE_WAKEUP) { 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); #ifdef TIZEN_FEATURE_BLUEZ_MODIFY if (!err) { if (old_state == BTD_SERVICE_STATE_UNAVAILABLE || new_state == BTD_SERVICE_STATE_UNAVAILABLE) DBG("Skip status updating ([%d] -> [%d])", old_state, new_state); else g_dbus_emit_signal(dbus_conn, device->path, DEVICE_INTERFACE, "ProfileStateChanged", DBUS_TYPE_STRING, &profile->remote_uuid, DBUS_TYPE_INT32, &new_state, DBUS_TYPE_INVALID); } if (new_state == BTD_SERVICE_STATE_CONNECTING || new_state == BTD_SERVICE_STATE_DISCONNECTING || new_state == BTD_SERVICE_STATE_UNAVAILABLE) #else if (new_state == BTD_SERVICE_STATE_CONNECTING || new_state == BTD_SERVICE_STATE_DISCONNECTING) #endif 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; #ifdef TIZEN_BT_HID_DEVICE_ENABLE if (g_str_equal(HID_UUID, remote_uuid)) { if (strcmp(p->name, "hid-device") == 0) return service; } #endif } 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); } #ifdef TIZEN_FEATURE_BLUEZ_MODIFY void btd_device_set_legacy_pairing(struct btd_device *dev, bool legacy_pairing) { dev->legacy_pairing = legacy_pairing; } void btd_device_set_svc_changed_indication(struct btd_device *dev, bool value) { bt_att_set_svc_changed_indication_registered(dev->att, value); store_device_info(dev); } bool btd_device_get_svc_changed_indication(struct btd_device *dev) { return bt_att_get_svc_changed_indication_registered(dev->att); } #endif