mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 18:25:13 +08:00
Signed-off-by: Antoine du Hamel <duhamelantoine1995@gmail.com> PR-URL: https://github.com/nodejs-private/node-private/pull/1002 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> CVE-ID: CVE-2026-111497
2385 lines
79 KiB
C++
2385 lines
79 KiB
C++
// Copyright Joyent, Inc. and other Node contributors.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
// copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
// persons to whom the Software is furnished to do so, subject to the
|
|
// following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included
|
|
// in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
#ifdef NODE_BUNDLED_ZSTD
|
|
#define ZSTD_STATIC_LINKING_ONLY
|
|
#endif
|
|
|
|
#include "memory_tracker-inl.h"
|
|
#include "node.h"
|
|
#include "node_buffer.h"
|
|
|
|
#include "async_wrap-inl.h"
|
|
#include "env-inl.h"
|
|
#include "node_errors.h"
|
|
#include "node_external_reference.h"
|
|
#include "threadpoolwork-inl.h"
|
|
#include "util-inl.h"
|
|
|
|
#include "absl/crc/crc32c.h"
|
|
#include "node_debug.h"
|
|
#include "v8-fast-api-calls.h"
|
|
#include "v8.h"
|
|
|
|
#include "brotli/decode.h"
|
|
#include "brotli/encode.h"
|
|
#include "brotli/shared_dictionary.h"
|
|
#include "zlib.h"
|
|
#include "zstd.h"
|
|
#include "zstd_errors.h"
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <atomic>
|
|
#include <cerrno>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <optional>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace node {
|
|
|
|
using v8::ArrayBuffer;
|
|
using v8::CFunction;
|
|
using v8::Context;
|
|
using v8::Function;
|
|
using v8::FunctionCallbackInfo;
|
|
using v8::FunctionTemplate;
|
|
using v8::HandleScope;
|
|
using v8::Integer;
|
|
using v8::Isolate;
|
|
using v8::Local;
|
|
using v8::Object;
|
|
using v8::Uint32Array;
|
|
using v8::Value;
|
|
|
|
namespace {
|
|
|
|
// Fewer than 64 bytes per chunk is not recommended.
|
|
// Technically it could work with as few as 8, but even 64 bytes
|
|
// is low. Usually a MB or more is best.
|
|
#define Z_MIN_CHUNK 64
|
|
#define Z_MAX_CHUNK std::numeric_limits<double>::infinity()
|
|
#define Z_DEFAULT_CHUNK (16 * 1024)
|
|
#define Z_MIN_MEMLEVEL 1
|
|
#define Z_MAX_MEMLEVEL 9
|
|
#define Z_DEFAULT_MEMLEVEL 8
|
|
#define Z_MIN_LEVEL -1
|
|
#define Z_MAX_LEVEL 9
|
|
#define Z_DEFAULT_LEVEL Z_DEFAULT_COMPRESSION
|
|
#define Z_MIN_WINDOWBITS 8
|
|
#define Z_MAX_WINDOWBITS 15
|
|
#define Z_DEFAULT_WINDOWBITS 15
|
|
|
|
#define ZLIB_ERROR_CODES(V) \
|
|
V(Z_OK) \
|
|
V(Z_STREAM_END) \
|
|
V(Z_NEED_DICT) \
|
|
V(Z_ERRNO) \
|
|
V(Z_STREAM_ERROR) \
|
|
V(Z_DATA_ERROR) \
|
|
V(Z_MEM_ERROR) \
|
|
V(Z_BUF_ERROR) \
|
|
V(Z_VERSION_ERROR) \
|
|
|
|
inline const char* ZlibStrerror(int err) {
|
|
#define V(code) if (err == code) return #code;
|
|
ZLIB_ERROR_CODES(V)
|
|
#undef V
|
|
return "Z_UNKNOWN_ERROR";
|
|
}
|
|
|
|
#define ZSTD_ERROR_CODES(V) \
|
|
V(ZSTD_error_no_error) \
|
|
V(ZSTD_error_GENERIC) \
|
|
V(ZSTD_error_prefix_unknown) \
|
|
V(ZSTD_error_version_unsupported) \
|
|
V(ZSTD_error_frameParameter_unsupported) \
|
|
V(ZSTD_error_frameParameter_windowTooLarge) \
|
|
V(ZSTD_error_corruption_detected) \
|
|
V(ZSTD_error_checksum_wrong) \
|
|
V(ZSTD_error_literals_headerWrong) \
|
|
V(ZSTD_error_dictionary_corrupted) \
|
|
V(ZSTD_error_dictionary_wrong) \
|
|
V(ZSTD_error_dictionaryCreation_failed) \
|
|
V(ZSTD_error_parameter_unsupported) \
|
|
V(ZSTD_error_parameter_combination_unsupported) \
|
|
V(ZSTD_error_parameter_outOfBound) \
|
|
V(ZSTD_error_tableLog_tooLarge) \
|
|
V(ZSTD_error_maxSymbolValue_tooLarge) \
|
|
V(ZSTD_error_maxSymbolValue_tooSmall) \
|
|
V(ZSTD_error_stabilityCondition_notRespected) \
|
|
V(ZSTD_error_stage_wrong) \
|
|
V(ZSTD_error_init_missing) \
|
|
V(ZSTD_error_memory_allocation) \
|
|
V(ZSTD_error_workSpace_tooSmall) \
|
|
V(ZSTD_error_dstSize_tooSmall) \
|
|
V(ZSTD_error_srcSize_wrong) \
|
|
V(ZSTD_error_dstBuffer_null) \
|
|
V(ZSTD_error_noForwardProgress_destFull) \
|
|
V(ZSTD_error_noForwardProgress_inputEmpty)
|
|
|
|
inline const char* ZstdStrerror(int err) {
|
|
#define V(code) \
|
|
if (err == code) return #code;
|
|
ZSTD_ERROR_CODES(V)
|
|
#undef V
|
|
return "ZSTD_error_GENERIC";
|
|
}
|
|
|
|
enum node_zlib_mode {
|
|
NONE,
|
|
DEFLATE,
|
|
INFLATE,
|
|
GZIP,
|
|
GUNZIP,
|
|
DEFLATERAW,
|
|
INFLATERAW,
|
|
UNZIP,
|
|
BROTLI_DECODE,
|
|
BROTLI_ENCODE,
|
|
ZSTD_COMPRESS,
|
|
ZSTD_DECOMPRESS
|
|
};
|
|
|
|
constexpr uint8_t GZIP_HEADER_ID1 = 0x1f;
|
|
constexpr uint8_t GZIP_HEADER_ID2 = 0x8b;
|
|
|
|
struct CompressionError {
|
|
CompressionError(const char* message, const char* code, int err)
|
|
: message(message),
|
|
code(code),
|
|
err(err) {
|
|
CHECK_NOT_NULL(message);
|
|
}
|
|
|
|
CompressionError() = default;
|
|
|
|
const char* message = nullptr;
|
|
const char* code = nullptr;
|
|
int err = 0;
|
|
|
|
inline bool IsError() const { return code != nullptr; }
|
|
};
|
|
|
|
void RecordBrotliParam(std::vector<std::pair<int, uint32_t>>* params,
|
|
int key,
|
|
uint32_t value) {
|
|
for (auto& entry : *params) {
|
|
if (entry.first == key) {
|
|
entry.second = value;
|
|
return;
|
|
}
|
|
}
|
|
params->emplace_back(key, value);
|
|
}
|
|
|
|
class ZlibContext final : public MemoryRetainer {
|
|
public:
|
|
ZlibContext() = default;
|
|
|
|
// Streaming-related, should be available for all compression libraries:
|
|
void Close();
|
|
void DoThreadPoolWork();
|
|
void SetBuffers(const 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;
|
|
CompressionError GetErrorInfo() const;
|
|
inline void SetMode(node_zlib_mode mode) { mode_ = mode; }
|
|
CompressionError ResetStream();
|
|
|
|
// Zlib-specific:
|
|
void Init(int level,
|
|
int window_bits,
|
|
int mem_level,
|
|
int strategy,
|
|
bool reject_garbage_after_end,
|
|
std::vector<unsigned char>&& dictionary);
|
|
CompressionError SetParams(int level, int strategy);
|
|
|
|
SET_MEMORY_INFO_NAME(ZlibContext)
|
|
SET_SELF_SIZE(ZlibContext)
|
|
|
|
void MemoryInfo(MemoryTracker* tracker) const override {
|
|
tracker->TrackField("dictionary", dictionary_);
|
|
}
|
|
|
|
ZlibContext(const ZlibContext&) = delete;
|
|
ZlibContext& operator=(const ZlibContext&) = delete;
|
|
|
|
private:
|
|
CompressionError ErrorForMessage(const char* message) const;
|
|
CompressionError SetDictionary();
|
|
bool InitZlib();
|
|
|
|
Mutex mutex_; // Protects zlib_init_done_.
|
|
bool zlib_init_done_ = false;
|
|
int err_ = 0;
|
|
int flush_ = 0;
|
|
int level_ = 0;
|
|
int mem_level_ = 0;
|
|
node_zlib_mode mode_ = NONE;
|
|
int strategy_ = 0;
|
|
int window_bits_ = 0;
|
|
bool reject_garbage_after_end_ = false;
|
|
unsigned int gzip_id_bytes_read_ = 0;
|
|
std::vector<unsigned char> dictionary_;
|
|
|
|
// gzip and zlib-wrapped deflate emit a header on the first deflate() call.
|
|
// Resetting after those bytes have left the compressor starts a new member
|
|
// while the fragment remains, so gunzip/inflate fail with Z_DATA_ERROR.
|
|
// Raw deflate has no header; Z_FULL_FLUSH + reset still concatenates.
|
|
// A member is complete once deflate() has been called with Z_FINISH and
|
|
// returned Z_STREAM_END.
|
|
bool stream_complete_ = true;
|
|
bool output_emitted_ = false;
|
|
|
|
z_stream strm_;
|
|
};
|
|
|
|
// 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(const 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; }
|
|
|
|
BrotliContext(const BrotliContext&) = delete;
|
|
BrotliContext& operator=(const BrotliContext&) = delete;
|
|
|
|
protected:
|
|
node_zlib_mode mode_ = NONE;
|
|
const 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;
|
|
void* alloc_opaque_ = nullptr;
|
|
};
|
|
|
|
class BrotliEncoderContext final : public BrotliContext {
|
|
public:
|
|
void Close();
|
|
void DoThreadPoolWork();
|
|
CompressionError Init(std::vector<uint8_t>&& dictionary = {});
|
|
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_;
|
|
DeleteFnPtr<BrotliEncoderPreparedDictionary,
|
|
BrotliEncoderDestroyPreparedDictionary>
|
|
prepared_dictionary_;
|
|
// Dictionary data must remain valid while the prepared dictionary is alive.
|
|
std::vector<uint8_t> dictionary_;
|
|
// Last successful parameters, replayed by ResetStream.
|
|
std::vector<std::pair<int, uint32_t>> params_;
|
|
};
|
|
|
|
class BrotliDecoderContext final : public BrotliContext {
|
|
public:
|
|
void Close();
|
|
void DoThreadPoolWork();
|
|
CompressionError Init(std::vector<uint8_t>&& dictionary = {});
|
|
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_;
|
|
// Dictionary data must remain valid for the lifetime of the decoder.
|
|
std::vector<uint8_t> dictionary_;
|
|
// Last successful parameters, replayed by ResetStream.
|
|
std::vector<std::pair<int, uint32_t>> params_;
|
|
};
|
|
|
|
class ZstdContext : public MemoryRetainer {
|
|
public:
|
|
ZstdContext() = default;
|
|
|
|
// Streaming-related, should be available for all compression libraries:
|
|
void SetBuffers(const 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;
|
|
CompressionError GetErrorInfo() const;
|
|
|
|
ZstdContext(const ZstdContext&) = delete;
|
|
ZstdContext& operator=(const ZstdContext&) = delete;
|
|
|
|
protected:
|
|
ZSTD_EndDirective flush_ = ZSTD_e_continue;
|
|
|
|
ZSTD_inBuffer input_ = {nullptr, 0, 0};
|
|
ZSTD_outBuffer output_ = {nullptr, 0, 0};
|
|
|
|
ZSTD_ErrorCode error_ = ZSTD_error_no_error;
|
|
std::string error_string_;
|
|
std::string error_code_string_;
|
|
};
|
|
|
|
class ZstdCompressContext final : public ZstdContext {
|
|
public:
|
|
ZstdCompressContext() = default;
|
|
|
|
// Streaming-related, should be available for all compression libraries:
|
|
void Close();
|
|
void DoThreadPoolWork();
|
|
CompressionError ResetStream();
|
|
|
|
// Zstd specific:
|
|
CompressionError Init(uint64_t pledged_src_size,
|
|
std::string_view dictionary = {},
|
|
bool reject_garbage_after_end = false);
|
|
CompressionError SetParameter(int key, int value);
|
|
|
|
// Wrap ZSTD_freeCCtx to remove the return type.
|
|
static void FreeZstd(ZSTD_CCtx* cctx) { ZSTD_freeCCtx(cctx); }
|
|
|
|
SET_MEMORY_INFO_NAME(ZstdCompressContext)
|
|
SET_SELF_SIZE(ZstdCompressContext)
|
|
SET_NO_MEMORY_INFO()
|
|
|
|
private:
|
|
DeleteFnPtr<ZSTD_CCtx, ZstdCompressContext::FreeZstd> cctx_;
|
|
|
|
uint64_t pledged_src_size_ = ZSTD_CONTENTSIZE_UNKNOWN;
|
|
std::optional<uint64_t> consumed_src_size_;
|
|
|
|
// A frame is complete once ZSTD_compressStream2() has been called with
|
|
// ZSTD_e_end and has returned 0. Resetting an incomplete frame is only unsafe
|
|
// once some of that frame has already been written out: those bytes cannot be
|
|
// discarded, and the next frame would be appended to the fragment. Unflushed
|
|
// internal state alone is cancelled by ZSTD_reset_session_only.
|
|
bool frame_complete_ = true;
|
|
bool frame_output_emitted_ = false;
|
|
};
|
|
|
|
class ZstdDecompressContext final : public ZstdContext {
|
|
public:
|
|
ZstdDecompressContext() = default;
|
|
|
|
// Streaming-related, should be available for all compression libraries:
|
|
void Close();
|
|
void DoThreadPoolWork();
|
|
CompressionError GetErrorInfo() const;
|
|
CompressionError ResetStream();
|
|
|
|
// Zstd specific:
|
|
CompressionError Init(uint64_t pledged_src_size,
|
|
std::string_view dictionary = {},
|
|
bool reject_garbage_after_end = false);
|
|
|
|
CompressionError SetParameter(int key, int value);
|
|
|
|
// Wrap ZSTD_freeDCtx to remove the return type.
|
|
static void FreeZstd(ZSTD_DCtx* dctx) { ZSTD_freeDCtx(dctx); }
|
|
|
|
SET_MEMORY_INFO_NAME(ZstdDecompressContext)
|
|
SET_SELF_SIZE(ZstdDecompressContext)
|
|
SET_NO_MEMORY_INFO()
|
|
|
|
private:
|
|
DeleteFnPtr<ZSTD_DCtx, ZstdDecompressContext::FreeZstd> dctx_;
|
|
bool frame_complete_ = false;
|
|
bool decoding_frame_after_complete_ = false;
|
|
bool reject_garbage_after_end_ = false;
|
|
bool ignoring_trailing_input_ = false;
|
|
size_t frame_prefix_size_ = 0;
|
|
uint8_t possible_frame_types_ = 0;
|
|
};
|
|
|
|
class CompressionStreamMemoryOwner {
|
|
public:
|
|
// Allocation functions provided to zlib itself. We store the real size of
|
|
// the allocated memory chunk just before the "payload" memory we return
|
|
// to zlib.
|
|
// Because we use zlib off the thread pool, we can not report memory directly
|
|
// 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) {
|
|
size_t real_size = MultiplyWithOverflowCheck(static_cast<size_t>(items),
|
|
static_cast<size_t>(size));
|
|
return AllocForBrotli(data, real_size);
|
|
}
|
|
|
|
static constexpr size_t reserveSizeAndAlign =
|
|
std::max(sizeof(size_t), alignof(max_align_t));
|
|
|
|
static void* AllocForBrotli(void* data, size_t size) {
|
|
size += reserveSizeAndAlign;
|
|
CompressionStreamMemoryOwner* ctx =
|
|
static_cast<CompressionStreamMemoryOwner*>(data);
|
|
char* memory = UncheckedMalloc(size);
|
|
if (memory == nullptr) [[unlikely]] {
|
|
return nullptr;
|
|
}
|
|
*reinterpret_cast<size_t*>(memory) = size;
|
|
ctx->unreported_allocations_.fetch_add(size, std::memory_order_relaxed);
|
|
return memory + reserveSizeAndAlign;
|
|
}
|
|
|
|
static void FreeForZlib(void* data, void* pointer) {
|
|
if (pointer == nullptr) [[unlikely]] {
|
|
return;
|
|
}
|
|
CompressionStreamMemoryOwner* ctx =
|
|
static_cast<CompressionStreamMemoryOwner*>(data);
|
|
char* real_pointer = static_cast<char*>(pointer) - reserveSizeAndAlign;
|
|
size_t real_size = *reinterpret_cast<size_t*>(real_pointer);
|
|
ctx->unreported_allocations_.fetch_sub(real_size,
|
|
std::memory_order_relaxed);
|
|
free(real_pointer);
|
|
}
|
|
|
|
void* as_allocator_opaque_value() { return static_cast<void*>(this); }
|
|
|
|
protected:
|
|
ssize_t ComputeAdjustmentToExternalAllocatedMemory() {
|
|
ssize_t report =
|
|
unreported_allocations_.exchange(0, std::memory_order_relaxed);
|
|
CHECK_IMPLIES(report < 0, zlib_memory_ >= static_cast<size_t>(-report));
|
|
zlib_memory_ += report;
|
|
return report;
|
|
}
|
|
|
|
std::atomic<ssize_t> unreported_allocations_{0};
|
|
size_t zlib_memory_ = 0;
|
|
};
|
|
|
|
template <typename CompressionContext>
|
|
class CompressionStream : public AsyncWrap,
|
|
public ThreadPoolWork,
|
|
protected CompressionStreamMemoryOwner {
|
|
public:
|
|
enum InternalFields {
|
|
kCompressionStreamBaseField = AsyncWrap::kInternalFieldCount,
|
|
kWriteJSCallback,
|
|
kInternalFieldCount
|
|
};
|
|
|
|
CompressionStream(Environment* env, Local<Object> wrap)
|
|
: AsyncWrap(env, wrap, AsyncWrap::PROVIDER_ZLIB),
|
|
ThreadPoolWork(env, "zlib"),
|
|
write_result_(nullptr) {
|
|
MakeWeak();
|
|
}
|
|
|
|
~CompressionStream() override {
|
|
CHECK(!write_in_progress_);
|
|
Close();
|
|
CHECK_EQ(zlib_memory_, 0);
|
|
CHECK_EQ(unreported_allocations_, 0);
|
|
}
|
|
|
|
Environment* env() const { return this->ThreadPoolWork::env(); }
|
|
|
|
void Close() {
|
|
if (write_in_progress_) {
|
|
pending_close_ = true;
|
|
return;
|
|
}
|
|
|
|
pending_close_ = false;
|
|
closed_ = true;
|
|
CHECK(init_done_ && "close before init");
|
|
|
|
AllocScope alloc_scope(this);
|
|
ctx_.Close();
|
|
}
|
|
|
|
|
|
static void Close(const FunctionCallbackInfo<Value>& args) {
|
|
CompressionStream* ctx;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ctx, args.This());
|
|
ctx->Close();
|
|
}
|
|
|
|
|
|
// write(flush, in, in_off, in_len, out, out_off, out_len)
|
|
template <bool async>
|
|
static void Write(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
Local<Context> context = env->context();
|
|
CHECK_EQ(args.Length(), 7);
|
|
|
|
uint32_t in_off, in_len, out_off, out_len, flush;
|
|
const char* in;
|
|
char* out;
|
|
|
|
CHECK_EQ(false, args[0]->IsUndefined() && "must provide flush value");
|
|
if (!args[0]->Uint32Value(context).To(&flush)) return;
|
|
|
|
if (flush != Z_NO_FLUSH &&
|
|
flush != Z_PARTIAL_FLUSH &&
|
|
flush != Z_SYNC_FLUSH &&
|
|
flush != Z_FULL_FLUSH &&
|
|
flush != Z_FINISH &&
|
|
flush != Z_BLOCK) {
|
|
UNREACHABLE("Invalid flush value");
|
|
}
|
|
|
|
if (args[1]->IsNull()) {
|
|
// just a flush
|
|
in = nullptr;
|
|
in_len = 0;
|
|
in_off = 0;
|
|
} else {
|
|
CHECK(Buffer::HasInstance(args[1]));
|
|
Local<Object> in_buf = args[1].As<Object>();
|
|
if (!args[2]->Uint32Value(context).To(&in_off)) return;
|
|
if (!args[3]->Uint32Value(context).To(&in_len)) return;
|
|
|
|
if (!Buffer::IsWithinBounds(in_off, in_len, Buffer::Length(in_buf))) {
|
|
return THROW_ERR_OUT_OF_RANGE(env, "input buffer is out of bounds");
|
|
}
|
|
in = Buffer::Data(in_buf) + in_off;
|
|
}
|
|
|
|
CHECK(Buffer::HasInstance(args[4]));
|
|
Local<Object> out_buf = args[4].As<Object>();
|
|
if (!args[5]->Uint32Value(context).To(&out_off)) return;
|
|
if (!args[6]->Uint32Value(context).To(&out_len)) return;
|
|
if (!Buffer::IsWithinBounds(out_off, out_len, Buffer::Length(out_buf))) {
|
|
return THROW_ERR_OUT_OF_RANGE(env, "output buffer is out of bounds");
|
|
}
|
|
out = Buffer::Data(out_buf) + out_off;
|
|
|
|
CompressionStream* ctx;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ctx, args.This());
|
|
|
|
ctx->Write<async>(flush, in, in_len, out, out_len);
|
|
}
|
|
|
|
template <bool async>
|
|
void Write(uint32_t flush,
|
|
const char* in, uint32_t in_len,
|
|
char* out, uint32_t out_len) {
|
|
AllocScope alloc_scope(this);
|
|
|
|
CHECK(init_done_ && "write before init");
|
|
CHECK(!closed_ && "already finalized");
|
|
|
|
CHECK_EQ(false, write_in_progress_);
|
|
CHECK_EQ(false, pending_close_);
|
|
write_in_progress_ = true;
|
|
Ref();
|
|
|
|
ctx_.SetBuffers(in, in_len, out, out_len);
|
|
ctx_.SetFlush(flush);
|
|
|
|
if constexpr (!async) {
|
|
// sync version
|
|
AsyncWrap::env()->PrintSyncTrace();
|
|
DoThreadPoolWork();
|
|
if (CheckError()) {
|
|
UpdateWriteResult();
|
|
write_in_progress_ = false;
|
|
}
|
|
Unref();
|
|
return;
|
|
}
|
|
|
|
// async version
|
|
ScheduleWork();
|
|
}
|
|
|
|
void UpdateWriteResult() {
|
|
ctx_.GetAfterWriteOffsets(&write_result_[1], &write_result_[0]);
|
|
}
|
|
|
|
// thread pool!
|
|
// This function may be called multiple times on the uv_work pool
|
|
// for a single write() call, until all of the input bytes have
|
|
// been consumed.
|
|
void DoThreadPoolWork() override {
|
|
ctx_.DoThreadPoolWork();
|
|
}
|
|
|
|
|
|
bool CheckError() {
|
|
const CompressionError err = ctx_.GetErrorInfo();
|
|
if (!err.IsError()) return true;
|
|
EmitError(err);
|
|
return false;
|
|
}
|
|
|
|
|
|
// v8 land!
|
|
void AfterThreadPoolWork(int status) override {
|
|
DCHECK(init_done_);
|
|
AllocScope alloc_scope(this);
|
|
auto on_scope_leave = OnScopeLeave([&]() { Unref(); });
|
|
|
|
write_in_progress_ = false;
|
|
|
|
if (status == UV_ECANCELED) {
|
|
Close();
|
|
return;
|
|
}
|
|
|
|
CHECK_EQ(status, 0);
|
|
|
|
Environment* env = AsyncWrap::env();
|
|
HandleScope handle_scope(env->isolate());
|
|
Context::Scope context_scope(env->context());
|
|
|
|
if (!CheckError())
|
|
return;
|
|
|
|
UpdateWriteResult();
|
|
|
|
// call the write() cb
|
|
Local<Value> cb =
|
|
object()->GetInternalField(kWriteJSCallback).template As<Value>();
|
|
MakeCallback(cb.As<Function>(), 0, nullptr);
|
|
|
|
if (pending_close_)
|
|
Close();
|
|
}
|
|
|
|
// TODO(addaleax): Switch to modern error system (node_errors.h).
|
|
void EmitError(const CompressionError& err) {
|
|
Environment* env = AsyncWrap::env();
|
|
// If you hit this assertion, you forgot to enter the v8::Context first.
|
|
CHECK_EQ(env->context(), env->isolate()->GetCurrentContext());
|
|
|
|
HandleScope scope(env->isolate());
|
|
Local<Value> args[] = {
|
|
OneByteString(env->isolate(), err.message),
|
|
Integer::New(env->isolate(), err.err),
|
|
OneByteString(env->isolate(), err.code)
|
|
};
|
|
MakeCallback(env->onerror_string(), arraysize(args), args);
|
|
|
|
// no hope of rescue.
|
|
write_in_progress_ = false;
|
|
if (pending_close_)
|
|
Close();
|
|
}
|
|
|
|
static void Reset(const FunctionCallbackInfo<Value> &args) {
|
|
CompressionStream* wrap;
|
|
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());
|
|
|
|
if (wrap->write_in_progress_) {
|
|
wrap->env()->ThrowError(
|
|
"Cannot reset zlib stream while a write is in progress");
|
|
return;
|
|
}
|
|
|
|
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",
|
|
zlib_memory_ + unreported_allocations_);
|
|
}
|
|
|
|
static void* AllocatorOpaquePointerForContext(CompressionContext* ctx) {
|
|
CompressionStream* self = ContainerOf(&CompressionStream::ctx_, ctx);
|
|
// There is nothing at the type level stopping someone from using this
|
|
// method with `ctx` being an argument that is not part of a
|
|
// CompressionStream. This check catches that potential discrepancy in debug
|
|
// builds. std::launder is necessary to keep the compiler from optimizing
|
|
// away the check in the (common) case that `CompressionContext` is a final
|
|
// class.
|
|
DCHECK_EQ(std::launder<MemoryRetainer>(&self->ctx_)->MemoryInfoName(),
|
|
CompressionContext{}.MemoryInfoName());
|
|
return self->as_allocator_opaque_value();
|
|
}
|
|
|
|
protected:
|
|
CompressionContext* context() { return &ctx_; }
|
|
|
|
void InitStream(uint32_t* write_result, Local<Function> write_js_callback) {
|
|
write_result_ = write_result;
|
|
object()->SetInternalField(kWriteJSCallback, write_js_callback);
|
|
init_done_ = true;
|
|
}
|
|
|
|
// This is called on the main thread after zlib may have allocated something
|
|
// in order to report it back to V8.
|
|
void AdjustAmountOfExternalAllocatedMemory() {
|
|
ssize_t report = ComputeAdjustmentToExternalAllocatedMemory();
|
|
if (report == 0) return;
|
|
AsyncWrap::env()->external_memory_accounter()->Update(
|
|
AsyncWrap::env()->isolate(), report);
|
|
}
|
|
|
|
struct AllocScope {
|
|
explicit AllocScope(CompressionStream* stream) : stream(stream) {}
|
|
~AllocScope() { stream->AdjustAmountOfExternalAllocatedMemory(); }
|
|
CompressionStream* stream;
|
|
};
|
|
|
|
private:
|
|
void Ref() {
|
|
if (++refs_ == 1) {
|
|
ClearWeak();
|
|
}
|
|
}
|
|
|
|
void Unref() {
|
|
CHECK_GT(refs_, 0);
|
|
if (--refs_ == 0) {
|
|
MakeWeak();
|
|
}
|
|
}
|
|
|
|
bool init_done_ = false;
|
|
bool write_in_progress_ = false;
|
|
bool pending_close_ = false;
|
|
bool closed_ = false;
|
|
unsigned int refs_ = 0;
|
|
uint32_t* write_result_ = nullptr;
|
|
|
|
CompressionContext ctx_;
|
|
};
|
|
|
|
class ZlibStream final : public CompressionStream<ZlibContext> {
|
|
public:
|
|
ZlibStream(Environment* env, Local<Object> wrap, node_zlib_mode mode)
|
|
: CompressionStream(env, wrap) {
|
|
context()->SetMode(mode);
|
|
}
|
|
|
|
static void New(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
CHECK(args[0]->IsInt32());
|
|
node_zlib_mode mode = FromV8Value<node_zlib_mode>(args[0]);
|
|
new ZlibStream(env, args.This(), mode);
|
|
}
|
|
|
|
// just pull the ints out of the args and call the other Init
|
|
static void Init(const FunctionCallbackInfo<Value>& args) {
|
|
// Refs: https://github.com/nodejs/node/issues/16649
|
|
// Refs: https://github.com/nodejs/node/issues/14161
|
|
if (args.Length() == 5) {
|
|
fprintf(stderr,
|
|
"WARNING: You are likely using a version of node-tar or npm that "
|
|
"is incompatible with this version of Node.js.\nPlease use "
|
|
"either the version of npm that is bundled with Node.js, or "
|
|
"a version of npm (> 5.5.1 or < 5.4.0) or node-tar (> 4.0.1) "
|
|
"that is compatible with Node.js 9 and above.\n");
|
|
}
|
|
CHECK((args.Length() == 7 || args.Length() == 8) &&
|
|
"init(windowBits, level, memLevel, strategy, writeResult, "
|
|
"writeCallback,"
|
|
" dictionary[, rejectGarbageAfterEnd])");
|
|
|
|
ZlibStream* wrap;
|
|
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());
|
|
|
|
Local<Context> context = args.GetIsolate()->GetCurrentContext();
|
|
|
|
// windowBits is special. On the compression side, 0 is an invalid value.
|
|
// But on the decompression side, a value of 0 for windowBits tells zlib
|
|
// to use the window size in the zlib header of the compressed stream.
|
|
uint32_t window_bits;
|
|
if (!args[0]->Uint32Value(context).To(&window_bits)) return;
|
|
|
|
int32_t level;
|
|
if (!args[1]->Int32Value(context).To(&level)) return;
|
|
|
|
uint32_t mem_level;
|
|
if (!args[2]->Uint32Value(context).To(&mem_level)) return;
|
|
|
|
uint32_t strategy;
|
|
if (!args[3]->Uint32Value(context).To(&strategy)) return;
|
|
|
|
CHECK(args[4]->IsUint32Array());
|
|
Local<Uint32Array> array = args[4].As<Uint32Array>();
|
|
CHECK_GE(array->Length(), 2);
|
|
Local<ArrayBuffer> ab = array->Buffer();
|
|
uint32_t* write_result = static_cast<uint32_t*>(ab->Data());
|
|
|
|
CHECK(args[5]->IsFunction());
|
|
Local<Function> write_js_callback = args[5].As<Function>();
|
|
|
|
std::vector<unsigned char> dictionary;
|
|
if (Buffer::HasInstance(args[6])) {
|
|
unsigned char* data =
|
|
reinterpret_cast<unsigned char*>(Buffer::Data(args[6]));
|
|
dictionary = std::vector<unsigned char>(
|
|
data,
|
|
data + Buffer::Length(args[6]));
|
|
}
|
|
|
|
bool reject_garbage_after_end = false;
|
|
if (args.Length() == 8) {
|
|
CHECK(args[7]->IsBoolean());
|
|
reject_garbage_after_end = args[7]->IsTrue();
|
|
}
|
|
|
|
wrap->InitStream(write_result, write_js_callback);
|
|
|
|
AllocScope alloc_scope(wrap);
|
|
wrap->context()->Init(level,
|
|
window_bits,
|
|
mem_level,
|
|
strategy,
|
|
reject_garbage_after_end,
|
|
std::move(dictionary));
|
|
}
|
|
|
|
static void Params(const FunctionCallbackInfo<Value>& args) {
|
|
CHECK(args.Length() == 2 && "params(level, strategy)");
|
|
ZlibStream* wrap;
|
|
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());
|
|
Local<Context> context = args.GetIsolate()->GetCurrentContext();
|
|
int level;
|
|
if (!args[0]->Int32Value(context).To(&level)) return;
|
|
int strategy;
|
|
if (!args[1]->Int32Value(context).To(&strategy)) return;
|
|
|
|
AllocScope alloc_scope(wrap);
|
|
const CompressionError err = wrap->context()->SetParams(level, strategy);
|
|
if (err.IsError())
|
|
wrap->EmitError(err);
|
|
}
|
|
|
|
SET_MEMORY_INFO_NAME(ZlibStream)
|
|
SET_SELF_SIZE(ZlibStream)
|
|
};
|
|
|
|
template <typename CompressionContext>
|
|
class BrotliCompressionStream final :
|
|
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 = FromV8Value<node_zlib_mode>(args[0]);
|
|
new BrotliCompressionStream(env, args.This(), mode);
|
|
}
|
|
|
|
static void Init(const FunctionCallbackInfo<Value>& args) {
|
|
BrotliCompressionStream* wrap;
|
|
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());
|
|
CHECK((args.Length() == 3 || args.Length() == 4) &&
|
|
"init(params, writeResult, writeCallback[, dictionary])");
|
|
|
|
CHECK(args[1]->IsUint32Array());
|
|
CHECK_GE(args[1].As<Uint32Array>()->Length(), 2);
|
|
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);
|
|
std::vector<uint8_t> dictionary;
|
|
if (args.Length() == 4 && !args[3]->IsUndefined()) {
|
|
if (!args[3]->IsArrayBufferView()) {
|
|
THROW_ERR_INVALID_ARG_TYPE(
|
|
wrap->env(), "dictionary must be an ArrayBufferView if provided");
|
|
return;
|
|
}
|
|
ArrayBufferViewContents<uint8_t> contents(args[3]);
|
|
dictionary.assign(contents.data(), contents.data() + contents.length());
|
|
}
|
|
|
|
CompressionError err = wrap->context()->Init(std::move(dictionary));
|
|
if (err.IsError()) {
|
|
wrap->EmitError(err);
|
|
// 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_ERR_ZLIB_INITIALIZATION_FAILED(wrap->env(),
|
|
"Initialization failed");
|
|
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);
|
|
THROW_ERR_ZLIB_INITIALIZATION_FAILED(wrap->env(),
|
|
"Initialization failed");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
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>;
|
|
|
|
template <typename CompressionContext>
|
|
class ZstdStream final : public CompressionStream<CompressionContext> {
|
|
public:
|
|
ZstdStream(Environment* env, Local<Object> wrap)
|
|
: CompressionStream<CompressionContext>(env, wrap) {}
|
|
|
|
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);
|
|
new ZstdStream(env, args.This());
|
|
}
|
|
|
|
static void Init(const FunctionCallbackInfo<Value>& args) {
|
|
CHECK((args.Length() >= 4 && args.Length() <= 6) &&
|
|
"init(params, pledgedSrcSize, writeResult, writeCallback[, "
|
|
"dictionary[, rejectGarbageAfterEnd]])");
|
|
|
|
ZstdStream* wrap;
|
|
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());
|
|
|
|
CHECK(args[2]->IsUint32Array());
|
|
CHECK_GE(args[2].As<Uint32Array>()->Length(), 2);
|
|
uint32_t* write_result = reinterpret_cast<uint32_t*>(Buffer::Data(args[2]));
|
|
|
|
CHECK(args[3]->IsFunction());
|
|
Local<Function> write_js_callback = args[3].As<Function>();
|
|
wrap->InitStream(write_result, write_js_callback);
|
|
|
|
uint64_t pledged_src_size = ZSTD_CONTENTSIZE_UNKNOWN;
|
|
if (!args[1]->IsUndefined()) {
|
|
if (!args[1]->IsNumber()) {
|
|
THROW_ERR_INVALID_ARG_TYPE(wrap->env(),
|
|
"pledgedSrcSize must be a number");
|
|
return;
|
|
}
|
|
if (!IsSafeJsInt(args[1])) {
|
|
THROW_ERR_OUT_OF_RANGE(wrap->env(),
|
|
"pledgedSrcSize must be a safe integer");
|
|
return;
|
|
}
|
|
const int64_t signed_pledged_src_size = args[1].As<Integer>()->Value();
|
|
if (signed_pledged_src_size < 0) {
|
|
THROW_ERR_OUT_OF_RANGE(wrap->env(),
|
|
"pledgedSrcSize must be non-negative");
|
|
return;
|
|
}
|
|
pledged_src_size = static_cast<uint64_t>(signed_pledged_src_size);
|
|
}
|
|
|
|
AllocScope alloc_scope(wrap);
|
|
std::string_view dictionary;
|
|
ArrayBufferViewContents<char> contents;
|
|
if (args.Length() >= 5 && !args[4]->IsUndefined()) {
|
|
if (!args[4]->IsArrayBufferView()) {
|
|
THROW_ERR_INVALID_ARG_TYPE(
|
|
wrap->env(), "dictionary must be an ArrayBufferView if provided");
|
|
return;
|
|
}
|
|
contents.ReadValue(args[4]);
|
|
dictionary = std::string_view(contents.data(), contents.length());
|
|
}
|
|
|
|
bool reject_garbage_after_end = false;
|
|
if (args.Length() == 6) {
|
|
CHECK(args[5]->IsBoolean());
|
|
reject_garbage_after_end = args[5]->IsTrue();
|
|
}
|
|
|
|
CompressionError err = wrap->context()->Init(
|
|
pledged_src_size, dictionary, reject_garbage_after_end);
|
|
if (err.IsError()) {
|
|
wrap->EmitError(err);
|
|
THROW_ERR_ZLIB_INITIALIZATION_FAILED(wrap->env(), err.message);
|
|
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;
|
|
|
|
CompressionError err = wrap->context()->SetParameter(i, data[i]);
|
|
if (err.IsError()) {
|
|
wrap->EmitError(err);
|
|
THROW_ERR_ZLIB_INITIALIZATION_FAILED(wrap->env(), err.message);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void Params(const FunctionCallbackInfo<Value>& args) {
|
|
// Currently a no-op, and not accessed from JS land.
|
|
// At some point zstd 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(ZstdStream)
|
|
SET_SELF_SIZE(ZstdStream)
|
|
};
|
|
|
|
using ZstdCompressStream = ZstdStream<ZstdCompressContext>;
|
|
using ZstdDecompressStream = ZstdStream<ZstdDecompressContext>;
|
|
|
|
void ZlibContext::Close() {
|
|
{
|
|
Mutex::ScopedLock lock(mutex_);
|
|
if (!zlib_init_done_) {
|
|
dictionary_.clear();
|
|
mode_ = NONE;
|
|
return;
|
|
}
|
|
}
|
|
|
|
CHECK_LE(mode_, UNZIP);
|
|
|
|
int status = Z_OK;
|
|
if (mode_ == DEFLATE || mode_ == GZIP || mode_ == DEFLATERAW) {
|
|
status = deflateEnd(&strm_);
|
|
} else if (mode_ == INFLATE || mode_ == GUNZIP || mode_ == INFLATERAW ||
|
|
mode_ == UNZIP) {
|
|
status = inflateEnd(&strm_);
|
|
}
|
|
|
|
CHECK(status == Z_OK || status == Z_DATA_ERROR);
|
|
mode_ = NONE;
|
|
|
|
dictionary_.clear();
|
|
}
|
|
|
|
|
|
void ZlibContext::DoThreadPoolWork() {
|
|
bool first_init_call = InitZlib();
|
|
if (first_init_call && err_ != Z_OK) {
|
|
return;
|
|
}
|
|
|
|
const Bytef* next_expected_header_byte = nullptr;
|
|
|
|
// If the avail_out is left at 0, then it means that it ran out
|
|
// of room. If there was avail_out left over, then it means
|
|
// that all of the input was consumed.
|
|
switch (mode_) {
|
|
case DEFLATE:
|
|
case GZIP:
|
|
case DEFLATERAW: {
|
|
const unsigned out_before = strm_.avail_out;
|
|
err_ = deflate(&strm_, flush_);
|
|
if (out_before > strm_.avail_out) {
|
|
output_emitted_ = true;
|
|
}
|
|
if (err_ == Z_STREAM_END) {
|
|
stream_complete_ = true;
|
|
output_emitted_ = false;
|
|
} else if (err_ == Z_OK || err_ == Z_BUF_ERROR) {
|
|
stream_complete_ = false;
|
|
}
|
|
break;
|
|
}
|
|
case UNZIP:
|
|
if (strm_.avail_in > 0) {
|
|
next_expected_header_byte = strm_.next_in;
|
|
}
|
|
|
|
switch (gzip_id_bytes_read_) {
|
|
case 0:
|
|
if (next_expected_header_byte == nullptr) {
|
|
break;
|
|
}
|
|
|
|
if (*next_expected_header_byte == GZIP_HEADER_ID1) {
|
|
gzip_id_bytes_read_ = 1;
|
|
next_expected_header_byte++;
|
|
|
|
if (strm_.avail_in == 1) {
|
|
// The only available byte was already read.
|
|
break;
|
|
}
|
|
} else {
|
|
mode_ = INFLATE;
|
|
break;
|
|
}
|
|
|
|
[[fallthrough]];
|
|
case 1:
|
|
if (next_expected_header_byte == nullptr) {
|
|
break;
|
|
}
|
|
|
|
if (*next_expected_header_byte == GZIP_HEADER_ID2) {
|
|
gzip_id_bytes_read_ = 2;
|
|
mode_ = GUNZIP;
|
|
} else {
|
|
// There is no actual difference between INFLATE and INFLATERAW
|
|
// (after initialization).
|
|
mode_ = INFLATE;
|
|
}
|
|
|
|
break;
|
|
default:
|
|
UNREACHABLE("invalid number of gzip magic number bytes read");
|
|
}
|
|
|
|
[[fallthrough]];
|
|
case INFLATE:
|
|
case GUNZIP:
|
|
case INFLATERAW:
|
|
err_ = inflate(&strm_, flush_);
|
|
|
|
// If data was encoded with dictionary (INFLATERAW will have it set in
|
|
// SetDictionary, don't repeat that here)
|
|
if (mode_ != INFLATERAW &&
|
|
err_ == Z_NEED_DICT &&
|
|
!dictionary_.empty()) {
|
|
// Load it
|
|
err_ = inflateSetDictionary(&strm_,
|
|
dictionary_.data(),
|
|
dictionary_.size());
|
|
if (err_ == Z_OK) {
|
|
// And try to decode again
|
|
err_ = inflate(&strm_, flush_);
|
|
} else if (err_ == Z_DATA_ERROR) {
|
|
// Both inflateSetDictionary() and inflate() return Z_DATA_ERROR.
|
|
// Make it possible for After() to tell a bad dictionary from bad
|
|
// input.
|
|
err_ = Z_NEED_DICT;
|
|
}
|
|
}
|
|
|
|
while (strm_.avail_in > 0 && mode_ == GUNZIP && err_ == Z_STREAM_END &&
|
|
!reject_garbage_after_end_ && strm_.next_in[0] != 0x00) {
|
|
// Bytes remain in input buffer. Perhaps this is another compressed
|
|
// member in the same archive, or just trailing garbage.
|
|
// Trailing zero bytes are okay, though, since they are frequently
|
|
// used for padding.
|
|
|
|
ResetStream();
|
|
err_ = inflate(&strm_, flush_);
|
|
}
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
|
|
void ZlibContext::SetBuffers(const char* in, uint32_t in_len,
|
|
char* out, uint32_t out_len) {
|
|
strm_.avail_in = in_len;
|
|
strm_.next_in = const_cast<Bytef*>(reinterpret_cast<const Bytef*>(in));
|
|
strm_.avail_out = out_len;
|
|
strm_.next_out = reinterpret_cast<Bytef*>(out);
|
|
}
|
|
|
|
|
|
void ZlibContext::SetFlush(int flush) {
|
|
flush_ = flush;
|
|
}
|
|
|
|
|
|
void ZlibContext::GetAfterWriteOffsets(uint32_t* avail_in,
|
|
uint32_t* avail_out) const {
|
|
*avail_in = strm_.avail_in;
|
|
*avail_out = strm_.avail_out;
|
|
}
|
|
|
|
|
|
CompressionError ZlibContext::ErrorForMessage(const char* message) const {
|
|
if (strm_.msg != nullptr)
|
|
message = strm_.msg;
|
|
|
|
return CompressionError { message, ZlibStrerror(err_), err_ };
|
|
}
|
|
|
|
|
|
CompressionError ZlibContext::GetErrorInfo() const {
|
|
// Acceptable error states depend on the type of zlib stream.
|
|
switch (err_) {
|
|
case Z_OK:
|
|
case Z_BUF_ERROR:
|
|
if (strm_.avail_out != 0 && flush_ == Z_FINISH) {
|
|
return ErrorForMessage("unexpected end of file");
|
|
}
|
|
case Z_STREAM_END:
|
|
// normal statuses, not fatal
|
|
break;
|
|
case Z_NEED_DICT:
|
|
if (dictionary_.empty())
|
|
return ErrorForMessage("Missing dictionary");
|
|
else
|
|
return ErrorForMessage("Bad dictionary");
|
|
default:
|
|
// something else.
|
|
return ErrorForMessage("Zlib error");
|
|
}
|
|
|
|
return CompressionError {};
|
|
}
|
|
|
|
|
|
CompressionError ZlibContext::ResetStream() {
|
|
// deflateReset() is deflateEnd + deflateInit: a new stream. gzip emits a
|
|
// wrapper header on the first write; those bytes cannot be taken back, so
|
|
// refuse reset once an incomplete gzip member has emitted output.
|
|
// zlib-wrapped deflate still allows reset after flush: callers discard the
|
|
// first member (test-zlib-dictionary.js). Raw deflate has no wrapper header,
|
|
// so flush+reset still concatenates.
|
|
if (mode_ == GZIP && !stream_complete_ && output_emitted_) {
|
|
return CompressionError(
|
|
"Cannot reset a gzip stream with an incomplete member; end the "
|
|
"stream or start a new gzip compressor",
|
|
"ERR_ZLIB_INCOMPLETE_FRAME",
|
|
Z_STREAM_ERROR);
|
|
}
|
|
|
|
bool first_init_call = InitZlib();
|
|
if (first_init_call && err_ != Z_OK) {
|
|
return ErrorForMessage("Failed to init stream before reset");
|
|
}
|
|
|
|
err_ = Z_OK;
|
|
|
|
switch (mode_) {
|
|
case DEFLATE:
|
|
case DEFLATERAW:
|
|
case GZIP:
|
|
err_ = deflateReset(&strm_);
|
|
break;
|
|
case INFLATE:
|
|
case INFLATERAW:
|
|
case GUNZIP:
|
|
err_ = inflateReset(&strm_);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (err_ != Z_OK)
|
|
return ErrorForMessage("Failed to reset stream");
|
|
|
|
stream_complete_ = true;
|
|
output_emitted_ = false;
|
|
return SetDictionary();
|
|
}
|
|
|
|
void ZlibContext::Init(int level,
|
|
int window_bits,
|
|
int mem_level,
|
|
int strategy,
|
|
bool reject_garbage_after_end,
|
|
std::vector<unsigned char>&& dictionary) {
|
|
// Set allocation functions
|
|
strm_.zalloc = CompressionStreamMemoryOwner::AllocForZlib;
|
|
strm_.zfree = CompressionStreamMemoryOwner::FreeForZlib;
|
|
strm_.opaque =
|
|
CompressionStream<ZlibContext>::AllocatorOpaquePointerForContext(this);
|
|
|
|
if (!((window_bits == 0) &&
|
|
(mode_ == INFLATE ||
|
|
mode_ == GUNZIP ||
|
|
mode_ == UNZIP))) {
|
|
CHECK(
|
|
(window_bits >= Z_MIN_WINDOWBITS && window_bits <= Z_MAX_WINDOWBITS) &&
|
|
"invalid windowBits");
|
|
}
|
|
|
|
CHECK((level >= Z_MIN_LEVEL && level <= Z_MAX_LEVEL) &&
|
|
"invalid compression level");
|
|
|
|
CHECK((mem_level >= Z_MIN_MEMLEVEL && mem_level <= Z_MAX_MEMLEVEL) &&
|
|
"invalid memlevel");
|
|
|
|
CHECK((strategy == Z_FILTERED || strategy == Z_HUFFMAN_ONLY ||
|
|
strategy == Z_RLE || strategy == Z_FIXED ||
|
|
strategy == Z_DEFAULT_STRATEGY) &&
|
|
"invalid strategy");
|
|
|
|
level_ = level;
|
|
window_bits_ = window_bits;
|
|
mem_level_ = mem_level;
|
|
strategy_ = strategy;
|
|
reject_garbage_after_end_ = reject_garbage_after_end;
|
|
|
|
flush_ = Z_NO_FLUSH;
|
|
|
|
err_ = Z_OK;
|
|
stream_complete_ = true;
|
|
output_emitted_ = false;
|
|
|
|
if (mode_ == GZIP || mode_ == GUNZIP) {
|
|
window_bits_ += 16;
|
|
}
|
|
|
|
if (mode_ == UNZIP) {
|
|
window_bits_ += 32;
|
|
}
|
|
|
|
if (mode_ == DEFLATERAW || mode_ == INFLATERAW) {
|
|
window_bits_ *= -1;
|
|
}
|
|
|
|
dictionary_ = std::move(dictionary);
|
|
}
|
|
|
|
bool ZlibContext::InitZlib() {
|
|
Mutex::ScopedLock lock(mutex_);
|
|
if (zlib_init_done_) {
|
|
return false;
|
|
}
|
|
|
|
// If deflateInit2() fails with Z_VERSION_ERROR, msg remains uninitialized.
|
|
// Initialize it here to avoid reading the uninitialized pointer during error
|
|
// emission.
|
|
strm_.msg = nullptr;
|
|
|
|
switch (mode_) {
|
|
case DEFLATE:
|
|
case GZIP:
|
|
case DEFLATERAW:
|
|
err_ = deflateInit2(&strm_,
|
|
level_,
|
|
Z_DEFLATED,
|
|
window_bits_,
|
|
mem_level_,
|
|
strategy_);
|
|
break;
|
|
case INFLATE:
|
|
case GUNZIP:
|
|
case INFLATERAW:
|
|
case UNZIP:
|
|
err_ = inflateInit2(&strm_, window_bits_);
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
|
|
if (err_ != Z_OK) {
|
|
dictionary_.clear();
|
|
mode_ = NONE;
|
|
return true;
|
|
}
|
|
|
|
SetDictionary();
|
|
zlib_init_done_ = true;
|
|
return true;
|
|
}
|
|
|
|
|
|
CompressionError ZlibContext::SetDictionary() {
|
|
if (dictionary_.empty())
|
|
return CompressionError {};
|
|
|
|
err_ = Z_OK;
|
|
|
|
switch (mode_) {
|
|
case DEFLATE:
|
|
case DEFLATERAW:
|
|
err_ = deflateSetDictionary(&strm_,
|
|
dictionary_.data(),
|
|
dictionary_.size());
|
|
break;
|
|
case INFLATERAW:
|
|
// The other inflate cases will have the dictionary set when inflate()
|
|
// returns Z_NEED_DICT in Process()
|
|
err_ = inflateSetDictionary(&strm_,
|
|
dictionary_.data(),
|
|
dictionary_.size());
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (err_ != Z_OK) {
|
|
return ErrorForMessage("Failed to set dictionary");
|
|
}
|
|
|
|
return CompressionError {};
|
|
}
|
|
|
|
|
|
CompressionError ZlibContext::SetParams(int level, int strategy) {
|
|
bool first_init_call = InitZlib();
|
|
if (first_init_call && err_ != Z_OK) {
|
|
return ErrorForMessage("Failed to init stream before set parameters");
|
|
}
|
|
|
|
err_ = Z_OK;
|
|
|
|
switch (mode_) {
|
|
case DEFLATE:
|
|
case DEFLATERAW:
|
|
err_ = deflateParams(&strm_, level, strategy);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (err_ != Z_OK && err_ != Z_BUF_ERROR) {
|
|
return ErrorForMessage("Failed to set parameters");
|
|
}
|
|
|
|
return CompressionError {};
|
|
}
|
|
|
|
|
|
void BrotliContext::SetBuffers(const char* in, uint32_t in_len,
|
|
char* out, uint32_t out_len) {
|
|
next_in_ = reinterpret_cast<const 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();
|
|
prepared_dictionary_.reset();
|
|
dictionary_.clear();
|
|
params_.clear();
|
|
mode_ = NONE;
|
|
}
|
|
|
|
CompressionError BrotliEncoderContext::Init(std::vector<uint8_t>&& dictionary) {
|
|
brotli_alloc_func alloc = CompressionStreamMemoryOwner::AllocForBrotli;
|
|
brotli_free_func free = CompressionStreamMemoryOwner::FreeForZlib;
|
|
void* opaque =
|
|
CompressionStream<BrotliEncoderContext>::AllocatorOpaquePointerForContext(
|
|
this);
|
|
|
|
// Clean up any previous dictionary state before re-initializing.
|
|
prepared_dictionary_.reset();
|
|
dictionary_.clear();
|
|
params_.clear();
|
|
|
|
state_.reset(BrotliEncoderCreateInstance(alloc, free, opaque));
|
|
if (!state_) {
|
|
return CompressionError("Could not initialize Brotli instance",
|
|
"ERR_ZLIB_INITIALIZATION_FAILED",
|
|
-1);
|
|
}
|
|
|
|
if (!dictionary.empty()) {
|
|
// The dictionary data must remain valid for the lifetime of the prepared
|
|
// dictionary, so take ownership via move.
|
|
dictionary_ = std::move(dictionary);
|
|
|
|
prepared_dictionary_.reset(
|
|
BrotliEncoderPrepareDictionary(BROTLI_SHARED_DICTIONARY_RAW,
|
|
dictionary_.size(),
|
|
dictionary_.data(),
|
|
BROTLI_MAX_QUALITY,
|
|
alloc,
|
|
free,
|
|
opaque));
|
|
if (!prepared_dictionary_) {
|
|
return CompressionError("Failed to prepare brotli dictionary",
|
|
"ERR_ZLIB_DICTIONARY_LOAD_FAILED",
|
|
-1);
|
|
}
|
|
|
|
if (!BrotliEncoderAttachPreparedDictionary(state_.get(),
|
|
prepared_dictionary_.get())) {
|
|
return CompressionError("Failed to attach brotli dictionary",
|
|
"ERR_ZLIB_DICTIONARY_LOAD_FAILED",
|
|
-1);
|
|
}
|
|
}
|
|
|
|
return CompressionError{};
|
|
}
|
|
|
|
CompressionError BrotliEncoderContext::ResetStream() {
|
|
std::vector<uint8_t> dictionary = dictionary_;
|
|
const auto params = params_;
|
|
CompressionError err = Init(std::move(dictionary));
|
|
if (err.IsError()) {
|
|
return err;
|
|
}
|
|
for (const auto& entry : params) {
|
|
err = SetParams(entry.first, entry.second);
|
|
if (err.IsError()) {
|
|
return err;
|
|
}
|
|
}
|
|
return CompressionError{};
|
|
}
|
|
|
|
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 {
|
|
RecordBrotliParam(¶ms_, key, value);
|
|
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();
|
|
dictionary_.clear();
|
|
params_.clear();
|
|
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(std::vector<uint8_t>&& dictionary) {
|
|
brotli_alloc_func alloc = CompressionStreamMemoryOwner::AllocForBrotli;
|
|
brotli_free_func free = CompressionStreamMemoryOwner::FreeForZlib;
|
|
void* opaque =
|
|
CompressionStream<BrotliDecoderContext>::AllocatorOpaquePointerForContext(
|
|
this);
|
|
|
|
// Clean up any previous dictionary state before re-initializing.
|
|
dictionary_.clear();
|
|
params_.clear();
|
|
|
|
state_.reset(BrotliDecoderCreateInstance(alloc, free, opaque));
|
|
if (!state_) {
|
|
return CompressionError("Could not initialize Brotli instance",
|
|
"ERR_ZLIB_INITIALIZATION_FAILED",
|
|
-1);
|
|
}
|
|
|
|
if (!dictionary.empty()) {
|
|
// The dictionary data must remain valid for the lifetime of the decoder,
|
|
// so take ownership via move.
|
|
dictionary_ = std::move(dictionary);
|
|
|
|
if (!BrotliDecoderAttachDictionary(state_.get(),
|
|
BROTLI_SHARED_DICTIONARY_RAW,
|
|
dictionary_.size(),
|
|
dictionary_.data())) {
|
|
return CompressionError("Failed to attach brotli dictionary",
|
|
"ERR_ZLIB_DICTIONARY_LOAD_FAILED",
|
|
-1);
|
|
}
|
|
}
|
|
|
|
return CompressionError{};
|
|
}
|
|
|
|
CompressionError BrotliDecoderContext::ResetStream() {
|
|
std::vector<uint8_t> dictionary = dictionary_;
|
|
const auto params = params_;
|
|
CompressionError err = Init(std::move(dictionary));
|
|
if (err.IsError()) {
|
|
return err;
|
|
}
|
|
for (const auto& entry : params) {
|
|
err = SetParams(entry.first, entry.second);
|
|
if (err.IsError()) {
|
|
return err;
|
|
}
|
|
}
|
|
return CompressionError{};
|
|
}
|
|
|
|
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 {
|
|
RecordBrotliParam(¶ms_, key, value);
|
|
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 {};
|
|
}
|
|
}
|
|
|
|
void ZstdContext::SetBuffers(const char* in,
|
|
uint32_t in_len,
|
|
char* out,
|
|
uint32_t out_len) {
|
|
input_.src = reinterpret_cast<const uint8_t*>(in);
|
|
input_.size = in_len;
|
|
input_.pos = 0;
|
|
|
|
output_.dst = reinterpret_cast<uint8_t*>(out);
|
|
output_.size = out_len;
|
|
output_.pos = 0;
|
|
}
|
|
|
|
void ZstdContext::SetFlush(int flush) {
|
|
flush_ = static_cast<ZSTD_EndDirective>(flush);
|
|
}
|
|
|
|
void ZstdContext::GetAfterWriteOffsets(uint32_t* avail_in,
|
|
uint32_t* avail_out) const {
|
|
*avail_in = input_.size - input_.pos;
|
|
*avail_out = output_.size - output_.pos;
|
|
}
|
|
|
|
CompressionError ZstdContext::GetErrorInfo() const {
|
|
if (error_ != ZSTD_error_no_error) {
|
|
return CompressionError(error_string_.c_str(),
|
|
error_code_string_.c_str(),
|
|
static_cast<int>(error_));
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
CompressionError ZstdCompressContext::SetParameter(int key, int value) {
|
|
size_t result = ZSTD_CCtx_setParameter(
|
|
cctx_.get(), static_cast<ZSTD_cParameter>(key), value);
|
|
if (ZSTD_isError(result)) {
|
|
return CompressionError(
|
|
"Setting parameter failed", "ERR_ZSTD_PARAM_SET_FAILED", -1);
|
|
}
|
|
return {};
|
|
}
|
|
|
|
void ZstdCompressContext::Close() {
|
|
cctx_.reset();
|
|
}
|
|
|
|
CompressionError ZstdCompressContext::Init(uint64_t pledged_src_size,
|
|
std::string_view dictionary,
|
|
bool) {
|
|
pledged_src_size_ = pledged_src_size;
|
|
frame_complete_ = true;
|
|
frame_output_emitted_ = false;
|
|
if (pledged_src_size == ZSTD_CONTENTSIZE_UNKNOWN) {
|
|
consumed_src_size_.reset();
|
|
} else {
|
|
consumed_src_size_ = 0;
|
|
}
|
|
#ifdef NODE_BUNDLED_ZSTD
|
|
ZSTD_customMem custom_mem = {
|
|
CompressionStreamMemoryOwner::AllocForBrotli,
|
|
CompressionStreamMemoryOwner::FreeForZlib,
|
|
CompressionStream<ZstdCompressContext>::AllocatorOpaquePointerForContext(
|
|
this)};
|
|
cctx_.reset(ZSTD_createCCtx_advanced(custom_mem));
|
|
#else
|
|
cctx_.reset(ZSTD_createCCtx());
|
|
#endif
|
|
if (!cctx_) {
|
|
return CompressionError("Could not initialize zstd instance",
|
|
"ERR_ZLIB_INITIALIZATION_FAILED",
|
|
-1);
|
|
}
|
|
|
|
if (!dictionary.empty()) {
|
|
size_t ret = ZSTD_CCtx_loadDictionary(
|
|
cctx_.get(), dictionary.data(), dictionary.size());
|
|
if (ZSTD_isError(ret)) {
|
|
return CompressionError("Failed to load zstd dictionary",
|
|
"ERR_ZLIB_DICTIONARY_LOAD_FAILED",
|
|
-1);
|
|
}
|
|
}
|
|
|
|
size_t result = ZSTD_CCtx_setPledgedSrcSize(cctx_.get(), pledged_src_size);
|
|
if (ZSTD_isError(result)) {
|
|
return CompressionError(
|
|
"Could not set pledged src size", "ERR_ZLIB_INITIALIZATION_FAILED", -1);
|
|
}
|
|
return {};
|
|
}
|
|
|
|
CompressionError ZstdCompressContext::ResetStream() {
|
|
// ZSTD_reset_session_only cancels unflushed internal data. Bytes that have
|
|
// already been written out cannot be taken back, so refuse reset only when
|
|
// the current incomplete frame has already emitted output.
|
|
if (!frame_complete_ && frame_output_emitted_) {
|
|
return CompressionError(
|
|
"Cannot reset a zstd stream with an incomplete frame; end the frame "
|
|
"or discard the output produced so far",
|
|
"ERR_ZLIB_INCOMPLETE_FRAME",
|
|
ZSTD_error_stage_wrong);
|
|
}
|
|
|
|
size_t result = ZSTD_CCtx_reset(cctx_.get(), ZSTD_reset_session_only);
|
|
if (ZSTD_isError(result)) {
|
|
const ZSTD_ErrorCode error = ZSTD_getErrorCode(result);
|
|
return CompressionError(
|
|
ZSTD_getErrorString(error), ZstdStrerror(error), error);
|
|
}
|
|
|
|
result = ZSTD_CCtx_setPledgedSrcSize(cctx_.get(), pledged_src_size_);
|
|
if (ZSTD_isError(result)) {
|
|
const ZSTD_ErrorCode error = ZSTD_getErrorCode(result);
|
|
return CompressionError(
|
|
ZSTD_getErrorString(error), ZstdStrerror(error), error);
|
|
}
|
|
|
|
if (pledged_src_size_ == ZSTD_CONTENTSIZE_UNKNOWN) {
|
|
consumed_src_size_.reset();
|
|
} else {
|
|
consumed_src_size_ = 0;
|
|
}
|
|
frame_complete_ = true;
|
|
frame_output_emitted_ = false;
|
|
error_ = ZSTD_error_no_error;
|
|
error_string_.clear();
|
|
error_code_string_.clear();
|
|
return {};
|
|
}
|
|
|
|
void ZstdCompressContext::DoThreadPoolWork() {
|
|
// Zstd overrides a configured pledge when the first call uses ZSTD_e_end.
|
|
size_t const input_pos = input_.pos;
|
|
size_t const remaining =
|
|
ZSTD_compressStream2(cctx_.get(), &output_, &input_, flush_);
|
|
if (consumed_src_size_.has_value()) {
|
|
*consumed_src_size_ += input_.pos - input_pos;
|
|
}
|
|
if (output_.pos > 0) {
|
|
frame_output_emitted_ = true;
|
|
}
|
|
if (ZSTD_isError(remaining)) {
|
|
error_ = ZSTD_getErrorCode(remaining);
|
|
error_code_string_ = ZstdStrerror(error_);
|
|
error_string_ = ZSTD_getErrorString(error_);
|
|
frame_complete_ = false;
|
|
} else if (remaining == 0 && flush_ == ZSTD_e_end) {
|
|
frame_complete_ = true;
|
|
frame_output_emitted_ = false;
|
|
if (consumed_src_size_.has_value()) {
|
|
uint64_t const consumed_src_size = *consumed_src_size_;
|
|
consumed_src_size_.reset();
|
|
if (consumed_src_size != pledged_src_size_) {
|
|
error_ = ZSTD_error_srcSize_wrong;
|
|
error_code_string_ = ZstdStrerror(error_);
|
|
error_string_ = ZSTD_getErrorString(error_);
|
|
}
|
|
}
|
|
} else {
|
|
frame_complete_ = false;
|
|
}
|
|
}
|
|
|
|
CompressionError ZstdDecompressContext::SetParameter(int key, int value) {
|
|
size_t result = ZSTD_DCtx_setParameter(
|
|
dctx_.get(), static_cast<ZSTD_dParameter>(key), value);
|
|
if (ZSTD_isError(result)) {
|
|
return CompressionError(
|
|
"Setting parameter failed", "ERR_ZSTD_PARAM_SET_FAILED", -1);
|
|
}
|
|
return {};
|
|
}
|
|
|
|
void ZstdDecompressContext::Close() {
|
|
dctx_.reset();
|
|
}
|
|
|
|
CompressionError ZstdDecompressContext::Init(uint64_t pledged_src_size,
|
|
std::string_view dictionary,
|
|
bool reject_garbage_after_end) {
|
|
frame_complete_ = false;
|
|
decoding_frame_after_complete_ = false;
|
|
reject_garbage_after_end_ = reject_garbage_after_end;
|
|
ignoring_trailing_input_ = false;
|
|
frame_prefix_size_ = 0;
|
|
possible_frame_types_ = 0;
|
|
|
|
#ifdef NODE_BUNDLED_ZSTD
|
|
ZSTD_customMem custom_mem = {
|
|
CompressionStreamMemoryOwner::AllocForBrotli,
|
|
CompressionStreamMemoryOwner::FreeForZlib,
|
|
CompressionStream<
|
|
ZstdDecompressContext>::AllocatorOpaquePointerForContext(this)};
|
|
dctx_.reset(ZSTD_createDCtx_advanced(custom_mem));
|
|
#else
|
|
dctx_.reset(ZSTD_createDCtx());
|
|
#endif
|
|
if (!dctx_) {
|
|
return CompressionError("Could not initialize zstd instance",
|
|
"ERR_ZLIB_INITIALIZATION_FAILED",
|
|
-1);
|
|
}
|
|
|
|
if (!dictionary.empty()) {
|
|
size_t ret = ZSTD_DCtx_loadDictionary(
|
|
dctx_.get(), dictionary.data(), dictionary.size());
|
|
if (ZSTD_isError(ret)) {
|
|
return CompressionError("Failed to load zstd dictionary",
|
|
"ERR_ZLIB_DICTIONARY_LOAD_FAILED",
|
|
-1);
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
CompressionError ZstdDecompressContext::ResetStream() {
|
|
const size_t result = ZSTD_DCtx_reset(dctx_.get(), ZSTD_reset_session_only);
|
|
if (ZSTD_isError(result)) {
|
|
const ZSTD_ErrorCode error = ZSTD_getErrorCode(result);
|
|
return CompressionError(
|
|
ZSTD_getErrorString(error), ZstdStrerror(error), error);
|
|
}
|
|
|
|
frame_complete_ = false;
|
|
decoding_frame_after_complete_ = false;
|
|
ignoring_trailing_input_ = false;
|
|
frame_prefix_size_ = 0;
|
|
possible_frame_types_ = 0;
|
|
error_ = ZSTD_error_no_error;
|
|
error_string_.clear();
|
|
error_code_string_.clear();
|
|
return {};
|
|
}
|
|
|
|
void ZstdDecompressContext::DoThreadPoolWork() {
|
|
if (ignoring_trailing_input_) {
|
|
return;
|
|
}
|
|
|
|
// The JavaScript processing loop retries with an empty input buffer when the
|
|
// previous call filled the output buffer. Avoid interpreting that retry as
|
|
// the beginning of a new, incomplete frame.
|
|
if (frame_complete_ && input_.size == 0) {
|
|
return;
|
|
}
|
|
|
|
do {
|
|
if (frame_complete_) {
|
|
decoding_frame_after_complete_ = true;
|
|
frame_prefix_size_ = 0;
|
|
possible_frame_types_ = 0b11;
|
|
}
|
|
|
|
if (decoding_frame_after_complete_ && frame_prefix_size_ < 4) {
|
|
static constexpr uint8_t zstd_magic[] = {0x28, 0xb5, 0x2f, 0xfd};
|
|
static constexpr uint8_t skippable_magic[] = {0x50, 0x2a, 0x4d, 0x18};
|
|
const auto* data = static_cast<const uint8_t*>(input_.src);
|
|
size_t input_prefix_offset = 0;
|
|
|
|
while (frame_prefix_size_ < 4 &&
|
|
input_.pos + input_prefix_offset < input_.size) {
|
|
const size_t index = frame_prefix_size_;
|
|
const uint8_t byte = data[input_.pos + input_prefix_offset];
|
|
if (byte != zstd_magic[index]) {
|
|
possible_frame_types_ &= ~0b01;
|
|
}
|
|
if ((index == 0 && (byte & 0xf0) != skippable_magic[0]) ||
|
|
(index != 0 && byte != skippable_magic[index])) {
|
|
possible_frame_types_ &= ~0b10;
|
|
}
|
|
frame_prefix_size_++;
|
|
input_prefix_offset++;
|
|
}
|
|
|
|
if (possible_frame_types_ == 0) {
|
|
frame_complete_ = true;
|
|
decoding_frame_after_complete_ = false;
|
|
if (reject_garbage_after_end_) {
|
|
error_ = ZSTD_error_GENERIC;
|
|
error_code_string_ = "ERR_TRAILING_JUNK_AFTER_STREAM_END";
|
|
error_string_ =
|
|
"Trailing junk found after the end of the compressed stream";
|
|
} else {
|
|
ignoring_trailing_input_ = true;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
const size_t ret = ZSTD_decompressStream(dctx_.get(), &output_, &input_);
|
|
if (ZSTD_isError(ret)) {
|
|
frame_complete_ = false;
|
|
error_ = ZSTD_getErrorCode(ret);
|
|
error_code_string_ = ZstdStrerror(error_);
|
|
error_string_ = ZSTD_getErrorString(error_);
|
|
return;
|
|
}
|
|
|
|
frame_complete_ = ret == 0;
|
|
if (frame_complete_) {
|
|
decoding_frame_after_complete_ = false;
|
|
}
|
|
} while (frame_complete_ && input_.pos < input_.size &&
|
|
output_.pos < output_.size);
|
|
}
|
|
|
|
CompressionError ZstdDecompressContext::GetErrorInfo() const {
|
|
CompressionError error = ZstdContext::GetErrorInfo();
|
|
if (error.IsError()) {
|
|
return error;
|
|
}
|
|
|
|
if (flush_ == ZSTD_e_end && !frame_complete_ && input_.pos == input_.size &&
|
|
output_.pos < output_.size) {
|
|
if (decoding_frame_after_complete_) {
|
|
if (frame_prefix_size_ < 4) {
|
|
if (reject_garbage_after_end_) {
|
|
return CompressionError(
|
|
"Trailing junk found after the end of the compressed stream",
|
|
"ERR_TRAILING_JUNK_AFTER_STREAM_END",
|
|
-1);
|
|
}
|
|
return {};
|
|
}
|
|
}
|
|
return CompressionError(
|
|
"unexpected end of file", "Z_BUF_ERROR", Z_BUF_ERROR);
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
template <typename Stream>
|
|
struct MakeClass {
|
|
static void Make(Environment* env, Local<Object> target, const char* name) {
|
|
Isolate* isolate = env->isolate();
|
|
Local<FunctionTemplate> z = NewFunctionTemplate(isolate, Stream::New);
|
|
|
|
z->InstanceTemplate()->SetInternalFieldCount(
|
|
Stream::kInternalFieldCount);
|
|
z->Inherit(AsyncWrap::GetConstructorTemplate(env));
|
|
|
|
SetProtoMethod(isolate, z, "write", Stream::template Write<true>);
|
|
SetProtoMethod(isolate, z, "writeSync", Stream::template Write<false>);
|
|
SetProtoMethod(isolate, z, "close", Stream::Close);
|
|
|
|
SetProtoMethod(isolate, z, "init", Stream::Init);
|
|
SetProtoMethod(isolate, z, "params", Stream::Params);
|
|
SetProtoMethod(isolate, z, "reset", Stream::Reset);
|
|
|
|
SetConstructorFunction(env->context(), target, name, z);
|
|
}
|
|
|
|
static void Make(ExternalReferenceRegistry* registry) {
|
|
registry->Register(Stream::New);
|
|
registry->Register(Stream::template Write<true>);
|
|
registry->Register(Stream::template Write<false>);
|
|
registry->Register(Stream::Close);
|
|
registry->Register(Stream::Init);
|
|
registry->Register(Stream::Params);
|
|
registry->Register(Stream::Reset);
|
|
}
|
|
};
|
|
|
|
template <typename T, typename F>
|
|
T CallOnSequence(v8::Isolate* isolate, Local<Value> value, F callback) {
|
|
if (value->IsString()) {
|
|
Utf8Value data(isolate, value);
|
|
return callback(data.out(), data.length());
|
|
} else {
|
|
ArrayBufferViewContents<char> data(value);
|
|
return callback(data.data(), data.length());
|
|
}
|
|
}
|
|
|
|
static inline uint32_t CRC32Impl(Isolate* isolate,
|
|
Local<Value> data,
|
|
uint32_t value) {
|
|
return CallOnSequence<uint32_t>(
|
|
isolate, data, [&](const char* ptr, size_t size) -> uint32_t {
|
|
return static_cast<uint32_t>(
|
|
crc32(value, reinterpret_cast<const Bytef*>(ptr), size));
|
|
});
|
|
}
|
|
|
|
static void CRC32(const FunctionCallbackInfo<Value>& args) {
|
|
CHECK(args[0]->IsArrayBufferView() || args[0]->IsString());
|
|
CHECK(args[1]->IsUint32());
|
|
uint32_t value = args[1].As<v8::Uint32>()->Value();
|
|
args.GetReturnValue().Set(CRC32Impl(args.GetIsolate(), args[0], value));
|
|
}
|
|
|
|
static uint32_t FastCRC32(v8::Local<v8::Value> receiver,
|
|
v8::Local<v8::Value> data,
|
|
uint32_t value,
|
|
// NOLINTNEXTLINE(runtime/references)
|
|
v8::FastApiCallbackOptions& options) {
|
|
TRACK_V8_FAST_API_CALL("zlib.crc32");
|
|
v8::HandleScope handle_scope(options.isolate);
|
|
CHECK(data->IsArrayBufferView() || data->IsString());
|
|
return CRC32Impl(options.isolate, data, value);
|
|
}
|
|
|
|
static CFunction fast_crc32_(CFunction::Make(FastCRC32));
|
|
|
|
static inline uint32_t CRC32CImpl(Isolate* isolate,
|
|
Local<Value> data,
|
|
uint32_t value) {
|
|
return CallOnSequence<uint32_t>(
|
|
isolate, data, [&](const char* ptr, size_t size) -> uint32_t {
|
|
return static_cast<uint32_t>(absl::ExtendCrc32c(
|
|
absl::crc32c_t{value}, std::string_view(ptr, size)));
|
|
});
|
|
}
|
|
|
|
static void CRC32C(const FunctionCallbackInfo<Value>& args) {
|
|
CHECK(args[0]->IsArrayBufferView() || args[0]->IsString());
|
|
CHECK(args[1]->IsUint32());
|
|
uint32_t value = args[1].As<v8::Uint32>()->Value();
|
|
args.GetReturnValue().Set(CRC32CImpl(args.GetIsolate(), args[0], value));
|
|
}
|
|
|
|
static uint32_t FastCRC32C(v8::Local<v8::Value> receiver,
|
|
v8::Local<v8::Value> data,
|
|
uint32_t value,
|
|
// NOLINTNEXTLINE(runtime/references)
|
|
v8::FastApiCallbackOptions& options) {
|
|
TRACK_V8_FAST_API_CALL("zlib.crc32c");
|
|
v8::HandleScope handle_scope(options.isolate);
|
|
CHECK(data->IsArrayBufferView() || data->IsString());
|
|
return CRC32CImpl(options.isolate, data, value);
|
|
}
|
|
|
|
static CFunction fast_crc32c_(CFunction::Make(FastCRC32C));
|
|
|
|
void Initialize(Local<Object> target,
|
|
Local<Value> unused,
|
|
Local<Context> context,
|
|
void* priv) {
|
|
Environment* env = Environment::GetCurrent(context);
|
|
|
|
MakeClass<ZlibStream>::Make(env, target, "Zlib");
|
|
MakeClass<BrotliEncoderStream>::Make(env, target, "BrotliEncoder");
|
|
MakeClass<BrotliDecoderStream>::Make(env, target, "BrotliDecoder");
|
|
MakeClass<ZstdCompressStream>::Make(env, target, "ZstdCompress");
|
|
MakeClass<ZstdDecompressStream>::Make(env, target, "ZstdDecompress");
|
|
|
|
SetFastMethodNoSideEffect(context, target, "crc32", CRC32, &fast_crc32_);
|
|
SetFastMethodNoSideEffect(context, target, "crc32c", CRC32C, &fast_crc32c_);
|
|
target->Set(env->context(),
|
|
FIXED_ONE_BYTE_STRING(env->isolate(), "ZLIB_VERSION"),
|
|
FIXED_ONE_BYTE_STRING(env->isolate(), ZLIB_VERSION)).Check();
|
|
}
|
|
|
|
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
|
|
MakeClass<ZlibStream>::Make(registry);
|
|
MakeClass<BrotliEncoderStream>::Make(registry);
|
|
MakeClass<BrotliDecoderStream>::Make(registry);
|
|
MakeClass<ZstdCompressStream>::Make(registry);
|
|
MakeClass<ZstdDecompressStream>::Make(registry);
|
|
registry->Register(CRC32);
|
|
registry->Register(fast_crc32_);
|
|
registry->Register(CRC32C);
|
|
registry->Register(fast_crc32c_);
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
void DefineZlibConstants(Local<Object> target) {
|
|
NODE_DEFINE_CONSTANT(target, Z_NO_FLUSH);
|
|
NODE_DEFINE_CONSTANT(target, Z_PARTIAL_FLUSH);
|
|
NODE_DEFINE_CONSTANT(target, Z_SYNC_FLUSH);
|
|
NODE_DEFINE_CONSTANT(target, Z_FULL_FLUSH);
|
|
NODE_DEFINE_CONSTANT(target, Z_FINISH);
|
|
NODE_DEFINE_CONSTANT(target, Z_BLOCK);
|
|
|
|
// return/error codes
|
|
NODE_DEFINE_CONSTANT(target, Z_OK);
|
|
NODE_DEFINE_CONSTANT(target, Z_STREAM_END);
|
|
NODE_DEFINE_CONSTANT(target, Z_NEED_DICT);
|
|
NODE_DEFINE_CONSTANT(target, Z_ERRNO);
|
|
NODE_DEFINE_CONSTANT(target, Z_STREAM_ERROR);
|
|
NODE_DEFINE_CONSTANT(target, Z_DATA_ERROR);
|
|
NODE_DEFINE_CONSTANT(target, Z_MEM_ERROR);
|
|
NODE_DEFINE_CONSTANT(target, Z_BUF_ERROR);
|
|
NODE_DEFINE_CONSTANT(target, Z_VERSION_ERROR);
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_NO_COMPRESSION);
|
|
NODE_DEFINE_CONSTANT(target, Z_BEST_SPEED);
|
|
NODE_DEFINE_CONSTANT(target, Z_BEST_COMPRESSION);
|
|
NODE_DEFINE_CONSTANT(target, Z_DEFAULT_COMPRESSION);
|
|
NODE_DEFINE_CONSTANT(target, Z_FILTERED);
|
|
NODE_DEFINE_CONSTANT(target, Z_HUFFMAN_ONLY);
|
|
NODE_DEFINE_CONSTANT(target, Z_RLE);
|
|
NODE_DEFINE_CONSTANT(target, Z_FIXED);
|
|
NODE_DEFINE_CONSTANT(target, Z_DEFAULT_STRATEGY);
|
|
NODE_DEFINE_CONSTANT(target, ZLIB_VERNUM);
|
|
|
|
NODE_DEFINE_CONSTANT(target, DEFLATE);
|
|
NODE_DEFINE_CONSTANT(target, INFLATE);
|
|
NODE_DEFINE_CONSTANT(target, GZIP);
|
|
NODE_DEFINE_CONSTANT(target, GUNZIP);
|
|
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, ZSTD_DECOMPRESS);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_COMPRESS);
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_MIN_WINDOWBITS);
|
|
NODE_DEFINE_CONSTANT(target, Z_MAX_WINDOWBITS);
|
|
NODE_DEFINE_CONSTANT(target, Z_DEFAULT_WINDOWBITS);
|
|
NODE_DEFINE_CONSTANT(target, Z_MIN_CHUNK);
|
|
NODE_DEFINE_CONSTANT(target, Z_MAX_CHUNK);
|
|
NODE_DEFINE_CONSTANT(target, Z_DEFAULT_CHUNK);
|
|
NODE_DEFINE_CONSTANT(target, Z_MIN_MEMLEVEL);
|
|
NODE_DEFINE_CONSTANT(target, Z_MAX_MEMLEVEL);
|
|
NODE_DEFINE_CONSTANT(target, Z_DEFAULT_MEMLEVEL);
|
|
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_FORMAT_BLOCK_SWITCH);
|
|
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);
|
|
|
|
// Zstd constants
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_e_continue);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_e_flush);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_e_end);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_fast);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_dfast);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_greedy);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_lazy);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_lazy2);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_btlazy2);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_btopt);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_btultra);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_btultra2);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_c_compressionLevel);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_c_windowLog);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_c_hashLog);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_c_chainLog);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_c_searchLog);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_c_minMatch);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_c_targetLength);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_c_strategy);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_c_enableLongDistanceMatching);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_c_ldmHashLog);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_c_ldmMinMatch);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_c_ldmBucketSizeLog);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_c_ldmHashRateLog);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_c_contentSizeFlag);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_c_checksumFlag);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_c_dictIDFlag);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_c_nbWorkers);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_c_jobSize);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_c_overlapLog);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_d_windowLogMax);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_CLEVEL_DEFAULT);
|
|
// Error codes
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_no_error);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_GENERIC);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_prefix_unknown);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_version_unsupported);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_frameParameter_unsupported);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_frameParameter_windowTooLarge);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_corruption_detected);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_checksum_wrong);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_literals_headerWrong);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_dictionary_corrupted);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_dictionary_wrong);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_dictionaryCreation_failed);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_parameter_unsupported);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_parameter_combination_unsupported);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_parameter_outOfBound);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_tableLog_tooLarge);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_maxSymbolValue_tooLarge);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_maxSymbolValue_tooSmall);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_stabilityCondition_notRespected);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_stage_wrong);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_init_missing);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_memory_allocation);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_workSpace_tooSmall);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_dstSize_tooSmall);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_srcSize_wrong);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_dstBuffer_null);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_noForwardProgress_destFull);
|
|
NODE_DEFINE_CONSTANT(target, ZSTD_error_noForwardProgress_inputEmpty);
|
|
}
|
|
|
|
} // namespace node
|
|
|
|
NODE_BINDING_CONTEXT_AWARE_INTERNAL(zlib, node::Initialize)
|
|
NODE_BINDING_EXTERNAL_REFERENCE(zlib, node::RegisterExternalReferences)
|