s7commplus 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {s7commplus-0.2.0/s7commplus.egg-info → s7commplus-0.3.0}/PKG-INFO +13 -4
- {s7commplus-0.2.0 → s7commplus-0.3.0}/README.md +11 -2
- {s7commplus-0.2.0 → s7commplus-0.3.0}/pyproject.toml +12 -2
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/__init__.py +6 -1
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/_fragment_hmac.py +58 -18
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/alarm.py +7 -5
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/async_client.py +517 -95
- s7commplus-0.3.0/s7commplus/blob_decompressor.py +188 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/catalog.py +30 -35
- s7commplus-0.3.0/s7commplus/cli/__init__.py +129 -0
- s7commplus-0.3.0/s7commplus/cli/__main__.py +6 -0
- s7commplus-0.3.0/s7commplus/cli/_common.py +323 -0
- s7commplus-0.3.0/s7commplus/cli/browse.py +27 -0
- s7commplus-0.3.0/s7commplus/cli/db_read.py +34 -0
- s7commplus-0.3.0/s7commplus/cli/db_write.py +34 -0
- s7commplus-0.3.0/s7commplus/cli/read.py +48 -0
- s7commplus-0.3.0/s7commplus/cli/state.py +21 -0
- s7commplus-0.3.0/s7commplus/cli/write.py +62 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/client.py +734 -88
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/codec.py +28 -1
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/connection.py +388 -96
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/error.py +90 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/legitimation.py +92 -49
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/protocol.py +32 -10
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/server.py +157 -23
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/subscription.py +8 -2
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/tag_browser.py +33 -24
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/typeinfo.py +38 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/ARCHITECTURE.md +62 -5
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/blob_metadata.py +24 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/keys.py +3 -0
- s7commplus-0.3.0/s7commplus/v1_session_key/legitimation.py +203 -0
- s7commplus-0.3.0/s7commplus/values.py +561 -0
- s7commplus-0.3.0/s7commplus/zlib_dicts/__init__.py +57 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0/s7commplus.egg-info}/PKG-INFO +13 -4
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus.egg-info/SOURCES.txt +23 -0
- s7commplus-0.3.0/s7commplus.egg-info/entry_points.txt +2 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus.egg-info/requires.txt +1 -1
- s7commplus-0.3.0/tests/test_blob_decompressor.py +133 -0
- s7commplus-0.3.0/tests/test_cli.py +792 -0
- s7commplus-0.3.0/tests/test_free_tcp_port.py +39 -0
- s7commplus-0.3.0/tests/test_no_fixed_test_ports.py +59 -0
- s7commplus-0.3.0/tests/test_real_plc_dry_run.py +118 -0
- s7commplus-0.3.0/tests/test_run_real_plc_acceptance.py +55 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_alarm.py +41 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_async_session_key.py +12 -0
- s7commplus-0.3.0/tests/test_s7_auto_tls.py +290 -0
- s7commplus-0.3.0/tests/test_s7_cert_pin.py +152 -0
- s7commplus-0.3.0/tests/test_s7_chunking.py +701 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_codec.py +47 -0
- s7commplus-0.3.0/tests/test_s7_connect_signature.py +47 -0
- s7commplus-0.3.0/tests/test_s7_error_pickle.py +80 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_fragment_hmac.py +42 -0
- s7commplus-0.3.0/tests/test_s7_fragmented_response.py +288 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_legacy_request_layout.py +2 -1
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_server.py +139 -27
- s7commplus-0.3.0/tests/test_s7_stale_tags.py +632 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_subscription.py +2 -1
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_tag_catalog.py +6 -6
- s7commplus-0.3.0/tests/test_s7_tls.py +846 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_unit.py +196 -1
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_v2.py +319 -0
- s7commplus-0.3.0/tests/test_s7_values.py +405 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_tag_browser.py +133 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_legitimation.py +87 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_plcsim.py +165 -9
- s7commplus-0.2.0/s7commplus/blob_decompressor.py +0 -89
- s7commplus-0.2.0/s7commplus/v1_session_key/legitimation.py +0 -97
- s7commplus-0.2.0/s7commplus/zlib_dicts/__init__.py +0 -28
- s7commplus-0.2.0/tests/test_blob_decompressor.py +0 -71
- s7commplus-0.2.0/tests/test_run_real_plc_acceptance.py +0 -24
- s7commplus-0.2.0/tests/test_s7_tls.py +0 -445
- {s7commplus-0.2.0 → s7commplus-0.3.0}/LICENSE +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/devices.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/object_model.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/py.typed +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/transport.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/LICENSE-HarpoS7 +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/MAINTAINER_GUIDE.md +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/MODEL_BENCHMARKS.md +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/__init__.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/aes_ecb.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/aes_gcm.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/ghash.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/handshake.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/key_derivation.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/plcsim/__init__.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/plcsim/authenticator.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/plcsim/seed.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/real_plc/__init__.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/real_plc/authenticator.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/real_plc/checksum.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/real_plc/curve.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/real_plc/fingerprint.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/real_plc/present.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/real_plc/seed.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/utils.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/vlq.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/1398a37f_CompilerSettings_90000001.xml +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/1bac39f0_DebugInfo_90000001.xml +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/3c55436a_LineComm_98000001.xml +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/4b8416f0_IntfDesc_90000001.xml +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/5814b03b_IdentES_98000001.xml +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/66052b13_DebugInfo_IntfDesc_98000001.xml +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/79b2bda3_LineComm_90000001.xml +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/81d8db20_IdentES_90000002.xml +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/845fc605_NWT_98000001.xml +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/9b6a3a92_ExtRefData_90000001.xml +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/ab6fa31e_NWC_90000001.xml +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/b0155ff8_IntRefData_98000001.xml +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/c5d26ac3_NWC_98000001.xml +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/ce9b821b_IntfDescTag_90000001.xml +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/da4a88f4_IntRefData_90000001.xml +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/df91b6bb_IdentES_90000001.xml +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/e2729ea1_TagLineComm_90000001.xml +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/efaeae49_BodyDesc_90000001.xml +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/fd69ac74_NWT_90000001.xml +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus.egg-info/dependency_links.txt +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus.egg-info/top_level.txt +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/setup.cfg +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_golden_tia_captures.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_real_plc_support.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_attribute_flags.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_connection_type.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_devices.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_e2e.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_family_key_fallback.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_invoke_methods.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_legacy_browse.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_object_model.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_operating_state.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_real_s7_1200_handshake.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_response_values.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_resubscribe.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_secured_session.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_session_oms.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_symbolic_multi_errors.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_vlq.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_write_types.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_typeinfo.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_aes_ecb.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_aes_gcm.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_artifacts.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_bit_trace.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_bitwise_analysis.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_blob_metadata.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_boolean_decomposition.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_curve.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_evidence.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_family0_big_int.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_family0_big_int_transforms.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_family0_monoliths.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_family0_transforms.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_fingerprint.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_ghash.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_handshake.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_key_derivation.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_model_benchmarks.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith11_compact.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith3_span_identity.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith4_carry_stages.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith4_span_identity.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith5_compact.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith5_gates.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith5_model.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith5_span_decoder.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith6_span_identity.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith7_analysis.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith7_full_analysis.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith7_middle_analysis.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith_map.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_output_trace.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_addition_shortcuts.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_cancel_macros.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_compiled_stage.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_constant_lifts.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_correction_dag.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_correction_domains.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_curve.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_demand_guards.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_exclusive_corrections.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_factored_stage.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_field_circuit.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_first_defect.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_ladder.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_lazy_guards.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_lift_categories.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_predicate_dag.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_predictor.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_product_shortcuts.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_raw_bounds.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_relation_ladder.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_representatives.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_saturated_history.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_seed_boundary.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_semiring_rewrites.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_shift_macros.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_small_product_shortcuts.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_square_shortcuts.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_stage_algebra.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_stage_plan.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_structural_guards.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_subtraction_shortcuts.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_transport.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_seed.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_setup_integer.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_setup_invariant.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform12_analysis.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform12_compact.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform12_formulas.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform12_integer_model.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform12_phase1.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform7_compact.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform7_reference.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform7_setup_corrections.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform7_setup_encoding.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform7_setup_merge.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform7_setup_ranges.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform7_setup_shadows.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform7_tail_trace.py +0 -0
- {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: s7commplus
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Pure Python S7CommPlus client and server emulator for Siemens S7-1200/1500 PLCs
|
|
5
5
|
Author-email: Gijs Molenaar <gijsmolenaar@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -28,7 +28,7 @@ Requires-Dist: cryptography>=41
|
|
|
28
28
|
Provides-Extra: test
|
|
29
29
|
Requires-Dist: pytest; extra == "test"
|
|
30
30
|
Requires-Dist: pytest-asyncio; extra == "test"
|
|
31
|
-
Requires-Dist: pytest-bdd<9
|
|
31
|
+
Requires-Dist: pytest-bdd<10,>=9; extra == "test"
|
|
32
32
|
Requires-Dist: pytest-cov; extra == "test"
|
|
33
33
|
Requires-Dist: hypothesis; extra == "test"
|
|
34
34
|
Requires-Dist: mypy; extra == "test"
|
|
@@ -45,8 +45,8 @@ Dynamic: license-file
|
|
|
45
45
|
Pure-Python S7CommPlus communication for Siemens S7-1200 and S7-1500 PLCs.
|
|
46
46
|
It supports S7CommPlus V1, V2 (TLS), and V3; synchronous and asyncio clients;
|
|
47
47
|
and a server emulator for testing. Both clients support legacy V1 SessionKey
|
|
48
|
-
authentication, validated on a real S7-1200 (sync and async clients)
|
|
49
|
-
S7-1500 (sync client)
|
|
48
|
+
authentication, validated on a real S7-1200 (sync and async clients), an
|
|
49
|
+
S7-1500 (sync client), and S7-PLCSIM Advanced (sync and async clients).
|
|
50
50
|
|
|
51
51
|
## Installation
|
|
52
52
|
|
|
@@ -93,6 +93,15 @@ device_family("6ES7 512-1DK01-0AB0") # 's7-1500-sp'
|
|
|
93
93
|
variants, software controllers and PLCSIM; classic S7-300/400 is out of
|
|
94
94
|
scope). `python tools/check_devices_table.py` validates its shape.
|
|
95
95
|
|
|
96
|
+
Installing the package also installs a `s7commplus` command-line tool:
|
|
97
|
+
|
|
98
|
+
```console
|
|
99
|
+
s7commplus browse --host 192.168.1.10
|
|
100
|
+
s7commplus read --host 192.168.1.10 DB1.Motor.Speed
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
See the command-line interface guide in the documentation.
|
|
104
|
+
|
|
96
105
|
## Development
|
|
97
106
|
|
|
98
107
|
Cloning the repository is only necessary for developing or testing
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
Pure-Python S7CommPlus communication for Siemens S7-1200 and S7-1500 PLCs.
|
|
4
4
|
It supports S7CommPlus V1, V2 (TLS), and V3; synchronous and asyncio clients;
|
|
5
5
|
and a server emulator for testing. Both clients support legacy V1 SessionKey
|
|
6
|
-
authentication, validated on a real S7-1200 (sync and async clients)
|
|
7
|
-
S7-1500 (sync client)
|
|
6
|
+
authentication, validated on a real S7-1200 (sync and async clients), an
|
|
7
|
+
S7-1500 (sync client), and S7-PLCSIM Advanced (sync and async clients).
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
@@ -51,6 +51,15 @@ device_family("6ES7 512-1DK01-0AB0") # 's7-1500-sp'
|
|
|
51
51
|
variants, software controllers and PLCSIM; classic S7-300/400 is out of
|
|
52
52
|
scope). `python tools/check_devices_table.py` validates its shape.
|
|
53
53
|
|
|
54
|
+
Installing the package also installs a `s7commplus` command-line tool:
|
|
55
|
+
|
|
56
|
+
```console
|
|
57
|
+
s7commplus browse --host 192.168.1.10
|
|
58
|
+
s7commplus read --host 192.168.1.10 DB1.Motor.Speed
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
See the command-line interface guide in the documentation.
|
|
62
|
+
|
|
54
63
|
## Development
|
|
55
64
|
|
|
56
65
|
Cloning the repository is only necessary for developing or testing
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "s7commplus"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Pure Python S7CommPlus client and server emulator for Siemens S7-1200/1500 PLCs"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -27,8 +27,11 @@ classifiers = [
|
|
|
27
27
|
]
|
|
28
28
|
dependencies = ["cryptography>=41"]
|
|
29
29
|
|
|
30
|
+
[project.scripts]
|
|
31
|
+
s7commplus = "s7commplus.cli:main"
|
|
32
|
+
|
|
30
33
|
[project.optional-dependencies]
|
|
31
|
-
test = ["pytest", "pytest-asyncio", "pytest-bdd>=
|
|
34
|
+
test = ["pytest", "pytest-asyncio", "pytest-bdd>=9,<10", "pytest-cov", "hypothesis", "mypy", "ruff"]
|
|
32
35
|
docs = ["sphinx>=7.2", "furo>=2024.8.6"]
|
|
33
36
|
analysis = ["z3-solver>=4.13,<5"]
|
|
34
37
|
|
|
@@ -70,6 +73,13 @@ markers = [
|
|
|
70
73
|
"write: real-PLC scenario that changes and restores scratch data",
|
|
71
74
|
"plc_write: existing real-PLC test that modifies scratch data",
|
|
72
75
|
"administrative: disruptive real-PLC scenario requiring separate opt-in",
|
|
76
|
+
"symbolic: real-PLC scenario that browses or addresses tags by name",
|
|
77
|
+
"subscription: real-PLC scenario using data change subscriptions",
|
|
78
|
+
"alarm: real-PLC scenario using the alarm snapshot or alarm subscriptions",
|
|
79
|
+
"cpu_state: real-PLC scenario reading the CPU operating state",
|
|
80
|
+
"tls: real-PLC scenario that needs --plc-use-tls",
|
|
81
|
+
"password: real-PLC scenario that needs the S7COMMPLUS_TEST_PASSWORD environment variable",
|
|
82
|
+
"pending: real-PLC scenario for an API from an open pull request; skipped until it is merged",
|
|
73
83
|
]
|
|
74
84
|
|
|
75
85
|
[tool.mypy]
|
|
@@ -16,7 +16,7 @@ Usage::
|
|
|
16
16
|
from .async_client import AsyncSubscriptionQueue
|
|
17
17
|
from .async_client import S7CommPlusAsyncClient as AsyncClient
|
|
18
18
|
from .alarm import Alarm, AlarmNotification, AlarmText, LanguageId
|
|
19
|
-
from .blob_decompressor import decompress_blob, find_and_decompress
|
|
19
|
+
from .blob_decompressor import PresetStream, decompress_blob, find_and_decompress, iter_preset_headers, iter_preset_streams
|
|
20
20
|
from .catalog import ArrayDimension, SymbolCatalog, SymbolicTag, TagResult
|
|
21
21
|
from .client import DBWriteItem, SymbolicReadItem
|
|
22
22
|
from .client import S7CommPlusClient as Client
|
|
@@ -36,6 +36,7 @@ from .tag_browser import (
|
|
|
36
36
|
datablocks_from_explore,
|
|
37
37
|
tags_from_explore,
|
|
38
38
|
)
|
|
39
|
+
from .zlib_dicts import PresetIdentity
|
|
39
40
|
|
|
40
41
|
__all__ = [
|
|
41
42
|
"Alarm",
|
|
@@ -52,6 +53,8 @@ __all__ = [
|
|
|
52
53
|
"ExploreDataBlock",
|
|
53
54
|
"LanguageId",
|
|
54
55
|
"Member",
|
|
56
|
+
"PresetIdentity",
|
|
57
|
+
"PresetStream",
|
|
55
58
|
"S7CommPlusConnection",
|
|
56
59
|
"Server",
|
|
57
60
|
"SubscriptionItem",
|
|
@@ -71,5 +74,7 @@ __all__ = [
|
|
|
71
74
|
"device_family",
|
|
72
75
|
"device_name",
|
|
73
76
|
"find_and_decompress",
|
|
77
|
+
"iter_preset_headers",
|
|
78
|
+
"iter_preset_streams",
|
|
74
79
|
"tags_from_explore",
|
|
75
80
|
]
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
"""Verify legacy S7-1500 V3 fragments without discarding integrity checks.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
A multi-fragment V3 response chains its fragments with HMAC-SHA256, but the
|
|
4
|
+
PLCs do not agree on the continuation dialect. Real S7-1200/1500 firmware
|
|
5
|
+
finalize, then reuse, both SHA-256 states of HMAC for each fragment
|
|
6
|
+
(*state-resume*); finalization mutates the chaining state but does not
|
|
7
|
+
increase the message byte count, and hashlib deliberately cannot resume a
|
|
8
|
+
finalized digest, so the small SHA-256 continuation below handles that wire
|
|
9
|
+
compatibility case. PLCSIM Advanced clears the HMAC key each time instead and
|
|
10
|
+
chains *feed-forward*: each later fragment's digest is a fresh HMAC over the
|
|
11
|
+
previous fragment's digest followed by this fragment's data,
|
|
12
|
+
``HMAC(key, digest_{n-1} || fragment_n)``. A response may also carry plain,
|
|
13
|
+
independent per-fragment HMACs. The dialect is fixed for the whole response
|
|
14
|
+
and is detected from the second fragment; a mismatch afterwards is rejected.
|
|
15
|
+
|
|
7
16
|
Each verifier is scoped to exactly one reassembled response.
|
|
8
17
|
|
|
9
18
|
This module is private, and is used only by the opt-in legacy S7-1500
|
|
@@ -139,10 +148,11 @@ def _continue_sha256(digest: bytes, data: bytes, total: int) -> bytes:
|
|
|
139
148
|
class FragmentHMACVerifier:
|
|
140
149
|
"""Authenticate fragments belonging to one V3 response.
|
|
141
150
|
|
|
142
|
-
The first fragment must
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
151
|
+
The first fragment must always carry a standard HMAC. The second picks the
|
|
152
|
+
continuation dialect among independent fragments, the state-resume
|
|
153
|
+
continuation, and the feed-forward chain, and the choice is then fixed for
|
|
154
|
+
the rest of the response. Return application data only after comparing the
|
|
155
|
+
full digest in constant time.
|
|
146
156
|
|
|
147
157
|
Construct a fresh verifier per response; never share it between sessions.
|
|
148
158
|
"""
|
|
@@ -155,15 +165,18 @@ class FragmentHMACVerifier:
|
|
|
155
165
|
self._outer: bytes | None = None
|
|
156
166
|
self._inner_count = 64
|
|
157
167
|
self._outer_count = 64
|
|
158
|
-
|
|
168
|
+
# The previous fragment's received digest, for the feed-forward dialect.
|
|
169
|
+
self._last = b""
|
|
170
|
+
self._mode: Literal["independent", "legacy", "feed-forward"] | None = None
|
|
159
171
|
|
|
160
172
|
def verify(self, protected: bytes) -> bytes:
|
|
161
173
|
if not protected or protected[0] != 32 or len(protected) < 33:
|
|
162
174
|
raise S7IntegrityError("Invalid V3 HMAC prefix")
|
|
163
175
|
received = protected[1:33]
|
|
164
176
|
data = protected[33:]
|
|
165
|
-
|
|
177
|
+
|
|
166
178
|
if self._inner is None:
|
|
179
|
+
independent = hmac.new(self._key, data, hashlib.sha256).digest()
|
|
167
180
|
if not hmac.compare_digest(received, independent):
|
|
168
181
|
raise S7IntegrityError("Invalid V3 HMAC")
|
|
169
182
|
ipad = bytes(value ^ 0x36 for value in self._key.ljust(64, b"\0"))
|
|
@@ -171,22 +184,49 @@ class FragmentHMACVerifier:
|
|
|
171
184
|
self._outer = independent
|
|
172
185
|
self._inner_count += len(data)
|
|
173
186
|
self._outer_count += 32
|
|
187
|
+
self._last = received
|
|
174
188
|
return data
|
|
175
189
|
|
|
176
|
-
if self._mode != "legacy" and hmac.compare_digest(received, independent):
|
|
177
|
-
self._mode = "independent"
|
|
178
|
-
return data
|
|
179
190
|
if self._mode == "independent":
|
|
180
|
-
|
|
191
|
+
independent = hmac.new(self._key, data, hashlib.sha256).digest()
|
|
192
|
+
if not hmac.compare_digest(received, independent):
|
|
193
|
+
raise S7IntegrityError("Invalid V3 continuation HMAC")
|
|
194
|
+
self._last = received
|
|
195
|
+
return data
|
|
196
|
+
|
|
197
|
+
if self._mode == "feed-forward":
|
|
198
|
+
expected = hmac.new(self._key, self._last + data, hashlib.sha256).digest()
|
|
199
|
+
if not hmac.compare_digest(received, expected):
|
|
200
|
+
raise S7IntegrityError("Invalid V3 continuation HMAC")
|
|
201
|
+
self._last = received
|
|
202
|
+
return data
|
|
181
203
|
|
|
182
204
|
assert self._outer is not None
|
|
183
205
|
inner_count = self._inner_count + len(data)
|
|
184
206
|
outer_count = self._outer_count + 32
|
|
185
207
|
inner = _continue_sha256(self._inner, data, inner_count)
|
|
186
208
|
outer = _continue_sha256(self._outer, inner, outer_count)
|
|
187
|
-
|
|
209
|
+
|
|
210
|
+
if self._mode == "legacy":
|
|
211
|
+
if not hmac.compare_digest(received, outer):
|
|
212
|
+
raise S7IntegrityError("Invalid V3 continuation HMAC")
|
|
213
|
+
self._inner, self._outer = inner, outer
|
|
214
|
+
self._inner_count, self._outer_count = inner_count, outer_count
|
|
215
|
+
self._last = received
|
|
216
|
+
return data
|
|
217
|
+
|
|
218
|
+
# Second fragment: detect the dialect and fix it for the rest of the response.
|
|
219
|
+
independent = hmac.new(self._key, data, hashlib.sha256).digest()
|
|
220
|
+
feed_forward = hmac.new(self._key, self._last + data, hashlib.sha256).digest()
|
|
221
|
+
if hmac.compare_digest(received, independent):
|
|
222
|
+
self._mode = "independent"
|
|
223
|
+
elif hmac.compare_digest(received, outer):
|
|
224
|
+
self._mode = "legacy"
|
|
225
|
+
self._inner, self._outer = inner, outer
|
|
226
|
+
self._inner_count, self._outer_count = inner_count, outer_count
|
|
227
|
+
elif hmac.compare_digest(received, feed_forward):
|
|
228
|
+
self._mode = "feed-forward"
|
|
229
|
+
else:
|
|
188
230
|
raise S7IntegrityError("Invalid V3 continuation HMAC")
|
|
189
|
-
self.
|
|
190
|
-
self._inner, self._outer = inner, outer
|
|
191
|
-
self._inner_count, self._outer_count = inner_count, outer_count
|
|
231
|
+
self._last = received
|
|
192
232
|
return data
|
|
@@ -9,7 +9,7 @@ from dataclasses import dataclass, field
|
|
|
9
9
|
from enum import IntEnum
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
-
from .codec import encode_object_qualifier
|
|
12
|
+
from .codec import encode_object_qualifier, parse_or_raise_for_return_value
|
|
13
13
|
from .protocol import DataType, ElementID, Ids, Opcode
|
|
14
14
|
from .vlq import decode_int32_vlq, decode_int64_vlq, decode_uint32_vlq, decode_uint64_vlq, encode_uint32_vlq
|
|
15
15
|
|
|
@@ -488,10 +488,12 @@ def _alarm_from_object(obj: _Object, language_ids: set[LanguageId | int] | None)
|
|
|
488
488
|
|
|
489
489
|
|
|
490
490
|
def parse_alarm_explore_response(response: bytes, language_ids: Sequence[LanguageId | int] | None = None) -> list[Alarm]:
|
|
491
|
-
"""Parse the payload returned by an alarm-subsystem EXPLORE request.
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
491
|
+
"""Parse the payload returned by an alarm-subsystem EXPLORE request.
|
|
492
|
+
|
|
493
|
+
Raises:
|
|
494
|
+
S7ReturnValueError: If the ReturnValue is non-zero.
|
|
495
|
+
"""
|
|
496
|
+
offset = parse_or_raise_for_return_value(response, "Alarm browse")
|
|
495
497
|
if offset + 4 > len(response):
|
|
496
498
|
raise ValueError("Alarm browse response is truncated")
|
|
497
499
|
offset += 4 # ExploreId
|