From 57f3c22b32671abbda9024f955d0ea9ee4ff47bf Mon Sep 17 00:00:00 2001 From: hellerve Date: Sat, 25 Jan 2020 11:18:56 +0100 Subject: [PATCH] initial --- README.md | 7 ++ tests/zlib.carp | 26 +++++++ zlib.carp | 50 +++++++++++++ zlib_helper.h | 195 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 278 insertions(+) create mode 100644 README.md create mode 100644 tests/zlib.carp create mode 100644 zlib.carp create mode 100644 zlib_helper.h diff --git a/README.md b/README.md new file mode 100644 index 0000000..7fc8481 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# zlib.carp + +is a high-level wrapper around [zlib](https://zlib.net/). + +## Usage + +The `ZLib` module provides only two functions diff --git a/tests/zlib.carp b/tests/zlib.carp new file mode 100644 index 0000000..c75d373 --- /dev/null +++ b/tests/zlib.carp @@ -0,0 +1,26 @@ +(load "Test.carp") + +(load "zlib.carp") + +(use-all ZLib Test) + +(deftest test + (assert-true test + (Result.success? &(deflate @"hi")) + "deflation returns success" + ) + (assert-true test + (Result.success? &(inflate (Result.unsafe-from-success (deflate @"hi")))) + "deflation->inflation returns success" + ) + (assert-equal test + &(Result.Error @"Data Error") + &(inflate (ZBytes.init 3 @"hi")) + "inflating random data returns data error" + ) + (assert-equal test + &(Result.Success @"hi") + &(inflate (Result.unsafe-from-success (deflate @"hi"))) + "deflation->inflation works" + ) +) diff --git a/zlib.carp b/zlib.carp new file mode 100644 index 0000000..e21d39e --- /dev/null +++ b/zlib.carp @@ -0,0 +1,50 @@ +(relative-include "zlib_helper.h") +(add-cflag "-lz") + +; i tried doing this in carp, but it’s a bit of a pain to wrap the API +; idiomatically, so for now you’ll only get regular inflation and deflation +(defmodule ZLib + (register-type ZBytes [ + len Int + bytes String + ]) + + (register-type ZRes) + (defmodule ZRes + (register ok? (Fn [&ZRes] Bool) "ZRes_is_ok") + (register bytes (Fn [ZRes] ZBytes) "ZRes_bytes") + (register str (Fn [ZRes] String) "ZRes_str") + (register err (Fn [ZRes] String) "ZRes_err") + ) + + (deftype ZLevel + (NoCompression []) + (BestSpeed []) + (BestCompression []) + (DefaultCompression []) + ) + + (defmodule ZLevel + (defn to-int [l] + (match l + (NoCompression) 0 + (BestSpeed) 1 + (BestCompression) 9 + (DefaultCompression) -1))) + + (register inflate- (Fn [ZBytes] ZRes) "ZLib_inflate_c") + (defn inflate [s] + (let [r (inflate- s)] + (if (ZRes.ok? &r) + (Result.Success (ZRes.str r)) + (Result.Error (ZRes.err r))))) + + (register deflate- (Fn [String Int] ZRes) "ZLib_deflate_c") + (defn deflate-with [s level] + (let [r (deflate- s (ZLevel.to-int level))] + (if (ZRes.ok? &r) + (Result.Success (ZRes.bytes r)) + (Result.Error (ZRes.err r))))) + + (defn deflate [s] (deflate-with s (ZLevel.DefaultCompression))) +) diff --git a/zlib_helper.h b/zlib_helper.h new file mode 100644 index 0000000..09a6b48 --- /dev/null +++ b/zlib_helper.h @@ -0,0 +1,195 @@ +#include "zlib.h" + +// --- BEGIN ZRES HELPER --- + +typedef struct { + int len; + char* bytes; +} ZBytes; + +typedef struct { + int which; + union { + int err; + ZBytes* out; + }; +} ZRes; +#define ZRES_OK 0 +#define ZRES_ERR 1 + +bool ZRes_is_ok(ZRes* r) { + return r->which == ZRES_OK; +} + +ZBytes ZRes_bytes(ZRes r) { + assert(r.which == ZRES_OK); + return *r.out; +} + +String ZRes_str(ZRes r) { + assert(r.which == ZRES_OK); + String res = r.out->bytes; + free(r.out); + return res; +} + +static const char* errors[9] = { + "Version Error", + "Buffer Error", + "Memory Error", + "Data Error", + "Stream Error", + "errno", + NULL, + "Stream Ended", + "Need Dict", +}; + +char* ZRes_err(ZRes r) { + assert(r.which == ZRES_ERR); + // +6 because zlib errors are contiguous, starting at -6 + char* x = (char*)errors[(r.err)+6]; + return String_copy(&x); +} + +// --- END ZRES HELPER --- + +// this code is mostly from the zlib usage example: https://www.zlib.net/zlib_how.html + +#define CHUNK 16384 + +#define min(a,b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a < _b ? _a : _b; }) + +ZRes ZLib_inflate_c(ZBytes b) { + int ret; + ZRes res; + unsigned have; + z_stream strm; + unsigned char in[CHUNK]; + unsigned char out[CHUNK]; + int offs = 0; + int len = b.len; + char* source = b.bytes; + ZBytes* bytes = malloc(sizeof(ZBytes)); + bytes->bytes = NULL; + bytes->len = 0; + + /* allocate inflate state */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = 0; + strm.next_in = Z_NULL; + ret = inflateInit(&strm); + if (ret != Z_OK) goto err; + + /* decompress until deflate stream ends or end of file */ + do { + strm.avail_in = min(CHUNK, len-offs); + if (strm.avail_in <= 0) break; + memcpy(in, source+offs, strm.avail_in); + offs += strm.avail_in; + strm.next_in = in; + /* run inflate() on input until output buffer not full */ + do { + strm.avail_out = CHUNK; + strm.next_out = out; + ret = inflate(&strm, Z_NO_FLUSH); + assert(ret != Z_STREAM_ERROR); /* state not clobbered */ + switch (ret) { + case Z_NEED_DICT: + ret = Z_DATA_ERROR; /* and fall through */ + case Z_DATA_ERROR: + case Z_MEM_ERROR: + (void)inflateEnd(&strm); + goto err; + } + + have = CHUNK - strm.avail_out; + bytes->bytes = realloc(bytes->bytes, (bytes->len)+have); + memcpy((bytes->bytes)+(bytes->len), out, have); + bytes->len += have; + } while (strm.avail_out == 0); + /* done when inflate() says it’s done */ + } while (ret != Z_STREAM_END); + + /* clean up and return */ + (void)inflateEnd(&strm); + if (ret == Z_STREAM_END) { + res.which = ZRES_OK; + res.out = bytes; + return res; + } + + /* we didn’t succeed, we fail */ + ret = Z_DATA_ERROR; +err: + res.which = ZRES_ERR; + res.err = ret; + if (bytes->bytes) free(bytes->bytes); + free(bytes); + return res; +} + +ZRes ZLib_deflate_c(char* s, int level) { + int ret, flush; + unsigned have; + z_stream strm; + ZRes res; + unsigned char in[CHUNK]; + unsigned char out[CHUNK]; + int offs = 0; + int len = strlen(s); + ZBytes* bytes = malloc(sizeof(ZBytes)); + bytes->bytes = NULL; + bytes->len = 0; + + /* allocate deflate state */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + ret = deflateInit(&strm, level); + if (ret != Z_OK) goto err; + + /* compress until end of file */ + do { + strm.avail_in = min(CHUNK, len-offs); + if (strm.avail_in <= 0) break; + memcpy(in, s+offs, strm.avail_in); + offs += strm.avail_in; + flush = offs >= len-1 ? Z_FINISH : Z_NO_FLUSH; + strm.next_in = in; + + /* run deflate() on input until output buffer not full, finish + compression if all of source has been read in */ + do { + strm.avail_out = CHUNK; + strm.next_out = out; + ret = deflate(&strm, flush); /* no bad return value */ + assert(ret != Z_STREAM_ERROR); /* state not clobbered */ + + have = CHUNK - strm.avail_out; + bytes->bytes = realloc(bytes->bytes, (bytes->len)+have); + memcpy((bytes->bytes)+(bytes->len), out, have); + bytes->len += have; + } while (strm.avail_out == 0); + assert(strm.avail_in == 0); /* all input will be used */ + /* done when last data in file processed */ + } while (flush != Z_FINISH); + assert(ret == Z_STREAM_END); /* stream will be complete */ + + /* clean up and return */ + (void)deflateEnd(&strm); + res.which = ZRES_OK; + res.out = bytes; + return res; +err: + res.which = ZRES_ERR; + res.err = ret; + if (bytes->bytes) free(bytes->bytes); + free(bytes); + return res; +}