Skip to main content

clawdstrike_ocsf/objects/
observable.rs

1//! OCSF Observable object.
2
3use serde::{Deserialize, Serialize};
4
5/// OCSF Observable type IDs.
6#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
7#[repr(u8)]
8pub enum ObservableTypeId {
9    /// Unknown observable type.
10    Unknown = 0,
11    /// IP address.
12    IpAddress = 2,
13    /// Domain name.
14    Domain = 3,
15    /// File path.
16    FilePath = 7,
17    /// File name.
18    FileName = 8,
19    /// Process name.
20    ProcessName = 9,
21    /// URL.
22    Url = 20,
23    /// Hash.
24    Hash = 28,
25    /// Other (vendor-specific).
26    Other = 99,
27}
28
29impl ObservableTypeId {
30    /// Returns the integer representation.
31    #[must_use]
32    pub const fn as_u8(self) -> u8 {
33        self as u8
34    }
35}
36
37/// OCSF Observable object — a key-value pair identifying something observed.
38#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
39#[serde(deny_unknown_fields)]
40pub struct Observable {
41    /// Human-readable name of the observable.
42    pub name: String,
43    /// Observable value.
44    pub value: String,
45    /// Observable type ID.
46    pub type_id: u8,
47    /// Human-readable type label.
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub r#type: Option<String>,
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn observable_roundtrip() {
58        let o = Observable {
59            name: "file.path".to_string(),
60            value: "/etc/shadow".to_string(),
61            type_id: ObservableTypeId::FilePath.as_u8(),
62            r#type: Some("File Path".to_string()),
63        };
64        let json = serde_json::to_string(&o).unwrap();
65        let o2: Observable = serde_json::from_str(&json).unwrap();
66        assert_eq!(o, o2);
67    }
68}