Skip to content

Commit e1dea63

Browse files
Anna Henningsenrefack
Anna Henningsen
authored andcommitted
zlib: add brotli support
Refs: nodejs#20458 Co-authored-by: Hackzzila <[email protected]> PR-URL: nodejs#24938 Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Jan Krems <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Myles Borins <[email protected]> Reviewed-By: Ali Ijaz Sheikh <[email protected]> Reviewed-By: Daniel Bevenius <[email protected]>
1 parent 37fb986 commit e1dea63

File tree

9 files changed

+540
-37
lines changed

9 files changed

+540
-37
lines changed

doc/api/errors.md

+10
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,16 @@ An attempt was made to register something that is not a function as an
624624
The type of an asynchronous resource was invalid. Note that users are also able
625625
to define their own types if using the public embedder API.
626626

627+
<a id="ERR_BROTLI_INVALID_PARAM"></a>
628+
### ERR_BROTLI_INVALID_PARAM
629+
630+
An invalid parameter key was passed during construction of a Brotli stream.
631+
632+
<a id="ERR_BROTLI_COMPRESSION_FAILED">
633+
### ERR_BROTLI_COMPRESSION_FAILED
634+
635+
Data passed to a Brotli stream was not successfully compressed.
636+
627637
<a id="ERR_BUFFER_CONTEXT_NOT_AVAILABLE"></a>
628638
### ERR_BUFFER_CONTEXT_NOT_AVAILABLE
629639

lib/internal/errors.js

+1
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,7 @@ E('ERR_ARG_NOT_ITERABLE', '%s must be iterable', TypeError);
546546
E('ERR_ASSERTION', '%s', Error);
547547
E('ERR_ASYNC_CALLBACK', '%s must be a function', TypeError);
548548
E('ERR_ASYNC_TYPE', 'Invalid name for async "type": %s', TypeError);
549+
E('ERR_BROTLI_INVALID_PARAM', '%s is not a valid Brotli parameter', RangeError);
549550
E('ERR_BUFFER_OUT_OF_BOUNDS',
550551
// Using a default argument here is important so the argument is not counted
551552
// towards `Function#length`.

lib/zlib.js

+89-5
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@
2222
'use strict';
2323

2424
const {
25+
ERR_BROTLI_INVALID_PARAM,
2526
ERR_BUFFER_TOO_LARGE,
2627
ERR_INVALID_ARG_TYPE,
2728
ERR_OUT_OF_RANGE,
28-
ERR_ZLIB_INITIALIZATION_FAILED
29+
ERR_ZLIB_INITIALIZATION_FAILED,
2930
} = require('internal/errors').codes;
3031
const Transform = require('_stream_transform');
3132
const {
@@ -45,11 +46,18 @@ const { owner_symbol } = require('internal/async_hooks').symbols;
4546

4647
const constants = internalBinding('constants').zlib;
4748
const {
49+
// Zlib flush levels
4850
Z_NO_FLUSH, Z_BLOCK, Z_PARTIAL_FLUSH, Z_SYNC_FLUSH, Z_FULL_FLUSH, Z_FINISH,
51+
// Zlib option values
4952
Z_MIN_CHUNK, Z_MIN_WINDOWBITS, Z_MAX_WINDOWBITS, Z_MIN_LEVEL, Z_MAX_LEVEL,
5053
Z_MIN_MEMLEVEL, Z_MAX_MEMLEVEL, Z_DEFAULT_CHUNK, Z_DEFAULT_COMPRESSION,
5154
Z_DEFAULT_STRATEGY, Z_DEFAULT_WINDOWBITS, Z_DEFAULT_MEMLEVEL, Z_FIXED,
52-
DEFLATE, DEFLATERAW, INFLATE, INFLATERAW, GZIP, GUNZIP, UNZIP
55+
// Node's compression stream modes (node_zlib_mode)
56+
DEFLATE, DEFLATERAW, INFLATE, INFLATERAW, GZIP, GUNZIP, UNZIP,
57+
BROTLI_DECODE, BROTLI_ENCODE,
58+
// Brotli operations (~flush levels)
59+
BROTLI_OPERATION_PROCESS, BROTLI_OPERATION_FLUSH,
60+
BROTLI_OPERATION_FINISH
5361
} = constants;
5462

5563
// translation table for return codes.
@@ -212,7 +220,7 @@ function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) {
212220
// The ZlibBase class is not exported to user land, the mode should only be
213221
// passed in by us.
214222
assert(typeof mode === 'number');
215-
assert(mode >= DEFLATE && mode <= UNZIP);
223+
assert(mode >= DEFLATE && mode <= BROTLI_ENCODE);
216224

217225
if (opts) {
218226
chunkSize = opts.chunkSize;
@@ -481,7 +489,7 @@ function processCallback() {
481489
// important to null out the values once they are no longer needed since
482490
// `_handle` can stay in memory long after the buffer is needed.
483491
var handle = this;
484-
var self = this.jsref;
492+
var self = this[owner_symbol];
485493
var state = self._writeState;
486494

487495
if (self._hadError) {
@@ -622,6 +630,9 @@ function Zlib(opts, mode) {
622630
this._writeState,
623631
processCallback,
624632
dictionary)) {
633+
// TODO(addaleax): Sometimes we generate better error codes in C++ land,
634+
// e.g. ERR_BROTLI_PARAM_SET_FAILED -- it's hard to access them with
635+
// the current bindings setup, though.
625636
throw new ERR_ZLIB_INITIALIZATION_FAILED();
626637
}
627638

@@ -734,6 +745,70 @@ function createConvenienceMethod(ctor, sync) {
734745
}
735746
}
736747

748+
const kMaxBrotliParam = Math.max(...Object.keys(constants).map((key) => {
749+
return key.startsWith('BROTLI_PARAM_') ? constants[key] : 0;
750+
}));
751+
752+
const brotliInitParamsArray = new Uint32Array(kMaxBrotliParam + 1);
753+
754+
const brotliDefaultOpts = {
755+
flush: BROTLI_OPERATION_PROCESS,
756+
finishFlush: BROTLI_OPERATION_FINISH,
757+
fullFlush: BROTLI_OPERATION_FLUSH
758+
};
759+
function Brotli(opts, mode) {
760+
assert(mode === BROTLI_DECODE || mode === BROTLI_ENCODE);
761+
762+
brotliInitParamsArray.fill(-1);
763+
if (opts && opts.params) {
764+
for (const origKey of Object.keys(opts.params)) {
765+
const key = +origKey;
766+
if (Number.isNaN(key) || key < 0 || key > kMaxBrotliParam ||
767+
(brotliInitParamsArray[key] | 0) !== -1) {
768+
throw new ERR_BROTLI_INVALID_PARAM(origKey);
769+
}
770+
771+
const value = opts.params[origKey];
772+
if (typeof value !== 'number' && typeof value !== 'boolean') {
773+
throw new ERR_INVALID_ARG_TYPE('options.params[key]',
774+
'number', opts.params[origKey]);
775+
}
776+
brotliInitParamsArray[key] = value;
777+
}
778+
}
779+
780+
const handle = mode === BROTLI_DECODE ?
781+
new binding.BrotliDecoder(mode) : new binding.BrotliEncoder(mode);
782+
783+
this._writeState = new Uint32Array(2);
784+
if (!handle.init(brotliInitParamsArray,
785+
this._writeState,
786+
processCallback)) {
787+
throw new ERR_ZLIB_INITIALIZATION_FAILED();
788+
}
789+
790+
ZlibBase.call(this, opts, mode, handle, brotliDefaultOpts);
791+
}
792+
Object.setPrototypeOf(Brotli.prototype, Zlib.prototype);
793+
Object.setPrototypeOf(Brotli, Zlib);
794+
795+
function BrotliCompress(opts) {
796+
if (!(this instanceof BrotliCompress))
797+
return new BrotliCompress(opts);
798+
Brotli.call(this, opts, BROTLI_ENCODE);
799+
}
800+
Object.setPrototypeOf(BrotliCompress.prototype, Brotli.prototype);
801+
Object.setPrototypeOf(BrotliCompress, Brotli);
802+
803+
function BrotliDecompress(opts) {
804+
if (!(this instanceof BrotliDecompress))
805+
return new BrotliDecompress(opts);
806+
Brotli.call(this, opts, BROTLI_DECODE);
807+
}
808+
Object.setPrototypeOf(BrotliDecompress.prototype, Brotli.prototype);
809+
Object.setPrototypeOf(BrotliDecompress, Brotli);
810+
811+
737812
function createProperty(ctor) {
738813
return {
739814
configurable: true,
@@ -759,6 +834,8 @@ module.exports = {
759834
DeflateRaw,
760835
InflateRaw,
761836
Unzip,
837+
BrotliCompress,
838+
BrotliDecompress,
762839

763840
// Convenience methods.
764841
// compress/decompress a string or buffer in one step.
@@ -775,7 +852,11 @@ module.exports = {
775852
gunzip: createConvenienceMethod(Gunzip, false),
776853
gunzipSync: createConvenienceMethod(Gunzip, true),
777854
inflateRaw: createConvenienceMethod(InflateRaw, false),
778-
inflateRawSync: createConvenienceMethod(InflateRaw, true)
855+
inflateRawSync: createConvenienceMethod(InflateRaw, true),
856+
brotliCompress: createConvenienceMethod(BrotliCompress, false),
857+
brotliCompressSync: createConvenienceMethod(BrotliCompress, true),
858+
brotliDecompress: createConvenienceMethod(BrotliDecompress, false),
859+
brotliDecompressSync: createConvenienceMethod(BrotliDecompress, true),
779860
};
780861

781862
Object.defineProperties(module.exports, {
@@ -786,6 +867,8 @@ Object.defineProperties(module.exports, {
786867
createGzip: createProperty(Gzip),
787868
createGunzip: createProperty(Gunzip),
788869
createUnzip: createProperty(Unzip),
870+
createBrotliCompress: createProperty(BrotliCompress),
871+
createBrotliDecompress: createProperty(BrotliDecompress),
789872
constants: {
790873
configurable: false,
791874
enumerable: true,
@@ -803,6 +886,7 @@ Object.defineProperties(module.exports, {
803886
const bkeys = Object.keys(constants);
804887
for (var bk = 0; bk < bkeys.length; bk++) {
805888
var bkey = bkeys[bk];
889+
if (bkey.startsWith('BROTLI')) continue;
806890
Object.defineProperty(module.exports, bkey, {
807891
enumerable: false, value: constants[bkey], writable: false
808892
});

node.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
'node_shared%': 'false',
1212
'force_dynamic_crt%': 0,
1313
'node_module_version%': '',
14+
'node_shared_brotli%': 'false',
1415
'node_shared_zlib%': 'false',
1516
'node_shared_http_parser%': 'false',
1617
'node_shared_cares%': 'false',

node.gypi

+4
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@
208208
'dependencies': [ 'deps/nghttp2/nghttp2.gyp:nghttp2' ],
209209
}],
210210

211+
[ 'node_shared_brotli=="false"', {
212+
'dependencies': [ 'deps/brotli/brotli.gyp:brotli' ],
213+
}],
214+
211215
[ 'OS=="mac"', {
212216
# linking Corefoundation is needed since certain OSX debugging tools
213217
# like Instruments require it for some features

src/node_metadata.cc

+8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "node_metadata.h"
22
#include "ares.h"
3+
#include "brotli/encode.h"
34
#include "nghttp2/nghttp2ver.h"
45
#include "node.h"
56
#include "util.h"
@@ -72,6 +73,13 @@ Metadata::Versions::Versions() {
7273
llhttp = per_process::llhttp_version;
7374
http_parser = per_process::http_parser_version;
7475

76+
brotli =
77+
std::to_string(BrotliEncoderVersion() >> 24) +
78+
"." +
79+
std::to_string((BrotliEncoderVersion() & 0xFFF000) >> 12) +
80+
"." +
81+
std::to_string(BrotliEncoderVersion() & 0xFFF);
82+
7583
#if HAVE_OPENSSL
7684
openssl = GetOpenSSLVersion();
7785
#endif

src/node_metadata.h

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ namespace node {
1212
V(v8) \
1313
V(uv) \
1414
V(zlib) \
15+
V(brotli) \
1516
V(ares) \
1617
V(modules) \
1718
V(nghttp2) \

0 commit comments

Comments
 (0)