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.
Files changed (222) hide show
  1. {s7commplus-0.2.0/s7commplus.egg-info → s7commplus-0.3.0}/PKG-INFO +13 -4
  2. {s7commplus-0.2.0 → s7commplus-0.3.0}/README.md +11 -2
  3. {s7commplus-0.2.0 → s7commplus-0.3.0}/pyproject.toml +12 -2
  4. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/__init__.py +6 -1
  5. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/_fragment_hmac.py +58 -18
  6. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/alarm.py +7 -5
  7. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/async_client.py +517 -95
  8. s7commplus-0.3.0/s7commplus/blob_decompressor.py +188 -0
  9. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/catalog.py +30 -35
  10. s7commplus-0.3.0/s7commplus/cli/__init__.py +129 -0
  11. s7commplus-0.3.0/s7commplus/cli/__main__.py +6 -0
  12. s7commplus-0.3.0/s7commplus/cli/_common.py +323 -0
  13. s7commplus-0.3.0/s7commplus/cli/browse.py +27 -0
  14. s7commplus-0.3.0/s7commplus/cli/db_read.py +34 -0
  15. s7commplus-0.3.0/s7commplus/cli/db_write.py +34 -0
  16. s7commplus-0.3.0/s7commplus/cli/read.py +48 -0
  17. s7commplus-0.3.0/s7commplus/cli/state.py +21 -0
  18. s7commplus-0.3.0/s7commplus/cli/write.py +62 -0
  19. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/client.py +734 -88
  20. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/codec.py +28 -1
  21. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/connection.py +388 -96
  22. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/error.py +90 -0
  23. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/legitimation.py +92 -49
  24. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/protocol.py +32 -10
  25. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/server.py +157 -23
  26. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/subscription.py +8 -2
  27. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/tag_browser.py +33 -24
  28. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/typeinfo.py +38 -0
  29. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/ARCHITECTURE.md +62 -5
  30. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/blob_metadata.py +24 -0
  31. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/keys.py +3 -0
  32. s7commplus-0.3.0/s7commplus/v1_session_key/legitimation.py +203 -0
  33. s7commplus-0.3.0/s7commplus/values.py +561 -0
  34. s7commplus-0.3.0/s7commplus/zlib_dicts/__init__.py +57 -0
  35. {s7commplus-0.2.0 → s7commplus-0.3.0/s7commplus.egg-info}/PKG-INFO +13 -4
  36. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus.egg-info/SOURCES.txt +23 -0
  37. s7commplus-0.3.0/s7commplus.egg-info/entry_points.txt +2 -0
  38. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus.egg-info/requires.txt +1 -1
  39. s7commplus-0.3.0/tests/test_blob_decompressor.py +133 -0
  40. s7commplus-0.3.0/tests/test_cli.py +792 -0
  41. s7commplus-0.3.0/tests/test_free_tcp_port.py +39 -0
  42. s7commplus-0.3.0/tests/test_no_fixed_test_ports.py +59 -0
  43. s7commplus-0.3.0/tests/test_real_plc_dry_run.py +118 -0
  44. s7commplus-0.3.0/tests/test_run_real_plc_acceptance.py +55 -0
  45. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_alarm.py +41 -0
  46. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_async_session_key.py +12 -0
  47. s7commplus-0.3.0/tests/test_s7_auto_tls.py +290 -0
  48. s7commplus-0.3.0/tests/test_s7_cert_pin.py +152 -0
  49. s7commplus-0.3.0/tests/test_s7_chunking.py +701 -0
  50. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_codec.py +47 -0
  51. s7commplus-0.3.0/tests/test_s7_connect_signature.py +47 -0
  52. s7commplus-0.3.0/tests/test_s7_error_pickle.py +80 -0
  53. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_fragment_hmac.py +42 -0
  54. s7commplus-0.3.0/tests/test_s7_fragmented_response.py +288 -0
  55. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_legacy_request_layout.py +2 -1
  56. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_server.py +139 -27
  57. s7commplus-0.3.0/tests/test_s7_stale_tags.py +632 -0
  58. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_subscription.py +2 -1
  59. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_tag_catalog.py +6 -6
  60. s7commplus-0.3.0/tests/test_s7_tls.py +846 -0
  61. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_unit.py +196 -1
  62. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_v2.py +319 -0
  63. s7commplus-0.3.0/tests/test_s7_values.py +405 -0
  64. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_tag_browser.py +133 -0
  65. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_legitimation.py +87 -0
  66. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_plcsim.py +165 -9
  67. s7commplus-0.2.0/s7commplus/blob_decompressor.py +0 -89
  68. s7commplus-0.2.0/s7commplus/v1_session_key/legitimation.py +0 -97
  69. s7commplus-0.2.0/s7commplus/zlib_dicts/__init__.py +0 -28
  70. s7commplus-0.2.0/tests/test_blob_decompressor.py +0 -71
  71. s7commplus-0.2.0/tests/test_run_real_plc_acceptance.py +0 -24
  72. s7commplus-0.2.0/tests/test_s7_tls.py +0 -445
  73. {s7commplus-0.2.0 → s7commplus-0.3.0}/LICENSE +0 -0
  74. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/devices.py +0 -0
  75. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/object_model.py +0 -0
  76. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/py.typed +0 -0
  77. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/transport.py +0 -0
  78. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/LICENSE-HarpoS7 +0 -0
  79. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/MAINTAINER_GUIDE.md +0 -0
  80. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/MODEL_BENCHMARKS.md +0 -0
  81. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/__init__.py +0 -0
  82. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/aes_ecb.py +0 -0
  83. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/aes_gcm.py +0 -0
  84. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/ghash.py +0 -0
  85. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/handshake.py +0 -0
  86. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/key_derivation.py +0 -0
  87. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/plcsim/__init__.py +0 -0
  88. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/plcsim/authenticator.py +0 -0
  89. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/plcsim/seed.py +0 -0
  90. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/real_plc/__init__.py +0 -0
  91. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/real_plc/authenticator.py +0 -0
  92. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/real_plc/checksum.py +0 -0
  93. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/real_plc/curve.py +0 -0
  94. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/real_plc/fingerprint.py +0 -0
  95. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/real_plc/present.py +0 -0
  96. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/real_plc/seed.py +0 -0
  97. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/v1_session_key/utils.py +0 -0
  98. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/vlq.py +0 -0
  99. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/1398a37f_CompilerSettings_90000001.xml +0 -0
  100. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/1bac39f0_DebugInfo_90000001.xml +0 -0
  101. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/3c55436a_LineComm_98000001.xml +0 -0
  102. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/4b8416f0_IntfDesc_90000001.xml +0 -0
  103. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/5814b03b_IdentES_98000001.xml +0 -0
  104. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/66052b13_DebugInfo_IntfDesc_98000001.xml +0 -0
  105. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/79b2bda3_LineComm_90000001.xml +0 -0
  106. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/81d8db20_IdentES_90000002.xml +0 -0
  107. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/845fc605_NWT_98000001.xml +0 -0
  108. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/9b6a3a92_ExtRefData_90000001.xml +0 -0
  109. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/ab6fa31e_NWC_90000001.xml +0 -0
  110. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/b0155ff8_IntRefData_98000001.xml +0 -0
  111. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/c5d26ac3_NWC_98000001.xml +0 -0
  112. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/ce9b821b_IntfDescTag_90000001.xml +0 -0
  113. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/da4a88f4_IntRefData_90000001.xml +0 -0
  114. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/df91b6bb_IdentES_90000001.xml +0 -0
  115. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/e2729ea1_TagLineComm_90000001.xml +0 -0
  116. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/efaeae49_BodyDesc_90000001.xml +0 -0
  117. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus/zlib_dicts/fd69ac74_NWT_90000001.xml +0 -0
  118. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus.egg-info/dependency_links.txt +0 -0
  119. {s7commplus-0.2.0 → s7commplus-0.3.0}/s7commplus.egg-info/top_level.txt +0 -0
  120. {s7commplus-0.2.0 → s7commplus-0.3.0}/setup.cfg +0 -0
  121. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_golden_tia_captures.py +0 -0
  122. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_real_plc_support.py +0 -0
  123. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_attribute_flags.py +0 -0
  124. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_connection_type.py +0 -0
  125. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_devices.py +0 -0
  126. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_e2e.py +0 -0
  127. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_family_key_fallback.py +0 -0
  128. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_invoke_methods.py +0 -0
  129. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_legacy_browse.py +0 -0
  130. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_object_model.py +0 -0
  131. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_operating_state.py +0 -0
  132. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_real_s7_1200_handshake.py +0 -0
  133. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_response_values.py +0 -0
  134. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_resubscribe.py +0 -0
  135. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_secured_session.py +0 -0
  136. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_session_oms.py +0 -0
  137. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_symbolic_multi_errors.py +0 -0
  138. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_vlq.py +0 -0
  139. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_s7_write_types.py +0 -0
  140. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_typeinfo.py +0 -0
  141. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key.py +0 -0
  142. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_aes_ecb.py +0 -0
  143. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_aes_gcm.py +0 -0
  144. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_artifacts.py +0 -0
  145. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_bit_trace.py +0 -0
  146. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_bitwise_analysis.py +0 -0
  147. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_blob_metadata.py +0 -0
  148. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_boolean_decomposition.py +0 -0
  149. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_curve.py +0 -0
  150. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_evidence.py +0 -0
  151. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_family0_big_int.py +0 -0
  152. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_family0_big_int_transforms.py +0 -0
  153. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_family0_monoliths.py +0 -0
  154. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_family0_transforms.py +0 -0
  155. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_fingerprint.py +0 -0
  156. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_ghash.py +0 -0
  157. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_handshake.py +0 -0
  158. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_key_derivation.py +0 -0
  159. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_model_benchmarks.py +0 -0
  160. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith11_compact.py +0 -0
  161. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith3_span_identity.py +0 -0
  162. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith4_carry_stages.py +0 -0
  163. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith4_span_identity.py +0 -0
  164. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith5_compact.py +0 -0
  165. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith5_gates.py +0 -0
  166. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith5_model.py +0 -0
  167. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith5_span_decoder.py +0 -0
  168. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith6_span_identity.py +0 -0
  169. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith7_analysis.py +0 -0
  170. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith7_full_analysis.py +0 -0
  171. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith7_middle_analysis.py +0 -0
  172. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_monolith_map.py +0 -0
  173. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_output_trace.py +0 -0
  174. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_addition_shortcuts.py +0 -0
  175. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_cancel_macros.py +0 -0
  176. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_compiled_stage.py +0 -0
  177. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_constant_lifts.py +0 -0
  178. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_correction_dag.py +0 -0
  179. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_correction_domains.py +0 -0
  180. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_curve.py +0 -0
  181. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_demand_guards.py +0 -0
  182. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_exclusive_corrections.py +0 -0
  183. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_factored_stage.py +0 -0
  184. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_field_circuit.py +0 -0
  185. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_first_defect.py +0 -0
  186. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_ladder.py +0 -0
  187. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_lazy_guards.py +0 -0
  188. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_lift_categories.py +0 -0
  189. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_predicate_dag.py +0 -0
  190. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_predictor.py +0 -0
  191. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_product_shortcuts.py +0 -0
  192. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_raw_bounds.py +0 -0
  193. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_relation_ladder.py +0 -0
  194. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_representatives.py +0 -0
  195. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_saturated_history.py +0 -0
  196. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_seed_boundary.py +0 -0
  197. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_semiring_rewrites.py +0 -0
  198. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_shift_macros.py +0 -0
  199. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_small_product_shortcuts.py +0 -0
  200. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_square_shortcuts.py +0 -0
  201. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_stage_algebra.py +0 -0
  202. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_stage_plan.py +0 -0
  203. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_structural_guards.py +0 -0
  204. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_subtraction_shortcuts.py +0 -0
  205. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_scalar_transport.py +0 -0
  206. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_seed.py +0 -0
  207. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_setup_integer.py +0 -0
  208. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_setup_invariant.py +0 -0
  209. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform12_analysis.py +0 -0
  210. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform12_compact.py +0 -0
  211. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform12_formulas.py +0 -0
  212. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform12_integer_model.py +0 -0
  213. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform12_phase1.py +0 -0
  214. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform7_compact.py +0 -0
  215. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform7_reference.py +0 -0
  216. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform7_setup_corrections.py +0 -0
  217. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform7_setup_encoding.py +0 -0
  218. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform7_setup_merge.py +0 -0
  219. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform7_setup_ranges.py +0 -0
  220. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform7_setup_shadows.py +0 -0
  221. {s7commplus-0.2.0 → s7commplus-0.3.0}/tests/test_v1_session_key_transform7_tail_trace.py +0 -0
  222. {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.2.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,>=8.1; extra == "test"
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) and an
49
- S7-1500 (sync client); PLCSIM support is so far emulator-tested.
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) and an
7
- S7-1500 (sync client); PLCSIM support is so far emulator-tested.
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.2.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>=8.1,<9", "pytest-cov", "hypothesis", "mypy", "ruff"]
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
- Older PLCs finalize, then reuse, both SHA-256 states of HMAC for each
4
- fragment. Finalization mutates the chaining state but does not increase the
5
- message byte count. hashlib deliberately cannot resume a finalized digest,
6
- so the small SHA-256 continuation below handles this wire compatibility case.
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 have a standard HMAC. The second chooses between
143
- independent fragment HMACs and the legacy finalized-state continuation.
144
- The chosen mode cannot change within a response. Return application data
145
- only after comparing the full digest in constant time.
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
- self._mode: Literal["independent", "legacy"] | None = None
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
- independent = hmac.new(self._key, data, hashlib.sha256).digest()
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
- raise S7IntegrityError("Invalid V3 HMAC")
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
- if not hmac.compare_digest(received, outer):
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._mode = "legacy"
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
- return_value, offset = _read_vlq64(response, 0)
493
- if return_value != 0:
494
- raise RuntimeError(f"Alarm browse failed: PLC returned {return_value:#x}")
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