Version: pymodbus 3.15.0 (dev identical in simruntime.py / simdevice.py), Python 3.12
SimDevice.__create_block() fills gaps between SimData entries with DataType.INVALID entries and appends one more INVALID entry at the end of every block. Those entries count towards register_count, so get_reg_block() lets an access to them pass its range check. The INVALID flag is only evaluated in __check_block() after self.action(...) has been awaited. The action is therefore called for addresses that do not exist — for a write, with the values — before the request is rejected with ILLEGAL_ADDRESS. The client gets the right exception, but an action that reacts to writes acts on registers that do not exist. #2926 fixed the lower bound in the same function; this is the other side.
import asyncio
from pymodbus.simulator import DataType, SimData, SimDevice
from pymodbus.simulator.simcore import SimCore
seen = []
async def action(fc, start_address, address, count, current_registers, set_values):
seen.append((fc, address, set_values))
device = SimDevice(1, simdata=(
[SimData(0, values=False, datatype=DataType.BITS)],
[SimData(0, values=False, datatype=DataType.BITS)],
[SimData(0, values=[0] * 5, datatype=DataType.REGISTERS), # holding 0..4
SimData(10, values=[0] * 5, datatype=DataType.REGISTERS)], # holding 10..14 - gap 5..9
[SimData(0, values=0, datatype=DataType.REGISTERS)]), action=action)
async def main():
core = SimCore([device])
for address in (7, 15): # in the gap, one past the end
seen.clear()
rc = await core.async_setValues(1, 6, address, [4321])
print(address, rc, seen)
asyncio.run(main())
7 ExcCodes.ILLEGAL_ADDRESS [(6, 7, [4321])]
15 ExcCodes.ILLEGAL_ADDRESS [(6, 15, [4321])]
Expected: the action is only called for addresses that exist; an access to an INVALID address is rejected before the action runs.
Suggested fix: in __check_block(), evaluate the INVALID (and READONLY) flags before awaiting self.action.
Workaround: a bounds check inside the action.
Version: pymodbus 3.15.0 (dev identical in simruntime.py / simdevice.py), Python 3.12
SimDevice.__create_block() fills gaps between SimData entries with DataType.INVALID entries and appends one more INVALID entry at the end of every block. Those entries count towards register_count, so get_reg_block() lets an access to them pass its range check. The INVALID flag is only evaluated in __check_block() after self.action(...) has been awaited. The action is therefore called for addresses that do not exist — for a write, with the values — before the request is rejected with ILLEGAL_ADDRESS. The client gets the right exception, but an action that reacts to writes acts on registers that do not exist. #2926 fixed the lower bound in the same function; this is the other side.
import asyncio
from pymodbus.simulator import DataType, SimData, SimDevice
from pymodbus.simulator.simcore import SimCore
seen = []
async def action(fc, start_address, address, count, current_registers, set_values):
seen.append((fc, address, set_values))
device = SimDevice(1, simdata=(
[SimData(0, values=False, datatype=DataType.BITS)],
[SimData(0, values=False, datatype=DataType.BITS)],
[SimData(0, values=[0] * 5, datatype=DataType.REGISTERS), # holding 0..4
SimData(10, values=[0] * 5, datatype=DataType.REGISTERS)], # holding 10..14 - gap 5..9
[SimData(0, values=0, datatype=DataType.REGISTERS)]), action=action)
async def main():
core = SimCore([device])
for address in (7, 15): # in the gap, one past the end
seen.clear()
rc = await core.async_setValues(1, 6, address, [4321])
print(address, rc, seen)
asyncio.run(main())
7 ExcCodes.ILLEGAL_ADDRESS [(6, 7, [4321])]
15 ExcCodes.ILLEGAL_ADDRESS [(6, 15, [4321])]
Expected: the action is only called for addresses that exist; an access to an INVALID address is rejected before the action runs.
Suggested fix: in __check_block(), evaluate the INVALID (and READONLY) flags before awaiting self.action.
Workaround: a bounds check inside the action.