#include "liblzma-node.hpp" #include #include #include #include namespace lzma { #ifdef LZMA_ASYNC_AVAILABLE const bool LZMAStream::asyncCodeAvailable = true; #else const bool LZMAStream::asyncCodeAvailable = false; #endif namespace { extern "C" void* LZMA_API_CALL alloc_for_lzma(void *opaque, size_t nmemb, size_t size) { LZMAStream* strm = static_cast(opaque); return strm->alloc(nmemb, size); } extern "C" void LZMA_API_CALL free_for_lzma(void *opaque, void *ptr) { LZMAStream* strm = static_cast(opaque); return strm->free(ptr); } } Nan::Persistent LZMAStream::constructor; LZMAStream::LZMAStream() : bufsize(65536), shouldFinish(false), processedChunks(0), lastCodeResult(LZMA_OK) { std::memset(&_, 0, sizeof(lzma_stream)); allocator.alloc = alloc_for_lzma; allocator.free = free_for_lzma; allocator.opaque = static_cast(this); _.allocator = &allocator; #ifdef LZMA_ASYNC_AVAILABLE uv_mutex_init(&mutex); nonAdjustedExternalMemory = 0; #endif } void LZMAStream::resetUnderlying() { if (_.internal) lzma_end(&_); reportAdjustedExternalMemoryToV8(); std::memset(&_, 0, sizeof(lzma_stream)); _.allocator = &allocator; lastCodeResult = LZMA_OK; processedChunks = 0; } LZMAStream::~LZMAStream() { resetUnderlying(); #ifdef LZMA_ASYNC_AVAILABLE uv_mutex_destroy(&mutex); #endif Nan::AdjustExternalMemory(-int64_t(sizeof(LZMAStream))); } void* LZMAStream::alloc(size_t nmemb, size_t size) { size_t nBytes = nmemb * size + sizeof(size_t); size_t* result = static_cast(::malloc(nBytes)); if (!result) return result; *result = nBytes; adjustExternalMemory(static_cast(nBytes)); return static_cast(result + 1); } void LZMAStream::free(void* ptr) { if (!ptr) return; size_t* orig = static_cast(ptr) - 1; adjustExternalMemory(-static_cast(*orig)); return ::free(static_cast(orig)); } void LZMAStream::reportAdjustedExternalMemoryToV8() { #ifdef LZMA_ASYNC_AVAILABLE if (nonAdjustedExternalMemory == 0) return; Nan::AdjustExternalMemory(nonAdjustedExternalMemory); nonAdjustedExternalMemory = 0; #endif } void LZMAStream::adjustExternalMemory(int64_t bytesChange) { #ifdef LZMA_ASYNC_AVAILABLE nonAdjustedExternalMemory += bytesChange; #else Nan::AdjustExternalMemory(bytesChange); #endif } #define LZMA_FETCH_SELF() \ LZMAStream* self = NULL; \ if (!info.This().IsEmpty() && info.This()->InternalFieldCount() > 0) { \ self = Nan::ObjectWrap::Unwrap(info.This()); \ } \ if (!self) { \ _failMissingSelf(info); \ return; \ } \ struct _MemScopeGuard { \ _MemScopeGuard(LZMAStream* self_) : self(self_) {} \ ~_MemScopeGuard() { \ self->reportAdjustedExternalMemoryToV8(); \ } \ \ LZMAStream* self; \ }; \ _MemScopeGuard guard(self); NAN_METHOD(LZMAStream::ResetUnderlying) { LZMA_FETCH_SELF(); LZMA_ASYNC_LOCK(self); self->resetUnderlying(); info.GetReturnValue().SetUndefined(); } NAN_METHOD(LZMAStream::SetBufsize) { size_t oldBufsize, newBufsize = NumberToUint64ClampNullMax(info[0]); { LZMA_FETCH_SELF(); LZMA_ASYNC_LOCK(self); oldBufsize = self->bufsize; if (newBufsize && newBufsize != UINT_MAX) self->bufsize = newBufsize; } info.GetReturnValue().Set(double(oldBufsize)); } NAN_METHOD(LZMAStream::Code) { LZMA_FETCH_SELF(); LZMA_ASYNC_LOCK(self); self->reportAdjustedExternalMemoryToV8(); std::vector inputData; Local bufarg = Local::Cast(info[0]); if (bufarg.IsEmpty() || bufarg->IsUndefined() || bufarg->IsNull()) { self->shouldFinish = true; } else { if (!readBufferFromObj(bufarg, inputData)) { info.GetReturnValue().SetUndefined(); return; } if (inputData.empty()) self->shouldFinish = true; } self->inbufs.push(LZMA_NATIVE_MOVE(inputData)); bool async = info[1]->BooleanValue(); if (async) { #ifdef LZMA_ASYNC_AVAILABLE Nan::AsyncQueueWorker(new LZMAStreamCodingWorker(self)); #else std::abort(); #endif } else { self->doLZMACode(); self->invokeBufferHandlers(true); } info.GetReturnValue().SetUndefined(); } void LZMAStream::invokeBufferHandlers(bool hasLock) { #ifdef LZMA_ASYNC_AVAILABLE uv_mutex_guard lock(mutex, !hasLock); #define POSSIBLY_LOCK_MX do { if (!hasLock) lock.lock(); } while(0) #define POSSIBLY_UNLOCK_MX do { if (!hasLock) lock.unlock(); } while(0) #else #define POSSIBLY_LOCK_MX #define POSSIBLY_UNLOCK_MX #endif Nan::HandleScope scope; reportAdjustedExternalMemoryToV8(); Local bufferHandler = Local::Cast(EmptyToUndefined(Nan::Get(handle(), NewString("bufferHandler")))); std::vector outbuf; #define CALL_BUFFER_HANDLER_WITH_ARGV \ POSSIBLY_UNLOCK_MX; \ Nan::MakeCallback(handle(), bufferHandler, 5, argv); \ POSSIBLY_LOCK_MX; uint64_t in = UINT64_MAX, out = UINT64_MAX; if (_.internal) lzma_get_progress(&_, &in, &out); Local in_ = Uint64ToNumberMaxNull(in); Local out_ = Uint64ToNumberMaxNull(out); while (outbufs.size() > 0) { outbuf = LZMA_NATIVE_MOVE(outbufs.front()); outbufs.pop(); Local argv[5] = { Nan::CopyBuffer(reinterpret_cast(outbuf.data()), outbuf.size()).ToLocalChecked(), Nan::Undefined(), Nan::Undefined(), in_, out_ }; CALL_BUFFER_HANDLER_WITH_ARGV } bool reset = false; if (lastCodeResult != LZMA_OK) { Local errorArg = Local(Nan::Null()); if (lastCodeResult != LZMA_STREAM_END) errorArg = lzmaRetError(lastCodeResult); reset = true; Local argv[5] = { Nan::Null(), Nan::Undefined(), errorArg, in_, out_ }; CALL_BUFFER_HANDLER_WITH_ARGV } if (processedChunks) { size_t pc = processedChunks; processedChunks = 0; Local argv[5] = { Nan::Undefined(), Nan::New(uint32_t(pc)), Nan::Undefined(), in_, out_ }; CALL_BUFFER_HANDLER_WITH_ARGV } if (reset) resetUnderlying(); // resets lastCodeResult! } void LZMAStream::doLZMACodeFromAsync() { LZMA_ASYNC_LOCK(this); doLZMACode(); } void LZMAStream::doLZMACode() { std::vector outbuf(bufsize), inbuf; _.next_out = outbuf.data(); _.avail_out = outbuf.size(); _.avail_in = 0; lzma_action action = LZMA_RUN; size_t readChunks = 0; // _.internal is set to NULL when lzma_end() is called via resetUnderlying() while (_.internal) { if (_.avail_in == 0) { // more input neccessary? while (_.avail_in == 0 && !inbufs.empty()) { inbuf = LZMA_NATIVE_MOVE(inbufs.front()); inbufs.pop(); readChunks++; _.next_in = inbuf.data(); _.avail_in = inbuf.size(); } } if (shouldFinish && inbufs.empty()) action = LZMA_FINISH; _.next_out = outbuf.data(); _.avail_out = outbuf.size(); lastCodeResult = lzma_code(&_, action); if (lastCodeResult != LZMA_OK && lastCodeResult != LZMA_STREAM_END) { processedChunks += readChunks; readChunks = 0; break; } if (_.avail_out == 0 || _.avail_in == 0 || lastCodeResult == LZMA_STREAM_END) { size_t outsz = outbuf.size() - _.avail_out; if (outsz > 0) { #ifndef LZMA_NO_CXX11_RVALUE_REFERENCES // C++11 outbufs.emplace(outbuf.data(), outbuf.data() + outsz); #else outbufs.push(std::vector(outbuf.data(), outbuf.data() + outsz)); #endif } if (lastCodeResult == LZMA_STREAM_END) { processedChunks += readChunks; readChunks = 0; break; } } if (_.avail_out == outbuf.size()) { // no progress was made if (!shouldFinish) { processedChunks += readChunks; readChunks = 0; } if (!shouldFinish) break; } } } void LZMAStream::Init(Local exports) { Local tpl = Nan::New(New); tpl->SetClassName(NewString("LZMAStream")); tpl->InstanceTemplate()->SetInternalFieldCount(1); Nan::SetPrototypeMethod(tpl, "setBufsize", SetBufsize); Nan::SetPrototypeMethod(tpl, "resetUnderlying", ResetUnderlying); Nan::SetPrototypeMethod(tpl, "code", Code); Nan::SetPrototypeMethod(tpl, "memusage", Memusage); Nan::SetPrototypeMethod(tpl, "memlimitGet", MemlimitGet); Nan::SetPrototypeMethod(tpl, "memlimitSet", MemlimitSet); Nan::SetPrototypeMethod(tpl, "rawEncoder_", RawEncoder); Nan::SetPrototypeMethod(tpl, "rawDecoder_", RawDecoder); Nan::SetPrototypeMethod(tpl, "filtersUpdate", FiltersUpdate); Nan::SetPrototypeMethod(tpl, "easyEncoder_", EasyEncoder); Nan::SetPrototypeMethod(tpl, "streamEncoder_", StreamEncoder); Nan::SetPrototypeMethod(tpl, "aloneEncoder", AloneEncoder); Nan::SetPrototypeMethod(tpl, "mtEncoder_", MTEncoder); Nan::SetPrototypeMethod(tpl, "streamDecoder_", StreamDecoder); Nan::SetPrototypeMethod(tpl, "autoDecoder_", AutoDecoder); Nan::SetPrototypeMethod(tpl, "aloneDecoder_", AloneDecoder); constructor.Reset(Nan::GetFunction(tpl).ToLocalChecked()); exports->Set(NewString("Stream"), Nan::New(constructor)); } NAN_METHOD(LZMAStream::New) { if (info.IsConstructCall()) { LZMAStream* self = new LZMAStream(); if (!self) { Nan::ThrowRangeError("Out of memory, cannot create LZMAStream"); info.GetReturnValue().SetUndefined(); return; } self->Wrap(info.This()); Nan::AdjustExternalMemory(sizeof(LZMAStream)); info.GetReturnValue().Set(info.This()); } else { info.GetReturnValue().Set(Nan::NewInstance(Nan::New(constructor), 0, NULL).ToLocalChecked()); } } void LZMAStream::_failMissingSelf(const Nan::FunctionCallbackInfo& info) { Nan::ThrowTypeError("LZMAStream methods need to be called on an LZMAStream object"); info.GetReturnValue().SetUndefined(); } NAN_METHOD(LZMAStream::Memusage) { LZMA_FETCH_SELF(); LZMA_ASYNC_LOCK(self); info.GetReturnValue().Set(Uint64ToNumber0Null(lzma_memusage(&self->_))); } NAN_METHOD(LZMAStream::MemlimitGet) { LZMA_FETCH_SELF(); LZMA_ASYNC_LOCK(self); info.GetReturnValue().Set(Uint64ToNumber0Null(lzma_memlimit_get(&self->_))); } NAN_METHOD(LZMAStream::MemlimitSet) { LZMA_FETCH_SELF(); LZMA_ASYNC_LOCK(self); Local arg = Local::Cast(info[0]); if (info[0]->IsUndefined() || arg.IsEmpty()) { Nan::ThrowTypeError("memlimitSet() needs a numerical argument"); info.GetReturnValue().SetUndefined(); return; } info.GetReturnValue().Set(lzmaRet(lzma_memlimit_set(&self->_, NumberToUint64ClampNullMax(arg)))); } NAN_METHOD(LZMAStream::RawEncoder) { LZMA_FETCH_SELF(); LZMA_ASYNC_LOCK(self); const FilterArray filters(Local::Cast(info[0])); info.GetReturnValue().Set(lzmaRet(lzma_raw_encoder(&self->_, filters.array()))); } NAN_METHOD(LZMAStream::RawDecoder) { LZMA_FETCH_SELF(); LZMA_ASYNC_LOCK(self); const FilterArray filters(Local::Cast(info[0])); info.GetReturnValue().Set(lzmaRet(lzma_raw_decoder(&self->_, filters.array()))); } NAN_METHOD(LZMAStream::FiltersUpdate) { LZMA_FETCH_SELF(); LZMA_ASYNC_LOCK(self); const FilterArray filters(Local::Cast(info[0])); info.GetReturnValue().Set(lzmaRet(lzma_filters_update(&self->_, filters.array()))); } NAN_METHOD(LZMAStream::EasyEncoder) { LZMA_FETCH_SELF(); LZMA_ASYNC_LOCK(self); Local preset = Local::Cast(info[0]); Local check = Local::Cast(info[1]); info.GetReturnValue().Set(lzmaRet(lzma_easy_encoder(&self->_, preset->Value(), (lzma_check) check->Value()))); } NAN_METHOD(LZMAStream::StreamEncoder) { LZMA_FETCH_SELF(); LZMA_ASYNC_LOCK(self); const FilterArray filters(Local::Cast(info[0])); Local check = Local::Cast(info[1]); if (!filters.ok()) return; info.GetReturnValue().Set(lzmaRet(lzma_stream_encoder(&self->_, filters.array(), (lzma_check) check->Value()))); } NAN_METHOD(LZMAStream::MTEncoder) { LZMA_FETCH_SELF(); LZMA_ASYNC_LOCK(self); const MTOptions mt(Local::Cast(info[0])); if (!mt.ok()) return; info.GetReturnValue().Set(lzmaRet(lzma_stream_encoder_mt(&self->_, mt.opts()))); } NAN_METHOD(LZMAStream::AloneEncoder) { LZMA_FETCH_SELF(); LZMA_ASYNC_LOCK(self); Local opt = Local::Cast(info[0]); lzma_options_lzma o = parseOptionsLZMA(opt); info.GetReturnValue().Set(lzmaRet(lzma_alone_encoder(&self->_, &o))); } NAN_METHOD(LZMAStream::StreamDecoder) { LZMA_FETCH_SELF(); LZMA_ASYNC_LOCK(self); uint64_t memlimit = NumberToUint64ClampNullMax(info[0]); Local flags = Local::Cast(info[1]); info.GetReturnValue().Set(lzmaRet(lzma_stream_decoder(&self->_, memlimit, flags->Value()))); } NAN_METHOD(LZMAStream::AutoDecoder) { LZMA_FETCH_SELF(); LZMA_ASYNC_LOCK(self); uint64_t memlimit = NumberToUint64ClampNullMax(info[0]); Local flags = Local::Cast(info[1]); info.GetReturnValue().Set(lzmaRet(lzma_auto_decoder(&self->_, memlimit, flags->Value()))); } NAN_METHOD(LZMAStream::AloneDecoder) { LZMA_FETCH_SELF(); LZMA_ASYNC_LOCK(self); uint64_t memlimit = NumberToUint64ClampNullMax(info[0]); info.GetReturnValue().Set(lzmaRet(lzma_alone_decoder(&self->_, memlimit))); } }