use bt_hci::cmd::le::{LeConnUpdate, LeReadLocalSupportedFeatures, LeReadPhy, LeSetDataLength, LeSetPhy};
use bt_hci::cmd::status::ReadRssi;
use bt_hci::controller::{ControllerCmdAsync, ControllerCmdSync};
use bt_hci::param::{
AddrKind, AllPhys, BdAddr, ConnHandle, DisconnectReason, LeConnRole, PhyKind, PhyMask, PhyOptions, 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, BleHostError, Error, Identity, PacketPool, Stack};
#[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 [(AddrKind, &'d BdAddr)],
pub phys: PhySet,
pub interval: Duration,
pub window: Duration,
pub timeout: Duration,
}
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),
}
}
}
#[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)]
#[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,
},
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),
}
#[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) 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) -> Pdu<P::Packet> {
self.manager.next_gatt_client(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_addr_kind(&self) -> AddrKind {
self.manager.peer_addr_kind(self.index)
}
pub fn peer_address(&self) -> BdAddr {
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)
}
pub fn security_level(&self) -> Result<SecurityLevel, Error> {
self.manager.get_security_level(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)
}
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)
}
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(())
}
#[cfg(feature = "gatt")]
pub fn with_attribute_server<
'values,
'server,
M: RawMutex,
const ATT_MAX: usize,
const CCCD_MAX: usize,
const CONN_MAX: usize,
>(
self,
server: &'server AttributeServer<'values, M, P, ATT_MAX, CCCD_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),
)
}