Files
neovim/src/mpack/lmpack.c

1180 lines
32 KiB
C

/*
* 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;
}