use super::*; use crate::{ flutter::{self, APP_TYPE_CM, APP_TYPE_MAIN, SESSIONS}, ui_interface::get_api_server, }; use hbb_common::{lazy_static, log, message_proto::PluginRequest}; use serde_derive::{Deserialize, Serialize}; use serde_json; use std::{ collections::HashMap, ffi::{c_char, c_void}, sync::Arc, thread, time::Duration, }; const MSG_TO_RUSTDESK_TARGET: &str = "rustdesk"; const MSG_TO_PEER_TARGET: &str = "peer"; const MSG_TO_UI_TARGET: &str = "ui"; const MSG_TO_CONFIG_TARGET: &str = "config"; const MSG_TO_EXT_SUPPORT_TARGET: &str = "ext-support"; const MSG_TO_RUSTDESK_SIGNATURE_VERIFICATION: &str = "signature_verification"; #[allow(dead_code)] const MSG_TO_UI_FLUTTER_CHANNEL_MAIN: u16 = 0x01 << 0; #[allow(dead_code)] #[cfg(not(any(target_os = "android", target_os = "ios")))] const MSG_TO_UI_FLUTTER_CHANNEL_CM: u16 = 0x01 << 1; #[cfg(any(target_os = "android", target_os = "ios"))] const MSG_TO_UI_FLUTTER_CHANNEL_CM: u16 = 0x01; const MSG_TO_UI_FLUTTER_CHANNEL_REMOTE: u16 = 0x01 << 2; #[allow(dead_code)] const MSG_TO_UI_FLUTTER_CHANNEL_TRANSFER: u16 = 0x01 << 3; #[allow(dead_code)] const MSG_TO_UI_FLUTTER_CHANNEL_FORWARD: u16 = 0x01 << 4; lazy_static::lazy_static! { static ref MSG_TO_UI_FLUTTER_CHANNELS: Arc> = { let channels = HashMap::from([ (MSG_TO_UI_FLUTTER_CHANNEL_MAIN, APP_TYPE_MAIN.to_string()), (MSG_TO_UI_FLUTTER_CHANNEL_CM, APP_TYPE_CM.to_string()), ]); Arc::new(channels) }; } #[derive(Deserialize)] pub struct MsgToRustDesk { pub r#type: String, pub data: Vec, } #[derive(Deserialize)] pub struct SignatureVerification { pub version: String, pub data: Vec, } #[derive(Debug, Deserialize)] struct ConfigToUi { channel: u16, location: String, } #[derive(Debug, Deserialize)] struct MsgToConfig { r#type: String, key: String, value: String, #[serde(skip_serializing_if = "Option::is_none")] ui: Option, // If not None, send msg to ui. } #[derive(Debug, Deserialize)] pub(super) struct MsgToExtSupport { pub r#type: String, pub data: Vec, } #[derive(Debug, Serialize)] struct PluginSignReq { peer_id: String, plugin_id: String, version: String, msg: Vec, } #[derive(Debug, Deserialize)] struct PluginSignResp { signed_msg: Vec, } macro_rules! cb_msg_field { ($field: ident) => { let $field = match cstr_to_string($field) { Err(e) => { let msg = format!("Failed to convert {} to string, {}", stringify!($field), e); log::error!("{}", &msg); return PluginReturn::new(errno::ERR_CALLBACK_INVALID_ARGS, &msg); } Ok(v) => v, }; }; } macro_rules! early_return_value { ($e:expr, $code: ident, $($arg:tt)*) => { match $e { Err(e) => return PluginReturn::new( errno::$code, &format!("Failed to {} '{}'", format_args!($($arg)*), e), ), Ok(v) => v, } }; } /// Callback to send message to peer or ui. /// peer, target, id are utf8 strings(null terminated). /// /// peer: The peer id. /// target: "peer" or "ui". /// id: The id of this plugin. /// content: The content. /// len: The length of the content. /// /// Return null ptr if success. /// Return the error message if failed. `i32-String` without dash, i32 is a signed little-endian number, the String is utf8 string. /// The plugin allocate memory with `libc::malloc` and return the pointer. #[no_mangle] pub(super) extern "C" fn cb_msg( peer: *const c_char, target: *const c_char, id: *const c_char, content: *const c_void, len: usize, ) -> PluginReturn { cb_msg_field!(target); cb_msg_field!(id); match &target as _ { MSG_TO_PEER_TARGET => { cb_msg_field!(peer); if let Some(session) = SESSIONS.write().unwrap().get_mut(&peer) { let content_slice = unsafe { std::slice::from_raw_parts(content as *const u8, len) }; let content_vec = Vec::from(content_slice); let request = PluginRequest { id, content: bytes::Bytes::from(content_vec), ..Default::default() }; session.send_plugin_request(request); PluginReturn::success() } else { PluginReturn::new( errno::ERR_CALLBACK_PEER_NOT_FOUND, &format!("Failed to find session for peer '{}'", peer), ) } } MSG_TO_UI_TARGET => { cb_msg_field!(peer); let content_slice = unsafe { std::slice::from_raw_parts(content as *const u8, len) }; let channel = u16::from_le_bytes([content_slice[0], content_slice[1]]); let content = std::string::String::from_utf8(content_slice[2..].to_vec()) .unwrap_or("".to_string()); push_event_to_ui(channel, &peer, &content); PluginReturn::success() } MSG_TO_CONFIG_TARGET => { cb_msg_field!(peer); let s = early_return_value!( std::str::from_utf8(unsafe { std::slice::from_raw_parts(content as _, len) }), ERR_CALLBACK_INVALID_MSG, "parse msg string" ); // No need to merge the msgs. Handling the msg one by one is ok. let msg = early_return_value!( serde_json::from_str::(s), ERR_CALLBACK_INVALID_MSG, "parse msg '{}'", s ); match &msg.r#type as _ { config::CONFIG_TYPE_SHARED => { let _r = early_return_value!( config::SharedConfig::set(&id, &msg.key, &msg.value), ERR_CALLBACK_INVALID_MSG, "set local config" ); if let Some(ui) = &msg.ui { // No need to set the peer id for location config. push_option_to_ui(ui.channel, &id, "", &msg, ui); } PluginReturn::success() } config::CONFIG_TYPE_PEER => { let _r = early_return_value!( config::PeerConfig::set(&id, &peer, &msg.key, &msg.value), ERR_CALLBACK_INVALID_MSG, "set peer config" ); if let Some(ui) = &msg.ui { push_option_to_ui(ui.channel, &id, &peer, &msg, ui); } PluginReturn::success() } _ => PluginReturn::new( errno::ERR_CALLBACK_TARGET_TYPE, &format!("Unknown target type '{}'", &msg.r#type), ), } } MSG_TO_EXT_SUPPORT_TARGET => { cb_msg_field!(peer); let s = early_return_value!( std::str::from_utf8(unsafe { std::slice::from_raw_parts(content as _, len) }), ERR_CALLBACK_INVALID_MSG, "parse msg string" ); let msg = early_return_value!( serde_json::from_str::(s), ERR_CALLBACK_INVALID_MSG, "parse msg '{}'", s ); super::callback_ext::ext_support_callback(&id, &peer, &msg) } MSG_TO_RUSTDESK_TARGET => handle_msg_to_rustdesk(id, content, len), _ => PluginReturn::new( errno::ERR_CALLBACK_TARGET, &format!("Unknown target '{}'", target), ), } } #[inline] fn is_peer_channel(channel: u16) -> bool { channel & MSG_TO_UI_FLUTTER_CHANNEL_REMOTE != 0 || channel & MSG_TO_UI_FLUTTER_CHANNEL_TRANSFER != 0 || channel & MSG_TO_UI_FLUTTER_CHANNEL_FORWARD != 0 } fn handle_msg_to_rustdesk(id: String, content: *const c_void, len: usize) -> PluginReturn { let s = early_return_value!( std::str::from_utf8(unsafe { std::slice::from_raw_parts(content as _, len) }), ERR_CALLBACK_INVALID_MSG, "parse msg string" ); let msg_to_rustdesk = early_return_value!( serde_json::from_str::(s), ERR_CALLBACK_INVALID_MSG, "parse msg '{}'", s ); match &msg_to_rustdesk.r#type as &str { MSG_TO_RUSTDESK_SIGNATURE_VERIFICATION => request_plugin_sign(id, msg_to_rustdesk), t => PluginReturn::new( errno::ERR_CALLBACK_TARGET_TYPE, &format!( "Unknown target type '{}' for target {}", t, MSG_TO_RUSTDESK_TARGET ), ), } } fn request_plugin_sign(id: String, msg_to_rustdesk: MsgToRustDesk) -> PluginReturn { let signature_data = early_return_value!( std::str::from_utf8(&msg_to_rustdesk.data), ERR_CALLBACK_INVALID_MSG, "parse signature data string" ); let signature_data = early_return_value!( serde_json::from_str::(signature_data), ERR_CALLBACK_INVALID_MSG, "parse signature data '{}'", signature_data ); // to-do: Request server to sign the data. thread::spawn(move || { let sign_url = format!("{}/lic/web/api/plugin-sign", get_api_server()); let client = reqwest::blocking::Client::new(); let req = PluginSignReq { peer_id: crate::ui_interface::get_id(), plugin_id: id.clone(), version: signature_data.version, msg: signature_data.data, }; match client .post(sign_url) .json(&req) .timeout(Duration::from_secs(10)) .send() { Ok(response) => match response.json::() { Ok(sign_resp) => { match super::plugins::plugin_call( &id, super::plugins::METHOD_HANDLE_SIGNATURE_VERIFICATION, "", &sign_resp.signed_msg, ) { Ok(..) => { match super::plugins::plugin_call_get_return( &id, super::plugins::METHOD_HANDLE_STATUS, "", &[], ) { Ok(ret) => { debug_assert!(!ret.msg.is_null(), "msg is null"); let msg = cstr_to_string(ret.msg).unwrap_or_default(); free_c_ptr(ret.msg as _); if ret.code == super::errno::ERR_SUCCESS { log::info!("Plugin '{}' status: '{}'", id, msg); } else { log::error!( "Failed to handle plugin event, id: {}, method: {}, code: {}, msg: {}", id, std::string::String::from_utf8(super::plugins::METHOD_HANDLE_STATUS.to_vec()).unwrap_or_default(), ret.code, msg ); } } Err(e) => { log::error!( "Failed to call status for plugin '{}': {}", &id, e ); } } } Err(e) => { log::error!( "Failed to call signature verification for plugin '{}': {}", &id, e ); } } } Err(e) => { log::error!("Failed to decode response for plugin '{}': {}", &id, e); } }, Err(e) => { log::error!("Failed to request sign for plugin '{}', {}", &id, e); } } }); PluginReturn::success() } fn push_event_to_ui(channel: u16, peer: &str, content: &str) { let mut m = HashMap::new(); m.insert("name", MSG_TO_UI_TYPE_PLUGIN_EVENT); m.insert("peer", &peer); m.insert("content", &content); let event = serde_json::to_string(&m).unwrap_or("".to_string()); for (k, v) in MSG_TO_UI_FLUTTER_CHANNELS.iter() { if channel & k != 0 { let _res = flutter::push_global_event(v as _, event.to_string()); } } if !peer.is_empty() && is_peer_channel(channel) { let _res = flutter::push_session_event( &peer, MSG_TO_UI_TYPE_PLUGIN_EVENT, vec![("peer", &peer), ("content", &content)], ); } } fn push_option_to_ui(channel: u16, id: &str, peer: &str, msg: &MsgToConfig, ui: &ConfigToUi) { let v = [ ("id", id), ("location", &ui.location), ("key", &msg.key), ("value", &msg.value), ]; // Send main and cm let mut m = HashMap::from(v); m.insert("name", MSG_TO_UI_TYPE_PLUGIN_OPTION); let event = serde_json::to_string(&m).unwrap_or("".to_string()); for (k, v) in MSG_TO_UI_FLUTTER_CHANNELS.iter() { if channel & k != 0 { let _res = flutter::push_global_event(v as _, event.to_string()); } } // Send remote, transfer and forward if !peer.is_empty() && is_peer_channel(channel) { let mut v = v.to_vec(); v.push(("peer", &peer)); let _res = flutter::push_session_event(&peer, MSG_TO_UI_TYPE_PLUGIN_OPTION, v); } }