[PATCH v3 4/7] lua: add non-recursive msgpack decoding functions

Alexander Turenko alexander.turenko at tarantool.org
Wed Apr 10 18:21:22 MSK 2019


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




More information about the Tarantool-patches mailing list