Skip to main content

clawdstrike_ocsf/objects/
file.rs

1//! OCSF File object.
2
3use serde::{Deserialize, Serialize};
4
5/// OCSF File object.
6#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
7#[serde(deny_unknown_fields)]
8pub struct OcsfFile {
9    /// Full file path.
10    #[serde(skip_serializing_if = "Option::is_none")]
11    pub path: Option<String>,
12    /// File name (basename).
13    #[serde(skip_serializing_if = "Option::is_none")]
14    pub name: Option<String>,
15    /// File unique identifier.
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub uid: Option<String>,
18    /// OCSF file type ID (1=Regular, 2=Folder, 3=Character, 4=Block, 5=FIFO, 6=Socket, 7=Symlink).
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub type_id: Option<u8>,
21    /// File size in bytes.
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub size: Option<u64>,
24    /// File hashes.
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub hashes: Option<Vec<FileHash>>,
27}
28
29/// File hash entry.
30#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
31#[serde(deny_unknown_fields)]
32pub struct FileHash {
33    /// Hash algorithm ID (1=MD5, 2=SHA-1, 3=SHA-256, 4=SHA-512, 99=Other).
34    pub algorithm_id: u8,
35    /// Hex-encoded hash value.
36    pub value: String,
37}
38
39impl OcsfFile {
40    /// Derive the file name from the path if not explicitly set.
41    #[must_use]
42    pub fn with_name_from_path(mut self) -> Self {
43        if self.name.is_none() {
44            if let Some(ref path) = self.path {
45                self.name = path.rsplit('/').next().map(String::from);
46            }
47        }
48        self
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn file_roundtrip() {
58        let f = OcsfFile {
59            path: Some("/etc/shadow".to_string()),
60            name: Some("shadow".to_string()),
61            uid: None,
62            type_id: Some(1),
63            size: Some(1024),
64            hashes: Some(vec![FileHash {
65                algorithm_id: 3,
66                value: "abc123".to_string(),
67            }]),
68        };
69        let json = serde_json::to_string(&f).unwrap();
70        let f2: OcsfFile = serde_json::from_str(&json).unwrap();
71        assert_eq!(f, f2);
72    }
73
74    #[test]
75    fn name_from_path() {
76        let f = OcsfFile {
77            path: Some("/usr/bin/curl".to_string()),
78            name: None,
79            uid: None,
80            type_id: None,
81            size: None,
82            hashes: None,
83        }
84        .with_name_from_path();
85        assert_eq!(f.name.as_deref(), Some("curl"));
86    }
87
88    #[test]
89    fn name_from_path_preserves_existing() {
90        let f = OcsfFile {
91            path: Some("/usr/bin/curl".to_string()),
92            name: Some("custom".to_string()),
93            uid: None,
94            type_id: None,
95            size: None,
96            hashes: None,
97        }
98        .with_name_from_path();
99        assert_eq!(f.name.as_deref(), Some("custom"));
100    }
101}