LibCompress: Introduce generic de/compressor using zlib

This compressor decompressor is capable of handling zlib, gzip and
deflate all in one.
This commit is contained in:
devgianlu 2025-03-01 17:33:52 +01:00 committed by Jelle Raaijmakers
parent 1c836588d9
commit c4b76bea13
Notes: github-actions[bot] 2025-03-19 12:48:58 +00:00
3 changed files with 322 additions and 1 deletions

View file

@ -1,8 +1,9 @@
set(SOURCES
Deflate.cpp
GenericZlib.cpp
Gzip.cpp
PackBitsDecoder.cpp
Zlib.cpp
Gzip.cpp
)
serenity_lib(LibCompress compress)

View file

@ -0,0 +1,219 @@
/*
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCompress/GenericZlib.h>
#include <zlib.h>
namespace Compress {
static Error handle_zlib_error(int ret)
{
switch (ret) {
case Z_ERRNO:
return Error::from_errno(errno);
case Z_DATA_ERROR:
// Z_DATA_ERROR if the input data was corrupted
return Error::from_string_literal("zlib data error");
case Z_STREAM_ERROR:
// Z_STREAM_ERROR if the parameters are invalid, such as a null pointer to the structure
return Error::from_string_literal("zlib stream error");
case Z_VERSION_ERROR:
// Z_VERSION_ERROR if the zlib library version is incompatible with the version assumed by the caller
return Error::from_string_literal("zlib version mismatch");
case Z_MEM_ERROR:
// Z_MEM_ERROR if there was not enough memory
return Error::from_errno(ENOMEM);
default:
VERIFY_NOT_REACHED();
}
}
GenericZlibDecompressor::GenericZlibDecompressor(AK::FixedArray<u8> buffer, MaybeOwned<Stream> stream, z_stream* zstream)
: m_stream(move(stream))
, m_zstream(zstream)
, m_buffer(move(buffer))
{
}
ErrorOr<z_stream*> GenericZlibDecompressor::new_z_stream(int window_bits)
{
auto zstream = new (nothrow) z_stream {};
if (!zstream)
return Error::from_errno(ENOMEM);
// The fields next_in, avail_in, zalloc, zfree and opaque must be initialized before by the caller.
zstream->next_in = nullptr;
zstream->avail_in = 0;
zstream->zalloc = nullptr;
zstream->zfree = nullptr;
zstream->opaque = nullptr;
if (auto ret = inflateInit2(zstream, window_bits); ret != Z_OK)
return handle_zlib_error(ret);
return zstream;
}
GenericZlibDecompressor::~GenericZlibDecompressor()
{
inflateEnd(m_zstream);
delete m_zstream;
}
ErrorOr<Bytes> GenericZlibDecompressor::read_some(Bytes bytes)
{
m_zstream->avail_out = bytes.size();
m_zstream->next_out = bytes.data();
if (m_zstream->avail_in == 0) {
auto in = TRY(m_stream->read_some(m_buffer.span()));
m_zstream->avail_in = in.size();
m_zstream->next_in = m_buffer.data();
}
auto ret = inflate(m_zstream, Z_NO_FLUSH);
if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR)
return handle_zlib_error(ret);
if (ret == Z_STREAM_END) {
inflateReset(m_zstream);
if (m_zstream->avail_in == 0)
m_eof = true;
}
return bytes.slice(0, bytes.size() - m_zstream->avail_out);
}
ErrorOr<size_t> GenericZlibDecompressor::write_some(ReadonlyBytes)
{
return Error::from_errno(EBADF);
}
bool GenericZlibDecompressor::is_eof() const
{
return m_eof;
}
bool GenericZlibDecompressor::is_open() const
{
return m_stream->is_open();
}
void GenericZlibDecompressor::close()
{
}
GenericZlibCompressor::GenericZlibCompressor(AK::FixedArray<u8> buffer, MaybeOwned<Stream> stream, z_stream* zstream)
: m_stream(move(stream))
, m_zstream(zstream)
, m_buffer(move(buffer))
{
}
ErrorOr<z_stream*> GenericZlibCompressor::new_z_stream(int window_bits, GenericZlibCompressionLevel compression_level)
{
auto zstream = new (nothrow) z_stream {};
if (!zstream)
return Error::from_errno(ENOMEM);
// The fields zalloc, zfree and opaque must be initialized before by the caller.
zstream->zalloc = nullptr;
zstream->zfree = nullptr;
zstream->opaque = nullptr;
int level = [&] {
switch (compression_level) {
case GenericZlibCompressionLevel::Fastest:
return Z_BEST_SPEED;
case GenericZlibCompressionLevel::Default:
return Z_DEFAULT_COMPRESSION;
case GenericZlibCompressionLevel::Best:
return Z_BEST_COMPRESSION;
default:
VERIFY_NOT_REACHED();
}
}();
if (auto ret = deflateInit2(zstream, level, Z_DEFLATED, window_bits, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); ret != Z_OK)
return handle_zlib_error(ret);
return zstream;
}
GenericZlibCompressor::~GenericZlibCompressor()
{
deflateEnd(m_zstream);
delete m_zstream;
}
ErrorOr<Bytes> GenericZlibCompressor::read_some(Bytes)
{
return Error::from_errno(EBADF);
}
ErrorOr<size_t> GenericZlibCompressor::write_some(ReadonlyBytes bytes)
{
m_zstream->avail_in = bytes.size();
m_zstream->next_in = const_cast<u8*>(bytes.data());
// If deflate returns with avail_out == 0, this function must be called again with the same value of the flush parameter
// and more output space (updated avail_out), until the flush is complete (deflate returns with non-zero avail_out).
do {
m_zstream->avail_out = m_buffer.size();
m_zstream->next_out = m_buffer.data();
auto ret = deflate(m_zstream, Z_NO_FLUSH);
if (ret != Z_OK && ret != Z_BUF_ERROR)
return handle_zlib_error(ret);
auto have = m_buffer.size() - m_zstream->avail_out;
TRY(m_stream->write_until_depleted(m_buffer.span().slice(0, have)));
} while (m_zstream->avail_out == 0);
VERIFY(m_zstream->avail_in == 0);
return bytes.size();
}
bool GenericZlibCompressor::is_eof() const
{
return false;
}
bool GenericZlibCompressor::is_open() const
{
return m_stream->is_open();
}
void GenericZlibCompressor::close()
{
}
ErrorOr<void> GenericZlibCompressor::finish()
{
VERIFY(m_zstream->avail_in == 0);
// If the parameter flush is set to Z_FINISH, pending input is processed, pending output is flushed and deflate returns with Z_STREAM_END
// if there was enough output space. If deflate returns with Z_OK or Z_BUF_ERROR, this function must be called again with Z_FINISH
// and more output space (updated avail_out) but no more input data, until it returns with Z_STREAM_END or an error.
while (true) {
m_zstream->avail_out = m_buffer.size();
m_zstream->next_out = m_buffer.data();
auto ret = deflate(m_zstream, Z_FINISH);
if (ret == Z_STREAM_END || ret == Z_BUF_ERROR || ret == Z_OK) {
auto have = m_buffer.size() - m_zstream->avail_out;
TRY(m_stream->write_until_depleted(m_buffer.span().slice(0, have)));
if (ret == Z_STREAM_END)
return {};
} else {
return handle_zlib_error(ret);
}
}
}
}

View file

@ -0,0 +1,101 @@
/*
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteBuffer.h>
#include <AK/FixedArray.h>
#include <AK/MaybeOwned.h>
#include <AK/MemoryStream.h>
#include <AK/Stream.h>
extern "C" {
typedef struct z_stream_s z_stream;
}
namespace Compress {
enum class GenericZlibCompressionLevel : u8 {
Fastest,
Default,
Best,
};
class GenericZlibDecompressor : public Stream {
AK_MAKE_NONCOPYABLE(GenericZlibDecompressor);
public:
~GenericZlibDecompressor() override;
virtual ErrorOr<Bytes> read_some(Bytes) override;
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
virtual bool is_eof() const override;
virtual bool is_open() const override;
virtual void close() override;
protected:
GenericZlibDecompressor(AK::FixedArray<u8>, MaybeOwned<Stream>, z_stream*);
static ErrorOr<z_stream*> new_z_stream(int window_bits);
private:
MaybeOwned<Stream> m_stream;
z_stream* m_zstream;
bool m_eof { false };
AK::FixedArray<u8> m_buffer;
};
class GenericZlibCompressor : public Stream {
AK_MAKE_NONCOPYABLE(GenericZlibCompressor);
public:
~GenericZlibCompressor() override;
virtual ErrorOr<Bytes> read_some(Bytes) override;
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
virtual bool is_eof() const override;
virtual bool is_open() const override;
virtual void close() override;
ErrorOr<void> finish();
protected:
GenericZlibCompressor(AK::FixedArray<u8>, MaybeOwned<Stream>, z_stream*);
static ErrorOr<z_stream*> new_z_stream(int window_bits, GenericZlibCompressionLevel compression_level);
private:
MaybeOwned<Stream> m_stream;
z_stream* m_zstream;
AK::FixedArray<u8> m_buffer;
};
template<class T>
ErrorOr<ByteBuffer> decompress_all(ReadonlyBytes bytes)
{
auto input_stream = make<AK::FixedMemoryStream>(bytes);
auto deflate_stream = TRY(T::create(MaybeOwned<Stream>(move(input_stream))));
return TRY(deflate_stream->read_until_eof(4096));
}
template<class T>
ErrorOr<ByteBuffer> compress_all(ReadonlyBytes bytes, GenericZlibCompressionLevel compression_level)
{
auto output_stream = TRY(try_make<AllocatingMemoryStream>());
auto gzip_stream = TRY(T::create(MaybeOwned { *output_stream }, compression_level));
TRY(gzip_stream->write_until_depleted(bytes));
TRY(gzip_stream->finish());
auto buffer = TRY(ByteBuffer::create_uninitialized(output_stream->used_buffer_size()));
TRY(output_stream->read_until_filled(buffer.bytes()));
return buffer;
}
}