This is an automated email from the ASF dual-hosted git repository.
cdutz 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 bb9c49c64a feat: Added an initial version of an improved UMAS mspec.
bb9c49c64a is described below
commit bb9c49c64a8d19bc9c491d478c683fee90bb2d63
Author: Christofer Dutz <[email protected]>
AuthorDate: Wed Apr 1 11:00:55 2026 +0200
feat: Added an initial version of an improved UMAS mspec.
chore: Fixed some rat-plugin related warnings.
---
plc4c/pom.xml | 8 +-
plc4py/pom.xml | 1 +
pom.xml | 124 ++--
.../plc4x/protocol/umas/{ => v0}/UmasProtocol.java | 9 +-
.../plc4x/protocol/umas/{ => v1}/UmasProtocol.java | 9 +-
...e.plc4x.plugins.codegenerator.protocol.Protocol | 3 +-
.../resources/protocols/umas/{ => v0}/umas.mspec | 0
.../main/resources/protocols/umas/v1/umas.mspec | 647 +++++++++++++++++++++
.../protocol/umas/{ => v0}/UmasProtocolTest.java | 2 +-
.../protocol/umas/{ => v1}/UmasProtocolTest.java | 2 +-
10 files changed, 734 insertions(+), 71 deletions(-)
diff --git a/plc4c/pom.xml b/plc4c/pom.xml
index cd7b43ad4d..b7a976326f 100644
--- a/plc4c/pom.xml
+++ b/plc4c/pom.xml
@@ -273,10 +273,10 @@
<groupId>org.apache.rat</groupId>
<artifactId>apache-rat-plugin</artifactId>
<configuration>
- <excludes combine.children="append">
- <exclude>cmake-*/**</exclude>
- <exclude>.cmake/**</exclude>
- </excludes>
+ <inputExcludes combine.children="append">
+ <inputExclude>cmake-*/**</inputExclude>
+ <inputExclude>.cmake/**</inputExclude>
+ </inputExcludes>
</configuration>
</plugin>
</plugins>
diff --git a/plc4py/pom.xml b/plc4py/pom.xml
index 2acc56fff3..5abe065887 100644
--- a/plc4py/pom.xml
+++ b/plc4py/pom.xml
@@ -286,6 +286,7 @@
</goals>
<configuration>
<protocolName>umas</protocolName>
+ <protocolVersion>0</protocolVersion>
<languageName>python</languageName>
<outputFlavor>read-write</outputFlavor>
<outputDir>${project.basedir}/plc4py/protocols</outputDir>
diff --git a/pom.xml b/pom.xml
index 160c17bb7b..95e52b4581 100644
--- a/pom.xml
+++ b/pom.xml
@@ -665,125 +665,125 @@
</executions>
<configuration>
<useMavenDefaultExcludes>true</useMavenDefaultExcludes>
- <excludes>
+ <inputExcludes>
<!-- Git related files -->
- <exclude>**/.git/**</exclude>
- <exclude>**/.gitignore</exclude>
- <exclude>**/cmake-*/**</exclude>
- <exclude>**/.lock/**</exclude>
+ <inputExclude>**/.git/**</inputExclude>
+ <inputExclude>**/.gitignore</inputExclude>
+ <inputExclude>**/cmake-*/**</inputExclude>
+ <inputExclude>**/.lock/**</inputExclude>
<!-- License Files for other licenses -->
- <exclude>**/UNLICENSE</exclude>
- <exclude>**/CHANGELOG.md</exclude>
- <exclude>**/EPL-2.0</exclude>
+ <inputExclude>**/UNLICENSE</inputExclude>
+ <inputExclude>**/CHANGELOG.md</inputExclude>
+ <inputExclude>**/EPL-2.0</inputExclude>
<!--
Files licensed under other compatible licenses.
-->
- <exclude>**/Expression.g4</exclude>
+ <excinputExcludelude>**/Expression.g4</excinputExcludelude>
<!-- Maven related files -->
- <exclude>**/target/**</exclude>
- <exclude>target/**</exclude>
- <exclude>out/**</exclude>
+ <inputExclude>**/target/**</inputExclude>
+ <inputExclude>target/**</inputExclude>
+ <inputExclude>out/**</inputExclude>
<!-- Python related files -->
- <exclude>**/.pytest_cache/**</exclude>
- <exclude>**/.mypy_cache/**</exclude>
- <exclude>**/Pipfile</exclude>
- <exclude>**/Pipfile.lock</exclude>
+ <inputExclude>**/.pytest_cache/**</inputExclude>
+ <inputExclude>**/.mypy_cache/**</inputExclude>
+ <inputExclude>**/Pipfile</inputExclude>
+ <inputExclude>**/Pipfile.lock</inputExclude>
<!-- Eclipse related files -->
- <exclude>**/.project</exclude>
- <exclude>**/.settings/**</exclude>
- <exclude>**/.classpath</exclude>
- <exclude>**/.factorypath</exclude>
+ <inputExclude>**/.project</inputExclude>
+ <inputExclude>**/.settings/**</inputExclude>
+ <inputExclude>**/.classpath</inputExclude>
+ <inputExclude>**/.factorypath</inputExclude>
<!-- IntelliJ related files -->
- <exclude>**/.idea/**</exclude>
- <exclude>**/*.iml</exclude>
- <exclude>**/.attach_pid*</exclude>
+ <inputExclude>**/.idea/**</inputExclude>
+ <inputExclude>**/*.iml</inputExclude>
+ <inputExclude>**/.attach_pid*</inputExclude>
<!-- Wireshark Captures -->
- <exclude>**/*.pcap</exclude>
- <exclude>**/*.pcapng</exclude>
+ <inputExclude>**/*.pcap</inputExclude>
+ <inputExclude>**/*.pcapng</inputExclude>
<!-- JEVN local version files -->
- <exclude>**/.java-version</exclude>
+ <inputExclude>**/.java-version</inputExclude>
<!-- ASCIIdoctor generated files -->
- <exclude>**/.asciidoctor/**</exclude>
+ <inputExclude>**/.asciidoctor/**</inputExclude>
<!-- Jenkins build and docker-compose related files -->
- <exclude>.repository/**</exclude>
- <exclude>local-snapshots-dir/**</exclude>
- <exclude>.local-snapshots-dir/**</exclude>
+ <inputExclude>.repository/**</inputExclude>
+ <inputExclude>local-snapshots-dir/**</inputExclude>
+ <inputExclude>.local-snapshots-dir/**</inputExclude>
<!-- Data files created by examples running an embedded
elasticsearch -->
- <exclude>elasticsearch-data/**</exclude>
+ <inputExclude>elasticsearch-data/**</inputExclude>
<!-- JSON doesn't like comments -->
- <exclude>**/*.json</exclude>
- <exclude>**/*.avsc</exclude>
+ <inputExclude>**/*.json</inputExclude>
+ <inputExclude>**/*.avsc</inputExclude>
<!-- comments are trouble some for CSVs which are static -->
- <exclude>**/*.csv</exclude>
+ <inputExclude>**/*.csv</inputExclude>
<!-- Output of the profiler maven extension -->
- <exclude>**/.profiler/**</exclude>
+ <inputExclude>**/.profiler/**</inputExclude>
<!-- CLion stuff (C++ IDE) -->
- <exclude>**/cmake-build-debug/**</exclude>
- <exclude>**/*.make</exclude>
- <exclude>**/*.cmake</exclude>
- <exclude>**/*.internal</exclude>
- <exclude>**/link.txt</exclude>
+ <inputExclude>**/cmake-build-debug/**</inputExclude>
+ <inputExclude>**/*.make</inputExclude>
+ <inputExclude>**/*.cmake</inputExclude>
+ <inputExclude>**/*.internal</inputExclude>
+ <inputExclude>**/link.txt</inputExclude>
<!-- Not sure why I can't exclude all Makefile's from just the
plc4c directory -->
<!-- exclude>**/Makefile</exclude -->
- <exclude>**/Makefile</exclude>
- <exclude>**/*.includecache</exclude>
- <exclude>**/CMakeFiles/**</exclude>
- <exclude>**/CMakeCache.txt</exclude>
- <exclude>**/DartConfiguration.tcl</exclude>
+ <inputExclude>**/Makefile</inputExclude>
+ <inputExclude>**/*.includecache</inputExclude>
+ <inputExclude>**/CMakeFiles/**</inputExclude>
+ <inputExclude>**/CMakeCache.txt</inputExclude>
+ <inputExclude>**/DartConfiguration.tcl</inputExclude>
<!-- .Net stuff -->
- <exclude>plc4net/plc4net.sln.DotSettings.user</exclude>
+ <inputExclude>plc4net/plc4net.sln.DotSettings.user</inputExclude>
<!-- Stuff created during a plc4net build -->
- <exclude>**/obj/**</exclude>
+ <inputExclude>**/obj/**</inputExclude>
<!-- Temporarily exclude generated code from thrift in python
modules -->
<!--exclude>generated/**</exclude-->
<!-- Temporary Python files -->
- <exclude>**/.eggs/**</exclude>
- <exclude>**/plc4py.egg-info/**</exclude>
+ <inputExclude>**/.eggs/**</inputExclude>
+ <inputExclude>**/plc4py.egg-info/**</inputExclude>
<!-- Temporary Python virtualenv files-->
- <exclude>**/venv/**</exclude>
+ <inputExclude>**/venv/**</inputExclude>
<!-- Temporary pytest and mypy folders -->
- <exclude>**/.mypy_cache/**</exclude>
- <exclude>**/.pytest_cache/**</exclude>
+ <inputExclude>**/.mypy_cache/**</inputExclude>
+ <inputExclude>**/.pytest_cache/**</inputExclude>
<!-- Exclude a temp file needed by Docker -->
- <exclude>project_version</exclude>
+ <inputExclude>project_version</inputExclude>
<!-- Exclude files generated by the go build system -->
- <exclude>**/go.sum</exclude>
+ <inputExclude>**/go.sum</inputExclude>
<!-- Exclude Node related files -->
- <exclude>ui/frontend/project/dist/**</exclude>
- <exclude>ui/frontend/project/node/**</exclude>
- <exclude>ui/frontend/project/node_modules/**</exclude>
+ <inputExclude>ui/frontend/project/dist/**</inputExclude>
+ <inputExclude>ui/frontend/project/node/**</inputExclude>
+ <inputExclude>ui/frontend/project/node_modules/**</inputExclude>
- <exclude>website/node_modules/**</exclude>
+ <inputExclude>website/node_modules/**</inputExclude>
- <exclude>.mvn/**</exclude>
+ <inputExclude>.mvn/**</inputExclude>
<!-- Exclude core dumps-->
- <exclude>**/hs_err_pid*</exclude>
- <exclude>**/replay_pid*</exclude>
- </excludes>
+ <inputExclude>**/hs_err_pid*</inputExclude>
+ <inputExclude>**/replay_pid*</inputExclude>
+ </inputExcludes>
</configuration>
</plugin>
diff --git
a/protocols/umas/src/main/java/org/apache/plc4x/protocol/umas/UmasProtocol.java
b/protocols/umas/src/main/java/org/apache/plc4x/protocol/umas/v0/UmasProtocol.java
similarity index 90%
copy from
protocols/umas/src/main/java/org/apache/plc4x/protocol/umas/UmasProtocol.java
copy to
protocols/umas/src/main/java/org/apache/plc4x/protocol/umas/v0/UmasProtocol.java
index fc15994f3a..474938db2a 100644
---
a/protocols/umas/src/main/java/org/apache/plc4x/protocol/umas/UmasProtocol.java
+++
b/protocols/umas/src/main/java/org/apache/plc4x/protocol/umas/v0/UmasProtocol.java
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.plc4x.protocol.umas;
+package org.apache.plc4x.protocol.umas.v0;
import
org.apache.plc4x.plugins.codegenerator.language.mspec.parser.MessageFormatParser;
import
org.apache.plc4x.plugins.codegenerator.language.mspec.protocol.ProtocolHelpers;
@@ -25,6 +25,8 @@ import
org.apache.plc4x.plugins.codegenerator.protocol.Protocol;
import org.apache.plc4x.plugins.codegenerator.protocol.TypeContext;
import
org.apache.plc4x.plugins.codegenerator.types.exceptions.GenerationException;
+import java.util.Optional;
+
public class UmasProtocol implements Protocol, ProtocolHelpers {
@Override
@@ -32,6 +34,11 @@ public class UmasProtocol implements Protocol,
ProtocolHelpers {
return "umas";
}
+ @Override
+ public Optional<String> getVersion() {
+ return Optional.of("0");
+ }
+
@Override
public TypeContext getTypeContext() throws GenerationException {
ValidatableTypeContext typeContext = new
MessageFormatParser().parse(getMspecStream());
diff --git
a/protocols/umas/src/main/java/org/apache/plc4x/protocol/umas/UmasProtocol.java
b/protocols/umas/src/main/java/org/apache/plc4x/protocol/umas/v1/UmasProtocol.java
similarity index 90%
rename from
protocols/umas/src/main/java/org/apache/plc4x/protocol/umas/UmasProtocol.java
rename to
protocols/umas/src/main/java/org/apache/plc4x/protocol/umas/v1/UmasProtocol.java
index fc15994f3a..888f7896ba 100644
---
a/protocols/umas/src/main/java/org/apache/plc4x/protocol/umas/UmasProtocol.java
+++
b/protocols/umas/src/main/java/org/apache/plc4x/protocol/umas/v1/UmasProtocol.java
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.plc4x.protocol.umas;
+package org.apache.plc4x.protocol.umas.v1;
import
org.apache.plc4x.plugins.codegenerator.language.mspec.parser.MessageFormatParser;
import
org.apache.plc4x.plugins.codegenerator.language.mspec.protocol.ProtocolHelpers;
@@ -25,6 +25,8 @@ import
org.apache.plc4x.plugins.codegenerator.protocol.Protocol;
import org.apache.plc4x.plugins.codegenerator.protocol.TypeContext;
import
org.apache.plc4x.plugins.codegenerator.types.exceptions.GenerationException;
+import java.util.Optional;
+
public class UmasProtocol implements Protocol, ProtocolHelpers {
@Override
@@ -32,6 +34,11 @@ public class UmasProtocol implements Protocol,
ProtocolHelpers {
return "umas";
}
+ @Override
+ public Optional<String> getVersion() {
+ return Optional.of("1");
+ }
+
@Override
public TypeContext getTypeContext() throws GenerationException {
ValidatableTypeContext typeContext = new
MessageFormatParser().parse(getMspecStream());
diff --git
a/protocols/umas/src/main/resources/META-INF/services/org.apache.plc4x.plugins.codegenerator.protocol.Protocol
b/protocols/umas/src/main/resources/META-INF/services/org.apache.plc4x.plugins.codegenerator.protocol.Protocol
index 2bbb535e12..2526b3c466 100644
---
a/protocols/umas/src/main/resources/META-INF/services/org.apache.plc4x.plugins.codegenerator.protocol.Protocol
+++
b/protocols/umas/src/main/resources/META-INF/services/org.apache.plc4x.plugins.codegenerator.protocol.Protocol
@@ -16,4 +16,5 @@
# specific language governing permissions and limitations
# under the License.
#
-org.apache.plc4x.protocol.umas.UmasProtocol
+org.apache.plc4x.protocol.umas.v0.UmasProtocol
+org.apache.plc4x.protocol.umas.v1.UmasProtocol
diff --git a/protocols/umas/src/main/resources/protocols/umas/umas.mspec
b/protocols/umas/src/main/resources/protocols/umas/v0/umas.mspec
similarity index 100%
rename from protocols/umas/src/main/resources/protocols/umas/umas.mspec
rename to protocols/umas/src/main/resources/protocols/umas/v0/umas.mspec
diff --git a/protocols/umas/src/main/resources/protocols/umas/v1/umas.mspec
b/protocols/umas/src/main/resources/protocols/umas/v1/umas.mspec
new file mode 100644
index 0000000000..cf076f030f
--- /dev/null
+++ b/protocols/umas/src/main/resources/protocols/umas/v1/umas.mspec
@@ -0,0 +1,647 @@
+/*
+ * 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.
+ */
+
+////////////////////////////////////////////////////////////////
+// UMAS Protocol (Unified Messaging Application Services)
+//
+// Schneider Electric's proprietary protocol for Modicon PLCs (M340, M580,
+// Quantum, Premium). Tunneled inside Modbus/TCP using Function Code 90 (0x5A).
+//
+// Key detail: Modbus/TCP MBAP header is big-endian, UMAS payload is
little-endian.
+//
+// References:
+// - Apache PLC4X umas.mspec
+// - Kaspersky ICS CERT: "The secrets of Schneider Electric's UMAS protocol"
+// - Wireshark Lua dissectors (zaltzman, yanissec, biero-el-corridor)
+//
+// Procedure of reverse Engineering:
+// - Setup a Windows VM and installed Control Expert Classic
+// - Configured an example program with all data-structures an default values
+// - Setup a virtual PLC and installed the example program on that
+// - Setup OPC UA Server Expert and connected it to the virtual PLC
+// - Used WireShark to observe the traffic between the OPC UA Server and the
PLC
+// - Used Claude Code and existing public resources to make sense out of the
+// observed traffic
+//
+////////////////////////////////////////////////////////////////
+
+[constants
+ [const uint 16 UmasTcpDefaultPort 502]
+]
+
+////////////////////////////////////////////////////////////////
+// Modbus/TCP Transport Framing (BIG-ENDIAN)
+////////////////////////////////////////////////////////////////
+
+// Modbus/TCP Application Data Unit — wraps the UMAS payload.
+// The MBAP header uses big-endian byte ordering per the Modbus specification.
+[type ModbusTcpADU(uint 8 umasRequestFunctionKey) byteOrder='"BIG_ENDIAN"'
unsignedIntegerEncoding='"unsigned-binary"'
signedIntegerEncoding='"twos-complement"' floatEncoding='"IEEE754"'
stringEncoding='"UTF8"'
+ // Transaction identifier for request/response correlation
+ [simple uint 16 transactionIdentifier]
+
+ // Protocol identifier — always 0x0000 for Modbus
+ [const uint 16 protocolIdentifier 0x0000]
+
+ // Length of remaining bytes (unitIdentifier + pdu)
+ [implicit uint 16 length 'pdu.lengthInBytes + 1']
+
+ // Unit/slave address
+ [simple uint 8 unitIdentifier]
+
+ // UMAS payload wrapped in a Modbus PDU (FC 0x5A).
+ // umasRequestFunctionKey is forwarded for response discrimination — pass 0
+ // when the original request FC is unknown (the catch-all handles it).
+ [simple ModbusPDU('umasRequestFunctionKey', 'length - 1') pdu]
+]
+
+////////////////////////////////////////////////////////////////
+// Modbus PDU with UMAS discrimination
+////////////////////////////////////////////////////////////////
+
+// The Modbus PDU discriminates on error flag and function code.
+// FC 0x5A (90) identifies a UMAS payload.
+[discriminatedType ModbusPDU(uint 8 umasRequestFunctionKey, uint 16 byteLength)
+ [discriminator bit errorFlag]
+ [discriminator uint 7 functionFlag]
+ [typeSwitch errorFlag,functionFlag
+ ['true' ModbusPDUError
+ [simple ModbusErrorCode exceptionCode]
+ ]
+ ['false','0x5a' UmasPDU
+ [simple UmasPDUItem('umasRequestFunctionKey','byteLength - 1')
item]
+ ]
+ ]
+]
+
+////////////////////////////////////////////////////////////////
+// UMAS PDU Item (LITTLE-ENDIAN)
+//
+// The core UMAS protocol data unit. Byte order switches to little-endian
+// here — all UMAS payload fields are little-endian.
+//
+// Requests are discriminated by umasFunctionKey alone.
+// Responses use 0xFE (success) or 0xFD (error) as umasFunctionKey;
+// the second discriminator umasRequestFunctionKey (the original request FC)
+// selects the specific response type. A catch-all ['0xFE'] at the end
+// handles responses when the original request FC is unknown (pass 0).
+////////////////////////////////////////////////////////////////
+
+[type UmasPDUItem(uint 8 umasRequestFunctionKey, uint 16 byteLength)
byteOrder='"LITTLE_ENDIAN"'
+ [simple uint 8 pairingKey]
+ [discriminator uint 8 umasFunctionKey]
+ [typeSwitch umasFunctionKey, umasRequestFunctionKey
+
+ // ---- FC 0x01: Init Communication ----
+ ['0x01' UmasInitCommsRequest
+ [simple uint 8 subCode]
+ ]
+ ['0xFE', '0x01' UmasInitCommsResponse
+ [simple uint 16 maxFrameSize]
+ [simple uint 16 firmwareVersion]
+ [simple uint 32 notSure]
+ [simple uint 32 internalCode]
+ [simple uint 8 hostnameLength]
+ [simple vstring 'hostnameLength*8' hostname]
+ ]
+
+ // ---- FC 0x02: PLC Identification ----
+ ['0x02' UmasPDUPlcIdentRequest
+ ]
+ ['0xFE', '0x02' UmasPDUPlcIdentResponse
+ [simple uint 16 range]
+ [simple uint 32 ident]
+ [simple uint 16 model]
+ [simple uint 16 comVersion]
+ [simple uint 16 comPatch]
+ [simple uint 16 intVersion]
+ [simple uint 16 hardwareVersion]
+ [simple uint 32 crashCode]
+ [simple uint 16 unknown1]
+ [simple uint 8 hostnameLength]
+ [simple vstring 'hostnameLength*8' hostname]
+ [simple uint 8 numberOfMemoryBanks]
+ [array PlcMemoryBlockIdent memoryIdents count
'numberOfMemoryBanks']
+ ]
+
+ // ---- FC 0x03: Project Info ----
+ ['0x03' UmasPDUProjectInfoRequest
+ [simple uint 8 subcode]
+ ]
+ ['0xFE', '0x03' UmasPDUProjectInfoResponse
+ [array byte block count 'byteLength - 2']
+ ]
+
+ // ---- FC 0x04: PLC Status ----
+ ['0x04' UmasPDUPlcStatusRequest
+ ]
+ ['0xFE', '0x04' UmasPDUPlcStatusResponse
+ [simple uint 8 notUsed1]
+ [simple uint 16 notUsed2]
+ [simple uint 8 numberOfBlocks]
+ [array uint 32 blocks count 'numberOfBlocks']
+ // Additional status data present on M580 PLCs (absent on M340)
+ [array byte additionalData count 'byteLength - 2 -
4 - (numberOfBlocks * 4)']
+ ]
+
+ // ---- FC 0x06: Read Card Info ----
+ ['0x06' UmasPDUReadCardInfoRequest
+ [array byte data count 'byteLength - 2']
+ ]
+ ['0xFE', '0x06' UmasPDUReadCardInfoResponse
+ [array byte block count 'byteLength - 2']
+ ]
+
+ // ---- FC 0x0A: Repeat / Echo ----
+ ['0x0A' UmasPDURepeatRequest
+ [array byte data count 'byteLength - 2']
+ ]
+ ['0xFE', '0x0A' UmasPDURepeatResponse
+ [array byte block count 'byteLength - 2']
+ ]
+
+ // ---- FC 0x10: Take PLC Reservation ----
+ ['0x10' UmasPDUTakePlcReservationRequest
+ [array byte data count 'byteLength - 2']
+ ]
+ ['0xFE', '0x10' UmasPDUTakePlcReservationResponse
+ [array byte block count 'byteLength - 2']
+ ]
+
+ // ---- FC 0x11: Release PLC Reservation ----
+ ['0x11' UmasPDUReleasePlcReservationRequest
+ [array byte data count 'byteLength - 2']
+ ]
+ ['0xFE', '0x11' UmasPDUReleasePlcReservationResponse
+ [array byte block count 'byteLength - 2']
+ ]
+
+ // ---- FC 0x12: Keep Alive ----
+ ['0x12' UmasPDUKeepAliveRequest
+ [array byte data count 'byteLength - 2']
+ ]
+ ['0xFE', '0x12' UmasPDUKeepAliveResponse
+ [array byte block count 'byteLength - 2']
+ ]
+
+ // ---- FC 0x20: Read Memory Block ----
+ ['0x20' UmasPDUReadMemoryBlockRequest
+ [simple uint 8 range]
+ [simple uint 16 blockNumber]
+ [simple uint 16 offset]
+ [simple uint 16 unknownObject1]
+ [simple uint 16 numberOfBytes]
+ ]
+ ['0xFE', '0x20' UmasPDUReadMemoryBlockResponse
+ [simple uint 8 range]
+ [simple uint 16 numberOfBytes]
+ [array byte block count 'numberOfBytes']
+ ]
+
+ // ---- FC 0x22: Read Variables ----
+ ['0x22' UmasPDUReadVariableRequest
+ [simple uint 32 crc]
+ [simple uint 8 variableCount]
+ [array VariableReadRequestReference variables count
'variableCount']
+ ]
+ ['0xFE', '0x22' UmasPDUReadVariableResponse
+ [array byte block count 'byteLength - 2']
+ ]
+
+ // ---- FC 0x23: Write Variables ----
+ ['0x23' UmasPDUWriteVariableRequest
+ [simple uint 32 crc]
+ [simple uint 8 variableCount]
+ [array VariableWriteRequestReference variables count
'variableCount']
+ ]
+ ['0xFE', '0x23' UmasPDUWriteVariableResponse
+ [array byte block count 'byteLength - 2']
+ ]
+
+ // ---- FC 0x24: Read Coils/Registers ----
+ ['0x24' UmasPDUReadCoilsRegistersRequest
+ [array byte data count 'byteLength - 2']
+ ]
+ ['0xFE', '0x24' UmasPDUReadCoilsRegistersResponse
+ [array byte block count 'byteLength - 2']
+ ]
+
+ // ---- FC 0x25: Write Coils/Registers ----
+ ['0x25' UmasPDUWriteCoilsRegistersRequest
+ [array byte data count 'byteLength - 2']
+ ]
+ ['0xFE', '0x25' UmasPDUWriteCoilsRegistersResponse
+ [array byte block count 'byteLength - 2']
+ ]
+
+ // ---- FC 0x26: Data Dictionary ----
+ ['0x26' UmasPDUReadUnlocatedVariableNamesRequest
+ [simple uint 16 recordType]
+ [simple uint 8 index]
+ [simple uint 32 hardwareId]
+ [simple uint 16 blockNo]
+ [simple uint 16 offset]
+ // Trailing padding present in DD02 requests but absent in DD03
+ [optional uint 16 blank 'recordType == 0xDD02']
+ ]
+ ['0xFE', '0x26' UmasPDUReadUnlocatedVariableResponse
+ [array byte block count 'byteLength - 2']
+ ]
+
+ // ---- FC 0x30-0x35: Strategy Transfer ----
+ ['0x30' UmasPDUInitializeUploadRequest
+ [array byte data count 'byteLength - 2']
+ ]
+ ['0x31' UmasPDUUploadBlockRequest
+ [array byte data count 'byteLength - 2']
+ ]
+ ['0x32' UmasPDUEndStrategyUploadRequest
+ [array byte data count 'byteLength - 2']
+ ]
+ ['0x33' UmasPDUInitializeDownloadRequest
+ [array byte data count 'byteLength - 2']
+ ]
+ ['0x34' UmasPDUDownloadBlockRequest
+ [array byte data count 'byteLength - 2']
+ ]
+ ['0x35' UmasPDUEndStrategyDownloadRequest
+ [array byte data count 'byteLength - 2']
+ ]
+
+ // ---- FC 0x39: Read Ethernet Master Data ----
+ ['0x39' UmasPDUReadEthMasterDataRequest
+ [array byte data count 'byteLength - 2']
+ ]
+
+ // ---- FC 0x40: Start PLC ----
+ ['0x40' UmasPDUStartPlcRequest
+ [array byte data count 'byteLength - 2']
+ ]
+
+ // ---- FC 0x41: Stop PLC ----
+ ['0x41' UmasPDUStopPlcRequest
+ [array byte data count 'byteLength - 2']
+ ]
+
+ // ---- FC 0x50: Monitor PLC ----
+ ['0x50' UmasPDUMonitorPlcRequest
+ [array byte data count 'byteLength - 2']
+ ]
+
+ // ---- FC 0x58: Check PLC ----
+ ['0x58' UmasPDUCheckPlcRequest
+ [array byte data count 'byteLength - 2']
+ ]
+
+ // ---- FC 0x70: Read IO Object ----
+ ['0x70' UmasPDUReadIoObjectRequest
+ [array byte data count 'byteLength - 2']
+ ]
+
+ // ---- FC 0x71: Write IO Object ----
+ ['0x71' UmasPDUWriteIoObjectRequest
+ [array byte data count 'byteLength - 2']
+ ]
+
+ // ---- FC 0x73: Get Status Module ----
+ ['0x73' UmasPDUGetStatusModuleRequest
+ [array byte data count 'byteLength - 2']
+ ]
+
+ // ---- Error Response (any FC) ----
+ ['0xFD' UmasPDUErrorResponse
+ [array byte block count 'byteLength - 2']
+ ]
+
+ // ---- Generic Success Response ----
+ // Catch-all for responses when umasRequestFunctionKey is unknown
(pass 0).
+ // Specific response types above take priority when the correct FC is
supplied.
+ ['0xFE' UmasPDUSuccessResponse
+ [array byte block count 'byteLength - 2']
+ ]
+ ]
+]
+
+////////////////////////////////////////////////////////////////
+// Helper Types
+////////////////////////////////////////////////////////////////
+
+// Memory block structure for specific block/offset combinations
+[type UmasMemoryBlock(uint 16 blockNumber, uint 16 offset)
+ [typeSwitch blockNumber, offset
+ ['0x30', '0x00' UmasMemoryBlockBasicInfo
+ [simple uint 16 range]
+ [simple uint 16 notSure]
+ [simple uint 8 index]
+ [simple uint 32 hardwareId]
+ ]
+ ]
+]
+
+// Parsed response for unlocated variable names (used by driver layer)
+[type UmasPDUReadUnlocatedVariableNamesResponse
+ [simple uint 8 range]
+ [simple uint 16 nextAddress]
+ [simple uint 16 unknown1]
+ [simple uint 16 noOfRecords]
+ [array UmasUnlocatedVariableReference records count
'noOfRecords']
+]
+
+// Parsed response for UDT definitions
+[type UmasPDUReadUmasUDTDefinitionResponse
+ [simple uint 8 range]
+ [simple uint 32 unknown1]
+ [simple uint 16 noOfRecords]
+ [array UmasUDTDefinition records count 'noOfRecords']
+]
+
+// Parsed response for datatype names
+[type UmasPDUReadDatatypeNamesResponse
+ [simple uint 8 range]
+ [simple uint 16 nextAddress]
+ [simple uint 8 unknown1]
+ [simple uint 16 noOfRecords]
+ [array UmasDatatypeReference records count 'noOfRecords']
+]
+
+// Variable read request reference — identifies a variable to read
+[type VariableReadRequestReference
+ [simple uint 4 isArray]
+ [simple uint 4 dataSizeIndex]
+ [simple uint 16 block]
+ [const uint 8 unknown1 0x01]
+ [simple uint 16 baseOffset]
+ [simple uint 8 offset]
+ [optional uint 16 arrayLength 'isArray != 0']
+]
+
+// Variable write request reference — identifies a variable to write with data
+[type VariableWriteRequestReference
+ [simple uint 4 isArray]
+ [simple uint 4 dataSizeIndex]
+ [simple uint 16 block]
+ [simple uint 16 baseOffset]
+ [simple uint 16 offset]
+ [optional uint 16 arrayLength 'isArray != 0']
+ [array byte recordData length 'isArray == 1 ?
dataSizeIndex * arrayLength : dataSizeIndex']
+]
+
+// Unlocated variable reference from the data dictionary
+[type UmasUnlocatedVariableReference
+ [simple uint 16 dataType]
+ [simple uint 16 block]
+ [simple uint 32 offset]
+ [simple uint 8 flags]
+ [simple uint 8 unknown4]
+ // Name is null-terminated (no length prefix)
+ [manual vstring value 'STATIC_CALL("parseTerminatedString", readBuffer,
-1)' 'STATIC_CALL("serializeTerminatedString", writeBuffer, value, -1)'
'(STR_LEN(value) + 1) * 8']
+]
+
+// User-Defined Type definition
+[type UmasUDTDefinition
+ [simple uint 16 dataType]
+ [simple uint 16 offset]
+ [simple uint 16 unknown5]
+ [simple uint 16 unknown4]
+ [manual vstring value 'STATIC_CALL("parseTerminatedString", readBuffer,
-1)' 'STATIC_CALL("serializeTerminatedString", writeBuffer, value, -1)'
'(STR_LEN(value) + 1) * 8']
+]
+
+// Datatype reference from the datatype dictionary
+[type UmasDatatypeReference
+ [simple uint 16 dataSize]
+ [simple uint 16 unknown1]
+ [simple uint 8 classIdentifier]
+ [simple uint 8 dataType]
+ // Name is null-terminated (no length prefix) — uses -1 to signal "read
until null"
+ [manual vstring value 'STATIC_CALL("parseTerminatedString", readBuffer,
-1)' 'STATIC_CALL("serializeTerminatedString", writeBuffer, value, -1)'
'(STR_LEN(value) + 1) * 8']
+]
+
+// Memory bank identification in PLC identification responses
+[type PlcMemoryBlockIdent
+ [simple uint 8 blockType]
+ [simple uint 8 folio]
+ [simple uint 16 status]
+ [simple uint 32 memoryLength]
+]
+
+////////////////////////////////////////////////////////////////
+// Data Type Mapping (DataIO)
+////////////////////////////////////////////////////////////////
+
+// Maps UMAS data types to their wire format representations
+[dataIo DataItem(UmasDataType dataType, uint 16 numberOfValues)
+ [typeSwitch dataType,numberOfValues
+ ['BOOL','1' BOOL
+ [reserved uint 7 '0x0000' ]
+ [simple bit value ]
+ ]
+ ['EBOOL','1' BOOL
+ [reserved uint 7 '0x0000' ]
+ [simple bit value ]
+ ]
+ ['BYTE','1' BYTE
+ [simple byte value]
+ ]
+ ['BYTE' List
+ [array byte value count 'numberOfValues' ]
+ ]
+ ['WORD' WORD
+ [simple uint 16 value]
+ ]
+ ['DWORD' DWORD
+ [simple uint 32 value]
+ ]
+ ['INT','1' INT
+ [simple int 16 value]
+ ]
+ ['INT' List
+ [array int 16 value count 'numberOfValues']
+ ]
+ ['DINT','1' DINT
+ [simple int 32 value]
+ ]
+ ['DINT' List
+ [array int 32 value count 'numberOfValues']
+ ]
+ ['UINT','1' UINT
+ [simple uint 16 value]
+ ]
+ ['UINT' List
+ [array uint 16 value count 'numberOfValues']
+ ]
+ ['UDINT','1' UDINT
+ [simple uint 32 value]
+ ]
+ ['UDINT' List
+ [array uint 32 value count 'numberOfValues']
+ ]
+ ['REAL','1' REAL
+ [simple float 32 value]
+ ]
+ ['REAL' List
+ [array float 32 value count 'numberOfValues']
+ ]
+ ['STRING','1' STRING
+ [manual vstring value 'STATIC_CALL("parseTerminatedStringBytes",
readBuffer, numberOfValues)' 'STATIC_CALL("serializeTerminatedString",
writeBuffer, _value, numberOfValues)' '(numberOfValues * 8)']
+ ]
+ ['STRING' List
+ [array float 32 value count 'numberOfValues']
+ ]
+ ['TIME','1' TIME
+ [simple uint 32 value]
+ ]
+ ['TIME' List
+ [array uint 32 value count 'numberOfValues']
+ ]
+ ['DATE','1' DATE
+ [simple uint 8 day encoding='"BCD"']
+ [simple uint 8 month encoding='"BCD"']
+ [simple uint 16 year encoding='"BCD"']
+ ]
+ ['TOD','1' TIME_OF_DAY
+ [simple uint 32 value]
+ ]
+ ['TOD' List
+ [array uint 32 value count 'numberOfValues']
+ ]
+ ['DATE_AND_TIME','1' DATE_AND_TIME
+ [reserved uint 8 '0x00']
+ [simple uint 8 seconds encoding='"BCD"']
+ [simple uint 8 minutes encoding='"BCD"']
+ [simple uint 8 hour encoding='"BCD"']
+ [simple uint 8 day encoding='"BCD"']
+ [simple uint 8 month encoding='"BCD"']
+ [simple uint 16 year encoding='"BCD"']
+ ]
+ ]
+]
+
+////////////////////////////////////////////////////////////////
+// Enumerations
+////////////////////////////////////////////////////////////////
+
+// Schneider PLC data types with size metadata for variable read/write
operations
+[enum uint 8 UmasDataType(uint 8 dataTypeSize, uint 8 requestSize, vstring
data_type_conversion)
+ ['1' BOOL ['1','1','"BOOL"']]
+ ['2' UNKNOWN2 ['1','1','"BOOL"']]
+ ['3' UNKNOWN3 ['1','1','"BOOL"']]
+ ['4' INT ['2', '2','"INT"']]
+ ['5' UINT ['2','2','"UINT"']]
+ ['6' DINT ['4','3','"DINT"']]
+ ['7' UDINT ['4','3','"UDINT"']]
+ ['8' REAL ['4','3','"REAL"']]
+ ['9' STRING ['1','17','"STRING"']]
+ ['10' TIME ['4','3','"TIME"']]
+ ['11' UNKNOWN11 ['1','1','"BYTE"']]
+ ['12' UNKNOWN12 ['1','1','"BYTE"']]
+ ['13' UNKNOWN13 ['1','1','"BYTE"']]
+ ['14' DATE ['4','3','"DATE"']]
+ ['15' TOD ['4','3','"TIME_OF_DAY"']]
+ ['16' DATE_AND_TIME ['8','4','"DATE_AND_TIME"']]
+ ['17' UNKNOWN17 ['1','1','"BYTE"']]
+ ['18' UNKNOWN18 ['1','1','"BYTE"']]
+ ['19' UNKNOWN19 ['1','1','"BYTE"']]
+ ['20' UNKNOWN20 ['1','1','"BYTE"']]
+ ['21' BYTE ['1','1','"BYTE"']]
+ ['22' WORD ['2','2','"WORD"']]
+ ['23' DWORD ['4','3','"DWORD"']]
+ ['24' UNKNOWN24 ['1','1','"BYTE"']]
+ ['25' EBOOL ['1','1','"BOOL"']]
+]
+
+// Standard Modbus error codes used in UMAS error responses
+[enum uint 8 ModbusErrorCode
+ ['1' ILLEGAL_FUNCTION]
+ ['2' ILLEGAL_DATA_ADDRESS]
+ ['3' ILLEGAL_DATA_VALUE]
+ ['4' SLAVE_DEVICE_FAILURE]
+ ['5' ACKNOWLEDGE]
+ ['6' SLAVE_DEVICE_BUSY]
+ ['7' NEGATIVE_ACKNOWLEDGE]
+ ['8' MEMORY_PARITY_ERROR]
+ ['10' GATEWAY_PATH_UNAVAILABLE]
+ ['11' GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND]
+]
+
+[enum uint 8 UmasDeviceInformationLevel
+ ['0x01' BASIC ]
+ ['0x02' REGULAR ]
+ ['0x03' EXTENDED ]
+ ['0x04' INDIVIDUAL]
+]
+
+[enum uint 7 UmasDeviceInformationConformityLevel
+ ['0x01' BASIC_STREAM_ONLY ]
+ ['0x02' REGULAR_STREAM_ONLY ]
+ ['0x03' EXTENDED_STREAM_ONLY]
+]
+
+[enum uint 8 UmasDeviceInformationMoreFollows
+ ['0x00' NO_MORE_OBJECTS_AVAILABLE]
+ ['0xFF' MORE_OBJECTS_AVAILABLE ]
+]
+
+// PLC memory block types reported in PlcMemoryBlockIdent
(UmasPDUPlcIdentResponse)
+[enum uint 8 PlcMemoryBlockType
+ ['0x01' RAM_CPU ]
+ ['0x04' SD_CARD ]
+]
+
+// Data dictionary record types used in FC 0x26 requests (recordType field)
+[enum uint 16 UmasDataDictionaryRecordType
+ ['0xDD02' VARIABLE_AND_UDT_DEFINITIONS ]
+ ['0xDD03' DATATYPE_DEFINITIONS ]
+]
+
+// Type class identifiers returned in datatype table responses (FC 0x26,
recordType 0xDD03)
+[enum uint 8 UmasTypeClass
+ ['0x02' STRUCT ]
+ ['0x04' ARRAY ]
+]
+
+////////////////////////////////////////////////////////////////
+// Data Dictionary Helper Types
+//
+// These types model the internal structure of data dictionary
+// (FC 0x26) responses. They are parsed at the driver layer from
+// the raw byte arrays returned by UmasPDUReadUnlocatedVariableResponse.
+////////////////////////////////////////////////////////////////
+
+// Array type definition — returned inside DD02 responses for array types.
+// The classId distinguishes arrays (0x04) from structs (0x02).
+[type UmasArrayTypeDefinition byteOrder='"LITTLE_ENDIAN"'
+ // Always 0x04 for arrays
+ [simple uint 8 classId]
+
+ // Element data type — primitive UmasDataType ID for simple arrays,
+ // or a custom type table ID (>= 0x1A) for arrays of structs
+ [simple uint 16 elementTypeId]
+
+ // Number of array dimensions (1D, 2D, or 3D observed in captures)
+ [simple uint 8 numberOfDimensions]
+
+ // Bounds for each dimension (inclusive start and upper bound)
+ [array UmasArrayDimension dimensions count 'numberOfDimensions']
+]
+
+// A single array dimension with inclusive start and upper bounds.
+// Element count for this dimension = upperBound - startIndex + 1
+[type UmasArrayDimension byteOrder='"LITTLE_ENDIAN"'
+ [simple uint 32 startIndex]
+ [simple uint 32 upperBound]
+]
diff --git
a/protocols/umas/src/test/java/org/apache/plc4x/protocol/umas/UmasProtocolTest.java
b/protocols/umas/src/test/java/org/apache/plc4x/protocol/umas/v0/UmasProtocolTest.java
similarity index 96%
copy from
protocols/umas/src/test/java/org/apache/plc4x/protocol/umas/UmasProtocolTest.java
copy to
protocols/umas/src/test/java/org/apache/plc4x/protocol/umas/v0/UmasProtocolTest.java
index a6b8117759..2aa36ea220 100644
---
a/protocols/umas/src/test/java/org/apache/plc4x/protocol/umas/UmasProtocolTest.java
+++
b/protocols/umas/src/test/java/org/apache/plc4x/protocol/umas/v0/UmasProtocolTest.java
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.plc4x.protocol.umas;
+package org.apache.plc4x.protocol.umas.v0;
import org.apache.plc4x.plugins.codegenerator.protocol.TypeContext;
import org.junit.jupiter.api.Test;
diff --git
a/protocols/umas/src/test/java/org/apache/plc4x/protocol/umas/UmasProtocolTest.java
b/protocols/umas/src/test/java/org/apache/plc4x/protocol/umas/v1/UmasProtocolTest.java
similarity index 96%
rename from
protocols/umas/src/test/java/org/apache/plc4x/protocol/umas/UmasProtocolTest.java
rename to
protocols/umas/src/test/java/org/apache/plc4x/protocol/umas/v1/UmasProtocolTest.java
index a6b8117759..67e6d286d2 100644
---
a/protocols/umas/src/test/java/org/apache/plc4x/protocol/umas/UmasProtocolTest.java
+++
b/protocols/umas/src/test/java/org/apache/plc4x/protocol/umas/v1/UmasProtocolTest.java
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.plc4x.protocol.umas;
+package org.apache.plc4x.protocol.umas.v1;
import org.apache.plc4x.plugins.codegenerator.protocol.TypeContext;
import org.junit.jupiter.api.Test;