com.hedera.hapi.node.base.codec.TransactionProtoCodec Maven / Gradle / Ivy
The newest version!
package com.hedera.hapi.node.base.codec;
import com.hedera.pbj.runtime.*;
import com.hedera.pbj.runtime.io.*;
import com.hedera.pbj.runtime.io.buffer.*;
import com.hedera.pbj.runtime.io.stream.EOFException;
import java.io.IOException;
import java.nio.*;
import java.nio.charset.*;
import java.util.*;
import edu.umd.cs.findbugs.annotations.NonNull;
import com.hedera.hapi.node.base.Transaction;
import com.hedera.hapi.node.base.*;
import com.hedera.hapi.node.base.schema.*;
import com.hedera.hapi.node.transaction.*;
import com.hedera.hapi.node.transaction.codec.*;
import com.hedera.pbj.runtime.io.buffer.*;
import static com.hedera.hapi.node.base.schema.TransactionSchema.*;
import static com.hedera.pbj.runtime.ProtoWriterTools.*;
import static com.hedera.pbj.runtime.ProtoParserTools.*;
import static com.hedera.pbj.runtime.ProtoConstants.*;
/**
* Protobuf Codec for Transaction model object. Generated based on protobuf schema.
*/
public final class TransactionProtoCodec implements Codec {
/**
* Parses a Transaction object from ProtoBuf bytes in a {@link ReadableSequentialData}. Throws if in strict mode ONLY.
*
* @param input The data input to parse data from, it is assumed to be in a state ready to read with position at start
* of data to read and limit set at the end of data to read. The data inputs limit will be changed by this
* method. If there are no bytes remaining in the data input,
* then the method also returns immediately.
* @param strictMode when {@code true}, the parser errors out on unknown fields; otherwise they'll be simply skipped.
* @param maxDepth a ParseException will be thrown if the depth of nested messages exceeds the maxDepth value.
* @return Parsed Transaction model object or null if data input was null or empty
* @throws ParseException If parsing fails
*/
public @NonNull Transaction parse(
@NonNull final ReadableSequentialData input,
final boolean strictMode,
final int maxDepth) throws ParseException {
if (maxDepth < 0) {
throw new ParseException("Reached maximum allowed depth of nested messages");
}
try {
// -- TEMP STATE FIELDS --------------------------------------
TransactionBody temp_body = null;
SignatureList temp_sigs = null;
SignatureMap temp_sigMap = null;
Bytes temp_bodyBytes = Bytes.EMPTY;
Bytes temp_signedTransactionBytes = Bytes.EMPTY;
// -- PARSE LOOP ---------------------------------------------
// Continue to parse bytes out of the input stream until we get to the end.
while (input.hasRemaining()) {
// Note: ReadableStreamingData.hasRemaining() won't flip to false
// until the end of stream is actually hit with a read operation.
// So we catch this exception here and **only** here, because an EOFException
// anywhere else suggests that we're processing malformed data and so
// we must re-throw the exception then.
final int tag;
try {
// Read the "tag" byte which gives us the field number for the next field to read
// and the wire type (way it is encoded on the wire).
tag = input.readVarInt(false);
} catch (EOFException e) {
// There's no more fields. Stop the parsing loop.
break;
}
// The field is the top 5 bits of the byte. Read this off
final int field = tag >>> TAG_FIELD_OFFSET;
// Ask the Schema to inform us what field this represents.
final var f = getField(field);
// Given the wire type and the field type, parse the field
switch (tag) {
case 10 /* type=2 [MESSAGE] field=1 [body] */ -> {
final var messageLength = input.readVarInt(false);
final TransactionBody value;
if (messageLength == 0) {
value = TransactionBody.DEFAULT;
} else {
if (messageLength > 2097152) {
throw new ParseException("body size " + messageLength + " is greater than max " + 2097152);
}
final var limitBefore = input.limit();
// Make sure that we have enough bytes in the message
// to read the subObject.
// If the buffer is truncated on the boundary of a subObject,
// we will not throw.
final var startPos = input.position();
try {
if ((startPos + messageLength) > limitBefore) {
throw new BufferUnderflowException();
}
input.limit(startPos + messageLength);
value = TransactionBody.PROTOBUF.parse(input, strictMode, maxDepth - 1);
// Make sure we read the full number of bytes. for the types
if ((startPos + messageLength) != input.position()) {
throw new BufferOverflowException();
}
} finally {
input.limit(limitBefore);
}
}
temp_body = value;
}
case 18 /* type=2 [MESSAGE] field=2 [sigs] */ -> {
final var messageLength = input.readVarInt(false);
final SignatureList value;
if (messageLength == 0) {
value = SignatureList.DEFAULT;
} else {
if (messageLength > 2097152) {
throw new ParseException("sigs size " + messageLength + " is greater than max " + 2097152);
}
final var limitBefore = input.limit();
// Make sure that we have enough bytes in the message
// to read the subObject.
// If the buffer is truncated on the boundary of a subObject,
// we will not throw.
final var startPos = input.position();
try {
if ((startPos + messageLength) > limitBefore) {
throw new BufferUnderflowException();
}
input.limit(startPos + messageLength);
value = SignatureList.PROTOBUF.parse(input, strictMode, maxDepth - 1);
// Make sure we read the full number of bytes. for the types
if ((startPos + messageLength) != input.position()) {
throw new BufferOverflowException();
}
} finally {
input.limit(limitBefore);
}
}
temp_sigs = value;
}
case 26 /* type=2 [MESSAGE] field=3 [sigMap] */ -> {
final var messageLength = input.readVarInt(false);
final SignatureMap value;
if (messageLength == 0) {
value = SignatureMap.DEFAULT;
} else {
if (messageLength > 2097152) {
throw new ParseException("sigMap size " + messageLength + " is greater than max " + 2097152);
}
final var limitBefore = input.limit();
// Make sure that we have enough bytes in the message
// to read the subObject.
// If the buffer is truncated on the boundary of a subObject,
// we will not throw.
final var startPos = input.position();
try {
if ((startPos + messageLength) > limitBefore) {
throw new BufferUnderflowException();
}
input.limit(startPos + messageLength);
value = SignatureMap.PROTOBUF.parse(input, strictMode, maxDepth - 1);
// Make sure we read the full number of bytes. for the types
if ((startPos + messageLength) != input.position()) {
throw new BufferOverflowException();
}
} finally {
input.limit(limitBefore);
}
}
temp_sigMap = value;
}
case 34 /* type=2 [BYTES] field=4 [bodyBytes] */ -> {
final var value = readBytes(input, 2097152);
temp_bodyBytes = value;
}
case 42 /* type=2 [BYTES] field=5 [signedTransactionBytes] */ -> {
final var value = readBytes(input, 2097152);
temp_signedTransactionBytes = value;
}
default -> {
// The wire type is the bottom 3 bits of the byte. Read that off
final int wireType = tag & TAG_WIRE_TYPE_MASK;
// handle error cases here, so we do not do if statements in normal loop
// Validate the field number is valid (must be > 0)
if (field == 0) {
throw new IOException("Bad protobuf encoding. We read a field value of "
+ field);
}
// Validate the wire type is valid (must be >=0 && <= 5).
// Otherwise we cannot parse this.
// Note: it is always >= 0 at this point (see code above where it is defined).
if (wireType > 5) {
throw new IOException("Cannot understand wire_type of " + wireType);
}
// It may be that the parser subclass doesn't know about this field
if (f == null) {
if (strictMode) {
// Since we are parsing is strict mode, this is an exceptional condition.
throw new UnknownFieldException(field);
} else {
// We just need to read off the bytes for this field to skip it
// and move on to the next one.
skipField(input, ProtoConstants.get(wireType), 2097152);
}
} else {
throw new IOException("Bad tag [" + tag + "], field [" + field
+ "] wireType [" + wireType + "]");
}
}
}
}
return new Transaction(temp_body, temp_sigs, temp_sigMap, temp_bodyBytes, temp_signedTransactionBytes);
} catch (final Exception anyException) {
if (anyException instanceof ParseException parseException) {
throw parseException;
}
throw new ParseException(anyException);
}
}
/**
* Write out a Transaction model to output stream in protobuf format.
*
* @param data The input model data to write
* @param out The output stream to write to
* @throws IOException If there is a problem writing
*/
public void write(@NonNull Transaction data, @NonNull final WritableSequentialData out) throws IOException {
// [1] - body
writeMessage(out, BODY, data.body(), com.hedera.hapi.node.transaction.TransactionBody.PROTOBUF::write, com.hedera.hapi.node.transaction.TransactionBody.PROTOBUF::measureRecord);
// [2] - sigs
writeMessage(out, SIGS, data.sigs(), com.hedera.hapi.node.base.SignatureList.PROTOBUF::write, com.hedera.hapi.node.base.SignatureList.PROTOBUF::measureRecord);
// [3] - sigMap
writeMessage(out, SIG_MAP, data.sigMap(), com.hedera.hapi.node.base.SignatureMap.PROTOBUF::write, com.hedera.hapi.node.base.SignatureMap.PROTOBUF::measureRecord);
// [4] - bodyBytes
writeBytes(out, BODY_BYTES, data.bodyBytes(), true);
// [5] - signedTransactionBytes
writeBytes(out, SIGNED_TRANSACTION_BYTES, data.signedTransactionBytes(), true);
}
/**
* Reads from this data input the length of the data within the input. The implementation may
* read all the data, or just some special serialized data, as needed to find out the length of
* the data.
*
* @param input The input to use
* @return The length of the data item in the input
* @throws ParseException If parsing fails
*/
public int measure(@NonNull final ReadableSequentialData input) throws ParseException {
final var start = input.position();
parse(input);
final var end = input.position();
return (int)(end - start);
}
/**
* Compute number of bytes that would be written when calling {@code write()} method.
*
* @param data The input model data to measure write bytes for
* @return The length in bytes that would be written
*/
public int measureRecord(Transaction data) {
int size = 0;
// [1] - body
size += sizeOfMessage(BODY, data.body(), com.hedera.hapi.node.transaction.TransactionBody.PROTOBUF::measureRecord);
// [2] - sigs
size += sizeOfMessage(SIGS, data.sigs(), com.hedera.hapi.node.base.SignatureList.PROTOBUF::measureRecord);
// [3] - sigMap
size += sizeOfMessage(SIG_MAP, data.sigMap(), com.hedera.hapi.node.base.SignatureMap.PROTOBUF::measureRecord);
// [4] - bodyBytes
size += sizeOfBytes(BODY_BYTES, data.bodyBytes(), true);
// [5] - signedTransactionBytes
size += sizeOfBytes(SIGNED_TRANSACTION_BYTES, data.signedTransactionBytes(), true);
return size;
}
/**
* Compares the given item with the bytes in the input, and returns false if it determines that
* the bytes in the input could not be equal to the given item. Sometimes we need to compare an
* item in memory with serialized bytes and don't want to incur the cost of deserializing the
* entire object, when we could have determined the bytes do not represent the same object very
* cheaply and quickly.
*
* @param item The item to compare. Cannot be null.
* @param input The input with the bytes to compare
* @return true if the bytes represent the item, false otherwise.
* @throws ParseException If parsing fails
*/
public boolean fastEquals(@NonNull Transaction item, @NonNull final ReadableSequentialData input) throws ParseException {
return item.equals(parse(input));
}
}