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> &mdash; 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> &mdash; 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>=, +, &amp;, |, ^, : </code>&mdash;
                 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: