Source code for winregrc.data_format
# -*- coding: utf-8 -*-
"""Binary data format."""
import os
from dfdatetime import filetime as dfdatetime_filetime
from dtfabric import errors as dtfabric_errors
from dtfabric.runtime import fabric as dtfabric_fabric
from winregrc import errors
[docs]
class BinaryDataFormat(object):
"""Binary data format."""
# The dtFabric definition file, which must be overwritten by a subclass.
_DEFINITION_FILE = None
# Preserve the absolute path value of __file__ in case it is changed
# at run-time.
_DEFINITION_FILES_PATH = os.path.dirname(__file__)
_HEXDUMP_CHARACTER_MAP = [
'.' if byte < 0x20 or byte > 0x7e else chr(byte) for byte in range(256)]
[docs]
def __init__(self, debug=False, output_writer=None):
"""Initializes a binary data format.
Args:
debug (Optional[bool]): True if debug information should be written.
output_writer (Optional[OutputWriter]): output writer.
"""
super(BinaryDataFormat, self).__init__()
self._data_type_maps = {}
self._debug = debug
self._fabric = self._ReadDefinitionFile(self._DEFINITION_FILE)
self._output_writer = output_writer
def _DebugPrintData(self, description, data):
"""Prints data for debugging.
Args:
description (str): description.
data (bytes): data.
"""
if self._output_writer:
self._output_writer.WriteText(f'{description:s}:\n')
value_string = self._FormatDataInHexadecimal(data)
self._output_writer.WriteText(value_string)
def _DebugPrintDecimalValue(self, description, value):
"""Prints a decimal value for debugging.
Args:
description (str): description.
value (int): value.
"""
self._DebugPrintValue(description, f'{value:d}')
def _DebugPrintFiletimeValue(self, description, value):
"""Prints a FILETIME timestamp value for debugging.
Args:
description (str): description.
value (object): value.
"""
if value == 0:
date_time_string = 'Not set (0)'
elif value == 0x7fffffffffffffff:
date_time_string = 'Never (0x7fffffffffffffff)'
else:
date_time = dfdatetime_filetime.Filetime(timestamp=value)
date_time_string = date_time.CopyToDateTimeString()
if date_time_string:
date_time_string = f'{date_time_string:s} UTC'
else:
date_time_string = f'0x{value:08x}'
self._DebugPrintValue(description, date_time_string)
def _DebugPrintStructureObject(self, structure_object, debug_info):
"""Prints structure object debug information.
Args:
structure_object (object): structure object.
debug_info (list[tuple[str, str, int]]): debug information.
"""
text = self._FormatStructureObject(structure_object, debug_info)
self._output_writer.WriteText(text)
def _DebugPrintText(self, text):
"""Prints text for debugging.
Args:
text (str): text.
"""
if self._output_writer:
self._output_writer.DebugPrintText(text)
def _DebugPrintValue(self, description, value):
"""Prints a value for debugging.
Args:
description (str): description.
value (object): value.
"""
if self._output_writer:
text = self._FormatValue(description, value)
self._output_writer.WriteText(text)
def _FormatDataInHexadecimal(self, data):
"""Formats data in a hexadecimal representation.
Args:
data (bytes): data.
Returns:
str: hexadecimal representation of the data.
"""
in_group = False
previous_hexadecimal_string = None
lines = []
data_size = len(data)
for block_index in range(0, data_size, 16):
data_string = data[block_index:block_index + 16]
hexadecimal_byte_values = []
printable_values = []
for byte_value in data_string:
if isinstance(byte_value, str):
byte_value = ord(byte_value)
hexadecimal_byte_values.append(f'{byte_value:02x}')
printable_value = self._HEXDUMP_CHARACTER_MAP[byte_value]
printable_values.append(printable_value)
remaining_size = 16 - len(data_string)
if remaining_size == 0:
whitespace = ''
elif remaining_size >= 8:
whitespace = ' ' * ((3 * remaining_size) - 1)
else:
whitespace = ' ' * (3 * remaining_size)
hexadecimal_string_part1 = ' '.join(hexadecimal_byte_values[0:8])
hexadecimal_string_part2 = ' '.join(hexadecimal_byte_values[8:16])
hexadecimal_string = (
f'{hexadecimal_string_part1:s} {hexadecimal_string_part2:s}'
f'{whitespace:s}')
if (previous_hexadecimal_string is not None and
previous_hexadecimal_string == hexadecimal_string and
block_index + 16 < data_size):
if not in_group:
in_group = True
lines.append('...')
else:
printable_string = ''.join(printable_values)
lines.append(
f'0x{block_index:08x} {hexadecimal_string:s} '
f'{printable_string:s}')
in_group = False
previous_hexadecimal_string = hexadecimal_string
lines.extend(['', ''])
return '\n'.join(lines)
def _FormatIntegerAsDecimal(self, integer):
"""Formats an integer as a decimal.
Args:
integer (int): integer.
Returns:
str: integer formatted as a decimal.
"""
return f'{integer:d}'
def _FormatIntegerAsFiletime(self, integer):
"""Formats an integer as a FILETIME date and time value.
Args:
integer (int): integer.
Returns:
str: integer formatted as a FILETIME date and time value.
"""
if integer == 0:
return 'Not set (0)'
if integer == 0x7fffffffffffffff:
return 'Never (0x7fffffffffffffff)'
date_time = dfdatetime_filetime.Filetime(timestamp=integer)
date_time_string = date_time.CopyToDateTimeString()
if not date_time_string:
return f'0x{integer:08x}'
return f'{date_time_string:s} UTC'
def _FormatIntegerAsHexadecimal2(self, integer):
"""Formats an integer as an 2-digit hexadecimal.
Args:
integer (int): integer.
Returns:
str: integer formatted as an 2-digit hexadecimal.
"""
return f'0x{integer:02x}'
def _FormatIntegerAsHexadecimal4(self, integer):
"""Formats an integer as an 4-digit hexadecimal.
Args:
integer (int): integer.
Returns:
str: integer formatted as an 4-digit hexadecimal.
"""
return f'0x{integer:04x}'
def _FormatIntegerAsHexadecimal8(self, integer):
"""Formats an integer as an 8-digit hexadecimal.
Args:
integer (int): integer.
Returns:
str: integer formatted as an 8-digit hexadecimal.
"""
return f'0x{integer:08x}'
def _FormatStructureObject(self, structure_object, debug_info):
"""Formats a structure object debug information.
Args:
structure_object (object): structure object.
debug_info (list[tuple[str, str, int]]): debug information.
Returns:
str: structure object debug information.
"""
lines = []
attribute_value = ''
for attribute_name, description, value_format_callback in debug_info:
attribute_value = getattr(structure_object, attribute_name, None)
if attribute_value is None:
continue
value_format_function = None
if value_format_callback:
value_format_function = getattr(self, value_format_callback, None)
if value_format_function:
attribute_value = value_format_function(attribute_value)
if isinstance(attribute_value, str) and '\n' in attribute_value:
text = ''
if description is not None:
text = f'{description:s}:\n'
text = ''.join([text, attribute_value])
else:
text = self._FormatValue(description, attribute_value)
lines.append(text)
if not attribute_value or attribute_value[:-2] != '\n\n':
lines.append('\n')
return ''.join(lines)
def _FormatValue(self, description, value):
"""Formats a value for debugging.
Args:
description (str): description.
value (object): value.
Returns:
str: formatted value.
"""
alignment, _ = divmod(len(description), 8)
alignment_string = '\t' * (8 - alignment + 1)
return f'{description:s}{alignment_string:s}: {value!s}\n'
def _GetDataTypeMap(self, name):
"""Retrieves a data type map defined by the definition file.
The data type maps are cached for reuse.
Args:
name (str): name of the data type as defined by the definition file.
Returns:
dtfabric.DataTypeMap: data type map which contains a data type definition,
such as a structure, that can be mapped onto binary data.
"""
data_type_map = self._data_type_maps.get(name, None)
if not data_type_map:
data_type_map = self._fabric.CreateDataTypeMap(name)
self._data_type_maps[name] = data_type_map
return data_type_map
def _ReadDefinitionFile(self, filename):
"""Reads a dtFabric definition file.
Args:
filename (str): name of the dtFabric definition file.
Returns:
dtfabric.DataTypeFabric: data type fabric which contains the data format
data type maps of the data type definition, such as a structure, that
can be mapped onto binary data or None if no filename is provided.
"""
if not filename:
return None
path = os.path.join(self._DEFINITION_FILES_PATH, filename)
with open(path, 'rb') as file_object:
definition = file_object.read()
return dtfabric_fabric.DataTypeFabric(yaml_definition=definition)
def _ReadStructureFromByteStream(
self, byte_stream, file_offset, data_type_map, description, context=None):
"""Reads a structure from a byte stream.
Args:
byte_stream (bytes): byte stream.
file_offset (int): offset of the structure data relative to the start
of the file-like object.
data_type_map (dtfabric.DataTypeMap): data type map of the structure.
description (str): description of the structure.
context (Optional[dtfabric.DataTypeMapContext]): data type map context.
Returns:
object: structure values object.
Raises:
ParseError: if the structure cannot be read.
ValueError: if file-like object or data type map is missing.
"""
if not byte_stream:
raise ValueError('Missing byte stream.')
if not data_type_map:
raise ValueError('Missing data type map.')
try:
return data_type_map.MapByteStream(byte_stream, context=context)
except (dtfabric_errors.ByteStreamTooSmallError,
dtfabric_errors.MappingError) as exception:
raise errors.ParseError((
f'Unable to map {description:s} data at offset: 0x{file_offset:08x} '
f'with error: {exception!s}'))