use chrono::{DateTime, Utc};
use md5::Digest;
use serde::{Serialize, Deserialize};
use serde_with::{DeserializeFromStr, SerializeDisplay};
use struct_metadata::Described;
#[cfg(feature = "rand")]
use rand::RngExt;
use crate::{ElasticMeta, Readable};
use crate::types::{ExpandingClassification, SSDeepHash, Sha1, Sha256, Text, MD5};
#[derive(Debug, Serialize, Deserialize, Described, Clone)]
#[metadata_type(ElasticMeta)]
#[metadata(index=true, store=true)]
pub struct File {
pub archive_ts: Option<DateTime<Utc>>,
#[metadata(index=false, store=false)]
pub ascii: String,
#[serde(flatten)]
pub classification: ExpandingClassification,
pub entropy: f32,
#[metadata(store=false)]
pub expiry_ts: Option<DateTime<Utc>>,
#[serde(default)]
pub is_section_image: bool,
#[serde(default)]
pub is_supplementary: bool,
#[metadata(index=false, store=false)]
pub hex: String,
#[serde(default)]
#[metadata(copyto="__text__")]
pub labels: Vec<String>,
#[serde(default)]
pub label_categories: LabelCategories,
#[metadata(copyto="__text__")]
pub md5: MD5,
#[metadata(store=false)]
pub magic: String,
#[metadata(store=false)]
pub mime: Option<String>,
#[serde(default)]
pub seen: Seen,
#[metadata(copyto="__text__")]
pub sha1: Sha1,
#[metadata(copyto="__text__")]
pub sha256: Sha256,
#[metadata(mapping="long")]
pub size: u64,
#[metadata(store=false)]
pub ssdeep: SSDeepHash,
#[serde(rename = "type")]
#[metadata(copyto="__text__")]
pub file_type: String,
#[metadata(copyto="__text__")]
pub tlsh: Option<String>,
#[serde(default)]
#[metadata(index=false, store=false)]
pub from_archive: bool,
#[serde(default)]
pub uri_info: Option<URIInfo>,
#[serde(default)]
pub comments: Vec<Comment>,
}
#[cfg(feature = "rand")]
impl rand::distr::Distribution<File> for rand::distr::StandardUniform {
fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> File {
let mut data = vec![];
for _ in 0..1000 {
data.push(rng.random());
}
File::gen_for_sample(&data, rng)
}
}
impl File {
pub fn gen_for_sample<R: rand::Rng + ?Sized>(data: &[u8], rng: &mut R) -> File {
let sha256 = hex::encode(sha2::Sha256::new().chain_update(data).finalize());
let sha1 = hex::encode(sha1::Sha1::new().chain_update(data).finalize());
let md5 = hex::encode(md5::Md5::new().chain_update(data).finalize());
File {
archive_ts: None,
ascii: String::from_iter(data.iter().take(64).map(|byte| if byte.is_ascii() { *byte as char } else { '.' })),
classification: ExpandingClassification::try_unrestricted().unwrap(),
entropy: rng.random_range(0.0..1.0),
expiry_ts: None,
is_section_image: rng.random(),
is_supplementary: rng.random(),
hex: String::from_iter(data.iter().take(64).map(|byte| if byte.is_ascii() { *byte as char } else { '.' })),
labels: vec![],
label_categories: Default::default(),
md5: md5.parse().unwrap(),
magic: "Binary data".to_string(),
mime: Some("application/octet-stream".to_string()),
seen: Seen { count: 1, first: chrono::Utc::now(), last: chrono::Utc::now() },
sha1: sha1.parse().unwrap(),
sha256: sha256.parse().unwrap(),
size: data.len() as u64,
ssdeep: rng.random(),
file_type: "unknown".to_string(),
tlsh: None,
from_archive: false,
uri_info: None,
comments: vec![],
}
}
}
impl Readable for File {
fn set_from_archive(&mut self, from_archive: bool) {
self.from_archive = from_archive;
}
}
#[derive(Debug, Serialize, Deserialize, Described, Clone, PartialEq, Eq)]
#[metadata_type(ElasticMeta)]
#[metadata(index=true, store=true)]
pub struct URIInfo {
pub uri: String,
pub scheme: String,
pub netloc: String,
pub path: Option<String>,
pub params: Option<String>,
pub query: Option<String>,
pub fragment: Option<String>,
pub username: Option<String>,
pub password: Option<String>,
pub hostname: String,
pub port: Option<u16>,
}
#[derive(Debug, Serialize, Deserialize, Described, Clone)]
#[metadata_type(ElasticMeta)]
#[metadata(index=true, store=true)]
pub struct Seen {
#[serde(default = "default_seen_count")]
#[metadata(mapping="integer")]
pub count: u64,
#[serde(default = "default_now")]
pub first: DateTime<Utc>,
#[serde(default = "default_now")]
pub last: DateTime<Utc>,
}
fn default_seen_count() -> u64 { 1 }
fn default_now() -> DateTime<Utc> { Utc::now() }
impl Default for Seen {
fn default() -> Self {
Self {
count: default_seen_count(),
first: default_now(),
last: default_now()
}
}
}
#[derive(Debug, Serialize, Deserialize, Described, Clone, Default)]
#[serde(default)]
#[metadata_type(ElasticMeta)]
#[metadata(index=true, store=true)]
pub struct LabelCategories {
pub info: Vec<String>,
pub technique: Vec<String>,
pub attribution: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, Described, Clone)]
#[metadata_type(ElasticMeta)]
#[metadata(index=true, store=false)]
pub struct Comment {
pub cid: String,
pub uname: String,
#[serde(default="Utc::now")]
#[metadata(store=true)]
pub date: DateTime<Utc>,
pub text: Text,
#[serde(default)]
pub reactions: Vec<Reaction>,
}
#[derive(Debug, Serialize, Deserialize, Described, Clone)]
#[metadata_type(ElasticMeta)]
#[metadata(index=true, store=false)]
pub struct Reaction {
pub icon: ReactionsTypes,
pub uname: String,
}
#[derive(SerializeDisplay, DeserializeFromStr, strum::Display, strum::EnumString, Described, PartialEq, Eq, Debug, Clone, Copy)]
#[metadata_type(ElasticMeta)]
#[metadata(mapping="keyword")]
#[strum(serialize_all = "snake_case")]
pub enum ReactionsTypes {
ThumbsUp,
ThumbsDown,
Love,
Smile,
Surprised,
Party
}