Skip to content

Commit

Permalink
Merge pull request #248 from alexmoon/disconnect-reasons
Browse files Browse the repository at this point in the history
Add a `disconnect_reason` method to `Connection`
  • Loading branch information
alexmoon committed Mar 26, 2024
2 parents d86eec8 + 73d5ae3 commit 30a5084
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 20 deletions.
2 changes: 1 addition & 1 deletion nrf-softdevice/src/ble/central.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ where
Err(_) => {
raw::sd_ble_gap_disconnect(
conn_handle,
raw::BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION as _,
raw::BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_LOW_RESOURCES as _,
);
Err(ConnectError::NoFreeConn)
}
Expand Down
94 changes: 76 additions & 18 deletions nrf-softdevice/src/ble/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ use core::iter::FusedIterator;

use raw::ble_gap_conn_params_t;

use super::PhySet;
use super::{HciStatus, PhySet};
#[cfg(feature = "ble-central")]
use crate::ble::gap::default_security_params;
#[cfg(feature = "ble-sec")]
use crate::ble::security::SecurityHandler;
use crate::ble::types::{Address, AddressType, Role, SecurityMode};
use crate::util::get_union_field;
use crate::{raw, RawError};

#[cfg(any(feature = "s113", feature = "s132", feature = "s140"))]
Expand All @@ -22,6 +23,40 @@ pub(crate) struct OutOfConnsError;
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct DisconnectedError;

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub(crate) enum ConnHandleState {
Disconnected(HciStatus),
Connected(u16),
}

impl ConnHandleState {
const fn to_result(self) -> Result<u16, DisconnectedError> {
match self {
ConnHandleState::Disconnected(_) => Err(DisconnectedError),
ConnHandleState::Connected(handle) => Ok(handle),
}
}

const fn handle(self) -> Option<u16> {
match self {
ConnHandleState::Disconnected(_) => None,
ConnHandleState::Connected(handle) => Some(handle),
}
}

const fn is_connected(&self) -> bool {
matches!(self, ConnHandleState::Connected(_))
}

const fn disconnect_reason(self) -> Option<HciStatus> {
match self {
ConnHandleState::Disconnected(reason) => Some(reason),
ConnHandleState::Connected(_) => None,
}
}
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum SetConnParamsError {
Expand Down Expand Up @@ -199,7 +234,7 @@ pub(crate) struct ConnectionState {
// However, disconnection is not complete until the event GAP_DISCONNECTED.
// so there's a small gap of time where the ConnectionState is not "free" even if refcount=0.
pub refcount: u8,
pub conn_handle: Option<u16>,
pub conn_handle: ConnHandleState,

pub disconnecting: bool,
pub role: Role,
Expand All @@ -226,7 +261,7 @@ impl ConnectionState {
// can go into .bss instead of .data, which saves flash space.
Self {
refcount: 0,
conn_handle: None,
conn_handle: ConnHandleState::Disconnected(HciStatus::SUCCESS),
#[cfg(feature = "ble-central")]
role: Role::Central,
#[cfg(not(feature = "ble-central"))]
Expand All @@ -251,26 +286,32 @@ impl ConnectionState {
}
}
pub(crate) fn check_connected(&mut self) -> Result<u16, DisconnectedError> {
self.conn_handle.ok_or(DisconnectedError)
self.conn_handle.to_result()
}

pub(crate) fn disconnect(&mut self) -> Result<(), DisconnectedError> {
self.disconnect_with_reason(HciStatus::REMOTE_USER_TERMINATED_CONNECTION)
}

pub(crate) fn disconnect_with_reason(&mut self, reason: HciStatus) -> Result<(), DisconnectedError> {
let conn_handle = self.check_connected()?;

if self.disconnecting {
return Ok(());
}

let ret =
unsafe { raw::sd_ble_gap_disconnect(conn_handle, raw::BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION as u8) };
let ret = unsafe { raw::sd_ble_gap_disconnect(conn_handle, reason.into()) };
unwrap!(RawError::convert(ret), "sd_ble_gap_disconnect");

self.disconnecting = true;
Ok(())
}

pub(crate) fn on_disconnected(&mut self, _ble_evt: *const raw::ble_evt_t) {
let conn_handle = unwrap!(self.conn_handle, "bug: on_disconnected when already disconnected");
pub(crate) fn on_disconnected(&mut self, ble_evt: *const raw::ble_evt_t) {
let conn_handle = unwrap!(
self.conn_handle.handle(),
"bug: on_disconnected when already disconnected"
);

let ibh = index_by_handle(conn_handle);
let _index = unwrap!(ibh.get(), "bug: conn_handle has no index");
Expand All @@ -283,17 +324,26 @@ impl ConnectionState {

ibh.set(None);

self.conn_handle = None;
let reason = unsafe {
assert_eq!(
u32::from((*ble_evt).header.evt_id),
raw::BLE_GAP_EVTS_BLE_GAP_EVT_DISCONNECTED,
"bug: on_disconnected called with non-disconnect event"
);
let gap_evt = get_union_field(ble_evt, &(*ble_evt).evt.gap_evt);
HciStatus::new(gap_evt.params.disconnected.reason)
};
self.conn_handle = ConnHandleState::Disconnected(reason);

// Signal possible in-progess operations that the connection has disconnected.
#[cfg(feature = "ble-gatt-client")]
crate::ble::gatt_client::portal(conn_handle).call(_ble_evt);
crate::ble::gatt_client::portal(conn_handle).call(ble_evt);
#[cfg(feature = "ble-gatt-client")]
crate::ble::gatt_client::hvx_portal(conn_handle).call(_ble_evt);
crate::ble::gatt_client::hvx_portal(conn_handle).call(ble_evt);
#[cfg(feature = "ble-gatt-server")]
crate::ble::gatt_server::portal(conn_handle).call(_ble_evt);
crate::ble::gatt_server::portal(conn_handle).call(ble_evt);
#[cfg(feature = "ble-l2cap")]
crate::ble::l2cap::portal(conn_handle).call(_ble_evt);
crate::ble::l2cap::portal(conn_handle).call(ble_evt);

trace!("conn {:?}: disconnected", _index);
}
Expand Down Expand Up @@ -346,7 +396,7 @@ impl Drop for Connection {
);

if state.refcount == 0 {
if state.conn_handle.is_some() {
if state.conn_handle.is_connected() {
trace!("conn {:?}: dropped, disconnecting", self.index);
// We still leave conn_handle set, because the connection is
// not really disconnected until we get GAP_DISCONNECTED event.
Expand Down Expand Up @@ -382,8 +432,16 @@ impl Connection {
self.with_state(|state| state.disconnect())
}

pub fn disconnect_with_reason(&self, reason: HciStatus) -> Result<(), DisconnectedError> {
self.with_state(|state| state.disconnect_with_reason(reason))
}

pub fn disconnect_reason(&self) -> Option<HciStatus> {
self.with_state(|state| state.conn_handle.disconnect_reason())
}

pub fn handle(&self) -> Option<u16> {
self.with_state(|state| state.conn_handle)
self.with_state(|state| state.conn_handle.handle())
}

pub fn from_handle(conn_handle: u16) -> Option<Connection> {
Expand All @@ -405,7 +463,7 @@ impl Connection {
// Initialize
*state = ConnectionState {
refcount: 1,
conn_handle: Some(conn_handle),
conn_handle: ConnHandleState::Connected(conn_handle),
role,
peer_address,
security_mode: SecurityMode::Open,
Expand Down Expand Up @@ -727,7 +785,7 @@ impl Iterator for ConnectionIter {
unsafe {
for (i, s) in STATES[n..].iter().enumerate() {
let state = &mut *s.get();
if state.conn_handle.is_some() {
if state.conn_handle.is_connected() {
let index = (n + i) as u8;
state.refcount =
unwrap!(state.refcount.checked_add(1), "Too many references to same connection");
Expand Down Expand Up @@ -769,7 +827,7 @@ fn allocate_index<T>(f: impl FnOnce(u8, &mut ConnectionState) -> T) -> Result<T,
unsafe {
for (i, s) in STATES.iter().enumerate() {
let state = &mut *s.get();
if state.refcount == 0 && state.conn_handle.is_none() {
if state.refcount == 0 && !state.conn_handle.is_connected() {
return Ok(f(i as u8, state));
}
}
Expand Down
2 changes: 1 addition & 1 deletion nrf-softdevice/src/ble/peripheral.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ where
Err(_) => {
raw::sd_ble_gap_disconnect(
conn_handle,
raw::BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION as _,
raw::BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_LOW_RESOURCES as _,
);
Err(AdvertiseError::NoFreeConn)
}
Expand Down
119 changes: 119 additions & 0 deletions nrf-softdevice/src/ble/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -611,3 +611,122 @@ error_codes! {
/// ATT Common Profile and Service Error: Out Of Range.
(ATTERR_CPS_OUT_OF_RANGE, BLE_GATT_STATUS_ATTERR_CPS_OUT_OF_RANGE, "Out of Range");
}

#[derive(PartialEq, Eq, Clone, Copy)]
pub struct HciStatus(u8);

impl HciStatus {
pub const fn new(status: u8) -> Self {
Self(status)
}
}

impl From<u8> for HciStatus {
fn from(value: u8) -> Self {
Self(value)
}
}

impl From<HciStatus> for u8 {
fn from(value: HciStatus) -> Self {
value.0
}
}

macro_rules! hci_status_codes {
(
$(
$(#[$docs:meta])*
($konst:ident, $raw:expr, $phrase:expr);
)+
) => {
impl HciStatus {
$(
$(#[$docs])*
pub const $konst: HciStatus = HciStatus($raw as u8);
)+
}

#[cfg(feature = "defmt")]
impl defmt::Format for HciStatus {
fn format(&self, fmt: defmt::Formatter) {
match *self {
$(
Self::$konst => defmt::write!(fmt, $phrase),
)+
_ => defmt::write!(fmt, "Unknown HCI status: 0x{:02x}", self.0),
}
}
}

impl core::fmt::Debug for HciStatus {
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match *self {
$(
Self::$konst => core::write!(fmt, $phrase),
)+
_ => core::write!(fmt, "Unknown HCI status: 0x{:02x}", self.0),
}
}
}
}
}

hci_status_codes! {
/// Success
(SUCCESS, raw::BLE_HCI_STATUS_CODE_SUCCESS, "Success");
/// Unknown HCI Command
(UNKNOWN_BTLE_COMMAND, raw::BLE_HCI_STATUS_CODE_UNKNOWN_BTLE_COMMAND, "Unknown HCI Command");
/// Unknown Connection Identifier
(UNKNOWN_CONNECTION_IDENTIFIER, raw::BLE_HCI_STATUS_CODE_UNKNOWN_CONNECTION_IDENTIFIER, "Unknown Connection Identifier");
/// Authentication Failure
(AUTHENTICATION_FAILURE, raw::BLE_HCI_AUTHENTICATION_FAILURE, "Authentication Failure");
/// PIN Or Key Missing
(PIN_OR_KEY_MISSING, raw::BLE_HCI_STATUS_CODE_PIN_OR_KEY_MISSING, "PIN Or Key Missing");
/// Memory Capacity Exceeded
(MEMORY_CAPACITY_EXCEEDED, raw::BLE_HCI_MEMORY_CAPACITY_EXCEEDED, "Memory Capacity Exceeded");
/// Connection Timeout
(CONNECTION_TIMEOUT, raw::BLE_HCI_CONNECTION_TIMEOUT, "Connection Timeout");
/// Command Disallowed
(COMMAND_DISALLOWED, raw::BLE_HCI_STATUS_CODE_COMMAND_DISALLOWED, "Command Disallowed");
/// Invalid HCI Command Parameters
(INVALID_BTLE_COMMAND_PARAMETERS, raw::BLE_HCI_STATUS_CODE_INVALID_BTLE_COMMAND_PARAMETERS, "Invalid HCI Command Parameters");
/// Remote User Terminated Connection
(REMOTE_USER_TERMINATED_CONNECTION, raw::BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION, "Remote User Terminated Connection");
/// Remote Device Terminated Connection due to Low Resources
(REMOTE_DEV_TERMINATION_DUE_TO_LOW_RESOURCES, raw::BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_LOW_RESOURCES, "Remote Device Terminated Connection due to Low Resources");
/// Remote Device Terminated Connection due to Power Off
(REMOTE_DEV_TERMINATION_DUE_TO_POWER_OFF, raw::BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_POWER_OFF, "Remote Device Terminated Connection due to Power Off");
/// Connection Terminated by Local Host
(LOCAL_HOST_TERMINATED_CONNECTION, raw::BLE_HCI_LOCAL_HOST_TERMINATED_CONNECTION, "Connection Terminated by Local Host");
/// Unsupported Remote Feature
(UNSUPPORTED_REMOTE_FEATURE, raw::BLE_HCI_UNSUPPORTED_REMOTE_FEATURE, "Unsupported Remote Feature");
/// Invalid LMP Parameters
(INVALID_LMP_PARAMETERS, raw::BLE_HCI_STATUS_CODE_INVALID_LMP_PARAMETERS, "Invalid LMP Parameters");
/// Unspecified Error
(UNSPECIFIED_ERROR, raw::BLE_HCI_STATUS_CODE_UNSPECIFIED_ERROR, "Unspecified Error");
/// LMP Response Timeout
(LMP_RESPONSE_TIMEOUT, raw::BLE_HCI_STATUS_CODE_LMP_RESPONSE_TIMEOUT, "LMP Response Timeout");
/// LMP Error Transaction Collision
(LMP_ERROR_TRANSACTION_COLLISION, raw::BLE_HCI_STATUS_CODE_LMP_ERROR_TRANSACTION_COLLISION, "LMP Error Transaction Collision");
/// LMP PDU Not Allowed
(LMP_PDU_NOT_ALLOWED, raw::BLE_HCI_STATUS_CODE_LMP_PDU_NOT_ALLOWED, "LMP PDU Not Allowed");
/// Instant Passed
(INSTANT_PASSED, raw::BLE_HCI_INSTANT_PASSED, "Instant Passed");
/// Pairing With Unit Key Not Supported
(PAIRING_WITH_UNIT_KEY_UNSUPPORTED, raw::BLE_HCI_PAIRING_WITH_UNIT_KEY_UNSUPPORTED, "Pairing With Unit Key Not Supported");
/// Different Transaction Collision
(DIFFERENT_TRANSACTION_COLLISION, raw::BLE_HCI_DIFFERENT_TRANSACTION_COLLISION, "Different Transaction Collision");
/// Parameter Out Of Mandatory Range
(PARAMETER_OUT_OF_MANDATORY_RANGE, raw::BLE_HCI_PARAMETER_OUT_OF_MANDATORY_RANGE, "Parameter Out Of Mandatory Range");
/// Controller Busy
(CONTROLLER_BUSY, raw::BLE_HCI_CONTROLLER_BUSY, "Controller Busy");
/// Unacceptable Connection Parameters
(CONN_INTERVAL_UNACCEPTABLE, raw::BLE_HCI_CONN_INTERVAL_UNACCEPTABLE, "Unacceptable Connection Parameters");
/// Advertising Timeout
(DIRECTED_ADVERTISER_TIMEOUT, raw::BLE_HCI_DIRECTED_ADVERTISER_TIMEOUT, "Advertising Timeout");
/// Connection Terminated due to MIC Failure
(CONN_TERMINATED_DUE_TO_MIC_FAILURE, raw::BLE_HCI_CONN_TERMINATED_DUE_TO_MIC_FAILURE, "Connection Terminated due to MIC Failure");
/// Connection Failed to be Established
(CONN_FAILED_TO_BE_ESTABLISHED, raw::BLE_HCI_CONN_FAILED_TO_BE_ESTABLISHED, "Connection Failed to be Established");
}

0 comments on commit 30a5084

Please sign in to comment.