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); }
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. */ switch (op->type) { case ATT_OP_TYPE_REQ: att->pending_req = op; break; case ATT_OP_TYPE_IND: att->pending_ind = op; break; 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 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) { /* Return if error response cause a retry */ if (handle_error_rsp(att, pdu, pdu_len, &req_opcode)) { wakeup_writer(att); return; } } 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 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 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; 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); return false; }
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) destroy_att_send_op(att->pending_req); if (att->pending_ind) destroy_att_send_op(att->pending_ind); return true; }
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(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) { 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); wakeup_writer(att); return true; }
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); }
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; }