From 568d4e5825577983b74f89b3b05ed42dba0dce01 Mon Sep 17 00:00:00 2001 From: Nathan Shaaban <86252985+ctrlaltf24@users.noreply.github.com> Date: Fri, 10 May 2024 21:10:09 +0000 Subject: [PATCH] feat: support message ids as strings MSGID examples in rfc 5424 are strings > For example, a firewall might use the MSGID "TCPIN" for incoming TCP traffic and the MSGID "TCPOUT" for outgoing TCP traffic. - rfc 5424 Unsure how you want to handle validating the message_id's input. The current implementation has the easiest to interact with API boundary, but at the cost of runtime cycles. message id is likely a constant possibly called many times, we could add an option to run the validation once using a wrapper type, then use it safely per call --- src/format.rs | 73 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/src/format.rs b/src/format.rs index e6e07b5..c1cdabe 100644 --- a/src/format.rs +++ b/src/format.rs @@ -137,6 +137,7 @@ pub type StructuredData = HashMap>; pub struct Formatter5424 { pub facility: Facility, pub hostname: Option, + /// Called APP-NAME in RFC5424 pub process: String, pub pid: u32, } @@ -165,15 +166,24 @@ impl Formatter5424 { } } -impl LogFormat<(u32, StructuredData, T)> for Formatter5424 { +impl LogFormat<(Option, StructuredData, T)> for Formatter5424 { fn format( &self, w: &mut W, severity: Severity, - log_message: (u32, StructuredData, T), + log_message: (Option, StructuredData, T), ) -> Result<()> { let (message_id, data, message) = log_message; + // XXX: seems a lot of effort per-call, we could do this via a wrapper type instead + // So the caller could do this once and pass it in + let message_id = message_id + .unwrap_or_else(|| NILL_VALUE.to_owned()) + .chars() + .filter(is_us_print_ascii) + .take(32) + .collect::(); + // Guard against sub-second precision over 6 digits per rfc5424 section 6 let timestamp = time::OffsetDateTime::now_utc(); // SAFETY: timestamp range is enforced, so this will never fail @@ -203,6 +213,27 @@ impl LogFormat<(u32, StructuredData, T)> for Formatter5424 { } } +impl LogFormat<(u32, StructuredData, T)> for Formatter5424 { + fn format( + &self, + w: &mut W, + severity: Severity, + log_message: (u32, StructuredData, T), + ) -> Result<()> { + // Slight bit more overhead, but we can reuse the other implementation + LogFormat::<(Option, StructuredData, T)>::format( + self, + w, + severity, + ( + Some(log_message.0.to_string()), + log_message.1, + log_message.2, + ), + ) + } +} + impl Default for Formatter5424 { /// Returns a `Formatter5424` with default settings. /// @@ -241,10 +272,20 @@ fn escape_structure_data_param_value(value: &str) -> String { .replace(']', "\\]") } +/// Checks if a character is printable US ASCII +/// Defined by rfc5424 as between 33 and 126 +fn is_us_print_ascii(c: &char) -> bool { + 33 <= *c as u32 && *c as u32 <= 126 +} + fn encode_priority(severity: Severity, facility: Facility) -> Priority { facility as u8 | severity as u8 } +/// The value to use when a field is not present +/// Defined by rfc5424 as a single hyphen +const NILL_VALUE: &str = "-"; + #[cfg(unix)] // On unix platforms, time::OffsetDateTime::now_local always returns an error so use UTC instead // https://github.com/time-rs/time/issues/380 @@ -280,6 +321,34 @@ mod test { assert_eq!(value, "\\]"); } + #[test] + fn space_is_out_of_us_printable_ascii() { + assert!(!is_us_print_ascii(&' ')); + } + + #[test] + fn ascii_chars_are_in_range() { + for i in 33..=126 { + assert!(is_us_print_ascii(&char::from(i))); + } + for i in 'a'..'z' { + assert!(is_us_print_ascii(&i)); + } + for i in 'A'..'Z' { + assert!(is_us_print_ascii(&i)); + } + } + + #[test] + fn ascii_thirty_two_out_of_range() { + assert!(!is_us_print_ascii(&char::from(32))); + } + + #[test] + fn ascii_one_hundred_twenty_seven_out_of_range() { + assert!(!is_us_print_ascii(&char::from(127))); + } + #[test] fn test_formatter3164_defaults() { let d = Formatter3164::default();