[PATCH v2 5/6] net.box: add helpers to decode msgpack headers

Alexander Turenko alexander.turenko at tarantool.org
Tue Mar 5 15:00:11 MSK 2019


Fixed for the case when we're read into a non-empty buffer.

The patch is below.

WBR, Alexander Turenko.

diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua
index 08bfb3444..c6ed3e138 100644
--- a/src/box/lua/net_box.lua
+++ b/src/box/lua/net_box.lua
@@ -554,18 +554,18 @@ local function create_transport(host, port, user, password, callback,
         if buffer ~= nil then
             -- Copy xrow.body to user-provided buffer
             local body_len = body_end - body_rpos
-            local wpos = buffer:alloc(body_len)
-            ffi.copy(wpos, body_rpos, body_len)
-            body_len = tonumber(body_len)
             if request.skip_header then
                 -- Skip {[IPROTO_DATA_KEY] = ...} wrapper.
                 local map_len, key
-                map_len, buffer.rpos = decode_map(buffer.rpos, buffer:size())
+                map_len, body_rpos = decode_map(body_rpos, body_len)
                 assert(map_len == 1)
-                key, buffer.rpos = decode(buffer.rpos)
+                key, body_rpos = decode(body_rpos)
                 assert(key == IPROTO_DATA_KEY)
-                body_len = buffer:size()
+                body_len = body_end - body_rpos
             end
+            local wpos = buffer:alloc(body_len)
+            ffi.copy(wpos, body_rpos, body_len)
+            body_len = tonumber(body_len)
             if status == IPROTO_OK_KEY then
                 request.response = body_len
                 requests[id] = nil
diff --git a/test/box/net.box.result b/test/box/net.box.result
index 1cba78a5f..37f615323 100644
--- a/test/box/net.box.result
+++ b/test/box/net.box.result
@@ -1912,6 +1912,41 @@ result
 ---
 - []
 ...
+-- make several request into a buffer with skip_header, then read
+-- results
+c:call("echo", {1, 2, 3}, {buffer = ibuf, skip_header = true})
+---
+- 8
+...
+c:call("echo", {1, 2, 3}, {buffer = ibuf, skip_header = true})
+---
+- 8
+...
+c:call("echo", {1, 2, 3}, {buffer = ibuf, skip_header = true})
+---
+- 8
+...
+result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
+---
+...
+result
+---
+- [1, 2, 3]
+...
+result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
+---
+...
+result
+---
+- [1, 2, 3]
+...
+result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
+---
+...
+result
+---
+- [1, 2, 3]
+...
 -- unsupported methods
 c.space.test:get({1}, { buffer = ibuf})
 ---
diff --git a/test/box/net.box.test.lua b/test/box/net.box.test.lua
index 0fe948c29..9fda23088 100644
--- a/test/box/net.box.test.lua
+++ b/test/box/net.box.test.lua
@@ -737,6 +737,18 @@ c:eval("echo(...)", nil, {buffer = ibuf, skip_header = true})
 result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
 result
 
+-- make several request into a buffer with skip_header, then read
+-- results
+c:call("echo", {1, 2, 3}, {buffer = ibuf, skip_header = true})
+c:call("echo", {1, 2, 3}, {buffer = ibuf, skip_header = true})
+c:call("echo", {1, 2, 3}, {buffer = ibuf, skip_header = true})
+result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
+result
+result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
+result
+result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
+result
+
 -- unsupported methods
 c.space.test:get({1}, { buffer = ibuf})
 c.space.test.index.primary:min({}, { buffer = ibuf})

On Fri, Feb 01, 2019 at 06:11:41PM +0300, Alexander Turenko wrote:
> Splitted this patch to two ones:
> 
> * lua: add non-recursive msgpack decoding functions
> * net.box: add skip_header option to use with buffer
> 
> I attached them at end of the email.
> 
> WBR, Alexander Turenko.
> 
> On Thu, Jan 10, 2019 at 08:29:33PM +0300, Vladimir Davydov wrote:
> > 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
> > 
> > I'd prefer if this was done right in net.box.select or whatever function
> > writing the response to ibuf. Yes, this is going to break backward
> > compatibility, but IMO it's OK for 2.1 - I doubt anybody have used this
> > weird high perf API anyway.
> 
> 1. This will break tarantool/shard.
> 2. Hey, Guido thinks it is okay to break compatibility btw Python 2 and
>    Python 3 and it seems that Python 2 is in use ten years or like so.
> 
> I can do it under a separate option: skip_iproto_header or skip_header.
> It is not about a packet header, but part of body, however I have no
> better variants.
> 
> > > msgpack.check_array(buf.rpos, buf.wpos - buf.rpos, [, arr_len])
> > >     -> new_rpos, arr_len
> > >     -> nil, err_msg
> > 
> > This seems to be OK, although I'm not sure if we really need to check
> > the length in this function. Looks like we will definitely need it
> > because of net.box.call, which wraps function return value in an array.
> > Not sure about the name either, because it doesn't just checks the
> > msgpack - it decodes it, but can't come up with anything substantially
> > better. May be, msgpack.decode_array?
> 
> Re check length: the reason was to simplify user's code, but ok, it will
> not much more complex if we'll factor this check out. Like so (except
> from the merger's commit message):
> 
> ```
> conn:call('batch_select', <...>, {buffer = buf, skip_header = true})
> local len, _
> len, buf.rpos = msgpack.decode_array(buf.rpos, buf:size())
> assert(len == 1)
> _, buf.rpos = msgpack.decode_array(buf.rpos, buf:size())
> ```
> 
> Re name: now I understood: decode_unchecked() is like mp_decode(),
> decode() is like mp_check() + mp_decode(). So it worth to rename it to
> decode_array(). Done.
> 
> Also I changed order of return values to match msgpack.decode() (before
> it matches msgpack.ibuf_decode()).
> 
> > > ```
> > > 
> > > 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)
> > 
> > Instead of adding this function to net_box.c, I'd rather try to add
> > msgpack helpers for decoding a map, similar to msgpack.check_array added
> > by your patch, and use them in net_box.lua.
> 
> Done.
> 
> We discussed that we should add such helpers for all types like nil,
> bool, number, string, maybe bin. I think we can reuse recursive
> msgpack.decode() if we expect a scalar value.
> 
> > > +{
> > > +	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)
> > 
> > Hm, msgpack.decode doesn't care about CTID_CONST_CHAR_PTR. Why should we?
> 
> It looks natural to support a const pointer where we allow non-const
> one. But I don't have an example where we can obtain 'const char *'
> buffer with msgpack in Lua (w/o ffi.cast()). Msgpackffi returns 'const
> unsigned char *', but it is the bug and should be fixed in
> https://github.com/tarantool/tarantool/issues/3926
> 
> > > +		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");
> > 
> > Why not simply luaL_checkinteger?
> 
> We can separatelly check lua_gettop() and use luaL_checkinteger(). It
> looks shorter, now I see. Fixed.
> 
> > > +
> > > +	static const char *end_of_buffer_msg = "msgpack.check_array: "
> > > +		"unexpected end of buffer";
> > 
> > No point to make this variable static.
> 
> Ok. But now I removed it.
> 
> > > +
> > > +	if (data >= end) {
> > > +		lua_pushnil(L);
> > > +		lua_pushstring(L, end_of_buffer_msg);
> > 
> > msgpack.decode throws an error when it fails to decode msgpack data, so
> > I think this function should throw too.
> 
> Or Lua code style states we should report errors with `nil, err`. But
> this aspect is more about external modules as I see. It is quite unclear
> what is the best option for built-in modules.
> 
> If one likely want to handle an error in Lua the `nil, err` approach
> looks better. As far as I know at least some of our commercial projects
> primarily use this approach and have to wrap many functions with pcall.
> Don't sure how much the overhead is.
> 
> But anyway other msgpack functions just raise an error and it seems the
> new functions should have similar contract.
> 
> Changed.
> 
> > > +		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);
> > 
> > IMO it would be better if you set exp_len when you checked the arguments
> > (using luaL_checkinteger).
> 
> Expected length was removed from the function as you suggested.
> 
> > > +		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;
> > >  }
> 
> ----
> 
> commit 8c820dff279734d79e26591dcb771f7c6ab13639
> Author: Alexander Turenko <alexander.turenko at tarantool.org>
> Date:   Thu Jan 31 01:45:22 2019 +0300
> 
>     lua: add non-recursive msgpack decoding functions
>     
>     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.
> 
> diff --git a/src/lua/msgpack.c b/src/lua/msgpack.c
> index b47006038..92a9efd25 100644
> --- a/src/lua/msgpack.c
> +++ b/src/lua/msgpack.c
> @@ -418,6 +418,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);
>  
> @@ -426,6 +504,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)
> 
> ----
> 
> commit 3868d5c2551c893f16bd05c79d4d52a564c6a833
> Author: Alexander Turenko <alexander.turenko at tarantool.org>
> Date:   Thu Jan 31 01:59:18 2019 +0300
> 
>     net.box: add skip_header option to use with buffer
>     
>     Needed for #3276.
>     
>     @TarantoolBot document
>     Title: net.box: skip_header option
>     
>     This option instructs net.box to skip {[IPROTO_DATA_KEY] = ...} wrapper
>     from a buffer. This may be needed to pass this buffer to some C function
>     when it expects some specific msgpack input.
>     
>     See src/box/lua/net_box.lua for examples. Also consider merger's docbot
>     comment for more examples.
> 
> diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua
> index 2bf772aa8..53c93cafb 100644
> --- a/src/box/lua/net_box.lua
> +++ b/src/box/lua/net_box.lua
> @@ -15,6 +15,7 @@ local max           = math.max
>  local fiber_clock   = fiber.clock
>  local fiber_self    = fiber.self
>  local decode        = msgpack.decode_unchecked
> +local decode_map    = msgpack.decode_map
>  
>  local table_new           = require('table.new')
>  local check_iterator_type = box.internal.check_iterator_type
> @@ -483,8 +484,8 @@ local function create_transport(host, port, user, password, callback,
>      -- @retval nil, error Error occured.
>      -- @retval not nil Future object.
>      --
> -    local function perform_async_request(buffer, method, on_push, on_push_ctx,
> -                                         ...)
> +    local function perform_async_request(buffer, skip_header, method, on_push,
> +                                         on_push_ctx, ...)
>          if state ~= 'active' and state ~= 'fetch_schema' then
>              return nil, box.error.new({code = last_errno or E_NO_CONNECTION,
>                                         reason = last_error})
> @@ -497,12 +498,13 @@ local function create_transport(host, port, user, password, callback,
>          local id = next_request_id
>          method_encoder[method](send_buf, id, ...)
>          next_request_id = next_id(id)
> -        -- Request in most cases has maximum 8 members:
> -        -- method, buffer, id, cond, errno, response, on_push,
> -        -- on_push_ctx.
> -        local request = setmetatable(table_new(0, 8), request_mt)
> +        -- Request in most cases has maximum 9 members:
> +        -- method, buffer, skip_header, id, cond, errno, response,
> +        -- on_push, on_push_ctx.
> +        local request = setmetatable(table_new(0, 9), request_mt)
>          request.method = method
>          request.buffer = buffer
> +        request.skip_header = skip_header
>          request.id = id
>          request.cond = fiber.cond()
>          requests[id] = request
> @@ -516,10 +518,11 @@ local function create_transport(host, port, user, password, callback,
>      -- @retval nil, error Error occured.
>      -- @retval not nil Response object.
>      --
> -    local function perform_request(timeout, buffer, method, on_push,
> -                                   on_push_ctx, ...)
> +    local function perform_request(timeout, buffer, skip_header, method,
> +                                   on_push, on_push_ctx, ...)
>          local request, err =
> -            perform_async_request(buffer, method, on_push, on_push_ctx, ...)
> +            perform_async_request(buffer, skip_header, method, on_push,
> +                                  on_push_ctx, ...)
>          if not request then
>              return nil, err
>          end
> @@ -554,6 +557,15 @@ local function create_transport(host, port, user, password, callback,
>              local wpos = buffer:alloc(body_len)
>              ffi.copy(wpos, body_rpos, body_len)
>              body_len = tonumber(body_len)
> +            if request.skip_header then
> +                -- Skip {[IPROTO_DATA_KEY] = ...} wrapper.
> +                local map_len, key
> +                map_len, buffer.rpos = decode_map(buffer.rpos, buffer:size())
> +                assert(map_len == 1)
> +                key, buffer.rpos = decode(buffer.rpos)
> +                assert(key == IPROTO_DATA_KEY)
> +                body_len = buffer:size()
> +            end
>              if status == IPROTO_OK_KEY then
>                  request.response = body_len
>                  requests[id] = nil
> @@ -1047,17 +1059,18 @@ end
>  
>  function remote_methods:_request(method, opts, ...)
>      local transport = self._transport
> -    local on_push, on_push_ctx, buffer, deadline
> +    local on_push, on_push_ctx, buffer, skip_header, deadline
>      -- Extract options, set defaults, check if the request is
>      -- async.
>      if opts then
>          buffer = opts.buffer
> +        skip_header = opts.skip_header
>          if opts.is_async then
>              if opts.on_push or opts.on_push_ctx then
>                  error('To handle pushes in an async request use future:pairs()')
>              end
> -            return transport.perform_async_request(buffer, method, table.insert,
> -                                                   {}, ...)
> +            return transport.perform_async_request(buffer, skip_header, method,
> +                                                   table.insert, {}, ...)
>          end
>          if opts.timeout then
>              -- conn.space:request(, { timeout = timeout })
> @@ -1079,8 +1092,9 @@ function remote_methods:_request(method, opts, ...)
>          transport.wait_state('active', timeout)
>          timeout = deadline and max(0, deadline - fiber_clock())
>      end
> -    local res, err = transport.perform_request(timeout, buffer, method,
> -                                               on_push, on_push_ctx, ...)
> +    local res, err = transport.perform_request(timeout, buffer, skip_header,
> +                                               method, on_push, on_push_ctx,
> +                                               ...)
>      if err then
>          box.error(err)
>      end
> @@ -1283,10 +1297,10 @@ function console_methods:eval(line, timeout)
>      end
>      if self.protocol == 'Binary' then
>          local loader = 'return require("console").eval(...)'
> -        res, err = pr(timeout, nil, 'eval', nil, nil, loader, {line})
> +        res, err = pr(timeout, nil, false, 'eval', nil, nil, loader, {line})
>      else
>          assert(self.protocol == 'Lua console')
> -        res, err = pr(timeout, nil, 'inject', nil, nil, line..'$EOF$\n')
> +        res, err = pr(timeout, nil, false, 'inject', nil, nil, line..'$EOF$\n')
>      end
>      if err then
>          box.error(err)
> diff --git a/test/box/net.box.result b/test/box/net.box.result
> index 2b5a84646..71d0e0a50 100644
> --- a/test/box/net.box.result
> +++ b/test/box/net.box.result
> @@ -29,7 +29,7 @@ function x_select(cn, space_id, index_id, iterator, offset, limit, key, opts)
>                              offset, limit, key)
>      return ret
>  end
> -function x_fatal(cn) cn._transport.perform_request(nil, nil, 'inject', nil, nil, '\x80') end
> +function x_fatal(cn) cn._transport.perform_request(nil, nil, false, 'inject', nil, nil, '\x80') end
>  test_run:cmd("setopt delimiter ''");
>  ---
>  ...
> @@ -1573,6 +1573,18 @@ result
>  ---
>  - {48: [[2]]}
>  ...
> +-- replace + skip_header
> +c.space.test:replace({2}, {buffer = ibuf, skip_header = true})
> +---
> +- 7
> +...
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +---
> +...
> +result
> +---
> +- [[2]]
> +...
>  -- insert
>  c.space.test:insert({3}, {buffer = ibuf})
>  ---
> @@ -1585,6 +1597,21 @@ result
>  ---
>  - {48: [[3]]}
>  ...
> +-- insert + skip_header
> +_ = space:delete({3})
> +---
> +...
> +c.space.test:insert({3}, {buffer = ibuf, skip_header = true})
> +---
> +- 7
> +...
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +---
> +...
> +result
> +---
> +- [[3]]
> +...
>  -- update
>  c.space.test:update({3}, {}, {buffer = ibuf})
>  ---
> @@ -1608,6 +1635,29 @@ result
>  ---
>  - {48: [[3]]}
>  ...
> +-- update + skip_header
> +c.space.test:update({3}, {}, {buffer = ibuf, skip_header = true})
> +---
> +- 7
> +...
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +---
> +...
> +result
> +---
> +- [[3]]
> +...
> +c.space.test.index.primary:update({3}, {}, {buffer = ibuf, skip_header = true})
> +---
> +- 7
> +...
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +---
> +...
> +result
> +---
> +- [[3]]
> +...
>  -- upsert
>  c.space.test:upsert({4}, {}, {buffer = ibuf})
>  ---
> @@ -1620,6 +1670,18 @@ result
>  ---
>  - {48: []}
>  ...
> +-- upsert + skip_header
> +c.space.test:upsert({4}, {}, {buffer = ibuf, skip_header = true})
> +---
> +- 5
> +...
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +---
> +...
> +result
> +---
> +- []
> +...
>  -- delete
>  c.space.test:upsert({4}, {}, {buffer = ibuf})
>  ---
> @@ -1632,6 +1694,18 @@ result
>  ---
>  - {48: []}
>  ...
> +-- delete + skip_header
> +c.space.test:upsert({4}, {}, {buffer = ibuf, skip_header = true})
> +---
> +- 5
> +...
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +---
> +...
> +result
> +---
> +- []
> +...
>  -- select
>  c.space.test.index.primary:select({3}, {iterator = 'LE', buffer = ibuf})
>  ---
> @@ -1644,6 +1718,18 @@ result
>  ---
>  - {48: [[3], [2], [1, 'hello']]}
>  ...
> +-- select + skip_header
> +c.space.test.index.primary:select({3}, {iterator = 'LE', buffer = ibuf, skip_header = true})
> +---
> +- 17
> +...
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +---
> +...
> +result
> +---
> +- [[3], [2], [1, 'hello']]
> +...
>  -- select
>  len = c.space.test:select({}, {buffer = ibuf})
>  ---
> @@ -1667,6 +1753,29 @@ result
>  ---
>  - {48: [[1, 'hello'], [2], [3], [4]]}
>  ...
> +-- select + skip_header
> +len = c.space.test:select({}, {buffer = ibuf, skip_header = true})
> +---
> +...
> +ibuf.rpos + len == ibuf.wpos
> +---
> +- true
> +...
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +---
> +...
> +ibuf.rpos == ibuf.wpos
> +---
> +- true
> +...
> +len
> +---
> +- 19
> +...
> +result
> +---
> +- [[1, 'hello'], [2], [3], [4]]
> +...
>  -- call
>  c:call("echo", {1, 2, 3}, {buffer = ibuf})
>  ---
> @@ -1701,6 +1810,40 @@ result
>  ---
>  - {48: []}
>  ...
> +-- call + skip_header
> +c:call("echo", {1, 2, 3}, {buffer = ibuf, skip_header = true})
> +---
> +- 8
> +...
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +---
> +...
> +result
> +---
> +- [1, 2, 3]
> +...
> +c:call("echo", {}, {buffer = ibuf, skip_header = true})
> +---
> +- 5
> +...
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +---
> +...
> +result
> +---
> +- []
> +...
> +c:call("echo", nil, {buffer = ibuf, skip_header = true})
> +---
> +- 5
> +...
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +---
> +...
> +result
> +---
> +- []
> +...
>  -- eval
>  c:eval("echo(...)", {1, 2, 3}, {buffer = ibuf})
>  ---
> @@ -1735,6 +1878,40 @@ result
>  ---
>  - {48: []}
>  ...
> +-- eval + skip_header
> +c:eval("echo(...)", {1, 2, 3}, {buffer = ibuf, skip_header = true})
> +---
> +- 5
> +...
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +---
> +...
> +result
> +---
> +- []
> +...
> +c:eval("echo(...)", {}, {buffer = ibuf, skip_header = true})
> +---
> +- 5
> +...
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +---
> +...
> +result
> +---
> +- []
> +...
> +c:eval("echo(...)", nil, {buffer = ibuf, skip_header = true})
> +---
> +- 5
> +...
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +---
> +...
> +result
> +---
> +- []
> +...
>  -- unsupported methods
>  c.space.test:get({1}, { buffer = ibuf})
>  ---
> @@ -2571,7 +2748,7 @@ c.space.test:delete{1}
>  --
>  -- Break a connection to test reconnect_after.
>  --
> -_ = c._transport.perform_request(nil, nil, 'inject', nil, nil, '\x80')
> +_ = c._transport.perform_request(nil, nil, false, 'inject', nil, nil, '\x80')
>  ---
>  ...
>  c.state
> @@ -3205,7 +3382,7 @@ c = net:connect(box.cfg.listen, {reconnect_after = 0.01})
>  future = c:call('long_function', {1, 2, 3}, {is_async = true})
>  ---
>  ...
> -_ = c._transport.perform_request(nil, nil, 'inject', nil, nil, '\x80')
> +_ = c._transport.perform_request(nil, nil, false, 'inject', nil, nil, '\x80')
>  ---
>  ...
>  while not c:is_connected() do fiber.sleep(0.01) end
> @@ -3340,7 +3517,7 @@ c:ping()
>  -- new attempts to read any data - the connection is closed
>  -- already.
>  --
> -f = fiber.create(c._transport.perform_request, nil, nil, 'call_17', nil, nil, 'long', {}) c._transport.perform_request(nil, nil, 'inject', nil, nil, '\x80')
> +f = fiber.create(c._transport.perform_request, nil, nil, false, 'call_17', nil, nil, 'long', {}) c._transport.perform_request(nil, nil, false, 'inject', nil, nil, '\x80')
>  ---
>  ...
>  while f:status() ~= 'dead' do fiber.sleep(0.01) end
> @@ -3359,7 +3536,7 @@ c = net:connect(box.cfg.listen)
>  data = msgpack.encode(18400000000000000000)..'aaaaaaa'
>  ---
>  ...
> -c._transport.perform_request(nil, nil, 'inject', nil, nil, data)
> +c._transport.perform_request(nil, nil, false, 'inject', nil, nil, data)
>  ---
>  - null
>  - Peer closed
> diff --git a/test/box/net.box.test.lua b/test/box/net.box.test.lua
> index 96d822820..48cc7147d 100644
> --- a/test/box/net.box.test.lua
> +++ b/test/box/net.box.test.lua
> @@ -12,7 +12,7 @@ function x_select(cn, space_id, index_id, iterator, offset, limit, key, opts)
>                              offset, limit, key)
>      return ret
>  end
> -function x_fatal(cn) cn._transport.perform_request(nil, nil, 'inject', nil, nil, '\x80') end
> +function x_fatal(cn) cn._transport.perform_request(nil, nil, false, 'inject', nil, nil, '\x80') end
>  test_run:cmd("setopt delimiter ''");
>  
>  LISTEN = require('uri').parse(box.cfg.listen)
> @@ -615,11 +615,22 @@ c.space.test:replace({2}, {buffer = ibuf})
>  result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
>  result
>  
> +-- replace + skip_header
> +c.space.test:replace({2}, {buffer = ibuf, skip_header = true})
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +result
> +
>  -- insert
>  c.space.test:insert({3}, {buffer = ibuf})
>  result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
>  result
>  
> +-- insert + skip_header
> +_ = space:delete({3})
> +c.space.test:insert({3}, {buffer = ibuf, skip_header = true})
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +result
> +
>  -- update
>  c.space.test:update({3}, {}, {buffer = ibuf})
>  result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> @@ -628,21 +639,44 @@ c.space.test.index.primary:update({3}, {}, {buffer = ibuf})
>  result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
>  result
>  
> +-- update + skip_header
> +c.space.test:update({3}, {}, {buffer = ibuf, skip_header = true})
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +result
> +c.space.test.index.primary:update({3}, {}, {buffer = ibuf, skip_header = true})
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +result
> +
>  -- upsert
>  c.space.test:upsert({4}, {}, {buffer = ibuf})
>  result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
>  result
>  
> +-- upsert + skip_header
> +c.space.test:upsert({4}, {}, {buffer = ibuf, skip_header = true})
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +result
> +
>  -- delete
>  c.space.test:upsert({4}, {}, {buffer = ibuf})
>  result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
>  result
>  
> +-- delete + skip_header
> +c.space.test:upsert({4}, {}, {buffer = ibuf, skip_header = true})
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +result
> +
>  -- select
>  c.space.test.index.primary:select({3}, {iterator = 'LE', buffer = ibuf})
>  result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
>  result
>  
> +-- select + skip_header
> +c.space.test.index.primary:select({3}, {iterator = 'LE', buffer = ibuf, skip_header = true})
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +result
> +
>  -- select
>  len = c.space.test:select({}, {buffer = ibuf})
>  ibuf.rpos + len == ibuf.wpos
> @@ -651,6 +685,14 @@ ibuf.rpos == ibuf.wpos
>  len
>  result
>  
> +-- select + skip_header
> +len = c.space.test:select({}, {buffer = ibuf, skip_header = true})
> +ibuf.rpos + len == ibuf.wpos
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +ibuf.rpos == ibuf.wpos
> +len
> +result
> +
>  -- call
>  c:call("echo", {1, 2, 3}, {buffer = ibuf})
>  result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> @@ -662,6 +704,17 @@ c:call("echo", nil, {buffer = ibuf})
>  result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
>  result
>  
> +-- call + skip_header
> +c:call("echo", {1, 2, 3}, {buffer = ibuf, skip_header = true})
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +result
> +c:call("echo", {}, {buffer = ibuf, skip_header = true})
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +result
> +c:call("echo", nil, {buffer = ibuf, skip_header = true})
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +result
> +
>  -- eval
>  c:eval("echo(...)", {1, 2, 3}, {buffer = ibuf})
>  result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> @@ -673,6 +726,17 @@ c:eval("echo(...)", nil, {buffer = ibuf})
>  result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
>  result
>  
> +-- eval + skip_header
> +c:eval("echo(...)", {1, 2, 3}, {buffer = ibuf, skip_header = true})
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +result
> +c:eval("echo(...)", {}, {buffer = ibuf, skip_header = true})
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +result
> +c:eval("echo(...)", nil, {buffer = ibuf, skip_header = true})
> +result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
> +result
> +
>  -- unsupported methods
>  c.space.test:get({1}, { buffer = ibuf})
>  c.space.test.index.primary:min({}, { buffer = ibuf})
> @@ -1063,7 +1127,7 @@ c.space.test:delete{1}
>  --
>  -- Break a connection to test reconnect_after.
>  --
> -_ = c._transport.perform_request(nil, nil, 'inject', nil, nil, '\x80')
> +_ = c._transport.perform_request(nil, nil, false, 'inject', nil, nil, '\x80')
>  c.state
>  while not c:is_connected() do fiber.sleep(0.01) end
>  c:ping()
> @@ -1291,7 +1355,7 @@ finalize_long()
>  --
>  c = net:connect(box.cfg.listen, {reconnect_after = 0.01})
>  future = c:call('long_function', {1, 2, 3}, {is_async = true})
> -_ = c._transport.perform_request(nil, nil, 'inject', nil, nil, '\x80')
> +_ = c._transport.perform_request(nil, nil, false, 'inject', nil, nil, '\x80')
>  while not c:is_connected() do fiber.sleep(0.01) end
>  finalize_long()
>  future:wait_result(100)
> @@ -1348,7 +1412,7 @@ c:ping()
>  -- new attempts to read any data - the connection is closed
>  -- already.
>  --
> -f = fiber.create(c._transport.perform_request, nil, nil, 'call_17', nil, nil, 'long', {}) c._transport.perform_request(nil, nil, 'inject', nil, nil, '\x80')
> +f = fiber.create(c._transport.perform_request, nil, nil, false, 'call_17', nil, nil, 'long', {}) c._transport.perform_request(nil, nil, false, 'inject', nil, nil, '\x80')
>  while f:status() ~= 'dead' do fiber.sleep(0.01) end
>  c:close()
>  
> @@ -1358,7 +1422,7 @@ c:close()
>  --
>  c = net:connect(box.cfg.listen)
>  data = msgpack.encode(18400000000000000000)..'aaaaaaa'
> -c._transport.perform_request(nil, nil, 'inject', nil, nil, data)
> +c._transport.perform_request(nil, nil, false, 'inject', nil, nil, data)
>  c:close()
>  test_run:grep_log('default', 'too big packet size in the header') ~= nil



More information about the Tarantool-patches mailing list