LibTMJ 1.4.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 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
200fail_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
210uint8_t* tmj_zlib_compress(const uint8_t* data, size_t data_size, int level, size_t* compressed_size) {
211 logmsg(TMJ_LOG_DEBUG, "Decode (zlib): Compressing buffer of size %zu", data_size);
212
213 if (data == NULL) {
214 logmsg(TMJ_LOG_ERR, "Decode (zlib): Cannot compress NULL buffer");
215
216 return NULL;
217 }
218
219 const size_t DEFLATE_BLOCK_SIZE = 262144;
220
221 uint8_t* out = malloc(DEFLATE_BLOCK_SIZE);
222
223 if (out == NULL) {
224 logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to allocate buffer for compressed data, the system is out of memory");
225
226 return NULL;
227 }
228
229 z_stream stream = {0};
230
231 stream.zalloc = Z_NULL;
232 stream.zfree = Z_NULL;
233 stream.opaque = Z_NULL;
234
235 stream.avail_in = data_size;
236 stream.avail_out = DEFLATE_BLOCK_SIZE;
237
238 stream.next_in = data; // NOLINT(clang-diagnostic-incompatible-pointer-types-discards-qualifiers)
239 stream.next_out = out;
240
241 int ret = deflateInit(&stream, level);
242
243 switch (ret) {
244 case Z_OK:
245 logmsg(TMJ_LOG_DEBUG, "Decode (zlib): deflate initialization OK");
246 break;
247
248 case Z_MEM_ERROR:
249 logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to initialize deflate, the system is out of memory");
250
251 goto fail_zlib;
252
253 case Z_VERSION_ERROR:
254 logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to initialize deflate, incompatible zlib library version");
255
256 goto fail_zlib;
257
258 case Z_STREAM_ERROR:
260 "Decode (zlib): Unable to initialize deflate, invalid parameter(s) to deflate initialization "
261 "routine");
262
263 goto fail_zlib;
264
265 default:
266 goto fail_zlib;
267 }
268
269 size_t realloc_scale = 2;
270
271 // Iteratively deflate, growing the output buffer by 1 block each time we run out of space
272 int flush = Z_NO_FLUSH;
273 int stat = deflate(&stream, flush);
274
275 while (stat != Z_STREAM_END) {
276 switch (stat) {
277 case Z_BUF_ERROR:
278 if (stream.avail_out != 0) {
279 logmsg(TMJ_LOG_ERR, "Decode (zlib): No progress possible");
280
281 return NULL;
282 }
283
284 logmsg(TMJ_LOG_DEBUG, "Decode (zlib): Z_BUF_ERROR");
285 case Z_OK:
286 logmsg(TMJ_LOG_DEBUG, "Decode (zlib): deflate OK");
287
288 if (stream.avail_out == 0) { // more space needed
289 out = realloc(out, DEFLATE_BLOCK_SIZE * realloc_scale);
290 if (out == NULL) {
291 logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to grow deflate output buffer, the system is out memory");
292
293 return NULL;
294 }
295
296 stream.avail_out = DEFLATE_BLOCK_SIZE;
297
298 stream.next_out = out + stream.total_out;
299
300 realloc_scale++;
301 } else { // finished!
302 flush = Z_FINISH;
303 }
304
305 break;
306
307 case Z_STREAM_ERROR:
308 logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to complete deflate, stream structure inconsistent");
309
310 goto fail_zlib;
311
312 default:
313 goto fail_zlib;
314 }
315
316 stat = deflate(&stream, flush);
317 }
318
319 logmsg(TMJ_LOG_DEBUG, "Decode (zlib): Completed deflate, %zd bytes written to output buffer", stream.total_out);
320
321 if (deflateEnd(&stream) != Z_OK) {
322 logmsg(TMJ_LOG_ERR, "Decode (zlib): Completed deflate, but could not clean up; stream state was inconsistent");
323
324 goto fail_zlib;
325 }
326
327 *compressed_size = stream.total_out;
328
329 return out;
330
331fail_zlib:
332 free(out);
333
334 if (stream.msg) {
335 logmsg(TMJ_LOG_ERR, "Decode (zlib): zlib error: '%s'", stream.msg);
336 }
337
338 return NULL;
339}
340
341#endif
342
343// Thanks to John's article for explaining Base64: https://nachtimwald.com/2017/11/18/base64-encode-and-decode-in-c/
344
345// clang-format off
346const char b64_encode_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
347const unsigned char b64_decode_table[] = { 255, 255, 255, 255, 255, 255, 255, 255, 255,
348255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
349255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
350255, 255, 62, 255, 255, 255, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255,
351255, 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
35215, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, 255, 26,
35327, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
35447, 48, 49, 50, 51, 255, 255, 255, 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, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
358255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
359255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
360255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
361255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
362255, 255, 255, 255, 255, 255, 255, 255, 255, };
363// clang-format on
364
365// Not used, but included here for reference, to explain how the decode table was generated
366// void b64_generate_decode_table(){
367// memset(&b64_decode_table, -1, 256);
368//
369// for(size_t i = 0; i < 256; i++){
370// b64_decode_table[b64_encode_table[i]] = i;
371// }
372//}
373
377size_t b64_decode_size(const char* data) {
378 if (data == NULL) {
379 return 0;
380 }
381
382 size_t len = strlen(data);
383
384 size_t ret = len / 4 * 3;
385
386 // Check to see if the last 2 characters are padding bytes
387 if (data[len - 1] == '=') {
388 ret--;
389
390 if (data[len - 2] == '=') {
391 ret--;
392 }
393 }
394
395 return ret;
396}
397
398bool b64_is_valid_char(char c) {
399 if (isalnum(c)) {
400 return true;
401 }
402
403 if (c == '+' || c == '/' || c == '=') {
404 return true;
405 }
406
407 return false;
408}
409
410uint8_t* tmj_b64_decode(const char* data, size_t* decoded_size) {
411 if (data == NULL) {
412 logmsg(TMJ_LOG_ERR, "Decode (b64): Unable to decode null input");
413
414 return NULL;
415 }
416
417 size_t len = strlen(data);
418
419 if (len % 4 != 0) {
420 logmsg(TMJ_LOG_ERR, "Decode (b64): Invalid Base64 string, input length is not a multiple of 4");
421
422 return NULL;
423 }
424
425 size_t dSize = b64_decode_size(data);
426 uint8_t* out = malloc(dSize);
427
428 if (out == NULL) {
429 logmsg(TMJ_LOG_ERR, "Decode (b64): Unable to allocate output buffer, the system is out of memory");
430
431 return NULL;
432 }
433
434 // Validate the input
435 for (size_t i = 0; i < len; i++) {
436 if (!b64_is_valid_char(data[i])) {
437 logmsg(TMJ_LOG_ERR, "Decode (b64): Invalid Base64 character, '%c'", data[i]);
438
439 free(out);
440
441 return NULL;
442 }
443 }
444
445 for (size_t i = 0, j = 0; i < len; i += 4, j += 3) {
446 // Pack decoded 6-bit values into an integer
447 uint32_t p = b64_decode_table[data[i]];
448 p = (p << 6) | b64_decode_table[data[i + 1]];
449 p = data[i + 2] == '=' ? p << 6 : (p << 6) | b64_decode_table[data[i + 2]];
450 p = data[i + 3] == '=' ? p << 6 : (p << 6) | b64_decode_table[data[i + 3]];
451
452 // Reinterpret into 8-bit bytes
453 out[j] = (p >> 16) & 0xFF;
454 if (data[i + 2] != '=') {
455 out[j + 1] = (p >> 8) & 0xFF;
456 }
457 if (data[i + 3] != '=') {
458 out[j + 2] = p & 0xFF;
459 }
460 }
461
462 *decoded_size = dSize;
463
464 return out;
465}
466
467size_t b64_encoded_size(size_t inlen) {
468 size_t ret;
469
470 ret = inlen;
471 if (inlen % 3 != 0)
472 ret += 3 - (inlen % 3);
473 ret /= 3;
474 ret *= 4;
475
476 return ret;
477}
478
479char* tmj_b64_encode(uint8_t* data, size_t size) {
480 if (data == NULL || size == 0) {
481 logmsg(TMJ_LOG_ERR, "Encode (b64): Unable to encode null input");
482
483 return NULL;
484 }
485
486 size_t enc_size = b64_encoded_size(size);
487 char* out = malloc(enc_size + 1);
488 out[enc_size] = '\0';
489
490 for (size_t i = 0, j = 0; i < size; i += 3, j += 4) {
491 size_t v = data[i];
492 v = i + 1 < size ? v << 8 | data[i + 1] : v << 8;
493 v = i + 2 < size ? v << 8 | data[i + 2] : v << 8;
494
495 out[j] = b64_encode_table[(v >> 18) & 0x3F];
496 out[j + 1] = b64_encode_table[(v >> 12) & 0x3F];
497 if (i + 1 < size) {
498 out[j + 2] = b64_encode_table[(v >> 6) & 0x3F];
499 } else {
500 out[j + 2] = '=';
501 }
502 if (i + 2 < size) {
503 out[j + 3] = b64_encode_table[v & 0x3F];
504 } else {
505 out[j + 3] = '=';
506 }
507 }
508
509 return out;
510}
const unsigned char b64_decode_table[]
Definition decode.c:347
size_t b64_encoded_size(size_t inlen)
Definition decode.c:467
bool b64_is_valid_char(char c)
Definition decode.c:398
const char b64_encode_table[]
Definition decode.c:346
size_t b64_decode_size(const char *data)
Calculates the size of the data decoded from the given base64 string.
Definition decode.c:377
char * tmj_b64_encode(uint8_t *data, size_t size)
Encodes a base64 string.
Definition decode.c:479
uint8_t * tmj_b64_decode(const char *data, size_t *decoded_size)
Decodes a base64 string.
Definition decode.c:410
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:210
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