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;
}