1use std::collections::HashMap;
2
3use chrono::{DateTime, Utc};
4use serde::{Serialize, Deserialize};
5use serde_with::{SerializeDisplay, DeserializeFromStr};
6use struct_metadata::Described;
7
8#[cfg(feature = "rand")]
9use rand::RngExt;
10
11use crate::datastore::tagging::LayoutError;
12use crate::messages::task::{generate_conf_key, TagEntry, Task};
13use crate::types::strings::Keyword;
14use crate::{random_word, ElasticMeta, Readable};
15use crate::types::{ClassificationString, ExpandingClassification, ServiceName, Sha256, Text};
16
17use super::tagging::Tagging;
18
19#[derive(SerializeDisplay, DeserializeFromStr, strum::Display, strum::EnumString, Debug, Described, Clone, PartialEq, Eq)]
20#[metadata_type(ElasticMeta)]
21#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
22pub enum BodyFormat {
23 Text,
24 MemoryDump,
25 GraphData,
26 Url,
27 Json,
28 KeyValue,
29 ProcessTree,
30 Table,
31 Image,
32 Multi,
33 OrderedKeyValue,
34 Timeline,
35 Sandbox
36}
37
38#[derive(Serialize, Deserialize, Debug, Described, Clone, PartialEq, Eq)]
42#[metadata_type(ElasticMeta)]
43#[serde(rename_all="SCREAMING_SNAKE_CASE")]
44pub enum PromoteTo {
45 Screenshot,
46 Entropy,
47 UriParams
48}
49
50#[derive(Serialize, Deserialize, Debug, Described, Clone, PartialEq, Eq)]
53#[metadata_type(ElasticMeta)]
54#[metadata(index=true, store=false)]
55pub struct Attack {
56 #[metadata(copyto="__text__")]
58 pub attack_id: String,
59 #[metadata(copyto="__text__")]
61 pub pattern: String,
62 pub categories: Vec<String>,
64}
65
66#[derive(Serialize, Deserialize, Debug, Described, Clone, PartialEq, Eq)]
68#[metadata_type(ElasticMeta)]
69#[metadata(index=true, store=false)]
70pub struct Signature {
71 #[metadata(copyto="__text__")]
73 pub name: String,
74 #[serde(default = "default_signature_frequency")]
76 pub frequency: i32,
77 #[serde(default)]
79 pub safe: bool,
80}
81
82fn default_signature_frequency() -> i32 { 1 }
83
84#[derive(Serialize, Deserialize, Debug, Described, Clone, PartialEq, Eq)]
86#[metadata_type(ElasticMeta)]
87#[metadata(index=true, store=false)]
88pub struct Heuristic {
89 #[metadata(copyto="__text__")]
91 pub heur_id: String,
92 #[metadata(copyto="__text__")]
94 pub name: String,
95 #[serde(default)]
97 pub attack: Vec<Attack>,
98 #[serde(default)]
100 pub signature: Vec<Signature>,
101 pub score: i32,
103}
104
105#[derive(Serialize, Deserialize, Debug, Described, Clone, PartialEq, Eq)]
107#[metadata_type(ElasticMeta)]
108#[metadata(index=true, store=false)]
109pub struct Section {
110 #[serde(default)]
112 pub auto_collapse: bool,
113 #[metadata(copyto="__text__")]
115 pub body: Option<Text>,
116 pub classification: ClassificationString,
118 #[metadata(index=false)]
120 pub body_format: BodyFormat,
121 #[metadata(index=false)]
123 pub body_config: Option<HashMap<String, serde_json::Value>>,
124 #[metadata(index=false)]
126 pub depth: i32,
127 pub heuristic: Option<Heuristic>,
129 #[serde(default)]
131 pub tags: Tagging,
132 #[serde(default)]
134 #[metadata(store=false, mapping="flattenedobject")]
135 pub safelisted_tags: HashMap<String, Vec<Keyword>>,
136 #[metadata(copyto="__text__")]
138 pub title_text: Text,
139 pub promote_to: Option<PromoteTo>,
141}
142
143#[derive(Serialize, Deserialize, Debug, Default, Described, Clone, PartialEq, Eq)]
145#[metadata_type(ElasticMeta)]
146#[metadata(index=true, store=true)]
147pub struct ResultBody {
148 #[serde(default)]
150 pub score: i32,
151 #[serde(default)]
153 pub sections: Vec<Section>,
154}
155
156#[derive(Serialize, Deserialize, Debug, Default, Described, Clone, PartialEq, Eq)]
158#[metadata_type(ElasticMeta)]
159#[metadata(index=false, store=false)]
160pub struct Milestone {
161 pub service_started: DateTime<Utc>,
163 pub service_completed: DateTime<Utc>,
165}
166
167#[cfg(feature = "rand")]
168impl rand::distr::Distribution<Milestone> for rand::distr::StandardUniform {
169 fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Milestone {
170 let service_started = chrono::Utc::now() - chrono::TimeDelta::hours(rng.random_range(1..200));
171 let duration = chrono::TimeDelta::seconds(rng.random_range(1..900));
172 Milestone {
173 service_started,
174 service_completed: service_started + duration
175 }
176 }
177}
178
179
180#[derive(Serialize, Deserialize, Debug, Described, Clone, Eq)]
182#[metadata_type(ElasticMeta)]
183#[metadata(index=true, store=false)]
184pub struct File {
185 #[metadata(copyto="__text__")]
187 pub name: String,
188 #[metadata(copyto="__text__")]
190 pub sha256: Sha256,
191 #[metadata(copyto="__text__")]
193 pub description: Text,
194 pub classification: ClassificationString,
196 #[serde(default)]
198 pub is_section_image: bool,
199 #[serde(default = "default_file_parent_relation")]
201 pub parent_relation: Text,
202 #[serde(default)]
204 pub allow_dynamic_recursion: bool,
205}
206
207
208impl PartialEq for File {
209 fn eq(&self, other: &Self) ->bool {
210 self.sha256 == other.sha256
211 }
212}
213
214impl File {
215 pub fn new(sha256: Sha256, name: String) -> Self {
216 File {
217 name,
218 sha256,
219 description: Default::default(),
220 classification: ClassificationString::default_unrestricted(),
221 is_section_image: false,
222 parent_relation: Default::default(),
223 allow_dynamic_recursion: false
224 }
225 }
226}
227
228fn default_file_parent_relation() -> Text { Text("EXTRACTED".to_owned()) }
229
230#[derive(Serialize, Deserialize, Debug, Described, Clone, PartialEq, Eq)]
232#[metadata_type(ElasticMeta)]
233#[metadata(index=true, store=true)]
234pub struct ResponseBody {
235 #[serde(default)]
237 pub milestones: Milestone,
238 #[metadata(store=false)]
240 pub service_version: String,
241 #[metadata(copyto="__text__")]
243 pub service_name: ServiceName,
244 #[serde(default)]
246 #[metadata(copyto="__text__")]
247 pub service_tool_version: Option<String>,
248 #[serde(default)]
250 pub supplementary: Vec<File>,
251 #[serde(default)]
253 pub extracted: Vec<File>,
254 #[serde(default)]
256 #[metadata(index=false, store=false)]
257 pub service_context: Option<String>,
258 #[serde(default)]
260 #[metadata(index=false, store=false)]
261 pub service_debug_info: Option<String>,
262}
263
264
265#[cfg(feature = "rand")]
266impl rand::distr::Distribution<ResponseBody> for rand::distr::StandardUniform {
267 fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> ResponseBody {
268 ResponseBody {
269 milestones: rng.random(),
270 service_version: random_word(rng),
271 service_name: ServiceName::from_string(random_word(rng)),
272 service_tool_version: None,
273 supplementary: Default::default(),
274 extracted: Default::default(),
275 service_context: Default::default(),
276 service_debug_info: Default::default(),
277 }
278 }
279}
280
281
282#[derive(Serialize, Deserialize, Debug, Described, Clone, PartialEq, Eq)]
284#[metadata_type(ElasticMeta)]
285#[metadata(index=true, store=true)]
286pub struct Result {
287 pub archive_ts: Option<DateTime<Utc>>,
289 #[serde(flatten)]
291 pub classification: ExpandingClassification,
292 #[serde(default="chrono::Utc::now")]
294 pub created: DateTime<Utc>,
295 #[metadata(store=false)]
297 pub expiry_ts: Option<DateTime<Utc>>,
298 pub response: ResponseBody,
300 #[serde(default)]
302 pub result: ResultBody,
303 #[metadata(store=false)]
305 pub sha256: Sha256,
306 #[serde(rename = "type")]
308 pub result_type: Option<String>,
309 pub size: Option<i32>,
311 #[serde(default)]
313 pub drop_file: bool,
314 #[serde(default)]
316 pub partial: bool,
317 #[serde(default)]
319 #[metadata(index=false)]
320 pub from_archive: bool,
321}
322
323#[cfg(feature = "rand")]
324impl rand::distr::Distribution<Result> for rand::distr::StandardUniform {
325 fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Result {
326 Result {
327 archive_ts: None,
328 classification: ExpandingClassification::try_unrestricted().unwrap(),
329 created: chrono::Utc::now(),
330 expiry_ts: None,
331 response: rng.random(),
332 result: Default::default(),
333 sha256: rng.random(),
334 result_type: None,
335 size: None,
336 partial: Default::default(),
337 drop_file: Default::default(),
338 from_archive: Default::default(),
339 }
340 }
341}
342
343impl Readable for Result {
344 fn set_from_archive(&mut self, from_archive: bool) {
345 self.from_archive = from_archive;
346 }
347}
348
349pub struct ResultKeyBuilder<'a> {
350 hash: &'a Sha256,
351 service_name: &'a str,
352 service_version: &'a str,
353 service_tool_version: Option<&'a str>,
354 task: Option<&'a Task>,
355 partial: bool,
356 ignore_cache: bool,
357 is_empty: bool,
358}
359
360impl<'a> ResultKeyBuilder<'a> {
361
362 pub fn new(hash: &'a Sha256, service_name: &'a str, service_version: &'a str) -> Self {
363 Self {
364 hash,
365 service_name,
366 service_version,
367 service_tool_version: None,
368 task: None,
369 partial: false,
370 ignore_cache: false,
371 is_empty: false,
372 }
373 }
374
375 pub fn service_tool_version(mut self, service_tool_version: Option<&'a str>) -> Self {
376 self.service_tool_version = service_tool_version; self
377 }
378 pub fn task(mut self, task: Option<&'a Task>) -> Self {
379 self.task = task; self
380 }
381 pub fn partial(mut self, partial: bool) -> Self {
382 self.partial = partial; self
383 }
384 pub fn ignore_cache(mut self, ignore_cache: bool) -> Self {
385 self.ignore_cache = ignore_cache; self
386 }
387 pub fn is_empty(mut self, is_empty: bool) -> Self {
388 self.is_empty = is_empty; self
389 }
390
391 pub fn build(self) -> std::result::Result<String, serde_json::Error> {
392 let mut key_list = vec![
393 self.hash.to_string(),
394 self.service_name.replace('.', "_"),
395 format!("v{}", self.service_version.replace('.', "_")),
396 format!("c{}", generate_conf_key(self.service_tool_version, self.task, Some(self.partial), self.ignore_cache)?),
397 ];
398
399 if self.is_empty {
400 key_list.push("e".to_owned())
401 }
402
403 Ok(key_list.join("."))
404 }
405}
406
407
408impl Result {
409
410 pub fn start_build_key<'a>(&'a self, task: Option<&'a Task>) -> ResultKeyBuilder<'a> {
411 ResultKeyBuilder::new(&self.sha256, &self.response.service_name, &self.response.service_version)
412 .is_empty(self.is_empty())
413 .partial(self.partial)
414 .service_tool_version(self.response.service_tool_version.as_deref())
415 .task(task)
416 }
417
418 pub fn build_key(&self, task: Option<&Task>) -> std::result::Result<String, serde_json::Error> {
419 self.start_build_key(task).build()
420 }
421
422 pub fn scored_tag_dict(&self) -> std::result::Result<HashMap<String, TagEntry>, LayoutError> {
423 let mut tags: HashMap<String, TagEntry> = Default::default();
424 for section in &self.result.sections {
426 for tag in section.tags.to_list(None)? {
427 let key = format!("{}:{}", tag.tag_type, tag.value);
428 let entry = tags.entry(key).or_insert(tag);
429 if let Some(heuristic) = §ion.heuristic {
430 entry.score += heuristic.score;
431 }
432 }
433 }
434 Ok(tags)
435 }
436
437 pub fn is_empty(&self) -> bool {
438 self.response.extracted.is_empty() && self.response.supplementary.is_empty() && self.result.sections.is_empty() && self.result.score == 0 && !self.partial
439 }
440}