/* * Copyright © 2021 Valve Corporation * * 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 (including the next * paragraph) 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 HAVE_COMPRESSION #include /* Ensure that zlib uses 'const' in 'z_const' declarations. */ #ifndef ZLIB_CONST #define ZLIB_CONST #endif #ifdef HAVE_ZLIB #include "zlib.h" #endif #ifdef HAVE_ZSTD #include "zstd.h" #endif #include "util/compress.h" #include "macros.h" /* 3 is the recomended level, with 22 as the absolute maximum */ #define ZSTD_COMPRESSION_LEVEL 3 size_t util_compress_max_compressed_len(size_t in_data_size) { #ifdef HAVE_ZSTD /* from the zstd docs (https://facebook.github.io/zstd/zstd_manual.html): * compression runs faster if `dstCapacity` >= `ZSTD_compressBound(srcSize)`. */ return ZSTD_compressBound(in_data_size); #elif defined(HAVE_ZLIB) /* From https://zlib.net/zlib_tech.html: * * "In the worst possible case, where the other block types would expand * the data, deflation falls back to stored (uncompressed) blocks. Thus * for the default settings used by deflateInit(), compress(), and * compress2(), the only expansion is an overhead of five bytes per 16 KB * block (about 0.03%), plus a one-time overhead of six bytes for the * entire stream." */ size_t num_blocks = (in_data_size + 16383) / 16384; /* round up blocks */ return in_data_size + 6 + (num_blocks * 5); #else STATIC_ASSERT(false); #endif } /* Compress data and return the size of the compressed data */ size_t util_compress_deflate(const uint8_t *in_data, size_t in_data_size, uint8_t *out_data, size_t out_buff_size) { #ifdef HAVE_ZSTD size_t ret = ZSTD_compress(out_data, out_buff_size, in_data, in_data_size, ZSTD_COMPRESSION_LEVEL); if (ZSTD_isError(ret)) return 0; return ret; #elif defined(HAVE_ZLIB) size_t compressed_size = 0; /* allocate deflate state */ z_stream strm; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; strm.next_in = in_data; strm.next_out = out_data; strm.avail_in = in_data_size; strm.avail_out = out_buff_size; int ret = deflateInit(&strm, Z_BEST_COMPRESSION); if (ret != Z_OK) { (void) deflateEnd(&strm); return 0; } /* compress until end of in_data */ ret = deflate(&strm, Z_FINISH); /* stream should be complete */ assert(ret == Z_STREAM_END); if (ret == Z_STREAM_END) { compressed_size = strm.total_out; } /* clean up and return */ (void) deflateEnd(&strm); return compressed_size; #else STATIC_ASSERT(false); # endif } /** * Decompresses data, returns true if successful. */ bool util_compress_inflate(const uint8_t *in_data, size_t in_data_size, uint8_t *out_data, size_t out_data_size) { #ifdef HAVE_ZSTD size_t ret = ZSTD_decompress(out_data, out_data_size, in_data, in_data_size); return !ZSTD_isError(ret); #elif defined(HAVE_ZLIB) z_stream strm; /* allocate inflate state */ strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; strm.next_in = in_data; strm.avail_in = in_data_size; strm.next_out = out_data; strm.avail_out = out_data_size; int ret = inflateInit(&strm); if (ret != Z_OK) return false; ret = inflate(&strm, Z_NO_FLUSH); assert(ret != Z_STREAM_ERROR); /* state not clobbered */ /* Unless there was an error we should have decompressed everything in one * go as we know the uncompressed file size. */ if (ret != Z_STREAM_END) { (void)inflateEnd(&strm); return false; } assert(strm.avail_out == 0); /* clean up and return */ (void)inflateEnd(&strm); return true; #else STATIC_ASSERT(false); #endif } #endif