static struct characteristic *characteristic_create( struct gatt_db_attribute *attr, struct service *service) { struct characteristic *chrc; bt_uuid_t uuid; chrc = new0(struct characteristic, 1); if (!chrc) return NULL; chrc->descs = queue_new(); if (!chrc->descs) { free(chrc); return NULL; } chrc->notify_clients = queue_new(); if (!chrc->notify_clients) { queue_destroy(chrc->descs, NULL); free(chrc); return NULL; } chrc->service = service; gatt_db_attribute_get_char_data(attr, &chrc->handle, &chrc->value_handle, &chrc->props, &uuid); chrc->attr = gatt_db_get_attribute(service->client->db, chrc->value_handle); if (!chrc->attr) { error("Attribute 0x%04x not found", chrc->value_handle); characteristic_free(chrc); return NULL; } bt_uuid_to_uuid128(&uuid, &chrc->uuid); chrc->path = g_strdup_printf("%s/char%04x", service->path, chrc->handle); if (!g_dbus_register_interface(btd_get_dbus_connection(), chrc->path, GATT_CHARACTERISTIC_IFACE, characteristic_methods, NULL, characteristic_properties, chrc, characteristic_free)) { error("Unable to register GATT characteristic with handle " "0x%04x", chrc->handle); characteristic_free(chrc); return NULL; } DBG("Exported GATT characteristic: %s", chrc->path); return chrc; }
static void write_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct bt_gatt_server *server = user_data; struct gatt_db_attribute *attr; uint16_t handle = 0; struct async_write_op *op = NULL; uint8_t ecode; if (length < 2) { ecode = BT_ATT_ERROR_INVALID_PDU; goto error; } handle = get_le16(pdu); attr = gatt_db_get_attribute(server->db, handle); if (!attr) { ecode = BT_ATT_ERROR_INVALID_HANDLE; goto error; } util_debug(server->debug_callback, server->debug_data, "Write %s - handle: 0x%04x", (opcode == BT_ATT_OP_WRITE_REQ) ? "Req" : "Cmd", handle); ecode = check_permissions(server, attr, BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_AUTHEN | BT_ATT_PERM_WRITE_ENCRYPT); if (ecode) goto error; if (server->pending_write_op) { ecode = BT_ATT_ERROR_UNLIKELY; goto error; } op = new0(struct async_write_op, 1); op->server = server; op->opcode = opcode; server->pending_write_op = op; if (gatt_db_attribute_write(attr, 0, pdu + 2, length - 2, opcode, server->att, write_complete_cb, op)) return; if (op) async_write_op_destroy(op); ecode = BT_ATT_ERROR_UNLIKELY; error: if (opcode == BT_ATT_OP_WRITE_CMD) return; bt_att_send_error_rsp(server->att, opcode, handle, ecode); }
static void prep_write_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct bt_gatt_server *server = user_data; uint16_t handle = 0; uint16_t offset; struct gatt_db_attribute *attr; struct prep_write_complete_data *pwcd; uint8_t ecode, status; if (length < 4) { ecode = BT_ATT_ERROR_INVALID_PDU; goto error; } if (queue_length(server->prep_queue) >= server->max_prep_queue_len) { ecode = BT_ATT_ERROR_PREPARE_QUEUE_FULL; goto error; } handle = get_le16(pdu); offset = get_le16(pdu + 2); attr = gatt_db_get_attribute(server->db, handle); if (!attr) { ecode = BT_ATT_ERROR_INVALID_HANDLE; goto error; } util_debug(server->debug_callback, server->debug_data, "Prep Write Req - handle: 0x%04x", handle); ecode = check_permissions(server, attr, BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_AUTHEN | BT_ATT_PERM_WRITE_ENCRYPT); if (ecode) goto error; pwcd = new0(struct prep_write_complete_data, 1); pwcd->pdu = malloc(length); memcpy(pwcd->pdu, pdu, length); pwcd->length = length; pwcd->server = server; status = gatt_db_attribute_write(attr, offset, NULL, 0, BT_ATT_OP_PREP_WRITE_REQ, server->att, prep_write_complete_cb, pwcd); if (status) return; ecode = BT_ATT_ERROR_UNLIKELY; error: bt_att_send_error_rsp(server->att, opcode, handle, ecode); }
static void handle_read_req(struct bt_gatt_server *server, uint8_t opcode, uint16_t handle, uint16_t offset) { struct gatt_db_attribute *attr; uint8_t ecode; uint32_t perm; struct async_read_op *op = NULL; attr = gatt_db_get_attribute(server->db, handle); if (!attr) { ecode = BT_ATT_ERROR_INVALID_HANDLE; goto error; } util_debug(server->debug_callback, server->debug_data, "Read %sReq - handle: 0x%04x", opcode == BT_ATT_OP_READ_BLOB_REQ ? "Blob " : "", handle); perm = gatt_db_attribute_get_permissions(attr); if (perm && !(perm & BT_ATT_PERM_READ)) { ecode = BT_ATT_ERROR_READ_NOT_PERMITTED; goto error; } if (server->pending_read_op) { ecode = BT_ATT_ERROR_UNLIKELY; goto error; } op = new0(struct async_read_op, 1); if (!op) { ecode = BT_ATT_ERROR_INSUFFICIENT_RESOURCES; goto error; } op->opcode = opcode; op->server = server; server->pending_read_op = op; if (gatt_db_attribute_read(attr, offset, opcode, server->att, read_complete_cb, op)) return; ecode = BT_ATT_ERROR_UNLIKELY; error: if (op) async_read_op_destroy(op); bt_att_send_error_rsp(server->att, opcode, handle, ecode); }
static void read_multiple_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct bt_gatt_server *server = user_data; struct gatt_db_attribute *attr; struct read_multiple_resp_data data; uint8_t ecode = BT_ATT_ERROR_UNLIKELY; size_t i = 0; data.handles = NULL; data.rsp_data = NULL; if (length < 4) { ecode = BT_ATT_ERROR_INVALID_PDU; goto error; } data.server = server; data.num_handles = length / 2; data.cur_handle = 0; data.mtu = bt_att_get_mtu(server->att); data.length = 0; data.rsp_data = malloc(data.mtu - 1); if (!data.rsp_data) goto error; data.handles = new0(uint16_t, data.num_handles); if (!data.handles) goto error; for (i = 0; i < data.num_handles; i++) data.handles[i] = get_le16(pdu + i * 2); util_debug(server->debug_callback, server->debug_data, "Read Multiple Req - %zu handles, 1st: 0x%04x", data.num_handles, data.handles[0]); attr = gatt_db_get_attribute(server->db, data.handles[0]); if (!attr) { ecode = BT_ATT_ERROR_INVALID_HANDLE; goto error; } if (gatt_db_attribute_read(attr, 0, opcode, server->att, read_multiple_complete_cb, &data)) return; error: read_multiple_resp_data_free(&data); bt_att_send_error_rsp(server->att, opcode, 0, ecode); }
static void handle_read_req(struct bt_gatt_server *server, uint8_t opcode, uint16_t handle, uint16_t offset) { struct gatt_db_attribute *attr; uint8_t ecode; struct async_read_op *op = NULL; ecode = authorize_req(server, opcode, handle); if (ecode) goto error; attr = gatt_db_get_attribute(server->db, handle); if (!attr) { ecode = BT_ATT_ERROR_INVALID_HANDLE; goto error; } util_debug(server->debug_callback, server->debug_data, "Read %sReq - handle: 0x%04x", opcode == BT_ATT_OP_READ_BLOB_REQ ? "Blob " : "", handle); ecode = check_permissions(server, attr, BT_ATT_PERM_READ | BT_ATT_PERM_READ_AUTHEN | BT_ATT_PERM_READ_ENCRYPT); if (ecode) goto error; if (server->pending_read_op) { ecode = BT_ATT_ERROR_UNLIKELY; goto error; } op = new0(struct async_read_op, 1); op->opcode = opcode; op->server = server; server->pending_read_op = op; if (gatt_db_attribute_read(attr, offset, opcode, server->att, read_complete_cb, op)) return; ecode = BT_ATT_ERROR_UNLIKELY; error: if (op) async_read_op_destroy(op); bt_att_send_error_rsp(server->att, opcode, handle, ecode); }
static void prep_write_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct bt_gatt_server *server = user_data; uint16_t handle = 0; uint16_t offset; struct gatt_db_attribute *attr; uint8_t ecode; if (length < 4) { ecode = BT_ATT_ERROR_INVALID_PDU; goto error; } if (queue_length(server->prep_queue) >= server->max_prep_queue_len) { ecode = BT_ATT_ERROR_PREPARE_QUEUE_FULL; goto error; } handle = get_le16(pdu); offset = get_le16(pdu + 2); attr = gatt_db_get_attribute(server->db, handle); if (!attr) { ecode = BT_ATT_ERROR_INVALID_HANDLE; goto error; } util_debug(server->debug_callback, server->debug_data, "Prep Write Req - handle: 0x%04x", handle); ecode = check_permissions(server, attr, BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_AUTHEN | BT_ATT_PERM_WRITE_ENCRYPT); if (ecode) goto error; if (!store_prep_data(server, handle, offset, length - 4, &((uint8_t *) pdu)[4])) { ecode = BT_ATT_ERROR_INSUFFICIENT_RESOURCES; goto error; } bt_att_send(server->att, BT_ATT_OP_PREP_WRITE_RSP, pdu, length, NULL, NULL, NULL); return; error: bt_att_send_error_rsp(server->att, opcode, handle, ecode); }
static bool is_reliable_write_supported(const struct bt_gatt_server *server, uint16_t handle) { struct gatt_db_attribute *attr; uint16_t ext_prop; attr = gatt_db_get_attribute(server->db, handle); if (!attr) return false; if (!gatt_db_attribute_get_char_data(attr, NULL, NULL, NULL, &ext_prop, NULL)) return false; return (ext_prop & BT_GATT_CHRC_EXT_PROP_RELIABLE_WRITE); }
static void cmd_heart_rate(struct server *server, char *cmd_str) { bool enable; uint8_t pdu[4]; struct gatt_db_attribute *attr; if (!cmd_str) { heart_rate_usage(); return; } if (strcmp(cmd_str, "on") == 0) enable = true; else if (strcmp(cmd_str, "off") == 0) enable = false; else { heart_rate_usage(); return; } if (enable == server->hr_visible) { printf("Heart Rate Service already %s\n", enable ? "visible" : "hidden"); return; } server->hr_visible = enable; attr = gatt_db_get_attribute(server->db, server->hr_handle); gatt_db_service_set_active(attr, server->hr_visible); update_hr_msrmt_simulation(server); if (!server->svc_chngd_enabled) return; put_le16(server->hr_handle, pdu); put_le16(server->hr_handle + 7, pdu + 2); server->hr_msrmt_enabled = false; update_hr_msrmt_simulation(server); bt_gatt_server_send_indication(server->gatt, server->gatt_svc_chngd_handle, pdu, 4, conf_cb, NULL, NULL); }
static void exec_next_prep_write(struct bt_gatt_server *server, uint16_t ehandle, int err) { struct prep_write_data *next = NULL; struct gatt_db_attribute *attr; bool status; if (err) goto error; next = queue_pop_head(server->prep_queue); if (!next) { bt_att_send(server->att, BT_ATT_OP_EXEC_WRITE_RSP, NULL, 0, NULL, NULL, NULL); return; } attr = gatt_db_get_attribute(server->db, next->handle); if (!attr) { err = BT_ATT_ERROR_UNLIKELY; goto error; } status = gatt_db_attribute_write(attr, next->offset, next->value, next->length, BT_ATT_OP_EXEC_WRITE_REQ, server->att, exec_write_complete_cb, server); prep_write_data_destroy(next); if (status) return; err = BT_ATT_ERROR_UNLIKELY; error: queue_remove_all(server->prep_queue, NULL, NULL, prep_write_data_destroy); bt_att_send_error_rsp(server->att, BT_ATT_OP_EXEC_WRITE_REQ, ehandle, err); }
static void print_incl(struct gatt_db_attribute *attr, void *user_data) { struct server *server = user_data; uint16_t handle, start, end; struct gatt_db_attribute *service; bt_uuid_t uuid; if (!gatt_db_attribute_get_incl_data(attr, &handle, &start, &end)) return; service = gatt_db_get_attribute(server->db, start); if (!service) return; gatt_db_attribute_get_service_uuid(service, &uuid); printf("\t " COLOR_GREEN "include" COLOR_OFF " - handle: " "0x%04x, - start: 0x%04x, end: 0x%04x," "uuid: ", handle, start, end); print_uuid(&uuid); }
static void read_multiple_complete_cb(struct gatt_db_attribute *attr, int err, const uint8_t *value, size_t len, void *user_data) { struct read_multiple_resp_data *data = user_data; struct gatt_db_attribute *next_attr; uint32_t perm; uint16_t handle = gatt_db_attribute_get_handle(attr); if (err != 0) { bt_att_send_error_rsp(data->server->att, BT_ATT_OP_READ_MULT_REQ, handle, err); read_multiple_resp_data_free(data); return; } perm = gatt_db_attribute_get_permissions(attr); if (perm && !(perm & BT_ATT_PERM_READ)) { bt_att_send_error_rsp(data->server->att, BT_ATT_OP_READ_MULT_REQ, handle, BT_ATT_ERROR_READ_NOT_PERMITTED); read_multiple_resp_data_free(data); return; } len = MIN(len, data->mtu - data->length - 1); memcpy(data->rsp_data + data->length, value, len); data->length += len; data->cur_handle++; if ((data->length >= data->mtu - 1) || (data->cur_handle == data->num_handles)) { bt_att_send(data->server->att, BT_ATT_OP_READ_MULT_RSP, data->rsp_data, data->length, NULL, NULL, NULL); read_multiple_resp_data_free(data); return; } util_debug(data->server->debug_callback, data->server->debug_data, "Read Multiple Req - #%zu of %zu: 0x%04x", data->cur_handle + 1, data->num_handles, data->handles[data->cur_handle]); next_attr = gatt_db_get_attribute(data->server->db, data->handles[data->cur_handle]); if (!next_attr) { bt_att_send_error_rsp(data->server->att, BT_ATT_OP_READ_MULT_REQ, data->handles[data->cur_handle], BT_ATT_ERROR_INVALID_HANDLE); read_multiple_resp_data_free(data); return; } if (!gatt_db_attribute_read(next_attr, 0, BT_ATT_OP_READ_MULT_REQ, data->server->att, read_multiple_complete_cb, data)) { bt_att_send_error_rsp(data->server->att, BT_ATT_OP_READ_MULT_REQ, data->handles[data->cur_handle], BT_ATT_ERROR_UNLIKELY); read_multiple_resp_data_free(data); } }
static void prep_write_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct bt_gatt_server *server = user_data; struct prep_write_data *prep_data = NULL; uint16_t handle = 0; uint16_t offset; struct gatt_db_attribute *attr; uint8_t ecode; uint32_t perm; if (length < 4) { ecode = BT_ATT_ERROR_INVALID_PDU; goto error; } if (queue_length(server->prep_queue) >= server->max_prep_queue_len) { ecode = BT_ATT_ERROR_PREPARE_QUEUE_FULL; goto error; } handle = get_le16(pdu); offset = get_le16(pdu + 2); attr = gatt_db_get_attribute(server->db, handle); if (!attr) { ecode = BT_ATT_ERROR_INVALID_HANDLE; goto error; } util_debug(server->debug_callback, server->debug_data, "Prep Write Req - handle: 0x%04x", handle); perm = gatt_db_attribute_get_permissions(attr); /* * TODO: The "Prepare Write" request requires security permission checks * to be performed before the write is executed. I.e., we can't leave * the permission check to the upper layer since we can't call * gatt_db_write until the entire queue is atomically processed during * an "Execute Write" request. Figure out how to make this check here. */ if (!(perm & BT_ATT_PERM_WRITE)) { ecode = BT_ATT_ERROR_WRITE_NOT_PERMITTED; goto error; } prep_data = new0(struct prep_write_data, 1); if (!prep_data) { ecode = BT_ATT_ERROR_INSUFFICIENT_RESOURCES; goto error; } prep_data->length = length - 4; if (prep_data->length) { prep_data->value = malloc(prep_data->length); if (!prep_data->value) { ecode = BT_ATT_ERROR_INSUFFICIENT_RESOURCES; goto error; } } prep_data->server = server; prep_data->handle = handle; prep_data->offset = offset; memcpy(prep_data->value, pdu + 4, prep_data->length); queue_push_tail(server->prep_queue, prep_data); bt_att_send(server->att, BT_ATT_OP_PREP_WRITE_RSP, pdu, length, NULL, NULL, NULL); return; error: if (prep_data) prep_write_data_destroy(prep_data); bt_att_send_error_rsp(server->att, opcode, handle, ecode); }