1use std::collections::HashMap;
2
3use log::debug;
4use md5::Digest;
5use rand::Rng;
6use serde::{Serialize, Deserialize};
7
8use crate::datastore::tagging::TagValue;
9use crate::random_word;
10use crate::types::{ClassificationString, JsonMap, MD5, SSDeepHash, ServiceName, Sha1, Sha256, Sid, Wildcard};
11use crate::{datastore::file::URIInfo, config::ServiceSafelist};
12
13
14#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
16pub struct FileInfo {
17 pub magic: String,
19 pub md5: MD5,
21 pub mime: Option<String>,
23 pub sha1: Sha1,
25 pub sha256: Sha256,
27 pub size: u64,
29 #[serde(default)]
31 pub ssdeep: Option<SSDeepHash>,
32 #[serde(default)]
34 pub tlsh: Option<String>,
35 #[serde(rename="type")]
37 #[serde(default)]
38 pub file_type: String,
39 #[serde(default)]
41 pub uri_info: Option<URIInfo>,
42}
43
44#[cfg(feature = "rand")]
45impl rand::distr::Distribution<FileInfo> for rand::distr::StandardUniform {
46 fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> FileInfo {
47 FileInfo {
48 magic: "".to_string(),
49 md5: rng.random(),
50 mime: None,
51 sha1: rng.random(),
52 sha256: rng.random(),
53 size: rng.random(),
54 ssdeep: Some(rng.random()),
55 tlsh: None,
56 file_type: "unknown".to_owned(),
57 uri_info: None
58 }
59 }
60}
61
62#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
64pub struct TagItem {
65 #[serde(rename="type")]
67 pub tag_type: String,
68 pub short_type: String,
70 pub value: TagValue,
72 #[serde(skip_serializing_if="Option::is_none")]
74 pub score: Option<i32>,
75}
76
77
78#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
80pub struct DataItem {
81 pub name: String,
82 pub value: serde_json::Value,
83}
84
85#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
87pub struct Task {
88 pub task_id: u64,
90 pub dispatcher: String,
92 pub dispatcher_address: String,
93 pub sid: Sid,
95 pub metadata: HashMap<String, Wildcard>,
97 pub min_classification: ClassificationString,
99 pub fileinfo: FileInfo,
101 pub filename: String,
103 pub service_name: ServiceName,
105 pub service_config: JsonMap,
107 pub depth: u32,
109 pub max_files: i32,
111 pub ttl: i32,
113
114 pub tags: Vec<TagItem>,
116 pub temporary_submission_data: Vec<DataItem>,
118
119 pub deep_scan: bool,
121
122 pub ignore_cache: bool,
124
125 pub ignore_recursion_prevention: bool,
127
128 pub ignore_filtering: bool,
130
131 pub priority: i32,
133
134 #[serde(default="task_default_safelist_config")]
136 pub safelist_config: ServiceSafelist, }
138
139#[cfg(feature = "rand")]
140impl rand::distr::Distribution<Task> for rand::distr::StandardUniform {
141 fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Task {
142 Task {
143 task_id: rng.random(),
144 dispatcher: random_word(rng),
145 dispatcher_address: "localhost:8080".to_string(),
146 sid: rng.random(),
147 metadata: Default::default(),
148 min_classification: ClassificationString::default_unrestricted(),
149 fileinfo: rng.random(),
150 filename: random_word(rng),
151 service_name: ServiceName::from_string(random_word(rng)),
152 service_config: Default::default(),
153 depth: rng.random(),
154 max_files: rng.random(),
155 ttl: rng.random_range(0..100),
156 tags: Default::default(),
157 temporary_submission_data: Default::default(),
158 deep_scan: rng.random(),
159 ignore_cache: rng.random(),
160 ignore_recursion_prevention: rng.random(),
161 ignore_filtering: rng.random(),
162 priority: rng.random(),
163 safelist_config: Default::default(),
164 }
165 }
166}
167
168pub fn task_default_safelist_config() -> ServiceSafelist {
169 ServiceSafelist {
170 enabled: false,
171 ..Default::default()
172 }
173}
174
175impl Task {
176 pub fn make_key(sid: Sid, service_name: &str, sha: &Sha256) -> String {
177 format!("{sid}_{service_name}_{sha}")
178 }
179
180 pub fn key(&self) -> String {
181 Self::make_key(self.sid, &self.service_name, &self.fileinfo.sha256)
182 }
183
184 pub fn signature(&self) -> TaskSignature {
185 TaskSignature {
186 task_id: self.task_id,
187 sid: self.sid,
188 service: self.service_name,
189 hash: self.fileinfo.sha256.clone()
190
191 }
192 }
193}
194
195pub fn generate_conf_key(service_tool_version: Option<&str>, task: Option<&Task>, partial: Option<bool>) -> Result<String, serde_json::Error> {
196 if let Some(task) = task {
197 let service_config = serde_json::to_string(&{
198 let mut pairs: Vec<_> = task.service_config.iter().collect();
199 pairs.sort_unstable_by_key(|row| row.0);
200 pairs
201 })?;
202
203 let submission_params_str = serde_json::to_string(&[
204 ("deep_scan", serde_json::json!(task.deep_scan)),
205 ("ignore_filtering", serde_json::json!(task.ignore_filtering)),
206 ("max_files", serde_json::json!(task.max_files)),
207 ("min_classification", serde_json::json!(task.min_classification)),
208 ])?;
209
210 let ignore_salt = if task.ignore_cache || partial.unwrap_or_default() {
211 &rand::rng().random::<u128>().to_string()
212 } else {
213 "None"
214 };
215
216 let service_tool_version = service_tool_version.unwrap_or("None");
217 let total_str = format!("{service_tool_version}_{service_config}_{submission_params_str}_{ignore_salt}");
218
219 let mut hasher = md5::Md5::new();
221 hasher.update(&total_str);
222 let hash = hasher.finalize();
223
224 let number = u64::from_be_bytes(hash[0..8].try_into().unwrap());
226
227 let key = base62::encode(number);
228 debug!("Unhashed result config value {}/{}: {total_str} -> {key}", task.fileinfo.sha256, task.service_name);
229
230 Ok(key)
232 } else {
233 Ok("0".to_string())
234 }
235}
236
237
238#[derive(Hash, PartialEq, Eq)]
239pub struct TaskSignature {
240 pub task_id: u64,
241 pub sid: Sid,
242 pub service: ServiceName,
243 pub hash: Sha256,
244}
245
246
247
248#[derive(Serialize, Deserialize)]
250pub struct TaskToken {
251 pub task_id: u64,
252 pub dispatcher: String,
253}
254
255#[derive(Serialize, Deserialize, Clone, Debug)]
259pub struct ResultSummary {
260 pub key: String,
261 pub drop: bool,
262 pub score: i32,
263 pub partial: bool,
264 pub children: Vec<(Sha256, String)>
265}
266
267#[derive(Serialize, Deserialize)]
268pub enum ServiceResponse {
269 Result(Box<ServiceResult>),
270 Error(Box<ServiceError>),
271}
272
273impl std::fmt::Debug for ServiceResponse {
274 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
275 f.debug_struct("ServiceResponse").field("result", &matches!(self, ServiceResponse::Result(..))).finish()
276 }
277}
278
279impl ServiceResponse {
280 pub fn sid(&self) -> Sid {
281 match self {
282 ServiceResponse::Result(item) => item.sid,
283 ServiceResponse::Error(item) => item.sid,
284 }
285 }
286
287 pub fn sha256(&self) -> Sha256 {
288 match self {
289 ServiceResponse::Result(item) => item.sha256.clone(),
290 ServiceResponse::Error(item) => item.service_task.fileinfo.sha256.clone(),
291 }
292 }
293
294 pub fn service_name(&self) -> ServiceName {
295 match self {
296 ServiceResponse::Result(item) => item.service_name,
297 ServiceResponse::Error(item) => item.service_task.service_name,
298 }
299 }
300}
301
302#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
303pub struct TagEntry {
304 pub score: i32,
305 #[serde(rename="type")]
306 pub tag_type: String,
307 pub value: TagValue,
308
309}
310
311#[derive(Serialize, Deserialize)]
312pub struct ServiceResult {
313 pub dynamic_recursion_bypass: Vec<Sha256>,
314 pub sid: Sid,
315 pub sha256: Sha256,
316 pub service_name: ServiceName,
317 pub service_version: String,
318 pub service_tool_version: Option<String>,
319 pub expiry_ts: Option<chrono::DateTime<chrono::Utc>>,
320 pub result_summary: ResultSummary,
321 pub tags: HashMap<String, TagEntry>,
322 pub extracted_names: HashMap<Sha256, String>,
323 pub temporary_data: JsonMap,
324 pub extra_errors: Vec<String>,
325}
326
327#[derive(Serialize, Deserialize)]
328pub struct ServiceError {
329 pub sid: Sid,
330 pub service_task: Task,
331 pub error: crate::datastore::Error,
332 pub error_key: String,
333}