dart_lz4 1.0.0
dart_lz4: ^1.0.0 copied to clipboard
Pure Dart LZ4 and LZ4HC (block + frame) with streaming support.
dart_lz4 #
Pure Dart implementation of LZ4 (block + frame) and LZ4HC, including streaming frame encode/decode.
Repository: https://github.com/jxoesneon/dart_lz4
Status #
This package provides a feature-complete pure Dart implementation of the LZ4 frame format.
Implemented:
- LZ4 block encode/decode
- LZ4 frame encode/decode (including skippable and legacy frames)
- Streaming frame encode/decode (
StreamTransformer) - LZ4HC block compression
- Dictionary support (encode + decode)
- 64-bit content size (encode + decode)
- xxHash32 with VM + Web parity
Goals #
- Pure Dart (no FFI)
- Web-safe core (no
dart:ioin library code) - Strict, bounds-safe decoding with deterministic errors
- Streaming-friendly APIs with output limits
- Compatibility with LZ4 frame format (current)
Limitations #
None. This package provides complete encode/decode support for all LZ4 frame formats.
Security / untrusted input #
- Always set a reasonable
maxOutputByteswhen decoding frames (lz4FrameDecode/lz4FrameDecoder) to mitigate decompression bombs. - Use
blockChecksumand/orcontentChecksumwhen encoding if you want corruption detection. These checksums are not cryptographic authentication. - For block decompression (
lz4Decompress),decompressedSizemust be known and trusted/validated.
Interop / compatibility #
Tested against the reference lz4 CLI (lz4 v1.10.0) via embedded decode vectors and a CLI decode test.
| Feature | Decode | Encode | Notes |
|---|---|---|---|
Current LZ4 frame (magic 0x184D2204) |
Yes | Yes | |
| Concatenated frames | Yes | N/A | You can concatenate multiple encoded frames yourself. |
Skippable frames (magic 0x184D2A5x) |
Yes | Yes | Skipped on decode; use lz4SkippableEncode to create. |
Independent blocks (blockIndependence: true) |
Yes | Yes | Default. |
Dependent blocks (blockIndependence: false) |
Yes | Yes | Uses a 64KiB history window. |
| Block checksum | Yes | Yes | |
| Content checksum | Yes | Yes | |
| Content size (<= 4GiB) | Yes | Yes | |
| Content size (> 4GiB) | Yes | Yes | |
Dictionary ID (dictId) |
Yes | Yes | |
Legacy -l format |
Yes | Yes | Use lz4LegacyEncode for legacy frame magic 0x184C2102. |
Usage #
Block #
Block decompression requires the decompressed size.
import 'dart:typed_data';
import 'package:dart_lz4/dart_lz4.dart';
final src = Uint8List.fromList('hello'.codeUnits);
final compressed = lz4Compress(src);
final decoded = lz4Decompress(compressed, decompressedSize: src.length);
LZ4HC #
final compressed = lz4Compress(
src,
level: Lz4CompressionLevel.hc,
hcOptions: Lz4HcOptions(maxSearchDepth: 64), // Optional tuning
);
Frame #
final frame = lz4FrameEncode(src);
final decoded = lz4FrameDecode(frame);
Frame with options #
final frame = lz4FrameEncodeWithOptions(
src,
options: Lz4FrameOptions(
blockSize: Lz4FrameBlockSize.k64KB,
blockChecksum: true,
contentChecksum: true,
contentSize: src.length,
compression: Lz4FrameCompression.fast,
acceleration: 1,
),
);
final decoded = lz4FrameDecode(frame);
Dependent blocks are supported by setting blockIndependence: false. When enabled,
blocks may reference up to 64KiB of history from prior blocks.
Frame Inspection #
Inspect a frame header without decoding the payload:
final info = lz4FrameInfo(frameBytes);
print('Content Size: ${info.contentSize}');
print('Dictionary ID: ${info.dictId}');
Dictionary Support #
To decode frames that use a preset dictionary (identified by dictId):
final decoded = lz4FrameDecode(
frameBytes,
dictionaryResolver: (dictId) {
if (dictId == 0x123456) return myDictionaryBytes;
return null; // Dictionary not found
},
);
Skippable Frames #
Embed custom metadata in an LZ4 stream using skippable frames:
import 'dart:convert';
final metadata = Uint8List.fromList(utf8.encode('{"version": 1}'));
final skippable = lz4SkippableEncode(metadata, index: 0);
// Concatenate with a regular frame
final combined = Uint8List.fromList([...skippable, ...lz4FrameEncode(data)]);
// Decoders will skip the metadata and decode only the payload
final decoded = lz4FrameDecode(combined);
Legacy Frames #
Encode data using the legacy LZ4 format (compatible with lz4 -l):
final frame = lz4LegacyEncode(src);
final decoded = lz4FrameDecode(frame);
Sized Blocks #
Simple helper for block compression with prepended 4-byte length header:
final compressed = lz4CompressWithSize(src);
final decoded = lz4DecompressWithSize(compressed);
Streaming frame decode #
final decodedChunks = byteChunksStream.transform(
lz4FrameDecoder(maxOutputBytes: 128 * 1024 * 1024),
);
Streaming frame encode #
final encodedChunks = byteChunksStream.transform(
lz4FrameEncoder(),
);
Streaming encoding also supports Lz4FrameOptions:
final encodedChunks = byteChunksStream.transform(
lz4FrameEncoderWithOptions(
options: Lz4FrameOptions(
blockSize: Lz4FrameBlockSize.k64KB,
blockIndependence: false,
),
),
);
Benchmarks #
Run:
dart run benchmark/lz4_benchmark.dart
It reports throughput (MiB/s) and ratio for:
- Block compress/decompress (fast + hc)
- Frame (sync) encode/decode (fast + hc)
- Frame (streaming) encode/decode (fast + hc)
License #
Apache-2.0. See LICENSE.