Skip to main content

malbox_plugin_sdk/
result.rs

1//! Named results produced by plugin analysis.
2//!
3//! [`PluginResult`] represents a single output from a plugin. It can be
4//! JSON data, raw bytes, or a file on disk. Each result carries a name
5//! that identifies it in the task output (e.g. `"yara_matches"`).
6
7use crate::error::Result;
8use serde::Serialize;
9use std::path::PathBuf;
10
11/// A named result produced by plugin analysis.
12///
13/// Each variant carries a `name` that identifies the result in the task
14/// report (e.g. `"yara_matches"`, `"extracted_pe"`).
15#[derive(Debug)]
16pub enum PluginResult {
17    /// Structured data serialized as JSON.
18    Json { name: String, data: Vec<u8> },
19    /// Arbitrary binary data.
20    Bytes { name: String, data: Vec<u8> },
21    /// A file on disk - the runtime streams it back to the daemon.
22    File { name: String, path: PathBuf },
23}
24
25impl PluginResult {
26    /// Serialize a value as JSON and wrap it as a named result.
27    pub fn json(name: impl Into<String>, value: &impl Serialize) -> Result<Self> {
28        let data = serde_json::to_vec(value)?;
29        Ok(PluginResult::Json {
30            name: name.into(),
31            data,
32        })
33    }
34
35    /// Wrap raw bytes as a named result.
36    pub fn bytes(name: impl Into<String>, data: Vec<u8>) -> Self {
37        PluginResult::Bytes {
38            name: name.into(),
39            data,
40        }
41    }
42
43    /// Reference a file on disk as a named result. The runtime reads and
44    /// streams the file contents back to the daemon.
45    pub fn file(name: impl Into<String>, path: impl Into<PathBuf>) -> Self {
46        PluginResult::File {
47            name: name.into(),
48            path: path.into(),
49        }
50    }
51
52    /// The name that identifies this result in the task output.
53    pub fn name(&self) -> &str {
54        match self {
55            PluginResult::Json { name, .. } => name,
56            PluginResult::Bytes { name, .. } => name,
57            PluginResult::File { name, .. } => name,
58        }
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65    use serde::Serialize;
66
67    #[derive(Serialize)]
68    struct TestData {
69        key: String,
70        value: i32,
71    }
72
73    #[test]
74    fn plugin_result_json_serializes_correctly() {
75        let data = TestData {
76            key: "hello".into(),
77            value: 42,
78        };
79        let result = PluginResult::json("test_result", &data).unwrap();
80        assert_eq!(result.name(), "test_result");
81        match result {
82            PluginResult::Json { data, .. } => {
83                let parsed: serde_json::Value = serde_json::from_slice(&data).unwrap();
84                assert_eq!(parsed["key"], "hello");
85                assert_eq!(parsed["value"], 42);
86            }
87            _ => panic!("expected Json variant"),
88        }
89    }
90
91    #[test]
92    fn plugin_result_bytes_stores_raw_data() {
93        let result = PluginResult::bytes("raw", vec![0xDE, 0xAD]);
94        assert_eq!(result.name(), "raw");
95        match result {
96            PluginResult::Bytes { data, .. } => assert_eq!(data, vec![0xDE, 0xAD]),
97            _ => panic!("expected Bytes variant"),
98        }
99    }
100
101    #[test]
102    fn plugin_result_file_stores_path() {
103        let result = PluginResult::file("capture", "/tmp/out.pcap");
104        assert_eq!(result.name(), "capture");
105        match result {
106            PluginResult::File { path, .. } => assert_eq!(path, PathBuf::from("/tmp/out.pcap")),
107            _ => panic!("expected File variant"),
108        }
109    }
110
111    #[test]
112    fn plugin_result_constructors_accept_owned_string() {
113        let owned = String::from("owned_name");
114        let r = PluginResult::bytes(owned, vec![1, 2, 3]);
115        assert_eq!(r.name(), "owned_name");
116
117        let r = PluginResult::file(String::from("file_name"), "/tmp/x");
118        assert_eq!(r.name(), "file_name");
119
120        let r = PluginResult::json(String::from("json_name"), &42i32).unwrap();
121        assert_eq!(r.name(), "json_name");
122    }
123}