1
2
3use std::collections::HashMap;
4
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use serde_json::{json, Value};
8use serde_with::{DeserializeFromStr, SerializeDisplay};
9use struct_metadata::Described;
10
11use crate::types::{ClassificationString, ExpandingClassification, JsonMap, ServiceName, Sha256, Sid, Text, UpperString, Wildcard};
12use crate::{ElasticMeta, Readable};
13
14
15#[derive(Serialize, Deserialize, Debug, Described, Clone)]
17#[metadata_type(ElasticMeta)]
18#[metadata(index=false, store=false)]
19pub struct TraceEvent {
20 #[serde(default="default_now")]
21 pub timestamp: DateTime<Utc>,
22 pub event_type: String,
23 pub service: Option<ServiceName>,
24 pub file: Option<Sha256>,
25 pub message: Option<String>,
26}
27
28fn default_now() -> DateTime<Utc> { Utc::now() }
29
30#[derive(Serialize, Deserialize, Debug, Described, Clone)]
32#[metadata_type(ElasticMeta)]
33#[metadata(index=true, store=true)]
34pub struct Submission {
35 #[serde(default)]
37 pub archive_ts: Option<DateTime<Utc>>,
38 #[serde(default)]
40 pub archived: bool,
41 #[serde(flatten)]
43 pub classification: ExpandingClassification,
44 #[serde(default)]
46 #[metadata(store=false, index=false)]
47 pub tracing_events: Vec<TraceEvent>,
48 pub error_count: i32,
50 #[metadata(store=false)]
52 pub errors: Vec<String>,
53 #[serde(default)]
55 #[metadata(store=false)]
56 pub expiry_ts: Option<DateTime<Utc>>,
57 pub file_count: i32,
59 pub files: Vec<File>,
61 pub max_score: i32,
64 #[serde(default)]
66 #[metadata(store=false, mapping="flattenedobject", copyto="__text__")]
67 pub metadata: HashMap<String, Wildcard>,
68 pub params: SubmissionParams,
70 #[metadata(store=false)]
72 pub results: Vec<Wildcard>,
73 #[metadata(copyto="__text__")]
75 pub sid: Sid,
76 pub state: SubmissionState,
78 #[serde(default)]
80 pub to_be_deleted: bool,
81 #[serde(default)]
83 pub times: Times,
84 #[serde(default)]
86 pub verdict: Verdict,
87 #[serde(default)]
89 #[metadata(index=false)]
90 pub from_archive: bool,
91
92 #[serde(default)]
95 #[metadata(index=false, store=false)]
96 pub scan_key: Option<String>,
97}
98
99#[cfg(feature = "rand")]
100impl rand::distr::Distribution<Submission> for rand::distr::StandardUniform {
101 fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Submission {
102 Submission {
103 archive_ts: None,
104 archived: rng.random(),
105 classification: ExpandingClassification::try_unrestricted().unwrap(),
106 tracing_events: Default::default(),
107 error_count: 0,
108 errors: vec![],
109 expiry_ts: None,
110 file_count: 1,
111 files: vec![rng.random()],
112 max_score: rng.random(),
113 metadata: Default::default(),
114 params: SubmissionParams::new(ClassificationString::try_unrestricted().unwrap()),
115 results: vec![],
116 sid: rng.random(),
117 state: SubmissionState::Submitted,
118 to_be_deleted: false,
119 times: Times {
120 completed: None,
121 submitted: Utc::now(),
122 },
123 verdict: Verdict {
124 malicious: vec![],
125 non_malicious: vec![],
126 },
127 from_archive: false,
128 scan_key: None,
129 }
130 }
131}
132
133impl Readable for Submission {
134 fn set_from_archive(&mut self, from_archive: bool) {
135 self.from_archive = from_archive
136 }
137}
138
139
140#[derive(Serialize, Deserialize, Debug, Described, Clone)]
142#[metadata_type(ElasticMeta)]
143#[metadata(index=true, store=false)]
144pub struct SubmissionParams {
145 pub classification: ClassificationString,
147 #[serde(default)]
149 pub deep_scan: bool,
150 #[serde(default, skip_serializing_if = "Option::is_none")]
152 #[metadata(store=true, copyto="__text__")]
153 pub description: Option<Text>,
154 #[serde(default)]
156 pub generate_alert: bool,
157 #[serde(default)]
160 pub groups: Vec<UpperString>,
161 #[serde(default)]
163 pub ignore_cache: bool,
164 #[serde(default)]
166 pub ignore_recursion_prevention: bool,
167 #[serde(default)]
169 pub ignore_filtering: bool,
170 #[serde(default)]
172 pub ignore_size: bool,
173 #[serde(default)]
175 pub never_drop: bool,
176 #[serde(default)]
178 pub malicious: bool,
179 #[serde(default="default_max_extracted")]
181 pub max_extracted: i32,
182 #[serde(default="default_max_supplementary")]
184 pub max_supplementary: i32,
185 #[serde(default="default_priority")]
187 pub priority: u16,
188 #[serde(default)]
190 pub quota_item: bool,
191 #[serde(default)]
193 pub services: ServiceSelection,
194 #[serde(default)]
196 #[metadata(index=false, store=false)]
197 pub service_spec: HashMap<ServiceName, JsonMap>,
198 #[metadata(store=true, copyto="__text__")]
200 pub submitter: String,
201 #[serde(default)]
203 pub trace: bool,
204 #[serde(default)]
206 pub ttl: i32,
207 #[serde(rename="type", default="default_type")]
209 pub submission_type: String,
210 #[serde(default, skip_serializing_if = "Option::is_none")]
212 #[metadata(index=false)]
213 pub initial_data: Option<Text>,
214 #[serde(default)]
216 pub auto_archive: bool,
217 #[serde(default)]
219 pub delete_after_archive: bool,
220 #[serde(default)]
222 pub psid: Option<Sid>,
223 #[serde(default)]
225 pub use_archive_alternate_dtl: bool,
226}
227
228fn default_max_extracted() -> i32 { 100 }
229fn default_max_supplementary() -> i32 { 100 }
230fn default_priority() -> u16 { 1000 }
231fn default_type() -> String { "USER".to_owned() }
232
233impl SubmissionParams {
234 pub fn new(classification: ClassificationString) -> Self {
235 Self {
236 classification,
237 deep_scan: false,
238 description: None,
239 generate_alert: false,
240 groups: vec![],
241 ignore_cache: false,
242 ignore_recursion_prevention: false,
243 ignore_filtering: false,
244 ignore_size: false,
245 never_drop: false,
246 malicious: false,
247 max_extracted: default_max_extracted(),
248 max_supplementary: default_max_supplementary(),
249 priority: default_priority(),
250 quota_item: false,
251 services: Default::default(),
252 service_spec: Default::default(),
253 submitter: "USER".to_owned(),
254 trace: false,
255 ttl: 30,
256 submission_type: default_type(),
257 initial_data: None,
258 auto_archive: false,
259 delete_after_archive: false,
260 psid: None,
261 use_archive_alternate_dtl: false,
262 }
263 }
264
265 pub fn set_description(mut self, text: &str) -> Self {
266 self.description = Some(text.into()); self
267 }
268
269 pub fn set_services_selected(mut self, selected: &[&str]) -> Self {
270 self.services.selected = selected.iter().map(|s|ServiceName::from_string(s.to_string())).collect(); self
271 }
272
273 pub fn set_submitter(mut self, submitter: &str) -> Self {
274 self.submitter = submitter.to_owned(); self
275 }
276
277 pub fn set_groups(mut self, groups: &[&str]) -> Self {
278 self.groups = groups.iter().map(|s|s.parse().unwrap()).collect(); self
279 }
280
281 pub fn set_max_extracted(mut self, max_extracted: i32) -> Self {
282 self.max_extracted = max_extracted; self
283 }
284
285 pub fn set_generate_alert(mut self, alert: bool) -> Self {
286 self.generate_alert = alert; self
287 }
288
289 pub fn set_initial_data(mut self, initial_data: Option<Text>) -> Self {
290 self.initial_data = initial_data; self
291 }
292
293 fn get_hashing_keys(&self) -> Vec<(String, serde_json::Value)> {
295 [
296 ("classification", json!(self.classification)),
297 ("deep_scan", json!(self.deep_scan)),
298 ("ignore_cache", json!(self.ignore_cache)),
299 ("ignore_recursion_prevention", json!(self.ignore_recursion_prevention)),
300 ("ignore_filtering", json!(self.ignore_filtering)),
301 ("ignore_size", json!(self.ignore_size)),
302 ("max_extracted", json!(self.max_extracted)),
303 ("max_supplementary", json!(self.max_supplementary)),
304 ].into_iter().map(|(key, value)|(key.to_owned(), value)).collect()
305 }
306
307
308 pub fn create_filescore_key(&self, sha256: &Sha256, services: Option<Vec<ServiceName>>) -> String {
313 let version = 0;
317
318 let services = match services {
319 Some(services) => services,
320 None => self.services.selected.clone(),
321 };
322
323 let mut data = self.get_hashing_keys();
324 data.push(("service_spec".to_owned(), {
325 let mut spec = vec![];
326 for (key, values) in self.service_spec.clone() {
327 let mut values: Vec<(String, Value)> = values.into_iter().collect();
328 values.sort_by(|a, b|a.0.cmp(&b.0));
329 spec.push((key, values));
330 }
331 spec.sort_by(|a, b|a.0.cmp(&b.0));
332 json!(spec)
333 }));
334 data.push(("sha256".to_owned(), json!(sha256)));
335 data.push(("services".to_owned(), json!(services)));
336
337 let s = data.into_iter().map(|(k, v)| format!("{k}: {v}")).collect::<Vec<String>>().join(", ");
338 use md5::{Md5, Digest};
341 let mut hasher = Md5::new();
342 hasher.update(s);
343 let hash = hasher.finalize();
344 let mut hex = String::new();
345 for byte in hash {
346 hex += &format!("{byte:x}");
347 }
348
349 format!("{hex}v{version}")
350 }
351}
352
353#[derive(Serialize, Deserialize, Default, Debug, Described, Clone)]
368#[serde(default)]
369#[metadata_type(ElasticMeta)]
370#[metadata(index=false, store=false)]
371pub struct ServiceSelection {
372 #[serde(skip_serializing_if = "Vec::is_empty")]
374 pub excluded: Vec<ServiceName>,
375 #[serde(skip_serializing_if = "Vec::is_empty")]
377 pub rescan: Vec<ServiceName>,
378 #[serde(skip_serializing_if = "Vec::is_empty")]
380 pub resubmit: Vec<ServiceName>,
381 #[serde(skip_serializing_if = "Vec::is_empty")]
383 pub selected: Vec<ServiceName>,
384}
385
386#[derive(Serialize, Deserialize, Debug, Described, Clone)]
388#[metadata_type(ElasticMeta)]
389#[metadata(index=true, store=true)]
390pub struct Times {
391 #[metadata(store=false)]
393 pub completed: Option<DateTime<Utc>>,
394 pub submitted: DateTime<Utc>,
396}
397
398impl Default for Times {
399 fn default() -> Self {
400 Self {
401 completed: None,
402 submitted: Utc::now()
403 }
404 }
405}
406
407#[derive(Serialize, Deserialize, Debug, Described, Clone, Default)]
409#[metadata_type(ElasticMeta)]
410#[metadata(index=true, store=false)]
411#[serde(default)]
412pub struct Verdict {
413 pub malicious: Vec<String>,
415 pub non_malicious: Vec<String>,
417}
418
419#[derive(SerializeDisplay, DeserializeFromStr, Debug, PartialEq, Eq, strum::Display, strum::EnumString, Described, Clone, Copy)]
420#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
421#[metadata_type(ElasticMeta)]
422pub enum SubmissionState {
423 Failed,
424 Submitted,
425 Completed,
426}
427
428#[test]
429fn test_state_serialization() {
430 assert_eq!(serde_json::to_string(&SubmissionState::Failed).unwrap(), "\"failed\"");
431 assert_eq!(serde_json::from_str::<SubmissionState>("\"failed\"").unwrap(), SubmissionState::Failed);
432 assert_eq!(serde_json::to_value(SubmissionState::Failed).unwrap(), serde_json::json!("failed"));
433 assert_eq!(serde_json::from_str::<SubmissionState>("\"Failed\"").unwrap(), SubmissionState::Failed);
434}
435
436
437#[derive(Serialize, Deserialize, Debug, Described, Clone)]
439#[metadata_type(ElasticMeta)]
440#[metadata(index=true, store=false)]
441pub struct File {
442 #[metadata(copyto="__text__")]
444 pub name: String,
445 #[metadata(mapping="long")]
447 pub size: Option<u64>,
448 #[metadata(copyto="__text__")]
450 pub sha256: Sha256,
451}
452
453#[cfg(feature = "rand")]
454impl rand::distr::Distribution<File> for rand::distr::StandardUniform {
455 fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> File {
456 File {
457 name: "readme.txt".to_string(),
458 size: Some(rng.random_range(10..1_000_000)),
459 sha256: rng.random()
460 }
461 }
462}