initial
This commit is contained in:
7
README.md
Normal file
7
README.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# zlib.carp
|
||||||
|
|
||||||
|
is a high-level wrapper around [zlib](https://zlib.net/).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The `ZLib` module provides only two functions
|
26
tests/zlib.carp
Normal file
26
tests/zlib.carp
Normal file
@@ -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"
|
||||||
|
)
|
||||||
|
)
|
50
zlib.carp
Normal file
50
zlib.carp
Normal file
@@ -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)))
|
||||||
|
)
|
195
zlib_helper.h
Normal file
195
zlib_helper.h
Normal file
@@ -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;
|
||||||
|
}
|
Reference in New Issue
Block a user