Files
HMS MediaEngine Agent 362e089be0 AUFGERAUMT: Root auf 10 sichtbare Elemente reduziert
Der Nutzer hat recht: Der Ordner war voller Entwicklungs-Muell.
Jetzt ist sauber getrennt:

ROOT (was der Nutzer sieht und braucht):
- run.py                     = das Programm
- hms_app/                   = der Anwendungscode
- HMS MediaEngine.app        = macOS Doppelklick-Starter
- HMS-Start.vbs              = Windows Doppelklick-Starter
- HMS-Install.vbs             = Windows Erst-Installation
- HMS-Mac-Install.command     = macOS Homebrew-Installation
- HMS-Portable-Install.command = macOS Portable-Installation (16GB-Fix)
- installer_gui.py           = grafischer Installer
- launcher.pyw + launcher_core.py = interne Start-Logik
- LIESMICH.txt               = 10-Zeilen-Kurzanleitung
- .gitignore

_entwicklung/ (alles andere, NICHT benoetigt):
- packages/ apps/ native/ plugins/ tools/ schemas/ tests/ docs/
  build/ fixture_profiles/
- PLAN.md STATUS.md ERRORS.md TEST_REPORT.md CHANGELOG.md README.md
- pyproject.toml uv.lock setup_*.sh/ps1 make_mac_app.py

Diese Trennung gilt ab sofort fuer alle Commits. Der Nutzer kann
_entwicklung/ loeschen wenn er Platz braucht - die App laeuft ohne.

Verifiziert: App startet nach Aufraeumen unveraendert (Health 200).
2026-09-11 23:44:06 +02:00

73 lines
2.5 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 asyncio
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)
async def read_frame_async(reader: asyncio.StreamReader) -> dict:
"""Liest einen Frame von einem asyncio-StreamReader (IPC-Server/Client)."""
header = await reader.readexactly(_LENGTH.size)
(length,) = _LENGTH.unpack(header)
if length > MAX_PAYLOAD_SIZE:
raise ValueError(f"declared length {length} exceeds limit")
body = await reader.readexactly(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)