go up view raw 19923
// 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 <https://www.gnu.org/licenses/>.
//
// 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, 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<unsafe extern "C" fn(ph: MutLtPtr<'ph, Ph<'ph>>) -> c_int>,
    context: Option<Rc<Context>>,
}

struct MockPluginK<'env> {
    _p: PhantomData<&'env PluginEnvironment<'env>>,
}
opaque! {
    impl['env] Opaque for MockPluginK<'env> {
        type Kind<'ph> = UnsafeCell<MockPlugin<'ph, 'env>>;
    }
}

enum MockCallback {
    HookCommand {
        help: Option<Box<str>>,
        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<Holder<'env, MockPluginK<'env>>>,
    //pl: Rc<MockPlugin<'env>>,
    cb: RefCell<MockCallback>,
    name: Option<Box<str>>,
    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<Vec<Pin<Rc<Holder<'env, MockPluginK<'env>>>>>>,
    /// Priority-based hooks.
    hooks: RefCell<Vec<Pin<Rc<Holder<'env, MockHookK<'env>>>>>>,
    /// Timer and fd hooks.
    extra_hooks: RefCell<Vec<Pin<Rc<Holder<'env, MockHookK<'env>>>>>>,
    /// Hook recursion depth.
    depth: Cell<usize>,
    /// Contexts.
    contexts: RefCell<Vec<Rc<Context>>>,
}

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> {
        Holder::<'_, PluginEnvironmentK>::new_with(|builder| builder.build(
            PluginEnvironment {
                plugins: Default::default(),
                hooks: Default::default(),
                extra_hooks: Default::default(),
                depth: Default::default(),
                contexts: Default::default(),
            }
        ))
    }

    /// Loads a plugin.
    pub fn load_plugin<T: PluginFactory>(
        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());

        if plug.as_ref().operate_in(|ph| {
            let ptr = ph.get();
            unsafe {
                // setup deinit function
                (*ptr).deinit = Some(
                    crate::hexchat_plugin_deinit::<'_, T::Plugin<'_>>
                );
                crate::hexchat_plugin_init::<T::Plugin<'_>>(
                    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),
                    ConstLtPtr::from_raw(
                        arg.map(|arg| arg.as_ptr()).unwrap_or(ptr::null())
                    ),
                ) != 0
            }
        }) {
            Ok(())
        } else {
            // remove the plugin - aka retain every plugin that isn't this one
            self.plugins.borrow_mut().retain(|e| {
                !ptr::eq::<Holder<_>>(&**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<Rc<Holder<'env, MockPluginK<'env>>>> {
        let plug = Rc::pin(Holder::<'_, MockPluginK<'env>>::new_with(
            |builder| builder.build({
                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,
                })
            })
        ));
        plug.as_ref().operate_in(|ph| {
            let ptr = ph.get();
            unsafe {
                // initialize plugin_name from filename(!)
                (*ptr).plugin_name = (*ptr).filename.as_ptr();
            }
        });
        plug
    }
}