bluest 0.5.3

A cross-platform Bluetooth Low Energy (BLE) library
Documentation
#![allow(clippy::let_unit_value)]
#![allow(missing_docs)]
#![allow(unused)]

use std::collections::HashMap;
use std::ffi::c_ulong;
use std::os::raw::{c_char, c_void};

use objc::rc::autoreleasepool;
use objc::runtime::{Object, BOOL, NO};
use objc::{msg_send, sel, sel_impl};
use objc_foundation::{
    object_struct, INSData, INSDictionary, INSFastEnumeration, INSObject, INSString, NSArray, NSData, NSDictionary,
    NSObject, NSString,
};
use objc_id::{Id, ShareId};

use super::delegates::{CentralDelegate, PeripheralDelegate};
use crate::btuuid::BluetoothUuidExt;
use crate::{AdvertisementData, CharacteristicProperties, ManufacturerData, Uuid};

#[allow(non_camel_case_types)]
pub type id = *mut Object;

pub type NSInteger = isize;
pub type NSUInteger = usize;

#[allow(non_upper_case_globals)]
pub const nil: *mut Object = std::ptr::null_mut();

#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CBManagerState(pub NSInteger);

impl CBManagerState {
    pub const UNKNOWN: CBManagerState = CBManagerState(0);
    pub const RESETTING: CBManagerState = CBManagerState(1);
    pub const UNSUPPORTED: CBManagerState = CBManagerState(2);
    pub const UNAUTHORIZED: CBManagerState = CBManagerState(3);
    pub const POWERED_OFF: CBManagerState = CBManagerState(4);
    pub const POWERED_ON: CBManagerState = CBManagerState(5);
}

#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CBManagerAuthorization(pub NSInteger);

impl CBManagerAuthorization {
    pub const NOT_DETERMINED: CBManagerAuthorization = CBManagerAuthorization(0);
    pub const RESTRICTED: CBManagerAuthorization = CBManagerAuthorization(1);
    pub const DENIED: CBManagerAuthorization = CBManagerAuthorization(2);
    pub const ALLOWED_ALWAYS: CBManagerAuthorization = CBManagerAuthorization(3);
}

#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CBCentralManagerFeatures(pub NSUInteger);

impl CBCentralManagerFeatures {
    pub const EXTENDED_SCAN_AND_CONNECT: CBCentralManagerFeatures = CBCentralManagerFeatures(1);
}

#[non_exhaustive]
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CBCharacteristicWriteType {
    #[default]
    WithResponse = 0,
    WithoutResponse = 1,
}

#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CBPeripheralState(pub NSInteger);

impl CBPeripheralState {
    pub const DISCONNECTED: CBPeripheralState = CBPeripheralState(0);
    pub const CONNECTING: CBPeripheralState = CBPeripheralState(1);
    pub const CONNECTED: CBPeripheralState = CBPeripheralState(2);
    pub const DISCONNECTING: CBPeripheralState = CBPeripheralState(3);
}

#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CBCharacteristicProperties(pub NSUInteger);

impl CBCharacteristicProperties {
    pub const BROADCAST: CBCharacteristicProperties = CBCharacteristicProperties(0b0000_0001);
    pub const READ: CBCharacteristicProperties = CBCharacteristicProperties(0b0000_0010);
    pub const WRITE_WITHOUT_RESPONSE: CBCharacteristicProperties = CBCharacteristicProperties(0b0000_0100);
    pub const WRITE: CBCharacteristicProperties = CBCharacteristicProperties(0b0000_1000);
    pub const NOTIFY: CBCharacteristicProperties = CBCharacteristicProperties(0b0001_0000);
    pub const INDICATE: CBCharacteristicProperties = CBCharacteristicProperties(0b0010_0000);
    pub const AUTHENTICATED_SIGNED_WRITES: CBCharacteristicProperties = CBCharacteristicProperties(0b0100_0000);
    pub const EXTENDED_PROPERTIES: CBCharacteristicProperties = CBCharacteristicProperties(0b1000_0000);
    pub const NOTIFY_ENCRYPTION_REQUIRED: CBCharacteristicProperties = CBCharacteristicProperties(0b0001_0000_0000);
    pub const INDICATE_ENCRYPTION_REQUIRED: CBCharacteristicProperties = CBCharacteristicProperties(0b0010_0000_0000);
}

impl From<CBCharacteristicProperties> for CharacteristicProperties {
    fn from(val: CBCharacteristicProperties) -> Self {
        CharacteristicProperties::from_bits((val.0 & 0xff) as u32)
    }
}

#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CBError(pub NSInteger);

impl CBError {
    pub const UNKNOWN: CBError = CBError(0);
    pub const INVALID_PARAMETERS: CBError = CBError(1);
    pub const INVALID_HANDLE: CBError = CBError(2);
    pub const NOT_CONNECTED: CBError = CBError(3);
    pub const OUT_OF_SPACE: CBError = CBError(4);
    pub const OPERATION_CANCELLED: CBError = CBError(5);
    pub const CONNECTION_TIMEOUT: CBError = CBError(6);
    pub const PERIPHERAL_DISCONNECTED: CBError = CBError(7);
    pub const UUID_NOT_ALLOWED: CBError = CBError(8);
    pub const ALREADY_ADVERTISING: CBError = CBError(9);
    pub const CONNECTION_FAILED: CBError = CBError(10);
    pub const CONNECTION_LIMIT_REACHED: CBError = CBError(11);
    pub const UNKOWN_DEVICE: CBError = CBError(12);
    pub const OPERATION_NOT_SUPPORTED: CBError = CBError(13);
    pub const PEER_REMOVED_PAIRING_INFORMATION: CBError = CBError(14);
    pub const ENCRYPTION_TIMED_OUT: CBError = CBError(15);
    pub const TOO_MANY_LE_PAIRED_DEVICES: CBError = CBError(16);
}

#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CBATTError(pub NSInteger);

impl CBATTError {
    pub const SUCCESS: CBATTError = CBATTError(0);
    pub const INVALID_HANDLE: CBATTError = CBATTError(1);
    pub const READ_NOT_PERMITTED: CBATTError = CBATTError(2);
    pub const WRITE_NOT_PERMITTED: CBATTError = CBATTError(3);
    pub const INVALID_PDU: CBATTError = CBATTError(4);
    pub const INSUFFICIENT_AUTHENTICATION: CBATTError = CBATTError(5);
    pub const REQUEST_NOT_SUPPORTED: CBATTError = CBATTError(6);
    pub const INVALID_OFFSET: CBATTError = CBATTError(7);
    pub const INSUFFICIENT_AUTHORIZATION: CBATTError = CBATTError(8);
    pub const PREPARE_QUEUE_FULL: CBATTError = CBATTError(9);
    pub const ATTRIBUTE_NOT_FOUND: CBATTError = CBATTError(10);
    pub const ATTRIBUTE_NOT_LONG: CBATTError = CBATTError(11);
    pub const INSUFFICIENT_ENCRYPTION_KEY_SIZE: CBATTError = CBATTError(12);
    pub const INVALID_ATTRIBUTE_VALUE_LENGTH: CBATTError = CBATTError(13);
    pub const UNLIKELY_ERROR: CBATTError = CBATTError(14);
    pub const INSUFFICIENT_ENCRYPTION: CBATTError = CBATTError(15);
    pub const UNSUPPORTED_GROUP_TYPE: CBATTError = CBATTError(16);
    pub const INSUFFICIENT_RESOURCES: CBATTError = CBATTError(17);
}

impl AdvertisementData {
    pub(super) fn from_nsdictionary(adv_data: &ShareId<NSDictionary<NSString, NSObject>>) -> Self {
        let is_connectable = adv_data
            .object_for(&*INSString::from_str("kCBAdvDataIsConnectable"))
            .map_or(false, |val| unsafe {
                let n: BOOL = msg_send![val, boolValue];
                n != NO
            });

        let local_name = adv_data
            .object_for(&*INSString::from_str("kCBAdvDataLocalName"))
            .map(|val| unsafe { (*(val as *const NSObject).cast::<NSString>()).as_str().to_string() });

        let manufacturer_data = adv_data
            .object_for(&*INSString::from_str("kCBAdvDataManufacturerData"))
            .map(|val| unsafe { (*(val as *const NSObject).cast::<NSData>()).bytes() })
            .and_then(|val| {
                (val.len() >= 2).then(|| ManufacturerData {
                    company_id: u16::from_le_bytes(val[0..2].try_into().unwrap()),
                    data: val[2..].to_vec(),
                })
            });

        let tx_power_level: Option<i16> = adv_data
            .object_for(&*INSString::from_str("kCBAdvDataTxPowerLevel"))
            .map(|val| unsafe { msg_send![val, shortValue] });

        let service_data = if let Some(val) = adv_data.object_for(&*INSString::from_str("kCBAdvDataServiceData")) {
            unsafe {
                let val: &NSDictionary<CBUUID, NSData> = &*(val as *const NSObject).cast();
                let mut res = HashMap::with_capacity(val.count());
                for k in val.enumerator() {
                    res.insert(k.to_uuid(), val.object_for(k).unwrap().bytes().to_vec());
                }
                res
            }
        } else {
            HashMap::new()
        };

        let services = adv_data
            .object_for(&*INSString::from_str("kCBAdvDataServiceUUIDs"))
            .into_iter()
            .chain(
                adv_data
                    .object_for(&*INSString::from_str("kCBAdvDataHashedServiceUUIDs"))
                    .into_iter(),
            )
            .flat_map(|x| {
                let val: &NSArray<CBUUID> = unsafe { &*(x as *const NSObject).cast() };
                val.enumerator()
            })
            .map(CBUUID::to_uuid)
            .collect();

        AdvertisementData {
            local_name,
            manufacturer_data,
            services,
            service_data,
            tx_power_level,
            is_connectable,
        }
    }
}

#[link(name = "CoreBluetooth", kind = "framework")]
extern "C" {
    pub static _dispatch_queue_attr_concurrent: Object;
    pub fn dispatch_queue_attr_make_with_autorelease_frequency(attr: id, frequency: c_ulong) -> id;
    pub fn dispatch_queue_create(label: *const c_char, attr: id) -> id;
    pub fn dispatch_get_global_queue(identifier: isize, flags: usize) -> id;
    pub fn dispatch_release(object: id) -> c_void;
}

pub const QOS_CLASS_USER_INTERACTIVE: isize = 0x21;
pub const QOS_CLASS_USER_INITIATED: isize = 0x19;
pub const QOS_CLASS_DEFAULT: isize = 0x15;
pub const QOS_CLASS_UTILITY: isize = 0x11;
pub const QOS_CLASS_BACKGROUND: isize = 0x09;
pub const QOS_CLASS_UNSPECIFIED: isize = 0x00;

pub fn id_or_nil<T>(val: Option<&T>) -> *const T {
    match val {
        Some(x) => x,
        None => std::ptr::null(),
    }
}

pub unsafe fn option_from_ptr<T: objc::Message, O: objc_id::Ownership>(ptr: *mut T) -> Option<Id<T, O>> {
    (!ptr.is_null()).then(|| Id::from_ptr(ptr))
}

object_struct!(NSError);
object_struct!(NSUUID);
object_struct!(CBUUID);
object_struct!(CBCentralManager);
object_struct!(CBPeripheral);
object_struct!(CBService);
object_struct!(CBCharacteristic);
object_struct!(CBDescriptor);
object_struct!(CBL2CAPChannel);

impl NSError {
    pub fn code(&self) -> NSInteger {
        unsafe { msg_send![self, code] }
    }

    pub fn domain(&self) -> ShareId<NSString> {
        autoreleasepool(move || unsafe { Id::from_ptr(msg_send![self, domain]) })
    }

    pub fn user_info(&self) -> ShareId<NSDictionary<NSString, NSObject>> {
        autoreleasepool(move || unsafe { Id::from_ptr(msg_send![self, userInfo]) })
    }

    pub fn localized_description(&self) -> ShareId<NSString> {
        autoreleasepool(move || unsafe { Id::from_ptr(msg_send![self, localizedDescription]) })
    }

    pub fn localized_recovery_options(&self) -> Option<ShareId<NSArray<NSString>>> {
        autoreleasepool(move || unsafe { option_from_ptr(msg_send![self, localizedRecoveryOptions]) })
    }

    pub fn localized_recovery_suggestion(&self) -> Option<ShareId<NSString>> {
        autoreleasepool(move || unsafe { option_from_ptr(msg_send![self, localizedRecoverySuggestion]) })
    }

    pub fn localized_failure_reason(&self) -> Option<ShareId<NSString>> {
        autoreleasepool(move || unsafe { option_from_ptr(msg_send![self, localizedFailureReason]) })
    }

    pub fn help_anchor(&self) -> Option<ShareId<NSString>> {
        autoreleasepool(move || unsafe { option_from_ptr(msg_send![self, helpAnchor]) })
    }

    pub fn underlying_errors(&self) -> ShareId<NSArray<NSError>> {
        autoreleasepool(move || unsafe { Id::from_ptr(msg_send![self, underlyingErrors]) })
    }
}

impl NSUUID {
    pub fn from_uuid(uuid: Uuid) -> Id<Self> {
        unsafe {
            let obj: *mut Self = msg_send![Self::class(), alloc];
            Id::from_retained_ptr(msg_send![obj, initWithUUIDBytes: uuid.as_bytes()])
        }
    }

    pub fn to_uuid(&self) -> Uuid {
        let mut bytes = [0u8; 16];
        let _: () = unsafe { msg_send!(self, getUUIDBytes: &mut bytes) };
        Uuid::from_bytes(bytes)
    }
}

impl CBUUID {
    pub fn from_uuid(uuid: Uuid) -> Id<Self> {
        autoreleasepool(|| unsafe {
            let data = NSData::from_vec(uuid.as_bluetooth_bytes().to_vec());
            let obj: *mut Self = msg_send![Self::class(), UUIDWithData: &*data];
            Id::from_ptr(obj)
        })
    }

    pub fn to_uuid(&self) -> Uuid {
        autoreleasepool(move || {
            let data: ShareId<NSData> = unsafe { ShareId::from_ptr(msg_send!(self, data)) };
            Uuid::from_bluetooth_bytes(data.bytes())
        })
    }
}

impl CBCentralManager {
    pub fn with_delegate(delegate: &CentralDelegate, queue: id) -> Id<CBCentralManager> {
        unsafe {
            let obj: *mut Self = msg_send![Self::class(), alloc];
            Id::from_retained_ptr(msg_send![obj, initWithDelegate: delegate queue: queue])
        }
    }

    pub fn state(&self) -> CBManagerState {
        CBManagerState(unsafe { msg_send![self, state] })
    }

    pub fn authorization() -> CBManagerAuthorization {
        CBManagerAuthorization(unsafe { msg_send![Self::class(), authorization] })
    }

    pub fn connect_peripheral(&self, peripheral: &CBPeripheral, options: Option<&NSDictionary<NSString, NSObject>>) {
        unsafe { msg_send![self, connectPeripheral: peripheral options: id_or_nil(options)] }
    }

    pub fn cancel_peripheral_connection(&self, peripheral: &CBPeripheral) {
        unsafe { msg_send![self, cancelPeripheralConnection: peripheral] }
    }

    pub fn retrieve_connected_peripherals_with_services(
        &self,
        services: &NSArray<CBUUID>,
    ) -> Id<NSArray<CBPeripheral>> {
        autoreleasepool(move || unsafe {
            Id::from_ptr(msg_send![self, retrieveConnectedPeripheralsWithServices: services])
        })
    }

    pub fn retrieve_peripherals_with_identifiers(&self, identifiers: &NSArray<NSUUID>) -> Id<NSArray<CBPeripheral>> {
        autoreleasepool(move || unsafe {
            Id::from_ptr(msg_send![self, retrievePeripheralsWithIdentifiers: identifiers])
        })
    }

    pub fn scan_for_peripherals_with_services(
        &self,
        services: Option<&NSArray<CBUUID>>,
        options: Option<&NSDictionary<NSString, NSObject>>,
    ) {
        unsafe { msg_send![self, scanForPeripheralsWithServices: id_or_nil(services) options: id_or_nil(options)] }
    }

    pub fn stop_scan(&self) {
        unsafe { msg_send![self, stopScan] }
    }

    pub fn is_scanning(&self) -> bool {
        let res: BOOL = unsafe { msg_send![self, isScanning] };
        res != NO
    }

    pub fn supports_features(&self, features: CBCentralManagerFeatures) -> bool {
        let res: BOOL = unsafe { msg_send![self, supportsFeatures: features.0] };
        res != NO
    }

    pub fn delegate(&self) -> Option<ShareId<CentralDelegate>> {
        autoreleasepool(move || unsafe { option_from_ptr(msg_send![self, delegate]) })
    }

    pub fn register_for_connection_events_with_options(&self, options: &NSDictionary<NSString, NSObject>) {
        unsafe { msg_send![self, registerForConnectionEventsWithOptions: options] }
    }
}

impl CBPeripheral {
    pub fn identifier(&self) -> ShareId<NSUUID> {
        autoreleasepool(move || unsafe { ShareId::from_ptr(msg_send![self, identifier]) })
    }

    pub fn name(&self) -> Option<ShareId<NSString>> {
        autoreleasepool(move || unsafe { option_from_ptr(msg_send![self, name]) })
    }

    pub fn delegate(&self) -> Option<ShareId<PeripheralDelegate>> {
        autoreleasepool(move || unsafe { option_from_ptr(msg_send![self, delegate]) })
    }

    pub fn set_delegate(&self, delegate: &PeripheralDelegate) {
        unsafe { msg_send![self, setDelegate: delegate] }
    }

    pub fn services(&self) -> Option<ShareId<NSArray<CBService>>> {
        autoreleasepool(move || unsafe { option_from_ptr(msg_send![self, services]) })
    }
    pub fn discover_services(&self, services: Option<&NSArray<CBUUID>>) {
        unsafe { msg_send![self, discoverServices: id_or_nil(services)] }
    }

    pub fn discover_included_services(&self, service: &CBService, services: Option<&NSArray<CBUUID>>) {
        unsafe { msg_send![self, discoverIncludedServices: id_or_nil(services) forService: service] }
    }

    pub fn discover_characteristics(&self, service: &CBService, characteristics: Option<&NSArray<CBUUID>>) {
        unsafe { msg_send![self, discoverCharacteristics: id_or_nil(characteristics) forService: service] }
    }

    pub fn discover_descriptors(&self, characteristic: &CBCharacteristic) {
        unsafe { msg_send![self, discoverDescriptorsForCharacteristic: characteristic] }
    }

    pub fn read_characteristic_value(&self, characteristic: &CBCharacteristic) {
        unsafe { msg_send![self, readValueForCharacteristic: characteristic] }
    }

    pub fn read_descriptor_value(&self, descriptor: &CBDescriptor) {
        unsafe { msg_send![self, readValueForDescriptor: descriptor] }
    }

    pub fn write_characteristic_value(
        &self,
        characteristic: &CBCharacteristic,
        value: &NSData,
        write_type: CBCharacteristicWriteType,
    ) {
        let write_type: isize = write_type as isize;
        unsafe { msg_send![self, writeValue: value forCharacteristic: characteristic type: write_type] }
    }

    pub fn write_descriptor_value(&self, descriptor: &CBDescriptor, value: &NSData) {
        unsafe { msg_send![self, writeValue: value forDescriptor: descriptor] }
    }

    pub fn set_notify(&self, characteristic: &CBCharacteristic, enabled: bool) {
        unsafe { msg_send![self, setNotifyValue: enabled as BOOL forCharacteristic: characteristic] }
    }

    pub fn state(&self) -> CBPeripheralState {
        CBPeripheralState(unsafe { msg_send![self, state] })
    }

    pub fn read_rssi(&self) {
        unsafe { msg_send![self, readRSSI] }
    }
}

impl CBService {
    pub fn uuid(&self) -> ShareId<CBUUID> {
        autoreleasepool(move || unsafe { ShareId::from_ptr(msg_send![self, UUID]) })
    }

    pub fn peripheral(&self) -> ShareId<CBPeripheral> {
        autoreleasepool(move || unsafe { ShareId::from_ptr(msg_send![self, peripheral]) })
    }

    pub fn is_primary(&self) -> bool {
        let res: BOOL = unsafe { msg_send![self, isPrimary] };
        res != NO
    }

    pub fn characteristics(&self) -> Option<ShareId<NSArray<CBCharacteristic>>> {
        autoreleasepool(move || unsafe { option_from_ptr(msg_send![self, characteristics]) })
    }

    pub fn included_services(&self) -> Option<ShareId<NSArray<CBService>>> {
        autoreleasepool(move || unsafe { option_from_ptr(msg_send![self, includedServices]) })
    }
}

impl CBCharacteristic {
    pub fn uuid(&self) -> ShareId<CBUUID> {
        autoreleasepool(move || unsafe { ShareId::from_ptr(msg_send![self, UUID]) })
    }

    pub fn service(&self) -> ShareId<CBService> {
        autoreleasepool(move || unsafe { ShareId::from_ptr(msg_send![self, service]) })
    }

    pub fn value(&self) -> Option<ShareId<NSData>> {
        autoreleasepool(move || unsafe { option_from_ptr(msg_send![self, value]) })
    }

    pub fn descriptors(&self) -> Option<ShareId<NSArray<CBDescriptor>>> {
        autoreleasepool(move || unsafe { option_from_ptr(msg_send![self, descriptors]) })
    }

    pub fn properties(&self) -> CBCharacteristicProperties {
        CBCharacteristicProperties(unsafe { msg_send![self, properties] })
    }

    pub fn is_notifying(&self) -> bool {
        let res: BOOL = unsafe { msg_send![self, isNotifying] };
        res != NO
    }

    pub fn is_broadcasting(&self) -> bool {
        let res: BOOL = unsafe { msg_send![self, isBroadcasting] };
        res != NO
    }
}

impl CBDescriptor {
    pub fn uuid(&self) -> ShareId<CBUUID> {
        autoreleasepool(move || unsafe { ShareId::from_ptr(msg_send![self, UUID]) })
    }

    pub fn characteristic(&self) -> ShareId<CBCharacteristic> {
        autoreleasepool(move || unsafe { ShareId::from_ptr(msg_send![self, characteristic]) })
    }

    pub fn value(&self) -> Option<ShareId<NSObject>> {
        autoreleasepool(move || unsafe { option_from_ptr(msg_send![self, value]) })
    }
}