esp32-nimble 0.12.0

A wrapper for the ESP32 NimBLE Bluetooth stack.
use alloc::{ffi::CString, vec::Vec};
use core::{
    ffi::c_void,
    sync::atomic::{AtomicBool, Ordering},
};
use esp_idf_svc::sys as esp_idf_sys;
use esp_idf_sys::{EspError, esp, esp_nofail};
use once_cell::sync::Lazy;

use crate::{
    BLEAddress, BLEAddressType, BLEClient, BLEError, BLESecurity, BLEServer, ble, enums::*,
    utilities::mutex::Mutex,
};

#[cfg(not(esp_idf_bt_nimble_ext_adv))]
type BLEAdvertising = crate::BLEAdvertising;
#[cfg(esp_idf_bt_nimble_ext_adv)]
type BLEAdvertising = crate::BLEExtAdvertising;

unsafe extern "C" {
    fn ble_store_config_init();
}

#[cfg(esp32)]
unsafe extern "C" {
    fn ble_hs_pvcy_rpa_config(enable: u8) -> core::ffi::c_int;
}
#[cfg(esp32)]
const NIMBLE_HOST_DISABLE_PRIVACY: u8 = 0x00;
#[cfg(esp32)]
const NIMBLE_HOST_ENABLE_RPA: u8 = 0x01;
#[cfg(esp32)]
const NIMBLE_HOST_ENABLE_NRPA: u8 = 0x02;

static mut BLE_DEVICE: Lazy<BLEDevice> = Lazy::new(|| {
    BLEDevice::init();
    BLEDevice {
        security: BLESecurity::new(),
    }
});
pub static mut BLE_SERVER: Lazy<BLEServer> = Lazy::new(BLEServer::new);
static BLE_ADVERTISING: Lazy<Mutex<BLEAdvertising>> =
    Lazy::new(|| Mutex::new(BLEAdvertising::new()));

pub static mut OWN_ADDR_TYPE: OwnAddrType = OwnAddrType::Public;
static INITIALIZED: AtomicBool = AtomicBool::new(false);
static SYNCED: AtomicBool = AtomicBool::new(false);

pub struct BLEDevice {
    security: BLESecurity,
}

impl BLEDevice {
    pub fn init() {
        // NVS initialisation.
        unsafe {
            let initialized = INITIALIZED.load(Ordering::Acquire);
            if !initialized {
                let result = esp_idf_sys::nvs_flash_init();
                if result == esp_idf_sys::ESP_ERR_NVS_NO_FREE_PAGES
                    || result == esp_idf_sys::ESP_ERR_NVS_NEW_VERSION_FOUND
                {
                    ::log::warn!("NVS initialisation failed. Erasing NVS.");
                    esp_nofail!(esp_idf_sys::nvs_flash_erase());
                    esp_nofail!(esp_idf_sys::nvs_flash_init());
                }

                esp_idf_sys::esp_bt_controller_mem_release(
                    esp_idf_sys::esp_bt_mode_t_ESP_BT_MODE_CLASSIC_BT,
                );

                #[cfg(esp_idf_version_major = "4")]
                esp_nofail!(esp_idf_sys::esp_nimble_hci_and_controller_init());

                esp_idf_sys::nimble_port_init();

                esp_idf_sys::ble_hs_cfg.sync_cb = Some(Self::on_sync);
                esp_idf_sys::ble_hs_cfg.reset_cb = Some(Self::on_reset);

                // Set initial security capabilities
                esp_idf_sys::ble_hs_cfg.sm_io_cap = esp_idf_sys::BLE_HS_IO_NO_INPUT_OUTPUT as _;
                esp_idf_sys::ble_hs_cfg.set_sm_bonding(0);
                esp_idf_sys::ble_hs_cfg.set_sm_mitm(0);
                esp_idf_sys::ble_hs_cfg.set_sm_sc(1);
                esp_idf_sys::ble_hs_cfg.sm_our_key_dist = 1;
                esp_idf_sys::ble_hs_cfg.sm_their_key_dist = 3;
                esp_idf_sys::ble_hs_cfg.store_status_cb =
                    Some(esp_idf_sys::ble_store_util_status_rr);

                ble_store_config_init();

                esp_idf_sys::nimble_port_freertos_init(Some(Self::blecent_host_task));
            }

            loop {
                let syncd = SYNCED.load(Ordering::Acquire);
                if syncd {
                    break;
                }
                esp_idf_sys::vPortYield();
            }

            INITIALIZED.store(true, Ordering::Release);
        }
    }

    pub fn take() -> &'static mut Self {
        unsafe { Lazy::force_mut(&mut BLE_DEVICE) }
    }

    /// Shutdown the NimBLE stack/controller
    ///
    /// To restart, call `BLEDevice::init()`.
    pub fn deinit() -> Result<(), EspError> {
        if !INITIALIZED.load(Ordering::Acquire) {
            ::log::info!("BLE stack is not initialized, skipping deinit.");
            return Ok(());
        }

        unsafe {
            esp!(esp_idf_sys::nimble_port_stop())?;

            #[cfg(esp_idf_version_major = "4")]
            {
                esp_idf_sys::nimble_port_deinit();
                esp!(esp_idf_sys::esp_nimble_hci_and_controller_deinit())?;
            }

            #[cfg(not(esp_idf_version_major = "4"))]
            esp!(esp_idf_sys::nimble_port_deinit())?;

            INITIALIZED.store(false, Ordering::Release);
            SYNCED.store(false, Ordering::Release);

            if let Some(server) = Lazy::get_mut(&mut BLE_SERVER) {
                server.started = false;
            }
        };

        Ok(())
    }

    /// Shutdown the NimBLE stack/controller.
    ///
    /// server/advertising/scan will be reset.
    ///
    /// To restart, call `BLEDevice::init()`.
    pub fn deinit_full() -> Result<(), EspError> {
        Self::deinit()?;
        unsafe {
            #[cfg(not(esp_idf_bt_nimble_ext_adv))]
            if let Some(advertising) = Lazy::get(&BLE_ADVERTISING) {
                advertising.lock().reset().unwrap();
            }

            if let Some(server) = Lazy::get_mut(&mut BLE_SERVER) {
                server.reset();
            }
        }
        Ok(())
    }

    pub fn new_client(&self) -> BLEClient {
        BLEClient::new()
    }

    pub fn get_server(&self) -> &'static mut BLEServer {
        unsafe { Lazy::force_mut(&mut BLE_SERVER) }
    }

    pub fn get_advertising(&self) -> &'static Mutex<BLEAdvertising> {
        Lazy::force(&BLE_ADVERTISING)
    }

    pub fn set_power(
        &mut self,
        power_type: PowerType,
        power_level: PowerLevel,
    ) -> Result<(), BLEError> {
        unsafe {
            ble!(esp_idf_sys::esp_ble_tx_power_set(
                power_type.into(),
                power_level.into()
            ))
        }
    }

    pub fn get_power(&self, power_type: PowerType) -> PowerLevel {
        PowerLevel::try_from(unsafe { esp_idf_sys::esp_ble_tx_power_get(power_type.into()) })
            .unwrap()
    }

    /// Sets the preferred ATT MTU; the device will indicate this value in all subsequent ATT MTU exchanges.
    /// The ATT MTU of a connection is equal to the lower of the two peers’preferred MTU values.
    /// The ATT MTU is what dictates the maximum size of any message sent during a GATT procedure.
    ///
    /// The specified MTU must be within the following range: [23, BLE_ATT_MTU_MAX].
    /// 23 is a minimum imposed by the Bluetooth specification;
    /// BLE_ATT_MTU_MAX is a NimBLE compile-time setting.
    pub fn set_preferred_mtu(&self, mtu: u16) -> Result<(), BLEError> {
        unsafe { ble!(esp_idf_sys::ble_att_set_preferred_mtu(mtu)) }
    }

    /// Retrieves the preferred ATT MTU.
    /// This is the value indicated by the device during an ATT MTU exchange.
    pub fn get_preferred_mtu(&self) -> u16 {
        unsafe { esp_idf_sys::ble_att_preferred_mtu() }
    }

    /// Get the addresses of all bonded peer device.
    pub fn bonded_addresses(&self) -> Result<Vec<BLEAddress>, BLEError> {
        let mut peer_id_addrs =
            [esp_idf_sys::ble_addr_t::default(); esp_idf_sys::MYNEWT_VAL_BLE_STORE_MAX_BONDS as _];
        let mut num_peers: core::ffi::c_int = 0;

        unsafe {
            ble!(esp_idf_sys::ble_store_util_bonded_peers(
                peer_id_addrs.as_mut_ptr(),
                &mut num_peers,
                esp_idf_sys::MYNEWT_VAL_BLE_STORE_MAX_BONDS as _,
            ))?
        };

        let mut result = Vec::with_capacity(esp_idf_sys::MYNEWT_VAL_BLE_STORE_MAX_BONDS as _);
        for addr in peer_id_addrs.iter().take(num_peers as _) {
            result.push(BLEAddress::from(*addr));
        }

        Ok(result)
    }

    /// Deletes all bonding information.
    pub fn delete_all_bonds(&self) -> Result<(), BLEError> {
        unsafe { ble!(esp_idf_sys::ble_store_clear()) }
    }

    /// Deletes a peer bond.
    ///
    /// * `address`: The address of the peer with which to delete bond info.
    pub fn delete_bond(&self, address: &BLEAddress) -> Result<(), BLEError> {
        unsafe { ble!(esp_idf_sys::ble_gap_unpair(&address.value)) }
    }

    pub fn set_white_list(&mut self, white_list: &[BLEAddress]) -> Result<(), BLEError> {
        unsafe {
            ble!(esp_idf_sys::ble_gap_wl_set(
                white_list.as_ptr() as _,
                white_list.len() as _
            ))
        }
    }

    pub fn security(&mut self) -> &mut BLESecurity {
        &mut self.security
    }

    pub fn get_addr(&self) -> Result<BLEAddress, BLEError> {
        let mut addr = [0; 6];

        unsafe {
            ble!(esp_idf_sys::ble_hs_id_copy_addr(
                OWN_ADDR_TYPE.into(),
                addr.as_mut_ptr(),
                core::ptr::null_mut()
            ))?;

            let addr_type = match OWN_ADDR_TYPE {
                OwnAddrType::Public => BLEAddressType::Public,
                _ => BLEAddressType::Random,
            };

            Ok(BLEAddress::from_le_bytes(addr, addr_type))
        }
    }

    /// Set the own address type.
    pub fn set_own_addr_type(&mut self, own_addr_type: OwnAddrType) {
        self._set_own_addr_type(own_addr_type, false);
    }

    /// Set the own address type to non-resolvable random address.
    #[cfg(esp32)]
    pub fn set_own_addr_type_to_non_resolvable_random(&mut self) {
        self._set_own_addr_type(OwnAddrType::Random, true);
    }

    #[allow(unused_variables)]
    fn _set_own_addr_type(&mut self, own_addr_type: OwnAddrType, use_nrpa: bool) {
        unsafe {
            OWN_ADDR_TYPE = own_addr_type;
            match own_addr_type {
                OwnAddrType::Public => {
                    #[cfg(esp32)]
                    ble_hs_pvcy_rpa_config(NIMBLE_HOST_DISABLE_PRIVACY);
                }
                OwnAddrType::Random => {
                    self.security().resolve_rpa();
                    #[cfg(esp32)]
                    ble_hs_pvcy_rpa_config(if use_nrpa {
                        NIMBLE_HOST_ENABLE_NRPA
                    } else {
                        NIMBLE_HOST_ENABLE_RPA
                    });
                }
                OwnAddrType::RpaPublicDefault | OwnAddrType::RpaRandomDefault => {
                    self.security().resolve_rpa();
                    #[cfg(esp32)]
                    ble_hs_pvcy_rpa_config(NIMBLE_HOST_ENABLE_RPA);
                }
            }
        }
    }

    /// Set the own address to be used when the address type is random.
    pub fn set_rnd_addr(&mut self, mut addr: [u8; 6]) -> Result<(), BLEError> {
        addr.reverse();
        unsafe { ble!(esp_idf_sys::ble_hs_id_set_rnd(addr.as_ptr())) }
    }

    #[allow(dangling_pointers_from_temporaries)]
    pub fn set_device_name(device_name: &str) -> Result<(), BLEError> {
        unsafe {
            ble!(esp_idf_sys::ble_svc_gap_device_name_set(
                CString::new(device_name).unwrap().as_ptr().cast()
            ))
        }
    }

    extern "C" fn on_sync() {
        unsafe {
            esp_idf_sys::ble_hs_util_ensure_addr(0);

            esp_nofail!(esp_idf_sys::ble_hs_id_infer_auto(
                0,
                core::mem::transmute::<&mut OwnAddrType, &mut u8>(&mut OWN_ADDR_TYPE) as *mut _
            ));

            let mut addr = [0; 6];
            esp_nofail!(esp_idf_sys::ble_hs_id_copy_addr(
                OWN_ADDR_TYPE.into(),
                addr.as_mut_ptr(),
                core::ptr::null_mut()
            ));
            ::log::info!(
                "Device Address: {:X}:{:X}:{:X}:{:X}:{:X}:{:X}",
                addr[5],
                addr[4],
                addr[3],
                addr[2],
                addr[1],
                addr[0]
            );

            SYNCED.store(true, Ordering::Release);
        }
    }

    extern "C" fn on_reset(reason: i32) {
        ::log::info!("Resetting state; reason={reason}");
    }

    extern "C" fn blecent_host_task(_: *mut c_void) {
        unsafe {
            ::log::info!("BLE Host Task Started");
            esp_idf_sys::nimble_port_run();
            esp_idf_sys::nimble_port_freertos_deinit();
        }
    }
}