"""File containing a Windows Registry plugin to parse the USBStor key.

Also see:
  https://forensics.wiki/usb_history_viewing
"""

import os

from dfdatetime import filetime as dfdatetime_filetime

from plaso.containers import events
from plaso.lib import dtfabric_helper
from plaso.lib import errors
from plaso.parsers import winreg_parser
from plaso.parsers.winreg_plugins import interface


class USBStorDeviceInstanceEventData(events.EventData):
    """USBStor device instance event data attribute container.

    Attributes:
      device_display_name (str): display name of the USB device.
      device_last_arrival_time (dfdatetime.DateTimeValues): date and time of the device
          insertion.
      device_last_removal_time (dfdatetime.DateTimeValues): date and time of the removal
          insertion.
      device_type (str): type of USB device.
      key_path (str): Windows Registry key path.
      driver_first_installation_time (dfdatetime.DateTimeValues): date and time of when
          the device instance was first installed in the system
      driver_last_installation_time (dfdatetime.DateTimeValues): date and time of when
          the current device instance was installed in the system.
      firmware_time (dfdatetime.DateTimeValues): date and time of the firmware.
      product (str): product of the USB device.
      revision (str): revision number of the USB device.
      vendor (str): vendor of the USB device.
    """

    DATA_TYPE = "windows:registry:usbstor:instance"

    def __init__(self):
        """Initializes event data."""
        super().__init__(data_type=self.DATA_TYPE)
        self.device_display_name = None
        self.device_last_arrival_time = None
        self.device_last_removal_time = None
        self.device_type = None
        self.driver_first_installation_time = None
        self.driver_last_installation_time = None
        self.firmware_time = None
        self.key_path = None
        self.product = None
        self.revision = None
        self.vendor = None


class USBStorPlugin(interface.WindowsRegistryPlugin, dtfabric_helper.DtFabricHelper):
    """USBStor key plugin."""

    NAME = "windows_usbstor_devices"
    DATA_FORMAT = "Windows USB Plug And Play Manager USBStor Registry data"

    FILTERS = frozenset(
        [
            interface.WindowsRegistryKeyPathFilter(
                "HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Enum\\USBSTOR"
            )
        ]
    )

    _DEFINITION_FILE = os.path.join(os.path.dirname(__file__), "usbstor.yaml")

    def _GetPropertyValueData(self, property_value_key, value_type):
        """Retrieves a property value data.

        Args:
          property_value_key (dfwinreg.WinRegistryKey): property value Windows Registry
              key.
          value_type (int): value type.

        Returns:
          object: property value data.

        Raises:
          ParseError: if the property value data cannot be determined.
        """
        binary_data = self._GetValueDataFromKey(property_value_key, "Data")

        if value_type == 0x00000007:
            data_type_map = self._GetDataTypeMap("uint32le")
        elif value_type == 0x00000010:
            data_type_map = self._GetDataTypeMap("uint64le")
        elif value_type == 0x00000012:
            data_type_map = self._GetDataTypeMap("utf16le_string")
        else:
            raise errors.ParseError(f"Unsupported value type: 0x{value_type:08x}")

        try:
            value_data = self._ReadStructureFromByteStream(
                binary_data, 0, data_type_map
            )
        except (ValueError, errors.ParseError) as exception:
            raise errors.ParseError(
                f"Unable to parse value: Data with error: {exception!s}"
            )

        if value_type == 0x00000010:
            value_data = dfdatetime_filetime.Filetime(timestamp=value_data)

        return value_data

    def _GetPropertyValueType(self, property_value_key):
        """Retrieves a property value type.

        Args:
          property_value_key (dfwinreg.WinRegistryKey): property value Windows
              Registry key.

        Returns:
          int: property value type.

        Raises:
          ParseError: if the property value type cannot be determined.
        """
        binary_data = self._GetValueDataFromKey(property_value_key, "Type")

        data_type_map = self._GetDataTypeMap("uint32le")

        try:
            return self._ReadStructureFromByteStream(binary_data, 0, data_type_map)
        except (ValueError, errors.ParseError) as exception:
            raise errors.ParseError(
                f"Unable to parse value: 'Type' with error: {exception!s}"
            )

    def _ParseDeviceKey(self, parser_mediator, device_key):
        """Parses an USB storage device key.

        Args:
          parser_mediator (ParserMediator): mediates interactions between parsers and
              other components, such as storage and dfVFS.
          device_key (dfwinreg.WinRegistryKey): USB storage device Windows Registry
              key.

        Returns:
          USBStorageDevice: an USB storage device.
        """
        name_values = device_key.name.split("&")
        device_type = None
        product = None
        revision = None
        vendor = None

        number_of_name_values = len(name_values)
        if number_of_name_values >= 1:
            device_type = name_values[0]
        if number_of_name_values >= 2:
            vendor = name_values[1]
        if number_of_name_values >= 3:
            product = name_values[2]
        if number_of_name_values >= 4:
            revision = name_values[3]

        for device_instance_key in device_key.GetSubkeys():
            properties = {}

            properties_key = device_instance_key.GetSubkeyByName("Properties")
            if properties_key:
                for property_set_key in properties_key.GetSubkeys():
                    for property_key in property_set_key.GetSubkeys():
                        for property_value_key in property_key.GetSubkeys():
                            lookup_key = ":".join(
                                [property_set_key.name, property_key.name]
                            ).lower()

                            value_type = self._GetPropertyValueType(property_value_key)
                            properties[lookup_key] = self._GetPropertyValueData(
                                property_value_key, value_type
                            )

            event_data = USBStorDeviceInstanceEventData()
            event_data.device_display_name = self._GetValueFromKey(
                device_instance_key, "FriendlyName"
            )
            event_data.device_last_arrival_time = properties.get(
                "{83da6326-97a6-4088-9453-a1923f573b29}:00000066", None
            )
            event_data.device_last_removal_time = properties.get(
                "{83da6326-97a6-4088-9453-a1923f573b29}:00000067", None
            )
            event_data.device_type = device_type
            event_data.driver_first_installation_time = properties.get(
                "{83da6326-97a6-4088-9453-a1923f573b29}:00000064", None
            )
            event_data.driver_last_installation_time = properties.get(
                "{83da6326-97a6-4088-9453-a1923f573b29}:00000065", None
            )
            event_data.firmware_time = properties.get(
                "{540b947e-8b40-45bc-a8a2-6a0b894cbda2}:00000011", None
            )
            event_data.key_path = device_instance_key.path
            event_data.product = product
            event_data.revision = revision
            event_data.vendor = vendor

            parser_mediator.ProduceEventData(event_data)

    def ExtractEvents(self, parser_mediator, registry_key, **kwargs):
        """Extracts events from a Windows Registry key.

        Args:
          parser_mediator (ParserMediator): mediates interactions between parsers and
              other components, such as storage and dfVFS.
          registry_key (dfwinreg.WinRegistryKey): Windows Registry key.
        """
        for device_key in registry_key.GetSubkeys():
            try:
                self._ParseDeviceKey(parser_mediator, device_key)
            except (ValueError, errors.ParseError) as exception:
                parser_mediator.ProduceWarning(
                    f"unable to parse device key with error: {exception!s}"
                )
                continue

        self._ProduceDefaultWindowsRegistryEvent(parser_mediator, registry_key)


winreg_parser.WinRegistryParser.RegisterPlugin(USBStorPlugin)
