mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
ResetStream currently calls Init() with no arguments, which drops the stored dictionary and never replays SetParams. Remember each successful parameter and replay both the dictionary and the params when the stream is reset. Fixes: https://github.com/nodejs/node/issues/66156 Signed-off-by: Xia Chao <shapirolutts@gmail.com> PR-URL: https://github.com/nodejs/node/pull/66157 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Filip Skokan <panva.ip@gmail.com>
2350 lines
78 KiB
C++
2350 lines
78 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 "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));
|
|
|
|
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_);
|
|
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_);
|
|
}
|
|
|
|
} // 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_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)
|