"""@file firmware.py @brief Проверка образов и переносимая машина блочной передачи прошивки. Реализует загрузку и валидацию образов прошивки из различных форматов файлов, проверку целостности данных, и логику передачи образов на устройство. """ from __future__ import annotations from dataclasses import dataclass from pathlib import Path from firmware_image.core import FirmwareImageError, contiguous_image, image_digest @dataclass(frozen=True) class FirmwareImage: """@brief Загруженный образ прошивки с метаданными. @param path Путь к исходному файлу образа @param data Бинарные данные образа @param base_address Адрес начала загрузки (для Intel HEX формата) @param version Номер версии прошивки """ path: Path data: bytes base_address: int = 0 version: int = 0 @property def crc32(self) -> int: return int(image_digest(self.data)[0], 16) @property def sha256(self) -> str: return image_digest(self.data)[1].lower() @classmethod def load(cls, path: str | Path, maximum_size: int = 2 * 1024 * 1024) -> "FirmwareImage": file_path = Path(path) raw = file_path.read_bytes() if file_path.suffix.lower() == ".hex": data, base = _parse_intel_hex(raw, maximum_size) elif file_path.suffix.lower() == ".bin": data, base = raw, 0 else: raise FirmwareImageError("Поддерживаются только .bin и Intel HEX") if not data: raise FirmwareImageError("Образ пуст") if len(data) > maximum_size: raise FirmwareImageError(f"Размер образа превышает {maximum_size} байт") return cls(file_path, data, base) def _parse_intel_hex(text: str, maximum_size=2 * 1024 * 1024) -> tuple[bytes, int]: return contiguous_image(text, maximum_size) @dataclass class FirmwareTransfer: image: FirmwareImage block_size: int = 480 offset: int = 0 def next_block(self) -> tuple[int, bytes] | None: if self.offset >= len(self.image.data): return None offset = self.offset return offset, self.image.data[offset : offset + self.block_size] def acknowledge(self, next_offset: int) -> None: current = self.next_block() if current is None or next_offset != current[0] + len(current[1]): raise FirmwareImageError("МК подтвердил неожиданный offset") self.offset = next_offset @property def percent(self) -> int: return int(self.offset * 100 / len(self.image.data))