Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ String formatCaseStatement() {
methodLambda = "typedReplies -> " + name + "(typedReplies, options)";
}

// spotless:off
return """
case $methodName -> Pipelines.<$requestType, $replyType>$kind()
.mapRequest(bytes -> parse$simpleRequestType(bytes, options))
Expand All @@ -225,9 +226,11 @@ String formatCaseStatement() {
.replace("$replyType", replyType)
.replace("$simpleReplyType", replyType.replace(".", ""))
.replace("$kind", kind);
// spotless:on
}

private String formatUnaryMethodImplementation() {
// spotless:off
return """
@Override
$methodSignatureWithoutOptions {
Expand Down Expand Up @@ -298,9 +301,11 @@ public void onComplete() {
.replace("$replyType", replyType)
.replace("$simpleReplyType", replyType.replace(".", ""))
.replace("$methodName", name);
// spotless:on
}

private String formatClientStreamingMethodImplementation() {
// spotless:off
return """
@Override
$methodSignatureWithoutOptions {
Expand Down Expand Up @@ -369,9 +374,11 @@ public void onComplete() {
.replace("$replyType", replyType)
.replace("$simpleReplyType", replyType.replace(".", ""))
.replace("$methodName", name);
// spotless:on
}

private String formatServerStreamingMethodImplementation() {
// spotless:off
return """
@Override
$methodSignatureWithoutOptions {
Expand Down Expand Up @@ -429,9 +436,11 @@ public void onComplete() {
.replace("$replyType", replyType)
.replace("$simpleReplyType", replyType.replace(".", ""))
.replace("$methodName", name);
// spotless:on
}

private String formatBidiStreamingMethodImplementation() {
// spotless:off
return """
@Override
$methodSignatureWithoutOptions {
Expand Down Expand Up @@ -496,6 +505,7 @@ public void onComplete() {
.replace("$replyType", replyType)
.replace("$simpleReplyType", replyType.replace(".", ""))
.replace("$methodName", name);
// spotless:on
}

String formatMethodImplementation() {
Expand Down Expand Up @@ -771,7 +781,7 @@ private static String formatParseRequestMethod(final String requestType) {
Objects.requireNonNull(options);

// not strict, no unknown fields, hard-code maxDepth for now, and use custom maxSize:
return get$simpleRequestTypeCodec(options).parse(message.toReadableSequentialData(), false, false, 16, options.maxMessageSizeBytes());
return get$simpleRequestTypeCodec(options).parse(message, false, false, 16, options.maxMessageSizeBytes());
}
"""
.replace("$requestType", requestType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ private void receiveRepliesLoop() {

try {
final ReplyT reply = replyCodec.parse(
replyBytes.toReadableSequentialData(),
replyBytes,
false,
false,
Codec.DEFAULT_MAX_DEPTH,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import com.hedera.pbj.runtime.grpc.GrpcStatus;
import com.hedera.pbj.runtime.grpc.Pipeline;
import com.hedera.pbj.runtime.grpc.ServiceInterface;
import com.hedera.pbj.runtime.io.ReadableSequentialData;
import com.hedera.pbj.runtime.io.buffer.Bytes;
import io.helidon.common.buffers.BufferData;
import io.helidon.common.buffers.DataWriter;
Expand Down Expand Up @@ -279,12 +278,7 @@ public void testReceiveRepliesLoopSingleReply(final boolean isTimeout) throws Ex
final Object reply = mock(Object.class);
doReturn(reply)
.when(replyCodec)
.parse(
any(ReadableSequentialData.class),
eq(false),
eq(false),
eq(Codec.DEFAULT_MAX_DEPTH),
eq(Codec.DEFAULT_MAX_SIZE));
.parse(any(Bytes.class), eq(false), eq(false), eq(Codec.DEFAULT_MAX_DEPTH), eq(Codec.DEFAULT_MAX_SIZE));

runnable.run();

Expand Down Expand Up @@ -342,12 +336,7 @@ public void testReceiveRepliesLoopParseException() throws Exception {
final ParseException exception = new ParseException("test");
doThrow(exception)
.when(replyCodec)
.parse(
any(ReadableSequentialData.class),
eq(false),
eq(false),
eq(Codec.DEFAULT_MAX_DEPTH),
eq(Codec.DEFAULT_MAX_SIZE));
.parse(any(Bytes.class), eq(false), eq(false), eq(Codec.DEFAULT_MAX_DEPTH), eq(Codec.DEFAULT_MAX_SIZE));

runnable.run();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,12 @@ public final T parse(
throws ParseException {
return parse(input, strictMode, parseUnknownFields, maxDepth, DEFAULT_MAX_SIZE);
}

@NonNull
public final T parse(@NonNull PbjReader input, boolean strictMode, boolean parseUnknownFields, int maxDepth)
throws ParseException {
return parse(input, strictMode, parseUnknownFields, maxDepth, DEFAULT_MAX_SIZE);
}
/**
* Temporary for test compatibility
*
Expand Down Expand Up @@ -251,7 +257,45 @@ public final T parse(@NonNull ReadableSequentialData input, final boolean strict
*/
@NonNull
public final T parse(@NonNull Bytes bytes, final boolean strictMode, final int maxDepth) throws ParseException {
return parse(new PbjReader(bytes), strictMode, false, maxDepth, DEFAULT_MAX_SIZE);
return parse(bytes, strictMode, false, maxDepth, DEFAULT_MAX_SIZE);
}

/**
* Parses an object from the {@link Bytes} and returns it.
* <p>
* If {@code strictMode} is {@code true}, then throws an exception if fields
* have been defined on the encoded object that are not supported by the parser. This
* breaks forwards compatibility (an older parser cannot parse a newer encoded object),
* which is sometimes requires to avoid parsing an object that is newer than the code
* parsing it is prepared to handle.
* <p>
* The {@code maxDepth} specifies the maximum allowed depth of nested messages. The parsing
* will fail with a ParseException if the maximum depth is reached.
* <p>
* The {@code maxSize} specifies a custom value for the default `Codec.DEFAULT_MAX_SIZE` limit. IMPORTANT:
* specifying a value larger than the default one can put the application at risk because a maliciously-crafted
* payload can cause the parser to allocate too much memory which can result in OutOfMemory and/or crashes.
* It's important to carefully estimate the maximum size limit that a particular protobuf model type should support,
* and then pass that value as a parameter. Note that the estimated limit should apply to the **type** as a whole,
* rather than to individual instances of the model. In other words, this value should be a constant, or a config
* value that is controlled by the application, rather than come from the input that the application reads.
* When in doubt, use the other overloaded versions of this method that use the default `Codec.DEFAULT_MAX_SIZE`.
*
* @param input The {@link Bytes} from which to read the data to construct an object
* @param strictMode when {@code true}, the parser errors out on unknown fields; otherwise they'll be simply skipped.
* @param parseUnknownFields when {@code true} and strictMode is {@code false}, the parser will collect unknown
* fields in the unknownFields list in the model; otherwise they'll be simply skipped.
* @param maxDepth a ParseException will be thrown if the depth of nested messages exceeds the maxDepth value.
* @param maxSize a ParseException will be thrown if the size of a delimited field exceeds the limit
* @return The parsed object. It must not return null.
* @throws ParseException If parsing fails
*/
@NonNull
public final T parse(
@NonNull Bytes input, boolean strictMode, boolean parseUnknownFields, int maxDepth, int maxSize)
throws ParseException {
final PbjReader reader = new PbjReader(input);
return parse(reader, strictMode, parseUnknownFields, maxDepth, maxSize);
}

/**
Expand All @@ -277,7 +321,7 @@ public final T parse(@NonNull ReadableSequentialData input) throws ParseExceptio
*/
@NonNull
public final T parse(@NonNull Bytes bytes) throws ParseException {
return parse(bytes.toReadableSequentialData());
return parse(bytes, false, false, DEFAULT_MAX_DEPTH, DEFAULT_MAX_SIZE);
}

/**
Expand Down Expand Up @@ -311,7 +355,7 @@ public final T parseStrict(@NonNull ReadableSequentialData input) throws ParseEx
*/
@NonNull
public final T parseStrict(@NonNull Bytes bytes) throws ParseException {
return parseStrict(bytes.toReadableSequentialData());
return parse(bytes, true, DEFAULT_MAX_DEPTH);
}

/**
Expand Down Expand Up @@ -489,7 +533,7 @@ public final boolean fastEquals(@NonNull T item, @NonNull ReadableSequentialData
* to write to the {@link WritableStreamingData}
*/
public Bytes toBytes(@NonNull T item) {
// TODO: Confirm if this next line is accurate with PbjWriter
// TODO: Confirm if this next line is still true with PbjWriter
// it is cheaper performance wise to measure the size of the object first than grow a buffer as needed
final byte[] bytes = new byte[measureRecord(item)];
final PbjWriter writer = new PbjWriter(bytes, 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ protected final void writeImpl(@NonNull T item, @NonNull PbjWriter output) {
public void write(@NonNull T item, @NonNull WritableSequentialData output) throws IOException {
output.writeUTF8(toJSON(item));
}

/**
* Returns JSON string representing an item.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,16 @@ public ReadableSequentialData toReadableSequentialData() {
return new RandomAccessSequenceAdapter(this);
}

/**
* Create and return a new {@link PbjReader} that is backed by this {@link Bytes}.
*
* @return A {@link PbjReader} backed by this {@link Bytes}.
*/
@NonNull
public PbjReader toPbjReader() {
return new PbjReader(this);
}

@NonNull
public void resetPbjReader(@NonNull PbjReader reader) {
reader.resetWith(buffer, start, start + length);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,14 @@
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

import com.hedera.pbj.runtime.io.ReadableSequentialData;
import com.hedera.pbj.runtime.io.buffer.BufferedData;
import com.hedera.pbj.runtime.io.buffer.Bytes;
import com.hedera.pbj.runtime.io.buffer.PbjReader;
import com.hedera.pbj.runtime.io.buffer.PbjWriter;
import com.hedera.pbj.runtime.io.stream.ReadableStreamingData;
import com.hedera.pbj.runtime.io.stream.WritableStreamingData;
import com.hedera.pbj.runtime.test.UncheckedThrowingFunction;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.nio.ByteOrder;
Expand Down Expand Up @@ -359,20 +356,18 @@ void testSkipUnsupported(ProtoConstants unsupportedType) {
@Test
void testExtractBytesNullInput() {
final FieldDefinition field = createFieldDefinition(BYTES);
assertThrows(
NullPointerException.class,
() -> ProtoParserTools.extractFieldBytes((ReadableSequentialData) null, field));
assertThrows(NullPointerException.class, () -> ProtoParserTools.extractFieldBytes((PbjReader) null, field));
}

@Test
void testExtractBytesNullField() {
final ReadableSequentialData input = Bytes.EMPTY.toReadableSequentialData();
final PbjReader input = Bytes.EMPTY.toPbjReader();
assertThrows(NullPointerException.class, () -> ProtoParserTools.extractFieldBytes(input, null));
}

@Test
void testExtractBytesRepeatedField() {
final ReadableSequentialData input = Bytes.EMPTY.toReadableSequentialData();
final PbjReader input = Bytes.EMPTY.toPbjReader();
final FieldDefinition field = new FieldDefinition("field", FieldType.BYTES, true, true, false, 1);
assertThrows(IllegalArgumentException.class, () -> ProtoParserTools.extractFieldBytes(input, field));
}
Expand Down Expand Up @@ -407,90 +402,94 @@ void testExtractBytesRepeatedField() {
private static final FieldDefinition BOOL_F = new FieldDefinition("boolfield", BOOL, false, true, false, 11);
private static final boolean BOOL_V = true;

private static Bytes prepareExtractBytesTestInput() throws IOException {
try (final ByteArrayOutputStream bout = new ByteArrayOutputStream();
final WritableStreamingData out = new WritableStreamingData(bout)) {
ProtoWriterTools.writeInteger(out, INT32_F, INT32_V);
ProtoWriterTools.writeInteger(out, FIXED_F, FIXED32_V);
ProtoWriterTools.writeString(out, STRING_F, STRING_V);
ProtoWriterTools.writeBytes(out, BYTES_F, BYTES_V);
ProtoWriterTools.writeMessage(out, MESSAGE_F, MESSAGE_V, TestMessageCodec.INSTANCE);
ProtoWriterTools.writeDouble(out, DOUBLE_F, DOUBLE32_V);
return Bytes.wrap(bout.toByteArray());
}
private static PbjReader prepareExtractBytesTestInput() throws IOException {
PbjWriter out = new PbjWriter();
ProtoWriterTools.writeInteger(out, INT32_F, INT32_V);
ProtoWriterTools.writeInteger(out, FIXED_F, FIXED32_V);
ProtoWriterTools.writeString(out, STRING_F, STRING_V);
ProtoWriterTools.writeBytes(out, BYTES_F, BYTES_V);
ProtoWriterTools.writeMessage(out, MESSAGE_F, MESSAGE_V, TestMessageCodec.INSTANCE);
ProtoWriterTools.writeDouble(out, DOUBLE_F, DOUBLE32_V);
return out.toPbjReader();
}

@Test
void testExtractBytesStringField() throws IOException, ParseException {
final ReadableSequentialData input = prepareExtractBytesTestInput().toReadableSequentialData();
final PbjReader input = prepareExtractBytesTestInput();
final Bytes bytes = ProtoParserTools.extractFieldBytes(input, STRING_F);
assertNotNull(bytes);
assertEquals(STRING_V, new String(bytes.toByteArray(), StandardCharsets.UTF_8));
}

@Test
void testExtractFieldBytesInvalidType() throws IOException, ParseException {
final ReadableSequentialData input = prepareExtractBytesTestInput().toReadableSequentialData();
final PbjReader input = prepareExtractBytesTestInput();
// should throw because INT32 is not a delimited type
assertThrows(IllegalArgumentException.class, () -> ProtoParserTools.extractFieldBytes(input, INT32_F));
}

@Test
void testExtractBytesBytesField() throws IOException, ParseException {
final ReadableSequentialData input = prepareExtractBytesTestInput().toReadableSequentialData();
final PbjReader input = prepareExtractBytesTestInput();
final Bytes bytes = ProtoParserTools.extractFieldBytes(input, BYTES_F);
assertNotNull(bytes);
assertEquals(BYTES_V, bytes);
}

@Test
void testExtractBytesMessageField() throws IOException, ParseException {
final ReadableSequentialData input = prepareExtractBytesTestInput().toReadableSequentialData();
final PbjReader input = prepareExtractBytesTestInput();
final Bytes bytes = ProtoParserTools.extractFieldBytes(input, MESSAGE_F);
assertNotNull(bytes);
final TestMessage value = TestMessageCodec.INSTANCE.parse(bytes.toReadableSequentialData());
final TestMessage value = TestMessageCodec.INSTANCE.parse(bytes);
assertNotNull(value);
assertEquals(MESSAGE_V, value);
}

@Test
void testExtractBytesUnknownField() throws IOException, ParseException {
final ReadableSequentialData input = prepareExtractBytesTestInput().toReadableSequentialData();
final PbjReader input = prepareExtractBytesTestInput();
final Bytes bytes = ProtoParserTools.extractFieldBytes(input, UNKNOWN_F);
assertNull(bytes);
}

@Test
void testExtractField32Bit() throws IOException, ParseException {
final ReadableSequentialData input = prepareExtractBytesTestInput().toReadableSequentialData();
final PbjReader input = prepareExtractBytesTestInput();
final var res = ProtoParserTools.extractField(input, WIRE_TYPE_FIXED_32_BIT, 32);
assertNotNull(res);
}

@Test
void testExtractField64Bit() throws IOException, ParseException {
final ReadableSequentialData input = prepareExtractBytesTestInput().toReadableSequentialData();
final PbjReader input = prepareExtractBytesTestInput();
final var res = ProtoParserTools.extractField(input, WIRE_TYPE_FIXED_64_BIT, 32);
assertNotNull(res);
}

@Test
void testExtractFieldVarInt() throws IOException, ParseException {
final ReadableSequentialData input = prepareExtractBytesTestInput().toReadableSequentialData();
final PbjReader input = prepareExtractBytesTestInput();
final var res = ProtoParserTools.extractField(input, WIRE_TYPE_VARINT_OR_ZIGZAG, 32);
assertNotNull(res);
}

@Test
void testExtractFieldGroupStartUnsupported() throws IOException {
final ReadableSequentialData input = prepareExtractBytesTestInput().toReadableSequentialData();
assertThrows(IOException.class, () -> ProtoParserTools.extractField(input, WIRE_TYPE_GROUP_START, 32));
final PbjReader input = prepareExtractBytesTestInput();
assertThrows(IOException.class, () -> {
ProtoParserTools.extractField(input, WIRE_TYPE_GROUP_START, 32);
input.throwOnError2();
});
}

@Test
void testExtractFieldGroupEndUnsupported() throws IOException {
final ReadableSequentialData input = prepareExtractBytesTestInput().toReadableSequentialData();
assertThrows(IOException.class, () -> ProtoParserTools.extractField(input, WIRE_TYPE_GROUP_END, 32));
final PbjReader input = prepareExtractBytesTestInput();
assertThrows(IOException.class, () -> {
ProtoParserTools.extractField(input, WIRE_TYPE_GROUP_END, 32);
input.throwOnError2();
});
}

private static void skipTag(BufferedData data) {
Expand Down
Loading
Loading