Skip to main content

maec/objects/
package.rs

1//! MAEC Package object implementation
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6use crate::common::{CommonProperties, MaecObject};
7use crate::error::{MaecError, Result};
8use chrono::{DateTime, Utc};
9
10/// Top-level MAEC Package
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
12#[serde(rename_all = "snake_case")]
13pub struct Package {
14    /// Common MAEC properties
15    #[serde(flatten)]
16    pub common: CommonProperties,
17
18    /// MAEC objects contained in this package
19    #[serde(default)]
20    pub maec_objects: Vec<MaecObjectType>,
21
22    /// STIX Cyber Observable Objects relevant to the package
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub observable_objects: Option<HashMap<String, serde_json::Value>>,
25
26    /// Relationships between objects in the package
27    #[serde(default, skip_serializing_if = "Vec::is_empty")]
28    pub relationships: Vec<crate::Relationship>,
29}
30
31/// MAEC object types that can be contained in a Package
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
33#[serde(untagged)]
34pub enum MaecObjectType {
35    /// Behavior object
36    Behavior(crate::Behavior),
37    /// Collection object
38    Collection(crate::Collection),
39    /// Malware Action object
40    MalwareAction(crate::MalwareAction),
41    /// Malware Family object
42    MalwareFamily(crate::MalwareFamily),
43    /// Malware Instance object
44    MalwareInstance(crate::MalwareInstance),
45}
46
47impl Package {
48    /// Creates a new Package builder
49    pub fn builder() -> PackageBuilder {
50        PackageBuilder::default()
51    }
52
53    /// Creates a new minimal Package with required fields
54    pub fn new() -> Self {
55        Self {
56            common: CommonProperties::new("package", None),
57            maec_objects: vec![],
58            observable_objects: None,
59            relationships: vec![],
60        }
61    }
62
63    /// Validates the Package structure
64    pub fn validate(&self) -> Result<()> {
65        if self.common.r#type != "package" {
66            return Err(MaecError::ValidationError(format!(
67                "type must be 'package', got '{}'",
68                self.common.r#type
69            )));
70        }
71
72        if self.common.schema_version.as_deref() != Some("5.0") {
73            return Err(MaecError::ValidationError(format!(
74                "schema_version must be '5.0', got '{:?}'",
75                self.common.schema_version
76            )));
77        }
78
79        if !crate::common::is_valid_maec_id(&self.common.id) {
80            return Err(MaecError::InvalidId(self.common.id.clone()));
81        }
82
83        Ok(())
84    }
85
86    pub fn malware_families(&self) -> Vec<&crate::MalwareFamily> {
87        self.maec_objects
88            .iter()
89            .filter_map(|obj| match obj {
90                MaecObjectType::MalwareFamily(family) => Some(family),
91                _ => None,
92            })
93            .collect()
94    }
95
96    pub fn malware_instances(&self) -> Vec<&crate::MalwareInstance> {
97        self.maec_objects
98            .iter()
99            .filter_map(|obj| match obj {
100                MaecObjectType::MalwareInstance(instance) => Some(instance),
101                _ => None,
102            })
103            .collect()
104    }
105
106    pub fn behaviors(&self) -> Vec<&crate::Behavior> {
107        self.maec_objects
108            .iter()
109            .filter_map(|obj| match obj {
110                MaecObjectType::Behavior(behavior) => Some(behavior),
111                _ => None,
112            })
113            .collect()
114    }
115
116    pub fn malware_actions(&self) -> Vec<&crate::MalwareAction> {
117        self.maec_objects
118            .iter()
119            .filter_map(|obj| match obj {
120                MaecObjectType::MalwareAction(action) => Some(action),
121                _ => None,
122            })
123            .collect()
124    }
125}
126
127impl MaecObject for Package {
128    fn id(&self) -> &str {
129        &self.common.id
130    }
131    fn type_(&self) -> &str {
132        &self.common.r#type
133    }
134    fn created(&self) -> DateTime<Utc> {
135        self.common.created
136    }
137}
138
139impl Default for Package {
140    fn default() -> Self {
141        Self::new()
142    }
143}
144
145/// Builder for Package objects
146#[derive(Debug, Default)]
147pub struct PackageBuilder {
148    id: Option<String>,
149    schema_version: Option<String>,
150    maec_objects: Vec<MaecObjectType>,
151    observable_objects: Option<HashMap<String, serde_json::Value>>,
152    relationships: Vec<crate::Relationship>,
153}
154
155impl PackageBuilder {
156    pub fn id(mut self, id: impl Into<String>) -> Self {
157        self.id = Some(id.into());
158        self
159    }
160
161    pub fn schema_version(mut self, version: impl Into<String>) -> Self {
162        self.schema_version = Some(version.into());
163        self
164    }
165
166    pub fn add_object(mut self, object: MaecObjectType) -> Self {
167        self.maec_objects.push(object);
168        self
169    }
170
171    pub fn add_malware_family(mut self, family: crate::MalwareFamily) -> Self {
172        self.maec_objects
173            .push(MaecObjectType::MalwareFamily(family));
174        self
175    }
176
177    pub fn add_malware_instance(mut self, instance: crate::MalwareInstance) -> Self {
178        self.maec_objects
179            .push(MaecObjectType::MalwareInstance(instance));
180        self
181    }
182
183    pub fn add_behavior(mut self, behavior: crate::Behavior) -> Self {
184        self.maec_objects.push(MaecObjectType::Behavior(behavior));
185        self
186    }
187
188    pub fn add_malware_action(mut self, action: crate::MalwareAction) -> Self {
189        self.maec_objects
190            .push(MaecObjectType::MalwareAction(action));
191        self
192    }
193
194    pub fn build(self) -> Result<Package> {
195        let mut common = CommonProperties::new("package", None);
196        if let Some(id) = self.id {
197            common.id = id;
198        }
199        if let Some(version) = self.schema_version {
200            common.schema_version = Some(version);
201        }
202
203        let package = Package {
204            common,
205            maec_objects: self.maec_objects,
206            observable_objects: self.observable_objects,
207            relationships: self.relationships,
208        };
209
210        package.validate()?;
211        Ok(package)
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218
219    #[test]
220    fn test_package_new() {
221        let package = Package::new();
222        assert_eq!(package.common.r#type, "package");
223        assert_eq!(package.common.schema_version, Some("5.0".to_string()));
224        assert!(package.common.id.starts_with("package--"));
225    }
226
227    #[test]
228    fn test_package_builder() {
229        let package = Package::builder().schema_version("5.0").build().unwrap();
230        assert_eq!(package.common.r#type, "package");
231        assert_eq!(package.common.schema_version, Some("5.0".to_string()));
232    }
233}