diff --git a/src/chunks/firehose/firehose_log.rs b/src/chunks/firehose/firehose_log.rs index 7fcd768..f3a8274 100755 --- a/src/chunks/firehose/firehose_log.rs +++ b/src/chunks/firehose/firehose_log.rs @@ -13,6 +13,8 @@ use crate::chunks::firehose::trace::FirehoseTrace; use crate::util::{encode_standard, extract_string_size, padding_size, padding_size_four}; use log::{debug, error, warn}; use nom::bytes::complete::take_while; +use nom::combinator::map; +use nom::multi::many_m_n; use nom::number::complete::{be_u128, le_i16, le_i32, le_i64, le_i8}; use nom::{ bytes::complete::take, @@ -82,80 +84,72 @@ pub struct FirehoseItemInfo { } impl FirehosePreamble { + // Now at the end of firehose item types. + // Remaining data (if any) contains strings for the string item types + const STRING_ITEM: [u8; 8] = [0x20, 0x22, 0x40, 0x42, 0x30, 0x31, 0x32, 0xf2]; + const PRIVATE_NUMBER: u8 = 0x1; + // 0x81 and 0xf1 Added in macOS Sequioa + const PRIVATE_STRINGS: [u8; 7] = [0x21, 0x25, 0x35, 0x31, 0x41, 0x81, 0xf1]; + const LOG_TYPES: [u8; 5] = [0x2, 0x6, 0x4, 0x7, 0x3]; + const REMNANT_DATA: u8 = 0x0; + /// Parse the start of the Firehose data pub fn parse_firehose_preamble( firehose_input_data: &[u8], ) -> nom::IResult<&[u8], FirehosePreamble> { let mut firehose_data = FirehosePreamble::default(); - let (input, chunk_tag) = take(size_of::())(firehose_input_data)?; - let (input, chunk_sub_tag) = take(size_of::())(input)?; - let (input, chunk_data_size) = take(size_of::())(input)?; - let (input, first_number_proc_id) = take(size_of::())(input)?; - let (input, second_number_proc_id) = take(size_of::())(input)?; - let (input, ttl) = take(size_of::())(input)?; - let (input, collapsed) = take(size_of::())(input)?; + let (input, chunk_tag) = le_u32(firehose_input_data)?; + let (input, chunk_sub_tag) = le_u32(input)?; + let (input, chunk_data_size) = le_u64(input)?; + let (input, first_proc_id) = le_u64(input)?; + let (input, second_proc_id) = le_u32(input)?; + let (input, ttl) = le_u8(input)?; + let (input, collapsed) = le_u8(input)?; let (input, unknown) = take(size_of::())(input)?; - let (input, public_data_size) = take(size_of::())(input)?; - let (input, private_data_virtual_offset) = take(size_of::())(input)?; - let (input, unknown2) = take(size_of::())(input)?; - let (input, unknown3) = take(size_of::())(input)?; - let (log_data, base_continous_time) = take(size_of::())(input)?; - - let (_, firehose_chunk_tag) = le_u32(chunk_tag)?; - let (_, firehose_chunk_sub_tag) = le_u32(chunk_sub_tag)?; - let (_, firehose_chunk_data_size) = le_u64(chunk_data_size)?; - let (_, firehose_first_proc_id) = le_u64(first_number_proc_id)?; - let (_, firehose_second_proc_id) = le_u32(second_number_proc_id)?; - let (_, firehose_collapsed) = le_u8(collapsed)?; - let (_, firehose_ttl) = le_u8(ttl)?; - - let (_, firehose_public_data_size) = le_u16(public_data_size)?; - let (_, firehose_private_data_virtual_offset) = le_u16(private_data_virtual_offset)?; - let (_, firehose_unknown2) = le_u16(unknown2)?; - let (_, firehose_unknown3) = le_u16(unknown3)?; - let (_, firehose_base_continous_time) = le_u64(base_continous_time)?; - - firehose_data.chunk_tag = firehose_chunk_tag; - firehose_data.chunk_sub_tag = firehose_chunk_sub_tag; - firehose_data.chunk_data_size = firehose_chunk_data_size; - firehose_data.first_number_proc_id = firehose_first_proc_id; - firehose_data.second_number_proc_id = firehose_second_proc_id; - firehose_data.collapsed = firehose_collapsed; - firehose_data.ttl = firehose_ttl; + let (input, public_data_size) = le_u16(input)?; + let (input, private_data_virtual_offset) = le_u16(input)?; + let (input, unknown2) = le_u16(input)?; + let (input, unknown3) = le_u16(input)?; + let (log_data, base_continous_time) = le_u64(input)?; + + firehose_data.chunk_tag = chunk_tag; + firehose_data.chunk_sub_tag = chunk_sub_tag; + firehose_data.chunk_data_size = chunk_data_size; + firehose_data.first_number_proc_id = first_proc_id; + firehose_data.second_number_proc_id = second_proc_id; + firehose_data.collapsed = collapsed; + firehose_data.ttl = ttl; firehose_data.unknown = unknown.to_vec(); - firehose_data.public_data_size = firehose_public_data_size; - firehose_data.private_data_virtual_offset = firehose_private_data_virtual_offset; - firehose_data.unkonwn2 = firehose_unknown2; - firehose_data.unknown3 = firehose_unknown3; - firehose_data.base_continous_time = firehose_base_continous_time; + firehose_data.public_data_size = public_data_size; + firehose_data.private_data_virtual_offset = private_data_virtual_offset; + firehose_data.unkonwn2 = unknown2; + firehose_data.unknown3 = unknown3; + firehose_data.base_continous_time = base_continous_time; // firehose_public_data_size includes the 16 bytes before the public data offset let public_data_size_offset = 16; let (mut input, mut public_data) = - take(firehose_public_data_size - public_data_size_offset)(log_data)?; + take(public_data_size - public_data_size_offset)(log_data)?; - let log_types = [0x2, 0x6, 0x4, 0x7, 0x3]; - - let remnant_data = 0x0; // Go through all the public data associated with log Firehose entry while !public_data.is_empty() { let (firehose_input, firehose_public_data) = FirehosePreamble::parse_firehose(public_data)?; public_data = firehose_input; - if !log_types.contains(&firehose_public_data.unknown_log_activity_type) + if !Self::LOG_TYPES.contains(&firehose_public_data.unknown_log_activity_type) || public_data.len() < 24 { - if remnant_data == firehose_public_data.unknown_log_activity_type { + if Self::REMNANT_DATA == firehose_public_data.unknown_log_activity_type { break; } - if firehose_private_data_virtual_offset != 0x1000 { + if private_data_virtual_offset != 0x1000 { let private_offst = 0x1000; - let private_data_offset = private_offst - firehose_private_data_virtual_offset; + let private_data_offset = private_offst - private_data_virtual_offset; // Calculate start of private data. If the remaining input is greater than private data offset. // Remove any padding/junk data in front of the private data if input.len() > private_data_offset.into() && public_data.is_empty() { @@ -164,12 +158,9 @@ impl FirehosePreamble { input = private_data; } else { // If log data and public data are the same size. Use private data offset to calculate the private data - if log_data.len() - == (firehose_public_data_size - public_data_size_offset) as usize - { + if log_data.len() == (public_data_size - public_data_size_offset) as usize { let (private_input_data, _) = take( - (firehose_private_data_virtual_offset - public_data_size_offset) - as usize + (private_data_virtual_offset - public_data_size_offset) as usize - public_data.len(), )(log_data)?; input = private_input_data; @@ -189,7 +180,7 @@ impl FirehosePreamble { 00000040: 6d 65 73 65 72 76 65 72 20 30 00 43 6f 6e 66 69 meserver 0.Confi */ let (private_input_data, _) = take( - (firehose_public_data_size - public_data_size_offset) as usize + (public_data_size - public_data_size_offset) as usize - public_data.len(), )(log_data)?; input = private_input_data; @@ -203,7 +194,7 @@ impl FirehosePreamble { } // If there is private data, go through and update any logs that have private data items - if firehose_private_data_virtual_offset != 0x1000 { + if private_data_virtual_offset != 0x1000 { debug!("[macos-unifiedlogs] Parsing Private Firehose Data"); // Nom any padding let (mut private_input, _) = take_while(|b: u8| b == 0)(input)?; @@ -220,8 +211,8 @@ impl FirehosePreamble { continue; } // Get the start of private string data - let string_offset = data.firehose_non_activity.private_strings_offset - - firehose_private_data_virtual_offset; + let string_offset = + data.firehose_non_activity.private_strings_offset - private_data_virtual_offset; let (private_string_start, _) = take(string_offset)(private_input)?; let _ = FirehosePreamble::parse_private_data(private_string_start, &mut data.message); @@ -313,13 +304,6 @@ impl FirehosePreamble { } } - // 0x81 and 0xf1 Added in macOS Sequioa - let private_strings: Vec = vec![0x21, 0x25, 0x35, 0x31, 0x41, 0x81, 0xf1]; - let private_number = 0x1; - // Now at the end of firehose item types. - // Remaining data (if any) contains strings for the string item types - let string_item: Vec = vec![0x20, 0x22, 0x40, 0x42, 0x30, 0x31, 0x32, 0xf2]; - for item in &mut items_data { // We already got number items above since the values immediantly follow the number type if number_item_type.contains(&item.item_type) { @@ -327,14 +311,14 @@ impl FirehosePreamble { } // Check if item type is a private string. This is used for privacy related data - if private_strings.contains(&item.item_type) + if Self::PRIVATE_STRINGS.contains(&item.item_type) || sensitive_items.contains(&item.item_type) { item.message_strings = String::from(""); continue; } - if item.item_type == private_number { + if item.item_type == Self::PRIVATE_NUMBER { continue; } @@ -350,10 +334,10 @@ impl FirehosePreamble { if firehose_input.is_empty() { break; } - if string_item.contains(&item.item_type) { + if Self::STRING_ITEM.contains(&item.item_type) { let (item_value_input, message_string) = FirehosePreamble::parse_item_string( firehose_input, - &item.item_type, + item.item_type, item.message_string_size, )?; firehose_input = item_value_input; @@ -433,37 +417,27 @@ impl FirehosePreamble { fn parse_firehose(data: &[u8]) -> nom::IResult<&[u8], Firehose> { let mut firehose_results = Firehose::default(); - let (input, unknown_log_activity_type) = take(size_of::())(data)?; - let (input, unknown_log_type) = take(size_of::())(input)?; - let (input, flags) = take(size_of::())(input)?; - let (input, format_string_location) = take(size_of::())(input)?; - - let (input, thread_id) = take(size_of::())(input)?; - - let (input, continous_time_delta) = take(size_of::())(input)?; - let (input, continous_time_delta_upper) = take(size_of::())(input)?; - let (input, data_size) = take(size_of::())(input)?; + let (input, unknown_log_activity_type) = le_u8(data)?; + let (input, unknown_log_type) = le_u8(input)?; + let (input, flags) = le_u16(input)?; + let (input, format_string_location) = le_u32(input)?; - let (_, firehose_unknown_log_activity_type) = le_u8(unknown_log_activity_type)?; - let (_, firehose_unknown_log_type) = le_u8(unknown_log_type)?; - let (_, firehose_flags) = le_u16(flags)?; - let (_, firehose_format_string_location) = le_u32(format_string_location)?; - let (_, firehose_continous_delta_upper) = le_u16(continous_time_delta_upper)?; - let (_, firehose_continous_delta) = le_u32(continous_time_delta)?; + let (input, thread_id) = le_u64(input)?; - let (_, firehose_thread_id) = le_u64(thread_id)?; - let (_, firehose_data_size) = le_u16(data_size)?; + let (input, continous_delta) = le_u32(input)?; + let (input, continous_delta_upper) = le_u16(input)?; + let (input, data_size) = le_u16(input)?; - firehose_results.unknown_log_activity_type = firehose_unknown_log_activity_type; - firehose_results.unknown_log_type = firehose_unknown_log_type; - firehose_results.flags = firehose_flags; - firehose_results.format_string_location = firehose_format_string_location; - firehose_results.thread_id = firehose_thread_id; - firehose_results.continous_time_delta_upper = firehose_continous_delta_upper; - firehose_results.continous_time_delta = firehose_continous_delta; - firehose_results.data_size = firehose_data_size; + firehose_results.unknown_log_activity_type = unknown_log_activity_type; + firehose_results.unknown_log_type = unknown_log_type; + firehose_results.flags = flags; + firehose_results.format_string_location = format_string_location; + firehose_results.thread_id = thread_id; + firehose_results.continous_time_delta_upper = continous_delta_upper; + firehose_results.continous_time_delta = continous_delta; + firehose_results.data_size = data_size; - let (mut input, mut firehose_input) = take(firehose_data_size)(input)?; + let (mut input, mut firehose_input) = take(data_size)(input)?; // Log activity type (unknown_log_activity_type) let activity: u8 = 0x2; @@ -475,40 +449,37 @@ impl FirehosePreamble { // Unknown types let unknown_remnant_data = 0x0; // 0x0 appears to be remnant data or garbage data (log command does not parse it either) - if firehose_unknown_log_activity_type == activity { - let (activity_data, activity) = FirehoseActivity::parse_activity( - firehose_input, - &firehose_flags, - &firehose_unknown_log_type, - )?; + if unknown_log_activity_type == activity { + let (activity_data, activity) = + FirehoseActivity::parse_activity(firehose_input, &flags, &unknown_log_type)?; firehose_input = activity_data; firehose_results.firehose_activity = activity; - } else if firehose_unknown_log_activity_type == nonactivity { + } else if unknown_log_activity_type == nonactivity { let (non_activity_data, non_activity) = - FirehoseNonActivity::parse_non_activity(firehose_input, &firehose_flags)?; + FirehoseNonActivity::parse_non_activity(firehose_input, &flags)?; firehose_input = non_activity_data; firehose_results.firehose_non_activity = non_activity; - } else if firehose_unknown_log_activity_type == signpost { + } else if unknown_log_activity_type == signpost { let (process_data, firehose_signpost) = - FirehoseSignpost::parse_signpost(firehose_input, &firehose_flags)?; + FirehoseSignpost::parse_signpost(firehose_input, &flags)?; firehose_input = process_data; firehose_results.firehose_signpost = firehose_signpost; - } else if firehose_unknown_log_activity_type == loss { + } else if unknown_log_activity_type == loss { let (loss_data, firehose_loss) = FirehoseLoss::parse_firehose_loss(firehose_input)?; firehose_results.firehose_loss = firehose_loss; firehose_input = loss_data; - } else if firehose_unknown_log_activity_type == trace { + } else if unknown_log_activity_type == trace { let (trace_data, firehose_trace) = FirehoseTrace::parse_firehose_trace(firehose_input)?; firehose_results.firehose_trace = firehose_trace; firehose_input = trace_data; firehose_results.message = firehose_results.firehose_trace.message_data.clone(); - } else if firehose_unknown_log_activity_type == unknown_remnant_data { + } else if unknown_log_activity_type == unknown_remnant_data { return Ok((input, firehose_results)); } else { warn!( "[macos-unifiedlogs] Unknown log activity type: {} - {} bytes left", - firehose_unknown_log_activity_type, + unknown_log_activity_type, input.len() ); debug!("[macos-unifiedlogs] Firehose data: {:X?}", data); @@ -524,20 +495,14 @@ impl FirehosePreamble { return Ok((input, firehose_results)); } - let (firehose_input, unknown_item) = take(size_of::())(firehose_input)?; - let (firehose_input, number_items) = take(size_of::())(firehose_input)?; + let (firehose_input, unknown_item) = le_u8(firehose_input)?; + let (firehose_input, number_items) = le_u8(firehose_input)?; - let (_, firehose_unknown_item) = le_u8(unknown_item)?; - let (_, firehose_number_items) = le_u8(number_items)?; + firehose_results.unknown_item = unknown_item; + firehose_results.number_items = number_items; - firehose_results.unknown_item = firehose_unknown_item; - firehose_results.number_items = firehose_number_items; - - let (_, firehose_item_data) = FirehosePreamble::collect_items( - firehose_input, - &firehose_number_items, - &firehose_flags, - )?; + let (_, firehose_item_data) = + FirehosePreamble::collect_items(firehose_input, &number_items, &flags)?; firehose_results.message = firehose_item_data; @@ -545,7 +510,7 @@ impl FirehosePreamble { let (remaining_data, taken_data) = take_while(|b: u8| b == 0)(input)?; // Verify we did not nom into remnant/junk data - let padding_data = padding_size(firehose_data_size.into()); + let padding_data = padding_size(data_size.into()); let (mut input, _) = take(padding_data)(input)?; if (padding_data as usize) > taken_data.len() { input = remaining_data; @@ -556,83 +521,58 @@ impl FirehosePreamble { // Parse Backtrace data for log entry (chunk). This only exists if has_context_data flag is set fn get_backtrace_data(data: &[u8]) -> nom::IResult<&[u8], Vec> { - let (input, _unknown_data) = take(size_of::())(data)?; - let (input, _unknown_data2) = take(size_of::())(input)?; - let (input, number_uuids) = take(size_of::())(input)?; - let (mut input, number_offsets) = take(size_of::())(input)?; - - let (_, backtrace_uuid_count) = le_u8(number_uuids)?; - let (_, backtrace_offsets_count) = le_u16(number_offsets)?; - - let mut uuid_vec: Vec = Vec::new(); - let mut count = 0; - while count < backtrace_uuid_count { - let (uuid_input, uuid_data) = take(size_of::())(input)?; - let (_, uuid) = be_u128(uuid_data)?; - uuid_vec.push(format!("{:X}", uuid)); - input = uuid_input; - count += 1; - } - count = 0; - let mut offsets_vec: Vec = Vec::new(); - while (u16::from(count)) < backtrace_offsets_count { - let (offset_input, offset) = take(size_of::())(input)?; - let (_, backtrace_offsets) = le_u32(offset)?; - offsets_vec.push(backtrace_offsets); - input = offset_input; - count += 1; - } - - let mut backtrace_data: Vec = Vec::new(); - count = 0; - while (u16::from(count)) < backtrace_offsets_count { - let (index_input, index) = take(size_of::())(input)?; - let (_, uuid_index) = le_u8(index)?; - if (uuid_index as usize) < uuid_vec.len() { - backtrace_data.push(format!( - "{:?} +0x{:?}", - uuid_vec[uuid_index as usize], offsets_vec[count as usize] - )); - } else { - backtrace_data.push(format!( - "00000000-0000-0000-0000-000000000000 +0x{:?}", - offsets_vec[count as usize] - )); - } - input = index_input; - count += 1; - } - let padding_size = padding_size_four(u64::from(backtrace_offsets_count)); + let (input, _unknown_data) = take(3usize)(data)?; + let (input, uuid_count) = map(le_u8, usize::from)(input)?; + let (input, offset_count) = map(le_u16, usize::from)(input)?; + + let (input, uuid_vec) = many_m_n(uuid_count, uuid_count, be_u128)(input)?; + + let (input, offsets_vec) = many_m_n(offset_count, offset_count, le_u32)(input)?; + + let (mut input, indexes) = + many_m_n(offset_count, offset_count, map(le_u8, usize::from))(input)?; + + let backtrace_data = indexes + .iter() + .enumerate() + .map(|(i, idx)| { + format!( + "\"{:X}\" +0x{:?}", + uuid_vec.get(*idx).unwrap_or(&0), + offsets_vec.get(i).unwrap_or(&0u32) + ) + }) + .collect::>(); + + let padding_size = padding_size_four(u64::try_from(offset_count).unwrap()); let (backtrace_input, _) = take(padding_size)(input)?; input = backtrace_input; + Ok((input, backtrace_data)) } // Get the strings, precision, and private (sensitive) firehose message items fn get_firehose_items(data: &[u8]) -> nom::IResult<&[u8], FirehoseItemType> { - let (firehose_input, item_type) = take(size_of::())(data)?; - let (mut firehose_input, item_size) = take(size_of::())(firehose_input)?; - let (_, item_type) = le_u8(item_type)?; - let (_, item_size) = le_u8(item_size)?; + let (firehost_input, item_type) = le_u8(data)?; + let (mut firehose_input, item_size) = le_u8(firehost_input)?; let mut item = FirehoseItemType::default(); item.item_type = item_type; item.item_size = item_size; // Firehose string item values - let string_item: Vec = vec![ + const STRING_ITEM: [u8; 14] = [ 0x20, 0x21, 0x22, 0x25, 0x40, 0x41, 0x42, 0x30, 0x31, 0x32, 0xf2, 0x35, 0x81, 0xf1, ]; - let private_number = 0x1; + const PRIVATE_NUMBER: u8 = 0x1; + // String and private number items metadata is 4 bytes // first two (2) bytes point to the offset of the string data // last two (2) bytes is the size of of string - if string_item.contains(&item.item_type) || item.item_type == private_number { + if STRING_ITEM.contains(&item.item_type) || item.item_type == PRIVATE_NUMBER { // The offset is relative to start of string data (after all other firehose items) - let (input, offset) = take(size_of::())(firehose_input)?; - let (_, message_offset) = le_u16(offset)?; - let (input, message_data_size) = take(size_of::())(input)?; - let (_, message_size) = le_u16(message_data_size)?; + let (input, message_offset) = le_u16(firehose_input)?; + let (input, message_size) = le_u16(input)?; item.offset = message_offset; item.message_string_size = message_size; @@ -640,17 +580,16 @@ impl FirehosePreamble { } // Precision items just contain the length for the actual item. Ex: %*s - let precision_items = [0x10, 0x12]; - if precision_items.contains(&item.item_type) { + const PRECISION_ITEMS: [u8; 2] = [0x10, 0x12]; + if PRECISION_ITEMS.contains(&item.item_type) { let (input, _) = take(item.item_size)(firehose_input)?; firehose_input = input; } - let sensitive_items = [0x5, 0x45, 0x85]; - if sensitive_items.contains(&item.item_type) { - let (input, offset) = take(size_of::())(firehose_input)?; - let (_, message_offset) = le_u16(offset)?; - let (input, message_data_size) = take(size_of::())(input)?; - let (_, message_size) = le_u16(message_data_size)?; + + const SENSITIVE_ITEMS: [u8; 3] = [0x5, 0x45, 0x85]; + if SENSITIVE_ITEMS.contains(&item.item_type) { + let (input, message_offset) = le_u16(firehose_input)?; + let (input, message_size) = le_u16(input)?; item.offset = message_offset; item.message_string_size = message_size; @@ -660,26 +599,27 @@ impl FirehosePreamble { } // Parse the item string - fn parse_item_string<'a>( - data: &'a [u8], - item_type: &u8, + fn parse_item_string( + data: &[u8], + item_type: u8, message_size: u16, - ) -> nom::IResult<&'a [u8], String> { + ) -> nom::IResult<&[u8], String> { // If message item size is greater than the remaining data, just use the rest of the data if message_size as usize > data.len() { return extract_string_size(data, data.len() as u64); } let (input, message_data) = take(message_size)(data)?; - let arbitrary: Vec = vec![0x30, 0x31, 0x32]; + + const ARBITRARY: [u8; 3] = [0x30, 0x31, 0x32]; // 0x30, 0x31, and 0x32 represent arbitrary data, need to be decoded again // Ex: name: %{private, mask.hash, mdnsresponder:domain_name}.*P, type: A, rdata: %{private, mask.hash, network:in_addr}.4P - if arbitrary.contains(item_type) { + if ARBITRARY.contains(&item_type) { return Ok((input, encode_standard(message_data))); } - let base64_raw_bytes: u8 = 0xf2; - if item_type == &base64_raw_bytes { + const BASE64_RAW_BYTES: u8 = 0xf2; + if item_type == BASE64_RAW_BYTES { return Ok((input, encode_standard(message_data))); } @@ -689,34 +629,21 @@ impl FirehosePreamble { // Parse the Firehose item number fn parse_item_number(data: &[u8], item_size: u16) -> nom::IResult<&[u8], i64> { - let (input, message_data) = take(item_size)(data)?; - let message_number = match item_size { - 4 => { - let (_, message) = le_i32(message_data)?; - i64::from(message) - } - 2 => { - let (_, message) = le_i16(message_data)?; - i64::from(message) - } - 8 => { - let (_, message) = le_i64(message_data)?; - message - } - 1 => { - let (_, message) = le_i8(message_data)?; - i64::from(message) - } + let input = data; + Ok(match item_size { + 4 => map(le_i32, i64::from)(input)?, + 2 => map(le_i16, i64::from)(input)?, + 8 => le_i64(input)?, + 1 => map(le_i8, i64::from)(input)?, _ => { warn!( "[macos-unifiedlogs] Unknown number size support: {:?}", item_size ); debug!("[macos-unifiedlogs] Item data: {:?}", data); - -9999 + (input, -9999) } - }; - Ok((input, message_number)) + }) } } @@ -2962,7 +2889,7 @@ mod tests { let test_item = 34; let test_size = 8; let (_, results) = - FirehosePreamble::parse_item_string(&test_data, &test_item, test_size).unwrap(); + FirehosePreamble::parse_item_string(&test_data, test_item, test_size).unwrap(); assert_eq!(results, "796.100"); }