use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct OcsfFile {
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub uid: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub type_id: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub size: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hashes: Option<Vec<FileHash>>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct FileHash {
pub algorithm_id: u8,
pub value: String,
}
impl OcsfFile {
#[must_use]
pub fn with_name_from_path(mut self) -> Self {
if self.name.is_none() {
if let Some(ref path) = self.path {
self.name = path.rsplit('/').next().map(String::from);
}
}
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn file_roundtrip() {
let f = OcsfFile {
path: Some("/etc/shadow".to_string()),
name: Some("shadow".to_string()),
uid: None,
type_id: Some(1),
size: Some(1024),
hashes: Some(vec![FileHash {
algorithm_id: 3,
value: "abc123".to_string(),
}]),
};
let json = serde_json::to_string(&f).unwrap();
let f2: OcsfFile = serde_json::from_str(&json).unwrap();
assert_eq!(f, f2);
}
#[test]
fn name_from_path() {
let f = OcsfFile {
path: Some("/usr/bin/curl".to_string()),
name: None,
uid: None,
type_id: None,
size: None,
hashes: None,
}
.with_name_from_path();
assert_eq!(f.name.as_deref(), Some("curl"));
}
#[test]
fn name_from_path_preserves_existing() {
let f = OcsfFile {
path: Some("/usr/bin/curl".to_string()),
name: Some("custom".to_string()),
uid: None,
type_id: None,
size: None,
hashes: None,
}
.with_name_from_path();
assert_eq!(f.name.as_deref(), Some("custom"));
}
}