/*1* Copyright © 2021 Valve Corporation2*3* Permission is hereby granted, free of charge, to any person obtaining a4* copy of this software and associated documentation files (the "Software"),5* to deal in the Software without restriction, including without limitation6* the rights to use, copy, modify, merge, publish, distribute, sublicense,7* and/or sell copies of the Software, and to permit persons to whom the8* Software is furnished to do so, subject to the following conditions:9*10* The above copyright notice and this permission notice (including the next11* paragraph) shall be included in all copies or substantial portions of the12* Software.13*14* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR15* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,16* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL17* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER18* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING19* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS20* IN THE SOFTWARE.21*/2223#ifdef HAVE_COMPRESSION2425#include <assert.h>2627/* Ensure that zlib uses 'const' in 'z_const' declarations. */28#ifndef ZLIB_CONST29#define ZLIB_CONST30#endif3132#ifdef HAVE_ZLIB33#include "zlib.h"34#endif3536#ifdef HAVE_ZSTD37#include "zstd.h"38#endif3940#include "util/compress.h"41#include "macros.h"4243/* 3 is the recomended level, with 22 as the absolute maximum */44#define ZSTD_COMPRESSION_LEVEL 34546size_t47util_compress_max_compressed_len(size_t in_data_size)48{49#ifdef HAVE_ZSTD50/* from the zstd docs (https://facebook.github.io/zstd/zstd_manual.html):51* compression runs faster if `dstCapacity` >= `ZSTD_compressBound(srcSize)`.52*/53return ZSTD_compressBound(in_data_size);54#elif defined(HAVE_ZLIB)55/* From https://zlib.net/zlib_tech.html:56*57* "In the worst possible case, where the other block types would expand58* the data, deflation falls back to stored (uncompressed) blocks. Thus59* for the default settings used by deflateInit(), compress(), and60* compress2(), the only expansion is an overhead of five bytes per 16 KB61* block (about 0.03%), plus a one-time overhead of six bytes for the62* entire stream."63*/64size_t num_blocks = (in_data_size + 16383) / 16384; /* round up blocks */65return in_data_size + 6 + (num_blocks * 5);66#else67STATIC_ASSERT(false);68#endif69}7071/* Compress data and return the size of the compressed data */72size_t73util_compress_deflate(const uint8_t *in_data, size_t in_data_size,74uint8_t *out_data, size_t out_buff_size)75{76#ifdef HAVE_ZSTD77size_t ret = ZSTD_compress(out_data, out_buff_size, in_data, in_data_size,78ZSTD_COMPRESSION_LEVEL);79if (ZSTD_isError(ret))80return 0;8182return ret;83#elif defined(HAVE_ZLIB)84size_t compressed_size = 0;8586/* allocate deflate state */87z_stream strm;88strm.zalloc = Z_NULL;89strm.zfree = Z_NULL;90strm.opaque = Z_NULL;91strm.next_in = in_data;92strm.next_out = out_data;93strm.avail_in = in_data_size;94strm.avail_out = out_buff_size;9596int ret = deflateInit(&strm, Z_BEST_COMPRESSION);97if (ret != Z_OK) {98(void) deflateEnd(&strm);99return 0;100}101102/* compress until end of in_data */103ret = deflate(&strm, Z_FINISH);104105/* stream should be complete */106assert(ret == Z_STREAM_END);107if (ret == Z_STREAM_END) {108compressed_size = strm.total_out;109}110111/* clean up and return */112(void) deflateEnd(&strm);113return compressed_size;114#else115STATIC_ASSERT(false);116# endif117}118119/**120* Decompresses data, returns true if successful.121*/122bool123util_compress_inflate(const uint8_t *in_data, size_t in_data_size,124uint8_t *out_data, size_t out_data_size)125{126#ifdef HAVE_ZSTD127size_t ret = ZSTD_decompress(out_data, out_data_size, in_data, in_data_size);128return !ZSTD_isError(ret);129#elif defined(HAVE_ZLIB)130z_stream strm;131132/* allocate inflate state */133strm.zalloc = Z_NULL;134strm.zfree = Z_NULL;135strm.opaque = Z_NULL;136strm.next_in = in_data;137strm.avail_in = in_data_size;138strm.next_out = out_data;139strm.avail_out = out_data_size;140141int ret = inflateInit(&strm);142if (ret != Z_OK)143return false;144145ret = inflate(&strm, Z_NO_FLUSH);146assert(ret != Z_STREAM_ERROR); /* state not clobbered */147148/* Unless there was an error we should have decompressed everything in one149* go as we know the uncompressed file size.150*/151if (ret != Z_STREAM_END) {152(void)inflateEnd(&strm);153return false;154}155assert(strm.avail_out == 0);156157/* clean up and return */158(void)inflateEnd(&strm);159return true;160#else161STATIC_ASSERT(false);162#endif163}164165#endif166167168