All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 00/12] Userspace Bluetooth 5.2 initial support
@ 2020-02-28 23:46 Luiz Augusto von Dentz
  2020-02-28 23:46 ` [PATCH v2 01/12] lib: Add definitions for Enhanced Credits Based Mode Luiz Augusto von Dentz
                   ` (12 more replies)
  0 siblings, 13 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2020-02-28 23:46 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

This introduces the initial support for Bluetooth 5.2 features:

* ISO channels:
        + tools/isotest command line tool to run validation tests

* L2CAP Enhanced Credit Based Flow Control Mode
        + tools/l2test has been update to include the new mode

* Enhanced ATT Bearer:
        + Client and Server support
        + Include all new procedures
        + Automaticlly detects and enables channels
        + Number of channels configurable via main.conf

v2: Dropped ISO changes since there still under discussions how it will be
supported, change ECRED to EXT_FLOWCTL and use BT_MODE to set it.

Luiz Augusto von Dentz (12):
  lib: Add definitions for Enhanced Credits Based Mode
  btio: Add mode to for Enhanced Credit Mode
  l2test: Add support for L2CAP_EXT_FLOWCTL_MODE
  share/att: Add EATT support
  shared/gatt-client: Add support for EATT features
  gatt: Enable EATT bearer support
  shared/gatt-server: Add support for Read Multiple Variable Length
  shared/gatt-client: Add support for Read Multiple Variable Length
  shared/gatt: Add support for Handle Value Multiple Notifications
  gatt: Add support for Notify Multiple
  core: Add support for setting the number of GATT bearers
  monitor: Add support for decoding EATT

 attrib/gattrib.c         |   5 +-
 btio/btio.c              |  53 ++-
 btio/btio.h              |   3 +-
 lib/bluetooth.h          |   2 +
 lib/l2cap.h              |   1 +
 lib/uuid.h               |   3 +
 monitor/l2cap.c          |  39 ++
 peripheral/gatt.c        |   2 +-
 src/device.c             |  18 +-
 src/gatt-client.c        |  85 +++++
 src/gatt-database.c      | 125 +++++--
 src/hcid.h               |   1 +
 src/main.c               |  14 +
 src/main.conf            |   5 +
 src/shared/att-types.h   |  25 +-
 src/shared/att.c         | 780 ++++++++++++++++++++++++++-------------
 src/shared/att.h         |  18 +-
 src/shared/gatt-client.c | 287 +++++++++++---
 src/shared/gatt-client.h |   5 +-
 src/shared/gatt-server.c | 389 ++++++++++++-------
 src/shared/gatt-server.h |   2 +-
 tools/btgatt-client.c    |   2 +-
 tools/btgatt-server.c    |   4 +-
 tools/l2test.c           |  10 +-
 unit/test-gatt.c         |  25 +-
 25 files changed, 1407 insertions(+), 496 deletions(-)

-- 
2.21.1


^ permalink raw reply	[flat|nested] 15+ messages in thread

* [PATCH v2 01/12] lib: Add definitions for Enhanced Credits Based Mode
  2020-02-28 23:46 [PATCH v2 00/12] Userspace Bluetooth 5.2 initial support Luiz Augusto von Dentz
@ 2020-02-28 23:46 ` Luiz Augusto von Dentz
  2020-02-28 23:46 ` [PATCH v2 02/12] btio: Add mode to for Enhanced Credit Mode Luiz Augusto von Dentz
                   ` (11 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2020-02-28 23:46 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

---
 lib/bluetooth.h | 2 ++
 lib/l2cap.h     | 1 +
 2 files changed, 3 insertions(+)

diff --git a/lib/bluetooth.h b/lib/bluetooth.h
index d14217eac..47521d50e 100644
--- a/lib/bluetooth.h
+++ b/lib/bluetooth.h
@@ -141,6 +141,8 @@ struct bt_voice {
 #define BT_PHY_LE_CODED_TX	0x00002000
 #define BT_PHY_LE_CODED_RX	0x00004000
 
+#define BT_MODE			15
+
 /* Connection and socket states */
 enum {
 	BT_CONNECTED = 1, /* Equal to TCP_ESTABLISHED to make net code happy */
diff --git a/lib/l2cap.h b/lib/l2cap.h
index 5ce94c4ee..f9ceb2f33 100644
--- a/lib/l2cap.h
+++ b/lib/l2cap.h
@@ -197,6 +197,7 @@ typedef struct {
 #define L2CAP_MODE_FLOWCTL	0x02
 #define L2CAP_MODE_ERTM		0x03
 #define L2CAP_MODE_STREAMING	0x04
+#define L2CAP_MODE_EXT_FLOWCTL	0x81
 
 #define L2CAP_SERVTYPE_NOTRAFFIC	0x00
 #define L2CAP_SERVTYPE_BESTEFFORT	0x01
-- 
2.21.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 02/12] btio: Add mode to for Enhanced Credit Mode
  2020-02-28 23:46 [PATCH v2 00/12] Userspace Bluetooth 5.2 initial support Luiz Augusto von Dentz
  2020-02-28 23:46 ` [PATCH v2 01/12] lib: Add definitions for Enhanced Credits Based Mode Luiz Augusto von Dentz
@ 2020-02-28 23:46 ` Luiz Augusto von Dentz
  2020-02-28 23:46 ` [PATCH v2 03/12] l2test: Add support for L2CAP_EXT_FLOWCTL_MODE Luiz Augusto von Dentz
                   ` (10 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2020-02-28 23:46 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

This adds BT_IO_MODE_ECRED which directly maps to L2CAP_MODE_ECRED.
---
 btio/btio.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++------
 btio/btio.h |  3 ++-
 2 files changed, 49 insertions(+), 7 deletions(-)

diff --git a/btio/btio.c b/btio/btio.c
index db37b99da..4c84da0ee 100644
--- a/btio/btio.c
+++ b/btio/btio.c
@@ -630,18 +630,34 @@ static gboolean set_le_imtu(int sock, uint16_t imtu, GError **err)
 	return TRUE;
 }
 
+static gboolean set_le_mode(int sock, uint8_t mode, GError **err)
+{
+	if (setsockopt(sock, SOL_BLUETOOTH, BT_MODE, &mode,
+						sizeof(mode)) < 0) {
+		ERROR_FAILED(err, "setsockopt(BT_MODE)", errno);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
 static gboolean l2cap_set(int sock, uint8_t src_type, int sec_level,
 				uint16_t imtu, uint16_t omtu, uint8_t mode,
 				int master, int flushable, uint32_t priority,
 				GError **err)
 {
 	if (imtu || omtu || mode) {
-		gboolean ret;
+		gboolean ret = FALSE;
 
 		if (src_type == BDADDR_BREDR)
 			ret = set_l2opts(sock, imtu, omtu, mode, err);
-		else
-			ret = set_le_imtu(sock, imtu, err);
+		else {
+			if (imtu)
+				ret = set_le_imtu(sock, imtu, err);
+
+			if (ret && mode)
+				ret = set_le_mode(sock, mode, err);
+		}
 
 		if (!ret)
 			return ret;
@@ -980,6 +996,30 @@ static int get_phy(int sock, uint32_t *phy)
 	return 0;
 }
 
+static int get_le_imtu(int sock, uint16_t *mtu)
+{
+	socklen_t len;
+
+	len = sizeof(*mtu);
+
+	if (getsockopt(sock, SOL_BLUETOOTH, BT_RCVMTU, mtu, &len) < 0)
+		return -errno;
+
+	return 0;
+}
+
+static int get_le_mode(int sock, uint8_t *mode)
+{
+	socklen_t len;
+
+	len = sizeof(*mode);
+
+	if (getsockopt(sock, SOL_BLUETOOTH, BT_MODE, mode, &len) < 0)
+		return -errno;
+
+	return 0;
+}
+
 static gboolean l2cap_get(int sock, GError **err, BtIOOption opt1,
 								va_list args)
 {
@@ -999,10 +1039,11 @@ static gboolean l2cap_get(int sock, GError **err, BtIOOption opt1,
 	memset(&l2o, 0, sizeof(l2o));
 
 	if (src.l2_bdaddr_type != BDADDR_BREDR) {
-		len = sizeof(l2o.imtu);
-		if (getsockopt(sock, SOL_BLUETOOTH, BT_RCVMTU,
-						&l2o.imtu, &len) == 0)
+		if (get_le_imtu(sock, &l2o.imtu) == 0) {
+			/* Older kernels may not support BT_MODE */
+			get_le_mode(sock, &l2o.mode);
 			goto parse_opts;
+		}
 
 		/* Non-LE CoC enabled kernels will return one of these
 		 * in which case we need to fall back to L2CAP_OPTIONS.
diff --git a/btio/btio.h b/btio/btio.h
index 41a017acb..5ebfb85c6 100644
--- a/btio/btio.h
+++ b/btio/btio.h
@@ -71,7 +71,8 @@ typedef enum {
 	BT_IO_MODE_RETRANS,
 	BT_IO_MODE_FLOWCTL,
 	BT_IO_MODE_ERTM,
-	BT_IO_MODE_STREAMING
+	BT_IO_MODE_STREAMING,
+	BT_IO_MODE_EXT_FLOWCTL = 0x81
 } BtIOMode;
 
 typedef void (*BtIOConfirm)(GIOChannel *io, gpointer user_data);
-- 
2.21.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 03/12] l2test: Add support for L2CAP_EXT_FLOWCTL_MODE
  2020-02-28 23:46 [PATCH v2 00/12] Userspace Bluetooth 5.2 initial support Luiz Augusto von Dentz
  2020-02-28 23:46 ` [PATCH v2 01/12] lib: Add definitions for Enhanced Credits Based Mode Luiz Augusto von Dentz
  2020-02-28 23:46 ` [PATCH v2 02/12] btio: Add mode to for Enhanced Credit Mode Luiz Augusto von Dentz
@ 2020-02-28 23:46 ` Luiz Augusto von Dentz
  2020-02-28 23:46 ` [PATCH v2 04/12] share/att: Add EATT support Luiz Augusto von Dentz
                   ` (9 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2020-02-28 23:46 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

This enables using l2test to connect or listen with
L2CAP_EXT_FLOWCTL_MODE.
---
 tools/l2test.c | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/tools/l2test.c b/tools/l2test.c
index 8c6e08646..ee3637699 100644
--- a/tools/l2test.c
+++ b/tools/l2test.c
@@ -150,6 +150,7 @@ static struct lookup_table l2cap_modes[] = {
 	*/
 	{ "ertm",	L2CAP_MODE_ERTM		},
 	{ "streaming",	L2CAP_MODE_STREAMING	},
+	{ "ext-flowctl",L2CAP_MODE_EXT_FLOWCTL	},
 	{ 0 }
 };
 
@@ -283,7 +284,7 @@ static int getopts(int sk, struct l2cap_options *opts, bool connected)
 
 	memset(opts, 0, sizeof(*opts));
 
-	if (bdaddr_type == BDADDR_BREDR) {
+	if (bdaddr_type == BDADDR_BREDR || rfcmode) {
 		optlen = sizeof(*opts);
 		return getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, opts, &optlen);
 	}
@@ -303,6 +304,13 @@ static int setopts(int sk, struct l2cap_options *opts)
 		return setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, opts,
 								sizeof(*opts));
 
+	if (opts->mode) {
+		if (setsockopt(sk, SOL_BLUETOOTH, BT_MODE, &opts->mode,
+						sizeof(opts->mode)) < 0) {
+			return -errno;
+		}
+	}
+
 	return setsockopt(sk, SOL_BLUETOOTH, BT_RCVMTU, &opts->imtu,
 							sizeof(opts->imtu));
 }
-- 
2.21.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 04/12] share/att: Add EATT support
  2020-02-28 23:46 [PATCH v2 00/12] Userspace Bluetooth 5.2 initial support Luiz Augusto von Dentz
                   ` (2 preceding siblings ...)
  2020-02-28 23:46 ` [PATCH v2 03/12] l2test: Add support for L2CAP_EXT_FLOWCTL_MODE Luiz Augusto von Dentz
@ 2020-02-28 23:46 ` Luiz Augusto von Dentz
  2020-02-28 23:46 ` [PATCH v2 05/12] shared/gatt-client: Add support for EATT features Luiz Augusto von Dentz
                   ` (8 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2020-02-28 23:46 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

This adds EATT support to bt_att, EATT bearers are handled as
additional channels which auto allocated for queued requests.
---
 src/gatt-database.c      |   4 +-
 src/shared/att-types.h   |  16 +-
 src/shared/att.c         | 566 +++++++++++++++++++++++++--------------
 src/shared/att.h         |   4 +
 src/shared/gatt-client.c |   2 +-
 5 files changed, 389 insertions(+), 203 deletions(-)

diff --git a/src/gatt-database.c b/src/gatt-database.c
index 2bae8711a..419e4f9e1 100644
--- a/src/gatt-database.c
+++ b/src/gatt-database.c
@@ -2102,10 +2102,10 @@ static void append_options(DBusMessageIter *iter, void *user_data)
 	uint16_t mtu;
 
 	switch (op->link_type) {
-	case BT_ATT_LINK_BREDR:
+	case BT_ATT_BREDR:
 		link = "BR/EDR";
 		break;
-	case BT_ATT_LINK_LE:
+	case BT_ATT_LE:
 		link = "LE";
 		break;
 	default:
diff --git a/src/shared/att-types.h b/src/shared/att-types.h
index 8a2658de3..7b88e7d92 100644
--- a/src/shared/att-types.h
+++ b/src/shared/att-types.h
@@ -27,6 +27,10 @@
 #define __packed __attribute__((packed))
 #endif
 
+#define BT_ATT_CID		4
+#define BT_ATT_PSM		31
+#define BT_ATT_EATT_PSM		0x27
+
 #define BT_ATT_SECURITY_AUTO	0
 #define BT_ATT_SECURITY_LOW	1
 #define BT_ATT_SECURITY_MEDIUM	2
@@ -37,9 +41,10 @@
 #define BT_ATT_MAX_LE_MTU	517
 #define BT_ATT_MAX_VALUE_LEN	512
 
-#define BT_ATT_LINK_BREDR	0x00
-#define BT_ATT_LINK_LE		0x01
-#define BT_ATT_LINK_LOCAL	0xff
+#define BT_ATT_BREDR		0x00
+#define BT_ATT_LE		0x01
+#define BT_ATT_EATT		0x02
+#define BT_ATT_LOCAL		0xff
 
 /* ATT protocol opcodes */
 #define BT_ATT_OP_ERROR_RSP			0x01
@@ -159,3 +164,8 @@ struct bt_att_pdu_error_rsp {
 
 /* GATT Characteristic Client Features Bitfield values */
 #define BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING		0x01
+#define BT_GATT_CHRC_CLI_FEAT_EATT			0x02
+#define BT_GATT_CHRC_CLI_FEAT_NFY_MULTI			0x04
+
+/* GATT Characteristic Server Features Bitfield values */
+#define BT_GATT_CHRC_SERVER_FEAT_EATT			0x01
diff --git a/src/shared/att.c b/src/shared/att.c
index 0ea6d55bd..1313703f9 100644
--- a/src/shared/att.c
+++ b/src/shared/att.c
@@ -49,32 +49,40 @@
 
 struct att_send_op;
 
-struct bt_att {
-	int ref_count;
+struct bt_att_chan {
+	struct bt_att *att;
 	int fd;
 	struct io *io;
-	bool io_on_l2cap;
-	int io_sec_level;		/* Only used for non-L2CAP */
-	uint8_t enc_size;
+	uint8_t type;
+	int sec_level;			/* Only used for non-L2CAP */
 
-	struct queue *req_queue;	/* Queued ATT protocol requests */
 	struct att_send_op *pending_req;
-	struct queue *ind_queue;	/* Queued ATT protocol indications */
 	struct att_send_op *pending_ind;
-	struct queue *write_queue;	/* Queue of PDUs ready to send */
 	bool writer_active;
 
-	struct queue *notify_list;	/* List of registered callbacks */
-	struct queue *disconn_list;	/* List of disconnect handlers */
-
 	bool in_req;			/* There's a pending incoming request */
 
 	uint8_t *buf;
 	uint16_t mtu;
+};
+
+struct bt_att {
+	int ref_count;
+	bool close_on_unref;
+	struct queue *chans;
+	uint8_t enc_size;
+	uint16_t mtu;			/* Biggest possible MTU */
+
+	struct queue *notify_list;	/* List of registered callbacks */
+	struct queue *disconn_list;	/* List of disconnect handlers */
 
 	unsigned int next_send_id;	/* IDs for "send" ops */
 	unsigned int next_reg_id;	/* IDs for registered callbacks */
 
+	struct queue *req_queue;	/* Queued ATT protocol requests */
+	struct queue *ind_queue;	/* Queued ATT protocol indications */
+	struct queue *write_queue;	/* Queue of PDUs ready to send */
+
 	bt_att_timeout_func_t timeout_callback;
 	bt_att_destroy_func_t timeout_destroy;
 	void *timeout_data;
@@ -362,8 +370,9 @@ static struct att_send_op *create_att_send_op(struct bt_att *att,
 	return op;
 }
 
-static struct att_send_op *pick_next_send_op(struct bt_att *att)
+static struct att_send_op *pick_next_send_op(struct bt_att_chan *chan)
 {
+	struct bt_att *att = chan->att;
 	struct att_send_op *op;
 
 	/* See if any operations are already in the write queue */
@@ -374,7 +383,7 @@ static struct att_send_op *pick_next_send_op(struct bt_att *att)
 	/* If there is no pending request, pick an operation from the
 	 * request queue.
 	 */
-	if (!att->pending_req) {
+	if (!chan->pending_req) {
 		op = queue_pop_head(att->req_queue);
 		if (op)
 			return op;
@@ -383,7 +392,7 @@ static struct att_send_op *pick_next_send_op(struct bt_att *att)
 	/* There is either a request pending or no requests queued. If there is
 	 * no pending indication, pick an operation from the indication queue.
 	 */
-	if (!att->pending_ind) {
+	if (!chan->pending_ind) {
 		op = queue_pop_head(att->ind_queue);
 		if (op)
 			return op;
@@ -393,22 +402,23 @@ static struct att_send_op *pick_next_send_op(struct bt_att *att)
 }
 
 struct timeout_data {
-	struct bt_att *att;
+	struct bt_att_chan *chan;
 	unsigned int id;
 };
 
 static bool timeout_cb(void *user_data)
 {
 	struct timeout_data *timeout = user_data;
-	struct bt_att *att = timeout->att;
+	struct bt_att_chan *chan = timeout->chan;
+	struct bt_att *att = chan->att;
 	struct att_send_op *op = NULL;
 
-	if (att->pending_req && att->pending_req->id == timeout->id) {
-		op = att->pending_req;
-		att->pending_req = NULL;
-	} else if (att->pending_ind && att->pending_ind->id == timeout->id) {
-		op = att->pending_ind;
-		att->pending_ind = NULL;
+	if (chan->pending_req && chan->pending_req->id == timeout->id) {
+		op = chan->pending_req;
+		chan->pending_req = NULL;
+	} else if (chan->pending_ind && chan->pending_ind->id == timeout->id) {
+		op = chan->pending_ind;
+		chan->pending_ind = NULL;
 	}
 
 	if (!op)
@@ -428,27 +438,28 @@ static bool timeout_cb(void *user_data)
 	 * This should trigger an io disconnect event which will clean up the
 	 * io and notify the upper layer.
 	 */
-	io_shutdown(att->io);
+	io_shutdown(chan->io);
 
 	return false;
 }
 
 static void write_watch_destroy(void *user_data)
 {
-	struct bt_att *att = user_data;
+	struct bt_att_chan *chan = user_data;
 
-	att->writer_active = false;
+	chan->writer_active = false;
 }
 
 static bool can_write_data(struct io *io, void *user_data)
 {
-	struct bt_att *att = user_data;
+	struct bt_att_chan *chan = user_data;
+	struct bt_att *att = chan->att;
 	struct att_send_op *op;
 	struct timeout_data *timeout;
 	ssize_t ret;
 	struct iovec iov;
 
-	op = pick_next_send_op(att);
+	op = pick_next_send_op(chan);
 	if (!op)
 		return false;
 
@@ -478,14 +489,14 @@ static bool can_write_data(struct io *io, void *user_data)
 	 */
 	switch (op->type) {
 	case ATT_OP_TYPE_REQ:
-		att->pending_req = op;
+		chan->pending_req = op;
 		break;
 	case ATT_OP_TYPE_IND:
-		att->pending_ind = op;
+		chan->pending_ind = op;
 		break;
 	case ATT_OP_TYPE_RSP:
 		/* Set in_req to false to indicate that no request is pending */
-		att->in_req = false;
+		chan->in_req = false;
 		/* fall through */
 	case ATT_OP_TYPE_CMD:
 	case ATT_OP_TYPE_NOT:
@@ -497,7 +508,7 @@ static bool can_write_data(struct io *io, void *user_data)
 	}
 
 	timeout = new0(struct timeout_data, 1);
-	timeout->att = att;
+	timeout->chan = chan;
 	timeout->id = op->id;
 	op->timeout_id = timeout_add(ATT_TIMEOUT_INTERVAL, timeout_cb,
 								timeout, free);
@@ -506,25 +517,33 @@ static bool can_write_data(struct io *io, void *user_data)
 	return true;
 }
 
-static void wakeup_writer(struct bt_att *att)
+static void wakeup_chan_writer(void *data, void *user_data)
 {
-	if (att->writer_active)
+	struct bt_att_chan *chan = data;
+	struct bt_att *att = chan->att;
+
+	if (chan->writer_active)
 		return;
 
 	/* Set the write handler only if there is anything that can be sent
 	 * at all.
 	 */
 	if (queue_isempty(att->write_queue)) {
-		if ((att->pending_req || queue_isempty(att->req_queue)) &&
-			(att->pending_ind || queue_isempty(att->ind_queue)))
+		if ((chan->pending_req || queue_isempty(att->req_queue)) &&
+			(chan->pending_ind || queue_isempty(att->ind_queue)))
 			return;
 	}
 
-	if (!io_set_write_handler(att->io, can_write_data, att,
+	if (!io_set_write_handler(chan->io, can_write_data, chan,
 							write_watch_destroy))
 		return;
 
-	att->writer_active = true;
+	chan->writer_active = true;
+}
+
+static void wakeup_writer(struct bt_att *att)
+{
+	queue_foreach(att->chans, wakeup_chan_writer, NULL);
 }
 
 static void disconn_handler(void *data, void *user_data)
@@ -549,44 +568,66 @@ static void disc_att_send_op(void *data)
 	destroy_att_send_op(op);
 }
 
+static void bt_att_chan_free(void *data)
+{
+	struct bt_att_chan *chan = data;
+
+	if (chan->pending_req)
+		destroy_att_send_op(chan->pending_req);
+
+	if (chan->pending_ind)
+		destroy_att_send_op(chan->pending_ind);
+
+	io_destroy(chan->io);
+
+	free(chan->buf);
+	free(chan);
+}
+
 static bool disconnect_cb(struct io *io, void *user_data)
 {
-	struct bt_att *att = user_data;
+	struct bt_att_chan *chan = user_data;
+	struct bt_att *att = chan->att;
 	int err;
 	socklen_t len;
 
 	len = sizeof(err);
 
-	if (getsockopt(att->fd, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {
-		util_debug(att->debug_callback, att->debug_data,
+	if (getsockopt(chan->fd, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {
+		util_debug(chan->att->debug_callback, chan->att->debug_data,
 					"Failed to obtain disconnect error: %s",
 					strerror(errno));
 		err = 0;
 	}
 
-	util_debug(att->debug_callback, att->debug_data,
-					"Physical link disconnected: %s",
-					strerror(err));
+	util_debug(chan->att->debug_callback, chan->att->debug_data,
+					"Channel %p disconnected: %s",
+					chan, strerror(err));
 
-	io_destroy(att->io);
-	att->io = NULL;
-	att->fd = -1;
+	/* Dettach channel */
+	queue_remove(att->chans, chan);
 
 	/* Notify request callbacks */
 	queue_remove_all(att->req_queue, NULL, NULL, disc_att_send_op);
 	queue_remove_all(att->ind_queue, NULL, NULL, disc_att_send_op);
 	queue_remove_all(att->write_queue, NULL, NULL, disc_att_send_op);
 
-	if (att->pending_req) {
-		disc_att_send_op(att->pending_req);
-		att->pending_req = NULL;
+	if (chan->pending_req) {
+		disc_att_send_op(chan->pending_req);
+		chan->pending_req = NULL;
 	}
 
-	if (att->pending_ind) {
-		disc_att_send_op(att->pending_ind);
-		att->pending_ind = NULL;
+	if (chan->pending_ind) {
+		disc_att_send_op(chan->pending_ind);
+		chan->pending_ind = NULL;
 	}
 
+	bt_att_chan_free(chan);
+
+	/* Don't run disconnect callback if there are channels left */
+	if (!queue_isempty(att->chans))
+		return false;
+
 	bt_att_ref(att);
 
 	queue_foreach(att->disconn_list, disconn_handler, INT_TO_PTR(err));
@@ -597,14 +638,49 @@ static bool disconnect_cb(struct io *io, void *user_data)
 	return false;
 }
 
-static bool change_security(struct bt_att *att, uint8_t ecode)
+static int bt_att_chan_get_security(struct bt_att_chan *chan)
+{
+	struct bt_security sec;
+	socklen_t len;
+
+	if (chan->type == BT_ATT_LOCAL)
+		return chan->sec_level;
+
+	memset(&sec, 0, sizeof(sec));
+	len = sizeof(sec);
+	if (getsockopt(chan->fd, SOL_BLUETOOTH, BT_SECURITY, &sec, &len) < 0)
+		return -EIO;
+
+	return sec.level;
+}
+
+static bool bt_att_chan_set_security(struct bt_att_chan *chan, int level)
+{
+	struct bt_security sec;
+
+	if (chan->type == BT_ATT_LOCAL) {
+		chan->sec_level = level;
+		return true;
+	}
+
+	memset(&sec, 0, sizeof(sec));
+	sec.level = level;
+
+	if (setsockopt(chan->fd, SOL_BLUETOOTH, BT_SECURITY, &sec,
+							sizeof(sec)) < 0)
+		return false;
+
+	return true;
+}
+
+static bool change_security(struct bt_att_chan *chan, uint8_t ecode)
 {
 	int security;
 
-	if (att->io_sec_level != BT_ATT_SECURITY_AUTO)
+	if (chan->sec_level != BT_ATT_SECURITY_AUTO)
 		return false;
 
-	security = bt_att_get_security(att, NULL);
+	security = bt_att_chan_get_security(chan);
 
 	if (ecode == BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION &&
 					security < BT_ATT_SECURITY_MEDIUM) {
@@ -622,14 +698,15 @@ static bool change_security(struct bt_att *att, uint8_t ecode)
 		return false;
 	}
 
-	return bt_att_set_security(att, security);
+	return bt_att_chan_set_security(chan, security);
 }
 
-static bool handle_error_rsp(struct bt_att *att, uint8_t *pdu,
+static bool handle_error_rsp(struct bt_att_chan *chan, uint8_t *pdu,
 					ssize_t pdu_len, uint8_t *opcode)
 {
+	struct bt_att *att = chan->att;
 	const struct bt_att_pdu_error_rsp *rsp;
-	struct att_send_op *op = att->pending_req;
+	struct att_send_op *op = chan->pending_req;
 
 	if (pdu_len != sizeof(*rsp)) {
 		*opcode = 0;
@@ -641,7 +718,7 @@ static bool handle_error_rsp(struct bt_att *att, uint8_t *pdu,
 	*opcode = rsp->opcode;
 
 	/* Attempt to change security */
-	if (!change_security(att, rsp->ecode))
+	if (!change_security(chan, rsp->ecode))
 		return false;
 
 	/* Remove timeout_id if outstanding */
@@ -653,16 +730,17 @@ static bool handle_error_rsp(struct bt_att *att, uint8_t *pdu,
 	util_debug(att->debug_callback, att->debug_data,
 						"Retrying operation %p", op);
 
-	att->pending_req = NULL;
+	chan->pending_req = NULL;
 
 	/* Push operation back to request queue */
 	return queue_push_head(att->req_queue, op);
 }
 
-static void handle_rsp(struct bt_att *att, uint8_t opcode, uint8_t *pdu,
+static void handle_rsp(struct bt_att_chan *chan, uint8_t opcode, uint8_t *pdu,
 								ssize_t pdu_len)
 {
-	struct att_send_op *op = att->pending_req;
+	struct bt_att *att = chan->att;
+	struct att_send_op *op = chan->pending_req;
 	uint8_t req_opcode;
 	uint8_t rsp_opcode;
 	uint8_t *rsp_pdu = NULL;
@@ -675,7 +753,7 @@ static void handle_rsp(struct bt_att *att, uint8_t opcode, uint8_t *pdu,
 	if (!op) {
 		util_debug(att->debug_callback, att->debug_data,
 					"Received unexpected ATT response");
-		io_shutdown(att->io);
+		io_shutdown(chan->io);
 		return;
 	}
 
@@ -685,8 +763,8 @@ static void handle_rsp(struct bt_att *att, uint8_t opcode, uint8_t *pdu,
 	 */
 	if (opcode == BT_ATT_OP_ERROR_RSP) {
 		/* Return if error response cause a retry */
-		if (handle_error_rsp(att, pdu, pdu_len, &req_opcode)) {
-			wakeup_writer(att);
+		if (handle_error_rsp(chan, pdu, pdu_len, &req_opcode)) {
+			wakeup_chan_writer(chan, NULL);
 			return;
 		}
 	} else if (!(req_opcode = get_req_opcode(opcode)))
@@ -715,14 +793,15 @@ done:
 		op->callback(rsp_opcode, rsp_pdu, rsp_pdu_len, op->user_data);
 
 	destroy_att_send_op(op);
-	att->pending_req = NULL;
+	chan->pending_req = NULL;
 
-	wakeup_writer(att);
+	wakeup_chan_writer(chan, NULL);
 }
 
-static void handle_conf(struct bt_att *att, uint8_t *pdu, ssize_t pdu_len)
+static void handle_conf(struct bt_att_chan *chan, uint8_t *pdu, ssize_t pdu_len)
 {
-	struct att_send_op *op = att->pending_ind;
+	struct bt_att *att = chan->att;
+	struct att_send_op *op = chan->pending_ind;
 
 	/*
 	 * Disconnect the bearer if the confirmation is unexpected or the PDU is
@@ -731,7 +810,7 @@ static void handle_conf(struct bt_att *att, uint8_t *pdu, ssize_t pdu_len)
 	if (!op || pdu_len) {
 		util_debug(att->debug_callback, att->debug_data,
 				"Received unexpected/invalid ATT confirmation");
-		io_shutdown(att->io);
+		io_shutdown(chan->io);
 		return;
 	}
 
@@ -739,9 +818,9 @@ static void handle_conf(struct bt_att *att, uint8_t *pdu, ssize_t pdu_len)
 		op->callback(BT_ATT_OP_HANDLE_VAL_CONF, NULL, 0, op->user_data);
 
 	destroy_att_send_op(op);
-	att->pending_ind = NULL;
+	chan->pending_ind = NULL;
 
-	wakeup_writer(att);
+	wakeup_chan_writer(chan, NULL);
 }
 
 struct notify_data {
@@ -811,9 +890,10 @@ fail:
 	return false;
 }
 
-static void handle_notify(struct bt_att *att, uint8_t opcode, uint8_t *pdu,
-								ssize_t pdu_len)
+static void handle_notify(struct bt_att_chan *chan, uint8_t opcode,
+						uint8_t *pdu, ssize_t pdu_len)
 {
+	struct bt_att *att = chan->att;
 	const struct queue_entry *entry;
 	bool found;
 
@@ -845,7 +925,7 @@ static void handle_notify(struct bt_att *att, uint8_t opcode, uint8_t *pdu,
 		 * link since the MTU size is negotiated using L2CAP channel
 		 * configuration procedures.
 		 */
-		if (bt_att_get_link_type(att) == BT_ATT_LINK_BREDR) {
+		if (bt_att_get_link_type(att) == BT_ATT_BREDR) {
 			switch (opcode) {
 			case BT_ATT_OP_MTU_REQ:
 				goto not_supported;
@@ -876,22 +956,23 @@ not_supported:
 
 static bool can_read_data(struct io *io, void *user_data)
 {
-	struct bt_att *att = user_data;
+	struct bt_att_chan *chan = user_data;
+	struct bt_att *att = chan->att;
 	uint8_t opcode;
 	uint8_t *pdu;
 	ssize_t bytes_read;
 
-	bytes_read = read(att->fd, att->buf, att->mtu);
+	bytes_read = read(chan->fd, chan->buf, chan->mtu);
 	if (bytes_read < 0)
 		return false;
 
-	util_hexdump('>', att->buf, bytes_read,
-					att->debug_callback, att->debug_data);
+	util_hexdump('>', chan->buf, bytes_read,
+				att->debug_callback, att->debug_data);
 
 	if (bytes_read < ATT_MIN_PDU_LEN)
 		return true;
 
-	pdu = att->buf;
+	pdu = chan->buf;
 	opcode = pdu[0];
 
 	bt_att_ref(att);
@@ -901,12 +982,12 @@ static bool can_read_data(struct io *io, void *user_data)
 	case ATT_OP_TYPE_RSP:
 		util_debug(att->debug_callback, att->debug_data,
 				"ATT response received: 0x%02x", opcode);
-		handle_rsp(att, opcode, pdu + 1, bytes_read - 1);
+		handle_rsp(chan, opcode, pdu + 1, bytes_read - 1);
 		break;
 	case ATT_OP_TYPE_CONF:
 		util_debug(att->debug_callback, att->debug_data,
 				"ATT confirmation received: 0x%02x", opcode);
-		handle_conf(att, pdu + 1, bytes_read - 1);
+		handle_conf(chan, pdu + 1, bytes_read - 1);
 		break;
 	case ATT_OP_TYPE_REQ:
 		/*
@@ -914,17 +995,17 @@ static bool can_read_data(struct io *io, void *user_data)
 		 * protocol was violated. Disconnect the bearer, which will
 		 * promptly notify the upper layer via disconnect handlers.
 		 */
-		if (att->in_req) {
+		if (chan->in_req) {
 			util_debug(att->debug_callback, att->debug_data,
 					"Received request while another is "
 					"pending: 0x%02x", opcode);
-			io_shutdown(att->io);
-			bt_att_unref(att);
+			io_shutdown(chan->io);
+			bt_att_unref(chan->att);
 
 			return false;
 		}
 
-		att->in_req = true;
+		chan->in_req = true;
 		/* fall through */
 	case ATT_OP_TYPE_CMD:
 	case ATT_OP_TYPE_NOT:
@@ -937,7 +1018,7 @@ static bool can_read_data(struct io *io, void *user_data)
 		 */
 		util_debug(att->debug_callback, att->debug_data,
 					"ATT PDU received: 0x%02x", opcode);
-		handle_notify(att, opcode, pdu + 1, bytes_read - 1);
+		handle_notify(chan, opcode, pdu + 1, bytes_read - 1);
 		break;
 	}
 
@@ -973,21 +1054,8 @@ static bool is_io_l2cap_based(int fd)
 
 static void bt_att_free(struct bt_att *att)
 {
-	if (att->pending_req)
-		destroy_att_send_op(att->pending_req);
-
-	if (att->pending_ind)
-		destroy_att_send_op(att->pending_ind);
-
-	io_destroy(att->io);
 	bt_crypto_unref(att->crypto);
 
-	queue_destroy(att->req_queue, NULL);
-	queue_destroy(att->ind_queue, NULL);
-	queue_destroy(att->write_queue, NULL);
-	queue_destroy(att->notify_list, NULL);
-	queue_destroy(att->disconn_list, NULL);
-
 	if (att->timeout_destroy)
 		att->timeout_destroy(att->timeout_data);
 
@@ -997,7 +1065,12 @@ static void bt_att_free(struct bt_att *att)
 	free(att->local_sign);
 	free(att->remote_sign);
 
-	free(att->buf);
+	queue_destroy(att->req_queue, NULL);
+	queue_destroy(att->ind_queue, NULL);
+	queue_destroy(att->write_queue, NULL);
+	queue_destroy(att->notify_list, NULL);
+	queue_destroy(att->disconn_list, NULL);
+	queue_destroy(att->chans, bt_att_chan_free);
 
 	free(att);
 }
@@ -1014,60 +1087,101 @@ static uint16_t get_l2cap_mtu(int fd)
 	return l2o.omtu;
 }
 
-struct bt_att *bt_att_new(int fd, bool ext_signed)
+static uint8_t io_get_type(int fd)
 {
-	struct bt_att *att;
+	struct sockaddr_l2 src;
+	socklen_t len;
 
-	if (fd < 0)
-		return NULL;
+	if (!is_io_l2cap_based(fd))
+		return BT_ATT_LOCAL;
 
-	att = new0(struct bt_att, 1);
-	att->fd = fd;
+	len = sizeof(src);
+	memset(&src, 0, len);
+	if (getsockname(fd, (void *)&src, &len) < 0)
+		return -errno;
 
-	att->io = io_new(fd);
-	if (!att->io)
-		goto fail;
+	if (src.l2_bdaddr_type == BDADDR_BREDR)
+		return BT_ATT_BREDR;
 
-	/* crypto is optional, if not available leave it NULL */
-	if (!ext_signed)
-		att->crypto = bt_crypto_new();
+	return BT_ATT_LE;
+}
 
-	att->req_queue = queue_new();
-	att->ind_queue = queue_new();
-	att->write_queue = queue_new();
-	att->notify_list = queue_new();
-	att->disconn_list = queue_new();
+static struct bt_att_chan *bt_att_chan_new(int fd, uint8_t type)
+{
+	struct bt_att_chan *chan;
 
-	if (!io_set_read_handler(att->io, can_read_data, att, NULL))
+	if (fd < 0)
+		return NULL;
+
+	chan = new0(struct bt_att_chan, 1);
+	chan->fd = fd;
+
+	chan->io = io_new(fd);
+	if (!chan->io)
 		goto fail;
 
-	if (!io_set_disconnect_handler(att->io, disconnect_cb, att, NULL))
+	if (!io_set_read_handler(chan->io, can_read_data, chan, NULL))
 		goto fail;
 
-	att->io_on_l2cap = is_io_l2cap_based(att->fd);
-	if (!att->io_on_l2cap)
-		att->io_sec_level = BT_ATT_SECURITY_LOW;
+	if (!io_set_disconnect_handler(chan->io, disconnect_cb, chan, NULL))
+		goto fail;
 
-	if (bt_att_get_link_type(att) == BT_ATT_LINK_BREDR)
-		att->mtu = get_l2cap_mtu(att->fd);
-	else
-		att->mtu = BT_ATT_DEFAULT_LE_MTU;
+	chan->type = type;
+	switch (chan->type) {
+	case BT_ATT_LOCAL:
+		chan->sec_level = BT_ATT_SECURITY_LOW;
+		/* fall through */
+	case BT_ATT_LE:
+		chan->mtu = BT_ATT_DEFAULT_LE_MTU;
+		break;
+	default:
+		chan->mtu = get_l2cap_mtu(chan->fd);
+	}
 
-	if (att->mtu < BT_ATT_DEFAULT_LE_MTU)
+	if (chan->mtu < BT_ATT_DEFAULT_LE_MTU)
 		goto fail;
 
-	att->buf = malloc(att->mtu);
-	if (!att->buf)
+	chan->buf = malloc(chan->mtu);
+	if (!chan->buf)
 		goto fail;
 
-	return bt_att_ref(att);
+	return chan;
 
 fail:
-	bt_att_free(att);
+	bt_att_chan_free(chan);
 
 	return NULL;
 }
 
+struct bt_att *bt_att_new(int fd, bool ext_signed)
+{
+	struct bt_att *att;
+	struct bt_att_chan *chan;
+
+	chan = bt_att_chan_new(fd, io_get_type(fd));
+	if (!chan)
+		return NULL;
+
+	att = new0(struct bt_att, 1);
+	att->chans = queue_new();
+	att->mtu = chan->mtu;
+
+	queue_push_head(att->chans, chan);
+	chan->att = att;
+
+	/* crypto is optional, if not available leave it NULL */
+	if (!ext_signed)
+		att->crypto = bt_crypto_new();
+
+	att->req_queue = queue_new();
+	att->ind_queue = queue_new();
+	att->write_queue = queue_new();
+	att->notify_list = queue_new();
+	att->disconn_list = queue_new();
+
+	return bt_att_ref(att);
+}
+
 struct bt_att *bt_att_ref(struct bt_att *att)
 {
 	if (!att)
@@ -1094,18 +1208,67 @@ void bt_att_unref(struct bt_att *att)
 
 bool bt_att_set_close_on_unref(struct bt_att *att, bool do_close)
 {
-	if (!att || !att->io)
+	const struct queue_entry *entry;
+
+	if (!att)
 		return false;
 
-	return io_set_close_on_destroy(att->io, do_close);
+	att->close_on_unref = do_close;
+
+	for (entry = queue_get_entries(att->chans); entry;
+						entry = entry->next) {
+		struct bt_att_chan *chan = entry->data;
+
+		if (!io_set_close_on_destroy(chan->io, do_close))
+			return false;
+	}
+
+	return true;
+}
+
+int bt_att_attach_fd(struct bt_att *att, int fd)
+{
+	struct bt_att_chan *chan;
+
+	if (!att || fd < 0)
+		return -EINVAL;
+
+	chan = bt_att_chan_new(fd, BT_ATT_EATT);
+	if (!chan)
+		return -EINVAL;
+
+	queue_push_tail(att->chans, chan);
+	chan->att = att;
+
+	if (chan->mtu > att->mtu)
+		att->mtu = chan->mtu;
+
+	io_set_close_on_destroy(chan->io, att->close_on_unref);
+
+	return 0;
 }
 
 int bt_att_get_fd(struct bt_att *att)
 {
+	struct bt_att_chan *chan;
+
 	if (!att)
 		return -1;
 
-	return att->fd;
+	if (queue_isempty(att->chans))
+		return -ENOTCONN;
+
+	chan = queue_peek_head(att->chans);
+
+	return chan->fd;
+}
+
+int bt_att_get_channels(struct bt_att *att)
+{
+	if (!att)
+		return 0;
+
+	return queue_length(att->chans);
 }
 
 bool bt_att_set_debug(struct bt_att *att, bt_att_debug_func_t callback,
@@ -1134,6 +1297,7 @@ uint16_t bt_att_get_mtu(struct bt_att *att)
 
 bool bt_att_set_mtu(struct bt_att *att, uint16_t mtu)
 {
+	struct bt_att_chan *chan;
 	void *buf;
 
 	if (!att)
@@ -1142,38 +1306,37 @@ bool bt_att_set_mtu(struct bt_att *att, uint16_t mtu)
 	if (mtu < BT_ATT_DEFAULT_LE_MTU)
 		return false;
 
+	chan = queue_peek_head(att->chans);
+	if (!chan)
+		return -ENOTCONN;
+
 	buf = malloc(mtu);
 	if (!buf)
 		return false;
 
-	free(att->buf);
+	free(chan->buf);
+
+	chan->mtu = mtu;
+	chan->buf = buf;
 
-	att->mtu = mtu;
-	att->buf = buf;
+	if (chan->mtu > att->mtu)
+		att->mtu = chan->mtu;
 
 	return true;
 }
 
 uint8_t bt_att_get_link_type(struct bt_att *att)
 {
-	struct sockaddr_l2 src;
-	socklen_t len;
+	struct bt_att_chan *chan;
 
 	if (!att)
 		return -EINVAL;
 
-	if (!att->io_on_l2cap)
-		return BT_ATT_LINK_LOCAL;
+	chan = queue_peek_head(att->chans);
+	if (!chan)
+		return -ENOTCONN;
 
-	len = sizeof(src);
-	memset(&src, 0, len);
-	if (getsockname(att->fd, (void *)&src, &len) < 0)
-		return -errno;
-
-	if (src.l2_bdaddr_type == BDADDR_BREDR)
-		return BT_ATT_LINK_BREDR;
-
-	return BT_ATT_LINK_LE;
+	return chan->type;
 }
 
 bool bt_att_set_timeout_cb(struct bt_att *att, bt_att_timeout_func_t callback,
@@ -1200,7 +1363,7 @@ unsigned int bt_att_register_disconnect(struct bt_att *att,
 {
 	struct att_disconn *disconn;
 
-	if (!att || !att->io)
+	if (!att || queue_isempty(att->chans))
 		return 0;
 
 	disconn = new0(struct att_disconn, 1);
@@ -1229,7 +1392,7 @@ bool bt_att_unregister_disconnect(struct bt_att *att, unsigned int id)
 		return false;
 
 	/* Check if disconnect is running */
-	if (!att->io) {
+	if (!queue_isempty(att->chans)) {
 		disconn = queue_find(att->disconn_list, match_disconn_id,
 							UINT_TO_PTR(id));
 		if (!disconn)
@@ -1256,7 +1419,7 @@ unsigned int bt_att_send(struct bt_att *att, uint8_t opcode,
 	struct att_send_op *op;
 	bool result;
 
-	if (!att || !att->io)
+	if (!att || queue_isempty(att->chans))
 		return 0;
 
 	op = create_att_send_op(att, opcode, pdu, length, callback, user_data,
@@ -1308,21 +1471,31 @@ static bool match_op_id(const void *a, const void *b)
 
 bool bt_att_cancel(struct bt_att *att, unsigned int id)
 {
+	const struct queue_entry *entry;
 	struct att_send_op *op;
 
 	if (!att || !id)
 		return false;
 
-	if (att->pending_req && att->pending_req->id == id) {
-		/* Don't cancel the pending request; remove it's handlers */
-		cancel_att_send_op(att->pending_req);
-		return true;
-	}
+	for (entry = queue_get_entries(att->chans); entry;
+						entry = entry->next) {
+		struct bt_att_chan *chan = entry->data;
 
-	if (att->pending_ind && att->pending_ind->id == id) {
-		/* Don't cancel the pending indication; remove it's handlers */
-		cancel_att_send_op(att->pending_ind);
-		return true;
+		if (chan->pending_req && chan->pending_req->id == id) {
+			/* Don't cancel the pending request; remove it's
+			 * handlers
+			 */
+			cancel_att_send_op(chan->pending_req);
+			return true;
+		}
+
+		if (chan->pending_ind && chan->pending_ind->id == id) {
+			/* Don't cancel the pending indication; remove it's
+			 * handlers.
+			 */
+			cancel_att_send_op(chan->pending_ind);
+			return true;
+		}
 	}
 
 	op = queue_remove_if(att->req_queue, match_op_id, UINT_TO_PTR(id));
@@ -1350,6 +1523,8 @@ done:
 
 bool bt_att_cancel_all(struct bt_att *att)
 {
+	const struct queue_entry *entry;
+
 	if (!att)
 		return false;
 
@@ -1357,13 +1532,22 @@ bool bt_att_cancel_all(struct bt_att *att)
 	queue_remove_all(att->ind_queue, NULL, NULL, destroy_att_send_op);
 	queue_remove_all(att->write_queue, NULL, NULL, destroy_att_send_op);
 
-	if (att->pending_req)
-		/* Don't cancel the pending request; remove it's handlers */
-		cancel_att_send_op(att->pending_req);
-
-	if (att->pending_ind)
-		/* Don't cancel the pending request; remove it's handlers */
-		cancel_att_send_op(att->pending_ind);
+	for (entry = queue_get_entries(att->chans); entry;
+						entry = entry->next) {
+		struct bt_att_chan *chan = entry->data;
+
+		if (chan->pending_req)
+			/* Don't cancel the pending request; remove it's
+			 * handlers
+			 */
+			cancel_att_send_op(chan->pending_req);
+
+		if (chan->pending_ind)
+			/* Don't cancel the pending request; remove it's
+			 * handlers
+			 */
+			cancel_att_send_op(chan->pending_ind);
+	}
 
 	return true;
 }
@@ -1424,7 +1608,7 @@ unsigned int bt_att_register(struct bt_att *att, uint8_t opcode,
 {
 	struct att_notify *notify;
 
-	if (!att || !callback || !att->io)
+	if (!att || !callback || queue_isempty(att->chans))
 		return 0;
 
 	notify = new0(struct att_notify, 1);
@@ -1475,51 +1659,39 @@ bool bt_att_unregister_all(struct bt_att *att)
 
 int bt_att_get_security(struct bt_att *att, uint8_t *enc_size)
 {
-	struct bt_security sec;
-	socklen_t len;
+	struct bt_att_chan *chan;
+	int ret;
 
 	if (!att)
 		return -EINVAL;
 
-	if (!att->io_on_l2cap) {
-		if (enc_size)
-			*enc_size = att->enc_size;
+	chan = queue_peek_head(att->chans);
+	if (!chan)
+		return -ENOTCONN;
 
-		return att->io_sec_level;
-	}
-
-	memset(&sec, 0, sizeof(sec));
-	len = sizeof(sec);
-	if (getsockopt(att->fd, SOL_BLUETOOTH, BT_SECURITY, &sec, &len) < 0)
-		return -EIO;
+	ret = bt_att_chan_get_security(chan);
+	if (ret < 0)
+		return ret;
 
 	if (enc_size)
 		*enc_size = att->enc_size;
 
-	return sec.level;
+	return ret;
 }
 
 bool bt_att_set_security(struct bt_att *att, int level)
 {
-	struct bt_security sec;
+	struct bt_att_chan *chan;
 
 	if (!att || level < BT_ATT_SECURITY_AUTO ||
 						level > BT_ATT_SECURITY_HIGH)
 		return false;
 
-	if (!att->io_on_l2cap) {
-		att->io_sec_level = level;
-		return true;
-	}
+	chan = queue_peek_head(att->chans);
+	if (!chan)
+		return -ENOTCONN;
 
-	memset(&sec, 0, sizeof(sec));
-	sec.level = level;
-
-	if (setsockopt(att->fd, SOL_BLUETOOTH, BT_SECURITY, &sec,
-							sizeof(sec)) < 0)
-		return false;
-
-	return true;
+	return bt_att_chan_set_security(chan, level);
 }
 
 void bt_att_set_enc_key_size(struct bt_att *att, uint8_t enc_size)
diff --git a/src/shared/att.h b/src/shared/att.h
index 49d93269b..110700846 100644
--- a/src/shared/att.h
+++ b/src/shared/att.h
@@ -37,6 +37,10 @@ bool bt_att_set_close_on_unref(struct bt_att *att, bool do_close);
 
 int bt_att_get_fd(struct bt_att *att);
 
+int bt_att_attach_fd(struct bt_att *att, int fd);
+
+int bt_att_get_channels(struct bt_att *att);
+
 typedef void (*bt_att_response_func_t)(uint8_t opcode, const void *pdu,
 					uint16_t length, void *user_data);
 typedef void (*bt_att_notify_func_t)(uint8_t opcode, const void *pdu,
diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c
index 29254cb61..3ce126485 100644
--- a/src/shared/gatt-client.c
+++ b/src/shared/gatt-client.c
@@ -1909,7 +1909,7 @@ static bool gatt_client_init(struct bt_gatt_client *client, uint16_t mtu)
 	 * the MTU size is negotiated using L2CAP channel configuration
 	 * procedures.
 	 */
-	if (bt_att_get_link_type(client->att) == BT_ATT_LINK_BREDR)
+	if (bt_att_get_link_type(client->att) == BT_ATT_BREDR)
 		goto discover;
 
 	/* Check if MTU needs to be send */
-- 
2.21.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 05/12] shared/gatt-client: Add support for EATT features
  2020-02-28 23:46 [PATCH v2 00/12] Userspace Bluetooth 5.2 initial support Luiz Augusto von Dentz
                   ` (3 preceding siblings ...)
  2020-02-28 23:46 ` [PATCH v2 04/12] share/att: Add EATT support Luiz Augusto von Dentz
@ 2020-02-28 23:46 ` Luiz Augusto von Dentz
  2020-02-28 23:46 ` [PATCH v2 06/12] gatt: Enable EATT bearer support Luiz Augusto von Dentz
                   ` (7 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2020-02-28 23:46 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

This enables EATT in the Client Features if the EATT characteristic is
present in the database.
---
 attrib/gattrib.c         |   5 +-
 lib/uuid.h               |   3 +
 peripheral/gatt.c        |   2 +-
 src/device.c             |   2 +-
 src/shared/att.c         | 271 ++++++++++++++++++++++++++-------------
 src/shared/att.h         |  14 +-
 src/shared/gatt-client.c | 167 ++++++++++++++++++++++--
 src/shared/gatt-client.h |   5 +-
 src/shared/gatt-server.c | 205 ++++++++++++++++-------------
 tools/btgatt-client.c    |   2 +-
 unit/test-gatt.c         |  23 ++--
 11 files changed, 494 insertions(+), 205 deletions(-)

diff --git a/attrib/gattrib.c b/attrib/gattrib.c
index 57ca01541..8aa0f5eff 100644
--- a/attrib/gattrib.c
+++ b/attrib/gattrib.c
@@ -275,8 +275,9 @@ static void attrib_callback_result(uint8_t opcode, const void *pdu,
 	free(buf);
 }
 
-static void attrib_callback_notify(uint8_t opcode, const void *pdu,
-					uint16_t length, void *user_data)
+static void attrib_callback_notify(struct bt_att_chan *chan, uint8_t opcode,
+					const void *pdu, uint16_t length,
+					void *user_data)
 {
 	uint8_t *buf;
 	struct attrib_callbacks *cb = user_data;
diff --git a/lib/uuid.h b/lib/uuid.h
index fbc08f51e..ebdcf729c 100644
--- a/lib/uuid.h
+++ b/lib/uuid.h
@@ -154,6 +154,9 @@ extern "C" {
 #define GATT_CHARAC_CLI_FEAT				0x2B29
 #define GATT_CHARAC_DB_HASH				0x2B2A
 
+/* GATT Server Supported features */
+#define GATT_CHARAC_SERVER_FEAT				0x2B3A
+
 typedef struct {
 	enum {
 		BT_UUID_UNSPEC = 0,
diff --git a/peripheral/gatt.c b/peripheral/gatt.c
index 08541c424..bbbf3f59f 100644
--- a/peripheral/gatt.c
+++ b/peripheral/gatt.c
@@ -136,7 +136,7 @@ static struct gatt_conn *gatt_conn_new(int fd)
 		return NULL;
 	}
 
-	conn->client = bt_gatt_client_new(gatt_cache, conn->att, mtu);
+	conn->client = bt_gatt_client_new(gatt_cache, conn->att, mtu, 0);
 	if (!conn->gatt) {
 		fprintf(stderr, "Failed to create GATT client\n");
 		bt_gatt_server_unref(conn->gatt);
diff --git a/src/device.c b/src/device.c
index a8f4c22f3..3f4afa281 100644
--- a/src/device.c
+++ b/src/device.c
@@ -4939,7 +4939,7 @@ static void gatt_client_init(struct btd_device *device)
 	}
 
 	device->client = bt_gatt_client_new(device->db, device->att,
-							device->att_mtu);
+							device->att_mtu, 0);
 	if (!device->client) {
 		DBG("Failed to initialize");
 		return;
diff --git a/src/shared/att.c b/src/shared/att.c
index 1313703f9..56ea40c46 100644
--- a/src/shared/att.c
+++ b/src/shared/att.c
@@ -56,6 +56,8 @@ struct bt_att_chan {
 	uint8_t type;
 	int sec_level;			/* Only used for non-L2CAP */
 
+	struct queue *queue;		/* Channel dedicated queue */
+
 	struct att_send_op *pending_req;
 	struct att_send_op *pending_ind;
 	bool writer_active;
@@ -375,32 +377,47 @@ static struct att_send_op *pick_next_send_op(struct bt_att_chan *chan)
 	struct bt_att *att = chan->att;
 	struct att_send_op *op;
 
-	/* See if any operations are already in the write queue */
-	op = queue_pop_head(att->write_queue);
+	/* Check if there is anything queued on the channel */
+	op = queue_pop_head(chan->queue);
 	if (op)
 		return op;
 
+	/* See if any operations are already in the write queue */
+	op = queue_peek_head(att->write_queue);
+	if (op && op->len <= chan->mtu)
+		return queue_pop_head(att->write_queue);
+
 	/* If there is no pending request, pick an operation from the
 	 * request queue.
 	 */
 	if (!chan->pending_req) {
-		op = queue_pop_head(att->req_queue);
-		if (op)
-			return op;
+		op = queue_peek_head(att->req_queue);
+		if (op && op->len <= chan->mtu)
+			return queue_pop_head(att->req_queue);
 	}
 
 	/* There is either a request pending or no requests queued. If there is
 	 * no pending indication, pick an operation from the indication queue.
 	 */
 	if (!chan->pending_ind) {
-		op = queue_pop_head(att->ind_queue);
-		if (op)
-			return op;
+		op = queue_peek_head(att->ind_queue);
+		if (op && op->len <= chan->mtu)
+			return queue_pop_head(att->ind_queue);
 	}
 
 	return NULL;
 }
 
+static void disc_att_send_op(void *data)
+{
+	struct att_send_op *op = data;
+
+	if (op->callback)
+		op->callback(BT_ATT_OP_ERROR_RSP, NULL, 0, op->user_data);
+
+	destroy_att_send_op(op);
+}
+
 struct timeout_data {
 	struct bt_att_chan *chan;
 	unsigned int id;
@@ -425,13 +442,14 @@ static bool timeout_cb(void *user_data)
 		return false;
 
 	util_debug(att->debug_callback, att->debug_data,
-				"Operation timed out: 0x%02x", op->opcode);
+				"(chan %p) Operation timed out: 0x%02x",
+				chan, op->opcode);
 
 	if (att->timeout_callback)
 		att->timeout_callback(op->id, op->opcode, att->timeout_data);
 
 	op->timeout_id = 0;
-	destroy_att_send_op(op);
+	disc_att_send_op(op);
 
 	/*
 	 * Directly terminate the connection as required by the ATT protocol.
@@ -450,39 +468,52 @@ static void write_watch_destroy(void *user_data)
 	chan->writer_active = false;
 }
 
+static ssize_t bt_att_chan_write(struct bt_att_chan *chan, uint8_t opcode,
+					const void *pdu, uint16_t len)
+{
+	struct bt_att *att = chan->att;
+	ssize_t ret;
+	struct iovec iov;
+
+	iov.iov_base = (void *) pdu;
+	iov.iov_len = len;
+
+	util_debug(att->debug_callback, att->debug_data,
+					"(chan %p) ATT op 0x%02x",
+					chan, opcode);
+
+	ret = io_send(chan->io, &iov, 1);
+	if (ret < 0) {
+		util_debug(att->debug_callback, att->debug_data,
+					"(chan %p) write failed: %s",
+					chan, strerror(-ret));
+
+		return ret;
+	}
+
+	util_hexdump('<', pdu, ret, att->debug_callback, att->debug_data);
+
+	return ret;
+}
+
 static bool can_write_data(struct io *io, void *user_data)
 {
 	struct bt_att_chan *chan = user_data;
-	struct bt_att *att = chan->att;
 	struct att_send_op *op;
 	struct timeout_data *timeout;
-	ssize_t ret;
-	struct iovec iov;
 
 	op = pick_next_send_op(chan);
 	if (!op)
 		return false;
 
-	iov.iov_base = op->pdu;
-	iov.iov_len = op->len;
-
-	ret = io_send(io, &iov, 1);
-	if (ret < 0) {
-		util_debug(att->debug_callback, att->debug_data,
-					"write failed: %s", strerror(-ret));
+	if (!bt_att_chan_write(chan, op->opcode, op->pdu, op->len)) {
 		if (op->callback)
 			op->callback(BT_ATT_OP_ERROR_RSP, NULL, 0,
 							op->user_data);
-
 		destroy_att_send_op(op);
 		return true;
 	}
 
-	util_debug(att->debug_callback, att->debug_data,
-					"ATT op 0x%02x", op->opcode);
-
-	util_hexdump('<', op->pdu, ret, att->debug_callback, att->debug_data);
-
 	/* Based on the operation type, set either the pending request or the
 	 * pending indication. If it came from the write queue, then there is
 	 * no need to keep it around.
@@ -528,7 +559,7 @@ static void wakeup_chan_writer(void *data, void *user_data)
 	/* Set the write handler only if there is anything that can be sent
 	 * at all.
 	 */
-	if (queue_isempty(att->write_queue)) {
+	if (queue_isempty(chan->queue) && queue_isempty(att->write_queue)) {
 		if ((chan->pending_req || queue_isempty(att->req_queue)) &&
 			(chan->pending_ind || queue_isempty(att->ind_queue)))
 			return;
@@ -558,16 +589,6 @@ static void disconn_handler(void *data, void *user_data)
 		disconn->callback(err, disconn->user_data);
 }
 
-static void disc_att_send_op(void *data)
-{
-	struct att_send_op *op = data;
-
-	if (op->callback)
-		op->callback(BT_ATT_OP_ERROR_RSP, NULL, 0, op->user_data);
-
-	destroy_att_send_op(op);
-}
-
 static void bt_att_chan_free(void *data)
 {
 	struct bt_att_chan *chan = data;
@@ -578,6 +599,8 @@ static void bt_att_chan_free(void *data)
 	if (chan->pending_ind)
 		destroy_att_send_op(chan->pending_ind);
 
+	queue_destroy(chan->queue, destroy_att_send_op);
+
 	io_destroy(chan->io);
 
 	free(chan->buf);
@@ -595,8 +618,8 @@ static bool disconnect_cb(struct io *io, void *user_data)
 
 	if (getsockopt(chan->fd, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {
 		util_debug(chan->att->debug_callback, chan->att->debug_data,
-					"Failed to obtain disconnect error: %s",
-					strerror(errno));
+					"(chan %p) Failed to obtain disconnect"
+					" error: %s", chan, strerror(errno));
 		err = 0;
 	}
 
@@ -728,7 +751,8 @@ static bool handle_error_rsp(struct bt_att_chan *chan, uint8_t *pdu,
 	}
 
 	util_debug(att->debug_callback, att->debug_data,
-						"Retrying operation %p", op);
+						"(chan %p) Retrying operation "
+						"%p", chan, op);
 
 	chan->pending_req = NULL;
 
@@ -752,7 +776,8 @@ static void handle_rsp(struct bt_att_chan *chan, uint8_t opcode, uint8_t *pdu,
 	 */
 	if (!op) {
 		util_debug(att->debug_callback, att->debug_data,
-					"Received unexpected ATT response");
+					"(chan %p) Received unexpected ATT "
+					"response", chan);
 		io_shutdown(chan->io);
 		return;
 	}
@@ -784,7 +809,8 @@ static void handle_rsp(struct bt_att_chan *chan, uint8_t opcode, uint8_t *pdu,
 
 fail:
 	util_debug(att->debug_callback, att->debug_data,
-			"Failed to handle response PDU; opcode: 0x%02x", opcode);
+			"(chan %p) Failed to handle response PDU; opcode: "
+			"0x%02x", chan, opcode);
 
 	rsp_opcode = BT_ATT_OP_ERROR_RSP;
 
@@ -809,7 +835,8 @@ static void handle_conf(struct bt_att_chan *chan, uint8_t *pdu, ssize_t pdu_len)
 	 */
 	if (!op || pdu_len) {
 		util_debug(att->debug_callback, att->debug_data,
-				"Received unexpected/invalid ATT confirmation");
+				"(chan %p) Received unexpected/invalid ATT "
+				"confirmation", chan);
 		io_shutdown(chan->io);
 		return;
 	}
@@ -935,7 +962,7 @@ static void handle_notify(struct bt_att_chan *chan, uint8_t opcode,
 		found = true;
 
 		if (notify->callback)
-			notify->callback(opcode, pdu, pdu_len,
+			notify->callback(chan, opcode, pdu, pdu_len,
 							notify->user_data);
 
 		/* callback could remove all entries from notify list */
@@ -966,6 +993,10 @@ static bool can_read_data(struct io *io, void *user_data)
 	if (bytes_read < 0)
 		return false;
 
+	util_debug(att->debug_callback, att->debug_data,
+				"(chan %p) ATT received: %zd",
+				chan, bytes_read);
+
 	util_hexdump('>', chan->buf, bytes_read,
 				att->debug_callback, att->debug_data);
 
@@ -981,12 +1012,14 @@ static bool can_read_data(struct io *io, void *user_data)
 	switch (get_op_type(opcode)) {
 	case ATT_OP_TYPE_RSP:
 		util_debug(att->debug_callback, att->debug_data,
-				"ATT response received: 0x%02x", opcode);
+				"(chan %p) ATT response received: 0x%02x",
+				chan, opcode);
 		handle_rsp(chan, opcode, pdu + 1, bytes_read - 1);
 		break;
 	case ATT_OP_TYPE_CONF:
 		util_debug(att->debug_callback, att->debug_data,
-				"ATT confirmation received: 0x%02x", opcode);
+				"(chan %p) ATT confirmation received: 0x%02x",
+				chan, opcode);
 		handle_conf(chan, pdu + 1, bytes_read - 1);
 		break;
 	case ATT_OP_TYPE_REQ:
@@ -997,8 +1030,9 @@ static bool can_read_data(struct io *io, void *user_data)
 		 */
 		if (chan->in_req) {
 			util_debug(att->debug_callback, att->debug_data,
-					"Received request while another is "
-					"pending: 0x%02x", opcode);
+					"(chan %p) Received request while "
+					"another is pending: 0x%02x",
+					chan, opcode);
 			io_shutdown(chan->io);
 			bt_att_unref(chan->att);
 
@@ -1017,7 +1051,8 @@ static bool can_read_data(struct io *io, void *user_data)
 		 * let them act on it.
 		 */
 		util_debug(att->debug_callback, att->debug_data,
-					"ATT PDU received: 0x%02x", opcode);
+					"(chan %p) ATT PDU received: 0x%02x",
+					chan, opcode);
 		handle_notify(chan, opcode, pdu + 1, bytes_read - 1);
 		break;
 	}
@@ -1075,16 +1110,19 @@ static void bt_att_free(struct bt_att *att)
 	free(att);
 }
 
-static uint16_t get_l2cap_mtu(int fd)
+static uint16_t io_get_mtu(int fd)
 {
 	socklen_t len;
 	struct l2cap_options l2o;
 
 	len = sizeof(l2o);
-	if (getsockopt(fd, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &len) < 0)
-		return 0;
+	if (!getsockopt(fd, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &len))
+		return l2o.omtu;
 
-	return l2o.omtu;
+	if (!getsockopt(fd, SOL_BLUETOOTH, BT_SNDMTU, &l2o.omtu, &len))
+		return l2o.omtu;
+
+	return 0;
 }
 
 static uint8_t io_get_type(int fd)
@@ -1135,7 +1173,7 @@ static struct bt_att_chan *bt_att_chan_new(int fd, uint8_t type)
 		chan->mtu = BT_ATT_DEFAULT_LE_MTU;
 		break;
 	default:
-		chan->mtu = get_l2cap_mtu(chan->fd);
+		chan->mtu = io_get_mtu(chan->fd);
 	}
 
 	if (chan->mtu < BT_ATT_DEFAULT_LE_MTU)
@@ -1145,6 +1183,8 @@ static struct bt_att_chan *bt_att_chan_new(int fd, uint8_t type)
 	if (!chan->buf)
 		goto fail;
 
+	chan->queue = queue_new();
+
 	return chan;
 
 fail:
@@ -1153,6 +1193,23 @@ fail:
 	return NULL;
 }
 
+static void bt_att_attach_chan(struct bt_att *att, struct bt_att_chan *chan)
+{
+	/* Push to head as EATT channels have higher priority */
+	queue_push_head(att->chans, chan);
+	chan->att = att;
+
+	if (chan->mtu > att->mtu)
+		att->mtu = chan->mtu;
+
+	io_set_close_on_destroy(chan->io, att->close_on_unref);
+
+	util_debug(att->debug_callback, att->debug_data, "Channel %p attached",
+									chan);
+
+	wakeup_chan_writer(chan, NULL);
+}
+
 struct bt_att *bt_att_new(int fd, bool ext_signed)
 {
 	struct bt_att *att;
@@ -1166,9 +1223,6 @@ struct bt_att *bt_att_new(int fd, bool ext_signed)
 	att->chans = queue_new();
 	att->mtu = chan->mtu;
 
-	queue_push_head(att->chans, chan);
-	chan->att = att;
-
 	/* crypto is optional, if not available leave it NULL */
 	if (!ext_signed)
 		att->crypto = bt_crypto_new();
@@ -1179,6 +1233,8 @@ struct bt_att *bt_att_new(int fd, bool ext_signed)
 	att->notify_list = queue_new();
 	att->disconn_list = queue_new();
 
+	bt_att_attach_chan(att, chan);
+
 	return bt_att_ref(att);
 }
 
@@ -1237,13 +1293,7 @@ int bt_att_attach_fd(struct bt_att *att, int fd)
 	if (!chan)
 		return -EINVAL;
 
-	queue_push_tail(att->chans, chan);
-	chan->att = att;
-
-	if (chan->mtu > att->mtu)
-		att->mtu = chan->mtu;
-
-	io_set_close_on_destroy(chan->io, att->close_on_unref);
+	bt_att_attach_chan(att, chan);
 
 	return 0;
 }
@@ -1258,7 +1308,7 @@ int bt_att_get_fd(struct bt_att *att)
 	if (queue_isempty(att->chans))
 		return -ENOTCONN;
 
-	chan = queue_peek_head(att->chans);
+	chan = queue_peek_tail(att->chans);
 
 	return chan->fd;
 }
@@ -1306,7 +1356,8 @@ bool bt_att_set_mtu(struct bt_att *att, uint16_t mtu)
 	if (mtu < BT_ATT_DEFAULT_LE_MTU)
 		return false;
 
-	chan = queue_peek_head(att->chans);
+	/* Original channel is always the last */
+	chan = queue_peek_tail(att->chans);
 	if (!chan)
 		return -ENOTCONN;
 
@@ -1332,7 +1383,7 @@ uint8_t bt_att_get_link_type(struct bt_att *att)
 	if (!att)
 		return -EINVAL;
 
-	chan = queue_peek_head(att->chans);
+	chan = queue_peek_tail(att->chans);
 	if (!chan)
 		return -ENOTCONN;
 
@@ -1392,7 +1443,7 @@ bool bt_att_unregister_disconnect(struct bt_att *att, unsigned int id)
 		return false;
 
 	/* Check if disconnect is running */
-	if (!queue_isempty(att->chans)) {
+	if (queue_isempty(att->chans)) {
 		disconn = queue_find(att->disconn_list, match_disconn_id,
 							UINT_TO_PTR(id));
 		if (!disconn)
@@ -1461,6 +1512,33 @@ unsigned int bt_att_send(struct bt_att *att, uint8_t opcode,
 	return op->id;
 }
 
+unsigned int bt_att_chan_send(struct bt_att_chan *chan, uint8_t opcode,
+				const void *pdu, uint16_t len,
+				bt_att_response_func_t callback,
+				void *user_data,
+				bt_att_destroy_func_t destroy)
+{
+	struct att_send_op *op;
+
+	if (!chan || !chan->att)
+		return -EINVAL;
+
+	op = create_att_send_op(chan->att, opcode, pdu, len, callback,
+						user_data, destroy);
+	if (!op)
+		return -EINVAL;
+
+	if (!queue_push_tail(chan->queue, op)) {
+		free(op->pdu);
+		free(op);
+		return 0;
+	}
+
+	wakeup_chan_writer(chan, NULL);
+
+	return op->id;
+}
+
 static bool match_op_id(const void *a, const void *b)
 {
 	const struct att_send_op *op = a;
@@ -1469,6 +1547,33 @@ static bool match_op_id(const void *a, const void *b)
 	return op->id == id;
 }
 
+bool bt_att_chan_cancel(struct bt_att_chan *chan, unsigned int id)
+{
+	struct att_send_op *op;
+
+	if (chan->pending_req && chan->pending_req->id == id) {
+		/* Don't cancel the pending request; remove it's handlers */
+		cancel_att_send_op(chan->pending_req);
+		return true;
+	}
+
+	if (chan->pending_ind && chan->pending_ind->id == id) {
+		/* Don't cancel the pending indication; remove it's handlers. */
+		cancel_att_send_op(chan->pending_ind);
+		return true;
+	}
+
+	op = queue_remove_if(chan->queue, match_op_id, UINT_TO_PTR(id));
+	if (!op)
+		return false;
+
+	destroy_att_send_op(op);
+
+	wakeup_chan_writer(chan, NULL);
+
+	return true;
+}
+
 bool bt_att_cancel(struct bt_att *att, unsigned int id)
 {
 	const struct queue_entry *entry;
@@ -1477,25 +1582,13 @@ bool bt_att_cancel(struct bt_att *att, unsigned int id)
 	if (!att || !id)
 		return false;
 
+	/* Lookuo request on each channel first */
 	for (entry = queue_get_entries(att->chans); entry;
 						entry = entry->next) {
 		struct bt_att_chan *chan = entry->data;
 
-		if (chan->pending_req && chan->pending_req->id == id) {
-			/* Don't cancel the pending request; remove it's
-			 * handlers
-			 */
-			cancel_att_send_op(chan->pending_req);
-			return true;
-		}
-
-		if (chan->pending_ind && chan->pending_ind->id == id) {
-			/* Don't cancel the pending indication; remove it's
-			 * handlers.
-			 */
-			cancel_att_send_op(chan->pending_ind);
+		if (bt_att_chan_cancel(chan, id))
 			return true;
-		}
 	}
 
 	op = queue_remove_if(att->req_queue, match_op_id, UINT_TO_PTR(id));
@@ -1580,14 +1673,14 @@ static uint8_t att_ecode_from_error(int err)
 	return BT_ATT_ERROR_UNLIKELY;
 }
 
-unsigned int bt_att_send_error_rsp(struct bt_att *att, uint8_t opcode,
+int bt_att_chan_send_error_rsp(struct bt_att_chan *chan, uint8_t opcode,
 						uint16_t handle, int error)
 {
 	struct bt_att_pdu_error_rsp pdu;
 	uint8_t ecode;
 
-	if (!att || !opcode)
-		return 0;
+	if (!chan || !chan->att || !opcode)
+		return -EINVAL;
 
 	ecode = att_ecode_from_error(error);
 
@@ -1597,8 +1690,8 @@ unsigned int bt_att_send_error_rsp(struct bt_att *att, uint8_t opcode,
 	put_le16(handle, &pdu.handle);
 	pdu.ecode = ecode;
 
-	return bt_att_send(att, BT_ATT_OP_ERROR_RSP, &pdu, sizeof(pdu),
-							NULL, NULL, NULL);
+	return bt_att_chan_send_rsp(chan, BT_ATT_OP_ERROR_RSP, &pdu,
+							sizeof(pdu));
 }
 
 unsigned int bt_att_register(struct bt_att *att, uint8_t opcode,
@@ -1665,7 +1758,7 @@ int bt_att_get_security(struct bt_att *att, uint8_t *enc_size)
 	if (!att)
 		return -EINVAL;
 
-	chan = queue_peek_head(att->chans);
+	chan = queue_peek_tail(att->chans);
 	if (!chan)
 		return -ENOTCONN;
 
@@ -1687,7 +1780,7 @@ bool bt_att_set_security(struct bt_att *att, int level)
 						level > BT_ATT_SECURITY_HIGH)
 		return false;
 
-	chan = queue_peek_head(att->chans);
+	chan = queue_peek_tail(att->chans);
 	if (!chan)
 		return -ENOTCONN;
 
diff --git a/src/shared/att.h b/src/shared/att.h
index 110700846..ed20bb5b8 100644
--- a/src/shared/att.h
+++ b/src/shared/att.h
@@ -27,6 +27,7 @@
 #include "src/shared/att-types.h"
 
 struct bt_att;
+struct bt_att_chan;
 
 struct bt_att *bt_att_new(int fd, bool ext_signed);
 
@@ -43,7 +44,8 @@ int bt_att_get_channels(struct bt_att *att);
 
 typedef void (*bt_att_response_func_t)(uint8_t opcode, const void *pdu,
 					uint16_t length, void *user_data);
-typedef void (*bt_att_notify_func_t)(uint8_t opcode, const void *pdu,
+typedef void (*bt_att_notify_func_t)(struct bt_att_chan *chan,
+					uint8_t opcode, const void *pdu,
 					uint16_t length, void *user_data);
 typedef void (*bt_att_destroy_func_t)(void *user_data);
 typedef void (*bt_att_debug_func_t)(const char *str, void *user_data);
@@ -68,10 +70,18 @@ unsigned int bt_att_send(struct bt_att *att, uint8_t opcode,
 					bt_att_response_func_t callback,
 					void *user_data,
 					bt_att_destroy_func_t destroy);
+unsigned int bt_att_chan_send(struct bt_att_chan *chan, uint8_t opcode,
+					const void *pdu, uint16_t len,
+					bt_att_response_func_t callback,
+					void *user_data,
+					bt_att_destroy_func_t destroy);
+#define bt_att_chan_send_rsp(chan, opcode, pdu, len) \
+	bt_att_chan_send(chan, opcode, pdu, len, NULL, NULL, NULL)
+bool bt_att_chan_cancel(struct bt_att_chan *chan, unsigned int id);
 bool bt_att_cancel(struct bt_att *att, unsigned int id);
 bool bt_att_cancel_all(struct bt_att *att);
 
-unsigned int bt_att_send_error_rsp(struct bt_att *att, uint8_t opcode,
+int bt_att_chan_send_error_rsp(struct bt_att_chan *chan, uint8_t opcode,
 						uint16_t handle, int error);
 
 unsigned int bt_att_register(struct bt_att *att, uint8_t opcode,
diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c
index 3ce126485..2c5fe14dc 100644
--- a/src/shared/gatt-client.c
+++ b/src/shared/gatt-client.c
@@ -60,6 +60,7 @@ struct ready_cb {
 struct bt_gatt_client {
 	struct bt_att *att;
 	int ref_count;
+	uint8_t features;
 
 	struct bt_gatt_client *parent;
 	struct queue *clones;
@@ -326,6 +327,7 @@ struct discovery_op {
 	struct queue *ext_prop_desc;
 	struct gatt_db_attribute *cur_svc;
 	struct gatt_db_attribute *hash;
+	uint8_t server_feat;
 	bool success;
 	uint16_t start;
 	uint16_t end;
@@ -1278,6 +1280,9 @@ static void notify_client_ready(struct bt_gatt_client *client, bool success,
 	bt_gatt_client_ref(client);
 	client->ready = success;
 
+	if (client->parent)
+		client->features = client->parent->features;
+
 	for (entry = queue_get_entries(client->ready_cbs); entry;
 							entry = entry->next) {
 		struct ready_cb *ready = entry->data;
@@ -1381,7 +1386,7 @@ static void db_hash_read_cb(bool success, uint8_t att_ecode,
 	util_hexdump(' ', value, len, client->debug_callback,
 						client->debug_data);
 
-	/* Store the new hash in the db */
+	/* Store ithe new hash in the db */
 	gatt_db_attribute_write(op->hash, 0, value, len, 0, NULL,
 					db_hash_write_value_cb, client);
 
@@ -1431,6 +1436,67 @@ static bool read_db_hash(struct discovery_op *op)
 	return true;
 }
 
+static void db_server_feat_read(bool success, uint8_t att_ecode,
+				struct bt_gatt_result *result, void *user_data)
+{
+	struct discovery_op *op = user_data;
+	struct bt_gatt_client *client = op->client;
+	const uint8_t *value;
+	uint16_t len, handle;
+	struct bt_gatt_iter iter;
+
+	if (!result)
+		return;
+
+	bt_gatt_iter_init(&iter, result);
+	bt_gatt_iter_next_read_by_type(&iter, &handle, &len, &value);
+
+	util_debug(client->debug_callback, client->debug_data,
+				"Server Features found: handle 0x%04x "
+				"length 0x%04x value 0x%02x", handle, len,
+				value[0]);
+
+	op->server_feat = value[0];
+}
+
+static void server_feat_read_value(struct gatt_db_attribute *attrib,
+						int err, const uint8_t *value,
+						size_t length, void *user_data)
+{
+	const uint8_t **feat = user_data;
+
+	if (err)
+		return;
+
+	*feat = value;
+}
+
+static void read_server_feat(struct discovery_op *op)
+{
+	struct bt_gatt_client *client = op->client;
+	struct gatt_db_attribute *attr = NULL;
+	const uint8_t *feat = NULL;
+	bt_uuid_t uuid;
+
+	bt_uuid16_create(&uuid, GATT_CHARAC_SERVER_FEAT);
+
+	gatt_db_find_by_type(client->db, 0x0001, 0xffff, &uuid,
+						get_first_attribute, &attr);
+	if (attr) {
+		/* Read stored value in the db */
+		gatt_db_attribute_read(attr, 0, BT_ATT_OP_READ_REQ, NULL,
+					server_feat_read_value, &feat);
+		if (feat)
+			return;
+	}
+
+	if (!bt_gatt_read_by_type(client->att, 0x0001, 0xffff, &uuid,
+							db_server_feat_read,
+							discovery_op_ref(op),
+							discovery_op_unref))
+		discovery_op_unref(op);
+}
+
 static void exchange_mtu_cb(bool success, uint8_t att_ecode, void *user_data)
 {
 	struct discovery_op *op = user_data;
@@ -1464,6 +1530,8 @@ static void exchange_mtu_cb(bool success, uint8_t att_ecode, void *user_data)
 					bt_att_get_mtu(client->att));
 
 discover:
+	read_server_feat(op);
+
 	if (read_db_hash(op)) {
 		op->success = false;
 		return;
@@ -1839,12 +1907,41 @@ static void service_changed_cb(uint16_t value_handle, const uint8_t *value,
 	queue_push_tail(client->svc_chngd_queue, op);
 }
 
+static void server_feat_write_value(struct gatt_db_attribute *attrib,
+						int err, void *user_data)
+{
+	struct bt_gatt_client *client = user_data;
+
+	util_debug(client->debug_callback, client->debug_data,
+			"Server Features Value set status: %d", err);
+}
+
+static void write_server_features(struct bt_gatt_client *client, uint8_t feat)
+{
+	bt_uuid_t uuid;
+	struct gatt_db_attribute *attr = NULL;
+
+	bt_uuid16_create(&uuid, GATT_CHARAC_SERVER_FEAT);
+
+	gatt_db_find_by_type(client->db, 0x0001, 0xffff, &uuid,
+						get_first_attribute, &attr);
+	if (!attr)
+		return;
+
+	/* Store value in the DB */
+	if (!gatt_db_attribute_write(attr, 0, &feat, sizeof(feat),
+					0, NULL, server_feat_write_value,
+					client))
+		util_debug(client->debug_callback, client->debug_data,
+					"Unable to store Server Features");
+}
+
 static void write_client_features(struct bt_gatt_client *client)
 {
 	bt_uuid_t uuid;
 	struct gatt_db_attribute *attr = NULL;
 	uint16_t handle;
-	uint8_t value;
+	const uint8_t *feat = NULL;
 
 	bt_uuid16_create(&uuid, GATT_CHARAC_CLI_FEAT);
 
@@ -1854,10 +1951,28 @@ static void write_client_features(struct bt_gatt_client *client)
 		return;
 
 	handle = gatt_db_attribute_get_handle(attr);
-	value = BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING;
 
-	bt_gatt_client_write_value(client, handle, &value, sizeof(value), NULL,
-								NULL, NULL);
+	client->features = BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING;
+
+	bt_uuid16_create(&uuid, GATT_CHARAC_SERVER_FEAT);
+
+	attr = NULL;
+	gatt_db_find_by_type(client->db, 0x0001, 0xffff, &uuid,
+						get_first_attribute, &attr);
+	if (attr) {
+		/* Read stored value in the db */
+		gatt_db_attribute_read(attr, 0, BT_ATT_OP_READ_REQ,
+						NULL, server_feat_read_value,
+						&feat);
+		if (feat && feat[0] & BT_GATT_CHRC_SERVER_FEAT_EATT)
+			client->features |= BT_GATT_CHRC_CLI_FEAT_EATT;
+	}
+
+	util_debug(client->debug_callback, client->debug_data,
+			"Writing Client Features 0x%02x", client->features);
+
+	bt_gatt_client_write_value(client, handle, &client->features,
+				sizeof(client->features), NULL, NULL, NULL);
 }
 
 static void init_complete(struct discovery_op *op, bool success,
@@ -1870,6 +1985,9 @@ static void init_complete(struct discovery_op *op, bool success,
 	if (!success)
 		goto fail;
 
+	if (op->server_feat)
+		write_server_features(client, op->server_feat);
+
 	write_client_features(client);
 
 	if (register_service_changed(client))
@@ -1932,6 +2050,8 @@ static bool gatt_client_init(struct bt_gatt_client *client, uint16_t mtu)
 	return true;
 
 discover:
+	read_server_feat(op);
+
 	if (read_db_hash(op)) {
 		op->success = false;
 		goto done;
@@ -2026,8 +2146,9 @@ static void notify_handler(void *data, void *user_data)
 							notify_data->user_data);
 }
 
-static void notify_cb(uint8_t opcode, const void *pdu, uint16_t length,
-								void *user_data)
+static void notify_cb(struct bt_att_chan *chan, uint8_t opcode,
+					const void *pdu, uint16_t length,
+					void *user_data)
 {
 	struct bt_gatt_client *client = user_data;
 	struct pdu_data pdu_data;
@@ -2041,7 +2162,7 @@ static void notify_cb(uint8_t opcode, const void *pdu, uint16_t length,
 	queue_foreach(client->notify_list, notify_handler, &pdu_data);
 
 	if (opcode == BT_ATT_OP_HANDLE_VAL_IND && !client->parent)
-		bt_att_send(client->att, BT_ATT_OP_HANDLE_VAL_CONF, NULL, 0,
+		bt_att_chan_send(chan, BT_ATT_OP_HANDLE_VAL_CONF, NULL, 0,
 							NULL, NULL, NULL);
 
 	bt_gatt_client_unref(client);
@@ -2099,7 +2220,8 @@ static void att_disconnect_cb(int err, void *user_data)
 }
 
 static struct bt_gatt_client *gatt_client_new(struct gatt_db *db,
-							struct bt_att *att)
+							struct bt_att *att,
+							uint8_t features)
 {
 	struct bt_gatt_client *client;
 
@@ -2129,6 +2251,7 @@ static struct bt_gatt_client *gatt_client_new(struct gatt_db *db,
 
 	client->att = bt_att_ref(att);
 	client->db = gatt_db_ref(db);
+	client->features = features;
 
 	return client;
 
@@ -2140,14 +2263,15 @@ fail:
 
 struct bt_gatt_client *bt_gatt_client_new(struct gatt_db *db,
 							struct bt_att *att,
-							uint16_t mtu)
+							uint16_t mtu,
+							uint8_t features)
 {
 	struct bt_gatt_client *client;
 
 	if (!att || !db)
 		return NULL;
 
-	client = gatt_client_new(db, att);
+	client = gatt_client_new(db, att, features);
 	if (!client)
 		return NULL;
 
@@ -2166,7 +2290,7 @@ struct bt_gatt_client *bt_gatt_client_clone(struct bt_gatt_client *client)
 	if (!client)
 		return NULL;
 
-	clone = gatt_client_new(client->db, client->att);
+	clone = gatt_client_new(client->db, client->att, client->features);
 	if (!clone)
 		return NULL;
 
@@ -2284,6 +2408,14 @@ uint16_t bt_gatt_client_get_mtu(struct bt_gatt_client *client)
 	return bt_att_get_mtu(client->att);
 }
 
+struct bt_att *bt_gatt_client_get_att(struct bt_gatt_client *client)
+{
+	if (!client)
+		return NULL;
+
+	return client->att;
+}
+
 struct gatt_db *bt_gatt_client_get_db(struct bt_gatt_client *client)
 {
 	if (!client || !client->db)
@@ -2292,6 +2424,17 @@ struct gatt_db *bt_gatt_client_get_db(struct bt_gatt_client *client)
 	return client->db;
 }
 
+uint8_t bt_gatt_client_get_features(struct bt_gatt_client *client)
+{
+	if (!client)
+		return 0;
+
+	if (client->parent)
+		return client->parent->features;
+
+	return client->features;
+}
+
 static bool match_req_id(const void *a, const void *b)
 {
 	const struct request *req = a;
diff --git a/src/shared/gatt-client.h b/src/shared/gatt-client.h
index 6d8bf8043..10900168b 100644
--- a/src/shared/gatt-client.h
+++ b/src/shared/gatt-client.h
@@ -31,7 +31,8 @@ struct bt_gatt_client;
 
 struct bt_gatt_client *bt_gatt_client_new(struct gatt_db *db,
 							struct bt_att *att,
-							uint16_t mtu);
+							uint16_t mtu,
+							uint8_t features);
 struct bt_gatt_client *bt_gatt_client_clone(struct bt_gatt_client *client);
 
 struct bt_gatt_client *bt_gatt_client_ref(struct bt_gatt_client *client);
@@ -73,7 +74,9 @@ bool bt_gatt_client_set_debug(struct bt_gatt_client *client,
 					bt_gatt_client_destroy_func_t destroy);
 
 uint16_t bt_gatt_client_get_mtu(struct bt_gatt_client *client);
+struct bt_att *bt_gatt_client_get_att(struct bt_gatt_client *client);
 struct gatt_db *bt_gatt_client_get_db(struct bt_gatt_client *client);
+uint8_t bt_gatt_client_get_features(struct bt_gatt_client *client);
 
 bool bt_gatt_client_cancel(struct bt_gatt_client *client, unsigned int id);
 bool bt_gatt_client_cancel_all(struct bt_gatt_client *client);
diff --git a/src/shared/gatt-server.c b/src/shared/gatt-server.c
index 0d9bb0762..ee0058486 100644
--- a/src/shared/gatt-server.c
+++ b/src/shared/gatt-server.c
@@ -52,6 +52,7 @@
 #define DEFAULT_MAX_PREP_QUEUE_LEN 30
 
 struct async_read_op {
+	struct bt_att_chan *chan;
 	struct bt_gatt_server *server;
 	uint8_t opcode;
 	bool done;
@@ -62,6 +63,7 @@ struct async_read_op {
 };
 
 struct async_write_op {
+	struct bt_att_chan *chan;
 	struct bt_gatt_server *server;
 	uint8_t opcode;
 };
@@ -239,8 +241,9 @@ static bool encode_read_by_grp_type_rsp(struct gatt_db *db, struct queue *q,
 	return true;
 }
 
-static void read_by_grp_type_cb(uint8_t opcode, const void *pdu,
-					uint16_t length, void *user_data)
+static void read_by_grp_type_cb(struct bt_att_chan *chan, uint8_t opcode,
+					const void *pdu, uint16_t length,
+					void *user_data)
 {
 	struct bt_gatt_server *server = user_data;
 	uint16_t start, end;
@@ -308,15 +311,14 @@ static void read_by_grp_type_cb(uint8_t opcode, const void *pdu,
 
 	queue_destroy(q, NULL);
 
-	bt_att_send(server->att, BT_ATT_OP_READ_BY_GRP_TYPE_RSP,
-							rsp_pdu, rsp_len,
-							NULL, NULL, NULL);
+	bt_att_chan_send_rsp(chan, BT_ATT_OP_READ_BY_GRP_TYPE_RSP,
+						rsp_pdu, rsp_len);
 
 	return;
 
 error:
 	queue_destroy(q, NULL);
-	bt_att_send_error_rsp(server->att, opcode, ehandle, ecode);
+	bt_att_chan_send_error_rsp(chan, opcode, ehandle, ecode);
 }
 
 static void async_read_op_destroy(struct async_read_op *op)
@@ -350,7 +352,7 @@ static void read_by_type_read_complete_cb(struct gatt_db_attribute *attr,
 
 	/* Terminate the operation if there was an error */
 	if (err) {
-		bt_att_send_error_rsp(server->att, BT_ATT_OP_READ_BY_TYPE_REQ,
+		bt_att_chan_send_error_rsp(op->chan, BT_ATT_OP_READ_BY_TYPE_REQ,
 								handle, err);
 		async_read_op_destroy(op);
 		return;
@@ -451,10 +453,8 @@ static void process_read_by_type(struct async_read_op *op)
 	attr = queue_pop_head(op->db_data);
 
 	if (op->done || !attr) {
-		bt_att_send(server->att, BT_ATT_OP_READ_BY_TYPE_RSP, op->pdu,
-								op->pdu_len,
-								NULL, NULL,
-								NULL);
+		bt_att_chan_send_rsp(op->chan, BT_ATT_OP_READ_BY_TYPE_RSP,
+						op->pdu, op->pdu_len);
 		async_read_op_destroy(op);
 		return;
 	}
@@ -472,13 +472,14 @@ static void process_read_by_type(struct async_read_op *op)
 	ecode = BT_ATT_ERROR_UNLIKELY;
 
 error:
-	bt_att_send_error_rsp(server->att, BT_ATT_OP_READ_BY_TYPE_REQ,
+	bt_att_chan_send_error_rsp(op->chan, BT_ATT_OP_READ_BY_TYPE_REQ,
 				gatt_db_attribute_get_handle(attr), ecode);
 	async_read_op_destroy(op);
 }
 
-static void read_by_type_cb(uint8_t opcode, const void *pdu,
-					uint16_t length, void *user_data)
+static void read_by_type_cb(struct bt_att_chan *chan, uint8_t opcode,
+					const void *pdu, uint16_t length,
+					void *user_data)
 {
 	struct bt_gatt_server *server = user_data;
 	uint16_t start, end;
@@ -535,6 +536,7 @@ static void read_by_type_cb(uint8_t opcode, const void *pdu,
 		goto error;
 	}
 
+	op->chan = chan;
 	op->opcode = opcode;
 	op->server = server;
 	op->db_data = q;
@@ -545,7 +547,7 @@ static void read_by_type_cb(uint8_t opcode, const void *pdu,
 	return;
 
 error:
-	bt_att_send_error_rsp(server->att, opcode, ehandle, ecode);
+	bt_att_chan_send_error_rsp(chan, opcode, ehandle, ecode);
 	queue_destroy(q, NULL);
 }
 
@@ -603,8 +605,9 @@ static bool encode_find_info_rsp(struct gatt_db *db, struct queue *q,
 	return true;
 }
 
-static void find_info_cb(uint8_t opcode, const void *pdu,
-					uint16_t length, void *user_data)
+static void find_info_cb(struct bt_att_chan *chan, uint8_t opcode,
+					const void *pdu, uint16_t length,
+					void *user_data)
 {
 	struct bt_gatt_server *server = user_data;
 	uint16_t start, end;
@@ -653,14 +656,14 @@ static void find_info_cb(uint8_t opcode, const void *pdu,
 		goto error;
 	}
 
-	bt_att_send(server->att, BT_ATT_OP_FIND_INFO_RSP, rsp_pdu, rsp_len,
-							NULL, NULL, NULL);
+	bt_att_chan_send_rsp(chan, BT_ATT_OP_FIND_INFO_RSP, rsp_pdu, rsp_len);
+
 	queue_destroy(q, NULL);
 
 	return;
 
 error:
-	bt_att_send_error_rsp(server->att, opcode, ehandle, ecode);
+	bt_att_chan_send_error_rsp(chan, opcode, ehandle, ecode);
 	queue_destroy(q, NULL);
 
 }
@@ -702,8 +705,9 @@ static void find_by_type_val_att_cb(struct gatt_db_attribute *attrib,
 	data->len += 4;
 }
 
-static void find_by_type_val_cb(uint8_t opcode, const void *pdu,
-					uint16_t length, void *user_data)
+static void find_by_type_val_cb(struct bt_att_chan *chan, uint8_t opcode,
+					const void *pdu, uint16_t length,
+					void *user_data)
 {
 	struct bt_gatt_server *server = user_data;
 	uint16_t start, end, uuid16;
@@ -748,13 +752,13 @@ static void find_by_type_val_cb(uint8_t opcode, const void *pdu,
 	if (data.ecode)
 		goto error;
 
-	bt_att_send(server->att, BT_ATT_OP_FIND_BY_TYPE_RSP, data.pdu,
-						data.len, NULL, NULL, NULL);
+	bt_att_chan_send_rsp(chan, BT_ATT_OP_FIND_BY_TYPE_RSP,
+					data.pdu, data.len);
 
 	return;
 
 error:
-	bt_att_send_error_rsp(server->att, opcode, ehandle, data.ecode);
+	bt_att_chan_send_error_rsp(chan, opcode, ehandle, data.ecode);
 }
 
 static void async_write_op_destroy(struct async_write_op *op)
@@ -772,6 +776,9 @@ static void write_complete_cb(struct gatt_db_attribute *attr, int err,
 	struct bt_gatt_server *server = op->server;
 	uint16_t handle;
 
+	util_debug(server->debug_callback, server->debug_data,
+				"Write Complete: err %d", err);
+
 	if (!server || op->opcode == BT_ATT_OP_WRITE_CMD) {
 		async_write_op_destroy(op);
 		return;
@@ -780,10 +787,9 @@ static void write_complete_cb(struct gatt_db_attribute *attr, int err,
 	handle = gatt_db_attribute_get_handle(attr);
 
 	if (err)
-		bt_att_send_error_rsp(server->att, op->opcode, handle, err);
+		bt_att_chan_send_error_rsp(op->chan, op->opcode, handle, err);
 	else
-		bt_att_send(server->att, BT_ATT_OP_WRITE_RSP, NULL, 0,
-							NULL, NULL, NULL);
+		bt_att_chan_send_rsp(op->chan, BT_ATT_OP_WRITE_RSP, NULL, 0);
 
 	async_write_op_destroy(op);
 }
@@ -798,7 +804,7 @@ static uint8_t authorize_req(struct bt_gatt_server *server,
 						server->authorize_data);
 }
 
-static void write_cb(uint8_t opcode, const void *pdu,
+static void write_cb(struct bt_att_chan *chan, uint8_t opcode, const void *pdu,
 					uint16_t length, void *user_data)
 {
 	struct bt_gatt_server *server = user_data;
@@ -840,6 +846,7 @@ static void write_cb(uint8_t opcode, const void *pdu,
 	}
 
 	op = new0(struct async_write_op, 1);
+	op->chan = chan;
 	op->server = server;
 	op->opcode = opcode;
 	server->pending_write_op = op;
@@ -857,7 +864,7 @@ error:
 	if (opcode == BT_ATT_OP_WRITE_CMD)
 		return;
 
-	bt_att_send_error_rsp(server->att, opcode, handle, ecode);
+	bt_att_chan_send_error_rsp(chan, opcode, handle, ecode);
 }
 
 static uint8_t get_read_rsp_opcode(uint8_t opcode)
@@ -893,6 +900,9 @@ static void read_complete_cb(struct gatt_db_attribute *attr, int err,
 	uint16_t mtu;
 	uint16_t handle;
 
+	util_debug(server->debug_callback, server->debug_data,
+				"Read Complete: err %d", err);
+
 	if (!server) {
 		async_read_op_destroy(op);
 		return;
@@ -902,22 +912,21 @@ static void read_complete_cb(struct gatt_db_attribute *attr, int err,
 	handle = gatt_db_attribute_get_handle(attr);
 
 	if (err) {
-		bt_att_send_error_rsp(server->att, op->opcode, handle, err);
+		bt_att_chan_send_error_rsp(op->chan, op->opcode, handle, err);
 		async_read_op_destroy(op);
 		return;
 	}
 
 	rsp_opcode = get_read_rsp_opcode(op->opcode);
 
-	bt_att_send(server->att, rsp_opcode, len ? value : NULL,
-						MIN((unsigned) mtu - 1, len),
-						NULL, NULL, NULL);
+	bt_att_chan_send_rsp(op->chan, rsp_opcode, len ? value : NULL,
+					MIN((unsigned int) mtu - 1, len));
 	async_read_op_destroy(op);
 }
 
-static void handle_read_req(struct bt_gatt_server *server, uint8_t opcode,
-								uint16_t handle,
-								uint16_t offset)
+static void handle_read_req(struct bt_att_chan *chan,
+				struct bt_gatt_server *server, uint8_t opcode,
+				uint16_t handle, uint16_t offset)
 {
 	struct gatt_db_attribute *attr;
 	uint8_t ecode;
@@ -950,6 +959,7 @@ static void handle_read_req(struct bt_gatt_server *server, uint8_t opcode,
 	}
 
 	op = new0(struct async_read_op, 1);
+	op->chan = chan;
 	op->opcode = opcode;
 	op->server = server;
 	server->pending_read_op = op;
@@ -964,34 +974,35 @@ error:
 	if (op)
 		async_read_op_destroy(op);
 
-	bt_att_send_error_rsp(server->att, opcode, handle, ecode);
+	bt_att_chan_send_error_rsp(chan, opcode, handle, ecode);
 }
 
-static void read_cb(uint8_t opcode, const void *pdu,
+static void read_cb(struct bt_att_chan *chan, uint8_t opcode, const void *pdu,
 					uint16_t length, void *user_data)
 {
 	struct bt_gatt_server *server = user_data;
 	uint16_t handle;
 
 	if (length != 2) {
-		bt_att_send_error_rsp(server->att, opcode, 0,
+		bt_att_chan_send_error_rsp(chan, opcode, 0,
 						BT_ATT_ERROR_INVALID_PDU);
 		return;
 	}
 
 	handle = get_le16(pdu);
 
-	handle_read_req(server, opcode, handle, 0);
+	handle_read_req(chan, server, opcode, handle, 0);
 }
 
-static void read_blob_cb(uint8_t opcode, const void *pdu,
-					uint16_t length, void *user_data)
+static void read_blob_cb(struct bt_att_chan *chan, uint8_t opcode,
+					const void *pdu, uint16_t length,
+					void *user_data)
 {
 	struct bt_gatt_server *server = user_data;
 	uint16_t handle, offset;
 
 	if (length != 4) {
-		bt_att_send_error_rsp(server->att, opcode, 0,
+		bt_att_chan_send_error_rsp(chan, opcode, 0,
 						BT_ATT_ERROR_INVALID_PDU);
 		return;
 	}
@@ -999,10 +1010,11 @@ static void read_blob_cb(uint8_t opcode, const void *pdu,
 	handle = get_le16(pdu);
 	offset = get_le16(pdu + 2);
 
-	handle_read_req(server, opcode, handle, offset);
+	handle_read_req(chan, server, opcode, handle, offset);
 }
 
 struct read_multiple_resp_data {
+	struct bt_att_chan *chan;
 	struct bt_gatt_server *server;
 	uint16_t *handles;
 	size_t cur_handle;
@@ -1029,7 +1041,7 @@ static void read_multiple_complete_cb(struct gatt_db_attribute *attr, int err,
 	uint8_t ecode;
 
 	if (err != 0) {
-		bt_att_send_error_rsp(data->server->att,
+		bt_att_chan_send_error_rsp(data->chan,
 					BT_ATT_OP_READ_MULT_REQ, handle, err);
 		read_multiple_resp_data_free(data);
 		return;
@@ -1039,7 +1051,7 @@ static void read_multiple_complete_cb(struct gatt_db_attribute *attr, int err,
 						BT_ATT_PERM_READ_AUTHEN |
 						BT_ATT_PERM_READ_ENCRYPT);
 	if (ecode) {
-		bt_att_send_error_rsp(data->server->att,
+		bt_att_chan_send_error_rsp(data->chan,
 					BT_ATT_OP_READ_MULT_REQ, handle, ecode);
 		read_multiple_resp_data_free(data);
 		return;
@@ -1054,8 +1066,8 @@ static void read_multiple_complete_cb(struct gatt_db_attribute *attr, int err,
 
 	if ((data->length >= data->mtu - 1) ||
 				(data->cur_handle == data->num_handles)) {
-		bt_att_send(data->server->att, BT_ATT_OP_READ_MULT_RSP,
-				data->rsp_data, data->length, NULL, NULL, NULL);
+		bt_att_chan_send_rsp(data->chan, BT_ATT_OP_READ_MULT_RSP,
+						data->rsp_data, data->length);
 		read_multiple_resp_data_free(data);
 		return;
 	}
@@ -1069,7 +1081,7 @@ static void read_multiple_complete_cb(struct gatt_db_attribute *attr, int err,
 					data->handles[data->cur_handle]);
 
 	if (!next_attr) {
-		bt_att_send_error_rsp(data->server->att,
+		bt_att_chan_send_error_rsp(data->chan,
 					BT_ATT_OP_READ_MULT_REQ,
 					data->handles[data->cur_handle],
 					BT_ATT_ERROR_INVALID_HANDLE);
@@ -1080,7 +1092,7 @@ static void read_multiple_complete_cb(struct gatt_db_attribute *attr, int err,
 	if (!gatt_db_attribute_read(next_attr, 0, BT_ATT_OP_READ_MULT_REQ,
 					data->server->att,
 					read_multiple_complete_cb, data)) {
-		bt_att_send_error_rsp(data->server->att,
+		bt_att_chan_send_error_rsp(data->chan,
 						BT_ATT_OP_READ_MULT_REQ,
 						data->handles[data->cur_handle],
 						BT_ATT_ERROR_UNLIKELY);
@@ -1088,8 +1100,9 @@ static void read_multiple_complete_cb(struct gatt_db_attribute *attr, int err,
 	}
 }
 
-static void read_multiple_cb(uint8_t opcode, const void *pdu,
-					uint16_t length, void *user_data)
+static void read_multiple_cb(struct bt_att_chan *chan, uint8_t opcode,
+					const void *pdu, uint16_t length,
+					void *user_data)
 {
 	struct bt_gatt_server *server = user_data;
 	struct gatt_db_attribute *attr;
@@ -1103,6 +1116,7 @@ static void read_multiple_cb(uint8_t opcode, const void *pdu,
 	}
 
 	data = new0(struct read_multiple_resp_data, 1);
+	data->chan = chan;
 	data->handles = NULL;
 	data->rsp_data = NULL;
 	data->server = server;
@@ -1139,7 +1153,7 @@ error:
 	if (data)
 		read_multiple_resp_data_free(data);
 
-	bt_att_send_error_rsp(server->att, opcode, 0, ecode);
+	bt_att_chan_send_error_rsp(chan, opcode, 0, ecode);
 }
 
 static bool append_prep_data(struct prep_write_data *prep_data, uint16_t handle,
@@ -1230,6 +1244,7 @@ static bool store_prep_data(struct bt_gatt_server *server,
 }
 
 struct prep_write_complete_data {
+	struct bt_att_chan *chan;
 	void *pdu;
 	uint16_t length;
 	struct bt_gatt_server *server;
@@ -1245,8 +1260,8 @@ static void prep_write_complete_cb(struct gatt_db_attribute *attr, int err,
 	handle = get_le16(pwcd->pdu);
 
 	if (err) {
-		bt_att_send_error_rsp(pwcd->server->att,
-					BT_ATT_OP_PREP_WRITE_REQ, handle, err);
+		bt_att_chan_send_error_rsp(pwcd->chan, BT_ATT_OP_PREP_WRITE_REQ,
+								handle, err);
 		free(pwcd->pdu);
 		free(pwcd);
 
@@ -1257,19 +1272,20 @@ static void prep_write_complete_cb(struct gatt_db_attribute *attr, int err,
 
 	if (!store_prep_data(pwcd->server, handle, offset, pwcd->length - 4,
 						&((uint8_t *) pwcd->pdu)[4]))
-		bt_att_send_error_rsp(pwcd->server->att,
-					BT_ATT_OP_PREP_WRITE_RSP, handle,
+		bt_att_chan_send_error_rsp(pwcd->chan, BT_ATT_OP_PREP_WRITE_RSP,
+					handle,
 					BT_ATT_ERROR_INSUFFICIENT_RESOURCES);
 
-	bt_att_send(pwcd->server->att, BT_ATT_OP_PREP_WRITE_RSP, pwcd->pdu,
-						pwcd->length, NULL, NULL, NULL);
+	bt_att_chan_send_rsp(pwcd->chan, BT_ATT_OP_PREP_WRITE_RSP, pwcd->pdu,
+								pwcd->length);
 
 	free(pwcd->pdu);
 	free(pwcd);
 }
 
-static void prep_write_cb(uint8_t opcode, const void *pdu,
-					uint16_t length, void *user_data)
+static void prep_write_cb(struct bt_att_chan *chan, uint8_t opcode,
+					const void *pdu, uint16_t length,
+					void *user_data)
 {
 	struct bt_gatt_server *server = user_data;
 	uint16_t handle = 0;
@@ -1307,6 +1323,7 @@ static void prep_write_cb(uint8_t opcode, const void *pdu,
 		goto error;
 
 	pwcd = new0(struct prep_write_complete_data, 1);
+	pwcd->chan = chan;
 	pwcd->pdu = malloc(length);
 	memcpy(pwcd->pdu, pdu, length);
 	pwcd->length = length;
@@ -1323,23 +1340,28 @@ static void prep_write_cb(uint8_t opcode, const void *pdu,
 	ecode = BT_ATT_ERROR_UNLIKELY;
 
 error:
-	bt_att_send_error_rsp(server->att, opcode, handle, ecode);
+	bt_att_chan_send_error_rsp(chan, opcode, handle, ecode);
 }
 
-static void exec_next_prep_write(struct bt_gatt_server *server,
-						uint16_t ehandle, int err);
+struct exec_data {
+	struct bt_att_chan *chan;
+	struct bt_gatt_server *server;
+};
+
+static void exec_next_prep_write(struct exec_data *data, uint16_t ehandle,
+								int err);
 
 static void exec_write_complete_cb(struct gatt_db_attribute *attr, int err,
 								void *user_data)
 {
-	struct bt_gatt_server *server = user_data;
+	struct exec_data *data = user_data;
 	uint16_t handle = gatt_db_attribute_get_handle(attr);
 
-	exec_next_prep_write(server, handle, err);
+	exec_next_prep_write(data, handle, err);
 }
 
-static void exec_next_prep_write(struct bt_gatt_server *server,
-						uint16_t ehandle, int err)
+static void exec_next_prep_write(struct exec_data *data, uint16_t ehandle,
+								int err)
 {
 	struct prep_write_data *next = NULL;
 	struct gatt_db_attribute *attr;
@@ -1348,14 +1370,15 @@ static void exec_next_prep_write(struct bt_gatt_server *server,
 	if (err)
 		goto error;
 
-	next = queue_pop_head(server->prep_queue);
+	next = queue_pop_head(data->server->prep_queue);
 	if (!next) {
-		bt_att_send(server->att, BT_ATT_OP_EXEC_WRITE_RSP, NULL, 0,
-							NULL, NULL, NULL);
+		bt_att_chan_send_rsp(data->chan, BT_ATT_OP_EXEC_WRITE_RSP,
+								NULL, 0);
+		free(data);
 		return;
 	}
 
-	attr = gatt_db_get_attribute(server->db, next->handle);
+	attr = gatt_db_get_attribute(data->server->db, next->handle);
 	if (!attr) {
 		err = BT_ATT_ERROR_UNLIKELY;
 		goto error;
@@ -1364,8 +1387,8 @@ static void exec_next_prep_write(struct bt_gatt_server *server,
 	status = gatt_db_attribute_write(attr, next->offset,
 						next->value, next->length,
 						BT_ATT_OP_EXEC_WRITE_REQ,
-						server->att,
-						exec_write_complete_cb, server);
+						data->server->att,
+						exec_write_complete_cb, data);
 
 	prep_write_data_destroy(next);
 
@@ -1375,11 +1398,12 @@ static void exec_next_prep_write(struct bt_gatt_server *server,
 	err = BT_ATT_ERROR_UNLIKELY;
 
 error:
-	queue_remove_all(server->prep_queue, NULL, NULL,
+	queue_remove_all(data->server->prep_queue, NULL, NULL,
 						prep_write_data_destroy);
 
-	bt_att_send_error_rsp(server->att, BT_ATT_OP_EXEC_WRITE_REQ,
+	bt_att_chan_send_error_rsp(data->chan, BT_ATT_OP_EXEC_WRITE_REQ,
 								ehandle, err);
+	free(data);
 }
 
 static bool find_no_reliable_characteristic(const void *data,
@@ -1390,10 +1414,12 @@ static bool find_no_reliable_characteristic(const void *data,
 	return !prep_data->reliable_supported;
 }
 
-static void exec_write_cb(uint8_t opcode, const void *pdu,
-					uint16_t length, void *user_data)
+static void exec_write_cb(struct bt_att_chan *chan, uint8_t opcode,
+					const void *pdu, uint16_t length,
+					void *user_data)
 {
 	struct bt_gatt_server *server = user_data;
+	struct exec_data *data;
 	uint8_t flags;
 	uint8_t ecode;
 	bool write;
@@ -1421,8 +1447,7 @@ static void exec_write_cb(uint8_t opcode, const void *pdu,
 	if (!write) {
 		queue_remove_all(server->prep_queue, NULL, NULL,
 						prep_write_data_destroy);
-		bt_att_send(server->att, BT_ATT_OP_EXEC_WRITE_RSP, NULL, 0,
-							NULL, NULL, NULL);
+		bt_att_chan_send_rsp(chan, BT_ATT_OP_EXEC_WRITE_RSP, NULL, 0);
 		return;
 	}
 
@@ -1439,18 +1464,23 @@ static void exec_write_cb(uint8_t opcode, const void *pdu,
 		}
 	}
 
-	exec_next_prep_write(server, 0, 0);
+	data = new0(struct exec_data, 1);
+	data->chan = chan;
+	data->server = server;
+
+	exec_next_prep_write(data, 0, 0);
 
 	return;
 
 error:
 	queue_remove_all(server->prep_queue, NULL, NULL,
 						prep_write_data_destroy);
-	bt_att_send_error_rsp(server->att, opcode, ehandle, ecode);
+	bt_att_chan_send_error_rsp(chan, opcode, ehandle, ecode);
 }
 
-static void exchange_mtu_cb(uint8_t opcode, const void *pdu,
-					uint16_t length, void *user_data)
+static void exchange_mtu_cb(struct bt_att_chan *chan, uint8_t opcode,
+				const void *pdu, uint16_t length,
+				void *user_data)
 {
 	struct bt_gatt_server *server = user_data;
 	uint16_t client_rx_mtu;
@@ -1458,7 +1488,7 @@ static void exchange_mtu_cb(uint8_t opcode, const void *pdu,
 	uint8_t rsp_pdu[2];
 
 	if (length != 2) {
-		bt_att_send_error_rsp(server->att, opcode, 0,
+		bt_att_chan_send_error_rsp(chan, opcode, 0,
 						BT_ATT_ERROR_INVALID_PDU);
 		return;
 	}
@@ -1468,8 +1498,7 @@ static void exchange_mtu_cb(uint8_t opcode, const void *pdu,
 
 	/* Respond with the server MTU */
 	put_le16(server->mtu, rsp_pdu);
-	bt_att_send(server->att, BT_ATT_OP_MTU_RSP, rsp_pdu, 2, NULL, NULL,
-									NULL);
+	bt_att_chan_send_rsp(chan, BT_ATT_OP_MTU_RSP, rsp_pdu, 2);
 
 	/* Set MTU to be the minimum */
 	server->mtu = final_mtu;
diff --git a/tools/btgatt-client.c b/tools/btgatt-client.c
index 7df659747..82a9e3fe0 100644
--- a/tools/btgatt-client.c
+++ b/tools/btgatt-client.c
@@ -218,7 +218,7 @@ static struct client *client_create(int fd, uint16_t mtu)
 		return NULL;
 	}
 
-	cli->gatt = bt_gatt_client_new(cli->db, cli->att, mtu);
+	cli->gatt = bt_gatt_client_new(cli->db, cli->att, mtu, 0);
 	if (!cli->gatt) {
 		fprintf(stderr, "Failed to create GATT client\n");
 		gatt_db_unref(cli->db);
diff --git a/unit/test-gatt.c b/unit/test-gatt.c
index e35271b61..d94993b9c 100644
--- a/unit/test-gatt.c
+++ b/unit/test-gatt.c
@@ -124,8 +124,16 @@ struct context {
 		raw_pdu(0x02, 0x00, 0x02),				\
 		raw_pdu(0x03, 0x00, 0x02)
 
-#define SERVICE_DATA_1_PDUS						\
+#define READ_SERVER_FEAT_PDUS						\
+		raw_pdu(0x08, 0x01, 0x00, 0xff, 0xff, 0x3a, 0x2b),	\
+		raw_pdu(0x01, 0x08, 0x01, 0x00, 0x0a)
+
+#define CLIENT_INIT_PDUS						\
 		MTU_EXCHANGE_CLIENT_PDUS,				\
+		READ_SERVER_FEAT_PDUS
+
+#define SERVICE_DATA_1_PDUS						\
+		CLIENT_INIT_PDUS,					\
 		raw_pdu(0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28),	\
 		raw_pdu(0x11, 0x06, 0x01, 0x00, 0x04, 0x00, 0x01, 0x18),\
 		raw_pdu(0x10, 0x05, 0x00, 0xff, 0xff, 0x00, 0x28),	\
@@ -150,7 +158,7 @@ struct context {
 		raw_pdu(0x05, 0x01, 0x08, 0x00, 0x01, 0x29)
 
 #define SERVICE_DATA_2_PDUS						\
-		MTU_EXCHANGE_CLIENT_PDUS,				\
+		CLIENT_INIT_PDUS,					\
 		raw_pdu(0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28),	\
 		raw_pdu(0x11, 0x06, 0x01, 0x00, 0x04, 0x00, 0x01, 0x18),\
 		raw_pdu(0x10, 0x05, 0x00, 0xff, 0xff, 0x00, 0x28),	\
@@ -175,7 +183,7 @@ struct context {
 		raw_pdu(0x05, 0x01, 0x0a, 0x00, 0x01, 0x29)
 
 #define SERVICE_DATA_3_PDUS						\
-		MTU_EXCHANGE_CLIENT_PDUS,				\
+		CLIENT_INIT_PDUS,					\
 		raw_pdu(0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28),	\
 		raw_pdu(0x11, 0x06, 0x00, 0x01, 0x21, 0x01, 0x00, 0x18, \
 			0x00, 0x02, 0x00, 0x02, 0x01, 0x18),		\
@@ -683,7 +691,7 @@ static struct context *create_context(uint16_t mtu, gconstpointer data)
 		g_assert(context->client_db);
 
 		context->client = bt_gatt_client_new(context->client_db,
-							context->att, mtu);
+							context->att, mtu, 0);
 		g_assert(context->client);
 
 		bt_gatt_client_set_debug(context->client, print_debug,
@@ -2371,8 +2379,7 @@ int main(int argc, char *argv[])
 	 * Discovery of Services and Service Characteristics.
 	 */
 	define_test_att("/TP/GAD/CL/BV-01-C", test_search_primary, NULL, NULL,
-			raw_pdu(0x02, 0x00, 0x02),
-			raw_pdu(0x03, 0x00, 0x02),
+			MTU_EXCHANGE_CLIENT_PDUS,
 			raw_pdu(0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28),
 			raw_pdu(0x11, 0x06, 0x10, 0x00, 0x13, 0x00, 0x00, 0x18,
 					0x20, 0x00, 0x29, 0x00, 0xb0, 0x68,
@@ -3245,7 +3252,7 @@ int main(int argc, char *argv[])
 
 	define_test_client("/TP/GAN/CL/BV-01-C", test_client, ts_small_db,
 			&test_notification_1,
-			MTU_EXCHANGE_CLIENT_PDUS,
+			CLIENT_INIT_PDUS,
 			SMALL_DB_DISCOVERY_PDUS,
 			raw_pdu(0x12, 0x04, 0x00, 0x03, 0x00),
 			raw_pdu(0x13),
@@ -3271,7 +3278,7 @@ int main(int argc, char *argv[])
 
 	define_test_client("/TP/GAI/CL/BV-01-C", test_client, ts_small_db,
 			&test_indication_1,
-			MTU_EXCHANGE_CLIENT_PDUS,
+			CLIENT_INIT_PDUS,
 			SMALL_DB_DISCOVERY_PDUS,
 			raw_pdu(0x12, 0x04, 0x00, 0x03, 0x00),
 			raw_pdu(0x13),
-- 
2.21.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 06/12] gatt: Enable EATT bearer support
  2020-02-28 23:46 [PATCH v2 00/12] Userspace Bluetooth 5.2 initial support Luiz Augusto von Dentz
                   ` (4 preceding siblings ...)
  2020-02-28 23:46 ` [PATCH v2 05/12] shared/gatt-client: Add support for EATT features Luiz Augusto von Dentz
@ 2020-02-28 23:46 ` Luiz Augusto von Dentz
  2020-02-28 23:46 ` [PATCH v2 07/12] shared/gatt-server: Add support for Read Multiple Variable Length Luiz Augusto von Dentz
                   ` (6 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2020-02-28 23:46 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

This adds support for EATT connections.
---
 src/device.c        |  11 +++++
 src/gatt-client.c   |  83 ++++++++++++++++++++++++++++++++++
 src/gatt-database.c | 107 +++++++++++++++++++++++++++++++++-----------
 3 files changed, 175 insertions(+), 26 deletions(-)

diff --git a/src/device.c b/src/device.c
index 3f4afa281..c86e9a64d 100644
--- a/src/device.c
+++ b/src/device.c
@@ -5048,6 +5048,17 @@ bool device_attach_att(struct btd_device *dev, GIOChannel *io)
 		return false;
 	}
 
+	if (dev->att) {
+		if (!bt_att_attach_fd(dev->att, g_io_channel_unix_get_fd(io))) {
+			DBG("EATT channel connected");
+			g_io_channel_set_close_on_unref(io, FALSE);
+			return true;
+		}
+
+		error("Failed to attach EATT channel");
+		return false;
+	}
+
 	if (sec_level == BT_IO_SEC_LOW && dev->le_state.paired) {
 		DBG("Elevating security level since LTK is available");
 
diff --git a/src/gatt-client.c b/src/gatt-client.c
index 6bcdecf09..aa77661ad 100644
--- a/src/gatt-client.c
+++ b/src/gatt-client.c
@@ -35,9 +35,11 @@
 #include "lib/uuid.h"
 
 #include "gdbus/gdbus.h"
+#include "btio/btio.h"
 
 #include "log.h"
 #include "error.h"
+#include "hcid.h"
 #include "adapter.h"
 #include "device.h"
 #include "src/shared/io.h"
@@ -57,8 +59,11 @@
 #define GATT_CHARACTERISTIC_IFACE	"org.bluez.GattCharacteristic1"
 #define GATT_DESCRIPTOR_IFACE		"org.bluez.GattDescriptor1"
 
+#define EATT_MAX_BEARERS 2
+
 struct btd_gatt_client {
 	struct btd_device *device;
+	uint8_t features;
 	bool ready;
 	char devaddr[18];
 	struct gatt_db *db;
@@ -2154,6 +2159,70 @@ static void register_notify(void *data, void *user_data)
 	notify_client_free(notify_client);
 }
 
+static void eatt_connect_cb(GIOChannel *io, GError *gerr, gpointer user_data)
+{
+	struct btd_gatt_client *client = user_data;
+
+	if (gerr)
+		return;
+
+	device_attach_att(client->device, io);
+}
+
+static void eatt_connect(struct btd_gatt_client *client)
+{
+	struct btd_device *dev = client->device;
+	struct btd_adapter *adapter = device_get_adapter(dev);
+	GIOChannel *io;
+	GError *gerr = NULL;
+	char addr[18];
+	int i;
+
+	ba2str(device_get_address(dev), addr);
+
+	DBG("Connection attempt to: %s", addr);
+
+	for (i = 0; i < EATT_MAX_BEARERS; i++) {
+		io = bt_io_connect(eatt_connect_cb, client, 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_get_address(dev),
+				BT_IO_OPT_DEST_TYPE,
+				device_get_le_address_type(dev),
+				BT_IO_OPT_MODE, BT_IO_MODE_EXT_FLOWCTL,
+				BT_IO_OPT_PSM, BT_ATT_EATT_PSM,
+				BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
+				BT_IO_OPT_MTU, main_opts.gatt_mtu,
+				BT_IO_OPT_INVALID);
+		if (io == NULL) {
+			/* Fallback to regular LE mode */
+			io = bt_io_connect(eatt_connect_cb, client, 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,
+					device_get_address(dev),
+					BT_IO_OPT_DEST_TYPE,
+					device_get_le_address_type(dev),
+					BT_IO_OPT_PSM, BT_ATT_EATT_PSM,
+					BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
+					BT_IO_OPT_MTU, main_opts.gatt_mtu,
+					BT_IO_OPT_INVALID);
+			if (!io) {
+				error("EATT bt_io_connect(%s): %s", addr,
+								gerr->message);
+				g_error_free(gerr);
+				return;
+			}
+		}
+
+		g_io_channel_unref(io);
+	}
+}
+
 void btd_gatt_client_ready(struct btd_gatt_client *client)
 {
 	if (!client)
@@ -2175,6 +2244,15 @@ void btd_gatt_client_ready(struct btd_gatt_client *client)
 	DBG("GATT client ready");
 
 	create_services(client);
+
+	DBG("Features 0x%02x", client->features);
+
+	if (!client->features) {
+		client->features = bt_gatt_client_get_features(client->gatt);
+		DBG("Update Features 0x%02x", client->features);
+		if (client->features & BT_GATT_CHRC_CLI_FEAT_EATT)
+			eatt_connect(client);
+	}
 }
 
 void btd_gatt_client_connected(struct btd_gatt_client *client)
@@ -2197,6 +2275,11 @@ void btd_gatt_client_connected(struct btd_gatt_client *client)
 	 * for any pre-registered notification sessions.
 	 */
 	queue_foreach(client->all_notify_clients, register_notify, client);
+
+	if (!(client->features & BT_GATT_CHRC_CLI_FEAT_EATT))
+		return;
+
+	eatt_connect(client);
 }
 
 void btd_gatt_client_service_added(struct btd_gatt_client *client,
diff --git a/src/gatt-database.c b/src/gatt-database.c
index 419e4f9e1..2445d1fa5 100644
--- a/src/gatt-database.c
+++ b/src/gatt-database.c
@@ -48,14 +48,6 @@
 #include "profile.h"
 #include "service.h"
 
-#ifndef ATT_CID
-#define ATT_CID 4
-#endif
-
-#ifndef ATT_PSM
-#define ATT_PSM 31
-#endif
-
 #define GATT_MANAGER_IFACE	"org.bluez.GattManager1"
 #define GATT_PROFILE_IFACE	"org.bluez.GattProfile1"
 #define GATT_SERVICE_IFACE	"org.bluez.GattService1"
@@ -80,7 +72,8 @@ struct btd_gatt_database {
 	struct gatt_db *db;
 	unsigned int db_id;
 	GIOChannel *le_io;
-	GIOChannel *l2cap_io;
+	GIOChannel *eatt_io;
+	GIOChannel *bredr_io;
 	struct queue *records;
 	struct queue *device_states;
 	struct queue *ccc_callbacks;
@@ -88,6 +81,7 @@ struct btd_gatt_database {
 	struct gatt_db_attribute *svc_chngd_ccc;
 	struct gatt_db_attribute *cli_feat;
 	struct gatt_db_attribute *db_hash;
+	struct gatt_db_attribute *eatt;
 	struct queue *apps;
 	struct queue *profiles;
 };
@@ -594,9 +588,14 @@ static void gatt_database_free(void *data)
 		g_io_channel_unref(database->le_io);
 	}
 
-	if (database->l2cap_io) {
-		g_io_channel_shutdown(database->l2cap_io, FALSE, NULL);
-		g_io_channel_unref(database->l2cap_io);
+	if (database->eatt_io) {
+		g_io_channel_shutdown(database->eatt_io, FALSE, NULL);
+		g_io_channel_unref(database->eatt_io);
+	}
+
+	if (database->bredr_io) {
+		g_io_channel_shutdown(database->bredr_io, FALSE, NULL);
+		g_io_channel_unref(database->bredr_io);
 	}
 
 	/* TODO: Persistently store CCC states before freeing them */
@@ -717,7 +716,7 @@ static sdp_record_t *record_new(uuid_t *uuid, uint16_t start, uint16_t end)
 	uuid_t root_uuid, proto_uuid, l2cap;
 	sdp_record_t *record;
 	sdp_data_t *psm, *sh, *eh;
-	uint16_t lp = ATT_PSM;
+	uint16_t lp = BT_ATT_PSM;
 
 	if (uuid == NULL)
 		return NULL;
@@ -1098,7 +1097,10 @@ static void cli_feat_write_cb(struct gatt_db_attribute *attrib,
 {
 	struct btd_gatt_database *database = user_data;
 	struct device_state *state;
+	uint8_t bits[] = { BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING,
+				BT_GATT_CHRC_CLI_FEAT_EATT };
 	uint8_t ecode = 0;
+	unsigned int i;
 
 	DBG("Client Features write");
 
@@ -1113,13 +1115,12 @@ static void cli_feat_write_cb(struct gatt_db_attribute *attrib,
 		goto done;
 	}
 
-	/* A client shall never clear a bit it has set.
-	 * TODO: make it generic to any bits.
-	 */
-	if (state->cli_feat[0] & BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING &&
-			!(value[0] & BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING)) {
-		ecode = BT_ATT_ERROR_VALUE_NOT_ALLOWED;
-		goto done;
+	for (i = 0; i < sizeof(bits); i++) {
+		/* A client shall never clear a bit it has set */
+		if (state->cli_feat[0] & (1 << i) && !(value[0] & (1 << i))) {
+			ecode = BT_ATT_ERROR_VALUE_NOT_ALLOWED;
+			goto done;
+		}
 	}
 
 	/* Shall we reallocate the feat array if bigger? */
@@ -1129,7 +1130,7 @@ static void cli_feat_write_cb(struct gatt_db_attribute *attrib,
 		len--;
 	}
 
-	state->cli_feat[0] &= BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING;
+	state->cli_feat[0] &= ((1 << sizeof(bits)) - 1);
 	state->change_aware = true;
 
 done:
@@ -1161,6 +1162,28 @@ static void db_hash_read_cb(struct gatt_db_attribute *attrib,
 		state->change_aware = true;
 }
 
+static void server_feat_read_cb(struct gatt_db_attribute *attrib,
+					unsigned int id, uint16_t offset,
+					uint8_t opcode, struct bt_att *att,
+					void *user_data)
+{
+	struct btd_gatt_database *database = user_data;
+	struct device_state *state;
+	uint8_t ecode = 0;
+	uint8_t value = 0;
+
+	state = get_device_state(database, att);
+	if (!state) {
+		ecode = BT_ATT_ERROR_UNLIKELY;
+		goto done;
+	}
+
+	value |= BT_GATT_CHRC_SERVER_FEAT_EATT;
+
+done:
+	gatt_db_attribute_read_result(attrib, id, ecode, &value, sizeof(value));
+}
+
 static void populate_gatt_service(struct btd_gatt_database *database)
 {
 	bt_uuid_t uuid;
@@ -1168,7 +1191,7 @@ static void populate_gatt_service(struct btd_gatt_database *database)
 
 	/* Add the GATT service */
 	bt_uuid16_create(&uuid, UUID_GATT);
-	service = gatt_db_add_service(database->db, &uuid, true, 8);
+	service = gatt_db_add_service(database->db, &uuid, true, 10);
 
 	bt_uuid16_create(&uuid, GATT_CHARAC_SERVICE_CHANGED);
 	database->svc_chngd = gatt_db_service_add_characteristic(service, &uuid,
@@ -1191,6 +1214,11 @@ static void populate_gatt_service(struct btd_gatt_database *database)
 				&uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ,
 				db_hash_read_cb, NULL, database);
 
+	bt_uuid16_create(&uuid, GATT_CHARAC_SERVER_FEAT);
+	database->eatt = gatt_db_service_add_characteristic(service,
+				&uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ,
+				server_feat_read_cb, NULL, database);
+
 	gatt_db_service_set_active(service, true);
 
 	database_add_record(database, service);
@@ -3525,7 +3553,7 @@ struct btd_gatt_database *btd_gatt_database_new(struct btd_adapter *adapter)
 					BT_IO_OPT_SOURCE_BDADDR, addr,
 					BT_IO_OPT_SOURCE_TYPE,
 					btd_adapter_get_address_type(adapter),
-					BT_IO_OPT_CID, ATT_CID,
+					BT_IO_OPT_CID, BT_ATT_CID,
 					BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
 					BT_IO_OPT_INVALID);
 	if (!database->le_io) {
@@ -3534,14 +3562,41 @@ struct btd_gatt_database *btd_gatt_database_new(struct btd_adapter *adapter)
 		goto fail;
 	}
 
+	/* EATT socket */
+	database->eatt_io = bt_io_listen(connect_cb, NULL, NULL, NULL, NULL,
+					BT_IO_OPT_SOURCE_BDADDR, addr,
+					BT_IO_OPT_SOURCE_TYPE,
+					btd_adapter_get_address_type(adapter),
+					BT_IO_OPT_MODE, BT_IO_MODE_EXT_FLOWCTL,
+					BT_IO_OPT_PSM, BT_ATT_EATT_PSM,
+					BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
+					BT_IO_OPT_MTU, main_opts.gatt_mtu,
+					BT_IO_OPT_INVALID);
+	if (!database->eatt_io) {
+		/* Fallback to regular LE mode */
+		database->eatt_io = bt_io_listen(connect_cb, NULL, NULL, NULL,
+					&gerr,
+					BT_IO_OPT_SOURCE_BDADDR, addr,
+					BT_IO_OPT_SOURCE_TYPE,
+					btd_adapter_get_address_type(adapter),
+					BT_IO_OPT_PSM, BT_ATT_EATT_PSM,
+					BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
+					BT_IO_OPT_MTU, main_opts.gatt_mtu,
+					BT_IO_OPT_INVALID);
+		if (!database->eatt_io) {
+			g_error_free(gerr);
+			goto fail;
+		}
+	}
+
 	/* BR/EDR socket */
-	database->l2cap_io = bt_io_listen(connect_cb, NULL, NULL, NULL, &gerr,
+	database->bredr_io = bt_io_listen(connect_cb, NULL, NULL, NULL, &gerr,
 					BT_IO_OPT_SOURCE_BDADDR, addr,
-					BT_IO_OPT_PSM, ATT_PSM,
+					BT_IO_OPT_PSM, BT_ATT_PSM,
 					BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
 					BT_IO_OPT_MTU, main_opts.gatt_mtu,
 					BT_IO_OPT_INVALID);
-	if (database->l2cap_io == NULL) {
+	if (database->bredr_io == NULL) {
 		error("Failed to start listening: %s", gerr->message);
 		g_error_free(gerr);
 		goto fail;
-- 
2.21.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 07/12] shared/gatt-server: Add support for Read Multiple Variable Length
  2020-02-28 23:46 [PATCH v2 00/12] Userspace Bluetooth 5.2 initial support Luiz Augusto von Dentz
                   ` (5 preceding siblings ...)
  2020-02-28 23:46 ` [PATCH v2 06/12] gatt: Enable EATT bearer support Luiz Augusto von Dentz
@ 2020-02-28 23:46 ` Luiz Augusto von Dentz
  2020-02-28 23:46 ` [PATCH v2 08/12] shared/gatt-client: " Luiz Augusto von Dentz
                   ` (5 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2020-02-28 23:46 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

The Read Multiple Variable Length Request is used to request that the
server read two or more values of a set of attributes that have a
variable or unknown value length and return their values in a
Read Multiple Variable Length Response.
---
 src/shared/att-types.h   |   2 +
 src/shared/gatt-server.c | 113 ++++++++++++++++++++++++++-------------
 2 files changed, 79 insertions(+), 36 deletions(-)

diff --git a/src/shared/att-types.h b/src/shared/att-types.h
index 7b88e7d92..cc9cc9fd6 100644
--- a/src/shared/att-types.h
+++ b/src/shared/att-types.h
@@ -75,6 +75,8 @@
 #define BT_ATT_OP_HANDLE_VAL_NOT		0x1B
 #define BT_ATT_OP_HANDLE_VAL_IND		0x1D
 #define BT_ATT_OP_HANDLE_VAL_CONF		0x1E
+#define BT_ATT_OP_READ_MULT_VL_REQ		0x20
+#define BT_ATT_OP_READ_MULT_VL_RSP		0x21
 
 /* Packed struct definitions for ATT protocol PDUs */
 /* TODO: Complete these definitions for all opcodes */
diff --git a/src/shared/gatt-server.c b/src/shared/gatt-server.c
index ee0058486..8b18cb21f 100644
--- a/src/shared/gatt-server.c
+++ b/src/shared/gatt-server.c
@@ -102,6 +102,7 @@ struct bt_gatt_server {
 	unsigned int read_id;
 	unsigned int read_blob_id;
 	unsigned int read_multiple_id;
+	unsigned int read_multiple_vl_id;
 	unsigned int prep_write_id;
 	unsigned int exec_write_id;
 
@@ -136,6 +137,7 @@ static void bt_gatt_server_free(struct bt_gatt_server *server)
 	bt_att_unregister(server->att, server->read_id);
 	bt_att_unregister(server->att, server->read_blob_id);
 	bt_att_unregister(server->att, server->read_multiple_id);
+	bt_att_unregister(server->att, server->read_multiple_vl_id);
 	bt_att_unregister(server->att, server->prep_write_id);
 	bt_att_unregister(server->att, server->exec_write_id);
 
@@ -1013,9 +1015,10 @@ static void read_blob_cb(struct bt_att_chan *chan, uint8_t opcode,
 	handle_read_req(chan, server, opcode, handle, offset);
 }
 
-struct read_multiple_resp_data {
+struct read_mult_data {
 	struct bt_att_chan *chan;
 	struct bt_gatt_server *server;
+	uint8_t opcode;
 	uint16_t *handles;
 	size_t cur_handle;
 	size_t num_handles;
@@ -1024,7 +1027,7 @@ struct read_multiple_resp_data {
 	size_t mtu;
 };
 
-static void read_multiple_resp_data_free(struct read_multiple_resp_data *data)
+static void read_mult_data_free(struct read_mult_data *data)
 {
 	free(data->handles);
 	free(data->rsp_data);
@@ -1035,15 +1038,16 @@ static void read_multiple_complete_cb(struct gatt_db_attribute *attr, int err,
 					const uint8_t *value, size_t len,
 					void *user_data)
 {
-	struct read_multiple_resp_data *data = user_data;
+	struct read_mult_data *data = user_data;
 	struct gatt_db_attribute *next_attr;
 	uint16_t handle = gatt_db_attribute_get_handle(attr);
 	uint8_t ecode;
+	uint16_t length;
 
 	if (err != 0) {
-		bt_att_chan_send_error_rsp(data->chan,
-					BT_ATT_OP_READ_MULT_REQ, handle, err);
-		read_multiple_resp_data_free(data);
+		bt_att_chan_send_error_rsp(data->chan, data->opcode, handle,
+									err);
+		read_mult_data_free(data);
 		return;
 	}
 
@@ -1051,29 +1055,45 @@ static void read_multiple_complete_cb(struct gatt_db_attribute *attr, int err,
 						BT_ATT_PERM_READ_AUTHEN |
 						BT_ATT_PERM_READ_ENCRYPT);
 	if (ecode) {
-		bt_att_chan_send_error_rsp(data->chan,
-					BT_ATT_OP_READ_MULT_REQ, handle, ecode);
-		read_multiple_resp_data_free(data);
+		bt_att_chan_send_error_rsp(data->chan, data->opcode, handle,
+									ecode);
+		read_mult_data_free(data);
 		return;
 	}
 
-	len = MIN(len, data->mtu - data->length - 1);
+	length = data->opcode == BT_ATT_OP_READ_MULT_VL_REQ ?
+			MIN(len, data->mtu - data->length - 3) :
+			MIN(len, data->mtu - data->length - 1);
 
-	memcpy(data->rsp_data + data->length, value, len);
-	data->length += len;
+	if (data->opcode == BT_ATT_OP_READ_MULT_VL_REQ) {
+		/* The Length Value Tuple List may be truncated within the first
+		 * two octets of a tuple due to the size limits of the current
+		 * ATT_MTU.
+		 */
+		put_le16(len, data->rsp_data + data->length);
+		data->length += 2;
+	}
+
+	memcpy(data->rsp_data + data->length, value, length);
+	data->length += length;
 
 	data->cur_handle++;
 
-	if ((data->length >= data->mtu - 1) ||
-				(data->cur_handle == data->num_handles)) {
-		bt_att_chan_send_rsp(data->chan, BT_ATT_OP_READ_MULT_RSP,
+	len = data->opcode == BT_ATT_OP_READ_MULT_VL_REQ ?
+		data->mtu - data->length - 3 : data->mtu - data->length - 1;
+
+	if (!len || (data->cur_handle == data->num_handles)) {
+		bt_att_chan_send_rsp(data->chan, data->opcode + 1,
 						data->rsp_data, data->length);
-		read_multiple_resp_data_free(data);
+		read_mult_data_free(data);
 		return;
 	}
 
 	util_debug(data->server->debug_callback, data->server->debug_data,
-				"Read Multiple Req - #%zu of %zu: 0x%04x",
+				"%s Req - #%zu of %zu: 0x%04x",
+				data->opcode == BT_ATT_OP_READ_MULT_REQ ?
+				"Read Multiple" :
+				"Read Multiple Variable Length",
 				data->cur_handle + 1, data->num_handles,
 				data->handles[data->cur_handle]);
 
@@ -1085,7 +1105,7 @@ static void read_multiple_complete_cb(struct gatt_db_attribute *attr, int err,
 					BT_ATT_OP_READ_MULT_REQ,
 					data->handles[data->cur_handle],
 					BT_ATT_ERROR_INVALID_HANDLE);
-		read_multiple_resp_data_free(data);
+		read_mult_data_free(data);
 		return;
 	}
 
@@ -1096,17 +1116,39 @@ static void read_multiple_complete_cb(struct gatt_db_attribute *attr, int err,
 						BT_ATT_OP_READ_MULT_REQ,
 						data->handles[data->cur_handle],
 						BT_ATT_ERROR_UNLIKELY);
-		read_multiple_resp_data_free(data);
+		read_mult_data_free(data);
 	}
 }
 
+static struct read_mult_data *read_mult_data_new(struct bt_gatt_server *server,
+						struct bt_att_chan *chan,
+						uint8_t opcode,
+						uint16_t num_handles)
+{
+	struct read_mult_data *data;
+
+	data = new0(struct read_mult_data, 1);
+	data->chan = chan;
+	data->opcode = opcode;
+	data->handles = new0(uint16_t, num_handles);
+	data->rsp_data = NULL;
+	data->server = server;
+	data->num_handles = num_handles;
+	data->cur_handle = 0;
+	data->mtu = bt_att_get_mtu(server->att);
+	data->length = 0;
+	data->rsp_data = new0(uint8_t, data->mtu - 1);
+
+	return data;
+}
+
 static void read_multiple_cb(struct bt_att_chan *chan, uint8_t opcode,
 					const void *pdu, uint16_t length,
 					void *user_data)
 {
 	struct bt_gatt_server *server = user_data;
 	struct gatt_db_attribute *attr;
-	struct read_multiple_resp_data *data = NULL;
+	struct read_mult_data *data = NULL;
 	uint8_t ecode = BT_ATT_ERROR_UNLIKELY;
 	size_t i = 0;
 
@@ -1115,27 +1157,17 @@ static void read_multiple_cb(struct bt_att_chan *chan, uint8_t opcode,
 		goto error;
 	}
 
-	data = new0(struct read_multiple_resp_data, 1);
-	data->chan = chan;
-	data->handles = NULL;
-	data->rsp_data = NULL;
-	data->server = server;
-	data->num_handles = length / 2;
-	data->cur_handle = 0;
-	data->mtu = bt_att_get_mtu(server->att);
-	data->length = 0;
-	data->rsp_data = malloc(data->mtu - 1);
-
-	if (!data->rsp_data)
+	data = read_mult_data_new(server, chan, opcode, length / 2);
+	if (!data)
 		goto error;
 
-	data->handles = new0(uint16_t, data->num_handles);
-
 	for (i = 0; i < data->num_handles; i++)
 		data->handles[i] = get_le16(pdu + i * 2);
 
 	util_debug(server->debug_callback, server->debug_data,
-			"Read Multiple Req - %zu handles, 1st: 0x%04x",
+			"%s Req - %zu handles, 1st: 0x%04x",
+			data->opcode == BT_ATT_OP_READ_MULT_REQ ?
+			"Read Multiple" : "Read Multiple Variable Length",
 			data->num_handles, data->handles[0]);
 
 	attr = gatt_db_get_attribute(server->db, data->handles[0]);
@@ -1151,7 +1183,7 @@ static void read_multiple_cb(struct bt_att_chan *chan, uint8_t opcode,
 
 error:
 	if (data)
-		read_multiple_resp_data_free(data);
+		read_mult_data_free(data);
 
 	bt_att_chan_send_error_rsp(chan, opcode, 0, ecode);
 }
@@ -1588,6 +1620,15 @@ static bool gatt_server_register_att_handlers(struct bt_gatt_server *server)
 	if (!server->read_multiple_id)
 		return false;
 
+	/* Read Multiple Variable Length Request */
+	server->read_multiple_vl_id = bt_att_register(server->att,
+						BT_ATT_OP_READ_MULT_VL_REQ,
+						read_multiple_cb,
+						server, NULL);
+
+	if (!server->read_multiple_vl_id)
+		return false;
+
 	/* Prepare Write Request */
 	server->prep_write_id = bt_att_register(server->att,
 						BT_ATT_OP_PREP_WRITE_REQ,
-- 
2.21.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 08/12] shared/gatt-client: Add support for Read Multiple Variable Length
  2020-02-28 23:46 [PATCH v2 00/12] Userspace Bluetooth 5.2 initial support Luiz Augusto von Dentz
                   ` (6 preceding siblings ...)
  2020-02-28 23:46 ` [PATCH v2 07/12] shared/gatt-server: Add support for Read Multiple Variable Length Luiz Augusto von Dentz
@ 2020-02-28 23:46 ` Luiz Augusto von Dentz
  2020-02-28 23:46 ` [PATCH v2 09/12] shared/gatt: Add support for Handle Value Multiple Notifications Luiz Augusto von Dentz
                   ` (4 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2020-02-28 23:46 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

The Read Multiple Variable Length Request is used to request that the
server read two or more values of a set of attributes that have a
variable or unknown value length and return their values in a
Read Multiple Variable Length Response.
---
 src/shared/gatt-client.c | 42 ++++++++++++++++++++++++++++++++++++----
 1 file changed, 38 insertions(+), 4 deletions(-)

diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c
index 2c5fe14dc..5b6723f97 100644
--- a/src/shared/gatt-client.c
+++ b/src/shared/gatt-client.c
@@ -2642,7 +2642,9 @@ static void read_multiple_cb(uint8_t opcode, const void *pdu, uint16_t length,
 	uint8_t att_ecode;
 	bool success;
 
-	if (opcode != BT_ATT_OP_READ_MULT_RSP || (!pdu && length)) {
+	if ((opcode != BT_ATT_OP_READ_MULT_RSP &&
+			opcode != BT_ATT_OP_READ_MULT_VL_RSP) ||
+			(!pdu && length)) {
 		success = false;
 
 		if (opcode == BT_ATT_OP_ERROR_RSP)
@@ -2657,8 +2659,36 @@ static void read_multiple_cb(uint8_t opcode, const void *pdu, uint16_t length,
 		att_ecode = 0;
 	}
 
-	if (op->callback)
+	if (!op->callback)
+		return;
+
+	if (opcode == BT_ATT_OP_READ_MULT_RSP || att_ecode) {
 		op->callback(success, att_ecode, pdu, length, op->user_data);
+		return;
+	}
+
+	if (length < 2) {
+		op->callback(success, att_ecode, pdu, length, op->user_data);
+		return;
+	}
+
+	/* Parse response */
+	while (length >= 2) {
+		uint16_t len;
+
+		len = get_le16(pdu);
+		length -= 2;
+		pdu += 2;
+
+		/* The Length Value Tuple List may be truncated within the
+		 * first two octets of a tuple due to the size limits of the
+		 * current ATT_MTU.
+		 */
+		if (len > length)
+			length = len;
+
+		op->callback(success, att_ecode, pdu, len, op->user_data);
+	}
 }
 
 unsigned int bt_gatt_client_read_multiple(struct bt_gatt_client *client,
@@ -2670,6 +2700,7 @@ unsigned int bt_gatt_client_read_multiple(struct bt_gatt_client *client,
 	uint8_t pdu[num_handles * 2];
 	struct request *req;
 	struct read_op *op;
+	uint8_t opcode;
 	int i;
 
 	if (!client)
@@ -2699,8 +2730,11 @@ unsigned int bt_gatt_client_read_multiple(struct bt_gatt_client *client,
 	for (i = 0; i < num_handles; i++)
 		put_le16(handles[i], pdu + (2 * i));
 
-	req->att_id = bt_att_send(client->att, BT_ATT_OP_READ_MULT_REQ,
-							pdu, sizeof(pdu),
+	opcode = bt_gatt_client_get_features(client) &
+		BT_GATT_CHRC_CLI_FEAT_EATT ? BT_ATT_OP_READ_MULT_VL_REQ :
+		BT_ATT_OP_READ_MULT_REQ;
+
+	req->att_id = bt_att_send(client->att, opcode, pdu, sizeof(pdu),
 							read_multiple_cb, req,
 							request_unref);
 	if (!req->att_id) {
-- 
2.21.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 09/12] shared/gatt: Add support for Handle Value Multiple Notifications
  2020-02-28 23:46 [PATCH v2 00/12] Userspace Bluetooth 5.2 initial support Luiz Augusto von Dentz
                   ` (7 preceding siblings ...)
  2020-02-28 23:46 ` [PATCH v2 08/12] shared/gatt-client: " Luiz Augusto von Dentz
@ 2020-02-28 23:46 ` Luiz Augusto von Dentz
  2020-02-28 23:46 ` [PATCH v2 10/12] gatt: Add support for Notify Multiple Luiz Augusto von Dentz
                   ` (3 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2020-02-28 23:46 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

Handle Value Multiple Notification can be used to notify multiple
values at once.
---
 src/shared/att-types.h   |  7 ++--
 src/shared/att.c         | 16 ++++-----
 src/shared/gatt-client.c | 76 ++++++++++++++++++++++++++--------------
 src/shared/gatt-server.c |  4 +--
 4 files changed, 64 insertions(+), 39 deletions(-)

diff --git a/src/shared/att-types.h b/src/shared/att-types.h
index cc9cc9fd6..99b108990 100644
--- a/src/shared/att-types.h
+++ b/src/shared/att-types.h
@@ -72,11 +72,12 @@
 #define BT_ATT_OP_PREP_WRITE_RSP		0x17
 #define BT_ATT_OP_EXEC_WRITE_REQ		0x18
 #define BT_ATT_OP_EXEC_WRITE_RSP		0x19
-#define BT_ATT_OP_HANDLE_VAL_NOT		0x1B
-#define BT_ATT_OP_HANDLE_VAL_IND		0x1D
-#define BT_ATT_OP_HANDLE_VAL_CONF		0x1E
+#define BT_ATT_OP_HANDLE_NFY			0x1B
+#define BT_ATT_OP_HANDLE_IND			0x1D
+#define BT_ATT_OP_HANDLE_CONF			0x1E
 #define BT_ATT_OP_READ_MULT_VL_REQ		0x20
 #define BT_ATT_OP_READ_MULT_VL_RSP		0x21
+#define BT_ATT_OP_HANDLE_NFY_MULT		0x23
 
 /* Packed struct definitions for ATT protocol PDUs */
 /* TODO: Complete these definitions for all opcodes */
diff --git a/src/shared/att.c b/src/shared/att.c
index 56ea40c46..6fa16e422 100644
--- a/src/shared/att.c
+++ b/src/shared/att.c
@@ -110,7 +110,7 @@ enum att_op_type {
 	ATT_OP_TYPE_RSP,
 	ATT_OP_TYPE_CMD,
 	ATT_OP_TYPE_IND,
-	ATT_OP_TYPE_NOT,
+	ATT_OP_TYPE_NFY,
 	ATT_OP_TYPE_CONF,
 	ATT_OP_TYPE_UNKNOWN,
 };
@@ -144,9 +144,9 @@ static const struct {
 	{ BT_ATT_OP_PREP_WRITE_RSP,		ATT_OP_TYPE_RSP },
 	{ BT_ATT_OP_EXEC_WRITE_REQ,		ATT_OP_TYPE_REQ },
 	{ BT_ATT_OP_EXEC_WRITE_RSP,		ATT_OP_TYPE_RSP },
-	{ BT_ATT_OP_HANDLE_VAL_NOT,		ATT_OP_TYPE_NOT },
-	{ BT_ATT_OP_HANDLE_VAL_IND,		ATT_OP_TYPE_IND },
-	{ BT_ATT_OP_HANDLE_VAL_CONF,		ATT_OP_TYPE_CONF },
+	{ BT_ATT_OP_HANDLE_NFY,			ATT_OP_TYPE_NFY },
+	{ BT_ATT_OP_HANDLE_IND,			ATT_OP_TYPE_IND },
+	{ BT_ATT_OP_HANDLE_CONF,		ATT_OP_TYPE_CONF },
 	{ }
 };
 
@@ -530,7 +530,7 @@ static bool can_write_data(struct io *io, void *user_data)
 		chan->in_req = false;
 		/* fall through */
 	case ATT_OP_TYPE_CMD:
-	case ATT_OP_TYPE_NOT:
+	case ATT_OP_TYPE_NFY:
 	case ATT_OP_TYPE_CONF:
 	case ATT_OP_TYPE_UNKNOWN:
 	default:
@@ -842,7 +842,7 @@ static void handle_conf(struct bt_att_chan *chan, uint8_t *pdu, ssize_t pdu_len)
 	}
 
 	if (op->callback)
-		op->callback(BT_ATT_OP_HANDLE_VAL_CONF, NULL, 0, op->user_data);
+		op->callback(BT_ATT_OP_HANDLE_NFY, NULL, 0, op->user_data);
 
 	destroy_att_send_op(op);
 	chan->pending_ind = NULL;
@@ -1042,7 +1042,7 @@ static bool can_read_data(struct io *io, void *user_data)
 		chan->in_req = true;
 		/* fall through */
 	case ATT_OP_TYPE_CMD:
-	case ATT_OP_TYPE_NOT:
+	case ATT_OP_TYPE_NFY:
 	case ATT_OP_TYPE_UNKNOWN:
 	case ATT_OP_TYPE_IND:
 		/* fall through */
@@ -1492,7 +1492,7 @@ unsigned int bt_att_send(struct bt_att *att, uint8_t opcode,
 		result = queue_push_tail(att->ind_queue, op);
 		break;
 	case ATT_OP_TYPE_CMD:
-	case ATT_OP_TYPE_NOT:
+	case ATT_OP_TYPE_NFY:
 	case ATT_OP_TYPE_UNKNOWN:
 	case ATT_OP_TYPE_RSP:
 	case ATT_OP_TYPE_CONF:
diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c
index 5b6723f97..7381f96e0 100644
--- a/src/shared/gatt-client.c
+++ b/src/shared/gatt-client.c
@@ -95,7 +95,7 @@ struct bt_gatt_client {
 	struct queue *notify_list;
 	struct queue *notify_chrcs;
 	int next_reg_id;
-	unsigned int disc_id, notify_id, ind_id;
+	unsigned int disc_id, nfy_id, nfy_mult_id, ind_id;
 
 	/*
 	 * Handles of the GATT Service and the Service Changed characteristic
@@ -2072,9 +2072,10 @@ done:
 	return true;
 }
 
-struct pdu_data {
-	const void *pdu;
-	uint16_t length;
+struct value_data {
+	uint16_t handle;
+	uint16_t len;
+	const void *data;
 };
 
 static void disable_ccc_callback(uint8_t opcode, const void *pdu,
@@ -2125,25 +2126,18 @@ done:
 static void notify_handler(void *data, void *user_data)
 {
 	struct notify_data *notify_data = data;
-	struct pdu_data *pdu_data = user_data;
-	uint16_t value_handle;
-	const uint8_t *value = NULL;
-
-	value_handle = get_le16(pdu_data->pdu);
+	struct value_data *value_data = user_data;
 
-	if (notify_data->chrc->value_handle != value_handle)
+	if (notify_data->chrc->value_handle != value_data->handle)
 		return;
 
-	if (pdu_data->length > 2)
-		value = pdu_data->pdu + 2;
-
 	/*
 	 * Even if the notify data has a pending ATT request to write to the
 	 * CCC, there is really no reason not to notify the handlers.
 	 */
 	if (notify_data->notify)
-		notify_data->notify(value_handle, value, pdu_data->length - 2,
-							notify_data->user_data);
+		notify_data->notify(value_data->handle, value_data->data,
+				value_data->len, notify_data->user_data);
 }
 
 static void notify_cb(struct bt_att_chan *chan, uint8_t opcode,
@@ -2151,18 +2145,42 @@ static void notify_cb(struct bt_att_chan *chan, uint8_t opcode,
 					void *user_data)
 {
 	struct bt_gatt_client *client = user_data;
-	struct pdu_data pdu_data;
+	struct value_data data;
 
 	bt_gatt_client_ref(client);
 
-	memset(&pdu_data, 0, sizeof(pdu_data));
-	pdu_data.pdu = pdu;
-	pdu_data.length = length;
+	memset(&data, 0, sizeof(data));
+
+	if (opcode == BT_ATT_OP_HANDLE_NFY_MULT) {
+		while (length >= 4) {
+			data.handle = get_le16(pdu);
+			length -= 2;
+			pdu += 2;
+
+			data.len = get_le16(pdu);
+			length -= 2;
+			pdu += 2;
+
+			data.data = pdu;
+
+			queue_foreach(client->notify_list, notify_handler,
+								&data);
 
-	queue_foreach(client->notify_list, notify_handler, &pdu_data);
+			length -= data.len;
+		}
+	} else {
+		data.handle = get_le16(pdu);
+		length -= 2;
+		pdu += 2;
+
+		data.len = length;
+		data.data = pdu;
 
-	if (opcode == BT_ATT_OP_HANDLE_VAL_IND && !client->parent)
-		bt_att_chan_send(chan, BT_ATT_OP_HANDLE_VAL_CONF, NULL, 0,
+		queue_foreach(client->notify_list, notify_handler, &data);
+	}
+
+	if (opcode == BT_ATT_OP_HANDLE_IND && !client->parent)
+		bt_att_chan_send(chan, BT_ATT_OP_HANDLE_CONF, NULL, 0,
 							NULL, NULL, NULL);
 
 	bt_gatt_client_unref(client);
@@ -2181,7 +2199,8 @@ static void bt_gatt_client_free(struct bt_gatt_client *client)
 
 	if (client->att) {
 		bt_att_unregister_disconnect(client->att, client->disc_id);
-		bt_att_unregister(client->att, client->notify_id);
+		bt_att_unregister(client->att, client->nfy_id);
+		bt_att_unregister(client->att, client->nfy_mult_id);
 		bt_att_unregister(client->att, client->ind_id);
 		bt_att_unref(client->att);
 	}
@@ -2239,12 +2258,17 @@ static struct bt_gatt_client *gatt_client_new(struct gatt_db *db,
 	client->notify_chrcs = queue_new();
 	client->pending_requests = queue_new();
 
-	client->notify_id = bt_att_register(att, BT_ATT_OP_HANDLE_VAL_NOT,
+	client->nfy_id = bt_att_register(att, BT_ATT_OP_HANDLE_NFY,
+						notify_cb, client, NULL);
+	if (!client->nfy_id)
+		goto fail;
+
+	client->nfy_mult_id = bt_att_register(att, BT_ATT_OP_HANDLE_NFY_MULT,
 						notify_cb, client, NULL);
-	if (!client->notify_id)
+	if (!client->nfy_mult_id)
 		goto fail;
 
-	client->ind_id = bt_att_register(att, BT_ATT_OP_HANDLE_VAL_IND,
+	client->ind_id = bt_att_register(att, BT_ATT_OP_HANDLE_IND,
 						notify_cb, client, NULL);
 	if (!client->ind_id)
 		goto fail;
diff --git a/src/shared/gatt-server.c b/src/shared/gatt-server.c
index 8b18cb21f..8e81d6e0e 100644
--- a/src/shared/gatt-server.c
+++ b/src/shared/gatt-server.c
@@ -1745,7 +1745,7 @@ bool bt_gatt_server_send_notification(struct bt_gatt_server *server,
 	put_le16(handle, pdu);
 	memcpy(pdu + 2, value, pdu_len - 2);
 
-	result = !!bt_att_send(server->att, BT_ATT_OP_HANDLE_VAL_NOT, pdu,
+	result = !!bt_att_send(server->att, BT_ATT_OP_HANDLE_NFY, pdu,
 						pdu_len, NULL, NULL, NULL);
 	free(pdu);
 
@@ -1806,7 +1806,7 @@ bool bt_gatt_server_send_indication(struct bt_gatt_server *server,
 	put_le16(handle, pdu);
 	memcpy(pdu + 2, value, pdu_len - 2);
 
-	result = !!bt_att_send(server->att, BT_ATT_OP_HANDLE_VAL_IND, pdu,
+	result = !!bt_att_send(server->att, BT_ATT_OP_HANDLE_IND, pdu,
 							pdu_len, conf_cb,
 							data, destroy_ind_data);
 	if (!result)
-- 
2.21.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 10/12] gatt: Add support for Notify Multiple
  2020-02-28 23:46 [PATCH v2 00/12] Userspace Bluetooth 5.2 initial support Luiz Augusto von Dentz
                   ` (8 preceding siblings ...)
  2020-02-28 23:46 ` [PATCH v2 09/12] shared/gatt: Add support for Handle Value Multiple Notifications Luiz Augusto von Dentz
@ 2020-02-28 23:46 ` Luiz Augusto von Dentz
  2020-02-28 23:47 ` [PATCH v2 11/12] core: Add support for setting the number of GATT bearers Luiz Augusto von Dentz
                   ` (2 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2020-02-28 23:46 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

This adds support for Notify Multiple procedure marking its bit as
supported in the Client Features.
---
 src/gatt-database.c      |  8 +++--
 src/shared/att.c         |  3 +-
 src/shared/gatt-client.c |  2 ++
 src/shared/gatt-server.c | 77 +++++++++++++++++++++++++++++++++-------
 src/shared/gatt-server.h |  2 +-
 tools/btgatt-server.c    |  4 +--
 unit/test-gatt.c         |  2 +-
 7 files changed, 78 insertions(+), 20 deletions(-)

diff --git a/src/gatt-database.c b/src/gatt-database.c
index 2445d1fa5..1b514aa4f 100644
--- a/src/gatt-database.c
+++ b/src/gatt-database.c
@@ -1011,7 +1011,7 @@ static void gatt_ccc_write_cb(struct gatt_db_attribute *attrib,
 	if (ccc_cb->callback) {
 		struct pending_op *op;
 
-		op = pending_ccc_new(att, attrib, get_le16(value),
+		op = pending_ccc_new(att, attrib, val,
 					bt_att_get_link_type(att));
 		if (!op) {
 			ecode = BT_ATT_ERROR_UNLIKELY;
@@ -1098,7 +1098,8 @@ static void cli_feat_write_cb(struct gatt_db_attribute *attrib,
 	struct btd_gatt_database *database = user_data;
 	struct device_state *state;
 	uint8_t bits[] = { BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING,
-				BT_GATT_CHRC_CLI_FEAT_EATT };
+				BT_GATT_CHRC_CLI_FEAT_EATT,
+				BT_GATT_CHRC_CLI_FEAT_NFY_MULTI };
 	uint8_t ecode = 0;
 	unsigned int i;
 
@@ -1330,7 +1331,8 @@ static void send_notification_to_device(void *data, void *user_data)
 		DBG("GATT server sending notification");
 		bt_gatt_server_send_notification(server,
 					notify->handle, notify->value,
-					notify->len);
+					notify->len, device_state->cli_feat[0] &
+					BT_GATT_CHRC_CLI_FEAT_NFY_MULTI);
 		return;
 	}
 
diff --git a/src/shared/att.c b/src/shared/att.c
index 6fa16e422..948a5548b 100644
--- a/src/shared/att.c
+++ b/src/shared/att.c
@@ -145,6 +145,7 @@ static const struct {
 	{ BT_ATT_OP_EXEC_WRITE_REQ,		ATT_OP_TYPE_REQ },
 	{ BT_ATT_OP_EXEC_WRITE_RSP,		ATT_OP_TYPE_RSP },
 	{ BT_ATT_OP_HANDLE_NFY,			ATT_OP_TYPE_NFY },
+	{ BT_ATT_OP_HANDLE_NFY_MULT,		ATT_OP_TYPE_NFY },
 	{ BT_ATT_OP_HANDLE_IND,			ATT_OP_TYPE_IND },
 	{ BT_ATT_OP_HANDLE_CONF,		ATT_OP_TYPE_CONF },
 	{ }
@@ -842,7 +843,7 @@ static void handle_conf(struct bt_att_chan *chan, uint8_t *pdu, ssize_t pdu_len)
 	}
 
 	if (op->callback)
-		op->callback(BT_ATT_OP_HANDLE_NFY, NULL, 0, op->user_data);
+		op->callback(BT_ATT_OP_HANDLE_CONF, NULL, 0, op->user_data);
 
 	destroy_att_send_op(op);
 	chan->pending_ind = NULL;
diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c
index 7381f96e0..963ad619f 100644
--- a/src/shared/gatt-client.c
+++ b/src/shared/gatt-client.c
@@ -1968,6 +1968,8 @@ static void write_client_features(struct bt_gatt_client *client)
 			client->features |= BT_GATT_CHRC_CLI_FEAT_EATT;
 	}
 
+	client->features |= BT_GATT_CHRC_CLI_FEAT_NFY_MULTI;
+
 	util_debug(client->debug_callback, client->debug_data,
 			"Writing Client Features 0x%02x", client->features);
 
diff --git a/src/shared/gatt-server.c b/src/shared/gatt-server.c
index 8e81d6e0e..7e5d652e4 100644
--- a/src/shared/gatt-server.c
+++ b/src/shared/gatt-server.c
@@ -36,6 +36,7 @@
 #include "src/shared/gatt-server.h"
 #include "src/shared/gatt-helpers.h"
 #include "src/shared/util.h"
+#include "src/shared/timeout.h"
 
 #ifndef MAX
 #define MAX(a, b) ((a) > (b) ? (a) : (b))
@@ -51,6 +52,8 @@
  */
 #define DEFAULT_MAX_PREP_QUEUE_LEN 30
 
+#define NFY_MULT_TIMEOUT 10
+
 struct async_read_op {
 	struct bt_att_chan *chan;
 	struct bt_gatt_server *server;
@@ -86,6 +89,13 @@ static void prep_write_data_destroy(void *user_data)
 	free(data);
 }
 
+struct nfy_mult_data {
+	unsigned int id;
+	uint8_t *pdu;
+	uint16_t offset;
+	uint16_t len;
+};
+
 struct bt_gatt_server {
 	struct gatt_db *db;
 	struct bt_att *att;
@@ -120,6 +130,8 @@ struct bt_gatt_server {
 
 	bt_gatt_server_authorize_cb_t authorize;
 	void *authorize_data;
+
+	struct nfy_mult_data *nfy_mult;
 };
 
 static void bt_gatt_server_free(struct bt_gatt_server *server)
@@ -1726,28 +1738,69 @@ bool bt_gatt_server_set_debug(struct bt_gatt_server *server,
 	return true;
 }
 
+static bool notify_multiple(void *user_data)
+{
+	struct bt_gatt_server *server = user_data;
+
+	bt_att_send(server->att, BT_ATT_OP_HANDLE_NFY_MULT,
+			server->nfy_mult->pdu, server->nfy_mult->offset, NULL,
+			NULL, NULL);
+
+	free(server->nfy_mult->pdu);
+	free(server->nfy_mult);
+	server->nfy_mult = NULL;
+
+	return false;
+}
+
 bool bt_gatt_server_send_notification(struct bt_gatt_server *server,
 					uint16_t handle, const uint8_t *value,
-					uint16_t length)
+					uint16_t length, bool multiple)
 {
-	uint16_t pdu_len;
-	uint8_t *pdu;
+	struct nfy_mult_data *data = NULL;
 	bool result;
 
 	if (!server || (length && !value))
 		return false;
 
-	pdu_len = MIN(bt_att_get_mtu(server->att) - 1, length + 2);
-	pdu = malloc(pdu_len);
-	if (!pdu)
-		return false;
+	if (multiple)
+		data = server->nfy_mult;
 
-	put_le16(handle, pdu);
-	memcpy(pdu + 2, value, pdu_len - 2);
+	if (!data) {
+		data = new0(struct nfy_mult_data, 1);
+		data->len = bt_att_get_mtu(server->att) - 1;
+		data->pdu = malloc(data->len);
+	}
 
-	result = !!bt_att_send(server->att, BT_ATT_OP_HANDLE_NFY, pdu,
-						pdu_len, NULL, NULL, NULL);
-	free(pdu);
+	put_le16(handle, data->pdu + data->offset);
+	data->offset += 2;
+
+	length = MIN(data->len - data->offset, length);
+
+	if (multiple) {
+		put_le16(length, data->pdu + data->offset);
+		data->offset += 2;
+	}
+
+	memcpy(data->pdu + data->offset, value, length);
+	data->offset += length;
+
+	if (multiple) {
+		if (!server->nfy_mult)
+			server->nfy_mult = data;
+
+		if (!server->nfy_mult->id)
+			server->nfy_mult->id = timeout_add(NFY_MULT_TIMEOUT,
+						   notify_multiple, server,
+						   NULL);
+
+		return true;
+	}
+
+	result = !!bt_att_send(server->att, BT_ATT_OP_HANDLE_NFY,
+				data->pdu, data->offset, NULL, NULL, NULL);
+	free(data->pdu);
+	free(data);
 
 	return result;
 }
diff --git a/src/shared/gatt-server.h b/src/shared/gatt-server.h
index c3d83f225..a2492d275 100644
--- a/src/shared/gatt-server.h
+++ b/src/shared/gatt-server.h
@@ -52,7 +52,7 @@ bool bt_gatt_server_set_authorize(struct bt_gatt_server *server,
 
 bool bt_gatt_server_send_notification(struct bt_gatt_server *server,
 					uint16_t handle, const uint8_t *value,
-					uint16_t length);
+					uint16_t length, bool multiple);
 
 bool bt_gatt_server_send_indication(struct bt_gatt_server *server,
 					uint16_t handle, const uint8_t *value,
diff --git a/tools/btgatt-server.c b/tools/btgatt-server.c
index d9d96e691..5b7857b00 100644
--- a/tools/btgatt-server.c
+++ b/tools/btgatt-server.c
@@ -305,7 +305,7 @@ static bool hr_msrmt_cb(void *user_data)
 
 	bt_gatt_server_send_notification(server->gatt,
 						server->hr_msrmt_handle,
-						pdu, len);
+						pdu, len, false);
 
 
 	cur_ee = server->hr_energy_expended;
@@ -831,7 +831,7 @@ static void cmd_notify(struct server *server, char *cmd_str)
 							conf_cb, NULL, NULL))
 			printf("Failed to initiate indication\n");
 	} else if (!bt_gatt_server_send_notification(server->gatt, handle,
-								value, length))
+							value, length, false))
 		printf("Failed to initiate notification\n");
 
 done:
diff --git a/unit/test-gatt.c b/unit/test-gatt.c
index d94993b9c..36dd2847c 100644
--- a/unit/test-gatt.c
+++ b/unit/test-gatt.c
@@ -2271,7 +2271,7 @@ static void test_server_notification(struct context *context)
 	const struct test_step *step = context->data->step;
 
 	bt_gatt_server_send_notification(context->server, step->handle,
-						step->value, step->length);
+					step->value, step->length, false);
 }
 
 static const struct test_step test_notification_server_1 = {
-- 
2.21.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 11/12] core: Add support for setting the number of GATT bearers
  2020-02-28 23:46 [PATCH v2 00/12] Userspace Bluetooth 5.2 initial support Luiz Augusto von Dentz
                   ` (9 preceding siblings ...)
  2020-02-28 23:46 ` [PATCH v2 10/12] gatt: Add support for Notify Multiple Luiz Augusto von Dentz
@ 2020-02-28 23:47 ` Luiz Augusto von Dentz
  2020-02-28 23:47 ` [PATCH v2 12/12] monitor: Add support for decoding EATT Luiz Augusto von Dentz
  2020-03-01  2:49 ` [PATCH v2 00/12] Userspace Bluetooth 5.2 initial support Marcel Holtmann
  12 siblings, 0 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2020-02-28 23:47 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

This adds option to set the numbers of GATT Channels/Bearers to be
connected in main.conf.
---
 src/device.c        |  5 +++++
 src/gatt-client.c   |  8 +++++---
 src/gatt-database.c | 12 ++++++++++--
 src/hcid.h          |  1 +
 src/main.c          | 14 ++++++++++++++
 src/main.conf       |  5 +++++
 6 files changed, 40 insertions(+), 5 deletions(-)

diff --git a/src/device.c b/src/device.c
index c86e9a64d..7e3c2b215 100644
--- a/src/device.c
+++ b/src/device.c
@@ -5049,6 +5049,11 @@ bool device_attach_att(struct btd_device *dev, GIOChannel *io)
 	}
 
 	if (dev->att) {
+		if (main_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);
diff --git a/src/gatt-client.c b/src/gatt-client.c
index aa77661ad..958ce5f9b 100644
--- a/src/gatt-client.c
+++ b/src/gatt-client.c
@@ -59,8 +59,6 @@
 #define GATT_CHARACTERISTIC_IFACE	"org.bluez.GattCharacteristic1"
 #define GATT_DESCRIPTOR_IFACE		"org.bluez.GattDescriptor1"
 
-#define EATT_MAX_BEARERS 2
-
 struct btd_gatt_client {
 	struct btd_device *device;
 	uint8_t features;
@@ -2171,6 +2169,7 @@ static void eatt_connect_cb(GIOChannel *io, GError *gerr, gpointer user_data)
 
 static void eatt_connect(struct btd_gatt_client *client)
 {
+	struct bt_att *att = bt_gatt_client_get_att(client->gatt);
 	struct btd_device *dev = client->device;
 	struct btd_adapter *adapter = device_get_adapter(dev);
 	GIOChannel *io;
@@ -2178,11 +2177,14 @@ static void eatt_connect(struct btd_gatt_client *client)
 	char addr[18];
 	int i;
 
+	if (bt_att_get_channels(att) == main_opts.gatt_channels)
+		return;
+
 	ba2str(device_get_address(dev), addr);
 
 	DBG("Connection attempt to: %s", addr);
 
-	for (i = 0; i < EATT_MAX_BEARERS; i++) {
+	for (i = bt_att_get_channels(att); i < main_opts.gatt_channels; i++) {
 		io = bt_io_connect(eatt_connect_cb, client, NULL, NULL,
 				BT_IO_OPT_SOURCE_BDADDR,
 				btd_adapter_get_address(adapter),
diff --git a/src/gatt-database.c b/src/gatt-database.c
index 1b514aa4f..d0ab4da99 100644
--- a/src/gatt-database.c
+++ b/src/gatt-database.c
@@ -1215,10 +1215,13 @@ static void populate_gatt_service(struct btd_gatt_database *database)
 				&uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ,
 				db_hash_read_cb, NULL, database);
 
-	bt_uuid16_create(&uuid, GATT_CHARAC_SERVER_FEAT);
-	database->eatt = gatt_db_service_add_characteristic(service,
+	/* Only enable EATT if there is a socket listening */
+	if (database->eatt_io) {
+		bt_uuid16_create(&uuid, GATT_CHARAC_SERVER_FEAT);
+		database->eatt = gatt_db_service_add_characteristic(service,
 				&uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ,
 				server_feat_read_cb, NULL, database);
+	}
 
 	gatt_db_service_set_active(service, true);
 
@@ -3564,6 +3567,10 @@ struct btd_gatt_database *btd_gatt_database_new(struct btd_adapter *adapter)
 		goto fail;
 	}
 
+	/* If just just 1 channel is enabled EATT is not required */
+	if (main_opts.gatt_channels == 1)
+		goto bredr;
+
 	/* EATT socket */
 	database->eatt_io = bt_io_listen(connect_cb, NULL, NULL, NULL, NULL,
 					BT_IO_OPT_SOURCE_BDADDR, addr,
@@ -3591,6 +3598,7 @@ struct btd_gatt_database *btd_gatt_database_new(struct btd_adapter *adapter)
 		}
 	}
 
+bredr:
 	/* BR/EDR socket */
 	database->bredr_io = bt_io_listen(connect_cb, NULL, NULL, NULL, &gerr,
 					BT_IO_OPT_SOURCE_BDADDR, addr,
diff --git a/src/hcid.h b/src/hcid.h
index ca405fde4..1bf93784d 100644
--- a/src/hcid.h
+++ b/src/hcid.h
@@ -62,6 +62,7 @@ struct main_opts {
 	bt_mode_t	mode;
 	bt_gatt_cache_t gatt_cache;
 	uint16_t	gatt_mtu;
+	uint8_t		gatt_channels;
 
 	uint8_t		key_size;
 
diff --git a/src/main.c b/src/main.c
index fc8c869fc..7b927ac79 100644
--- a/src/main.c
+++ b/src/main.c
@@ -109,6 +109,7 @@ static const char *gatt_options[] = {
 	"Cache",
 	"KeySize",
 	"ExchangeMTU",
+	"EATTChannels",
 	NULL
 };
 
@@ -471,6 +472,18 @@ static void parse_config(GKeyFile *config)
 		DBG("ExchangeMTU=%d", val);
 		main_opts.gatt_mtu = val;
 	}
+
+	val = g_key_file_get_integer(config, "GATT", "Channels", &err);
+	if (err) {
+		DBG("%s", err->message);
+		g_clear_error(&err);
+	} else {
+		DBG("Channels=%d", val);
+		/* Ensure the channels is within a valid range. */
+		val = MIN(val, 5);
+		val = MAX(val, 1);
+		main_opts.gatt_channels = val;
+	}
 }
 
 static void init_defaults(void)
@@ -497,6 +510,7 @@ static void init_defaults(void)
 
 	main_opts.gatt_cache = BT_GATT_CACHE_ALWAYS;
 	main_opts.gatt_mtu = BT_ATT_MAX_LE_MTU;
+	main_opts.gatt_channels = 3;
 }
 
 static void log_handler(const gchar *log_domain, GLogLevelFlags log_level,
diff --git a/src/main.conf b/src/main.conf
index bb5ff5b15..16701ebe4 100644
--- a/src/main.conf
+++ b/src/main.conf
@@ -99,6 +99,11 @@
 # Defaults to 517
 #ExchangeMTU = 517
 
+# Number of ATT channels
+# Possible values: 1-5 (1 disables EATT)
+# Default to 3
+#Channels = 3
+
 [Policy]
 #
 # The ReconnectUUIDs defines the set of remote services that should try
-- 
2.21.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 12/12] monitor: Add support for decoding EATT
  2020-02-28 23:46 [PATCH v2 00/12] Userspace Bluetooth 5.2 initial support Luiz Augusto von Dentz
                   ` (10 preceding siblings ...)
  2020-02-28 23:47 ` [PATCH v2 11/12] core: Add support for setting the number of GATT bearers Luiz Augusto von Dentz
@ 2020-02-28 23:47 ` Luiz Augusto von Dentz
  2020-03-01  2:49 ` [PATCH v2 00/12] Userspace Bluetooth 5.2 initial support Marcel Holtmann
  12 siblings, 0 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2020-02-28 23:47 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

This decodes packets received over EATT PSM.
---
 monitor/l2cap.c | 39 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 39 insertions(+)

diff --git a/monitor/l2cap.c b/monitor/l2cap.c
index 3b2b25f24..9409604c3 100644
--- a/monitor/l2cap.c
+++ b/monitor/l2cap.c
@@ -2573,6 +2573,36 @@ static void att_handle_value_conf(const struct l2cap_frame *frame)
 {
 }
 
+static void att_multiple_vl_rsp(const struct l2cap_frame *frame)
+{
+	struct l2cap_frame *f = (void *) frame;
+
+	while (frame->size) {
+		uint16_t handle;
+		uint16_t len;
+
+		if (!l2cap_frame_get_le16(f, &handle))
+			return;
+
+		print_field("Handle: 0x%4.4x", handle);
+
+		if (!l2cap_frame_get_le16(f, &len))
+			return;
+
+		print_field("Length: 0x%4.4x", len);
+
+		print_hex_field("  Data", f->data,
+				len < f->size ? len : f->size);
+
+		if (len > f->size) {
+			print_text(COLOR_ERROR, "invalid size");
+			return;
+		}
+
+		l2cap_frame_pull(f, f, len);
+	}
+}
+
 static void att_write_command(const struct l2cap_frame *frame)
 {
 	print_field("Handle: 0x%4.4x", get_le16(frame->data));
@@ -2645,6 +2675,12 @@ static const struct att_opcode_data att_opcode_table[] = {
 			att_handle_value_ind, 2, false },
 	{ 0x1e, "Handle Value Confirmation",
 			att_handle_value_conf, 0, true },
+	{ 0x20, "Read Multiple Request Variable Length",
+			att_read_multiple_req, 4, false },
+	{ 0x21, "Read Multiple Response Variable Length",
+			att_multiple_vl_rsp, 4, false },
+	{ 0x23, "Handle Multiple Value Notification",
+			att_multiple_vl_rsp, 4, false },
 	{ 0x52, "Write Command",
 			att_write_command, 2, false },
 	{ 0xd2, "Signed Write Command", att_signed_write_command, 14, false },
@@ -3287,6 +3323,9 @@ void l2cap_frame(uint16_t index, bool in, uint16_t handle, uint16_t cid,
 		case 0x001f:
 			att_packet(index, in, handle, cid, data, size);
 			break;
+		case 0x0027:
+			att_packet(index, in, handle, cid, data + 2, size - 2);
+			break;
 		case 0x0017:
 		case 0x001B:
 			avctp_packet(&frame);
-- 
2.21.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* Re: [PATCH v2 00/12] Userspace Bluetooth 5.2 initial support
  2020-02-28 23:46 [PATCH v2 00/12] Userspace Bluetooth 5.2 initial support Luiz Augusto von Dentz
                   ` (11 preceding siblings ...)
  2020-02-28 23:47 ` [PATCH v2 12/12] monitor: Add support for decoding EATT Luiz Augusto von Dentz
@ 2020-03-01  2:49 ` Marcel Holtmann
  2020-03-02 22:48   ` Luiz Augusto von Dentz
  12 siblings, 1 reply; 15+ messages in thread
From: Marcel Holtmann @ 2020-03-01  2:49 UTC (permalink / raw)
  To: Luiz Augusto von Dentz; +Cc: linux-bluetooth

Hi Luiz,

> This introduces the initial support for Bluetooth 5.2 features:
> 
> * ISO channels:
>        + tools/isotest command line tool to run validation tests
> 
> * L2CAP Enhanced Credit Based Flow Control Mode
>        + tools/l2test has been update to include the new mode

I prefer if we not merge these two just yet. Let this settle a bit first and figure out what the best userspace API is. Otherwise we are stuck with an API that doesn’t work.

> * Enhanced ATT Bearer:
>        + Client and Server support
>        + Include all new procedures
>        + Automaticlly detects and enables channels
>        + Number of channels configurable via main.conf

This looks ok to be merged.

Regards

Marcel


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v2 00/12] Userspace Bluetooth 5.2 initial support
  2020-03-01  2:49 ` [PATCH v2 00/12] Userspace Bluetooth 5.2 initial support Marcel Holtmann
@ 2020-03-02 22:48   ` Luiz Augusto von Dentz
  0 siblings, 0 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2020-03-02 22:48 UTC (permalink / raw)
  To: Marcel Holtmann; +Cc: linux-bluetooth

Hi,

On Sat, Feb 29, 2020 at 6:49 PM Marcel Holtmann <marcel@holtmann.org> wrote:
>
> Hi Luiz,
>
> > This introduces the initial support for Bluetooth 5.2 features:
> >
> > * ISO channels:
> >        + tools/isotest command line tool to run validation tests
> >
> > * L2CAP Enhanced Credit Based Flow Control Mode
> >        + tools/l2test has been update to include the new mode
>
> I prefer if we not merge these two just yet. Let this settle a bit first and figure out what the best userspace API is. Otherwise we are stuck with an API that doesn’t work.
>
> > * Enhanced ATT Bearer:
> >        + Client and Server support
> >        + Include all new procedures
> >        + Automaticlly detects and enables channels
> >        + Number of channels configurable via main.conf
>
> This looks ok to be merged.
>
> Regards
>
> Marcel

Applied, L2CAP changes were excluded since we still need to settle on
the userspace API.

-- 
Luiz Augusto von Dentz

^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2020-03-02 22:48 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-02-28 23:46 [PATCH v2 00/12] Userspace Bluetooth 5.2 initial support Luiz Augusto von Dentz
2020-02-28 23:46 ` [PATCH v2 01/12] lib: Add definitions for Enhanced Credits Based Mode Luiz Augusto von Dentz
2020-02-28 23:46 ` [PATCH v2 02/12] btio: Add mode to for Enhanced Credit Mode Luiz Augusto von Dentz
2020-02-28 23:46 ` [PATCH v2 03/12] l2test: Add support for L2CAP_EXT_FLOWCTL_MODE Luiz Augusto von Dentz
2020-02-28 23:46 ` [PATCH v2 04/12] share/att: Add EATT support Luiz Augusto von Dentz
2020-02-28 23:46 ` [PATCH v2 05/12] shared/gatt-client: Add support for EATT features Luiz Augusto von Dentz
2020-02-28 23:46 ` [PATCH v2 06/12] gatt: Enable EATT bearer support Luiz Augusto von Dentz
2020-02-28 23:46 ` [PATCH v2 07/12] shared/gatt-server: Add support for Read Multiple Variable Length Luiz Augusto von Dentz
2020-02-28 23:46 ` [PATCH v2 08/12] shared/gatt-client: " Luiz Augusto von Dentz
2020-02-28 23:46 ` [PATCH v2 09/12] shared/gatt: Add support for Handle Value Multiple Notifications Luiz Augusto von Dentz
2020-02-28 23:46 ` [PATCH v2 10/12] gatt: Add support for Notify Multiple Luiz Augusto von Dentz
2020-02-28 23:47 ` [PATCH v2 11/12] core: Add support for setting the number of GATT bearers Luiz Augusto von Dentz
2020-02-28 23:47 ` [PATCH v2 12/12] monitor: Add support for decoding EATT Luiz Augusto von Dentz
2020-03-01  2:49 ` [PATCH v2 00/12] Userspace Bluetooth 5.2 initial support Marcel Holtmann
2020-03-02 22:48   ` Luiz Augusto von Dentz

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.