diff options
author | Björn Linse <bjorn.linse@gmail.com> | 2021-09-09 18:47:03 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-09 18:47:03 +0200 |
commit | d80aac3b2a3ce4b25d4f12ff64b8c947eaf75826 (patch) | |
tree | b10dcaea8ee55a9f27b528360eb0dab50e165121 /src | |
parent | 13748512f6d6dfb5895c2233d2e07480e00eb753 (diff) | |
parent | ad8eda3f5b7db424de912ac748bce6b02e43f64f (diff) | |
download | rneovim-d80aac3b2a3ce4b25d4f12ff64b8c947eaf75826.tar.gz rneovim-d80aac3b2a3ce4b25d4f12ff64b8c947eaf75826.tar.bz2 rneovim-d80aac3b2a3ce4b25d4f12ff64b8c947eaf75826.zip |
Merge pull request #15566 from bfredl/mpack
libs: vendor libmpack and libmpack-lua
Diffstat (limited to 'src')
-rw-r--r-- | src/mpack/LICENSE-MIT | 22 | ||||
-rw-r--r-- | src/mpack/conv.c | 375 | ||||
-rw-r--r-- | src/mpack/conv.h | 55 | ||||
-rw-r--r-- | src/mpack/lmpack.c | 1215 | ||||
-rw-r--r-- | src/mpack/lmpack.h | 3 | ||||
-rw-r--r-- | src/mpack/mpack_core.c | 575 | ||||
-rw-r--r-- | src/mpack/mpack_core.h | 87 | ||||
-rw-r--r-- | src/mpack/object.c | 195 | ||||
-rw-r--r-- | src/mpack/object.h | 86 | ||||
-rw-r--r-- | src/mpack/rpc.c | 331 | ||||
-rw-r--r-- | src/mpack/rpc.h | 83 | ||||
-rw-r--r-- | src/nvim/CMakeLists.txt | 14 | ||||
-rw-r--r-- | src/nvim/lua/executor.c | 18 |
13 files changed, 3052 insertions, 7 deletions
diff --git a/src/mpack/LICENSE-MIT b/src/mpack/LICENSE-MIT new file mode 100644 index 0000000000..030ba872c5 --- /dev/null +++ b/src/mpack/LICENSE-MIT @@ -0,0 +1,22 @@ +Copyright (c) 2016 Thiago de Arruda + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/mpack/conv.c b/src/mpack/conv.c new file mode 100644 index 0000000000..203b13fadb --- /dev/null +++ b/src/mpack/conv.c @@ -0,0 +1,375 @@ +#include "conv.h" + +static int mpack_fits_single(double v); +static mpack_value_t mpack_pack_ieee754(double v, unsigned m, unsigned e); +static int mpack_is_be(void) FPURE; +static double mpack_fmod_pow2_32(double a); + + +#define POW2(n) \ + ((double)(1 << (n / 2)) * (double)(1 << (n / 2)) * (double)(1 << (n % 2))) + +#define MPACK_SWAP_VALUE(val) \ + do { \ + mpack_uint32_t lo = val.lo; \ + val.lo = val.hi; \ + val.hi = lo; \ + } while (0) + +MPACK_API mpack_token_t mpack_pack_nil(void) +{ + mpack_token_t rv; + rv.type = MPACK_TOKEN_NIL; + return rv; +} + +MPACK_API mpack_token_t mpack_pack_boolean(unsigned v) +{ + mpack_token_t rv; + rv.type = MPACK_TOKEN_BOOLEAN; + rv.data.value.lo = v ? 1 : 0; + rv.data.value.hi = 0; + return rv; +} + +MPACK_API mpack_token_t mpack_pack_uint(mpack_uintmax_t v) +{ + mpack_token_t rv; + rv.data.value.lo = v & 0xffffffff; + rv.data.value.hi = (mpack_uint32_t)((v >> 31) >> 1); + rv.type = MPACK_TOKEN_UINT; + return rv; +} + +MPACK_API mpack_token_t mpack_pack_sint(mpack_sintmax_t v) +{ + if (v < 0) { + mpack_token_t rv; + mpack_uintmax_t tc = -((mpack_uintmax_t)(v + 1)) + 1; + tc = ~tc + 1; + rv = mpack_pack_uint(tc); + rv.type = MPACK_TOKEN_SINT; + return rv; + } + + return mpack_pack_uint((mpack_uintmax_t)v); +} + +MPACK_API mpack_token_t mpack_pack_float_compat(double v) +{ + /* ieee754 single-precision limits to determine if "v" can be fully + * represented in 4 bytes */ + mpack_token_t rv; + + if (mpack_fits_single(v)) { + rv.length = 4; + rv.data.value = mpack_pack_ieee754(v, 23, 8); + } else { + rv.length = 8; + rv.data.value = mpack_pack_ieee754(v, 52, 11); + } + + rv.type = MPACK_TOKEN_FLOAT; + return rv; +} + +MPACK_API mpack_token_t mpack_pack_float_fast(double v) +{ + /* ieee754 single-precision limits to determine if "v" can be fully + * represented in 4 bytes */ + mpack_token_t rv; + + if (mpack_fits_single(v)) { + union { + float f; + mpack_uint32_t m; + } conv; + conv.f = (float)v; + rv.length = 4; + rv.data.value.lo = conv.m; + rv.data.value.hi = 0; + } else { + union { + double d; + mpack_value_t m; + } conv; + conv.d = v; + rv.length = 8; + rv.data.value = conv.m; + if (mpack_is_be()) { + MPACK_SWAP_VALUE(rv.data.value); + } + } + + rv.type = MPACK_TOKEN_FLOAT; + return rv; +} + +MPACK_API mpack_token_t mpack_pack_number(double v) +{ + mpack_token_t tok; + double vabs; + vabs = v < 0 ? -v : v; + assert(v <= 9007199254740991. && v >= -9007199254740991.); + tok.data.value.hi = (mpack_uint32_t)(vabs / POW2(32)); + tok.data.value.lo = (mpack_uint32_t)mpack_fmod_pow2_32(vabs); + + if (v < 0) { + /* Compute the two's complement */ + tok.type = MPACK_TOKEN_SINT; + tok.data.value.hi = ~tok.data.value.hi; + tok.data.value.lo = ~tok.data.value.lo + 1; + if (!tok.data.value.lo) tok.data.value.hi++; + if (tok.data.value.lo == 0 && tok.data.value.hi == 0) tok.length = 1; + else if (tok.data.value.lo < 0x80000000) tok.length = 8; + else if (tok.data.value.lo < 0xffff7fff) tok.length = 4; + else if (tok.data.value.lo < 0xffffff7f) tok.length = 2; + else tok.length = 1; + } else { + tok.type = MPACK_TOKEN_UINT; + if (tok.data.value.hi) tok.length = 8; + else if (tok.data.value.lo > 0xffff) tok.length = 4; + else if (tok.data.value.lo > 0xff) tok.length = 2; + else tok.length = 1; + } + + if (mpack_unpack_number(tok) != v) { + return mpack_pack_float(v); + } + + return tok; +} + +MPACK_API mpack_token_t mpack_pack_chunk(const char *p, mpack_uint32_t l) +{ + mpack_token_t rv; + rv.type = MPACK_TOKEN_CHUNK; + rv.data.chunk_ptr = p; + rv.length = l; + return rv; +} + +MPACK_API mpack_token_t mpack_pack_str(mpack_uint32_t l) +{ + mpack_token_t rv; + rv.type = MPACK_TOKEN_STR; + rv.length = l; + return rv; +} + +MPACK_API mpack_token_t mpack_pack_bin(mpack_uint32_t l) +{ + mpack_token_t rv; + rv.type = MPACK_TOKEN_BIN; + rv.length = l; + return rv; +} + +MPACK_API mpack_token_t mpack_pack_ext(int t, mpack_uint32_t l) +{ + mpack_token_t rv; + rv.type = MPACK_TOKEN_EXT; + rv.length = l; + rv.data.ext_type = t; + return rv; +} + +MPACK_API mpack_token_t mpack_pack_array(mpack_uint32_t l) +{ + mpack_token_t rv; + rv.type = MPACK_TOKEN_ARRAY; + rv.length = l; + return rv; +} + +MPACK_API mpack_token_t mpack_pack_map(mpack_uint32_t l) +{ + mpack_token_t rv; + rv.type = MPACK_TOKEN_MAP; + rv.length = l; + return rv; +} + +MPACK_API bool mpack_unpack_boolean(mpack_token_t t) +{ + return t.data.value.lo || t.data.value.hi; +} + +MPACK_API mpack_uintmax_t mpack_unpack_uint(mpack_token_t t) +{ + return (((mpack_uintmax_t)t.data.value.hi << 31) << 1) | t.data.value.lo; +} + +/* unpack signed integer without relying on two's complement as internal + * representation */ +MPACK_API mpack_sintmax_t mpack_unpack_sint(mpack_token_t t) +{ + mpack_uint32_t hi = t.data.value.hi; + mpack_uint32_t lo = t.data.value.lo; + mpack_uintmax_t rv = lo; + assert(t.length <= sizeof(mpack_sintmax_t)); + + if (t.length == 8) { + rv |= (((mpack_uintmax_t)hi) << 31) << 1; + } + /* reverse the two's complement so that lo/hi contain the absolute value. + * note that we have to mask ~rv so that it reflects the two's complement + * of the appropriate byte length */ + rv = (~rv & (((mpack_uintmax_t)1 << ((t.length * 8) - 1)) - 1)) + 1; + /* negate and return the absolute value, making sure mpack_sintmax_t can + * represent the positive cast. */ + return -((mpack_sintmax_t)(rv - 1)) - 1; +} + +MPACK_API double mpack_unpack_float_compat(mpack_token_t t) +{ + mpack_uint32_t sign; + mpack_sint32_t exponent, bias; + unsigned mantbits; + unsigned expbits; + double mant; + + if (t.data.value.lo == 0 && t.data.value.hi == 0) + /* nothing to do */ + return 0; + + if (t.length == 4) mantbits = 23, expbits = 8; + else mantbits = 52, expbits = 11; + bias = (1 << (expbits - 1)) - 1; + + /* restore sign/exponent/mantissa */ + if (mantbits == 52) { + sign = t.data.value.hi >> 31; + exponent = (t.data.value.hi >> 20) & ((1 << 11) - 1); + mant = (t.data.value.hi & ((1 << 20) - 1)) * POW2(32); + mant += t.data.value.lo; + } else { + sign = t.data.value.lo >> 31; + exponent = (t.data.value.lo >> 23) & ((1 << 8) - 1); + mant = t.data.value.lo & ((1 << 23) - 1); + } + + mant /= POW2(mantbits); + if (exponent) mant += 1.0; /* restore leading 1 */ + else exponent = 1; /* subnormal */ + exponent -= bias; + + /* restore original value */ + while (exponent > 0) mant *= 2.0, exponent--; + while (exponent < 0) mant /= 2.0, exponent++; + return mant * (sign ? -1 : 1); +} + +MPACK_API double mpack_unpack_float_fast(mpack_token_t t) +{ + if (t.length == 4) { + union { + float f; + mpack_uint32_t m; + } conv; + conv.m = t.data.value.lo; + return conv.f; + } else { + union { + double d; + mpack_value_t m; + } conv; + conv.m = t.data.value; + + if (mpack_is_be()) { + MPACK_SWAP_VALUE(conv.m); + } + + return conv.d; + } +} + +MPACK_API double mpack_unpack_number(mpack_token_t t) +{ + double rv; + mpack_uint32_t hi, lo; + if (t.type == MPACK_TOKEN_FLOAT) return mpack_unpack_float(t); + assert(t.type == MPACK_TOKEN_UINT || t.type == MPACK_TOKEN_SINT); + hi = t.data.value.hi; + lo = t.data.value.lo; + if (t.type == MPACK_TOKEN_SINT) { + /* same idea as mpack_unpack_sint, except here we shouldn't rely on + * mpack_uintmax_t having 64-bits, operating on the 32-bit words separately. + */ + if (!hi) { + assert(t.length <= 4); + hi = 0; + lo = (~lo & (((mpack_uint32_t)1 << ((t.length * 8) - 1)) - 1)); + } else { + hi = ~hi; + lo = ~lo; + } + lo++; + if (!lo) hi++; + } + rv = (double)lo + POW2(32) * hi; + return t.type == MPACK_TOKEN_SINT ? -rv : rv; +} + +static int mpack_fits_single(double v) +{ + return (float)v == v; +} + +static mpack_value_t mpack_pack_ieee754(double v, unsigned mantbits, + unsigned expbits) +{ + mpack_value_t rv = {0, 0}; + mpack_sint32_t exponent, bias = (1 << (expbits - 1)) - 1; + mpack_uint32_t sign; + double mant; + + if (v == 0) { + rv.lo = 0; + rv.hi = 0; + goto end; + } + + if (v < 0) sign = 1, mant = -v; + else sign = 0, mant = v; + + exponent = 0; + while (mant >= 2.0) mant /= 2.0, exponent++; + while (mant < 1.0 && exponent > -(bias - 1)) mant *= 2.0, exponent--; + + if (mant < 1.0) exponent = -bias; /* subnormal value */ + else mant = mant - 1.0; /* remove leading 1 */ + exponent += bias; + mant *= POW2(mantbits); + + if (mantbits == 52) { + rv.hi = (mpack_uint32_t)(mant / POW2(32)); + rv.lo = (mpack_uint32_t)(mant - rv.hi * POW2(32)); + rv.hi |= ((mpack_uint32_t)exponent << 20) | (sign << 31); + } else if (mantbits == 23) { + rv.hi = 0; + rv.lo = (mpack_uint32_t)mant; + rv.lo |= ((mpack_uint32_t)exponent << 23) | (sign << 31); + } + +end: + return rv; +} + +static int mpack_is_be(void) +{ + union { + mpack_uint32_t i; + char c[sizeof(mpack_uint32_t)]; + } test; + + test.i = 1; + return test.c[0] == 0; +} + +/* this simplified version of `fmod` that returns the remainder of double + * division by 0xffffffff, which is enough for our purposes */ +static double mpack_fmod_pow2_32(double a) +{ + return a - ((double)(mpack_uint32_t)(a / POW2(32)) * POW2(32)); +} diff --git a/src/mpack/conv.h b/src/mpack/conv.h new file mode 100644 index 0000000000..71f14a067e --- /dev/null +++ b/src/mpack/conv.h @@ -0,0 +1,55 @@ +#ifndef MPACK_CONV_H +#define MPACK_CONV_H + +#include "mpack_core.h" + +#if ULLONG_MAX == 0xffffffffffffffff +typedef long long mpack_sintmax_t; +typedef unsigned long long mpack_uintmax_t; +#elif UINT64_MAX == 0xffffffffffffffff +typedef int64_t mpack_sintmax_t; +typedef uint64_t mpack_uintmax_t; +#else +typedef mpack_sint32_t mpack_sintmax_t; +typedef mpack_uint32_t mpack_uintmax_t; +#endif + +#ifndef bool +# define bool unsigned +#endif + +MPACK_API mpack_token_t mpack_pack_nil(void) FUNUSED FPURE; +MPACK_API mpack_token_t mpack_pack_boolean(unsigned v) FUNUSED FPURE; +MPACK_API mpack_token_t mpack_pack_uint(mpack_uintmax_t v) FUNUSED FPURE; +MPACK_API mpack_token_t mpack_pack_sint(mpack_sintmax_t v) FUNUSED FPURE; +MPACK_API mpack_token_t mpack_pack_float_compat(double v) FUNUSED FPURE; +MPACK_API mpack_token_t mpack_pack_float_fast(double v) FUNUSED FPURE; +MPACK_API mpack_token_t mpack_pack_number(double v) FUNUSED FPURE; +MPACK_API mpack_token_t mpack_pack_chunk(const char *p, mpack_uint32_t l) + FUNUSED FPURE FNONULL; +MPACK_API mpack_token_t mpack_pack_str(mpack_uint32_t l) FUNUSED FPURE; +MPACK_API mpack_token_t mpack_pack_bin(mpack_uint32_t l) FUNUSED FPURE; +MPACK_API mpack_token_t mpack_pack_ext(int type, mpack_uint32_t l) + FUNUSED FPURE; +MPACK_API mpack_token_t mpack_pack_array(mpack_uint32_t l) FUNUSED FPURE; +MPACK_API mpack_token_t mpack_pack_map(mpack_uint32_t l) FUNUSED FPURE; +MPACK_API bool mpack_unpack_boolean(mpack_token_t t) FUNUSED FPURE; +MPACK_API mpack_uintmax_t mpack_unpack_uint(mpack_token_t t) FUNUSED FPURE; +MPACK_API mpack_sintmax_t mpack_unpack_sint(mpack_token_t t) FUNUSED FPURE; +MPACK_API double mpack_unpack_float_fast(mpack_token_t t) FUNUSED FPURE; +MPACK_API double mpack_unpack_float_compat(mpack_token_t t) FUNUSED FPURE; +MPACK_API double mpack_unpack_number(mpack_token_t t) FUNUSED FPURE; + +/* The mpack_{pack,unpack}_float_fast functions should work in 99% of the + * platforms. When compiling for a platform where floats don't use ieee754 as + * the internal format, pass + * -Dmpack_{pack,unpack}_float=mpack_{pack,unpack}_float_compat to the + * compiler.*/ +#ifndef mpack_pack_float +# define mpack_pack_float mpack_pack_float_fast +#endif +#ifndef mpack_unpack_float +# define mpack_unpack_float mpack_unpack_float_fast +#endif + +#endif /* MPACK_CONV_H */ diff --git a/src/mpack/lmpack.c b/src/mpack/lmpack.c new file mode 100644 index 0000000000..99207246c8 --- /dev/null +++ b/src/mpack/lmpack.c @@ -0,0 +1,1215 @@ +/* + * This module exports three classes, and each instance of those classes has its + * own private registry for temporary reference storage(keeping state between + * calls). A private registry makes managing memory much easier since all we + * have to do is call luaL_unref passing the registry reference when the + * instance is collected by the __gc metamethod. + * + * This private registry is manipulated with `lmpack_ref` / `lmpack_unref` / + * `lmpack_geti`, which are analogous to `luaL_ref` / `luaL_unref` / + * `lua_rawgeti` but operate on the private registry passed as argument. + * + * In order to simplify debug registry leaks during normal operation(with the + * leak_test.lua script), these `lmpack_*` registry functions will target the + * normal lua registry when MPACK_DEBUG_REGISTRY_LEAK is defined during + * compilation. + */ +#define LUA_LIB +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include <lauxlib.h> +#include <lua.h> +#include <luaconf.h> + +#include "nvim/macros.h" + +#include "lmpack.h" + +#include "rpc.h" + +#define UNPACKER_META_NAME "mpack.Unpacker" +#define PACKER_META_NAME "mpack.Packer" +#define SESSION_META_NAME "mpack.Session" +#define NIL_NAME "mpack.NIL" +#define EMPTY_DICT_NAME "mpack.empty_dict" + +/* + * 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, mtdict; + char *string_buffer; +} Unpacker; + +typedef struct { + lua_State *L; + mpack_parser_t *parser; + int reg, ext, root, packing, mtdict; + 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++; + } + + // when len==0, the caller should guess the type! + if (len > 0) { + *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; + + lua_getfield(L, LUA_REGISTRYINDEX, EMPTY_DICT_NAME); + rv->mtdict = lmpack_ref(L, rv->reg); + + 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); + if (node->key_visited == 0 && node->tok.type == MPACK_TOKEN_MAP) { + lmpack_geti(L, unpacker->reg, unpacker->mtdict); // [table, mtdict] + lua_setmetatable(L, -2); // [table] + } + + 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; + + lua_getfield(L, LUA_REGISTRYINDEX, EMPTY_DICT_NAME); + rv->mtdict = lmpack_ref(L, rv->reg); + + 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: { + mpack_uint32_t len; + mpack_node_t *n; + + int has_meta = lua_getmetatable(L, -1); + if (packer->ext != LUA_NOREF && has_meta) { + /* 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 and metatable since they + * will be handled below, so pop 1 + */ + lua_pop(L, 1); + } + } + + int is_array = 1; + if (has_meta) { + // stack: [table, metatable] + if (packer->mtdict != LUA_NOREF) { + lmpack_geti(L, packer->reg, packer->mtdict); // [table, metatable, mtdict] + is_array = !lua_rawequal(L, -1, -2); + lua_pop(L, 1); // [table, metatable]; + } + lua_pop(L, 1); // [table] + } + + /* 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; + + lua_getfield(L, LUA_REGISTRYINDEX, EMPTY_DICT_NAME); + unpacker.mtdict = lmpack_ref(L, unpacker.reg); + + 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); + + lua_getfield(L, LUA_REGISTRYINDEX, EMPTY_DICT_NAME); + packer.mtdict = 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); + lua_pop(L, 1); + /* Packer */ + luaL_newmetatable(L, PACKER_META_NAME); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + luaL_register(L, NULL, packer_methods); + lua_pop(L, 1); + /* Session */ + luaL_newmetatable(L, SESSION_META_NAME); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + luaL_register(L, NULL, session_methods); + lua_pop(L, 1); + /* 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); + } + + lua_pop(L, 1); + + /* 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; +} diff --git a/src/mpack/lmpack.h b/src/mpack/lmpack.h new file mode 100644 index 0000000000..e35f40fab6 --- /dev/null +++ b/src/mpack/lmpack.h @@ -0,0 +1,3 @@ +#include <lua.h> + +int luaopen_mpack(lua_State *L); diff --git a/src/mpack/mpack_core.c b/src/mpack/mpack_core.c new file mode 100644 index 0000000000..0ad09bd46a --- /dev/null +++ b/src/mpack/mpack_core.c @@ -0,0 +1,575 @@ +#include <string.h> + +#include "mpack_core.h" + +#define UNUSED(p) (void)p; +#define ADVANCE(buf, buflen) ((*buflen)--, (unsigned char)*((*buf)++)) +#define TLEN(val, range_start) ((mpack_uint32_t)(1 << (val - range_start))) +#ifndef MIN +# define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) +#endif + +static int mpack_rtoken(const char **buf, size_t *buflen, + mpack_token_t *tok); +static int mpack_rpending(const char **b, size_t *nl, mpack_tokbuf_t *tb); +static int mpack_rvalue(mpack_token_type_t t, mpack_uint32_t l, + const char **b, size_t *bl, mpack_token_t *tok); +static int mpack_rblob(mpack_token_type_t t, mpack_uint32_t l, + const char **b, size_t *bl, mpack_token_t *tok); +static int mpack_wtoken(const mpack_token_t *tok, char **b, size_t *bl); +static int mpack_wpending(char **b, size_t *bl, mpack_tokbuf_t *tb); +static int mpack_wpint(char **b, size_t *bl, mpack_value_t v); +static int mpack_wnint(char **b, size_t *bl, mpack_value_t v); +static int mpack_wfloat(char **b, size_t *bl, const mpack_token_t *v); +static int mpack_wstr(char **buf, size_t *buflen, mpack_uint32_t len); +static int mpack_wbin(char **buf, size_t *buflen, mpack_uint32_t len); +static int mpack_wext(char **buf, size_t *buflen, int type, + mpack_uint32_t len); +static int mpack_warray(char **buf, size_t *buflen, mpack_uint32_t len); +static int mpack_wmap(char **buf, size_t *buflen, mpack_uint32_t len); +static int mpack_w1(char **b, size_t *bl, mpack_uint32_t v); +static int mpack_w2(char **b, size_t *bl, mpack_uint32_t v); +static int mpack_w4(char **b, size_t *bl, mpack_uint32_t v); +static mpack_value_t mpack_byte(unsigned char b); +static int mpack_value(mpack_token_type_t t, mpack_uint32_t l, + mpack_value_t v, mpack_token_t *tok); +static int mpack_blob(mpack_token_type_t t, mpack_uint32_t l, int et, + mpack_token_t *tok); + +MPACK_API void mpack_tokbuf_init(mpack_tokbuf_t *tokbuf) +{ + tokbuf->ppos = 0; + tokbuf->plen = 0; + tokbuf->passthrough = 0; +} + +MPACK_API int mpack_read(mpack_tokbuf_t *tokbuf, const char **buf, + size_t *buflen, mpack_token_t *tok) +{ + int status; + size_t initial_ppos, ptrlen, advanced; + const char *ptr, *ptr_save; + assert(*buf && *buflen); + + if (tokbuf->passthrough) { + /* pass data from str/bin/ext directly as a MPACK_TOKEN_CHUNK, adjusting + * *buf and *buflen */ + tok->type = MPACK_TOKEN_CHUNK; + tok->data.chunk_ptr = *buf; + tok->length = MIN((mpack_uint32_t)*buflen, tokbuf->passthrough); + tokbuf->passthrough -= tok->length; + *buf += tok->length; + *buflen -= tok->length; + goto done; + } + + initial_ppos = tokbuf->ppos; + + if (tokbuf->plen) { + if (!mpack_rpending(buf, buflen, tokbuf)) { + return MPACK_EOF; + } + ptr = tokbuf->pending; + ptrlen = tokbuf->ppos; + } else { + ptr = *buf; + ptrlen = *buflen; + } + + ptr_save = ptr; + + if ((status = mpack_rtoken(&ptr, &ptrlen, tok))) { + if (status != MPACK_EOF) return MPACK_ERROR; + /* need more data */ + assert(!tokbuf->plen); + /* read the remainder of *buf to tokbuf->pending so it can be parsed + * later with more data. only required when tokbuf->plen == 0 or else + * it would have been done already. */ + tokbuf->plen = tok->length + 1; + assert(tokbuf->plen <= sizeof(tokbuf->pending)); + tokbuf->ppos = 0; + status = mpack_rpending(buf, buflen, tokbuf); + assert(!status); + return MPACK_EOF; + } + + advanced = (size_t)(ptr - ptr_save) - initial_ppos; + tokbuf->plen = tokbuf->ppos = 0; + *buflen -= advanced; + *buf += advanced; + + if (tok->type > MPACK_TOKEN_MAP) { + tokbuf->passthrough = tok->length; + } + +done: + return MPACK_OK; +} + +MPACK_API int mpack_write(mpack_tokbuf_t *tokbuf, char **buf, size_t *buflen, + const mpack_token_t *t) +{ + int status; + char *ptr; + size_t ptrlen; + mpack_token_t tok = tokbuf->plen ? tokbuf->pending_tok : *t; + assert(*buf && *buflen); + + if (tok.type == MPACK_TOKEN_CHUNK) { + size_t written, pending, count; + if (!tokbuf->plen) tokbuf->ppos = 0; + written = tokbuf->ppos; + pending = tok.length - written; + count = MIN(pending, *buflen); + memcpy(*buf, tok.data.chunk_ptr + written, count); + *buf += count; + *buflen -= count; + tokbuf->ppos += count; + tokbuf->plen = count == pending ? 0 : tok.length; + if (count == pending) { + return MPACK_OK; + } else { + tokbuf->pending_tok = tok; + return MPACK_EOF; + } + } + + if (tokbuf->plen) return mpack_wpending(buf, buflen, tokbuf); + + if (*buflen < MPACK_MAX_TOKEN_LEN) { + ptr = tokbuf->pending; + ptrlen = sizeof(tokbuf->pending); + } else { + ptr = *buf; + ptrlen = *buflen; + } + + if ((status = mpack_wtoken(&tok, &ptr, &ptrlen))) return status; + + if (*buflen < MPACK_MAX_TOKEN_LEN) { + size_t toklen = sizeof(tokbuf->pending) - ptrlen; + size_t write_cnt = MIN(toklen, *buflen); + memcpy(*buf, tokbuf->pending, write_cnt); + *buf += write_cnt; + *buflen -= write_cnt; + if (write_cnt < toklen) { + assert(!*buflen); + tokbuf->plen = toklen; + tokbuf->ppos = write_cnt; + tokbuf->pending_tok = tok; + return MPACK_EOF; + } + } else { + *buflen -= (size_t)(ptr - *buf); + *buf = ptr; + } + + return MPACK_OK; +} + +static int mpack_rtoken(const char **buf, size_t *buflen, + mpack_token_t *tok) +{ + unsigned char t = ADVANCE(buf, buflen); + if (t < 0x80) { + /* positive fixint */ + return mpack_value(MPACK_TOKEN_UINT, 1, mpack_byte(t), tok); + } else if (t < 0x90) { + /* fixmap */ + return mpack_blob(MPACK_TOKEN_MAP, t & 0xf, 0, tok); + } else if (t < 0xa0) { + /* fixarray */ + return mpack_blob(MPACK_TOKEN_ARRAY, t & 0xf, 0, tok); + } else if (t < 0xc0) { + /* fixstr */ + return mpack_blob(MPACK_TOKEN_STR, t & 0x1f, 0, tok); + } else if (t < 0xe0) { + switch (t) { + case 0xc0: /* nil */ + return mpack_value(MPACK_TOKEN_NIL, 0, mpack_byte(0), tok); + case 0xc2: /* false */ + return mpack_value(MPACK_TOKEN_BOOLEAN, 1, mpack_byte(0), tok); + case 0xc3: /* true */ + return mpack_value(MPACK_TOKEN_BOOLEAN, 1, mpack_byte(1), tok); + case 0xc4: /* bin 8 */ + case 0xc5: /* bin 16 */ + case 0xc6: /* bin 32 */ + return mpack_rblob(MPACK_TOKEN_BIN, TLEN(t, 0xc4), buf, buflen, tok); + case 0xc7: /* ext 8 */ + case 0xc8: /* ext 16 */ + case 0xc9: /* ext 32 */ + return mpack_rblob(MPACK_TOKEN_EXT, TLEN(t, 0xc7), buf, buflen, tok); + case 0xca: /* float 32 */ + case 0xcb: /* float 64 */ + return mpack_rvalue(MPACK_TOKEN_FLOAT, TLEN(t, 0xc8), buf, buflen, tok); + case 0xcc: /* uint 8 */ + case 0xcd: /* uint 16 */ + case 0xce: /* uint 32 */ + case 0xcf: /* uint 64 */ + return mpack_rvalue(MPACK_TOKEN_UINT, TLEN(t, 0xcc), buf, buflen, tok); + case 0xd0: /* int 8 */ + case 0xd1: /* int 16 */ + case 0xd2: /* int 32 */ + case 0xd3: /* int 64 */ + return mpack_rvalue(MPACK_TOKEN_SINT, TLEN(t, 0xd0), buf, buflen, tok); + case 0xd4: /* fixext 1 */ + case 0xd5: /* fixext 2 */ + case 0xd6: /* fixext 4 */ + case 0xd7: /* fixext 8 */ + case 0xd8: /* fixext 16 */ + if (*buflen == 0) { + /* require only one extra byte for the type code */ + tok->length = 1; + return MPACK_EOF; + } + tok->length = TLEN(t, 0xd4); + tok->type = MPACK_TOKEN_EXT; + tok->data.ext_type = ADVANCE(buf, buflen); + return MPACK_OK; + case 0xd9: /* str 8 */ + case 0xda: /* str 16 */ + case 0xdb: /* str 32 */ + return mpack_rblob(MPACK_TOKEN_STR, TLEN(t, 0xd9), buf, buflen, tok); + case 0xdc: /* array 16 */ + case 0xdd: /* array 32 */ + return mpack_rblob(MPACK_TOKEN_ARRAY, TLEN(t, 0xdb), buf, buflen, tok); + case 0xde: /* map 16 */ + case 0xdf: /* map 32 */ + return mpack_rblob(MPACK_TOKEN_MAP, TLEN(t, 0xdd), buf, buflen, tok); + default: + return MPACK_ERROR; + } + } else { + /* negative fixint */ + return mpack_value(MPACK_TOKEN_SINT, 1, mpack_byte(t), tok); + } +} + +static int mpack_rpending(const char **buf, size_t *buflen, + mpack_tokbuf_t *state) +{ + size_t count; + assert(state->ppos < state->plen); + count = MIN(state->plen - state->ppos, *buflen); + memcpy(state->pending + state->ppos, *buf, count); + state->ppos += count; + if (state->ppos < state->plen) { + /* consume buffer since no token will be parsed yet. */ + *buf += *buflen; + *buflen = 0; + return 0; + } + return 1; +} + +static int mpack_rvalue(mpack_token_type_t type, mpack_uint32_t remaining, + const char **buf, size_t *buflen, mpack_token_t *tok) +{ + if (*buflen < remaining) { + tok->length = remaining; + return MPACK_EOF; + } + + mpack_value(type, remaining, mpack_byte(0), tok); + + while (remaining) { + mpack_uint32_t byte = ADVANCE(buf, buflen), byte_idx, byte_shift; + byte_idx = (mpack_uint32_t)--remaining; + byte_shift = (byte_idx % 4) * 8; + tok->data.value.lo |= byte << byte_shift; + if (remaining == 4) { + /* unpacked the first half of a 8-byte value, shift what was parsed to the + * "hi" field and reset "lo" for the trailing 4 bytes. */ + tok->data.value.hi = tok->data.value.lo; + tok->data.value.lo = 0; + } + } + + if (type == MPACK_TOKEN_SINT) { + mpack_uint32_t hi = tok->data.value.hi; + mpack_uint32_t lo = tok->data.value.lo; + mpack_uint32_t msb = (tok->length == 8 && hi >> 31) || + (tok->length == 4 && lo >> 31) || + (tok->length == 2 && lo >> 15) || + (tok->length == 1 && lo >> 7); + if (!msb) { + tok->type = MPACK_TOKEN_UINT; + } + } + + return MPACK_OK; +} + +static int mpack_rblob(mpack_token_type_t type, mpack_uint32_t tlen, + const char **buf, size_t *buflen, mpack_token_t *tok) +{ + mpack_token_t l; + mpack_uint32_t required = tlen + (type == MPACK_TOKEN_EXT ? 1 : 0); + + if (*buflen < required) { + tok->length = required; + return MPACK_EOF; + } + + l.data.value.lo = 0; + mpack_rvalue(MPACK_TOKEN_UINT, tlen, buf, buflen, &l); + tok->type = type; + tok->length = l.data.value.lo; + + if (type == MPACK_TOKEN_EXT) { + tok->data.ext_type = ADVANCE(buf, buflen); + } + + return MPACK_OK; +} + +static int mpack_wtoken(const mpack_token_t *tok, char **buf, + size_t *buflen) +{ + switch (tok->type) { + case MPACK_TOKEN_NIL: + return mpack_w1(buf, buflen, 0xc0); + case MPACK_TOKEN_BOOLEAN: + return mpack_w1(buf, buflen, tok->data.value.lo ? 0xc3 : 0xc2); + case MPACK_TOKEN_UINT: + return mpack_wpint(buf, buflen, tok->data.value); + case MPACK_TOKEN_SINT: + return mpack_wnint(buf, buflen, tok->data.value); + case MPACK_TOKEN_FLOAT: + return mpack_wfloat(buf, buflen, tok); + case MPACK_TOKEN_BIN: + return mpack_wbin(buf, buflen, tok->length); + case MPACK_TOKEN_STR: + return mpack_wstr(buf, buflen, tok->length); + case MPACK_TOKEN_EXT: + return mpack_wext(buf, buflen, tok->data.ext_type, tok->length); + case MPACK_TOKEN_ARRAY: + return mpack_warray(buf, buflen, tok->length); + case MPACK_TOKEN_MAP: + return mpack_wmap(buf, buflen, tok->length); + default: + return MPACK_ERROR; + } +} + +static int mpack_wpending(char **buf, size_t *buflen, mpack_tokbuf_t *state) +{ + size_t count; + assert(state->ppos < state->plen); + count = MIN(state->plen - state->ppos, *buflen); + memcpy(*buf, state->pending + state->ppos, count); + state->ppos += count; + *buf += count; + *buflen -= count; + if (state->ppos == state->plen) { + state->plen = 0; + return MPACK_OK; + } + return MPACK_EOF; +} + +static int mpack_wpint(char **buf, size_t *buflen, mpack_value_t val) +{ + mpack_uint32_t hi = val.hi; + mpack_uint32_t lo = val.lo; + + if (hi) { + /* uint 64 */ + return mpack_w1(buf, buflen, 0xcf) || + mpack_w4(buf, buflen, hi) || + mpack_w4(buf, buflen, lo); + } else if (lo > 0xffff) { + /* uint 32 */ + return mpack_w1(buf, buflen, 0xce) || + mpack_w4(buf, buflen, lo); + } else if (lo > 0xff) { + /* uint 16 */ + return mpack_w1(buf, buflen, 0xcd) || + mpack_w2(buf, buflen, lo); + } else if (lo > 0x7f) { + /* uint 8 */ + return mpack_w1(buf, buflen, 0xcc) || + mpack_w1(buf, buflen, lo); + } else { + return mpack_w1(buf, buflen, lo); + } +} + +static int mpack_wnint(char **buf, size_t *buflen, mpack_value_t val) +{ + mpack_uint32_t hi = val.hi; + mpack_uint32_t lo = val.lo; + + if (lo < 0x80000000) { + /* int 64 */ + return mpack_w1(buf, buflen, 0xd3) || + mpack_w4(buf, buflen, hi) || + mpack_w4(buf, buflen, lo); + } else if (lo < 0xffff7fff) { + /* int 32 */ + return mpack_w1(buf, buflen, 0xd2) || + mpack_w4(buf, buflen, lo); + } else if (lo < 0xffffff7f) { + /* int 16 */ + return mpack_w1(buf, buflen, 0xd1) || + mpack_w2(buf, buflen, lo); + } else if (lo < 0xffffffe0) { + /* int 8 */ + return mpack_w1(buf, buflen, 0xd0) || + mpack_w1(buf, buflen, lo); + } else { + /* negative fixint */ + return mpack_w1(buf, buflen, (mpack_uint32_t)(0x100 + lo)); + } +} + +static int mpack_wfloat(char **buf, size_t *buflen, + const mpack_token_t *tok) +{ + if (tok->length == 4) { + return mpack_w1(buf, buflen, 0xca) || + mpack_w4(buf, buflen, tok->data.value.lo); + } else if (tok->length == 8) { + return mpack_w1(buf, buflen, 0xcb) || + mpack_w4(buf, buflen, tok->data.value.hi) || + mpack_w4(buf, buflen, tok->data.value.lo); + } else { + return MPACK_ERROR; + } +} + +static int mpack_wstr(char **buf, size_t *buflen, mpack_uint32_t len) +{ + if (len < 0x20) { + return mpack_w1(buf, buflen, 0xa0 | len); + } else if (len < 0x100) { + return mpack_w1(buf, buflen, 0xd9) || + mpack_w1(buf, buflen, len); + } else if (len < 0x10000) { + return mpack_w1(buf, buflen, 0xda) || + mpack_w2(buf, buflen, len); + } else { + return mpack_w1(buf, buflen, 0xdb) || + mpack_w4(buf, buflen, len); + } +} + +static int mpack_wbin(char **buf, size_t *buflen, mpack_uint32_t len) +{ + if (len < 0x100) { + return mpack_w1(buf, buflen, 0xc4) || + mpack_w1(buf, buflen, len); + } else if (len < 0x10000) { + return mpack_w1(buf, buflen, 0xc5) || + mpack_w2(buf, buflen, len); + } else { + return mpack_w1(buf, buflen, 0xc6) || + mpack_w4(buf, buflen, len); + } +} + +static int mpack_wext(char **buf, size_t *buflen, int type, + mpack_uint32_t len) +{ + mpack_uint32_t t; + assert(type >= 0 && type < 0x80); + t = (mpack_uint32_t)type; + switch (len) { + case 1: mpack_w1(buf, buflen, 0xd4); return mpack_w1(buf, buflen, t); + case 2: mpack_w1(buf, buflen, 0xd5); return mpack_w1(buf, buflen, t); + case 4: mpack_w1(buf, buflen, 0xd6); return mpack_w1(buf, buflen, t); + case 8: mpack_w1(buf, buflen, 0xd7); return mpack_w1(buf, buflen, t); + case 16: mpack_w1(buf, buflen, 0xd8); return mpack_w1(buf, buflen, t); + default: + if (len < 0x100) { + return mpack_w1(buf, buflen, 0xc7) || + mpack_w1(buf, buflen, len) || + mpack_w1(buf, buflen, t); + } else if (len < 0x10000) { + return mpack_w1(buf, buflen, 0xc8) || + mpack_w2(buf, buflen, len) || + mpack_w1(buf, buflen, t); + } else { + return mpack_w1(buf, buflen, 0xc9) || + mpack_w4(buf, buflen, len) || + mpack_w1(buf, buflen, t); + } + } +} + +static int mpack_warray(char **buf, size_t *buflen, mpack_uint32_t len) +{ + if (len < 0x10) { + return mpack_w1(buf, buflen, 0x90 | len); + } else if (len < 0x10000) { + return mpack_w1(buf, buflen, 0xdc) || + mpack_w2(buf, buflen, len); + } else { + return mpack_w1(buf, buflen, 0xdd) || + mpack_w4(buf, buflen, len); + } +} + +static int mpack_wmap(char **buf, size_t *buflen, mpack_uint32_t len) +{ + if (len < 0x10) { + return mpack_w1(buf, buflen, 0x80 | len); + } else if (len < 0x10000) { + return mpack_w1(buf, buflen, 0xde) || + mpack_w2(buf, buflen, len); + } else { + return mpack_w1(buf, buflen, 0xdf) || + mpack_w4(buf, buflen, len); + } +} + +static int mpack_w1(char **b, size_t *bl, mpack_uint32_t v) +{ + (*bl)--; + *(*b)++ = (char)(v & 0xff); + return MPACK_OK; +} + +static int mpack_w2(char **b, size_t *bl, mpack_uint32_t v) +{ + *bl -= 2; + *(*b)++ = (char)((v >> 8) & 0xff); + *(*b)++ = (char)(v & 0xff); + return MPACK_OK; +} + +static int mpack_w4(char **b, size_t *bl, mpack_uint32_t v) +{ + *bl -= 4; + *(*b)++ = (char)((v >> 24) & 0xff); + *(*b)++ = (char)((v >> 16) & 0xff); + *(*b)++ = (char)((v >> 8) & 0xff); + *(*b)++ = (char)(v & 0xff); + return MPACK_OK; +} + +static int mpack_value(mpack_token_type_t type, mpack_uint32_t length, + mpack_value_t value, mpack_token_t *tok) +{ + tok->type = type; + tok->length = length; + tok->data.value = value; + return MPACK_OK; +} + +static int mpack_blob(mpack_token_type_t type, mpack_uint32_t length, + int ext_type, mpack_token_t *tok) +{ + tok->type = type; + tok->length = length; + tok->data.ext_type = ext_type; + return MPACK_OK; +} + +static mpack_value_t mpack_byte(unsigned char byte) +{ + mpack_value_t rv; + rv.lo = byte; + rv.hi = 0; + return rv; +} diff --git a/src/mpack/mpack_core.h b/src/mpack/mpack_core.h new file mode 100644 index 0000000000..9edd13c41e --- /dev/null +++ b/src/mpack/mpack_core.h @@ -0,0 +1,87 @@ +#ifndef MPACK_CORE_H +#define MPACK_CORE_H + +#ifndef MPACK_API +# define MPACK_API extern +#endif + +#include <assert.h> +#include <limits.h> +#include <stddef.h> + +#ifdef __GNUC__ +# define FPURE __attribute__((const)) +# define FNONULL __attribute__((nonnull)) +# define FNONULL_ARG(x) __attribute__((nonnull x)) +# define FUNUSED __attribute__((unused)) +#else +# define FPURE +# define FNONULL +# define FNONULL_ARG(x) +# define FUNUSED +#endif + +#if UINT_MAX == 0xffffffff +typedef int mpack_sint32_t; +typedef unsigned int mpack_uint32_t; +#elif ULONG_MAX == 0xffffffff +typedef long mpack_sint32_t; +typedef unsigned long mpack_uint32_t; +#else +# error "can't find unsigned 32-bit integer type" +#endif + +typedef struct mpack_value_s { + mpack_uint32_t lo, hi; +} mpack_value_t; + + +enum { + MPACK_OK = 0, + MPACK_EOF = 1, + MPACK_ERROR = 2 +}; + +#define MPACK_MAX_TOKEN_LEN 9 /* 64-bit ints/floats plus type code */ + +typedef enum { + MPACK_TOKEN_NIL = 1, + MPACK_TOKEN_BOOLEAN = 2, + MPACK_TOKEN_UINT = 3, + MPACK_TOKEN_SINT = 4, + MPACK_TOKEN_FLOAT = 5, + MPACK_TOKEN_CHUNK = 6, + MPACK_TOKEN_ARRAY = 7, + MPACK_TOKEN_MAP = 8, + MPACK_TOKEN_BIN = 9, + MPACK_TOKEN_STR = 10, + MPACK_TOKEN_EXT = 11 +} mpack_token_type_t; + +typedef struct mpack_token_s { + mpack_token_type_t type; /* Type of token */ + mpack_uint32_t length; /* Byte length for str/bin/ext/chunk/float/int/uint. + Item count for array/map. */ + union { + mpack_value_t value; /* 32-bit parts of primitives (bool,int,float) */ + const char *chunk_ptr; /* Chunk of data from str/bin/ext */ + int ext_type; /* Type field for ext tokens */ + } data; +} mpack_token_t; + +typedef struct mpack_tokbuf_s { + char pending[MPACK_MAX_TOKEN_LEN]; + mpack_token_t pending_tok; + size_t ppos, plen; + mpack_uint32_t passthrough; +} mpack_tokbuf_t; + +#define MPACK_TOKBUF_INITIAL_VALUE { { 0 }, { 0, 0, { { 0, 0 } } }, 0, 0, 0 } + +MPACK_API void mpack_tokbuf_init(mpack_tokbuf_t *tb) FUNUSED FNONULL; +MPACK_API int mpack_read(mpack_tokbuf_t *tb, const char **b, size_t *bl, + mpack_token_t *tok) FUNUSED FNONULL; +MPACK_API int mpack_write(mpack_tokbuf_t *tb, char **b, size_t *bl, + const mpack_token_t *tok) FUNUSED FNONULL; + +#endif /* MPACK_CORE_H */ diff --git a/src/mpack/object.c b/src/mpack/object.c new file mode 100644 index 0000000000..0c7759ee51 --- /dev/null +++ b/src/mpack/object.c @@ -0,0 +1,195 @@ +#include <string.h> + +#include "object.h" + +static int mpack_parser_full(mpack_parser_t *w); +static mpack_node_t *mpack_parser_push(mpack_parser_t *w); +static mpack_node_t *mpack_parser_pop(mpack_parser_t *w); + +MPACK_API void mpack_parser_init(mpack_parser_t *parser, + mpack_uint32_t capacity) +{ + mpack_tokbuf_init(&parser->tokbuf); + parser->data.p = NULL; + parser->capacity = capacity ? capacity : MPACK_MAX_OBJECT_DEPTH; + parser->size = 0; + parser->exiting = 0; + memset(parser->items, 0, sizeof(mpack_node_t) * (parser->capacity + 1)); + parser->items[0].pos = (size_t)-1; + parser->status = 0; +} + +#define MPACK_EXCEPTION_CHECK(parser) \ + do { \ + if (parser->status == MPACK_EXCEPTION) { \ + return MPACK_EXCEPTION; \ + } \ + } while (0) + +#define MPACK_WALK(action) \ + do { \ + mpack_node_t *n; \ + \ + if (parser->exiting) goto exit; \ + if (mpack_parser_full(parser)) return MPACK_NOMEM; \ + n = mpack_parser_push(parser); \ + action; \ + MPACK_EXCEPTION_CHECK(parser); \ + parser->exiting = 1; \ + return MPACK_EOF; \ + \ +exit: \ + parser->exiting = 0; \ + while ((n = mpack_parser_pop(parser))) { \ + exit_cb(parser, n); \ + MPACK_EXCEPTION_CHECK(parser); \ + if (!parser->size) return MPACK_OK; \ + } \ + \ + return MPACK_EOF; \ + } while (0) + +MPACK_API int mpack_parse_tok(mpack_parser_t *parser, mpack_token_t tok, + mpack_walk_cb enter_cb, mpack_walk_cb exit_cb) +{ + MPACK_EXCEPTION_CHECK(parser); + MPACK_WALK({n->tok = tok; enter_cb(parser, n);}); +} + +MPACK_API int mpack_unparse_tok(mpack_parser_t *parser, mpack_token_t *tok, + mpack_walk_cb enter_cb, mpack_walk_cb exit_cb) +{ + MPACK_EXCEPTION_CHECK(parser); + MPACK_WALK({enter_cb(parser, n); *tok = n->tok;}); +} + +MPACK_API int mpack_parse(mpack_parser_t *parser, const char **buf, + size_t *buflen, mpack_walk_cb enter_cb, mpack_walk_cb exit_cb) +{ + int status = MPACK_EOF; + MPACK_EXCEPTION_CHECK(parser); + + while (*buflen && status) { + mpack_token_t tok; + mpack_tokbuf_t *tb = &parser->tokbuf; + const char *buf_save = *buf; + size_t buflen_save = *buflen; + + if ((status = mpack_read(tb, buf, buflen, &tok)) == MPACK_EOF) continue; + else if (status == MPACK_ERROR) goto rollback; + + do { + status = mpack_parse_tok(parser, tok, enter_cb, exit_cb); + MPACK_EXCEPTION_CHECK(parser); + } while (parser->exiting); + + if (status != MPACK_NOMEM) continue; + +rollback: + /* restore buf/buflen so the next call will try to read the same token */ + *buf = buf_save; + *buflen = buflen_save; + break; + } + + return status; +} + +MPACK_API int mpack_unparse(mpack_parser_t *parser, char **buf, size_t *buflen, + mpack_walk_cb enter_cb, mpack_walk_cb exit_cb) +{ + int status = MPACK_EOF; + MPACK_EXCEPTION_CHECK(parser); + + while (*buflen && status) { + int write_status; + mpack_token_t tok; + mpack_tokbuf_t *tb = &parser->tokbuf; + + if (!tb->plen) + parser->status = mpack_unparse_tok(parser, &tok, enter_cb, exit_cb); + + MPACK_EXCEPTION_CHECK(parser); + + status = parser->status; + + if (status == MPACK_NOMEM) + break; + + if (parser->exiting) { + write_status = mpack_write(tb, buf, buflen, &tok); + status = write_status ? write_status : status; + } + } + + return status; +} + +MPACK_API void mpack_parser_copy(mpack_parser_t *dst, mpack_parser_t *src) +{ + mpack_uint32_t i; + mpack_uint32_t dst_capacity = dst->capacity; + assert(src->capacity <= dst_capacity); + /* copy all fields except the stack */ + memcpy(dst, src, sizeof(mpack_one_parser_t) - sizeof(mpack_node_t)); + /* reset capacity */ + dst->capacity = dst_capacity; + /* copy the stack */ + for (i = 0; i <= src->capacity; i++) { + dst->items[i] = src->items[i]; + } +} + +static int mpack_parser_full(mpack_parser_t *parser) +{ + return parser->size == parser->capacity; +} + +static mpack_node_t *mpack_parser_push(mpack_parser_t *parser) +{ + mpack_node_t *top; + assert(parser->size < parser->capacity); + top = parser->items + parser->size + 1; + top->data[0].p = NULL; + top->data[1].p = NULL; + top->pos = 0; + top->key_visited = 0; + /* increase size and invoke callback, passing parent node if any */ + parser->size++; + return top; +} + +static mpack_node_t *mpack_parser_pop(mpack_parser_t *parser) +{ + mpack_node_t *top, *parent; + assert(parser->size); + top = parser->items + parser->size; + + if (top->tok.type > MPACK_TOKEN_CHUNK && top->pos < top->tok.length) { + /* continue processing children */ + return NULL; + } + + parent = MPACK_PARENT_NODE(top); + if (parent) { + /* we use parent->tok.length to keep track of how many children remain. + * update it to reflect the processed node. */ + if (top->tok.type == MPACK_TOKEN_CHUNK) { + parent->pos += top->tok.length; + } else if (parent->tok.type == MPACK_TOKEN_MAP) { + /* maps allow up to 2^32 - 1 pairs, so to allow this many items in a + * 32-bit length variable we use an additional flag to determine if the + * key of a certain position was visited */ + if (parent->key_visited) { + parent->pos++; + } + parent->key_visited = !parent->key_visited; + } else { + parent->pos++; + } + } + + parser->size--; + return top; +} + diff --git a/src/mpack/object.h b/src/mpack/object.h new file mode 100644 index 0000000000..5327e56e18 --- /dev/null +++ b/src/mpack/object.h @@ -0,0 +1,86 @@ +#ifndef MPACK_OBJECT_H +#define MPACK_OBJECT_H + +#include "mpack_core.h" +#include "conv.h" + +#ifndef MPACK_MAX_OBJECT_DEPTH +# define MPACK_MAX_OBJECT_DEPTH 32 +#endif + +#define MPACK_PARENT_NODE(n) (((n) - 1)->pos == (size_t)-1 ? NULL : (n) - 1) + +#define MPACK_THROW(parser) \ + do { \ + parser->status = MPACK_EXCEPTION; \ + return; \ + } while (0) + +enum { + MPACK_EXCEPTION = -1, + MPACK_NOMEM = MPACK_ERROR + 1 +}; + +/* Storing integer in pointers in undefined behavior according to the C + * standard. Define a union type to accomodate arbitrary user data associated + * with nodes(and with requests in rpc.h). */ +typedef union { + void *p; + mpack_uintmax_t u; + mpack_sintmax_t i; + double d; +} mpack_data_t; + +typedef struct mpack_node_s { + mpack_token_t tok; + size_t pos; + /* flag to determine if the key was visited when traversing a map */ + int key_visited; + /* allow 2 instances mpack_data_t per node. the reason is that when + * serializing, the user may need to keep track of traversal state besides the + * parent node reference */ + mpack_data_t data[2]; +} mpack_node_t; + +#define MPACK_PARSER_STRUCT(c) \ + struct { \ + mpack_data_t data; \ + mpack_uint32_t size, capacity; \ + int status; \ + int exiting; \ + mpack_tokbuf_t tokbuf; \ + mpack_node_t items[c + 1]; \ + } + +/* Some compilers warn against anonymous structs: + * https://github.com/libmpack/libmpack/issues/6 */ +typedef MPACK_PARSER_STRUCT(0) mpack_one_parser_t; + +#define MPACK_PARSER_STRUCT_SIZE(c) \ + (sizeof(mpack_node_t) * c + \ + sizeof(mpack_one_parser_t)) + +typedef MPACK_PARSER_STRUCT(MPACK_MAX_OBJECT_DEPTH) mpack_parser_t; +typedef void(*mpack_walk_cb)(mpack_parser_t *w, mpack_node_t *n); + +MPACK_API void mpack_parser_init(mpack_parser_t *p, mpack_uint32_t c) + FUNUSED FNONULL; + +MPACK_API int mpack_parse_tok(mpack_parser_t *walker, mpack_token_t tok, + mpack_walk_cb enter_cb, mpack_walk_cb exit_cb) + FUNUSED FNONULL_ARG((1,3,4)); +MPACK_API int mpack_unparse_tok(mpack_parser_t *walker, mpack_token_t *tok, + mpack_walk_cb enter_cb, mpack_walk_cb exit_cb) + FUNUSED FNONULL_ARG((1,2,3,4)); + +MPACK_API int mpack_parse(mpack_parser_t *parser, const char **b, size_t *bl, + mpack_walk_cb enter_cb, mpack_walk_cb exit_cb) + FUNUSED FNONULL_ARG((1,2,3,4,5)); +MPACK_API int mpack_unparse(mpack_parser_t *parser, char **b, size_t *bl, + mpack_walk_cb enter_cb, mpack_walk_cb exit_cb) + FUNUSED FNONULL_ARG((1,2,3,4,5)); + +MPACK_API void mpack_parser_copy(mpack_parser_t *d, mpack_parser_t *s) + FUNUSED FNONULL; + +#endif /* MPACK_OBJECT_H */ diff --git a/src/mpack/rpc.c b/src/mpack/rpc.c new file mode 100644 index 0000000000..3b2b328065 --- /dev/null +++ b/src/mpack/rpc.c @@ -0,0 +1,331 @@ +#include <string.h> + +#include "rpc.h" + +enum { + MPACK_RPC_RECEIVE_ARRAY = 1, + MPACK_RPC_RECEIVE_TYPE, + MPACK_RPC_RECEIVE_ID +}; + +static mpack_rpc_header_t mpack_rpc_request_hdr(void); +static mpack_rpc_header_t mpack_rpc_reply_hdr(void); +static mpack_rpc_header_t mpack_rpc_notify_hdr(void); +static int mpack_rpc_put(mpack_rpc_session_t *s, mpack_rpc_message_t m); +static int mpack_rpc_pop(mpack_rpc_session_t *s, mpack_rpc_message_t *m); +static void mpack_rpc_reset_hdr(mpack_rpc_header_t *hdr); + +MPACK_API void mpack_rpc_session_init(mpack_rpc_session_t *session, + mpack_uint32_t capacity) +{ + session->capacity = capacity ? capacity : MPACK_RPC_MAX_REQUESTS; + session->request_id = 0; + mpack_tokbuf_init(&session->reader); + mpack_tokbuf_init(&session->writer); + mpack_rpc_reset_hdr(&session->receive); + mpack_rpc_reset_hdr(&session->send); + memset(session->slots, 0, + sizeof(struct mpack_rpc_slot_s) * session->capacity); +} + +MPACK_API int mpack_rpc_receive_tok(mpack_rpc_session_t *session, + mpack_token_t tok, mpack_rpc_message_t *msg) +{ + int type; + + if (session->receive.index == 0) { + if (tok.type != MPACK_TOKEN_ARRAY) + /* not an array */ + return MPACK_RPC_EARRAY; + + if (tok.length < 3 || tok.length > 4) + /* invalid array length */ + return MPACK_RPC_EARRAYL; + + session->receive.toks[0] = tok; + session->receive.index++; + return MPACK_EOF; /* get the type */ + } + + if (session->receive.index == 1) { + + if (tok.type != MPACK_TOKEN_UINT || tok.length > 1 || tok.data.value.lo > 2) + /* invalid type */ + return MPACK_RPC_ETYPE; + + if (tok.data.value.lo < 2 && session->receive.toks[0].length != 4) + /* request or response with array length != 4 */ + return MPACK_RPC_EARRAYL; + + if (tok.data.value.lo == 2 && session->receive.toks[0].length != 3) + /* notification with array length != 3 */ + return MPACK_RPC_EARRAYL; + + session->receive.toks[1] = tok; + session->receive.index++; + + if (tok.data.value.lo < 2) return MPACK_EOF; + + type = MPACK_RPC_NOTIFICATION; + goto end; + } + + assert(session->receive.index == 2); + + if (tok.type != MPACK_TOKEN_UINT || tok.length > 4) + /* invalid request/response id */ + return MPACK_RPC_EMSGID; + + msg->id = tok.data.value.lo; + msg->data.p = NULL; + type = (int)session->receive.toks[1].data.value.lo + MPACK_RPC_REQUEST; + + if (type == MPACK_RPC_RESPONSE && !mpack_rpc_pop(session, msg)) + /* response with invalid id */ + return MPACK_RPC_ERESPID; + +end: + mpack_rpc_reset_hdr(&session->receive); + return type; +} + +MPACK_API int mpack_rpc_request_tok(mpack_rpc_session_t *session, + mpack_token_t *tok, mpack_data_t data) +{ + if (session->send.index == 0) { + int status; + mpack_rpc_message_t msg; + do { + msg.id = session->request_id; + msg.data = data; + session->send = mpack_rpc_request_hdr(); + session->send.toks[2].type = MPACK_TOKEN_UINT; + session->send.toks[2].data.value.lo = msg.id; + session->send.toks[2].data.value.hi = 0; + *tok = session->send.toks[0]; + status = mpack_rpc_put(session, msg); + if (status == -1) return MPACK_NOMEM; + session->request_id = (session->request_id + 1) % 0xffffffff; + } while (!status); + session->send.index++; + return MPACK_EOF; + } + + if (session->send.index == 1) { + *tok = session->send.toks[1]; + session->send.index++; + return MPACK_EOF; + } + + assert(session->send.index == 2); + *tok = session->send.toks[2]; + mpack_rpc_reset_hdr(&session->send); + return MPACK_OK; +} + +MPACK_API int mpack_rpc_reply_tok(mpack_rpc_session_t *session, + mpack_token_t *tok, mpack_uint32_t id) +{ + if (session->send.index == 0) { + session->send = mpack_rpc_reply_hdr(); + session->send.toks[2].type = MPACK_TOKEN_UINT; + session->send.toks[2].data.value.lo = id; + session->send.toks[2].data.value.hi = 0; + *tok = session->send.toks[0]; + session->send.index++; + return MPACK_EOF; + } + + if (session->send.index == 1) { + *tok = session->send.toks[1]; + session->send.index++; + return MPACK_EOF; + } + + assert(session->send.index == 2); + *tok = session->send.toks[2]; + mpack_rpc_reset_hdr(&session->send); + return MPACK_OK; +} + +MPACK_API int mpack_rpc_notify_tok(mpack_rpc_session_t *session, + mpack_token_t *tok) +{ + if (session->send.index == 0) { + session->send = mpack_rpc_notify_hdr(); + *tok = session->send.toks[0]; + session->send.index++; + return MPACK_EOF; + } + + assert(session->send.index == 1); + *tok = session->send.toks[1]; + mpack_rpc_reset_hdr(&session->send); + return MPACK_OK; +} + +MPACK_API int mpack_rpc_receive(mpack_rpc_session_t *session, const char **buf, + size_t *buflen, mpack_rpc_message_t *msg) +{ + int status; + + do { + mpack_token_t tok; + status = mpack_read(&session->reader, buf, buflen, &tok); + if (status) break; + status = mpack_rpc_receive_tok(session, tok, msg); + if (status >= MPACK_RPC_REQUEST) break; + } while (*buflen); + + return status; +} + +MPACK_API int mpack_rpc_request(mpack_rpc_session_t *session, char **buf, + size_t *buflen, mpack_data_t data) +{ + int status = MPACK_EOF; + + while (status && *buflen) { + int write_status; + mpack_token_t tok; + if (!session->writer.plen) { + status = mpack_rpc_request_tok(session, &tok, data); + } + if (status == MPACK_NOMEM) break; + write_status = mpack_write(&session->writer, buf, buflen, &tok); + status = write_status ? write_status : status; + } + + return status; +} + +MPACK_API int mpack_rpc_reply(mpack_rpc_session_t *session, char **buf, + size_t *buflen, mpack_uint32_t id) +{ + int status = MPACK_EOF; + + while (status && *buflen) { + int write_status; + mpack_token_t tok; + if (!session->writer.plen) { + status = mpack_rpc_reply_tok(session, &tok, id); + } + write_status = mpack_write(&session->writer, buf, buflen, &tok); + status = write_status ? write_status : status; + } + + return status; +} + +MPACK_API int mpack_rpc_notify(mpack_rpc_session_t *session, char **buf, + size_t *buflen) +{ + int status = MPACK_EOF; + + while (status && *buflen) { + int write_status; + mpack_token_t tok; + if (!session->writer.plen) { + status = mpack_rpc_notify_tok(session, &tok); + } + write_status = mpack_write(&session->writer, buf, buflen, &tok); + status = write_status ? write_status : status; + } + + return status; +} + +MPACK_API void mpack_rpc_session_copy(mpack_rpc_session_t *dst, + mpack_rpc_session_t *src) +{ + mpack_uint32_t i; + mpack_uint32_t dst_capacity = dst->capacity; + assert(src->capacity <= dst_capacity); + /* copy all fields except slots */ + memcpy(dst, src, sizeof(mpack_rpc_one_session_t) - + sizeof(struct mpack_rpc_slot_s)); + /* reset capacity */ + dst->capacity = dst_capacity; + /* reinsert requests */ + memset(dst->slots, 0, sizeof(struct mpack_rpc_slot_s) * dst->capacity); + for (i = 0; i < src->capacity; i++) { + if (src->slots[i].used) mpack_rpc_put(dst, src->slots[i].msg); + } +} + +static mpack_rpc_header_t mpack_rpc_request_hdr(void) +{ + mpack_rpc_header_t hdr; + hdr.index = 0; + hdr.toks[0].type = MPACK_TOKEN_ARRAY; + hdr.toks[0].length = 4; + hdr.toks[1].type = MPACK_TOKEN_UINT; + hdr.toks[1].data.value.lo = 0; + hdr.toks[1].data.value.hi = 0; + return hdr; +} + +static mpack_rpc_header_t mpack_rpc_reply_hdr(void) +{ + mpack_rpc_header_t hdr = mpack_rpc_request_hdr(); + hdr.toks[1].data.value.lo = 1; + hdr.toks[1].data.value.hi = 0; + return hdr; +} + +static mpack_rpc_header_t mpack_rpc_notify_hdr(void) +{ + mpack_rpc_header_t hdr = mpack_rpc_request_hdr(); + hdr.toks[0].length = 3; + hdr.toks[1].data.value.lo = 2; + hdr.toks[1].data.value.hi = 0; + return hdr; +} + +static int mpack_rpc_put(mpack_rpc_session_t *session, mpack_rpc_message_t msg) +{ + struct mpack_rpc_slot_s *slot = NULL; + mpack_uint32_t i; + mpack_uint32_t hash = msg.id % session->capacity; + + for (i = 0; i < session->capacity; i++) { + if (!session->slots[hash].used || session->slots[hash].msg.id == msg.id) { + slot = session->slots + hash; + break; + } + hash = hash > 0 ? hash - 1 : session->capacity - 1; + } + + if (!slot) return -1; /* no space */ + if (slot->msg.id == msg.id && slot->used) return 0; /* duplicate key */ + slot->msg = msg; + slot->used = 1; + return 1; +} + +static int mpack_rpc_pop(mpack_rpc_session_t *session, mpack_rpc_message_t *msg) +{ + struct mpack_rpc_slot_s *slot = NULL; + mpack_uint32_t i; + mpack_uint32_t hash = msg->id % session->capacity; + + for (i = 0; i < session->capacity; i++) { + if (session->slots[hash].used && session->slots[hash].msg.id == msg->id) { + slot = session->slots + hash; + break; + } + hash = hash > 0 ? hash - 1 : session->capacity - 1; + } + + if (!slot) return 0; + + *msg = slot->msg; + slot->used = 0; + + return 1; +} + +static void mpack_rpc_reset_hdr(mpack_rpc_header_t *hdr) +{ + hdr->index = 0; +} diff --git a/src/mpack/rpc.h b/src/mpack/rpc.h new file mode 100644 index 0000000000..c1e8d656b5 --- /dev/null +++ b/src/mpack/rpc.h @@ -0,0 +1,83 @@ +#ifndef MPACK_RPC_H +#define MPACK_RPC_H + +#include "mpack_core.h" +#include "object.h" + +#ifndef MPACK_RPC_MAX_REQUESTS +# define MPACK_RPC_MAX_REQUESTS 32 +#endif + +enum { + MPACK_RPC_REQUEST = MPACK_NOMEM + 1, + MPACK_RPC_RESPONSE, + MPACK_RPC_NOTIFICATION, + MPACK_RPC_ERROR +}; + +enum { + MPACK_RPC_EARRAY = MPACK_RPC_ERROR, + MPACK_RPC_EARRAYL, + MPACK_RPC_ETYPE, + MPACK_RPC_EMSGID, + MPACK_RPC_ERESPID +}; + +typedef struct mpack_rpc_header_s { + mpack_token_t toks[3]; + int index; +} mpack_rpc_header_t; + +typedef struct mpack_rpc_message_s { + mpack_uint32_t id; + mpack_data_t data; +} mpack_rpc_message_t; + +struct mpack_rpc_slot_s { + int used; + mpack_rpc_message_t msg; +}; + +#define MPACK_RPC_SESSION_STRUCT(c) \ + struct { \ + mpack_tokbuf_t reader, writer; \ + mpack_rpc_header_t receive, send; \ + mpack_uint32_t request_id, capacity; \ + struct mpack_rpc_slot_s slots[c]; \ + } + +/* Some compilers warn against anonymous structs: + * https://github.com/libmpack/libmpack/issues/6 */ +typedef MPACK_RPC_SESSION_STRUCT(1) mpack_rpc_one_session_t; + +#define MPACK_RPC_SESSION_STRUCT_SIZE(c) \ + (sizeof(struct mpack_rpc_slot_s) * (c - 1) + \ + sizeof(mpack_rpc_one_session_t)) + +typedef MPACK_RPC_SESSION_STRUCT(MPACK_RPC_MAX_REQUESTS) mpack_rpc_session_t; + +MPACK_API void mpack_rpc_session_init(mpack_rpc_session_t *s, mpack_uint32_t c) + FUNUSED FNONULL; + +MPACK_API int mpack_rpc_receive_tok(mpack_rpc_session_t *s, mpack_token_t t, + mpack_rpc_message_t *msg) FUNUSED FNONULL; +MPACK_API int mpack_rpc_request_tok(mpack_rpc_session_t *s, mpack_token_t *t, + mpack_data_t d) FUNUSED FNONULL_ARG((1,2)); +MPACK_API int mpack_rpc_reply_tok(mpack_rpc_session_t *s, mpack_token_t *t, + mpack_uint32_t i) FUNUSED FNONULL; +MPACK_API int mpack_rpc_notify_tok(mpack_rpc_session_t *s, mpack_token_t *t) + FUNUSED FNONULL; + +MPACK_API int mpack_rpc_receive(mpack_rpc_session_t *s, const char **b, + size_t *bl, mpack_rpc_message_t *m) FUNUSED FNONULL; +MPACK_API int mpack_rpc_request(mpack_rpc_session_t *s, char **b, size_t *bl, + mpack_data_t d) FUNUSED FNONULL_ARG((1,2,3)); +MPACK_API int mpack_rpc_reply(mpack_rpc_session_t *s, char **b, size_t *bl, + mpack_uint32_t i) FNONULL FUNUSED; +MPACK_API int mpack_rpc_notify(mpack_rpc_session_t *s, char **b, size_t *bl) + FNONULL FUNUSED; + +MPACK_API void mpack_rpc_session_copy(mpack_rpc_session_t *d, + mpack_rpc_session_t *s) FUNUSED FNONULL; + +#endif /* MPACK_RPC_H */ diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 4a698052ee..331ab16dd7 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -87,8 +87,8 @@ file(MAKE_DIRECTORY ${LINT_SUPPRESSES_ROOT}/src) file(GLOB NVIM_SOURCES *.c) file(GLOB NVIM_HEADERS *.h) -file(GLOB XDIFF_SOURCES ../xdiff/*.c) -file(GLOB XDIFF_HEADERS ../xdiff/*.h) +file(GLOB EXTERNAL_SOURCES ../xdiff/*.c ../mpack/*.c) +file(GLOB EXTERNAL_HEADERS ../xdiff/*.h ../mpack/*.h) foreach(subdir os @@ -171,8 +171,8 @@ foreach(sfile ${CONV_SOURCES}) message(FATAL_ERROR "${sfile} doesn't exist (it was added to CONV_SOURCES)") endif() endforeach() -# xdiff: inlined external project, we don't maintain it. #9306 -list(APPEND CONV_SOURCES ${XDIFF_SOURCES}) +# xdiff, mpack: inlined external project, we don't maintain it. #9306 +list(APPEND CONV_SOURCES ${EXTERNAL_SOURCES}) if(NOT MSVC) set_source_files_properties( @@ -471,7 +471,7 @@ endif() add_executable(nvim ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} ${NVIM_GENERATED_SOURCES} ${NVIM_SOURCES} ${NVIM_HEADERS} - ${XDIFF_SOURCES} ${XDIFF_HEADERS}) + ${EXTERNAL_SOURCES} ${EXTERNAL_HEADERS}) target_link_libraries(nvim ${NVIM_EXEC_LINK_LIBRARIES}) install_helper(TARGETS nvim) @@ -602,7 +602,7 @@ add_library( EXCLUDE_FROM_ALL ${NVIM_SOURCES} ${NVIM_GENERATED_SOURCES} ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} - ${XDIFF_SOURCES} ${XDIFF_HEADERS} + ${EXTERNAL_SOURCES} ${EXTERNAL_HEADERS} ) set_property(TARGET libnvim APPEND PROPERTY INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS}) @@ -632,7 +632,7 @@ else() EXCLUDE_FROM_ALL ${NVIM_SOURCES} ${NVIM_GENERATED_SOURCES} ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} - ${XDIFF_SOURCES} ${XDIFF_HEADERS} + ${EXTERNAL_SOURCES} ${EXTERNAL_HEADERS} ${UNIT_TEST_FIXTURES} ) target_link_libraries(nvim-test ${NVIM_TEST_LINK_LIBRARIES}) diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index a8b10f86f5..d071203db1 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -33,6 +33,7 @@ #include "nvim/eval/userfunc.h" #include "nvim/event/time.h" #include "nvim/event/loop.h" +#include "mpack/lmpack.h" #include "nvim/os/os.h" @@ -506,6 +507,8 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_setfield(lstate, -2, "__tostring"); lua_setmetatable(lstate, -2); nlua_nil_ref = nlua_ref(lstate, -1); + lua_pushvalue(lstate, -1); + lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.NIL"); lua_setfield(lstate, -2, "NIL"); // vim._empty_dict_mt @@ -513,8 +516,23 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, &nlua_empty_dict_tostring); lua_setfield(lstate, -2, "__tostring"); nlua_empty_dict_ref = nlua_ref(lstate, -1); + lua_pushvalue(lstate, -1); + lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.empty_dict"); lua_setfield(lstate, -2, "_empty_dict_mt"); + // vim.mpack + luaopen_mpack(lstate); + lua_pushvalue(lstate, -1); + lua_setfield(lstate, -3, "mpack"); + + // package.loaded.mpack = vim.mpack + // otherwise luv will be reinitialized when require'mpack' + lua_getglobal(lstate, "package"); + lua_getfield(lstate, -1, "loaded"); + lua_pushvalue(lstate, -3); + lua_setfield(lstate, -2, "mpack"); + lua_pop(lstate, 3); + // internal vim._treesitter... API nlua_add_treesitter(lstate); |