diff options
Diffstat (limited to 'bluez/att.c')
-rw-r--r-- | bluez/att.c | 1434 |
1 files changed, 1434 insertions, 0 deletions
diff --git a/bluez/att.c b/bluez/att.c new file mode 100644 index 0000000..fd6ef05 --- /dev/null +++ b/bluez/att.c @@ -0,0 +1,1434 @@ +/* + * + * 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> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> + +#include <bluetooth/bluetooth.h> + +#include "io.h" +#include "queue.h" +#include "util.h" +#include "timeout.h" +#include "uuid.h" +#include "att.h" +#include "crypto.h" + +#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 */ + +/* + * Common Profile and Service Error Code descriptions (see Supplement to the + * Bluetooth Core Specification, sections 1.2 and 2). The error codes within + * 0xE0-0xFC are reserved for future use. The remaining 3 are defined as the + * following: + */ +#define BT_ERROR_CCC_IMPROPERLY_CONFIGURED 0xfd +#define BT_ERROR_ALREADY_IN_PROGRESS 0xfe +#define BT_ERROR_OUT_OF_RANGE 0xff + +/* Length of signature in write signed packet */ +#define BT_ATT_SIGNATURE_LEN 12 + +struct att_send_op; + +struct bt_att { + int ref_count; + int fd; + struct io *io; + bool io_on_l2cap; + int io_sec_level; /* Only used for non-L2CAP */ + + struct queue *req_queue; /* Queued ATT protocol requests */ + struct att_send_op *pending_req; + struct queue *ind_queue; /* Queued ATT protocol indications */ + struct att_send_op *pending_ind; + struct queue *write_queue; /* Queue of PDUs ready to send */ + bool writer_active; + + struct queue *notify_list; /* List of registered callbacks */ + struct queue *disconn_list; /* List of disconnect handlers */ + + bool in_req; /* There's a pending incoming request */ + + uint8_t *buf; + uint16_t mtu; + + 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 bt_crypto *crypto; + + struct sign_info *local_sign; + struct sign_info *remote_sign; +}; + +struct sign_info { + uint8_t key[16]; + bt_att_counter_func_t counter; + void *user_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; +} + +static const struct { + uint8_t req_opcode; + uint8_t rsp_opcode; +} att_req_rsp_mapping_table[] = { + { BT_ATT_OP_MTU_REQ, BT_ATT_OP_MTU_RSP }, + { BT_ATT_OP_FIND_INFO_REQ, BT_ATT_OP_FIND_INFO_RSP}, + { BT_ATT_OP_FIND_BY_TYPE_VAL_REQ, BT_ATT_OP_FIND_BY_TYPE_VAL_RSP }, + { BT_ATT_OP_READ_BY_TYPE_REQ, BT_ATT_OP_READ_BY_TYPE_RSP }, + { BT_ATT_OP_READ_REQ, BT_ATT_OP_READ_RSP }, + { BT_ATT_OP_READ_BLOB_REQ, BT_ATT_OP_READ_BLOB_RSP }, + { BT_ATT_OP_READ_MULT_REQ, BT_ATT_OP_READ_MULT_RSP }, + { BT_ATT_OP_READ_BY_GRP_TYPE_REQ, BT_ATT_OP_READ_BY_GRP_TYPE_RSP }, + { BT_ATT_OP_WRITE_REQ, BT_ATT_OP_WRITE_RSP }, + { BT_ATT_OP_PREP_WRITE_REQ, BT_ATT_OP_PREP_WRITE_RSP }, + { BT_ATT_OP_EXEC_WRITE_REQ, BT_ATT_OP_EXEC_WRITE_RSP }, + { } +}; + +static uint8_t get_req_opcode(uint8_t rsp_opcode) +{ + int i; + + for (i = 0; att_req_rsp_mapping_table[i].rsp_opcode; i++) { + if (att_req_rsp_mapping_table[i].rsp_opcode == rsp_opcode) + return att_req_rsp_mapping_table[i].req_opcode; + } + + return 0; +} + +struct att_send_op { + unsigned int id; + unsigned int timeout_id; + enum att_op_type type; + uint16_t opcode; + void *pdu; + uint16_t len; + bt_att_response_func_t callback; + bt_att_destroy_func_t destroy; + void *user_data; +}; + +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); + + free(op->pdu); + free(op); +} + +static void cancel_att_send_op(struct att_send_op *op) +{ + if (op->destroy) + op->destroy(op->user_data); + + op->user_data = NULL; + op->callback = NULL; + op->destroy = NULL; +} + +struct att_notify { + unsigned int id; + uint16_t opcode; + bt_att_notify_func_t callback; + bt_att_destroy_func_t destroy; + void *user_data; +}; + +static void destroy_att_notify(void *data) +{ + struct att_notify *notify = data; + + if (notify->destroy) + notify->destroy(notify->user_data); + + free(notify); +} + +static bool match_notify_id(const void *a, const void *b) +{ + const struct att_notify *notify = a; + unsigned int id = PTR_TO_UINT(b); + + return notify->id == id; +} + +struct att_disconn { + unsigned int id; + bool removed; + bt_att_disconnect_func_t callback; + bt_att_destroy_func_t destroy; + void *user_data; +}; + +static void destroy_att_disconn(void *data) +{ + struct att_disconn *disconn = data; + + if (disconn->destroy) + disconn->destroy(disconn->user_data); + + free(disconn); +} + +static bool match_disconn_id(const void *a, const void *b) +{ + const struct att_disconn *disconn = a; + unsigned int id = PTR_TO_UINT(b); + + return disconn->id == id; +} + +static bool encode_pdu(struct bt_att *att, struct att_send_op *op, + const void *pdu, uint16_t length) +{ + uint16_t pdu_len = 1; + struct sign_info *sign = att->local_sign; + uint32_t sign_cnt; + + if (sign && (op->opcode & ATT_OP_SIGNED_MASK)) + pdu_len += BT_ATT_SIGNATURE_LEN; + + if (length && pdu) + pdu_len += length; + + if (pdu_len > att->mtu) + return false; + + op->len = pdu_len; + op->pdu = malloc(op->len); + if (!op->pdu) + return false; + + ((uint8_t *) op->pdu)[0] = op->opcode; + if (pdu_len > 1) + memcpy(op->pdu + 1, pdu, length); + + if (!sign || !(op->opcode & ATT_OP_SIGNED_MASK)) + return true; + + if (!sign->counter(&sign_cnt, sign->user_data)) + goto fail; + + if ((bt_crypto_sign_att(att->crypto, sign->key, op->pdu, 1 + length, + sign_cnt, &((uint8_t *) op->pdu)[1 + length]))) + return true; + + util_debug(att->debug_callback, att->debug_data, + "ATT unable to generate signature"); + +fail: + free(op->pdu); + return false; +} + +static struct att_send_op *create_att_send_op(struct bt_att *att, + uint8_t opcode, + const void *pdu, + uint16_t length, + bt_att_response_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 && !pdu) + 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 callback should have been + * provided, since it will never be called. + */ + if (callback && op_type != ATT_OP_TYPE_REQ && op_type != ATT_OP_TYPE_IND) + return NULL; + + /* Similarly, if the operation does elicit a response then a callback + * must 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(att, op, pdu, length)) { + 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; +} + +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; + + util_debug(att->debug_callback, att->debug_data, + "Operation timed out: 0x%02x", op->opcode); + + if (att->timeout_callback) + att->timeout_callback(op->id, op->opcode, att->timeout_data); + + op->timeout_id = 0; + destroy_att_send_op(op); + + /* + * Directly terminate the connection as required by the ATT protocol. + * This should trigger an io disconnect event which will clean up the + * io and notify the upper layer. + */ + io_shutdown(att->io); + + 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 ret; + struct iovec iov; + + op = pick_next_send_op(att); + if (!op) + return false; + + iov.iov_base = op->pdu; + iov.iov_len = op->len; + + ret = io_send(io, &iov, 1); + if (ret < 0) { + util_debug(att->debug_callback, att->debug_data, + "write failed: %s", strerror(-ret)); + if (op->callback) + op->callback(BT_ATT_OP_ERROR_RSP, NULL, 0, + op->user_data); + + destroy_att_send_op(op); + return true; + } + + util_debug(att->debug_callback, att->debug_data, + "ATT op 0x%02x", op->opcode); + + util_hexdump('<', op->pdu, ret, att->debug_callback, att->debug_data); + + /* Based on the operation type, set either the pending request or the + * pending indication. If it came from the write queue, then there is + * no need to keep it around. + */ + switch (op->type) { + case ATT_OP_TYPE_REQ: + att->pending_req = op; + break; + case ATT_OP_TYPE_IND: + att->pending_ind = op; + break; + case ATT_OP_TYPE_RSP: + /* Set in_req to false to indicate that no request is pending */ + att->in_req = false; + + /* Fall through to the next case */ + case ATT_OP_TYPE_CMD: + case ATT_OP_TYPE_NOT: + case ATT_OP_TYPE_CONF: + case ATT_OP_TYPE_UNKNOWN: + default: + 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 void disconn_handler(void *data, void *user_data) +{ + struct att_disconn *disconn = data; + int err = PTR_TO_INT(user_data); + + if (disconn->removed) + return; + + if (disconn->callback) + disconn->callback(err, disconn->user_data); +} + +static bool disconnect_cb(struct io *io, void *user_data) +{ + struct bt_att *att = user_data; + int err; + socklen_t len; + + len = sizeof(err); + + if (getsockopt(att->fd, SOL_SOCKET, SO_ERROR, &err, &len) < 0) { + util_debug(att->debug_callback, att->debug_data, + "Failed to obtain disconnect error: %s", + strerror(errno)); + err = 0; + } + + util_debug(att->debug_callback, att->debug_data, + "Physical link disconnected: %s", + strerror(err)); + + io_destroy(att->io); + att->io = NULL; + + bt_att_cancel_all(att); + + bt_att_ref(att); + + queue_foreach(att->disconn_list, disconn_handler, INT_TO_PTR(err)); + + 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) +{ + struct att_send_op *op = att->pending_req; + uint8_t req_opcode; + uint8_t rsp_opcode; + uint8_t *rsp_pdu = NULL; + uint16_t rsp_pdu_len = 0; + + /* + * If no request is pending, then the response is unexpected. Disconnect + * the bearer. + */ + if (!op) { + util_debug(att->debug_callback, att->debug_data, + "Received unexpected ATT response"); + io_shutdown(att->io); + return; + } + + /* + * 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) { + if (pdu_len != 4) + goto fail; + + req_opcode = pdu[0]; + } else if (!(req_opcode = get_req_opcode(opcode))) + goto fail; + + if (req_opcode != op->opcode) + goto fail; + + rsp_opcode = opcode; + + if (pdu_len > 0) { + rsp_pdu = pdu; + rsp_pdu_len = pdu_len; + } + + goto done; + +fail: + util_debug(att->debug_callback, att->debug_data, + "Failed to handle response PDU; opcode: 0x%02x", opcode); + + rsp_opcode = BT_ATT_OP_ERROR_RSP; + +done: + if (op->callback) + op->callback(rsp_opcode, rsp_pdu, rsp_pdu_len, op->user_data); + + destroy_att_send_op(op); + att->pending_req = NULL; + + 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"); + io_shutdown(att->io); + 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; + ssize_t pdu_len; + bool handler_found; +}; + +static bool opcode_match(uint8_t opcode, uint8_t test_opcode) +{ + enum att_op_type op_type = get_op_type(test_opcode); + + if (opcode == BT_ATT_ALL_REQUESTS && (op_type == ATT_OP_TYPE_REQ || + op_type == ATT_OP_TYPE_CMD)) + return true; + + return opcode == test_opcode; +} + +static void respond_not_supported(struct bt_att *att, uint8_t opcode) +{ + uint8_t pdu[4]; + + pdu[0] = opcode; + pdu[1] = 0; + pdu[2] = 0; + pdu[3] = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED; + + bt_att_send(att, BT_ATT_OP_ERROR_RSP, pdu, sizeof(pdu), NULL, NULL, + NULL); +} + +static bool handle_signed(struct bt_att *att, uint8_t opcode, uint8_t *pdu, + ssize_t pdu_len) +{ + uint8_t *signature; + uint32_t sign_cnt; + struct sign_info *sign; + + /* Check if there is enough data for a signature */ + if (pdu_len < 2 + BT_ATT_SIGNATURE_LEN) + goto fail; + + sign = att->remote_sign; + if (!sign) + goto fail; + + signature = pdu + (pdu_len - BT_ATT_SIGNATURE_LEN); + sign_cnt = get_le32(signature); + + /* Validate counter */ + if (!sign->counter(&sign_cnt, sign->user_data)) + goto fail; + + /* Generate signature and verify it */ + if (!bt_crypto_sign_att(att->crypto, sign->key, pdu, + pdu_len - BT_ATT_SIGNATURE_LEN, sign_cnt, + signature)) + goto fail; + + return true; + +fail: + util_debug(att->debug_callback, att->debug_data, + "ATT failed to verify signature: 0x%02x", opcode); + + return false; +} + +static void handle_notify(struct bt_att *att, uint8_t opcode, uint8_t *pdu, + ssize_t pdu_len) +{ + const struct queue_entry *entry; + bool found; + + if (opcode & ATT_OP_SIGNED_MASK) { + if (!handle_signed(att, opcode, pdu, pdu_len)) + return; + pdu_len -= BT_ATT_SIGNATURE_LEN; + } + + bt_att_ref(att); + + found = false; + entry = queue_get_entries(att->notify_list); + + while (entry) { + struct att_notify *notify = entry->data; + + entry = entry->next; + + if (!opcode_match(notify->opcode, opcode)) + continue; + + found = true; + + if (notify->callback) + notify->callback(opcode, pdu, pdu_len, + notify->user_data); + + /* callback could remove all entries from notify list */ + if (queue_isempty(att->notify_list)) + break; + } + + /* + * If this was a request and no handler was registered for it, respond + * with "Not Supported" + */ + if (!found && get_op_type(opcode) == ATT_OP_TYPE_REQ) + respond_not_supported(att, opcode); + + bt_att_unref(att); +} + +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; + + 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; + + pdu = att->buf; + opcode = pdu[0]; + + bt_att_ref(att); + + /* Act on the received PDU based on the opcode type */ + switch (get_op_type(opcode)) { + case ATT_OP_TYPE_RSP: + util_debug(att->debug_callback, att->debug_data, + "ATT response received: 0x%02x", opcode); + handle_rsp(att, opcode, pdu + 1, bytes_read - 1); + break; + case ATT_OP_TYPE_CONF: + util_debug(att->debug_callback, att->debug_data, + "ATT confirmation received: 0x%02x", opcode); + handle_conf(att, pdu + 1, bytes_read - 1); + break; + case ATT_OP_TYPE_REQ: + /* + * If a request is currently pending, then the sequential + * protocol was violated. Disconnect the bearer, which will + * promptly notify the upper layer via disconnect handlers. + */ + if (att->in_req) { + util_debug(att->debug_callback, att->debug_data, + "Received request while another is " + "pending: 0x%02x", opcode); + io_shutdown(att->io); + bt_att_unref(att); + + return false; + } + + att->in_req = true; + + /* Fall through to the next case */ + case ATT_OP_TYPE_CMD: + case ATT_OP_TYPE_NOT: + case ATT_OP_TYPE_UNKNOWN: + case ATT_OP_TYPE_IND: + default: + /* For all other opcodes notify the upper layer of the PDU and + * let them act on it. + */ + util_debug(att->debug_callback, att->debug_data, + "ATT PDU received: 0x%02x", opcode); + handle_notify(att, opcode, pdu + 1, bytes_read - 1); + break; + } + + bt_att_unref(att); + + return true; +} + +static bool is_io_l2cap_based(int fd) +{ + int domain; + int proto; + int err; + socklen_t len; + + domain = 0; + len = sizeof(domain); + err = getsockopt(fd, SOL_SOCKET, SO_DOMAIN, &domain, &len); + if (err < 0) + return false; + + if (domain != AF_BLUETOOTH) + return false; + + proto = 0; + len = sizeof(proto); + err = getsockopt(fd, SOL_SOCKET, SO_PROTOCOL, &proto, &len); + if (err < 0) + return false; + + return proto == BTPROTO_L2CAP; +} + +static void bt_att_free(struct bt_att *att) +{ + if (att->pending_req) + destroy_att_send_op(att->pending_req); + + if (att->pending_ind) + destroy_att_send_op(att->pending_ind); + + io_destroy(att->io); + bt_crypto_unref(att->crypto); + + queue_destroy(att->req_queue, NULL); + queue_destroy(att->ind_queue, NULL); + queue_destroy(att->write_queue, NULL); + queue_destroy(att->notify_list, NULL); + queue_destroy(att->disconn_list, NULL); + + if (att->timeout_destroy) + att->timeout_destroy(att->timeout_data); + + if (att->debug_destroy) + att->debug_destroy(att->debug_data); + + free(att->local_sign); + free(att->remote_sign); + + free(att->buf); + + free(att); +} + +struct bt_att *bt_att_new(int fd) +{ + struct bt_att *att; + + if (fd < 0) + return NULL; + + att = new0(struct bt_att, 1); + if (!att) + return NULL; + + att->fd = fd; + + att->mtu = BT_ATT_DEFAULT_LE_MTU; + att->buf = malloc(att->mtu); + if (!att->buf) + goto fail; + + att->io = io_new(fd); + if (!att->io) + goto fail; + + /* crypto is optional, if not available leave it NULL */ + att->crypto = bt_crypto_new(); + + 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; + + att->notify_list = queue_new(); + if (!att->notify_list) + goto fail; + + att->disconn_list = queue_new(); + if (!att->disconn_list) + goto fail; + + if (!io_set_read_handler(att->io, can_read_data, att, NULL)) + goto fail; + + if (!io_set_disconnect_handler(att->io, disconnect_cb, att, NULL)) + goto fail; + + att->io_on_l2cap = is_io_l2cap_based(att->fd); + if (!att->io_on_l2cap) + att->io_sec_level = BT_SECURITY_LOW; + + return bt_att_ref(att); + +fail: + bt_att_free(att); + + return NULL; +} + +struct bt_att *bt_att_ref(struct bt_att *att) +{ + if (!att) + return NULL; + + __sync_fetch_and_add(&att->ref_count, 1); + + return att; +} + +void bt_att_unref(struct bt_att *att) +{ + if (!att) + return; + + if (__sync_sub_and_fetch(&att->ref_count, 1)) + return; + + bt_att_unregister_all(att); + bt_att_cancel_all(att); + + bt_att_free(att); +} + +bool bt_att_set_close_on_unref(struct bt_att *att, bool do_close) +{ + if (!att || !att->io) + return false; + + return io_set_close_on_destroy(att->io, do_close); +} + +int bt_att_get_fd(struct bt_att *att) +{ + if (!att) + return -1; + + return att->fd; +} + +bool bt_att_set_debug(struct bt_att *att, bt_att_debug_func_t callback, + void *user_data, bt_att_destroy_func_t destroy) +{ + 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) +{ + if (!att) + return 0; + + return att->mtu; +} + +bool bt_att_set_mtu(struct bt_att *att, uint16_t mtu) +{ + void *buf; + + if (!att) + return false; + + if (mtu < BT_ATT_DEFAULT_LE_MTU) + return false; + + buf = malloc(mtu); + if (!buf) + return false; + + free(att->buf); + + att->mtu = mtu; + att->buf = buf; + + return 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) +{ + 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_register_disconnect(struct bt_att *att, + bt_att_disconnect_func_t callback, + void *user_data, + bt_att_destroy_func_t destroy) +{ + struct att_disconn *disconn; + + if (!att || !att->io) + return 0; + + disconn = new0(struct att_disconn, 1); + if (!disconn) + return 0; + + disconn->callback = callback; + disconn->destroy = destroy; + disconn->user_data = user_data; + + if (att->next_reg_id < 1) + att->next_reg_id = 1; + + disconn->id = att->next_reg_id++; + + if (!queue_push_tail(att->disconn_list, disconn)) { + free(disconn); + return 0; + } + + return disconn->id; +} + +bool bt_att_unregister_disconnect(struct bt_att *att, unsigned int id) +{ + struct att_disconn *disconn; + + if (!att || !id) + return false; + + disconn = queue_remove_if(att->disconn_list, match_disconn_id, + UINT_TO_PTR(id)); + if (!disconn) + return false; + + destroy_att_disconn(disconn); + return true; +} + +unsigned int bt_att_send(struct bt_att *att, uint8_t opcode, + const void *pdu, uint16_t length, + bt_att_response_func_t callback, void *user_data, + bt_att_destroy_func_t destroy) +{ + struct att_send_op *op; + bool result; + + if (!att || !att->io) + return 0; + + op = create_att_send_op(att, opcode, pdu, length, 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; + case ATT_OP_TYPE_CMD: + case ATT_OP_TYPE_NOT: + case ATT_OP_TYPE_UNKNOWN: + case ATT_OP_TYPE_RSP: + case ATT_OP_TYPE_CONF: + 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) +{ + 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) +{ + struct att_send_op *op; + + if (!att || !id) + return false; + + if (att->pending_req && att->pending_req->id == id) { + /* Don't cancel the pending request; remove it's handlers */ + cancel_att_send_op(att->pending_req); + return true; + } + + if (att->pending_ind && att->pending_ind->id == id) { + /* Don't cancel the pending indication; remove it's handlers */ + cancel_att_send_op(att->pending_ind); + return true; + } + + 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); + + wakeup_writer(att); + + return true; +} + +bool bt_att_cancel_all(struct bt_att *att) +{ + 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) + /* Don't cancel the pending request; remove it's handlers */ + cancel_att_send_op(att->pending_req); + + if (att->pending_ind) + /* Don't cancel the pending request; remove it's handlers */ + cancel_att_send_op(att->pending_ind); + + return true; +} + +static uint8_t att_ecode_from_error(int err) +{ + /* + * If the error fits in a single byte, treat it as an ATT protocol + * error as is. Since "0" is not a valid ATT protocol error code, we map + * that to UNLIKELY below. + */ + if (err > 0 && err < UINT8_MAX) + return err; + + /* + * Since we allow UNIX errnos, map them to appropriate ATT protocol + * and "Common Profile and Service" error codes. + */ + switch (err) { + case -ENOENT: + return BT_ATT_ERROR_INVALID_HANDLE; + case -ENOMEM: + return BT_ATT_ERROR_INSUFFICIENT_RESOURCES; + case -EALREADY: + return BT_ERROR_ALREADY_IN_PROGRESS; + case -EOVERFLOW: + return BT_ERROR_OUT_OF_RANGE; + } + + return BT_ATT_ERROR_UNLIKELY; +} + +unsigned int bt_att_send_error_rsp(struct bt_att *att, uint8_t opcode, + uint16_t handle, int error) +{ + struct bt_att_pdu_error_rsp pdu; + uint8_t ecode; + + if (!att || !opcode) + return 0; + + ecode = att_ecode_from_error(error); + + memset(&pdu, 0, sizeof(pdu)); + + pdu.opcode = opcode; + put_le16(handle, &pdu.handle); + pdu.ecode = ecode; + + return bt_att_send(att, BT_ATT_OP_ERROR_RSP, &pdu, sizeof(pdu), + NULL, NULL, NULL); +} + +unsigned int bt_att_register(struct bt_att *att, uint8_t opcode, + bt_att_notify_func_t callback, + void *user_data, + bt_att_destroy_func_t destroy) +{ + struct att_notify *notify; + + if (!att || !callback || !att->io) + return 0; + + notify = new0(struct att_notify, 1); + if (!notify) + return 0; + + notify->opcode = opcode; + notify->callback = callback; + notify->destroy = destroy; + notify->user_data = user_data; + + if (att->next_reg_id < 1) + att->next_reg_id = 1; + + notify->id = att->next_reg_id++; + + if (!queue_push_tail(att->notify_list, notify)) { + free(notify); + return 0; + } + + return notify->id; +} + +bool bt_att_unregister(struct bt_att *att, unsigned int id) +{ + struct att_notify *notify; + + if (!att || !id) + return false; + + notify = queue_remove_if(att->notify_list, match_notify_id, + UINT_TO_PTR(id)); + if (!notify) + return false; + + destroy_att_notify(notify); + return true; +} + +bool bt_att_unregister_all(struct bt_att *att) +{ + if (!att) + return false; + + queue_remove_all(att->notify_list, NULL, NULL, destroy_att_notify); + queue_remove_all(att->disconn_list, NULL, NULL, destroy_att_disconn); + + return true; +} + +int bt_att_get_sec_level(struct bt_att *att) +{ + struct bt_security sec; + socklen_t len; + + if (!att) + return -EINVAL; + + if (!att->io_on_l2cap) + return att->io_sec_level; + + memset(&sec, 0, sizeof(sec)); + len = sizeof(sec); + if (getsockopt(att->fd, SOL_BLUETOOTH, BT_SECURITY, &sec, &len) < 0) + return -EIO; + + return sec.level; +} + +bool bt_att_set_sec_level(struct bt_att *att, int level) +{ + struct bt_security sec; + + if (!att || level < BT_SECURITY_LOW || level > BT_SECURITY_HIGH) + return false; + + if (!att->io_on_l2cap) { + att->io_sec_level = level; + return true; + } + + memset(&sec, 0, sizeof(sec)); + sec.level = level; + + if (setsockopt(att->fd, SOL_BLUETOOTH, BT_SECURITY, &sec, + sizeof(sec)) < 0) + return false; + + return true; +} + +static bool sign_set_key(struct sign_info **sign, uint8_t key[16], + bt_att_counter_func_t func, void *user_data) +{ + if (!(*sign)) { + *sign = new0(struct sign_info, 1); + if (!(*sign)) + return false; + } + + (*sign)->counter = func; + (*sign)->user_data = user_data; + memcpy((*sign)->key, key, 16); + + return true; +} + +bool bt_att_set_local_key(struct bt_att *att, uint8_t sign_key[16], + bt_att_counter_func_t func, void *user_data) +{ + if (!att) + return false; + + return sign_set_key(&att->local_sign, sign_key, func, user_data); +} + +bool bt_att_set_remote_key(struct bt_att *att, uint8_t sign_key[16], + bt_att_counter_func_t func, void *user_data) +{ + if (!att) + return false; + + return sign_set_key(&att->remote_sign, sign_key, func, user_data); +} + +bool bt_att_has_crypto(struct bt_att *att) +{ + if (!att) + return false; + + return att->crypto ? true : false; +} |