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_ignore_cache(mut self, value: bool) -> Self {
270 self.ignore_cache = value; self
271 }
272
273 pub fn set_services_selected(mut self, selected: &[&str]) -> Self {
274 self.services.selected = selected.iter().map(|s|ServiceName::from_string(s.to_string())).collect(); self
275 }
276
277 pub fn set_submitter(mut self, submitter: &str) -> Self {
278 self.submitter = submitter.to_owned(); self
279 }
280
281 pub fn set_groups(mut self, groups: &[&str]) -> Self {
282 self.groups = groups.iter().map(|s|s.parse().unwrap()).collect(); self
283 }
284
285 pub fn set_max_extracted(mut self, max_extracted: i32) -> Self {
286 self.max_extracted = max_extracted; self
287 }
288
289 pub fn set_generate_alert(mut self, alert: bool) -> Self {
290 self.generate_alert = alert; self
291 }
292
293 pub fn set_initial_data(mut self, initial_data: Option<Text>) -> Self {
294 self.initial_data = initial_data; self
295 }
296
297 fn get_hashing_keys(&self) -> Vec<(String, serde_json::Value)> {
299 [
300 ("classification", json!(self.classification)),
301 ("deep_scan", json!(self.deep_scan)),
302 ("ignore_cache", json!(self.ignore_cache)),
303 ("ignore_recursion_prevention", json!(self.ignore_recursion_prevention)),
304 ("ignore_filtering", json!(self.ignore_filtering)),
305 ("ignore_size", json!(self.ignore_size)),
306 ("max_extracted", json!(self.max_extracted)),
307 ("max_supplementary", json!(self.max_supplementary)),
308 ].into_iter().map(|(key, value)|(key.to_owned(), value)).collect()
309 }
310
311
312 pub fn create_filescore_key(&self, sha256: &Sha256, services: Option<Vec<ServiceName>>) -> String {
317 let version = 0;
321
322 let services = match services {
323 Some(services) => services,
324 None => self.services.selected.clone(),
325 };
326
327 let mut data = self.get_hashing_keys();
328 data.push(("service_spec".to_owned(), {
329 let mut spec = vec![];
330 for (key, values) in self.service_spec.clone() {
331 let mut values: Vec<(String, Value)> = values.into_iter().collect();
332 values.sort_by(|a, b|a.0.cmp(&b.0));
333 spec.push((key, values));
334 }
335 spec.sort_by(|a, b|a.0.cmp(&b.0));
336 json!(spec)
337 }));
338 data.push(("sha256".to_owned(), json!(sha256)));
339 data.push(("services".to_owned(), json!(services)));
340
341 let s = data.into_iter().map(|(k, v)| format!("{k}: {v}")).collect::<Vec<String>>().join(", ");
342 use md5::{Md5, Digest};
345 let mut hasher = Md5::new();
346 hasher.update(s);
347 let hash = hasher.finalize();
348 let mut hex = String::new();
349 for byte in hash {
350 hex += &format!("{byte:x}");
351 }
352
353 format!("{hex}v{version}")
354 }
355}
356
357#[derive(Serialize, Deserialize, Default, Debug, Described, Clone)]
372#[serde(default)]
373#[metadata_type(ElasticMeta)]
374#[metadata(index=false, store=false)]
375pub struct ServiceSelection {
376 #[serde(skip_serializing_if = "Vec::is_empty")]
378 pub excluded: Vec<ServiceName>,
379 #[serde(skip_serializing_if = "Vec::is_empty")]
381 pub rescan: Vec<ServiceName>,
382 #[serde(skip_serializing_if = "Vec::is_empty")]
384 pub resubmit: Vec<ServiceName>,
385 #[serde(skip_serializing_if = "Vec::is_empty")]
387 pub selected: Vec<ServiceName>,
388}
389
390#[derive(Serialize, Deserialize, Debug, Described, Clone)]
392#[metadata_type(ElasticMeta)]
393#[metadata(index=true, store=true)]
394pub struct Times {
395 #[metadata(store=false)]
397 pub completed: Option<DateTime<Utc>>,
398 pub submitted: DateTime<Utc>,
400}
401
402impl Default for Times {
403 fn default() -> Self {
404 Self {
405 completed: None,
406 submitted: Utc::now()
407 }
408 }
409}
410
411#[derive(Serialize, Deserialize, Debug, Described, Clone, Default)]
413#[metadata_type(ElasticMeta)]
414#[metadata(index=true, store=false)]
415#[serde(default)]
416pub struct Verdict {
417 pub malicious: Vec<String>,
419 pub non_malicious: Vec<String>,
421}
422
423#[derive(SerializeDisplay, DeserializeFromStr, Debug, PartialEq, Eq, strum::Display, strum::EnumString, Described, Clone, Copy)]
424#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
425#[metadata_type(ElasticMeta)]
426pub enum SubmissionState {
427 Failed,
428 Submitted,
429 Completed,
430}
431
432#[test]
433fn test_state_serialization() {
434 assert_eq!(serde_json::to_string(&SubmissionState::Failed).unwrap(), "\"failed\"");
435 assert_eq!(serde_json::from_str::<SubmissionState>("\"failed\"").unwrap(), SubmissionState::Failed);
436 assert_eq!(serde_json::to_value(SubmissionState::Failed).unwrap(), serde_json::json!("failed"));
437 assert_eq!(serde_json::from_str::<SubmissionState>("\"Failed\"").unwrap(), SubmissionState::Failed);
438}
439
440
441#[derive(Serialize, Deserialize, Debug, Described, Clone)]
443#[metadata_type(ElasticMeta)]
444#[metadata(index=true, store=false)]
445pub struct File {
446 #[metadata(copyto="__text__")]
448 pub name: String,
449 #[metadata(mapping="long")]
451 pub size: Option<u64>,
452 #[metadata(copyto="__text__")]
454 pub sha256: Sha256,
455}
456
457#[cfg(feature = "rand")]
458impl rand::distr::Distribution<File> for rand::distr::StandardUniform {
459 fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> File {
460 File {
461 name: "readme.txt".to_string(),
462 size: Some(rng.random_range(10..1_000_000)),
463 sha256: rng.random()
464 }
465 }
466}