Skip to content

Commit ec217a5

Browse files
authored
Handle empty response PDU in ModbusTcpClient (#122)
fixes #121
1 parent 8e0d755 commit ec217a5

File tree

3 files changed

+94
-0
lines changed

3 files changed

+94
-0
lines changed

modbus-tcp/src/test/java/com/digitalpetri/modbus/tcp/ModbusTcpCodecTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.digitalpetri.modbus.ModbusTcpFrame;
77
import io.netty.buffer.ByteBuf;
88
import io.netty.buffer.ByteBufUtil;
9+
import io.netty.buffer.Unpooled;
910
import io.netty.channel.embedded.EmbeddedChannel;
1011
import java.nio.ByteBuffer;
1112
import org.junit.jupiter.api.Test;
@@ -34,4 +35,16 @@ void encodeDecodeFrame() {
3435

3536
assertEquals(frame, decoded);
3637
}
38+
39+
@Test
40+
void emptyPdu() {
41+
var rx = Unpooled.copiedBuffer(ByteBufUtil.decodeHexDump("5FFD0000000101"));
42+
var channel = new EmbeddedChannel(new ModbusTcpCodec());
43+
44+
channel.writeInbound(rx);
45+
ModbusTcpFrame frame = channel.readInbound();
46+
47+
System.out.println(frame);
48+
assertEquals(0, frame.pdu().remaining());
49+
}
3750
}

modbus/src/main/java/com/digitalpetri/modbus/client/ModbusTcpClient.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,12 @@ private void onFrameReceived(ModbusTcpFrame frame) {
172172
promise.timeout.cancel();
173173

174174
ByteBuffer buffer = frame.pdu();
175+
176+
if (buffer.remaining() == 0) {
177+
promise.future.completeExceptionally(new ModbusException("empty response PDU"));
178+
return;
179+
}
180+
175181
int functionCode = buffer.get(buffer.position()) & 0xFF;
176182

177183
if (functionCode == promise.functionCode) {
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.digitalpetri.modbus.client;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
6+
import com.digitalpetri.modbus.MbapHeader;
7+
import com.digitalpetri.modbus.ModbusTcpFrame;
8+
import com.digitalpetri.modbus.exceptions.ModbusException;
9+
import java.nio.ByteBuffer;
10+
import java.util.concurrent.CompletableFuture;
11+
import java.util.concurrent.CompletionStage;
12+
import java.util.concurrent.ExecutionException;
13+
import java.util.function.Consumer;
14+
import org.junit.jupiter.api.Test;
15+
16+
public class ModbusTcpClientTest {
17+
18+
/**
19+
* Tests handling of an erroneous empty response PDU.
20+
*
21+
* @see <a
22+
* href="https://github.com/digitalpetri/modbus/issues/121">https://github.com/digitalpetri/modbus/issues/121</a>
23+
*/
24+
@Test
25+
void emptyResponsePdu() {
26+
var transport = new TestTransport();
27+
var client = ModbusTcpClient.create(transport);
28+
29+
CompletionStage<byte[]> cs = client.sendRawAsync(1, new byte[] {0x04, 0x03, 0x00, 0x00, 0x01});
30+
31+
transport.frameReceiver.accept(
32+
new ModbusTcpFrame(new MbapHeader(0, 1, 1, 1), ByteBuffer.allocate(0)));
33+
34+
ExecutionException ex =
35+
assertThrows(ExecutionException.class, () -> cs.toCompletableFuture().get());
36+
37+
ModbusException cause = (ModbusException) ex.getCause();
38+
assertEquals("empty response PDU", cause.getMessage());
39+
}
40+
41+
private static class TestTransport implements ModbusTcpClientTransport {
42+
43+
boolean connected = false;
44+
ModbusTcpFrame lastFrameSent;
45+
Consumer<ModbusTcpFrame> frameReceiver;
46+
47+
@Override
48+
public CompletionStage<Void> connect() {
49+
connected = true;
50+
return CompletableFuture.completedFuture(null);
51+
}
52+
53+
@Override
54+
public CompletionStage<Void> disconnect() {
55+
connected = false;
56+
return CompletableFuture.completedFuture(null);
57+
}
58+
59+
@Override
60+
public boolean isConnected() {
61+
return connected;
62+
}
63+
64+
@Override
65+
public CompletionStage<Void> send(ModbusTcpFrame frame) {
66+
lastFrameSent = frame;
67+
return CompletableFuture.completedFuture(null);
68+
}
69+
70+
@Override
71+
public void receive(Consumer<ModbusTcpFrame> frameReceiver) {
72+
this.frameReceiver = frameReceiver;
73+
}
74+
}
75+
}

0 commit comments

Comments
 (0)