Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pymodbus/pdu/decoders.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ def decode(self, frame: bytes) -> ModbusPDU:
function_code = 0
try:
if (function_code := int(frame[0])) > 0x80:
if self.is_server:
raise ModbusIOException(
f"Reserved function code {function_code}",
function_code=function_code,
)
pdu_exp = ExceptionResponse(function_code & 0x7F)
pdu_exp.from_frame(False, frame)
return pdu_exp
Expand Down
2 changes: 1 addition & 1 deletion test/framer/test_extras.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ def test_ascii_process_incoming_packets(self):
def test_rtu_decode_exception(self):
"""Test that the RTU framer can decode errors."""
msg = b"\x00\x90\x02\x9c\x01"
_, pdu = self._rtu.handleFrame(msg, 0, 0)
_, pdu = FramerRTU(DecodePDU(False)).handleFrame(msg, 0, 0)
assert pdu

def test_tcp_framer_skip_wrong_dev_id_then_decode(self):
Expand Down
7 changes: 7 additions & 0 deletions test/pdu/test_decoders.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ def test_server_decode(self, code, frame):
assert pdu
assert pdu.function_code == code

@pytest.mark.parametrize(("code", "frame"), list(exceptions))
def test_server_decode_reserved_function_code(self, code, frame):
"""A request may not use a function code reserved for exception responses."""
with pytest.raises(ModbusIOException) as exc:
self.server.decode(frame)
assert exc.value.fcode == code

@pytest.mark.parametrize(("frame"), [b"", b"NO FRAME"])
@pytest.mark.parametrize(("decoder"), [server, client])
def test_decode_bad_frame(self, decoder, frame):
Expand Down
2 changes: 1 addition & 1 deletion test/pdu/test_pdu.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ async def test_error_methods(self):
"""Test all error methods."""
result = self.exception.x_encode()
assert result == b"\x01"
DecodePDU(True).decode(b"\x81" + result)
DecodePDU(False).decode(b"\x81" + result)
assert self.exception.exception_code == 1
self.exception.x_decode(b"")

Expand Down
8 changes: 8 additions & 0 deletions test/server/test_server_asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,14 @@ async def test_async_tcp_server_write_coil_illegal_value(self):
await asyncio.wait_for(BasicClient.done, timeout=0.1)
assert BasicClient.received_data == b"\x00\x00\x00\x00\x00\x03\x01\x85\x03"

async def test_async_tcp_server_reserved_function_code(self):
"""A function code reserved for exception responses must answer 0x01."""
BasicClient.data = b"\x00\x00\x00\x00\x00\x04\x01\x81\x00\x00"
await self.start_server()
await self.connect_server()
await asyncio.wait_for(BasicClient.done, timeout=0.1)
assert BasicClient.received_data == b"\x00\x00\x00\x00\x00\x03\x01\x81\x01"

async def test_async_server_trace_connect_disconnect(self):
"""Test connect/disconnect trace handler."""
trace_connect = mock.Mock()
Expand Down
Loading