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/18 12:41:11 UTC
(plc4x) branch develop updated (5c316cce12 -> 84cedbda2e)
This is an automated email from the ASF dual-hosted git repository.
sruehl pushed a change to branch develop
in repository https://gitbox.apache.org/repos/asf/plc4x.git
from 5c316cce12 test(plc4go/s7): re-enable the driver testsuite cases skipped since the s7h drift
new 2ed860e80c fix(plc4j/spi): make the XML buffer able to read back its own writer's output
new 84cedbda2e test(plc4j/s7): resurrect S7DriverIT on a Java-specific testsuite
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
plc4j/drivers/s7/pom.xml | 6 +
.../java/org/apache/plc4x/java/s7/S7DriverIT.java} | 16 +-
.../plc4x/java/s7/S7TestsuiteXmlDumperTest.java | 88 +++++
.../test/resources/testsuite/S7DriverTestsuite.xml | 421 +++++++++++++++++++++
.../spi/buffers/xmlbased/ReadBufferXmlBased.java | 14 +-
.../data/reader/DataReaderComplexDefault.java | 6 +
.../spi/fields/fields/reader/FieldReaderArray.java | 16 +-
.../spi/fields/fields/reader/FieldReaderEnum.java | 5 +-
.../fields/fields/reader/FieldReaderPadding.java | 5 +-
.../handlers/IncomingPlcMessageHandler.java | 9 +-
.../handlers/OutgoingPlcMessageHandler.java | 10 +-
11 files changed, 582 insertions(+), 14 deletions(-)
copy plc4j/drivers/{modbus/src/test/java/org/apache/plc4x/java/modbus/tcp/ModbusTcpAllDatatypesLittleEndianIT.java => s7/src/test/java/org/apache/plc4x/java/s7/S7DriverIT.java} (59%)
create mode 100644 plc4j/drivers/s7/src/test/java/org/apache/plc4x/java/s7/S7TestsuiteXmlDumperTest.java
create mode 100644 plc4j/drivers/s7/src/test/resources/testsuite/S7DriverTestsuite.xml
(plc4x) 02/02: test(plc4j/s7): resurrect S7DriverIT on a Java-specific testsuite
Posted by sr...@apache.org.
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 84cedbda2ec9563110beb8cfde15ce8bb87bf0f8
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Tue Aug 18 14:39:23 2026 +0200
test(plc4j/s7): resurrect S7DriverIT on a Java-specific testsuite
The old S7DriverIT was deleted in the new-SPI rewrite after both its
testcases had been skipped since the s7h mspec changes ('carcia changes').
It can't simply return on the shared protocols/s7 testsuite: that one is
TPKT/COTP framed (matching plc4go, whose driver speaks COTP itself),
while this driver delegates COTP framing to the cotp transport - and the
testsuite runner swaps the transport for the synthetic test transport, so
the driver-visible wire is bare S7 messages.
The new testsuite describes exactly that boundary: setup communication,
the COMPONENT_IDENTIFICATION SZL probe, and the two classic read
testcases (value read + PUT/GET-disabled refusal). Message bodies are the
canonical WriteBufferXmlBased serialization, generated by the
accompanying S7TestsuiteXmlDumperTest.
---
plc4j/drivers/s7/pom.xml | 6 +
.../java/org/apache/plc4x/java/s7/S7DriverIT.java | 37 ++
.../plc4x/java/s7/S7TestsuiteXmlDumperTest.java | 88 +++++
.../test/resources/testsuite/S7DriverTestsuite.xml | 421 +++++++++++++++++++++
4 files changed, 552 insertions(+)
diff --git a/plc4j/drivers/s7/pom.xml b/plc4j/drivers/s7/pom.xml
index 225e27d194..4c5d961545 100644
--- a/plc4j/drivers/s7/pom.xml
+++ b/plc4j/drivers/s7/pom.xml
@@ -233,6 +233,12 @@
<version>1.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.plc4x</groupId>
+ <artifactId>plc4j-spi-buffers-xml</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>org.skyscreamer</groupId>
<artifactId>jsonassert</artifactId>
diff --git a/plc4j/drivers/s7/src/test/java/org/apache/plc4x/java/s7/S7DriverIT.java b/plc4j/drivers/s7/src/test/java/org/apache/plc4x/java/s7/S7DriverIT.java
new file mode 100644
index 0000000000..240657b8c6
--- /dev/null
+++ b/plc4j/drivers/s7/src/test/java/org/apache/plc4x/java/s7/S7DriverIT.java
@@ -0,0 +1,37 @@
+/*
+ * 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
+ *
+ * https://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.s7;
+
+import org.apache.plc4x.java.utils.testutils.driver.DriverTestsuiteRunner;
+
+/**
+ * Runs the Java specific S7 driver testsuite.
+ * <p>
+ * The shared testsuite in the protocols/s7 module is TPKT/COTP framed and only fits drivers
+ * that speak COTP themselves (like plc4go's). This driver delegates that framing to the cotp
+ * transport, which the testsuite runner replaces with the synthetic test transport - so its
+ * testsuite describes the bare S7 message exchange instead.
+ */
+public class S7DriverIT extends DriverTestsuiteRunner {
+
+ public S7DriverIT() {
+ super("/testsuite/S7DriverTestsuite.xml", "org.apache.plc4x.java.s7.readwrite");
+ }
+
+}
diff --git a/plc4j/drivers/s7/src/test/java/org/apache/plc4x/java/s7/S7TestsuiteXmlDumperTest.java b/plc4j/drivers/s7/src/test/java/org/apache/plc4x/java/s7/S7TestsuiteXmlDumperTest.java
new file mode 100644
index 0000000000..dc50e5f7f2
--- /dev/null
+++ b/plc4j/drivers/s7/src/test/java/org/apache/plc4x/java/s7/S7TestsuiteXmlDumperTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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
+ *
+ * https://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.s7;
+
+import org.apache.plc4x.java.s7.readwrite.*;
+import org.apache.plc4x.java.s7.userdata.S7SzlService;
+import org.apache.plc4x.java.spi.buffers.xmlbased.WriteBufferXmlBased;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.HexFormat;
+import java.util.List;
+
+/**
+ * Temporary helper dumping the canonical XML serialization of the messages used by the
+ * S7 driver testsuite into target/testsuite-xml/.
+ */
+class S7TestsuiteXmlDumperTest {
+
+ @Test
+ void dumpTestsuiteMessages() throws Exception {
+ // Setup communication request/response
+ dump("01-setup-request", new S7MessageRequest(10,
+ new S7ParameterSetupCommunication(8, 8, 1024), null));
+ dump("02-setup-response", new S7MessageResponseData(10,
+ new S7ParameterSetupCommunication(3, 3, 240), null, (short) 0, (short) 0));
+
+ // SZL identification request/response
+ dump("03-szl-request", S7SzlService.buildRequest(11, S7SzlService.COMPONENT_IDENTIFICATION, 0x0001));
+ byte[] szlItems = HexFormat.of().parseHex(
+ "001c0001001c0001000136455337203231322d31424433302d3058423020202000012020");
+ S7ParameterUserDataItemCPUFunctions szlResponseParameter = new S7ParameterUserDataItemCPUFunctions(
+ (short) 0x12, (byte) 0x08, (byte) 0x04, (short) 0x01, (short) 0x01,
+ (short) 0, (short) 0, 0);
+ dump("04-szl-response", new S7MessageUserData(11,
+ new S7ParameterUserData(Collections.singletonList(szlResponseParameter)),
+ new S7PayloadUserData(Collections.singletonList(
+ new S7PayloadUserDataItemCpuFunctionReadSzlResponse(
+ DataTransportErrorCode.OK, DataTransportSize.OCTET_STRING, szlItems.length, szlItems)))));
+
+ // Read request/response
+ dump("05-read-request", new S7MessageRequest(12,
+ new S7ParameterReadVarRequest(List.of(
+ new S7VarRequestParameterItemAddress(
+ new S7AddressAny(TransportSize.BOOL, 1, 0, MemoryArea.OUTPUTS, 0, (byte) 0)))),
+ null));
+ dump("06-read-response", new S7MessageResponseData(12,
+ new S7ParameterReadVarResponse((short) 1),
+ new S7PayloadReadVarResponse(List.of(
+ new S7VarPayloadDataItem(DataTransportErrorCode.OK, DataTransportSize.BIT, new byte[]{0x01}))),
+ (short) 0, (short) 0));
+
+ // PUT/GET disabled error response
+ dump("07-putget-error-response", new S7MessageResponse(12, null, null, (short) 129, (short) 4));
+ }
+
+ private void dump(String name, S7Message message) throws Exception {
+ WriteBufferXmlBased writeBuffer = new WriteBufferXmlBased();
+ writeBuffer.writeMessage(message);
+ write(name, writeBuffer.getXmlString());
+ }
+
+ private void write(String name, String xml) throws IOException {
+ Path directory = Path.of("target", "testsuite-xml");
+ Files.createDirectories(directory);
+ Files.writeString(directory.resolve(name + ".xml"), xml, StandardCharsets.UTF_8);
+ }
+}
diff --git a/plc4j/drivers/s7/src/test/resources/testsuite/S7DriverTestsuite.xml b/plc4j/drivers/s7/src/test/resources/testsuite/S7DriverTestsuite.xml
new file mode 100644
index 0000000000..07828fc4b4
--- /dev/null
+++ b/plc4j/drivers/s7/src/test/resources/testsuite/S7DriverTestsuite.xml
@@ -0,0 +1,421 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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
+
+ https://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.
+ -->
+<!--
+ Java-specific S7 driver testsuite.
+
+ Unlike the shared testsuite in the protocols/s7 module (which is TPKT/COTP framed and used
+ by plc4go, whose driver speaks COTP itself over a TCP transport), the plc4j S7 driver
+ delegates TPKT/COTP framing to the cotp transport. The driver testsuite runner replaces the
+ transport with the synthetic test transport, so the driver-visible wire consists of bare
+ S7 messages - which is what the steps below describe.
+
+ The message XML bodies are the canonical WriteBufferXmlBased serialization of the messages.
+ -->
+<test:driver-testsuite xmlns:test="https://plc4x.apache.org/schemas/driver-testsuite.xsd"
+ byteOrder="BIG_ENDIAN">
+
+ <name>S7 (COTP transport boundary)</name>
+
+ <protocol-name>s7</protocol-name>
+ <output-flavor>read-write</output-flavor>
+
+ <driver-name>s7</driver-name>
+
+ <setup>
+ <!-- The driver starts with an S7 setup-communication request (tpdu ids start at 10) -->
+ <outgoing-plc-message name="Send S7 Setup Communication Request">
+ <S7Message>
+ <protocolId dataType="uint" bitLength="8">50</protocolId>
+ <messageType dataType="uint" bitLength="8">1</messageType>
+ <reserved dataType="uint" bitLength="16">0</reserved>
+ <tpduReference dataType="uint" bitLength="16">10</tpduReference>
+ <parameterLength dataType="uint" bitLength="16">8</parameterLength>
+ <payloadLength dataType="uint" bitLength="16">0</payloadLength>
+ <S7MessageRequest>
+ </S7MessageRequest>
+ <parameter>
+ <S7Parameter>
+ <parameterType dataType="uint" bitLength="8">240</parameterType>
+ <S7ParameterSetupCommunication>
+ <reserved dataType="uint" bitLength="8">0</reserved>
+ <maxAmqCaller dataType="uint" bitLength="16">8</maxAmqCaller>
+ <maxAmqCallee dataType="uint" bitLength="16">8</maxAmqCallee>
+ <pduLength dataType="uint" bitLength="16">1024</pduLength>
+ </S7ParameterSetupCommunication>
+ </S7Parameter>
+ </parameter>
+ </S7Message>
+ </outgoing-plc-message>
+ <!-- Which the PLC acknowledges with the negotiated values -->
+ <incoming-plc-message name="Receive S7 Setup Communication Response">
+ <S7Message>
+ <protocolId dataType="uint" bitLength="8">50</protocolId>
+ <messageType dataType="uint" bitLength="8">3</messageType>
+ <reserved dataType="uint" bitLength="16">0</reserved>
+ <tpduReference dataType="uint" bitLength="16">10</tpduReference>
+ <parameterLength dataType="uint" bitLength="16">8</parameterLength>
+ <payloadLength dataType="uint" bitLength="16">0</payloadLength>
+ <S7MessageResponseData>
+ <errorClass dataType="uint" bitLength="8">0</errorClass>
+ <errorCode dataType="uint" bitLength="8">0</errorCode>
+ </S7MessageResponseData>
+ <parameter>
+ <S7Parameter>
+ <parameterType dataType="uint" bitLength="8">240</parameterType>
+ <S7ParameterSetupCommunication>
+ <reserved dataType="uint" bitLength="8">0</reserved>
+ <maxAmqCaller dataType="uint" bitLength="16">3</maxAmqCaller>
+ <maxAmqCallee dataType="uint" bitLength="16">3</maxAmqCallee>
+ <pduLength dataType="uint" bitLength="16">240</pduLength>
+ </S7ParameterSetupCommunication>
+ </S7Parameter>
+ </parameter>
+ </S7Message>
+ </incoming-plc-message>
+ <!-- Next the driver probes the COMPONENT_IDENTIFICATION SZL to identify the device -->
+ <outgoing-plc-message name="Send S7 Identification Request">
+ <S7Message>
+ <protocolId dataType="uint" bitLength="8">50</protocolId>
+ <messageType dataType="uint" bitLength="8">7</messageType>
+ <reserved dataType="uint" bitLength="16">0</reserved>
+ <tpduReference dataType="uint" bitLength="16">11</tpduReference>
+ <parameterLength dataType="uint" bitLength="16">8</parameterLength>
+ <payloadLength dataType="uint" bitLength="16">8</payloadLength>
+ <S7MessageUserData>
+ </S7MessageUserData>
+ <parameter>
+ <S7Parameter>
+ <parameterType dataType="uint" bitLength="8">0</parameterType>
+ <S7ParameterUserData>
+ <numItems dataType="uint" bitLength="8">1</numItems>
+ <items isList="true">
+ <S7ParameterUserDataItem>
+ <itemType dataType="uint" bitLength="8">18</itemType>
+ <S7ParameterUserDataItemCPUFunctions>
+ <itemLength dataType="uint" bitLength="8">4</itemLength>
+ <method dataType="uint" bitLength="8">17</method>
+ <cpuFunctionType dataType="uint" bitLength="4">4</cpuFunctionType>
+ <cpuFunctionGroup dataType="uint" bitLength="4">4</cpuFunctionGroup>
+ <cpuSubfunction dataType="uint" bitLength="8">1</cpuSubfunction>
+ <sequenceNumber dataType="uint" bitLength="8">0</sequenceNumber>
+ </S7ParameterUserDataItemCPUFunctions>
+ </S7ParameterUserDataItem>
+ </items>
+ </S7ParameterUserData>
+ </S7Parameter>
+ </parameter>
+ <payload>
+ <S7Payload>
+ <S7PayloadUserData>
+ <items isList="true">
+ <S7PayloadUserDataItem>
+ <returnCode>
+ <DataTransportErrorCode dataType="uint" bitLength="8" stringRepresentation="OK">255</DataTransportErrorCode>
+ </returnCode>
+ <transportSize>
+ <DataTransportSize dataType="uint" bitLength="8" stringRepresentation="OCTET_STRING">9</DataTransportSize>
+ </transportSize>
+ <dataLength dataType="uint" bitLength="16">4</dataLength>
+ <S7PayloadUserDataItemCpuFunctionReadSzlRequest>
+ <szlId>
+ <SzlId>
+ <typeClass>
+ <SzlModuleTypeClass dataType="uint" bitLength="4" stringRepresentation="CPU">0</SzlModuleTypeClass>
+ </typeClass>
+ <sublistExtract dataType="uint" bitLength="4">0</sublistExtract>
+ <sublistList>
+ <SzlSublist dataType="uint" bitLength="8" stringRepresentation="COMPONENT_IDENTIFICATION">28</SzlSublist>
+ </sublistList>
+ </SzlId>
+ </szlId>
+ <szlIndex dataType="uint" bitLength="16">1</szlIndex>
+ </S7PayloadUserDataItemCpuFunctionReadSzlRequest>
+ </S7PayloadUserDataItem>
+ </items>
+ </S7PayloadUserData>
+ </S7Payload>
+ </payload>
+ </S7Message>
+ </outgoing-plc-message>
+ <!-- Which the PLC will gladly provide to us -->
+ <incoming-plc-message name="Receive S7 Identification Response">
+ <S7Message>
+ <protocolId dataType="uint" bitLength="8">50</protocolId>
+ <messageType dataType="uint" bitLength="8">7</messageType>
+ <reserved dataType="uint" bitLength="16">0</reserved>
+ <tpduReference dataType="uint" bitLength="16">11</tpduReference>
+ <parameterLength dataType="uint" bitLength="16">12</parameterLength>
+ <payloadLength dataType="uint" bitLength="16">40</payloadLength>
+ <S7MessageUserData>
+ </S7MessageUserData>
+ <parameter>
+ <S7Parameter>
+ <parameterType dataType="uint" bitLength="8">0</parameterType>
+ <S7ParameterUserData>
+ <numItems dataType="uint" bitLength="8">1</numItems>
+ <items isList="true">
+ <S7ParameterUserDataItem>
+ <itemType dataType="uint" bitLength="8">18</itemType>
+ <S7ParameterUserDataItemCPUFunctions>
+ <itemLength dataType="uint" bitLength="8">8</itemLength>
+ <method dataType="uint" bitLength="8">18</method>
+ <cpuFunctionType dataType="uint" bitLength="4">8</cpuFunctionType>
+ <cpuFunctionGroup dataType="uint" bitLength="4">4</cpuFunctionGroup>
+ <cpuSubfunction dataType="uint" bitLength="8">1</cpuSubfunction>
+ <sequenceNumber dataType="uint" bitLength="8">1</sequenceNumber>
+ <dataUnitReferenceNumber dataType="uint" bitLength="8">0</dataUnitReferenceNumber>
+ <lastDataUnit dataType="uint" bitLength="8">0</lastDataUnit>
+ <errorCode dataType="uint" bitLength="16">0</errorCode>
+ </S7ParameterUserDataItemCPUFunctions>
+ </S7ParameterUserDataItem>
+ </items>
+ </S7ParameterUserData>
+ </S7Parameter>
+ </parameter>
+ <payload>
+ <S7Payload>
+ <S7PayloadUserData>
+ <items isList="true">
+ <S7PayloadUserDataItem>
+ <returnCode>
+ <DataTransportErrorCode dataType="uint" bitLength="8" stringRepresentation="OK">255</DataTransportErrorCode>
+ </returnCode>
+ <transportSize>
+ <DataTransportSize dataType="uint" bitLength="8" stringRepresentation="OCTET_STRING">9</DataTransportSize>
+ </transportSize>
+ <dataLength dataType="uint" bitLength="16">36</dataLength>
+ <S7PayloadUserDataItemCpuFunctionReadSzlResponse>
+ <items dataType="byte" bitLength="288">0x001c0001001c0001000136455337203231322d31424433302d3058423020202000012020</items>
+ </S7PayloadUserDataItemCpuFunctionReadSzlResponse>
+ </S7PayloadUserDataItem>
+ </items>
+ </S7PayloadUserData>
+ </S7Payload>
+ </payload>
+ </S7Message>
+ </incoming-plc-message>
+ </setup>
+
+ <testcase>
+ <name>Single element read request</name>
+ <steps>
+ <api-request name="Receive Read Request from application">
+ <TestReadRequest>
+ <tags isList="true">
+ <tag>
+ <name>hurz</name>
+ <address>%Q0.0:BOOL</address>
+ </tag>
+ </tags>
+ </TestReadRequest>
+ </api-request>
+ <outgoing-plc-message name="Send S7 Read Request">
+ <S7Message>
+ <protocolId dataType="uint" bitLength="8">50</protocolId>
+ <messageType dataType="uint" bitLength="8">1</messageType>
+ <reserved dataType="uint" bitLength="16">0</reserved>
+ <tpduReference dataType="uint" bitLength="16">12</tpduReference>
+ <parameterLength dataType="uint" bitLength="16">14</parameterLength>
+ <payloadLength dataType="uint" bitLength="16">0</payloadLength>
+ <S7MessageRequest>
+ </S7MessageRequest>
+ <parameter>
+ <S7Parameter>
+ <parameterType dataType="uint" bitLength="8">4</parameterType>
+ <S7ParameterReadVarRequest>
+ <numItems dataType="uint" bitLength="8">1</numItems>
+ <items isList="true">
+ <S7VarRequestParameterItem>
+ <itemType dataType="uint" bitLength="8">18</itemType>
+ <S7VarRequestParameterItemAddress>
+ <itemLength dataType="uint" bitLength="8">10</itemLength>
+ <address>
+ <S7Address>
+ <addressType dataType="uint" bitLength="8">16</addressType>
+ <S7AddressAny>
+ <transportSize>
+ <transportSize dataType="uint" bitLength="8" stringRepresentation="BOOL">1</transportSize>
+ </transportSize>
+ <numberOfElements dataType="uint" bitLength="16">1</numberOfElements>
+ <dbNumber dataType="uint" bitLength="16">0</dbNumber>
+ <area>
+ <MemoryArea dataType="uint" bitLength="8" stringRepresentation="OUTPUTS">130</MemoryArea>
+ </area>
+ <reserved dataType="uint" bitLength="5">0</reserved>
+ <byteAddress dataType="uint" bitLength="16">0</byteAddress>
+ <bitAddress dataType="uint" bitLength="3">0</bitAddress>
+ </S7AddressAny>
+ </S7Address>
+ </address>
+ </S7VarRequestParameterItemAddress>
+ </S7VarRequestParameterItem>
+ </items>
+ </S7ParameterReadVarRequest>
+ </S7Parameter>
+ </parameter>
+ </S7Message>
+ </outgoing-plc-message>
+ <incoming-plc-message name="Receive S7 Read Response">
+ <S7Message>
+ <protocolId dataType="uint" bitLength="8">50</protocolId>
+ <messageType dataType="uint" bitLength="8">3</messageType>
+ <reserved dataType="uint" bitLength="16">0</reserved>
+ <tpduReference dataType="uint" bitLength="16">12</tpduReference>
+ <parameterLength dataType="uint" bitLength="16">2</parameterLength>
+ <payloadLength dataType="uint" bitLength="16">5</payloadLength>
+ <S7MessageResponseData>
+ <errorClass dataType="uint" bitLength="8">0</errorClass>
+ <errorCode dataType="uint" bitLength="8">0</errorCode>
+ </S7MessageResponseData>
+ <parameter>
+ <S7Parameter>
+ <parameterType dataType="uint" bitLength="8">4</parameterType>
+ <S7ParameterReadVarResponse>
+ <numItems dataType="uint" bitLength="8">1</numItems>
+ </S7ParameterReadVarResponse>
+ </S7Parameter>
+ </parameter>
+ <payload>
+ <S7Payload>
+ <S7PayloadReadVarResponse>
+ <items isList="true">
+ <S7VarPayloadDataItem>
+ <returnCode>
+ <DataTransportErrorCode dataType="uint" bitLength="8" stringRepresentation="OK">255</DataTransportErrorCode>
+ </returnCode>
+ <transportSize>
+ <DataTransportSize dataType="uint" bitLength="8" stringRepresentation="BIT">3</DataTransportSize>
+ </transportSize>
+ <dataLength dataType="uint" bitLength="16">1</dataLength>
+ <data dataType="byte" bitLength="8">0x01</data>
+ <padding isList="true">
+ </padding>
+ </S7VarPayloadDataItem>
+ </items>
+ </S7PayloadReadVarResponse>
+ </S7Payload>
+ </payload>
+ </S7Message>
+ </incoming-plc-message>
+ <api-response name="Report Read Response to application">
+ <PlcReadResponse>
+ <values isList="true">
+ <hurz>
+ <PlcResponseItem>
+ <code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
+ <value>
+ <PlcBOOL dataType="bit" bitLength="1">true</PlcBOOL>
+ </value>
+ </PlcResponseItem>
+ </hurz>
+ </values>
+ </PlcReadResponse>
+ </api-response>
+ </steps>
+ </testcase>
+
+ <testcase>
+ <name>Single element read request with disabled PUT/GET</name>
+ <steps>
+ <api-request name="Receive Read Request from application">
+ <TestReadRequest>
+ <tags isList="true">
+ <tag>
+ <name>hurz</name>
+ <address>%Q0.0:BOOL</address>
+ </tag>
+ </tags>
+ </TestReadRequest>
+ </api-request>
+ <outgoing-plc-message name="Send S7 Read Request">
+ <S7Message>
+ <protocolId dataType="uint" bitLength="8">50</protocolId>
+ <messageType dataType="uint" bitLength="8">1</messageType>
+ <reserved dataType="uint" bitLength="16">0</reserved>
+ <tpduReference dataType="uint" bitLength="16">12</tpduReference>
+ <parameterLength dataType="uint" bitLength="16">14</parameterLength>
+ <payloadLength dataType="uint" bitLength="16">0</payloadLength>
+ <S7MessageRequest>
+ </S7MessageRequest>
+ <parameter>
+ <S7Parameter>
+ <parameterType dataType="uint" bitLength="8">4</parameterType>
+ <S7ParameterReadVarRequest>
+ <numItems dataType="uint" bitLength="8">1</numItems>
+ <items isList="true">
+ <S7VarRequestParameterItem>
+ <itemType dataType="uint" bitLength="8">18</itemType>
+ <S7VarRequestParameterItemAddress>
+ <itemLength dataType="uint" bitLength="8">10</itemLength>
+ <address>
+ <S7Address>
+ <addressType dataType="uint" bitLength="8">16</addressType>
+ <S7AddressAny>
+ <transportSize>
+ <transportSize dataType="uint" bitLength="8" stringRepresentation="BOOL">1</transportSize>
+ </transportSize>
+ <numberOfElements dataType="uint" bitLength="16">1</numberOfElements>
+ <dbNumber dataType="uint" bitLength="16">0</dbNumber>
+ <area>
+ <MemoryArea dataType="uint" bitLength="8" stringRepresentation="OUTPUTS">130</MemoryArea>
+ </area>
+ <reserved dataType="uint" bitLength="5">0</reserved>
+ <byteAddress dataType="uint" bitLength="16">0</byteAddress>
+ <bitAddress dataType="uint" bitLength="3">0</bitAddress>
+ </S7AddressAny>
+ </S7Address>
+ </address>
+ </S7VarRequestParameterItemAddress>
+ </S7VarRequestParameterItem>
+ </items>
+ </S7ParameterReadVarRequest>
+ </S7Parameter>
+ </parameter>
+ </S7Message>
+ </outgoing-plc-message>
+ <incoming-plc-message name="Receive S7 Read Response with PUT/GET disabled error">
+ <S7Message>
+ <protocolId dataType="uint" bitLength="8">50</protocolId>
+ <messageType dataType="uint" bitLength="8">2</messageType>
+ <reserved dataType="uint" bitLength="16">0</reserved>
+ <tpduReference dataType="uint" bitLength="16">12</tpduReference>
+ <parameterLength dataType="uint" bitLength="16">0</parameterLength>
+ <payloadLength dataType="uint" bitLength="16">0</payloadLength>
+ <S7MessageResponse>
+ <errorClass dataType="uint" bitLength="8">129</errorClass>
+ <errorCode dataType="uint" bitLength="8">4</errorCode>
+ </S7MessageResponse>
+ </S7Message>
+ </incoming-plc-message>
+ <api-response name="Report Read Response to application">
+ <PlcReadResponse>
+ <values isList="true">
+ <hurz>
+ <PlcResponseItem>
+ <code dataType="string" bitLength="16" encoding="UTF-8">ACCESS_DENIED</code>
+ </PlcResponseItem>
+ </hurz>
+ </values>
+ </PlcReadResponse>
+ </api-response>
+ </steps>
+ </testcase>
+
+</test:driver-testsuite>
(plc4x) 01/02: fix(plc4j/spi): make the XML buffer able to read back its own writer's output
Posted by sr...@apache.org.
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 2ed860e80cbd96258984777d06f197cba91d90dc
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Tue Aug 18 14:39:01 2026 +0200
fix(plc4j/spi): make the XML buffer able to read back its own writer's output
The driver testsuite runner round-trips messages through the XML buffers,
which exposed several parse/serialize asymmetries that made any XML-driven
driver testsuite fail:
- reserved fields are serialized as plain <reserved> but parsed with the
qualified '<Type>.reservedN' name - the reader now accepts the
serializer's form
- complex array elements are serialized without a logical-name wrapper
(the element type provides the element), but the complex data reader
pushed one per element - array element reads now carry the
render-as-list marker and the complex reader skips the wrapper for them
- enum fields are serialized as <fieldName><EnumTypeName ...> where the
inner name is only known dynamically - the enum field reader now reads
the inner element with a wildcard name
- padding fields are serialized under an explicit 'padding' context that
the reader didn't set
The testsuite runner's byte buffers also lacked the standard
unsigned-binary/twos-complement/IEEE754 encodings every driver codec
configures, so generated (de)serializers failed on fields without
explicit per-field options.
Byte-based parsing is unaffected: contexts and logical names are no-ops
there.
---
.../java/spi/buffers/xmlbased/ReadBufferXmlBased.java | 14 ++++++++++++--
.../spi/fields/data/reader/DataReaderComplexDefault.java | 6 ++++++
.../java/spi/fields/fields/reader/FieldReaderArray.java | 16 +++++++++++++---
.../java/spi/fields/fields/reader/FieldReaderEnum.java | 5 ++++-
.../spi/fields/fields/reader/FieldReaderPadding.java | 5 +++--
.../internal/handlers/IncomingPlcMessageHandler.java | 9 ++++++++-
.../internal/handlers/OutgoingPlcMessageHandler.java | 10 +++++++++-
7 files changed, 55 insertions(+), 10 deletions(-)
diff --git a/plc4j/spi/buffers/xml/src/main/java/org/apache/plc4x/java/spi/buffers/xmlbased/ReadBufferXmlBased.java b/plc4j/spi/buffers/xml/src/main/java/org/apache/plc4x/java/spi/buffers/xmlbased/ReadBufferXmlBased.java
index b18c19896b..6876646590 100644
--- a/plc4j/spi/buffers/xml/src/main/java/org/apache/plc4x/java/spi/buffers/xmlbased/ReadBufferXmlBased.java
+++ b/plc4j/spi/buffers/xml/src/main/java/org/apache/plc4x/java/spi/buffers/xmlbased/ReadBufferXmlBased.java
@@ -287,12 +287,22 @@ public class ReadBufferXmlBased extends AbstractBuffer implements ReadBuffer, Xm
private void validateStartElement(StartElement startElement, String name, String dataType, int numBits) throws BufferException {
//name = sanitizeLogicalName(name);
- if (!startElement.getName().getLocalPart().equals(name)) {
- throw new BufferException(String.format("unexpected element '%s'. Expected '%s'", startElement.getName().getLocalPart(), name));
+ String elementName = startElement.getName().getLocalPart();
+ // "*" matches any element name: used for elements whose serialized name is only known
+ // dynamically (e.g. enum fields are written as <fieldName><EnumTypeName ...>).
+ if (!"*".equals(name) && !elementName.equals(name) && !isReservedFieldName(name, elementName)) {
+ throw new BufferException(String.format("unexpected element '%s'. Expected '%s'", elementName, name));
}
validateAttr(startElement.getAttributes(), name, dataType, numBits);
}
+ private boolean isReservedFieldName(String expectedName, String elementName) {
+ // The generated parsers qualify reserved fields as "<Type>.reserved<N>" while the
+ // generated serializers write them plainly as "reserved" - accept the serializer's
+ // form so XML written by WriteBufferXmlBased can be read back.
+ return "reserved".equals(elementName) && expectedName.matches("(.*\\.)?reserved\\d*");
+ }
+
private void validateAttr(Iterator<Attribute> attr, String name, String dataType, int numBits) throws BufferException {
boolean dataTypeValidated = false;
boolean numBitsValidate = false;
diff --git a/plc4j/spi/fields/src/main/java/org/apache/plc4x/java/spi/fields/data/reader/DataReaderComplexDefault.java b/plc4j/spi/fields/src/main/java/org/apache/plc4x/java/spi/fields/data/reader/DataReaderComplexDefault.java
index b0673f0c83..6bcadc8f66 100644
--- a/plc4j/spi/fields/src/main/java/org/apache/plc4x/java/spi/fields/data/reader/DataReaderComplexDefault.java
+++ b/plc4j/spi/fields/src/main/java/org/apache/plc4x/java/spi/fields/data/reader/DataReaderComplexDefault.java
@@ -50,6 +50,12 @@ public class DataReaderComplexDefault<T> implements DataReaderComplex<T> {
}
public T read(ComplexTypeSupplier<T> complexSupplier, WithOption... options) throws BufferException {
+ // Array elements carry the render-as-list marker: the serializer writes them without a
+ // logical-name wrapper (the element type's own serialize provides the element), so
+ // skip the wrapper context here as well to keep parse and serialize symmetric.
+ if (WithOption.extractRenderAsList(options).orElse(false)) {
+ return complexSupplier.get();
+ }
readBuffer.pushContext(options);
final T t = complexSupplier.get();
readBuffer.popContext(options);
diff --git a/plc4j/spi/fields/src/main/java/org/apache/plc4x/java/spi/fields/fields/reader/FieldReaderArray.java b/plc4j/spi/fields/src/main/java/org/apache/plc4x/java/spi/fields/fields/reader/FieldReaderArray.java
index 9f454bb67f..df9acfeb52 100644
--- a/plc4j/spi/fields/src/main/java/org/apache/plc4x/java/spi/fields/fields/reader/FieldReaderArray.java
+++ b/plc4j/spi/fields/src/main/java/org/apache/plc4x/java/spi/fields/fields/reader/FieldReaderArray.java
@@ -64,7 +64,7 @@ public class FieldReaderArray<T> implements FieldCommons {
// Make some variables available that would be otherwise challenging to forward.
ThreadLocalHelper.curItemThreadLocal.set(curItem);
ThreadLocalHelper.lastItemThreadLocal.set(curItem == itemCount - 1);
- result.add(dataReader.read(options));
+ result.add(dataReader.read(listElementOptions(options)));
}
dataReader.popContext(options);
LOGGER.debug("done reading field {}", getName(options));
@@ -83,7 +83,7 @@ public class FieldReaderArray<T> implements FieldCommons {
LOGGER.debug("start reading at pos {} while < {}", startPos, stopPosition);
while (dataReader.getPositionInBits() < stopPosition) {
numberOfElements++;
- T element = dataReader.read(options);
+ T element = dataReader.read(listElementOptions(options));
LOGGER.debug("Read element[{}] {}", numberOfElements, element);
result.add(element);
}
@@ -99,11 +99,21 @@ public class FieldReaderArray<T> implements FieldCommons {
dataReader.pushContext(options);
List<T> result = new ArrayList<>();
while (!termination.get()) {
- result.add(dataReader.read(options));
+ result.add(dataReader.read(listElementOptions(options)));
}
dataReader.popContext(options);
LOGGER.debug("done reading field {}", getName(options));
return result;
}
+ /**
+ * Marks an element read as part of a list: complex element types are serialized without a
+ * logical-name wrapper inside their list context, so the complex data reader skips the
+ * wrapper for reads carrying this marker (simple data readers ignore it).
+ */
+ private WithOption[] listElementOptions(WithOption[] options) {
+ // AddOptions prepends the new options, so the marker takes precedence.
+ return WithOption.AddOptions(options, WithOption.WithRenderAsList(true));
+ }
+
}
diff --git a/plc4j/spi/fields/src/main/java/org/apache/plc4x/java/spi/fields/fields/reader/FieldReaderEnum.java b/plc4j/spi/fields/src/main/java/org/apache/plc4x/java/spi/fields/fields/reader/FieldReaderEnum.java
index eb4f536028..f4a6617cdc 100644
--- a/plc4j/spi/fields/src/main/java/org/apache/plc4x/java/spi/fields/fields/reader/FieldReaderEnum.java
+++ b/plc4j/spi/fields/src/main/java/org/apache/plc4x/java/spi/fields/fields/reader/FieldReaderEnum.java
@@ -33,7 +33,10 @@ public class FieldReaderEnum<T> implements FieldCommons {
public T readEnumField(DataReader<T> dataReader, WithOption... options) throws BufferException {
LOGGER.debug("reading field {}", getName(options));
dataReader.pushContext(WithOption.AddOptions(options, WithOption.WithRenderAsList(true)));
- T result = dataReader.read(options);
+ // The serializer names the inner element after the enum type (see FieldWriterSimpleEnum),
+ // which isn't statically known here - read with the wildcard name so name-validating
+ // buffers (XML) accept it.
+ T result = dataReader.read(WithOption.AddOptions(options, WithOption.WithName("*")));
dataReader.popContext(WithOption.AddOptions(options, WithOption.WithRenderAsList(true)));
return result;
}
diff --git a/plc4j/spi/fields/src/main/java/org/apache/plc4x/java/spi/fields/fields/reader/FieldReaderPadding.java b/plc4j/spi/fields/src/main/java/org/apache/plc4x/java/spi/fields/fields/reader/FieldReaderPadding.java
index b77ebe93ba..0338a506b3 100644
--- a/plc4j/spi/fields/src/main/java/org/apache/plc4x/java/spi/fields/fields/reader/FieldReaderPadding.java
+++ b/plc4j/spi/fields/src/main/java/org/apache/plc4x/java/spi/fields/fields/reader/FieldReaderPadding.java
@@ -32,7 +32,8 @@ public class FieldReaderPadding<T> implements FieldCommons {
public void readPaddingField(DataReader<T> dataReader, int timesPadding, WithOption... options) throws BufferException {
LOGGER.debug("reading field padding");
- dataReader.pushContext(WithOption.AddOptions(options, WithOption.WithRenderAsList(true)));
+ // Mirror FieldWriterPadding, which names the context "padding" explicitly.
+ dataReader.pushContext(WithOption.AddOptions(options, WithOption.WithRenderAsList(true), WithOption.WithName("padding")));
while (timesPadding-- > 0) {
// Just read the padding data and ignore it
try {
@@ -44,7 +45,7 @@ public class FieldReaderPadding<T> implements FieldCommons {
// last field item, then the packet might end here.
}
}
- dataReader.popContext(WithOption.AddOptions(options, WithOption.WithRenderAsList(true)));
+ dataReader.popContext(WithOption.AddOptions(options, WithOption.WithRenderAsList(true), WithOption.WithName("padding")));
}
}
diff --git a/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/java/utils/testutils/driver/internal/handlers/IncomingPlcMessageHandler.java b/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/java/utils/testutils/driver/internal/handlers/IncomingPlcMessageHandler.java
index b90f9826f6..099c62e08a 100644
--- a/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/java/utils/testutils/driver/internal/handlers/IncomingPlcMessageHandler.java
+++ b/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/java/utils/testutils/driver/internal/handlers/IncomingPlcMessageHandler.java
@@ -20,6 +20,7 @@ package org.apache.plc4x.java.utils.testutils.driver.internal.handlers;
import org.apache.plc4x.java.spi.buffers.api.Message;
import org.apache.plc4x.java.spi.buffers.api.ReadBuffer;
+import org.apache.plc4x.java.spi.buffers.api.WithOption;
import org.apache.plc4x.java.spi.buffers.api.exceptions.BufferException;
import org.apache.plc4x.java.spi.buffers.bytebased.WriteBufferByteBased;
import org.apache.plc4x.java.spi.buffers.xmlbased.ReadBufferXmlBased;
@@ -129,7 +130,13 @@ public class IncomingPlcMessageHandler {
}
try {
- WriteBufferByteBased writeBuffer = new WriteBufferByteBased(new byte[message.getLengthInBytes()]);
+ // The default integer/float encodings match what every driver codec configures;
+ // generated serializers (e.g. S7) fail without them on fields that don't pass
+ // explicit per-field options.
+ WriteBufferByteBased writeBuffer = new WriteBufferByteBased(new byte[message.getLengthInBytes()],
+ WithOption.WithUnsignedIntegerEncoding("unsigned-binary"),
+ WithOption.WithSignedIntegerEncoding("twos-complement"),
+ WithOption.WithFloatEncoding("IEEE754"));
writeBuffer.writeMessage(message);
byte[] bytes = writeBuffer.getBytes();
LOGGER.info("Sending serialized message: {}", StaticHelper.ENCODE_HEX(bytes));
diff --git a/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/java/utils/testutils/driver/internal/handlers/OutgoingPlcMessageHandler.java b/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/java/utils/testutils/driver/internal/handlers/OutgoingPlcMessageHandler.java
index bab2f21fea..c54f7b50e8 100644
--- a/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/java/utils/testutils/driver/internal/handlers/OutgoingPlcMessageHandler.java
+++ b/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/java/utils/testutils/driver/internal/handlers/OutgoingPlcMessageHandler.java
@@ -20,6 +20,7 @@ package org.apache.plc4x.java.utils.testutils.driver.internal.handlers;
import org.apache.plc4x.java.spi.buffers.api.Message;
import org.apache.plc4x.java.spi.buffers.api.ReadBuffer;
+import org.apache.plc4x.java.spi.buffers.api.WithOption;
import org.apache.plc4x.java.spi.buffers.api.exceptions.BufferException;
import org.apache.plc4x.java.spi.buffers.bytebased.ReadBufferByteBased;
import org.apache.plc4x.java.spi.buffers.bytebased.WithByteBasedOption;
@@ -115,7 +116,14 @@ public class OutgoingPlcMessageHandler {
.findFirst()
.orElseThrow(() -> new RuntimeException("No staticParse method found in class " + className));
- ReadBufferByteBased readBuffer = new ReadBufferByteBased(actualBytes, WithByteBasedOption.WithByteOrder(byteOrder));
+ // The default integer/float encodings match what every driver codec configures;
+ // generated parsers (e.g. S7) fail without them on fields that don't pass
+ // explicit per-field options.
+ ReadBufferByteBased readBuffer = new ReadBufferByteBased(actualBytes,
+ WithOption.WithUnsignedIntegerEncoding("unsigned-binary"),
+ WithOption.WithSignedIntegerEncoding("twos-complement"),
+ WithOption.WithFloatEncoding("IEEE754"),
+ WithByteBasedOption.WithByteOrder(byteOrder));
Element parserArgumentsElement = referenceXml.element(new QName("parser-arguments"));