From f2630bbb255dab230348dff8cedfcef40787e57e Mon Sep 17 00:00:00 2001 From: Romeo Disca Date: Sat, 8 Aug 2020 04:24:43 +0200 Subject: chore: bring everything together --- Cargo.lock | 38 +++ Cargo.toml | 4 +- src/client.rs | 66 +++-- src/commands.rs | 99 ------- src/commands/mod.rs | 76 ++++++ src/commands/stream_mapper.rs | 192 ++++++++++++++ src/enums.rs | 0 src/events.rs | 169 ------------ src/events/mod.rs | 162 ++++++++++++ src/events/stream_mapper.rs | 581 ++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 10 +- 11 files changed, 1103 insertions(+), 294 deletions(-) delete mode 100755 src/commands.rs create mode 100644 src/commands/mod.rs create mode 100644 src/commands/stream_mapper.rs mode change 100755 => 100644 src/enums.rs delete mode 100644 src/events.rs create mode 100644 src/events/mod.rs create mode 100644 src/events/stream_mapper.rs diff --git a/Cargo.lock b/Cargo.lock index 9513666..64df848 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,14 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" +dependencies = [ + "memchr", +] + [[package]] name = "arc-swap" version = "0.4.7" @@ -264,6 +273,24 @@ version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +[[package]] +name = "regex" +version = "1.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" + [[package]] name = "serde" version = "1.0.114" @@ -284,7 +311,9 @@ dependencies = [ name = "simpleclient" version = "0.1.0" dependencies = [ + "bytes", "num_enum", + "regex", "tokio", ] @@ -317,6 +346,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + [[package]] name = "tokio" version = "0.2.22" diff --git a/Cargo.toml b/Cargo.toml index de433c6..cfacbb8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +bytes = "0.5" num_enum = "0.4.2" -tokio = { version = "0.2", features = ["full"] } \ No newline at end of file +regex = "1" +tokio = { version = "0.2", features = ["full"] } diff --git a/src/client.rs b/src/client.rs index b6b681a..320ae86 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,22 +1,31 @@ -use std::sync::Arc; - use tokio::io::*; + +use bytes::BufMut; use tokio::net::TcpStream; -use super::events::*; +use std::net::Shutdown; +use std::sync::{Arc, Mutex, MutexGuard}; +use std::time::Duration; + +use super::events::Event; +use super::events::stream_mapper::*; +use super::commands::stream_mapper::CommandToByteMapper; use super::commands::Command; -pub fn event_handler(f: F) -> Box - where F: FnMut(Event) + 'static +type EventClosure = dyn FnMut(Event) + Sync + Send + 'static; +type EventClosureMutex = Box; + +pub fn event_handler(f: F) -> EventClosureMutex + where F: FnMut(Event) + Sync + Send + 'static { Box::new(f) } pub struct FlicClient { - reader: Arc, - writer: Arc, - map: Vec>, + stream: TcpStream, + command_mapper: CommandToByteMapper, + map: Vec, is_running: bool, } @@ -24,12 +33,10 @@ impl FlicClient { pub async fn new(conn: &str) -> Result { match TcpStream::connect(conn).await { Ok(stream) => { - let reader = Arc::new(stream); - let writer = reader.clone(); - + println!("stream open"); Ok(FlicClient{ - reader, - writer, + stream, + command_mapper: CommandToByteMapper::new(), map: vec![], is_running: true, }) @@ -38,30 +45,41 @@ impl FlicClient { } } - pub fn register_event_handler(mut self, event: Box) -> Self { + pub fn register_event_handler(mut self, event: EventClosureMutex) -> Self { self.map.push(event); self } pub async fn listen(&mut self) { + let mut mapper = ByteToEventMapper::new(); + let (mut reader, _writer) = self.stream.split(); + let mut buffer = vec![]; while self.is_running { - if let Some(mut r) = Arc::get_mut(&mut self.reader) { - if let Ok(value) = r.read_u8().await { - for ref mut f in self.map.as_mut_slice() { - f(Event::read_event(value, &mut r)); + if let Some(size) = reader.read_buf(&mut buffer).await.ok() { + for b in buffer.iter() { + match mapper.map(*b) { + EventResult::Some(Event::NoOp) => {} + EventResult::Some(event) => for ref mut f in &mut self.map { + f(event.clone()); + } + _ => {} } } + } } } - pub fn stop(&mut self) { + pub async fn stop(&mut self) { self.is_running = false; + self.stream.shutdown(Shutdown::Both); + println!("stopped"); } - pub fn submit(&mut self, cmd: Command) -> Result<()> { - if let Some(mut w) = Arc::get_mut(&mut self.writer) { - cmd.write_command(&mut w)?; - } - Ok(()) + pub async fn submit(&mut self, cmd: Command) { + let (_reader, mut writer) = self.stream.split(); + for b in self.command_mapper.map(cmd) { + writer.write_u8(b).await; + println!("{:?}", b); + } } } diff --git a/src/commands.rs b/src/commands.rs deleted file mode 100755 index d84d9cd..0000000 --- a/src/commands.rs +++ /dev/null @@ -1,99 +0,0 @@ - -use num_enum::TryFromPrimitive; -use num_enum::IntoPrimitive; -use tokio::io::*; -use tokio::net::TcpStream; - -use super::enums::LatencyMode; - -/// Commands - -pub enum Command { - GetInfo, - CreateScanner { - scan_id: u32, - }, - RemoveScanner { - scan_id: u32, - }, - CreateConnectionChannel { - conn_id: u32, - bd_addr: String, - latency_mode: LatencyMode, - auto_disconnect_time: i16, - }, - RemoveConnectionChannel { - conn_id: u32, - }, - ForceDisconnect { - bd_addr: String, - }, - ChangeModeParameters { - conn_id: u32, - latency_mode: LatencyMode, - auto_disconnect_time: i16, - }, - Ping { - ping_id: u32, - }, - GetButtonInfo { - bd_addr: String, - }, - CreateScanWizard { - scan_wizard_id: u32, - }, - CancelScanWizard { - scan_wizard_id: u32, - }, - DeleteButton { - bd_addr: String, - }, - CreateBatteryStatusListener { - listener_id: u32, - bd_addr: String, - }, - RemoveBatteryStatusListener { - listener_id: u32, - }, -} - -impl Command { - pub fn opcode(&self) -> u8 { - match self { - Self::GetInfo{..} => 0, - Self::CreateScanner{..} => 1, - Self::RemoveScanner{..} => 2, - Self::CreateConnectionChannel{..} => 3, - Self::RemoveConnectionChannel{..} => 4, - Self::ForceDisconnect{..} => 5, - Self::ChangeModeParameters{..} => 6, - Self::Ping{..} => 7, - Self::GetButtonInfo{..} => 8, - Self::CreateScanWizard{..} => 9, - Self::CancelScanWizard{..} => 10, - Self::DeleteButton{..} => 11, - Self::CreateBatteryStatusListener{..} => 12, - Self::RemoveBatteryStatusListener{..} => 13, - } - } - pub fn write_command(&self, writer: &mut TcpStream) -> Result<()> { - match self { - Self::GetInfo{..} => 0, - Self::CreateScanner{..} => 1, - Self::RemoveScanner{..} => 2, - Self::CreateConnectionChannel{..} => 3, - Self::RemoveConnectionChannel{..} => 4, - Self::ForceDisconnect{..} => 5, - Self::ChangeModeParameters{..} => 6, - Self::Ping{..} => 7, - Self::GetButtonInfo{..} => 8, - Self::CreateScanWizard{..} => 9, - Self::CancelScanWizard{..} => 10, - Self::DeleteButton{..} => 11, - Self::CreateBatteryStatusListener{..} => 12, - Self::RemoveBatteryStatusListener{..} => 13, - }; - - Ok(()) - } -} \ No newline at end of file diff --git a/src/commands/mod.rs b/src/commands/mod.rs new file mode 100644 index 0000000..e2b886b --- /dev/null +++ b/src/commands/mod.rs @@ -0,0 +1,76 @@ + +pub mod stream_mapper; + +use super::enums::LatencyMode; + +/// Commands + +pub enum Command { + GetInfo, + CreateScanner { + scan_id: u32, + }, + RemoveScanner { + scan_id: u32, + }, + CreateConnectionChannel { + conn_id: u32, + bd_addr: String, + latency_mode: LatencyMode, + auto_disconnect_time: i16, + }, + RemoveConnectionChannel { + conn_id: u32, + }, + ForceDisconnect { + bd_addr: String, + }, + ChangeModeParameters { + conn_id: u32, + latency_mode: LatencyMode, + auto_disconnect_time: i16, + }, + Ping { + ping_id: u32, + }, + GetButtonInfo { + bd_addr: String, + }, + CreateScanWizard { + scan_wizard_id: u32, + }, + CancelScanWizard { + scan_wizard_id: u32, + }, + DeleteButton { + bd_addr: String, + }, + CreateBatteryStatusListener { + listener_id: u32, + bd_addr: String, + }, + RemoveBatteryStatusListener { + listener_id: u32, + }, +} + +impl Command { + pub fn opcode(&self) -> u8 { + match self { + Self::GetInfo{..} => 0, + Self::CreateScanner{..} => 1, + Self::RemoveScanner{..} => 2, + Self::CreateConnectionChannel{..} => 3, + Self::RemoveConnectionChannel{..} => 4, + Self::ForceDisconnect{..} => 5, + Self::ChangeModeParameters{..} => 6, + Self::Ping{..} => 7, + Self::GetButtonInfo{..} => 8, + Self::CreateScanWizard{..} => 9, + Self::CancelScanWizard{..} => 10, + Self::DeleteButton{..} => 11, + Self::CreateBatteryStatusListener{..} => 12, + Self::RemoveBatteryStatusListener{..} => 13, + } + } +} \ No newline at end of file diff --git a/src/commands/stream_mapper.rs b/src/commands/stream_mapper.rs new file mode 100644 index 0000000..2b6f612 --- /dev/null +++ b/src/commands/stream_mapper.rs @@ -0,0 +1,192 @@ + +use regex::Regex; + +use std::collections::VecDeque; +use std::collections::vec_deque::Drain; +use std::convert::TryInto; + +use super::*; + +pub struct CommandToByteMapper { + buffer: VecDeque, +} +impl CommandToByteMapper { + + pub fn new() -> CommandToByteMapper { + CommandToByteMapper{ buffer: VecDeque::new() } + } + + pub fn map(&mut self, command: Command) -> Drain { + self.clear_buffer(); + + self.write_u8(command.opcode()); + match command { + Command::GetInfo => {}, + Command::CreateScanner { + scan_id, + } => { + + self.write_u32(scan_id); + }, + Command::RemoveScanner { + scan_id, + } => { + + self.write_u32(scan_id); + }, + Command::CreateConnectionChannel { + conn_id, + bd_addr, + latency_mode, + auto_disconnect_time, + } => { + + self.write_u32(conn_id); + self.write_bdaddr(&bd_addr[..]); + if let Ok(latency_mode) = latency_mode.try_into() { + self.write_u8(latency_mode); + } + self.write_i16(auto_disconnect_time); + }, + Command::RemoveConnectionChannel { + conn_id, + } => { + + self.write_u32(conn_id); + }, + Command::ForceDisconnect { + bd_addr, + } => { + + self.write_bdaddr(&bd_addr[..]); + }, + Command::ChangeModeParameters { + conn_id, + latency_mode, + auto_disconnect_time, + } => { + + self.write_u32(conn_id); + if let Ok(latency_mode) = latency_mode.try_into() { + self.write_u8(latency_mode); + } + self.write_i16(auto_disconnect_time); + }, + Command::Ping { + ping_id, + } => { + + self.write_u32(ping_id); + }, + Command::GetButtonInfo { + bd_addr, + } => { + + self.write_bdaddr(&bd_addr[..]); + }, + Command::CreateScanWizard { + scan_wizard_id, + } => { + + self.write_u32(scan_wizard_id); + }, + Command::CancelScanWizard { + scan_wizard_id, + } => { + + self.write_u32(scan_wizard_id); + }, + Command::DeleteButton { + bd_addr, + } => { + + self.write_bdaddr(&bd_addr[..]); + }, + Command::CreateBatteryStatusListener { + listener_id, + bd_addr, + } => { + + self.write_u32(listener_id); + self.write_bdaddr(&bd_addr[..]); + }, + Command::RemoveBatteryStatusListener { + listener_id, + } => { + + self.write_u32(listener_id); + }, + } + + self.prepend_size(); + + self.buffer.drain(..) + } + + fn clear_buffer(&mut self) { + self.buffer.drain(..); + } + + fn prepend_size(&mut self) { + let len = self.buffer.len(); + self.buffer.push_front((len >> 8) as u8); + self.buffer.push_front((len & 255) as u8); + } + fn write_u8(&mut self, value: u8) { + let mut buf = vec![value].into_iter().collect(); + self.buffer.append(&mut buf); + } +/* + fn write_bool(&mut self, value: bool) { + if value { + self.write_u8(1); + } + else { + self.write_u8(0); + } + } +*/ + fn write_u16(&mut self, value: u16) { + self.write_u8(value as u8); + self.write_u8((value >> 8) as u8); + } + fn write_i16(&mut self, value: i16) { + self.write_u8(value as u8); + self.write_u8((value >> 8) as u8); + } + fn write_u32(&mut self, value: u32) { + self.write_u16(value as u16); + self.write_u16((value >> 16) as u16); + } +/* + fn write_i32(&mut self, value: i32) { + self.write_i16(value as i16); + self.write_i16((value >> 16) as i16); + } +*/ + fn write_bdaddr(&mut self, str: &str) { + let re = Regex::new(r"([0-9a-z]{2}:){5}[0-9a-z]{2}").unwrap(); + if re.is_match(str) { + if let Some(b) = hex_to_u8(&str[15..]) { self.write_u8(b); } + if let Some(b) = hex_to_u8(&str[12..]) { self.write_u8(b); } + if let Some(b) = hex_to_u8(&str[ 9..]) { self.write_u8(b); } + if let Some(b) = hex_to_u8(&str[ 6..]) { self.write_u8(b); } + if let Some(b) = hex_to_u8(&str[ 3..]) { self.write_u8(b); } + if let Some(b) = hex_to_u8(&str[ ..]) { self.write_u8(b); } + } + } + +} + +fn hex_to_u8(buffer: &str) -> Option { + let mut char_indices = buffer.char_indices(); + match (char_indices.next(), char_indices.next()) { + (Some((0, upper)), Some((1, lower))) => { + let mut b = 0u8; + if let Some(v) = upper.to_digit(16) { b += (v as u8) << 4; } + if let Some(v) = lower.to_digit(16) { b += v as u8; } + Some(b) + }, + _ => None, + } +} diff --git a/src/enums.rs b/src/enums.rs old mode 100755 new mode 100644 diff --git a/src/events.rs b/src/events.rs deleted file mode 100644 index ac00472..0000000 --- a/src/events.rs +++ /dev/null @@ -1,169 +0,0 @@ - -use tokio::net::TcpStream; - -use super::enums::*; - -#[allow(dead_code)] -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum Event { - CorruptEvent, - - AdvertisementPacket { - scan_id: u32, - bd_addr: String, - name: String, - rssi: u8, - is_private: bool, - already_verified: bool, - already_connected_to_this_device: bool, - already_connected_to_other_device: bool, - }, - - CreateConnectionChannelResponse { - conn_id: u32, - error: CreateConnectionChannelError, - connection_status: ConnectionStatus, - }, - - ConnectionStatusChanged { - conn_id: u32, - connection_status: ConnectionStatus, - disconnect_reason: DisconnectReason, - }, - - ConnectionChannelRemoved { - conn_id: u32, - removed_reason: RemovedReason, - }, - - ButtonUpOrDown{ - conn_id: u32, - click_type: ClickType, - was_queued: bool, - time_diff: i32, - }, - ButtonClickOrHold{ - conn_id: u32, - click_type: ClickType, - was_queued: bool, - time_diff: i32, - }, - ButtonSingleOrDoubleClick{ - conn_id: u32, - click_type: ClickType, - was_queued: bool, - time_diff: i32, - }, - ButtonSingleOrDoubleClickOrHold{ - conn_id: u32, - click_type: ClickType, - was_queued: bool, - time_diff: i32, - }, - - NewVerifiedButton { - bd_addr: String, - }, - - GetInfoResponse { - bluetooth_controller_state: BluetoothControllerState, - my_bd_addr: String, - my_bd_addr_type: BdAddrType, - max_pending_connections: u8, - max_concurrently_connected_buttons: i16, - current_pending_connections: u8, - currently_no_space_for_new_connection: bool, - bd_addr_of_verified_buttons: Vec, - }, - - NoSpaceForNewConnection { - max_concurrently_connected_buttons: u8, - }, - - GotSpaceForNewConnection { - max_concurrently_connected_buttons: u8, - }, - - BluetoothControllerStateChange { - state: BluetoothControllerState, - }, - - PingResponse { - ping_id: u32, - }, - - GetButtonInfoResponse { - bd_addr: String, - uuid: String, - color: Option, - serial_number: Option, - }, - - ScanWizardFoundPrivateButton { - scan_wizard_id: u32, - }, - - ScanWizardFoundPublicButton { - scan_wizard_id: u32, - bd_addr: String, - name: String, - }, - - ScanWizardButtonConnected { - scan_wizard_id: u32, - }, - - ScanWizardCompleted { - scan_wizard_id: u32, - result: ScanWizardResult, - }, - - ButtonDeleted { - bd_addr: String, - deleted_by_this_client: bool, - }, - - BatteryStatus { - listener_id: u32, - battery_percentage: i8, - timestamp: u64, - }, -} - -impl Event { - pub fn opcode(&self) -> u8 { - match self { - Self::CorruptEvent => 255, - Self::AdvertisementPacket{..} => 0, - Self::CreateConnectionChannelResponse{..} => 1, - Self::ConnectionStatusChanged{..} => 2, - Self::ConnectionChannelRemoved{..} => 3, - Self::ButtonUpOrDown{..} => 4, - Self::ButtonClickOrHold{..} => 5, - Self::ButtonSingleOrDoubleClick{..} => 6, - Self::ButtonSingleOrDoubleClickOrHold{..} => 7, - Self::NewVerifiedButton{..} => 8, - Self::GetInfoResponse{..} => 9, - Self::NoSpaceForNewConnection{..} => 10, - Self::GotSpaceForNewConnection{..} => 11, - Self::BluetoothControllerStateChange{..} => 12, - Self::PingResponse{..} => 13, - Self::GetButtonInfoResponse{..} => 14, - Self::ScanWizardFoundPrivateButton{..} => 15, - Self::ScanWizardFoundPublicButton{..} => 16, - Self::ScanWizardButtonConnected{..} => 17, - Self::ScanWizardCompleted{..} => 18, - Self::ButtonDeleted{..} => 19, - Self::BatteryStatus{..} => 20, - } - } - - pub fn read_event(opcode: u8, reader: &mut TcpStream) -> Event { - match opcode { - 13 => Self::PingResponse{ping_id: 8}, - _ => Self::CorruptEvent, - } - } - -} - diff --git a/src/events/mod.rs b/src/events/mod.rs new file mode 100644 index 0000000..0cfd910 --- /dev/null +++ b/src/events/mod.rs @@ -0,0 +1,162 @@ + +pub mod stream_mapper; + +use num_enum::TryFromPrimitive; +use num_enum::IntoPrimitive; + +use super::enums::*; + +#[repr(u8)] +#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, IntoPrimitive, TryFromPrimitive)] +pub enum OpCode { + AdvertisementPacket = 0, + CreateConnectionChannelResponse = 1, + ConnectionStatusChanged = 2, + ConnectionChannelRemoved = 3, + ButtonUpOrDown = 4, + ButtonClickOrHold = 5, + ButtonSingleOrDoubleClick = 6, + ButtonSingleOrDoubleClickOrHold = 7, + NewVerifiedButton = 8, + GetInfoResponse = 9, + NoSpaceForNewConnection = 10, + GotSpaceForNewConnection = 11, + BluetoothControllerStateChange = 12, + PingResponse = 13, + GetButtonInfoResponse = 14, + ScanWizardFoundPrivateButton = 15, + ScanWizardFoundPublicButton = 16, + ScanWizardButtonConnected = 17, + ScanWizardCompleted = 18, + ButtonDeleted = 19, + BatteryStatus = 20, +} + +#[allow(dead_code)] +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum Event { + NoOp, + CorruptEvent, + + AdvertisementPacket { + scan_id: u32, + bd_addr: String, + name: String, + rssi: u8, + is_private: bool, + already_verified: bool, + already_connected_to_this_device: bool, + already_connected_to_other_device: bool, + }, + + CreateConnectionChannelResponse { + conn_id: u32, + error: CreateConnectionChannelError, + connection_status: ConnectionStatus, + }, + + ConnectionStatusChanged { + conn_id: u32, + connection_status: ConnectionStatus, + disconnect_reason: DisconnectReason, + }, + + ConnectionChannelRemoved { + conn_id: u32, + removed_reason: RemovedReason, + }, + + ButtonUpOrDown{ + conn_id: u32, + click_type: ClickType, + was_queued: bool, + time_diff: i32, + }, + ButtonClickOrHold{ + conn_id: u32, + click_type: ClickType, + was_queued: bool, + time_diff: i32, + }, + ButtonSingleOrDoubleClick{ + conn_id: u32, + click_type: ClickType, + was_queued: bool, + time_diff: i32, + }, + ButtonSingleOrDoubleClickOrHold{ + conn_id: u32, + click_type: ClickType, + was_queued: bool, + time_diff: i32, + }, + + NewVerifiedButton { + bd_addr: String, + }, + + GetInfoResponse { + bluetooth_controller_state: BluetoothControllerState, + my_bd_addr: String, + my_bd_addr_type: BdAddrType, + max_pending_connections: u8, + max_concurrently_connected_buttons: i16, + current_pending_connections: u8, + currently_no_space_for_new_connection: bool, + bd_addr_of_verified_buttons: Vec, + }, + + NoSpaceForNewConnection { + max_concurrently_connected_buttons: u8, + }, + + GotSpaceForNewConnection { + max_concurrently_connected_buttons: u8, + }, + + BluetoothControllerStateChange { + state: BluetoothControllerState, + }, + + PingResponse { + ping_id: u32, + }, + + GetButtonInfoResponse { + bd_addr: String, + uuid: String, + color: Option, + serial_number: Option, + }, + + ScanWizardFoundPrivateButton { + scan_wizard_id: u32, + }, + + ScanWizardFoundPublicButton { + scan_wizard_id: u32, + bd_addr: String, + name: String, + }, + + ScanWizardButtonConnected { + scan_wizard_id: u32, + }, + + ScanWizardCompleted { + scan_wizard_id: u32, + result: ScanWizardResult, + }, + + ButtonDeleted { + bd_addr: String, + deleted_by_this_client: bool, + }, + + BatteryStatus { + listener_id: u32, + battery_percentage: i8, + timestamp: u64, + }, +} + diff --git a/src/events/stream_mapper.rs b/src/events/stream_mapper.rs new file mode 100644 index 0000000..5244d58 --- /dev/null +++ b/src/events/stream_mapper.rs @@ -0,0 +1,581 @@ + +use super::*; +use std::collections::VecDeque; +use std::usize; +use std::convert::TryInto; +use std::convert::TryFrom; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum EventResult { + Some(Event), + None, + Failure(Event), + Drained, + Pending, + CorruptPackage, +} +enum HasPacketResult { + Yes, + NotYet, + Failure(Event), +} + +pub struct ByteToEventMapper { + fifo: VecDeque, +} +impl ByteToEventMapper { + + pub fn new() -> ByteToEventMapper { + ByteToEventMapper { fifo: VecDeque::new() } + } + + pub fn map(&mut self, value: u8) -> EventResult { + + self.fifo.push_back(value); + + match self.fifo.len() { + 0..=2 => EventResult::None, + 3..=2047 => match self.has_packet() { + HasPacketResult::NotYet => EventResult::Pending, + HasPacketResult::Failure(event) => { + self.fifo.drain(..); + EventResult::Failure(event) + }, + HasPacketResult::Yes => match self.read_event() { + Event::CorruptEvent => EventResult::CorruptPackage, + event => EventResult::Some(event), + } + }, + _ => { + self.fifo.drain(..); + EventResult::Drained + }, + } + + } + + pub fn has_packet(&self) -> HasPacketResult { + match (self.fifo.get(0), self.fifo.get(1), self.fifo.get(2)) { + (Some(&lower), Some(&upper), Some(&opcode)) => { + let len = ((upper as usize) << 8) + (lower as usize); + if OpCode::try_from(opcode).is_err() { + HasPacketResult::Failure(Event::CorruptEvent) + } + else if self.fifo.len() >= len + 2 { + HasPacketResult::Yes + } + else { + HasPacketResult::NotYet + } + }, + _ => HasPacketResult::NotYet, + } + } + fn read_u8(&mut self) -> Option { + if self.fifo.is_empty() { return None; } + let mut iter = self.fifo.drain(..1); + iter.next() + } + fn read_u16(&mut self) -> Option { + match (self.read_u8(), self.read_u8()) { + (Some(lower), Some(higher)) => Some((lower as u16) | (higher as u16) << 8), + _ => None, + } + } + fn read_u32(&mut self) -> Option { + match (self.read_u16(), self.read_u16()) { + (Some(lower), Some(higher)) => Some((lower as u32) | (higher as u32) << 16), + _ => None, + } + } + fn read_u64(&mut self) -> Option { + match (self.read_u32(), self.read_u32()) { + (Some(lower), Some(higher)) => Some((lower as u64) | (higher as u64) << 32), + _ => None, + } + } + fn read_i8(&mut self) -> Option { + match self.read_u8() { + Some(expr) => Some(expr as i8), + None => None, + } + } + fn read_i16(&mut self) -> Option { + match self.read_u16() { + Some(expr) => Some(expr as i16), + None => None, + } + } + fn read_i32(&mut self) -> Option { + match self.read_u32() { + Some(expr) => Some(expr as i32), + None => None, + } + } + fn read_bool(&mut self) -> Option { + match self.read_u8() { + Some(expr) => Some(expr != 0), + None => None, + } + } + fn read_enum(&mut self) -> Option { + self.read_u8() + } + fn read_bdaddr(&mut self) -> Option { + let mut out = String::new(); + let mut buffer = vec![self.read_u8(), self.read_u8(), self.read_u8(), self.read_u8(), self.read_u8(), self.read_u8()]; + buffer.reverse(); + for (idx, b) in buffer.iter().enumerate() { + if idx > 0 { out.push(':'); } + match b { + Some(v) => { + out.push(u8_to_hex(v >> 4)); + out.push(u8_to_hex(v << 4 >> 4)); + }, + None => return None, + } + } + Some(out) + } + fn read_string(&mut self) -> Option { + match self.read_u8() { + Some(len) => { + let mut buf = vec![]; + for _ in 0..len { + match self.read_u8() { + Some(b) => buf.push(b), + _ => return None, + } + } + String::from_utf8(buf).ok() + }, + _ => None, + } + } + fn read_uuid(&mut self) -> Option { + let mut out = String::new(); + for _ in 0..16 { + let b = self.read_u8(); + match b { + Some(v) => { + out.push(u8_to_hex(v >> 4)); + out.push(u8_to_hex(v << 4 >> 4)); + }, + None => return None, + } + } + if &out[..] == "00000000000000000000000000000000" { + None + } + else { + Some(out) + } + } + + fn read_event(&mut self) -> Event { + self.read_u8(); + self.read_u8(); + match self.read_u8() { + Some(opcode) => match opcode.try_into().ok() { + + Some(OpCode::AdvertisementPacket) => match ( + self.read_u32(), + self.read_bdaddr(), + self.read_string(), + self.read_u8(), + self.read_bool(), + self.read_bool(), + self.read_bool(), + self.read_bool(), + ) { + ( + Some(scan_id), + Some(bd_addr), + Some(name), + Some(rssi), + Some(is_private), + Some(already_verified), + Some(already_connected_to_this_device), + Some(already_connected_to_other_device), + ) => Event::AdvertisementPacket { + scan_id, + bd_addr, + name, + rssi, + is_private, + already_verified, + already_connected_to_this_device, + already_connected_to_other_device, + }, + _ => Event::CorruptEvent, + }, + Some(OpCode::CreateConnectionChannelResponse) => match ( + self.read_u32(), + self.read_enum(), + self.read_enum(), + ) { + ( + Some(conn_id), + Some(error), + Some(connection_status), + + ) => + match (error.try_into().ok(), connection_status.try_into().ok()) { + (Some(error), Some(connection_status)) => + Event::CreateConnectionChannelResponse { + conn_id, + error, + connection_status, + }, + _ => Event::CorruptEvent, + }, + _ => Event::CorruptEvent, + }, + Some(OpCode::ConnectionStatusChanged) => match ( + self.read_u32(), + self.read_enum(), + self.read_enum(), + ) { + ( + Some(conn_id), + Some(connection_status), + Some(disconnect_reason), + ) => + match (connection_status.try_into().ok(), disconnect_reason.try_into().ok()) { + (Some(connection_status), Some(disconnect_reason)) => + Event::ConnectionStatusChanged { + conn_id, + connection_status, + disconnect_reason, + }, + _ => Event::CorruptEvent, + }, + _ => Event::CorruptEvent, + }, + Some(OpCode::ConnectionChannelRemoved) => match ( + self.read_u32(), + self.read_enum(), + ) { + ( + Some(conn_id), + Some(removed_reason), + ) => + match removed_reason.try_into().ok() { + Some(removed_reason) => + Event::ConnectionChannelRemoved { + conn_id, + removed_reason, + }, + _ => Event::CorruptEvent, + }, + _ => Event::CorruptEvent, + }, + Some(OpCode::ButtonUpOrDown) => match ( + self.read_u32(), + self.read_enum(), + self.read_bool(), + self.read_i32(), + ) { + ( + Some(conn_id), + Some(click_type), + Some(was_queued), + Some(time_diff), + ) => + match click_type.try_into().ok() { + Some(click_type) => + Event::ButtonUpOrDown { + conn_id, + click_type, + was_queued, + time_diff, + }, + _ => Event::CorruptEvent, + } + _ => Event::CorruptEvent, + }, + Some(OpCode::ButtonClickOrHold) => match ( + self.read_u32(), + self.read_enum(), + self.read_bool(), + self.read_i32(), + ) { + ( + Some(conn_id), + Some(click_type), + Some(was_queued), + Some(time_diff), + ) => + match click_type.try_into().ok() { + Some(click_type) => + Event::ButtonClickOrHold { + conn_id, + click_type, + was_queued, + time_diff, + }, + _ => Event::CorruptEvent, + } + _ => Event::CorruptEvent, + }, + Some(OpCode::ButtonSingleOrDoubleClick) => match ( + self.read_u32(), + self.read_enum(), + self.read_bool(), + self.read_i32(), + ) { + ( + Some(conn_id), + Some(click_type), + Some(was_queued), + Some(time_diff), + ) => + match click_type.try_into().ok() { + Some(click_type) => + Event::ButtonSingleOrDoubleClick { + conn_id, + click_type, + was_queued, + time_diff, + }, + _ => Event::CorruptEvent, + } + _ => Event::CorruptEvent, + }, + Some(OpCode::ButtonSingleOrDoubleClickOrHold) => match ( + self.read_u32(), + self.read_enum(), + self.read_bool(), + self.read_i32(), + ) { + ( + Some(conn_id), + Some(click_type), + Some(was_queued), + Some(time_diff), + ) => + match click_type.try_into().ok() { + Some(click_type) => + Event::ButtonSingleOrDoubleClickOrHold { + conn_id, + click_type, + was_queued, + time_diff, + }, + _ => Event::CorruptEvent, + } + _ => Event::CorruptEvent, + }, + Some(OpCode::NewVerifiedButton) => match ( + self.read_bdaddr(), + ) { + ( + Some(bd_addr), + ) => Event::NewVerifiedButton { + bd_addr, + }, + _ => Event::CorruptEvent, + }, + Some(OpCode::GetInfoResponse) => match ( + self.read_enum(), + self.read_bdaddr(), + self.read_enum(), + self.read_u8(), + self.read_i16(), + self.read_u8(), + self.read_bool(), + self.read_u16(), + ) { + ( + Some(bluetooth_controller_state), + Some(my_bd_addr), + Some(my_bd_addr_type), + Some(max_pending_connections), + Some(max_concurrently_connected_buttons), + Some(current_pending_connections), + Some(currently_no_space_for_new_connection), + Some(buttons_size), + ) => { + let mut bd_addr_of_verified_buttons = vec![]; + for _ in 0..buttons_size { + match self.read_bdaddr() { + Some(bd_addr) => bd_addr_of_verified_buttons.push(bd_addr), + None => return Event::CorruptEvent, + } + } + match (bluetooth_controller_state.try_into().ok(), my_bd_addr_type.try_into().ok()) { + (Some(bluetooth_controller_state), Some(my_bd_addr_type)) => + Event::GetInfoResponse { + bluetooth_controller_state, + my_bd_addr, + my_bd_addr_type, + max_pending_connections, + max_concurrently_connected_buttons, + current_pending_connections, + currently_no_space_for_new_connection, + bd_addr_of_verified_buttons, + }, + _ => Event::CorruptEvent, + } + }, + _ => Event::CorruptEvent, + }, + Some(OpCode::NoSpaceForNewConnection) => match ( + self.read_u8(), + ) { + ( + Some(max_concurrently_connected_buttons), + ) => Event::NoSpaceForNewConnection { + max_concurrently_connected_buttons, + }, + _ => Event::CorruptEvent, + }, + Some(OpCode::GotSpaceForNewConnection) => match ( + self.read_u8(), + ) { + ( + Some(max_concurrently_connected_buttons), + ) => Event::GotSpaceForNewConnection { + max_concurrently_connected_buttons, + }, + _ => Event::CorruptEvent, + }, + Some(OpCode::BluetoothControllerStateChange) => match ( + self.read_enum(), + ) { + ( + Some(state), + ) => + match state.try_into().ok() { + Some(state) => + Event::BluetoothControllerStateChange { + state, + }, + _ => Event::CorruptEvent, + } + _ => Event::CorruptEvent, + }, + Some(OpCode::PingResponse) => match ( + self.read_u32(), + ) { + ( + Some(ping_id), + ) => Event::PingResponse { + ping_id, + }, + _ => Event::CorruptEvent, + }, + Some(OpCode::GetButtonInfoResponse) => match ( + self.read_bdaddr(), + self.read_uuid(), + self.read_string(), + self.read_string(), + ) { + ( + Some(bd_addr), + Some(uuid), + color, + serial_number, + ) => Event::GetButtonInfoResponse { + bd_addr, + uuid, + color, + serial_number, + }, + _ => Event::CorruptEvent, + }, + Some(OpCode::ScanWizardFoundPrivateButton) => match ( + self.read_u32(), + ) { + ( + Some(scan_wizard_id), + ) => Event::ScanWizardFoundPrivateButton { + scan_wizard_id, + }, + _ => Event::CorruptEvent, + }, + Some(OpCode::ScanWizardButtonConnected) => match ( + self.read_u32(), + ) { + ( + Some(scan_wizard_id), + ) => Event::ScanWizardButtonConnected { + scan_wizard_id, + }, + _ => Event::CorruptEvent, + }, + Some(OpCode::ScanWizardFoundPublicButton) => match ( + self.read_u32(), + self.read_bdaddr(), + self.read_string(), + ) { + ( + Some(scan_wizard_id), + Some(bd_addr), + Some(name), + ) => Event::ScanWizardFoundPublicButton { + scan_wizard_id, + bd_addr, + name, + }, + _ => Event::CorruptEvent, + }, + Some(OpCode::ScanWizardCompleted) => match ( + self.read_u32(), + self.read_enum(), + ) { + ( + Some(scan_wizard_id), + Some(result), + ) => + match result.try_into().ok() { + Some(result) => + Event::ScanWizardCompleted { + scan_wizard_id, + result, + }, + _ => Event::CorruptEvent, + } + _ => Event::CorruptEvent, + }, + Some(OpCode::ButtonDeleted) => match ( + self.read_bdaddr(), + self.read_bool(), + ) { + ( + Some(bd_addr), + Some(deleted_by_this_client), + ) => Event::ButtonDeleted { + bd_addr, + deleted_by_this_client, + }, + _ => Event::CorruptEvent, + }, + Some(OpCode::BatteryStatus) => match ( + self.read_u32(), + self.read_i8(), + self.read_u64(), + ) { + ( + Some(listener_id), + Some(battery_percentage), + Some(timestamp), + ) => Event::BatteryStatus { + listener_id, + battery_percentage, + timestamp, + }, + _ => Event::CorruptEvent, + }, + _ => Event::CorruptEvent, + }, + None => Event::NoOp, + } + } +} +fn u8_to_hex(value: u8) -> char { + match value { + 0u8..=9u8 => (value + b'0') as char, + _ => (value - 10u8 + b'a') as char, + } +} diff --git a/src/main.rs b/src/main.rs index bcefc75..581547b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,8 +5,13 @@ mod commands; mod client; use std::error::Error; +use std::time::Duration; +use std::sync::Arc; + +use tokio::sync::Mutex; use client::*; +use commands::Command; #[tokio::main] async fn main() -> Result<(), Box> { @@ -14,10 +19,13 @@ async fn main() -> Result<(), Box> { let event = event_handler(|event| { println!("ping response: {:?}", event); }); let event2 = event_handler(|event| { println!("ping response: {:?}", event); }); - let _client = FlicClient::new("127.0.0.1:5551").await? + let mut client = FlicClient::new("127.0.0.1:5551").await? .register_event_handler(event) .register_event_handler(event2) ; + + client.submit(Command::GetInfo).await; + client.listen().await; Ok(()) } -- cgit v1.2.3