On Wed, Jan 09, 2019 at 11:20:13PM +0300, Alexander Turenko wrote:
Needed for #3276.
@TarantoolBot document
Title: net.box: helpers to decode msgpack headers
They allow to skip iproto packet and msgpack array headers and pass raw
msgpack data to some other function, say, merger.
Contracts:
```
net_box.check_iproto_data(buf.rpos, buf.wpos - buf.rpos)
-> new_rpos
-> nil, err_msg
msgpack.check_array(buf.rpos, buf.wpos - buf.rpos, [, arr_len])
-> new_rpos, arr_len
-> nil, err_msg
```
Below the example with msgpack.decode() as the function that need raw
msgpack data. It is just to illustrate the approach, there is no sense
to skip iproto/array headers manually in Lua and then decode the rest in
Lua. But it worth when the raw msgpack data is subject to process in a C
module.
```lua
local function single_select(space, ...)
return box.space[space]:select(...)
end
local function batch_select(spaces, ...)
local res = {}
for _, space in ipairs(spaces) do
table.insert(res, box.space[space]:select(...))
end
return res
end
_G.single_select = single_select
_G.batch_select = batch_select
local res
local buf = buffer.ibuf()
conn.space.s:select(nil, {buffer = buf})
-- check and skip iproto_data header
buf.rpos = assert(net_box.check_iproto_data(buf.rpos, buf.wpos - buf.rpos))
-- check that we really got data from :select() as result
res, buf.rpos = msgpack.decode(buf.rpos, buf.wpos - buf.rpos)
-- check that the buffer ends
assert(buf.rpos == buf.wpos)
buf:recycle()
conn:call('single_select', {'s'}, {buffer = buf})
-- check and skip the iproto_data header
buf.rpos = assert(net_box.check_iproto_data(buf.rpos, buf.wpos - buf.rpos))
-- check and skip the array around return values
buf.rpos = assert(msgpack.check_array(buf.rpos, buf.wpos - buf.rpos, 1))
-- check that we really got data from :select() as result
res, buf.rpos = msgpack.decode(buf.rpos, buf.wpos - buf.rpos)
-- check that the buffer ends
assert(buf.rpos == buf.wpos)
buf:recycle()
local spaces = {'s', 't'}
conn:call('batch_select', {spaces}, {buffer = buf})
-- check and skip the iproto_data header
buf.rpos = assert(net_box.check_iproto_data(buf.rpos, buf.wpos - buf.rpos))
-- check and skip the array around return values
buf.rpos = assert(msgpack.check_array(buf.rpos, buf.wpos - buf.rpos, 1))
-- check and skip the array header before the first select result
buf.rpos = assert(msgpack.check_array(buf.rpos, buf.wpos - buf.rpos, #spaces))
-- check that we really got data from s:select() as result
res, buf.rpos = msgpack.decode(buf.rpos, buf.wpos - buf.rpos)
-- t:select() data
res, buf.rpos = msgpack.decode(buf.rpos, buf.wpos - buf.rpos)
-- check that the buffer ends
assert(buf.rpos == buf.wpos)
```
---
src/box/lua/net_box.c | 49 +++++++++++
src/box/lua/net_box.lua | 1 +
src/lua/msgpack.c | 66 ++++++++++++++
test/app-tap/msgpack.test.lua | 157 +++++++++++++++++++++++++++++++++-
test/box/net.box.result | 58 +++++++++++++
test/box/net.box.test.lua | 26 ++++++
6 files changed, 356 insertions(+), 1 deletion(-)
diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c
index c7063d9c8..d71f33768 100644
--- a/src/box/lua/net_box.c
+++ b/src/box/lua/net_box.c
@@ -51,6 +51,9 @@
#define cfg luaL_msgpack_default
+static uint32_t CTID_CHAR_PTR;
+static uint32_t CTID_CONST_CHAR_PTR;
+
static inline size_t
netbox_prepare_request(lua_State *L, struct mpstream *stream, uint32_t
r_type)
{
@@ -745,9 +748,54 @@ netbox_decode_execute(struct lua_State *L)
return 2;
}
+/**
+ * net_box.check_iproto_data(buf.rpos, buf.wpos - buf.rpos)
+ * -> new_rpos
+ * -> nil, err_msg
+ */
+int
+netbox_check_iproto_data(struct lua_State *L)
+{
+ uint32_t ctypeid;
+ const char *data = *(const char **) luaL_checkcdata(L, 1, &ctypeid);
+ if (ctypeid != CTID_CHAR_PTR && ctypeid != CTID_CONST_CHAR_PTR)
+ return luaL_error(L,
+ "net_box.check_iproto_data: 'char *' or "
+ "'const char *' expected");
+
+ if (!lua_isnumber(L, 2))
+ return luaL_error(L, "net_box.check_iproto_data: number "
+ "expected as 2nd argument");
+ const char *end = data + lua_tointeger(L, 2);
+
+ int ok = data < end &&
+ mp_typeof(*data) == MP_MAP &&
+ mp_check_map(data, end) <= 0 &&
+ mp_decode_map(&data) == 1 &&
+ data < end &&
+ mp_typeof(*data) == MP_UINT &&
+ mp_check_uint(data, end) <= 0 &&
+ mp_decode_uint(&data) == IPROTO_DATA;
+
+ if (!ok) {
+ lua_pushnil(L);
+ lua_pushstring(L,
+ "net_box.check_iproto_data: wrong iproto data packet");
+ return 2;
+ }
+
+ *(const char **) luaL_pushcdata(L, ctypeid) = data;
+ return 1;
+}
+
int
luaopen_net_box(struct lua_State *L)
{
+ CTID_CHAR_PTR = luaL_ctypeid(L, "char *");
+ assert(CTID_CHAR_PTR != 0);
+ CTID_CONST_CHAR_PTR = luaL_ctypeid(L, "const char *");
+ assert(CTID_CONST_CHAR_PTR != 0);
+
static const luaL_Reg net_box_lib[] = {
{ "encode_ping", netbox_encode_ping },
{ "encode_call_16", netbox_encode_call_16 },
@@ -765,6 +813,7 @@ luaopen_net_box(struct lua_State *L)
{ "communicate", netbox_communicate },
{ "decode_select", netbox_decode_select },
{ "decode_execute", netbox_decode_execute },
+ { "check_iproto_data", netbox_check_iproto_data },
{ NULL, NULL}
};
/* luaL_register_module polutes _G */
diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua
index 2bf772aa8..0a38efa5a 100644
--- a/src/box/lua/net_box.lua
+++ b/src/box/lua/net_box.lua
@@ -1424,6 +1424,7 @@ local this_module = {
new = connect, -- Tarantool < 1.7.1 compatibility,
wrap = wrap,
establish_connection = establish_connection,
+ check_iproto_data = internal.check_iproto_data,
}
function this_module.timeout(timeout, ...)
diff --git a/src/lua/msgpack.c b/src/lua/msgpack.c
index b47006038..fca440660 100644
--- a/src/lua/msgpack.c
+++ b/src/lua/msgpack.c
@@ -51,6 +51,7 @@ luamp_error(void *error_ctx)
}
static uint32_t CTID_CHAR_PTR;
+static uint32_t CTID_CONST_CHAR_PTR;
static uint32_t CTID_STRUCT_IBUF;
struct luaL_serializer *luaL_msgpack_default = NULL;
@@ -418,6 +419,68 @@ lua_ibuf_msgpack_decode(lua_State *L)
return 2;
}
+/**
+ * msgpack.check_array(buf.rpos, buf.wpos - buf.rpos, [, arr_len])
+ * -> new_rpos, arr_len
+ * -> nil, err_msg
+ */
+static int
+lua_check_array(lua_State *L)
+{
+ uint32_t ctypeid;
+ const char *data = *(const char **) luaL_checkcdata(L, 1, &ctypeid);
+ if (ctypeid != CTID_CHAR_PTR && ctypeid != CTID_CONST_CHAR_PTR)
+ return luaL_error(L, "msgpack.check_array: 'char *' or "
+ "'const char *' expected");
+
+ if (!lua_isnumber(L, 2))
+ return luaL_error(L, "msgpack.check_array: number expected as "
+ "2nd argument");
+ const char *end = data + lua_tointeger(L, 2);
+
+ if (!lua_isnoneornil(L, 3) && !lua_isnumber(L, 3))
+ return luaL_error(L, "msgpack.check_array: number or nil "
+ "expected as 3rd argument");
+
+ static const char *end_of_buffer_msg = "msgpack.check_array: "
+ "unexpected end of buffer";
+
+ if (data >= end) {
+ lua_pushnil(L);
+ lua_pushstring(L, end_of_buffer_msg);
+ return 2;
+ }
+
+ if (mp_typeof(*data) != MP_ARRAY) {
+ lua_pushnil(L);
+ lua_pushstring(L, "msgpack.check_array: wrong array header");
+ return 2;
+ }
+
+ if (mp_check_array(data, end) > 0) {
+ lua_pushnil(L);
+ lua_pushstring(L, end_of_buffer_msg);
+ return 2;
+ }
+
+ uint32_t len = mp_decode_array(&data);
+
+ if (!lua_isnoneornil(L, 3)) {
+ uint32_t exp_len = (uint32_t) lua_tointeger(L, 3);
+ if (len != exp_len) {
+ lua_pushnil(L);
+ lua_pushfstring(L, "msgpack.check_array: expected "
+ "array of length %d, got length %d",
+ len, exp_len);
+ return 2;
+ }
+ }
+
+ *(const char **) luaL_pushcdata(L, ctypeid) = data;
+ lua_pushinteger(L, len);
+ return 2;
+}
+
static int
lua_msgpack_new(lua_State *L);
@@ -426,6 +489,7 @@ static const luaL_Reg msgpacklib[] = {
{ "decode", lua_msgpack_decode },
{ "decode_unchecked", lua_msgpack_decode_unchecked },
{ "ibuf_decode", lua_ibuf_msgpack_decode },
+ { "check_array", lua_check_array },
{ "new", lua_msgpack_new },
{ NULL, NULL }
};
@@ -447,6 +511,8 @@ luaopen_msgpack(lua_State *L)
assert(CTID_STRUCT_IBUF != 0);
CTID_CHAR_PTR = luaL_ctypeid(L, "char *");
assert(CTID_CHAR_PTR != 0);
+ CTID_CONST_CHAR_PTR = luaL_ctypeid(L, "const char *");
+ assert(CTID_CONST_CHAR_PTR != 0);
luaL_msgpack_default = luaL_newserializer(L, "msgpack", msgpacklib);
return 1;
}