1use std::collections::HashMap;
2
3use md5::Digest;
4use rand::Rng;
5use serde::{Serialize, Deserialize};
6
7use crate::random_word;
8use crate::types::Wildcard;
9use crate::{MD5, Sha1, Sha256, Sid, JsonMap, SSDeepHash, datastore::file::URIInfo, config::ServiceSafelist};
10
11
12
13#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
15pub struct FileInfo {
16 pub magic: String,
18 pub md5: MD5,
20 pub mime: Option<String>,
22 pub sha1: Sha1,
24 pub sha256: Sha256,
26 pub size: u64,
28 pub ssdeep: Option<SSDeepHash>,
30 pub tlsh: Option<String>,
32 #[serde(rename="type")]
34 pub file_type: String,
35 pub uri_info: Option<URIInfo>,
37}
38
39#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
41pub struct TagItem {
42 #[serde(rename="type")]
44 pub tag_type: String,
45 pub value: String,
47 pub score: Option<i32>,
49}
50
51
52#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
54pub struct DataItem {
55 pub name: String,
56 pub value: serde_json::Value,
57}
58
59#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
61pub struct Task {
62 pub task_id: u64,
64 pub dispatcher: String,
66 pub dispatcher_address: String,
67 pub sid: Sid,
69 pub metadata: HashMap<String, Wildcard>,
71 pub min_classification: String,
73 pub fileinfo: FileInfo,
75 pub filename: String,
77 pub service_name: String,
79 pub service_config: JsonMap,
81 pub depth: u32,
83 pub max_files: i32,
85 pub ttl: i32,
87
88 pub tags: Vec<TagItem>,
90 pub temporary_submission_data: Vec<DataItem>,
92
93 pub deep_scan: bool,
95
96 pub ignore_cache: bool,
98
99 pub ignore_recursion_prevention: bool,
101
102 pub ignore_filtering: bool,
104
105 pub priority: i32,
107
108 #[serde(default="task_default_safelist_config")]
110 pub safelist_config: ServiceSafelist, }
112
113#[cfg(feature = "rand")]
114impl rand::distr::Distribution<Task> for rand::distr::StandardUniform {
115 fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Task {
116 Task {
117 task_id: rng.random(),
118 dispatcher: random_word(rng),
119 dispatcher_address: "localhost:8080".to_string(),
120 sid: rng.random(),
121 metadata: Default::default(),
122 min_classification: Default::default(),
123 fileinfo: FileInfo {
124 magic: "".to_string(),
125 md5: rng.random(),
126 mime: None,
127 sha1: rng.random(),
128 sha256: rng.random(),
129 size: rng.random(),
130 ssdeep: Some(rng.random()),
131 tlsh: None,
132 file_type: "unknown".to_owned(),
133 uri_info: None
134 },
135 filename: random_word(rng),
136 service_name: random_word(rng),
137 service_config: Default::default(),
138 depth: rng.random(),
139 max_files: rng.random(),
140 ttl: rng.random_range(0..100),
141 tags: Default::default(),
142 temporary_submission_data: Default::default(),
143 deep_scan: rng.random(),
144 ignore_cache: rng.random(),
145 ignore_recursion_prevention: rng.random(),
146 ignore_filtering: rng.random(),
147 priority: rng.random(),
148 safelist_config: Default::default(),
149 }
150 }
151}
152
153pub fn task_default_safelist_config() -> ServiceSafelist {
154 ServiceSafelist {
155 enabled: false,
156 ..Default::default()
157 }
158}
159
160impl Task {
161 pub fn make_key(sid: Sid, service_name: &str, sha: &Sha256) -> String {
162 format!("{sid}_{service_name}_{sha}")
163 }
164
165 pub fn key(&self) -> String {
166 Self::make_key(self.sid, &self.service_name, &self.fileinfo.sha256)
167 }
168
169 pub fn signature(&self) -> TaskSignature {
170 TaskSignature {
171 task_id: self.task_id,
172 sid: self.sid,
173 service: self.service_name.clone(),
174 hash: self.fileinfo.sha256.clone()
175 }
176 }
177}
178
179pub fn generate_conf_key(service_tool_version: Option<&str>, task: Option<&Task>, partial: Option<bool>) -> Result<String, serde_json::Error> {
180 if let Some(task) = task {
181 let service_config = serde_json::to_string(&{
182 let mut pairs: Vec<_> = task.service_config.iter().collect();
183 pairs.sort_unstable_by_key(|row| row.0);
184 pairs
185 })?;
186
187 let submission_params_str = serde_json::to_string(&[
188 ("deep_scan", serde_json::json!(task.deep_scan)),
189 ("ignore_filtering", serde_json::json!(task.ignore_filtering)),
190 ("max_files", serde_json::json!(task.max_files)),
191 ("min_classification", serde_json::json!(task.min_classification)),
192 ])?;
193
194 let ignore_salt = if task.ignore_cache || partial.unwrap_or_default() {
195 &rand::rng().random::<u128>().to_string()
196 } else {
197 "None"
198 };
199
200 let service_tool_version = service_tool_version.unwrap_or("None");
201 let total_str = format!("{service_tool_version}_{service_config}_{submission_params_str}_{ignore_salt}");
202
203 let mut hasher = md5::Md5::new();
205 hasher.update(total_str);
206 let hash = hasher.finalize();
207
208 let number = u64::from_be_bytes(hash.as_slice()[0..8].try_into().unwrap());
210
211 Ok(base62::encode(number))
213 } else {
214 Ok("0".to_string())
215 }
216}
217
218
219#[derive(Hash, PartialEq, Eq)]
220pub struct TaskSignature {
221 pub task_id: u64,
222 pub sid: Sid,
223 pub service: String,
224 pub hash: Sha256,
225}
226
227
228
229#[derive(Serialize, Deserialize)]
231pub struct TaskToken {
232 pub task_id: u64,
233 pub dispatcher: String,
234}
235
236#[derive(Serialize, Deserialize, Clone)]
240pub struct ResultSummary {
241 pub key: String,
242 pub drop: bool,
243 pub score: i32,
244 pub partial: bool,
245 pub children: Vec<(Sha256, String)>
246}
247
248#[derive(Serialize, Deserialize)]
249pub enum ServiceResponse {
250 Result(ServiceResult),
251 Error(ServiceError),
252}
253
254impl ServiceResponse {
255 pub fn sid(&self) -> Sid {
256 match self {
257 ServiceResponse::Result(item) => item.sid,
258 ServiceResponse::Error(item) => item.sid,
259 }
260 }
261
262 pub fn sha256(&self) -> Sha256 {
263 match self {
264 ServiceResponse::Result(item) => item.sha256.clone(),
265 ServiceResponse::Error(item) => item.service_task.fileinfo.sha256.clone(),
266 }
267 }
268
269 pub fn service_name(&self) -> &str {
270 match self {
271 ServiceResponse::Result(item) => &item.service_name,
272 ServiceResponse::Error(item) => &item.service_task.service_name,
273 }
274 }
275}
276
277#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct TagEntry {
279 pub score: i32,
280 #[serde(rename="type")]
281 pub tag_type: String,
282 pub value: String,
283}
284
285#[derive(Serialize, Deserialize)]
286pub struct ServiceResult {
287 pub dynamic_recursion_bypass: Vec<Sha256>,
288 pub sid: Sid,
289 pub sha256: Sha256,
290 pub service_name: String,
291 pub service_version: String,
292 pub service_tool_version: Option<String>,
293 pub expiry_ts: Option<chrono::DateTime<chrono::Utc>>,
294 pub result_summary: ResultSummary,
295 pub tags: HashMap<String, TagEntry>,
296 pub extracted_names: HashMap<Sha256, String>,
297 pub temporary_data: JsonMap,
298}
299
300#[derive(Serialize, Deserialize)]
301pub struct ServiceError {
302 pub sid: Sid,
303 pub service_task: Task,
304 pub error: crate::datastore::Error,
305 pub error_key: String,
306}