zupt/tests/archive_surgery.py
2026-08-31 14:14:36 -03:00

275 lines
9.4 KiB
Python

#!/usr/bin/env python3
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Strict structural mutations used by archive-authentication tests."""
import argparse
import pathlib
import sys
ARCHIVE_HEADER_SIZE = 64
FOOTER_SIZE = 32
AIT_SIZE = 32
BLOCK_DATA = 0x00
BLOCK_INDEX = 0x02
BLOCK_ENC_HEADER = 0x03
BLOCK_DEDUP_REF = 0x04
BLOCK_COMMENT = 0x05
BLOCK_FLAG_ENCRYPTED = 0x01
class ArchiveError(ValueError):
pass
def _u16le(data, offset):
return int.from_bytes(data[offset:offset + 2], "little")
def _u64le(data, offset):
return int.from_bytes(data[offset:offset + 8], "little")
def _read_varint(data, offset, limit):
value = 0
for byte_number in range(10):
if offset >= limit:
raise ArchiveError("truncated varint")
byte = data[offset]
offset += 1
if byte_number == 9 and byte > 1:
raise ArchiveError("varint exceeds uint64")
value |= (byte & 0x7f) << (7 * byte_number)
if (byte & 0x80) == 0:
if byte_number and value < (1 << (7 * byte_number)):
raise ArchiveError("non-canonical varint")
return value, offset
raise ArchiveError("unterminated varint")
def _parse_frame(data, offset, limit):
start = offset
if limit - offset < 17:
raise ArchiveError("truncated block header")
if data[offset:offset + 2] != b"\xbb\x01":
raise ArchiveError("invalid block magic")
block_type = data[offset + 2]
codec = _u16le(data, offset + 3)
flags = _u16le(data, offset + 5)
offset += 7
uncompressed_size, offset = _read_varint(data, offset, limit)
compressed_size, offset = _read_varint(data, offset, limit)
if limit - offset < 8:
raise ArchiveError("truncated block checksum")
checksum = _u64le(data, offset)
payload_start = offset + 8
if compressed_size > limit - payload_start:
raise ArchiveError("block payload exceeds structural boundary")
end = payload_start + compressed_size
return {
"type": block_type,
"codec": codec,
"flags": flags,
"uncompressed_size": uncompressed_size,
"compressed_size": compressed_size,
"checksum": checksum,
"start": start,
"payload_start": payload_start,
"end": end,
}
def _parse_current_archive(data):
minimum = ARCHIVE_HEADER_SIZE + FOOTER_SIZE + AIT_SIZE
if len(data) < minimum:
raise ArchiveError("archive is too short")
if data[:6] != b"ZUPT\x1a\x00":
raise ArchiveError("invalid archive magic")
footer_start = len(data) - FOOTER_SIZE - AIT_SIZE
if data[footer_start + 24:footer_start + 28] != b"ZEND":
raise ArchiveError("current footer before AIT not found")
if int.from_bytes(data[footer_start + 28:footer_start + 32],
"little") != 1:
raise ArchiveError("unsupported footer version")
index_offset = _u64le(data, footer_start)
if index_offset < ARCHIVE_HEADER_SIZE or index_offset >= footer_start:
raise ArchiveError("index offset is outside the archive body")
frames = []
offset = ARCHIVE_HEADER_SIZE
while offset < index_offset:
frame = _parse_frame(data, offset, index_offset)
if frame["type"] == BLOCK_INDEX:
raise ArchiveError("index frame occurs before footer index offset")
frames.append(frame)
offset = frame["end"]
if offset != index_offset:
raise ArchiveError("archive body does not end at index offset")
index = _parse_frame(data, index_offset, footer_start)
if index["type"] != BLOCK_INDEX:
raise ArchiveError("footer does not point to an index frame")
if index["end"] != footer_start:
raise ArchiveError("bytes remain between index and footer")
return {
"frames": frames,
"index": index,
"footer_start": footer_start,
}
def _kind_value(name):
return {"data": BLOCK_DATA, "enc": BLOCK_ENC_HEADER,
"ref": BLOCK_DEDUP_REF}[name]
def _matching_frames(layout, kind, require_encrypted):
matches = [frame for frame in layout["frames"]
if frame["type"] == _kind_value(kind)]
if require_encrypted:
matches = [frame for frame in matches
if frame["flags"] & BLOCK_FLAG_ENCRYPTED]
return matches
def _same_metadata(left, right):
fields = ("type", "codec", "flags", "uncompressed_size",
"compressed_size", "checksum")
return all(left[field] == right[field] for field in fields)
def _select_equal_length_pair(frames, same_metadata):
for index, left in enumerate(frames):
for right in frames[index + 1:]:
if left["end"] - left["start"] != right["end"] - right["start"]:
continue
if same_metadata and not _same_metadata(left, right):
continue
return left, right
qualifier = " with identical metadata" if same_metadata else ""
raise ArchiveError("no two equal-length frames" + qualifier)
def _write(destination, data):
pathlib.Path(destination).write_bytes(data)
def command_strip_ait(args):
data = pathlib.Path(args.source).read_bytes()
layout = _parse_current_archive(data)
_write(args.destination, data[:layout["footer_start"] + FOOTER_SIZE])
def command_flip_payload(args):
data = bytearray(pathlib.Path(args.source).read_bytes())
layout = _parse_current_archive(data)
frames = _matching_frames(layout, args.kind, args.require_encrypted)
if not frames:
raise ArchiveError("requested frame was not found")
frame = frames[0]
if frame["compressed_size"] == 0:
raise ArchiveError("requested frame has no payload")
position = frame["payload_start"] + frame["compressed_size"] // 2
data[position] ^= 0x01
_write(args.destination, data)
def command_swap_frames(args):
data = bytearray(pathlib.Path(args.source).read_bytes())
layout = _parse_current_archive(data)
frames = _matching_frames(layout, args.kind, args.require_encrypted)
left, right = _select_equal_length_pair(frames, args.same_metadata)
left_bytes = bytes(data[left["start"]:left["end"]])
right_bytes = bytes(data[right["start"]:right["end"]])
if left_bytes == right_bytes:
raise ArchiveError("selected frames are byte-identical; swap is a no-op")
data[left["start"]:left["end"]] = right_bytes
data[right["start"]:right["end"]] = left_bytes
_write(args.destination, data)
def command_replay_frame(args):
data = bytearray(pathlib.Path(args.source).read_bytes())
layout = _parse_current_archive(data)
frames = _matching_frames(layout, args.kind, args.require_encrypted)
source, destination = _select_equal_length_pair(frames,
args.same_metadata)
replay = bytes(data[source["start"]:source["end"]])
if replay == bytes(data[destination["start"]:destination["end"]]):
raise ArchiveError("selected frames are already byte-identical")
data[destination["start"]:destination["end"]] = replay
_write(args.destination, data)
def command_preface_positions(args):
data = pathlib.Path(args.source).read_bytes()
layout = _parse_current_archive(data)
for frame in layout["frames"] + [layout["index"]]:
for position in range(frame["start"], frame["payload_start"]):
print(position)
def command_set_frame_type(args):
data = bytearray(pathlib.Path(args.source).read_bytes())
layout = _parse_current_archive(data)
frames = _matching_frames(layout, args.kind, args.require_encrypted)
if not frames:
raise ArchiveError("requested frame was not found")
replacement = {"data": BLOCK_DATA, "comment": BLOCK_COMMENT}[args.type]
data[frames[0]["start"] + 2] = replacement
_write(args.destination, data)
def _add_frame_options(parser):
parser.add_argument("source")
parser.add_argument("destination")
parser.add_argument("--kind", choices=("data", "enc", "ref"), required=True)
parser.add_argument("--require-encrypted", action="store_true")
def main():
parser = argparse.ArgumentParser()
commands = parser.add_subparsers(dest="command")
strip_ait = commands.add_parser("strip-ait")
strip_ait.add_argument("source")
strip_ait.add_argument("destination")
strip_ait.set_defaults(function=command_strip_ait)
flip = commands.add_parser("flip-payload")
_add_frame_options(flip)
flip.set_defaults(function=command_flip_payload)
swap = commands.add_parser("swap-frames")
_add_frame_options(swap)
swap.add_argument("--same-metadata", action="store_true")
swap.set_defaults(function=command_swap_frames)
replay = commands.add_parser("replay-frame")
_add_frame_options(replay)
replay.add_argument("--same-metadata", action="store_true")
replay.set_defaults(function=command_replay_frame)
prefaces = commands.add_parser("preface-positions")
prefaces.add_argument("source")
prefaces.set_defaults(function=command_preface_positions)
set_type = commands.add_parser("set-frame-type")
_add_frame_options(set_type)
set_type.add_argument("--type", choices=("data", "comment"), required=True)
set_type.set_defaults(function=command_set_frame_type)
args = parser.parse_args()
if not hasattr(args, "function"):
parser.error("a mutation command is required")
try:
args.function(args)
except (ArchiveError, OSError) as error:
parser.error(str(error))
if __name__ == "__main__":
sys.exit(main())