====== 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