| /* |
| * 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 "syscfg/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). */ |
| #define BLE_HS_CONN_MIN_CHANS 3 |
| |
| 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); |
| STAILQ_INIT(&conn->att_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_time_get(); |
| int32_t next_exp_in = BLE_HS_FOREVER; |
| int32_t next_exp_in_new; |
| bool next_exp_in_updated; |
| int32_t time_diff; |
| |
| ble_hs_lock(); |
| |
| /* This loop performs one of two tasks: |
| * 1. Determine if any connections need to be terminated due to timeout. If |
| * so connection is disconnected. |
| * 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)) { |
| next_exp_in_updated = false; |
| |
| #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.*/ |
| ble_gap_terminate_with_conn(conn, BLE_ERR_REM_USER_CONN_TERM); |
| continue; |
| } |
| |
| /* Determine if this connection is the soonest to time out. */ |
| if (time_diff < next_exp_in) { |
| next_exp_in_new = time_diff; |
| next_exp_in_updated = true; |
| } |
| } |
| #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) { |
| /* Queued write has timed out.*/ |
| ble_gap_terminate_with_conn(conn, BLE_ERR_REM_USER_CONN_TERM); |
| continue; |
| } |
| |
| /* Determine if this connection is the soonest to time out. */ |
| if (time_diff < next_exp_in) { |
| next_exp_in_new = time_diff; |
| next_exp_in_updated = true; |
| } |
| #endif |
| |
| if (next_exp_in_updated) { |
| next_exp_in = next_exp_in_new; |
| } |
| } |
| } |
| |
| ble_hs_unlock(); |
| |
| 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; |
| } |