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 06:40:56 UTC

(plc4x) 01/03: fix(plc4j/simulated): bound how much a tag may ask the device to make up

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 03d51246c7daedc4e67fbc6e08cdccb4c5df15a1
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Tue Aug 25 08:32:02 2026 +0200

    fix(plc4j/simulated): bound how much a tag may ask the device to make up
    
    The count in a simulated tag's address is multiplied by the size of one element to get
    the array the device fills with random bytes, and that multiplication was done in an
    int. So a large enough count did not ask for a large array, it asked for a negative
    one: four hundred million doubles is 3.2e9 bytes, which as an int is not a size at
    all. Below that it simply got what it asked for - three hundred million doubles is
    2.4 GB, from a tag address.
    
    The product is now measured as a long against a budget on the bytes rather than on the
    count, since what a count costs depends on the type beside it. There is no protocol
    here to take a limit from - the device invents whatever is asked of it - so the budget
    is stated for what it is: a size past which a test is not testing anything, set well
    above any real use. A count too wide to be a number is refused by the address pattern,
    so it no longer leaves as a NumberFormatException.
---
 .../plc4x/java/simulated/tag/SimulatedTag.java     | 45 ++++++++++++-
 .../java/simulated/tag/SimulatedTagBoundTest.java  | 74 ++++++++++++++++++++++
 2 files changed, 117 insertions(+), 2 deletions(-)

diff --git a/plc4j/drivers/simulated/src/main/java/org/apache/plc4x/java/simulated/tag/SimulatedTag.java b/plc4j/drivers/simulated/src/main/java/org/apache/plc4x/java/simulated/tag/SimulatedTag.java
index 4465acf6ed..e64f4ea125 100644
--- a/plc4j/drivers/simulated/src/main/java/org/apache/plc4x/java/simulated/tag/SimulatedTag.java
+++ b/plc4j/drivers/simulated/src/main/java/org/apache/plc4x/java/simulated/tag/SimulatedTag.java
@@ -22,6 +22,7 @@ import org.apache.plc4x.java.api.exceptions.PlcInvalidTagException;
 import org.apache.plc4x.java.api.model.ArrayInfo;
 import org.apache.plc4x.java.api.model.PlcTag;
 import org.apache.plc4x.java.api.types.PlcValueType;
+import org.apache.plc4x.java.simulated.readwrite.SimulatedDataTypeSizes;
 import org.apache.plc4x.java.simulated.types.SimulatedTagType;
 import org.apache.plc4x.java.spi.drivers.model.DefaultArrayInfo;
 
@@ -41,7 +42,17 @@ public class SimulatedTag implements PlcTag {
      * - {@code RANDOM/foo:INTEGER}
      * - {@code STDOUT/foo:STRING}
      */
-    private static final Pattern ADDRESS_PATTERN = Pattern.compile("^(?<type>\\w+)/(?<name>[a-zA-Z0-9_\\\\.]+):(?<dataType>[a-zA-Z0-9]++)(\\[(?<numElements>\\d+)])?$");
+    private static final Pattern ADDRESS_PATTERN = Pattern.compile(
+        "^(?<type>\\w+)/(?<name>[a-zA-Z0-9_\\\\.]+):(?<dataType>[a-zA-Z0-9]++)(\\[(?<numElements>\\d{1,9})])?$");
+
+    /**
+     * Largest amount of made-up data a single tag may ask for.
+     *
+     * <p>There is no protocol here to take a limit from - the device invents whatever is asked of
+     * it, and the count in the address decides how much. So this is simply a size past which a
+     * test is not testing anything, set well above any real use of it.</p>
+     */
+    static final int MAX_TAG_BYTES = 16 * 1024 * 1024;
 
     private final SimulatedTagType type;
     private final String name;
@@ -70,13 +81,43 @@ public class SimulatedTag implements PlcTag {
 
             int numElements = 1;
             if (matcher.group("numElements") != null) {
-                numElements = Integer.parseInt(matcher.group("numElements"));
+                numElements = checkNumElements(dataType, Integer.parseInt(matcher.group("numElements")));
             }
             return new SimulatedTag(type, name, dataType, numElements);
         }
         throw new PlcInvalidTagException("Unable to parse address: " + tagString);
     }
 
+    /**
+     * Checks that the tag asks for an amount of data that could be made.
+     *
+     * <p>The count is multiplied by the size of one element to get the array the device fills, and
+     * that multiplication is done in an int - so a large enough count does not produce a large
+     * array, it produces a negative one. Measuring the product as a long first is what keeps that
+     * from being the way anyone finds out.</p>
+     */
+    private static int checkNumElements(PlcValueType dataType, int numElements) throws PlcInvalidTagException {
+        if (numElements < 1) {
+            throw new PlcInvalidTagException("The number of elements must be greater than zero.");
+        }
+        long totalBytes = (long) numElements * elementSizeOf(dataType);
+        if (totalBytes > MAX_TAG_BYTES) {
+            throw new PlcInvalidTagException("A tag of " + numElements + " elements of type "
+                + dataType.name() + " would take " + totalBytes + " bytes, more than the "
+                + MAX_TAG_BYTES + " a simulated tag may take.");
+        }
+        return numElements;
+    }
+
+    /** What one element of this type occupies, or one byte for a type with no fixed size. */
+    private static int elementSizeOf(PlcValueType dataType) {
+        try {
+            return Math.max(1, SimulatedDataTypeSizes.valueOf(dataType.name()).getDataTypeSize());
+        } catch (IllegalArgumentException e) {
+            return 1;
+        }
+    }
+
     static boolean matches(String tagString) {
         return ADDRESS_PATTERN.matcher(tagString).matches();
     }
diff --git a/plc4j/drivers/simulated/src/test/java/org/apache/plc4x/java/simulated/tag/SimulatedTagBoundTest.java b/plc4j/drivers/simulated/src/test/java/org/apache/plc4x/java/simulated/tag/SimulatedTagBoundTest.java
new file mode 100644
index 0000000000..9c8d1d4771
--- /dev/null
+++ b/plc4j/drivers/simulated/src/test/java/org/apache/plc4x/java/simulated/tag/SimulatedTagBoundTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.simulated.tag;
+
+import org.apache.plc4x.java.api.exceptions.PlcInvalidTagException;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+
+
+/**
+ * The count in a simulated tag's address decides the size of the array the device fills with
+ * made-up data. That size was worked out in an int, so a large enough count did not produce a
+ * large array - it produced a negative one.
+ */
+public class SimulatedTagBoundTest {
+
+    /** How many elements the tag reports, read the way the device reads it. */
+    private static int elementsOf(String address) {
+        SimulatedTag tag = SimulatedTag.of(address);
+        return tag.getArrayInfo().isEmpty() ? 1 : tag.getArrayInfo().get(0).getSize();
+    }
+
+    @Test
+    void aCountWhoseSizeWouldNotFitAnIntIsRefused() {
+        // 400000000 LREALs is 3.2e9 bytes, which as an int is negative.
+        assertThrows(PlcInvalidTagException.class, () -> SimulatedTag.of("RANDOM/foo:LREAL[400000000]"));
+    }
+
+    @Test
+    void aCountThatWouldBeMerelyEnormousIsAlsoRefused() {
+        assertThrows(PlcInvalidTagException.class, () -> SimulatedTag.of("RANDOM/foo:LREAL[300000000]"));
+    }
+
+    @Test
+    void aCountTooWideToBeANumberIsAnInvalidTagNotANumberFormatError() {
+        assertThrows(PlcInvalidTagException.class, () -> SimulatedTag.of("RANDOM/foo:LREAL[99999999999]"));
+    }
+
+    @Test
+    void aWiderElementLeavesRoomForFewerOfThem() {
+        // The budget is in bytes, so the same count passes as a byte and fails as an eight-byte
+        // double: 4194304 of them is 32MiB, past the budget, while as SINT it is 4MiB.
+        assertEquals(4194304, elementsOf("RANDOM/foo:SINT[4194304]"));
+        assertThrows(PlcInvalidTagException.class, () -> SimulatedTag.of("RANDOM/foo:LREAL[4194304]"));
+    }
+
+    @Test
+    void aPlausibleCountStillParses() {
+        assertEquals(16, elementsOf("RANDOM/foo:LREAL[16]"));
+    }
+
+    @Test
+    void aTagWithNoCountIsStillOneElement() {
+        assertEquals(1, elementsOf("RANDOM/foo:LREAL"));
+    }
+}