Skip to content

Commit 7edf8c7

Browse files
committed
zlib: add brotli support
Refs: #20458 Co-authored-by: Hackzzila <[email protected]> PR-URL: #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 ec87b6c commit 7edf8c7

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 {
@@ -47,11 +48,18 @@ const { owner_symbol } = require('internal/async_hooks').symbols;
4748

4849
const constants = internalBinding('constants').zlib;
4950
const {
51+
// Zlib flush levels
5052
Z_NO_FLUSH, Z_BLOCK, Z_PARTIAL_FLUSH, Z_SYNC_FLUSH, Z_FULL_FLUSH, Z_FINISH,
53+
// Zlib option values
5154
Z_MIN_CHUNK, Z_MIN_WINDOWBITS, Z_MAX_WINDOWBITS, Z_MIN_LEVEL, Z_MAX_LEVEL,
5255
Z_MIN_MEMLEVEL, Z_MAX_MEMLEVEL, Z_DEFAULT_CHUNK, Z_DEFAULT_COMPRESSION,
5356
Z_DEFAULT_STRATEGY, Z_DEFAULT_WINDOWBITS, Z_DEFAULT_MEMLEVEL, Z_FIXED,
54-
DEFLATE, DEFLATERAW, INFLATE, INFLATERAW, GZIP, GUNZIP, UNZIP
57+
// Node's compression stream modes (node_zlib_mode)
58+
DEFLATE, DEFLATERAW, INFLATE, INFLATERAW, GZIP, GUNZIP, UNZIP,
59+
BROTLI_DECODE, BROTLI_ENCODE,
60+
// Brotli operations (~flush levels)
61+
BROTLI_OPERATION_PROCESS, BROTLI_OPERATION_FLUSH,
62+
BROTLI_OPERATION_FINISH
5563
} = constants;
5664

5765
// 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

@@ -726,6 +737,70 @@ function createConvenienceMethod(ctor, sync) {
726737
}
727738
}
728739

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

755832
// Convenience methods.
756833
// compress/decompress a string or buffer in one step.
@@ -767,7 +844,11 @@ module.exports = {
767844
gunzip: createConvenienceMethod(Gunzip, false),
768845
gunzipSync: createConvenienceMethod(Gunzip, true),
769846
inflateRaw: createConvenienceMethod(InflateRaw, false),
770-
inflateRawSync: createConvenienceMethod(InflateRaw, true)
847+
inflateRawSync: createConvenienceMethod(InflateRaw, true),
848+
brotliCompress: createConvenienceMethod(BrotliCompress, false),
849+
brotliCompressSync: createConvenienceMethod(BrotliCompress, true),
850+
brotliDecompress: createConvenienceMethod(BrotliDecompress, false),
851+
brotliDecompressSync: createConvenienceMethod(BrotliDecompress, true),
771852
};
772853

773854
Object.defineProperties(module.exports, {
@@ -778,6 +859,8 @@ Object.defineProperties(module.exports, {
778859
createGzip: createProperty(Gzip),
779860
createGunzip: createProperty(Gunzip),
780861
createUnzip: createProperty(Unzip),
862+
createBrotliCompress: createProperty(BrotliCompress),
863+
createBrotliDecompress: createProperty(BrotliDecompress),
781864
constants: {
782865
configurable: false,
783866
enumerable: true,
@@ -795,6 +878,7 @@ Object.defineProperties(module.exports, {
795878
const bkeys = Object.keys(constants);
796879
for (var bk = 0; bk < bkeys.length; bk++) {
797880
var bkey = bkeys[bk];
881+
if (bkey.startsWith('BROTLI')) continue;
798882
Object.defineProperty(module.exports, bkey, {
799883
enumerable: true, value: constants[bkey], writable: false
800884
});

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_experimental_http_parser%': 'false',
1617
'node_shared_http_parser%': 'false',

node.gypi

+4
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,10 @@
212212
'dependencies': [ 'deps/nghttp2/nghttp2.gyp:nghttp2' ],
213213
}],
214214

215+
[ 'node_shared_brotli=="false"', {
216+
'dependencies': [ 'deps/brotli/brotli.gyp:brotli' ],
217+
}],
218+
215219
[ 'OS=="mac"', {
216220
# linking Corefoundation is needed since certain OSX debugging tools
217221
# 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 "node_internals.h"
@@ -30,6 +31,13 @@ Metadata::Versions::Versions() {
3031
llhttp = llhttp_version;
3132
http_parser = http_parser_version;
3233

34+
brotli =
35+
std::to_string(BrotliEncoderVersion() >> 24) +
36+
"." +
37+
std::to_string((BrotliEncoderVersion() & 0xFFF000) >> 12) +
38+
"." +
39+
std::to_string(BrotliEncoderVersion() & 0xFFF);
40+
3341
#if HAVE_OPENSSL
3442
openssl = crypto::GetOpenSSLVersion();
3543
#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)