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