Skip to main content

maec/objects/
malware_instance.rs

1//! MAEC Malware Instance object implementation
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6use crate::common::MaecObject;
7use crate::error::{MaecError, Result};
8use crate::objects::types::{FieldData, Name};
9use crate::Capability;
10
11/// MAEC Malware Instance
12///
13/// A Malware Instance can be thought of as a single member of a Malware Family
14/// that is typically packaged as a binary.
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
16#[serde(rename_all = "snake_case")]
17pub struct MalwareInstance {
18    /// Common MAEC properties
19    #[serde(flatten)]
20    pub common: crate::common::CommonProperties,
21
22    /// References to observable objects (typically STIX file objects)
23    pub instance_object_refs: Vec<String>,
24
25    /// Name of the malware instance
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub name: Option<Name>,
28
29    /// Alternative names/aliases
30    #[serde(default, skip_serializing_if = "Vec::is_empty")]
31    pub aliases: Vec<Name>,
32
33    /// Labels describing the instance (e.g., "worm", "ransomware")
34    #[serde(default, skip_serializing_if = "Vec::is_empty")]
35    pub labels: Vec<String>,
36
37    /// Textual description
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub description: Option<String>,
40
41    /// Field data (delivery vectors, timestamps)
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub field_data: Option<FieldData>,
44
45    /// Operating systems the malware executes on
46    #[serde(default, skip_serializing_if = "Vec::is_empty")]
47    pub os_execution_envs: Vec<String>,
48
49    /// Processor architectures the malware executes on
50    #[serde(default, skip_serializing_if = "Vec::is_empty")]
51    pub architecture_execution_envs: Vec<String>,
52
53    /// Capabilities possessed by the malware
54    #[serde(default, skip_serializing_if = "Vec::is_empty")]
55    pub capabilities: Vec<Capability>,
56
57    /// OS-specific features used
58    #[serde(default, skip_serializing_if = "Vec::is_empty")]
59    pub os_features: Vec<String>,
60}
61
62impl MalwareInstance {
63    /// Creates a new MalwareInstance builder
64    pub fn builder() -> MalwareInstanceBuilder {
65        MalwareInstanceBuilder::default()
66    }
67
68    /// Creates a minimal MalwareInstance with object refs
69    pub fn new(instance_object_refs: Vec<String>) -> Self {
70        Self {
71            common: crate::common::CommonProperties::new("malware-instance", None),
72            instance_object_refs,
73            name: None,
74            aliases: vec![],
75            labels: vec![],
76            description: None,
77            field_data: None,
78            os_execution_envs: vec![],
79            architecture_execution_envs: vec![],
80            capabilities: vec![],
81            os_features: vec![],
82        }
83    }
84
85    /// Validates the MalwareInstance structure
86    pub fn validate(&self) -> Result<()> {
87        if self.common.r#type != "malware-instance" {
88            return Err(MaecError::ValidationError(format!(
89                "type must be 'malware-instance', got '{}'",
90                self.common.r#type
91            )));
92        }
93
94        if !crate::common::is_valid_maec_id(&self.common.id) {
95            return Err(MaecError::InvalidId(self.common.id.clone()));
96        }
97
98        if self.instance_object_refs.is_empty() {
99            return Err(MaecError::MissingField("instance_object_refs"));
100        }
101
102        Ok(())
103    }
104}
105
106impl MaecObject for MalwareInstance {
107    fn id(&self) -> &str {
108        &self.common.id
109    }
110
111    fn type_(&self) -> &str {
112        &self.common.r#type
113    }
114
115    fn created(&self) -> DateTime<Utc> {
116        self.common.created
117    }
118}
119
120/// Builder for MalwareInstance objects
121#[derive(Debug, Default)]
122pub struct MalwareInstanceBuilder {
123    id: Option<String>,
124    instance_object_refs: Vec<String>,
125    name: Option<Name>,
126    aliases: Vec<Name>,
127    labels: Vec<String>,
128    description: Option<String>,
129    field_data: Option<FieldData>,
130    os_execution_envs: Vec<String>,
131    architecture_execution_envs: Vec<String>,
132    capabilities: Vec<Capability>,
133    os_features: Vec<String>,
134}
135
136impl MalwareInstanceBuilder {
137    pub fn id(mut self, id: impl Into<String>) -> Self {
138        self.id = Some(id.into());
139        self
140    }
141
142    pub fn add_instance_object_ref(mut self, ref_id: impl Into<String>) -> Self {
143        self.instance_object_refs.push(ref_id.into());
144        self
145    }
146
147    pub fn instance_object_refs(mut self, refs: Vec<String>) -> Self {
148        self.instance_object_refs = refs;
149        self
150    }
151
152    pub fn name(mut self, name: impl Into<Name>) -> Self {
153        self.name = Some(name.into());
154        self
155    }
156
157    pub fn description(mut self, desc: impl Into<String>) -> Self {
158        self.description = Some(desc.into());
159        self
160    }
161
162    pub fn add_label(mut self, label: impl Into<String>) -> Self {
163        self.labels.push(label.into());
164        self
165    }
166
167    pub fn field_data(mut self, field_data: FieldData) -> Self {
168        self.field_data = Some(field_data);
169        self
170    }
171
172    pub fn add_capability(mut self, capability: Capability) -> Self {
173        self.capabilities.push(capability);
174        self
175    }
176
177    pub fn build(self) -> Result<MalwareInstance> {
178        if self.instance_object_refs.is_empty() {
179            return Err(MaecError::MissingField("instance_object_refs"));
180        }
181
182        let mut common = crate::common::CommonProperties::new("malware-instance", None);
183        if let Some(id) = self.id {
184            common.id = id;
185        }
186
187        let instance = MalwareInstance {
188            common,
189            instance_object_refs: self.instance_object_refs,
190            name: self.name,
191            aliases: self.aliases,
192            labels: self.labels,
193            description: self.description,
194            field_data: self.field_data,
195            os_execution_envs: self.os_execution_envs,
196            architecture_execution_envs: self.architecture_execution_envs,
197            capabilities: self.capabilities,
198            os_features: self.os_features,
199        };
200
201        instance.validate()?;
202        Ok(instance)
203    }
204}