bluster 0.1.3

A Rust crate for implementing BLE (Bluetooth Low Energy) peripherals
Documentation
use std::{
    ffi::CString,
    sync::{Once, ONCE_INIT},
};

use objc::{
    class,
    declare::ClassDecl,
    msg_send,
    runtime::{Class, Object, Protocol, Sel, NO, YES},
    sel, sel_impl,
};
use objc_foundation::{
    INSArray, INSData, INSDictionary, INSString, NSArray, NSData, NSDictionary, NSObject, NSString,
};
use objc_id::{Id, Shared};

use uuid::Uuid;

use crate::gatt::service::Service;

use super::{
    characteristic_flags::get_properties_and_permissions,
    constants::{PERIPHERAL_MANAGER_DELEGATE_CLASS_NAME, PERIPHERAL_MANAGER_IVAR, POWERED_ON_IVAR},
    events::{
        peripheral_manager_did_add_service_error, peripheral_manager_did_receive_read_request,
        peripheral_manager_did_receive_write_requests,
        peripheral_manager_did_start_advertising_error, peripheral_manager_did_update_state,
    },
    ffi::{
        dispatch_queue_create, nil, CBAdvertisementDataLocalNameKey,
        CBAdvertisementDataServiceUUIDsKey, DISPATCH_QUEUE_SERIAL,
    },
    into_bool::IntoBool,
    into_cbuuid::IntoCBUUID,
};

static REGISTER_DELEGATE_CLASS: Once = ONCE_INIT;

#[derive(Debug)]
pub struct PeripheralManager {
    peripheral_manager_delegate: Id<Object, Shared>,
}

impl PeripheralManager {
    pub fn new() -> Self {
        REGISTER_DELEGATE_CLASS.call_once(|| {
            let mut decl =
                ClassDecl::new(PERIPHERAL_MANAGER_DELEGATE_CLASS_NAME, class!(NSObject)).unwrap();
            decl.add_protocol(Protocol::get("CBPeripheralManagerDelegate").unwrap());

            decl.add_ivar::<*mut Object>(PERIPHERAL_MANAGER_IVAR);
            decl.add_ivar::<*mut Object>(POWERED_ON_IVAR);

            unsafe {
                decl.add_method(
                    sel!(init),
                    init as extern "C" fn(&mut Object, Sel) -> *mut Object,
                );
                decl.add_method(
                    sel!(peripheralManagerDidUpdateState:),
                    peripheral_manager_did_update_state
                        as extern "C" fn(&mut Object, Sel, *mut Object),
                );
                decl.add_method(
                    sel!(peripheralManagerDidStartAdvertising:error:),
                    peripheral_manager_did_start_advertising_error
                        as extern "C" fn(&mut Object, Sel, *mut Object, *mut Object),
                );
                decl.add_method(
                    sel!(peripheralManager:didAddService:error:),
                    peripheral_manager_did_add_service_error
                        as extern "C" fn(&mut Object, Sel, *mut Object, *mut Object, *mut Object),
                );
                decl.add_method(
                    sel!(peripheralManager:didReceiveReadRequest:),
                    peripheral_manager_did_receive_read_request
                        as extern "C" fn(&mut Object, Sel, *mut Object, *mut Object),
                );
                decl.add_method(
                    sel!(peripheralManager:didReceiveWriteRequests:),
                    peripheral_manager_did_receive_write_requests
                        as extern "C" fn(&mut Object, Sel, *mut Object, *mut Object),
                );
            }

            decl.register();
        });

        let peripheral_manager_delegate = unsafe {
            let cls = Class::get(PERIPHERAL_MANAGER_DELEGATE_CLASS_NAME).unwrap();
            let mut obj: *mut Object = msg_send![cls, alloc];
            obj = msg_send![obj, init];
            Id::from_ptr(obj).share()
        };

        PeripheralManager {
            peripheral_manager_delegate,
        }
    }

    pub fn is_powered(self: &Self) -> bool {
        unsafe {
            let powered_on = *self
                .peripheral_manager_delegate
                .get_ivar::<*mut Object>(POWERED_ON_IVAR);
            powered_on.into_bool()
        }
    }

    pub fn start_advertising(self: &Self, name: &str, uuids: &[Uuid]) {
        let peripheral_manager = unsafe {
            *self
                .peripheral_manager_delegate
                .get_ivar::<*mut Object>(PERIPHERAL_MANAGER_IVAR)
        };

        let mut keys: Vec<&NSString> = vec![];
        let mut objects: Vec<Id<NSObject>> = vec![];

        unsafe {
            keys.push(&*(CBAdvertisementDataLocalNameKey as *mut NSString));
            objects.push(Id::from_retained_ptr(msg_send![
                NSString::from_str(name),
                copy
            ]));
            keys.push(&*(CBAdvertisementDataServiceUUIDsKey as *mut NSString));
            objects.push(Id::from_retained_ptr(msg_send![
                NSArray::from_vec(
                    uuids
                        .iter()
                        .map(|u| NSString::from_str(&u.to_hyphenated().to_string()))
                        .collect::<Vec<Id<NSString>>>()
                ),
                copy
            ]));
        }

        let advertising_data = NSDictionary::from_keys_and_objects(keys.as_slice(), objects);
        unsafe {
            let _: Result<(), ()> =
                msg_send![peripheral_manager, startAdvertising: advertising_data];
        }
    }

    pub fn stop_advertising(self: &Self) {
        unsafe {
            let peripheral_manager = *self
                .peripheral_manager_delegate
                .get_ivar::<*mut Object>(PERIPHERAL_MANAGER_IVAR);
            let _: Result<(), ()> = msg_send![peripheral_manager, stopAdvertising];
        }
    }

    pub fn is_advertising(self: &Self) -> bool {
        unsafe {
            let peripheral_manager = *self
                .peripheral_manager_delegate
                .get_ivar::<*mut Object>(PERIPHERAL_MANAGER_IVAR);
            let response: *mut Object = msg_send![peripheral_manager, isAdvertising];
            response.into_bool()
        }
    }

    pub fn add_service(self: &Self, service: &Service) {
        let characteristics: Vec<Id<NSObject>> = service
            .characteristics
            .iter()
            .map(|characteristic| {
                let (properties, permissions) = get_properties_and_permissions(characteristic);
                unsafe {
                    let cls = class!(CBMutableCharacteristic);
                    let obj: *mut Object = msg_send![cls, alloc];

                    let init_with_type = characteristic.uuid.into_cbuuid();
                    let mutable_characteristic: *mut Object = match characteristic.value {
                        Some(ref value) => msg_send![obj, initWithType:init_with_type
                                                            properties:properties
                                                                 value:NSData::with_bytes(value)
                                                           permissions:permissions],
                        None => msg_send![obj, initWithType:init_with_type
                                                 properties:properties
                                                      value:nil
                                                permissions:permissions],
                    };

                    Id::from_ptr(mutable_characteristic as *mut NSObject)
                }
            })
            .collect();

        unsafe {
            let cls = class!(CBMutableService);
            let obj: *mut Object = msg_send![cls, alloc];
            let service: *mut Object = msg_send![obj, initWithType:service.uuid.into_cbuuid()
                                                           primary:YES];
            let _: Result<(), ()> = msg_send![service, setValue:NSArray::from_vec(characteristics)
                                 forKey:NSString::from_str("characteristics")];

            let peripheral_manager = *self
                .peripheral_manager_delegate
                .get_ivar::<*mut Object>(PERIPHERAL_MANAGER_IVAR);

            let _: Result<(), ()> = msg_send![peripheral_manager, addService: service];
        }
    }
}

impl Default for PeripheralManager {
    fn default() -> Self {
        PeripheralManager::new()
    }
}

extern "C" fn init(delegate: &mut Object, _cmd: Sel) -> *mut Object {
    unsafe {
        let cls = class!(CBPeripheralManager);
        let mut obj: *mut Object = msg_send![cls, alloc];

        #[allow(clippy::cast_ptr_alignment)]
        let init_with_delegate = delegate as *mut Object as *mut *mut Object;

        let label = CString::new("CBqueue").unwrap();
        let queue = dispatch_queue_create(label.as_ptr(), DISPATCH_QUEUE_SERIAL);

        obj = msg_send![obj, initWithDelegate:init_with_delegate
                                        queue:queue];
        delegate.set_ivar::<*mut Object>(PERIPHERAL_MANAGER_IVAR, obj);

        delegate.set_ivar::<*mut Object>(POWERED_ON_IVAR, NO as *mut Object);

        delegate
    }
}