diff --git a/s7/_s7commplus_async_client.py b/s7/_s7commplus_async_client.py index 19eaaab6..c183e450 100644 --- a/s7/_s7commplus_async_client.py +++ b/s7/_s7commplus_async_client.py @@ -488,7 +488,8 @@ async def read_symbolic(self, access_area: int, lids: list[int], symbol_crc: int .. warning:: This method is **experimental** and may change. """ - payload = _build_symbolic_read_payload(access_area, lids, symbol_crc) + # TODO: Send the correct integrity id once available + payload = _build_symbolic_read_payload(access_area, lids, symbol_crc, False, self._integrity_id_read) response = await self._send_request(FunctionCode.GET_MULTI_VARIABLES, payload) results = _parse_read_response(response) if not results or results[0] is None: @@ -815,12 +816,12 @@ async def _create_session(self) -> None: version, data_length, consumed = decode_header(response_data) response = response_data[consumed : consumed + data_length] - if len(response) < 14: + if len(response) < 10: raise RuntimeError("CreateObject response too short") - # Parse response body: ReturnValue(VLQ) + ObjectIdCount + ObjectIds(VLQ). - # The usable session id is ObjectIds[0] (NOT the header SessionId field). - body = response[14:] + # Response header is 10 bytes (opcode+reserved+func+reserved+seq+transport). + # Responses do NOT carry a SessionId field (unlike requests which are 14 bytes). + body = response[10:] object_ids, obj_end = parse_create_object_session_id(body) if object_ids: self._session_id = object_ids[0] @@ -828,7 +829,7 @@ async def _create_session(self) -> None: self._session_id = struct.unpack_from(">I", response, 9)[0] self._protocol_version = version - self._server_session_version = parse_server_session_version(response[14 + obj_end :]) + self._server_session_version = parse_server_session_version(response[10 + obj_end :]) if self._server_session_version is not None: logger.info(f"ServerSessionVersion captured: {len(self._server_session_version)} bytes") else: diff --git a/s7/_s7commplus_client.py b/s7/_s7commplus_client.py index 5bd99779..c44ef8e2 100644 --- a/s7/_s7commplus_client.py +++ b/s7/_s7commplus_client.py @@ -238,7 +238,8 @@ def read_symbolic(self, access_area: int, lids: list[int], symbol_crc: int = 0) if self._connection is None: raise RuntimeError("Not connected") - payload = _build_symbolic_read_payload(access_area, lids, symbol_crc) + # TODO: Send the correct integrity id once available + payload = _build_symbolic_read_payload(access_area, lids, symbol_crc, False) response = self._connection.send_request(FunctionCode.GET_MULTI_VARIABLES, payload) results = _parse_read_response(response) if not results or results[0] is None: @@ -730,7 +731,13 @@ def _build_area_write_payload(area_rid: int, start: int, data: bytes) -> bytes: return bytes(payload) -def _build_symbolic_read_payload(access_area: int, lids: list[int], symbol_crc: int = 0) -> bytes: +def _build_symbolic_read_payload( + access_area: int, + lids: list[int], + symbol_crc: int = 0, + with_integrity: bool = True, + integrity_id: int = 1, +) -> bytes: """Build a GetMultiVariables payload for symbolic (LID-based) access. Used for optimized block access on S7-1200/1500 where byte offsets @@ -758,7 +765,8 @@ def _build_symbolic_read_payload(access_area: int, lids: list[int], symbol_crc: payload += encode_uint32_vlq(field_count) payload += addr_bytes payload += encode_object_qualifier() - payload += encode_uint32_vlq(1) + if with_integrity: + payload += encode_uint32_vlq(integrity_id) payload += struct.pack(">I", 0) return bytes(payload) diff --git a/s7/connection.py b/s7/connection.py index 4df3af64..9fba4f4d 100644 --- a/s7/connection.py +++ b/s7/connection.py @@ -660,7 +660,7 @@ def _init_ssl(self) -> None: version, data_length, consumed = decode_header(response_frame) response = response_frame[consumed:] - if len(response) < 14: + if len(response) < 10: from snap7.error import S7ConnectionError raise S7ConnectionError("InitSSL response too short") @@ -751,14 +751,15 @@ def _create_session(self) -> None: logger.debug(f"CreateObject response: version=V{version}, data_length={data_length}") logger.debug(f"CreateObject response body ({len(response)} bytes): {response.hex(' ')}") - if len(response) < 14: + if len(response) < 10: from snap7.error import S7ConnectionError raise S7ConnectionError("CreateObject response too short") - # Parse response body: ReturnValue(UInt64 VLQ) + ObjectIdCount + ObjectIds(VLQ). - # The usable session id is ObjectIds[0] (NOT the header SessionId field). - body = response[14:] + # Response header is 10 bytes (opcode+reserved+func+reserved+seq+transport). + # Responses do NOT carry a SessionId field (unlike requests which are 14 bytes). + # The session ID comes from the payload body, not the header. + body = response[10:] object_ids, obj_end = parse_create_object_session_id(body) if object_ids: self._session_id = object_ids[0] @@ -770,16 +771,16 @@ def _create_session(self) -> None: resp_opcode = response[0] resp_func = struct.unpack_from(">H", response, 3)[0] resp_seq = struct.unpack_from(">H", response, 7)[0] - resp_transport = response[13] + resp_transport = response[9] logger.debug( f"CreateObject response header: opcode=0x{resp_opcode:02X} function=0x{resp_func:04X} " f"seq={resp_seq} session=0x{self._session_id:08X} transport=0x{resp_transport:02X}" ) - logger.debug(f"CreateObject response payload: {response[14:].hex(' ')}") + logger.debug(f"CreateObject response payload: {response[10:].hex(' ')}") logger.debug(f"Session created: id=0x{self._session_id:08X} ({self._session_id}), version=V{version}") # Parse response payload to extract ServerSessionVersion - self._server_session_version = parse_server_session_version(response[14 + obj_end :]) + self._server_session_version = parse_server_session_version(response[10 + obj_end :]) if self._server_session_version is not None: logger.info(f"ServerSessionVersion captured: {len(self._server_session_version)} bytes") else: