Skip to content

Commit fd4e348

Browse files
author
Claudio Catterina
committed
Handle the reception of incomplete responses.
If an incomplete response is received strange things happened (e.g. receiving 0xFF raise an IndexError, receiving 0x011012 raise a struct.error). Now if an incomplete response is received an error is logged. Related to discussion in pymodbus-dev#188 , Fix pymodbus-dev#211
1 parent c49038a commit fd4e348

File tree

5 files changed

+66
-43
lines changed

5 files changed

+66
-43
lines changed

pymodbus/exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def __init__(self, string=""):
7878
ModbusException.__init__(self, message)
7979

8080

81-
class InvalidMessageRecievedException(ModbusException):
81+
class InvalidMessageReceivedException(ModbusException):
8282
"""
8383
Error resulting from invalid response received or decoded
8484
"""

pymodbus/framer/rtu_framer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import time
33

44
from pymodbus.exceptions import ModbusIOException
5-
from pymodbus.exceptions import InvalidMessageRecievedException
5+
from pymodbus.exceptions import InvalidMessageReceivedException
66
from pymodbus.utilities import checkCRC, computeCRC
77
from pymodbus.utilities import hexlify_packets, ModbusTransactionState
88
from pymodbus.compat import byte2int
@@ -313,7 +313,7 @@ def _process(self, callback, error=False):
313313
if result is None:
314314
raise ModbusIOException("Unable to decode request")
315315
elif error and result.function_code < 0x80:
316-
raise InvalidMessageRecievedException(result)
316+
raise InvalidMessageReceivedException(result)
317317
else:
318318
self.populateResult(result)
319319
self.advanceFrame()

pymodbus/framer/socket_framer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import struct
22
from pymodbus.exceptions import ModbusIOException
3-
from pymodbus.exceptions import InvalidMessageRecievedException
3+
from pymodbus.exceptions import InvalidMessageReceivedException
44
from pymodbus.utilities import hexlify_packets
55
from pymodbus.framer import ModbusFramer, SOCKET_FRAME_HEADER
66

@@ -174,7 +174,7 @@ def _process(self, callback, error=False):
174174
if result is None:
175175
raise ModbusIOException("Unable to decode request")
176176
elif error and result.function_code < 0x80:
177-
raise InvalidMessageRecievedException(result)
177+
raise InvalidMessageReceivedException(result)
178178
else:
179179
self.populateResult(result)
180180
self.advanceFrame()

pymodbus/transaction.py

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from threading import RLock
77

88
from pymodbus.exceptions import ModbusIOException, NotImplementedException
9-
from pymodbus.exceptions import InvalidMessageRecievedException
9+
from pymodbus.exceptions import InvalidMessageReceivedException
1010
from pymodbus.constants import Defaults
1111
from pymodbus.framer.ascii_framer import ModbusAsciiFramer
1212
from pymodbus.framer.rtu_framer import ModbusRtuFramer
@@ -74,14 +74,9 @@ def _set_adu_size(self):
7474
self.base_adu_size = 7 # start(1)+ Address(2), LRC(2) + end(2)
7575
elif isinstance(self.client.framer, ModbusBinaryFramer):
7676
self.base_adu_size = 5 # start(1) + Address(1), CRC(2) + end(1)
77-
else:
78-
self.base_adu_size = -1
7977

8078
def _calculate_response_length(self, expected_pdu_size):
81-
if self.base_adu_size == -1:
82-
return None
83-
else:
84-
return self.base_adu_size + expected_pdu_size
79+
return self.base_adu_size + expected_pdu_size
8580

8681
def _calculate_exception_length(self):
8782
''' Returns the length of the Modbus Exception Response according to
@@ -94,8 +89,6 @@ def _calculate_exception_length(self):
9489
elif isinstance(self.client.framer, (ModbusRtuFramer, ModbusBinaryFramer)):
9590
return self.base_adu_size + 2 # Fcode(1), ExcecptionCode(1)
9691

97-
return None
98-
9992
def _check_response(self, response):
10093
''' Checks if the response is a Modbus Exception.
10194
'''
@@ -208,11 +201,11 @@ def _transact(self, packet, response_length, full=False):
208201
_logger.debug("Changing transaction state from 'SENDING' "
209202
"to 'WAITING FOR REPLY'")
210203
self.client.state = ModbusTransactionState.WAITING_FOR_REPLY
211-
result = self._recv(response_length or 1024, full)
204+
result = self._recv(response_length, full)
212205
if _logger.isEnabledFor(logging.DEBUG):
213206
_logger.debug("RECV: " + hexlify_packets(result))
214207
except (socket.error, ModbusIOException,
215-
InvalidMessageRecievedException) as msg:
208+
InvalidMessageReceivedException) as msg:
216209
self.client.close()
217210
_logger.debug("Transaction failed. (%s) " % msg)
218211
last_exception = msg
@@ -223,7 +216,6 @@ def _send(self, packet):
223216
return self.client.framer.sendPacket(packet)
224217

225218
def _recv(self, expected_response_length, full):
226-
expected_response_length = expected_response_length or 1024
227219
if not full:
228220
exception_length = self._calculate_exception_length()
229221
if isinstance(self.client.framer, ModbusSocketFramer):
@@ -238,31 +230,37 @@ def _recv(self, expected_response_length, full):
238230
min_size = expected_response_length
239231

240232
read_min = self.client.framer.recvPacket(min_size)
241-
if read_min:
233+
if not read_min:
234+
return read_min
235+
236+
if len(read_min) < min_size:
237+
raise InvalidMessageReceivedException(
238+
"Incomplete message received, expected at least %d bytes (%d received)"
239+
% (min_size, len(read_min)))
240+
241+
if isinstance(self.client.framer, ModbusSocketFramer):
242+
func_code = byte2int(read_min[-1])
243+
elif isinstance(self.client.framer, ModbusRtuFramer):
244+
func_code = byte2int(read_min[-1])
245+
elif isinstance(self.client.framer, ModbusAsciiFramer):
246+
func_code = int(read_min[3:5], 16)
247+
elif isinstance(self.client.framer, ModbusBinaryFramer):
248+
func_code = byte2int(read_min[-1])
249+
else:
250+
func_code = -1
251+
252+
if func_code < 0x80: # Not an error
242253
if isinstance(self.client.framer, ModbusSocketFramer):
243-
func_code = byte2int(read_min[-1])
244-
elif isinstance(self.client.framer, ModbusRtuFramer):
245-
func_code = byte2int(read_min[-1])
246-
elif isinstance(self.client.framer, ModbusAsciiFramer):
247-
func_code = int(read_min[3:5], 16)
248-
elif isinstance(self.client.framer, ModbusBinaryFramer):
249-
func_code = byte2int(read_min[-1])
250-
else:
251-
func_code = -1
252-
253-
if func_code < 0x80: # Not an error
254-
if isinstance(self.client.framer, ModbusSocketFramer):
255-
# Ommit UID, which is included in header size
256-
h_size = self.client.framer._hsize
257-
length = struct.unpack(">H", read_min[4:6])[0] - 1
258-
expected_response_length = h_size + length
259-
expected_response_length -= min_size
260-
total = expected_response_length + min_size
261-
else:
262-
expected_response_length = exception_length - min_size
263-
total = expected_response_length + min_size
254+
# Ommit UID, which is included in header size
255+
h_size = self.client.framer._hsize
256+
length = struct.unpack(">H", read_min[4:6])[0] - 1
257+
expected_response_length = h_size + length
258+
expected_response_length -= min_size
259+
total = expected_response_length + min_size
264260
else:
265-
total = expected_response_length
261+
expected_response_length = exception_length - min_size
262+
total = expected_response_length + min_size
263+
266264
else:
267265
read_min = b''
268266
total = expected_response_length
@@ -273,6 +271,9 @@ def _recv(self, expected_response_length, full):
273271
_logger.debug("Incomplete message received, "
274272
"Expected {} bytes Recieved "
275273
"{} bytes !!!!".format(total, actual))
274+
raise InvalidMessageReceivedException(
275+
"Incomplete message received, %d bytes expected (%d received)"
276+
% (total, actual))
276277
if self.client.state != ModbusTransactionState.PROCESSING_REPLY:
277278
_logger.debug("Changing transaction state from "
278279
"'WAITING FOR REPLY' to 'PROCESSING REPLY'")

test/test_transaction.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@
88
ModbusRtuFramer, ModbusBinaryFramer
99
)
1010
from pymodbus.factory import ServerDecoder
11-
from pymodbus.compat import byte2int
12-
from mock import MagicMock
11+
from pymodbus.compat import IS_PYTHON3, byte2int
12+
if IS_PYTHON3: # Python 3
13+
from unittest.mock import MagicMock, PropertyMock
14+
else: # Python 2
15+
from mock import MagicMock, PropertyMock
1316
from pymodbus.exceptions import (
14-
NotImplementedException, ModbusIOException, InvalidMessageRecievedException
17+
NotImplementedException, ModbusIOException, InvalidMessageReceivedException
1518
)
1619

1720
class ModbusTransactionTest(unittest.TestCase):
@@ -41,6 +44,25 @@ def tearDown(self):
4144
del self._rtu
4245
del self._ascii
4346

47+
def testTransactionManagerRecv(self):
48+
mock_client = MagicMock()
49+
mock_client.recv.side_effect = iter([b'\x11\x04', b'\x02\x00\x0a\xf8\xf4'])
50+
framer = ModbusRtuFramer(self.decoder, mock_client)
51+
type(mock_client).framer = PropertyMock(return_value=framer)
52+
manager = ModbusTransactionManager(mock_client)
53+
self.assertEqual(manager._recv(7, False), b'\x11\x04\x02\x00\x0a\xf8\xf4')
54+
55+
mock_client.recv.side_effect = iter([b'\x11'])
56+
with self.assertRaises(InvalidMessageReceivedException):
57+
manager._recv(7, False)
58+
59+
mock_client.recv.side_effect = iter([b''])
60+
self.assertEqual(manager._recv(7, False), b'')
61+
62+
mock_client.recv.side_effect = iter([b'\x11\x04', b'\x02'])
63+
with self.assertRaises(InvalidMessageReceivedException):
64+
manager._recv(7, False)
65+
4466
#---------------------------------------------------------------------------#
4567
# Dictionary based transaction manager
4668
#---------------------------------------------------------------------------#

0 commit comments

Comments
 (0)