<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Eduardo Rojas</title>
    <description>The latest articles on DEV Community by Eduardo Rojas (@jxoesneon).</description>
    <link>https://dev.to/jxoesneon</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4123482%2F9191de9c-a229-41b4-9cf0-5edfa5e47ad5.png</url>
      <title>DEV Community: Eduardo Rojas</title>
      <link>https://dev.to/jxoesneon</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jxoesneon"/>
    <language>en</language>
    <item>
      <title>The Most Complete LZ4 Implementation in Dart</title>
      <dc:creator>Eduardo Rojas</dc:creator>
      <pubDate>Sun, 13 Sep 2026 18:33:49 +0000</pubDate>
      <link>https://dev.to/jxoesneon/the-most-complete-lz4-implementation-in-dart-167b</link>
      <guid>https://dev.to/jxoesneon/the-most-complete-lz4-implementation-in-dart-167b</guid>
      <description>&lt;p&gt;When you need LZ4 compression in Dart, you have options. But not all LZ4 implementations are equal — the LZ4 specification is more than just block decompression. It includes frame format, legacy format, dictionaries, skippable frames, high-compression mode, and streaming. Many implementations cover only a subset.&lt;/p&gt;

&lt;p&gt;This article walks through the full LZ4 feature surface and shows what a complete implementation looks like, with code examples from &lt;a href="https://pub.dev/packages/dart_lz4" rel="noopener noreferrer"&gt;dart_lz4&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The LZ4 Feature Surface
&lt;/h2&gt;

&lt;p&gt;The LZ4 ecosystem has several layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Block format&lt;/strong&gt; — the core compression algorithm. Compresses a single block of bytes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frame format&lt;/strong&gt; — the standard container format (magic &lt;code&gt;0x184D2204&lt;/code&gt;). Wraps blocks with headers, flags, checksums, and optional content size.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Legacy frame format&lt;/strong&gt; — the older format (magic &lt;code&gt;0x184C2102&lt;/code&gt;), produced by &lt;code&gt;lz4 -l&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skippable frames&lt;/strong&gt; — metadata containers that decoders skip but encoders can embed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LZ4HC&lt;/strong&gt; — high-compression mode. Same decompression, better ratio at the cost of speed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dictionaries&lt;/strong&gt; — preset dictionaries for compressing small inputs with known patterns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming&lt;/strong&gt; — chunk-by-chunk encode/decode via &lt;code&gt;StreamTransformer&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;xxHash32&lt;/strong&gt; — the checksum used by LZ4 frames.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A "complete" LZ4 implementation should handle all of these. Let's look at each.&lt;/p&gt;

&lt;h2&gt;
  
  
  Block Format
&lt;/h2&gt;

&lt;p&gt;The basics — compress and decompress a buffer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:dart_lz4/dart_lz4.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Uint8List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'hello world'&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;codeUnits&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;compressed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lz4Compress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lz4Decompress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;compressed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;decompressedSize:&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Block decompression requires the original size — this is inherent to the LZ4 format, not a limitation of a particular implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Zero-Copy Decompression
&lt;/h3&gt;

&lt;p&gt;For performance-critical paths, you can decompress directly into a pre-allocated buffer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;dst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Uint8List&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;bytesWritten&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lz4DecompressInto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;compressed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Or at an offset within a shared buffer:&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;offsetBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lz4DecompressInto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;compressed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;dstOffset:&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This avoids intermediate allocations — important in high-throughput pipelines.&lt;/p&gt;

&lt;h2&gt;
  
  
  LZ4HC: High-Compression Mode
&lt;/h2&gt;

&lt;p&gt;LZ4HC uses a more thorough search strategy to find better matches. The decompressed output is identical — any LZ4 decoder can read LZ4HC output. The tradeoff is compression speed: LZ4HC is slower but produces smaller output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;compressed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lz4Compress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;level:&lt;/span&gt; &lt;span class="n"&gt;Lz4CompressionLevel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;hcOptions:&lt;/span&gt; &lt;span class="n"&gt;Lz4HcOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;maxSearchDepth:&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;dart_lz4 supports HC levels 1–12, matching the C reference implementation's range. This matters because higher levels can meaningfully improve ratio on compressible data while remaining fast enough for interactive use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frame Format
&lt;/h2&gt;

&lt;p&gt;The frame format is the standard LZ4 container. It wraps blocks with a descriptor that specifies flags, block size, and optional metadata:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lz4FrameEncode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lz4FrameDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Frame Options
&lt;/h3&gt;

&lt;p&gt;The frame descriptor supports several options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lz4FrameEncodeWithOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;options:&lt;/span&gt; &lt;span class="n"&gt;Lz4FrameOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;blockSize:&lt;/span&gt; &lt;span class="n"&gt;Lz4FrameBlockSize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;k64KB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;blockChecksum:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;contentChecksum:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;contentSize:&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;compression:&lt;/span&gt; &lt;span class="n"&gt;Lz4FrameCompression&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;acceleration:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each option has a purpose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;blockSize&lt;/code&gt;&lt;/strong&gt; — maximum block size (64KB, 256KB, 1MB, or 4MB)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;blockChecksum&lt;/code&gt;&lt;/strong&gt; — per-block xxHash32 for corruption detection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;contentChecksum&lt;/code&gt;&lt;/strong&gt; — end-of-frame xxHash32 over all decompressed data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;contentSize&lt;/code&gt;&lt;/strong&gt; — 64-bit total decompressed size in the header&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;blockIndependence&lt;/code&gt;&lt;/strong&gt; — independent blocks (default) or linked blocks with 64KB history&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Dependent Blocks
&lt;/h3&gt;

&lt;p&gt;Linked blocks can reference up to 64 KiB of history from prior blocks, improving ratio on data with patterns spanning block boundaries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lz4FrameEncodeWithOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;options:&lt;/span&gt; &lt;span class="n"&gt;Lz4FrameOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;blockIndependence:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Legacy Frame Format
&lt;/h2&gt;

&lt;p&gt;The legacy format (&lt;code&gt;0x184C2102&lt;/code&gt;) is what &lt;code&gt;lz4 -l&lt;/code&gt; produces. It's simpler — no descriptor, no flags, no optional fields. Some systems still produce or consume this format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lz4LegacyEncode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lz4FrameDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A complete implementation should both encode and decode legacy frames, not just decode them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Skippable Frames
&lt;/h2&gt;

&lt;p&gt;Skippable frames let you embed custom metadata in an LZ4 stream. Decoders skip them; encoders can use them for application-specific data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'dart:convert'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Uint8List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;utf8&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'{"version": 1}'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;skippable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lz4SkippableEncode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;index:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Concatenate with a regular frame&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;combined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Uint8List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromList&lt;/span&gt;&lt;span class="p"&gt;([..&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;skippable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lz4FrameEncode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)]);&lt;/span&gt;

&lt;span class="c1"&gt;// Decoders skip the metadata and decode only the payload&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lz4FrameDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;combined&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;index&lt;/code&gt; parameter (0–15) selects the magic number in the skippable range (&lt;code&gt;0x184D2A50&lt;/code&gt;–&lt;code&gt;0x184D2A5F&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Dictionary Support
&lt;/h2&gt;

&lt;p&gt;Dictionaries let you compress small inputs more effectively by providing preset context. This is useful when compressing many small messages with shared patterns (e.g., log entries, API payloads):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Encoding with a dictionary&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;compressed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lz4FrameEncodeWithOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;options:&lt;/span&gt; &lt;span class="n"&gt;Lz4FrameOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;dictionary:&lt;/span&gt; &lt;span class="n"&gt;myDictionary&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Decoding with a dictionary resolver&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lz4FrameDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;frameBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;dictionaryResolver:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dictId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dictId&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mh"&gt;0x123456&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;myDictionaryBytes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dictionary resolver is a callback — the decoder calls it with the &lt;code&gt;dictId&lt;/code&gt; from the frame header, and you return the matching dictionary bytes. This design supports multiple dictionaries without embedding them in the library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Streaming
&lt;/h2&gt;

&lt;p&gt;Streaming encode/decode processes data chunk by chunk via &lt;code&gt;StreamTransformer&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Streaming decode&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;decodedChunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;byteChunksStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;lz4FrameDecoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;maxOutputBytes:&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Streaming encode&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;encodedChunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;byteChunksStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;lz4FrameEncoder&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Streaming supports the same &lt;code&gt;Lz4FrameOptions&lt;/code&gt; as the sync API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;encodedChunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;byteChunksStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;lz4FrameEncoderWithOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;options:&lt;/span&gt; &lt;span class="n"&gt;Lz4FrameOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;blockSize:&lt;/span&gt; &lt;span class="n"&gt;Lz4FrameBlockSize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;k64KB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;blockIndependence:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Buffer Pooling
&lt;/h3&gt;

&lt;p&gt;For zero-allocation streaming, buffer pools reuse memory across operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SimpleLz4BufferPool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;maxTotalBuffers:&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;maxBuffersPerBucket:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;decodedStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;byteChunksStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;lz4FrameDecoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;bufferPool:&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pool uses power-of-two slab bucketing (64B to 8MB) matching LZ4 block sizes. A &lt;code&gt;SecureLz4BufferPool&lt;/code&gt; variant zeroes buffers on return for CWE-226 mitigation in security-sensitive contexts.&lt;/p&gt;

&lt;h2&gt;
  
  
  dart:convert Integration
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;Lz4Codec&lt;/code&gt; wraps LZ4 frame encode/decode as a standard &lt;code&gt;dart:convert&lt;/code&gt; &lt;code&gt;Codec&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'dart:convert'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;codec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Lz4Codec&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;compressed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;codec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;codec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;compressed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Fuse with other codecs:&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;jsonLz4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fuse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;codec&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;utf8&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'{"hello":"world"}'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;compressedJson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonLz4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;restored&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonLz4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;compressedJson&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes LZ4 composable with the entire Dart conversion ecosystem — &lt;code&gt;utf8&lt;/code&gt;, &lt;code&gt;json&lt;/code&gt;, &lt;code&gt;base64&lt;/code&gt;, and any custom codec.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use dart_lz4
&lt;/h2&gt;

&lt;p&gt;dart_lz4 is a pure-Dart implementation with zero runtime dependencies. It works on all Dart platforms including Web and WASM. It does not use FFI — native LZ4 bindings via &lt;code&gt;dart:ffi&lt;/code&gt; will be faster for large payloads on native platforms.&lt;/p&gt;

&lt;p&gt;Choose dart_lz4 when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need LZ4 on Web or WASM (FFI-based libraries can't run there without native binaries)&lt;/li&gt;
&lt;li&gt;You want zero native dependencies (simpler deployment, no platform-specific binaries)&lt;/li&gt;
&lt;li&gt;You need features beyond basic block compression (dictionaries, legacy frames, skippable frames, streaming with buffer pools)&lt;/li&gt;
&lt;li&gt;You want bounds-safe decoding with output limits on untrusted input&lt;/li&gt;
&lt;li&gt;You value supply-chain hardening (OpenSSF Gold, fuzzing in CI, 100% test coverage)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choose FFI-based libraries when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need maximum throughput on native platforms&lt;/li&gt;
&lt;li&gt;You don't need Web/WASM support&lt;/li&gt;
&lt;li&gt;You're compressing large payloads where the native speed advantage matters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are complementary tools for different use cases, not competitors in the same lane.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Support&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Block encode/decode&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zero-copy decompression&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LZ4HC levels 1–12&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frame format (all flags)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Legacy frame format&lt;/td&gt;
&lt;td&gt;Encode + decode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skippable frames&lt;/td&gt;
&lt;td&gt;Encode + decode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dictionary encode/decode&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Streaming (StreamTransformer)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Buffer pooling (simple + secure)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dart:convert Codec&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;xxHash32&lt;/td&gt;
&lt;td&gt;Yes (VM + Web parity)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web/WASM&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FFI acceleration&lt;/td&gt;
&lt;td&gt;No (pure Dart)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;a href="https://pub.dev/packages/dart_lz4" rel="noopener noreferrer"&gt;dart_lz4&lt;/a&gt; is on pub.dev with a 160/160 score, OpenSSF Best Practices Gold badge, 361 tests, and frame fuzzing in CI. Source code and documentation at &lt;a href="https://github.com/jxoesneon/dart_lz4" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>dart</category>
      <category>compression</category>
      <category>flutter</category>
      <category>webassembly</category>
    </item>
    <item>
      <title>Defending Against Decompression Bombs in Dart</title>
      <dc:creator>Eduardo Rojas</dc:creator>
      <pubDate>Sun, 13 Sep 2026 18:33:13 +0000</pubDate>
      <link>https://dev.to/jxoesneon/defending-against-decompression-bombs-in-dart-goi</link>
      <guid>https://dev.to/jxoesneon/defending-against-decompression-bombs-in-dart-goi</guid>
      <description>&lt;h1&gt;
  
  
  Defending Against Decompression Bombs in Dart
&lt;/h1&gt;

&lt;p&gt;A decompression bomb is a small compressed input that expands to an enormous size when decompressed. A 10 MB file might decompress to 10 GB — or more. If your application accepts compressed input from untrusted sources (file uploads, API payloads, user-generated content), this is a real attack vector.&lt;/p&gt;

&lt;p&gt;This article walks through what decompression bombs are, why Dart applications are vulnerable, and how to defend against them — with code examples from &lt;a href="https://pub.dev/packages/dart_lz4" rel="noopener noreferrer"&gt;dart_lz4&lt;/a&gt;, a pure-Dart LZ4 implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Decompression Bomb?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://cwe.mitre.org/data/definitions/400.html" rel="noopener noreferrer"&gt;CWE-400&lt;/a&gt; defines the vulnerability: "The software does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed."&lt;/p&gt;

&lt;p&gt;In the context of compression, the resource is memory. LZ4 — like all LZ-family algorithms — uses back-references to encode repeated data. A single byte in the compressed stream can reference a previously decompressed region and copy it forward. This means a compact compressed input can produce output many orders of magnitude larger than itself.&lt;/p&gt;

&lt;p&gt;Consider this: LZ4's maximum block size is 4 MB. A well-crafted frame can chain hundreds of these blocks. A 1 MB compressed file could legitimately decompress to 4 GB if the blocks are independent, or even more with linked blocks referencing prior history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Dart Applications Are Vulnerable
&lt;/h2&gt;

&lt;p&gt;Dart's memory model makes this particularly dangerous:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Heap allocation is automatic.&lt;/strong&gt; &lt;code&gt;Uint8List&lt;/code&gt; allocations grow the Dart heap. There's no &lt;code&gt;mmap&lt;/code&gt; with &lt;code&gt;MAP_ANONYMOUS&lt;/code&gt; to get OS-level overcommit protection — the Dart VM allocates real memory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Web targets are worse.&lt;/strong&gt; On Web (JS/WASM), a 4 GB allocation will crash the browser tab. There's no graceful recovery.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Streaming amplifies the risk.&lt;/strong&gt; If you're decoding a compressed stream chunk by chunk, you might not know the total decompressed size until you've already allocated it. Without a bound, an attacker can keep feeding compressed chunks that each expand to the maximum block size.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The &lt;code&gt;dart:convert&lt;/code&gt; pattern hides the danger.&lt;/strong&gt; If you use a &lt;code&gt;Codec&amp;lt;List&amp;lt;int&amp;gt;, List&amp;lt;int&amp;gt;&amp;gt;&lt;/code&gt; wrapper, the decode call looks innocent:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;codec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Lz4Codec&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;codec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;compressedBytes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// How big is decoded?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without a limit, &lt;code&gt;decoded&lt;/code&gt; could be gigabytes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Defense: Bounded Decompression
&lt;/h2&gt;

&lt;p&gt;The fundamental defense is simple: &lt;strong&gt;always set a maximum output size when decompressing untrusted input.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting &lt;code&gt;maxOutputBytes&lt;/code&gt; on Frame Decode
&lt;/h3&gt;

&lt;p&gt;The sync frame decoder accepts an explicit limit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:dart_lz4/dart_lz4.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Set a reasonable upper bound for your use case&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lz4FrameDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;untrustedFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;maxOutputBytes:&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 64 MiB max&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the decompressed output would exceed this limit, the decoder throws an &lt;code&gt;Lz4Exception&lt;/code&gt; before the allocation happens. No partial output, no memory exhaustion — just a clean error you can handle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Streaming Decoder: The Higher-Risk Path
&lt;/h3&gt;

&lt;p&gt;Streaming decoders are more dangerous because the input arrives in chunks. Without a bound, an attacker can keep feeding compressed data indefinitely. The streaming decoder also accepts &lt;code&gt;maxOutputBytes&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;decodedChunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;byteChunksStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;lz4FrameDecoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;maxOutputBytes:&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// 128 MiB max&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was a real vulnerability in dart_lz4. Before v1.4.0, the streaming decoder had no default output limit — only the sync decoder did. If you used the streaming decoder without explicitly setting &lt;code&gt;maxOutputBytes&lt;/code&gt;, you were unprotected. The v1.4.0 release closed this gap by applying a 256 MiB default to both decoders.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;dart:convert&lt;/code&gt; Codec
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;Lz4Codec&lt;/code&gt; wrapper enforces the same 256 MiB default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;codec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Lz4Codec&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 256 MiB default&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;codec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;untrustedInput&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Or set your own limit:&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;codec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Lz4Codec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;maxOutputBytes:&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 32 MiB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is important because the &lt;code&gt;Codec&lt;/code&gt; pattern doesn't naturally expose safety parameters — you'd expect &lt;code&gt;codec.decode(input)&lt;/code&gt; to "just work." The default limit ensures it does, safely.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Should the Limit Be?
&lt;/h2&gt;

&lt;p&gt;There's no universal answer. The limit should be based on what your application actually expects:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;th&gt;Suggested limit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API payload (JSON)&lt;/td&gt;
&lt;td&gt;8–16 MiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File upload (images)&lt;/td&gt;
&lt;td&gt;64–128 MiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Log stream&lt;/td&gt;
&lt;td&gt;256 MiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trusted internal pipeline&lt;/td&gt;
&lt;td&gt;Unlimited (pass &lt;code&gt;maxOutputBytes: -1&lt;/code&gt; or a very large value)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The key principle: &lt;strong&gt;the limit should be the largest size your application would legitimately accept, not the largest size the format can produce.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond Output Limits: Buffer Zeroization
&lt;/h2&gt;

&lt;p&gt;There's a second, subtler vulnerability: &lt;a href="https://cwe.mitre.org/data/definitions/226.html" rel="noopener noreferrer"&gt;CWE-226&lt;/a&gt;, "Sensitive Information in Resource Not Cleared Before Reuse."&lt;/p&gt;

&lt;p&gt;When you reuse buffers across decompression operations (via a buffer pool for performance), the buffers may contain residual data from previous operations. If a subsequent decompression reads past the logical end of its output — due to a bug, a truncated block, or a malicious input — it can observe bytes from a prior, possibly sensitive, decompression.&lt;/p&gt;

&lt;p&gt;dart_lz4 provides two buffer pool implementations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Standard pool — fast, but does NOT zero buffers on return&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SimpleLz4BufferPool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;maxTotalBuffers:&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;maxBuffersPerBucket:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Secure pool — zeroes buffers on return (CWE-226 mitigation)&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;securePool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SecureLz4BufferPool&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SecureLz4BufferPool&lt;/code&gt; calls &lt;code&gt;buffer.fillRange(0, buffer.length, 0)&lt;/code&gt; on every buffer return, ensuring no residual data persists. The performance cost is small — a single &lt;code&gt;fillRange&lt;/code&gt; call per buffer — but the security guarantee is significant for multi-tenant or security-sensitive workloads.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;SecureLz4BufferPool&lt;/code&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Processing data from different users/tenants through the same pipeline&lt;/li&gt;
&lt;li&gt;Handling sensitive payloads (session tokens, PII, decrypted data)&lt;/li&gt;
&lt;li&gt;Operating in environments where memory isolation matters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use &lt;code&gt;SimpleLz4BufferPool&lt;/code&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single-tenant, high-throughput pipelines&lt;/li&gt;
&lt;li&gt;Non-sensitive data&lt;/li&gt;
&lt;li&gt;Buffers are guaranteed to be fully overwritten&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Checksums Are Not Authentication
&lt;/h2&gt;

&lt;p&gt;LZ4 frames support block checksums and content checksums (xxHash32). These detect accidental corruption — bit flips, truncated files, transmission errors. They do &lt;strong&gt;not&lt;/strong&gt; provide cryptographic authentication.&lt;/p&gt;

&lt;p&gt;If an attacker can modify the compressed input, they can produce a valid frame with different decompressed output that still passes the checksum. For authentication, use a MAC (e.g., HMAC-SHA256) or a digital signature over the compressed frame.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Threat&lt;/th&gt;
&lt;th&gt;Mitigation&lt;/th&gt;
&lt;th&gt;CWE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Decompression bomb (memory exhaustion)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;maxOutputBytes&lt;/code&gt; on all decoders&lt;/td&gt;
&lt;td&gt;CWE-400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Residual memory leakage (buffer reuse)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;SecureLz4BufferPool&lt;/code&gt; with zeroization&lt;/td&gt;
&lt;td&gt;CWE-226&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Corrupted input&lt;/td&gt;
&lt;td&gt;Block/content checksums&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tampered input&lt;/td&gt;
&lt;td&gt;MAC or signature (not checksum)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The core principle: &lt;strong&gt;treat compressed input like any other untrusted input.&lt;/strong&gt; Set bounds, validate output, and don't assume the decompressed size matches the compressed size. A 10 MB upload should never produce 10 GB of output.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://pub.dev/packages/dart_lz4" rel="noopener noreferrer"&gt;dart_lz4&lt;/a&gt; is a pure-Dart LZ4/LZ4HC implementation with bounds-safe decoding, buffer pool architecture, frame fuzzing in CI, and an OpenSSF Best Practices Gold badge. It works on all Dart platforms including Web and WASM.&lt;/p&gt;

</description>
      <category>dart</category>
      <category>security</category>
      <category>compression</category>
      <category>webassembly</category>
    </item>
  </channel>
</rss>
