LibTMJ  1.0.0
A library for loading JSON Tiled maps
decode.c
Go to the documentation of this file.
1 #include <ctype.h>
2 #include <stdlib.h>
3 #include <string.h>
4 
5 #include "decode.h"
6 #include "log.h"
7 
8 #ifdef LIBTMJ_ZSTD
9 
10 uint8_t* tmj_zstd_decompress(const uint8_t* data, size_t data_size, size_t* decompressed_size) {
11  logmsg(TMJ_LOG_DEBUG, "Decode (zstd): Decompressing buffer of size %zu", data_size);
12 
13  if (data == NULL) {
14  logmsg(TMJ_LOG_ERR, "Decode (zstd): Cannot decompress NULL buffer");
15 
16  return NULL;
17  }
18 
19  size_t ret_size = ZSTD_getFrameContentSize(data, data_size);
20 
21  if (ret_size == ZSTD_CONTENTSIZE_ERROR) {
22  logmsg(TMJ_LOG_ERR, "Decode (zstd): Unable to decompress non-zstd buffer");
23 
24  return NULL;
25  }
26 
27  if (ret_size == ZSTD_CONTENTSIZE_UNKNOWN) {
28  logmsg(TMJ_LOG_ERR, "Decode (zstd): Unable to determine uncompressed size of compressed data");
29 
30  return NULL;
31  }
32 
33  void* ret = malloc(ret_size);
34 
35  if (ret == NULL) {
36  logmsg(TMJ_LOG_ERR, "Decode (zstd): Unable to allocate buffer for decompressed data, the system is out of memory");
37 
38  return NULL;
39  }
40 
41  size_t dsize = ZSTD_decompress(ret, ret_size, data, data_size);
42 
43  logmsg(TMJ_LOG_DEBUG, "Decode (zstd): Decompressed byte total: %zu", dsize);
44 
45  if (ZSTD_isError(dsize)) {
46  logmsg(TMJ_LOG_ERR, "Decode (zstd): Decompression error: %s", ZSTD_getErrorName(dsize));
47 
48  free(ret);
49 
50  return NULL;
51  }
52 
53  *decompressed_size = ret_size;
54 
55  return ret;
56 }
57 
58 #endif
59 
60 #ifdef LIBTMJ_ZLIB
61 
62 uint8_t* tmj_zlib_decompress(const uint8_t* data, size_t data_size, size_t* decompressed_size) {
63  logmsg(TMJ_LOG_DEBUG, "Decode (zlib): Decompressing buffer of size %zu", data_size);
64 
65  if (data == NULL) {
66  logmsg(TMJ_LOG_ERR, "Decode (zlib): Cannot decompress NULL buffer");
67 
68  return NULL;
69  }
70 
71  const size_t INFLATE_BLOCK_SIZE = 262144;
72 
73  uint8_t* out = malloc(INFLATE_BLOCK_SIZE);
74 
75  if (out == NULL) {
76  logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to allocate buffer for decompressed data, the system is out of memory");
77 
78  return NULL;
79  }
80 
81  z_stream stream = {0};
82 
83  stream.zalloc = Z_NULL;
84  stream.zfree = Z_NULL;
85  stream.opaque = Z_NULL;
86 
87  stream.avail_in = data_size;
88  stream.avail_out = INFLATE_BLOCK_SIZE;
89 
90  stream.next_in = data; // NOLINT(clang-diagnostic-incompatible-pointer-types-discards-qualifiers)
91  stream.next_out = out;
92 
93  // 15 + 32 for zlib and gzip decoding with automatic header detection, according to the manual
94  int ret = inflateInit2(&stream, 15 + 32);
95 
96  switch (ret) {
97  case Z_OK:
98  logmsg(TMJ_LOG_DEBUG, "Decode (zlib): inflate initialization OK");
99  break;
100 
101  case Z_MEM_ERROR:
102  logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to initialize inflate, the system is out of memory");
103 
104  goto fail_zlib;
105 
106  case Z_VERSION_ERROR:
107  logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to initialize inflate, incompatible zlib library version");
108 
109  goto fail_zlib;
110 
111  case Z_STREAM_ERROR:
113  "Decode (zlib): Unable to initialize inflate, invalid parameter(s) to inflate initialization "
114  "routine");
115 
116  goto fail_zlib;
117 
118  default:
119  goto fail_zlib;
120  }
121 
122  size_t realloc_scale = 2;
123 
124  // Iteratively inflate, growing the output buffer by 1 block each time we run out of space
125  //
126  // Note from the manual: "If inflate returns Z_OK and with zero avail_out,
127  // it must be called again after making room in the output buffer because
128  // there might be more output pending."
129  // Unsure if this is actually necessary, or if we can rely on Z_BUF_ERROR to tell us when to resize
130  int stat = inflate(&stream, Z_NO_FLUSH);
131 
132  while (stat != Z_STREAM_END) {
133  switch (stat) {
134  case Z_BUF_ERROR:
135  // If we hit Z_BUF_ERROR with buffer space left, something's fucked
136  if (stream.avail_out != 0) {
137  logmsg(TMJ_LOG_ERR, "Decode (zlib): No progress possible");
138 
139  return NULL;
140  }
141 
142  logmsg(TMJ_LOG_DEBUG, "Decode (zlib): Z_BUF_ERROR");
143  case Z_OK:
144  logmsg(TMJ_LOG_DEBUG, "Decode (zlib): inflate OK");
145 
146  out = realloc(out, INFLATE_BLOCK_SIZE * realloc_scale);
147  if (out == NULL) {
148  logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to grow inflate output buffer, the system is out memory");
149 
150  return NULL;
151  }
152 
153  stream.avail_out = INFLATE_BLOCK_SIZE;
154 
155  stream.next_out = out + stream.total_out;
156 
157  realloc_scale++;
158 
159  break;
160 
161  case Z_NEED_DICT:
162  logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to complete inflate, preset dictionary required");
163 
164  goto fail_zlib;
165 
166  case Z_DATA_ERROR:
167  logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to complete inflate, input data appears corrupted");
168 
169  goto fail_zlib;
170 
171  case Z_STREAM_ERROR:
172  logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to complete inflate, stream structure inconsistent");
173 
174  goto fail_zlib;
175 
176  case Z_MEM_ERROR:
177  logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to complete inflate, the system is out of memory");
178 
179  goto fail_zlib;
180 
181  default:
182  goto fail_zlib;
183  }
184 
185  stat = inflate(&stream, Z_NO_FLUSH);
186  }
187 
188  logmsg(TMJ_LOG_DEBUG, "Decode (zlib): Completed inflate, %zd bytes written to output buffer", stream.total_out);
189 
190  if (inflateEnd(&stream) != Z_OK) {
191  logmsg(TMJ_LOG_ERR, "Decode (zlib): Completed inflate, but could not clean up; stream state was inconsistent");
192 
193  goto fail_zlib;
194  }
195 
196  *decompressed_size = stream.total_out;
197 
198  return out;
199 
200 fail_zlib:
201  free(out);
202 
203  if (stream.msg) {
204  logmsg(TMJ_LOG_ERR, "Decode (zlib): zlib error: '%s'", stream.msg);
205  }
206 
207  return NULL;
208 }
209 
210 #endif
211 
212 // Thanks to John's article for explaining Base64: https://nachtimwald.com/2017/11/18/base64-encode-and-decode-in-c/
213 
214 // clang-format off
215 const char b64_encode_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
216 const unsigned char b64_decode_table[] = { 255, 255, 255, 255, 255, 255, 255, 255, 255,
217 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
218 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
219 255, 255, 62, 255, 255, 255, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255,
220 255, 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
221 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, 255, 26,
222 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
223 47, 48, 49, 50, 51, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
224 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
225 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
226 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
227 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
228 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
229 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
230 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
231 255, 255, 255, 255, 255, 255, 255, 255, 255, };
232 // clang-format on
233 
234 // Not used, but included here for reference, to explain how the decode table was generated
235 // void b64_generate_decode_table(){
236 // memset(&b64_decode_table, -1, 256);
237 //
238 // for(size_t i = 0; i < 256; i++){
239 // b64_decode_table[b64_encode_table[i]] = i;
240 // }
241 //}
242 
246 size_t b64_decode_size(const char* data) {
247  if (data == NULL) {
248  return 0;
249  }
250 
251  size_t len = strlen(data);
252 
253  size_t ret = len / 4 * 3;
254 
255  // Check to see if the last 2 characters are padding bytes
256  if (data[len - 1] == '=') {
257  ret--;
258 
259  if (data[len - 2] == '=') {
260  ret--;
261  }
262  }
263 
264  return ret;
265 }
266 
267 bool b64_is_valid_char(char c) {
268  if (isalnum(c)) {
269  return true;
270  }
271 
272  if (c == '+' || c == '/' || c == '=') {
273  return true;
274  }
275 
276  return false;
277 }
278 
279 // Don't need this yet, so it'll stay unimplemented for now
283 char* tmj_b64_encode(uint8_t* data) {
284  return NULL;
285 }
286 
287 uint8_t* tmj_b64_decode(const char* data, size_t* decoded_size) {
288  if (data == NULL) {
289  logmsg(TMJ_LOG_ERR, "Decode (b64): Unable to decode null input");
290 
291  return NULL;
292  }
293 
294  size_t len = strlen(data);
295 
296  if (len % 4 != 0) {
297  logmsg(TMJ_LOG_ERR, "Decode (b64): Invalid Base64 string, input length is not a multiple of 4");
298 
299  return NULL;
300  }
301 
302  size_t dSize = b64_decode_size(data);
303  uint8_t* out = malloc(dSize);
304 
305  if (out == NULL) {
306  logmsg(TMJ_LOG_ERR, "Decode (b64): Unable to allocate output buffer, the system is out of memory");
307 
308  return NULL;
309  }
310 
311  // Validate the input
312  for (size_t i = 0; i < len; i++) {
313  if (!b64_is_valid_char(data[i])) {
314  logmsg(TMJ_LOG_ERR, "Decode (b64): Invalid Base64 character, '%c'", data[i]);
315 
316  free(out);
317 
318  return NULL;
319  }
320  }
321 
322  for (size_t i = 0, j = 0; i < len; i += 4, j += 3) {
323  // Pack decoded 6-bit values into an integer
324  uint32_t p = b64_decode_table[data[i]];
325  p = (p << 6) | b64_decode_table[data[i + 1]];
326  p = data[i + 2] == '=' ? p << 6 : (p << 6) | b64_decode_table[data[i + 2]];
327  p = data[i + 3] == '=' ? p << 6 : (p << 6) | b64_decode_table[data[i + 3]];
328 
329  // Reinterpret into 8-bit bytes
330  out[j] = (p >> 16) & 0xFF;
331  if (data[i + 2] != '=') {
332  out[j + 1] = (p >> 8) & 0xFF;
333  }
334  if (data[i + 3] != '=') {
335  out[j + 2] = p & 0xFF;
336  }
337  }
338 
339  *decoded_size = dSize;
340 
341  return out;
342 }
const unsigned char b64_decode_table[]
Definition: decode.c:216
bool b64_is_valid_char(char c)
Definition: decode.c:267
char * tmj_b64_encode(uint8_t *data)
Unimplemented, but may be useful to implement in the future.
Definition: decode.c:283
const char b64_encode_table[]
Definition: decode.c:215
size_t b64_decode_size(const char *data)
Calculates the size of the data decoded from the given base64 string.
Definition: decode.c:246
uint8_t * tmj_zlib_decompress(const uint8_t *data, size_t data_size, size_t *decompressed_size)
Decompresses a zlib/gzip-compressed buffer of bytes.
Definition: decode.c:62
uint8_t * tmj_b64_decode(const char *data, size_t *decoded_size)
Decodes a base64 string.
Definition: decode.c:287
uint8_t * tmj_zstd_decompress(const uint8_t *data, size_t data_size, size_t *decompressed_size)
Decompresses a zstd-compressed buffer of bytes.
Definition: decode.c:10
void logmsg(tmj_log_priority priority, char *msg,...)
Processes log messages and passes them to the active logging callback, if there is one.
Definition: log.c:23
@ TMJ_LOG_ERR
Definition: tmj.h:500
@ TMJ_LOG_DEBUG
Definition: tmj.h:497