diff --git a/CHANGES.md b/CHANGES.md index 73346d4f..3e5e82c2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -36,6 +36,9 @@ Unreleased * Compress now uses the PI service `_GARB` and copy RAM to ROM uses file system `EP` with `_MODU`, matching real engineering tools and native Snap7, instead of the invented `_MSZL`. The bundled server recognises the new requests (#941). +* Read-SZL requests now carry the `0xFF`/octet-string data header and continuation requests the 12-byte parameter + block (method `0x12`) that real engineering tools send; both previously used the header of a request without data + and an 8-byte parameter block (#942). 3.2.1 ----- diff --git a/snap7/s7protocol.py b/snap7/s7protocol.py index 8791d65a..22707588 100644 --- a/snap7/s7protocol.py +++ b/snap7/s7protocol.py @@ -1059,8 +1059,8 @@ def build_read_szl_request(self, szl_id: int, szl_index: int) -> bytes: # Data section: SZL ID and Index data_section = struct.pack( ">BBHHH", - 0x0A, # Return value (request) - 0x00, # Transport size + 0xFF, # Return value (0xFF, as engineering tools and native Snap7 send for requests carrying data) + 0x09, # Transport size (octet string) 0x0004, # Length (4 bytes for ID + Index) szl_id, # SZL ID szl_index, # SZL Index @@ -1091,18 +1091,23 @@ def build_userdata_followup_request(self, group: int, subfunction: int, sequence Returns: Complete S7 PDU for follow-up request """ - # Parameter section: same as initial but with DataRef = sequence_number + # Parameter section as sent by engineering tools for continuation requests: 12 bytes, method 0x12, + # DataRef = sequence_number, followed by last-data-unit and error code fields set to zero type_group = 0x40 | (group & 0x0F) # Type 4 (request) | group param_data = struct.pack( - ">BBBBBBBB", + ">BBBBBBBBBBBB", 0x00, # Reserved 0x01, # Parameter count 0x12, # Type/length header - 0x04, # Length of following data - 0x11, # Method (0x11 = request) + 0x08, # Length of following data + 0x12, # Method (0x12) type_group, # Type | Group subfunction, # Subfunction sequence_number, # DataRef from previous response + 0x00, # Last data unit + 0x00, # Reserved + 0x00, # Error code high + 0x00, # Error code low ) # Minimal data section for follow-up diff --git a/tests/test_multipacket.py b/tests/test_multipacket.py index 56f726e6..1a982f72 100644 --- a/tests/test_multipacket.py +++ b/tests/test_multipacket.py @@ -136,7 +136,7 @@ def test_szl_followup(self) -> None: # Extract param_len and data_len from header param_len = struct.unpack(">H", pdu[6:8])[0] data_len = struct.unpack(">H", pdu[8:10])[0] - assert param_len == 8 + assert param_len == 12 assert data_len == 4 # Parameter section starts at offset 10 @@ -144,11 +144,12 @@ def test_szl_followup(self) -> None: assert params[0] == 0x00 # Reserved assert params[1] == 0x01 # Param count assert params[2] == 0x12 # Type header - assert params[3] == 0x04 # Length - assert params[4] == 0x11 # Method (request) + assert params[3] == 0x08 # Length + assert params[4] == 0x12 # Method, as engineering tools send for continuation requests assert params[5] == 0x44 # Type(4) | Group(4=SZL) assert params[6] == 0x01 # Subfunction assert params[7] == 0x02 # DataRef = sequence_number + assert params[8:12] == bytes(4) # Data section data = pdu[10 + param_len :] diff --git a/tests/test_s7protocol.py b/tests/test_s7protocol.py index 049748d4..7071b6d3 100644 --- a/tests/test_s7protocol.py +++ b/tests/test_s7protocol.py @@ -284,6 +284,12 @@ def test_copy_ram_to_rom_request_matches_real_job(self) -> None: request = self.proto.build_copy_ram_to_rom_request() assert request[10:] == bytes.fromhex("28000000000000fd00024550055f4d4f4455") + def test_read_szl_request_matches_real_tool(self) -> None: + # Captured from a real S7-300 session: SZL id 0x0132 index 4. Bytes 4-5 are the PDU reference. + real = bytes.fromhex("320700000300000800080001120411440100ff09000401320004") + request = self.proto.build_read_szl_request(0x0132, 4) + assert request[:4] + request[6:] == real[:4] + real[6:] + def test_request_db_download(self) -> None: assert self.proto.build_download_request(0x41, 1, bytes(64)).hex() == ( "320100000001002000001a00010000000000095f30413030303031500d31303030303634303030303238"