mbrotli 0.1.4

Fast Brotli (RFC 7932 and RFC 9841) compression in safe Rust, byte-identical to Google's reference encoder
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
//! Experimental RFC 9841 containers over a reusable compressor.
//!
//! Finish each resource, then finish the container. Neither destructor writes.
//! Chunk methods accept a chunk into a bounded queue; [`FramedWriter::flush`]
//! drains that queue. After a sink error, retry draining or finalization.

mod core;

use super::dictionary::PreparedDictionary;
use super::{Compressor, EncodeError, EncoderSession, StreamConfig};
use std::io::{self, Write};
use thiserror::Error;

/// Container policy and explicit resource ceilings.
#[derive(Debug, Clone, Copy)]
pub struct FramingConfig {
    /// Include a final footer and permit multiple resources and metadata.
    pub container: bool,
    /// Emit a central directory containing every data and metadata header.
    pub central_directory: bool,
    /// Repeat all resource metadata before the central directory.
    pub repeat_metadata: bool,
    /// Maximum uncompressed input retained for one resource chunk (default 64 KiB).
    pub chunk_bytes: usize,
    /// Maximum aggregate metadata content (default 1 MiB).
    pub max_metadata_bytes: usize,
    /// Maximum framing storage (default 8 MiB), excluding the sink, compressor
    /// workspace, and separately prepared dictionaries.
    pub max_buffer_bytes: usize,
    /// Maximum number of resources (default 10,000).
    pub max_resources: u64,
    /// Maximum number of chunks, including generated directory/footer chunks.
    pub max_chunks: u64,
}

impl Default for FramingConfig {
    fn default() -> Self {
        Self {
            container: true,
            central_directory: true,
            repeat_metadata: false,
            chunk_bytes: 65536,
            max_metadata_bytes: 1 << 20,
            max_buffer_bytes: 8 << 20,
            max_resources: 10000,
            max_chunks: 1000000,
        }
    }
}

/// A caller-supplied 256-bit HighwayHash value. No key or hashing policy is implied.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DictionaryId(pub [u8; 32]);

/// An explicit dictionary source, in decoder attachment order.
#[derive(Debug, Clone, Copy)]
pub enum DictionaryReference {
    /// An application-resolved external prefix dictionary.
    PrefixId(DictionaryId),
    /// An application-resolved serialized dictionary.
    SerializedId(DictionaryId),
    /// A complete, earlier resource containing prefix bytes.
    PrefixResource(u64),
    /// A complete, earlier resource containing a serialized dictionary.
    SerializedResource(u64),
    /// The contents of an earlier individual chunk, used as a prefix.
    PrefixChunk(u64),
}

/// Resource visibility and optional caller-supplied checksum.
#[derive(Debug, Default, Clone, Copy)]
pub struct ResourceOptions {
    /// Suppress implicit extraction, for example for dictionary resources.
    pub hidden: bool,
    /// Checksum of the whole uncompressed resource, emitted on its last chunk.
    pub id: Option<DictionaryId>,
}

/// Where metadata applies.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MetadataKind {
    /// Applies to the next resource; permits `id`, `mt`, and uppercase codes.
    Resource,
    /// Applies to the preceding resource; permits uppercase codes only.
    Footer,
    /// Applies to the container; permits uppercase codes only.
    Global,
}

/// One borrowed metadata field. Codes and reserved value shapes are validated.
#[derive(Debug, Clone, Copy)]
pub struct MetadataField<'a> {
    /// Two uppercase ASCII letters, or a recognized lowercase code.
    pub code: [u8; 2],
    /// Raw field content. `id` is UTF-8; `mt` is an eight-byte signed timestamp.
    pub value: &'a [u8],
}

/// Compression for a self-contained metadata chunk.
///
/// `Brotli` uses the borrowed compressor's configuration (including Large
/// Window). Shared references must match the supplied dictionary and attachment
/// order. Repeated metadata permits only external references through this API.
#[derive(Debug, Default, Clone, Copy)]
pub enum MetadataEncoding<'a> {
    /// Store the serialized fields without compression.
    #[default]
    Uncompressed,
    /// Start a fresh Brotli stream without an attached dictionary.
    Brotli,
    /// Start a fresh Shared Brotli stream with explicit dictionary references.
    Shared {
        /// Immutable dictionary borrowed only while this metadata is queued.
        dictionary: &'a PreparedDictionary,
        /// Caller-supplied references in decoder attachment order.
        references: &'a [DictionaryReference],
    },
}

/// Independent encodings for original and repeated metadata.
#[derive(Debug, Default, Clone, Copy)]
pub struct MetadataOptions<'a> {
    /// Encoding of the metadata adjacent to its resource, or global metadata.
    pub encoding: MetadataEncoding<'a>,
    /// Encoding of its repeated copy, when repetition is enabled.
    pub repeated_encoding: MetadataEncoding<'a>,
}

/// Failure to validate, encode, or deliver a container.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum FramingError {
    /// Invalid options, references, metadata, or chunk order.
    #[error("invalid framing operation: {0}")]
    Invalid(&'static str),
    /// A configured resource ceiling would be exceeded before allocation.
    #[error("framing resource limit exceeded: {0}")]
    Limit(&'static str),
    /// A wire size or offset cannot fit the RFC's 63-bit varint.
    #[error("framing size or offset overflow")]
    Overflow,
    /// Compression failed. The resource must be abandoned.
    #[error("resource encoding failed: {0}")]
    Encode(#[from] EncodeError),
    /// Sink failure; the unwritten suffix is retained for retry.
    #[error("container output failed: {0}")]
    Io(#[from] io::Error),
}

impl From<FramingError> for io::Error {
    fn from(error: FramingError) -> Self {
        match error {
            FramingError::Io(error) => error,
            other => Self::other(other),
        }
    }
}

/// Recoverable finalization failure retaining the writer and its pending bytes.
#[derive(Debug)]
pub struct FramingFinishError<T> {
    /// Writer to retry or recover the sink from.
    pub writer: T,
    /// Failure reported by the last finalization attempt.
    pub error: FramingError,
}

/// A container borrowing one worker-local compressor.
#[derive(Debug)]
pub struct FramedWriter<'c, W> {
    compressor: &'c mut Compressor,
    core: core::Container<W>,
}

impl Compressor {
    /// Starts an experimental RFC 9841 container without performing I/O.
    ///
    /// The header is queued; the first operation or `flush` delivers it.
    /// Allocation is bounded by `config`. Drop never finalizes a container.
    ///
    /// # Errors
    /// Rejects inconsistent profiles and buffer limits.
    ///
    /// # Examples
    /// ```
    /// use mbrotli::{Compressor, framing::{FramingConfig, ResourceOptions}};
    /// use std::io::Write;
    /// let mut compressor = Compressor::new(Default::default())?;
    /// let mut container = compressor.framed_writer(Vec::new(), FramingConfig::default())?;
    /// let mut resource = container.resource(ResourceOptions::default(), Default::default())?;
    /// resource.write_all(b"a resource")?;
    /// resource.try_finish()?;
    /// drop(resource);
    /// let bytes = container.finish().map_err(|failure| failure.error)?;
    /// assert_eq!(&bytes[..4], &[0x91, 0x0a, 0x42, 0x52]);
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    pub fn framed_writer<W: Write>(
        &mut self,
        writer: W,
        config: FramingConfig,
    ) -> Result<FramedWriter<'_, W>, FramingError> {
        Ok(FramedWriter {
            compressor: self,
            core: core::Container::new(writer, config)?,
        })
    }
}

impl<W: Write> FramedWriter<'_, W> {
    /// Starts a Brotli resource. Input is buffered at most one chunk at a time.
    ///
    /// # Errors
    /// Rejects invalid chunk order, exhausted limits, or invalid stream settings;
    /// propagates a pending sink error before starting the new resource.
    pub fn resource(
        &mut self,
        options: ResourceOptions,
        stream: StreamConfig,
    ) -> Result<ResourceWriter<'_, 'static, W>, FramingError> {
        if stream.stream_offset() != 0 {
            return Err(FramingError::Invalid(
                "a new resource requires a stream header; its offset must be zero",
            ));
        }
        self.core.begin()?;
        let references =
            if self.compressor.config().window().encoding() == super::WindowEncoding::Large {
                vec![0]
            } else {
                Vec::new()
            };
        let session = self.compressor.start(stream)?;
        Ok(ResourceWriter::new(
            &mut self.core,
            Some(session),
            options,
            references,
        ))
    }

    /// Starts a Shared Brotli resource with explicit out-of-band references.
    ///
    /// References must describe the bytes used to prepare `dictionary`, in the
    /// same order. Identifiers are caller-supplied; no resolution or hashing runs.
    ///
    /// # Errors
    /// As [`Self::resource`], plus malformed, forward, or excessive references.
    pub fn resource_with_dictionary<'a, 'd>(
        &'a mut self,
        options: ResourceOptions,
        stream: StreamConfig,
        dictionary: &'d PreparedDictionary,
        references: &[DictionaryReference],
    ) -> Result<ResourceWriter<'a, 'd, W>, FramingError> {
        if stream.stream_offset() != 0 {
            return Err(FramingError::Invalid(
                "a new resource requires a stream header; its offset must be zero",
            ));
        }
        let encoded = self.core.references(references)?;
        self.core.begin()?;
        let session = self.compressor.start_with_dictionary(dictionary, stream)?;
        Ok(ResourceWriter::new(
            &mut self.core,
            Some(session),
            options,
            encoded,
        ))
    }

    /// Starts an uncompressed resource with the same bounded, retryable writer.
    ///
    /// # Errors
    /// Rejects invalid order or resource limits, or a pending sink error.
    pub fn uncompressed_resource(
        &mut self,
        options: ResourceOptions,
    ) -> Result<ResourceWriter<'_, 'static, W>, FramingError> {
        self.core.begin()?;
        Ok(ResourceWriter::new(
            &mut self.core,
            None,
            options,
            Vec::new(),
        ))
    }

    /// Queues validated, uncompressed metadata. Fields are copied once.
    ///
    /// # Errors
    /// Rejects malformed fields, duplicate reserved codes, invalid order or limits.
    pub fn metadata(
        &mut self,
        kind: MetadataKind,
        fields: &[MetadataField<'_>],
    ) -> Result<(), FramingError> {
        self.metadata_with_options(kind, fields, MetadataOptions::default())
    }

    /// Queues metadata with explicit, independent compression of its repeated copy.
    ///
    /// All fields, references and staging limits are checked before compression.
    /// A sink error leaves any earlier pending chunk retryable; once accepted,
    /// this chunk is drained through [`Self::flush`] or [`Self::try_finish`].
    ///
    /// # Errors
    /// Rejects malformed fields, invalid references, ordering and limits, and
    /// propagates compression or pending sink errors without accepting metadata.
    ///
    /// # Examples
    /// ```
    /// use mbrotli::{Compressor, framing::*};
    /// let mut compressor = Compressor::new(Default::default())?;
    /// let mut writer = compressor.framed_writer(Vec::new(), FramingConfig::default())?;
    /// writer.metadata_with_options(MetadataKind::Global,
    ///     &[MetadataField { code: *b"AB", value: b"application metadata" }],
    ///     MetadataOptions { encoding: MetadataEncoding::Brotli, ..Default::default() })?;
    /// let bytes = writer.finish().map_err(|e| e.error)?;
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    pub fn metadata_with_options(
        &mut self,
        kind: MetadataKind,
        fields: &[MetadataField<'_>],
        options: MetadataOptions<'_>,
    ) -> Result<(), FramingError> {
        self.core.metadata(self.compressor, kind, fields, options)
    }

    /// Selects the field codes copied into every repeated metadata chunk.
    ///
    /// Call before emitting any metadata. By default every field is repeated;
    /// an empty selection emits one empty repeated chunk per original. The
    /// selection applies globally, preserving RFC field-presence consistency.
    ///
    /// # Errors
    /// Rejects invalid/duplicate codes, disabled repetition, late changes,
    /// and exhausted retained-buffer limits. This performs no sink I/O.
    ///
    /// # Examples
    /// ```
    /// use mbrotli::{Compressor, framing::*};
    /// let mut compressor = Compressor::new(Default::default())?;
    /// let config = FramingConfig { repeat_metadata: true, ..Default::default() };
    /// let mut writer = compressor.framed_writer(Vec::new(), config)?;
    /// writer.repeat_metadata_fields(&[*b"id"])?;
    /// writer.metadata(MetadataKind::Resource,
    ///     &[MetadataField { code: *b"id", value: b"example.txt" }])?;
    /// writer.uncompressed_resource(Default::default())?.try_finish()?;
    /// let bytes = writer.finish().map_err(|e| e.error)?;
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    pub fn repeat_metadata_fields(&mut self, codes: &[[u8; 2]]) -> Result<(), FramingError> {
        self.core.repeat_metadata_fields(codes)
    }

    /// Queues a single padding chunk with `bytes` zero content bytes.
    ///
    /// # Errors
    /// Rejects exhausted buffer/chunk limits or a pending sink error.
    pub fn padding(&mut self, bytes: usize) -> Result<(), FramingError> {
        self.core.padding(bytes)
    }

    /// Drains queued bytes and flushes the sink; retry after a recoverable error.
    ///
    /// # Errors
    /// Reports sink errors while retaining the unwritten suffix.
    pub fn flush(&mut self) -> Result<(), FramingError> {
        self.core.drain()?;
        self.core.writer.flush()?;
        Ok(())
    }

    /// Finalizes repeats, directory and footer, retaining progress on sink errors.
    ///
    /// # Errors
    /// Rejects an unfinished/abandoned resource or reports a retryable sink error.
    pub fn try_finish(&mut self) -> Result<(), FramingError> {
        self.core.finish()
    }

    /// Finalizes and returns the sink, or preserves this writer for retry.
    ///
    /// # Errors
    /// As [`Self::try_finish`].
    pub fn finish(mut self) -> Result<W, Box<FramingFinishError<Self>>> {
        match self.try_finish() {
            Ok(()) => Ok(self.core.writer),
            Err(error) => Err(Box::new(FramingFinishError {
                writer: self,
                error,
            })),
        }
    }

    /// Borrows the sink, for inspection.
    pub const fn get_ref(&self) -> &W {
        &self.core.writer
    }

    /// Borrows the sink to repair an I/O failure. Do not write bytes through it.
    pub fn get_mut(&mut self) -> &mut W {
        &mut self.core.writer
    }

    /// Returns the offset at which the next queued chunk will start.
    /// Record it immediately before a resource or metadata call to reference
    /// that content from a later resource.
    pub const fn next_chunk_offset(&self) -> u64 {
        self.core.offset()
    }

    /// Abandons the container and returns the sink. No I/O is performed.
    pub fn into_inner(self) -> W {
        self.core.writer
    }
}

/// One bounded resource stream. Finish explicitly before starting another.
#[derive(Debug)]
pub struct ResourceWriter<'a, 'd, W> {
    inner: core::Resource<'a, 'd, W>,
}

impl<'a, 'd, W: Write> ResourceWriter<'a, 'd, W> {
    fn new(
        core: &'a mut core::Container<W>,
        session: Option<EncoderSession<'a, 'd>>,
        options: ResourceOptions,
        references: Vec<u8>,
    ) -> Self {
        Self {
            inner: core::Resource::new(core, session, options, references),
        }
    }

    /// Finishes the resource and drains its last chunk. Safe to retry after I/O errors.
    ///
    /// # Errors
    /// Reports encoding/limit failures or a sink error with its suffix retained.
    pub fn try_finish(&mut self) -> Result<(), FramingError> {
        self.inner.try_finish()
    }

    /// Borrows the sink to repair a failure. Do not insert container bytes.
    pub fn get_mut(&mut self) -> &mut W {
        self.inner.get_mut()
    }
}

impl<W: Write> Write for ResourceWriter<'_, '_, W> {
    fn write(&mut self, input: &[u8]) -> io::Result<usize> {
        self.inner.write(input)
    }
    fn flush(&mut self) -> io::Result<()> {
        self.inner.flush()
    }
}