v4:
- Changed get_op_type from a big switch statement to a function that walks a
static const array that represents a table of ATT opcodes and their types.
- Single bt_att_register function for incoming requests and notifications/
indications.
- Replaced bt_att_set_auth_signature with bt_att_set_signing_data, which takes
in the CSRK and the sign counters, which will be used to generate the
authentication signature for outgoing signed PDUs and to verify incoming
signed PDUs.
- Added bt_att_set_timeout_cb. Added a 30 second timeout for pending requests,
after which the user will be notified and bt_att invalidated.
- Moved parameter structure, opcode, and error code definitions to
src/shared/att-types.h
Arman Uguray (4):
src/shared/att: Introduce struct bt_att.
src/shared/att: Implement basic boilerplate.
src/shared/att: Implement write handler and bt_att_send.
src/shared/att: Handle incoming response PDUs
Makefile.am | 3 +-
src/shared/att-types.h | 222 +++++++++++++
src/shared/att.c | 862 +++++++++++++++++++++++++++++++++++++++++++++++++
src/shared/att.h | 73 +++++
4 files changed, 1159 insertions(+), 1 deletion(-)
create mode 100644 src/shared/att-types.h
create mode 100644 src/shared/att.c
create mode 100644 src/shared/att.h
--
1.8.3.2
Hi Arman,
>> I am still not buying this tail call optimization. However we might need to thing about how we can make sure we do not need this. It feels bad to me every time I look at it. However this is not something that holds up merging this patchset. It is just something that I prefer to get somehow worked out. Especially since I am afraid we have then similar issues in the whole codebase if this holds true. Maybe we need a magic gcc option.
>>
>
> To be honest, this doesn't make sense to me either. Any decent
> compiler should be able to detect that a pointer to a local variable
> is being passed to the tail call and determine not to pop the stack. I
> don't really know why I've been getting these weird crashes; then
> again, they stopped happening after I moved things around. It is a
> mystery to me why the tail call actually made a difference, but for
> now I'm removing the comment and the weird work around from the patch.
> We can investigate later if this is actually an issue or has something
> to do with the compiler configuration I'm building with.
>
quick question, are you sending a new version without the tail call optimization fix or do you want me to apply this set?
Regards
Marcel
Hi Marcel,
> I am still not buying this tail call optimization. However we might need =
to thing about how we can make sure we do not need this. It feels bad to me=
every time I look at it. However this is not something that holds up mergi=
ng this patchset. It is just something that I prefer to get somehow worked =
out. Especially since I am afraid we have then similar issues in the whole =
codebase if this holds true. Maybe we need a magic gcc option.
>
To be honest, this doesn't make sense to me either. Any decent
compiler should be able to detect that a pointer to a local variable
is being passed to the tail call and determine not to pop the stack. I
don't really know why I've been getting these weird crashes; then
again, they stopped happening after I moved things around. It is a
mystery to me why the tail call actually made a difference, but for
now I'm removing the comment and the weird work around from the patch.
We can investigate later if this is actually an issue or has something
to do with the compiler configuration I'm building with.
Hi Arman,
> v4:
> - Changed get_op_type from a big switch statement to a function that walks a
> static const array that represents a table of ATT opcodes and their types.
> - Single bt_att_register function for incoming requests and notifications/
> indications.
> - Replaced bt_att_set_auth_signature with bt_att_set_signing_data, which takes
> in the CSRK and the sign counters, which will be used to generate the
> authentication signature for outgoing signed PDUs and to verify incoming
> signed PDUs.
> - Added bt_att_set_timeout_cb. Added a 30 second timeout for pending requests,
> after which the user will be notified and bt_att invalidated.
> - Moved parameter structure, opcode, and error code definitions to
> src/shared/att-types.h
so besides minor cosmetic changes and leaving the CSRK detail our for now, this looks good.
Johan, Luiz, have an extra look, but I think v5 should be ready to be merged.
Regards
Marcel
Hi Arman,
> This patch adds handling for incoming ATT protocol response PDUs. Basic
> incoming PDU handling logic added to the io read handler.
> ---
> src/shared/att.c | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 116 insertions(+), 1 deletion(-)
>
> diff --git a/src/shared/att.c b/src/shared/att.c
> index 0c26811..df8461b 100644
> --- a/src/shared/att.c
> +++ b/src/shared/att.c
> @@ -404,9 +404,111 @@ static void wakeup_writer(struct bt_att *att)
> att->writer_active = true;
> }
>
> +static bool request_complete(struct bt_att *att, uint8_t req_opcode,
> + uint8_t rsp_opcode, const void *param,
> + uint16_t len)
> +{
> + struct att_send_op *op = att->pending_req;
> +
> + if (!op) {
> + /* There is no pending request so the response is unexpected. */
> + wakeup_writer(att);
> + return false;
> + }
> +
> + if (op->opcode != req_opcode) {
> + /* The request opcode corresponding to the received response
> + * opcode does not match the currently pending request.
> + */
> + return false;
> + }
> +
> + if (op->callback)
> + op->callback(rsp_opcode, param, len, op->user_data);
> +
> + destroy_att_send_op(op);
> + att->pending_req = NULL;
> +
> + wakeup_writer(att);
> + return true;
> +}
> +
> +static bool handle_error_rsp(struct bt_att *att, uint8_t opcode, uint8_t *pdu,
> + ssize_t pdu_len)
> +{
> + struct bt_att_error_rsp_param param;
> + bool result;
> +
> + if (pdu_len != 5)
> + return false;
> +
> + memset(¶m, 0, sizeof(param));
> + param.request_opcode = pdu[1];
> + param.handle = get_le16(pdu + 2);
> + param.error_code = pdu[4];
> +
> + /* Note: avoid a tail call here, since "param" would go out of scope
> + * due to tail call optimization.
> + */
> + result = request_complete(att, param.request_opcode, opcode,
> + ¶m, sizeof(param));
> + return result;
> +}
> +
> +static bool handle_mtu_rsp(struct bt_att *att, uint8_t opcode, uint8_t *pdu,
> + ssize_t pdu_len)
> +{
> + struct bt_att_mtu_rsp_param param;
> + bool result;
> +
> + if (pdu_len != 3)
> + return false;
> +
> + memset(¶m, 0, sizeof(param));
> + param.server_rx_mtu = get_le16(pdu + 1);
> +
> + /* Note: avoid a tail call here, since "param" would go out of scope
> + * due to tail call optimization.
> + */
> + result = request_complete(att, BT_ATT_OP_MTU_REQ, opcode,
> + ¶m, sizeof(param));
> + return result;
> +}
I am still not buying this tail call optimization. However we might need to thing about how we can make sure we do not need this. It feels bad to me every time I look at it. However this is not something that holds up merging this patchset. It is just something that I prefer to get somehow worked out. Especially since I am afraid we have then similar issues in the whole codebase if this holds true. Maybe we need a magic gcc option.
> +
> +static void handle_rsp(struct bt_att *att, uint8_t opcode, uint8_t *pdu,
> + ssize_t pdu_len)
> +{
> + bool success;
> +
> + switch (opcode) {
> + case BT_ATT_OP_ERROR_RSP:
> + success = handle_error_rsp(att, opcode, pdu, pdu_len);
> + break;
> + case BT_ATT_OP_MTU_RSP:
> + success = handle_mtu_rsp(att, opcode, pdu, pdu_len);
> + break;
> + default:
> + success = false;
> + util_debug(att->debug_callback, att->debug_data,
> + "Unknown response opcode: 0x%02x", opcode);
> + break;
> + }
> +
> + if (success)
> + return;
> +
> + util_debug(att->debug_callback, att->debug_data,
> + "Failed to handle respone PDU; opcode: 0x%02x", opcode);
> +
> + if (att->pending_req)
> + request_complete(att, att->pending_req->opcode,
> + BT_ATT_OP_ERROR_RSP, NULL, 0);
> +}
> +
> static bool can_read_data(struct io *io, void *user_data)
> {
> struct bt_att *att = user_data;
> + uint8_t opcode;
> uint8_t *pdu;
> ssize_t bytes_read;
>
> @@ -420,7 +522,20 @@ static bool can_read_data(struct io *io, void *user_data)
> if (bytes_read < ATT_MIN_PDU_LEN)
> return true;
>
> - /* TODO: Handle different types of PDUs here */
> + pdu = att->buf;
> + opcode = pdu[0];
> +
> + /* Act on the received PDU based on the opcode type */
> + switch (get_op_type(opcode)) {
> + case ATT_OP_TYPE_RSP:
> + handle_rsp(att, opcode, pdu, bytes_read);
> + break;
> + default:
> + util_debug(att->debug_callback, att->debug_data,
> + "ATT opcode cannot be handled: 0x%02x", opcode);
> + break;
> + }
> +
> return true;
> }
Regards
Marcel
Hi Arman,
> This patch implements the write handler logic, including the way send
> operations are process from the various internal queues. Added PDU
> encoding for the Exchange MTU request.
> ---
> src/shared/att.c | 295 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 292 insertions(+), 3 deletions(-)
>
> diff --git a/src/shared/att.c b/src/shared/att.c
> index 1c11cd7..0c26811 100644
> --- a/src/shared/att.c
> +++ b/src/shared/att.c
> @@ -27,10 +27,12 @@
>
> #include <stdlib.h>
> #include <unistd.h>
> +#include <errno.h>
>
> #include "src/shared/io.h"
> #include "src/shared/queue.h"
> #include "src/shared/util.h"
> +#include "src/shared/timeout.h"
> #include "lib/uuid.h"
> #include "src/shared/att.h"
>
> @@ -38,6 +40,7 @@
> #define ATT_MIN_PDU_LEN 1 /* At least 1 byte for the opcode. */
> #define ATT_OP_CMD_MASK 0x40
> #define ATT_OP_SIGNED_MASK 0x80
> +#define ATT_TIMEOUT_INTERVAL 30000 /* 30000 ms */
>
> struct att_send_op;
>
> @@ -134,6 +137,7 @@ static enum att_op_type get_op_type(uint8_t opcode)
>
> struct att_send_op {
> unsigned int id;
> + unsigned int timeout_id;
> enum att_op_type type;
> uint16_t opcode;
> void *pdu;
> @@ -143,10 +147,140 @@ struct att_send_op {
> void *user_data;
> };
>
> +static bool encode_mtu_req(struct att_send_op *op, const void *param,
> + uint16_t length, uint16_t mtu)
> +{
> + const struct bt_att_mtu_req_param *p = param;
> + const uint16_t len = 3;
> +
> + if (length != sizeof(*p))
> + return false;
> +
> + if (len > mtu)
> + return false;
> +
> + op->pdu = malloc(len);
> + if (!op->pdu)
> + return false;
> +
> + ((uint8_t *) op->pdu)[0] = op->opcode;
> + put_le16(p->client_rx_mtu, ((uint8_t *) op->pdu) + 1);
> + op->len = len;
> +
> + return true;
> +}
> +
> +static bool encode_pdu(struct att_send_op *op, const void *param,
> + uint16_t length, uint16_t mtu)
> +{
> + /* If no parameters are given, simply set the PDU to consist of the
> + * opcode (e.g. BT_ATT_OP_WRITE_RSP),
> + */
> + if (!length || !param) {
> + op->len = 1;
> + op->pdu = malloc(1);
> + if (!op->pdu)
> + return false;
> +
> + ((uint8_t *) op->pdu)[0] = op->opcode;
> + return true;
> + }
> +
> + /* TODO: If the opcode has the "signed" bit set, make sure that the
> + * resulting PDU contains the authentication signature. Return an error,
> + * if the provided parameters structure is such that it leaves no room
> + * for an authentication signature in the PDU, or if no signing data
> + * has been set to generate the authentication signature.
> + */
> +
> + switch (op->opcode) {
> + case BT_ATT_OP_MTU_REQ:
> + return encode_mtu_req(op, param, length, mtu);
> + dafault:
> + break;
> + }
> +
> + return false;
> +}
> +
> +static struct att_send_op *create_att_send_op(uint8_t opcode, const void *param,
> + uint16_t length, uint16_t mtu,
> + bt_att_request_func_t callback,
> + void *user_data,
> + bt_att_destroy_func_t destroy)
> +{
> + struct att_send_op *op;
> + enum att_op_type op_type;
> +
> + if (!length && !param)
> + return NULL;
> +
> + op_type = get_op_type(opcode);
> + if (op_type == ATT_OP_TYPE_UNKNOWN)
> + return NULL;
> +
> + /* If the opcode corresponds to an operation type that does not elicit a
> + * response from the remote end, then no callbacks should have been
> + * provided. Otherwise, at least a callback should be provided.
> + */
> + if (!callback == (op_type == ATT_OP_TYPE_REQ || op_type == ATT_OP_TYPE_IND))
> + return NULL;
interesting trick on how to handle this. Kudos to that. However I prefer the more readable version that does twist your brain less ;)
if (!callback && (op_type == ATT_OP_TYPE_REQ || op_type == ATT_OP_TYPE_IND))
return NULL;
> +
> + op = new0(struct att_send_op, 1);
> + if (!op)
> + return NULL;
> +
> + op->type = op_type;
> + op->opcode = opcode;
> + op->callback = callback;
> + op->destroy = destroy;
> + op->user_data = user_data;
> +
> + if (!encode_pdu(op, param, length, mtu)) {
> + free(op);
> + return NULL;
> + }
> +
> + return op;
> +}
> +
> +static struct att_send_op *pick_next_send_op(struct bt_att *att)
> +{
> + struct att_send_op *op;
> +
> + /* See if any operations are already in the write queue */
> + op = queue_pop_head(att->write_queue);
> + if (op)
> + return op;
> +
> + /* If there is no pending request, pick an operation from the
> + * request queue.
> + */
> + if (!att->pending_req) {
> + op = queue_pop_head(att->req_queue);
> + if (op)
> + return op;
> + }
> +
> + /* There is either a request pending or no requests queued. If there is
> + * no pending indication, pick an operation from the indication queue.
> + */
> + if (!att->pending_ind) {
> + op = queue_pop_head(att->ind_queue);
> + if (op)
> + return op;
> + }
> +
> + return NULL;
> +}
> +
> static void destroy_att_send_op(void *data)
> {
> struct att_send_op *op = data;
>
> + if (op->timeout_id)
> + timeout_remove(op->timeout_id);
> +
> if (op->destroy)
> op->destroy(op->user_data);
>
> @@ -154,6 +288,122 @@ static void destroy_att_send_op(void *data)
> free(op);
> }
>
> +struct timeout_data {
> + struct bt_att *att;
> + unsigned int id;
> +};
> +
> +static bool timeout_cb(void *user_data)
> +{
> + struct timeout_data *timeout = user_data;
> + struct bt_att *att = timeout->att;
> + struct att_send_op *op = NULL;
> +
> + if (att->pending_req && att->pending_req->id == timeout->id) {
> + op = att->pending_req;
> + att->pending_req = NULL;
> + } else if (att->pending_ind && att->pending_ind->id == timeout->id) {
> + op = att->pending_ind;
> + att->pending_ind = NULL;
> + }
> +
> + if (!op)
> + return false;
> +
> + att->invalid = true;
> +
> + if (att->timeout_callback)
> + att->timeout_callback(op->id, op->opcode, att->timeout_data);
> +
> + op->timeout_id = 0;
> + destroy_att_send_op(op);
> +
> + return false;
> +}
> +
> +static void write_watch_destroy(void *user_data)
> +{
> + struct bt_att *att = user_data;
> +
> + att->writer_active = false;
> +}
> +
> +static bool can_write_data(struct io *io, void *user_data)
> +{
> + struct bt_att *att = user_data;
> + struct att_send_op *op;
> + struct timeout_data *timeout;
> + ssize_t bytes_written;
> +
> + op = pick_next_send_op(att);
> + if (!op)
> + return false;
> +
> + bytes_written = write(att->fd, op->pdu, op->len);
> + if (bytes_written < 0) {
> + util_debug(att->debug_callback, att->debug_data,
> + "write failed: %s", strerror(errno));
> + if (op->callback)
> + op->callback(BT_ATT_OP_ERROR_RSP, NULL, 0,
> + op->user_data);
> +
> + destroy_att_send_op(op);
> + return true;
> + }
> +
> + util_debug(att->debug_callback, att->debug_data,
> + "ATT op 0x%02x", op->opcode);
> +
> + util_hexdump('<', op->pdu, bytes_written,
> + att->debug_callback, att->debug_data);
> +
> + /* Based on the operation type, set either the pending request or the
> + * pending indication. If it came from the write queue, then there is
> + * no need to keep it around.
> + */
> + if (op->type == ATT_OP_TYPE_REQ)
> + att->pending_req = op;
> + else if (op->type == ATT_OP_TYPE_IND)
> + att->pending_ind = op;
> + else {
> + destroy_att_send_op(op);
> + return true;
> + }
I would use a switch statement here.
> +
> + timeout = new0(struct timeout_data, 1);
> + if (!timeout)
> + return true;
> +
> + timeout->att = att;
> + timeout->id = op->id;
> + op->timeout_id = timeout_add(ATT_TIMEOUT_INTERVAL, timeout_cb,
> + timeout, free);
> +
> + /* Return true as there may be more operations ready to write. */
> + return true;
> +}
> +
> +static void wakeup_writer(struct bt_att *att)
> +{
> + if (att->writer_active)
> + return;
> +
> + /* Set the write handler only if there is anything that can be sent
> + * at all.
> + */
> + if (queue_isempty(att->write_queue)) {
> + if ((att->pending_req || queue_isempty(att->req_queue)) &&
> + (att->pending_ind || queue_isempty(att->ind_queue)))
> + return;
> + }
> +
> + if (!io_set_write_handler(att->io, can_write_data, att,
> + write_watch_destroy))
> + return;
> +
> + att->writer_active = true;
> +}
> +
> static bool can_read_data(struct io *io, void *user_data)
> {
> struct bt_att *att = user_data;
> @@ -363,8 +613,47 @@ unsigned int bt_att_send(struct bt_att *att, uint8_t opcode,
> bt_att_request_func_t callback, void *user_data,
> bt_att_destroy_func_t destroy)
> {
> - /* TODO */
> - return 0;
> + struct att_send_op *op;
> + bool result;
> +
> + if (!att)
> + return 0;
> +
> + if (att->invalid)
> + return 0;
> +
> + op = create_att_send_op(opcode, param, length, att->mtu, callback,
> + user_data, destroy);
> + if (!op)
> + return 0;
> +
> + if (att->next_send_id < 1)
> + att->next_send_id = 1;
> +
> + op->id = att->next_send_id++;
> +
> + /* Add the op to the correct queue based on its type */
> + switch (op->type) {
> + case ATT_OP_TYPE_REQ:
> + result = queue_push_tail(att->req_queue, op);
> + break;
> + case ATT_OP_TYPE_IND:
> + result = queue_push_tail(att->ind_queue, op);
> + break;
> + default:
> + result = queue_push_tail(att->write_queue, op);
> + break;
> + }
> +
> + if (!result) {
> + free(op->pdu);
> + free(op);
> + return 0;
> + }
> +
> + wakeup_writer(att);
> +
> + return op->id;
> }
>
> static bool match_op_id(const void *a, const void *b)
> @@ -410,7 +699,7 @@ bool bt_att_cancel(struct bt_att *att, unsigned int id)
> done:
> destroy_att_send_op(op);
>
> - /* TODO: Set the write handler here */
> + wakeup_writer(att);
>
> return true;
> }
Regards
Marcel
Hi Arman,
> This patch implements the getters, setters, creation, ref, and unref
> functions for struct bt_att. Also added is a simple table for
> determining the ATT op type given an opcode and the io read handler that
> currently does nothing.
> ---
> src/shared/att.c | 314 +++++++++++++++++++++++++++++++++++++++++++++++++++----
> 1 file changed, 293 insertions(+), 21 deletions(-)
>
> diff --git a/src/shared/att.c b/src/shared/att.c
> index fc75eb8..1c11cd7 100644
> --- a/src/shared/att.c
> +++ b/src/shared/att.c
> @@ -25,15 +25,19 @@
> #include <config.h>
> #endif
>
> +#include <stdlib.h>
> #include <unistd.h>
>
> #include "src/shared/io.h"
> #include "src/shared/queue.h"
> +#include "src/shared/util.h"
> #include "lib/uuid.h"
> #include "src/shared/att.h"
>
> #define ATT_DEFAULT_LE_MTU 23
> #define ATT_MIN_PDU_LEN 1 /* At least 1 byte for the opcode. */
> +#define ATT_OP_CMD_MASK 0x40
> +#define ATT_OP_SIGNED_MASK 0x80
>
> struct att_send_op;
>
> @@ -42,7 +46,6 @@ struct bt_att {
> int fd;
> bool close_on_unref;
> struct io *io;
> - bool destroyed;
please fix up patch 1 to not even introducing it.
> struct queue *req_queue; /* Queued ATT protocol requests */
> struct att_send_op *pending_req;
> @@ -51,7 +54,7 @@ struct bt_att {
> struct queue *write_queue; /* Queue of PDUs ready to send */
> bool writer_active;
>
> - /* TODO Add notify queue */
> + bool invalid; /* bt_att becomes invalid, when a request times out */
Move the invalid higher up to where you had destroyed.
>
> uint8_t *buf;
> uint16_t mtu;
> @@ -73,8 +76,65 @@ struct bt_att {
> void *debug_data;
> };
>
> +enum att_op_type {
> + ATT_OP_TYPE_REQ,
> + ATT_OP_TYPE_RSP,
> + ATT_OP_TYPE_CMD,
> + ATT_OP_TYPE_IND,
> + ATT_OP_TYPE_NOT,
> + ATT_OP_TYPE_CONF,
> + ATT_OP_TYPE_UNKNOWN,
> +};
> +
> +static const struct {
> + uint8_t opcode;
> + enum att_op_type type;
> +} att_opcode_type_table[] = {
> + { BT_ATT_OP_ERROR_RSP, ATT_OP_TYPE_RSP },
> + { BT_ATT_OP_MTU_REQ, ATT_OP_TYPE_REQ },
> + { BT_ATT_OP_MTU_RSP, ATT_OP_TYPE_RSP },
> + { BT_ATT_OP_FIND_INFO_REQ, ATT_OP_TYPE_REQ },
> + { BT_ATT_OP_FIND_INFO_RSP, ATT_OP_TYPE_RSP },
> + { BT_ATT_OP_FIND_BY_TYPE_VAL_REQ, ATT_OP_TYPE_REQ },
> + { BT_ATT_OP_FIND_BY_TYPE_VAL_RSP, ATT_OP_TYPE_RSP },
> + { BT_ATT_OP_READ_BY_TYPE_REQ, ATT_OP_TYPE_REQ },
> + { BT_ATT_OP_READ_BY_TYPE_RSP, ATT_OP_TYPE_RSP },
> + { BT_ATT_OP_READ_REQ, ATT_OP_TYPE_REQ },
> + { BT_ATT_OP_READ_RSP, ATT_OP_TYPE_RSP },
> + { BT_ATT_OP_READ_BLOB_REQ, ATT_OP_TYPE_REQ },
> + { BT_ATT_OP_READ_BLOB_RSP, ATT_OP_TYPE_RSP },
> + { BT_ATT_OP_READ_MULT_REQ, ATT_OP_TYPE_REQ },
> + { BT_ATT_OP_READ_MULT_RSP, ATT_OP_TYPE_RSP },
> + { BT_ATT_OP_READ_BY_GRP_TYPE_REQ, ATT_OP_TYPE_REQ },
> + { BT_ATT_OP_READ_BY_GRP_TYPE_RSP, ATT_OP_TYPE_RSP },
> + { BT_ATT_OP_WRITE_REQ, ATT_OP_TYPE_REQ },
> + { BT_ATT_OP_WRITE_RSP, ATT_OP_TYPE_RSP },
> + { BT_ATT_OP_WRITE_CMD, ATT_OP_TYPE_CMD },
> + { BT_ATT_OP_SIGNED_WRITE_CMD, ATT_OP_TYPE_CMD },
> + { BT_ATT_OP_PREP_WRITE_REQ, ATT_OP_TYPE_REQ },
> + { BT_ATT_OP_PREP_WRITE_RSP, ATT_OP_TYPE_RSP },
> + { BT_ATT_OP_EXEC_WRITE_REQ, ATT_OP_TYPE_REQ },
> + { BT_ATT_OP_EXEC_WRITE_RSP, ATT_OP_TYPE_RSP },
> + { BT_ATT_OP_HANDLE_VAL_NOT, ATT_OP_TYPE_NOT },
> + { BT_ATT_OP_HANDLE_VAL_IND, ATT_OP_TYPE_IND },
> + { BT_ATT_OP_HANDLE_VAL_CONF, ATT_OP_TYPE_CONF },
> + { }
> +};
> +
> +static enum att_op_type get_op_type(uint8_t opcode)
> +{
> + int i;
Extra empty line between variable declarations and code.
> + for (i = 0; att_opcode_type_table[i].opcode; i++) {
> + if (att_opcode_type_table[i].opcode == opcode)
> + return att_opcode_type_table[i].type;
> + }
> +
> + return ATT_OP_TYPE_UNKNOWN;
> +}
> +
> struct att_send_op {
> unsigned int id;
> + enum att_op_type type;
> uint16_t opcode;
> void *pdu;
> uint16_t len;
> @@ -83,61 +143,219 @@ struct att_send_op {
> void *user_data;
> };
>
> +static void destroy_att_send_op(void *data)
> +{
> + struct att_send_op *op = data;
> +
> + if (op->destroy)
> + op->destroy(op->user_data);
> +
> + free(op->pdu);
> + free(op);
> +}
> +
> +static bool can_read_data(struct io *io, void *user_data)
> +{
> + struct bt_att *att = user_data;
> + uint8_t *pdu;
> + ssize_t bytes_read;
> +
> + bytes_read = read(att->fd, att->buf, att->mtu);
> + if (bytes_read < 0)
> + return false;
> +
> + util_hexdump('>', att->buf, bytes_read,
> + att->debug_callback, att->debug_data);
> +
> + if (bytes_read < ATT_MIN_PDU_LEN)
> + return true;
> +
> + /* TODO: Handle different types of PDUs here */
> + return true;
> +}
> +
> struct bt_att *bt_att_new(int fd)
> {
> - /* TODO */
> + struct bt_att *att;
> +
> + if (fd < 0)
> + return NULL;
> +
> + att = new0(struct bt_att, 1);
> + if (!att)
> + return NULL;
> +
> + att->fd = fd;
> +
> + att->mtu = ATT_DEFAULT_LE_MTU;
> + att->buf = malloc(att->mtu);
> + if (!att->buf)
> + goto fail;
> +
> + att->io = io_new(fd);
> + if (!att->io)
> + goto fail;
> +
> + att->req_queue = queue_new();
> + if (!att->req_queue)
> + goto fail;
> +
> + att->ind_queue = queue_new();
> + if (!att->ind_queue)
> + goto fail;
> +
> + att->write_queue = queue_new();
> + if (!att->write_queue)
> + goto fail;
> +
> + if (!io_set_read_handler(att->io, can_read_data, att, NULL))
> + goto fail;
> +
> + return bt_att_ref(att);
> +
> +fail:
> + queue_destroy(att->req_queue, NULL);
> + queue_destroy(att->ind_queue, NULL);
> + queue_destroy(att->write_queue, NULL);
> + io_destroy(att->io);
> + free(att->buf);
> + free(att);
> +
> return NULL;
> }
>
> struct bt_att *bt_att_ref(struct bt_att *att)
> {
> - /* TODO */
> - return NULL;
> + if (!att)
> + return NULL;
> +
> + __sync_fetch_and_add(&att->ref_count, 1);
> +
> + return att;
> }
>
> void bt_att_unref(struct bt_att *att)
> {
> - /* TODO */
> + if (!att)
> + return;
> +
> + if (__sync_sub_and_fetch(&att->ref_count, 1))
> + return;
> +
> + bt_att_cancel_all(att);
> +
> + io_set_write_handler(att->io, NULL, NULL, NULL);
> + io_set_read_handler(att->io, NULL, NULL, NULL);
> +
> + queue_destroy(att->req_queue, NULL);
> + queue_destroy(att->ind_queue, NULL);
> + queue_destroy(att->write_queue, NULL);
> + att->req_queue = NULL;
> + att->ind_queue = NULL;
> + att->write_queue = NULL;
> +
> + io_destroy(att->io);
> + att->io = NULL;
> +
> + if (att->close_on_unref)
> + close(att->fd);
> +
> + if (att->timeout_destroy)
> + att->timeout_destroy(att->timeout_data);
> +
> + if (att->debug_destroy)
> + att->debug_destroy(att->debug_data);
> +
> + free(att->buf);
> + att->buf = NULL;
> +
> + free(att);
> }
>
> bool bt_att_set_close_on_unref(struct bt_att *att, bool do_close)
> {
> - /* TODO */
> - return false;
> + if (!att)
> + return false;
> +
> + att->close_on_unref = do_close;
> +
> + return true;
> }
>
> bool bt_att_set_debug(struct bt_att *att, bt_att_debug_func_t callback,
> void *user_data, bt_att_destroy_func_t destroy)
> {
> - /* TODO */
> - return false;
> + if (!att)
> + return false;
> +
> + if (att->debug_destroy)
> + att->debug_destroy(att->debug_data);
> +
> + att->debug_callback = callback;
> + att->debug_destroy = destroy;
> + att->debug_data = user_data;
> +
> + return true;
> }
>
> uint16_t bt_att_get_mtu(struct bt_att *att)
> {
> - /* TODO */
> - return 0;
> + if (!att)
> + return 0;
> +
> + return att->mtu;
> }
>
> bool bt_att_set_mtu(struct bt_att *att, uint16_t mtu)
> {
> - /* TODO */
> - return false;
> + char *buf;
> +
> + if (!att)
> + return false;
> +
> + if (mtu < ATT_DEFAULT_LE_MTU)
> + return false;
> +
> + buf = malloc(mtu);
> + if (!buf)
> + return false;
> +
> + free(att->buf);
> +
> + att->mtu = mtu;
> + att->buf = buf;
> +
> + return true;
> }
>
> void bt_att_set_signing_data(struct bt_att *att, uint8_t csrk[16],
> uint32_t local_sign_cnt,
> uint32_t remote_sign_cnt)
> {
> - /* TODO */
> + if (!att)
> + return;
> +
> + memcpy(att->csrk, csrk, sizeof(csrk));
> + att->local_sign_cnt = local_sign_cnt;
> + att->remote_sign_cnt = remote_sign_cnt;
> + att->signing_data_set = true;
> }
As I said, don't even bother introducing the internal variables and this function. We do it later. Just keep it on your todo list that this is needed.
>
> bool bt_att_set_timeout_cb(struct bt_att *att, bt_att_timeout_func_t callback,
> void *user_data,
> bt_att_destroy_func_t destroy)
> {
> - /* TODO */
> - return false;
> + if (!att)
> + return false;
> +
> + if (att->timeout_destroy)
> + att->timeout_destroy(att->timeout_data);
> +
> + att->timeout_callback = callback;
> + att->timeout_destroy = destroy;
> + att->timeout_data = user_data;
> +
> + return true;
> }
>
> unsigned int bt_att_send(struct bt_att *att, uint8_t opcode,
> @@ -149,16 +367,70 @@ unsigned int bt_att_send(struct bt_att *att, uint8_t opcode,
> return 0;
> }
>
> +static bool match_op_id(const void *a, const void *b)
> +{
> + const struct att_send_op *op = a;
> + unsigned int id = PTR_TO_UINT(b);
> +
> + return op->id == id;
> +}
> +
> bool bt_att_cancel(struct bt_att *att, unsigned int id)
> {
> - /* TODO */
> - return false;
> + struct att_send_op *op;
> +
> + if (!att || !id)
> + return false;
> +
> + if (att->pending_req && att->pending_req->id == id) {
> + op = att->pending_req;
> + goto done;
> + }
> +
> + if (att->pending_ind && att->pending_ind->id == id) {
> + op = att->pending_ind;
> + goto done;
> + }
> +
> + op = queue_remove_if(att->req_queue, match_op_id, UINT_TO_PTR(id));
> + if (op)
> + goto done;
> +
> + op = queue_remove_if(att->ind_queue, match_op_id, UINT_TO_PTR(id));
> + if (op)
> + goto done;
> +
> + op = queue_remove_if(att->write_queue, match_op_id, UINT_TO_PTR(id));
> + if (op)
> + goto done;
> +
> + if (!op)
> + return false;
> +
> +done:
> + destroy_att_send_op(op);
> +
> + /* TODO: Set the write handler here */
> +
> + return true;
> }
>
> bool bt_att_cancel_all(struct bt_att *att)
> {
> - /* TODO */
> - return false;
> + if (!att)
> + return false;
> +
> + queue_remove_all(att->req_queue, NULL, NULL, destroy_att_send_op);
> + queue_remove_all(att->ind_queue, NULL, NULL, destroy_att_send_op);
> + queue_remove_all(att->write_queue, NULL, NULL, destroy_att_send_op);
> +
> + if (att->pending_req)
> + destroy_att_send_op(att->pending_req);
> +
> + if (att->pending_ind)
> + destroy_att_send_op(att->pending_ind);
> +
> + return true;
> }
>
> unsigned int bt_att_register(struct bt_att *att, uint8_t opcode,
Regards
Marcel
Hi Arman,
> This patch introduces struct bt_att, which handles the transport and
> encoding/decoding for the ATT protocol. The structure of the code
> follows that of src/shared/mgmt and lib/mgmt.h, where individual
> parameter structures are defined for all ATT protocol requests, responses,
> commands, indications, and notifications. The serialization and
> endianness conversion for all parameters are handled by bt_att.
>
> struct bt_att is based around struct io and operates on a raw file
> descriptor.
> ---
> Makefile.am | 3 +-
> src/shared/att-types.h | 222 +++++++++++++++++++++++++++++++++++++++++++++++++
> src/shared/att.c | 182 ++++++++++++++++++++++++++++++++++++++++
> src/shared/att.h | 73 ++++++++++++++++
> 4 files changed, 479 insertions(+), 1 deletion(-)
> create mode 100644 src/shared/att-types.h
> create mode 100644 src/shared/att.c
> create mode 100644 src/shared/att.h
>
> diff --git a/Makefile.am b/Makefile.am
> index dc88816..d10b573 100644
> --- a/Makefile.am
> +++ b/Makefile.am
> @@ -156,7 +156,8 @@ src_bluetoothd_SOURCES = $(builtin_sources) \
> src/shared/timeout.h src/shared/timeout-glib.c \
> src/shared/queue.h src/shared/queue.c \
> src/shared/util.h src/shared/util.c \
> - src/shared/mgmt.h src/shared/mgmt.c
> + src/shared/mgmt.h src/shared/mgmt.c \
> + src/shared/att-types.h src/shared/att.h src/shared/att.c
> src_bluetoothd_LDADD = lib/libbluetooth-internal.la gdbus/libgdbus-internal.la \
> @GLIB_LIBS@ @DBUS_LIBS@ -ldl -lrt
> src_bluetoothd_LDFLAGS = $(AM_LDFLAGS) -Wl,--export-dynamic \
> diff --git a/src/shared/att-types.h b/src/shared/att-types.h
> new file mode 100644
> index 0000000..636a5e3
> --- /dev/null
> +++ b/src/shared/att-types.h
> @@ -0,0 +1,222 @@
> +/*
> + *
> + * BlueZ - Bluetooth protocol stack for Linux
> + *
> + * Copyright (C) 2014 Google Inc.
> + *
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
> + *
> + */
> +
> +#include <stdint.h>
> +
> +/* Error response */
> +#define BT_ATT_OP_ERROR_RSP 0x01
> +struct bt_att_error_rsp_param {
> + uint8_t request_opcode;
> + uint16_t handle;
> + uint8_t error_code;
> +};
> +
> +/* Exchange MTU */
> +#define BT_ATT_OP_MTU_REQ 0x02
> +struct bt_att_mtu_req_param {
> + uint16_t client_rx_mtu;
> +};
> +
> +#define BT_ATT_OP_MTU_RSP 0x03
> +struct bt_att_mtu_rsp_param {
> + uint16_t server_rx_mtu;
> +};
> +
> +/* Find Information */
> +#define BT_ATT_OP_FIND_INFO_REQ 0x04
> +struct bt_att_find_info_req_param {
> + uint16_t start_handle;
> + uint16_t end_handle;
> +};
> +
> +#define BT_ATT_OP_FIND_INFO_RSP 0x05
> +struct bt_att_find_info_rsp_param {
> + uint8_t format;
> + const uint8_t *info_data;
> + uint16_t length;
> +};
> +
> +/* Find By Type Value */
> +#define BT_ATT_OP_FIND_BY_TYPE_VAL_REQ 0x06
> +struct bt_att_find_by_type_value_req_param {
> + uint16_t start_handle;
> + uint16_t end_handle;
> + uint16_t type; /* 2 octet UUID */
> + const uint8_t *value;
> + uint16_t length; /* MAX length: (ATT_MTU - 7) */
> +};
> +
> +#define BT_ATT_OP_FIND_BY_TYPE_VAL_RSP 0x07
> +struct bt_att_find_by_type_value_rsp_param {
> + const uint8_t *handles_info_list;
> + uint16_t length;
> +};
> +
> +/* Read By Type */
> +#define BT_ATT_OP_READ_BY_TYPE_REQ 0x08
> +struct bt_att_read_by_type_req_param {
> + uint16_t start_handle;
> + uint16_t end_handle;
> + bt_uuid_t type; /* 2 or 16 octet UUID */
> +};
> +
> +#define BT_ATT_OP_READ_BY_TYPE_RSP 0x09
> +struct bt_att_read_by_type_rsp_param {
> + uint8_t length;
> + const uint8_t *attr_data_list;
> + uint16_t list_length; /* Length of "attr_data_list" */
> +};
> +
> +/* Read */
> +#define BT_ATT_OP_READ_REQ 0x0a
> +struct bt_att_read_req_param {
> + uint16_t handle;
> +};
> +
> +#define BT_ATT_OP_READ_RSP 0x0b
> +struct bt_att_read_rsp_param {
> + const uint8_t *value;
> + uint16_t length;
> +};
> +
> +/* Read Blob */
> +#define BT_ATT_OP_READ_BLOB_REQ 0x0c
> +struct bt_att_read_blob_req_param {
> + uint16_t handle;
> + uint16_t offset;
> +};
> +
> +#define BT_ATT_OP_READ_BLOB_RSP 0x0d
> +struct bt_att_read_blob_rsp_param {
> + const uint8_t *part_value;
> + uint16_t length;
> +};
> +
> +/* Read Multiple */
> +#define BT_ATT_OP_READ_MULT_REQ 0x0e
> +struct bt_att_read_multiple_req_param {
> + const uint16_t *handles;
> + uint16_t num_handles;
> +};
> +
> +#define BT_ATT_OP_READ_MULT_RSP 0x0f
> +struct bt_att_read_multiple_rsp_param {
> + const uint8_t *values;
> + uint16_t length;
> +};
> +
> +/* Read By Group Type */
> +#define BT_ATT_OP_READ_BY_GRP_TYPE_REQ 0x10
> +struct bt_att_read_by_group_type_req_param {
> + uint16_t start_handle;
> + uint16_t end_handle;
> + bt_uuid_t type;
> +};
> +
> +#define BT_ATT_OP_READ_BY_GRP_TYPE_RSP 0x11
> +struct bt_att_read_by_group_type_rsp_param {
> + uint8_t length;
> + const uint8_t *attr_data_list;
> + uint16_t list_length; /* Length of "attr_data_list" */
> +};
> +
> +/* Write Request */
> +#define BT_ATT_OP_WRITE_REQ 0x12
> +/*
> + * bt_att_write_param is used for write request and signed and unsigned write
> + * command.
> + */
> +struct bt_att_write_param {
> + uint16_t handle;
> + const uint8_t *value;
> + uint16_t length;
> +};
> +
> +#define BT_ATT_OP_WRITE_RSP 0x13 /* No parameters */
> +
> +/* Write Command */
> +#define BT_ATT_OP_WRITE_CMD 0x52
> +
> +/* Signed Write Command */
> +#define BT_ATT_OP_SIGNED_WRITE_CMD 0xD2
> +
> +/* Prepare Write */
> +#define BT_ATT_OP_PREP_WRITE_REQ 0x16
> +struct bt_att_prepare_write_req_param {
> + uint16_t handle;
> + uint16_t offset;
> + const uint8_t *part_value;
> + uint16_t length;
> +};
> +
> +#define BT_ATT_OP_PREP_WRITE_RSP 0x17
> +struct bt_att_prepare_write_rsp_param {
> + uint16_t handle;
> + uint16_t offset;
> + const uint8_t *part_value;
> + uint16_t length;
> +};
> +
> +/* Execute Write */
> +#define BT_ATT_OP_EXEC_WRITE_REQ 0x18
> +typedef enum {
> + BT_ATT_EXEC_WRITE_FLAG_CANCEL = 0x00,
> + BT_ATT_EXEC_WRITE_FLAG_WRITE = 0x01,
> +} bt_att_exec_write_flag_t;
> +
> +struct bt_att_exec_write_req_param {
> + bt_att_exec_write_flag_t flags;
> +};
> +
> +#define BT_ATT_OP_EXEC_WRITE_RSP 0x19
> +
> +/* Handle Value Notification/Indication */
> +#define BT_ATT_OP_HANDLE_VAL_NOT 0x1B
> +#define BT_ATT_OP_HANDLE_VAL_IND 0x1D
> +struct bt_att_notify_param {
> + uint16_t handle;
> + const uint8_t *value;
> + uint16_t length;
> +};
> +
> +/* Handle Value Confirmation */
> +#define BT_ATT_OP_HANDLE_VAL_CONF 0x1E
> +
> +/* Error codes for Error response PDU */
> +#define BT_ATT_ERROR_INVALID_HANDLE 0x01
> +#define BT_ATT_ERROR_READ_NOT_PERMITTED 0x02
> +#define BT_ATT_ERROR_WRITE_NOT_PERMITTED 0x03
> +#define BT_ATT_ERROR_INVALID_PDU 0x04
> +#define BT_ATT_ERROR_AUTHENTICATION 0x05
> +#define BT_ATT_ERROR_REQUEST_NOT_SUPPORTED 0x06
> +#define BT_ATT_ERROR_INVALID_OFFSET 0x07
> +#define BT_ATT_ERROR_AUTHORIZATION 0x08
> +#define BT_ATT_ERROR_PREPARE_QUEUE_FULL 0x09
> +#define BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND 0x0A
> +#define BT_ATT_ERROR_ATTRIBUTE_NOT_LONG 0x0B
> +#define BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION_KEY_SIZE 0x0C
> +#define BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN 0x0D
> +#define BT_ATT_ERROR_UNLIKELY 0x0E
> +#define BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION 0x0F
> +#define BT_ATT_ERROR_UNSUPPORTED_GROUP_TYPE 0x10
> +#define BT_ATT_ERROR_INSUFFICIENT_RESOURCES 0x11
> diff --git a/src/shared/att.c b/src/shared/att.c
> new file mode 100644
> index 0000000..fc75eb8
> --- /dev/null
> +++ b/src/shared/att.c
> @@ -0,0 +1,182 @@
> +/*
> + *
> + * BlueZ - Bluetooth protocol stack for Linux
> + *
> + * Copyright (C) 2014 Google Inc.
> + *
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
> + *
> + */
> +
> +#ifdef HAVE_CONFIG_H
> +#include <config.h>
> +#endif
> +
> +#include <unistd.h>
> +
> +#include "src/shared/io.h"
> +#include "src/shared/queue.h"
> +#include "lib/uuid.h"
> +#include "src/shared/att.h"
> +
> +#define ATT_DEFAULT_LE_MTU 23
> +#define ATT_MIN_PDU_LEN 1 /* At least 1 byte for the opcode. */
> +
> +struct att_send_op;
> +
> +struct bt_att {
> + int ref_count;
> + int fd;
> + bool close_on_unref;
> + struct io *io;
> + bool destroyed;
> +
> + struct queue *req_queue; /* Queued ATT protocol requests */
> + struct att_send_op *pending_req;
> + struct queue *ind_queue; /* Queued ATT protocol indications */
> + struct att_send_op *pending_ind;
> + struct queue *write_queue; /* Queue of PDUs ready to send */
> + bool writer_active;
> +
> + /* TODO Add notify queue */
> +
> + uint8_t *buf;
> + uint16_t mtu;
> +
> + uint8_t csrk[16];
> + uint32_t local_sign_cnt;
> + uint32_t remote_sign_cnt;
> + bool signing_data_set;
> +
> + unsigned int next_send_id; /* IDs for "send" ops */
> + unsigned int next_reg_id; /* IDs for registered callbacks */
> +
> + bt_att_timeout_func_t timeout_callback;
> + bt_att_destroy_func_t timeout_destroy;
> + void *timeout_data;
> +
> + bt_att_debug_func_t debug_callback;
> + bt_att_destroy_func_t debug_destroy;
> + void *debug_data;
> +};
> +
> +struct att_send_op {
> + unsigned int id;
> + uint16_t opcode;
> + void *pdu;
> + uint16_t len;
> + bt_att_request_func_t callback;
> + bt_att_destroy_func_t destroy;
> + void *user_data;
> +};
> +
> +struct bt_att *bt_att_new(int fd)
> +{
> + /* TODO */
> + return NULL;
> +}
> +
> +struct bt_att *bt_att_ref(struct bt_att *att)
> +{
> + /* TODO */
> + return NULL;
> +}
> +
> +void bt_att_unref(struct bt_att *att)
> +{
> + /* TODO */
> +}
> +
> +bool bt_att_set_close_on_unref(struct bt_att *att, bool do_close)
> +{
> + /* TODO */
> + return false;
> +}
> +
> +bool bt_att_set_debug(struct bt_att *att, bt_att_debug_func_t callback,
> + void *user_data, bt_att_destroy_func_t destroy)
> +{
> + /* TODO */
> + return false;
> +}
> +
> +uint16_t bt_att_get_mtu(struct bt_att *att)
> +{
> + /* TODO */
> + return 0;
> +}
> +
> +bool bt_att_set_mtu(struct bt_att *att, uint16_t mtu)
> +{
> + /* TODO */
> + return false;
> +}
> +
> +void bt_att_set_signing_data(struct bt_att *att, uint8_t csrk[16],
> + uint32_t local_sign_cnt,
> + uint32_t remote_sign_cnt)
> +{
> + /* TODO */
> +}
> +
> +bool bt_att_set_timeout_cb(struct bt_att *att, bt_att_timeout_func_t callback,
> + void *user_data,
> + bt_att_destroy_func_t destroy)
> +{
> + /* TODO */
> + return false;
> +}
> +
> +unsigned int bt_att_send(struct bt_att *att, uint8_t opcode,
> + const void *param, uint16_t length,
> + bt_att_request_func_t callback, void *user_data,
> + bt_att_destroy_func_t destroy)
> +{
> + /* TODO */
> + return 0;
> +}
> +
> +bool bt_att_cancel(struct bt_att *att, unsigned int id)
> +{
> + /* TODO */
> + return false;
> +}
> +
> +bool bt_att_cancel_all(struct bt_att *att)
> +{
> + /* TODO */
> + return false;
> +}
> +
> +unsigned int bt_att_register(struct bt_att *att, uint8_t opcode,
> + bt_att_request_func_t callback,
> + void *user_data, bt_att_destroy_func_t destroy)
> +{
> + /* TODO */
> + return 0;
> +}
> +
> +bool bt_att_unregister(struct bt_att *att, unsigned int id)
> +{
> + /* TODO */
> + return false;
> +}
> +
> +bool bt_att_unregister_all(struct bt_att *att)
> +{
> + /* TODO */
> + return false;
> +}
> diff --git a/src/shared/att.h b/src/shared/att.h
> new file mode 100644
> index 0000000..4cbe7c2
> --- /dev/null
> +++ b/src/shared/att.h
> @@ -0,0 +1,73 @@
> +/*
> + *
> + * BlueZ - Bluetooth protocol stack for Linux
> + *
> + * Copyright (C) 2014 Google Inc.
> + *
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
> + *
> + */
> +
> +#include <stdbool.h>
> +#include <stdint.h>
> +
> +#include "src/shared/att-types.h"
> +
> +struct bt_att;
> +
> +struct bt_att *bt_att_new(int fd);
> +
> +struct bt_att *bt_att_ref(struct bt_att *att);
> +void bt_att_unref(struct bt_att *att);
> +
> +bool bt_att_set_close_on_unref(struct bt_att *att, bool do_close);
> +
> +typedef void (*bt_att_request_func_t)(uint8_t opcode, const void *param,
> + uint16_t length, void *user_data);
> +typedef void (*bt_att_destroy_func_t)(void *user_data);
> +typedef void (*bt_att_debug_func_t)(const char *str, void *user_data);
> +typedef void (*bt_att_notify_func_t)(uint8_t opcode,
> + const struct bt_att_notify_param *param,
> + void *user_data);
> +typedef void (*bt_att_timeout_func_t)(unsigned int id, uint8_t opcode,
> + void *user_data);
> +
> +bool bt_att_set_debug(struct bt_att *att, bt_att_debug_func_t callback,
> + void *user_data, bt_att_destroy_func_t destroy);
> +
> +uint16_t bt_att_get_mtu(struct bt_att *att);
> +bool bt_att_set_mtu(struct bt_att *att, uint16_t mtu);
> +
> +void bt_att_set_signing_data(struct bt_att *att, uint8_t csrk[16],
> + uint32_t local_sign_cnt,
> + uint32_t remote_sign_cnt);
leave the CSRK part out for now. This is still not correct. Lets do that later since I really don't want this to hold up us merging this patchset. I think it is ready to get merged and all other fixes or extensions, can be made once this is upstream.
Just an update on the CSRK part. You will have actually two CSRK. One for signing and one for verification. You might have both, one of them, or none of them. This all depends on what the remote agrees to distribute over SMP.
So I think this needs to be bt_att_set_signing_key and bt_att_set_resolving_key. But again here, lets deal with this after this gets merged. Just something to keep in mind.
Regards
Marcel
This patch adds handling for incoming ATT protocol response PDUs. Basic
incoming PDU handling logic added to the io read handler.
---
src/shared/att.c | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 116 insertions(+), 1 deletion(-)
diff --git a/src/shared/att.c b/src/shared/att.c
index 0c26811..df8461b 100644
--- a/src/shared/att.c
+++ b/src/shared/att.c
@@ -404,9 +404,111 @@ static void wakeup_writer(struct bt_att *att)
att->writer_active = true;
}
+static bool request_complete(struct bt_att *att, uint8_t req_opcode,
+ uint8_t rsp_opcode, const void *param,
+ uint16_t len)
+{
+ struct att_send_op *op = att->pending_req;
+
+ if (!op) {
+ /* There is no pending request so the response is unexpected. */
+ wakeup_writer(att);
+ return false;
+ }
+
+ if (op->opcode != req_opcode) {
+ /* The request opcode corresponding to the received response
+ * opcode does not match the currently pending request.
+ */
+ return false;
+ }
+
+ if (op->callback)
+ op->callback(rsp_opcode, param, len, op->user_data);
+
+ destroy_att_send_op(op);
+ att->pending_req = NULL;
+
+ wakeup_writer(att);
+ return true;
+}
+
+static bool handle_error_rsp(struct bt_att *att, uint8_t opcode, uint8_t *pdu,
+ ssize_t pdu_len)
+{
+ struct bt_att_error_rsp_param param;
+ bool result;
+
+ if (pdu_len != 5)
+ return false;
+
+ memset(¶m, 0, sizeof(param));
+ param.request_opcode = pdu[1];
+ param.handle = get_le16(pdu + 2);
+ param.error_code = pdu[4];
+
+ /* Note: avoid a tail call here, since "param" would go out of scope
+ * due to tail call optimization.
+ */
+ result = request_complete(att, param.request_opcode, opcode,
+ ¶m, sizeof(param));
+ return result;
+}
+
+static bool handle_mtu_rsp(struct bt_att *att, uint8_t opcode, uint8_t *pdu,
+ ssize_t pdu_len)
+{
+ struct bt_att_mtu_rsp_param param;
+ bool result;
+
+ if (pdu_len != 3)
+ return false;
+
+ memset(¶m, 0, sizeof(param));
+ param.server_rx_mtu = get_le16(pdu + 1);
+
+ /* Note: avoid a tail call here, since "param" would go out of scope
+ * due to tail call optimization.
+ */
+ result = request_complete(att, BT_ATT_OP_MTU_REQ, opcode,
+ ¶m, sizeof(param));
+ return result;
+}
+
+static void handle_rsp(struct bt_att *att, uint8_t opcode, uint8_t *pdu,
+ ssize_t pdu_len)
+{
+ bool success;
+
+ switch (opcode) {
+ case BT_ATT_OP_ERROR_RSP:
+ success = handle_error_rsp(att, opcode, pdu, pdu_len);
+ break;
+ case BT_ATT_OP_MTU_RSP:
+ success = handle_mtu_rsp(att, opcode, pdu, pdu_len);
+ break;
+ default:
+ success = false;
+ util_debug(att->debug_callback, att->debug_data,
+ "Unknown response opcode: 0x%02x", opcode);
+ break;
+ }
+
+ if (success)
+ return;
+
+ util_debug(att->debug_callback, att->debug_data,
+ "Failed to handle respone PDU; opcode: 0x%02x", opcode);
+
+ if (att->pending_req)
+ request_complete(att, att->pending_req->opcode,
+ BT_ATT_OP_ERROR_RSP, NULL, 0);
+}
+
static bool can_read_data(struct io *io, void *user_data)
{
struct bt_att *att = user_data;
+ uint8_t opcode;
uint8_t *pdu;
ssize_t bytes_read;
@@ -420,7 +522,20 @@ static bool can_read_data(struct io *io, void *user_data)
if (bytes_read < ATT_MIN_PDU_LEN)
return true;
- /* TODO: Handle different types of PDUs here */
+ pdu = att->buf;
+ opcode = pdu[0];
+
+ /* Act on the received PDU based on the opcode type */
+ switch (get_op_type(opcode)) {
+ case ATT_OP_TYPE_RSP:
+ handle_rsp(att, opcode, pdu, bytes_read);
+ break;
+ default:
+ util_debug(att->debug_callback, att->debug_data,
+ "ATT opcode cannot be handled: 0x%02x", opcode);
+ break;
+ }
+
return true;
}
--
1.8.3.2
This patch implements the write handler logic, including the way send
operations are process from the various internal queues. Added PDU
encoding for the Exchange MTU request.
---
src/shared/att.c | 295 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 292 insertions(+), 3 deletions(-)
diff --git a/src/shared/att.c b/src/shared/att.c
index 1c11cd7..0c26811 100644
--- a/src/shared/att.c
+++ b/src/shared/att.c
@@ -27,10 +27,12 @@
#include <stdlib.h>
#include <unistd.h>
+#include <errno.h>
#include "src/shared/io.h"
#include "src/shared/queue.h"
#include "src/shared/util.h"
+#include "src/shared/timeout.h"
#include "lib/uuid.h"
#include "src/shared/att.h"
@@ -38,6 +40,7 @@
#define ATT_MIN_PDU_LEN 1 /* At least 1 byte for the opcode. */
#define ATT_OP_CMD_MASK 0x40
#define ATT_OP_SIGNED_MASK 0x80
+#define ATT_TIMEOUT_INTERVAL 30000 /* 30000 ms */
struct att_send_op;
@@ -134,6 +137,7 @@ static enum att_op_type get_op_type(uint8_t opcode)
struct att_send_op {
unsigned int id;
+ unsigned int timeout_id;
enum att_op_type type;
uint16_t opcode;
void *pdu;
@@ -143,10 +147,140 @@ struct att_send_op {
void *user_data;
};
+static bool encode_mtu_req(struct att_send_op *op, const void *param,
+ uint16_t length, uint16_t mtu)
+{
+ const struct bt_att_mtu_req_param *p = param;
+ const uint16_t len = 3;
+
+ if (length != sizeof(*p))
+ return false;
+
+ if (len > mtu)
+ return false;
+
+ op->pdu = malloc(len);
+ if (!op->pdu)
+ return false;
+
+ ((uint8_t *) op->pdu)[0] = op->opcode;
+ put_le16(p->client_rx_mtu, ((uint8_t *) op->pdu) + 1);
+ op->len = len;
+
+ return true;
+}
+
+static bool encode_pdu(struct att_send_op *op, const void *param,
+ uint16_t length, uint16_t mtu)
+{
+ /* If no parameters are given, simply set the PDU to consist of the
+ * opcode (e.g. BT_ATT_OP_WRITE_RSP),
+ */
+ if (!length || !param) {
+ op->len = 1;
+ op->pdu = malloc(1);
+ if (!op->pdu)
+ return false;
+
+ ((uint8_t *) op->pdu)[0] = op->opcode;
+ return true;
+ }
+
+ /* TODO: If the opcode has the "signed" bit set, make sure that the
+ * resulting PDU contains the authentication signature. Return an error,
+ * if the provided parameters structure is such that it leaves no room
+ * for an authentication signature in the PDU, or if no signing data
+ * has been set to generate the authentication signature.
+ */
+
+ switch (op->opcode) {
+ case BT_ATT_OP_MTU_REQ:
+ return encode_mtu_req(op, param, length, mtu);
+ dafault:
+ break;
+ }
+
+ return false;
+}
+
+static struct att_send_op *create_att_send_op(uint8_t opcode, const void *param,
+ uint16_t length, uint16_t mtu,
+ bt_att_request_func_t callback,
+ void *user_data,
+ bt_att_destroy_func_t destroy)
+{
+ struct att_send_op *op;
+ enum att_op_type op_type;
+
+ if (!length && !param)
+ return NULL;
+
+ op_type = get_op_type(opcode);
+ if (op_type == ATT_OP_TYPE_UNKNOWN)
+ return NULL;
+
+ /* If the opcode corresponds to an operation type that does not elicit a
+ * response from the remote end, then no callbacks should have been
+ * provided. Otherwise, at least a callback should be provided.
+ */
+ if (!callback == (op_type == ATT_OP_TYPE_REQ || op_type == ATT_OP_TYPE_IND))
+ return NULL;
+
+ op = new0(struct att_send_op, 1);
+ if (!op)
+ return NULL;
+
+ op->type = op_type;
+ op->opcode = opcode;
+ op->callback = callback;
+ op->destroy = destroy;
+ op->user_data = user_data;
+
+ if (!encode_pdu(op, param, length, mtu)) {
+ free(op);
+ return NULL;
+ }
+
+ return op;
+}
+
+static struct att_send_op *pick_next_send_op(struct bt_att *att)
+{
+ struct att_send_op *op;
+
+ /* See if any operations are already in the write queue */
+ op = queue_pop_head(att->write_queue);
+ if (op)
+ return op;
+
+ /* If there is no pending request, pick an operation from the
+ * request queue.
+ */
+ if (!att->pending_req) {
+ op = queue_pop_head(att->req_queue);
+ if (op)
+ return op;
+ }
+
+ /* There is either a request pending or no requests queued. If there is
+ * no pending indication, pick an operation from the indication queue.
+ */
+ if (!att->pending_ind) {
+ op = queue_pop_head(att->ind_queue);
+ if (op)
+ return op;
+ }
+
+ return NULL;
+}
+
static void destroy_att_send_op(void *data)
{
struct att_send_op *op = data;
+ if (op->timeout_id)
+ timeout_remove(op->timeout_id);
+
if (op->destroy)
op->destroy(op->user_data);
@@ -154,6 +288,122 @@ static void destroy_att_send_op(void *data)
free(op);
}
+struct timeout_data {
+ struct bt_att *att;
+ unsigned int id;
+};
+
+static bool timeout_cb(void *user_data)
+{
+ struct timeout_data *timeout = user_data;
+ struct bt_att *att = timeout->att;
+ struct att_send_op *op = NULL;
+
+ if (att->pending_req && att->pending_req->id == timeout->id) {
+ op = att->pending_req;
+ att->pending_req = NULL;
+ } else if (att->pending_ind && att->pending_ind->id == timeout->id) {
+ op = att->pending_ind;
+ att->pending_ind = NULL;
+ }
+
+ if (!op)
+ return false;
+
+ att->invalid = true;
+
+ if (att->timeout_callback)
+ att->timeout_callback(op->id, op->opcode, att->timeout_data);
+
+ op->timeout_id = 0;
+ destroy_att_send_op(op);
+
+ return false;
+}
+
+static void write_watch_destroy(void *user_data)
+{
+ struct bt_att *att = user_data;
+
+ att->writer_active = false;
+}
+
+static bool can_write_data(struct io *io, void *user_data)
+{
+ struct bt_att *att = user_data;
+ struct att_send_op *op;
+ struct timeout_data *timeout;
+ ssize_t bytes_written;
+
+ op = pick_next_send_op(att);
+ if (!op)
+ return false;
+
+ bytes_written = write(att->fd, op->pdu, op->len);
+ if (bytes_written < 0) {
+ util_debug(att->debug_callback, att->debug_data,
+ "write failed: %s", strerror(errno));
+ if (op->callback)
+ op->callback(BT_ATT_OP_ERROR_RSP, NULL, 0,
+ op->user_data);
+
+ destroy_att_send_op(op);
+ return true;
+ }
+
+ util_debug(att->debug_callback, att->debug_data,
+ "ATT op 0x%02x", op->opcode);
+
+ util_hexdump('<', op->pdu, bytes_written,
+ att->debug_callback, att->debug_data);
+
+ /* Based on the operation type, set either the pending request or the
+ * pending indication. If it came from the write queue, then there is
+ * no need to keep it around.
+ */
+ if (op->type == ATT_OP_TYPE_REQ)
+ att->pending_req = op;
+ else if (op->type == ATT_OP_TYPE_IND)
+ att->pending_ind = op;
+ else {
+ destroy_att_send_op(op);
+ return true;
+ }
+
+ timeout = new0(struct timeout_data, 1);
+ if (!timeout)
+ return true;
+
+ timeout->att = att;
+ timeout->id = op->id;
+ op->timeout_id = timeout_add(ATT_TIMEOUT_INTERVAL, timeout_cb,
+ timeout, free);
+
+ /* Return true as there may be more operations ready to write. */
+ return true;
+}
+
+static void wakeup_writer(struct bt_att *att)
+{
+ if (att->writer_active)
+ return;
+
+ /* Set the write handler only if there is anything that can be sent
+ * at all.
+ */
+ if (queue_isempty(att->write_queue)) {
+ if ((att->pending_req || queue_isempty(att->req_queue)) &&
+ (att->pending_ind || queue_isempty(att->ind_queue)))
+ return;
+ }
+
+ if (!io_set_write_handler(att->io, can_write_data, att,
+ write_watch_destroy))
+ return;
+
+ att->writer_active = true;
+}
+
static bool can_read_data(struct io *io, void *user_data)
{
struct bt_att *att = user_data;
@@ -363,8 +613,47 @@ unsigned int bt_att_send(struct bt_att *att, uint8_t opcode,
bt_att_request_func_t callback, void *user_data,
bt_att_destroy_func_t destroy)
{
- /* TODO */
- return 0;
+ struct att_send_op *op;
+ bool result;
+
+ if (!att)
+ return 0;
+
+ if (att->invalid)
+ return 0;
+
+ op = create_att_send_op(opcode, param, length, att->mtu, callback,
+ user_data, destroy);
+ if (!op)
+ return 0;
+
+ if (att->next_send_id < 1)
+ att->next_send_id = 1;
+
+ op->id = att->next_send_id++;
+
+ /* Add the op to the correct queue based on its type */
+ switch (op->type) {
+ case ATT_OP_TYPE_REQ:
+ result = queue_push_tail(att->req_queue, op);
+ break;
+ case ATT_OP_TYPE_IND:
+ result = queue_push_tail(att->ind_queue, op);
+ break;
+ default:
+ result = queue_push_tail(att->write_queue, op);
+ break;
+ }
+
+ if (!result) {
+ free(op->pdu);
+ free(op);
+ return 0;
+ }
+
+ wakeup_writer(att);
+
+ return op->id;
}
static bool match_op_id(const void *a, const void *b)
@@ -410,7 +699,7 @@ bool bt_att_cancel(struct bt_att *att, unsigned int id)
done:
destroy_att_send_op(op);
- /* TODO: Set the write handler here */
+ wakeup_writer(att);
return true;
}
--
1.8.3.2
This patch implements the getters, setters, creation, ref, and unref
functions for struct bt_att. Also added is a simple table for
determining the ATT op type given an opcode and the io read handler that
currently does nothing.
---
src/shared/att.c | 314 +++++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 293 insertions(+), 21 deletions(-)
diff --git a/src/shared/att.c b/src/shared/att.c
index fc75eb8..1c11cd7 100644
--- a/src/shared/att.c
+++ b/src/shared/att.c
@@ -25,15 +25,19 @@
#include <config.h>
#endif
+#include <stdlib.h>
#include <unistd.h>
#include "src/shared/io.h"
#include "src/shared/queue.h"
+#include "src/shared/util.h"
#include "lib/uuid.h"
#include "src/shared/att.h"
#define ATT_DEFAULT_LE_MTU 23
#define ATT_MIN_PDU_LEN 1 /* At least 1 byte for the opcode. */
+#define ATT_OP_CMD_MASK 0x40
+#define ATT_OP_SIGNED_MASK 0x80
struct att_send_op;
@@ -42,7 +46,6 @@ struct bt_att {
int fd;
bool close_on_unref;
struct io *io;
- bool destroyed;
struct queue *req_queue; /* Queued ATT protocol requests */
struct att_send_op *pending_req;
@@ -51,7 +54,7 @@ struct bt_att {
struct queue *write_queue; /* Queue of PDUs ready to send */
bool writer_active;
- /* TODO Add notify queue */
+ bool invalid; /* bt_att becomes invalid, when a request times out */
uint8_t *buf;
uint16_t mtu;
@@ -73,8 +76,65 @@ struct bt_att {
void *debug_data;
};
+enum att_op_type {
+ ATT_OP_TYPE_REQ,
+ ATT_OP_TYPE_RSP,
+ ATT_OP_TYPE_CMD,
+ ATT_OP_TYPE_IND,
+ ATT_OP_TYPE_NOT,
+ ATT_OP_TYPE_CONF,
+ ATT_OP_TYPE_UNKNOWN,
+};
+
+static const struct {
+ uint8_t opcode;
+ enum att_op_type type;
+} att_opcode_type_table[] = {
+ { BT_ATT_OP_ERROR_RSP, ATT_OP_TYPE_RSP },
+ { BT_ATT_OP_MTU_REQ, ATT_OP_TYPE_REQ },
+ { BT_ATT_OP_MTU_RSP, ATT_OP_TYPE_RSP },
+ { BT_ATT_OP_FIND_INFO_REQ, ATT_OP_TYPE_REQ },
+ { BT_ATT_OP_FIND_INFO_RSP, ATT_OP_TYPE_RSP },
+ { BT_ATT_OP_FIND_BY_TYPE_VAL_REQ, ATT_OP_TYPE_REQ },
+ { BT_ATT_OP_FIND_BY_TYPE_VAL_RSP, ATT_OP_TYPE_RSP },
+ { BT_ATT_OP_READ_BY_TYPE_REQ, ATT_OP_TYPE_REQ },
+ { BT_ATT_OP_READ_BY_TYPE_RSP, ATT_OP_TYPE_RSP },
+ { BT_ATT_OP_READ_REQ, ATT_OP_TYPE_REQ },
+ { BT_ATT_OP_READ_RSP, ATT_OP_TYPE_RSP },
+ { BT_ATT_OP_READ_BLOB_REQ, ATT_OP_TYPE_REQ },
+ { BT_ATT_OP_READ_BLOB_RSP, ATT_OP_TYPE_RSP },
+ { BT_ATT_OP_READ_MULT_REQ, ATT_OP_TYPE_REQ },
+ { BT_ATT_OP_READ_MULT_RSP, ATT_OP_TYPE_RSP },
+ { BT_ATT_OP_READ_BY_GRP_TYPE_REQ, ATT_OP_TYPE_REQ },
+ { BT_ATT_OP_READ_BY_GRP_TYPE_RSP, ATT_OP_TYPE_RSP },
+ { BT_ATT_OP_WRITE_REQ, ATT_OP_TYPE_REQ },
+ { BT_ATT_OP_WRITE_RSP, ATT_OP_TYPE_RSP },
+ { BT_ATT_OP_WRITE_CMD, ATT_OP_TYPE_CMD },
+ { BT_ATT_OP_SIGNED_WRITE_CMD, ATT_OP_TYPE_CMD },
+ { BT_ATT_OP_PREP_WRITE_REQ, ATT_OP_TYPE_REQ },
+ { BT_ATT_OP_PREP_WRITE_RSP, ATT_OP_TYPE_RSP },
+ { BT_ATT_OP_EXEC_WRITE_REQ, ATT_OP_TYPE_REQ },
+ { BT_ATT_OP_EXEC_WRITE_RSP, ATT_OP_TYPE_RSP },
+ { BT_ATT_OP_HANDLE_VAL_NOT, ATT_OP_TYPE_NOT },
+ { BT_ATT_OP_HANDLE_VAL_IND, ATT_OP_TYPE_IND },
+ { BT_ATT_OP_HANDLE_VAL_CONF, ATT_OP_TYPE_CONF },
+ { }
+};
+
+static enum att_op_type get_op_type(uint8_t opcode)
+{
+ int i;
+ for (i = 0; att_opcode_type_table[i].opcode; i++) {
+ if (att_opcode_type_table[i].opcode == opcode)
+ return att_opcode_type_table[i].type;
+ }
+
+ return ATT_OP_TYPE_UNKNOWN;
+}
+
struct att_send_op {
unsigned int id;
+ enum att_op_type type;
uint16_t opcode;
void *pdu;
uint16_t len;
@@ -83,61 +143,219 @@ struct att_send_op {
void *user_data;
};
+static void destroy_att_send_op(void *data)
+{
+ struct att_send_op *op = data;
+
+ if (op->destroy)
+ op->destroy(op->user_data);
+
+ free(op->pdu);
+ free(op);
+}
+
+static bool can_read_data(struct io *io, void *user_data)
+{
+ struct bt_att *att = user_data;
+ uint8_t *pdu;
+ ssize_t bytes_read;
+
+ bytes_read = read(att->fd, att->buf, att->mtu);
+ if (bytes_read < 0)
+ return false;
+
+ util_hexdump('>', att->buf, bytes_read,
+ att->debug_callback, att->debug_data);
+
+ if (bytes_read < ATT_MIN_PDU_LEN)
+ return true;
+
+ /* TODO: Handle different types of PDUs here */
+ return true;
+}
+
struct bt_att *bt_att_new(int fd)
{
- /* TODO */
+ struct bt_att *att;
+
+ if (fd < 0)
+ return NULL;
+
+ att = new0(struct bt_att, 1);
+ if (!att)
+ return NULL;
+
+ att->fd = fd;
+
+ att->mtu = ATT_DEFAULT_LE_MTU;
+ att->buf = malloc(att->mtu);
+ if (!att->buf)
+ goto fail;
+
+ att->io = io_new(fd);
+ if (!att->io)
+ goto fail;
+
+ att->req_queue = queue_new();
+ if (!att->req_queue)
+ goto fail;
+
+ att->ind_queue = queue_new();
+ if (!att->ind_queue)
+ goto fail;
+
+ att->write_queue = queue_new();
+ if (!att->write_queue)
+ goto fail;
+
+ if (!io_set_read_handler(att->io, can_read_data, att, NULL))
+ goto fail;
+
+ return bt_att_ref(att);
+
+fail:
+ queue_destroy(att->req_queue, NULL);
+ queue_destroy(att->ind_queue, NULL);
+ queue_destroy(att->write_queue, NULL);
+ io_destroy(att->io);
+ free(att->buf);
+ free(att);
+
return NULL;
}
struct bt_att *bt_att_ref(struct bt_att *att)
{
- /* TODO */
- return NULL;
+ if (!att)
+ return NULL;
+
+ __sync_fetch_and_add(&att->ref_count, 1);
+
+ return att;
}
void bt_att_unref(struct bt_att *att)
{
- /* TODO */
+ if (!att)
+ return;
+
+ if (__sync_sub_and_fetch(&att->ref_count, 1))
+ return;
+
+ bt_att_cancel_all(att);
+
+ io_set_write_handler(att->io, NULL, NULL, NULL);
+ io_set_read_handler(att->io, NULL, NULL, NULL);
+
+ queue_destroy(att->req_queue, NULL);
+ queue_destroy(att->ind_queue, NULL);
+ queue_destroy(att->write_queue, NULL);
+ att->req_queue = NULL;
+ att->ind_queue = NULL;
+ att->write_queue = NULL;
+
+ io_destroy(att->io);
+ att->io = NULL;
+
+ if (att->close_on_unref)
+ close(att->fd);
+
+ if (att->timeout_destroy)
+ att->timeout_destroy(att->timeout_data);
+
+ if (att->debug_destroy)
+ att->debug_destroy(att->debug_data);
+
+ free(att->buf);
+ att->buf = NULL;
+
+ free(att);
}
bool bt_att_set_close_on_unref(struct bt_att *att, bool do_close)
{
- /* TODO */
- return false;
+ if (!att)
+ return false;
+
+ att->close_on_unref = do_close;
+
+ return true;
}
bool bt_att_set_debug(struct bt_att *att, bt_att_debug_func_t callback,
void *user_data, bt_att_destroy_func_t destroy)
{
- /* TODO */
- return false;
+ if (!att)
+ return false;
+
+ if (att->debug_destroy)
+ att->debug_destroy(att->debug_data);
+
+ att->debug_callback = callback;
+ att->debug_destroy = destroy;
+ att->debug_data = user_data;
+
+ return true;
}
uint16_t bt_att_get_mtu(struct bt_att *att)
{
- /* TODO */
- return 0;
+ if (!att)
+ return 0;
+
+ return att->mtu;
}
bool bt_att_set_mtu(struct bt_att *att, uint16_t mtu)
{
- /* TODO */
- return false;
+ char *buf;
+
+ if (!att)
+ return false;
+
+ if (mtu < ATT_DEFAULT_LE_MTU)
+ return false;
+
+ buf = malloc(mtu);
+ if (!buf)
+ return false;
+
+ free(att->buf);
+
+ att->mtu = mtu;
+ att->buf = buf;
+
+ return true;
}
void bt_att_set_signing_data(struct bt_att *att, uint8_t csrk[16],
uint32_t local_sign_cnt,
uint32_t remote_sign_cnt)
{
- /* TODO */
+ if (!att)
+ return;
+
+ memcpy(att->csrk, csrk, sizeof(csrk));
+ att->local_sign_cnt = local_sign_cnt;
+ att->remote_sign_cnt = remote_sign_cnt;
+ att->signing_data_set = true;
}
bool bt_att_set_timeout_cb(struct bt_att *att, bt_att_timeout_func_t callback,
void *user_data,
bt_att_destroy_func_t destroy)
{
- /* TODO */
- return false;
+ if (!att)
+ return false;
+
+ if (att->timeout_destroy)
+ att->timeout_destroy(att->timeout_data);
+
+ att->timeout_callback = callback;
+ att->timeout_destroy = destroy;
+ att->timeout_data = user_data;
+
+ return true;
}
unsigned int bt_att_send(struct bt_att *att, uint8_t opcode,
@@ -149,16 +367,70 @@ unsigned int bt_att_send(struct bt_att *att, uint8_t opcode,
return 0;
}
+static bool match_op_id(const void *a, const void *b)
+{
+ const struct att_send_op *op = a;
+ unsigned int id = PTR_TO_UINT(b);
+
+ return op->id == id;
+}
+
bool bt_att_cancel(struct bt_att *att, unsigned int id)
{
- /* TODO */
- return false;
+ struct att_send_op *op;
+
+ if (!att || !id)
+ return false;
+
+ if (att->pending_req && att->pending_req->id == id) {
+ op = att->pending_req;
+ goto done;
+ }
+
+ if (att->pending_ind && att->pending_ind->id == id) {
+ op = att->pending_ind;
+ goto done;
+ }
+
+ op = queue_remove_if(att->req_queue, match_op_id, UINT_TO_PTR(id));
+ if (op)
+ goto done;
+
+ op = queue_remove_if(att->ind_queue, match_op_id, UINT_TO_PTR(id));
+ if (op)
+ goto done;
+
+ op = queue_remove_if(att->write_queue, match_op_id, UINT_TO_PTR(id));
+ if (op)
+ goto done;
+
+ if (!op)
+ return false;
+
+done:
+ destroy_att_send_op(op);
+
+ /* TODO: Set the write handler here */
+
+ return true;
}
bool bt_att_cancel_all(struct bt_att *att)
{
- /* TODO */
- return false;
+ if (!att)
+ return false;
+
+ queue_remove_all(att->req_queue, NULL, NULL, destroy_att_send_op);
+ queue_remove_all(att->ind_queue, NULL, NULL, destroy_att_send_op);
+ queue_remove_all(att->write_queue, NULL, NULL, destroy_att_send_op);
+
+ if (att->pending_req)
+ destroy_att_send_op(att->pending_req);
+
+ if (att->pending_ind)
+ destroy_att_send_op(att->pending_ind);
+
+ return true;
}
unsigned int bt_att_register(struct bt_att *att, uint8_t opcode,
--
1.8.3.2
This patch introduces struct bt_att, which handles the transport and
encoding/decoding for the ATT protocol. The structure of the code
follows that of src/shared/mgmt and lib/mgmt.h, where individual
parameter structures are defined for all ATT protocol requests, responses,
commands, indications, and notifications. The serialization and
endianness conversion for all parameters are handled by bt_att.
struct bt_att is based around struct io and operates on a raw file
descriptor.
---
Makefile.am | 3 +-
src/shared/att-types.h | 222 +++++++++++++++++++++++++++++++++++++++++++++++++
src/shared/att.c | 182 ++++++++++++++++++++++++++++++++++++++++
src/shared/att.h | 73 ++++++++++++++++
4 files changed, 479 insertions(+), 1 deletion(-)
create mode 100644 src/shared/att-types.h
create mode 100644 src/shared/att.c
create mode 100644 src/shared/att.h
diff --git a/Makefile.am b/Makefile.am
index dc88816..d10b573 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -156,7 +156,8 @@ src_bluetoothd_SOURCES = $(builtin_sources) \
src/shared/timeout.h src/shared/timeout-glib.c \
src/shared/queue.h src/shared/queue.c \
src/shared/util.h src/shared/util.c \
- src/shared/mgmt.h src/shared/mgmt.c
+ src/shared/mgmt.h src/shared/mgmt.c \
+ src/shared/att-types.h src/shared/att.h src/shared/att.c
src_bluetoothd_LDADD = lib/libbluetooth-internal.la gdbus/libgdbus-internal.la \
@GLIB_LIBS@ @DBUS_LIBS@ -ldl -lrt
src_bluetoothd_LDFLAGS = $(AM_LDFLAGS) -Wl,--export-dynamic \
diff --git a/src/shared/att-types.h b/src/shared/att-types.h
new file mode 100644
index 0000000..636a5e3
--- /dev/null
+++ b/src/shared/att-types.h
@@ -0,0 +1,222 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2014 Google Inc.
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <stdint.h>
+
+/* Error response */
+#define BT_ATT_OP_ERROR_RSP 0x01
+struct bt_att_error_rsp_param {
+ uint8_t request_opcode;
+ uint16_t handle;
+ uint8_t error_code;
+};
+
+/* Exchange MTU */
+#define BT_ATT_OP_MTU_REQ 0x02
+struct bt_att_mtu_req_param {
+ uint16_t client_rx_mtu;
+};
+
+#define BT_ATT_OP_MTU_RSP 0x03
+struct bt_att_mtu_rsp_param {
+ uint16_t server_rx_mtu;
+};
+
+/* Find Information */
+#define BT_ATT_OP_FIND_INFO_REQ 0x04
+struct bt_att_find_info_req_param {
+ uint16_t start_handle;
+ uint16_t end_handle;
+};
+
+#define BT_ATT_OP_FIND_INFO_RSP 0x05
+struct bt_att_find_info_rsp_param {
+ uint8_t format;
+ const uint8_t *info_data;
+ uint16_t length;
+};
+
+/* Find By Type Value */
+#define BT_ATT_OP_FIND_BY_TYPE_VAL_REQ 0x06
+struct bt_att_find_by_type_value_req_param {
+ uint16_t start_handle;
+ uint16_t end_handle;
+ uint16_t type; /* 2 octet UUID */
+ const uint8_t *value;
+ uint16_t length; /* MAX length: (ATT_MTU - 7) */
+};
+
+#define BT_ATT_OP_FIND_BY_TYPE_VAL_RSP 0x07
+struct bt_att_find_by_type_value_rsp_param {
+ const uint8_t *handles_info_list;
+ uint16_t length;
+};
+
+/* Read By Type */
+#define BT_ATT_OP_READ_BY_TYPE_REQ 0x08
+struct bt_att_read_by_type_req_param {
+ uint16_t start_handle;
+ uint16_t end_handle;
+ bt_uuid_t type; /* 2 or 16 octet UUID */
+};
+
+#define BT_ATT_OP_READ_BY_TYPE_RSP 0x09
+struct bt_att_read_by_type_rsp_param {
+ uint8_t length;
+ const uint8_t *attr_data_list;
+ uint16_t list_length; /* Length of "attr_data_list" */
+};
+
+/* Read */
+#define BT_ATT_OP_READ_REQ 0x0a
+struct bt_att_read_req_param {
+ uint16_t handle;
+};
+
+#define BT_ATT_OP_READ_RSP 0x0b
+struct bt_att_read_rsp_param {
+ const uint8_t *value;
+ uint16_t length;
+};
+
+/* Read Blob */
+#define BT_ATT_OP_READ_BLOB_REQ 0x0c
+struct bt_att_read_blob_req_param {
+ uint16_t handle;
+ uint16_t offset;
+};
+
+#define BT_ATT_OP_READ_BLOB_RSP 0x0d
+struct bt_att_read_blob_rsp_param {
+ const uint8_t *part_value;
+ uint16_t length;
+};
+
+/* Read Multiple */
+#define BT_ATT_OP_READ_MULT_REQ 0x0e
+struct bt_att_read_multiple_req_param {
+ const uint16_t *handles;
+ uint16_t num_handles;
+};
+
+#define BT_ATT_OP_READ_MULT_RSP 0x0f
+struct bt_att_read_multiple_rsp_param {
+ const uint8_t *values;
+ uint16_t length;
+};
+
+/* Read By Group Type */
+#define BT_ATT_OP_READ_BY_GRP_TYPE_REQ 0x10
+struct bt_att_read_by_group_type_req_param {
+ uint16_t start_handle;
+ uint16_t end_handle;
+ bt_uuid_t type;
+};
+
+#define BT_ATT_OP_READ_BY_GRP_TYPE_RSP 0x11
+struct bt_att_read_by_group_type_rsp_param {
+ uint8_t length;
+ const uint8_t *attr_data_list;
+ uint16_t list_length; /* Length of "attr_data_list" */
+};
+
+/* Write Request */
+#define BT_ATT_OP_WRITE_REQ 0x12
+/*
+ * bt_att_write_param is used for write request and signed and unsigned write
+ * command.
+ */
+struct bt_att_write_param {
+ uint16_t handle;
+ const uint8_t *value;
+ uint16_t length;
+};
+
+#define BT_ATT_OP_WRITE_RSP 0x13 /* No parameters */
+
+/* Write Command */
+#define BT_ATT_OP_WRITE_CMD 0x52
+
+/* Signed Write Command */
+#define BT_ATT_OP_SIGNED_WRITE_CMD 0xD2
+
+/* Prepare Write */
+#define BT_ATT_OP_PREP_WRITE_REQ 0x16
+struct bt_att_prepare_write_req_param {
+ uint16_t handle;
+ uint16_t offset;
+ const uint8_t *part_value;
+ uint16_t length;
+};
+
+#define BT_ATT_OP_PREP_WRITE_RSP 0x17
+struct bt_att_prepare_write_rsp_param {
+ uint16_t handle;
+ uint16_t offset;
+ const uint8_t *part_value;
+ uint16_t length;
+};
+
+/* Execute Write */
+#define BT_ATT_OP_EXEC_WRITE_REQ 0x18
+typedef enum {
+ BT_ATT_EXEC_WRITE_FLAG_CANCEL = 0x00,
+ BT_ATT_EXEC_WRITE_FLAG_WRITE = 0x01,
+} bt_att_exec_write_flag_t;
+
+struct bt_att_exec_write_req_param {
+ bt_att_exec_write_flag_t flags;
+};
+
+#define BT_ATT_OP_EXEC_WRITE_RSP 0x19
+
+/* Handle Value Notification/Indication */
+#define BT_ATT_OP_HANDLE_VAL_NOT 0x1B
+#define BT_ATT_OP_HANDLE_VAL_IND 0x1D
+struct bt_att_notify_param {
+ uint16_t handle;
+ const uint8_t *value;
+ uint16_t length;
+};
+
+/* Handle Value Confirmation */
+#define BT_ATT_OP_HANDLE_VAL_CONF 0x1E
+
+/* Error codes for Error response PDU */
+#define BT_ATT_ERROR_INVALID_HANDLE 0x01
+#define BT_ATT_ERROR_READ_NOT_PERMITTED 0x02
+#define BT_ATT_ERROR_WRITE_NOT_PERMITTED 0x03
+#define BT_ATT_ERROR_INVALID_PDU 0x04
+#define BT_ATT_ERROR_AUTHENTICATION 0x05
+#define BT_ATT_ERROR_REQUEST_NOT_SUPPORTED 0x06
+#define BT_ATT_ERROR_INVALID_OFFSET 0x07
+#define BT_ATT_ERROR_AUTHORIZATION 0x08
+#define BT_ATT_ERROR_PREPARE_QUEUE_FULL 0x09
+#define BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND 0x0A
+#define BT_ATT_ERROR_ATTRIBUTE_NOT_LONG 0x0B
+#define BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION_KEY_SIZE 0x0C
+#define BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN 0x0D
+#define BT_ATT_ERROR_UNLIKELY 0x0E
+#define BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION 0x0F
+#define BT_ATT_ERROR_UNSUPPORTED_GROUP_TYPE 0x10
+#define BT_ATT_ERROR_INSUFFICIENT_RESOURCES 0x11
diff --git a/src/shared/att.c b/src/shared/att.c
new file mode 100644
index 0000000..fc75eb8
--- /dev/null
+++ b/src/shared/att.c
@@ -0,0 +1,182 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2014 Google Inc.
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+
+#include "src/shared/io.h"
+#include "src/shared/queue.h"
+#include "lib/uuid.h"
+#include "src/shared/att.h"
+
+#define ATT_DEFAULT_LE_MTU 23
+#define ATT_MIN_PDU_LEN 1 /* At least 1 byte for the opcode. */
+
+struct att_send_op;
+
+struct bt_att {
+ int ref_count;
+ int fd;
+ bool close_on_unref;
+ struct io *io;
+ bool destroyed;
+
+ struct queue *req_queue; /* Queued ATT protocol requests */
+ struct att_send_op *pending_req;
+ struct queue *ind_queue; /* Queued ATT protocol indications */
+ struct att_send_op *pending_ind;
+ struct queue *write_queue; /* Queue of PDUs ready to send */
+ bool writer_active;
+
+ /* TODO Add notify queue */
+
+ uint8_t *buf;
+ uint16_t mtu;
+
+ uint8_t csrk[16];
+ uint32_t local_sign_cnt;
+ uint32_t remote_sign_cnt;
+ bool signing_data_set;
+
+ unsigned int next_send_id; /* IDs for "send" ops */
+ unsigned int next_reg_id; /* IDs for registered callbacks */
+
+ bt_att_timeout_func_t timeout_callback;
+ bt_att_destroy_func_t timeout_destroy;
+ void *timeout_data;
+
+ bt_att_debug_func_t debug_callback;
+ bt_att_destroy_func_t debug_destroy;
+ void *debug_data;
+};
+
+struct att_send_op {
+ unsigned int id;
+ uint16_t opcode;
+ void *pdu;
+ uint16_t len;
+ bt_att_request_func_t callback;
+ bt_att_destroy_func_t destroy;
+ void *user_data;
+};
+
+struct bt_att *bt_att_new(int fd)
+{
+ /* TODO */
+ return NULL;
+}
+
+struct bt_att *bt_att_ref(struct bt_att *att)
+{
+ /* TODO */
+ return NULL;
+}
+
+void bt_att_unref(struct bt_att *att)
+{
+ /* TODO */
+}
+
+bool bt_att_set_close_on_unref(struct bt_att *att, bool do_close)
+{
+ /* TODO */
+ return false;
+}
+
+bool bt_att_set_debug(struct bt_att *att, bt_att_debug_func_t callback,
+ void *user_data, bt_att_destroy_func_t destroy)
+{
+ /* TODO */
+ return false;
+}
+
+uint16_t bt_att_get_mtu(struct bt_att *att)
+{
+ /* TODO */
+ return 0;
+}
+
+bool bt_att_set_mtu(struct bt_att *att, uint16_t mtu)
+{
+ /* TODO */
+ return false;
+}
+
+void bt_att_set_signing_data(struct bt_att *att, uint8_t csrk[16],
+ uint32_t local_sign_cnt,
+ uint32_t remote_sign_cnt)
+{
+ /* TODO */
+}
+
+bool bt_att_set_timeout_cb(struct bt_att *att, bt_att_timeout_func_t callback,
+ void *user_data,
+ bt_att_destroy_func_t destroy)
+{
+ /* TODO */
+ return false;
+}
+
+unsigned int bt_att_send(struct bt_att *att, uint8_t opcode,
+ const void *param, uint16_t length,
+ bt_att_request_func_t callback, void *user_data,
+ bt_att_destroy_func_t destroy)
+{
+ /* TODO */
+ return 0;
+}
+
+bool bt_att_cancel(struct bt_att *att, unsigned int id)
+{
+ /* TODO */
+ return false;
+}
+
+bool bt_att_cancel_all(struct bt_att *att)
+{
+ /* TODO */
+ return false;
+}
+
+unsigned int bt_att_register(struct bt_att *att, uint8_t opcode,
+ bt_att_request_func_t callback,
+ void *user_data, bt_att_destroy_func_t destroy)
+{
+ /* TODO */
+ return 0;
+}
+
+bool bt_att_unregister(struct bt_att *att, unsigned int id)
+{
+ /* TODO */
+ return false;
+}
+
+bool bt_att_unregister_all(struct bt_att *att)
+{
+ /* TODO */
+ return false;
+}
diff --git a/src/shared/att.h b/src/shared/att.h
new file mode 100644
index 0000000..4cbe7c2
--- /dev/null
+++ b/src/shared/att.h
@@ -0,0 +1,73 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2014 Google Inc.
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "src/shared/att-types.h"
+
+struct bt_att;
+
+struct bt_att *bt_att_new(int fd);
+
+struct bt_att *bt_att_ref(struct bt_att *att);
+void bt_att_unref(struct bt_att *att);
+
+bool bt_att_set_close_on_unref(struct bt_att *att, bool do_close);
+
+typedef void (*bt_att_request_func_t)(uint8_t opcode, const void *param,
+ uint16_t length, void *user_data);
+typedef void (*bt_att_destroy_func_t)(void *user_data);
+typedef void (*bt_att_debug_func_t)(const char *str, void *user_data);
+typedef void (*bt_att_notify_func_t)(uint8_t opcode,
+ const struct bt_att_notify_param *param,
+ void *user_data);
+typedef void (*bt_att_timeout_func_t)(unsigned int id, uint8_t opcode,
+ void *user_data);
+
+bool bt_att_set_debug(struct bt_att *att, bt_att_debug_func_t callback,
+ void *user_data, bt_att_destroy_func_t destroy);
+
+uint16_t bt_att_get_mtu(struct bt_att *att);
+bool bt_att_set_mtu(struct bt_att *att, uint16_t mtu);
+
+void bt_att_set_signing_data(struct bt_att *att, uint8_t csrk[16],
+ uint32_t local_sign_cnt,
+ uint32_t remote_sign_cnt);
+
+bool bt_att_set_timeout_cb(struct bt_att *att, bt_att_timeout_func_t callback,
+ void *user_data,
+ bt_att_destroy_func_t destroy);
+
+unsigned int bt_att_send(struct bt_att *att, uint8_t opcode,
+ const void *param, uint16_t length,
+ bt_att_request_func_t callback, void *user_data,
+ bt_att_destroy_func_t destroy);
+bool bt_att_cancel(struct bt_att *att, unsigned int id);
+bool bt_att_cancel_all(struct bt_att *att);
+
+unsigned int bt_att_register(struct bt_att *att, uint8_t opcode,
+ bt_att_request_func_t callback,
+ void *user_data, bt_att_destroy_func_t destroy);
+bool bt_att_unregister(struct bt_att *att, unsigned int id);
+bool bt_att_unregister_all(struct bt_att *att);
--
1.8.3.2