summaryrefslogtreecommitdiff
path: root/src/libs/mynewt-nimble/nimble/host/src/ble_gap.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/mynewt-nimble/nimble/host/src/ble_gap.c')
-rw-r--r--src/libs/mynewt-nimble/nimble/host/src/ble_gap.c6073
1 files changed, 6073 insertions, 0 deletions
diff --git a/src/libs/mynewt-nimble/nimble/host/src/ble_gap.c b/src/libs/mynewt-nimble/nimble/host/src/ble_gap.c
new file mode 100644
index 00000000..7d1c5252
--- /dev/null
+++ b/src/libs/mynewt-nimble/nimble/host/src/ble_gap.c
@@ -0,0 +1,6073 @@
+/*
+ * 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 <assert.h>
+#include <string.h>
+#include <errno.h>
+#include "nimble/nimble_opt.h"
+#include "host/ble_hs_adv.h"
+#include "host/ble_hs_hci.h"
+#include "ble_hs_priv.h"
+
+#if MYNEWT
+#include "bsp/bsp.h"
+#else
+#define bssnz_t
+#endif
+
+/**
+ * GAP - Generic Access Profile.
+ *
+ * Design overview:
+ *
+ * GAP 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.
+ *
+ * The GAP is always in one of two states:
+ * 1. Free
+ * 2. Preempted
+ *
+ * While GAP is in the free state, new procedures can be started at will.
+ * While GAP is in the preempted state, no new procedures are allowed. The
+ * host sets GAP to the preempted state when it needs to ensure no ongoing
+ * procedures, a condition required for some HCI commands to succeed. The host
+ * must take care to take GAP out of the preempted state as soon as possible.
+ *
+ * Notes on thread-safety:
+ * 1. The ble_hs mutex must always be unlocked when an application callback is
+ * executed. The purpose of this requirement is to allow callbacks to
+ * initiate additional host procedures, which may require locking of the
+ * mutex.
+ * 2. Functions called directly by the application never call callbacks.
+ * Generally, these functions lock the ble_hs mutex at the start, and only
+ * unlock it at return.
+ * 3. Functions which do call callbacks (receive handlers and timer
+ * expirations) generally only lock the mutex long enough to modify
+ * affected state and make copies of data needed for the callback. A copy
+ * of various pieces of data is called a "snapshot" (struct
+ * ble_gap_snapshot). The sole purpose of snapshots is to allow callbacks
+ * to be executed after unlocking the mutex.
+ */
+
+/** GAP procedure op codes. */
+#define BLE_GAP_OP_NULL 0
+#define BLE_GAP_OP_M_DISC 1
+#define BLE_GAP_OP_M_CONN 2
+#define BLE_GAP_OP_S_ADV 1
+#define BLE_GAP_OP_S_PERIODIC_ADV 2
+#define BLE_GAP_OP_SYNC 1
+
+/**
+ * If an attempt to cancel an active procedure fails, the attempt is retried
+ * at this rate (ms).
+ */
+#define BLE_GAP_CANCEL_RETRY_TIMEOUT_MS 100 /* ms */
+
+#define BLE_GAP_UPDATE_TIMEOUT_MS 40000 /* ms */
+
+#if MYNEWT_VAL(BLE_ROLE_CENTRAL)
+static const struct ble_gap_conn_params ble_gap_conn_params_dflt = {
+ .scan_itvl = 0x0010,
+ .scan_window = 0x0010,
+ .itvl_min = BLE_GAP_INITIAL_CONN_ITVL_MIN,
+ .itvl_max = BLE_GAP_INITIAL_CONN_ITVL_MAX,
+ .latency = BLE_GAP_INITIAL_CONN_LATENCY,
+ .supervision_timeout = BLE_GAP_INITIAL_SUPERVISION_TIMEOUT,
+ .min_ce_len = BLE_GAP_INITIAL_CONN_MIN_CE_LEN,
+ .max_ce_len = BLE_GAP_INITIAL_CONN_MAX_CE_LEN,
+};
+#endif
+
+/**
+ * The state of the in-progress master connection. If no master connection is
+ * currently in progress, then the op field is set to BLE_GAP_OP_NULL.
+ */
+struct ble_gap_master_state {
+ uint8_t op;
+
+ uint8_t exp_set:1;
+ ble_npl_time_t exp_os_ticks;
+
+ ble_gap_event_fn *cb;
+ void *cb_arg;
+
+ /**
+ * Indicates the type of master procedure that was preempted, or
+ * BLE_GAP_OP_NULL if no procedure was preempted.
+ */
+ uint8_t preempted_op;
+
+ union {
+ struct {
+ uint8_t using_wl:1;
+ uint8_t our_addr_type:2;
+ uint8_t cancel:1;
+ } conn;
+
+ struct {
+ uint8_t limited:1;
+ } disc;
+ };
+};
+static bssnz_t struct ble_gap_master_state ble_gap_master;
+
+#if MYNEWT_VAL(BLE_PERIODIC_ADV)
+/**
+ * The state of the in-progress sync creation. If no sync creation connection is
+ * currently in progress, then the op field is set to BLE_GAP_OP_NULL.
+ */
+struct ble_gap_sync_state {
+ uint8_t op;
+ struct ble_hs_periodic_sync *psync;
+
+ ble_gap_event_fn *cb;
+ void *cb_arg;
+};
+
+static bssnz_t struct ble_gap_sync_state ble_gap_sync;
+#endif
+
+/**
+ * The state of the in-progress slave connection. If no slave connection is
+ * currently in progress, then the op field is set to BLE_GAP_OP_NULL.
+ */
+struct ble_gap_slave_state {
+ uint8_t op;
+
+ unsigned int our_addr_type:2;
+ unsigned int preempted:1; /** Set to 1 if advertising was preempted. */
+ unsigned int connectable:1;
+
+#if MYNEWT_VAL(BLE_EXT_ADV)
+ unsigned int configured:1; /** If instance is configured */
+ unsigned int scannable:1;
+ unsigned int directed:1;
+ unsigned int high_duty_directed:1;
+ unsigned int legacy_pdu:1;
+ unsigned int rnd_addr_set:1;
+#if MYNEWT_VAL(BLE_PERIODIC_ADV)
+ unsigned int periodic_configured:1;
+ uint8_t periodic_op;
+#endif
+ uint8_t rnd_addr[6];
+#else
+/* timer is used only with legacy advertising */
+ unsigned int exp_set:1;
+ ble_npl_time_t exp_os_ticks;
+#endif
+
+ ble_gap_event_fn *cb;
+ void *cb_arg;
+};
+
+static bssnz_t struct ble_gap_slave_state ble_gap_slave[BLE_ADV_INSTANCES];
+
+struct ble_gap_update_entry {
+ SLIST_ENTRY(ble_gap_update_entry) next;
+ struct ble_gap_upd_params params;
+ ble_npl_time_t exp_os_ticks;
+ uint16_t conn_handle;
+};
+SLIST_HEAD(ble_gap_update_entry_list, ble_gap_update_entry);
+
+struct ble_gap_snapshot {
+ struct ble_gap_conn_desc *desc;
+ ble_gap_event_fn *cb;
+ void *cb_arg;
+};
+
+static SLIST_HEAD(ble_gap_hook_list, ble_gap_event_listener) ble_gap_event_listener_list;
+static os_membuf_t ble_gap_update_entry_mem[
+ OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_GAP_MAX_PENDING_CONN_PARAM_UPDATE),
+ sizeof (struct ble_gap_update_entry))];
+static struct os_mempool ble_gap_update_entry_pool;
+static struct ble_gap_update_entry_list ble_gap_update_entries;
+
+static void ble_gap_update_entry_free(struct ble_gap_update_entry *entry);
+
+#if NIMBLE_BLE_CONNECT
+static struct ble_gap_update_entry *
+ble_gap_update_entry_find(uint16_t conn_handle,
+ struct ble_gap_update_entry **out_prev);
+
+static void
+ble_gap_update_l2cap_cb(uint16_t conn_handle, int status, void *arg);
+#endif
+
+static struct ble_gap_update_entry *
+ble_gap_update_entry_remove(uint16_t conn_handle);
+
+#if NIMBLE_BLE_ADVERTISE && !MYNEWT_VAL(BLE_EXT_ADV)
+static int ble_gap_adv_enable_tx(int enable);
+#endif
+
+static int ble_gap_conn_cancel_tx(void);
+
+#if NIMBLE_BLE_SCAN && !MYNEWT_VAL(BLE_EXT_ADV)
+static int ble_gap_disc_enable_tx(int enable, int filter_duplicates);
+#endif
+
+STATS_SECT_DECL(ble_gap_stats) ble_gap_stats;
+STATS_NAME_START(ble_gap_stats)
+ STATS_NAME(ble_gap_stats, wl_set)
+ STATS_NAME(ble_gap_stats, wl_set_fail)
+ STATS_NAME(ble_gap_stats, adv_stop)
+ STATS_NAME(ble_gap_stats, adv_stop_fail)
+ STATS_NAME(ble_gap_stats, adv_start)
+ STATS_NAME(ble_gap_stats, adv_start_fail)
+ STATS_NAME(ble_gap_stats, adv_set_data)
+ STATS_NAME(ble_gap_stats, adv_set_data_fail)
+ STATS_NAME(ble_gap_stats, adv_rsp_set_data)
+ STATS_NAME(ble_gap_stats, adv_rsp_set_data_fail)
+ STATS_NAME(ble_gap_stats, discover)
+ STATS_NAME(ble_gap_stats, discover_fail)
+ STATS_NAME(ble_gap_stats, initiate)
+ STATS_NAME(ble_gap_stats, initiate_fail)
+ STATS_NAME(ble_gap_stats, terminate)
+ STATS_NAME(ble_gap_stats, terminate_fail)
+ STATS_NAME(ble_gap_stats, cancel)
+ STATS_NAME(ble_gap_stats, cancel_fail)
+ STATS_NAME(ble_gap_stats, update)
+ STATS_NAME(ble_gap_stats, update_fail)
+ STATS_NAME(ble_gap_stats, connect_mst)
+ STATS_NAME(ble_gap_stats, connect_slv)
+ STATS_NAME(ble_gap_stats, disconnect)
+ STATS_NAME(ble_gap_stats, rx_disconnect)
+ STATS_NAME(ble_gap_stats, rx_update_complete)
+ STATS_NAME(ble_gap_stats, rx_adv_report)
+ STATS_NAME(ble_gap_stats, rx_conn_complete)
+ STATS_NAME(ble_gap_stats, discover_cancel)
+ STATS_NAME(ble_gap_stats, discover_cancel_fail)
+ STATS_NAME(ble_gap_stats, security_initiate)
+ STATS_NAME(ble_gap_stats, security_initiate_fail)
+STATS_NAME_END(ble_gap_stats)
+
+/*****************************************************************************
+ * $debug *
+ *****************************************************************************/
+
+#if MYNEWT_VAL(BLE_HS_DEBUG)
+int
+ble_gap_dbg_update_active(uint16_t conn_handle)
+{
+ const struct ble_gap_update_entry *entry;
+
+ ble_hs_lock();
+ entry = ble_gap_update_entry_find(conn_handle, NULL);
+ ble_hs_unlock();
+
+ return entry != NULL;
+}
+#endif
+
+/*****************************************************************************
+ * $log *
+ *****************************************************************************/
+
+#if NIMBLE_BLE_SCAN && !MYNEWT_VAL(BLE_EXT_ADV)
+static void
+ble_gap_log_duration(int32_t duration_ms)
+{
+ if (duration_ms == BLE_HS_FOREVER) {
+ BLE_HS_LOG(INFO, "duration=forever");
+ } else {
+ BLE_HS_LOG(INFO, "duration=%dms", duration_ms);
+ }
+}
+#endif
+
+#if MYNEWT_VAL(BLE_ROLE_CENTRAL) && !MYNEWT_VAL(BLE_EXT_ADV)
+static void
+ble_gap_log_conn(uint8_t own_addr_type, const ble_addr_t *peer_addr,
+ const struct ble_gap_conn_params *params)
+{
+ if (peer_addr != NULL) {
+ BLE_HS_LOG(INFO, "peer_addr_type=%d peer_addr=", peer_addr->type);
+ BLE_HS_LOG_ADDR(INFO, peer_addr->val);
+ }
+
+ BLE_HS_LOG(INFO, " scan_itvl=%d scan_window=%d itvl_min=%d itvl_max=%d "
+ "latency=%d supervision_timeout=%d min_ce_len=%d "
+ "max_ce_len=%d own_addr_type=%d",
+ params->scan_itvl, params->scan_window, params->itvl_min,
+ params->itvl_max, params->latency, params->supervision_timeout,
+ params->min_ce_len, params->max_ce_len, own_addr_type);
+}
+#endif
+
+#if NIMBLE_BLE_SCAN && !MYNEWT_VAL(BLE_EXT_ADV)
+static void
+ble_gap_log_disc(uint8_t own_addr_type, int32_t duration_ms,
+ const struct ble_gap_disc_params *disc_params)
+{
+ BLE_HS_LOG(INFO, "own_addr_type=%d filter_policy=%d passive=%d limited=%d "
+ "filter_duplicates=%d ",
+ own_addr_type, disc_params->filter_policy, disc_params->passive,
+ disc_params->limited, disc_params->filter_duplicates);
+ ble_gap_log_duration(duration_ms);
+}
+#endif
+
+#if NIMBLE_BLE_CONNECT
+static void
+ble_gap_log_update(uint16_t conn_handle,
+ const struct ble_gap_upd_params *params)
+{
+ BLE_HS_LOG(INFO, "connection parameter update; "
+ "conn_handle=%d itvl_min=%d itvl_max=%d latency=%d "
+ "supervision_timeout=%d min_ce_len=%d max_ce_len=???",
+ conn_handle, params->itvl_min, params->itvl_max,
+ params->latency, params->supervision_timeout,
+ params->min_ce_len);
+}
+#endif
+
+#if MYNEWT_VAL(BLE_WHITELIST)
+static void
+ble_gap_log_wl(const ble_addr_t *addr, uint8_t white_list_count)
+{
+ int i;
+
+ BLE_HS_LOG(INFO, "count=%d ", white_list_count);
+
+ for (i = 0; i < white_list_count; i++, addr++) {
+ BLE_HS_LOG(INFO, "entry-%d={addr_type=%d addr=", i, addr->type);
+ BLE_HS_LOG_ADDR(INFO, addr->val);
+ BLE_HS_LOG(INFO, "} ");
+ }
+}
+#endif
+
+#if NIMBLE_BLE_ADVERTISE && !MYNEWT_VAL(BLE_EXT_ADV)
+static void
+ble_gap_log_adv(uint8_t own_addr_type, const ble_addr_t *direct_addr,
+ const struct ble_gap_adv_params *adv_params)
+{
+ BLE_HS_LOG(INFO, "disc_mode=%d", adv_params->disc_mode);
+ if (direct_addr) {
+ BLE_HS_LOG(INFO, " direct_addr_type=%d direct_addr=",
+ direct_addr->type);
+ BLE_HS_LOG_ADDR(INFO, direct_addr->val);
+ }
+ BLE_HS_LOG(INFO, " adv_channel_map=%d own_addr_type=%d "
+ "adv_filter_policy=%d adv_itvl_min=%d adv_itvl_max=%d",
+ adv_params->channel_map,
+ own_addr_type,
+ adv_params->filter_policy,
+ adv_params->itvl_min,
+ adv_params->itvl_max);
+}
+#endif
+
+/*****************************************************************************
+ * $snapshot *
+ *****************************************************************************/
+
+static void
+ble_gap_fill_conn_desc(struct ble_hs_conn *conn,
+ struct ble_gap_conn_desc *desc)
+{
+ struct ble_hs_conn_addrs addrs;
+
+ ble_hs_conn_addrs(conn, &addrs);
+
+ desc->our_id_addr = addrs.our_id_addr;
+ desc->peer_id_addr = addrs.peer_id_addr;
+ desc->our_ota_addr = addrs.our_ota_addr;
+ desc->peer_ota_addr = addrs.peer_ota_addr;
+
+ desc->conn_handle = conn->bhc_handle;
+ desc->conn_itvl = conn->bhc_itvl;
+ desc->conn_latency = conn->bhc_latency;
+ desc->supervision_timeout = conn->bhc_supervision_timeout;
+ desc->master_clock_accuracy = conn->bhc_master_clock_accuracy;
+ desc->sec_state = conn->bhc_sec_state;
+
+ if (conn->bhc_flags & BLE_HS_CONN_F_MASTER) {
+ desc->role = BLE_GAP_ROLE_MASTER;
+ } else {
+ desc->role = BLE_GAP_ROLE_SLAVE;
+ }
+}
+
+static void
+ble_gap_conn_to_snapshot(struct ble_hs_conn *conn,
+ struct ble_gap_snapshot *snap)
+{
+ ble_gap_fill_conn_desc(conn, snap->desc);
+ snap->cb = conn->bhc_cb;
+ snap->cb_arg = conn->bhc_cb_arg;
+}
+
+static int
+ble_gap_find_snapshot(uint16_t handle, struct ble_gap_snapshot *snap)
+{
+ struct ble_hs_conn *conn;
+
+ ble_hs_lock();
+
+ conn = ble_hs_conn_find(handle);
+ if (conn != NULL) {
+ ble_gap_conn_to_snapshot(conn, snap);
+ }
+
+ ble_hs_unlock();
+
+ if (conn == NULL) {
+ return BLE_HS_ENOTCONN;
+ } else {
+ return 0;
+ }
+}
+
+int
+ble_gap_conn_find(uint16_t handle, struct ble_gap_conn_desc *out_desc)
+{
+ struct ble_hs_conn *conn;
+
+ ble_hs_lock();
+
+ conn = ble_hs_conn_find(handle);
+ if (conn != NULL && out_desc != NULL) {
+ ble_gap_fill_conn_desc(conn, out_desc);
+ }
+
+ ble_hs_unlock();
+
+ if (conn == NULL) {
+ return BLE_HS_ENOTCONN;
+ } else {
+ return 0;
+ }
+}
+
+int
+ble_gap_conn_find_by_addr(const ble_addr_t *addr,
+ struct ble_gap_conn_desc *out_desc)
+{
+ struct ble_hs_conn *conn;
+
+ ble_hs_lock();
+
+ conn = ble_hs_conn_find_by_addr(addr);
+ if (conn != NULL && out_desc != NULL) {
+ ble_gap_fill_conn_desc(conn, out_desc);
+ }
+
+ ble_hs_unlock();
+
+ if (conn == NULL) {
+ return BLE_HS_ENOTCONN;
+ }
+
+ return 0;
+}
+
+static int
+ble_gap_extract_conn_cb(uint16_t conn_handle,
+ ble_gap_event_fn **out_cb, void **out_cb_arg)
+{
+ const struct ble_hs_conn *conn;
+
+ BLE_HS_DBG_ASSERT(conn_handle <= BLE_HCI_LE_CONN_HANDLE_MAX);
+
+ ble_hs_lock();
+
+ conn = ble_hs_conn_find(conn_handle);
+ if (conn != NULL) {
+ *out_cb = conn->bhc_cb;
+ *out_cb_arg = conn->bhc_cb_arg;
+ } else {
+ *out_cb = NULL;
+ *out_cb_arg = NULL;
+ }
+
+ ble_hs_unlock();
+
+ if (conn == NULL) {
+ return BLE_HS_ENOTCONN;
+ } else {
+ return 0;
+ }
+}
+
+int
+ble_gap_set_priv_mode(const ble_addr_t *peer_addr, uint8_t priv_mode)
+{
+ return ble_hs_pvcy_set_mode(peer_addr, priv_mode);
+}
+
+int
+ble_gap_read_le_phy(uint16_t conn_handle, uint8_t *tx_phy, uint8_t *rx_phy)
+{
+ struct ble_hci_le_rd_phy_cp cmd;
+ struct ble_hci_le_rd_phy_rp rsp;
+ struct ble_hs_conn *conn;
+ int rc;
+
+ ble_hs_lock();
+ conn = ble_hs_conn_find(conn_handle);
+ ble_hs_unlock();
+
+ if (conn == NULL) {
+ return BLE_HS_ENOTCONN;
+ }
+
+ cmd.conn_handle = htole16(conn_handle);
+
+ rc = ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_RD_PHY),
+ &cmd, sizeof(cmd), &rsp, sizeof(rsp));
+ if (rc != 0) {
+ return rc;
+ }
+
+ /* sanity check for response */
+ if (le16toh(rsp.conn_handle) != conn_handle) {
+ return BLE_HS_ECONTROLLER;
+ }
+
+ *tx_phy = rsp.tx_phy;
+ *rx_phy = rsp.rx_phy;
+
+ return 0;
+}
+
+int
+ble_gap_set_prefered_default_le_phy(uint8_t tx_phys_mask, uint8_t rx_phys_mask)
+{
+ struct ble_hci_le_set_default_phy_cp cmd;
+
+ if (tx_phys_mask > (BLE_HCI_LE_PHY_1M_PREF_MASK |
+ BLE_HCI_LE_PHY_2M_PREF_MASK |
+ BLE_HCI_LE_PHY_CODED_PREF_MASK)) {
+ return BLE_ERR_INV_HCI_CMD_PARMS;
+ }
+
+ if (rx_phys_mask > (BLE_HCI_LE_PHY_1M_PREF_MASK |
+ BLE_HCI_LE_PHY_2M_PREF_MASK |
+ BLE_HCI_LE_PHY_CODED_PREF_MASK)) {
+ return BLE_ERR_INV_HCI_CMD_PARMS;
+ }
+
+ memset(&cmd, 0, sizeof(cmd));
+
+ if (tx_phys_mask == 0) {
+ cmd.all_phys |= BLE_HCI_LE_PHY_NO_TX_PREF_MASK;
+ } else {
+ cmd.tx_phys = tx_phys_mask;
+ }
+
+ if (rx_phys_mask == 0) {
+ cmd.all_phys |= BLE_HCI_LE_PHY_NO_RX_PREF_MASK;
+ } else {
+ cmd.rx_phys = rx_phys_mask;
+ }
+
+ return ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_LE,
+ BLE_HCI_OCF_LE_SET_DEFAULT_PHY),
+ &cmd, sizeof(cmd), NULL, 0);
+}
+
+int
+ble_gap_set_prefered_le_phy(uint16_t conn_handle, uint8_t tx_phys_mask,
+ uint8_t rx_phys_mask, uint16_t phy_opts)
+{
+ struct ble_hci_le_set_phy_cp cmd;
+ struct ble_hs_conn *conn;
+
+ ble_hs_lock();
+ conn = ble_hs_conn_find(conn_handle);
+ ble_hs_unlock();
+
+ if (conn == NULL) {
+ return BLE_HS_ENOTCONN;
+ }
+
+ if (tx_phys_mask > (BLE_HCI_LE_PHY_1M_PREF_MASK |
+ BLE_HCI_LE_PHY_2M_PREF_MASK |
+ BLE_HCI_LE_PHY_CODED_PREF_MASK)) {
+ return BLE_ERR_INV_HCI_CMD_PARMS;
+ }
+
+ if (rx_phys_mask > (BLE_HCI_LE_PHY_1M_PREF_MASK |
+ BLE_HCI_LE_PHY_2M_PREF_MASK |
+ BLE_HCI_LE_PHY_CODED_PREF_MASK)) {
+ return BLE_ERR_INV_HCI_CMD_PARMS;
+ }
+
+ if (phy_opts > BLE_HCI_LE_PHY_CODED_S8_PREF) {
+ return BLE_ERR_INV_HCI_CMD_PARMS;
+ }
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.conn_handle = htole16(conn_handle);
+
+ if (tx_phys_mask == 0) {
+ cmd.all_phys |= BLE_HCI_LE_PHY_NO_TX_PREF_MASK;
+ } else {
+ cmd.tx_phys = tx_phys_mask;
+ }
+
+ if (rx_phys_mask == 0) {
+ cmd.all_phys |= BLE_HCI_LE_PHY_NO_RX_PREF_MASK;
+ } else {
+ cmd.rx_phys = rx_phys_mask;
+ }
+
+ cmd.phy_options = htole16(phy_opts);
+
+ return ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_SET_PHY),
+ &cmd, sizeof(cmd), NULL, 0);
+}
+
+/*****************************************************************************
+ * $misc *
+ *****************************************************************************/
+
+static int
+ble_gap_event_listener_call(struct ble_gap_event *event);
+
+static int
+ble_gap_call_event_cb(struct ble_gap_event *event,
+ ble_gap_event_fn *cb, void *cb_arg)
+{
+ int rc;
+
+ BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());
+
+ if (cb != NULL) {
+ rc = cb(event, cb_arg);
+ } else {
+ if (event->type == BLE_GAP_EVENT_CONN_UPDATE_REQ) {
+ /* Just copy peer parameters back into the reply. */
+ *event->conn_update_req.self_params =
+ *event->conn_update_req.peer_params;
+ }
+ rc = 0;
+ }
+
+ return rc;
+}
+
+
+static int
+ble_gap_call_conn_event_cb(struct ble_gap_event *event, uint16_t conn_handle)
+{
+ ble_gap_event_fn *cb;
+ void *cb_arg;
+ int rc;
+
+ rc = ble_gap_extract_conn_cb(conn_handle, &cb, &cb_arg);
+ if (rc != 0) {
+ return rc;
+ }
+
+ rc = ble_gap_call_event_cb(event, cb, cb_arg);
+ if (rc != 0) {
+ return rc;
+ }
+
+ return 0;
+}
+
+static bool
+ble_gap_is_preempted(void)
+{
+ int i;
+
+ BLE_HS_DBG_ASSERT(ble_hs_locked_by_cur_task());
+
+ if (ble_gap_master.preempted_op != BLE_GAP_OP_NULL) {
+ return true;
+ }
+
+ for (i = 0; i < BLE_ADV_INSTANCES; i++) {
+ if (ble_gap_slave[i].preempted) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+#if NIMBLE_BLE_CONNECT
+static void
+ble_gap_master_reset_state(void)
+{
+ ble_gap_master.op = BLE_GAP_OP_NULL;
+ ble_gap_master.exp_set = 0;
+ ble_gap_master.conn.cancel = 0;
+
+ ble_hs_timer_resched();
+}
+#endif
+
+static void
+ble_gap_slave_reset_state(uint8_t instance)
+{
+ ble_gap_slave[instance].op = BLE_GAP_OP_NULL;
+
+#if !MYNEWT_VAL(BLE_EXT_ADV)
+ ble_gap_slave[instance].exp_set = 0;
+ ble_hs_timer_resched();
+#endif
+}
+
+#if NIMBLE_BLE_CONNECT
+static bool
+ble_gap_has_client(struct ble_gap_master_state *out_state)
+{
+ if (!out_state) {
+ return 0;
+ }
+
+ return out_state->cb;
+}
+
+static void
+ble_gap_master_extract_state(struct ble_gap_master_state *out_state,
+ int reset_state)
+{
+ ble_hs_lock();
+
+ *out_state = ble_gap_master;
+
+ if (reset_state) {
+ ble_gap_master_reset_state();
+ ble_gap_master.preempted_op = BLE_GAP_OP_NULL;
+ }
+
+ ble_hs_unlock();
+}
+#endif
+
+static void
+ble_gap_slave_extract_cb(uint8_t instance,
+ ble_gap_event_fn **out_cb, void **out_cb_arg)
+{
+ ble_hs_lock();
+
+ *out_cb = ble_gap_slave[instance].cb;
+ *out_cb_arg = ble_gap_slave[instance].cb_arg;
+ ble_gap_slave_reset_state(instance);
+
+ ble_hs_unlock();
+}
+
+static void
+ble_gap_adv_finished(uint8_t instance, int reason, uint16_t conn_handle,
+ uint8_t num_events)
+{
+ struct ble_gap_event event;
+ ble_gap_event_fn *cb;
+ void *cb_arg;
+
+ memset(&event, 0, sizeof event);
+ event.type = BLE_GAP_EVENT_ADV_COMPLETE;
+ event.adv_complete.reason = reason;
+#if MYNEWT_VAL(BLE_EXT_ADV)
+ event.adv_complete.instance = instance;
+ event.adv_complete.conn_handle = conn_handle;
+ event.adv_complete.num_ext_adv_events = num_events;
+#endif
+
+ ble_gap_event_listener_call(&event);
+
+ ble_gap_slave_extract_cb(instance, &cb, &cb_arg);
+ if (cb != NULL) {
+ cb(&event, cb_arg);
+ }
+}
+
+#if NIMBLE_BLE_CONNECT
+static int
+ble_gap_master_connect_failure(int status)
+{
+ struct ble_gap_master_state state;
+ struct ble_gap_event event;
+ int rc;
+
+ ble_gap_master_extract_state(&state, 1);
+ if (ble_gap_has_client(&state)) {
+ memset(&event, 0, sizeof event);
+ event.type = BLE_GAP_EVENT_CONNECT;
+ event.connect.status = status;
+
+ rc = state.cb(&event, state.cb_arg);
+ } else {
+ rc = 0;
+ }
+
+ return rc;
+}
+
+static void
+ble_gap_master_connect_cancelled(void)
+{
+ struct ble_gap_master_state state;
+ struct ble_gap_event event;
+
+ ble_gap_master_extract_state(&state, 1);
+ if (state.cb != NULL) {
+ memset(&event, 0, sizeof event);
+ event.type = BLE_GAP_EVENT_CONNECT;
+ event.connect.conn_handle = BLE_HS_CONN_HANDLE_NONE;
+ if (state.conn.cancel) {
+ /* Connect procedure successfully cancelled. */
+ event.connect.status = BLE_HS_EAPP;
+ } else {
+ /* Connect procedure timed out. */
+ event.connect.status = BLE_HS_ETIMEOUT;
+ }
+ state.cb(&event, state.cb_arg);
+ }
+}
+#endif
+
+#if NIMBLE_BLE_SCAN
+static void
+ble_gap_disc_report(void *desc)
+{
+ struct ble_gap_master_state state;
+ struct ble_gap_event event;
+
+ memset(&event, 0, sizeof event);
+#if MYNEWT_VAL(BLE_EXT_ADV)
+ event.type = BLE_GAP_EVENT_EXT_DISC;
+ event.ext_disc = *((struct ble_gap_ext_disc_desc *)desc);
+#else
+ event.type = BLE_GAP_EVENT_DISC;
+ event.disc = *((struct ble_gap_disc_desc *)desc);
+#endif
+
+ ble_gap_master_extract_state(&state, 0);
+ if (ble_gap_has_client(&state)) {
+ state.cb(&event, state.cb_arg);
+ }
+
+ ble_gap_event_listener_call(&event);
+}
+
+static void
+ble_gap_disc_complete(void)
+{
+ struct ble_gap_master_state state;
+ struct ble_gap_event event;
+
+ memset(&event, 0, sizeof event);
+ event.type = BLE_GAP_EVENT_DISC_COMPLETE;
+ event.disc_complete.reason = 0;
+
+ ble_gap_master_extract_state(&state, 1);
+ if (ble_gap_has_client(&state)) {
+ ble_gap_call_event_cb(&event, state.cb, state.cb_arg);
+ }
+
+ ble_gap_event_listener_call(&event);
+}
+#endif
+
+static void
+ble_gap_update_notify(uint16_t conn_handle, int status)
+{
+ struct ble_gap_event event;
+
+ memset(&event, 0, sizeof event);
+ event.type = BLE_GAP_EVENT_CONN_UPDATE;
+ event.conn_update.conn_handle = conn_handle;
+ event.conn_update.status = status;
+
+ ble_gap_event_listener_call(&event);
+ ble_gap_call_conn_event_cb(&event, conn_handle);
+
+ /* Terminate the connection on procedure timeout. */
+ if (status == BLE_HS_ETIMEOUT) {
+ ble_gap_terminate(conn_handle, BLE_ERR_REM_USER_CONN_TERM);
+ }
+}
+
+static uint32_t
+ble_gap_master_ticks_until_exp(void)
+{
+ ble_npl_stime_t ticks;
+
+ if (ble_gap_master.op == BLE_GAP_OP_NULL || !ble_gap_master.exp_set) {
+ /* Timer not set; infinity ticks until next event. */
+ return BLE_HS_FOREVER;
+ }
+
+ ticks = ble_gap_master.exp_os_ticks - ble_npl_time_get();
+ if (ticks > 0) {
+ /* Timer not expired yet. */
+ return ticks;
+ }
+
+ /* Timer just expired. */
+ return 0;
+}
+
+#if !MYNEWT_VAL(BLE_EXT_ADV)
+static uint32_t
+ble_gap_slave_ticks_until_exp(void)
+{
+ ble_npl_stime_t ticks;
+
+ if (ble_gap_slave[0].op == BLE_GAP_OP_NULL || !ble_gap_slave[0].exp_set) {
+ /* Timer not set; infinity ticks until next event. */
+ return BLE_HS_FOREVER;
+ }
+
+ ticks = ble_gap_slave[0].exp_os_ticks - ble_npl_time_get();
+ if (ticks > 0) {
+ /* Timer not expired yet. */
+ return ticks;
+ }
+
+ /* Timer just expired. */
+ return 0;
+}
+#endif
+
+/**
+ * Finds the update procedure that expires soonest.
+ *
+ * @param out_ticks_from_now On success, the ticks until the update
+ * procedure's expiry time gets written here.
+ *
+ * @return The connection handle of the update procedure
+ * that expires soonest, or
+ * BLE_HS_CONN_HANDLE_NONE if there are no
+ * active update procedures.
+ */
+static uint16_t
+ble_gap_update_next_exp(int32_t *out_ticks_from_now)
+{
+ struct ble_gap_update_entry *entry;
+ ble_npl_time_t now;
+ uint16_t conn_handle;
+ int32_t best_ticks;
+ int32_t ticks;
+
+ BLE_HS_DBG_ASSERT(ble_hs_locked_by_cur_task());
+
+ conn_handle = BLE_HS_CONN_HANDLE_NONE;
+ best_ticks = BLE_HS_FOREVER;
+ now = ble_npl_time_get();
+
+ SLIST_FOREACH(entry, &ble_gap_update_entries, next) {
+ ticks = entry->exp_os_ticks - now;
+ if (ticks <= 0) {
+ ticks = 0;
+ }
+
+ if (ticks < best_ticks) {
+ conn_handle = entry->conn_handle;
+ best_ticks = ticks;
+ }
+ }
+
+ if (out_ticks_from_now != NULL) {
+ *out_ticks_from_now = best_ticks;
+ }
+
+ return conn_handle;
+
+}
+
+#if MYNEWT_VAL(BLE_ROLE_CENTRAL)
+static void
+ble_gap_master_set_timer(uint32_t ticks_from_now)
+{
+ ble_gap_master.exp_os_ticks = ble_npl_time_get() + ticks_from_now;
+ ble_gap_master.exp_set = 1;
+
+ ble_hs_timer_resched();
+}
+#endif
+
+#if NIMBLE_BLE_ADVERTISE && !MYNEWT_VAL(BLE_EXT_ADV)
+static void
+ble_gap_slave_set_timer(uint32_t ticks_from_now)
+{
+ ble_gap_slave[0].exp_os_ticks = ble_npl_time_get() + ticks_from_now;
+ ble_gap_slave[0].exp_set = 1;
+
+ ble_hs_timer_resched();
+}
+#endif
+
+#if (NIMBLE_BLE_CONNECT || NIMBLE_BLE_SCAN)
+/**
+ * Called when an error is encountered while the master-connection-fsm is
+ * active.
+ */
+static void
+ble_gap_master_failed(int status)
+{
+ switch (ble_gap_master.op) {
+ case BLE_GAP_OP_M_CONN:
+ STATS_INC(ble_gap_stats, initiate_fail);
+ ble_gap_master_connect_failure(status);
+ break;
+
+#if NIMBLE_BLE_SCAN
+ case BLE_GAP_OP_M_DISC:
+ STATS_INC(ble_gap_stats, initiate_fail);
+ ble_gap_disc_complete();
+ ble_gap_master_reset_state();
+ break;
+#endif
+
+ default:
+ BLE_HS_DBG_ASSERT(0);
+ break;
+ }
+}
+#endif
+
+#if NIMBLE_BLE_CONNECT
+static void
+ble_gap_update_failed(uint16_t conn_handle, int status)
+{
+ struct ble_gap_update_entry *entry;
+
+ STATS_INC(ble_gap_stats, update_fail);
+
+ ble_hs_lock();
+ entry = ble_gap_update_entry_remove(conn_handle);
+ ble_hs_unlock();
+
+ ble_gap_update_entry_free(entry);
+
+ ble_gap_update_notify(conn_handle, status);
+}
+#endif
+
+void
+ble_gap_conn_broken(uint16_t conn_handle, int reason)
+{
+ struct ble_gap_update_entry *entry;
+ struct ble_gap_snapshot snap;
+ struct ble_gap_event event;
+ int rc;
+
+ memset(&event, 0, sizeof event);
+ snap.desc = &event.disconnect.conn;
+
+ rc = ble_gap_find_snapshot(conn_handle, &snap);
+ if (rc != 0) {
+ /* No longer connected. */
+ return;
+ }
+
+ /* If there was a connection update in progress, indicate to the
+ * application that it did not complete.
+ */
+ ble_hs_lock();
+ entry = ble_gap_update_entry_remove(conn_handle);
+ ble_hs_unlock();
+
+ if (entry != NULL) {
+ ble_gap_update_notify(conn_handle, reason);
+ ble_gap_update_entry_free(entry);
+ }
+
+ /* Indicate the connection termination to each module. The order matters
+ * here: gatts must come before gattc to ensure the application does not
+ * get informed of spurious notify-tx events.
+ */
+ ble_l2cap_sig_conn_broken(conn_handle, reason);
+ ble_sm_connection_broken(conn_handle);
+ ble_gatts_connection_broken(conn_handle);
+ ble_gattc_connection_broken(conn_handle);
+ ble_hs_flow_connection_broken(conn_handle);;
+
+ ble_hs_atomic_conn_delete(conn_handle);
+
+ event.type = BLE_GAP_EVENT_DISCONNECT;
+ event.disconnect.reason = reason;
+
+ ble_gap_event_listener_call(&event);
+ ble_gap_call_event_cb(&event, snap.cb, snap.cb_arg);
+
+ STATS_INC(ble_gap_stats, disconnect);
+}
+
+#if NIMBLE_BLE_CONNECT
+static void
+ble_gap_update_to_l2cap(const struct ble_gap_upd_params *params,
+ struct ble_l2cap_sig_update_params *l2cap_params)
+{
+ l2cap_params->itvl_min = params->itvl_min;
+ l2cap_params->itvl_max = params->itvl_max;
+ l2cap_params->slave_latency = params->latency;
+ l2cap_params->timeout_multiplier = params->supervision_timeout;
+}
+#endif
+
+void
+ble_gap_rx_disconn_complete(const struct ble_hci_ev_disconn_cmp *ev)
+{
+#if NIMBLE_BLE_CONNECT
+ struct ble_gap_event event;
+ uint16_t handle = le16toh(ev->conn_handle);
+
+ STATS_INC(ble_gap_stats, rx_disconnect);
+
+ if (ev->status == 0) {
+ ble_gap_conn_broken(handle, BLE_HS_HCI_ERR(ev->reason));
+ } else {
+ memset(&event, 0, sizeof event);
+ event.type = BLE_GAP_EVENT_TERM_FAILURE;
+ event.term_failure.conn_handle = handle;
+ event.term_failure.status = BLE_HS_HCI_ERR(ev->status);
+
+ ble_gap_event_listener_call(&event);
+ ble_gap_call_conn_event_cb(&event, handle);
+ }
+#endif
+}
+
+void
+ble_gap_rx_update_complete(const struct ble_hci_ev_le_subev_conn_upd_complete *ev)
+{
+#if NIMBLE_BLE_CONNECT
+ struct ble_gap_update_entry *entry;
+ struct ble_l2cap_sig_update_params l2cap_params;
+ struct ble_gap_event event;
+ struct ble_hs_conn *conn;
+ uint16_t conn_handle;
+ int cb_status;
+ int call_cb;
+ int rc;
+
+ STATS_INC(ble_gap_stats, rx_update_complete);
+
+ memset(&event, 0, sizeof event);
+ memset(&l2cap_params, 0, sizeof l2cap_params);
+
+ ble_hs_lock();
+
+ conn_handle = le16toh(ev->conn_handle);
+
+ conn = ble_hs_conn_find(conn_handle);
+ if (conn != NULL) {
+ switch (ev->status) {
+ case 0:
+ /* Connection successfully updated. */
+ conn->bhc_itvl = le16toh(ev->conn_itvl);
+ conn->bhc_latency = le16toh(ev->conn_latency);
+ conn->bhc_supervision_timeout = le16toh(ev->supervision_timeout);
+ break;
+
+ case BLE_ERR_UNSUPP_REM_FEATURE:
+ /* Peer reports that it doesn't support the procedure. This should
+ * only happen if our controller sent the 4.1 Connection Parameters
+ * Request Procedure. If we are the slave, fail over to the L2CAP
+ * update procedure.
+ */
+ entry = ble_gap_update_entry_find(conn_handle, NULL);
+ if (entry != NULL && !(conn->bhc_flags & BLE_HS_CONN_F_MASTER)) {
+ ble_gap_update_to_l2cap(&entry->params, &l2cap_params);
+ entry->exp_os_ticks = ble_npl_time_get() +
+ ble_npl_time_ms_to_ticks32(BLE_GAP_UPDATE_TIMEOUT_MS);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* We aren't failing over to L2CAP, the update procedure is complete. */
+ if (l2cap_params.itvl_min == 0) {
+ entry = ble_gap_update_entry_remove(conn_handle);
+ ble_gap_update_entry_free(entry);
+ }
+
+ ble_hs_unlock();
+
+ if (l2cap_params.itvl_min != 0) {
+ rc = ble_l2cap_sig_update(conn_handle, &l2cap_params,
+ ble_gap_update_l2cap_cb, NULL);
+ if (rc == 0) {
+ call_cb = 0;
+ } else {
+ call_cb = 1;
+ cb_status = rc;
+ }
+ } else {
+ call_cb = 1;
+ cb_status = BLE_HS_HCI_ERR(ev->status);
+ }
+
+ if (call_cb) {
+ ble_gap_update_notify(conn_handle, cb_status);
+ }
+#endif
+}
+
+/**
+ * Tells you if there is an active central GAP procedure (connect or discover).
+ */
+int
+ble_gap_master_in_progress(void)
+{
+ return ble_gap_master.op != BLE_GAP_OP_NULL;
+}
+
+static int
+ble_gap_adv_active_instance(uint8_t instance)
+{
+ /* Assume read is atomic; mutex not necessary. */
+ return ble_gap_slave[instance].op == BLE_GAP_OP_S_ADV;
+}
+
+/**
+ * Clears advertisement and discovery state. This function is necessary
+ * when the controller loses its active state (e.g. on stack reset).
+ */
+void
+ble_gap_reset_state(int reason)
+{
+ uint16_t conn_handle;
+
+ while (1) {
+ conn_handle = ble_hs_atomic_first_conn_handle();
+ if (conn_handle == BLE_HS_CONN_HANDLE_NONE) {
+ break;
+ }
+
+ ble_gap_conn_broken(conn_handle, reason);
+ }
+
+#if NIMBLE_BLE_ADVERTISE
+#if MYNEWT_VAL(BLE_EXT_ADV)
+ uint8_t i;
+ for (i = 0; i < BLE_ADV_INSTANCES; i++) {
+ if (ble_gap_adv_active_instance(i)) {
+ /* Indicate to application that advertising has stopped. */
+ ble_gap_adv_finished(i, reason, 0, 0);
+ }
+ }
+#else
+ if (ble_gap_adv_active_instance(0)) {
+ /* Indicate to application that advertising has stopped. */
+ ble_gap_adv_finished(0, reason, 0, 0);
+ }
+#endif
+#endif
+
+#if (NIMBLE_BLE_SCAN || NIMBLE_BLE_CONNECT)
+ ble_gap_master_failed(reason);
+#endif
+}
+
+#if NIMBLE_BLE_CONNECT
+static int
+ble_gap_accept_master_conn(void)
+{
+ int rc;
+
+ switch (ble_gap_master.op) {
+ case BLE_GAP_OP_NULL:
+ case BLE_GAP_OP_M_DISC:
+ rc = BLE_HS_ENOENT;
+ break;
+
+ case BLE_GAP_OP_M_CONN:
+ rc = 0;
+ break;
+
+ default:
+ BLE_HS_DBG_ASSERT(0);
+ rc = BLE_HS_ENOENT;
+ break;
+ }
+
+ if (rc == 0) {
+ STATS_INC(ble_gap_stats, connect_mst);
+ }
+
+ return rc;
+}
+
+static int
+ble_gap_accept_slave_conn(uint8_t instance)
+{
+ int rc;
+
+ if (instance >= BLE_ADV_INSTANCES) {
+ rc = BLE_HS_ENOENT;
+ } else if (!ble_gap_adv_active_instance(instance)) {
+ rc = BLE_HS_ENOENT;
+ } else {
+ if (ble_gap_slave[instance].connectable) {
+ rc = 0;
+ } else {
+ rc = BLE_HS_ENOENT;
+ }
+ }
+
+ if (rc == 0) {
+ STATS_INC(ble_gap_stats, connect_slv);
+ }
+
+ return rc;
+}
+#endif
+
+#if NIMBLE_BLE_SCAN
+static int
+ble_gap_rx_adv_report_sanity_check(const uint8_t *adv_data, uint8_t adv_data_len)
+{
+ const struct ble_hs_adv_field *flags;
+ int rc;
+
+ STATS_INC(ble_gap_stats, rx_adv_report);
+
+ if (ble_gap_master.op != BLE_GAP_OP_M_DISC) {
+ return -1;
+ }
+
+ /* If a limited discovery procedure is active, discard non-limited
+ * advertisements.
+ */
+ if (ble_gap_master.disc.limited) {
+ rc = ble_hs_adv_find_field(BLE_HS_ADV_TYPE_FLAGS, adv_data,
+ adv_data_len, &flags);
+ if ((rc == 0) && (flags->length == 2) &&
+ !(flags->value[0] & BLE_HS_ADV_F_DISC_LTD)) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+#endif
+
+void
+ble_gap_rx_adv_report(struct ble_gap_disc_desc *desc)
+{
+#if NIMBLE_BLE_SCAN
+ if (ble_gap_rx_adv_report_sanity_check(desc->data, desc->length_data)) {
+ return;
+ }
+
+ ble_gap_disc_report(desc);
+#endif
+}
+
+#if MYNEWT_VAL(BLE_EXT_ADV)
+#if NIMBLE_BLE_SCAN
+void
+ble_gap_rx_le_scan_timeout(void)
+{
+ ble_gap_disc_complete();
+}
+
+void
+ble_gap_rx_ext_adv_report(struct ble_gap_ext_disc_desc *desc)
+{
+ if (ble_gap_rx_adv_report_sanity_check(desc->data, desc->length_data)) {
+ return;
+ }
+
+ ble_gap_disc_report(desc);
+}
+#endif
+
+void
+ble_gap_rx_adv_set_terminated(const struct ble_hci_ev_le_subev_adv_set_terminated *ev)
+{
+ uint16_t conn_handle;
+ int reason;
+
+ /* Currently spec allows only 0x3c and 0x43 when advertising was stopped
+ * due to timeout or events limit, mp this for timeout error for now */
+ if (ev->status) {
+ reason = BLE_HS_ETIMEOUT;
+ conn_handle = 0;
+ } else {
+ reason = 0;
+ conn_handle = le16toh(ev->conn_handle);
+ }
+
+ ble_gap_adv_finished(ev->adv_handle, reason, conn_handle, ev->num_events);
+}
+
+void
+ble_gap_rx_scan_req_rcvd(const struct ble_hci_ev_le_subev_scan_req_rcvd *ev)
+{
+ struct ble_gap_event event;
+ ble_gap_event_fn *cb;
+ void *cb_arg;
+
+ ble_gap_slave_extract_cb(ev->adv_handle, &cb, &cb_arg);
+ if (cb != NULL) {
+ memset(&event, 0, sizeof event);
+ event.type = BLE_GAP_EVENT_SCAN_REQ_RCVD;
+ event.scan_req_rcvd.instance = ev->adv_handle;
+ event.scan_req_rcvd.scan_addr.type = ev->peer_addr_type;
+ memcpy(event.scan_req_rcvd.scan_addr.val, ev->peer_addr, BLE_DEV_ADDR_LEN);
+ cb(&event, cb_arg);
+ }
+}
+#endif
+
+/* Periodic adv events */
+#if MYNEWT_VAL(BLE_PERIODIC_ADV)
+
+void
+ble_gap_rx_peroidic_adv_sync_estab(const struct ble_hci_ev_le_subev_periodic_adv_sync_estab *ev)
+{
+ uint16_t sync_handle;
+ struct ble_gap_event event;
+ ble_gap_event_fn *cb;
+ void *cb_arg;
+
+ memset(&event, 0, sizeof event);
+
+ event.type = BLE_GAP_EVENT_PERIODIC_SYNC;
+ event.periodic_sync.status = ev->status;
+
+ ble_hs_lock();
+
+ BLE_HS_DBG_ASSERT(ble_gap_sync.psync);
+
+ if (!ev->status) {
+ sync_handle = le16toh(ev->sync_handle);
+
+ ble_gap_sync.psync->sync_handle = sync_handle;
+ ble_gap_sync.psync->adv_sid = ev->sid;
+ memcpy(ble_gap_sync.psync->advertiser_addr.val, ev->peer_addr, 6);
+ ble_gap_sync.psync->advertiser_addr.type = ev->peer_addr_type;
+
+ ble_gap_sync.psync->cb = ble_gap_sync.cb;
+ ble_gap_sync.psync->cb_arg = ble_gap_sync.cb_arg;
+
+ event.periodic_sync.sync_handle = sync_handle;
+ event.periodic_sync.sid = ev->sid;
+ event.periodic_sync.adv_addr = ble_gap_sync.psync->advertiser_addr;
+ event.periodic_sync.adv_phy = ev->phy;
+ event.periodic_sync.per_adv_ival = ev->interval;
+ event.periodic_sync.adv_clk_accuracy = ev->aca;
+
+ ble_hs_periodic_sync_insert(ble_gap_sync.psync);
+ } else {
+ ble_hs_periodic_sync_free(ble_gap_sync.psync);
+ }
+
+ cb = ble_gap_sync.cb;
+ cb_arg = ble_gap_sync.cb_arg;
+
+ ble_gap_sync.op = BLE_GAP_OP_NULL;
+ ble_gap_sync.cb_arg = NULL;
+ ble_gap_sync.cb_arg = NULL;
+ ble_gap_sync.psync = NULL;
+
+ ble_hs_unlock();
+
+ ble_gap_event_listener_call(&event);
+ if (cb) {
+ cb(&event, cb_arg);
+ }
+}
+
+void
+ble_gap_rx_periodic_adv_rpt(const struct ble_hci_ev_le_subev_periodic_adv_rpt *ev)
+{
+ struct ble_hs_periodic_sync *psync;
+ struct ble_gap_event event;
+ ble_gap_event_fn *cb;
+ void *cb_arg;
+
+ ble_hs_lock();
+ psync = ble_hs_periodic_sync_find_by_handle(le16toh(ev->sync_handle));
+ if (psync) {
+ cb = psync->cb;
+ cb_arg = psync->cb_arg;
+ }
+ ble_hs_unlock();
+
+ if (!psync || !cb) {
+ return;
+ }
+
+ memset(&event, 0, sizeof event);
+
+ event.type = BLE_GAP_EVENT_PERIODIC_REPORT;
+ event.periodic_report.sync_handle = psync->sync_handle;
+ event.periodic_report.tx_power = ev->tx_power;
+ event.periodic_report.rssi = ev->rssi;
+ event.periodic_report.data_status = ev->data_status;
+ event.periodic_report.data_length = ev->data_len;
+ event.periodic_report.data = ev->data;
+
+ /* TODO should we allow for listener too? this can be spammy and is more
+ * like ACL data, not general event
+ */
+ cb(&event, cb_arg);
+}
+
+void
+ble_gap_rx_periodic_adv_sync_lost(const struct ble_hci_ev_le_subev_periodic_adv_sync_lost *ev)
+{
+ struct ble_hs_periodic_sync *psync;
+ struct ble_gap_event event;
+ ble_gap_event_fn *cb;
+ void *cb_arg;
+
+ ble_hs_lock();
+ /* The handle must be in the list */
+ psync = ble_hs_periodic_sync_find_by_handle(le16toh(ev->sync_handle));
+ BLE_HS_DBG_ASSERT(psync);
+
+ cb = psync->cb;
+ cb_arg = psync->cb_arg;
+
+ /* Remove the handle from the list */
+ ble_hs_periodic_sync_remove(psync);
+ ble_hs_unlock();
+
+ memset(&event, 0, sizeof event);
+
+ event.type = BLE_GAP_EVENT_PERIODIC_SYNC_LOST;
+ event.periodic_sync_lost.sync_handle = psync->sync_handle;
+ event.periodic_sync_lost.reason = BLE_HS_ETIMEOUT;
+
+ /* remove any sync_lost event from queue */
+ ble_npl_eventq_remove(ble_hs_evq_get(), &psync->lost_ev);
+
+ /* Free the memory occupied by psync as it is no longer needed */
+ ble_hs_periodic_sync_free(psync);
+
+ ble_gap_event_listener_call(&event);
+ if (cb) {
+ cb(&event, cb_arg);
+ }
+}
+#endif
+
+#if MYNEWT_VAL(BLE_PERIODIC_ADV_SYNC_TRANSFER)
+static int
+periodic_adv_transfer_disable(uint16_t conn_handle)
+{
+ struct ble_hci_le_periodic_adv_sync_transfer_params_cp cmd;
+ struct ble_hci_le_periodic_adv_sync_transfer_params_rp rsp;
+ uint16_t opcode;
+ int rc;
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_PERIODIC_ADV_SYNC_TRANSFER_PARAMS);
+
+ cmd.conn_handle = htole16(conn_handle);
+ cmd.sync_cte_type = 0x00;
+ cmd.mode = 0x00;
+ cmd.skip = 0x0000;
+ cmd.sync_timeout = 0x000a;
+
+ rc = ble_hs_hci_cmd_tx(opcode, &cmd, sizeof(cmd), &rsp, sizeof(rsp));
+ if (!rc) {
+ BLE_HS_DBG_ASSERT(le16toh(rsp.conn_handle) == conn_handle);
+ }
+
+ return rc;
+}
+
+void
+ble_gap_rx_periodic_adv_sync_transfer(const struct ble_hci_ev_le_subev_periodic_adv_sync_transfer *ev)
+{
+ struct ble_hci_le_periodic_adv_term_sync_cp cmd_term;
+ struct ble_gap_event event;
+ struct ble_hs_conn *conn;
+ ble_gap_event_fn *cb;
+ uint16_t sync_handle;
+ uint16_t conn_handle;
+ uint16_t opcode;
+ void *cb_arg;
+
+ conn_handle = le16toh(ev->conn_handle);
+
+ ble_hs_lock();
+
+ /* Unfortunately spec sucks here as it doesn't explicitly stop
+ * transfer reception on first transfer... for now just disable it on
+ * every transfer event we get.
+ */
+ periodic_adv_transfer_disable(conn_handle);
+
+ conn = ble_hs_conn_find(le16toh(ev->conn_handle));
+ if (!conn || !conn->psync) {
+ /* terminate sync if we didn't expect it */
+ if (!ev->status) {
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_PERIODIC_ADV_TERM_SYNC);
+ cmd_term.sync_handle = ev->sync_handle;
+ ble_hs_hci_cmd_tx(opcode, &cmd_term, sizeof(cmd_term), NULL, 0);
+ }
+
+ ble_hs_unlock();
+ return;
+ }
+
+ cb = conn->psync->cb;
+ cb_arg = conn->psync->cb_arg;
+
+ memset(&event, 0, sizeof event);
+
+ event.type = BLE_GAP_EVENT_PERIODIC_TRANSFER;
+ event.periodic_transfer.status = ev->status;
+
+ /* only sync handle is not valid on error */
+ if (ev->status) {
+ sync_handle = 0;
+ ble_hs_periodic_sync_free(conn->psync);
+ } else {
+ sync_handle = le16toh(ev->sync_handle);
+
+ conn->psync->sync_handle = sync_handle;
+ conn->psync->adv_sid = ev->sid;
+ memcpy(conn->psync->advertiser_addr.val, ev->peer_addr, 6);
+ conn->psync->advertiser_addr.type = ev->peer_addr_type;
+ ble_hs_periodic_sync_insert(conn->psync);
+ }
+
+ conn->psync = NULL;
+
+ event.periodic_transfer.sync_handle = sync_handle;
+ event.periodic_transfer.conn_handle = conn_handle;
+ event.periodic_transfer.service_data = le16toh(ev->service_data);
+ event.periodic_transfer.sid = ev->sid;
+ memcpy(event.periodic_transfer.adv_addr.val, ev->peer_addr, 6);
+ event.periodic_transfer.adv_addr.type = ev->peer_addr_type;
+
+ event.periodic_transfer.adv_phy = ev->phy;
+ event.periodic_transfer.per_adv_itvl = le16toh(ev->interval);
+ event.periodic_transfer.adv_clk_accuracy = ev->aca;
+
+ ble_hs_unlock();
+
+ ble_gap_event_listener_call(&event);
+ if (cb) {
+ cb(&event, cb_arg);
+ }
+}
+#endif
+
+#if NIMBLE_BLE_CONNECT
+static int
+ble_gap_rd_rem_sup_feat_tx(uint16_t handle)
+{
+ struct ble_hci_le_rd_rem_feat_cp cmd;
+
+ cmd.conn_handle = htole16(handle);
+
+ return ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_LE,
+ BLE_HCI_OCF_LE_RD_REM_FEAT),
+ &cmd, sizeof(cmd), NULL, 0);
+}
+#endif
+
+/**
+ * Processes an incoming connection-complete HCI event.
+ * instance parameter is valid only for slave connection.
+ */
+int
+ble_gap_rx_conn_complete(struct ble_gap_conn_complete *evt, uint8_t instance)
+{
+#if NIMBLE_BLE_CONNECT
+ struct ble_gap_event event;
+ struct ble_hs_conn *conn;
+ int rc;
+
+ STATS_INC(ble_gap_stats, rx_conn_complete);
+
+ /* in that case *only* status field is valid so we determine role
+ * based on error code
+ */
+ if (evt->status != BLE_ERR_SUCCESS) {
+ switch (evt->status) {
+ case BLE_ERR_DIR_ADV_TMO:
+ /* slave role (HD directed advertising)
+ *
+ * with ext advertising this is send from set terminated event
+ */
+#if !MYNEWT_VAL(BLE_EXT_ADV)
+ if (ble_gap_adv_active()) {
+ ble_gap_adv_finished(0, 0, 0, 0);
+ }
+#endif
+ break;
+ case BLE_ERR_UNK_CONN_ID:
+ /* master role */
+ if (ble_gap_master_in_progress()) {
+ /* Connect procedure successfully cancelled. */
+ if (ble_gap_master.preempted_op == BLE_GAP_OP_M_CONN) {
+ ble_gap_master_failed(BLE_HS_EPREEMPTED);
+ } else {
+ ble_gap_master_connect_cancelled();
+ }
+ }
+ break;
+ default:
+ /* this should never happen, unless controller is broken */
+ BLE_HS_LOG(INFO, "controller reported invalid error code in conn"
+ "complete event: %u", evt->status);
+ assert(0);
+ break;
+ }
+ return 0;
+ }
+
+ /* Apply the event to the existing connection if it exists. */
+ if (ble_hs_atomic_conn_flags(evt->connection_handle, NULL) == 0) {
+ /* XXX: Does this ever happen? */
+ return 0;
+ }
+
+ /* This event refers to a new connection. */
+
+ switch (evt->role) {
+ case BLE_HCI_LE_CONN_COMPLETE_ROLE_MASTER:
+ rc = ble_gap_accept_master_conn();
+ if (rc != 0) {
+ return rc;
+ }
+ break;
+
+ case BLE_HCI_LE_CONN_COMPLETE_ROLE_SLAVE:
+ rc = ble_gap_accept_slave_conn(instance);
+ if (rc != 0) {
+ return rc;
+ }
+ break;
+
+ default:
+ BLE_HS_DBG_ASSERT(0);
+ break;
+ }
+
+ /* We verified that there is a free connection when the procedure began. */
+ conn = ble_hs_conn_alloc(evt->connection_handle);
+ BLE_HS_DBG_ASSERT(conn != NULL);
+
+ conn->bhc_itvl = evt->conn_itvl;
+ conn->bhc_latency = evt->conn_latency;
+ conn->bhc_supervision_timeout = evt->supervision_timeout;
+ conn->bhc_master_clock_accuracy = evt->master_clk_acc;
+ if (evt->role == BLE_HCI_LE_CONN_COMPLETE_ROLE_MASTER) {
+ conn->bhc_cb = ble_gap_master.cb;
+ conn->bhc_cb_arg = ble_gap_master.cb_arg;
+ conn->bhc_flags |= BLE_HS_CONN_F_MASTER;
+ conn->bhc_our_addr_type = ble_gap_master.conn.our_addr_type;
+ ble_gap_master_reset_state();
+ } else {
+ conn->bhc_cb = ble_gap_slave[instance].cb;
+ conn->bhc_cb_arg = ble_gap_slave[instance].cb_arg;
+ conn->bhc_our_addr_type = ble_gap_slave[instance].our_addr_type;
+#if MYNEWT_VAL(BLE_EXT_ADV)
+ memcpy(conn->bhc_our_rnd_addr, ble_gap_slave[instance].rnd_addr, 6);
+#endif
+ ble_gap_slave_reset_state(instance);
+ }
+
+ conn->bhc_peer_addr.type = evt->peer_addr_type;
+ memcpy(conn->bhc_peer_addr.val, evt->peer_addr, 6);
+
+ conn->bhc_our_rpa_addr.type = BLE_ADDR_RANDOM;
+ memcpy(conn->bhc_our_rpa_addr.val, evt->local_rpa, 6);
+
+ /* If peer RPA is not set in the event and peer address
+ * is RPA then store the peer RPA address so when the peer
+ * address is resolved, the RPA is not forgotten.
+ */
+ if (memcmp(BLE_ADDR_ANY->val, evt->peer_rpa, 6) == 0) {
+ if (BLE_ADDR_IS_RPA(&conn->bhc_peer_addr)) {
+ conn->bhc_peer_rpa_addr = conn->bhc_peer_addr;
+ }
+ } else {
+ conn->bhc_peer_rpa_addr.type = BLE_ADDR_RANDOM;
+ memcpy(conn->bhc_peer_rpa_addr.val, evt->peer_rpa, 6);
+ }
+
+ ble_hs_lock();
+
+ memset(&event, 0, sizeof event);
+ ble_hs_conn_insert(conn);
+
+ ble_hs_unlock();
+
+ event.type = BLE_GAP_EVENT_CONNECT;
+ event.connect.conn_handle = evt->connection_handle;
+ event.connect.status = 0;
+
+ ble_gap_event_listener_call(&event);
+ ble_gap_call_conn_event_cb(&event, evt->connection_handle);
+
+ ble_gap_rd_rem_sup_feat_tx(evt->connection_handle);
+
+ return 0;
+#else
+ return BLE_HS_ENOTSUP;
+#endif
+}
+
+void
+ble_gap_rx_rd_rem_sup_feat_complete(const struct ble_hci_ev_le_subev_rd_rem_used_feat *ev)
+{
+#if NIMBLE_BLE_CONNECT
+ struct ble_hs_conn *conn;
+
+ ble_hs_lock();
+
+ conn = ble_hs_conn_find(le16toh(ev->conn_handle));
+ if ((conn != NULL) && (ev->status == 0)) {
+ conn->supported_feat = get_le32(ev->features);
+ }
+
+ ble_hs_unlock();
+#endif
+}
+
+int
+ble_gap_rx_l2cap_update_req(uint16_t conn_handle,
+ struct ble_gap_upd_params *params)
+{
+ struct ble_gap_event event;
+ int rc;
+
+ memset(&event, 0, sizeof event);
+ event.type = BLE_GAP_EVENT_L2CAP_UPDATE_REQ;
+ event.conn_update_req.conn_handle = conn_handle;
+ event.conn_update_req.peer_params = params;
+
+ rc = ble_gap_call_conn_event_cb(&event, conn_handle);
+ return rc;
+}
+
+void
+ble_gap_rx_phy_update_complete(const struct ble_hci_ev_le_subev_phy_update_complete *ev)
+{
+ struct ble_gap_event event;
+ uint16_t conn_handle = le16toh(ev->conn_handle);
+
+ memset(&event, 0, sizeof event);
+ event.type = BLE_GAP_EVENT_PHY_UPDATE_COMPLETE;
+ event.phy_updated.status = ev->status;
+ event.phy_updated.conn_handle = conn_handle;
+ event.phy_updated.tx_phy = ev->tx_phy;
+ event.phy_updated.rx_phy = ev->rx_phy;
+
+ ble_gap_event_listener_call(&event);
+ ble_gap_call_conn_event_cb(&event, conn_handle);
+}
+
+static int32_t
+ble_gap_master_timer(void)
+{
+ uint32_t ticks_until_exp;
+ int rc;
+
+ ticks_until_exp = ble_gap_master_ticks_until_exp();
+ if (ticks_until_exp != 0) {
+ /* Timer not expired yet. */
+ return ticks_until_exp;
+ }
+
+ /*** Timer expired; process event. */
+
+ switch (ble_gap_master.op) {
+ case BLE_GAP_OP_M_CONN:
+ rc = ble_gap_conn_cancel_tx();
+ if (rc != 0) {
+ /* Failed to stop connecting; try again in 100 ms. */
+ return ble_npl_time_ms_to_ticks32(BLE_GAP_CANCEL_RETRY_TIMEOUT_MS);
+ } else {
+ /* Stop the timer now that the cancel command has been acked. */
+ ble_gap_master.exp_set = 0;
+
+ /* Timeout gets reported when we receive a connection complete
+ * event indicating the connect procedure has been cancelled.
+ */
+ /* XXX: Set a timer to reset the controller if a connection
+ * complete event isn't received within a reasonable interval.
+ */
+ }
+ break;
+
+ case BLE_GAP_OP_M_DISC:
+#if NIMBLE_BLE_SCAN && !MYNEWT_VAL(BLE_EXT_ADV)
+ /* When a discovery procedure times out, it is not a failure. */
+ rc = ble_gap_disc_enable_tx(0, 0);
+ if (rc != 0) {
+ /* Failed to stop discovery; try again in 100 ms. */
+ return ble_npl_time_ms_to_ticks32(BLE_GAP_CANCEL_RETRY_TIMEOUT_MS);
+ }
+
+ ble_gap_disc_complete();
+#else
+ assert(0);
+#endif
+ break;
+
+ default:
+ BLE_HS_DBG_ASSERT(0);
+ break;
+ }
+
+ return BLE_HS_FOREVER;
+}
+
+#if !MYNEWT_VAL(BLE_EXT_ADV)
+static int32_t
+ble_gap_slave_timer(void)
+{
+ uint32_t ticks_until_exp;
+ int rc;
+
+ ticks_until_exp = ble_gap_slave_ticks_until_exp();
+ if (ticks_until_exp != 0) {
+ /* Timer not expired yet. */
+ return ticks_until_exp;
+ }
+
+ /*** Timer expired; process event. */
+
+ /* Stop advertising. */
+ rc = ble_gap_adv_enable_tx(0);
+ if (rc != 0) {
+ /* Failed to stop advertising; try again in 100 ms. */
+ return 100;
+ }
+
+ /* Clear the timer and cancel the current procedure. */
+ ble_gap_slave_reset_state(0);
+
+ /* Indicate to application that advertising has stopped. */
+ ble_gap_adv_finished(0, BLE_HS_ETIMEOUT, 0, 0);
+
+ return BLE_HS_FOREVER;
+}
+#endif
+
+static int32_t
+ble_gap_update_timer(void)
+{
+ struct ble_gap_update_entry *entry;
+ int32_t ticks_until_exp;
+ uint16_t conn_handle;
+
+ do {
+ ble_hs_lock();
+
+ conn_handle = ble_gap_update_next_exp(&ticks_until_exp);
+ if (ticks_until_exp == 0) {
+ entry = ble_gap_update_entry_remove(conn_handle);
+ } else {
+ entry = NULL;
+ }
+
+ ble_hs_unlock();
+
+ if (entry != NULL) {
+ ble_gap_update_entry_free(entry);
+ }
+ } while (entry != NULL);
+
+ return ticks_until_exp;
+}
+
+int
+ble_gap_set_event_cb(uint16_t conn_handle, ble_gap_event_fn *cb, void *cb_arg)
+{
+ struct ble_hs_conn *conn;
+
+ ble_hs_lock();
+
+ conn = ble_hs_conn_find(conn_handle);
+ if (conn != NULL) {
+ conn->bhc_cb = cb;
+ conn->bhc_cb_arg = cb_arg;
+ }
+
+ ble_hs_unlock();
+
+ if (conn == NULL) {
+ return BLE_HS_ENOTCONN;
+ }
+
+ return 0;
+}
+
+/**
+ * Handles timed-out GAP procedures.
+ *
+ * @return The number of ticks until this function should
+ * be called again.
+ */
+int32_t
+ble_gap_timer(void)
+{
+ int32_t update_ticks;
+ int32_t master_ticks;
+ int32_t min_ticks;
+
+ master_ticks = ble_gap_master_timer();
+ update_ticks = ble_gap_update_timer();
+
+ min_ticks = min(master_ticks, update_ticks);
+
+#if !MYNEWT_VAL(BLE_EXT_ADV)
+ min_ticks = min(min_ticks, ble_gap_slave_timer());
+#endif
+
+ return min_ticks;
+}
+
+/*****************************************************************************
+ * $white list *
+ *****************************************************************************/
+
+#if MYNEWT_VAL(BLE_WHITELIST)
+static int
+ble_gap_wl_busy(void)
+{
+ /* Check if an auto or selective connection establishment procedure is in
+ * progress.
+ */
+ return ble_gap_master.op == BLE_GAP_OP_M_CONN &&
+ ble_gap_master.conn.using_wl;
+}
+
+static int
+ble_gap_wl_tx_add(const ble_addr_t *addr)
+{
+ struct ble_hci_le_add_whte_list_cp cmd;
+
+ if (addr->type > BLE_ADDR_RANDOM) {
+ return BLE_HS_EINVAL;
+ }
+
+ memcpy(cmd.addr, addr->val, BLE_DEV_ADDR_LEN);
+ cmd.addr_type = addr->type;
+
+ return ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_LE,
+ BLE_HCI_OCF_LE_ADD_WHITE_LIST),
+ &cmd, sizeof(cmd), NULL, 0);
+}
+
+static int
+ble_gap_wl_tx_clear(void)
+{
+ return ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_LE,
+ BLE_HCI_OCF_LE_CLEAR_WHITE_LIST),
+ NULL, 0, NULL, 0 );
+}
+#endif
+
+int
+ble_gap_wl_set(const ble_addr_t *addrs, uint8_t white_list_count)
+{
+#if MYNEWT_VAL(BLE_WHITELIST)
+ int rc;
+ int i;
+
+ STATS_INC(ble_gap_stats, wl_set);
+
+ ble_hs_lock();
+
+ if (white_list_count == 0) {
+ rc = BLE_HS_EINVAL;
+ goto done;
+ }
+
+ for (i = 0; i < white_list_count; i++) {
+ if (addrs[i].type != BLE_ADDR_PUBLIC &&
+ addrs[i].type != BLE_ADDR_RANDOM) {
+
+ rc = BLE_HS_EINVAL;
+ goto done;
+ }
+ }
+
+ if (ble_gap_wl_busy()) {
+ rc = BLE_HS_EBUSY;
+ goto done;
+ }
+
+ BLE_HS_LOG(INFO, "GAP procedure initiated: set whitelist; ");
+ ble_gap_log_wl(addrs, white_list_count);
+ BLE_HS_LOG(INFO, "\n");
+
+ rc = ble_gap_wl_tx_clear();
+ if (rc != 0) {
+ goto done;
+ }
+
+ for (i = 0; i < white_list_count; i++) {
+ rc = ble_gap_wl_tx_add(addrs + i);
+ if (rc != 0) {
+ goto done;
+ }
+ }
+
+ rc = 0;
+
+done:
+ ble_hs_unlock();
+
+ if (rc != 0) {
+ STATS_INC(ble_gap_stats, wl_set_fail);
+ }
+ return rc;
+#else
+ return BLE_HS_ENOTSUP;
+#endif
+}
+
+/*****************************************************************************
+ * $stop advertise *
+ *****************************************************************************/
+#if NIMBLE_BLE_ADVERTISE && !MYNEWT_VAL(BLE_EXT_ADV)
+static int
+ble_gap_adv_enable_tx(int enable)
+{
+ struct ble_hci_le_set_adv_enable_cp cmd;
+
+ cmd.enable = !!enable;
+
+ return ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_LE,
+ BLE_HCI_OCF_LE_SET_ADV_ENABLE),
+ &cmd, sizeof(cmd), NULL, 0);
+}
+
+static int
+ble_gap_adv_stop_no_lock(void)
+{
+ bool active;
+ int rc;
+
+ BLE_HS_DBG_ASSERT(ble_hs_locked_by_cur_task());
+
+ STATS_INC(ble_gap_stats, adv_stop);
+
+ active = ble_gap_adv_active();
+
+ BLE_HS_LOG(INFO, "GAP procedure initiated: stop advertising.\n");
+
+ rc = ble_gap_adv_enable_tx(0);
+ if (rc != 0) {
+ goto done;
+ }
+
+ ble_gap_slave_reset_state(0);
+
+ if (!active) {
+ rc = BLE_HS_EALREADY;
+ } else {
+ rc = 0;
+ }
+
+done:
+ if (rc != 0) {
+ STATS_INC(ble_gap_stats, adv_stop_fail);
+ }
+
+ return rc;
+}
+#endif
+
+int
+ble_gap_adv_stop(void)
+{
+#if NIMBLE_BLE_ADVERTISE && !MYNEWT_VAL(BLE_EXT_ADV)
+ int rc;
+
+ ble_hs_lock();
+ rc = ble_gap_adv_stop_no_lock();
+ ble_hs_unlock();
+
+ return rc;
+#else
+ return BLE_HS_ENOTSUP;
+#endif
+}
+
+/*****************************************************************************
+ * $advertise *
+ *****************************************************************************/
+#if NIMBLE_BLE_ADVERTISE && !MYNEWT_VAL(BLE_EXT_ADV)
+static int
+ble_gap_adv_type(const struct ble_gap_adv_params *adv_params)
+{
+ switch (adv_params->conn_mode) {
+ case BLE_GAP_CONN_MODE_NON:
+ if (adv_params->disc_mode == BLE_GAP_DISC_MODE_NON) {
+ return BLE_HCI_ADV_TYPE_ADV_NONCONN_IND;
+ } else {
+ return BLE_HCI_ADV_TYPE_ADV_SCAN_IND;
+ }
+
+ case BLE_GAP_CONN_MODE_UND:
+ return BLE_HCI_ADV_TYPE_ADV_IND;
+
+ case BLE_GAP_CONN_MODE_DIR:
+ if (adv_params->high_duty_cycle) {
+ return BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_HD;
+ } else {
+ return BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_LD;
+ }
+
+ default:
+ BLE_HS_DBG_ASSERT(0);
+ return BLE_HCI_ADV_TYPE_ADV_IND;
+ }
+}
+
+static void
+ble_gap_adv_dflt_itvls(uint8_t conn_mode,
+ uint16_t *out_itvl_min, uint16_t *out_itvl_max)
+{
+ switch (conn_mode) {
+ case BLE_GAP_CONN_MODE_NON:
+ *out_itvl_min = BLE_GAP_ADV_FAST_INTERVAL2_MIN;
+ *out_itvl_max = BLE_GAP_ADV_FAST_INTERVAL2_MAX;
+ break;
+
+ case BLE_GAP_CONN_MODE_UND:
+ *out_itvl_min = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
+ *out_itvl_max = BLE_GAP_ADV_FAST_INTERVAL1_MAX;
+ break;
+
+ case BLE_GAP_CONN_MODE_DIR:
+ *out_itvl_min = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
+ *out_itvl_max = BLE_GAP_ADV_FAST_INTERVAL1_MAX;
+ break;
+
+ default:
+ BLE_HS_DBG_ASSERT(0);
+ *out_itvl_min = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
+ *out_itvl_max = BLE_GAP_ADV_FAST_INTERVAL1_MAX;
+ break;
+ }
+}
+
+static int
+ble_gap_adv_params_tx(uint8_t own_addr_type, const ble_addr_t *peer_addr,
+ const struct ble_gap_adv_params *adv_params)
+
+{
+ const ble_addr_t *peer_any = BLE_ADDR_ANY;
+ struct ble_hci_le_set_adv_params_cp cmd;
+ uint16_t opcode;
+ uint16_t min;
+ uint16_t max;
+
+ /* Fill optional fields if application did not specify them. */
+ if ((adv_params->itvl_min == 0) && (adv_params->itvl_max == 0)) {
+ ble_gap_adv_dflt_itvls(adv_params->conn_mode, &min, &max);
+ cmd.min_interval = htole16(min);
+ cmd.max_interval = htole16(max);
+ } else {
+ cmd.min_interval = htole16(adv_params->itvl_min);
+ cmd.max_interval = htole16(adv_params->itvl_max);
+ }
+
+ cmd.type = ble_gap_adv_type(adv_params);
+ cmd.own_addr_type = own_addr_type;
+
+ if (peer_addr == NULL) {
+ peer_addr = peer_any;
+ }
+
+ cmd.peer_addr_type = peer_addr->type;
+ memcpy(&cmd.peer_addr, peer_addr->val, sizeof(cmd.peer_addr));
+
+ if (adv_params->channel_map == 0) {
+ cmd.chan_map = BLE_GAP_ADV_DFLT_CHANNEL_MAP;
+ } else {
+ cmd.chan_map = adv_params->channel_map;
+ }
+
+ /* Zero is the default value for filter policy and high duty cycle */
+ cmd.filter_policy = adv_params->filter_policy;
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_SET_ADV_PARAMS);
+
+ return ble_hs_hci_cmd_tx(opcode, &cmd, sizeof(cmd), NULL, 0);
+}
+
+static int
+ble_gap_adv_validate(uint8_t own_addr_type, const ble_addr_t *peer_addr,
+ const struct ble_gap_adv_params *adv_params)
+{
+ if (adv_params == NULL) {
+ return BLE_HS_EINVAL;
+ }
+
+ if (own_addr_type > BLE_HCI_ADV_OWN_ADDR_MAX) {
+ return BLE_HS_EINVAL;
+ }
+
+ if (adv_params->disc_mode >= BLE_GAP_DISC_MODE_MAX) {
+ return BLE_HS_EINVAL;
+ }
+
+ if (ble_gap_slave[0].op != BLE_GAP_OP_NULL) {
+ return BLE_HS_EALREADY;
+ }
+
+ switch (adv_params->conn_mode) {
+ case BLE_GAP_CONN_MODE_NON:
+ /* High duty cycle only allowed for directed advertising. */
+ if (adv_params->high_duty_cycle) {
+ return BLE_HS_EINVAL;
+ }
+ break;
+
+ case BLE_GAP_CONN_MODE_UND:
+ /* High duty cycle only allowed for directed advertising. */
+ if (adv_params->high_duty_cycle) {
+ return BLE_HS_EINVAL;
+ }
+
+ /* Don't allow connectable advertising if we won't be able to allocate
+ * a new connection.
+ */
+ if (!ble_hs_conn_can_alloc()) {
+ return BLE_HS_ENOMEM;
+ }
+ break;
+
+ case BLE_GAP_CONN_MODE_DIR:
+ if (peer_addr == NULL) {
+ return BLE_HS_EINVAL;
+ }
+
+ if (peer_addr->type != BLE_ADDR_PUBLIC &&
+ peer_addr->type != BLE_ADDR_RANDOM &&
+ peer_addr->type != BLE_ADDR_PUBLIC_ID &&
+ peer_addr->type != BLE_ADDR_RANDOM_ID) {
+
+ return BLE_HS_EINVAL;
+ }
+
+ /* Don't allow connectable advertising if we won't be able to allocate
+ * a new connection.
+ */
+ if (!ble_hs_conn_can_alloc()) {
+ return BLE_HS_ENOMEM;
+ }
+ break;
+
+ default:
+ return BLE_HS_EINVAL;
+ }
+
+ return 0;
+}
+#endif
+
+int
+ble_gap_adv_start(uint8_t own_addr_type, const ble_addr_t *direct_addr,
+ int32_t duration_ms,
+ const struct ble_gap_adv_params *adv_params,
+ ble_gap_event_fn *cb, void *cb_arg)
+{
+#if NIMBLE_BLE_ADVERTISE && !MYNEWT_VAL(BLE_EXT_ADV)
+ uint32_t duration_ticks;
+ int rc;
+
+ STATS_INC(ble_gap_stats, adv_start);
+
+ ble_hs_lock();
+
+ rc = ble_gap_adv_validate(own_addr_type, direct_addr, adv_params);
+ if (rc != 0) {
+ goto done;
+ }
+
+ if (duration_ms != BLE_HS_FOREVER) {
+ rc = ble_npl_time_ms_to_ticks(duration_ms, &duration_ticks);
+ if (rc != 0) {
+ /* Duration too great. */
+ rc = BLE_HS_EINVAL;
+ goto done;
+ }
+ }
+
+ if (!ble_hs_is_enabled()) {
+ rc = BLE_HS_EDISABLED;
+ goto done;
+ }
+
+ if (ble_gap_is_preempted()) {
+ rc = BLE_HS_EPREEMPTED;
+ goto done;
+ }
+
+ rc = ble_hs_id_use_addr(own_addr_type);
+ if (rc != 0) {
+ goto done;
+ }
+
+ BLE_HS_LOG(INFO, "GAP procedure initiated: advertise; ");
+ ble_gap_log_adv(own_addr_type, direct_addr, adv_params);
+ BLE_HS_LOG(INFO, "\n");
+
+ ble_gap_slave[0].cb = cb;
+ ble_gap_slave[0].cb_arg = cb_arg;
+ ble_gap_slave[0].our_addr_type = own_addr_type;
+
+ if (adv_params->conn_mode != BLE_GAP_CONN_MODE_NON) {
+ ble_gap_slave[0].connectable = 1;
+ } else {
+ ble_gap_slave[0].connectable = 0;
+ }
+
+ rc = ble_gap_adv_params_tx(own_addr_type, direct_addr, adv_params);
+ if (rc != 0) {
+ goto done;
+ }
+
+ ble_gap_slave[0].op = BLE_GAP_OP_S_ADV;
+
+ rc = ble_gap_adv_enable_tx(1);
+ if (rc != 0) {
+ ble_gap_slave_reset_state(0);
+ goto done;
+ }
+
+ if (duration_ms != BLE_HS_FOREVER) {
+ ble_gap_slave_set_timer(duration_ticks);
+ }
+
+ rc = 0;
+
+done:
+ ble_hs_unlock();
+
+ if (rc != 0) {
+ STATS_INC(ble_gap_stats, adv_start_fail);
+ }
+ return rc;
+#else
+ return BLE_HS_ENOTSUP;
+#endif
+}
+
+int
+ble_gap_adv_set_data(const uint8_t *data, int data_len)
+{
+#if NIMBLE_BLE_ADVERTISE && !MYNEWT_VAL(BLE_EXT_ADV)
+ struct ble_hci_le_set_adv_data_cp cmd;
+ uint16_t opcode;
+
+ STATS_INC(ble_gap_stats, adv_set_data);
+
+ /* Check for valid parameters */
+ if (((data == NULL) && (data_len != 0)) ||
+ (data_len > BLE_HCI_MAX_ADV_DATA_LEN)) {
+ return BLE_ERR_INV_HCI_CMD_PARMS;
+ }
+
+ memcpy(cmd.adv_data, data, data_len);
+ cmd.adv_data_len = data_len;
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_SET_ADV_DATA);
+
+ return ble_hs_hci_cmd_tx(opcode, &cmd, sizeof(cmd), NULL, 0);
+#else
+ return BLE_HS_ENOTSUP;
+#endif
+}
+
+int
+ble_gap_adv_rsp_set_data(const uint8_t *data, int data_len)
+{
+#if NIMBLE_BLE_ADVERTISE && !MYNEWT_VAL(BLE_EXT_ADV)
+ struct ble_hci_le_set_scan_rsp_data_cp cmd;
+ uint16_t opcode;
+
+
+ /* Check for valid parameters */
+ if (((data == NULL) && (data_len != 0)) ||
+ (data_len > BLE_HCI_MAX_SCAN_RSP_DATA_LEN)) {
+ return BLE_HS_EINVAL;
+ }
+
+ memcpy(cmd.scan_rsp, data, data_len);
+ cmd.scan_rsp_len = data_len;
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_SET_SCAN_RSP_DATA);
+
+ return ble_hs_hci_cmd_tx(opcode, &cmd, sizeof(cmd), NULL, 0);
+#else
+ return BLE_HS_ENOTSUP;
+#endif
+}
+
+int
+ble_gap_adv_set_fields(const struct ble_hs_adv_fields *adv_fields)
+{
+#if NIMBLE_BLE_ADVERTISE && !MYNEWT_VAL(BLE_EXT_ADV)
+ uint8_t buf[BLE_HS_ADV_MAX_SZ];
+ uint8_t buf_sz;
+ int rc;
+
+ rc = ble_hs_adv_set_fields(adv_fields, buf, &buf_sz, sizeof buf);
+ if (rc != 0) {
+ return rc;
+ }
+
+ rc = ble_gap_adv_set_data(buf, buf_sz);
+ if (rc != 0) {
+ return rc;
+ }
+
+ return 0;
+#else
+ return BLE_HS_ENOTSUP;
+#endif
+}
+
+int
+ble_gap_adv_rsp_set_fields(const struct ble_hs_adv_fields *rsp_fields)
+{
+#if NIMBLE_BLE_ADVERTISE && !MYNEWT_VAL(BLE_EXT_ADV)
+ uint8_t buf[BLE_HS_ADV_MAX_SZ];
+ uint8_t buf_sz;
+ int rc;
+
+ rc = ble_hs_adv_set_fields(rsp_fields, buf, &buf_sz, sizeof buf);
+ if (rc != 0) {
+ return rc;
+ }
+
+ rc = ble_gap_adv_rsp_set_data(buf, buf_sz);
+ if (rc != 0) {
+ return rc;
+ }
+
+ return 0;
+#else
+ return BLE_HS_ENOTSUP;
+#endif
+}
+
+int
+ble_gap_adv_active(void)
+{
+ return ble_gap_adv_active_instance(0);
+}
+
+#if MYNEWT_VAL(BLE_EXT_ADV)
+static int
+ble_gap_ext_adv_params_tx(uint8_t instance,
+ const struct ble_gap_ext_adv_params *params,
+ int8_t *selected_tx_power)
+
+{
+ struct ble_hci_le_set_ext_adv_params_cp cmd;
+ struct ble_hci_le_set_ext_adv_params_rp rsp;
+ int rc;
+
+ memset(&cmd, 0, sizeof(cmd));
+
+ cmd.adv_handle = instance;
+
+ if (params->connectable) {
+ cmd.props |= BLE_HCI_LE_SET_EXT_ADV_PROP_CONNECTABLE;
+ }
+ if (params->scannable) {
+ cmd.props |= BLE_HCI_LE_SET_EXT_ADV_PROP_SCANNABLE;
+ }
+ if (params->directed) {
+ cmd.props |= BLE_HCI_LE_SET_EXT_ADV_PROP_DIRECTED;
+ cmd.peer_addr_type = params->peer.type;
+ memcpy(cmd.peer_addr, params->peer.val, BLE_DEV_ADDR_LEN);
+ }
+ if (params->high_duty_directed) {
+ cmd.props |= BLE_HCI_LE_SET_EXT_ADV_PROP_HD_DIRECTED;
+ }
+ if (params->legacy_pdu) {
+ cmd.props |= BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY;
+ }
+ if (params->anonymous) {
+ cmd.props |= BLE_HCI_LE_SET_EXT_ADV_PROP_ANON_ADV;
+ }
+ if (params->include_tx_power) {
+ cmd.props |= BLE_HCI_LE_SET_EXT_ADV_PROP_INC_TX_PWR;
+ }
+
+ /* Fill optional fields if application did not specify them. */
+ if (params->itvl_min == 0 && params->itvl_max == 0) {
+ /* TODO for now limited to legacy values*/
+ put_le24(cmd.pri_itvl_min, BLE_GAP_ADV_FAST_INTERVAL1_MIN);
+ put_le24(cmd.pri_itvl_max, BLE_GAP_ADV_FAST_INTERVAL2_MAX);
+ } else {
+ put_le24(cmd.pri_itvl_min, params->itvl_min);
+ put_le24(cmd.pri_itvl_max, params->itvl_max);
+ }
+
+ if (params->channel_map == 0) {
+ cmd.pri_chan_map = BLE_GAP_ADV_DFLT_CHANNEL_MAP;
+ } else {
+ cmd.pri_chan_map = params->channel_map;
+ }
+
+ /* Zero is the default value for filter policy and high duty cycle */
+ cmd.filter_policy = params->filter_policy;
+ cmd.tx_power = params->tx_power;
+
+ if (params->legacy_pdu) {
+ cmd.pri_phy = BLE_HCI_LE_PHY_1M;
+ cmd.sec_phy = BLE_HCI_LE_PHY_1M;
+ } else {
+ cmd.pri_phy = params->primary_phy;
+ cmd.sec_phy = params->secondary_phy;
+ }
+
+ cmd.own_addr_type = params->own_addr_type;
+ cmd.sec_max_skip = 0;
+ cmd.sid = params->sid;
+ cmd.scan_req_notif = params->scan_req_notif;
+
+ rc = ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_LE,
+ BLE_HCI_OCF_LE_SET_EXT_ADV_PARAM),
+ &cmd, sizeof(cmd), &rsp, sizeof(rsp));
+
+ if (rc != 0) {
+ return rc;
+ }
+
+ if (selected_tx_power) {
+ *selected_tx_power = rsp.tx_power;
+ }
+
+ return 0;
+}
+
+static int
+ble_gap_ext_adv_params_validate(const struct ble_gap_ext_adv_params *params)
+{
+ if (!params) {
+ return BLE_HS_EINVAL;
+ }
+
+ if (params->own_addr_type > BLE_HCI_ADV_OWN_ADDR_MAX) {
+ return BLE_HS_EINVAL;
+ }
+
+ /* Don't allow connectable advertising if we won't be able to allocate
+ * a new connection.
+ */
+ if (params->connectable && !ble_hs_conn_can_alloc()) {
+ return BLE_HS_ENOMEM;
+ }
+
+ if (params->legacy_pdu) {
+ /* not allowed for legacy PDUs */
+ if (params->anonymous || params->include_tx_power) {
+ return BLE_HS_EINVAL;
+ }
+ }
+
+ if (params->directed) {
+ if (params->scannable && params->connectable) {
+ return BLE_HS_EINVAL;
+ }
+ }
+
+ if (!params->legacy_pdu) {
+ /* not allowed for extended advertising PDUs */
+ if (params->connectable && params->scannable) {
+ return BLE_HS_EINVAL;
+ }
+
+ /* HD directed advertising allowed only for legacy PDUs */
+ if (params->high_duty_directed) {
+ return BLE_HS_EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+int
+ble_gap_ext_adv_configure(uint8_t instance,
+ const struct ble_gap_ext_adv_params *params,
+ int8_t *selected_tx_power,
+ ble_gap_event_fn *cb, void *cb_arg)
+{
+ int rc;
+
+ if (instance >= BLE_ADV_INSTANCES) {
+ return BLE_HS_EINVAL;
+ }
+
+ rc = ble_gap_ext_adv_params_validate(params);
+ if (rc) {
+ return rc;
+ }
+
+ ble_hs_lock();
+
+ if (ble_gap_adv_active_instance(instance)) {
+ ble_hs_unlock();
+ return BLE_HS_EBUSY;
+ }
+
+ rc = ble_gap_ext_adv_params_tx(instance, params, selected_tx_power);
+ if (rc) {
+ ble_hs_unlock();
+ return rc;
+ }
+
+ ble_gap_slave[instance].configured = 1;
+ ble_gap_slave[instance].cb = cb;
+ ble_gap_slave[instance].cb_arg = cb_arg;
+ ble_gap_slave[instance].our_addr_type = params->own_addr_type;
+
+ ble_gap_slave[instance].connectable = params->connectable;
+ ble_gap_slave[instance].scannable = params->scannable;
+ ble_gap_slave[instance].directed = params->directed;
+ ble_gap_slave[instance].high_duty_directed = params->high_duty_directed;
+ ble_gap_slave[instance].legacy_pdu = params->legacy_pdu;
+
+ ble_hs_unlock();
+ return 0;
+}
+
+static int
+ble_gap_ext_adv_set_addr_no_lock(uint8_t instance, const uint8_t *addr)
+{
+ struct ble_hci_le_set_adv_set_rnd_addr_cp cmd;
+ int rc;
+
+ cmd.adv_handle = instance;
+ memcpy(cmd.addr, addr, BLE_DEV_ADDR_LEN);
+
+ rc = ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_LE,
+ BLE_HCI_OCF_LE_SET_ADV_SET_RND_ADDR),
+ &cmd, sizeof(cmd), NULL, 0);
+ if (rc != 0) {
+ return rc;
+ }
+
+ ble_gap_slave[instance].rnd_addr_set = 1;
+ memcpy(ble_gap_slave[instance].rnd_addr, addr, 6);
+
+ return 0;
+}
+
+int
+ble_gap_ext_adv_set_addr(uint8_t instance, const ble_addr_t *addr)
+{
+ int rc;
+
+ if (instance >= BLE_ADV_INSTANCES || addr->type != BLE_ADDR_RANDOM) {
+ return BLE_HS_EINVAL;
+ }
+
+ ble_hs_lock();
+ rc = ble_gap_ext_adv_set_addr_no_lock(instance, addr->val);
+ ble_hs_unlock();
+
+ return rc;
+}
+
+int
+ble_gap_ext_adv_start(uint8_t instance, int duration, int max_events)
+{
+ struct ble_hci_le_set_ext_adv_enable_cp *cmd;
+ uint8_t buf[sizeof(*cmd) + sizeof(cmd->sets[0])];
+ const uint8_t *rnd_addr;
+ uint16_t opcode;
+ int rc;
+
+ if (instance >= BLE_ADV_INSTANCES) {
+ return BLE_HS_EINVAL;
+ }
+
+ ble_hs_lock();
+ if (!ble_gap_slave[instance].configured) {
+ ble_hs_unlock();
+ return BLE_HS_EINVAL;
+ }
+
+ if (ble_gap_slave[instance].op != BLE_GAP_OP_NULL) {
+ ble_hs_unlock();
+ return BLE_HS_EALREADY;
+ }
+
+ /* HD directed duration shall not be 0 or larger than >1.28s */
+ if (ble_gap_slave[instance].high_duty_directed &&
+ ((duration == 0) || (duration > 128)) ) {
+ ble_hs_unlock();
+ return BLE_HS_EINVAL;
+ }
+
+ /* verify own address type if random address for instance wasn't explicitly
+ * set
+ */
+ switch (ble_gap_slave[instance].our_addr_type) {
+ case BLE_OWN_ADDR_RANDOM:
+ case BLE_OWN_ADDR_RPA_RANDOM_DEFAULT:
+ if (ble_gap_slave[instance].rnd_addr_set) {
+ break;
+ }
+ /* fall through */
+ case BLE_OWN_ADDR_PUBLIC:
+ case BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT:
+ default:
+ rc = ble_hs_id_use_addr(ble_gap_slave[instance].our_addr_type);
+ if (rc) {
+ ble_hs_unlock();
+ return BLE_HS_EINVAL;
+ }
+ break;
+ }
+
+ /* fallback to ID static random address if using random address and instance
+ * wasn't configured with own address
+ */
+ if (!ble_gap_slave[instance].rnd_addr_set) {
+ switch (ble_gap_slave[instance].our_addr_type) {
+ case BLE_OWN_ADDR_RANDOM:
+ case BLE_OWN_ADDR_RPA_RANDOM_DEFAULT:
+ rc = ble_hs_id_addr(BLE_ADDR_RANDOM, &rnd_addr, NULL);
+ if (rc != 0) {
+ ble_hs_unlock();
+ return rc;
+ }
+
+ rc = ble_gap_ext_adv_set_addr_no_lock(instance, rnd_addr);
+ if (rc != 0) {
+ ble_hs_unlock();
+ return rc;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_SET_EXT_ADV_ENABLE);
+
+ cmd = (void *) buf;
+
+ cmd->enable = 0x01;
+ cmd->num_sets = 1;
+
+ cmd->sets[0].adv_handle = instance;
+ cmd->sets[0].duration = htole16(duration);
+ cmd->sets[0].max_events = max_events;
+
+ rc = ble_hs_hci_cmd_tx(opcode, cmd, sizeof(buf), NULL, 0);
+ if (rc != 0) {
+ ble_hs_unlock();
+ return rc;
+ }
+
+ ble_gap_slave[instance].op = BLE_GAP_OP_S_ADV;
+
+ ble_hs_unlock();
+ return 0;
+}
+
+static int
+ble_gap_ext_adv_stop_no_lock(uint8_t instance)
+{
+ struct ble_hci_le_set_ext_adv_enable_cp *cmd;
+ uint8_t buf[sizeof(*cmd) + sizeof(cmd->sets[0])];
+ uint16_t opcode;
+ bool active;
+ int rc;
+
+ if (!ble_gap_slave[instance].configured) {
+ return BLE_HS_EINVAL;
+ }
+
+ active = ble_gap_adv_active_instance(instance);
+
+ cmd = (void *) buf;
+
+ cmd->enable = 0x00;
+ cmd->num_sets = 1;
+ cmd->sets[0].adv_handle = instance;
+ cmd->sets[0].duration = 0x0000;
+ cmd->sets[0].max_events = 0x00;
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_SET_EXT_ADV_ENABLE);
+
+ rc = ble_hs_hci_cmd_tx(opcode, cmd, sizeof(buf), NULL, 0);
+ if (rc != 0) {
+ return rc;
+ }
+
+ ble_gap_slave[instance].op = BLE_GAP_OP_NULL;
+
+ if (!active) {
+ return BLE_HS_EALREADY;
+ } else {
+ return 0;
+ }
+}
+
+int
+ble_gap_ext_adv_stop(uint8_t instance)
+{
+ int rc;
+
+ if (instance >= BLE_ADV_INSTANCES) {
+ return BLE_HS_EINVAL;
+ }
+
+ ble_hs_lock();
+ rc = ble_gap_ext_adv_stop_no_lock(instance);
+ ble_hs_unlock();
+
+ return rc;
+}
+
+
+static int
+ble_gap_ext_adv_set_data_validate(uint8_t instance, struct os_mbuf *data)
+{
+ uint16_t len = OS_MBUF_PKTLEN(data);
+
+ if (!ble_gap_slave[instance].configured) {
+ return BLE_HS_EINVAL;
+ }
+
+ /* not allowed with directed advertising for legacy*/
+ if (ble_gap_slave[instance].legacy_pdu && ble_gap_slave[instance].directed) {
+ return BLE_HS_EINVAL;
+ }
+
+ /* always allowed with legacy PDU but limited to legacy length */
+ if (ble_gap_slave[instance].legacy_pdu) {
+ if (len > BLE_HS_ADV_MAX_SZ) {
+ return BLE_HS_EINVAL;
+ }
+
+ return 0;
+ }
+
+ /* if already advertising, data must fit in single HCI command
+ * as per BT 5.0 Vol 2, Part E, 7.8.54. Don't bother Controller with such
+ * a request.
+ */
+ if (ble_gap_slave[instance].op == BLE_GAP_OP_S_ADV) {
+ if (len > min(MYNEWT_VAL(BLE_EXT_ADV_MAX_SIZE), 251)) {
+ return BLE_HS_EINVAL;
+ }
+ }
+
+ /* not allowed with scannable advertising */
+ if (ble_gap_slave[instance].scannable) {
+ return BLE_HS_EINVAL;
+ }
+
+ return 0;
+}
+
+static int
+ble_gap_ext_adv_set(uint8_t instance, uint16_t opcode, struct os_mbuf **data)
+{
+ /* in that case we always fit all data in single HCI command */
+#if MYNEWT_VAL(BLE_EXT_ADV_MAX_SIZE) <= BLE_HCI_MAX_EXT_ADV_DATA_LEN
+ static uint8_t buf[sizeof(struct ble_hci_le_set_ext_adv_data_cp) + \
+ MYNEWT_VAL(BLE_EXT_ADV_MAX_SIZE)];
+ struct ble_hci_le_set_ext_adv_data_cp *cmd = (void *)buf;
+ uint16_t len = OS_MBUF_PKTLEN(*data);
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, opcode);
+ cmd->adv_handle = instance;
+ cmd->operation = BLE_HCI_LE_SET_DATA_OPER_COMPLETE;
+ cmd->fragment_pref = 0;
+ cmd->adv_data_len = len;
+ os_mbuf_copydata(*data, 0, len, cmd->adv_data);
+
+ os_mbuf_adj(*data, len);
+ *data = os_mbuf_trim_front(*data);
+
+ return ble_hs_hci_cmd_tx(opcode, cmd, sizeof(*cmd) + cmd->adv_data_len,
+ NULL, 0);
+#else
+ static uint8_t buf[sizeof(struct ble_hci_le_set_ext_adv_data_cp) + \
+ BLE_HCI_MAX_EXT_ADV_DATA_LEN];
+ struct ble_hci_le_set_ext_adv_data_cp *cmd = (void *)buf;
+ uint16_t len = OS_MBUF_PKTLEN(*data);
+ uint8_t op;
+ int rc;
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, opcode);
+
+ cmd->adv_handle = instance;
+
+ /* complete data */
+ if (len <= BLE_HCI_MAX_EXT_ADV_DATA_LEN) {
+ cmd->operation = BLE_HCI_LE_SET_DATA_OPER_COMPLETE;
+ cmd->fragment_pref = 0;
+ cmd->adv_data_len = len;
+ os_mbuf_copydata(*data, 0, len, cmd->adv_data);
+
+ os_mbuf_adj(*data, len);
+ *data = os_mbuf_trim_front(*data);
+
+ return ble_hs_hci_cmd_tx(opcode, cmd, sizeof(*cmd) + cmd->adv_data_len,
+ NULL, 0);
+ }
+
+ /* first fragment */
+ op = BLE_HCI_LE_SET_DATA_OPER_FIRST;
+
+ do {
+ cmd->operation = op;
+ cmd->fragment_pref = 0;
+ cmd->adv_data_len = BLE_HCI_MAX_EXT_ADV_DATA_LEN;
+ os_mbuf_copydata(*data, 0, BLE_HCI_MAX_EXT_ADV_DATA_LEN, cmd->adv_data);
+
+ os_mbuf_adj(*data, BLE_HCI_MAX_EXT_ADV_DATA_LEN);
+ *data = os_mbuf_trim_front(*data);
+
+ rc = ble_hs_hci_cmd_tx(opcode, cmd, sizeof(*cmd) + cmd->adv_data_len,
+ NULL, 0);
+ if (rc) {
+ return rc;
+ }
+
+ len -= BLE_HCI_MAX_EXT_ADV_DATA_LEN;
+ op = BLE_HCI_LE_SET_DATA_OPER_INT;
+ } while (len > BLE_HCI_MAX_EXT_ADV_DATA_LEN);
+
+ /* last fragment */
+ cmd->operation = BLE_HCI_LE_SET_DATA_OPER_LAST;
+ cmd->fragment_pref = 0;
+ cmd->adv_data_len = len;
+ os_mbuf_copydata(*data, 0, len, cmd->adv_data);
+
+ os_mbuf_adj(*data, len);
+ *data = os_mbuf_trim_front(*data);
+
+ return ble_hs_hci_cmd_tx(opcode, cmd, sizeof(*cmd) + cmd->adv_data_len,
+ NULL, 0);
+#endif
+}
+
+int
+ble_gap_ext_adv_set_data(uint8_t instance, struct os_mbuf *data)
+{
+ int rc;
+
+ if (instance >= BLE_ADV_INSTANCES) {
+ rc = BLE_HS_EINVAL;
+ goto done;
+ }
+
+ ble_hs_lock();
+ rc = ble_gap_ext_adv_set_data_validate(instance, data);
+ if (rc != 0) {
+ ble_hs_unlock();
+ goto done;
+ }
+
+ rc = ble_gap_ext_adv_set(instance, BLE_HCI_OCF_LE_SET_EXT_ADV_DATA, &data);
+
+ ble_hs_unlock();
+
+done:
+ os_mbuf_free_chain(data);
+ return rc;
+}
+
+static int
+ble_gap_ext_adv_rsp_set_validate(uint8_t instance, struct os_mbuf *data)
+{
+ uint16_t len = OS_MBUF_PKTLEN(data);
+
+ if (!ble_gap_slave[instance].configured) {
+ return BLE_HS_EINVAL;
+ }
+
+ /* not allowed with directed advertising */
+ if (ble_gap_slave[instance].directed && ble_gap_slave[instance].connectable) {
+ return BLE_HS_EINVAL;
+ }
+
+ /* only allowed with scannable advertising */
+ if (!ble_gap_slave[instance].scannable) {
+ return BLE_HS_EINVAL;
+ }
+
+ /* with legacy PDU limited to legacy length */
+ if (ble_gap_slave[instance].legacy_pdu) {
+ if (len > BLE_HS_ADV_MAX_SZ) {
+ return BLE_HS_EINVAL;
+ }
+
+ return 0;
+ }
+
+ /* if already advertising, data must fit in single HCI command
+ * as per BT 5.0 Vol 2, Part E, 7.8.55. Don't bother Controller with such
+ * a request.
+ */
+ if (ble_gap_slave[instance].op == BLE_GAP_OP_S_ADV) {
+ if (len > min(MYNEWT_VAL(BLE_EXT_ADV_MAX_SIZE), 251)) {
+ return BLE_HS_EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+int
+ble_gap_ext_adv_rsp_set_data(uint8_t instance, struct os_mbuf *data)
+{
+ int rc;
+
+ if (instance >= BLE_ADV_INSTANCES) {
+ rc = BLE_HS_EINVAL;
+ goto done;
+ }
+
+ ble_hs_lock();
+ rc = ble_gap_ext_adv_rsp_set_validate(instance, data);
+ if (rc != 0) {
+ ble_hs_unlock();
+ goto done;
+ }
+
+ rc = ble_gap_ext_adv_set(instance, BLE_HCI_OCF_LE_SET_EXT_SCAN_RSP_DATA,
+ &data);
+
+ ble_hs_unlock();
+
+done:
+ os_mbuf_free_chain(data);
+ return rc;
+}
+
+int
+ble_gap_ext_adv_remove(uint8_t instance)
+{
+ struct ble_hci_le_remove_adv_set_cp cmd;
+ uint16_t opcode;
+ int rc;
+
+ if (instance >= BLE_ADV_INSTANCES) {
+ return BLE_HS_EINVAL;
+ }
+
+ ble_hs_lock();
+ if (!ble_gap_slave[instance].configured) {
+ ble_hs_unlock();
+ return BLE_HS_EALREADY;
+ }
+
+ if (ble_gap_slave[instance].op == BLE_GAP_OP_S_ADV) {
+ ble_hs_unlock();
+ return BLE_HS_EBUSY;
+ }
+
+ cmd.adv_handle = instance;
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_REMOVE_ADV_SET);
+
+ rc = ble_hs_hci_cmd_tx(opcode, &cmd, sizeof(cmd), NULL, 0);
+ if (rc != 0) {
+ ble_hs_unlock();
+ return rc;
+ }
+
+ memset(&ble_gap_slave[instance], 0, sizeof(struct ble_gap_slave_state));
+ ble_hs_unlock();
+
+ return 0;
+}
+
+int
+ble_gap_ext_adv_clear(void)
+{
+ int rc;
+ uint8_t instance;
+ uint16_t opcode;
+
+ ble_hs_lock();
+
+ for (instance = 0; instance < BLE_ADV_INSTANCES; instance++) {
+ /* If there is an active instance or periodic adv instance,
+ * Don't send the command
+ * */
+ if ((ble_gap_slave[instance].op == BLE_GAP_OP_S_ADV)) {
+ ble_hs_unlock();
+ return BLE_HS_EBUSY;
+ }
+
+#if MYNEWT_VAL(BLE_PERIODIC_ADV)
+ if (ble_gap_slave[instance].periodic_op == BLE_GAP_OP_S_PERIODIC_ADV) {
+ ble_hs_unlock();
+ return BLE_HS_EBUSY;
+ }
+#endif
+ }
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_CLEAR_ADV_SETS);
+
+ rc = ble_hs_hci_cmd_tx(opcode, NULL, 0, NULL, 0);
+ if (rc != 0) {
+ ble_hs_unlock();
+ return rc;
+ }
+
+ memset(ble_gap_slave, 0, sizeof(ble_gap_slave));
+ ble_hs_unlock();
+
+ return 0;
+}
+
+#if MYNEWT_VAL(BLE_PERIODIC_ADV)
+static int
+ble_gap_periodic_adv_params_tx(uint8_t instance,
+ const struct ble_gap_periodic_adv_params *params)
+
+{
+ struct ble_hci_le_set_periodic_adv_params_cp cmd;
+ uint16_t opcode;
+
+ cmd.adv_handle = instance;
+
+ /* Fill optional fields if application did not specify them. */
+ if (params->itvl_min == 0 && params->itvl_max == 0) {
+ /* TODO defines for those */
+ cmd.min_itvl = htole16(30 / 1.25); //30 ms
+ cmd.max_itvl = htole16(60 / 1.25); //150 ms
+
+ } else {
+ cmd.min_itvl = htole16( params->itvl_min);
+ cmd.max_itvl = htole16(params->itvl_max);
+ }
+
+ if (params->include_tx_power) {
+ cmd.props = BLE_HCI_LE_SET_PERIODIC_ADV_PROP_INC_TX_PWR;
+ } else {
+ cmd.props = 0;
+ }
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_SET_PERIODIC_ADV_PARAMS);
+
+ return ble_hs_hci_cmd_tx(opcode, &cmd, sizeof(cmd), NULL, 0);
+}
+
+static int
+ble_gap_periodic_adv_params_validate(
+ const struct ble_gap_periodic_adv_params *params)
+{
+ if (!params) {
+ return BLE_HS_EINVAL;
+ }
+
+ if (params->itvl_min && params->itvl_min < 6) {
+ return BLE_HS_EINVAL;
+ }
+ if (params->itvl_max && params->itvl_max < 6) {
+ return BLE_HS_EINVAL;
+ }
+ return 0;
+}
+
+int
+ble_gap_periodic_adv_configure(uint8_t instance,
+ const struct ble_gap_periodic_adv_params *params)
+{
+ int rc;
+
+ if (instance >= BLE_ADV_INSTANCES) {
+ return BLE_HS_EINVAL;
+ }
+
+ rc = ble_gap_periodic_adv_params_validate(params);
+ if (rc) {
+ return rc;
+ }
+
+ ble_hs_lock();
+
+ /* The corresponding extended advertising instance should be configured */
+ if (!ble_gap_slave[instance].configured) {
+ ble_hs_unlock();
+ return ENOMEM;
+ }
+
+ /* Periodic advertising shall not be configured while it is already
+ * running.
+ * Bluetooth Core Specification, Section 7.8.61
+ */
+ if (ble_gap_slave[instance].periodic_op == BLE_GAP_OP_S_PERIODIC_ADV) {
+ ble_hs_unlock();
+ return BLE_HS_EINVAL;
+ }
+
+ rc = ble_gap_periodic_adv_params_tx(instance, params);
+ if (rc) {
+ ble_hs_unlock();
+ return rc;
+ }
+
+ ble_gap_slave[instance].periodic_configured = 1;
+
+ ble_hs_unlock();
+
+ return 0;
+}
+
+int
+ble_gap_periodic_adv_start(uint8_t instance)
+{
+ struct ble_hci_le_set_periodic_adv_enable_cp cmd;
+ uint16_t opcode;
+ int rc;
+
+ if (instance >= BLE_ADV_INSTANCES) {
+ return BLE_HS_EINVAL;
+ }
+
+ ble_hs_lock();
+
+ /* Periodic advertising cannot start unless it is configured before */
+ if (!ble_gap_slave[instance].periodic_configured) {
+ ble_hs_unlock();
+ return BLE_HS_EINVAL;
+ }
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_SET_PERIODIC_ADV_ENABLE);
+
+ cmd.enable = 0x01;
+ cmd.adv_handle = instance;
+
+ rc = ble_hs_hci_cmd_tx(opcode, &cmd, sizeof(cmd), NULL, 0);
+ if (rc != 0) {
+ ble_hs_unlock();
+ return rc;
+ }
+
+ ble_gap_slave[instance].periodic_op = BLE_GAP_OP_S_PERIODIC_ADV;
+
+ ble_hs_unlock();
+ return 0;
+}
+
+static int
+ble_gap_periodic_adv_set(uint8_t instance, struct os_mbuf **data)
+{
+ /* In that case we always fit all data in single HCI command */
+#if MYNEWT_VAL(BLE_EXT_ADV_MAX_SIZE) <= BLE_HCI_MAX_PERIODIC_ADV_DATA_LEN
+ static uint8_t buf[sizeof(struct ble_hci_le_set_periodic_adv_data_cp) +
+ MYNEWT_VAL(BLE_EXT_ADV_MAX_SIZE)];
+ struct ble_hci_le_set_periodic_adv_data_cp *cmd = (void *) buf;
+ uint16_t len = OS_MBUF_PKTLEN(*data);
+ uint16_t opcode;
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_SET_PERIODIC_ADV_DATA);
+
+ cmd->adv_handle = instance;
+ cmd->operation = BLE_HCI_LE_SET_DATA_OPER_COMPLETE;
+ cmd->adv_data_len = len;
+ os_mbuf_copydata(*data, 0, len, cmd->adv_data);
+
+ os_mbuf_adj(*data, len);
+ *data = os_mbuf_trim_front(*data);
+
+ return ble_hs_hci_cmd_tx(opcode, cmd, sizeof(*cmd) + cmd->adv_data_len,
+ NULL, 0);
+#else
+ static uint8_t buf[sizeof(struct ble_hci_le_set_periodic_adv_data_cp) +
+ BLE_HCI_MAX_PERIODIC_ADV_DATA_LEN];
+ struct ble_hci_le_set_periodic_adv_data_cp *cmd = (void *) buf;
+ uint16_t len = OS_MBUF_PKTLEN(*data);
+ uint16_t opcode;
+ uint8_t op;
+ int rc;
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_SET_PERIODIC_ADV_DATA);
+ cmd->adv_handle = instance;
+
+ /* Complete data */
+ if (len <= BLE_HCI_MAX_PERIODIC_ADV_DATA_LEN) {
+ cmd->operation = BLE_HCI_LE_SET_DATA_OPER_COMPLETE;
+ cmd->adv_data_len = len;
+ os_mbuf_copydata(*data, 0, len, cmd->adv_data);
+
+ os_mbuf_adj(*data, len);
+ *data = os_mbuf_trim_front(*data);
+
+ return ble_hs_hci_cmd_tx(opcode, cmd, sizeof(*cmd) + cmd->adv_data_len,
+ NULL, 0);
+ }
+
+ /* If the periodic advertising is already enabled, the periodic advertising
+ * the op code shall be nothing but 0x03
+ * Bluetooth Core Specification, section 7.8.62
+ */
+ if (ble_gap_slave[instance].periodic_op == BLE_GAP_OP_S_PERIODIC_ADV) {
+ return BLE_HS_EINVAL;
+ }
+
+ /* First fragment */
+ op = BLE_HCI_LE_SET_DATA_OPER_FIRST;
+
+ do{
+ cmd->operation = op;
+ cmd->adv_data_len = BLE_HCI_MAX_PERIODIC_ADV_DATA_LEN;
+ os_mbuf_copydata(*data, 0, BLE_HCI_MAX_PERIODIC_ADV_DATA_LEN,
+ cmd->adv_data);
+
+ os_mbuf_adj(*data, BLE_HCI_MAX_PERIODIC_ADV_DATA_LEN);
+ *data = os_mbuf_trim_front(*data);
+
+ rc = ble_hs_hci_cmd_tx(opcode, cmd, sizeof(*cmd) + cmd->adv_data_len,
+ NULL, 0);
+ if (rc) {
+ return rc;
+ }
+
+ len -= BLE_HCI_MAX_PERIODIC_ADV_DATA_LEN;
+ op = BLE_HCI_LE_SET_DATA_OPER_INT;
+ } while (len > BLE_HCI_MAX_PERIODIC_ADV_DATA_LEN);
+
+ /* Last fragment */
+ cmd->operation = BLE_HCI_LE_SET_DATA_OPER_LAST;
+ cmd->adv_data_len = len;
+ os_mbuf_copydata(*data, 0, len, cmd->adv_data);
+
+ os_mbuf_adj(*data, len);
+ *data = os_mbuf_trim_front(*data);
+
+ return ble_hs_hci_cmd_tx(opcode, cmd, sizeof(*cmd) + cmd->adv_data_len,
+ NULL, 0);
+#endif
+}
+
+static int
+ble_gap_periodic_adv_set_data_validate(uint8_t instance,
+ struct os_mbuf *data)
+{
+ /* The corresponding extended advertising instance should be configured */
+ if (!ble_gap_slave[instance].configured) {
+ return BLE_HS_EINVAL;
+ }
+
+ if (ble_gap_slave[instance].legacy_pdu) {
+ return BLE_HS_EINVAL;
+ }
+
+ /* One more check states that if the periodic advertising is already
+ * enabled, the operation shall be 0x03 (Complete).
+ * This check is handled during sending the data to the controller, as the
+ * length checks are already checked there, so this saves duplicate code
+ */
+
+ return 0;
+}
+
+int
+ble_gap_periodic_adv_set_data(uint8_t instance, struct os_mbuf *data)
+{
+ int rc;
+ if (instance >= BLE_ADV_INSTANCES) {
+ rc = BLE_HS_EINVAL;
+ goto done;
+ }
+
+ ble_hs_lock();
+
+ rc = ble_gap_periodic_adv_set_data_validate(instance, data);
+ if (rc != 0) {
+ ble_hs_unlock();
+ goto done;
+ }
+
+ rc = ble_gap_periodic_adv_set(instance, &data);
+
+ ble_hs_unlock();
+
+done:
+ os_mbuf_free_chain(data);
+ return rc;
+}
+
+static int
+ble_gap_periodic_adv_stop_no_lock(uint8_t instance)
+{
+ struct ble_hci_le_set_periodic_adv_enable_cp cmd;
+ uint16_t opcode;
+ int rc;
+
+ cmd.enable = 0x00;
+ cmd.adv_handle = instance;
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_SET_PERIODIC_ADV_ENABLE);
+
+ rc = ble_hs_hci_cmd_tx(opcode, &cmd, sizeof(cmd), NULL, 0);
+ if (rc != 0) {
+ return rc;
+ }
+
+ ble_gap_slave[instance].periodic_op = BLE_GAP_OP_NULL;
+
+ return 0;
+}
+
+int
+ble_gap_periodic_adv_stop(uint8_t instance)
+{
+ int rc;
+
+ if (instance >= BLE_ADV_INSTANCES) {
+ return BLE_HS_EINVAL;
+ }
+
+ ble_hs_lock();
+ rc = ble_gap_periodic_adv_stop_no_lock(instance);
+ ble_hs_unlock();
+
+ return rc;
+}
+
+static void
+ble_gap_npl_sync_lost(struct ble_npl_event *ev)
+{
+ struct ble_hs_periodic_sync *psync;
+ struct ble_gap_event event;
+ ble_gap_event_fn *cb;
+ void *cb_arg;
+
+ /* this psync is no longer on list so no lock needed */
+ psync = ble_npl_event_get_arg(ev);
+ cb = psync->cb;
+ cb_arg = psync->cb_arg;
+
+ memset(&event, 0, sizeof event);
+
+ event.type = BLE_GAP_EVENT_PERIODIC_SYNC_LOST;
+ event.periodic_sync_lost.sync_handle = psync->sync_handle;
+ event.periodic_sync_lost.reason = BLE_HS_EDONE;
+
+ /* Free the memory occupied by psync as it is no longer needed */
+ ble_hs_periodic_sync_free(psync);
+
+ ble_gap_event_listener_call(&event);
+ if (cb) {
+ cb(&event, cb_arg);
+ }
+}
+
+int
+ble_gap_periodic_adv_sync_create(const ble_addr_t *addr, uint8_t adv_sid,
+ const struct ble_gap_periodic_sync_params *params,
+ ble_gap_event_fn *cb, void *cb_arg)
+{
+ struct ble_hci_le_periodic_adv_create_sync_cp cmd;
+ struct ble_hs_periodic_sync *psync;
+ uint16_t opcode;
+ int rc;
+
+ if (addr && (addr->type > BLE_ADDR_RANDOM)) {
+ return BLE_HS_EINVAL;
+ }
+ if (adv_sid > 0x0f) {
+ return BLE_HS_EINVAL;
+ }
+ if ((params->skip > 0x1f3) || (params->sync_timeout > 0x4000) ||
+ (params->sync_timeout < 0x0A)) {
+ return BLE_HS_EINVAL;
+ }
+
+ ble_hs_lock();
+
+ /* No sync can be created if another sync is still pending */
+ if (ble_gap_sync.op == BLE_GAP_OP_SYNC) {
+ ble_hs_unlock();
+ return BLE_HS_EBUSY;
+ }
+
+ /* cannot create another sync if already synchronized */
+ if (ble_hs_periodic_sync_find(addr, adv_sid)) {
+ ble_hs_unlock();
+ return BLE_HS_EALREADY;
+ }
+
+ /* preallocate sync element */
+ psync = ble_hs_periodic_sync_alloc();
+ if (!psync) {
+ ble_hs_unlock();
+ return BLE_HS_ENOMEM;
+ }
+
+ ble_npl_event_init(&psync->lost_ev, ble_gap_npl_sync_lost, psync);
+
+ if (addr) {
+ cmd.options = 0x00;
+ cmd.peer_addr_type = addr->type;
+ memcpy(cmd.peer_addr, addr->val, BLE_DEV_ADDR_LEN);
+ } else {
+ cmd.options = 0x01;
+ cmd.peer_addr_type = BLE_ADDR_ANY->type;
+ memcpy(cmd.peer_addr, BLE_ADDR_ANY->val, BLE_DEV_ADDR_LEN);
+ }
+
+ cmd.sid = adv_sid;
+ cmd.skip = params->skip;
+ cmd.sync_timeout = htole16(params->sync_timeout);
+ cmd.sync_cte_type = 0x00;
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_PERIODIC_ADV_CREATE_SYNC);
+ rc = ble_hs_hci_cmd_tx(opcode, &cmd, sizeof(cmd), NULL, 0);
+ if (!rc) {
+ /* This shall be reset upon receiving sync_established event,
+ * or if the sync is cancelled before receiving that event.
+ */
+ ble_gap_sync.op = BLE_GAP_OP_SYNC;
+ ble_gap_sync.cb = cb;
+ ble_gap_sync.cb_arg = cb_arg;
+ ble_gap_sync.psync = psync;
+ } else {
+ ble_hs_periodic_sync_free(psync);
+ }
+
+ ble_hs_unlock();
+
+ return rc;
+}
+
+int
+ble_gap_periodic_adv_sync_create_cancel(void)
+{
+ uint16_t opcode;
+ int rc = 0;
+
+ ble_hs_lock();
+
+ if (ble_gap_sync.op != BLE_GAP_OP_SYNC) {
+ ble_hs_unlock();
+ return BLE_HS_EBUSY;
+ }
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE,
+ BLE_HCI_OCF_LE_PERIODIC_ADV_CREATE_SYNC_CANCEL);
+
+ rc = ble_hs_hci_cmd_tx(opcode, NULL, 0, NULL, 0);
+
+ ble_hs_unlock();
+
+ return rc;
+}
+
+int
+ble_gap_periodic_adv_sync_terminate(uint16_t sync_handle)
+{
+ struct ble_hci_le_periodic_adv_term_sync_cp cmd;
+ struct ble_hs_periodic_sync *psync;
+ uint16_t opcode;
+ int rc;
+
+ ble_hs_lock();
+
+ if (ble_gap_sync.op == BLE_GAP_OP_SYNC) {
+ ble_hs_unlock();
+ return BLE_HS_EBUSY;
+ }
+
+ /* The handle must be in the list. If it doesn't exist, it means
+ * that the sync may have been lost at the same moment in which
+ * the app wants to terminate that sync handle
+ */
+ psync = ble_hs_periodic_sync_find_by_handle(sync_handle);
+ if (!psync) {
+ /* Sync already terminated.*/
+ ble_hs_unlock();
+ return BLE_HS_ENOTCONN;
+ }
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_PERIODIC_ADV_TERM_SYNC);
+
+ cmd.sync_handle = htole16(sync_handle);
+
+ rc = ble_hs_hci_cmd_tx(opcode, &cmd, sizeof(cmd), NULL, 0);
+ if (rc == 0) {
+ /* Remove the handle from the list */
+ ble_hs_periodic_sync_remove(psync);
+
+ /* send sync_lost event, this is to mimic connection behavior and thus
+ * simplify application error handling
+ */
+ ble_npl_eventq_put(ble_hs_evq_get(), &psync->lost_ev);
+ }
+
+ ble_hs_unlock();
+
+ return rc;
+}
+#if MYNEWT_VAL(BLE_PERIODIC_ADV_SYNC_TRANSFER)
+int
+ble_gap_periodic_adv_sync_reporting(uint16_t sync_handle, bool enable)
+{
+ struct ble_hci_le_periodic_adv_receive_enable_cp cmd;
+ struct ble_hs_periodic_sync *psync;
+ uint16_t opcode;
+ int rc;
+
+ ble_hs_lock();
+
+ if (ble_gap_sync.op == BLE_GAP_OP_SYNC) {
+ ble_hs_unlock();
+ return BLE_HS_EBUSY;
+ }
+
+ psync = ble_hs_periodic_sync_find_by_handle(sync_handle);
+ if (!psync) {
+ ble_hs_unlock();
+ return BLE_HS_ENOTCONN;
+ }
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_PERIODIC_ADV_RECEIVE_ENABLE);
+
+ cmd.sync_handle = htole16(sync_handle);
+ cmd.enable = enable ? 0x01 : 0x00;
+
+ rc = ble_hs_hci_cmd_tx(opcode, &cmd, sizeof(cmd), NULL, 0);
+
+ ble_hs_unlock();
+
+ return rc;
+}
+
+int
+ble_gap_periodic_adv_sync_transfer(uint16_t sync_handle, uint16_t conn_handle,
+ uint16_t service_data)
+{
+ struct ble_hci_le_periodic_adv_sync_transfer_cp cmd;
+ struct ble_hci_le_periodic_adv_sync_transfer_rp rsp;
+ struct ble_hs_periodic_sync *psync;
+ struct ble_hs_conn *conn;
+ uint16_t opcode;
+ int rc;
+
+ ble_hs_lock();
+
+ conn = ble_hs_conn_find(conn_handle);
+ if (!conn) {
+ ble_hs_unlock();
+ return BLE_HS_ENOTCONN;
+ }
+
+ psync = ble_hs_periodic_sync_find_by_handle(sync_handle);
+ if (!psync) {
+ ble_hs_unlock();
+ return BLE_HS_ENOTCONN;
+ }
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_PERIODIC_ADV_SYNC_TRANSFER);
+
+ cmd.conn_handle = htole16(conn_handle);
+ cmd.sync_handle = htole16(sync_handle);
+ cmd.service_data = htole16(service_data);
+
+ rc = ble_hs_hci_cmd_tx(opcode, &cmd, sizeof(cmd), &rsp, sizeof(rsp));
+ if (!rc) {
+ BLE_HS_DBG_ASSERT(le16toh(rsp.conn_handle) == conn_handle);
+ }
+
+ ble_hs_unlock();
+
+ return rc;
+}
+
+int
+ble_gap_periodic_adv_sync_set_info(uint8_t instance, uint16_t conn_handle,
+ uint16_t service_data)
+{
+ struct ble_hci_le_periodic_adv_set_info_transfer_cp cmd;
+ struct ble_hci_le_periodic_adv_set_info_transfer_rp rsp;
+ struct ble_hs_conn *conn;
+ uint16_t opcode;
+ int rc;
+
+ if (instance >= BLE_ADV_INSTANCES) {
+ return BLE_HS_EINVAL;
+ }
+
+ ble_hs_lock();
+ if (ble_gap_slave[instance].periodic_op != BLE_GAP_OP_S_PERIODIC_ADV) {
+ /* periodic adv not enabled */
+ ble_hs_unlock();
+ return BLE_HS_EINVAL;
+ }
+
+ conn = ble_hs_conn_find(conn_handle);
+ if (!conn) {
+ ble_hs_unlock();
+ return BLE_HS_ENOTCONN;
+ }
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_PERIODIC_ADV_SET_INFO_TRANSFER);
+
+ cmd.conn_handle = htole16(conn_handle);
+ cmd.adv_handle = instance;
+ cmd.service_data = htole16(service_data);
+
+ rc = ble_hs_hci_cmd_tx(opcode, &cmd, sizeof(cmd), &rsp, sizeof(rsp));
+ if (!rc) {
+ BLE_HS_DBG_ASSERT(le16toh(rsp.conn_handle) == conn_handle);
+ }
+
+ ble_hs_unlock();
+
+ return rc;
+}
+
+static int
+periodic_adv_transfer_enable(uint16_t conn_handle,
+ const struct ble_gap_periodic_sync_params *params)
+{
+ struct ble_hci_le_periodic_adv_sync_transfer_params_cp cmd;
+ struct ble_hci_le_periodic_adv_sync_transfer_params_rp rsp;
+ uint16_t opcode;
+ int rc;
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_PERIODIC_ADV_SYNC_TRANSFER_PARAMS);
+
+ cmd.conn_handle = htole16(conn_handle);
+ cmd.sync_cte_type = 0x00;
+ cmd.mode = params->reports_disabled ? 0x01 : 0x02;
+ cmd.skip = htole16(params->skip);
+ cmd.sync_timeout = htole16(params->sync_timeout);
+
+ rc = ble_hs_hci_cmd_tx(opcode, &cmd, sizeof(cmd), &rsp, sizeof(rsp));
+ if (!rc) {
+ BLE_HS_DBG_ASSERT(le16toh(rsp.conn_handle) == conn_handle);
+ }
+
+ return rc;
+}
+
+int
+ble_gap_periodic_adv_sync_receive(uint16_t conn_handle,
+ const struct ble_gap_periodic_sync_params *params,
+ ble_gap_event_fn *cb, void *cb_arg)
+{
+ struct ble_hs_conn *conn;
+ int rc;
+
+ ble_hs_lock();
+
+ conn = ble_hs_conn_find(conn_handle);
+ if (!conn) {
+ ble_hs_unlock();
+ return BLE_HS_ENOTCONN;
+ }
+
+ if (params) {
+ if (conn->psync) {
+ ble_hs_unlock();
+ return BLE_HS_EALREADY;
+ }
+
+ conn->psync = ble_hs_periodic_sync_alloc();
+ if (!conn->psync) {
+ ble_hs_unlock();
+ return BLE_HS_ENOMEM;
+ }
+
+ rc = periodic_adv_transfer_enable(conn_handle, params);
+ if (rc) {
+ ble_hs_periodic_sync_free(conn->psync);
+ conn->psync = NULL;
+ } else {
+ conn->psync->cb = cb;
+ conn->psync->cb_arg = cb_arg;
+ ble_npl_event_init(&conn->psync->lost_ev, ble_gap_npl_sync_lost,
+ conn->psync);
+ }
+ } else {
+ if (!conn->psync) {
+ ble_hs_unlock();
+ return BLE_HS_EALREADY;
+ }
+
+ rc = periodic_adv_transfer_disable(conn_handle);
+ if (!rc) {
+ ble_hs_periodic_sync_free(conn->psync);
+ conn->psync = NULL;
+ }
+ }
+
+ ble_hs_unlock();
+
+ return rc;
+}
+#endif
+
+int
+ble_gap_add_dev_to_periodic_adv_list(const ble_addr_t *peer_addr,
+ uint8_t adv_sid)
+{
+ struct ble_hci_le_add_dev_to_periodic_adv_list_cp cmd;
+ uint16_t opcode;
+
+ if ((peer_addr->type > BLE_ADDR_RANDOM) || (adv_sid > 0x0f)) {
+ return BLE_ERR_INV_HCI_CMD_PARMS;
+ }
+
+ cmd.peer_addr_type = peer_addr->type;
+ memcpy(cmd.peer_addr, peer_addr->val, BLE_DEV_ADDR_LEN);
+ cmd.sid = adv_sid;
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE,
+ BLE_HCI_OCF_LE_ADD_DEV_TO_PERIODIC_ADV_LIST);
+
+ return ble_hs_hci_cmd_tx(opcode, &cmd, sizeof(cmd), NULL, 0);
+}
+
+int
+ble_gap_rem_dev_from_periodic_adv_list(const ble_addr_t *peer_addr, uint8_t adv_sid)
+{
+ struct ble_hci_le_rem_dev_from_periodic_adv_list_cp cmd;
+ uint16_t opcode;
+
+ if ((peer_addr->type > BLE_ADDR_RANDOM) || (adv_sid > 0x0f)) {
+ return BLE_ERR_INV_HCI_CMD_PARMS;
+ }
+
+ cmd.peer_addr_type = peer_addr->type;
+ memcpy(cmd.peer_addr, peer_addr->val, BLE_DEV_ADDR_LEN);
+ cmd.sid = adv_sid;
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE,
+ BLE_HCI_OCF_LE_REM_DEV_FROM_PERIODIC_ADV_LIST);
+
+ return ble_hs_hci_cmd_tx(opcode, &cmd, sizeof(cmd), NULL, 0);
+}
+
+int
+ble_gap_clear_periodic_adv_list(void)
+{
+ uint16_t opcode;
+ int rc = 0;
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_CLEAR_PERIODIC_ADV_LIST);
+
+ rc = ble_hs_hci_cmd_tx(opcode, NULL, 0, NULL, 0);
+
+ return rc;
+}
+
+int
+ble_gap_read_periodic_adv_list_size(uint8_t *per_adv_list_size)
+{
+ struct ble_hci_le_rd_periodic_adv_list_size_rp rsp;
+ uint16_t opcode;
+ int rc = 0;
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_RD_PERIODIC_ADV_LIST_SIZE);
+
+ rc = ble_hs_hci_cmd_tx(opcode, NULL, 0, &rsp, sizeof(rsp));
+ if (rc != 0) {
+ return rc;
+ }
+
+ *per_adv_list_size = rsp.list_size;
+
+ return 0;
+}
+#endif
+
+/*****************************************************************************
+ * $discovery procedures *
+ *****************************************************************************/
+
+#if MYNEWT_VAL(BLE_EXT_ADV) && NIMBLE_BLE_SCAN
+static int
+ble_gap_ext_disc_tx_params(uint8_t own_addr_type, uint8_t filter_policy,
+ const struct ble_hs_hci_ext_scan_param *uncoded_params,
+ const struct ble_hs_hci_ext_scan_param *coded_params)
+{
+ struct ble_hci_le_set_ext_scan_params_cp *cmd;
+ struct scan_params *params;
+ uint8_t buf[sizeof(*cmd) + 2 * sizeof(*params)];
+ uint8_t len = sizeof(*cmd);
+
+ /* Check own addr type */
+ if (own_addr_type > BLE_HCI_ADV_OWN_ADDR_MAX) {
+ return BLE_ERR_INV_HCI_CMD_PARMS;
+ }
+
+ /* Check scanner filter policy */
+ if (filter_policy > BLE_HCI_SCAN_FILT_MAX) {
+ return BLE_ERR_INV_HCI_CMD_PARMS;
+ }
+
+ cmd = (void *) buf;
+ params = cmd->scans;
+
+ cmd->filter_policy = filter_policy;
+ cmd->own_addr_type = own_addr_type;
+ cmd->phys = 0;
+
+ if (uncoded_params) {
+ cmd->phys |= BLE_HCI_LE_PHY_1M_PREF_MASK;
+
+ params->type = uncoded_params->scan_type;
+ params->itvl = htole16(uncoded_params->scan_itvl);
+ params->window = htole16(uncoded_params->scan_window);
+
+ len += sizeof(*params);
+ params++;
+ }
+
+ if (coded_params) {
+ cmd->phys |= BLE_HCI_LE_PHY_CODED_PREF_MASK;
+
+ params->type = coded_params->scan_type;
+ params->itvl = htole16(coded_params->scan_itvl);
+ params->window = htole16(coded_params->scan_window);
+
+ len += sizeof(*params);
+ params++;
+ }
+
+ if (!cmd->phys) {
+ return BLE_ERR_INV_HCI_CMD_PARMS;
+ }
+
+ return ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_LE,
+ BLE_HCI_OCF_LE_SET_EXT_SCAN_PARAM),
+ cmd, len, NULL, 0);
+}
+
+static int
+ble_gap_ext_disc_enable_tx(uint8_t enable, uint8_t filter_duplicates,
+ uint16_t duration, uint16_t period)
+{
+ struct ble_hci_le_set_ext_scan_enable_cp cmd;
+
+ cmd.enable = enable;
+ cmd.filter_dup = filter_duplicates;
+ cmd.duration = htole16(duration);
+ cmd.period = htole16(period);
+
+ return ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_LE,
+ BLE_HCI_OCF_LE_SET_EXT_SCAN_ENABLE),
+ &cmd, sizeof(cmd), NULL, 0);
+}
+#endif
+#endif
+#if NIMBLE_BLE_SCAN
+#if !MYNEWT_VAL(BLE_EXT_ADV)
+static int
+ble_gap_disc_enable_tx(int enable, int filter_duplicates)
+{
+ struct ble_hci_le_set_scan_enable_cp cmd;
+ uint16_t opcode;
+
+ cmd.enable = !!enable;
+ cmd.filter_duplicates = !!filter_duplicates;
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_SET_SCAN_ENABLE);
+
+ return ble_hs_hci_cmd_tx(opcode, &cmd, sizeof(cmd), NULL, 0);
+}
+
+static int
+ble_gap_disc_tx_params(uint8_t own_addr_type,
+ const struct ble_gap_disc_params *disc_params)
+{
+ struct ble_hci_le_set_scan_params_cp cmd;
+ uint16_t opcode;
+
+ if (disc_params->passive) {
+ cmd.scan_type = BLE_HCI_SCAN_TYPE_PASSIVE;
+ } else {
+ cmd.scan_type = BLE_HCI_SCAN_TYPE_ACTIVE;
+ }
+
+ cmd.scan_itvl = htole16(disc_params->itvl);
+ cmd.scan_window = htole16(disc_params->window);
+ cmd.own_addr_type = own_addr_type;
+ cmd.filter_policy = disc_params->filter_policy;
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_SET_SCAN_PARAMS);
+
+ return ble_hs_hci_cmd_tx(opcode, &cmd, sizeof(cmd), NULL, 0);
+}
+#endif
+
+static int
+ble_gap_disc_disable_tx(void)
+{
+#if MYNEWT_VAL(BLE_EXT_ADV)
+ return ble_gap_ext_disc_enable_tx(0, 0, 0, 0);
+#else
+ return ble_gap_disc_enable_tx(0, 0);
+#endif
+}
+
+static int
+ble_gap_disc_cancel_no_lock(void)
+{
+ int rc;
+
+ STATS_INC(ble_gap_stats, discover_cancel);
+
+ if (!ble_gap_disc_active()) {
+ rc = BLE_HS_EALREADY;
+ goto done;
+ }
+
+ rc = ble_gap_disc_disable_tx();
+ if (rc != 0) {
+ goto done;
+ }
+
+ ble_gap_master_reset_state();
+
+done:
+ if (rc != 0) {
+ STATS_INC(ble_gap_stats, discover_cancel_fail);
+ }
+
+ return rc;
+}
+#endif
+
+int
+ble_gap_disc_cancel(void)
+{
+#if NIMBLE_BLE_SCAN
+ int rc;
+
+ ble_hs_lock();
+ rc = ble_gap_disc_cancel_no_lock();
+ ble_hs_unlock();
+
+ return rc;
+#else
+ return BLE_HS_ENOTSUP;
+#endif
+}
+
+#if NIMBLE_BLE_SCAN
+static int
+ble_gap_disc_ext_validate(uint8_t own_addr_type)
+{
+ if (own_addr_type > BLE_HCI_ADV_OWN_ADDR_MAX) {
+ return BLE_HS_EINVAL;
+ }
+
+ if (ble_gap_conn_active()) {
+ return BLE_HS_EBUSY;
+ }
+
+ if (ble_gap_disc_active()) {
+ return BLE_HS_EALREADY;
+ }
+
+ if (!ble_hs_is_enabled()) {
+ return BLE_HS_EDISABLED;
+ }
+
+ if (ble_gap_is_preempted()) {
+ return BLE_HS_EPREEMPTED;
+ }
+
+ return 0;
+}
+#endif
+
+#if MYNEWT_VAL(BLE_EXT_ADV) && NIMBLE_BLE_SCAN
+static void
+ble_gap_ext_disc_fill_dflts(uint8_t limited,
+ struct ble_hs_hci_ext_scan_param *disc_params)
+{
+ if (disc_params->scan_itvl == 0) {
+ if (limited) {
+ disc_params->scan_itvl = BLE_GAP_LIM_DISC_SCAN_INT;
+ } else {
+ disc_params->scan_itvl = BLE_GAP_SCAN_FAST_INTERVAL_MIN;
+ }
+ }
+
+ if (disc_params->scan_window == 0) {
+ if (limited) {
+ disc_params->scan_window = BLE_GAP_LIM_DISC_SCAN_WINDOW;
+ } else {
+ disc_params->scan_window = BLE_GAP_SCAN_FAST_WINDOW;
+ }
+ }
+}
+
+static void
+ble_gap_ext_scan_params_to_hci(const struct ble_gap_ext_disc_params *params,
+ struct ble_hs_hci_ext_scan_param *hci_params)
+{
+
+ memset(hci_params, 0, sizeof(*hci_params));
+
+ if (params->passive) {
+ hci_params->scan_type = BLE_HCI_SCAN_TYPE_PASSIVE;
+ } else {
+ hci_params->scan_type = BLE_HCI_SCAN_TYPE_ACTIVE;
+ }
+
+ hci_params->scan_itvl = params->itvl;
+ hci_params->scan_window = params->window;
+}
+#endif
+
+int
+ble_gap_ext_disc(uint8_t own_addr_type, uint16_t duration, uint16_t period,
+ uint8_t filter_duplicates, uint8_t filter_policy,
+ uint8_t limited,
+ const struct ble_gap_ext_disc_params *uncoded_params,
+ const struct ble_gap_ext_disc_params *coded_params,
+ ble_gap_event_fn *cb, void *cb_arg)
+{
+#if NIMBLE_BLE_SCAN && MYNEWT_VAL(BLE_EXT_ADV)
+ struct ble_hs_hci_ext_scan_param ucp;
+ struct ble_hs_hci_ext_scan_param cp;
+ int rc;
+
+ STATS_INC(ble_gap_stats, discover);
+
+ ble_hs_lock();
+
+ rc = ble_gap_disc_ext_validate(own_addr_type);
+ if (rc != 0) {
+ goto done;
+ }
+
+ /* Make a copy of the parameter structure and fill unspecified values with
+ * defaults.
+ */
+
+ if (uncoded_params) {
+ ble_gap_ext_scan_params_to_hci(uncoded_params, &ucp);
+ ble_gap_ext_disc_fill_dflts(limited, &ucp);
+
+ /* XXX: We should do it only once */
+ if (!uncoded_params->passive) {
+ rc = ble_hs_id_use_addr(own_addr_type);
+ if (rc != 0) {
+ goto done;
+ }
+ }
+ }
+
+ if (coded_params) {
+ ble_gap_ext_scan_params_to_hci(coded_params, &cp);
+ ble_gap_ext_disc_fill_dflts(limited, &cp);
+
+ /* XXX: We should do it only once */
+ if (!coded_params->passive) {
+ rc = ble_hs_id_use_addr(own_addr_type);
+ if (rc != 0) {
+ goto done;
+ }
+ }
+ }
+
+ ble_gap_master.disc.limited = limited;
+ ble_gap_master.cb = cb;
+ ble_gap_master.cb_arg = cb_arg;
+
+ rc = ble_gap_ext_disc_tx_params(own_addr_type, filter_policy,
+ uncoded_params ? &ucp : NULL,
+ coded_params ? &cp : NULL);
+ if (rc != 0) {
+ goto done;
+ }
+
+ ble_gap_master.op = BLE_GAP_OP_M_DISC;
+
+ rc = ble_gap_ext_disc_enable_tx(1, filter_duplicates, duration, period);
+ if (rc != 0) {
+ ble_gap_master_reset_state();
+ goto done;
+ }
+
+ rc = 0;
+
+done:
+ ble_hs_unlock();
+
+ if (rc != 0) {
+ STATS_INC(ble_gap_stats, discover_fail);
+ }
+ return rc;
+#else
+ return BLE_HS_ENOTSUP;
+#endif
+}
+
+#if NIMBLE_BLE_SCAN && !MYNEWT_VAL(BLE_EXT_ADV)
+static void
+ble_gap_disc_fill_dflts(struct ble_gap_disc_params *disc_params)
+{
+ if (disc_params->itvl == 0) {
+ if (disc_params->limited) {
+ disc_params->itvl = BLE_GAP_LIM_DISC_SCAN_INT;
+ } else {
+ disc_params->itvl = BLE_GAP_SCAN_FAST_INTERVAL_MIN;
+ }
+ }
+
+ if (disc_params->window == 0) {
+ if (disc_params->limited) {
+ disc_params->window = BLE_GAP_LIM_DISC_SCAN_WINDOW;
+ } else {
+ disc_params->window = BLE_GAP_SCAN_FAST_WINDOW;
+ }
+ }
+}
+
+static int
+ble_gap_disc_validate(uint8_t own_addr_type,
+ const struct ble_gap_disc_params *disc_params)
+{
+ if (disc_params == NULL) {
+ return BLE_HS_EINVAL;
+ }
+
+ /* Check interval and window */
+ if ((disc_params->itvl < BLE_HCI_SCAN_ITVL_MIN) ||
+ (disc_params->itvl > BLE_HCI_SCAN_ITVL_MAX) ||
+ (disc_params->window < BLE_HCI_SCAN_WINDOW_MIN) ||
+ (disc_params->window > BLE_HCI_SCAN_WINDOW_MAX) ||
+ (disc_params->itvl < disc_params->window)) {
+ return BLE_HS_EINVAL;
+ }
+
+ /* Check scanner filter policy */
+ if (disc_params->filter_policy > BLE_HCI_SCAN_FILT_MAX) {
+ return BLE_HS_EINVAL;
+ }
+
+ return ble_gap_disc_ext_validate(own_addr_type);
+}
+#endif
+
+int
+ble_gap_disc(uint8_t own_addr_type, int32_t duration_ms,
+ const struct ble_gap_disc_params *disc_params,
+ ble_gap_event_fn *cb, void *cb_arg)
+{
+#if NIMBLE_BLE_SCAN
+#if MYNEWT_VAL(BLE_EXT_ADV)
+ struct ble_gap_ext_disc_params p = {0};
+
+ p.itvl = disc_params->itvl;
+ p.passive = disc_params->passive;
+ p.window = disc_params->window;
+
+ if (duration_ms == BLE_HS_FOREVER) {
+ duration_ms = 0;
+ } else if (duration_ms == 0) {
+ duration_ms = BLE_GAP_DISC_DUR_DFLT;
+ }
+
+ return ble_gap_ext_disc(own_addr_type, duration_ms/10, 0,
+ disc_params->filter_duplicates,
+ disc_params->filter_policy, disc_params->limited,
+ &p, NULL, cb, cb_arg);
+#else
+ struct ble_gap_disc_params params;
+ uint32_t duration_ticks = 0;
+ int rc;
+
+ STATS_INC(ble_gap_stats, discover);
+
+ ble_hs_lock();
+
+ /* Make a copy of the parameter strcuture and fill unspecified values with
+ * defaults.
+ */
+ params = *disc_params;
+ ble_gap_disc_fill_dflts(&params);
+
+ rc = ble_gap_disc_validate(own_addr_type, &params);
+ if (rc != 0) {
+ goto done;
+ }
+
+ if (duration_ms == 0) {
+ duration_ms = BLE_GAP_DISC_DUR_DFLT;
+ }
+
+ if (duration_ms != BLE_HS_FOREVER) {
+ rc = ble_npl_time_ms_to_ticks(duration_ms, &duration_ticks);
+ if (rc != 0) {
+ /* Duration too great. */
+ rc = BLE_HS_EINVAL;
+ goto done;
+ }
+ }
+
+ if (!params.passive) {
+ rc = ble_hs_id_use_addr(own_addr_type);
+ if (rc != 0) {
+ goto done;
+ }
+ }
+
+ ble_gap_master.disc.limited = params.limited;
+ ble_gap_master.cb = cb;
+ ble_gap_master.cb_arg = cb_arg;
+
+ BLE_HS_LOG(INFO, "GAP procedure initiated: discovery; ");
+ ble_gap_log_disc(own_addr_type, duration_ms, &params);
+ BLE_HS_LOG(INFO, "\n");
+
+ rc = ble_gap_disc_tx_params(own_addr_type, &params);
+ if (rc != 0) {
+ goto done;
+ }
+
+ ble_gap_master.op = BLE_GAP_OP_M_DISC;
+
+ rc = ble_gap_disc_enable_tx(1, params.filter_duplicates);
+ if (rc != 0) {
+ ble_gap_master_reset_state();
+ goto done;
+ }
+
+ if (duration_ms != BLE_HS_FOREVER) {
+ ble_gap_master_set_timer(duration_ticks);
+ }
+
+ rc = 0;
+
+done:
+ ble_hs_unlock();
+
+ if (rc != 0) {
+ STATS_INC(ble_gap_stats, discover_fail);
+ }
+ return rc;
+#endif
+#else
+ return BLE_HS_ENOTSUP;
+#endif
+}
+
+int
+ble_gap_disc_active(void)
+{
+ /* Assume read is atomic; mutex not necessary. */
+ return ble_gap_master.op == BLE_GAP_OP_M_DISC;
+}
+
+#if MYNEWT_VAL(BLE_ROLE_CENTRAL) && !MYNEWT_VAL(BLE_EXT_ADV)
+/*****************************************************************************
+ * $connection establishment procedures *
+ *****************************************************************************/
+
+static int
+ble_gap_conn_create_tx(uint8_t own_addr_type, const ble_addr_t *peer_addr,
+ const struct ble_gap_conn_params *params)
+{
+ struct ble_hci_le_create_conn_cp cmd;
+ uint16_t opcode;
+
+ cmd.scan_itvl = htole16(params->scan_itvl);
+ cmd.scan_window = htole16(params->scan_window);
+ if (peer_addr == NULL) {
+ /* Application wants to connect to any device in the white list. The
+ * peer address type and peer address fields are ignored by the
+ * controller; fill them with dummy values.
+ */
+ cmd.filter_policy = BLE_HCI_CONN_FILT_USE_WL;
+ cmd.peer_addr_type = 0;
+ memset(cmd.peer_addr, 0, sizeof(cmd.peer_addr));
+ } else {
+ cmd.filter_policy = BLE_HCI_CONN_FILT_NO_WL;
+ cmd.peer_addr_type = peer_addr->type;
+ memcpy(cmd.peer_addr, peer_addr->val, sizeof(cmd.peer_addr));
+ }
+
+ cmd.own_addr_type = own_addr_type;
+ cmd.min_conn_itvl = htole16(params->itvl_min);
+ cmd.max_conn_itvl = htole16(params->itvl_max);
+ cmd.conn_latency = htole16(params->latency);
+ cmd.tmo = htole16(params->supervision_timeout);
+ cmd.min_ce = htole16(params->min_ce_len);
+ cmd.max_ce = htole16(params->max_ce_len);
+
+ opcode = BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_CREATE_CONN);
+
+ return ble_hs_hci_cmd_tx(opcode, &cmd, sizeof(cmd), NULL, 0);
+}
+#endif
+
+#if MYNEWT_VAL(BLE_EXT_ADV)
+#if MYNEWT_VAL(BLE_ROLE_CENTRAL)
+static int
+ble_gap_check_conn_params(uint8_t phy, const struct ble_gap_conn_params *params)
+{
+ if (phy != BLE_HCI_LE_PHY_2M) {
+ /* Check scan interval and window */
+ if ((params->scan_itvl < BLE_HCI_SCAN_ITVL_MIN) ||
+ (params->scan_itvl > BLE_HCI_SCAN_ITVL_MAX) ||
+ (params->scan_window < BLE_HCI_SCAN_WINDOW_MIN) ||
+ (params->scan_window > BLE_HCI_SCAN_WINDOW_MAX) ||
+ (params->scan_itvl < params->scan_window)) {
+ return BLE_ERR_INV_HCI_CMD_PARMS;
+ }
+ }
+ /* Check connection interval min */
+ if ((params->itvl_min < BLE_HCI_CONN_ITVL_MIN) ||
+ (params->itvl_min > BLE_HCI_CONN_ITVL_MAX)) {
+ return BLE_ERR_INV_HCI_CMD_PARMS;
+ }
+ /* Check connection interval max */
+ if ((params->itvl_max < BLE_HCI_CONN_ITVL_MIN) ||
+ (params->itvl_max > BLE_HCI_CONN_ITVL_MAX) ||
+ (params->itvl_max < params->itvl_min)) {
+ return BLE_ERR_INV_HCI_CMD_PARMS;
+ }
+
+ /* Check connection latency */
+ if ((params->latency < BLE_HCI_CONN_LATENCY_MIN) ||
+ (params->latency > BLE_HCI_CONN_LATENCY_MAX)) {
+ return BLE_ERR_INV_HCI_CMD_PARMS;
+ }
+
+ /* Check supervision timeout */
+ if ((params->supervision_timeout < BLE_HCI_CONN_SPVN_TIMEOUT_MIN) ||
+ (params->supervision_timeout > BLE_HCI_CONN_SPVN_TIMEOUT_MAX)) {
+ return BLE_ERR_INV_HCI_CMD_PARMS;
+ }
+
+ /* Check connection event length */
+ if (params->min_ce_len > params->max_ce_len) {
+ return BLE_ERR_INV_HCI_CMD_PARMS;
+ }
+
+ return 0;
+}
+
+static int
+ble_gap_ext_conn_create_tx(
+ uint8_t own_addr_type, const ble_addr_t *peer_addr, uint8_t phy_mask,
+ const struct ble_gap_conn_params *phy_1m_conn_params,
+ const struct ble_gap_conn_params *phy_2m_conn_params,
+ const struct ble_gap_conn_params *phy_coded_conn_params)
+{
+ struct ble_hci_le_ext_create_conn_cp *cmd;
+ struct conn_params *params;
+ uint8_t buf[sizeof(*cmd) + 3 * sizeof(*params)];
+ uint8_t len = sizeof(*cmd);
+ int rc;
+
+ /* Check own addr type */
+ if (own_addr_type > BLE_HCI_ADV_OWN_ADDR_MAX) {
+ return BLE_ERR_INV_HCI_CMD_PARMS;
+ }
+
+ if (phy_mask > (BLE_HCI_LE_PHY_1M_PREF_MASK |
+ BLE_HCI_LE_PHY_2M_PREF_MASK |
+ BLE_HCI_LE_PHY_CODED_PREF_MASK)) {
+ return BLE_ERR_INV_HCI_CMD_PARMS;
+ }
+
+ cmd = (void *) buf;
+ params = cmd->conn_params;
+
+ if (peer_addr == NULL) {
+ /* Application wants to connect to any device in the white list. The
+ * peer address type and peer address fields are ignored by the
+ * controller; fill them with dummy values.
+ */
+ cmd->filter_policy = BLE_HCI_CONN_FILT_USE_WL;
+ cmd->peer_addr_type = 0;
+ memset(cmd->peer_addr, 0, sizeof(cmd->peer_addr));
+ } else {
+ /* Check peer addr type */
+ if (peer_addr->type > BLE_HCI_CONN_PEER_ADDR_MAX) {
+ return BLE_ERR_INV_HCI_CMD_PARMS;
+ }
+
+ cmd->filter_policy = BLE_HCI_CONN_FILT_NO_WL;
+ cmd->peer_addr_type = peer_addr->type;
+ memcpy(cmd->peer_addr, peer_addr->val, sizeof(cmd->peer_addr));
+ }
+
+ cmd->own_addr_type = own_addr_type;
+ cmd->init_phy_mask = phy_mask;
+
+ if (phy_mask & BLE_GAP_LE_PHY_1M_MASK) {
+ rc = ble_gap_check_conn_params(BLE_HCI_LE_PHY_1M, phy_1m_conn_params);
+ if (rc) {
+ return rc;
+ }
+
+ params->scan_itvl = htole16(phy_1m_conn_params->scan_itvl);
+ params->scan_window = htole16(phy_1m_conn_params->scan_window);
+ params->conn_min_itvl = htole16(phy_1m_conn_params->itvl_min);
+ params->conn_max_itvl = htole16(phy_1m_conn_params->itvl_max);
+ params->conn_latency = htole16(phy_1m_conn_params->latency);
+ params->supervision_timeout = htole16(phy_1m_conn_params->supervision_timeout);
+ params->min_ce = htole16(phy_1m_conn_params->min_ce_len);
+ params->max_ce = htole16(phy_1m_conn_params->max_ce_len);
+
+ params++;
+ len += sizeof(*params);
+ }
+
+ if (phy_mask & BLE_GAP_LE_PHY_2M_MASK) {
+ rc = ble_gap_check_conn_params(BLE_HCI_LE_PHY_2M, phy_2m_conn_params);
+ if (rc) {
+ return rc;
+ }
+
+ params->scan_itvl = htole16(phy_2m_conn_params->scan_itvl);
+ params->scan_window = htole16(phy_2m_conn_params->scan_window);
+ params->conn_min_itvl = htole16(phy_2m_conn_params->itvl_min);
+ params->conn_max_itvl = htole16(phy_2m_conn_params->itvl_max);
+ params->conn_latency = htole16(phy_2m_conn_params->latency);
+ params->supervision_timeout = htole16(phy_2m_conn_params->supervision_timeout);
+ params->min_ce = htole16(phy_2m_conn_params->min_ce_len);
+ params->max_ce = htole16(phy_2m_conn_params->max_ce_len);
+
+ params++;
+ len += sizeof(*params);
+ }
+
+ if (phy_mask & BLE_GAP_LE_PHY_CODED_MASK) {
+ rc = ble_gap_check_conn_params(BLE_HCI_LE_PHY_CODED, phy_coded_conn_params);
+ if (rc) {
+ return rc;
+ }
+
+ params->scan_itvl = htole16(phy_coded_conn_params->scan_itvl);
+ params->scan_window = htole16(phy_coded_conn_params->scan_window);
+ params->conn_min_itvl = htole16(phy_coded_conn_params->itvl_min);
+ params->conn_max_itvl = htole16(phy_coded_conn_params->itvl_max);
+ params->conn_latency = htole16(phy_coded_conn_params->latency);
+ params->supervision_timeout = htole16(phy_coded_conn_params->supervision_timeout);
+ params->min_ce = htole16(phy_coded_conn_params->min_ce_len);
+ params->max_ce = htole16(phy_coded_conn_params->max_ce_len);
+
+ params++;
+ len += sizeof(*params);
+ }
+
+ return ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_LE,
+ BLE_HCI_OCF_LE_EXT_CREATE_CONN),
+ cmd, len, NULL, 0);
+}
+#endif
+
+/**
+ * Initiates a connect procedure.
+ *
+ * @param own_addr_type The type of address the stack should use for
+ * itself during connection establishment.
+ * o BLE_OWN_ADDR_PUBLIC
+ * o BLE_OWN_ADDR_RANDOM
+ * o BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT
+ * o BLE_OWN_ADDR_RPA_RANDOM_DEFAULT
+ * @param peer_addr The address of the peer to connect to.
+ * If this parameter is NULL, the white list
+ * is used.
+ * @param duration_ms The duration of the discovery procedure.
+ * On expiration, the procedure ends and a
+ * BLE_GAP_EVENT_DISC_COMPLETE event is
+ * reported. Units are milliseconds.
+ * @param phy_mask Define on which PHYs connection attempt should
+ * be done
+ * @param phy_1m_conn_params Additional arguments specifying the
+ * particulars of the connect procedure. When
+ * BLE_GAP_LE_PHY_1M_MASK is set in phy_mask
+ * this parameter can be specify to null for
+ * default values.
+ * @param phy_2m_conn_params Additional arguments specifying the
+ * particulars of the connect procedure. When
+ * BLE_GAP_LE_PHY_2M_MASK is set in phy_mask
+ * this parameter can be specify to null for
+ * default values.
+ * @param phy_coded_conn_params Additional arguments specifying the
+ * particulars of the connect procedure. When
+ * BLE_GAP_LE_PHY_CODED_MASK is set in
+ * phy_mask this parameter can be specify to
+ * null for default values.
+ * @param cb The callback to associate with this connect
+ * procedure. When the connect procedure
+ * completes, the result is reported through
+ * this callback. If the connect procedure
+ * succeeds, the connection inherits this
+ * callback as its event-reporting mechanism.
+ * @param cb_arg The optional argument to pass to the callback
+ * function.
+ *
+ * @return 0 on success;
+ * BLE_HS_EALREADY if a connection attempt is
+ * already in progress;
+ * BLE_HS_EBUSY if initiating a connection is not
+ * possible because scanning is in progress;
+ * BLE_HS_EDONE if the specified peer is already
+ * connected;
+ * Other nonzero on error.
+ */
+int
+ble_gap_ext_connect(uint8_t own_addr_type, const ble_addr_t *peer_addr,
+ int32_t duration_ms, uint8_t phy_mask,
+ const struct ble_gap_conn_params *phy_1m_conn_params,
+ const struct ble_gap_conn_params *phy_2m_conn_params,
+ const struct ble_gap_conn_params *phy_coded_conn_params,
+ ble_gap_event_fn *cb, void *cb_arg)
+{
+#if MYNEWT_VAL(BLE_ROLE_CENTRAL)
+ ble_npl_time_t duration_ticks;
+ int rc;
+
+ STATS_INC(ble_gap_stats, initiate);
+
+ ble_hs_lock();
+
+ if (ble_gap_conn_active()) {
+ rc = BLE_HS_EALREADY;
+ goto done;
+ }
+
+ if (ble_gap_disc_active()) {
+ rc = BLE_HS_EBUSY;
+ goto done;
+ }
+
+ if (!ble_hs_is_enabled()) {
+ return BLE_HS_EDISABLED;
+ }
+
+ if (ble_gap_is_preempted()) {
+ rc = BLE_HS_EPREEMPTED;
+ goto done;
+ }
+
+ if (!ble_hs_conn_can_alloc()) {
+ rc = BLE_HS_ENOMEM;
+ goto done;
+ }
+
+ if (peer_addr &&
+ peer_addr->type != BLE_ADDR_PUBLIC &&
+ peer_addr->type != BLE_ADDR_RANDOM &&
+ peer_addr->type != BLE_ADDR_PUBLIC_ID &&
+ peer_addr->type != BLE_ADDR_RANDOM_ID) {
+
+ rc = BLE_HS_EINVAL;
+ goto done;
+ }
+
+ if ((phy_mask & BLE_GAP_LE_PHY_1M_MASK) && phy_1m_conn_params == NULL) {
+ phy_1m_conn_params = &ble_gap_conn_params_dflt;
+ }
+
+ if ((phy_mask & BLE_GAP_LE_PHY_2M_MASK) && phy_2m_conn_params == NULL) {
+ phy_2m_conn_params = &ble_gap_conn_params_dflt;
+ }
+
+ if ((phy_mask & BLE_GAP_LE_PHY_CODED_MASK) &&
+ phy_coded_conn_params == NULL) {
+
+ phy_coded_conn_params = &ble_gap_conn_params_dflt;
+ }
+
+ if (duration_ms == 0) {
+ duration_ms = BLE_GAP_CONN_DUR_DFLT;
+ }
+
+ if (duration_ms != BLE_HS_FOREVER) {
+ rc = ble_npl_time_ms_to_ticks(duration_ms, &duration_ticks);
+ if (rc != 0) {
+ /* Duration too great. */
+ rc = BLE_HS_EINVAL;
+ goto done;
+ }
+ }
+
+ /* Verify peer not already connected. */
+ if (ble_hs_conn_find_by_addr(peer_addr) != NULL) {
+ rc = BLE_HS_EDONE;
+ goto done;
+ }
+
+ /* XXX: Verify conn_params. */
+
+ rc = ble_hs_id_use_addr(own_addr_type);
+ if (rc != 0) {
+ goto done;
+ }
+
+ ble_gap_master.cb = cb;
+ ble_gap_master.cb_arg = cb_arg;
+ ble_gap_master.conn.using_wl = peer_addr == NULL;
+ ble_gap_master.conn.our_addr_type = own_addr_type;
+
+ ble_gap_master.op = BLE_GAP_OP_M_CONN;
+
+ rc = ble_gap_ext_conn_create_tx(own_addr_type, peer_addr, phy_mask,
+ phy_1m_conn_params, phy_2m_conn_params,
+ phy_coded_conn_params);
+ if (rc != 0) {
+ ble_gap_master_reset_state();
+ goto done;
+ }
+
+ if (duration_ms != BLE_HS_FOREVER) {
+ ble_gap_master_set_timer(duration_ticks);
+ }
+
+ rc = 0;
+
+done:
+ ble_hs_unlock();
+
+ if (rc != 0) {
+ STATS_INC(ble_gap_stats, initiate_fail);
+ }
+ return rc;
+#else
+ return BLE_HS_ENOTSUP;
+#endif
+
+}
+#endif
+
+int
+ble_gap_connect(uint8_t own_addr_type, const ble_addr_t *peer_addr,
+ int32_t duration_ms,
+ const struct ble_gap_conn_params *conn_params,
+ ble_gap_event_fn *cb, void *cb_arg)
+{
+#if MYNEWT_VAL(BLE_ROLE_CENTRAL)
+#if MYNEWT_VAL(BLE_EXT_ADV)
+ return ble_gap_ext_connect(own_addr_type, peer_addr, duration_ms,
+ BLE_GAP_LE_PHY_1M_MASK,
+ conn_params, NULL, NULL, cb, cb_arg);
+#else
+ uint32_t duration_ticks;
+ int rc;
+
+ STATS_INC(ble_gap_stats, initiate);
+
+ ble_hs_lock();
+
+ if (ble_gap_conn_active()) {
+ rc = BLE_HS_EALREADY;
+ goto done;
+ }
+
+ if (ble_gap_disc_active()) {
+ rc = BLE_HS_EBUSY;
+ goto done;
+ }
+
+ if (!ble_hs_is_enabled()) {
+ rc = BLE_HS_EDISABLED;
+ goto done;
+ }
+
+ if (ble_gap_is_preempted()) {
+ rc = BLE_HS_EPREEMPTED;
+ goto done;
+ }
+
+ if (!ble_hs_conn_can_alloc()) {
+ rc = BLE_HS_ENOMEM;
+ goto done;
+ }
+
+ if (peer_addr &&
+ peer_addr->type != BLE_ADDR_PUBLIC &&
+ peer_addr->type != BLE_ADDR_RANDOM &&
+ peer_addr->type != BLE_ADDR_PUBLIC_ID &&
+ peer_addr->type != BLE_ADDR_RANDOM_ID) {
+
+ rc = BLE_HS_EINVAL;
+ goto done;
+ }
+
+ if (conn_params == NULL) {
+ conn_params = &ble_gap_conn_params_dflt;
+ }
+
+ if (duration_ms == 0) {
+ duration_ms = BLE_GAP_CONN_DUR_DFLT;
+ }
+
+ if (duration_ms != BLE_HS_FOREVER) {
+ rc = ble_npl_time_ms_to_ticks(duration_ms, &duration_ticks);
+ if (rc != 0) {
+ /* Duration too great. */
+ rc = BLE_HS_EINVAL;
+ goto done;
+ }
+ }
+
+ /* Verify peer not already connected. */
+ if (ble_hs_conn_find_by_addr(peer_addr) != NULL) {
+ rc = BLE_HS_EDONE;
+ goto done;
+ }
+
+ /* XXX: Verify conn_params. */
+
+ rc = ble_hs_id_use_addr(own_addr_type);
+ if (rc != 0) {
+ goto done;
+ }
+
+ BLE_HS_LOG(INFO, "GAP procedure initiated: connect; ");
+ ble_gap_log_conn(own_addr_type, peer_addr, conn_params);
+ BLE_HS_LOG(INFO, "\n");
+
+ ble_gap_master.cb = cb;
+ ble_gap_master.cb_arg = cb_arg;
+ ble_gap_master.conn.using_wl = peer_addr == NULL;
+ ble_gap_master.conn.our_addr_type = own_addr_type;
+
+ ble_gap_master.op = BLE_GAP_OP_M_CONN;
+
+ rc = ble_gap_conn_create_tx(own_addr_type, peer_addr,
+ conn_params);
+ if (rc != 0) {
+ ble_gap_master_reset_state();
+ goto done;
+ }
+
+ if (duration_ms != BLE_HS_FOREVER) {
+ ble_gap_master_set_timer(duration_ticks);
+ }
+
+ rc = 0;
+
+done:
+ ble_hs_unlock();
+
+ if (rc != 0) {
+ STATS_INC(ble_gap_stats, initiate_fail);
+ }
+ return rc;
+#endif
+#else
+ return BLE_HS_ENOTSUP;
+#endif
+
+}
+
+int
+ble_gap_conn_active(void)
+{
+ /* Assume read is atomic; mutex not necessary. */
+ return ble_gap_master.op == BLE_GAP_OP_M_CONN;
+}
+
+/*****************************************************************************
+ * $terminate connection procedure *
+ *****************************************************************************/
+int
+ble_gap_terminate_with_conn(struct ble_hs_conn *conn, uint8_t hci_reason)
+{
+ struct ble_hci_lc_disconnect_cp cmd;
+ int rc;
+
+ BLE_HS_DBG_ASSERT(ble_hs_locked_by_cur_task());
+ if (conn->bhc_flags & BLE_HS_CONN_F_TERMINATING) {
+ return BLE_HS_EALREADY;
+ }
+
+ BLE_HS_LOG(INFO, "GAP procedure initiated: terminate connection; "
+ "conn_handle=%d hci_reason=%d\n",
+ conn->bhc_handle, hci_reason);
+
+ cmd.conn_handle = htole16(conn->bhc_handle);
+ cmd.reason = hci_reason;
+
+ rc = ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_LINK_CTRL,
+ BLE_HCI_OCF_DISCONNECT_CMD),
+ &cmd, sizeof(cmd), NULL, 0);
+ if (rc != 0) {
+ return rc;
+ }
+
+ conn->bhc_flags |= BLE_HS_CONN_F_TERMINATING;
+ return 0;
+}
+
+int
+ble_gap_terminate(uint16_t conn_handle, uint8_t hci_reason)
+{
+ struct ble_hs_conn *conn;
+ int rc;
+
+ STATS_INC(ble_gap_stats, terminate);
+
+ ble_hs_lock();
+
+ conn = ble_hs_conn_find(conn_handle);
+ if (conn == NULL) {
+ rc = BLE_HS_ENOTCONN;
+ goto done;
+ }
+
+ rc = ble_gap_terminate_with_conn(conn, hci_reason);
+
+done:
+ ble_hs_unlock();
+
+ if (rc != 0) {
+ STATS_INC(ble_gap_stats, terminate_fail);
+ }
+ return rc;
+}
+
+/*****************************************************************************
+ * $cancel *
+ *****************************************************************************/
+
+static int
+ble_gap_conn_cancel_tx(void)
+{
+ int rc;
+
+ rc = ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_LE,
+ BLE_HCI_OCF_LE_CREATE_CONN_CANCEL),
+ NULL, 0, NULL, 0);
+ if (rc != 0) {
+ return rc;
+ }
+
+ return 0;
+}
+
+#if NIMBLE_BLE_CONNECT
+static int
+ble_gap_conn_cancel_no_lock(void)
+{
+ int rc;
+
+ STATS_INC(ble_gap_stats, cancel);
+
+ if (!ble_gap_conn_active()) {
+ rc = BLE_HS_EALREADY;
+ goto done;
+ }
+
+ BLE_HS_LOG(INFO, "GAP procedure initiated: cancel connection\n");
+
+ rc = ble_gap_conn_cancel_tx();
+ if (rc != 0) {
+ goto done;
+ }
+
+ ble_gap_master.conn.cancel = 1;
+ rc = 0;
+
+done:
+ if (rc != 0) {
+ STATS_INC(ble_gap_stats, cancel_fail);
+ }
+
+ return rc;
+}
+#endif
+
+int
+ble_gap_conn_cancel(void)
+{
+#if MYNEWT_VAL(BLE_ROLE_CENTRAL)
+ int rc;
+
+ ble_hs_lock();
+ rc = ble_gap_conn_cancel_no_lock();
+ ble_hs_unlock();
+
+ return rc;
+#else
+ return BLE_HS_ENOTSUP;
+#endif
+
+}
+
+/*****************************************************************************
+ * $update connection parameters *
+ *****************************************************************************/
+
+#if NIMBLE_BLE_CONNECT
+static struct ble_gap_update_entry *
+ble_gap_update_entry_alloc(void)
+{
+ struct ble_gap_update_entry *entry;
+
+ entry = os_memblock_get(&ble_gap_update_entry_pool);
+ if (entry != NULL) {
+ memset(entry, 0, sizeof *entry);
+ }
+
+ return entry;
+}
+#endif
+
+static void
+ble_gap_update_entry_free(struct ble_gap_update_entry *entry)
+{
+ int rc;
+
+ if (entry != NULL) {
+#if MYNEWT_VAL(BLE_HS_DEBUG)
+ memset(entry, 0xff, sizeof *entry);
+#endif
+ rc = os_memblock_put(&ble_gap_update_entry_pool, entry);
+ BLE_HS_DBG_ASSERT_EVAL(rc == 0);
+ }
+}
+
+static struct ble_gap_update_entry *
+ble_gap_update_entry_find(uint16_t conn_handle,
+ struct ble_gap_update_entry **out_prev)
+{
+ struct ble_gap_update_entry *entry;
+ struct ble_gap_update_entry *prev;
+
+ BLE_HS_DBG_ASSERT(ble_hs_locked_by_cur_task());
+
+ prev = NULL;
+ SLIST_FOREACH(entry, &ble_gap_update_entries, next) {
+ if (entry->conn_handle == conn_handle) {
+ break;
+ }
+
+ prev = entry;
+ }
+
+ if (out_prev != NULL) {
+ *out_prev = prev;
+ }
+
+ return entry;
+}
+
+static struct ble_gap_update_entry *
+ble_gap_update_entry_remove(uint16_t conn_handle)
+{
+ struct ble_gap_update_entry *entry;
+ struct ble_gap_update_entry *prev;
+
+ entry = ble_gap_update_entry_find(conn_handle, &prev);
+ if (entry != NULL) {
+ if (prev == NULL) {
+ SLIST_REMOVE_HEAD(&ble_gap_update_entries, next);
+ } else {
+ SLIST_NEXT(prev, next) = SLIST_NEXT(entry, next);
+ }
+ ble_hs_timer_resched();
+ }
+
+ return entry;
+}
+
+#if NIMBLE_BLE_CONNECT
+static void
+ble_gap_update_l2cap_cb(uint16_t conn_handle, int status, void *arg)
+{
+ struct ble_gap_update_entry *entry;
+
+ /* Report failures and rejections. Success gets reported when the
+ * controller sends the connection update complete event.
+ */
+
+ ble_hs_lock();
+ entry = ble_gap_update_entry_remove(conn_handle);
+ ble_hs_unlock();
+
+ if (entry != NULL) {
+ ble_gap_update_entry_free(entry);
+ if (status != 0) {
+ ble_gap_update_notify(conn_handle, status);
+ }
+ /* On success let's wait for the controller to notify about update */
+ }
+}
+
+static int
+ble_gap_tx_param_pos_reply(uint16_t conn_handle,
+ struct ble_gap_upd_params *params)
+{
+ struct ble_hci_le_rem_conn_param_rr_cp cmd;
+
+ cmd.conn_handle = htole16(conn_handle);
+ cmd.conn_itvl_min = htole16(params->itvl_min);
+ cmd.conn_itvl_max = htole16(params->itvl_max);
+ cmd.conn_latency = htole16(params->latency);
+ cmd.supervision_timeout = htole16(params->supervision_timeout);
+ cmd.min_ce = htole16(params->min_ce_len);
+ cmd.max_ce = htole16(params->max_ce_len);
+
+ return ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_LE,
+ BLE_HCI_OCF_LE_REM_CONN_PARAM_RR),
+ &cmd, sizeof(cmd), NULL, 0);
+}
+
+static int
+ble_gap_tx_param_neg_reply(uint16_t conn_handle, uint8_t reject_reason)
+{
+ struct ble_hci_le_rem_conn_params_nrr_cp cmd;
+
+ cmd.conn_handle = htole16(conn_handle);
+ cmd.reason = reject_reason;
+
+ return ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_LE,
+ BLE_HCI_OCF_LE_REM_CONN_PARAM_NRR),
+ &cmd, sizeof(cmd), NULL, 0);
+}
+#endif
+
+void
+ble_gap_rx_param_req(const struct ble_hci_ev_le_subev_rem_conn_param_req *ev)
+{
+#if NIMBLE_BLE_CONNECT
+ struct ble_gap_upd_params peer_params;
+ struct ble_gap_upd_params self_params;
+ struct ble_gap_event event;
+ uint16_t conn_handle;
+ int rc;
+
+ memset(&event, 0, sizeof event);
+
+ peer_params.itvl_min = le16toh(ev->min_interval);
+ peer_params.itvl_max = le16toh(ev->max_interval);
+ peer_params.latency = le16toh(ev->latency);
+ peer_params.supervision_timeout = le16toh(ev->timeout);
+ peer_params.min_ce_len = 0;
+ peer_params.max_ce_len = 0;
+
+ /* Copy the peer params into the self params to make it easy on the
+ * application. The application callback will change only the fields which
+ * it finds unsuitable.
+ */
+ self_params = peer_params;
+
+ conn_handle = le16toh(ev->conn_handle);
+
+ memset(&event, 0, sizeof event);
+ event.type = BLE_GAP_EVENT_CONN_UPDATE_REQ;
+ event.conn_update_req.conn_handle = conn_handle;
+ event.conn_update_req.self_params = &self_params;
+ event.conn_update_req.peer_params = &peer_params;
+ rc = ble_gap_call_conn_event_cb(&event, conn_handle);
+ if (rc == 0) {
+ rc = ble_gap_tx_param_pos_reply(conn_handle, &self_params);
+ if (rc != 0) {
+ ble_gap_update_failed(conn_handle, rc);
+ }
+ } else {
+ ble_gap_tx_param_neg_reply(conn_handle, rc);
+ }
+#endif
+}
+
+#if NIMBLE_BLE_CONNECT
+static int
+ble_gap_update_tx(uint16_t conn_handle,
+ const struct ble_gap_upd_params *params)
+{
+ struct ble_hci_le_conn_update_cp cmd;
+
+ cmd.conn_handle = htole16(conn_handle);
+ cmd.conn_itvl_min = htole16(params->itvl_min);
+ cmd.conn_itvl_max = htole16(params->itvl_max);
+ cmd.conn_latency = htole16(params->latency);
+ cmd.supervision_timeout = htole16(params->supervision_timeout);
+ cmd.min_ce_len = htole16(params->min_ce_len);
+ cmd.max_ce_len = htole16(params->max_ce_len);
+
+ return ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_LE,
+ BLE_HCI_OCF_LE_CONN_UPDATE),
+ &cmd, sizeof(cmd), NULL, 0);
+}
+
+static bool
+ble_gap_validate_conn_params(const struct ble_gap_upd_params *params)
+{
+
+ /* Requirements from Bluetooth spec. v4.2 [Vol 2, Part E], 7.8.18 */
+ if (params->itvl_min > params->itvl_max) {
+ return false;
+ }
+
+ if (params->itvl_min < 0x0006 || params->itvl_max > 0x0C80) {
+ return false;
+ }
+
+ if (params->latency > 0x01F3) {
+ return false;
+ }
+
+ /* According to specification mentioned above we should make sure that:
+ * supervision_timeout_ms > (1 + latency) * 2 * max_interval_ms
+ * =>
+ * supervision_timeout * 10 ms > (1 + latency) * 2 * itvl_max * 1.25ms
+ */
+ if (params->supervision_timeout <=
+ (((1 + params->latency) * params->itvl_max) / 4)) {
+ return false;
+ }
+
+ return true;
+}
+#endif
+
+int
+ble_gap_update_params(uint16_t conn_handle,
+ const struct ble_gap_upd_params *params)
+{
+#if NIMBLE_BLE_CONNECT
+ struct ble_l2cap_sig_update_params l2cap_params;
+ struct ble_gap_update_entry *entry;
+ struct ble_gap_update_entry *dup;
+ struct ble_hs_conn *conn;
+ int l2cap_update;
+ int rc;
+
+ l2cap_update = 0;
+
+ /* Validate parameters with a spec */
+ if (!ble_gap_validate_conn_params(params)) {
+ return BLE_HS_EINVAL;
+ }
+
+ STATS_INC(ble_gap_stats, update);
+ memset(&l2cap_params, 0, sizeof l2cap_params);
+ entry = NULL;
+
+ ble_hs_lock();
+
+ conn = ble_hs_conn_find(conn_handle);
+ if (conn == NULL) {
+ rc = BLE_HS_ENOTCONN;
+ goto done;
+ }
+
+ /* Don't allow two concurrent updates to the same connection. */
+ dup = ble_gap_update_entry_find(conn_handle, NULL);
+ if (dup != NULL) {
+ rc = BLE_HS_EALREADY;
+ goto done;
+ }
+
+ entry = ble_gap_update_entry_alloc();
+ if (entry == NULL) {
+ rc = BLE_HS_ENOMEM;
+ goto done;
+ }
+
+ entry->conn_handle = conn_handle;
+ entry->params = *params;
+
+ entry->exp_os_ticks = ble_npl_time_get() +
+ ble_npl_time_ms_to_ticks32(BLE_GAP_UPDATE_TIMEOUT_MS);
+
+ BLE_HS_LOG(INFO, "GAP procedure initiated: ");
+ ble_gap_log_update(conn_handle, params);
+ BLE_HS_LOG(INFO, "\n");
+
+ /*
+ * If LL update procedure is not supported on this connection and we are
+ * the slave, fail over to the L2CAP update procedure.
+ */
+ if ((conn->supported_feat & BLE_HS_HCI_LE_FEAT_CONN_PARAM_REQUEST) == 0 &&
+ !(conn->bhc_flags & BLE_HS_CONN_F_MASTER)) {
+ l2cap_update = 1;
+ rc = 0;
+ } else {
+ rc = ble_gap_update_tx(conn_handle, params);
+ }
+
+done:
+ ble_hs_unlock();
+
+ if (!l2cap_update) {
+ ble_hs_timer_resched();
+ } else {
+ ble_gap_update_to_l2cap(params, &l2cap_params);
+
+ rc = ble_l2cap_sig_update(conn_handle, &l2cap_params,
+ ble_gap_update_l2cap_cb, NULL);
+ }
+
+ ble_hs_lock();
+ if (rc == 0) {
+ SLIST_INSERT_HEAD(&ble_gap_update_entries, entry, next);
+ } else {
+ ble_gap_update_entry_free(entry);
+ STATS_INC(ble_gap_stats, update_fail);
+ }
+ ble_hs_unlock();
+
+ return rc;
+#else
+ return BLE_HS_ENOTSUP;
+#endif
+}
+
+/*****************************************************************************
+ * $security *
+ *****************************************************************************/
+int
+ble_gap_security_initiate(uint16_t conn_handle)
+{
+#if NIMBLE_BLE_SM
+ struct ble_store_value_sec value_sec;
+ struct ble_store_key_sec key_sec;
+ struct ble_hs_conn_addrs addrs;
+ ble_hs_conn_flags_t conn_flags;
+ struct ble_hs_conn *conn;
+ int rc;
+
+ STATS_INC(ble_gap_stats, security_initiate);
+
+ ble_hs_lock();
+ conn = ble_hs_conn_find(conn_handle);
+ if (conn != NULL) {
+ conn_flags = conn->bhc_flags;
+ ble_hs_conn_addrs(conn, &addrs);
+
+ memset(&key_sec, 0, sizeof key_sec);
+ key_sec.peer_addr = addrs.peer_id_addr;
+ }
+ ble_hs_unlock();
+
+ if (conn == NULL) {
+ rc = BLE_HS_ENOTCONN;
+ goto done;
+ }
+
+ if (conn_flags & BLE_HS_CONN_F_MASTER) {
+ /* Search the security database for an LTK for this peer. If one
+ * is found, perform the encryption procedure rather than the pairing
+ * procedure.
+ */
+ rc = ble_store_read_peer_sec(&key_sec, &value_sec);
+ if (rc == 0 && value_sec.ltk_present) {
+ rc = ble_sm_enc_initiate(conn_handle, value_sec.key_size,
+ value_sec.ltk, value_sec.ediv,
+ value_sec.rand_num,
+ value_sec.authenticated);
+ if (rc != 0) {
+ goto done;
+ }
+ } else {
+ rc = ble_sm_pair_initiate(conn_handle);
+ if (rc != 0) {
+ goto done;
+ }
+ }
+ } else {
+ rc = ble_sm_slave_initiate(conn_handle);
+ if (rc != 0) {
+ goto done;
+ }
+ }
+
+ rc = 0;
+
+done:
+ if (rc != 0) {
+ STATS_INC(ble_gap_stats, security_initiate_fail);
+ }
+
+ return rc;
+#else
+ return BLE_HS_ENOTSUP;
+#endif
+}
+
+int
+ble_gap_pair_initiate(uint16_t conn_handle)
+{
+ int rc;
+
+ rc = ble_sm_pair_initiate(conn_handle);
+
+ return rc;
+}
+
+int
+ble_gap_encryption_initiate(uint16_t conn_handle,
+ uint8_t key_size,
+ const uint8_t *ltk,
+ uint16_t ediv,
+ uint64_t rand_val,
+ int auth)
+{
+#if NIMBLE_BLE_SM
+ ble_hs_conn_flags_t conn_flags;
+ int rc;
+
+ rc = ble_hs_atomic_conn_flags(conn_handle, &conn_flags);
+ if (rc != 0) {
+ return rc;
+ }
+
+ if (!(conn_flags & BLE_HS_CONN_F_MASTER)) {
+ return BLE_HS_EROLE;
+ }
+
+ rc = ble_sm_enc_initiate(conn_handle, key_size, ltk,
+ ediv, rand_val, auth);
+ return rc;
+#else
+ return BLE_HS_ENOTSUP;
+#endif
+}
+
+int
+ble_gap_unpair(const ble_addr_t *peer_addr)
+{
+ struct ble_hs_conn *conn;
+
+ if (ble_addr_cmp(peer_addr, BLE_ADDR_ANY) == 0) {
+ return BLE_HS_EINVAL;
+ }
+
+ ble_hs_lock();
+
+ conn = ble_hs_conn_find_by_addr(peer_addr);
+ if (conn != NULL) {
+ ble_gap_terminate_with_conn(conn, BLE_ERR_REM_USER_CONN_TERM);
+ }
+
+ ble_hs_unlock();
+
+ ble_hs_pvcy_remove_entry(peer_addr->type,
+ peer_addr->val);
+
+ return ble_store_util_delete_peer(peer_addr);
+}
+
+int
+ble_gap_unpair_oldest_peer(void)
+{
+ ble_addr_t oldest_peer_id_addr;
+ int num_peers;
+ int rc;
+
+ rc = ble_store_util_bonded_peers(
+ &oldest_peer_id_addr, &num_peers, 1);
+ if (rc != 0) {
+ return rc;
+ }
+
+ if (num_peers == 0) {
+ return BLE_HS_ENOENT;
+ }
+
+ rc = ble_gap_unpair(&oldest_peer_id_addr);
+ if (rc != 0) {
+ return rc;
+ }
+
+ return 0;
+}
+
+int
+ble_gap_unpair_oldest_except(const ble_addr_t *peer_addr)
+{
+ ble_addr_t peer_id_addrs[MYNEWT_VAL(BLE_STORE_MAX_BONDS)];
+ int num_peers;
+ int rc, i;
+
+ rc = ble_store_util_bonded_peers(
+ &peer_id_addrs[0], &num_peers, MYNEWT_VAL(BLE_STORE_MAX_BONDS));
+ if (rc != 0) {
+ return rc;
+ }
+
+ if (num_peers == 0) {
+ return BLE_HS_ENOENT;
+ }
+
+ for (i = 0; i < num_peers; i++) {
+ if (ble_addr_cmp(peer_addr, &peer_id_addrs[i]) != 0) {
+ break;
+ }
+ }
+
+ if (i >= num_peers) {
+ return BLE_HS_ENOMEM;
+ }
+
+ return ble_gap_unpair(&peer_id_addrs[i]);
+}
+
+void
+ble_gap_passkey_event(uint16_t conn_handle,
+ struct ble_gap_passkey_params *passkey_params)
+{
+#if NIMBLE_BLE_SM
+ struct ble_gap_event event;
+
+ BLE_HS_LOG(DEBUG, "send passkey action request %d\n",
+ passkey_params->action);
+
+ memset(&event, 0, sizeof event);
+ event.type = BLE_GAP_EVENT_PASSKEY_ACTION;
+ event.passkey.conn_handle = conn_handle;
+ event.passkey.params = *passkey_params;
+ ble_gap_call_conn_event_cb(&event, conn_handle);
+#endif
+}
+
+void
+ble_gap_enc_event(uint16_t conn_handle, int status, int security_restored)
+{
+#if NIMBLE_BLE_SM
+ struct ble_gap_event event;
+
+ memset(&event, 0, sizeof event);
+ event.type = BLE_GAP_EVENT_ENC_CHANGE;
+ event.enc_change.conn_handle = conn_handle;
+ event.enc_change.status = status;
+
+ ble_gap_event_listener_call(&event);
+ ble_gap_call_conn_event_cb(&event, conn_handle);
+
+ if (status == 0) {
+ if (security_restored) {
+ ble_gatts_bonding_restored(conn_handle);
+ } else {
+ ble_gatts_bonding_established(conn_handle);
+ }
+ }
+#endif
+}
+
+void
+ble_gap_identity_event(uint16_t conn_handle)
+{
+#if NIMBLE_BLE_SM
+ struct ble_gap_event event;
+
+ BLE_HS_LOG(DEBUG, "send identity changed");
+
+ memset(&event, 0, sizeof event);
+ event.type = BLE_GAP_EVENT_IDENTITY_RESOLVED;
+ event.identity_resolved.conn_handle = conn_handle;
+ ble_gap_call_conn_event_cb(&event, conn_handle);
+#endif
+}
+
+int
+ble_gap_repeat_pairing_event(const struct ble_gap_repeat_pairing *rp)
+{
+#if NIMBLE_BLE_SM
+ struct ble_gap_event event;
+ int rc;
+
+ memset(&event, 0, sizeof event);
+ event.type = BLE_GAP_EVENT_REPEAT_PAIRING;
+ event.repeat_pairing = *rp;
+ rc = ble_gap_call_conn_event_cb(&event, rp->conn_handle);
+ return rc;
+#else
+ return 0;
+#endif
+}
+
+/*****************************************************************************
+ * $rssi *
+ *****************************************************************************/
+
+int
+ble_gap_conn_rssi(uint16_t conn_handle, int8_t *out_rssi)
+{
+ int rc;
+
+ rc = ble_hs_hci_util_read_rssi(conn_handle, out_rssi);
+ return rc;
+}
+
+/*****************************************************************************
+ * $notify *
+ *****************************************************************************/
+
+void
+ble_gap_notify_rx_event(uint16_t conn_handle, uint16_t attr_handle,
+ struct os_mbuf *om, int is_indication)
+{
+#if !MYNEWT_VAL(BLE_GATT_NOTIFY) && !MYNEWT_VAL(BLE_GATT_INDICATE)
+ return;
+#endif
+
+ struct ble_gap_event event;
+
+ memset(&event, 0, sizeof event);
+ event.type = BLE_GAP_EVENT_NOTIFY_RX;
+ event.notify_rx.conn_handle = conn_handle;
+ event.notify_rx.attr_handle = attr_handle;
+ event.notify_rx.om = om;
+ event.notify_rx.indication = is_indication;
+ ble_gap_event_listener_call(&event);
+ ble_gap_call_conn_event_cb(&event, conn_handle);
+
+ os_mbuf_free_chain(event.notify_rx.om);
+}
+
+void
+ble_gap_notify_tx_event(int status, uint16_t conn_handle, uint16_t attr_handle,
+ int is_indication)
+{
+#if MYNEWT_VAL(BLE_GATT_NOTIFY) || MYNEWT_VAL(BLE_GATT_INDICATE)
+ struct ble_gap_event event;
+
+ memset(&event, 0, sizeof event);
+ event.type = BLE_GAP_EVENT_NOTIFY_TX;
+ event.notify_tx.conn_handle = conn_handle;
+ event.notify_tx.status = status;
+ event.notify_tx.attr_handle = attr_handle;
+ event.notify_tx.indication = is_indication;
+ ble_gap_event_listener_call(&event);
+ ble_gap_call_conn_event_cb(&event, conn_handle);
+#endif
+}
+
+/*****************************************************************************
+ * $subscribe *
+ *****************************************************************************/
+
+void
+ble_gap_subscribe_event(uint16_t conn_handle, uint16_t attr_handle,
+ uint8_t reason,
+ uint8_t prev_notify, uint8_t cur_notify,
+ uint8_t prev_indicate, uint8_t cur_indicate)
+{
+ struct ble_gap_event event;
+
+ BLE_HS_DBG_ASSERT(prev_notify != cur_notify ||
+ prev_indicate != cur_indicate);
+ BLE_HS_DBG_ASSERT(reason == BLE_GAP_SUBSCRIBE_REASON_WRITE ||
+ reason == BLE_GAP_SUBSCRIBE_REASON_TERM ||
+ reason == BLE_GAP_SUBSCRIBE_REASON_RESTORE);
+
+ memset(&event, 0, sizeof event);
+ event.type = BLE_GAP_EVENT_SUBSCRIBE;
+ event.subscribe.conn_handle = conn_handle;
+ event.subscribe.attr_handle = attr_handle;
+ event.subscribe.reason = reason;
+ event.subscribe.prev_notify = !!prev_notify;
+ event.subscribe.cur_notify = !!cur_notify;
+ event.subscribe.prev_indicate = !!prev_indicate;
+ event.subscribe.cur_indicate = !!cur_indicate;
+
+ ble_gap_event_listener_call(&event);
+ ble_gap_call_conn_event_cb(&event, conn_handle);
+}
+
+/*****************************************************************************
+ * $mtu *
+ *****************************************************************************/
+
+void
+ble_gap_mtu_event(uint16_t conn_handle, uint16_t cid, uint16_t mtu)
+{
+ struct ble_gap_event event;
+
+ memset(&event, 0, sizeof event);
+ event.type = BLE_GAP_EVENT_MTU;
+ event.mtu.conn_handle = conn_handle;
+ event.mtu.channel_id = cid;
+ event.mtu.value = mtu;
+
+ ble_gap_event_listener_call(&event);
+ ble_gap_call_conn_event_cb(&event, conn_handle);
+}
+
+/*****************************************************************************
+ * $preempt *
+ *****************************************************************************/
+
+void
+ble_gap_preempt_no_lock(void)
+{
+ int rc;
+ int i;
+
+ (void)rc;
+ (void)i;
+
+#if NIMBLE_BLE_ADVERTISE
+#if MYNEWT_VAL(BLE_EXT_ADV)
+ for (i = 0; i < BLE_ADV_INSTANCES; i++) {
+ rc = ble_gap_ext_adv_stop_no_lock(i);
+ if (rc == 0) {
+ ble_gap_slave[i].preempted = 1;
+ }
+ }
+#else
+ rc = ble_gap_adv_stop_no_lock();
+ if (rc == 0) {
+ ble_gap_slave[0].preempted = 1;
+ }
+#endif
+#endif
+
+#if NIMBLE_BLE_CONNECT
+ rc = ble_gap_conn_cancel_no_lock();
+ if (rc == 0) {
+ ble_gap_master.preempted_op = BLE_GAP_OP_M_CONN;
+ }
+#endif
+
+#if NIMBLE_BLE_SCAN
+ rc = ble_gap_disc_cancel_no_lock();
+ if (rc == 0) {
+ ble_gap_master.preempted_op = BLE_GAP_OP_M_DISC;
+ }
+#endif
+}
+
+/**
+ * @brief Preempts the GAP if it is not already preempted.
+ *
+ * Aborts all active GAP procedures and prevents new ones from being started.
+ * This function is used to ensure an idle GAP so that the controller's
+ * resolving list can be modified. When done accessing the resolving list, the
+ * caller must call `ble_gap_preempt_done()` to permit new GAP procedures.
+ *
+ * On preemption, all aborted GAP procedures are reported with a status or
+ * reason code of BLE_HS_EPREEMPTED. An attempt to initiate a new GAP
+ * procedure during preemption fails with a return code of BLE_HS_EPREEMPTED.
+ */
+void
+ble_gap_preempt(void)
+{
+ ble_hs_lock();
+
+ if (!ble_gap_is_preempted()) {
+ ble_gap_preempt_no_lock();
+ }
+
+ ble_hs_unlock();
+}
+
+/**
+ * Takes GAP out of the preempted state, allowing new GAP procedures to be
+ * initiated. This function should only be called after a call to
+ * `ble_gap_preempt()`.
+ */
+
+static struct ble_npl_mutex preempt_done_mutex;
+
+void
+ble_gap_preempt_done(void)
+{
+ struct ble_gap_event event;
+ ble_gap_event_fn *master_cb;
+ void *master_arg;
+ int disc_preempted;
+ int i;
+ static struct {
+ ble_gap_event_fn *cb;
+ void *arg;
+ } slaves[BLE_ADV_INSTANCES];
+
+ disc_preempted = 0;
+
+ /* Protects slaves from accessing by multiple threads */
+ ble_npl_mutex_pend(&preempt_done_mutex, 0xFFFFFFFF);
+ memset(slaves, 0, sizeof(slaves));
+
+ ble_hs_lock();
+
+ for (i = 0; i < BLE_ADV_INSTANCES; i++) {
+ if (ble_gap_slave[i].preempted) {
+ ble_gap_slave[i].preempted = 0;
+ slaves[i].cb = ble_gap_slave[i].cb;
+ slaves[i].arg = ble_gap_slave[i].cb_arg;
+ }
+ }
+
+ if (ble_gap_master.preempted_op == BLE_GAP_OP_M_DISC) {
+ ble_gap_master.preempted_op = BLE_GAP_OP_NULL;
+ disc_preempted = 1;
+ master_cb = ble_gap_master.cb;
+ master_arg = ble_gap_master.cb_arg;
+ }
+
+ ble_hs_unlock();
+
+ event.type = BLE_GAP_EVENT_ADV_COMPLETE;
+ event.adv_complete.reason = BLE_HS_EPREEMPTED;
+
+ for (i = 0; i < BLE_ADV_INSTANCES; i++) {
+ if (slaves[i].cb) {
+#if MYNEWT_VAL(BLE_EXT_ADV)
+ event.adv_complete.instance = i;
+ event.adv_complete.conn_handle = i;
+#endif
+ ble_gap_call_event_cb(&event, slaves[i].cb, slaves[i].arg);
+ }
+ }
+ ble_npl_mutex_release(&preempt_done_mutex);
+
+ if (disc_preempted) {
+ event.type = BLE_GAP_EVENT_DISC_COMPLETE;
+ event.disc_complete.reason = BLE_HS_EPREEMPTED;
+ ble_gap_call_event_cb(&event, master_cb, master_arg);
+ }
+}
+
+int
+ble_gap_event_listener_register(struct ble_gap_event_listener *listener,
+ ble_gap_event_fn *fn, void *arg)
+{
+ struct ble_gap_event_listener *evl = NULL;
+ int rc;
+
+ SLIST_FOREACH(evl, &ble_gap_event_listener_list, link) {
+ if (evl == listener) {
+ break;
+ }
+ }
+
+ if (!evl) {
+ if (fn) {
+ memset(listener, 0, sizeof(*listener));
+ listener->fn = fn;
+ listener->arg = arg;
+ SLIST_INSERT_HEAD(&ble_gap_event_listener_list, listener, link);
+ rc = 0;
+ } else {
+ rc = BLE_HS_EINVAL;
+ }
+ } else {
+ rc = BLE_HS_EALREADY;
+ }
+
+ return rc;
+}
+
+int
+ble_gap_event_listener_unregister(struct ble_gap_event_listener *listener)
+{
+ struct ble_gap_event_listener *evl = NULL;
+ int rc;
+
+ /*
+ * We check if element exists on the list only for sanity to let caller
+ * know whether it registered its listener before.
+ */
+
+ SLIST_FOREACH(evl, &ble_gap_event_listener_list, link) {
+ if (evl == listener) {
+ break;
+ }
+ }
+
+ if (!evl) {
+ rc = BLE_HS_ENOENT;
+ } else {
+ SLIST_REMOVE(&ble_gap_event_listener_list, listener,
+ ble_gap_event_listener, link);
+ rc = 0;
+ }
+
+ return rc;
+}
+
+static int
+ble_gap_event_listener_call(struct ble_gap_event *event)
+{
+ struct ble_gap_event_listener *evl = NULL;
+
+ SLIST_FOREACH(evl, &ble_gap_event_listener_list, link) {
+ evl->fn(event, evl->arg);
+ }
+
+ return 0;
+}
+
+/*****************************************************************************
+ * $init *
+ *****************************************************************************/
+
+int
+ble_gap_init(void)
+{
+ int rc;
+
+ memset(&ble_gap_master, 0, sizeof(ble_gap_master));
+ memset(ble_gap_slave, 0, sizeof(ble_gap_slave));
+
+#if MYNEWT_VAL(BLE_PERIODIC_ADV)
+ memset(&ble_gap_sync, 0, sizeof(ble_gap_sync));
+#endif
+
+ ble_npl_mutex_init(&preempt_done_mutex);
+
+ SLIST_INIT(&ble_gap_update_entries);
+ SLIST_INIT(&ble_gap_event_listener_list);
+
+ rc = os_mempool_init(&ble_gap_update_entry_pool,
+ MYNEWT_VAL(BLE_GAP_MAX_PENDING_CONN_PARAM_UPDATE),
+ sizeof (struct ble_gap_update_entry),
+ ble_gap_update_entry_mem,
+ "ble_gap_update");
+ switch (rc) {
+ case 0:
+ break;
+ case OS_ENOMEM:
+ rc = BLE_HS_ENOMEM;
+ goto err;
+ default:
+ rc = BLE_HS_EOS;
+ goto err;
+ }
+
+ rc = stats_init_and_reg(
+ STATS_HDR(ble_gap_stats), STATS_SIZE_INIT_PARMS(ble_gap_stats,
+ STATS_SIZE_32), STATS_NAME_INIT_PARMS(ble_gap_stats), "ble_gap");
+ if (rc != 0) {
+ goto err;
+ }
+
+ return 0;
+
+err:
+ return rc;
+}