====== std.zstd — Zstandard-Kompression und -Dekompression ====== ''std.zstd'' implementiert Zstandard (RFC 8878) vollständig in Lyx — keine externen Abhängigkeiten. Der Dekompressionspfad unterstützt den vollständigen Zstd-Frame-Aufbau mit FSE-codierten Sequenzen und Huffman-codierten Literalen; der Kompressor arbeitet im Store-Modus (gültige RFC-8878-Frames, keine LZ77-Reduktion). → [[lyx_-_programmiersprache:units|Standard Library]] · [[lyx_-_programmiersprache:units:zlib|std.zlib]] · [[lyx_-_programmiersprache:units:brotli|std.brotli]] · [[lyx_-_programmiersprache:units:archiv|Archiv-Übersicht]] **Wann Zstd?** Zstd ist das Kompressionsformat der modernen Server-Infrastruktur: Linux-Kernel-Module, Docker-Image-Layers, Kafka-Nachrichten, npm-Pakete und .tar.zst-Archive nutzen Zstd. ''std.zstd'' ist der richtige Einstiegspunkt wenn Zstd-komprimierte Daten dekomprimiert werden müssen — z.B. Streams aus Kafka, Backup-Dateien oder Systemarchive. Für das Erzeugen komprimierter Inhalte ist der Store-Kompressor ein gültiger Platzhalter; echte Kompression folgt in einer späteren Version. ---- ===== Konstanten ===== ^ Konstante ^ Wert ^ Bedeutung ^ | ''ZSTD_OK'' | 0 | Kein Fehler | | ''ZSTD_ERR'' | -1 | Allgemeiner Fehler (kein Zstd-Frame, korrupter Stream, nicht unterstütztes Feature) | | ''ZSTD_OVERFLOW'' | -2 | Ausgabepuffer zu klein | ---- ===== Funktionen ===== ^ Funktion ^ Rückgabe ^ Beschreibung ^ | ''ZstdDecompress(src, srcLen, dst, dstMax)'' | ''int64'' Bytes oder ''ZSTD_ERR'' / ''ZSTD_OVERFLOW'' | Dekomprimiert einen Zstd-Frame vollständig | | ''ZstdCompress(src, srcLen, dst, dstMax)'' | ''int64'' Bytes oder ''ZSTD_ERR'' / ''ZSTD_OVERFLOW'' | Erzeugt einen gültigen Zstd-Frame (Store-Modus) | Beide Funktionen arbeiten ohne interne Allokation des Ausgabepuffers — der Aufrufer stellt ''dst'' und ''dstMax'' bereit. ---- ===== ZstdDecompress ===== Dekomprimiert einen vollständigen Zstd-Frame (RFC 8878). Unterstützt: * **Raw-Blöcke** (unkomprimierte Rohdaten) * **RLE-Blöcke** (ein Byte, n-fach wiederholt) * **Komprimierte Blöcke** mit FSE-codierten Sequenzen (Literal Length / Match Length / Offset) und Huffman-codierten Literalen (Single-Stream und 4-Stream-interleaved) * **Frame Header** aller Varianten: Window Descriptor, Dictionary ID (alle Feldgrößen), Content Size (1/2/4/8 Bytes) * **Vordefinierte FSE-Tabellen** für LL, ML und Offset (RFC 8878 Anhang B) Wenn der Frame-Header eine bekannte unkomprimierte Größe enthält und diese ''dstMax'' übersteigt, gibt die Funktion sofort ''ZSTD_OVERFLOW'' zurück — ohne den gesamten Stream zu durchlaufen. import std.zstd; import std.alloc; import std.io; fn ZstdBeispiel(komprimiert: int64, kompLen: int64): void { // Zstd-Frame enthält die unkomprimierte Größe im Header (wenn gesetzt). // Ohne Vorabwissen großzügig schätzen. var dstMax: int64 := kompLen * 20; if dstMax < 65536 then { dstMax := 65536; } var dst: int64 := alloc(dstMax + 1); var n: int64 := ZstdDecompress(komprimiert, kompLen, dst, dstMax); if n == ZSTD_ERR then { PrintLn("Kein gültiger Zstd-Frame oder nicht unterstütztes Feature"c); free(dst, dstMax + 1); return; } if n == ZSTD_OVERFLOW then { PrintLn("Ausgabepuffer zu klein — größeren Puffer verwenden"c); free(dst, dstMax + 1); return; } poke8(dst + n, 0); Print("Dekomprimiert: "c); PrintLn(IntToStr(n)c); free(dst, dstMax + 1); } ==== Bekannte Einschränkungen ==== ^ Einschränkung ^ Details ^ | Treeless-Literalblöcke | Huffman-Typ 3 (Wiederverwendung des Baums aus dem Vorblock) wird nicht unterstützt — gibt ''ZSTD_ERR'' zurück | | Wörterbücher (Dictionary) | Dictionary-ID wird gelesen und übersprungen; der Inhalt wird nicht angewandt | | Skippable Frames | Magic ''0x184D2A5x'' wird nicht erkannt — gibt ''ZSTD_ERR'' zurück | | Multi-Frame-Streams | Nur ein Frame pro Aufruf; verkettete Frames werden nicht durchlaufen | In der Praxis erzeugen Standard-Werkzeuge (''zstd'', ''tar --zstd'', Kafka-Codec) selten Treeless-Blöcke für einzelne unkomprimierte Streams. Multi-Frame-Ausgabe tritt bei sehr großen Dateien auf, die mit ''--long'' komprimiert wurden. ---- ===== ZstdCompress ===== Erzeugt einen RFC-8878-konformen Zstd-Frame im Store-Modus: Alle Daten werden in Raw-Blöcken unkomprimiert eingebettet. Der Output ist ein vollständiger, gültiger Zstd-Frame — der Dekompressionspfad aller RFC-konformen Zstd-Implementierungen kann ihn lesen. Er ist nicht kleiner als die Eingabe. **Frame-Aufbau:** * 4 Bytes Magic (''28 B5 2F FD'') * 1 Byte Frame Header Descriptor (FCS=8 Byte, Single Segment Flag) * 8 Bytes unkomprimierte Größe (LE64) * Rohdaten in Raw-Blöcken à max. 128 KB * Jeder Block: 3-Byte-Header + Nutzdaten **Puffergröße (Worst Case):** benötigt = srcLen + 13 + ceil(srcLen / 131072) × 3 Für Eingaben ≤ 128 KB genügt ''dstMax = srcLen + 16''. Leere Eingabe erzeugt 16 Bytes (Frame-Header + ein leerer letzter Block). ''ZstdCompress'' prüft den Puffer vorab und gibt ''ZSTD_OVERFLOW'' zurück bevor Daten geschrieben werden. import std.zstd; import std.alloc; fn ZstdStoreRoundtrip(): void { var original: pchar := "Hallo Zstd!"c; var srcLen: int64 := 11; // Komprimieren (Store-Modus) var cBuf: int64 := alloc(srcLen + 16); var cLen: int64 := ZstdCompress(original as int64, srcLen, cBuf, srcLen + 16); if cLen == ZSTD_ERR then { PrintLn("Fehler"c); return; } Print("Frame-Größe: "c); PrintLn(IntToStr(cLen)c); // Dekomprimieren var dBuf: int64 := alloc(srcLen + 1); var dLen: int64 := ZstdDecompress(cBuf, cLen, dBuf, srcLen + 1); poke8(dBuf + dLen, 0); PrintLn(dBuf as pchar); // → "Hallo Zstd!" free(cBuf, srcLen + 16); free(dBuf, srcLen + 1); } ---- ===== Ausgabepuffer-Größe schätzen ===== Zstd-Frames enthalten im Header die unkomprimierte Größe (wenn der Kompressor sie gesetzt hat). Für gängige Werkzeuge trifft das in der Regel zu. **1. Content Size aus Frame-Header lesen (empfohlen, wenn möglich):** Die ersten 13 Bytes eines Zstd-Frames lassen sich manuell lesen: Bytes 0–3: Magic (28 B5 2F FD) Byte 4: Frame Header Descriptor (FHD) bit[7:6] = FCS (Frame Content Size Feld-Größe): 0=1B, 1=2B, 2=4B, 3=8B bit[5] = SSF (Single Segment Flag) bit[1:0] = DID (Dictionary ID size): 0=0B, 1=1B, 2=2B, 3=4B Ab Byte 5: optionaler Window Descriptor (wenn SSF=0), DID-Feld, Content Size Für die meisten Standard-Streams (''zstd''-Werkzeug, kein Dictionary) ist FHD ''0xE0'' → SSF=1, FCS=3 → 8-Byte Content Size direkt ab Byte 5 im LE64-Format. **2. Konservativer Faktor (unbekannte Größe):** var dstMax: int64 := srcLen * 20; if dstMax < 65536 then { dstMax := 65536; } var dst: int64 := alloc(dstMax + 1); var n: int64 := ZstdDecompress(src, srcLen, dst, dstMax); if n == ZSTD_OVERFLOW then { // Puffer war zu klein — größer versuchen } ---- ===== Vergleich mit anderen Kompressionseinheiten ===== ^ Merkmal ^ ''std.zstd'' ^ ''std.brotli'' ^ ''std.zlib'' ^ | Standard | RFC 8878 | RFC 7932 | RFC 1950 / 1951 | | HTTP-Encoding | ''zstd'' (aufkommend) | ''br'' (HTTP/2, HTTP/3) | ''gzip'' / ''deflate'' (universell) | | Typischer Einsatz | Server-Infrastruktur, Backups, Kafka, Docker | Web-Assets, HTTP-Responses | ZIP-Inhalte, .tar.gz, HTTP | | Kompressor | Store-Modus (kein LZ77) | Store-Modus (kein LZ77) | vollständig (LZ77 + Huffman) | | Dekompressionstempo | sehr schnell (typisch schnellster) | vergleichbar | gut | | Dekompressionspfad | vollständig (RFC 8878, keine Wörterbücher) | vollständig (RFC 7932, kein §8-Static-Dict) | vollständig | | Einsatz in std.zip | nein | nein | ja (Methode 8) | | Frame enthält unkomprimierte Größe | ja (meist) | nein | nein | Für HTTP-Responses (serverseitig) ist ''std.zlib'' mit GZIP aktuell die beste Wahl. ''std.zstd'' ist bevorzugt für Infrastruktur-nahe Systeme (Kafka-Dekodierung, Backup-Restore, .zst-Dateien). ---- ===== Speicherverwaltung ===== Beide Funktionen allokieren keinen Ausgabepuffer — der Aufrufer ist vollständig verantwortlich: ^ Puffer ^ Wer allokiert ^ Wer gibt frei ^ | ''src'' (Eingabe) | Aufrufer | Aufrufer | | ''dst'' (Ausgabe) | Aufrufer (mindestens ''dstMax'' Bytes) | Aufrufer | | Interne Arbeitspuffer | ''ZstdDecompress'' / ''ZstdCompress'' | automatisch beim Return | ''ZstdDecompress'' allokiert intern FSE-Tabellen, Huffman-Bäume und Lese-Kontexte — diese werden vollständig freigegeben bevor die Funktion zurückkehrt. Letzte Aktualisierung: 2026-06-13