diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..fa20d8f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,51 @@ +language: erlang + +env: + global: + - LUAROCKS_VER=2.1.0 + - LUAROCKS_BASE=luarocks-$LUAROCKS_VER + - LUAROCKS_INSTALL=git + - LUAROCKS_GITTAG=master + # - LUAROCKS_GITTAG=v$LUAROCKS_VER + matrix: + - LUA=lua5.1 LUA_DEV=liblua5.1-dev LUA_VER=5.1 LUA_SFX=5.1 LUA_INCDIR=/usr/include/lua5.1 + - LUA=lua5.2 LUA_DEV=liblua5.2-dev LUA_VER=5.2 LUA_SFX=5.2 LUA_INCDIR=/usr/include/lua5.2 + - LUA=luajit LUA_DEV=libluajit-5.1-dev LUA_VER=5.1 LUA_SFX=jit LUA_INCDIR=/usr/include/luajit-2.0 + +branches: + only: + - master + - moteus + +before_install: + - if [ $LUA = "luajit" ]; then + sudo add-apt-repository ppa:mwild1/ppa -y && sudo apt-get update -y; + fi + - sudo apt-get install $LUA + - sudo apt-get install $LUA_DEV + - lua$LUA_SFX -v + # Install a recent luarocks release + - if [ $LUAROCKS_INSTALL = "git" ]; then + git clone https://github.com/keplerproject/luarocks.git; + cd luarocks; + git checkout $LUAROCKS_GITTAG; + else + wget http://luarocks.org/releases/$LUAROCKS_BASE.tar.gz; + tar zxvpf $LUAROCKS_BASE.tar.gz; + cd $LUAROCKS_BASE; + fi + - ./configure --lua-version=$LUA_VER --lua-suffix=$LUA_SFX --with-lua-include="$LUA_INCDIR" + - sudo make + - sudo make install + - cd $TRAVIS_BUILD_DIR + +install: + - sudo luarocks make rockspec/lua-cmsgpack-scm-1.rockspec + +script: + - lua$LUA_SFX test.lua + +notifications: + email: + on_success: change + on_failure: always \ No newline at end of file diff --git a/README b/README index d6317b1..95028c9 100644 --- a/README +++ b/README @@ -1,5 +1,6 @@ README for lua_cmsgpack.c === +!https://travis-ci.org/moteus/lua-cmsgpack.png!:https://travis-ci.org/moteus/lua-cmsgpack Lua-cmsgpack is a MessagePack (http://msgpack.org) implementation and bindings for Lua 5.1 in a self contained C file without external dependencies. diff --git a/lua_cmsgpack.c b/lua_cmsgpack.c index 53dc1cf..4ca0fed 100644 --- a/lua_cmsgpack.c +++ b/lua_cmsgpack.c @@ -31,6 +31,32 @@ * 20-Feb-2012 (ver 0.3.0): Module renamed lua-cmsgpack (was lua-msgpack). * ============================================================================ */ +#if LUA_VERSION_NUM >= 502 // lua 5.2 + +#define lua_objlen lua_rawlen + +static void luaL_register (lua_State *L, const char *libname, const luaL_Reg *l){ + if(libname) lua_newtable(L); + luaL_setfuncs(L, l, 0); +} + +#endif + +/* + * Compatibility wrapper to determine whether a float/double x is finite. + */ +#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 + +/* Checks if a float or double value x can be represented as an integer of type T 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) + /* --------------------------- Endian conversion -------------------------------- * We use it only for floats and doubles, all the other conversions are performed * in an endian independent fashion. So the only thing we need is a function @@ -299,6 +325,22 @@ static void mp_encode_map(mp_buf *buf, int64_t n) { mp_buf_append(buf,b,enclen); } + +/* ----------------------------- Lua fail functions --------------------------- */ + +static int mp_err_fail(lua_State *L, const char *msg){ + lua_pushstring(L,msg); + return lua_error(L); +} + +static int mp_safe_fail(lua_State *L, const char *msg){ + lua_pushnil(L); + lua_pushstring(L,msg); + return 2; +} + +typedef int (*mp_fail_ptr) (lua_State *, const char *); + /* ----------------------------- Lua types encoding --------------------------- */ static void mp_encode_lua_string(lua_State *L, mp_buf *buf) { @@ -317,10 +359,10 @@ static void mp_encode_lua_bool(lua_State *L, mp_buf *buf) { 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); - } else { + if (IS_INT64_EQUIVALENT(n)) { mp_encode_int(buf,(int64_t)n); + } else { + mp_encode_double(buf,(double)n); } } @@ -379,14 +421,14 @@ static int table_is_an_array(lua_State *L) { idx = n; if (idx != n || idx < 1) goto not_array; count++; - max = idx; + if (max < idx) max = idx; } /* We have the total number of elements in "count". Also we have * the max index encountered in "idx". 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 * that there are all the keys form 1 to count (both included). */ - return idx == count; + return max == count; not_array: lua_pop(L,1); @@ -426,12 +468,30 @@ static void mp_encode_lua_type(lua_State *L, mp_buf *buf, int level) { lua_pop(L,1); } +/* + * Packs all arguments as a stream. + * Returns the empty string if no argument was given. + */ static int mp_pack(lua_State *L) { - mp_buf *buf = mp_buf_new(); + int i, nargs = lua_gettop(L); + if(nargs == 0) { + lua_pushliteral(L, ""); + return 1; + } + + for(i = 1; i <= nargs; i++) { + mp_buf *buf; + lua_pushvalue(L, i); + buf = mp_buf_new(); mp_encode_lua_type(L,buf,0); + + lua_settop(L, nargs + i - 1); lua_pushlstring(L,(char*)buf->b,buf->len); mp_buf_free(buf); + } + + lua_concat(L, nargs); return 1; } @@ -654,54 +714,75 @@ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) { } } -static int mp_unpack(lua_State *L) { +static int mp_unpack_impl(lua_State *L, mp_fail_ptr mp_fail) { size_t len; const unsigned char *s; mp_cur *c; + int cnt;/* Number of objects unpacked */ if (!lua_isstring(L,-1)) { - lua_pushstring(L,"MessagePack decoding needs a string as input."); - lua_error(L); + return mp_fail(L,"MessagePack decoding needs a string as 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); + + for(cnt = 0; c->left > 0; cnt++) { + mp_decode_to_lua_type(L,c); + + if (c->err == MP_CUR_ERROR_EOF) { + mp_cur_free(c); + return mp_fail(L,"Missing bytes in input."); + } else if (c->err == MP_CUR_ERROR_BADFMT) { + mp_cur_free(c); + return mp_fail(L,"Bad data format in input."); + } } + mp_cur_free(c); - return 1; + return cnt; +} + +static int mp_unpack(lua_State *L){ + return mp_unpack_impl(L, mp_err_fail); +} + +static int mp_unpack_safe(lua_State *L){ + return mp_unpack_impl(L, mp_safe_fail); } /* ---------------------------------------------------------------------------- */ -static const struct luaL_reg thislib[] = { +static const struct luaL_Reg thislib[] = { {"pack", mp_pack}, {"unpack", mp_unpack}, {NULL, NULL} }; LUALIB_API int luaopen_cmsgpack (lua_State *L) { - luaL_register(L, "cmsgpack", thislib); + lua_settop(L, 0); + lua_newtable(L); + luaL_register(L, 0, thislib); 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"); + +#if LUA_VERSION_NUM < 502 + lua_pushvalue(L, -1); + lua_setglobal(L, "cmsgpack"); +#endif + + return 1; +} + +LUALIB_API int luaopen_cmsgpack_safe (lua_State *L) { + luaopen_cmsgpack(L); + lua_pushcfunction(L, mp_unpack_safe); + lua_setfield(L, -2, "unpack"); return 1; } diff --git a/test.lua b/test.lua index 8bec0f5..bb220b2 100644 --- a/test.lua +++ b/test.lua @@ -2,8 +2,13 @@ -- Copyright(C) 2012 Salvatore Sanfilippo, All Rights Reserved. -- 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 + passed = 0 failed = 0 +skipped = 0 function hex(s) local i @@ -92,6 +97,94 @@ function test_pack_and_unpack(name,obj,raw) test_unpack(name,raw,obj) end +function test_error(name, fn) + io.write("Testing generate error '",name,"' ...") + local ok, ret, err = pcall(fn) + if ok then + print("ERROR: result ", ret, err) + failed = failed+1 + else + print("ok") + passed = passed+1 + end +end + +function test_safe(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 + +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 + +local function test_global() + io.write("test 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 +end + +test_global() +test_array() test_circular("positive fixnum",17); test_circular("negative fixnum",-1); test_circular("true boolean",true); @@ -118,6 +211,18 @@ 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("map with number keys", {["1"] = {1,2,3}}) +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_safe("unpack table", function() cmsgpack_safe.unpack({}) end) +test_safe("unpack nil", function() cmsgpack_safe.unpack(nil) end) +test_safe("unpack udata", function() cmsgpack_safe.unpack(io.stdout) end) +test_multiple("two ints", 1, 2) +test_multiple("holes", 1, nil, 2, nil, 4) -- The following test vectors are taken from the Javascript lib at: -- https://github.com/cuzic/MessagePack-JS/blob/master/test/test_pack.html @@ -150,5 +255,10 @@ test_pack("regression for issue #4",a,"82a17905a17881a17882a17905a17881a17882a17 -- Final report print() -print("TEST PASSED:",passed) -print("TEST FAILED:",failed) +print("TEST PASSED:",passed) +print("TEST FAILED:",failed) +print("TEST SKIPPED:",skipped) + +if failed > 0 then + os.exit(1) +end