1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
//! For testing your extension.

use std::time::Duration;

use crate::{CallbackMessage, Context, State, Value};

use crate::{ArmaCallContext, Caller, Mission, Server, Source};

/// Wrapper around [`crate::Extension`] used for testing.
pub struct Extension(crate::Extension);

const BUFFER_SIZE: libc::size_t = 10240; // The sized used by Arma 3 as of 2021-12-30

#[derive(Debug, PartialEq, Eq)]
/// Result of an event handler
pub enum Result<T, E> {
    /// an event has been handled and the handler is done, the value of T is the return value of the event handler
    Ok(T),
    /// the handler has encountered an error, the value of T is the return value of the event handler
    Err(E),
    /// an event is handled but the handler is not done and should receive another event
    Continue,
    /// the handler reached the specified timeout
    Timeout,
}

impl<T, E> Result<T, E> {
    /// Returns true if the result is an ok result
    pub fn is_ok(&self) -> bool {
        matches!(self, Self::Ok(_))
    }

    /// Returns true if the result is an error
    pub fn is_err(&self) -> bool {
        matches!(self, Self::Err(_))
    }

    /// Returns true if the result is a continue result
    pub fn is_continue(&self) -> bool {
        matches!(self, Self::Continue)
    }

    /// Returns true if the result is a timeout result
    pub fn is_timeout(&self) -> bool {
        matches!(self, Self::Timeout)
    }
}

impl Extension {
    /// Create a new testing Extension
    pub fn new(ext: crate::Extension) -> Self {
        Self(ext)
    }

    #[must_use]
    /// Returns a context for simulating interactions with Arma
    pub fn context(&self) -> Context {
        self.0.context().with_buffer_size(BUFFER_SIZE)
    }

    #[must_use]
    /// Get a reference to the extensions state container
    pub fn state(&self) -> &State {
        &self.0.group.state
    }

    #[must_use]
    #[allow(clippy::too_many_arguments)]
    /// Call a function with Arma call context.
    ///
    /// # Safety
    /// This function is unsafe because it interacts with the C API.
    pub fn call_with_context(
        &self,
        function: &str,
        args: Option<Vec<String>>,
        caller: Caller,
        source: Source,
        mission: Mission,
        server: Server,
        remote_exec_owner: i16,
    ) -> (String, libc::c_int) {
        self.0.context_manager.replace(Some(ArmaCallContext::new(
            caller,
            source,
            mission,
            server,
            remote_exec_owner,
        )));
        unsafe { self.handle_call(function, args) }
    }

    #[must_use]
    /// Call a function without Arma call context.
    ///
    /// # Safety
    /// This function is unsafe because it interacts with the C API.
    ///
    /// # Note
    /// If the `call-context` feature is enabled, this function passes default values for each field.
    pub fn call(&self, function: &str, args: Option<Vec<String>>) -> (String, libc::c_int) {
        self.0.context_manager.replace(None);
        unsafe { self.handle_call(function, args) }
    }

    unsafe fn handle_call(
        &self,
        function: &str,
        args: Option<Vec<String>>,
    ) -> (String, libc::c_int) {
        let mut output = [0; BUFFER_SIZE];
        let len = args.as_ref().map(|a| a.len().try_into().unwrap());
        let mut args_pointer = args.map(|v| {
            v.into_iter()
                .map(|s| std::ffi::CString::new(s).unwrap().into_raw())
                .collect::<Vec<*mut i8>>()
        });
        let res = self.0.group.handle(
            self.context(),
            &self.0.context_manager,
            function,
            output.as_mut_ptr(),
            BUFFER_SIZE,
            args_pointer.as_mut().map(Vec::as_mut_ptr),
            len,
        );
        if let Some(args) = args_pointer {
            for arg in args {
                let _ = std::ffi::CString::from_raw(arg);
            }
        }
        (
            std::ffi::CStr::from_ptr(output.as_ptr())
                .to_str()
                .unwrap()
                .to_string(),
            res,
        )
    }

    /// Create a callback handler
    ///
    /// Returns a Result from the handler if the callback was handled,
    /// or `Result::Timeout` if either no event was received, or the handler
    /// returned `Result::Continue` until the timeout was reached.
    ///
    /// The handler must return a Result indicating the callback was handled to exit
    /// `Result::Continue` will continue to provide events to the handler until another variant is returned
    pub fn callback_handler<F, T, E>(&self, handler: F, timeout: Duration) -> Result<T, E>
    where
        F: Fn(&str, &str, Option<Value>) -> Result<T, E>,
    {
        let (_, rx) = &self.0.callback_channel;
        let deadline = std::time::Instant::now() + timeout;
        loop {
            match rx.recv_deadline(deadline) {
                Ok(CallbackMessage::Call(name, func, data)) => match handler(&name, &func, data) {
                    Result::Ok(value) => return Result::Ok(value),
                    Result::Err(error) => return Result::Err(error),
                    Result::Timeout => return Result::Timeout,
                    Result::Continue => {}
                },
                _ => return Result::Timeout,
            }
        }
    }
}