"""Basic Security Module (BSM) event auditing file parser."""

import os

from dfdatetime import posix_time as dfdatetime_posix_time

from plaso.containers import events
from plaso.lib import definitions
from plaso.lib import dtfabric_helper
from plaso.lib import errors
from plaso.parsers import interface
from plaso.parsers import manager


class BSMEventData(events.EventData):
  """Basic Security Module (BSM) audit event data.

  Attributes:
    event_type (int): identifier that represents the type of the event.
    extra_tokens (list[dict[str, dict[str, str]]]): event extra tokens, which
        is a list of dictionaries that contain: {token type: {token values}}
    offset (int): offset of the BSM record relative to the start of the file,
        from which the event data was extracted.
    record_length (int): record length in bytes (trailer number).
    return_value (str): processed return value and exit status.
    written_time (dfdatetime.DateTimeValues): entry written date and time.
  """

  DATA_TYPE = 'bsm:entry'

  def __init__(self):
    """Initializes event data."""
    super().__init__(data_type=self.DATA_TYPE)
    self.event_type = None
    self.extra_tokens = None
    self.offset = None
    self.record_length = None
    self.return_value = None
    self.written_time = None


class BSMParser(interface.FileObjectParser, dtfabric_helper.DtFabricHelper):
  """Parser for Basic Security Module (BSM) event auditing files."""

  NAME = 'bsm_log'
  DATA_FORMAT = 'Basic Security Module (BSM) event auditing file'

  _DEFINITION_FILE = os.path.join(
      os.path.dirname(__file__), 'bsm.yaml')

  _TOKEN_TYPE_AUT_TRAILER = 0x13
  _TOKEN_TYPE_AUT_HEADER32 = 0x14
  _TOKEN_TYPE_AUT_HEADER32_EX = 0x15
  _TOKEN_TYPE_AUT_RETURN32 = 0x27
  _TOKEN_TYPE_AUT_RETURN64 = 0x72
  _TOKEN_TYPE_AUT_HEADER64 = 0x74
  _TOKEN_TYPE_AUT_HEADER64_EX = 0x79

  _HEADER_TOKEN_TYPES = frozenset([
      _TOKEN_TYPE_AUT_HEADER32,
      _TOKEN_TYPE_AUT_HEADER32_EX,
      _TOKEN_TYPE_AUT_HEADER64,
      _TOKEN_TYPE_AUT_HEADER64_EX])

  _TOKEN_TYPES = {
      0x00: 'AUT_INVALID',
      0x11: 'AUT_OTHER_FILE32',
      0x12: 'AUT_OHEADER',
      0x13: 'AUT_TRAILER',
      0x14: 'AUT_HEADER32',
      0x15: 'AUT_HEADER32_EX',
      0x21: 'AUT_DATA',
      0x22: 'AUT_IPC',
      0x23: 'AUT_PATH',
      0x24: 'AUT_SUBJECT32',
      0x25: 'AUT_XATPATH',
      0x26: 'AUT_PROCESS32',
      0x27: 'AUT_RETURN32',
      0x28: 'AUT_TEXT',
      0x29: 'AUT_OPAQUE',
      0x2a: 'AUT_IN_ADDR',
      0x2b: 'AUT_IP',
      0x2c: 'AUT_IPORT',
      0x2d: 'AUT_ARG32',
      0x2e: 'AUT_SOCKET',
      0x2f: 'AUT_SEQ',
      0x30: 'AUT_ACL',
      0x31: 'AUT_ATTR',
      0x32: 'AUT_IPC_PERM',
      0x33: 'AUT_LABEL',
      0x34: 'AUT_GROUPS',
      0x35: 'AUT_ACE',
      0x36: 'AUT_SLABEL',
      0x37: 'AUT_CLEAR',
      0x38: 'AUT_PRIV',
      0x39: 'AUT_UPRIV',
      0x3a: 'AUT_LIAISON',
      0x3b: 'AUT_NEWGROUPS',
      0x3c: 'AUT_EXEC_ARGS',
      0x3d: 'AUT_EXEC_ENV',
      0x3e: 'AUT_ATTR32',
      0x3f: 'AUT_UNAUTH',
      0x40: 'AUT_XATOM',
      0x41: 'AUT_XOBJ',
      0x42: 'AUT_XPROTO',
      0x43: 'AUT_XSELECT',
      0x44: 'AUT_XCOLORMAP',
      0x45: 'AUT_XCURSOR',
      0x46: 'AUT_XFONT',
      0x47: 'AUT_XGC',
      0x48: 'AUT_XPIXMAP',
      0x49: 'AUT_XPROPERTY',
      0x4a: 'AUT_XWINDOW',
      0x4b: 'AUT_XCLIENT',
      0x51: 'AUT_CMD',
      0x52: 'AUT_EXIT',
      0x60: 'AUT_ZONENAME',
      0x70: 'AUT_HOST',
      0x71: 'AUT_ARG64',
      0x72: 'AUT_RETURN64',
      0x73: 'AUT_ATTR64',
      0x74: 'AUT_HEADER64',
      0x75: 'AUT_SUBJECT64',
      0x76: 'AUT_SERVER64',
      0x77: 'AUT_PROCESS64',
      0x78: 'AUT_OTHER_FILE64',
      0x79: 'AUT_HEADER64_EX',
      0x7a: 'AUT_SUBJECT32_EX',
      0x7b: 'AUT_PROCESS32_EX',
      0x7c: 'AUT_SUBJECT64_EX',
      0x7d: 'AUT_PROCESS64_EX',
      0x7e: 'AUT_IN_ADDR_EX',
      0x7f: 'AUT_SOCKET_EX',
      0x80: 'AUT_SOCKINET32',
      0x81: 'AUT_SOCKINET128',
      0x82: 'AUT_SOCKUNIX'}

  _DATA_TYPE_MAP_PER_TOKEN_TYPE = {
      0x11: 'bsm_token_data_other_file32',
      0x13: 'bsm_token_data_trailer',
      0x14: 'bsm_token_data_header32',
      0x15: 'bsm_token_data_header32_ex',
      0x21: 'bsm_token_data_data',
      0x22: 'bsm_token_data_ipc',
      0x23: 'bsm_token_data_path',
      0x24: 'bsm_token_data_subject32',
      0x26: 'bsm_token_data_subject32',
      0x27: 'bsm_token_data_return32',
      0x28: 'bsm_token_data_text',
      0x29: 'bsm_token_data_opaque',
      0x2a: 'bsm_token_data_in_addr',
      0x2b: 'bsm_token_data_ip',
      0x2c: 'bsm_token_data_iport',
      0x2d: 'bsm_token_data_arg32',
      0x2f: 'bsm_token_data_seq',
      0x32: 'bsm_token_data_ipc_perm',
      0x34: 'bsm_token_data_groups',
      0x3b: 'bsm_token_data_groups',
      0x3c: 'bsm_token_data_exec_args',
      0x3d: 'bsm_token_data_exec_args',
      0x3e: 'bsm_token_data_attr32',
      0x52: 'bsm_token_data_exit',
      0x60: 'bsm_token_data_zonename',
      0x71: 'bsm_token_data_arg64',
      0x72: 'bsm_token_data_return64',
      0x73: 'bsm_token_data_attr64',
      0x74: 'bsm_token_data_header64',
      0x75: 'bsm_token_data_subject64',
      0x77: 'bsm_token_data_subject64',
      0x79: 'bsm_token_data_header64_ex',
      0x7a: 'bsm_token_data_subject32_ex',
      0x7b: 'bsm_token_data_subject32_ex',
      0x7c: 'bsm_token_data_subject64_ex',
      0x7d: 'bsm_token_data_subject64_ex',
      0x7e: 'bsm_token_data_in_addr_ex',
      0x7f: 'bsm_token_data_socket_ex',
      0x80: 'bsm_token_data_sockinet32',
      0x81: 'bsm_token_data_sockinet64',
      0x82: 'bsm_token_data_sockunix'}

  _TOKEN_DATA_FORMAT_FUNCTIONS = {
      0x11: '_FormatOtherFileToken',
      0x21: '_FormatDataToken',
      0x22: '_FormatIPCToken',
      0x23: '_FormatPathToken',
      0x24: '_FormatSubjectOrProcessToken',
      0x26: '_FormatSubjectOrProcessToken',
      0x27: '_FormatReturnOrExitToken',
      0x28: '_FormatTextToken',
      0x29: '_FormatOpaqueToken',
      0x2a: '_FormatInAddrToken',
      0x2b: '_FormatIPToken',
      0x2c: '_FormatIPortToken',
      0x2d: '_FormatArgToken',
      0x2f: '_FormatSeqToken',
      0x32: '_FormatIPCPermToken',
      0x34: '_FormatGroupsToken',
      0x3b: '_FormatGroupsToken',
      0x3c: '_FormatExecArgsToken',
      0x3d: '_FormatExecArgsToken',
      0x3e: '_FormatAttrToken',
      0x52: '_FormatReturnOrExitToken',
      0x60: '_FormatZonenameToken',
      0x71: '_FormatArgToken',
      0x72: '_FormatReturnOrExitToken',
      0x73: '_FormatAttrToken',
      0x75: '_FormatSubjectOrProcessToken',
      0x77: '_FormatSubjectOrProcessToken',
      0x7a: '_FormatSubjectExOrProcessExToken',
      0x7b: '_FormatSubjectExOrProcessExToken',
      0x7c: '_FormatSubjectExOrProcessExToken',
      0x7d: '_FormatSubjectExOrProcessExToken',
      0x7e: '_FormatInAddrExToken',
      0x7f: '_FormatSocketExToken',
      0x80: '_FormatSocketInet32Token',
      0x81: '_FormatSocketInet128Token',
      0x82: '_FormatSocketUnixToken'}

  _DATA_TOKEN_FORMAT = {
      0: 'Binary',
      1: 'Octal',
      2: 'Decimal',
      3: 'Hexadecimal',
      4: 'String'}

  # BSM identification errors.
  _ERRORS = {
      0: 'Success',
      1: 'Operation not permitted',
      2: 'No such file or directory',
      3: 'No such process',
      4: 'Interrupted system call',
      5: 'Input/output error',
      6: 'Device not configured',
      7: 'Argument list too long',
      8: 'Exec format error',
      9: 'Bad file descriptor',
      10: 'No child processes',
      11: 'Resource temporarily unavailable',
      12: 'Cannot allocate memory',
      13: 'Permission denied',
      14: 'Bad address',
      15: 'Block device required',
      16: 'Device busy',
      17: 'File exists',
      18: 'ross-device link',
      19: 'Operation not supported by device',
      20: 'Not a directory',
      21: 'Is a directory',
      22: 'Invalid argument',
      23: 'Too many open files in system',
      24: 'Too many open files',
      25: 'Inappropriate ioctl for device',
      26: 'Text file busy',
      27: 'File too large',
      28: 'No space left on device',
      29: 'Illegal seek',
      30: 'Read-only file system',
      31: 'Too many links',
      32: 'Broken pipe',
      33: 'Numerical argument out of domain',
      34: 'Result too large',
      35: 'No message of desired type',
      36: 'Identifier removed',
      45: 'Resource deadlock avoided',
      46: 'No locks available',
      47: 'Operation canceled',
      48: 'Operation not supported',
      49: 'Disc quota exceeded',
      66: 'Too many levels of remote in path',
      67: 'Link has been severed',
      71: 'Protocol error',
      74: 'Multihop attempted',
      77: 'Bad message',
      78: 'File name too long',
      79: 'Value too large to be stored in data type',
      88: 'Illegal byte sequence',
      89: 'Function not implemented',
      90: 'Too many levels of symbolic links',
      91: 'Restart syscall',
      93: 'Directory not empty',
      94: 'Too many users',
      95: 'Socket operation on non-socket',
      96: 'Destination address required',
      97: 'Message too long',
      98: 'Protocol wrong type for socket',
      99: 'Protocol not available',
      120: 'Protocol not supported',
      121: 'Socket type not supported',
      122: 'Operation not supported',
      123: 'Protocol family not supported',
      124: 'Address family not supported by protocol family',
      125: 'Address already in use',
      126: 'Can\'t assign requested address',
      127: 'Network is down',
      128: 'Network unreachable',
      129: 'Network dropped connection on reset',
      130: 'Software caused connection abort',
      131: 'Connection reset by peer',
      132: 'No buffer space available',
      133: 'Socket is already connected',
      134: 'Socket is not connected',
      143: 'Can\'t send after socket shutdown',
      144: 'Too many references: can\'t splice',
      145: 'Operation timed out',
      146: 'Connection refused',
      147: 'Host is down',
      148: 'No route to host',
      149: 'Operation already in progress',
      150: 'Operation now in progress',
      151: 'Stale NFS file handle',
      190: 'PROCLIM',
      191: 'BADRPC',
      192: 'RPCMISMATCH',
      193: 'PROGUNAVAIL',
      194: 'PROGMISMATCH',
      195: 'PROCUNAVAIL',
      196: 'FTYPE',
      197: 'AUTH',
      198: 'NEEDAUTH',
      199: 'NOATTR',
      200: 'DOOFUS',
      201: 'USTRETURN',
      202: 'NOIOCTL',
      203: 'DIRIOCTL',
      204: 'PWROFF',
      205: 'DEVERR',
      206: 'BADEXEC',
      207: 'BADARCH',
      208: 'SHLIBVERS',
      209: 'BADMACHO',
      210: 'POLICY'}

  # BSM network protocolsb based on information from OpenBSD.
  _NETWORK_PROTOCOLS = {
      0: 'UNSPEC',
      1: 'LOCAL',
      2: 'INET',
      3: 'IMPLINK',
      4: 'PUP',
      5: 'CHAOS',
      6: 'NS',
      8: 'ECMA',
      9: 'DATAKIT',
      10: 'CCITT',
      11: 'SNA',
      12: 'DECnet',
      13: 'DLI',
      14: 'LAT',
      15: 'HYLINK',
      16: 'APPLETALK',
      19: 'OSI',
      23: 'IPX',
      24: 'ROUTE',
      25: 'LINK',
      26: 'INET6',
      27: 'KEY',
      500: 'NETBIOS',
      501: 'ISO',
      502: 'XTP',
      503: 'COIP',
      504: 'CNT',
      505: 'RTIP',
      506: 'SIP',
      507: 'PIP',
      508: 'ISDN',
      509: 'E164',
      510: 'NATM',
      511: 'ATM',
      512: 'NETGRAPH',
      513: 'SLOW',
      514: 'CLUSTER',
      515: 'ARP',
      516: 'BLUETOOTH'}

  def _FormatArgToken(self, token_data):
    """Formats an argument token as a dictionary of values.

    Args:
      token_data (bsm_token_data_arg32|bsm_token_data_arg64): AUT_ARG32 or
          AUT_ARG64 token data.

    Returns:
      dict[str, str]: token values.
    """
    return {
        'string': token_data.argument_value.rstrip('\x00'),
        'num_arg': token_data.argument_index,
        'is': token_data.argument_name}

  def _FormatAttrToken(self, token_data):
    """Formats an attribute token as a dictionary of values.

    Args:
      token_data (bsm_token_data_attr32|bsm_token_data_attr64): AUT_ATTR32 or
          AUT_ATTR64 token data.

    Returns:
      dict[str, str]: token values.
    """
    return {
        'mode': token_data.file_mode,
        'uid': token_data.user_identifier,
        'gid': token_data.group_identifier,
        'system_id': token_data.file_system_identifier,
        'node_id': token_data.file_identifier,
        'device': token_data.device}

  def _FormatDataToken(self, token_data):
    """Formats a data token as a dictionary of values.

    Args:
      token_data (bsm_token_data_data): AUT_DATA token data.

    Returns:
      dict[str, str]: token values.
    """
    format_string = self._DATA_TOKEN_FORMAT.get(
        token_data.data_format, 'UNKNOWN')

    if token_data.data_format == 4:
      data = bytes(bytearray(token_data.data)).split(b'\x00', maxsplit=1)[0]
      data = data.decode('utf-8')
    else:
      data = ''.join([f'{byte:02x}' for byte in token_data.data])
    return {
        'format': format_string,
        'data': data}

  def _FormatInAddrExToken(self, token_data):
    """Formats an extended IPv4 address token as a dictionary of values.

    Args:
      token_data (bsm_token_data_in_addr_ex): AUT_IN_ADDR_EX token data.

    Returns:
      dict[str, str]: token values.
    """
    protocol = self._NETWORK_PROTOCOLS.get(token_data.net_type, 'UNKNOWN')

    if token_data.net_type == 4:
      ip_address = self._FormatPackedIPv6Address(token_data.ip_address[:4])
    elif token_data.net_type == 16:
      ip_address = self._FormatPackedIPv6Address(token_data.ip_address)
    else:
      ip_address = None

    return {
        'protocols': protocol,
        'net_type': token_data.net_type,
        'address': ip_address}

  def _FormatInAddrToken(self, token_data):
    """Formats an IPv4 address token as a dictionary of values.

    Args:
      token_data (bsm_token_data_in_addr): AUT_IN_ADDR token data.

    Returns:
      dict[str, str]: token values.
    """
    ip_address = self._FormatPackedIPv4Address(token_data.ip_address)
    return {'ip': ip_address}

  def _FormatIPCPermToken(self, token_data):
    """Formats an IPC permissions token as a dictionary of values.

    Args:
      token_data (bsm_token_data_ipc_perm): AUT_IPC_PERM token data.

    Returns:
      dict[str, str]: token values.
    """
    return {
        'user_id': token_data.user_identifier,
        'group_id': token_data.group_identifier,
        'creator_user_id': token_data.creator_user_identifier,
        'creator_group_id': token_data.creator_group_identifier,
        'access': token_data.access_mode}

  def _FormatIPCToken(self, token_data):
    """Formats an IPC token as a dictionary of values.

    Args:
      token_data (bsm_token_data_ipc): AUT_IPC token data.

    Returns:
      dict[str, str]: token values.
    """
    return {
        'object_type': token_data.object_type,
        'object_id': token_data.object_identifier}

  def _FormatGroupsToken(self, token_data):
    """Formats a groups token as a dictionary of values.

    Args:
      token_data (bsm_token_data_groups): AUT_GROUPS or AUT_NEWGROUPS token
          data.

    Returns:
      dict[str, str]: token values.
    """
    return {
        'number_of_groups': token_data.number_of_groups,
        'groups': ', '.join(token_data.groups)}

  def _FormatExecArgsToken(self, token_data):
    """Formats an execution arguments token as a dictionary of values.

    Args:
      token_data (bsm_token_data_exec_args): AUT_EXEC_ARGS or AUT_EXEC_ENV
          token data.

    Returns:
      dict[str, str]: token values.
    """
    return {
        'number_of_strings': token_data.number_of_strings,
        'strings': ', '.join(token_data.strings)}

  def _FormatIPortToken(self, token_data):
    """Formats an IP port token as a dictionary of values.

    Args:
      token_data (bsm_token_data_iport): AUT_IPORT token data.

    Returns:
      dict[str, str]: token values.
    """
    return {'port_number': token_data.port_number}

  def _FormatIPToken(self, token_data):
    """Formats an IPv4 packet header token as a dictionary of values.

    Args:
      token_data (bsm_token_data_ip): AUT_IP token data.

    Returns:
      dict[str, str]: token values.
    """
    data = ''.join([f'{byte:02x}' for byte in token_data.data])
    return {'IPv4_Header': data}

  def _FormatOpaqueToken(self, token_data):
    """Formats an opaque token as a dictionary of values.

    Args:
      token_data (bsm_token_data_opaque): AUT_OPAQUE token data.

    Returns:
      dict[str, str]: token values.
    """
    data = ''.join([f'{byte:02x}' for byte in token_data.data])
    return {'data': data}

  def _FormatOtherFileToken(self, token_data):
    """Formats an other file token as a dictionary of values.

    Args:
      token_data (bsm_token_data_other_file32): AUT_OTHER_FILE32 token data.

    Returns:
      dict[str, str]: token values.
    """
    # TODO: if this timestamp is useful, it must be extracted as a separate
    # event object.
    timestamp = token_data.microseconds + (
        token_data.timestamp * definitions.MICROSECONDS_PER_SECOND)
    date_time = dfdatetime_posix_time.PosixTimeInMicroseconds(
        timestamp=timestamp)
    date_time_string = date_time.CopyToDateTimeString()

    return {
        'string': token_data.name.rstrip('\x00'),
        'timestamp': date_time_string}

  def _FormatPathToken(self, token_data):
    """Formats a path token as a dictionary of values.

    Args:
      token_data (bsm_token_data_path): AUT_PATH token data.

    Returns:
      dict[str, str]: token values.
    """
    return {'path': token_data.path.rstrip('\x00')}

  def _FormatReturnOrExitToken(self, token_data):
    """Formats a return or exit token as a dictionary of values.

    Args:
      token_data (bsm_token_data_exit|bsm_token_data_return32|
                  bsm_token_data_return64): AUT_EXIT, AUT_RETURN32 or
          AUT_RETURN64 token data.

    Returns:
      dict[str, str]: token values.
    """
    error_string = self._ERRORS.get(token_data.status, 'UNKNOWN')
    return {
        'error': error_string,
        'token_status': token_data.status,
        'call_status': token_data.return_value}

  def _FormatSeqToken(self, token_data):
    """Formats a sequence token as a dictionary of values.

    Args:
      token_data (bsm_token_data_seq): AUT_SEQ token data.

    Returns:
      dict[str, str]: token values.
    """
    return {'sequence_number': token_data.sequence_number}

  def _FormatSocketExToken(self, token_data):
    """Formats an extended socket token as a dictionary of values.

    Args:
      token_data (bsm_token_data_socket_ex): AUT_SOCKET_EX token data.

    Returns:
      dict[str, str]: token values.
    """
    if token_data.socket_domain == 10:
      local_ip_address = self._FormatPackedIPv6Address(
          token_data.local_ip_address)
      remote_ip_address = self._FormatPackedIPv6Address(
          token_data.remote_ip_address)
    else:
      local_ip_address = self._FormatPackedIPv4Address(
          token_data.local_ip_address)
      remote_ip_address = self._FormatPackedIPv4Address(
          token_data.remote_ip_address)

    return {
        'from': local_ip_address,
        'from_port': token_data.local_port,
        'to': remote_ip_address,
        'to_port': token_data.remote_port}

  def _FormatSocketInet32Token(self, token_data):
    """Formats an Internet socket token as a dictionary of values.

    Args:
      token_data (bsm_token_data_sockinet32): AUT_SOCKINET32 token data.

    Returns:
      dict[str, str]: token values.
    """
    protocol = self._NETWORK_PROTOCOLS.get(token_data.socket_family, 'UNKNOWN')
    ip_address = self._FormatPackedIPv4Address(token_data.ip_addresss)
    return {
        'protocols': protocol,
        'family': token_data.socket_family,
        'port': token_data.port_number,
        'address': ip_address}

  def _FormatSocketInet128Token(self, token_data):
    """Formats an Internet socket token as a dictionary of values.

    Args:
      token_data (bsm_token_data_sockinet64): AUT_SOCKINET128 token data.

    Returns:
      dict[str, str]: token values.
    """
    protocol = self._NETWORK_PROTOCOLS.get(token_data.socket_family, 'UNKNOWN')
    ip_address = self._FormatPackedIPv6Address(token_data.ip_addresss)
    return {
        'protocols': protocol,
        'family': token_data.socket_family,
        'port': token_data.port_number,
        'address': ip_address}

  def _FormatSocketUnixToken(self, token_data):
    """Formats an Unix socket token as a dictionary of values.

    Args:
      token_data (bsm_token_data_sockunix): AUT_SOCKUNIX token data.

    Returns:
      dict[str, str]: token values.
    """
    protocol = self._NETWORK_PROTOCOLS.get(token_data.socket_family, 'UNKNOWN')
    return {
        'protocols': protocol,
        'family': token_data.socket_family,
        'path': token_data.socket_path}

  def _FormatSubjectOrProcessToken(self, token_data):
    """Formats a subject or process token as a dictionary of values.

    Args:
      token_data (bsm_token_data_subject32|bsm_token_data_subject64):
          AUT_SUBJECT32, AUT_PROCESS32, AUT_SUBJECT64 or AUT_PROCESS64 token
          data.

    Returns:
      dict[str, str]: token values.
    """
    ip_address = self._FormatPackedIPv4Address(token_data.ip_address)
    return {
        'aid': token_data.audit_user_identifier,
        'euid': token_data.effective_user_identifier,
        'egid': token_data.effective_group_identifier,
        'uid': token_data.real_user_identifier,
        'gid': token_data.real_group_identifier,
        'pid': token_data.process_identifier,
        'session_id': token_data.session_identifier,
        'terminal_port': token_data.terminal_port,
        'terminal_ip': ip_address}

  def _FormatSubjectExOrProcessExToken(self, token_data):
    """Formats a subject or process token as a dictionary of values.

    Args:
      token_data (bsm_token_data_subject32_ex|bsm_token_data_subject64_ex):
          AUT_SUBJECT32_EX, AUT_PROCESS32_EX, AUT_SUBJECT64_EX or
          AUT_PROCESS64_EX token data.

    Returns:
      dict[str, str]: token values.
    """
    if token_data.net_type == 4:
      ip_address = self._FormatPackedIPv4Address(token_data.ip_address)
    elif token_data.net_type == 16:
      ip_address = self._FormatPackedIPv6Address(token_data.ip_address)
    else:
      ip_address = 'unknown'

    return {
        'aid': token_data.audit_user_identifier,
        'euid': token_data.effective_user_identifier,
        'egid': token_data.effective_group_identifier,
        'uid': token_data.real_user_identifier,
        'gid': token_data.real_group_identifier,
        'pid': token_data.process_identifier,
        'session_id': token_data.session_identifier,
        'terminal_port': token_data.terminal_port,
        'terminal_ip': ip_address}

  def _FormatTextToken(self, token_data):
    """Formats a text token as a dictionary of values.

    Args:
      token_data (bsm_token_data_text): AUT_TEXT token data.

    Returns:
      dict[str, str]: token values.
    """
    return {'text': token_data.text.rstrip('\x00')}

  def _FormatTokenData(self, token_type, token_data):
    """Formats the token data as a dictionary of values.

    Args:
      token_type (int): token type.
      token_data (object): token data.

    Returns:
      dict[str, str]: formatted token values or an empty dictionary if no
          formatted token values could be determined.
    """
    token_data_format_function = self._TOKEN_DATA_FORMAT_FUNCTIONS.get(
        token_type)
    if token_data_format_function:
      token_data_format_function = getattr(
          self, token_data_format_function, None)

    if not token_data_format_function:
      return {}

    return token_data_format_function(token_data)

  def _FormatZonenameToken(self, token_data):
    """Formats a time zone name token as a dictionary of values.

    Args:
      token_data (bsm_token_data_zonename): AUT_ZONENAME token data.

    Returns:
      dict[str, str]: token values.
    """
    return {'name': token_data.name.rstrip('\x00')}

  def _ParseRecord(self, parser_mediator, file_object):
    """Parses an event record.

    Args:
      parser_mediator (ParserMediator): mediates interactions between parsers
          and other components, such as storage and dfVFS.
      file_object (dfvfs.FileIO): file-like object.

    Raises:
      ParseError: if the event record cannot be read.
    """
    header_record_offset = file_object.tell()

    # Check the header token type before reading the token data to prevent
    # variable size tokens to consume a large amount of memory.
    token_type = self._ParseTokenType(file_object, header_record_offset)
    if token_type not in self._HEADER_TOKEN_TYPES:
      raise errors.ParseError(
          f'Unsupported header token type: 0x{token_type:02x}')

    token_type, token_data = self._ParseToken(file_object, header_record_offset)

    if token_data.format_version != 11:
      raise errors.ParseError(
          f'Unsupported format version type: {token_data.format_version:d}')

    timestamp = token_data.microseconds + (
        token_data.timestamp * definitions.MICROSECONDS_PER_SECOND)

    event_type = token_data.event_type
    header_record_size = token_data.record_size
    record_end_offset = header_record_offset + header_record_size

    event_tokens = []
    return_token_values = None

    file_offset = file_object.tell()
    while file_offset < record_end_offset:
      token_type, token_data = self._ParseToken(file_object, file_offset)
      if not token_data:
        raise errors.ParseError(f'Unsupported token type: 0x{token_type:02x}')

      file_offset = file_object.tell()

      if token_type == self._TOKEN_TYPE_AUT_TRAILER:
        break

      token_type_string = self._TOKEN_TYPES.get(token_type, 'UNKNOWN')
      token_values = self._FormatTokenData(token_type, token_data)
      event_tokens.append({token_type_string: token_values})

      if token_type in (
          self._TOKEN_TYPE_AUT_RETURN32, self._TOKEN_TYPE_AUT_RETURN64):
        # Make sure return_token_values is a string.
        return_token_values = (
            f"{{'error': '{token_values['error']:s}', "
            f"'token_status': {token_values['token_status']:d}, "
            f"'call_status': {token_values['call_status']:d}}}")

    if token_data.record_size != header_record_size:
      raise errors.ParseError(
          'Mismatch of event record size between header and trailer token.')

    event_data = BSMEventData()
    event_data.event_type = event_type
    event_data.extra_tokens = event_tokens
    event_data.offset = header_record_offset
    event_data.record_length = header_record_size
    event_data.return_value = return_token_values
    event_data.written_time = dfdatetime_posix_time.PosixTimeInMicroseconds(
        timestamp=timestamp)

    parser_mediator.ProduceEventData(event_data)

  def _ParseToken(self, file_object, file_offset):
    """Parses a token.

    Args:
      file_object (dfvfs.FileIO): file-like object.
      file_offset (int): offset of the token relative to the start of
          the file-like object.

    Returns:
      tuple: containing:
        int: token type
        object: token data or None if the token type is not supported.
    """
    token_type = self._ParseTokenType(file_object, file_offset)
    token_data = None

    token_data_map_name = self._DATA_TYPE_MAP_PER_TOKEN_TYPE.get(
        token_type, None)
    if token_data_map_name:
      token_data_map = self._GetDataTypeMap(token_data_map_name)

      token_data, _ = self._ReadStructureFromFileObject(
          file_object, file_offset + 1, token_data_map)

    return token_type, token_data

  def _ParseTokenType(self, file_object, file_offset):
    """Parses a token type.

    Args:
      file_object (dfvfs.FileIO): file-like object.
      file_offset (int): offset of the token relative to the start of
          the file-like object.

    Returns:
      int: token type
    """
    token_type_map = self._GetDataTypeMap('uint8')

    token_type, _ = self._ReadStructureFromFileObject(
        file_object, file_offset, token_type_map)

    return token_type

  def ParseFileObject(self, parser_mediator, file_object):
    """Parses a BSM file-like object.

    Args:
      parser_mediator (ParserMediator): mediates interactions between parsers
          and other components, such as storage and dfVFS.
      file_object (dfvfs.FileIO): a file-like object.

    Raises:
      WrongParser: when the file cannot be parsed.
    """
    file_offset = file_object.get_offset()
    file_size = file_object.get_size()
    while file_offset < file_size:
      try:
        self._ParseRecord(parser_mediator, file_object)
      except errors.ParseError as exception:
        if file_offset == 0:
          raise errors.WrongParser(
              f'Unable to parse first event record with error: {exception!s}')

        # TODO: skip to next event record.

      file_offset = file_object.get_offset()


manager.ParsersManager.RegisterParser(BSMParser)
