diff --git a/src/brotli.bitreader.js b/src/brotli.bitreader.js index 1049aa8..f0402b9 100644 --- a/src/brotli.bitreader.js +++ b/src/brotli.bitreader.js @@ -31,7 +31,7 @@ const kBitMask = new Uint32Array([ * @typedef {import('./brotli.streams.js').BrotliInput} BrotliInput * @param {BrotliInput} input */ -function BrotliBitReader(input) { +export function BrotliBitReader(input) { this.buf_ = new Uint8Array(BROTLI_IBUF_SIZE) this.input_ = input /* input callback */ @@ -132,5 +132,3 @@ BrotliBitReader.prototype.readBits = function(n_bits) { this.bit_pos_ += n_bits return val } - -export default BrotliBitReader diff --git a/src/brotli.blocks.js b/src/brotli.blocks.js new file mode 100644 index 0000000..dc86fa8 --- /dev/null +++ b/src/brotli.blocks.js @@ -0,0 +1,235 @@ +import { readSymbol } from './brotli.huffman.js' +import { BrotliBitReader } from './brotli.bitreader.js' +import { kBlockLengthPrefixCode } from './brotli.prefix.js' +import { HUFFMAN_MAX_TABLE_SIZE } from './gzip.huffman.js' + +/** + * @import {HuffmanCode} from './brotli.huffman.js' + * @param {number} max_block_type + * @param {HuffmanCode[]} trees + * @param {number} tree_type + * @param {number[]} block_types + * @param {number[]} ringbuffers + * @param {number[]} indexes + * @param {BrotliBitReader} br + */ +export function decodeBlockType(max_block_type, trees, tree_type, block_types, ringbuffers, indexes, br) { + const ringbuffer = tree_type * 2 + const index = tree_type + const type_code = readSymbol(trees, tree_type * HUFFMAN_MAX_TABLE_SIZE, br) + let block_type + if (type_code === 0) { + block_type = ringbuffers[ringbuffer + (indexes[index] & 1)] + } else if (type_code === 1) { + block_type = ringbuffers[ringbuffer + (indexes[index] - 1 & 1)] + 1 + } else { + block_type = type_code - 2 + } + if (block_type >= max_block_type) { + block_type -= max_block_type + } + block_types[tree_type] = block_type + ringbuffers[ringbuffer + (indexes[index] & 1)] = block_type + indexes[index]++ +} + +/** + * @typedef {{ input_end: number, is_metadata: boolean, meta_block_length: number, is_uncompressed: number }} MetaBlockLength + * @param {BrotliBitReader} br + * @returns {MetaBlockLength} + */ +export function decodeMetaBlockLength(br) { + const out = { + meta_block_length: 0, + input_end: 0, + is_uncompressed: 0, + is_metadata: false, + } + + out.input_end = br.readBits(1) + if (out.input_end && br.readBits(1)) { + return out + } + + const size_nibbles = br.readBits(2) + 4 + if (size_nibbles === 7) { + out.is_metadata = true + + if (br.readBits(1) !== 0) + throw new Error('Invalid reserved bit') + + const size_bytes = br.readBits(2) + if (size_bytes === 0) + return out + + for (let i = 0; i < size_bytes; i++) { + const next_byte = br.readBits(8) + if (i + 1 === size_bytes && size_bytes > 1 && next_byte === 0) + throw new Error('Invalid size byte') + + out.meta_block_length |= next_byte << i * 8 + } + } else { + for (let i = 0; i < size_nibbles; i++) { + const next_nibble = br.readBits(4) + if (i + 1 === size_nibbles && size_nibbles > 4 && next_nibble === 0) + throw new Error('Invalid size nibble') + + out.meta_block_length |= next_nibble << i * 4 + } + } + + out.meta_block_length++ + + if (!out.input_end && !out.is_metadata) { + out.is_uncompressed = br.readBits(1) + } + + return out +} + + +/** + * @import {BrotliOutput} from './brotli.streams.js' + * @param {BrotliOutput} output + * @param {number} len + * @param {number} pos + * @param {Uint8Array} ringbuffer + * @param {number} ringbuffer_mask + * @param {BrotliBitReader} br + */ +export function copyUncompressedBlockToOutput(output, len, pos, ringbuffer, ringbuffer_mask, br) { + const rb_size = ringbuffer_mask + 1 + let rb_pos = pos & ringbuffer_mask + let br_pos = br.pos_ & BrotliBitReader.IBUF_MASK + + // For short lengths copy byte-by-byte + if (len < 8 || br.bit_pos_ + (len << 3) < br.bit_end_pos_) { + while (len-- > 0) { + br.readMoreInput() + ringbuffer[rb_pos++] = br.readBits(8) + if (rb_pos === rb_size) { + output.write(ringbuffer, rb_size) + rb_pos = 0 + } + } + return + } + + if (br.bit_end_pos_ < 32) { + throw new Error('copyUncompressedBlockToOutput: br.bit_end_pos_ < 32') + } + + // Copy remaining 0-4 bytes from br.val_ to ringbuffer + while (br.bit_pos_ < 32) { + ringbuffer[rb_pos] = br.val_ >>> br.bit_pos_ + br.bit_pos_ += 8 + rb_pos++ + len-- + } + + // Copy remaining bytes from br.buf_ to ringbuffer + let nbytes = br.bit_end_pos_ - br.bit_pos_ >> 3 + if (br_pos + nbytes > BrotliBitReader.IBUF_MASK) { + const tail = BrotliBitReader.IBUF_MASK + 1 - br_pos + for (let x = 0; x < tail; x++) + ringbuffer[rb_pos + x] = br.buf_[br_pos + x] + + nbytes -= tail + rb_pos += tail + len -= tail + br_pos = 0 + } + + for (let x = 0; x < nbytes; x++) + ringbuffer[rb_pos + x] = br.buf_[br_pos + x] + + rb_pos += nbytes + len -= nbytes + + // If we wrote past the logical end of the ringbuffer, copy the tail of the + // ringbuffer to its beginning and flush the ringbuffer to the output + if (rb_pos >= rb_size) { + output.write(ringbuffer, rb_size) + rb_pos -= rb_size + for (let x = 0; x < rb_pos; x++) + ringbuffer[x] = ringbuffer[rb_size + x] + } + + // If we have more to copy than the remaining size of the ringbuffer, then we + // first fill the ringbuffer from the input and then flush the ringbuffer + while (rb_pos + len >= rb_size) { + nbytes = rb_size - rb_pos + if (br.input_.read(ringbuffer, rb_pos, nbytes) < nbytes) { + throw new Error('copyUncompressedBlockToOutput: not enough bytes') + } + output.write(ringbuffer, rb_size) + len -= nbytes + rb_pos = 0 + } + + // Copy straight from the input onto the ringbuffer + // Ringbuffer will be flushed to output later + if (br.input_.read(ringbuffer, rb_pos, len) < len) { + throw new Error('copyUncompressedBlockToOutput: not enough bytes') + } + + // Restore the state of the bit reader + br.reset() +} + +/** + * Decodes a number in the range [0..255], by reading 1 - 11 bits. + * @param {BrotliBitReader} br + * @returns {number} + */ +export function decodeVarLenUint8(br) { + if (br.readBits(1)) { + const nbits = br.readBits(3) + if (nbits === 0) { + return 1 + } else { + return br.readBits(nbits) + (1 << nbits) + } + } + return 0 +} + +/** + * @param {BrotliBitReader} br + * @returns {number} + */ +export function decodeWindowBits(br) { + if (br.readBits(1) === 0) return 16 + + let n = br.readBits(3) + if (n > 0) return 17 + n + + n = br.readBits(3) + if (n > 0) return 8 + n + + return 17 +} + +/** + * Advances the bit reader position to the next byte boundary and verifies + * that any skipped bits are set to zero. + * @param {BrotliBitReader} br + * @returns {boolean} + */ +export function jumpToByteBoundary(br) { + const new_bit_pos = br.bit_pos_ + 7 & ~7 + return !br.readBits(new_bit_pos - br.bit_pos_) +} + +/** + * @param {HuffmanCode[]} table + * @param {number} index + * @param {BrotliBitReader} br + * @returns {number} + */ +export function readBlockLength(table, index, br) { + const code = readSymbol(table, index, br) + const { offset, nbits } = kBlockLengthPrefixCode[code] + return offset + br.readBits(nbits) +} diff --git a/src/brotli.contextmap.js b/src/brotli.contextmap.js index 5c1fc9e..90a483b 100644 --- a/src/brotli.contextmap.js +++ b/src/brotli.contextmap.js @@ -1,10 +1,11 @@ +import { decodeVarLenUint8 } from './brotli.blocks.js' import { HuffmanCode, readHuffmanCode, readSymbol } from './brotli.huffman.js' -import { decodeVarLenUint8 } from './brotli.js' import { HUFFMAN_MAX_TABLE_SIZE } from './gzip.huffman.js' /** + * @import {BrotliBitReader} from './brotli.bitreader.js' * @param {number} context_map_size - * @param {import('./brotli.huffman.js').BrotliBitReader} br + * @param {BrotliBitReader} br * @returns {[number, Uint8Array]} // num_htrees, context_map */ export function decodeContextMap(context_map_size, br) { diff --git a/src/brotli.dictionary.js b/src/brotli.dictionary.js index cc9c553..f12b2b2 100644 --- a/src/brotli.dictionary.js +++ b/src/brotli.dictionary.js @@ -1,17 +1,5 @@ /* Copyright 2013 Google Inc. All Rights Reserved. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - Collection of static dictionary words. */ diff --git a/src/brotli.huffman.js b/src/brotli.huffman.js index 9e2f46e..67942e0 100644 --- a/src/brotli.huffman.js +++ b/src/brotli.huffman.js @@ -9,13 +9,18 @@ const kCodeLengthCodeOrder = new Uint8Array([ 1, 2, 3, 4, 0, 5, 17, 6, 16, 7, 8, 9, 10, 11, 12, 13, 14, 15, ]) +const kMaxHuffmanTableSize = new Uint16Array([ + 256, 402, 436, 468, 500, 534, 566, 598, 630, 662, 694, 726, 758, 790, 822, + 854, 886, 920, 952, 984, 1016, 1048, 1080, +]) + /** * @param {number} bits * @param {number} value */ export function HuffmanCode(bits, value) { - this.bits = bits /* number of bits used for this symbol */ - this.value = value /* symbol value or table offset */ + this.bits = bits // number of bits used for this symbol + this.value = value // symbol value or table offset } const kCodeLengthRepeatCode = 16 @@ -80,24 +85,24 @@ function nextTableBitSize(count, len, root_bits) { * @param {number} code_lengths_size * @returns {number} */ -export function buildHuffmanTable(root_table, table, root_bits, code_lengths, code_lengths_size) { +function buildHuffmanTable(root_table, table, root_bits, code_lengths, code_lengths_size) { const start_table = table - const count = new Int32Array(MAX_LENGTH + 1) /* number of codes of each length */ - const offset = new Int32Array(MAX_LENGTH + 1) /* offsets in sorted table for each length */ - const sorted = new Int32Array(code_lengths_size) /* symbols sorted by code length */ + const count = new Int32Array(MAX_LENGTH + 1) // number of codes of each length + const offset = new Int32Array(MAX_LENGTH + 1) // offsets in sorted table for each length + const sorted = new Int32Array(code_lengths_size) // symbols sorted by code length - /* build histogram of code lengths */ + // build histogram of code lengths for (let i = 0; i < code_lengths_size; i++) { count[code_lengths[i]]++ } - /* generate offsets into sorted symbol table by code length */ + // generate offsets into sorted symbol table by code length offset[1] = 0 for (let i = 1; i < MAX_LENGTH; i++) { offset[i + 1] = offset[i] + count[i] } - /* sort symbols by length, by symbol order within each length */ + // sort symbols by length, by symbol order within each length for (let i = 0; i < code_lengths_size; i++) { if (code_lengths[i] !== 0) { sorted[offset[code_lengths[i]]++] = i @@ -108,7 +113,7 @@ export function buildHuffmanTable(root_table, table, root_bits, code_lengths, co let table_size = 1 << table_bits let total_size = table_size // sum of root table size and 2nd level table sizes - /* special case code with only one value */ + // special case code with only one value if (offset[MAX_LENGTH] === 1) { for (let key = 0; key < total_size; ++key) { root_table[table + key] = new HuffmanCode(0, sorted[0] & 0xffff) @@ -117,7 +122,7 @@ export function buildHuffmanTable(root_table, table, root_bits, code_lengths, co return total_size } - /* fill in root table */ + // fill in root table let key = 0 // reversed prefix code let symbol = 0 // symbol index in original or sorted table for (let len = 1, step = 2; len <= root_bits; ++len, step <<= 1) { @@ -128,7 +133,7 @@ export function buildHuffmanTable(root_table, table, root_bits, code_lengths, co } } - /* fill in 2nd level tables and add pointers to root table */ + // fill in 2nd level tables and add pointers to root table const mask = total_size - 1 let low = -1 // low bits for current root entry for (let len = root_bits + 1, step = 2; len <= MAX_LENGTH; ++len, step <<= 1) { @@ -151,7 +156,7 @@ export function buildHuffmanTable(root_table, table, root_bits, code_lengths, co } /** - * @typedef {import('./brotli.bitreader.js').default} BrotliBitReader + * @import {BrotliBitReader} from './brotli.bitreader.js' * @param {number} alphabet_size * @param {HuffmanCode[]} tables * @param {number} table @@ -163,12 +168,12 @@ export function readHuffmanCode(alphabet_size, tables, table, br) { br.readMoreInput() - /* simple_code_or_skip is used as follows: - 1 for simple code; - 0 for no skipping, 2 skips 2 code lengths, 3 skips 3 code lengths */ + // simple_code_or_skip is used as follows: + // - 1 for simple code; + // - 0 for no skipping, 2 skips 2 code lengths, 3 skips 3 code lengths const simple_code_or_skip = br.readBits(2) if (simple_code_or_skip === 1) { - /* Read symbols, codes & code lengths directly. */ + // Read symbols, codes & code lengths directly let max_bits_counter = alphabet_size - 1 let max_bits = 0 const symbols = new Int32Array(4) @@ -218,11 +223,11 @@ export function readHuffmanCode(alphabet_size, tables, table, br) { } break } - } else { /* Decode Huffman-coded code lengths. */ + } else { // Decode Huffman-coded code lengths const code_length_code_lengths = new Uint8Array(CODE_LENGTH_CODES) let space = 32 let num_codes = 0 - /* Static Huffman code for the code length code lengths */ + // Static Huffman code for the code length code lengths const huff = [ new HuffmanCode(2, 0), new HuffmanCode(2, 4), new HuffmanCode(2, 3), new HuffmanCode(3, 2), new HuffmanCode(2, 0), new HuffmanCode(2, 4), new HuffmanCode(2, 3), new HuffmanCode(4, 1), @@ -347,3 +352,28 @@ function readHuffmanCodeLengths(code_length_code_lengths, num_symbols, code_leng for (; symbol < num_symbols; symbol++) code_lengths[symbol] = 0 } + + +/** + * Contains a collection of huffman trees with the same alphabet size. + * + * @param {number} alphabet_size + * @param {number} num_htrees + */ +export function HuffmanTreeGroup(alphabet_size, num_htrees) { + this.alphabet_size = alphabet_size + this.num_htrees = num_htrees + this.codes = new Array(num_htrees + num_htrees * kMaxHuffmanTableSize[alphabet_size + 31 >>> 5]) + this.htrees = new Uint32Array(num_htrees) +} + +/** + * @param {BrotliBitReader} br + */ +HuffmanTreeGroup.prototype.decode = function(br) { + let next = 0 + for (let i = 0; i < this.num_htrees; i++) { + this.htrees[i] = next + next += readHuffmanCode(this.alphabet_size, this.codes, next, br) + } +} diff --git a/src/brotli.js b/src/brotli.js index b8bd6b2..f157b33 100644 --- a/src/brotli.js +++ b/src/brotli.js @@ -3,11 +3,12 @@ * Copyright 2013 Google Inc, Apache License 2.0 */ -import BrotliBitReader from './brotli.bitreader.js' +import { BrotliBitReader } from './brotli.bitreader.js' +import { copyUncompressedBlockToOutput, decodeBlockType, decodeMetaBlockLength, decodeVarLenUint8, decodeWindowBits, jumpToByteBoundary, readBlockLength } from './brotli.blocks.js' import { lookup, lookupOffsets } from './brotli.context.js' import { decodeContextMap } from './brotli.contextmap.js' -import { HuffmanCode, readHuffmanCode, readSymbol } from './brotli.huffman.js' -import { kBlockLengthPrefixCode, kCopyLengthPrefixCode, kCopyRangeLut, kInsertLengthPrefixCode, kInsertRangeLut } from './brotli.prefix.js' +import { HuffmanCode, HuffmanTreeGroup, readHuffmanCode, readSymbol } from './brotli.huffman.js' +import { kCopyLengthPrefixCode, kCopyRangeLut, kInsertLengthPrefixCode, kInsertRangeLut } from './brotli.prefix.js' import { BrotliInput, BrotliOutput } from './brotli.streams.js' import { kNumTransforms, transformDictionaryWord } from './brotli.transform.js' import { HUFFMAN_MAX_TABLE_SIZE } from './gzip.huffman.js' @@ -27,11 +28,6 @@ const kDistanceShortCodeValueOffset = new Int8Array([ 0, 0, 0, 0, -1, 1, -2, 2, -3, 3, -1, 1, -2, 2, -3, 3, ]) -const kMaxHuffmanTableSize = new Uint16Array([ - 256, 402, 436, 468, 500, 534, 566, 598, 630, 662, 694, 726, 758, 790, 822, - 854, 886, 920, 952, 984, 1016, 1048, 1080, -]) - // Brotli dictionary const offsetsByLength = new Uint32Array([ 0, 0, 0, 0, 0, 4096, 9216, 21504, 35840, 44032, @@ -345,87 +341,6 @@ function brotli(input, output) { output.write(ringbuffer, pos & ringbuffer_mask) } -/** - * @param {BrotliBitReader} br - * @returns {number} - */ -function decodeWindowBits(br) { - if (br.readBits(1) === 0) return 16 - - let n = br.readBits(3) - if (n > 0) return 17 + n - - n = br.readBits(3) - if (n > 0) return 8 + n - - return 17 -} - -/** - * @param {number} max_block_type - * @param {HuffmanCode[]} trees - * @param {number} tree_type - * @param {number[]} block_types - * @param {number[]} ringbuffers - * @param {number[]} indexes - * @param {BrotliBitReader} br - */ -function decodeBlockType(max_block_type, trees, tree_type, block_types, ringbuffers, indexes, br) { - const ringbuffer = tree_type * 2 - const index = tree_type - const type_code = readSymbol(trees, tree_type * HUFFMAN_MAX_TABLE_SIZE, br) - let block_type - if (type_code === 0) { - block_type = ringbuffers[ringbuffer + (indexes[index] & 1)] - } else if (type_code === 1) { - block_type = ringbuffers[ringbuffer + (indexes[index] - 1 & 1)] + 1 - } else { - block_type = type_code - 2 - } - if (block_type >= max_block_type) { - block_type -= max_block_type - } - block_types[tree_type] = block_type - ringbuffers[ringbuffer + (indexes[index] & 1)] = block_type - ++indexes[index] -} - -/** - * Contains a collection of huffman trees with the same alphabet size. - * - * @param {number} alphabet_size - * @param {number} num_htrees - */ -function HuffmanTreeGroup(alphabet_size, num_htrees) { - this.alphabet_size = alphabet_size - this.num_htrees = num_htrees - this.codes = new Array(num_htrees + num_htrees * kMaxHuffmanTableSize[alphabet_size + 31 >>> 5]) - this.htrees = new Uint32Array(num_htrees) -} - -/** - * @param {BrotliBitReader} br - */ -HuffmanTreeGroup.prototype.decode = function(br) { - let next = 0 - for (let i = 0; i < this.num_htrees; i++) { - this.htrees[i] = next - next += readHuffmanCode(this.alphabet_size, this.codes, next, br) - } -} - -/** - * @param {HuffmanCode[]} table - * @param {number} index - * @param {BrotliBitReader} br - * @returns {number} - */ -function readBlockLength(table, index, br) { - const code = readSymbol(table, index, br) - const { offset, nbits } = kBlockLengthPrefixCode[code] - return offset + br.readBits(nbits) -} - /** * @param {number} code * @param {number[]} ringbuffer @@ -441,174 +356,3 @@ function translateShortCodes(code, ringbuffer, index) { return code - NUM_DISTANCE_SHORT_CODES + 1 } } - -/** - * @param {*} output - * @param {number} len - * @param {number} pos - * @param {Uint8Array} ringbuffer - * @param {number} ringbuffer_mask - * @param {BrotliBitReader} br - */ -function copyUncompressedBlockToOutput(output, len, pos, ringbuffer, ringbuffer_mask, br) { - const rb_size = ringbuffer_mask + 1 - let rb_pos = pos & ringbuffer_mask - let br_pos = br.pos_ & BrotliBitReader.IBUF_MASK - - // For short lengths copy byte-by-byte - if (len < 8 || br.bit_pos_ + (len << 3) < br.bit_end_pos_) { - while (len-- > 0) { - br.readMoreInput() - ringbuffer[rb_pos++] = br.readBits(8) - if (rb_pos === rb_size) { - output.write(ringbuffer, rb_size) - rb_pos = 0 - } - } - return - } - - if (br.bit_end_pos_ < 32) { - throw new Error('copyUncompressedBlockToOutput: br.bit_end_pos_ < 32') - } - - // Copy remaining 0-4 bytes from br.val_ to ringbuffer - while (br.bit_pos_ < 32) { - ringbuffer[rb_pos] = br.val_ >>> br.bit_pos_ - br.bit_pos_ += 8 - rb_pos++ - len-- - } - - // Copy remaining bytes from br.buf_ to ringbuffer - let nbytes = br.bit_end_pos_ - br.bit_pos_ >> 3 - if (br_pos + nbytes > BrotliBitReader.IBUF_MASK) { - const tail = BrotliBitReader.IBUF_MASK + 1 - br_pos - for (let x = 0; x < tail; x++) - ringbuffer[rb_pos + x] = br.buf_[br_pos + x] - - nbytes -= tail - rb_pos += tail - len -= tail - br_pos = 0 - } - - for (let x = 0; x < nbytes; x++) - ringbuffer[rb_pos + x] = br.buf_[br_pos + x] - - rb_pos += nbytes - len -= nbytes - - // If we wrote past the logical end of the ringbuffer, copy the tail of the - // ringbuffer to its beginning and flush the ringbuffer to the output - if (rb_pos >= rb_size) { - output.write(ringbuffer, rb_size) - rb_pos -= rb_size - for (let x = 0; x < rb_pos; x++) - ringbuffer[x] = ringbuffer[rb_size + x] - } - - // If we have more to copy than the remaining size of the ringbuffer, then we - // first fill the ringbuffer from the input and then flush the ringbuffer - while (rb_pos + len >= rb_size) { - nbytes = rb_size - rb_pos - if (br.input_.read(ringbuffer, rb_pos, nbytes) < nbytes) { - throw new Error('copyUncompressedBlockToOutput: not enough bytes') - } - output.write(ringbuffer, rb_size) - len -= nbytes - rb_pos = 0 - } - - // Copy straight from the input onto the ringbuffer - // Ringbuffer will be flushed to output later - if (br.input_.read(ringbuffer, rb_pos, len) < len) { - throw new Error('copyUncompressedBlockToOutput: not enough bytes') - } - - // Restore the state of the bit reader - br.reset() -} - -/** - * Decodes a number in the range [0..255], by reading 1 - 11 bits. - * @param {BrotliBitReader} br - * @returns {number} - */ -export function decodeVarLenUint8(br) { - if (br.readBits(1)) { - const nbits = br.readBits(3) - if (nbits === 0) { - return 1 - } else { - return br.readBits(nbits) + (1 << nbits) - } - } - return 0 -} - -/** - * @typedef {{ input_end: number, is_metadata: boolean, meta_block_length: number, is_uncompressed: number }} MetaBlockLength - * @param {BrotliBitReader} br - * @returns {MetaBlockLength} - */ -function decodeMetaBlockLength(br) { - const out = { - meta_block_length: 0, - input_end: 0, - is_uncompressed: 0, - is_metadata: false, - } - - out.input_end = br.readBits(1) - if (out.input_end && br.readBits(1)) { - return out - } - - const size_nibbles = br.readBits(2) + 4 - if (size_nibbles === 7) { - out.is_metadata = true - - if (br.readBits(1) !== 0) - throw new Error('Invalid reserved bit') - - const size_bytes = br.readBits(2) - if (size_bytes === 0) - return out - - for (let i = 0; i < size_bytes; i++) { - const next_byte = br.readBits(8) - if (i + 1 === size_bytes && size_bytes > 1 && next_byte === 0) - throw new Error('Invalid size byte') - - out.meta_block_length |= next_byte << i * 8 - } - } else { - for (let i = 0; i < size_nibbles; i++) { - const next_nibble = br.readBits(4) - if (i + 1 === size_nibbles && size_nibbles > 4 && next_nibble === 0) - throw new Error('Invalid size nibble') - - out.meta_block_length |= next_nibble << i * 4 - } - } - - out.meta_block_length++ - - if (!out.input_end && !out.is_metadata) { - out.is_uncompressed = br.readBits(1) - } - - return out -} - -/** - * Advances the bit reader position to the next byte boundary and verifies - * that any skipped bits are set to zero. - * @param {BrotliBitReader} br - * @returns {boolean} - */ -function jumpToByteBoundary(br) { - const new_bit_pos = br.bit_pos_ + 7 & ~7 - return !br.readBits(new_bit_pos - br.bit_pos_) -}