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());
+    }
+}