1#![doc = include_str!("../README.md")]
4#![deny(missing_docs)]
5#![deny(clippy::all)]
6#![deny(clippy::pedantic)]
7#![forbid(unsafe_code)]
8
9pub mod digest;
11
12use std::fmt::{Display, Formatter};
13
14use chrono::serde::ts_seconds_option;
15use chrono::{DateTime, Utc};
16use serde::{Deserialize, Serialize};
17use zeroize::{Zeroize, ZeroizeOnDrop};
18
19pub const MDB_VERSION: &str = env!("CARGO_PKG_VERSION");
21
22pub const MDB_API_HEADER: &str = "mdb-api-key";
24
25pub const USER_LOGIN_URL: &str = "/v1/users/getkey";
27
28#[derive(Deserialize, Serialize, Zeroize, ZeroizeOnDrop)]
30pub struct GetAPIKeyRequest {
31 pub user: String,
33
34 pub password: String,
36}
37
38pub const USER_LOGOUT_URL: &str = "/v1/users/clearkey";
40
41#[derive(Deserialize, Serialize, Zeroize, ZeroizeOnDrop)]
44pub struct GetAPIKeyResponse {
45 pub key: Option<String>,
47
48 pub message: Option<String>,
50}
51
52pub const USER_INFO_URL: &str = "/v1/users/info";
55
56#[derive(Clone, Debug, Deserialize, Serialize)]
58pub struct GetUserInfoResponse {
59 pub id: u32,
61
62 pub username: String,
64
65 pub groups: Vec<String>,
67
68 pub sources: Vec<String>,
70
71 pub is_admin: bool,
73
74 pub created: DateTime<Utc>,
76
77 pub is_readonly: bool,
79}
80
81pub const SERVER_INFO: &str = "/v1/server/info";
83
84#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
86pub struct ServerInfo {
87 pub os_name: String,
89
90 pub memory_used: String,
92
93 pub mdb_version: String,
95
96 pub db_version: String,
98
99 pub db_size: String,
101
102 pub num_samples: u64,
104
105 pub num_users: u32,
107
108 pub uptime: String,
110}
111
112pub const SUPPORTED_FILE_TYPES: &str = "/v1/server/types";
114
115#[derive(Clone, Debug, Deserialize, Serialize)]
117pub struct SupportedFileType {
118 pub name: String,
120
121 pub magic: Vec<String>,
123
124 pub is_executable: bool,
126
127 pub description: Option<String>,
129}
130
131#[derive(Clone, Debug, Deserialize, Serialize)]
133pub struct SupportedFileTypes {
134 pub types: Vec<SupportedFileType>,
136
137 pub message: Option<String>,
139}
140
141pub const LIST_SOURCES: &str = "/v1/sources/list";
143
144#[derive(Clone, Debug, Deserialize, Serialize)]
146pub struct SourceInfo {
147 pub id: u32,
149
150 pub name: String,
152
153 pub description: Option<String>,
155
156 pub url: Option<String>,
158
159 pub first_acquisition: DateTime<Utc>,
161
162 pub malicious: Option<bool>,
164}
165
166#[derive(Clone, Debug, Deserialize, Serialize)]
168pub struct Sources {
169 pub sources: Vec<SourceInfo>,
171
172 pub message: Option<String>,
174}
175
176pub const UPLOAD_SAMPLE: &str = "/v1/samples/upload";
178
179#[derive(Clone, Debug, Deserialize, Serialize)]
181pub struct NewSample {
182 pub file_name: String,
184
185 pub source_id: u32,
187
188 pub file_contents_b64: String,
190
191 pub sha256: String,
193}
194
195pub const DOWNLOAD_SAMPLE: &str = "/v1/samples/download";
199
200pub const DOWNLOAD_SAMPLE_CART: &str = "/v1/samples/download/cart";
204
205pub const SAMPLE_REPORT: &str = "/v1/samples/report";
208
209#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
211pub struct VirusTotalSummary {
212 pub hits: u32,
214
215 pub total: u32,
217
218 #[serde(default)]
220 pub detail: Option<serde_json::Value>,
221
222 #[serde(default, with = "ts_seconds_option")]
224 pub last_analysis_date: Option<DateTime<Utc>>,
225}
226
227#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
230pub struct Report {
231 pub md5: String,
233
234 pub sha1: String,
236
237 pub sha256: String,
239
240 pub sha384: String,
242
243 pub sha512: String,
245
246 pub lzjd: Option<String>,
249
250 pub tlsh: Option<String>,
253
254 pub ssdeep: Option<String>,
257
258 pub humanhash: Option<String>,
261
262 pub filecommand: Option<String>,
265
266 pub bytes: u32,
268
269 pub size: String,
271
272 pub entropy: f32,
274
275 #[serde(default)]
278 pub vt: Option<VirusTotalSummary>,
279}
280
281impl Display for Report {
282 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
283 writeln!(f, "Size: {} bytes, or {}", self.bytes, self.size)?;
284 writeln!(f, "Entropy: {}", self.entropy)?;
285 if let Some(filecmd) = &self.filecommand {
286 writeln!(f, "File command: {filecmd}")?;
287 }
288 if let Some(vt) = &self.vt {
289 writeln!(f, "VT Hits: {}/{}", vt.hits, vt.total)?;
290 }
291 writeln!(f, "MD5: {}", self.md5)?;
292 writeln!(f, "SHA-1: {}", self.sha1)?;
293 writeln!(f, "SHA256: {}", self.sha256)
294 }
295}
296
297pub const SIMILAR_SAMPLES: &str = "/v1/samples/similar";
299
300#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
302#[non_exhaustive]
303pub enum SimilarityHashType {
304 SSDeep,
306
307 LZJD,
309
310 TLSH,
312
313 PEHash,
315
316 ImportHash,
318
319 FuzzyImportHash,
321}
322
323impl SimilarityHashType {
324 #[must_use]
328 pub fn get_table_field_simfunc(&self) -> (&'static str, Option<&'static str>) {
329 match self {
330 SimilarityHashType::SSDeep => ("file.ssdeep", Some("fuzzy_hash_compare")),
331 SimilarityHashType::LZJD => ("file.lzjd", Some("lzjd_compare")),
332 SimilarityHashType::TLSH => ("file.tlsh", Some("tlsh_compare")),
333 SimilarityHashType::PEHash => ("executable.pehash", None),
334 SimilarityHashType::ImportHash => ("executable.importhash", None),
335 SimilarityHashType::FuzzyImportHash => {
336 ("executable.importhashfuzzy", Some("fuzzy_hash_compare"))
337 }
338 }
339 }
340}
341
342impl Display for SimilarityHashType {
343 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
344 match self {
345 SimilarityHashType::SSDeep => write!(f, "SSDeep"),
346 SimilarityHashType::LZJD => write!(f, "LZJD"),
347 SimilarityHashType::TLSH => write!(f, "TLSH"),
348 SimilarityHashType::PEHash => write!(f, "PeHash"),
349 SimilarityHashType::ImportHash => write!(f, "Import Hash (IMPHASH)"),
350 SimilarityHashType::FuzzyImportHash => write!(f, "Fuzzy Import hash"),
351 }
352 }
353}
354
355#[derive(Clone, Debug, Deserialize, Serialize)]
357pub struct SimilarSamplesRequest {
358 pub hashes: Vec<(SimilarityHashType, String)>,
360}
361
362#[derive(Clone, Debug, Deserialize, Serialize)]
364pub struct SimilarSample {
365 pub sha256: String,
367
368 pub algorithms: Vec<(SimilarityHashType, f32)>,
370}
371
372#[derive(Clone, Debug, Deserialize, Serialize)]
374pub struct SimilarSamplesResponse {
375 pub results: Vec<SimilarSample>,
377
378 pub message: Option<String>,
380}
381
382pub const LIST_LABELS: &str = "/v1/labels";
384
385#[derive(Clone, Debug, Deserialize, Serialize)]
387pub struct Label {
388 pub id: u64,
390
391 pub name: String,
393
394 pub parent: Option<String>,
396}
397
398#[derive(Clone, Debug, Default, Deserialize, Serialize)]
400pub struct Labels(pub Vec<Label>);
401
402impl Labels {
404 #[must_use]
406 pub fn len(&self) -> usize {
407 self.0.len()
408 }
409
410 #[must_use]
412 pub fn is_empty(&self) -> bool {
413 self.0.is_empty()
414 }
415}
416
417impl Display for Labels {
418 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
419 if self.is_empty() {
420 return writeln!(f, "No labels.");
421 }
422 for label in &self.0 {
423 let parent = if let Some(parent) = &label.parent {
424 format!(", parent: {parent}")
425 } else {
426 String::new()
427 };
428 writeln!(f, "{}: {}{parent}", label.id, label.name)?;
429 }
430 Ok(())
431 }
432}