pax_global_header00006660000000000000000000000064132012603260014505gustar00rootroot0000000000000052 comment=3eda97c0ac4108c0030b70e4a4ea186542f67025 libmpack-lua-1.0.7/000077500000000000000000000000001320126032600140535ustar00rootroot00000000000000libmpack-lua-1.0.7/.gitignore000066400000000000000000000000351320126032600160410ustar00rootroot00000000000000.deps/ mpack-src/ *.src.rock libmpack-lua-1.0.7/.travis.yml000066400000000000000000000015351320126032600161700ustar00rootroot00000000000000sudo: false language: c env: global: - VERBOSE=1 matrix: - MPACK_LUA_VERSION=5.1.5 - MPACK_LUA_VERSION=5.2.4 - MPACK_LUA_VERSION=5.3.3 addons: apt: packages: - valgrind - liblua5.1-0-dev matrix: include: - os: linux env: USE_SYSTEM_LUA=1 script: - make ci-test - os: linux env: USE_SYSTEM_LUA=1 USE_SYSTEM_MPACK=1 script: - export PREFIX=$(pwd)/mpack-src/usr - export LD_LIBRARY_PATH=${PREFIX}/lib - export PKG_CONFIG_PATH=${PREFIX}/lib/pkgconfig - make mpack-src - make -C mpack-src config=release install - make ci-test - ldd -d mpack.so | grep -q ${PREFIX}/lib/libmpack script: make ci-test cache: directories: - .deps/5.1.5/usr - .deps/5.2.4/usr - .deps/5.3.3/usr after_failure: cat valgrind.log libmpack-lua-1.0.7/LICENSE-MIT000066400000000000000000000020441320126032600155070ustar00rootroot00000000000000Copyright (c) 2017 Thiago de Arruda 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. libmpack-lua-1.0.7/Makefile000066400000000000000000000113441320126032600155160ustar00rootroot00000000000000# makefile to setup environment for travis and development # distributors probably want to set this to 'yes' for both make and make install USE_SYSTEM_LUA ?= no USE_SYSTEM_MPACK ?= no ifneq ($(USE_SYSTEM_MPACK),no) # Can't use luarocks to build if linking against system libmpack because # apparently luarocks doesn't let you specify extra linker flags from the # command line USE_SYSTEM_LUA := 1 endif # Lua-related configuration MPACK_LUA_VERSION ?= 5.1.5 MPACK_LUA_VERSION_NOPATCH = $(basename $(MPACK_LUA_VERSION)) LUA_URL ?= https://lua.org/ftp/lua-$(MPACK_LUA_VERSION).tar.gz LUAROCKS_URL ?= https://github.com/keplerproject/luarocks/archive/v2.2.0.tar.gz LUA_TARGET ?= linux MPACK_VERSION ?= 1.0.5 MPACK_URL ?= https://github.com/tarruda/libmpack/archive/$(MPACK_VERSION).tar.gz LMPACK_VERSION != sed "/^local git_tag =/!d;s/[^']*'//;s/'\$$//;q" mpack-*.rockspec # deps location DEPS_DIR ?= $(CURDIR)/.deps/$(MPACK_LUA_VERSION) DEPS_PREFIX ?= $(DEPS_DIR)/usr DEPS_BIN ?= $(DEPS_PREFIX)/bin DEPS_CMOD ?= $(DEPS_PREFIX)/lib/lua/$(MPACK_LUA_VERSION_NOPATCH) # targets LUA ?= $(DEPS_BIN)/lua LUAROCKS ?= $(DEPS_BIN)/luarocks BUSTED ?= $(DEPS_BIN)/busted ifeq ($(USE_SYSTEM_LUA),no) MPACK ?= $(DEPS_CMOD)/mpack.so else MPACK ?= mpack.so endif # Compilation CC ?= gcc PKG_CONFIG ?= pkg-config CFLAGS ?= -ansi -O0 -g3 -Wall -Wextra -Werror -Wconversion \ -Wstrict-prototypes -Wno-unused-parameter -pedantic CFLAGS += -fPIC -DMPACK_DEBUG_REGISTRY_LEAK ifeq ($(MPACK_LUA_VERSION_NOPATCH),5.3) # Lua 5.3 has integer type, which is not 64 bits for -ansi since c89 doesn't # have `long long` type. CFLAGS += -DLUA_C89_NUMBERS endif LUA_IMPL ?= lua-$(MPACK_LUA_VERSION_NOPATCH) LUA_INCLUDE ?= $(shell $(PKG_CONFIG) --cflags $(LUA_IMPL) 2>/dev/null || echo "-I/usr/include/lua$(MPACK_LUA_VERSION_NOPATCH)") INCLUDES = $(LUA_INCLUDE) LIBS = ifeq ($(USE_SYSTEM_MPACK),no) MPACK_SRC = mpack-src else MPACK_SRC = LIBS += $(shell $(PKG_CONFIG) --libs mpack 2>/dev/null || echo "-lmpack") CFLAGS += -DMPACK_USE_SYSTEM $(shell $(PKG_CONFIG) --cflags mpack 2> /dev/null) endif LUA_CMOD_INSTALLDIR ?= $(shell $(PKG_CONFIG) --variable=INSTALL_CMOD $(LUA_IMPL) 2>/dev/null || echo "/usr/lib/lua/$(MPACK_LUA_VERSION_NOPATCH)") # Misc # Options used by the 'valgrind' target, which runs the tests under valgrind VALGRIND_OPTS ?= --error-exitcode=1 --log-file=valgrind.log --leak-check=yes \ --track-origins=yes # Command that will download a file and pipe it's contents to stdout FETCH ?= curl -L -o - # Command that will gunzip/untar a file from stdin to the current directory, # stripping one directory component UNTGZ ?= tar xfz - --strip-components=1 all: $(MPACK) mpack-src: dir="mpack-src"; \ mkdir -p $$dir && cd $$dir && \ $(FETCH) $(MPACK_URL) | $(UNTGZ) release: mpack-src rm -f libmpack-lua-$(LMPACK_VERSION).tar.gz tar cvfz libmpack-lua-$(LMPACK_VERSION).tar.gz \ --transform 's,^,libmpack-lua-$(LMPACK_VERSION)/,' \ mpack-*.rockspec lmpack.c mpack-src/src clean: rm -rf mpack-src *.tar.gz *.src.rock *.so *.o depsclean: rm -rf $(DEPS_DIR) test: $(BUSTED) $(MPACK) $(BUSTED) -o gtest test.lua valgrind: $(BUSTED) $(MPACK) eval $$($(LUAROCKS) path); \ valgrind $(VALGRIND_OPTS) $(LUA) \ $(DEPS_PREFIX)/lib/luarocks/rocks/busted/2.0.rc12-1/bin/busted test.lua ci-test: valgrind $(LUA) leak_test.lua gdb: $(BUSTED) $(MPACK) eval $$($(LUAROCKS) path); \ gdb -x .gdb --args $(LUA) \ $(DEPS_PREFIX)/lib/luarocks/rocks/busted/2.0.rc12-1/bin/busted test.lua $(DEPS_CMOD)/mpack.so: $(LUAROCKS) $(MPACK_SRC) lmpack.c $(LUAROCKS) make CFLAGS='$(CFLAGS)' $(LUAROCKS_LDFLAGS) mpack.so: lmpack.c $(MPACK_SRC) $(CC) -shared $(CFLAGS) $(INCLUDES) $(LDFLAGS) $< -o $@ $(LIBS) $(BUSTED): $(LUAROCKS) $(LUAROCKS) install penlight 1.3.2-2 $(LUAROCKS) install lua-term 0.7-1 $(LUAROCKS) install dkjson 2.5-2 $(LUAROCKS) install lua_cliargs 3.0-1 $(LUAROCKS) install say 1.3-1 $(LUAROCKS) install luafilesystem 1.6.3-2 $(LUAROCKS) install luassert 1.7.10-0 $(LUAROCKS) install mediator_lua 1.1.2-0 $(LUAROCKS) install luasystem 0.2.0-0 $(LUAROCKS) install busted 2.0.rc12-1 $(LUAROCKS) install inspect # helpful for debugging $(LUAROCKS): $(LUA) dir="$(DEPS_DIR)/src/luarocks"; \ mkdir -p $$dir && cd $$dir && \ $(FETCH) $(LUAROCKS_URL) | $(UNTGZ) && \ ./configure --prefix=$(DEPS_PREFIX) --force-config \ --with-lua=$(DEPS_PREFIX) && make bootstrap $(LUA): dir="$(DEPS_DIR)/src/lua"; \ mkdir -p $$dir && cd $$dir && \ $(FETCH) $(LUA_URL) | $(UNTGZ) && \ sed -i -e '/^CFLAGS/s/-O2/-g3/' src/Makefile && \ make $(LUA_TARGET) install INSTALL_TOP=$(DEPS_PREFIX) install: $(MPACK) ifeq ($(USE_SYSTEM_LUA),no) @: else mkdir -p "$(DESTDIR)$(LUA_CMOD_INSTALLDIR)" install -Dm755 $< "$(DESTDIR)$(LUA_CMOD_INSTALLDIR)/$<" endif .PHONY: all clean depsclean install test gdb valgrind ci-test release libmpack-lua-1.0.7/README.md000066400000000000000000000002371320126032600153340ustar00rootroot00000000000000## libmpack lua binding [![Travis Build Status](https://travis-ci.org/libmpack/libmpack-lua.svg?branch=master)](https://travis-ci.org/libmpack/libmpack-lua) libmpack-lua-1.0.7/cases.mpac000066400000000000000000000003251320126032600160130ustar00rootroot00000000000000ÂÃÀÌÍÎÏÐÑÒÓÿÐÿÑÿÿÒÿÿÿÿÓÿÿÿÿÿÿÿÿÌÍÿÎÿÿÏÿÿÿÿàÐàÑÿ€Òÿÿ€Óÿÿÿÿ€ËË€Ë?ðË¿ð¡aÚaÛa ÚÛ‘ÜÝÜÝ€Þß¡aaÞ¡aaß¡aa‘‘‘¡alibmpack-lua-1.0.7/cases_compact.mpac000066400000000000000000000001641320126032600175220ustar00rootroot00000000000000ÂÃÀÿÿÿÿÿÌÿÍÿÿÎÿÿÿÿààЀрҀËË€Ë?ðË¿ð¡a¡a¡a   ‘‘‘€€€¡aa¡aa¡aa‘‘‘¡alibmpack-lua-1.0.7/leak_test.lua000066400000000000000000000133331320126032600165340ustar00rootroot00000000000000-- sanity checks for memory leak(eg: leaving dead references alive in the -- registry) local mpack = require('mpack') local packed, unpacked function fhex(s) local rv = {} for h in s:gmatch('%x+') do rv[#rv + 1] = string.char(tonumber(h, 16)) end return table.concat(rv) end local Type = {} Type.__index = Type function Type.new(a, b, c) return setmetatable({a = a, b = b, c = c}, Type) end function Type:get_a() return self.a end function Type:get_b() return self.b end function Type:get_c() return self.c end local ext_unpackers = { [5] = function(code, str) local data = mpack.unpack(str) return Type.new(data._a, data._b, data._c) end } local ext_packers = { [Type] = function(obj) local data = {_a = obj.a, _b = obj.b, _c = obj.c} return 5, mpack.pack(data) end } function simple_unpack() local blob = io.open('cases.mpac'):read('*a') .. io.open('cases_compact.mpac'):read('*a') -- simple unpack local unpack = mpack.Unpacker() local pos = 1 unpacked = {} while pos <= #blob do local item item, pos = unpack(blob, pos) unpacked[#unpacked + 1] = item end end function simple_pack() local items = { false, true, mpack.NIL, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, 127, 127, 255, 65535, 4294967295, -32, -32, -128, -32768, -2147483648, 0, -0, 1, -1, "a", "a", "a", "", "", "", { 0 }, { 0 }, { 0 }, {}, {}, {}, {}, {}, {}, { a = 97 }, { a = 97 }, { a = 97 }, { {} }, { { "a" } }, false, true, mpack.NIL, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, 127, 127, 255, 65535, 4294967295, -32, -32, -128, -32768, -2147483648, 0, -0, 1, -1, "a", "a", "a", "", "", "", { 0 }, { 0 }, { 0 }, {}, {}, {}, {}, {}, {}, { a = 97 }, { a = 97 }, { a = 97 }, { {} }, { { "a" } } } local pack = mpack.Packer() local chunks = {} for i = 1, #items do chunks[#chunks + 1] = pack(items[i]) end packed = pack(items) assert(table.concat(chunks, '') == packed:sub(4)) end function unpack_chunks() local unpack = mpack.Unpacker() local blob = io.open('cases.mpac'):read('*a') .. io.open('cases_compact.mpac'):read('*a') while #blob > 1 do unpacked = unpack(blob:sub(1, 1)) blob = blob:sub(2) end unpacked = unpack(blob) end function pack_unpack_big_table() local arr = {} for i = 1, 10000 do table.insert(arr, ('item%d'):format(i)) end local unpack = mpack.Unpacker() local pack = mpack.Packer() packed = pack(arr) unpacked = unpack(packed) end function pack_unpack_ext() local unpacker = mpack.Unpacker({ext = ext_unpackers}) local packer = mpack.Packer({ext = ext_packers}) local t1 = Type.new('field a', 2, {1, 2}) local t2 = unpacker(packer({k={1,t1}})).k[2] assert(t1:get_a() == t2:get_a()) assert(t1:get_b() == t2:get_b()) end function cyclic_ref() local tbl1 = {1} table.insert(tbl1, tbl1) table.insert(tbl1, 2) local tbl2 = {1, 2, 3} local tbl3 = {4, 5, tbl2, 6} local tbl4 = {7, tbl3} table.insert(tbl2, tbl4) local unpack = mpack.Unpacker() local pack = mpack.Packer() unpack(pack(tbl1)) unpack(pack(tbl2)) end function rpc() local session = mpack.Session() session:request('req1') session:request('req2') session:request('req3') session:notify() session:receive('\148\000\000') session:receive('\148\000\001') session:receive('\147\002') session:receive('\148\001\002') session:receive('\148\001\000') session:receive('\148\001\001') session:receive('\148') session:receive('\000') session:receive('\007') session:request('req4') session:receive('\148') session:receive('\001') session:receive('\003') session:receive('\147') session:receive('\002') local failed = true pcall(function() session:receive('\148\001\009') failed = false end) assert(failed) end function rpc_unpack() local session = mpack.Session({unpack = mpack.Unpacker()}) session:receive(fhex('94 00 00 a6 6d 65 74 68 6f 64 92 01 a1 61')) session:request('req1') session:receive(fhex('94 01 00 c0 a6 72 65 73 75 6c 74')) session:receive(fhex('94')) session:receive(fhex('00')) session:receive(fhex('01')) session:receive(fhex('a6')) session:receive(fhex('6d')) session:receive(fhex('65')) session:receive(fhex('74')) session:receive(fhex('68')) session:receive(fhex('6f')) session:receive(fhex('64')) session:receive(fhex('92')) session:receive(fhex('01')) session:receive(fhex('a1')) session:receive(fhex('61')) session:request('req2') session:receive(fhex('94')) session:receive(fhex('01')) session:receive(fhex('01')) session:receive(fhex('c0')) session:receive(fhex('a6')) session:receive(fhex('72')) session:receive(fhex('65')) session:receive(fhex('73')) session:receive(fhex('75')) session:receive(fhex('6c')) session:receive(fhex('74')) session:receive(fhex('93 02 a6 6d 65 74 68 6f 64 92 01 a1 61')) session:receive(fhex('93')) session:receive(fhex('02')) session:receive(fhex('a6')) session:receive(fhex('6d')) session:receive(fhex('65')) session:receive(fhex('74')) session:receive(fhex('68')) session:receive(fhex('6f')) session:receive(fhex('64')) session:receive(fhex('92')) session:receive(fhex('01')) session:receive(fhex('a1')) session:receive(fhex('61')) end function run() simple_unpack() simple_pack() unpack_chunks() pack_unpack_big_table() pack_unpack_ext() cyclic_ref() rpc() rpc_unpack() end function collect() -- run multiple times to ensure lua has a chance to reclaim all unused memomry for i = 1, 10 do collectgarbage() end return collectgarbage('count') end run() -- run once to "initialize" the state before = collect() run() after = collect() if before ~= after then print('fail .. leaked '..((after - before) * 1024)..' kilobytes') os.exit(1) else print('ok') os.exit(0) end libmpack-lua-1.0.7/lmpack.c000066400000000000000000000774121320126032600155010ustar00rootroot00000000000000/* * This module exports three classes, and each instance of those classes has its * own private registry for temporary reference storage(keeping state between * calls). A private registry makes managing memory much easier since all we * have to do is call luaL_unref passing the registry reference when the * instance is collected by the __gc metamethod. * * This private registry is manipulated with `lmpack_ref` / `lmpack_unref` / * `lmpack_geti`, which are analogous to `luaL_ref` / `luaL_unref` / * `lua_rawgeti` but operate on the private registry passed as argument. * * In order to simplify debug registry leaks during normal operation(with the * leak_test.lua script), these `lmpack_*` registry functions will target the * normal lua registry when MPACK_DEBUG_REGISTRY_LEAK is defined during * compilation. */ #define LUA_LIB #include #include #include #include #include #include #ifdef MPACK_USE_SYSTEM # include #else # define MPACK_API static # include "mpack-src/src/mpack.c" #endif #define UNPACKER_META_NAME "mpack.Unpacker" #define PACKER_META_NAME "mpack.Packer" #define SESSION_META_NAME "mpack.Session" #define NIL_NAME "mpack.NIL" /* * TODO(tarruda): When targeting lua 5.3 and being compiled with `long long` * support(not -ansi), we should make use of lua 64 bit integers for * representing msgpack integers, since `double` can't represent the full range. */ #ifndef luaL_reg /* Taken from Lua5.1's lauxlib.h */ #define luaL_reg luaL_Reg #endif #if LUA_VERSION_NUM > 501 #ifndef luaL_register #define luaL_register(L,n,f) luaL_setfuncs(L,f,0) #endif #endif typedef struct { lua_State *L; mpack_parser_t *parser; int reg, ext, unpacking; char *string_buffer; } Unpacker; typedef struct { lua_State *L; mpack_parser_t *parser; int reg, ext, root, packing; int is_bin, is_bin_fn; } Packer; typedef struct { lua_State *L; int reg; mpack_rpc_session_t *session; struct { int type; mpack_rpc_message_t msg; int method_or_error; int args_or_result; } unpacked; int unpacker; } Session; static int lmpack_ref(lua_State *L, int reg) { #ifdef MPACK_DEBUG_REGISTRY_LEAK return luaL_ref(L, LUA_REGISTRYINDEX); #else int rv; lua_rawgeti(L, LUA_REGISTRYINDEX, reg); lua_pushvalue(L, -2); rv = luaL_ref(L, -2); lua_pop(L, 2); return rv; #endif } static void lmpack_unref(lua_State *L, int reg, int ref) { #ifdef MPACK_DEBUG_REGISTRY_LEAK luaL_unref(L, LUA_REGISTRYINDEX, ref); #else lua_rawgeti(L, LUA_REGISTRYINDEX, reg); luaL_unref(L, -1, ref); lua_pop(L, 1); #endif } static void lmpack_geti(lua_State *L, int reg, int ref) { #ifdef MPACK_DEBUG_REGISTRY_LEAK lua_rawgeti(L, LUA_REGISTRYINDEX, ref); #else lua_rawgeti(L, LUA_REGISTRYINDEX, reg); lua_rawgeti(L, -1, ref); lua_replace(L, -2); #endif } /* make a shallow copy of the table on stack and remove it after the copy is * done */ static void lmpack_shallow_copy(lua_State *L) { lua_newtable(L); lua_pushnil(L); while (lua_next(L, -3)) { lua_pushvalue(L, -2); lua_insert(L, -2); lua_settable(L, -4); } lua_remove(L, -2); } static mpack_parser_t *lmpack_grow_parser(mpack_parser_t *parser) { mpack_parser_t *old = parser; mpack_uint32_t new_capacity = old->capacity * 2; parser = malloc(MPACK_PARSER_STRUCT_SIZE(new_capacity)); if (!parser) goto end; mpack_parser_init(parser, new_capacity); mpack_parser_copy(parser, old); free(old); end: return parser; } static mpack_rpc_session_t *lmpack_grow_session(mpack_rpc_session_t *session) { mpack_rpc_session_t *old = session; mpack_uint32_t new_capacity = old->capacity * 2; session = malloc(MPACK_RPC_SESSION_STRUCT_SIZE(new_capacity)); if (!session) goto end; mpack_rpc_session_init(session, new_capacity); mpack_rpc_session_copy(session, old); free(old); end: return session; } static Unpacker *lmpack_check_unpacker(lua_State *L, int index) { return luaL_checkudata(L, index, UNPACKER_META_NAME); } static Packer *lmpack_check_packer(lua_State *L, int index) { return luaL_checkudata(L, index, PACKER_META_NAME); } static Session *lmpack_check_session(lua_State *L, int index) { return luaL_checkudata(L, index, SESSION_META_NAME); } static int lmpack_isnil(lua_State *L, int index) { int rv; if (!lua_isuserdata(L, index)) return 0; lua_getfield(L, LUA_REGISTRYINDEX, NIL_NAME); rv = lua_rawequal(L, -1, -2); lua_pop(L, 1); return rv; } static int lmpack_isunpacker(lua_State *L, int index) { int rv; if (!lua_isuserdata(L, index) || !lua_getmetatable(L, index)) return 0; luaL_getmetatable(L, UNPACKER_META_NAME); rv = lua_rawequal(L, -1, -2); lua_pop(L, 2); return rv; } static void lmpack_pushnil(lua_State *L) { lua_getfield(L, LUA_REGISTRYINDEX, NIL_NAME); } /* adapted from * https://github.com/antirez/lua-cmsgpack/blob/master/lua_cmsgpack.c */ static mpack_uint32_t lmpack_objlen(lua_State *L, int *is_array) { size_t len, max; int isarr, type, top; lua_Number n; assert(top = lua_gettop(L)); if ((type = lua_type(L, -1)) != LUA_TTABLE) { #if LUA_VERSION_NUM >= 502 len = lua_rawlen(L, -1); #elif LUA_VERSION_NUM == 501 len = lua_objlen(L, -1); #else #error You have either broken or too old Lua installation. This library requires Lua>=5.1 #endif goto end; } /* count the number of keys and determine if it is an array */ len = 0; max = 0; isarr = 1; lua_pushnil(L); while (lua_next(L, -2)) { lua_pop(L, 1); /* pop value */ isarr = isarr && lua_isnumber(L, -1) /* lua number */ && (n = lua_tonumber(L, -1)) > 0 /* greater than 0 */ && (size_t)n == n; /* and integer */ max = isarr && (size_t)n > max ? (size_t)n : max; len++; } *is_array = isarr && max == len; end: if ((size_t)-1 > (mpack_uint32_t)-1 && len > (mpack_uint32_t)-1) /* msgpack spec doesn't allow lengths > 32 bits */ len = (mpack_uint32_t)-1; assert(top == lua_gettop(L)); return (mpack_uint32_t)len; } static int lmpack_unpacker_new(lua_State *L) { Unpacker *rv; if (lua_gettop(L) > 1) return luaL_error(L, "expecting at most 1 table argument"); rv = lua_newuserdata(L, sizeof(*rv)); rv->parser = malloc(sizeof(*rv->parser)); if (!rv->parser) return luaL_error(L, "Failed to allocate memory"); mpack_parser_init(rv->parser, 0); rv->parser->data.p = rv; rv->string_buffer = NULL; rv->L = L; rv->unpacking = 0; luaL_getmetatable(L, UNPACKER_META_NAME); lua_setmetatable(L, -2); #ifndef MPACK_DEBUG_REGISTRY_LEAK lua_newtable(L); rv->reg = luaL_ref(L, LUA_REGISTRYINDEX); #endif rv->ext = LUA_NOREF; if (lua_istable(L, 1)) { /* parse options */ lua_getfield(L, 1, "ext"); if (!lua_isnil(L, -1)) { if (!lua_istable(L, -1)) return luaL_error(L, "\"ext\" option must be a table"); lmpack_shallow_copy(L); } rv->ext = lmpack_ref(L, rv->reg); } return 1; } static int lmpack_unpacker_delete(lua_State *L) { Unpacker *unpacker = lmpack_check_unpacker(L, 1); if (unpacker->ext != LUA_NOREF) lmpack_unref(L, unpacker->reg, unpacker->ext); #ifndef MPACK_DEBUG_REGISTRY_LEAK luaL_unref(L, LUA_REGISTRYINDEX, unpacker->reg); #endif free(unpacker->parser); return 0; } static void lmpack_parse_enter(mpack_parser_t *parser, mpack_node_t *node) { Unpacker *unpacker = parser->data.p; lua_State *L = unpacker->L; switch (node->tok.type) { case MPACK_TOKEN_NIL: lmpack_pushnil(L); break; case MPACK_TOKEN_BOOLEAN: lua_pushboolean(L, (int)mpack_unpack_boolean(node->tok)); break; case MPACK_TOKEN_UINT: case MPACK_TOKEN_SINT: case MPACK_TOKEN_FLOAT: lua_pushnumber(L, mpack_unpack_number(node->tok)); break; case MPACK_TOKEN_CHUNK: assert(unpacker->string_buffer); memcpy(unpacker->string_buffer + MPACK_PARENT_NODE(node)->pos, node->tok.data.chunk_ptr, node->tok.length); break; case MPACK_TOKEN_BIN: case MPACK_TOKEN_STR: case MPACK_TOKEN_EXT: unpacker->string_buffer = malloc(node->tok.length); if (!unpacker->string_buffer) luaL_error(L, "Failed to allocate memory"); break; case MPACK_TOKEN_ARRAY: case MPACK_TOKEN_MAP: lua_newtable(L); node->data[0].i = lmpack_ref(L, unpacker->reg); break; } } static void lmpack_parse_exit(mpack_parser_t *parser, mpack_node_t *node) { Unpacker *unpacker = parser->data.p; lua_State *L = unpacker->L; mpack_node_t *parent = MPACK_PARENT_NODE(node); switch (node->tok.type) { case MPACK_TOKEN_BIN: case MPACK_TOKEN_STR: case MPACK_TOKEN_EXT: lua_pushlstring(L, unpacker->string_buffer, node->tok.length); free(unpacker->string_buffer); unpacker->string_buffer = NULL; if (node->tok.type == MPACK_TOKEN_EXT && unpacker->ext != LUA_NOREF) { /* check if there's a handler for this type */ lmpack_geti(L, unpacker->reg, unpacker->ext); lua_rawgeti(L, -1, node->tok.data.ext_type); if (lua_isfunction(L, -1)) { /* stack: * * -1: ext unpacker function * -2: ext unpackers table * -3: ext string * * We want to call the ext unpacker function with the type and string * as arguments, so push those now */ lua_pushinteger(L, node->tok.data.ext_type); lua_pushvalue(L, -4); lua_call(L, 2, 1); /* stack: * * -1: returned object * -2: ext unpackers table * -3: ext string */ lua_replace(L, -3); } else { /* the last lua_rawgeti should have pushed nil on the stack, * remove it */ lua_pop(L, 1); } /* pop the ext unpackers table */ lua_pop(L, 1); } break; case MPACK_TOKEN_ARRAY: case MPACK_TOKEN_MAP: lmpack_geti(L, unpacker->reg, (int)node->data[0].i); lmpack_unref(L, unpacker->reg, (int)node->data[0].i); break; default: break; } if (parent && parent->tok.type < MPACK_TOKEN_BIN) { /* At this point the parsed object is on the stack. Add it to the parent * container. First put the container on the stack. */ lmpack_geti(L, unpacker->reg, (int)parent->data[0].i); if (parent->tok.type == MPACK_TOKEN_ARRAY) { /* Array, save the value on key equal to `parent->pos` */ lua_pushnumber(L, (lua_Number)parent->pos); lua_pushvalue(L, -3); lua_settable(L, -3); } else { assert(parent->tok.type == MPACK_TOKEN_MAP); if (parent->key_visited) { /* save the key on the registry */ lua_pushvalue(L, -2); parent->data[1].i = lmpack_ref(L, unpacker->reg); } else { /* set the key/value pair */ lmpack_geti(L, unpacker->reg, (int)parent->data[1].i); lmpack_unref(L, unpacker->reg, (int)parent->data[1].i); lua_pushvalue(L, -3); lua_settable(L, -3); } } lua_pop(L, 2); /* pop the container/object */ } } static int lmpack_unpacker_unpack_str(lua_State *L, Unpacker *unpacker, const char **str, size_t *len) { int rv; if (unpacker->unpacking) { return luaL_error(L, "Unpacker instance already working. Use another " "Unpacker or the module's \"unpack\" function if you " "need to unpack from the ext handler"); } do { unpacker->unpacking = 1; rv = mpack_parse(unpacker->parser, str, len, lmpack_parse_enter, lmpack_parse_exit); unpacker->unpacking = 0; if (rv == MPACK_NOMEM) { unpacker->parser = lmpack_grow_parser(unpacker->parser); if (!unpacker->parser) { unpacker->unpacking = 0; return luaL_error(L, "failed to grow Unpacker capacity"); } } } while (rv == MPACK_NOMEM); if (rv == MPACK_ERROR) return luaL_error(L, "invalid msgpack string"); return rv; } static int lmpack_unpacker_unpack(lua_State *L) { int result, argc; lua_Number startpos; size_t len, offset; const char *str, *str_init; Unpacker *unpacker; if ((argc = lua_gettop(L)) > 3 || argc < 2) return luaL_error(L, "expecting between 2 and 3 arguments"); unpacker = lmpack_check_unpacker(L, 1); unpacker->L = L; str_init = str = luaL_checklstring(L, 2, &len); startpos = lua_gettop(L) == 3 ? luaL_checknumber(L, 3) : 1; luaL_argcheck(L, startpos > 0, 3, "start position must be greater than zero"); luaL_argcheck(L, (size_t)startpos == startpos, 3, "start position must be an integer"); luaL_argcheck(L, (size_t)startpos <= len, 3, "start position must be less than or equal to the input string length"); offset = (size_t)startpos - 1 ; str += offset; len -= offset; result = lmpack_unpacker_unpack_str(L, unpacker, &str, &len); if (result == MPACK_EOF) /* if we hit EOF, return nil as the object */ lua_pushnil(L); /* also return the new position in the input string */ lua_pushinteger(L, str - str_init + 1); assert(lua_gettop(L) == argc + 2); return 2; } static int lmpack_packer_new(lua_State *L) { Packer *rv; if (lua_gettop(L) > 1) return luaL_error(L, "expecting at most 1 table argument"); rv = lua_newuserdata(L, sizeof(*rv)); rv->parser = malloc(sizeof(*rv->parser)); if (!rv->parser) return luaL_error(L, "failed to allocate parser memory"); mpack_parser_init(rv->parser, 0); rv->parser->data.p = rv; rv->L = L; rv->packing = 0; rv->is_bin = 0; rv->is_bin_fn = LUA_NOREF; luaL_getmetatable(L, PACKER_META_NAME); lua_setmetatable(L, -2); #ifndef MPACK_DEBUG_REGISTRY_LEAK lua_newtable(L); rv->reg = luaL_ref(L, LUA_REGISTRYINDEX); #endif rv->ext = LUA_NOREF; if (lua_istable(L, 1)) { /* parse options */ lua_getfield(L, 1, "ext"); if (!lua_isnil(L, -1)) { if (!lua_istable(L, -1)) return luaL_error(L, "\"ext\" option must be a table"); lmpack_shallow_copy(L); } rv->ext = lmpack_ref(L, rv->reg); lua_getfield(L, 1, "is_bin"); if (!lua_isnil(L, -1)) { if (!lua_isboolean(L, -1) && !lua_isfunction(L, -1)) return luaL_error(L, "\"is_bin\" option must be a boolean or function"); rv->is_bin = lua_toboolean(L, -1); if (lua_isfunction(L, -1)) rv->is_bin_fn = lmpack_ref(L, rv->reg); else lua_pop(L, 1); } else { lua_pop(L, 1); } } return 1; } static int lmpack_packer_delete(lua_State *L) { Packer *packer = lmpack_check_packer(L, 1); if (packer->ext != LUA_NOREF) lmpack_unref(L, packer->reg, packer->ext); #ifndef MPACK_DEBUG_REGISTRY_LEAK luaL_unref(L, LUA_REGISTRYINDEX, packer->reg); #endif free(packer->parser); return 0; } static void lmpack_unparse_enter(mpack_parser_t *parser, mpack_node_t *node) { int type; Packer *packer = parser->data.p; lua_State *L = packer->L; mpack_node_t *parent = MPACK_PARENT_NODE(node); if (parent) { /* get the parent */ lmpack_geti(L, packer->reg, (int)parent->data[0].i); if (parent->tok.type > MPACK_TOKEN_MAP) { /* strings are a special case, they are packed as single child chunk * node */ const char *str = lua_tolstring(L, -1, NULL); node->tok = mpack_pack_chunk(str, parent->tok.length); lua_pop(L, 1); return; } if (parent->tok.type == MPACK_TOKEN_ARRAY) { /* push the next index */ lua_pushnumber(L, (lua_Number)(parent->pos + 1)); /* push the element */ lua_gettable(L, -2); } else if (parent->tok.type == MPACK_TOKEN_MAP) { int result; /* push the previous key */ lmpack_geti(L, packer->reg, (int)parent->data[1].i); /* push the pair */ result = lua_next(L, -2); assert(result); /* should not be here if the map was fully processed */ if (parent->key_visited) { /* release the current key */ lmpack_unref(L, packer->reg, (int)parent->data[1].i); /* push key to the top */ lua_pushvalue(L, -2); /* set the key for the next iteration, leaving value on top */ parent->data[1].i = lmpack_ref(L, packer->reg); /* replace key by the value */ lua_replace(L, -2); } else { /* pop value */ lua_pop(L, 1); } } /* remove parent, leaving only the object which will be serialized */ lua_remove(L, -2); } else { /* root object */ lmpack_geti(L, packer->reg, packer->root); } type = lua_type(L, -1); switch (type) { case LUA_TBOOLEAN: node->tok = mpack_pack_boolean((unsigned)lua_toboolean(L, -1)); break; case LUA_TNUMBER: node->tok = mpack_pack_number(lua_tonumber(L, -1)); break; case LUA_TSTRING: { int is_bin = packer->is_bin; if (is_bin && packer->is_bin_fn != LUA_NOREF) { lmpack_geti(L, packer->reg, packer->is_bin_fn); lua_pushvalue(L, -2); lua_call(L, 1, 1); is_bin = lua_toboolean(L, -1); lua_pop(L, 1); } if (is_bin) node->tok = mpack_pack_bin(lmpack_objlen(L, NULL)); else node->tok = mpack_pack_str(lmpack_objlen(L, NULL)); break; } case LUA_TTABLE: { int is_array; mpack_uint32_t len; mpack_node_t *n; if (packer->ext != LUA_NOREF && lua_getmetatable(L, -1)) { /* check if there's a handler for this metatable */ lmpack_geti(L, packer->reg, packer->ext); lua_pushvalue(L, -2); lua_gettable(L, -2); if (lua_isfunction(L, -1)) { lua_Number ext = -1; /* stack: * * -1: ext packer function * -2: ext packers table * -3: metatable * -4: original object * * We want to call the ext packer function with the original object as * argument, so push it on the top */ lua_pushvalue(L, -4); /* handler should return type code and string */ lua_call(L, 1, 2); if (!lua_isnumber(L, -2) || (ext = lua_tonumber(L, -2)) < 0 || ext > 127 || (int)ext != ext) luaL_error(L, "the first result from ext packer must be an integer " "between 0 and 127"); if (!lua_isstring(L, -1)) luaL_error(L, "the second result from ext packer must be a string"); node->tok = mpack_pack_ext((int)ext, lmpack_objlen(L, NULL)); /* stack: * * -1: ext string * -2: ext type * -3: ext packers table * -4: metatable * -5: original table * * We want to leave only the returned ext string, so * replace -5 with the string and pop 3 */ lua_replace(L, -5); lua_pop(L, 3); break; /* done */ } else { /* stack: * * -1: ext packers table * -2: metatable * -3: original table * * We want to leave only the original table since it will be handled * below, so pop 2 */ lua_pop(L, 2); } } /* check for cycles */ n = node; while ((n = MPACK_PARENT_NODE(n))) { lmpack_geti(L, packer->reg, (int)n->data[0].i); if (lua_rawequal(L, -1, -2)) { /* break out of cycles with NIL */ node->tok = mpack_pack_nil(); lua_pop(L, 2); lmpack_pushnil(L); goto end; } lua_pop(L, 1); } len = lmpack_objlen(L, &is_array); if (is_array) { node->tok = mpack_pack_array(len); } else { node->tok = mpack_pack_map(len); /* save nil as the previous key to start iteration */ node->data[1].i = LUA_REFNIL; } break; } case LUA_TUSERDATA: if (lmpack_isnil(L, -1)) { node->tok = mpack_pack_nil(); break; } /* Fallthrough */ default: luaL_error(L, "can't serialize object"); } end: node->data[0].i = lmpack_ref(L, packer->reg); } static void lmpack_unparse_exit(mpack_parser_t *parser, mpack_node_t *node) { Packer *packer = parser->data.p; lua_State *L = packer->L; if (node->tok.type != MPACK_TOKEN_CHUNK) { /* release the object */ lmpack_unref(L, packer->reg, (int)node->data[0].i); if (node->tok.type == MPACK_TOKEN_MAP) lmpack_unref(L, packer->reg, (int)node->data[1].i); } } static int lmpack_packer_pack(lua_State *L) { char *b; size_t bl; int result, argc; Packer *packer; luaL_Buffer buffer; if ((argc = lua_gettop(L)) != 2) return luaL_error(L, "expecting exactly 2 arguments"); packer = lmpack_check_packer(L, 1); packer->L = L; packer->root = lmpack_ref(L, packer->reg); luaL_buffinit(L, &buffer); b = luaL_prepbuffer(&buffer); bl = LUAL_BUFFERSIZE; if (packer->packing) { return luaL_error(L, "Packer instance already working. Use another Packer " "or the module's \"pack\" function if you need to " "pack from the ext handler"); } do { size_t bl_init = bl; packer->packing = 1; result = mpack_unparse(packer->parser, &b, &bl, lmpack_unparse_enter, lmpack_unparse_exit); packer->packing = 0; if (result == MPACK_NOMEM) { packer->parser = lmpack_grow_parser(packer->parser); if (!packer->parser) { packer->packing = 0; return luaL_error(L, "Failed to grow Packer capacity"); } } luaL_addsize(&buffer, bl_init - bl); if (!bl) { /* buffer empty, resize */ b = luaL_prepbuffer(&buffer); bl = LUAL_BUFFERSIZE; } } while (result == MPACK_EOF || result == MPACK_NOMEM); lmpack_unref(L, packer->reg, packer->root); luaL_pushresult(&buffer); assert(lua_gettop(L) == argc); return 1; } static int lmpack_session_new(lua_State *L) { Session *rv = lua_newuserdata(L, sizeof(*rv)); rv->session = malloc(sizeof(*rv->session)); if (!rv->session) return luaL_error(L, "Failed to allocate memory"); mpack_rpc_session_init(rv->session, 0); rv->L = L; luaL_getmetatable(L, SESSION_META_NAME); lua_setmetatable(L, -2); #ifndef MPACK_DEBUG_REGISTRY_LEAK lua_newtable(L); rv->reg = luaL_ref(L, LUA_REGISTRYINDEX); #endif rv->unpacker = LUA_REFNIL; rv->unpacked.args_or_result = LUA_NOREF; rv->unpacked.method_or_error = LUA_NOREF; rv->unpacked.type = MPACK_EOF; if (lua_istable(L, 1)) { /* parse options */ lua_getfield(L, 1, "unpack"); if (!lmpack_isunpacker(L, -1)) { return luaL_error(L, "\"unpack\" option must be a " UNPACKER_META_NAME " instance"); } rv->unpacker = lmpack_ref(L, rv->reg); } return 1; } static int lmpack_session_delete(lua_State *L) { Session *session = lmpack_check_session(L, 1); lmpack_unref(L, session->reg, session->unpacker); #ifndef MPACK_DEBUG_REGISTRY_LEAK luaL_unref(L, LUA_REGISTRYINDEX, session->reg); #endif free(session->session); return 0; } static int lmpack_session_receive(lua_State *L) { int argc, done, rcount = 3; lua_Number startpos; size_t len; const char *str, *str_init; Session *session; Unpacker *unpacker = NULL; if ((argc = lua_gettop(L)) > 3 || argc < 2) return luaL_error(L, "expecting between 2 and 3 arguments"); session = lmpack_check_session(L, 1); str_init = str = luaL_checklstring(L, 2, &len); startpos = lua_gettop(L) == 3 ? luaL_checknumber(L, 3) : 1; luaL_argcheck(L, startpos > 0, 3, "start position must be greater than zero"); luaL_argcheck(L, (size_t)startpos == startpos, 3, "start position must be an integer"); luaL_argcheck(L, (size_t)startpos <= len, 3, "start position must be less than or equal to the input string length"); str += (size_t)startpos - 1; if (session->unpacker != LUA_REFNIL) { lmpack_geti(L, session->reg, session->unpacker); unpacker = lmpack_check_unpacker(L, -1); unpacker->L = L; rcount += 2; lua_pop(L, 1); } for (;;) { int result; if (session->unpacked.type == MPACK_EOF) { session->unpacked.type = mpack_rpc_receive(session->session, &str, &len, &session->unpacked.msg); if (!unpacker || session->unpacked.type == MPACK_EOF) break; } result = lmpack_unpacker_unpack_str(L, unpacker, &str, &len); if (result == MPACK_EOF) break; if (session->unpacked.method_or_error == LUA_NOREF) { session->unpacked.method_or_error = lmpack_ref(L, session->reg); } else { session->unpacked.args_or_result = lmpack_ref(L, session->reg); break; } } done = session->unpacked.type != MPACK_EOF && (session->unpacked.args_or_result != LUA_NOREF || !unpacker); if (!done) { lua_pushnil(L); lua_pushnil(L); if (unpacker) { lua_pushnil(L); lua_pushnil(L); } goto end; } switch (session->unpacked.type) { case MPACK_RPC_REQUEST: lua_pushstring(L, "request"); lua_pushnumber(L, session->unpacked.msg.id); break; case MPACK_RPC_RESPONSE: lua_pushstring(L, "response"); lmpack_geti(L, session->reg, (int)session->unpacked.msg.data.i); break; case MPACK_RPC_NOTIFICATION: lua_pushstring(L, "notification"); lua_pushnil(L); break; default: /* In most cases the only sane thing to do when receiving invalid * msgpack-rpc is to close the connection, so handle all errors with * this generic message. Later may add more detailed information. */ return luaL_error(L, "invalid msgpack-rpc string"); } session->unpacked.type = MPACK_EOF; if (unpacker) { lmpack_geti(L, session->reg, session->unpacked.method_or_error); lmpack_geti(L, session->reg, session->unpacked.args_or_result); lmpack_unref(L, session->reg, session->unpacked.method_or_error); lmpack_unref(L, session->reg, session->unpacked.args_or_result); session->unpacked.method_or_error = LUA_NOREF; session->unpacked.args_or_result = LUA_NOREF; } end: lua_pushinteger(L, str - str_init + 1); return rcount; } static int lmpack_session_request(lua_State *L) { int result; char buf[16], *b = buf; size_t bl = sizeof(buf); Session *session; mpack_data_t data; if (lua_gettop(L) > 2 || lua_gettop(L) < 1) return luaL_error(L, "expecting 1 or 2 arguments"); session = lmpack_check_session(L, 1); data.i = lua_isnoneornil(L, 2) ? LUA_NOREF : lmpack_ref(L, session->reg); do { result = mpack_rpc_request(session->session, &b, &bl, data); if (result == MPACK_NOMEM) { session->session = lmpack_grow_session(session->session); if (!session->session) return luaL_error(L, "Failed to grow Session capacity"); } } while (result == MPACK_NOMEM); assert(result == MPACK_OK); lua_pushlstring(L, buf, sizeof(buf) - bl); return 1; } static int lmpack_session_reply(lua_State *L) { int result; char buf[16], *b = buf; size_t bl = sizeof(buf); Session *session; lua_Number id; if (lua_gettop(L) != 2) return luaL_error(L, "expecting exactly 2 arguments"); session = lmpack_check_session(L, 1); id = lua_tonumber(L, 2); luaL_argcheck(L, ((size_t)id == id && id >= 0 && id <= 0xffffffff), 2, "invalid request id"); result = mpack_rpc_reply(session->session, &b, &bl, (mpack_uint32_t)id); assert(result == MPACK_OK); lua_pushlstring(L, buf, sizeof(buf) - bl); return 1; } static int lmpack_session_notify(lua_State *L) { int result; char buf[16], *b = buf; size_t bl = sizeof(buf); Session *session; if (lua_gettop(L) != 1) return luaL_error(L, "expecting exactly 1 argument"); session = lmpack_check_session(L, 1); result = mpack_rpc_notify(session->session, &b, &bl); assert(result == MPACK_OK); lua_pushlstring(L, buf, sizeof(buf) - bl); return 1; } static int lmpack_nil_tostring(lua_State* L) { lua_pushfstring(L, NIL_NAME, lua_topointer(L, 1)); return 1; } static int lmpack_unpack(lua_State *L) { int result; size_t len; const char *str; Unpacker unpacker; mpack_parser_t parser; if (lua_gettop(L) != 1) return luaL_error(L, "expecting exactly 1 argument"); str = luaL_checklstring(L, 1, &len); /* initialize unpacker */ lua_newtable(L); unpacker.reg = luaL_ref(L, LUA_REGISTRYINDEX); unpacker.ext = LUA_NOREF; unpacker.parser = &parser; mpack_parser_init(unpacker.parser, 0); unpacker.parser->data.p = &unpacker; unpacker.string_buffer = NULL; unpacker.L = L; result = mpack_parse(&parser, &str, &len, lmpack_parse_enter, lmpack_parse_exit); luaL_unref(L, LUA_REGISTRYINDEX, unpacker.reg); if (result == MPACK_NOMEM) return luaL_error(L, "object was too deep to unpack"); else if (result == MPACK_EOF) return luaL_error(L, "incomplete msgpack string"); else if (result == MPACK_ERROR) return luaL_error(L, "invalid msgpack string"); else if (result == MPACK_OK && len) return luaL_error(L, "trailing data in msgpack string"); assert(result == MPACK_OK); return 1; } static int lmpack_pack(lua_State *L) { char *b; size_t bl; int result; Packer packer; mpack_parser_t parser; luaL_Buffer buffer; if (lua_gettop(L) != 1) return luaL_error(L, "expecting exactly 1 argument"); /* initialize packer */ lua_newtable(L); packer.reg = luaL_ref(L, LUA_REGISTRYINDEX); packer.ext = LUA_NOREF; packer.parser = &parser; mpack_parser_init(packer.parser, 0); packer.parser->data.p = &packer; packer.is_bin = 0; packer.L = L; packer.root = lmpack_ref(L, packer.reg); luaL_buffinit(L, &buffer); b = luaL_prepbuffer(&buffer); bl = LUAL_BUFFERSIZE; do { size_t bl_init = bl; result = mpack_unparse(packer.parser, &b, &bl, lmpack_unparse_enter, lmpack_unparse_exit); if (result == MPACK_NOMEM) { lmpack_unref(L, packer.reg, packer.root); luaL_unref(L, LUA_REGISTRYINDEX, packer.reg); return luaL_error(L, "object was too deep to pack"); } luaL_addsize(&buffer, bl_init - bl); if (!bl) { /* buffer empty, resize */ b = luaL_prepbuffer(&buffer); bl = LUAL_BUFFERSIZE; } } while (result == MPACK_EOF); lmpack_unref(L, packer.reg, packer.root); luaL_unref(L, LUA_REGISTRYINDEX, packer.reg); luaL_pushresult(&buffer); return 1; } static const luaL_reg unpacker_methods[] = { {"__call", lmpack_unpacker_unpack}, {"__gc", lmpack_unpacker_delete}, {NULL, NULL} }; static const luaL_reg packer_methods[] = { {"__call", lmpack_packer_pack}, {"__gc", lmpack_packer_delete}, {NULL, NULL} }; static const luaL_reg session_methods[] = { {"receive", lmpack_session_receive}, {"request", lmpack_session_request}, {"reply", lmpack_session_reply}, {"notify", lmpack_session_notify}, {"__gc", lmpack_session_delete}, {NULL, NULL} }; static const luaL_reg mpack_functions[] = { {"Unpacker", lmpack_unpacker_new}, {"Packer", lmpack_packer_new}, {"Session", lmpack_session_new}, {"unpack", lmpack_unpack}, {"pack", lmpack_pack}, {NULL, NULL} }; int luaopen_mpack(lua_State *L) { /* Unpacker */ luaL_newmetatable(L, UNPACKER_META_NAME); lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); luaL_register(L, NULL, unpacker_methods); /* Packer */ luaL_newmetatable(L, PACKER_META_NAME); lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); luaL_register(L, NULL, packer_methods); /* Session */ luaL_newmetatable(L, SESSION_META_NAME); lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); luaL_register(L, NULL, session_methods); /* NIL */ luaL_newmetatable(L, NIL_NAME); lua_pushstring(L, "__tostring"); lua_pushcfunction(L, lmpack_nil_tostring); lua_settable(L, -3); /* Use a constant userdata to represent NIL */ (void)lua_newuserdata(L, sizeof(void *)); /* Assign the metatable to the userdata object */ luaL_getmetatable(L, NIL_NAME); lua_setmetatable(L, -2); /* Save NIL on the registry so we can access it easily from other functions */ lua_setfield(L, LUA_REGISTRYINDEX, NIL_NAME); /* module */ lua_newtable(L); luaL_register(L, NULL, mpack_functions); /* save NIL on the module */ lua_getfield(L, LUA_REGISTRYINDEX, NIL_NAME); lua_setfield(L, -2, "NIL"); return 1; } libmpack-lua-1.0.7/mpack-1.0.7-0.rockspec000066400000000000000000000006071320126032600175020ustar00rootroot00000000000000local git_tag = '1.0.7' package = 'mpack' version = git_tag .. '-0' source = { url = 'https://github.com/libmpack/libmpack-lua/releases/download/' .. git_tag .. '/libmpack-lua-' .. git_tag .. '.tar.gz' } description = { summary = 'Lua binding to libmpack', license = 'MIT' } build = { type = 'builtin', modules = { ['mpack'] = { sources = {'lmpack.c'} } } } libmpack-lua-1.0.7/test.lua000066400000000000000000000470161320126032600155450ustar00rootroot00000000000000local mpack = require('mpack') -- helpers convert binary strings from/to hex representation function fhex(s) local rv = {} for h in s:gmatch('%x+') do rv[#rv + 1] = string.char(tonumber(h, 16)) end return table.concat(rv) end function thex(s) local rv = {} for c in s:gmatch('.') do rv[#rv + 1] = ('%02x'):format(string.byte(c)) end return table.concat(rv, ' ') end describe('mpack', function() local msgpack = fhex( '81 a4 6b 65 79 31 93 01 81 a4 6b 65 79 32 82 a4 6b 65 79 33 93 a3 '.. '61 76 64 a2 63 64 03 a4 6b 65 79 34 a7 6a 66 73 64 61 6c 6b 96 cd '.. '01 43 c3 c2 c0 cf 00 1f ff ff ff ff ff ff d3 ff e0 00 00 00 00 00 '.. '01') local object = { key1 = { 1, {key2 = {key3 = {'avd', 'cd', 3}, key4 = 'jfsdalk'}}, { 323, true, false, mpack.NIL, 9007199254740991, -9007199254740991 } } } before_each(function() collectgarbage() end) describe('unpack', function() local unpack, pos, unpacked; before_each(function() unpack = mpack.Unpacker() end) describe('a msgpack chunk', function() it('returns object and consumed count', function() unpacked, pos = unpack(msgpack) assert.are_same(object, unpacked) assert.are_same(#msgpack + 1, pos) end) end) describe('a msgpack chunk with trailing data', function() it('returns object and consumed count', function() unpacked, pos = unpack(msgpack..fhex('ab cd')) assert.are_same(object, unpacked) assert.are_same(#msgpack + 1, pos) end) end) describe('an incomplete msgpack chunk', function() it('returns nil and consumed count', function() unpacked, pos = unpack(msgpack:sub(1, #msgpack - 1)) assert.is_nil(unpacked) assert.are_same(#msgpack, pos) end) end) describe('msgpack chunk split over multiple calls', function() it('has the same result as if the complete chunk was passed in a single call', function() -- in chunks of 1 local mp = msgpack while #mp > 1 do unpacked = unpack(mp:sub(1, 1)) assert.is_nil(unpacked) mp = mp:sub(2) end unpacked = unpack(mp) assert.are_same(object, unpacked) -- in chunks of 2 mp = msgpack while #mp > 2 do unpacked = unpack(mp:sub(1, 2)) assert.is_nil(unpacked) mp = mp:sub(3) end unpacked = unpack(mp) assert.are_same(object, unpacked) end) end) describe('when used across coroutine boundaries', function() it('has the same behavior as when not', function() unpacked, pos = coroutine.wrap(function() return unpack(msgpack) end)() assert.are_same(object, unpacked) assert.are_same(#msgpack + 1, pos) end) end) end) describe('pack', function() local pack, packed; before_each(function() pack = mpack.Packer() collectgarbage() end) describe('an object graph', function() it('returns the corresponding msgpack blob', function() packed = pack(object) -- there's no guarantee that the order the keys are packed packed will -- the same as the one in the `msgpack` variable. verify that pack -- works, we unpack it and compare with `object` local unpack = mpack.Unpacker() local o = unpack(packed) assert.are_same(object, o) end) end) describe('when used across coroutine boundaries', function() it('has the same behavior as when not', function() packed = coroutine.wrap(function() return pack(object) end)() local unpack = mpack.Unpacker() local o = unpack(packed) assert.are_same(object, o) end) end) end) describe('handling cyclic references', function() it('ok', function() local tbl1 = {1} table.insert(tbl1, tbl1) table.insert(tbl1, 2) local tbl2 = {1, 2, 3} local tbl3 = {4, 5, tbl2, 6} local tbl4 = {7, tbl3} table.insert(tbl2, tbl4) local unpack = mpack.Unpacker() local pack = mpack.Packer() assert.are_same({1, mpack.NIL, 2}, unpack(pack(tbl1))) assert.are_same({1, 2, 3, {7, {4, 5, mpack.NIL, 6}}}, unpack(pack(tbl2))) end) end) describe('very large array', function() it('ok', function() local arr = {} for i = 1, 10000 do table.insert(arr, 'item'..i) end local unpack = mpack.Unpacker() local pack = mpack.Packer() local a = unpack(pack(arr)) assert.are_same(arr, a) end) end) describe('very deep table', function() it('ok', function() -- 33 levels of nested tables. The initial stack size is 32, so this will -- make the internal unpacker/packer to grow once. local obj = { k1 = { { k2 = {{{{{{{{{{{{{{{{{{{{{{{{{{{{{1}}}}}}}}}}}}}}}}}}}}}}}}}}}}} } } } local unpack = mpack.Unpacker() local pack = mpack.Packer() local o = unpack(pack(obj)) assert.are_same(obj, o) end) end) describe('cases', function() -- cases.mpac/cases_compact.mpac(taken from msgpack-c tests) local cases = { false, true, mpack.NIL, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, 127, 127, 255, 65535, 4294967295, -32, -32, -128, -32768, -2147483648, 0, -0, 1, -1, "a", "a", "a", "", "", "", { 0 }, { 0 }, { 0 }, {}, {}, {}, {}, {}, {}, { a = 97 }, { a = 97 }, { a = 97 }, { {} }, { { "a" } }, false, true, mpack.NIL, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, 127, 127, 255, 65535, 4294967295, -32, -32, -128, -32768, -2147483648, 0, -0, 1, -1, "a", "a", "a", "", "", "", { 0 }, { 0 }, { 0 }, {}, {}, {}, {}, {}, {}, { a = 97 }, { a = 97 }, { a = 97 }, { {} }, { { "a" } } } it('ok', function() local blob = io.open('cases.mpac'):read('*a') .. io.open('cases_compact.mpac'):read('*a') local unpack = mpack.Unpacker() local pack = mpack.Packer() local unpacked = {} local pos = 1 while pos <= #blob do local item item, pos = unpack(blob, pos) unpacked[#unpacked + 1] = item end assert.are_same(cases, unpacked) assert.are_same(unpacked, unpack(pack(unpacked))) end) end) describe('ext packing/unpacking', function() local Type = {} Type.__index = Type function Type.new(a, b, c) return setmetatable({a = a, b = b, c = c}, Type) end function Type:get_a() return self.a end function Type:get_b() return self.b end function Type:get_c() return self.c end local ext_unpackers = { [5] = function(code, str) assert(code == 5) local s = mpack.unpack(str) return Type.new(s._a, s._b, s._c) end } local ext_packers = { [Type] = function(obj) return 5, mpack.pack({_a = obj.a, _b = obj.b, _c = obj.c}) end } it('ok', function() local unpacker = mpack.Unpacker({ext = ext_unpackers}) local packer = mpack.Packer({ext = ext_packers}) local t1 = Type.new('field a', 2, {1, 2}) local t2 = unpacker(packer({key = {3, t1}})).key[2] assert.are_same('field a', t2:get_a()) assert.are_same(2, t2:get_b()) assert.are_same({1, 2}, t2:get_c()) end) it('errors on recursive packing or unpacking', function() local unpack, pack local ext_unpackers = { [5] = function(code, str) local s = unpack(str) return Type.new(s._a, s._b, s._c) end } local ext_packers = { [Type] = function(obj) return 5, pack({_a = obj.a, _b = obj.b, _c = obj.c}) end } unpack = mpack.Unpacker({ext = ext_unpackers}) pack = mpack.Packer({ext = ext_packers}) assert.has_error(function() pack(Type.new('field a', 2, {1, 2})) end, "Packer instance already working. Use another Packer or the ".. "module's \"pack\" function if you need to pack from the ext ".. "handler") assert.has_error(function() unpack(fhex('c7 16 05 83 a2 5f 61 a7 66 69 65 6c 64 20 61 a2 5f '.. '63 92 01 02 a2 5f 62 02')) end, "Unpacker instance already working. Use another Unpacker or the ".. "module's \"unpack\" function if you need to unpack from the ext ".. "handler") end) end) it('should not leak memory', function() -- get the path to the lua interpreter, taken from -- http://stackoverflow.com/a/18304231 local i_min = 0 while arg[ i_min ] do i_min = i_min - 1 end i_min = i_min + 1 local res = io.popen(arg[i_min]..' leak_test.lua'):read('*a') assert.are_same('ok\n', res) end) describe('is_bin option', function() it('controls if strings are serialized to BIN or STR', function() local isbin = false local pack = mpack.Packer({is_bin = function(str) isbin = not isbin return isbin end}) assert.are_same('c4 03 73 74 72', thex(pack('str'))) assert.are_same('a3 73 74 72', thex(pack('str'))) assert.are_same('c4 03 73 74 72', thex(pack('str'))) assert.are_same('a3 73 74 72', thex(pack('str'))) pack = mpack.Packer({is_bin = true}) assert.are_same('c4 03 73 74 72', thex(pack('str'))) assert.are_same('c4 03 73 74 72', thex(pack('str'))) pack = mpack.Packer() assert.are_same('a3 73 74 72', thex(pack('str'))) assert.are_same('a3 73 74 72', thex(pack('str'))) end) end) describe('rpc', function() it('ok', function() local session = mpack.Session() assert.are_same(fhex('94 00 00'), session:request('req1')) assert.are_same(fhex('94 00 01'), session:request('req2')) assert.are_same(fhex('94 00 02'), session:request('req3')) assert.are_same(fhex('93 02'), session:notify()) assert.are_same({'request', 0, 4}, {session:receive(fhex('94 00 00'))}) assert.are_same({'request', 1, 4}, {session:receive(fhex('94 00 01'))}) assert.are_same({'notification', nil, 3}, {session:receive(fhex('93 02'))}) assert.are_same({'response', 'req3', 4}, {session:receive(fhex('94 01 02'))}) assert.are_same({'response', 'req1', 4}, {session:receive(fhex('94 01 00'))}) assert.are_same({'response', 'req2', 4}, {session:receive(fhex('94 01 01'))}) assert.are_same({nil, nil, 2}, {session:receive(fhex('94'))}) assert.are_same({nil, nil, 2}, {session:receive(fhex('00'))}) assert.are_same({'request', 7, 2}, {session:receive(fhex('07'))}) assert.are_same(fhex('94 00 03'), session:request('req4')) assert.are_same({nil, nil, 2}, {session:receive(fhex('94'))}) assert.are_same({nil, nil, 2}, {session:receive(fhex('01'))}) assert.are_same({'response', 'req4', 2}, {session:receive(fhex('03'))}) assert.are_same({nil, nil, 2}, {session:receive(fhex('93'))}) assert.are_same({'notification', nil, 2}, {session:receive(fhex('02'))}) assert.has_errors(function() session:receive(fhex('94 01 09')) end) end) it('can unpack automatically if an unpacker is passed as option', function() local session = mpack.Session({unpack = mpack.Unpacker()}) assert.are_same({'request', 0, 'method', {1, 'a'}, 15}, {session:receive(fhex('94 00 00 a6 6d 65 74 68 6f 64 92 01 a1 61'))}) assert.are_same(fhex('94 00 00'), session:request('req1')) assert.are_same({'response', 'req1', mpack.NIL, 'result', 12}, {session:receive(fhex('94 01 00 c0 a6 72 65 73 75 6c 74'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('94'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('00'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('01'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('a6'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('6d'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('65'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('74'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('68'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('6f'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('64'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('92'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('01'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('a1'))}) assert.are_same({'request', 1, 'method', {1, 'a'}, 2}, {session:receive(fhex('61'))}) assert.are_same(fhex('94 00 01'), session:request('req2')) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('94'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('01'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('01'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('c0'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('a6'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('72'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('65'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('73'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('75'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('6c'))}) assert.are_same({'response', 'req2', mpack.NIL, 'result', 2}, {session:receive(fhex('74'))}) assert.are_same({'notification', nil, 'method', {1, 'a'}, 14}, {session:receive(fhex('93 02 a6 6d 65 74 68 6f 64 92 01 a1 61'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('93'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('02'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('a6'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('6d'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('65'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('74'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('68'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('6f'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('64'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('92'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('01'))}) assert.are_same({nil, nil, nil, nil, 2}, {session:receive(fhex('a1'))}) assert.are_same({'notification', nil, 'method', {1, 'a'}, 2}, {session:receive(fhex('61'))}) end) describe('when used across coroutine boundaries', function() it('has the same behavior as when not', function() local input = fhex('94 00 00 a6 6d 65 74 68 6f 64 92 01 a1 61') local received = {'request', 0, 'method', {1, 'a'}, 15} local session = mpack.Session({unpack = mpack.Unpacker()}) assert.are_same(received, coroutine.wrap(function() return {session:receive(input)} end)()) end) end) it('grows session capacity', function() local session = mpack.Session() assert.are_same(fhex('94 00 00'), session:request()) assert.are_same(fhex('94 00 01'), session:request()) assert.are_same(fhex('94 00 02'), session:request()) assert.are_same(fhex('94 00 03'), session:request()) assert.are_same(fhex('94 00 04'), session:request()) assert.are_same(fhex('94 00 05'), session:request()) assert.are_same(fhex('94 00 06'), session:request()) assert.are_same(fhex('94 00 07'), session:request()) assert.are_same(fhex('94 00 08'), session:request()) assert.are_same(fhex('94 00 09'), session:request()) assert.are_same(fhex('94 00 0a'), session:request()) assert.are_same(fhex('94 00 0b'), session:request()) assert.are_same(fhex('94 00 0c'), session:request()) assert.are_same(fhex('94 00 0d'), session:request()) assert.are_same(fhex('94 00 0e'), session:request()) assert.are_same(fhex('94 00 0f'), session:request()) assert.are_same(fhex('94 00 10'), session:request()) assert.are_same(fhex('94 00 11'), session:request()) assert.are_same(fhex('94 00 12'), session:request()) assert.are_same(fhex('94 00 13'), session:request()) assert.are_same(fhex('94 00 14'), session:request()) assert.are_same(fhex('94 00 15'), session:request()) assert.are_same(fhex('94 00 16'), session:request()) assert.are_same(fhex('94 00 17'), session:request()) assert.are_same(fhex('94 00 18'), session:request()) assert.are_same(fhex('94 00 19'), session:request()) assert.are_same(fhex('94 00 1a'), session:request()) assert.are_same(fhex('94 00 1b'), session:request()) assert.are_same(fhex('94 00 1c'), session:request()) assert.are_same(fhex('94 00 1d'), session:request()) assert.are_same(fhex('94 00 1e'), session:request()) assert.are_same(fhex('94 00 1f'), session:request()) assert.are_same(fhex('94 00 20'), session:request()) assert.are_same('response', session:receive(fhex('94 01 00'))) assert.are_same('response', session:receive(fhex('94 01 01'))) assert.are_same('response', session:receive(fhex('94 01 02'))) assert.are_same('response', session:receive(fhex('94 01 03'))) assert.are_same('response', session:receive(fhex('94 01 04'))) assert.are_same('response', session:receive(fhex('94 01 05'))) assert.are_same('response', session:receive(fhex('94 01 06'))) assert.are_same('response', session:receive(fhex('94 01 07'))) assert.are_same('response', session:receive(fhex('94 01 08'))) assert.are_same('response', session:receive(fhex('94 01 09'))) assert.are_same('response', session:receive(fhex('94 01 0a'))) assert.are_same('response', session:receive(fhex('94 01 0b'))) assert.are_same('response', session:receive(fhex('94 01 0c'))) assert.are_same('response', session:receive(fhex('94 01 0d'))) assert.are_same('response', session:receive(fhex('94 01 0e'))) assert.are_same('response', session:receive(fhex('94 01 0f'))) assert.are_same('response', session:receive(fhex('94 01 10'))) assert.are_same('response', session:receive(fhex('94 01 11'))) assert.are_same('response', session:receive(fhex('94 01 12'))) assert.are_same('response', session:receive(fhex('94 01 13'))) assert.are_same('response', session:receive(fhex('94 01 14'))) assert.are_same('response', session:receive(fhex('94 01 15'))) assert.are_same('response', session:receive(fhex('94 01 16'))) assert.are_same('response', session:receive(fhex('94 01 17'))) assert.are_same('response', session:receive(fhex('94 01 18'))) assert.are_same('response', session:receive(fhex('94 01 19'))) assert.are_same('response', session:receive(fhex('94 01 1a'))) assert.are_same('response', session:receive(fhex('94 01 1b'))) assert.are_same('response', session:receive(fhex('94 01 1c'))) assert.are_same('response', session:receive(fhex('94 01 1d'))) assert.are_same('response', session:receive(fhex('94 01 1e'))) assert.are_same('response', session:receive(fhex('94 01 1f'))) assert.are_same('response', session:receive(fhex('94 01 20'))) assert.has_errors(function() session:receive(fhex('94 01 21')) end) end) end) describe('NIL', function() it('tostring() is human-readable', function() assert.are_same("mpack.NIL", tostring(mpack.NIL)) end) end) end)