/* * 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 #include #include #include "syscfg/syscfg.h" #include "os/os.h" #include "nimble/ble.h" #include "nimble/nimble_opt.h" #include "nimble/hci_common.h" #include "nimble/ble_hci_trans.h" #include "controller/ble_ll.h" #include "controller/ble_ll_utils.h" #include "controller/ble_ll_hci.h" #include "controller/ble_ll_conn.h" #include "controller/ble_ll_ctrl.h" #include "controller/ble_ll_scan.h" #include "controller/ble_ll_adv.h" #include "ble_ll_conn_priv.h" /* * Used to limit the rate at which we send the number of completed packets * event to the host. This is the os time at which we can send an event. */ static ble_npl_time_t g_ble_ll_last_num_comp_pkt_evt; extern uint8_t *g_ble_ll_conn_comp_ev; #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV) static const uint8_t ble_ll_valid_conn_phy_mask = (BLE_HCI_LE_PHY_1M_PREF_MASK #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_2M_PHY) | BLE_HCI_LE_PHY_2M_PREF_MASK #endif #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CODED_PHY) | BLE_HCI_LE_PHY_CODED_PREF_MASK #endif ); static const uint8_t ble_ll_conn_required_phy_mask = (BLE_HCI_LE_PHY_1M_PREF_MASK #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CODED_PHY) | BLE_HCI_LE_PHY_CODED_PREF_MASK #endif ); #endif /** * Allocate an event to send a connection complete event when initiating * * @return int 0: success -1: failure */ static int ble_ll_init_alloc_conn_comp_ev(void) { int rc; uint8_t *evbuf; rc = 0; evbuf = g_ble_ll_conn_comp_ev; if (evbuf == NULL) { evbuf = ble_hci_trans_buf_alloc(BLE_HCI_TRANS_BUF_EVT_HI); if (!evbuf) { rc = -1; } else { g_ble_ll_conn_comp_ev = evbuf; } } return rc; } /** * Called to check that the connection parameters are within range * * @param itvl_min * @param itvl_max * @param latency * @param spvn_tmo * * @return int BLE_ERR_INV_HCI_CMD_PARMS if invalid parameters, 0 otherwise */ int ble_ll_conn_hci_chk_conn_params(uint16_t itvl_min, uint16_t itvl_max, uint16_t latency, uint16_t spvn_tmo) { uint32_t spvn_tmo_usecs; uint32_t min_spvn_tmo_usecs; if ((itvl_min > itvl_max) || (itvl_min < BLE_HCI_CONN_ITVL_MIN) || (itvl_max > BLE_HCI_CONN_ITVL_MAX) || (latency > BLE_HCI_CONN_LATENCY_MAX) || (spvn_tmo < BLE_HCI_CONN_SPVN_TIMEOUT_MIN) || (spvn_tmo > BLE_HCI_CONN_SPVN_TIMEOUT_MAX)) { return BLE_ERR_INV_HCI_CMD_PARMS; } /* * Supervision timeout (in msecs) must be more than: * (1 + connLatency) * connIntervalMax * 1.25 msecs * 2. */ spvn_tmo_usecs = spvn_tmo; spvn_tmo_usecs *= (BLE_HCI_CONN_SPVN_TMO_UNITS * 1000); min_spvn_tmo_usecs = (uint32_t)itvl_max * 2 * BLE_LL_CONN_ITVL_USECS; min_spvn_tmo_usecs *= (1 + latency); if (spvn_tmo_usecs <= min_spvn_tmo_usecs) { return BLE_ERR_INV_HCI_CMD_PARMS; } return BLE_ERR_SUCCESS; } /** * Send a connection complete event * * @param status The BLE error code associated with the event */ void ble_ll_conn_comp_event_send(struct ble_ll_conn_sm *connsm, uint8_t status, uint8_t *evbuf, struct ble_ll_adv_sm *advsm) { struct ble_hci_ev_le_subev_enh_conn_complete *enh_ev; struct ble_hci_ev_le_subev_conn_complete *ev; struct ble_hci_ev *hci_ev = (void *) evbuf; uint8_t *rpa; BLE_LL_ASSERT(evbuf); if (ble_ll_hci_is_le_event_enabled(BLE_HCI_LE_SUBEV_ENH_CONN_COMPLETE)) { hci_ev->opcode = BLE_HCI_EVCODE_LE_META; hci_ev->length = sizeof(*enh_ev); enh_ev = (void *) hci_ev->data; memset(enh_ev, 0, sizeof(*enh_ev)); enh_ev->subev_code = BLE_HCI_LE_SUBEV_ENH_CONN_COMPLETE; enh_ev->status = status; if (connsm) { enh_ev->conn_handle = htole16(connsm->conn_handle); enh_ev->role = connsm->conn_role - 1; enh_ev->peer_addr_type = connsm->peer_addr_type; memcpy(enh_ev->peer_addr, connsm->peer_addr, BLE_DEV_ADDR_LEN); if (connsm->conn_role == BLE_LL_CONN_ROLE_MASTER) { if (connsm->inita_identity_used) { /* We used identity address in CONNECT_IND which can be just * fine if * a) it was direct advertising we replied to and remote uses * its identity address in device privacy mode or IRK is all * zeros. * b) peer uses RPA and this is first time we connect to him */ rpa = NULL; } else if (connsm->own_addr_type > BLE_HCI_ADV_OWN_ADDR_RANDOM) { rpa = ble_ll_scan_get_local_rpa(); } else { rpa = NULL; } } else { rpa = ble_ll_adv_get_local_rpa(advsm); } if (rpa) { memcpy(enh_ev->local_rpa, rpa, BLE_DEV_ADDR_LEN); } /* We need to adjust peer type if device connected using RPA * and was resolved since RPA needs to be added to HCI event. */ if (connsm->peer_addr_type < BLE_HCI_CONN_PEER_ADDR_PUBLIC_IDENT && (connsm->rpa_index > -1)) { enh_ev->peer_addr_type += 2; } if (enh_ev->peer_addr_type > BLE_HCI_CONN_PEER_ADDR_RANDOM) { if (connsm->conn_role == BLE_LL_CONN_ROLE_MASTER) { rpa = ble_ll_scan_get_peer_rpa(); } else { rpa = ble_ll_adv_get_peer_rpa(advsm); } memcpy(enh_ev->peer_rpa, rpa, BLE_DEV_ADDR_LEN); } enh_ev->conn_itvl = htole16(connsm->conn_itvl); enh_ev->conn_latency = htole16(connsm->slave_latency); enh_ev->supervision_timeout = htole16(connsm->supervision_tmo); enh_ev->mca = connsm->master_sca; } ble_ll_hci_event_send(hci_ev); return; } if (ble_ll_hci_is_le_event_enabled(BLE_HCI_LE_SUBEV_CONN_COMPLETE)) { hci_ev->opcode = BLE_HCI_EVCODE_LE_META; hci_ev->length = sizeof(*ev); ev = (void *) hci_ev->data; memset(ev, 0, sizeof(*ev)); ev->subev_code = BLE_HCI_LE_SUBEV_CONN_COMPLETE; ev->status = status; if (connsm) { ev->conn_handle = htole16(connsm->conn_handle); ev->role = connsm->conn_role - 1; ev->peer_addr_type = connsm->peer_addr_type; if (ev->peer_addr_type > BLE_HCI_CONN_PEER_ADDR_RANDOM) { ev->peer_addr_type -= 2; } memcpy(ev->peer_addr, connsm->peer_addr, BLE_DEV_ADDR_LEN); ev->conn_itvl = htole16(connsm->conn_itvl); ev->conn_latency = htole16(connsm->slave_latency); ev->supervision_timeout = htole16(connsm->supervision_tmo); ev->mca = connsm->master_sca; } ble_ll_hci_event_send(hci_ev); return; } ble_hci_trans_buf_free(evbuf); } /** * Called to create and send the number of completed packets event to the * host. */ void ble_ll_conn_num_comp_pkts_event_send(struct ble_ll_conn_sm *connsm) { /** The maximum number of handles that will fit in an event buffer. */ static const int max_handles = (BLE_LL_MAX_EVT_LEN - sizeof(struct ble_hci_ev_num_comp_pkts) - 1) / 4; struct ble_hci_ev_num_comp_pkts *ev; struct ble_hci_ev *hci_ev; int event_sent; if (connsm == NULL) { goto skip_conn; } /* * At some periodic rate, make sure we go through all active connections * and send the number of completed packet events. We do this mainly * because the spec says we must update the host even though no packets * have completed but there are data packets in the controller buffers * (i.e. enqueued in a connection state machine). */ if ((ble_npl_stime_t)(ble_npl_time_get() - g_ble_ll_last_num_comp_pkt_evt) < ble_npl_time_ms_to_ticks32(MYNEWT_VAL(BLE_LL_NUM_COMP_PKT_ITVL_MS))) { /* * If this connection has completed packets, send an event right away. * We do this to increase throughput but we dont want to search the * entire active list every time. */ if (connsm->completed_pkts) { hci_ev = (void *) ble_hci_trans_buf_alloc(BLE_HCI_TRANS_BUF_EVT_HI); if (hci_ev) { hci_ev->opcode = BLE_HCI_EVCODE_NUM_COMP_PKTS; hci_ev->length = sizeof(*ev); ev = (void *)hci_ev->data; ev->count = 1; ev->completed[0].handle = htole16(connsm->conn_handle); ev->completed[0].packets = htole16(connsm->completed_pkts); hci_ev->length += sizeof(ev->completed[0]); connsm->completed_pkts = 0; ble_ll_hci_event_send(hci_ev); } } return; } /* Iterate through all the active, created connections */ skip_conn: hci_ev = NULL; ev = NULL; event_sent = 0; SLIST_FOREACH(connsm, &g_ble_ll_conn_active_list, act_sle) { /* * Only look at connections that we have sent a connection complete * event and that either has packets enqueued or has completed packets. */ if ((connsm->conn_state != BLE_LL_CONN_STATE_IDLE) && (connsm->completed_pkts || !STAILQ_EMPTY(&connsm->conn_txq))) { /* If no buffer, get one, If cant get one, leave. */ if (!hci_ev) { hci_ev = (void *) ble_hci_trans_buf_alloc(BLE_HCI_TRANS_BUF_EVT_HI); if (!hci_ev) { break; } hci_ev->opcode = BLE_HCI_EVCODE_NUM_COMP_PKTS; hci_ev->length = sizeof(*ev); ev = (void *)hci_ev->data; ev->count = 0; } /* Add handle and complete packets */ ev->completed[ev->count].handle = htole16(connsm->conn_handle); ev->completed[ev->count].packets = htole16(connsm->completed_pkts); hci_ev->length += sizeof(ev->completed[ev->count]); ev->count++; connsm->completed_pkts = 0; /* Send now if the buffer is full. */ if (ev->count == max_handles) { ble_ll_hci_event_send(hci_ev); hci_ev = NULL; event_sent = 1; } } } /* Send event if there is an event to send */ if (hci_ev) { ble_ll_hci_event_send(hci_ev); event_sent = 1; } if (event_sent) { g_ble_ll_last_num_comp_pkt_evt = ble_npl_time_get(); } } #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_PING) /** * Send a authenticated payload timeout event * * NOTE: we currently only send this event when we have a reason to send it; * not when it fails. * * @param reason The BLE error code to send as a disconnect reason */ void ble_ll_auth_pyld_tmo_event_send(struct ble_ll_conn_sm *connsm) { struct ble_hci_ev_auth_pyld_tmo *ev; struct ble_hci_ev *hci_ev; if (ble_ll_hci_is_event_enabled(BLE_HCI_EVCODE_AUTH_PYLD_TMO)) { hci_ev = (void *) ble_hci_trans_buf_alloc(BLE_HCI_TRANS_BUF_EVT_HI); if (hci_ev) { hci_ev->opcode = BLE_HCI_EVCODE_AUTH_PYLD_TMO; hci_ev->length = sizeof(*ev); ev = (void *) hci_ev->data; ev->conn_handle = htole16(connsm->conn_handle); ble_ll_hci_event_send(hci_ev); } } } #endif /** * Send a disconnection complete event. * * NOTE: we currently only send this event when we have a reason to send it; * not when it fails. * * @param reason The BLE error code to send as a disconnect reason */ void ble_ll_disconn_comp_event_send(struct ble_ll_conn_sm *connsm, uint8_t reason) { struct ble_hci_ev_disconn_cmp *ev; struct ble_hci_ev *hci_ev; if (ble_ll_hci_is_event_enabled(BLE_HCI_EVCODE_DISCONN_CMP)) { hci_ev = (void *) ble_hci_trans_buf_alloc(BLE_HCI_TRANS_BUF_EVT_HI); if (hci_ev) { hci_ev->opcode = BLE_HCI_EVCODE_DISCONN_CMP; hci_ev->length = sizeof(*ev); ev = (void *) hci_ev->data; ev->status = BLE_ERR_SUCCESS; ev->conn_handle = htole16(connsm->conn_handle); ev->reason = reason; ble_ll_hci_event_send(hci_ev); } } } static int ble_ll_conn_hci_chk_scan_params(uint16_t itvl, uint16_t window) { /* Check interval and window */ if ((itvl < BLE_HCI_SCAN_ITVL_MIN) || (itvl > BLE_HCI_SCAN_ITVL_MAX) || (window < BLE_HCI_SCAN_WINDOW_MIN) || (window > BLE_HCI_SCAN_WINDOW_MAX) || (itvl < window)) { return BLE_ERR_INV_HCI_CMD_PARMS; } return 0; } /** * Process the HCI command to create a connection. * * Context: Link Layer task (HCI command processing) * * @param cmdbuf * * @return int */ int ble_ll_conn_create(const uint8_t *cmdbuf, uint8_t len) { const struct ble_hci_le_create_conn_cp *cmd = (const void *) cmdbuf; struct ble_ll_conn_sm *connsm; struct hci_create_conn hcc = { 0 }; int rc; if (len < sizeof(*cmd)) { return BLE_ERR_INV_HCI_CMD_PARMS; } /* If we are already creating a connection we should leave */ if (g_ble_ll_conn_create_sm) { return BLE_ERR_CMD_DISALLOWED; } /* If already enabled, we return an error */ if (ble_ll_scan_enabled()) { return BLE_ERR_CMD_DISALLOWED; } /* Retrieve command data */ hcc.scan_itvl = le16toh(cmd->scan_itvl); hcc.scan_window = le16toh(cmd->scan_window); rc = ble_ll_conn_hci_chk_scan_params(hcc.scan_itvl, hcc.scan_window); if (rc) { return BLE_ERR_INV_HCI_CMD_PARMS; } /* Check filter policy */ hcc.filter_policy = cmd->filter_policy; if (hcc.filter_policy > BLE_HCI_INITIATOR_FILT_POLICY_MAX) { return BLE_ERR_INV_HCI_CMD_PARMS; } /* Get peer address type and address only if no whitelist used */ if (hcc.filter_policy == 0) { hcc.peer_addr_type = cmd->peer_addr_type; if (hcc.peer_addr_type > BLE_HCI_CONN_PEER_ADDR_MAX) { return BLE_ERR_INV_HCI_CMD_PARMS; } memcpy(&hcc.peer_addr, cmd->peer_addr, BLE_DEV_ADDR_LEN); } /* Get own address type (used in connection request) */ hcc.own_addr_type = cmd->own_addr_type; if (hcc.own_addr_type > BLE_HCI_ADV_OWN_ADDR_MAX) { return BLE_ERR_INV_HCI_CMD_PARMS; } /* Check connection interval, latency and supervision timeoout */ hcc.conn_itvl_min = le16toh(cmd->min_conn_itvl); hcc.conn_itvl_max = le16toh(cmd->max_conn_itvl); hcc.conn_latency = le16toh(cmd->conn_latency); hcc.supervision_timeout = le16toh(cmd->tmo); rc = ble_ll_conn_hci_chk_conn_params(hcc.conn_itvl_min, hcc.conn_itvl_max, hcc.conn_latency, hcc.supervision_timeout); if (rc) { return rc; } /* Min/max connection event lengths */ hcc.min_ce_len = le16toh(cmd->min_ce); hcc.max_ce_len = le16toh(cmd->max_ce); if (hcc.min_ce_len > hcc.max_ce_len) { return BLE_ERR_INV_HCI_CMD_PARMS; } /* Make sure we can allocate an event to send the connection complete */ if (ble_ll_init_alloc_conn_comp_ev()) { return BLE_ERR_MEM_CAPACITY; } /* Make sure we can accept a connection! */ connsm = ble_ll_conn_sm_get(); if (connsm == NULL) { return BLE_ERR_CONN_LIMIT; } /* Initialize state machine in master role and start state machine */ ble_ll_conn_master_init(connsm, &hcc); ble_ll_conn_sm_new(connsm); /* CSA will be selected when advertising is received */ /* Start scanning */ rc = ble_ll_scan_initiator_start(&hcc, &connsm->scansm); if (rc) { SLIST_REMOVE(&g_ble_ll_conn_active_list,connsm,ble_ll_conn_sm,act_sle); STAILQ_INSERT_TAIL(&g_ble_ll_conn_free_list, connsm, free_stqe); } else { /* Set the connection state machine we are trying to create. */ g_ble_ll_conn_create_sm = connsm; } return rc; } #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV) static void ble_ll_conn_hcc_params_set_fallback(struct hci_ext_create_conn *hcc, const struct hci_ext_conn_params *fallback) { BLE_LL_ASSERT(fallback); if (!(hcc->init_phy_mask & BLE_PHY_MASK_1M)) { hcc->params[0] = *fallback; } #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_2M_PHY) if (!(hcc->init_phy_mask & BLE_PHY_MASK_2M)) { hcc->params[1] = *fallback; } #endif #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CODED_PHY) if (!(hcc->init_phy_mask & BLE_PHY_MASK_CODED)) { hcc->params[2] = *fallback; } #endif } int ble_ll_ext_conn_create(const uint8_t *cmdbuf, uint8_t len) { const struct ble_hci_le_ext_create_conn_cp *cmd = (const void *) cmdbuf; const struct conn_params *params = cmd->conn_params; const struct hci_ext_conn_params *fallback_params = NULL; struct hci_ext_create_conn hcc = { 0 }; struct ble_ll_conn_sm *connsm; int rc; /* validate length */ if (len < sizeof(*cmd)) { return BLE_ERR_INV_HCI_CMD_PARMS; } len -= sizeof(*cmd); /* If we are already creating a connection we should leave */ if (g_ble_ll_conn_create_sm) { return BLE_ERR_CMD_DISALLOWED; } /* If already enabled, we return an error */ if (ble_ll_scan_enabled()) { return BLE_ERR_CMD_DISALLOWED; } hcc.filter_policy = cmd->filter_policy; if (hcc.filter_policy > BLE_HCI_INITIATOR_FILT_POLICY_MAX) { return BLE_ERR_INV_HCI_CMD_PARMS; } hcc.own_addr_type = cmd->own_addr_type; if (hcc.own_addr_type > BLE_HCI_ADV_OWN_ADDR_MAX) { return BLE_ERR_INV_HCI_CMD_PARMS; } /* Validate peer address type only if no whitelist used */ if (hcc.filter_policy == 0) { hcc.peer_addr_type = cmd->peer_addr_type; if (hcc.peer_addr_type > BLE_HCI_CONN_PEER_ADDR_MAX) { return BLE_ERR_INV_HCI_CMD_PARMS; } memcpy(hcc.peer_addr, cmd->peer_addr, BLE_DEV_ADDR_LEN); } hcc.init_phy_mask = cmd->init_phy_mask; if (hcc.init_phy_mask & ~ble_ll_valid_conn_phy_mask) { return BLE_ERR_INV_HCI_CMD_PARMS; } if (!(hcc.init_phy_mask & ble_ll_conn_required_phy_mask)) { /* At least one of those need to be set */ return BLE_ERR_INV_HCI_CMD_PARMS; } if (hcc.init_phy_mask & BLE_PHY_MASK_1M) { if (len < sizeof(*params)) { return BLE_ERR_INV_HCI_CMD_PARMS; } len -= sizeof(*params); hcc.params[0].scan_itvl = le16toh(params->scan_itvl); hcc.params[0].scan_window = le16toh(params->scan_window); rc = ble_ll_conn_hci_chk_scan_params(hcc.params[0].scan_itvl, hcc.params[0].scan_window); if (rc) { return rc; } hcc.params[0].conn_itvl_min = le16toh(params->conn_min_itvl); hcc.params[0].conn_itvl_max = le16toh(params->conn_min_itvl); hcc.params[0].conn_latency = le16toh(params->conn_latency); hcc.params[0].supervision_timeout = le16toh(params->supervision_timeout); rc = ble_ll_conn_hci_chk_conn_params(hcc.params[0].conn_itvl_min, hcc.params[0].conn_itvl_max, hcc.params[0].conn_latency, hcc.params[0].supervision_timeout); if (rc) { return rc; } /* Min/max connection event lengths */ hcc.params[0].min_ce_len = le16toh(params->min_ce); hcc.params[0].max_ce_len = le16toh(params->max_ce); if (hcc.params[0].min_ce_len > hcc.params[0].max_ce_len) { return BLE_ERR_INV_HCI_CMD_PARMS; } fallback_params = &hcc.params[0]; params++; } #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_2M_PHY) if (hcc.init_phy_mask & BLE_PHY_MASK_2M) { if (len < sizeof(*params)) { return BLE_ERR_INV_HCI_CMD_PARMS; } len -= sizeof(*params); hcc.params[1].conn_itvl_min = le16toh(params->conn_min_itvl); hcc.params[1].conn_itvl_max = le16toh(params->conn_min_itvl); hcc.params[1].conn_latency = le16toh(params->conn_latency); hcc.params[1].supervision_timeout = le16toh(params->supervision_timeout); rc = ble_ll_conn_hci_chk_conn_params(hcc.params[1].conn_itvl_min, hcc.params[1].conn_itvl_max, hcc.params[1].conn_latency, hcc.params[1].supervision_timeout); if (rc) { return rc; } /* Min/max connection event lengths */ hcc.params[1].min_ce_len = le16toh(params->min_ce); hcc.params[1].max_ce_len = le16toh(params->max_ce); if (hcc.params[1].min_ce_len > hcc.params[1].max_ce_len) { return BLE_ERR_INV_HCI_CMD_PARMS; } params++; } #endif #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CODED_PHY) if (hcc.init_phy_mask & BLE_PHY_MASK_CODED) { if (len < sizeof(*params)) { return BLE_ERR_INV_HCI_CMD_PARMS; } len -= sizeof(*params); hcc.params[2].scan_itvl = le16toh(params->scan_itvl); hcc.params[2].scan_window = le16toh(params->scan_window); rc = ble_ll_conn_hci_chk_scan_params(hcc.params[2].scan_itvl, hcc.params[2].scan_window); if (rc) { return rc; } hcc.params[2].conn_itvl_min = le16toh(params->conn_min_itvl); hcc.params[2].conn_itvl_max = le16toh(params->conn_min_itvl); hcc.params[2].conn_latency = le16toh(params->conn_latency); hcc.params[2].supervision_timeout = le16toh(params->supervision_timeout); rc = ble_ll_conn_hci_chk_conn_params(hcc.params[2].conn_itvl_min, hcc.params[2].conn_itvl_max, hcc.params[2].conn_latency, hcc.params[2].supervision_timeout); if (rc) { return rc; } /* Min/max connection event lengths */ hcc.params[2].min_ce_len = le16toh(params->min_ce); hcc.params[2].max_ce_len = le16toh(params->max_ce); if (hcc.params[2].min_ce_len > hcc.params[2].max_ce_len) { return BLE_ERR_INV_HCI_CMD_PARMS; } if (!fallback_params) { fallback_params = &hcc.params[2]; } params++; } #endif /* Make sure we can allocate an event to send the connection complete */ if (ble_ll_init_alloc_conn_comp_ev()) { return BLE_ERR_MEM_CAPACITY; } /* Make sure we can accept a connection! */ connsm = ble_ll_conn_sm_get(); if (connsm == NULL) { return BLE_ERR_CONN_LIMIT; } ble_ll_conn_hcc_params_set_fallback(&hcc, fallback_params); /* Initialize state machine in master role and start state machine */ ble_ll_conn_ext_master_init(connsm, &hcc); ble_ll_conn_sm_new(connsm); /* CSA will be selected when advertising is received */ /* Start scanning */ rc = ble_ll_scan_ext_initiator_start(&hcc, &connsm->scansm); if (rc) { SLIST_REMOVE(&g_ble_ll_conn_active_list,connsm,ble_ll_conn_sm,act_sle); STAILQ_INSERT_TAIL(&g_ble_ll_conn_free_list, connsm, free_stqe); } else { /* Set the connection state machine we are trying to create. */ g_ble_ll_conn_create_sm = connsm; } return rc; } #endif static int ble_ll_conn_process_conn_params(const struct ble_hci_le_rem_conn_param_rr_cp *cmd, struct ble_ll_conn_sm *connsm) { int rc; struct hci_conn_update *hcu; /* Retrieve command data */ hcu = &connsm->conn_param_req; hcu->handle = connsm->conn_handle; BLE_LL_ASSERT(connsm->conn_handle == le16toh(cmd->conn_handle)); hcu->conn_itvl_min = le16toh(cmd->conn_itvl_min); hcu->conn_itvl_max = le16toh(cmd->conn_itvl_max); hcu->conn_latency = le16toh(cmd->conn_latency); hcu->supervision_timeout = le16toh(cmd->supervision_timeout); hcu->min_ce_len = le16toh(cmd->min_ce); hcu->max_ce_len = le16toh(cmd->max_ce); /* Check that parameter values are in range */ rc = ble_ll_conn_hci_chk_conn_params(hcu->conn_itvl_min, hcu->conn_itvl_max, hcu->conn_latency, hcu->supervision_timeout); /* Check valid min/max ce length */ if (rc || (hcu->min_ce_len > hcu->max_ce_len)) { return BLE_ERR_INV_HCI_CMD_PARMS; } return rc; } /** * Called when the host issues the read remote features command * * @param cmdbuf * * @return int */ int ble_ll_conn_hci_read_rem_features(const uint8_t *cmdbuf, uint8_t len) { const struct ble_hci_le_rd_rem_feat_cp *cmd = (const void *) cmdbuf; struct ble_ll_conn_sm *connsm; if (len != sizeof(*cmd)) { return BLE_ERR_INV_HCI_CMD_PARMS; } /* If no connection handle exit with error */ connsm = ble_ll_conn_find_active_conn(le16toh(cmd->conn_handle)); if (!connsm) { return BLE_ERR_UNK_CONN_ID; } /* If already pending exit with error */ if (connsm->csmflags.cfbit.pending_hci_rd_features) { return BLE_ERR_CMD_DISALLOWED; } /* * Start control procedure if we did not receive peer's features and did not * start procedure already. */ if (!connsm->csmflags.cfbit.rxd_features && !IS_PENDING_CTRL_PROC(connsm, BLE_LL_CTRL_PROC_FEATURE_XCHG)) { if ((connsm->conn_role == BLE_LL_CONN_ROLE_SLAVE) && !(ble_ll_read_supp_features() & BLE_LL_FEAT_SLAVE_INIT)) { return BLE_ERR_CMD_DISALLOWED; } ble_ll_ctrl_proc_start(connsm, BLE_LL_CTRL_PROC_FEATURE_XCHG); } connsm->csmflags.cfbit.pending_hci_rd_features = 1; return BLE_ERR_SUCCESS; } /** * Called to process a connection update command. * * @param cmdbuf * * @return int */ int ble_ll_conn_hci_update(const uint8_t *cmdbuf, uint8_t len) { const struct ble_hci_le_conn_update_cp *cmd = (const void *) cmdbuf; int rc; uint8_t ctrl_proc; uint16_t handle; struct ble_ll_conn_sm *connsm; struct hci_conn_update *hcu; /* * XXX: must deal with slave not supporting this feature and using * conn update! Right now, we only check if WE support the connection * parameters request procedure. We dont check if the remote does. * We should also be able to deal with sending the parameter request, * getting an UNKOWN_RSP ctrl pdu and resorting to use normal * connection update procedure. */ /* If no connection handle exit with error */ handle = le16toh(cmd->conn_handle); connsm = ble_ll_conn_find_active_conn(handle); if (!connsm) { return BLE_ERR_UNK_CONN_ID; } /* Better not have this procedure ongoing! */ if (IS_PENDING_CTRL_PROC(connsm, BLE_LL_CTRL_PROC_CONN_PARAM_REQ) || IS_PENDING_CTRL_PROC(connsm, BLE_LL_CTRL_PROC_CONN_UPDATE)) { return BLE_ERR_CMD_DISALLOWED; } /* See if this feature is supported on both sides */ if ((connsm->conn_features & BLE_LL_FEAT_CONN_PARM_REQ) == 0) { if (connsm->conn_role == BLE_LL_CONN_ROLE_SLAVE) { return BLE_ERR_UNSUPP_REM_FEATURE; } ctrl_proc = BLE_LL_CTRL_PROC_CONN_UPDATE; } else { ctrl_proc = BLE_LL_CTRL_PROC_CONN_PARAM_REQ; } /* * If we are a slave and the master has initiated the procedure already * we should deny the slave request for now. If we are a master and the * slave has initiated the procedure, we need to send a reject to the * slave. */ if (connsm->csmflags.cfbit.awaiting_host_reply) { if (connsm->conn_role == BLE_LL_CONN_ROLE_SLAVE) { return BLE_ERR_LMP_COLLISION; } else { connsm->csmflags.cfbit.awaiting_host_reply = 0; /* XXX: If this fails no reject ind will be sent! */ ble_ll_ctrl_reject_ind_send(connsm, connsm->host_reply_opcode, BLE_ERR_LMP_COLLISION); } } /* * If we are a slave and the master has initiated the channel map * update procedure we should deny the slave request for now. */ if (connsm->csmflags.cfbit.chanmap_update_scheduled) { if (connsm->conn_role == BLE_LL_CONN_ROLE_SLAVE) { return BLE_ERR_DIFF_TRANS_COLL; } } /* Retrieve command data */ hcu = &connsm->conn_param_req; hcu->conn_itvl_min = le16toh(cmd->conn_itvl_min); hcu->conn_itvl_max = le16toh(cmd->conn_itvl_max); hcu->conn_latency = le16toh(cmd->conn_latency); hcu->supervision_timeout = le16toh(cmd->supervision_timeout); hcu->min_ce_len = le16toh(cmd->min_ce_len); hcu->max_ce_len = le16toh(cmd->max_ce_len); if (hcu->min_ce_len > hcu->max_ce_len) { return BLE_ERR_INV_HCI_CMD_PARMS; } /* Check that parameter values are in range */ rc = ble_ll_conn_hci_chk_conn_params(hcu->conn_itvl_min, hcu->conn_itvl_max, hcu->conn_latency, hcu->supervision_timeout); if (!rc) { hcu->handle = handle; /* Start the control procedure */ ble_ll_ctrl_proc_start(connsm, ctrl_proc); } return rc; } int ble_ll_conn_hci_param_rr(const uint8_t *cmdbuf, uint8_t len, uint8_t *rspbuf, uint8_t *rsplen) { const struct ble_hci_le_rem_conn_param_rr_cp *cmd = (const void *) cmdbuf; struct ble_hci_le_rem_conn_param_rr_rp *rsp = (void *) rspbuf; int rc; uint8_t *dptr; uint8_t rsp_opcode; uint16_t handle; struct os_mbuf *om; struct ble_ll_conn_sm *connsm; if (len != sizeof(*cmd)) { return BLE_ERR_INV_HCI_CMD_PARMS; } handle = le16toh(cmd->conn_handle); /* See if we support this feature */ if ((ble_ll_read_supp_features() & BLE_LL_FEAT_CONN_PARM_REQ) == 0) { rc = BLE_ERR_UNKNOWN_HCI_CMD; goto done; } /* If we dont have a handle we cant do anything */ connsm = ble_ll_conn_find_active_conn(handle); if (!connsm) { rc = BLE_ERR_UNK_CONN_ID; goto done; } /* Make sure connection parameters are valid */ rc = ble_ll_conn_process_conn_params(cmd, connsm); /* The connection should be awaiting a reply. If not, just discard */ if (connsm->csmflags.cfbit.awaiting_host_reply) { /* Get a control packet buffer */ if (rc == BLE_ERR_SUCCESS) { om = os_msys_get_pkthdr(BLE_LL_CTRL_MAX_PDU_LEN, sizeof(struct ble_mbuf_hdr)); if (om) { dptr = om->om_data; rsp_opcode = ble_ll_ctrl_conn_param_reply(connsm, dptr, &connsm->conn_cp); dptr[0] = rsp_opcode; len = g_ble_ll_ctrl_pkt_lengths[rsp_opcode] + 1; ble_ll_conn_enqueue_pkt(connsm, om, BLE_LL_LLID_CTRL, len); } } else { /* XXX: check return code and deal */ ble_ll_ctrl_reject_ind_send(connsm, connsm->host_reply_opcode, BLE_ERR_CONN_PARMS); } connsm->csmflags.cfbit.awaiting_host_reply = 0; /* XXX: if we cant get a buffer, what do we do? We need to remember * reason if it was a negative reply. We also would need to have * some state to tell us this happened */ } done: rsp->conn_handle = htole16(handle); *rsplen = sizeof(*rsp); return rc; } int ble_ll_conn_hci_param_nrr(const uint8_t *cmdbuf, uint8_t len, uint8_t *rspbuf, uint8_t *rsplen) { const struct ble_hci_le_rem_conn_params_nrr_cp *cmd = (const void *) cmdbuf; struct ble_hci_le_rem_conn_params_nrr_rp *rsp = (void *) rspbuf; struct ble_ll_conn_sm *connsm; uint16_t handle; int rc; if (len != sizeof(*cmd)) { return BLE_ERR_INV_HCI_CMD_PARMS; } handle = le16toh(cmd->conn_handle); /* See if we support this feature */ if ((ble_ll_read_supp_features() & BLE_LL_FEAT_CONN_PARM_REQ) == 0) { rc = BLE_ERR_UNKNOWN_HCI_CMD; goto done; } /* If we dont have a handle we cant do anything */ connsm = ble_ll_conn_find_active_conn(handle); if (!connsm) { rc = BLE_ERR_UNK_CONN_ID; goto done; } rc = BLE_ERR_SUCCESS; /* The connection should be awaiting a reply. If not, just discard */ if (connsm->csmflags.cfbit.awaiting_host_reply) { /* XXX: check return code and deal */ ble_ll_ctrl_reject_ind_send(connsm, connsm->host_reply_opcode, cmd->reason); connsm->csmflags.cfbit.awaiting_host_reply = 0; /* XXX: if we cant get a buffer, what do we do? We need to remember * reason if it was a negative reply. We also would need to have * some state to tell us this happened */ } done: rsp->conn_handle = htole16(handle); *rsplen = sizeof(*rsp); return rc; } /* this is called from same context after cmd complete is send so it is * safe to use g_ble_ll_conn_comp_ev */ static void ble_ll_conn_hci_cancel_conn_complete_event(void) { BLE_LL_ASSERT(g_ble_ll_conn_comp_ev); ble_ll_conn_comp_event_send(NULL, BLE_ERR_UNK_CONN_ID, g_ble_ll_conn_comp_ev, NULL); g_ble_ll_conn_comp_ev = NULL; } /** * Called when HCI command to cancel a create connection command has been * received. * * Context: Link Layer (HCI command parser) * * @return int */ int ble_ll_conn_create_cancel(ble_ll_hci_post_cmd_complete_cb *post_cmd_cb) { int rc; struct ble_ll_conn_sm *connsm; os_sr_t sr; /* * If we receive this command and we have not got a connection * create command, we have to return disallowed. The spec does not say * what happens if the connection has already been established. We * return disallowed as well */ OS_ENTER_CRITICAL(sr); connsm = g_ble_ll_conn_create_sm; if (connsm && (connsm->conn_state == BLE_LL_CONN_STATE_IDLE)) { /* stop scanning and end the connection event */ g_ble_ll_conn_create_sm = NULL; ble_ll_scan_sm_stop(1); ble_ll_conn_end(connsm, BLE_ERR_UNK_CONN_ID); *post_cmd_cb = ble_ll_conn_hci_cancel_conn_complete_event; rc = BLE_ERR_SUCCESS; } else { /* If we are not attempting to create a connection*/ rc = BLE_ERR_CMD_DISALLOWED; } OS_EXIT_CRITICAL(sr); return rc; } /** * Called to process a HCI disconnect command * * Context: Link Layer task (HCI command parser). * * @param cmdbuf * * @return int */ int ble_ll_conn_hci_disconnect_cmd(const struct ble_hci_lc_disconnect_cp *cmd) { int rc; uint16_t handle; struct ble_ll_conn_sm *connsm; /* Check for valid parameters */ handle = le16toh(cmd->conn_handle); rc = BLE_ERR_INV_HCI_CMD_PARMS; if (handle <= BLE_LL_CONN_MAX_CONN_HANDLE) { /* Make sure reason is valid */ switch (cmd->reason) { case BLE_ERR_AUTH_FAIL: case BLE_ERR_REM_USER_CONN_TERM: case BLE_ERR_RD_CONN_TERM_RESRCS: case BLE_ERR_RD_CONN_TERM_PWROFF: case BLE_ERR_UNSUPP_REM_FEATURE: case BLE_ERR_UNIT_KEY_PAIRING: case BLE_ERR_CONN_PARMS: connsm = ble_ll_conn_find_active_conn(handle); if (connsm) { /* Do not allow command if we are in process of disconnecting */ if (connsm->disconnect_reason) { rc = BLE_ERR_CMD_DISALLOWED; } else { /* This control procedure better not be pending! */ BLE_LL_ASSERT(CONN_F_TERMINATE_STARTED(connsm) == 0); /* Record the disconnect reason */ connsm->disconnect_reason = cmd->reason; /* Start this control procedure */ ble_ll_ctrl_terminate_start(connsm); rc = BLE_ERR_SUCCESS; } } else { rc = BLE_ERR_UNK_CONN_ID; } break; default: break; } } return rc; } /** * Called to process a HCI disconnect command * * Context: Link Layer task (HCI command parser). * * @param cmdbuf * * @return int */ int ble_ll_conn_hci_rd_rem_ver_cmd(const uint8_t *cmdbuf, uint8_t len) { struct ble_ll_conn_sm *connsm; const struct ble_hci_rd_rem_ver_info_cp *cmd = (const void *) cmdbuf; if (len != sizeof(*cmd)) { return BLE_ERR_INV_HCI_CMD_PARMS; } /* Check for valid parameters */ connsm = ble_ll_conn_find_active_conn(le16toh(cmd->conn_handle)); if (!connsm) { return BLE_ERR_UNK_CONN_ID; } /* Return error if in progress */ if (IS_PENDING_CTRL_PROC(connsm, BLE_LL_CTRL_PROC_VERSION_XCHG)) { return BLE_ERR_CMD_DISALLOWED; } /* * Start this control procedure. If we have already done this control * procedure we set the pending bit so that the host gets an event because * it is obviously expecting one (or would not have sent the command). * NOTE: we cant just send the event here. That would cause the event to * be queued before the command status. */ if (!connsm->csmflags.cfbit.version_ind_sent) { ble_ll_ctrl_proc_start(connsm, BLE_LL_CTRL_PROC_VERSION_XCHG); } else { connsm->pending_ctrl_procs |= (1 << BLE_LL_CTRL_PROC_VERSION_XCHG); } return BLE_ERR_SUCCESS; } /** * Called to read the RSSI for a given connection handle * * @param cmdbuf * @param rspbuf * @param rsplen * * @return int */ int ble_ll_conn_hci_rd_rssi(const uint8_t *cmdbuf, uint8_t len, uint8_t *rspbuf, uint8_t *rsplen) { const struct ble_hci_rd_rssi_cp *cmd = (const void *) cmdbuf; struct ble_hci_rd_rssi_rp *rsp = (void *) rspbuf; struct ble_ll_conn_sm *connsm; int rc; if (len != sizeof(*cmd)) { return BLE_ERR_INV_HCI_CMD_PARMS; } rsp->handle = cmd->handle; connsm = ble_ll_conn_find_active_conn(le16toh(cmd->handle)); if (!connsm) { rsp->rssi = 127; rc = BLE_ERR_UNK_CONN_ID; } else { rsp->rssi = connsm->conn_rssi; rc = BLE_ERR_SUCCESS; } *rsplen = sizeof(*rsp); return rc; } /** * Called to read the current channel map of a connection * * @param cmdbuf * @param rspbuf * @param rsplen * * @return int */ int ble_ll_conn_hci_rd_chan_map(const uint8_t *cmdbuf, uint8_t len, uint8_t *rspbuf, uint8_t *rsplen) { const struct ble_hci_le_rd_chan_map_cp *cmd = (const void *) cmdbuf; struct ble_hci_le_rd_chan_map_rp *rsp = (void *) rspbuf; struct ble_ll_conn_sm *connsm; uint16_t handle; int rc; if (len != sizeof(*cmd)) { return BLE_ERR_INV_HCI_CMD_PARMS; } handle = le16toh(cmd->conn_handle); connsm = ble_ll_conn_find_active_conn(handle); if (!connsm) { rc = BLE_ERR_UNK_CONN_ID; memset(rsp->chan_map, 0, sizeof(rsp->chan_map)); } else { if (connsm->csmflags.cfbit.chanmap_update_scheduled) { memcpy(rsp->chan_map, connsm->req_chanmap, BLE_LL_CONN_CHMAP_LEN); } else { memcpy(rsp->chan_map, connsm->chanmap, BLE_LL_CONN_CHMAP_LEN); } rc = BLE_ERR_SUCCESS; } rsp->conn_handle = htole16(handle); *rsplen = sizeof(*rsp); return rc; } /** * Called when the host issues the LE command "set host channel classification" * * @param cmdbuf * * @return int */ int ble_ll_conn_hci_set_chan_class(const uint8_t *cmdbuf, uint8_t len) { const struct ble_hci_le_set_host_chan_class_cp *cmd = (const void *) cmdbuf; uint8_t num_used_chans; if (len != sizeof(*cmd)) { return BLE_ERR_INV_HCI_CMD_PARMS; } /* * The HCI command states that the host is allowed to mask in just one * channel but the Link Layer needs minimum two channels to operate. So * I will not allow this command if there are less than 2 channels masked. */ num_used_chans = ble_ll_utils_calc_num_used_chans(cmd->chan_map); if ((num_used_chans < 2) || ((cmd->chan_map[4] & 0xe0) != 0)) { return BLE_ERR_INV_HCI_CMD_PARMS; } /* Set the host channel mask */ ble_ll_conn_set_global_chanmap(num_used_chans, cmd->chan_map); return BLE_ERR_SUCCESS; } #if MYNEWT_VAL(BLE_LL_CFG_FEAT_DATA_LEN_EXT) int ble_ll_conn_hci_set_data_len(const uint8_t *cmdbuf, uint8_t len, uint8_t *rspbuf, uint8_t *rsplen) { const struct ble_hci_le_set_data_len_cp *cmd = (const void *) cmdbuf; struct ble_hci_le_set_data_len_rp *rsp = (void *) rspbuf; int rc; uint16_t handle; uint16_t txoctets; uint16_t txtime; struct ble_ll_conn_sm *connsm; if (len != sizeof(*cmd)) { return BLE_ERR_INV_HCI_CMD_PARMS; } /* Find connection */ handle = le16toh(cmd->conn_handle); connsm = ble_ll_conn_find_active_conn(handle); if (!connsm) { rc = BLE_ERR_UNK_CONN_ID; goto done; } txoctets = le16toh(cmd->tx_octets); txtime = le16toh(cmd->tx_time); /* Make sure it is valid */ if (!ble_ll_chk_txrx_octets(txoctets) || !ble_ll_chk_txrx_time(txtime)) { rc = BLE_ERR_INV_HCI_CMD_PARMS; goto done; } #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CODED_PHY) /* * Keep original value requested by host since we may want to recalculate * MaxTxTime after PHY changes between coded and uncoded. */ connsm->host_req_max_tx_time = txtime; /* If peer does not support coded, we cannot use value larger than 2120us */ if (!(connsm->remote_features[0] & (BLE_LL_FEAT_LE_CODED_PHY >> 8))) { txtime = min(txtime, BLE_LL_CONN_SUPP_TIME_MAX_UNCODED); } #endif rc = BLE_ERR_SUCCESS; if (connsm->max_tx_time != txtime || connsm->max_tx_octets != txoctets) { connsm->max_tx_time = txtime; connsm->max_tx_octets = txoctets; ble_ll_ctrl_initiate_dle(connsm); } done: rsp->conn_handle = htole16(handle); *rsplen = sizeof(*rsp); return rc; } #endif #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION) /** * LE start encrypt command * * @param cmdbuf * * @return int */ int ble_ll_conn_hci_le_start_encrypt(const uint8_t *cmdbuf, uint8_t len) { const struct ble_hci_le_start_encrypt_cp *cmd = (const void *) cmdbuf; struct ble_ll_conn_sm *connsm; int rc; if (len != sizeof(*cmd)) { return BLE_ERR_INV_HCI_CMD_PARMS; } connsm = ble_ll_conn_find_active_conn(le16toh(cmd->conn_handle)); if (!connsm) { rc = BLE_ERR_UNK_CONN_ID; } else if (connsm->conn_role == BLE_LL_CONN_ROLE_SLAVE) { rc = BLE_ERR_UNSPECIFIED; } else if (connsm->cur_ctrl_proc == BLE_LL_CTRL_PROC_ENCRYPT) { /* * The specification does not say what to do here but the host should * not be telling us to start encryption while we are in the process * of honoring a previous start encrypt. */ rc = BLE_ERR_CMD_DISALLOWED; } else { /* Start the control procedure */ connsm->enc_data.host_rand_num = le64toh(cmd->rand); connsm->enc_data.enc_div = le16toh(cmd->div); swap_buf(connsm->enc_data.enc_block.key, cmd->ltk, 16); ble_ll_ctrl_proc_start(connsm, BLE_LL_CTRL_PROC_ENCRYPT); rc = BLE_ERR_SUCCESS; } return rc; } /** * Called to process the LE long term key reply. * * Context: Link Layer Task. * * @param cmdbuf * @param rspbuf * @param ocf * * @return int */ int ble_ll_conn_hci_le_ltk_reply(const uint8_t *cmdbuf, uint8_t len, uint8_t *rspbuf, uint8_t *rsplen) { const struct ble_hci_le_lt_key_req_reply_cp *cmd = (const void *) cmdbuf; struct ble_hci_le_lt_key_req_reply_rp *rsp = (void *) rspbuf; struct ble_ll_conn_sm *connsm; uint16_t handle; int rc; if (len != sizeof(*cmd)) { return BLE_ERR_INV_HCI_CMD_PARMS; } /* Find connection handle */ handle = le16toh(cmd->conn_handle); connsm = ble_ll_conn_find_active_conn(handle); if (!connsm) { rc = BLE_ERR_UNK_CONN_ID; goto ltk_key_cmd_complete; } /* Should never get this if we are a master! */ if (connsm->conn_role == BLE_LL_CONN_ROLE_MASTER) { rc = BLE_ERR_UNSPECIFIED; goto ltk_key_cmd_complete; } /* The connection should be awaiting a reply. If not, just discard */ if (connsm->enc_data.enc_state != CONN_ENC_S_LTK_REQ_WAIT) { rc = BLE_ERR_CMD_DISALLOWED; goto ltk_key_cmd_complete; } swap_buf(connsm->enc_data.enc_block.key, cmd->ltk, 16); ble_ll_calc_session_key(connsm); ble_ll_ctrl_start_enc_send(connsm); rc = BLE_ERR_SUCCESS; ltk_key_cmd_complete: rsp->conn_handle = htole16(handle); *rsplen = sizeof(*rsp); return rc; } /** * Called to process the LE long term key negative reply. * * Context: Link Layer Task. * * @param cmdbuf * @param rspbuf * @param ocf * * @return int */ int ble_ll_conn_hci_le_ltk_neg_reply(const uint8_t *cmdbuf, uint8_t len, uint8_t *rspbuf, uint8_t *rsplen) { const struct ble_hci_le_lt_key_req_neg_reply_cp *cmd = (const void *) cmdbuf; struct ble_hci_le_lt_key_req_neg_reply_rp *rsp = (void *) rspbuf; struct ble_ll_conn_sm *connsm; uint16_t handle; int rc; if (len != sizeof(*cmd)) { return BLE_ERR_INV_HCI_CMD_PARMS; } /* Find connection handle */ handle = le16toh(cmd->conn_handle); connsm = ble_ll_conn_find_active_conn(handle); if (!connsm) { rc = BLE_ERR_UNK_CONN_ID; goto ltk_key_cmd_complete; } /* Should never get this if we are a master! */ if (connsm->conn_role == BLE_LL_CONN_ROLE_MASTER) { rc = BLE_ERR_UNSPECIFIED; goto ltk_key_cmd_complete; } /* The connection should be awaiting a reply. If not, just discard */ if (connsm->enc_data.enc_state != CONN_ENC_S_LTK_REQ_WAIT) { rc = BLE_ERR_CMD_DISALLOWED; goto ltk_key_cmd_complete; } /* We received a negative reply! Send REJECT_IND */ ble_ll_ctrl_reject_ind_send(connsm, BLE_LL_CTRL_ENC_REQ, BLE_ERR_PINKEY_MISSING); connsm->enc_data.enc_state = CONN_ENC_S_LTK_NEG_REPLY; rc = BLE_ERR_SUCCESS; ltk_key_cmd_complete: rsp->conn_handle = htole16(handle); *rsplen = sizeof(*rsp); return rc; } #endif #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_SCA_UPDATE) int ble_ll_conn_req_peer_sca(const uint8_t *cmdbuf, uint8_t len, uint8_t *rspbuf, uint8_t *rsplen) { const struct ble_hci_le_request_peer_sca_cp *params = (const void *)cmdbuf; struct ble_ll_conn_sm *connsm; connsm = ble_ll_conn_find_active_conn(params->conn_handle); if (!connsm) { return BLE_ERR_UNK_CONN_ID; } if (!(connsm->remote_features[2] & (BLE_LL_FEAT_SCA_UPDATE >> 24))) { return BLE_ERR_UNSUPP_REM_FEATURE; } if (IS_PENDING_CTRL_PROC(connsm, BLE_LL_CTRL_PROC_SCA_UPDATE)) { /* Not really specified what we should return */ return BLE_ERR_CTLR_BUSY; } ble_ll_ctrl_proc_start(connsm, BLE_LL_CTRL_PROC_SCA_UPDATE); return 0; } #endif #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_PING) /** * Read authenticated payload timeout (OGF=3, OCF==0x007B) * * @param cmdbuf * @param rsplen * * @return int */ int ble_ll_conn_hci_rd_auth_pyld_tmo(const uint8_t *cmdbuf, uint8_t len, uint8_t *rspbuf, uint8_t *rsplen) { const struct ble_hci_cb_rd_auth_pyld_tmo_cp *cmd = (const void *) cmdbuf; struct ble_hci_cb_rd_auth_pyld_tmo_rp *rsp = (void *) rspbuf; struct ble_ll_conn_sm *connsm; uint16_t handle; int rc; if (len != sizeof(*cmd)) { return BLE_ERR_INV_HCI_CMD_PARMS; } handle = le16toh(cmd->conn_handle); connsm = ble_ll_conn_find_active_conn(handle); if (!connsm) { rc = BLE_ERR_UNK_CONN_ID; rsp->tmo = 0; } else { rc = BLE_ERR_SUCCESS; rsp->tmo = htole16(connsm->auth_pyld_tmo); } rsp->conn_handle = htole16(handle); *rsplen = sizeof(*rsp); return rc; } /** * Write authenticated payload timeout (OGF=3, OCF=00x7C) * * @param cmdbuf * @param rsplen * * @return int */ int ble_ll_conn_hci_wr_auth_pyld_tmo(const uint8_t *cmdbuf, uint8_t len, uint8_t *rspbuf, uint8_t *rsplen) { const struct ble_hci_cb_wr_auth_pyld_tmo_cp *cmd = (const void *) cmdbuf; struct ble_hci_cb_wr_auth_pyld_tmo_rp *rsp = (void *) rspbuf; struct ble_ll_conn_sm *connsm; uint32_t min_tmo; uint16_t handle; uint16_t tmo; int rc; if (len != sizeof(*cmd)) { return BLE_ERR_INV_HCI_CMD_PARMS; } rc = BLE_ERR_SUCCESS; handle = le16toh(cmd->conn_handle); connsm = ble_ll_conn_find_active_conn(handle); if (!connsm) { rc = BLE_ERR_UNK_CONN_ID; } else { /* * The timeout is in units of 10 msecs. We need to make sure that the * timeout is greater than or equal to connItvl * (1 + slaveLatency) */ tmo = le16toh(cmd->tmo); min_tmo = (uint32_t)connsm->conn_itvl * BLE_LL_CONN_ITVL_USECS; min_tmo *= (connsm->slave_latency + 1); min_tmo /= 10000; if (tmo < min_tmo) { rc = BLE_ERR_INV_HCI_CMD_PARMS; } else { connsm->auth_pyld_tmo = tmo; if (ble_npl_callout_is_active(&connsm->auth_pyld_timer)) { ble_ll_conn_auth_pyld_timer_start(connsm); } } } rsp->conn_handle = htole16(handle); *rsplen = sizeof(*rsp); return rc; } #endif #if (BLE_LL_BT5_PHY_SUPPORTED == 1) /** * Read current phy for connection (OGF=8, OCF==0x0030) * * @param cmdbuf * @param rsplen * * @return int */ int ble_ll_conn_hci_le_rd_phy(const uint8_t *cmdbuf, uint8_t len, uint8_t *rspbuf, uint8_t *rsplen) { const struct ble_hci_le_rd_phy_cp *cmd = (const void *) cmdbuf; struct ble_hci_le_rd_phy_rp *rsp = (void *) rspbuf; int rc; uint16_t handle; struct ble_ll_conn_sm *connsm; if (len != sizeof(*cmd)) { return BLE_ERR_INV_HCI_CMD_PARMS; } handle = le16toh(cmd->conn_handle); connsm = ble_ll_conn_find_active_conn(handle); if (!connsm) { rsp->tx_phy = 0; rsp->rx_phy = 0; rc = BLE_ERR_UNK_CONN_ID; } else { rsp->tx_phy = connsm->phy_data.cur_tx_phy; rsp->rx_phy = connsm->phy_data.cur_rx_phy; rc = BLE_ERR_SUCCESS; } rsp->conn_handle = htole16(handle); *rsplen = sizeof(*rsp); return rc; } /** * Set PHY preferences for connection * * @param cmdbuf * * @return int */ int ble_ll_conn_hci_le_set_phy(const uint8_t *cmdbuf, uint8_t len) { const struct ble_hci_le_set_phy_cp *cmd = (const void *) cmdbuf; int rc; uint16_t phy_options; uint8_t tx_phys; uint8_t rx_phys; uint16_t handle; struct ble_ll_conn_sm *connsm; if (len != sizeof(*cmd)) { return BLE_ERR_INV_HCI_CMD_PARMS; } handle = le16toh(cmd->conn_handle); connsm = ble_ll_conn_find_active_conn(handle); if (!connsm) { return BLE_ERR_UNK_CONN_ID; } /* * If host has requested a PHY update and we are not finished do * not allow another one */ if (CONN_F_HOST_PHY_UPDATE(connsm)) { return BLE_ERR_CMD_DISALLOWED; } phy_options = le16toh(cmd->phy_options); if (phy_options > BLE_HCI_LE_PHY_CODED_S8_PREF) { return BLE_ERR_INV_HCI_CMD_PARMS; } /* Check valid parameters */ rc = ble_ll_hci_chk_phy_masks(cmd->all_phys, cmd->tx_phys, cmd->rx_phys, &tx_phys, &rx_phys); if (rc) { goto phy_cmd_param_err; } connsm->phy_data.phy_options = phy_options & 0x03; connsm->phy_data.host_pref_tx_phys_mask = tx_phys, connsm->phy_data.host_pref_rx_phys_mask = rx_phys; /* * The host preferences override the default phy preferences. Currently, * the only reason the controller will initiate a procedure on its own * is due to the fact that the host set default preferences. So if there * is a pending control procedure and it has not yet started, we do not * need to perform the default controller procedure. */ if (IS_PENDING_CTRL_PROC(connsm, BLE_LL_CTRL_PROC_PHY_UPDATE)) { if (connsm->cur_ctrl_proc != BLE_LL_CTRL_PROC_PHY_UPDATE) { CONN_F_CTRLR_PHY_UPDATE(connsm) = 0; } CONN_F_HOST_PHY_UPDATE(connsm) = 1; } else { /* * We could be doing a peer-initiated PHY update procedure. If this * is the case the requested phy preferences will not both be 0. If * we are not done with a peer-initiated procedure we just set the * pending bit but do not start the control procedure. */ if (CONN_F_PEER_PHY_UPDATE(connsm)) { connsm->pending_ctrl_procs |= (1 << BLE_LL_CTRL_PROC_PHY_UPDATE); CONN_F_HOST_PHY_UPDATE(connsm) = 1; } else { /* Check if we should start phy update procedure */ if (!ble_ll_conn_chk_phy_upd_start(connsm)) { CONN_F_HOST_PHY_UPDATE(connsm) = 1; } else { /* * Set flag to send a PHY update complete event. We set flag * even if we do not do an update procedure since we have to * inform the host even if we decide not to change anything. */ CONN_F_PHY_UPDATE_EVENT(connsm) = 1; } } } phy_cmd_param_err: return rc; } #endif #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV_SYNC_TRANSFER) int ble_ll_set_sync_transfer_params(const uint8_t *cmdbuf, uint8_t len, uint8_t *rspbuf, uint8_t *rsplen) { const struct ble_hci_le_periodic_adv_sync_transfer_params_cp *cmd = (const void *)cmdbuf; struct ble_hci_le_periodic_adv_sync_transfer_params_rp *rsp = (void *) rspbuf; struct ble_ll_conn_sm *connsm; uint16_t sync_timeout; uint16_t skip; int rc; if (len != sizeof(*cmd)) { rc = BLE_ERR_INV_HCI_CMD_PARMS; goto done; } if (cmd->mode > 0x02) { rc = BLE_ERR_INV_HCI_CMD_PARMS; goto done; } skip = le16toh(cmd->skip); if (skip > 0x01f3) { rc = BLE_ERR_INV_HCI_CMD_PARMS; goto done; } sync_timeout = le16toh(cmd->sync_timeout); if ((sync_timeout < 0x000a) || (sync_timeout > 0x4000)) { rc = BLE_ERR_INV_HCI_CMD_PARMS; goto done; } /* we don't support any CTE yet */ if (cmd->sync_cte_type) { rc = BLE_ERR_UNSUPPORTED; goto done; } connsm = ble_ll_conn_find_active_conn(le16toh(cmd->conn_handle)); if (!connsm) { rc = BLE_ERR_UNK_CONN_ID; goto done; } /* timeout in 10ms units */ connsm->sync_transfer_sync_timeout = sync_timeout * 10000; connsm->sync_transfer_mode = cmd->mode; connsm->sync_transfer_skip = skip; rc = BLE_ERR_SUCCESS; done: rsp->conn_handle = cmd->conn_handle; *rsplen = sizeof(*rsp); return rc; } int ble_ll_set_default_sync_transfer_params(const uint8_t *cmdbuf, uint8_t len) { const struct ble_hci_le_set_default_periodic_sync_transfer_params_cp *cmd = (const void *)cmdbuf; uint16_t sync_timeout; uint16_t skip; if (len != sizeof(*cmd)) { return BLE_ERR_INV_HCI_CMD_PARMS; } if (cmd->mode > 0x02) { return BLE_ERR_INV_HCI_CMD_PARMS; } skip = le16toh(cmd->skip); if (skip > 0x01f3) { return BLE_ERR_INV_HCI_CMD_PARMS; } sync_timeout = le16toh(cmd->sync_timeout); if ((sync_timeout < 0x000a) || (sync_timeout > 0x4000)) { return BLE_ERR_INV_HCI_CMD_PARMS; } /* we don't support any CTE yet */ if (cmd->sync_cte_type) { return BLE_ERR_UNSUPPORTED; } /* timeout in 10ms units */ g_ble_ll_conn_sync_transfer_params.sync_timeout_us = sync_timeout * 10000; g_ble_ll_conn_sync_transfer_params.mode = cmd->mode; g_ble_ll_conn_sync_transfer_params.max_skip = skip; return BLE_ERR_SUCCESS; } #endif