#!/usr/bin/env python3 """Inspect SERiF archive data without compiling SERiF or installing dependencies. This is a data reader, not a physics/model factory. The C++ loader additionally checks SERiF version tolerance, component schemas and discretization compatibility. """ import argparse import csv import json import shlex import struct import sys def fingerprint(data): """Match the archive's versioned corruption-detection fingerprint.""" value = 14695981039346656037 for byte in data: value = ((value ^ byte) * 1099511628211) & ((1 << 64) - 1) return f"fnv1a64-v1:{value:016x}" def unquote(data): """Decode std::quoted text, whose escapes differ from JSON escapes.""" if len(data) < 2 or data[0] != 34 or data[-1] != 34: raise ValueError("Invalid quoted string") result = bytearray() index = 1 while index < len(data) - 1: if data[index] == 92: index += 1 if index >= len(data) - 1: raise ValueError("Unfinished string escape") elif data[index] == 34: raise ValueError("Unescaped quote inside string") result.append(data[index]) index += 1 return bytes(result) class Archive: """Read the ledger once and seek directly to individual data sections.""" def __init__(self, path, maximum_bytes=1024 * 1024 * 1024): self.file = open(path, "rb") self.maximum_bytes = maximum_bytes try: self.file.seek(0, 2) size = self.file.tell() self.file.seek(0) header = self.file.read(512).decode("ascii") lines = header.split("END_HEADER\n", 1)[0].splitlines() if not lines or lines[0] != "SERIF_ARCHIVE 1": raise ValueError("Unsupported SERiF archive format") self.header = dict(shlex.split(line) for line in lines[1:]) offset = int(self.header["ledger_offset"]) count = int(self.header["ledger_bytes"]) if (offset != 512 or count < 0 or count > min(maximum_bytes, 64 * 1024 * 1024) or offset + count > size or int(self.header["file_bytes"]) != size or int(self.header["data_offset"]) != offset + count): raise ValueError("Invalid archive section bounds") ledger = self.file.read(count) if fingerprint(ledger) != self.header["ledger_checksum"]: raise ValueError("Damaged archive ledger") self.entries = {} previous_end = offset + count previous_path = "" for line in ledger.decode("utf-8").splitlines()[1:]: path, schema, version, kind, extent, start, length, checksum = shlex.split(line) entry = dict(schema=schema, version=int(version), kind=int(kind), count=int(extent), offset=int(start), bytes=int(length), checksum=checksum) if path <= previous_path or not 0 <= entry["kind"] <= 8 or entry["count"] < 0: raise ValueError("Invalid or duplicate ledger entry") if entry["kind"]: if (entry["offset"] < previous_end or entry["bytes"] < 0 or entry["bytes"] > maximum_bytes or entry["offset"] + entry["bytes"] > size): raise ValueError("Invalid or overlapping payload bounds") previous_end = entry["offset"] + entry["bytes"] elif entry["offset"] or entry["bytes"] or entry["count"]: raise ValueError("Directory contains a payload") self.entries[path] = entry previous_path = path if len(self.entries) != int(self.header["entries"]): raise ValueError("Incorrect ledger extent") if self.header["format"] not in ("binary", "text"): raise ValueError("Unknown numeric encoding") except Exception: self.file.close() raise def __enter__(self): """Allow a with-statement to own the open file.""" return self def __exit__(self, *_): """Close the reader after analysis, including when decoding fails.""" self.file.close() def read(self, path): """Return one string, number or numerical array without reading sibling fields.""" entry = self.entries[path] kind, count = entry["kind"], entry["count"] self.file.seek(entry["offset"]) data = self.file.read(entry["bytes"]) if len(data) != entry["bytes"] or fingerprint(data) != entry["checksum"]: raise ValueError(f"Damaged payload: {path}") binary = self.header["format"] == "binary" if kind == 0: return None if kind == 1: allowed = (b"\x00", b"\x01") if binary else (b"0", b"1") if count != 1 or data not in allowed: raise ValueError("Invalid boolean") return data == allowed[1] if kind == 5: text = data if binary else unquote(data) if len(text) != count: raise ValueError("Invalid string length") return text.decode("utf-8", errors="surrogateescape") if kind in (2, 3, 4) and count != 1: raise ValueError("Invalid scalar extent") if count > len(data) or count > self.maximum_bytes // 8: raise ValueError("Invalid numerical extent") code = {2: "q", 3: "Q", 4: "d", 6: "d", 7: "q", 8: "Q"}[kind] if binary: if len(data) != 8 * count: raise ValueError("Invalid binary array length") values = list(struct.unpack(f"<{count}{code}", data)) else: tokens = data.decode("ascii").split() if len(tokens) != count: raise ValueError("Invalid text array length") values = [] for token in tokens: if code == "d": value = struct.unpack("