aboutsummaryrefslogtreecommitdiff
path: root/src/mpack/lmpack.c
diff options
context:
space:
mode:
authorBjörn Linse <bjorn.linse@gmail.com>2021-09-04 17:01:45 +0200
committerBjörn Linse <bjorn.linse@gmail.com>2021-09-09 16:06:43 +0200
commit4ef3c2c2c121862a9d2b3d1d7578e1f7f1f0f60c (patch)
tree11b21b23e4ac6a3849124d05f6a3874fabb9501e /src/mpack/lmpack.c
parentc8f46480bc0bfd07c8a69d61e365706e3184abc9 (diff)
downloadrneovim-4ef3c2c2c121862a9d2b3d1d7578e1f7f1f0f60c.tar.gz
rneovim-4ef3c2c2c121862a9d2b3d1d7578e1f7f1f0f60c.tar.bz2
rneovim-4ef3c2c2c121862a9d2b3d1d7578e1f7f1f0f60c.zip
build: vendor libmpack/libmpack-lua sources from b593809d93b5299bbee4e1d18e2e2d55f9fa53a6
Diffstat (limited to 'src/mpack/lmpack.c')
-rw-r--r--src/mpack/lmpack.c1179
1 files changed, 1179 insertions, 0 deletions
diff --git a/src/mpack/lmpack.c b/src/mpack/lmpack.c
new file mode 100644
index 0000000000..6da28fc263
--- /dev/null
+++ b/src/mpack/lmpack.c
@@ -0,0 +1,1179 @@
+/*
+ * 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
+/* for snprintf */
+#define _XOPEN_SOURCE 500
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <lauxlib.h>
+#include <lua.h>
+#include <luaconf.h>
+
+#ifdef MPACK_USE_SYSTEM
+# include <mpack.h>
+#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;
+ lua_Number n;
+#ifndef NDEBUG
+ int top = lua_gettop(L);
+ assert(top);
+#endif
+
+ 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:
+ {
+ /* #define FMT */
+ char errmsg[50];
+ snprintf(errmsg, 50, "can't serialize object of type %d", type);
+ luaL_error(L, errmsg);
+ }
+ }
+
+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 */
+ /* Check if NIL is already stored in the registry */
+ lua_getfield(L, LUA_REGISTRYINDEX, NIL_NAME);
+ /* If it isn't, create it */
+ if (lua_isnil(L, -1)) {
+ /* Use a constant userdata to represent NIL */
+ (void)lua_newuserdata(L, sizeof(void *));
+ /* Create a metatable for NIL userdata */
+ lua_createtable(L, 0, 1);
+ lua_pushstring(L, "__tostring");
+ lua_pushcfunction(L, lmpack_nil_tostring);
+ lua_settable(L, -3);
+ /* Assign the metatable to the userdata object */
+ 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;
+}