RLPOutput.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.math.BigInteger;
import java.net.InetAddress;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.MutableBytes;
import org.apache.tuweni.units.bigints.UInt256Value;
import org.apache.tuweni.units.bigints.UInt64Value;
/**
* An output used to encode data in RLP encoding.
*
* <p>An RLP "value" is fundamentally an {@code Item} defined the following way:
*
* <pre>
* Item ::= List | Bytes
* List ::= [ Item, ... , Item ]
* Bytes ::= a binary value (comprised of an arbitrary number of bytes).
* </pre>
*
* In other words, RLP encodes binary data organized in arbitrary nested lists.
*
* <p>A {@link RLPOutput} thus provides methods to write both lists and binary values. A list is
* started by calling {@link #startList()} and ended by {@link #endList()}. Lists can be nested in
* other lists in arbitrary ways. Binary values can be written directly with {@link
* #writeBytes(Bytes)}, but the {@link RLPOutput} interface provides a wealth of convenience methods
* to write specific types of data with a specific encoding.
*
* <p>Amongst the methods to write binary data, some methods are provided to write "scalar". A
* scalar should simply be understood as a positive integer that is encoded with no leading zeros.
* In other word, if an integer is written with a "Scalar" method variant, that number will be
* encoded with the minimum number of bytes necessary to represent it.
*
* <p>The {@link RLPOutput} only defines the interface for writing data meant to be RLP encoded.
* Getting the finally encoded output will depend on the concrete implementation, see {@link
* BytesValueRLPOutput} for instance.
*/
public interface RLPOutput {
/** Starts a new list. */
void startList();
/**
* Ends the current list.
*
* @throws IllegalStateException if no list has been previously started with {@link #startList()}
* (or any started had already be ended).
*/
void endList();
/**
* Writes a new value.
*
* @param v The value to write.
*/
void writeBytes(Bytes v);
/**
* Writes a scalar 64 bits unsigned int (encoded without leading zeros)
*
* @param v unsigned 64 bit integer value
*/
default void writeUInt64Scalar(final UInt64Value<?> v) {
writeBytes(v.toBytes().trimLeadingZeros());
}
/**
* Writes a scalar (encoded with no leading zeroes).
*
* @param v The scalar to write.
*/
default void writeUInt256Scalar(final UInt256Value<?> v) {
writeBytes(v.trimLeadingZeros());
}
/**
* Writes a RLP "null", that is an empty value.
*
* <p>This is a shortcut for {@code writeBytes(Bytes.EMPTY)}.
*/
default void writeNull() {
writeBytes(Bytes.EMPTY);
}
/**
* Writes a scalar (encoded with no leading zeroes).
*
* @param v The scalar to write.
* @throws IllegalArgumentException if {@code v < 0}.
*/
default void writeIntScalar(final int v) {
writeLongScalar(v);
}
/**
* Writes a scalar (encoded with no leading zeroes).
*
* @param v The scalar to write.
*/
default void writeLongScalar(final long v) {
writeBytes(Bytes.minimalBytes(v));
}
/**
* Writes a scalar (encoded with no leading zeroes).
*
* @param v The scalar to write.
*/
default void writeBigIntegerScalar(final BigInteger v) {
if (v.equals(BigInteger.ZERO)) {
writeBytes(Bytes.EMPTY);
return;
}
final byte[] bytes = v.toByteArray();
// BigInteger will not include leading zeros by contract, but it always includes at least one
// bit of sign (a zero here since it's positive). What that mean is that if the first 1 of the
// resulting number is exactly on a byte boundary, then the sign bit constraint will make the
// value include one extra byte, which will be zero. In other words, they can be one zero bytes
// in practice we should ignore, but there should never be more than one.
writeBytes(
bytes.length > 1 && bytes[0] == 0
? Bytes.wrap(bytes, 1, bytes.length - 1)
: Bytes.wrap(bytes));
}
/**
* Writes a single byte value.
*
* @param b The byte to write.
*/
default void writeByte(final byte b) {
writeBytes(Bytes.of(b));
}
/**
* Writes a 2-bytes value.
*
* <p>Note that this is not a "scalar" write: the value will be encoded with exactly 2 bytes.
*
* @param s The 2-bytes short to write.
*/
default void writeShort(final short s) {
final byte[] res = new byte[2];
res[0] = (byte) (s >> 8);
res[1] = (byte) s;
writeBytes(Bytes.wrap(res));
}
/**
* Writes a 4-bytes value.
*
* <p>Note that this is not a "scalar" write: the value will be encoded with exactly 4 bytes.
*
* @param i The 4-bytes int to write.
*/
default void writeInt(final int i) {
final MutableBytes v = MutableBytes.create(4);
v.setInt(0, i);
writeBytes(v);
}
/**
* Writes a 8-bytes value.
*
* <p>Note that this is not a "scalar" write: the value will be encoded with exactly 8 bytes.
*
* @param l The 8-bytes long to write.
*/
default void writeLong(final long l) {
final MutableBytes v = MutableBytes.create(8);
v.setLong(0, l);
writeBytes(v);
}
/**
* Writes a single byte value.
*
* @param b A value that must fit an unsigned byte.
* @throws IllegalArgumentException if {@code b} does not fit an unsigned byte, that is if either
* {@code b < 0} or {@code b > 0xFF}.
*/
default void writeUnsignedByte(final int b) {
processZeroByte(Long.valueOf(b), a -> writeBytes(Bytes.of(b)));
}
/**
* Writes a 2-bytes value.
*
* @param s A value that must fit an unsigned 2-bytes short.
* @throws IllegalArgumentException if {@code s} does not fit an unsigned 2-bytes short, that is
* if either {@code s < 0} or {@code s > 0xFFFF}.
*/
default void writeUnsignedShort(final int s) {
processZeroByte(Long.valueOf(s), a -> writeBytes(Bytes.ofUnsignedShort(s).trimLeadingZeros()));
}
/**
* Writes a 4-bytes value.
*
* @param i A value that must fit an unsigned 4-bytes integer.
* @throws IllegalArgumentException if {@code i} does not fit an unsigned 4-bytes int, that is if
* either {@code i < 0} or {@code i > 0xFFFFFFFFL}.
*/
default void writeUnsignedInt(final long i) {
processZeroByte(i, a -> writeBytes(Bytes.ofUnsignedInt(i).trimLeadingZeros()));
}
/**
* Writes the byte representation of an inet address (so either 4 or 16 bytes long).
*
* @param address The address to write.
*/
default void writeInetAddress(final InetAddress address) {
writeBytes(Bytes.wrap(address.getAddress()));
}
/**
* Writes a list of values of a specific class provided a function to write values of that class
* to an {@link RLPOutput}.
*
* <p>This is a convenience method whose result is equivalent to doing:
*
* <pre>{@code
* startList();
* for (T v : values) {
* valueWriter.accept(v, this);
* }
* endList();
* }</pre>
*
* @param values A list of value of type {@code T}.
* @param valueWriter A method that given a value of type {@code T} and an {@link RLPOutput},
* writes this value to the output.
* @param <T> The type of values to write.
*/
default <T> void writeList(final Iterable<T> values, final BiConsumer<T, RLPOutput> valueWriter) {
startList();
for (final T v : values) {
valueWriter.accept(v, this);
}
endList();
}
/**
* Writes an empty list to the output.
*
* <p>This is a shortcut for doing:
*
* <pre>{@code
* startList();
* endList();
* }</pre>
*/
default void writeEmptyList() {
startList();
endList();
}
/**
* Writes an already RLP encoded item to the output.
*
* <p>This method is the functional equivalent of decoding the provided value entirely (to an
* fully formed Java object) and then re-encoding that result to this output. It is however a lot
* more efficient in that it saves most of that decoding/re-encoding work. Please note however
* that this method <b>does</b> validate that the input is a valid RLP encoding. If you can
* guaranteed that the input is valid and do not want this validation step, please have a look at
* {@link #writeRaw(Bytes)}.
*
* @param rlpEncodedValue An already RLP encoded value to write as next item of this output.
*/
default void writeRLPBytes(final Bytes rlpEncodedValue) {
RLP.validate(rlpEncodedValue);
writeRaw(rlpEncodedValue);
}
/**
* Writes an already RLP encoded item to the output.
*
* <p>This method is equivalent to {@link #writeRLPBytes(Bytes)}, but is unsafe in that it does
* not do any validation of the its input. As such, it is faster but can silently yield invalid
* RLP output if misused.
*
* @param bytes An already RLP encoded value to write as next item of this output.
*/
void writeRaw(Bytes bytes);
/**
* Check if the incoming value is 0 and writes it as 0x80, per the spec.
*
* @param input The value to check
* @param writer The consumer to write the non-zero output
*/
private void processZeroByte(final Long input, final Consumer<Long> writer) {
// If input == 0, encode 0 value as 0x80
if (input == 0) {
writeRaw(Bytes.of(0x80));
} else {
writer.accept(input);
}
}
}