Return-Path: From: Lukasz Rymanowski To: CC: Lukasz Rymanowski Subject: [RFC 7/8] android/gatt: Add GATT Connect Date: Thu, 13 Mar 2014 19:46:14 +0100 Message-ID: <1394736375-22599-8-git-send-email-lukasz.rymanowski@tieto.com> In-Reply-To: <1394736375-22599-1-git-send-email-lukasz.rymanowski@tieto.com> References: <1394736375-22599-1-git-send-email-lukasz.rymanowski@tieto.com> MIME-Version: 1.0 Content-Type: text/plain Sender: linux-bluetooth-owner@vger.kernel.org List-ID: This patch introduce connect LE device functionality. There is gatt_device representing remote le device. Each gatt device has a list own list of clients as it is possible that more apps would like to use same remote device. Possible connect scenarios: 1. There is no ACL connection to device: Then new dev is put on conn_wait_queue and le scan is enabled. Once device is found we do connect it. Once device is connected then device is moved form conn_wait_queue to conn_list and success event is sent to client(s) with conn_id 2. Device is already connected: Then we update client list, reply with success and do send connect event. 3. For unregisterd clients or uknown conn_id, failed response is sent. --- android/Android.mk | 4 + android/Makefile.am | 3 + android/gatt.c | 475 +++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 463 insertions(+), 19 deletions(-) diff --git a/android/Android.mk b/android/Android.mk index 49bf108..9b1e6b9 100644 --- a/android/Android.mk +++ b/android/Android.mk @@ -59,9 +59,13 @@ LOCAL_SRC_FILES := \ bluez/lib/sdp.c \ bluez/lib/bluetooth.c \ bluez/lib/hci.c \ + bluez/lib/uuid.c \ bluez/btio/btio.c \ bluez/src/sdp-client.c \ bluez/profiles/network/bnep.c \ + bluez/attrib/gattrib.c \ + bluez/attrib/gatt.c \ + bluez/attrib/att.c LOCAL_C_INCLUDES := \ $(call include-path-for, glib) \ diff --git a/android/Makefile.am b/android/Makefile.am index 330529f..7b7d160 100644 --- a/android/Makefile.am +++ b/android/Makefile.am @@ -41,6 +41,9 @@ android_bluetoothd_SOURCES = android/main.c \ android/pan.h android/pan.c \ android/handsfree.h android/handsfree.c \ android/gatt.h android/gatt.c \ + attrib/att.c attrib/att.h \ + attrib/gatt.c attrib/gatt.h \ + attrib/gattrib.c attrib/gattrib.h \ btio/btio.h btio/btio.c \ src/sdp-client.h src/sdp-client.c \ profiles/network/bnep.h profiles/network/bnep.c diff --git a/android/gatt.c b/android/gatt.c index 57883a4..c342a7d 100644 --- a/android/gatt.c +++ b/android/gatt.c @@ -29,10 +29,13 @@ #include #include #include +#include +#include #include "ipc.h" #include "ipc-common.h" #include "lib/sdp.h" +#include "lib/uuid.h" #include "bluetooth.h" #include "gatt.h" #include "src/log.h" @@ -40,16 +43,38 @@ #include "utils.h" #include "src/shared/util.h" #include "src/shared/queue.h" +#include "attrib/gattrib.h" +#include "attrib/att.h" +#include "attrib/gatt.h" +#include "btio/btio.h" struct gatt_client { int32_t id; uint8_t uuid[16]; }; +struct gatt_device { + bdaddr_t bdaddr; + uint8_t bdaddr_type; + + struct queue *clients; + + bool connect_ready; + int32_t conn_id; + + GAttrib *attrib; + GIOChannel *att_io; + + guint watch_id; +}; + static struct ipc *hal_ipc = NULL; static bdaddr_t adapter_addr; + static struct queue *gatt_clients = NULL; static struct queue *scan_clients = NULL; +static struct queue *conn_list = NULL; /* Connected devices */ +static struct queue *conn_wait_queue = NULL; /* Devices waiting for connect */ static bool match_client_by_uuid(const void *data, const void *user_data) { @@ -72,29 +97,25 @@ static bool match_by_value(const void *data, const void *user_data) return data == user_data; } -static void le_device_found_handler(bdaddr_t *addr, uint8_t addr_type, int rssi, - uint16_t eir_len, const void *eir) +static bool match_dev_by_bdaddr(const void *data, const void *user_data) { - uint8_t buf[IPC_MTU]; - struct hal_ev_gatt_client_scan_result *ev = (void *) buf; - char bda[18]; + const struct gatt_device *dev = data; + const bdaddr_t *addr = user_data; - if (queue_isempty(scan_clients)) - return; - - ba2str(addr, bda); - DBG("gatt: LE Device found: %s, rssi: %d, adv_data: %d", bda, rssi, - eir ? true : false); + return !bacmp(&dev->bdaddr, addr); +} - bdaddr2android(addr, ev->bda); - ev->rssi = rssi; - ev->len = eir_len; +static bool match_dev_connect_ready(const void *data, const void *user_data) +{ + const struct gatt_device *dev = data; - memcpy(ev->adv_data, eir, ev->len); + return dev->connect_ready; +} - ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, - HAL_EV_GATT_CLIENT_SCAN_RESULT, - sizeof(ev) + eir_len, ev); +static void destroy_device(struct gatt_device *dev) +{ + queue_destroy(dev->clients, NULL); + free(dev); } static void handle_client_register(const void *buf, uint16_t len) @@ -172,6 +193,253 @@ failed: HAL_OP_GATT_CLIENT_UNREGISTER, status); } +static void connection_cleanup(struct gatt_device *device) +{ + if (device->watch_id) { + g_source_remove(device->watch_id); + device->watch_id = 0; + } + + if (device->att_io) { + g_io_channel_shutdown(device->att_io, FALSE, NULL); + g_io_channel_unref(device->att_io); + device->att_io = NULL; + } + + if (device->attrib) { + GAttrib *attrib = device->attrib; + device->attrib = NULL; + g_attrib_cancel_all(attrib); + g_attrib_unref(attrib); + } +} + +static void send_disconnect_notify(void *data, void *user_data) +{ + struct hal_ev_gatt_client_disconnect ev; + struct gatt_device *dev = user_data; + + ev.client_if = PTR_TO_INT(data); + ev.conn_id = dev->conn_id; + ev.status = HAL_STATUS_SUCCESS; + bdaddr2android(&dev->bdaddr, &ev.bda); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_CLIENT_DISCONNECT, sizeof(ev), &ev); +} + +static bool is_device_wating_for_connect(const bdaddr_t *addr, uint8_t addr_type) +{ + struct gatt_device *dev; + + DBG(""); + + dev = queue_find(conn_wait_queue, match_dev_by_bdaddr, (void *)addr); + if (!dev) + return false; + + dev->bdaddr_type = addr_type; + + /* Mark that this device is ready for connect. + * Need it because will continue with connect after scan is stopped + */ + dev->connect_ready = true; + + return true; +} + +static void bt_le_discovery_stop_cb(void); + +static void le_device_found_handler(bdaddr_t *addr, uint8_t addr_type, + int rssi, uint16_t eir_len, + const void *eir) +{ + uint8_t buf[IPC_MTU]; + struct hal_ev_gatt_client_scan_result *ev = (void *) buf; + char bda[18]; + + if (queue_isempty(scan_clients)) + goto connect; + + ba2str(addr, bda); + DBG("gatt: LE Device found: %s, rssi: %d, adv_data: %d", bda, rssi, + !!eir); + + bdaddr2android(addr, ev->bda); + ev->rssi = rssi; + ev->len = eir_len; + + memcpy(ev->adv_data, eir, ev->len); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_CLIENT_SCAN_RESULT, + sizeof(ev) + eir_len, ev); + +connect: + if (!is_device_wating_for_connect(addr, addr_type)) + return; + + /* We are ok to perform connect now. Stop discovery + * and once it is stopped continue with creating ACL + */ + bt_le_discovery_stop(bt_le_discovery_stop_cb); +} + +static gboolean disconnected_cb(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + bdaddr_t *addr = user_data; + struct gatt_device *dev; + int sock, err = 0; + socklen_t len; + + sock = g_io_channel_unix_get_fd(io); + len = sizeof(err); + getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &len); + + DBG("%s (%d)", strerror(err), err); + + dev = queue_remove_if(conn_list, match_dev_by_bdaddr, addr); + connection_cleanup(dev); + + /* 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) { + if (!queue_push_tail(conn_wait_queue, dev)) { + error("gatt: Cannot push data"); + } else { + bt_le_discovery_start(le_device_found_handler); + return FALSE; + } + } + + queue_foreach(dev->clients, send_disconnect_notify, dev); + destroy_device(dev); + + return FALSE; +} + +static void send_client_connect_notify(void *data, void *user_data) +{ + struct hal_ev_gatt_client_connect *ev = user_data; + int32_t id = PTR_TO_INT(data); + + ev->client_if = id; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_CLIENT_CONNECT, sizeof(*ev), ev); + +} +static void connect_cb(GIOChannel *io, GError *gerr, gpointer user_data) +{ + bdaddr_t *addr = user_data; + struct gatt_device *dev; + struct hal_ev_gatt_client_connect ev; + GAttrib *attrib; + static uint32_t conn_id = 0; + uint8_t status; + + /* Take device form conn waiting queue */ + dev = queue_remove_if(conn_wait_queue, match_dev_by_bdaddr, addr); + if (!dev) { + error("gatt: Device not on the connect wait queue!?"); + g_io_channel_shutdown(io, TRUE, NULL); + return; + } + + g_io_channel_unref(dev->att_io); + dev->att_io = NULL; + + /* Set address and client id in the event */ + bdaddr2android(&dev->bdaddr, &ev.bda); + + if (gerr) { + error("gatt: connection failed %s", gerr->message); + status = HAL_STATUS_FAILED; + goto reply; + } + + attrib = g_attrib_new(io); + if (!attrib) { + error("gatt: unable to create new GAttrib instance"); + status = HAL_STATUS_FAILED; + goto reply; + } + + dev->attrib = attrib; + dev->watch_id = g_io_add_watch(io, G_IO_HUP, disconnected_cb, dev); + dev->conn_id = ++conn_id; + + /* Move gatt device from connect queue to conn_list */ + if (!queue_push_tail(conn_list, dev)) { + error("gatt: Cannot push dev on conn_list"); + connection_cleanup(dev); + status = HAL_STATUS_FAILED; + goto reply; + } + + status = HAL_STATUS_SUCCESS; + goto reply; + +reply: + ev.conn_id = dev ? conn_id : 0; + ev.status = status; + + queue_foreach(dev->clients, send_client_connect_notify, &ev); + + /* If connection did not succeed, destroy device */ + if (status) + destroy_device(dev); +} + +static int connect_le(struct gatt_device *dev) +{ + BtIOSecLevel sec_level; + GIOChannel *io; + GError *gerr = NULL; + char addr[18]; + + ba2str(&dev->bdaddr, addr); + + /* There is one connection attempt going on */ + if (dev->att_io) { + info("gatt: connection to dev %s is ongoing", addr); + return -EALREADY; + } + + DBG("Connection attempt to: %s", addr); + + /*TODO: If we are bonded then we should use higier sec level */ + sec_level = BT_IO_SEC_LOW; + + /* + * This connection will help us catch any PDUs that comes before + * pairing finishes + */ + io = bt_io_connect(connect_cb, dev, NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, + &adapter_addr, + BT_IO_OPT_SOURCE_TYPE, BDADDR_LE_PUBLIC, + BT_IO_OPT_DEST_BDADDR, &dev->bdaddr, + BT_IO_OPT_DEST_TYPE, dev->bdaddr_type, + BT_IO_OPT_CID, ATT_CID, + BT_IO_OPT_SEC_LEVEL, sec_level, + BT_IO_OPT_INVALID); + if (!io) { + error("gatt: Failed 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 void handle_client_scan(const void *buf, uint16_t len) { const struct hal_cmd_gatt_client_scan *cmd = buf; @@ -229,12 +497,166 @@ reply: status); } +static int connect_next_dev(void) +{ + struct gatt_device *dev; + + DBG(""); + + if (queue_isempty(conn_wait_queue)) + return 0; + + /* Discovery has been stopped because there is connection waiting */ + dev = queue_find(conn_wait_queue, match_dev_connect_ready, NULL); + if (!dev) + /* Lets try again. */ + return -1; + + dev->connect_ready = false; + + return connect_le(dev); +} + +static void bt_le_discovery_stop_cb(void) +{ + DBG(""); + + /* Check now if there is any device ready to connect*/ + if (connect_next_dev() < 0) + bt_le_discovery_start(le_device_found_handler); +} + +static struct gatt_device *find_device(bdaddr_t *addr) +{ + struct gatt_device *dev; + + dev = queue_find(conn_list, match_dev_by_bdaddr, addr); + if (dev) + return dev; + + dev = queue_find(conn_wait_queue, match_dev_by_bdaddr, addr); + if (dev) + return dev; + + return NULL; +} + static void handle_client_connect(const void *buf, uint16_t len) { + const struct hal_cmd_gatt_client_connect *cmd = buf; + struct gatt_device *dev = NULL; + void *l; + bdaddr_t addr; + uint8_t status; + bool send_notify = false; + DBG(""); + /* Check if client is registered */ + l = queue_find(gatt_clients, match_client_by_id, + INT_TO_PTR(cmd->client_if)); + if (!l) { + error("gatt: Client id %d not found", cmd->client_if); + status = HAL_STATUS_FAILED; + goto reply; + } + + android2bdaddr(&cmd->bdaddr, &addr); + + /* We do support many clients for one device connection so lets check + * If device is connected or in connecting state just update list of + * clients + */ + dev = find_device(&addr); + if (dev) { + + status = HAL_STATUS_SUCCESS; + + /* Remeber to send dummy notification event if we area + * connected + */ + if (dev->conn_id) + send_notify = true; + + if (queue_find(dev->clients, match_by_value, + INT_TO_PTR(cmd->client_if))) + goto reply; + + /* Store another client */ + if (!queue_push_tail(dev->clients, + INT_TO_PTR(cmd->client_if))) { + error("gatt: Cannot push client on gatt device list"); + status = HAL_STATUS_FAILED; + goto reply; + } + + goto reply; + } + + /* Lets create new gatt device and put it on conn_wait_queue. + * Once it is connected we move it to conn_list + */ + dev = new0(struct gatt_device, 1); + if (!dev) { + status = HAL_STATUS_FAILED; + goto reply; + } + + memcpy(&dev->bdaddr, &addr, sizeof(bdaddr_t)); + + /* Create queue to keep list of clients for given device*/ + dev->clients = queue_new(); + if (!dev->clients) { + error("gatt: Cannot create client queue"); + status = HAL_STATUS_FAILED; + goto reply; + } + + /* Update client list of device */ + if (!queue_push_tail(dev->clients, INT_TO_PTR(cmd->client_if))) { + error("gatt: Cannot push client on the client queue!?"); + status = HAL_STATUS_FAILED; + goto reply; + } + + /* Start le scan if not started */ + if (queue_isempty(scan_clients)) { + if (!bt_le_discovery_start(le_device_found_handler)) { + error("gatt: Could not start scan"); + status = HAL_STATUS_FAILED; + goto reply; + } + } + + if (!queue_push_tail(conn_wait_queue, dev)) { + error("gatt: Cannot push device on conn_wait_queue"); + status = HAL_STATUS_FAILED; + goto reply; + } + + status = HAL_STATUS_SUCCESS; + goto reply; + +reply: ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, HAL_OP_GATT_CLIENT_CONNECT, - HAL_STATUS_FAILED); + status); + + /* Send dummy notification since ACL is already up*/ + if (send_notify) { + struct hal_ev_gatt_client_connect ev; + + ev.conn_id = dev->conn_id; + ev.status = HAL_STATUS_SUCCESS; + ev.client_if = cmd->client_if; + bdaddr2android(&addr, &ev.bda); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_CLIENT_CONNECT, + sizeof(ev), &ev); + } + + if (status && dev) + destroy_device(dev); } static void handle_client_disconnect(const void *buf, uint16_t len) @@ -610,6 +1032,18 @@ bool bt_gatt_register(struct ipc *ipc, const bdaddr_t *addr) hal_ipc = ipc; + conn_list = queue_new(); + if (!conn_list) { + error("gatt: Can not create conn queue"); + return false; + } + + conn_wait_queue = queue_new(); + if (!conn_wait_queue) { + error("gatt: Can not create conn queue"); + return false; + } + ipc_register(hal_ipc, HAL_SERVICE_ID_GATT, cmd_handlers, G_N_ELEMENTS(cmd_handlers)); @@ -637,4 +1071,7 @@ void bt_gatt_unregister(void) ipc_unregister(hal_ipc, HAL_SERVICE_ID_GATT); hal_ipc = NULL; + + queue_destroy(conn_list, NULL); + queue_destroy(conn_wait_queue, NULL); } -- 1.8.4