"""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.
                error = token_values["error"]
                token_status = token_values["token_status"]
                call_status = token_values["call_status"]
                return_token_values = (
                    f"{{'error': '{error:s}', "
                    f"'token_status': {token_status:d}, "
                    f"'call_status': {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)
