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)
|