Repository: libyal/dtformats (language: python)

Issue:
Expand the tracev3 parser for Apple's Unified Logging
I wish to expand the tracev3 parser under unified_logging.py


Discussion / hints:

Fix patch:
diff --git a/documentation/Apple Unified Logging and Activity Tracing formats.asciidoc b/documentation/Apple Unified Logging and Activity Tracing formats.asciidoc
index 845f059..8d39ed6 100644
--- a/documentation/Apple Unified Logging and Activity Tracing formats.asciidoc
+++ b/documentation/Apple Unified Logging and Activity Tracing formats.asciidoc
@@ -218,7 +218,7 @@ The Header chunk is 208 bytes of size and consists of:
 | 32 | 8 | | [yellow-background]*Unknown time (related to starttime?)* +
 Contains number of seconds since January 1, 1970 00:00:00 UTC (POSIX epoch), disregarding leap seconds where the upper 4 bytes should be 0
 | 40 | 4 | | [yellow-background]*Unknown*
-| 44 | 4 | | Bias in minutes
+| 44 | 4 | | Timezone offset in minutes
 | 48 | 4 | | Daylight savings active +
 0 = daylight savings is not active (no-DST) +
 1 = daylight savings is active (DST)
diff --git a/dtformats/unified_logging.py b/dtformats/unified_logging.py
index a33213e..d357538 100644
--- a/dtformats/unified_logging.py
+++ b/dtformats/unified_logging.py
@@ -347,6 +347,31 @@ class TraceV3File(data_format.BinaryDataFile):
        '_FormatIntegerAsDecimal'),
       ('data_size', 'Data size', '_FormatIntegerAsDecimal')]
 
+  _DEBUG_INFO_HEADER_CHUNK = [
+      ('timebase_numerator', 'Timebase numerator', '_FormatIntegerAsDecimal'),
+      ('timebase_denominator', 'Timebase denominator',
+       '_FormatIntegerAsDecimal'),
+      ('continuous_time', 'Continuous Time', '_FormatIntegerAsDecimal'),
+      ('time_zone_offset', 'Timezone_offset', '_FormatIntegerAsDecimal'),
+      ('daylight_savings_flag', 'Daylight Savings Flag',
+       '_FormatIntegerAsDecimal'),
+      ('subchunk_continuous_tag', 'Subchunk Continuous Tag',
+       '_FormatIntegerAsHexadecimal4'),
+      ('continuous_time2', 'Continuous Time2', '_FormatIntegerAsDecimal'),
+      ('subchunk_systeminfo_tag', 'Subchunk Systeminfo Tag',
+       '_FormatIntegerAsHexadecimal4'),
+      ('build_identifier', 'Build Identifier', '_FormatString'),
+      ('hardware_identifier', 'Hardware Identifier', '_FormatString'),
+      ('subchunk_generation_tag', 'Subchunk Generation Tag',
+       '_FormatIntegerAsHexadecimal4'),
+      ('boot_identifier', 'Boot Identifier', '_FormatUUIDAsString'),
+      ('logd_process_identifier', 'Logd Process Identifier',
+       '_FormatIntegerAsDecimal'),
+      ('logd_exit_code', 'Logd Exit Code', '_FormatIntegerAsDecimal'),
+      ('subchunk_timezone_tag', 'Subchunk Timezone Tag',
+       '_FormatIntegerAsHexadecimal4'),
+      ('timezone_path', 'Timezone Path', '_FormatString')]
+
   _DEBUG_INFO_LZ4_BLOCK_HEADER = [
       ('signature', 'Signature', '_FormatStreamAsSignature'),
       ('uncompressed_data_size', 'Uncompressed data size',
@@ -585,6 +610,32 @@ def _ReadFirehoseTracepointData(self, tracepoint_data, data_offset):
 
     return firehose_tracepoint
 
+  def _ReadHeaderChunk(self, file_object, file_offset):
+    """Reads a Header Chunk.
+
+    Args:
+       file_object (file): file-like object.
+       file_offset (int): offset of the chunk relative to the start of the file.
+
+    Raises:
+      ParseError: if the chunk cannot be read.
+
+    Returns:
+      header_chunk: a header chunk.
+
+    """
+
+    data_type_map = self._GetDataTypeMap('tracev3_header_chunk')
+
+    header_chunk, _ = self._ReadStructureFromFileObject(
+        file_object, file_offset, data_type_map, 'header chunk')
+
+    if self._debug:
+      self._DebugPrintStructureObject(
+          header_chunk, self._DEBUG_INFO_HEADER_CHUNK)
+
+    return header_chunk
+
   def ReadFileObject(self, file_object):
     """Reads a timezone information file-like object.
 
@@ -600,7 +651,10 @@ def ReadFileObject(self, file_object):
       chunk_header = self._ReadChunkHeader(file_object, file_offset)
       file_offset += 16
 
-      if chunk_header.chunk_tag == 0x600b:
+      if chunk_header.chunk_tag == 0x1000:
+        self._ReadHeaderChunk(file_object, file_offset)
+
+      elif chunk_header.chunk_tag == 0x600b:
         self._ReadCatalog(file_object, file_offset, chunk_header)
 
       elif chunk_header.chunk_tag == 0x600d:
diff --git a/dtformats/unified_logging.yaml b/dtformats/unified_logging.yaml
index fc5d16b..1eb8650 100644
--- a/dtformats/unified_logging.yaml
+++ b/dtformats/unified_logging.yaml
@@ -152,6 +152,72 @@ members:
 - name: unknown1
   data_type: uint32
 ---
+name: tracev3_header_chunk
+type: structure
+description: TraceV3 header chunk.
+attributes:
+  byte_order: little-endian
+members:
+- name: timebase_numerator
+  data_type: uint32
+- name: timebase_denominator
+  data_type: uint32
+- name: continuous_time
+  data_type: uint64
+- name: unknown_time
+  data_type: uint64
+- name: unknown1
+  data_type: uint32
+- name: timezone_offset
+  data_type: uint32
+- name: daylight_savings_flag
+  data_type: uint32
+- name: unknown_flags
+  data_type: uint32
+- name: subchunk_continuous_tag
+  data_type: uint32
+- name: subchunk_continuous_tag_size
+  data_type: uint32
+- name: continuous_time2
+  data_type: uint64
+- name: subchunk_systeminfo_tag
+  data_type: uint32
+- name: subchunk_systeminfo_size
+  data_type: uint32
+- name: unknown2
+  data_type: uint32
+- name: unknown3
+  data_type: uint32
+- name: build_identifier
+  type: string
+  encoding: ascii
+  element_data_type: byte
+  elements_data_size: 16
+- name: hardware_identifier
+  type: string
+  encoding: ascii
+  element_data_type: byte
+  elements_data_size: 32
+- name: subchunk_generation_tag
+  data_type: uint32
+- name: subchunk_generation_size
+  data_type: uint32
+- name: boot_identifier
+  data_type: uuid_be
+- name: logd_process_identifier
+  data_type: uint32
+- name: logd_exit_code
+  data_type: uint32
+- name: subchunk_timezone_tag
+  data_type: uint32
+- name: subchunk_timezone_size
+  data_type: uint32
+- name: timezone_path
+  type: string
+  encoding: ascii
+  element_data_type: byte
+  elements_data_size: 48
+---
 name: tracev3_lz4_block_header
 type: structure
 description: TraceV3 LZ4 block header.


Test patch:
diff --git a/test_data/0000000000000f85.tracev3 b/test_data/0000000000000f85.tracev3
new file mode 100755
index 0000000..bcb4f07
Binary files /dev/null and b/test_data/0000000000000f85.tracev3 differ
diff --git a/tests/unified_logging.py b/tests/unified_logging.py
index 0831022..351906f 100644
--- a/tests/unified_logging.py
+++ b/tests/unified_logging.py
@@ -145,6 +145,33 @@ def testReadChunkHeader(self):
   # TODO: add tests for _ReadCatalog
   # TODO: add tests for _ReadChunkSet
 
+  def testReadHeaderChunk(self):
+    """Tests the _ReadHeaderChunk function."""
+    output_writer = test_lib.TestOutputWriter()
+    test_file = unified_logging.TraceV3File(output_writer=output_writer)
+
+    test_file_path = self._GetTestFilePath(['0000000000000f85.tracev3'])
+    self._SkipIfPathNotExists(test_file_path)
+
+    with open(test_file_path, 'rb') as file_object:
+      header_chunk = test_file._ReadHeaderChunk(file_object, 16)
+
+    self.assertEqual(header_chunk.timebase_numerator, 125)
+    self.assertEqual(header_chunk.timebase_denominator, 3)
+    self.assertEqual(header_chunk.timezone_offset, 300)
+    self.assertEqual(header_chunk.subchunk_continuous_tag, 0x6100)
+    self.assertEqual(header_chunk.subchunk_systeminfo_tag, 0x6101)
+    self.assertEqual(header_chunk.build_identifier.rstrip('\x00'), '19D52')
+    self.assertEqual(header_chunk.hardware_identifier.rstrip('\00'), 'J96AP')
+    self.assertEqual(header_chunk.subchunk_generation_tag, 0x6102)
+    self.assertEqual(
+        str(header_chunk.boot_identifier),
+        'a6ebc8e3-0a1c-40e8-93b9-da3a7f671d19')
+    self.assertEqual(header_chunk.subchunk_timezone_tag, 0x6103)
+    self.assertEqual(
+        header_chunk.timezone_path.rstrip('\x00'),
+        '/var/db/timezone/zoneinfo/America/Toronto')
+
   def testReadFileObject(self):
     """Tests the ReadFileObject function."""
     output_writer = test_lib.TestOutputWriter()