/*
|
* Licensed to the Apache Software Foundation (ASF) under one
|
* or more contributor license agreements. See the NOTICE file
|
* distributed with this work for additional information
|
* regarding copyright ownership. The ASF licenses this file
|
* to you under the Apache License, Version 2.0 (the
|
* "License"); you may not use this file except in compliance
|
* with the License. You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing,
|
* software distributed under the License is distributed on an
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
* KIND, either express or implied. See the License for the
|
* specific language governing permissions and limitations
|
* under the License.
|
*/
|
|
#include <string.h>
|
#include <errno.h>
|
#include "nimble_syscfg.h"
|
#include "os/os.h"
|
#include "host/ble_hs_id.h"
|
#include "ble_hs_priv.h"
|
|
/** At least three channels required per connection (sig, att, sm). */
|
#if NIMBLE_BLE_SM
|
#define BLE_HS_CONN_MIN_CHANS 3
|
#else
|
#define BLE_HS_CONN_MIN_CHANS 2
|
#endif
|
|
static SLIST_HEAD(, ble_hs_conn) ble_hs_conns;
|
static struct os_mempool ble_hs_conn_pool;
|
|
static os_membuf_t ble_hs_conn_elem_mem[
|
OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_MAX_CONNECTIONS),
|
sizeof (struct ble_hs_conn))
|
];
|
|
static const uint8_t ble_hs_conn_null_addr[6];
|
|
int
|
ble_hs_conn_can_alloc(void)
|
{
|
#if !NIMBLE_BLE_CONNECT
|
return 0;
|
#endif
|
|
return ble_hs_conn_pool.mp_num_free >= 1 &&
|
ble_l2cap_chan_pool.mp_num_free >= BLE_HS_CONN_MIN_CHANS &&
|
ble_gatts_conn_can_alloc();
|
}
|
|
struct ble_l2cap_chan *
|
ble_hs_conn_chan_find_by_scid(struct ble_hs_conn *conn, uint16_t cid)
|
{
|
#if !NIMBLE_BLE_CONNECT
|
return NULL;
|
#endif
|
|
struct ble_l2cap_chan *chan;
|
|
SLIST_FOREACH(chan, &conn->bhc_channels, next) {
|
if (chan->scid == cid) {
|
return chan;
|
}
|
if (chan->scid > cid) {
|
return NULL;
|
}
|
}
|
|
return NULL;
|
}
|
|
struct ble_l2cap_chan *
|
ble_hs_conn_chan_find_by_dcid(struct ble_hs_conn *conn, uint16_t cid)
|
{
|
#if !NIMBLE_BLE_CONNECT
|
return NULL;
|
#endif
|
|
struct ble_l2cap_chan *chan;
|
|
SLIST_FOREACH(chan, &conn->bhc_channels, next) {
|
if (chan->dcid == cid) {
|
return chan;
|
}
|
}
|
|
return NULL;
|
}
|
|
bool
|
ble_hs_conn_chan_exist(struct ble_hs_conn *conn, struct ble_l2cap_chan *chan)
|
{
|
#if !NIMBLE_BLE_CONNECT
|
return NULL;
|
#endif
|
|
struct ble_l2cap_chan *tmp;
|
|
SLIST_FOREACH(tmp, &conn->bhc_channels, next) {
|
if (chan == tmp) {
|
return true;
|
}
|
}
|
|
return false;
|
}
|
|
int
|
ble_hs_conn_chan_insert(struct ble_hs_conn *conn, struct ble_l2cap_chan *chan)
|
{
|
#if !NIMBLE_BLE_CONNECT
|
return BLE_HS_ENOTSUP;
|
#endif
|
|
struct ble_l2cap_chan *prev;
|
struct ble_l2cap_chan *cur;
|
|
prev = NULL;
|
SLIST_FOREACH(cur, &conn->bhc_channels, next) {
|
if (cur->scid == chan->scid) {
|
return BLE_HS_EALREADY;
|
}
|
if (cur->scid > chan->scid) {
|
break;
|
}
|
|
prev = cur;
|
}
|
|
if (prev == NULL) {
|
SLIST_INSERT_HEAD(&conn->bhc_channels, chan, next);
|
} else {
|
SLIST_INSERT_AFTER(prev, chan, next);
|
}
|
|
return 0;
|
}
|
|
struct ble_hs_conn *
|
ble_hs_conn_alloc(uint16_t conn_handle)
|
{
|
#if !NIMBLE_BLE_CONNECT
|
return NULL;
|
#endif
|
|
struct ble_l2cap_chan *chan;
|
struct ble_hs_conn *conn;
|
int rc;
|
|
conn = os_memblock_get(&ble_hs_conn_pool);
|
if (conn == NULL) {
|
goto err;
|
}
|
memset(conn, 0, sizeof *conn);
|
conn->bhc_handle = conn_handle;
|
|
SLIST_INIT(&conn->bhc_channels);
|
|
chan = ble_att_create_chan(conn_handle);
|
if (chan == NULL) {
|
goto err;
|
}
|
rc = ble_hs_conn_chan_insert(conn, chan);
|
if (rc != 0) {
|
goto err;
|
}
|
|
chan = ble_l2cap_sig_create_chan(conn_handle);
|
if (chan == NULL) {
|
goto err;
|
}
|
rc = ble_hs_conn_chan_insert(conn, chan);
|
if (rc != 0) {
|
goto err;
|
}
|
|
/* Create the SM channel even if not configured. We need it to reject SM
|
* messages.
|
*/
|
chan = ble_sm_create_chan(conn_handle);
|
if (chan == NULL) {
|
goto err;
|
}
|
rc = ble_hs_conn_chan_insert(conn, chan);
|
if (rc != 0) {
|
goto err;
|
}
|
|
rc = ble_gatts_conn_init(&conn->bhc_gatt_svr);
|
if (rc != 0) {
|
goto err;
|
}
|
|
STAILQ_INIT(&conn->bhc_tx_q);
|
|
STATS_INC(ble_hs_stats, conn_create);
|
|
return conn;
|
|
err:
|
ble_hs_conn_free(conn);
|
return NULL;
|
}
|
|
void
|
ble_hs_conn_delete_chan(struct ble_hs_conn *conn, struct ble_l2cap_chan *chan)
|
{
|
if (conn->bhc_rx_chan == chan) {
|
conn->bhc_rx_chan = NULL;
|
}
|
|
SLIST_REMOVE(&conn->bhc_channels, chan, ble_l2cap_chan, next);
|
ble_l2cap_chan_free(conn, chan);
|
}
|
|
void
|
ble_hs_conn_foreach(ble_hs_conn_foreach_fn *cb, void *arg)
|
{
|
struct ble_hs_conn *conn;
|
|
BLE_HS_DBG_ASSERT(ble_hs_locked_by_cur_task());
|
|
SLIST_FOREACH(conn, &ble_hs_conns, bhc_next) {
|
if (cb(conn, arg) != 0) {
|
return;
|
}
|
}
|
}
|
|
void
|
ble_hs_conn_free(struct ble_hs_conn *conn)
|
{
|
#if !NIMBLE_BLE_CONNECT
|
return;
|
#endif
|
|
struct ble_l2cap_chan *chan;
|
struct os_mbuf_pkthdr *omp;
|
int rc;
|
|
if (conn == NULL) {
|
return;
|
}
|
|
ble_att_svr_prep_clear(&conn->bhc_att_svr.basc_prep_list);
|
|
while ((chan = SLIST_FIRST(&conn->bhc_channels)) != NULL) {
|
ble_hs_conn_delete_chan(conn, chan);
|
}
|
|
while ((omp = STAILQ_FIRST(&conn->bhc_tx_q)) != NULL) {
|
STAILQ_REMOVE_HEAD(&conn->bhc_tx_q, omp_next);
|
os_mbuf_free_chain(OS_MBUF_PKTHDR_TO_MBUF(omp));
|
}
|
|
#if MYNEWT_VAL(BLE_HS_DEBUG)
|
memset(conn, 0xff, sizeof *conn);
|
#endif
|
rc = os_memblock_put(&ble_hs_conn_pool, conn);
|
BLE_HS_DBG_ASSERT_EVAL(rc == 0);
|
|
STATS_INC(ble_hs_stats, conn_delete);
|
}
|
|
void
|
ble_hs_conn_insert(struct ble_hs_conn *conn)
|
{
|
#if !NIMBLE_BLE_CONNECT
|
return;
|
#endif
|
|
BLE_HS_DBG_ASSERT(ble_hs_locked_by_cur_task());
|
|
BLE_HS_DBG_ASSERT_EVAL(ble_hs_conn_find(conn->bhc_handle) == NULL);
|
SLIST_INSERT_HEAD(&ble_hs_conns, conn, bhc_next);
|
}
|
|
void
|
ble_hs_conn_remove(struct ble_hs_conn *conn)
|
{
|
#if !NIMBLE_BLE_CONNECT
|
return;
|
#endif
|
|
BLE_HS_DBG_ASSERT(ble_hs_locked_by_cur_task());
|
|
SLIST_REMOVE(&ble_hs_conns, conn, ble_hs_conn, bhc_next);
|
}
|
|
struct ble_hs_conn *
|
ble_hs_conn_find(uint16_t conn_handle)
|
{
|
#if !NIMBLE_BLE_CONNECT
|
return NULL;
|
#endif
|
|
struct ble_hs_conn *conn;
|
|
BLE_HS_DBG_ASSERT(ble_hs_locked_by_cur_task());
|
|
SLIST_FOREACH(conn, &ble_hs_conns, bhc_next) {
|
if (conn->bhc_handle == conn_handle) {
|
return conn;
|
}
|
}
|
|
return NULL;
|
}
|
|
struct ble_hs_conn *
|
ble_hs_conn_find_assert(uint16_t conn_handle)
|
{
|
struct ble_hs_conn *conn;
|
|
conn = ble_hs_conn_find(conn_handle);
|
BLE_HS_DBG_ASSERT(conn != NULL);
|
|
return conn;
|
}
|
|
struct ble_hs_conn *
|
ble_hs_conn_find_by_addr(const ble_addr_t *addr)
|
{
|
#if !NIMBLE_BLE_CONNECT
|
return NULL;
|
#endif
|
|
struct ble_hs_conn *conn;
|
struct ble_hs_conn_addrs addrs;
|
|
BLE_HS_DBG_ASSERT(ble_hs_locked_by_cur_task());
|
|
if (!addr) {
|
return NULL;
|
}
|
|
SLIST_FOREACH(conn, &ble_hs_conns, bhc_next) {
|
if (BLE_ADDR_IS_RPA(addr)) {
|
if (ble_addr_cmp(&conn->bhc_peer_rpa_addr, addr) == 0) {
|
return conn;
|
}
|
} else {
|
if (ble_addr_cmp(&conn->bhc_peer_addr, addr) == 0) {
|
return conn;
|
}
|
if (conn->bhc_peer_addr.type < BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT) {
|
continue;
|
}
|
/*If type 0x02 or 0x03 is used, let's double check if address is good */
|
ble_hs_conn_addrs(conn, &addrs);
|
if (ble_addr_cmp(&addrs.peer_id_addr, addr) == 0) {
|
return conn;
|
}
|
}
|
}
|
|
return NULL;
|
}
|
|
struct ble_hs_conn *
|
ble_hs_conn_find_by_idx(int idx)
|
{
|
#if !NIMBLE_BLE_CONNECT
|
return NULL;
|
#endif
|
|
struct ble_hs_conn *conn;
|
int i;
|
|
BLE_HS_DBG_ASSERT(ble_hs_locked_by_cur_task());
|
|
i = 0;
|
SLIST_FOREACH(conn, &ble_hs_conns, bhc_next) {
|
if (i == idx) {
|
return conn;
|
}
|
|
i++;
|
}
|
|
return NULL;
|
}
|
|
int
|
ble_hs_conn_exists(uint16_t conn_handle)
|
{
|
#if !NIMBLE_BLE_CONNECT
|
return 0;
|
#endif
|
return ble_hs_conn_find(conn_handle) != NULL;
|
}
|
|
/**
|
* Retrieves the first connection in the list.
|
*/
|
struct ble_hs_conn *
|
ble_hs_conn_first(void)
|
{
|
#if !NIMBLE_BLE_CONNECT
|
return NULL;
|
#endif
|
|
BLE_HS_DBG_ASSERT(ble_hs_locked_by_cur_task());
|
return SLIST_FIRST(&ble_hs_conns);
|
}
|
|
void
|
ble_hs_conn_addrs(const struct ble_hs_conn *conn,
|
struct ble_hs_conn_addrs *addrs)
|
{
|
const uint8_t *our_id_addr_val;
|
int rc;
|
|
/* Determine our address information. */
|
addrs->our_id_addr.type =
|
ble_hs_misc_own_addr_type_to_id(conn->bhc_our_addr_type);
|
|
#if MYNEWT_VAL(BLE_EXT_ADV)
|
/* With EA enabled random address for slave connection is per advertising
|
* instance and requires special handling here.
|
*/
|
|
if (!(conn->bhc_flags & BLE_HS_CONN_F_MASTER) &&
|
addrs->our_id_addr.type == BLE_ADDR_RANDOM) {
|
our_id_addr_val = conn->bhc_our_rnd_addr;
|
} else {
|
rc = ble_hs_id_addr(addrs->our_id_addr.type, &our_id_addr_val, NULL);
|
assert(rc == 0);
|
}
|
#else
|
rc = ble_hs_id_addr(addrs->our_id_addr.type, &our_id_addr_val, NULL);
|
assert(rc == 0);
|
#endif
|
|
memcpy(addrs->our_id_addr.val, our_id_addr_val, 6);
|
|
if (memcmp(conn->bhc_our_rpa_addr.val, ble_hs_conn_null_addr, 6) == 0) {
|
addrs->our_ota_addr = addrs->our_id_addr;
|
} else {
|
addrs->our_ota_addr = conn->bhc_our_rpa_addr;
|
}
|
|
/* Determine peer address information. */
|
addrs->peer_id_addr = conn->bhc_peer_addr;
|
addrs->peer_ota_addr = conn->bhc_peer_addr;
|
switch (conn->bhc_peer_addr.type) {
|
case BLE_ADDR_PUBLIC:
|
case BLE_ADDR_RANDOM:
|
break;
|
|
case BLE_ADDR_PUBLIC_ID:
|
addrs->peer_id_addr.type = BLE_ADDR_PUBLIC;
|
addrs->peer_ota_addr = conn->bhc_peer_rpa_addr;
|
break;
|
|
case BLE_ADDR_RANDOM_ID:
|
addrs->peer_id_addr.type = BLE_ADDR_RANDOM;
|
addrs->peer_ota_addr = conn->bhc_peer_rpa_addr;
|
break;
|
|
default:
|
BLE_HS_DBG_ASSERT(0);
|
break;
|
}
|
}
|
|
int32_t
|
ble_hs_conn_timer(void)
|
{
|
/* If there are no timeouts configured, then there is nothing to check. */
|
#if MYNEWT_VAL(BLE_L2CAP_RX_FRAG_TIMEOUT) == 0 && \
|
BLE_HS_ATT_SVR_QUEUED_WRITE_TMO == 0
|
|
return BLE_HS_FOREVER;
|
#endif
|
|
struct ble_hs_conn *conn;
|
ble_npl_time_t now;
|
ble_npl_stime_t next_exp_in;
|
ble_npl_stime_t time_diff;
|
uint16_t conn_handle;
|
|
for (;;) {
|
conn_handle = BLE_HS_CONN_HANDLE_NONE;
|
next_exp_in = BLE_HS_FOREVER;
|
now = ble_npl_time_get();
|
|
ble_hs_lock();
|
|
/* This loop performs one of two tasks:
|
* 1. Determine if any connections need to be terminated due to timeout.
|
* If so, break out of the loop and terminate the connection. This
|
* function will need to be executed again.
|
* 2. Otherwise, determine when the next timeout will occur.
|
*/
|
SLIST_FOREACH(conn, &ble_hs_conns, bhc_next) {
|
if (!(conn->bhc_flags & BLE_HS_CONN_F_TERMINATING)) {
|
|
#if MYNEWT_VAL(BLE_L2CAP_RX_FRAG_TIMEOUT) != 0
|
/* Check each connection's rx fragment timer. If too much time
|
* passes after a partial packet is received, the connection is
|
* terminated.
|
*/
|
if (conn->bhc_rx_chan != NULL) {
|
time_diff = conn->bhc_rx_timeout - now;
|
|
if (time_diff <= 0) {
|
/* ACL reassembly has timed out. Remember the connection
|
* handle so it can be terminated after the mutex is
|
* unlocked.
|
*/
|
conn_handle = conn->bhc_handle;
|
break;
|
}
|
|
/* Determine if this connection is the soonest to time out. */
|
if (time_diff < next_exp_in) {
|
next_exp_in = time_diff;
|
}
|
}
|
#endif
|
|
#if BLE_HS_ATT_SVR_QUEUED_WRITE_TMO
|
/* Check each connection's rx queued write timer. If too much
|
* time passes after a prep write is received, the queue is
|
* cleared.
|
*/
|
time_diff = ble_att_svr_ticks_until_tmo(&conn->bhc_att_svr, now);
|
if (time_diff <= 0) {
|
/* ACL reassembly has timed out. Remember the connection
|
* handle so it can be terminated after the mutex is
|
* unlocked.
|
*/
|
conn_handle = conn->bhc_handle;
|
break;
|
}
|
|
/* Determine if this connection is the soonest to time out. */
|
if (time_diff < next_exp_in) {
|
next_exp_in = time_diff;
|
}
|
#endif
|
}
|
}
|
|
ble_hs_unlock();
|
|
/* If a connection has timed out, terminate it. We need to repeatedly
|
* call this function again to determine when the next timeout is.
|
*/
|
if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
|
ble_gap_terminate(conn_handle, BLE_ERR_REM_USER_CONN_TERM);
|
continue;
|
}
|
|
return next_exp_in;
|
}
|
}
|
|
int
|
ble_hs_conn_init(void)
|
{
|
int rc;
|
|
rc = os_mempool_init(&ble_hs_conn_pool, MYNEWT_VAL(BLE_MAX_CONNECTIONS),
|
sizeof (struct ble_hs_conn),
|
ble_hs_conn_elem_mem, "ble_hs_conn_pool");
|
if (rc != 0) {
|
return BLE_HS_EOS;
|
}
|
|
SLIST_INIT(&ble_hs_conns);
|
|
return 0;
|
}
|