This patch set implements the most important server-side ATT protocol
operations by interfacing with the new gatt-db API functions. All
requests except "Read Multiple" and "Signed Write Command" are provided
in this set.
This patch set also doesn't address checks for encryption/authentication/
authorization permissions. A TODO item has been added to address this
later.
*v1: Removed offsetof math while casting service pointer to (void *)
*v2: - Added TODO for using struct iovec
- Removed bt_gatt_server_set_max_prep_queue_len
- Always pass write_cb to gatt_db_attribute_write even if the opcode is
"Write Command".
- Added more util_debug lines in handler functions.
Arman Uguray (11):
shared/gatt-server: Implement "Read By Type" request.
shared/gatt-server: Implement "Find Information" request.
shared/gatt-server: Implement "Write" request and command.
shared/gatt-server: Implement "Read" request.
shared/gatt-server: Implement "Read Blob" request.
shared/att: Handle incoming confirmation PDU.
shared/gatt-server: Add functions for sending
notifications/indications.
shared/gatt-server: Implement "Prepare Write" request.
shared/gatt-server: Implement "Execute Write" request.
shared/gatt-client: Fix alignment warnings.
TODO: Update shared/gatt-server items.
TODO | 26 +-
src/shared/att.c | 119 +++--
src/shared/gatt-client.c | 6 +-
src/shared/gatt-server.c | 1221 +++++++++++++++++++++++++++++++++++++++++++---
src/shared/gatt-server.h | 12 +
5 files changed, 1257 insertions(+), 127 deletions(-)
--
2.1.0.rc2.206.gedb03e5
Hi Arman,
On Mon, Nov 10, 2014 at 09:07:21AM -0800, Arman Uguray wrote:
> This patch implements the "Read Blob" request for the GATT server role.
> ---
> src/shared/gatt-server.c | 100 ++++++++++++++++++++++++++++++++++++++++-------
> 1 file changed, 85 insertions(+), 15 deletions(-)
>
...
> +static void read_blob_cb(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) {
> + uint8_t pdu[4];
Can we avoid shadowing variables without any need?
This unfortunately became common practice for gatt code.
Best regards
Andrei Emeltchenko
Hi Luiz,
> On Tue, Nov 11, 2014 at 3:52 AM, Luiz Augusto von Dentz <[email protected]> wrote:
> Hi Arman,
>
> On Mon, Nov 10, 2014 at 7:07 PM, Arman Uguray <[email protected]> wrote:
>> This patch set implements the most important server-side ATT protocol
>> operations by interfacing with the new gatt-db API functions. All
>> requests except "Read Multiple" and "Signed Write Command" are provided
>> in this set.
>>
>> This patch set also doesn't address checks for encryption/authentication/
>> authorization permissions. A TODO item has been added to address this
>> later.
>>
>> *v1: Removed offsetof math while casting service pointer to (void *)
>> *v2: - Added TODO for using struct iovec
>> - Removed bt_gatt_server_set_max_prep_queue_len
>> - Always pass write_cb to gatt_db_attribute_write even if the opcode is
>> "Write Command".
>> - Added more util_debug lines in handler functions.
>>
>> Arman Uguray (11):
>> shared/gatt-server: Implement "Read By Type" request.
>> shared/gatt-server: Implement "Find Information" request.
>> shared/gatt-server: Implement "Write" request and command.
>> shared/gatt-server: Implement "Read" request.
>> shared/gatt-server: Implement "Read Blob" request.
>> shared/att: Handle incoming confirmation PDU.
>> shared/gatt-server: Add functions for sending
>> notifications/indications.
>> shared/gatt-server: Implement "Prepare Write" request.
>> shared/gatt-server: Implement "Execute Write" request.
>> shared/gatt-client: Fix alignment warnings.
>> TODO: Update shared/gatt-server items.
>>
>> TODO | 26 +-
>> src/shared/att.c | 119 +++--
>> src/shared/gatt-client.c | 6 +-
>> src/shared/gatt-server.c | 1221 +++++++++++++++++++++++++++++++++++++++++++---
>> src/shared/gatt-server.h | 12 +
>> 5 files changed, 1257 insertions(+), 127 deletions(-)
>>
>> --
>> 2.1.0.rc2.206.gedb03e5
>
> I went ahead and applied this set, I will probably send a patch in a
> moment adding a timeout for db read/write operations, I figure 1
> seconds should be enough to start with since LE is focus on low
> latency, if that doesn't cut it we should adjust the write queue
> according to how many operation it can deliver before it times out.
>
Sounds reasonable to me. I guess, since the ATT timeout is 30 seconds,
a queue length of 30 and operation timeout of 1 seem good for now,
even if not optimal for all cases. We can tweak this later as
necessary.
>
> --
> Luiz Augusto von Dentz
Cheers,
Arman
Hi Arman,
On Mon, Nov 10, 2014 at 7:07 PM, Arman Uguray <[email protected]> wrote:
> This patch set implements the most important server-side ATT protocol
> operations by interfacing with the new gatt-db API functions. All
> requests except "Read Multiple" and "Signed Write Command" are provided
> in this set.
>
> This patch set also doesn't address checks for encryption/authentication/
> authorization permissions. A TODO item has been added to address this
> later.
>
> *v1: Removed offsetof math while casting service pointer to (void *)
> *v2: - Added TODO for using struct iovec
> - Removed bt_gatt_server_set_max_prep_queue_len
> - Always pass write_cb to gatt_db_attribute_write even if the opcode is
> "Write Command".
> - Added more util_debug lines in handler functions.
>
> Arman Uguray (11):
> shared/gatt-server: Implement "Read By Type" request.
> shared/gatt-server: Implement "Find Information" request.
> shared/gatt-server: Implement "Write" request and command.
> shared/gatt-server: Implement "Read" request.
> shared/gatt-server: Implement "Read Blob" request.
> shared/att: Handle incoming confirmation PDU.
> shared/gatt-server: Add functions for sending
> notifications/indications.
> shared/gatt-server: Implement "Prepare Write" request.
> shared/gatt-server: Implement "Execute Write" request.
> shared/gatt-client: Fix alignment warnings.
> TODO: Update shared/gatt-server items.
>
> TODO | 26 +-
> src/shared/att.c | 119 +++--
> src/shared/gatt-client.c | 6 +-
> src/shared/gatt-server.c | 1221 +++++++++++++++++++++++++++++++++++++++++++---
> src/shared/gatt-server.h | 12 +
> 5 files changed, 1257 insertions(+), 127 deletions(-)
>
> --
> 2.1.0.rc2.206.gedb03e5
I went ahead and applied this set, I will probably send a patch in a
moment adding a timeout for db read/write operations, I figure 1
seconds should be enough to start with since LE is focus on low
latency, if that doesn't cut it we should adjust the write queue
according to how many operation it can deliver before it times out.
--
Luiz Augusto von Dentz
shared/gatt-server has been introduced, so removed that item. Added new
items for remaining tasks.
---
TODO | 26 ++++++++++++++++++++++++--
1 file changed, 24 insertions(+), 2 deletions(-)
diff --git a/TODO b/TODO
index b5a9612..fec0fa2 100644
--- a/TODO
+++ b/TODO
@@ -120,6 +120,12 @@ ATT/GATT (new shared stack)
Priority: Medium
Complexity: C2
+- Use struct iovec to pass around byte buffers that will be sent over the wire,
+ instead of passing uint8_t and size_t parameters everywhere.
+
+ Priority: Medium
+ Complexity: C1
+
- Introduce a handler interface to shared/gatt-client which can be used by the
upper layer to determine when the link has been disconnected or an ATT
protocol request times out.
@@ -155,12 +161,28 @@ ATT/GATT (new shared stack)
Priority: Medium
Complexity: C2
-- Introduce shared/gatt-server, which combined with shared/gatt-db, can be used
- as a GATT server implementation.
+- Introduce a way for shared/gatt-server to check security permissions on the
+ current connection through bt_att.
Priority: Medium
Complexity: C2
+- Provide a tool for shared/gatt-server. This tool should demonstrate how a
+ shared/gatt-db can be used together with a shared/gatt-server to implement the
+ GATT server role. This should be written in a way so that it can be easily
+ used in conjunction with a remote instance of tools/btgatt-client (i.e. it
+ should listen for incoming connections, have similar verbose output, etc.)
+
+ Priority: Medium
+ Complexity: C2
+
+- Implement other low-priority ATT protocol operations for shared/gatt-server:
+
+ Read Multiple Request
+
+ Priority: Low
+ Complexity: C1
+
- Implement the server portion of doc/gatt-api.txt using shared/gatt-server once
it exists.
--
2.1.0.rc2.206.gedb03e5
This patch adds support for handling ATT "Execute Write" requests for
the server role.
---
src/shared/gatt-server.c | 122 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 122 insertions(+)
diff --git a/src/shared/gatt-server.c b/src/shared/gatt-server.c
index 17e15b1..2ca318a 100644
--- a/src/shared/gatt-server.c
+++ b/src/shared/gatt-server.c
@@ -92,6 +92,7 @@ struct bt_gatt_server {
unsigned int read_id;
unsigned int read_blob_id;
unsigned int prep_write_id;
+ unsigned int exec_write_id;
struct queue *prep_queue;
unsigned int max_prep_queue_len;
@@ -118,6 +119,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->prep_write_id);
+ bt_att_unregister(server->att, server->exec_write_id);
if (server->pending_read_op)
server->pending_read_op->server = NULL;
@@ -1037,6 +1039,119 @@ done:
NULL);
}
+static void exec_next_prep_write(struct bt_gatt_server *server,
+ uint16_t ehandle, uint8_t att_ecode);
+
+static void exec_write_complete_cb(struct gatt_db_attribute *attr, int err,
+ void *user_data)
+{
+ struct bt_gatt_server *server = user_data;
+ uint16_t handle = gatt_db_attribute_get_handle(attr);
+ uint16_t att_ecode = att_ecode_from_error(err);
+
+ exec_next_prep_write(server, handle, att_ecode);
+}
+
+static void exec_next_prep_write(struct bt_gatt_server *server,
+ uint16_t ehandle, uint8_t att_ecode)
+{
+ struct prep_write_data *next = NULL;
+ uint8_t rsp_opcode = BT_ATT_OP_EXEC_WRITE_RSP;
+ uint8_t error_pdu[4];
+ uint8_t *rsp_pdu = NULL;
+ uint16_t rsp_len = 0;
+ struct gatt_db_attribute *attr;
+ bool status;
+
+ if (att_ecode)
+ goto error;
+
+ next = queue_pop_head(server->prep_queue);
+ if (!next)
+ goto done;
+
+ attr = gatt_db_get_attribute(server->db, next->handle);
+ if (!attr) {
+ att_ecode = BT_ATT_ERROR_UNLIKELY;
+ goto error;
+ }
+
+ status = gatt_db_attribute_write(attr, next->offset,
+ next->value, next->length,
+ BT_ATT_OP_EXEC_WRITE_REQ, NULL,
+ exec_write_complete_cb, server);
+
+ prep_write_data_destroy(next);
+
+ if (status)
+ return;
+
+ att_ecode = BT_ATT_ERROR_UNLIKELY;
+
+error:
+ rsp_opcode = BT_ATT_OP_ERROR_RSP;
+ rsp_len = 4;
+ rsp_pdu = error_pdu;
+ encode_error_rsp(BT_ATT_OP_EXEC_WRITE_REQ, ehandle, att_ecode, rsp_pdu);
+
+done:
+ bt_att_send(server->att, rsp_opcode, rsp_pdu, rsp_len, NULL, NULL,
+ NULL);
+}
+
+static void exec_write_cb(uint8_t opcode, const void *pdu,
+ uint16_t length, void *user_data)
+{
+ struct bt_gatt_server *server = user_data;
+ uint8_t flags;
+ uint8_t ecode;
+ uint8_t rsp_opcode;
+ uint8_t error_pdu[4];
+ uint8_t *rsp_pdu = NULL;
+ uint16_t rsp_len = 0;
+ bool write;
+
+ if (length != 1) {
+ ecode = BT_ATT_ERROR_INVALID_PDU;
+ goto error;
+ }
+
+ flags = ((uint8_t *) pdu)[0];
+
+ util_debug(server->debug_callback, server->debug_data,
+ "Exec Write Req - flags: 0x%02x", flags);
+
+ if (flags == 0x00)
+ write = false;
+ else if (flags == 0x01)
+ write = true;
+ else {
+ ecode = BT_ATT_ERROR_INVALID_PDU;
+ goto error;
+ }
+
+ if (!write) {
+ queue_remove_all(server->prep_queue, NULL, NULL,
+ prep_write_data_destroy);
+ rsp_opcode = BT_ATT_OP_EXEC_WRITE_RSP;
+ goto done;
+ }
+
+ exec_next_prep_write(server, 0, 0);
+
+ return;
+
+error:
+ rsp_opcode = BT_ATT_OP_ERROR_RSP;
+ rsp_len = 4;
+ rsp_pdu = error_pdu;
+ encode_error_rsp(opcode, 0, ecode, rsp_pdu);
+
+done:
+ bt_att_send(server->att, rsp_opcode, rsp_pdu, rsp_len, NULL, NULL,
+ NULL);
+}
+
static void exchange_mtu_cb(uint8_t opcode, const void *pdu,
uint16_t length, void *user_data)
{
@@ -1137,6 +1252,13 @@ static bool gatt_server_register_att_handlers(struct bt_gatt_server *server)
if (!server->prep_write_id)
return false;
+ /* Execute Write Request */
+ server->exec_write_id = bt_att_register(server->att,
+ BT_ATT_OP_EXEC_WRITE_REQ,
+ exec_write_cb, server, NULL);
+ if (!server->exec_write_id)
+ return NULL;
+
return true;
}
--
2.1.0.rc2.206.gedb03e5
This patch addresses warnings that arise when compiled with
-W=cast-align.
---
src/shared/gatt-client.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c
index b4f28b2..401f551 100644
--- a/src/shared/gatt-client.c
+++ b/src/shared/gatt-client.c
@@ -1647,7 +1647,7 @@ bool bt_gatt_characteristic_iter_init(struct bt_gatt_characteristic_iter *iter,
return false;
memset(iter, 0, sizeof(*iter));
- iter->service = (struct service_list *) service;
+ iter->service = (void *) service;
return true;
}
@@ -1677,7 +1677,7 @@ bool bt_gatt_include_service_iter_init(struct bt_gatt_incl_service_iter *iter,
return false;
memset(iter, 0, sizeof(*iter));
- iter->service = (struct service_list *) service;
+ iter->service = (void *) service;
return true;
}
@@ -2402,7 +2402,7 @@ bool bt_gatt_client_register_notify(struct bt_gatt_client *client,
while (bt_gatt_service_iter_next(&iter, &service)) {
if (chrc_value_handle >= service->start_handle &&
chrc_value_handle <= service->end_handle) {
- svc_data = (struct service_list *) service;
+ svc_data = (void *) service;
break;
}
}
--
2.1.0.rc2.206.gedb03e5
This patch add support for handling ATT "Prepare Write" requests for the
server role.
---
src/shared/gatt-server.c | 138 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 138 insertions(+)
diff --git a/src/shared/gatt-server.c b/src/shared/gatt-server.c
index a8749b8..17e15b1 100644
--- a/src/shared/gatt-server.c
+++ b/src/shared/gatt-server.c
@@ -40,6 +40,12 @@
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
+/*
+ * TODO: This is an arbitrary limit. Come up with something reasonable or
+ * perhaps an API to set this value if there is a use case for it.
+ */
+#define DEFAULT_MAX_PREP_QUEUE_LEN 30
+
struct async_read_op {
struct bt_gatt_server *server;
uint8_t opcode;
@@ -55,6 +61,22 @@ struct async_write_op {
uint8_t opcode;
};
+struct prep_write_data {
+ struct bt_gatt_server *server;
+ uint8_t *value;
+ uint16_t handle;
+ uint16_t offset;
+ uint16_t length;
+};
+
+static void prep_write_data_destroy(void *user_data)
+{
+ struct prep_write_data *data = user_data;
+
+ free(data->value);
+ free(data);
+}
+
struct bt_gatt_server {
struct gatt_db *db;
struct bt_att *att;
@@ -69,6 +91,10 @@ struct bt_gatt_server {
unsigned int write_cmd_id;
unsigned int read_id;
unsigned int read_blob_id;
+ unsigned int prep_write_id;
+
+ struct queue *prep_queue;
+ unsigned int max_prep_queue_len;
struct async_read_op *pending_read_op;
struct async_write_op *pending_write_op;
@@ -91,6 +117,7 @@ static void bt_gatt_server_free(struct bt_gatt_server *server)
bt_att_unregister(server->att, server->write_cmd_id);
bt_att_unregister(server->att, server->read_id);
bt_att_unregister(server->att, server->read_blob_id);
+ bt_att_unregister(server->att, server->prep_write_id);
if (server->pending_read_op)
server->pending_read_op->server = NULL;
@@ -98,6 +125,8 @@ static void bt_gatt_server_free(struct bt_gatt_server *server)
if (server->pending_write_op)
server->pending_write_op->server = NULL;
+ queue_destroy(server->prep_queue, prep_write_data_destroy);
+
bt_att_unref(server->att);
free(server);
}
@@ -913,6 +942,101 @@ static void read_blob_cb(uint8_t opcode, const void *pdu,
handle_read_req(server, opcode, handle, offset);
}
+static void prep_write_cb(uint8_t opcode, const void *pdu,
+ uint16_t length, void *user_data)
+{
+ struct bt_gatt_server *server = user_data;
+ struct prep_write_data *prep_data = NULL;
+ uint16_t handle = 0;
+ uint16_t offset;
+ struct gatt_db_attribute *attr;
+ uint8_t rsp_opcode;
+ uint8_t rsp_pdu[MAX(4, length)];
+ uint16_t rsp_len;
+ uint8_t ecode;
+ uint32_t perm;
+
+ if (length < 4) {
+ ecode = BT_ATT_ERROR_INVALID_PDU;
+ goto error;
+ }
+
+ if (queue_length(server->prep_queue) >= server->max_prep_queue_len) {
+ ecode = BT_ATT_ERROR_PREPARE_QUEUE_FULL;
+ goto error;
+ }
+
+ handle = get_le16(pdu);
+ offset = get_le16(pdu + 2);
+
+ attr = gatt_db_get_attribute(server->db, handle);
+ if (!attr) {
+ ecode = BT_ATT_ERROR_INVALID_HANDLE;
+ goto error;
+ }
+
+ util_debug(server->debug_callback, server->debug_data,
+ "Prep Write Req - handle: 0x%04x", handle);
+
+ if (!gatt_db_attribute_get_permissions(attr, &perm)) {
+ ecode = BT_ATT_ERROR_INVALID_HANDLE;
+ goto error;
+ }
+
+ /*
+ * TODO: The "Prepare Write" request requires security permission checks
+ * to be performed before the write is executed. I.e., we can't leave
+ * the permission check to the upper layer since we can't call
+ * gatt_db_write until the entire queue is atomically processed during
+ * an "Execute Write" request. Figure out how to make this check here.
+ */
+ if (!(perm & BT_ATT_PERM_WRITE)) {
+ ecode = BT_ATT_ERROR_WRITE_NOT_PERMITTED;
+ goto error;
+ }
+
+ prep_data = new0(struct prep_write_data, 1);
+ if (!prep_data) {
+ ecode = BT_ATT_ERROR_INSUFFICIENT_RESOURCES;
+ goto error;
+ }
+
+ prep_data->length = length - 4;
+ if (prep_data->length) {
+ prep_data->value = malloc(prep_data->length);
+ if (!prep_data->value) {
+ ecode = BT_ATT_ERROR_INSUFFICIENT_RESOURCES;
+ goto error;
+ }
+ }
+
+ prep_data->server = server;
+ prep_data->handle = handle;
+ prep_data->offset = offset;
+ memcpy(prep_data->value, pdu + 4, prep_data->length);
+
+ queue_push_tail(server->prep_queue, prep_data);
+
+ /* Create the response */
+ rsp_len = length;
+ rsp_opcode = BT_ATT_OP_PREP_WRITE_RSP;
+ memcpy(rsp_pdu, pdu, rsp_len);
+
+ goto done;
+
+error:
+ if (prep_data)
+ prep_write_data_destroy(prep_data);
+
+ rsp_opcode = BT_ATT_OP_ERROR_RSP;
+ rsp_len = 4;
+ encode_error_rsp(opcode, handle, ecode, rsp_pdu);
+
+done:
+ bt_att_send(server->att, rsp_opcode, rsp_pdu, rsp_len, NULL, NULL,
+ NULL);
+}
+
static void exchange_mtu_cb(uint8_t opcode, const void *pdu,
uint16_t length, void *user_data)
{
@@ -1006,6 +1130,13 @@ static bool gatt_server_register_att_handlers(struct bt_gatt_server *server)
if (!server->read_blob_id)
return false;
+ /* Prepare Write Request */
+ server->prep_write_id = bt_att_register(server->att,
+ BT_ATT_OP_PREP_WRITE_REQ,
+ prep_write_cb, server, NULL);
+ if (!server->prep_write_id)
+ return false;
+
return true;
}
@@ -1024,6 +1155,13 @@ struct bt_gatt_server *bt_gatt_server_new(struct gatt_db *db,
server->db = db;
server->att = bt_att_ref(att);
server->mtu = MAX(mtu, BT_ATT_DEFAULT_LE_MTU);
+ server->max_prep_queue_len = DEFAULT_MAX_PREP_QUEUE_LEN;
+
+ server->prep_queue = queue_new();
+ if (!server->prep_queue) {
+ bt_gatt_server_free(server);
+ return NULL;
+ }
if (!gatt_server_register_att_handlers(server)) {
bt_gatt_server_free(server);
--
2.1.0.rc2.206.gedb03e5
This patch introduces new functions for sending notifications and
indications as a GATT server.
---
src/shared/gatt-server.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++++
src/shared/gatt-server.h | 12 ++++++
2 files changed, 107 insertions(+)
diff --git a/src/shared/gatt-server.c b/src/shared/gatt-server.c
index 16b6460..a8749b8 100644
--- a/src/shared/gatt-server.c
+++ b/src/shared/gatt-server.c
@@ -1068,3 +1068,98 @@ bool bt_gatt_server_set_debug(struct bt_gatt_server *server,
return true;
}
+
+bool bt_gatt_server_send_notification(struct bt_gatt_server *server,
+ uint16_t handle, const uint8_t *value,
+ uint16_t length)
+{
+ uint16_t pdu_len;
+ uint8_t *pdu;
+ bool result;
+
+ if (!server || (length && !value))
+ return false;
+
+ pdu_len = MIN(bt_att_get_mtu(server->att), length + 2);
+ pdu = malloc(pdu_len);
+ if (!pdu)
+ return false;
+
+ put_le16(handle, pdu);
+ memcpy(pdu + 2, value, length);
+
+ result = !!bt_att_send(server->att, BT_ATT_OP_HANDLE_VAL_NOT, pdu,
+ pdu_len, NULL, NULL, NULL);
+ free(pdu);
+
+ return result;
+}
+
+struct ind_data {
+ bt_gatt_server_conf_func_t callback;
+ bt_gatt_server_destroy_func_t destroy;
+ void *user_data;
+};
+
+static void destroy_ind_data(void *user_data)
+{
+ struct ind_data *data = user_data;
+
+ if (data->destroy)
+ data->destroy(data->user_data);
+
+ free(data);
+}
+
+static void conf_cb(uint8_t opcode, const void *pdu,
+ uint16_t length, void *user_data)
+{
+ struct ind_data *data = user_data;
+
+ if (data->callback)
+ data->callback(data->user_data);
+}
+
+bool bt_gatt_server_send_indication(struct bt_gatt_server *server,
+ uint16_t handle, const uint8_t *value,
+ uint16_t length,
+ bt_gatt_server_conf_func_t callback,
+ void *user_data,
+ bt_gatt_server_destroy_func_t destroy)
+{
+ uint16_t pdu_len;
+ uint8_t *pdu;
+ struct ind_data *data;
+ bool result;
+
+ if (!server || (length && !value))
+ return false;
+
+ pdu_len = MIN(bt_att_get_mtu(server->att), length + 2);
+ pdu = malloc(pdu_len);
+ if (!pdu)
+ return false;
+
+ data = new0(struct ind_data, 1);
+ if (!data) {
+ free(pdu);
+ return false;
+ }
+
+ data->callback = callback;
+ data->destroy = destroy;
+ data->user_data = user_data;
+
+ put_le16(handle, pdu);
+ memcpy(pdu + 2, value, length);
+
+ result = !!bt_att_send(server->att, BT_ATT_OP_HANDLE_VAL_IND, pdu,
+ pdu_len, conf_cb,
+ data, destroy_ind_data);
+ if (!result)
+ destroy_ind_data(data);
+
+ free(pdu);
+
+ return result;
+}
diff --git a/src/shared/gatt-server.h b/src/shared/gatt-server.h
index e3c4def..0e480e1 100644
--- a/src/shared/gatt-server.h
+++ b/src/shared/gatt-server.h
@@ -33,8 +33,20 @@ void bt_gatt_server_unref(struct bt_gatt_server *server);
typedef void (*bt_gatt_server_destroy_func_t)(void *user_data);
typedef void (*bt_gatt_server_debug_func_t)(const char *str, void *user_data);
+typedef void (*bt_gatt_server_conf_func_t)(void *user_data);
bool bt_gatt_server_set_debug(struct bt_gatt_server *server,
bt_gatt_server_debug_func_t callback,
void *user_data,
bt_gatt_server_destroy_func_t destroy);
+
+bool bt_gatt_server_send_notification(struct bt_gatt_server *server,
+ uint16_t handle, const uint8_t *value,
+ uint16_t length);
+
+bool bt_gatt_server_send_indication(struct bt_gatt_server *server,
+ uint16_t handle, const uint8_t *value,
+ uint16_t length,
+ bt_gatt_server_conf_func_t callback,
+ void *user_data,
+ bt_gatt_server_destroy_func_t destroy);
--
2.1.0.rc2.206.gedb03e5
This patch adds handling of incoming confirmation PDUs to shared/att.
The code makes sure that there is a pending indication and notifies
the associated callback.
Also included is a fix that automatically disconnects the io if a
response is received while no request is pending.
---
src/shared/att.c | 119 +++++++++++++++++++++++++++++++++++--------------------
1 file changed, 75 insertions(+), 44 deletions(-)
diff --git a/src/shared/att.c b/src/shared/att.c
index aa80cef..f043523 100644
--- a/src/shared/att.c
+++ b/src/shared/att.c
@@ -514,6 +514,46 @@ static void wakeup_writer(struct bt_att *att)
att->writer_active = true;
}
+static void disconn_handler(void *data, void *user_data)
+{
+ struct att_disconn *disconn = data;
+
+ if (disconn->removed)
+ return;
+
+ if (disconn->callback)
+ disconn->callback(disconn->user_data);
+}
+
+static bool disconnect_cb(struct io *io, void *user_data)
+{
+ struct bt_att *att = user_data;
+
+ io_destroy(att->io);
+ att->io = NULL;
+
+ util_debug(att->debug_callback, att->debug_data,
+ "Physical link disconnected");
+
+ bt_att_ref(att);
+ att->in_disconn = true;
+ queue_foreach(att->disconn_list, disconn_handler, NULL);
+ att->in_disconn = false;
+
+ if (att->need_disconn_cleanup) {
+ queue_remove_all(att->disconn_list, match_disconn_removed, NULL,
+ destroy_att_disconn);
+ att->need_disconn_cleanup = false;
+ }
+
+ bt_att_cancel_all(att);
+ bt_att_unregister_all(att);
+
+ bt_att_unref(att);
+
+ return false;
+}
+
static void handle_rsp(struct bt_att *att, uint8_t opcode, uint8_t *pdu,
ssize_t pdu_len)
{
@@ -523,13 +563,19 @@ static void handle_rsp(struct bt_att *att, uint8_t opcode, uint8_t *pdu,
uint8_t *rsp_pdu = NULL;
uint16_t rsp_pdu_len = 0;
- /* If no request is pending, then the response is unexpected. */
+ /*
+ * If no request is pending, then the response is unexpected. Disconnect
+ * the bearer.
+ */
if (!op) {
- wakeup_writer(att);
+ util_debug(att->debug_callback, att->debug_data,
+ "Received unexpected ATT response");
+ disconnect_cb(att->io, att);
return;
}
- /* If the received response doesn't match the pending request, or if
+ /*
+ * If the received response doesn't match the pending request, or if
* the request is malformed, end the current request with failure.
*/
if (opcode == BT_ATT_OP_ERROR_RSP) {
@@ -568,6 +614,30 @@ done:
wakeup_writer(att);
}
+static void handle_conf(struct bt_att *att, uint8_t *pdu, ssize_t pdu_len)
+{
+ struct att_send_op *op = att->pending_ind;
+
+ /*
+ * Disconnect the bearer if the confirmation is unexpected or the PDU is
+ * invalid.
+ */
+ if (!op || pdu_len) {
+ util_debug(att->debug_callback, att->debug_data,
+ "Received unexpected/invalid ATT confirmation");
+ disconnect_cb(att->io, att);
+ return;
+ }
+
+ if (op->callback)
+ op->callback(BT_ATT_OP_HANDLE_VAL_CONF, NULL, 0, op->user_data);
+
+ destroy_att_send_op(op);
+ att->pending_ind = NULL;
+
+ wakeup_writer(att);
+}
+
struct notify_data {
uint8_t opcode;
uint8_t *pdu;
@@ -651,46 +721,6 @@ static void handle_notify(struct bt_att *att, uint8_t opcode, uint8_t *pdu,
respond_not_supported(att, opcode);
}
-static void disconn_handler(void *data, void *user_data)
-{
- struct att_disconn *disconn = data;
-
- if (disconn->removed)
- return;
-
- if (disconn->callback)
- disconn->callback(disconn->user_data);
-}
-
-static bool disconnect_cb(struct io *io, void *user_data)
-{
- struct bt_att *att = user_data;
-
- io_destroy(att->io);
- att->io = NULL;
-
- util_debug(att->debug_callback, att->debug_data,
- "Physical link disconnected");
-
- bt_att_ref(att);
- att->in_disconn = true;
- queue_foreach(att->disconn_list, disconn_handler, NULL);
- att->in_disconn = false;
-
- if (att->need_disconn_cleanup) {
- queue_remove_all(att->disconn_list, match_disconn_removed, NULL,
- destroy_att_disconn);
- att->need_disconn_cleanup = false;
- }
-
- bt_att_cancel_all(att);
- bt_att_unregister_all(att);
-
- bt_att_unref(att);
-
- return false;
-}
-
static bool can_read_data(struct io *io, void *user_data)
{
struct bt_att *att = user_data;
@@ -720,7 +750,8 @@ static bool can_read_data(struct io *io, void *user_data)
break;
case ATT_OP_TYPE_CONF:
util_debug(att->debug_callback, att->debug_data,
- "ATT opcode cannot be handled: 0x%02x", opcode);
+ "ATT confirmation received: 0x%02x", opcode);
+ handle_conf(att, pdu + 1, bytes_read - 1);
break;
case ATT_OP_TYPE_REQ:
/*
--
2.1.0.rc2.206.gedb03e5
This patch implements the "Find Information" request for the GATT
server role.
---
src/shared/gatt-server.c | 156 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 155 insertions(+), 1 deletion(-)
diff --git a/src/shared/gatt-server.c b/src/shared/gatt-server.c
index ddb0be1..ddb714d 100644
--- a/src/shared/gatt-server.c
+++ b/src/shared/gatt-server.c
@@ -59,6 +59,7 @@ struct bt_gatt_server {
unsigned int mtu_id;
unsigned int read_by_grp_type_id;
unsigned int read_by_type_id;
+ unsigned int find_info_id;
struct async_read_op *pending_read_op;
@@ -75,11 +76,12 @@ static void bt_gatt_server_free(struct bt_gatt_server *server)
bt_att_unregister(server->att, server->mtu_id);
bt_att_unregister(server->att, server->read_by_grp_type_id);
bt_att_unregister(server->att, server->read_by_type_id);
- bt_att_unref(server->att);
+ bt_att_unregister(server->att, server->find_info_id);
if (server->pending_read_op)
server->pending_read_op->server = NULL;
+ bt_att_unref(server->att);
free(server);
}
@@ -480,6 +482,150 @@ error:
NULL, NULL, NULL);
}
+static void put_uuid_le(const bt_uuid_t *src, void *dst)
+{
+ bt_uuid_t uuid;
+
+ switch (src->type) {
+ case BT_UUID16:
+ put_le16(src->value.u16, dst);
+ break;
+ case BT_UUID128:
+ bswap_128(&src->value.u128, dst);
+ break;
+ case BT_UUID32:
+ bt_uuid_to_uuid128(src, &uuid);
+ bswap_128(&uuid.value.u128, dst);
+ break;
+ default:
+ break;
+ }
+}
+
+static bool encode_find_info_rsp(struct gatt_db *db, struct queue *q,
+ uint16_t mtu,
+ uint8_t *pdu, uint16_t *len)
+{
+ uint16_t handle;
+ struct gatt_db_attribute *attr;
+ const bt_uuid_t *type;
+ int uuid_len, cur_uuid_len;
+ int iter = 0;
+
+ *len = 0;
+
+ while (queue_peek_head(q)) {
+ attr = queue_pop_head(q);
+ handle = gatt_db_attribute_get_handle(attr);
+ type = gatt_db_attribute_get_type(attr);
+ if (!handle || !type)
+ return false;
+
+ cur_uuid_len = bt_uuid_len(type);
+
+ if (iter == 0) {
+ switch (cur_uuid_len) {
+ case 2:
+ uuid_len = 2;
+ pdu[0] = 0x01;
+ break;
+ case 4:
+ case 16:
+ uuid_len = 16;
+ pdu[0] = 0x02;
+ break;
+ default:
+ return false;
+ }
+
+ iter++;
+ } else if (cur_uuid_len != uuid_len)
+ break;
+
+ if (iter + uuid_len + 2 > mtu - 1)
+ break;
+
+ put_le16(handle, pdu + iter);
+ put_uuid_le(type, pdu + iter + 2);
+
+ iter += uuid_len + 2;
+ }
+
+ *len = iter;
+
+ return true;
+}
+
+static void find_info_cb(uint8_t opcode, const void *pdu,
+ uint16_t length, void *user_data)
+{
+ struct bt_gatt_server *server = user_data;
+ uint16_t start, end;
+ uint16_t mtu = bt_att_get_mtu(server->att);
+ uint8_t rsp_pdu[mtu];
+ uint16_t rsp_len;
+ uint8_t rsp_opcode;
+ uint8_t ecode = 0;
+ uint16_t ehandle = 0;
+ struct queue *q = NULL;
+
+ if (length != 4) {
+ ecode = BT_ATT_ERROR_INVALID_PDU;
+ goto error;
+ }
+
+ q = queue_new();
+ if (!q) {
+ ecode = BT_ATT_ERROR_INSUFFICIENT_RESOURCES;
+ goto error;
+ }
+
+ start = get_le16(pdu);
+ end = get_le16(pdu + 2);
+
+ util_debug(server->debug_callback, server->debug_data,
+ "Find Info - start: 0x%04x end: 0x%04x",
+ start, end);
+
+ if (!start || !end) {
+ ecode = BT_ATT_ERROR_INVALID_HANDLE;
+ goto error;
+ }
+
+ ehandle = start;
+
+ if (start > end) {
+ ecode = BT_ATT_ERROR_INVALID_HANDLE;
+ goto error;
+ }
+
+ gatt_db_find_information(server->db, start, end, q);
+
+ if (queue_isempty(q)) {
+ ecode = BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND;
+ goto error;
+ }
+
+ if (!encode_find_info_rsp(server->db, q, mtu, rsp_pdu, &rsp_len)) {
+ ecode = BT_ATT_ERROR_UNLIKELY;
+ goto error;
+ }
+
+ rsp_opcode = BT_ATT_OP_FIND_INFO_RSP;
+
+ goto done;
+
+error:
+ rsp_opcode = BT_ATT_OP_ERROR_RSP;
+ rsp_len = 4;
+ encode_error_rsp(opcode, ehandle, ecode, rsp_pdu);
+
+done:
+ queue_destroy(q, NULL);
+ bt_att_send(server->att, rsp_opcode, rsp_pdu, rsp_len,
+ NULL, NULL, NULL);
+}
+
static void exchange_mtu_cb(uint8_t opcode, const void *pdu,
uint16_t length, void *user_data)
{
@@ -536,6 +682,14 @@ static bool gatt_server_register_att_handlers(struct bt_gatt_server *server)
if (!server->read_by_type_id)
return false;
+ /* Find Information */
+ server->find_info_id = bt_att_register(server->att,
+ BT_ATT_OP_FIND_INFO_REQ,
+ find_info_cb,
+ server, NULL);
+ if (!server->find_info_id)
+ return false;
+
return true;
}
--
2.1.0.rc2.206.gedb03e5
This patch implements the "Read Blob" request for the GATT server role.
---
src/shared/gatt-server.c | 100 ++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 85 insertions(+), 15 deletions(-)
diff --git a/src/shared/gatt-server.c b/src/shared/gatt-server.c
index 707248a..16b6460 100644
--- a/src/shared/gatt-server.c
+++ b/src/shared/gatt-server.c
@@ -68,6 +68,7 @@ struct bt_gatt_server {
unsigned int write_id;
unsigned int write_cmd_id;
unsigned int read_id;
+ unsigned int read_blob_id;
struct async_read_op *pending_read_op;
struct async_write_op *pending_write_op;
@@ -89,6 +90,7 @@ static void bt_gatt_server_free(struct bt_gatt_server *server)
bt_att_unregister(server->att, server->write_id);
bt_att_unregister(server->att, server->write_cmd_id);
bt_att_unregister(server->att, server->read_id);
+ bt_att_unregister(server->att, server->read_blob_id);
if (server->pending_read_op)
server->pending_read_op->server = NULL;
@@ -749,12 +751,36 @@ error:
NULL, NULL, NULL);
}
+static uint8_t get_read_rsp_opcode(uint8_t opcode)
+{
+
+ switch (opcode) {
+ case BT_ATT_OP_READ_REQ:
+ return BT_ATT_OP_READ_RSP;
+ case BT_ATT_OP_READ_BLOB_REQ:
+ return BT_ATT_OP_READ_BLOB_RSP;
+ default:
+ /*
+ * Should never happen
+ *
+ * TODO: It would be nice to have a debug-mode assert macro
+ * for development builds. This way bugs could be easily catched
+ * during development and there would be self documenting code
+ * that wouldn't be crash release builds.
+ */
+ return 0;
+ }
+
+ return 0;
+}
+
static void read_complete_cb(struct gatt_db_attribute *attr, int err,
const uint8_t *value, size_t len,
void *user_data)
{
struct async_read_op *op = user_data;
struct bt_gatt_server *server = op->server;
+ uint8_t rsp_opcode;
uint16_t mtu;
uint16_t handle;
@@ -777,32 +803,24 @@ static void read_complete_cb(struct gatt_db_attribute *attr, int err,
return;
}
- /* TODO: Send Read Blob response based on the request */
+ rsp_opcode = get_read_rsp_opcode(op->opcode);
- bt_att_send(server->att, BT_ATT_OP_READ_RSP, len ? value : NULL,
+ bt_att_send(server->att, rsp_opcode, len ? value : NULL,
MIN((unsigned) mtu - 1, len),
NULL, NULL, NULL);
async_read_op_destroy(op);
}
-static void read_cb(uint8_t opcode, const void *pdu,
- uint16_t length, void *user_data)
+static void handle_read_req(struct bt_gatt_server *server, uint8_t opcode,
+ uint16_t handle,
+ uint16_t offset)
{
- struct bt_gatt_server *server = user_data;
- uint16_t mtu = bt_att_get_mtu(server->att);
uint8_t error_pdu[4];
- uint16_t handle = 0;
struct gatt_db_attribute *attr;
uint8_t ecode;
uint32_t perm;
struct async_read_op *op = NULL;
- if (length != 2) {
- ecode = BT_ATT_ERROR_INVALID_PDU;
- goto error;
- }
-
- handle = get_le16(pdu);
attr = gatt_db_get_attribute(server->db, handle);
if (!attr) {
ecode = BT_ATT_ERROR_INVALID_HANDLE;
@@ -810,7 +828,9 @@ static void read_cb(uint8_t opcode, const void *pdu,
}
util_debug(server->debug_callback, server->debug_data,
- "Read - handle: 0x%04x", handle);
+ "Read %sReq - handle: 0x%04x",
+ opcode == BT_ATT_OP_READ_BLOB_REQ ? "Blob " : "",
+ handle);
if (!gatt_db_attribute_get_permissions(attr, &perm)) {
ecode = BT_ATT_ERROR_INVALID_HANDLE;
@@ -837,7 +857,8 @@ static void read_cb(uint8_t opcode, const void *pdu,
op->server = server;
server->pending_read_op = op;
- if (gatt_db_attribute_read(attr, 0, opcode, NULL, read_complete_cb, op))
+ if (gatt_db_attribute_read(attr, offset, opcode, NULL,
+ read_complete_cb, op))
return;
ecode = BT_ATT_ERROR_UNLIKELY;
@@ -851,6 +872,47 @@ error:
NULL);
}
+static void read_cb(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) {
+ uint8_t pdu[4];
+
+ encode_error_rsp(opcode, 0, BT_ATT_ERROR_INVALID_PDU, pdu);
+ bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, pdu, 4, NULL,
+ NULL, NULL);
+ return;
+ }
+
+ handle = get_le16(pdu);
+
+ handle_read_req(server, opcode, handle, 0);
+}
+
+static void read_blob_cb(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) {
+ uint8_t pdu[4];
+
+ encode_error_rsp(opcode, 0, BT_ATT_ERROR_INVALID_PDU, pdu);
+ bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, pdu, 4, NULL,
+ NULL, NULL);
+ return;
+ }
+
+ handle = get_le16(pdu);
+ offset = get_le16(pdu + 2);
+
+ handle_read_req(server, opcode, handle, offset);
+}
+
static void exchange_mtu_cb(uint8_t opcode, const void *pdu,
uint16_t length, void *user_data)
{
@@ -936,6 +998,14 @@ static bool gatt_server_register_att_handlers(struct bt_gatt_server *server)
if (!server->read_id)
return false;
+ /* Read Blob Request */
+ server->read_blob_id = bt_att_register(server->att,
+ BT_ATT_OP_READ_BLOB_REQ,
+ read_blob_cb,
+ server, NULL);
+ if (!server->read_blob_id)
+ return false;
+
return true;
}
--
2.1.0.rc2.206.gedb03e5
This patch implements the "Read" request for the GATT server role.
---
src/shared/gatt-server.c | 111 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 111 insertions(+)
diff --git a/src/shared/gatt-server.c b/src/shared/gatt-server.c
index 34cab05..707248a 100644
--- a/src/shared/gatt-server.c
+++ b/src/shared/gatt-server.c
@@ -67,6 +67,7 @@ struct bt_gatt_server {
unsigned int find_info_id;
unsigned int write_id;
unsigned int write_cmd_id;
+ unsigned int read_id;
struct async_read_op *pending_read_op;
struct async_write_op *pending_write_op;
@@ -87,6 +88,7 @@ static void bt_gatt_server_free(struct bt_gatt_server *server)
bt_att_unregister(server->att, server->find_info_id);
bt_att_unregister(server->att, server->write_id);
bt_att_unregister(server->att, server->write_cmd_id);
+ bt_att_unregister(server->att, server->read_id);
if (server->pending_read_op)
server->pending_read_op->server = NULL;
@@ -747,6 +749,108 @@ error:
NULL, NULL, NULL);
}
+static void read_complete_cb(struct gatt_db_attribute *attr, int err,
+ const uint8_t *value, size_t len,
+ void *user_data)
+{
+ struct async_read_op *op = user_data;
+ struct bt_gatt_server *server = op->server;
+ uint16_t mtu;
+ uint16_t handle;
+
+ if (!server) {
+ async_read_op_destroy(op);
+ return;
+ }
+
+ mtu = bt_att_get_mtu(server->att);
+ handle = gatt_db_attribute_get_handle(attr);
+
+ if (err) {
+ uint8_t pdu[4];
+ uint8_t att_ecode = att_ecode_from_error(err);
+
+ encode_error_rsp(op->opcode, handle, att_ecode, pdu);
+ bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, pdu, 4, NULL,
+ NULL, NULL);
+ async_read_op_destroy(op);
+ return;
+ }
+
+ /* TODO: Send Read Blob response based on the request */
+
+ bt_att_send(server->att, BT_ATT_OP_READ_RSP, len ? value : NULL,
+ MIN((unsigned) mtu - 1, len),
+ NULL, NULL, NULL);
+ async_read_op_destroy(op);
+}
+
+static void read_cb(uint8_t opcode, const void *pdu,
+ uint16_t length, void *user_data)
+{
+ struct bt_gatt_server *server = user_data;
+ uint16_t mtu = bt_att_get_mtu(server->att);
+ uint8_t error_pdu[4];
+ uint16_t handle = 0;
+ struct gatt_db_attribute *attr;
+ uint8_t ecode;
+ uint32_t perm;
+ struct async_read_op *op = NULL;
+
+ if (length != 2) {
+ ecode = BT_ATT_ERROR_INVALID_PDU;
+ goto error;
+ }
+
+ handle = get_le16(pdu);
+ attr = gatt_db_get_attribute(server->db, handle);
+ if (!attr) {
+ ecode = BT_ATT_ERROR_INVALID_HANDLE;
+ goto error;
+ }
+
+ util_debug(server->debug_callback, server->debug_data,
+ "Read - handle: 0x%04x", handle);
+
+ if (!gatt_db_attribute_get_permissions(attr, &perm)) {
+ ecode = BT_ATT_ERROR_INVALID_HANDLE;
+ goto error;
+ }
+
+ if (perm && !(perm & BT_ATT_PERM_READ)) {
+ ecode = BT_ATT_ERROR_READ_NOT_PERMITTED;
+ goto error;
+ }
+
+ if (server->pending_read_op) {
+ ecode = BT_ATT_ERROR_UNLIKELY;
+ goto error;
+ }
+
+ op = new0(struct async_read_op, 1);
+ if (!op) {
+ ecode = BT_ATT_ERROR_INSUFFICIENT_RESOURCES;
+ goto error;
+ }
+
+ op->opcode = opcode;
+ op->server = server;
+ server->pending_read_op = op;
+
+ if (gatt_db_attribute_read(attr, 0, opcode, NULL, read_complete_cb, op))
+ return;
+
+ ecode = BT_ATT_ERROR_UNLIKELY;
+
+error:
+ if (op)
+ async_read_op_destroy(op);
+
+ encode_error_rsp(opcode, handle, ecode, error_pdu);
+ bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, error_pdu, 4, NULL, NULL,
+ NULL);
+}
+
static void exchange_mtu_cb(uint8_t opcode, const void *pdu,
uint16_t length, void *user_data)
{
@@ -825,6 +929,13 @@ static bool gatt_server_register_att_handlers(struct bt_gatt_server *server)
if (!server->write_cmd_id)
return false;
+ /* Read Request */
+ server->read_id = bt_att_register(server->att, BT_ATT_OP_READ_REQ,
+ read_cb,
+ server, NULL);
+ if (!server->read_id)
+ return false;
+
return true;
}
--
2.1.0.rc2.206.gedb03e5
This patch implements the ATT protocol "Read By Type" request for
shared/gatt-server. Logic is implemented that allows asynchronous
reading of non-standard attribute values via the registered read and
read completion callbacks.
---
src/shared/gatt-server.c | 246 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 243 insertions(+), 3 deletions(-)
diff --git a/src/shared/gatt-server.c b/src/shared/gatt-server.c
index 18f82c4..ddb0be1 100644
--- a/src/shared/gatt-server.c
+++ b/src/shared/gatt-server.c
@@ -40,6 +40,16 @@
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
+struct async_read_op {
+ struct bt_gatt_server *server;
+ uint8_t opcode;
+ bool done;
+ uint8_t *pdu;
+ size_t pdu_len;
+ size_t value_len;
+ struct queue *db_data;
+};
+
struct bt_gatt_server {
struct gatt_db *db;
struct bt_att *att;
@@ -48,6 +58,9 @@ struct bt_gatt_server {
unsigned int mtu_id;
unsigned int read_by_grp_type_id;
+ unsigned int read_by_type_id;
+
+ struct async_read_op *pending_read_op;
bt_gatt_server_debug_func_t debug_callback;
bt_gatt_server_destroy_func_t debug_destroy;
@@ -61,11 +74,23 @@ static void bt_gatt_server_free(struct bt_gatt_server *server)
bt_att_unregister(server->att, server->mtu_id);
bt_att_unregister(server->att, server->read_by_grp_type_id);
+ bt_att_unregister(server->att, server->read_by_type_id);
bt_att_unref(server->att);
+ if (server->pending_read_op)
+ server->pending_read_op->server = NULL;
+
free(server);
}
+static uint8_t att_ecode_from_error(int err)
+{
+ if (err < 0 || err > UINT8_MAX)
+ return 0xff;
+
+ return err;
+}
+
static void encode_error_rsp(uint8_t opcode, uint16_t handle, uint8_t ecode,
uint8_t pdu[4])
{
@@ -136,14 +161,15 @@ static bool encode_read_by_grp_type_rsp(struct gatt_db *db, struct queue *q,
* value is seen.
*/
if (iter == 0) {
- data_val_len = value.iov_len;
+ data_val_len = MIN(MIN((unsigned)mtu - 6, 251),
+ value.iov_len);
pdu[0] = data_val_len + 4;
iter++;
} else if (value.iov_len != data_val_len)
break;
/* Stop if this unit would surpass the MTU */
- if (iter + data_val_len + 4 > mtu)
+ if (iter + data_val_len + 4 > mtu - 1)
break;
gatt_db_attribute_get_service_handles(attrib, &start_handle,
@@ -151,7 +177,7 @@ static bool encode_read_by_grp_type_rsp(struct gatt_db *db, struct queue *q,
put_le16(start_handle, pdu + iter);
put_le16(end_handle, pdu + iter + 2);
- memcpy(pdu + iter + 4, value.iov_base, value.iov_len);
+ memcpy(pdu + iter + 4, value.iov_base, data_val_len);
iter += data_val_len + 4;
}
@@ -248,6 +274,212 @@ done:
NULL, NULL, NULL);
}
+static void async_read_op_destroy(struct async_read_op *op)
+{
+ if (op->server)
+ op->server->pending_read_op = NULL;
+
+ queue_destroy(op->db_data, NULL);
+ free(op->pdu);
+ free(op);
+}
+
+static void process_read_by_type(struct async_read_op *op);
+
+static void read_by_type_read_complete_cb(struct gatt_db_attribute *attr,
+ int err, const uint8_t *value,
+ size_t len, void *user_data)
+{
+ struct async_read_op *op = user_data;
+ struct bt_gatt_server *server = op->server;
+ uint16_t mtu;
+ uint16_t handle;
+
+ if (!server) {
+ async_read_op_destroy(op);
+ return;
+ }
+
+ mtu = bt_att_get_mtu(server->att);
+ handle = gatt_db_attribute_get_handle(attr);
+
+ /* Terminate the operation if there was an error */
+ if (err) {
+ uint8_t pdu[4];
+ uint8_t att_ecode = att_ecode_from_error(err);
+
+ encode_error_rsp(BT_ATT_OP_READ_BY_TYPE_REQ, handle, att_ecode,
+ pdu);
+ bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, pdu, 4, NULL,
+ NULL, NULL);
+ async_read_op_destroy(op);
+ return;
+ }
+
+ if (op->pdu_len == 0) {
+ op->value_len = MIN(MIN((unsigned) mtu - 4, 253), len);
+ op->pdu[0] = op->value_len + 2;
+ op->pdu_len++;
+ } else if (len != op->value_len) {
+ op->done = true;
+ goto done;
+ }
+
+ /* Stop if this would surpass the MTU */
+ if (op->pdu_len + op->value_len + 2 > (unsigned) mtu - 1) {
+ op->done = true;
+ goto done;
+ }
+
+ /* Encode the current value */
+ put_le16(handle, op->pdu + op->pdu_len);
+ memcpy(op->pdu + op->pdu_len + 2, value, op->value_len);
+
+ op->pdu_len += op->value_len + 2;
+
+ if (op->pdu_len == (unsigned) mtu - 1)
+ op->done = true;
+
+done:
+ process_read_by_type(op);
+}
+
+static void process_read_by_type(struct async_read_op *op)
+{
+ struct bt_gatt_server *server = op->server;
+ uint8_t rsp_opcode;
+ uint8_t rsp_len;
+ uint8_t ecode;
+ uint16_t ehandle;
+ struct gatt_db_attribute *attr;
+ uint32_t perm;
+
+ attr = queue_pop_head(op->db_data);
+
+ if (op->done || !attr) {
+ rsp_opcode = BT_ATT_OP_READ_BY_TYPE_RSP;
+ rsp_len = op->pdu_len;
+ goto done;
+ }
+
+ if (!gatt_db_attribute_get_permissions(attr, &perm)) {
+ ecode = BT_ATT_ERROR_UNLIKELY;
+ goto error;
+ }
+
+ /*
+ * Check for the READ access permission. Encryption,
+ * authentication, and authorization permissions need to be
+ * checked by the read handler, since bt_att is agnostic to
+ * connection type and doesn't have security information on it.
+ */
+ if (perm && !(perm & BT_ATT_PERM_READ)) {
+ ecode = BT_ATT_ERROR_READ_NOT_PERMITTED;
+ goto error;
+ }
+
+ if (gatt_db_attribute_read(attr, 0, op->opcode, NULL,
+ read_by_type_read_complete_cb, op))
+ return;
+
+ ecode = BT_ATT_ERROR_UNLIKELY;
+
+error:
+ ehandle = gatt_db_attribute_get_handle(attr);
+ rsp_opcode = BT_ATT_OP_ERROR_RSP;
+ rsp_len = 4;
+ encode_error_rsp(BT_ATT_OP_READ_BY_TYPE_REQ, ehandle, ecode, op->pdu);
+
+done:
+ bt_att_send(server->att, rsp_opcode, op->pdu, rsp_len, NULL,
+ NULL, NULL);
+ async_read_op_destroy(op);
+}
+
+static void read_by_type_cb(uint8_t opcode, const void *pdu,
+ uint16_t length, void *user_data)
+{
+ struct bt_gatt_server *server = user_data;
+ uint16_t start, end;
+ bt_uuid_t type;
+ uint8_t rsp_pdu[4];
+ uint16_t ehandle = 0;
+ uint8_t ecode;
+ struct queue *q = NULL;
+ struct async_read_op *op;
+
+ if (length != 6 && length != 20) {
+ ecode = BT_ATT_ERROR_INVALID_PDU;
+ goto error;
+ }
+
+ q = queue_new();
+ if (!q) {
+ ecode = BT_ATT_ERROR_INSUFFICIENT_RESOURCES;
+ goto error;
+ }
+
+ start = get_le16(pdu);
+ end = get_le16(pdu + 2);
+ get_uuid_le(pdu + 4, length - 4, &type);
+
+ util_debug(server->debug_callback, server->debug_data,
+ "Read By Type - start: 0x%04x end: 0x%04x",
+ start, end);
+
+ if (!start || !end) {
+ ecode = BT_ATT_ERROR_INVALID_HANDLE;
+ goto error;
+ }
+
+ ehandle = start;
+
+ if (start > end) {
+ ecode = BT_ATT_ERROR_INVALID_HANDLE;
+ goto error;
+ }
+
+ gatt_db_read_by_type(server->db, start, end, type, q);
+
+ if (queue_isempty(q)) {
+ ecode = BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND;
+ goto error;
+ }
+
+ if (server->pending_read_op) {
+ ecode = BT_ATT_ERROR_UNLIKELY;
+ goto error;
+ }
+
+ op = new0(struct async_read_op, 1);
+ if (!op) {
+ ecode = BT_ATT_ERROR_INSUFFICIENT_RESOURCES;
+ goto error;
+ }
+
+ op->pdu = malloc(bt_att_get_mtu(server->att));
+ if (!op->pdu) {
+ free(op);
+ ecode = BT_ATT_ERROR_INSUFFICIENT_RESOURCES;
+ goto error;
+ }
+
+ op->opcode = opcode;
+ op->server = server;
+ op->db_data = q;
+ server->pending_read_op = op;
+
+ process_read_by_type(op);
+
+ return;
+
+error:
+ encode_error_rsp(opcode, ehandle, ecode, rsp_pdu);
+ queue_destroy(q, NULL);
+ bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, rsp_pdu, 4,
+ NULL, NULL, NULL);
+}
+
static void exchange_mtu_cb(uint8_t opcode, const void *pdu,
uint16_t length, void *user_data)
{
@@ -296,6 +528,14 @@ static bool gatt_server_register_att_handlers(struct bt_gatt_server *server)
if (!server->read_by_grp_type_id)
return false;
+ /* Read By Type */
+ server->read_by_type_id = bt_att_register(server->att,
+ BT_ATT_OP_READ_BY_TYPE_REQ,
+ read_by_type_cb,
+ server, NULL);
+ if (!server->read_by_type_id)
+ return false;
+
return true;
}
--
2.1.0.rc2.206.gedb03e5
This patch implements the "Write" request and command for the GATT
server role. Writes are delegated to the attribute's write handler,
which is also responsible for performing certain permission checks.
---
src/shared/gatt-server.c | 135 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 135 insertions(+)
diff --git a/src/shared/gatt-server.c b/src/shared/gatt-server.c
index ddb714d..34cab05 100644
--- a/src/shared/gatt-server.c
+++ b/src/shared/gatt-server.c
@@ -50,6 +50,11 @@ struct async_read_op {
struct queue *db_data;
};
+struct async_write_op {
+ struct bt_gatt_server *server;
+ uint8_t opcode;
+};
+
struct bt_gatt_server {
struct gatt_db *db;
struct bt_att *att;
@@ -60,8 +65,11 @@ struct bt_gatt_server {
unsigned int read_by_grp_type_id;
unsigned int read_by_type_id;
unsigned int find_info_id;
+ unsigned int write_id;
+ unsigned int write_cmd_id;
struct async_read_op *pending_read_op;
+ struct async_write_op *pending_write_op;
bt_gatt_server_debug_func_t debug_callback;
bt_gatt_server_destroy_func_t debug_destroy;
@@ -77,10 +85,15 @@ static void bt_gatt_server_free(struct bt_gatt_server *server)
bt_att_unregister(server->att, server->read_by_grp_type_id);
bt_att_unregister(server->att, server->read_by_type_id);
bt_att_unregister(server->att, server->find_info_id);
+ bt_att_unregister(server->att, server->write_id);
+ bt_att_unregister(server->att, server->write_cmd_id);
if (server->pending_read_op)
server->pending_read_op->server = NULL;
+ if (server->pending_write_op)
+ server->pending_write_op->server = NULL;
+
bt_att_unref(server->att);
free(server);
}
@@ -626,6 +639,114 @@ done:
NULL, NULL, NULL);
}
+static void async_write_op_destroy(struct async_write_op *op)
+{
+ if (op->server)
+ op->server->pending_write_op = NULL;
+
+ free(op);
+}
+
+static void write_complete_cb(struct gatt_db_attribute *attr, int err,
+ void *user_data)
+{
+ struct async_write_op *op = user_data;
+ struct bt_gatt_server *server = op->server;
+ uint16_t handle;
+
+ if (!server || op->opcode == BT_ATT_OP_WRITE_CMD) {
+ async_write_op_destroy(op);
+ return;
+ }
+
+ handle = gatt_db_attribute_get_handle(attr);
+
+ if (err) {
+ uint8_t rsp_pdu[4];
+ uint8_t att_ecode = att_ecode_from_error(err);
+
+ encode_error_rsp(op->opcode, handle, att_ecode, rsp_pdu);
+ bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, rsp_pdu, 4,
+ NULL, NULL, NULL);
+ } else {
+ bt_att_send(server->att, BT_ATT_OP_WRITE_RSP, NULL, 0,
+ NULL, NULL, NULL);
+ }
+
+ async_write_op_destroy(op);
+}
+
+static void write_cb(uint8_t opcode, const void *pdu,
+ uint16_t length, void *user_data)
+{
+ struct bt_gatt_server *server = user_data;
+ struct gatt_db_attribute *attr;
+ uint16_t handle = 0;
+ uint8_t rsp_pdu[4];
+ struct async_write_op *op = NULL;
+ uint8_t ecode;
+ uint32_t perm;
+
+ if (length < 2) {
+ ecode = BT_ATT_ERROR_INVALID_PDU;
+ goto error;
+ }
+
+ handle = get_le16(pdu);
+ attr = gatt_db_get_attribute(server->db, handle);
+ if (!attr) {
+ ecode = BT_ATT_ERROR_INVALID_HANDLE;
+ goto error;
+ }
+
+ util_debug(server->debug_callback, server->debug_data,
+ "Write %s - handle: 0x%04x",
+ (opcode == BT_ATT_OP_WRITE_REQ) ? "Req" : "Cmd",
+ handle);
+
+ if (!gatt_db_attribute_get_permissions(attr, &perm)) {
+ ecode = BT_ATT_ERROR_INVALID_HANDLE;
+ goto error;
+ }
+
+ if (!(perm & BT_ATT_PERM_WRITE)) {
+ ecode = BT_ATT_ERROR_WRITE_NOT_PERMITTED;
+ goto error;
+ }
+
+ if (server->pending_write_op) {
+ ecode = BT_ATT_ERROR_UNLIKELY;
+ goto error;
+ }
+
+ op = new0(struct async_write_op, 1);
+ if (!op) {
+ ecode = BT_ATT_ERROR_INSUFFICIENT_RESOURCES;
+ goto error;
+ }
+
+ op->server = server;
+ op->opcode = opcode;
+ server->pending_write_op = op;
+
+ if (gatt_db_attribute_write(attr, 0, pdu + 2, length - 2, opcode,
+ NULL, write_complete_cb, op))
+ return;
+
+ if (op)
+ async_write_op_destroy(op);
+
+ ecode = BT_ATT_ERROR_UNLIKELY;
+
+error:
+ if (opcode == BT_ATT_OP_WRITE_CMD)
+ return;
+
+ encode_error_rsp(opcode, handle, ecode, rsp_pdu);
+ bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, rsp_pdu, 4,
+ NULL, NULL, NULL);
+}
+
static void exchange_mtu_cb(uint8_t opcode, const void *pdu,
uint16_t length, void *user_data)
{
@@ -690,6 +811,20 @@ static bool gatt_server_register_att_handlers(struct bt_gatt_server *server)
if (!server->find_info_id)
return false;
+ /* Write Request */
+ server->write_id = bt_att_register(server->att, BT_ATT_OP_WRITE_REQ,
+ write_cb,
+ server, NULL);
+ if (!server->write_id)
+ return false;
+
+ /* Write Command */
+ server->write_cmd_id = bt_att_register(server->att, BT_ATT_OP_WRITE_CMD,
+ write_cb,
+ server, NULL);
+ if (!server->write_cmd_id)
+ return false;
+
return true;
}
--
2.1.0.rc2.206.gedb03e5