From 282db823fc2ad17caa1c32b5ccd0781d3ec09663 Mon Sep 17 00:00:00 2001 From: Amichai Rothman Date: Mon, 29 Jun 2015 03:50:50 +0300 Subject: [PATCH] Simplify VarInt implementation. --- .../main/java/org/bitcoinj/core/VarInt.java | 106 +++++++++--------- .../java/org/bitcoinj/core/VarIntTest.java | 9 +- 2 files changed, 59 insertions(+), 56 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/core/VarInt.java b/core/src/main/java/org/bitcoinj/core/VarInt.java index 1e4597bc..ad7531ab 100644 --- a/core/src/main/java/org/bitcoinj/core/VarInt.java +++ b/core/src/main/java/org/bitcoinj/core/VarInt.java @@ -16,99 +16,97 @@ package org.bitcoinj.core; -import static org.bitcoinj.core.Utils.isLessThanUnsigned; -import static org.bitcoinj.core.Utils.isLessThanOrEqualToUnsigned; - /** - * A variable-length encoded integer using Satoshis encoding. + * A variable-length encoded unsigned integer using Satoshi's encoding (a.k.a. "CompactSize"). */ public class VarInt { public final long value; private final int originallyEncodedSize; + /** + * Constructs a new VarInt with the given unsigned long value. + * + * @param value the unsigned long value (beware widening conversion of negatives!) + */ public VarInt(long value) { this.value = value; originallyEncodedSize = getSizeInBytes(); } - // Bitcoin has its own varint format, known in the C++ source as "compact size". + /** + * Constructs a new VarInt with the value parsed from the specified offset of the given buffer. + * + * @param buf the buffer containing the value + * @param offset the offset of the value + */ public VarInt(byte[] buf, int offset) { int first = 0xFF & buf[offset]; if (first < 253) { - // 8 bits. - this.value = first; - originallyEncodedSize = 1; + value = first; + originallyEncodedSize = 1; // 1 data byte (8 bits) } else if (first == 253) { - // 16 bits. - this.value = (0xFF & buf[offset + 1]) | ((0xFF & buf[offset + 2]) << 8); - originallyEncodedSize = 3; + value = (0xFF & buf[offset + 1]) | ((0xFF & buf[offset + 2]) << 8); + originallyEncodedSize = 3; // 1 marker + 2 data bytes (16 bits) } else if (first == 254) { - // 32 bits. - this.value = Utils.readUint32(buf, offset + 1); - originallyEncodedSize = 5; + value = Utils.readUint32(buf, offset + 1); + originallyEncodedSize = 5; // 1 marker + 4 data bytes (32 bits) } else { - // 64 bits. - this.value = Utils.readUint32(buf, offset + 1) | (Utils.readUint32(buf, offset + 5) << 32); - originallyEncodedSize = 9; + value = Utils.readInt64(buf, offset + 1); + originallyEncodedSize = 9; // 1 marker + 8 data bytes (64 bits) } } /** - * Gets the number of bytes used to encode this originally if deserialized from a byte array. - * Otherwise returns the minimum encoded size + * Returns the original number of bytes used to encode the value if it was + * deserialized from a byte array, or the minimum encoded size if it was not. */ public int getOriginalSizeInBytes() { return originallyEncodedSize; } /** - * Gets the minimum encoded size of the value stored in this VarInt + * Returns the minimum encoded size of the value. */ public int getSizeInBytes() { return sizeOf(value); } /** - * Gets the minimum encoded size of the given value. + * Returns the minimum encoded size of the given unsigned long value. + * + * @param value the unsigned long value (beware widening conversion of negatives!) */ - public static int sizeOf(int value) { - if (value < 253) - return 1; - else if (value < 65536) - return 3; // 1 marker + 2 data bytes - return 5; // 1 marker + 4 data bytes + public static int sizeOf(long value) { + // if negative, it's actually a very large unsigned long value + if (value < 0) return 9; // 1 marker + 8 data bytes + if (value < 253) return 1; // 1 data byte + if (value <= 0xFFFFL) return 3; // 1 marker + 2 data bytes + if (value <= 0xFFFFFFFFL) return 5; // 1 marker + 4 data bytes + return 9; // 1 marker + 8 data bytes } /** - * Gets the minimum encoded size of the given value. + * Encodes the value into its minimal representation. + * + * @return the minimal encoded bytes of the value */ - public static int sizeOf(long value) { - if (isLessThanUnsigned(value, 253)) - return 1; - else if (isLessThanOrEqualToUnsigned(value, 0xFFFFL)) - return 3; // 1 marker + 2 data bytes - else if (isLessThanOrEqualToUnsigned(value, 0xFFFFFFFFL)) - return 5; // 1 marker + 4 data bytes - else - return 9; // 1 marker + 8 data bytes - } - public byte[] encode() { - if (isLessThanUnsigned(value, 253)) { - return new byte[]{(byte) value}; - } else if (isLessThanOrEqualToUnsigned(value, 0xFFFFL)) { - return new byte[]{(byte) 253, (byte) (value), (byte) (value >> 8)}; - } else if (isLessThanOrEqualToUnsigned(value, 0xFFFFFFFFL)) { - byte[] bytes = new byte[5]; - bytes[0] = (byte) 254; - Utils.uint32ToByteArrayLE(value, bytes, 1); - return bytes; - } else { - byte[] bytes = new byte[9]; - bytes[0] = (byte) 255; - Utils.uint32ToByteArrayLE(value, bytes, 1); - Utils.uint32ToByteArrayLE(value >>> 32, bytes, 5); - return bytes; + byte[] bytes; + switch (sizeOf(value)) { + case 1: + return new byte[]{(byte) value}; + case 3: + return new byte[]{(byte) 253, (byte) (value), (byte) (value >> 8)}; + case 5: + bytes = new byte[5]; + bytes[0] = (byte) 254; + Utils.uint32ToByteArrayLE(value, bytes, 1); + return bytes; + default: + bytes = new byte[9]; + bytes[0] = (byte) 255; + Utils.uint64ToByteArrayLE(value, bytes, 1); + return bytes; } } } diff --git a/core/src/test/java/org/bitcoinj/core/VarIntTest.java b/core/src/test/java/org/bitcoinj/core/VarIntTest.java index 35360268..5b59e551 100644 --- a/core/src/test/java/org/bitcoinj/core/VarIntTest.java +++ b/core/src/test/java/org/bitcoinj/core/VarIntTest.java @@ -20,14 +20,14 @@ import junit.framework.TestCase; public class VarIntTest extends TestCase { public void testBytes() throws Exception { - VarInt a = new VarInt(10); + VarInt a = new VarInt(10); // with widening conversion assertEquals(1, a.getSizeInBytes()); assertEquals(1, a.encode().length); assertEquals(10, new VarInt(a.encode(), 0).value); } public void testShorts() throws Exception { - VarInt a = new VarInt(64000); + VarInt a = new VarInt(64000); // with widening conversion assertEquals(3, a.getSizeInBytes()); assertEquals(3, a.encode().length); assertEquals(64000, new VarInt(a.encode(), 0).value); @@ -63,4 +63,9 @@ public class VarIntTest extends TestCase { byte[] bytes = a.encode(); assertEquals(0xCAFEBABEDEADBEEFL, new VarInt(bytes, 0).value); } + + public void testSizeOfNegativeInt() throws Exception { + // shouldn't normally be passed, but at least stay consistent (bug regression test) + assertEquals(VarInt.sizeOf(-1), new VarInt(-1).encode().length); + } }