zlib: add brotli support

Refs: https://github.com/nodejs/node/pull/20458

Co-authored-by: Hackzzila <admin@hackzzila.com>

PR-URL: https://github.com/nodejs/node/pull/24938
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Jan Krems <jan.krems@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Myles Borins <myles.borins@gmail.com>
Reviewed-By: Ali Ijaz Sheikh <ofrobots@google.com>
Reviewed-By: Daniel Bevenius <daniel.bevenius@gmail.com>
This commit is contained in:
Anna Henningsen 2018-11-27 10:16:34 +09:00
parent b857d713f5
commit 73753d4863
No known key found for this signature in database
GPG Key ID: 9C63F3A6CD2AD8F9
9 changed files with 544 additions and 41 deletions

View File

@ -624,6 +624,16 @@ An attempt was made to register something that is not a function as an
The type of an asynchronous resource was invalid. Note that users are also able The type of an asynchronous resource was invalid. Note that users are also able
to define their own types if using the public embedder API. to define their own types if using the public embedder API.
<a id="ERR_BROTLI_INVALID_PARAM"></a>
### ERR_BROTLI_INVALID_PARAM
An invalid parameter key was passed during construction of a Brotli stream.
<a id="ERR_BROTLI_COMPRESSION_FAILED">
### ERR_BROTLI_COMPRESSION_FAILED
Data passed to a Brotli stream was not successfully compressed.
<a id="ERR_BUFFER_CONTEXT_NOT_AVAILABLE"></a> <a id="ERR_BUFFER_CONTEXT_NOT_AVAILABLE"></a>
### ERR_BUFFER_CONTEXT_NOT_AVAILABLE ### ERR_BUFFER_CONTEXT_NOT_AVAILABLE

View File

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

View File

@ -22,10 +22,11 @@
'use strict'; 'use strict';
const { const {
ERR_BROTLI_INVALID_PARAM,
ERR_BUFFER_TOO_LARGE, ERR_BUFFER_TOO_LARGE,
ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_TYPE,
ERR_OUT_OF_RANGE, ERR_OUT_OF_RANGE,
ERR_ZLIB_INITIALIZATION_FAILED ERR_ZLIB_INITIALIZATION_FAILED,
} = require('internal/errors').codes; } = require('internal/errors').codes;
const Transform = require('_stream_transform'); const Transform = require('_stream_transform');
const { const {
@ -45,11 +46,18 @@ const { owner_symbol } = require('internal/async_hooks').symbols;
const constants = internalBinding('constants').zlib; const constants = internalBinding('constants').zlib;
const { const {
// Zlib flush levels
Z_NO_FLUSH, Z_BLOCK, Z_PARTIAL_FLUSH, Z_SYNC_FLUSH, Z_FULL_FLUSH, Z_FINISH, Z_NO_FLUSH, Z_BLOCK, Z_PARTIAL_FLUSH, Z_SYNC_FLUSH, Z_FULL_FLUSH, Z_FINISH,
// Zlib option values
Z_MIN_CHUNK, Z_MIN_WINDOWBITS, Z_MAX_WINDOWBITS, Z_MIN_LEVEL, Z_MAX_LEVEL, Z_MIN_CHUNK, Z_MIN_WINDOWBITS, Z_MAX_WINDOWBITS, Z_MIN_LEVEL, Z_MAX_LEVEL,
Z_MIN_MEMLEVEL, Z_MAX_MEMLEVEL, Z_DEFAULT_CHUNK, Z_DEFAULT_COMPRESSION, Z_MIN_MEMLEVEL, Z_MAX_MEMLEVEL, Z_DEFAULT_CHUNK, Z_DEFAULT_COMPRESSION,
Z_DEFAULT_STRATEGY, Z_DEFAULT_WINDOWBITS, Z_DEFAULT_MEMLEVEL, Z_FIXED, Z_DEFAULT_STRATEGY, Z_DEFAULT_WINDOWBITS, Z_DEFAULT_MEMLEVEL, Z_FIXED,
DEFLATE, DEFLATERAW, INFLATE, INFLATERAW, GZIP, GUNZIP, UNZIP // Node's compression stream modes (node_zlib_mode)
DEFLATE, DEFLATERAW, INFLATE, INFLATERAW, GZIP, GUNZIP, UNZIP,
BROTLI_DECODE, BROTLI_ENCODE,
// Brotli operations (~flush levels)
BROTLI_OPERATION_PROCESS, BROTLI_OPERATION_FLUSH,
BROTLI_OPERATION_FINISH
} = constants; } = constants;
// translation table for return codes. // translation table for return codes.
@ -212,7 +220,7 @@ function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) {
// The ZlibBase class is not exported to user land, the mode should only be // The ZlibBase class is not exported to user land, the mode should only be
// passed in by us. // passed in by us.
assert(typeof mode === 'number'); assert(typeof mode === 'number');
assert(mode >= DEFLATE && mode <= UNZIP); assert(mode >= DEFLATE && mode <= BROTLI_ENCODE);
if (opts) { if (opts) {
chunkSize = opts.chunkSize; chunkSize = opts.chunkSize;
@ -481,7 +489,7 @@ function processCallback() {
// important to null out the values once they are no longer needed since // important to null out the values once they are no longer needed since
// `_handle` can stay in memory long after the buffer is needed. // `_handle` can stay in memory long after the buffer is needed.
var handle = this; var handle = this;
var self = this.jsref; var self = this[owner_symbol];
var state = self._writeState; var state = self._writeState;
if (self._hadError) { if (self._hadError) {
@ -622,6 +630,9 @@ function Zlib(opts, mode) {
this._writeState, this._writeState,
processCallback, processCallback,
dictionary)) { dictionary)) {
// TODO(addaleax): Sometimes we generate better error codes in C++ land,
// e.g. ERR_BROTLI_PARAM_SET_FAILED -- it's hard to access them with
// the current bindings setup, though.
throw new ERR_ZLIB_INITIALIZATION_FAILED(); throw new ERR_ZLIB_INITIALIZATION_FAILED();
} }
@ -734,6 +745,70 @@ function createConvenienceMethod(ctor, sync) {
} }
} }
const kMaxBrotliParam = Math.max(...Object.keys(constants).map((key) => {
return key.startsWith('BROTLI_PARAM_') ? constants[key] : 0;
}));
const brotliInitParamsArray = new Uint32Array(kMaxBrotliParam + 1);
const brotliDefaultOpts = {
flush: BROTLI_OPERATION_PROCESS,
finishFlush: BROTLI_OPERATION_FINISH,
fullFlush: BROTLI_OPERATION_FLUSH
};
function Brotli(opts, mode) {
assert(mode === BROTLI_DECODE || mode === BROTLI_ENCODE);
brotliInitParamsArray.fill(-1);
if (opts && opts.params) {
for (const origKey of Object.keys(opts.params)) {
const key = +origKey;
if (Number.isNaN(key) || key < 0 || key > kMaxBrotliParam ||
(brotliInitParamsArray[key] | 0) !== -1) {
throw new ERR_BROTLI_INVALID_PARAM(origKey);
}
const value = opts.params[origKey];
if (typeof value !== 'number' && typeof value !== 'boolean') {
throw new ERR_INVALID_ARG_TYPE('options.params[key]',
'number', opts.params[origKey]);
}
brotliInitParamsArray[key] = value;
}
}
const handle = mode === BROTLI_DECODE ?
new binding.BrotliDecoder(mode) : new binding.BrotliEncoder(mode);
this._writeState = new Uint32Array(2);
if (!handle.init(brotliInitParamsArray,
this._writeState,
processCallback)) {
throw new ERR_ZLIB_INITIALIZATION_FAILED();
}
ZlibBase.call(this, opts, mode, handle, brotliDefaultOpts);
}
Object.setPrototypeOf(Brotli.prototype, Zlib.prototype);
Object.setPrototypeOf(Brotli, Zlib);
function BrotliCompress(opts) {
if (!(this instanceof BrotliCompress))
return new BrotliCompress(opts);
Brotli.call(this, opts, BROTLI_ENCODE);
}
Object.setPrototypeOf(BrotliCompress.prototype, Brotli.prototype);
Object.setPrototypeOf(BrotliCompress, Brotli);
function BrotliDecompress(opts) {
if (!(this instanceof BrotliDecompress))
return new BrotliDecompress(opts);
Brotli.call(this, opts, BROTLI_DECODE);
}
Object.setPrototypeOf(BrotliDecompress.prototype, Brotli.prototype);
Object.setPrototypeOf(BrotliDecompress, Brotli);
function createProperty(ctor) { function createProperty(ctor) {
return { return {
configurable: true, configurable: true,
@ -759,6 +834,8 @@ module.exports = {
DeflateRaw, DeflateRaw,
InflateRaw, InflateRaw,
Unzip, Unzip,
BrotliCompress,
BrotliDecompress,
// Convenience methods. // Convenience methods.
// compress/decompress a string or buffer in one step. // compress/decompress a string or buffer in one step.
@ -775,7 +852,11 @@ module.exports = {
gunzip: createConvenienceMethod(Gunzip, false), gunzip: createConvenienceMethod(Gunzip, false),
gunzipSync: createConvenienceMethod(Gunzip, true), gunzipSync: createConvenienceMethod(Gunzip, true),
inflateRaw: createConvenienceMethod(InflateRaw, false), inflateRaw: createConvenienceMethod(InflateRaw, false),
inflateRawSync: createConvenienceMethod(InflateRaw, true) inflateRawSync: createConvenienceMethod(InflateRaw, true),
brotliCompress: createConvenienceMethod(BrotliCompress, false),
brotliCompressSync: createConvenienceMethod(BrotliCompress, true),
brotliDecompress: createConvenienceMethod(BrotliDecompress, false),
brotliDecompressSync: createConvenienceMethod(BrotliDecompress, true),
}; };
Object.defineProperties(module.exports, { Object.defineProperties(module.exports, {
@ -786,6 +867,8 @@ Object.defineProperties(module.exports, {
createGzip: createProperty(Gzip), createGzip: createProperty(Gzip),
createGunzip: createProperty(Gunzip), createGunzip: createProperty(Gunzip),
createUnzip: createProperty(Unzip), createUnzip: createProperty(Unzip),
createBrotliCompress: createProperty(BrotliCompress),
createBrotliDecompress: createProperty(BrotliDecompress),
constants: { constants: {
configurable: false, configurable: false,
enumerable: true, enumerable: true,
@ -803,6 +886,7 @@ Object.defineProperties(module.exports, {
const bkeys = Object.keys(constants); const bkeys = Object.keys(constants);
for (var bk = 0; bk < bkeys.length; bk++) { for (var bk = 0; bk < bkeys.length; bk++) {
var bkey = bkeys[bk]; var bkey = bkeys[bk];
if (bkey.startsWith('BROTLI')) continue;
Object.defineProperty(module.exports, bkey, { Object.defineProperty(module.exports, bkey, {
enumerable: false, value: constants[bkey], writable: false enumerable: false, value: constants[bkey], writable: false
}); });

View File

@ -11,6 +11,7 @@
'node_shared%': 'false', 'node_shared%': 'false',
'force_dynamic_crt%': 0, 'force_dynamic_crt%': 0,
'node_module_version%': '', 'node_module_version%': '',
'node_shared_brotli%': 'false',
'node_shared_zlib%': 'false', 'node_shared_zlib%': 'false',
'node_shared_http_parser%': 'false', 'node_shared_http_parser%': 'false',
'node_shared_cares%': 'false', 'node_shared_cares%': 'false',

View File

@ -208,6 +208,10 @@
'dependencies': [ 'deps/nghttp2/nghttp2.gyp:nghttp2' ], 'dependencies': [ 'deps/nghttp2/nghttp2.gyp:nghttp2' ],
}], }],
[ 'node_shared_brotli=="false"', {
'dependencies': [ 'deps/brotli/brotli.gyp:brotli' ],
}],
[ 'OS=="mac"', { [ 'OS=="mac"', {
# linking Corefoundation is needed since certain OSX debugging tools # linking Corefoundation is needed since certain OSX debugging tools
# like Instruments require it for some features # like Instruments require it for some features

View File

@ -1,5 +1,6 @@
#include "node_metadata.h" #include "node_metadata.h"
#include "ares.h" #include "ares.h"
#include "brotli/encode.h"
#include "nghttp2/nghttp2ver.h" #include "nghttp2/nghttp2ver.h"
#include "node.h" #include "node.h"
#include "util.h" #include "util.h"
@ -72,6 +73,13 @@ Metadata::Versions::Versions() {
llhttp = per_process::llhttp_version; llhttp = per_process::llhttp_version;
http_parser = per_process::http_parser_version; http_parser = per_process::http_parser_version;
brotli =
std::to_string(BrotliEncoderVersion() >> 24) +
"." +
std::to_string((BrotliEncoderVersion() & 0xFFF000) >> 12) +
"." +
std::to_string(BrotliEncoderVersion() & 0xFFF);
#if HAVE_OPENSSL #if HAVE_OPENSSL
openssl = GetOpenSSLVersion(); openssl = GetOpenSSLVersion();
#endif #endif

View File

@ -12,6 +12,7 @@ namespace node {
V(v8) \ V(v8) \
V(uv) \ V(uv) \
V(zlib) \ V(zlib) \
V(brotli) \
V(ares) \ V(ares) \
V(modules) \ V(modules) \
V(nghttp2) \ V(nghttp2) \

View File

@ -28,6 +28,9 @@
#include "util-inl.h" #include "util-inl.h"
#include "v8.h" #include "v8.h"
#include "brotli/encode.h"
#include "brotli/decode.h"
#include "zlib.h" #include "zlib.h"
#include <errno.h> #include <errno.h>
@ -82,7 +85,9 @@ enum node_zlib_mode {
GUNZIP, GUNZIP,
DEFLATERAW, DEFLATERAW,
INFLATERAW, INFLATERAW,
UNZIP UNZIP,
BROTLI_DECODE,
BROTLI_ENCODE
}; };
#define GZIP_HEADER_ID1 0x1f #define GZIP_HEADER_ID1 0x1f
@ -111,13 +116,13 @@ class ZlibContext : public MemoryRetainer {
void SetFlush(int flush); void SetFlush(int flush);
void GetAfterWriteOffsets(uint32_t* avail_in, uint32_t* avail_out) const; void GetAfterWriteOffsets(uint32_t* avail_in, uint32_t* avail_out) const;
CompressionError GetErrorInfo() const; CompressionError GetErrorInfo() const;
inline void SetMode(node_zlib_mode mode) { mode_ = mode; }
CompressionError ResetStream();
// Zlib-specific: // Zlib-specific:
CompressionError Init(int level, int window_bits, int mem_level, int strategy, CompressionError Init(int level, int window_bits, int mem_level, int strategy,
std::vector<unsigned char>&& dictionary); std::vector<unsigned char>&& dictionary);
inline void SetMode(node_zlib_mode mode) { mode_ = mode; }
void SetAllocationFunctions(alloc_func alloc, free_func free, void* opaque); void SetAllocationFunctions(alloc_func alloc, free_func free, void* opaque);
CompressionError ResetStream();
CompressionError SetParams(int level, int strategy); CompressionError SetParams(int level, int strategy);
SET_MEMORY_INFO_NAME(ZlibContext) SET_MEMORY_INFO_NAME(ZlibContext)
@ -146,6 +151,77 @@ class ZlibContext : public MemoryRetainer {
DISALLOW_COPY_AND_ASSIGN(ZlibContext); DISALLOW_COPY_AND_ASSIGN(ZlibContext);
}; };
// Brotli has different data types for compression and decompression streams,
// so some of the specifics are implemented in more specific subclasses
class BrotliContext : public MemoryRetainer {
public:
BrotliContext() = default;
void SetBuffers(char* in, uint32_t in_len, char* out, uint32_t out_len);
void SetFlush(int flush);
void GetAfterWriteOffsets(uint32_t* avail_in, uint32_t* avail_out) const;
inline void SetMode(node_zlib_mode mode) { mode_ = mode; }
protected:
node_zlib_mode mode_ = NONE;
uint8_t* next_in_ = nullptr;
uint8_t* next_out_ = nullptr;
size_t avail_in_ = 0;
size_t avail_out_ = 0;
BrotliEncoderOperation flush_ = BROTLI_OPERATION_PROCESS;
// TODO(addaleax): These should not need to be stored here.
// This is currently only done this way to make implementing ResetStream()
// easier.
brotli_alloc_func alloc_ = nullptr;
brotli_free_func free_ = nullptr;
void* alloc_opaque_ = nullptr;
private:
DISALLOW_COPY_AND_ASSIGN(BrotliContext);
};
class BrotliEncoderContext final : public BrotliContext {
public:
void Close();
void DoThreadPoolWork();
CompressionError Init(brotli_alloc_func alloc,
brotli_free_func free,
void* opaque);
CompressionError ResetStream();
CompressionError SetParams(int key, uint32_t value);
CompressionError GetErrorInfo() const;
SET_MEMORY_INFO_NAME(BrotliEncoderContext)
SET_SELF_SIZE(BrotliEncoderContext)
SET_NO_MEMORY_INFO() // state_ is covered through allocation tracking.
private:
bool last_result_ = false;
DeleteFnPtr<BrotliEncoderState, BrotliEncoderDestroyInstance> state_;
};
class BrotliDecoderContext final : public BrotliContext {
public:
void Close();
void DoThreadPoolWork();
CompressionError Init(brotli_alloc_func alloc,
brotli_free_func free,
void* opaque);
CompressionError ResetStream();
CompressionError SetParams(int key, uint32_t value);
CompressionError GetErrorInfo() const;
SET_MEMORY_INFO_NAME(BrotliDecoderContext)
SET_SELF_SIZE(BrotliDecoderContext)
SET_NO_MEMORY_INFO() // state_ is covered through allocation tracking.
private:
BrotliDecoderResult last_result_ = BROTLI_DECODER_RESULT_SUCCESS;
BrotliDecoderErrorCode error_ = BROTLI_DECODER_NO_ERROR;
std::string error_string_;
DeleteFnPtr<BrotliDecoderState, BrotliDecoderDestroyInstance> state_;
};
template <typename CompressionContext> template <typename CompressionContext>
class CompressionStream : public AsyncWrap, public ThreadPoolWork { class CompressionStream : public AsyncWrap, public ThreadPoolWork {
public: public:
@ -340,6 +416,16 @@ class CompressionStream : public AsyncWrap, public ThreadPoolWork {
Close(); Close();
} }
static void Reset(const FunctionCallbackInfo<Value> &args) {
CompressionStream* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
AllocScope alloc_scope(wrap);
const CompressionError err = wrap->context()->ResetStream();
if (err.IsError())
wrap->EmitError(err);
}
void MemoryInfo(MemoryTracker* tracker) const override { void MemoryInfo(MemoryTracker* tracker) const override {
tracker->TrackField("compression context", ctx_); tracker->TrackField("compression context", ctx_);
tracker->TrackFieldWithSize("zlib_memory", tracker->TrackFieldWithSize("zlib_memory",
@ -362,14 +448,19 @@ class CompressionStream : public AsyncWrap, public ThreadPoolWork {
// to V8; rather, we first store it as "unreported" memory in a separate // to V8; rather, we first store it as "unreported" memory in a separate
// field and later report it back from the main thread. // field and later report it back from the main thread.
static void* AllocForZlib(void* data, uInt items, uInt size) { static void* AllocForZlib(void* data, uInt items, uInt size) {
CompressionStream* ctx = static_cast<CompressionStream*>(data);
size_t real_size = size_t real_size =
MultiplyWithOverflowCheck(static_cast<size_t>(items), MultiplyWithOverflowCheck(static_cast<size_t>(items),
static_cast<size_t>(size)) + sizeof(size_t); static_cast<size_t>(size));
char* memory = UncheckedMalloc(real_size); return AllocForBrotli(data, real_size);
}
static void* AllocForBrotli(void* data, size_t size) {
size += sizeof(size_t);
CompressionStream* ctx = static_cast<CompressionStream*>(data);
char* memory = UncheckedMalloc(size);
if (UNLIKELY(memory == nullptr)) return nullptr; if (UNLIKELY(memory == nullptr)) return nullptr;
*reinterpret_cast<size_t*>(memory) = real_size; *reinterpret_cast<size_t*>(memory) = size;
ctx->unreported_allocations_.fetch_add(real_size, ctx->unreported_allocations_.fetch_add(size,
std::memory_order_relaxed); std::memory_order_relaxed);
return memory + sizeof(size_t); return memory + sizeof(size_t);
} }
@ -484,6 +575,7 @@ class ZlibStream : public CompressionStream<ZlibContext> {
Local<ArrayBuffer> ab = array->Buffer(); Local<ArrayBuffer> ab = array->Buffer();
uint32_t* write_result = static_cast<uint32_t*>(ab->GetContents().Data()); uint32_t* write_result = static_cast<uint32_t*>(ab->GetContents().Data());
CHECK(args[5]->IsFunction());
Local<Function> write_js_callback = args[5].As<Function>(); Local<Function> write_js_callback = args[5].As<Function>();
std::vector<unsigned char> dictionary; std::vector<unsigned char> dictionary;
@ -525,20 +617,88 @@ class ZlibStream : public CompressionStream<ZlibContext> {
wrap->EmitError(err); wrap->EmitError(err);
} }
static void Reset(const FunctionCallbackInfo<Value> &args) {
ZlibStream* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
AllocScope alloc_scope(wrap);
const CompressionError err = wrap->context()->ResetStream();
if (err.IsError())
wrap->EmitError(err);
}
SET_MEMORY_INFO_NAME(ZlibStream) SET_MEMORY_INFO_NAME(ZlibStream)
SET_SELF_SIZE(ZlibStream) SET_SELF_SIZE(ZlibStream)
}; };
template <typename CompressionContext>
class BrotliCompressionStream : public CompressionStream<CompressionContext> {
public:
BrotliCompressionStream(Environment* env,
Local<Object> wrap,
node_zlib_mode mode)
: CompressionStream<CompressionContext>(env, wrap) {
context()->SetMode(mode);
}
inline CompressionContext* context() {
return this->CompressionStream<CompressionContext>::context();
}
typedef typename CompressionStream<CompressionContext>::AllocScope AllocScope;
static void New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsInt32());
node_zlib_mode mode =
static_cast<node_zlib_mode>(args[0].As<Int32>()->Value());
new BrotliCompressionStream(env, args.This(), mode);
}
static void Init(const FunctionCallbackInfo<Value>& args) {
BrotliCompressionStream* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
CHECK(args.Length() == 3 && "init(params, writeResult, writeCallback)");
CHECK(args[1]->IsUint32Array());
uint32_t* write_result = reinterpret_cast<uint32_t*>(Buffer::Data(args[1]));
CHECK(args[2]->IsFunction());
Local<Function> write_js_callback = args[2].As<Function>();
wrap->InitStream(write_result, write_js_callback);
AllocScope alloc_scope(wrap);
CompressionError err =
wrap->context()->Init(
CompressionStream<CompressionContext>::AllocForBrotli,
CompressionStream<CompressionContext>::FreeForZlib,
static_cast<CompressionStream<CompressionContext>*>(wrap));
if (err.IsError()) {
wrap->EmitError(err);
args.GetReturnValue().Set(false);
return;
}
CHECK(args[0]->IsUint32Array());
const uint32_t* data = reinterpret_cast<uint32_t*>(Buffer::Data(args[0]));
size_t len = args[0].As<Uint32Array>()->Length();
for (int i = 0; static_cast<size_t>(i) < len; i++) {
if (data[i] == static_cast<uint32_t>(-1))
continue;
err = wrap->context()->SetParams(i, data[i]);
if (err.IsError()) {
wrap->EmitError(err);
args.GetReturnValue().Set(false);
return;
}
}
args.GetReturnValue().Set(true);
}
static void Params(const FunctionCallbackInfo<Value>& args) {
// Currently a no-op, and not accessed from JS land.
// At some point Brotli may support changing parameters on the fly,
// in which case we can implement this and a JS equivalent similar to
// the zlib Params() function.
}
SET_MEMORY_INFO_NAME(BrotliCompressionStream)
SET_SELF_SIZE(BrotliCompressionStream)
};
using BrotliEncoderStream = BrotliCompressionStream<BrotliEncoderContext>;
using BrotliDecoderStream = BrotliCompressionStream<BrotliDecoderContext>;
void ZlibContext::Close() { void ZlibContext::Close() {
CHECK_LE(mode_, UNZIP); CHECK_LE(mode_, UNZIP);
@ -876,29 +1036,194 @@ CompressionError ZlibContext::SetParams(int level, int strategy) {
} }
void BrotliContext::SetBuffers(char* in, uint32_t in_len,
char* out, uint32_t out_len) {
next_in_ = reinterpret_cast<uint8_t*>(in);
next_out_ = reinterpret_cast<uint8_t*>(out);
avail_in_ = in_len;
avail_out_ = out_len;
}
void BrotliContext::SetFlush(int flush) {
flush_ = static_cast<BrotliEncoderOperation>(flush);
}
void BrotliContext::GetAfterWriteOffsets(uint32_t* avail_in,
uint32_t* avail_out) const {
*avail_in = avail_in_;
*avail_out = avail_out_;
}
void BrotliEncoderContext::DoThreadPoolWork() {
CHECK_EQ(mode_, BROTLI_ENCODE);
CHECK(state_);
const uint8_t* next_in = next_in_;
last_result_ = BrotliEncoderCompressStream(state_.get(),
flush_,
&avail_in_,
&next_in,
&avail_out_,
&next_out_,
nullptr);
next_in_ += next_in - next_in_;
}
void BrotliEncoderContext::Close() {
state_.reset();
mode_ = NONE;
}
CompressionError BrotliEncoderContext::Init(brotli_alloc_func alloc,
brotli_free_func free,
void* opaque) {
alloc_ = alloc;
free_ = free;
alloc_opaque_ = opaque;
state_.reset(BrotliEncoderCreateInstance(alloc, free, opaque));
if (!state_) {
return CompressionError("Could not initialize Brotli instance",
"ERR_ZLIB_INITIALIZATION_FAILED",
-1);
} else {
return CompressionError {};
}
}
CompressionError BrotliEncoderContext::ResetStream() {
return Init(alloc_, free_, alloc_opaque_);
}
CompressionError BrotliEncoderContext::SetParams(int key, uint32_t value) {
if (!BrotliEncoderSetParameter(state_.get(),
static_cast<BrotliEncoderParameter>(key),
value)) {
return CompressionError("Setting parameter failed",
"ERR_BROTLI_PARAM_SET_FAILED",
-1);
} else {
return CompressionError {};
}
}
CompressionError BrotliEncoderContext::GetErrorInfo() const {
if (!last_result_) {
return CompressionError("Compression failed",
"ERR_BROTLI_COMPRESSION_FAILED",
-1);
} else {
return CompressionError {};
}
}
void BrotliDecoderContext::Close() {
state_.reset();
mode_ = NONE;
}
void BrotliDecoderContext::DoThreadPoolWork() {
CHECK_EQ(mode_, BROTLI_DECODE);
CHECK(state_);
const uint8_t* next_in = next_in_;
last_result_ = BrotliDecoderDecompressStream(state_.get(),
&avail_in_,
&next_in,
&avail_out_,
&next_out_,
nullptr);
next_in_ += next_in - next_in_;
if (last_result_ == BROTLI_DECODER_RESULT_ERROR) {
error_ = BrotliDecoderGetErrorCode(state_.get());
error_string_ = std::string("ERR_") + BrotliDecoderErrorString(error_);
}
}
CompressionError BrotliDecoderContext::Init(brotli_alloc_func alloc,
brotli_free_func free,
void* opaque) {
alloc_ = alloc;
free_ = free;
alloc_opaque_ = opaque;
state_.reset(BrotliDecoderCreateInstance(alloc, free, opaque));
if (!state_) {
return CompressionError("Could not initialize Brotli instance",
"ERR_ZLIB_INITIALIZATION_FAILED",
-1);
} else {
return CompressionError {};
}
}
CompressionError BrotliDecoderContext::ResetStream() {
return Init(alloc_, free_, alloc_opaque_);
}
CompressionError BrotliDecoderContext::SetParams(int key, uint32_t value) {
if (!BrotliDecoderSetParameter(state_.get(),
static_cast<BrotliDecoderParameter>(key),
value)) {
return CompressionError("Setting parameter failed",
"ERR_BROTLI_PARAM_SET_FAILED",
-1);
} else {
return CompressionError {};
}
}
CompressionError BrotliDecoderContext::GetErrorInfo() const {
if (error_ != BROTLI_DECODER_NO_ERROR) {
return CompressionError("Decompression failed",
error_string_.c_str(),
static_cast<int>(error_));
} else if (flush_ == BROTLI_OPERATION_FINISH &&
last_result_ == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {
// Match zlib's behaviour, as brotli doesn't have its own code for this.
return CompressionError("unexpected end of file",
"Z_BUF_ERROR",
Z_BUF_ERROR);
} else {
return CompressionError {};
}
}
template <typename Stream>
struct MakeClass {
static void Make(Environment* env, Local<Object> target, const char* name) {
Local<FunctionTemplate> z = env->NewFunctionTemplate(Stream::New);
z->InstanceTemplate()->SetInternalFieldCount(1);
z->Inherit(AsyncWrap::GetConstructorTemplate(env));
env->SetProtoMethod(z, "write", Stream::template Write<true>);
env->SetProtoMethod(z, "writeSync", Stream::template Write<false>);
env->SetProtoMethod(z, "close", Stream::Close);
env->SetProtoMethod(z, "init", Stream::Init);
env->SetProtoMethod(z, "params", Stream::Params);
env->SetProtoMethod(z, "reset", Stream::Reset);
Local<String> zlibString = OneByteString(env->isolate(), name);
z->SetClassName(zlibString);
target->Set(env->context(),
zlibString,
z->GetFunction(env->context()).ToLocalChecked()).FromJust();
}
};
void Initialize(Local<Object> target, void Initialize(Local<Object> target,
Local<Value> unused, Local<Value> unused,
Local<Context> context, Local<Context> context,
void* priv) { void* priv) {
Environment* env = Environment::GetCurrent(context); Environment* env = Environment::GetCurrent(context);
Local<FunctionTemplate> z = env->NewFunctionTemplate(ZlibStream::New);
z->InstanceTemplate()->SetInternalFieldCount(1); MakeClass<ZlibStream>::Make(env, target, "Zlib");
z->Inherit(AsyncWrap::GetConstructorTemplate(env)); MakeClass<BrotliEncoderStream>::Make(env, target, "BrotliEncoder");
MakeClass<BrotliDecoderStream>::Make(env, target, "BrotliDecoder");
env->SetProtoMethod(z, "write", ZlibStream::Write<true>);
env->SetProtoMethod(z, "writeSync", ZlibStream::Write<false>);
env->SetProtoMethod(z, "close", ZlibStream::Close);
env->SetProtoMethod(z, "init", ZlibStream::Init);
env->SetProtoMethod(z, "params", ZlibStream::Params);
env->SetProtoMethod(z, "reset", ZlibStream::Reset);
Local<String> zlibString = FIXED_ONE_BYTE_STRING(env->isolate(), "Zlib");
z->SetClassName(zlibString);
target->Set(env->context(),
zlibString,
z->GetFunction(env->context()).ToLocalChecked()).FromJust();
target->Set(env->context(), target->Set(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "ZLIB_VERSION"), FIXED_ONE_BYTE_STRING(env->isolate(), "ZLIB_VERSION"),
@ -944,6 +1269,8 @@ void DefineZlibConstants(Local<Object> target) {
NODE_DEFINE_CONSTANT(target, DEFLATERAW); NODE_DEFINE_CONSTANT(target, DEFLATERAW);
NODE_DEFINE_CONSTANT(target, INFLATERAW); NODE_DEFINE_CONSTANT(target, INFLATERAW);
NODE_DEFINE_CONSTANT(target, UNZIP); NODE_DEFINE_CONSTANT(target, UNZIP);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODE);
NODE_DEFINE_CONSTANT(target, BROTLI_ENCODE);
NODE_DEFINE_CONSTANT(target, Z_MIN_WINDOWBITS); NODE_DEFINE_CONSTANT(target, Z_MIN_WINDOWBITS);
NODE_DEFINE_CONSTANT(target, Z_MAX_WINDOWBITS); NODE_DEFINE_CONSTANT(target, Z_MAX_WINDOWBITS);
@ -957,6 +1284,72 @@ void DefineZlibConstants(Local<Object> target) {
NODE_DEFINE_CONSTANT(target, Z_MIN_LEVEL); NODE_DEFINE_CONSTANT(target, Z_MIN_LEVEL);
NODE_DEFINE_CONSTANT(target, Z_MAX_LEVEL); NODE_DEFINE_CONSTANT(target, Z_MAX_LEVEL);
NODE_DEFINE_CONSTANT(target, Z_DEFAULT_LEVEL); NODE_DEFINE_CONSTANT(target, Z_DEFAULT_LEVEL);
// Brotli constants
NODE_DEFINE_CONSTANT(target, BROTLI_OPERATION_PROCESS);
NODE_DEFINE_CONSTANT(target, BROTLI_OPERATION_FLUSH);
NODE_DEFINE_CONSTANT(target, BROTLI_OPERATION_FINISH);
NODE_DEFINE_CONSTANT(target, BROTLI_OPERATION_EMIT_METADATA);
NODE_DEFINE_CONSTANT(target, BROTLI_PARAM_MODE);
NODE_DEFINE_CONSTANT(target, BROTLI_MODE_GENERIC);
NODE_DEFINE_CONSTANT(target, BROTLI_MODE_TEXT);
NODE_DEFINE_CONSTANT(target, BROTLI_MODE_FONT);
NODE_DEFINE_CONSTANT(target, BROTLI_DEFAULT_MODE);
NODE_DEFINE_CONSTANT(target, BROTLI_PARAM_QUALITY);
NODE_DEFINE_CONSTANT(target, BROTLI_MIN_QUALITY);
NODE_DEFINE_CONSTANT(target, BROTLI_MAX_QUALITY);
NODE_DEFINE_CONSTANT(target, BROTLI_DEFAULT_QUALITY);
NODE_DEFINE_CONSTANT(target, BROTLI_PARAM_LGWIN);
NODE_DEFINE_CONSTANT(target, BROTLI_MIN_WINDOW_BITS);
NODE_DEFINE_CONSTANT(target, BROTLI_MAX_WINDOW_BITS);
NODE_DEFINE_CONSTANT(target, BROTLI_LARGE_MAX_WINDOW_BITS);
NODE_DEFINE_CONSTANT(target, BROTLI_DEFAULT_WINDOW);
NODE_DEFINE_CONSTANT(target, BROTLI_PARAM_LGBLOCK);
NODE_DEFINE_CONSTANT(target, BROTLI_MIN_INPUT_BLOCK_BITS);
NODE_DEFINE_CONSTANT(target, BROTLI_MAX_INPUT_BLOCK_BITS);
NODE_DEFINE_CONSTANT(target, BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING);
NODE_DEFINE_CONSTANT(target, BROTLI_PARAM_SIZE_HINT);
NODE_DEFINE_CONSTANT(target, BROTLI_PARAM_LARGE_WINDOW);
NODE_DEFINE_CONSTANT(target, BROTLI_PARAM_NPOSTFIX);
NODE_DEFINE_CONSTANT(target, BROTLI_PARAM_NDIRECT);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_RESULT_ERROR);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_RESULT_SUCCESS);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT);
NODE_DEFINE_CONSTANT(target,
BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_PARAM_LARGE_WINDOW);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_NO_ERROR);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_SUCCESS);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_NEEDS_MORE_INPUT);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_NEEDS_MORE_OUTPUT);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_ERROR_FORMAT_RESERVED);
NODE_DEFINE_CONSTANT(target,
BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE);
NODE_DEFINE_CONSTANT(target,
BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_ERROR_FORMAT_CL_SPACE);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_ERROR_FORMAT_TRANSFORM);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_ERROR_FORMAT_DICTIONARY);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_ERROR_FORMAT_PADDING_1);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_ERROR_FORMAT_PADDING_2);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_ERROR_FORMAT_DISTANCE);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_ERROR_INVALID_ARGUMENTS);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES);
NODE_DEFINE_CONSTANT(target, BROTLI_DECODER_ERROR_UNREACHABLE);
} }
} // namespace node } // namespace node

View File

@ -2,7 +2,7 @@
const common = require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const expected_keys = ['ares', 'modules', 'node', const expected_keys = ['ares', 'brotli', 'modules', 'node',
'uv', 'v8', 'zlib', 'nghttp2', 'napi', 'uv', 'v8', 'zlib', 'nghttp2', 'napi',
'http_parser', 'llhttp']; 'http_parser', 'llhttp'];
@ -25,6 +25,7 @@ assert.deepStrictEqual(actual_keys, expected_keys);
const commonTemplate = /^\d+\.\d+\.\d+(?:-.*)?$/; const commonTemplate = /^\d+\.\d+\.\d+(?:-.*)?$/;
assert(commonTemplate.test(process.versions.ares)); assert(commonTemplate.test(process.versions.ares));
assert(commonTemplate.test(process.versions.brotli));
assert(commonTemplate.test(process.versions.llhttp)); assert(commonTemplate.test(process.versions.llhttp));
assert(commonTemplate.test(process.versions.http_parser)); assert(commonTemplate.test(process.versions.http_parser));
assert(commonTemplate.test(process.versions.node)); assert(commonTemplate.test(process.versions.node));