1#![doc = include_str!("../README.md")]
4#![deny(missing_docs)]
5#![forbid(unsafe_code)]
6
7pub mod digest;
9
10use std::fmt::{Display, Formatter};
11
12use chrono::serde::ts_seconds_option;
13use chrono::{DateTime, Utc};
14use serde::{Deserialize, Serialize};
15use zeroize::{Zeroize, ZeroizeOnDrop};
16
17pub const MDB_VERSION: &str = env!("CARGO_PKG_VERSION");
19
20pub const MDB_API_HEADER: &str = "mdb-api-key";
22
23pub const USER_LOGIN_URL: &str = "/v1/users/getkey";
25
26#[derive(Deserialize, Serialize, Zeroize, ZeroizeOnDrop)]
28pub struct GetAPIKeyRequest {
29 pub user: String,
31
32 pub password: String,
34}
35
36pub const USER_LOGOUT_URL: &str = "/v1/users/clearkey";
38
39#[derive(Deserialize, Serialize, Zeroize, ZeroizeOnDrop)]
42pub struct GetAPIKeyResponse {
43 pub key: Option<String>,
45
46 pub message: Option<String>,
48}
49
50pub const USER_INFO_URL: &str = "/v1/users/info";
53
54#[derive(Clone, Debug, Deserialize, Serialize)]
56pub struct GetUserInfoResponse {
57 pub id: i32,
59
60 pub username: String,
62
63 pub groups: Vec<String>,
65
66 pub sources: Vec<String>,
68
69 pub is_admin: bool,
71
72 pub created: DateTime<Utc>,
74
75 pub is_readonly: bool,
77}
78
79pub const SERVER_INFO: &str = "/v1/server/info";
81
82#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
84pub struct ServerInfo {
85 pub os_name: String,
87
88 pub memory_used: String,
90
91 pub mdb_version: String,
93
94 pub db_version: String,
96
97 pub db_size: String,
99
100 pub num_samples: u64,
102
103 pub num_users: u32,
105
106 pub uptime: String,
108}
109
110pub const SUPPORTED_FILE_TYPES: &str = "/v1/server/types";
112
113#[derive(Clone, Debug, Deserialize, Serialize)]
115pub struct SupportedFileType {
116 pub name: String,
118
119 pub magic: Vec<String>,
121
122 pub is_executable: bool,
124
125 pub description: Option<String>,
127}
128
129#[derive(Clone, Debug, Deserialize, Serialize)]
131pub struct SupportedFileTypes {
132 pub types: Vec<SupportedFileType>,
134
135 pub message: Option<String>,
137}
138
139pub const LIST_SOURCES: &str = "/v1/sources/list";
141
142#[derive(Clone, Debug, Deserialize, Serialize)]
144pub struct SourceInfo {
145 pub id: u32,
147
148 pub name: String,
150
151 pub description: Option<String>,
153
154 pub url: Option<String>,
156
157 pub first_acquisition: DateTime<Utc>,
159
160 pub malicious: Option<bool>,
162}
163
164#[derive(Clone, Debug, Deserialize, Serialize)]
166pub struct Sources {
167 pub sources: Vec<SourceInfo>,
169
170 pub message: Option<String>,
172}
173
174pub const UPLOAD_SAMPLE: &str = "/v1/samples/upload";
176
177#[derive(Clone, Debug, Deserialize, Serialize)]
179pub struct NewSample {
180 pub file_name: String,
182
183 pub source_id: u32,
185
186 pub file_contents_b64: String,
188
189 pub sha256: String,
191}
192
193pub const DOWNLOAD_SAMPLE: &str = "/v1/samples/download";
197
198pub const DOWNLOAD_SAMPLE_CART: &str = "/v1/samples/download/cart";
202
203pub const SAMPLE_REPORT: &str = "/v1/samples/report";
206
207#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
209pub struct VirusTotalSummary {
210 pub hits: u32,
212
213 pub total: u32,
215
216 #[serde(default)]
218 pub detail: Option<serde_json::Value>,
219
220 #[serde(default, with = "ts_seconds_option")]
222 pub last_analysis_date: Option<DateTime<Utc>>,
223}
224
225#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
228pub struct Report {
229 pub md5: String,
231
232 pub sha1: String,
234
235 pub sha256: String,
237
238 pub sha384: String,
240
241 pub sha512: String,
243
244 pub lzjd: Option<String>,
247
248 pub tlsh: Option<String>,
251
252 pub ssdeep: Option<String>,
255
256 pub humanhash: Option<String>,
259
260 pub filecommand: Option<String>,
263
264 pub bytes: u32,
266
267 pub size: String,
269
270 pub entropy: f32,
272
273 #[serde(default)]
276 pub vt: Option<VirusTotalSummary>,
277}
278
279impl Display for Report {
280 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
281 writeln!(f, "Size: {} bytes, or {}", self.bytes, self.size)?;
282 writeln!(f, "Entropy: {}", self.entropy)?;
283 if let Some(filecmd) = &self.filecommand {
284 writeln!(f, "File command: {filecmd}")?;
285 }
286 if let Some(vt) = &self.vt {
287 writeln!(f, "VT Hits: {}/{}", vt.hits, vt.total)?;
288 }
289 writeln!(f, "MD5: {}", self.md5)?;
290 writeln!(f, "SHA-1: {}", self.sha1)?;
291 writeln!(f, "SHA256: {}", self.sha256)
292 }
293}
294
295pub const SIMILAR_SAMPLES: &str = "/v1/samples/similar";
297
298#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
300#[non_exhaustive]
301pub enum SimilarityHashType {
302 SSDeep,
304
305 LZJD,
307
308 TLSH,
310
311 PEHash,
313
314 ImportHash,
316
317 FuzzyImportHash,
319}
320
321impl SimilarityHashType {
322 pub fn get_table_field_simfunc(&self) -> (&'static str, Option<&'static str>) {
326 match self {
327 SimilarityHashType::SSDeep => ("file.ssdeep", Some("fuzzy_hash_compare")),
328 SimilarityHashType::LZJD => ("file.lzjd", Some("lzjd_compare")),
329 SimilarityHashType::TLSH => ("file.tlsh", Some("tlsh_compare")),
330 SimilarityHashType::PEHash => ("executable.pehash", None),
331 SimilarityHashType::ImportHash => ("executable.importhash", None),
332 SimilarityHashType::FuzzyImportHash => {
333 ("executable.importhashfuzzy", Some("fuzzy_hash_compare"))
334 }
335 }
336 }
337}
338
339impl Display for SimilarityHashType {
340 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
341 match self {
342 SimilarityHashType::SSDeep => write!(f, "SSDeep"),
343 SimilarityHashType::LZJD => write!(f, "LZJD"),
344 SimilarityHashType::TLSH => write!(f, "TLSH"),
345 SimilarityHashType::PEHash => write!(f, "PeHash"),
346 SimilarityHashType::ImportHash => write!(f, "Import Hash (IMPHASH)"),
347 SimilarityHashType::FuzzyImportHash => write!(f, "Fuzzy Import hash"),
348 }
349 }
350}
351
352#[derive(Clone, Debug, Deserialize, Serialize)]
354pub struct SimilarSamplesRequest {
355 pub hashes: Vec<(SimilarityHashType, String)>,
357}
358
359#[derive(Clone, Debug, Deserialize, Serialize)]
361pub struct SimilarSample {
362 pub sha256: String,
364
365 pub algorithms: Vec<(SimilarityHashType, f32)>,
367}
368
369#[derive(Clone, Debug, Deserialize, Serialize)]
371pub struct SimilarSamplesResponse {
372 pub results: Vec<SimilarSample>,
374
375 pub message: Option<String>,
377}
378
379pub const LIST_LABELS: &str = "/v1/labels";
381
382#[derive(Clone, Debug, Deserialize, Serialize)]
384pub struct Label {
385 pub id: u64,
387
388 pub name: String,
390
391 pub parent: Option<String>,
393}
394
395#[derive(Clone, Debug, Default, Deserialize, Serialize)]
397pub struct Labels(pub Vec<Label>);
398
399impl Labels {
401 pub fn len(&self) -> usize {
403 self.0.len()
404 }
405
406 pub fn is_empty(&self) -> bool {
408 self.0.is_empty()
409 }
410}
411
412impl Display for Labels {
413 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
414 if self.is_empty() {
415 return writeln!(f, "No labels.");
416 }
417 for label in &self.0 {
418 let parent = if let Some(parent) = &label.parent {
419 format!(", parent: {parent}")
420 } else {
421 String::new()
422 };
423 writeln!(f, "{}: {}{parent}", label.id, label.name)?;
424 }
425 Ok(())
426 }
427}