diff --git a/nimble/host/include/host/ble_gap.h b/nimble/host/include/host/ble_gap.h index 34a833dd3..78dedb769 100644 --- a/nimble/host/include/host/ble_gap.h +++ b/nimble/host/include/host/ble_gap.h @@ -3282,10 +3282,12 @@ int ble_gap_connect(uint8_t own_addr_type, const ble_addr_t *peer_addr, * @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 duration_ms The duration of the connect procedure. On + * expiration, the procedure is cancelled and + * a BLE_GAP_EVENT_CONNECT event with a status + * of BLE_HS_ETIMEOUT is reported. Units are + * milliseconds. Specify BLE_HS_FOREVER to + * wait indefinitely for the controller. * @param phy_mask Define on which PHYs connection attempt should * be done * @param phy_1m_conn_params Additional arguments specifying the diff --git a/nimble/host/src/ble_gap.c b/nimble/host/src/ble_gap.c index 1c8cd9636..aacaebc0b 100644 --- a/nimble/host/src/ble_gap.c +++ b/nimble/host/src/ble_gap.c @@ -222,6 +222,10 @@ struct ble_gap_master_state { uint8_t using_wl:1; uint8_t our_addr_type:2; uint8_t cancel:1; +#if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) + uint8_t synchronized:1; + uint8_t timeout_cancel_pending:1; +#endif ble_addr_t peer_addr; } conn; @@ -1672,6 +1676,10 @@ ble_gap_master_reset_state(void) ble_gap_master.conn.cancel = 0; ble_gap_master.cb = NULL; ble_gap_master.cb_arg = NULL; +#if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) + ble_gap_master.conn.synchronized = 0; + ble_gap_master.conn.timeout_cancel_pending = 0; +#endif memset(&ble_gap_master.conn.peer_addr, 0, sizeof(ble_gap_master.conn.peer_addr)); @@ -3275,7 +3283,8 @@ ble_gap_periodic_sync_reattempt_clear(bool clear_sync_reattempt) #endif void -ble_gap_rx_peroidic_adv_sync_estab(const struct ble_hci_ev_le_subev_periodic_adv_sync_estab *ev) +ble_gap_rx_peroidic_adv_sync_estab(const struct ble_hci_ev_le_subev_periodic_adv_sync_estab *ev, + uint8_t subevent) { uint16_t sync_handle; struct ble_gap_event event; @@ -3284,6 +3293,9 @@ ble_gap_rx_peroidic_adv_sync_estab(const struct ble_hci_ev_le_subev_periodic_adv #if MYNEWT_VAL(BLE_ENABLE_CONN_REATTEMPT) && NIMBLE_BLE_CONNECT int rc; bool reattempt_triggered = false; +#endif +#if !MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) + (void)subevent; #endif memset(&event, 0, sizeof event); @@ -3320,10 +3332,13 @@ ble_gap_rx_peroidic_adv_sync_estab(const struct ble_hci_ev_le_subev_periodic_adv event.periodic_sync.per_adv_ival = ev->interval; event.periodic_sync.adv_clk_accuracy = ev->aca; #if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) - event.periodic_sync.num_subevents = ev->num_subevents; - event.periodic_sync.subevent_interval = ev->subevent_interval; - event.periodic_sync.response_slot_delay = ev->response_slot_delay; - event.periodic_sync.response_slot_spacing = ev->response_slot_spacing; + if (subevent == BLE_HCI_LE_SUBEV_PERIODIC_ADV_SYNC_ESTAB_V2) { + ble_gap_sync.psync->num_subevents = ev->num_subevents; + event.periodic_sync.num_subevents = ev->num_subevents; + event.periodic_sync.subevent_interval = ev->subevent_interval; + event.periodic_sync.response_slot_delay = ev->response_slot_delay; + event.periodic_sync.response_slot_spacing = ev->response_slot_spacing; + } #endif ble_hs_periodic_sync_insert(ble_gap_sync.psync); @@ -3657,7 +3672,8 @@ periodic_adv_transfer_disable(uint16_t conn_handle) } void -ble_gap_rx_periodic_adv_sync_transfer(const struct ble_hci_ev_le_subev_periodic_adv_sync_transfer *ev) +ble_gap_rx_periodic_adv_sync_transfer(const struct ble_hci_ev_le_subev_periodic_adv_sync_transfer *ev, + uint8_t subevent) { struct ble_hci_le_periodic_adv_term_sync_cp cmd_term; struct ble_gap_event event; @@ -3667,6 +3683,9 @@ ble_gap_rx_periodic_adv_sync_transfer(const struct ble_hci_ev_le_subev_periodic_ uint16_t conn_handle; uint16_t opcode; void *cb_arg; +#if !MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) + (void)subevent; +#endif conn_handle = le16toh(ev->conn_handle); @@ -3697,7 +3716,11 @@ ble_gap_rx_periodic_adv_sync_transfer(const struct ble_hci_ev_le_subev_periodic_ memset(&event, 0, sizeof event); #if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) - event.type = BLE_GAP_EVENT_PERIODIC_TRANSFER_V2; + if (subevent == BLE_HCI_LE_SUBEV_PERIODIC_ADV_SYNC_TRANSFER_V2) { + event.type = BLE_GAP_EVENT_PERIODIC_TRANSFER_V2; + } else { + event.type = BLE_GAP_EVENT_PERIODIC_TRANSFER; + } #else event.type = BLE_GAP_EVENT_PERIODIC_TRANSFER; #endif // MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) @@ -3714,6 +3737,11 @@ ble_gap_rx_periodic_adv_sync_transfer(const struct ble_hci_ev_le_subev_periodic_ 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; +#if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) + if (subevent == BLE_HCI_LE_SUBEV_PERIODIC_ADV_SYNC_TRANSFER_V2) { + conn->psync->num_subevents = ev->num_subevents; + } +#endif ble_hs_periodic_sync_insert(conn->psync); } @@ -3730,10 +3758,12 @@ ble_gap_rx_periodic_adv_sync_transfer(const struct ble_hci_ev_le_subev_periodic_ event.periodic_transfer.per_adv_itvl = le16toh(ev->interval); event.periodic_transfer.adv_clk_accuracy = ev->aca; #if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) - event.periodic_transfer.num_subevents = ev->num_subevents; - event.periodic_transfer.subevent_interval = ev->subevent_interval; - event.periodic_transfer.response_slot_delay = ev->response_slot_delay; - event.periodic_transfer.response_slot_spacing = ev->response_slot_spacing; + if (subevent == BLE_HCI_LE_SUBEV_PERIODIC_ADV_SYNC_TRANSFER_V2) { + event.periodic_transfer.num_subevents = ev->num_subevents; + event.periodic_transfer.subevent_interval = ev->subevent_interval; + event.periodic_transfer.response_slot_delay = ev->response_slot_delay; + event.periodic_transfer.response_slot_spacing = ev->response_slot_spacing; + } #endif ble_hs_unlock(); @@ -3953,6 +3983,69 @@ ble_gap_rd_rem_ver_tx(uint16_t handle) } #endif +#if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) && NIMBLE_BLE_CONNECT +/** + * Determines the local address type used for a PAwR peripheral connection. + * + * Such a link is established by the controller through the periodic sync, so + * no advertising instance describes it; the controller uses our identity + * address, or an RPA generated from it. + */ +static int +ble_gap_pawr_conn_our_addr(const struct ble_gap_conn_complete *evt, + uint8_t *out_own_addr_type, + uint8_t *out_random_addr) +{ + uint8_t own_addr_type; + int privacy; + int rc; + + privacy = memcmp(evt->local_rpa, BLE_ADDR_ANY->val, 6) != 0; + + rc = ble_hs_id_infer_auto(privacy, &own_addr_type); + if (rc != 0) { + return rc; + } + +#if MYNEWT_VAL(BLE_EXT_ADV) + if (ble_hs_misc_own_addr_type_to_id(own_addr_type) == BLE_ADDR_RANDOM) { + rc = ble_hs_id_copy_addr(BLE_ADDR_RANDOM, out_random_addr, NULL); + if (rc != 0) { + return rc; + } + } +#else + (void)out_random_addr; +#endif + + *out_own_addr_type = own_addr_type; + return 0; +} +#endif + +#if NIMBLE_BLE_CONNECT +/** + * Drops a link the host has no state for. Such a connection can never be used + * or terminated by the application, so leaving it up would strand it until the + * supervision timeout expires. + */ +static void +ble_gap_disconnect_unclaimed_conn(uint16_t conn_handle) +{ + struct ble_hci_lc_disconnect_cp cmd; + int rc; + + cmd.conn_handle = htole16(conn_handle); + cmd.reason = BLE_ERR_REM_USER_CONN_TERM; + + rc = ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_LINK_CTRL, + BLE_HCI_OCF_DISCONNECT_CMD), + &cmd, sizeof(cmd), NULL, 0); + BLE_HS_LOG(WARN, "GAP: no procedure owns conn_handle=%d; disconnecting; " + "rc=%d\n", conn_handle, rc); +} +#endif + /** * Processes an incoming connection-complete HCI event. * instance parameter is valid only for slave connection. @@ -4010,13 +4103,26 @@ ble_gap_rx_conn_complete(struct ble_gap_conn_complete *evt, uint8_t instance) } #endif break; -#if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) case BLE_ERR_CONN_ESTABLISHMENT: +#if MYNEWT_VAL(BLE_ROLE_CENTRAL) || MYNEWT_VAL(BLE_ROLE_OBSERVER) + /* + * Failed enhanced connection-complete events do not contain + * valid role or PAwR handle fields. Use host state to route + * failures before inspecting those fields. + */ + if (ble_gap_conn_active()) { + ble_gap_master_connect_failure( + BLE_HS_HCI_ERR(evt->status)); + break; + } +#endif +#if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) if (!v1_evt) { ble_gap_rx_conn_comp_failed(evt); + break; } +#endif break; -#endif // MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) default: /* this should never happen, unless controller is broken */ BLE_HS_LOG(INFO, "controller reported invalid error code in conn" @@ -4044,13 +4150,22 @@ ble_gap_rx_conn_complete(struct ble_gap_conn_complete *evt, uint8_t instance) case BLE_HCI_LE_CONN_COMPLETE_ROLE_MASTER: rc = ble_gap_accept_master_conn(); if (rc != 0) { + ble_gap_disconnect_unclaimed_conn(evt->connection_handle); return rc; } break; case BLE_HCI_LE_CONN_COMPLETE_ROLE_SLAVE: +#if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) + if (evt->sync_handle != 0xFFFF) { + /* PAwR peripheral links are accepted by the controller through + * the synchronized connection procedure, not local advertising. */ + rc = 0; + } else +#endif rc = ble_gap_accept_slave_conn(instance); if (rc != 0) { + ble_gap_disconnect_unclaimed_conn(evt->connection_handle); return rc; } break; @@ -4082,7 +4197,26 @@ ble_gap_rx_conn_complete(struct ble_gap_conn_complete *evt, uint8_t instance) if (!v1_evt && evt->sync_handle != 0xFFFF) { /* PAwR peripheral: application callback is on the periodic sync. */ struct ble_hs_periodic_sync *psync; + uint8_t own_addr_type; +#if MYNEWT_VAL(BLE_EXT_ADV) + uint8_t random_addr[BLE_DEV_ADDR_LEN]; +#endif +#if MYNEWT_VAL(BLE_EXT_ADV) + rc = ble_gap_pawr_conn_our_addr(evt, &own_addr_type, random_addr); +#else + rc = ble_gap_pawr_conn_our_addr(evt, &own_addr_type, NULL); +#endif + if (rc == 0) { + conn->bhc_our_addr_type = own_addr_type; +#if MYNEWT_VAL(BLE_EXT_ADV) + if (ble_hs_misc_own_addr_type_to_id(own_addr_type) == + BLE_ADDR_RANDOM) { + memcpy(conn->bhc_our_rnd_addr, random_addr, + BLE_DEV_ADDR_LEN); + } +#endif + } ble_hs_lock(); psync = ble_hs_periodic_sync_find_by_handle(evt->sync_handle); if (psync != NULL) { @@ -4094,12 +4228,20 @@ ble_gap_rx_conn_complete(struct ble_gap_conn_complete *evt, uint8_t instance) conn->bhc_cb = ble_gap_master.cb; conn->bhc_cb_arg = ble_gap_master.cb_arg; conn->bhc_our_addr_type = ble_gap_master.conn.our_addr_type; + ble_gap_master_reset_state(); } else { + ble_hs_lock(); 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); + ble_hs_unlock(); } #else + ble_hs_lock(); 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; @@ -4107,6 +4249,7 @@ ble_gap_rx_conn_complete(struct ble_gap_conn_complete *evt, uint8_t instance) memcpy(conn->bhc_our_rnd_addr, ble_gap_slave[instance].rnd_addr, 6); #endif ble_gap_slave_reset_state(instance); + ble_hs_unlock(); #endif if (master_match) { @@ -4439,11 +4582,28 @@ static int32_t ble_gap_master_timer(void) { uint32_t ticks_until_exp; + uint8_t op; +#if NIMBLE_BLE_CONNECT && MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) && \ + (MYNEWT_VAL(BLE_ROLE_CENTRAL) || MYNEWT_VAL(BLE_ROLE_OBSERVER)) + bool synchronized; + bool timeout_cancel_pending; +#endif #if NIMBLE_BLE_CONNECT || (NIMBLE_BLE_SCAN && !MYNEWT_VAL(BLE_EXT_ADV)) int rc; #endif + ble_hs_lock(); ticks_until_exp = ble_gap_master_ticks_until_exp(); + op = ble_gap_master.op; +#if NIMBLE_BLE_CONNECT && MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) && \ + (MYNEWT_VAL(BLE_ROLE_CENTRAL) || MYNEWT_VAL(BLE_ROLE_OBSERVER)) + synchronized = op == BLE_GAP_OP_M_CONN && + ble_gap_master.conn.synchronized; + timeout_cancel_pending = op == BLE_GAP_OP_M_CONN && + ble_gap_master.conn.timeout_cancel_pending; +#endif + ble_hs_unlock(); + if (ticks_until_exp != 0) { /* Timer not expired yet. */ return ticks_until_exp; @@ -4451,16 +4611,70 @@ ble_gap_master_timer(void) /*** Timer expired; process event. */ - switch (ble_gap_master.op) { + switch (op) { #if NIMBLE_BLE_CONNECT case BLE_GAP_OP_M_CONN: +#if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) && \ + (MYNEWT_VAL(BLE_ROLE_CENTRAL) || MYNEWT_VAL(BLE_ROLE_OBSERVER)) + if (timeout_cancel_pending) { + /* + * The controller accepted the cancel but did not report its + * connection-complete result within the grace period. + */ + ble_hs_lock(); + ble_gap_master.exp_set = 0; + ble_hs_unlock(); + + ble_gap_master_connect_cancelled(); + break; + } +#endif rc = ble_gap_conn_cancel_tx(); +#if MYNEWT_VAL(BLE_ROLE_CENTRAL) || MYNEWT_VAL(BLE_ROLE_OBSERVER) + if (rc == BLE_HS_HCI_ERR(BLE_ERR_CMD_DISALLOWED) || + rc == BLE_HS_HCI_ERR(BLE_ERR_UNKNOWN_HCI_CMD)) { + /* The controller has no connection attempt pending, so no + * connection complete event will follow the cancel. Report the + * timeout here instead of retrying the cancel forever. + */ + ble_hs_lock(); + ble_gap_master.exp_set = 0; + ble_hs_unlock(); + + ble_gap_master_connect_cancelled(); + break; + } +#endif 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 { +#if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) && \ + (MYNEWT_VAL(BLE_ROLE_CENTRAL) || MYNEWT_VAL(BLE_ROLE_OBSERVER)) + /* + * A synchronized create-connection controller can fail to send + * the completion event after accepting cancel. Allow the normal + * event a short grace period, then complete the host procedure if + * the controller remains silent. + */ + if (synchronized) { + uint32_t grace_ticks = + ble_npl_time_ms_to_ticks32( + BLE_GAP_CANCEL_RETRY_TIMEOUT_MS); + + ble_hs_lock(); + ble_gap_master.conn.timeout_cancel_pending = 1; + ble_gap_master.exp_os_ticks = + ble_npl_time_get() + grace_ticks; + ble_hs_unlock(); + return grace_ticks; + } +#endif + /* Stop the timer now that the cancel command has been acked. */ + ble_hs_lock(); ble_gap_master.exp_set = 0; + ble_hs_unlock(); /* Timeout gets reported when we receive a connection complete * event indicating the connect procedure has been cancelled. @@ -8840,6 +9054,7 @@ ble_gap_connect_with_synced(uint8_t own_addr_type, uint8_t advertising_handle, const struct ble_gap_conn_params *phy_coded_conn_params, ble_gap_event_fn *cb, void *cb_arg) { + uint32_t duration_ticks = 0; int rc; STATS_INC(ble_gap_stats, initiate); @@ -8903,11 +9118,20 @@ ble_gap_connect_with_synced(uint8_t own_addr_type, uint8_t advertising_handle, duration_ms = BLE_GAP_CONN_DUR_DFLT; } - /* The connection creation timeout is not really useful for PAwR. - * The controller will give a result for the connection attempt - * within a periodic interval. We do not know the periodic interval - * used, so disable the timeout. - */ + /* The controller reports the outcome of a synchronized connection attempt + * within a periodic interval, but a controller that never reports it would + * otherwise leave the initiate procedure pending forever with no event for + * the application. Keep the host timeout as a recovery path; callers that + * do not want it can pass BLE_HS_FOREVER. + */ + 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) { @@ -8928,6 +9152,7 @@ ble_gap_connect_with_synced(uint8_t own_addr_type, uint8_t advertising_handle, 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.conn.synchronized = 1; if (peer_addr != NULL) { ble_gap_master.conn.peer_addr = *peer_addr; } else { @@ -8945,6 +9170,12 @@ ble_gap_connect_with_synced(uint8_t own_addr_type, uint8_t advertising_handle, goto done; } +#if MYNEWT_VAL(BLE_ROLE_CENTRAL) + if (duration_ms != BLE_HS_FOREVER) { + ble_gap_master_set_timer(duration_ticks); + } +#endif + rc = 0; done: @@ -9300,6 +9531,9 @@ ble_gap_ext_connect(uint8_t own_addr_type, const ble_addr_t *peer_addr, 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; +#if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) + ble_gap_master.conn.synchronized = 0; +#endif if (peer_addr != NULL) { ble_gap_master.conn.peer_addr = *peer_addr; } else { @@ -9483,6 +9717,9 @@ ble_gap_connect(uint8_t own_addr_type, const ble_addr_t *peer_addr, 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; +#if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) + ble_gap_master.conn.synchronized = 0; +#endif if (peer_addr != NULL) { ble_gap_master.conn.peer_addr = *peer_addr; } else { diff --git a/nimble/host/src/ble_gap_priv.h b/nimble/host/src/ble_gap_priv.h index 8afdc1699..7e44b8810 100644 --- a/nimble/host/src/ble_gap_priv.h +++ b/nimble/host/src/ble_gap_priv.h @@ -84,10 +84,12 @@ void ble_gap_rx_le_scan_timeout(void); void ble_gap_rx_ext_adv_report(struct ble_gap_ext_disc_desc *desc); void ble_gap_rx_adv_set_terminated(const struct ble_hci_ev_le_subev_adv_set_terminated *ev); #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); +void ble_gap_rx_peroidic_adv_sync_estab(const struct ble_hci_ev_le_subev_periodic_adv_sync_estab *ev, + uint8_t subevent); void ble_gap_rx_periodic_adv_rpt(const struct ble_hci_ev_le_subev_periodic_adv_rpt *ev); void ble_gap_rx_periodic_adv_sync_lost(const struct ble_hci_ev_le_subev_periodic_adv_sync_lost *ev); -void ble_gap_rx_periodic_adv_sync_transfer(const struct ble_hci_ev_le_subev_periodic_adv_sync_transfer *ev); +void ble_gap_rx_periodic_adv_sync_transfer(const struct ble_hci_ev_le_subev_periodic_adv_sync_transfer *ev, + uint8_t subevent); #endif #if MYNEWT_VAL(BLE_PERIODIC_ADV_SYNC_BIGINFO_REPORTS) && !MYNEWT_VAL(BLE_ISO) void ble_gap_rx_biginfo_adv_rpt(const struct ble_hci_ev_le_subev_biginfo_adv_report *ev); diff --git a/nimble/host/src/ble_hs_hci_evt.c b/nimble/host/src/ble_hs_hci_evt.c index 5c096e832..08b69fd29 100644 --- a/nimble/host/src/ble_hs_hci_evt.c +++ b/nimble/host/src/ble_hs_hci_evt.c @@ -17,6 +17,7 @@ * under the License. */ #include +#include #include #include #include @@ -761,6 +762,86 @@ ble_hs_hci_evt_le_meta(uint8_t event_code, const void *data, unsigned int len) #if NIMBLE_BLE_CONNECT +#if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) +static void +ble_hs_hci_evt_conn_norm_handles(struct ble_gap_conn_complete *evt) +{ + if (evt->status != BLE_ERR_SUCCESS) { + evt->adv_handle = 0xff; + evt->sync_handle = 0xffff; + return; + } + + switch (evt->role) { + case BLE_HCI_LE_CONN_COMPLETE_ROLE_MASTER: + evt->sync_handle = 0xffff; + if (evt->adv_handle > 0xef) { + evt->adv_handle = 0xff; + } + break; + + case BLE_HCI_LE_CONN_COMPLETE_ROLE_SLAVE: + evt->adv_handle = 0xff; + if (evt->sync_handle > 0x0eff) { + evt->sync_handle = 0xffff; + } + break; + + default: + evt->adv_handle = 0xff; + evt->sync_handle = 0xffff; + break; + } +} + +static bool +ble_hs_hci_evt_pawr_conn(struct ble_gap_conn_complete *evt, + bool handles_present) +{ + struct ble_hs_periodic_sync *psync; + + /* Only a peripheral is identified by the sync handle. */ + if (evt->status == BLE_ERR_SUCCESS && + evt->role != BLE_HCI_LE_CONN_COMPLETE_ROLE_SLAVE) { + return false; + } + + ble_hs_lock(); + /* + * The sync handle is only meaningful on a successful V2 event; it has + * already been dropped in all the circumstances where the spec requires the + * host to ignore it. Regular periodic syncs share the same list, so a sync + * without subevents does not describe a PAwR train and must not + * short-circuit the pending-connection / Advertising Set Terminated path. + * That also rejects controllers reporting sync handle 0 for a connection + * established from extended advertising. + */ + psync = NULL; + if (evt->status == BLE_ERR_SUCCESS && handles_present && + evt->sync_handle != 0xffff) { + psync = ble_hs_periodic_sync_find_by_handle(evt->sync_handle); + if (psync != NULL && psync->num_subevents == 0) { + psync = NULL; + } + } + + if (psync == NULL && + (evt->status == BLE_ERR_CONN_ESTABLISHMENT || + (evt->status == BLE_ERR_SUCCESS && !handles_present))) { + psync = ble_hs_periodic_sync_find_pawr_locked(); + } + + if (psync != NULL) { + evt->sync_handle = psync->sync_handle; + } else { + evt->sync_handle = 0xffff; + } + ble_hs_unlock(); + + return psync != NULL; +} +#endif + static int ble_hs_hci_evt_le_enh_conn_complete(uint8_t subevent, const void *data, unsigned int len) @@ -791,6 +872,16 @@ ble_hs_hci_evt_le_enh_conn_complete(uint8_t subevent, const void *data, evt.status = ev->status; +#if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) + if (subevent == BLE_HCI_LE_SUBEV_ENH_CONN_COMPLETE) { + evt.adv_handle = 0xff; + evt.sync_handle = 0xffff; + } else { + evt.adv_handle = ev->adv_handle; + evt.sync_handle = le16toh(ev->sync_handle); + } +#endif + if (evt.status == BLE_ERR_SUCCESS) { evt.connection_handle = le16toh(ev->conn_handle); evt.role = ev->role; @@ -824,10 +915,32 @@ ble_hs_hci_evt_le_enh_conn_complete(uint8_t subevent, const void *data, evt.connection_handle = BLE_HS_CONN_HANDLE_NONE; #endif } -#if MYNEWT_VAL(BLE_EXT_ADV) && !MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) +#if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) + ble_hs_hci_evt_conn_norm_handles(&evt); + + /* + * PAwR connection-complete failures contain only a valid status field, + * so associate the event with the active PAwR sync before inspecting role. + */ + if (subevent == BLE_HCI_LE_SUBEV_ENH_CONN_COMPLETE_V2 && + ble_hs_hci_evt_pawr_conn(&evt, true)) { + return ble_gap_rx_conn_complete(&evt, 0); + } +#endif +#if MYNEWT_VAL(BLE_EXT_ADV) if (evt.status == BLE_ERR_DIR_ADV_TMO || evt.role == BLE_HCI_LE_CONN_COMPLETE_ROLE_SLAVE) { + /* PAwR slave connections are not followed by LE Advertising Set + * Terminated, so they must not be stored as pending. + */ +#if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) + if (evt.status == BLE_ERR_SUCCESS && + subevent == BLE_HCI_LE_SUBEV_ENH_CONN_COMPLETE && + ble_hs_hci_evt_pawr_conn(&evt, false)) { + return ble_gap_rx_conn_complete(&evt, 0); + } +#endif #if MYNEWT_VAL(BLE_STATIC_TO_DYNAMIC) if (ble_hs_hci_ensure_ctx()) { BLE_HS_LOG(ERROR, "%s rc=%d\n", __func__, BLE_HS_ENOMEM); @@ -843,16 +956,15 @@ ble_hs_hci_evt_le_enh_conn_complete(uint8_t subevent, const void *data, #endif #if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) - if (subevent == BLE_HCI_LE_SUBEV_ENH_CONN_COMPLETE) { - evt.adv_handle = 0xFF; - evt.sync_handle = 0xFFFF; - } else { - evt.adv_handle = ev->adv_handle; - evt.sync_handle = ev->sync_handle; - } -#endif - return ble_gap_rx_conn_complete(&evt, 0); + uint8_t instance = 0; + if (evt.adv_handle < BLE_ADV_INSTANCES) { + instance = evt.adv_handle; + } + return ble_gap_rx_conn_complete(&evt, instance); +#else + return ble_gap_rx_conn_complete(&evt, 0); +#endif } static int @@ -871,6 +983,12 @@ ble_hs_hci_evt_le_conn_complete(uint8_t subevent, const void *data, evt.status = ev->status; +#if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) + /* This event carries no PAwR handles. */ + evt.adv_handle = 0xff; + evt.sync_handle = 0xffff; +#endif + if (evt.status == BLE_ERR_SUCCESS) { evt.connection_handle = le16toh(ev->conn_handle); evt.role = ev->role; @@ -907,6 +1025,12 @@ ble_hs_hci_evt_le_conn_complete(uint8_t subevent, const void *data, if (evt.status == BLE_ERR_DIR_ADV_TMO || evt.role == BLE_HCI_LE_CONN_COMPLETE_ROLE_SLAVE) { +#if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) + if (evt.status == BLE_ERR_SUCCESS && + ble_hs_hci_evt_pawr_conn(&evt, false)) { + return ble_gap_rx_conn_complete(&evt, 0); + } +#endif #if MYNEWT_VAL(BLE_STATIC_TO_DYNAMIC) if (ble_hs_hci_ensure_ctx()) { BLE_HS_LOG(ERROR, "%s rc=%d\n", __func__, BLE_HS_ENOMEM); @@ -1477,13 +1601,25 @@ ble_hs_hci_evt_le_periodic_adv_sync_estab(uint8_t subevent, const void *data, unsigned int len) { const struct ble_hci_ev_le_subev_periodic_adv_sync_estab *ev = data; + unsigned int min_len; - if (len != sizeof(*ev)) { +#if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) + if (subevent == BLE_HCI_LE_SUBEV_PERIODIC_ADV_SYNC_ESTAB) { + min_len = offsetof(struct ble_hci_ev_le_subev_periodic_adv_sync_estab, + num_subevents); + } else { + min_len = sizeof(*ev); + } +#else + min_len = sizeof(*ev); +#endif + + if (len < min_len) { BLE_HS_LOG(ERROR, "%s rc=%d\n", __func__, BLE_HS_ECONTROLLER); return BLE_HS_ECONTROLLER; } - ble_gap_rx_peroidic_adv_sync_estab(ev); + ble_gap_rx_peroidic_adv_sync_estab(ev, subevent); return 0; } @@ -1564,22 +1700,25 @@ ble_hs_hci_evt_le_periodic_adv_sync_transfer(uint8_t subevent, const void *data, { #if MYNEWT_VAL(BLE_PERIODIC_ADV_SYNC_TRANSFER) const struct ble_hci_ev_le_subev_periodic_adv_sync_transfer *ev = data; - struct ble_hci_ev_le_subev_periodic_adv_sync_transfer ev_copy; + unsigned int min_len; #if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) - /* V1 event is 4 bytes shorter (lacks PAwR fields); accept both lengths */ - unsigned int v1_len = sizeof(*ev) - 4; - if (len != sizeof(*ev) && len != v1_len) { + if (subevent == BLE_HCI_LE_SUBEV_PERIODIC_ADV_SYNC_TRANSFER) { + min_len = offsetof(struct ble_hci_ev_le_subev_periodic_adv_sync_transfer, + num_subevents); + } else { + min_len = sizeof(*ev); + } #else - if (len != sizeof(*ev)) { + min_len = sizeof(*ev); #endif + + if (len < min_len) { BLE_HS_LOG(ERROR, "%s rc=%d\n", __func__, BLE_HS_EBADDATA); return BLE_HS_EBADDATA; } - memset(&ev_copy, 0, sizeof(ev_copy)); - memcpy(&ev_copy, ev, len); - ble_gap_rx_periodic_adv_sync_transfer(&ev_copy); + ble_gap_rx_periodic_adv_sync_transfer(ev, subevent); #endif return 0; diff --git a/nimble/host/src/ble_hs_periodic_sync.c b/nimble/host/src/ble_hs_periodic_sync.c index c4f2e908f..9fbc01db3 100644 --- a/nimble/host/src/ble_hs_periodic_sync.c +++ b/nimble/host/src/ble_hs_periodic_sync.c @@ -206,6 +206,32 @@ ble_hs_periodic_sync_first(void) return psync; } +#if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) +/** + * Find first PAwR sync (num_subevents > 0) in the active sync list. + * + * Regular periodic syncs share the same list; callers that need a PAwR + * association must not use ble_hs_periodic_sync_first(). + * + * @note Host lock must be held. + */ +struct ble_hs_periodic_sync * +ble_hs_periodic_sync_find_pawr_locked(void) +{ + struct ble_hs_periodic_sync *psync; + + BLE_HS_DBG_ASSERT(ble_hs_locked_by_cur_task()); + + SLIST_FOREACH(psync, &g_ble_hs_periodic_sync_handles, next) { + if (psync->num_subevents > 0) { + return psync; + } + } + + return NULL; +} +#endif + void ble_hs_periodic_sync_deinit(void) { diff --git a/nimble/host/src/ble_hs_periodic_sync_priv.h b/nimble/host/src/ble_hs_periodic_sync_priv.h index 1f0a02526..a675b27c6 100644 --- a/nimble/host/src/ble_hs_periodic_sync_priv.h +++ b/nimble/host/src/ble_hs_periodic_sync_priv.h @@ -31,6 +31,10 @@ struct ble_hs_periodic_sync { uint16_t sync_handle; ble_addr_t advertiser_addr; uint8_t adv_sid; +#if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) + /* From Sync Established/Transfer V2; >0 means PAwR sync. */ + uint8_t num_subevents; +#endif ble_gap_event_fn *cb; void *cb_arg; @@ -46,6 +50,9 @@ struct ble_hs_periodic_sync *ble_hs_periodic_sync_find_by_handle(uint16_t sync_h struct ble_hs_periodic_sync *ble_hs_periodic_sync_find(const ble_addr_t *addr, uint8_t sid); struct ble_hs_periodic_sync *ble_hs_periodic_sync_first(void); +#if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES) +struct ble_hs_periodic_sync *ble_hs_periodic_sync_find_pawr_locked(void); +#endif int ble_hs_periodic_sync_init(void); void ble_hs_periodic_sync_deinit(void); #ifdef __cplusplus