136 lines
4.1 KiB
Python
136 lines
4.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Independent B21 superblock ordering oracle."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
import struct
|
|
|
|
|
|
SUPER = 1024
|
|
PAGE_SHIFT = 12
|
|
FEATURE_COMPAT_SB_CHKSUM = 0x00000001
|
|
FEATURE_INCOMPAT_ALL = 0x000001FF
|
|
FEATURE_INCOMPAT_48BIT = 0x00000080
|
|
FEATURE_INCOMPAT_XATTR_PREFIXES = 0x00000040
|
|
CRC32C_POLY = 0x82F63B78
|
|
|
|
|
|
def crc32c(data: bytes | bytearray, seed: int = 0xFFFFFFFF) -> int:
|
|
value = seed
|
|
for byte in data:
|
|
value ^= byte
|
|
for _ in range(8):
|
|
value = (value >> 1) ^ (CRC32C_POLY if value & 1 else 0)
|
|
return value & 0xFFFFFFFF
|
|
|
|
|
|
def u16(data: bytes, offset: int) -> int:
|
|
return struct.unpack_from("<H", data, offset)[0]
|
|
|
|
|
|
def u32(data: bytes, offset: int) -> int:
|
|
return struct.unpack_from("<I", data, offset)[0]
|
|
|
|
|
|
def u64(data: bytes, offset: int) -> int:
|
|
return struct.unpack_from("<Q", data, offset)[0]
|
|
|
|
|
|
def checksum_valid(data: bytes) -> bool:
|
|
block_bits = data[SUPER + 12]
|
|
if not 9 <= block_bits <= PAGE_SHIFT:
|
|
return False
|
|
span = 1 << block_bits
|
|
if span > SUPER:
|
|
span -= SUPER
|
|
end = SUPER + span
|
|
expected = u32(data, SUPER + 4)
|
|
return expected == crc32c(data[SUPER + 8 : end], 0x5045B54A)
|
|
|
|
|
|
def classify(data: bytes) -> tuple[int, str]:
|
|
if u32(data, SUPER) != 0xE0F5E1E2:
|
|
return 22, "magic"
|
|
block_bits = data[SUPER + 12]
|
|
if not 9 <= block_bits <= PAGE_SHIFT:
|
|
return 22, "block-size"
|
|
feature_compat = u32(data, SUPER + 8)
|
|
if feature_compat & FEATURE_COMPAT_SB_CHKSUM and not checksum_valid(data):
|
|
return 97, "checksum"
|
|
if data[SUPER + 90] != 0:
|
|
return 45, "dirblkbits"
|
|
feature_incompat = u32(data, SUPER + 80)
|
|
if feature_incompat & ~FEATURE_INCOMPAT_ALL:
|
|
if u32(data, SUPER + 92) == 0xFFFFFFFF:
|
|
return 45, "feature-before-prefix"
|
|
return 45, "feature-incompat"
|
|
if 128 + data[SUPER + 13] * 16 > (1 << PAGE_SHIFT) - SUPER:
|
|
return 22, "super-extension"
|
|
blocks = u32(data, SUPER + 36)
|
|
root_nid_8b = u64(data, SUPER + 112)
|
|
if feature_incompat & FEATURE_INCOMPAT_48BIT and root_nid_8b != 0:
|
|
blocks |= u16(data, SUPER + 14) << 32
|
|
if blocks == 0:
|
|
return 97, "blocks-zero"
|
|
if blocks << block_bits > len(data):
|
|
return 6, "provider-size"
|
|
if (
|
|
feature_incompat & FEATURE_INCOMPAT_XATTR_PREFIXES
|
|
and data[SUPER + 91] != 0
|
|
and u32(data, SUPER + 92) == 0xFFFFFFFF
|
|
):
|
|
return 97, "prefix-offset"
|
|
return 0, "accepted"
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--fixtures", type=Path, required=True)
|
|
parser.add_argument("--report", type=Path, required=True)
|
|
args = parser.parse_args()
|
|
index = json.loads(
|
|
(args.fixtures / "fixture-index.json").read_text(encoding="ascii")
|
|
)
|
|
results = []
|
|
for item in index["cases"]:
|
|
path = args.fixtures / item["filename"]
|
|
actual_errno, actual_reject = classify(path.read_bytes())
|
|
passed = (
|
|
actual_errno == item["expected_errno"]
|
|
and actual_reject == item["expected_reject"]
|
|
)
|
|
results.append(
|
|
{
|
|
"actual_errno": actual_errno,
|
|
"actual_reject": actual_reject,
|
|
"expected_errno": item["expected_errno"],
|
|
"expected_reject": item["expected_reject"],
|
|
"id": item["id"],
|
|
"passed": passed,
|
|
}
|
|
)
|
|
report = {
|
|
"batch": "B21",
|
|
"case_count": len(results),
|
|
"passed_count": sum(item["passed"] for item in results),
|
|
"results": results,
|
|
"schema": 1,
|
|
"status": "PASS" if all(item["passed"] for item in results) else "FAIL",
|
|
}
|
|
args.report.write_text(
|
|
json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="ascii"
|
|
)
|
|
for item in results:
|
|
print(
|
|
f"{'PASS' if item['passed'] else 'FAIL'} {item['id']} "
|
|
f"errno={item['actual_errno']} reject={item['actual_reject']}"
|
|
)
|
|
return 0 if report["status"] == "PASS" else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|