RLPDecodingHelpers.java
/*
* Copyright 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.rlp;
import java.util.function.IntUnaryOperator;
import java.util.function.LongUnaryOperator;
/**
* Helper static methods to facilitate RLP decoding <b>within this package</b>. Neither this class
* nor any of its method are meant to be exposed publicly, they are too low level.
*/
class RLPDecodingHelpers {
/** The kind of items an RLP item can be. */
enum Kind {
BYTE_ELEMENT,
SHORT_ELEMENT,
LONG_ELEMENT,
SHORT_LIST,
LONG_LIST;
static Kind of(final int prefix) {
if (prefix <= 0x7F) {
return Kind.BYTE_ELEMENT;
} else if (prefix <= 0xb7) {
return Kind.SHORT_ELEMENT;
} else if (prefix <= 0xbf) {
return Kind.LONG_ELEMENT;
} else if (prefix <= 0xf7) {
return Kind.SHORT_LIST;
} else {
return Kind.LONG_LIST;
}
}
boolean isList() {
switch (this) {
case SHORT_LIST:
case LONG_LIST:
return true;
default:
return false;
}
}
}
/** Read from the provided offset a size of the provided length, assuming this is enough bytes. */
static int extractSize(final IntUnaryOperator getter, final int offset, final int sizeLength) {
int res = 0;
int shift = 0;
for (int i = 0; i < sizeLength; i++) {
res |= (getter.applyAsInt(offset + (sizeLength - 1) - i) & 0xFF) << shift;
shift += 8;
}
return res;
}
/** Read from the provided offset a size of the provided length, assuming this is enough bytes. */
private static int extractSizeFromLongItem(
final LongUnaryOperator getter, final long offset, final int sizeLength) {
if (sizeLength > 4) {
throw new RLPException(
"RLP item at offset "
+ offset
+ " with size value consuming "
+ sizeLength
+ " bytes exceeds max supported size of "
+ Integer.MAX_VALUE);
}
long res = 0;
int shift = 0;
for (int i = 0; i < sizeLength; i++) {
res |= (getter.applyAsLong(offset + (sizeLength - 1) - i) & 0xFF) << shift;
shift += 8;
}
try {
return Math.toIntExact(res);
} catch (final ArithmeticException e) {
throw new RLPException(
"RLP item at offset "
+ offset
+ " with size value consuming "
+ sizeLength
+ " bytes exceeds max supported size of "
+ Integer.MAX_VALUE,
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.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) {
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 payload
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;
}
/**
* The size of the byte string holding the rlp-encoded value and metadata
*
* @return the size of the byte string holding the rlp-encoded value and metadata
*/
int getEncodedSize() {
final long encodedSize = elementEnd() - elementStart + 1;
try {
return Math.toIntExact(encodedSize);
} catch (final ArithmeticException e) {
throw new RLPException(
String.format(
"RLP item exceeds max supported size of %d: %d", Integer.MAX_VALUE, encodedSize),
e);
}
}
/**
* The index of the last byte of the rlp encoded element at startIndex
*
* @return the index of the last byte of the rlp encoded element at startIndex
*/
long elementEnd() {
return payloadStart + payloadSize - 1;
}
}
}