diff --git a/.gitignore b/.gitignore index 302bf46..9f7b2a5 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ *.o *.so + +build/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..7a336c4 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,29 @@ +language: erlang + +env: + global: + - PLATFORM=linux + - LUAROCKS_VER=2.2.0 + matrix: + - LUA=lua5.1 LUA_SFX= + - LUA=lua5.2 LUA_SFX= + - LUA=luajit LUA_SFX=jit + - LUA=lua5.3 LUA_SFX= + +before_install: + - bash -x .travis/setup_lua.sh + - sudo pip install cpp-coveralls + +install: + - sudo luarocks make rockspec/lua-cmsgpack-scm-1.rockspec CFLAGS="-O2 -fPIC -ftest-coverage -fprofile-arcs" LIBFLAG="-shared --coverage" + +script: + - lua$LUA_SFX test.lua + +after_success: + - coveralls + +notifications: + email: + on_success: change + on_failure: always diff --git a/.travis/platform.sh b/.travis/platform.sh new file mode 100644 index 0000000..4a3af0d --- /dev/null +++ b/.travis/platform.sh @@ -0,0 +1,15 @@ +if [ -z "$PLATFORM" ]; then + PLATFORM=$TRAVIS_OS_NAME; +fi + +if [ "$PLATFORM" == "osx" ]; then + PLATFORM="macosx"; +fi + +if [ -z "$PLATFORM" ]; then + if [ "$(uname)" == "Linux" ]; then + PLATFORM="linux"; + else + PLATFORM="macosx"; + fi; +fi diff --git a/.travis/setup_lua.sh b/.travis/setup_lua.sh new file mode 100644 index 0000000..373e24d --- /dev/null +++ b/.travis/setup_lua.sh @@ -0,0 +1,101 @@ +#! /bin/bash + +# A script for setting up environment for travis-ci testing. +# Sets up Lua and Luarocks. +# LUA must be "lua5.1", "lua5.2" or "luajit". +# luajit2.0 - master v2.0 +# luajit2.1 - master v2.1 + +LUAJIT_BASE="LuaJIT-2.0.3" + +source .travis/platform.sh + +LUAJIT="no" + +if [ "$PLATFORM" == "macosx" ]; then + if [ "$LUA" == "luajit" ]; then + LUAJIT="yes"; + fi + if [ "$LUA" == "luajit2.0" ]; then + LUAJIT="yes"; + fi + if [ "$LUA" == "luajit2.1" ]; then + LUAJIT="yes"; + fi; +elif [ "$(expr substr $LUA 1 6)" == "luajit" ]; then + LUAJIT="yes"; +fi + +if [ "$LUAJIT" == "yes" ]; then + + if [ "$LUA" == "luajit" ]; then + curl http://luajit.org/download/$LUAJIT_BASE.tar.gz | tar xz; + else + git clone http://luajit.org/git/luajit-2.0.git $LUAJIT_BASE; + fi + + cd $LUAJIT_BASE + + if [ "$LUA" == "luajit2.1" ]; then + git checkout v2.1; + fi + + make && sudo make install + + if [ "$LUA" == "luajit2.1" ]; then + sudo ln -s /usr/local/bin/luajit-2.1.0-alpha /usr/local/bin/luajit + sudo ln -s /usr/local/bin/luajit /usr/local/bin/lua; + else + sudo ln -s /usr/local/bin/luajit /usr/local/bin/lua; + fi; + +else + if [ "$LUA" == "lua5.1" ]; then + curl http://www.lua.org/ftp/lua-5.1.5.tar.gz | tar xz + cd lua-5.1.5; + elif [ "$LUA" == "lua5.2" ]; then + curl http://www.lua.org/ftp/lua-5.2.3.tar.gz | tar xz + cd lua-5.2.3; + elif [ "$LUA" == "lua5.3" ]; then + curl http://www.lua.org/work/lua-5.3.0-beta.tar.gz | tar xz + cd lua-5.3.0-beta; + fi + sudo make $PLATFORM install; +fi + +cd $TRAVIS_BUILD_DIR; + +LUAROCKS_BASE=luarocks-$LUAROCKS + +# curl http://luarocks.org/releases/$LUAROCKS_BASE.tar.gz | tar xz + +git clone https://github.com/keplerproject/luarocks.git $LUAROCKS_BASE +cd $LUAROCKS_BASE + +git checkout v$LUAROCKS + +if [ "$LUA" == "luajit" ]; then + ./configure --lua-suffix=jit --with-lua-include=/usr/local/include/luajit-2.0; +elif [ "$LUA" == "luajit2.0" ]; then + ./configure --lua-suffix=jit --with-lua-include=/usr/local/include/luajit-2.0; +elif [ "$LUA" == "luajit2.1" ]; then + ./configure --lua-suffix=jit --with-lua-include=/usr/local/include/luajit-2.1; +else + ./configure; +fi + +make build && sudo make install + +cd $TRAVIS_BUILD_DIR + +rm -rf $LUAROCKS_BASE + +if [ "$LUAJIT" == "yes" ]; then + rm -rf $LUAJIT_BASE; +elif [ "$LUA" == "lua5.1" ]; then + rm -rf lua-5.1.5; +elif [ "$LUA" == "lua5.2" ]; then + rm -rf lua-5.2.3; +elif [ "$LUA" == "lua5.3" ]; then + rm -rf lua-5.3.0-beta; +fi diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..6092565 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,45 @@ +# If Lua is installed in a non-standard location, please set the LUA_DIR +# environment variable to point to prefix for the install. Eg: +# Unix: export LUA_DIR=/home/user/pkg +# Windows: set LUA_DIR=c:\lua51 + +project(lua-cmsgpack C) +cmake_minimum_required(VERSION 2.6) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release CACHE STRING + "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." + FORCE) +endif() + +find_package(Lua51 REQUIRED) +include_directories(${LUA_INCLUDE_DIR}) + +if(APPLE) + set(CMAKE_SHARED_MODULE_CREATE_C_FLAGS + "${CMAKE_SHARED_MODULE_CREATE_C_FLAGS} -undefined dynamic_lookup") +endif() + +if(WIN32) + # Win32 modules need to be linked to the Lua library. + set(_MODULE_LINK ${LUA_LIBRARY} ${_MODULE_LINK}) + set(_lua_module_dir "${_lua_lib_dir}") +else() + set(_lua_module_dir "${_lua_lib_dir}/lua/5.1") +endif() + +option(Build32Bit "Build 32-bit Library" OFF) + +set(CMAKE_C_FLAGS "-O2 -g -ggdb -Wall -pedantic -std=c99") +add_library(cmsgpack MODULE lua_cmsgpack.c) +set_target_properties(cmsgpack PROPERTIES PREFIX "") + +if(Build32Bit) + set_target_properties(cmsgpack + PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32") +endif() + +target_link_libraries(cmsgpack ${_MODULE_LINK}) +install(TARGETS cmsgpack DESTINATION "${_lua_module_dir}") + +# vi:ai et sw=4 ts=4: diff --git a/README.md b/README.md index 5404856..417337a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ README for lua-cmsgpack.c === Lua-cmsgpack is a [MessagePack](http://msgpack.org) implementation and bindings for -Lua 5.1/5.2 in a self contained C file without external dependencies. +Lua 5.1/5.2/5.3 in a self contained C file without external dependencies. This library is open source software licensed under the BSD two-clause license. @@ -33,10 +33,33 @@ interpreter: USAGE --- -The exported API is very simple, consisting in two functions: +The exported API is very simple, consisting of four functions: - msgpack = cmsgpack.pack(lua_object) - lua_object = cmsgpack.unpack(msgpack) +Basic API: + + msgpack = cmsgpack.pack(lua_object1, lua_object2, ..., lua_objectN) + lua_object1, lua_object2, ..., lua_objectN = cmsgpack.unpack(msgpack) + +Detailed API giving you more control over unpacking multiple values: + + resume_offset, lua_object1 = cmsgpack.unpack_one(msgpack) + resume_offset1, lua_object2 = cmsgpack.unpack_one(msgpack, resume_offset) + ... + -1, lua_objectN = cmsgpack.unpack_one(msgpack, resume_offset_previous) + + resume_offset, lua_object1, lua_object2 = cmsgpack.unpack_limit(msgpack, 2) + resume_offset2, lua_object3 = cmsgpack.unpack_limit(msgpack, 1, resume_offset1) + +Functions: + + - `pack(arg1, arg2, ..., argn)` - pack any number of lua objects into one msgpack stream. returns: msgpack + - `unpack(msgpack)` - unpack all objects in msgpack to individual return values. returns: object1, object2, ..., objectN + - `unpack_one(msgpack); unpack_one(msgpack, offset)` - unpacks the first object after offset. returns: offset, object + - `unpack_limit(msgpack, limit); unpack_limit(msgpack, limit, offset)` - unpacks the first `limit` objects and returns: offset, object1, objet2, ..., objectN (up to limit, but may return fewer than limit if not that many objects remain to be unpacked) + +When you reach the end of your input stream with `unpack_one` or `unpack_limit`, an offset of `-1` is returned. + +You may `require "msgpack"` or you may `require "msgpack.safe"`. The safe version returns errors as (nil, errstring). However because of the nature of Lua numerical and table type a few behavior of the library must be well understood to avoid problems: @@ -50,6 +73,23 @@ maps. * When a Lua number is converted to float or double, the former is preferred if there is no loss of precision compared to the double representation. * When a MessagePack big integer (64 bit) is converted to a Lua number it is possible that the resulting number will not represent the original number but just an approximation. This is unavoidable because the Lua numerical type is usually a double precision floating point type. +TESTING +--- + +Build and test: + + mkdir build; cd build + cmake .. + make + lua ../test.lua + +You can build a 32-bit module on a 64-bit platform with: + + mkdir build; cd build + cmake -DBuild32Bit=ON .. + make + lua ../test.lua + NESTED TABLES --- Nested tables are handled correctly up to `LUACMSGPACK_MAX_NESTING` levels of diff --git a/lua_cmsgpack.c b/lua_cmsgpack.c index f7325e7..786daab 100644 --- a/lua_cmsgpack.c +++ b/lua_cmsgpack.c @@ -7,13 +7,43 @@ #include "lua.h" #include "lauxlib.h" -#define LUACMSGPACK_VERSION "lua-cmsgpack 0.3.1" +#define LUACMSGPACK_NAME "cmsgpack" +#define LUACMSGPACK_SAFE_NAME "cmsgpack_safe" +#define LUACMSGPACK_VERSION "lua-cmsgpack 0.4.0" #define LUACMSGPACK_COPYRIGHT "Copyright (C) 2012, Salvatore Sanfilippo" #define LUACMSGPACK_DESCRIPTION "MessagePack C implementation for Lua" -#define LUACMSGPACK_MAX_NESTING 16 /* Max tables nesting. */ +/* Allows a preprocessor directive to override MAX_NESTING */ +#ifndef LUACMSGPACK_MAX_NESTING + #define LUACMSGPACK_MAX_NESTING 16 /* Max tables nesting. */ +#endif + +#if (_XOPEN_SOURCE >= 600 || _ISOC99_SOURCE || _POSIX_C_SOURCE >= 200112L) + #define IS_FINITE(x) isfinite(x) +#else + #define IS_FINITE(x) ((x) == (x) && (x) + 1 > (x)) +#endif + +/* Check if float or double can be an integer without loss of precision */ +#define IS_INT_TYPE_EQUIVALENT(x, T) (IS_FINITE(x) && (T)(x) == (x)) + +#define IS_INT64_EQUIVALENT(x) IS_INT_TYPE_EQUIVALENT(x, int64_t) +#define IS_INT_EQUIVALENT(x) IS_INT_TYPE_EQUIVALENT(x, int) + +/* If size of pointer is equal to a 4 byte integer, we're on 32 bits. */ +#if UINTPTR_MAX == UINT_MAX + #define BITS_32 1 +#else + #define BITS_32 0 +#endif -/* ============================================================================== +#if BITS_32 + #define lua_pushunsigned(L, n) lua_pushnumber(L, n) +#else + #define lua_pushunsigned(L, n) lua_pushinteger(L, n) +#endif + +/* ============================================================================= * MessagePack implementation and bindings for Lua 5.1/5.2. * Copyright(C) 2012 Salvatore Sanfilippo * @@ -30,23 +60,26 @@ * 20-Feb-2012 (ver 0.2.1): Minor bug fixing. * 20-Feb-2012 (ver 0.3.0): Module renamed lua-cmsgpack (was lua-msgpack). * 04-Apr-2014 (ver 0.3.1): Lua 5.2 support and minor bug fix. - * ============================================================================ */ + * 07-Apr-2014 (ver 0.4.0): Multiple pack/unpack, lua allocator, efficiency. + * ========================================================================== */ -/* --------------------------- Endian conversion -------------------------------- - * We use it only for floats and doubles, all the other conversions are performed +/* -------------------------- Endian conversion -------------------------------- + * We use it only for floats and doubles, all the other conversions performed * in an endian independent fashion. So the only thing we need is a function - * that swaps a binary string if the arch is little endian (and left it untouched + * that swaps a binary string if arch is little endian (and left it untouched * otherwise). */ /* Reverse memory bytes if arch is little endian. Given the conceptual - * simplicity of the Lua build system we prefer to check for endianess at runtime. + * simplicity of the Lua build system we prefer check for endianess at runtime. * The performance difference should be acceptable. */ static void memrevifle(void *ptr, size_t len) { - unsigned char *p = ptr, *e = p+len-1, aux; + unsigned char *p = (unsigned char *)ptr, + *e = (unsigned char *)p+len-1, + aux; int test = 1; unsigned char *testp = (unsigned char*) &test; - if (testp[0] == 0) return; /* Big endian, nothign to do. */ + if (testp[0] == 0) return; /* Big endian, nothing to do. */ len /= 2; while(len--) { aux = *p; @@ -57,30 +90,44 @@ static void memrevifle(void *ptr, size_t len) { } } -/* ----------------------------- String buffer ---------------------------------- - * This is a simple implementation of string buffers. The only opereation +/* ---------------------------- String buffer ---------------------------------- + * This is a simple implementation of string buffers. The only operation * supported is creating empty buffers and appending bytes to it. * The string buffer uses 2x preallocation on every realloc for O(N) append * behavior. */ typedef struct mp_buf { + lua_State *L; unsigned char *b; size_t len, free; } mp_buf; -static mp_buf *mp_buf_new(void) { - mp_buf *buf = malloc(sizeof(*buf)); - +static void *mp_realloc(lua_State *L, void *target, size_t osize,size_t nsize) { + void *(*local_realloc) (void *, void *, size_t osize, size_t nsize) = NULL; + void *ud; + + local_realloc = lua_getallocf(L, &ud); + + return local_realloc(ud, target, osize, nsize); +} + +static mp_buf *mp_buf_new(lua_State *L) { + mp_buf *buf = NULL; + + /* Old size = 0; new size = sizeof(*buf) */ + buf = (mp_buf*)mp_realloc(L, NULL, 0, sizeof(*buf)); + + buf->L = L; buf->b = NULL; buf->len = buf->free = 0; return buf; } -void mp_buf_append(mp_buf *buf, const unsigned char *s, size_t len) { +static void mp_buf_append(mp_buf *buf, const unsigned char *s, size_t len) { if (buf->free < len) { size_t newlen = buf->len+len; - buf->b = realloc(buf->b,newlen*2); + buf->b = (unsigned char*)mp_realloc(buf->L, buf->b, buf->len, newlen*2); buf->free = newlen; } memcpy(buf->b+buf->len,s,len); @@ -89,11 +136,11 @@ void mp_buf_append(mp_buf *buf, const unsigned char *s, size_t len) { } void mp_buf_free(mp_buf *buf) { - free(buf->b); - free(buf); + mp_realloc(buf->L, buf->b, buf->len, 0); /* realloc to 0 = free */ + mp_realloc(buf->L, buf, sizeof(*buf), 0); } -/* ------------------------------ String cursor ---------------------------------- +/* ---------------------------- String cursor ---------------------------------- * This simple data structure is used for parsing. Basically you create a cursor * using a string pointer and a length, then it is possible to access the * current string position with cursor->p, check the remaining length @@ -103,7 +150,7 @@ void mp_buf_free(mp_buf *buf) { * be used to report errors. */ #define MP_CUR_ERROR_NONE 0 -#define MP_CUR_ERROR_EOF 1 /* Not enough data to complete the opereation. */ +#define MP_CUR_ERROR_EOF 1 /* Not enough data to complete operation. */ #define MP_CUR_ERROR_BADFMT 2 /* Bad data format */ typedef struct mp_cur { @@ -112,22 +159,15 @@ typedef struct mp_cur { int err; } mp_cur; -static mp_cur *mp_cur_new(const unsigned char *s, size_t len) { - mp_cur *cursor = malloc(sizeof(*cursor)); - +static void mp_cur_init(mp_cur *cursor, const unsigned char *s, size_t len) { cursor->p = s; cursor->left = len; cursor->err = MP_CUR_ERROR_NONE; - return cursor; -} - -static void mp_cur_free(mp_cur *cursor) { - free(cursor); } #define mp_cur_consume(_c,_len) do { _c->p += _len; _c->left -= _len; } while(0) -/* When there is not enough room we set an error in the cursor and return, this +/* When there is not enough room we set an error in the cursor and return. This * is very common across the code so we have a macro to make the code look * a bit simpler. */ #define mp_cur_need(_c,_len) do { \ @@ -137,7 +177,7 @@ static void mp_cur_free(mp_cur *cursor) { } \ } while(0) -/* --------------------------- Low level MP encoding -------------------------- */ +/* ------------------------- Low level MP encoding -------------------------- */ static void mp_encode_bytes(mp_buf *buf, const unsigned char *s, size_t len) { unsigned char hdr[5]; @@ -300,7 +340,7 @@ static void mp_encode_map(mp_buf *buf, int64_t n) { mp_buf_append(buf,b,enclen); } -/* ----------------------------- Lua types encoding --------------------------- */ +/* --------------------------- Lua types encoding --------------------------- */ static void mp_encode_lua_string(lua_State *L, mp_buf *buf) { size_t len; @@ -315,13 +355,26 @@ static void mp_encode_lua_bool(lua_State *L, mp_buf *buf) { mp_buf_append(buf,&b,1); } +/* Lua 5.3 has a built in 64-bit integer type */ +static void mp_encode_lua_integer(lua_State *L, mp_buf *buf) { +#if (LUA_VERSION_NUM < 503) && BITS_32 + lua_Number i = lua_tonumber(L,-1); +#else + lua_Integer i = lua_tointeger(L,-1); +#endif + mp_encode_int(buf, (int64_t)i); +} + +/* Lua 5.2 and lower only has 64-bit doubles, so we need to + * detect if the double may be representable as an int + * for Lua < 5.3 */ static void mp_encode_lua_number(lua_State *L, mp_buf *buf) { lua_Number n = lua_tonumber(L,-1); - if (floor(n) != n) { - mp_encode_double(buf,(double)n); + if (IS_INT64_EQUIVALENT(n)) { + mp_encode_lua_integer(L, buf); } else { - mp_encode_int(buf,(int64_t)n); + mp_encode_double(buf,(double)n); } } @@ -350,7 +403,7 @@ static void mp_encode_lua_table_as_map(lua_State *L, mp_buf *buf, int level) { /* First step: count keys into table. No other way to do it with the * Lua API, we need to iterate a first time. Note that an alternative * would be to do a single run, and then hack the buffer to insert the - * map opcodes for message pack. Too hachish for this lib. */ + * map opcodes for message pack. Too hackish for this lib. */ lua_pushnil(L); while(lua_next(L,-2)) { lua_pop(L,1); /* remove value, keep key for next iteration. */ @@ -372,29 +425,43 @@ static void mp_encode_lua_table_as_map(lua_State *L, mp_buf *buf, int level) { * of keys from numerical keys from 1 up to N, with N being the total number * of elements, without any hole in the middle. */ static int table_is_an_array(lua_State *L) { - long count = 0, idx = 0; + int count = 0, max = 0; +#if LUA_VERSION_NUM < 503 lua_Number n; +#else + lua_Integer n; +#endif + + /* Stack top on function entry */ + int stacktop; + + stacktop = lua_gettop(L); lua_pushnil(L); while(lua_next(L,-2)) { /* Stack: ... key value */ lua_pop(L,1); /* Stack: ... key */ - if (lua_type(L,-1) != LUA_TNUMBER) goto not_array; - n = lua_tonumber(L,-1); - idx = n; - if (idx != n || idx < 1) goto not_array; + /* The <= 0 check is valid here because we're comparing indexes. */ +#if LUA_VERSION_NUM < 503 + if ((LUA_TNUMBER != lua_type(L,-1)) || (n = lua_tonumber(L, -1)) <= 0 || + !IS_INT_EQUIVALENT(n)) +#else + if (!lua_isinteger(L,-1) || (n = lua_tointeger(L, -1)) <= 0) +#endif + { + lua_settop(L, stacktop); + return 0; + } + max = (n > max ? n : max); count++; } /* We have the total number of elements in "count". Also we have - * the max index encountered in "idx". We can't reach this code + * the max index encountered in "max". We can't reach this code * if there are indexes <= 0. If you also note that there can not be - * repeated keys into a table, you have that if idx==count you are sure + * repeated keys into a table, you have that if max==count you are sure * that there are all the keys form 1 to count (both included). */ - return idx == count; - -not_array: - lua_pop(L,1); - return 0; + lua_settop(L, stacktop); + return max == count; } /* If the length operator returns non-zero, that is, there is at least @@ -409,6 +476,7 @@ static void mp_encode_lua_table(lua_State *L, mp_buf *buf, int level) { static void mp_encode_lua_null(lua_State *L, mp_buf *buf) { unsigned char b[1]; + (void)L; b[0] = 0xc0; mp_buf_append(buf,b,1); @@ -417,29 +485,65 @@ static void mp_encode_lua_null(lua_State *L, mp_buf *buf) { static void mp_encode_lua_type(lua_State *L, mp_buf *buf, int level) { int t = lua_type(L,-1); - /* Limit the encoding of nested tables to a specfiied maximum depth, so that + /* Limit the encoding of nested tables to a specified maximum depth, so that * we survive when called against circular references in tables. */ if (t == LUA_TTABLE && level == LUACMSGPACK_MAX_NESTING) t = LUA_TNIL; switch(t) { case LUA_TSTRING: mp_encode_lua_string(L,buf); break; case LUA_TBOOLEAN: mp_encode_lua_bool(L,buf); break; - case LUA_TNUMBER: mp_encode_lua_number(L,buf); break; + case LUA_TNUMBER: + #if LUA_VERSION_NUM < 503 + mp_encode_lua_number(L,buf); break; + #else + if (lua_isinteger(L, -1)) { + mp_encode_lua_integer(L, buf); + } else { + mp_encode_lua_number(L, buf); + } + break; + #endif case LUA_TTABLE: mp_encode_lua_table(L,buf,level); break; default: mp_encode_lua_null(L,buf); break; } lua_pop(L,1); } +/* + * Packs all arguments as a stream for multiple upacking later. + * Returns error if no arguments provided. + */ static int mp_pack(lua_State *L) { - mp_buf *buf = mp_buf_new(); + int nargs = lua_gettop(L); + int i; + mp_buf *buf; + + if (nargs == 0) + return luaL_argerror(L, 0, "MessagePack pack needs input."); - mp_encode_lua_type(L,buf,0); - lua_pushlstring(L,(char*)buf->b,buf->len); + buf = mp_buf_new(L); + for(i = 1; i <= nargs; i++) { + /* Copy argument i to top of stack for _encode processing; + * the encode function pops it from the stack when complete. */ + lua_pushvalue(L, i); + + mp_encode_lua_type(L,buf,0); + + lua_pushlstring(L,(char*)buf->b,buf->len); + + /* Reuse the buffer for the next operation by + * setting its free count to the total buffer size + * and the current position to zero. */ + buf->free += buf->len; + buf->len = 0; + } mp_buf_free(buf); + + /* Concatenate all nargs buffers together */ + lua_concat(L, nargs); return 1; } -/* --------------------------------- Decoding --------------------------------- */ +/* ------------------------------- Decoding --------------------------------- */ void mp_decode_to_lua_type(lua_State *L, mp_cur *c); @@ -470,34 +574,44 @@ void mp_decode_to_lua_hash(lua_State *L, mp_cur *c, size_t len) { * a Lua type, that is left as the only result on the stack. */ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) { mp_cur_need(c,1); + + /* If we return more than 18 elements, we must resize the stack to + * fit all our return values. But, there is no way to + * determine how many objects a msgpack will unpack to up front, so + * we request a +1 larger stack on each iteration (noop if stack is + * big enough, and when stack does require resize it doubles in size) */ + luaL_checkstack(L, 1, + "too many return values at once; " + "use unpack_one or unpack_limit instead."); + switch(c->p[0]) { case 0xcc: /* uint 8 */ mp_cur_need(c,2); - lua_pushnumber(L,c->p[1]); + lua_pushunsigned(L,c->p[1]); mp_cur_consume(c,2); break; case 0xd0: /* int 8 */ mp_cur_need(c,2); - lua_pushnumber(L,(char)c->p[1]); + lua_pushinteger(L,(char)c->p[1]); mp_cur_consume(c,2); break; case 0xcd: /* uint 16 */ mp_cur_need(c,3); - lua_pushnumber(L, + lua_pushunsigned(L, (c->p[1] << 8) | c->p[2]); mp_cur_consume(c,3); break; case 0xd1: /* int 16 */ mp_cur_need(c,3); - lua_pushnumber(L,(int16_t) + lua_pushinteger(L,(int16_t) (c->p[1] << 8) | c->p[2]); mp_cur_consume(c,3); break; case 0xce: /* uint 32 */ mp_cur_need(c,5); - lua_pushnumber(L, + lua_pushunsigned(L, ((uint32_t)c->p[1] << 24) | ((uint32_t)c->p[2] << 16) | ((uint32_t)c->p[3] << 8) | @@ -506,7 +620,7 @@ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) { break; case 0xd2: /* int 32 */ mp_cur_need(c,5); - lua_pushnumber(L, + lua_pushinteger(L, ((int32_t)c->p[1] << 24) | ((int32_t)c->p[2] << 16) | ((int32_t)c->p[3] << 8) | @@ -515,7 +629,7 @@ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) { break; case 0xcf: /* uint 64 */ mp_cur_need(c,9); - lua_pushnumber(L, + lua_pushunsigned(L, ((uint64_t)c->p[1] << 56) | ((uint64_t)c->p[2] << 48) | ((uint64_t)c->p[3] << 40) | @@ -528,7 +642,11 @@ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) { break; case 0xd3: /* int 64 */ mp_cur_need(c,9); +#if LUA_VERSION_NUM < 503 lua_pushnumber(L, +#else + lua_pushinteger(L, +#endif ((int64_t)c->p[1] << 56) | ((int64_t)c->p[2] << 48) | ((int64_t)c->p[3] << 40) | @@ -634,10 +752,10 @@ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) { break; default: /* types that can't be idenitified by first byte value. */ if ((c->p[0] & 0x80) == 0) { /* positive fixnum */ - lua_pushnumber(L,c->p[0]); + lua_pushunsigned(L,c->p[0]); mp_cur_consume(c,1); } else if ((c->p[0] & 0xe0) == 0xe0) { /* negative fixnum */ - lua_pushnumber(L,(signed char)c->p[0]); + lua_pushinteger(L,(signed char)c->p[0]); mp_cur_consume(c,1); } else if ((c->p[0] & 0xe0) == 0xa0) { /* fix raw */ size_t l = c->p[0] & 0x1f; @@ -658,63 +776,163 @@ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) { } } -static int mp_unpack(lua_State *L) { +static int mp_unpack_full(lua_State *L, int limit, int offset) { size_t len; - const unsigned char *s; - mp_cur *c; + const char *s; + mp_cur c; + int cnt; /* Number of objects unpacked */ + int decode_all = (!limit && !offset); + + s = luaL_checklstring(L,1,&len); /* if no match, exits */ + + if (offset < 0 || limit < 0) /* requesting negative off or lim is invalid */ + return luaL_error(L, + "Invalid request to unpack with offset of %d and limit of %d.", + offset, len); + else if (offset > len) + return luaL_error(L, + "Start offset %d greater than input length %d.", offset, len); + + if (decode_all) limit = INT_MAX; + + mp_cur_init(&c,(const unsigned char *)s+offset,len-offset); + + /* We loop over the decode because this could be a stream + * of multiple top-level values serialized together */ + for(cnt = 0; c.left > 0 && cnt < limit; cnt++) { + mp_decode_to_lua_type(L,&c); - if (!lua_isstring(L,-1)) { - lua_pushstring(L,"MessagePack decoding needs a string as input."); - lua_error(L); + if (c.err == MP_CUR_ERROR_EOF) { + return luaL_error(L,"Missing bytes in input."); + } else if (c.err == MP_CUR_ERROR_BADFMT) { + return luaL_error(L,"Bad data format in input."); + } } - s = (const unsigned char*) lua_tolstring(L,-1,&len); - c = mp_cur_new(s,len); - mp_decode_to_lua_type(L,c); - - if (c->err == MP_CUR_ERROR_EOF) { - mp_cur_free(c); - lua_pushstring(L,"Missing bytes in input."); - lua_error(L); - } else if (c->err == MP_CUR_ERROR_BADFMT) { - mp_cur_free(c); - lua_pushstring(L,"Bad data format in input."); - lua_error(L); - } else if (c->left != 0) { - mp_cur_free(c); - lua_pushstring(L,"Extra bytes in input."); - lua_error(L); - } else { - mp_cur_free(c); + if (!decode_all) { + /* c->left is the remaining size of the input buffer. + * subtract the entire buffer size from the unprocessed size + * to get our next start offset */ + int offset = len - c.left; + /* Return offset -1 when we have have processed the entire buffer. */ + lua_pushinteger(L, c.left == 0 ? -1 : offset); + /* Results are returned with the arg elements still + * in place. Lua takes care of only returning + * elements above the args for us. + * In this case, we have one arg on the stack + * for this function, so we insert our first return + * value at position 2. */ + lua_insert(L, 2); + cnt += 1; /* increase return count by one to make room for offset */ } - return 1; + + return cnt; } -/* ---------------------------------------------------------------------------- */ +static int mp_unpack(lua_State *L) { + return mp_unpack_full(L, 0, 0); +} -#if LUA_VERSION_NUM < 502 -static const struct luaL_reg thislib[] = { -#else -static const struct luaL_Reg thislib[] = { -#endif +static int mp_unpack_one(lua_State *L) { + int offset = luaL_optinteger(L, 2, 0); + /* Variable pop because offset may not exist */ + lua_pop(L, lua_gettop(L)-1); + return mp_unpack_full(L, 1, offset); +} + +static int mp_unpack_limit(lua_State *L) { + int limit = luaL_checkinteger(L, 2); + int offset = luaL_optinteger(L, 3, 0); + /* Variable pop because offset may not exist */ + lua_pop(L, lua_gettop(L)-1); + + return mp_unpack_full(L, limit, offset); +} + +static int mp_safe(lua_State *L) { + int argc, err, total_results; + + argc = lua_gettop(L); + + /* This adds our function to the bottom of the stack + * (the "call this function" position) */ + lua_pushvalue(L, lua_upvalueindex(1)); + lua_insert(L, 1); + + err = lua_pcall(L, argc, LUA_MULTRET, 0); + total_results = lua_gettop(L); + + if (!err) { + return total_results; + } else { + lua_pushnil(L); + lua_insert(L,-2); + return 2; + } +} + +/* -------------------------------------------------------------------------- */ +static const struct luaL_Reg cmds[] = { {"pack", mp_pack}, {"unpack", mp_unpack}, - {NULL, NULL} + {"unpack_one", mp_unpack_one}, + {"unpack_limit", mp_unpack_limit}, + {0} }; -LUALIB_API int luaopen_cmsgpack (lua_State *L) { -#if LUA_VERSION_NUM < 502 - luaL_register(L, "cmsgpack", thislib); -#else - luaL_newlib(L, thislib); -#endif +static int luaopen_create(lua_State *L) { + int i; + /* Manually construct our module table instead of + * relying on _register or _newlib */ + lua_newtable(L); + for (i = 0; i < (sizeof(cmds)/sizeof(*cmds) - 1); i++) { + lua_pushcfunction(L, cmds[i].func); + lua_setfield(L, -2, cmds[i].name); + } + + /* Add metadata */ + lua_pushliteral(L, LUACMSGPACK_NAME); + lua_setfield(L, -2, "_NAME"); lua_pushliteral(L, LUACMSGPACK_VERSION); lua_setfield(L, -2, "_VERSION"); lua_pushliteral(L, LUACMSGPACK_COPYRIGHT); lua_setfield(L, -2, "_COPYRIGHT"); lua_pushliteral(L, LUACMSGPACK_DESCRIPTION); - lua_setfield(L, -2, "_DESCRIPTION"); + lua_setfield(L, -2, "_DESCRIPTION"); + return 1; +} + +LUALIB_API int luaopen_cmsgpack(lua_State *L) { + luaopen_create(L); + +#if LUA_VERSION_NUM < 502 + /* Register name globally for 5.1 */ + lua_pushvalue(L, -1); + lua_setglobal(L, LUACMSGPACK_NAME); +#endif + + return 1; +} + +LUALIB_API int luaopen_cmsgpack_safe(lua_State *L) { + int i; + + luaopen_cmsgpack(L); + + /* Wrap all functions in the safe handler */ + for (i = 0; i < (sizeof(cmds)/sizeof(*cmds) - 1); i++) { + lua_getfield(L, -1, cmds[i].name); + lua_pushcclosure(L, mp_safe, 1); + lua_setfield(L, -2, cmds[i].name); + } + +#if LUA_VERSION_NUM < 502 + /* Register name globally for 5.1 */ + lua_pushvalue(L, -1); + lua_setglobal(L, LUACMSGPACK_SAFE_NAME); +#endif + return 1; } diff --git a/rockspec/lua-cmsgpack-0.4.0-0.rockspec b/rockspec/lua-cmsgpack-0.4.0-0.rockspec new file mode 100644 index 0000000..30552a1 --- /dev/null +++ b/rockspec/lua-cmsgpack-0.4.0-0.rockspec @@ -0,0 +1,25 @@ +package = "lua-cmsgpack" +version = "0.4.0-0" +source = { + url = "git://github.com/antirez/lua-cmsgpack.git", + tag = "0.4.0" +} +description = { + summary = "MessagePack C implementation and bindings for Lua 5.1/5.2/5.3", + homepage = "http://github.com/antirez/lua-cmsgpack", + license = "Two-clause BSD", + maintainer = "Salvatore Sanfilippo " +} +dependencies = { + "lua >= 5.1" +} +build = { + type = "builtin", + modules = { + cmsgpack = { + sources = { + "lua_cmsgpack.c" + } + } + } +} diff --git a/rockspec/lua-cmsgpack-scm-1.rockspec b/rockspec/lua-cmsgpack-scm-1.rockspec index e95fd42..fe2a034 100644 --- a/rockspec/lua-cmsgpack-scm-1.rockspec +++ b/rockspec/lua-cmsgpack-scm-1.rockspec @@ -18,7 +18,7 @@ build = { modules = { cmsgpack = { sources = { - "lua_cmsgpack.c", + "lua_cmsgpack.c" } } } diff --git a/test.lua b/test.lua index 56cabf8..f7eed34 100644 --- a/test.lua +++ b/test.lua @@ -3,9 +3,18 @@ -- See the copyright notice at the end of lua_cmsgpack.c for more information. local cmsgpack = require "cmsgpack" +local ok, cmsgpack_safe = pcall(require, 'cmsgpack.safe') +if not ok then cmsgpack_safe = nil end + +print("------------------------------------") +print("Lua version: " .. (_G.jit and _G.jit.version or _G._VERSION)) +print("------------------------------------") + +local unpack = unpack or table.unpack passed = 0 -failed = 0 +failed = 0 +skipped = 0 function hex(s) local i @@ -40,11 +49,57 @@ function unhex(h) return s end -function compare_objects(a,b) +function test_error(name, fn) + io.write("Testing generate error '",name,"' ...") + local ok, ret, err = pcall(fn) + -- 'ok' is an error because we are testing for expicit *failure* + if ok then + print("ERROR: result ", ret, err) + failed = failed+1 + else + print("ok") + passed = passed+1 + end +end + +local function test_multiple(name, ...) + io.write("Multiple test '",name,"' ...") + if not compare_objects({...},{cmsgpack.unpack(cmsgpack.pack(...))}) then + print("ERROR:", {...}, cmsgpack.unpack(cmsgpack.pack(...))) + failed = failed+1 + else + print("ok") + passed = passed+1 + end +end + +function test_noerror(name, fn) + io.write("Testing safe calling '",name,"' ...") + if not cmsgpack_safe then + print("skip: no `cmsgpack.safe` module") + skipped = skipped + 1 + return + end + local ok, ret, err = pcall(fn) + if not ok then + print("ERROR: result ", ret, err) + failed = failed+1 + else + print("ok") + passed = passed+1 + end +end + +function compare_objects(a,b,depth) if (type(a) == "table") then local count = 0 + if not depth then + depth = 1 + elseif depth == 10 then + return true -- assume if match down 10 levels, the rest is okay too + end for k,v in pairs(a) do - if not compare_objects(b[k],v) then return false end + if not compare_objects(b[k],v, depth + 1) then return false end count = count + 1 end -- All the 'a' keys are equal to their 'b' equivalents. @@ -67,9 +122,107 @@ function test_circular(name,obj) end end -function test_pack(name,obj,raw) +function test_stream(mod, name, ...) + io.write("Stream test '", name, "' ...\n") + if not mod then + print("skip: no `cmsgpack.safe` module") + skipped = skipped + 1 + return + end + local argc = select('#', ...) + for i=1, argc do + test_circular(name, select(i, ...)) + end + local ret = {mod.unpack(mod.pack(unpack({...})))} + for i=1, argc do + local origin = select(i, ...) + if (type(origin) == "table") then + for k,v in pairs(origin) do + local fail = not compare_objects(v, ret[i][k]) + if fail then + print("ERRORa:", k, v, " not match ", ret[i][k]) + failed = failed + 1 + elseif not fail then + print("ok; matched stream table member") + passed = passed + 1 + end + end + else + local fail = not compare_objects(origin, ret[i]) + if fail then + print("ERRORc:", origin, " not match ", ret[i]) + failed = failed + 1 + elseif not fail then + print("ok; matched individual stream member") + passed = passed + 1 + end + end + end + +end + +function test_partial_unpack(name, count, ...) + io.write("Testing partial unpack '",name,"' ...\n") + local first = select(1, ...) + local pack, unpacked, args, offset, cargs, ok, err + if (type(first) == "table") then + pack = first.p + args = first.remaining + offset = first.o + cargs = {pack, count, offset} + else + pack = cmsgpack.pack(unpack({...})) + args = {...} + cargs = {pack, count} + end + if offset and offset < 0 then + ok, unpacked, err = pcall(function()return {cmsgpack.unpack_limit(unpack(cargs))} end) + if not ok then + print("ok; received error as expected") --, unpacked) + passed = passed + 1 + return + end + else + unpacked = {cmsgpack.unpack_limit(unpack(cargs))} + -- print ("GOT RETURNED:", unpack(unpacked)) + end + + if count == 0 and #unpacked == 1 then + print("ok; received zero decodes as expected") + passed = passed + 1 + return + end + + if not (((#unpacked)-1) == count) then + print(string.format("ERROR: received %d instead of %d objects:", (#unpacked)-1, count), + unpack(select(1, unpacked))) + failed = failed + 1 + return + end + + for i=2, #unpacked do + local origin = args[i-1] + --print("Comparing ", origin, unpacked[i]) + if not compare_objects(origin, unpacked[i]) then + print("ERROR:", origin, " not match ", unpacked[i]) + failed = failed + 1 + else + print("ok; matched unpacked value to input") + passed = passed + 1 + end + end + + -- return the packed value and our continue offset + return pack, unpacked[1] +end + +function test_pack(name,obj,raw,optraw) io.write("Testing encoder '",name,"' ...") - if hex(cmsgpack.pack(obj)) ~= raw then + local result = hex(cmsgpack.pack(obj)) + if optraw and (result == optraw) then + print("ok") + passed = passed + 1 + elseif result ~= raw then print("ERROR:", obj, hex(cmsgpack.pack(obj)), raw) failed = failed+1 else @@ -78,6 +231,24 @@ function test_pack(name,obj,raw) end end +function test_unpack_one(name, packed, check, offset) + io.write("Testing one unpack '",name,"' ...") + local unpacked = {cmsgpack.unpack_one(unpack({packed, offset}))} + + if #unpacked > 2 then + print("ERROR: unpacked more than one object:", unpack(unpacked)) + failed = failed + 1 + elseif not compare_objects(unpacked[2], check) then + print("ERROR: unpacked unexpected result:", unpack(unpacked)) + failed = failed + 1 + else + print("ok") --; unpacked", unpacked[2]) + passed = passed + 1 + end + + return unpacked[1] +end + function test_unpack(name,raw,obj) io.write("Testing decoder '",name,"' ...") if not compare_objects(cmsgpack.unpack(unhex(raw)),obj) then @@ -94,6 +265,67 @@ function test_pack_and_unpack(name,obj,raw) test_unpack(name,raw,obj) end +local function test_global() + io.write("Testing global variable ...") + + if _VERSION == "Lua 5.1" then + if not _G.cmsgpack then + print("ERROR: Lua 5.1 should set global") + failed = failed+1 + else + print("ok") + passed = passed+1 + end + else + if _G.cmsgpack then + print("ERROR: Lua 5.2 should not set global") + failed = failed+1 + else + print("ok") + passed = passed+1 + end + end +end + +local function test_array() + io.write("Testing array detection ...") + + local a = {a1 = 1, a2 = 1, a3 = 1, a4 = 1, a5 = 1, a6 = 1, a7 = 1, a8 = 1, a9 = 1} + a[1] = 10 a[2] = 20 a[3] = 30 + a.a1,a.a2,a.a3,a.a4,a.a5,a.a6,a.a7,a.a8, a.a9 = nil + + local test_obj = {10,20,30} + assert(compare_objects(test_obj, a)) + + local etalon = cmsgpack.pack(test_obj) + local encode = cmsgpack.pack(a) + + if etalon ~= encode then + print("ERROR:") + print("", "expected: ", hex(etalon)) + print("", " got: ", hex(encode)) + failed = failed+1 + else + print("ok") + passed = passed+1 + end + + io.write("Testing array detection ...") + + a = {["1"] = 20, [2] = 30, [3] = 40} + encode = cmsgpack.pack(a) + if etalon == encode then + print("ERROR:") + print("", " incorrect: ", hex(etalon)) + failed = failed+1 + else + print("ok") + passed = passed+1 + end +end + +test_global() +test_array() test_circular("positive fixnum",17); test_circular("negative fixnum",-1); test_circular("true boolean",true); @@ -120,6 +352,12 @@ test_circular("fix array (1)",{1,2,3,"foo"}) test_circular("fix array (2)",{}) test_circular("fix array (3)",{1,{},{}}) test_circular("fix map",{a=5,b=10,c="string"}) +test_circular("positive infinity", math.huge) +test_circular("negative infinity", -math.huge) +test_circular("high bits", 0xFFFFFFFF) +test_circular("higher bits", 0xFFFFFFFFFFFFFFFF) +test_circular("high bits", -0x7FFFFFFF) +test_circular("higher bits", -0x7FFFFFFFFFFFFFFF) -- The following test vectors are taken from the Javascript lib at: -- https://github.com/cuzic/MessagePack-JS/blob/master/test/test_pack.html @@ -148,9 +386,179 @@ a = {x=nil,y=5} b = {x=a} a['x'] = b pack = cmsgpack.pack(a) -test_pack("regression for issue #4",a,"82a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a178c0") +-- Note: the generated result isn't stable because the order of traversal for +-- a table isn't defined. So far we've only noticed two serializations of a +-- (and the second serialization only happens on Lua 5.3 sometimes) +test_pack("regression for issue #4 output matching",a,"82a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a178c0", "82a17881a17882a17881a17882a17881a17882a17881a17882a17881a17882a17881a17882a17881a17882a17881a178c0a17905a17905a17905a17905a17905a17905a17905a17905") +test_circular("regression for issue #4 circular",a) + +-- test unpacking malformed input without crashing. This actually returns one integer value (the ASCII code) +-- for each character in the string. We don't care about the return value, just that we don't segfault. +cmsgpack.unpack("82a17881a17882a17881a17882a17881a17882a17881a17882a17881a17882a17881a17882a17881a17882a17881a17") + +-- Tests from github.com/moteus +test_circular("map with number keys", {[1] = {1,2,3}}) +test_circular("map with string keys", {["1"] = {1,2,3}}) +test_circular("map with string keys", {["1"] = 20, [2] = 30, ["3"] = 40}) +test_circular("map with float keys", {[1.5] = {1,2,3}}) +test_error("unpack nil", function() cmsgpack.unpack(nil) end) +test_error("unpack table", function() cmsgpack.unpack({}) end) +test_error("unpack udata", function() cmsgpack.unpack(io.stdout) end) +test_noerror("unpack nil", function() cmsgpack_safe.unpack(nil) end) +test_noerror("unpack nil", function() cmsgpack_safe.unpack(nil) end) +test_noerror("unpack table", function() cmsgpack_safe.unpack({}) end) +test_noerror("unpack udata", function() cmsgpack_safe.unpack(io.stdout) end) +test_multiple("two ints", 1, 2) +test_multiple("holes", 1, nil, 2, nil, 4) + +-- Streaming/Multi-Input Tests +test_stream(cmsgpack, "simple", {a=1}, {b=2}, {c=3}, 4, 5, 6, 7) +test_stream(cmsgpack_safe, "safe simple", {a=1}, {b=2}, {c=3}, 4, 5, 6, 7) +test_stream(cmsgpack, "oddities", {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0}, {a=64}, math.huge, -math.huge) +test_stream(cmsgpack_safe, "safe oddities", {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0}, {a=64}, math.huge, -math.huge) +test_stream(cmsgpack, "strange things", nil, {}, {nil}, a, b, b, b, a, a, b, {c = a, d = b}) +test_stream(cmsgpack_safe, "strange things", nil, {}, {nil}, a, b, b, b, a, a, b, {c = a, d = b}) +test_error("pack nothing", function() cmsgpack.pack() end) +test_noerror("pack nothing safe", function() cmsgpack_safe.pack() end) +test_circular("large object test", + {A=9483, a=9483, aa=9483, aal=9483, aalii=9483, aam=9483, Aani=9483, + aardvark=9483, aardwolf=9483, Aaron=9483, Aaronic=9483, Aaronical=9483, + Aaronite=9483, Aaronitic=9483, Aaru=9483, Ab=9483, aba=9483, Ababdeh=9483, + Ababua=9483, abac=9483, abaca=9483, abacate=9483, abacay=9483, + abacinate=9483, abacination=9483, abaciscus=9483, abacist=9483, + aback=9483, abactinal=9483, abactinally=9483, abaction=9483, abactor=9483, + abaculus=9483, abacus=9483, Abadite=9483, abaff=9483, abaft=9483, + abaisance=9483, abaiser=9483, abaissed=9483, abalienate=9483, + abalienation=9483, abalone=9483, Abama=9483, abampere=9483, abandon=9483, + abandonable=9483, abandoned=9483, abandonedly=9483, abandonee=9483, + abandoner=9483, abandonment=9483, Abanic=9483, Abantes=9483, + abaptiston=9483, Abarambo=9483, Abaris=9483, abarthrosis=9483, + abarticular=9483, abarticulation=9483, abas=9483, abase=9483, abased=9483, + abasedly=9483, abasedness=9483, abasement=9483, abaser=9483, Abasgi=9483, + abash=9483, abashed=9483, abashedly=9483, abashedness=9483, + abashless=9483, abashlessly=9483, abashment=9483, abasia=9483, + abasic=9483, abask=9483, Abassin=9483, abastardize=9483, abatable=9483, + abate=9483, abatement=9483, abater=9483, abatis=9483, abatised=9483, + abaton=9483, abator=9483, abattoir=9483, Abatua=9483, abature=9483, + abave=9483, abaxial=9483, abaxile=9483, abaze=9483, abb=9483, Abba=9483, + abbacomes=9483, abbacy=9483, Abbadide=9483, abbas=9483, abbasi=9483, + abbassi=9483, Abbasside=9483, abbatial=9483, abbatical=9483, abbess=9483, + abbey=9483, abbeystede=9483, Abbie=9483, abbot=9483, abbotcy=9483, + abbotnullius=9483, abbotship=9483, abbreviate=9483, abbreviately=9483, + abbreviation=9483, abbreviator=9483, abbreviatory=9483, abbreviature=9483, + Abby=9483, abcoulomb=9483, abdal=9483, abdat=9483, Abderian=9483, + Abderite=9483, abdest=9483, abdicable=9483, abdicant=9483, abdicate=9483, + abdication=9483, abdicative=9483, abdicator=9483, Abdiel=9483, + abditive=9483, abditory=9483, abdomen=9483, abdominal=9483, + Abdominales=9483, abdominalian=9483, abdominally=9483, + abdominoanterior=9483, abdominocardiac=9483, abdominocentesis=9483, + abdominocystic=9483, abdominogenital=9483, abdominohysterectomy=9483, + abdominohysterotomy=9483, abdominoposterior=9483, abdominoscope=9483, + abdominoscopy=9483, abdominothoracic=9483, abdominous=9483, + abdominovaginal=9483, abdominovesical=9483, abduce=9483, abducens=9483, + abducent=9483, abduct=9483, abduction=9483, abductor=9483, Abe=9483, + abeam=9483, abear=9483, abearance=9483, abecedarian=9483, + abecedarium=9483, abecedary=9483, abed=9483, abeigh=9483, Abel=9483, + abele=9483, Abelia=9483, Abelian=9483, Abelicea=9483, Abelite=9483, + abelite=9483, Abelmoschus=9483, abelmosk=9483, Abelonian=9483, + abeltree=9483, Abencerrages=9483, abenteric=9483, abepithymia=9483, + Aberdeen=9483, aberdevine=9483, Aberdonian=9483, Aberia=9483, + aberrance=9483, aberrancy=9483, aberrant=9483, aberrate=9483, + aberration=9483, aberrational=9483, aberrator=9483, aberrometer=9483, + aberroscope=9483, aberuncator=9483, abet=9483, abetment=9483, + abettal=9483, abettor=9483, abevacuation=9483, abey=9483, abeyance=9483, + abeyancy=9483, abeyant=9483, abfarad=9483, abhenry=9483, abhiseka=9483, + abhominable=9483, abhor=9483, abhorrence=9483, abhorrency=9483, + abhorrent=9483, abhorrently=9483, abhorrer=9483, abhorrible=9483, + abhorring=9483, Abhorson=9483, abidal=9483, abidance=9483, abide=9483, + abider=9483, abidi=9483, abiding=9483, abidingly=9483, abidingness=9483, + Abie=9483, Abies=9483, abietate=9483, abietene=9483, abietic=9483, + abietin=9483, Abietineae=9483, abietineous=9483, abietinic=9483, + Abiezer=9483, Abigail=9483, abigail=9483, abigailship=9483, abigeat=9483, + abigeus=9483, abilao=9483, ability=9483, abilla=9483, abilo=9483, + abintestate=9483, abiogenesis=9483, abiogenesist=9483, abiogenetic=9483, + abiogenetical=9483, abiogenetically=9483, abiogenist=9483, + abiogenous=9483, abiogeny=9483, abiological=9483, abiologically=9483, + abiology=9483, abiosis=9483, abiotic=9483, abiotrophic=9483, + abiotrophy=9483, Abipon=9483, abir=9483, abirritant=9483, abirritate=9483, + abirritation=9483, abirritative=9483, abiston=9483, Abitibi=9483, + abiuret=9483, abject=9483, abjectedness=9483, abjection=9483, + abjective=9483, abjectly=9483, abjectness=9483, abjoint=9483, + abjudge=9483, abjudicate=9483, abjudication=9483, abjunction=9483, + abjunctive=9483, abjuration=9483, abjuratory=9483, abjure=9483, + abjurement=9483, abjurer=9483, abkar=9483, abkari=9483, Abkhas=9483, + Abkhasian=9483, ablach=9483, ablactate=9483, ablactation=9483, + ablare=9483, ablastemic=9483, ablastous=9483, ablate=9483, ablation=9483, + ablatitious=9483, ablatival=9483, ablative=9483, ablator=9483, + ablaut=9483, ablaze=9483, able=9483, ableeze=9483, ablegate=9483, + ableness=9483, ablepharia=9483, ablepharon=9483, ablepharous=9483, + Ablepharus=9483, ablepsia=9483, ableptical=9483, ableptically=9483, + abler=9483, ablest=9483, ablewhackets=9483, ablins=9483, abloom=9483, + ablow=9483, ablude=9483, abluent=9483, ablush=9483, ablution=9483, + ablutionary=9483, abluvion=9483, ably=9483, abmho=9483, Abnaki=9483, + abnegate=9483, abnegation=9483, abnegative=9483, abnegator=9483, + Abner=9483, abnerval=9483, abnet=9483, abneural=9483, abnormal=9483, + abnormalism=9483, abnormalist=9483, abnormality=9483, abnormalize=9483, + abnormally=9483, abnormalness=9483, abnormity=9483, abnormous=9483, + abnumerable=9483, Abo=9483, aboard=9483, Abobra=9483, abode=9483, + abodement=9483, abody=9483, abohm=9483, aboil=9483, abolish=9483, + abolisher=9483, abolishment=9483, abolition=9483, abolitionary=9483, + abolitionism=9483, abolitionist=9483, abolitionize=9483, abolla=9483, + aboma=9483, abomasum=9483, abomasus=9483, abominable=9483, + abominableness=9483, abominably=9483, abominate=9483, abomination=9483, + abominator=9483, abomine=9483, Abongo=9483, aboon=9483, aborad=9483, + aboral=9483, aborally=9483, abord=9483, aboriginal=9483, + aboriginality=9483, aboriginally=9483, aboriginary=9483, aborigine=9483, + abort=9483, aborted=9483, aborticide=9483, abortient=9483, + abortifacient=9483, abortin=9483, abortion=9483, abortional=9483, + abortionist=9483, abortive=9483, abortively=9483, abortiveness=9483, + abortus=9483, abouchement=9483, abound=9483, abounder=9483, + abounding=9483, aboundingly=9483, about=9483, abouts=9483, above=9483, + aboveboard=9483, abovedeck=9483, aboveground=9483, aboveproof=9483, + abovestairs=9483, abox=9483, abracadabra=9483, abrachia=9483, + abradant=9483, abrade=9483, abrader=9483, Abraham=9483, Abrahamic=9483, + Abrahamidae=9483, Abrahamite=9483, Abrahamitic=9483, abraid=9483, + Abram=9483, Abramis=9483, abranchial=9483, abranchialism=9483, + abranchian=9483, Abranchiata=9483, abranchiate=9483, abranchious=9483, + abrasax=9483, abrase=9483, abrash=9483, abrasiometer=9483, abrasion=9483, + abrasive=9483, abrastol=9483, abraum=9483, abraxas=9483, abreact=9483, + abreaction=9483, abreast=9483, abrenounce=9483, abret=9483, abrico=9483, + abridge=9483, abridgeable=9483, abridged=9483, abridgedly=9483, + abridger=9483, abridgment=9483, abrim=9483, abrin=9483, abristle=9483, + abroach=9483, abroad=9483, Abrocoma=9483, abrocome=9483, abrogable=9483, + abrogate=9483, abrogation=9483, abrogative=9483, abrogator=9483, + Abroma=9483, Abronia=9483, abrook=9483, abrotanum=9483, abrotine=9483, + abrupt=9483, abruptedly=9483, abruption=9483, abruptly=9483, + abruptness=9483, Abrus=9483, Absalom=9483, absampere=9483, Absaroka=9483, + absarokite=9483, abscess=9483, abscessed=9483, abscession=9483, + abscessroot=9483, abscind=9483, abscise=9483, abscision=9483, + absciss=9483, abscissa=9483, abscissae=9483, abscisse=9483, + abscission=9483, absconce=9483, abscond=9483, absconded=9483, + abscondedly=9483, abscondence=9483}) + +-- Test limited streaming +packed, offset = test_partial_unpack("unpack 1a out of 7", 1, "a", "b", "c", "d", "e", "f", "g") +packed, offset = test_partial_unpack("unpack 1b of remaining 7", 1, {p=packed,o=offset,remaining={"b"}}) +packed, offset = test_partial_unpack("unpack 1c of remaining 7", 1, {p=packed,o=offset,remaining={"c"}}) +packed, offset = test_partial_unpack("unpack 1d of remaining 7", 1, {p=packed,o=offset,remaining={"d"}}) +packed, offset = test_partial_unpack("unpack 1e of remaining 7", 1, {p=packed,o=offset,remaining={"e"}}) +packed, offset = test_partial_unpack("unpack 1f of remaining 7", 1, {p=packed,o=offset,remaining={"f"}}) +packed, offset = test_partial_unpack("unpack 1g of remaining 7", 1, {p=packed,o=offset,remaining={"g"}}) +packed, offset = test_partial_unpack("unpack 1nil of remaining 7", 0, {p=packed,o=offset}) + +packed, offset = test_partial_unpack("unpack 3 out of 7", 3, "a", "b", "c", "d", "e", "f", "g") +test_partial_unpack("unpack remaining 4", 4, {p=packed,o=offset,remaining={"d", "e", "f", "g"}}) + +test_unpack_one("simple", packed, "a") +offset = test_unpack_one("simple", cmsgpack.pack({f = 3, j = 2}, "m", "e", 7), {f = 3, j = 2}) +test_unpack_one("simple", cmsgpack.pack({f = 3, j = 2}, "m", "e", 7), "m", offset) -- Final report print() print("TEST PASSED:",passed) print("TEST FAILED:",failed) +print("TEST SKIPPED:",skipped) + +if failed > 0 then + os.exit(1) +end