diff options
author | Björn Linse <bjorn.linse@gmail.com> | 2021-09-04 17:01:45 +0200 |
---|---|---|
committer | Björn Linse <bjorn.linse@gmail.com> | 2021-09-09 16:06:43 +0200 |
commit | 4ef3c2c2c121862a9d2b3d1d7578e1f7f1f0f60c (patch) | |
tree | 11b21b23e4ac6a3849124d05f6a3874fabb9501e /src/mpack/lmpack.c | |
parent | c8f46480bc0bfd07c8a69d61e365706e3184abc9 (diff) | |
download | rneovim-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.c | 1179 |
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; +} |