65 lines
2.1 KiB
Python
65 lines
2.1 KiB
Python
"""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)
|