mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Add basic C FFI for chat plugins and corresponding plugin loader
This commit is contained in:
11
Cargo.lock
generated
11
Cargo.lock
generated
@@ -2049,6 +2049,16 @@ dependencies = [
|
|||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libloading"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libm"
|
name = "libm"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
@@ -2608,6 +2618,7 @@ dependencies = [
|
|||||||
name = "oj_rc_plugins"
|
name = "oj_rc_plugins"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"libloading",
|
||||||
"log",
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ async fn main() -> std::io::Result<()> {
|
|||||||
let cubes = oj_rc_core::persist::config::ConfigImpl::load(&args.assets).expect("Bad config data");
|
let cubes = oj_rc_core::persist::config::ConfigImpl::load(&args.assets).expect("Bad config data");
|
||||||
let users = std::sync::Arc::new(oj_rc_core::persist::user::UserImpl::load(&args.data, &cubes).await.expect("Bad user data"));
|
let users = std::sync::Arc::new(oj_rc_core::persist::user::UserImpl::load(&args.data, &cubes).await.expect("Bad user data"));
|
||||||
|
|
||||||
let chat_system = state::chat::ChatImpl::new(<oj_rc_core::ConfigImpl as ConfigProvider<()>>::chat_system_config(&cubes)).expect("Bad chat config data");
|
let chat_plugin_path = std::path::PathBuf::from(&args.data).join("plugins/chat");
|
||||||
|
let chat_system = state::chat::ChatImpl::new(<oj_rc_core::ConfigImpl as ConfigProvider<()>>::chat_system_config(&cubes), chat_plugin_path).expect("Bad chat config data");
|
||||||
|
|
||||||
let server = std::sync::Arc::new(polariton_server::Server::new(operations::handler(chat_system.clone(), &cubes), polariton_server::events::EventsHandler::new()));
|
let server = std::sync::Arc::new(polariton_server::Server::new(operations::handler(chat_system.clone(), &cubes), polariton_server::events::EventsHandler::new()));
|
||||||
|
|
||||||
|
|||||||
@@ -51,3 +51,43 @@ impl oj_rc_plugins::chat::ChatProvider for ProviderWrapper {
|
|||||||
tokio::task::spawn(Self::do_send_message(self.provider.clone(), message.to_owned(), channel.to_owned(), username.to_owned()));
|
tokio::task::spawn(Self::do_send_message(self.provider.clone(), message.to_owned(), channel.to_owned(), username.to_owned()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn load_chat_plugins(from_dir: impl AsRef<std::path::Path>) -> Vec<Box<dyn oj_rc_plugins::chat::ChatPlugin>> {
|
||||||
|
let path_ref = from_dir.as_ref();
|
||||||
|
if path_ref.exists() {
|
||||||
|
log::warn!("Chat plugins are experimental and insecure");
|
||||||
|
} else {
|
||||||
|
log::info!("Not loading chat plugins; {} does not exist", path_ref.display());
|
||||||
|
return Vec::default();
|
||||||
|
}
|
||||||
|
match path_ref.read_dir() {
|
||||||
|
Ok(dir) => {
|
||||||
|
let mut plugins = Vec::new();
|
||||||
|
for entry in dir {
|
||||||
|
match entry {
|
||||||
|
Ok(entry) => {
|
||||||
|
if entry.path().is_file() {
|
||||||
|
match oj_rc_plugins::chat::ChatCPlugin::new(entry.path()) {
|
||||||
|
Ok(plugin) => {
|
||||||
|
plugins.push(Box::new(plugin) as Box<dyn oj_rc_plugins::chat::ChatPlugin>);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Failed to load chat plugin {}: {}", entry.path().display(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Failed to read entry in {}: {}", path_ref.display(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
plugins
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to load chat plugins from {}: {}", path_ref.display(), e);
|
||||||
|
Vec::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ pub struct ChatProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ChatProvider {
|
impl ChatProvider {
|
||||||
pub fn new(conf: oj_rc_core::persist::config::ChatSystemConfig) -> std::io::Result<Self> {
|
pub fn new(conf: oj_rc_core::persist::config::ChatSystemConfig, dir: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
chat_system: std::sync::Arc::new(tokio::sync::RwLock::new(crate::state::chat::ChatSystem::new(conf)?)),
|
chat_system: std::sync::Arc::new(tokio::sync::RwLock::new(crate::state::chat::ChatSystem::new(conf, dir)?)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,12 +198,12 @@ impl ChatSystem {
|
|||||||
handle.send_private_message(response);
|
handle.send_private_message(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(config: oj_rc_core::persist::config::ChatSystemConfig) -> std::io::Result<Self> {
|
pub fn new(config: oj_rc_core::persist::config::ChatSystemConfig, dir: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
chats: HashMap::new(),
|
chats: HashMap::new(),
|
||||||
online_users: HashMap::new(),
|
online_users: HashMap::new(),
|
||||||
config: super::ChatSystemConfig::from_persist(config)?,
|
config: super::ChatSystemConfig::from_persist(config)?,
|
||||||
plugin: crate::PluginWrapper::new(vec![]), // TODO construct plugins
|
plugin: crate::PluginWrapper::new(crate::plugin_wrapper::load_chat_plugins(dir)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,3 +9,4 @@ readme.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
libloading = "0.9"
|
||||||
|
|||||||
86
rc_plugins/src/chat/c_binding.rs
Normal file
86
rc_plugins/src/chat/c_binding.rs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
//! The foreign function interface implementation for writing ChatPlugins in different shared objects/libraries.
|
||||||
|
//! This is sort of cursed, I'm sorry in advance.
|
||||||
|
use std::ffi::{CString, c_char, CStr};
|
||||||
|
|
||||||
|
const SET_SEND_MESSAGE_CALLBACK_SYMBOL_NAME: &[u8] = b"oj_rc_set_chat_send_message_callback";
|
||||||
|
const SET_SEND_MESSAGE_CALLBACK_SYMBOL_NAME_STR: &str = "oj_rc_set_chat_send_message_callback";
|
||||||
|
|
||||||
|
const ON_MESSAGE_SYMBOL_NAME: &[u8] = b"oj_rc_on_chat_message";
|
||||||
|
const ON_MESSAGE_SYMBOL_NAME_STR: &str = "oj_rc_on_chat_message";
|
||||||
|
|
||||||
|
pub struct ChatCPlugin {
|
||||||
|
dll: libloading::Library,
|
||||||
|
pretty_name: String,
|
||||||
|
provider: std::sync::Mutex<Option<std::sync::Arc<Box<dyn super::ChatProvider>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChatCPlugin {
|
||||||
|
pub fn new(file: impl AsRef<std::path::Path>) -> Result<Self, libloading::Error> {
|
||||||
|
let dll = unsafe { libloading::Library::new(file.as_ref()) }?;
|
||||||
|
Ok(Self {
|
||||||
|
dll,
|
||||||
|
pretty_name: file.as_ref().to_string_lossy().to_string(),
|
||||||
|
provider: std::sync::Mutex::new(None),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ContextC {
|
||||||
|
provider: std::sync::Weak<Box<dyn super::ChatProvider>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::ChatPlugin for ChatCPlugin {
|
||||||
|
fn set_provider(&self, provider: std::sync::Arc<Box<dyn super::ChatProvider>>) {
|
||||||
|
let func: libloading::Symbol<unsafe extern "C" fn(*const ContextC, unsafe extern "C" fn(*const ContextC, *const c_char, *const c_char, *const c_char))> = match unsafe { self.dll.get(SET_SEND_MESSAGE_CALLBACK_SYMBOL_NAME) } {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to find symbol {} in library {}: {}", SET_SEND_MESSAGE_CALLBACK_SYMBOL_NAME_STR, self.pretty_name, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
*self.provider.lock().unwrap() = Some(provider.clone());
|
||||||
|
let weak_provider = std::sync::Arc::downgrade(&provider);
|
||||||
|
let ctx = Box::new(ContextC {
|
||||||
|
provider: weak_provider,
|
||||||
|
});
|
||||||
|
extern "C" fn callback(ctx: *const ContextC, msg: *const c_char, chann: *const c_char, usern: *const c_char) {
|
||||||
|
let ctx = if let Some(ctx) = unsafe { ctx.as_ref() } { ctx } else { return };
|
||||||
|
let msg = unsafe { CStr::from_ptr(msg) };
|
||||||
|
let chann = unsafe { CStr::from_ptr(chann) };
|
||||||
|
let usern = unsafe { CStr::from_ptr(usern) };
|
||||||
|
if let Some(provider) = ctx.provider.upgrade() {
|
||||||
|
match (msg.to_str(), chann.to_str(), usern.to_str()) {
|
||||||
|
(Ok(message), Ok(channel), Ok(username)) => {
|
||||||
|
provider.send_message(message, channel, username);
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
log::warn!("{} called with invalid string parameter", SET_SEND_MESSAGE_CALLBACK_SYMBOL_NAME_STR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log::warn!("Chat provider callback invoked after it has been dropped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
func(&*ctx, callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_message(&self, message: &str, channel: &str, username: &str) {
|
||||||
|
let func: libloading::Symbol<unsafe extern "C" fn(*const c_char, *const c_char, *const c_char)> = match unsafe { self.dll.get(ON_MESSAGE_SYMBOL_NAME) } {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to find symbol {} in library {}: {}", ON_MESSAGE_SYMBOL_NAME_STR, self.pretty_name, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let message = CString::new(message).unwrap_or_default();
|
||||||
|
let channel = CString::new(channel).unwrap_or_default();
|
||||||
|
let username = CString::new(username).unwrap_or_default();
|
||||||
|
unsafe {
|
||||||
|
func(message.as_c_str().as_ptr(), channel.as_c_str().as_ptr(), username.as_c_str().as_ptr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::Plugin for ChatCPlugin {}
|
||||||
@@ -1,2 +1,5 @@
|
|||||||
mod plugin;
|
mod plugin;
|
||||||
pub use plugin::{ChatProvider, ChatPlugin};
|
pub use plugin::{ChatProvider, ChatPlugin};
|
||||||
|
|
||||||
|
mod c_binding;
|
||||||
|
pub use c_binding::ChatCPlugin;
|
||||||
|
|||||||
Reference in New Issue
Block a user