Skip to main content

Crate mbrotli

Crate mbrotli 

Source
Expand description

Safe Rust Brotli codecs, independently selected with Cargo features.

compression and decompression are enabled by default. Disable default features and select either codec alongside std or no_std. For example:

mbrotli = { version = "0.4.1", default-features = false, features = ["std", "decompression"] }

A disabled codec has no module, API re-exports, dictionaries or I/O adapters. Shared Backend and RetentionPolicy types exist when either codec is enabled. With neither codec enabled, the crate exposes no codec API.

§Choose your API

Compression and decompression expose the same core I/O shapes. Pick the shape that matches how your application already moves bytes; parallel compression is a separate execution strategy, not another streaming API. Each codec requires its Cargo feature; reader/writer adapters and parallel compression are unavailable with no_std.

I/O shapeCompressionDecompression
Return a new Vec<u8>Compressor::compressDecompressor::decompress
Append to an existing Veccompress_intodecompress_into
Write into a caller-owned slicecompress_to_slicedecompress_to_slice
Pull output through std::io::ReadCompressor::readerDecompressor::reader
Push input through std::io::WriteCompressor::writerDecompressor::writer
Drive input/output incrementallystartEncoderSessionstartDecoderSession

§Reuse memory between payloads

Compressor and Decompressor own reusable working state. Keep the codec and your output buffer alive across operations when allocation reuse matters.

Example: reuse the compressor and output buffer
use mbrotli::{Compressor, EncoderConfig, Quality};

pub fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut encoder = Compressor::new(
        EncoderConfig::default().with_quality(Quality::Q5),
    )?;
    let mut output = Vec::new();

    for input in [b"first payload".as_slice(), b"second payload".as_slice()] {
        output.clear(); // Keep the allocation; compress_into appends.
        let written = encoder.compress_into(input, &mut output)?;
        assert_eq!(written, 0..output.len());
    }
    Ok(())
}

§Read and Write streaming

Both codecs provide synchronous adapters for the standard Rust I/O traits. The two adapter shapes are complementary:

  • reader(...) wraps an input Read and exposes transformed bytes through Read.
  • writer(...) wraps an output Write and accepts source bytes through Write.

That means you can plug Brotli into an existing pull-based or push-based pipeline without first collecting the whole payload in memory.

§Compression I/O

Compressor::reader consumes uncompressed bytes from a Read source and yields compressed bytes. Compressor::writer accepts uncompressed bytes and writes compressed bytes to its sink. Encoder writers must be explicitly finished; dropping one abandons the stream.

Compression with both Read and Write
use mbrotli::{Compressor, EncoderConfig, InputSize, Quality};
use std::io::{Read, Write};

pub fn main() -> Result<(), Box<dyn std::error::Error>> {
    let payload = b"streamed payload";
    // Pull model: read compressed bytes from an uncompressed source.
    let mut encoder = Compressor::new(
        EncoderConfig::default().with_quality(Quality::Q5),
    )?;
    let stream = InputSize::Exact(payload.len() as u64).into();
    let mut reader = encoder.reader(&payload[..], stream)?;
    let mut compressed_from_reader = Vec::new();
    reader.read_to_end(&mut compressed_from_reader)?;

    // Push model: write uncompressed bytes into a compressed sink.
    let mut encoder = Compressor::new(
        EncoderConfig::default().with_quality(Quality::Q5),
    )?;
    let stream = InputSize::Exact(payload.len() as u64).into();
    let mut writer = encoder.writer(Vec::new(), stream)?;
    writer.write_all(payload)?;
    let compressed_from_writer = writer
        .finish()
        .map_err(mbrotli::io::FinishError::into_error)?;

    assert_eq!(compressed_from_reader, compressed_from_writer);
    Ok(())
}

flush() makes accepted input decodable without ending the encoder stream, and flush boundaries can affect compressed bytes. Use finish() when the stream is complete.

§Decompression I/O

Decompressor::reader consumes a compressed Read source and yields the decompressed payload. Decompressor::writer accepts compressed bytes and writes the decompressed payload to its sink.

Decompression with both Read and Write
use mbrotli::{DecodeStreamConfig, DecoderConfig, Decompressor};
use std::io::{Read, Write};

fn decode_with_reader(compressed: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
    let mut decoder = Decompressor::new(DecoderConfig::default())?;
    let mut reader = decoder.reader(compressed, DecodeStreamConfig::default())?;
    let mut output = Vec::new();
    reader.read_to_end(&mut output)?;
    Ok(output)
}

fn decode_with_writer(compressed: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
    let mut decoder = Decompressor::new(DecoderConfig::default())?;
    let mut writer = decoder.writer(Vec::new(), DecodeStreamConfig::default())?;
    writer.write_all(compressed)?;
    Ok(writer
        .finish()
        .map_err(mbrotli::io::FinishError::into_error)?)
}

The decoder adapters use bounded internal buffering. Reader read-ahead can be recovered with into_parts(), while decoder-writer finalization reports truncated or invalid input instead of silently accepting an incomplete stream.

§Parallel compression

Parallel compression is independent of the Read/Write adapters. It changes how one compression job is scheduled: mbrotli splits the input into segments, exposes work items to the caller, and assembles the completed segments back into one Brotli stream. The library does not create or own a thread pool.

Example: parallel compression with scoped threads
use mbrotli::compressor::parallel::{
    BatchConfig, ParallelCompressor, ParallelConfig, TaskCount,
};
use mbrotli::{EncoderConfig, Quality};

pub fn main() -> Result<(), Box<dyn std::error::Error>> {
    let input = vec![b'a'; 8 << 20];
    let mut encoder = ParallelCompressor::new(
        EncoderConfig::default().with_quality(Quality::Q5),
        ParallelConfig::default(),
    )?;
    let mut batch = encoder.prepare_slice(
        &input,
        BatchConfig::auto(TaskCount::try_from(2)?),
    )?;
    let tasks = batch.take_tasks()?;

    std::thread::scope(|scope| {
        for task in tasks {
            scope.spawn(move || task.run());
        }
    });

    let mut output = Vec::new();
    let result = batch.finish_into(&mut output)?;
    assert_eq!(result.stats.effective_tasks, 2);
    assert!(output.len() < input.len());
    println!("{} -> {} bytes", input.len(), output.len());
    Ok(())
}

For fixed segment settings, parallel output is deterministic across task counts, but it can differ in bytes and size from serial compression. The example stages compressed segments in memory; see the parallel guide for budgets, disk staging, file input, and other executors.

§Native decompression

Decompressor provides reusable Vec/slice APIs, incremental sessions, and synchronous reader/writer adapters. Vec appends are rolled back on failure. The decoder is currently scalar Rust; SIMD acceleration applies to the encoder.

Configure limits for untrusted input. Numeric budgets are unlimited by default. This example accepts standard windows and sets explicit input, output, and workspace budgets; choose limits appropriate for your application.

use mbrotli::{DecodeLimits, DecoderConfig, Decompressor, WindowLimit};

fn decode_payload(input: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
    let limits = DecodeLimits::default()
        .with_max_input_bytes(Some(1 << 20))
        .with_max_output_bytes(Some(8 << 20))
        .with_max_workspace_bytes(Some(32 << 20));
    let config = DecoderConfig::default()
        .with_window_limit(WindowLimit::standard(24)?)
        .with_limits(limits);
    let mut decoder = Decompressor::new(config)?;

    Ok(decoder.decompress(input)?)
}

The workspace budget excludes caller-owned output and borrowed dictionaries; it is not a total process-memory limit. Retain the decoder across calls when reuse matters. See decoder configuration and semantics and compatibility evidence.

§Structured framing

With compression,experimental, framing::FramedCompressor owns reusable raw and container storage. Borrowed input preserves resource/metadata order. Native sessions and one-shot operations support alloc; framed Read/Write adapters require std APIs.

use mbrotli::framing::{FramedCompressor, FramedInput, FramedItem, FramedResource};
let items = [FramedItem::Resource(FramedResource::from(&b"hello"[..]))];
let mut encoder = FramedCompressor::new(Default::default())?;
let bytes = encoder.compress(FramedInput::from(items.as_slice()))?;
assert_eq!(&bytes[..4], &[0x91, 10, 66, 82]);

With decompression,experimental, FramedDecompressor decodes a container into FramedOutput: resources in wire order with their metadata, global metadata, and the validated container layout. Keep the decoder across calls to reuse its storage. Compression support is optional; the decoder can be built on its own.

To decode a container in bytes:

use mbrotli::framing::FramedDecompressor;
let mut decoder = FramedDecompressor::new(Default::default())?;
let output = decoder.decompress(&bytes)?;
assert_eq!(output.resources.len(), 1);
assert_eq!(output.resources[0].data, b"hello");

For incremental input, decoder.start(stream_config) exposes resource and metadata events with payload fragments. With std APIs, decoder.framed_reader(source, stream_config) wraps a BufRead source and exposes next_event(). This event reader preserves resource boundaries; it does not flatten the container into one Read stream.

FramedDecodeConfig defaults to InputMode::FramedOnly; InputMode::Auto also accepts a raw Brotli member. FramedDecodeLimits configures resource, input, output, metadata and workspace budgets. External dictionary references use an explicit DictionaryResolver; resource checksums are recorded without verification. The owner, one-shot APIs and native sessions also work with no_std and alloc; FramedReader requires std APIs. See framed decoder mechanics for validation, dictionaries and streaming semantics.

§Select only the codecs you need

The default feature set is std, compression, and decompression. Disable default features to select one codec or use no_std with alloc. For an alloc-backed decoder:

[dependencies]
mbrotli = { version = "0.4.1", default-features = false, features = ["no_std", "decompression"] }

Add "compression" for both codecs, or use "std" instead of "no_std" for standard I/O support. no_std requires a global allocator; it excludes I/O adapters, parallel compression, framed I/O adapters, and profiling, and uses compile-time SIMD selection. Cargo features are additive: another dependency can re-enable a codec or std. Leave std and hotpath* disabled throughout the dependency graph for a std-free build.

§Choosing a quality

QualityWhat it doesTypical use
0One pass, static entropy codesFastest, largest output
1Two passes, per-block entropy codesFast
2Greedy matching with the format’s fixed codesFast
3Greedy matching, one prefix code per streamBalanced
4Adds block splitting and histogram optimisationBalanced, denser
5Adds an extensive search and literal context modellingDensest of these
6 to 9Wider match search, more cached distances, richer context modelsDenser, slower
10, 11Binary-tree matching and a Zopfli dynamic programDensest, slowest

EncoderConfig::default is quality 11, which mirrors the reference encoder’s default and is far slower than most callers want. For online compression, say so:

use mbrotli::{Compressor, EncoderConfig, Quality};

let mut encoder = Compressor::new(EncoderConfig::default().with_quality(Quality::Q5))?;
let payload = "the quick brown fox ".repeat(500);

let compressed = encoder.compress(payload.as_bytes())?;

assert!(compressed.len() < payload.len() / 100);

§Large Window Brotli

RFC 9841 widens the sliding window past what RFC 7932 can express. Which header a stream carries is part of the window itself: build one with Window::standard or Window::large, never by widening a number.

use mbrotli::{Compressor, EncoderConfig, Quality, Window};

let config = EncoderConfig::default()
    .with_quality(Quality::Q5)
    .with_window(Window::large(30)?);
let mut encoder = Compressor::new(config)?;

let compressed = encoder.compress("large window ".repeat(1000).as_bytes())?;

// The stream carries the RFC 9841 header, so it needs a decoder expecting one.
assert_eq!(compressed[0], 0b0001_0001);

Qualities 0, 1 and 2 write distances through a model built for the RFC 7932 alphabet, so Compressor::new refuses a Large Window there rather than quietly dropping the request.

§Shared dictionaries

RFC 9841 also lets a caller attach up to fifteen LZ77 prefix dictionaries in front of a stream. A PreparedDictionary is immutable and holds no per-stream state, so any number of compressors may borrow one at once without a lock.

use mbrotli::dictionary::DictionaryBuilder;
use mbrotli::{Compressor, EncoderConfig, Quality};

let dictionary = DictionaryBuilder::new()
    .add_prefix(&b"HTTP/1.1 200 OK\r\nContent-Type: "[..])
    .build()?;
let mut encoder = Compressor::new(EncoderConfig::default().with_quality(Quality::Q5))?;

let payload = b"Content-Type: text/html; charset=utf-8";
assert!(
    encoder.compress_with_dictionary(&dictionary, payload)?.len()
        < encoder.compress(payload)?.len()
);

Below quality five no match finder can consult a dictionary, and one handed to such a compressor is refused with EncodeError::DictionaryUnsupportedForQuality rather than ignored: a stream compressed without the dictionary it was given decodes perfectly well, which is what would make the mistake invisible.

The experimental feature adds serialized shared dictionaries, custom word and transform indexes, headerless stream continuations, and the separate Shared Brotli framing writer. Equivalent-C-streaming byte comparisons do not cover every extension. Rust API/backend identity and decoder compatibility remain required for equivalent stream settings.

Re-exports§

pub use decompressor::DecodeFailure;
pub use decompressor::DecodeOperation;
pub use decompressor::DecodeProgress;
pub use decompressor::DecoderSession;
pub use decompressor::DecoderStatus;
pub use decompressor::Decompressor;
pub use decompressor::DecompressorBuilder;
pub use decompressor::DecodeConfigError;
pub use decompressor::DecodeError;
pub use decompressor::DecodeLimits;
pub use decompressor::DecodeStreamConfig;
pub use decompressor::DecoderConfig;
pub use decompressor::InvalidDataKind;
pub use decompressor::MemberMode;
pub use decompressor::OutputSize;
pub use decompressor::WindowLimit;
pub use compressor::BlockBits;
pub use compressor::BlockSize;
pub use compressor::CompressionMode;
pub use compressor::Compressor;
pub use compressor::CompressorBuilder;
pub use compressor::DistanceParams;
pub use compressor::EncodeError;
pub use compressor::EncoderConfig;
pub use compressor::EncoderSession;
pub use compressor::EncoderStatus;
pub use compressor::InputSize;
pub use compressor::LiteralContextMode;
pub use compressor::Operation;
pub use compressor::Progress;
pub use compressor::Quality;
pub use compressor::SizeOverflow;
pub use compressor::StreamConfig;

Modules§

compressor
The compressor: configuration, the encoder itself, and everything it needs.
decompressor
Incremental Brotli decompression and explicit resource policies.
dictionary
Immutable dictionaries for the enabled codecs.
io
Streaming I/O adapters for compression and decompression.

Structs§

Backend
A supported execution backend, independent of the SIMD implementation crate.
Window
The sliding window: how wide it is, and which header declares it.

Enums§

ConfigError
Error returned when a configuration cannot be expressed or cannot be used.
RetentionPolicy
What a codec keeps allocated between operations.
WindowEncoding
Which header a Window is written with.