#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-only
"""Execute unchanged Linux verifier control flow with an explicit checksum stub."""
import argparse
import base64
import datetime
import hashlib
import json
import pathlib
import platform
import subprocess
import tempfile
import urllib.request

COMMIT = "adc218676eef25575469234709c2d87185ca223a"
PATHS = ("block/t10-pi.c", "include/linux/t10-pi.h")
HERE = pathlib.Path(__file__).resolve().parent
EXPECTED = {
    PATHS[0]: "b06dfd2d5f8f2dfd993c6e61e1e77ba51a4b6b26d10f080bf32889df1d2d5c3d",
    PATHS[1]: "1705a7647736980907b030a21cda7240b43695e6f6bcbcd81b92c99a28bde9b4",
}


def digest(data):
    return hashlib.sha256(data).hexdigest()


def extract(text, prefix, structure=False):
    start = text.index(prefix)
    end = text.index("{", start) + 1
    depth = 1
    while depth:
        depth += (text[end] == "{") - (text[end] == "}")
        end += 1
    if structure:
        if text[end] != ";":
            raise ValueError("Unexpected structure terminator")
        end += 1
    return text[start:end] + "\n"


def acquire(path, directory):
    target = directory / path
    if not target.exists():
        url = f"https://api.github.com/repos/torvalds/linux/contents/{path}?ref={COMMIT}"
        req = urllib.request.Request(url, headers={"User-Agent": "Hesela-source-audit"})
        with urllib.request.urlopen(req, timeout=60) as response:
            record = json.load(response)
        payload = base64.b64decode(record["content"], validate=False)
        target.parent.mkdir(parents=True, exist_ok=True)
        target.write_bytes(payload)
    data = target.read_bytes()
    if digest(data) != EXPECTED[path]:
        raise ValueError(f"Source hash mismatch: {path}")
    return data


EXPECTED_CASES = {
    "ordinary-valid": ("accepted", 1),
    "ordinary-bad-guard": ("protection-error", 1),
    "ordinary-bad-reference": ("protection-error", 0),
    "escaped-bad-guard-and-reference": ("accepted", 0),
    "near-escape-bad-guard": ("protection-error", 1),
    "no-reference-check": ("accepted", 1),
    "no-ref-app-escape-only": ("protection-error", 1),
    "no-ref-both-escapes": ("accepted", 0),
    "reference-wrap-alias": ("accepted", 1),
    "escaped-then-bad-guard": ("protection-error", 1),
    "escaped-then-valid": ("accepted", 1),
    "bad-guard-then-escaped": ("protection-error", 1),
    "metadata-prefix-valid": ("accepted", 2),
    "metadata-prefix-bad-guard": ("protection-error", 2),
    "metadata-prefix-escaped": ("accepted", 0),
    "metadata-prefix-two-blocks": ("accepted", 4),
}


def validate_rows(rows):
    """Fail closed even with python -O; do not trust a fixture's success flag."""
    if len(rows) != len(EXPECTED_CASES):
        raise ValueError("Unexpected fixture count")
    seen = set()
    for row in rows:
        if not isinstance(row, dict):
            raise ValueError("Fixture must be an object")
        name = row.get("id")
        if not isinstance(name, str) or name not in EXPECTED_CASES or name in seen:
            raise ValueError("Unknown or duplicate fixture")
        seen.add(name)
        if (row.get("matched_expected") is not True
                or type(row.get("checksumCalls")) is not int
                or (row.get("status"), row.get("checksumCalls")) != EXPECTED_CASES[name]):
            raise ValueError(f"Fixture outcome mismatch: {name}")


def run(source_dir, cc, sanitize):
    sources = {path: acquire(path, source_dir) for path in PATHS}
    code = sources[PATHS[0]].decode()
    header = sources[PATHS[1]].decode()
    for text, expected in [
        (code, "SPDX-License-Identifier: GPL-2.0"),
        (header, "#define T10_PI_APP_ESCAPE cpu_to_be16(0xffff)"),
        (header, "#define T10_PI_REF_ESCAPE cpu_to_be32(0xffffffff)"),
    ]:
        if expected not in text:
            raise ValueError(f"Required source marker missing: {expected}")
    pieces = [
        extract(code, "struct blk_integrity_iter", True),
        extract(header, "struct t10_pi_tuple", True),
        extract(code, "static blk_status_t t10_pi_verify"),
    ]
    shim = (HERE / "shim.c").read_text()
    tests = (HERE / "cases.c").read_text()
    flags = ["-std=gnu11", "-Wall", "-Wextra", "-Werror", "-O1"]
    if sanitize:
        flags += ["-fsanitize=address,undefined", "-fno-omit-frame-pointer"]
    with tempfile.TemporaryDirectory(prefix="hesela-pi-") as tmp:
        source = pathlib.Path(tmp) / "probe.c"
        binary = pathlib.Path(tmp) / "probe"
        source.write_text(shim + "\n" + "\n".join(pieces) + tests)
        subprocess.run([cc, *flags, str(source), "-o", str(binary)], check=True)
        process = subprocess.run([str(binary)], check=True, text=True, capture_output=True)
    rows = [json.loads(line) for line in process.stdout.splitlines()]
    validate_rows(rows)
    return {
        "schemaVersion": 1,
        "evidenceType": "extracted-source-control-flow-with-checksum-stub",
        "generatedAt": datetime.datetime.now(datetime.timezone.utc).isoformat(),
        "sourceVersion": "Linux v6.12",
        "sourceCommit": COMMIT,
        "sources": [{
            "path": path,
            "url": f"https://github.com/torvalds/linux/blob/{COMMIT}/{path}",
            "sha256": digest(data),
            "license": "GPL-2.0 per source SPDX identifier",
        } for path, data in sources.items()],
        "verifierSha256": digest(pieces[2].encode()),
        "artifactSha256": {name: digest((HERE / name).read_bytes()) for name in
                           ["runner.py", "shim.c", "cases.c"]},
        "environment": {"platform": platform.platform(), "python": platform.python_version(),
                        "compiler": subprocess.check_output([cc, "--version"], text=True).splitlines()[0],
                        "flags": flags},
        "method": "Extract unchanged t10_pi_verify and the two source structures; compile with minimal user-space types and a fixed-value checksum stub. Count checksum calls. No device I/O.",
        "limits": [
            "Control-flow test, not a running-kernel, controller, NVMe command or hardware experiment.",
            "Checksum stub returns 0x1234 and does not compute CRC or IP checksums.",
            "Only 8-byte PI fields, 512-byte data intervals, pi_offset=0 or 8 and one or two intervals.",
            "Flag constants and status codes are shim-local, not an ABI test.",
            "No CRC64 PI, command masks, remapping, DMA, concurrency, crash or fault-rate claims.",
        ],
        "cases": rows,
    }


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--source-dir", type=pathlib.Path, required=True,
                        help="Cache for pinned sources; may be pre-populated offline")
    parser.add_argument("--cc", default="cc")
    parser.add_argument("--sanitize", action="store_true")
    parser.add_argument("--output", type=pathlib.Path, required=True)
    args = parser.parse_args()
    result = run(args.source_dir, args.cc, args.sanitize)
    args.output.parent.mkdir(parents=True, exist_ok=True)
    args.output.write_text(json.dumps(result, indent=2) + "\n")
    print(f"{len(result['cases'])} cases passed; {result['evidenceType']}")


if __name__ == "__main__":
    main()
