This is an automated email from the ASF dual-hosted git repository.
chrisdutz pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/plc4x.git
The following commit(s) were added to refs/heads/develop by this push:
new aa26615734 bug(plc4j/ads): Disabling symbol-table reading disables
symbolic address usage.
aa26615734 is described below
commit aa26615734ea428da513d143c68059f632ae611c
Author: Christofer Dutz <[email protected]>
AuthorDate: Fri Aug 14 16:10:05 2026 +0200
bug(plc4j/ads): Disabling symbol-table reading disables symbolic address
usage.
Fixes: #1626
---
.../apache/plc4x/java/ads/AdsTcpConnection.java | 59 +++++++++++++---------
.../java/ads/configuration/AdsConfiguration.java | 2 +-
.../plc4x/java/ads/resolution/TagResolver.java | 24 +++++++++
.../plc4x/java/ads/resolution/TagResolverTest.java | 29 +++++++++++
.../modules/users/pages/protocols/ads.adoc | 13 +++--
website/asciidoc/modules/users/partials/ads.adoc | 2 +-
6 files changed, 98 insertions(+), 31 deletions(-)
diff --git
a/plc4j/drivers/ads/src/main/java/org/apache/plc4x/java/ads/AdsTcpConnection.java
b/plc4j/drivers/ads/src/main/java/org/apache/plc4x/java/ads/AdsTcpConnection.java
index c6a6032d00..89868dd126 100644
---
a/plc4j/drivers/ads/src/main/java/org/apache/plc4x/java/ads/AdsTcpConnection.java
+++
b/plc4j/drivers/ads/src/main/java/org/apache/plc4x/java/ads/AdsTcpConnection.java
@@ -60,9 +60,7 @@ import
org.apache.plc4x.java.spi.drivers.model.DefaultArrayInfo;
import org.apache.plc4x.java.spi.drivers.tags.PlcTagHandler;
import org.apache.plc4x.java.spi.transports.api.TransportInstance;
import org.apache.plc4x.java.spi.values.DefaultPlcValueHandler;
-import org.apache.plc4x.java.spi.values.PlcList;
import org.apache.plc4x.java.spi.values.PlcSTRING;
-import org.apache.plc4x.java.spi.values.PlcStruct;
import org.apache.plc4x.java.spi.values.PlcUDINT;
import org.apache.plc4x.java.spi.values.PlcValueHandler;
import org.apache.plc4x.java.utils.auditlog.api.AuditLog;
@@ -81,7 +79,6 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.stream.Collectors;
-import java.util.stream.IntStream;
/**
* ADS over TCP connection.
@@ -486,7 +483,8 @@ public class AdsTcpConnection extends
ConnectionBase<AdsConfiguration> {
// through the legacy ADSIGRP_SYM_HNDBYNAME round-trip — direct
(group, offset, size)
// resolution from the locally-cached tables is enough and lets us
support the full
// path syntax (".field", "[i]", multi-dim, mixed).
- TagResolver resolver = new TagResolver(symbolTable, dataTypeTable);
+ TagResolver resolver = new TagResolver(symbolTable, dataTypeTable,
+ getConfiguration().isLoadSymbolAndDataTypeTables());
Map<String, ResolvedAdsTag> resolved = new LinkedHashMap<>();
Map<String, PlcResponseCode> initialFailures = new HashMap<>();
for (String tagName : readRequest.getTagNames()) {
@@ -508,25 +506,19 @@ public class AdsTcpConnection extends
ConnectionBase<AdsConfiguration> {
}
private ResolvedAdsTag resolveForReadOrWrite(TagResolver resolver, PlcTag
tag) {
- if (tag == null) {
- throw new PlcInvalidTagException("Tag could not be parsed");
- }
- if (tag instanceof SymbolicAdsTag s) {
- return resolver.resolve(s);
- }
- if (tag instanceof DirectAdsStringTag s) {
- return new ResolvedAdsTag(s.getIndexGroup(), s.getIndexOffset(),
+ return switch (tag) {
+ case null -> throw new PlcInvalidTagException("Tag could not be
parsed");
+ case SymbolicAdsTag s -> resolver.resolve(s);
+ case DirectAdsStringTag s -> new ResolvedAdsTag(s.getIndexGroup(),
s.getIndexOffset(),
computeDirectSize(s.getPlcDataType(), s.getStringLength(),
s.getNumberOfElements()),
s.getPlcDataType(),
TagResolver.plcValueTypeForName(s.getPlcDataType(), null),
s.getStringLength(), Collections.emptyList());
- }
- if (tag instanceof DirectAdsTag d) {
- return new ResolvedAdsTag(d.getIndexGroup(), d.getIndexOffset(),
+ case DirectAdsTag d -> new ResolvedAdsTag(d.getIndexGroup(),
d.getIndexOffset(),
computeDirectSize(d.getPlcDataType(), 0,
d.getNumberOfElements()),
d.getPlcDataType(),
TagResolver.plcValueTypeForName(d.getPlcDataType(), null),
0, Collections.emptyList());
- }
- throw new PlcInvalidTagException("Unsupported tag type: " +
tag.getClass().getName());
+ default -> throw new PlcInvalidTagException("Unsupported tag type:
" + tag.getClass().getName());
+ };
}
private long computeDirectSize(String typeName, int stringLength, int
numberOfElements) {
@@ -598,7 +590,7 @@ public class AdsTcpConnection extends
ConnectionBase<AdsConfiguration> {
Map<String,
PlcResponseCode> initialFailures) {
// Tags participating in the sum-up read, in request order.
List<String> orderedNames = readRequest.getTagNames().stream()
- .filter(resolved::containsKey).collect(Collectors.toList());
+ .filter(resolved::containsKey).toList();
long expectedDataSize = orderedNames.stream()
.mapToLong(n -> 4L + resolved.get(n).sizeInBytes()) // 4-byte
per-item return code + data
@@ -710,7 +702,8 @@ public class AdsTcpConnection extends
ConnectionBase<AdsConfiguration> {
@Override
protected CompletableFuture<PlcWriteResponse> onWrite(PlcWriteRequest
writeRequest) {
- TagResolver resolver = new TagResolver(symbolTable, dataTypeTable);
+ TagResolver resolver = new TagResolver(symbolTable, dataTypeTable,
+ getConfiguration().isLoadSymbolAndDataTypeTables());
Map<String, ResolvedAdsTag> resolved = new LinkedHashMap<>();
Map<String, PlcResponseCode> initialFailures = new HashMap<>();
for (String tagName : writeRequest.getTagNames()) {
@@ -895,8 +888,11 @@ public class AdsTcpConnection extends
ConnectionBase<AdsConfiguration> {
}
AdsDataTypeTableEntry dataType = dataTypeOpt.get();
DirectAdsTag directAdsTag =
getDirectAdsTagForSymbolicName(t.getTag());
+ if (directAdsTag == null) {
+ continue;
+ }
// TODO: We should implement multi-dimensional arrays here ...
- int numberOfElements = (t.getArrayInfo().isEmpty()) ? 1 :
t.getArrayInfo().get(0).getSize();
+ int numberOfElements = (t.getArrayInfo().isEmpty()) ? 1 :
t.getArrayInfo().getFirst().getSize();
amsTCPPackets.add(new AmsTCPPacket(new
AdsAddDeviceNotificationRequest(
getConfiguration().getTargetAmsNetId(),
getConfiguration().getTargetAmsPort(),
getConfiguration().getSourceAmsNetId(),
getConfiguration().getSourceAmsPort(), ReturnCode.OK, getInvokeId(),
@@ -1084,6 +1080,19 @@ public class AdsTcpConnection extends
ConnectionBase<AdsConfiguration> {
protected CompletableFuture<PlcBrowseResponse>
onBrowseWithInterceptor(PlcBrowseRequest browseRequest,
PlcBrowseRequestInterceptor interceptor) {
Map<String, PlcResponseCode> responseCodes = new HashMap<>();
Map<String, List<PlcBrowseItem>> values = new HashMap<>();
+ if (!getConfiguration().isLoadSymbolAndDataTypeTables()) {
+ // Browsing lists the symbol table, so without it there is nothing
to browse. Report
+ // that instead of answering with an empty result, which would
look like a PLC without
+ // any symbols - see GH-1626.
+ LOGGER.warn("Cannot browse: the symbol and data-type tables were
not loaded because"
+ + " 'load-symbol-and-data-type-tables' is disabled.");
+ for (String queryName : browseRequest.getQueryNames()) {
+ responseCodes.put(queryName, PlcResponseCode.UNSUPPORTED);
+ values.put(queryName, Collections.emptyList());
+ }
+ return CompletableFuture.completedFuture(
+ new DefaultPlcBrowseResponse(browseRequest, responseCodes,
values));
+ }
for (String queryName : browseRequest.getQueryNames()) {
PlcQuery query = browseRequest.getQuery(queryName);
List<PlcBrowseItem> resultsForQuery = new ArrayList<>();
@@ -1191,11 +1200,11 @@ public class AdsTcpConnection extends
ConnectionBase<AdsConfiguration> {
List<SymbolicAdsTag> referencedSymbolicTags = tags.stream()
.filter(SymbolicAdsTag.class::isInstance)
.map(SymbolicAdsTag.class::cast)
- .collect(Collectors.toList());
+ .toList();
List<SymbolicAdsTag> symbolicTagsNeedingResolution =
referencedSymbolicTags.stream()
.filter(t -> getDirectAdsTagForSymbolicName(t) == null)
- .collect(Collectors.toList());
+ .toList();
if (!symbolicTagsNeedingResolution.isEmpty()) {
List<SymbolicAdsTag> requiredResolutionTags =
symbolicTagsNeedingResolution.stream()
@@ -1204,7 +1213,7 @@ public class AdsTcpConnection extends
ConnectionBase<AdsConfiguration> {
if (!requiredResolutionTags.isEmpty()) {
CompletableFuture<Void> resolutionFuture;
if (requiredResolutionTags.size() == 1) {
- SymbolicAdsTag t = requiredResolutionTags.get(0);
+ SymbolicAdsTag t = requiredResolutionTags.getFirst();
resolutionFuture = resolveSingleSymbolicAddress(t);
pendingResolutionRequests.put(t, resolutionFuture);
} else {
@@ -1364,7 +1373,7 @@ public class AdsTcpConnection extends
ConnectionBase<AdsConfiguration> {
}
}
for (AdsDataTypeTableEntry child :
adsDataTypeTableEntry.getChildren()) {
- if (child.getMainName().equals(remainingAddressParts.get(0))) {
+ if (child.getMainName().equals(remainingAddressParts.getFirst())) {
Optional<AdsDataTypeTableEntry> opt =
getDataTypeTableEntry(child.getSecondaryName());
if (opt.isEmpty()) {
throw new PlcRuntimeException("Could not resolve data type
" + child.getSecondaryName());
@@ -1375,7 +1384,7 @@ public class AdsTcpConnection extends
ConnectionBase<AdsConfiguration> {
}
}
throw new PlcRuntimeException(String.format("Couldn't find child with
name '%s' for type '%s'",
- remainingAddressParts.get(0),
adsDataTypeTableEntry.getMainName()));
+ remainingAddressParts.getFirst(),
adsDataTypeTableEntry.getMainName()));
}
private PlcValueType
getPlcValueTypeForAdsDataTypeForBrowse(AdsDataTypeTableEntry
dataTypeTableEntry) {
diff --git
a/plc4j/drivers/ads/src/main/java/org/apache/plc4x/java/ads/configuration/AdsConfiguration.java
b/plc4j/drivers/ads/src/main/java/org/apache/plc4x/java/ads/configuration/AdsConfiguration.java
index b299b56ec2..b876ba18eb 100644
---
a/plc4j/drivers/ads/src/main/java/org/apache/plc4x/java/ads/configuration/AdsConfiguration.java
+++
b/plc4j/drivers/ads/src/main/java/org/apache/plc4x/java/ads/configuration/AdsConfiguration.java
@@ -66,7 +66,7 @@ public class AdsConfiguration implements Configuration {
@ConfigurationParameter("load-symbol-and-data-type-tables")
@BooleanDefaultValue(true)
- @Description("Configures, if when connecting the data-type- and
symbol-table should be read. This is an optimization that can help in cases,
where the PLC program is pretty large and downloading the full tables is
causing problems. When disabled, only direct addresses
(`{IndexGroup}/{IndexOffset}:{TYPE}`) can be used: symbolic addresses are
resolved against these tables and will fail with `Unknown symbol` if they were
not loaded. Browsing is unavailable too, as it lists the symbol table.")
+ @Description("Configures, if when connecting the data-type- and
symbol-table should be read. This is an optimization that can help in cases,
where the PLC program is pretty large and downloading the full tables is
causing problems. When disabled, reading and writing is limited to direct
addresses (`{IndexGroup}/{IndexOffset}:{TYPE}`): symbolic addresses cannot be
resolved without the tables and are rejected with a corresponding error.
Browsing is unavailable for the same reason. Subs [...]
protected boolean loadSymbolAndDataTypeTables;
public AmsNetId getTargetAmsNetId() {
diff --git
a/plc4j/drivers/ads/src/main/java/org/apache/plc4x/java/ads/resolution/TagResolver.java
b/plc4j/drivers/ads/src/main/java/org/apache/plc4x/java/ads/resolution/TagResolver.java
index eccf9dec0f..2827d0a4b6 100644
---
a/plc4j/drivers/ads/src/main/java/org/apache/plc4x/java/ads/resolution/TagResolver.java
+++
b/plc4j/drivers/ads/src/main/java/org/apache/plc4x/java/ads/resolution/TagResolver.java
@@ -39,13 +39,37 @@ public final class TagResolver {
private final Map<String, AdsSymbolTableEntry> symbolTable;
private final Map<String, AdsDataTypeTableEntry> dataTypeTable;
+ private final boolean symbolTablesLoaded;
+
public TagResolver(Map<String, AdsSymbolTableEntry> symbolTable,
Map<String, AdsDataTypeTableEntry> dataTypeTable) {
+ this(symbolTable, dataTypeTable, true);
+ }
+
+ /**
+ * @param symbolTablesLoaded whether the symbol- and data-type tables were
loaded at all. When
+ * they weren't (the connection option
+ * {@code load-symbol-and-data-type-tables} is
disabled), symbolic
+ * addresses simply cannot be resolved and
saying so is more useful
+ * than reporting every symbol as unknown - see
GH-1626.
+ */
+ public TagResolver(Map<String, AdsSymbolTableEntry> symbolTable,
+ Map<String, AdsDataTypeTableEntry> dataTypeTable,
+ boolean symbolTablesLoaded) {
this.symbolTable = symbolTable;
this.dataTypeTable = dataTypeTable;
+ this.symbolTablesLoaded = symbolTablesLoaded;
}
public ResolvedAdsTag resolve(SymbolicAdsTag tag) {
+ if (!symbolTablesLoaded) {
+ throw new PlcInvalidTagException(
+ "Cannot resolve the symbolic address '" +
tag.getSymbolicAddress()
+ + "': the symbol and data-type tables were not loaded
because the connection"
+ + " option 'load-symbol-and-data-type-tables' is disabled.
Either enable that"
+ + " option or address the value directly as"
+ + " '{IndexGroup}/{IndexOffset}:{TYPE}'.");
+ }
AddressParser.AddressPart root =
AddressParser.parse(tag.getSymbolicAddress());
AdsSymbolTableEntry symbol = symbolTable.get(root.baseSegment());
if (symbol == null) {
diff --git
a/plc4j/drivers/ads/src/test/java/org/apache/plc4x/java/ads/resolution/TagResolverTest.java
b/plc4j/drivers/ads/src/test/java/org/apache/plc4x/java/ads/resolution/TagResolverTest.java
index 68dfe0ffdb..9254e15131 100644
---
a/plc4j/drivers/ads/src/test/java/org/apache/plc4x/java/ads/resolution/TagResolverTest.java
+++
b/plc4j/drivers/ads/src/test/java/org/apache/plc4x/java/ads/resolution/TagResolverTest.java
@@ -231,6 +231,35 @@ class TagResolverTest {
assertEquals(PlcValueType.Struct,
TagResolver.plcValueTypeForName(null, null));
}
+ /**
+ * With 'load-symbol-and-data-type-tables' disabled the tables are never
fetched, so symbolic
+ * addresses cannot be resolved at all. Saying that is far more useful
than reporting every
+ * symbol as unknown, which is what a user saw before - see GH-1626.
+ */
+ @Test
+ void symbolicAddressIsRejectedWhenTablesWereNotLoaded() {
+ TagResolver withoutTables = new TagResolver(symbols, types, false);
+
+ PlcInvalidTagException exception =
assertThrows(PlcInvalidTagException.class,
+ () -> withoutTables.resolve(new SymbolicAdsTag("MAIN.g_s32", null,
List.of())));
+
+ assertTrue(exception.getMessage().contains("MAIN.g_s32"),
exception.getMessage());
+
assertTrue(exception.getMessage().contains("load-symbol-and-data-type-tables"),
+ "the message has to name the option that disabled the tables: " +
exception.getMessage());
+ }
+
+ /**
+ * A genuinely unknown symbol still reports as unknown when the tables
*are* loaded - the new
+ * message must not swallow that case.
+ */
+ @Test
+ void unknownSymbolStillReportsAsUnknownWhenTablesAreLoaded() {
+ PlcInvalidTagException exception =
assertThrows(PlcInvalidTagException.class,
+ () -> resolver.resolve(new SymbolicAdsTag("MAIN.doesNotExist",
null, List.of())));
+
+ assertTrue(exception.getMessage().contains("Unknown symbol"),
exception.getMessage());
+ }
+
@Test
void extractStringLength_parsesParens() {
assertEquals(80, TagResolver.extractStringLength("STRING(80)"));
diff --git a/website/asciidoc/modules/users/pages/protocols/ads.adoc
b/website/asciidoc/modules/users/pages/protocols/ads.adoc
index f8803f4752..ccfe415254 100644
--- a/website/asciidoc/modules/users/pages/protocols/ads.adoc
+++ b/website/asciidoc/modules/users/pages/protocols/ads.adoc
@@ -47,9 +47,13 @@ The driver accepts three kinds of addresses.
MAIN.g_plant.channels[1].setpoints[4]
----
-Array indices may appear at any position of the path, not only at the end.
Symbolic addresses are
-resolved against the symbol- and data-type-table, so they require
-`load-symbol-and-data-type-tables` to be enabled (the default).
+Array indices may appear at any position of the path, not only at the end.
+
+Symbolic addresses are resolved against the symbol- and data-type-table, so
they require
+`load-symbol-and-data-type-tables` to be enabled (the default). With that
option disabled the
+tables are never fetched and a symbolic address is rejected with an error
saying so - use a direct
+address instead. Subscriptions are the exception: they resolve symbol handles
on the device and
+work either way.
*Direct addresses* name the index group and index offset explicitly, in
decimal or hexadecimal, and
carry the data type:
@@ -76,7 +80,8 @@ carry the data type:
----
Unlike symbolic addresses, direct addresses need no symbol table and therefore
work regardless of
-the `load-symbol-and-data-type-tables` setting.
+the `load-symbol-and-data-type-tables` setting. Browsing does need it, since
it lists the symbol
+table; with the option disabled a browse request is answered with
`UNSUPPORTED`.
== Connection String Options
diff --git a/website/asciidoc/modules/users/partials/ads.adoc
b/website/asciidoc/modules/users/partials/ads.adoc
index 3f999b856f..c29b2a5752 100644
--- a/website/asciidoc/modules/users/partials/ads.adoc
+++ b/website/asciidoc/modules/users/partials/ads.adoc
@@ -41,7 +41,7 @@
|`source-ams-net-id` |STRUCT | |required |AMS-Net-Id of the source. An
AMS-Net-Id has the regular format of an IPv4 IP-Address, however with 6
segments instead of 4.
|`source-ams-port` |INT | |required |AMS port of the source.
|`timeout-request` |INT |4000| |Default timeout for all types of requests.
-|`load-symbol-and-data-type-tables` |BOOLEAN |true| |Configures, if when
connecting the data-type- and symbol-table should be read. This is an
optimization that can help in cases, where the PLC program is pretty large and
downloading the full tables is causing problems. When disabled, symbolic
addresses will manually be resolved as soon as an address is used.
+|`load-symbol-and-data-type-tables` |BOOLEAN |true| |Configures, if when
connecting the data-type- and symbol-table should be read. This is an
optimization that can help in cases, where the PLC program is pretty large and
downloading the full tables is causing problems. When disabled, reading and
writing is limited to direct addresses (`{IndexGroup}/{IndexOffset}:{TYPE}`):
symbolic addresses cannot be resolved without the tables and are rejected with
a corresponding error. Browsing is un [...]
5+|Transport config options:
5+|
+++