rust_cef/
lib.rs

1use std::collections::HashMap;
2/// Copyright 2020 Polyverse Corporation
3/// This module provides traits to allow arbitrary Rust items (structs, enums, etc.)
4/// to be converted into Common Event Format strings used by popular loggers around the world.
5///
6/// This is primarily built to have guard rails and ensure the CEF doesn't
7/// break by accident when making changes to Rust items.
8use std::error::Error;
9use std::fmt::{Display, Formatter, Result as FmtResult};
10use time::OffsetDateTime;
11
12/// An error consistently used all code
13/// in this module and sub-modules.
14///
15/// May have structured errors, and arbitrary errors
16/// are flagged as `Unexpected(s)` with the string `s`
17/// containing the message.
18///
19#[derive(Debug, PartialEq)]
20pub enum CefConversionError {
21    Unexpected(String),
22}
23impl Error for CefConversionError {}
24impl Display for CefConversionError {
25    fn fmt(&self, f: &mut Formatter) -> FmtResult {
26        match self {
27            CefConversionError::Unexpected(message) => {
28                write!(f, "CefConversionError::Unexpected {}", message)
29            }
30        }
31    }
32}
33
34/// CefResult is the consistent result type used by all
35/// code in this module and sub-modules
36pub type CefResult = Result<String, CefConversionError>;
37
38// CefExtensionsResult is used to return an error when necessary
39// but nothing useful when it works. Making it an error
40// provides proper context vs doing Option
41pub type CefExtensionsResult = Result<(), CefConversionError>;
42
43/// A trait that returns the "Version" CEF Header
44pub trait CefHeaderVersion {
45    fn cef_header_version(&self) -> CefResult;
46}
47
48/// A trait that returns the "DeviceVendor" CEF Header
49pub trait CefHeaderDeviceVendor {
50    fn cef_header_device_vendor(&self) -> CefResult;
51}
52
53/// A trait that returns the "DeviceProduct" CEF Header
54pub trait CefHeaderDeviceProduct {
55    fn cef_header_device_product(&self) -> CefResult;
56}
57
58/// A trait that returns the "DeviceVersion" CEF Header
59pub trait CefHeaderDeviceVersion {
60    fn cef_header_device_version(&self) -> CefResult;
61}
62
63/// A trait that returns the "DeviceEventClassID" CEF Header
64pub trait CefHeaderDeviceEventClassID {
65    fn cef_header_device_event_class_id(&self) -> CefResult;
66}
67
68/// A trait that returns the "Name" CEF Header
69pub trait CefHeaderName {
70    fn cef_header_name(&self) -> CefResult;
71}
72
73/// A trait that returns the "Severity" CEF Header
74pub trait CefHeaderSeverity {
75    fn cef_header_severity(&self) -> CefResult;
76}
77
78/// A trait that returns CEF Extensions. This is a roll-up
79/// trait that should ideally take into account any CEF extensions
80/// added by sub-fields or sub-objects from the object on which
81/// this is implemented.
82pub trait CefExtensions {
83    fn cef_extensions(&self, collector: &mut HashMap<String, String>) -> CefExtensionsResult;
84}
85
86/// This trait emits an ArcSight Common Event Format
87/// string by combining all the other traits that provide
88/// CEF headers and extensions.
89pub trait ToCef:
90    CefHeaderVersion
91    + CefHeaderDeviceVendor
92    + CefHeaderDeviceProduct
93    + CefHeaderDeviceVersion
94    + CefHeaderDeviceEventClassID
95    + CefHeaderName
96    + CefHeaderSeverity
97    + CefExtensions
98{
99    fn to_cef(&self) -> CefResult {
100        let mut extensions: HashMap<String, String> = HashMap::new();
101
102        // get our extensions
103        if let Err(err) = self.cef_extensions(&mut extensions) {
104            return Err(err);
105        };
106
107        // make it into key=value strings
108        let mut kvstrs: Vec<String> = extensions
109            .into_iter()
110            .map(|(key, value)| [key, value].join("="))
111            .collect();
112
113        kvstrs.sort_unstable();
114
115        // Make it into a "key1=value1 key2=value2" string (each key=value string concatenated and separated by spaces)
116        let extensionsstr = kvstrs.join(" ");
117
118        let mut cef_entry = String::new();
119        cef_entry.push_str("CEF:");
120        cef_entry.push_str(&self.cef_header_version()?);
121        cef_entry.push('|');
122        cef_entry.push_str(&self.cef_header_device_vendor()?);
123        cef_entry.push('|');
124        cef_entry.push_str(&self.cef_header_device_product()?);
125        cef_entry.push('|');
126        cef_entry.push_str(&self.cef_header_device_version()?);
127        cef_entry.push('|');
128        cef_entry.push_str(&self.cef_header_device_event_class_id()?);
129        cef_entry.push('|');
130        cef_entry.push_str(&self.cef_header_name()?);
131        cef_entry.push('|');
132        cef_entry.push_str(&self.cef_header_severity()?);
133        cef_entry.push('|');
134        cef_entry.push_str(extensionsstr.as_str());
135
136        Ok(cef_entry)
137    }
138}
139
140/// Implement CefExtensions (since it's defined here) for type
141/// DateTime<Utc>
142impl CefExtensions for OffsetDateTime {
143    /// we serialize using:
144    /// Milliseconds since January 1, 1970 (integer). (This time format supplies an integer
145    /// with the count in milliseconds from January 1, 1970 to the time the event occurred.)
146    fn cef_extensions(&self, collector: &mut HashMap<String, String>) -> CefExtensionsResult {
147        collector.insert(
148            "rt".to_owned(),
149            format!("{}", self.unix_timestamp_nanos() / 1000000),
150        );
151        Ok(())
152    }
153}
154
155/********************************************************************************************** */
156/* Tests! Tests! Tests! */
157
158#[cfg(test)]
159mod test {
160    use super::*;
161
162    struct GoodExample {}
163
164    impl ToCef for GoodExample {}
165    impl CefHeaderVersion for GoodExample {
166        fn cef_header_version(&self) -> CefResult {
167            Ok("0".to_owned())
168        }
169    }
170
171    impl CefHeaderDeviceVendor for GoodExample {
172        fn cef_header_device_vendor(&self) -> CefResult {
173            Ok("polyverse".to_owned())
174        }
175    }
176
177    impl CefHeaderDeviceProduct for GoodExample {
178        fn cef_header_device_product(&self) -> CefResult {
179            Ok("zerotect".to_owned())
180        }
181    }
182
183    impl CefHeaderDeviceVersion for GoodExample {
184        fn cef_header_device_version(&self) -> CefResult {
185            Ok("V1".to_owned())
186        }
187    }
188
189    impl CefHeaderDeviceEventClassID for GoodExample {
190        fn cef_header_device_event_class_id(&self) -> CefResult {
191            Ok("LinuxKernelTrap".to_owned())
192        }
193    }
194
195    impl CefHeaderName for GoodExample {
196        fn cef_header_name(&self) -> CefResult {
197            Ok("Linux Kernel Trap".to_owned())
198        }
199    }
200
201    impl CefHeaderSeverity for GoodExample {
202        fn cef_header_severity(&self) -> CefResult {
203            Ok("10".to_owned())
204        }
205    }
206
207    impl CefExtensions for GoodExample {
208        fn cef_extensions(&self, collector: &mut HashMap<String, String>) -> CefExtensionsResult {
209            collector.insert("customField1".to_owned(), "customValue1".to_owned());
210            collector.insert("customField2".to_owned(), "customValue2".to_owned());
211            collector.insert("customField3".to_owned(), "customValue2".to_owned());
212            collector.insert("customField4".to_owned(), "customValue3".to_owned());
213            Ok(())
214        }
215    }
216
217    struct BadExample {}
218    impl ToCef for BadExample {}
219    impl CefHeaderVersion for BadExample {
220        fn cef_header_version(&self) -> CefResult {
221            Ok("0".to_owned())
222        }
223    }
224
225    impl CefHeaderDeviceVendor for BadExample {
226        fn cef_header_device_vendor(&self) -> CefResult {
227            Ok("polyverse".to_owned())
228        }
229    }
230
231    impl CefHeaderDeviceProduct for BadExample {
232        fn cef_header_device_product(&self) -> CefResult {
233            Ok("zerotect".to_owned())
234        }
235    }
236
237    impl CefHeaderDeviceVersion for BadExample {
238        fn cef_header_device_version(&self) -> CefResult {
239            Ok("V1".to_owned())
240        }
241    }
242
243    impl CefHeaderDeviceEventClassID for BadExample {
244        fn cef_header_device_event_class_id(&self) -> CefResult {
245            Err(CefConversionError::Unexpected(
246                "This error should propagate".to_owned(),
247            ))
248        }
249    }
250
251    impl CefHeaderName for BadExample {
252        fn cef_header_name(&self) -> CefResult {
253            Ok("Linux Kernel Trap".to_owned())
254        }
255    }
256
257    impl CefHeaderSeverity for BadExample {
258        fn cef_header_severity(&self) -> CefResult {
259            Ok("10".to_owned())
260        }
261    }
262
263    impl CefExtensions for BadExample {
264        fn cef_extensions(&self, collector: &mut HashMap<String, String>) -> CefExtensionsResult {
265            collector.insert("customField".to_owned(), "customValue".to_owned());
266            Ok(())
267        }
268    }
269
270    #[test]
271    fn test_impl_works() {
272        let example = GoodExample {};
273        let result = example.to_cef();
274        assert!(result.is_ok());
275        assert_eq!(result.unwrap(), "CEF:0|polyverse|zerotect|V1|LinuxKernelTrap|Linux Kernel Trap|10|customField1=customValue1 customField2=customValue2 customField3=customValue2 customField4=customValue3");
276    }
277
278    #[test]
279    fn test_error_propagates() {
280        let example = BadExample {};
281        let result = example.to_cef();
282        assert!(result.is_err());
283        assert_eq!(
284            result.unwrap_err(),
285            CefConversionError::Unexpected("This error should propagate".to_owned())
286        );
287    }
288
289    #[test]
290    fn test_ext_for_datetime() {
291        let mut collector = HashMap::<String, String>::new();
292        let example = OffsetDateTime::from_unix_timestamp_nanos(3435315515325000000).unwrap();
293        let result = example.cef_extensions(&mut collector);
294        assert!(result.is_ok());
295
296        let maybe_rt = collector.get("rt");
297        assert!(maybe_rt.is_some());
298
299        let rt = maybe_rt.unwrap();
300        assert_eq!(rt, "3435315515325");
301    }
302}