// This file is part of Hexchat Plugin API Bindings for Rust // Copyright (C) 2022 Soni L. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // Based on hexchat's plugin.c // /* X-Chat * Copyright (C) 2002 Peter Zelezny. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ //! Internal mock test framework. //! //! This API is not stable. Do not rely on this API to be stable. use crate::internals::Ph; use std::cell::Cell; use std::cell::RefCell; use std::cell::UnsafeCell; use std::ffi::CStr; use std::ffi::CString; use std::marker::PhantomData; use std::marker::PhantomPinned; use std::pin::Pin; use std::ptr; use std::rc::Rc; use ::libc::{c_char, c_int}; use ::ltptr::*; use ::selfref::{Holder, NewWith, OperateIn, opaque}; struct Context { } // MUST be repr(C), despite having repr(Rust) types inside it. /// A loaded plugin/complete plugin handle, including private fields. #[repr(C)] struct MockPlugin<'ph, 'env: 'ph> { // NOTE: MUST be first thing in the struct. // we actually pass the whole MockPlugin to the plugin, but we pretend it's // just the Ph. methods: Ph<'ph>, filename: CString, plugin_name: *const c_char, plugin_desc: *const c_char, plugin_vers: *const c_char, free_strings: bool, env: Pin<&'env PluginEnvironment<'env>>, deinit: Option>) -> c_int>, context: Option>, } struct MockPluginK<'env> { _p: PhantomData<&'env PluginEnvironment<'env>>, } opaque! { impl['env] Opaque for MockPluginK<'env> { type Kind<'ph> = UnsafeCell>; } } enum MockCallback { HookCommand { help: Option>, f: unsafe extern "C" fn(word: *const *const libc::c_char, word_eol: *const *const libc::c_char, user_data: *mut libc::c_void) -> libc::c_int, }, HookServer { f: unsafe extern "C" fn(word: *const *const libc::c_char, word_eol: *const *const libc::c_char, user_data: *mut libc::c_void) -> libc::c_int, }, HookServerAttrs { f: unsafe extern "C" fn(word: *const *const libc::c_char, word_eol: *const *const libc::c_char, attrs: *const crate::internals::HexchatEventAttrs, user_data: *mut libc::c_void) -> libc::c_int, }, HookPrint { f: unsafe extern "C" fn(word: *const *const libc::c_char, user_data: *mut libc::c_void) -> libc::c_int, }, HookPrintAttrs { f: unsafe extern "C" fn(word: *const *const libc::c_char, attrs: *const crate::internals::HexchatEventAttrs, user_data: *mut libc::c_void) -> libc::c_int }, HookTimer { tag: libc::c_int, f: unsafe extern "C" fn(user_data: *mut libc::c_void) -> libc::c_int, }, HookFd { tag: libc::c_int, f: unsafe extern "C" fn(fd: libc::c_int, flags: libc::c_int, user_data: *mut libc::c_void) -> libc::c_int }, HookDeleted, } struct MockHook<'env> { pl: Rc>>, //pl: Rc>, cb: RefCell, name: Option>, pri_or_fd: libc::c_int, userdata: *mut libc::c_void, _pin: PhantomPinned, } struct MockHookK<'env> { _p: PhantomData<&'env PluginEnvironment<'env>>, } opaque! { impl['env] Opaque for MockHookK<'env> { type Kind<'ph> = MockHook<'env>; } } /// A plugin environment. An emulated hexchat instance. // TODO: maybe make this a full plugin host, with <'app>? pub struct PluginEnvironment<'env> { /// The loaded plugins. plugins: RefCell>>>>>, /// Priority-based hooks. hooks: RefCell>>>>>, /// Timer and fd hooks. extra_hooks: RefCell>>>>>, /// Hook recursion depth. depth: Cell, /// Contexts. contexts: RefCell>>, } pub struct PluginEnvironmentK; opaque! { impl Opaque for PluginEnvironmentK { type Kind<'env> = PluginEnvironment<'env>; } } unsafe extern "C" fn hexchat_hook_command<'ph>( ph: MutLtPtr<'ph, Ph<'ph>>, name: *const libc::c_char, pri: libc::c_int, /* CALLBACK */ callback: unsafe extern "C" fn(word: *const *const libc::c_char, word_eol: *const *const libc::c_char, user_data: *mut libc::c_void) -> libc::c_int, help_text: *const libc::c_char, userdata: *mut libc::c_void ) -> *const crate::internals::HexchatHook { let ph = MutLtPtr::from_raw(ph.as_raw() as *mut MockPlugin<'ph, '_>); todo!(); } unsafe extern "C" fn hexchat_hook_server<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, name: *const libc::c_char, pri: libc::c_int, /* CALLBACK */ callback: unsafe extern "C" fn(word: *const *const libc::c_char, word_eol: *const *const libc::c_char, user_data: *mut libc::c_void) -> libc::c_int, userdata: *mut libc::c_void) -> *const crate::internals::HexchatHook { todo!(); } unsafe extern "C" fn hexchat_hook_print<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, name: *const libc::c_char, pri: libc::c_int, /* CALLBACK */ callback: unsafe extern "C" fn(word: *const *const libc::c_char, user_data: *mut libc::c_void) -> libc::c_int, userdata: *mut libc::c_void) -> *const crate::internals::HexchatHook { todo!(); } unsafe extern "C" fn hexchat_hook_timer<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, timeout: libc::c_int, /* CALLBACK */ callback: unsafe extern "C" fn(user_data: *mut libc::c_void) -> libc::c_int, userdata: *mut libc::c_void) -> *const crate::internals::HexchatHook { todo!(); } unsafe extern "C" fn hexchat_hook_fd<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, fd: libc::c_int, flags: libc::c_int, /* CALLBACK */ callback: unsafe extern "C" fn(fd: libc::c_int, flags: libc::c_int, user_data: *mut libc::c_void) -> libc::c_int, userdata: *mut libc::c_void) -> *const crate::internals::HexchatHook { todo!(); } unsafe extern "C" fn hexchat_unhook<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, hook: *const crate::internals::HexchatHook) -> *const libc::c_void { todo!(); } unsafe extern "C" fn hexchat_print<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, text: *const libc::c_char) { todo!(); } pub unsafe extern "C" fn hexchat_printf<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, format: *const libc::c_char, ...) { unimplemented!(); } unsafe extern "C" fn hexchat_command<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, command: *const libc::c_char) { todo!(); } unsafe extern "C" fn hexchat_commandf<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, format: *const libc::c_char, ...) { unimplemented!(); } unsafe extern "C" fn hexchat_nickcmp<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, s1: *const libc::c_char, s2: *const libc::c_char) -> libc::c_int { todo!(); } unsafe extern "C" fn hexchat_set_context<'ph>( ph: MutLtPtr<'ph, Ph<'ph>>, ctx: *const crate::internals::HexchatContext, ) -> libc::c_int { let ctx = ctx as *const Context; let ph: *mut MockPlugin<'_, '_> = ph.as_raw() as *mut _; if ctx.is_null() { return 0; } // in C, ctx must NOT be dangling, so we let miri catch it let _ = &*ctx; // in addition to that we also panic if ctx isn't on the list, // because you're supposed to use "Close Context" for cleanup. // if your plugin makes it this far, you're literally passing in garbage. let env: Pin<&PluginEnvironment<'_>> = (*ph).env; if env.contexts.borrow().iter().any(|e| ptr::eq(&**e, ctx)) { (*ph).context = Some(crate::rc_clone_from_raw(ctx)); return 1; } else { panic!("garbage in hexchat_set_context - broken plugin") } } unsafe extern "C" fn hexchat_find_context<'ph>( ph: MutLtPtr<'ph, Ph<'ph>>, servname: *const libc::c_char, channel: *const libc::c_char, ) -> *const crate::internals::HexchatContext { todo!(); } unsafe extern "C" fn hexchat_get_context<'ph>( ph: MutLtPtr<'ph, Ph<'ph>>, ) -> *const crate::internals::HexchatContext { let ph: *mut MockPlugin<'_, '_> = ph.as_raw() as *mut _; let ctx = (*ph).context.as_ref().map(|rc| Rc::as_ptr(rc)); ctx.unwrap_or(ptr::null()) as *const _ } unsafe extern "C" fn hexchat_get_info<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, id: *const libc::c_char) -> *const libc::c_char { todo!(); } unsafe extern "C" fn hexchat_get_prefs<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, name: *const libc::c_char, string: *mut *const libc::c_char, integer: *mut libc::c_int) -> libc::c_int { todo!(); } unsafe extern "C" fn hexchat_list_get<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, name: *const libc::c_char) -> *mut crate::internals::HexchatList { todo!(); } unsafe extern "C" fn hexchat_list_free<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, xlist: *const crate::internals::HexchatList) { todo!(); } unsafe extern "C" fn hexchat_list_fields<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, name: *const libc::c_char) -> *const *const libc::c_char { todo!(); } unsafe extern "C" fn hexchat_list_next<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, xlist: *const crate::internals::HexchatList) -> libc::c_int { todo!(); } unsafe extern "C" fn hexchat_list_str<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, xlist: *const crate::internals::HexchatList, name: *const libc::c_char) -> *const libc::c_char { todo!(); } unsafe extern "C" fn hexchat_list_int<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, xlist: *const crate::internals::HexchatList, name: *const libc::c_char) -> libc::c_int { todo!(); } unsafe extern "C" fn hexchat_plugingui_add<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, filename: *const libc::c_char, name: *const libc::c_char, desc: *const libc::c_char, version: *const libc::c_char, reserved: *mut char) -> *const crate::internals::PluginGuiHandle { todo!(); } unsafe extern "C" fn hexchat_plugingui_remove<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, handle: *const crate::internals::PluginGuiHandle) { todo!(); } unsafe extern "C" fn hexchat_emit_print<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, event_name: *const libc::c_char, ...) -> libc::c_int { todo!(); } unsafe extern "C" fn hexchat_read_fd<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, src: *const libc::c_void, buf: *mut char, len: *mut libc::c_int) -> libc::c_int { todo!(); } unsafe extern "C" fn hexchat_list_time<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, xlist: *const crate::internals::HexchatList, name: *const libc::c_char) -> libc::time_t { todo!(); } unsafe extern "C" fn hexchat_gettext<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, msgid: *const libc::c_char) -> *const libc::c_char { todo!(); } unsafe extern "C" fn hexchat_send_modes<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, targets: *mut *const libc::c_char, ntargets: libc::c_int, modes_per_line: libc::c_int, sign: libc::c_char, mode: libc::c_char) { todo!(); } unsafe extern "C" fn hexchat_strip<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, string: *const libc::c_char, len: libc::c_int, flags: libc::c_int) -> *const libc::c_char { todo!(); } unsafe extern "C" fn hexchat_free<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, ptr: *const libc::c_void) { todo!(); } unsafe extern "C" fn hexchat_pluginpref_set_str<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, var: *const libc::c_char, value: *const libc::c_char) -> libc::c_int { todo!(); } unsafe extern "C" fn hexchat_pluginpref_get_str<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, var: *const libc::c_char, dest: *mut char) -> libc::c_int { todo!(); } unsafe extern "C" fn hexchat_pluginpref_set_int<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, var: *const libc::c_char, value: libc::c_int) -> libc::c_int { todo!(); } unsafe extern "C" fn hexchat_pluginpref_get_int<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, var: *const libc::c_char) -> libc::c_int { todo!(); } unsafe extern "C" fn hexchat_pluginpref_delete<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, var: *const libc::c_char) -> libc::c_int { todo!(); } unsafe extern "C" fn hexchat_pluginpref_list<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, dest: *mut char) -> libc::c_int { todo!(); } unsafe extern "C" fn hexchat_hook_server_attrs<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, name: *const libc::c_char, pri: libc::c_int, /* CALLBACK */ callback: unsafe extern "C" fn(word: *const *const libc::c_char, word_eol: *const *const libc::c_char, attrs: *const crate::internals::HexchatEventAttrs, user_data: *mut libc::c_void) -> libc::c_int, userdata: *mut libc::c_void) -> *const crate::internals::HexchatHook { todo!(); } unsafe extern "C" fn hexchat_hook_print_attrs<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, name: *const libc::c_char, pri: libc::c_int, /* CALLBACK */ callback: unsafe extern "C" fn(word: *const *const libc::c_char, attrs: *const crate::internals::HexchatEventAttrs, user_data: *mut libc::c_void) -> libc::c_int, userdata: *mut libc::c_void) -> *const crate::internals::HexchatHook { todo!(); } unsafe extern "C" fn hexchat_emit_print_attrs<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, attrs: *const crate::internals::HexchatEventAttrs, event_name: *const libc::c_char, ...) -> libc::c_int { todo!(); } unsafe extern "C" fn hexchat_event_attrs_create<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>) -> *mut crate::internals::HexchatEventAttrs { todo!(); } unsafe extern "C" fn hexchat_event_attrs_free<'ph>(ph: MutLtPtr<'ph, Ph<'ph>>, attrs: *mut crate::internals::HexchatEventAttrs) { todo!(); } impl<'env> PluginEnvironment<'env> { /// Creates a new plugin environment. pub fn new() -> Holder<'static, PluginEnvironmentK> { struct NewEnv; impl<'k> NewWith<'k, PluginEnvironmentK> for NewEnv { fn new_with<'env>(self) -> PluginEnvironment<'env> { PluginEnvironment { plugins: Default::default(), hooks: Default::default(), extra_hooks: Default::default(), depth: Default::default(), contexts: Default::default(), } } } Holder::new_with(NewEnv) } /// Loads a plugin. pub fn load_plugin( self: Pin<&'env Self>, filename: CString, arg: Option<&CStr>, ) -> Result<(), ()> { let plug = MockPlugin::new(self, filename); // add the plugin self.plugins.borrow_mut().push(plug.clone()); struct MockPluginInit<'arg, T> { arg: Option<&'arg CStr>, _p: PhantomData } impl<'k, 'env: 'k, 'arg, T> OperateIn<'k, MockPluginK<'env>> for MockPluginInit<'arg, T> where T: PluginFactory { type Out = bool; fn operate_in<'ph>( self, ph: Pin<&'ph UnsafeCell>>, ) -> bool where 'k: 'ph { let ptr = ph.get(); let arg = self.arg; unsafe { // setup deinit function (*ptr).deinit = Some( crate::hexchat_plugin_deinit::<'ph, T::Plugin<'ph>> ); crate::hexchat_plugin_init::<'ph, T::Plugin<'ph>>( MutLtPtr::from_raw(ptr as *mut _), ptr::addr_of_mut!((*ptr).plugin_name), ptr::addr_of_mut!((*ptr).plugin_desc), ptr::addr_of_mut!((*ptr).plugin_vers), arg.map(|arg| arg.as_ptr()).unwrap_or(ptr::null()), ) != 0 } } } if plug.as_ref().operate_in(MockPluginInit:: { arg, _p: PhantomData, }) { Ok(()) } else { // remove the plugin - aka retain every plugin that isn't this one self.plugins.borrow_mut().retain(|e| { !ptr::eq::>(&**e, &*plug) }); Err(()) } } } /// Helper for making [`MockPlugin`]s from [`crate::Plugin`]s. pub trait PluginFactory: 'static { /// The plugin type. type Plugin<'a>: crate::Plugin<'a> + Default; } impl<'ph, 'env> MockPlugin<'ph, 'env> { /// Creates a MockPlugin, but does not load it (yet). fn new( env: Pin<&'env PluginEnvironment<'env>>, filename: CString, ) -> Pin>>> { struct MockPluginNewWith<'env> { filename: CString, env: Pin<&'env PluginEnvironment<'env>> } impl<'k, 'env> NewWith<'k, MockPluginK<'env>> for MockPluginNewWith<'env> where 'env: 'k { fn new_with<'ph>(self) -> UnsafeCell> where 'k: 'ph { let filename = self.filename; let env = self.env; UnsafeCell::new(MockPlugin { methods: Ph { hexchat_hook_command, hexchat_hook_server, hexchat_hook_print, hexchat_hook_timer, hexchat_hook_fd, hexchat_unhook, hexchat_print, userdata: hexchat_printf as *mut _, hexchat_command, hexchat_commandf_do_not_use: hexchat_commandf, hexchat_nickcmp, hexchat_set_context, hexchat_find_context, hexchat_get_context, hexchat_get_info, hexchat_get_prefs, hexchat_list_get, hexchat_list_free, hexchat_list_fields, hexchat_list_next, hexchat_list_str, hexchat_list_int, hexchat_plugingui_add, hexchat_plugingui_remove, hexchat_emit_print, hexchat_read_fd, hexchat_list_time, hexchat_gettext, hexchat_send_modes, hexchat_strip, hexchat_free, hexchat_pluginpref_set_str, hexchat_pluginpref_get_str, hexchat_pluginpref_set_int, hexchat_pluginpref_get_int, hexchat_pluginpref_delete, hexchat_pluginpref_list, hexchat_hook_server_attrs, hexchat_hook_print_attrs, hexchat_emit_print_attrs, hexchat_event_attrs_create, hexchat_event_attrs_free, }, filename: filename, plugin_name: ptr::null(), // not initialized yet! plugin_desc: ptr::null(), plugin_vers: ptr::null(), free_strings: false, env: env, deinit: None, context: None, }) } } let plug = Rc::pin(Holder::new_with(MockPluginNewWith { filename, env, })); struct MockPluginPreInit; impl<'k, 'env: 'k> OperateIn<'k, MockPluginK<'env>> for MockPluginPreInit { type Out = (); fn operate_in<'ph>( self, ph: Pin<&'ph UnsafeCell>>, ) where 'k: 'ph { let ptr = ph.get(); unsafe { // initialize plugin_name from filename(!) (*ptr).plugin_name = (*ptr).filename.as_ptr(); } } } plug.as_ref().operate_in(MockPluginPreInit); plug } }