Merge pull request #4386 from fufesou/feat/plugin_framework

Feat/plugin framework
This commit is contained in:
RustDesk 2023-05-16 15:26:04 +08:00 committed by GitHub
commit 07484e00f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 210 additions and 33 deletions

View File

@ -1,19 +1,27 @@
use super::*; use super::*;
use crate::flutter::{self, APP_TYPE_CM, APP_TYPE_MAIN, SESSIONS}; 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 hbb_common::{lazy_static, log, message_proto::PluginRequest};
use serde_derive::Deserialize; use serde_derive::{Deserialize, Serialize};
use serde_json; use serde_json;
use std::{ use std::{
collections::HashMap, collections::HashMap,
ffi::{c_char, c_void}, ffi::{c_char, c_void},
sync::Arc, sync::Arc,
thread,
time::Duration,
}; };
const MSG_TO_RUSTDESK_TARGET: &str = "rustdesk";
const MSG_TO_PEER_TARGET: &str = "peer"; const MSG_TO_PEER_TARGET: &str = "peer";
const MSG_TO_UI_TARGET: &str = "ui"; const MSG_TO_UI_TARGET: &str = "ui";
const MSG_TO_CONFIG_TARGET: &str = "config"; const MSG_TO_CONFIG_TARGET: &str = "config";
const MSG_TO_EXT_SUPPORT_TARGET: &str = "ext-support"; const MSG_TO_EXT_SUPPORT_TARGET: &str = "ext-support";
const MSG_TO_RUSTDESK_SIGNATURE_VERIFICATION: &str = "signature_verification";
#[allow(dead_code)] #[allow(dead_code)]
const MSG_TO_UI_FLUTTER_CHANNEL_MAIN: u16 = 0x01 << 0; const MSG_TO_UI_FLUTTER_CHANNEL_MAIN: u16 = 0x01 << 0;
#[allow(dead_code)] #[allow(dead_code)]
@ -37,6 +45,18 @@ lazy_static::lazy_static! {
}; };
} }
#[derive(Deserialize)]
pub struct MsgToRustDesk {
pub r#type: String,
pub data: Vec<u8>,
}
#[derive(Deserialize)]
pub struct SignatureVerification {
pub version: String,
pub data: Vec<u8>,
}
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct ConfigToUi { struct ConfigToUi {
channel: u16, channel: u16,
@ -58,6 +78,19 @@ pub(super) struct MsgToExtSupport {
pub data: Vec<u8>, pub data: Vec<u8>,
} }
#[derive(Debug, Serialize)]
struct PluginSignReq {
peer_id: String,
plugin_id: String,
version: String,
msg: Vec<u8>,
}
#[derive(Debug, Deserialize)]
struct PluginSignResp {
signed_msg: Vec<u8>,
}
macro_rules! cb_msg_field { macro_rules! cb_msg_field {
($field: ident) => { ($field: ident) => {
let $field = match cstr_to_string($field) { let $field = match cstr_to_string($field) {
@ -103,12 +136,12 @@ pub(super) extern "C" fn cb_msg(
content: *const c_void, content: *const c_void,
len: usize, len: usize,
) -> PluginReturn { ) -> PluginReturn {
cb_msg_field!(peer);
cb_msg_field!(target); cb_msg_field!(target);
cb_msg_field!(id); cb_msg_field!(id);
match &target as _ { match &target as _ {
MSG_TO_PEER_TARGET => { MSG_TO_PEER_TARGET => {
cb_msg_field!(peer);
if let Some(session) = SESSIONS.write().unwrap().get_mut(&peer) { if let Some(session) = SESSIONS.write().unwrap().get_mut(&peer) {
let content_slice = let content_slice =
unsafe { std::slice::from_raw_parts(content as *const u8, len) }; unsafe { std::slice::from_raw_parts(content as *const u8, len) };
@ -128,6 +161,7 @@ pub(super) extern "C" fn cb_msg(
} }
} }
MSG_TO_UI_TARGET => { MSG_TO_UI_TARGET => {
cb_msg_field!(peer);
let content_slice = unsafe { std::slice::from_raw_parts(content as *const u8, len) }; 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 channel = u16::from_le_bytes([content_slice[0], content_slice[1]]);
let content = std::string::String::from_utf8(content_slice[2..].to_vec()) let content = std::string::String::from_utf8(content_slice[2..].to_vec())
@ -136,6 +170,7 @@ pub(super) extern "C" fn cb_msg(
PluginReturn::success() PluginReturn::success()
} }
MSG_TO_CONFIG_TARGET => { MSG_TO_CONFIG_TARGET => {
cb_msg_field!(peer);
let s = early_return_value!( let s = early_return_value!(
std::str::from_utf8(unsafe { std::slice::from_raw_parts(content as _, len) }), std::str::from_utf8(unsafe { std::slice::from_raw_parts(content as _, len) }),
ERR_CALLBACK_INVALID_MSG, ERR_CALLBACK_INVALID_MSG,
@ -179,6 +214,7 @@ pub(super) extern "C" fn cb_msg(
} }
} }
MSG_TO_EXT_SUPPORT_TARGET => { MSG_TO_EXT_SUPPORT_TARGET => {
cb_msg_field!(peer);
let s = early_return_value!( let s = early_return_value!(
std::str::from_utf8(unsafe { std::slice::from_raw_parts(content as _, len) }), std::str::from_utf8(unsafe { std::slice::from_raw_parts(content as _, len) }),
ERR_CALLBACK_INVALID_MSG, ERR_CALLBACK_INVALID_MSG,
@ -192,6 +228,7 @@ pub(super) extern "C" fn cb_msg(
); );
super::callback_ext::ext_support_callback(&id, &peer, &msg) super::callback_ext::ext_support_callback(&id, &peer, &msg)
} }
MSG_TO_RUSTDESK_TARGET => handle_msg_to_rustdesk(id, content, len),
_ => PluginReturn::new( _ => PluginReturn::new(
errno::ERR_CALLBACK_TARGET, errno::ERR_CALLBACK_TARGET,
&format!("Unknown target '{}'", target), &format!("Unknown target '{}'", target),
@ -206,6 +243,119 @@ fn is_peer_channel(channel: u16) -> bool {
|| channel & MSG_TO_UI_FLUTTER_CHANNEL_FORWARD != 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::<MsgToRustDesk>(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::<SignatureVerification>(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::<PluginSignResp>() {
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) => {
assert!(!ret.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) { fn push_event_to_ui(channel: u16, peer: &str, content: &str) {
let mut m = HashMap::new(); let mut m = HashMap::new();
m.insert("name", MSG_TO_UI_TYPE_PLUGIN_EVENT); m.insert("name", MSG_TO_UI_TYPE_PLUGIN_EVENT);

View File

@ -13,6 +13,8 @@ pub const ERR_PLUGIN_LOAD: i32 = 10001;
pub const ERR_PLUGIN_MSG_INIT: i32 = 10101; pub const ERR_PLUGIN_MSG_INIT: i32 = 10101;
pub const ERR_PLUGIN_MSG_INIT_INVALID: i32 = 10102; pub const ERR_PLUGIN_MSG_INIT_INVALID: i32 = 10102;
pub const ERR_PLUGIN_MSG_GET_LOCAL_PEER_ID: i32 = 10103; pub const ERR_PLUGIN_MSG_GET_LOCAL_PEER_ID: i32 = 10103;
pub const ERR_PLUGIN_SIGNATURE_NOT_VERIFIED: i32 = 10104;
pub const ERR_PLUGIN_SIGNATURE_VERIFICATION_FAILED: i32 = 10105;
// invalid // invalid
pub const ERR_CALL_UNIMPLEMENTED: i32 = 10201; pub const ERR_CALL_UNIMPLEMENTED: i32 = 10201;
pub const ERR_CALL_INVALID_METHOD: i32 = 10202; pub const ERR_CALL_INVALID_METHOD: i32 = 10202;

View File

@ -1,4 +1,4 @@
use hbb_common::{libc, log, ResultType}; use hbb_common::{bail, libc, log, ResultType};
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
use std::env; use std::env;
use std::{ use std::{
@ -146,6 +146,10 @@ fn get_uninstall_file_path() -> ResultType<PathBuf> {
#[inline] #[inline]
fn cstr_to_string(cstr: *const c_char) -> ResultType<String> { fn cstr_to_string(cstr: *const c_char) -> ResultType<String> {
assert!(!cstr.is_null(), "cstr must be a valid pointer");
if cstr.is_null() {
bail!("failed to convert string, the pointer is null");
}
Ok(String::from_utf8(unsafe { Ok(String::from_utf8(unsafe {
CStr::from_ptr(cstr).to_bytes().to_vec() CStr::from_ptr(cstr).to_bytes().to_vec()
})?) })?)

View File

@ -17,6 +17,8 @@ use std::{
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
pub const METHOD_HANDLE_STATUS: &[u8; 14] = b"handle_status\0";
pub const METHOD_HANDLE_SIGNATURE_VERIFICATION: &[u8; 30] = b"handle_signature_verification\0";
const METHOD_HANDLE_UI: &[u8; 10] = b"handle_ui\0"; const METHOD_HANDLE_UI: &[u8; 10] = b"handle_ui\0";
const METHOD_HANDLE_PEER: &[u8; 12] = b"handle_peer\0"; const METHOD_HANDLE_PEER: &[u8; 12] = b"handle_peer\0";
pub const METHOD_HANDLE_LISTEN_EVENT: &[u8; 20] = b"handle_listen_event\0"; pub const METHOD_HANDLE_LISTEN_EVENT: &[u8; 20] = b"handle_listen_event\0";
@ -93,19 +95,21 @@ type CallbackNative = extern "C" fn(
raw: *const c_void, raw: *const c_void,
raw_len: usize, raw_len: usize,
) -> super::native::NativeReturnValue; ) -> super::native::NativeReturnValue;
/// The main function of the plugin on the client(self) side. /// The main function of the plugin.
/// ///
/// method: The method. "handle_ui" or "handle_peer" /// method: The method. "handle_ui" or "handle_peer"
/// peer: The peer id. /// peer: The peer id.
/// args: The arguments. /// args: The arguments.
/// len: The length of the arguments. /// len: The length of the arguments.
type PluginFuncClientCall = extern "C" fn( type PluginFuncCall = extern "C" fn(
method: *const c_char, method: *const c_char,
peer: *const c_char, peer: *const c_char,
args: *const c_void, args: *const c_void,
len: usize, len: usize,
) -> PluginReturn; ) -> PluginReturn;
/// The main function of the plugin on the server(remote) side. /// The main function of the plugin.
/// This function is called mainly for handling messages from the peer,
/// and then send messages back to the peer.
/// ///
/// method: The method. "handle_ui" or "handle_peer" /// method: The method. "handle_ui" or "handle_peer"
/// peer: The peer id. /// peer: The peer id.
@ -114,7 +118,7 @@ type PluginFuncClientCall = extern "C" fn(
/// out: The output. /// out: The output.
/// The plugin allocate memory with `libc::malloc` and return the pointer. /// The plugin allocate memory with `libc::malloc` and return the pointer.
/// out_len: The length of the output. /// out_len: The length of the output.
type PluginFuncServerCall = extern "C" fn( type PluginFuncCallWithOutData = extern "C" fn(
method: *const c_char, method: *const c_char,
peer: *const c_char, peer: *const c_char,
args: *const c_void, args: *const c_void,
@ -138,6 +142,7 @@ struct Callbacks {
} }
#[derive(Serialize)] #[derive(Serialize)]
#[repr(C)]
struct InitInfo { struct InitInfo {
is_server: bool, is_server: bool,
} }
@ -247,8 +252,8 @@ make_plugin!(
reset: PluginFuncReset, reset: PluginFuncReset,
clear: PluginFuncClear, clear: PluginFuncClear,
desc: PluginFuncDesc, desc: PluginFuncDesc,
client_call: PluginFuncClientCall, call: PluginFuncCall,
server_call: PluginFuncServerCall call_with_out_data: PluginFuncCallWithOutData
); );
#[derive(Serialize)] #[derive(Serialize)]
@ -295,6 +300,7 @@ pub(super) fn load_plugins(uninstalled_ids: &HashSet<String>) -> ResultType<()>
} }
fn load_plugin_dir(dir: &PathBuf) { fn load_plugin_dir(dir: &PathBuf) {
log::debug!("Begin load plugin dir: {}", dir.display());
if let Ok(rd) = std::fs::read_dir(dir) { if let Ok(rd) = std::fs::read_dir(dir) {
for entry in rd { for entry in rd {
match entry { match entry {
@ -348,6 +354,8 @@ pub fn reload_plugin(id: &str) -> ResultType<()> {
} }
fn load_plugin_path(path: &str) -> ResultType<()> { fn load_plugin_path(path: &str) -> ResultType<()> {
log::info!("Begin load plugin {}", path);
let plugin = Plugin::new(path)?; let plugin = Plugin::new(path)?;
let desc = plugin.desc()?; let desc = plugin.desc()?;
@ -392,7 +400,7 @@ fn load_plugin_path(path: &str) -> ResultType<()> {
// add plugins // add plugins
PLUGINS.write().unwrap().insert(id.clone(), plugin); PLUGINS.write().unwrap().insert(id.clone(), plugin);
log::info!("Plugin {} loaded", id); log::info!("Plugin {} loaded, {}", id, path);
Ok(()) Ok(())
} }
@ -408,17 +416,15 @@ pub fn load_plugin(id: &str) -> ResultType<()> {
Ok(()) Ok(())
} }
#[inline]
fn handle_event(method: &[u8], id: &str, peer: &str, event: &[u8]) -> ResultType<()> { fn handle_event(method: &[u8], id: &str, peer: &str, event: &[u8]) -> ResultType<()> {
let mut peer: String = peer.to_owned(); let mut peer: String = peer.to_owned();
peer.push('\0'); peer.push('\0');
match PLUGINS.read().unwrap().get(id) { plugin_call(id, method, &peer, event)
Some(plugin) => { }
let mut ret = (plugin.client_call)(
method.as_ptr() as _, pub fn plugin_call(id: &str, method: &[u8], peer: &str, event: &[u8]) -> ResultType<()> {
peer.as_ptr() as _, let mut ret = plugin_call_get_return(id, method, peer, event)?;
event.as_ptr() as _,
event.len(),
);
if ret.is_success() { if ret.is_success() {
Ok(()) Ok(())
} else { } else {
@ -431,7 +437,22 @@ fn handle_event(method: &[u8], id: &str, peer: &str, event: &[u8]) -> ResultType
msg msg
); );
} }
} }
#[inline]
pub fn plugin_call_get_return(
id: &str,
method: &[u8],
peer: &str,
event: &[u8],
) -> ResultType<PluginReturn> {
match PLUGINS.read().unwrap().get(id) {
Some(plugin) => Ok((plugin.call)(
method.as_ptr() as _,
peer.as_ptr() as _,
event.as_ptr() as _,
event.len(),
)),
None => bail!("Plugin {} not found", id), None => bail!("Plugin {} not found", id),
} }
} }
@ -468,7 +489,7 @@ fn _handle_listen_event(event: String, peer: String) {
for id in plugins { for id in plugins {
match PLUGINS.read().unwrap().get(&id) { match PLUGINS.read().unwrap().get(&id) {
Some(plugin) => { Some(plugin) => {
let mut ret = (plugin.client_call)( let mut ret = (plugin.call)(
METHOD_HANDLE_LISTEN_EVENT.as_ptr() as _, METHOD_HANDLE_LISTEN_EVENT.as_ptr() as _,
peer.as_ptr() as _, peer.as_ptr() as _,
evt_bytes.as_ptr() as _, evt_bytes.as_ptr() as _,
@ -506,7 +527,7 @@ pub fn handle_client_event(id: &str, peer: &str, event: &[u8]) -> Message {
Some(plugin) => { Some(plugin) => {
let mut out = std::ptr::null_mut(); let mut out = std::ptr::null_mut();
let mut out_len: usize = 0; let mut out_len: usize = 0;
let mut ret = (plugin.server_call)( let mut ret = (plugin.call_with_out_data)(
METHOD_HANDLE_PEER.as_ptr() as _, METHOD_HANDLE_PEER.as_ptr() as _,
peer.as_ptr() as _, peer.as_ptr() as _,
event.as_ptr() as _, event.as_ptr() as _,