1 /*
2 * Copyright © 2021 Valve Corporation
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 */
23
24 #ifdef HAVE_COMPRESSION
25
26 #include <assert.h>
27
28 /* Ensure that zlib uses 'const' in 'z_const' declarations. */
29 #ifndef ZLIB_CONST
30 #define ZLIB_CONST
31 #endif
32
33 #ifdef HAVE_ZLIB
34 #include "zlib.h"
35 #endif
36
37 #ifdef HAVE_ZSTD
38 #include "zstd.h"
39 #endif
40
41 #include "util/compress.h"
42 #include "util/perf/cpu_trace.h"
43 #include "macros.h"
44
45 /* 3 is the recomended level, with 22 as the absolute maximum */
46 #define ZSTD_COMPRESSION_LEVEL 3
47
48 size_t
util_compress_max_compressed_len(size_t in_data_size)49 util_compress_max_compressed_len(size_t in_data_size)
50 {
51 #ifdef HAVE_ZSTD
52 /* from the zstd docs (https://facebook.github.io/zstd/zstd_manual.html):
53 * compression runs faster if `dstCapacity` >= `ZSTD_compressBound(srcSize)`.
54 */
55 return ZSTD_compressBound(in_data_size);
56 #elif defined(HAVE_ZLIB)
57 /* From https://zlib.net/zlib_tech.html:
58 *
59 * "In the worst possible case, where the other block types would expand
60 * the data, deflation falls back to stored (uncompressed) blocks. Thus
61 * for the default settings used by deflateInit(), compress(), and
62 * compress2(), the only expansion is an overhead of five bytes per 16 KB
63 * block (about 0.03%), plus a one-time overhead of six bytes for the
64 * entire stream."
65 */
66 size_t num_blocks = (in_data_size + 16383) / 16384; /* round up blocks */
67 return in_data_size + 6 + (num_blocks * 5);
68 #else
69 STATIC_ASSERT(false);
70 #endif
71 }
72
73 /* Compress data and return the size of the compressed data */
74 size_t
util_compress_deflate(const uint8_t * in_data,size_t in_data_size,uint8_t * out_data,size_t out_buff_size)75 util_compress_deflate(const uint8_t *in_data, size_t in_data_size,
76 uint8_t *out_data, size_t out_buff_size)
77 {
78 MESA_TRACE_FUNC();
79 #ifdef HAVE_ZSTD
80 size_t ret = ZSTD_compress(out_data, out_buff_size, in_data, in_data_size,
81 ZSTD_COMPRESSION_LEVEL);
82 if (ZSTD_isError(ret))
83 return 0;
84
85 return ret;
86 #elif defined(HAVE_ZLIB)
87 size_t compressed_size = 0;
88
89 /* allocate deflate state */
90 z_stream strm;
91 strm.zalloc = Z_NULL;
92 strm.zfree = Z_NULL;
93 strm.opaque = Z_NULL;
94 strm.next_in = in_data;
95 strm.next_out = out_data;
96 strm.avail_in = in_data_size;
97 strm.avail_out = out_buff_size;
98
99 int ret = deflateInit(&strm, Z_BEST_COMPRESSION);
100 if (ret != Z_OK) {
101 (void) deflateEnd(&strm);
102 return 0;
103 }
104
105 /* compress until end of in_data */
106 ret = deflate(&strm, Z_FINISH);
107
108 /* stream should be complete */
109 assert(ret == Z_STREAM_END);
110 if (ret == Z_STREAM_END) {
111 compressed_size = strm.total_out;
112 }
113
114 /* clean up and return */
115 (void) deflateEnd(&strm);
116 return compressed_size;
117 #else
118 STATIC_ASSERT(false);
119 # endif
120 }
121
122 /**
123 * Decompresses data, returns true if successful.
124 */
125 bool
util_compress_inflate(const uint8_t * in_data,size_t in_data_size,uint8_t * out_data,size_t out_data_size)126 util_compress_inflate(const uint8_t *in_data, size_t in_data_size,
127 uint8_t *out_data, size_t out_data_size)
128 {
129 MESA_TRACE_FUNC();
130 #ifdef HAVE_ZSTD
131 size_t ret = ZSTD_decompress(out_data, out_data_size, in_data, in_data_size);
132 return !ZSTD_isError(ret);
133 #elif defined(HAVE_ZLIB)
134 z_stream strm;
135
136 /* allocate inflate state */
137 strm.zalloc = Z_NULL;
138 strm.zfree = Z_NULL;
139 strm.opaque = Z_NULL;
140 strm.next_in = in_data;
141 strm.avail_in = in_data_size;
142 strm.next_out = out_data;
143 strm.avail_out = out_data_size;
144
145 int ret = inflateInit(&strm);
146 if (ret != Z_OK)
147 return false;
148
149 ret = inflate(&strm, Z_NO_FLUSH);
150 assert(ret != Z_STREAM_ERROR); /* state not clobbered */
151
152 /* Unless there was an error we should have decompressed everything in one
153 * go as we know the uncompressed file size.
154 */
155 if (ret != Z_STREAM_END) {
156 (void)inflateEnd(&strm);
157 return false;
158 }
159 assert(strm.avail_out == 0);
160
161 /* clean up and return */
162 (void)inflateEnd(&strm);
163 return true;
164 #else
165 STATIC_ASSERT(false);
166 #endif
167 }
168
169 #endif
170