[NC-1561] Remove RLPUtils from RawBlockIterator (#179)

mbaxter 6 years ago committed by GitHub
parent fb4fdb801d
commit f0553ef5f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 24
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/util/RawBlockIterator.java
  2. 107
      ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/util/RawBlockIteratorTest.java
  3. 1
      ethereum/rlp/build.gradle
  4. 96
      ethereum/rlp/src/main/java/tech/pegasys/pantheon/ethereum/rlp/AbstractRLPInput.java
  5. 2
      ethereum/rlp/src/main/java/tech/pegasys/pantheon/ethereum/rlp/BytesValueRLPInput.java
  6. 11
      ethereum/rlp/src/main/java/tech/pegasys/pantheon/ethereum/rlp/RLP.java
  7. 96
      ethereum/rlp/src/main/java/tech/pegasys/pantheon/ethereum/rlp/RLPDecodingHelpers.java
  8. 71
      ethereum/rlp/src/test/java/tech/pegasys/pantheon/ethereum/rlp/BytesValueRLPInputTest.java
  9. 70
      ethereum/rlp/src/test/java/tech/pegasys/pantheon/ethereum/rlp/RLPTest.java
  10. 42
      util/src/main/java/tech/pegasys/pantheon/util/bytes/BytesValue.java
  11. 10
      util/src/main/java/tech/pegasys/pantheon/util/bytes/MutableBufferWrappingBytesValue.java
  12. 8
      util/src/main/java/tech/pegasys/pantheon/util/bytes/MutableByteBufWrappingBytesValue.java
  13. 125
      util/src/main/java/tech/pegasys/pantheon/util/bytes/MutableByteBufferWrappingBytesValue.java
  14. 79
      util/src/main/java/tech/pegasys/pantheon/util/bytes/MutableBytesValue.java
  15. 656
      util/src/test/java/tech/pegasys/pantheon/util/bytes/BytesValueImplementationsTest.java
  16. 584
      util/src/test/java/tech/pegasys/pantheon/util/bytes/BytesValueTest.java

@ -17,8 +17,8 @@ import tech.pegasys.pantheon.ethereum.core.BlockBody;
import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.core.Transaction; import tech.pegasys.pantheon.ethereum.core.Transaction;
import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPInput; import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPInput;
import tech.pegasys.pantheon.ethereum.rlp.RLP;
import tech.pegasys.pantheon.ethereum.rlp.RLPInput; import tech.pegasys.pantheon.ethereum.rlp.RLPInput;
import tech.pegasys.pantheon.ethereum.rlp.RlpUtils;
import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.io.Closeable; import java.io.Closeable;
@ -26,27 +26,36 @@ import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.function.Function; import java.util.function.Function;
public final class RawBlockIterator implements Iterator<Block>, Closeable { public final class RawBlockIterator implements Iterator<Block>, Closeable {
private static final int DEFAULT_INIT_BUFFER_CAPACITY = 1 << 16;
private final FileChannel fileChannel; private final FileChannel fileChannel;
private final Function<RLPInput, BlockHeader> headerReader; private final Function<RLPInput, BlockHeader> headerReader;
private ByteBuffer readBuffer = ByteBuffer.allocate(2 << 15); private ByteBuffer readBuffer;
private Block next; private Block next;
public RawBlockIterator(final Path file, final Function<RLPInput, BlockHeader> headerReader) RawBlockIterator(
final Path file,
final Function<RLPInput, BlockHeader> headerReader,
final int initialCapacity)
throws IOException { throws IOException {
fileChannel = FileChannel.open(file); fileChannel = FileChannel.open(file);
this.headerReader = headerReader; this.headerReader = headerReader;
readBuffer = ByteBuffer.allocate(initialCapacity);
nextBlock(); nextBlock();
} }
public RawBlockIterator(final Path file, final Function<RLPInput, BlockHeader> headerReader)
throws IOException {
this(file, headerReader, DEFAULT_INIT_BUFFER_CAPACITY);
}
@Override @Override
public boolean hasNext() { public boolean hasNext() {
return next != null; return next != null;
@ -75,7 +84,7 @@ public final class RawBlockIterator implements Iterator<Block>, Closeable {
fillReadBuffer(); fillReadBuffer();
int initial = readBuffer.position(); int initial = readBuffer.position();
if (initial > 0) { if (initial > 0) {
final int length = RlpUtils.decodeLength(readBuffer, 0); final int length = RLP.calculateSize(BytesValue.wrapBuffer(readBuffer));
if (length > readBuffer.capacity()) { if (length > readBuffer.capacity()) {
readBuffer.flip(); readBuffer.flip();
final ByteBuffer newBuffer = ByteBuffer.allocate(2 * length); final ByteBuffer newBuffer = ByteBuffer.allocate(2 * length);
@ -84,8 +93,9 @@ public final class RawBlockIterator implements Iterator<Block>, Closeable {
fillReadBuffer(); fillReadBuffer();
initial = readBuffer.position(); initial = readBuffer.position();
} }
final RLPInput rlp =
new BytesValueRLPInput(BytesValue.wrap(Arrays.copyOf(readBuffer.array(), length)), false); final BytesValue rlpBytes = BytesValue.wrapBuffer(readBuffer, 0, length).copy();
final RLPInput rlp = new BytesValueRLPInput(rlpBytes, false);
rlp.enterList(); rlp.enterList();
final BlockHeader header = headerReader.apply(rlp); final BlockHeader header = headerReader.apply(rlp);
final BlockBody body = final BlockBody body =

@ -0,0 +1,107 @@
/*
* 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.util;
import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.ethereum.core.Block;
import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.core.Transaction;
import tech.pegasys.pantheon.ethereum.mainnet.MainnetBlockHashFunction;
import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPOutput;
import tech.pegasys.pantheon.ethereum.testutil.BlockDataGenerator;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.function.Function;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
public class RawBlockIteratorTest {
@Rule public final TemporaryFolder tmp = new TemporaryFolder();
private BlockDataGenerator gen;
@Before
public void setup() {
gen = new BlockDataGenerator(1);
}
@Test
public void readsBlockAtBoundaryOfInitialCapacity() throws IOException {
readsBlocksWithInitialCapacity(Function.identity());
}
@Test
public void readsBlockThatExtendsPastInitialCapacity() throws IOException {
readsBlocksWithInitialCapacity((size) -> size / 2);
}
@Test
public void readsBlockWithinInitialCapacity() throws IOException {
readsBlocksWithInitialCapacity((size) -> size * 2);
}
public void readsBlocksWithInitialCapacity(
final Function<Integer, Integer> initialCapacityFromBlockSize) throws IOException {
final int blockCount = 3;
final List<Block> blocks = gen.blockSequence(blockCount);
// Write a few blocks to a tmp file
byte[] firstSerializedBlock = null;
final File blocksFile = tmp.newFolder().toPath().resolve("blocks").toFile();
final DataOutputStream writer = new DataOutputStream(new FileOutputStream(blocksFile));
for (Block block : blocks) {
final byte[] serializedBlock = serializeBlock(block);
writer.write(serializedBlock);
if (firstSerializedBlock == null) {
firstSerializedBlock = serializedBlock;
}
}
writer.close();
// Read blocks
final int initialCapacity = initialCapacityFromBlockSize.apply(firstSerializedBlock.length);
final RawBlockIterator iterator =
new RawBlockIterator(
blocksFile.toPath(),
rlp -> BlockHeader.readFrom(rlp, MainnetBlockHashFunction::createHash),
initialCapacity);
// Read blocks and check that they match
for (int i = 0; i < blockCount; i++) {
assertThat(iterator.hasNext()).isTrue();
final Block readBlock = iterator.next();
final Block expectedBlock = blocks.get(i);
assertThat(readBlock).isEqualTo(expectedBlock);
}
assertThat(iterator.hasNext()).isFalse();
}
private byte[] serializeBlock(final Block block) {
final BytesValueRLPOutput out = new BytesValueRLPOutput();
out.startList();
block.getHeader().writeTo(out);
out.writeList(block.getBody().getTransactions(), Transaction::writeTo);
out.writeList(block.getBody().getOmmers(), BlockHeader::writeTo);
out.endList();
return out.encoded().extractArray();
}
}

@ -34,6 +34,7 @@ dependencies {
testImplementation project(':testutil') testImplementation project(':testutil')
testImplementation project(path:':ethereum:referencetests', configuration: 'testOutput') testImplementation project(path:':ethereum:referencetests', configuration: 'testOutput')
testImplementation 'org.assertj:assertj-core'
testImplementation 'com.fasterxml.jackson.core:jackson-databind' testImplementation 'com.fasterxml.jackson.core:jackson-databind'
testImplementation 'junit:junit' testImplementation 'junit:junit'
} }

@ -15,6 +15,7 @@ package tech.pegasys.pantheon.ethereum.rlp;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import tech.pegasys.pantheon.ethereum.rlp.RLPDecodingHelpers.Kind; import tech.pegasys.pantheon.ethereum.rlp.RLPDecodingHelpers.Kind;
import tech.pegasys.pantheon.ethereum.rlp.RLPDecodingHelpers.RLPElementMetadata;
import tech.pegasys.pantheon.util.bytes.Bytes32; import tech.pegasys.pantheon.util.bytes.Bytes32;
import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.bytes.MutableBytes32; import tech.pegasys.pantheon.util.bytes.MutableBytes32;
@ -31,7 +32,7 @@ abstract class AbstractRLPInput implements RLPInput {
private final boolean lenient; private final boolean lenient;
protected long size; protected long size; // The number of bytes in this rlp-encoded byte string
// Information on the item the input currently is at (next thing to read). // Information on the item the input currently is at (next thing to read).
protected long protected long
@ -56,7 +57,7 @@ abstract class AbstractRLPInput implements RLPInput {
} }
currentItem = 0; currentItem = 0;
// Initially set the size to the input as prepareCurrentTime() needs it. Once we've prepare the // Initially set the size to the input as prepareCurrentItem() needs it. Once we've prepared the
// top level item, we know where that item ends exactly and can update the size to that more // top level item, we know where that item ends exactly and can update the size to that more
// precise value (which basically mean we'll throw errors on malformed inputs potentially // precise value (which basically mean we'll throw errors on malformed inputs potentially
// sooner). // sooner).
@ -127,31 +128,17 @@ abstract class AbstractRLPInput implements RLPInput {
private void prepareCurrentItem() { private void prepareCurrentItem() {
// Sets the kind of the item, the offset at which his payload starts and the size of this // Sets the kind of the item, the offset at which his payload starts and the size of this
// payload. // payload.
final int prefix = inputByte(currentItem) & 0xFF; try {
currentKind = Kind.of(prefix); RLPElementMetadata elementMetadata =
switch (currentKind) { RLPDecodingHelpers.rlpElementMetadata(this::inputByte, size, currentItem);
case BYTE_ELEMENT: currentKind = elementMetadata.kind;
currentPayloadOffset = currentItem; currentPayloadOffset = elementMetadata.payloadStart;
currentPayloadSize = 1; currentPayloadSize = elementMetadata.payloadSize;
break; } catch (RLPException exception) {
case SHORT_ELEMENT: String message =
currentPayloadOffset = currentItem + 1; String.format(
currentPayloadSize = prefix - 0x80; exception.getMessage() + getErrorMessageSuffix(), getErrorMessageSuffixParams());
break; throw new RLPException(message, exception);
case LONG_ELEMENT:
final int sizeLengthElt = prefix - 0xb7;
currentPayloadOffset = currentItem + 1 + sizeLengthElt;
currentPayloadSize = readLongSize(currentItem, sizeLengthElt);
break;
case SHORT_LIST:
currentPayloadOffset = currentItem + 1;
currentPayloadSize = prefix - 0xc0;
break;
case LONG_LIST:
final int sizeLengthList = prefix - 0xf7;
currentPayloadOffset = currentItem + 1 + sizeLengthList;
currentPayloadSize = readLongSize(currentItem, sizeLengthList);
break;
} }
} }
@ -182,32 +169,6 @@ abstract class AbstractRLPInput implements RLPInput {
} }
} }
/** The size of the item payload for a "long" item, given the length in bytes of the said size. */
private int readLongSize(final long item, final int sizeLength) {
// We will read sizeLength bytes from item + 1. There must be enough bytes for this or the input
// is corrupted.
if (size - (item + 1) < sizeLength) {
throw corrupted(
"Invalid RLP item: value of size %d has not enough bytes to read the %d "
+ "bytes payload size",
size, sizeLength);
}
// That size (which is at least 1 byte by construction) shouldn't have leading zeros.
if (inputByte(item + 1) == 0) {
throwMalformed("Malformed RLP item: size of payload has leading zeros");
}
final int res = RLPDecodingHelpers.extractSizeFromLong(this::inputByte, item + 1, sizeLength);
// We should not have had the size written separately if it was less than 56 bytes long.
if (res < 56) {
throwMalformed("Malformed RLP item: written as a long item, but size %d < 56 bytes", res);
}
return res;
}
private long nextItem() { private long nextItem() {
return currentPayloadOffset + currentPayloadSize; return currentPayloadOffset + currentPayloadSize;
} }
@ -246,21 +207,28 @@ abstract class AbstractRLPInput implements RLPInput {
} }
private String errorMsg(final String message, final Object... params) { private String errorMsg(final String message, final Object... params) {
return String.format(
message + getErrorMessageSuffix(), concatParams(params, getErrorMessageSuffixParams()));
}
private String getErrorMessageSuffix() {
return " (at bytes %d-%d: %s%s[%s]%s%s)";
}
private Object[] getErrorMessageSuffixParams() {
final long start = currentItem; final long start = currentItem;
final long end = Math.min(size, nextItem()); final long end = Math.min(size, nextItem());
final long realStart = Math.max(0, start - 4); final long realStart = Math.max(0, start - 4);
final long realEnd = Math.min(size, end + 4); final long realEnd = Math.min(size, end + 4);
return String.format( return new Object[] {
message + " (at bytes %d-%d: %s%s[%s]%s%s)", start,
concatParams( end,
params, realStart == 0 ? "" : "...",
start, hex(realStart, start),
end, hex(start, end),
realStart == 0 ? "" : "...", hex(end, realEnd),
hex(realStart, start), realEnd == size ? "" : "..."
hex(start, end), };
hex(end, realEnd),
realEnd == size ? "" : "..."));
} }
private static Object[] concatParams(final Object[] initial, final Object... others) { private static Object[] concatParams(final Object[] initial, final Object... others) {

@ -32,7 +32,7 @@ public class BytesValueRLPInput extends AbstractRLPInput {
@Override @Override
protected byte inputByte(final long offset) { protected byte inputByte(final long offset) {
return value.get(Math.toIntExact(offset)); return value.get(offset);
} }
@Override @Override

@ -272,4 +272,15 @@ public abstract class RLP {
} }
} }
} }
/**
* Given a {@link BytesValue} containing rlp-encoded data, determines the full length of the
* encoded value (including the prefix) by inspecting the prefixed metadata.
*
* @param value the rlp-encoded byte string
* @return the length of the encoded data, according to the prefixed metadata
*/
public static int calculateSize(final BytesValue value) {
return RLPDecodingHelpers.rlpElementMetadata(value::get, value.size(), 0).getEncodedSize();
}
} }

@ -82,4 +82,100 @@ class RLPDecodingHelpers {
throw new RLPException(msg, e); throw new RLPException(msg, e);
} }
} }
static RLPElementMetadata rlpElementMetadata(
final LongUnaryOperator byteGetter, final long size, final long elementStart) {
final int prefix = Math.toIntExact(byteGetter.applyAsLong(elementStart)) & 0xFF;
final Kind kind = Kind.of(prefix);
long payloadStart = 0;
int payloadSize = 0;
switch (kind) {
case BYTE_ELEMENT:
payloadStart = elementStart;
payloadSize = 1;
break;
case SHORT_ELEMENT:
payloadStart = elementStart + 1;
payloadSize = prefix - 0x80;
break;
case LONG_ELEMENT:
final int sizeLengthElt = prefix - 0xb7;
payloadStart = elementStart + 1 + sizeLengthElt;
payloadSize = readLongSize(byteGetter, size, elementStart, sizeLengthElt);
break;
case SHORT_LIST:
payloadStart = elementStart + 1;
payloadSize = prefix - 0xc0;
break;
case LONG_LIST:
final int sizeLengthList = prefix - 0xf7;
payloadStart = elementStart + 1 + sizeLengthList;
payloadSize = readLongSize(byteGetter, size, elementStart, sizeLengthList);
break;
}
return new RLPElementMetadata(kind, elementStart, payloadStart, payloadSize);
}
/** The size of the item payload for a "long" item, given the length in bytes of the said size. */
private static int readLongSize(
final LongUnaryOperator byteGetter,
final long sizeOfRlpEncodedByteString,
final long item,
final int sizeLength) {
// We will read sizeLength bytes from item + 1. There must be enough bytes for this or the input
// is corrupted.
if (sizeOfRlpEncodedByteString - (item + 1) < sizeLength) {
throw new CorruptedRLPInputException(
String.format(
"Invalid RLP item: value of size %d has not enough bytes to read the %d "
+ "bytes payload size",
sizeOfRlpEncodedByteString, sizeLength));
}
// That size (which is at least 1 byte by construction) shouldn't have leading zeros.
if (byteGetter.applyAsLong(item + 1) == 0) {
throw new MalformedRLPInputException("Malformed RLP item: size of payload has leading zeros");
}
final int res = RLPDecodingHelpers.extractSizeFromLong(byteGetter, item + 1, sizeLength);
// We should not have had the size written separately if it was less than 56 bytes long.
if (res < 56) {
throw new MalformedRLPInputException(
String.format("Malformed RLP item: written as a long item, but size %d < 56 bytes", res));
}
return res;
}
static class RLPElementMetadata {
final Kind kind; // The type of rlp element
final long elementStart; // The index at which this element starts
final long payloadStart; // The index at which the payload of this element starts
final int payloadSize; // The size of the paylod
RLPElementMetadata(
final Kind kind, final long elementStart, final long payloadStart, final int payloadSize) {
this.kind = kind;
this.elementStart = elementStart;
this.payloadStart = payloadStart;
this.payloadSize = payloadSize;
}
/** @return the size of the byte string holding the rlp-encoded value and metadata */
int getEncodedSize() {
return Math.toIntExact(elementEnd() - elementStart + 1);
}
/**
* The index of the last byte of the rlp encoded element at startIndex
*
* @return
*/
long elementEnd() {
return payloadStart + payloadSize - 1;
}
}
} }

@ -12,6 +12,7 @@
*/ */
package tech.pegasys.pantheon.ethereum.rlp; package tech.pegasys.pantheon.ethereum.rlp;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@ -156,9 +157,13 @@ public class BytesValueRLPInputTest {
assertLongScalar(1024L, h("0x820400")); assertLongScalar(1024L, h("0x820400"));
} }
@Test(expected = RLPException.class) @Test
public void longScalar_NegativeLong() { public void longScalar_NegativeLong() {
assertLongScalar(-1L, h("0xFFFFFFFFFFFFFFFF")); BytesValue bytes = h("0x88FFFFFFFFFFFFFFFF");
final RLPInput in = RLP.input(bytes);
assertThatThrownBy(in::readLongScalar)
.isInstanceOf(RLPException.class)
.hasMessageStartingWith("long scalar -1 is not non-negative");
} }
private void assertLongScalar(final long expected, final BytesValue toTest) { private void assertLongScalar(final long expected, final BytesValue toTest) {
@ -438,11 +443,69 @@ public class BytesValueRLPInputTest {
in.leaveList(true); in.leaveList(true);
} }
@Test(expected = RLPException.class) @Test
public void leaveListEarly() { public void leaveListEarly() {
final RLPInput in = RLP.input(h("0xc80102c51112c22122")); final RLPInput in = RLP.input(h("0xc80102c51112c22122"));
assertEquals(3, in.enterList()); assertEquals(3, in.enterList());
assertEquals(0x01, in.readByte()); assertEquals(0x01, in.readByte());
in.leaveList(false); assertThatThrownBy(() -> in.leaveList(false))
.isInstanceOf(RLPException.class)
.hasMessageStartingWith("Not at the end of the current list");
}
@Test
public void failsWhenPayloadSizeIsTruncated() {
// The prefix B9 indicates this is a long value that requires 2 bytes to encode the payload size
// Only 1 byte follows the prefix
BytesValue bytes = h("0xB901");
assertThatThrownBy(() -> RLP.input(bytes))
.isInstanceOf(RLPException.class)
.hasRootCauseInstanceOf(CorruptedRLPInputException.class)
.hasMessageContaining(
"value of size 2 has not enough bytes to read the 2 bytes payload size ");
}
@Test
public void failsWhenPayloadSizeHasLeadingZeroes() {
// Sanity check correctly encoded value: a byte string of 56 bytes, requiring 1 byte to encode
// size 56
final BytesValue correctBytes =
h(
"0xB8380102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738");
assertEquals(
RLP.input(correctBytes).readBytesValue(),
h(
"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738"));
// Encode same value, but use 2 bytes to represent the size, and pad size value with leading
// zeroes
final BytesValue incorrectBytes =
h(
"0xB900380102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738");
assertThatThrownBy(() -> RLP.input(incorrectBytes))
.isInstanceOf(RLPException.class)
.hasRootCauseInstanceOf(MalformedRLPInputException.class)
.hasMessageContaining("size of payload has leading zeros");
}
@Test
public void failsWhenShortByteStringEncodedAsLongByteString() {
// Sanity check correctly encoded value: a byte string of 55 bytes encoded as short byte string
final BytesValue correctBytes =
h(
"0xB70102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637");
assertEquals(
RLP.input(correctBytes).readBytesValue(),
h(
"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637"));
// Encode same value using long format
final BytesValue incorrectBytes =
h(
"0xB8370102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637");
assertThatThrownBy(() -> RLP.input(incorrectBytes))
.isInstanceOf(RLPException.class)
.hasRootCauseInstanceOf(MalformedRLPInputException.class)
.hasMessageContaining("written as a long item, but size 55 < 56 bytes");
} }
} }

@ -0,0 +1,70 @@
/*
* 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;
import static junit.framework.TestCase.assertEquals;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import org.junit.Test;
public class RLPTest {
@Test
public void calculateSize_singleByteValue() {
int size = RLP.calculateSize(BytesValue.fromHexString("0x01"));
assertEquals(1, size);
}
@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);
}
@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);
}
@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);
}
@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);
}
@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);
}
@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);
}
}

@ -15,6 +15,7 @@ package tech.pegasys.pantheon.util.bytes;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkElementIndex; import static com.google.common.base.Preconditions.checkElementIndex;
import java.nio.ByteBuffer;
import java.security.MessageDigest; import java.security.MessageDigest;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
@ -180,6 +181,36 @@ public interface BytesValue extends Comparable<BytesValue> {
return MutableBytesValue.wrapBuffer(buffer, offset, size); return MutableBytesValue.wrapBuffer(buffer, offset, size);
} }
/**
* Wraps a {@link ByteBuffer} as a {@link BytesValue}.
*
* <p>Note that as the buffer is wrapped, any change to the content of that buffer may be
* reflected in the returned value.
*
* @param buffer The buffer to wrap.
* @return A {@link BytesValue} that exposes the bytes of {@code buffer}.
*/
static BytesValue wrapBuffer(final ByteBuffer buffer) {
return MutableBytesValue.wrapBuffer(buffer, 0, buffer.capacity());
}
/**
* Wraps a {@link ByteBuffer} as a {@link BytesValue}.
*
* <p>Note that as the buffer is wrapped, any change to the content of that buffer may be
* reflected in the returned value.
*
* @param buffer The buffer to wrap.
* @param offset The offset in {@code buffer} from which to expose the bytes in the returned
* value. That is, {@code wrapBuffer(buffer, i, 1).get(0) == buffer.getByte(i)}.
* @param size The size of the returned value.
* @return A {@link BytesValue} that exposes the equivalent of {@code buffer.getBytes(offset,
* offset + size)} (but without copying said bytes).
*/
static BytesValue wrapBuffer(final ByteBuffer buffer, final int offset, final int size) {
return MutableBytesValue.wrapBuffer(buffer, offset, size);
}
/** /**
* Creates a newly allocated value that contains the provided bytes in their provided order. * Creates a newly allocated value that contains the provided bytes in their provided order.
* *
@ -293,6 +324,17 @@ public interface BytesValue extends Comparable<BytesValue> {
*/ */
byte get(int i); byte get(int i);
/**
* Retrieves a byte in this value.
*
* @param i The index of the byte to fetch within the value (0-indexed).
* @return The byte at index {@code i} in this value.
* @throws IndexOutOfBoundsException if {@code i &lt; 0} or {i &gt;= size()}.
*/
default byte get(final long i) {
return get(Math.toIntExact(i));
}
/** /**
* Retrieves the 4 bytes starting at the provided index in this value as an integer. * Retrieves the 4 bytes starting at the provided index in this value as an integer.
* *

@ -25,7 +25,9 @@ class MutableBufferWrappingBytesValue extends AbstractBytesValue implements Muta
MutableBufferWrappingBytesValue(final Buffer buffer, final int offset, final int size) { MutableBufferWrappingBytesValue(final Buffer buffer, final int offset, final int size) {
checkArgument(size >= 0, "Invalid negative length provided"); checkArgument(size >= 0, "Invalid negative length provided");
checkElementIndex(offset, buffer.length()); if (size > 0) {
checkElementIndex(offset, buffer.length());
}
checkArgument( checkArgument(
offset + size <= buffer.length(), offset + size <= buffer.length(),
"Provided length %s is too big: the buffer has size %s and has only %s bytes from %s", "Provided length %s is too big: the buffer has size %s and has only %s bytes from %s",
@ -39,6 +41,10 @@ class MutableBufferWrappingBytesValue extends AbstractBytesValue implements Muta
this.size = size; this.size = size;
} }
MutableBufferWrappingBytesValue(final Buffer buffer) {
this(buffer, 0, buffer.length());
}
@Override @Override
public int size() { public int size() {
return size; return size;
@ -46,11 +52,13 @@ class MutableBufferWrappingBytesValue extends AbstractBytesValue implements Muta
@Override @Override
public byte get(final int i) { public byte get(final int i) {
checkElementIndex(i, size());
return buffer.getByte(offset + i); return buffer.getByte(offset + i);
} }
@Override @Override
public void set(final int i, final byte b) { public void set(final int i, final byte b) {
checkElementIndex(i, size());
buffer.setByte(offset + i, b); buffer.setByte(offset + i, b);
} }

@ -25,7 +25,9 @@ class MutableByteBufWrappingBytesValue extends AbstractBytesValue implements Mut
MutableByteBufWrappingBytesValue(final ByteBuf buffer, final int offset, final int size) { MutableByteBufWrappingBytesValue(final ByteBuf buffer, final int offset, final int size) {
checkArgument(size >= 0, "Invalid negative length provided"); checkArgument(size >= 0, "Invalid negative length provided");
checkElementIndex(offset, buffer.writerIndex()); if (size > 0) {
checkElementIndex(offset, buffer.writerIndex());
}
checkArgument( checkArgument(
offset + size <= buffer.writerIndex(), offset + size <= buffer.writerIndex(),
"Provided length %s is too big: the buffer has size %s and has only %s bytes from %s", "Provided length %s is too big: the buffer has size %s and has only %s bytes from %s",
@ -39,6 +41,10 @@ class MutableByteBufWrappingBytesValue extends AbstractBytesValue implements Mut
this.size = size; this.size = size;
} }
MutableByteBufWrappingBytesValue(final ByteBuf buffer) {
this(buffer, 0, buffer.writerIndex());
}
@Override @Override
public int size() { public int size() {
return size; return size;

@ -0,0 +1,125 @@
/*
* 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.util.bytes;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkElementIndex;
import static com.google.common.base.Preconditions.checkNotNull;
import java.nio.ByteBuffer;
public class MutableByteBufferWrappingBytesValue extends AbstractBytesValue
implements MutableBytesValue {
protected final ByteBuffer bytes;
protected final int offset;
protected final int size;
/**
* Wraps a ByteBuffer given absolute values for offset.
*
* @param bytes the source byte buffer
* @param offset the absolute offset where this value should begin
* @param size the number of bytes to include in this value
*/
MutableByteBufferWrappingBytesValue(final ByteBuffer bytes, final int offset, final int size) {
int bytesSize = bytes.capacity();
checkNotNull(bytes, "Invalid 'null' byte buffer provided");
checkArgument(size >= 0, "Invalid negative length provided");
if (size > 0) {
checkElementIndex(offset, bytesSize);
}
checkArgument(
offset + size <= bytesSize,
"Provided length %s is too big: the value has only %s bytes from offset %s",
size,
bytesSize - offset,
offset);
this.bytes = bytes;
this.offset = offset;
this.size = size;
}
MutableByteBufferWrappingBytesValue(final ByteBuffer bytes) {
this(bytes, 0, bytes.capacity());
}
@Override
public int size() {
return size;
}
@Override
public byte get(final int i) {
checkElementIndex(i, size());
return bytes.get(offset + i);
}
@Override
public BytesValue slice(final int index, final int length) {
if (index == 0 && length == size()) {
return this;
}
if (length == 0) {
return BytesValue.EMPTY;
}
checkElementIndex(index, size());
checkArgument(
index + length <= size(),
"Provided length %s is too big: the value has size %s and has only %s bytes from %s",
length,
size(),
size() - index,
index);
return new MutableByteBufferWrappingBytesValue(bytes, offset + index, length);
}
@Override
public void set(final int i, final byte b) {
checkElementIndex(i, size());
bytes.put(offset + i, b);
}
@Override
public MutableBytesValue mutableSlice(final int index, final int length) {
if (index == 0 && length == size()) {
return this;
}
if (length == 0) {
return MutableBytesValue.EMPTY;
}
checkElementIndex(index, size());
checkArgument(
index + length <= size(),
"Provided length %s is too big: the value has size %s and has only %s bytes from %s",
length,
size(),
size() - index,
index);
return new MutableByteBufferWrappingBytesValue(bytes, offset + index, length);
}
@Override
public byte[] getArrayUnsafe() {
if (bytes.hasArray() && offset == 0 && size == bytes.capacity() && bytes.arrayOffset() == 0) {
return bytes.array();
}
return super.getArrayUnsafe();
}
}

@ -15,6 +15,8 @@ package tech.pegasys.pantheon.util.bytes;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkElementIndex; import static com.google.common.base.Preconditions.checkElementIndex;
import java.nio.ByteBuffer;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.vertx.core.buffer.Buffer; import io.vertx.core.buffer.Buffer;
@ -57,6 +59,39 @@ public interface MutableBytesValue extends BytesValue {
return new MutableArrayWrappingBytesValue(value); return new MutableArrayWrappingBytesValue(value);
} }
/**
* /** Wraps a byte array as a mutable byte value.
*
* <p>This method behave exactly as {@link BytesValue#wrap(byte[],int,int)} except that the result
* is mutable.
*
* @param value The value to wrap.
* @param offset The index (inclusive) in {@code value} of the first byte exposed by the returned
* value. In other words, you will have {@code wrap(value, o, l).get(0) == value[o]}.
* @param length The length of the resulting value.
* @return A {@link BytesValue} that expose the bytes of {@code value} from {@code offset}
* (inclusive) to {@code offset + length} (exclusive).
* @throws IndexOutOfBoundsException if {@code offset &lt; 0 || (value.length > 0 && offset >=
* value.length)}.
* @throws IllegalArgumentException if {@code length &lt; 0 || offset + length > value.length}.
*/
static MutableBytesValue wrap(final byte[] value, final int offset, final int length) {
return new MutableArrayWrappingBytesValue(value, offset, length);
}
/**
* Wraps a full Vert.x {@link Buffer} as a {@link BytesValue}.
*
* <p>Note that as the buffer is wrapped, any change to the content of that buffer may be
* reflected in the returned value.
*
* @param buffer The buffer to wrap.
* @return A {@link BytesValue} that exposes the bytes of {@code buffer}.
*/
static BytesValue wrapBuffer(final Buffer buffer) {
return wrapBuffer(buffer, 0, buffer.length());
}
/** /**
* Wraps a slice of a Vert.x {@link Buffer} as a {@link MutableBytesValue}. * Wraps a slice of a Vert.x {@link Buffer} as a {@link MutableBytesValue}.
* *
@ -78,6 +113,16 @@ public interface MutableBytesValue extends BytesValue {
return new MutableBufferWrappingBytesValue(buffer, offset, size); return new MutableBufferWrappingBytesValue(buffer, offset, size);
} }
/**
* Wraps a full Netty {@link ByteBuf} as a {@link BytesValue}.
*
* @param buffer The buffer to wrap.
* @return A {@link BytesValue} that exposes the bytes of {@code buffer}.
*/
static BytesValue wrapBuffer(final ByteBuf buffer) {
return wrapBuffer(buffer, buffer.readerIndex(), buffer.readableBytes());
}
/** /**
* Wraps a slice of a Netty {@link ByteBuf} as a {@link MutableBytesValue}. * Wraps a slice of a Netty {@link ByteBuf} as a {@link MutableBytesValue}.
* *
@ -95,6 +140,40 @@ public interface MutableBytesValue extends BytesValue {
return new MutableByteBufWrappingBytesValue(buffer, offset, size); return new MutableByteBufWrappingBytesValue(buffer, offset, size);
} }
/**
* Wraps a {@link ByteBuffer} as a {@link BytesValue}.
*
* <p>Note that as the buffer is wrapped, any change to the content of that buffer may be
* reflected in the returned value.
*
* @param buffer The buffer to wrap.
* @return A {@link BytesValue} that exposes the bytes of {@code buffer}.
*/
static BytesValue wrapBuffer(final ByteBuffer buffer) {
return MutableBytesValue.wrapBuffer(buffer, 0, buffer.capacity());
}
/**
* Wraps a slice of a {@link ByteBuffer} as a {@link MutableBytesValue}.
*
* <p>Note that as the buffer is wrapped, any change to the content of that buffer may be
* reflected in the returned value, and any change to the returned value will be reflected in the
* buffer.
*
* @param buffer The buffer to wrap.
* @param offset The offset in {@code buffer} from which to expose the bytes in the returned
* value. That is, {@code wrapBuffer(buffer, i, 1).get(0) == buffer.getByte(i)}.
* @param size The size of the returned value.
* @return A {@link MutableBytesValue} that exposes (reading and writing) the bytes in {@code
* buffer} from {@code offset} (inclusive) to {@code offset + size} (exclusive).
*/
static MutableBytesValue wrapBuffer(final ByteBuffer buffer, final int offset, final int size) {
if (size == 0) {
return EMPTY;
}
return new MutableByteBufferWrappingBytesValue(buffer, offset, size);
}
/** /**
* Sets a particular byte in this value. * Sets a particular byte in this value.
* *

@ -0,0 +1,656 @@
/*
* 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.util.bytes;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static tech.pegasys.pantheon.util.bytes.BytesValue.fromHexString;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Collection;
import java.util.function.Function;
import com.google.common.io.BaseEncoding;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.vertx.core.buffer.Buffer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class BytesValueImplementationsTest {
private final BytesValueCreator creator;
private final BytesValueSliceCreator sliceCreator;
public BytesValueImplementationsTest(
final String name,
final BytesValueCreator creator,
final BytesValueSliceCreator sliceCreator) {
this.creator = creator;
this.sliceCreator = sliceCreator;
}
@Parameters(name = "{0}")
public static Collection<Object[]> data() {
return Arrays.asList(
new Object[][] {
{
"BytesValue.wrap()",
(BytesValueCreator) BytesValue::wrap,
(BytesValueSliceCreator) BytesValue::wrap
},
{
"BytesValue.of()",
(BytesValueCreator) BytesValue::of,
// There isn't really an analogue of slice for of, so just slice it
(BytesValueSliceCreator) (b, start, len) -> BytesValue.of(b).slice(start, len)
},
{
"BytesValue.wrapBuffer() (Vertx Buffer)",
(BytesValueCreator) (b) -> BytesValue.wrapBuffer(buffer(b)),
(BytesValueSliceCreator) (b, start, len) -> BytesValue.wrapBuffer(buffer(b), start, len)
},
{
"BytesValue.wrapBuffer() (Netty ByteBuf)",
(BytesValueCreator) (b) -> BytesValue.wrapBuffer(byteBuf(b)),
(BytesValueSliceCreator)
(b, start, len) -> BytesValue.wrapBuffer(byteBuf(b), start, len)
},
{
"BytesValue.wrapBuffer() (nio ByteBuffer)",
(BytesValueCreator) (b) -> BytesValue.wrapBuffer(byteBuffer(b)),
(BytesValueSliceCreator)
(b, start, len) -> BytesValue.wrapBuffer(byteBuffer(b), start, len)
},
{
"MutableBytesValue.wrap()",
(BytesValueCreator) MutableBytesValue::wrap,
(BytesValueSliceCreator) MutableBytesValue::wrap
},
{
"MutableBytesValue.wrapBuffer() (Vertx Buffer)",
(BytesValueCreator) (b) -> MutableBytesValue.wrapBuffer(buffer(b)),
(BytesValueSliceCreator)
(b, start, len) -> MutableBytesValue.wrapBuffer(buffer(b), start, len)
},
{
"MutableBytesValue.wrapBuffer() (Netty ByteBuf)",
(BytesValueCreator) (b) -> MutableBytesValue.wrapBuffer(byteBuf(b)),
(BytesValueSliceCreator)
(b, start, len) -> MutableBytesValue.wrapBuffer(byteBuf(b), start, len)
},
{
"MutableBytesValue.wrapBuffer() (nio ByteBuffer)",
(BytesValueCreator) (b) -> MutableBytesValue.wrapBuffer(byteBuffer(b)),
(BytesValueSliceCreator)
(b, start, len) -> MutableBytesValue.wrapBuffer(byteBuffer(b), start, len)
},
{
ArrayWrappingBytesValue.class.getSimpleName(),
(BytesValueCreator) ArrayWrappingBytesValue::new,
(BytesValueSliceCreator) ArrayWrappingBytesValue::new
},
{
MutableArrayWrappingBytesValue.class.getSimpleName(),
(BytesValueCreator) MutableArrayWrappingBytesValue::new,
(BytesValueSliceCreator) MutableArrayWrappingBytesValue::new
},
{
MutableBufferWrappingBytesValue.class.getSimpleName(),
(BytesValueCreator) (b) -> new MutableBufferWrappingBytesValue(buffer(b)),
(BytesValueSliceCreator)
(b, start, len) -> new MutableBufferWrappingBytesValue(buffer(b), start, len)
},
{
MutableByteBufWrappingBytesValue.class.getSimpleName(),
(BytesValueCreator) (b) -> new MutableByteBufWrappingBytesValue(byteBuf(b)),
(BytesValueSliceCreator)
(b, start, len) -> new MutableByteBufWrappingBytesValue(byteBuf(b), start, len),
},
{
MutableByteBufferWrappingBytesValue.class.getSimpleName(),
(BytesValueCreator) (b) -> new MutableByteBufferWrappingBytesValue(byteBuffer(b)),
(BytesValueSliceCreator)
(b, start, len) ->
new MutableByteBufferWrappingBytesValue(byteBuffer(b), start, len)
}
});
}
private static ByteBuffer byteBuffer(final byte[] bytes) {
return ByteBuffer.wrap(bytes);
}
private static ByteBuf byteBuf(final byte[] bytes) {
return Unpooled.copiedBuffer(bytes);
}
private static Buffer buffer(final byte[] bytes) {
return Buffer.buffer(bytes);
}
private static Buffer hexToBuffer(final String hex) {
return Buffer.buffer(fromHexString(hex).getArrayUnsafe());
}
private static ByteBuf hexToByteBuf(final String hex) {
final byte[] bytes = fromHexString(hex).getArrayUnsafe();
return Unpooled.unreleasableBuffer(Unpooled.buffer(bytes.length, Integer.MAX_VALUE))
.writeBytes(bytes);
}
private BytesValue fromHex(final String hex) {
String hexVal = hex;
if (hex.substring(0, 2).equals("0x")) {
hexVal = hex.substring((2));
}
byte[] bytes = BaseEncoding.base16().decode(hexVal);
return creator.create(bytes);
}
@Test
public void createInstance() {
assertEquals(BytesValue.EMPTY, creator.create(new byte[0]));
assertCreateInstance(new byte[10]);
assertCreateInstance(new byte[] {1});
assertCreateInstance(new byte[] {1, 2, 3, 4});
assertCreateInstance(new byte[] {-1, 127, -128});
}
private void assertCreateInstance(final byte[] bytes) {
final BytesValue value = creator.create(bytes);
assertEquals(bytes.length, value.size());
assertArrayEquals(bytes, value.extractArray());
}
@Test(expected = NullPointerException.class)
public void testWrapNull() {
creator.create(null);
}
@Test
public void createSlice() {
assertEquals(BytesValue.EMPTY, sliceCreator.create(new byte[0], 0, 0));
assertEquals(BytesValue.EMPTY, sliceCreator.create(new byte[] {1, 2, 3}, 0, 0));
assertEquals(BytesValue.EMPTY, sliceCreator.create(new byte[] {1, 2, 3}, 2, 0));
assertSliceCreated(new byte[] {1, 2, 3, 4}, 0, 4);
assertSliceCreated(new byte[] {1, 2, 3, 4}, 0, 2);
assertSliceCreated(new byte[] {1, 2, 3, 4}, 2, 1);
assertSliceCreated(new byte[] {1, 2, 3, 4}, 2, 2);
}
private void assertSliceCreated(final byte[] bytes, final int offset, final int length) {
final BytesValue value = sliceCreator.create(bytes, offset, length);
assertEquals(length, value.size());
assertArrayEquals(Arrays.copyOfRange(bytes, offset, offset + length), value.extractArray());
}
@Test(expected = NullPointerException.class)
public void createSliceFromNull() {
sliceCreator.create(null, 0, 2);
}
@Test(expected = IndexOutOfBoundsException.class)
public void createSliceNegativeOffset() {
assertSliceCreated(new byte[] {1, 2, 3, 4}, -1, 4);
}
@Test(expected = IndexOutOfBoundsException.class)
public void createSliceOutOfBoundOffset() {
assertSliceCreated(new byte[] {1, 2, 3, 4}, 5, 1);
}
@Test
public void createSliceNegativeLength() {
assertThatThrownBy(() -> assertSliceCreated(new byte[] {1, 2, 3, 4}, 0, -2))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Invalid negative length provided");
}
@Test
public void createSliceTooBig() {
assertThatThrownBy(() -> assertSliceCreated(new byte[] {1, 2, 3, 4}, 2, 3))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageStartingWith("Provided length 3 is too big");
}
@Test
public void sizes() {
assertEquals(0, creator.create(new byte[0]).size());
assertEquals(1, creator.create(new byte[1]).size());
assertEquals(10, creator.create(new byte[10]).size());
}
@Test
public void gets() {
final BytesValue v = creator.create(new byte[] {1, 2, 3, 4});
assertEquals(1, v.get(0));
assertEquals(2, v.get(1));
assertEquals(3, v.get(2));
assertEquals(4, v.get(3));
}
@Test(expected = IndexOutOfBoundsException.class)
public void getNegativeIndex() {
creator.create(new byte[] {1, 2, 3, 4}).get(-1);
}
@Test(expected = IndexOutOfBoundsException.class)
public void getOutOfBound() {
creator.create(new byte[] {1, 2, 3, 4}).get(4);
}
@Test
public void getInt() {
final BytesValue value = creator.create(new byte[] {0, 0, 1, 0, -1, -1, -1, -1});
// 0x00000100 = 256
assertEquals(256, value.getInt(0));
// 0x000100FF = 65536 + 255 = 65791
assertEquals(65791, value.getInt(1));
// 0x0100FFFF = 16777216 (2^24) + (65536 - 1) = 16842751
assertEquals(16842751, value.getInt(2));
// 0xFFFFFFFF = -1
assertEquals(-1, value.getInt(4));
}
@Test(expected = IndexOutOfBoundsException.class)
public void getIntNegativeIndex() {
creator.create(new byte[] {1, 2, 3, 4}).getInt(-1);
}
@Test(expected = IndexOutOfBoundsException.class)
public void getIntOutOfBound() {
creator.create(new byte[] {1, 2, 3, 4}).getInt(4);
}
@Test
public void getIntNotEnoughBytes() {
assertThatThrownBy(() -> creator.create(new byte[] {1, 2, 3, 4}).getInt(1))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Value of size 4 has not enough bytes to read a 4 bytes int from index 1");
}
@Test
public void getLong() {
final BytesValue value1 = creator.create(new byte[] {0, 0, 1, 0, -1, -1, -1, -1, 0, 0});
// 0x00000100FFFFFFFF = (2^40) + (2^32) - 1 = 1103806595071
assertEquals(1103806595071L, value1.getLong(0));
// 0x 000100FFFFFFFF00 = (2^48) + (2^40) - 1 - 255 = 282574488338176
assertEquals(282574488338176L, value1.getLong(1));
final BytesValue value2 = creator.create(new byte[] {-1, -1, -1, -1, -1, -1, -1, -1});
assertEquals(-1L, value2.getLong(0));
}
@Test(expected = IndexOutOfBoundsException.class)
public void getLongNegativeIndex() {
creator.create(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}).getLong(-1);
}
@Test(expected = IndexOutOfBoundsException.class)
public void getLongOutOfBound() {
creator.create(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}).getLong(8);
}
@Test
public void getLongNotEnoughBytes() {
assertThatThrownBy(() -> creator.create(new byte[] {1, 2, 3, 4}).getLong(0))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Value of size 4 has not enough bytes to read a 8 bytes long from index 0");
}
@Test(expected = IndexOutOfBoundsException.class)
public void sliceNegativeOffset() {
fromHex("0x012345").slice(-1, 2);
}
@Test(expected = IndexOutOfBoundsException.class)
public void sliceOffsetOutOfBound() {
fromHex("0x012345").slice(3, 2);
}
@Test
public void sliceTooLong() {
assertThatThrownBy(() -> fromHex("0x012345").slice(1, 3))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage(
"Provided length 3 is too big: the value has size 3 and has only 2 bytes from 1");
}
@Test
public void rangedSlice() {
assertEquals(fromHex("0x"), fromHex("0x0123456789").slice(0, 0));
assertEquals(fromHex("0x"), fromHex("0x0123456789").slice(2, 0));
assertEquals(fromHex("0x01"), fromHex("0x0123456789").slice(0, 1));
assertEquals(fromHex("0x0123"), fromHex("0x0123456789").slice(0, 2));
assertEquals(fromHex("0x4567"), fromHex("0x0123456789").slice(2, 2));
assertEquals(fromHex("0x23456789"), fromHex("0x0123456789").slice(1, 4));
}
@Test
public void appending() {
assertAppendTo(BytesValue.EMPTY, Buffer.buffer(), BytesValue.EMPTY);
assertAppendTo(BytesValue.EMPTY, hexToBuffer("0x1234"), fromHex("0x1234"));
assertAppendTo(fromHex("0x1234"), Buffer.buffer(), fromHex("0x1234"));
assertAppendTo(fromHex("0x5678"), hexToBuffer("0x1234"), fromHex("0x12345678"));
}
private void assertAppendTo(
final BytesValue toAppend, final Buffer buffer, final BytesValue expected) {
toAppend.appendTo(buffer);
assertEquals(expected, BytesValue.wrap(buffer.getBytes()));
}
@Test
public void appendingToByteBuf() {
final byte[] bytes0 = new byte[0];
final byte[] bytes1 = new byte[0];
assertAppendToByteBuf(
BytesValue.EMPTY,
Unpooled.unreleasableBuffer(Unpooled.buffer(bytes0.length, Integer.MAX_VALUE))
.writeBytes(bytes0),
BytesValue.EMPTY);
assertAppendToByteBuf(BytesValue.EMPTY, hexToByteBuf("0x1234"), fromHex("0x1234"));
assertAppendToByteBuf(
fromHex("0x1234"),
Unpooled.unreleasableBuffer(Unpooled.buffer(bytes1.length, Integer.MAX_VALUE))
.writeBytes(bytes1),
fromHex("0x1234"));
assertAppendToByteBuf(fromHex("0x5678"), hexToByteBuf("0x1234"), fromHex("0x12345678"));
}
private void assertAppendToByteBuf(
final BytesValue toAppend, final ByteBuf buffer, final BytesValue expected) {
toAppend.appendTo(buffer);
final byte[] arr = new byte[buffer.writerIndex()];
buffer.getBytes(0, arr);
assertEquals(expected, BytesValue.wrap(arr));
}
@SuppressWarnings("DoNotInvokeMessageDigestDirectly")
@Test
public void update() throws NoSuchAlgorithmException {
// Digest the same byte array in 4 ways:
// 1) directly from the array
// 2) after wrapped using the update() method
// 3) after wrapped and copied using the update() method
// 4) after wrapped but getting the byte manually
// and check all compute the same digest.
final MessageDigest md1 = MessageDigest.getInstance("SHA-1");
final MessageDigest md2 = MessageDigest.getInstance("SHA-1");
final MessageDigest md3 = MessageDigest.getInstance("SHA-1");
final MessageDigest md4 = MessageDigest.getInstance("SHA-1");
final byte[] toDigest = new BigInteger("12324029423415041783577517238472017314").toByteArray();
final BytesValue wrapped = creator.create(toDigest);
final byte[] digest1 = md1.digest(toDigest);
wrapped.update(md2);
final byte[] digest2 = md2.digest();
wrapped.copy().update(md3);
final byte[] digest3 = md3.digest();
for (int i = 0; i < wrapped.size(); i++) md4.update(wrapped.get(i));
final byte[] digest4 = md4.digest();
assertArrayEquals(digest1, digest2);
assertArrayEquals(digest1, digest3);
assertArrayEquals(digest1, digest4);
}
@Test
public void asString() {
assertEquals("0x", BytesValue.EMPTY.toString());
assertEquals("0x01", creator.create(new byte[] {1}).toString());
assertEquals("0x0aff03", creator.create(new byte[] {0x0a, (byte) 0xff, 0x03}).toString());
}
@Test
public void zero() {
assertTrue(BytesValue.EMPTY.isZero());
assertTrue(creator.create(new byte[] {0}).isZero());
assertTrue(creator.create(new byte[] {0, 0, 0}).isZero());
assertFalse(creator.create(new byte[] {1}).isZero());
assertFalse(creator.create(new byte[] {1, 0, 0}).isZero());
assertFalse(creator.create(new byte[] {0, 0, 1}).isZero());
assertFalse(creator.create(new byte[] {0, 0, 1, 0, 0}).isZero());
}
@Test
public void findsCommonPrefix() {
final BytesValue v = creator.create(new byte[] {1, 2, 3, 4, 5, 6, 7});
final BytesValue o = creator.create(new byte[] {1, 2, 3, 4, 4, 3, 2});
assertThat(v.commonPrefixLength(o)).isEqualTo(4);
assertThat(v.commonPrefix(o)).isEqualTo(creator.create(new byte[] {1, 2, 3, 4}));
}
@Test
public void findsCommonPrefixOfShorter() {
final BytesValue v = creator.create(new byte[] {1, 2, 3, 4, 5, 6, 7});
final BytesValue o = creator.create(new byte[] {1, 2, 3, 4});
assertThat(v.commonPrefixLength(o)).isEqualTo(4);
assertThat(v.commonPrefix(o)).isEqualTo(creator.create(new byte[] {1, 2, 3, 4}));
}
@Test
public void findsCommonPrefixOfLonger() {
final BytesValue v = creator.create(new byte[] {1, 2, 3, 4});
final BytesValue o = creator.create(new byte[] {1, 2, 3, 4, 4, 3, 2});
assertThat(v.commonPrefixLength(o)).isEqualTo(4);
assertThat(v.commonPrefix(o)).isEqualTo(creator.create(new byte[] {1, 2, 3, 4}));
}
@Test
public void findsCommonPrefixOfSliced() {
final BytesValue v = creator.create(new byte[] {1, 2, 3, 4}).slice(2, 2);
final BytesValue o = creator.create(new byte[] {3, 4, 3, 3, 2}).slice(3, 2);
assertThat(v.commonPrefixLength(o)).isEqualTo(1);
assertThat(v.commonPrefix(o)).isEqualTo(creator.create(new byte[] {3}));
}
@Test
public void slideToEnd() {
assertThat(creator.create(new byte[] {1, 2, 3, 4}).slice(0))
.isEqualTo(creator.create(new byte[] {1, 2, 3, 4}));
assertThat(creator.create(new byte[] {1, 2, 3, 4}).slice(1))
.isEqualTo(creator.create(new byte[] {2, 3, 4}));
assertThat(creator.create(new byte[] {1, 2, 3, 4}).slice(2))
.isEqualTo(creator.create(new byte[] {3, 4}));
assertThat(creator.create(new byte[] {1, 2, 3, 4}).slice(3))
.isEqualTo(creator.create(new byte[] {4}));
}
@Test
public void slicePastEndReturnsEmpty() {
assertThat(creator.create(new byte[] {1, 2, 3, 4}).slice(4)).isEqualTo(BytesValue.EMPTY);
assertThat(creator.create(new byte[] {1, 2, 3, 4}).slice(5)).isEqualTo(BytesValue.EMPTY);
}
@Test
public void arrayExtraction() {
// extractArray() and getArrayUnsafe() have essentially the same contract...
assertArrayExtraction(BytesValue::extractArray);
assertArrayExtraction(BytesValue::getArrayUnsafe);
// But on top of the basic, extractArray() guarantees modifying the returned array is safe from
// impacting the original value (not that getArrayUnsafe makes no guarantees here one way or
// another, so there is nothing to test).
final byte[] orig = new byte[] {1, 2, 3, 4};
final BytesValue value = creator.create(orig);
final byte[] extracted = value.extractArray();
assertArrayEquals(extracted, orig);
Arrays.fill(extracted, (byte) -1);
assertArrayEquals(new byte[] {-1, -1, -1, -1}, extracted);
assertArrayEquals(new byte[] {1, 2, 3, 4}, orig);
assertEquals(creator.create(new byte[] {1, 2, 3, 4}), value);
}
private void assertArrayExtraction(final Function<BytesValue, byte[]> extractor) {
assertArrayEquals(new byte[0], extractor.apply(BytesValue.EMPTY));
final byte[][] toTest =
new byte[][] {new byte[] {1}, new byte[] {1, 2, 3, 4, 5, 6}, new byte[] {-1, -1, 0, -1}};
for (final byte[] array : toTest) {
assertArrayEquals(array, extractor.apply(creator.create(array)));
}
// Test slightly more complex interactions
assertArrayEquals(
new byte[] {3, 4}, extractor.apply(creator.create(new byte[] {1, 2, 3, 4, 5}).slice(2, 2)));
assertArrayEquals(
new byte[] {}, extractor.apply(creator.create(new byte[] {1, 2, 3, 4, 5}).slice(2, 0)));
}
@Test
public void testBytesValuesComparatorReturnsMatchUnsignedValueByteValue() {
final BytesValue big = creator.create(new byte[] {(byte) 129});
final BytesValue small = creator.create(new byte[] {127});
final BytesValue otherSmall = creator.create(new byte[] {127});
assertThat(big.compareTo(small)).isEqualTo(1);
assertThat(small.compareTo(big)).isEqualTo(-1);
assertThat(small.compareTo(otherSmall)).isEqualTo(0);
}
@Test
public void rangedMutableCopy() {
final BytesValue v = fromHex("0x012345");
final MutableBytesValue mutableCopy = v.mutableCopy();
// Initially, copy must be equal.
assertEquals(mutableCopy, v);
// Upon modification, original should not have been modified.
mutableCopy.set(0, (byte) -1);
assertNotEquals(mutableCopy, v);
assertEquals(fromHex("0x012345"), v);
assertEquals(fromHex("0xFF2345"), mutableCopy);
}
@Test
public void copying() {
MutableBytesValue dest;
// The follow does nothing, but simply making sure it doesn't throw.
dest = MutableBytesValue.EMPTY;
BytesValue.EMPTY.copyTo(dest);
assertEquals(BytesValue.EMPTY, dest);
dest = MutableBytesValue.create(1);
creator.create(new byte[] {1}).copyTo(dest);
assertEquals(fromHex("0x01"), dest);
dest = MutableBytesValue.create(1);
creator.create(new byte[] {10}).copyTo(dest);
assertEquals(fromHex("0x0A"), dest);
dest = MutableBytesValue.create(2);
creator.create(new byte[] {(byte) 0xff, 0x03}).copyTo(dest);
assertEquals(fromHex("0xFF03"), dest);
dest = MutableBytesValue.create(4);
creator.create(new byte[] {(byte) 0xff, 0x03}).copyTo(dest.mutableSlice(1, 2));
assertEquals(fromHex("0x00FF0300"), dest);
}
@Test
public void copyingToTooSmall() {
assertThatThrownBy(
() -> creator.create(new byte[] {1, 2, 3}).copyTo(MutableBytesValue.create(2)))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Cannot copy 3 bytes to destination of non-equal size 2");
}
@Test
public void copyingToTooBig() {
assertThatThrownBy(
() -> creator.create(new byte[] {1, 2, 3}).copyTo(MutableBytesValue.create(4)))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Cannot copy 3 bytes to destination of non-equal size 4");
}
@Test
public void copyingToWithOffset() {
MutableBytesValue dest;
dest = MutableBytesValue.wrap(new byte[] {1, 2, 3});
BytesValue.EMPTY.copyTo(dest, 0);
assertEquals(fromHex("0x010203"), dest);
dest = MutableBytesValue.wrap(new byte[] {1, 2, 3});
creator.create(new byte[] {1}).copyTo(dest, 1);
assertEquals(fromHex("0x010103"), dest);
dest = MutableBytesValue.wrap(new byte[] {1, 2, 3});
creator.create(new byte[] {2}).copyTo(dest, 0);
assertEquals(fromHex("0x020203"), dest);
dest = MutableBytesValue.wrap(new byte[] {1, 2, 3});
creator.create(new byte[] {1, 1}).copyTo(dest, 1);
assertEquals(fromHex("0x010101"), dest);
dest = MutableBytesValue.create(4);
creator.create(new byte[] {(byte) 0xff, 0x03}).copyTo(dest, 1);
assertEquals(fromHex("0x00FF0300"), dest);
}
@Test
public void copyingToWithOffsetTooSmall() {
assertThatThrownBy(
() -> creator.create(new byte[] {1, 2, 3}).copyTo(MutableBytesValue.create(4), 2))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Cannot copy 3 bytes, destination has only 2 bytes from index 2");
}
@Test(expected = IndexOutOfBoundsException.class)
public void copyingToWithNegativeOffset() {
creator.create(new byte[] {1, 2, 3}).copyTo(MutableBytesValue.create(10), -1);
}
@Test(expected = IndexOutOfBoundsException.class)
public void copyingToWithOutOfBoundIndex() {
creator.create(new byte[] {1, 2, 3}).copyTo(MutableBytesValue.create(10), 10);
}
@FunctionalInterface
private interface BytesValueCreator {
public BytesValue create(byte[] bytes);
}
@FunctionalInterface
private interface BytesValueSliceCreator {
public BytesValue create(byte[] bytes, int start, int length);
}
}

@ -12,26 +12,18 @@
*/ */
package tech.pegasys.pantheon.util.bytes; package tech.pegasys.pantheon.util.bytes;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static tech.pegasys.pantheon.util.bytes.BytesValue.fromHexString; import static tech.pegasys.pantheon.util.bytes.BytesValue.fromHexString;
import static tech.pegasys.pantheon.util.bytes.BytesValue.fromHexStringLenient; import static tech.pegasys.pantheon.util.bytes.BytesValue.fromHexStringLenient;
import static tech.pegasys.pantheon.util.bytes.BytesValue.of; import static tech.pegasys.pantheon.util.bytes.BytesValue.of;
import static tech.pegasys.pantheon.util.bytes.BytesValue.wrap; import static tech.pegasys.pantheon.util.bytes.BytesValue.wrap;
import static tech.pegasys.pantheon.util.bytes.BytesValue.wrapBuffer; import static tech.pegasys.pantheon.util.bytes.BytesValue.wrapBuffer;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays; import java.util.Arrays;
import java.util.function.Function;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.vertx.core.buffer.Buffer; import io.vertx.core.buffer.Buffer;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
@ -45,37 +37,6 @@ public class BytesValueTest {
return fromHexString(hex); return fromHexString(hex);
} }
private static Buffer b(final String hex) {
return Buffer.buffer(fromHexString(hex).getArrayUnsafe());
}
private static ByteBuf bb(final String hex) {
final byte[] bytes = fromHexString(hex).getArrayUnsafe();
return Unpooled.unreleasableBuffer(Unpooled.buffer(bytes.length, Integer.MAX_VALUE))
.writeBytes(bytes);
}
@Test
public void rangeOfWrap() {
assertEquals(BytesValue.EMPTY, wrap(new byte[0]));
assertWrap(new byte[10]);
assertWrap(new byte[] {1});
assertWrap(new byte[] {1, 2, 3, 4});
assertWrap(new byte[] {-1, 127, -128});
}
private static void assertWrap(final byte[] bytes) {
final BytesValue value = wrap(bytes);
assertEquals(bytes.length, value.size());
assertArrayEquals(bytes, value.extractArray());
}
@Test(expected = NullPointerException.class)
public void testWrapNull() {
wrap(null);
}
/** Checks that modifying a wrapped array modifies the value itself. */ /** Checks that modifying a wrapped array modifies the value itself. */
@Test @Test
public void wrapReflectsUpdates() { public void wrapReflectsUpdates() {
@ -92,53 +53,12 @@ public class BytesValueTest {
assertArrayEquals(bytes, value.extractArray()); assertArrayEquals(bytes, value.extractArray());
} }
@Test
public void wrapSlice() {
assertEquals(BytesValue.EMPTY, wrap(new byte[0], 0, 0));
assertEquals(BytesValue.EMPTY, wrap(new byte[] {1, 2, 3}, 0, 0));
assertEquals(BytesValue.EMPTY, wrap(new byte[] {1, 2, 3}, 2, 0));
assertWrapSlice(new byte[] {1, 2, 3, 4}, 0, 4);
assertWrapSlice(new byte[] {1, 2, 3, 4}, 0, 2);
assertWrapSlice(new byte[] {1, 2, 3, 4}, 2, 1);
assertWrapSlice(new byte[] {1, 2, 3, 4}, 2, 2);
}
private static void assertWrapSlice(final byte[] bytes, final int offset, final int length) { private static void assertWrapSlice(final byte[] bytes, final int offset, final int length) {
final BytesValue value = wrap(bytes, offset, length); final BytesValue value = wrap(bytes, offset, length);
assertEquals(length, value.size()); assertEquals(length, value.size());
assertArrayEquals(Arrays.copyOfRange(bytes, offset, offset + length), value.extractArray()); assertArrayEquals(Arrays.copyOfRange(bytes, offset, offset + length), value.extractArray());
} }
@Test(expected = NullPointerException.class)
public void wrapSliceNull() {
wrap(null, 0, 2);
}
@Test(expected = IndexOutOfBoundsException.class)
public void wrapSliceNegativeOffset() {
assertWrapSlice(new byte[] {1, 2, 3, 4}, -1, 4);
}
@Test(expected = IndexOutOfBoundsException.class)
public void wrapSliceOutOfBoundOffset() {
assertWrapSlice(new byte[] {1, 2, 3, 4}, 5, 1);
}
@Test
public void wrapSliceNegativeLength() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Invalid negative length");
assertWrapSlice(new byte[] {1, 2, 3, 4}, 0, -2);
}
@Test
public void wrapSliceTooBig() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Provided length 3 is too big: the value has only 2 bytes from offset 2");
assertWrapSlice(new byte[] {1, 2, 3, 4}, 2, 3);
}
/** /**
* Checks that modifying a wrapped array modifies the value itself, but only if within the wrapped * Checks that modifying a wrapped array modifies the value itself, but only if within the wrapped
* slice. * slice.
@ -192,54 +112,6 @@ public class BytesValueTest {
assertArrayEquals(expected2, res.extractArray()); assertArrayEquals(expected2, res.extractArray());
} }
@Test
public void wrapByteBuf() {
assertEquals(BytesValue.EMPTY, wrapBuffer(Buffer.buffer()));
assertWrapByteBuf(new byte[10]);
assertWrapByteBuf(new byte[] {1});
assertWrapByteBuf(new byte[] {1, 2, 3, 4});
assertWrapByteBuf(new byte[] {-1, 127, -128});
}
private static void assertWrapByteBuf(final byte[] bytes) {
final ByteBuf buffer =
Unpooled.unreleasableBuffer(Unpooled.buffer(bytes.length, Integer.MAX_VALUE))
.writeBytes(bytes);
final BytesValue value = wrapBuffer(buffer);
assertEquals(buffer.writerIndex(), value.size());
final byte[] arr = new byte[buffer.writerIndex()];
buffer.getBytes(0, arr);
assertArrayEquals(arr, value.extractArray());
}
@Test
public void rangeOfWrapBuffer() {
assertEquals(BytesValue.EMPTY, wrapBuffer(Buffer.buffer()));
assertWrapBuffer(new byte[10]);
assertWrapBuffer(new byte[] {1});
assertWrapBuffer(new byte[] {1, 2, 3, 4});
assertWrapBuffer(new byte[] {-1, 127, -128});
}
private static void assertWrapBuffer(final byte[] bytes) {
final Buffer buffer = Buffer.buffer(bytes);
final BytesValue value = wrapBuffer(buffer);
assertEquals(buffer.length(), value.size());
assertArrayEquals(buffer.getBytes(), value.extractArray());
}
@Test(expected = NullPointerException.class)
public void wrapBufferNull() {
wrapBuffer((Buffer) null);
}
@Test(expected = NullPointerException.class)
public void wrapByteBufNull() {
wrapBuffer((ByteBuf) null);
}
/** Checks that modifying a wrapped buffer modifies the value itself. */ /** Checks that modifying a wrapped buffer modifies the value itself. */
@Test @Test
public void wrapBufferReflectsUpdates() { public void wrapBufferReflectsUpdates() {
@ -256,61 +128,6 @@ public class BytesValueTest {
assertArrayEquals(buffer.getBytes(), value.extractArray()); assertArrayEquals(buffer.getBytes(), value.extractArray());
} }
@Test
public void wrapBufferSlice() {
assertEquals(BytesValue.EMPTY, wrapBuffer(Buffer.buffer(new byte[0]), 0, 0));
assertEquals(BytesValue.EMPTY, wrapBuffer(Buffer.buffer(new byte[] {1, 2, 3}), 0, 0));
assertEquals(BytesValue.EMPTY, wrapBuffer(Buffer.buffer(new byte[] {1, 2, 3}), 2, 0));
assertWrapBufferSlice(new byte[] {1, 2, 3, 4}, 0, 4);
assertWrapBufferSlice(new byte[] {1, 2, 3, 4}, 0, 2);
assertWrapBufferSlice(new byte[] {1, 2, 3, 4}, 2, 1);
assertWrapBufferSlice(new byte[] {1, 2, 3, 4}, 2, 2);
}
private static void assertWrapBufferSlice(
final byte[] bytes, final int offset, final int length) {
final Buffer buffer = Buffer.buffer(bytes);
final BytesValue value = wrapBuffer(buffer, offset, length);
assertEquals(length, value.size());
assertArrayEquals(Arrays.copyOfRange(bytes, offset, offset + length), value.extractArray());
}
@Test(expected = NullPointerException.class)
public void wrapBufferSliceNull() {
wrapBuffer((Buffer) null, 0, 2);
}
@Test(expected = NullPointerException.class)
public void wrapByteBufSliceNull() {
wrapBuffer((ByteBuf) null, 0, 2);
}
@Test(expected = IndexOutOfBoundsException.class)
public void wrapBufferSliceNegativeOffset() {
assertWrapBufferSlice(new byte[] {1, 2, 3, 4}, -1, 4);
}
@Test(expected = IndexOutOfBoundsException.class)
public void wrapBufferSliceOutOfBoundOffset() {
assertWrapBufferSlice(new byte[] {1, 2, 3, 4}, 5, 1);
}
@Test
public void wrapBufferSliceNegativeLength() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Invalid negative length");
assertWrapBufferSlice(new byte[] {1, 2, 3, 4}, 0, -2);
}
@Test
public void wrapBufferSliceTooBig() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage(
"Provided length 3 is too big: the buffer has size 4 and has only 2 bytes from 2");
assertWrapBufferSlice(new byte[] {1, 2, 3, 4}, 2, 3);
}
/** /**
* Checks that modifying a wrapped array modifies the value itself, but only if within the wrapped * Checks that modifying a wrapped array modifies the value itself, but only if within the wrapped
* slice. * slice.
@ -393,7 +210,7 @@ public class BytesValueTest {
} }
@Test @Test
public void hxeStringLenientInvalidInput() { public void hexStringLenientInvalidInput() {
thrown.expect(IllegalArgumentException.class); thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Illegal character 'o' found at index 1"); thrown.expectMessage("Illegal character 'o' found at index 1");
fromHexStringLenient("foo"); fromHexStringLenient("foo");
@ -500,403 +317,4 @@ public class BytesValueTest {
thrown.expectMessage("Hex value 0x001F34 is too big: expected at most 2 bytes but got 3"); thrown.expectMessage("Hex value 0x001F34 is too big: expected at most 2 bytes but got 3");
fromHexStringLenient("0x001F34", 2); fromHexStringLenient("0x001F34", 2);
} }
@Test
public void sizes() {
assertEquals(0, wrap(new byte[0]).size());
assertEquals(1, wrap(new byte[1]).size());
assertEquals(10, wrap(new byte[10]).size());
}
@Test
public void gets() {
final BytesValue v = wrap(new byte[] {1, 2, 3, 4});
assertEquals(1, v.get(0));
assertEquals(2, v.get(1));
assertEquals(3, v.get(2));
assertEquals(4, v.get(3));
}
@Test(expected = IndexOutOfBoundsException.class)
public void getNegativeIndex() {
wrap(new byte[] {1, 2, 3, 4}).get(-1);
}
@Test(expected = IndexOutOfBoundsException.class)
public void getOutOfBound() {
wrap(new byte[] {1, 2, 3, 4}).get(4);
}
@Test
public void getInt() {
final BytesValue value = wrap(new byte[] {0, 0, 1, 0, -1, -1, -1, -1});
// 0x00000100 = 256
assertEquals(256, value.getInt(0));
// 0x000100FF = 65536 + 255 = 65791
assertEquals(65791, value.getInt(1));
// 0x0100FFFF = 16777216 (2^24) + (65536 - 1) = 16842751
assertEquals(16842751, value.getInt(2));
// 0xFFFFFFFF = -1
assertEquals(-1, value.getInt(4));
}
@Test(expected = IndexOutOfBoundsException.class)
public void getIntNegativeIndex() {
wrap(new byte[] {1, 2, 3, 4}).getInt(-1);
}
@Test(expected = IndexOutOfBoundsException.class)
public void getIntOutOfBound() {
wrap(new byte[] {1, 2, 3, 4}).getInt(4);
}
@Test
public void getIntNotEnoughBytes() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Value of size 4 has not enough bytes to read a 4 bytes int from index 1");
wrap(new byte[] {1, 2, 3, 4}).getInt(1);
}
@Test
public void getLong() {
final BytesValue value1 = wrap(new byte[] {0, 0, 1, 0, -1, -1, -1, -1, 0, 0});
// 0x00000100FFFFFFFF = (2^40) + (2^32) - 1 = 1103806595071
assertEquals(1103806595071L, value1.getLong(0));
// 0x 000100FFFFFFFF00 = (2^48) + (2^40) - 1 - 255 = 282574488338176
assertEquals(282574488338176L, value1.getLong(1));
final BytesValue value2 = wrap(new byte[] {-1, -1, -1, -1, -1, -1, -1, -1});
assertEquals(-1L, value2.getLong(0));
}
@Test(expected = IndexOutOfBoundsException.class)
public void getLongNegativeIndex() {
wrap(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}).getLong(-1);
}
@Test(expected = IndexOutOfBoundsException.class)
public void getLongOutOfBound() {
wrap(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}).getLong(8);
}
@Test
public void getLongNotEnoughBytes() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage(
"Value of size 4 has not enough bytes to read a 8 bytes long from index 0");
wrap(new byte[] {1, 2, 3, 4}).getLong(0);
}
@Test
public void rangedSlice() {
assertEquals(h("0x"), h("0x0123456789").slice(0, 0));
assertEquals(h("0x"), h("0x0123456789").slice(2, 0));
assertEquals(h("0x01"), h("0x0123456789").slice(0, 1));
assertEquals(h("0x0123"), h("0x0123456789").slice(0, 2));
assertEquals(h("0x4567"), h("0x0123456789").slice(2, 2));
assertEquals(h("0x23456789"), h("0x0123456789").slice(1, 4));
}
@Test(expected = IndexOutOfBoundsException.class)
public void sliceNegativeOffset() {
h("0x012345").slice(-1, 2);
}
@Test(expected = IndexOutOfBoundsException.class)
public void sliceOffsetOutOfBound() {
h("0x012345").slice(3, 2);
}
@Test
public void sliceTooLong() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage(
"Provided length 3 is too big: the value has size 3 and has only 2 bytes from 1");
h("0x012345").slice(1, 3);
}
@Test
public void rangedMutableCopy() {
final BytesValue v = h("0x012345");
final MutableBytesValue mutableCopy = v.mutableCopy();
// Initially, copy must be equal.
assertEquals(mutableCopy, v);
// Upon modification, original should not have been modified.
mutableCopy.set(0, (byte) -1);
assertNotEquals(mutableCopy, v);
assertEquals(h("0x012345"), v);
assertEquals(h("0xFF2345"), mutableCopy);
}
@Test
public void copying() {
MutableBytesValue dest;
// The follow does nothing, but simply making sure it doesn't throw.
dest = MutableBytesValue.EMPTY;
BytesValue.EMPTY.copyTo(dest);
assertEquals(BytesValue.EMPTY, dest);
dest = MutableBytesValue.create(1);
of(1).copyTo(dest);
assertEquals(h("0x01"), dest);
dest = MutableBytesValue.create(1);
of(10).copyTo(dest);
assertEquals(h("0x0A"), dest);
dest = MutableBytesValue.create(2);
of(0xff, 0x03).copyTo(dest);
assertEquals(h("0xFF03"), dest);
dest = MutableBytesValue.create(4);
of(0xff, 0x03).copyTo(dest.mutableSlice(1, 2));
assertEquals(h("0x00FF0300"), dest);
}
@Test
public void copyingToTooSmall() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Cannot copy 3 bytes to destination of non-equal size 2");
of(1, 2, 3).copyTo(MutableBytesValue.create(2));
}
@Test
public void copyingToTooBig() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Cannot copy 3 bytes to destination of non-equal size 4");
of(1, 2, 3).copyTo(MutableBytesValue.create(4));
}
@Test
public void copyingToWithOffset() {
MutableBytesValue dest;
dest = MutableBytesValue.wrap(new byte[] {1, 2, 3});
BytesValue.EMPTY.copyTo(dest, 0);
assertEquals(h("0x010203"), dest);
dest = MutableBytesValue.wrap(new byte[] {1, 2, 3});
of(1).copyTo(dest, 1);
assertEquals(h("0x010103"), dest);
dest = MutableBytesValue.wrap(new byte[] {1, 2, 3});
of(2).copyTo(dest, 0);
assertEquals(h("0x020203"), dest);
dest = MutableBytesValue.wrap(new byte[] {1, 2, 3});
of(1, 1).copyTo(dest, 1);
assertEquals(h("0x010101"), dest);
dest = MutableBytesValue.create(4);
of(0xff, 0x03).copyTo(dest, 1);
assertEquals(h("0x00FF0300"), dest);
}
@Test
public void copyingToWithOffsetTooSmall() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Cannot copy 3 bytes, destination has only 2 bytes from index 2");
of(1, 2, 3).copyTo(MutableBytesValue.create(4), 2);
}
@Test(expected = IndexOutOfBoundsException.class)
public void copyingToWithNegativeOffset() {
of(1, 2, 3).copyTo(MutableBytesValue.create(10), -1);
}
@Test(expected = IndexOutOfBoundsException.class)
public void copyingToWithOutOfBoundIndex() {
of(1, 2, 3).copyTo(MutableBytesValue.create(10), 10);
}
@Test
public void appending() {
assertAppendTo(BytesValue.EMPTY, Buffer.buffer(), BytesValue.EMPTY);
assertAppendTo(BytesValue.EMPTY, b("0x1234"), h("0x1234"));
assertAppendTo(h("0x1234"), Buffer.buffer(), h("0x1234"));
assertAppendTo(h("0x5678"), b("0x1234"), h("0x12345678"));
}
private void assertAppendTo(
final BytesValue toAppend, final Buffer buffer, final BytesValue expected) {
toAppend.appendTo(buffer);
assertEquals(expected, BytesValue.wrap(buffer.getBytes()));
}
@Test
public void appendingToByteBuf() {
final byte[] bytes0 = new byte[0];
final byte[] bytes1 = new byte[0];
assertAppendToByteBuf(
BytesValue.EMPTY,
Unpooled.unreleasableBuffer(Unpooled.buffer(bytes0.length, Integer.MAX_VALUE))
.writeBytes(bytes0),
BytesValue.EMPTY);
assertAppendToByteBuf(BytesValue.EMPTY, bb("0x1234"), h("0x1234"));
assertAppendToByteBuf(
h("0x1234"),
Unpooled.unreleasableBuffer(Unpooled.buffer(bytes1.length, Integer.MAX_VALUE))
.writeBytes(bytes1),
h("0x1234"));
assertAppendToByteBuf(h("0x5678"), bb("0x1234"), h("0x12345678"));
}
private void assertAppendToByteBuf(
final BytesValue toAppend, final ByteBuf buffer, final BytesValue expected) {
toAppend.appendTo(buffer);
final byte[] arr = new byte[buffer.writerIndex()];
buffer.getBytes(0, arr);
assertEquals(expected, BytesValue.wrap(arr));
}
@Test
public void zero() {
assertTrue(BytesValue.EMPTY.isZero());
assertTrue(BytesValue.of(0).isZero());
assertTrue(BytesValue.of(0, 0, 0).isZero());
assertFalse(BytesValue.of(1).isZero());
assertFalse(BytesValue.of(1, 0, 0).isZero());
assertFalse(BytesValue.of(0, 0, 1).isZero());
assertFalse(BytesValue.of(0, 0, 1, 0, 0).isZero());
}
@Test
public void findsCommonPrefix() {
final BytesValue v = BytesValue.of(1, 2, 3, 4, 5, 6, 7);
final BytesValue o = BytesValue.of(1, 2, 3, 4, 4, 3, 2);
assertThat(v.commonPrefixLength(o)).isEqualTo(4);
assertThat(v.commonPrefix(o)).isEqualTo(BytesValue.of(1, 2, 3, 4));
}
@Test
public void findsCommonPrefixOfShorter() {
final BytesValue v = BytesValue.of(1, 2, 3, 4, 5, 6, 7);
final BytesValue o = BytesValue.of(1, 2, 3, 4);
assertThat(v.commonPrefixLength(o)).isEqualTo(4);
assertThat(v.commonPrefix(o)).isEqualTo(BytesValue.of(1, 2, 3, 4));
}
@Test
public void findsCommonPrefixOfLonger() {
final BytesValue v = BytesValue.of(1, 2, 3, 4);
final BytesValue o = BytesValue.of(1, 2, 3, 4, 4, 3, 2);
assertThat(v.commonPrefixLength(o)).isEqualTo(4);
assertThat(v.commonPrefix(o)).isEqualTo(BytesValue.of(1, 2, 3, 4));
}
@Test
public void findsCommonPrefixOfSliced() {
final BytesValue v = BytesValue.of(1, 2, 3, 4).slice(2, 2);
final BytesValue o = BytesValue.of(3, 4, 3, 3, 2).slice(3, 2);
assertThat(v.commonPrefixLength(o)).isEqualTo(1);
assertThat(v.commonPrefix(o)).isEqualTo(BytesValue.of(3));
}
@Test
public void slideToEnd() {
assertThat(BytesValue.of(1, 2, 3, 4).slice(0)).isEqualTo(BytesValue.of(1, 2, 3, 4));
assertThat(BytesValue.of(1, 2, 3, 4).slice(1)).isEqualTo(BytesValue.of(2, 3, 4));
assertThat(BytesValue.of(1, 2, 3, 4).slice(2)).isEqualTo(BytesValue.of(3, 4));
assertThat(BytesValue.of(1, 2, 3, 4).slice(3)).isEqualTo(BytesValue.of(4));
}
@Test
public void slicePastEndReturnsEmpty() {
assertThat(BytesValue.of(1, 2, 3, 4).slice(4)).isEqualTo(BytesValue.EMPTY);
assertThat(BytesValue.of(1, 2, 3, 4).slice(5)).isEqualTo(BytesValue.EMPTY);
}
@SuppressWarnings("DoNotInvokeMessageDigestDirectly")
@Test
public void update() throws NoSuchAlgorithmException {
// Digest the same byte array in 4 ways:
// 1) directly from the array
// 2) after wrapped using the update() method
// 3) after wrapped and copied using the update() method
// 4) after wrapped but getting the byte manually
// and check all compute the same digest.
final MessageDigest md1 = MessageDigest.getInstance("SHA-1");
final MessageDigest md2 = MessageDigest.getInstance("SHA-1");
final MessageDigest md3 = MessageDigest.getInstance("SHA-1");
final MessageDigest md4 = MessageDigest.getInstance("SHA-1");
final byte[] toDigest = new BigInteger("12324029423415041783577517238472017314").toByteArray();
final BytesValue wrapped = wrap(toDigest);
final byte[] digest1 = md1.digest(toDigest);
wrapped.update(md2);
final byte[] digest2 = md2.digest();
wrapped.copy().update(md3);
final byte[] digest3 = md3.digest();
for (int i = 0; i < wrapped.size(); i++) md4.update(wrapped.get(i));
final byte[] digest4 = md4.digest();
assertArrayEquals(digest1, digest2);
assertArrayEquals(digest1, digest3);
assertArrayEquals(digest1, digest4);
}
@Test
public void arrayExtraction() {
// extractArray() and getArrayUnsafe() have essentially the same contract...
assertArrayExtraction(BytesValue::extractArray);
assertArrayExtraction(BytesValue::getArrayUnsafe);
// But on top of the basic, extractArray() guarantees modifying the returned array is safe from
// impacting the original value (not that getArrayUnsafe makes no guarantees here one way or
// another, so there is nothing to test).
final byte[] orig = new byte[] {1, 2, 3, 4};
final BytesValue value = wrap(orig);
final byte[] extracted = value.extractArray();
assertArrayEquals(extracted, orig);
Arrays.fill(extracted, (byte) -1);
assertArrayEquals(new byte[] {-1, -1, -1, -1}, extracted);
assertArrayEquals(new byte[] {1, 2, 3, 4}, orig);
assertEquals(of(1, 2, 3, 4), value);
}
private void assertArrayExtraction(final Function<BytesValue, byte[]> extractor) {
assertArrayEquals(new byte[0], extractor.apply(BytesValue.EMPTY));
final byte[][] toTest =
new byte[][] {new byte[] {1}, new byte[] {1, 2, 3, 4, 5, 6}, new byte[] {-1, -1, 0, -1}};
for (final byte[] array : toTest) {
assertArrayEquals(array, extractor.apply(wrap(array)));
}
// Test slightly more complex interactions
assertArrayEquals(
new byte[] {3, 4}, extractor.apply(wrap(new byte[] {1, 2, 3, 4, 5}).slice(2, 2)));
assertArrayEquals(new byte[] {}, extractor.apply(wrap(new byte[] {1, 2, 3, 4, 5}).slice(2, 0)));
}
@Test
public void asString() {
assertEquals("0x", BytesValue.EMPTY.toString());
assertEquals("0x01", of(1).toString());
assertEquals("0x0aff03", of(0x0a, 0xff, 0x03).toString());
}
@Test
public void testBytesValuesComparatorReturnsMatchUnsignedValueByteValue() {
final BytesValue big = BytesValue.of(129);
final BytesValue small = BytesValue.of(127);
final BytesValue otherSmall = BytesValue.of(127);
assertThat(big.compareTo(small)).isEqualTo(1);
assertThat(small.compareTo(big)).isEqualTo(-1);
assertThat(small.compareTo(otherSmall)).isEqualTo(0);
}
} }

Loading…
Cancel
Save