/*
|
* 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.
|
*/
|
|
/**
|
* GATT client - Generic Attribute Profile; client operations.
|
*
|
* Design overview:
|
*
|
* GATT client procedures are initiated by the application via function calls.
|
* Such functions return when either of the following happens:
|
*
|
* (1) The procedure completes (success or failure).
|
* (2) The procedure cannot proceed until a BLE peer responds.
|
*
|
* For (1), the result of the procedure if fully indicated by the function
|
* return code.
|
* For (2), the procedure result is indicated by an application-configured
|
* callback. The callback is executed when the procedure completes.
|
*
|
* Notes on thread-safety:
|
* 1. The ble_hs mutex must never be locked when an application callback is
|
* executed. A callback is free to initiate additional host procedures.
|
* 2. The only resource protected by the mutex is the list of active procedures
|
* (ble_gattc_procs). Thread-safety is achieved by locking the mutex during
|
* removal and insertion operations. Procedure objects are only modified
|
* while they are not in the list. This is sufficient, as the host parent
|
* task is the only task which inspects or modifies individual procedure
|
* entries. Tasks have the following permissions regarding procedure
|
* entries:
|
*
|
* | insert | remove | inspect | modify
|
* ------------+---------+-----------|-----------|---------
|
* parent task | X | X | X | X
|
* other tasks | X | | |
|
*/
|
#pragma diag_suppress 111
|
|
#include <stddef.h>
|
#include <stdlib.h>
|
#include <errno.h>
|
#include <string.h>
|
#include "os/os_mempool.h"
|
#include "nimble/ble.h"
|
#include "host/ble_gatt.h"
|
#include "host/ble_uuid.h"
|
#include "host/ble_gap.h"
|
#include "ble_hs_priv.h"
|
#include "os/panchip_mempool.h"
|
|
#if NIMBLE_BLE_CONNECT
|
/*****************************************************************************
|
* $definitions / declarations *
|
*****************************************************************************/
|
|
/**
|
* The maximum time to wait for a single ATT response. The spec defines this
|
* as the ATT transaction time (Vol. 3, Part F, 3.3.3)
|
*/
|
#define BLE_GATTC_UNRESPONSIVE_TIMEOUT_MS 30000 /* ms */
|
|
#define BLE_GATT_OP_NONE UINT8_MAX
|
#define BLE_GATT_OP_MTU 0
|
#define BLE_GATT_OP_DISC_ALL_SVCS 1
|
#define BLE_GATT_OP_DISC_SVC_UUID 2
|
#define BLE_GATT_OP_FIND_INC_SVCS 3
|
#define BLE_GATT_OP_DISC_ALL_CHRS 4
|
#define BLE_GATT_OP_DISC_CHR_UUID 5
|
#define BLE_GATT_OP_DISC_ALL_DSCS 6
|
#define BLE_GATT_OP_READ 7
|
#define BLE_GATT_OP_READ_UUID 8
|
#define BLE_GATT_OP_READ_LONG 9
|
#define BLE_GATT_OP_READ_MULT 10
|
#define BLE_GATT_OP_WRITE 11
|
#define BLE_GATT_OP_WRITE_LONG 12
|
#define BLE_GATT_OP_WRITE_RELIABLE 13
|
#define BLE_GATT_OP_INDICATE 14
|
#define BLE_GATT_OP_CNT 15
|
|
/** Procedure stalled due to resource exhaustion. */
|
#define BLE_GATTC_PROC_F_STALLED 0x01
|
|
/** Represents an in-progress GATT procedure. */
|
struct ble_gattc_proc {
|
STAILQ_ENTRY(ble_gattc_proc) next;
|
|
uint32_t exp_os_ticks;
|
uint16_t conn_handle;
|
uint8_t op;
|
uint8_t flags;
|
|
union {
|
struct {
|
ble_gatt_mtu_fn *cb;
|
void *cb_arg;
|
} mtu;
|
|
struct {
|
uint16_t prev_handle;
|
ble_gatt_disc_svc_fn *cb;
|
void *cb_arg;
|
} disc_all_svcs;
|
|
struct {
|
ble_uuid_any_t service_uuid;
|
uint16_t prev_handle;
|
ble_gatt_disc_svc_fn *cb;
|
void *cb_arg;
|
} disc_svc_uuid;
|
|
struct {
|
uint16_t prev_handle;
|
uint16_t end_handle;
|
|
uint16_t cur_start;
|
uint16_t cur_end;
|
|
ble_gatt_disc_svc_fn *cb;
|
void *cb_arg;
|
} find_inc_svcs;
|
|
struct {
|
uint16_t prev_handle;
|
uint16_t end_handle;
|
ble_gatt_chr_fn *cb;
|
void *cb_arg;
|
} disc_all_chrs;
|
|
struct {
|
ble_uuid_any_t chr_uuid;
|
uint16_t prev_handle;
|
uint16_t end_handle;
|
ble_gatt_chr_fn *cb;
|
void *cb_arg;
|
} disc_chr_uuid;
|
|
struct {
|
uint16_t chr_val_handle;
|
uint16_t prev_handle;
|
uint16_t end_handle;
|
ble_gatt_dsc_fn *cb;
|
void *cb_arg;
|
} disc_all_dscs;
|
|
struct {
|
uint16_t handle;
|
ble_gatt_attr_fn *cb;
|
void *cb_arg;
|
} read;
|
|
struct {
|
ble_uuid_any_t chr_uuid;
|
uint16_t start_handle;
|
uint16_t end_handle;
|
ble_gatt_attr_fn *cb;
|
void *cb_arg;
|
} read_uuid;
|
|
struct {
|
uint16_t handle;
|
uint16_t offset;
|
ble_gatt_attr_fn *cb;
|
void *cb_arg;
|
} read_long;
|
|
struct {
|
uint16_t handles[MYNEWT_VAL(BLE_GATT_READ_MAX_ATTRS)];
|
uint8_t num_handles;
|
ble_gatt_attr_fn *cb;
|
void *cb_arg;
|
} read_mult;
|
|
struct {
|
uint16_t att_handle;
|
ble_gatt_attr_fn *cb;
|
void *cb_arg;
|
} write;
|
|
struct {
|
struct ble_gatt_attr attr;
|
uint16_t length;
|
ble_gatt_attr_fn *cb;
|
void *cb_arg;
|
} write_long;
|
|
struct {
|
struct ble_gatt_attr attrs[MYNEWT_VAL(BLE_GATT_WRITE_MAX_ATTRS)];
|
uint8_t num_attrs;
|
uint8_t cur_attr;
|
uint16_t length;
|
ble_gatt_reliable_attr_fn *cb;
|
void *cb_arg;
|
} write_reliable;
|
|
struct {
|
uint16_t chr_val_handle;
|
} indicate;
|
};
|
};
|
|
STAILQ_HEAD(ble_gattc_proc_list, ble_gattc_proc);
|
|
/**
|
* Error functions - these handle an incoming ATT error response and apply it
|
* to the appropriate active GATT procedure.
|
*/
|
typedef void ble_gattc_err_fn(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle);
|
static ble_gattc_err_fn ble_gattc_mtu_err;
|
static ble_gattc_err_fn ble_gattc_disc_all_svcs_err;
|
static ble_gattc_err_fn ble_gattc_disc_svc_uuid_err;
|
static ble_gattc_err_fn ble_gattc_find_inc_svcs_err;
|
static ble_gattc_err_fn ble_gattc_disc_all_chrs_err;
|
static ble_gattc_err_fn ble_gattc_disc_chr_uuid_err;
|
static ble_gattc_err_fn ble_gattc_disc_all_dscs_err;
|
static ble_gattc_err_fn ble_gattc_read_err;
|
static ble_gattc_err_fn ble_gattc_read_uuid_err;
|
static ble_gattc_err_fn ble_gattc_read_long_err;
|
static ble_gattc_err_fn ble_gattc_read_mult_err;
|
static ble_gattc_err_fn ble_gattc_write_err;
|
static ble_gattc_err_fn ble_gattc_write_long_err;
|
static ble_gattc_err_fn ble_gattc_write_reliable_err;
|
static ble_gattc_err_fn ble_gatts_indicate_err;
|
|
static ble_gattc_err_fn * const ble_gattc_err_dispatch[BLE_GATT_OP_CNT] = {
|
[BLE_GATT_OP_MTU] = ble_gattc_mtu_err,
|
[BLE_GATT_OP_DISC_ALL_SVCS] = ble_gattc_disc_all_svcs_err,
|
[BLE_GATT_OP_DISC_SVC_UUID] = ble_gattc_disc_svc_uuid_err,
|
[BLE_GATT_OP_FIND_INC_SVCS] = ble_gattc_find_inc_svcs_err,
|
[BLE_GATT_OP_DISC_ALL_CHRS] = ble_gattc_disc_all_chrs_err,
|
[BLE_GATT_OP_DISC_CHR_UUID] = ble_gattc_disc_chr_uuid_err,
|
[BLE_GATT_OP_DISC_ALL_DSCS] = ble_gattc_disc_all_dscs_err,
|
[BLE_GATT_OP_READ] = ble_gattc_read_err,
|
[BLE_GATT_OP_READ_UUID] = ble_gattc_read_uuid_err,
|
[BLE_GATT_OP_READ_LONG] = ble_gattc_read_long_err,
|
[BLE_GATT_OP_READ_MULT] = ble_gattc_read_mult_err,
|
[BLE_GATT_OP_WRITE] = ble_gattc_write_err,
|
[BLE_GATT_OP_WRITE_LONG] = ble_gattc_write_long_err,
|
[BLE_GATT_OP_WRITE_RELIABLE] = ble_gattc_write_reliable_err,
|
[BLE_GATT_OP_INDICATE] = ble_gatts_indicate_err,
|
};
|
|
/**
|
* Resume functions - these handle periodic retries of procedures that have
|
* stalled due to memory exhaustion.
|
*/
|
typedef int ble_gattc_resume_fn(struct ble_gattc_proc *proc);
|
|
static ble_gattc_resume_fn ble_gattc_disc_all_svcs_resume;
|
static ble_gattc_resume_fn ble_gattc_disc_svc_uuid_resume;
|
static ble_gattc_resume_fn ble_gattc_find_inc_svcs_resume;
|
static ble_gattc_resume_fn ble_gattc_disc_all_chrs_resume;
|
static ble_gattc_resume_fn ble_gattc_disc_chr_uuid_resume;
|
static ble_gattc_resume_fn ble_gattc_disc_all_dscs_resume;
|
static ble_gattc_resume_fn ble_gattc_read_long_resume;
|
static ble_gattc_resume_fn ble_gattc_write_long_resume;
|
static ble_gattc_resume_fn ble_gattc_write_reliable_resume;
|
|
static ble_gattc_resume_fn * const
|
ble_gattc_resume_dispatch[BLE_GATT_OP_CNT] = {
|
[BLE_GATT_OP_MTU] = NULL,
|
[BLE_GATT_OP_DISC_ALL_SVCS] = ble_gattc_disc_all_svcs_resume,
|
[BLE_GATT_OP_DISC_SVC_UUID] = ble_gattc_disc_svc_uuid_resume,
|
[BLE_GATT_OP_FIND_INC_SVCS] = ble_gattc_find_inc_svcs_resume,
|
[BLE_GATT_OP_DISC_ALL_CHRS] = ble_gattc_disc_all_chrs_resume,
|
[BLE_GATT_OP_DISC_CHR_UUID] = ble_gattc_disc_chr_uuid_resume,
|
[BLE_GATT_OP_DISC_ALL_DSCS] = ble_gattc_disc_all_dscs_resume,
|
[BLE_GATT_OP_READ] = NULL,
|
[BLE_GATT_OP_READ_UUID] = NULL,
|
[BLE_GATT_OP_READ_LONG] = ble_gattc_read_long_resume,
|
[BLE_GATT_OP_READ_MULT] = NULL,
|
[BLE_GATT_OP_WRITE] = NULL,
|
[BLE_GATT_OP_WRITE_LONG] = ble_gattc_write_long_resume,
|
[BLE_GATT_OP_WRITE_RELIABLE] = ble_gattc_write_reliable_resume,
|
[BLE_GATT_OP_INDICATE] = NULL,
|
};
|
|
/**
|
* Timeout functions - these notify the application that a GATT procedure has
|
* timed out while waiting for a response.
|
*/
|
typedef void ble_gattc_tmo_fn(struct ble_gattc_proc *proc);
|
|
static ble_gattc_tmo_fn ble_gattc_mtu_tmo;
|
static ble_gattc_tmo_fn ble_gattc_disc_all_svcs_tmo;
|
static ble_gattc_tmo_fn ble_gattc_disc_svc_uuid_tmo;
|
static ble_gattc_tmo_fn ble_gattc_find_inc_svcs_tmo;
|
static ble_gattc_tmo_fn ble_gattc_disc_all_chrs_tmo;
|
static ble_gattc_tmo_fn ble_gattc_disc_chr_uuid_tmo;
|
static ble_gattc_tmo_fn ble_gattc_disc_all_dscs_tmo;
|
static ble_gattc_tmo_fn ble_gattc_read_tmo;
|
static ble_gattc_tmo_fn ble_gattc_read_uuid_tmo;
|
static ble_gattc_tmo_fn ble_gattc_read_long_tmo;
|
static ble_gattc_tmo_fn ble_gattc_read_mult_tmo;
|
static ble_gattc_tmo_fn ble_gattc_write_tmo;
|
static ble_gattc_tmo_fn ble_gattc_write_long_tmo;
|
static ble_gattc_tmo_fn ble_gattc_write_reliable_tmo;
|
static ble_gattc_tmo_fn ble_gatts_indicate_tmo;
|
|
static ble_gattc_tmo_fn * const
|
ble_gattc_tmo_dispatch[BLE_GATT_OP_CNT] = {
|
[BLE_GATT_OP_MTU] = ble_gattc_mtu_tmo,
|
[BLE_GATT_OP_DISC_ALL_SVCS] = ble_gattc_disc_all_svcs_tmo,
|
[BLE_GATT_OP_DISC_SVC_UUID] = ble_gattc_disc_svc_uuid_tmo,
|
[BLE_GATT_OP_FIND_INC_SVCS] = ble_gattc_find_inc_svcs_tmo,
|
[BLE_GATT_OP_DISC_ALL_CHRS] = ble_gattc_disc_all_chrs_tmo,
|
[BLE_GATT_OP_DISC_CHR_UUID] = ble_gattc_disc_chr_uuid_tmo,
|
[BLE_GATT_OP_DISC_ALL_DSCS] = ble_gattc_disc_all_dscs_tmo,
|
[BLE_GATT_OP_READ] = ble_gattc_read_tmo,
|
[BLE_GATT_OP_READ_UUID] = ble_gattc_read_uuid_tmo,
|
[BLE_GATT_OP_READ_LONG] = ble_gattc_read_long_tmo,
|
[BLE_GATT_OP_READ_MULT] = ble_gattc_read_mult_tmo,
|
[BLE_GATT_OP_WRITE] = ble_gattc_write_tmo,
|
[BLE_GATT_OP_WRITE_LONG] = ble_gattc_write_long_tmo,
|
[BLE_GATT_OP_WRITE_RELIABLE] = ble_gattc_write_reliable_tmo,
|
[BLE_GATT_OP_INDICATE] = ble_gatts_indicate_tmo,
|
};
|
|
/**
|
* Receive functions - these handle specific incoming responses and apply them
|
* to the appropriate active GATT procedure.
|
*/
|
typedef int ble_gattc_rx_adata_fn(struct ble_gattc_proc *proc,
|
struct ble_att_read_type_adata *adata);
|
|
typedef int ble_gattc_rx_prep_fn(struct ble_gattc_proc *proc, int status,
|
uint16_t handle, uint16_t offset,
|
struct os_mbuf **om);
|
|
typedef int ble_gattc_rx_attr_fn(struct ble_gattc_proc *proc, int status,
|
struct os_mbuf **om);
|
|
typedef int ble_gattc_rx_complete_fn(struct ble_gattc_proc *proc, int status);
|
typedef int ble_gattc_rx_exec_fn(struct ble_gattc_proc *proc, int status);
|
|
static ble_gattc_rx_adata_fn ble_gattc_find_inc_svcs_rx_adata;
|
static ble_gattc_rx_complete_fn ble_gattc_find_inc_svcs_rx_complete;
|
static ble_gattc_rx_attr_fn ble_gattc_find_inc_svcs_rx_read_rsp;
|
static ble_gattc_rx_adata_fn ble_gattc_disc_all_chrs_rx_adata;
|
static ble_gattc_rx_complete_fn ble_gattc_disc_all_chrs_rx_complete;
|
static ble_gattc_rx_adata_fn ble_gattc_disc_chr_uuid_rx_adata;
|
static ble_gattc_rx_complete_fn ble_gattc_disc_chr_uuid_rx_complete;
|
static ble_gattc_rx_attr_fn ble_gattc_read_rx_read_rsp;
|
static ble_gattc_rx_attr_fn ble_gattc_read_long_rx_read_rsp;
|
static ble_gattc_rx_adata_fn ble_gattc_read_uuid_rx_adata;
|
static ble_gattc_rx_complete_fn ble_gattc_read_uuid_rx_complete;
|
static ble_gattc_rx_prep_fn ble_gattc_write_long_rx_prep;
|
static ble_gattc_rx_exec_fn ble_gattc_write_long_rx_exec;
|
static ble_gattc_rx_prep_fn ble_gattc_write_reliable_rx_prep;
|
static ble_gattc_rx_exec_fn ble_gattc_write_reliable_rx_exec;
|
|
static const struct ble_gattc_rx_adata_entry {
|
uint8_t op;
|
ble_gattc_rx_adata_fn *cb;
|
} ble_gattc_rx_read_type_elem_entries[] = {
|
{ BLE_GATT_OP_FIND_INC_SVCS, ble_gattc_find_inc_svcs_rx_adata },
|
{ BLE_GATT_OP_DISC_ALL_CHRS, ble_gattc_disc_all_chrs_rx_adata },
|
{ BLE_GATT_OP_DISC_CHR_UUID, ble_gattc_disc_chr_uuid_rx_adata },
|
{ BLE_GATT_OP_READ_UUID, ble_gattc_read_uuid_rx_adata },
|
};
|
|
static const struct ble_gattc_rx_complete_entry {
|
uint8_t op;
|
ble_gattc_rx_complete_fn *cb;
|
} ble_gattc_rx_read_type_complete_entries[] = {
|
{ BLE_GATT_OP_FIND_INC_SVCS, ble_gattc_find_inc_svcs_rx_complete },
|
{ BLE_GATT_OP_DISC_ALL_CHRS, ble_gattc_disc_all_chrs_rx_complete },
|
{ BLE_GATT_OP_DISC_CHR_UUID, ble_gattc_disc_chr_uuid_rx_complete },
|
{ BLE_GATT_OP_READ_UUID, ble_gattc_read_uuid_rx_complete },
|
};
|
|
static const struct ble_gattc_rx_attr_entry {
|
uint8_t op;
|
ble_gattc_rx_attr_fn *cb;
|
} ble_gattc_rx_read_rsp_entries[] = {
|
{ BLE_GATT_OP_READ, ble_gattc_read_rx_read_rsp },
|
{ BLE_GATT_OP_READ_LONG, ble_gattc_read_long_rx_read_rsp },
|
{ BLE_GATT_OP_FIND_INC_SVCS, ble_gattc_find_inc_svcs_rx_read_rsp },
|
};
|
|
static const struct ble_gattc_rx_prep_entry {
|
uint8_t op;
|
ble_gattc_rx_prep_fn *cb;
|
} ble_gattc_rx_prep_entries[] = {
|
{ BLE_GATT_OP_WRITE_LONG, ble_gattc_write_long_rx_prep },
|
{ BLE_GATT_OP_WRITE_RELIABLE, ble_gattc_write_reliable_rx_prep },
|
};
|
|
static const struct ble_gattc_rx_exec_entry {
|
uint8_t op;
|
ble_gattc_rx_exec_fn *cb;
|
} ble_gattc_rx_exec_entries[] = {
|
{ BLE_GATT_OP_WRITE_LONG, ble_gattc_write_long_rx_exec },
|
{ BLE_GATT_OP_WRITE_RELIABLE, ble_gattc_write_reliable_rx_exec },
|
};
|
#ifdef IP_101x
|
static os_membuf_t *ble_gattc_proc_mem;
|
#else
|
static os_membuf_t ble_gattc_proc_mem[
|
OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_GATT_MAX_PROCS),
|
sizeof (struct ble_gattc_proc))
|
];
|
#endif
|
static struct os_mempool ble_gattc_proc_pool;
|
|
/* The list of active GATT client procedures. */
|
static struct ble_gattc_proc_list ble_gattc_procs;
|
|
/* The time when we should attempt to resume stalled procedures, in OS ticks.
|
* A value of 0 indicates no stalled procedures.
|
*/
|
static ble_npl_time_t ble_gattc_resume_at;
|
|
/* Statistics. */
|
STATS_SECT_DECL(ble_gattc_stats) ble_gattc_stats;
|
STATS_NAME_START(ble_gattc_stats)
|
STATS_NAME(ble_gattc_stats, mtu)
|
STATS_NAME(ble_gattc_stats, mtu_fail)
|
STATS_NAME(ble_gattc_stats, disc_all_svcs)
|
STATS_NAME(ble_gattc_stats, disc_all_svcs_fail)
|
STATS_NAME(ble_gattc_stats, disc_svc_uuid)
|
STATS_NAME(ble_gattc_stats, disc_svc_uuid_fail)
|
STATS_NAME(ble_gattc_stats, find_inc_svcs)
|
STATS_NAME(ble_gattc_stats, find_inc_svcs_fail)
|
STATS_NAME(ble_gattc_stats, disc_all_chrs)
|
STATS_NAME(ble_gattc_stats, disc_all_chrs_fail)
|
STATS_NAME(ble_gattc_stats, disc_chrs_uuid)
|
STATS_NAME(ble_gattc_stats, disc_chrs_uuid_fail)
|
STATS_NAME(ble_gattc_stats, disc_all_dscs)
|
STATS_NAME(ble_gattc_stats, disc_all_dscs_fail)
|
STATS_NAME(ble_gattc_stats, read)
|
STATS_NAME(ble_gattc_stats, read_fail)
|
STATS_NAME(ble_gattc_stats, read_uuid)
|
STATS_NAME(ble_gattc_stats, read_uuid_fail)
|
STATS_NAME(ble_gattc_stats, read_long)
|
STATS_NAME(ble_gattc_stats, read_long_fail)
|
STATS_NAME(ble_gattc_stats, read_mult)
|
STATS_NAME(ble_gattc_stats, read_mult_fail)
|
STATS_NAME(ble_gattc_stats, write_no_rsp)
|
STATS_NAME(ble_gattc_stats, write_no_rsp_fail)
|
STATS_NAME(ble_gattc_stats, write)
|
STATS_NAME(ble_gattc_stats, write_fail)
|
STATS_NAME(ble_gattc_stats, write_long)
|
STATS_NAME(ble_gattc_stats, write_long_fail)
|
STATS_NAME(ble_gattc_stats, write_reliable)
|
STATS_NAME(ble_gattc_stats, write_reliable_fail)
|
STATS_NAME(ble_gattc_stats, notify)
|
STATS_NAME(ble_gattc_stats, notify_fail)
|
STATS_NAME(ble_gattc_stats, indicate)
|
STATS_NAME(ble_gattc_stats, indicate_fail)
|
STATS_NAME(ble_gattc_stats, proc_timeout)
|
STATS_NAME_END(ble_gattc_stats)
|
|
/*****************************************************************************
|
* $debug *
|
*****************************************************************************/
|
|
static void
|
ble_gattc_dbg_assert_proc_not_inserted(struct ble_gattc_proc *proc)
|
{
|
#if MYNEWT_VAL(BLE_HS_DEBUG)
|
struct ble_gattc_proc *cur;
|
|
ble_hs_lock();
|
|
STAILQ_FOREACH(cur, &ble_gattc_procs, next) {
|
BLE_HS_DBG_ASSERT(cur != proc);
|
}
|
|
ble_hs_unlock();
|
#endif
|
}
|
|
/*****************************************************************************
|
* $log *
|
*****************************************************************************/
|
|
static void
|
ble_gattc_log_proc_init(const char *name)
|
{
|
BLE_HS_LOG(INFO, "GATT procedure initiated: %s", name);
|
}
|
|
static void
|
ble_gattc_log_uuid(const ble_uuid_t *uuid)
|
{
|
char buf[BLE_UUID_STR_LEN];
|
|
ble_uuid_to_str(uuid, buf);
|
|
BLE_HS_LOG(INFO, "%s", buf);
|
}
|
|
static void
|
ble_gattc_log_disc_svc_uuid(struct ble_gattc_proc *proc)
|
{
|
ble_gattc_log_proc_init("discover service by uuid; uuid=");
|
ble_gattc_log_uuid(&proc->disc_svc_uuid.service_uuid.u);
|
BLE_HS_LOG(INFO, "\n");
|
}
|
|
static void
|
ble_gattc_log_find_inc_svcs(struct ble_gattc_proc *proc)
|
{
|
ble_gattc_log_proc_init("find included services; ");
|
BLE_HS_LOG(INFO, "start_handle=%d end_handle=%d\n",
|
proc->find_inc_svcs.prev_handle + 1,
|
proc->find_inc_svcs.end_handle);
|
}
|
|
static void
|
ble_gattc_log_disc_all_chrs(struct ble_gattc_proc *proc)
|
{
|
ble_gattc_log_proc_init("discover all characteristics; ");
|
BLE_HS_LOG(INFO, "start_handle=%d end_handle=%d\n",
|
proc->disc_all_chrs.prev_handle + 1,
|
proc->disc_all_chrs.end_handle);
|
}
|
|
static void
|
ble_gattc_log_disc_chr_uuid(struct ble_gattc_proc *proc)
|
{
|
ble_gattc_log_proc_init("discover characteristics by uuid; ");
|
BLE_HS_LOG(INFO, "start_handle=%d end_handle=%d uuid=",
|
proc->disc_chr_uuid.prev_handle + 1,
|
proc->disc_chr_uuid.end_handle);
|
ble_gattc_log_uuid(&proc->disc_chr_uuid.chr_uuid.u);
|
BLE_HS_LOG(INFO, "\n");
|
}
|
|
static void
|
ble_gattc_log_disc_all_dscs(struct ble_gattc_proc *proc)
|
{
|
ble_gattc_log_proc_init("discover all descriptors; ");
|
BLE_HS_LOG(INFO, "chr_val_handle=%d end_handle=%d\n",
|
proc->disc_all_dscs.chr_val_handle,
|
proc->disc_all_dscs.end_handle);
|
}
|
|
static void
|
ble_gattc_log_read(uint16_t att_handle)
|
{
|
ble_gattc_log_proc_init("read; ");
|
BLE_HS_LOG(INFO, "att_handle=%d\n", att_handle);
|
}
|
|
static void
|
ble_gattc_log_read_uuid(uint16_t start_handle, uint16_t end_handle,
|
const ble_uuid_t *uuid)
|
{
|
ble_gattc_log_proc_init("read by uuid; ");
|
BLE_HS_LOG(INFO, "start_handle=%d end_handle=%d uuid=",
|
start_handle, end_handle);
|
ble_gattc_log_uuid(uuid);
|
BLE_HS_LOG(INFO, "\n");
|
}
|
|
static void
|
ble_gattc_log_read_long(struct ble_gattc_proc *proc)
|
{
|
ble_gattc_log_proc_init("read long; ");
|
BLE_HS_LOG(INFO, "att_handle=%d\n", proc->read_long.handle);
|
}
|
|
static void
|
ble_gattc_log_read_mult(const uint16_t *handles, uint8_t num_handles)
|
{
|
int i;
|
|
ble_gattc_log_proc_init("read multiple; ");
|
BLE_HS_LOG(INFO, "att_handles=");
|
for (i = 0; i < num_handles; i++) {
|
BLE_HS_LOG(INFO, "%s%d", i != 0 ? "," : "", handles[i]);
|
}
|
BLE_HS_LOG(INFO, "\n");
|
}
|
|
static void
|
ble_gattc_log_write(uint16_t att_handle, uint16_t len, int expecting_rsp)
|
{
|
const char *name;
|
|
if (expecting_rsp) {
|
name = "write; ";
|
} else {
|
name = "write no rsp; ";
|
}
|
|
ble_gattc_log_proc_init(name);
|
BLE_HS_LOG(INFO, "att_handle=%d len=%d\n", att_handle, len);
|
}
|
|
static void
|
ble_gattc_log_write_long(struct ble_gattc_proc *proc)
|
{
|
ble_gattc_log_proc_init("write long; ");
|
BLE_HS_LOG(INFO, "att_handle=%d len=%d\n",
|
proc->write_long.attr.handle,
|
OS_MBUF_PKTLEN(proc->write_long.attr.om));
|
}
|
|
static void
|
ble_gattc_log_write_reliable(struct ble_gattc_proc *proc)
|
{
|
int i;
|
|
ble_gattc_log_proc_init("write reliable; ");
|
BLE_HS_LOG(INFO, "att_handles=");
|
for (i = 0; i < proc->write_reliable.num_attrs; i++) {
|
BLE_HS_LOG(INFO, "%s%d", i != 0 ? "," : "",
|
proc->write_reliable.attrs[i].handle);
|
}
|
BLE_HS_LOG(INFO, "\n");
|
}
|
|
static void
|
ble_gattc_log_notify(uint16_t att_handle)
|
{
|
ble_gattc_log_proc_init("notify; ");
|
BLE_HS_LOG(INFO, "att_handle=%d\n", att_handle);
|
}
|
|
static void
|
ble_gattc_log_indicate(uint16_t att_handle)
|
{
|
ble_gattc_log_proc_init("indicate; ");
|
BLE_HS_LOG(INFO, "att_handle=%d\n", att_handle);
|
}
|
|
/*****************************************************************************
|
* $rx entry *
|
*****************************************************************************/
|
|
static const void *
|
ble_gattc_rx_entry_find(uint8_t op, const void *rx_entries, int num_entries)
|
{
|
struct gen_entry {
|
uint8_t op;
|
void (*cb)(void);
|
};
|
|
const struct gen_entry *entries;
|
int i;
|
|
entries = rx_entries;
|
for (i = 0; i < num_entries; i++) {
|
if (entries[i].op == op) {
|
return entries + i;
|
}
|
}
|
|
return NULL;
|
}
|
|
/*****************************************************************************
|
* $proc *
|
*****************************************************************************/
|
|
/**
|
* Allocates a proc entry.
|
*
|
* @return An entry on success; null on failure.
|
*/
|
static struct ble_gattc_proc *
|
ble_gattc_proc_alloc(void)
|
{
|
struct ble_gattc_proc *proc;
|
|
proc = os_memblock_get(&ble_gattc_proc_pool);
|
if (proc != NULL) {
|
memset(proc, 0, sizeof *proc);
|
}
|
|
return proc;
|
}
|
|
/**
|
* Frees the specified proc entry. No-op if passed a null pointer.
|
*/
|
static void
|
ble_gattc_proc_free(struct ble_gattc_proc *proc)
|
{
|
int rc;
|
int i;
|
|
if (proc != NULL) {
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
switch (proc->op) {
|
case BLE_GATT_OP_WRITE_LONG:
|
if (MYNEWT_VAL(BLE_GATT_WRITE_LONG)) {
|
os_mbuf_free_chain(proc->write_long.attr.om);
|
}
|
break;
|
|
case BLE_GATT_OP_WRITE_RELIABLE:
|
if (MYNEWT_VAL(BLE_GATT_WRITE_RELIABLE)) {
|
for (i = 0; i < proc->write_reliable.num_attrs; i++) {
|
os_mbuf_free_chain(proc->write_reliable.attrs[i].om);
|
}
|
}
|
break;
|
|
default:
|
break;
|
}
|
|
#if MYNEWT_VAL(BLE_HS_DEBUG)
|
memset(proc, 0xff, sizeof *proc);
|
#endif
|
rc = os_memblock_put(&ble_gattc_proc_pool, proc);
|
BLE_HS_DBG_ASSERT_EVAL(rc == 0);
|
}
|
}
|
|
static void
|
ble_gattc_proc_insert(struct ble_gattc_proc *proc)
|
{
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
ble_hs_lock();
|
STAILQ_INSERT_TAIL(&ble_gattc_procs, proc, next);
|
ble_hs_unlock();
|
}
|
|
static void
|
ble_gattc_proc_set_exp_timer(struct ble_gattc_proc *proc)
|
{
|
proc->exp_os_ticks = ble_npl_time_get() +
|
ble_npl_time_ms_to_ticks32(BLE_GATTC_UNRESPONSIVE_TIMEOUT_MS);
|
}
|
|
static void
|
ble_gattc_proc_set_resume_timer(struct ble_gattc_proc *proc)
|
{
|
proc->flags |= BLE_GATTC_PROC_F_STALLED;
|
|
/* Don't overwrite resume time if it is already set; piggyback on it
|
* instead.
|
*/
|
if (ble_gattc_resume_at == 0) {
|
ble_gattc_resume_at = ble_npl_time_get() +
|
ble_npl_time_ms_to_ticks32(MYNEWT_VAL(BLE_GATT_RESUME_RATE));
|
|
/* A value of 0 indicates the timer is unset. Disambiguate this. */
|
if (ble_gattc_resume_at == 0) {
|
ble_gattc_resume_at++;
|
}
|
}
|
}
|
|
static void
|
ble_gattc_process_status(struct ble_gattc_proc *proc, int status)
|
{
|
switch (status) {
|
case 0:
|
if (!(proc->flags & BLE_GATTC_PROC_F_STALLED)) {
|
ble_gattc_proc_set_exp_timer(proc);
|
}
|
|
ble_gattc_proc_insert(proc);
|
ble_hs_timer_resched();
|
break;
|
|
default:
|
ble_gattc_proc_free(proc);
|
break;
|
}
|
}
|
|
/**
|
* Processes the return code that results from an attempt to resume a
|
* procedure. If the resume attempt failed due to memory exhaustion at a lower
|
* layer, the procedure is marked as stalled but still in progress. Otherwise,
|
* the resume error code is unmodified.
|
*/
|
static int
|
ble_gattc_process_resume_status(struct ble_gattc_proc *proc, int status)
|
{
|
switch (status) {
|
case 0:
|
return 0;
|
|
case BLE_HS_ENOMEM:
|
ble_gattc_proc_set_resume_timer(proc);
|
return 0;
|
|
default:
|
return status;
|
}
|
}
|
|
/*****************************************************************************
|
* $util *
|
*****************************************************************************/
|
|
/**
|
* Retrieves the error dispatch entry with the specified op code.
|
*/
|
static ble_gattc_err_fn *
|
ble_gattc_err_dispatch_get(uint8_t op)
|
{
|
BLE_HS_DBG_ASSERT(op < BLE_GATT_OP_CNT);
|
return ble_gattc_err_dispatch[op];
|
}
|
|
/**
|
* Retrieves the error dispatch entry with the specified op code.
|
*/
|
static ble_gattc_resume_fn *
|
ble_gattc_resume_dispatch_get(uint8_t op)
|
{
|
BLE_HS_DBG_ASSERT(op < BLE_GATT_OP_CNT);
|
return ble_gattc_resume_dispatch[op];
|
}
|
|
static ble_gattc_tmo_fn *
|
ble_gattc_tmo_dispatch_get(uint8_t op)
|
{
|
BLE_HS_DBG_ASSERT(op < BLE_GATT_OP_CNT);
|
return ble_gattc_tmo_dispatch[op];
|
}
|
|
typedef int ble_gattc_match_fn(struct ble_gattc_proc *proc, void *arg);
|
|
struct ble_gattc_criteria_conn_op {
|
uint16_t conn_handle;
|
uint8_t op;
|
};
|
|
/**
|
* Tests if a proc entry fits the specified criteria.
|
*
|
* @param proc The procedure to test.
|
* @param conn_handle The connection handle to match against.
|
* @param op The op code to match against, or
|
* BLE_GATT_OP_NONE to ignore this criterion.
|
*
|
* @return 1 if the proc matches; 0 otherwise.
|
*/
|
static int
|
ble_gattc_proc_matches_conn_op(struct ble_gattc_proc *proc, void *arg)
|
{
|
const struct ble_gattc_criteria_conn_op *criteria;
|
|
criteria = arg;
|
|
if (criteria->conn_handle != proc->conn_handle) {
|
return 0;
|
}
|
|
if (criteria->op != proc->op && criteria->op != BLE_GATT_OP_NONE) {
|
return 0;
|
}
|
|
return 1;
|
}
|
|
struct ble_gattc_criteria_exp {
|
ble_npl_time_t now;
|
ble_npl_stime_t next_exp_in;
|
};
|
|
static int
|
ble_gattc_proc_matches_expired(struct ble_gattc_proc *proc, void *arg)
|
{
|
struct ble_gattc_criteria_exp *criteria;
|
int32_t time_diff;
|
|
criteria = arg;
|
|
time_diff = proc->exp_os_ticks - criteria->now;
|
|
if (time_diff <= 0) {
|
/* Procedure is expired. */
|
return 1;
|
}
|
|
/* Procedure isn't expired; determine if it is the next to expire. */
|
if (time_diff < criteria->next_exp_in) {
|
criteria->next_exp_in = time_diff;
|
}
|
return 0;
|
}
|
|
struct ble_gattc_criteria_conn_rx_entry {
|
uint16_t conn_handle;
|
const void *rx_entries;
|
int num_rx_entries;
|
const void *matching_rx_entry;
|
};
|
|
static int
|
ble_gattc_proc_matches_conn_rx_entry(struct ble_gattc_proc *proc, void *arg)
|
{
|
struct ble_gattc_criteria_conn_rx_entry *criteria;
|
|
criteria = arg;
|
|
if (criteria->conn_handle != BLE_HS_CONN_HANDLE_NONE &&
|
criteria->conn_handle != proc->conn_handle) {
|
|
return 0;
|
}
|
|
/* Entry matches; indicate corresponding rx entry. */
|
criteria->matching_rx_entry = ble_gattc_rx_entry_find(
|
proc->op, criteria->rx_entries, criteria->num_rx_entries);
|
|
return (criteria->matching_rx_entry != NULL);
|
}
|
|
static void
|
ble_gattc_extract(ble_gattc_match_fn *cb, void *arg, int max_procs,
|
struct ble_gattc_proc_list *dst_list)
|
{
|
struct ble_gattc_proc *proc;
|
struct ble_gattc_proc *prev;
|
struct ble_gattc_proc *next;
|
int num_extracted;
|
|
/* Only the parent task is allowed to remove entries from the list. */
|
BLE_HS_DBG_ASSERT(ble_hs_is_parent_task());
|
|
STAILQ_INIT(dst_list);
|
num_extracted = 0;
|
|
ble_hs_lock();
|
|
prev = NULL;
|
proc = STAILQ_FIRST(&ble_gattc_procs);
|
while (proc != NULL) {
|
next = STAILQ_NEXT(proc, next);
|
|
if (cb(proc, arg)) {
|
if (prev == NULL) {
|
STAILQ_REMOVE_HEAD(&ble_gattc_procs, next);
|
} else {
|
STAILQ_REMOVE_AFTER(&ble_gattc_procs, prev, next);
|
}
|
STAILQ_INSERT_TAIL(dst_list, proc, next);
|
|
if (max_procs > 0) {
|
num_extracted++;
|
if (num_extracted >= max_procs) {
|
break;
|
}
|
}
|
} else {
|
prev = proc;
|
}
|
|
proc = next;
|
}
|
|
ble_hs_unlock();
|
}
|
|
static struct ble_gattc_proc *
|
ble_gattc_extract_one(ble_gattc_match_fn *cb, void *arg)
|
{
|
struct ble_gattc_proc_list dst_list;
|
|
ble_gattc_extract(cb, arg, 1, &dst_list);
|
return STAILQ_FIRST(&dst_list);
|
}
|
|
static void
|
ble_gattc_extract_by_conn_op(uint16_t conn_handle, uint8_t op, int max_procs,
|
struct ble_gattc_proc_list *dst_list)
|
{
|
struct ble_gattc_criteria_conn_op criteria;
|
|
criteria.conn_handle = conn_handle;
|
criteria.op = op;
|
|
ble_gattc_extract(ble_gattc_proc_matches_conn_op, &criteria, max_procs, dst_list);
|
}
|
|
static struct ble_gattc_proc *
|
ble_gattc_extract_first_by_conn_op(uint16_t conn_handle, uint8_t op)
|
{
|
struct ble_gattc_proc_list dst_list;
|
|
ble_gattc_extract_by_conn_op(conn_handle, op, 1, &dst_list);
|
return STAILQ_FIRST(&dst_list);
|
}
|
|
static int
|
ble_gattc_proc_matches_stalled(struct ble_gattc_proc *proc, void *unused)
|
{
|
return proc->flags & BLE_GATTC_PROC_F_STALLED;
|
}
|
|
static void
|
ble_gattc_extract_stalled(struct ble_gattc_proc_list *dst_list)
|
{
|
ble_gattc_extract(ble_gattc_proc_matches_stalled, NULL, 0, dst_list);
|
}
|
|
/**
|
* @return The number of ticks until the next expiration
|
* occurs.
|
*/
|
static int32_t
|
ble_gattc_extract_expired(struct ble_gattc_proc_list *dst_list)
|
{
|
struct ble_gattc_criteria_exp criteria;
|
|
criteria.now = ble_npl_time_get();
|
criteria.next_exp_in = BLE_HS_FOREVER;
|
|
STAILQ_INIT(dst_list);
|
ble_gattc_extract(ble_gattc_proc_matches_expired, &criteria, 0, dst_list);
|
|
return criteria.next_exp_in;
|
}
|
|
static struct ble_gattc_proc *
|
ble_gattc_extract_with_rx_entry(uint16_t conn_handle,
|
const void *rx_entries, int num_rx_entries,
|
const void **out_rx_entry)
|
{
|
struct ble_gattc_criteria_conn_rx_entry criteria;
|
struct ble_gattc_proc *proc;
|
|
criteria.conn_handle = conn_handle;
|
criteria.rx_entries = rx_entries;
|
criteria.num_rx_entries = num_rx_entries;
|
criteria.matching_rx_entry = NULL;
|
|
proc = ble_gattc_extract_one(ble_gattc_proc_matches_conn_rx_entry,
|
&criteria);
|
*out_rx_entry = criteria.matching_rx_entry;
|
|
return proc;
|
}
|
|
/**
|
* Searches the main proc list for an entry whose connection handle and op code
|
* match those specified. If a matching entry is found, it is removed from the
|
* list and returned.
|
*
|
* @param conn_handle The connection handle to match against.
|
* @param rx_entries The array of rx entries corresponding to the
|
* op code of the incoming response.
|
* @param out_rx_entry On success, the address of the matching rx
|
* entry is written to this pointer.
|
*
|
* @return The matching proc entry on success;
|
* null on failure.
|
*/
|
#define BLE_GATTC_RX_EXTRACT_RX_ENTRY(conn_handle, rx_entries, out_rx_entry) \
|
ble_gattc_extract_with_rx_entry( \
|
(conn_handle), (rx_entries), \
|
sizeof (rx_entries) / sizeof (rx_entries)[0], \
|
(const void **)(out_rx_entry))
|
|
|
/**
|
* Causes all GATT procedures matching the specified criteria to fail with the
|
* specified status code.
|
*/
|
static void
|
ble_gattc_fail_procs(uint16_t conn_handle, uint8_t op, int status)
|
{
|
struct ble_gattc_proc_list temp_list;
|
struct ble_gattc_proc *proc;
|
ble_gattc_err_fn *err_cb;
|
|
/* Remove all procs with the specified conn handle-op-pair and insert them
|
* into the temporary list.
|
*/
|
ble_gattc_extract_by_conn_op(conn_handle, op, 0, &temp_list);
|
|
/* Notify application of failed procedures and free the corresponding proc
|
* entries.
|
*/
|
while ((proc = STAILQ_FIRST(&temp_list)) != NULL) {
|
err_cb = ble_gattc_err_dispatch_get(proc->op);
|
err_cb(proc, status, 0);
|
|
STAILQ_REMOVE_HEAD(&temp_list, next);
|
ble_gattc_proc_free(proc);
|
}
|
}
|
|
static void
|
ble_gattc_resume_procs(void)
|
{
|
struct ble_gattc_proc_list stall_list;
|
struct ble_gattc_proc *proc;
|
ble_gattc_resume_fn *resume_cb;
|
int rc;
|
|
/* Cancel resume timer since it is being serviced. */
|
ble_gattc_resume_at = 0;
|
|
ble_gattc_extract_stalled(&stall_list);
|
|
STAILQ_FOREACH(proc, &stall_list, next) {
|
resume_cb = ble_gattc_resume_dispatch_get(proc->op);
|
BLE_HS_DBG_ASSERT(resume_cb != NULL);
|
|
proc->flags &= ~BLE_GATTC_PROC_F_STALLED;
|
rc = resume_cb(proc);
|
ble_gattc_process_status(proc, rc);
|
}
|
}
|
|
static int32_t
|
ble_gattc_ticks_until_resume(void)
|
{
|
ble_npl_time_t now;
|
ble_npl_stime_t diff;
|
|
/* Resume timer not set. */
|
if (ble_gattc_resume_at == 0) {
|
return BLE_HS_FOREVER;
|
}
|
|
now = ble_npl_time_get();
|
diff = ble_gattc_resume_at - now;
|
if (diff <= 0) {
|
/* Timer already expired; resume immediately. */
|
return 0;
|
}
|
|
return diff;
|
}
|
|
static void
|
ble_gattc_proc_timeout(struct ble_gattc_proc *proc)
|
{
|
ble_gattc_tmo_fn *cb;
|
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
cb = ble_gattc_tmo_dispatch_get(proc->op);
|
if (cb != NULL) {
|
cb(proc);
|
}
|
}
|
|
/**
|
* Times out expired GATT client procedures.
|
*
|
* @return The number of ticks until this function should
|
* be called again.
|
*/
|
int32_t
|
ble_gattc_timer(void)
|
{
|
struct ble_gattc_proc_list exp_list;
|
struct ble_gattc_proc *proc;
|
int32_t ticks_until_resume;
|
int32_t ticks_until_exp;
|
|
/* Remove timed-out procedures from the main list and insert them into a
|
* temporary list. This function also calculates the number of ticks until
|
* the next expiration will occur.
|
*/
|
ticks_until_exp = ble_gattc_extract_expired(&exp_list);
|
|
/* Terminate the connection associated with each timed-out procedure. */
|
while ((proc = STAILQ_FIRST(&exp_list)) != NULL) {
|
STATS_INC(ble_gattc_stats, proc_timeout);
|
|
ble_gattc_proc_timeout(proc);
|
|
ble_gap_terminate(proc->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
|
|
STAILQ_REMOVE_HEAD(&exp_list, next);
|
ble_gattc_proc_free(proc);
|
}
|
|
/* If there are stalled procedures, the GATT client will need to wake up to
|
* resume them.
|
*/
|
ticks_until_resume = ble_gattc_ticks_until_resume();
|
if (ticks_until_resume == 0) {
|
ble_gattc_resume_procs();
|
ticks_until_resume = ble_gattc_ticks_until_resume();
|
}
|
|
return min(ticks_until_exp, ticks_until_resume);
|
}
|
|
/**
|
* Returns a pointer to a GATT error object with the specified fields. The
|
* returned object is statically allocated, so this function is not reentrant.
|
* This function should only ever be called by the ble_hs task.
|
*/
|
static struct ble_gatt_error *
|
ble_gattc_error(int status, uint16_t att_handle)
|
{
|
static struct ble_gatt_error error;
|
|
/* For consistency, always indicate a handle of 0 on success. */
|
if (status == 0 || status == BLE_HS_EDONE) {
|
att_handle = 0;
|
}
|
|
error.status = status;
|
error.att_handle = att_handle;
|
return &error;
|
}
|
|
/*****************************************************************************
|
* $mtu *
|
*****************************************************************************/
|
|
/**
|
* Calls an mtu-exchange proc's callback with the specified parameters. If the
|
* proc has no callback, this function is a no-op.
|
*
|
* @return The return code of the callback (or 0 if there
|
* is no callback).
|
*/
|
static int
|
ble_gattc_mtu_cb(struct ble_gattc_proc *proc, int status, uint16_t att_handle,
|
uint16_t mtu)
|
{
|
int rc;
|
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status != 0 && status != BLE_HS_EDONE) {
|
STATS_INC(ble_gattc_stats, mtu_fail);
|
}
|
|
if (proc->mtu.cb == NULL) {
|
rc = 0;
|
} else {
|
rc = proc->mtu.cb(proc->conn_handle,
|
ble_gattc_error(status, att_handle),
|
mtu, proc->mtu.cb_arg);
|
}
|
|
return rc;
|
}
|
|
static void
|
ble_gattc_mtu_tmo(struct ble_gattc_proc *proc)
|
{
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
ble_gattc_mtu_cb(proc, BLE_HS_ETIMEOUT, 0, 0);
|
}
|
|
/**
|
* Handles an incoming ATT error response for the specified mtu-exchange proc.
|
*/
|
static void
|
ble_gattc_mtu_err(struct ble_gattc_proc *proc, int status, uint16_t att_handle)
|
{
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
ble_gattc_mtu_cb(proc, status, att_handle, 0);
|
}
|
|
static int
|
ble_gattc_mtu_tx(struct ble_gattc_proc *proc)
|
{
|
struct ble_l2cap_chan *chan;
|
struct ble_hs_conn *conn;
|
uint16_t mtu;
|
int rc;
|
|
ble_hs_lock();
|
rc = ble_att_conn_chan_find(proc->conn_handle, &conn, &chan);
|
if (rc == 0) {
|
mtu = chan->my_mtu;
|
}
|
ble_hs_unlock();
|
|
if (rc == 0) {
|
rc = ble_att_clt_tx_mtu(proc->conn_handle, mtu);
|
}
|
|
return rc;
|
}
|
|
int
|
ble_gattc_exchange_mtu(uint16_t conn_handle, ble_gatt_mtu_fn *cb, void *cb_arg)
|
{
|
struct ble_gattc_proc *proc;
|
int rc;
|
|
STATS_INC(ble_gattc_stats, mtu);
|
|
proc = ble_gattc_proc_alloc();
|
if (proc == NULL) {
|
rc = BLE_HS_ENOMEM;
|
goto done;
|
}
|
|
proc->op = BLE_GATT_OP_MTU;
|
proc->conn_handle = conn_handle;
|
proc->mtu.cb = cb;
|
proc->mtu.cb_arg = cb_arg;
|
|
ble_gattc_log_proc_init("exchange mtu\n");
|
|
rc = ble_gattc_mtu_tx(proc);
|
if (rc != 0) {
|
goto done;
|
}
|
|
done:
|
if (rc != 0) {
|
STATS_INC(ble_gattc_stats, mtu_fail);
|
}
|
|
ble_gattc_process_status(proc, rc);
|
return rc;
|
}
|
|
/*****************************************************************************
|
* $discover all services *
|
*****************************************************************************/
|
|
/**
|
* Calls a discover-all-services proc's callback with the specified parameters.
|
* If the proc has no callback, this function is a no-op.
|
*
|
* @return The return code of the callback (or 0 if there
|
* is no callback).
|
*/
|
static int
|
ble_gattc_disc_all_svcs_cb(struct ble_gattc_proc *proc,
|
uint16_t status, uint16_t att_handle,
|
struct ble_gatt_svc *service)
|
{
|
int rc;
|
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
BLE_HS_DBG_ASSERT(service != NULL || status != 0);
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status != 0 && status != BLE_HS_EDONE) {
|
STATS_INC(ble_gattc_stats, disc_all_svcs_fail);
|
}
|
|
if (proc->disc_all_svcs.cb == NULL) {
|
rc = 0;
|
} else {
|
rc = proc->disc_all_svcs.cb(proc->conn_handle,
|
ble_gattc_error(status, att_handle),
|
service, proc->disc_all_svcs.cb_arg);
|
}
|
|
return rc;
|
}
|
|
static void
|
ble_gattc_disc_all_svcs_tmo(struct ble_gattc_proc *proc)
|
{
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
ble_gattc_disc_all_svcs_cb(proc, BLE_HS_ETIMEOUT, 0, 0);
|
}
|
|
/**
|
* Triggers a pending transmit for the specified discover-all-services proc.
|
*/
|
static int
|
ble_gattc_disc_all_svcs_tx(struct ble_gattc_proc *proc)
|
{
|
ble_uuid16_t uuid = BLE_UUID16_INIT(BLE_ATT_UUID_PRIMARY_SERVICE);
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
rc = ble_att_clt_tx_read_group_type(proc->conn_handle,
|
proc->disc_all_svcs.prev_handle + 1,
|
0xffff, &uuid.u);
|
if (rc != 0) {
|
return rc;
|
}
|
|
return 0;
|
}
|
|
static int
|
ble_gattc_disc_all_svcs_resume(struct ble_gattc_proc *proc)
|
{
|
int status;
|
int rc;
|
|
status = ble_gattc_disc_all_svcs_tx(proc);
|
rc = ble_gattc_process_resume_status(proc, status);
|
if (rc != 0) {
|
ble_gattc_disc_all_svcs_cb(proc, rc, 0, NULL);
|
return rc;
|
}
|
|
return 0;
|
}
|
|
/**
|
* Handles an incoming ATT error response for the specified
|
* discover-all-services proc.
|
*/
|
static void
|
ble_gattc_disc_all_svcs_err(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle)
|
{
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status == BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_FOUND)) {
|
/* Discovery is complete. */
|
status = BLE_HS_EDONE;
|
}
|
|
ble_gattc_disc_all_svcs_cb(proc, status, att_handle, NULL);
|
}
|
|
/**
|
* Handles an incoming attribute data entry from a read-group-type response for
|
* the specified discover-all-services proc.
|
*/
|
static int
|
ble_gattc_disc_all_svcs_rx_adata(struct ble_gattc_proc *proc,
|
struct ble_att_read_group_type_adata *adata)
|
{
|
struct ble_gatt_svc service;
|
int cbrc;
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
switch (adata->value_len) {
|
case 2:
|
case 16:
|
rc = ble_uuid_init_from_att_buf(&service.uuid, adata->value,
|
adata->value_len);
|
if (rc != 0) {
|
rc = BLE_HS_EBADDATA;
|
goto done;
|
}
|
break;
|
|
default:
|
rc = BLE_HS_EBADDATA;
|
goto done;
|
}
|
|
if (adata->end_group_handle <= proc->disc_all_svcs.prev_handle) {
|
/* Peer sent services out of order; terminate procedure. */
|
rc = BLE_HS_EBADDATA;
|
goto done;
|
}
|
|
proc->disc_all_svcs.prev_handle = adata->end_group_handle;
|
|
service.start_handle = adata->att_handle;
|
service.end_handle = adata->end_group_handle;
|
|
rc = 0;
|
|
done:
|
cbrc = ble_gattc_disc_all_svcs_cb(proc, rc, 0, &service);
|
if (rc != 0 || cbrc != 0) {
|
return BLE_HS_EDONE;
|
} else {
|
return 0;
|
}
|
}
|
|
/**
|
* Handles a notification that an incoming read-group-type response has been
|
* fully processed.
|
*/
|
static int
|
ble_gattc_disc_all_svcs_rx_complete(struct ble_gattc_proc *proc, int status)
|
{
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status != 0) {
|
ble_gattc_disc_all_svcs_cb(proc, status, 0, NULL);
|
return BLE_HS_EDONE;
|
}
|
|
if (proc->disc_all_svcs.prev_handle == 0xffff) {
|
/* Service discovery complete. */
|
ble_gattc_disc_all_svcs_cb(proc, BLE_HS_EDONE, 0, NULL);
|
return BLE_HS_EDONE;
|
}
|
|
/* Send follow-up request. */
|
rc = ble_gattc_disc_all_svcs_resume(proc);
|
if (rc != 0) {
|
return BLE_HS_EDONE;
|
}
|
|
return 0;
|
}
|
|
int
|
ble_gattc_disc_all_svcs(uint16_t conn_handle, ble_gatt_disc_svc_fn *cb,
|
void *cb_arg)
|
{
|
#if !MYNEWT_VAL(BLE_GATT_DISC_ALL_SVCS)
|
return BLE_HS_ENOTSUP;
|
#endif
|
|
struct ble_gattc_proc *proc;
|
int rc;
|
|
STATS_INC(ble_gattc_stats, disc_all_svcs);
|
|
proc = ble_gattc_proc_alloc();
|
if (proc == NULL) {
|
rc = BLE_HS_ENOMEM;
|
goto done;
|
}
|
|
proc->op = BLE_GATT_OP_DISC_ALL_SVCS;
|
proc->conn_handle = conn_handle;
|
proc->disc_all_svcs.prev_handle = 0x0000;
|
proc->disc_all_svcs.cb = cb;
|
proc->disc_all_svcs.cb_arg = cb_arg;
|
|
ble_gattc_log_proc_init("discover all services\n");
|
|
rc = ble_gattc_disc_all_svcs_tx(proc);
|
if (rc != 0) {
|
goto done;
|
}
|
|
done:
|
if (rc != 0) {
|
STATS_INC(ble_gattc_stats, disc_all_svcs_fail);
|
}
|
|
ble_gattc_process_status(proc, rc);
|
return rc;
|
}
|
|
/*****************************************************************************
|
* $discover service by uuid *
|
*****************************************************************************/
|
|
/**
|
* Calls a discover-service-by-uuid proc's callback with the specified
|
* parameters. If the proc has no callback, this function is a no-op.
|
*
|
* @return The return code of the callback (or 0 if there
|
* is no callback).
|
*/
|
static int
|
ble_gattc_disc_svc_uuid_cb(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle,
|
struct ble_gatt_svc *service)
|
{
|
int rc;
|
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
BLE_HS_DBG_ASSERT(service != NULL || status != 0);
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status != 0 && status != BLE_HS_EDONE) {
|
STATS_INC(ble_gattc_stats, disc_svc_uuid_fail);
|
}
|
|
if (proc->disc_svc_uuid.cb == NULL) {
|
rc = 0;
|
} else {
|
rc = proc->disc_svc_uuid.cb(proc->conn_handle,
|
ble_gattc_error(status, att_handle),
|
service, proc->disc_svc_uuid.cb_arg);
|
}
|
|
return rc;
|
}
|
|
static void
|
ble_gattc_disc_svc_uuid_tmo(struct ble_gattc_proc *proc)
|
{
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
ble_gattc_disc_svc_uuid_cb(proc, BLE_HS_ETIMEOUT, 0, 0);
|
}
|
|
/**
|
* Triggers a pending transmit for the specified discover-service-by-uuid proc.
|
*/
|
static int
|
ble_gattc_disc_svc_uuid_tx(struct ble_gattc_proc *proc)
|
{
|
uint8_t val[16];
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
ble_uuid_flat(&proc->disc_svc_uuid.service_uuid.u, val);
|
rc = ble_att_clt_tx_find_type_value(proc->conn_handle,
|
proc->disc_svc_uuid.prev_handle + 1,
|
0xffff, BLE_ATT_UUID_PRIMARY_SERVICE,
|
val,
|
ble_uuid_length(&proc->disc_svc_uuid.service_uuid.u));
|
if (rc != 0) {
|
return rc;
|
}
|
|
return 0;
|
}
|
|
static int
|
ble_gattc_disc_svc_uuid_resume(struct ble_gattc_proc *proc)
|
{
|
int status;
|
int rc;
|
|
status = ble_gattc_disc_svc_uuid_tx(proc);
|
rc = ble_gattc_process_resume_status(proc, status);
|
if (rc != 0) {
|
ble_gattc_disc_svc_uuid_cb(proc, rc, 0, NULL);
|
return rc;
|
}
|
|
return 0;
|
}
|
|
/**
|
* Handles an incoming ATT error response for the specified
|
* discover-service-by-uuid proc.
|
*/
|
static void
|
ble_gattc_disc_svc_uuid_err(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle)
|
{
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status == BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_FOUND)) {
|
/* Discovery is complete. */
|
status = BLE_HS_EDONE;
|
}
|
|
ble_gattc_disc_svc_uuid_cb(proc, status, att_handle, NULL);
|
}
|
|
/**
|
* Handles an incoming "handles info" entry from a find-type-value response for
|
* the specified discover-service-by-uuid proc.
|
*/
|
static int
|
ble_gattc_disc_svc_uuid_rx_hinfo(struct ble_gattc_proc *proc,
|
struct ble_att_find_type_value_hinfo *hinfo)
|
{
|
struct ble_gatt_svc service;
|
int cbrc;
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (hinfo->group_end_handle <= proc->disc_svc_uuid.prev_handle) {
|
/* Peer sent services out of order; terminate procedure. */
|
rc = BLE_HS_EBADDATA;
|
goto done;
|
}
|
|
proc->disc_svc_uuid.prev_handle = hinfo->group_end_handle;
|
|
service.start_handle = hinfo->attr_handle;
|
service.end_handle = hinfo->group_end_handle;
|
service.uuid = proc->disc_svc_uuid.service_uuid;
|
|
rc = 0;
|
|
done:
|
cbrc = ble_gattc_disc_svc_uuid_cb(proc, rc, 0, &service);
|
if (rc != 0 || cbrc != 0) {
|
return BLE_HS_EDONE;
|
} else {
|
return 0;
|
}
|
}
|
|
/**
|
* Handles a notification that a find-type-value response has been fully
|
* processed for the specified discover-service-by-uuid proc.
|
*/
|
static int
|
ble_gattc_disc_svc_uuid_rx_complete(struct ble_gattc_proc *proc, int status)
|
{
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status != 0) {
|
ble_gattc_disc_svc_uuid_cb(proc, status, 0, NULL);
|
return BLE_HS_EDONE;
|
}
|
|
if (proc->disc_svc_uuid.prev_handle == 0xffff) {
|
/* Service discovery complete. */
|
ble_gattc_disc_svc_uuid_cb(proc, BLE_HS_EDONE, 0, NULL);
|
return BLE_HS_EDONE;
|
}
|
|
/* Send follow-up request. */
|
rc = ble_gattc_disc_svc_uuid_resume(proc);
|
if (rc != 0) {
|
return BLE_HS_EDONE;
|
}
|
|
return 0;
|
}
|
|
int
|
ble_gattc_disc_svc_by_uuid(uint16_t conn_handle, const ble_uuid_t *uuid,
|
ble_gatt_disc_svc_fn *cb, void *cb_arg)
|
{
|
#if !MYNEWT_VAL(BLE_GATT_DISC_SVC_UUID)
|
return BLE_HS_ENOTSUP;
|
#endif
|
|
struct ble_gattc_proc *proc;
|
int rc;
|
|
STATS_INC(ble_gattc_stats, disc_svc_uuid);
|
|
proc = ble_gattc_proc_alloc();
|
if (proc == NULL) {
|
rc = BLE_HS_ENOMEM;
|
goto done;
|
}
|
|
proc->op = BLE_GATT_OP_DISC_SVC_UUID;
|
proc->conn_handle = conn_handle;
|
ble_uuid_to_any(uuid, &proc->disc_svc_uuid.service_uuid);
|
proc->disc_svc_uuid.prev_handle = 0x0000;
|
proc->disc_svc_uuid.cb = cb;
|
proc->disc_svc_uuid.cb_arg = cb_arg;
|
|
ble_gattc_log_disc_svc_uuid(proc);
|
|
rc = ble_gattc_disc_svc_uuid_tx(proc);
|
if (rc != 0) {
|
goto done;
|
}
|
|
done:
|
if (rc != 0) {
|
STATS_INC(ble_gattc_stats, disc_svc_uuid_fail);
|
}
|
|
ble_gattc_process_status(proc, rc);
|
return rc;
|
}
|
|
/*****************************************************************************
|
* $find included svcs *
|
*****************************************************************************/
|
|
/**
|
* Calls a find-included-services proc's callback with the specified
|
* parameters. If the proc has no callback, this function is a no-op.
|
*
|
* @return The return code of the callback (or 0 if there
|
* is no callback).
|
*/
|
static int
|
ble_gattc_find_inc_svcs_cb(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle,
|
struct ble_gatt_svc *service)
|
{
|
int rc;
|
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
BLE_HS_DBG_ASSERT(service != NULL || status != 0);
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status != 0 && status != BLE_HS_EDONE) {
|
STATS_INC(ble_gattc_stats, find_inc_svcs_fail);
|
}
|
|
if (proc->find_inc_svcs.cb == NULL) {
|
rc = 0;
|
} else {
|
rc = proc->find_inc_svcs.cb(proc->conn_handle,
|
ble_gattc_error(status, att_handle),
|
service, proc->find_inc_svcs.cb_arg);
|
}
|
|
return rc;
|
}
|
|
static void
|
ble_gattc_find_inc_svcs_tmo(struct ble_gattc_proc *proc)
|
{
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
ble_gattc_find_inc_svcs_cb(proc, BLE_HS_ETIMEOUT, 0, 0);
|
}
|
|
/**
|
* Triggers a pending transmit for the specified find-included-services proc.
|
*/
|
static int
|
ble_gattc_find_inc_svcs_tx(struct ble_gattc_proc *proc)
|
{
|
ble_uuid16_t uuid = BLE_UUID16_INIT(BLE_ATT_UUID_INCLUDE);
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (proc->find_inc_svcs.cur_start == 0) {
|
/* Find the next included service. */
|
rc = ble_att_clt_tx_read_type(proc->conn_handle,
|
proc->find_inc_svcs.prev_handle + 1,
|
proc->find_inc_svcs.end_handle, &uuid.u);
|
if (rc != 0) {
|
return rc;
|
}
|
} else {
|
/* Read the UUID of the previously found service. */
|
rc = ble_att_clt_tx_read(proc->conn_handle,
|
proc->find_inc_svcs.cur_start);
|
if (rc != 0) {
|
return rc;
|
}
|
}
|
|
return 0;
|
}
|
|
static int
|
ble_gattc_find_inc_svcs_resume(struct ble_gattc_proc *proc)
|
{
|
int status;
|
int rc;
|
|
status = ble_gattc_find_inc_svcs_tx(proc);
|
rc = ble_gattc_process_resume_status(proc, status);
|
if (rc != 0) {
|
ble_gattc_find_inc_svcs_cb(proc, rc, 0, NULL);
|
return rc;
|
}
|
|
return 0;
|
}
|
|
/**
|
* Handles an incoming ATT error response for the specified
|
* find-included-services proc.
|
*/
|
static void
|
ble_gattc_find_inc_svcs_err(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle)
|
{
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (proc->find_inc_svcs.cur_start == 0 &&
|
status == BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_FOUND)) {
|
|
/* Discovery is complete. */
|
status = BLE_HS_EDONE;
|
}
|
|
ble_gattc_find_inc_svcs_cb(proc, status, att_handle, NULL);
|
}
|
|
/**
|
* Handles an incoming read-response for the specified find-included-services
|
* proc.
|
*/
|
static int
|
ble_gattc_find_inc_svcs_rx_read_rsp(struct ble_gattc_proc *proc, int status,
|
struct os_mbuf **om)
|
{
|
struct ble_gatt_svc service;
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
rc = ble_uuid_init_from_att_mbuf(&service.uuid, *om, 0, 16);
|
os_mbuf_free_chain(*om);
|
*om = NULL;
|
|
if (rc != 0) {
|
/* Invalid UUID. */
|
rc = BLE_HS_EBADDATA;
|
goto err;
|
}
|
|
if (proc->find_inc_svcs.cur_start == 0) {
|
/* Unexpected read response; terminate procedure. */
|
rc = BLE_HS_EBADDATA;
|
goto err;
|
}
|
|
if (status != 0) {
|
rc = status;
|
goto err;
|
}
|
|
/* Report discovered service to application. */
|
service.start_handle = proc->find_inc_svcs.cur_start;
|
service.end_handle = proc->find_inc_svcs.cur_end;
|
rc = ble_gattc_find_inc_svcs_cb(proc, 0, 0, &service);
|
if (rc != 0) {
|
/* Application has indicated that the procedure should be aborted. */
|
return BLE_HS_EDONE;
|
}
|
|
/* Proceed to the next service. */
|
proc->find_inc_svcs.cur_start = 0;
|
proc->find_inc_svcs.cur_end = 0;
|
rc = ble_gattc_find_inc_svcs_resume(proc);
|
if (rc != 0) {
|
goto err;
|
}
|
|
return 0;
|
|
err:
|
ble_gattc_find_inc_svcs_cb(proc, rc, 0, NULL);
|
return BLE_HS_EDONE;
|
}
|
|
/**
|
* Handles an incoming "attribute data" entry from a read-by-type response for
|
* the specified find-included-services proc.
|
*/
|
static int
|
ble_gattc_find_inc_svcs_rx_adata(struct ble_gattc_proc *proc,
|
struct ble_att_read_type_adata *adata)
|
{
|
struct ble_gatt_svc service;
|
int call_cb;
|
int cbrc;
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (proc->find_inc_svcs.cur_start != 0) {
|
/* We only read one 128-bit UUID service at a time. Ignore the
|
* additional services in the response.
|
*/
|
return 0;
|
}
|
|
call_cb = 1;
|
|
if (adata->att_handle <= proc->find_inc_svcs.prev_handle) {
|
/* Peer sent services out of order; terminate procedure. */
|
rc = BLE_HS_EBADDATA;
|
goto done;
|
}
|
|
proc->find_inc_svcs.prev_handle = adata->att_handle;
|
|
rc = 0;
|
|
switch (adata->value_len) {
|
case BLE_GATTS_INC_SVC_LEN_NO_UUID:
|
proc->find_inc_svcs.cur_start = get_le16(adata->value + 0);
|
proc->find_inc_svcs.cur_end = get_le16(adata->value + 2);
|
call_cb = 0;
|
break;
|
|
case BLE_GATTS_INC_SVC_LEN_UUID:
|
service.start_handle = get_le16(adata->value + 0);
|
service.end_handle = get_le16(adata->value + 2);
|
rc = ble_uuid_init_from_att_buf(&service.uuid, adata->value + 4, 2);
|
if (rc != 0) {
|
rc = BLE_HS_EBADDATA;
|
}
|
break;
|
|
default:
|
rc = BLE_HS_EBADDATA;
|
break;
|
}
|
|
done:
|
if (call_cb) {
|
cbrc = ble_gattc_find_inc_svcs_cb(proc, 0, 0, &service);
|
if (rc != 0) {
|
rc = cbrc;
|
}
|
} else {
|
cbrc = 0;
|
}
|
|
if (rc != 0 || cbrc != 0) {
|
return BLE_HS_EDONE;
|
} else {
|
return 0;
|
}
|
}
|
|
/**
|
* Handles a notification that a read-by-type response has been fully
|
* processed for the specified find-included-services proc.
|
*/
|
static int
|
ble_gattc_find_inc_svcs_rx_complete(struct ble_gattc_proc *proc, int status)
|
{
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status != 0) {
|
ble_gattc_find_inc_svcs_cb(proc, status, 0, NULL);
|
return BLE_HS_EDONE;
|
}
|
|
if (proc->find_inc_svcs.prev_handle == 0xffff) {
|
/* Procedure complete. */
|
ble_gattc_find_inc_svcs_cb(proc, BLE_HS_EDONE, 0, NULL);
|
return BLE_HS_EDONE;
|
}
|
|
/* Send follow-up request. */
|
rc = ble_gattc_find_inc_svcs_resume(proc);
|
if (rc != 0) {
|
return BLE_HS_EDONE;
|
}
|
return 0;
|
}
|
|
int
|
ble_gattc_find_inc_svcs(uint16_t conn_handle, uint16_t start_handle,
|
uint16_t end_handle,
|
ble_gatt_disc_svc_fn *cb, void *cb_arg)
|
{
|
#if !MYNEWT_VAL(BLE_GATT_FIND_INC_SVCS)
|
return BLE_HS_ENOTSUP;
|
#endif
|
|
struct ble_gattc_proc *proc;
|
int rc;
|
|
STATS_INC(ble_gattc_stats, find_inc_svcs);
|
|
proc = ble_gattc_proc_alloc();
|
if (proc == NULL) {
|
rc = BLE_HS_ENOMEM;
|
goto done;
|
}
|
|
proc->op = BLE_GATT_OP_FIND_INC_SVCS;
|
proc->conn_handle = conn_handle;
|
proc->find_inc_svcs.prev_handle = start_handle - 1;
|
proc->find_inc_svcs.end_handle = end_handle;
|
proc->find_inc_svcs.cb = cb;
|
proc->find_inc_svcs.cb_arg = cb_arg;
|
|
ble_gattc_log_find_inc_svcs(proc);
|
|
rc = ble_gattc_find_inc_svcs_tx(proc);
|
if (rc != 0) {
|
goto done;
|
}
|
|
done:
|
if (rc != 0) {
|
STATS_INC(ble_gattc_stats, find_inc_svcs_fail);
|
}
|
|
ble_gattc_process_status(proc, rc);
|
return rc;
|
}
|
|
/*****************************************************************************
|
* $discover all characteristics *
|
*****************************************************************************/
|
|
/**
|
* Calls a discover-all-characteristics proc's callback with the specified
|
* parameters. If the proc has no callback, this function is a no-op.
|
*
|
* @return The return code of the callback (or 0 if there
|
* is no callback).
|
*/
|
static int
|
ble_gattc_disc_all_chrs_cb(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle, struct ble_gatt_chr *chr)
|
{
|
int rc;
|
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
BLE_HS_DBG_ASSERT(chr != NULL || status != 0);
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status != 0 && status != BLE_HS_EDONE) {
|
STATS_INC(ble_gattc_stats, disc_all_chrs_fail);
|
}
|
|
if (proc->disc_all_chrs.cb == NULL) {
|
rc = 0;
|
} else {
|
rc = proc->disc_all_chrs.cb(proc->conn_handle,
|
ble_gattc_error(status, att_handle), chr,
|
proc->disc_all_chrs.cb_arg);
|
}
|
|
return rc;
|
}
|
|
static void
|
ble_gattc_disc_all_chrs_tmo(struct ble_gattc_proc *proc)
|
{
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
ble_gattc_disc_all_chrs_cb(proc, BLE_HS_ETIMEOUT, 0, NULL);
|
}
|
|
/**
|
* Triggers a pending transmit for the specified discover-all-characteristics
|
* proc.
|
*/
|
static int
|
ble_gattc_disc_all_chrs_tx(struct ble_gattc_proc *proc)
|
{
|
ble_uuid16_t uuid = BLE_UUID16_INIT(BLE_ATT_UUID_CHARACTERISTIC);
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
rc = ble_att_clt_tx_read_type(proc->conn_handle,
|
proc->disc_all_chrs.prev_handle + 1,
|
proc->disc_all_chrs.end_handle, &uuid.u);
|
if (rc != 0) {
|
return rc;
|
}
|
|
return 0;
|
}
|
|
static int
|
ble_gattc_disc_all_chrs_resume(struct ble_gattc_proc *proc)
|
{
|
int status;
|
int rc;
|
|
status = ble_gattc_disc_all_chrs_tx(proc);
|
rc = ble_gattc_process_resume_status(proc, status);
|
if (rc != 0) {
|
ble_gattc_disc_all_chrs_cb(proc, rc, 0, NULL);
|
return rc;
|
}
|
|
return 0;
|
}
|
|
/**
|
* Handles an incoming ATT error response for the specified
|
* discover-all-characteristics proc.
|
*/
|
static void
|
ble_gattc_disc_all_chrs_err(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle)
|
{
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status == BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_FOUND)) {
|
/* Discovery is complete. */
|
status = BLE_HS_EDONE;
|
}
|
|
ble_gattc_disc_all_chrs_cb(proc, status, att_handle, NULL);
|
}
|
|
/**
|
* Handles an incoming "attribute data" entry from a read-by-type response for
|
* the specified discover-all-characteristics proc.
|
*/
|
static int
|
ble_gattc_disc_all_chrs_rx_adata(struct ble_gattc_proc *proc,
|
struct ble_att_read_type_adata *adata)
|
{
|
struct ble_gatt_chr chr;
|
int cbrc;
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
memset(&chr, 0, sizeof chr);
|
chr.def_handle = adata->att_handle;
|
|
switch (adata->value_len) {
|
case BLE_GATT_CHR_DECL_SZ_16:
|
case BLE_GATT_CHR_DECL_SZ_128:
|
rc = ble_uuid_init_from_att_buf(&chr.uuid, adata->value + 3,
|
adata->value_len - 3);
|
if (rc != 0) {
|
rc = BLE_HS_EBADDATA;
|
goto done;
|
}
|
break;
|
|
default:
|
rc = BLE_HS_EBADDATA;
|
goto done;
|
}
|
|
chr.properties = adata->value[0];
|
chr.val_handle = get_le16(adata->value + 1);
|
|
if (adata->att_handle <= proc->disc_all_chrs.prev_handle) {
|
/* Peer sent characteristics out of order; terminate procedure. */
|
rc = BLE_HS_EBADDATA;
|
goto done;
|
}
|
proc->disc_all_chrs.prev_handle = adata->att_handle;
|
|
rc = 0;
|
|
done:
|
cbrc = ble_gattc_disc_all_chrs_cb(proc, rc, 0, &chr);
|
if (rc != 0 || cbrc != 0) {
|
return BLE_HS_EDONE;
|
} else {
|
return 0;
|
}
|
}
|
|
/**
|
* Handles a notification that a read-by-type response has been fully
|
* processed for the specified discover-all-characteristics proc.
|
*/
|
static int
|
ble_gattc_disc_all_chrs_rx_complete(struct ble_gattc_proc *proc, int status)
|
{
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status != 0) {
|
ble_gattc_disc_all_chrs_cb(proc, status, 0, NULL);
|
return BLE_HS_EDONE;
|
}
|
|
if (proc->disc_all_chrs.prev_handle == proc->disc_all_chrs.end_handle) {
|
/* Characteristic discovery complete. */
|
ble_gattc_disc_all_chrs_cb(proc, BLE_HS_EDONE, 0, NULL);
|
return BLE_HS_EDONE;
|
}
|
|
/* Send follow-up request. */
|
rc = ble_gattc_disc_all_chrs_resume(proc);
|
if (rc != 0) {
|
return BLE_HS_EDONE;
|
}
|
return 0;
|
}
|
|
int
|
ble_gattc_disc_all_chrs(uint16_t conn_handle, uint16_t start_handle,
|
uint16_t end_handle, ble_gatt_chr_fn *cb,
|
void *cb_arg)
|
{
|
#if !MYNEWT_VAL(BLE_GATT_DISC_ALL_CHRS)
|
return BLE_HS_ENOTSUP;
|
#endif
|
|
struct ble_gattc_proc *proc;
|
int rc;
|
|
STATS_INC(ble_gattc_stats, disc_all_chrs);
|
|
proc = ble_gattc_proc_alloc();
|
if (proc == NULL) {
|
rc = BLE_HS_ENOMEM;
|
goto done;
|
}
|
|
proc->op = BLE_GATT_OP_DISC_ALL_CHRS;
|
proc->conn_handle = conn_handle;
|
proc->disc_all_chrs.prev_handle = start_handle - 1;
|
proc->disc_all_chrs.end_handle = end_handle;
|
proc->disc_all_chrs.cb = cb;
|
proc->disc_all_chrs.cb_arg = cb_arg;
|
|
ble_gattc_log_disc_all_chrs(proc);
|
|
rc = ble_gattc_disc_all_chrs_tx(proc);
|
if (rc != 0) {
|
goto done;
|
}
|
|
done:
|
if (rc != 0) {
|
STATS_INC(ble_gattc_stats, disc_all_chrs_fail);
|
}
|
|
ble_gattc_process_status(proc, rc);
|
return rc;
|
}
|
|
/*****************************************************************************
|
* $discover characteristic by uuid *
|
*****************************************************************************/
|
|
/**
|
* Calls a discover-characteristic-by-uuid proc's callback with the specified
|
* parameters. If the proc has no callback, this function is a no-op.
|
*
|
* @return The return code of the callback (or 0 if there
|
* is no callback).
|
*/
|
static int
|
ble_gattc_disc_chr_uuid_cb(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle, struct ble_gatt_chr *chr)
|
{
|
int rc;
|
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
BLE_HS_DBG_ASSERT(chr != NULL || status != 0);
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status != 0 && status != BLE_HS_EDONE) {
|
STATS_INC(ble_gattc_stats, disc_chrs_uuid_fail);
|
}
|
|
if (proc->disc_chr_uuid.cb == NULL) {
|
rc = 0;
|
} else {
|
rc = proc->disc_chr_uuid.cb(proc->conn_handle,
|
ble_gattc_error(status, att_handle), chr,
|
proc->disc_chr_uuid.cb_arg);
|
}
|
|
return rc;
|
}
|
|
static void
|
ble_gattc_disc_chr_uuid_tmo(struct ble_gattc_proc *proc)
|
{
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
ble_gattc_disc_chr_uuid_cb(proc, BLE_HS_ETIMEOUT, 0, NULL);
|
}
|
|
/**
|
* Triggers a pending transmit for the specified
|
* discover-characteristic-by-uuid proc.
|
*/
|
static int
|
ble_gattc_disc_chr_uuid_tx(struct ble_gattc_proc *proc)
|
{
|
ble_uuid16_t uuid = BLE_UUID16_INIT(BLE_ATT_UUID_CHARACTERISTIC);
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
rc = ble_att_clt_tx_read_type(proc->conn_handle,
|
proc->disc_chr_uuid.prev_handle + 1,
|
proc->disc_chr_uuid.end_handle, &uuid.u);
|
if (rc != 0) {
|
return rc;
|
}
|
|
return 0;
|
}
|
|
static int
|
ble_gattc_disc_chr_uuid_resume(struct ble_gattc_proc *proc)
|
{
|
int status;
|
int rc;
|
|
status = ble_gattc_disc_chr_uuid_tx(proc);
|
rc = ble_gattc_process_resume_status(proc, status);
|
if (rc != 0) {
|
ble_gattc_disc_chr_uuid_cb(proc, rc, 0, NULL);
|
return rc;
|
}
|
|
return 0;
|
}
|
|
/**
|
* Handles an incoming ATT error response for the specified
|
* discover-characteristic-by-uuid proc.
|
*/
|
static void
|
ble_gattc_disc_chr_uuid_err(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle)
|
{
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status == BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_FOUND)) {
|
/* Discovery is complete. */
|
status = BLE_HS_EDONE;
|
}
|
|
ble_gattc_disc_chr_uuid_cb(proc, status, att_handle, NULL);
|
}
|
|
/**
|
* Handles an incoming "attribute data" entry from a read-by-type response for
|
* the specified discover-characteristics-by-uuid proc.
|
*/
|
static int
|
ble_gattc_disc_chr_uuid_rx_adata(struct ble_gattc_proc *proc,
|
struct ble_att_read_type_adata *adata)
|
{
|
struct ble_gatt_chr chr;
|
int cbrc;
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
memset(&chr, 0, sizeof chr);
|
chr.def_handle = adata->att_handle;
|
|
switch (adata->value_len) {
|
case BLE_GATT_CHR_DECL_SZ_16:
|
case BLE_GATT_CHR_DECL_SZ_128:
|
rc = ble_uuid_init_from_att_buf(&chr.uuid, adata->value + 3,
|
adata->value_len - 3);
|
if (rc != 0) {
|
rc = BLE_HS_EBADDATA;
|
goto done;
|
}
|
break;
|
|
default:
|
rc = BLE_HS_EBADDATA;
|
goto done;
|
}
|
|
chr.properties = adata->value[0];
|
chr.val_handle = get_le16(adata->value + 1);
|
|
if (adata->att_handle <= proc->disc_chr_uuid.prev_handle) {
|
/* Peer sent characteristics out of order; terminate procedure. */
|
rc = BLE_HS_EBADDATA;
|
goto done;
|
}
|
|
proc->disc_chr_uuid.prev_handle = adata->att_handle;
|
|
rc = 0;
|
|
done:
|
if (rc != 0) {
|
/* Failure. */
|
cbrc = ble_gattc_disc_chr_uuid_cb(proc, rc, 0, NULL);
|
} else if (ble_uuid_cmp(&chr.uuid.u, &proc->disc_chr_uuid.chr_uuid.u) == 0) {
|
/* Requested characteristic discovered. */
|
cbrc = ble_gattc_disc_chr_uuid_cb(proc, 0, 0, &chr);
|
} else {
|
/* Uninteresting characteristic; ignore. */
|
cbrc = 0;
|
}
|
|
if (rc != 0 || cbrc != 0) {
|
return BLE_HS_EDONE;
|
} else {
|
return 0;
|
}
|
}
|
|
/**
|
* Handles a notification that a read-by-type response has been fully
|
* processed for the specified discover-characteristics-by-uuid proc.
|
*/
|
static int
|
ble_gattc_disc_chr_uuid_rx_complete(struct ble_gattc_proc *proc, int status)
|
{
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status != 0) {
|
ble_gattc_disc_chr_uuid_cb(proc, status, 0, NULL);
|
return BLE_HS_EDONE;
|
}
|
|
if (proc->disc_chr_uuid.prev_handle == proc->disc_chr_uuid.end_handle) {
|
/* Characteristic discovery complete. */
|
ble_gattc_disc_chr_uuid_cb(proc, BLE_HS_EDONE, 0, NULL);
|
return BLE_HS_EDONE;
|
}
|
|
/* Send follow-up request. */
|
rc = ble_gattc_disc_chr_uuid_resume(proc);
|
if (rc != 0) {
|
return BLE_HS_EDONE;
|
}
|
return 0;
|
}
|
|
int
|
ble_gattc_disc_chrs_by_uuid(uint16_t conn_handle, uint16_t start_handle,
|
uint16_t end_handle, const ble_uuid_t *uuid,
|
ble_gatt_chr_fn *cb, void *cb_arg)
|
{
|
#if !MYNEWT_VAL(BLE_GATT_DISC_CHR_UUID)
|
return BLE_HS_ENOTSUP;
|
#endif
|
|
struct ble_gattc_proc *proc;
|
int rc;
|
|
STATS_INC(ble_gattc_stats, disc_chrs_uuid);
|
|
proc = ble_gattc_proc_alloc();
|
if (proc == NULL) {
|
rc = BLE_HS_ENOMEM;
|
goto done;
|
}
|
|
proc->op = BLE_GATT_OP_DISC_CHR_UUID;
|
proc->conn_handle = conn_handle;
|
ble_uuid_to_any(uuid, &proc->disc_chr_uuid.chr_uuid);
|
proc->disc_chr_uuid.prev_handle = start_handle - 1;
|
proc->disc_chr_uuid.end_handle = end_handle;
|
proc->disc_chr_uuid.cb = cb;
|
proc->disc_chr_uuid.cb_arg = cb_arg;
|
|
ble_gattc_log_disc_chr_uuid(proc);
|
|
rc = ble_gattc_disc_chr_uuid_tx(proc);
|
if (rc != 0) {
|
goto done;
|
}
|
|
done:
|
if (rc != 0) {
|
STATS_INC(ble_gattc_stats, disc_chrs_uuid_fail);
|
}
|
|
ble_gattc_process_status(proc, rc);
|
return rc;
|
}
|
|
/*****************************************************************************
|
* $discover all characteristic descriptors *
|
*****************************************************************************/
|
|
/**
|
* Calls a discover-all-descriptors proc's callback with the specified
|
* parameters. If the proc has no callback, this function is a no-op.
|
*
|
* @return The return code of the callback (or 0 if there
|
* is no callback).
|
*/
|
static int
|
ble_gattc_disc_all_dscs_cb(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle, struct ble_gatt_dsc *dsc)
|
{
|
int rc;
|
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
BLE_HS_DBG_ASSERT(dsc != NULL || status != 0);
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status != 0 && status != BLE_HS_EDONE) {
|
STATS_INC(ble_gattc_stats, disc_all_dscs_fail);
|
}
|
|
if (proc->disc_all_dscs.cb == NULL) {
|
rc = 0;
|
} else {
|
rc = proc->disc_all_dscs.cb(proc->conn_handle,
|
ble_gattc_error(status, att_handle),
|
proc->disc_all_dscs.chr_val_handle,
|
dsc, proc->disc_all_dscs.cb_arg);
|
}
|
|
return rc;
|
}
|
|
static void
|
ble_gattc_disc_all_dscs_tmo(struct ble_gattc_proc *proc)
|
{
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
ble_gattc_disc_all_dscs_cb(proc, BLE_HS_ETIMEOUT, 0, NULL);
|
}
|
|
/**
|
* Triggers a pending transmit for the specified discover-all-descriptors proc.
|
*/
|
static int
|
ble_gattc_disc_all_dscs_tx(struct ble_gattc_proc *proc)
|
{
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
rc = ble_att_clt_tx_find_info(proc->conn_handle,
|
proc->disc_all_dscs.prev_handle + 1,
|
proc->disc_all_dscs.end_handle);
|
if (rc != 0) {
|
return rc;
|
}
|
|
return 0;
|
}
|
|
static int
|
ble_gattc_disc_all_dscs_resume(struct ble_gattc_proc *proc)
|
{
|
int status;
|
int rc;
|
|
status = ble_gattc_disc_all_dscs_tx(proc);
|
rc = ble_gattc_process_resume_status(proc, status);
|
if (rc != 0) {
|
ble_gattc_disc_all_dscs_cb(proc, rc, 0, NULL);
|
return rc;
|
}
|
|
return 0;
|
}
|
|
/**
|
* Handles an incoming ATT error response for the specified
|
* discover-all-descriptors proc.
|
*/
|
static void
|
ble_gattc_disc_all_dscs_err(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle)
|
{
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status == BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_FOUND)) {
|
/* Discovery is complete. */
|
status = BLE_HS_EDONE;
|
}
|
|
ble_gattc_disc_all_dscs_cb(proc, status, att_handle, NULL);
|
}
|
|
/**
|
* Handles an incoming "information data" entry from a find-information
|
* response for the specified discover-all-descriptors proc.
|
*/
|
static int
|
ble_gattc_disc_all_dscs_rx_idata(struct ble_gattc_proc *proc,
|
struct ble_att_find_info_idata *idata)
|
{
|
struct ble_gatt_dsc dsc;
|
int cbrc;
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (idata->attr_handle <= proc->disc_all_dscs.prev_handle) {
|
/* Peer sent descriptors out of order; terminate procedure. */
|
rc = BLE_HS_EBADDATA;
|
goto done;
|
}
|
proc->disc_all_dscs.prev_handle = idata->attr_handle;
|
|
rc = 0;
|
|
done:
|
dsc.handle = idata->attr_handle;
|
dsc.uuid = idata->uuid;
|
|
cbrc = ble_gattc_disc_all_dscs_cb(proc, rc, 0, &dsc);
|
if (rc != 0 || cbrc != 0) {
|
return BLE_HS_EDONE;
|
} else {
|
return 0;
|
}
|
}
|
|
/**
|
* Handles a notification that a find-information response has been fully
|
* processed for the specified discover-all-descriptors proc.
|
*/
|
static int
|
ble_gattc_disc_all_dscs_rx_complete(struct ble_gattc_proc *proc, int status)
|
{
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status != 0) {
|
ble_gattc_disc_all_dscs_cb(proc, status, 0, NULL);
|
return BLE_HS_EDONE;
|
}
|
|
if (proc->disc_all_dscs.prev_handle == proc->disc_all_dscs.end_handle) {
|
/* All descriptors discovered. */
|
ble_gattc_disc_all_dscs_cb(proc, BLE_HS_EDONE, 0, NULL);
|
return BLE_HS_EDONE;
|
}
|
|
/* Send follow-up request. */
|
rc = ble_gattc_disc_all_dscs_resume(proc);
|
if (rc != 0) {
|
return BLE_HS_EDONE;
|
}
|
|
return 0;
|
}
|
|
int
|
ble_gattc_disc_all_dscs(uint16_t conn_handle, uint16_t start_handle,
|
uint16_t end_handle,
|
ble_gatt_dsc_fn *cb, void *cb_arg)
|
{
|
#if !MYNEWT_VAL(BLE_GATT_DISC_ALL_DSCS)
|
return BLE_HS_ENOTSUP;
|
#endif
|
|
struct ble_gattc_proc *proc;
|
int rc;
|
|
STATS_INC(ble_gattc_stats, disc_all_dscs);
|
|
proc = ble_gattc_proc_alloc();
|
if (proc == NULL) {
|
rc = BLE_HS_ENOMEM;
|
goto done;
|
}
|
|
proc->op = BLE_GATT_OP_DISC_ALL_DSCS;
|
proc->conn_handle = conn_handle;
|
proc->disc_all_dscs.chr_val_handle = start_handle;
|
proc->disc_all_dscs.prev_handle = start_handle;
|
proc->disc_all_dscs.end_handle = end_handle;
|
proc->disc_all_dscs.cb = cb;
|
proc->disc_all_dscs.cb_arg = cb_arg;
|
|
ble_gattc_log_disc_all_dscs(proc);
|
|
rc = ble_gattc_disc_all_dscs_tx(proc);
|
if (rc != 0) {
|
goto done;
|
}
|
|
done:
|
if (rc != 0) {
|
STATS_INC(ble_gattc_stats, disc_all_dscs_fail);
|
}
|
|
ble_gattc_process_status(proc, rc);
|
return rc;
|
}
|
|
/*****************************************************************************
|
* $read *
|
*****************************************************************************/
|
|
/**
|
* Calls a read-characteristic proc's callback with the specified parameters.
|
* If the proc has no callback, this function is a no-op.
|
*
|
* @return The return code of the callback (or 0 if there
|
* is no callback).
|
*/
|
static int
|
ble_gattc_read_cb(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle, struct ble_gatt_attr *attr)
|
{
|
int rc;
|
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
BLE_HS_DBG_ASSERT(attr != NULL || status != 0);
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status != 0 && status != BLE_HS_EDONE) {
|
STATS_INC(ble_gattc_stats, read_fail);
|
}
|
|
if (proc->read.cb == NULL) {
|
rc = 0;
|
} else {
|
rc = proc->read.cb(proc->conn_handle,
|
ble_gattc_error(status, att_handle), attr,
|
proc->read.cb_arg);
|
}
|
|
return rc;
|
}
|
|
static void
|
ble_gattc_read_tmo(struct ble_gattc_proc *proc)
|
{
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
ble_gattc_read_cb(proc, BLE_HS_ETIMEOUT, 0, NULL);
|
}
|
|
/**
|
* Handles an incoming ATT error response for the specified
|
* read-characteristic-value proc.
|
*/
|
static void
|
ble_gattc_read_err(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle)
|
{
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
ble_gattc_read_cb(proc, status, att_handle, NULL);
|
}
|
|
/**
|
* Handles an incoming read-response for the specified
|
* read-characteristic-value proc.
|
*/
|
static int
|
ble_gattc_read_rx_read_rsp(struct ble_gattc_proc *proc, int status,
|
struct os_mbuf **om)
|
{
|
struct ble_gatt_attr attr;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
attr.handle = proc->read.handle;
|
attr.offset = 0;
|
attr.om = *om;
|
|
ble_gattc_read_cb(proc, status, 0, &attr);
|
|
/* Indicate to the caller whether the application consumed the mbuf. */
|
*om = attr.om;
|
|
/* The read operation only has a single request / response exchange. */
|
return BLE_HS_EDONE;
|
}
|
|
static int
|
ble_gattc_read_tx(struct ble_gattc_proc *proc)
|
{
|
int rc;
|
|
rc = ble_att_clt_tx_read(proc->conn_handle, proc->read.handle);
|
if (rc != 0) {
|
return rc;
|
}
|
|
return 0;
|
}
|
|
int
|
ble_gattc_read(uint16_t conn_handle, uint16_t attr_handle,
|
ble_gatt_attr_fn *cb, void *cb_arg)
|
{
|
#if !MYNEWT_VAL(BLE_GATT_READ)
|
return BLE_HS_ENOTSUP;
|
#endif
|
|
struct ble_gattc_proc *proc;
|
int rc;
|
|
STATS_INC(ble_gattc_stats, read);
|
|
proc = ble_gattc_proc_alloc();
|
if (proc == NULL) {
|
rc = BLE_HS_ENOMEM;
|
goto done;
|
}
|
|
proc->op = BLE_GATT_OP_READ;
|
proc->conn_handle = conn_handle;
|
proc->read.handle = attr_handle;
|
proc->read.cb = cb;
|
proc->read.cb_arg = cb_arg;
|
|
ble_gattc_log_read(attr_handle);
|
rc = ble_gattc_read_tx(proc);
|
if (rc != 0) {
|
goto done;
|
}
|
|
done:
|
if (rc != 0) {
|
STATS_INC(ble_gattc_stats, read_fail);
|
}
|
|
ble_gattc_process_status(proc, rc);
|
return rc;
|
}
|
|
/*****************************************************************************
|
* $read by uuid *
|
*****************************************************************************/
|
|
/**
|
* Calls a read-using-characteristic-uuid proc's callback with the specified
|
* parameters. If the proc has no callback, this function is a no-op.
|
*
|
* @return The return code of the callback (or 0 if there
|
* is no callback).
|
*/
|
static int
|
ble_gattc_read_uuid_cb(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle, struct ble_gatt_attr *attr)
|
{
|
int rc;
|
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
BLE_HS_DBG_ASSERT(attr != NULL || status != 0);
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status != 0 && status != BLE_HS_EDONE) {
|
STATS_INC(ble_gattc_stats, read_uuid_fail);
|
}
|
|
if (proc->read_uuid.cb == NULL) {
|
rc = 0;
|
} else {
|
rc = proc->read_uuid.cb(proc->conn_handle,
|
ble_gattc_error(status, att_handle), attr,
|
proc->read_uuid.cb_arg);
|
}
|
|
return rc;
|
}
|
|
static void
|
ble_gattc_read_uuid_tmo(struct ble_gattc_proc *proc)
|
{
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
ble_gattc_read_uuid_cb(proc, BLE_HS_ETIMEOUT, 0, NULL);
|
}
|
|
/**
|
* Handles an incoming ATT error response for the specified
|
* read-using-characteristic-uuid proc.
|
*/
|
static void
|
ble_gattc_read_uuid_err(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle)
|
{
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
ble_gattc_read_uuid_cb(proc, status, att_handle, NULL);
|
}
|
|
/**
|
* Handles an incoming "attribute data" entry from a read-by-type response for
|
* the specified read-using-characteristic-uuid proc.
|
*/
|
static int
|
ble_gattc_read_uuid_rx_adata(struct ble_gattc_proc *proc,
|
struct ble_att_read_type_adata *adata)
|
{
|
struct ble_gatt_attr attr;
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
attr.handle = adata->att_handle;
|
attr.offset = 0;
|
attr.om = ble_hs_mbuf_from_flat(adata->value, adata->value_len);
|
if (attr.om == NULL) {
|
rc = BLE_HS_ENOMEM;
|
} else {
|
rc = 0;
|
}
|
rc = ble_gattc_read_uuid_cb(proc, rc, 0, &attr);
|
|
/* Free the attribute mbuf if the application has not consumed it. */
|
os_mbuf_free_chain(attr.om);
|
|
if (rc != 0) {
|
return BLE_HS_EDONE;
|
}
|
|
return 0;
|
}
|
|
/**
|
* Handles a notification that a read-by-type response has been fully
|
* processed for the specified read-using-characteristic-uuid proc.
|
*/
|
static int
|
ble_gattc_read_uuid_rx_complete(struct ble_gattc_proc *proc, int status)
|
{
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status != 0) {
|
ble_gattc_read_uuid_cb(proc, status, 0, NULL);
|
return BLE_HS_EDONE;
|
}
|
|
/* XXX: We may need to send a follow-up request to address the possibility
|
* of multiple characteristics with identical UUIDs.
|
*/
|
ble_gattc_read_uuid_cb(proc, BLE_HS_EDONE, 0, NULL);
|
return BLE_HS_EDONE;
|
}
|
|
static int
|
ble_gattc_read_uuid_tx(struct ble_gattc_proc *proc)
|
{
|
return ble_att_clt_tx_read_type(proc->conn_handle,
|
proc->read_uuid.start_handle,
|
proc->read_uuid.end_handle,
|
&proc->read_uuid.chr_uuid.u);
|
}
|
|
int
|
ble_gattc_read_by_uuid(uint16_t conn_handle, uint16_t start_handle,
|
uint16_t end_handle, const ble_uuid_t *uuid,
|
ble_gatt_attr_fn *cb, void *cb_arg)
|
{
|
#if !MYNEWT_VAL(BLE_GATT_READ_UUID)
|
return BLE_HS_ENOTSUP;
|
#endif
|
|
struct ble_gattc_proc *proc;
|
int rc;
|
|
STATS_INC(ble_gattc_stats, read_uuid);
|
|
proc = ble_gattc_proc_alloc();
|
if (proc == NULL) {
|
rc = BLE_HS_ENOMEM;
|
goto done;
|
}
|
|
proc->op = BLE_GATT_OP_READ_UUID;
|
proc->conn_handle = conn_handle;
|
ble_uuid_to_any(uuid, &proc->read_uuid.chr_uuid);
|
proc->read_uuid.start_handle = start_handle;
|
proc->read_uuid.end_handle = end_handle;
|
proc->read_uuid.cb = cb;
|
proc->read_uuid.cb_arg = cb_arg;
|
|
ble_gattc_log_read_uuid(start_handle, end_handle, uuid);
|
rc = ble_gattc_read_uuid_tx(proc);
|
if (rc != 0) {
|
goto done;
|
}
|
|
done:
|
if (rc != 0) {
|
STATS_INC(ble_gattc_stats, read_uuid_fail);
|
}
|
|
ble_gattc_process_status(proc, rc);
|
return rc;
|
}
|
|
/*****************************************************************************
|
* $read long *
|
*****************************************************************************/
|
|
/**
|
* Calls a read-long-characteristic proc's callback with the specified
|
* parameters. If the proc has no callback, this function is a no-op.
|
*
|
* @return The return code of the callback (or 0 if there
|
* is no callback).
|
*/
|
static int
|
ble_gattc_read_long_cb(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle, struct ble_gatt_attr *attr)
|
{
|
int rc;
|
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
BLE_HS_DBG_ASSERT(attr != NULL || status != 0);
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status != 0 && status != BLE_HS_EDONE) {
|
STATS_INC(ble_gattc_stats, read_long_fail);
|
}
|
|
if (proc->read_long.cb == NULL) {
|
rc = 0;
|
} else {
|
rc = proc->read_long.cb(proc->conn_handle,
|
ble_gattc_error(status, att_handle), attr,
|
proc->read_long.cb_arg);
|
}
|
|
return rc;
|
}
|
|
static void
|
ble_gattc_read_long_tmo(struct ble_gattc_proc *proc)
|
{
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
ble_gattc_read_long_cb(proc, BLE_HS_ETIMEOUT, 0, NULL);
|
}
|
|
/**
|
* Triggers a pending transmit for the specified read-long-characteristic proc.
|
*/
|
static int
|
ble_gattc_read_long_tx(struct ble_gattc_proc *proc)
|
{
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (proc->read_long.offset == 0) {
|
rc = ble_att_clt_tx_read(proc->conn_handle, proc->read_long.handle);
|
if (rc != 0) {
|
return rc;
|
}
|
} else {
|
rc = ble_att_clt_tx_read_blob(proc->conn_handle,
|
proc->read_long.handle,
|
proc->read_long.offset);
|
if (rc != 0) {
|
return rc;
|
}
|
}
|
|
return 0;
|
}
|
|
static int
|
ble_gattc_read_long_resume(struct ble_gattc_proc *proc)
|
{
|
int status;
|
int rc;
|
|
status = ble_gattc_read_long_tx(proc);
|
rc = ble_gattc_process_resume_status(proc, status);
|
if (rc != 0) {
|
ble_gattc_read_long_cb(proc, rc, 0, NULL);
|
return rc;
|
}
|
|
return 0;
|
}
|
|
/**
|
* Handles an incoming ATT error response for the specified
|
* read-long-characteristic proc.
|
*/
|
static void
|
ble_gattc_read_long_err(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle)
|
{
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
ble_gattc_read_long_cb(proc, status, att_handle, NULL);
|
}
|
|
/**
|
* Handles an incoming read-response for the specified
|
* read-long-characteristic-values proc.
|
*/
|
static int
|
ble_gattc_read_long_rx_read_rsp(struct ble_gattc_proc *proc, int status,
|
struct os_mbuf **om)
|
{
|
struct ble_gatt_attr attr;
|
uint16_t data_len;
|
uint16_t mtu;
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
data_len = OS_MBUF_PKTLEN(*om);
|
|
attr.handle = proc->read_long.handle;
|
attr.offset = proc->read_long.offset;
|
attr.om = *om;
|
|
/* Report partial payload to application. */
|
rc = ble_gattc_read_long_cb(proc, status, 0, &attr);
|
|
/* Indicate to the caller whether the application consumed the mbuf. */
|
*om = attr.om;
|
|
if (rc != 0 || status != 0) {
|
return BLE_HS_EDONE;
|
}
|
|
/* Determine if this is the end of the attribute value. */
|
mtu = ble_att_mtu(proc->conn_handle);
|
if (mtu == 0) {
|
/* No longer connected. */
|
return BLE_HS_EDONE;
|
}
|
|
if (data_len < mtu - 1) {
|
/* Response shorter than maximum allowed; read complete. */
|
ble_gattc_read_long_cb(proc, BLE_HS_EDONE, 0, NULL);
|
return BLE_HS_EDONE;
|
}
|
|
/* Send follow-up request. */
|
proc->read_long.offset += data_len;
|
rc = ble_gattc_read_long_resume(proc);
|
if (rc != 0) {
|
return BLE_HS_EDONE;
|
}
|
|
return 0;
|
}
|
|
int
|
ble_gattc_read_long(uint16_t conn_handle, uint16_t handle, uint16_t offset,
|
ble_gatt_attr_fn *cb, void *cb_arg)
|
{
|
#if !MYNEWT_VAL(BLE_GATT_READ_LONG)
|
return BLE_HS_ENOTSUP;
|
#endif
|
|
struct ble_gattc_proc *proc;
|
int rc;
|
|
STATS_INC(ble_gattc_stats, read_long);
|
|
proc = ble_gattc_proc_alloc();
|
if (proc == NULL) {
|
rc = BLE_HS_ENOMEM;
|
goto done;
|
}
|
|
proc->op = BLE_GATT_OP_READ_LONG;
|
proc->conn_handle = conn_handle;
|
proc->read_long.handle = handle;
|
proc->read_long.offset = offset;
|
proc->read_long.cb = cb;
|
proc->read_long.cb_arg = cb_arg;
|
|
ble_gattc_log_read_long(proc);
|
|
rc = ble_gattc_read_long_tx(proc);
|
if (rc != 0) {
|
goto done;
|
}
|
|
done:
|
if (rc != 0) {
|
STATS_INC(ble_gattc_stats, read_long_fail);
|
}
|
|
ble_gattc_process_status(proc, rc);
|
return rc;
|
}
|
|
/*****************************************************************************
|
* $read multiple *
|
*****************************************************************************/
|
|
/**
|
* Calls a read-multiple-characteristics proc's callback with the specified
|
* parameters. If the proc has no callback, this function is a no-op.
|
*
|
* @return The return code of the callback (or 0 if there
|
* is no callback).
|
*/
|
static int
|
ble_gattc_read_mult_cb(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle, struct os_mbuf **om)
|
{
|
struct ble_gatt_attr attr;
|
int rc;
|
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
BLE_HS_DBG_ASSERT(om != NULL || status != 0);
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status != 0 && status != BLE_HS_EDONE) {
|
STATS_INC(ble_gattc_stats, read_mult_fail);
|
}
|
|
attr.handle = 0;
|
attr.offset = 0;
|
if (om == NULL) {
|
attr.om = NULL;
|
} else {
|
attr.om = *om;
|
}
|
|
if (proc->read_mult.cb == NULL) {
|
rc = 0;
|
} else {
|
rc = proc->read_mult.cb(proc->conn_handle,
|
ble_gattc_error(status, att_handle), &attr,
|
proc->read_mult.cb_arg);
|
}
|
|
/* Indicate to the caller whether the application consumed the mbuf. */
|
if (om != NULL) {
|
*om = attr.om;
|
}
|
|
return rc;
|
}
|
|
static void
|
ble_gattc_read_mult_tmo(struct ble_gattc_proc *proc)
|
{
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
ble_gattc_read_mult_cb(proc, BLE_HS_ETIMEOUT, 0, 0);
|
}
|
|
/**
|
* Handles an incoming ATT error response for the specified
|
* read-multiple-characteristics proc.
|
*/
|
static void
|
ble_gattc_read_mult_err(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle)
|
{
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
ble_gattc_read_mult_cb(proc, status, att_handle, NULL);
|
}
|
|
static int
|
ble_gattc_read_mult_tx(struct ble_gattc_proc *proc)
|
{
|
int rc;
|
|
rc = ble_att_clt_tx_read_mult(proc->conn_handle, proc->read_mult.handles,
|
proc->read_mult.num_handles);
|
if (rc != 0) {
|
return rc;
|
}
|
|
return 0;
|
}
|
|
|
int
|
ble_gattc_read_mult(uint16_t conn_handle, const uint16_t *handles,
|
uint8_t num_handles, ble_gatt_attr_fn *cb,
|
void *cb_arg)
|
{
|
#if !MYNEWT_VAL(BLE_GATT_READ_MULT)
|
return BLE_HS_ENOTSUP;
|
#endif
|
|
struct ble_gattc_proc *proc;
|
int rc;
|
|
proc = NULL;
|
|
STATS_INC(ble_gattc_stats, read_mult);
|
|
if (num_handles > MYNEWT_VAL(BLE_GATT_READ_MAX_ATTRS)) {
|
rc = BLE_HS_EINVAL;
|
goto done;
|
}
|
|
proc = ble_gattc_proc_alloc();
|
if (proc == NULL) {
|
rc = BLE_HS_ENOMEM;
|
goto done;
|
}
|
|
proc->op = BLE_GATT_OP_READ_MULT;
|
proc->conn_handle = conn_handle;
|
memcpy(proc->read_mult.handles, handles, num_handles * sizeof *handles);
|
proc->read_mult.num_handles = num_handles;
|
proc->read_mult.cb = cb;
|
proc->read_mult.cb_arg = cb_arg;
|
|
ble_gattc_log_read_mult(handles, num_handles);
|
rc = ble_gattc_read_mult_tx(proc);
|
if (rc != 0) {
|
goto done;
|
}
|
|
done:
|
if (rc != 0) {
|
STATS_INC(ble_gattc_stats, read_mult_fail);
|
}
|
|
ble_gattc_process_status(proc, rc);
|
return rc;
|
}
|
|
/*****************************************************************************
|
* $write no response *
|
*****************************************************************************/
|
|
int
|
ble_gattc_write_no_rsp(uint16_t conn_handle, uint16_t attr_handle,
|
struct os_mbuf *txom)
|
{
|
#if !MYNEWT_VAL(BLE_GATT_WRITE_NO_RSP)
|
return BLE_HS_ENOTSUP;
|
#endif
|
|
int rc;
|
|
STATS_INC(ble_gattc_stats, write_no_rsp);
|
|
ble_gattc_log_write(attr_handle, OS_MBUF_PKTLEN(txom), 0);
|
|
rc = ble_att_clt_tx_write_cmd(conn_handle, attr_handle, txom);
|
if (rc != 0) {
|
STATS_INC(ble_gattc_stats, write);
|
}
|
|
return rc;
|
}
|
|
int
|
ble_gattc_write_no_rsp_flat(uint16_t conn_handle, uint16_t attr_handle,
|
const void *data, uint16_t data_len)
|
{
|
struct os_mbuf *om;
|
int rc;
|
|
om = ble_hs_mbuf_from_flat(data, data_len);
|
if (om == NULL) {
|
return BLE_HS_ENOMEM;
|
}
|
|
rc = ble_gattc_write_no_rsp(conn_handle, attr_handle, om);
|
if (rc != 0) {
|
return rc;
|
}
|
|
return 0;
|
}
|
|
/*****************************************************************************
|
* $write *
|
*****************************************************************************/
|
|
/**
|
* Calls a write-characteristic-value proc's callback with the specified
|
* parameters. If the proc has no callback, this function is a no-op.
|
*
|
* @return The return code of the callback (or 0 if there
|
* is no callback).
|
*/
|
static int
|
ble_gattc_write_cb(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle)
|
{
|
struct ble_gatt_attr attr;
|
int rc;
|
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status != 0 && status != BLE_HS_EDONE) {
|
STATS_INC(ble_gattc_stats, write_fail);
|
}
|
|
if (proc->write.cb == NULL) {
|
rc = 0;
|
} else {
|
memset(&attr, 0, sizeof attr);
|
attr.handle = proc->write.att_handle;
|
rc = proc->write.cb(proc->conn_handle,
|
ble_gattc_error(status, att_handle),
|
&attr, proc->write.cb_arg);
|
}
|
|
return rc;
|
}
|
|
static void
|
ble_gattc_write_tmo(struct ble_gattc_proc *proc)
|
{
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
ble_gattc_write_cb(proc, BLE_HS_ETIMEOUT, 0);
|
}
|
|
/**
|
* Handles an incoming ATT error response for the specified
|
* write-characteristic-value proc.
|
*/
|
static void
|
ble_gattc_write_err(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle)
|
{
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
ble_gattc_write_cb(proc, status, att_handle);
|
}
|
|
int
|
ble_gattc_write(uint16_t conn_handle, uint16_t attr_handle,
|
struct os_mbuf *txom, ble_gatt_attr_fn *cb, void *cb_arg)
|
{
|
#if !MYNEWT_VAL(BLE_GATT_WRITE)
|
return BLE_HS_ENOTSUP;
|
#endif
|
|
struct ble_gattc_proc *proc;
|
int rc;
|
|
STATS_INC(ble_gattc_stats, write);
|
|
proc = ble_gattc_proc_alloc();
|
if (proc == NULL) {
|
rc = BLE_HS_ENOMEM;
|
goto done;
|
}
|
|
proc->op = BLE_GATT_OP_WRITE;
|
proc->conn_handle = conn_handle;
|
proc->write.att_handle = attr_handle;
|
proc->write.cb = cb;
|
proc->write.cb_arg = cb_arg;
|
|
ble_gattc_log_write(attr_handle, OS_MBUF_PKTLEN(txom), 1);
|
|
rc = ble_att_clt_tx_write_req(conn_handle, attr_handle, txom);
|
txom = NULL;
|
if (rc != 0) {
|
goto done;
|
}
|
|
done:
|
if (rc != 0) {
|
STATS_INC(ble_gattc_stats, write_fail);
|
}
|
|
/* Free the mbuf in case the send failed. */
|
os_mbuf_free_chain(txom);
|
|
ble_gattc_process_status(proc, rc);
|
return rc;
|
}
|
|
int
|
ble_gattc_write_flat(uint16_t conn_handle, uint16_t attr_handle,
|
const void *data, uint16_t data_len,
|
ble_gatt_attr_fn *cb, void *cb_arg)
|
{
|
struct os_mbuf *om;
|
int rc;
|
|
om = ble_hs_mbuf_from_flat(data, data_len);
|
if (om == NULL) {
|
return BLE_HS_ENOMEM;
|
}
|
|
rc = ble_gattc_write(conn_handle, attr_handle, om, cb, cb_arg);
|
if (rc != 0) {
|
return rc;
|
}
|
|
return 0;
|
}
|
|
/*****************************************************************************
|
* $write long *
|
*****************************************************************************/
|
|
/**
|
* Calls a write-long-characteristic-value proc's callback with the specified
|
* parameters. If the proc has no callback, this function is a no-op.
|
*
|
* @return The return code of the callback (or 0 if there
|
* is no callback).
|
*/
|
static int
|
ble_gattc_write_long_cb(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle)
|
{
|
int rc;
|
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status != 0 && status != BLE_HS_EDONE) {
|
STATS_INC(ble_gattc_stats, write_long_fail);
|
}
|
|
if (proc->write_long.cb == NULL) {
|
rc = 0;
|
} else {
|
rc = proc->write_long.cb(proc->conn_handle,
|
ble_gattc_error(status, att_handle),
|
&proc->write_long.attr,
|
proc->write_long.cb_arg);
|
}
|
|
return rc;
|
}
|
|
static void
|
ble_gattc_write_long_tmo(struct ble_gattc_proc *proc)
|
{
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
ble_gattc_write_long_cb(proc, BLE_HS_ETIMEOUT, 0);
|
}
|
|
/**
|
* Triggers a pending transmit for the specified
|
* write-long-characteristic-value proc.
|
*/
|
static int
|
ble_gattc_write_long_tx(struct ble_gattc_proc *proc)
|
{
|
struct os_mbuf *om;
|
int write_len;
|
int max_sz;
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
om = NULL;
|
|
max_sz = ble_att_mtu(proc->conn_handle) - BLE_ATT_PREP_WRITE_CMD_BASE_SZ;
|
if (max_sz <= 0) {
|
/* Not connected. */
|
rc = BLE_HS_ENOTCONN;
|
goto done;
|
}
|
|
write_len = min(max_sz,
|
OS_MBUF_PKTLEN(proc->write_long.attr.om) -
|
proc->write_long.attr.offset);
|
|
if (write_len <= 0) {
|
rc = ble_att_clt_tx_exec_write(proc->conn_handle,
|
BLE_ATT_EXEC_WRITE_F_EXECUTE);
|
goto done;
|
}
|
|
proc->write_long.length = write_len;
|
om = ble_hs_mbuf_att_pkt();
|
if (om == NULL) {
|
rc = BLE_HS_ENOMEM;
|
goto done;
|
}
|
|
rc = os_mbuf_appendfrom(om, proc->write_long.attr.om,
|
proc->write_long.attr.offset,
|
proc->write_long.length);
|
if (rc != 0) {
|
rc = BLE_HS_ENOMEM;
|
goto done;
|
}
|
|
rc = ble_att_clt_tx_prep_write(proc->conn_handle,
|
proc->write_long.attr.handle,
|
proc->write_long.attr.offset, om);
|
om = NULL;
|
if (rc != 0) {
|
goto done;
|
}
|
|
done:
|
os_mbuf_free_chain(om);
|
return rc;
|
}
|
|
static int
|
ble_gattc_write_long_resume(struct ble_gattc_proc *proc)
|
{
|
int status;
|
int rc;
|
|
status = ble_gattc_write_long_tx(proc);
|
rc = ble_gattc_process_resume_status(proc, status);
|
if (rc != 0) {
|
ble_gattc_write_long_cb(proc, rc, 0);
|
return rc;
|
}
|
|
return 0;
|
}
|
|
/**
|
* Handles an incoming ATT error response for the specified
|
* write-long-characteristic-value proc.
|
*/
|
static void
|
ble_gattc_write_long_err(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle)
|
{
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
/* If we have successfully queued any data, and the failure occurred before
|
* we could send the execute write command, then erase all queued data.
|
*/
|
if (proc->write_long.attr.offset > 0 &&
|
proc->write_long.attr.offset <
|
OS_MBUF_PKTLEN(proc->write_long.attr.om)) {
|
|
ble_att_clt_tx_exec_write(proc->conn_handle,
|
BLE_ATT_EXEC_WRITE_F_CANCEL);
|
}
|
|
/* Report failure. */
|
ble_gattc_write_long_cb(proc, status, att_handle);
|
}
|
|
/**
|
* Handles an incoming prepare-write-response for the specified
|
* write-long-cahracteristic-values proc.
|
*/
|
static int
|
ble_gattc_write_long_rx_prep(struct ble_gattc_proc *proc,
|
int status,
|
uint16_t handle, uint16_t offset,
|
struct os_mbuf **rxom)
|
{
|
struct os_mbuf *om;
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
/* Let the caller free the mbuf. */
|
om = *rxom;
|
|
if (status != 0) {
|
rc = status;
|
goto err;
|
}
|
|
/* Verify the response. */
|
if (proc->write_long.attr.offset >=
|
OS_MBUF_PKTLEN(proc->write_long.attr.om)) {
|
|
/* Expecting a prepare write response, not an execute write
|
* response.
|
*/
|
rc = BLE_HS_EBADDATA;
|
goto err;
|
}
|
if (handle != proc->write_long.attr.handle) {
|
rc = BLE_HS_EBADDATA;
|
goto err;
|
}
|
if (offset != proc->write_long.attr.offset) {
|
rc = BLE_HS_EBADDATA;
|
goto err;
|
}
|
if (offset + OS_MBUF_PKTLEN(om) >
|
OS_MBUF_PKTLEN(proc->write_long.attr.om)) {
|
|
rc = BLE_HS_EBADDATA;
|
goto err;
|
}
|
if (OS_MBUF_PKTLEN(om) != proc->write_long.length) {
|
rc = BLE_HS_EBADDATA;
|
goto err;
|
}
|
if (os_mbuf_cmpm(om, 0,
|
proc->write_long.attr.om, offset,
|
proc->write_long.length) != 0) {
|
|
rc = BLE_HS_EBADDATA;
|
|
/* if data doesn't match up send cancel write */
|
ble_att_clt_tx_exec_write(proc->conn_handle, BLE_ATT_EXEC_WRITE_F_CANCEL);
|
goto err;
|
} else {
|
/* Send follow-up request. */
|
proc->write_long.attr.offset += OS_MBUF_PKTLEN(om);
|
rc = ble_gattc_write_long_resume(proc);
|
if (rc != 0) {
|
goto err;
|
}
|
|
return 0;
|
}
|
|
err:
|
/* XXX: Might need to cancel pending writes. */
|
ble_gattc_write_long_cb(proc, rc, 0);
|
return BLE_HS_EDONE;
|
}
|
|
/**
|
* Handles an incoming execute-write-response for the specified
|
* write-long-characteristic-values proc.
|
*/
|
static int
|
ble_gattc_write_long_rx_exec(struct ble_gattc_proc *proc, int status)
|
{
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (proc->write_long.attr.offset <
|
OS_MBUF_PKTLEN(proc->write_long.attr.om)) {
|
|
/* Expecting an execute write response, not a prepare write
|
* response.
|
*/
|
return BLE_HS_EBADDATA;
|
}
|
|
ble_gattc_write_long_cb(proc, status, 0);
|
return BLE_HS_EDONE;
|
}
|
|
int
|
ble_gattc_write_long(uint16_t conn_handle, uint16_t attr_handle,
|
uint16_t offset, struct os_mbuf *txom,
|
ble_gatt_attr_fn *cb, void *cb_arg)
|
{
|
#if !MYNEWT_VAL(BLE_GATT_WRITE_LONG)
|
return BLE_HS_ENOTSUP;
|
#endif
|
|
struct ble_gattc_proc *proc;
|
int rc;
|
|
STATS_INC(ble_gattc_stats, write_long);
|
|
proc = ble_gattc_proc_alloc();
|
if (proc == NULL) {
|
rc = BLE_HS_ENOMEM;
|
goto done;
|
}
|
|
proc->op = BLE_GATT_OP_WRITE_LONG;
|
proc->conn_handle = conn_handle;
|
proc->write_long.attr.handle = attr_handle;
|
proc->write_long.attr.offset = offset;
|
proc->write_long.attr.om = txom;
|
proc->write_long.cb = cb;
|
proc->write_long.cb_arg = cb_arg;
|
|
/* The mbuf is consumed by the procedure. */
|
txom = NULL;
|
|
ble_gattc_log_write_long(proc);
|
|
rc = ble_gattc_write_long_tx(proc);
|
if (rc != 0) {
|
goto done;
|
}
|
|
done:
|
if (rc != 0) {
|
STATS_INC(ble_gattc_stats, write_long_fail);
|
}
|
|
/* Free the mbuf in case of failure. */
|
os_mbuf_free_chain(txom);
|
|
ble_gattc_process_status(proc, rc);
|
return rc;
|
}
|
|
/*****************************************************************************
|
* $write reliable *
|
*****************************************************************************/
|
|
/**
|
* Calls a write-long-characteristic-value proc's callback with the specified
|
* parameters. If the proc has no callback, this function is a no-op.
|
*
|
* @return The return code of the callback (or 0 if there
|
* is no callback).
|
*/
|
static int
|
ble_gattc_write_reliable_cb(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle)
|
{
|
int rc;
|
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status != 0 && status != BLE_HS_EDONE) {
|
STATS_INC(ble_gattc_stats, write_reliable_fail);
|
}
|
|
if (proc->write_reliable.cb == NULL) {
|
rc = 0;
|
} else {
|
rc = proc->write_reliable.cb(proc->conn_handle,
|
ble_gattc_error(status, att_handle),
|
proc->write_reliable.attrs,
|
proc->write_reliable.num_attrs,
|
proc->write_reliable.cb_arg);
|
}
|
|
return rc;
|
}
|
|
static void
|
ble_gattc_write_reliable_tmo(struct ble_gattc_proc *proc)
|
{
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
ble_gattc_write_reliable_cb(proc, BLE_HS_ETIMEOUT, 0);
|
}
|
|
/**
|
* Triggers a pending transmit for the specified
|
* write-reliable-characteristic-value proc.
|
*/
|
static int
|
ble_gattc_write_reliable_tx(struct ble_gattc_proc *proc)
|
{
|
struct ble_gatt_attr *attr;
|
struct os_mbuf *om;
|
uint16_t max_sz;
|
int attr_idx;
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
om = NULL;
|
|
attr_idx = proc->write_reliable.cur_attr;
|
|
if (attr_idx >= proc->write_reliable.num_attrs) {
|
rc = ble_att_clt_tx_exec_write(proc->conn_handle,
|
BLE_ATT_EXEC_WRITE_F_EXECUTE);
|
goto done;
|
}
|
|
attr = proc->write_reliable.attrs + attr_idx;
|
|
max_sz = ble_att_mtu(proc->conn_handle) - BLE_ATT_PREP_WRITE_CMD_BASE_SZ;
|
if (max_sz <= 0) {
|
/* Not connected. */
|
rc = BLE_HS_ENOTCONN;
|
goto done;
|
}
|
|
proc->write_reliable.length =
|
min(max_sz, OS_MBUF_PKTLEN(attr->om) - attr->offset);
|
|
om = ble_hs_mbuf_att_pkt();
|
if (om == NULL) {
|
rc = BLE_HS_ENOMEM;
|
goto done;
|
}
|
|
rc = os_mbuf_appendfrom(om, attr->om, attr->offset,
|
proc->write_reliable.length);
|
if (rc != 0) {
|
rc = BLE_HS_ENOMEM;
|
goto done;
|
}
|
|
rc = ble_att_clt_tx_prep_write(proc->conn_handle, attr->handle,
|
attr->offset, om);
|
om = NULL;
|
if (rc != 0) {
|
goto done;
|
}
|
|
done:
|
os_mbuf_free_chain(om);
|
return rc;
|
}
|
|
static int
|
ble_gattc_write_reliable_resume(struct ble_gattc_proc *proc)
|
{
|
int status;
|
int rc;
|
|
status = ble_gattc_write_reliable_tx(proc);
|
rc = ble_gattc_process_resume_status(proc, status);
|
if (rc != 0) {
|
ble_gattc_write_reliable_cb(proc, rc, 0);
|
return rc;
|
}
|
|
return 0;
|
}
|
|
/**
|
* Handles an incoming ATT error response for the specified
|
* write-reliable-characteristic-value proc.
|
*/
|
static void
|
ble_gattc_write_reliable_err(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle)
|
{
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
ble_gattc_write_reliable_cb(proc, status, att_handle);
|
|
/* If we have successfully queued any data, and the failure occurred before
|
* we could send the execute write command, then erase all queued data.
|
*/
|
if (proc->write_reliable.cur_attr < proc->write_reliable.num_attrs) {
|
|
ble_att_clt_tx_exec_write(proc->conn_handle,
|
BLE_ATT_EXEC_WRITE_F_CANCEL);
|
}
|
}
|
|
/**
|
* Handles an incoming prepare-write-response for the specified
|
* write-reliable-cahracteristic-values proc.
|
*/
|
static int
|
ble_gattc_write_reliable_rx_prep(struct ble_gattc_proc *proc,
|
int status,
|
uint16_t handle, uint16_t offset,
|
struct os_mbuf **rxom)
|
{
|
struct ble_gatt_attr *attr;
|
struct os_mbuf *om;
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
/* Let the caller free the mbuf. */
|
om = *rxom;
|
|
if (status != 0) {
|
rc = status;
|
goto err;
|
}
|
|
if (proc->write_reliable.cur_attr >= proc->write_reliable.num_attrs) {
|
/* Expecting an execute write response, not a prepare write
|
* response.
|
*/
|
rc = BLE_HS_EBADDATA;
|
goto err;
|
}
|
attr = proc->write_reliable.attrs + proc->write_reliable.cur_attr;
|
|
/* Verify the response. */
|
if (handle != attr->handle) {
|
rc = BLE_HS_EBADDATA;
|
goto err;
|
}
|
if (offset != attr->offset) {
|
rc = BLE_HS_EBADDATA;
|
goto err;
|
}
|
if (os_mbuf_cmpm(attr->om, offset, om, 0,
|
proc->write_reliable.length) != 0) {
|
|
rc = BLE_HS_EBADDATA;
|
goto err;
|
}
|
|
/* Send follow-up request. */
|
attr->offset += proc->write_reliable.length;
|
if (attr->offset >= OS_MBUF_PKTLEN(attr->om)) {
|
attr->offset = 0;
|
proc->write_reliable.cur_attr++;
|
}
|
rc = ble_gattc_write_reliable_resume(proc);
|
if (rc != 0) {
|
goto err;
|
}
|
|
return 0;
|
|
err:
|
ble_gattc_write_reliable_err(proc, rc, 0);
|
return BLE_HS_EDONE;
|
}
|
|
/**
|
* Handles an incoming execute-write-response for the specified
|
* write-reliable-characteristic-values proc.
|
*/
|
static int
|
ble_gattc_write_reliable_rx_exec(struct ble_gattc_proc *proc, int status)
|
{
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
ble_gattc_write_reliable_cb(proc, status, 0);
|
return BLE_HS_EDONE;
|
}
|
|
int
|
ble_gattc_write_reliable(uint16_t conn_handle,
|
struct ble_gatt_attr *attrs,
|
int num_attrs,
|
ble_gatt_reliable_attr_fn *cb, void *cb_arg)
|
{
|
#if !MYNEWT_VAL(BLE_GATT_WRITE_RELIABLE)
|
return BLE_HS_ENOTSUP;
|
#endif
|
|
struct ble_gattc_proc *proc;
|
int rc;
|
int i;
|
|
proc = NULL;
|
|
STATS_INC(ble_gattc_stats, write_reliable);
|
|
if (num_attrs > MYNEWT_VAL(BLE_GATT_WRITE_MAX_ATTRS)) {
|
rc = BLE_HS_EINVAL;
|
goto done;
|
}
|
|
proc = ble_gattc_proc_alloc();
|
if (proc == NULL) {
|
rc = BLE_HS_ENOMEM;
|
goto done;
|
}
|
|
proc->op = BLE_GATT_OP_WRITE_RELIABLE;
|
proc->conn_handle = conn_handle;
|
proc->write_reliable.num_attrs = num_attrs;
|
proc->write_reliable.cur_attr = 0;
|
proc->write_reliable.cb = cb;
|
proc->write_reliable.cb_arg = cb_arg;
|
|
for (i = 0; i < num_attrs; i++) {
|
proc->write_reliable.attrs[i] = attrs[i];
|
proc->write_reliable.attrs[i].offset = 0;
|
|
/* Consume mbuf from caller. */
|
attrs[i].om = NULL;
|
}
|
|
ble_gattc_log_write_reliable(proc);
|
rc = ble_gattc_write_reliable_tx(proc);
|
if (rc != 0) {
|
goto done;
|
}
|
|
done:
|
if (rc != 0) {
|
STATS_INC(ble_gattc_stats, write_reliable_fail);
|
}
|
|
/* Free supplied mbufs in case something failed. */
|
for (i = 0; i < num_attrs; i++) {
|
os_mbuf_free_chain(attrs[i].om);
|
attrs[i].om = NULL;
|
}
|
|
ble_gattc_process_status(proc, rc);
|
return rc;
|
}
|
|
/*****************************************************************************
|
* $notify *
|
*****************************************************************************/
|
|
int
|
ble_gatts_notify_custom(uint16_t conn_handle, uint16_t chr_val_handle,
|
struct os_mbuf *txom)
|
{
|
#if !MYNEWT_VAL(BLE_GATT_NOTIFY)
|
return BLE_HS_ENOTSUP;
|
#endif
|
|
int rc;
|
|
STATS_INC(ble_gattc_stats, notify);
|
|
ble_gattc_log_notify(chr_val_handle);
|
|
if (txom == NULL) {
|
/* No custom attribute data; read the value from the specified
|
* attribute.
|
*/
|
txom = ble_hs_mbuf_att_pkt();
|
if (txom == NULL) {
|
rc = BLE_HS_ENOMEM;
|
goto done;
|
}
|
rc = ble_att_svr_read_handle(BLE_HS_CONN_HANDLE_NONE,
|
chr_val_handle, 0, txom, NULL);
|
if (rc != 0) {
|
/* Fatal error; application disallowed attribute read. */
|
rc = BLE_HS_EAPP;
|
goto done;
|
}
|
}
|
|
rc = ble_att_clt_tx_notify(conn_handle, chr_val_handle, txom);
|
txom = NULL;
|
if (rc != 0) {
|
goto done;
|
}
|
|
done:
|
if (rc != 0) {
|
STATS_INC(ble_gattc_stats, notify_fail);
|
}
|
|
/* Tell the application that a notification transmission was attempted. */
|
ble_gap_notify_tx_event(rc, conn_handle, chr_val_handle, 0);
|
|
os_mbuf_free_chain(txom);
|
|
return rc;
|
}
|
|
/**
|
* Deprecated. Should not be used. Use ble_gatts_notify_custom instead.
|
*/
|
int
|
ble_gattc_notify_custom(uint16_t conn_handle, uint16_t chr_val_handle,
|
struct os_mbuf *txom)
|
{
|
return ble_gatts_notify_custom(conn_handle, chr_val_handle, txom);
|
}
|
|
int
|
ble_gatts_notify(uint16_t conn_handle, uint16_t chr_val_handle)
|
{
|
#if !MYNEWT_VAL(BLE_GATT_NOTIFY)
|
return BLE_HS_ENOTSUP;
|
#endif
|
|
int rc;
|
|
rc = ble_gatts_notify_custom(conn_handle, chr_val_handle, NULL);
|
|
return rc;
|
}
|
|
/**
|
* Deprecated. Should not be used. Use ble_gatts_notify instead.
|
*/
|
int
|
ble_gattc_notify(uint16_t conn_handle, uint16_t chr_val_handle)
|
{
|
return ble_gatts_notify(conn_handle, chr_val_handle);
|
}
|
|
/*****************************************************************************
|
* $indicate *
|
*****************************************************************************/
|
|
/**
|
* Handles an incoming ATT error response for the specified indication proc.
|
* A device should never send an error in response to an indication. If this
|
* happens, we treat it like a confirmation (indication ack), but report the
|
* error status to the application.
|
*/
|
static void
|
ble_gatts_indicate_err(struct ble_gattc_proc *proc, int status,
|
uint16_t att_handle)
|
{
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
if (status != BLE_HS_ENOTCONN) {
|
rc = ble_gatts_rx_indicate_ack(proc->conn_handle,
|
proc->indicate.chr_val_handle);
|
if (rc != 0) {
|
return;
|
}
|
}
|
|
/* Tell the application about the received acknowledgment. */
|
ble_gap_notify_tx_event(status, proc->conn_handle,
|
proc->indicate.chr_val_handle, 1);
|
|
/* Send the next indication if one is pending. */
|
ble_gatts_send_next_indicate(proc->conn_handle);
|
}
|
|
static void
|
ble_gatts_indicate_tmo(struct ble_gattc_proc *proc)
|
{
|
BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
ble_gap_notify_tx_event(BLE_HS_ETIMEOUT, proc->conn_handle,
|
proc->indicate.chr_val_handle, 1);
|
}
|
|
/**
|
* Handles an incoming handle-value-confirmation for the specified indication
|
* proc.
|
*/
|
static void
|
ble_gatts_indicate_rx_rsp(struct ble_gattc_proc *proc)
|
{
|
int rc;
|
|
ble_gattc_dbg_assert_proc_not_inserted(proc);
|
|
rc = ble_gatts_rx_indicate_ack(proc->conn_handle,
|
proc->indicate.chr_val_handle);
|
if (rc != 0) {
|
return;
|
}
|
|
/* Tell the application about the received acknowledgment. */
|
ble_gap_notify_tx_event(BLE_HS_EDONE, proc->conn_handle,
|
proc->indicate.chr_val_handle, 1);
|
|
/* Send the next indication if one is pending. */
|
ble_gatts_send_next_indicate(proc->conn_handle);
|
}
|
|
/**
|
* Causes the indication in progress for the specified connection (if any) to
|
* fail with a status code of BLE_HS_ENOTCONN;
|
*/
|
void
|
ble_gatts_indicate_fail_notconn(uint16_t conn_handle)
|
{
|
ble_gattc_fail_procs(conn_handle, BLE_GATT_OP_INDICATE, BLE_HS_ENOTCONN);
|
}
|
|
int
|
ble_gatts_indicate_custom(uint16_t conn_handle, uint16_t chr_val_handle,
|
struct os_mbuf *txom)
|
{
|
#if !MYNEWT_VAL(BLE_GATT_INDICATE)
|
return BLE_HS_ENOTSUP;
|
#endif
|
|
struct ble_gattc_proc *proc;
|
struct ble_hs_conn *conn;
|
int rc;
|
|
STATS_INC(ble_gattc_stats, indicate);
|
|
proc = ble_gattc_proc_alloc();
|
if (proc == NULL) {
|
rc = BLE_HS_ENOMEM;
|
goto done;
|
}
|
|
proc->op = BLE_GATT_OP_INDICATE;
|
proc->conn_handle = conn_handle;
|
proc->indicate.chr_val_handle = chr_val_handle;
|
|
ble_gattc_log_indicate(chr_val_handle);
|
|
if (txom == NULL) {
|
/* No custom attribute data; read the value from the specified
|
* attribute.
|
*/
|
txom = ble_hs_mbuf_att_pkt();
|
if (txom == NULL) {
|
rc = BLE_HS_ENOMEM;
|
goto done;
|
}
|
|
rc = ble_att_svr_read_handle(BLE_HS_CONN_HANDLE_NONE, chr_val_handle,
|
0, txom, NULL);
|
if (rc != 0) {
|
/* Fatal error; application disallowed attribute read. */
|
BLE_HS_DBG_ASSERT(0);
|
rc = BLE_HS_EAPP;
|
goto done;
|
}
|
}
|
|
rc = ble_att_clt_tx_indicate(conn_handle, chr_val_handle, txom);
|
txom = NULL;
|
if (rc != 0) {
|
goto done;
|
}
|
|
ble_hs_lock();
|
conn = ble_hs_conn_find(conn_handle);
|
if (conn != NULL) {
|
BLE_HS_DBG_ASSERT(conn->bhc_gatt_svr.indicate_val_handle == 0);
|
conn->bhc_gatt_svr.indicate_val_handle = chr_val_handle;
|
}
|
ble_hs_unlock();
|
|
done:
|
if (rc != 0) {
|
STATS_INC(ble_gattc_stats, indicate_fail);
|
}
|
|
/* Tell the application that an indication transmission was attempted. */
|
ble_gap_notify_tx_event(rc, conn_handle, chr_val_handle, 1);
|
|
ble_gattc_process_status(proc, rc);
|
os_mbuf_free_chain(txom);
|
return rc;
|
}
|
|
/**
|
* Deprecated. Should not be used. Use ble_gatts_indicate_custom instead.
|
*/
|
int
|
ble_gattc_indicate_custom(uint16_t conn_handle, uint16_t chr_val_handle,
|
struct os_mbuf *txom)
|
{
|
return ble_gatts_indicate_custom(conn_handle, chr_val_handle, txom);
|
}
|
|
int
|
ble_gatts_indicate(uint16_t conn_handle, uint16_t chr_val_handle)
|
{
|
return ble_gatts_indicate_custom(conn_handle, chr_val_handle, NULL);
|
}
|
|
/**
|
* Deprecated. Should not be used. Use ble_gatts_indicate instead.
|
*/
|
int
|
ble_gattc_indicate(uint16_t conn_handle, uint16_t chr_val_handle)
|
{
|
return ble_gatts_indicate(conn_handle, chr_val_handle);
|
}
|
|
/*****************************************************************************
|
* $rx *
|
*****************************************************************************/
|
|
/**
|
* Dispatches an incoming ATT error-response to the appropriate active GATT
|
* procedure.
|
*/
|
void
|
ble_gattc_rx_err(uint16_t conn_handle, uint16_t handle, uint16_t status)
|
{
|
struct ble_gattc_proc *proc;
|
ble_gattc_err_fn *err_cb;
|
|
proc = ble_gattc_extract_first_by_conn_op(conn_handle, BLE_GATT_OP_NONE);
|
if (proc != NULL) {
|
err_cb = ble_gattc_err_dispatch_get(proc->op);
|
if (err_cb != NULL) {
|
err_cb(proc, BLE_HS_ERR_ATT_BASE + status, handle);
|
}
|
ble_gattc_proc_free(proc);
|
}
|
}
|
|
/**
|
* Dispatches an incoming ATT exchange-mtu-response to the appropriate active
|
* GATT procedure.
|
*/
|
void
|
ble_gattc_rx_mtu(uint16_t conn_handle, int status, uint16_t chan_mtu)
|
{
|
struct ble_gattc_proc *proc;
|
|
proc = ble_gattc_extract_first_by_conn_op(conn_handle, BLE_GATT_OP_MTU);
|
if (proc != NULL) {
|
ble_gattc_mtu_cb(proc, status, 0, chan_mtu);
|
ble_gattc_process_status(proc, BLE_HS_EDONE);
|
}
|
}
|
|
/**
|
* Dispatches an incoming "information data" entry from a
|
* find-information-response to the appropriate active GATT procedure.
|
*/
|
void
|
ble_gattc_rx_find_info_idata(uint16_t conn_handle,
|
struct ble_att_find_info_idata *idata)
|
{
|
#if !NIMBLE_BLE_ATT_CLT_FIND_INFO
|
return;
|
#endif
|
|
struct ble_gattc_proc *proc;
|
int rc;
|
|
proc = ble_gattc_extract_first_by_conn_op(conn_handle,
|
BLE_GATT_OP_DISC_ALL_DSCS);
|
if (proc != NULL) {
|
rc = ble_gattc_disc_all_dscs_rx_idata(proc, idata);
|
ble_gattc_process_status(proc, rc);
|
}
|
}
|
|
/**
|
* Dispatches an incoming notification of the end of a
|
* find-information-response to the appropriate active GATT procedure.
|
*/
|
void
|
ble_gattc_rx_find_info_complete(uint16_t conn_handle, int status)
|
{
|
#if !NIMBLE_BLE_ATT_CLT_FIND_INFO
|
return;
|
#endif
|
|
struct ble_gattc_proc *proc;
|
int rc;
|
|
proc = ble_gattc_extract_first_by_conn_op(conn_handle,
|
BLE_GATT_OP_DISC_ALL_DSCS);
|
if (proc != NULL) {
|
rc = ble_gattc_disc_all_dscs_rx_complete(proc, status);
|
ble_gattc_process_status(proc, rc);
|
}
|
}
|
|
/**
|
* Dispatches an incoming "handles info" entry from a
|
* find-by-type-value-response to the appropriate active GATT procedure.
|
*/
|
void
|
ble_gattc_rx_find_type_value_hinfo(uint16_t conn_handle,
|
struct ble_att_find_type_value_hinfo *hinfo)
|
{
|
#if !NIMBLE_BLE_ATT_CLT_FIND_TYPE
|
return;
|
#endif
|
|
struct ble_gattc_proc *proc;
|
int rc;
|
|
proc = ble_gattc_extract_first_by_conn_op(conn_handle,
|
BLE_GATT_OP_DISC_SVC_UUID);
|
if (proc != NULL) {
|
rc = ble_gattc_disc_svc_uuid_rx_hinfo(proc, hinfo);
|
ble_gattc_process_status(proc, rc);
|
}
|
}
|
|
/**
|
* Dispatches an incoming notification of the end of a
|
* find-by-type-value-response to the appropriate active GATT procedure.
|
*/
|
void
|
ble_gattc_rx_find_type_value_complete(uint16_t conn_handle, int status)
|
{
|
#if !NIMBLE_BLE_ATT_CLT_FIND_TYPE
|
return;
|
#endif
|
|
struct ble_gattc_proc *proc;
|
int rc;
|
|
proc = ble_gattc_extract_first_by_conn_op(conn_handle,
|
BLE_GATT_OP_DISC_SVC_UUID);
|
if (proc != NULL) {
|
rc = ble_gattc_disc_svc_uuid_rx_complete(proc, status);
|
ble_gattc_process_status(proc, rc);
|
}
|
}
|
|
/**
|
* Dispatches an incoming "attribute data" entry from a read-by-type-response
|
* to the appropriate active GATT procedure.
|
*/
|
void
|
ble_gattc_rx_read_type_adata(uint16_t conn_handle,
|
struct ble_att_read_type_adata *adata)
|
{
|
#if !NIMBLE_BLE_ATT_CLT_READ_TYPE
|
return;
|
#endif
|
|
const struct ble_gattc_rx_adata_entry *rx_entry;
|
struct ble_gattc_proc *proc;
|
int rc;
|
|
proc = BLE_GATTC_RX_EXTRACT_RX_ENTRY(conn_handle,
|
ble_gattc_rx_read_type_elem_entries,
|
&rx_entry);
|
if (proc != NULL) {
|
rc = rx_entry->cb(proc, adata);
|
ble_gattc_process_status(proc, rc);
|
}
|
}
|
|
/**
|
* Dispatches an incoming notification of the end of a read-by-type-response to
|
* the appropriate active GATT procedure.
|
*/
|
void
|
ble_gattc_rx_read_type_complete(uint16_t conn_handle, int status)
|
{
|
#if !NIMBLE_BLE_ATT_CLT_READ_TYPE
|
return;
|
#endif
|
|
const struct ble_gattc_rx_complete_entry *rx_entry;
|
struct ble_gattc_proc *proc;
|
int rc;
|
|
proc = BLE_GATTC_RX_EXTRACT_RX_ENTRY(
|
conn_handle, ble_gattc_rx_read_type_complete_entries,
|
&rx_entry);
|
if (proc != NULL) {
|
rc = rx_entry->cb(proc, status);
|
ble_gattc_process_status(proc, rc);
|
}
|
}
|
|
/**
|
* Dispatches an incoming "attribute data" entry from a
|
* read-by-group-type-response to the appropriate active GATT procedure.
|
*/
|
void
|
ble_gattc_rx_read_group_type_adata(uint16_t conn_handle,
|
struct ble_att_read_group_type_adata *adata)
|
{
|
#if !NIMBLE_BLE_ATT_CLT_READ_GROUP_TYPE
|
return;
|
#endif
|
|
struct ble_gattc_proc *proc;
|
int rc;
|
|
proc = ble_gattc_extract_first_by_conn_op(conn_handle,
|
BLE_GATT_OP_DISC_ALL_SVCS);
|
if (proc != NULL) {
|
rc = ble_gattc_disc_all_svcs_rx_adata(proc, adata);
|
ble_gattc_process_status(proc, rc);
|
}
|
}
|
|
/**
|
* Dispatches an incoming notification of the end of a
|
* read-by-group-type-response to the appropriate active GATT procedure.
|
*/
|
void
|
ble_gattc_rx_read_group_type_complete(uint16_t conn_handle, int status)
|
{
|
#if !NIMBLE_BLE_ATT_CLT_READ_GROUP_TYPE
|
return;
|
#endif
|
|
struct ble_gattc_proc *proc;
|
int rc;
|
|
proc = ble_gattc_extract_first_by_conn_op(conn_handle,
|
BLE_GATT_OP_DISC_ALL_SVCS);
|
if (proc != NULL) {
|
rc = ble_gattc_disc_all_svcs_rx_complete(proc, status);
|
ble_gattc_process_status(proc, rc);
|
}
|
}
|
|
/**
|
* Dispatches an incoming ATT read-response to the appropriate active GATT
|
* procedure.
|
*/
|
void
|
ble_gattc_rx_read_rsp(uint16_t conn_handle, int status, struct os_mbuf **om)
|
{
|
#if !NIMBLE_BLE_ATT_CLT_READ
|
return;
|
#endif
|
|
const struct ble_gattc_rx_attr_entry *rx_entry;
|
struct ble_gattc_proc *proc;
|
int rc;
|
|
proc = BLE_GATTC_RX_EXTRACT_RX_ENTRY(conn_handle,
|
ble_gattc_rx_read_rsp_entries,
|
&rx_entry);
|
if (proc != NULL) {
|
rc = rx_entry->cb(proc, status, om);
|
ble_gattc_process_status(proc, rc);
|
}
|
}
|
|
/**
|
* Dispatches an incoming ATT read-blob-response to the appropriate active GATT
|
* procedure.
|
*/
|
void
|
ble_gattc_rx_read_blob_rsp(uint16_t conn_handle, int status,
|
struct os_mbuf **om)
|
{
|
#if !NIMBLE_BLE_ATT_CLT_READ_BLOB
|
return;
|
#endif
|
|
struct ble_gattc_proc *proc;
|
int rc;
|
|
proc = ble_gattc_extract_first_by_conn_op(conn_handle,
|
BLE_GATT_OP_READ_LONG);
|
if (proc != NULL) {
|
rc = ble_gattc_read_long_rx_read_rsp(proc, status, om);
|
ble_gattc_process_status(proc, rc);
|
}
|
}
|
|
/**
|
* Dispatches an incoming ATT read-multiple-response to the appropriate active
|
* GATT procedure.
|
*/
|
void
|
ble_gattc_rx_read_mult_rsp(uint16_t conn_handle, int status,
|
struct os_mbuf **om)
|
{
|
#if !NIMBLE_BLE_ATT_CLT_READ_MULT
|
return;
|
#endif
|
|
struct ble_gattc_proc *proc;
|
|
proc = ble_gattc_extract_first_by_conn_op(conn_handle,
|
BLE_GATT_OP_READ_MULT);
|
if (proc != NULL) {
|
ble_gattc_read_mult_cb(proc, status, 0, om);
|
ble_gattc_process_status(proc, BLE_HS_EDONE);
|
}
|
}
|
|
/**
|
* Dispatches an incoming ATT write-response to the appropriate active GATT
|
* procedure.
|
*/
|
void
|
ble_gattc_rx_write_rsp(uint16_t conn_handle)
|
{
|
#if !NIMBLE_BLE_ATT_CLT_WRITE
|
return;
|
#endif
|
|
struct ble_gattc_proc *proc;
|
|
proc = ble_gattc_extract_first_by_conn_op(conn_handle,
|
BLE_GATT_OP_WRITE);
|
if (proc != NULL) {
|
ble_gattc_write_cb(proc, 0, 0);
|
ble_gattc_process_status(proc, BLE_HS_EDONE);
|
}
|
}
|
|
/**
|
* Dispatches an incoming ATT prepare-write-response to the appropriate active
|
* GATT procedure.
|
*/
|
void
|
ble_gattc_rx_prep_write_rsp(uint16_t conn_handle, int status,
|
uint16_t handle, uint16_t offset,
|
struct os_mbuf **om)
|
{
|
#if !NIMBLE_BLE_ATT_CLT_PREP_WRITE
|
return;
|
#endif
|
|
const struct ble_gattc_rx_prep_entry *rx_entry;
|
struct ble_gattc_proc *proc;
|
int rc;
|
|
proc = BLE_GATTC_RX_EXTRACT_RX_ENTRY(conn_handle,
|
ble_gattc_rx_prep_entries,
|
&rx_entry);
|
if (proc != NULL) {
|
rc = rx_entry->cb(proc, status, handle, offset, om);
|
ble_gattc_process_status(proc, rc);
|
}
|
}
|
|
/**
|
* Dispatches an incoming ATT execute-write-response to the appropriate active
|
* GATT procedure.
|
*/
|
void
|
ble_gattc_rx_exec_write_rsp(uint16_t conn_handle, int status)
|
{
|
#if !NIMBLE_BLE_ATT_CLT_EXEC_WRITE
|
return;
|
#endif
|
|
const struct ble_gattc_rx_exec_entry *rx_entry;
|
struct ble_gattc_proc *proc;
|
int rc;
|
|
proc = BLE_GATTC_RX_EXTRACT_RX_ENTRY(conn_handle,
|
ble_gattc_rx_exec_entries, &rx_entry);
|
if (proc != NULL) {
|
rc = rx_entry->cb(proc, status);
|
ble_gattc_process_status(proc, rc);
|
}
|
}
|
|
/**
|
* Dispatches an incoming ATT handle-value-confirmation to the appropriate
|
* active GATT procedure.
|
*/
|
void
|
ble_gatts_rx_indicate_rsp(uint16_t conn_handle)
|
{
|
#if !NIMBLE_BLE_ATT_CLT_INDICATE
|
return;
|
#endif
|
|
struct ble_gattc_proc *proc;
|
|
proc = ble_gattc_extract_first_by_conn_op(conn_handle,
|
BLE_GATT_OP_INDICATE);
|
if (proc != NULL) {
|
ble_gatts_indicate_rx_rsp(proc);
|
ble_gattc_process_status(proc, BLE_HS_EDONE);
|
}
|
}
|
|
/*****************************************************************************
|
* $misc *
|
*****************************************************************************/
|
|
/**
|
* Called when a BLE connection ends. Frees all GATT resources associated with
|
* the connection and cancels all relevant pending and in-progress GATT
|
* procedures.
|
*
|
* @param conn_handle The handle of the connection that was
|
* terminated.
|
*/
|
void
|
ble_gattc_connection_broken(uint16_t conn_handle)
|
{
|
ble_gattc_fail_procs(conn_handle, BLE_GATT_OP_NONE, BLE_HS_ENOTCONN);
|
}
|
|
/**
|
* Indicates whether there are currently any active GATT client procedures.
|
*/
|
int
|
ble_gattc_any_jobs(void)
|
{
|
return !STAILQ_EMPTY(&ble_gattc_procs);
|
}
|
|
int
|
ble_gattc_init(void)
|
{
|
int rc;
|
|
#ifdef IP_101x
|
ble_gattc_proc_mem = panchip_mem_get((OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_GATT_MAX_PROCS),
|
sizeof (struct ble_gattc_proc))) * 4, 4);
|
#endif
|
STAILQ_INIT(&ble_gattc_procs);
|
|
if (MYNEWT_VAL(BLE_GATT_MAX_PROCS) > 0) {
|
rc = os_mempool_init(&ble_gattc_proc_pool,
|
MYNEWT_VAL(BLE_GATT_MAX_PROCS),
|
sizeof (struct ble_gattc_proc),
|
ble_gattc_proc_mem,
|
"ble_gattc_proc_pool");
|
if (rc != 0) {
|
return rc;
|
}
|
}
|
|
rc = stats_init_and_reg(
|
STATS_HDR(ble_gattc_stats), STATS_SIZE_INIT_PARMS(ble_gattc_stats,
|
STATS_SIZE_32), STATS_NAME_INIT_PARMS(ble_gattc_stats), "ble_gattc");
|
if (rc != 0) {
|
return BLE_HS_EOS;
|
}
|
|
return 0;
|
}
|
|
#endif
|