From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: From: Alexander Turenko Subject: [PATCH v3 4/7] lua: add non-recursive msgpack decoding functions Date: Wed, 10 Apr 2019 18:21:22 +0300 Message-Id: In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit To: Vladimir Davydov Cc: Alexander Turenko , tarantool-patches@freelists.org List-ID: Needed for #3276. @TarantoolBot document Title: Non-recursive msgpack decoding functions Contracts: ``` msgpack.decode_array(buf.rpos, buf:size()) -> arr_len, new_rpos msgpack.decode_map(buf.rpos, buf:size()) -> map_len, new_rpos ``` These functions are intended to be used with a msgpack buffer received from net.box. A user may want to skip {[IPROTO_DATA_KEY] = ...} wrapper and an array header before pass the buffer to decode in some C function. See https://github.com/tarantool/tarantool/issues/2195 for more information re this net.box's API. Consider merger's docbot comment for usage examples. --- src/lua/msgpack.c | 80 +++++++++++++++ test/app-tap/msgpack.test.lua | 180 +++++++++++++++++++++++++++++++++- 2 files changed, 259 insertions(+), 1 deletion(-) diff --git a/src/lua/msgpack.c b/src/lua/msgpack.c index 1b1874eb2..f445840bf 100644 --- a/src/lua/msgpack.c +++ b/src/lua/msgpack.c @@ -422,6 +422,84 @@ lua_ibuf_msgpack_decode(lua_State *L) return 2; } +/** + * Verify and set arguments: data and size. + * + * Always return 0. In case of any fail raise a Lua error. + */ +static int +verify_decode_args(lua_State *L, const char *func_name, const char **data_p, + ptrdiff_t *size_p) +{ + /* Verify arguments count. */ + if (lua_gettop(L) != 2) + return luaL_error(L, "Usage: %s(ptr, size)", func_name); + + /* Verify ptr type. */ + uint32_t ctypeid; + const char *data = *(char **) luaL_checkcdata(L, 1, &ctypeid); + if (ctypeid != CTID_CHAR_PTR) + return luaL_error(L, "%s: 'char *' expected", func_name); + + /* Verify size type and value. */ + ptrdiff_t size = (ptrdiff_t) luaL_checkinteger(L, 2); + if (size <= 0) + return luaL_error(L, "%s: non-positive size", func_name); + + *data_p = data; + *size_p = size; + + return 0; +} + +/** + * msgpack.decode_array(buf.rpos, buf:size()) -> arr_len, new_rpos + */ +static int +lua_decode_array(lua_State *L) +{ + const char *func_name = "msgpack.decode_array"; + const char *data; + ptrdiff_t size; + verify_decode_args(L, func_name, &data, &size); + + if (mp_typeof(*data) != MP_ARRAY) + return luaL_error(L, "%s: unexpected msgpack type", func_name); + + if (mp_check_array(data, data + size) > 0) + return luaL_error(L, "%s: unexpected end of buffer", func_name); + + uint32_t len = mp_decode_array(&data); + + lua_pushinteger(L, len); + *(const char **) luaL_pushcdata(L, CTID_CHAR_PTR) = data; + return 2; +} + +/** + * msgpack.decode_map(buf.rpos, buf:size()) -> map_len, new_rpos + */ +static int +lua_decode_map(lua_State *L) +{ + const char *func_name = "msgpack.decode_map"; + const char *data; + ptrdiff_t size; + verify_decode_args(L, func_name, &data, &size); + + if (mp_typeof(*data) != MP_MAP) + return luaL_error(L, "%s: unexpected msgpack type", func_name); + + if (mp_check_map(data, data + size) > 0) + return luaL_error(L, "%s: unexpected end of buffer", func_name); + + uint32_t len = mp_decode_map(&data); + + lua_pushinteger(L, len); + *(const char **) luaL_pushcdata(L, CTID_CHAR_PTR) = data; + return 2; +} + static int lua_msgpack_new(lua_State *L); @@ -430,6 +508,8 @@ static const luaL_Reg msgpacklib[] = { { "decode", lua_msgpack_decode }, { "decode_unchecked", lua_msgpack_decode_unchecked }, { "ibuf_decode", lua_ibuf_msgpack_decode }, + { "decode_array", lua_decode_array }, + { "decode_map", lua_decode_map }, { "new", lua_msgpack_new }, { NULL, NULL } }; diff --git a/test/app-tap/msgpack.test.lua b/test/app-tap/msgpack.test.lua index 0e1692ad9..ee215dfb1 100755 --- a/test/app-tap/msgpack.test.lua +++ b/test/app-tap/msgpack.test.lua @@ -49,9 +49,186 @@ local function test_misc(test, s) test:ok(not st and e:match("null"), "null ibuf") end +local function test_decode_array_map(test, s) + local ffi = require('ffi') + + local usage_err = 'Usage: msgpack%.decode_[^_(]+%(ptr, size%)' + local end_of_buffer_err = 'msgpack%.decode_[^_]+: unexpected end of buffer' + local non_positive_size_err = 'msgpack.decode_[^_]+: non%-positive size' + + local decode_cases = { + { + 'fixarray', + func = s.decode_array, + data = ffi.cast('char *', '\x94'), + size = 1, + exp_len = 4, + exp_rewind = 1, + }, + { + 'array 16', + func = s.decode_array, + data = ffi.cast('char *', '\xdc\x00\x04'), + size = 3, + exp_len = 4, + exp_rewind = 3, + }, + { + 'array 32', + func = s.decode_array, + data = ffi.cast('char *', '\xdd\x00\x00\x00\x04'), + size = 5, + exp_len = 4, + exp_rewind = 5, + }, + { + 'truncated array 16', + func = s.decode_array, + data = ffi.cast('char *', '\xdc\x00'), + size = 2, + exp_err = end_of_buffer_err, + }, + { + 'truncated array 32', + func = s.decode_array, + data = ffi.cast('char *', '\xdd\x00\x00\x00'), + size = 4, + exp_err = end_of_buffer_err, + }, + { + 'fixmap', + func = s.decode_map, + data = ffi.cast('char *', '\x84'), + size = 1, + exp_len = 4, + exp_rewind = 1, + }, + { + 'map 16', + func = s.decode_map, + data = ffi.cast('char *', '\xde\x00\x04'), + size = 3, + exp_len = 4, + exp_rewind = 3, + }, + { + 'array 32', + func = s.decode_map, + data = ffi.cast('char *', '\xdf\x00\x00\x00\x04'), + size = 5, + exp_len = 4, + exp_rewind = 5, + }, + { + 'truncated map 16', + func = s.decode_map, + data = ffi.cast('char *', '\xde\x00'), + size = 2, + exp_err = end_of_buffer_err, + }, + { + 'truncated map 32', + func = s.decode_map, + data = ffi.cast('char *', '\xdf\x00\x00\x00'), + size = 4, + exp_err = end_of_buffer_err, + }, + } + + local bad_api_cases = { + { + 'wrong msgpack type', + data = ffi.cast('char *', '\xc0'), + size = 1, + exp_err = 'msgpack.decode_[^_]+: unexpected msgpack type', + }, + { + 'zero size buffer', + data = ffi.cast('char *', ''), + size = 0, + exp_err = non_positive_size_err, + }, + { + 'negative size buffer', + data = ffi.cast('char *', ''), + size = -1, + exp_err = non_positive_size_err, + }, + { + 'size is nil', + data = ffi.cast('char *', ''), + size = nil, + exp_err = 'bad argument', + }, + { + 'no arguments', + args = {}, + exp_err = usage_err, + }, + { + 'one argument', + args = {ffi.cast('char *', '')}, + exp_err = usage_err, + }, + { + 'data is nil', + args = {nil, 1}, + exp_err = 'expected cdata as 1 argument', + }, + { + 'data is not cdata', + args = {1, 1}, + exp_err = 'expected cdata as 1 argument', + }, + { + 'data with wrong cdata type', + args = {box.tuple.new(), 1}, + exp_err = "msgpack.decode_[^_]+: 'char %*' expected", + }, + { + 'size has wrong type', + args = {ffi.cast('char *', ''), 'eee'}, + exp_err = 'bad argument', + }, + } + + test:plan(#decode_cases + 2 * #bad_api_cases) + + -- Decode cases. + for _, case in ipairs(decode_cases) do + if case.exp_err ~= nil then + local ok, err = pcall(case.func, case.data, case.size) + local description = ('bad; %s'):format(case[1]) + test:ok(ok == false and err:match(case.exp_err), description) + else + local len, new_buf = case.func(case.data, case.size) + local rewind = new_buf - case.data + local description = ('good; %s'):format(case[1]) + test:is_deeply({len, rewind}, {case.exp_len, case.exp_rewind}, + description) + end + end + + -- Bad api usage cases. + for _, func_name in ipairs({'decode_array', 'decode_map'}) do + for _, case in ipairs(bad_api_cases) do + local ok, err + if case.args ~= nil then + local args_len = table.maxn(case.args) + ok, err = pcall(s[func_name], unpack(case.args, 1, args_len)) + else + ok, err = pcall(s[func_name], case.data, case.size) + end + local description = ('%s bad api usage; %s'):format(func_name, + case[1]) + test:ok(ok == false and err:match(case.exp_err), description) + end + end +end + tap.test("msgpack", function(test) local serializer = require('msgpack') - test:plan(10) + test:plan(11) test:test("unsigned", common.test_unsigned, serializer) test:test("signed", common.test_signed, serializer) test:test("double", common.test_double, serializer) @@ -62,4 +239,5 @@ tap.test("msgpack", function(test) test:test("ucdata", common.test_ucdata, serializer) test:test("offsets", test_offsets, serializer) test:test("misc", test_misc, serializer) + test:test("decode_array_map", test_decode_array_map, serializer) end) -- 2.20.1