You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by sr...@apache.org on 2026/08/25 09:12:01 UTC
(plc4x) 01/06: test(plc4j/opcua): pin the array count a Variant may announce
This is an automated email from the ASF dual-hosted git repository.
sruehl pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/plc4x.git
commit 1ad257c39ba493ceabb88a1013365de66cdaf272
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Tue Aug 25 09:35:52 2026 +0200
test(plc4j/opcua): pin the array count a Variant may announce
A Variant carrying an array says how many elements follow, four bytes ahead of the
elements themselves. Announcing Integer.MAX_VALUE and then sending nothing used to get
the room it asked for: the array reader pre-sized its backing list to the announced
count before reading anything, so eight gigabytes were reserved on the strength of four
bytes, and what came back was an OutOfMemoryError - an Error, so it passed straight
through anything catching Exception.
That was fixed in the shared array reader, which now caps what it reserves and lets the
list grow as elements actually arrive; the buffer runs out long before the count does.
What was missing was a test saying so at the level the claim is made, which is a Variant
off the wire rather than a reader called directly.
Covers the announced count with no payload, that the failure is a parse failure and not
an Error, and that a count matching its payload still reads.
---
.../java/opcua/VariantArrayCountLimitTest.java | 78 ++++++++++++++++++++++
1 file changed, 78 insertions(+)
diff --git a/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/VariantArrayCountLimitTest.java b/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/VariantArrayCountLimitTest.java
new file mode 100644
index 0000000000..12eb05905b
--- /dev/null
+++ b/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/VariantArrayCountLimitTest.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.plc4x.java.opcua;
+
+import org.apache.plc4x.java.opcua.protocol.chunk.PayloadConverter;
+import org.apache.plc4x.java.opcua.readwrite.Variant;
+import org.apache.plc4x.java.opcua.readwrite.VariantInt32;
+import org.apache.plc4x.java.spi.buffers.api.exceptions.BufferException;
+import org.apache.plc4x.java.spi.buffers.bytebased.ReadBufferByteBased;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * A Variant carrying an array announces how many elements follow, and the announcement arrives
+ * four bytes ahead of the elements. A server that says Integer.MAX_VALUE and then sends nothing
+ * is describing an array that cannot be there, and the reading of it must not reserve room for
+ * the claim before testing it.
+ */
+public class VariantArrayCountLimitTest {
+
+ /** arrayLengthSpecified, no arrayDimensions, VariantType 6 (Int32). */
+ private static final byte INT32_ARRAY_HEADER = (byte) 0x86;
+
+ private static ReadBufferByteBased buffer(byte... bytes) {
+ return new ReadBufferByteBased(bytes, PayloadConverter.LITTLE_ENDIAN);
+ }
+
+ @Test
+ void anArrayCountOfIntegerMaxValueWithNoPayloadIsRefused() {
+ // The reported payload: the four length bytes set to ff ff ff 7f and no elements.
+ BufferException e = assertThrows(BufferException.class, () -> Variant.staticParse(
+ buffer(INT32_ARRAY_HEADER, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x7F)));
+ assertNotNull(e);
+ }
+
+ @Test
+ void theCountIsNotReservedForBeforeTheElementsAreRead() {
+ // Reserving Integer.MAX_VALUE slots is an eight gigabyte array. The point is that this
+ // returns a parse failure rather than an OutOfMemoryError, which is an Error and would
+ // pass straight through any handler catching Exception.
+ assertDoesNotThrow(() -> {
+ try {
+ Variant.staticParse(
+ buffer(INT32_ARRAY_HEADER, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x7F));
+ fail("expected the overlarge count to be refused");
+ } catch (BufferException expected) {
+ // The buffer runs out long before the count does.
+ }
+ });
+ }
+
+ @Test
+ void anArrayWhoseCountMatchesItsPayloadStillParses() throws Exception {
+ Variant variant = Variant.staticParse(buffer(INT32_ARRAY_HEADER,
+ (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x00, // two elements
+ (byte) 0x07, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x09, (byte) 0x00, (byte) 0x00, (byte) 0x00));
+ assertInstanceOf(VariantInt32.class, variant);
+ assertEquals(java.util.List.of(7, 9), ((VariantInt32) variant).getValue());
+ }
+}