diff --git a/ethereum/rlp/src/jmh/java/tech/pegasys/pantheon/ethereum/rlp/RLPBench.java b/ethereum/rlp/src/jmh/java/tech/pegasys/pantheon/ethereum/rlp/RLPBench.java
index 48389caa0f..82b1c3b92b 100644
--- a/ethereum/rlp/src/jmh/java/tech/pegasys/pantheon/ethereum/rlp/RLPBench.java
+++ b/ethereum/rlp/src/jmh/java/tech/pegasys/pantheon/ethereum/rlp/RLPBench.java
@@ -12,6 +12,7 @@
*/
package tech.pegasys.pantheon.ethereum.rlp;
+import tech.pegasys.pantheon.ethereum.rlp.util.RLPTestUtil;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.util.ArrayList;
@@ -62,16 +63,16 @@ public class RLPBench {
@Setup(Level.Trial)
public void prepare() {
toEncode = generate(depth, width, size);
- toDecode = RLP.encode(toEncode);
+ toDecode = RLPTestUtil.encode(toEncode);
}
@Benchmark
public BytesValue getBenchmarkEncoding() {
- return RLP.encode(toEncode);
+ return RLPTestUtil.encode(toEncode);
}
@Benchmark
public Object getBenchmarkDecoding() {
- return RLP.decode(toDecode);
+ return RLPTestUtil.decode(toDecode);
}
}
diff --git a/ethereum/rlp/src/main/java/tech/pegasys/pantheon/ethereum/rlp/RLP.java b/ethereum/rlp/src/main/java/tech/pegasys/pantheon/ethereum/rlp/RLP.java
index 993d6b8480..af0015458d 100644
--- a/ethereum/rlp/src/main/java/tech/pegasys/pantheon/ethereum/rlp/RLP.java
+++ b/ethereum/rlp/src/main/java/tech/pegasys/pantheon/ethereum/rlp/RLP.java
@@ -22,8 +22,6 @@ import tech.pegasys.pantheon.ethereum.rlp.RLPDecodingHelpers.Kind;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.bytes.MutableBytesValue;
-import java.util.ArrayList;
-import java.util.List;
import java.util.function.Consumer;
import io.vertx.core.buffer.Buffer;
@@ -81,65 +79,6 @@ public abstract class RLP {
return new BytesValueRLPInput(BytesValue.wrapBuffer(buffer, offset), false, false);
}
- /**
- * Fully decodes a RLP encoded value.
- *
- *
This method is mostly intended for testing as it is often more convenient and
- * efficient to use a {@link RLPInput} (through {@link #input(BytesValue)}) instead.
- *
- * @param value The RLP encoded value to decode.
- * @return The output of decoding {@code value}. It will be either directly a {@link BytesValue},
- * or a list whose elements are either {@link BytesValue}, or similarly composed sub-lists.
- * @throws RLPException if {@code value} is not a properly formed RLP encoding.
- */
- public static Object decode(final BytesValue value) {
- return decode(input(value));
- }
-
- private static Object decode(final RLPInput in) {
- if (!in.nextIsList()) {
- return in.readBytesValue();
- }
-
- final int size = in.enterList();
- final List l = new ArrayList<>(size);
- for (int i = 0; i < size; i++) l.add(decode(in));
- in.leaveList();
- return l;
- }
-
- /**
- * Fully RLP encode an object consisting of recursive lists of {@link BytesValue}.
- *
- * This method is mostly intended for testing as it is often more convenient and
- * efficient to use a {@link RLPOutput} (through {@link #encode(Consumer)} for instance) instead.
- *
- * @param obj An object that must be either directly a {@link BytesValue}, or a list whose
- * elements are either {@link BytesValue}, or similarly composed sub-lists.
- * @return The RLP encoding corresponding to {@code obj}.
- * @throws IllegalArgumentException if {@code obj} is not a valid input (not entirely composed
- * from lists and {@link BytesValue}).
- */
- public static BytesValue encode(final Object obj) {
- final BytesValueRLPOutput out = new BytesValueRLPOutput();
- encode(obj, out);
- return out.encoded();
- }
-
- private static void encode(final Object obj, final RLPOutput out) {
- if (obj instanceof BytesValue) {
- out.writeBytesValue((BytesValue) obj);
- } else if (obj instanceof List) {
- final List> l = (List>) obj;
- out.startList();
- for (final Object o : l) encode(o, out);
- out.endList();
- } else {
- throw new IllegalArgumentException(
- format("Invalid input type %s for RLP encoding", obj.getClass()));
- }
- }
-
/**
* Creates a {@link RLPOutput}, pass it to the provided consumer for writing, and then return the
* RLP encoded result of that writing.
diff --git a/ethereum/rlp/src/main/java/tech/pegasys/pantheon/ethereum/rlp/RLPDecodingHelpers.java b/ethereum/rlp/src/main/java/tech/pegasys/pantheon/ethereum/rlp/RLPDecodingHelpers.java
index 9c6c723537..d016caaf27 100644
--- a/ethereum/rlp/src/main/java/tech/pegasys/pantheon/ethereum/rlp/RLPDecodingHelpers.java
+++ b/ethereum/rlp/src/main/java/tech/pegasys/pantheon/ethereum/rlp/RLPDecodingHelpers.java
@@ -66,8 +66,19 @@ class RLPDecodingHelpers {
}
/** Read from the provided offset a size of the provided length, assuming this is enough bytes. */
- static int extractSizeFromLong(
+ static int extractSizeFromLongItem(
final LongUnaryOperator getter, final long offset, final int sizeLength) {
+ String oversizedErrorMessage =
+ "RLP item at offset "
+ + offset
+ + " with size value consuming "
+ + sizeLength
+ + " bytes exceeds max supported size of "
+ + Integer.MAX_VALUE;
+ if (sizeLength > 4) {
+ throw new RLPException(oversizedErrorMessage);
+ }
+
long res = 0;
int shift = 0;
for (int i = 0; i < sizeLength; i++) {
@@ -77,9 +88,7 @@ class RLPDecodingHelpers {
try {
return Math.toIntExact(res);
} catch (final ArithmeticException e) {
- final String msg =
- "unable to extract size from long at offset " + offset + ", sizeLen=" + sizeLength;
- throw new RLPException(msg, e);
+ throw new RLPException(oversizedErrorMessage, e);
}
}
@@ -139,7 +148,7 @@ class RLPDecodingHelpers {
throw new MalformedRLPInputException("Malformed RLP item: size of payload has leading zeros");
}
- final int res = RLPDecodingHelpers.extractSizeFromLong(byteGetter, item + 1, sizeLength);
+ final int res = RLPDecodingHelpers.extractSizeFromLongItem(byteGetter, item + 1, sizeLength);
// We should not have had the size written separately if it was less than 56 bytes long.
if (res < 56) {
@@ -166,7 +175,15 @@ class RLPDecodingHelpers {
/** @return the size of the byte string holding the rlp-encoded value and metadata */
int getEncodedSize() {
- return Math.toIntExact(elementEnd() - elementStart + 1);
+ long encodedSize = elementEnd() - elementStart + 1;
+ try {
+ return Math.toIntExact(encodedSize);
+ } catch (ArithmeticException e) {
+ String errorMessage =
+ String.format(
+ "RLP item exceeds max supported size of %d: %d", Integer.MAX_VALUE, encodedSize);
+ throw new RLPException(errorMessage, e);
+ }
}
/**
diff --git a/ethereum/rlp/src/test/java/tech/pegasys/pantheon/ethereum/rlp/BytesValueRLPInputTest.java b/ethereum/rlp/src/test/java/tech/pegasys/pantheon/ethereum/rlp/BytesValueRLPInputTest.java
index a08ac0701d..a6fff6dc17 100644
--- a/ethereum/rlp/src/test/java/tech/pegasys/pantheon/ethereum/rlp/BytesValueRLPInputTest.java
+++ b/ethereum/rlp/src/test/java/tech/pegasys/pantheon/ethereum/rlp/BytesValueRLPInputTest.java
@@ -12,13 +12,17 @@
*/
package tech.pegasys.pantheon.ethereum.rlp;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
import tech.pegasys.pantheon.util.bytes.BytesValue;
+import tech.pegasys.pantheon.util.bytes.BytesValues;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+
+import org.assertj.core.api.AssertionsForClassTypes;
import org.junit.Test;
public class BytesValueRLPInputTest {
@@ -36,104 +40,104 @@ public class BytesValueRLPInputTest {
@Test
public void empty() {
final RLPInput in = RLP.input(BytesValue.EMPTY);
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isTrue();
}
@Test
public void singleByte() {
final RLPInput in = RLP.input(h("0x01"));
- assertFalse(in.isDone());
- assertEquals((byte) 1, in.readByte());
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.readByte()).isEqualTo((byte) 1);
+ assertThat(in.isDone()).isTrue();
}
@Test
public void singleByteLowerBoundary() {
final RLPInput in = RLP.input(h("0x00"));
- assertFalse(in.isDone());
- assertEquals((byte) 0, in.readByte());
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.readByte()).isEqualTo((byte) 0);
+ assertThat(in.isDone()).isTrue();
}
@Test
public void singleByteUpperBoundary() {
final RLPInput in = RLP.input(h("0x7f"));
- assertFalse(in.isDone());
- assertEquals((byte) 0x7f, in.readByte());
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.readByte()).isEqualTo((byte) 0x7f);
+ assertThat(in.isDone()).isTrue();
}
@Test
public void singleShortElement() {
final RLPInput in = RLP.input(h("0x81FF"));
- assertFalse(in.isDone());
- assertEquals((byte) 0xFF, in.readByte());
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.readByte()).isEqualTo((byte) 0xFF);
+ assertThat(in.isDone()).isTrue();
}
@Test
public void singleBarelyShortElement() {
final RLPInput in = RLP.input(h("0xb7" + times("2b", 55)));
- assertFalse(in.isDone());
- assertEquals(h(times("2b", 55)), in.readBytesValue());
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.readBytesValue()).isEqualTo(h(times("2b", 55)));
+ assertThat(in.isDone()).isTrue();
}
@Test
public void singleBarelyLongElement() {
final RLPInput in = RLP.input(h("0xb838" + times("2b", 56)));
- assertFalse(in.isDone());
- assertEquals(h(times("2b", 56)), in.readBytesValue());
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.readBytesValue()).isEqualTo(h(times("2b", 56)));
+ assertThat(in.isDone()).isTrue();
}
@Test
public void singleLongElement() {
final RLPInput in = RLP.input(h("0xb908c1" + times("3c", 2241)));
- assertFalse(in.isDone());
- assertEquals(h(times("3c", 2241)), in.readBytesValue());
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.readBytesValue()).isEqualTo(h(times("3c", 2241)));
+ assertThat(in.isDone()).isTrue();
}
@Test
public void singleLongElementBoundaryCase_1() {
final RLPInput in = RLP.input(h("0xb8ff" + times("3c", 255)));
- assertFalse(in.isDone());
- assertEquals(h(times("3c", 255)), in.readBytesValue());
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.readBytesValue()).isEqualTo(h(times("3c", 255)));
+ assertThat(in.isDone()).isTrue();
}
@Test
public void singleLongElementBoundaryCase_2() {
final RLPInput in = RLP.input(h("0xb90100" + times("3c", 256)));
- assertFalse(in.isDone());
- assertEquals(h(times("3c", 256)), in.readBytesValue());
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.readBytesValue()).isEqualTo(h(times("3c", 256)));
+ assertThat(in.isDone()).isTrue();
}
@Test
public void singleLongElementBoundaryCase_3() {
final RLPInput in = RLP.input(h("0xb9ffff" + times("3c", 65535)));
- assertFalse(in.isDone());
- assertEquals(h(times("3c", 65535)), in.readBytesValue());
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.readBytesValue()).isEqualTo(h(times("3c", 65535)));
+ assertThat(in.isDone()).isTrue();
}
@Test
public void singleLongElementBoundaryCase_4() {
final RLPInput in = RLP.input(h("0xba010000" + times("3c", 65536)));
- assertFalse(in.isDone());
- assertEquals(h(times("3c", 65536)), in.readBytesValue());
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.readBytesValue()).isEqualTo(h(times("3c", 65536)));
+ assertThat(in.isDone()).isTrue();
}
@Test
public void singleLongElementBoundaryCase_5() {
final RLPInput in = RLP.input(h("0xbaffffff" + times("3c", 16777215)));
- assertFalse(in.isDone());
- assertEquals(h(times("3c", 16777215)), in.readBytesValue());
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.readBytesValue()).isEqualTo(h(times("3c", 16777215)));
+ assertThat(in.isDone()).isTrue();
}
@Test
@@ -141,9 +145,9 @@ public class BytesValueRLPInputTest {
// A RLPx Frame can have a maximum length of 0xffffff, so boundary above this
// will be not be real world scenarios.
final RLPInput in = RLP.input(h("0xbb01000000" + times("3c", 16777216)));
- assertFalse(in.isDone());
- assertEquals(h(times("3c", 16777216)), in.readBytesValue());
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.readBytesValue()).isEqualTo(h(times("3c", 16777216)));
+ assertThat(in.isDone()).isTrue();
}
@Test
@@ -168,9 +172,9 @@ public class BytesValueRLPInputTest {
private void assertLongScalar(final long expected, final BytesValue toTest) {
final RLPInput in = RLP.input(toTest);
- assertFalse(in.isDone());
- assertEquals(expected, in.readLongScalar());
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.readLongScalar()).isEqualTo(expected);
+ assertThat(in.isDone()).isTrue();
}
@Test
@@ -186,128 +190,136 @@ public class BytesValueRLPInputTest {
private void assertIntScalar(final int expected, final BytesValue toTest) {
final RLPInput in = RLP.input(toTest);
- assertFalse(in.isDone());
- assertEquals(expected, in.readIntScalar());
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.readIntScalar()).isEqualTo(expected);
+ assertThat(in.isDone()).isTrue();
}
@Test
public void emptyList() {
final RLPInput in = RLP.input(h("0xc0"));
- assertFalse(in.isDone());
- assertEquals(0, in.enterList());
- assertFalse(in.isDone());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.enterList()).isEqualTo(0);
+ assertThat(in.isDone()).isFalse();
in.leaveList();
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isTrue();
+ }
+
+ @Test
+ public void emptyByteString() {
+ final RLPInput in = RLP.input(h("0x80"));
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.readBytesValue()).isEqualTo(BytesValue.EMPTY);
+ assertThat(in.isDone()).isTrue();
}
@Test
public void simpleShortList() {
final RLPInput in = RLP.input(h("0xc22c3b"));
- assertFalse(in.isDone());
- assertEquals(2, in.enterList());
- assertEquals((byte) 0x2c, in.readByte());
- assertEquals((byte) 0x3b, in.readByte());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.enterList()).isEqualTo(2);
+ assertThat(in.readByte()).isEqualTo((byte) 0x2c);
+ assertThat(in.readByte()).isEqualTo((byte) 0x3b);
in.leaveList();
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isTrue();
}
@Test
public void simpleIntBeforeShortList() {
final RLPInput in = RLP.input(h("0x02c22c3b"));
- assertFalse(in.isDone());
- assertEquals(2, in.readIntScalar());
- assertEquals(2, in.enterList());
- assertEquals((byte) 0x2c, in.readByte());
- assertEquals((byte) 0x3b, in.readByte());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.readIntScalar()).isEqualTo(2);
+ assertThat(in.enterList()).isEqualTo(2);
+ assertThat(in.readByte()).isEqualTo((byte) 0x2c);
+ assertThat(in.readByte()).isEqualTo((byte) 0x3b);
in.leaveList();
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isTrue();
}
@Test
public void simpleShortListUpperBoundary() {
final RLPInput in = RLP.input(h("0xf7" + times("3c", 55)));
- assertFalse(in.isDone());
- assertEquals(55, in.enterList());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.enterList()).isEqualTo(55);
for (int i = 0; i < 55; i++) {
- assertEquals((byte) 0x3c, in.readByte());
+ assertThat(in.readByte()).isEqualTo((byte) 0x3c);
}
in.leaveList();
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isTrue();
}
@Test
public void simpleLongListLowerBoundary() {
final RLPInput in = RLP.input(h("0xf838" + times("3c", 56)));
- assertFalse(in.isDone());
- assertEquals(56, in.enterList());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.enterList()).isEqualTo(56);
for (int i = 0; i < 56; i++) {
- assertEquals((byte) 0x3c, in.readByte());
+ assertThat(in.readByte()).isEqualTo((byte) 0x3c);
}
in.leaveList();
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isTrue();
}
@Test
public void simpleLongListBoundaryCase_1() {
final RLPInput in = RLP.input(h("0xf8ff" + times("3c", 255)));
- assertFalse(in.isDone());
- assertEquals(255, in.enterList());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.enterList()).isEqualTo(255);
for (int i = 0; i < 255; i++) {
- assertEquals((byte) 0x3c, in.readByte());
+ assertThat(in.readByte()).isEqualTo((byte) 0x3c);
}
in.leaveList();
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isTrue();
}
@Test
public void simpleLongListBoundaryCase_2() {
final RLPInput in = RLP.input(h("0xf90100" + times("3c", 256)));
- assertFalse(in.isDone());
- assertEquals(256, in.enterList());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.enterList()).isEqualTo(256);
for (int i = 0; i < 256; i++) {
- assertEquals((byte) 0x3c, in.readByte());
+ assertThat(in.readByte()).isEqualTo((byte) 0x3c);
}
in.leaveList();
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isTrue();
}
@Test
public void simpleLongListBoundaryCase_3() {
final RLPInput in = RLP.input(h("0xf9ffff" + times("3c", 65535)));
- assertFalse(in.isDone());
- assertEquals(65535, in.enterList());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.enterList()).isEqualTo(65535);
for (int i = 0; i < 65535; i++) {
- assertEquals((byte) 0x3c, in.readByte());
+ assertThat(in.readByte()).isEqualTo((byte) 0x3c);
}
in.leaveList();
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isTrue();
}
@Test
public void simpleLongListBoundaryCase_4() {
final RLPInput in = RLP.input(h("0xfa010000" + times("3c", 65536)));
- assertFalse(in.isDone());
- assertEquals(65536, in.enterList());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.enterList()).isEqualTo(65536);
for (int i = 0; i < 65536; i++) {
- assertEquals((byte) 0x3c, in.readByte());
+ assertThat(in.readByte()).isEqualTo((byte) 0x3c);
}
in.leaveList();
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isTrue();
}
@Test
public void simpleLongListBoundaryCase_5() {
final RLPInput in = RLP.input(h("0xfaffffff" + times("3c", 16777215)));
- assertFalse(in.isDone());
- assertEquals(16777215, in.enterList());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.enterList()).isEqualTo(16777215);
for (int i = 0; i < 16777215; i++) {
- assertEquals((byte) 0x3c, in.readByte());
+ assertThat(in.readByte()).isEqualTo((byte) 0x3c);
}
in.leaveList();
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isTrue();
}
@Test
@@ -315,39 +327,39 @@ public class BytesValueRLPInputTest {
// A RLPx Frame can have a maximum length of 0xffffff, so boundary above this
// will be not be real world scenarios.
final RLPInput in = RLP.input(h("0xfb01000000" + times("3c", 16777216)));
- assertFalse(in.isDone());
- assertEquals(16777216, in.enterList());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.enterList()).isEqualTo(16777216);
for (int i = 0; i < 16777216; i++) {
- assertEquals((byte) 0x3c, in.readByte());
+ assertThat(in.readByte()).isEqualTo((byte) 0x3c);
}
in.leaveList();
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isTrue();
}
@Test
public void simpleListwithBytesValue() {
final RLPInput in = RLP.input(h("0xc28180"));
- assertFalse(in.isDone());
- assertEquals(1, in.enterList());
- assertEquals(h("0x80"), in.readBytesValue());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.enterList()).isEqualTo(1);
+ assertThat(in.readBytesValue()).isEqualTo(h("0x80"));
in.leaveList();
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isTrue();
}
@Test
public void simpleNestedList() {
final RLPInput in = RLP.input(h("0xc52cc203123b"));
- assertFalse(in.isDone());
- assertEquals(3, in.enterList());
- assertEquals((byte) 0x2c, in.readByte());
- assertEquals(2, in.enterList());
- assertEquals((byte) 0x03, in.readByte());
- assertEquals((byte) 0x12, in.readByte());
+ assertThat(in.isDone()).isFalse();
+ assertThat(in.enterList()).isEqualTo(3);
+ assertThat(in.readByte()).isEqualTo((byte) 0x2c);
+ assertThat(in.enterList()).isEqualTo(2);
+ assertThat(in.readByte()).isEqualTo((byte) 0x03);
+ assertThat(in.readByte()).isEqualTo((byte) 0x12);
in.leaveList();
- assertEquals((byte) 0x3b, in.readByte());
+ assertThat(in.readByte()).isEqualTo((byte) 0x3b);
in.leaveList();
- assertTrue(in.isDone());
+ assertThat(in.isDone()).isTrue();
}
@Test
@@ -355,19 +367,19 @@ public class BytesValueRLPInputTest {
// Test null value
final BytesValue nullValue = h("0x80");
final RLPInput nv = RLP.input(nullValue);
- assertEquals(nv.raw(), nv.readAsRlp().raw());
+ assertThat(nv.readAsRlp().raw()).isEqualTo(nv.raw());
nv.reset();
- assertTrue(nv.nextIsNull());
- assertTrue(nv.readAsRlp().nextIsNull());
+ assertThat(nv.nextIsNull()).isTrue();
+ assertThat(nv.readAsRlp().nextIsNull()).isTrue();
// Test empty list
final BytesValue emptyList = h("0xc0");
final RLPInput el = RLP.input(emptyList);
- assertEquals(emptyList, el.readAsRlp().raw());
+ assertThat(el.readAsRlp().raw()).isEqualTo(emptyList);
el.reset();
- assertEquals(0, el.readAsRlp().enterList());
+ assertThat(el.readAsRlp().enterList()).isEqualTo(0);
el.reset();
- assertEquals(0, el.enterList());
+ assertThat(el.enterList()).isEqualTo(0);
final BytesValue nestedList =
RLP.encode(
@@ -388,16 +400,16 @@ public class BytesValueRLPInputTest {
final RLPInput nl = RLP.input(nestedList);
final RLPInput compare = nl.readAsRlp();
- assertEquals(nl.raw(), compare.raw());
+ assertThat(compare.raw()).isEqualTo(nl.raw());
nl.reset();
nl.enterList();
nl.skipNext(); // 0x01
// Read the next byte that's inside the list, extract it as raw RLP and assert it's its own
// representation.
- assertEquals(h("0x02"), nl.readAsRlp().raw());
+ assertThat(nl.readAsRlp().raw()).isEqualTo(h("0x02"));
// Extract the inner list.
- assertEquals(h("0xc51112c22122"), nl.readAsRlp().raw());
+ assertThat(nl.readAsRlp().raw()).isEqualTo(h("0xc51112c22122"));
// Reset
nl.reset();
nl.enterList();
@@ -408,29 +420,29 @@ public class BytesValueRLPInputTest {
nl.skipNext();
// Assert on the inner list of depth 3.
- assertEquals(h("0xc22122"), nl.readAsRlp().raw());
+ assertThat(nl.readAsRlp().raw()).isEqualTo(h("0xc22122"));
}
@Test
public void raw() {
final BytesValue initial = h("0xc80102c51112c22122");
final RLPInput in = RLP.input(initial);
- assertEquals(initial, in.raw());
+ assertThat(in.raw()).isEqualTo(initial);
}
@Test
public void reset() {
final RLPInput in = RLP.input(h("0xc80102c51112c22122"));
for (int i = 0; i < 100; i++) {
- assertEquals(3, in.enterList());
- assertEquals(0x01, in.readByte());
- assertEquals(0x02, in.readByte());
- assertEquals(3, in.enterList());
- assertEquals(0x11, in.readByte());
- assertEquals(0x12, in.readByte());
- assertEquals(2, in.enterList());
- assertEquals(0x21, in.readByte());
- assertEquals(0x22, in.readByte());
+ assertThat(in.enterList()).isEqualTo(3);
+ assertThat(in.readByte()).isEqualTo((byte) 0x01);
+ assertThat(in.readByte()).isEqualTo((byte) 0x02);
+ assertThat(in.enterList()).isEqualTo(3);
+ assertThat(in.readByte()).isEqualTo((byte) 0x11);
+ assertThat(in.readByte()).isEqualTo((byte) 0x12);
+ assertThat(in.enterList()).isEqualTo(2);
+ assertThat(in.readByte()).isEqualTo((byte) 0x21);
+ assertThat(in.readByte()).isEqualTo((byte) 0x22);
in.reset();
}
}
@@ -438,16 +450,16 @@ public class BytesValueRLPInputTest {
@Test
public void ignoreListTail() {
final RLPInput in = RLP.input(h("0xc80102c51112c22122"));
- assertEquals(3, in.enterList());
- assertEquals(0x01, in.readByte());
+ assertThat(in.enterList()).isEqualTo(3);
+ assertThat(in.readByte()).isEqualTo((byte) 0x01);
in.leaveList(true);
}
@Test
public void leaveListEarly() {
final RLPInput in = RLP.input(h("0xc80102c51112c22122"));
- assertEquals(3, in.enterList());
- assertEquals(0x01, in.readByte());
+ assertThat(in.enterList()).isEqualTo(3);
+ assertThat(in.readByte()).isEqualTo((byte) 0x01);
assertThatThrownBy(() -> in.leaveList(false))
.isInstanceOf(RLPException.class)
.hasMessageStartingWith("Not at the end of the current list");
@@ -472,10 +484,10 @@ public class BytesValueRLPInputTest {
final BytesValue correctBytes =
h(
"0xB8380102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738");
- assertEquals(
- RLP.input(correctBytes).readBytesValue(),
- h(
- "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738"));
+ assertThat(
+ h(
+ "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738"))
+ .isEqualTo(RLP.input(correctBytes).readBytesValue());
// Encode same value, but use 2 bytes to represent the size, and pad size value with leading
// zeroes
@@ -494,10 +506,10 @@ public class BytesValueRLPInputTest {
final BytesValue correctBytes =
h(
"0xB70102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637");
- assertEquals(
- RLP.input(correctBytes).readBytesValue(),
- h(
- "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637"));
+ assertThat(
+ h(
+ "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637"))
+ .isEqualTo(RLP.input(correctBytes).readBytesValue());
// Encode same value using long format
final BytesValue incorrectBytes =
@@ -508,4 +520,115 @@ public class BytesValueRLPInputTest {
.hasRootCauseInstanceOf(MalformedRLPInputException.class)
.hasMessageContaining("written as a long item, but size 55 < 56 bytes");
}
+
+ @Test
+ public void rlpItemSizeHoldsMaxValue() {
+ // Size value encode max positive int. So, size is decoded, but
+ // RLP is malformed because the actual payload is not present
+ AssertionsForClassTypes.assertThatThrownBy(() -> RLP.input(h("0xBB7FFFFFFF")).readBytesValue())
+ .isInstanceOf(CorruptedRLPInputException.class)
+ .hasMessageContaining("payload should start at offset 5 but input has only 5 bytes");
+ }
+
+ @Test
+ public void rlpItemSizeOverflowsSignedInt() {
+ // Size value encoded in 4 bytes but exceeds max positive int value
+ AssertionsForClassTypes.assertThatThrownBy(() -> RLP.input(h("0xBB80000000")))
+ .isInstanceOf(RLPException.class)
+ .hasMessageContaining(
+ "RLP item at offset 1 with size value consuming 4 bytes exceeds max supported size of 2147483647");
+ }
+
+ @Test
+ public void rlpItemSizeOverflowsInt() {
+ // Size value is encoded with 5 bytes - overflowing int
+ AssertionsForClassTypes.assertThatThrownBy(() -> RLP.input(h("0xBC0100000000")))
+ .isInstanceOf(RLPException.class)
+ .hasMessageContaining(
+ "RLP item at offset 1 with size value consuming 5 bytes exceeds max supported size of 2147483647");
+ }
+
+ @Test
+ public void rlpListSizeHoldsMaxValue() {
+ // Size value encode max positive int. So, size is decoded, but
+ // RLP is malformed because the actual payload is not present
+ AssertionsForClassTypes.assertThatThrownBy(() -> RLP.input(h("0xFB7FFFFFFF")).readBytesValue())
+ .isInstanceOf(CorruptedRLPInputException.class)
+ .hasMessageContaining(
+ "Input doesn't have enough data for RLP encoding: encoding advertise a payload ending at byte 2147483652 but input has size 5");
+ }
+
+ @Test
+ public void rlpListSizeOverflowsSignedInt() {
+ // Size value encoded in 4 bytes but exceeds max positive int value
+ AssertionsForClassTypes.assertThatThrownBy(() -> RLP.input(h("0xFB80000000")))
+ .isInstanceOf(RLPException.class)
+ .hasMessageContaining(
+ "RLP item at offset 1 with size value consuming 4 bytes exceeds max supported size of 2147483647");
+ }
+
+ @Test
+ public void rlpListSizeOverflowsInt() {
+ // Size value is encoded with 5 bytes - overflowing int
+ AssertionsForClassTypes.assertThatThrownBy(() -> RLP.input(h("0xFC0100000000")))
+ .isInstanceOf(RLPException.class)
+ .hasMessageContaining(
+ "RLP item at offset 1 with size value consuming 5 bytes exceeds max supported size of 2147483647");
+ }
+
+ @SuppressWarnings("ReturnValueIgnored")
+ @Test
+ public void decodeValueWithLeadingZerosAsScalar() {
+ String value = "0x8200D0";
+
+ List> invalidDecoders =
+ Arrays.asList(
+ RLPInput::readBigIntegerScalar,
+ RLPInput::readIntScalar,
+ RLPInput::readLongScalar,
+ RLPInput::readUInt256Scalar);
+
+ for (Function decoder : invalidDecoders) {
+ RLPInput in = RLP.input(h(value));
+ AssertionsForClassTypes.assertThatThrownBy(() -> decoder.apply(in))
+ .isInstanceOf(MalformedRLPInputException.class)
+ .hasMessageContaining("Invalid scalar");
+ }
+ }
+
+ @Test
+ public void decodeValueWithLeadingZerosAsUnsignedInt() {
+ RLPInput in = RLP.input(h("0x84000000D0"));
+ assertThat(in.readUnsignedInt()).isEqualTo(208);
+ }
+
+ @Test
+ public void decodeValueWithLeadingZerosAsUnsignedShort() {
+ RLPInput in = RLP.input(h("0x8200D0"));
+ assertThat(in.readUnsignedShort()).isEqualTo(208);
+ }
+
+ @Test
+ public void decodeValueWithLeadingZerosAsSignedInt() {
+ RLPInput in = RLP.input(h("0x84000000D0"));
+ assertThat(in.readInt()).isEqualTo(208);
+ }
+
+ @Test
+ public void decodeValueWithLeadingZerosAsSignedLong() {
+ RLPInput in = RLP.input(h("0x8800000000000000D0"));
+ assertThat(in.readLong()).isEqualTo(208);
+ }
+
+ @Test
+ public void decodeValueWithLeadingZerosAsSignedShort() {
+ RLPInput in = RLP.input(h("0x8200D0"));
+ assertThat(in.readShort()).isEqualTo((short) 208);
+ }
+
+ @Test
+ public void decodeValueWithLeadingZerosAsBytesValue() {
+ RLPInput in = RLP.input(h("0x8800000000000000D0"));
+ assertThat(BytesValues.extractLong(in.readBytesValue())).isEqualTo(208);
+ }
}
diff --git a/ethereum/rlp/src/test/java/tech/pegasys/pantheon/ethereum/rlp/BytesValueRLPOutputTest.java b/ethereum/rlp/src/test/java/tech/pegasys/pantheon/ethereum/rlp/BytesValueRLPOutputTest.java
index 29c04e9000..ce6e9209fe 100644
--- a/ethereum/rlp/src/test/java/tech/pegasys/pantheon/ethereum/rlp/BytesValueRLPOutputTest.java
+++ b/ethereum/rlp/src/test/java/tech/pegasys/pantheon/ethereum/rlp/BytesValueRLPOutputTest.java
@@ -12,7 +12,7 @@
*/
package tech.pegasys.pantheon.ethereum.rlp;
-import static org.junit.Assert.assertEquals;
+import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.util.bytes.BytesValue;
@@ -33,7 +33,18 @@ public class BytesValueRLPOutputTest {
@Test
public void empty() {
final BytesValueRLPOutput out = new BytesValueRLPOutput();
- assertEquals(BytesValue.EMPTY, out.encoded());
+
+ assertThat(out.encoded()).isEqualTo(BytesValue.EMPTY);
+ assertThat(out.encoded().toString()).isEqualTo("0x");
+ }
+
+ @Test
+ public void emptyBytesString() {
+ final BytesValueRLPOutput out = new BytesValueRLPOutput();
+ out.writeBytesValue(BytesValue.EMPTY);
+
+ assertThat(out.encoded()).isEqualTo(RLP.NULL);
+ assertThat(out.encoded().toString()).isEqualTo("0x80");
}
@Test
@@ -42,21 +53,21 @@ public class BytesValueRLPOutputTest {
out.writeByte((byte) 1);
// Single byte should be encoded as itself
- assertEquals(h("0x01"), out.encoded());
+ assertThat(out.encoded()).isEqualTo(h("0x01"));
}
@Test
public void singleByteLowerBoundary() {
final BytesValueRLPOutput out = new BytesValueRLPOutput();
out.writeByte((byte) 0);
- assertEquals(h("0x00"), out.encoded());
+ assertThat(out.encoded()).isEqualTo(h("0x00"));
}
@Test
public void singleByteUpperBoundary() {
final BytesValueRLPOutput out = new BytesValueRLPOutput();
out.writeByte((byte) 0x7f);
- assertEquals(h("0x7f"), out.encoded());
+ assertThat(out.encoded()).isEqualTo(h("0x7f"));
}
@Test
@@ -65,7 +76,7 @@ public class BytesValueRLPOutputTest {
out.writeByte((byte) 0xFF);
// Bigger than single byte: 0x80 + length then value, where length is 1.
- assertEquals(h("0x81FF"), out.encoded());
+ assertThat(out.encoded()).isEqualTo(h("0x81FF"));
}
@Test
@@ -74,7 +85,7 @@ public class BytesValueRLPOutputTest {
out.writeBytesValue(h(times("2b", 55)));
// 55 bytes, so still short: 0x80 + length then value, where length is 55.
- assertEquals(h("0xb7" + times("2b", 55)), out.encoded());
+ assertThat(out.encoded()).isEqualTo(h("0xb7" + times("2b", 55)));
}
@Test
@@ -84,7 +95,7 @@ public class BytesValueRLPOutputTest {
// 56 bytes, so long element: 0xb7 + length of value size + value, where the value size is 56.
// 56 is 0x38 so its size is 1 byte.
- assertEquals(h("0xb838" + times("2b", 56)), out.encoded());
+ assertThat(out.encoded()).isEqualTo(h("0xb838" + times("2b", 56)));
}
@Test
@@ -95,49 +106,49 @@ public class BytesValueRLPOutputTest {
// 2241 bytes, so long element: 0xb7 + length of value size + value, where the value size is
// 2241,
// 2241 is 0x8c1 so its size is 2 bytes.
- assertEquals(h("0xb908c1" + times("3c", 2241)), out.encoded());
+ assertThat(out.encoded()).isEqualTo(h("0xb908c1" + times("3c", 2241)));
}
@Test
public void singleLongElementBoundaryCase_1() {
final BytesValueRLPOutput out = new BytesValueRLPOutput();
out.writeBytesValue(h(times("3c", 255)));
- assertEquals(h("0xb8ff" + times("3c", 255)), out.encoded());
+ assertThat(out.encoded()).isEqualTo(h("0xb8ff" + times("3c", 255)));
}
@Test
public void singleLongElementBoundaryCase_2() {
final BytesValueRLPOutput out = new BytesValueRLPOutput();
out.writeBytesValue(h(times("3c", 256)));
- assertEquals(h("0xb90100" + times("3c", 256)), out.encoded());
+ assertThat(out.encoded()).isEqualTo(h("0xb90100" + times("3c", 256)));
}
@Test
public void singleLongElementBoundaryCase_3() {
final BytesValueRLPOutput out = new BytesValueRLPOutput();
out.writeBytesValue(h(times("3c", 65535)));
- assertEquals(h("0xb9ffff" + times("3c", 65535)), out.encoded());
+ assertThat(out.encoded()).isEqualTo(h("0xb9ffff" + times("3c", 65535)));
}
@Test
public void singleLongElementBoundaryCase_4() {
final BytesValueRLPOutput out = new BytesValueRLPOutput();
out.writeBytesValue(h(times("3c", 65536)));
- assertEquals(h("0xba010000" + times("3c", 65536)), out.encoded());
+ assertThat(out.encoded()).isEqualTo(h("0xba010000" + times("3c", 65536)));
}
@Test
public void singleLongElementBoundaryCase_5() {
final BytesValueRLPOutput out = new BytesValueRLPOutput();
out.writeBytesValue(h(times("3c", 16777215)));
- assertEquals(h("0xbaffffff" + times("3c", 16777215)), out.encoded());
+ assertThat(out.encoded()).isEqualTo(h("0xbaffffff" + times("3c", 16777215)));
}
@Test
public void singleLongElementBoundaryCase_6() {
final BytesValueRLPOutput out = new BytesValueRLPOutput();
out.writeBytesValue(h(times("3c", 16777216)));
- assertEquals(h("0xbb01000000" + times("3c", 16777216)), out.encoded());
+ assertThat(out.encoded()).isEqualTo(h("0xbb01000000" + times("3c", 16777216)));
}
@Test(expected = IllegalStateException.class)
@@ -161,7 +172,7 @@ public class BytesValueRLPOutputTest {
private void assertLongScalar(final BytesValue expected, final long toTest) {
final BytesValueRLPOutput out = new BytesValueRLPOutput();
out.writeLongScalar(toTest);
- assertEquals(expected, out.encoded());
+ assertThat(out.encoded()).isEqualTo(expected);
}
@Test
@@ -170,7 +181,7 @@ public class BytesValueRLPOutputTest {
out.startList();
out.endList();
- assertEquals(h("0xc0"), out.encoded());
+ assertThat(out.encoded()).isEqualTo(h("0xc0"));
}
@Test(expected = IllegalStateException.class)
@@ -196,7 +207,7 @@ public class BytesValueRLPOutputTest {
// List with payload size = 2 (both element are single bytes)
// so 0xc0 + size then payloads
- assertEquals(h("0xc22c3b"), out.encoded());
+ assertThat(out.encoded()).isEqualTo(h("0xc22c3b"));
}
@Test
@@ -207,7 +218,7 @@ public class BytesValueRLPOutputTest {
out.writeByte((byte) 0x3c);
}
out.endList();
- assertEquals(h("0xf7" + times("3c", 55)), out.encoded());
+ assertThat(out.encoded()).isEqualTo(h("0xf7" + times("3c", 55)));
}
@Test
@@ -218,7 +229,7 @@ public class BytesValueRLPOutputTest {
out.writeByte((byte) 0x3c);
}
out.endList();
- assertEquals(h("0xf838" + times("3c", 56)), out.encoded());
+ assertThat(out.encoded()).isEqualTo(h("0xf838" + times("3c", 56)));
}
@Test
@@ -229,7 +240,7 @@ public class BytesValueRLPOutputTest {
out.writeByte((byte) 0x3c);
}
out.endList();
- assertEquals(h("0xf8ff" + times("3c", 255)), out.encoded());
+ assertThat(out.encoded()).isEqualTo(h("0xf8ff" + times("3c", 255)));
}
@Test
@@ -240,7 +251,7 @@ public class BytesValueRLPOutputTest {
out.writeByte((byte) 0x3c);
}
out.endList();
- assertEquals(h("0xf90100" + times("3c", 256)), out.encoded());
+ assertThat(out.encoded()).isEqualTo(h("0xf90100" + times("3c", 256)));
}
@Test
@@ -251,7 +262,7 @@ public class BytesValueRLPOutputTest {
out.writeByte((byte) 0x3c);
}
out.endList();
- assertEquals(h("0xf9ffff" + times("3c", 65535)), out.encoded());
+ assertThat(out.encoded()).isEqualTo(h("0xf9ffff" + times("3c", 65535)));
}
@Test
@@ -262,7 +273,7 @@ public class BytesValueRLPOutputTest {
out.writeByte((byte) 0x3c);
}
out.endList();
- assertEquals(h("0xfa010000" + times("3c", 65536)), out.encoded());
+ assertThat(out.encoded()).isEqualTo(h("0xfa010000" + times("3c", 65536)));
}
@Test
@@ -273,7 +284,7 @@ public class BytesValueRLPOutputTest {
out.writeByte((byte) 0x3c);
}
out.endList();
- assertEquals(h("0xfaffffff" + times("3c", 16777215)), out.encoded());
+ assertThat(out.encoded()).isEqualTo(h("0xfaffffff" + times("3c", 16777215)));
}
@Test
@@ -284,7 +295,7 @@ public class BytesValueRLPOutputTest {
out.writeByte((byte) 0x3c);
}
out.endList();
- assertEquals(h("0xfb01000000" + times("3c", 16777216)), out.encoded());
+ assertThat(out.encoded()).isEqualTo(h("0xfb01000000" + times("3c", 16777216)));
}
@Test
@@ -302,6 +313,6 @@ public class BytesValueRLPOutputTest {
// List payload size = 5 (2 single bytes element + nested list of size 3)
// so 0xc0 + size then payloads
- assertEquals(h("0xc52cc203123b"), out.encoded());
+ assertThat(out.encoded()).isEqualTo(h("0xc52cc203123b"));
}
}
diff --git a/ethereum/rlp/src/test/java/tech/pegasys/pantheon/ethereum/rlp/InvalidRLPRefTest.java b/ethereum/rlp/src/test/java/tech/pegasys/pantheon/ethereum/rlp/InvalidRLPRefTest.java
index 505bfa7e1c..0b0ef2a9ca 100644
--- a/ethereum/rlp/src/test/java/tech/pegasys/pantheon/ethereum/rlp/InvalidRLPRefTest.java
+++ b/ethereum/rlp/src/test/java/tech/pegasys/pantheon/ethereum/rlp/InvalidRLPRefTest.java
@@ -14,6 +14,7 @@ package tech.pegasys.pantheon.ethereum.rlp;
import static org.junit.Assume.assumeTrue;
+import tech.pegasys.pantheon.ethereum.rlp.util.RLPTestUtil;
import tech.pegasys.pantheon.testutil.JsonTestParameters;
import java.util.Collection;
@@ -48,6 +49,6 @@ public class InvalidRLPRefTest {
/** Test RLP decoding. */
@Test(expected = RLPException.class)
public void decode() throws Exception {
- RLP.decode(spec.getRLP());
+ RLPTestUtil.decode(spec.getRLP());
}
}
diff --git a/ethereum/rlp/src/test/java/tech/pegasys/pantheon/ethereum/rlp/RLPRefTest.java b/ethereum/rlp/src/test/java/tech/pegasys/pantheon/ethereum/rlp/RLPRefTest.java
index 65b020c6df..a0f7e43d07 100644
--- a/ethereum/rlp/src/test/java/tech/pegasys/pantheon/ethereum/rlp/RLPRefTest.java
+++ b/ethereum/rlp/src/test/java/tech/pegasys/pantheon/ethereum/rlp/RLPRefTest.java
@@ -15,6 +15,7 @@ package tech.pegasys.pantheon.ethereum.rlp;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
+import tech.pegasys.pantheon.ethereum.rlp.util.RLPTestUtil;
import tech.pegasys.pantheon.testutil.JsonTestParameters;
import java.util.Collection;
@@ -44,11 +45,11 @@ public class RLPRefTest {
@Test
public void encode() {
- assertEquals(spec.getOut(), RLP.encode(spec.getIn()));
+ assertEquals(spec.getOut(), RLPTestUtil.encode(spec.getIn()));
}
@Test
public void decode() {
- assertEquals(spec.getIn(), RLP.decode(spec.getOut()));
+ assertEquals(spec.getIn(), RLPTestUtil.decode(spec.getOut()));
}
}
diff --git a/ethereum/rlp/src/test/java/tech/pegasys/pantheon/ethereum/rlp/RLPTest.java b/ethereum/rlp/src/test/java/tech/pegasys/pantheon/ethereum/rlp/RLPTest.java
index 9a9cebd784..fafb36e07e 100644
--- a/ethereum/rlp/src/test/java/tech/pegasys/pantheon/ethereum/rlp/RLPTest.java
+++ b/ethereum/rlp/src/test/java/tech/pegasys/pantheon/ethereum/rlp/RLPTest.java
@@ -12,10 +12,14 @@
*/
package tech.pegasys.pantheon.ethereum.rlp;
-import static junit.framework.TestCase.assertEquals;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+import tech.pegasys.pantheon.ethereum.rlp.util.RLPTestUtil;
import tech.pegasys.pantheon.util.bytes.BytesValue;
+import java.util.Random;
+
import org.junit.Test;
public class RLPTest {
@@ -23,48 +27,146 @@ public class RLPTest {
@Test
public void calculateSize_singleByteValue() {
int size = RLP.calculateSize(BytesValue.fromHexString("0x01"));
- assertEquals(1, size);
+ assertThat(size).isEqualTo(1);
+ }
+
+ @Test
+ public void calculateSize_minSingleByteValue() {
+ int size = RLP.calculateSize(BytesValue.fromHexString("0x00"));
+ assertThat(size).isEqualTo(1);
+ }
+
+ @Test
+ public void calculateSize_maxSingleByteValue() {
+ int size = RLP.calculateSize(BytesValue.fromHexString("0x7F"));
+ assertThat(size).isEqualTo(1);
}
@Test
public void calculateSize_smallByteString() {
// Prefix indicates a payload of size 5, with a 1 byte prefix
int size = RLP.calculateSize(BytesValue.fromHexString("0x85"));
- assertEquals(6, size);
+ assertThat(size).isEqualTo(6);
+ }
+
+ @Test
+ public void calculateSize_nullByteString() {
+ // Prefix indicates a payload of size 0, with a 1 byte prefix
+ int size = RLP.calculateSize(BytesValue.fromHexString("0x80"));
+ assertThat(size).isEqualTo(1);
+ }
+
+ @Test
+ public void calculateSize_minNonNullSmallByteString() {
+ // Prefix indicates a payload of size 1, with a 1 byte prefix
+ int size = RLP.calculateSize(BytesValue.fromHexString("0x81"));
+ assertThat(size).isEqualTo(2);
+ }
+
+ @Test
+ public void calculateSize_maxSmallByteString() {
+ // Prefix indicates a payload of size 55, with a 1 byte prefix
+ int size = RLP.calculateSize(BytesValue.fromHexString("0xB7"));
+ assertThat(size).isEqualTo(56);
}
@Test
public void calculateSize_longByteString() {
// Prefix indicates a payload of 56 bytes, with a 2 byte prefix
int size = RLP.calculateSize(BytesValue.fromHexString("0xB838"));
- assertEquals(58, size);
+ assertThat(size).isEqualTo(58);
}
@Test
public void calculateSize_longByteStringWithMultiByteSize() {
// Prefix indicates a payload of 258 bytes, with a 3 byte prefix
int size = RLP.calculateSize(BytesValue.fromHexString("0xB90102"));
- assertEquals(261, size);
+ assertThat(size).isEqualTo(261);
}
@Test
public void calculateSize_shortList() {
// Prefix indicates a payload of 5 bytes, with a 1 byte prefix
int size = RLP.calculateSize(BytesValue.fromHexString("0xC5"));
- assertEquals(6, size);
+ assertThat(size).isEqualTo(6);
+ }
+
+ @Test
+ public void calculateSize_emptyList() {
+ int size = RLP.calculateSize(BytesValue.fromHexString("0xC0"));
+ assertThat(size).isEqualTo(1);
+ }
+
+ @Test
+ public void calculateSize_minNonEmptyList() {
+ int size = RLP.calculateSize(BytesValue.fromHexString("0xC1"));
+ assertThat(size).isEqualTo(2);
+ }
+
+ @Test
+ public void calculateSize_maxShortList() {
+ int size = RLP.calculateSize(BytesValue.fromHexString("0xF7"));
+ assertThat(size).isEqualTo(56);
}
@Test
public void calculateSize_longList() {
// Prefix indicates a payload of 56 bytes, with a 2 byte prefix
int size = RLP.calculateSize(BytesValue.fromHexString("0xF838"));
- assertEquals(58, size);
+ assertThat(size).isEqualTo(58);
}
@Test
public void calculateSize_longListWithMultiByteSize() {
// Prefix indicates a payload of 258 bytes, with a 3 byte prefix
int size = RLP.calculateSize(BytesValue.fromHexString("0xF90102"));
- assertEquals(261, size);
+ assertThat(size).isEqualTo(261);
+ }
+
+ @Test
+ public void calculateSize_fuzz() {
+ final Random random = new Random(1);
+ for (int i = 0; i < 1000; ++i) {
+ BytesValueRLPOutput out = RLPTestUtil.randomRLPValue(random.nextInt());
+ assertThat(RLP.calculateSize(out.encoded())).isEqualTo(out.encodedSize());
+ }
+ }
+
+ @Test
+ public void calculateSize_extremelyDeepNestedList() {
+ final int MAX_DEPTH = 20000;
+
+ final BytesValueRLPOutput out = new BytesValueRLPOutput();
+ int depth = 0;
+
+ for (int i = 0; i < MAX_DEPTH; ++i) {
+ out.startList();
+ depth += 1;
+ }
+ while (depth > 0) {
+ out.endList();
+ --depth;
+ }
+ assertThat(RLP.calculateSize(out.encoded())).isEqualTo(out.encodedSize());
+ }
+
+ @Test
+ public void calculateSize_maxRLPStringLength() {
+ // Value represents a single item with an encoded payload size of MAX_VALUE - 5 and
+ // 5 bytes of metadata (payload is not actually present)
+ assertThat(RLP.calculateSize(h("0xBB7FFFFFFA"))).isEqualTo(Integer.MAX_VALUE);
+ }
+
+ @Test
+ public void calculateSize_overflowMaxRLPStringLength() {
+ // Value represents a single item with an encoded payload size of MAX_VALUE - 4 and
+ // 5 bytes of metadata (payload is not actually present)
+ assertThatThrownBy(() -> RLP.calculateSize(h("0xBB7FFFFFFB")))
+ .isInstanceOf(RLPException.class)
+ .hasMessageContaining("RLP item exceeds max supported size of 2147483647: 2147483648");
+ }
+
+ private static BytesValue h(final String hex) {
+ return BytesValue.fromHexString(hex);
}
}
diff --git a/ethereum/rlp/src/test/java/tech/pegasys/pantheon/ethereum/rlp/util/RLPTestUtil.java b/ethereum/rlp/src/test/java/tech/pegasys/pantheon/ethereum/rlp/util/RLPTestUtil.java
new file mode 100644
index 0000000000..87355aea5f
--- /dev/null
+++ b/ethereum/rlp/src/test/java/tech/pegasys/pantheon/ethereum/rlp/util/RLPTestUtil.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2018 ConsenSys AG.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package tech.pegasys.pantheon.ethereum.rlp.util;
+
+import static java.lang.String.format;
+
+import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPOutput;
+import tech.pegasys.pantheon.ethereum.rlp.RLP;
+import tech.pegasys.pantheon.ethereum.rlp.RLPException;
+import tech.pegasys.pantheon.ethereum.rlp.RLPInput;
+import tech.pegasys.pantheon.ethereum.rlp.RLPOutput;
+import tech.pegasys.pantheon.util.bytes.BytesValue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class RLPTestUtil {
+
+ /**
+ * Recursively decodes an RLP encoded value. Byte strings are assumed to be non-scalar (leading
+ * zeros are allowed).
+ *
+ * @param value The RLP encoded value to decode.
+ * @return The output of decoding {@code value}. It will be either directly a {@link BytesValue},
+ * or a list whose elements are either {@link BytesValue}, or similarly composed sub-lists.
+ * @throws RLPException if {@code value} is not a properly formed RLP encoding.
+ */
+ public static Object decode(final BytesValue value) {
+ return decode(RLP.input(value));
+ }
+
+ private static Object decode(final RLPInput in) {
+ if (!in.nextIsList()) {
+ return in.readBytesValue();
+ }
+
+ final int size = in.enterList();
+ final List l = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) l.add(decode(in));
+ in.leaveList();
+ return l;
+ }
+
+ /**
+ * Recursively RLP encode an object consisting of recursive lists of {@link BytesValue}.
+ * BytesValues are assumed to be non-scalar (leading zeros are not trimmed).
+ *
+ * @param obj An object that must be either directly a {@link BytesValue}, or a list whose
+ * elements are either {@link BytesValue}, or similarly composed sub-lists.
+ * @return The RLP encoding corresponding to {@code obj}.
+ * @throws IllegalArgumentException if {@code obj} is not a valid input (not entirely composed
+ * from lists and {@link BytesValue}).
+ */
+ public static BytesValue encode(final Object obj) {
+ final BytesValueRLPOutput out = new BytesValueRLPOutput();
+ encode(obj, out);
+ return out.encoded();
+ }
+
+ private static void encode(final Object obj, final RLPOutput out) {
+ if (obj instanceof BytesValue) {
+ out.writeBytesValue((BytesValue) obj);
+ } else if (obj instanceof List) {
+ final List> l = (List>) obj;
+ out.startList();
+ for (final Object o : l) encode(o, out);
+ out.endList();
+ } else {
+ throw new IllegalArgumentException(
+ format("Invalid input type %s for RLP encoding", obj.getClass()));
+ }
+ }
+
+ /**
+ * Generate a random rlp-encoded value.
+ *
+ * @param randomSeed Seed to use for random generation.
+ * @return a random rlp-encoded value
+ */
+ public static BytesValueRLPOutput randomRLPValue(final int randomSeed) {
+ final Random random = new Random(randomSeed);
+ final BytesValueRLPOutput out = new BytesValueRLPOutput();
+ final AtomicInteger listDepth = new AtomicInteger(0);
+ int iterations = 0;
+ do {
+ if (iterations > 1000) {
+ out.endList();
+ listDepth.decrementAndGet();
+ continue;
+ }
+ iterations += 1;
+
+ writeRandomRLPData(out, random, listDepth);
+ } while (listDepth.get() > 0);
+
+ return out;
+ }
+
+ private static void writeRandomRLPData(
+ final RLPOutput out, final Random random, final AtomicInteger listDepth) {
+ switch (random.nextInt(12)) {
+ case 0:
+ // Write empty byte string
+ out.writeBytesValue(BytesValue.EMPTY);
+ break;
+ case 1:
+ // Small single byte
+ out.writeByte((byte) random.nextInt(128));
+ break;
+ case 2:
+ // Large single byte
+ byte value = (byte) (random.nextInt(128) + 128);
+ out.writeByte(value);
+ break;
+ case 3:
+ // Small byte string
+ int smallBytesSize = random.nextInt(54) + 2;
+ out.writeBytesValue(randomBytesValue(random, smallBytesSize));
+ break;
+ case 4:
+ // Large byte string
+ int largeBytesSize = random.nextInt(500) + 56;
+ out.writeBytesValue(randomBytesValue(random, largeBytesSize));
+ break;
+ case 5:
+ // Close list
+ if (listDepth.get() == 0) {
+ // If we're outside of a list try again
+ writeRandomRLPData(out, random, listDepth);
+ return;
+ }
+ out.endList();
+ listDepth.decrementAndGet();
+ break;
+ default:
+ // Start list
+ out.startList();
+ listDepth.incrementAndGet();
+ break;
+ }
+ }
+
+ private static BytesValue randomBytesValue(final Random random, final int size) {
+ final byte[] bytes = new byte[size];
+ for (int i = 0; i < bytes.length; i++) {
+ bytes[i] = (byte) random.nextInt(256);
+ }
+ return BytesValue.wrap(bytes);
+ }
+}