diff --git a/src/decoders/decoder.rs b/src/decoders/decoder.rs index adf9d0e..8686959 100644 --- a/src/decoders/decoder.rs +++ b/src/decoders/decoder.rs @@ -70,9 +70,9 @@ pub(crate) fn check_objects( } else if format_string.contains("odtypes:mbridtype") { Ok(member_id_type(&message_values[index].message_strings)) } else if format_string.contains("odtypes:mbr_details") { - Ok(member_details(&message_values[index].message_strings)) + member_details(&message_values[index].message_strings) } else if format_string.contains("odtypes:nt_sid_t") { - Ok(sid_details(&message_values[index].message_strings)) + sid_details(&message_values[index].message_strings) } else if format_string.contains("location:CLClientAuthorizationStatus") { client_authorization_status(&message_values[index].message_strings) } else if format_string.contains("location:CLDaemonStatus_Type::Reachability") { @@ -86,11 +86,11 @@ pub(crate) fn check_objects( } else if format_string.contains("location:_CLLocationManagerStateTrackerState") { location_manager_state_tracker_state(&message_values[index].message_strings) } else if format_string.contains("network:in6_addr") { - Ok(ipv_six(&message_values[index].message_strings)) + ipv_six(&message_values[index].message_strings).map(|ip| ip.to_string()) } else if format_string.contains("network:in_addr") { - Ok(ipv_four(&message_values[index].message_strings)) + ipv_four(&message_values[index].message_strings).map(|ip| ip.to_string()) } else if format_string.contains("network:sockaddr") { - Ok(sockaddr(&message_values[index].message_strings)) + sockaddr(&message_values[index].message_strings) } else if format_string.contains("time_t") { Ok(parse_time(&message_values[index].message_strings)) } else if format_string.contains("mdns:dnshdr") { diff --git a/src/decoders/dns.rs b/src/decoders/dns.rs index 7a02083..8d70d5e 100644 --- a/src/decoders/dns.rs +++ b/src/decoders/dns.rs @@ -90,17 +90,17 @@ fn get_dns_flags(input: &[u8]) -> IResult<(&[u8], usize), String> { const Z: usize = 3; const RCODE: usize = 4; - type RET<'a> = ((&'a [u8], usize), u8); + type Ret<'a> = ((&'a [u8], usize), u8); use nom::bits::complete::take as bits; // Have to work with bits instead of bytes for the DNS flags - let ((input, offset), query): RET<'_> = bits(QR)((input, 0))?; - let ((input, offset), opcode): RET<'_> = bits(OPCODE)((input, offset))?; - let ((input, offset), authoritative_flag): RET<'_> = bits(AA)((input, offset))?; - let ((input, offset), truncation_flag): RET<'_> = bits(TC)((input, offset))?; - let ((input, offset), recursion_desired): RET<'_> = bits(RD)((input, offset))?; - let ((input, offset), recursion_available): RET<'_> = bits(RA)((input, offset))?; - let ((input, offset), _reserved): RET<'_> = bits(Z)((input, offset))?; - let ((input, _), response_code): RET<'_> = bits(RCODE)((input, offset))?; + let ((input, offset), query): Ret<'_> = bits(QR)((input, 0))?; + let ((input, offset), opcode): Ret<'_> = bits(OPCODE)((input, offset))?; + let ((input, offset), authoritative_flag): Ret<'_> = bits(AA)((input, offset))?; + let ((input, offset), truncation_flag): Ret<'_> = bits(TC)((input, offset))?; + let ((input, offset), recursion_desired): Ret<'_> = bits(RD)((input, offset))?; + let ((input, offset), recursion_available): Ret<'_> = bits(RA)((input, offset))?; + let ((input, offset), _reserved): Ret<'_> = bits(Z)((input, offset))?; + let ((input, _), response_code): Ret<'_> = bits(RCODE)((input, offset))?; let opcode_message = match opcode { 0 => "QUERY", @@ -324,9 +324,9 @@ fn parse_dns_ip_addr(data: &[u8]) -> nom::IResult<&[u8], String> { const IPV4: u32 = 4; const IPV6: u32 = 6; if ip_version == IPV4 { - return get_ip_four(data); + get_ip_four(data).map(|(data, result)| (data, result.to_string())) } else if ip_version == IPV6 { - return get_ip_six(data); + get_ip_six(data).map(|(data, result)| (data, result.to_string())) } else { fail(data) } diff --git a/src/decoders/mod.rs b/src/decoders/mod.rs index e89e9b9..ed77dd9 100644 --- a/src/decoders/mod.rs +++ b/src/decoders/mod.rs @@ -23,9 +23,9 @@ pub enum DecoderError<'a> { }, } -impl<'a> std::error::Error for DecoderError<'a> {} +impl std::error::Error for DecoderError<'_> {} -impl<'a> std::fmt::Display for DecoderError<'a> { +impl std::fmt::Display for DecoderError<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Parse { message, .. } => write!(f, "{message}"), @@ -33,7 +33,7 @@ impl<'a> std::fmt::Display for DecoderError<'a> { } } -impl<'a> std::fmt::Debug for DecoderError<'a> { +impl std::fmt::Debug for DecoderError<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Parse { diff --git a/src/decoders/network.rs b/src/decoders/network.rs index c28757a..1065d5a 100644 --- a/src/decoders/network.rs +++ b/src/decoders/network.rs @@ -5,159 +5,117 @@ // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and limitations under the License. +use super::DecoderError; use crate::util::decode_standard; -use log::{error, warn}; +use log::warn; use nom::{ - bytes::complete::take, combinator::map, number::complete::{be_u128, be_u16, be_u32, be_u8}, + sequence::tuple, }; -use std::net::Ipv6Addr; -use std::{mem::size_of, net::Ipv4Addr}; +use std::net::{Ipv4Addr, Ipv6Addr}; /// Parse an IPv6 address -pub(crate) fn ipv_six(data: &str) -> String { - let decoded_data_result = decode_standard(data); - let decoded_data = match decoded_data_result { - Ok(result) => result, - Err(err) => { - error!( - "[macos-unifiedlogs] Failed to base64 decode ipv6 data {}, error: {:?}", - data, err - ); - return String::from("Failed to base64 decode ipv6 data"); - } - }; - let message_result = get_ip_six(&decoded_data); - - match message_result { - Ok((_, result)) => result, - Err(err) => { - error!("[macos-unifiedlogs] Failed to get ipv6: {:?}", err); - format!("Failed to get ipv6: {}", data) - } - } +pub(crate) fn ipv_six(input: &str) -> Result> { + let decoded_data = decode_standard(input).map_err(|_| DecoderError::Parse { + input: input.as_bytes(), + parser_name: "ipv vix", + message: "Failed to base64 decode ipv6 data", + })?; + + let (_, result) = get_ip_six(&decoded_data).map_err(|_| DecoderError::Parse { + input: input.as_bytes(), + parser_name: "ipv vix", + message: "Failed to get ipv6", + })?; + + Ok(result) } /// Parse an IPv4 address -pub(crate) fn ipv_four(data: &str) -> String { - let decoded_data_result = decode_standard(data); - let decoded_data = match decoded_data_result { - Ok(result) => result, - Err(err) => { - error!( - "[macos-unifiedlogs] Failed to base64 decode ipv4 data {}, error: {:?}", - data, err - ); - return String::from("Failed to base64 decode ipv4 data"); - } - }; - let message_result = get_ip_four(&decoded_data); - - match message_result { - Ok((_, result)) => result, - Err(err) => { - error!("[macos-unifiedlogs] Failed to get ipv4: {:?}", err); - format!("Failed to get ipv4: {}", data) - } - } +pub(crate) fn ipv_four(input: &str) -> Result> { + let decoded_data = decode_standard(input).map_err(|_| DecoderError::Parse { + input: input.as_bytes(), + parser_name: "ipv four", + message: "Failed to base64 decode ipv4 data", + })?; + + let (_, result) = get_ip_four(&decoded_data).map_err(|_| DecoderError::Parse { + input: input.as_bytes(), + parser_name: "ipv four", + message: "Failed to get ipv4", + })?; + + Ok(result) } /// Parse a sockaddr structure -pub(crate) fn sockaddr(data: &str) -> String { - if data.is_empty() { - return String::from(""); - } - let decoded_data_result = decode_standard(data); - let decoded_data = match decoded_data_result { - Ok(result) => result, - Err(err) => { - error!( - "[macos-unifiedlogs] Failed to bas64 decode sockaddr data {}, error: {:?}", - data, err - ); - return String::from("Failed to base64 decode sockaddr data"); - } - }; - let message_result = get_sockaddr_data(&decoded_data); - match message_result { - Ok((_, result)) => result, - Err(err) => { - error!( - "[macos-unifiedlogs] Failed to get sockaddr structure: {:?}", - err - ); - format!("Failed to get sockaddr: {}", data) - } +pub(crate) fn sockaddr(input: &str) -> Result> { + if input.is_empty() { + return Ok(String::from("")); } + + let decoded_data = decode_standard(input).map_err(|_| DecoderError::Parse { + input: input.as_bytes(), + parser_name: "sock addr", + message: "Failed to bas64 decode sockaddr data", + })?; + + let (_, result) = get_sockaddr_data(&decoded_data).map_err(|_| DecoderError::Parse { + input: input.as_bytes(), + parser_name: "ipv four", + message: "Failed to get sockaddr structure", + })?; + + Ok(result) } /// Get the sockaddr data -fn get_sockaddr_data(data: &[u8]) -> nom::IResult<&[u8], String> { - let message; - let (sock_data, _total_length) = take(size_of::())(data)?; - - let (sock_data, family_data) = take(size_of::())(sock_data)?; - let (_, family) = be_u8(family_data)?; +fn get_sockaddr_data(input: &[u8]) -> nom::IResult<&[u8], String> { + let (input, (_total_length, family)) = tuple((be_u8, be_u8))(input)?; // Family types seen so far (AF_INET should be used most often) match family { 2 => { // AF_INET - let (sock_data, port_data) = take(size_of::())(sock_data)?; - let (_, ip_data) = take(size_of::())(sock_data)?; + let (input, port) = be_u16(input)?; + let (input, ip_addr) = get_ip_four(input)?; - let (_, port) = be_u16(port_data)?; - let (_, ip) = be_u32(ip_data)?; - let ip_addr = Ipv4Addr::from(ip); - - if port != 0 { - message = format!("{}:{}", ip_addr, port); - } else { - message = ip_addr.to_string(); + match port { + 0 => Ok((input, ip_addr.to_string())), + _ => Ok((input, format!("{ip_addr}:{port}"))), } } 30 => { - // AF_INET6 - let (sock_data, port_data) = take(size_of::())(sock_data)?; - let (sock_data, flow_data) = take(size_of::())(sock_data)?; - let (sock_data, ip_data) = take(size_of::())(sock_data)?; - let (_, scope_data) = take(size_of::())(sock_data)?; - - let (_, port) = be_u16(port_data)?; - let (_, flow) = be_u32(flow_data)?; - let (_, ip) = be_u128(ip_data)?; - let (_, scope) = be_u32(scope_data)?; - - let ip_addr = Ipv6Addr::from(ip); - if port != 0 { - message = format!( - "{}:{}, Flow ID: {}, Scope ID: {}", - ip_addr, port, flow, scope - ); - } else { - message = format!("{}, Flow ID: {}, Scope ID: {}", ip_addr, flow, scope); + let (input, (port, flow, ip_addr, scope)) = + tuple((be_u16, be_u32, get_ip_six, be_u32))(input)?; + + match port { + 0 => Ok(( + input, + format!("{ip_addr}, Flow ID: {flow}, Scope ID: {scope}"), + )), + _ => Ok(( + input, + format!("{ip_addr}:{port}, Flow ID: {flow}, Scope ID: {scope}"), + )), } } _ => { - warn!( - "[macos-unifiedlogs] Unknown sockaddr family: {}. From: {:?}", - family, data - ); - message = format!("Unknown sockaddr family: {}", family); + warn!("[macos-unifiedlogs] Unknown sockaddr family: {family}. From: {input:?}",); + Ok((input, format!("Unknown sockaddr family: {family}",))) } } - Ok((sock_data, message)) } /// Get the IPv4 data -pub(crate) fn get_ip_four(input: &[u8]) -> nom::IResult<&[u8], String> { - map(be_u32, |val| Ipv4Addr::from(val).to_string())(input) +pub(crate) fn get_ip_four(input: &[u8]) -> nom::IResult<&[u8], Ipv4Addr> { + map(be_u32, Ipv4Addr::from)(input) } /// Get the IPv6 data -pub(crate) fn get_ip_six(input: &[u8]) -> nom::IResult<&[u8], String> { - map(be_u128, |val| Ipv6Addr::from(val).to_string())(input) +pub(crate) fn get_ip_six(input: &[u8]) -> nom::IResult<&[u8], Ipv6Addr> { + map(be_u128, Ipv6Addr::from)(input) } #[cfg(test)] @@ -172,8 +130,8 @@ mod tests { #[test] fn test_ipv_six() { let test_data = "/wIAAAAAAAAAAAAAAAAA+w=="; - let result = ipv_six(test_data); - assert_eq!(result, "ff02::fb"); + let result = ipv_six(test_data).unwrap(); + assert_eq!(result.to_string(), "ff02::fb"); } #[test] @@ -182,14 +140,14 @@ mod tests { let decoded_data_result = decode_standard(test_data).unwrap(); let (_, result) = get_ip_six(&decoded_data_result).unwrap(); - assert_eq!(result, "ff02::fb"); + assert_eq!(result.to_string(), "ff02::fb"); } #[test] fn test_ipv_four() { let test_data = "4AAA+w=="; - let result = ipv_four(test_data); - assert_eq!(result, "224.0.0.251"); + let result = ipv_four(test_data).unwrap(); + assert_eq!(result.to_string(), "224.0.0.251"); } #[test] @@ -198,17 +156,16 @@ mod tests { let decoded_data_result = decode_standard(test_data).unwrap(); let (_, result) = get_ip_four(&decoded_data_result).unwrap(); - assert_eq!(result, "224.0.0.251"); + assert_eq!(result.to_string(), "224.0.0.251"); } - #[test] fn test_sockaddr() { let mut test_data = "EAIAALgciWcAAAAAAAAAAA=="; - let mut result = sockaddr(test_data); + let mut result = sockaddr(test_data).unwrap(); assert_eq!(result, "184.28.137.103"); test_data = "HB4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="; - result = sockaddr(test_data); + result = sockaddr(test_data).unwrap(); assert_eq!(result, "::, Flow ID: 0, Scope ID: 0"); } diff --git a/src/decoders/opendirectory.rs b/src/decoders/opendirectory.rs index 92780c5..8c990ab 100644 --- a/src/decoders/opendirectory.rs +++ b/src/decoders/opendirectory.rs @@ -5,13 +5,16 @@ // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and limitations under the License. -use crate::util::{decode_standard, extract_string}; -use log::{error, warn}; +use super::DecoderError; +use crate::util::{decode_standard, non_empty_cstring}; +use log::warn; use nom::{ - bytes::complete::{take, take_while}, + bytes::complete::take, + multi::fold_many0, number::complete::{le_i32, le_u32, le_u8}, + sequence::tuple, }; -use std::mem::size_of; +use std::fmt::Write; /// Convert Open Directory error codes to message pub(crate) fn errors(oderror: &str) -> String { @@ -119,165 +122,109 @@ pub(crate) fn member_id_type(member_string: &str) -> String { } /// Convert Open Directory member details to string -pub(crate) fn member_details(member_string: &str) -> String { - let decoded_data_result = decode_standard(member_string); - let decoded_data = match decoded_data_result { - Ok(result) => result, - Err(err) => { - error!("[macos-unifiedlogs] Failed to base64 decode open directory member details data {}, error: {:?}", member_string, err); - return String::from("Failed to base64 decode member details"); - } - }; - let message_result = get_member_data(&decoded_data); - match message_result { - Ok((_, result)) => result, - Err(err) => { - error!( - "[macos-unifiedlogs] Failed to get open directory member details: {:?}", - err - ); - format!( - "Failed to get open directory member details: {}", - member_string - ) - } - } +pub(crate) fn member_details(input: &str) -> Result> { + let decoded_data = decode_standard(input).map_err(|_| DecoderError::Parse { + input: input.as_bytes(), + parser_name: "member details", + message: "Failed to base64 decode open directory member details data", + })?; + + let (_, result) = get_member_data(&decoded_data).map_err(|_| DecoderError::Parse { + input: input.as_bytes(), + parser_name: "member details", + message: "Failed to get open directory member details", + })?; + + Ok(result) } /// Parse SID log data to SID string -pub(crate) fn sid_details(sid_string: &str) -> String { - let decoded_data_result = decode_standard(sid_string); - let decoded_data = match decoded_data_result { - Ok(result) => result, - Err(err) => { - error!("[macos-unifiedlogs] Failed to base64 decode open directory SID details data {}, error: {:?}", sid_string, err); - return String::from("Failed to base64 decode SID details"); - } - }; - let message_result = get_sid_data(&decoded_data); - match message_result { - Ok((_, result)) => result, - Err(err) => { - error!( - "[macos-unifiedlogs] Failed to get open directory sid details: {:?}", - err - ); - - format!("Failed to get open directory sid details: {}", sid_string) - } - } +pub(crate) fn sid_details(input: &str) -> Result> { + let decoded_data = decode_standard(input).map_err(|_| DecoderError::Parse { + input: input.as_bytes(), + parser_name: "sid details", + message: "Failed to base64 decode open directory SID details data", + })?; + + let (_, result) = get_sid_data(&decoded_data).map_err(|_| DecoderError::Parse { + input: input.as_bytes(), + parser_name: "sid details", + message: "Failed to get open directory sid details", + })?; + + Ok(result) } /// Parse Open Directory membership details data -fn get_member_data(data: &[u8]) -> nom::IResult<&[u8], String> { - let (mut member_details, member_type_data) = take(size_of::())(data)?; - let (_, member_type) = le_u8(member_type_data)?; - - let user_type = [36, 160, 164]; - let uid_type = [35, 163]; - let group_type = [68]; - let gid_type = [195]; - - let member_message; - if uid_type.contains(&member_type) { - let (details, uid) = get_member_id(member_details)?; - member_details = details; - member_message = format!("user: {}", uid); - } else if user_type.contains(&member_type) { - let (details, name) = get_member_string(member_details)?; - member_details = details; - member_message = format!("user: {}", name); - } else if gid_type.contains(&member_type) { - let (details, gid) = get_member_id(member_details)?; - member_details = details; - member_message = format!("group: {}", gid); - } else if group_type.contains(&member_type) { - let (details, name) = get_member_string(member_details)?; - member_details = details; - member_message = format!("group: {}", name); - } else { - warn!( - "[macos-unifiedlogs] Unknown open directory member type: {}", - member_type - ); - member_message = format!("Unknown Member type {}: @", member_type); - } - - let mut source_path = String::from(" "); - if !member_details.is_empty() { - let (details, path) = get_member_string(member_details)?; - source_path = path; - member_details = details; - } +fn get_member_data(input: &[u8]) -> nom::IResult<&[u8], String> { + let (input, member_type) = le_u8(input)?; + let (input, member_message) = match member_type { + 35 | 163 => { + // UID + let (input, uid) = get_member_id(input)?; + (input, format!("user: {uid}")) + } + 36 | 160 | 164 => { + // USER + let (input, name) = non_empty_cstring(input)?; + (input, format!("user: {name}")) + } + 68 => { + // GROUP + let (input, name) = non_empty_cstring(input)?; + (input, format!("group: {name}")) + } + 195 => { + // GID + let (input, gid) = get_member_id(input)?; + (input, format!("group: {gid}")) + } + _ => { + warn!("[macos-unifiedlogs] Unknown open directory member type: {member_type}",); + (input, format!("Unknown Member type {member_type}: @")) + } + }; - if source_path != " " { - source_path = format!("@{}", source_path) - } + let (input, source_path) = match non_empty_cstring(input) { + Ok((input, path)) => (input, format!("@{path}")), + Err(_) => (input, " ".to_string()), + }; let message = format!("{}{}", member_message, source_path); - Ok((member_details, message)) + Ok((input, message)) } /// Get UID/GID for Opendirectory membership -fn get_member_id(data: &[u8]) -> nom::IResult<&[u8], i32> { - let (details, id_data) = take(size_of::())(data)?; - let (_, id) = le_i32(id_data)?; - Ok((details, id)) +fn get_member_id(input: &[u8]) -> nom::IResult<&[u8], i32> { + le_i32(input) } -/// Get the username/group name for Opendirectory membership -fn get_member_string(data: &[u8]) -> nom::IResult<&[u8], String> { - let mut string_value = String::from(" "); - let (details, string_data) = take_while(|b: u8| b != 0)(data)?; - if string_data.is_empty() { - return Ok((details, string_value)); - } +/// Parse the SID data +fn get_sid_data(input: &[u8]) -> nom::IResult<&[u8], String> { + let (input, revision) = le_u8(input)?; - let (_, value) = extract_string(string_data)?; - if value != "Could not extract string" { - string_value = value; - } + let (input, unknown_size) = le_u8(input)?; + let (input, _) = take(unknown_size)(input)?; - // Nom of end string character - let (details, _) = take(size_of::())(details)?; - Ok((details, string_value)) -} + let (input, authority) = le_u8(input)?; + let (input, (subauthority, _)) = tuple((le_u8, take(3_usize)))(input)?; -/// Parse the SID data -fn get_sid_data(data: &[u8]) -> nom::IResult<&[u8], String> { - let (sid_details, revision_data) = take(size_of::())(data)?; - let (sid_details, unknown_size_data) = take(size_of::())(sid_details)?; - let (_, unknown_size) = le_u8(unknown_size_data)?; - - let (sid_details, _) = take(unknown_size)(sid_details)?; - let (sid_details, authority_data) = take(size_of::())(sid_details)?; - let (mut sid_details, subauthority_data) = take(size_of::())(sid_details)?; - - let (_, revision) = le_u8(revision_data)?; - let (_, authority) = le_u8(authority_data)?; - let (_, subauthority) = le_u8(subauthority_data)?; - - let mut message = format!("S-{}-{}-{}", revision, authority, subauthority); - - let subauthorit_size = 4; - while sid_details.len() >= subauthorit_size { - let (details, additional_subauthority_data) = take(subauthorit_size)(sid_details)?; - sid_details = details; - let (_, subauthority) = le_u32(additional_subauthority_data)?; - message = format!("{}-{}", message, subauthority); - } - Ok((sid_details, message)) + let (input, message) = fold_many0( + le_u32, + || format!("S-{}-{}-{}", revision, authority, subauthority), + |mut acc, additional_subauthority| { + write!(&mut acc, "-{additional_subauthority}").ok(); // ignored Write error + acc + }, + )(input)?; + + Ok((input, message)) } #[cfg(test)] mod tests { - use crate::{ - decoders::opendirectory::{ - errors, get_member_data, get_member_id, get_member_string, get_sid_data, - member_details, member_id_type, sid_details, - }, - util::decode_standard, - }; + use super::*; + use crate::util::decode_standard; #[test] fn test_errors() { @@ -304,14 +251,14 @@ mod tests { #[test] fn test_member_details_user() { let test_data = "I/7///8vTG9jYWwvRGVmYXVsdAA="; - let result = member_details(test_data); + let result = member_details(test_data).unwrap(); assert_eq!(result, "user: -2@/Local/Default"); } #[test] fn test_member_details_group() { let test_data = "RGNvbS5hcHBsZS5zaGFyZXBvaW50Lmdyb3VwLjEAL0xvY2FsL0RlZmF1bHQA"; - let result = member_details(test_data); + let result = member_details(test_data).unwrap(); assert_eq!(result, "group: com.apple.sharepoint.group.1@/Local/Default"); } @@ -331,7 +278,7 @@ mod tests { 108, 116, 0, ]; - let (_, result) = get_member_string(&test_data).unwrap(); + let (_, result) = non_empty_cstring(&test_data).unwrap(); assert_eq!(result, "nobody"); } @@ -346,7 +293,7 @@ mod tests { #[test] fn test_sid_details() { let test_data = "AQUAAAAAAAUVAAAAxbsdAg3Yp1FTmi50HAYAAA=="; - let result = sid_details(test_data); + let result = sid_details(test_data).unwrap(); assert_eq!(result, "S-1-5-21-35503045-1369954317-1949211219-1564"); } diff --git a/src/util.rs b/src/util.rs index e34747f..9da9d36 100755 --- a/src/util.rs +++ b/src/util.rs @@ -5,29 +5,28 @@ // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and limitations under the License. -use base64::engine::general_purpose; -use base64::DecodeError; -use base64::Engine; -use chrono::SecondsFormat; -use chrono::TimeZone; -use chrono::Utc; +use base64::{engine::general_purpose, DecodeError, Engine}; +use chrono::{SecondsFormat, TimeZone, Utc}; use log::{error, warn}; -use nom::bytes::complete::take; -use nom::bytes::complete::take_while; +use nom::{ + bytes::complete::{take, take_while}, + combinator::{fail, opt}, + sequence::tuple, +}; use std::str::from_utf8; /// Calculate 8 byte padding pub(crate) fn padding_size(data: u64) -> u64 { - let alignment = 8; + const ALIGNMENT: u64 = 8; // Calculate padding to achieve 64-bit alignment - (alignment - (data & (alignment - 1))) & (alignment - 1) + (ALIGNMENT - (data & (ALIGNMENT - 1))) & (ALIGNMENT - 1) } /// Calculate 4 byte padding pub(crate) fn padding_size_four(data: u64) -> u64 { - let alignment = 4; + const ALIGNMENT: u64 = 4; // Calculate padding to achieve 64-bit alignment - (alignment - (data & (alignment - 1))) & (alignment - 1) + (ALIGNMENT - (data & (ALIGNMENT - 1))) & (ALIGNMENT - 1) } /// Extract a size based on provided string size from Firehose string item entries @@ -64,13 +63,37 @@ pub(crate) fn extract_string_size(data: &[u8], message_size: u64) -> nom::IResul Ok((input, String::from("Could not find path string"))) } +const NULL_BYTE: u8 = 0; + +#[allow(dead_code)] +/// Extract an UTF8 string from a byte array, stops at `NULL_BYTE` or END OF STRING +/// Consumes the end byte +pub(crate) fn cstring(input: &[u8]) -> nom::IResult<&[u8], String> { + let (input, (str_part, _)) = + tuple((take_while(|b: u8| b != NULL_BYTE), opt(take(1_usize))))(input)?; + match from_utf8(str_part) { + Ok(results) => Ok((input, results.to_string())), + Err(_) => fail(input), + } +} + +/// Extract an UTF8 string from a byte array, stops at `NULL_BYTE` or END OF STRING +/// Consumes the end byte +/// Fails if the string is empty +pub(crate) fn non_empty_cstring(input: &[u8]) -> nom::IResult<&[u8], String> { + let (input, (str_part, _)) = + tuple((take_while(|b: u8| b != NULL_BYTE), opt(take(1_usize))))(input)?; + match from_utf8(str_part) { + Ok(s) if !s.is_empty() => Ok((input, s.to_string())), + _ => fail(input), + } +} + /// Extract strings that contain end of string characters pub(crate) fn extract_string(data: &[u8]) -> nom::IResult<&[u8], String> { let last_value = data.last(); match last_value { Some(value) => { - const NULL_BYTE: u8 = 0; - // If message data does not end with end of string character (0) // just grab everything and convert what we have to string if value != &NULL_BYTE { @@ -130,8 +153,7 @@ pub(crate) fn unixepoch_to_iso(timestamp: &i64) -> String { #[cfg(test)] mod tests { - use super::{decode_standard, encode_standard, unixepoch_to_iso}; - use crate::util::{extract_string, extract_string_size, padding_size, padding_size_four}; + use super::*; #[test] fn test_padding_size() { @@ -181,4 +203,53 @@ mod tests { let result = unixepoch_to_iso(&1650767813342574583); assert_eq!(result, "2022-04-24T02:36:53.342574583Z"); } + + #[test] + fn test_cstring() -> anyhow::Result<()> { + let input = &[55, 57, 54, 46, 49, 48, 48, 0]; + let (output, s) = cstring(input)?; + assert!(output.is_empty()); + assert_eq!(s, "796.100"); + + let input = &[55, 57, 54, 46, 49, 48, 48]; + let (output, s) = cstring(input)?; + assert!(output.is_empty()); + assert_eq!(s, "796.100"); + + let input = &[55, 57, 54, 46, 49, 48, 48, 0, 42, 42, 42]; + let (output, s) = cstring(input)?; + assert_eq!(output, [42, 42, 42]); + assert_eq!(s, "796.100"); + + let input = &[0, 42, 42, 42]; + let (output, s) = cstring(input)?; + assert_eq!(output, [42, 42, 42]); + assert_eq!(s, ""); + + Ok(()) + } + + #[test] + fn test_non_empty_cstring() -> anyhow::Result<()> { + let input = &[55, 57, 54, 46, 49, 48, 48, 0]; + let (output, s) = non_empty_cstring(input)?; + assert!(output.is_empty()); + assert_eq!(s, "796.100"); + + let input = &[55, 57, 54, 46, 49, 48, 48]; + let (output, s) = non_empty_cstring(input)?; + assert!(output.is_empty()); + assert_eq!(s, "796.100"); + + let input = &[55, 57, 54, 46, 49, 48, 48, 0, 42, 42, 42]; + let (output, s) = non_empty_cstring(input)?; + assert_eq!(output, [42, 42, 42]); + assert_eq!(s, "796.100"); + + let input = &[0, 42, 42, 42]; + let result = non_empty_cstring(input); + assert!(result.is_err()); + + Ok(()) + } }