Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 176 additions & 8 deletions src/decode.c
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,137 @@ uint8_t* tmj_zlib_decompress(const uint8_t* data, size_t data_size, size_t* deco
return NULL;
}

uint8_t* tmj_zlib_compress(const uint8_t* data, size_t data_size, int level, size_t* compressed_size) {
logmsg(TMJ_LOG_DEBUG, "Decode (zlib): Compressing buffer of size %zu", data_size);

if (data == NULL) {
logmsg(TMJ_LOG_ERR, "Decode (zlib): Cannot compress NULL buffer");

return NULL;
}

const size_t DEFLATE_BLOCK_SIZE = 262144;

uint8_t* out = malloc(DEFLATE_BLOCK_SIZE);

if (out == NULL) {
logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to allocate buffer for compressed data, the system is out of memory");

return NULL;
}

z_stream stream = {0};

stream.zalloc = Z_NULL;
stream.zfree = Z_NULL;
stream.opaque = Z_NULL;

stream.avail_in = data_size;
stream.avail_out = DEFLATE_BLOCK_SIZE;

stream.next_in = data; // NOLINT(clang-diagnostic-incompatible-pointer-types-discards-qualifiers)
stream.next_out = out;

int ret = deflateInit(&stream, level);

switch (ret) {
case Z_OK:
logmsg(TMJ_LOG_DEBUG, "Decode (zlib): deflate initialization OK");
break;

case Z_MEM_ERROR:
logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to initialize deflate, the system is out of memory");

goto fail_zlib;

case Z_VERSION_ERROR:
logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to initialize deflate, incompatible zlib library version");

goto fail_zlib;

case Z_STREAM_ERROR:
logmsg(TMJ_LOG_ERR,
"Decode (zlib): Unable to initialize deflate, invalid parameter(s) to deflate initialization "
"routine");

goto fail_zlib;

default:
goto fail_zlib;
}

size_t realloc_scale = 2;

// Iteratively deflate, growing the output buffer by 1 block each time we run out of space
int flush = Z_NO_FLUSH;
int stat = deflate(&stream, flush);

while (stat != Z_STREAM_END) {
switch (stat) {
case Z_BUF_ERROR:
if (stream.avail_out != 0) {
logmsg(TMJ_LOG_ERR, "Decode (zlib): No progress possible");

return NULL;
}

logmsg(TMJ_LOG_DEBUG, "Decode (zlib): Z_BUF_ERROR");
case Z_OK:
logmsg(TMJ_LOG_DEBUG, "Decode (zlib): deflate OK");

if (stream.avail_out == 0) { // more space needed
out = realloc(out, DEFLATE_BLOCK_SIZE * realloc_scale);
if (out == NULL) {
logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to grow deflate output buffer, the system is out memory");

return NULL;
}

stream.avail_out = DEFLATE_BLOCK_SIZE;

stream.next_out = out + stream.total_out;

realloc_scale++;
} else { // finished!
flush = Z_FINISH;
}

break;

case Z_STREAM_ERROR:
logmsg(TMJ_LOG_ERR, "Decode (zlib): Unable to complete deflate, stream structure inconsistent");

goto fail_zlib;

default:
goto fail_zlib;
}

stat = deflate(&stream, flush);
}

logmsg(TMJ_LOG_DEBUG, "Decode (zlib): Completed deflate, %zd bytes written to output buffer", stream.total_out);

if (deflateEnd(&stream) != Z_OK) {
logmsg(TMJ_LOG_ERR, "Decode (zlib): Completed deflate, but could not clean up; stream state was inconsistent");

goto fail_zlib;
}

*compressed_size = stream.total_out;

return out;

fail_zlib:
free(out);

if (stream.msg) {
logmsg(TMJ_LOG_ERR, "Decode (zlib): zlib error: '%s'", stream.msg);
}

return NULL;
}

#endif

// Thanks to John's article for explaining Base64: https://nachtimwald.com/2017/11/18/base64-encode-and-decode-in-c/
Expand Down Expand Up @@ -276,14 +407,6 @@ bool b64_is_valid_char(char c) {
return false;
}

// Don't need this yet, so it'll stay unimplemented for now
/**
* Unimplemented, but may be useful to implement in the future.
*/
char* tmj_b64_encode(uint8_t* data) {
return NULL;
}

uint8_t* tmj_b64_decode(const char* data, size_t* decoded_size) {
if (data == NULL) {
logmsg(TMJ_LOG_ERR, "Decode (b64): Unable to decode null input");
Expand Down Expand Up @@ -340,3 +463,48 @@ uint8_t* tmj_b64_decode(const char* data, size_t* decoded_size) {

return out;
}

size_t b64_encoded_size(size_t inlen) {
size_t ret;

ret = inlen;
if (inlen % 3 != 0)
ret += 3 - (inlen % 3);
ret /= 3;
ret *= 4;

return ret;
}

char* tmj_b64_encode(uint8_t* data, size_t size) {
if (data == NULL || size == 0) {
logmsg(TMJ_LOG_ERR, "Encode (b64): Unable to encode null input");

return NULL;
}

size_t enc_size = b64_encoded_size(size);
char* out = malloc(enc_size + 1);
out[enc_size] = '\0';

for (size_t i = 0, j = 0; i < size; i += 3, j += 4) {
size_t v = data[i];
v = i + 1 < size ? v << 8 | data[i + 1] : v << 8;
v = i + 2 < size ? v << 8 | data[i + 2] : v << 8;

out[j] = b64_encode_table[(v >> 18) & 0x3F];
out[j + 1] = b64_encode_table[(v >> 12) & 0x3F];
if (i + 1 < size) {
out[j + 2] = b64_encode_table[(v >> 6) & 0x3F];
} else {
out[j + 2] = '=';
}
if (i + 2 < size) {
out[j + 3] = b64_encode_table[v & 0x3F];
} else {
out[j + 3] = '=';
}
}

return out;
}
26 changes: 26 additions & 0 deletions src/decode.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,34 @@ uint8_t* tmj_zstd_decompress(const uint8_t* data, size_t data_size, size_t* deco
*/
uint8_t* tmj_zlib_decompress(const uint8_t* data, size_t data_size, size_t* decompressed_size);

/**
* @ingroup decode
* Compresses a buffer of bytes with zlib.
*
* @param data A buffer of unsigned bytes.
* @param data_size The length of the buffer.
* @param level The desired level of zlib compression (0-9).
* @param[out] compressed_size The length of the returned compressed buffer.
*
* @return On success, returns a dynamically-allocated, zlib-compressed
* buffer of unsigned bytes. The returned buffer must be freed by the caller.
*/
uint8_t* tmj_zlib_compress(const uint8_t* data, size_t data_size, int level, size_t* compressed_size);

#endif

/**
* @ingroup decode
* Encodes a base64 string.
*
* @param data An array of unsigned bytes.
* @param size The length of the array.
*
* @return On success, returns a dynamically-allocated null-terminated base64 string.
* The returned array must be freed by the caller.
*/
char* tmj_b64_encode(uint8_t* data, size_t size);

/**
* @ingroup decode
* Decodes a base64 string.
Expand Down
2 changes: 2 additions & 0 deletions src/tmj.def
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ EXPORTS
tmj_decode_layer
tmj_zstd_decompress
tmj_zlib_decompress
tmj_zlib_compress
tmj_b64_decode
tmj_b64_encode
31 changes: 31 additions & 0 deletions test/decode_tests.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,20 @@ void test_b64_decode(void) {
free(out2);
}

void test_b64_encode(void) {
const char* msg = "This is a test string";
const char* msg2 = "This is another test string!";

char* out = tmj_b64_encode((uint8_t*)msg, 22);
char* out2 = tmj_b64_encode((uint8_t*)msg2, 29);

TEST_ASSERT_EQUAL_STRING("VGhpcyBpcyBhIHRlc3Qgc3RyaW5nAA==", out);
TEST_ASSERT_EQUAL_STRING("VGhpcyBpcyBhbm90aGVyIHRlc3Qgc3RyaW5nIQA=", out2);

free(out);
free(out2);
}

#ifdef LIBTMJ_ZLIB
void test_zlib_decode(void) {
const char* msg_zlib = "eJwLycgsVgCixLz8kozUIoWS1OISheKSosy8dEUGAKBMCl4=";
Expand Down Expand Up @@ -82,6 +96,21 @@ void test_zlib_decode(void) {
free(msg_zlib_decompressed);
free(msg_gzip_decompressed);
}

void test_zlib_encode(void) {
const char* msg_zlib = "This is another test string!";

size_t compressed_size_zlib = 0;
uint8_t* msg_zlib_compressed = tmj_zlib_compress((uint8_t*)msg_zlib, 29, -1, &compressed_size_zlib);

char* out = tmj_b64_encode((uint8_t*)msg_zlib_compressed, compressed_size_zlib);
TEST_ASSERT_NOT_NULL(out);
free(msg_zlib_compressed);

TEST_ASSERT_EQUAL_STRING("eJwLycgsVgCixLz8kozUIoWS1OISheKSosy8dEUGAKBMCl4=", out);

free(out);
}
#endif

#ifdef LIBTMJ_ZSTD
Expand All @@ -106,8 +135,10 @@ void test_zstd_decode(void) {
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_b64_decode);
RUN_TEST(test_b64_encode);
#ifdef LIBTMJ_ZLIB
RUN_TEST(test_zlib_decode);
RUN_TEST(test_zlib_encode);
#endif
#ifdef LIBTMJ_ZSTD
RUN_TEST(test_zstd_decode);
Expand Down
Loading