use bt_hci::cmd::le::{
LeConnUpdate, LeConnectionRateRequest, LeFrameSpaceUpdate, LeReadLocalSupportedFeatures, LeReadPhy,
LeSetDataLength, LeSetPhy,
};
use bt_hci::cmd::status::ReadRssi;
use bt_hci::controller::{ControllerCmdAsync, ControllerCmdSync};
use bt_hci::param::{
AllPhys, ConnHandle, DisconnectReason, FilterDuplicates, FrameSpaceInitiator, LeConnRole, PhyKind, PhyMask,
PhyOptions, SpacingTypes, Status,
};
#[cfg(feature = "connection-params-update")]
use bt_hci::{
cmd::le::{LeRemoteConnectionParameterRequestNegativeReply, LeRemoteConnectionParameterRequestReply},
param::RemoteConnectionParamsRejectReason,
};
#[cfg(feature = "gatt")]
use embassy_sync::blocking_mutex::raw::RawMutex;
use embassy_time::Duration;
use crate::connection_manager::ConnectionManager;
#[cfg(feature = "connection-metrics")]
pub use crate::connection_manager::Metrics as ConnectionMetrics;
use crate::pdu::Pdu;
#[cfg(feature = "gatt")]
use crate::prelude::{AttributeServer, GattConnection};
#[cfg(feature = "security")]
use crate::security_manager::{BondInformation, PassKey};
use crate::types::l2cap::ConnParamUpdateRes;
use crate::{bt_hci_duration, Address, BleHostError, Error, Identity, PacketPool, Stack};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum SecurityLevel {
NoEncryption,
Encrypted,
EncryptedAuthenticated,
}
impl SecurityLevel {
pub fn encrypted(&self) -> bool {
!matches!(self, SecurityLevel::NoEncryption)
}
pub fn authenticated(&self) -> bool {
matches!(self, SecurityLevel::EncryptedAuthenticated)
}
}
pub struct ConnectConfig<'d> {
pub scan_config: ScanConfig<'d>,
pub connect_params: RequestedConnParams,
}
pub struct ScanConfig<'d> {
pub active: bool,
pub filter_accept_list: &'d [Address],
pub phys: PhySet,
pub interval: Duration,
pub window: Duration,
pub timeout: Duration,
pub filter_duplicates: FilterDuplicates,
}
impl Default for ScanConfig<'_> {
fn default() -> Self {
Self {
active: true,
filter_accept_list: &[],
phys: PhySet::M1,
interval: Duration::from_secs(1),
window: Duration::from_secs(1),
timeout: Duration::from_secs(0),
filter_duplicates: FilterDuplicates::Disabled,
}
}
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Eq, PartialEq, Copy, Clone)]
#[repr(u8)]
pub enum PhySet {
M1 = 1,
M2 = 2,
M1M2 = 3,
Coded = 4,
M1Coded = 5,
M2Coded = 6,
M1M2Coded = 7,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct RequestedConnParams {
pub min_connection_interval: Duration,
pub max_connection_interval: Duration,
pub max_latency: u16,
pub min_event_length: Duration,
pub max_event_length: Duration,
pub supervision_timeout: Duration,
}
impl RequestedConnParams {
pub fn is_valid(&self) -> bool {
self.min_connection_interval <= self.max_connection_interval
&& self.min_connection_interval >= Duration::from_micros(7_500)
&& self.max_connection_interval <= Duration::from_secs(4)
&& self.max_latency < 500
&& self.min_event_length <= self.max_event_length
&& self.supervision_timeout >= Duration::from_millis(100)
&& self.supervision_timeout <= Duration::from_millis(32_000)
&& self.supervision_timeout.as_micros()
> 2 * u64::from(self.max_latency + 1) * self.max_connection_interval.as_micros()
}
}
#[derive(Default, Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ConnParams {
pub conn_interval: Duration,
pub peripheral_latency: u16,
pub supervision_timeout: Duration,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ConnectRateParams {
pub min_connection_interval: Duration,
pub max_connection_interval: Duration,
pub subrate_min: u16,
pub subrate_max: u16,
pub max_latency: u16,
pub continuation_number: u16,
pub supervision_timeout: Duration,
pub min_ce_length: Duration,
pub max_ce_length: Duration,
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ConnectionEvent {
Disconnected {
reason: Status,
},
PhyUpdated {
tx_phy: PhyKind,
rx_phy: PhyKind,
},
ConnectionParamsUpdated {
conn_interval: Duration,
peripheral_latency: u16,
supervision_timeout: Duration,
},
DataLengthUpdated {
max_tx_octets: u16,
max_tx_time: u16,
max_rx_octets: u16,
max_rx_time: u16,
},
FrameSpaceUpdated {
frame_space: Duration,
initiator: FrameSpaceInitiator,
phys: PhyMask,
spacing_types: SpacingTypes,
},
ConnectionRateChanged {
conn_interval: Duration,
subrate_factor: u16,
peripheral_latency: u16,
continuation_number: u16,
supervision_timeout: Duration,
},
RequestConnectionParams(ConnectionParamsRequest),
#[cfg(feature = "security")]
PassKeyDisplay(PassKey),
#[cfg(feature = "security")]
PassKeyConfirm(PassKey),
#[cfg(feature = "security")]
PassKeyInput,
#[cfg(feature = "security")]
PairingComplete {
security_level: SecurityLevel,
bond: Option<BondInformation>,
},
#[cfg(feature = "security")]
PairingFailed(Error),
#[cfg(feature = "security")]
BondLost,
#[cfg(feature = "security")]
Encrypted {
security_level: SecurityLevel,
bond: Option<BondInformation>,
},
#[cfg(feature = "security")]
OobRequest,
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ConnectionParamsRequest {
params: RequestedConnParams,
handle: ConnHandle,
responded: bool,
#[cfg(feature = "connection-params-update")]
l2cap: bool,
}
impl ConnectionParamsRequest {
pub(crate) fn new(
params: RequestedConnParams,
handle: ConnHandle,
#[cfg(feature = "connection-params-update")] l2cap: bool,
) -> Self {
Self {
params,
handle,
responded: false,
#[cfg(feature = "connection-params-update")]
l2cap,
}
}
pub fn params(&self) -> &RequestedConnParams {
&self.params
}
}
#[cfg(not(feature = "connection-params-update"))]
impl ConnectionParamsRequest {
pub async fn accept<C, P: PacketPool>(
mut self,
params: Option<&RequestedConnParams>,
stack: &Stack<'_, C, P>,
) -> Result<(), BleHostError<C::Error>>
where
C: crate::Controller,
{
self.responded = true;
let params = params.unwrap_or(&self.params);
if !params.is_valid() {
return self.reject(stack).await;
}
match stack.host.async_command(into_le_conn_update(self.handle, params)).await {
Ok(()) => {
let param = ConnParamUpdateRes { result: 0 };
stack.host.send_conn_param_update_res(self.handle, ¶m).await
}
Err(BleHostError::BleHost(crate::Error::Hci(bt_hci::param::Error::UNKNOWN_CONN_IDENTIFIER))) => {
Err(crate::Error::Disconnected.into())
}
Err(e) => {
info!("Connection parameters request procedure failed");
if let Err(e) = self.reject(stack).await {
warn!("Failed to reject ConnParamRequest after failure");
}
Err(e)
}
}
}
pub async fn reject<C, P: PacketPool>(mut self, stack: &Stack<'_, C, P>) -> Result<(), BleHostError<C::Error>>
where
C: crate::Controller,
{
self.responded = true;
let param = ConnParamUpdateRes { result: 1 };
stack.host.send_conn_param_update_res(self.handle, ¶m).await
}
}
#[cfg(feature = "connection-params-update")]
impl ConnectionParamsRequest {
pub async fn accept<C, P: PacketPool>(
mut self,
params: Option<&RequestedConnParams>,
stack: &Stack<'_, C, P>,
) -> Result<(), BleHostError<C::Error>>
where
C: crate::Controller
+ ControllerCmdAsync<LeRemoteConnectionParameterRequestReply>
+ ControllerCmdAsync<LeRemoteConnectionParameterRequestNegativeReply>,
{
self.responded = true;
let params = params.unwrap_or(&self.params);
if !params.is_valid() {
return self.reject(stack).await;
}
match stack.host.async_command(into_le_conn_update(self.handle, params)).await {
Ok(()) => {
if self.l2cap {
let param = ConnParamUpdateRes { result: 0 };
stack.host.send_conn_param_update_res(self.handle, ¶m).await
} else {
let interval_min: bt_hci::param::Duration<1_250> = bt_hci_duration(params.min_connection_interval);
let interval_max: bt_hci::param::Duration<1_250> = bt_hci_duration(params.max_connection_interval);
let timeout: bt_hci::param::Duration<10_000> = bt_hci_duration(params.supervision_timeout);
stack
.host
.async_command(LeRemoteConnectionParameterRequestReply::new(
self.handle,
interval_min,
interval_max,
params.max_latency,
timeout,
bt_hci_duration(params.min_event_length),
bt_hci_duration(params.max_event_length),
))
.await
}
}
Err(BleHostError::BleHost(crate::Error::Hci(bt_hci::param::Error::UNKNOWN_CONN_IDENTIFIER))) => {
Err(crate::Error::Disconnected.into())
}
Err(e) => {
info!("Connection parameters request procedure failed");
if let Err(e) = self.reject(stack).await {
warn!("Failed to reject ConnParamRequest after failure");
}
Err(e)
}
}
}
pub async fn reject<C, P: PacketPool>(mut self, stack: &Stack<'_, C, P>) -> Result<(), BleHostError<C::Error>>
where
C: crate::Controller + ControllerCmdAsync<LeRemoteConnectionParameterRequestNegativeReply>,
{
self.responded = true;
if self.l2cap {
let param = ConnParamUpdateRes { result: 1 };
stack.host.send_conn_param_update_res(self.handle, ¶m).await
} else {
stack
.host
.async_command(LeRemoteConnectionParameterRequestNegativeReply::new(
self.handle,
RemoteConnectionParamsRejectReason::UnacceptableConnParameters,
))
.await
}
}
}
impl Drop for ConnectionParamsRequest {
fn drop(&mut self) {
if !self.responded {
error!("ConnParamRequest dropped without being acccepted/rejected");
}
}
}
impl Default for RequestedConnParams {
fn default() -> Self {
Self {
min_connection_interval: Duration::from_millis(80),
max_connection_interval: Duration::from_millis(80),
max_latency: 0,
min_event_length: Duration::from_secs(0),
max_event_length: Duration::from_secs(0),
supervision_timeout: Duration::from_secs(8),
}
}
}
impl ConnParams {
pub(crate) const fn new() -> Self {
Self {
conn_interval: Duration::from_ticks(0),
peripheral_latency: 0,
supervision_timeout: Duration::from_ticks(0),
}
}
}
pub struct Connection<'stack, P: PacketPool> {
index: u8,
manager: &'stack ConnectionManager<'stack, P>,
}
impl<P: PacketPool> Clone for Connection<'_, P> {
fn clone(&self) -> Self {
self.manager.inc_ref(self.index);
Connection::new(self.index, self.manager)
}
}
impl<P: PacketPool> Drop for Connection<'_, P> {
fn drop(&mut self) {
self.manager.dec_ref(self.index);
}
}
impl<'stack, P: PacketPool> Connection<'stack, P> {
pub(crate) fn new(index: u8, manager: &'stack ConnectionManager<'stack, P>) -> Self {
Self { index, manager }
}
pub(crate) fn set_att_mtu(&self, mtu: u16) {
self.manager.set_att_mtu(self.index, mtu);
}
pub(crate) fn get_att_mtu(&self) -> u16 {
self.manager.get_att_mtu(self.index)
}
pub(crate) fn set_l2cap_listening(&self, listening: bool) {
self.manager.set_l2cap_listening(self.index, listening);
}
pub(crate) async fn send(&self, pdu: Pdu<P::Packet>) {
self.manager.send(self.index, pdu).await
}
pub(crate) fn try_send(&self, pdu: Pdu<P::Packet>) -> Result<(), Error> {
self.manager.try_send(self.index, pdu)
}
pub(crate) async fn post_event(&self, event: ConnectionEvent) {
self.manager.post_event(self.index, event).await
}
pub async fn next(&self) -> ConnectionEvent {
self.manager.next(self.index).await
}
#[cfg(feature = "gatt")]
pub(crate) async fn next_gatt(&self) -> Pdu<P::Packet> {
self.manager.next_gatt(self.index).await
}
#[cfg(feature = "gatt")]
pub(crate) async fn next_gatt_client(&self) -> Option<Pdu<P::Packet>> {
self.manager.next_gatt_client(self.index).await
}
#[cfg(feature = "gatt")]
pub(crate) async fn acquire_indication_slot(&self) -> Result<(), Error> {
self.manager.acquire_indication_slot(self.index).await
}
#[cfg(feature = "gatt")]
pub(crate) fn release_indication_slot(&self) {
self.manager.release_indication_slot(self.index)
}
#[cfg(feature = "gatt")]
pub(crate) async fn wait_indication_confirmation(&self) -> Result<(), Error> {
self.manager.wait_indication_confirmation(self.index).await
}
pub fn is_connected(&self) -> bool {
self.manager.is_connected(self.index)
}
pub fn handle(&self) -> ConnHandle {
self.manager.handle(self.index)
}
pub fn att_mtu(&self) -> u16 {
self.get_att_mtu()
}
pub fn role(&self) -> LeConnRole {
self.manager.role(self.index)
}
pub fn peer_address(&self) -> Address {
self.manager.peer_address(self.index)
}
pub fn peer_identity(&self) -> Identity {
self.manager.peer_identity(self.index)
}
pub fn params(&self) -> ConnParams {
self.manager.params(self.index)
}
pub fn request_security(&self) -> Result<(), Error> {
self.manager.request_security(self.index, true)
}
#[cfg(feature = "security")]
pub fn is_bonded_peer(&self) -> bool {
self.manager.is_bonded_peer(self.index)
}
#[cfg(feature = "security")]
pub(crate) async fn try_enable_encryption(&self) -> Result<(), Error> {
self.manager.try_enable_encryption(self.index).await
}
pub fn security_level(&self) -> Result<SecurityLevel, Error> {
self.manager.get_security_level(self.index)
}
#[cfg(feature = "legacy-pairing")]
pub fn encryption_key_len(&self) -> Result<u8, Error> {
self.manager.get_encryption_key_len(self.index)
}
pub fn bondable(&self) -> Result<bool, Error> {
self.manager.get_bondable(self.index)
}
pub fn set_bondable(&self, bondable: bool) -> Result<(), Error> {
self.manager.set_bondable(self.index, bondable)
}
pub fn pass_key_confirm(&self) -> Result<(), Error> {
self.manager.pass_key_confirm(self.index, true)
}
pub fn pass_key_cancel(&self) -> Result<(), Error> {
self.manager.pass_key_confirm(self.index, false)
}
pub fn pass_key_input(&self, pass_key: u32) -> Result<(), Error> {
self.manager.pass_key_input(self.index, pass_key)
}
#[cfg(feature = "security")]
pub fn set_oob_available(&self, available: bool) -> Result<(), Error> {
self.manager.set_oob_available(self.index, available)
}
#[cfg(feature = "security")]
pub fn provide_oob_data(
&self,
local_oob: crate::security_manager::OobData,
peer_oob: crate::security_manager::OobData,
) -> Result<(), Error> {
self.manager.provide_oob_data(self.index, local_oob, peer_oob)
}
pub fn disconnect(&self) {
self.manager
.request_disconnect(self.index, DisconnectReason::RemoteUserTerminatedConn);
}
#[cfg(feature = "connection-metrics")]
pub fn metrics<F: FnOnce(&ConnectionMetrics) -> R, R>(&self, f: F) -> R {
self.manager.metrics(self.index, f)
}
#[cfg(feature = "att-queued-writes")]
pub(crate) fn prepare_write(&self, handle: u16, offset: u16, value: &[u8]) -> Result<(), crate::att::AttErrorCode> {
self.manager.prepare_write(self.index, handle, offset, value)
}
#[cfg(feature = "att-queued-writes")]
pub(crate) fn with_prepare_write<F, R>(&self, f: F) -> R
where
F: FnOnce(&crate::connection_manager::PrepareWriteState) -> R,
{
self.manager.with_prepare_write(self.index, f)
}
#[cfg(feature = "att-queued-writes")]
pub(crate) fn clear_prepare_write(&self) {
self.manager.clear_prepare_write(self.index)
}
pub async fn rssi<T>(&self, stack: &Stack<'_, T, P>) -> Result<i8, BleHostError<T::Error>>
where
T: ControllerCmdSync<ReadRssi>,
{
let handle = self.handle();
let ret = stack.host.command(ReadRssi::new(handle)).await?;
Ok(ret.rssi)
}
pub async fn set_phy<T>(&self, stack: &Stack<'_, T, P>, phy: PhyKind) -> Result<(), BleHostError<T::Error>>
where
T: ControllerCmdAsync<LeSetPhy>,
{
let all_phys = AllPhys::new()
.set_has_no_rx_phy_preference(false)
.set_has_no_tx_phy_preference(false);
let mut mask = PhyMask::new()
.set_le_coded_phy(false)
.set_le_1m_phy(false)
.set_le_2m_phy(false);
let mut options = PhyOptions::default();
match phy {
PhyKind::Le2M => {
mask = mask.set_le_2m_phy(true);
}
PhyKind::Le1M => {
mask = mask.set_le_1m_phy(true);
}
PhyKind::LeCoded => {
mask = mask.set_le_coded_phy(true);
options = PhyOptions::S8CodingPreferred;
}
PhyKind::LeCodedS2 => {
mask = mask.set_le_coded_phy(true);
options = PhyOptions::S2CodingPreferred;
}
}
stack
.host
.async_command(LeSetPhy::new(self.handle(), all_phys, mask, mask, options))
.await?;
Ok(())
}
pub async fn read_phy<T>(&self, stack: &Stack<'_, T, P>) -> Result<(PhyKind, PhyKind), BleHostError<T::Error>>
where
T: ControllerCmdSync<LeReadPhy>,
{
let res = stack.host.command(LeReadPhy::new(self.handle())).await?;
Ok((res.tx_phy, res.rx_phy))
}
pub async fn update_data_length<T>(
&self,
stack: &Stack<'_, T, P>,
length: u16,
time_us: u16,
) -> Result<(), BleHostError<T::Error>>
where
T: ControllerCmdSync<LeSetDataLength> + ControllerCmdSync<LeReadLocalSupportedFeatures>,
{
let handle = self.handle();
let features = stack.host.command(LeReadLocalSupportedFeatures::new()).await?;
if length <= 27 || features.supports_le_data_packet_length_extension() {
match stack.host.command(LeSetDataLength::new(handle, length, time_us)).await {
Ok(_) => Ok(()),
Err(BleHostError::BleHost(crate::Error::Hci(bt_hci::param::Error::UNKNOWN_CONN_IDENTIFIER))) => {
Err(crate::Error::Disconnected.into())
}
Err(e) => Err(e),
}
} else {
Err(BleHostError::BleHost(Error::InvalidValue))
}
}
pub async fn update_connection_params<T>(
&self,
stack: &Stack<'_, T, P>,
params: &RequestedConnParams,
) -> Result<(), BleHostError<T::Error>>
where
T: ControllerCmdAsync<LeConnUpdate> + ControllerCmdSync<LeReadLocalSupportedFeatures>,
{
let handle = self.handle();
let features = stack.host.command(LeReadLocalSupportedFeatures::new()).await?;
if features.supports_conn_parameters_request_procedure() || self.role() == LeConnRole::Central {
match stack.host.async_command(into_le_conn_update(handle, params)).await {
Ok(_) => return Ok(()),
Err(BleHostError::BleHost(crate::Error::Hci(bt_hci::param::Error::UNKNOWN_CONN_IDENTIFIER))) => {
return Err(crate::Error::Disconnected.into());
}
Err(BleHostError::BleHost(crate::Error::Hci(bt_hci::param::Error::UNSUPPORTED_REMOTE_FEATURE))) => {
}
Err(e) => return Err(e),
}
}
if self.role() == LeConnRole::Peripheral || cfg!(feature = "connection-params-update") {
use crate::types::l2cap::ConnParamUpdateReq;
info!(
"Connection parameters request procedure not supported, use l2cap connection parameter update req instead"
);
let interval_min: bt_hci::param::Duration<1_250> = bt_hci_duration(params.min_connection_interval);
let interval_max: bt_hci::param::Duration<1_250> = bt_hci_duration(params.max_connection_interval);
let timeout: bt_hci::param::Duration<10_000> = bt_hci_duration(params.supervision_timeout);
let param = ConnParamUpdateReq {
interval_min: interval_min.as_u16(),
interval_max: interval_max.as_u16(),
latency: params.max_latency,
timeout: timeout.as_u16(),
};
stack.host.send_conn_param_update_req(handle, ¶m).await?;
}
Ok(())
}
pub async fn update_frame_space<T>(
&self,
stack: &Stack<'_, T, P>,
frame_space_min: Duration,
frame_space_max: Duration,
phys: PhyMask,
spacing_types: SpacingTypes,
) -> Result<(), BleHostError<T::Error>>
where
T: ControllerCmdSync<LeFrameSpaceUpdate>,
{
let handle = self.handle();
let frame_space_min_dur = bt_hci_duration(frame_space_min);
let frame_space_max_dur = bt_hci_duration(frame_space_max);
match stack
.host
.command(LeFrameSpaceUpdate::new(
handle,
frame_space_min_dur,
frame_space_max_dur,
phys,
spacing_types,
))
.await
{
Ok(_) => Ok(()),
Err(BleHostError::BleHost(crate::Error::Hci(bt_hci::param::Error::UNKNOWN_CONN_IDENTIFIER))) => {
Err(crate::Error::Disconnected.into())
}
Err(e) => Err(e),
}
}
pub async fn request_connection_rate<T>(
&self,
stack: &Stack<'_, T, P>,
conn_rate_params: &ConnectRateParams,
) -> Result<(), BleHostError<T::Error>>
where
T: ControllerCmdSync<LeConnectionRateRequest>,
{
let handle = self.handle();
let min_interval = bt_hci_duration(conn_rate_params.min_connection_interval);
let max_interval = bt_hci_duration(conn_rate_params.max_connection_interval);
let timeout = bt_hci_duration(conn_rate_params.supervision_timeout);
let min_ce = bt_hci_duration(conn_rate_params.min_ce_length);
let max_ce = bt_hci_duration(conn_rate_params.max_ce_length);
match stack
.host
.command(LeConnectionRateRequest::new(
handle,
min_interval,
max_interval,
conn_rate_params.subrate_min,
conn_rate_params.subrate_max,
conn_rate_params.max_latency,
conn_rate_params.continuation_number,
timeout,
min_ce,
max_ce,
))
.await
{
Ok(_) => Ok(()),
Err(BleHostError::BleHost(crate::Error::Hci(bt_hci::param::Error::UNKNOWN_CONN_IDENTIFIER))) => {
Err(crate::Error::Disconnected.into())
}
Err(e) => Err(e),
}
}
#[cfg(feature = "gatt")]
pub fn with_attribute_server<'values, 'server, M: RawMutex, const ATT_MAX: usize, const CONN_MAX: usize>(
self,
server: &'server AttributeServer<'values, M, P, ATT_MAX, CONN_MAX>,
) -> Result<GattConnection<'stack, 'server, P>, Error> {
GattConnection::try_new(self, server)
}
}
fn into_le_conn_update(handle: ConnHandle, params: &RequestedConnParams) -> LeConnUpdate {
LeConnUpdate::new(
handle,
bt_hci_duration(params.min_connection_interval),
bt_hci_duration(params.max_connection_interval),
params.max_latency,
bt_hci_duration(params.supervision_timeout),
bt_hci_duration(params.min_event_length),
bt_hci_duration(params.max_event_length),
)
}