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 *)
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 | 20 +-
src/shared/att.c | 119 +++--
src/shared/gatt-client.c | 6 +-
src/shared/gatt-server.c | 1231 +++++++++++++++++++++++++++++++++++++++++++---
src/shared/gatt-server.h | 15 +
5 files changed, 1264 insertions(+), 127 deletions(-)
--
2.1.0.rc2.206.gedb03e5
Hi Luiz,
> On Tue, Nov 11, 2014 at 3:42 AM, Luiz Augusto von Dentz <[email protected]> wrote:
> Hi Arman,
>
> On Mon, Nov 10, 2014 at 6:26 PM, Arman Uguray <[email protected]> wrote:
>>>> diff --git a/src/shared/gatt-server.h b/src/shared/gatt-server.h
>>>> index 0e480e1..a95613c 100644
>>>> --- a/src/shared/gatt-server.h
>>>> +++ b/src/shared/gatt-server.h
>>>> @@ -40,6 +40,9 @@ bool bt_gatt_server_set_debug(struct bt_gatt_server *server,
>>>> void *user_data,
>>>> bt_gatt_server_destroy_func_t destroy);
>>>>
>>>> +bool bt_gatt_server_set_max_prep_queue_length(struct bt_gatt_server *server,
>>>> + unsigned int length);
>>>
>>> Perhaps we should leave this API for later, Im not really sure if this
>>> will be ever used except perhaps for the TS in that case perhaps we
>>> should find a shorter name.
>>>
>>
>> In our case would it even make sense to have a maximum queue length? I
>> only added because there is an error for it and an application with
>> memory constraints may want to limit the size perhaps? Anyway, for now
>> I can set this to something arbitrary for v2.
>
> Perhaps having some arbitrary limit is good enough, not exactly
> because of memory constraints since each entry should consume just a
> few bytes but we don't want to timeout so I suggest we queue based on
> how long we should wait the result to be called.
>
OK, that makes a lot of sense. For now we can keep the arbitrary limit
and see how well it works.
> --
> Luiz Augusto von Dentz
Cheers,
Arman
Hi Arman,
On Mon, Nov 10, 2014 at 6:26 PM, Arman Uguray <[email protected]> wrote:
>>> diff --git a/src/shared/gatt-server.h b/src/shared/gatt-server.h
>>> index 0e480e1..a95613c 100644
>>> --- a/src/shared/gatt-server.h
>>> +++ b/src/shared/gatt-server.h
>>> @@ -40,6 +40,9 @@ bool bt_gatt_server_set_debug(struct bt_gatt_server *server,
>>> void *user_data,
>>> bt_gatt_server_destroy_func_t destroy);
>>>
>>> +bool bt_gatt_server_set_max_prep_queue_length(struct bt_gatt_server *server,
>>> + unsigned int length);
>>
>> Perhaps we should leave this API for later, Im not really sure if this
>> will be ever used except perhaps for the TS in that case perhaps we
>> should find a shorter name.
>>
>
> In our case would it even make sense to have a maximum queue length? I
> only added because there is an error for it and an application with
> memory constraints may want to limit the size perhaps? Anyway, for now
> I can set this to something arbitrary for v2.
Perhaps having some arbitrary limit is good enough, not exactly
because of memory constraints since each entry should consume just a
few bytes but we don't want to timeout so I suggest we queue based on
how long we should wait the result to be called.
--
Luiz Augusto von Dentz
Hi Luiz,
> On Mon, Nov 10, 2014 at 6:02 AM, Luiz Augusto von Dentz <[email protected]> wrote:
> Hi Arman,
>
> On Fri, Nov 7, 2014 at 10:41 PM, Arman Uguray <[email protected]> wrote:
>> This patch add support for handling ATT "Prepare Write" requests for the
>> server role.
>> ---
>> src/shared/gatt-server.c | 142 +++++++++++++++++++++++++++++++++++++++++++++++
>> src/shared/gatt-server.h | 3 +
>> 2 files changed, 145 insertions(+)
>>
>> diff --git a/src/shared/gatt-server.c b/src/shared/gatt-server.c
>> index e77acce..48a55e7 100644
>> --- a/src/shared/gatt-server.c
>> +++ b/src/shared/gatt-server.c
>> @@ -40,6 +40,8 @@
>> #define MIN(a, b) ((a) < (b) ? (a) : (b))
>> #endif
>>
>> +#define DEFAULT_MAX_PREP_QUEUE_LEN 5
>> +
>> struct async_read_op {
>> struct bt_gatt_server *server;
>> uint8_t opcode;
>> @@ -55,6 +57,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 +87,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 +113,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 +121,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);
>> }
>> @@ -922,6 +947,98 @@ 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;
>> + }
>> +
>> + 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)
>> {
>> @@ -1015,6 +1132,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;
>> }
>>
>> @@ -1033,6 +1157,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);
>> @@ -1078,6 +1209,17 @@ bool bt_gatt_server_set_debug(struct bt_gatt_server *server,
>> return true;
>> }
>>
>> +bool bt_gatt_server_set_max_prep_queue_length(struct bt_gatt_server *server,
>> + unsigned int length)
>> +{
>> + if (!server || !length)
>> + return false;
>> +
>> + server->max_prep_queue_len = length;
>> +
>> + return true;
>> +}
>> +
>> bool bt_gatt_server_send_notification(struct bt_gatt_server *server,
>> uint16_t handle, const uint8_t *value,
>> uint16_t length)
>> diff --git a/src/shared/gatt-server.h b/src/shared/gatt-server.h
>> index 0e480e1..a95613c 100644
>> --- a/src/shared/gatt-server.h
>> +++ b/src/shared/gatt-server.h
>> @@ -40,6 +40,9 @@ bool bt_gatt_server_set_debug(struct bt_gatt_server *server,
>> void *user_data,
>> bt_gatt_server_destroy_func_t destroy);
>>
>> +bool bt_gatt_server_set_max_prep_queue_length(struct bt_gatt_server *server,
>> + unsigned int length);
>
> Perhaps we should leave this API for later, Im not really sure if this
> will be ever used except perhaps for the TS in that case perhaps we
> should find a shorter name.
>
In our case would it even make sense to have a maximum queue length? I
only added because there is an error for it and an application with
memory constraints may want to limit the size perhaps? Anyway, for now
I can set this to something arbitrary for v2.
>> bool bt_gatt_server_send_notification(struct bt_gatt_server *server,
>> uint16_t handle, const uint8_t *value,
>> uint16_t length);
>> --
>> 2.1.0.rc2.206.gedb03e5
>>
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
>> the body of a message to [email protected]
>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
>
>
> --
> Luiz Augusto von Dentz
Cheers,
Arman
Hi Luiz,
> On Mon, Nov 10, 2014 at 3:57 AM, Luiz Augusto von Dentz <[email protected]> wrote:
> Hi Arman,
>
> On Fri, Nov 7, 2014 at 10:41 PM, Arman Uguray <[email protected]> wrote:
>> 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 | 146 +++++++++++++++++++++++++++++++++++++++++++++++
>> 1 file changed, 146 insertions(+)
>>
>> diff --git a/src/shared/gatt-server.c b/src/shared/gatt-server.c
>> index ddb714d..db002db 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,125 @@ 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) {
>> + 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 {
>> + /*
>> + * TODO: Make this function work with Prepare Write request once
>> + * it's supported.
>> + */
>> +
>> + 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];
>> + gatt_db_attribute_write_t complete_func = NULL;
>> + 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 this is not a command, set up the completion callback and data */
>> + if (opcode == BT_ATT_OP_WRITE_REQ) {
>> + 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;
>> +
>> + complete_func = write_complete_cb;
>> + }
>> +
>> + if (gatt_db_attribute_write(attr, 0, pdu + 2, length - 2, opcode,
>> + NULL, complete_func, op))
>> + return;
>
> Currently gatt_db_attribute_write does not accept NULL callbacks so
> this would probably fail for BT_ATT_OP_WRITE_CMD, so either we do
> change it and allow unconfirmed writes or perhaps we can always set
> write_complete_cb and server->pending_write_op so the server can tell
> the write is still pending although for BT_ATT_OP_WRITE_CMD there is
> no response.
>
You're right, fixed for v2.
>> + 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 +822,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
>>
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
>> the body of a message to [email protected]
>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
>
>
> --
> Luiz Augusto von Dentz
Cheers,
Arman
Hi Luiz,
> On Mon, Nov 10, 2014 at 3:01 AM, Luiz Augusto von Dentz <[email protected]> wrote:
> Hi Arman,
>
> On Fri, Nov 7, 2014 at 10:41 PM, Arman Uguray <[email protected]> wrote:
>> 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;
>> }
>
> This seems to be unrelated to the patch, it looks like a fix or
> perhaps multiple fixes.
>
>> @@ -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;
>> + }
>
> Lately Ive been trying to advocate for using iovec whenever possible,
> for example here we are doing 2 copies for each attribute when we
> could be just passing references and only copy if the PDU has to be
> queued, perhaps because the ATT PDUs are rather small we did not think
> this would be a problem but for more complex operations such as this I
> think we should perhaps reconsider about this strategy. Anyway, we can
> add to the TODO since right now this is not critical.
>
Sounds good, adding this to the TODO in v2.
>> + 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
>>
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
>> the body of a message to [email protected]
>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
>
>
> --
> Luiz Augusto von Dentz
Cheers,
Arman
Hi Arman,
On Fri, Nov 7, 2014 at 10:41 PM, Arman Uguray <[email protected]> wrote:
> This patch add support for handling ATT "Prepare Write" requests for the
> server role.
> ---
> src/shared/gatt-server.c | 142 +++++++++++++++++++++++++++++++++++++++++++++++
> src/shared/gatt-server.h | 3 +
> 2 files changed, 145 insertions(+)
>
> diff --git a/src/shared/gatt-server.c b/src/shared/gatt-server.c
> index e77acce..48a55e7 100644
> --- a/src/shared/gatt-server.c
> +++ b/src/shared/gatt-server.c
> @@ -40,6 +40,8 @@
> #define MIN(a, b) ((a) < (b) ? (a) : (b))
> #endif
>
> +#define DEFAULT_MAX_PREP_QUEUE_LEN 5
> +
> struct async_read_op {
> struct bt_gatt_server *server;
> uint8_t opcode;
> @@ -55,6 +57,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 +87,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 +113,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 +121,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);
> }
> @@ -922,6 +947,98 @@ 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;
> + }
> +
> + 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)
> {
> @@ -1015,6 +1132,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;
> }
>
> @@ -1033,6 +1157,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);
> @@ -1078,6 +1209,17 @@ bool bt_gatt_server_set_debug(struct bt_gatt_server *server,
> return true;
> }
>
> +bool bt_gatt_server_set_max_prep_queue_length(struct bt_gatt_server *server,
> + unsigned int length)
> +{
> + if (!server || !length)
> + return false;
> +
> + server->max_prep_queue_len = length;
> +
> + return true;
> +}
> +
> bool bt_gatt_server_send_notification(struct bt_gatt_server *server,
> uint16_t handle, const uint8_t *value,
> uint16_t length)
> diff --git a/src/shared/gatt-server.h b/src/shared/gatt-server.h
> index 0e480e1..a95613c 100644
> --- a/src/shared/gatt-server.h
> +++ b/src/shared/gatt-server.h
> @@ -40,6 +40,9 @@ bool bt_gatt_server_set_debug(struct bt_gatt_server *server,
> void *user_data,
> bt_gatt_server_destroy_func_t destroy);
>
> +bool bt_gatt_server_set_max_prep_queue_length(struct bt_gatt_server *server,
> + unsigned int length);
Perhaps we should leave this API for later, Im not really sure if this
will be ever used except perhaps for the TS in that case perhaps we
should find a shorter name.
> bool bt_gatt_server_send_notification(struct bt_gatt_server *server,
> uint16_t handle, const uint8_t *value,
> uint16_t length);
> --
> 2.1.0.rc2.206.gedb03e5
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
--
Luiz Augusto von Dentz
Hi Arman,
On Fri, Nov 7, 2014 at 10:41 PM, Arman Uguray <[email protected]> wrote:
> 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 | 146 +++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 146 insertions(+)
>
> diff --git a/src/shared/gatt-server.c b/src/shared/gatt-server.c
> index ddb714d..db002db 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,125 @@ 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) {
> + 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 {
> + /*
> + * TODO: Make this function work with Prepare Write request once
> + * it's supported.
> + */
> +
> + 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];
> + gatt_db_attribute_write_t complete_func = NULL;
> + 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 this is not a command, set up the completion callback and data */
> + if (opcode == BT_ATT_OP_WRITE_REQ) {
> + 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;
> +
> + complete_func = write_complete_cb;
> + }
> +
> + if (gatt_db_attribute_write(attr, 0, pdu + 2, length - 2, opcode,
> + NULL, complete_func, op))
> + return;
Currently gatt_db_attribute_write does not accept NULL callbacks so
this would probably fail for BT_ATT_OP_WRITE_CMD, so either we do
change it and allow unconfirmed writes or perhaps we can always set
write_complete_cb and server->pending_write_op so the server can tell
the write is still pending although for BT_ATT_OP_WRITE_CMD there is
no response.
> + 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 +822,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
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
--
Luiz Augusto von Dentz
Hi Arman,
On Fri, Nov 7, 2014 at 10:41 PM, Arman Uguray <[email protected]> wrote:
> 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;
> }
This seems to be unrelated to the patch, it looks like a fix or
perhaps multiple fixes.
> @@ -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;
> + }
Lately Ive been trying to advocate for using iovec whenever possible,
for example here we are doing 2 copies for each attribute when we
could be just passing references and only copy if the PDU has to be
queued, perhaps because the ATT PDUs are rather small we did not think
this would be a problem but for more complex operations such as this I
think we should perhaps reconsider about this strategy. Anyway, we can
add to the TODO since right now this is not critical.
> + 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
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
--
Luiz Augusto von Dentz
shared/gatt-server has been introduced, so removed that item. Added new
items for remaining tasks.
---
TODO | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/TODO b/TODO
index b5a9612..98f5885 100644
--- a/TODO
+++ b/TODO
@@ -155,12 +155,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 | 119 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 119 insertions(+)
diff --git a/src/shared/gatt-server.c b/src/shared/gatt-server.c
index 48a55e7..3a247d9 100644
--- a/src/shared/gatt-server.c
+++ b/src/shared/gatt-server.c
@@ -88,6 +88,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;
@@ -114,6 +115,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;
@@ -1039,6 +1041,116 @@ 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];
+
+ 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)
{
@@ -1139,6 +1251,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 | 142 +++++++++++++++++++++++++++++++++++++++++++++++
src/shared/gatt-server.h | 3 +
2 files changed, 145 insertions(+)
diff --git a/src/shared/gatt-server.c b/src/shared/gatt-server.c
index e77acce..48a55e7 100644
--- a/src/shared/gatt-server.c
+++ b/src/shared/gatt-server.c
@@ -40,6 +40,8 @@
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
+#define DEFAULT_MAX_PREP_QUEUE_LEN 5
+
struct async_read_op {
struct bt_gatt_server *server;
uint8_t opcode;
@@ -55,6 +57,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 +87,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 +113,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 +121,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);
}
@@ -922,6 +947,98 @@ 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;
+ }
+
+ 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)
{
@@ -1015,6 +1132,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;
}
@@ -1033,6 +1157,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);
@@ -1078,6 +1209,17 @@ bool bt_gatt_server_set_debug(struct bt_gatt_server *server,
return true;
}
+bool bt_gatt_server_set_max_prep_queue_length(struct bt_gatt_server *server,
+ unsigned int length)
+{
+ if (!server || !length)
+ return false;
+
+ server->max_prep_queue_len = length;
+
+ return true;
+}
+
bool bt_gatt_server_send_notification(struct bt_gatt_server *server,
uint16_t handle, const uint8_t *value,
uint16_t length)
diff --git a/src/shared/gatt-server.h b/src/shared/gatt-server.h
index 0e480e1..a95613c 100644
--- a/src/shared/gatt-server.h
+++ b/src/shared/gatt-server.h
@@ -40,6 +40,9 @@ bool bt_gatt_server_set_debug(struct bt_gatt_server *server,
void *user_data,
bt_gatt_server_destroy_func_t destroy);
+bool bt_gatt_server_set_max_prep_queue_length(struct bt_gatt_server *server,
+ unsigned int length);
+
bool bt_gatt_server_send_notification(struct bt_gatt_server *server,
uint16_t handle, const uint8_t *value,
uint16_t length);
--
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 49da84e..e77acce 100644
--- a/src/shared/gatt-server.c
+++ b/src/shared/gatt-server.c
@@ -1077,3 +1077,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 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 db002db..5cf11a4 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;
@@ -758,6 +760,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)
{
@@ -836,6 +940,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 "Read Blob" request for the GATT server role.
---
src/shared/gatt-server.c | 96 +++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 82 insertions(+), 14 deletions(-)
diff --git a/src/shared/gatt-server.c b/src/shared/gatt-server.c
index 5cf11a4..49da84e 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;
@@ -760,12 +762,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;
@@ -788,32 +814,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;
@@ -848,7 +866,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;
@@ -862,6 +881,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)
{
@@ -947,6 +1007,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 "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 | 146 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 146 insertions(+)
diff --git a/src/shared/gatt-server.c b/src/shared/gatt-server.c
index ddb714d..db002db 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,125 @@ 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) {
+ 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 {
+ /*
+ * TODO: Make this function work with Prepare Write request once
+ * it's supported.
+ */
+
+ 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];
+ gatt_db_attribute_write_t complete_func = NULL;
+ 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 this is not a command, set up the completion callback and data */
+ if (opcode == BT_ATT_OP_WRITE_REQ) {
+ 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;
+
+ complete_func = write_complete_cb;
+ }
+
+ if (gatt_db_attribute_write(attr, 0, pdu + 2, length - 2, opcode,
+ NULL, complete_func, 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 +822,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
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 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 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