From aab0da6daf81b71c5b95e511f66d6c2ac2e74a5a Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Tue, 27 Nov 2018 10:16:34 +0900 Subject: [PATCH] zlib: add brotli support Refs: https://github.com/nodejs/node/pull/20458 Co-authored-by: Hackzzila Backport-PR-URL: https://github.com/nodejs/node/pull/27681 PR-URL: https://github.com/nodejs/node/pull/24938 Reviewed-By: Ruben Bridgewater Reviewed-By: Matteo Collina Reviewed-By: Jan Krems Reviewed-By: James M Snell Reviewed-By: Myles Borins Reviewed-By: Ali Ijaz Sheikh Reviewed-By: Daniel Bevenius --- doc/api/errors.md | 10 + lib/internal/errors.js | 1 + lib/zlib.js | 94 ++++- node.gyp | 1 + node.gypi | 4 + src/node.cc | 12 + src/node_zlib.cc | 453 +++++++++++++++++++++++-- test/parallel/test-process-versions.js | 3 +- 8 files changed, 543 insertions(+), 35 deletions(-) diff --git a/doc/api/errors.md b/doc/api/errors.md index d303012c735646..2cd34a8dbba771 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -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 to define their own types if using the public embedder API. + +### ERR_BROTLI_INVALID_PARAM + +An invalid parameter key was passed during construction of a Brotli stream. + + +### ERR_BROTLI_COMPRESSION_FAILED + +Data passed to a Brotli stream was not successfully compressed. + ### ERR_BUFFER_OUT_OF_BOUNDS diff --git a/lib/internal/errors.js b/lib/internal/errors.js index b2eb16c1f854b3..68ff3e28642ef3 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -512,6 +512,7 @@ E('ERR_ARG_NOT_ITERABLE', '%s must be iterable', TypeError); E('ERR_ASSERTION', '%s', Error); E('ERR_ASYNC_CALLBACK', '%s must be a function', 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', // Using a default argument here is important so the argument is not counted // towards `Function#length`. diff --git a/lib/zlib.js b/lib/zlib.js index 5a2e81ef3fc0a1..1b2b64f09cb3f3 100644 --- a/lib/zlib.js +++ b/lib/zlib.js @@ -22,10 +22,11 @@ 'use strict'; const { + ERR_BROTLI_INVALID_PARAM, ERR_BUFFER_TOO_LARGE, ERR_INVALID_ARG_TYPE, ERR_OUT_OF_RANGE, - ERR_ZLIB_INITIALIZATION_FAILED + ERR_ZLIB_INITIALIZATION_FAILED, } = require('internal/errors').codes; const Transform = require('_stream_transform'); const { @@ -46,11 +47,18 @@ const { owner_symbol } = require('internal/async_hooks').symbols; const constants = process.binding('constants').zlib; const { + // Zlib flush levels 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_MEMLEVEL, Z_MAX_MEMLEVEL, Z_DEFAULT_CHUNK, Z_DEFAULT_COMPRESSION, 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; // translation table for return codes. @@ -211,7 +219,7 @@ function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) { // The ZlibBase class is not exported to user land, the mode should only be // passed in by us. assert(typeof mode === 'number'); - assert(mode >= DEFLATE && mode <= UNZIP); + assert(mode >= DEFLATE && mode <= BROTLI_ENCODE); if (opts) { chunkSize = opts.chunkSize; @@ -478,7 +486,7 @@ function processCallback() { // important to null out the values once they are no longer needed since // `_handle` can stay in memory long after the buffer is needed. var handle = this; - var self = this.jsref; + var self = this[owner_symbol]; var state = self._writeState; if (self._hadError) { @@ -619,6 +627,9 @@ function Zlib(opts, mode) { this._writeState, processCallback, 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(); } @@ -723,6 +734,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) { return { configurable: true, @@ -748,6 +823,8 @@ module.exports = { DeflateRaw, InflateRaw, Unzip, + BrotliCompress, + BrotliDecompress, // Convenience methods. // compress/decompress a string or buffer in one step. @@ -764,7 +841,11 @@ module.exports = { gunzip: createConvenienceMethod(Gunzip, false), gunzipSync: createConvenienceMethod(Gunzip, true), 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, { @@ -775,6 +856,8 @@ Object.defineProperties(module.exports, { createGzip: createProperty(Gzip), createGunzip: createProperty(Gunzip), createUnzip: createProperty(Unzip), + createBrotliCompress: createProperty(BrotliCompress), + createBrotliDecompress: createProperty(BrotliDecompress), constants: { configurable: false, enumerable: true, @@ -792,6 +875,7 @@ Object.defineProperties(module.exports, { const bkeys = Object.keys(constants); for (var bk = 0; bk < bkeys.length; bk++) { var bkey = bkeys[bk]; + if (bkey.startsWith('BROTLI')) continue; Object.defineProperty(module.exports, bkey, { enumerable: true, value: constants[bkey], writable: false }); diff --git a/node.gyp b/node.gyp index d84baabcff0a48..e50a284b3011f0 100644 --- a/node.gyp +++ b/node.gyp @@ -12,6 +12,7 @@ 'node_shared%': 'false', 'force_dynamic_crt%': 0, 'node_module_version%': '', + 'node_shared_brotli%': 'false', 'node_shared_zlib%': 'false', 'node_shared_http_parser%': 'false', 'node_shared_cares%': 'false', diff --git a/node.gypi b/node.gypi index 6a948b48170b92..466a1746811cfa 100644 --- a/node.gypi +++ b/node.gypi @@ -205,6 +205,10 @@ 'dependencies': [ 'deps/nghttp2/nghttp2.gyp:nghttp2' ], }], + [ 'node_shared_brotli=="false"', { + 'dependencies': [ 'deps/brotli/brotli.gyp:brotli' ], + }], + [ 'OS=="mac"', { # linking Corefoundation is needed since certain OSX debugging tools # like Instruments require it for some features diff --git a/src/node.cc b/src/node.cc index 567925bbe1baaf..079bad24286cd2 100644 --- a/src/node.cc +++ b/src/node.cc @@ -53,6 +53,7 @@ #include "ares.h" #include "async_wrap-inl.h" +#include "brotli/encode.h" #include "env-inl.h" #include "handle_wrap.h" #include "http_parser.h" @@ -1723,6 +1724,14 @@ void SetupProcessObject(Environment* env, NODE_STRINGIFY(HTTP_PARSER_VERSION_MINOR) "." NODE_STRINGIFY(HTTP_PARSER_VERSION_PATCH); + + const std::string brotli_version = + std::to_string(BrotliEncoderVersion() >> 24) + + "." + + std::to_string((BrotliEncoderVersion() & 0xFFF000) >> 12) + + "." + + std::to_string(BrotliEncoderVersion() & 0xFFF); + READONLY_PROPERTY(versions, "http_parser", FIXED_ONE_BYTE_STRING(env->isolate(), http_parser_version)); @@ -1739,6 +1748,9 @@ void SetupProcessObject(Environment* env, READONLY_PROPERTY(versions, "zlib", FIXED_ONE_BYTE_STRING(env->isolate(), ZLIB_VERSION)); + READONLY_PROPERTY(versions, + "brotli", + OneByteString(env->isolate(), brotli_version.c_str())); READONLY_PROPERTY(versions, "ares", FIXED_ONE_BYTE_STRING(env->isolate(), ARES_VERSION_STR)); diff --git a/src/node_zlib.cc b/src/node_zlib.cc index 285742cd5ef94e..f97811d2758fed 100644 --- a/src/node_zlib.cc +++ b/src/node_zlib.cc @@ -28,6 +28,9 @@ #include "util-inl.h" #include "v8.h" + +#include "brotli/encode.h" +#include "brotli/decode.h" #include "zlib.h" #include @@ -82,7 +85,9 @@ enum node_zlib_mode { GUNZIP, DEFLATERAW, INFLATERAW, - UNZIP + UNZIP, + BROTLI_DECODE, + BROTLI_ENCODE }; #define GZIP_HEADER_ID1 0x1f @@ -111,13 +116,13 @@ class ZlibContext : public MemoryRetainer { void SetFlush(int flush); void GetAfterWriteOffsets(uint32_t* avail_in, uint32_t* avail_out) const; CompressionError GetErrorInfo() const; + inline void SetMode(node_zlib_mode mode) { mode_ = mode; } + CompressionError ResetStream(); // Zlib-specific: CompressionError Init(int level, int window_bits, int mem_level, int strategy, std::vector&& dictionary); - inline void SetMode(node_zlib_mode mode) { mode_ = mode; } void SetAllocationFunctions(alloc_func alloc, free_func free, void* opaque); - CompressionError ResetStream(); CompressionError SetParams(int level, int strategy); SET_MEMORY_INFO_NAME(ZlibContext) @@ -146,6 +151,77 @@ class ZlibContext : public MemoryRetainer { 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 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 state_; +}; + template class CompressionStream : public AsyncWrap, public ThreadPoolWork { public: @@ -340,6 +416,16 @@ class CompressionStream : public AsyncWrap, public ThreadPoolWork { Close(); } + static void Reset(const FunctionCallbackInfo &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 { tracker->TrackField("compression context", ctx_); 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 // field and later report it back from the main thread. static void* AllocForZlib(void* data, uInt items, uInt size) { - CompressionStream* ctx = static_cast(data); size_t real_size = MultiplyWithOverflowCheck(static_cast(items), - static_cast(size)) + sizeof(size_t); - char* memory = UncheckedMalloc(real_size); + static_cast(size)); + return AllocForBrotli(data, real_size); + } + + static void* AllocForBrotli(void* data, size_t size) { + size += sizeof(size_t); + CompressionStream* ctx = static_cast(data); + char* memory = UncheckedMalloc(size); if (UNLIKELY(memory == nullptr)) return nullptr; - *reinterpret_cast(memory) = real_size; - ctx->unreported_allocations_.fetch_add(real_size, + *reinterpret_cast(memory) = size; + ctx->unreported_allocations_.fetch_add(size, std::memory_order_relaxed); return memory + sizeof(size_t); } @@ -484,6 +575,7 @@ class ZlibStream : public CompressionStream { Local ab = array->Buffer(); uint32_t* write_result = static_cast(ab->GetContents().Data()); + CHECK(args[5]->IsFunction()); Local write_js_callback = args[5].As(); std::vector dictionary; @@ -525,20 +617,88 @@ class ZlibStream : public CompressionStream { wrap->EmitError(err); } - static void Reset(const FunctionCallbackInfo &args) { - ZlibStream* wrap; + SET_MEMORY_INFO_NAME(ZlibStream) + SET_SELF_SIZE(ZlibStream) +}; + +template +class BrotliCompressionStream : public CompressionStream { + public: + BrotliCompressionStream(Environment* env, + Local wrap, + node_zlib_mode mode) + : CompressionStream(env, wrap) { + context()->SetMode(mode); + } + + inline CompressionContext* context() { + return this->CompressionStream::context(); + } + typedef typename CompressionStream::AllocScope AllocScope; + + static void New(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(args[0]->IsInt32()); + node_zlib_mode mode = + static_cast(args[0].As()->Value()); + new BrotliCompressionStream(env, args.This(), mode); + } + + static void Init(const FunctionCallbackInfo& 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(Buffer::Data(args[1])); + + CHECK(args[2]->IsFunction()); + Local write_js_callback = args[2].As(); + wrap->InitStream(write_result, write_js_callback); AllocScope alloc_scope(wrap); - const CompressionError err = wrap->context()->ResetStream(); - if (err.IsError()) + CompressionError err = + wrap->context()->Init( + CompressionStream::AllocForBrotli, + CompressionStream::FreeForZlib, + static_cast*>(wrap)); + if (err.IsError()) { wrap->EmitError(err); + args.GetReturnValue().Set(false); + return; + } + + CHECK(args[0]->IsUint32Array()); + const uint32_t* data = reinterpret_cast(Buffer::Data(args[0])); + size_t len = args[0].As()->Length(); + + for (int i = 0; static_cast(i) < len; i++) { + if (data[i] == static_cast(-1)) + continue; + err = wrap->context()->SetParams(i, data[i]); + if (err.IsError()) { + wrap->EmitError(err); + args.GetReturnValue().Set(false); + return; + } + } + + args.GetReturnValue().Set(true); } - SET_MEMORY_INFO_NAME(ZlibStream) - SET_SELF_SIZE(ZlibStream) + static void Params(const FunctionCallbackInfo& 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; +using BrotliDecoderStream = BrotliCompressionStream; void ZlibContext::Close() { CHECK_LE(mode_, UNZIP); @@ -876,27 +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(in); + next_out_ = reinterpret_cast(out); + avail_in_ = in_len; + avail_out_ = out_len; +} + + +void BrotliContext::SetFlush(int flush) { + flush_ = static_cast(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(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(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(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 +struct MakeClass { + static void Make(Environment* env, Local target, const char* name) { + Local z = env->NewFunctionTemplate(Stream::New); + + z->InstanceTemplate()->SetInternalFieldCount(1); + z->Inherit(AsyncWrap::GetConstructorTemplate(env)); + + env->SetProtoMethod(z, "write", Stream::template Write); + env->SetProtoMethod(z, "writeSync", Stream::template Write); + 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 zlibString = OneByteString(env->isolate(), name); + z->SetClassName(zlibString); + target->Set(env->context(), + zlibString, + z->GetFunction(env->context()).ToLocalChecked()).FromJust(); + } +}; + void Initialize(Local target, Local unused, Local context, void* priv) { Environment* env = Environment::GetCurrent(context); - Local z = env->NewFunctionTemplate(ZlibStream::New); - z->InstanceTemplate()->SetInternalFieldCount(1); - z->Inherit(AsyncWrap::GetConstructorTemplate(env)); - - env->SetProtoMethod(z, "write", ZlibStream::Write); - env->SetProtoMethod(z, "writeSync", ZlibStream::Write); - 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 zlibString = FIXED_ONE_BYTE_STRING(env->isolate(), "Zlib"); - z->SetClassName(zlibString); - target->Set(zlibString, z->GetFunction(env->context()).ToLocalChecked()); + MakeClass::Make(env, target, "Zlib"); + MakeClass::Make(env, target, "BrotliEncoder"); + MakeClass::Make(env, target, "BrotliDecoder"); target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "ZLIB_VERSION"), FIXED_ONE_BYTE_STRING(env->isolate(), ZLIB_VERSION)); @@ -941,6 +1268,8 @@ void DefineZlibConstants(Local target) { NODE_DEFINE_CONSTANT(target, DEFLATERAW); NODE_DEFINE_CONSTANT(target, INFLATERAW); 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_MAX_WINDOWBITS); @@ -954,6 +1283,72 @@ void DefineZlibConstants(Local target) { NODE_DEFINE_CONSTANT(target, Z_MIN_LEVEL); NODE_DEFINE_CONSTANT(target, Z_MAX_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 diff --git a/test/parallel/test-process-versions.js b/test/parallel/test-process-versions.js index e8527fe574a53a..d04748517f3915 100644 --- a/test/parallel/test-process-versions.js +++ b/test/parallel/test-process-versions.js @@ -2,7 +2,7 @@ const common = require('../common'); const assert = require('assert'); -const expected_keys = ['ares', 'http_parser', 'modules', 'node', +const expected_keys = ['ares', 'brotli', 'http_parser', 'modules', 'node', 'uv', 'v8', 'zlib', 'nghttp2', 'napi']; if (common.hasCrypto) { @@ -24,6 +24,7 @@ assert.deepStrictEqual(actual_keys, expected_keys); const commonTemplate = /^\d+\.\d+\.\d+(?:-.*)?$/; assert(commonTemplate.test(process.versions.ares)); +assert(commonTemplate.test(process.versions.brotli)); assert(commonTemplate.test(process.versions.http_parser)); assert(commonTemplate.test(process.versions.node)); assert(commonTemplate.test(process.versions.uv));