/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ #pragma diag_suppress 111 #include #include #include #include #include "os/os_mempool.h" #include "nimble/ble.h" #include "host/ble_uuid.h" #include "ble_hs_priv.h" #if NIMBLE_BLE_CONNECT /***************************************************************************** * $error response * *****************************************************************************/ int ble_att_clt_rx_error(uint16_t conn_handle, struct os_mbuf **rxom) { struct ble_att_error_rsp *rsp; int rc; rc = ble_hs_mbuf_pullup_base(rxom, sizeof(*rsp)); if (rc != 0) { return rc; } rsp = (struct ble_att_error_rsp *)(*rxom)->om_data; ble_gattc_rx_err(conn_handle, le16toh(rsp->baep_handle), le16toh(rsp->baep_error_code)); return 0; } /***************************************************************************** * $mtu exchange * *****************************************************************************/ int ble_att_clt_tx_mtu(uint16_t conn_handle, uint16_t mtu) { struct ble_att_mtu_cmd *req; struct ble_l2cap_chan *chan; struct ble_hs_conn *conn; struct os_mbuf *txom; int rc; if (mtu < BLE_ATT_MTU_DFLT) { return BLE_HS_EINVAL; } ble_hs_lock(); rc = ble_att_conn_chan_find(conn_handle, &conn, &chan); if (rc != 0) { rc = BLE_HS_ENOTCONN; } else if (chan->flags & BLE_L2CAP_CHAN_F_TXED_MTU) { rc = BLE_HS_EALREADY; } else { rc = 0; } ble_hs_unlock(); if (rc != 0) { return rc; } req = ble_att_cmd_get(BLE_ATT_OP_MTU_REQ, sizeof(*req), &txom); if (req == NULL) { return BLE_HS_ENOMEM; } req->bamc_mtu = htole16(mtu); rc = ble_att_tx(conn_handle, txom); if (rc != 0) { return rc; } ble_hs_lock(); rc = ble_att_conn_chan_find(conn_handle, &conn, &chan); if (rc == 0) { chan->flags |= BLE_L2CAP_CHAN_F_TXED_MTU; } ble_hs_unlock(); return rc; } int ble_att_clt_rx_mtu(uint16_t conn_handle, struct os_mbuf **rxom) { struct ble_att_mtu_cmd *cmd; struct ble_l2cap_chan *chan; uint16_t mtu; int rc; mtu = 0; rc = ble_hs_mbuf_pullup_base(rxom, sizeof(*cmd)); if (rc == 0) { cmd = (struct ble_att_mtu_cmd *)(*rxom)->om_data; ble_hs_lock(); rc = ble_att_conn_chan_find(conn_handle, NULL, &chan); if (rc == 0) { ble_att_set_peer_mtu(chan, le16toh(cmd->bamc_mtu)); mtu = ble_att_chan_mtu(chan); } ble_hs_unlock(); if (rc == 0) { ble_gap_mtu_event(conn_handle, BLE_L2CAP_CID_ATT, mtu); } } ble_gattc_rx_mtu(conn_handle, rc, mtu); return rc; } /***************************************************************************** * $find information * *****************************************************************************/ int ble_att_clt_tx_find_info(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle) { #if !NIMBLE_BLE_ATT_CLT_FIND_INFO return BLE_HS_ENOTSUP; #endif struct ble_att_find_info_req *req; struct os_mbuf *txom; if (start_handle == 0 || start_handle > end_handle) { return BLE_HS_EINVAL; } req = ble_att_cmd_get(BLE_ATT_OP_FIND_INFO_REQ, sizeof(*req), &txom); if (req == NULL) { return BLE_HS_ENOMEM; } req->bafq_start_handle = htole16(start_handle); req->bafq_end_handle = htole16(end_handle); return ble_att_tx(conn_handle, txom); } static int ble_att_clt_parse_find_info_entry(struct os_mbuf **rxom, uint8_t rsp_format, struct ble_att_find_info_idata *idata) { int entry_len; int rc; switch (rsp_format) { case BLE_ATT_FIND_INFO_RSP_FORMAT_16BIT: entry_len = 2 + 2; break; case BLE_ATT_FIND_INFO_RSP_FORMAT_128BIT: entry_len = 2 + 16; break; default: return BLE_HS_EBADDATA; } rc = ble_hs_mbuf_pullup_base(rxom, entry_len); if (rc != 0) { return rc; } idata->attr_handle = get_le16((*rxom)->om_data); switch (rsp_format) { case BLE_ATT_FIND_INFO_RSP_FORMAT_16BIT: rc = ble_uuid_init_from_att_mbuf(&idata->uuid, *rxom, 2, 2); if (rc != 0) { return BLE_HS_EBADDATA; } break; case BLE_ATT_FIND_INFO_RSP_FORMAT_128BIT: rc = ble_uuid_init_from_att_mbuf(&idata->uuid, *rxom, 2, 16); if (rc != 0) { return BLE_HS_EBADDATA; } break; default: BLE_HS_DBG_ASSERT(0); break; } os_mbuf_adj(*rxom, entry_len); return 0; } int ble_att_clt_rx_find_info(uint16_t conn_handle, struct os_mbuf **om) { #if !NIMBLE_BLE_ATT_CLT_FIND_INFO return BLE_HS_ENOTSUP; #endif struct ble_att_find_info_idata idata; struct ble_att_find_info_rsp *rsp; int rc; rc = ble_hs_mbuf_pullup_base(om, sizeof(*rsp)); if (rc != 0) { goto done; } rsp = (struct ble_att_find_info_rsp *)(*om)->om_data; /* Strip the response base from the front of the mbuf. */ os_mbuf_adj((*om), sizeof(*rsp)); while (OS_MBUF_PKTLEN(*om) > 0) { rc = ble_att_clt_parse_find_info_entry(om, rsp->bafp_format, &idata); if (rc != 0) { goto done; } /* Hand find-info entry to GATT. */ ble_gattc_rx_find_info_idata(conn_handle, &idata); } rc = 0; done: /* Notify GATT that response processing is done. */ ble_gattc_rx_find_info_complete(conn_handle, rc); return rc; } /***************************************************************************** * $find by type value * *****************************************************************************/ /* * TODO consider this to accept UUID instead of value, it is used only for this * anyway */ int ble_att_clt_tx_find_type_value(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle, uint16_t attribute_type, const void *attribute_value, int value_len) { #if !NIMBLE_BLE_ATT_CLT_FIND_TYPE return BLE_HS_ENOTSUP; #endif struct ble_att_find_type_value_req *req; struct os_mbuf *txom; if (start_handle == 0 || start_handle > end_handle) { return BLE_HS_EINVAL; } req = ble_att_cmd_get(BLE_ATT_OP_FIND_TYPE_VALUE_REQ, sizeof(*req) + value_len, &txom); if (req == NULL) { return BLE_HS_ENOMEM; } req->bavq_start_handle = htole16(start_handle); req->bavq_end_handle = htole16(end_handle); req->bavq_attr_type = htole16(attribute_type); memcpy(req->bavq_value, attribute_value, value_len); return ble_att_tx(conn_handle, txom); } static int ble_att_clt_parse_find_type_value_hinfo( struct os_mbuf **om, struct ble_att_find_type_value_hinfo *dst) { struct ble_att_handle_group *group; int rc; rc = ble_hs_mbuf_pullup_base(om, sizeof(*group)); if (rc != 0) { return BLE_HS_EBADDATA; } group = (struct ble_att_handle_group *)(*om)->om_data; dst->attr_handle = le16toh(group->attr_handle); dst->group_end_handle = le16toh(group->group_end_handle); os_mbuf_adj((*om), sizeof(*group)); return 0; } int ble_att_clt_rx_find_type_value(uint16_t conn_handle, struct os_mbuf **rxom) { #if !NIMBLE_BLE_ATT_CLT_FIND_TYPE return BLE_HS_ENOTSUP; #endif struct ble_att_find_type_value_hinfo hinfo; int rc; /* Parse the Handles-Information-List field, passing each entry to GATT. */ rc = 0; while (OS_MBUF_PKTLEN(*rxom) > 0) { rc = ble_att_clt_parse_find_type_value_hinfo(rxom, &hinfo); if (rc != 0) { break; } ble_gattc_rx_find_type_value_hinfo(conn_handle, &hinfo); } /* Notify GATT client that the full response has been parsed. */ ble_gattc_rx_find_type_value_complete(conn_handle, rc); return 0; } /***************************************************************************** * $read by type * *****************************************************************************/ int ble_att_clt_tx_read_type(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle, const ble_uuid_t *uuid) { #if !NIMBLE_BLE_ATT_CLT_READ_TYPE return BLE_HS_ENOTSUP; #endif struct ble_att_read_type_req *req; struct os_mbuf *txom; if (start_handle == 0 || start_handle > end_handle) { return BLE_HS_EINVAL; } req = ble_att_cmd_get(BLE_ATT_OP_READ_TYPE_REQ, sizeof(*req) + ble_uuid_length(uuid), &txom); if (req == NULL) { return BLE_HS_ENOMEM; } req->batq_start_handle = htole16(start_handle); req->batq_end_handle = htole16(end_handle); ble_uuid_flat(uuid, req->uuid); return ble_att_tx(conn_handle, txom); } int ble_att_clt_rx_read_type(uint16_t conn_handle, struct os_mbuf **rxom) { #if !NIMBLE_BLE_ATT_CLT_READ_TYPE return BLE_HS_ENOTSUP; #endif struct ble_att_read_type_adata adata; struct ble_att_attr_data_list *data; struct ble_att_read_type_rsp *rsp; uint8_t data_len; int rc; rc = ble_hs_mbuf_pullup_base(rxom, sizeof(*rsp)); if (rc != 0) { goto done; } rsp = (struct ble_att_read_type_rsp *)(*rxom)->om_data; data_len = rsp->batp_length; /* Strip the response base from the front of the mbuf. */ os_mbuf_adj(*rxom, sizeof(*rsp)); if (data_len < sizeof(*data)) { rc = BLE_HS_EBADDATA; goto done; } /* Parse the Attribute Data List field, passing each entry to the GATT. */ while (OS_MBUF_PKTLEN(*rxom) > 0) { rc = ble_hs_mbuf_pullup_base(rxom, data_len); if (rc != 0) { break; } data = (struct ble_att_attr_data_list *)(*rxom)->om_data; adata.att_handle = le16toh(data->handle); adata.value_len = data_len - sizeof(*data); adata.value = data->value; ble_gattc_rx_read_type_adata(conn_handle, &adata); os_mbuf_adj(*rxom, data_len); } done: /* Notify GATT that the response is done being parsed. */ ble_gattc_rx_read_type_complete(conn_handle, rc); return rc; } /***************************************************************************** * $read * *****************************************************************************/ int ble_att_clt_tx_read(uint16_t conn_handle, uint16_t handle) { #if !NIMBLE_BLE_ATT_CLT_READ return BLE_HS_ENOTSUP; #endif struct ble_att_read_req *req; struct os_mbuf *txom; int rc; if (handle == 0) { return BLE_HS_EINVAL; } req = ble_att_cmd_get(BLE_ATT_OP_READ_REQ, sizeof(*req), &txom); if (req == NULL) { return BLE_HS_ENOMEM; } req->barq_handle = htole16(handle); rc = ble_att_tx(conn_handle, txom); if (rc != 0) { return rc; } return 0; } int ble_att_clt_rx_read(uint16_t conn_handle, struct os_mbuf **rxom) { #if !NIMBLE_BLE_ATT_CLT_READ return BLE_HS_ENOTSUP; #endif /* Pass the Attribute Value field to GATT. */ ble_gattc_rx_read_rsp(conn_handle, 0, rxom); return 0; } /***************************************************************************** * $read blob * *****************************************************************************/ int ble_att_clt_tx_read_blob(uint16_t conn_handle, uint16_t handle, uint16_t offset) { #if !NIMBLE_BLE_ATT_CLT_READ_BLOB return BLE_HS_ENOTSUP; #endif struct ble_att_read_blob_req *req; struct os_mbuf *txom; int rc; if (handle == 0) { return BLE_HS_EINVAL; } req = ble_att_cmd_get(BLE_ATT_OP_READ_BLOB_REQ, sizeof(*req), &txom); if (req == NULL) { return BLE_HS_ENOMEM; } req->babq_handle = htole16(handle); req->babq_offset = htole16(offset); rc = ble_att_tx(conn_handle, txom); if (rc != 0) { return rc; } return 0; } int ble_att_clt_rx_read_blob(uint16_t conn_handle, struct os_mbuf **rxom) { #if !NIMBLE_BLE_ATT_CLT_READ_BLOB return BLE_HS_ENOTSUP; #endif /* Pass the Attribute Value field to GATT. */ ble_gattc_rx_read_blob_rsp(conn_handle, 0, rxom); return 0; } /***************************************************************************** * $read multiple * *****************************************************************************/ int ble_att_clt_tx_read_mult(uint16_t conn_handle, const uint16_t *handles, int num_handles) { #if !NIMBLE_BLE_ATT_CLT_READ_MULT return BLE_HS_ENOTSUP; #endif struct ble_att_read_mult_req *req; struct os_mbuf *txom; int i; if (num_handles < 1) { return BLE_HS_EINVAL; } req = ble_att_cmd_get(BLE_ATT_OP_READ_MULT_REQ, sizeof(req->handles[0]) * num_handles, &txom); if (req == NULL) { return BLE_HS_ENOMEM; } for(i = 0; i < num_handles; i++) { req->handles[i] = htole16(handles[i]); } return ble_att_tx(conn_handle, txom); } int ble_att_clt_rx_read_mult(uint16_t conn_handle, struct os_mbuf **rxom) { #if !NIMBLE_BLE_ATT_CLT_READ_MULT return BLE_HS_ENOTSUP; #endif /* Pass the Attribute Value field to GATT. */ ble_gattc_rx_read_mult_rsp(conn_handle, 0, rxom); return 0; } /***************************************************************************** * $read by group type * *****************************************************************************/ int ble_att_clt_tx_read_group_type(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle, const ble_uuid_t *uuid) { #if !NIMBLE_BLE_ATT_CLT_READ_GROUP_TYPE return BLE_HS_ENOTSUP; #endif struct ble_att_read_group_type_req *req; struct os_mbuf *txom; if (start_handle == 0 || start_handle > end_handle) { return BLE_HS_EINVAL; } req = ble_att_cmd_get(BLE_ATT_OP_READ_GROUP_TYPE_REQ, sizeof(*req) + ble_uuid_length(uuid), &txom); if (req == NULL) { return BLE_HS_ENOMEM; } req->bagq_start_handle = htole16(start_handle); req->bagq_end_handle = htole16(end_handle); ble_uuid_flat(uuid, req->uuid); return ble_att_tx(conn_handle, txom); } static int ble_att_clt_parse_read_group_type_adata( struct os_mbuf **om, int data_len, struct ble_att_read_group_type_adata *adata) { int rc; if (data_len < BLE_ATT_READ_GROUP_TYPE_ADATA_BASE_SZ + 1) { return BLE_HS_EMSGSIZE; } rc = ble_hs_mbuf_pullup_base(om, data_len); if (rc != 0) { return rc; } adata->att_handle = get_le16((*om)->om_data + 0); adata->end_group_handle = get_le16((*om)->om_data + 2); adata->value_len = data_len - BLE_ATT_READ_GROUP_TYPE_ADATA_BASE_SZ; adata->value = (*om)->om_data + BLE_ATT_READ_GROUP_TYPE_ADATA_BASE_SZ; return 0; } int ble_att_clt_rx_read_group_type(uint16_t conn_handle, struct os_mbuf **rxom) { #if !NIMBLE_BLE_ATT_CLT_READ_GROUP_TYPE return BLE_HS_ENOTSUP; #endif struct ble_att_read_group_type_adata adata; struct ble_att_read_group_type_rsp *rsp; uint8_t len; int rc; rc = ble_hs_mbuf_pullup_base(rxom, sizeof(*rsp)); if (rc != 0) { goto done; } rsp = (struct ble_att_read_group_type_rsp *)(*rxom)->om_data; len = rsp->bagp_length; /* Strip the base from the front of the response. */ os_mbuf_adj(*rxom, sizeof(*rsp)); /* Parse the Attribute Data List field, passing each entry to GATT. */ while (OS_MBUF_PKTLEN(*rxom) > 0) { rc = ble_att_clt_parse_read_group_type_adata(rxom, len, &adata); if (rc != 0) { goto done; } ble_gattc_rx_read_group_type_adata(conn_handle, &adata); os_mbuf_adj(*rxom, len); } done: /* Notify GATT that the response is done being parsed. */ ble_gattc_rx_read_group_type_complete(conn_handle, rc); return rc; } /***************************************************************************** * $write * *****************************************************************************/ int ble_att_clt_tx_write_req(uint16_t conn_handle, uint16_t handle, struct os_mbuf *txom) { #if !NIMBLE_BLE_ATT_CLT_WRITE return BLE_HS_ENOTSUP; #endif struct ble_att_write_req *req; struct os_mbuf *txom2; req = ble_att_cmd_get(BLE_ATT_OP_WRITE_REQ, sizeof(*req), &txom2); if (req == NULL) { os_mbuf_free_chain(txom); return BLE_HS_ENOMEM; } req->bawq_handle = htole16(handle); os_mbuf_concat(txom2, txom); return ble_att_tx(conn_handle, txom2); } int ble_att_clt_tx_write_cmd(uint16_t conn_handle, uint16_t handle, struct os_mbuf *txom) { #if !NIMBLE_BLE_ATT_CLT_WRITE_NO_RSP return BLE_HS_ENOTSUP; #endif struct ble_att_write_cmd *cmd; struct os_mbuf *txom2; uint8_t b; int rc; int i; BLE_HS_LOG(DEBUG, "ble_att_clt_tx_write_cmd(): "); for (i = 0; i < OS_MBUF_PKTLEN(txom); i++) { if (i != 0) { BLE_HS_LOG(DEBUG, ":"); } rc = os_mbuf_copydata(txom, i, 1, &b); assert(rc == 0); BLE_HS_LOG(DEBUG, "0x%02x", b); } cmd = ble_att_cmd_get(BLE_ATT_OP_WRITE_CMD, sizeof(*cmd), &txom2); if (cmd == NULL) { os_mbuf_free_chain(txom); return BLE_HS_ENOMEM; } cmd->handle = htole16(handle); os_mbuf_concat(txom2, txom); return ble_att_tx(conn_handle, txom2); } int ble_att_clt_rx_write(uint16_t conn_handle, struct os_mbuf **rxom) { #if !NIMBLE_BLE_ATT_CLT_WRITE return BLE_HS_ENOTSUP; #endif /* No payload. */ ble_gattc_rx_write_rsp(conn_handle); return 0; } /***************************************************************************** * $prepare write request * *****************************************************************************/ int ble_att_clt_tx_prep_write(uint16_t conn_handle, uint16_t handle, uint16_t offset, struct os_mbuf *txom) { #if !NIMBLE_BLE_ATT_CLT_PREP_WRITE return BLE_HS_ENOTSUP; #endif struct ble_att_prep_write_cmd *req; struct os_mbuf *txom2; int rc; if (handle == 0) { rc = BLE_HS_EINVAL; goto err; } if (offset + OS_MBUF_PKTLEN(txom) > BLE_ATT_ATTR_MAX_LEN) { rc = BLE_HS_EINVAL; goto err; } if (OS_MBUF_PKTLEN(txom) > ble_att_mtu(conn_handle) - BLE_ATT_PREP_WRITE_CMD_BASE_SZ) { rc = BLE_HS_EINVAL; goto err; } req = ble_att_cmd_get(BLE_ATT_OP_PREP_WRITE_REQ, sizeof(*req), &txom2); if (req == NULL) { rc = BLE_HS_ENOMEM; goto err; } req->bapc_handle = htole16(handle); req->bapc_offset = htole16(offset); os_mbuf_concat(txom2, txom); return ble_att_tx(conn_handle, txom2); err: os_mbuf_free_chain(txom); return rc; } int ble_att_clt_rx_prep_write(uint16_t conn_handle, struct os_mbuf **rxom) { #if !NIMBLE_BLE_ATT_CLT_PREP_WRITE return BLE_HS_ENOTSUP; #endif struct ble_att_prep_write_cmd *rsp; uint16_t handle, offset; int rc; /* Initialize some values in case of early error. */ handle = 0; offset = 0; rc = ble_hs_mbuf_pullup_base(rxom, sizeof(*rsp)); if (rc != 0) { goto done; } rsp = (struct ble_att_prep_write_cmd *)(*rxom)->om_data; handle = le16toh(rsp->bapc_handle); offset = le16toh(rsp->bapc_offset); /* Strip the base from the front of the response. */ os_mbuf_adj(*rxom, sizeof(*rsp)); done: /* Notify GATT client that the full response has been parsed. */ ble_gattc_rx_prep_write_rsp(conn_handle, rc, handle, offset, rxom); return rc; } /***************************************************************************** * $execute write request * *****************************************************************************/ int ble_att_clt_tx_exec_write(uint16_t conn_handle, uint8_t flags) { #if !NIMBLE_BLE_ATT_CLT_EXEC_WRITE return BLE_HS_ENOTSUP; #endif struct ble_att_exec_write_req *req; struct os_mbuf *txom; int rc; req = ble_att_cmd_get(BLE_ATT_OP_EXEC_WRITE_REQ, sizeof(*req), &txom); if (req == NULL) { return BLE_HS_ENOMEM; } req->baeq_flags = flags; rc = ble_att_tx(conn_handle, txom); if (rc != 0) { return rc; } return 0; } int ble_att_clt_rx_exec_write(uint16_t conn_handle, struct os_mbuf **rxom) { #if !NIMBLE_BLE_ATT_CLT_EXEC_WRITE return BLE_HS_ENOTSUP; #endif ble_gattc_rx_exec_write_rsp(conn_handle, 0); return 0; } /***************************************************************************** * $handle value notification * *****************************************************************************/ int ble_att_clt_tx_notify(uint16_t conn_handle, uint16_t handle, struct os_mbuf *txom) { #if !NIMBLE_BLE_ATT_CLT_NOTIFY return BLE_HS_ENOTSUP; #endif struct ble_att_notify_req *req; struct os_mbuf *txom2; int rc; if (handle == 0) { rc = BLE_HS_EINVAL; goto err; } req = ble_att_cmd_get(BLE_ATT_OP_NOTIFY_REQ, sizeof(*req), &txom2); if (req == NULL) { rc = BLE_HS_ENOMEM; goto err; } req->banq_handle = htole16(handle); os_mbuf_concat(txom2, txom); return ble_att_tx(conn_handle, txom2); err: os_mbuf_free_chain(txom); return rc; } /***************************************************************************** * $handle value indication * *****************************************************************************/ int ble_att_clt_tx_indicate(uint16_t conn_handle, uint16_t handle, struct os_mbuf *txom) { #if !NIMBLE_BLE_ATT_CLT_INDICATE return BLE_HS_ENOTSUP; #endif struct ble_att_indicate_req *req; struct os_mbuf *txom2; int rc; if (handle == 0) { rc = BLE_HS_EINVAL; goto err; } req = ble_att_cmd_get(BLE_ATT_OP_INDICATE_REQ, sizeof(*req), &txom2); if (req == NULL) { rc = BLE_HS_ENOMEM; goto err; } req->baiq_handle = htole16(handle); os_mbuf_concat(txom2, txom); return ble_att_tx(conn_handle, txom2); err: os_mbuf_free_chain(txom); return rc; } int ble_att_clt_rx_indicate(uint16_t conn_handle, struct os_mbuf **rxom) { #if !NIMBLE_BLE_ATT_CLT_INDICATE return BLE_HS_ENOTSUP; #endif /* No payload. */ ble_gatts_rx_indicate_rsp(conn_handle); return 0; } #endif