fixes global
This commit is contained in:
1 parent
13dc4f39c8
commit
cad0f6aebe
64 files changed
+10379
-254
No files matched your search
@@ -0,0 +1,54 @@
|
||||
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()
|
||||
@@ -0,0 +1,95 @@
|
||||
"""
|
||||
Модель данных EncryptedContent для хранения результата шифрования контента.
|
||||
|
||||
Полезно для сериализации, логирования и передачи между подсистемами uploader-bot.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class EncryptedContent:
|
||||
"""
|
||||
Универсальная переносимая модель зашифрованного контента.
|
||||
Все бинарные поля хранятся в Base64 (строки), чтобы быть JSON-совместимыми.
|
||||
"""
|
||||
content_id: str
|
||||
ciphertext_b64: str
|
||||
nonce_b64: str
|
||||
tag_b64: str
|
||||
|
||||
# Подпись и открытый ключ подписанта (Ed25519). Могут отсутствовать.
|
||||
signature: Optional[str] = None
|
||||
signer_pubkey: Optional[str] = None
|
||||
|
||||
# Пользовательские/системные метаданные (должны совпадать при верификации)
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
# Служебная метка времени создания структуры
|
||||
created_at: str = field(default_factory=lambda: datetime.utcnow().isoformat())
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Сериализация в словарь (JSON-совместимый).
|
||||
"""
|
||||
data = asdict(self)
|
||||
# Ничего дополнительно не преобразуем — все поля уже JSON-friendly
|
||||
return data
|
||||
|
||||
def to_json(self) -> str:
|
||||
"""
|
||||
Сериализация в JSON-строку.
|
||||
"""
|
||||
payload = self.to_dict()
|
||||
try:
|
||||
return json.dumps(payload, ensure_ascii=False, sort_keys=True)
|
||||
except Exception as e:
|
||||
logger.error(f"EncryptedContent.to_json serialization error: {e}")
|
||||
raise
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "EncryptedContent":
|
||||
"""
|
||||
Десериализация из словаря.
|
||||
"""
|
||||
required = ["content_id", "ciphertext_b64", "nonce_b64", "tag_b64"]
|
||||
for f in required:
|
||||
if f not in data:
|
||||
raise ValueError(f"Missing required field in EncryptedContent: {f}")
|
||||
|
||||
return cls(
|
||||
content_id=data["content_id"],
|
||||
ciphertext_b64=data["ciphertext_b64"],
|
||||
nonce_b64=data["nonce_b64"],
|
||||
tag_b64=data["tag_b64"],
|
||||
signature=data.get("signature"),
|
||||
signer_pubkey=data.get("signer_pubkey"),
|
||||
metadata=data.get("metadata", {}) or {},
|
||||
created_at=data.get("created_at") or datetime.utcnow().isoformat(),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_crypto_result(cls, crypto_result: Dict[str, Any]) -> "EncryptedContent":
|
||||
"""
|
||||
Удобный конструктор из результата ContentCipher.encrypt_content()
|
||||
"""
|
||||
return cls.from_dict(crypto_result)
|
||||
|
||||
# Вспомогательные методы для работы с бинарными данными (если необходимо)
|
||||
def ciphertext_bytes(self) -> bytes:
|
||||
return base64.b64decode(self.ciphertext_b64)
|
||||
|
||||
def nonce_bytes(self) -> bytes:
|
||||
return base64.b64decode(self.nonce_b64)
|
||||
|
||||
def tag_bytes(self) -> bytes:
|
||||
return base64.b64decode(self.tag_b64)
|
||||
Reference in new issue
Block a user