diff --git a/doc/user/stored-procedures.xml b/doc/user/stored-procedures.xml index f142fc5deeb37d7c7f2e216dd96561c64ae9421d..6a6b5da7742656c930d7e27b62c6f18ff1ce0840 100644 --- a/doc/user/stored-procedures.xml +++ b/doc/user/stored-procedures.xml @@ -680,15 +680,12 @@ localhost> lua box.select_reverse_range(4, 1, 2, '1') string, low byte first, </member> <member><code>w</code> — converts Lua - variable to a BER-encoded - integer, and stores the integer in the resulting - string + integer to a BER-encoded integer, </member> <member><code>p</code> — stores the length - of the argument as a 4-byte integer, low byte first, - followed by the argument itself: a 4-byte integer, low - byte first, for integers, or a binary blob for - anything else, + of the argument as a BER-encoded integer + followed by the argument itself (a 4-bytes for integers (LE order) + and a binary blob for other types), </member> <member><code>=, +, &, |, ^, : </code>— stores the corresponding Tarantool UPDATE @@ -734,11 +731,9 @@ localhost> lua box.update(0, 0, "^p", 1, 4) </varlistentry> <varlistentry> - <term><emphasis role="lua">box.unpack(format, ...)</emphasis></term> + <term><emphasis role="lua">box.unpack(format, binary)</emphasis></term> <listitem><para> - Counterpart to <code>box.pack()</code>. Only supports - <code>'b'</code>, <code>'s'</code>, <code>'i'</code>, <code>'l'</code> format specifiers, and can be used - to convert packed integers to Lua numbers. + Counterpart to <code>box.pack()</code>. <bridgehead renderas="sect4">Example</bridgehead> <programlisting>localhost> lua tuple=box.replace(2, 0) --- @@ -751,6 +746,32 @@ localhost> lua box.unpack('i', tuple[0]) --- - 0 ... +localhost> lua box.unpack('bsil', box.pack('bsil', 255, 65535, 4294967295, tonumber64('18446744073709551615'))) +--- + - 255 + - 65535 + - 4294967295 + - 18446744073709551615 +... +localhost> lua num, str, num64 = box.unpack('ppp', box.pack('ppp', 666, 'string', tonumber64('666666666666666'))) +--- +... +localhost> lua print(box.unpack('i', num)); +--- +666 +... +localhost> lua print(str); +--- +string +... +localhost> lua print(box.unpack('l', num64)) +--- +666666666666666 +... +localhost> lua box.unpack('=p', box.pack('=p', 1, '666')) +--- + - 1 + - 666 </programlisting> </para></listitem> </varlistentry> diff --git a/include/pickle.h b/include/pickle.h index 83d2131eebcda9414d8af714d76f93e70a156126..d42b130f1190c3bbe425970bfc6960184779b66a 100644 --- a/include/pickle.h +++ b/include/pickle.h @@ -31,6 +31,7 @@ #include <stdbool.h> #include <util.h> +#include "exception.h" struct tbuf; @@ -53,35 +54,61 @@ u32 valid_tuple(struct tbuf *buf, u32 cardinality); size_t varint32_sizeof(u32); -inline static u32 load_varint32(void **data) +static inline u32 +load_varint32_s(void **data, size_t size) { + assert(data != NULL && *data != NULL); const u8 *b = *data; + if (unlikely(size < 1)) + tnt_raise(IllegalParams, :"varint is too short (expected 1+ bytes)"); + if (!(b[0] & 0x80)) { *data += 1; return (b[0] & 0x7f); } + + if (unlikely(size < 2)) + tnt_raise(IllegalParams, :"varint is too short (expected 2+ bytes)"); + if (!(b[1] & 0x80)) { *data += 2; return (b[0] & 0x7f) << 7 | (b[1] & 0x7f); } + + if (unlikely(size < 3)) + tnt_raise(IllegalParams, :"varint is too short (expected 3+ bytes)"); + if (!(b[2] & 0x80)) { *data += 3; return (b[0] & 0x7f) << 14 | (b[1] & 0x7f) << 7 | (b[2] & 0x7f); } + + if (unlikely(size < 4)) + tnt_raise(IllegalParams, :"varint is too short (expected 4+ bytes)"); + if (!(b[3] & 0x80)) { *data += 4; return (b[0] & 0x7f) << 21 | (b[1] & 0x7f) << 14 | (b[2] & 0x7f) << 7 | (b[3] & 0x7f); } + + if (unlikely(size < 5)) + tnt_raise(IllegalParams, :"varint is too short (expected 5+ bytes)"); + if (!(b[4] & 0x80)) { *data += 5; return (b[0] & 0x7f) << 28 | (b[1] & 0x7f) << 21 | (b[2] & 0x7f) << 14 | (b[3] & 0x7f) << 7 | (b[4] & 0x7f); } - assert(false); - return 0; + tnt_raise(IllegalParams, :"incorrect varint format"); +} + +static inline u32 +load_varint32(void **data) +{ + return load_varint32_s(data, 5); } /** diff --git a/src/lua/init.m b/src/lua/init.m index 527c67ce984a7ac9c82261af88eef413fb7b93c9..7606793855cbc6036165009d14fcd2bc156f05a1 100644 --- a/src/lua/init.m +++ b/src/lua/init.m @@ -168,6 +168,25 @@ static char format_to_opcode(char format) } } +/** + * Counterpart to @a format_to_opcode + */ +static char opcode_to_format(char opcode) +{ + switch (opcode) { + case 0: return '='; + case 1: return '+'; + case 2: return '&'; + case 3: return '^'; + case 4: return '|'; + case 5: return ':'; + case 6: return '#'; + case 7: return '!'; + case 8: return '-'; + default: return opcode; + } +} + /** * To use Tarantool/Box binary protocol primitives from Lua, we * need a way to pack Lua variables into a binary representation. @@ -334,70 +353,125 @@ luaL_pushnumber64(struct lua_State *L, uint64_t val) return 1; } + static int lbox_unpack(struct lua_State *L) { - const char *format = luaL_checkstring(L, 1); - /* first arg comes second */ - int i = 2; - int nargs = lua_gettop(L); - size_t size; - const char *str; + size_t format_size = 0; + const char *format = luaL_checklstring(L, 1, &format_size); + const char *f = format; + + size_t str_size = 0; + const u8 *str = (const u8 *) luaL_checklstring(L, 2, &str_size); + const u8 *end = str + str_size; + const u8 *s = str; + + int i = 0; + + char charbuf; u8 u8buf; u16 u16buf; u32 u32buf; - while (*format) { - if (i > nargs) - luaL_error(L, "box.unpack: argument count does not " - "match the format"); - switch (*format) { +#define CHECK_SIZE(cur) { if (unlikely((cur) >= end)) \ + luaL_error(L, "box.unpack('%c'): got %d bytes (expected: %d+)",\ + *f, (int) (end - str), (int) 1 + ((cur) - str));} + + while (*f) { + switch (*f) { case 'b': - str = lua_tolstring(L, i, &size); - if (str == NULL || size != sizeof(u8)) - luaL_error(L, "box.unpack('%c'): got %d bytes " - "(expected: 1)", *format, - (int) size); - u8buf = * (u8 *) str; + CHECK_SIZE(s); + u8buf = *(u8 *) s; lua_pushnumber(L, u8buf); + s++; break; case 's': - str = lua_tolstring(L, i, &size); - if (str == NULL || size != sizeof(u16)) - luaL_error(L, "box.unpack('%c'): got %d bytes " - "(expected: 2)", *format, - (int) size); - u16buf = * (u16 *) str; + CHECK_SIZE(s + 1); + u16buf = *(u16 *) s; lua_pushnumber(L, u16buf); + s += 2; break; case 'i': - str = lua_tolstring(L, i, &size); - if (str == NULL || size != sizeof(u32)) - luaL_error(L, "box.unpack('%c'): got %d bytes " - "(expected: 4)", *format, - (int) size); - u32buf = * (u32 *) str; + CHECK_SIZE(s + 3); + u32buf = *(u32 *) s; lua_pushnumber(L, u32buf); + s += 4; break; case 'l': - { - str = lua_tolstring(L, i, &size); - if (str == NULL || size != sizeof(u64)) - luaL_error(L, "box.unpack('%c'): got %d bytes " - "(expected: 8)", *format, - (int) size); + CHECK_SIZE(s + 7); GCcdata *cd = luaL_pushcdata(L, CTID_UINT64, 8); - *(uint64_t*)cdataptr(cd) = *(uint64_t*)str; + *(uint64_t*)cdataptr(cd) = *(uint64_t*) s; + s += 8; + break; + case 'w': + /* exception is thrown on error */ + u32buf = load_varint32_s((void *)&s, end - s); + lua_pushnumber(L, u32buf); + break; + case 'P': + case 'p': + /* exception is thrown on error */ + u32buf = load_varint32_s((void *)&s, end - s); + CHECK_SIZE(s + u32buf-1); + lua_pushlstring (L, (const char *) s, u32buf); + s += u32buf; + break; + case '=': + /* update tuple set foo = bar */ + case '+': + /* set field += val */ + case '-': + /* set field -= val */ + case '&': + /* set field & =val */ + case '|': + /* set field |= val */ + case '^': + /* set field ^= val */ + case ':': + /* splice */ + case '#': + /* delete field */ + case '!': + /* insert field */ + CHECK_SIZE(s + 4); + + /* field no */ + u32buf = *(u32 *) s; + + /* opcode */ + charbuf = *(char *) (s + 4); + charbuf = opcode_to_format(charbuf); + if (charbuf != *f) { + luaL_error(L, "box.unpack('%s'): " + "unexpected opcode: " + "offset %d, expected '%c'," + "found '%c'", + format, s - str, *f, charbuf); + } + + lua_pushnumber(L, u32buf); + s += 5; break; - } default: luaL_error(L, "box.unpack: unsupported pack " - "format specifier '%c'", *format); + "format specifier '%c'", *f); } i++; - format++; + f++; } - return i-2; + + assert(s <= end); + + if (s != end) { + luaL_error(L, "box.unpack('%s'): too many bytes: " + "unpacked %d, size %d", + format, s - str, str_size); + } + + return i; + +#undef CHECK_SIZE } /** diff --git a/src/pickle.m b/src/pickle.m index abdce773c0c7479848b1d55d8e576a742b1a71bf..fcc00d3600d2aad1dbe4f9176c21f2a1f6c7e25c 100644 --- a/src/pickle.m +++ b/src/pickle.m @@ -99,58 +99,15 @@ read_u(64) u32 read_varint32(struct tbuf *buf) { - u8 *b = buf->data; - int size = buf->size; + void *b = buf->data; + u32 ret = load_varint32_s(&b, buf->size); - if (size < 1) { - tnt_raise(IllegalParams, :"packet too short (expected 1 byte)"); - } - if (!(b[0] & 0x80)) { - buf->data += 1; - buf->capacity -= 1; - buf->size -= 1; - return (b[0] & 0x7f); - } - - if (size < 2) - tnt_raise(IllegalParams, :"packet too short (expected 2 bytes)"); - if (!(b[1] & 0x80)) { - buf->data += 2; - buf->capacity -= 2; - buf->size -= 2; - return (b[0] & 0x7f) << 7 | (b[1] & 0x7f); - } - if (size < 3) - tnt_raise(IllegalParams, :"packet too short (expected 3 bytes)"); - if (!(b[2] & 0x80)) { - buf->data += 3; - buf->capacity -= 3; - buf->size -= 3; - return (b[0] & 0x7f) << 14 | (b[1] & 0x7f) << 7 | (b[2] & 0x7f); - } - - if (size < 4) - tnt_raise(IllegalParams, :"packet too short (expected 4 bytes)"); - if (!(b[3] & 0x80)) { - buf->data += 4; - buf->capacity -= 4; - buf->size -= 4; - return (b[0] & 0x7f) << 21 | (b[1] & 0x7f) << 14 | - (b[2] & 0x7f) << 7 | (b[3] & 0x7f); - } - - if (size < 5) - tnt_raise(IllegalParams, :"packet too short (expected 5 bytes)"); - if (!(b[4] & 0x80)) { - buf->data += 5; - buf->capacity -= 5; - buf->size -= 5; - return (b[0] & 0x7f) << 28 | (b[1] & 0x7f) << 21 | - (b[2] & 0x7f) << 14 | (b[3] & 0x7f) << 7 | (b[4] & 0x7f); - } + size_t read = (b - buf->data); + buf->data = b; + buf->capacity -= read; + buf->size -= read; - tnt_raise(IllegalParams, :"incorrect BER format"); - return 0; + return ret; } u32 diff --git a/test/big/lua.result b/test/big/lua.result index 4f53097c83102f900109ec900e37bf3166acbe31..8153bc52eb3d1ae0159a02129185989ec43c3e76 100644 --- a/test/big/lua.result +++ b/test/big/lua.result @@ -99,21 +99,13 @@ lua num == tonumber64('18446744073709551615') --- - true ... -lua num,num1,num2 = box.unpack('lll', tu[0], tu[0], tu[0]) +lua num = box.unpack('l', tu[0]) --- ... lua num == tonumber64('18446744073709551615') --- - true ... -lua num1 == tonumber64('18446744073709551615') ---- - - true -... -lua num2 == tonumber64('18446744073709551615') ---- - - true -... lua box.space[8]:truncate() --- ... diff --git a/test/big/lua.test b/test/big/lua.test index dcbe51601e084594127696dbd47911f28e2ec7cb..115a30e68e799fb09ca634ba9b44e1b79e2b2324 100644 --- a/test/big/lua.test +++ b/test/big/lua.test @@ -52,10 +52,8 @@ exec admin "lua num = box.unpack('l', tu[0])" exec admin "lua print(num)" exec admin "lua type(num) == 'cdata'" exec admin "lua num == tonumber64('18446744073709551615')" -exec admin "lua num,num1,num2 = box.unpack('lll', tu[0], tu[0], tu[0])" +exec admin "lua num = box.unpack('l', tu[0])" exec admin "lua num == tonumber64('18446744073709551615')" -exec admin "lua num1 == tonumber64('18446744073709551615')" -exec admin "lua num2 == tonumber64('18446744073709551615')" exec admin "lua box.space[8]:truncate()" # diff --git a/test/box/lua.result b/test/box/lua.result index e57c46d84d83a338f3194d038f052c2da151955d..439b4f01b90e2a4f0537ea8fa072a5015001429e 100644 --- a/test/box/lua.result +++ b/test/box/lua.result @@ -119,6 +119,52 @@ lua print(box.unpack('l', 'Test ok.')) --- 3344889333436081492 ... +lua box.unpack('bsil', box.pack('bsil', 255, 65535, 4294967295, tonumber64('18446744073709551615'))) +--- + - 255 + - 65535 + - 4294967295 + - 18446744073709551615 +... +lua box.unpack('www', box.pack('www', 255, 65535, 4294967295)) +--- + - 255 + - 65535 + - 4294967295 +... +lua box.unpack('ppp', box.pack('ppp', 'one', 'two', 'three')) +--- + - one + - two + - three +... +lua num, str, num64 = box.unpack('ppp', box.pack('ppp', 666, 'string', tonumber64('666666666666666'))) +--- +... +lua print(box.unpack('i', num), str, box.unpack('l', num64)) +--- +666string666666666666666 +... +lua box.unpack('=p', box.pack('=p', 1, '666')) +--- + - 1 + - 666 +... +lua box.unpack('','') +--- +... +lua box.unpack('ii', box.pack('i', 1)) +--- +error: 'box.unpack(''i''): got 4 bytes (expected: 8+)' +... +lua box.unpack('i', box.pack('ii', 1, 1)) +--- +error: 'box.unpack(''i''): too many bytes: unpacked 4, size 8' +... +lua box.unpack('+p', box.pack('=p', 1, '666')) +--- +error: 'box.unpack(''+p''): unexpected opcode: offset 0, expected ''+'',found ''=''' +... lua box.process(13, box.pack('iiippp', 0, 1, 3, 1, 'testing', 'lua rocks')) --- - 1: {'testing', 'lua rocks'} diff --git a/test/box/lua.test b/test/box/lua.test index 5b869eb5ed4ad752d8263ce2f31ef8cc621cc488..e7f7765bc32b6c6128800944683d988cd6a75a5f 100644 --- a/test/box/lua.test +++ b/test/box/lua.test @@ -29,6 +29,17 @@ exec admin "lua print(box.unpack('b', 'T'))" exec admin "lua print(box.unpack('s', 'Te'))" exec admin "lua print(box.unpack('i', 'Test'))" exec admin "lua print(box.unpack('l', 'Test ok.'))" +exec admin "lua box.unpack('bsil', box.pack('bsil', 255, 65535, 4294967295, tonumber64('18446744073709551615')))" +exec admin "lua box.unpack('www', box.pack('www', 255, 65535, 4294967295))" +exec admin "lua box.unpack('ppp', box.pack('ppp', 'one', 'two', 'three'))" +exec admin "lua num, str, num64 = box.unpack('ppp', box.pack('ppp', 666, 'string', tonumber64('666666666666666')))" +exec admin "lua print(box.unpack('i', num), str, box.unpack('l', num64))" +exec admin "lua box.unpack('=p', box.pack('=p', 1, '666'))" +exec admin "lua box.unpack('','')" +exec admin "lua box.unpack('ii', box.pack('i', 1))" +exec admin "lua box.unpack('i', box.pack('ii', 1, 1))" +exec admin "lua box.unpack('+p', box.pack('=p', 1, '666'))" + # Test the low-level box.process() call, which takes a binary packet # and passes it to box for execution. # insert: