1use std::collections::HashMap;
13
14use crate::catalog::CatalogChunk;
15use crate::chunks::firehose::activity::FirehoseActivity;
16use crate::chunks::firehose::firehose_log::{Firehose, FirehoseItemInfo, FirehosePreamble};
17use crate::chunks::firehose::nonactivity::FirehoseNonActivity;
18use crate::chunks::firehose::signpost::FirehoseSignpost;
19use crate::chunks::firehose::trace::FirehoseTrace;
20use crate::chunks::oversize::Oversize;
21use crate::chunks::simpledump::SimpleDump;
22use crate::chunks::statedump::Statedump;
23use crate::chunkset::ChunksetChunk;
24use crate::header::HeaderChunk;
25use crate::message::format_firehose_log_message;
26use crate::preamble::LogPreamble;
27use crate::timesync::TimesyncBoot;
28use crate::traits::FileProvider;
29use crate::util::{
30 encode_standard, extract_string, padding_size_8, u64_to_usize, unixepoch_to_iso,
31};
32use log::{error, warn};
33use nom::bytes::complete::take;
34use regex::Regex;
35use serde::Serialize;
36use sunlight::light::extract_protobuf;
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
39pub enum LogType {
40 Debug,
41 Info,
42 Default,
43 Error,
44 Fault,
45 Create,
46 Useraction,
47 ProcessSignpostEvent,
48 ProcessSignpostStart,
49 ProcessSignpostEnd,
50 SystemSignpostEvent,
51 SystemSignpostStart,
52 SystemSignpostEnd,
53 ThreadSignpostEvent,
54 ThreadSignpostStart,
55 ThreadSignpostEnd,
56 Simpledump,
57 Statedump,
58 Loss,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
62pub enum EventType {
63 Unknown,
64 Log,
65 Activity,
66 Trace,
67 Signpost,
68 Simpledump,
69 Statedump,
70 Loss,
71}
72
73#[derive(Debug, Clone, Default)]
74pub struct UnifiedLogData {
75 pub header: Vec<HeaderChunk>,
76 pub catalog_data: Vec<UnifiedLogCatalogData>,
77 pub oversize: Vec<Oversize>,
79}
80
81#[derive(Debug, Clone, Default)]
82pub struct UnifiedLogCatalogData {
83 pub catalog: CatalogChunk,
84 pub firehose: Vec<FirehosePreamble>,
85 pub simpledump: Vec<SimpleDump>,
86 pub statedump: Vec<Statedump>,
87 pub oversize: Vec<Oversize>,
88}
89
90struct LogIterator<'a> {
91 unified_log_data: &'a UnifiedLogData,
92 provider: &'a mut dyn FileProvider,
93 timesync_data: &'a HashMap<String, TimesyncBoot>,
94 exclude_missing: bool,
95 message_re: Regex,
96 catalog_data_iterator_index: usize,
97}
98impl<'a> LogIterator<'a> {
99 fn new(
100 unified_log_data: &'a UnifiedLogData,
101 provider: &'a mut dyn FileProvider,
102 timesync_data: &'a HashMap<String, TimesyncBoot>,
103 exclude_missing: bool,
104 ) -> Result<Self, regex::Error> {
105 let message_re_result = Regex::new(
129 r"(%(?:(?:\{[^}]+}?)(?:[-+0#]{0,5})(?:\d+|\*)?(?:\.(?:\d+|\*))?(?:h|hh|l|ll|w|I|z|t|q|I32|I64)?[cmCdiouxXeEfgGaAnpsSZP@}]|(?:[-+0 #]{0,5})(?:\d+|\*)?(?:\.(?:\d+|\*))?(?:h|hh|l||q|t|ll|w|I|z|I32|I64)?[cmCdiouxXeEfgGaAnpsSZP@%]))",
130 );
131 let message_re = match message_re_result {
132 Ok(message_re) => message_re,
133 Err(err) => {
134 error!("Failed to compile regex for printf format parsing: {err:?}");
135 return Err(err);
136 }
137 };
138
139 Ok(LogIterator {
140 unified_log_data,
141 provider,
142 timesync_data,
143 exclude_missing,
144 message_re,
145 catalog_data_iterator_index: 0,
146 })
147 }
148}
149
150impl Iterator for LogIterator<'_> {
151 type Item = (Vec<LogData>, UnifiedLogData);
152
153 fn next(&mut self) -> Option<Self::Item> {
155 let catalog_data = self
156 .unified_log_data
157 .catalog_data
158 .get(self.catalog_data_iterator_index)?;
159 let mut log_data_vec: Vec<LogData> = Vec::new();
160 let mut missing_unified_log_data_vec = UnifiedLogData {
162 header: Vec::new(),
163 catalog_data: Vec::new(),
164 oversize: Vec::new(),
165 };
166
167 for (preamble_index, preamble) in catalog_data.firehose.iter().enumerate() {
168 for (firehose_index, firehose) in preamble.public_data.iter().enumerate() {
169 let firehose_log_entry_continous_time = u64::from(firehose.continous_time_delta)
171 | ((u64::from(firehose.continous_time_delta_upper)) << 32);
172
173 let continous_time =
174 preamble.base_continous_time + firehose_log_entry_continous_time;
175
176 let timestamp = TimesyncBoot::get_timestamp(
178 self.timesync_data,
179 &self.unified_log_data.header[0].boot_uuid,
180 continous_time,
181 preamble.base_continous_time,
182 );
183
184 let mut log_data = LogData {
186 subsystem: String::new(),
187 thread_id: firehose.thread_id,
188 pid: catalog_data.catalog.get_pid(
189 preamble.first_number_proc_id,
190 preamble.second_number_proc_id,
191 ),
192 library: String::new(),
193 activity_id: 0,
194 time: timestamp,
195 timestamp: unixepoch_to_iso(&(timestamp as i64)),
196 category: String::new(),
197 log_type: LogData::get_log_type(
198 firehose.unknown_log_type,
199 firehose.unknown_log_activity_type,
200 ),
201 process: String::new(),
202 message: String::new(),
203 event_type: LogData::get_event_type(firehose.unknown_log_activity_type),
204 euid: catalog_data.catalog.get_euid(
205 preamble.first_number_proc_id,
206 preamble.second_number_proc_id,
207 ),
208 boot_uuid: self.unified_log_data.header[0].boot_uuid.to_owned(),
209 timezone_name: self.unified_log_data.header[0]
210 .timezone_path
211 .split('/')
212 .next_back()
213 .unwrap_or("Unknown Timezone Name")
214 .to_string(),
215 library_uuid: String::new(),
216 process_uuid: String::new(),
217 raw_message: String::new(),
218 message_entries: firehose.message.item_info.to_owned(),
219 };
220
221 match firehose.unknown_log_activity_type {
227 0x4 => {
228 log_data.activity_id =
229 u64::from(firehose.firehose_non_activity.unknown_activity_id);
230 let message_data = FirehoseNonActivity::get_firehose_nonactivity_strings(
231 &firehose.firehose_non_activity,
232 self.provider,
233 u64::from(firehose.format_string_location),
234 &preamble.first_number_proc_id,
235 &preamble.second_number_proc_id,
236 &catalog_data.catalog,
237 );
238
239 match message_data {
240 Ok((_, results)) => {
241 log_data.library = results.library;
242 log_data.library_uuid = results.library_uuid;
243 log_data.process = results.process;
244 log_data.process_uuid = results.process_uuid;
245 results.format_string.clone_into(&mut log_data.raw_message);
246
247 let log_message =
249 if firehose.firehose_non_activity.data_ref_value != 0 {
250 let oversize_strings = Oversize::get_oversize_strings(
251 firehose.firehose_non_activity.data_ref_value,
252 preamble.first_number_proc_id,
253 preamble.second_number_proc_id,
254 &self.unified_log_data.oversize,
255 );
256 format_firehose_log_message(
258 results.format_string,
259 &oversize_strings,
260 &self.message_re,
261 )
262 } else {
263 format_firehose_log_message(
265 results.format_string,
266 &firehose.message.item_info,
267 &self.message_re,
268 )
269 };
270 if self.exclude_missing
272 && log_message.contains("<Missing message data>")
273 {
274 LogData::add_missing(
275 catalog_data,
276 preamble_index,
277 firehose_index,
278 &self.unified_log_data.header,
279 &mut missing_unified_log_data_vec,
280 preamble,
281 );
282 continue;
283 }
284
285 if !firehose.message.backtrace_strings.is_empty() {
286 log_data.message = format!(
287 "Backtrace:\n{}\n{}",
288 firehose.message.backtrace_strings.join("\n"),
289 log_message
290 );
291 } else {
292 log_data.message = log_message;
293 }
294 }
295 Err(err) => {
296 warn!(
297 "[macos-unifiedlogs] Failed to get message string data for firehose non-activity log entry: {err:?}"
298 );
299 }
300 }
301
302 if firehose.firehose_non_activity.subsystem_value != 0 {
303 let results = catalog_data.catalog.get_subsystem(
304 firehose.firehose_non_activity.subsystem_value,
305 preamble.first_number_proc_id,
306 preamble.second_number_proc_id,
307 );
308 match results {
309 Ok((_, subsystem)) => {
310 log_data.subsystem = subsystem.subsystem;
311 log_data.category = subsystem.category;
312 }
313 Err(err) => {
314 warn!("[macos-unifiedlogs] Failed to get subsystem: {err:?}")
315 }
316 }
317 }
318 }
319 0x7 => {
320 log_data.event_type = EventType::Loss;
322 log_data.log_type = LogType::Loss;
323 }
324 0x2 => {
325 log_data.activity_id =
326 u64::from(firehose.firehose_activity.unknown_activity_id);
327 let message_data = FirehoseActivity::get_firehose_activity_strings(
328 &firehose.firehose_activity,
329 self.provider,
330 u64::from(firehose.format_string_location),
331 &preamble.first_number_proc_id,
332 &preamble.second_number_proc_id,
333 &catalog_data.catalog,
334 );
335 match message_data {
336 Ok((_, results)) => {
337 log_data.library = results.library;
338 log_data.library_uuid = results.library_uuid;
339 log_data.process = results.process;
340 log_data.process_uuid = results.process_uuid;
341 results.format_string.clone_into(&mut log_data.raw_message);
342
343 let log_message = format_firehose_log_message(
344 results.format_string,
345 &firehose.message.item_info,
346 &self.message_re,
347 );
348
349 if self.exclude_missing
350 && log_message.contains("<Missing message data>")
351 {
352 LogData::add_missing(
353 catalog_data,
354 preamble_index,
355 firehose_index,
356 &self.unified_log_data.header,
357 &mut missing_unified_log_data_vec,
358 preamble,
359 );
360 continue;
361 }
362 if !firehose.message.backtrace_strings.is_empty() {
363 log_data.message = format!(
364 "Backtrace:\n{}\n{}",
365 firehose.message.backtrace_strings.join("\n"),
366 log_message
367 );
368 } else {
369 log_data.message = log_message;
370 }
371 }
372 Err(err) => {
373 warn!(
374 "[macos-unifiedlogs] Failed to get message string data for firehose activity log entry: {err:?}"
375 );
376 }
377 }
378 }
379 0x6 => {
380 log_data.activity_id =
381 u64::from(firehose.firehose_signpost.unknown_activity_id);
382 let message_data = FirehoseSignpost::get_firehose_signpost(
383 &firehose.firehose_signpost,
384 self.provider,
385 u64::from(firehose.format_string_location),
386 &preamble.first_number_proc_id,
387 &preamble.second_number_proc_id,
388 &catalog_data.catalog,
389 );
390 match message_data {
391 Ok((_, results)) => {
392 log_data.library = results.library;
393 log_data.library_uuid = results.library_uuid;
394 log_data.process = results.process;
395 log_data.process_uuid = results.process_uuid;
396 results.format_string.clone_into(&mut log_data.raw_message);
397
398 let mut log_message =
399 if firehose.firehose_non_activity.data_ref_value != 0 {
400 let oversize_strings = Oversize::get_oversize_strings(
401 firehose.firehose_non_activity.data_ref_value,
402 preamble.first_number_proc_id,
403 preamble.second_number_proc_id,
404 &self.unified_log_data.oversize,
405 );
406 format_firehose_log_message(
408 results.format_string,
409 &oversize_strings,
410 &self.message_re,
411 )
412 } else {
413 format_firehose_log_message(
415 results.format_string,
416 &firehose.message.item_info,
417 &self.message_re,
418 )
419 };
420 if self.exclude_missing
421 && log_message.contains("<Missing message data>")
422 {
423 LogData::add_missing(
424 catalog_data,
425 preamble_index,
426 firehose_index,
427 &self.unified_log_data.header,
428 &mut missing_unified_log_data_vec,
429 preamble,
430 );
431 continue;
432 }
433
434 log_message = format!(
435 "Signpost ID: {:X} - Signpost Name: {:X}\n {log_message}",
436 firehose.firehose_signpost.signpost_id,
437 firehose.firehose_signpost.signpost_name,
438 );
439
440 if !firehose.message.backtrace_strings.is_empty() {
441 log_data.message = format!(
442 "Backtrace:\n{}\n{log_message}",
443 firehose.message.backtrace_strings.join("\n"),
444 );
445 } else {
446 log_data.message = log_message;
447 }
448 }
449 Err(err) => {
450 warn!(
451 "[macos-unifiedlogs] Failed to get message string data for firehose signpost log entry: {err:?}"
452 );
453 }
454 }
455 if firehose.firehose_signpost.subsystem != 0 {
456 let results = catalog_data.catalog.get_subsystem(
457 firehose.firehose_signpost.subsystem,
458 preamble.first_number_proc_id,
459 preamble.second_number_proc_id,
460 );
461 match results {
462 Ok((_, subsystem)) => {
463 log_data.subsystem = subsystem.subsystem;
464 log_data.category = subsystem.category;
465 }
466 Err(err) => {
467 warn!("[macos-unifiedlogs] Failed to get subsystem: {err:?}")
468 }
469 }
470 }
471 }
472 0x3 => {
473 let message_data = FirehoseTrace::get_firehose_trace_strings(
474 self.provider,
475 u64::from(firehose.format_string_location),
476 &preamble.first_number_proc_id,
477 &preamble.second_number_proc_id,
478 &catalog_data.catalog,
479 );
480 match message_data {
481 Ok((_, results)) => {
482 log_data.library = results.library;
483 log_data.library_uuid = results.library_uuid;
484 log_data.process = results.process;
485 log_data.process_uuid = results.process_uuid;
486
487 let log_message = format_firehose_log_message(
488 results.format_string,
489 &firehose.message.item_info,
490 &self.message_re,
491 );
492
493 if self.exclude_missing
494 && log_message.contains("<Missing message data>")
495 {
496 LogData::add_missing(
497 catalog_data,
498 preamble_index,
499 firehose_index,
500 &self.unified_log_data.header,
501 &mut missing_unified_log_data_vec,
502 preamble,
503 );
504 continue;
505 }
506 if !firehose.message.backtrace_strings.is_empty() {
507 log_data.message = format!(
508 "Backtrace:\n{}\n{log_message}",
509 firehose.message.backtrace_strings.join("\n"),
510 );
511 } else {
512 log_data.message = log_message;
513 }
514 }
515 Err(err) => {
516 warn!(
517 "[macos-unifiedlogs] Failed to get message string data for firehose activity log entry: {err:?}",
518 );
519 }
520 }
521 }
522 _ => {
523 error!("[macos-unifiedlogs] Parsed unknown log firehose data: {firehose:?}",)
524 }
525 }
526 log_data_vec.push(log_data);
527 }
528 }
529
530 for simpledump in &catalog_data.simpledump {
531 let no_firehose_preamble = 1;
532 let timestamp = TimesyncBoot::get_timestamp(
533 self.timesync_data,
534 &self.unified_log_data.header[0].boot_uuid,
535 simpledump.continous_time,
536 no_firehose_preamble,
537 );
538 let log_data = LogData {
539 subsystem: simpledump.subsystem.to_owned(),
540 thread_id: simpledump.thread_id,
541 pid: simpledump.first_proc_id,
542 library: String::new(),
543 activity_id: 0,
544 time: timestamp,
545 timestamp: unixepoch_to_iso(&(timestamp as i64)),
546 category: String::new(),
547 log_type: LogType::Simpledump,
548 process: String::new(),
549 message: simpledump.message_string.to_owned(),
550 event_type: EventType::Simpledump,
551 euid: 0,
552 boot_uuid: self.unified_log_data.header[0].boot_uuid.to_owned(),
553 timezone_name: self.unified_log_data.header[0]
554 .timezone_path
555 .split('/')
556 .next_back()
557 .unwrap_or("Unknown Timezone Name")
558 .to_string(),
559 library_uuid: simpledump.sender_uuid.to_owned(),
560 process_uuid: simpledump.dsc_uuid.to_owned(),
561 raw_message: String::new(),
562 message_entries: Vec::new(),
563 };
564 log_data_vec.push(log_data);
565 }
566
567 for statedump in &catalog_data.statedump {
568 let no_firehose_preamble = 1;
569
570 let data_string = match statedump.unknown_data_type {
571 0x1 => Statedump::parse_statedump_plist(&statedump.statedump_data),
572 0x2 => match extract_protobuf(&statedump.statedump_data) {
573 Ok(map) => serde_json::to_string(&map)
574 .unwrap_or(String::from("Failed to serialize Protobuf HashMap")),
575 Err(_err) => format!(
576 "Failed to parse StateDump protobuf: {}",
577 encode_standard(&statedump.statedump_data)
578 ),
579 },
580 0x3 => Statedump::parse_statedump_object(
581 &statedump.statedump_data,
582 &statedump.title_name,
583 ),
584 _ => {
585 warn!(
586 "Unknown statedump data type: {}",
587 statedump.unknown_data_type
588 );
589 let results = extract_string(&statedump.statedump_data);
590 match results {
591 Ok((_, string_data)) => string_data,
592 Err(err) => {
593 error!(
594 "[macos-unifiedlogs] Failed to extract string from statedump: {err:?}"
595 );
596 String::from("Failed to extract string from statedump")
597 }
598 }
599 }
600 };
601 let timestamp = TimesyncBoot::get_timestamp(
602 self.timesync_data,
603 &self.unified_log_data.header[0].boot_uuid,
604 statedump.continuous_time,
605 no_firehose_preamble,
606 );
607 let log_data = LogData {
608 subsystem: String::new(),
609 thread_id: 0,
610 pid: statedump.first_proc_id,
611 library: String::new(),
612 activity_id: statedump.activity_id,
613 time: timestamp,
614 timestamp: unixepoch_to_iso(&(timestamp as i64)),
615 category: String::new(),
616 event_type: EventType::Statedump,
617 process: String::new(),
618 message: format!(
619 "title: {}\nObject Type: {}\nObject Type: {}\n{data_string}",
620 statedump.title_name, statedump.decoder_library, statedump.decoder_type,
621 ),
622 log_type: LogType::Statedump,
623 euid: 0,
624 boot_uuid: self.unified_log_data.header[0].boot_uuid.to_owned(),
625 timezone_name: self.unified_log_data.header[0]
626 .timezone_path
627 .split('/')
628 .next_back()
629 .unwrap_or("Unknown Timezone Name")
630 .to_string(),
631 library_uuid: String::new(),
632 process_uuid: String::new(),
633 raw_message: String::new(),
634 message_entries: Vec::new(),
635 };
636 log_data_vec.push(log_data);
637 }
638
639 self.catalog_data_iterator_index += 1;
640 Some((log_data_vec, missing_unified_log_data_vec))
641 }
642}
643
644#[derive(Debug, Serialize)]
645pub struct LogData {
646 pub subsystem: String,
647 pub thread_id: u64,
648 pub pid: u64,
649 pub euid: u32,
650 pub library: String,
651 pub library_uuid: String,
652 pub activity_id: u64,
653 pub time: f64,
654 pub category: String,
655 pub event_type: EventType,
656 pub log_type: LogType,
657 pub process: String,
658 pub process_uuid: String,
659 pub message: String,
660 pub raw_message: String,
661 pub boot_uuid: String,
662 pub timezone_name: String,
663 pub message_entries: Vec<FirehoseItemInfo>,
664 pub timestamp: String,
665}
666
667impl LogData {
668 pub fn parse_unified_log(data: &[u8]) -> nom::IResult<&[u8], UnifiedLogData> {
670 let mut unified_log_data_true = UnifiedLogData {
671 header: Vec::new(),
672 catalog_data: Vec::new(),
673 oversize: Vec::new(),
674 };
675
676 let mut catalog_data = UnifiedLogCatalogData::default();
677
678 let mut input = data;
679 let chunk_preamble_size = 16; let header_chunk = 0x1000;
682 let catalog_chunk = 0x600b;
683 let chunkset_chunk = 0x600d;
684 while !input.is_empty() {
686 let (_, preamble) = LogPreamble::detect_preamble(input)?;
687 let chunk_size = preamble.chunk_data_size;
688
689 let chunk_size = match u64_to_usize(chunk_size) {
691 Some(c) => c,
692 None => {
693 error!("[macos-unifiedlogs] u64 is bigger than system usize");
694 return Err(nom::Err::Error(nom::error::Error::new(
695 data,
696 nom::error::ErrorKind::TooLarge,
697 )));
698 }
699 };
700 let (data, chunk_data) = take(chunk_size + chunk_preamble_size)(input)?;
701
702 if preamble.chunk_tag == header_chunk {
703 LogData::get_header_data(chunk_data, &mut unified_log_data_true);
704 } else if preamble.chunk_tag == catalog_chunk {
705 if catalog_data.catalog.chunk_tag != 0 {
706 unified_log_data_true.catalog_data.push(catalog_data);
707 }
708 catalog_data = UnifiedLogCatalogData::default();
709
710 LogData::get_catalog_data(chunk_data, &mut catalog_data);
711 } else if preamble.chunk_tag == chunkset_chunk {
712 LogData::get_chunkset_data(
713 chunk_data,
714 &mut catalog_data,
715 &mut unified_log_data_true,
716 );
717 } else {
718 error!(
719 "[macos-unifiedlogs] Unknown chunk type: {:?}",
720 preamble.chunk_tag
721 );
722 }
723
724 let padding_size = padding_size_8(preamble.chunk_data_size);
725 if data.len() < padding_size as usize {
726 break;
727 }
728 let padding_size = match u64_to_usize(padding_size) {
729 Some(p) => p,
730 None => {
731 error!("[macos-unifiedlogs] u64 is bigger than system usize");
732 return Err(nom::Err::Error(nom::error::Error::new(
733 data,
734 nom::error::ErrorKind::TooLarge,
735 )));
736 }
737 };
738
739 let (data, _) = take(padding_size)(data)?;
740 if data.is_empty() {
741 break;
742 }
743 input = data;
744 if input.len() < chunk_preamble_size {
745 warn!(
746 "Not enough data for preamble header, needed 16 bytes. Got: {:?}",
747 input.len()
748 );
749 break;
750 }
751 }
752 if catalog_data.catalog.chunk_tag != 0 {
754 unified_log_data_true.catalog_data.push(catalog_data);
755 }
756 Ok((input, unified_log_data_true))
757 }
758
759 pub fn build_log(
762 unified_log_data: &UnifiedLogData,
763 provider: &mut dyn FileProvider,
764 timesync_data: &HashMap<String, TimesyncBoot>,
765 exclude_missing: bool,
766 ) -> (Vec<LogData>, UnifiedLogData) {
767 let mut log_data_vec: Vec<LogData> = Vec::new();
768 let mut missing_unified_log_data_vec = UnifiedLogData {
770 header: Vec::new(),
771 catalog_data: Vec::new(),
772 oversize: Vec::new(),
773 };
774
775 let Ok(log_iterator) =
776 LogIterator::new(unified_log_data, provider, timesync_data, exclude_missing)
777 else {
778 return (log_data_vec, missing_unified_log_data_vec);
779 };
780 for (mut log_data, mut missing_unified_log) in log_iterator {
781 log_data_vec.append(&mut log_data);
782 missing_unified_log_data_vec
783 .header
784 .append(&mut missing_unified_log.header);
785 missing_unified_log_data_vec
786 .catalog_data
787 .append(&mut missing_unified_log.catalog_data);
788 missing_unified_log_data_vec
789 .oversize
790 .append(&mut missing_unified_log.oversize);
791 }
792
793 (log_data_vec, missing_unified_log_data_vec)
794 }
795
796 fn get_log_type(log_type: u8, activity_type: u8) -> LogType {
798 match log_type {
799 0x1 => {
800 let activity = 2;
801 if activity_type == activity {
802 LogType::Create
803 } else {
804 LogType::Info
805 }
806 }
807 0x2 => LogType::Debug,
808 0x3 => LogType::Useraction,
809 0x10 => LogType::Error,
810 0x11 => LogType::Fault,
811 0x80 => LogType::ProcessSignpostEvent,
812 0x81 => LogType::ProcessSignpostStart,
813 0x82 => LogType::ProcessSignpostEnd,
814 0xc0 => LogType::SystemSignpostEvent, 0xc1 => LogType::SystemSignpostStart,
816 0xc2 => LogType::SystemSignpostEnd,
817 0x40 => LogType::ThreadSignpostEvent, 0x41 => LogType::ThreadSignpostStart,
819 0x42 => LogType::ThreadSignpostEnd,
820 _ => LogType::Default,
821 }
822 }
823
824 fn get_event_type(event_type: u8) -> EventType {
826 match event_type {
827 0x4 => EventType::Log,
828 0x2 => EventType::Activity,
829 0x3 => EventType::Trace,
830 0x6 => EventType::Signpost,
831 0x7 => EventType::Loss,
832 _ => EventType::Unknown,
833 }
834 }
835
836 pub(crate) fn get_header_data(data: &[u8], unified_log_data: &mut UnifiedLogData) {
838 let header_results = HeaderChunk::parse_header(data);
839 match header_results {
840 Ok((_, header_data)) => unified_log_data.header.push(header_data),
841 Err(err) => error!("[macos-unifiedlogs] Failed to parse header data: {err:?}"),
842 }
843 }
844
845 pub(crate) fn get_catalog_data(data: &[u8], unified_log_data: &mut UnifiedLogCatalogData) {
847 let catalog_results = CatalogChunk::parse_catalog(data);
848 match catalog_results {
849 Ok((_, catalog_data)) => unified_log_data.catalog = catalog_data,
850 Err(err) => error!("[macos-unifiedlogs] Failed to parse catalog data: {err:?}"),
851 }
852 }
853
854 pub(crate) fn get_chunkset_data(
856 data: &[u8],
857 catalog_data: &mut UnifiedLogCatalogData,
858 unified_log_data: &mut UnifiedLogData,
859 ) {
860 let chunkset_data_results = ChunksetChunk::parse_chunkset(data);
862 match chunkset_data_results {
863 Ok((_, chunkset_data)) => {
864 let _result = ChunksetChunk::parse_chunkset_data(
866 &chunkset_data.decompressed_data,
867 catalog_data,
868 );
869 unified_log_data.oversize.append(&mut catalog_data.oversize);
870 }
871 Err(err) => error!("[macos-unifiedlogs] Failed to parse chunkset data: {err:?}"),
872 }
873 }
874
875 fn track_missing(
877 first_proc_id: u64,
878 second_proc_id: u32,
879 time: u64,
880 firehose: Firehose,
881 ) -> FirehosePreamble {
882 FirehosePreamble {
883 chunk_tag: 0,
884 chunk_sub_tag: 0,
885 chunk_data_size: 0,
886 first_number_proc_id: first_proc_id,
887 second_number_proc_id: second_proc_id,
888 collapsed: 0,
889 unknown: Vec::new(),
890 public_data_size: 0,
891 private_data_virtual_offset: 0,
892 unkonwn2: 0,
893 unknown3: 0,
894 base_continous_time: time,
895 public_data: vec![firehose],
896 ttl: 0,
897 }
898 }
899
900 fn add_missing(
902 catalog_data: &UnifiedLogCatalogData,
903 preamble_index: usize,
904 firehose_index: usize,
905 header: &[HeaderChunk],
906 missing_unified_log_data_vec: &mut UnifiedLogData,
907 preamble: &FirehosePreamble,
908 ) {
909 let missing_firehose = LogData::track_missing(
910 catalog_data.firehose[preamble_index].first_number_proc_id,
911 catalog_data.firehose[preamble_index].second_number_proc_id,
912 catalog_data.firehose[preamble_index].base_continous_time,
913 preamble.public_data[firehose_index].to_owned(),
914 );
915 let mut missing_unified_log_data = UnifiedLogCatalogData {
916 catalog: catalog_data.catalog.to_owned(),
917 firehose: Vec::new(),
918 simpledump: Vec::new(),
919 statedump: Vec::new(),
920 oversize: Vec::new(),
921 };
922
923 missing_unified_log_data.firehose.push(missing_firehose);
924 header.clone_into(&mut missing_unified_log_data_vec.header);
925
926 missing_unified_log_data_vec
927 .catalog_data
928 .push(missing_unified_log_data);
929 }
930}
931
932#[cfg(test)]
933mod tests {
934 use super::{LogData, UnifiedLogData};
935 use crate::{
936 chunks::firehose::firehose_log::Firehose,
937 filesystem::LogarchiveProvider,
938 parser::{collect_timesync, parse_log},
939 unified_log::{EventType, LogType, UnifiedLogCatalogData},
940 };
941 use std::{fs, path::PathBuf};
942
943 #[test]
944 fn test_parse_unified_log() {
945 let mut test_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
946 test_path.push(
947 "tests/test_data/system_logs_big_sur.logarchive/Persist/0000000000000002.tracev3",
948 );
949
950 let buffer = fs::read(test_path).unwrap();
951
952 let (_, results) = LogData::parse_unified_log(&buffer).unwrap();
953 assert_eq!(results.catalog_data.len(), 56);
954 assert_eq!(results.header.len(), 1);
955 assert_eq!(results.oversize.len(), 12);
956 }
957
958 #[test]
959 fn test_bad_log_header() {
960 let mut test_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
961 test_path.push("tests/test_data/Bad Data/TraceV3/Bad_header_0000000000000005.tracev3");
962
963 let buffer = fs::read(test_path).unwrap();
964 let (_, results) = LogData::parse_unified_log(&buffer).unwrap();
965 assert_eq!(results.catalog_data.len(), 36);
966 assert_eq!(results.header.len(), 0);
967 assert_eq!(results.oversize.len(), 28);
968 }
969
970 #[test]
971 #[cfg(target_pointer_width = "64")]
972 #[should_panic(expected = "Eof")]
973 fn test_bad_log_content_64() {
974 test_bad_log_content();
975 }
976
977 #[test]
978 #[cfg(target_pointer_width = "32")]
979 #[should_panic(expected = "TooLarge")]
980 fn test_bad_log_content_32() {
981 test_bad_log_content();
982 }
983
984 fn test_bad_log_content() {
985 let mut test_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
986 test_path.push("tests/test_data/Bad Data/TraceV3/Bad_content_0000000000000005.tracev3");
987
988 let buffer = fs::read(test_path).unwrap();
989 let (_, _) = LogData::parse_unified_log(&buffer).unwrap();
990 }
991
992 #[test]
993 #[cfg(target_pointer_width = "64")]
994 #[should_panic(expected = "Eof")]
995 fn test_bad_log_file_64() {
996 test_bad_log_file();
997 }
998
999 #[test]
1000 #[cfg(target_pointer_width = "32")]
1001 #[should_panic(expected = "TooLarge")]
1002 fn test_bad_log_file_32() {
1003 test_bad_log_file();
1004 }
1005
1006 fn test_bad_log_file() {
1007 let mut test_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1008 test_path.push("tests/test_data/Bad Data/TraceV3/00.tracev3");
1009
1010 let buffer = fs::read(test_path).unwrap();
1011 let (_, _) = LogData::parse_unified_log(&buffer).unwrap();
1012 }
1013
1014 #[test]
1015 fn test_build_log() {
1016 let mut test_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1017 test_path.push("tests/test_data/system_logs_big_sur.logarchive");
1018
1019 let mut provider = LogarchiveProvider::new(test_path.as_path());
1020 let timesync_data = collect_timesync(&provider).unwrap();
1021
1022 test_path.push("Persist/0000000000000002.tracev3");
1023
1024 let reader = std::fs::File::open(test_path).unwrap();
1025 let log_data = parse_log(reader).unwrap();
1026
1027 let exclude_missing = false;
1028 let (results, _) =
1029 LogData::build_log(&log_data, &mut provider, ×ync_data, exclude_missing);
1030
1031 assert_eq!(results.len(), 207366);
1032 assert_eq!(results[0].process, "/usr/libexec/lightsoutmanagementd");
1033 assert_eq!(results[0].subsystem, "com.apple.lom");
1034 assert_eq!(results[0].time, 1_642_302_326_434_850_800.0);
1035 assert_eq!(results[0].activity_id, 0);
1036 assert_eq!(results[0].library, "/usr/libexec/lightsoutmanagementd");
1037 assert_eq!(results[0].library_uuid, "6C3ADF991F033C1C96C4ADFAA12D8CED");
1038 assert_eq!(results[0].process_uuid, "6C3ADF991F033C1C96C4ADFAA12D8CED");
1039 assert_eq!(results[0].message, "LOMD Start");
1040 assert_eq!(results[0].pid, 45);
1041 assert_eq!(results[0].thread_id, 588);
1042 assert_eq!(results[0].category, "device");
1043 assert_eq!(results[0].log_type, LogType::Default);
1044 assert_eq!(results[0].event_type, EventType::Log);
1045 assert_eq!(results[0].euid, 0);
1046 assert_eq!(results[0].boot_uuid, "80D194AF56A34C54867449D2130D41BB");
1047 assert_eq!(results[0].timezone_name, "Pacific");
1048 assert_eq!(results[0].raw_message, "LOMD Start");
1049 assert_eq!(results[0].timestamp, "2022-01-16T03:05:26.434850816Z")
1050 }
1051
1052 #[test]
1053 fn test_get_log_type() {
1054 let mut log_type = 0x2;
1055 let activity_type = 0x2;
1056
1057 let mut log_string = LogData::get_log_type(log_type, activity_type);
1058 assert_eq!(log_string, LogType::Debug);
1059 log_type = 0x1;
1060 log_string = LogData::get_log_type(log_type, activity_type);
1061 assert_eq!(log_string, LogType::Create);
1062 }
1063
1064 #[test]
1065 fn test_get_event_type() {
1066 let event_type = 0x2;
1067 let event_string = LogData::get_event_type(event_type);
1068 assert_eq!(event_string, EventType::Activity);
1069 }
1070
1071 #[test]
1072 fn test_get_header_data() {
1073 let test_chunk_header = [
1074 0, 16, 0, 0, 17, 0, 0, 0, 208, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 15, 105,
1075 217, 162, 204, 126, 0, 0, 48, 215, 18, 98, 0, 0, 0, 0, 203, 138, 9, 0, 44, 1, 0, 0, 0,
1076 0, 0, 0, 1, 0, 0, 0, 0, 97, 0, 0, 8, 0, 0, 0, 6, 112, 124, 198, 169, 153, 1, 0, 1, 97,
1077 0, 0, 56, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 50, 49, 65, 53, 53, 57, 0, 0, 0, 0, 0, 0, 0,
1078 0, 0, 0, 77, 97, 99, 66, 111, 111, 107, 80, 114, 111, 49, 54, 44, 49, 0, 0, 0, 0, 0, 0,
1079 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 97, 0, 0, 24, 0, 0, 0, 195, 32, 184, 206, 151,
1080 250, 77, 165, 159, 49, 125, 57, 46, 56, 156, 234, 85, 0, 0, 0, 0, 0, 0, 0, 3, 97, 0, 0,
1081 48, 0, 0, 0, 47, 118, 97, 114, 47, 100, 98, 47, 116, 105, 109, 101, 122, 111, 110, 101,
1082 47, 122, 111, 110, 101, 105, 110, 102, 111, 47, 65, 109, 101, 114, 105, 99, 97, 47, 78,
1083 101, 119, 95, 89, 111, 114, 107, 0, 0, 0, 0, 0, 0,
1084 ];
1085 let mut data = UnifiedLogData {
1086 header: Vec::new(),
1087 catalog_data: Vec::new(),
1088 oversize: Vec::new(),
1089 };
1090
1091 LogData::get_header_data(&test_chunk_header, &mut data);
1092 assert_eq!(data.header.len(), 1);
1093 }
1094
1095 #[test]
1096 fn test_get_catalog_data() {
1097 let test_chunk_catalog = [
1098 11, 96, 0, 0, 17, 0, 0, 0, 208, 1, 0, 0, 0, 0, 0, 0, 32, 0, 96, 0, 1, 0, 160, 0, 7, 0,
1099 0, 0, 0, 0, 0, 0, 20, 165, 44, 35, 253, 233, 2, 0, 43, 239, 210, 12, 24, 236, 56, 56,
1100 129, 79, 43, 78, 90, 243, 188, 236, 61, 5, 132, 95, 63, 101, 53, 143, 158, 191, 34, 54,
1101 231, 114, 172, 1, 99, 111, 109, 46, 97, 112, 112, 108, 101, 46, 83, 107, 121, 76, 105,
1102 103, 104, 116, 0, 112, 101, 114, 102, 111, 114, 109, 97, 110, 99, 101, 95, 105, 110,
1103 115, 116, 114, 117, 109, 101, 110, 116, 97, 116, 105, 111, 110, 0, 116, 114, 97, 99,
1104 105, 110, 103, 46, 115, 116, 97, 108, 108, 115, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 158,
1105 0, 0, 0, 0, 0, 0, 0, 55, 1, 0, 0, 158, 0, 0, 0, 88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1106 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 87, 0, 0, 0, 19, 0, 78, 0, 0, 0, 47, 0, 0, 0, 0, 0,
1107 246, 113, 118, 43, 250, 233, 2, 0, 62, 195, 90, 26, 9, 234, 2, 0, 120, 255, 0, 0, 0, 1,
1108 0, 0, 1, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 19, 0, 47, 0, 48, 89, 60, 28, 9, 234, 2, 0,
1109 99, 50, 207, 40, 18, 234, 2, 0, 112, 240, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 3, 0, 0,
1110 0, 0, 0, 19, 0, 47, 0, 153, 6, 208, 41, 18, 234, 2, 0, 0, 214, 108, 78, 32, 234, 2, 0,
1111 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 19, 0, 47, 0, 128, 0, 87,
1112 79, 32, 234, 2, 0, 137, 5, 2, 205, 41, 234, 2, 0, 88, 255, 0, 0, 0, 1, 0, 0, 1, 0, 0,
1113 0, 0, 0, 3, 0, 0, 0, 0, 0, 19, 0, 47, 0, 185, 11, 2, 205, 41, 234, 2, 0, 172, 57, 107,
1114 20, 56, 234, 2, 0, 152, 255, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 19,
1115 0, 47, 0, 53, 172, 105, 21, 56, 234, 2, 0, 170, 167, 194, 43, 68, 234, 2, 0, 144, 255,
1116 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 19, 0, 47, 0, 220, 202, 171, 57,
1117 68, 234, 2, 0, 119, 171, 170, 119, 76, 234, 2, 0, 240, 254, 0, 0, 0, 1, 0, 0, 1, 0, 0,
1118 0, 0, 0, 3, 0, 0, 0, 0, 0, 19, 0, 47, 0,
1119 ];
1120 let mut data = UnifiedLogCatalogData::default();
1121
1122 LogData::get_catalog_data(&test_chunk_catalog, &mut data);
1123 assert_eq!(data.catalog.chunk_tag, 0x600b);
1124 assert_eq!(data.catalog.chunk_sub_tag, 17);
1125 assert_eq!(data.catalog.chunk_data_size, 464);
1126 assert_eq!(data.catalog.catalog_subsystem_strings_offset, 32);
1127 assert_eq!(data.catalog.catalog_process_info_entries_offset, 96);
1128 assert_eq!(data.catalog.number_process_information_entries, 1);
1129 assert_eq!(data.catalog.catalog_offset_sub_chunks, 160);
1130 assert_eq!(data.catalog.number_sub_chunks, 7);
1131 assert_eq!(data.catalog.unknown, [0, 0, 0, 0, 0, 0]);
1132 assert_eq!(data.catalog.earliest_firehose_timestamp, 820223379547412);
1133 assert_eq!(
1134 data.catalog.catalog_uuids,
1135 [
1136 "2BEFD20C18EC3838814F2B4E5AF3BCEC",
1137 "3D05845F3F65358F9EBF2236E772AC01"
1138 ]
1139 );
1140 assert_eq!(
1141 data.catalog.catalog_subsystem_strings,
1142 [
1143 99, 111, 109, 46, 97, 112, 112, 108, 101, 46, 83, 107, 121, 76, 105, 103, 104, 116,
1144 0, 112, 101, 114, 102, 111, 114, 109, 97, 110, 99, 101, 95, 105, 110, 115, 116,
1145 114, 117, 109, 101, 110, 116, 97, 116, 105, 111, 110, 0, 116, 114, 97, 99, 105,
1146 110, 103, 46, 115, 116, 97, 108, 108, 115, 0, 0, 0
1147 ]
1148 );
1149 assert_eq!(data.catalog.catalog_process_info_entries.len(), 1);
1150 assert_eq!(
1151 data.catalog
1152 .catalog_process_info_entries
1153 .get("158_311")
1154 .unwrap()
1155 .main_uuid,
1156 "2BEFD20C18EC3838814F2B4E5AF3BCEC"
1157 );
1158 assert_eq!(
1159 data.catalog
1160 .catalog_process_info_entries
1161 .get("158_311")
1162 .unwrap()
1163 .dsc_uuid,
1164 "3D05845F3F65358F9EBF2236E772AC01"
1165 );
1166
1167 assert_eq!(data.catalog.catalog_subchunks.len(), 7)
1168 }
1169
1170 #[test]
1171 fn test_get_chunkset_data() {
1172 let mut test_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1173 test_path.push("tests/test_data/Chunkset Tests/high_sierra_compressed_chunkset.raw");
1174
1175 let buffer = fs::read(test_path).unwrap();
1176
1177 let mut unified_log = UnifiedLogCatalogData::default();
1178
1179 let mut log_data = UnifiedLogData::default();
1180
1181 LogData::get_chunkset_data(&buffer, &mut unified_log, &mut log_data);
1182 assert_eq!(unified_log.catalog.chunk_tag, 0);
1183 assert_eq!(unified_log.firehose.len(), 21);
1184 assert_eq!(unified_log.statedump.len(), 0);
1185 assert_eq!(unified_log.simpledump.len(), 0);
1186 assert_eq!(unified_log.oversize.len(), 0);
1187
1188 assert_eq!(
1189 unified_log.firehose[0].public_data[0].message.item_info[0].message_strings,
1190 "483.700"
1191 );
1192 assert_eq!(unified_log.firehose[0].base_continous_time, 0);
1193 assert_eq!(unified_log.firehose[0].first_number_proc_id, 70);
1194 assert_eq!(unified_log.firehose[0].second_number_proc_id, 71);
1195 assert_eq!(unified_log.firehose[0].public_data_size, 4040);
1196 assert_eq!(unified_log.firehose[0].private_data_virtual_offset, 4096);
1197 }
1198
1199 #[test]
1200 fn test_track_missing() {
1201 let first_proc_id = 1;
1202 let second_proc_id = 2;
1203 let time = 11;
1204 let test_firehose = Firehose::default();
1205
1206 let missing_firehose =
1207 LogData::track_missing(first_proc_id, second_proc_id, time, test_firehose);
1208 assert_eq!(missing_firehose.first_number_proc_id, first_proc_id);
1209 assert_eq!(missing_firehose.second_number_proc_id, second_proc_id);
1210 assert_eq!(missing_firehose.base_continous_time, time);
1211 }
1212
1213 #[test]
1214 fn test_add_missing() {
1215 let mut missing_unified_log_data_vec = UnifiedLogData {
1216 header: Vec::new(),
1217 catalog_data: Vec::new(),
1218 oversize: Vec::new(),
1219 };
1220 let mut test_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1221 test_path.push(
1222 "tests/test_data/system_logs_big_sur.logarchive/Persist/0000000000000002.tracev3",
1223 );
1224 let reader = std::fs::File::open(test_path).unwrap();
1225
1226 let log_data = parse_log(reader).unwrap();
1227
1228 LogData::add_missing(
1229 &log_data.catalog_data[0],
1230 0,
1231 0,
1232 &log_data.header,
1233 &mut missing_unified_log_data_vec,
1234 &log_data.catalog_data[0].firehose[0],
1235 );
1236 assert_eq!(missing_unified_log_data_vec.header.len(), 1);
1237 assert_eq!(
1238 missing_unified_log_data_vec.header[0].boot_uuid,
1239 "80D194AF56A34C54867449D2130D41BB"
1240 );
1241 assert_eq!(missing_unified_log_data_vec.header[0].logd_pid, 42);
1242 assert_eq!(missing_unified_log_data_vec.catalog_data.len(), 1);
1243 assert_eq!(
1244 missing_unified_log_data_vec.catalog_data[0]
1245 .catalog
1246 .catalog_subsystem_strings_offset,
1247 848
1248 );
1249 assert_eq!(
1250 missing_unified_log_data_vec.catalog_data[0].firehose.len(),
1251 1
1252 );
1253 assert_eq!(
1254 missing_unified_log_data_vec.catalog_data[0].firehose[0].first_number_proc_id,
1255 45
1256 );
1257 assert_eq!(
1258 missing_unified_log_data_vec.catalog_data[0].firehose[0].second_number_proc_id,
1259 188
1260 );
1261 }
1262}