From 1f8b98123c8340bb87c2d72931b89d6de7a7b272 Mon Sep 17 00:00:00 2001 From: Alexander Turenko Date: Thu, 24 Jan 2019 02:00:02 +0300 Subject: [PATCH] msgpack-lite: fix BigInteger pack / unpack Fixes #27. --- src/main/java/org/tarantool/MsgPackLite.java | 25 +++++++++++++ .../AbstractTarantoolConnectorIT.java | 35 +++++++++++++++---- .../org/tarantool/AbstractTarantoolOpsIT.java | 27 +++++++++++++- 3 files changed, 80 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/tarantool/MsgPackLite.java b/src/main/java/org/tarantool/MsgPackLite.java index 5b49f24e..e6450115 100644 --- a/src/main/java/org/tarantool/MsgPackLite.java +++ b/src/main/java/org/tarantool/MsgPackLite.java @@ -30,6 +30,10 @@ public class MsgPackLite { protected final int MAX_31BIT = 0x7fffffff; protected final long MAX_32BIT = 0xffffffffL; + protected final BigInteger BI_MIN_LONG = BigInteger.valueOf(Long.MIN_VALUE); + protected final BigInteger BI_MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE); + protected final BigInteger BI_MAX_64BIT = BigInteger.valueOf(2).pow(64).subtract(BigInteger.ONE); + //these values are from http://wiki.msgpack.org/display/MSGPACK/Format+specification protected final byte MP_NULL = (byte) 0xc0; protected final byte MP_FALSE = (byte) 0xc2; @@ -91,6 +95,23 @@ public void pack(Object item, OutputStream os) throws IOException { out.write(MP_DOUBLE); out.writeDouble((Double) item); } else { + if (item instanceof BigInteger) { + BigInteger value = (BigInteger) item; + boolean isPositive = value.signum() >= 0; + if (isPositive && value.compareTo(BI_MAX_64BIT) > 0 || + value.compareTo(BI_MIN_LONG) < 0) + throw new IllegalArgumentException( + "Cannot encode BigInteger as MsgPack: out of -2^63..2^64-1 range"); + if (isPositive && value.compareTo(BI_MAX_LONG) > 0) { + byte[] data = value.toByteArray(); + // data can contain leading zero bytes + for (int i = 0; i < data.length - 8; ++i) + assert data[i] == 0; + out.write(MP_UINT64); + out.write(data, data.length - 8, 8); + return; + } + } long value = item instanceof Code ? ((Code) item).getId() : ((Number) item).longValue(); if (value >= 0) { if (value <= MAX_7BIT) { @@ -238,6 +259,10 @@ public Object unpack(InputStream is) throws IOException { } else { //this is a little bit more tricky, since we don't have unsigned longs byte[] bytes = new byte[]{ + (byte) ((v >> 56) & 0xff), + (byte) ((v >> 48) & 0xff), + (byte) ((v >> 40) & 0xff), + (byte) ((v >> 32) & 0xff), (byte) ((v >> 24) & 0xff), (byte) ((v >> 16) & 0xff), (byte) ((v >> 8) & 0xff), diff --git a/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java b/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java index b8ef5a50..4c494f50 100644 --- a/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java +++ b/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.BeforeAll; import org.opentest4j.AssertionFailedError; +import java.math.BigInteger; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; @@ -180,17 +181,21 @@ protected TarantoolConnection openConnection() { } } - protected List consoleSelect(String spaceName, Object key) { - StringBuilder sb = new StringBuilder("box.space."); - sb.append(spaceName); - sb.append(":select{"); + private void appendBigInteger(StringBuilder sb, BigInteger value) { + sb.append(value); + sb.append(value.signum() >= 0 ? "ULL" : "LL"); + } + + private void appendKey(StringBuilder sb, Object key) { if (List.class.isAssignableFrom(key.getClass())) { - List parts = (List)key; + List parts = (List) key; for (int i = 0; i < parts.size(); i++) { if (i != 0) sb.append(", "); Object k = parts.get(i); - if (k.getClass().isAssignableFrom(String.class)) { + if (k instanceof BigInteger) { + appendBigInteger(sb, (BigInteger) k); + } else if (k instanceof String) { sb.append('\''); sb.append(k); sb.append('\''); @@ -198,9 +203,27 @@ protected List consoleSelect(String spaceName, Object key) { sb.append(k); } } + } else if (key instanceof BigInteger) { + appendBigInteger(sb, (BigInteger) key); } else { sb.append(key); } + } + + protected List consoleSelect(String spaceName, Object key) { + StringBuilder sb = new StringBuilder("box.space."); + sb.append(spaceName); + sb.append(":select{"); + appendKey(sb, key); + sb.append("}"); + return console.eval(sb.toString()); + } + + protected List consoleDelete(String spaceName, Object key) { + StringBuilder sb = new StringBuilder("box.space."); + sb.append(spaceName); + sb.append(":delete{"); + appendKey(sb, key); sb.append("}"); return console.eval(sb.toString()); } diff --git a/src/test/java/org/tarantool/AbstractTarantoolOpsIT.java b/src/test/java/org/tarantool/AbstractTarantoolOpsIT.java index efdb5407..5576a338 100644 --- a/src/test/java/org/tarantool/AbstractTarantoolOpsIT.java +++ b/src/test/java/org/tarantool/AbstractTarantoolOpsIT.java @@ -3,6 +3,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; +import java.math.BigInteger; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -61,6 +62,25 @@ public void testInsertSimple() { // Check it actually was inserted. checkTupleResult(consoleSelect(SPACE_NAME, 100), tup); + + // Leave the database in a clean state. + consoleDelete(SPACE_NAME, 100); + } + + @Test + public void testInsertBigInteger() { + BigInteger id = BigInteger.valueOf(2).pow(64).subtract(BigInteger.ONE); + + List tup = Arrays.asList(id, "big"); + List res = getOps().insert(SPACE_ID, tup); + + checkTupleResult(res, tup); + + // Check it actually was inserted. + checkTupleResult(consoleSelect(SPACE_NAME, id), tup); + + // Leave the database in a clean state. + consoleDelete(SPACE_NAME, id); } @Test @@ -70,8 +90,13 @@ public void testInsertMultiPart() { checkTupleResult(res, tup); + List id = Arrays.asList(100, "hundred"); + // Check it actually was inserted. - checkTupleResult(consoleSelect(MULTIPART_SPACE_NAME, Arrays.asList(100, "hundred")), tup); + checkTupleResult(consoleSelect(MULTIPART_SPACE_NAME, id), tup); + + // Leave the database in a clean state. + consoleDelete(MULTIPART_SPACE_NAME, id); } @Test