0922cc1d68
- Struktur gemäß §8 (Eigentumsgrenzen), PLAN.md als normative Basis - Pflichtdokumente: STATUS.md, ERRORS.md, TEST_REPORT.md, CHANGELOG.md, ADRs - ADR-0001 Python 3.13-Pin, ADR-0002 GStreamer 1.28.6-Pin (Windows), ADR-0003 IPC TCP+MessagePack v1 - Kernpakete: hms_protocol, hms_domain, hms_parameter, hms_artnet, hms_adaptive, hms_capabilities, hms_plugin_sdk - Renderer-Spike: D3D11-Primärpfad + Dev-GL-Pfad (§36 Nr. 4-5) - Control Core: FastAPI REST + WebSocket (§36 Nr. 9) - Beispielplugins: Passthrough + Gaussian Blur (3 Adaptive-Quality- Varianten, HLSL/GLSL/GLES) - Tools: Art-Net-Emulator, Fixture-Generator (Master32/Layer64-CSV), Capability-Probe - JSON-Schemas: IPC, Plugin, Projekt, Cluster - 121 Unit-/Integrationstests grün, Ruff grün Gate 0 bleibt offen: Hardwaremessungen nur auf echter Windows-Referenz- hardware gültig (§29.7, §33).
62 lines
2.0 KiB
Python
62 lines
2.0 KiB
Python
"""Length-prefixed MessagePack-Framing (ADR-0003).
|
|
|
|
4-Byte-Big-Endian-Länge, danach MessagePack-Payload. Maximale Payloadgröße
|
|
schützt vor unkontrollierten Queues/Resourcenerschöpfung (§6.2, §33).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import socket
|
|
import struct
|
|
|
|
import msgpack
|
|
|
|
MAX_PAYLOAD_SIZE = 16 * 1024 * 1024 # 16 MiB Obergrenze je Nachricht
|
|
_LENGTH = struct.Struct(">I")
|
|
|
|
|
|
def encode_frame(payload: dict) -> bytes:
|
|
"""Serialisiert ein dict zu length-prefixed MessagePack."""
|
|
body = msgpack.packb(payload, use_bin_type=True)
|
|
if len(body) > MAX_PAYLOAD_SIZE:
|
|
raise ValueError(f"payload too large: {len(body)} > {MAX_PAYLOAD_SIZE}")
|
|
return _LENGTH.pack(len(body)) + body
|
|
|
|
|
|
def decode_frame(frame: bytes) -> dict:
|
|
"""Dekodiert einen vollständigen Frame (Länge + Body)."""
|
|
if len(frame) < _LENGTH.size:
|
|
raise ValueError("frame too short")
|
|
(length,) = _LENGTH.unpack_from(frame, 0)
|
|
if length > MAX_PAYLOAD_SIZE:
|
|
raise ValueError(f"declared length {length} exceeds limit")
|
|
body = frame[_LENGTH.size : _LENGTH.size + length]
|
|
if len(body) != length:
|
|
raise ValueError(f"truncated frame: expected {length}, got {len(body)}")
|
|
return msgpack.unpackb(body, raw=False)
|
|
|
|
|
|
def read_frame(sock: socket.socket) -> dict:
|
|
"""Liest einen Frame von einem verbundenen Socket."""
|
|
header = _recv_exact(sock, _LENGTH.size)
|
|
(length,) = _LENGTH.unpack(header)
|
|
if length > MAX_PAYLOAD_SIZE:
|
|
raise ValueError(f"declared length {length} exceeds limit")
|
|
body = _recv_exact(sock, length)
|
|
return msgpack.unpackb(body, raw=False)
|
|
|
|
|
|
def write_frame(sock: socket.socket, payload: dict) -> None:
|
|
"""Schreibt einen Frame auf einen verbundenen Socket."""
|
|
sock.sendall(encode_frame(payload))
|
|
|
|
|
|
def _recv_exact(sock: socket.socket, count: int) -> bytes:
|
|
buf = bytearray()
|
|
while len(buf) < count:
|
|
chunk = sock.recv(count - len(buf))
|
|
if not chunk:
|
|
raise ConnectionError("socket closed mid-frame")
|
|
buf.extend(chunk)
|
|
return bytes(buf)
|