LibTMJ 1.6.0
A library for loading JSON Tiled maps
Loading...
Searching...
No Matches
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
10uint8_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
62uint8_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 free(out);
140
141 return NULL;
142 }
143
144 logmsg(TMJ_LOG_DEBUG, "Decode (zlib): Z_BUF_ERROR");
145 case Z_OK:
146 logmsg(TMJ_LOG_DEBUG, "Decode (zlib): inflate OK");
147
148 void* tmp = realloc(out, INFLATE_BLOCK_SIZE * realloc_scale);
149
150 if (tmp == NULL) {
151 logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to grow inflate output buffer, the system is out memory");
152
153 free(out);
154
155 return NULL;
156 }
157
158 out = tmp;
159
160 stream.avail_out = INFLATE_BLOCK_SIZE;
161
162 stream.next_out = out + stream.total_out;
163
164 realloc_scale++;
165
166 break;
167
168 case Z_NEED_DICT:
169 logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to complete inflate, preset dictionary required");
170
171 goto fail_zlib;
172
173 case Z_DATA_ERROR:
174 logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to complete inflate, input data appears corrupted");
175
176 goto fail_zlib;
177
178 case Z_STREAM_ERROR:
179 logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to complete inflate, stream structure inconsistent");
180
181 goto fail_zlib;
182
183 case Z_MEM_ERROR:
184 logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to complete inflate, the system is out of memory");
185
186 goto fail_zlib;
187
188 default:
189 goto fail_zlib;
190 }
191
192 stat = inflate(&stream, Z_NO_FLUSH);
193 }
194
195 logmsg(TMJ_LOG_DEBUG, "Decode (zlib): Completed inflate, %zd bytes written to output buffer", stream.total_out);
196
197 if (inflateEnd(&stream) != Z_OK) {
198 logmsg(TMJ_LOG_ERR, "Decode (zlib): Completed inflate, but could not clean up; stream state was inconsistent");
199
200 goto fail_zlib;
201 }
202
203 *decompressed_size = stream.total_out;
204
205 return out;
206
207fail_zlib:
208 free(out);
209
210 if (stream.msg) {
211 logmsg(TMJ_LOG_ERR, "Decode (zlib): zlib error: '%s'", stream.msg);
212 }
213
214 return NULL;
215}
216
217uint8_t* tmj_zlib_compress(const uint8_t* data, size_t data_size, int level, size_t* compressed_size) {
218 logmsg(TMJ_LOG_DEBUG, "Decode (zlib): Compressing buffer of size %zu", data_size);
219
220 if (data == NULL) {
221 logmsg(TMJ_LOG_ERR, "Decode (zlib): Cannot compress NULL buffer");
222
223 return NULL;
224 }
225
226 const size_t DEFLATE_BLOCK_SIZE = 262144;
227
228 uint8_t* out = malloc(DEFLATE_BLOCK_SIZE);
229
230 if (out == NULL) {
231 logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to allocate buffer for compressed data, the system is out of memory");
232
233 return NULL;
234 }
235
236 z_stream stream = {0};
237
238 stream.zalloc = Z_NULL;
239 stream.zfree = Z_NULL;
240 stream.opaque = Z_NULL;
241
242 stream.avail_in = data_size;
243 stream.avail_out = DEFLATE_BLOCK_SIZE;
244
245 stream.next_in = data; // NOLINT(clang-diagnostic-incompatible-pointer-types-discards-qualifiers)
246 stream.next_out = out;
247
248 int ret = deflateInit(&stream, level);
249
250 switch (ret) {
251 case Z_OK:
252 logmsg(TMJ_LOG_DEBUG, "Decode (zlib): deflate initialization OK");
253 break;
254
255 case Z_MEM_ERROR:
256 logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to initialize deflate, the system is out of memory");
257
258 goto fail_zlib;
259
260 case Z_VERSION_ERROR:
261 logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to initialize deflate, incompatible zlib library version");
262
263 goto fail_zlib;
264
265 case Z_STREAM_ERROR:
267 "Decode (zlib): Unable to initialize deflate, invalid parameter(s) to deflate initialization "
268 "routine");
269
270 goto fail_zlib;
271
272 default:
273 goto fail_zlib;
274 }
275
276 size_t realloc_scale = 2;
277
278 // Iteratively deflate, growing the output buffer by 1 block each time we run out of space
279 int flush = Z_NO_FLUSH;
280 int stat = deflate(&stream, flush);
281
282 while (stat != Z_STREAM_END) {
283 switch (stat) {
284 case Z_BUF_ERROR:
285 if (stream.avail_out != 0) {
286 logmsg(TMJ_LOG_ERR, "Decode (zlib): No progress possible");
287
288 return NULL;
289 }
290
291 logmsg(TMJ_LOG_DEBUG, "Decode (zlib): Z_BUF_ERROR");
292 case Z_OK:
293 logmsg(TMJ_LOG_DEBUG, "Decode (zlib): deflate OK");
294
295 if (stream.avail_out == 0) { // more space needed
296 out = realloc(out, DEFLATE_BLOCK_SIZE * realloc_scale);
297 if (out == NULL) {
298 logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to grow deflate output buffer, the system is out memory");
299
300 return NULL;
301 }
302
303 stream.avail_out = DEFLATE_BLOCK_SIZE;
304
305 stream.next_out = out + stream.total_out;
306
307 realloc_scale++;
308 } else { // finished!
309 flush = Z_FINISH;
310 }
311
312 break;
313
314 case Z_STREAM_ERROR:
315 logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to complete deflate, stream structure inconsistent");
316
317 goto fail_zlib;
318
319 default:
320 goto fail_zlib;
321 }
322
323 stat = deflate(&stream, flush);
324 }
325
326 logmsg(TMJ_LOG_DEBUG, "Decode (zlib): Completed deflate, %zd bytes written to output buffer", stream.total_out);
327
328 if (deflateEnd(&stream) != Z_OK) {
329 logmsg(TMJ_LOG_ERR, "Decode (zlib): Completed deflate, but could not clean up; stream state was inconsistent");
330
331 goto fail_zlib;
332 }
333
334 *compressed_size = stream.total_out;
335
336 return out;
337
338fail_zlib:
339 free(out);
340
341 if (stream.msg) {
342 logmsg(TMJ_LOG_ERR, "Decode (zlib): zlib error: '%s'", stream.msg);
343 }
344
345 return NULL;
346}
347
348#endif
349
350// Thanks to John's article for explaining Base64: https://nachtimwald.com/2017/11/18/base64-encode-and-decode-in-c/
351
352// clang-format off
353const char b64_encode_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
354const unsigned char b64_decode_table[] = { 255, 255, 255, 255, 255, 255, 255, 255, 255,
355255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
356255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
357255, 255, 62, 255, 255, 255, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255,
358255, 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
35915, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, 255, 26,
36027, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
36147, 48, 49, 50, 51, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
362255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
363255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
364255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
365255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
366255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
367255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
368255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
369255, 255, 255, 255, 255, 255, 255, 255, 255, };
370// clang-format on
371
372// Not used, but included here for reference, to explain how the decode table was generated
373// void b64_generate_decode_table(){
374// memset(&b64_decode_table, -1, 256);
375//
376// for(size_t i = 0; i < 256; i++){
377// b64_decode_table[b64_encode_table[i]] = i;
378// }
379//}
380
384size_t b64_decode_size(const char* data) {
385 if (data == NULL) {
386 return 0;
387 }
388
389 size_t len = strlen(data);
390
391 if (len == 0) {
392 return 0;
393 }
394
395 size_t ret = len / 4 * 3;
396
397 // Check to see if the last 2 characters are padding bytes
398 if (data[len - 1] == '=') {
399 ret--;
400
401 if (data[len - 2] == '=') {
402 ret--;
403 }
404 }
405
406 return ret;
407}
408
409bool b64_is_valid_char(char c) {
410 if (isalnum(c)) {
411 return true;
412 }
413
414 if (c == '+' || c == '/' || c == '=') {
415 return true;
416 }
417
418 return false;
419}
420
421uint8_t* tmj_b64_decode(const char* data, size_t* decoded_size) {
422 if (data == NULL) {
423 logmsg(TMJ_LOG_ERR, "Decode (b64): Unable to decode null input");
424
425 return NULL;
426 }
427
428 size_t len = strlen(data);
429
430 if (len % 4 != 0) {
431 logmsg(TMJ_LOG_ERR, "Decode (b64): Invalid Base64 string, input length is not a multiple of 4");
432
433 return NULL;
434 }
435
436 size_t dSize = b64_decode_size(data);
437 uint8_t* out = malloc(dSize);
438
439 if (out == NULL) {
440 logmsg(TMJ_LOG_ERR, "Decode (b64): Unable to allocate output buffer, the system is out of memory");
441
442 return NULL;
443 }
444
445 // Validate the input
446 for (size_t i = 0; i < len; i++) {
447 if (!b64_is_valid_char(data[i])) {
448 logmsg(TMJ_LOG_ERR, "Decode (b64): Invalid Base64 character, '%c'", data[i]);
449
450 free(out);
451
452 return NULL;
453 }
454 }
455
456 for (size_t i = 0, j = 0; i < len; i += 4, j += 3) {
457 // Pack decoded 6-bit values into an integer
458 uint32_t p = b64_decode_table[data[i]];
459 p = (p << 6) | b64_decode_table[data[i + 1]];
460 p = data[i + 2] == '=' ? p << 6 : (p << 6) | b64_decode_table[data[i + 2]];
461 p = data[i + 3] == '=' ? p << 6 : (p << 6) | b64_decode_table[data[i + 3]];
462
463 // Reinterpret into 8-bit bytes
464 out[j] = (p >> 16) & 0xFF;
465 if (data[i + 2] != '=') {
466 out[j + 1] = (p >> 8) & 0xFF;
467 }
468 if (data[i + 3] != '=') {
469 out[j + 2] = p & 0xFF;
470 }
471 }
472
473 *decoded_size = dSize;
474
475 return out;
476}
477
478size_t b64_encoded_size(size_t inlen) {
479 size_t ret;
480
481 ret = inlen;
482 if (inlen % 3 != 0)
483 ret += 3 - (inlen % 3);
484 ret /= 3;
485 ret *= 4;
486
487 return ret;
488}
489
490char* tmj_b64_encode(uint8_t* data, size_t size) {
491 if (data == NULL || size == 0) {
492 logmsg(TMJ_LOG_ERR, "Encode (b64): Unable to encode null input");
493
494 return NULL;
495 }
496
497 size_t enc_size = b64_encoded_size(size);
498 char* out = malloc(enc_size + 1);
499
500 if (out == NULL) {
501 logmsg(TMJ_LOG_ERR, "Encode (b64): Unable to allocate output buffer, the system is out of memory");
502
503 return NULL;
504 }
505
506 out[enc_size] = '\0';
507
508 for (size_t i = 0, j = 0; i < size; i += 3, j += 4) {
509 size_t v = data[i];
510 v = i + 1 < size ? v << 8 | data[i + 1] : v << 8;
511 v = i + 2 < size ? v << 8 | data[i + 2] : v << 8;
512
513 out[j] = b64_encode_table[(v >> 18) & 0x3F];
514 out[j + 1] = b64_encode_table[(v >> 12) & 0x3F];
515 if (i + 1 < size) {
516 out[j + 2] = b64_encode_table[(v >> 6) & 0x3F];
517 } else {
518 out[j + 2] = '=';
519 }
520 if (i + 2 < size) {
521 out[j + 3] = b64_encode_table[v & 0x3F];
522 } else {
523 out[j + 3] = '=';
524 }
525 }
526
527 return out;
528}
const unsigned char b64_decode_table[]
Definition decode.c:354
size_t b64_encoded_size(size_t inlen)
Definition decode.c:478
bool b64_is_valid_char(char c)
Definition decode.c:409
const char b64_encode_table[]
Definition decode.c:353
size_t b64_decode_size(const char *data)
Calculates the size of the data decoded from the given base64 string.
Definition decode.c:384
char * tmj_b64_encode(uint8_t *data, size_t size)
Encodes a base64 string.
Definition decode.c:490
uint8_t * tmj_b64_decode(const char *data, size_t *decoded_size)
Decodes a base64 string.
Definition decode.c:421
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
uint8_t * tmj_zlib_compress(const uint8_t *data, size_t data_size, int level, size_t *compressed_size)
Compresses a buffer of bytes with zlib.
Definition decode.c:217
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
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:502
@ TMJ_LOG_DEBUG
Definition tmj.h:499