"""Renderer-independent comparison of arbitrary binary files.""" from __future__ import annotations from dataclasses import dataclass import hashlib def compare_sha256(image: bytes, readback: bytes): """Compare a complete physical readback against an immutable BIN snapshot.""" if not image or len(image) != len(readback): raise ValueError('Для SHA-256 нужен полный прочитанный диапазон размером с BIN.') expected = hashlib.sha256(image).hexdigest() actual = hashlib.sha256(readback).hexdigest() return expected == actual, expected, actual @dataclass(frozen=True) class Comparison: left: bytes right: bytes offsets: tuple[int, ...] changed_bits: int @property def changed_bytes(self) -> int: return len(self.offsets) @property def size(self) -> int: return max(len(self.left), len(self.right)) def compare(left: bytes, right: bytes) -> Comparison: """Count bit differences in overlap; unmatched bytes count as changed bytes.""" common = min(len(left), len(right)) offsets = tuple(i for i in range(common) if left[i] != right[i]) bits = sum(bin(left[i] ^ right[i]).count('1') for i in offsets) return Comparison(left, right, offsets + tuple(range(common, max(len(left), len(right)))), bits) @dataclass(frozen=True) class MultiComparison: images: tuple[bytes, ...] offsets: tuple[int, ...] pairs: tuple @property def size(self): return max(map(len, self.images)) @property def changed_bytes(self): return len(self.offsets) def compare_many(images): """Highlight offsets where any pair differs, including missing bytes.""" images = tuple(images) if len(images) not in (1, 2, 3): raise ValueError('Выберите от 1 до 3 BIN-файлов.') if len(images) == 2: return compare(*images) pairs = tuple((a, b, compare(images[a], images[b])) for a in range(len(images)) for b in range(a + 1, len(images))) offsets = tuple(sorted({offset for _, _, result in pairs for offset in result.offsets})) return MultiComparison(images, offsets, pairs)