#[cfg(feature = "_ble")]
use bt_hci::{cmd::le::LeSetPhy, controller::ControllerCmdAsync};
use embassy_futures::select::{Either3, select3};
use embassy_sync::signal::Signal;
use trouble_host::prelude::*;
use trouble_host::{BondInformation, LongTermKey};
#[cfg(feature = "storage")]
use {crate::channel::FLASH_CHANNEL, crate::storage::FLASH_OPERATION_FINISHED};
use super::ble_server::CCCD_TABLE_SIZE;
use crate::NUM_BLE_PROFILE;
use crate::channel::BLE_PROFILE_CHANNEL;
use crate::state::{current_profile, set_ble_profile};
pub(crate) static UPDATED_PROFILE: Signal<crate::RawMutex, ProfileInfo> = Signal::new();
pub(crate) static UPDATED_CCCD_TABLE: Signal<crate::RawMutex, heapless::Vec<u8, CCCD_TABLE_SIZE>> = Signal::new();
#[cfg(feature = "dongle")]
pub(crate) const DONGLE_PROFILE: u8 = NUM_BLE_PROFILE as u8;
pub(crate) const BOND_SLOTS: usize = NUM_BLE_PROFILE + cfg!(feature = "dongle") as usize;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ProfileInfo {
pub(crate) slot_num: u8,
pub(crate) removed: bool,
pub(crate) info: BondInformation,
pub(crate) cccd_table: heapless::Vec<u8, CCCD_TABLE_SIZE>,
}
pub const fn varint_max<T: Sized>() -> usize {
const BITS_PER_BYTE: usize = 8;
const BITS_PER_VARINT_BYTE: usize = 7;
let bits = core::mem::size_of::<T>() * BITS_PER_BYTE;
let roundup_bits = bits + (BITS_PER_VARINT_BYTE - 1);
roundup_bits / BITS_PER_VARINT_BYTE
}
impl postcard::experimental::max_size::MaxSize for ProfileInfo {
const POSTCARD_MAX_SIZE: usize = varint_max::<Self>();
}
impl Default for ProfileInfo {
fn default() -> Self {
Self {
slot_num: 0,
removed: false,
info: BondInformation::new(
Identity {
addr: Address::default(),
irk: None,
},
LongTermKey(0),
SecurityLevel::NoEncryption,
false,
),
cccd_table: heapless::Vec::new(),
}
}
}
pub(crate) enum BleProfileAction {
Switch(u8),
Previous,
Next,
ClearBond,
ClearSlot(u8),
}
#[cfg(feature = "_ble")]
pub(crate) struct ProfileManager<
'b,
's,
C: Controller + ControllerCmdAsync<LeSetPhy>,
P: PacketPool,
const SLOTS: usize,
> where
's: 'b,
{
bonded_devices: heapless::Vec<ProfileInfo, SLOTS>,
stack: &'b Stack<'s, C, P>,
}
#[cfg(feature = "_ble")]
impl<'b, 's, C: Controller + ControllerCmdAsync<LeSetPhy>, P: PacketPool, const SLOTS: usize>
ProfileManager<'b, 's, C, P, SLOTS>
where
's: 'b,
{
pub(crate) fn new(stack: &'b Stack<'s, C, P>) -> Self {
Self {
bonded_devices: heapless::Vec::new(),
stack,
}
}
#[cfg(feature = "storage")]
pub(crate) async fn load_bonded_devices(&mut self) {
use crate::storage::{read_active_ble_profile, read_bond_info};
self.bonded_devices.clear();
for slot_num in 0..SLOTS {
if let Some(info) = read_bond_info(slot_num as u8).await
&& !info.removed
&& let Err(e) = self.bonded_devices.push(info)
{
error!("Failed to add bond info: {:?}", e);
}
}
debug!("Loaded {} bond info", self.bonded_devices.len());
let profile = if let Some(profile) = read_active_ble_profile().await {
debug!("Loaded active profile: {}", profile);
profile
} else {
debug!("Loaded default active profile",);
0
};
set_ble_profile(profile);
}
pub(crate) fn active_bond_info(&self) -> Option<ProfileInfo> {
let active_profile = current_profile();
self.bonded_devices
.iter()
.find(|bond_info| !bond_info.removed && bond_info.slot_num == active_profile)
.cloned()
}
#[cfg(feature = "dongle")]
pub(crate) fn is_bonded_dongle(&self, identity: &Identity) -> bool {
self.bonded_devices.iter().any(|bond_info| {
!bond_info.removed
&& bond_info.slot_num == DONGLE_PROFILE
&& bond_info.info.identity.match_identity(identity)
})
}
pub(crate) fn update_stack_bonds(&self) {
while let Some(identity) = self
.stack
.with_bond_information(|bonds| bonds.first().map(|b| b.identity))
{
if let Err(e) = self.stack.remove_bond_information(identity) {
debug!("Remove bond info error: {:?}", e);
break; }
}
if let Some(info) = self.active_bond_info() {
debug!("Add bond info of profile {}: {:?}", info.slot_num, info);
if let Err(e) = self.stack.add_bond_information(info.info) {
debug!("Add bond info error: {:?}", e);
}
}
}
pub(crate) async fn add_profile_info(&mut self, profile_info: ProfileInfo) {
if let Some(index) = self
.bonded_devices
.iter()
.position(|info| info.slot_num == profile_info.slot_num)
{
if self.bonded_devices[index].info == profile_info.info {
info!("Skip saving same bonding info");
return;
}
self.bonded_devices[index] = profile_info.clone();
} else {
if let Err(e) = self.bonded_devices.push(profile_info.clone()) {
error!("Failed to add bond info: {:?}", e);
}
}
self.update_stack_bonds();
#[cfg(feature = "storage")]
FLASH_CHANNEL
.send(crate::storage::FlashOperationMessage::ProfileInfo(profile_info))
.await;
}
pub(crate) async fn update_profile_cccd_table(&mut self, table: heapless::Vec<u8, CCCD_TABLE_SIZE>) {
let active_profile = current_profile();
if let Some(index) = self
.bonded_devices
.iter()
.position(|info| info.slot_num == active_profile)
{
if self.bonded_devices[index].cccd_table == table {
debug!("Skip updating same CCCD table");
return;
}
debug!("Updating profile {} CCCD table: {:?}", active_profile, table);
self.bonded_devices[index].cccd_table = table;
#[cfg(feature = "storage")]
FLASH_CHANNEL
.send(crate::storage::FlashOperationMessage::ProfileInfo(
self.bonded_devices[index].clone(),
))
.await;
} else {
error!("Failed to update profile CCCD table: profile not found");
}
}
pub(crate) async fn clear_bond(&mut self, slot_num: u8) {
info!("Clearing bonding information on profile: {}", slot_num);
for bond_info in self.bonded_devices.iter_mut() {
if bond_info.slot_num == slot_num {
bond_info.removed = true;
}
}
self.update_stack_bonds();
#[cfg(feature = "storage")]
FLASH_CHANNEL
.send(crate::storage::FlashOperationMessage::ClearSlot(slot_num))
.await;
}
pub(crate) async fn switch_profile(&mut self, profile: u8) -> bool {
let current = current_profile();
if profile == current {
return false;
}
set_ble_profile(profile);
self.update_stack_bonds();
#[cfg(feature = "storage")]
FLASH_CHANNEL
.send(crate::storage::FlashOperationMessage::ActiveBleProfile(profile))
.await;
info!("Switched to BLE profile: {}", profile);
true
}
pub(crate) async fn update_profile(&mut self) {
loop {
match select3(
BLE_PROFILE_CHANNEL.receive(),
UPDATED_PROFILE.wait(),
UPDATED_CCCD_TABLE.wait(),
)
.await
{
Either3::First(action) => {
#[cfg(feature = "storage")]
FLASH_OPERATION_FINISHED.reset();
match action {
BleProfileAction::Switch(profile) => {
if !self.switch_profile(profile).await {
continue;
}
}
BleProfileAction::Previous => {
let mut profile = current_profile();
profile = if profile == 0 {
NUM_BLE_PROFILE as u8 - 1
} else {
profile - 1
};
self.switch_profile(profile).await;
}
BleProfileAction::Next => {
let next = current_profile() + 1;
let profile = if next >= NUM_BLE_PROFILE as u8 { 0 } else { next };
self.switch_profile(profile).await;
}
BleProfileAction::ClearBond => {
self.clear_bond(current_profile()).await;
}
BleProfileAction::ClearSlot(slot) => {
self.clear_bond(slot).await;
}
}
#[cfg(feature = "storage")]
FLASH_OPERATION_FINISHED.wait().await;
info!("Update profile done");
break;
}
Either3::Second(profile_info) => {
self.add_profile_info(profile_info).await;
}
Either3::Third(table) => {
self.update_profile_cccd_table(table).await;
}
}
}
}
}