1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6use crate::common::{CommonProperties, MaecObject};
7use crate::error::{MaecError, Result};
8use chrono::{DateTime, Utc};
9
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
12#[serde(rename_all = "snake_case")]
13pub struct Package {
14 #[serde(flatten)]
16 pub common: CommonProperties,
17
18 #[serde(default)]
20 pub maec_objects: Vec<MaecObjectType>,
21
22 #[serde(skip_serializing_if = "Option::is_none")]
24 pub observable_objects: Option<HashMap<String, serde_json::Value>>,
25
26 #[serde(default, skip_serializing_if = "Vec::is_empty")]
28 pub relationships: Vec<crate::Relationship>,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
33#[serde(untagged)]
34pub enum MaecObjectType {
35 Behavior(crate::Behavior),
37 Collection(crate::Collection),
39 MalwareAction(crate::MalwareAction),
41 MalwareFamily(crate::MalwareFamily),
43 MalwareInstance(crate::MalwareInstance),
45}
46
47impl Package {
48 pub fn builder() -> PackageBuilder {
50 PackageBuilder::default()
51 }
52
53 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 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#[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}