From 807291fa7e752ba159eb27cff15475156dc0834d Mon Sep 17 00:00:00 2001 From: vyfor Date: Mon, 30 Dec 2024 18:59:48 +0500 Subject: [PATCH] feat(ipc): continuously read data from Discord pipe to detect sudden disconnects --- src/cord.rs | 28 +-- src/error.rs | 9 + src/ipc/bindings.rs | 116 +++++++++++ src/ipc/discord/client.rs | 51 ++--- src/ipc/discord/error.rs | 48 +++++ src/ipc/discord/mod.rs | 2 + src/ipc/discord/opcodes.rs | 28 +++ src/ipc/discord/platform/unix.rs | 142 ++++++++++++- src/ipc/discord/platform/windows.rs | 262 ++++++++++++++++++++++-- src/ipc/discord/utils.rs | 13 +- src/ipc/mod.rs | 1 + src/ipc/pipe/platform/windows/client.rs | 2 +- src/ipc/pipe/platform/windows/mod.rs | 93 --------- src/ipc/pipe/platform/windows/server.rs | 2 +- src/messages/events/local/error.rs | 14 ++ 15 files changed, 624 insertions(+), 187 deletions(-) create mode 100644 src/ipc/bindings.rs create mode 100644 src/ipc/discord/error.rs create mode 100644 src/ipc/discord/opcodes.rs diff --git a/src/cord.rs b/src/cord.rs index ef6db344..72ac3195 100644 --- a/src/cord.rs +++ b/src/cord.rs @@ -7,14 +7,12 @@ use crate::ipc::discord::client::{Connection, RichClient}; use crate::ipc::pipe::platform::server::PipeServer; use crate::ipc::pipe::PipeServerImpl; use crate::messages::events::event::{EventContext, OnEvent}; -use crate::messages::events::local::ErrorEvent; use crate::messages::events::server::LogEvent; use crate::messages::message::Message; use crate::protocol::msgpack::MsgPack; use crate::session::SessionManager; use crate::util::lockfile::ServerLock; use crate::util::logger::{LogLevel, Logger}; -use crate::{client_event, local_event, server_event}; /// Core application managing configuration, sessions, IPC with Discord, and logging. /// @@ -115,31 +113,13 @@ impl Cord { } /// Starts RPC with Discord. - pub fn start_rpc(&self) -> crate::Result<()> { + pub fn start_rpc(&mut self) -> crate::Result<()> { self.rich_client.handshake()?; - let rich_client = self.rich_client.clone(); let tx = self.tx.clone(); - let logger = self.logger.clone(); - std::thread::spawn(move || match rich_client.read() { - Ok(msg) => { - let msg = String::from_utf8_lossy(&msg); - if msg.contains("Invalid Client ID") { - logger.log( - LogLevel::Error, - format!("Invalid client ID: {}", msg).into(), - 0, - ); - tx.send(client_event!(0, Shutdown)).ok(); - } else { - tx.send(server_event!(0, Ready)).ok(); - } - } - Err(e) => { - tx.send(local_event!(0, Error, ErrorEvent::new(e.into()))) - .ok(); - } - }); + Arc::get_mut(&mut self.rich_client) + .expect("Failed to start read thread") + .start_read_thread(tx.clone())?; Ok(()) } diff --git a/src/error.rs b/src/error.rs index 968c865c..b41d06b7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,6 +4,7 @@ use std::string::ParseError; use std::{fmt, io}; use crate::cli::error::CliError; +use crate::ipc::discord::error::DiscordError; use crate::protocol::error::ProtocolError; /// Enumerates error types: IO, parsing, protocol, CLI, and others. @@ -17,6 +18,8 @@ pub enum CordErrorKind { Protocol, /// Errors related to CLI operations. Cli, + /// Errors related to Discord operations. + Discord, /// Other unspecified errors. Other, } @@ -92,6 +95,12 @@ impl From for CordError { } } +impl From for CordError { + fn from(err: DiscordError) -> Self { + Self::new(CordErrorKind::Discord, err) + } +} + impl From for CordError { fn from(err: String) -> Self { Self::new( diff --git a/src/ipc/bindings.rs b/src/ipc/bindings.rs new file mode 100644 index 00000000..e518f0b7 --- /dev/null +++ b/src/ipc/bindings.rs @@ -0,0 +1,116 @@ +#![allow(clippy::upper_case_acronyms)] + +#[cfg(target_os = "windows")] +mod windows { + pub type HANDLE = *mut std::ffi::c_void; + pub type DWORD = u32; + pub type BOOL = i32; + pub type LPCWSTR = *const u16; + pub type LPVOID = *mut std::ffi::c_void; + + pub const GENERIC_READ: DWORD = 0x80000000; + pub const GENERIC_WRITE: DWORD = 0x40000000; + pub const OPEN_EXISTING: DWORD = 3; + pub const INVALID_HANDLE_VALUE: HANDLE = -1isize as HANDLE; + pub const ERROR_PIPE_CONNECTED: DWORD = 535; + pub const ERROR_IO_PENDING: DWORD = 997; + pub const PIPE_ACCESS_DUPLEX: DWORD = 0x00000003; + pub const FILE_FLAG_OVERLAPPED: DWORD = 0x40000000; + pub const PIPE_TYPE_MESSAGE: DWORD = 0x00000004; + pub const PIPE_READMODE_MESSAGE: DWORD = 0x00000002; + pub const PIPE_WAIT: DWORD = 0x00000000; + pub const PIPE_UNLIMITED_INSTANCES: DWORD = 255; + + #[repr(C)] + pub struct Overlapped { + pub internal: usize, + pub internal_high: usize, + pub offset: DWORD, + pub offset_high: DWORD, + pub h_event: HANDLE, + } + + impl Default for Overlapped { + fn default() -> Self { + Self { + internal: 0, + internal_high: 0, + offset: 0, + offset_high: 0, + h_event: unsafe { + CreateEventW( + std::ptr::null_mut(), + 1, + 0, + std::ptr::null_mut(), + ) + }, + } + } + } + + extern "system" { + pub fn CreateFileW( + lfFileName: LPCWSTR, + dwDesiredAccess: DWORD, + dwShareMode: DWORD, + lpSecurityAttributes: LPVOID, + dwCreationDisposition: DWORD, + dwFlagsAndAttributes: DWORD, + hTemplateFile: HANDLE, + ) -> HANDLE; + + pub fn CreateNamedPipeW( + lpName: LPCWSTR, + dwOpenMode: DWORD, + dwPipeMode: DWORD, + nMaxInstances: DWORD, + nOutBufferSize: DWORD, + nInBufferSize: DWORD, + nDefaultTimeOut: DWORD, + lpSecurityAttributes: LPVOID, + ) -> HANDLE; + + pub fn ConnectNamedPipe( + hNamedPipe: HANDLE, + lpOverlapped: *mut Overlapped, + ) -> BOOL; + + pub fn GetLastError() -> DWORD; + + pub fn CloseHandle(hObject: HANDLE) -> BOOL; + + pub fn CreateEventW( + lpEventAttributes: LPVOID, + bManualReset: BOOL, + bInitialState: BOOL, + lpName: LPCWSTR, + ) -> HANDLE; + + pub fn WriteFile( + hFile: HANDLE, + lpBuffer: *const u8, + nNumberOfBytesToWrite: DWORD, + lpNumberOfBytesWritten: *mut DWORD, + lpOverlapped: *mut Overlapped, + ) -> BOOL; + + pub fn ReadFile( + hFile: HANDLE, + lpBuffer: *mut u8, + nNumberOfBytesToRead: DWORD, + lpNumberOfBytesRead: *mut DWORD, + lpOverlapped: *mut Overlapped, + ) -> BOOL; + + pub fn GetOverlappedResult( + hFile: HANDLE, + lpOverlapped: *mut Overlapped, + lpNumberOfBytesTransferred: *mut DWORD, + bWait: BOOL, + ) -> BOOL; + } +} + +#[cfg(target_os = "windows")] +pub use windows::*; diff --git a/src/ipc/discord/client.rs b/src/ipc/discord/client.rs index 35648a39..7def4a12 100644 --- a/src/ipc/discord/client.rs +++ b/src/ipc/discord/client.rs @@ -1,7 +1,9 @@ -use std::io::{Read, Write}; use std::sync::atomic::AtomicBool; +use std::sync::mpsc::Sender; +use std::sync::Arc; +use std::thread::JoinHandle; -use crate::ipc::discord::utils; +use crate::messages::message::Message; use crate::presence::packet::Packet; use crate::protocol::json::Json; @@ -15,11 +17,14 @@ use crate::protocol::json::Json; pub struct RichClient { pub client_id: u64, #[cfg(target_os = "windows")] - pub pipe: Option, + pub pipe: Option>, #[cfg(not(target_os = "windows"))] - pub pipe: Option, + pub read_pipe: Option, + #[cfg(not(target_os = "windows"))] + pub write_pipe: Option, pub pid: u32, pub is_ready: AtomicBool, + pub thread_handle: Option>, } /// Defines methods for connecting and closing the client. @@ -28,43 +33,13 @@ pub trait Connection { fn connect(client_id: u64) -> crate::Result; /// Closes the connection to Discord. fn close(&mut self); + /// Start reading from Discord in a separate thread + fn start_read_thread(&mut self, tx: Sender) -> crate::Result<()>; + /// Write data to Discord + fn write(&self, opcode: u32, data: Option<&[u8]>) -> crate::Result<()>; } impl RichClient { - /// Sends data to Discord. - pub fn write(&self, opcode: u32, data: Option<&[u8]>) -> crate::Result<()> { - self.pipe - .as_ref() - .map_or(Err("Pipe not found".into()), |mut pipe| { - let payload = match data { - Some(packet) => { - let mut payload = - utils::encode(opcode, packet.len() as u32); - payload.extend_from_slice(packet); - payload - } - None => utils::encode(opcode, 0), - }; - pipe.write_all(&payload)?; - - Ok(()) - }) - } - - /// Receives data from Discord. - pub fn read(&self) -> crate::Result> { - self.pipe - .as_ref() - .map_or(Err("Pipe not found".into()), |mut pipe| { - let mut header = [0; 8]; - pipe.read_exact(&mut header)?; - let size = utils::decode(&header) as usize; - let mut buffer = vec![0u8; size]; - pipe.read_exact(&mut buffer)?; - Ok(buffer) - }) - } - /// Establishes a connection with Discord. pub fn handshake(&self) -> crate::Result<()> { self.write( diff --git a/src/ipc/discord/error.rs b/src/ipc/discord/error.rs new file mode 100644 index 00000000..93588869 --- /dev/null +++ b/src/ipc/discord/error.rs @@ -0,0 +1,48 @@ +use std::{fmt, io}; + +#[derive(Debug)] +pub enum DiscordError { + Io(io::Error), + InvalidClientId(String), + ConnectionClosed, + PipeNotFound, + Custom(String), +} + +impl fmt::Display for DiscordError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + DiscordError::Io(err) => write!(f, "IO error: {}", err), + DiscordError::InvalidClientId(id) => { + write!(f, "'{}' is not a valid client ID", id) + } + DiscordError::ConnectionClosed => { + write!(f, "The connection was forcibly closed") + } + DiscordError::PipeNotFound => { + write!(f, "Discord IPC pipe not found") + } + DiscordError::Custom(msg) => write!(f, "{}", msg), + } + } +} + +impl std::error::Error for DiscordError {} + +impl From for DiscordError { + fn from(err: io::Error) -> Self { + DiscordError::Io(err) + } +} + +impl From<&str> for DiscordError { + fn from(err: &str) -> Self { + DiscordError::Custom(err.to_string()) + } +} + +impl From for DiscordError { + fn from(err: String) -> Self { + DiscordError::Custom(err) + } +} diff --git a/src/ipc/discord/mod.rs b/src/ipc/discord/mod.rs index a56952c7..76854b7b 100644 --- a/src/ipc/discord/mod.rs +++ b/src/ipc/discord/mod.rs @@ -1,3 +1,5 @@ pub mod client; +pub mod error; +pub mod opcodes; pub mod platform; mod utils; diff --git a/src/ipc/discord/opcodes.rs b/src/ipc/discord/opcodes.rs new file mode 100644 index 00000000..8cffdbbd --- /dev/null +++ b/src/ipc/discord/opcodes.rs @@ -0,0 +1,28 @@ +/// Discord IPC opcodes +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Opcode { + Handshake = 0, + Frame = 1, + Close = 2, + Ping = 3, + Pong = 4, +} + +impl From for Opcode { + fn from(code: u32) -> Self { + match code { + 0 => Opcode::Handshake, + 1 => Opcode::Frame, + 2 => Opcode::Close, + 3 => Opcode::Ping, + 4 => Opcode::Pong, + _ => Opcode::Frame, + } + } +} + +impl From for u32 { + fn from(op: Opcode) -> Self { + op as u32 + } +} diff --git a/src/ipc/discord/platform/unix.rs b/src/ipc/discord/platform/unix.rs index 49a6f1eb..b32ec0b5 100644 --- a/src/ipc/discord/platform/unix.rs +++ b/src/ipc/discord/platform/unix.rs @@ -1,9 +1,16 @@ use std::env::var; -use std::io; +use std::io::{self, Read, Write}; use std::net::Shutdown; use std::os::unix::net::UnixStream; +use std::sync::mpsc::Sender; use crate::ipc::discord::client::{Connection, RichClient}; +use crate::ipc::discord::error::DiscordError; +use crate::ipc::discord::opcodes::Opcode; +use crate::ipc::discord::utils; +use crate::messages::events::local::ErrorEvent; +use crate::messages::message::Message; +use crate::{local_event, server_event}; impl Connection for RichClient { /// Pipe path can be in any of the following directories: @@ -36,27 +43,150 @@ impl Connection for RichClient { for i in 0..10 { match UnixStream::connect(format!("{dir}/discord-ipc-{i}")) { Ok(pipe) => { + let read_pipe = + pipe.try_clone().map_err(DiscordError::Io)?; return Ok(RichClient { client_id, - pipe: Some(pipe), + read_pipe: Some(read_pipe), + write_pipe: Some(pipe), pid: std::process::id(), is_ready: false.into(), - }) + thread_handle: None, + }); } Err(e) => match e.kind() { io::ErrorKind::NotFound => continue, - _ => return Err(e.into()), + _ => return Err(DiscordError::Io(e).into()), }, } } } - Err("Pipe not found".into()) + Err(DiscordError::PipeNotFound.into()) } fn close(&mut self) { - if let Some(pipe) = self.pipe.take() { + if let Some(pipe) = self.read_pipe.take() { let _ = pipe.shutdown(Shutdown::Both); } + if let Some(pipe) = self.write_pipe.take() { + let _ = pipe.shutdown(Shutdown::Both); + } + if let Some(handle) = self.thread_handle.take() { + handle.join().ok(); + } + } + + fn start_read_thread(&mut self, tx: Sender) -> crate::Result<()> { + if let Some(mut read_pipe) = self.read_pipe.take() { + let client_id = self.client_id; + + let handle = std::thread::spawn(move || { + let mut buf = [0u8; 8192]; + loop { + match read_pipe.read(&mut buf) { + Ok(0) => { + tx.send(local_event!( + 0, + Error, + ErrorEvent::new(Box::new( + DiscordError::ConnectionClosed + )) + )) + .ok(); + break; + } + Ok(bytes_transferred) => { + if bytes_transferred >= 8 { + if let Some((opcode, size)) = + utils::decode(&buf[..bytes_transferred]) + { + if size > 0 + && bytes_transferred + >= 8 + size as usize + { + let data = &buf[8..8 + size as usize]; + let data_str = + String::from_utf8_lossy(data); + + match Opcode::from(opcode) { + Opcode::Frame => { + if data_str.contains( + "Invalid Client ID", + ) { + tx.send(local_event!( + 0, + Error, + ErrorEvent::new(Box::new( + DiscordError::InvalidClientId( + client_id.to_string() + ) + )) + )) + .ok(); + break; + } + tx.send(server_event!( + 0, Ready + )) + .ok(); + } + Opcode::Close => { + tx.send(local_event!( + 0, + Error, + ErrorEvent::new(Box::new( + DiscordError::ConnectionClosed + )) + )) + .ok(); + break; + } + _ => {} + } + } + } + } + } + Err(e) => { + tx.send(local_event!( + 0, + Error, + ErrorEvent::new(Box::new(DiscordError::Io(e))) + )) + .ok(); + break; + } + } + } + }); + + self.thread_handle = Some(handle); + Ok(()) + } else { + Err(DiscordError::PipeNotFound.into()) + } + } + + fn write(&self, opcode: u32, data: Option<&[u8]>) -> crate::Result<()> { + self.write_pipe.as_ref().map_or( + Err(DiscordError::PipeNotFound.into()), + |mut pipe| { + let payload = match data { + Some(packet) => { + let mut payload = + utils::encode(opcode, packet.len() as u32); + payload.extend_from_slice(packet); + payload + } + None => utils::encode(opcode, 0), + }; + + match pipe.write_all(&payload) { + Ok(_) => Ok(()), + Err(e) => Err(DiscordError::Io(e).into()), + } + }, + ) } } diff --git a/src/ipc/discord/platform/windows.rs b/src/ipc/discord/platform/windows.rs index 8f4089a2..f31b8fcd 100644 --- a/src/ipc/discord/platform/windows.rs +++ b/src/ipc/discord/platform/windows.rs @@ -1,38 +1,258 @@ -use std::fs::OpenOptions; -use std::io; -use std::os::windows::fs::OpenOptionsExt; +use std::ffi::OsStr; +use std::fs::File; +use std::os::windows::ffi::OsStrExt; +use std::os::windows::io::{AsRawHandle, FromRawHandle}; +use std::sync::mpsc::Sender; +use std::sync::Arc; +use std::{io, ptr}; +use crate::ipc::bindings::{ + CreateEventW, CreateFileW, GetLastError, GetOverlappedResult, Overlapped, + ReadFile, WriteFile, ERROR_IO_PENDING, FILE_FLAG_OVERLAPPED, GENERIC_READ, + GENERIC_WRITE, INVALID_HANDLE_VALUE, OPEN_EXISTING, +}; use crate::ipc::discord::client::{Connection, RichClient}; +use crate::ipc::discord::error::DiscordError; +use crate::ipc::discord::opcodes::Opcode; +use crate::ipc::discord::utils; +use crate::messages::events::local::ErrorEvent; +use crate::messages::message::Message; +use crate::{local_event, server_event}; impl Connection for RichClient { /// Pipe path can be under the directory `\\\\.\\pipe\\discord-ipc-{i}` where `i` is a number from 0 to 9. fn connect(client_id: u64) -> crate::Result { for i in 0..10 { - match OpenOptions::new() - .read(true) - .write(true) - .access_mode(0x3) - .open(format!("\\\\.\\pipe\\discord-ipc-{i}")) - { - Ok(pipe) => { - return Ok(RichClient { - client_id, - pipe: Some(pipe), - pid: std::process::id(), - is_ready: false.into(), - }) + let pipe_name = format!("\\\\.\\pipe\\discord-ipc-{i}"); + let wide_name: Vec = OsStr::new(&pipe_name) + .encode_wide() + .chain(Some(0)) + .collect(); + + unsafe { + let handle = CreateFileW( + wide_name.as_ptr(), + GENERIC_READ | GENERIC_WRITE, + 0, + ptr::null_mut(), + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + 0 as _, + ); + + if handle == INVALID_HANDLE_VALUE { + let error = io::Error::last_os_error(); + match error.kind() { + io::ErrorKind::NotFound => continue, + _ => return Err(DiscordError::Io(error).into()), + } } - Err(e) => match e.kind() { - io::ErrorKind::NotFound => continue, - _ => return Err(e.into()), - }, + + let pipe = File::from_raw_handle(handle); + let client = RichClient { + client_id, + pipe: Some(Arc::new(pipe)), + pid: std::process::id(), + is_ready: false.into(), + thread_handle: None, + }; + + return Ok(client); } } - Err("Pipe not found".into()) + Err(DiscordError::PipeNotFound.into()) } fn close(&mut self) { self.pipe = None; + if let Some(handle) = self.thread_handle.take() { + handle.join().ok(); + } + } + + fn start_read_thread(&mut self, tx: Sender) -> crate::Result<()> { + if let Some(pipe) = self.pipe.as_ref() { + let pipe = pipe.clone(); + let client_id = self.client_id; + + let handle = std::thread::spawn(move || { + let mut buf = [0u8; 8192]; + let handle = pipe.as_raw_handle(); + + loop { + unsafe { + let h_event = CreateEventW( + ptr::null_mut(), + 1, + 0, + ptr::null_mut(), + ); + + let mut overlapped = Overlapped { + internal: 0, + internal_high: 0, + offset: 0, + offset_high: 0, + h_event, + }; + + let mut bytes_read = 0; + let read_result = ReadFile( + handle, + buf.as_mut_ptr(), + buf.len() as u32, + &mut bytes_read, + &mut overlapped, + ); + + if read_result == 0 { + let error = GetLastError(); + if error != ERROR_IO_PENDING { + tx.send(local_event!( + 0, + Error, + ErrorEvent::new(Box::new( + DiscordError::ConnectionClosed + )) + )) + .ok(); + break; + } + } + + let mut bytes_transferred = 0; + if GetOverlappedResult( + handle, + &mut overlapped, + &mut bytes_transferred, + 1, + ) == 0 + { + tx.send(local_event!( + 0, + Error, + ErrorEvent::new(Box::new( + DiscordError::ConnectionClosed + )) + )) + .ok(); + break; + } + + if bytes_transferred >= 8 { + if let Some((opcode, size)) = utils::decode( + &buf[..bytes_transferred as usize], + ) { + if size > 0 && bytes_transferred >= (8 + size) { + let data = &buf[8..8 + size as usize]; + let data_str = + String::from_utf8_lossy(data); + + match Opcode::from(opcode) { + Opcode::Frame => { + if data_str + .contains("Invalid Client ID") + { + tx.send(local_event!( + 0, + Error, + ErrorEvent::new(Box::new( + DiscordError::InvalidClientId( + client_id.to_string() + ) + )) + )) + .ok(); + break; + } + tx.send(server_event!(0, Ready)) + .ok(); + } + Opcode::Close => { + tx.send(local_event!( + 0, + Error, + ErrorEvent::new(Box::new( + DiscordError::ConnectionClosed + )) + )) + .ok(); + break; + } + _ => {} + } + } + } + } + } + } + }); + + self.thread_handle = Some(handle); + Ok(()) + } else { + Err(DiscordError::PipeNotFound.into()) + } + } + + fn write(&self, opcode: u32, data: Option<&[u8]>) -> crate::Result<()> { + self.pipe.as_ref().map_or( + Err(DiscordError::PipeNotFound.into()), + |pipe| { + let payload = match data { + Some(packet) => { + let mut payload = + utils::encode(opcode, packet.len() as u32); + payload.extend_from_slice(packet); + payload + } + None => utils::encode(opcode, 0), + }; + + unsafe { + let handle = pipe.as_raw_handle(); + let h_event = + CreateEventW(ptr::null_mut(), 1, 0, ptr::null_mut()); + + let mut overlapped = Overlapped { + internal: 0, + internal_high: 0, + offset: 0, + offset_high: 0, + h_event, + }; + + let mut bytes_written = 0; + let write_result = WriteFile( + handle, + payload.as_ptr(), + payload.len() as u32, + &mut bytes_written, + &mut overlapped, + ); + + if write_result == 0 { + let error = GetLastError(); + if error != ERROR_IO_PENDING { + return Err(DiscordError::ConnectionClosed.into()); + } + } + + let mut bytes_transferred = 0; + if GetOverlappedResult( + handle, + &mut overlapped, + &mut bytes_transferred, + 1, + ) == 0 + { + return Err(DiscordError::ConnectionClosed.into()); + } + + Ok(()) + } + }, + ) } } diff --git a/src/ipc/discord/utils.rs b/src/ipc/discord/utils.rs index 3c9e18a1..683ea9c4 100644 --- a/src/ipc/discord/utils.rs +++ b/src/ipc/discord/utils.rs @@ -5,7 +5,14 @@ pub fn encode(opcode: u32, data_length: u32) -> Vec { [opcode.to_le_bytes(), data_length.to_le_bytes()].concat() } -/// Extracts the data length from a byte slice. -pub fn decode(data: &[u8]) -> u32 { - u32::from_le_bytes(data[4..8].try_into().unwrap()) +/// Extracts the opcode and data length from a byte slice. +/// Returns None if the slice is too short. +/// Returns Some(opcode, length) if successful. +pub fn decode(data: &[u8]) -> Option<(u32, u32)> { + if data.len() < 8 { + return None; + } + let opcode = data[..4].try_into().map(u32::from_le_bytes).ok()?; + let length = data[4..8].try_into().map(u32::from_le_bytes).ok()?; + Some((opcode, length)) } diff --git a/src/ipc/mod.rs b/src/ipc/mod.rs index a63e7d86..29a1ca64 100644 --- a/src/ipc/mod.rs +++ b/src/ipc/mod.rs @@ -1,2 +1,3 @@ +pub mod bindings; pub mod discord; pub mod pipe; diff --git a/src/ipc/pipe/platform/windows/client.rs b/src/ipc/pipe/platform/windows/client.rs index 8439b652..e4c365f8 100644 --- a/src/ipc/pipe/platform/windows/client.rs +++ b/src/ipc/pipe/platform/windows/client.rs @@ -5,7 +5,7 @@ use std::sync::mpsc::Sender; use std::sync::Arc; use std::thread::JoinHandle; -use super::{ +use crate::ipc::bindings::{ GetOverlappedResult, Overlapped, ReadFile, WriteFile, ERROR_IO_PENDING, }; use crate::ipc::pipe::{report_error, PipeClientImpl}; diff --git a/src/ipc/pipe/platform/windows/mod.rs b/src/ipc/pipe/platform/windows/mod.rs index 8d0c01a5..c07f47e0 100644 --- a/src/ipc/pipe/platform/windows/mod.rs +++ b/src/ipc/pipe/platform/windows/mod.rs @@ -1,95 +1,2 @@ -#![allow(clippy::upper_case_acronyms)] - pub mod client; pub mod server; - -pub type HANDLE = *mut std::ffi::c_void; -pub type DWORD = u32; -pub type BOOL = i32; -pub type LPCWSTR = *const u16; -pub type LPVOID = *mut std::ffi::c_void; - -pub const INVALID_HANDLE_VALUE: HANDLE = -1isize as HANDLE; -pub const ERROR_PIPE_CONNECTED: DWORD = 535; -pub const ERROR_IO_PENDING: DWORD = 997; -pub const PIPE_ACCESS_DUPLEX: DWORD = 0x00000003; -pub const FILE_FLAG_OVERLAPPED: DWORD = 0x40000000; -pub const PIPE_TYPE_MESSAGE: DWORD = 0x00000004; -pub const PIPE_READMODE_MESSAGE: DWORD = 0x00000002; -pub const PIPE_WAIT: DWORD = 0x00000000; -pub const PIPE_UNLIMITED_INSTANCES: DWORD = 255; - -#[repr(C)] -pub struct Overlapped { - pub internal: usize, - pub internal_high: usize, - pub offset: DWORD, - pub offset_high: DWORD, - pub h_event: HANDLE, -} - -impl Default for Overlapped { - fn default() -> Self { - Self { - internal: 0, - internal_high: 0, - offset: 0, - offset_high: 0, - h_event: unsafe { - CreateEventW(std::ptr::null_mut(), 1, 0, std::ptr::null_mut()) - }, - } - } -} - -extern "system" { - pub fn CreateNamedPipeW( - lpName: LPCWSTR, - dwOpenMode: DWORD, - dwPipeMode: DWORD, - nMaxInstances: DWORD, - nOutBufferSize: DWORD, - nInBufferSize: DWORD, - nDefaultTimeOut: DWORD, - lpSecurityAttributes: LPVOID, - ) -> HANDLE; - - pub fn ConnectNamedPipe( - hNamedPipe: HANDLE, - lpOverlapped: *mut Overlapped, - ) -> BOOL; - - pub fn GetLastError() -> DWORD; - - pub fn CloseHandle(hObject: HANDLE) -> BOOL; - - pub fn CreateEventW( - lpEventAttributes: LPVOID, - bManualReset: BOOL, - bInitialState: BOOL, - lpName: LPCWSTR, - ) -> HANDLE; - - pub fn WriteFile( - hFile: HANDLE, - lpBuffer: *const u8, - nNumberOfBytesToWrite: DWORD, - lpNumberOfBytesWritten: *mut DWORD, - lpOverlapped: *mut Overlapped, - ) -> BOOL; - - pub fn ReadFile( - hFile: HANDLE, - lpBuffer: *mut u8, - nNumberOfBytesToRead: DWORD, - lpNumberOfBytesRead: *mut DWORD, - lpOverlapped: *mut Overlapped, - ) -> BOOL; - - pub fn GetOverlappedResult( - hFile: HANDLE, - lpOverlapped: *mut Overlapped, - lpNumberOfBytesTransferred: *mut DWORD, - bWait: BOOL, - ) -> BOOL; -} diff --git a/src/ipc/pipe/platform/windows/server.rs b/src/ipc/pipe/platform/windows/server.rs index 6ae7cd83..7c40bd89 100644 --- a/src/ipc/pipe/platform/windows/server.rs +++ b/src/ipc/pipe/platform/windows/server.rs @@ -9,7 +9,7 @@ use std::sync::Arc; use std::thread::JoinHandle; use super::client::PipeClient; -use super::{ +use crate::ipc::bindings::{ CloseHandle, ConnectNamedPipe, CreateEventW, CreateNamedPipeW, GetLastError, GetOverlappedResult, Overlapped, ERROR_IO_PENDING, ERROR_PIPE_CONNECTED, FILE_FLAG_OVERLAPPED, HANDLE, INVALID_HANDLE_VALUE, diff --git a/src/messages/events/local/error.rs b/src/messages/events/local/error.rs index b2bd0541..4cf9fc79 100644 --- a/src/messages/events/local/error.rs +++ b/src/messages/events/local/error.rs @@ -1,3 +1,4 @@ +use crate::ipc::discord::error::DiscordError; use crate::messages::events::event::{EventContext, OnEvent}; use crate::util::logger::LogLevel; @@ -16,6 +17,19 @@ impl ErrorEvent { impl OnEvent for ErrorEvent { fn on_event(self, ctx: &mut EventContext) -> crate::Result<()> { + if let Some(discord_error) = self.error.downcast_ref::() { + match discord_error { + DiscordError::InvalidClientId(id) => { + return Err( + format!("'{}' is not a valid client ID", id).into() + ); + } + DiscordError::ConnectionClosed => { + return Err("The connection was forcibly closed".into()); + } + _ => (), + } + } ctx.cord.logger.log( LogLevel::Error, self.error.to_string().into(),