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
13 changes: 7 additions & 6 deletions s7/_s7commplus_async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -815,20 +816,20 @@ 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]
else:
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:
Expand Down
14 changes: 11 additions & 3 deletions s7/_s7commplus_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
17 changes: 9 additions & 8 deletions s7/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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]
Expand All @@ -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:
Expand Down