Skip to content

Commit 240bb56

Browse files
author
Steve Loughran
committed
review feedback
- if metadata length == 0, fail with specific error. With tests - add tests for rejection of version != 1 - use faster readUnsignedLittleEndian() in validation knowing metadata is always little-endian
1 parent 0091ae3 commit 240bb56

3 files changed

Lines changed: 52 additions & 22 deletions

File tree

‎parquet-variant/src/main/java/org/apache/parquet/variant/Variant.java‎

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,8 @@ public final class Variant {
7272
static final int BINARY_SEARCH_THRESHOLD = 32;
7373

7474
/**
75-
* Create a Variant from a tuple of (value, metadata) byte arrays..
76-
* Includes validation of version and a validation of the toplevel
77-
* structure.
75+
* Create a Variant from a tuple of (value, metadata) byte arrays.
76+
* Includes validation of the version and the top-level structure.
7877
* @param value value buffer
7978
* @param metadata metadata buffer
8079
* @throws UnsupportedOperationException if there is a version mismatch
@@ -87,8 +86,7 @@ public Variant(byte[] value, byte[] metadata) {
8786
/**
8887
* Create a Variant from a subset of the (value, metadata) buffers
8988
* supplied.
90-
* Includes validation of version and a validation of the toplevel
91-
* structure.
89+
* Includes validation of the version and the toplevel structure.
9290
* @param value value buffer
9391
* @param valuePos offset where the value data begins
9492
* @param valueLength length of value data
@@ -108,8 +106,7 @@ public Variant(byte[] value, int valuePos, int valueLength, byte[] metadata, int
108106

109107
/**
110108
* Create a Variant from a tuple of (value, metadata) buffers.
111-
* Includes validation of version and a validation of the toplevel
112-
* structure.
109+
* Includes validation of the version and the toplevel structure.
113110
* @param value value buffer
114111
* @param metadata metadata buffer
115112
* @throws UnsupportedOperationException if there is a version mismatch
@@ -119,6 +116,8 @@ public Variant(ByteBuffer value, ByteBuffer metadata) {
119116
this.value = value.asReadOnlyBuffer().order(ByteOrder.LITTLE_ENDIAN);
120117
this.metadata = metadata.asReadOnlyBuffer().order(ByteOrder.LITTLE_ENDIAN);
121118
this.depth = 0;
119+
// A metadata buffer must contain at least the version byte
120+
Preconditions.checkArgument(this.metadata.remaining() >= 1, "variant metadata is empty");
122121
// There is currently only one allowed version.
123122
if ((metadata.get(metadata.position()) & VariantUtil.VERSION_MASK) != VariantUtil.VERSION) {
124123
throw new UnsupportedOperationException(String.format(
@@ -129,18 +128,14 @@ public Variant(ByteBuffer value, ByteBuffer metadata) {
129128
// Pre-compute dictionary size for lazy metadata cache allocation.
130129
int pos = this.metadata.position();
131130
int metaOffsetSize = ((this.metadata.get(pos) >> 6) & 0x3) + 1;
132-
if (this.metadata.remaining() > 1) {
133-
Preconditions.checkArgument(
134-
this.metadata.remaining() >= 1 + metaOffsetSize,
135-
"variant metadata truncated: offsetSize=" + metaOffsetSize);
136-
this.dictSize = VariantUtil.readUnsignedLittleEndian(this.metadata, pos + 1, metaOffsetSize);
137-
long dictTableEnd = 1L + metaOffsetSize + ((long) this.dictSize + 1) * metaOffsetSize;
138-
Preconditions.checkArgument(
139-
dictTableEnd <= this.metadata.remaining(),
140-
"variant metadata dictionary extends past buffer: dictSize=" + this.dictSize);
141-
} else {
142-
this.dictSize = 0;
143-
}
131+
Preconditions.checkArgument(
132+
this.metadata.remaining() >= 1 + metaOffsetSize,
133+
"variant metadata truncated: offsetSize=" + metaOffsetSize);
134+
this.dictSize = VariantUtil.readUnsignedLittleEndian(this.metadata, pos + 1, metaOffsetSize);
135+
long dictTableEnd = 1L + metaOffsetSize + ((long) this.dictSize + 1) * metaOffsetSize;
136+
Preconditions.checkArgument(
137+
dictTableEnd <= this.metadata.remaining(),
138+
"variant metadata dictionary extends past buffer: dictSize=" + this.dictSize);
144139
this.metadataCache = null;
145140
VariantUtil.validateValueShallow(this.value, dictSize);
146141
}
@@ -407,6 +402,7 @@ public Variant getElementAtIndex(int index) {
407402

408403
/**
409404
* Creates a child Variant that shares this instance's metadata cache.
405+
* @throws IllegalArgumentException validation error during construction.
410406
*/
411407
private Variant childVariant(ByteBuffer childValue) {
412408
return new Variant(childValue, metadata, metadataCache, dictSize, depth + 1);

‎parquet-variant/src/main/java/org/apache/parquet/variant/VariantUtil.java‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -950,7 +950,7 @@ private static void validateContainerShallow(
950950
int offsetSize = (typeInfo & 0x3) + 1;
951951
int sizeBytes = largeSize ? U32_SIZE : 1;
952952
Preconditions.checkArgument(1L + sizeBytes <= length, "variant container header truncated");
953-
int numElements = readUnsigned(valueBuffer, pos + 1, sizeBytes);
953+
int numElements = readUnsignedLittleEndian(valueBuffer, pos + 1, sizeBytes);
954954
long idStart = 1L + sizeBytes;
955955
long idBytes = isObject ? (long) numElements * idSize : 0L;
956956
long offsetStart = idStart + idBytes;
@@ -961,7 +961,7 @@ private static void validateContainerShallow(
961961
long dataLen = length - dataStart;
962962
if (isObject) {
963963
for (int i = 0; i < numElements; i++) {
964-
int id = readUnsigned(valueBuffer, pos + (int) idStart + i * idSize, idSize);
964+
int id = readUnsignedLittleEndian(valueBuffer, pos + (int) idStart + i * idSize, idSize);
965965
Preconditions.checkArgument(
966966
id < dictSize, "variant object key id %s out of range (dictSize=%s)", id, dictSize);
967967
}
@@ -970,7 +970,7 @@ private static void validateContainerShallow(
970970
// the trailing terminator offset is range-checked for the same reason.
971971
for (int i = 0; i <= numElements; i++) {
972972
// O(elements)
973-
int off = readUnsigned(valueBuffer, pos + (int) offsetStart + i * offsetSize, offsetSize);
973+
int off = readUnsignedLittleEndian(valueBuffer, pos + (int) offsetStart + i * offsetSize, offsetSize);
974974
Preconditions.checkArgument(
975975
off <= dataLen, "variant child offset out of range: %s (data length %s)", off, dataLen);
976976
}

‎parquet-variant/src/test/java/org/apache/parquet/variant/TestHardenedReader.java‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,40 @@ public void testOversizedMetadataDictSize() throws IOException {
310310
expectRoundTripRaisesIllegalArgument(metadata, arrayOneNull(), "dictionary");
311311
}
312312

313+
/**
314+
* Reject an empty metadata buffer.
315+
*/
316+
@Test
317+
public void testEmptyMetadataRejected() {
318+
IllegalArgumentException thrown = assertThrows(
319+
IllegalArgumentException.class,
320+
() -> new Variant(ByteBuffer.wrap(arrayOneNull()), ByteBuffer.wrap(new byte[0])));
321+
assertExceptionMessageContains(thrown, "empty");
322+
}
323+
324+
/**
325+
* Reject a single-byte metadata buffer.
326+
*/
327+
@Test
328+
public void testSingleByteMetadataRejected() {
329+
IllegalArgumentException thrown = assertThrows(
330+
IllegalArgumentException.class,
331+
() -> new Variant(ByteBuffer.wrap(arrayOneNull()), ByteBuffer.wrap(new byte[] {0x01})));
332+
assertExceptionMessageContains(thrown, "truncated");
333+
}
334+
335+
/**
336+
* Reject metadata whose version bits are not the single supported version.
337+
*/
338+
@Test
339+
public void testUnsupportedMetadataVersionRejected() {
340+
// Header 0x02: version=2 in the low bits, offsetSize=1. A well-formed empty dictionary.
341+
UnsupportedOperationException thrown = assertThrows(
342+
UnsupportedOperationException.class,
343+
() -> new Variant(ByteBuffer.wrap(arrayOneNull()), ByteBuffer.wrap(new byte[] {0x02, 0x00, 0x00})));
344+
assertExceptionMessageContains(thrown, "version");
345+
}
346+
313347
// ------------------------------------------------------------------
314348
// Helpers
315349
// ------------------------------------------------------------------

0 commit comments

Comments
 (0)