from __future__ import annotations import base64 import hashlib import logging from dataclasses import dataclass, field, asdict from datetime import datetime from typing import Optional logger = logging.getLogger(__name__) @dataclass class ContentChunk: """ Модель чанка зашифрованного контента. Все бинарные поля представлены в base64-строках для JSON-совместимости. - chunk_hash: HEX(SHA-256(raw_encrypted_chunk_bytes)) — для дедупликации - signature: base64-encoded Ed25519 подпись структуры чанка (детали в ChunkManager) """ chunk_id: str content_id: str chunk_index: int chunk_hash: str # hex sha256(raw encrypted data) encrypted_data: str # base64 signature: Optional[str] = None created_at: str = field(default_factory=lambda: datetime.utcnow().isoformat()) def to_dict(self) -> dict: return asdict(self) @classmethod def from_dict(cls, data: dict) -> "ContentChunk": required = ["chunk_id", "content_id", "chunk_index", "chunk_hash", "encrypted_data"] for f in required: if f not in data: raise ValueError(f"Missing required field in ContentChunk: {f}") return cls( chunk_id=data["chunk_id"], content_id=data["content_id"], chunk_index=int(data["chunk_index"]), chunk_hash=data["chunk_hash"], encrypted_data=data["encrypted_data"], signature=data.get("signature"), created_at=data.get("created_at") or datetime.utcnow().isoformat(), ) def encrypted_bytes(self) -> bytes: return base64.b64decode(self.encrypted_data) @staticmethod def compute_sha256_hex(buf: bytes) -> str: return hashlib.sha256(buf).hexdigest()