aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDima Krasner <dima@dimakrasner.com>2015-08-24 00:04:55 +0300
committerSteve Bennett <steveb@workware.net.au>2016-03-28 09:14:24 +1000
commit62a0e4db89bb9c5cc022ca4272aa0ea64af51b99 (patch)
tree6ddc7ba3ee6dfec218ee7136b190caf88d9a95dd
parentb4968142db02207a1632213c566c1b25cc5555ba (diff)
downloadjimtcl-62a0e4db89bb9c5cc022ca4272aa0ea64af51b99.zip
jimtcl-62a0e4db89bb9c5cc022ca4272aa0ea64af51b99.tar.gz
jimtcl-62a0e4db89bb9c5cc022ca4272aa0ea64af51b99.tar.bz2
zlib: add zlib bindings
Including documentation and tests
-rw-r--r--auto.def3
-rw-r--r--jim-zlib.c315
-rw-r--r--jim_tcl.txt25
-rw-r--r--tests/zlib.test119
4 files changed, 462 insertions, 0 deletions
diff --git a/auto.def b/auto.def
index 4625b63..18d8a9f 100644
--- a/auto.def
+++ b/auto.def
@@ -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