| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use anyhow::{format_err, Error}; |
| use fidl_fuchsia_bluetooth::{Appearance, Uuid as FidlUuid}; |
| use fidl_fuchsia_bluetooth_avdtp_test::PeerControllerProxy; |
| use fidl_fuchsia_bluetooth_avrcp::{ |
| AvcPanelCommand, BatteryStatus, CustomAttributeValue, CustomPlayerApplicationSetting, |
| Equalizer, PlayStatus, PlaybackStatus, PlayerApplicationSettingAttributeId, |
| PlayerApplicationSettings, RepeatStatusMode, ScanMode, ShuffleMode, |
| }; |
| use fidl_fuchsia_bluetooth_gatt2::{ |
| AttributePermissions, Characteristic, Descriptor, ReadByTypeResult, SecurityRequirements, |
| ServiceHandle, ServiceInfo, ServiceKind, |
| }; |
| use fidl_fuchsia_bluetooth_le::{ |
| AdvertisingData, AdvertisingModeHint, AdvertisingParameters, ConnectionOptions, Filter, |
| ManufacturerData, ServiceData, |
| }; |
| use fidl_fuchsia_bluetooth_sys::Peer; |
| use fuchsia_bluetooth::types::Uuid; |
| use num_derive::FromPrimitive; |
| use serde::{Deserialize, Serialize}; |
| use serde_json::Value; |
| use std::collections::HashMap; |
| use std::str::FromStr; |
| |
| use crate::common_utils::common::macros::parse_arg; |
| |
| /// Handling different sessions. |
| /// Key is a generic id that is generated by the tool that is associated with a remote peer. |
| /// Value is the controller associated with the remote peer. |
| pub type PeerFactoryMap = HashMap<String, PeerControllerProxy>; |
| |
| /// BleScan result type |
| /// TODO(https://fxbug.dev/42168627): Add support for RemoteDevices when clone() is implemented |
| #[derive(Serialize, Clone, Debug)] |
| pub struct BleScanResponse { |
| pub id: String, |
| pub name: String, |
| pub connectable: bool, |
| } |
| |
| impl BleScanResponse { |
| pub fn new(id: String, name: String, connectable: bool) -> BleScanResponse { |
| BleScanResponse { id, name, connectable } |
| } |
| } |
| |
| /// BleAdvertise result type (only uuid) |
| /// TODO(https://fxbug.dev/42168627): Add support for AdvertisingData when clone() is implemented |
| #[derive(Serialize, Clone, Debug)] |
| pub struct BleAdvertiseResponse { |
| pub name: Option<String>, |
| } |
| |
| impl BleAdvertiseResponse { |
| pub fn new(name: Option<String>) -> BleAdvertiseResponse { |
| BleAdvertiseResponse { name } |
| } |
| } |
| |
| #[derive(Serialize, Deserialize, Clone, Debug, Default)] |
| pub struct SecurityRequirementsContainer { |
| pub encryption_required: bool, |
| pub authentication_required: bool, |
| pub authorization_required: bool, |
| } |
| |
| impl SecurityRequirementsContainer { |
| pub fn new(info: Option<SecurityRequirements>) -> SecurityRequirementsContainer { |
| match info { |
| Some(sec) => SecurityRequirementsContainer { |
| encryption_required: sec.encryption_required.unwrap_or(false), |
| authentication_required: sec.authentication_required.unwrap_or(false), |
| authorization_required: sec.authorization_required.unwrap_or(false), |
| }, |
| None => SecurityRequirementsContainer::default(), |
| } |
| } |
| } |
| |
| #[derive(Serialize, Deserialize, Clone, Debug)] |
| pub struct AttributePermissionsContainer { |
| pub read: SecurityRequirementsContainer, |
| pub write: SecurityRequirementsContainer, |
| pub update: SecurityRequirementsContainer, |
| } |
| |
| impl AttributePermissionsContainer { |
| pub fn new(info: Option<AttributePermissions>) -> Result<AttributePermissionsContainer, Error> { |
| match info { |
| Some(perm) => Ok(AttributePermissionsContainer { |
| read: SecurityRequirementsContainer::new(perm.read), |
| write: SecurityRequirementsContainer::new(perm.write), |
| update: SecurityRequirementsContainer::new(perm.update), |
| }), |
| None => return Err(format_err!("Unable to get information of AttributePermissions.")), |
| } |
| } |
| } |
| |
| // Discover Characteristic response to hold characteristic info |
| // as Characteristics are not serializable. |
| #[derive(Serialize, Deserialize, Clone, Debug)] |
| pub struct GattcDiscoverDescriptorResponse { |
| pub id: u64, |
| pub permissions: Option<AttributePermissionsContainer>, |
| pub uuid_type: String, |
| } |
| |
| impl GattcDiscoverDescriptorResponse { |
| pub fn new(info: Vec<Descriptor>) -> Vec<GattcDiscoverDescriptorResponse> { |
| let mut res = Vec::new(); |
| for v in info { |
| let copy = GattcDiscoverDescriptorResponse { |
| id: v.handle.unwrap().value, |
| permissions: match AttributePermissionsContainer::new(v.permissions) { |
| Ok(n) => Some(n), |
| Err(_) => None, |
| }, |
| uuid_type: Uuid::from(v.type_.unwrap()).to_string(), |
| }; |
| res.push(copy) |
| } |
| res |
| } |
| } |
| |
| // Discover Characteristic response to hold characteristic info |
| // as Characteristics are not serializable. |
| #[derive(Serialize, Deserialize, Clone, Debug)] |
| pub struct GattcDiscoverCharacteristicResponse { |
| pub id: u64, |
| pub properties: u32, |
| pub permissions: Option<AttributePermissionsContainer>, |
| pub uuid_type: String, |
| pub descriptors: Vec<GattcDiscoverDescriptorResponse>, |
| } |
| |
| impl GattcDiscoverCharacteristicResponse { |
| pub fn new(info: Vec<Characteristic>) -> Vec<GattcDiscoverCharacteristicResponse> { |
| let mut res = Vec::new(); |
| for v in info { |
| let copy = GattcDiscoverCharacteristicResponse { |
| id: v.handle.unwrap().value, |
| properties: v.properties.unwrap().bits().into(), |
| permissions: match AttributePermissionsContainer::new(v.permissions) { |
| Ok(n) => Some(n), |
| Err(_) => None, |
| }, |
| uuid_type: Uuid::from(v.type_.unwrap()).to_string(), |
| descriptors: { |
| match v.descriptors { |
| Some(d) => GattcDiscoverDescriptorResponse::new(d), |
| None => Vec::new(), |
| } |
| }, |
| }; |
| res.push(copy) |
| } |
| res |
| } |
| } |
| |
| /// BleConnectPeripheral response (aka ServiceInfo) |
| /// TODO(https://fxbug.dev/42168627): Add support for ServiceInfo when clone(), serialize(), derived |
| #[derive(Serialize, Deserialize, Clone, Debug)] |
| pub struct BleConnectPeripheralResponse { |
| pub id: u64, |
| pub primary: bool, |
| pub uuid_type: String, |
| } |
| |
| impl BleConnectPeripheralResponse { |
| pub fn new(info: Vec<ServiceInfo>) -> Vec<BleConnectPeripheralResponse> { |
| let mut res = Vec::new(); |
| for v in info { |
| let copy = BleConnectPeripheralResponse { |
| id: v.handle.unwrap().value, |
| primary: v.kind.unwrap() == ServiceKind::Primary, |
| uuid_type: Uuid::from(v.type_.unwrap()).to_string(), |
| }; |
| res.push(copy) |
| } |
| res |
| } |
| } |
| |
| #[derive(Clone, Debug, Serialize)] |
| pub struct SerializablePeer { |
| pub address: Option<[u8; 6]>, |
| pub appearance: Option<u32>, |
| pub device_class: Option<u32>, |
| pub id: Option<String>, |
| pub name: Option<String>, |
| pub connected: Option<bool>, |
| pub bonded: Option<bool>, |
| pub rssi: Option<i8>, |
| pub services: Option<Vec<[u8; 16]>>, |
| pub technology: Option<u32>, |
| pub tx_power: Option<i8>, |
| } |
| |
| impl From<&Peer> for SerializablePeer { |
| fn from(peer: &Peer) -> Self { |
| let services = match &peer.services { |
| Some(s) => { |
| let mut service_list = Vec::new(); |
| for item in s { |
| service_list.push(item.value); |
| } |
| Some(service_list) |
| } |
| None => None, |
| }; |
| SerializablePeer { |
| address: peer.address.map(|a| a.bytes), |
| appearance: peer.appearance.map(|a| a.into_primitive() as u32), |
| device_class: peer.device_class.map(|d| d.value), |
| id: peer.id.map(|i| i.value.to_string()), |
| name: peer.name.clone(), |
| connected: peer.connected, |
| bonded: peer.bonded, |
| rssi: peer.rssi, |
| services: services, |
| technology: peer.technology.map(|t| t as u32), |
| tx_power: peer.tx_power, |
| } |
| } |
| } |
| |
| #[derive(Clone, Debug, Serialize)] |
| pub struct SerializableReadByTypeResult { |
| pub id: Option<u64>, |
| pub value: Option<Vec<u8>>, |
| } |
| |
| impl SerializableReadByTypeResult { |
| pub fn new(result: ReadByTypeResult) -> Option<Self> { |
| if result.error.is_some() { |
| return None; |
| } |
| let id = result.handle.unwrap().value; |
| let value = result.value.unwrap().value.unwrap(); |
| Some(SerializableReadByTypeResult { id: Some(id), value: Some(value) }) |
| } |
| } |
| |
| #[derive(Clone, Debug, Serialize, Eq, Copy)] |
| pub struct CustomPlayStatus { |
| pub song_length: Option<u32>, |
| pub song_position: Option<u32>, |
| pub playback_status: Option<u8>, |
| } |
| |
| impl CustomPlayStatus { |
| pub fn new(status: &PlayStatus) -> Self { |
| let playback_status = match status.playback_status { |
| Some(p) => Some(p as u8), |
| None => None, |
| }; |
| CustomPlayStatus { |
| song_length: status.song_length, |
| song_position: status.song_position, |
| playback_status: playback_status, |
| } |
| } |
| } |
| |
| impl From<CustomPlayStatus> for PlayStatus { |
| fn from(status: CustomPlayStatus) -> Self { |
| let playback_status = match status.playback_status { |
| Some(0) => Some(PlaybackStatus::Stopped), |
| Some(1) => Some(PlaybackStatus::Playing), |
| Some(2) => Some(PlaybackStatus::Paused), |
| Some(3) => Some(PlaybackStatus::FwdSeek), |
| Some(4) => Some(PlaybackStatus::RevSeek), |
| Some(255) => Some(PlaybackStatus::Error), |
| None => None, |
| _ => panic!("Unknown playback status!"), |
| }; |
| PlayStatus { |
| song_length: status.song_length, |
| song_position: status.song_position, |
| playback_status: playback_status, |
| ..Default::default() |
| } |
| } |
| } |
| |
| impl From<PlayStatus> for CustomPlayStatus { |
| fn from(status: PlayStatus) -> Self { |
| CustomPlayStatus { |
| song_length: status.song_length, |
| song_position: status.song_position, |
| playback_status: match status.playback_status { |
| Some(p) => Some(p as u8), |
| None => None, |
| }, |
| } |
| } |
| } |
| |
| impl PartialEq for CustomPlayStatus { |
| fn eq(&self, other: &CustomPlayStatus) -> bool { |
| self.song_length == other.song_length |
| && self.song_position == other.song_position |
| && self.playback_status == other.playback_status |
| } |
| } |
| #[derive(Copy, Clone, Debug, FromPrimitive, Serialize, Deserialize)] |
| #[repr(u8)] |
| pub enum CustomAvcPanelCommand { |
| Select = 0, |
| Up = 1, |
| Down = 2, |
| Left = 3, |
| Right = 4, |
| RootMenu = 9, |
| ContentsMenu = 11, |
| FavoriteMenu = 12, |
| Exit = 13, |
| OnDemandMenu = 14, |
| AppsMenu = 15, |
| Key0 = 32, |
| Key1 = 33, |
| Key2 = 34, |
| Key3 = 35, |
| Key4 = 36, |
| Key5 = 37, |
| Key6 = 38, |
| Key7 = 39, |
| Key8 = 40, |
| Key9 = 41, |
| Dot = 42, |
| Enter = 43, |
| ChannelUp = 48, |
| ChannelDown = 49, |
| ChannelPrevious = 50, |
| InputSelect = 52, |
| Info = 53, |
| Help = 54, |
| PageUp = 55, |
| PageDown = 56, |
| Lock = 58, |
| Power = 64, |
| VolumeUp = 65, |
| VolumeDown = 66, |
| Mute = 67, |
| Play = 68, |
| Stop = 69, |
| Pause = 70, |
| Record = 71, |
| Rewind = 72, |
| FastForward = 73, |
| Eject = 74, |
| Forward = 75, |
| Backward = 76, |
| List = 77, |
| F1 = 113, |
| F2 = 114, |
| F3 = 115, |
| F4 = 116, |
| F5 = 117, |
| F6 = 118, |
| F7 = 119, |
| F8 = 120, |
| F9 = 121, |
| Red = 122, |
| Green = 123, |
| Blue = 124, |
| Yellow = 125, |
| } |
| |
| impl From<CustomAvcPanelCommand> for AvcPanelCommand { |
| fn from(command: CustomAvcPanelCommand) -> Self { |
| match command { |
| CustomAvcPanelCommand::Select => AvcPanelCommand::Select, |
| CustomAvcPanelCommand::Up => AvcPanelCommand::Up, |
| CustomAvcPanelCommand::Down => AvcPanelCommand::Down, |
| CustomAvcPanelCommand::Left => AvcPanelCommand::Left, |
| CustomAvcPanelCommand::Right => AvcPanelCommand::Right, |
| CustomAvcPanelCommand::RootMenu => AvcPanelCommand::RootMenu, |
| CustomAvcPanelCommand::ContentsMenu => AvcPanelCommand::ContentsMenu, |
| CustomAvcPanelCommand::FavoriteMenu => AvcPanelCommand::FavoriteMenu, |
| CustomAvcPanelCommand::Exit => AvcPanelCommand::Exit, |
| CustomAvcPanelCommand::OnDemandMenu => AvcPanelCommand::OnDemandMenu, |
| CustomAvcPanelCommand::AppsMenu => AvcPanelCommand::AppsMenu, |
| CustomAvcPanelCommand::Key0 => AvcPanelCommand::Key0, |
| CustomAvcPanelCommand::Key1 => AvcPanelCommand::Key1, |
| CustomAvcPanelCommand::Key2 => AvcPanelCommand::Key2, |
| CustomAvcPanelCommand::Key3 => AvcPanelCommand::Key3, |
| CustomAvcPanelCommand::Key4 => AvcPanelCommand::Key4, |
| CustomAvcPanelCommand::Key5 => AvcPanelCommand::Key5, |
| CustomAvcPanelCommand::Key6 => AvcPanelCommand::Key6, |
| CustomAvcPanelCommand::Key7 => AvcPanelCommand::Key7, |
| CustomAvcPanelCommand::Key8 => AvcPanelCommand::Key8, |
| CustomAvcPanelCommand::Key9 => AvcPanelCommand::Key9, |
| CustomAvcPanelCommand::Dot => AvcPanelCommand::Dot, |
| CustomAvcPanelCommand::Enter => AvcPanelCommand::Enter, |
| CustomAvcPanelCommand::ChannelUp => AvcPanelCommand::ChannelUp, |
| CustomAvcPanelCommand::ChannelDown => AvcPanelCommand::ChannelDown, |
| CustomAvcPanelCommand::ChannelPrevious => AvcPanelCommand::ChannelPrevious, |
| CustomAvcPanelCommand::InputSelect => AvcPanelCommand::InputSelect, |
| CustomAvcPanelCommand::Info => AvcPanelCommand::Info, |
| CustomAvcPanelCommand::Help => AvcPanelCommand::Help, |
| CustomAvcPanelCommand::PageUp => AvcPanelCommand::PageUp, |
| CustomAvcPanelCommand::PageDown => AvcPanelCommand::PageDown, |
| CustomAvcPanelCommand::Lock => AvcPanelCommand::Lock, |
| CustomAvcPanelCommand::Power => AvcPanelCommand::Power, |
| CustomAvcPanelCommand::VolumeUp => AvcPanelCommand::VolumeUp, |
| CustomAvcPanelCommand::VolumeDown => AvcPanelCommand::VolumeDown, |
| CustomAvcPanelCommand::Mute => AvcPanelCommand::Mute, |
| CustomAvcPanelCommand::Play => AvcPanelCommand::Play, |
| CustomAvcPanelCommand::Stop => AvcPanelCommand::Stop, |
| CustomAvcPanelCommand::Pause => AvcPanelCommand::Pause, |
| CustomAvcPanelCommand::Record => AvcPanelCommand::Record, |
| CustomAvcPanelCommand::Rewind => AvcPanelCommand::Rewind, |
| CustomAvcPanelCommand::FastForward => AvcPanelCommand::FastForward, |
| CustomAvcPanelCommand::Eject => AvcPanelCommand::Eject, |
| CustomAvcPanelCommand::Forward => AvcPanelCommand::Forward, |
| CustomAvcPanelCommand::Backward => AvcPanelCommand::Backward, |
| CustomAvcPanelCommand::List => AvcPanelCommand::List, |
| CustomAvcPanelCommand::F1 => AvcPanelCommand::F1, |
| CustomAvcPanelCommand::F2 => AvcPanelCommand::F2, |
| CustomAvcPanelCommand::F3 => AvcPanelCommand::F3, |
| CustomAvcPanelCommand::F4 => AvcPanelCommand::F4, |
| CustomAvcPanelCommand::F5 => AvcPanelCommand::F5, |
| CustomAvcPanelCommand::F6 => AvcPanelCommand::F6, |
| CustomAvcPanelCommand::F7 => AvcPanelCommand::F7, |
| CustomAvcPanelCommand::F8 => AvcPanelCommand::F8, |
| CustomAvcPanelCommand::F9 => AvcPanelCommand::F9, |
| CustomAvcPanelCommand::Red => AvcPanelCommand::Red, |
| CustomAvcPanelCommand::Green => AvcPanelCommand::Green, |
| CustomAvcPanelCommand::Blue => AvcPanelCommand::Blue, |
| CustomAvcPanelCommand::Yellow => AvcPanelCommand::Yellow, |
| } |
| } |
| } |
| |
| impl From<String> for CustomAvcPanelCommand { |
| fn from(command: String) -> Self { |
| match command.as_str() { |
| "Select" => CustomAvcPanelCommand::Select, |
| "Up" => CustomAvcPanelCommand::Up, |
| "Down" => CustomAvcPanelCommand::Down, |
| "Left" => CustomAvcPanelCommand::Left, |
| "Right" => CustomAvcPanelCommand::Right, |
| "RootMenu" => CustomAvcPanelCommand::RootMenu, |
| "ContentsMenu" => CustomAvcPanelCommand::ContentsMenu, |
| "FavoriteMenu" => CustomAvcPanelCommand::FavoriteMenu, |
| "Exit" => CustomAvcPanelCommand::Exit, |
| "OnDemandMenu" => CustomAvcPanelCommand::OnDemandMenu, |
| "AppsMenu" => CustomAvcPanelCommand::AppsMenu, |
| "Key0" => CustomAvcPanelCommand::Key0, |
| "Key1" => CustomAvcPanelCommand::Key1, |
| "Key2" => CustomAvcPanelCommand::Key2, |
| "Key3" => CustomAvcPanelCommand::Key3, |
| "Key4" => CustomAvcPanelCommand::Key4, |
| "Key5" => CustomAvcPanelCommand::Key5, |
| "Key6" => CustomAvcPanelCommand::Key6, |
| "Key7" => CustomAvcPanelCommand::Key7, |
| "Key8" => CustomAvcPanelCommand::Key8, |
| "Key9" => CustomAvcPanelCommand::Key9, |
| "Dot" => CustomAvcPanelCommand::Dot, |
| "Enter" => CustomAvcPanelCommand::Enter, |
| "ChannelUp" => CustomAvcPanelCommand::ChannelUp, |
| "ChannelDown" => CustomAvcPanelCommand::ChannelDown, |
| "ChannelPrevious" => CustomAvcPanelCommand::ChannelPrevious, |
| "InputSelect" => CustomAvcPanelCommand::InputSelect, |
| "Info" => CustomAvcPanelCommand::Info, |
| "Help" => CustomAvcPanelCommand::Help, |
| "PageUp" => CustomAvcPanelCommand::PageUp, |
| "PageDown" => CustomAvcPanelCommand::PageDown, |
| "Lock" => CustomAvcPanelCommand::Lock, |
| "Power" => CustomAvcPanelCommand::Power, |
| "VolumeUp" => CustomAvcPanelCommand::VolumeUp, |
| "VolumeDown" => CustomAvcPanelCommand::VolumeDown, |
| "Mute" => CustomAvcPanelCommand::Mute, |
| "Play" => CustomAvcPanelCommand::Play, |
| "Stop" => CustomAvcPanelCommand::Stop, |
| "Pause" => CustomAvcPanelCommand::Pause, |
| "Record" => CustomAvcPanelCommand::Record, |
| "Rewind" => CustomAvcPanelCommand::Rewind, |
| "FastForward" => CustomAvcPanelCommand::FastForward, |
| "Eject" => CustomAvcPanelCommand::Eject, |
| "Forward" => CustomAvcPanelCommand::Forward, |
| "Backward" => CustomAvcPanelCommand::Backward, |
| "List" => CustomAvcPanelCommand::List, |
| "F1" => CustomAvcPanelCommand::F1, |
| "F2" => CustomAvcPanelCommand::F2, |
| "F3" => CustomAvcPanelCommand::F3, |
| "F4" => CustomAvcPanelCommand::F4, |
| "F5" => CustomAvcPanelCommand::F5, |
| "F6" => CustomAvcPanelCommand::F6, |
| "F7" => CustomAvcPanelCommand::F7, |
| "F8" => CustomAvcPanelCommand::F8, |
| "F9" => CustomAvcPanelCommand::F9, |
| "Red" => CustomAvcPanelCommand::Red, |
| "Green" => CustomAvcPanelCommand::Green, |
| "Blue" => CustomAvcPanelCommand::Blue, |
| "Yellow" => CustomAvcPanelCommand::Yellow, |
| _invalid => panic!("Invalid CustomAvcPanelCommand command:{:?}", _invalid), |
| } |
| } |
| } |
| #[derive(Deserialize)] |
| pub struct AbsoluteVolumeCommand { |
| pub absolute_volume: u8, |
| } |
| |
| #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] |
| pub struct CustomPlayerApplicationSettingsAttributeIds { |
| pub attribute_ids: Option<Vec<u8>>, |
| } |
| |
| impl CustomPlayerApplicationSettingsAttributeIds { |
| pub fn to_vec(&self) -> Vec<PlayerApplicationSettingAttributeId> { |
| match &self.attribute_ids { |
| Some(vec) => vec |
| .into_iter() |
| .map(|u8| match u8 { |
| 1 => PlayerApplicationSettingAttributeId::Equalizer, |
| 2 => PlayerApplicationSettingAttributeId::RepeatStatusMode, |
| 3 => PlayerApplicationSettingAttributeId::ShuffleMode, |
| 4 => PlayerApplicationSettingAttributeId::ScanMode, |
| invalid => panic!( |
| "Invalid value for PlayerApplicationSettingAttributeId {:?}", |
| invalid |
| ), |
| }) |
| .collect(), |
| None => Vec::new(), |
| } |
| } |
| } |
| #[derive(Clone, Debug, Serialize)] |
| pub enum CustomPlayerApplicationSettingsAttributeId { |
| Equalizer = 1, |
| RepeatStatusMode = 2, |
| ShuffleMode = 3, |
| ScanMode = 4, |
| } |
| |
| impl From<u8> for CustomPlayerApplicationSettingsAttributeId { |
| fn from(attribute_id: u8) -> CustomPlayerApplicationSettingsAttributeId { |
| match attribute_id { |
| 1 => CustomPlayerApplicationSettingsAttributeId::Equalizer, |
| 2 => CustomPlayerApplicationSettingsAttributeId::RepeatStatusMode, |
| 3 => CustomPlayerApplicationSettingsAttributeId::ShuffleMode, |
| 4 => CustomPlayerApplicationSettingsAttributeId::ScanMode, |
| _ => panic!("Invalid attribute id: {:?}", attribute_id), |
| } |
| } |
| } |
| |
| #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] |
| pub struct CustomPlayerApplicationSettings { |
| pub equalizer: Option<CustomEqualizer>, |
| pub repeat_status_mode: Option<CustomRepeatStatusMode>, |
| pub shuffle_mode: Option<CustomShuffleMode>, |
| pub scan_mode: Option<CustomScanMode>, |
| pub custom_settings: Option<Vec<CustomCustomPlayerApplicationSetting>>, |
| } |
| |
| #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] |
| pub struct CustomCustomPlayerApplicationSetting { |
| pub attribute_id: Option<u8>, |
| pub attribute_name: Option<String>, |
| pub possible_values: Option<Vec<CustomCustomAttributeValue>>, |
| pub current_value: Option<u8>, |
| } |
| |
| #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] |
| pub struct CustomCustomAttributeValue { |
| pub description: String, |
| pub value: u8, |
| } |
| #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Copy)] |
| pub enum CustomEqualizer { |
| Off = 1, |
| On = 2, |
| } |
| |
| #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Copy)] |
| pub enum CustomRepeatStatusMode { |
| Off = 1, |
| SingleTrackRepeat = 2, |
| AllTrackRepeat = 3, |
| GroupRepeat = 4, |
| } |
| #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Copy)] |
| pub enum CustomShuffleMode { |
| Off = 1, |
| AllTrackShuffle = 2, |
| GroupShuffle = 3, |
| } |
| #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Copy)] |
| pub enum CustomScanMode { |
| Off = 1, |
| AllTrackScan = 2, |
| GroupScan = 3, |
| } |
| #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Copy)] |
| pub enum CustomBatteryStatus { |
| Normal = 0, |
| Warning = 1, |
| Critical = 2, |
| External = 3, |
| FullCharge = 4, |
| Reserved = 5, |
| } |
| |
| impl From<PlayerApplicationSettings> for CustomPlayerApplicationSettings { |
| fn from(settings: PlayerApplicationSettings) -> CustomPlayerApplicationSettings { |
| CustomPlayerApplicationSettings { |
| equalizer: match settings.equalizer { |
| Some(equalizer) => match equalizer { |
| Equalizer::Off => Some(CustomEqualizer::Off), |
| Equalizer::On => Some(CustomEqualizer::On), |
| }, |
| None => None, |
| }, |
| repeat_status_mode: match settings.repeat_status_mode { |
| Some(repeat_status_mode) => match repeat_status_mode { |
| RepeatStatusMode::Off => Some(CustomRepeatStatusMode::Off), |
| RepeatStatusMode::SingleTrackRepeat => { |
| Some(CustomRepeatStatusMode::SingleTrackRepeat) |
| } |
| RepeatStatusMode::AllTrackRepeat => { |
| Some(CustomRepeatStatusMode::AllTrackRepeat) |
| } |
| RepeatStatusMode::GroupRepeat => Some(CustomRepeatStatusMode::GroupRepeat), |
| }, |
| None => None, |
| }, |
| shuffle_mode: match settings.shuffle_mode { |
| Some(shuffle_mode) => match shuffle_mode { |
| ShuffleMode::Off => Some(CustomShuffleMode::Off), |
| ShuffleMode::AllTrackShuffle => Some(CustomShuffleMode::AllTrackShuffle), |
| ShuffleMode::GroupShuffle => Some(CustomShuffleMode::GroupShuffle), |
| }, |
| None => None, |
| }, |
| scan_mode: match settings.scan_mode { |
| Some(scan_mode) => match scan_mode { |
| ScanMode::Off => Some(CustomScanMode::Off), |
| ScanMode::AllTrackScan => Some(CustomScanMode::AllTrackScan), |
| ScanMode::GroupScan => Some(CustomScanMode::GroupScan), |
| }, |
| None => None, |
| }, |
| custom_settings: match settings.custom_settings { |
| Some(custom_settings_vec) => Some( |
| custom_settings_vec |
| .into_iter() |
| .map(|custom_settings| CustomCustomPlayerApplicationSetting { |
| attribute_id: custom_settings.attribute_id, |
| attribute_name: custom_settings.attribute_name, |
| possible_values: match custom_settings.possible_values { |
| Some(possible_values) => Some( |
| possible_values |
| .into_iter() |
| .map(|possible_value| possible_value.into()) |
| .collect(), |
| ), |
| None => None, |
| }, |
| current_value: custom_settings.current_value, |
| }) |
| .collect(), |
| ), |
| None => None, |
| }, |
| } |
| } |
| } |
| |
| impl From<CustomPlayerApplicationSettings> for PlayerApplicationSettings { |
| fn from(settings: CustomPlayerApplicationSettings) -> PlayerApplicationSettings { |
| PlayerApplicationSettings { |
| equalizer: match settings.equalizer { |
| Some(equalizer) => match equalizer { |
| CustomEqualizer::Off => Some(Equalizer::Off), |
| CustomEqualizer::On => Some(Equalizer::On), |
| }, |
| None => None, |
| }, |
| repeat_status_mode: match settings.repeat_status_mode { |
| Some(repeat_status_mode) => match repeat_status_mode { |
| CustomRepeatStatusMode::Off => Some(RepeatStatusMode::Off), |
| CustomRepeatStatusMode::SingleTrackRepeat => { |
| Some(RepeatStatusMode::SingleTrackRepeat) |
| } |
| CustomRepeatStatusMode::AllTrackRepeat => { |
| Some(RepeatStatusMode::AllTrackRepeat) |
| } |
| CustomRepeatStatusMode::GroupRepeat => Some(RepeatStatusMode::GroupRepeat), |
| }, |
| None => None, |
| }, |
| shuffle_mode: match settings.shuffle_mode { |
| Some(shuffle_mode) => match shuffle_mode { |
| CustomShuffleMode::Off => Some(ShuffleMode::Off), |
| CustomShuffleMode::AllTrackShuffle => Some(ShuffleMode::AllTrackShuffle), |
| CustomShuffleMode::GroupShuffle => Some(ShuffleMode::GroupShuffle), |
| }, |
| None => None, |
| }, |
| scan_mode: match settings.scan_mode { |
| Some(scan_mode) => match scan_mode { |
| CustomScanMode::Off => Some(ScanMode::Off), |
| CustomScanMode::AllTrackScan => Some(ScanMode::AllTrackScan), |
| CustomScanMode::GroupScan => Some(ScanMode::GroupScan), |
| }, |
| None => None, |
| }, |
| custom_settings: match settings.custom_settings { |
| Some(custom_settings_vec) => Some( |
| custom_settings_vec |
| .into_iter() |
| .map(|custom_settings| CustomPlayerApplicationSetting { |
| attribute_id: custom_settings.attribute_id, |
| attribute_name: custom_settings.attribute_name, |
| possible_values: match custom_settings.possible_values { |
| Some(possible_values) => Some( |
| possible_values |
| .into_iter() |
| .map(|possible_value| possible_value.into()) |
| .collect(), |
| ), |
| None => None, |
| }, |
| current_value: custom_settings.current_value, |
| ..Default::default() |
| }) |
| .collect(), |
| ), |
| None => None, |
| }, |
| ..Default::default() |
| } |
| } |
| } |
| |
| impl From<CustomCustomAttributeValue> for CustomAttributeValue { |
| fn from(attribute_value: CustomCustomAttributeValue) -> CustomAttributeValue { |
| CustomAttributeValue { |
| description: attribute_value.description, |
| value: attribute_value.value, |
| } |
| } |
| } |
| |
| impl From<CustomAttributeValue> for CustomCustomAttributeValue { |
| fn from(attribute_value: CustomAttributeValue) -> CustomCustomAttributeValue { |
| CustomCustomAttributeValue { |
| description: attribute_value.description, |
| value: attribute_value.value, |
| } |
| } |
| } |
| |
| impl From<BatteryStatus> for CustomBatteryStatus { |
| fn from(status: BatteryStatus) -> Self { |
| match status { |
| BatteryStatus::Normal => CustomBatteryStatus::Normal, |
| BatteryStatus::Warning => CustomBatteryStatus::Warning, |
| BatteryStatus::Critical => CustomBatteryStatus::Critical, |
| BatteryStatus::External => CustomBatteryStatus::External, |
| BatteryStatus::FullCharge => CustomBatteryStatus::FullCharge, |
| BatteryStatus::Reserved => CustomBatteryStatus::Reserved, |
| } |
| } |
| } |
| |
| impl From<CustomBatteryStatus> for BatteryStatus { |
| fn from(status: CustomBatteryStatus) -> Self { |
| match status { |
| CustomBatteryStatus::Normal => BatteryStatus::Normal, |
| CustomBatteryStatus::Warning => BatteryStatus::Warning, |
| CustomBatteryStatus::Critical => BatteryStatus::Critical, |
| CustomBatteryStatus::External => BatteryStatus::External, |
| CustomBatteryStatus::FullCharge => BatteryStatus::FullCharge, |
| CustomBatteryStatus::Reserved => BatteryStatus::Reserved, |
| } |
| } |
| } |
| |
| #[derive(Clone, Debug, Serialize, Deserialize)] |
| pub struct CustomNotificationsFilter { |
| pub notifications: u32, |
| pub position_change_interval: Option<u32>, |
| } |
| |
| pub struct FacadeArg { |
| value: Value, |
| } |
| |
| impl FacadeArg { |
| pub fn new(value: Value) -> Self { |
| FacadeArg { value } |
| } |
| } |
| |
| /// Deserializes a serde json object according to the following schema |
| /// |
| /// { |
| /// 'advertising_data': { |
| /// 'name': Some(String), |
| /// 'appearance': Some(u64), |
| /// 'service_data': { |
| /// 'uuid': ['fb', '34', '9b', '5f', '80', '00', '00', '80', '00', '10', '00', '00', '01', '18', '00', '00']. |
| /// 'data': "1" |
| /// }, |
| /// 'service_uuids': [ |
| /// ['fb', '34', '9b', '5f', '80', '00', '00', '80', '00', '10', '00', '00', '01', '18', '00', '00']. |
| /// ['fb', '34', '9b', '5f', '80', '00', '00', '80', '00', '10', '00', '00', '00', '18', '00', '00'] |
| /// ], |
| /// 'manufacturer_data': { |
| /// 'id': 10, |
| /// 'data' |
| /// }, |
| /// 'uris': Some(['telnet://192.0.2.16:80/']), |
| /// 'tx_power_level': Some(1), |
| /// |
| /// } |
| /// |
| /// } |
| /// |
| /// Note: A human readable uuid is represented as a list of bytes: |
| /// Example Human Readable UUID: '00001801-0000-1000-8000-00805f9b34fb' |
| /// Actual input: |
| /// ['fb', '34', '9b', '5f', '80', '00', '00', '80', '00', '10', '00', '00', '01', '18', '00', '00'] |
| impl TryInto<AdvertisingData> for FacadeArg { |
| type Error = Error; |
| fn try_into(self) -> Result<AdvertisingData, Self::Error> { |
| /// Parse json input UUID and return Bluetooth UUID format. |
| /// |
| /// # Arguments |
| /// * `json_uuid`: The JSON UUID in the form of a list of bytes. |
| fn parse_uuid(json_uuid: &Value) -> Result<FidlUuid, Error> { |
| let mut byte_list = vec![]; |
| |
| for byte_string in json_uuid.as_array().unwrap() { |
| let raw_value = match i64::from_str_radix(byte_string.as_str().unwrap(), 16) { |
| Ok(v) => v as u8, |
| Err(e) => bail!("Failed to convert raw value with: {:?}", e), |
| }; |
| byte_list.push(raw_value); |
| } |
| Ok(FidlUuid { |
| value: byte_list.as_slice().try_into().expect("Failed to set UUID value."), |
| }) |
| } |
| |
| /// Parse a list of service uuids from a json input list. |
| /// |
| /// # Arguments |
| /// * `json_service_uuids`: The JSON UUIDs in the form of lists of list of bytes. |
| fn parse_service_uuids(json_service_uuids: &Vec<Value>) -> Result<Vec<FidlUuid>, Error> { |
| let mut uuid_list = Vec::new(); |
| |
| for raw_uuid_list in json_service_uuids { |
| uuid_list.push(parse_uuid(raw_uuid_list)?); |
| } |
| Ok(uuid_list) |
| } |
| |
| /// Parse the json input service data into a list of ServiceData |
| /// |
| /// # Arguments |
| /// * `json_service_data`: The JSON representation of ServiceData to parse. |
| fn parse_service_data(json_service_data: &Vec<Value>) -> Result<Vec<ServiceData>, Error> { |
| let mut manufacturer_data_list = Vec::new(); |
| |
| for raw_service_data in json_service_data { |
| let uuid = match raw_service_data.get("uuid") { |
| Some(v) => parse_uuid(v)?, |
| None => bail!("Missing Service data info 'uuid'."), |
| }; |
| let data = match raw_service_data.get("data") { |
| Some(d) => d.to_string().into_bytes(), |
| None => bail!("Missing Service data info 'data'."), |
| }; |
| manufacturer_data_list.push(ServiceData { uuid, data }); |
| } |
| Ok(manufacturer_data_list) |
| } |
| |
| /// Parse the json input manufacturer data into a list of ManufacturerData |
| /// |
| /// # Arguments |
| /// * `json_manufacturer_data`: The JSON representation of ManufacturerData to parse. |
| fn parse_manufacturer_data( |
| json_manufacturer_data: &Vec<Value>, |
| ) -> Result<Vec<ManufacturerData>, Error> { |
| let mut manufacturer_data_list = Vec::new(); |
| |
| for raw_manufacturer_data in json_manufacturer_data { |
| let company_id = match raw_manufacturer_data.get("id") { |
| Some(v) => match v.as_u64() { |
| Some(c) => c as u16, |
| None => bail!("Company id not a valid value."), |
| }, |
| None => bail!("Missing Manufacturer info 'id'."), |
| }; |
| let data = match raw_manufacturer_data.get("data") { |
| Some(d) => d.to_string().into_bytes(), |
| None => bail!("Missing Manufacturer info 'data'."), |
| }; |
| manufacturer_data_list.push(ManufacturerData { company_id, data }); |
| } |
| Ok(manufacturer_data_list) |
| } |
| |
| let name: Option<String> = self.value["name"].as_str().map(String::from); |
| let service_uuids = match self.value.get("service_uuids") { |
| Some(v) => { |
| if v.is_null() { |
| None |
| } else { |
| match v.clone().as_array() { |
| Some(list) => Some(parse_service_uuids(list)?), |
| None => bail!("Attribute 'service_uuids' is not a parseable list."), |
| } |
| } |
| } |
| None => None, |
| }; |
| |
| let appearance = match self.value.get("appearance") { |
| Some(v) => { |
| if v.is_null() { |
| None |
| } else { |
| match v.as_u64() { |
| Some(c) => Appearance::from_primitive(c as u16), |
| None => None, |
| } |
| } |
| } |
| None => bail!("Value 'appearance' missing."), |
| }; |
| |
| let include_tx_power_level = |
| self.value.get("tx_power_level").and_then(|v| Some(!v.is_null())); |
| |
| let service_data = match self.value.get("service_data") { |
| Some(raw) => { |
| if raw.is_null() { |
| None |
| } else { |
| match raw.as_array() { |
| Some(list) => Some(parse_service_data(list)?), |
| None => None, |
| } |
| } |
| } |
| None => bail!("Value 'service_data' missing."), |
| }; |
| |
| let manufacturer_data = match self.value.get("manufacturer_data") { |
| Some(raw) => { |
| if raw.is_null() { |
| None |
| } else { |
| match raw.as_array() { |
| Some(list) => Some(parse_manufacturer_data(list)?), |
| None => None, |
| } |
| } |
| } |
| None => bail!("Value 'manufacturer_data' missing."), |
| }; |
| |
| let uris = match self.value.get("uris") { |
| Some(raw) => { |
| if raw.is_null() { |
| None |
| } else { |
| match raw.as_array() { |
| Some(list) => { |
| let mut uri_list = Vec::new(); |
| for item in list { |
| match item.as_str() { |
| Some(i) => uri_list.push(String::from(i)), |
| None => bail!("Expected URI string"), |
| } |
| } |
| Some(uri_list) |
| } |
| None => None, |
| } |
| } |
| } |
| None => bail!("Value 'uris' missing."), |
| }; |
| |
| Ok(AdvertisingData { |
| name, |
| appearance, |
| service_uuids, |
| service_data, |
| manufacturer_data, |
| uris: uris, |
| include_tx_power_level, |
| ..Default::default() |
| }) |
| } |
| } |
| |
| impl TryInto<AdvertisingParameters> for FacadeArg { |
| type Error = Error; |
| fn try_into(self) -> Result<AdvertisingParameters, Self::Error> { |
| let advertising_data = match self.value.get("advertising_data") { |
| Some(adr) => Some(FacadeArg::new(adr.clone()).try_into()?), |
| None => bail!("Value 'advertising_data' missing."), |
| }; |
| |
| let scan_response = match self.value.get("scan_response") { |
| Some(scn) => { |
| if scn.is_null() { |
| None |
| } else { |
| Some(FacadeArg::new(scn.clone()).try_into()?) |
| } |
| } |
| None => None, |
| }; |
| |
| let conn_raw = self.value.get("connectable").ok_or(format_err!("Connectable missing"))?; |
| |
| let connectable: bool = conn_raw.as_bool().unwrap_or(false); |
| |
| let conn_opts = if connectable { |
| Some(ConnectionOptions { |
| bondable_mode: Some(true), |
| service_filter: None, |
| ..Default::default() |
| }) |
| } else { |
| None |
| }; |
| |
| Ok(AdvertisingParameters { |
| data: advertising_data, |
| scan_response: scan_response, |
| mode_hint: Some(AdvertisingModeHint::VeryFast), |
| connection_options: conn_opts, |
| ..Default::default() |
| }) |
| } |
| } |
| |
| impl TryInto<Filter> for FacadeArg { |
| type Error = Error; |
| fn try_into(self) -> Result<Filter, Self::Error> { |
| let value = self.value.get("filter").ok_or(format_err!("Scan filter missing."))?.clone(); |
| let name = value["name_substring"].as_str().map(String::from); |
| // For now, no scan profile, so default to empty Filter |
| Ok(Filter { name, ..Default::default() }) |
| } |
| } |
| |
| impl TryInto<ServiceInfo> for FacadeArg { |
| type Error = Error; |
| fn try_into(self) -> Result<ServiceInfo, Self::Error> { |
| let value = self.value.clone(); |
| let id = parse_arg!(value, as_u64, "id")?; |
| let handle = ServiceHandle { value: id }; |
| let primary = parse_arg!(value, as_bool, "primary")?; |
| let kind = if primary { ServiceKind::Primary } else { ServiceKind::Secondary }; |
| let type_ = parse_arg!(value, as_str, "type")?.to_string(); |
| let type_: FidlUuid = |
| Uuid::from_str(&type_).map_err(|_| format_err!("Invalid type"))?.into(); |
| |
| Ok(ServiceInfo { |
| handle: Some(handle), |
| kind: Some(kind), |
| type_: Some(type_), |
| // TODO(https://fxbug.dev/42169516): Add support for GATT characteristics and includes |
| ..Default::default() |
| }) |
| } |
| } |