| /* |
| * 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. |
| */ |
| |
| #include <stddef.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include "nimble/ble.h" |
| #include "host/ble_gatt.h" |
| #include "host/ble_uuid.h" |
| #include "host/ble_store.h" |
| #include "ble_hs_priv.h" |
| |
| #define BLE_GATTS_INCLUDE_SZ 6 |
| #define BLE_GATTS_CHR_MAX_SZ 19 |
| |
| static const ble_uuid_t *uuid_pri = |
| BLE_UUID16_DECLARE(BLE_ATT_UUID_PRIMARY_SERVICE); |
| static const ble_uuid_t *uuid_sec = |
| BLE_UUID16_DECLARE(BLE_ATT_UUID_SECONDARY_SERVICE); |
| static const ble_uuid_t *uuid_inc = |
| BLE_UUID16_DECLARE(BLE_ATT_UUID_INCLUDE); |
| static const ble_uuid_t *uuid_chr = |
| BLE_UUID16_DECLARE(BLE_ATT_UUID_CHARACTERISTIC); |
| static const ble_uuid_t *uuid_ccc = |
| BLE_UUID16_DECLARE(BLE_GATT_DSC_CLT_CFG_UUID16); |
| |
| static const struct ble_gatt_svc_def **ble_gatts_svc_defs; |
| static int ble_gatts_num_svc_defs; |
| |
| struct ble_gatts_svc_entry { |
| const struct ble_gatt_svc_def *svc; |
| uint16_t handle; /* 0 means unregistered. */ |
| uint16_t end_group_handle; /* 0xffff means unset. */ |
| }; |
| |
| static struct ble_gatts_svc_entry *ble_gatts_svc_entries; |
| static uint16_t ble_gatts_num_svc_entries; |
| |
| static os_membuf_t *ble_gatts_clt_cfg_mem; |
| static struct os_mempool ble_gatts_clt_cfg_pool; |
| |
| struct ble_gatts_clt_cfg { |
| uint16_t chr_val_handle; |
| uint8_t flags; |
| uint8_t allowed; |
| }; |
| |
| /** A cached array of handles for the configurable characteristics. */ |
| static struct ble_gatts_clt_cfg *ble_gatts_clt_cfgs; |
| static int ble_gatts_num_cfgable_chrs; |
| |
| STATS_SECT_DECL(ble_gatts_stats) ble_gatts_stats; |
| STATS_NAME_START(ble_gatts_stats) |
| STATS_NAME(ble_gatts_stats, svcs) |
| STATS_NAME(ble_gatts_stats, chrs) |
| STATS_NAME(ble_gatts_stats, dscs) |
| STATS_NAME(ble_gatts_stats, svc_def_reads) |
| STATS_NAME(ble_gatts_stats, svc_inc_reads) |
| STATS_NAME(ble_gatts_stats, chr_def_reads) |
| STATS_NAME(ble_gatts_stats, chr_val_reads) |
| STATS_NAME(ble_gatts_stats, chr_val_writes) |
| STATS_NAME(ble_gatts_stats, dsc_reads) |
| STATS_NAME(ble_gatts_stats, dsc_writes) |
| STATS_NAME_END(ble_gatts_stats) |
| |
| static int |
| ble_gatts_svc_access(uint16_t conn_handle, uint16_t attr_handle, |
| uint8_t op, uint16_t offset, struct os_mbuf **om, |
| void *arg) |
| { |
| const struct ble_gatt_svc_def *svc; |
| uint8_t *buf; |
| |
| STATS_INC(ble_gatts_stats, svc_def_reads); |
| |
| BLE_HS_DBG_ASSERT(op == BLE_ATT_ACCESS_OP_READ); |
| |
| svc = arg; |
| |
| buf = os_mbuf_extend(*om, ble_uuid_length(svc->uuid)); |
| if (buf == NULL) { |
| return BLE_ATT_ERR_INSUFFICIENT_RES; |
| } |
| |
| ble_uuid_flat(svc->uuid, buf); |
| |
| return 0; |
| } |
| |
| static int |
| ble_gatts_inc_access(uint16_t conn_handle, uint16_t attr_handle, |
| uint8_t op, uint16_t offset, struct os_mbuf **om, |
| void *arg) |
| { |
| const struct ble_gatts_svc_entry *entry; |
| uint16_t uuid16; |
| uint8_t *buf; |
| |
| STATS_INC(ble_gatts_stats, svc_inc_reads); |
| |
| BLE_HS_DBG_ASSERT(op == BLE_ATT_ACCESS_OP_READ); |
| |
| entry = arg; |
| |
| buf = os_mbuf_extend(*om, 4); |
| if (buf == NULL) { |
| return BLE_ATT_ERR_INSUFFICIENT_RES; |
| } |
| put_le16(buf + 0, entry->handle); |
| put_le16(buf + 2, entry->end_group_handle); |
| |
| /* Only include the service UUID if it has a 16-bit representation. */ |
| uuid16 = ble_uuid_u16(entry->svc->uuid); |
| if (uuid16 != 0) { |
| buf = os_mbuf_extend(*om, 2); |
| if (buf == NULL) { |
| return BLE_ATT_ERR_INSUFFICIENT_RES; |
| } |
| put_le16(buf, uuid16); |
| } |
| |
| return 0; |
| } |
| |
| static uint16_t |
| ble_gatts_chr_clt_cfg_allowed(const struct ble_gatt_chr_def *chr) |
| { |
| uint16_t flags; |
| |
| flags = 0; |
| if (chr->flags & BLE_GATT_CHR_F_NOTIFY) { |
| flags |= BLE_GATTS_CLT_CFG_F_NOTIFY; |
| } |
| if (chr->flags & BLE_GATT_CHR_F_INDICATE) { |
| flags |= BLE_GATTS_CLT_CFG_F_INDICATE; |
| } |
| |
| return flags; |
| } |
| |
| static uint8_t |
| ble_gatts_att_flags_from_chr_flags(ble_gatt_chr_flags chr_flags) |
| { |
| uint8_t att_flags; |
| |
| att_flags = 0; |
| if (chr_flags & BLE_GATT_CHR_F_READ) { |
| att_flags |= BLE_ATT_F_READ; |
| } |
| if (chr_flags & (BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_WRITE)) { |
| att_flags |= BLE_ATT_F_WRITE; |
| } |
| if (chr_flags & BLE_GATT_CHR_F_READ_ENC) { |
| att_flags |= BLE_ATT_F_READ_ENC; |
| } |
| if (chr_flags & BLE_GATT_CHR_F_READ_AUTHEN) { |
| att_flags |= BLE_ATT_F_READ_AUTHEN; |
| } |
| if (chr_flags & BLE_GATT_CHR_F_READ_AUTHOR) { |
| att_flags |= BLE_ATT_F_READ_AUTHOR; |
| } |
| if (chr_flags & BLE_GATT_CHR_F_WRITE_ENC) { |
| att_flags |= BLE_ATT_F_WRITE_ENC; |
| } |
| if (chr_flags & BLE_GATT_CHR_F_WRITE_AUTHEN) { |
| att_flags |= BLE_ATT_F_WRITE_AUTHEN; |
| } |
| if (chr_flags & BLE_GATT_CHR_F_WRITE_AUTHOR) { |
| att_flags |= BLE_ATT_F_WRITE_AUTHOR; |
| } |
| |
| return att_flags; |
| } |
| |
| static uint8_t |
| ble_gatts_chr_properties(const struct ble_gatt_chr_def *chr) |
| { |
| uint8_t properties; |
| |
| properties = 0; |
| |
| if (chr->flags & BLE_GATT_CHR_F_BROADCAST) { |
| properties |= BLE_GATT_CHR_PROP_BROADCAST; |
| } |
| if (chr->flags & BLE_GATT_CHR_F_READ) { |
| properties |= BLE_GATT_CHR_PROP_READ; |
| } |
| if (chr->flags & BLE_GATT_CHR_F_WRITE_NO_RSP) { |
| properties |= BLE_GATT_CHR_PROP_WRITE_NO_RSP; |
| } |
| if (chr->flags & BLE_GATT_CHR_F_WRITE) { |
| properties |= BLE_GATT_CHR_PROP_WRITE; |
| } |
| if (chr->flags & BLE_GATT_CHR_F_NOTIFY) { |
| properties |= BLE_GATT_CHR_PROP_NOTIFY; |
| } |
| if (chr->flags & BLE_GATT_CHR_F_INDICATE) { |
| properties |= BLE_GATT_CHR_PROP_INDICATE; |
| } |
| if (chr->flags & BLE_GATT_CHR_F_AUTH_SIGN_WRITE) { |
| properties |= BLE_GATT_CHR_PROP_AUTH_SIGN_WRITE; |
| } |
| if (chr->flags & |
| (BLE_GATT_CHR_F_RELIABLE_WRITE | BLE_GATT_CHR_F_AUX_WRITE)) { |
| |
| properties |= BLE_GATT_CHR_PROP_EXTENDED; |
| } |
| |
| return properties; |
| } |
| |
| static int |
| ble_gatts_chr_def_access(uint16_t conn_handle, uint16_t attr_handle, |
| uint8_t op, uint16_t offset, struct os_mbuf **om, |
| void *arg) |
| { |
| const struct ble_gatt_chr_def *chr; |
| uint8_t *buf; |
| |
| STATS_INC(ble_gatts_stats, chr_def_reads); |
| |
| BLE_HS_DBG_ASSERT(op == BLE_ATT_ACCESS_OP_READ); |
| |
| chr = arg; |
| |
| buf = os_mbuf_extend(*om, 3); |
| if (buf == NULL) { |
| return BLE_ATT_ERR_INSUFFICIENT_RES; |
| } |
| |
| buf[0] = ble_gatts_chr_properties(chr); |
| |
| /* The value attribute is always immediately after the declaration. */ |
| put_le16(buf + 1, attr_handle + 1); |
| |
| buf = os_mbuf_extend(*om, ble_uuid_length(chr->uuid)); |
| if (buf == NULL) { |
| return BLE_ATT_ERR_INSUFFICIENT_RES; |
| } |
| |
| ble_uuid_flat(chr->uuid, buf); |
| |
| return 0; |
| } |
| |
| static int |
| ble_gatts_chr_is_sane(const struct ble_gatt_chr_def *chr) |
| { |
| if (chr->uuid == NULL) { |
| return 0; |
| } |
| |
| if (chr->access_cb == NULL) { |
| return 0; |
| } |
| |
| /* XXX: Check properties. */ |
| |
| return 1; |
| } |
| |
| static uint8_t |
| ble_gatts_chr_op(uint8_t att_op) |
| { |
| switch (att_op) { |
| case BLE_ATT_ACCESS_OP_READ: |
| return BLE_GATT_ACCESS_OP_READ_CHR; |
| |
| case BLE_ATT_ACCESS_OP_WRITE: |
| return BLE_GATT_ACCESS_OP_WRITE_CHR; |
| |
| default: |
| BLE_HS_DBG_ASSERT(0); |
| return BLE_GATT_ACCESS_OP_READ_CHR; |
| } |
| } |
| |
| static void |
| ble_gatts_chr_inc_val_stat(uint8_t gatt_op) |
| { |
| switch (gatt_op) { |
| case BLE_GATT_ACCESS_OP_READ_CHR: |
| STATS_INC(ble_gatts_stats, chr_val_reads); |
| break; |
| |
| case BLE_GATT_ACCESS_OP_WRITE_CHR: |
| STATS_INC(ble_gatts_stats, chr_val_writes); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| /** |
| * Indicates whether the set of registered services can be modified. The |
| * service set is mutable if: |
| * o No peers are connected, and |
| * o No GAP operations are active (advertise, discover, or connect). |
| * |
| * @return true if the GATT service set can be modified; |
| * false otherwise. |
| */ |
| static bool |
| ble_gatts_mutable(void) |
| { |
| /* Ensure no active GAP procedures. */ |
| if (ble_gap_adv_active() || |
| ble_gap_disc_active() || |
| ble_gap_conn_active()) { |
| |
| return false; |
| } |
| |
| /* Ensure no established connections. */ |
| if (ble_hs_conn_first() != NULL) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static int |
| ble_gatts_val_access(uint16_t conn_handle, uint16_t attr_handle, |
| uint16_t offset, struct ble_gatt_access_ctxt *gatt_ctxt, |
| struct os_mbuf **om, ble_gatt_access_fn *access_cb, |
| void *cb_arg) |
| { |
| uint16_t initial_len; |
| int attr_len; |
| int new_om; |
| int rc; |
| |
| switch (gatt_ctxt->op) { |
| case BLE_GATT_ACCESS_OP_READ_CHR: |
| case BLE_GATT_ACCESS_OP_READ_DSC: |
| /* A characteristic value is being read. |
| * |
| * If the read specifies an offset of 0: |
| * just append the characteristic value directly onto the response |
| * mbuf. |
| * |
| * Else: |
| * allocate a new mbuf to hold the characteristic data, then append |
| * the requested portion onto the response mbuf. |
| */ |
| if (offset == 0) { |
| new_om = 0; |
| gatt_ctxt->om = *om; |
| } else { |
| new_om = 1; |
| gatt_ctxt->om = ble_hs_mbuf_att_pkt(); |
| if (gatt_ctxt->om == NULL) { |
| return BLE_ATT_ERR_INSUFFICIENT_RES; |
| } |
| } |
| |
| initial_len = OS_MBUF_PKTLEN(gatt_ctxt->om); |
| rc = access_cb(conn_handle, attr_handle, gatt_ctxt, cb_arg); |
| if (rc == 0) { |
| attr_len = OS_MBUF_PKTLEN(gatt_ctxt->om) - initial_len - offset; |
| if (attr_len >= 0) { |
| if (new_om) { |
| os_mbuf_appendfrom(*om, gatt_ctxt->om, offset, attr_len); |
| } |
| } else { |
| rc = BLE_ATT_ERR_INVALID_OFFSET; |
| } |
| } |
| |
| if (new_om) { |
| os_mbuf_free_chain(gatt_ctxt->om); |
| } |
| return rc; |
| |
| case BLE_GATT_ACCESS_OP_WRITE_CHR: |
| case BLE_GATT_ACCESS_OP_WRITE_DSC: |
| gatt_ctxt->om = *om; |
| rc = access_cb(conn_handle, attr_handle, gatt_ctxt, cb_arg); |
| *om = gatt_ctxt->om; |
| return rc; |
| |
| default: |
| BLE_HS_DBG_ASSERT(0); |
| return BLE_ATT_ERR_UNLIKELY; |
| } |
| } |
| |
| static int |
| ble_gatts_chr_val_access(uint16_t conn_handle, uint16_t attr_handle, |
| uint8_t att_op, uint16_t offset, |
| struct os_mbuf **om, void *arg) |
| { |
| const struct ble_gatt_chr_def *chr_def; |
| struct ble_gatt_access_ctxt gatt_ctxt; |
| int rc; |
| |
| chr_def = arg; |
| BLE_HS_DBG_ASSERT(chr_def != NULL && chr_def->access_cb != NULL); |
| |
| gatt_ctxt.op = ble_gatts_chr_op(att_op); |
| gatt_ctxt.chr = chr_def; |
| |
| ble_gatts_chr_inc_val_stat(gatt_ctxt.op); |
| rc = ble_gatts_val_access(conn_handle, attr_handle, offset, &gatt_ctxt, om, |
| chr_def->access_cb, chr_def->arg); |
| |
| return rc; |
| } |
| |
| static int |
| ble_gatts_find_svc_entry_idx(const struct ble_gatt_svc_def *svc) |
| { |
| int i; |
| |
| for (i = 0; i < ble_gatts_num_svc_entries; i++) { |
| if (ble_gatts_svc_entries[i].svc == svc) { |
| return i; |
| } |
| } |
| |
| return -1; |
| } |
| |
| static int |
| ble_gatts_svc_incs_satisfied(const struct ble_gatt_svc_def *svc) |
| { |
| int idx; |
| int i; |
| |
| if (svc->includes == NULL) { |
| /* No included services. */ |
| return 1; |
| } |
| |
| for (i = 0; svc->includes[i] != NULL; i++) { |
| idx = ble_gatts_find_svc_entry_idx(svc->includes[i]); |
| if (idx == -1 || ble_gatts_svc_entries[idx].handle == 0) { |
| return 0; |
| } |
| } |
| |
| return 1; |
| } |
| |
| static int |
| ble_gatts_register_inc(struct ble_gatts_svc_entry *entry) |
| { |
| uint16_t handle; |
| int rc; |
| |
| BLE_HS_DBG_ASSERT(entry->handle != 0); |
| BLE_HS_DBG_ASSERT(entry->end_group_handle != 0xffff); |
| |
| rc = ble_att_svr_register(uuid_inc, BLE_ATT_F_READ, 0, &handle, |
| ble_gatts_inc_access, entry); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| static uint8_t |
| ble_gatts_dsc_op(uint8_t att_op) |
| { |
| switch (att_op) { |
| case BLE_ATT_ACCESS_OP_READ: |
| return BLE_GATT_ACCESS_OP_READ_DSC; |
| |
| case BLE_ATT_ACCESS_OP_WRITE: |
| return BLE_GATT_ACCESS_OP_WRITE_DSC; |
| |
| default: |
| BLE_HS_DBG_ASSERT(0); |
| return BLE_GATT_ACCESS_OP_READ_DSC; |
| } |
| } |
| |
| static void |
| ble_gatts_dsc_inc_stat(uint8_t gatt_op) |
| { |
| switch (gatt_op) { |
| case BLE_GATT_ACCESS_OP_READ_DSC: |
| STATS_INC(ble_gatts_stats, dsc_reads); |
| break; |
| |
| case BLE_GATT_ACCESS_OP_WRITE_DSC: |
| STATS_INC(ble_gatts_stats, dsc_writes); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| static int |
| ble_gatts_dsc_access(uint16_t conn_handle, uint16_t attr_handle, |
| uint8_t att_op, uint16_t offset, struct os_mbuf **om, |
| void *arg) |
| { |
| const struct ble_gatt_dsc_def *dsc_def; |
| struct ble_gatt_access_ctxt gatt_ctxt; |
| int rc; |
| |
| dsc_def = arg; |
| BLE_HS_DBG_ASSERT(dsc_def != NULL && dsc_def->access_cb != NULL); |
| |
| gatt_ctxt.op = ble_gatts_dsc_op(att_op); |
| gatt_ctxt.dsc = dsc_def; |
| |
| ble_gatts_dsc_inc_stat(gatt_ctxt.op); |
| rc = ble_gatts_val_access(conn_handle, attr_handle, offset, &gatt_ctxt, om, |
| dsc_def->access_cb, dsc_def->arg); |
| |
| return rc; |
| } |
| |
| static int |
| ble_gatts_dsc_is_sane(const struct ble_gatt_dsc_def *dsc) |
| { |
| if (dsc->uuid == NULL) { |
| return 0; |
| } |
| |
| if (dsc->access_cb == NULL) { |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static int |
| ble_gatts_register_dsc(const struct ble_gatt_svc_def *svc, |
| const struct ble_gatt_chr_def *chr, |
| const struct ble_gatt_dsc_def *dsc, |
| uint16_t chr_def_handle, |
| ble_gatt_register_fn *register_cb, void *cb_arg) |
| { |
| struct ble_gatt_register_ctxt register_ctxt; |
| uint16_t dsc_handle; |
| int rc; |
| |
| if (!ble_gatts_dsc_is_sane(dsc)) { |
| return BLE_HS_EINVAL; |
| } |
| |
| rc = ble_att_svr_register(dsc->uuid, dsc->att_flags, dsc->min_key_size, |
| &dsc_handle, ble_gatts_dsc_access, (void *)dsc); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| if (register_cb != NULL) { |
| register_ctxt.op = BLE_GATT_REGISTER_OP_DSC; |
| register_ctxt.dsc.handle = dsc_handle; |
| register_ctxt.dsc.svc_def = svc; |
| register_ctxt.dsc.chr_def = chr; |
| register_ctxt.dsc.dsc_def = dsc; |
| register_cb(®ister_ctxt, cb_arg); |
| } |
| |
| STATS_INC(ble_gatts_stats, dscs); |
| |
| return 0; |
| |
| } |
| |
| static int |
| ble_gatts_clt_cfg_find_idx(struct ble_gatts_clt_cfg *cfgs, |
| uint16_t chr_val_handle) |
| { |
| struct ble_gatts_clt_cfg *cfg; |
| int i; |
| |
| for (i = 0; i < ble_gatts_num_cfgable_chrs; i++) { |
| cfg = cfgs + i; |
| if (cfg->chr_val_handle == chr_val_handle) { |
| return i; |
| } |
| } |
| |
| return -1; |
| } |
| |
| static struct ble_gatts_clt_cfg * |
| ble_gatts_clt_cfg_find(struct ble_gatts_clt_cfg *cfgs, |
| uint16_t chr_val_handle) |
| { |
| int idx; |
| |
| idx = ble_gatts_clt_cfg_find_idx(cfgs, chr_val_handle); |
| if (idx == -1) { |
| return NULL; |
| } else { |
| return cfgs + idx; |
| } |
| } |
| |
| static void |
| ble_gatts_subscribe_event(uint16_t conn_handle, uint16_t attr_handle, |
| uint8_t reason, |
| uint8_t prev_flags, uint8_t cur_flags) |
| { |
| if ((prev_flags ^ cur_flags) & ~BLE_GATTS_CLT_CFG_F_RESERVED) { |
| ble_gap_subscribe_event(conn_handle, |
| attr_handle, |
| reason, |
| prev_flags & BLE_GATTS_CLT_CFG_F_NOTIFY, |
| cur_flags & BLE_GATTS_CLT_CFG_F_NOTIFY, |
| prev_flags & BLE_GATTS_CLT_CFG_F_INDICATE, |
| cur_flags & BLE_GATTS_CLT_CFG_F_INDICATE); |
| } |
| } |
| |
| /** |
| * Performs a read or write access on a client characteritic configuration |
| * descriptor (CCCD). |
| * |
| * @param conn The connection of the peer doing the accessing. |
| * @apram attr_handle The handle of the CCCD. |
| * @param att_op The ATT operation being performed (read or |
| * write). |
| * @param ctxt Communication channel between this function and |
| * the caller within the nimble stack. |
| * Semantics depends on the operation being |
| * performed. |
| * @param out_cccd If the CCCD should be persisted as a result of |
| * the access, the data-to-be-persisted gets |
| * written here. If no persistence is |
| * necessary, out_cccd->chr_val_handle is set |
| * to 0. |
| * |
| * @return 0 on success; nonzero on failure. |
| */ |
| static int |
| ble_gatts_clt_cfg_access_locked(struct ble_hs_conn *conn, uint16_t attr_handle, |
| uint8_t att_op, uint16_t offset, |
| struct os_mbuf *om, |
| struct ble_store_value_cccd *out_cccd, |
| uint8_t *out_prev_clt_cfg_flags, |
| uint8_t *out_cur_clt_cfg_flags) |
| { |
| struct ble_gatts_clt_cfg *clt_cfg; |
| uint16_t chr_val_handle; |
| uint16_t flags; |
| uint8_t gatt_op; |
| uint8_t *buf; |
| |
| /* Assume nothing needs to be persisted. */ |
| out_cccd->chr_val_handle = 0; |
| |
| /* We always register the client characteristics descriptor with handle |
| * (chr_val + 1). |
| */ |
| chr_val_handle = attr_handle - 1; |
| if (chr_val_handle > attr_handle) { |
| /* Attribute handle wrapped somehow. */ |
| return BLE_ATT_ERR_UNLIKELY; |
| } |
| |
| clt_cfg = ble_gatts_clt_cfg_find(conn->bhc_gatt_svr.clt_cfgs, |
| chr_val_handle); |
| if (clt_cfg == NULL) { |
| return BLE_ATT_ERR_UNLIKELY; |
| } |
| |
| /* Assume no change in flags. */ |
| *out_prev_clt_cfg_flags = clt_cfg->flags; |
| *out_cur_clt_cfg_flags = clt_cfg->flags; |
| |
| gatt_op = ble_gatts_dsc_op(att_op); |
| ble_gatts_dsc_inc_stat(gatt_op); |
| |
| switch (gatt_op) { |
| case BLE_GATT_ACCESS_OP_READ_DSC: |
| STATS_INC(ble_gatts_stats, dsc_reads); |
| buf = os_mbuf_extend(om, 2); |
| if (buf == NULL) { |
| return BLE_ATT_ERR_INSUFFICIENT_RES; |
| } |
| put_le16(buf, clt_cfg->flags & ~BLE_GATTS_CLT_CFG_F_RESERVED); |
| break; |
| |
| case BLE_GATT_ACCESS_OP_WRITE_DSC: |
| STATS_INC(ble_gatts_stats, dsc_writes); |
| if (OS_MBUF_PKTLEN(om) != 2) { |
| return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; |
| } |
| |
| om = os_mbuf_pullup(om, 2); |
| BLE_HS_DBG_ASSERT(om != NULL); |
| |
| flags = get_le16(om->om_data); |
| if ((flags & ~clt_cfg->allowed) != 0) { |
| return BLE_ATT_ERR_REQ_NOT_SUPPORTED; |
| } |
| |
| if (clt_cfg->flags != flags) { |
| clt_cfg->flags = flags; |
| *out_cur_clt_cfg_flags = flags; |
| |
| /* Successful writes get persisted for bonded connections. */ |
| if (conn->bhc_sec_state.bonded) { |
| out_cccd->peer_addr = conn->bhc_peer_addr; |
| out_cccd->peer_addr.type = |
| ble_hs_misc_peer_addr_type_to_id(conn->bhc_peer_addr.type); |
| out_cccd->chr_val_handle = chr_val_handle; |
| out_cccd->flags = clt_cfg->flags; |
| out_cccd->value_changed = 0; |
| } |
| } |
| break; |
| |
| default: |
| BLE_HS_DBG_ASSERT(0); |
| return BLE_ATT_ERR_UNLIKELY; |
| } |
| |
| return 0; |
| } |
| |
| int |
| ble_gatts_clt_cfg_access(uint16_t conn_handle, uint16_t attr_handle, |
| uint8_t op, uint16_t offset, struct os_mbuf **om, |
| void *arg) |
| { |
| struct ble_store_value_cccd cccd_value; |
| struct ble_store_key_cccd cccd_key; |
| struct ble_hs_conn *conn; |
| uint16_t chr_val_handle; |
| uint8_t prev_flags; |
| uint8_t cur_flags; |
| int rc; |
| |
| ble_hs_lock(); |
| |
| conn = ble_hs_conn_find(conn_handle); |
| if (conn == NULL) { |
| rc = BLE_ATT_ERR_UNLIKELY; |
| } else { |
| rc = ble_gatts_clt_cfg_access_locked(conn, attr_handle, op, offset, |
| *om, &cccd_value, &prev_flags, |
| &cur_flags); |
| } |
| |
| ble_hs_unlock(); |
| |
| if (rc != 0) { |
| return rc; |
| } |
| |
| /* The value attribute is always immediately after the declaration. */ |
| chr_val_handle = attr_handle - 1; |
| |
| /* Tell the application if the peer changed its subscription state. */ |
| ble_gatts_subscribe_event(conn_handle, chr_val_handle, |
| BLE_GAP_SUBSCRIBE_REASON_WRITE, |
| prev_flags, cur_flags); |
| |
| /* Persist the CCCD if required. */ |
| if (cccd_value.chr_val_handle != 0) { |
| if (cccd_value.flags == 0) { |
| ble_store_key_from_value_cccd(&cccd_key, &cccd_value); |
| rc = ble_store_delete_cccd(&cccd_key); |
| } else { |
| rc = ble_store_write_cccd(&cccd_value); |
| } |
| } |
| |
| return rc; |
| } |
| |
| static int |
| ble_gatts_register_clt_cfg_dsc(uint16_t *att_handle) |
| { |
| int rc; |
| |
| rc = ble_att_svr_register(uuid_ccc, BLE_ATT_F_READ | BLE_ATT_F_WRITE, 0, |
| att_handle, ble_gatts_clt_cfg_access, NULL); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| STATS_INC(ble_gatts_stats, dscs); |
| |
| return 0; |
| } |
| |
| static int |
| ble_gatts_register_chr(const struct ble_gatt_svc_def *svc, |
| const struct ble_gatt_chr_def *chr, |
| ble_gatt_register_fn *register_cb, void *cb_arg) |
| { |
| struct ble_gatt_register_ctxt register_ctxt; |
| struct ble_gatt_dsc_def *dsc; |
| uint16_t def_handle; |
| uint16_t val_handle; |
| uint16_t dsc_handle; |
| uint8_t att_flags; |
| int rc; |
| |
| if (!ble_gatts_chr_is_sane(chr)) { |
| return BLE_HS_EINVAL; |
| } |
| |
| if (ble_gatts_chr_clt_cfg_allowed(chr) != 0) { |
| if (ble_gatts_num_cfgable_chrs > ble_hs_max_client_configs) { |
| return BLE_HS_ENOMEM; |
| } |
| ble_gatts_num_cfgable_chrs++; |
| } |
| |
| /* Register characteristic definition attribute (cast away const on |
| * callback arg). |
| */ |
| rc = ble_att_svr_register(uuid_chr, BLE_ATT_F_READ, 0, &def_handle, |
| ble_gatts_chr_def_access, (void *)chr); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| /* Register characteristic value attribute (cast away const on callback |
| * arg). |
| */ |
| att_flags = ble_gatts_att_flags_from_chr_flags(chr->flags); |
| rc = ble_att_svr_register(chr->uuid, att_flags, chr->min_key_size, |
| &val_handle, ble_gatts_chr_val_access, |
| (void *)chr); |
| if (rc != 0) { |
| return rc; |
| } |
| BLE_HS_DBG_ASSERT(val_handle == def_handle + 1); |
| |
| if (chr->val_handle != NULL) { |
| *chr->val_handle = val_handle; |
| } |
| |
| if (register_cb != NULL) { |
| register_ctxt.op = BLE_GATT_REGISTER_OP_CHR; |
| register_ctxt.chr.def_handle = def_handle; |
| register_ctxt.chr.val_handle = val_handle; |
| register_ctxt.chr.svc_def = svc; |
| register_ctxt.chr.chr_def = chr; |
| register_cb(®ister_ctxt, cb_arg); |
| } |
| |
| if (ble_gatts_chr_clt_cfg_allowed(chr) != 0) { |
| rc = ble_gatts_register_clt_cfg_dsc(&dsc_handle); |
| if (rc != 0) { |
| return rc; |
| } |
| BLE_HS_DBG_ASSERT(dsc_handle == def_handle + 2); |
| } |
| |
| /* Register each descriptor. */ |
| if (chr->descriptors != NULL) { |
| for (dsc = chr->descriptors; dsc->uuid != NULL; dsc++) { |
| rc = ble_gatts_register_dsc(svc, chr, dsc, def_handle, register_cb, |
| cb_arg); |
| if (rc != 0) { |
| return rc; |
| } |
| } |
| } |
| |
| STATS_INC(ble_gatts_stats, chrs); |
| |
| return 0; |
| } |
| |
| static int |
| ble_gatts_svc_type_to_uuid(uint8_t svc_type, const ble_uuid_t **uuid) |
| { |
| switch (svc_type) { |
| case BLE_GATT_SVC_TYPE_PRIMARY: |
| *uuid = uuid_pri; |
| return 0; |
| |
| case BLE_GATT_SVC_TYPE_SECONDARY: |
| *uuid = uuid_sec; |
| return 0; |
| |
| default: |
| return BLE_HS_EINVAL; |
| } |
| } |
| |
| static int |
| ble_gatts_svc_is_sane(const struct ble_gatt_svc_def *svc) |
| { |
| if (svc->type != BLE_GATT_SVC_TYPE_PRIMARY && |
| svc->type != BLE_GATT_SVC_TYPE_SECONDARY) { |
| |
| return 0; |
| } |
| |
| if (svc->uuid == NULL) { |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static int |
| ble_gatts_register_svc(const struct ble_gatt_svc_def *svc, |
| uint16_t *out_handle, |
| ble_gatt_register_fn *register_cb, void *cb_arg) |
| { |
| const struct ble_gatt_chr_def *chr; |
| struct ble_gatt_register_ctxt register_ctxt; |
| const ble_uuid_t *uuid; |
| int idx; |
| int rc; |
| int i; |
| |
| if (!ble_gatts_svc_incs_satisfied(svc)) { |
| return BLE_HS_EAGAIN; |
| } |
| |
| if (!ble_gatts_svc_is_sane(svc)) { |
| return BLE_HS_EINVAL; |
| } |
| |
| /* Prevent spurious maybe-uninitialized gcc warning. */ |
| uuid = NULL; |
| |
| rc = ble_gatts_svc_type_to_uuid(svc->type, &uuid); |
| BLE_HS_DBG_ASSERT_EVAL(rc == 0); |
| |
| /* Register service definition attribute (cast away const on callback |
| * arg). |
| */ |
| rc = ble_att_svr_register(uuid, BLE_ATT_F_READ, 0, out_handle, |
| ble_gatts_svc_access, (void *)svc); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| if (register_cb != NULL) { |
| register_ctxt.op = BLE_GATT_REGISTER_OP_SVC; |
| register_ctxt.svc.handle = *out_handle; |
| register_ctxt.svc.svc_def = svc; |
| register_cb(®ister_ctxt, cb_arg); |
| } |
| |
| /* Register each include. */ |
| if (svc->includes != NULL) { |
| for (i = 0; svc->includes[i] != NULL; i++) { |
| idx = ble_gatts_find_svc_entry_idx(svc->includes[i]); |
| BLE_HS_DBG_ASSERT_EVAL(idx != -1); |
| |
| rc = ble_gatts_register_inc(ble_gatts_svc_entries + idx); |
| if (rc != 0) { |
| return rc; |
| } |
| } |
| } |
| |
| /* Register each characteristic. */ |
| if (svc->characteristics != NULL) { |
| for (chr = svc->characteristics; chr->uuid != NULL; chr++) { |
| rc = ble_gatts_register_chr(svc, chr, register_cb, cb_arg); |
| if (rc != 0) { |
| return rc; |
| } |
| } |
| } |
| |
| STATS_INC(ble_gatts_stats, svcs); |
| |
| return 0; |
| } |
| |
| static int |
| ble_gatts_register_round(int *out_num_registered, ble_gatt_register_fn *cb, |
| void *cb_arg) |
| { |
| struct ble_gatts_svc_entry *entry; |
| uint16_t handle; |
| int rc; |
| int i; |
| |
| *out_num_registered = 0; |
| for (i = 0; i < ble_gatts_num_svc_entries; i++) { |
| entry = ble_gatts_svc_entries + i; |
| |
| if (entry->handle == 0) { |
| rc = ble_gatts_register_svc(entry->svc, &handle, cb, cb_arg); |
| switch (rc) { |
| case 0: |
| /* Service successfully registered. */ |
| entry->handle = handle; |
| entry->end_group_handle = ble_att_svr_prev_handle(); |
| (*out_num_registered)++; |
| break; |
| |
| case BLE_HS_EAGAIN: |
| /* Service could not be registered due to unsatisfied includes. |
| * Try again on the next iteration. |
| */ |
| break; |
| |
| default: |
| return rc; |
| } |
| } |
| } |
| |
| if (*out_num_registered == 0) { |
| /* There is a circular dependency. */ |
| return BLE_HS_EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Registers a set of services, characteristics, and descriptors to be accessed |
| * by GATT clients. |
| * |
| * @param svcs A table of the service definitions to be |
| * registered. |
| * @param cb The function to call for each service, |
| * characteristic, and descriptor that gets |
| * registered. |
| * @param cb_arg The optional argument to pass to the callback |
| * function. |
| * |
| * @return 0 on success; |
| * BLE_HS_ENOMEM if registration failed due to |
| * resource exhaustion; |
| * BLE_HS_EINVAL if the service definition table |
| * contains an invalid element. |
| */ |
| int |
| ble_gatts_register_svcs(const struct ble_gatt_svc_def *svcs, |
| ble_gatt_register_fn *cb, void *cb_arg) |
| { |
| int total_registered; |
| int cur_registered; |
| int num_svcs; |
| int idx; |
| int rc; |
| int i; |
| |
| for (i = 0; svcs[i].type != BLE_GATT_SVC_TYPE_END; i++) { |
| idx = ble_gatts_num_svc_entries + i; |
| if (idx >= ble_hs_max_services) { |
| return BLE_HS_ENOMEM; |
| } |
| |
| ble_gatts_svc_entries[idx].svc = svcs + i; |
| ble_gatts_svc_entries[idx].handle = 0; |
| ble_gatts_svc_entries[idx].end_group_handle = 0xffff; |
| } |
| num_svcs = i; |
| ble_gatts_num_svc_entries += num_svcs; |
| |
| total_registered = 0; |
| while (total_registered < num_svcs) { |
| rc = ble_gatts_register_round(&cur_registered, cb, cb_arg); |
| if (rc != 0) { |
| return rc; |
| } |
| total_registered += cur_registered; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| ble_gatts_clt_cfg_size(void) |
| { |
| return ble_gatts_num_cfgable_chrs * sizeof (struct ble_gatts_clt_cfg); |
| } |
| |
| /** |
| * Handles GATT server clean up for a terminated connection: |
| * o Informs the application that the peer is no longer subscribed to any |
| * characteristic updates. |
| * o Frees GATT server resources consumed by the connection (CCCDs). |
| */ |
| void |
| ble_gatts_connection_broken(uint16_t conn_handle) |
| { |
| struct ble_gatts_clt_cfg *clt_cfgs; |
| struct ble_hs_conn *conn; |
| int num_clt_cfgs; |
| int rc; |
| int i; |
| |
| /* Find the specified connection and extract its CCCD entries. Extracting |
| * the clt_cfg pointer and setting the original to null is done for two |
| * reasons: |
| * 1. So that the CCCD entries can be safely processed after unlocking |
| * the mutex. |
| * 2. To ensure a subsequent indicate procedure for this peer is not |
| * attempted, as the connection is about to be terminated. This |
| * avoids a spurious notify-tx GAP event callback to the |
| * application. By setting the clt_cfg pointer to null, it is |
| * assured that the connection has no pending indications to send. |
| */ |
| ble_hs_lock(); |
| conn = ble_hs_conn_find(conn_handle); |
| if (conn != NULL) { |
| clt_cfgs = conn->bhc_gatt_svr.clt_cfgs; |
| num_clt_cfgs = conn->bhc_gatt_svr.num_clt_cfgs; |
| |
| conn->bhc_gatt_svr.clt_cfgs = NULL; |
| conn->bhc_gatt_svr.num_clt_cfgs = 0; |
| } |
| ble_hs_unlock(); |
| |
| if (conn == NULL) { |
| return; |
| } |
| |
| /* If there is an indicate procedure in progress for this connection, |
| * inform the application that it has failed. |
| */ |
| ble_gatts_indicate_fail_notconn(conn_handle); |
| |
| /* Now that the mutex is unlocked, inform the application that the peer is |
| * no longer subscribed to any characteristic updates. |
| */ |
| if (clt_cfgs != NULL) { |
| for (i = 0; i < num_clt_cfgs; i++) { |
| ble_gatts_subscribe_event(conn_handle, clt_cfgs[i].chr_val_handle, |
| BLE_GAP_SUBSCRIBE_REASON_TERM, |
| clt_cfgs[i].flags, 0); |
| } |
| |
| rc = os_memblock_put(&ble_gatts_clt_cfg_pool, clt_cfgs); |
| BLE_HS_DBG_ASSERT_EVAL(rc == 0); |
| } |
| } |
| |
| static void |
| ble_gatts_free_svc_defs(void) |
| { |
| free(ble_gatts_svc_defs); |
| ble_gatts_svc_defs = NULL; |
| ble_gatts_num_svc_defs = 0; |
| } |
| |
| static void |
| ble_gatts_free_mem(void) |
| { |
| free(ble_gatts_clt_cfg_mem); |
| ble_gatts_clt_cfg_mem = NULL; |
| |
| free(ble_gatts_svc_entries); |
| ble_gatts_svc_entries = NULL; |
| } |
| |
| int |
| ble_gatts_start(void) |
| { |
| struct ble_att_svr_entry *ha; |
| struct ble_gatt_chr_def *chr; |
| uint16_t allowed_flags; |
| ble_uuid16_t uuid = BLE_UUID16_INIT(BLE_ATT_UUID_CHARACTERISTIC); |
| int num_elems; |
| int idx; |
| int rc; |
| int i; |
| |
| ble_hs_lock(); |
| if (!ble_gatts_mutable()) { |
| rc = BLE_HS_EBUSY; |
| goto done; |
| } |
| |
| ble_gatts_free_mem(); |
| |
| rc = ble_att_svr_start(); |
| if (rc != 0) { |
| goto done; |
| } |
| |
| if (ble_hs_max_client_configs > 0) { |
| ble_gatts_clt_cfg_mem = malloc( |
| OS_MEMPOOL_BYTES(ble_hs_max_client_configs, |
| sizeof (struct ble_gatts_clt_cfg))); |
| if (ble_gatts_clt_cfg_mem == NULL) { |
| rc = BLE_HS_ENOMEM; |
| goto done; |
| } |
| } |
| |
| if (ble_hs_max_services > 0) { |
| ble_gatts_svc_entries = |
| malloc(ble_hs_max_services * sizeof *ble_gatts_svc_entries); |
| if (ble_gatts_svc_entries == NULL) { |
| rc = BLE_HS_ENOMEM; |
| goto done; |
| } |
| } |
| |
| |
| ble_gatts_num_svc_entries = 0; |
| for (i = 0; i < ble_gatts_num_svc_defs; i++) { |
| rc = ble_gatts_register_svcs(ble_gatts_svc_defs[i], |
| ble_hs_cfg.gatts_register_cb, |
| ble_hs_cfg.gatts_register_arg); |
| if (rc != 0) { |
| goto done; |
| } |
| } |
| ble_gatts_free_svc_defs(); |
| |
| if (ble_gatts_num_cfgable_chrs == 0) { |
| rc = 0; |
| goto done; |
| } |
| |
| /* Initialize client-configuration memory pool. */ |
| num_elems = ble_hs_max_client_configs / ble_gatts_num_cfgable_chrs; |
| rc = os_mempool_init(&ble_gatts_clt_cfg_pool, num_elems, |
| ble_gatts_clt_cfg_size(), ble_gatts_clt_cfg_mem, |
| "ble_gatts_clt_cfg_pool"); |
| if (rc != 0) { |
| rc = BLE_HS_EOS; |
| goto done; |
| } |
| |
| /* Allocate the cached array of handles for the configuration |
| * characteristics. |
| */ |
| ble_gatts_clt_cfgs = os_memblock_get(&ble_gatts_clt_cfg_pool); |
| if (ble_gatts_clt_cfgs == NULL) { |
| rc = BLE_HS_ENOMEM; |
| goto done; |
| } |
| |
| /* Fill the cache. */ |
| idx = 0; |
| ha = NULL; |
| while ((ha = ble_att_svr_find_by_uuid(ha, &uuid.u, 0xffff)) != NULL) { |
| chr = ha->ha_cb_arg; |
| allowed_flags = ble_gatts_chr_clt_cfg_allowed(chr); |
| if (allowed_flags != 0) { |
| BLE_HS_DBG_ASSERT_EVAL(idx < ble_gatts_num_cfgable_chrs); |
| |
| ble_gatts_clt_cfgs[idx].chr_val_handle = ha->ha_handle_id + 1; |
| ble_gatts_clt_cfgs[idx].allowed = allowed_flags; |
| ble_gatts_clt_cfgs[idx].flags = 0; |
| idx++; |
| } |
| } |
| |
| done: |
| if (rc != 0) { |
| ble_gatts_free_mem(); |
| ble_gatts_free_svc_defs(); |
| } |
| |
| ble_hs_unlock(); |
| return rc; |
| } |
| |
| int |
| ble_gatts_conn_can_alloc(void) |
| { |
| return ble_gatts_num_cfgable_chrs == 0 || |
| ble_gatts_clt_cfg_pool.mp_num_free > 0; |
| } |
| |
| int |
| ble_gatts_conn_init(struct ble_gatts_conn *gatts_conn) |
| { |
| if (ble_gatts_num_cfgable_chrs > 0) { |
| gatts_conn->clt_cfgs = os_memblock_get(&ble_gatts_clt_cfg_pool); |
| if (gatts_conn->clt_cfgs == NULL) { |
| return BLE_HS_ENOMEM; |
| } |
| |
| /* Initialize the client configuration with a copy of the cache. */ |
| memcpy(gatts_conn->clt_cfgs, ble_gatts_clt_cfgs, |
| ble_gatts_clt_cfg_size()); |
| gatts_conn->num_clt_cfgs = ble_gatts_num_cfgable_chrs; |
| } else { |
| gatts_conn->clt_cfgs = NULL; |
| gatts_conn->num_clt_cfgs = 0; |
| } |
| |
| return 0; |
| } |
| |
| |
| /** |
| * Schedules a notification or indication for the specified peer-CCCD pair. If |
| * the update should be sent immediately, it is indicated in the return code. |
| * |
| * @param conn The connection to schedule the update for. |
| * @param clt_cfg The client config entry corresponding to the |
| * peer and affected characteristic. |
| * |
| * @return The att_op of the update to send immediately, |
| * if any. 0 if nothing should get sent. |
| */ |
| static uint8_t |
| ble_gatts_schedule_update(struct ble_hs_conn *conn, |
| struct ble_gatts_clt_cfg *clt_cfg) |
| { |
| uint8_t att_op; |
| |
| if (!(clt_cfg->flags & BLE_GATTS_CLT_CFG_F_MODIFIED)) { |
| /* Characteristic not modified. Nothing to send. */ |
| att_op = 0; |
| } else if (clt_cfg->flags & BLE_GATTS_CLT_CFG_F_NOTIFY) { |
| /* Notifications always get sent immediately. */ |
| att_op = BLE_ATT_OP_NOTIFY_REQ; |
| } else if (clt_cfg->flags & BLE_GATTS_CLT_CFG_F_INDICATE) { |
| /* Only one outstanding indication per peer is allowed. If we |
| * are still awaiting an ack, mark this CCCD as updated so that |
| * we know to send the indication upon receiving the expected ack. |
| * If there isn't an outstanding indication, send this one now. |
| */ |
| if (conn->bhc_gatt_svr.indicate_val_handle != 0) { |
| att_op = 0; |
| } else { |
| att_op = BLE_ATT_OP_INDICATE_REQ; |
| } |
| } else { |
| /* Peer isn't subscribed to notifications or indications. Nothing to |
| * send. |
| */ |
| att_op = 0; |
| } |
| |
| /* If we will be sending an update, clear the modified flag so that we |
| * don't double-send. |
| */ |
| if (att_op != 0) { |
| clt_cfg->flags &= ~BLE_GATTS_CLT_CFG_F_MODIFIED; |
| } |
| |
| return att_op; |
| } |
| |
| int |
| ble_gatts_send_next_indicate(uint16_t conn_handle) |
| { |
| struct ble_gatts_clt_cfg *clt_cfg; |
| struct ble_hs_conn *conn; |
| uint16_t chr_val_handle; |
| int rc; |
| int i; |
| |
| /* Assume no pending indications. */ |
| chr_val_handle = 0; |
| |
| ble_hs_lock(); |
| |
| conn = ble_hs_conn_find(conn_handle); |
| if (conn != NULL) { |
| for (i = 0; i < conn->bhc_gatt_svr.num_clt_cfgs; i++) { |
| clt_cfg = conn->bhc_gatt_svr.clt_cfgs + i; |
| if (clt_cfg->flags & BLE_GATTS_CLT_CFG_F_MODIFIED) { |
| BLE_HS_DBG_ASSERT(clt_cfg->flags & |
| BLE_GATTS_CLT_CFG_F_INDICATE); |
| |
| chr_val_handle = clt_cfg->chr_val_handle; |
| |
| /* Clear pending flag in anticipation of indication tx. */ |
| clt_cfg->flags &= ~BLE_GATTS_CLT_CFG_F_MODIFIED; |
| break; |
| } |
| } |
| } |
| |
| ble_hs_unlock(); |
| |
| if (conn == NULL) { |
| return BLE_HS_ENOTCONN; |
| } |
| |
| if (chr_val_handle == 0) { |
| return BLE_HS_ENOENT; |
| } |
| |
| rc = ble_gatts_indicate(conn_handle, chr_val_handle); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| int |
| ble_gatts_rx_indicate_ack(uint16_t conn_handle, uint16_t chr_val_handle) |
| { |
| struct ble_store_value_cccd cccd_value; |
| struct ble_gatts_clt_cfg *clt_cfg; |
| struct ble_hs_conn *conn; |
| int clt_cfg_idx; |
| int persist; |
| int rc; |
| |
| clt_cfg_idx = ble_gatts_clt_cfg_find_idx(ble_gatts_clt_cfgs, |
| chr_val_handle); |
| if (clt_cfg_idx == -1) { |
| /* This characteristic does not have a CCCD. */ |
| return BLE_HS_ENOENT; |
| } |
| |
| clt_cfg = ble_gatts_clt_cfgs + clt_cfg_idx; |
| if (!(clt_cfg->allowed & BLE_GATTS_CLT_CFG_F_INDICATE)) { |
| /* This characteristic does not allow indications. */ |
| return BLE_HS_ENOENT; |
| } |
| |
| ble_hs_lock(); |
| |
| conn = ble_hs_conn_find(conn_handle); |
| BLE_HS_DBG_ASSERT(conn != NULL); |
| if (conn->bhc_gatt_svr.indicate_val_handle == chr_val_handle) { |
| /* This acknowledgement is expected. */ |
| rc = 0; |
| |
| /* Mark that there is no longer an outstanding txed indicate. */ |
| conn->bhc_gatt_svr.indicate_val_handle = 0; |
| |
| /* Determine if we need to persist that there is no pending indication |
| * for this peer-characteristic pair. If the characteristic has not |
| * been modified since we sent the indication, there is no indication |
| * pending. |
| */ |
| BLE_HS_DBG_ASSERT(conn->bhc_gatt_svr.num_clt_cfgs > clt_cfg_idx); |
| clt_cfg = conn->bhc_gatt_svr.clt_cfgs + clt_cfg_idx; |
| BLE_HS_DBG_ASSERT(clt_cfg->chr_val_handle == chr_val_handle); |
| |
| persist = conn->bhc_sec_state.bonded && |
| !(clt_cfg->flags & BLE_GATTS_CLT_CFG_F_MODIFIED); |
| if (persist) { |
| cccd_value.peer_addr = conn->bhc_peer_addr; |
| cccd_value.peer_addr.type = |
| ble_hs_misc_peer_addr_type_to_id(conn->bhc_peer_addr.type); |
| cccd_value.chr_val_handle = chr_val_handle; |
| cccd_value.flags = clt_cfg->flags; |
| cccd_value.value_changed = 0; |
| } |
| } else { |
| /* This acknowledgement doesn't correspond to the outstanding |
| * indication; ignore it. |
| */ |
| rc = BLE_HS_ENOENT; |
| } |
| |
| ble_hs_unlock(); |
| |
| if (rc != 0) { |
| return rc; |
| } |
| |
| if (persist) { |
| rc = ble_store_write_cccd(&cccd_value); |
| if (rc != 0) { |
| /* XXX: How should this error get reported? */ |
| } |
| } |
| |
| return 0; |
| } |
| |
| void |
| ble_gatts_chr_updated(uint16_t chr_val_handle) |
| { |
| struct ble_store_value_cccd cccd_value; |
| struct ble_store_key_cccd cccd_key; |
| struct ble_gatts_clt_cfg *clt_cfg; |
| struct ble_hs_conn *conn; |
| int new_notifications = 0; |
| int clt_cfg_idx; |
| int persist; |
| int rc; |
| int i; |
| |
| /* Determine if notifications or indications are allowed for this |
| * characteristic. If not, return immediately. |
| */ |
| clt_cfg_idx = ble_gatts_clt_cfg_find_idx(ble_gatts_clt_cfgs, |
| chr_val_handle); |
| if (clt_cfg_idx == -1) { |
| return; |
| } |
| |
| /*** Send notifications and indications to connected devices. */ |
| |
| ble_hs_lock(); |
| for (i = 0; ; i++) { |
| /* XXX: This is inefficient when there are a lot of connections. |
| * Consider using a "foreach" function to walk the connection list. |
| */ |
| conn = ble_hs_conn_find_by_idx(i); |
| if (conn == NULL) { |
| break; |
| } |
| |
| BLE_HS_DBG_ASSERT_EVAL(conn->bhc_gatt_svr.num_clt_cfgs > |
| clt_cfg_idx); |
| clt_cfg = conn->bhc_gatt_svr.clt_cfgs + clt_cfg_idx; |
| BLE_HS_DBG_ASSERT_EVAL(clt_cfg->chr_val_handle == chr_val_handle); |
| |
| /* Mark the CCCD entry as modified. */ |
| clt_cfg->flags |= BLE_GATTS_CLT_CFG_F_MODIFIED; |
| new_notifications = 1; |
| } |
| ble_hs_unlock(); |
| |
| if (new_notifications) { |
| ble_hs_notifications_sched(); |
| } |
| |
| /*** Persist updated flag for unconnected and not-yet-bonded devices. */ |
| |
| /* Retrieve each record corresponding to the modified characteristic. */ |
| cccd_key.peer_addr = *BLE_ADDR_ANY; |
| cccd_key.chr_val_handle = chr_val_handle; |
| cccd_key.idx = 0; |
| |
| while (1) { |
| rc = ble_store_read_cccd(&cccd_key, &cccd_value); |
| if (rc != 0) { |
| /* Read error or no more CCCD records. */ |
| break; |
| } |
| |
| /* Determine if this record needs to be rewritten. */ |
| ble_hs_lock(); |
| conn = ble_hs_conn_find_by_addr(&cccd_key.peer_addr); |
| |
| if (conn == NULL) { |
| /* Device isn't connected; persist the changed flag so that an |
| * update can be sent when the device reconnects and rebonds. |
| */ |
| persist = 1; |
| } else if (cccd_value.flags & BLE_GATTS_CLT_CFG_F_INDICATE) { |
| /* Indication for a connected device; record that the |
| * characteristic has changed until we receive the ack. |
| */ |
| persist = 1; |
| } else { |
| /* Notification for a connected device; we already sent it so there |
| * is no need to persist. |
| */ |
| persist = 0; |
| } |
| |
| ble_hs_unlock(); |
| |
| /* Only persist if the value changed flag wasn't already sent (i.e., |
| * don't overwrite with identical data). |
| */ |
| if (persist && !cccd_value.value_changed) { |
| cccd_value.value_changed = 1; |
| ble_store_write_cccd(&cccd_value); |
| } |
| |
| /* Read the next matching record. */ |
| cccd_key.idx++; |
| } |
| } |
| |
| int |
| ble_gatts_peer_cl_sup_feat_get(uint16_t conn_handle, uint8_t *out_supported_feat, uint8_t len) |
| { |
| struct ble_hs_conn *conn; |
| int rc = 0; |
| |
| if (out_supported_feat == NULL) { |
| return BLE_HS_EINVAL; |
| } |
| |
| ble_hs_lock(); |
| conn = ble_hs_conn_find(conn_handle); |
| if (conn == NULL) { |
| rc = BLE_HS_ENOTCONN; |
| goto done; |
| } |
| |
| if (BLE_GATT_CHR_CLI_SUP_FEAT_SZ < len) { |
| len = BLE_GATT_CHR_CLI_SUP_FEAT_SZ; |
| } |
| |
| memcpy(out_supported_feat, conn->bhc_gatt_svr.peer_cl_sup_feat, |
| sizeof(uint8_t) * len); |
| |
| done: |
| ble_hs_unlock(); |
| return rc; |
| } |
| |
| int |
| ble_gatts_peer_cl_sup_feat_update(uint16_t conn_handle, struct os_mbuf *om) |
| { |
| struct ble_hs_conn *conn; |
| uint8_t feat[BLE_GATT_CHR_CLI_SUP_FEAT_SZ] = {}; |
| uint16_t len; |
| int rc = 0; |
| int i; |
| |
| BLE_HS_LOG(DEBUG, ""); |
| |
| if (!om) { |
| return BLE_ATT_ERR_INSUFFICIENT_RES; |
| } |
| |
| /* RFU bits are ignored so we can skip any bytes larger than supported */ |
| len = os_mbuf_len(om); |
| if (len > BLE_GATT_CHR_CLI_SUP_FEAT_SZ) { |
| len = BLE_GATT_CHR_CLI_SUP_FEAT_SZ; |
| } |
| |
| if (os_mbuf_copydata(om, 0, len, feat) < 0) { |
| return BLE_ATT_ERR_UNLIKELY; |
| } |
| |
| /* clear RFU bits */ |
| for (i = 0; i < BLE_GATT_CHR_CLI_SUP_FEAT_SZ; i++) { |
| feat[i] &= (BLE_GATT_CHR_CLI_SUP_FEAT_MASK >> (8 * i)); |
| } |
| |
| ble_hs_lock(); |
| conn = ble_hs_conn_find(conn_handle); |
| if (conn == NULL) { |
| rc = BLE_ATT_ERR_UNLIKELY; |
| goto done; |
| } |
| |
| /** |
| * Disabling already enabled features is not permitted |
| * (Vol. 3, Part F, 3.3.3) |
| */ |
| for (i = 0; i < BLE_GATT_CHR_CLI_SUP_FEAT_SZ; i++) { |
| if ((conn->bhc_gatt_svr.peer_cl_sup_feat[i] & feat[i]) != |
| conn->bhc_gatt_svr.peer_cl_sup_feat[i]) { |
| rc = BLE_ATT_ERR_VALUE_NOT_ALLOWED; |
| goto done; |
| } |
| } |
| |
| memcpy(conn->bhc_gatt_svr.peer_cl_sup_feat, feat, BLE_GATT_CHR_CLI_SUP_FEAT_SZ); |
| |
| done: |
| ble_hs_unlock(); |
| return rc; |
| } |
| |
| /** |
| * Sends notifications or indications for the specified characteristic to all |
| * connected devices. The bluetooth spec does not allow more than one |
| * concurrent indication for a single peer, so this function will hold off on |
| * sending such indications. |
| */ |
| static void |
| ble_gatts_tx_notifications_one_chr(uint16_t chr_val_handle) |
| { |
| struct ble_gatts_clt_cfg *clt_cfg; |
| struct ble_hs_conn *conn; |
| uint16_t conn_handle; |
| uint8_t att_op; |
| int clt_cfg_idx; |
| int i; |
| |
| /* Determine if notifications / indications are enabled for this |
| * characteristic. |
| */ |
| clt_cfg_idx = ble_gatts_clt_cfg_find_idx(ble_gatts_clt_cfgs, |
| chr_val_handle); |
| if (clt_cfg_idx == -1) { |
| return; |
| } |
| |
| for (i = 0; ; i++) { |
| ble_hs_lock(); |
| |
| conn = ble_hs_conn_find_by_idx(i); |
| if (conn != NULL) { |
| BLE_HS_DBG_ASSERT_EVAL(conn->bhc_gatt_svr.num_clt_cfgs > |
| clt_cfg_idx); |
| clt_cfg = conn->bhc_gatt_svr.clt_cfgs + clt_cfg_idx; |
| BLE_HS_DBG_ASSERT_EVAL(clt_cfg->chr_val_handle == chr_val_handle); |
| |
| /* Determine what type of command should get sent, if any. */ |
| att_op = ble_gatts_schedule_update(conn, clt_cfg); |
| conn_handle = conn->bhc_handle; |
| } else { |
| /* Silence some spurious gcc warnings. */ |
| att_op = 0; |
| conn_handle = BLE_HS_CONN_HANDLE_NONE; |
| } |
| ble_hs_unlock(); |
| |
| if (conn == NULL) { |
| /* No more connected devices. */ |
| break; |
| } |
| |
| switch (att_op) { |
| case 0: |
| break; |
| |
| case BLE_ATT_OP_NOTIFY_REQ: |
| ble_gatts_notify(conn_handle, chr_val_handle); |
| break; |
| |
| case BLE_ATT_OP_INDICATE_REQ: |
| ble_gatts_indicate(conn_handle, chr_val_handle); |
| break; |
| |
| default: |
| BLE_HS_DBG_ASSERT(0); |
| break; |
| } |
| } |
| } |
| |
| /** |
| * Sends all pending notifications and indications. The bluetooth spec does |
| * not allow more than one concurrent indication for a single peer, so this |
| * function will hold off on sending such indications. |
| */ |
| void |
| ble_gatts_tx_notifications(void) |
| { |
| uint16_t chr_val_handle; |
| int i; |
| |
| for (i = 0; i < ble_gatts_num_cfgable_chrs; i++) { |
| chr_val_handle = ble_gatts_clt_cfgs[i].chr_val_handle; |
| ble_gatts_tx_notifications_one_chr(chr_val_handle); |
| } |
| } |
| |
| void |
| ble_gatts_bonding_established(uint16_t conn_handle) |
| { |
| struct ble_store_value_cccd cccd_value; |
| struct ble_gatts_clt_cfg *clt_cfg; |
| struct ble_gatts_conn *gatt_srv; |
| struct ble_hs_conn *conn; |
| int i; |
| |
| ble_hs_lock(); |
| |
| conn = ble_hs_conn_find(conn_handle); |
| BLE_HS_DBG_ASSERT(conn != NULL); |
| BLE_HS_DBG_ASSERT(conn->bhc_sec_state.bonded); |
| |
| cccd_value.peer_addr = conn->bhc_peer_addr; |
| cccd_value.peer_addr.type = |
| ble_hs_misc_peer_addr_type_to_id(conn->bhc_peer_addr.type); |
| gatt_srv = &conn->bhc_gatt_svr; |
| |
| for (i = 0; i < gatt_srv->num_clt_cfgs; ++i) { |
| clt_cfg = &gatt_srv->clt_cfgs[i]; |
| |
| if (clt_cfg->flags != 0) { |
| cccd_value.chr_val_handle = clt_cfg->chr_val_handle; |
| cccd_value.flags = clt_cfg->flags; |
| cccd_value.value_changed = 0; |
| |
| /* Store write use ble_hs_lock */ |
| ble_hs_unlock(); |
| ble_store_write_cccd(&cccd_value); |
| ble_hs_lock(); |
| |
| conn = ble_hs_conn_find(conn_handle); |
| BLE_HS_DBG_ASSERT(conn != NULL); |
| } |
| } |
| |
| ble_hs_unlock(); |
| } |
| |
| /** |
| * Called when bonding has been restored via the encryption procedure. This |
| * function: |
| * o Restores persisted CCCD entries for the connected peer. |
| * o Sends all pending notifications to the connected peer. |
| * o Sends up to one pending indication to the connected peer; schedules |
| * any remaining pending indications. |
| */ |
| void |
| ble_gatts_bonding_restored(uint16_t conn_handle) |
| { |
| struct ble_store_value_cccd cccd_value; |
| struct ble_store_key_cccd cccd_key; |
| struct ble_gatts_clt_cfg *clt_cfg; |
| struct ble_hs_conn *conn; |
| uint8_t att_op; |
| int rc; |
| |
| ble_hs_lock(); |
| |
| conn = ble_hs_conn_find(conn_handle); |
| BLE_HS_DBG_ASSERT(conn != NULL); |
| BLE_HS_DBG_ASSERT(conn->bhc_sec_state.bonded); |
| |
| cccd_key.peer_addr = conn->bhc_peer_addr; |
| cccd_key.peer_addr.type = |
| ble_hs_misc_peer_addr_type_to_id(conn->bhc_peer_addr.type); |
| cccd_key.chr_val_handle = 0; |
| cccd_key.idx = 0; |
| |
| ble_hs_unlock(); |
| |
| while (1) { |
| rc = ble_store_read_cccd(&cccd_key, &cccd_value); |
| if (rc != 0) { |
| break; |
| } |
| |
| /* Assume no notification or indication will get sent. */ |
| att_op = 0; |
| |
| ble_hs_lock(); |
| |
| conn = ble_hs_conn_find(conn_handle); |
| BLE_HS_DBG_ASSERT(conn != NULL); |
| |
| clt_cfg = ble_gatts_clt_cfg_find(conn->bhc_gatt_svr.clt_cfgs, |
| cccd_value.chr_val_handle); |
| if (clt_cfg != NULL) { |
| clt_cfg->flags = cccd_value.flags; |
| |
| if (cccd_value.value_changed) { |
| /* The characteristic's value changed while the device was |
| * disconnected or unbonded. Schedule the notification or |
| * indication now. |
| */ |
| clt_cfg->flags |= BLE_GATTS_CLT_CFG_F_MODIFIED; |
| att_op = ble_gatts_schedule_update(conn, clt_cfg); |
| } |
| } |
| |
| ble_hs_unlock(); |
| |
| /* Tell the application if the peer changed its subscription state |
| * when it was restored from persistence. |
| */ |
| ble_gatts_subscribe_event(conn_handle, cccd_value.chr_val_handle, |
| BLE_GAP_SUBSCRIBE_REASON_RESTORE, |
| 0, cccd_value.flags); |
| |
| switch (att_op) { |
| case 0: |
| break; |
| |
| case BLE_ATT_OP_NOTIFY_REQ: |
| rc = ble_gatts_notify(conn_handle, cccd_value.chr_val_handle); |
| if (rc == 0) { |
| cccd_value.value_changed = 0; |
| ble_store_write_cccd(&cccd_value); |
| } |
| break; |
| |
| case BLE_ATT_OP_INDICATE_REQ: |
| ble_gatts_indicate(conn_handle, cccd_value.chr_val_handle); |
| break; |
| |
| default: |
| BLE_HS_DBG_ASSERT(0); |
| break; |
| } |
| |
| cccd_key.idx++; |
| } |
| } |
| |
| static struct ble_gatts_svc_entry * |
| ble_gatts_find_svc_entry(const ble_uuid_t *uuid) |
| { |
| struct ble_gatts_svc_entry *entry; |
| int i; |
| |
| for (i = 0; i < ble_gatts_num_svc_entries; i++) { |
| entry = ble_gatts_svc_entries + i; |
| if (ble_uuid_cmp(uuid, entry->svc->uuid) == 0) { |
| return entry; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static int |
| ble_gatts_find_svc_chr_attr(const ble_uuid_t *svc_uuid, |
| const ble_uuid_t *chr_uuid, |
| struct ble_gatts_svc_entry **out_svc_entry, |
| struct ble_att_svr_entry **out_att_chr) |
| { |
| struct ble_gatts_svc_entry *svc_entry; |
| struct ble_att_svr_entry *att_svc; |
| struct ble_att_svr_entry *next; |
| struct ble_att_svr_entry *cur; |
| |
| svc_entry = ble_gatts_find_svc_entry(svc_uuid); |
| if (svc_entry == NULL) { |
| return BLE_HS_ENOENT; |
| } |
| |
| att_svc = ble_att_svr_find_by_handle(svc_entry->handle); |
| if (att_svc == NULL) { |
| return BLE_HS_EUNKNOWN; |
| } |
| |
| cur = STAILQ_NEXT(att_svc, ha_next); |
| while (1) { |
| if (cur == NULL) { |
| /* Reached end of attribute list without a match. */ |
| return BLE_HS_ENOENT; |
| } |
| next = STAILQ_NEXT(cur, ha_next); |
| |
| if (cur->ha_handle_id == svc_entry->end_group_handle) { |
| /* Reached end of service without a match. */ |
| return BLE_HS_ENOENT; |
| } |
| |
| if (ble_uuid_u16(cur->ha_uuid) == BLE_ATT_UUID_CHARACTERISTIC && |
| next != NULL && |
| ble_uuid_cmp(next->ha_uuid, chr_uuid) == 0) { |
| |
| if (out_svc_entry != NULL) { |
| *out_svc_entry = svc_entry; |
| } |
| if (out_att_chr != NULL) { |
| *out_att_chr = next; |
| } |
| return 0; |
| } |
| |
| cur = next; |
| } |
| } |
| |
| int |
| ble_gatts_find_svc(const ble_uuid_t *uuid, uint16_t *out_handle) |
| { |
| struct ble_gatts_svc_entry *entry; |
| |
| entry = ble_gatts_find_svc_entry(uuid); |
| if (entry == NULL) { |
| return BLE_HS_ENOENT; |
| } |
| |
| if (out_handle != NULL) { |
| *out_handle = entry->handle; |
| } |
| return 0; |
| } |
| |
| int |
| ble_gatts_find_chr(const ble_uuid_t *svc_uuid, const ble_uuid_t *chr_uuid, |
| uint16_t *out_def_handle, uint16_t *out_val_handle) |
| { |
| struct ble_att_svr_entry *att_chr; |
| int rc; |
| |
| rc = ble_gatts_find_svc_chr_attr(svc_uuid, chr_uuid, NULL, &att_chr); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| if (out_def_handle) { |
| *out_def_handle = att_chr->ha_handle_id - 1; |
| } |
| if (out_val_handle) { |
| *out_val_handle = att_chr->ha_handle_id; |
| } |
| return 0; |
| } |
| |
| int |
| ble_gatts_find_dsc(const ble_uuid_t *svc_uuid, const ble_uuid_t *chr_uuid, |
| const ble_uuid_t *dsc_uuid, uint16_t *out_handle) |
| { |
| struct ble_gatts_svc_entry *svc_entry; |
| struct ble_att_svr_entry *att_chr; |
| struct ble_att_svr_entry *cur; |
| uint16_t uuid16; |
| int rc; |
| |
| rc = ble_gatts_find_svc_chr_attr(svc_uuid, chr_uuid, &svc_entry, |
| &att_chr); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| cur = STAILQ_NEXT(att_chr, ha_next); |
| while (1) { |
| if (cur == NULL) { |
| /* Reached end of attribute list without a match. */ |
| return BLE_HS_ENOENT; |
| } |
| |
| if (cur->ha_handle_id > svc_entry->end_group_handle) { |
| /* Reached end of service without a match. */ |
| return BLE_HS_ENOENT; |
| } |
| |
| uuid16 = ble_uuid_u16(cur->ha_uuid); |
| if (uuid16 == BLE_ATT_UUID_CHARACTERISTIC) { |
| /* Reached end of characteristic without a match. */ |
| return BLE_HS_ENOENT; |
| } |
| |
| if (ble_uuid_cmp(cur->ha_uuid, dsc_uuid) == 0) { |
| if (out_handle != NULL) { |
| *out_handle = cur->ha_handle_id; |
| return 0; |
| } |
| } |
| cur = STAILQ_NEXT(cur, ha_next); |
| } |
| } |
| |
| int |
| ble_gatts_add_svcs(const struct ble_gatt_svc_def *svcs) |
| { |
| void *p; |
| int rc; |
| |
| ble_hs_lock(); |
| if (!ble_gatts_mutable()) { |
| rc = BLE_HS_EBUSY; |
| goto done; |
| } |
| |
| p = realloc(ble_gatts_svc_defs, |
| (ble_gatts_num_svc_defs + 1) * sizeof *ble_gatts_svc_defs); |
| if (p == NULL) { |
| rc = BLE_HS_ENOMEM; |
| goto done; |
| } |
| |
| ble_gatts_svc_defs = p; |
| ble_gatts_svc_defs[ble_gatts_num_svc_defs] = svcs; |
| ble_gatts_num_svc_defs++; |
| |
| rc = 0; |
| |
| done: |
| ble_hs_unlock(); |
| return rc; |
| } |
| |
| int |
| ble_gatts_svc_set_visibility(uint16_t handle, int visible) |
| { |
| int i; |
| |
| for (i = 0; i < ble_gatts_num_svc_entries; i++) { |
| struct ble_gatts_svc_entry *entry = &ble_gatts_svc_entries[i]; |
| |
| if (entry->handle == handle) { |
| if (visible) { |
| ble_att_svr_restore_range(entry->handle, entry->end_group_handle); |
| } else { |
| ble_att_svr_hide_range(entry->handle, entry->end_group_handle); |
| } |
| return 0; |
| } |
| } |
| |
| return BLE_HS_ENOENT; |
| } |
| |
| /** |
| * Accumulates counts of each resource type required by the specified service |
| * definition array. This function is generally used to calculate some host |
| * configuration values prior to initialization. This function adds the counts |
| * to the appropriate fields in the supplied ble_gatt_resources object without |
| * clearing them first, so it can be called repeatedly with different inputs to |
| * calculate totals. Be sure to zero the resource struct prior to the first |
| * call to this function. |
| * |
| * @param svcs The service array containing the resource |
| * definitions to be counted. |
| * @param res The resource counts are accumulated in this |
| * struct. |
| * |
| * @return 0 on success; |
| * BLE_HS_EINVAL if the svcs array contains an |
| * invalid resource definition. |
| */ |
| static int |
| ble_gatts_count_resources(const struct ble_gatt_svc_def *svcs, |
| struct ble_gatt_resources *res) |
| { |
| const struct ble_gatt_svc_def *svc; |
| const struct ble_gatt_chr_def *chr; |
| int s; |
| int i; |
| int c; |
| int d; |
| |
| for (s = 0; svcs[s].type != BLE_GATT_SVC_TYPE_END; s++) { |
| svc = svcs + s; |
| |
| if (!ble_gatts_svc_is_sane(svc)) { |
| BLE_HS_DBG_ASSERT(0); |
| return BLE_HS_EINVAL; |
| } |
| |
| /* Each service requires: |
| * o 1 service |
| * o 1 attribute |
| */ |
| res->svcs++; |
| res->attrs++; |
| |
| if (svc->includes != NULL) { |
| for (i = 0; svc->includes[i] != NULL; i++) { |
| /* Each include requires: |
| * o 1 include |
| * o 1 attribute |
| */ |
| res->incs++; |
| res->attrs++; |
| } |
| } |
| |
| if (svc->characteristics != NULL) { |
| for (c = 0; svc->characteristics[c].uuid != NULL; c++) { |
| chr = svc->characteristics + c; |
| |
| if (!ble_gatts_chr_is_sane(chr)) { |
| BLE_HS_DBG_ASSERT(0); |
| return BLE_HS_EINVAL; |
| } |
| |
| /* Each characteristic requires: |
| * o 1 characteristic |
| * o 2 attributes |
| */ |
| res->chrs++; |
| res->attrs += 2; |
| |
| /* If the characteristic permits notifications or indications, |
| * it has a CCCD. |
| */ |
| if (chr->flags & BLE_GATT_CHR_F_NOTIFY || |
| chr->flags & BLE_GATT_CHR_F_INDICATE) { |
| |
| /* Each CCCD requires: |
| * o 1 descriptor |
| * o 1 CCCD |
| * o 1 attribute |
| */ |
| res->dscs++; |
| res->cccds++; |
| res->attrs++; |
| } |
| |
| if (chr->descriptors != NULL) { |
| for (d = 0; chr->descriptors[d].uuid != NULL; d++) { |
| if (!ble_gatts_dsc_is_sane(chr->descriptors + d)) { |
| BLE_HS_DBG_ASSERT(0); |
| return BLE_HS_EINVAL; |
| } |
| |
| /* Each descriptor requires: |
| * o 1 descriptor |
| * o 1 attribute |
| */ |
| res->dscs++; |
| res->attrs++; |
| } |
| } |
| } |
| } |
| } |
| |
| return 0; |
| } |
| int |
| ble_gatts_count_cfg(const struct ble_gatt_svc_def *defs) |
| { |
| struct ble_gatt_resources res = { 0 }; |
| int rc; |
| |
| rc = ble_gatts_count_resources(defs, &res); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| ble_hs_max_services += res.svcs; |
| ble_hs_max_attrs += res.attrs; |
| |
| /* Reserve an extra CCCD for the cache. */ |
| ble_hs_max_client_configs += |
| res.cccds * (MYNEWT_VAL(BLE_MAX_CONNECTIONS) + 1); |
| |
| return 0; |
| } |
| |
| void |
| ble_gatts_lcl_svc_foreach(ble_gatt_svc_foreach_fn cb, void *arg) |
| { |
| int i; |
| |
| for (i = 0; i < ble_gatts_num_svc_entries; i++) { |
| cb(ble_gatts_svc_entries[i].svc, |
| ble_gatts_svc_entries[i].handle, |
| ble_gatts_svc_entries[i].end_group_handle, arg); |
| } |
| } |
| |
| int |
| ble_gatts_reset(void) |
| { |
| int rc; |
| |
| ble_hs_lock(); |
| |
| if (!ble_gatts_mutable()) { |
| rc = BLE_HS_EBUSY; |
| } else { |
| /* Unregister all ATT attributes. */ |
| ble_att_svr_reset(); |
| ble_gatts_num_cfgable_chrs = 0; |
| rc = 0; |
| |
| /* Note: gatts memory gets freed on next call to ble_gatts_start(). */ |
| } |
| |
| ble_hs_unlock(); |
| |
| return rc; |
| } |
| |
| int |
| ble_gatts_init(void) |
| { |
| int rc; |
| |
| ble_gatts_num_cfgable_chrs = 0; |
| ble_gatts_clt_cfgs = NULL; |
| |
| rc = stats_init_and_reg( |
| STATS_HDR(ble_gatts_stats), STATS_SIZE_INIT_PARMS(ble_gatts_stats, |
| STATS_SIZE_32), STATS_NAME_INIT_PARMS(ble_gatts_stats), "ble_gatts"); |
| if (rc != 0) { |
| return BLE_HS_EOS; |
| } |
| |
| return 0; |
| |
| } |