clawdstrike_ocsf/classes/
process_activity.rs1use serde::{Deserialize, Serialize};
6
7use crate::base::{category_for_class, compute_type_uid, ClassUid};
8use crate::objects::actor::Actor;
9use crate::objects::metadata::Metadata;
10use crate::objects::process::OcsfProcess;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
14#[repr(u8)]
15pub enum ProcessActivityType {
16 Launch = 1,
18 Terminate = 2,
20 Open = 3,
22 Inject = 4,
24 SetUserId = 5,
26 Other = 99,
28}
29
30impl ProcessActivityType {
31 #[must_use]
33 pub const fn as_u8(self) -> u8 {
34 self as u8
35 }
36}
37
38#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
40#[serde(deny_unknown_fields)]
41pub struct ProcessActivity {
42 pub class_uid: u16,
45 pub category_uid: u8,
47 pub type_uid: u32,
49 pub activity_id: u8,
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub activity_name: Option<String>,
54 pub time: i64,
56 pub severity_id: u8,
58 #[serde(skip_serializing_if = "Option::is_none")]
60 pub severity: Option<String>,
61 pub status_id: u8,
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub status: Option<String>,
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub message: Option<String>,
69 pub metadata: Metadata,
71
72 pub process: OcsfProcess,
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub actor: Option<Actor>,
78 #[serde(skip_serializing_if = "Option::is_none")]
80 pub unmapped: Option<serde_json::Value>,
81}
82
83impl ProcessActivity {
84 #[must_use]
86 pub fn new(
87 activity: ProcessActivityType,
88 time: i64,
89 severity_id: u8,
90 status_id: u8,
91 metadata: Metadata,
92 process: OcsfProcess,
93 ) -> Self {
94 let class_uid = ClassUid::ProcessActivity;
95 let activity_id = activity.as_u8();
96 Self {
97 class_uid: class_uid.as_u16(),
98 category_uid: category_for_class(class_uid).as_u8(),
99 type_uid: compute_type_uid(class_uid.as_u16(), activity_id),
100 activity_id,
101 activity_name: Some(process_activity_name(activity).to_string()),
102 time,
103 severity_id,
104 severity: None,
105 status_id,
106 status: None,
107 message: None,
108 metadata,
109 process,
110 actor: None,
111 unmapped: None,
112 }
113 }
114
115 #[must_use]
117 pub fn with_message(mut self, msg: impl Into<String>) -> Self {
118 self.message = Some(msg.into());
119 self
120 }
121
122 #[must_use]
124 pub fn with_actor(mut self, actor: Actor) -> Self {
125 self.actor = Some(actor);
126 self
127 }
128
129 #[must_use]
131 pub fn with_unmapped(mut self, unmapped: serde_json::Value) -> Self {
132 self.unmapped = Some(unmapped);
133 self
134 }
135}
136
137fn process_activity_name(activity: ProcessActivityType) -> &'static str {
138 match activity {
139 ProcessActivityType::Launch => "Launch",
140 ProcessActivityType::Terminate => "Terminate",
141 ProcessActivityType::Open => "Open",
142 ProcessActivityType::Inject => "Inject",
143 ProcessActivityType::SetUserId => "Set User ID",
144 ProcessActivityType::Other => "Other",
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 fn sample_process() -> OcsfProcess {
153 OcsfProcess {
154 pid: Some(1234),
155 name: Some("curl".to_string()),
156 cmd_line: Some("curl https://example.com".to_string()),
157 file: None,
158 user: None,
159 parent_process: None,
160 cwd: None,
161 }
162 }
163
164 #[test]
165 fn class_uid_is_1007() {
166 let e = ProcessActivity::new(
167 ProcessActivityType::Launch,
168 1_709_366_400_000,
169 1,
170 1,
171 Metadata::clawdstrike("0.1.3"),
172 sample_process(),
173 );
174 assert_eq!(e.class_uid, 1007);
175 }
176
177 #[test]
178 fn category_uid_is_1() {
179 let e = ProcessActivity::new(
180 ProcessActivityType::Launch,
181 0,
182 0,
183 0,
184 Metadata::clawdstrike("0.1.3"),
185 sample_process(),
186 );
187 assert_eq!(e.category_uid, 1);
188 }
189
190 #[test]
191 fn type_uid_launch() {
192 let e = ProcessActivity::new(
193 ProcessActivityType::Launch,
194 0,
195 0,
196 0,
197 Metadata::clawdstrike("0.1.3"),
198 sample_process(),
199 );
200 assert_eq!(e.type_uid, 100701);
201 }
202
203 #[test]
204 fn type_uid_terminate() {
205 let e = ProcessActivity::new(
206 ProcessActivityType::Terminate,
207 0,
208 0,
209 0,
210 Metadata::clawdstrike("0.1.3"),
211 sample_process(),
212 );
213 assert_eq!(e.type_uid, 100702);
214 }
215
216 #[test]
217 fn serialization_roundtrip() {
218 let e = ProcessActivity::new(
219 ProcessActivityType::Launch,
220 1_709_366_400_000,
221 1,
222 1,
223 Metadata::clawdstrike("0.1.3"),
224 sample_process(),
225 )
226 .with_message("curl launched");
227
228 let json = serde_json::to_string(&e).unwrap();
229 let e2: ProcessActivity = serde_json::from_str(&json).unwrap();
230 assert_eq!(e.type_uid, e2.type_uid);
231 assert_eq!(e.process.pid, e2.process.pid);
232 }
233}