RFC 9841 INFORMATIONAL COMPRESSION · BROTLI · DATA FORMAT

Shared Brotli Compressed Data Format

Brotli compression extended with shared dictionaries, a huge sliding window and a container format.

On rfc-editor.org ↗
PUBLISHED
September 2025
AUTHORS
J. Alakuijala, T. Duong, E. Kliuchnikov, Z. Szabadka, L. Vandevenne
STREAM
IETF
BUILDS ON
RFC 7932 — Brotli
LARGE WINDOW WBITS
10 to 62
DICTIONARY FILE SIGNATURE
Bytes 91 and 0
FRAMING FORMAT SIGNATURE
Bytes 0x91, 0x0a, 0x42, 0x52
DICTIONARY REFERENCES PER CHUNK
1 serialized and 15 prefix at most
DICTIONARY HASH TYPE
256-bit HighwayHash
TRANSFORM OPERATIONS
23, numbered 0 to 22
DOI
10.17487/RFC9841

RFC text

Contents
Original text · RFC Editor View on rfc-editor.org ↗
Internet Engineering Task Force (IETF)                     J. Alakuijala
Request for Comments: 9841                                      T. Duong
Updates: 7932                                             E. Kliuchnikov
Category: Informational                                      Z. Szabadka
ISSN: 2070-1721                                       L. Vandevenne, Ed.
                                                            Google, Inc.
                                                          September 2025


                  Shared Brotli Compressed Data Format

Abstract

   This specification defines a data format for shared brotli
   compression, which adds support for shared dictionaries, large
   window, and a container format to brotli (RFC 7932).  Shared
   dictionaries and large window support allow significant compression
   gains compared to regular brotli.  This document specifies an
   extension to the method defined in RFC 7932.

Status of This Memo

   This document is not an Internet Standards Track specification; it is
   published for informational purposes.

   This document is a product of the Internet Engineering Task Force
   (IETF).  It represents the consensus of the IETF community.  It has
   received public review and has been approved for publication by the
   Internet Engineering Steering Group (IESG).  Not all documents
   approved by the IESG are candidates for any level of Internet
   Standard; see Section 2 of RFC 7841.

   Information about the current status of this document, any errata,
   and how to provide feedback on it may be obtained at
   https://www.rfc-editor.org/info/rfc9841.

Copyright Notice

   Copyright (c) 2025 IETF Trust and the persons identified as the
   document authors.  All rights reserved.

   This document is subject to BCP 78 and the IETF Trust's Legal
   Provisions Relating to IETF Documents
   (https://trustee.ietf.org/license-info) in effect on the date of
   publication of this document.  Please review these documents
   carefully, as they describe your rights and restrictions with respect
   to this document.  Code Components extracted from this document must
   include Revised BSD License text as described in Section 4.e of the
   Trust Legal Provisions and are provided without warranty as described
   in the Revised BSD License.

Table of Contents

   1.  Introduction
     1.1.  Purpose
     1.2.  Intended Audience
     1.3.  Scope
     1.4.  Compliance
     1.5.  Definitions of Terms and Conventions Used
       1.5.1.  Packing into Bytes
   2.  Shared Brotli Overview
   3.  Shared Dictionaries
     3.1.  Custom Static Dictionaries
       3.1.1.  Transform Operations
     3.2.  LZ77 Dictionaries
   4.  Varint Encoding
   5.  Shared Dictionary Stream
   6.  Large Window Brotli Compressed Data Stream
   7.  Shared Brotli Compressed Data Stream
   8.  Shared Brotli Framing Format Stream
     8.1.  Main Format
     8.2.  Chunk Format
     8.3.  Metadata Format
     8.4.  Chunk Specifications
       8.4.1.  Padding Chunk (Type 0)
       8.4.2.  Metadata Chunk (Type 1)
       8.4.3.  Data Chunk (Type 2)
       8.4.4.  First Partial Data Chunk (Type 3)
       8.4.5.  Middle Partial Data Chunk (Type 4)
       8.4.6.  Last Partial Data Chunk (Type 5)
       8.4.7.  Footer Metadata Chunk (Type 6)
       8.4.8.  Global Metadata Chunk (Type 7)
       8.4.9.  Repeat Metadata Chunk (Type 8)
       8.4.10. Central Directory Chunk (Type 9)
       8.4.11. Final Footer Chunk (Type 10)
       8.4.12. Chunk Ordering
   9.  Security Considerations
   10. IANA Considerations
   11. References
     11.1.  Normative References
     11.2.  Informative References
   Acknowledgments
   Authors' Addresses

1.  Introduction

1.1.  Purpose

   The purpose of this specification is to extend the brotli compressed
   data format [RFC7932] with new abilities that allow further
   compression gains.

   *  Shared dictionaries allow a static shared context between encoder
      and decoder for significant compression gains.

   *  Large window brotli allows much larger back reference distances to
      give compression gains for files over 16 MiB.

   *  The framing format is a container format that allows storage of
      multiple resources and references dictionaries.

   This document is the authoritative specification of shared brotli
   data formats and the backwards compatible changes to brotli.  This
   document also defines the following:

   *  The data format of serialized shared dictionaries

   *  The data format of the framing format

   *  The encoding of window bits and distances for large window brotli
      in the brotli data format

   *  The encoding of shared dictionary references in the brotli data
      format

1.2.  Intended Audience

   This specification is intended for use by software implementers to
   compress data into and/or decompress data from the shared brotli
   dictionary format.

   The text of the specification assumes a basic background in
   programming at the level of bits and other primitive data
   representations.  Familiarity with the technique of LZ77 coding
   [LZ77] is helpful, but not required.

1.3.  Scope

   This specification defines a data format for shared brotli
   compression, which adds support for dictionaries and extended
   features to brotli [RFC7932].

1.4.  Compliance

   Unless otherwise indicated below, a compliant decompressor must be
   able to accept and decompress any data set that conforms to all the
   specifications presented here.  Additionally, a compliant compressor
   must produce data sets that conform to all the specifications
   presented here.

1.5.  Definitions of Terms and Conventions Used

   Byte:  8 bits stored or transmitted as a unit (same as an octet).
      For this specification, a byte is exactly 8 bits, even on machines
      that store a character on a number of bits different from eight.
      See below for the numbering of bits within a byte.

   String:  A sequence of arbitrary bytes.

   Bytes stored within a computer do not have a "bit order" since they
   are always treated as a unit.  However, a byte considered as an
   integer between 0 and 255 does have a most significant bit (MSB) and
   least significant bit (LSB), and since we write numbers with the most
   significant digit on the left, we also write bytes with the MSB on
   the left.  In the diagrams below, the bits of a byte are written so
   that bit 0 is the LSB, i.e., the bits are numbered as follows:

      +--------+
      |76543210|
      +--------+

   Within a computer, a number may occupy multiple bytes.  All multi-
   byte numbers in the format described here are unsigned and stored
   with the least significant byte first (at the lower memory address).
   For example, the decimal 16-bit number 520 is stored as:

      0        1
      +--------+--------+
      |00001000|00000010|
      +--------+--------+
      ^        ^
      |        |
      |        + more significant byte = 2 x 256
      + less significant byte = 8

1.5.1.  Packing into Bytes

   This document does not address the issue of the order in which bits
   of a byte are transmitted on a bit-sequential medium, since the final
   data format described here is byte- rather than bit-oriented.
   However, the compressed block format is described below as a sequence
   of data elements of various bit lengths, not a sequence of bytes.
   Therefore, we must specify how to pack these data elements into bytes
   to form the final compressed byte sequence:

   *  Data elements are packed into bytes in order of increasing bit
      number within the byte, i.e., starting with the LSB of the byte.

   *  Data elements other than prefix codes are packed starting with the
      LSB of the data element.  These are referred to here as integer
      values and are considered unsigned.

   *  Prefix codes are packed starting with the MSB of the code.

   In other words, if one were to print out the compressed data as a
   sequence of bytes starting with the first byte at the *right* margin
   and proceeding to the *left*, with the MSB of each byte on the left
   as usual, one would be able to parse the result from right to left
   with fixed-width elements in the correct MSB-to-LSB order and prefix
   codes in bit-reversed order (i.e., with the first bit of the code in
   the relative LSB position).

   As an example, consider packing the following data elements into a
   sequence of 3 bytes: 3-bit integer value 6, 4-bit integer value 2,
   3-bit prefix code b'110, 2-bit prefix code b'10, and 12-bit integer
   value 3628.

        byte 2   byte 1   byte 0
      +--------+--------+--------+
      |11100010|11000101|10010110|
      +--------+--------+--------+
       ^            ^ ^   ^   ^
       |            | |   |   |
       |            | |   |   +------ integer value 6
       |            | |   +---------- integer value 2
       |            | +-------------- prefix code 110
       |            +---------------- prefix code 10
       +----------------------------- integer value 3628

2.  Shared Brotli Overview

   Shared brotli extends brotli [RFC7932] with support for shared
   dictionaries, a larger LZ77 window, and a framing format.

3.  Shared Dictionaries

   A shared dictionary is a piece of data shared by a compressor and
   decompressor.  The compressor can take advantage of the dictionary
   context to encode the input in a more compact manner.  The compressor
   and the decompressor must use exactly the same dictionary.  A shared
   dictionary is specially useful to compress short input sequences.

   A shared brotli dictionary can use two methods of sharing context:

   LZ77 dictionary:  The encoder and decoder could refer to a given
      sequence of bytes.  Multiple LZ77 dictionaries can be set.

   Custom static dictionary:  A word list with transforms.  The encoder
      and decoder will replace the static dictionary data with the data
      in the shared dictionary.  The original static dictionary is
      described in Section 8 in [RFC7932].  The original data from
      Appendices A and B of [RFC7932] will be replaced.  In addition, it
      is possible to dynamically switch this dictionary based on the
      data compression context and/or include a reference to the
      original dictionary in the custom dictionary.

   If no shared dictionary is set, the decoder behaves the same as in
   [RFC7932] on a brotli stream.

   If a shared dictionary is set, then it can set LZ77 dictionaries,
   override static dictionary words, and/or override transforms.

3.1.  Custom Static Dictionaries

   If a custom word list is set, then the following behaviors of the
   decoder defined in [RFC7932] are overridden:

      Instead of the Static Dictionary Data from Appendix A of
      [RFC7932], one or more word lists from the custom static
      dictionary data are used.

      Instead of NDBITS at the end of Appendix A of [RFC7932], a custom
      SIZE_BITS_BY_LENGTH per custom word list is used.

      The copy length for a static dictionary reference must be between
      4 and 31 and may not be a value for which SIZE_BITS_BY_LENGTH of
      this dictionary is 0.

   If a custom transforms list is set without context dependency, then
   the following behaviors of the decoder defined in [RFC7932] are
   overridden:

      The "List of Word Transformations" from Appendix B of [RFC7932] is
      overridden by one or more lists of custom prefixes, suffixes, and
      transform operations.

      The transform_id must be smaller than the number of transforms
      given in the custom transforms list.

   If the dictionary is context dependent, it includes a lookup table of
   64 word list and transform list combinations.  When resolving a
   static dictionary word, the decoder computes the literal Context ID
   as described in Section 7.1 of [RFC7932].  The literal Context ID is
   used as the index in the lookup tables to select the word list and
   transforms to use.  If the dictionary is not context dependent, this
   ID is implicitly 0 instead.

   If a distance goes beyond the dictionary for the current ID and
   multiple word/transform list combinations are defined, then the next
   dictionary is used in the following order:

   *  If context dependent:

      -  use the index matching the current context first, and then

      -  use the same order as defined in the shared dictionary
         (excluding the current context) next.

   *  If not context dependent:

      -  use the same order as defined in the shared dictionary.

3.1.1.  Transform Operations

   A shared dictionary may include custom word transformations to
   replace those specified in Section 8 and Appendix B of [RFC7932].  A
   transform consists of a possible prefix, a transform operation, a
   parameter (for some operations), and a possible suffix.  In the
   shared dictionary format, the transform operation is represented by a
   numerical ID, which is listed in the table below.

                    +====+===========================+
                    | ID | Operation                 |
                    +====+===========================+
                    | 0  | Identity                  |
                    +----+---------------------------+
                    | 1  | OmitLast1                 |
                    +----+---------------------------+
                    | 2  | OmitLast2                 |
                    +----+---------------------------+
                    | 3  | OmitLast3                 |
                    +----+---------------------------+
                    | 4  | OmitLast4                 |
                    +----+---------------------------+
                    | 5  | OmitLast5                 |
                    +----+---------------------------+
                    | 6  | OmitLast6                 |
                    +----+---------------------------+
                    | 7  | OmitLast7                 |
                    +----+---------------------------+
                    | 8  | OmitLast8                 |
                    +----+---------------------------+
                    | 9  | OmitLast9                 |
                    +----+---------------------------+
                    | 10 | FermentFirst              |
                    +----+---------------------------+
                    | 11 | FermentAll                |
                    +----+---------------------------+
                    | 12 | OmitFirst1                |
                    +----+---------------------------+
                    | 13 | OmitFirst2                |
                    +----+---------------------------+
                    | 14 | OmitFirst3                |
                    +----+---------------------------+
                    | 15 | OmitFirst4                |
                    +----+---------------------------+
                    | 16 | OmitFirst5                |
                    +----+---------------------------+
                    | 17 | OmitFirst6                |
                    +----+---------------------------+
                    | 18 | OmitFirst7                |
                    +----+---------------------------+
                    | 19 | OmitFirst8                |
                    +----+---------------------------+
                    | 20 | OmitFirst9                |
                    +----+---------------------------+
                    | 21 | ShiftFirst (by PARAMETER) |
                    +----+---------------------------+
                    | 22 | ShiftAll (by PARAMETER)   |
                    +----+---------------------------+

                                 Table 1

   Operations 0 to 20 are specified in Section 8 of [RFC7932].
   ShiftFirst and ShiftAll transform specifically encoded SCALARs.

   A SCALAR is a 7-, 11-, 16-, or 21-bit unsigned integer encoded with
   1, 2, 3, or 4 bytes, respectively, with the following bit contents:

      7-bit SCALAR:
      +--------+
      |0sssssss|
      +--------+

      11-bit SCALAR:
      +--------+--------+
      |110sssss|XXssssss|
      +--------+--------+

      16-bit SCALAR:
      +--------+--------+--------+
      |1110ssss|XXssssss|XXssssss|
      +--------+--------+--------+

      21-bit SCALAR:
      +--------+--------+--------+--------+
      |11110sss|XXssssss|XXssssss|XXssssss|
      +--------+--------+--------+--------+

   Given the input bytes matching the SCALAR encoding pattern, the
   SCALAR value is obtained by concatenation of the "s" bits, with the
   MSBs coming from the earliest byte.  The "X" bits could have
   arbitrary value.

   An ADDEND is defined as the result of limited sign extension of a
   16-bit unsigned PARAMETER:

      At first, the PARAMETER is zero-extended to 32 bits.  After this,
      0xFF0000 is added if the resulting value is greater or equal than
      0x8000.

   ShiftAll starts at the beginning of the word and repetitively applies
   the following transformation until the whole word is transformed:

      If the next untransformed byte matches the first byte of the 7-,
      11-, 16-, or 21-bit SCALAR pattern, then:

         If the untransformed part of the word is not long enough to
         match the whole SCALAR pattern, then the whole word is marked
         as transformed.

         Otherwise, let SHIFTED be the sum of the ADDEND and the encoded
         SCALAR.  The lowest bits from SHIFTED are written back into the
         corresponding "s" bits.  The "0", "1", and "X" bits remain
         unchanged.  Next, 1, 2, 3, or 4 untransformed bytes are marked
         as transformed according to the SCALAR pattern length.

      Otherwise, the next untransformed byte is marked as transformed.

   ShiftFirst applies the same transformation as ShiftAll, but does not
   iterate.

3.2.  LZ77 Dictionaries

   If an LZ77 dictionary is set, the decoder treats it as a regular LZ77
   copy but behaves as if the bytes of this dictionary are accessible as
   the uncompressed bytes outside of the regular LZ77 window for
   backwards references.

   Let LZ77_DICTIONARY_LENGTH be the length of the LZ77 dictionary.
   Then word_id, described in Section 8 of [RFC7932], is redefined as:

   word_id = distance - (max allowed distance + 1 +
   LZ77_DICTIONARY_LENGTH)

   For the case when LZ77_DICTIONARY_LENGTH is 0, word_id matches the
   [RFC7932] definition.

   Let dictionary_address be:

      LZ77_DICTIONARY_LENGTH + max allowed distance - distance

   Then distance values of <length, distance> pairs [RFC7932] in range
   (max allowed distance + 1)..(LZ77_DICTIONARY_LENGTH + max allowed
   distance) are interpreted as references starting in the LZ77
   dictionary at the byte at dictionary_address.  If length is longer
   than (LZ77_DICTIONARY_LENGTH - dictionary_address), then the
   reference continues to copy (length - LZ77_DICTIONARY_LENGTH +
   dictionary_address) bytes from the regular LZ77 window starting at
   the beginning.

4.  Varint Encoding

   A varint is encoded in base 128 in one or more bytes as follows:

      +--------+--------+             +--------+
      |1xxxxxxx|1xxxxxxx| {0-8 times} |0xxxxxxx|
      +--------+--------+             +--------+

   where the "x" bits of the first byte are the LSBs of the value and
   the "x" bits of the last byte are the MSBs of the value.  The last
   byte must have its MSB set to 0 and all other bytes must have their
   MSBs set to 1 to indicate there is a next byte.

   The maximum allowed amount of bits to read is 63 bits; if the 9th
   byte is present and has its MSB set, then the stream must be
   considered as invalid.

5.  Shared Dictionary Stream

   The shared dictionary stream encodes a custom dictionary for brotli,
   including custom words and/or custom transformations.  A shared
   dictionary may appear as a standalone or as contents of a resource in
   a framing format container.

   A compliant shared brotli dictionary stream must have the following
   format:

   2 bytes:  File signature in hexadecimal format (bytes 91 and 0).

   varint:  LZ77_DICTIONARY_LENGTH.  The number of bytes for an LZ77
      dictionary, or 0 if there is none.  The maximum allowed value is
      the maximum possible sliding window size of brotli or large window
      brotli.

   LZ77_DICTIONARY_LENGTH bytes:  Contents of the LZ77 dictionary.

   1 byte:  NUM_CUSTOM_WORD_LISTS.  May have a value in range 0 to 64.

   NUM_CUSTOM_WORD_LISTS times a word list with the following format
   for each word list:

      28 bytes:  SIZE_BITS_BY_LENGTH.  An array of 28 unsigned 8-bit
         integers, indexed by word lengths 4 to 31.  The value
         represents log2(number of words of this length), with the
         exception of 0 meaning 0 words of this length.  The max allowed
         length value is 15 bits.  OFFSETS_BY_LENGTH is computed from
         this as OFFSETS_BY_LENGTH[i + 1] = OFFSETS_BY_LENGTH[i] +
         (SIZE_BITS_BY_LENGTH[i] ? (i << SIZE_BITS_BY_LENGTH[i]) : 0).

      N bytes:  Words dictionary data, where N is OFFSETS_BY_LENGTH[31]
         + (SIZE_BITS_BY_LENGTH[31] ?  (31 << SIZE_BITS_BY_LENGTH[31]) :
         0), with all the words of shortest length first, then all words
         of the next length, and so on, where there are either 0 or a
         positive power of two number of words for each length.

   1 byte:  NUM_CUSTOM_TRANSFORM_LISTS.  May have a value in range 0 to
      64.

   NUM_CUSTOM_TRANSFORM_LISTS times a transform list with the
   following format for each transform list:

      2 bytes:  PREFIX_SUFFIX_LENGTH.  The length of prefix/suffix data.
         Must be at least 1 because the list must always end with a
         zero-length stringlet even if it is empty.

      NUM_PREFIX_SUFFIX times:  Prefix/suffix stringlet.
         NUM_PREFIX_SUFFIX is the number of stringlets parsed and must
         be in range 1..256.

         1 byte:  STRING_LENGTH.  The length of the entry contents.  0
            for the last (terminating) entry of the transform list.  For
            other entries, STRING_LENGTH must be in range 1..255.  The 0
            entry must be present and must be the last byte of the
            PREFIX_SUFFIX_LENGTH bytes of prefix/suffix data, else the
            stream must be rejected as invalid.

         STRING_LENGTH bytes:  Contents of the prefix/suffix.

      1 byte:  NTRANSFORMS.  Number of transformation triplets.

      NTRANSFORMS times the data for each transform listed below:

         1 byte:  Index of prefix in prefix/suffix data; must be less
            than NUM_PREFIX_SUFFIX.

         1 byte:  Index of suffix in prefix/suffix data; must be less
            than NUM_PREFIX_SUFFIX.

         1 byte:  Operation index; must be an index in the table of
            operations listed in Section 3.1.1.

      If and only if at least one transform has operation index
      ShiftFirst or ShiftAll, then NTRANSFORMS times the following:

         2 bytes:  Parameters for the transform.  If the transform does
            not have type ShiftFirst or ShiftAll, the value must be 0.
            ShiftFirst and ShiftAll interpret these bytes as an unsigned
            16-bit integer.

   If NUM_CUSTOM_WORD_LISTS > 0 or NUM_CUSTOM_TRANSFORM_LISTS > 0
   (else implicitly NUM_DICTIONARIES is 1 and points to the brotli
   built-in and there is no context map):

      1 byte:  NUM_DICTIONARIES.  May have a value in range 1 to 64.
         Each dictionary is a combination of a word list and a transform
         list.  Each next dictionary is used when the distance goes
         beyond the previous.  If a CONTEXT_MAP is enabled, then the
         dictionary matching the context is moved to the front in the
         order for this context.

      NUM_DICTIONARIES times the DICTIONARY_MAP, which contains:

         1 byte:  Index into a custom word list or value
            NUM_CUSTOM_WORD_LISTS to indicate using the brotli [RFC7932]
            built-in default word list.

         1 byte:  Index into a custom transform list or value
            NUM_CUSTOM_TRANSFORM_LISTS to indicate using the brotli
            [RFC7932] built-in default transform list.

      1 byte:  CONTEXT_ENABLED.  If 0, there is no context map.  If 1, a
         context map used to select the dictionary is encoded as below.

      If CONTEXT_ENABLED is 1, there is a context map for the 64
      brotli [RFC7932] literals contexts:

         64 bytes:  CONTEXT_MAP.  Index into the DICTIONARY_MAP for the
            first dictionary to use for this context.

6.  Large Window Brotli Compressed Data Stream

   Large window brotli allows a sliding window beyond the 24-bit maximum
   of regular brotli [RFC7932].

   The compressed data stream is backwards compatible to brotli
   [RFC7932] and may optionally have the following differences:

   In the encoding of WBITS in the stream header, the following new
   pattern of 14 bits is supported:

      8 bits:  Value 00010001 to indicate a large window brotli stream.

      6 bits:  WBITS.  Must have value in range 10 to 62.

   Distance alphabet:  If the stream is a large window brotli stream,
      the maximum number of extra bits is 62 and the theoretical maximum
      size of the distance alphabet is (16 + NDIRECT + (124 <<
      NPOSTFIX)).  This overrides the value for the distance alphabet
      size given in Section 3.3 of [RFC7932] and affects the number of
      bits in the encoding of the Simple Prefix Code for distances as
      described in Section 3.4 of [RFC7932].  An additional limitation
      to distances, despite the large allowed alphabet size, is that the
      alphabet is not allowed to contain a distance symbol able to
      represent a distance larger than ((1 << 63) - 4) when its extra
      bits have their maximum value.  It depends on NPOSTFIX and NDIRECT
      when this can occur.

   A decoder that does not support 64-bit integers may reject a stream
   if WBITS is higher than 30 or a distance symbol from the distance
   alphabet is able to encode a distance larger than 2147483644.

7.  Shared Brotli Compressed Data Stream

   The format of a shared brotli compressed data stream without a
   framing format is backwards compatible with brotli [RFC7932] with the
   following optional differences:

   *  LZ77 dictionaries as described above are supported.

   *  Custom static dictionaries replacing or extending the static
      dictionary of brotli [RFC7932] with different words or transforms
      are supported.

   *  The stream may have the format of regular brotli [RFC7932] or the
      format of large window brotli as described in Section 6.

8.  Shared Brotli Framing Format Stream

   A compliant shared brotli framing format stream has the format
   described below.

8.1.  Main Format

   4 bytes:  File signature in hexadecimal format (bytes 0x91, 0x0a,
      0x42, and 0x52).  The first byte contains the invalid WBITS
      combination for brotli [RFC7932] and large window brotli.

   1 byte:  Container flags that are 8 bits and have the following
      meanings:

      bits 0 and 1:  Version indicator that must be b'00.  Otherwise,
         the decoder must reject the data stream as invalid.

      bit 2:  If 0, the file contains no final footer, may not contain
         any metadata chunks, may not contain a central directory, and
         may encode only a single resource (using one or more data
         chunks).  If 1, the file may contain one or more resources,
         metadata, and a central directory, and it must contain a final
         footer.

   multiple times:  A chunk, each with the format specified in
      Section 8.2.

8.2.  Chunk Format

   varint:  Length of this chunk excluding this varint but including all
      next header bytes and data.  If the value is 0, then the chunk
      type byte is not present and the chunk type is assumed to be 0.

   1 byte:  CHUNK_TYPE

      0:   padding chunk
      1:   metadata chunk
      2:   data chunk
      3:   first partial data chunk
      4:   middle partial data chunk
      5:   last partial data chunk
      6:   footer metadata chunk
      7:   global metadata chunk
      8:   repeat metadata chunk
      9:   central directory chunk
      10:  final footer

   If CHUNK_TYPE is not padding chunk, central directory, or final
   footer:

      1 byte:  CODEC:

         0:  uncompressed
         1:  keep decoder
         2:  brotli
         3:  shared brotli

   If CODEC is not "uncompressed":

      varint:  Uncompressed size in bytes of the data contained within
         the compressed stream.

   If CODEC is "shared brotli":

      1 byte:  Number of dictionary references.  Multiple dictionary
         references are possible with the following restrictions: there
         can be 1 serialized dictionary and 15 prefix dictionaries
         maximum (a serialized dictionary may already contain one of
         those).  Circular references are not allowed (any dictionary
         reference that directly or indirectly uses this chunk itself as
         dictionary).

      Per dictionary reference:

         1 byte:  Flags:

            bits 0 and 1:  Dictionary source:

               00:  Internal dictionary reference to a full resource by
                    pointer, which can span one or more chunks.  Must
                    point to a full data chunk or a first partial data
                    chunk.

               01:  Internal dictionary reference to single chunk
                    contents by pointer.  May point to any chunk with
                    content (data or metadata).  If a partial data
                    chunk, only this part is the dictionary.  In this
                    case, the dictionary type is not allowed to be a
                    serialized dictionary.

               10:  Reference to a dictionary by hash code of a
                    resource.  The dictionary can come from an external
                    source, such as a different container.  The user of
                    the decoder must be able to provide the dictionary
                    contents given its hash code (even if it comes from
                    this container itself) or treat it as an error when
                    the user does not have it available.

               11:  Invalid bit combination

            bits 2 and 3:  Dictionary type:

               00:  Prefix dictionary, set in front of the sliding
                    window

               01:  Serialized dictionary in the shared brotli format as
                    specified in Section 5.

               10:  Invalid bit combination

               11:  Invalid bit combination

            bits 4-7:  Must be 0

         If hash-based:

            1 byte:  Type of hash used.  Only supported value: 3,
               indicating 256-bit HighwayHash [HWYHASH].

            32 bytes:  256-bit HighwayHash checksum to refer to
               dictionary.

         If pointer based:  Varint-encoded pointer to its chunk in this
            container.  The chunk must come in the container earlier
            than the current chunk.

   X bytes:  Extra header bytes, depending on CHUNK_TYPE.  If present,
      they are specified in the subsequent sections.

   remaining bytes:  The chunk contents.  The uncompressed data in the
      chunk content depends on CHUNK_TYPE and is specified in the
      subsequent sections.  The compressed data has following format
      depending on CODEC:

      *  uncompressed: The raw bytes.

      *  If "keep decoder", the continuation of the compressed stream
         that was interrupted at the end of the previous chunk.  The
         decoder from the previous chunk must be used and its state it
         had at the end of the previous chunk must be kept at the start
         of the decoding of this chunk.

      *  brotli: The bytes are in brotli format [RFC7932].

      *  shared brotli: The bytes are in the shared brotli format
         specified in Section 7.

8.3.  Metadata Format

   All the metadata chunk types use the following format for the
   uncompressed content:

   Per field:
      2 bytes:  Code to identify this metadata field.  This must be two
         lowercase or two uppercase alpha ASCII characters.  If the
         decoder encounters a lowercase field that it does not recognize
         for the current chunk type, non-ASCII characters, or non-alpha
         characters, the decoder must reject the data stream as invalid.
         Uppercase codes may be used for custom user metadata and can be
         ignored by a compliant decoder.

      varint:  Length of the content of this field in bytes, excluding
         the code bytes and this varint.

      N bytes:  The contents of this field.

   The last field is reached when the chunk content end is reached.  If
   the length of the last field does not end at the same byte as the end
   of the uncompressed content of the chunk, the decoder must reject the
   data stream as invalid.

8.4.  Chunk Specifications

8.4.1.  Padding Chunk (Type 0)

   All bytes in this chunk must be zero except for the initial varint
   that specifies the remaining chunk length.

   Since the varint itself takes up bytes as well, when the goal is to
   introduce a number of padding bytes, the dependence of the length of
   the varint on the value it encodes must be taken into account.

   A single byte varint with a value of 0 is a padding chunk of length
   1.  For more padding, use higher varint values.  Do not use multiple
   shorter padding chunks since this is slower to decode.

8.4.2.  Metadata Chunk (Type 1)

   This chunk contains metadata that applies to the resource whose
   beginning is encoded in the subsequent data chunk or first partial
   data chunk.

   The contents of this chunk follows the format described in
   Section 8.3.

   The following field types are recognized:

   id (N bytes):  Name field.  May appear 0 or 1 times.  Has the
      following format: name in UTF-8 encoding, length determined by the
      field length.  Treated generically but may be used as a filename.
      If used as a filename, forward slashes '/' should be used as
      directory separators, relative paths should be used, and filenames
      ending in a slash with 0-length content in the matching data chunk
      should be treated as an empty directory.

   mt (8 bytes):  Modification type.  May appear 0 or 1 times.  Has the
      following format: contains microseconds since epoch, as a little-
      endian, signed two's complement 64-bit integer.

   custom user field:  Any two uppercase ASCII characters.

8.4.3.  Data Chunk (Type 2)

   A data chunk contains the actual data of a resource.

   This chunk has the following extra header bytes:

   1 byte:  Flags:

      bit 0:  If true, indicates this is not a resource that should be
         output implicitly as part of extracting resources from this
         container.  Instead, it may be referred to only explicitly,
         e.g., as a dictionary reference by hash code or offset.  This
         flag should be set for data used as dictionary to improve
         compression of actual resources.

      bit 1:  If true, hash code is given.

      bits 2-7:  Must be zero.

   If hash code is given:

      1 byte:  Type of hash used.  Only supported value: 3, indicating
         256-bit HighwayHash [HWYHASH].

      32 bytes:  256-bit HighwayHash checksum of the uncompressed data.

   The uncompressed content bytes of this chunk are the actual data of
   the resource.

8.4.4.  First Partial Data Chunk (Type 3)

   This chunk contains partial data of a resource.  This is the first
   chunk in a series containing the entire data of the resource.

   The format of this chunk is the same as the format of a data chunk
   (Section 8.4.3) except for the differences noted below.

   The second bit of flags must be set to 0 and no hash code given.

   The uncompressed data size is only of this part of the resource, not
   of the full resource.

8.4.5.  Middle Partial Data Chunk (Type 4)

   This chunk contains partial data of a resource and is neither the
   first nor the last part of the full resource.

   The format of this chunk is the same as the format of a data chunk
   (Section 8.4.3) except for the differences noted below.

   The first and second bits of flags must be set to 0.

   The uncompressed data size is only of this part of the resource, not
   of the full resource.

8.4.6.  Last Partial Data Chunk (Type 5)

   This chunk contains the final piece of partial data of a resource.

   The format of this chunk is the same as the format of a data chunk
   (Section 8.4.3) except for the differences noted below.

   The first bit of flags must be set to 0.

   If a hash code is given, the hash code of the full resource
   (concatenated from all previous chunks and this chunk) is given in
   this chunk.

   The uncompressed data size is only of this part of the resource, not
   of the full resource.

   The type of this chunk indicates that there are no further chunk
   encoding this resource, so the full resource is now known.

8.4.7.  Footer Metadata Chunk (Type 6)

   This metadata applies to the resource whose encoding ended in the
   preceding data chunk or last partial data chunk.

   The contents of this chunk follows the format described in
   Section 8.3.

   There are no lowercase field types defined for footer metadata.
   Uppercase field types can be used as custom user data.

8.4.8.  Global Metadata Chunk (Type 7)

   This metadata applies to the whole container instead of a single
   resource.

   The contents of this chunk follows the format described in
   Section 8.3.

   There are no lowercase field types defined for global metadata.
   Uppercase field types can be used as custom user data.

8.4.9.  Repeat Metadata Chunk (Type 8)

   These chunks optionally repeat metadata that is interleaved between
   data chunks.  To use these chunks, it is necessary to also read
   additional information, such as pointers to the original chunks, from
   the central directory.

   The contents of this chunk follows the format described in
   Section 8.3.

   This chunk has an extra header byte:

   1 byte:  Chunk type of repeated chunk (metadata chunk or footer
      metadata chunk).

   This set of chunks must follow the following restrictions:

   *  It is optional whether or not repeat metadata chunks are present.

   *  If they are present, then they must be present for all metadata
      chunks and footer metadata chunks.

   *  There may be only 1 repeat metadata chunk per repeated metadata
      chunk.

   *  They must appear in the same order as the chunks appear in the
      container, which is also the same order as listed in the central
      directory.

   *  Compression of these chunks is allowed; however, it is not allowed
      to use any internal dictionary except an earlier repeat metadata
      chunk of this series, and it is not allowed for a metadata chunk
      to keep the decoder state if the previous chunk is not a repeat
      metadata chunk.  That is, the series of metadata chunks must be
      decompressible without using other chunks of the framing format
      file.

   The fields contained in this metadata chunk must follow the following
   restrictions:

   *  If a field is present, it must exactly match the corresponding
      field of the copied chunk.

   *  It is allowed to leave out a field that is present in the copied
      chunk.

   *  If a field is present, then it must be present in *all* other
      repeat metadata chunks when the copied chunk contains this field.
      In other words, if you know you can get the name field from a
      repeat chunk, you know that you will be able to get all names of
      all resources from all repeat chunks.

8.4.10.  Central Directory Chunk (Type 9)

   The central directory chunk along with the repeat metadata chunks
   allow quickly finding and listing compressed resources in the
   container file.

   The central directory chunk is always uncompressed and does not have
   the codec byte.  It instead has the following format:

   varint:  Pointer into the file where the repeat metadata chunks are
      located or 0 if they are not present.

   per chunk listed:

      varint:  Pointer into the file where this chunk begins.

      varint:  Number of header bytes N used below.

      N bytes:  Copy of all the header bytes of the pointed at chunk,
         including total size, chunk type byte, codec, uncompressed
         size, dictionary references, and X extra header bytes.  The
         content is not repeated here.

   The last listed chunk is reached when the end of the contents of the
   central directory are reached.  If the end does not match the last
   byte of the central directory, the decoder must reject the data
   stream as invalid.

   If present, the central directory must list all data and metadata
   chunks of all types.

8.4.11.  Final Footer Chunk (Type 10)

   The final footer chunk closes the file and is only present if bit 2
   of the initial container flags was set.

   This chunk has the following content, which is always uncompressed:

   reversed varint:  Size of this entire framing format file, including
      these bytes themselves, or 0 if this size is not given.

   reversed varint:  Pointer to the start of the central directory, or 0
      if there is none.

   A reversed varint has the same format as a varint but its bytes are
   in reversed order, and it is designed to be parsed from the end of
   the file towards the beginning.

8.4.12.  Chunk Ordering

   The chunk ordering must follow the rules described below.  If the
   decoder sees otherwise, it must reject the data stream as invalid.

      Padding chunks may be inserted anywhere, even between chunks for
      which the rules below say no other chunk types may come in
      between.

      Metadata chunks must come immediately before the data chunks of
      the resource they apply to.

      Footer metadata chunks must come immediately after the data chunks
      of the resource they apply to.

      There may be only 0 or 1 metadata chunks per resource.

      There may be only 0 or 1 footer metadata chunks per resource.

      A resource must exist out of either 1 data chunk or 1 first
      partial data chunk, 0 or more middle partial data chunks, and 1
      last partial data chunk, in that order.

      Repeat metadata chunks must follow the rules of Section 8.4.9.

      There may be only 0 or 1 central directory chunks.

      If bit 2 of the container flags is set, there may be only a single
      resource, no metadata chunks of any type, no central directory,
      and no final footer.

      If bit 2 of the container flags is not set, there must be exactly
      1 final footer chunk, and it must be the last chunk in the file.

9.  Security Considerations

   The security considerations for brotli [RFC7932] apply to shared
   brotli as well.

   In addition, the same considerations apply to the decoding of new
   file format streams for shared brotli, including shared dictionaries,
   the framing format, and the shared brotli format.

   The dictionary must be treated with the same security precautions as
   the content because a change to the dictionary can result in a change
   to the decompressed content.

   The CRIME attack [CRIME] shows that it's a bad idea to compress data
   from mixed (e.g., public and private) sources -- the data sources
   include not only the compressed data but also the dictionaries.  For
   example, if you compress secret cookies using a public-data-only
   dictionary, you still leak information about the cookies.

   The dictionary can reveal information about the compressed data and
   vice versa.  That is, data compressed with the dictionary can reveal
   contents of the dictionary when an adversary can control parts of the
   data to compress and see the compressed size.  On the other hand, if
   the adversary can control the dictionary, the adversary can learn
   information about the compressed data.

   The most robust defense against CRIME is not to compress private
   data, e.g., sensitive headers like cookies or any content with
   personally identifiable information (PII).  The challenge has been to
   identify secrets within a vast amount of data to be compressed.
   Cloudflare uses a regular expression [CLOUDFLARE].  Another idea is
   to extend existing web template systems (e.g., Soy [SOY]) to allow
   developers to mark secrets that must not be compressed.

   A less robust idea, but easier to implement, is to randomize the
   compression algorithm, i.e., adding randomly generated padding,
   varying the compression ratio, etc.  The tricky part is to find the
   right balance between cost and security (i.e., on one hand, we don't
   want to add too much padding because it adds a cost to data, but on
   the other hand, we don't want to add too little because the adversary
   can detect a small amount of padding with traffic analysis).

   Additionally, another defense is to not use dictionaries for cross-
   domain requests and to only use shared brotli for the response when
   the origin is the same as where the content is hosted (using CORS).
   This prevents an adversary from using a private dictionary with user
   secrets to compress content hosted on the adversary's origin.  It
   also helps prevent CRIME attacks that try to benefit from a public
   dictionary by preventing data compression with dictionaries for
   requests that do not originate from the host itself.

   The content of the dictionary itself should not be affected by
   external users; allowing adversaries to control the dictionary allows
   a form of chosen plaintext attack.  Instead, only base the dictionary
   on content you control or generic large scale content such as a
   spoken language and update the dictionary with large time intervals
   (days, not seconds) to prevent fast probing.

   The use of HighwayHash [HWYHASH] for dictionary identifiers does not
   guarantee against collisions in an adversarial environment and is
   intended to be used for identifying the dictionary within a trusted,
   known set of dictionaries.  In an adversarial environment, users of
   shared brotli should use another mechanism to validate a negotiated
   dictionary such as a cryptographically proven secure hash.

10.  IANA Considerations

   This document has no IANA actions.

11.  References

11.1.  Normative References

   [HWYHASH]  Alakuijala, J., Cox, B., and J. Wassenberg, "Fast keyed
              hash/pseudo-random function using SIMD multiply and
              permute", DOI 10.48550/arXiv.1612.06257, February 2017,
              <https://arxiv.org/abs/1612.06257>.

   [RFC7932]  Alakuijala, J. and Z. Szabadka, "Brotli Compressed Data
              Format", RFC 7932, DOI 10.17487/RFC7932, July 2016,
              <https://www.rfc-editor.org/info/rfc7932>.

11.2.  Informative References

   [CLOUDFLARE]
              Loring, B., "A Solution to Compression Oracles on the
              Web", The Cloudflare Blog, 27 March 2018,
              <https://blog.cloudflare.com/a-solution-to-compression-
              oracles-on-the-web/>.

   [CRIME]    CVE Program, "CVE-2012-4929",
              <https://www.cve.org/CVERecord?id=CVE-2012-4929>.

   [LZ77]     Ziv, J. and A. Lempel, "A Universal Algorithm for
              Sequential Data Compression", IEEE Transactions on
              Information Theory, vol. 23, no. 3, pp. 337-343,
              DOI 10.1109/TIT.1977.1055714, May 1977,
              <https://doi.org/10.1109/TIT.1977.1055714>.

   [SOY]      Google Developers, "Closure Tools",
              <https://developers.google.com/closure>.

Acknowledgments

   The authors would like to thank Robert Obryk for suggesting
   improvements to the format and the text of the specification.

Authors' Addresses

   Jyrki Alakuijala
   Google, Inc.
   Email: jyrki@google.com


   Thai Duong
   Google, Inc.
   Email: thaidn@google.com


   Evgenii Kliuchnikov
   Google, Inc.
   Email: eustas@google.com


   Zoltan Szabadka
   Google, Inc.
   Email: szabadka@google.com


   Lode Vandevenne (editor)
   Google, Inc.
   Email: lode@google.com

ISO Plain Language

On this page
PROBLEMRegular brotli compresses each stream on its own, so it cannot use data that the sender and receiver already share, and its back references are limited to a 24-bit distance.
MECHANISMShared brotli adds shared dictionaries in two forms, LZ77 dictionaries and custom static dictionaries, a large window mode with window bits from 10 to 62, and a framing format that stores several resources and refers to dictionaries.
WHAT IT ADDSThe formats give significant compression gains over regular brotli and stay backwards compatible with it, so a decoder that does not know about the extensions can still read an ordinary brotli stream.

§1What Shared Brotli Adds

The purpose of this specification is to extend the brotli compressed data format with new abilities that allow further compression gains. Shared dictionaries allow a static shared context between encoder and decoder for significant compression gains. Large window brotli allows much larger back reference distances, to give compression gains for files over 16 MiB. The framing format is a container format that allows storage of multiple resources and references to dictionaries.

The document is the authoritative specification of the shared brotli data formats and of the backwards compatible changes to brotli. It defines the data format of serialized shared dictionaries, the data format of the framing format, the encoding of window bits and distances for large window brotli in the brotli data format, and the encoding of shared dictionary references in the brotli data format.

The scope is a data format for shared brotli compression, adding support for dictionaries and extended features to brotli. The text assumes a basic background in programming at the level of bits and other primitive data representations; familiarity with LZ77 coding is helpful but not required. Compliance is defined in the usual way: a compliant decompressor must be able to accept and decompress any data set that conforms to the specification, and a compliant compressor must produce data sets that conform to it.

Compression works by saying "copy this from a bit earlier". Two things limit how well that works. The sender and receiver can only refer to text one of them has already sent, and only within a limited distance. Shared brotli loosens both limits, and adds a container format so several files can travel together with their dictionary.

§3Shared Dictionaries

A shared dictionary is a piece of data shared by a compressor and a decompressor. The compressor can take advantage of the dictionary context to encode the input in a more compact manner, and the compressor and decompressor must use exactly the same dictionary. A shared dictionary is specially useful to compress short input sequences.

There are two methods of sharing context. An LZ77 dictionary lets the encoder and decoder refer to a given sequence of bytes, and multiple LZ77 dictionaries can be set. A custom static dictionary is a word list with transforms: the encoder and decoder replace the static dictionary data with the data in the shared dictionary. The original static dictionary is described in RFC 7932, and the original data from its appendices is replaced. It is also possible to switch this dictionary dynamically based on the data compression context, or to include a reference to the original dictionary in the custom dictionary.

If no shared dictionary is set, the decoder behaves the same as in RFC 7932 on a brotli stream. If one is set, it can set LZ77 dictionaries, override static dictionary words, and override transforms.

With a custom word list set, three decoder behaviours are overridden: the static dictionary data is replaced by one or more word lists from the custom static dictionary; the size bits at the end of the appendix are replaced by a custom value per word list; and the copy length for a static dictionary reference must be between 4 and 31 and may not be a length for which the size bits of that dictionary are zero. With a custom transforms list set and no context dependency, the list of word transformations is overridden by custom prefixes, suffixes and operations, and the transform identifier must be smaller than the number of transforms in the custom list.

A context-dependent dictionary includes a lookup table of 64 word list and transform list combinations. When resolving a static dictionary word, the decoder computes the literal context ID as in RFC 7932 and uses it as the index into the lookup tables to select the word list and transforms. If the dictionary is not context dependent, that ID is implicitly zero. If a distance goes beyond the dictionary for the current ID and several combinations are defined, the next dictionary is used: with a context-dependent dictionary, the index matching the current context comes first and then the order defined in the shared dictionary; otherwise the defined order is used as it is.

The transform operations are identified by numerical IDs. Operations 0 to 20 are the ones specified in RFC 7932, and two more are added here: ShiftFirst and ShiftAll, which transform specifically encoded scalars. A SCALAR is a 7-, 11-, 16- or 21-bit unsigned integer encoded in one, two, three or four bytes, whose bits come from a pattern where the trailing bits may hold any value. ShiftAll starts at the beginning of the word and applies the transformation repeatedly until the whole word is transformed; ShiftFirst applies the same transformation but does not iterate.

A dictionary has two shapes. One is a block of bytes the encoder can point into as if it were the recent past, which is useful for short inputs where there is not enough history yet. The other replaces the built-in list of common words with a list of your own, optionally chosen per context, so the encoder can say "word 17 of list 3" and both sides know which word that is.

THE FAMILIES OF TRANSFORM OPERATION
  • Identity (0)The word is used exactly as it appears in the dictionary.
  • OmitLast1 to OmitLast9 (1 to 9)The last one to nine bytes of the word are dropped.
  • FermentFirst and FermentAll (10 and 11)The first or all of the characters of the word are transformed.
  • OmitFirst1 to OmitFirst9 (12 to 20)The first one to nine bytes of the word are dropped.
  • ShiftFirst and ShiftAll (21 and 22)New in this document: they shift specifically encoded scalars in the word, and ShiftAll repeats until the whole word is transformed.

§5Varints and the Dictionary Stream

A varint is encoded in base 128 in one or more bytes. The trailing bits of each byte carry the value, and the last byte must have its most significant bit clear while all other bytes must have it set to show there is a next byte. The maximum number of bits to read is 63; if a ninth byte is present with its most significant bit set, the stream is invalid.

The shared dictionary stream encodes a custom dictionary for brotli, including custom words and transforms. It may appear standalone or as the contents of a resource in a framing format container, and its format is fixed. It begins with a two-byte file signature, the bytes 91 and 0. Then comes a varint giving the LZ77 dictionary length, the number of bytes in the LZ77 dictionary or zero if there is none, whose maximum allowed value is the maximum possible sliding window size of brotli or large window brotli. The dictionary contents follow, then one byte giving the number of custom word lists, which may be 0 to 64.

Each word list starts with 28 bytes of size information, one unsigned 8-bit integer per word length from 4 to 31, where the value is the base-2 logarithm of the number of words of that length and zero means no words of that length; the maximum allowed length value is 15 bits. The offsets are computed from this, and the word data follows, with all the words of the shortest length first and then the next length, where for each length there are either zero or a positive power of two number of words.

Next comes one byte giving the number of custom transform lists, which may be 0 to 64. Each transform list starts with two bytes giving the length of the prefix and suffix data, which must be at least 1 because the list always ends with a zero-length stringlet even if it is empty. The stringlets follow: each has one byte of length, zero for the last entry of the list and 1 to 255 otherwise, with the terminating zero entry required to be present and to be the last byte of the prefix and suffix data, or the stream is rejected as invalid. Then one byte gives the number of transformation triplets, and each triplet has one byte indexing the prefix, one byte indexing the suffix and one byte giving the operation index. If and only if at least one transform uses ShiftFirst or ShiftAll, a further two bytes of parameters follow per transform, which must be zero for other operations and are read as an unsigned 16-bit integer by the two shift operations.

If there is more than one custom word list or more than one custom transform list, one byte gives the number of dictionaries, from 1 to 64, each a combination of a word list and a transform list; the next dictionary is used when the distance goes beyond the previous one, and with a context map the dictionary matching the context moves to the front for that context. A dictionary map follows, with one byte indexing a custom word list or the value that means the built-in default word list, one byte indexing a custom transform list or the value that means the built-in default transform list, and one byte saying whether a context map is present. When it is, 64 bytes give, for each of the 64 literal contexts, the index into the dictionary map of the first dictionary to use for that context.

The dictionary itself is a file with a fixed layout: a signature, the block of raw bytes that acts as an LZ77 dictionary, the word lists with a small table saying how many words of each length, and the transform lists with their prefixes and suffixes. Everything that is a count is written in a variable-length form where each byte says whether another byte follows.

§6Large Window and Shared Brotli Streams

Large window brotli allows a sliding window beyond the 24-bit maximum of regular brotli. The compressed data stream is backwards compatible with brotli and may differ in two ways. In the encoding of the window size in the stream header, a new pattern of 14 bits is supported: eight bits with the value 00010001 to indicate a large window brotli stream, followed by six bits of window size that must be in the range 10 to 62.

The second difference is the distance alphabet. In a large window brotli stream the maximum number of extra bits is 62, and the theoretical maximum size of the distance alphabet is 16 plus the number of direct distances plus 124 shifted left by the number of postfix bits. This overrides the value given for the distance alphabet size in RFC 7932 and affects the number of bits in the encoding of the simple prefix code for distances. An additional limitation applies despite the large allowed alphabet size: the alphabet may not contain a distance symbol able to represent a distance larger than (1 shifted left by 63) minus 4 when its extra bits have their maximum value, which depends on the number of postfix bits and direct distances. A decoder that does not support 64-bit integers may reject a stream if the window size is higher than 30, or if a distance symbol can encode a distance larger than 2147483644.

A shared brotli compressed data stream without a framing format is backwards compatible with brotli, with three optional differences: LZ77 dictionaries are supported, custom static dictionaries that replace or extend the static dictionary of RFC 7932 with different words or transforms are supported, and the stream may have the format of regular brotli or the format of large window brotli.

Ordinary brotli can only look back about 16 MiB, because the distance field is 24 bits wide. The large window mode reuses a spare pattern in the stream header to say that the window can be up to 62 bits wide, with the distances stored in a larger alphabet. The bits that do this are new, so a decoder that does not understand them sees an invalid brotli stream and refuses it, which is what keeps the extension safe.

THE THREE STREAM FORMATS
FormatBack reference distanceDictionaries
BrotliLimited by the 24-bit maximumThe built-in static dictionary
Large window brotliWindow bits from 10 to 62The built-in static dictionary
Shared brotliEither the 24-bit maximum or the large windowLZ77 dictionaries and custom static dictionaries

§8The Framing Format

The framing format is a container that can hold several resources and refer to dictionaries. It begins with a four-byte file signature, the bytes 0x91, 0x0a, 0x42 and 0x52, where the first byte contains the invalid window-size combination for both brotli and large window brotli. Then comes one byte of container flags: bits 0 and 1 are a version indicator that must be zero, or the decoder must reject the stream as invalid, and bit 2 says whether the file contains a final footer, may contain metadata chunks and a central directory and may encode several resources, or whether it contains no final footer, no metadata chunks, no central directory and only a single resource. A sequence of chunks follows.

Each chunk starts with a varint giving its length, excluding the varint but including the header bytes and data that follow; a length of zero means the chunk type byte is not present and the type is assumed to be zero. Then one byte gives the chunk type: 0 padding, 1 metadata, 2 data, 3 first partial data, 4 middle partial data, 5 last partial data, 6 footer metadata, 7 global metadata, 8 repeat metadata, 9 central directory, 10 final footer.

Unless the chunk is a padding chunk, a central directory or a final footer, one byte gives the codec: 0 uncompressed, 1 keep decoder, 2 brotli, 3 shared brotli. If the codec is not uncompressed, a varint gives the uncompressed size in bytes of the data in the compressed stream. If the codec is shared brotli, one byte gives the number of dictionary references, and multiple references are possible under restrictions: there can be 1 serialized dictionary and 15 prefix dictionaries at most, and circular references are not allowed, meaning no dictionary reference may directly or indirectly use the chunk itself as its dictionary.

Each dictionary reference carries a byte of flags. Bits 0 and 1 give the source: an internal reference to a full resource by pointer, which may span one or more chunks and must point to a full data chunk or a first partial data chunk; an internal reference to the contents of a single chunk by pointer, which may point to any chunk with content, where a partial data chunk contributes only that part and the dictionary type may not be a serialized dictionary; or a reference by hash code of a resource, which may come from an external source such as a different container, and the user of the decoder must be able to provide the dictionary contents given its hash code or treat it as an error. Bits 2 and 3 give the type: a prefix dictionary, set in front of the sliding window, or a serialized dictionary in the shared brotli format. Bits 4 to 7 must be zero. For a hash-based reference there is one byte giving the type of hash, where the only supported value is 3, indicating a 256-bit HighwayHash, and 32 bytes of checksum. For a pointer-based reference there is a varint-encoded pointer to a chunk in the container, and that chunk must come earlier than the current chunk.

The chunk content then follows. For the uncompressed codec it is the raw bytes. For the keep decoder codec it is the continuation of the compressed stream that was interrupted at the end of the previous chunk, and the decoder from the previous chunk must be used with its state kept. Metadata chunks use a field format of a two-byte code, a varint length and that many bytes of content. The code must be two lowercase or two uppercase ASCII letters; a lowercase code the decoder does not recognise for the current chunk type, non-ASCII characters or non-alphabetic characters make the decoder reject the stream as invalid, while uppercase codes may be used for custom user metadata and can be ignored by a compliant decoder. Two field types are recognised for the metadata chunk: an id field carrying a name in UTF-8 that may be used as a filename, and an mt field carrying the modification type as microseconds since the epoch in a little-endian signed 64-bit integer.

The container is a sequence of chunks. Each chunk says how long it is, what kind it is, and how its data is compressed. The kinds cover the data itself, pieces of a resource that is too big for one chunk, and metadata about the resource. A chunk can also point at an earlier chunk and say "use that as the dictionary", either by position or by a checksum, which lets one file inside the container act as the dictionary for another.

THE CHUNK TYPES IN THE FRAMING FORMAT
  • 0Padding chunk — All bytes zero apart from the length varint.
  • 1Metadata chunk — Metadata applying to the resource that starts in the next data chunk.
  • 2Data chunk — The actual data of a resource.
  • 3First partial data chunk — The first part of a resource split across several chunks.
  • 4Middle partial data chunk — A part of a resource that is neither the first nor the last.
  • 5Last partial data chunk — The final part of a resource split across several chunks.
  • 6Footer metadata chunk — Metadata carried at the end of the container.
  • 7Global metadata chunk — Metadata that applies to the whole container.
  • 8Repeat metadata chunk — Repeats previously seen metadata.
  • 9Central directory chunk — Lists the resources in the container.
  • 10Final footer chunk — The last chunk of the container.

§9Security Considerations

The security considerations for brotli apply to shared brotli as well, and the same considerations apply to decoding the new file format streams, including shared dictionaries, the framing format and the shared brotli format. The dictionary must be treated with the same security precautions as the content, because a change to the dictionary can result in a change to the decompressed content.

The CRIME attack shows that it is a bad idea to compress data from mixed sources, public and private alike, and the data sources include not only the compressed data but also the dictionaries. Compressing secret cookies with a public-data-only dictionary still leaks information about the cookies. The dictionary can reveal information about the compressed data and the other way round: data compressed with the dictionary can reveal the contents of the dictionary when an adversary can control parts of the data and see the compressed size, and an adversary who controls the dictionary can learn about the compressed data.

The most robust defence against CRIME is not to compress private data, such as sensitive headers like cookies or content with personally identifiable information; the challenge has been to identify secrets within a large amount of data to be compressed, and one approach is a regular expression, another is to extend web template systems to let developers mark secrets that must not be compressed. A less robust but easier to implement defence is to randomize the compression algorithm, by adding randomly generated padding or varying the compression ratio, with the tricky part being to balance cost against security, since too much padding costs bandwidth and too little can be detected by traffic analysis. Another defence is not to use dictionaries for cross-domain requests, and to use shared brotli for the response only when the origin is the same as where the content is hosted, which prevents an adversary using a private dictionary with user secrets to compress content hosted on the adversary's origin.

The content of the dictionary itself should not be affected by external users, since allowing adversaries to control the dictionary allows a form of chosen plaintext attack. Instead, base the dictionary on content you control or on generic large-scale content such as a spoken language, and update it with large time intervals, days rather than seconds, to prevent fast probing. The use of HighwayHash for dictionary identifiers does not guarantee against collisions in an adversarial environment.

The dictionary is part of what gets compressed, so it leaks like the content does. If secret data is compressed together with anything the attacker can influence, the attacker can measure the output size and work out the secret. The safest rule is not to compress secrets at all; using a dictionary for one origin's own traffic is fine, but sharing it across origins is not.

§7References

[RFC7932]RFC 7932Alakuijala, J. and Z. Szabadka, "Brotli Compressed Data Format", RFC 7932, DOI 10.17487/RFC7932, July 2016.July 2016
[LZ77]Ziv, J. and A. Lempel, "A Universal Algorithm for Sequential Data Compression", IEEE Transactions on Information Theory, vol. 23, no. 3, pp. 337-343, DOI 10.1109/TIT.1977.1055714, May 1977.