You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by cd...@apache.org on 2024/05/24 11:57:51 UTC

(plc4x) 01/02: fix: Implemented the ByteLengthEstimator for Modbus ASCII and RTU to try parsing the message in order to correctly operate.

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

commit 436d483a7e98f3c39a87772caa79108d54951c00
Author: Christofer Dutz <cd...@apache.org>
AuthorDate: Fri May 24 13:57:27 2024 +0200

    fix: Implemented the ByteLengthEstimator for Modbus ASCII and RTU to try parsing the message in order to correctly operate.
    
    Addresses:
    https://github.com/apache/plc4x/issues/831
    https://github.com/apache/plc4x/issues/1604
---
 .../plc4x/java/modbus/ascii/ModbusAsciiDriver.java | 45 ++++++++++++++++++++--
 .../plc4x/java/modbus/rtu/ModbusRtuDriver.java     | 21 +++++++++-
 2 files changed, 61 insertions(+), 5 deletions(-)

diff --git a/plc4j/drivers/modbus/src/main/java/org/apache/plc4x/java/modbus/ascii/ModbusAsciiDriver.java b/plc4j/drivers/modbus/src/main/java/org/apache/plc4x/java/modbus/ascii/ModbusAsciiDriver.java
index b2c9a2f958..0bf989893c 100644
--- a/plc4j/drivers/modbus/src/main/java/org/apache/plc4x/java/modbus/ascii/ModbusAsciiDriver.java
+++ b/plc4j/drivers/modbus/src/main/java/org/apache/plc4x/java/modbus/ascii/ModbusAsciiDriver.java
@@ -21,6 +21,8 @@ package org.apache.plc4x.java.modbus.ascii;
 import io.netty.buffer.ByteBuf;
 import org.apache.commons.codec.DecoderException;
 import org.apache.commons.codec.binary.Hex;
+import org.apache.plc4x.java.modbus.readwrite.ModbusADU;
+import org.apache.plc4x.java.modbus.readwrite.ModbusRtuADU;
 import org.apache.plc4x.java.spi.configuration.PlcConnectionConfiguration;
 import org.apache.plc4x.java.spi.configuration.PlcTransportConfiguration;
 import org.apache.plc4x.java.modbus.ascii.config.ModbusAsciiConfiguration;
@@ -42,6 +44,7 @@ import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
+import java.util.function.Consumer;
 import java.util.function.ToIntFunction;
 
 public class ModbusAsciiDriver extends GeneratedDriverBase<ModbusAsciiADU> {
@@ -134,6 +137,7 @@ public class ModbusAsciiDriver extends GeneratedDriverBase<ModbusAsciiADU> {
                 new ModbusAsciiInput(), new ModbusAsciiOutput())
             .withProtocol(ModbusAsciiProtocolLogic.class)
             .withPacketSizeEstimator(ModbusAsciiDriver.ByteLengthEstimator.class)
+            .withCorruptPacketRemover(ModbusAsciiDriver.CorruptPackageCleaner.class)
             // Every incoming message is to be treated as a response.
             .withParserArgs(DriverType.MODBUS_ASCII, true)
             .build();
@@ -143,13 +147,44 @@ public class ModbusAsciiDriver extends GeneratedDriverBase<ModbusAsciiADU> {
     public static class ByteLengthEstimator implements ToIntFunction<ByteBuf> {
         @Override
         public int applyAsInt(ByteBuf byteBuf) {
-            if (byteBuf.readableBytes() >= 1) {
-                return byteBuf.readableBytes();
+            // A Modbus ASCII packet has the absolute minimum size of 9 (if it has absolutely no payload)
+            // That's one starting character ":" two chars at the end CR + LF
+            // The message itself is encoded as two ascii digits of a hex-encoded byte and the minimum of
+            // a Modbus ASCII PDU is 3 bytes, which is 6 bytes in hex-encoded ascii string.
+            if (byteBuf.readableBytes() >= 9) {
+                // Fetch what's currently in the buffer
+                byte[] buf = new byte[byteBuf.readableBytes()];
+                byteBuf.getBytes(byteBuf.readerIndex(), buf);
+                ReadBufferByteBased reader = new ReadBufferByteBased(buf);
+
+                // Try to parse the buffer content.
+                try {
+                    ModbusAsciiInput input = new ModbusAsciiInput();
+                    ModbusAsciiADU modbusADU = input.parse(reader, DriverType.MODBUS_ASCII, true);
+
+                    // Make sure we only read one message.
+                    return (modbusADU.getLengthInBytes() * 2) + 3;
+                } catch (ParseException e) {
+                    return -1;
+                }
             }
             return -1;
         }
     }
 
+    /**
+     * Consumes all Bytes until the starting character ":" is found
+     */
+    public static class CorruptPackageCleaner implements Consumer<ByteBuf> {
+        @Override
+        public void accept(ByteBuf byteBuf) {
+            while (byteBuf.getUnsignedByte(0) != 0x3A) {
+                // Just consume the bytes till the next possible start position.
+                byteBuf.readByte();
+            }
+        }
+    }
+
     @Override
     public ModbusTag prepareTag(String tagAddress){
         return ModbusTag.of(tagAddress);
@@ -158,6 +193,10 @@ public class ModbusAsciiDriver extends GeneratedDriverBase<ModbusAsciiADU> {
     public static class ModbusAsciiInput implements MessageInput<ModbusAsciiADU> {
         @Override
         public ModbusAsciiADU parse(ReadBuffer io, Object... args) throws ParseException {
+            // A Modbus ASCII message starts with an ASCII character ":" and is ended by two characters CRLF
+            // (Carriage-Return + Line-Feed)
+            // The actual payload is that each byte of the message is encoded by a string representation of it's
+            // two hex-digits.
             final short startChar = io.readShort(8);
             // Check if the message starts with the ":" char.
             if(startChar != 0x3A) {
@@ -165,7 +204,7 @@ public class ModbusAsciiDriver extends GeneratedDriverBase<ModbusAsciiADU> {
             }
             // Read in all the bytes in the message.
             final ReadBufferByteBased bufferByteBased = (ReadBufferByteBased) io;
-            // Read in all bytes except the last two ones, which contain a line-break and carriage-return.
+            // Read in all bytes except the last two ones, which contain a carriage-return and line-break.
             final byte[] bytes = bufferByteBased.getBytes(bufferByteBased.getPos(), bufferByteBased.getTotalBytes() - 2);
             // Convert the bytes into a string (Which is the hex-encoded message)
             final String inputString = new String(bytes, StandardCharsets.UTF_8);
diff --git a/plc4j/drivers/modbus/src/main/java/org/apache/plc4x/java/modbus/rtu/ModbusRtuDriver.java b/plc4j/drivers/modbus/src/main/java/org/apache/plc4x/java/modbus/rtu/ModbusRtuDriver.java
index 635eaf157f..a1b73c42a5 100644
--- a/plc4j/drivers/modbus/src/main/java/org/apache/plc4x/java/modbus/rtu/ModbusRtuDriver.java
+++ b/plc4j/drivers/modbus/src/main/java/org/apache/plc4x/java/modbus/rtu/ModbusRtuDriver.java
@@ -19,6 +19,7 @@
 package org.apache.plc4x.java.modbus.rtu;
 
 import io.netty.buffer.ByteBuf;
+import org.apache.plc4x.java.modbus.readwrite.ModbusADU;
 import org.apache.plc4x.java.spi.configuration.PlcConnectionConfiguration;
 import org.apache.plc4x.java.spi.configuration.PlcTransportConfiguration;
 import org.apache.plc4x.java.modbus.base.tag.ModbusTag;
@@ -31,6 +32,8 @@ import org.apache.plc4x.java.modbus.tcp.config.ModbusTcpTransportConfiguration;
 import org.apache.plc4x.java.spi.connection.GeneratedDriverBase;
 import org.apache.plc4x.java.spi.connection.ProtocolStackConfigurer;
 import org.apache.plc4x.java.spi.connection.SingleProtocolStackConfigurer;
+import org.apache.plc4x.java.spi.generation.ParseException;
+import org.apache.plc4x.java.spi.generation.ReadBufferByteBased;
 import org.apache.plc4x.java.spi.optimizer.BaseOptimizer;
 import org.apache.plc4x.java.spi.optimizer.SingleTagOptimizer;
 import org.apache.plc4x.java.spi.values.PlcValueHandler;
@@ -139,8 +142,22 @@ public class ModbusRtuDriver extends GeneratedDriverBase<ModbusRtuADU> {
     public static class ByteLengthEstimator implements ToIntFunction<ByteBuf> {
         @Override
         public int applyAsInt(ByteBuf byteBuf) {
-            if (byteBuf.readableBytes() >= 1) {
-                return byteBuf.readableBytes();
+            // A Modbus RTU packet has the absolute minimum size of 4 (if it has absolutely no payload)
+            if (byteBuf.readableBytes() >= 4) {
+                // Fetch what's currently in the buffer
+                byte[] buf = new byte[byteBuf.readableBytes()];
+                byteBuf.getBytes(byteBuf.readerIndex(), buf);
+                ReadBufferByteBased reader = new ReadBufferByteBased(buf);
+
+                // Try to parse the buffer content.
+                try {
+                    ModbusADU modbusADU = ModbusRtuADU.staticParse(reader, DriverType.MODBUS_RTU, true);
+
+                    // Theoretically, the buffer could contain more than one message.
+                    return modbusADU.getLengthInBytes();
+                } catch (ParseException e) {
+                    return -1;
+                }
             }
             return -1;
         }