diff options
author | Dima Krasner <dima@dimakrasner.com> | 2015-08-24 00:04:55 +0300 |
---|---|---|
committer | Steve Bennett <steveb@workware.net.au> | 2016-03-28 09:14:24 +1000 |
commit | 62a0e4db89bb9c5cc022ca4272aa0ea64af51b99 (patch) | |
tree | 6ddc7ba3ee6dfec218ee7136b190caf88d9a95dd | |
parent | b4968142db02207a1632213c566c1b25cc5555ba (diff) | |
download | jimtcl-62a0e4db89bb9c5cc022ca4272aa0ea64af51b99.zip jimtcl-62a0e4db89bb9c5cc022ca4272aa0ea64af51b99.tar.gz jimtcl-62a0e4db89bb9c5cc022ca4272aa0ea64af51b99.tar.bz2 |
zlib: add zlib bindings
Including documentation and tests
-rw-r--r-- | auto.def | 3 | ||||
-rw-r--r-- | jim-zlib.c | 315 | ||||
-rw-r--r-- | jim_tcl.txt | 25 | ||||
-rw-r--r-- | tests/zlib.test | 119 |
4 files changed, 462 insertions, 0 deletions
@@ -54,6 +54,7 @@ options { mk - Interface to Metakit tclprefix - Support for the tcl::prefix command sqlite3 - Interface to sqlite3 + zlib - Interface to zlib win32 - Interface to win32 } with-out-jim-ext: {without-ext:"default|ext1 ext2 ..."} => { @@ -252,6 +253,7 @@ dict set extdb attrs { sdl { optional } signal { static } sqlite3 { optional } + zlib { optional } stdlib { tcl static } syslog {} tclcompat { tcl static } @@ -281,6 +283,7 @@ dict set extdb info { } signal { check {[have-feature sigaction] && [have-feature vfork]} } sqlite3 { check {[cc-check-function-in-lib sqlite3_prepare_v2 sqlite3]} libdep lib_sqlite3_prepare_v2 } + zlib { check {[cc-check-function-in-lib deflate z]} libdep lib_deflate } syslog { check {[have-feature syslog]} } tree { dep oo } win32 { check {[have-feature windows]} } diff --git a/jim-zlib.c b/jim-zlib.c new file mode 100644 index 0000000..ea8b6dc --- /dev/null +++ b/jim-zlib.c @@ -0,0 +1,315 @@ +/* + * Jim - zlib bindings + * + * Copyright 2015, 2016 Dima Krasner <dima@dimakrasner.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE JIM TCL PROJECT ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * JIM TCL PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation + * are those of the authors and should not be interpreted as representing + * official policies, either expressed or implied, of the Jim Tcl Project. + */ + +#include <zlib.h> + +#include <jim.h> +#include <jim-subcmd.h> + +#define _PASTE(x) # x +#define PASTE(x) _PASTE(x) + +#define WBITS_GZIP (MAX_WBITS | 16) +/* use small 64K chunks if no size was specified during decompression, to reduce memory consumption */ +#define DEF_DECOMPRESS_BUFSIZ (64 * 1024) + +static int Jim_Crc32(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + long init; + const char *in; + int len; + + if (argc == 1) { + init = crc32(0L, Z_NULL, 0); + } else { + if (Jim_GetLong(interp, argv[1], &init) != JIM_OK) { + return JIM_ERR; + } + } + + in = Jim_GetString(argv[0], &len); + Jim_SetResultInt(interp, crc32((uLong)init, (const Bytef *)in, (uInt)len) & 0xFFFFFFFF); + + return JIM_OK; +} + +static int Jim_Compress(Jim_Interp *interp, const char *in, int len, long level, int wbits) +{ + z_stream strm = {0}; + Bytef *buf; + + if ((level != Z_DEFAULT_COMPRESSION) && ((level < Z_NO_COMPRESSION) || (level > Z_BEST_COMPRESSION))) { + Jim_SetResultString(interp, "level must be 0 to 9", -1); + return JIM_ERR; + } + + if (deflateInit2(&strm, level, Z_DEFLATED, wbits, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK) { + return JIM_ERR; + } + + strm.avail_out = deflateBound(&strm, (uLong)len); + if (strm.avail_out > INT_MAX) { + deflateEnd(&strm); + return JIM_ERR; + } + buf = (Bytef *)Jim_Alloc((int)strm.avail_out); + strm.next_out = buf; + strm.next_in = (Bytef *)in; + strm.avail_in = (uInt)len; + + /* always compress in one pass - the return value holds the entire + * decompressed data anyway, so there's no reason to do chunked + * decompression */ + if (deflate(&strm, Z_FINISH) != Z_STREAM_END) { + Jim_Free(strm.next_out); + deflateEnd(&strm); + return JIM_ERR; + } + + deflateEnd(&strm); + + if (strm.total_out > INT_MAX) { + Jim_Free(strm.next_out); + return JIM_ERR; + } + + Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, (char *)buf, (int)strm.total_out)); + return JIM_OK; +} + +static int Jim_Deflate(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + long level = Z_DEFAULT_COMPRESSION; + const char *in; + int len; + + if (argc != 1) { + if (Jim_GetLong(interp, argv[1], &level) != JIM_OK) { + return JIM_ERR; + } + } + + in = Jim_GetString(argv[0], &len); + return Jim_Compress(interp, in, len, level, -MAX_WBITS); +} + +static int Jim_Gzip(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + long level = Z_DEFAULT_COMPRESSION; + const char *in; + int len; + + if (argc == 3) { + if (!Jim_CompareStringImmediate(interp, argv[1], "-level")) { + Jim_WrongNumArgs(interp, 0, argv, "data ?-level level?"); + return JIM_ERR; + } + + if (Jim_GetLong(interp, argv[2], &level) != JIM_OK) { + Jim_WrongNumArgs(interp, 0, argv, "data ?-level level?"); + return JIM_ERR; + } + + } + else if (argc != 1) { + Jim_WrongNumArgs(interp, 0, argv, "data ?-level level?"); + return JIM_ERR; + } + + in = Jim_GetString(argv[0], &len); + return Jim_Compress(interp, in, len, level, WBITS_GZIP); +} + +static int Jim_Decompress(Jim_Interp *interp, const char *in, int len, long bufsiz, int wbits) +{ + z_stream strm = {0}; + void *buf; + Jim_Obj *out; + int ret; + + if ((bufsiz <= 0) || (bufsiz > INT_MAX)) { + Jim_SetResultString(interp, "buffer size must be 0 to "PASTE(INT_MAX), -1); + return JIM_ERR; + } + + if (inflateInit2(&strm, wbits) != Z_OK) { + return JIM_ERR; + } + + /* allocate a buffer - decompression is done in chunks, into this buffer; + * when the decompressed data size is given, decompression is faster because + * it's done in one pass, with less memcpy() overhead */ + buf = Jim_Alloc((int)bufsiz); + + out = Jim_NewEmptyStringObj(interp); + Jim_IncrRefCount(out); + + strm.next_in = (Bytef*)in; + strm.avail_in = (uInt)len; + do { + do { + strm.next_out = buf; + strm.avail_out = (uInt)bufsiz; + + ret = inflate(&strm, Z_NO_FLUSH); + switch (ret) { + case Z_OK: + case Z_STREAM_END: + /* append each chunk to the output object */ + Jim_AppendString(interp, out, buf, (int)(bufsiz - (long)strm.avail_out)); + break; + + default: + Jim_DecrRefCount(interp, out); + Jim_Free(buf); + inflateEnd(&strm); + if (strm.msg != NULL) + Jim_SetResultString(interp, strm.msg, -1); + return JIM_ERR; + } + } while (strm.avail_out == 0); + } while (ret != Z_STREAM_END); + + /* free memory used for decompression before we assign the return value */ + Jim_Free(buf); + inflateEnd(&strm); + + Jim_SetResult(interp, out); + Jim_DecrRefCount(interp, out); + + return JIM_OK; +} + +static int Jim_Inflate(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + long bufsiz = DEF_DECOMPRESS_BUFSIZ; + const char *in; + int len; + + if (argc != 1) { + if (Jim_GetLong(interp, argv[1], &bufsiz) != JIM_OK) { + return JIM_ERR; + } + + if ((bufsiz <= 0) || (bufsiz > INT_MAX)) { + Jim_SetResultString(interp, "buffer size must be 0 to "PASTE(INT_MAX), -1); + return JIM_ERR; + } + } + + in = Jim_GetString(argv[0], &len); + return Jim_Decompress(interp, in, len, bufsiz, -MAX_WBITS); +} + +static int Jim_Gunzip(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + long bufsiz = DEF_DECOMPRESS_BUFSIZ; + const char *in; + int len; + + if (argc == 3) { + if (!Jim_CompareStringImmediate(interp, argv[1], "-buffersize")) { + Jim_WrongNumArgs(interp, 0, argv, "data ?-buffersize size?"); + return JIM_ERR; + } + + if (Jim_GetLong(interp, argv[2], &bufsiz) != JIM_OK) { + Jim_WrongNumArgs(interp, 0, argv, "data ?-buffersize size?"); + return JIM_ERR; + } + + } + else if (argc != 1) { + Jim_WrongNumArgs(interp, 0, argv, "data ?-buffersize size?"); + return JIM_ERR; + } + + in = Jim_GetString(argv[0], &len); + return Jim_Decompress(interp, in, len, bufsiz, WBITS_GZIP); +} + +static const jim_subcmd_type zlib_command_table[] = { + { "crc32", + "data ?startValue?", + Jim_Crc32, + 1, + 2, + /* Description: Calculates the CRC32 checksum of a string */ + }, + { "deflate", + "string ?level?", + Jim_Deflate, + 1, + 2, + /* Description: Compresses a string and outputs a raw, zlib-compressed stream */ + }, + { "gzip", + "data ?-level level?", + Jim_Gzip, + 1, + 3, + /* Description: Compresses a string and outputs a gzip-compressed stream */ + }, + { "inflate", + "data ?bufferSize?", + Jim_Inflate, + 1, + 2, + /* Description: Decompresses a raw, zlib-compressed stream */ + }, + { "gunzip", + "data ?-buffersize size?", + Jim_Gunzip, + 1, + 3, + /* Description: Decompresses a gzip-compressed stream */ + }, + { NULL } +}; + +static int JimZlibCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + return Jim_CallSubCmd(interp, Jim_ParseSubCmd(interp, zlib_command_table, argc, argv), argc, argv); +} + +int Jim_zlibInit(Jim_Interp *interp) +{ + if (Jim_PackageProvide(interp, "zlib", "1.0", JIM_ERRMSG)) { + return JIM_ERR; + } + + Jim_CreateCommand(interp, "zlib", JimZlibCmd, 0, 0); + + return JIM_OK; +} diff --git a/jim_tcl.txt b/jim_tcl.txt index e555464..68cd9ee 100644 --- a/jim_tcl.txt +++ b/jim_tcl.txt @@ -55,6 +55,7 @@ Changes between 0.76 and 0.77 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1. Add support for `aio sync` 2. Add SSL and TLS support in aio +3. Added `zlib` Changes between 0.75 and 0.76 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -4767,6 +4768,30 @@ The optional 'pack' extension provides commands to encode and decode binary stri access outside the length of the value will return 0 for integer types, 0.0 for floating point types or the empty string for the string type. +zlib +~~~~ +The optional 'zlib' extension provides a is a Tcl-compatible subset of the `zlib` command. + ++*crc32* 'data' '?startValue?'+:: + Returns the CRC32 checksum of a buffer. Optionally, an initial value may be specified; this is most useful + for calculating the checksum of chunked data read from a stream (for instance, a pipe). + ++*deflate* 'string' '?level?'+:: + Compresses a buffer and outputs a raw, Deflate-compressed stream. Optionally, a compression level (1-9) may + be specified to choose the desired speed vs. compression rate ratio. + ++*inflate* 'data' '?bufferSize?'+:: + Decompresses a raw, Deflate-compressed stream. When the uncompressed data size is known and specified, memory + allocation is more efficient. Otherwise, decomperssion is chunked and therefore slower. + ++*gzip* 'string' '?-level level?'+:: + Compresses a buffer and adds a gzip header. + ++*gunzip* 'data' '?-buffersize size?'+:: + Decompresses a gzip-compressed buffer. Decompression is chunked, with a default, small buffer size of 64K + which guarantees lower memory footprint at the cost of speed. It is recommended to use a bigger size, on + systems without a severe memory constraint. + binary ~~~~~~ The optional, pure-Tcl 'binary' extension provides the Tcl-compatible `binary scan` and `binary format` diff --git a/tests/zlib.test b/tests/zlib.test new file mode 100644 index 0000000..e448ba1 --- /dev/null +++ b/tests/zlib.test @@ -0,0 +1,119 @@ +# The file tests the jim-zlib.c file; based on tests/zlib.test from Tcl 8.6. +# +# This file contains a collection of tests for one or more of the Tcl built-in +# commands. Sourcing this file into Tcl runs the tests and generates output +# for errors. No output means no errors were found. +# +# Copyright (c) 1996-1998 by Sun Microsystems, Inc. +# Copyright (c) 1998-1999 by Scriptics Corporation. +# +# See the file "license.terms" for information on usage and redistribution of +# this file, and for a DISCLAIMER OF ALL WARRANTIES. + +source [file dirname [info script]]/testing.tcl + +testConstraint zlib [llength [info commands zlib]] + +test zlib-1.1 {zlib deflate usage 1} -constraints zlib -returnCodes error -body { + zlib deflate +} -result {wrong # args: should be "zlib deflate string ?level?"} + +test zlib-1.2 {zlib deflate usage 2} -constraints zlib -returnCodes error -body { + zlib deflate a b +} -result {expected integer but got "b"} + +test zlib-1.3 {zlib deflate usage 3} -constraints zlib -returnCodes error -body { + zlib deflate a b c +} -result {wrong # args: should be "zlib deflate string ?level?"} + +test zlib-1.4 {zlib inflate usage 1} -constraints zlib -returnCodes error -body { + zlib inflate +} -result {wrong # args: should be "zlib inflate data ?bufferSize?"} + +test zlib-1.5 {zlib inflate usage 2} -constraints zlib -returnCodes error -body { + zlib inflate afdfdfdsfdsfsd +} -result {invalid stored block lengths} + +test zlib-1.6 {zlib inflate usage 3} -constraints zlib -returnCodes error -body { + zlib inflate afdfdfdsfdsfsd f +} -result {expected integer but got "f"} + +test zlib-1.7 {zlib inflate usage 4} -constraints zlib -returnCodes error -body { + zlib inflate afdfdfdsfdsfsd 0 +} -result {buffer size must be 0 to 2147483647} + +test zlib-2.1 {zlib deflate/inflate} zlib { + zlib inflate [zlib deflate abcdefghijklm] +} abcdefghijklm + +test zlib-2.2 {zlib deflate/inflate level and size known} zlib { + zlib inflate [zlib deflate abcdefghijklm 9] 13 +} abcdefghijklm + +test zlib-2.3 {zlib deflate/inflate bad size} -constraints zlib -returnCodes error -body { + zlib inflate [zlib deflate abcdefghijklm 9] 0 +} -result {buffer size must be 0 to 2147483647} + +test zlib-2.4 {zlib deflate/inflate wrong size} zlib { + zlib inflate [zlib deflate abcdefghijklm] 6 +} abcdefghijklm + +test zlib-3.1 {zlib gunzip usage 1} -constraints zlib -returnCodes error -body { + zlib gunzip +} -result {wrong # args: should be "zlib gunzip data ?-buffersize size?"} + +test zlib-3.2 {zlib gunzip usage 2} -constraints zlib -returnCodes error -body { + zlib gunzip aaa +} -result {incorrect header check} + +test zlib-3.3 {zlib gunzip usage 3} -constraints zlib -returnCodes error -body { + zlib gunzip aaa 4 +} -result {wrong # args: should be "data ?-buffersize size?"} + +test zlib-3.4 {zlib gunzip usage 4} -constraints zlib -returnCodes error -body { + zlib gunzip aaa -buffersize +} -result {wrong # args: should be "data ?-buffersize size?"} + +test zlib-3.5 {zlib gunzip usage 5} -constraints zlib -returnCodes error -body { + zlib gunzip aaa -buffersize a +} -result {wrong # args: should be "data ?-buffersize size?"} + +test zlib-3.6 {zlib gunzip usage 5} -constraints zlib -returnCodes error -body { + zlib gunzip aaa -buffersize a +} -result {wrong # args: should be "data ?-buffersize size?"} + +test zlib-3.7 {zlib gunzip usage 6} -constraints zlib -returnCodes error -body { + zlib gunzip aaa -buffersize 0 +} -result {buffer size must be 0 to 2147483647} + +test zlib-3.8 {zlib gzip usage 1} -constraints zlib -returnCodes error -body { + zlib gzip +} -result {wrong # args: should be "zlib gzip data ?-level level?"} + +test zlib-3.9 {zlib gzip usage 2} -constraints zlib -returnCodes error -body { + zlib gzip aa 9 +} -result {wrong # args: should be "data ?-level level?"} + +test zlib-3.10 {zlib gzip usage 3} -constraints zlib -returnCodes error -body { + zlib gzip -level a +} -result {wrong # args: should be "data ?-level level?"} + +test zlib-3.11 {zlib gzip usage 4} -constraints zlib -returnCodes error -body { + zlib gzip -level 9 a +} -result {wrong # args: should be "data ?-level level?"} + +test zlib-4.1 {zlib gzip/gunzip} zlib { + zlib gunzip [zlib gzip abcdefghijklm] +} abcdefghijklm + +test zlib-4.2 {zlib gzip/gunzip level and chunk size} zlib { + zlib gunzip [zlib gzip abcdefghijklm -level 9] -buffersize 128 +} abcdefghijklm + +test zlib-5.1 {zlib crc32} zlib { + format %x [expr {[zlib crc32 abcdeabcdeabcdeabcdeabcdeabcde] & 0xffffffff}] +} 6f73e901 + +test zlib-5.2 {zlib crc32} zlib { + format %x [expr {[zlib crc32 abcdeabcdeabcdeabcdeabcdeabcde 42] & 0xffffffff}] +} ce1c4914 |