From 22580990e7c029d255fd3705cdc86b81ce97dd14 Mon Sep 17 00:00:00 2001 From: Vladimir Davydov <vdavydov@tarantool.org> Date: Fri, 27 Oct 2023 17:59:56 +0300 Subject: [PATCH] lua: add helpers for encoding/decoding IPROTO packets Closes #8054 @TarantoolBot document Title: Document Lua helpers for encoding/decoding IPROTO packets The following new constants and functions were added to the `box.iproto` namespace: - `GREETING_SIZE`: Size of a Tarantool greeting message - `GREETING_PROTOCOL_LEN_MAX`: Max length of a protocol string that can be encoded in a Tarantool greeting message. - `GREETING_SALT_LEN_MAX`: Max length of a salt string that can be encoded in a Tarantool greeting message. - `box.iproto.encode_greeting({version = x, uuid = x, salt = x})`: Encodes a Tarantool greeting message. Takes a table. Returns a string. Raises on error. The protocol is set to "Binary" (IPROTO). - `box.iproto.decode_greeting(string)`: Decodes a Tarantool greeting message. Takes a string. Returns a table with the following fields: `protocol`, `version`, `uuid`, `salt`. Raises on error. The input string must be exactly `GREETING_SIZE` bytes long. - `box.iproto.encode_packet(header[, body])`: Encodes an IPROTO packet. Takes a packet header and optionally a body given as a table or a string. A table argument will be encoded in MsgPack. A string argument will be copied as is (it's supposed to contain valid MsgPack but it isn't enforced). Returns a string. Raises on error. - `box.iproto.decode_packet(string[, pos])`: Decodes an IPROTO packet. Takes a string containing one or more encoded IPROTO packets and optionally a position in the string to start decoding from. If the position is omitted, the function will start decoding from the beginning of the string. On success returns the decoded packet header, body, and the position in the string where decoding stopped. Both header and body are returned as `msgpack.object`. The body may be absent (set to nil). If the input is truncated, returns nil and the min number of bytes required to decode the packet. On failure, raises an error. For examples, see [`test/app-luatest/iproto_encoder_test.lua`][1]. [1]: https://github.com/tarantool/tarantool/blob/master/test/app-luatest/iproto_encoder_test.lua --- .../unreleased/gh-8054-lua-iproto-encoder.md | 3 + src/box/iproto.cc | 1 - src/box/iproto_constants.h | 4 +- src/box/lua/iproto.c | 293 +++++++++++++++++ test/app-luatest/iproto_encoder_test.lua | 295 ++++++++++++++++++ .../ghs_16_user_enumeration_test.lua | 53 ++-- 6 files changed, 619 insertions(+), 30 deletions(-) create mode 100644 changelogs/unreleased/gh-8054-lua-iproto-encoder.md create mode 100644 test/app-luatest/iproto_encoder_test.lua diff --git a/changelogs/unreleased/gh-8054-lua-iproto-encoder.md b/changelogs/unreleased/gh-8054-lua-iproto-encoder.md new file mode 100644 index 0000000000..dba4900d88 --- /dev/null +++ b/changelogs/unreleased/gh-8054-lua-iproto-encoder.md @@ -0,0 +1,3 @@ +## feature/lua + +* Introduced helpers for encoding and decoding IPROTO packets in Lua (gh-8054). diff --git a/src/box/iproto.cc b/src/box/iproto.cc index b5dc2ffbe2..d1a8944e1c 100644 --- a/src/box/iproto.cc +++ b/src/box/iproto.cc @@ -79,7 +79,6 @@ #include "mpstream/mpstream.h" enum { - IPROTO_SALT_SIZE = 32, IPROTO_PACKET_SIZE_MAX = 2UL * 1024 * 1024 * 1024, }; diff --git a/src/box/iproto_constants.h b/src/box/iproto_constants.h index af7f432c77..cf9792a483 100644 --- a/src/box/iproto_constants.h +++ b/src/box/iproto_constants.h @@ -43,8 +43,10 @@ extern "C" { enum { /** Maximal iproto package body length (2GiB) */ IPROTO_BODY_LEN_MAX = 2147483648UL, - /* Maximal length of text handshake (greeting) */ + /** Size of iproto greeting message. */ IPROTO_GREETING_SIZE = 128, + /** Size of salt sent in iproto greeting message. */ + IPROTO_SALT_SIZE = 32, /** marker + len + prev crc32 + cur crc32 + (padding) */ XLOG_FIXHEADER_SIZE = 19 }; diff --git a/src/box/lua/iproto.c b/src/box/lua/iproto.c index b7ad78252b..c6fd800790 100644 --- a/src/box/lua/iproto.c +++ b/src/box/lua/iproto.c @@ -12,12 +12,18 @@ #include "box/iproto_constants.h" #include "box/iproto_features.h" #include "box/user.h" +#include "box/xrow.h" + +#include "version.h" #include "core/assoc.h" #include "core/iostream.h" #include "core/fiber.h" #include "core/mp_ctx.h" +#include "core/random.h" + #include "core/tt_static.h" +#include "core/tt_uuid.h" #include "lua/msgpack.h" #include "lua/utils.h" @@ -150,6 +156,12 @@ push_iproto_raft_keys_enum(struct lua_State *L) static void push_iproto_constants(struct lua_State *L) { + lua_pushinteger(L, IPROTO_GREETING_SIZE); + lua_setfield(L, -2, "GREETING_SIZE"); + lua_pushinteger(L, GREETING_PROTOCOL_LEN_MAX); + lua_setfield(L, -2, "GREETING_PROTOCOL_LEN_MAX"); + lua_pushinteger(L, GREETING_SALT_LEN_MAX); + lua_setfield(L, -2, "GREETING_SALT_LEN_MAX"); push_iproto_flag_enum(L); push_iproto_key_enum(L); push_iproto_metadata_key_enum(L); @@ -380,6 +392,283 @@ lbox_iproto_override(struct lua_State *L) return 0; } +/** + * Encodes Tarantool greeting message. + * + * Takes a table with the following fields that will be used in + * the greeting (all fields are optional): + * - version: Tarantool version string in the form 'X.Y.Z'. + * Default: current Tarantool version. + * - uuid: Instance UUID string. Default: Some random UUID. + * (We don't use INSTANCE_UUID because it may be uninitialized.) + * - salt: Salt string (used for authentication). + * Default: Some random salt string. + * + * Returns the encoded greeting message string on success. + * Raises an error on invalid arguments. + */ +static int +lbox_iproto_encode_greeting(struct lua_State *L) +{ + int n_args = lua_gettop(L); + if (n_args == 0) { + lua_newtable(L); + } else if (n_args != 1 || lua_type(L, 1) != LUA_TTABLE) { + return luaL_error(L, "Usage: box.iproto.encode_greeting({" + "version = x, uuid = x, salt = x})"); + } + + uint32_t version; + lua_getfield(L, 1, "version"); + if (lua_isnil(L, -1)) { + version = tarantool_version_id(); + } else if (lua_type(L, -1) == LUA_TSTRING) { + const char *str = lua_tostring(L, -1); + unsigned major, minor, patch; + if (sscanf(str, "%u.%u.%u", &major, &minor, &patch) != 3) + return luaL_error(L, "cannot parse version string"); + version = version_id(major, minor, patch); + } else { + return luaL_error(L, "version must be a string"); + } + lua_pop(L, 1); /* version */ + + struct tt_uuid uuid; + lua_getfield(L, 1, "uuid"); + if (lua_isnil(L, -1)) { + tt_uuid_create(&uuid); + } else if (lua_type(L, -1) == LUA_TSTRING) { + const char *uuid_str = lua_tostring(L, -1); + if (tt_uuid_from_string(uuid_str, &uuid) != 0) + return luaL_error(L, "cannot parse uuid string"); + } else { + return luaL_error(L, "uuid must be a string"); + } + lua_pop(L, 1); /* uuid */ + + uint32_t salt_len; + char salt[GREETING_SALT_LEN_MAX]; + lua_getfield(L, 1, "salt"); + if (lua_isnil(L, -1)) { + salt_len = IPROTO_SALT_SIZE; + random_bytes(salt, IPROTO_SALT_SIZE); + } else if (lua_type(L, -1) == LUA_TSTRING) { + size_t len; + const char *str = lua_tolstring(L, -1, &len); + if (len > GREETING_SALT_LEN_MAX) + return luaL_error(L, "salt string length " + "cannot be greater than %d", + GREETING_SALT_LEN_MAX); + salt_len = len; + memcpy(salt, str, len); + } else { + return luaL_error(L, "salt must be a string"); + } + lua_pop(L, 1); /* salt */ + + char greeting_str[IPROTO_GREETING_SIZE]; + greeting_encode(greeting_str, version, &uuid, salt, salt_len); + + lua_pushlstring(L, greeting_str, sizeof(greeting_str)); + return 1; +} + +/** + * Decodes Tarantool greeting message. + * + * Takes a greeting message string and returns a table with the following + * fields on success: + * - version: Tarantool version string in the form 'X.Y.Z'. + * - protocol: Tarantool protocol string ("Binary" for IPROTO). + * - uuid: Instance UUID string. + * - salt: Salt string (used for authentication). + * + * Raises an error on invalid input. + */ +static int +lbox_iproto_decode_greeting(struct lua_State *L) +{ + int n_args = lua_gettop(L); + if (n_args != 1 || lua_type(L, 1) != LUA_TSTRING) { + return luaL_error( + L, "Usage: box.iproto.decode_greeting(string)"); + } + + size_t len; + const char *greeting_str = lua_tolstring(L, 1, &len); + if (len != IPROTO_GREETING_SIZE) { + return luaL_error(L, "greeting length must equal %d", + IPROTO_GREETING_SIZE); + } + struct greeting greeting; + if (greeting_decode(greeting_str, &greeting) != 0) + return luaL_error(L, "cannot parse greeting string"); + + lua_newtable(L); + lua_pushfstring(L, "%u.%u.%u", + version_id_major(greeting.version_id), + version_id_minor(greeting.version_id), + version_id_patch(greeting.version_id)); + lua_setfield(L, -2, "version"); + lua_pushstring(L, greeting.protocol); + lua_setfield(L, -2, "protocol"); + luaT_pushuuidstr(L, &greeting.uuid); + lua_setfield(L, -2, "uuid"); + lua_pushlstring(L, greeting.salt, greeting.salt_len); + lua_setfield(L, -2, "salt"); + return 1; +} + +/** + * Encodes IPROTO packet. + * + * Takes a packet header and optionally a body given as a string or a table. + * If an argument is a table, it will be encoded in MsgPack using the IPROTO + * key translation table. If an argument is a string, it's supposed to store + * valid MsgPack data and will be copied as is. + * + * On success, returns a string storing the encoded IPROTO packet. + * On failure, raises a Lua error. + */ +static int +lbox_iproto_encode_packet(struct lua_State *L) +{ + int n_args = lua_gettop(L); + if (n_args != 1 && n_args != 2) + return luaL_error( + L, "Usage: box.iproto.encode_packet(header[, body])"); + int header_type = lua_type(L, 1); + if (header_type != LUA_TSTRING && header_type != LUA_TTABLE) + return luaL_error(L, "header must be a string or a table"); + int body_type = lua_type(L, 2); + if (body_type != LUA_TSTRING && body_type != LUA_TTABLE && + body_type != LUA_TNONE && body_type != LUA_TNIL) + return luaL_error(L, "body must be a string or a table"); + struct region *region = &fiber()->gc; + size_t region_svp = region_used(region); + struct mpstream stream; + mpstream_init(&stream, region, region_reserve_cb, region_alloc_cb, + mpstream_panic_cb, NULL); + size_t fixheader_size = mp_sizeof_uint(UINT32_MAX); + char *fixheader = mpstream_reserve(&stream, fixheader_size); + mpstream_advance(&stream, fixheader_size); + struct mp_ctx ctx; + mp_ctx_create_default(&ctx, iproto_key_translation); + if (header_type == LUA_TTABLE) { + int rc = luamp_encode_with_ctx(L, luaL_msgpack_default, + &stream, 1, &ctx, NULL); + if (rc != 0) + goto error; + } else if (header_type == LUA_TSTRING) { + size_t size; + const char *data = lua_tolstring(L, 1, &size); + mpstream_memcpy(&stream, data, size); + } + if (body_type == LUA_TTABLE) { + int rc = luamp_encode_with_ctx(L, luaL_msgpack_default, + &stream, 2, &ctx, NULL); + if (rc != 0) + goto error; + } else if (body_type == LUA_TSTRING) { + size_t size; + const char *data = lua_tolstring(L, 2, &size); + mpstream_memcpy(&stream, data, size); + } + mpstream_flush(&stream); + size_t data_size = region_used(region) - region_svp; + *fixheader = 0xce; + mp_store_u32(fixheader + 1, data_size - fixheader_size); + char *data = xregion_join(region, data_size); + lua_pushlstring(L, data, data_size); + region_truncate(region, region_svp); + return 1; +error: + region_truncate(region, region_svp); + return luaT_error(L); +} + +/** + * Decodes IPROTO packet. + * + * Takes a string that contains an encoded IPROTO packet and optionally + * the position in the string to start decoding from (if the position is + * omitted, the function will start decoding from the beginning of the + * input string, i.e. assume that the position equals 1). + * + * On success returns three values: the decoded packet header (never nil), + * the decoded packet body (may be nil), and the position of the following + * packet in the string. The header and body are returned as MsgPack objects. + * + * If the packet is truncated, returns nil and the minimal number of bytes + * necessary to decode the packet. + * + * On failure, raises a Lua error. + */ +static int +lbox_iproto_decode_packet(struct lua_State *L) +{ + int n_args = lua_gettop(L); + if (n_args == 0 || n_args > 2 || + lua_type(L, 1) != LUA_TSTRING || + (n_args == 2 && lua_type(L, 2) != LUA_TNUMBER)) + return luaL_error( + L, "Usage: box.iproto.decode_packet(string[, pos])"); + + size_t data_size; + const char *data = lua_tolstring(L, 1, &data_size); + const char *data_end = data + data_size; + const char *p = data; + if (n_args == 2) { + int pos = lua_tointeger(L, 2); + if (pos <= 0) + return luaL_error(L, "position must be greater than 0"); + p += pos - 1; + } + ptrdiff_t n = p - data_end + 1; + if (n > 0) + goto truncated_input; + if (mp_typeof(*p) != MP_UINT) { + diag_set(ClientError, ER_PROTOCOL, "invalid fixheader"); + return luaT_error(L); + } + n = mp_check_uint(p, data_end); + if (n > 0) + goto truncated_input; + size_t packet_size = mp_decode_uint(&p); + if (packet_size == 0) { + diag_set(ClientError, ER_PROTOCOL, "invalid fixheader"); + return luaT_error(L); + } + const char *packet_end = p + packet_size; + n = packet_end - data_end; + if (n > 0) + goto truncated_input; + const char *header = p; + if (mp_check(&p, packet_end) != 0) + return luaT_error(L); + const char *header_end = p; + const char *body = p; + if (p != packet_end && mp_check_exact(&p, packet_end) != 0) + return luaT_error(L); + const char *body_end = p; + struct mp_ctx ctx; + mp_ctx_create_default(&ctx, iproto_key_translation); + luamp_push_with_ctx(L, header, header_end, &ctx); + if (body != body_end) { + mp_ctx_create_default(&ctx, iproto_key_translation); + luamp_push_with_ctx(L, body, body_end, &ctx); + } else { + lua_pushnil(L); + } + lua_pushnumber(L, packet_end - data + 1); + return 3; +truncated_input: + assert(n > 0); + lua_pushnil(L); + lua_pushnumber(L, n); + return 2; +} + /** * Initializes module for working with Tarantool's network subsystem. */ @@ -393,6 +682,10 @@ box_lua_iproto_init(struct lua_State *L) static const struct luaL_Reg funcs[] = { {"send", lbox_iproto_send}, {"override", lbox_iproto_override}, + {"encode_greeting", lbox_iproto_encode_greeting}, + {"decode_greeting", lbox_iproto_decode_greeting}, + {"encode_packet", lbox_iproto_encode_packet}, + {"decode_packet", lbox_iproto_decode_packet}, {NULL, NULL} }; luaL_setfuncs(L, funcs, 0); diff --git a/test/app-luatest/iproto_encoder_test.lua b/test/app-luatest/iproto_encoder_test.lua new file mode 100644 index 0000000000..0bd93331de --- /dev/null +++ b/test/app-luatest/iproto_encoder_test.lua @@ -0,0 +1,295 @@ +local msgpack = require('msgpack') +local t = require('luatest') +local tarantool = require('tarantool') +local uuid = require('uuid') + +local g = t.group() + +-- +-- Checks exported constant values. +-- +g.test_constants = function() + t.assert_equals(box.iproto.GREETING_SIZE, 128) + t.assert_equals(box.iproto.GREETING_PROTOCOL_LEN_MAX, 32) + t.assert_equals(box.iproto.GREETING_SALT_LEN_MAX, 44) +end + +-- +-- Checks errors raised on invalid arguments passed to +-- box.iproto.encode_greeting() and box.iproto.decode_greeting(). +-- +g.test_encode_decode_greeting_invalid_args = function() + local encode = box.iproto.encode_greeting + local decode = box.iproto.decode_greeting + + local errmsg = 'Usage: box.iproto.encode_greeting({' .. + 'version = x, uuid = x, salt = x})' + t.assert_error_msg_equals(errmsg, encode, 123) + t.assert_error_msg_equals(errmsg, encode, 'foo') + t.assert_error_msg_equals(errmsg, encode, {}, 123) + + t.assert_error_msg_equals('version must be a string', + encode, {version = 123}) + t.assert_error_msg_equals('cannot parse version string', + encode, {version = 'foo'}) + t.assert_error_msg_equals('uuid must be a string', + encode, {uuid = 123}) + t.assert_error_msg_equals('cannot parse uuid string', + encode, {uuid = 'foo'}) + t.assert_error_msg_equals('salt must be a string', + encode, {salt = 123}) + t.assert_error_msg_equals('salt string length cannot be greater than 44', + encode, {salt = string.rep('x', 45)}) + + errmsg = 'Usage: box.iproto.decode_greeting(string)' + t.assert_error_msg_equals(errmsg, decode, 123) + t.assert_error_msg_equals(errmsg, decode, {}) + t.assert_error_msg_equals(errmsg, decode, 'foo', 123) + + t.assert_error_msg_equals('greeting length must equal 128', decode, 'foo') +end + +-- +-- Checks box.iproto.encode_greeting() and box.iproto.decode_greeting() output. +-- +g.test_encode_decode_greeting = function() + local encode = box.iproto.encode_greeting + local decode = box.iproto.decode_greeting + + local pattern = + 'Tarantool%s+%d+%.%d+%.%d+%s+%(Binary%)%s+' .. + string.rep('%x', 8) .. '%-' .. string.rep('%x', 4) .. '%-' .. + string.rep('%x', 4) .. '%-' .. string.rep('%x', 4) .. '%-' .. + string.rep('%x', 12) .. '%s*\n[%w%p]+%s*$' + + local str = encode() + t.assert_equals(#str, box.iproto.GREETING_SIZE) + t.assert_str_matches(str, pattern) + + str = encode({}) + t.assert_equals(#str, box.iproto.GREETING_SIZE) + t.assert_str_matches(str, pattern) + + local greeting = decode(str) + t.assert_type(greeting, 'table') + t.assert_equals(greeting.version, tarantool.version:match('%d%.%d%.%d')) + t.assert_equals(greeting.protocol, 'Binary') + t.assert(uuid.fromstr(greeting.uuid)) + t.assert_not_equals(uuid.fromstr(greeting.uuid), uuid.NULL) + t.assert_type(greeting.salt, 'string') + t.assert_equals(#greeting.salt, 32) + t.assert_equals(encode(greeting), str) + + greeting = { + version = '2.3.4', + protocol = 'Binary', + uuid = uuid.str(), + salt = string.rep('x', 40), + } + str = encode(greeting) + t.assert_equals(#str, box.iproto.GREETING_SIZE) + t.assert_str_matches(str, pattern) + t.assert_equals(decode(str), greeting) +end + +-- +-- Checks errors raised on invalid arguments passed to +-- box.iproto.encode_packet() and box.iproto.decode_packet(). +-- +g.test_encode_decode_packet_invalid_args = function() + local encode = box.iproto.encode_packet + local decode = box.iproto.decode_packet + + local errmsg = 'Usage: box.iproto.encode_packet(header[, body])' + t.assert_error_msg_equals(errmsg, encode) + t.assert_error_msg_equals(errmsg, encode, {}, {}, {}) + + t.assert_error_msg_equals('header must be a string or a table', + encode, 123) + t.assert_error_msg_equals('body must be a string or a table', + encode, {}, 123) + t.assert_error_msg_equals("unsupported Lua type 'function'", + encode, {function() end}) + t.assert_error_msg_equals("unsupported Lua type 'function'", + encode, {}, {function() end}) + + errmsg = 'Usage: box.iproto.decode_packet(string[, pos])' + t.assert_error_msg_equals(errmsg, decode) + t.assert_error_msg_equals(errmsg, decode, {}) + t.assert_error_msg_equals(errmsg, decode, 123) + t.assert_error_msg_equals(errmsg, decode, '', '1') + t.assert_error_msg_equals(errmsg, decode, '', 1, '') + + errmsg = 'position must be greater than 0' + t.assert_error_msg_equals(errmsg, decode, '', 0) + t.assert_error_msg_equals(errmsg, decode, '', -1) +end + +-- +-- Checks errors raised by box.iproto.decode_packet() on bad input. +-- +g.test_decode_packet_bad_input = function() + local decode = box.iproto.decode_packet + + local errmsg = 'invalid fixheader' + t.assert_error_msg_equals(errmsg, decode, string.fromhex('00')) + t.assert_error_msg_equals(errmsg, decode, string.fromhex('ff')) + t.assert_error_msg_equals(errmsg, decode, string.fromhex('80')) + + t.assert_error_msg_equals('Invalid MsgPack - illegal code', + decode, string.fromhex('01c1')) + t.assert_error_msg_equals('Invalid MsgPack - truncated input', + decode, string.fromhex('0281c0')) + t.assert_error_msg_equals('Invalid MsgPack - truncated input', + decode, string.fromhex('0281c0c0')) + t.assert_error_msg_equals('Invalid MsgPack - truncated input', + decode, string.fromhex('0581c0c081c0')) + t.assert_error_msg_equals('Invalid MsgPack - truncated input', + decode, string.fromhex('0581c0c081c0c0')) + t.assert_error_msg_equals('Invalid MsgPack - junk after input', + decode, string.fromhex('0781c0c081c0c0c0')) +end + +-- +-- Checks output of box.iproto.decode_packet() on truncated input. +-- +g.test_decode_packet_truncated_input = function() + local decode = box.iproto.decode_packet + + t.assert_equals({decode('')}, {nil, 1}) + t.assert_equals({decode(string.fromhex('ce'))}, {nil, 4}) + t.assert_equals({decode(string.fromhex('ce0000'))}, {nil, 2}) + t.assert_equals({decode(string.fromhex('05'))}, {nil, 5}) + t.assert_equals({decode(string.fromhex('ce00000005'))}, {nil, 5}) + t.assert_equals({decode(string.fromhex('ce0000000581'))}, {nil, 4}) +end + +-- +-- Checks box.iproto.encode_packet() and box.iproto.decode_packet() output +-- on input containing a single packet. +-- +g.test_encode_decode_packet_one = function() + local encode = box.iproto.encode_packet + local decode = box.iproto.decode_packet + + local data = encode({ + sync = 123, + request_type = box.iproto.type.INSERT, + }, { + space_id = 512, + tuple = {1, 2, 3}, + }) + t.assert_equals(string.hex(data), + 'ce0000000f820002017b8210cd02002193010203') + local header, body, pos = decode(data) + t.assert(msgpack.is_object(header)) + t.assert_equals(header:decode(), { + [box.iproto.key.SYNC] = 123, + [box.iproto.key.REQUEST_TYPE] = box.iproto.type.INSERT, + }) + t.assert_equals(header.sync, 123) + t.assert_equals(header.request_type, box.iproto.type.INSERT) + t.assert(msgpack.is_object(body)) + t.assert_equals(body:decode(), { + [box.iproto.key.SPACE_ID] = 512, + [box.iproto.key.TUPLE] = {1, 2, 3}, + }) + t.assert_equals(body.space_id, 512) + t.assert_equals(body.tuple, {1, 2, 3}) + t.assert_equals(pos, #data + 1) + + data = encode({ + sync = 123, + request_type = box.iproto.type.NOP, + }) + t.assert_equals(string.hex(data), 'ce0000000582000c017b') + header, body, pos = decode(data) + t.assert(msgpack.is_object(header)) + t.assert_equals(header:decode(), { + [box.iproto.key.SYNC] = 123, + [box.iproto.key.REQUEST_TYPE] = box.iproto.type.NOP, + }) + t.assert_equals(header.sync, 123) + t.assert_equals(header.request_type, box.iproto.type.NOP) + t.assert_is(body, nil) + t.assert_equals(pos, #data + 1) +end + +-- +-- Checks box.iproto.encode_packet() and box.iproto.decode_packet() output +-- on input containing multiple packets. +-- +g.test_encode_decode_packet_many = function() + local encode = box.iproto.encode_packet + local decode = box.iproto.decode_packet + + local data = encode({ + sync = 1, + request_type = box.iproto.type.INSERT, + }, { + space_id = 512, + tuple = {'a', 'b', 'c'}, + }) .. encode({ + sync = 2, + request_type = box.iproto.type.NOP, + }) .. encode({ + sync = 3, + request_type = box.iproto.type.REPLACE, + }, { + space_name = 'test', + tuple = {1, 2, 3}, + }) + + local header, body, pos = decode(data) + t.assert(msgpack.is_object(header)) + t.assert_equals(header:decode(), { + [box.iproto.key.SYNC] = 1, + [box.iproto.key.REQUEST_TYPE] = box.iproto.type.INSERT, + }) + t.assert(msgpack.is_object(body)) + t.assert_equals(body:decode(), { + [box.iproto.key.SPACE_ID] = 512, + [box.iproto.key.TUPLE] = {'a', 'b', 'c'}, + }) + + header, body, pos = decode(data, pos) + t.assert(msgpack.is_object(header)) + t.assert_equals(header:decode(), { + [box.iproto.key.SYNC] = 2, + [box.iproto.key.REQUEST_TYPE] = box.iproto.type.NOP, + }) + t.assert_is(body, nil) + + header, body, pos = decode(data, pos) + t.assert(msgpack.is_object(header)) + t.assert_equals(header:decode(), { + [box.iproto.key.SYNC] = 3, + [box.iproto.key.REQUEST_TYPE] = box.iproto.type.REPLACE, + }) + t.assert(msgpack.is_object(body)) + t.assert_equals(body:decode(), { + [box.iproto.key.SPACE_NAME] = 'test', + [box.iproto.key.TUPLE] = {1, 2, 3}, + }) + + t.assert_equals(pos, #data + 1) + t.assert_equals({decode(data, pos)}, {nil, 1}) +end + +-- +-- Checks box.iproto.encode_packet() output on binary input. +-- +g.test_encode_packet_bin = function() + local encode = box.iproto.encode_packet + + local data = encode(string.fromhex('820002017b'), + string.fromhex('8210cd02002193010203')) + t.assert_equals(string.hex(data), + 'ce0000000f820002017b8210cd02002193010203') + data = encode(string.fromhex('82000c017b')) + t.assert_equals(string.hex(data), 'ce0000000582000c017b') + + -- box.iproto.encode_packet() doesn't check binary input. + data = encode(string.fromhex('c1'), string.fromhex('82')) + t.assert_equals(string.hex(data), 'ce00000002c182') +end diff --git a/test/box-luatest/ghs_16_user_enumeration_test.lua b/test/box-luatest/ghs_16_user_enumeration_test.lua index c32f5c15fb..6876e1f5fc 100644 --- a/test/box-luatest/ghs_16_user_enumeration_test.lua +++ b/test/box-luatest/ghs_16_user_enumeration_test.lua @@ -1,4 +1,3 @@ -local msgpack = require('msgpack') local net = require('net.box') local server = require('luatest.server') local socket = require('socket') @@ -7,39 +6,37 @@ local t = require('luatest') local g = t.group() -local IPROTO_REQUEST_TYPE = 0 -local IPROTO_TYPE_ERROR = bit.lshift(1, 15) -local IPROTO_AUTH = 7 -local IPROTO_TUPLE = 33 -local IPROTO_USER = 35 -local IPROTO_ERROR = 49 - -- Opens a new connection and sends IPROTO_AUTH request. -- Returns {code, error} local function auth(sock_path, user, tuple) - local hdr = msgpack.encode({[IPROTO_REQUEST_TYPE] = IPROTO_AUTH}) - local body = msgpack.encode({ - [IPROTO_USER] = user, - [IPROTO_TUPLE] = tuple, - }) - local len = hdr:len() + body:len() - t.assert_lt(len, 256) local s = socket.tcp_connect('unix/', sock_path) - local data = s:read(128) -- greeting - t.assert_equals(#data, 128) - data = '\xce\00\00\00' .. string.char(len) .. hdr .. body - t.assert_equals(s:write(data), #data) -- request - data = s:read(5) -- fixheader - t.assert_equals(#data, 5) - len = msgpack.decode(data) - data = s:read(len) -- response - t.assert_equals(#data, len) + local greeting = s:read(box.iproto.GREETING_SIZE) + greeting = box.iproto.decode_greeting(greeting) + t.assert_covers(greeting, {protocol = 'Binary'}) + local request = box.iproto.encode_packet({ + sync = 123, + request_type = box.iproto.type.AUTH, + }, { + user_name = user, + tuple = tuple, + }) + t.assert_equals(s:write(request), #request) + local response = '' + local header, body + repeat + header, body = box.iproto.decode_packet(response) + if header == nil then + local size = body + local data = s:read(size) + t.assert_is_not(data) + response = response .. data + end + until header ~= nil s:close() - hdr, len = msgpack.decode(data) - body = msgpack.decode(data, len) + t.assert_equals(header.sync, 123) return { - bit.band(hdr[IPROTO_REQUEST_TYPE], bit.bnot(IPROTO_TYPE_ERROR)), - body[IPROTO_ERROR], + bit.band(header.request_type, bit.bnot(box.iproto.type.TYPE_ERROR)), + body.error_24, } end -- GitLab