diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 117879382ef8b0c52aacfec60eb71ee2e7cd3745..25171da8e81e9bc7c61afb01af6990cdbe196a12 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -106,6 +106,7 @@ set (server_sources lua/trigger.cc lua/ipc.cc lua/msgpack.cc + lua/net_box.cc lua/utils.cc lua/errno.c lua/bsdsocket.cc diff --git a/src/box/lua/call.cc b/src/box/lua/call.cc index 87e26cf4c0ca8c4d18d45d1db02b349ccdfe790d..5c56e67ddde0a7aa53154378dfabc54554cb48b5 100644 --- a/src/box/lua/call.cc +++ b/src/box/lua/call.cc @@ -467,26 +467,6 @@ SetuidGuard::~SetuidGuard() fiber_set_user(fiber(), orig_credentials); } -/** - * A quick approximation if a Lua table is an array. - * - * JSON can only have strings as keys, so if the first - * table key is 1, it's definitely not a json map, - * and very likely an array. - */ -static inline bool -lua_isarray(struct lua_State *L, int i) -{ - if (lua_istable(L, i) == false) - return false; - lua_pushnil(L); - if (lua_next(L, i) == 0) /* the table is empty */ - return true; - bool index_starts_at_1 = lua_isnumber(L, -2) && - lua_tonumber(L, -2) == 1; - lua_pop(L, 2); - return index_starts_at_1; -} static inline void execute_c_call(struct func *func, struct request *request, struct obuf *out) @@ -605,7 +585,7 @@ execute_lua_call(lua_State *L, struct func *func, struct request *request, try { /** Check if we deal with a table of tables. */ int nrets = lua_gettop(L); - if (nrets == 1 && lua_isarray(L, 1)) { + if (nrets == 1 && luaL_isarray(L, 1)) { /* * The table is not empty and consists of tables * or tuples. Treat each table element as a tuple, @@ -613,7 +593,7 @@ execute_lua_call(lua_State *L, struct func *func, struct request *request, */ lua_pushnil(L); int has_keys = lua_next(L, 1); - if (has_keys && (lua_isarray(L, lua_gettop(L)) || lua_istuple(L, -1))) { + if (has_keys && (luaL_isarray(L, lua_gettop(L)) || lua_istuple(L, -1))) { do { luamp_encode_tuple(L, luaL_msgpack_default, &stream, -1); @@ -626,12 +606,7 @@ execute_lua_call(lua_State *L, struct func *func, struct request *request, } } for (int i = 1; i <= nrets; ++i) { - if (lua_isarray(L, i) || lua_istuple(L, i)) { - luamp_encode_tuple(L, luaL_msgpack_default, &stream, i); - } else { - luamp_encode_array(luaL_msgpack_default, &stream, 1); - luamp_encode(L, luaL_msgpack_default, &stream, i); - } + luamp_convert_tuple(L, luaL_msgpack_default, &stream, i); ++count; } diff --git a/src/box/lua/tuple.cc b/src/box/lua/tuple.cc index ac75b438f287e56e3cebd44b65f6ff6e08ff63bb..f67e23d59bd07a46765754080c453d41a21ad703 100644 --- a/src/box/lua/tuple.cc +++ b/src/box/lua/tuple.cc @@ -171,6 +171,30 @@ luamp_encode_extension_box(struct lua_State *L, int idx, return MP_EXT; } +void +luamp_convert_tuple(struct lua_State *L, struct luaL_serializer *cfg, + struct mpstream *stream, int index) +{ + if (luaL_isarray(L, index) || lua_istuple(L, index)) { + luamp_encode_tuple(L, cfg, stream, index); + } else { + luamp_encode_array(cfg, stream, 1); + luamp_encode(L, cfg, stream, index); + } +} + +void +luamp_convert_key(struct lua_State *L, struct luaL_serializer *cfg, + struct mpstream *stream, int index) +{ + /* Performs keyfy() logic */ + if (lua_isnil(L, index)) { + luamp_encode_array(cfg, stream, 0); + } else { + return luamp_convert_tuple(L, cfg, stream, index); + } +} + void luamp_encode_tuple(struct lua_State *L, struct luaL_serializer *cfg, struct mpstream *stream, int index) diff --git a/src/box/lua/tuple.h b/src/box/lua/tuple.h index def6d88d8aede83df4b059d900a0e6f8fb973894..c17ce7da112b0a46b3a02974964cb2d6e92876df 100644 --- a/src/box/lua/tuple.h +++ b/src/box/lua/tuple.h @@ -55,9 +55,17 @@ lbox_pushtupleornil(lua_State *L, box_tuple_t *tuple) struct tuple *lua_istuple(struct lua_State *L, int narg); void -luamp_encode_tuple(struct lua_State *L, struct luaL_serializer *cfg, +luamp_convert_key(struct lua_State *L, struct luaL_serializer *cfg, struct mpstream *stream, int index); +void +luamp_convert_tuple(struct lua_State *L, struct luaL_serializer *cfg, + struct mpstream *stream, int index); + +void +luamp_encode_tuple(struct lua_State *L, struct luaL_serializer *cfg, + struct mpstream *stream, int index); + char * lbox_encode_tuple_on_gc(lua_State *L, int idx, size_t *p_len); diff --git a/src/lib/msgpuck b/src/lib/msgpuck index b261d2c986f4de373e947a7d6eb6ca7d18a66488..0fb5dddd2a7f2ec4dd55a22e9c81d65bc0b992b0 160000 --- a/src/lib/msgpuck +++ b/src/lib/msgpuck @@ -1 +1 @@ -Subproject commit b261d2c986f4de373e947a7d6eb6ca7d18a66488 +Subproject commit 0fb5dddd2a7f2ec4dd55a22e9c81d65bc0b992b0 diff --git a/src/lib/small/ibuf.h b/src/lib/small/ibuf.h index 40da17f53a2865ce5043c946943d952860c8ffe4..7b73660a310553f6de4ac2c42a32d214bac2247d 100644 --- a/src/lib/small/ibuf.h +++ b/src/lib/small/ibuf.h @@ -159,6 +159,21 @@ ibuf_reserve(struct ibuf *ibuf, size_t size) return ptr; } +static inline void * +ibuf_reserve_cb(void *ptr, size_t *size) +{ + struct ibuf *b = (struct ibuf*) ptr; + size_t s = *size; + return ibuf_reserve(b, s); +} + +static inline void * +ibuf_alloc_cb(void *ptr, size_t size) +{ + struct ibuf *b = (struct ibuf*) ptr; + return ibuf_alloc_nothrow(b, size); +} + #endif /* defined(__cplusplus) */ #endif /* TARANTOOL_SMALL_IBUF_H_INCLUDED */ diff --git a/src/lua/fiber.cc b/src/lua/fiber.cc index 823ffcf7524c8d32a469aa1ebbab1ec21c543d25..4c4fe8020ca8f381c97d5027fcdc97b73433510b 100644 --- a/src/lua/fiber.cc +++ b/src/lua/fiber.cc @@ -161,7 +161,12 @@ lbox_pushfiber(struct lua_State *L, struct fiber *f) static struct fiber * lbox_checkfiber(struct lua_State *L, int index) { - uint32_t fid = *(uint32_t *) luaL_checkudata(L, index, fiberlib_name); + uint32_t fid; + if (lua_type(L, index) == LUA_TNUMBER) { + fid = lua_tointeger(L, index); + } else { + fid = *(uint32_t *) luaL_checkudata(L, index, fiberlib_name); + } struct fiber *f = fiber_find(fid); if (f == NULL) luaL_error(L, "the fiber is dead"); @@ -564,6 +569,7 @@ static const struct luaL_reg fiberlib[] = { {"id", lbox_fiber_id}, {"find", lbox_fiber_find}, {"kill", lbox_fiber_kill}, + {"wakeup", lbox_fiber_wakeup}, {"cancel", lbox_fiber_cancel}, {"testcancel", lbox_fiber_testcancel}, {"create", lbox_fiber_create}, diff --git a/src/lua/init.cc b/src/lua/init.cc index 11d7ac945cd8ceaf839ae18fdb69368ead9f6d8f..31c7f3a6c19094d50cafb9566060f2ed6f225161 100644 --- a/src/lua/init.cc +++ b/src/lua/init.cc @@ -56,6 +56,7 @@ extern "C" { #include "third_party/lua-cjson/lua_cjson.h" #include "third_party/lua-yaml/lyaml.h" #include "lua/msgpack.h" +#include "lua/net_box.h" #include "lua/pickle.h" #include "lua/fio.h" @@ -368,6 +369,10 @@ tarantool_lua_init(const char *tarantool_bin, int argc, char **argv) lua_pop(L, 1); luaopen_json(L); lua_pop(L, 1); + luaopen_msgpack(L); + lua_pop(L, 1); + luaopen_net_box(L); + lua_pop(L, 1); #if defined(HAVE_GNU_READLINE) /* diff --git a/src/lua/msgpack.cc b/src/lua/msgpack.cc index ca3fe1282024dfdd53650db350602dd1897fc01f..cb5953663d74ef2d5d1c55561dab0d27eebfb5a0 100644 --- a/src/lua/msgpack.cc +++ b/src/lua/msgpack.cc @@ -437,7 +437,7 @@ const luaL_reg msgpacklib[] = { { "encode", lua_msgpack_encode }, { "decode", lua_msgpack_decode }, { "ibuf_decode", lua_ibuf_msgpack_decode }, - { "new", lua_msgpack_new }, + { "new", lua_msgpack_new }, { NULL, NULL} }; diff --git a/src/lua/msgpackffi.lua b/src/lua/msgpackffi.lua index b6168ed57b7f9ee213cf8ddeb5048bbe119282d5..f02f72a3e8ae5a57b373c9a24d536dbc4ec45c0a 100644 --- a/src/lua/msgpackffi.lua +++ b/src/lua/msgpackffi.lua @@ -232,6 +232,15 @@ local function encode(obj) return r end +local function encode_ibuf(obj, ibuf) + encode_r(ibuf, obj, 0) +end + +local function encode_len(len, wpos) + wpos[0] = 0xce + ffi.cast(uint32_ptr_t, wpos + 1)[0] = bswap_u32(len) +end + on_encode(ffi.typeof('uint8_t'), encode_int) on_encode(ffi.typeof('uint16_t'), encode_int) on_encode(ffi.typeof('uint32_t'), encode_int) @@ -497,4 +506,9 @@ return { decode_unchecked = decode_unchecked; decode = decode_unchecked; -- just for tests encode_tuple = encode_tuple; + encode_ibuf = encode_ibuf; + encode_len = encode_len; + encode_map = encode_map; + encode_int = encode_int; + encode_array = encode_array; } diff --git a/src/lua/net_box.cc b/src/lua/net_box.cc new file mode 100644 index 0000000000000000000000000000000000000000..9e6fc68e0c9e5702205a096818d73f5c05dc0f46 --- /dev/null +++ b/src/lua/net_box.cc @@ -0,0 +1,378 @@ +/* + * Copyright 2010-2015, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "lua/net_box.h" +#include "lua/msgpack.h" + +#include <lib/small/ibuf.h> +#include "scramble.h" + +/* TODO: net.box depends on src/box/ */ +#include "box/iproto_constants.h" +#include "box/lua/tuple.h" /* luamp_convert_tuple() / luamp_convert_key() */ + +#include <msgpuck/msgpuck.h> /* mp_store_u32() */ +#include "third_party/base64.h" + +#define cfg luaL_msgpack_default + +static inline size_t +netbox_prepare_request(lua_State *L, struct mpstream *stream, uint32_t r_type) +{ + struct ibuf *ibuf = (struct ibuf *) lua_topointer(L, 1); + uint64_t sync = luaL_touint64(L, 2); + + mpstream_init(stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb); + + /* Remember initial size of ibuf (see netbox_encode_request()) */ + size_t used = ibuf_used(ibuf); + + /* Reserve and skip space for fixheader */ + size_t fixheader_size = mp_sizeof_uint(UINT32_MAX); + mpstream_reserve(stream, fixheader_size); + mpstream_advance(stream, fixheader_size); + + /* encode header */ + luamp_encode_map(cfg, stream, 2); + + luamp_encode_uint(cfg, stream, IPROTO_SYNC); + luamp_encode_uint(cfg, stream, sync); + + luamp_encode_uint(cfg, stream, IPROTO_REQUEST_TYPE); + luamp_encode_uint(cfg, stream, r_type); + + /* Caller should remember how many bytes was used in ibuf */ + return used; +} + +static inline void +netbox_encode_request(struct mpstream *stream, size_t initial_size) +{ + mpstream_flush(stream); + + struct ibuf *ibuf = (struct ibuf *) stream->ctx; + + /* + * Calculation the start position in ibuf by getting current size + * and then substracting initial size. Since we don't touch + * ibuf->rpos during encoding this approach should always work + * even on realloc or memmove inside ibuf. + */ + size_t fixheader_size = mp_sizeof_uint(UINT32_MAX); + size_t used = ibuf_used(ibuf); + assert(initial_size + fixheader_size <= used); + size_t total_size = used - initial_size; + char *fixheader = ibuf->wpos - total_size; + assert(fixheader >= ibuf->rpos); + + /* patch skipped len */ + *(fixheader++) = 0xce; + /* fixheader size is not included */ + mp_store_u32(fixheader, total_size - fixheader_size); +} + +static int +netbox_encode_ping(lua_State *L) +{ + if (lua_gettop(L) < 2) + return luaL_error(L, "Usage: netbox.encode_ping(ibuf, sync)"); + + struct mpstream stream; + size_t svp = netbox_prepare_request(L, &stream, IPROTO_PING); + netbox_encode_request(&stream, svp); + return 0; +} + +static int +netbox_encode_auth(lua_State *L) +{ + if (lua_gettop(L) < 5) + return luaL_error(L, "Usage: netbox.encode_update(ibuf, sync, " + "user, password, greeting)"); + + struct mpstream stream; + size_t svp = netbox_prepare_request(L, &stream, IPROTO_AUTH); + + size_t user_len; + const char *user = lua_tolstring(L, 3, &user_len); + size_t password_len; + const char *password = lua_tolstring(L, 4, &password_len); + size_t greeting_len; + const char *greeting = lua_tolstring(L, 5, &greeting_len); + if (greeting_len != IPROTO_GREETING_SIZE) + return luaL_error(L, "Invalid greeting"); + + /* Adapted from xrow_encode_auth() */ + luamp_encode_map(cfg, &stream, password != NULL ? 2 : 1); + luamp_encode_uint(cfg, &stream, IPROTO_USER_NAME); + luamp_encode_str(cfg, &stream, user, user_len); + if (password != NULL) { /* password can be omitted */ + char salt[SCRAMBLE_SIZE]; + char scramble[SCRAMBLE_SIZE]; + if (base64_decode(greeting + 64, SCRAMBLE_BASE64_SIZE, salt, + SCRAMBLE_SIZE) != SCRAMBLE_SIZE) + return luaL_error(L, "invalid salt"); + scramble_prepare(scramble, salt, password, password_len); + luamp_encode_uint(cfg, &stream, IPROTO_TUPLE); + luamp_encode_array(cfg, &stream, 2); + luamp_encode_str(cfg, &stream, "chap-sha1", strlen("chap-sha1")); + luamp_encode_str(cfg, &stream, scramble, SCRAMBLE_SIZE); + } + + netbox_encode_request(&stream, svp); + return 0; +} + +static int +netbox_encode_call(lua_State *L) +{ + if (lua_gettop(L) < 4) + return luaL_error(L, "Usage: netbox.encode_call(ibuf, sync, " + "function_name, args)"); + + struct mpstream stream; + size_t svp = netbox_prepare_request(L, &stream, IPROTO_CALL); + + luamp_encode_map(cfg, &stream, 2); + + /* encode proc name */ + size_t name_len; + const char *name = lua_tolstring(L, 3, &name_len); + luamp_encode_uint(cfg, &stream, IPROTO_FUNCTION_NAME); + luamp_encode_str(cfg, &stream, name, name_len); + + /* encode args */ + luamp_encode_uint(cfg, &stream, IPROTO_TUPLE); + luamp_encode_tuple(L, cfg, &stream, 4); + + netbox_encode_request(&stream, svp); + return 0; +} + +static int +netbox_encode_eval(lua_State *L) +{ + if (lua_gettop(L) < 4) + return luaL_error(L, "Usage: netbox.encode_eval(ibuf, sync, " + "expr, args)"); + + struct mpstream stream; + size_t svp = netbox_prepare_request(L, &stream, IPROTO_EVAL); + + luamp_encode_map(cfg, &stream, 2); + + /* encode expr */ + size_t expr_len; + const char *expr = lua_tolstring(L, 3, &expr_len); + luamp_encode_uint(cfg, &stream, IPROTO_EXPR); + luamp_encode_str(cfg, &stream, expr, expr_len); + + /* encode args */ + luamp_encode_uint(cfg, &stream, IPROTO_TUPLE); + luamp_encode_tuple(L, cfg, &stream, 4); + + netbox_encode_request(&stream, svp); + return 0; +} + +static int +netbox_encode_select(lua_State *L) +{ + if (lua_gettop(L) < 8) + return luaL_error(L, "Usage netbox.encode_select(ibuf, sync, " + "space_id, index_id, iterator, offset, " + "limit, key)"); + + struct mpstream stream; + size_t svp = netbox_prepare_request(L, &stream, IPROTO_SELECT); + + luamp_encode_map(cfg, &stream, 6); + + uint32_t space_id = lua_tointeger(L, 3); + uint32_t index_id = lua_tointeger(L, 4); + int iterator = lua_tointeger(L, 5); + uint32_t offset = lua_tointeger(L, 6); + uint32_t limit = lua_tointeger(L, 7); + + /* encode space_id */ + luamp_encode_uint(cfg, &stream, IPROTO_SPACE_ID); + luamp_encode_uint(cfg, &stream, space_id); + + /* encode index_id */ + luamp_encode_uint(cfg, &stream, IPROTO_INDEX_ID); + luamp_encode_uint(cfg, &stream, index_id); + + /* encode iterator */ + luamp_encode_uint(cfg, &stream, IPROTO_ITERATOR); + luamp_encode_uint(cfg, &stream, iterator); + + /* encode offset */ + luamp_encode_uint(cfg, &stream, IPROTO_OFFSET); + luamp_encode_uint(cfg, &stream, offset); + + /* encode limit */ + luamp_encode_uint(cfg, &stream, IPROTO_LIMIT); + luamp_encode_uint(cfg, &stream, limit); + + /* encode key */ + luamp_encode_uint(cfg, &stream, IPROTO_KEY); + luamp_convert_key(L, cfg, &stream, 8); + + netbox_encode_request(&stream, svp); + return 0; +} + +static inline int +netbox_encode_insert_or_replace(lua_State *L, uint32_t reqtype) +{ + if (lua_gettop(L) < 4) + return luaL_error(L, "Usage: netbox.encode_insert(ibuf, sync, " + "space_id, tuple)"); + lua_settop(L, 4); + + struct mpstream stream; + size_t svp = netbox_prepare_request(L, &stream, reqtype); + + luamp_encode_map(cfg, &stream, 2); + + /* encode space_id */ + uint32_t space_id = lua_tointeger(L, 3); + luamp_encode_uint(cfg, &stream, IPROTO_SPACE_ID); + luamp_encode_uint(cfg, &stream, space_id); + + /* encode args */ + luamp_encode_uint(cfg, &stream, IPROTO_TUPLE); + luamp_encode_tuple(L, cfg, &stream, 4); + + netbox_encode_request(&stream, svp); + return 0; +} + +static int +netbox_encode_insert(lua_State *L) +{ + return netbox_encode_insert_or_replace(L, IPROTO_INSERT); +} + +static int +netbox_encode_replace(lua_State *L) +{ + return netbox_encode_insert_or_replace(L, IPROTO_REPLACE); +} + +static int +netbox_encode_delete(lua_State *L) +{ + if (lua_gettop(L) < 5) + return luaL_error(L, "Usage: netbox.encode_delete(ibuf, sync, " + "space_id, index_id, key)"); + + struct mpstream stream; + size_t svp = netbox_prepare_request(L, &stream, IPROTO_DELETE); + + luamp_encode_map(cfg, &stream, 3); + + /* encode space_id */ + uint32_t space_id = lua_tointeger(L, 3); + luamp_encode_uint(cfg, &stream, IPROTO_SPACE_ID); + luamp_encode_uint(cfg, &stream, space_id); + + /* encode space_id */ + uint32_t index_id = lua_tointeger(L, 4); + luamp_encode_uint(cfg, &stream, IPROTO_INDEX_ID); + luamp_encode_uint(cfg, &stream, index_id); + + /* encode key */ + luamp_encode_uint(cfg, &stream, IPROTO_KEY); + luamp_convert_key(L, cfg, &stream, 5); + + netbox_encode_request(&stream, svp); + return 0; +} + +static int +netbox_encode_update(lua_State *L) +{ + if (lua_gettop(L) < 6) + return luaL_error(L, "Usage: netbox.encode_update(ibuf, sync, " + "space_id, index_id, key, ops)"); + + struct mpstream stream; + size_t svp = netbox_prepare_request(L, &stream, IPROTO_UPDATE); + + luamp_encode_map(cfg, &stream, 5); + + /* encode space_id */ + uint32_t space_id = lua_tointeger(L, 3); + luamp_encode_uint(cfg, &stream, IPROTO_SPACE_ID); + luamp_encode_uint(cfg, &stream, space_id); + + /* encode index_id */ + uint32_t index_id = lua_tointeger(L, 4); + luamp_encode_uint(cfg, &stream, IPROTO_INDEX_ID); + luamp_encode_uint(cfg, &stream, index_id); + + /* encode index_id */ + luamp_encode_uint(cfg, &stream, IPROTO_INDEX_BASE); + luamp_encode_uint(cfg, &stream, 1); + + /* encode in reverse order for speedup - see luamp_encode() code */ + /* encode ops */ + luamp_encode_uint(cfg, &stream, IPROTO_TUPLE); + luamp_encode_tuple(L, cfg, &stream, 6); + lua_pop(L, 1); /* ops */ + + /* encode key */ + luamp_encode_uint(cfg, &stream, IPROTO_KEY); + luamp_convert_key(L, cfg, &stream, 5); + + netbox_encode_request(&stream, svp); + return 0; +} + +int +luaopen_net_box(struct lua_State *L) +{ + const luaL_reg net_box_lib[] = { + { "encode_ping", netbox_encode_ping }, + { "encode_call", netbox_encode_call }, + { "encode_eval", netbox_encode_eval }, + { "encode_select", netbox_encode_select }, + { "encode_insert", netbox_encode_insert }, + { "encode_replace", netbox_encode_replace }, + { "encode_delete", netbox_encode_delete }, + { "encode_update", netbox_encode_update }, + { "encode_auth", netbox_encode_auth }, + { NULL, NULL} + }; + luaL_register(L, "net.box.lib", net_box_lib); + return 1; +} diff --git a/src/lua/net_box.h b/src/lua/net_box.h new file mode 100644 index 0000000000000000000000000000000000000000..5a48476a318af73b5e4660c3e9444201e5478c7a --- /dev/null +++ b/src/lua/net_box.h @@ -0,0 +1,47 @@ +#ifndef TARANTOOL_LUA_NET_BOX_H_INCLUDED +#define TARANTOOL_LUA_NET_BOX_H_INCLUDED +/* + * Copyright 2010-2015, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(__cplusplus) +extern "C" { +#endif /* defined(__cplusplus) */ + +struct lua_State; + +int +luaopen_net_box(struct lua_State *L); + +#if defined(__cplusplus) +} /* extern "C" */ +#endif /* defined(__cplusplus) */ + +#endif /* TARANTOOL_LUA_NET_BOX_H_INCLUDED */ diff --git a/src/lua/net_box.lua b/src/lua/net_box.lua index 536e681ce0ebe0a7484d78dca3a78261b2aa1ff6..76cbfa1371ab294b1606c8aadf243b8b86259b26 100644 --- a/src/lua/net_box.lua +++ b/src/lua/net_box.lua @@ -1,12 +1,12 @@ -- net_box.lua (internal file) +local internal = require 'net.box.lib' local msgpack = require 'msgpack' local fiber = require 'fiber' local socket = require 'socket' local log = require 'log' local errno = require 'errno' local ffi = require 'ffi' -local digest = require 'digest' local yaml = require 'yaml' local urilib = require 'uri' local buffer = require 'buffer' @@ -53,39 +53,24 @@ local mapping_mt = { __serialize = 'mapping' } local CONSOLE_FAKESYNC = 15121974 local CONSOLE_DELIMITER = "$EOF$" -local function request(header, body) - -- hint msgpack to always encode header and body as a map - header = msgpack.encode(setmetatable(header, mapping_mt)) - body = msgpack.encode(setmetatable(body, mapping_mt)) +local ch_buf = {} +local ch_buf_size = 0 - local len = msgpack.encode(string.len(header) + string.len(body)) - - return len .. header .. body -end - - -local function strxor(s1, s2) - local res = '' - for i = 1, string.len(s1) do - if i > string.len(s2) then - break - end - - local b1 = string.byte(s1, i) - local b2 = string.byte(s2, i) - res = res .. string.char(bit.bxor(b1, b2)) +local function get_channel() + if ch_buf_size == 0 then + return fiber.channel() end - return res + + local ch = ch_buf[ch_buf_size] + ch_buf[ch_buf_size] = nil + ch_buf_size = ch_buf_size - 1 + return ch end -local function keyfy(v) - if type(v) == 'table' then - return v - end - if v == nil then - return {} - end - return { v } +local function free_channel(ch) + -- return channel to buffer + ch_buf[ch_buf_size + 1] = ch + ch_buf_size = ch_buf_size + 1 end local function one_tuple(tbl) @@ -98,84 +83,16 @@ local function one_tuple(tbl) return end -local proto = { - _sync = -1, - - -- sync - sync = function(self) - self._sync = self._sync + 1 - if self._sync >= 0x7FFFFFFF then - self._sync = 0 - end - return self._sync - end, - - - ping = function(sync) - return request( - { [SYNC] = sync, [TYPE] = PING }, - {} - ) - end, - - - -- lua call - call = function(sync, proc, args) - if args == nil then - args = {} - end - return request( - { [SYNC] = sync, [TYPE] = CALL }, - { [FUNCTION_NAME] = proc, [TUPLE] = args } - ) - end, - - -- lua eval - eval = function(sync, expr, args) - if args == nil then - args = {} - end - return request( - { [SYNC] = sync, [TYPE] = EVAL }, - { [EXPR] = expr, [TUPLE] = args } - ) - end, - - -- insert - insert = function(sync, spaceno, tuple) - return request( - { [SYNC] = sync, [TYPE] = INSERT }, - { [SPACE_ID] = spaceno, [TUPLE] = tuple } - ) - end, - - -- replace - replace = function(sync, spaceno, tuple) - return request( - { [SYNC] = sync, [TYPE] = REPLACE }, - { [SPACE_ID] = spaceno, [TUPLE] = tuple } - ) - end, - - -- delete - delete = function(sync, spaceno, key, index_id) - return request( - { [SYNC] = sync, [TYPE] = DELETE }, - { [SPACE_ID] = spaceno, [INDEX_ID] = index_id, [KEY] = keyfy(key) } - ) - end, - - -- update - update = function(sync, spaceno, key, oplist, index_id) - return request( - { [SYNC] = sync, [TYPE] = UPDATE }, - { [KEY] = keyfy(key), [INDEX_BASE] = 1 , [TUPLE] = oplist, - [SPACE_ID] = spaceno, [INDEX_ID] = index_id } - ) - end, - - -- select - select = function(sync, spaceno, indexno, key, opts) +local requests = { + [PING] = internal.encode_ping; + [AUTH] = internal.encode_auth; + [CALL] = internal.encode_call; + [EVAL] = internal.encode_eval; + [INSERT] = internal.encode_insert; + [REPLACE] = internal.encode_replace; + [DELETE] = internal.encode_delete; + [UPDATE] = internal.encode_update; + [SELECT] = function(wbuf, sync, spaceno, indexno, key, opts) if opts == nil then opts = {} end @@ -187,49 +104,25 @@ local proto = { box.error(box.error.NO_SUCH_INDEX, indexno, '#'..tostring(spaceno)) end - key = keyfy(key) - - local body = { - [SPACE_ID] = spaceno, - [INDEX_ID] = indexno, - [KEY] = key - } - + local limit, offset if opts.limit ~= nil then - body[LIMIT] = tonumber(opts.limit) + limit = tonumber(opts.limit) else - body[LIMIT] = 0xFFFFFFFF + limit = 0xFFFFFFFF end if opts.offset ~= nil then - body[OFFSET] = tonumber(opts.offset) + offset = tonumber(opts.offset) else - body[OFFSET] = 0 + offset = 0 end + local iterator = require('box.internal').check_iterator_type(opts, + key == nil or (type(key) == 'table' and #key == 0)) - body[ITERATOR] = require('box.internal').check_iterator_type(opts, #key == 0) - - return request( { [SYNC] = sync, [TYPE] = SELECT }, body ) - end, - - auth = function(sync, user, password, handshake) - local saltb64 = string.sub(handshake, 65) - local salt = string.sub(digest.base64_decode(saltb64), 1, 20) - - local hpassword = digest.sha1(password) - local hhpassword = digest.sha1(hpassword) - local scramble = digest.sha1(salt .. hhpassword) - - local hash = strxor(hpassword, scramble) - return request( - { [SYNC] = sync, [TYPE] = AUTH }, - { [USER] = user, [TUPLE] = { 'chap-sha1', hash } } - ) - end, - - b64decode = digest.base64_decode, + internal.encode_select(wbuf, sync, spaceno, indexno, iterator, + offset, limit, key) + end; } - local function check_if_space(space) if type(space) == 'table' and space.id ~= nil then return @@ -360,10 +253,8 @@ local errno_is_transient = { local remote = {} local remote_methods = { - proto = proto, - new = function(cls, host, port, opts) - local self = {} + local self = { _sync = -1 } if type(cls) == 'table' then setmetatable(self, getmetatable(cls)) @@ -423,7 +314,7 @@ local remote_methods = { self.is_run = true self.state = 'init' - self.wbuf = {} + self.wbuf = buffer.ibuf(buffer.READAHEAD) self.rpos = 1 self.rlen = 0 @@ -442,6 +333,14 @@ local remote_methods = { return self end, + -- sync + sync = function(self) + self._sync = self._sync + 1 + if self._sync >= 0x7FFFFFFF then + self._sync = 0 + end + return self._sync + end, ping = function(self) if type(self) ~= 'table' then @@ -450,10 +349,7 @@ local remote_methods = { if not self:is_connected() then return false end - local sync = self.proto:sync() - local req = self.proto.ping(sync) - - local res = self:_request('ping', false) + local res = self:_request(PING, false) if res == nil then @@ -467,8 +363,11 @@ local remote_methods = { end, _console = function(self, line) - local res = self:_request_raw('eval', CONSOLE_FAKESYNC, - line..CONSOLE_DELIMITER.."\n", true) + local data = line..CONSOLE_DELIMITER.."\n\n" + ffi.copy(self.wbuf.wpos, data, #data) + self.wbuf.wpos = self.wbuf.wpos + #data + + local res = self:_request_raw(EVAL, CONSOLE_FAKESYNC, data, true) return res.body[DATA] end, @@ -479,7 +378,7 @@ local remote_methods = { proc_name = tostring(proc_name) - local res = self:_request('call', true, proc_name, {...}) + local res = self:_request(CALL, true, proc_name, {...}) return res.body[DATA] end, @@ -490,7 +389,7 @@ local remote_methods = { end expr = tostring(expr) - local data = self:_request('eval', true, expr, {...}).body[DATA] + local data = self:_request(EVAL, true, expr, {...}).body[DATA] local data_len = #data if data_len == 1 then return data[1] @@ -577,15 +476,26 @@ local remote_methods = { self:_error_waiters(emsg) self.rpos = 1 self.rlen = 0 - self.wbuf = {} self.handshake = '' end, + _wakeup_client = function(self, hdr, body) + local sync = hdr[SYNC] + + local ch = self.ch.sync[sync] + if ch ~= nil then + ch.response = { hdr = hdr, body = body } + fiber.wakeup(ch.fid) + else + log.warn("Unexpected response %s", tostring(sync)) + end + end, + _error_waiters = function(self, emsg) local waiters = self.ch.sync self.ch.sync = {} for sync, channel in pairs(waiters) do - channel:put{ + channel.response = { hdr = { [TYPE] = bit.bor(ERROR_TYPE, box.error.NO_CONNECTION), [SYNC] = sync @@ -594,6 +504,7 @@ local remote_methods = { [ERROR] = emsg } } + fiber.wakeup(channel.fid) end end, @@ -609,27 +520,11 @@ local remote_methods = { local hdr = { [SYNC] = CONSOLE_FAKESYNC, [TYPE] = 0 } local body = { [DATA] = resp } - if self.ch.sync[CONSOLE_FAKESYNC] ~= nil then - self.ch.sync[CONSOLE_FAKESYNC]:put({hdr = hdr, body = body }) - self.ch.sync[CONSOLE_FAKESYNC] = nil - else - log.warn("Unexpected console response: %s", resp) - end + self:_wakeup_client(hdr, body) self.rbuf:read(#resp) return 0 end, - _wakeup_client = function(self, hdr, body) - local sync = hdr[SYNC] - - if self.ch.sync[sync] ~= nil then - self.ch.sync[sync]:put({ hdr = hdr, body = body }) - self.ch.sync[sync] = nil - else - log.warn("Unexpected response %s", tostring(sync)) - end - end, - _check_binary_response = function(self) while true do if self.rbuf.rpos + 5 > self.rbuf.wpos then @@ -682,7 +577,7 @@ local remote_methods = { while timeout > 0 and self:_is_state(states) ~= true do local started = fiber.time() local fid = fiber.id() - local ch = fiber.channel() + local ch = get_channel() for state, _ in pairs(states) do self.wait.state[state] = self.wait.state[state] or {} self.wait.state[state][fid] = fid @@ -691,6 +586,7 @@ local remote_methods = { self.ch.fid[fid] = ch ch:get(timeout) self.ch.fid[fid] = nil + free_channel(ch) for state, _ in pairs(states) do self.wait.state[state][fid] = nil @@ -700,7 +596,6 @@ local remote_methods = { return self.state end, - _connect_worker = function(self) fiber.name('net.box.connector') local connect_states = { init = true, error = true, closed = true } @@ -735,9 +630,13 @@ local remote_methods = { self._check_response = self._check_console_response -- set delimiter self:_switch_state('schema') - local res = self:_request_raw('eval', CONSOLE_FAKESYNC, - "require('console').delimiter('"..CONSOLE_DELIMITER.."')\n\n", - true) + + local line = "require('console').delimiter('"..CONSOLE_DELIMITER.."')\n\n" + ffi.copy(self.wbuf.wpos, line, #line) + self.wbuf.wpos = self.wbuf.wpos + #line + + local res = self:_request_raw(EVAL, CONSOLE_FAKESYNC, line, true) + if res.hdr[TYPE] ~= OK then self:_fatal(res.body[ERROR]) end @@ -773,7 +672,7 @@ local remote_methods = { self:_switch_state('auth') - local auth_res = self:_request_internal('auth', + local auth_res = self:_request_internal(AUTH, false, self.opts.user, self.opts.password, self.handshake) if auth_res.hdr[TYPE] ~= OK then @@ -816,9 +715,9 @@ local remote_methods = { self:_switch_state('schema') - local spaces = self:_request_internal('select', + local spaces = self:_request_internal(SELECT, true, SPACE_SPACE_ID, 0, nil, { iterator = 'ALL' }).body[DATA] - local indexes = self:_request_internal('select', + local indexes = self:_request_internal(SELECT, true, SPACE_INDEX_ID, 0, nil, { iterator = 'ALL' }).body[DATA] local sl = {} @@ -930,16 +829,11 @@ local remote_methods = { _write_worker = function(self) fiber.name('net.box.write') while self:_wait_state(self._rw_states) ~= 'closed' do - while self.wbuf[1] ~= nil do - local s = table.concat(self.wbuf) - self.wbuf = {} - local written = self.s:syswrite(s) + while self.wbuf:size() > 0 do + local written = self.s:syswrite(self.wbuf.rpos, self.wbuf:size()) if written ~= nil then - if written ~= #s then - table.insert(self.wbuf, string.sub(s, written + 1)) - end + self.wbuf.rpos = self.wbuf.rpos + written else - table.insert(self.wbuf, s) if errno_is_transient[errno()] then -- the write is with a timeout to detect FIN -- packet on the receiving end, and close the connection. @@ -953,13 +847,14 @@ local remote_methods = { end end end + self.wbuf:reserve(buffer.READAHEAD) self:_switch_state(self._to_rstate[self.state]) end end, - _request = function(self, name, raise, ...) + _request = function(self, reqtype, raise, ...) if self.console then - box.error(box.error.UNSUPPORTED, "console", name) + box.error(box.error.UNSUPPORTED, "console", reqtype) end local fid = fiber.id() if self.timeouts[fid] == nil then @@ -990,29 +885,25 @@ local remote_methods = { end end - return self:_request_internal(name, raise, ...) + return self:_request_internal(reqtype, raise, ...) end, - _request_raw = function(self, name, sync, request, raise) + _request_raw = function(self, reqtype, sync, request, raise) local fid = fiber.id() if self.timeouts[fid] == nil then self.timeouts[fid] = TIMEOUT_INFINITY end - table.insert(self.wbuf, request) - self:_switch_state(self._to_wstate[self.state]) - local ch = fiber.channel() - + local ch = { fid = fid; } self.ch.sync[sync] = ch - - local response = ch:get(self.timeouts[fid]) + fiber.sleep(self.timeouts[fid]) + local response = ch.response self.ch.sync[sync] = nil self.timeouts[fid] = nil - if response == nil then if raise then box.error(box.error.TIMEOUT) @@ -1031,7 +922,7 @@ local remote_methods = { }) end - if response.body[DATA] ~= nil and name ~= 'eval' then + if response.body[DATA] ~= nil and reqtype ~= EVAL then if rawget(box, 'tuple') ~= nil then for i, v in pairs(response.body[DATA]) do response.body[DATA][i] = @@ -1045,35 +936,35 @@ local remote_methods = { return response end, - _request_internal = function(self, name, raise, ...) - local sync = self.proto:sync() - local request = self.proto[name](sync, ...) - return self:_request_raw(name, sync, request, raise) + _request_internal = function(self, reqtype, raise, ...) + local sync = self:sync() + local request = requests[reqtype](self.wbuf, sync, ...) + return self:_request_raw(reqtype, sync, request, raise) end, -- private (low level) methods _select = function(self, spaceno, indexno, key, opts) - local res = self:_request('select', true, spaceno, indexno, key, opts) + local res = self:_request(SELECT, true, spaceno, indexno, key, opts) return res.body[DATA] end, _insert = function(self, spaceno, tuple) - local res = self:_request('insert', true, spaceno, tuple) + local res = self:_request(INSERT, true, spaceno, tuple) return one_tuple(res.body[DATA]) end, _replace = function(self, spaceno, tuple) - local res = self:_request('replace', true, spaceno, tuple) + local res = self:_request(REPLACE, true, spaceno, tuple) return one_tuple(res.body[DATA]) end, _delete = function(self, spaceno, key, index_id) - local res = self:_request('delete', true, spaceno, key, index_id) + local res = self:_request(DELETE, true, spaceno, index_id, key, index_id) return one_tuple(res.body[DATA]) end, _update = function(self, spaceno, key, oplist, index_id) - local res = self:_request('update', true, spaceno, key, oplist, index_id) + local res = self:_request(UPDATE, true, spaceno, index_id, key, oplist) return one_tuple(res.body[DATA]) end } diff --git a/src/lua/utils.h b/src/lua/utils.h index e2990fcfb71c4b49d521f0ee095511c35a96c8c4..2cfce28b5f7da95ba88878372996cfcccfc57f48 100644 --- a/src/lua/utils.h +++ b/src/lua/utils.h @@ -421,6 +421,29 @@ luaL_toint64(struct lua_State *L, int idx); /** \endcond public */ +/** + * A quick approximation if a Lua table is an array. + * + * JSON can only have strings as keys, so if the first + * table key is 1, it's definitely not a json map, + * and very likely an array. + */ +static inline bool +luaL_isarray(struct lua_State *L, int idx) +{ + if (!lua_istable(L, idx)) + return false; + if (idx < 0) + idx = lua_gettop(L) + idx + 1; + lua_pushnil(L); + if (lua_next(L, idx) == 0) /* the table is empty */ + return true; + bool index_starts_at_1 = lua_isnumber(L, -2) && + lua_tonumber(L, -2) == 1; + lua_pop(L, 2); + return index_starts_at_1; +} + /** * Push Lua Table with __serialize = 'map' hint onto the stack. * Tables with __serialize hint are properly handled by all serializers. diff --git a/test/box/net.box.result b/test/box/net.box.result index f112a388b10967eef6bf98aa884b0078eca66acf..2f38086ed423cfa437c3a54dfed8a87a4f2c668a 100644 --- a/test/box/net.box.result +++ b/test/box/net.box.result @@ -288,7 +288,7 @@ cn.space.net_box_test_space:insert{234, 1,2,3} ... cn.space.net_box_test_space.insert{234, 1,2,3} --- -- error: 'builtin/net.box.lua:237: Use space:method(...) instead space.method(...)' +- error: 'builtin/net.box.lua:130: Use space:method(...) instead space.method(...)' ... cn.space.net_box_test_space:replace{354, 1,2,3} --- @@ -584,14 +584,6 @@ res[1][2] == string.rep('a', 50000) - true ... -- auth -cn.proto.b64decode('gJLocxbO32VmfO8x04xRVxKfgwzmNVM2t6a1ME8XsD0=') ---- -- !!binary gJLocxbO32VmfO8x04xRVxKfgwzmNVM2t6a1ME8XsD0= -... -cn.proto.b64decode('gJLoc!!!!!!!') ---- -- !!binary gJLo -... cn = remote:new(LISTEN.host, LISTEN.service, { user = 'netbox', password = '123', wait_connected = true }) --- ... diff --git a/test/box/net.box.test.lua b/test/box/net.box.test.lua index 62b3566c85327d684a331f396dda3a8251cdf091..0ea5f5b40e89a2dfc06d39d4fad8133fa47f7a9c 100644 --- a/test/box/net.box.test.lua +++ b/test/box/net.box.test.lua @@ -225,8 +225,6 @@ res[1][1] == 1 res[1][2] == string.rep('a', 50000) -- auth -cn.proto.b64decode('gJLocxbO32VmfO8x04xRVxKfgwzmNVM2t6a1ME8XsD0=') -cn.proto.b64decode('gJLoc!!!!!!!') cn = remote:new(LISTEN.host, LISTEN.service, { user = 'netbox', password = '123', wait_connected = true }) cn:is_connected() diff --git a/test/unit/msgpack.result b/test/unit/msgpack.result index a9a2e64987cd9fc18a03db282fd7ad3b38617c70..680d81fa946e8f081f1959b6c92d57b5e1b91128 100644 --- a/test/unit/msgpack.result +++ b/test/unit/msgpack.result @@ -1,4 +1,4 @@ -1..15 +1..16 1..135 # *** test_uints *** # uint 0U @@ -1323,3 +1323,237 @@ ok 14 - subtests ok 227 - mp_compare_uint(18446744073709551615, 18446744073709551615) == 0 # *** test_compare_uints: done *** ok 15 - subtests + 1..230 + # *** test_format *** + ok 1 - Test type on step 0 + ok 2 - Test value on step 0 + ok 3 - Test type on step 1 + ok 4 - Test value on step 1 + ok 5 - Test type on step 2 + ok 6 - Test value on step 2 + ok 7 - Test type on step 3 + ok 8 - Test value on step 3 + ok 9 - Test type on step 4 + ok 10 - Test value on step 4 + ok 11 - Test type on step 5 + ok 12 - Test value on step 5 + ok 13 - Test type on step 6 + ok 14 - Test value on step 6 + ok 15 - Test type on step 7 + ok 16 - Test value on step 7 + ok 17 - Test type on step 8 + ok 18 - Test value on step 8 + ok 19 - Test type on step 9 + ok 20 - Test value on step 9 + ok 21 - Test type on step 10 + ok 22 - Test value on step 10 + ok 23 - Test type on step 11 + ok 24 - Test value on step 11 + ok 25 - Test type on step 12 + ok 26 - Test value on step 12 + ok 27 - Test type on step 13 + ok 28 - Test value on step 13 + ok 29 - Test type on step 14 + ok 30 - Test value on step 14 + ok 31 - Test type on step 0 + ok 32 - Test value on step 0 + ok 33 - Test type on step 1 + ok 34 - Test value on step 1 + ok 35 - Test type on step 2 + ok 36 - Test value on step 2 + ok 37 - Test type on step 3 + ok 38 - Test value on step 3 + ok 39 - Test type on step 4 + ok 40 - Test value on step 4 + ok 41 - Test type on step 5 + ok 42 - Test value on step 5 + ok 43 - Test type on step 6 + ok 44 - Test value on step 6 + ok 45 - Test type on step 7 + ok 46 - Test value on step 7 + ok 47 - Test type on step 8 + ok 48 - Test value on step 8 + ok 49 - Test type on step 9 + ok 50 - Test value on step 9 + ok 51 - Test type on step 10 + ok 52 - Test value on step 10 + ok 53 - Test type on step 11 + ok 54 - Test value on step 11 + ok 55 - Test type on step 12 + ok 56 - Test value on step 12 + ok 57 - Test type on step 13 + ok 58 - Test value on step 13 + ok 59 - Test type on step 14 + ok 60 - Test value on step 14 + ok 61 - check at 551 + ok 62 - type at 552 + ok 63 - decode at 553 + ok 64 - check at 556 + ok 65 - type at 557 + ok 66 - check at 561 + ok 67 - type at 562 + ok 68 - decode at 563 + ok 69 - check at 566 + ok 70 - type at 567 + ok 71 - decode at 568 + ok 72 - check at 571 + ok 73 - type at 572 + ok 74 - decode at 573 + ok 75 - check at 576 + ok 76 - type at 577 + ok 77 - decode at 578 + ok 78 - check at 581 + ok 79 - type at 582 + ok 80 - decode at 583 + ok 81 - check at 586 + ok 82 - type at 587 + ok 83 - decode at 588 + ok 84 - check at 591 + ok 85 - type at 592 + ok 86 - decode at 593 + ok 87 - check at 596 + ok 88 - type at 597 + ok 89 - decode at 598 + ok 90 - check at 601 + ok 91 - type at 602 + ok 92 - decode at 603 + ok 93 - check at 606 + ok 94 - type at 607 + ok 95 - decode at 608 + ok 96 - check at 611 + ok 97 - type at 612 + ok 98 - decode at 614 + ok 99 - compare at 615 + ok 100 - check at 618 + ok 101 - type at 619 + ok 102 - decode at 620 + ok 103 - check at 623 + ok 104 - type at 624 + ok 105 - decode at 626 + ok 106 - compare at 627 + ok 107 - check at 630 + ok 108 - type at 631 + ok 109 - decode at 632 + ok 110 - check at 635 + ok 111 - type at 636 + ok 112 - decode at 638 + ok 113 - check at 641 + ok 114 - type at 642 + ok 115 - check at 646 + ok 116 - type at 647 + ok 117 - decode at 648 + ok 118 - check at 651 + ok 119 - type at 652 + ok 120 - decode at 653 + ok 121 - check at 656 + ok 122 - type at 657 + ok 123 - decode at 658 + ok 124 - check at 661 + ok 125 - type at 662 + ok 126 - decode at 663 + ok 127 - nothing more + ok 128 - no magic detected + ok 129 - return value on step 0 + ok 130 - buffer overflow on step 0 + ok 131 - return value on step 1 + ok 132 - buffer overflow on step 1 + ok 133 - return value on step 2 + ok 134 - buffer overflow on step 2 + ok 135 - return value on step 3 + ok 136 - buffer overflow on step 3 + ok 137 - return value on step 4 + ok 138 - buffer overflow on step 4 + ok 139 - return value on step 5 + ok 140 - buffer overflow on step 5 + ok 141 - return value on step 6 + ok 142 - buffer overflow on step 6 + ok 143 - return value on step 7 + ok 144 - buffer overflow on step 7 + ok 145 - return value on step 8 + ok 146 - buffer overflow on step 8 + ok 147 - return value on step 9 + ok 148 - buffer overflow on step 9 + ok 149 - return value on step 10 + ok 150 - buffer overflow on step 10 + ok 151 - return value on step 11 + ok 152 - buffer overflow on step 11 + ok 153 - return value on step 12 + ok 154 - buffer overflow on step 12 + ok 155 - return value on step 13 + ok 156 - buffer overflow on step 13 + ok 157 - return value on step 14 + ok 158 - buffer overflow on step 14 + ok 159 - return value on step 15 + ok 160 - buffer overflow on step 15 + ok 161 - return value on step 16 + ok 162 - buffer overflow on step 16 + ok 163 - return value on step 17 + ok 164 - buffer overflow on step 17 + ok 165 - return value on step 18 + ok 166 - buffer overflow on step 18 + ok 167 - return value on step 19 + ok 168 - buffer overflow on step 19 + ok 169 - return value on step 20 + ok 170 - buffer overflow on step 20 + ok 171 - return value on step 21 + ok 172 - buffer overflow on step 21 + ok 173 - return value on step 22 + ok 174 - buffer overflow on step 22 + ok 175 - return value on step 23 + ok 176 - buffer overflow on step 23 + ok 177 - return value on step 24 + ok 178 - buffer overflow on step 24 + ok 179 - return value on step 25 + ok 180 - buffer overflow on step 25 + ok 181 - return value on step 26 + ok 182 - buffer overflow on step 26 + ok 183 - return value on step 27 + ok 184 - buffer overflow on step 27 + ok 185 - return value on step 28 + ok 186 - buffer overflow on step 28 + ok 187 - return value on step 29 + ok 188 - buffer overflow on step 29 + ok 189 - return value on step 30 + ok 190 - buffer overflow on step 30 + ok 191 - return value on step 31 + ok 192 - buffer overflow on step 31 + ok 193 - return value on step 32 + ok 194 - buffer overflow on step 32 + ok 195 - return value on step 33 + ok 196 - buffer overflow on step 33 + ok 197 - return value on step 34 + ok 198 - buffer overflow on step 34 + ok 199 - return value on step 35 + ok 200 - buffer overflow on step 35 + ok 201 - return value on step 36 + ok 202 - buffer overflow on step 36 + ok 203 - return value on step 37 + ok 204 - buffer overflow on step 37 + ok 205 - return value on step 38 + ok 206 - buffer overflow on step 38 + ok 207 - return value on step 39 + ok 208 - buffer overflow on step 39 + ok 209 - return value on step 40 + ok 210 - buffer overflow on step 40 + ok 211 - return value on step 41 + ok 212 - buffer overflow on step 41 + ok 213 - return value on step 42 + ok 214 - buffer overflow on step 42 + ok 215 - return value on step 43 + ok 216 - buffer overflow on step 43 + ok 217 - return value on step 44 + ok 218 - buffer overflow on step 44 + ok 219 - return value on step 45 + ok 220 - buffer overflow on step 45 + ok 221 - return value on step 46 + ok 222 - buffer overflow on step 46 + ok 223 - return value on step 47 + ok 224 - buffer overflow on step 47 + ok 225 - return value on step 48 + ok 226 - buffer overflow on step 48 + ok 227 - return value on step 49 + ok 228 - buffer overflow on step 49 + ok 229 - return value on step 50 + ok 230 - buffer overflow on step 50 + # *** test_format: done *** +ok 16 - subtests