rustable 0.3.0

Another crate for using Bluetooth Low Energy with Bluez.
use async_std::channel::bounded;
use async_std::task::spawn;
use futures::future::{select, Either};
use std::collections::HashMap;

use super::*;
use crate::properties::{PropError, Properties};
use async_rustbus::RpcConn;

pub struct Descriptor {
    value: ValOrFn,
    uuid: UUID,
    flags: DescFlags,
    handle: u16,
}

impl Descriptor {
    pub fn new(uuid: UUID, flags: DescFlags) -> Self {
        Self {
            uuid,
            flags,
            handle: 0,
            value: ValOrFn::default(),
        }
    }
    pub fn set_value(&mut self, vf: ValOrFn) {
        self.value = vf;
    }
    pub fn set_handle(&mut self, handle: Option<NonZeroU16>) {
        self.handle = handle.map_or(0, |h| h.into());
    }
    pub fn uuid(&self) -> UUID {
        self.uuid
    }
    pub(super) fn start_worker(
        mut self,
        conn: &Arc<RpcConn>,
        path: &ObjectPath,
        filter: Option<Arc<str>>,
    ) -> Worker {
        let path = path.to_owned();
        let (sender, msg_recv) = bounded(8);
        let conn = conn.clone();
        let handle = spawn(async move {
            let call_recv = conn.get_call_recv(&*path).await.unwrap();
            let mut call_fut = call_recv.recv();
            let mut msg_fut = msg_recv.recv();
            loop {
                match select(msg_fut, call_fut).await {
                    Either::Left((msg, call_f)) => {
                        match msg? {
                            WorkerMsg::Unregister => break,
                            WorkerMsg::Update(vf, _) => {
                                self.value = vf;
                            }
                            WorkerMsg::Get(sender) => {
                                sender.send(self.value.to_value())?;
                            }
                            WorkerMsg::GetHandle(sender) => {
                                sender.send(NonZeroU16::new(self.handle).unwrap())?;
                            }
                            WorkerMsg::ObjMgr(sender) => {
                                sender.send((path.clone(), self.get_all_interfaces(&path)))?;
                            }
                            _ => unreachable!(),
                        }
                        call_fut = call_f;
                        msg_fut = msg_recv.recv();
                    }
                    Either::Right((call, msg_f)) => {
                        let call = call?;
                        let res = if is_msg_bluez(&call, filter.as_deref()) {
                            self.handle_call(&call)
                        } else {
                            call.dynheader.make_error_response("PermissionDenied", None)
                        };
                        conn.send_msg(&res).await?;
                        msg_fut = msg_f;
                        call_fut = call_recv.recv();
                    }
                }
            }
            Ok(WorkerJoin::Desc(self))
        });
        Worker { handle, sender }
    }
    fn handle_call(&mut self, call: &MarshalledMessage) -> MarshalledMessage {
        let interface = call.dynheader.interface.as_ref().unwrap();
        match &**interface {
            PROPS_IF => self.properties_call(call),
            INTRO_IF => self.handle_intro(call),
            BLUEZ_DES_IF => {
                let member = call.dynheader.member.as_ref().unwrap();
                match &**member {
                    "ReadValue" => {
                        if !(self.flags.read
                            || self.flags.secure_read
                            || self.flags.encrypt_read
                            || self.flags.encrypt_auth_read)
                        {
                            return call.dynheader.make_error_response("PermissionDenied", None);
                        }
                        let options: HashMap<&str, BluezOptions> = match call.body.parser().get() {
                            Ok(o) => o,
                            Err(_) => {
                                return call.dynheader.make_error_response("UnknownType", None);
                            }
                        };
                        let mut offset = 0;
                        if let Some(BluezOptions::U16(off)) = options.get("offset") {
                            offset = *off as usize;
                        }
                        let att_val = self.value.to_value();
                        let val = att_val.get(offset..).unwrap_or(&[]);
                        let mut reply = call.dynheader.make_response();
                        reply.body.push_param(val).unwrap();
                        reply
                    }
                    "WriteValue" => {
                        if !(self.flags.write
                            || self.flags.encrypt_write
                            || self.flags.encrypt_auth_write)
                        {
                            return call.dynheader.make_error_response("PermissionDenied", None);
                        }
                        let (mut att_val, options): (AttValue, HashMap<&str, BluezOptions>) =
                            match call.body.parser().get() {
                                Ok(o) => o,
                                Err(_) => {
                                    return call.dynheader.make_error_response("UnknownType", None);
                                }
                            };
                        let mut offset = 0;
                        if let Some(BluezOptions::U16(off)) = options.get("offset") {
                            offset = *off as usize;
                        }
                        if offset != 0 {
                            let mut old = self.value.to_value();
                            old.update(&att_val, offset);
                            att_val = old;
                        }
                        self.value = ValOrFn::Value(att_val);
                        call.dynheader.make_response()
                    }
                    _ => call.dynheader.make_error_response("UnknownMethod", None),
                }
            }
            _ => unreachable!(),
        }
    }
    fn handle_intro(&self, call: &MarshalledMessage) -> MarshalledMessage {
        let mut s = String::from(introspect::INTROSPECT_FMT_P1);
        s.push_str(introspect::PROP_STR);
        s.push_str(introspect::DESC_STR);
        s.push_str(introspect::INTROSPECT_FMT_P3);
        let mut reply = call.dynheader.make_response();
        reply.body.push_param(s).unwrap();
        reply
    }
}
impl Properties for Descriptor {
    const INTERFACES: &'static [(&'static str, &'static [&'static str])] = &[(
        BLUEZ_DES_IF,
        &[UUID_STR, HANDLE_STR, CHAR_STR, VAL_STR, FLAG_STR],
    )];
    fn get_inner(
        &mut self,
        path: &ObjectPath,
        interface: &str,
        prop: &str,
    ) -> Result<BluezOptions<'static, 'static>, PropError> {
        eprintln!("Descriptor::get_inner(): interface: {}", interface);
        if !matches!(interface, BLUEZ_DES_IF) {
            return Err(PropError::InterfaceNotFound);
        }
        match prop {
            UUID_STR => Ok(BluezOptions::OwnedStr(self.uuid.to_string())),
            HANDLE_STR => Ok(BluezOptions::U16(self.handle)),
            CHAR_STR => Ok(BluezOptions::OwnedPath(path.parent().unwrap().into())),
            VAL_STR => Ok(BluezOptions::OwnedBuf((&*self.value.to_value()).into())),
            FLAG_STR => Ok(BluezOptions::Flags(self.flags.to_strings())),
            _ => Err(PropError::PropertyNotFound),
        }
    }
    fn set_inner(
        &mut self,
        _p: &ObjectPath,
        interface: &str,
        prop: &str,
        val: BluezOptions,
    ) -> Result<(), PropError> {
        if !matches!(interface, BLUEZ_CHR_IF) {
            return Err(PropError::InterfaceNotFound);
        }
        match prop {
            HANDLE_STR => match val {
                BluezOptions::U16(h) => {
                    self.handle = h;
                    Ok(())
                }
                _ => Err(PropError::InvalidValue),
            },
            UUID_STR | CHAR_STR | VAL_STR | FLAG_STR => Err(PropError::PermissionDenied),
            _ => Err(PropError::PropertyNotFound),
        }
    }
}