1use std::io::{BufReader, Cursor, Read};
15
16use flate2::read::GzDecoder;
17
18use crate::{
19 error::MuninError,
20 events::*,
21 header::{BinlogHeader, open_binlog},
22 nvl_table::{NameValueListTable, NameValuePair},
23 primitives::{read_7bit_count, read_7bit_int, read_7bit_length},
24 record_kind::BinaryLogRecordKind,
25 string_table::StringTable,
26};
27
28#[derive(Debug, Clone)]
38pub enum BinlogEvent {
39 BuildStarted(BuildStartedEvent),
40 BuildFinished(BuildFinishedEvent),
41 ProjectStarted(ProjectStartedEvent),
42 ProjectFinished(ProjectFinishedEvent),
43 TargetStarted(TargetStartedEvent),
44 TargetFinished(TargetFinishedEvent),
45 TargetSkipped(TargetSkippedEvent),
46 TaskStarted(TaskStartedEvent),
47 TaskFinished(TaskFinishedEvent),
48 TaskCommandLine(TaskCommandLineEvent),
49 TaskParameter(TaskParameterEvent),
50 Error(BuildErrorEvent),
51 Warning(BuildWarningEvent),
52 Message(BuildMessageEvent),
53 CriticalBuildMessage(CriticalBuildMessageEvent),
54 ProjectEvaluationStarted(ProjectEvaluationStartedEvent),
55 ProjectEvaluationFinished(ProjectEvaluationFinishedEvent),
56 PropertyReassignment(PropertyReassignmentEvent),
57 UninitializedPropertyRead(UninitializedPropertyReadEvent),
58 PropertyInitialValueSet(PropertyInitialValueSetEvent),
59 EnvironmentVariableRead(EnvironmentVariableReadEvent),
60 ResponseFileUsed(ResponseFileUsedEvent),
61 AssemblyLoad(AssemblyLoadEvent),
62 ProjectImported(ProjectImportedEvent),
63 BuildCheckMessage(BuildCheckMessageEvent),
64 BuildCheckWarning(BuildCheckWarningEvent),
65 BuildCheckError(BuildCheckErrorEvent),
66 BuildCheckTracing(BuildCheckTracingEvent),
67 BuildCheckAcquisition(BuildCheckAcquisitionEvent),
68 BuildSubmissionStarted(BuildSubmissionStartedEvent),
69 BuildCanceled(BuildCanceledEvent),
70}
71
72impl BinlogEvent {
73 pub fn record_kind(&self) -> BinaryLogRecordKind {
75 match self {
76 Self::BuildStarted(_) => BinaryLogRecordKind::BuildStarted,
77 Self::BuildFinished(_) => BinaryLogRecordKind::BuildFinished,
78 Self::ProjectStarted(_) => BinaryLogRecordKind::ProjectStarted,
79 Self::ProjectFinished(_) => BinaryLogRecordKind::ProjectFinished,
80 Self::TargetStarted(_) => BinaryLogRecordKind::TargetStarted,
81 Self::TargetFinished(_) => BinaryLogRecordKind::TargetFinished,
82 Self::TargetSkipped(_) => BinaryLogRecordKind::TargetSkipped,
83 Self::TaskStarted(_) => BinaryLogRecordKind::TaskStarted,
84 Self::TaskFinished(_) => BinaryLogRecordKind::TaskFinished,
85 Self::TaskCommandLine(_) => BinaryLogRecordKind::TaskCommandLine,
86 Self::TaskParameter(_) => BinaryLogRecordKind::TaskParameter,
87 Self::Error(_) => BinaryLogRecordKind::Error,
88 Self::Warning(_) => BinaryLogRecordKind::Warning,
89 Self::Message(_) => BinaryLogRecordKind::Message,
90 Self::CriticalBuildMessage(_) => BinaryLogRecordKind::CriticalBuildMessage,
91 Self::ProjectEvaluationStarted(_) => BinaryLogRecordKind::ProjectEvaluationStarted,
92 Self::ProjectEvaluationFinished(_) => BinaryLogRecordKind::ProjectEvaluationFinished,
93 Self::PropertyReassignment(_) => BinaryLogRecordKind::PropertyReassignment,
94 Self::UninitializedPropertyRead(_) => BinaryLogRecordKind::UninitializedPropertyRead,
95 Self::PropertyInitialValueSet(_) => BinaryLogRecordKind::PropertyInitialValueSet,
96 Self::EnvironmentVariableRead(_) => BinaryLogRecordKind::EnvironmentVariableRead,
97 Self::ResponseFileUsed(_) => BinaryLogRecordKind::ResponseFileUsed,
98 Self::AssemblyLoad(_) => BinaryLogRecordKind::AssemblyLoad,
99 Self::ProjectImported(_) => BinaryLogRecordKind::ProjectImported,
100 Self::BuildCheckMessage(_) => BinaryLogRecordKind::BuildCheckMessage,
101 Self::BuildCheckWarning(_) => BinaryLogRecordKind::BuildCheckWarning,
102 Self::BuildCheckError(_) => BinaryLogRecordKind::BuildCheckError,
103 Self::BuildCheckTracing(_) => BinaryLogRecordKind::BuildCheckTracing,
104 Self::BuildCheckAcquisition(_) => BinaryLogRecordKind::BuildCheckAcquisition,
105 Self::BuildSubmissionStarted(_) => BinaryLogRecordKind::BuildSubmissionStarted,
106 Self::BuildCanceled(_) => BinaryLogRecordKind::BuildCanceled,
107 }
108 }
109}
110
111#[derive(Debug)]
124pub struct BinlogReader<R: Read> {
125 reader: BufReader<GzDecoder<R>>,
126 header: BinlogHeader,
127 strings: StringTable,
128 nvl_table: NameValueListTable,
129 archives: Vec<Vec<u8>>,
131 finished: bool,
133}
134
135impl<R: Read> BinlogReader<R> {
136 pub fn open(reader: R) -> Result<Self, MuninError> {
141 let (header, gz_reader) = open_binlog(reader)?;
142 Ok(Self {
143 reader: gz_reader,
144 header,
145 strings: StringTable::new(),
146 nvl_table: NameValueListTable::new(),
147 archives: Vec::new(),
148 finished: false,
149 })
150 }
151
152 pub fn header(&self) -> &BinlogHeader {
154 &self.header
155 }
156
157 pub fn strings(&self) -> &StringTable {
159 &self.strings
160 }
161
162 pub fn nvl_table(&self) -> &NameValueListTable {
164 &self.nvl_table
165 }
166
167 pub fn archives(&self) -> &[Vec<u8>] {
169 &self.archives
170 }
171
172 pub fn extract_archives(&self) -> Result<Vec<ArchiveEntry>, MuninError> {
178 let mut entries = Vec::new();
179 for archive_bytes in &self.archives {
180 extract_zip_entries(archive_bytes, &mut entries)?;
181 }
182 Ok(entries)
183 }
184
185 pub fn next_event(&mut self) -> Result<Option<BinlogEvent>, MuninError> {
193 if self.finished {
194 return Ok(None);
195 }
196
197 let version = self.header.file_format_version;
198
199 loop {
200 let kind_raw = read_7bit_int(&mut self.reader)?;
202
203 if kind_raw == BinaryLogRecordKind::EndOfFile as i32 {
204 self.finished = true;
205 return Ok(None);
206 }
207
208 let record_length = read_7bit_length(&mut self.reader, "record length")?;
211
212 let mut payload = vec![0u8; record_length];
216 self.reader.read_exact(&mut payload)?;
217
218 let kind = BinaryLogRecordKind::from_raw(kind_raw);
219
220 match kind {
221 Some(BinaryLogRecordKind::String) => {
225 let s = String::from_utf8(payload).map_err(|_| MuninError::InvalidUtf8)?;
226 self.strings.add(s);
227 }
228
229 Some(BinaryLogRecordKind::NameValueList) => {
231 let mut cursor = Cursor::new(&payload);
232 let count = read_7bit_count(&mut cursor, "name-value list count")?;
233 let mut pairs = Vec::with_capacity(count);
234 for _ in 0..count {
235 let key_index = read_7bit_int(&mut cursor)?;
236 let value_index = read_7bit_int(&mut cursor)?;
237 pairs.push(NameValuePair {
238 key_index,
239 value_index,
240 });
241 }
242 self.nvl_table.add(pairs);
243 }
244
245 Some(BinaryLogRecordKind::ProjectImportArchive) => {
247 self.archives.push(payload);
248 }
249
250 Some(record_kind) => {
252 let mut cursor = Cursor::new(&payload);
253 let event = dispatch_event(
254 &mut cursor,
255 record_kind,
256 &self.strings,
257 &self.nvl_table,
258 version,
259 )?;
260 return Ok(Some(event));
261 }
262
263 None => {
265 }
267 }
268 }
269 }
270
271 pub fn read_all_events(&mut self) -> Result<Vec<BinlogEvent>, MuninError> {
276 let mut events = Vec::new();
277 while let Some(event) = self.next_event()? {
278 events.push(event);
279 }
280 Ok(events)
281 }
282}
283
284pub(crate) fn dispatch_event(
293 reader: &mut impl Read,
294 kind: BinaryLogRecordKind,
295 strings: &StringTable,
296 nvl_table: &NameValueListTable,
297 version: i32,
298) -> Result<BinlogEvent, MuninError> {
299 match kind {
300 BinaryLogRecordKind::BuildStarted => {
301 let e = read_build_started(reader, strings, nvl_table, version)?;
302 Ok(BinlogEvent::BuildStarted(e))
303 }
304 BinaryLogRecordKind::BuildFinished => {
305 let e = read_build_finished(reader, strings, version)?;
306 Ok(BinlogEvent::BuildFinished(e))
307 }
308 BinaryLogRecordKind::ProjectStarted => {
309 let e = read_project_started(reader, strings, nvl_table, version)?;
310 Ok(BinlogEvent::ProjectStarted(e))
311 }
312 BinaryLogRecordKind::ProjectFinished => {
313 let e = read_project_finished(reader, strings, version)?;
314 Ok(BinlogEvent::ProjectFinished(e))
315 }
316 BinaryLogRecordKind::TargetStarted => {
317 let e = read_target_started(reader, strings, version)?;
318 Ok(BinlogEvent::TargetStarted(e))
319 }
320 BinaryLogRecordKind::TargetFinished => {
321 let e = read_target_finished(reader, strings, nvl_table, version)?;
322 Ok(BinlogEvent::TargetFinished(e))
323 }
324 BinaryLogRecordKind::TargetSkipped => {
325 let e = read_target_skipped(reader, strings, version)?;
326 Ok(BinlogEvent::TargetSkipped(e))
327 }
328 BinaryLogRecordKind::TaskStarted => {
329 let e = read_task_started(reader, strings, version)?;
330 Ok(BinlogEvent::TaskStarted(e))
331 }
332 BinaryLogRecordKind::TaskFinished => {
333 let e = read_task_finished(reader, strings, version)?;
334 Ok(BinlogEvent::TaskFinished(e))
335 }
336 BinaryLogRecordKind::TaskCommandLine => {
337 let e = read_task_command_line(reader, strings, version)?;
338 Ok(BinlogEvent::TaskCommandLine(e))
339 }
340 BinaryLogRecordKind::TaskParameter => {
341 let e = read_task_parameter(reader, strings, nvl_table, version)?;
342 Ok(BinlogEvent::TaskParameter(e))
343 }
344 BinaryLogRecordKind::Error => {
345 let e = read_build_error(reader, strings, version)?;
346 Ok(BinlogEvent::Error(e))
347 }
348 BinaryLogRecordKind::Warning => {
349 let e = read_build_warning(reader, strings, version)?;
350 Ok(BinlogEvent::Warning(e))
351 }
352 BinaryLogRecordKind::Message => {
353 let e = read_build_message(reader, strings, version)?;
354 Ok(BinlogEvent::Message(e))
355 }
356 BinaryLogRecordKind::CriticalBuildMessage => {
357 let e = read_critical_build_message(reader, strings, version)?;
358 Ok(BinlogEvent::CriticalBuildMessage(e))
359 }
360 BinaryLogRecordKind::ProjectEvaluationStarted => {
361 let e = read_project_evaluation_started(reader, strings, version)?;
362 Ok(BinlogEvent::ProjectEvaluationStarted(e))
363 }
364 BinaryLogRecordKind::ProjectEvaluationFinished => {
365 let e = read_project_evaluation_finished(reader, strings, nvl_table, version)?;
366 Ok(BinlogEvent::ProjectEvaluationFinished(e))
367 }
368 BinaryLogRecordKind::PropertyReassignment => {
369 let e = read_property_reassignment(reader, strings, version)?;
370 Ok(BinlogEvent::PropertyReassignment(e))
371 }
372 BinaryLogRecordKind::UninitializedPropertyRead => {
373 let e = read_uninitialized_property_read(reader, strings, version)?;
374 Ok(BinlogEvent::UninitializedPropertyRead(e))
375 }
376 BinaryLogRecordKind::PropertyInitialValueSet => {
377 let e = read_property_initial_value_set(reader, strings, version)?;
378 Ok(BinlogEvent::PropertyInitialValueSet(e))
379 }
380 BinaryLogRecordKind::EnvironmentVariableRead => {
381 let e = read_environment_variable_read(reader, strings, version)?;
382 Ok(BinlogEvent::EnvironmentVariableRead(e))
383 }
384 BinaryLogRecordKind::ResponseFileUsed => {
385 let e = read_response_file_used(reader, strings, version)?;
386 Ok(BinlogEvent::ResponseFileUsed(e))
387 }
388 BinaryLogRecordKind::AssemblyLoad => {
389 let e = read_assembly_load(reader, strings, version)?;
390 Ok(BinlogEvent::AssemblyLoad(e))
391 }
392 BinaryLogRecordKind::ProjectImported => {
393 let e = read_project_imported(reader, strings, version)?;
394 Ok(BinlogEvent::ProjectImported(e))
395 }
396 BinaryLogRecordKind::BuildCheckMessage => {
397 let e = read_build_message(reader, strings, version)?;
398 Ok(BinlogEvent::BuildCheckMessage(e))
399 }
400 BinaryLogRecordKind::BuildCheckWarning => {
401 let e = read_build_warning(reader, strings, version)?;
402 Ok(BinlogEvent::BuildCheckWarning(e))
403 }
404 BinaryLogRecordKind::BuildCheckError => {
405 let e = read_build_error(reader, strings, version)?;
406 Ok(BinlogEvent::BuildCheckError(e))
407 }
408 BinaryLogRecordKind::BuildCheckTracing => {
409 let e = read_build_check_tracing(reader, strings, nvl_table, version)?;
410 Ok(BinlogEvent::BuildCheckTracing(e))
411 }
412 BinaryLogRecordKind::BuildCheckAcquisition => {
413 let e = read_build_check_acquisition(reader, strings, version)?;
414 Ok(BinlogEvent::BuildCheckAcquisition(e))
415 }
416 BinaryLogRecordKind::BuildSubmissionStarted => {
417 let e = read_build_submission_started(reader, strings, nvl_table, version)?;
418 Ok(BinlogEvent::BuildSubmissionStarted(e))
419 }
420 BinaryLogRecordKind::BuildCanceled => {
421 let e = read_build_canceled(reader, strings, version)?;
422 Ok(BinlogEvent::BuildCanceled(e))
423 }
424
425 BinaryLogRecordKind::EndOfFile
427 | BinaryLogRecordKind::String
428 | BinaryLogRecordKind::NameValueList
429 | BinaryLogRecordKind::ProjectImportArchive => {
430 unreachable!("auxiliary records handled before dispatch")
431 }
432 }
433}
434
435#[derive(Debug, Clone)]
441pub struct ArchiveEntry {
442 pub path: String,
444 pub contents: String,
446}
447
448fn extract_zip_entries(
450 archive_bytes: &[u8],
451 out: &mut Vec<ArchiveEntry>,
452) -> Result<(), MuninError> {
453 let cursor = Cursor::new(archive_bytes);
454 let mut archive = zip::ZipArchive::new(cursor)
455 .map_err(|e| MuninError::InvalidFormat(format!("invalid ProjectImportArchive zip: {e}")))?;
456
457 for i in 0..archive.len() {
458 let mut file = archive
459 .by_index(i)
460 .map_err(|e| MuninError::InvalidFormat(format!("cannot read zip entry {i}: {e}")))?;
461
462 if file.is_dir() {
464 continue;
465 }
466
467 let path = file.name().to_string();
468 let mut contents = String::new();
469 match file.read_to_string(&mut contents) {
471 Ok(_) => {
472 out.push(ArchiveEntry { path, contents });
473 }
474 Err(_) => {
475 }
477 }
478 }
479 Ok(())
480}
481
482#[cfg(test)]
483mod tests;