diff options
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.c | 6073 |
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(¶ms); + + rc = ble_gap_disc_validate(own_addr_type, ¶ms); + 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, ¶ms); + BLE_HS_LOG(INFO, "\n"); + + rc = ble_gap_disc_tx_params(own_addr_type, ¶ms); + 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; +} |