diff --git a/.gitignore b/.gitignore index 302bf46..9f7b2a5 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ *.o *.so + +build/ diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..d07203c --- /dev/null +++ b/AUTHORS @@ -0,0 +1,11 @@ +ORIGINAL AUTHORS +--- +This library was written by Salvatore Sanfilippo for Redis, but is maintained as a separated project by the author. + +CONTRIBUTORS +--- +Fabrício Puppi (http://github.com/puppi) + +ADDITIONAL CREDITS +--- +Some of the test vectors in "test.lua" are obtained from the Javascript MessagePack-JS library (https://github.com/cuzic/MessagePack-JS). \ No newline at end of file diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..2ca7fe1 --- /dev/null +++ b/COPYING @@ -0,0 +1,20 @@ +Copyright (C) 2012 Salvatore Sanfilippo. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..b651220 --- /dev/null +++ b/NEWS @@ -0,0 +1,4 @@ +20-Feb-2012 (ver 0.3.0): Module renamed lua-cmsgpack (was lua-msgpack). +20-Feb-2012 (ver 0.2.1): Minor bug fixing. +20-Feb-2012 (ver 0.2.0): Tables encoding improved. +19-Feb-2012 (ver 0.1.0): Initial release. \ No newline at end of file diff --git a/README b/README.md similarity index 62% rename from README rename to README.md index d6317b1..35769ba 100644 --- a/README +++ b/README.md @@ -1,29 +1,30 @@ -README for lua_cmsgpack.c +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. +Lua 5.1 in C without external dependencies. This library is open source software licensed under the BSD two-clause license. The library is currently considered in BETA STAGE for lack of extensive testing. + + INSTALLATION --- - Using LuaRocks (http://luarocks.org): -* Install current stable release: +* Install current stable release - sudo luarocks install lua-cmsgpack + $ sudo luarocks install lua-cmsgpack -* Install current Git master head from GitHub: +* Install current Git master head from GitHub - sudo luarocks install lua-cmsgpack --from=rocks-cvs + $ sudo luarocks install lua-cmsgpack --from=rocks-cvs * Install from current working copy - cd lua-cmsgpack/ - sudo luarocks make rockspec/lua-cmsgpack-scm-1.rockspec + $ cd lua-cmsgpack/ + $ sudo luarocks make rockspec/lua-cmsgpack-scm-1.rockspec If you embed Lua and all modules into your C project, just add the lua_cmsgpack.c file and call the following function after creating the Lua @@ -31,16 +32,32 @@ interpreter: luaopen_cmsgpack(L); + +Note that this function should be called as a Lua function, and not directly. +It will return the module namespace and register it in the global environment +under the key "cmsgpack". + + + USAGE --- + cmsgpack = require 'cmsgpack' The exported API is very simple, consisting in two functions: -* msgpack = cmsgpack.pack(lua_object) -* lua_object = cmsgpack.unpack(msgpack) + msgpack = cmsgpack.pack(lua_object1, lua_object2, ...) + lua_object1, lua_object2, ... = cmsgpack.unpack(msgpack) -However because of the nature of Lua numerical and table type a few behavior -of the library must be well understood to avoid problems: + +cmsgpack.pack receives 0 or more Lua values and converts them into a stream in the MessagePack protocol. +If a value can't be converted (userdata, for instance), it is mapped to nil. The function doesn't throw +errors. + +cmsgpack.unpack receives a string representing a MessagePack stream and returns all unpacked objects. +If the string is invalid, an error is thrown. + +Because of the nature of Lua table, which can represent either a numerical or associative array, the +following principle is adopted: * A table is converted into a MessagePack array type only if *all* the keys are composed of incrementing integers starting at 1 end ending at N, without holes, @@ -51,6 +68,8 @@ 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. + + NESTED TABLES --- Nested tables are handled correctly up to LUACMSGPACK_MAX_NESTING levels of @@ -61,16 +80,15 @@ as MessagePack nil value. It is worth to note that in Lua it is possible to create tables that mutually refer to each other, creating a cycle. For example: -a = {x=nil,y=5} -b = {x=a} -a['x'] = b + a = { x = nil, y = 5 } + b = { x = a } + a['x'] = b This condition will simply make the encoder reach the max level of nesting, thus avoiding an infinite loop. -CREDITS ---- -This library was written by Salvatore Sanfilippo for Redis, but is maintained as a separated project by the author. -Some of the test vectors in "test.lua" are obtained from the Javascript MessagePack-JS library (https://github.com/cuzic/MessagePack-JS). +COPYRIGHT AND CREDITS +--- +See COPYING and AUTHORS. diff --git a/lua_cmsgpack.c b/lua_cmsgpack.c index 9061213..a7f7c11 100644 --- a/lua_cmsgpack.c +++ b/lua_cmsgpack.c @@ -1,17 +1,4 @@ -#include -#include -#include -#include -#include - -#include "lua.h" -#include "lauxlib.h" - -#define LUACMSGPACK_VERSION "lua-cmsgpack 0.3.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. */ +#include "lua_cmsgpack.h" /* ============================================================================== * MessagePack implementation and bindings for Lua 5.1. @@ -23,12 +10,6 @@ * http://wiki.msgpack.org/display/MSGPACK/Format+specification * * See Copyright Notice at the end of this file. - * - * CHANGELOG: - * 19-Feb-2012 (ver 0.1.0): Initial release. - * 20-Feb-2012 (ver 0.2.0): Tables encoding improved. - * 20-Feb-2012 (ver 0.2.1): Minor bug fixing. - * 20-Feb-2012 (ver 0.3.0): Module renamed lua-cmsgpack (was lua-msgpack). * ============================================================================ */ /* --------------------------- Endian conversion -------------------------------- @@ -41,7 +22,9 @@ * simplicity of the Lua build system we prefer to 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; @@ -68,7 +51,7 @@ typedef struct mp_buf { } mp_buf; static mp_buf *mp_buf_new(void) { - mp_buf *buf = malloc(sizeof(*buf)); + mp_buf *buf = (mp_buf*)malloc(sizeof(*buf)); buf->b = NULL; buf->len = buf->free = 0; @@ -79,7 +62,7 @@ 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*)realloc(buf->b,newlen*2); buf->free = newlen; } memcpy(buf->b+buf->len,s,len); @@ -112,7 +95,7 @@ typedef struct mp_cur { } mp_cur; static mp_cur *mp_cur_new(const unsigned char *s, size_t len) { - mp_cur *cursor = malloc(sizeof(*cursor)); + mp_cur *cursor = (mp_cur*)malloc(sizeof(*cursor)); cursor->p = s; cursor->left = len; @@ -317,10 +300,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); + if (IS_INT64_EQUIVALENT(n)) { + mp_encode_int(buf,(int64_t)n); } else { - mp_encode_int(buf,(int64_t)n); + mp_encode_double(buf,(double)n); } } @@ -367,30 +350,41 @@ 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, max = 0, idx = 0; - lua_Number n; + long count; + /* + * Lua table keys for the array part are always ints, no need for longs. + * Stores the maximum positive integral index so far. + */ + int max; + /* Stack top on function entry */ + int stacktop; + + stacktop = lua_gettop(L); + + count = 0; + max = 0; lua_pushnil(L); while(lua_next(L,-2)) { /* Stack: ... key value */ - lua_pop(L,1); /* Stack: ... key */ - if (!lua_isnumber(L,-1)) goto not_array; - n = lua_tonumber(L,-1); - idx = n; - if (idx != n || idx < 1) goto not_array; + lua_Number n; count++; - max = idx; + lua_pop(L,1); /* Stack: ... key */ + if ( !lua_isnumber(L,-1) || + (n = lua_tonumber(L, -1)) <= 0 || + !IS_INT_EQUIVALENT(n) ) { + lua_settop(L, stacktop); + return 0; + } + max = (n > max ? n : max); } /* 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 * 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 @@ -405,6 +399,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); @@ -426,12 +421,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(); - - mp_encode_lua_type(L,buf,0); - lua_pushlstring(L,(char*)buf->b,buf->len); - mp_buf_free(buf); + int nargs = lua_gettop(L); + if(nargs == 0) { + lua_pushliteral(L, ""); + return 1; + } + + int i; + for(i = 1; i <= nargs; i++) { + lua_pushvalue(L, i); + + mp_buf *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; } @@ -658,6 +671,7 @@ static int mp_unpack(lua_State *L) { 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."); @@ -666,42 +680,55 @@ static int mp_unpack(lua_State *L) { 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); + 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); + } + } + mp_cur_free(c); - return 1; + return cnt; } /* ---------------------------------------------------------------------------- */ -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_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"); + int module_table_index; + + lua_newtable(L); + module_table_index = lua_gettop(L); + { + LUACOMPAT_REGISTER(L, 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"); + } + + LUACOMPAT_PUSH_GLOBAL_ENVIRONMENT(L); + lua_pushliteral(L, "cmsgpack"); + lua_pushvalue(L, module_table_index); + lua_settable(L, -3); + + lua_settop(L, module_table_index); return 1; } diff --git a/lua_cmsgpack.h b/lua_cmsgpack.h new file mode 100644 index 0000000..778c1ef --- /dev/null +++ b/lua_cmsgpack.h @@ -0,0 +1,50 @@ +#ifndef LUA_CMSGPACK_MAIN_HEADER_HPP +#define LUA_CMSGPACK_MAIN_HEADER_HPP + +#include +#include +#include +#include +#include + +#include "lua.h" +#include "lauxlib.h" + +#define LUACMSGPACK_VERSION "lua-cmsgpack 0.3.0" +#define LUACMSGPACK_COPYRIGHT "Copyright (C) 2012, Salvatore Sanfilippo" +#define LUACMSGPACK_DESCRIPTION "MessagePack C implementation for Lua" + +/* Allows defining as a preprocessor directive on compilation */ +#ifndef LUACMSGPACK_MAX_NESTING +# define LUACMSGPACK_MAX_NESTING 16 /* Max tables nesting. */ +#endif + + +/* + * Allows compilation under Lua 5.1 and 5.2 and avoids use of functions which have been deprecated in 5.2. + */ +#if LUA_VERSION_NUM >= 502 /* If higher than Lua 5.2, hopes for compatibility */ +# define LUACOMPAT_REGISTER(L,l) luaL_setfuncs(L,l,0) +# define lua_objlen(L, i) lua_rawlen(L, i) +# define LUACOMPAT_PUSH_GLOBAL_ENVIRONMENT(L) lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS) +#else /* If lower than Lua 5.1, hopes for compatibility */ +# define LUACOMPAT_REGISTER(L,l) luaL_register(L,NULL,l) +# define LUACOMPAT_PUSH_GLOBAL_ENVIRONMENT(L) lua_pushvalue(L, LUA_GLOBALSINDEX) +#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) + +#endif // lua_cmsgpack.h 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 8bec0f5..5033e3d 100644 --- a/test.lua +++ b/test.lua @@ -2,6 +2,8 @@ -- 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' + passed = 0 failed = 0 @@ -118,6 +120,8 @@ 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) -- The following test vectors are taken from the Javascript lib at: -- https://github.com/cuzic/MessagePack-JS/blob/master/test/test_pack.html