Skip to content

Add S7CommPlus V2 protocol support (TLS + IntegrityId)#646

Open
gijzelaerr wants to merge 18 commits intos7commplus-scaffoldingfrom
feature/s7commplus-v2
Open

Add S7CommPlus V2 protocol support (TLS + IntegrityId)#646
gijzelaerr wants to merge 18 commits intos7commplus-scaffoldingfrom
feature/s7commplus-v2

Conversation

@gijzelaerr
Copy link
Owner

Summary

  • Adds V2 protocol support for S7-1200/1500 PLCs with modern firmware
  • TLS 1.3 wrapping of COTP socket with OMS exporter secret extraction
  • Dual IntegrityId counters (read vs write) inserted into V2 PDU headers
  • Password legitimation module: legacy SHA-1 XOR (older firmware) and AES-256-CBC (newer firmware)
  • V2 server emulator with TLS and IntegrityId tracking for testing
  • 39 new tests covering protocol constants, key derivation, legitimation, IntegrityId tracking

Architecture

V2 changes are minimal on top of the V1 scaffolding:

  • connection.py: TLS activation, IntegrityId in send_request()
  • protocol.py: READ_FUNCTION_CODES set, LegitimationId enum
  • legitimation.py: New module for password authentication
  • server.py: V2 emulation for unit tests
  • client.py/async_client.py: password parameter (legitimation deferred)

Test plan

  • 39 V2-specific tests pass
  • All 578 existing tests pass
  • mypy strict mode: 0 errors
  • ruff: all checks pass

🤖 Generated with Claude Code

@gijzelaerr gijzelaerr force-pushed the s7commplus-scaffolding branch 2 times, most recently from 7f2dd2d to 61ba9f0 Compare March 20, 2026 10:37
gijzelaerr and others added 18 commits March 20, 2026 12:38
* Add S7CommPlus protocol scaffolding for S7-1200/1500 support

Adds the snap7.s7commplus package as a foundation for future S7CommPlus
protocol support, targeting all S7-1200/1500 PLCs (V1/V2/V3/TLS).

Includes:
- Protocol constants (opcodes, function codes, data types, element IDs)
- VLQ encoding/decoding (Variable-Length Quantity, the S7CommPlus wire format)
- Codec for frame headers, request/response headers, and typed values
- Connection skeleton with multi-version support (V1/V2/V3/TLS)
- Client stub with symbolic variable access API
- 86 passing tests for VLQ and codec modules

The wire protocol (VLQ, data types, object model) is the same across all
protocol versions -- only the session authentication layer differs. The
protocol version is auto-detected from the PLC's CreateObject response.

Reference: thomas-v2/S7CommPlusDriver (C#, LGPL-3.0)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add S7CommPlus server emulator, async client, and integration tests

Server emulator (snap7/s7commplus/server.py):
- Full PLC memory model with thread-safe data blocks
- Named variable registration with type metadata
- Handles CreateObject/DeleteObject session management
- Handles Explore (browse registered DBs and variables)
- Handles GetMultiVariables/SetMultiVariables (read/write)
- Multi-client support (threaded)
- CPU state management

Async client (snap7/s7commplus/async_client.py):
- asyncio-based S7CommPlus client with Lock for concurrent safety
- Same API as sync client: db_read, db_write, db_read_multi, explore
- Native COTP/TPKT transport using asyncio streams

Updated sync client and connection to be functional for V1:
- CreateObject/DeleteObject session lifecycle
- Send/receive with S7CommPlus framing over COTP/TPKT
- db_read, db_write, db_read_multi operations

Integration tests (25 new tests):
- Server unit tests (data blocks, variables, CPU state)
- Sync client <-> server: connect, read, write, multi-read, explore
- Async client <-> server: connect, read, write, concurrent reads
- Data persistence across client sessions
- Multiple concurrent clients with unique sessions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Clean up security-focused wording in S7CommPlus docstrings

Reframe protocol version descriptions around interoperability rather
than security vulnerabilities. Remove CVE references and replace
implementation-specific language with neutral terminology.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix CI: remove pytest-asyncio dependency, fix formatting

Rewrite async tests to use asyncio.run() instead of @pytest.mark.asyncio
since pytest-asyncio is not a project dependency. Also apply ruff
formatting fixes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add pytest-asyncio dependency and use native async tests

Add pytest-asyncio to test dependencies and set asyncio_mode=auto.
Restore async test methods with @pytest.mark.asyncio instead of
asyncio.run() wrappers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix CI and add S7CommPlus end-to-end tests

Fix ruff formatting violations and mypy type errors in S7CommPlus code
that caused pre-commit CI to fail. Add end-to-end test suite for
validating S7CommPlus against a real S7-1200/1500 PLC.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Enhance S7CommPlus connection with variable-length TSAP support and async client improvements

Support bytes-type remote TSAP (e.g. "SIMATIC-ROOT-HMI") in ISOTCPConnection,
extend S7CommPlus protocol handling, and improve async client and server emulator.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add extensive debug logging to S7CommPlus protocol stack for real PLC diagnostics

Adds hex dumps and detailed parsing at every protocol layer (ISO-TCP,
S7CommPlus connection, client) plus 6 new diagnostic e2e tests that
probe different payload formats and function codes against real hardware.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix S7CommPlus wire format for real PLC compatibility

Rewrite client payload encoding/decoding to use the correct S7CommPlus
protocol format with ItemAddress structures (SymbolCrc, AccessArea,
AccessSubArea, LIDs), ObjectQualifier, and proper PValue response
parsing. Previously the client used a simplified custom format that
only worked with the emulated server, causing ERROR2 responses from
real S7-1200/1500 PLCs.

- client.py: Correct GetMultiVariables/SetMultiVariables request format
- async_client.py: Reuse corrected payload builders from client.py
- codec.py: Add ItemAddress, ObjectQualifier, PValue encode/decode
- protocol.py: Add Ids constants (DB_ACCESS_AREA_BASE, etc.)
- server.py: Update to parse/generate the corrected wire format

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix S7CommPlus LID byte offsets to use 1-based addressing

S7CommPlus protocol uses 1-based LID byte offsets, but the client was
sending 0-based offsets. This caused real PLCs to reject all db_read
and db_write requests. Also fixes lint issues in e2e test file.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add S7CommPlus session setup and legacy S7 fallback for data operations

Implement the missing SetMultiVariables session handshake step that echoes
ServerSessionVersion (attr 306) back to the PLC after CreateObject. Without
this, PLCs reject data operations with ERROR2 (0x05A9).

For PLCs that don't provide ServerSessionVersion or don't support S7CommPlus
data operations, the client transparently falls back to the legacy S7 protocol.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Potential fix for code scanning alert no. 9: Binding a socket to all network interfaces

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
* Add CLI tools for direct PLC interaction (read, write, dump, info)

Expand the snap7 CLI beyond snap7-server with subcommands for reading,
writing, dumping, and inspecting PLCs directly from the terminal.

Closes #621

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix CI: skip CLI tests when click is not installed

Uses pytest.importorskip("click") so test collection doesn't fail
in CI environments that only install the test dependencies.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Rename CLI entry point from snap7 to s7, add discover hook

Renames the CLI command from `snap7` to `s7` for a cleaner interface.
Adds a hook to auto-register `s7 discover` when the discovery module
is available, so both PRs compose cleanly when merged.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Adds db_read_*/db_write_* convenience methods for bool, byte, int, uint,
word, dint, udint, dword, real, lreal, string, and wstring types.
Closes #617

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Adds 70 tests validating TPKT, COTP, and S7 protocol encoding against
the specification at byte level.
Closes #620

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add examples cookbook and troubleshooting documentation

Based on analysis of 312 issues and discussions, the two biggest
sources of user confusion are S7-1200/1500 configuration and data
type handling. These new pages address the most common questions.

examples.rst: rack/slot reference table, PLC address mapping guide,
complete data types cookbook (BOOL through DATE_AND_TIME), memory
areas, analog I/O, multi-variable read, and server setup for testing.

troubleshooting.rst: error message reference table, S7-1200/1500
TIA Portal configuration steps, connection recovery patterns, timeout
configuration, thread safety, and protocol limitations FAQ.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add PLC support matrix documentation

Add a new page documenting which Siemens PLC families are supported,
their protocol capabilities, PUT/GET configuration requirements, and
alternatives for unsupported PLCs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Adds the snap7.s7commplus package as a foundation for future S7CommPlus
protocol support, targeting all S7-1200/1500 PLCs (V1/V2/V3/TLS).

Includes:
- Protocol constants (opcodes, function codes, data types, element IDs)
- VLQ encoding/decoding (Variable-Length Quantity, the S7CommPlus wire format)
- Codec for frame headers, request/response headers, and typed values
- Connection skeleton with multi-version support (V1/V2/V3/TLS)
- Client stub with symbolic variable access API
- 86 passing tests for VLQ and codec modules

The wire protocol (VLQ, data types, object model) is the same across all
protocol versions -- only the session authentication layer differs. The
protocol version is auto-detected from the PLC's CreateObject response.

Reference: thomas-v2/S7CommPlusDriver (C#, LGPL-3.0)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Server emulator (snap7/s7commplus/server.py):
- Full PLC memory model with thread-safe data blocks
- Named variable registration with type metadata
- Handles CreateObject/DeleteObject session management
- Handles Explore (browse registered DBs and variables)
- Handles GetMultiVariables/SetMultiVariables (read/write)
- Multi-client support (threaded)
- CPU state management

Async client (snap7/s7commplus/async_client.py):
- asyncio-based S7CommPlus client with Lock for concurrent safety
- Same API as sync client: db_read, db_write, db_read_multi, explore
- Native COTP/TPKT transport using asyncio streams

Updated sync client and connection to be functional for V1:
- CreateObject/DeleteObject session lifecycle
- Send/receive with S7CommPlus framing over COTP/TPKT
- db_read, db_write, db_read_multi operations

Integration tests (25 new tests):
- Server unit tests (data blocks, variables, CPU state)
- Sync client <-> server: connect, read, write, multi-read, explore
- Async client <-> server: connect, read, write, concurrent reads
- Data persistence across client sessions
- Multiple concurrent clients with unique sessions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reframe protocol version descriptions around interoperability rather
than security vulnerabilities. Remove CVE references and replace
implementation-specific language with neutral terminology.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rewrite async tests to use asyncio.run() instead of @pytest.mark.asyncio
since pytest-asyncio is not a project dependency. Also apply ruff
formatting fixes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add pytest-asyncio to test dependencies and set asyncio_mode=auto.
Restore async test methods with @pytest.mark.asyncio instead of
asyncio.run() wrappers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix ruff formatting violations and mypy type errors in S7CommPlus code
that caused pre-commit CI to fail. Add end-to-end test suite for
validating S7CommPlus against a real S7-1200/1500 PLC.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…sync client improvements

Support bytes-type remote TSAP (e.g. "SIMATIC-ROOT-HMI") in ISOTCPConnection,
extend S7CommPlus protocol handling, and improve async client and server emulator.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… diagnostics

Adds hex dumps and detailed parsing at every protocol layer (ISO-TCP,
S7CommPlus connection, client) plus 6 new diagnostic e2e tests that
probe different payload formats and function codes against real hardware.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rewrite client payload encoding/decoding to use the correct S7CommPlus
protocol format with ItemAddress structures (SymbolCrc, AccessArea,
AccessSubArea, LIDs), ObjectQualifier, and proper PValue response
parsing. Previously the client used a simplified custom format that
only worked with the emulated server, causing ERROR2 responses from
real S7-1200/1500 PLCs.

- client.py: Correct GetMultiVariables/SetMultiVariables request format
- async_client.py: Reuse corrected payload builders from client.py
- codec.py: Add ItemAddress, ObjectQualifier, PValue encode/decode
- protocol.py: Add Ids constants (DB_ACCESS_AREA_BASE, etc.)
- server.py: Update to parse/generate the corrected wire format

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
S7CommPlus protocol uses 1-based LID byte offsets, but the client was
sending 0-based offsets. This caused real PLCs to reject all db_read
and db_write requests. Also fixes lint issues in e2e test file.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement the missing SetMultiVariables session handshake step that echoes
ServerSessionVersion (attr 306) back to the PLC after CreateObject. Without
this, PLCs reject data operations with ERROR2 (0x05A9).

For PLCs that don't provide ServerSessionVersion or don't support S7CommPlus
data operations, the client transparently falls back to the legacy S7 protocol.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements V2 protocol support for S7-1200/1500 PLCs with modern firmware:
- TLS 1.3 activation between InitSSL and CreateObject
- OMS exporter secret extraction for legitimation key derivation
- Dual IntegrityId counters (read vs write) in V2 PDU headers
- Password legitimation module (legacy SHA-1 XOR + new AES-256-CBC)
- V2 server emulator with TLS and IntegrityId tracking
- 39 new tests covering all V2 components

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add cryptography as optional dep: pip install python-snap7[s7commplus]
- Implement connection.authenticate() with challenge/response flow
- Wire up legitimation in client.connect(password=...)
- Auto-detect legacy (SHA-1 XOR) vs new (AES-256-CBC) auth mode
- Add legitimation protocol methods: get challenge, send response
- 46 tests now (was 39), including AES roundtrip verification

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gijzelaerr gijzelaerr force-pushed the feature/s7commplus-v2 branch from 89275fb to 4fd702a Compare March 20, 2026 10:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant