#include "brotli.h" #include #include #include #include namespace { struct TAllocator { static void* Allocate(void* /* opaque */, size_t size) { return ::operator new(size); } static void Deallocate(void* /* opaque */, void* ptr) noexcept { ::operator delete(ptr); } }; } class TBrotliCompress::TImpl { public: TImpl(IOutputStream* slave, int quality) : Slave_(slave) , EncoderState_(BrotliEncoderCreateInstance(&TAllocator::Allocate, &TAllocator::Deallocate, nullptr)) { if (!EncoderState_) { ythrow yexception() << "Brotli encoder initialization failed"; } auto res = BrotliEncoderSetParameter( EncoderState_, BROTLI_PARAM_QUALITY, quality); if (!res) { BrotliEncoderDestroyInstance(EncoderState_); ythrow yexception() << "Failed to set brotli encoder quality to " << quality; } } ~TImpl() { BrotliEncoderDestroyInstance(EncoderState_); } void Write(const void* buffer, size_t size) { DoWrite(buffer, size, BROTLI_OPERATION_PROCESS); } void Flush() { DoWrite(nullptr, 0, BROTLI_OPERATION_FLUSH); } void Finish() { Flush(); DoWrite(nullptr, 0, BROTLI_OPERATION_FINISH); Y_ABORT_UNLESS(BrotliEncoderIsFinished(EncoderState_)); } private: IOutputStream* Slave_; BrotliEncoderState* EncoderState_; void DoWrite(const void* buffer, size_t size, BrotliEncoderOperation operation) { size_t availableOut = 0; ui8* outputBuffer = nullptr; const ui8* uBuffer = static_cast(buffer); do { auto result = BrotliEncoderCompressStream( EncoderState_, operation, &size, &uBuffer, &availableOut, &outputBuffer, nullptr); if (result == BROTLI_FALSE) { ythrow yexception() << "Brotli encoder failed to process buffer"; } size_t outputLength = 0; const ui8* output = BrotliEncoderTakeOutput(EncoderState_, &outputLength); if (outputLength > 0) { Slave_->Write(output, outputLength); } } while (size > 0 || BrotliEncoderHasMoreOutput(EncoderState_)); } }; TBrotliCompress::TBrotliCompress(IOutputStream* slave, int quality) { Impl_.Reset(new TImpl(slave, quality)); } TBrotliCompress::~TBrotliCompress() { try { Finish(); } catch (...) { } } void TBrotliCompress::DoWrite(const void* buffer, size_t size) { Impl_->Write(buffer, size); } void TBrotliCompress::DoFlush() { if (Impl_) { Impl_->Flush(); } } void TBrotliCompress::DoFinish() { THolder impl(Impl_.Release()); if (impl) { impl->Finish(); } } //////////////////////////////////////////////////////////////////////////////// class TBrotliDecompress::TImpl: public TAdditionalStorage { public: TImpl(IInputStream* slave) : Slave_(slave) { InitDecoder(); } ~TImpl() { FreeDecoder(); } size_t Read(void* buffer, size_t size) { Y_ASSERT(size > 0); ui8* outBuffer = static_cast(buffer); size_t availableOut = size; size_t decompressedSize = 0; BrotliDecoderResult result; do { if (InputAvailable_ == 0 && !InputExhausted_) { InputBuffer_ = TmpBuf(); InputAvailable_ = Slave_->Read((void*)InputBuffer_, TmpBufLen()); if (InputAvailable_ == 0) { InputExhausted_ = true; } } if (SubstreamFinished_ && !InputExhausted_) { ResetState(); } result = BrotliDecoderDecompressStream( DecoderState_, &InputAvailable_, &InputBuffer_, &availableOut, &outBuffer, nullptr); decompressedSize = size - availableOut; SubstreamFinished_ = (result == BROTLI_DECODER_RESULT_SUCCESS); if (result == BROTLI_DECODER_RESULT_ERROR) { BrotliDecoderErrorCode code = BrotliDecoderGetErrorCode(DecoderState_); ythrow yexception() << "Brotli decoder failed to decompress buffer: " << BrotliDecoderErrorString(code); } else if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { Y_ABORT_UNLESS(availableOut != size, "Buffer passed to read in Brotli decoder is too small"); break; } } while (decompressedSize == 0 && result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT && !InputExhausted_); if (!SubstreamFinished_ && decompressedSize == 0) { ythrow yexception() << "Input stream is incomplete"; } return decompressedSize; } private: IInputStream* Slave_; BrotliDecoderState* DecoderState_; bool SubstreamFinished_ = false; bool InputExhausted_ = false; const ui8* InputBuffer_ = nullptr; size_t InputAvailable_ = 0; unsigned char* TmpBuf() noexcept { return static_cast(AdditionalData()); } size_t TmpBufLen() const noexcept { return AdditionalDataLength(); } void InitDecoder() { DecoderState_ = BrotliDecoderCreateInstance(&TAllocator::Allocate, &TAllocator::Deallocate, nullptr); if (!DecoderState_) { ythrow yexception() << "Brotli decoder initialization failed"; } } void FreeDecoder() { BrotliDecoderDestroyInstance(DecoderState_); } void ResetState() { Y_ABORT_UNLESS(BrotliDecoderIsFinished(DecoderState_)); FreeDecoder(); InitDecoder(); } }; TBrotliDecompress::TBrotliDecompress(IInputStream* slave, size_t bufferSize) : Impl_(new (bufferSize) TImpl(slave)) { } TBrotliDecompress::~TBrotliDecompress() = default; size_t TBrotliDecompress::DoRead(void* buffer, size_t size) { return Impl_->Read(buffer, size); }