"""AMT's hardware event log: the base64 records of ``AMT_MessageLog.GetRecords``. Each record is a 21-byte platform event (ASF/PET): a little-endian Unix timestamp, then device address, sensor type, event type, offset, source type, severity, sensor number, entity, entity instance and eight bytes of event data. The texts follow Intel's decoder in go-wsman-messages, which knows the events AMT itself writes: failed logins, BIOS progress and errors, watchdog and boot failures. """ from __future__ import annotations import base64 import binascii from typing import Any _RECORD_LENGTH = 21 #: Entity codes (IPMI entity IDs) -> name. _ENTITIES = { 0: "Unspecified", 1: "Other", 2: "Unknown", 3: "Processor", 4: "Disk", 5: "Peripheral", 6: "System management module", 7: "System board", 8: "Memory module", 9: "Processor module", 10: "Power supply", 11: "Add in card", 12: "Front panel board", 13: "Back panel board", 14: "Power system board", 15: "Drive backplane", 16: "System internal expansion board", 17: "Other system board", 18: "Processor board", 19: "Power unit", 20: "Power module", 21: "Power management board", 22: "Chassis back panel board", 23: "System chassis", 24: "Sub chassis", 25: "Other chassis board", 26: "Disk drive bay", 27: "Peripheral bay", 28: "Device bay", 29: "Fan cooling", 30: "Cooling unit", 31: "Cable interconnect", 32: "Memory device", 33: "System management software", 34: "BIOS", 35: "Intel(r) ME", 36: "System bus", 37: "Group", 38: "Intel(r) ME", 39: "External environment", 40: "Battery", 41: "Processing blade", 42: "Connectivity switch", 43: "Processor/memory module", 44: "I/O module", 45: "Processor I/O module", 46: "Management controller firmware", 47: "IPMI channel", 48: "PCI bus", 49: "PCI express bus", 50: "SCSI bus", 51: "SATA/SAS bus", 52: "Processor front side bus", } _FIRMWARE_ERRORS = { 0: "Unspecified.", 1: "No system memory is physically installed in the system.", 2: "No usable system memory, all installed memory has experienced an unrecoverable failure.", 3: "Unrecoverable hard-disk/ATAPI/IDE device failure.", 4: "Unrecoverable system-board failure.", 5: "Unrecoverable diskette subsystem failure.", 6: "Unrecoverable hard-disk controller failure.", 7: "Unrecoverable PS/2 or USB keyboard failure.", 8: "Removable boot media not found.", 9: "Unrecoverable video controller failure.", 10: "No video device detected.", 11: "Firmware (BIOS) ROM corruption detected.", 12: "CPU voltage mismatch (processors that share same supply have mismatched voltage " "requirements)", 13: "CPU speed matching failure", } _FIRMWARE_PROGRESS = { 0: "Unspecified.", 1: "Memory initialization.", 2: "Starting hard-disk initialization and test", 3: "Secondary processor(s) initialization", 4: "User authentication", 5: "User-initiated system setup", 6: "USB resource configuration", 7: "PCI resource configuration", 8: "Option ROM initialization", 9: "Video initialization", 10: "Cache initialization", 11: "SM Bus initialization", 12: "Keyboard controller initialization", 13: "Embedded controller/management controller initialization", 14: "Docking station attachment", 15: "Enabling docking station", 16: "Docking station ejection", 17: "Disabling docking station", 18: "Calling operating system wake-up vector", 19: "Starting operating system boot process", 20: "Baseboard or motherboard initialization", 21: "reserved", 22: "Floppy initialization", 23: "Keyboard test", 24: "Pointing device test", 25: "Primary processor initialization", } _WATCHDOG_STATES = {1: "Not Started", 2: "Stopped", 4: "Running", 8: "Expired", 16: "Suspended"} _FIXED = { 30: "No bootable media", 32: "Operating system lockup or power interrupt", 35: "System boot failure", 37: "System firmware started (at least one CPU is properly executing).", } def _severity(code: int) -> str: """PET severity: 16 critical, 32 non-recoverable; 8 non-critical; the rest is fine.""" if code >= 16: return "Critical" if code >= 8: return "Warning" return "OK" def _description(sensor_type: int, offset: int, data: bytes) -> str: if sensor_type == 6: return ( f"Authentication failed {data[1] + (data[2] << 8)} times. " "The system may be under attack." ) if sensor_type == 15: if data[0] == 235: return "Invalid Data" table = _FIRMWARE_ERRORS if offset == 0 else _FIRMWARE_PROGRESS return table.get(data[1], f"code {data[1]}") if sensor_type == 18: if data[0] == 170: agent = f"{data[4]:x}{data[3]:x}{data[2]:x}{data[1]:x}-{data[6]:x}{data[5]:x}" state = _WATCHDOG_STATES.get(data[7], str(data[7])) return f"Agent watchdog {agent}-... changed to {state}" return "Unknown event data field" return _FIXED.get(sensor_type, f"sensor type {sensor_type}") def parse_event_record(record: str, entry_id: str) -> dict[str, Any] | None: """One base64 record as a role event log entry; None for a record that is not one.""" try: raw = base64.b64decode(record, validate=True) except (binascii.Error, ValueError): return None if len(raw) < _RECORD_LENGTH: return None timestamp = int.from_bytes(raw[0:4], "little") sensor_type, offset, severity, entity = raw[5], raw[7], raw[9], raw[11] entity_name = _ENTITIES.get(entity, f"Entity {entity}") return { "id": entry_id, "timestamp": float(timestamp) if timestamp else None, "severity": _severity(severity), "message": f"{entity_name}: {_description(sensor_type, offset, raw[13:21])}", }