"""SU3 file format parser — signed update file format. Parses the header of I2P's SU3 update files which contain signed router updates, plugins, and other content. Ported from net.i2p.crypto.SU3File. """ from __future__ import annotations import struct from dataclasses import dataclass _SU3_MAGIC = b"I2Psu3" _MIN_HEADER_SIZE = 40 @dataclass class SU3Header: """Parsed SU3 file header.""" magic: str format_version: int sig_type: int sig_length: int content_type: int file_type: int version_length: int signer_length: int content_length: int def parse_su3_header(data: bytes) -> SU3Header | None: """Parse an SU3 file header from raw bytes. Returns None if the data is too short or has invalid magic. """ if len(data) < _MIN_HEADER_SIZE: return None # Check magic (first 6 bytes) if data[:6] != _SU3_MAGIC: return None # Parse header fields # Byte 6: unused # Byte 7: format version (currently 0) format_version = data[7] # Bytes 8-9: signature type (big-endian uint16) sig_type = struct.unpack(">H", data[8:10])[0] # Bytes 10-11: signature length sig_length = struct.unpack(">H", data[10:12])[0] # Byte 12: unused # Byte 13: content type content_type = data[13] if len(data) > 13 else 0 # Byte 14: unused # Byte 15: file type file_type = data[15] if len(data) > 15 else 0 # Byte 16: version string length version_length = data[16] if len(data) > 16 else 0 # Byte 17: unused # Byte 18: signer ID length signer_length = data[18] if len(data) > 18 else 0 # Bytes 20-27: content length (big-endian uint64) content_length = 0 if len(data) >= 28: content_length = struct.unpack(">Q", data[20:28])[0] return SU3Header( magic=_SU3_MAGIC.decode("ascii"), format_version=format_version, sig_type=sig_type, sig_length=sig_length, content_type=content_type, file_type=file_type, version_length=version_length, signer_length=signer_length, content_length=content_length, )