/*
|
* 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 = pvPortMalloc(
|
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 =
|
pvPortMalloc(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++;
|
}
|
}
|
|
/**
|
* 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;
|
|
}
|