//! Cliente IPC para comunicacao com o Raven Service //! //! Este modulo permite que o app Tauri se comunique com o Raven Service //! via Named Pipes para executar operacoes privilegiadas. #![allow(dead_code)] use serde::{Deserialize, Serialize}; use std::io::{BufRead, BufReader, Write}; use std::time::Duration; use thiserror::Error; const PIPE_NAME: &str = r"\\.\pipe\RavenService"; #[derive(Debug, Error)] pub enum ServiceClientError { #[error("Servico nao disponivel: {0}")] ServiceUnavailable(String), #[error("Erro de comunicacao: {0}")] CommunicationError(String), #[error("Erro de serializacao: {0}")] SerializationError(#[from] serde_json::Error), #[error("Erro do servico: {message} (code: {code})")] ServiceError { code: i32, message: String }, #[error("Timeout aguardando resposta")] Timeout, } #[derive(Debug, Serialize)] struct Request { id: String, method: String, params: serde_json::Value, } #[derive(Debug, Deserialize)] struct Response { id: String, result: Option, error: Option, } #[derive(Debug, Deserialize)] struct ErrorResponse { code: i32, message: String, } // ============================================================================= // Tipos de Resultado // ============================================================================= #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UsbPolicyResult { pub success: bool, pub policy: String, pub error: Option, pub applied_at: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RustdeskResult { pub id: String, pub password: String, pub installed_version: Option, pub updated: bool, pub last_provisioned_at: i64, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RustdeskStatus { pub installed: bool, pub running: bool, pub id: Option, pub version: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HealthCheckResult { pub status: String, pub service: String, pub version: String, pub timestamp: i64, } // ============================================================================= // Cliente // ============================================================================= /// Verifica se o servico esta disponivel pub fn is_service_available() -> bool { health_check().is_ok() } /// Verifica saude do servico pub fn health_check() -> Result { let response = call_service("health_check", serde_json::json!({}))?; serde_json::from_value(response).map_err(|e| e.into()) } /// Aplica politica de USB pub fn apply_usb_policy(policy: &str) -> Result { let response = call_service( "apply_usb_policy", serde_json::json!({ "policy": policy }), )?; serde_json::from_value(response).map_err(|e| e.into()) } /// Obtem politica de USB atual pub fn get_usb_policy() -> Result { let response = call_service("get_usb_policy", serde_json::json!({}))?; response .get("policy") .and_then(|p| p.as_str()) .map(String::from) .ok_or_else(|| ServiceClientError::CommunicationError("Resposta invalida".into())) } /// Provisiona RustDesk pub fn provision_rustdesk( config: Option<&str>, password: Option<&str>, machine_id: Option<&str>, ) -> Result { let params = serde_json::json!({ "config": config, "password": password, "machineId": machine_id, }); let response = call_service("provision_rustdesk", params)?; serde_json::from_value(response).map_err(|e| e.into()) } /// Obtem status do RustDesk pub fn get_rustdesk_status() -> Result { let response = call_service("get_rustdesk_status", serde_json::json!({}))?; serde_json::from_value(response).map_err(|e| e.into()) } // ============================================================================= // Comunicacao IPC // ============================================================================= fn call_service( method: &str, params: serde_json::Value, ) -> Result { // Gera ID unico para a requisicao let id = uuid::Uuid::new_v4().to_string(); let request = Request { id: id.clone(), method: method.to_string(), params, }; // Serializa requisicao let request_json = serde_json::to_string(&request)?; // Conecta ao pipe let mut pipe = connect_to_pipe()?; // Envia requisicao writeln!(pipe, "{}", request_json).map_err(|e| { ServiceClientError::CommunicationError(format!("Erro ao enviar requisicao: {}", e)) })?; pipe.flush().map_err(|e| { ServiceClientError::CommunicationError(format!("Erro ao flush: {}", e)) })?; // Le resposta let mut reader = BufReader::new(pipe); let mut response_line = String::new(); reader.read_line(&mut response_line).map_err(|e| { ServiceClientError::CommunicationError(format!("Erro ao ler resposta: {}", e)) })?; // Parse da resposta let response: Response = serde_json::from_str(&response_line)?; // Verifica se o ID bate if response.id != id { return Err(ServiceClientError::CommunicationError( "ID de resposta nao corresponde".into(), )); } // Verifica erro if let Some(error) = response.error { return Err(ServiceClientError::ServiceError { code: error.code, message: error.message, }); } // Retorna resultado response .result .ok_or_else(|| ServiceClientError::CommunicationError("Resposta sem resultado".into())) } #[cfg(target_os = "windows")] fn connect_to_pipe() -> Result { // Tenta conectar ao pipe com retry let mut attempts = 0; let max_attempts = 3; loop { match std::fs::OpenOptions::new() .read(true) .write(true) .open(PIPE_NAME) { Ok(file) => return Ok(file), Err(e) => { attempts += 1; if attempts >= max_attempts { return Err(ServiceClientError::ServiceUnavailable(format!( "Nao foi possivel conectar ao servico apos {} tentativas: {}", max_attempts, e ))); } std::thread::sleep(Duration::from_millis(500)); } } } } #[cfg(not(target_os = "windows"))] fn connect_to_pipe() -> Result { Err(ServiceClientError::ServiceUnavailable( "Named Pipes so estao disponiveis no Windows".into(), )) }