macos_unifiedlogs/
iterator.rs1use crate::{
2 header::HeaderChunk,
3 preamble::LogPreamble,
4 unified_log::{LogData, UnifiedLogCatalogData, UnifiedLogData},
5 util::padding_size_8,
6};
7use log::{error, warn};
8use nom::bytes::complete::take;
9
10#[derive(Debug, Clone)]
11pub struct UnifiedLogIterator {
13 pub data: Vec<u8>,
14 pub header: Vec<HeaderChunk>,
15}
16
17impl Iterator for UnifiedLogIterator {
18 type Item = UnifiedLogData;
19 fn next(&mut self) -> Option<Self::Item> {
20 if self.data.is_empty() {
21 return None;
22 }
23 let mut unified_log_data_true = UnifiedLogData {
24 header: self.header.clone(),
25 catalog_data: Vec::new(),
26 oversize: Vec::new(),
27 };
28
29 let mut catalog_data = UnifiedLogCatalogData::default();
30
31 let mut input = self.data.as_slice();
32 let chunk_preamble_size = 16; let header_chunk = 0x1000;
35 let catalog_chunk = 0x600b;
36 let chunkset_chunk = 0x600d;
37
38 loop {
39 let preamble_result = LogPreamble::detect_preamble(input);
40 let preamble = match preamble_result {
41 Ok((_, result)) => result,
42 Err(_err) => {
43 error!("Failed to determine preamble chunk");
44 return None;
45 }
46 };
47 let chunk_size = preamble.chunk_data_size;
48
49 let chunk_result = nom_bytes(input, &(chunk_size + chunk_preamble_size));
51
52 let (data, chunk_data) = match chunk_result {
53 Ok(result) => result,
54 Err(_err) => {
55 error!("Failed to nom chunk bytes");
56 return None;
57 }
58 };
59
60 if preamble.chunk_tag == header_chunk {
61 LogData::get_header_data(chunk_data, &mut unified_log_data_true);
62 } else if preamble.chunk_tag == catalog_chunk {
63 if catalog_data.catalog.chunk_tag != 0 {
64 self.data = input.to_vec();
65 break;
66 }
67
68 LogData::get_catalog_data(chunk_data, &mut catalog_data);
69 } else if preamble.chunk_tag == chunkset_chunk {
70 LogData::get_chunkset_data(
71 chunk_data,
72 &mut catalog_data,
73 &mut unified_log_data_true,
74 );
75 } else {
76 error!(
77 "[macos-unifiedlogs] Unknown chunk type: {}",
78 preamble.chunk_tag
79 );
80 }
81
82 let padding_size = padding_size_8(preamble.chunk_data_size);
83 if self.data.len() < padding_size as usize {
84 self.data = Vec::new();
85 break;
86 }
87 let data_result = nom_bytes(data, &padding_size);
88 let data = match data_result {
89 Ok((result, _)) => result,
90 Err(_err) => {
91 error!("Failed to nom log end padding");
92 return None;
93 }
94 };
95 if data.is_empty() {
96 self.data = Vec::new();
97 break;
98 }
99 input = data;
100 if input.len() < chunk_preamble_size as usize {
101 warn!(
102 "Not enough data for preamble header, needed 16 bytes. Got: {}",
103 input.len()
104 );
105 self.data = Vec::new();
106 break;
107 }
108 }
109
110 if catalog_data.catalog.chunk_tag != 0 {
112 unified_log_data_true.catalog_data.push(catalog_data);
113 }
114 self.header = unified_log_data_true.header.clone();
115 Some(unified_log_data_true)
116 }
117}
118
119fn nom_bytes<'a>(data: &'a [u8], size: &u64) -> nom::IResult<&'a [u8], &'a [u8]> {
121 let size = match usize::try_from(*size).ok() {
122 Some(s) => s,
123 None => {
124 error!("[macos-unifiedlogs] u64 is bigger than system usize");
125 return Err(nom::Err::Error(nom::error::Error::new(
126 data,
127 nom::error::ErrorKind::TooLarge,
128 )));
129 }
130 };
131 take(size)(data)
132}
133
134#[cfg(test)]
135mod tests {
136 use super::UnifiedLogIterator;
137 use crate::{
138 filesystem::LogarchiveProvider,
139 iterator::nom_bytes,
140 parser::{build_log, collect_timesync},
141 unified_log::{EventType, LogType},
142 };
143 use std::{fs, path::PathBuf};
144
145 #[test]
146 fn test_unified_log_iterator() {
147 let mut test_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
148 test_path.push("tests/test_data/system_logs_big_sur.logarchive");
149
150 test_path.push("Persist/0000000000000002.tracev3");
151 let buffer_results = fs::read(test_path.to_str().unwrap()).unwrap();
152
153 let log_iterator = UnifiedLogIterator {
154 data: buffer_results,
155 header: Vec::new(),
156 };
157
158 let mut total = 0;
159 for chunk in log_iterator {
160 if chunk.catalog_data[0].firehose.len() == 99 {
161 assert_eq!(chunk.catalog_data[0].firehose.len(), 99);
162 assert_eq!(chunk.catalog_data[0].simpledump.len(), 0);
163 assert_eq!(chunk.header.len(), 1);
164 assert!(
165 chunk.catalog_data[0]
166 .catalog
167 .catalog_process_info_entries
168 .len()
169 > 40
170 );
171 assert_eq!(chunk.catalog_data[0].statedump.len(), 0);
172 }
173
174 total += chunk.catalog_data.len();
175 }
176
177 assert_eq!(total, 56);
178 }
179
180 #[test]
181 fn test_unified_log_iterator_build_log() {
182 let mut test_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
183 test_path.push("tests/test_data/system_logs_big_sur.logarchive");
184
185 let mut provider = LogarchiveProvider::new(test_path.as_path());
186 let timesync_data = collect_timesync(&provider).unwrap();
187
188 test_path.push("Persist/0000000000000002.tracev3");
189 let buffer_results = fs::read(test_path.to_str().unwrap()).unwrap();
190
191 let log_iterator = UnifiedLogIterator {
192 data: buffer_results,
193 header: Vec::new(),
194 };
195
196 let mut total = 0;
197 for chunk in log_iterator {
198 let exclude_missing = false;
199 let (results, _) = build_log(&chunk, &mut provider, ×ync_data, exclude_missing);
200
201 if results[10].time == 1642302327364384800.0 {
202 assert_eq!(results.len(), 3805);
203 assert_eq!(results[10].process, "/usr/libexec/lightsoutmanagementd");
204 assert_eq!(results[10].subsystem, "com.apple.lom");
205 assert_eq!(results[10].time, 1642302327364384800.0);
206 assert_eq!(results[10].activity_id, 0);
207 assert_eq!(
208 results[10].library,
209 "/System/Library/PrivateFrameworks/AppleLOM.framework/Versions/A/AppleLOM"
210 );
211 assert_eq!(results[10].message, "<private> LOM isSupported : No");
212 assert_eq!(results[10].pid, 45);
213 assert_eq!(results[10].thread_id, 588);
214 assert_eq!(results[10].category, "device");
215 assert_eq!(results[10].log_type, LogType::Default);
216 assert_eq!(results[10].event_type, EventType::Log);
217 assert_eq!(results[10].euid, 0);
218 assert_eq!(results[10].boot_uuid, "80D194AF56A34C54867449D2130D41BB");
219 assert_eq!(results[10].timezone_name, "Pacific");
220 assert_eq!(results[10].library_uuid, "D8E5AF1CAF4F3CEB8731E6F240E8EA7D");
221 assert_eq!(results[10].process_uuid, "6C3ADF991F033C1C96C4ADFAA12D8CED");
222 assert_eq!(results[10].raw_message, "%@ LOM isSupported : %s");
223 }
224
225 total += results.len();
226 }
227
228 assert_eq!(total, 207366);
229 }
230
231 #[test]
232 fn test_nom_bytes() {
233 let test = [1, 0, 0, 0];
234 let (left, _) = nom_bytes(&test, &1).unwrap();
235 assert_eq!(left.len(), 3);
236 }
237}