oscura 0.5.0__py3-none-any.whl → 0.6.0__py3-none-any.whl

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 (513) hide show
  1. oscura/__init__.py +169 -167
  2. oscura/analyzers/__init__.py +3 -0
  3. oscura/analyzers/classification.py +659 -0
  4. oscura/analyzers/digital/__init__.py +0 -48
  5. oscura/analyzers/digital/edges.py +325 -65
  6. oscura/analyzers/digital/extraction.py +0 -195
  7. oscura/analyzers/digital/quality.py +293 -166
  8. oscura/analyzers/digital/timing.py +260 -115
  9. oscura/analyzers/digital/timing_numba.py +334 -0
  10. oscura/analyzers/entropy.py +605 -0
  11. oscura/analyzers/eye/diagram.py +176 -109
  12. oscura/analyzers/eye/metrics.py +5 -5
  13. oscura/analyzers/jitter/__init__.py +6 -4
  14. oscura/analyzers/jitter/ber.py +52 -52
  15. oscura/analyzers/jitter/classification.py +156 -0
  16. oscura/analyzers/jitter/decomposition.py +163 -113
  17. oscura/analyzers/jitter/spectrum.py +80 -64
  18. oscura/analyzers/ml/__init__.py +39 -0
  19. oscura/analyzers/ml/features.py +600 -0
  20. oscura/analyzers/ml/signal_classifier.py +604 -0
  21. oscura/analyzers/packet/daq.py +246 -158
  22. oscura/analyzers/packet/parser.py +12 -1
  23. oscura/analyzers/packet/payload.py +50 -2110
  24. oscura/analyzers/packet/payload_analysis.py +361 -181
  25. oscura/analyzers/packet/payload_patterns.py +133 -70
  26. oscura/analyzers/packet/stream.py +84 -23
  27. oscura/analyzers/patterns/__init__.py +26 -5
  28. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  29. oscura/analyzers/patterns/clustering.py +169 -108
  30. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  31. oscura/analyzers/patterns/discovery.py +1 -1
  32. oscura/analyzers/patterns/matching.py +581 -197
  33. oscura/analyzers/patterns/pattern_mining.py +778 -0
  34. oscura/analyzers/patterns/periodic.py +121 -38
  35. oscura/analyzers/patterns/sequences.py +175 -78
  36. oscura/analyzers/power/conduction.py +1 -1
  37. oscura/analyzers/power/soa.py +6 -6
  38. oscura/analyzers/power/switching.py +250 -110
  39. oscura/analyzers/protocol/__init__.py +17 -1
  40. oscura/analyzers/protocols/__init__.py +1 -22
  41. oscura/analyzers/protocols/base.py +6 -6
  42. oscura/analyzers/protocols/ble/__init__.py +38 -0
  43. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  44. oscura/analyzers/protocols/ble/uuids.py +288 -0
  45. oscura/analyzers/protocols/can.py +257 -127
  46. oscura/analyzers/protocols/can_fd.py +107 -80
  47. oscura/analyzers/protocols/flexray.py +139 -80
  48. oscura/analyzers/protocols/hdlc.py +93 -58
  49. oscura/analyzers/protocols/i2c.py +247 -106
  50. oscura/analyzers/protocols/i2s.py +138 -86
  51. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  52. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  53. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  54. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  55. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  56. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  57. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  58. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  59. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  60. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  61. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  62. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  63. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  64. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  65. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  66. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  67. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  68. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  69. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  70. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  71. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  72. oscura/analyzers/protocols/jtag.py +180 -98
  73. oscura/analyzers/protocols/lin.py +219 -114
  74. oscura/analyzers/protocols/manchester.py +4 -4
  75. oscura/analyzers/protocols/onewire.py +253 -149
  76. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  77. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  78. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  79. oscura/analyzers/protocols/spi.py +192 -95
  80. oscura/analyzers/protocols/swd.py +321 -167
  81. oscura/analyzers/protocols/uart.py +267 -125
  82. oscura/analyzers/protocols/usb.py +235 -131
  83. oscura/analyzers/side_channel/power.py +17 -12
  84. oscura/analyzers/signal/__init__.py +15 -0
  85. oscura/analyzers/signal/timing_analysis.py +1086 -0
  86. oscura/analyzers/signal_integrity/__init__.py +4 -1
  87. oscura/analyzers/signal_integrity/sparams.py +2 -19
  88. oscura/analyzers/spectral/chunked.py +129 -60
  89. oscura/analyzers/spectral/chunked_fft.py +300 -94
  90. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  91. oscura/analyzers/statistical/checksum.py +376 -217
  92. oscura/analyzers/statistical/classification.py +229 -107
  93. oscura/analyzers/statistical/entropy.py +78 -53
  94. oscura/analyzers/statistics/correlation.py +407 -211
  95. oscura/analyzers/statistics/outliers.py +2 -2
  96. oscura/analyzers/statistics/streaming.py +30 -5
  97. oscura/analyzers/validation.py +216 -101
  98. oscura/analyzers/waveform/measurements.py +9 -0
  99. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  100. oscura/analyzers/waveform/spectral.py +500 -228
  101. oscura/api/__init__.py +31 -5
  102. oscura/api/dsl/__init__.py +582 -0
  103. oscura/{dsl → api/dsl}/commands.py +43 -76
  104. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  105. oscura/{dsl → api/dsl}/parser.py +107 -77
  106. oscura/{dsl → api/dsl}/repl.py +2 -2
  107. oscura/api/dsl.py +1 -1
  108. oscura/{integrations → api/integrations}/__init__.py +1 -1
  109. oscura/{integrations → api/integrations}/llm.py +201 -102
  110. oscura/api/operators.py +3 -3
  111. oscura/api/optimization.py +144 -30
  112. oscura/api/rest_server.py +921 -0
  113. oscura/api/server/__init__.py +17 -0
  114. oscura/api/server/dashboard.py +850 -0
  115. oscura/api/server/static/README.md +34 -0
  116. oscura/api/server/templates/base.html +181 -0
  117. oscura/api/server/templates/export.html +120 -0
  118. oscura/api/server/templates/home.html +284 -0
  119. oscura/api/server/templates/protocols.html +58 -0
  120. oscura/api/server/templates/reports.html +43 -0
  121. oscura/api/server/templates/session_detail.html +89 -0
  122. oscura/api/server/templates/sessions.html +83 -0
  123. oscura/api/server/templates/waveforms.html +73 -0
  124. oscura/automotive/__init__.py +8 -1
  125. oscura/automotive/can/__init__.py +10 -0
  126. oscura/automotive/can/checksum.py +3 -1
  127. oscura/automotive/can/dbc_generator.py +590 -0
  128. oscura/automotive/can/message_wrapper.py +121 -74
  129. oscura/automotive/can/patterns.py +98 -21
  130. oscura/automotive/can/session.py +292 -56
  131. oscura/automotive/can/state_machine.py +6 -3
  132. oscura/automotive/can/stimulus_response.py +97 -75
  133. oscura/automotive/dbc/__init__.py +10 -2
  134. oscura/automotive/dbc/generator.py +84 -56
  135. oscura/automotive/dbc/parser.py +6 -6
  136. oscura/automotive/dtc/data.json +2763 -0
  137. oscura/automotive/dtc/database.py +2 -2
  138. oscura/automotive/flexray/__init__.py +31 -0
  139. oscura/automotive/flexray/analyzer.py +504 -0
  140. oscura/automotive/flexray/crc.py +185 -0
  141. oscura/automotive/flexray/fibex.py +449 -0
  142. oscura/automotive/j1939/__init__.py +45 -8
  143. oscura/automotive/j1939/analyzer.py +605 -0
  144. oscura/automotive/j1939/spns.py +326 -0
  145. oscura/automotive/j1939/transport.py +306 -0
  146. oscura/automotive/lin/__init__.py +47 -0
  147. oscura/automotive/lin/analyzer.py +612 -0
  148. oscura/automotive/loaders/blf.py +13 -2
  149. oscura/automotive/loaders/csv_can.py +143 -72
  150. oscura/automotive/loaders/dispatcher.py +50 -2
  151. oscura/automotive/loaders/mdf.py +86 -45
  152. oscura/automotive/loaders/pcap.py +111 -61
  153. oscura/automotive/uds/__init__.py +4 -0
  154. oscura/automotive/uds/analyzer.py +725 -0
  155. oscura/automotive/uds/decoder.py +140 -58
  156. oscura/automotive/uds/models.py +7 -1
  157. oscura/automotive/visualization.py +1 -1
  158. oscura/cli/analyze.py +348 -0
  159. oscura/cli/batch.py +142 -122
  160. oscura/cli/benchmark.py +275 -0
  161. oscura/cli/characterize.py +137 -82
  162. oscura/cli/compare.py +224 -131
  163. oscura/cli/completion.py +250 -0
  164. oscura/cli/config_cmd.py +361 -0
  165. oscura/cli/decode.py +164 -87
  166. oscura/cli/export.py +286 -0
  167. oscura/cli/main.py +115 -31
  168. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  169. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  170. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  171. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  172. oscura/cli/progress.py +147 -0
  173. oscura/cli/shell.py +157 -135
  174. oscura/cli/validate_cmd.py +204 -0
  175. oscura/cli/visualize.py +158 -0
  176. oscura/convenience.py +125 -79
  177. oscura/core/__init__.py +4 -2
  178. oscura/core/backend_selector.py +3 -3
  179. oscura/core/cache.py +126 -15
  180. oscura/core/cancellation.py +1 -1
  181. oscura/{config → core/config}/__init__.py +20 -11
  182. oscura/{config → core/config}/defaults.py +1 -1
  183. oscura/{config → core/config}/loader.py +7 -5
  184. oscura/{config → core/config}/memory.py +5 -5
  185. oscura/{config → core/config}/migration.py +1 -1
  186. oscura/{config → core/config}/pipeline.py +99 -23
  187. oscura/{config → core/config}/preferences.py +1 -1
  188. oscura/{config → core/config}/protocol.py +3 -3
  189. oscura/{config → core/config}/schema.py +426 -272
  190. oscura/{config → core/config}/settings.py +1 -1
  191. oscura/{config → core/config}/thresholds.py +195 -153
  192. oscura/core/correlation.py +5 -6
  193. oscura/core/cross_domain.py +0 -2
  194. oscura/core/debug.py +9 -5
  195. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  196. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  197. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  198. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  199. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  200. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  201. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  202. oscura/core/gpu_backend.py +11 -7
  203. oscura/core/log_query.py +101 -11
  204. oscura/core/logging.py +126 -54
  205. oscura/core/logging_advanced.py +5 -5
  206. oscura/core/memory_limits.py +108 -70
  207. oscura/core/memory_monitor.py +2 -2
  208. oscura/core/memory_progress.py +7 -7
  209. oscura/core/memory_warnings.py +1 -1
  210. oscura/core/numba_backend.py +13 -13
  211. oscura/{plugins → core/plugins}/__init__.py +9 -9
  212. oscura/{plugins → core/plugins}/base.py +7 -7
  213. oscura/{plugins → core/plugins}/cli.py +3 -3
  214. oscura/{plugins → core/plugins}/discovery.py +186 -106
  215. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  216. oscura/{plugins → core/plugins}/manager.py +7 -7
  217. oscura/{plugins → core/plugins}/registry.py +3 -3
  218. oscura/{plugins → core/plugins}/versioning.py +1 -1
  219. oscura/core/progress.py +16 -1
  220. oscura/core/provenance.py +8 -2
  221. oscura/{schemas → core/schemas}/__init__.py +2 -2
  222. oscura/core/schemas/bus_configuration.json +322 -0
  223. oscura/core/schemas/device_mapping.json +182 -0
  224. oscura/core/schemas/packet_format.json +418 -0
  225. oscura/core/schemas/protocol_definition.json +363 -0
  226. oscura/core/types.py +4 -0
  227. oscura/core/uncertainty.py +3 -3
  228. oscura/correlation/__init__.py +52 -0
  229. oscura/correlation/multi_protocol.py +811 -0
  230. oscura/discovery/auto_decoder.py +117 -35
  231. oscura/discovery/comparison.py +191 -86
  232. oscura/discovery/quality_validator.py +155 -68
  233. oscura/discovery/signal_detector.py +196 -79
  234. oscura/export/__init__.py +18 -20
  235. oscura/export/kaitai_struct.py +513 -0
  236. oscura/export/scapy_layer.py +801 -0
  237. oscura/export/wireshark/README.md +15 -15
  238. oscura/export/wireshark/generator.py +1 -1
  239. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  240. oscura/export/wireshark_dissector.py +746 -0
  241. oscura/guidance/wizard.py +207 -111
  242. oscura/hardware/__init__.py +19 -0
  243. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  244. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  245. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  246. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  247. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  248. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  249. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  250. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  251. oscura/hardware/firmware/__init__.py +29 -0
  252. oscura/hardware/firmware/pattern_recognition.py +874 -0
  253. oscura/hardware/hal_detector.py +736 -0
  254. oscura/hardware/security/__init__.py +37 -0
  255. oscura/hardware/security/side_channel_detector.py +1126 -0
  256. oscura/inference/__init__.py +4 -0
  257. oscura/inference/active_learning/README.md +7 -7
  258. oscura/inference/active_learning/observation_table.py +4 -1
  259. oscura/inference/alignment.py +216 -123
  260. oscura/inference/bayesian.py +113 -33
  261. oscura/inference/crc_reverse.py +101 -55
  262. oscura/inference/logic.py +6 -2
  263. oscura/inference/message_format.py +342 -183
  264. oscura/inference/protocol.py +95 -44
  265. oscura/inference/protocol_dsl.py +180 -82
  266. oscura/inference/signal_intelligence.py +1439 -706
  267. oscura/inference/spectral.py +99 -57
  268. oscura/inference/state_machine.py +810 -158
  269. oscura/inference/stream.py +270 -110
  270. oscura/iot/__init__.py +34 -0
  271. oscura/iot/coap/__init__.py +32 -0
  272. oscura/iot/coap/analyzer.py +668 -0
  273. oscura/iot/coap/options.py +212 -0
  274. oscura/iot/lorawan/__init__.py +21 -0
  275. oscura/iot/lorawan/crypto.py +206 -0
  276. oscura/iot/lorawan/decoder.py +801 -0
  277. oscura/iot/lorawan/mac_commands.py +341 -0
  278. oscura/iot/mqtt/__init__.py +27 -0
  279. oscura/iot/mqtt/analyzer.py +999 -0
  280. oscura/iot/mqtt/properties.py +315 -0
  281. oscura/iot/zigbee/__init__.py +31 -0
  282. oscura/iot/zigbee/analyzer.py +615 -0
  283. oscura/iot/zigbee/security.py +153 -0
  284. oscura/iot/zigbee/zcl.py +349 -0
  285. oscura/jupyter/display.py +125 -45
  286. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  287. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  288. oscura/jupyter/exploratory/fuzzy.py +746 -0
  289. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  290. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  291. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  292. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  293. oscura/jupyter/exploratory/sync.py +612 -0
  294. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  295. oscura/jupyter/magic.py +4 -4
  296. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  297. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  298. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  299. oscura/loaders/__init__.py +171 -63
  300. oscura/loaders/binary.py +88 -1
  301. oscura/loaders/chipwhisperer.py +153 -137
  302. oscura/loaders/configurable.py +208 -86
  303. oscura/loaders/csv_loader.py +458 -215
  304. oscura/loaders/hdf5_loader.py +278 -119
  305. oscura/loaders/lazy.py +87 -54
  306. oscura/loaders/mmap_loader.py +1 -1
  307. oscura/loaders/numpy_loader.py +253 -116
  308. oscura/loaders/pcap.py +226 -151
  309. oscura/loaders/rigol.py +110 -49
  310. oscura/loaders/sigrok.py +201 -78
  311. oscura/loaders/tdms.py +81 -58
  312. oscura/loaders/tektronix.py +291 -174
  313. oscura/loaders/touchstone.py +182 -87
  314. oscura/loaders/vcd.py +215 -117
  315. oscura/loaders/wav.py +155 -68
  316. oscura/reporting/__init__.py +9 -7
  317. oscura/reporting/analyze.py +352 -146
  318. oscura/reporting/argument_preparer.py +69 -14
  319. oscura/reporting/auto_report.py +97 -61
  320. oscura/reporting/batch.py +131 -58
  321. oscura/reporting/chart_selection.py +57 -45
  322. oscura/reporting/comparison.py +63 -17
  323. oscura/reporting/content/executive.py +76 -24
  324. oscura/reporting/core_formats/multi_format.py +11 -8
  325. oscura/reporting/engine.py +312 -158
  326. oscura/reporting/enhanced_reports.py +949 -0
  327. oscura/reporting/export.py +86 -43
  328. oscura/reporting/formatting/numbers.py +69 -42
  329. oscura/reporting/html.py +139 -58
  330. oscura/reporting/index.py +137 -65
  331. oscura/reporting/output.py +158 -67
  332. oscura/reporting/pdf.py +67 -102
  333. oscura/reporting/plots.py +191 -112
  334. oscura/reporting/sections.py +88 -47
  335. oscura/reporting/standards.py +104 -61
  336. oscura/reporting/summary_generator.py +75 -55
  337. oscura/reporting/tables.py +138 -54
  338. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  339. oscura/reporting/templates/index.md +13 -13
  340. oscura/sessions/__init__.py +14 -23
  341. oscura/sessions/base.py +3 -3
  342. oscura/sessions/blackbox.py +106 -10
  343. oscura/sessions/generic.py +2 -2
  344. oscura/sessions/legacy.py +783 -0
  345. oscura/side_channel/__init__.py +63 -0
  346. oscura/side_channel/dpa.py +1025 -0
  347. oscura/utils/__init__.py +15 -1
  348. oscura/utils/autodetect.py +1 -5
  349. oscura/utils/bitwise.py +118 -0
  350. oscura/{builders → utils/builders}/__init__.py +1 -1
  351. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  352. oscura/{comparison → utils/comparison}/compare.py +202 -101
  353. oscura/{comparison → utils/comparison}/golden.py +83 -63
  354. oscura/{comparison → utils/comparison}/limits.py +313 -89
  355. oscura/{comparison → utils/comparison}/mask.py +151 -45
  356. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  357. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  358. oscura/{component → utils/component}/__init__.py +3 -3
  359. oscura/{component → utils/component}/impedance.py +122 -58
  360. oscura/{component → utils/component}/reactive.py +165 -168
  361. oscura/{component → utils/component}/transmission_line.py +3 -3
  362. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  363. oscura/{filtering → utils/filtering}/base.py +1 -1
  364. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  365. oscura/{filtering → utils/filtering}/design.py +169 -93
  366. oscura/{filtering → utils/filtering}/filters.py +2 -2
  367. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  368. oscura/utils/geometry.py +31 -0
  369. oscura/utils/imports.py +184 -0
  370. oscura/utils/lazy.py +1 -1
  371. oscura/{math → utils/math}/__init__.py +2 -2
  372. oscura/{math → utils/math}/arithmetic.py +114 -48
  373. oscura/{math → utils/math}/interpolation.py +139 -106
  374. oscura/utils/memory.py +129 -66
  375. oscura/utils/memory_advanced.py +92 -9
  376. oscura/utils/memory_extensions.py +10 -8
  377. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  378. oscura/{optimization → utils/optimization}/search.py +2 -2
  379. oscura/utils/performance/__init__.py +58 -0
  380. oscura/utils/performance/caching.py +889 -0
  381. oscura/utils/performance/lsh_clustering.py +333 -0
  382. oscura/utils/performance/memory_optimizer.py +699 -0
  383. oscura/utils/performance/optimizations.py +675 -0
  384. oscura/utils/performance/parallel.py +654 -0
  385. oscura/utils/performance/profiling.py +661 -0
  386. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  387. oscura/{pipeline → utils/pipeline}/composition.py +11 -3
  388. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  389. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  390. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  391. oscura/{search → utils/search}/__init__.py +3 -3
  392. oscura/{search → utils/search}/anomaly.py +188 -58
  393. oscura/utils/search/context.py +294 -0
  394. oscura/{search → utils/search}/pattern.py +138 -10
  395. oscura/utils/serial.py +51 -0
  396. oscura/utils/storage/__init__.py +61 -0
  397. oscura/utils/storage/database.py +1166 -0
  398. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  399. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  400. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  401. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  402. oscura/{triggering → utils/triggering}/base.py +6 -6
  403. oscura/{triggering → utils/triggering}/edge.py +2 -2
  404. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  405. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  406. oscura/{triggering → utils/triggering}/window.py +2 -2
  407. oscura/utils/validation.py +32 -0
  408. oscura/validation/__init__.py +121 -0
  409. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  410. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  411. oscura/{compliance → validation/compliance}/masks.py +1 -1
  412. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  413. oscura/{compliance → validation/compliance}/testing.py +114 -52
  414. oscura/validation/compliance_tests.py +915 -0
  415. oscura/validation/fuzzer.py +990 -0
  416. oscura/validation/grammar_tests.py +596 -0
  417. oscura/validation/grammar_validator.py +904 -0
  418. oscura/validation/hil_testing.py +977 -0
  419. oscura/{quality → validation/quality}/__init__.py +4 -4
  420. oscura/{quality → validation/quality}/ensemble.py +251 -171
  421. oscura/{quality → validation/quality}/explainer.py +3 -3
  422. oscura/{quality → validation/quality}/scoring.py +1 -1
  423. oscura/{quality → validation/quality}/warnings.py +4 -4
  424. oscura/validation/regression_suite.py +808 -0
  425. oscura/validation/replay.py +788 -0
  426. oscura/{testing → validation/testing}/__init__.py +2 -2
  427. oscura/{testing → validation/testing}/synthetic.py +5 -5
  428. oscura/visualization/__init__.py +9 -0
  429. oscura/visualization/accessibility.py +1 -1
  430. oscura/visualization/annotations.py +64 -67
  431. oscura/visualization/colors.py +7 -7
  432. oscura/visualization/digital.py +180 -81
  433. oscura/visualization/eye.py +236 -85
  434. oscura/visualization/interactive.py +320 -143
  435. oscura/visualization/jitter.py +587 -247
  436. oscura/visualization/layout.py +169 -134
  437. oscura/visualization/optimization.py +103 -52
  438. oscura/visualization/palettes.py +1 -1
  439. oscura/visualization/power.py +427 -211
  440. oscura/visualization/power_extended.py +626 -297
  441. oscura/visualization/presets.py +2 -0
  442. oscura/visualization/protocols.py +495 -181
  443. oscura/visualization/render.py +79 -63
  444. oscura/visualization/reverse_engineering.py +171 -124
  445. oscura/visualization/signal_integrity.py +460 -279
  446. oscura/visualization/specialized.py +190 -100
  447. oscura/visualization/spectral.py +670 -255
  448. oscura/visualization/thumbnails.py +166 -137
  449. oscura/visualization/waveform.py +150 -63
  450. oscura/workflows/__init__.py +3 -0
  451. oscura/{batch → workflows/batch}/__init__.py +5 -5
  452. oscura/{batch → workflows/batch}/advanced.py +150 -75
  453. oscura/workflows/batch/aggregate.py +531 -0
  454. oscura/workflows/batch/analyze.py +236 -0
  455. oscura/{batch → workflows/batch}/logging.py +2 -2
  456. oscura/{batch → workflows/batch}/metrics.py +1 -1
  457. oscura/workflows/complete_re.py +1144 -0
  458. oscura/workflows/compliance.py +44 -54
  459. oscura/workflows/digital.py +197 -51
  460. oscura/workflows/legacy/__init__.py +12 -0
  461. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  462. oscura/workflows/multi_trace.py +9 -9
  463. oscura/workflows/power.py +42 -62
  464. oscura/workflows/protocol.py +82 -49
  465. oscura/workflows/reverse_engineering.py +351 -150
  466. oscura/workflows/signal_integrity.py +157 -82
  467. oscura-0.6.0.dist-info/METADATA +643 -0
  468. oscura-0.6.0.dist-info/RECORD +590 -0
  469. oscura/analyzers/digital/ic_database.py +0 -498
  470. oscura/analyzers/digital/timing_paths.py +0 -339
  471. oscura/analyzers/digital/vintage.py +0 -377
  472. oscura/analyzers/digital/vintage_result.py +0 -148
  473. oscura/analyzers/protocols/parallel_bus.py +0 -449
  474. oscura/batch/aggregate.py +0 -300
  475. oscura/batch/analyze.py +0 -139
  476. oscura/dsl/__init__.py +0 -73
  477. oscura/exceptions.py +0 -59
  478. oscura/exploratory/fuzzy.py +0 -513
  479. oscura/exploratory/sync.py +0 -384
  480. oscura/export/wavedrom.py +0 -430
  481. oscura/exporters/__init__.py +0 -94
  482. oscura/exporters/csv.py +0 -303
  483. oscura/exporters/exporters.py +0 -44
  484. oscura/exporters/hdf5.py +0 -217
  485. oscura/exporters/html_export.py +0 -701
  486. oscura/exporters/json_export.py +0 -338
  487. oscura/exporters/markdown_export.py +0 -367
  488. oscura/exporters/matlab_export.py +0 -354
  489. oscura/exporters/npz_export.py +0 -219
  490. oscura/exporters/spice_export.py +0 -210
  491. oscura/exporters/vintage_logic_csv.py +0 -247
  492. oscura/reporting/vintage_logic_report.py +0 -523
  493. oscura/search/context.py +0 -149
  494. oscura/session/__init__.py +0 -34
  495. oscura/session/annotations.py +0 -289
  496. oscura/session/history.py +0 -313
  497. oscura/session/session.py +0 -520
  498. oscura/visualization/digital_advanced.py +0 -718
  499. oscura/visualization/figure_manager.py +0 -156
  500. oscura/workflow/__init__.py +0 -13
  501. oscura-0.5.0.dist-info/METADATA +0 -407
  502. oscura-0.5.0.dist-info/RECORD +0 -486
  503. /oscura/core/{config.py → config/legacy.py} +0 -0
  504. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  505. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  506. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  507. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  508. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  509. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  510. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  511. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
  512. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
  513. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,446 @@
1
+ """OPC UA data type parsers.
2
+
3
+ This module implements parsing for OPC UA built-in data types including
4
+ NodeId, Variant, String, DateTime, and other primitives.
5
+
6
+ References:
7
+ OPC UA Part 6: Mappings - Section 5.1 Built-in Types
8
+ https://reference.opcfoundation.org/Core/Part6/v105/docs/5.1
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Any
14
+
15
+
16
+ def parse_string(data: bytes, offset: int) -> tuple[str | None, int]:
17
+ """Parse OPC UA String (length-prefixed UTF-8).
18
+
19
+ String Format:
20
+ - Length (4 bytes, little-endian, -1 for null)
21
+ - Data (Length bytes, UTF-8 encoded)
22
+
23
+ Args:
24
+ data: Binary data containing the string.
25
+ offset: Starting offset in data.
26
+
27
+ Returns:
28
+ Tuple of (parsed_string, bytes_consumed).
29
+ Returns (None, 4) for null string.
30
+
31
+ Example:
32
+ >>> data = b'\\x05\\x00\\x00\\x00Hello'
33
+ >>> s, consumed = parse_string(data, 0)
34
+ >>> assert s == "Hello"
35
+ >>> assert consumed == 9
36
+ """
37
+ if offset + 4 > len(data):
38
+ return None, 0
39
+
40
+ length = int.from_bytes(data[offset : offset + 4], "little", signed=True)
41
+ consumed = 4
42
+
43
+ if length == -1:
44
+ # Null string
45
+ return None, consumed
46
+
47
+ if length < 0:
48
+ # Invalid length
49
+ return None, consumed
50
+
51
+ if offset + 4 + length > len(data):
52
+ # Not enough data
53
+ return None, consumed
54
+
55
+ try:
56
+ string_value = data[offset + 4 : offset + 4 + length].decode("utf-8")
57
+ except UnicodeDecodeError:
58
+ # Invalid UTF-8
59
+ string_value = data[offset + 4 : offset + 4 + length].decode("utf-8", errors="replace")
60
+
61
+ consumed += length
62
+ return string_value, consumed
63
+
64
+
65
+ def parse_node_id(data: bytes, offset: int) -> tuple[str, int]:
66
+ """Parse OPC UA NodeId.
67
+
68
+ NodeId Encoding:
69
+ - EncodingByte (1 byte):
70
+ * Bits 0-5: Encoding type
71
+ - 0x00: TwoByte (ns=0, numeric id < 256)
72
+ - 0x01: FourByte (ns < 256, numeric id < 65536)
73
+ - 0x02: Numeric (full 32-bit namespace and identifier)
74
+ - 0x03: String (namespace + UTF-8 string)
75
+ - 0x04: Guid (namespace + 16-byte GUID)
76
+ - 0x05: ByteString (namespace + byte string)
77
+ * Bits 6-7: Namespace URI and Server Index flags
78
+ - Namespace (varies by encoding)
79
+ - Identifier (varies by encoding)
80
+
81
+ Args:
82
+ data: Binary data containing the NodeId.
83
+ offset: Starting offset in data.
84
+
85
+ Returns:
86
+ Tuple of (node_id_string, bytes_consumed).
87
+ NodeId string formats:
88
+ - Numeric: "ns=X;i=Y" or "i=Y" (if ns=0)
89
+ - String: "ns=X;s=string"
90
+ - Guid: "ns=X;g=guid"
91
+ - ByteString: "ns=X;b=base64"
92
+
93
+ Example:
94
+ >>> # FourByte numeric NodeId: ns=2, id=1001
95
+ >>> data = bytes([0x01, 0x02, 0xE9, 0x03])
96
+ >>> node_id, consumed = parse_node_id(data, 0)
97
+ >>> assert node_id == "ns=2;i=1001"
98
+ >>> assert consumed == 4
99
+ """
100
+ if offset >= len(data):
101
+ return "i=0", 0
102
+
103
+ encoding_byte = data[offset]
104
+ encoding_type = encoding_byte & 0x3F
105
+ consumed = 1
106
+
107
+ if encoding_type == 0x00:
108
+ return _parse_twobyte_nodeid(data, offset, consumed)
109
+ elif encoding_type == 0x01:
110
+ return _parse_fourbyte_nodeid(data, offset, consumed)
111
+ elif encoding_type == 0x02:
112
+ return _parse_numeric_nodeid(data, offset, consumed)
113
+ elif encoding_type == 0x03:
114
+ return _parse_string_nodeid(data, offset, consumed)
115
+ elif encoding_type == 0x04:
116
+ return _parse_guid_nodeid(data, offset, consumed)
117
+ elif encoding_type == 0x05:
118
+ return _parse_bytestring_nodeid(data, offset, consumed)
119
+ else:
120
+ return "i=0", consumed
121
+
122
+
123
+ def _parse_twobyte_nodeid(data: bytes, offset: int, consumed: int) -> tuple[str, int]:
124
+ """Parse TwoByte NodeId (ns=0, identifier < 256).
125
+
126
+ Args:
127
+ data: Binary data.
128
+ offset: Starting offset.
129
+ consumed: Bytes already consumed.
130
+
131
+ Returns:
132
+ Tuple of (node_id_string, total_bytes_consumed).
133
+ """
134
+ if offset + 1 >= len(data):
135
+ return "i=0", consumed
136
+ identifier = data[offset + 1]
137
+ return f"i={identifier}", consumed + 1
138
+
139
+
140
+ def _parse_fourbyte_nodeid(data: bytes, offset: int, consumed: int) -> tuple[str, int]:
141
+ """Parse FourByte NodeId (ns < 256, identifier < 65536).
142
+
143
+ Args:
144
+ data: Binary data.
145
+ offset: Starting offset.
146
+ consumed: Bytes already consumed.
147
+
148
+ Returns:
149
+ Tuple of (node_id_string, total_bytes_consumed).
150
+ """
151
+ if offset + 3 >= len(data):
152
+ return "i=0", consumed
153
+
154
+ namespace = data[offset + 1]
155
+ identifier = int.from_bytes(data[offset + 2 : offset + 4], "little")
156
+ consumed += 3
157
+
158
+ return _format_numeric_nodeid(namespace, identifier), consumed
159
+
160
+
161
+ def _parse_numeric_nodeid(data: bytes, offset: int, consumed: int) -> tuple[str, int]:
162
+ """Parse Numeric NodeId (full 32-bit namespace and identifier).
163
+
164
+ Args:
165
+ data: Binary data.
166
+ offset: Starting offset.
167
+ consumed: Bytes already consumed.
168
+
169
+ Returns:
170
+ Tuple of (node_id_string, total_bytes_consumed).
171
+ """
172
+ if offset + 6 >= len(data):
173
+ return "i=0", consumed
174
+
175
+ namespace = int.from_bytes(data[offset + 1 : offset + 3], "little")
176
+ identifier = int.from_bytes(data[offset + 3 : offset + 7], "little")
177
+ consumed += 6
178
+
179
+ return _format_numeric_nodeid(namespace, identifier), consumed
180
+
181
+
182
+ def _parse_string_nodeid(data: bytes, offset: int, consumed: int) -> tuple[str, int]:
183
+ """Parse String NodeId (namespace + UTF-8 string).
184
+
185
+ Args:
186
+ data: Binary data.
187
+ offset: Starting offset.
188
+ consumed: Bytes already consumed.
189
+
190
+ Returns:
191
+ Tuple of (node_id_string, total_bytes_consumed).
192
+ """
193
+ if offset + 2 >= len(data):
194
+ return "i=0", consumed
195
+
196
+ namespace = int.from_bytes(data[offset + 1 : offset + 3], "little")
197
+ consumed += 2
198
+
199
+ string_value, string_consumed = parse_string(data, offset + consumed)
200
+ consumed += string_consumed
201
+
202
+ if string_value is None:
203
+ return "i=0", consumed
204
+
205
+ if namespace == 0:
206
+ return f"s={string_value}", consumed
207
+ return f"ns={namespace};s={string_value}", consumed
208
+
209
+
210
+ def _parse_guid_nodeid(data: bytes, offset: int, consumed: int) -> tuple[str, int]:
211
+ """Parse Guid NodeId (namespace + 16-byte GUID).
212
+
213
+ Args:
214
+ data: Binary data.
215
+ offset: Starting offset.
216
+ consumed: Bytes already consumed.
217
+
218
+ Returns:
219
+ Tuple of (node_id_string, total_bytes_consumed).
220
+ """
221
+ if offset + 18 >= len(data):
222
+ return "i=0", consumed
223
+
224
+ namespace = int.from_bytes(data[offset + 1 : offset + 3], "little")
225
+ guid_bytes = data[offset + 3 : offset + 19]
226
+ consumed += 18
227
+ guid_str = guid_bytes.hex()
228
+
229
+ if namespace == 0:
230
+ return f"g={guid_str}", consumed
231
+ return f"ns={namespace};g={guid_str}", consumed
232
+
233
+
234
+ def _parse_bytestring_nodeid(data: bytes, offset: int, consumed: int) -> tuple[str, int]:
235
+ """Parse ByteString NodeId (namespace + length-prefixed byte string).
236
+
237
+ Args:
238
+ data: Binary data.
239
+ offset: Starting offset.
240
+ consumed: Bytes already consumed.
241
+
242
+ Returns:
243
+ Tuple of (node_id_string, total_bytes_consumed).
244
+ """
245
+ if offset + 2 >= len(data):
246
+ return "i=0", consumed
247
+
248
+ namespace = int.from_bytes(data[offset + 1 : offset + 3], "little")
249
+ consumed += 2
250
+
251
+ bs_value, bs_consumed = parse_string(data, offset + consumed)
252
+ consumed += bs_consumed
253
+
254
+ if bs_value is None:
255
+ return "i=0", consumed
256
+
257
+ if namespace == 0:
258
+ return f"b={bs_value}", consumed
259
+ return f"ns={namespace};b={bs_value}", consumed
260
+
261
+
262
+ def _format_numeric_nodeid(namespace: int, identifier: int) -> str:
263
+ """Format numeric NodeId as string.
264
+
265
+ Args:
266
+ namespace: Namespace index.
267
+ identifier: Numeric identifier.
268
+
269
+ Returns:
270
+ Formatted NodeId string.
271
+ """
272
+ if namespace == 0:
273
+ return f"i={identifier}"
274
+ return f"ns={namespace};i={identifier}"
275
+
276
+
277
+ def parse_variant(data: bytes, offset: int) -> tuple[Any, int]:
278
+ """Parse OPC UA Variant data type.
279
+
280
+ Variant Encoding:
281
+ - EncodingByte (1 byte):
282
+ * Bits 0-5: Data type
283
+ * Bit 6: Array flag
284
+ * Bit 7: Array dimensions flag
285
+ - Value (varies by type)
286
+ - Array length (if array flag set)
287
+ - Array dimensions (if dimensions flag set)
288
+
289
+ Built-in type IDs:
290
+ - 1: Boolean
291
+ - 2: SByte
292
+ - 3: Byte
293
+ - 4: Int16
294
+ - 5: UInt16
295
+ - 6: Int32
296
+ - 7: UInt32
297
+ - 8: Int64
298
+ - 9: UInt64
299
+ - 10: Float
300
+ - 11: Double
301
+ - 12: String
302
+ - 13: DateTime
303
+ - 15: Guid
304
+ - 17: NodeId
305
+ - 22: LocalizedText
306
+
307
+ Args:
308
+ data: Binary data containing the variant.
309
+ offset: Starting offset in data.
310
+
311
+ Returns:
312
+ Tuple of (parsed_value, bytes_consumed).
313
+
314
+ Example:
315
+ >>> # UInt32 variant
316
+ >>> data = bytes([0x07, 0x2A, 0x00, 0x00, 0x00])
317
+ >>> value, consumed = parse_variant(data, 0)
318
+ >>> assert value == 42
319
+ >>> assert consumed == 5
320
+ """
321
+ if offset >= len(data):
322
+ return None, 0
323
+
324
+ encoding_byte = data[offset]
325
+ type_id = encoding_byte & 0x3F
326
+ is_array = bool(encoding_byte & 0x40)
327
+ consumed = 1
328
+
329
+ # Handle arrays (simplified - just return indication)
330
+ if is_array:
331
+ return {"array": True, "type_id": type_id}, consumed
332
+
333
+ # Parse scalar value based on type
334
+ value, bytes_read = _parse_variant_scalar(data, offset + 1, type_id)
335
+ consumed += bytes_read
336
+
337
+ return value, consumed
338
+
339
+
340
+ def _parse_variant_scalar(data: bytes, offset: int, type_id: int) -> tuple[Any, int]:
341
+ """Parse a scalar variant value.
342
+
343
+ Args:
344
+ data: Binary data.
345
+ offset: Starting offset (after encoding byte).
346
+ type_id: Variant type ID.
347
+
348
+ Returns:
349
+ Tuple of (parsed_value, bytes_consumed).
350
+ """
351
+ # Boolean (1 byte)
352
+ if type_id == 1:
353
+ return _parse_boolean_variant(data, offset)
354
+
355
+ # Byte (1 byte)
356
+ elif type_id == 3:
357
+ return _parse_byte_variant(data, offset)
358
+
359
+ # Int16 (2 bytes)
360
+ elif type_id == 4:
361
+ return _parse_int16_variant(data, offset)
362
+
363
+ # UInt16 (2 bytes)
364
+ elif type_id == 5:
365
+ return _parse_uint16_variant(data, offset)
366
+
367
+ # Int32 (4 bytes)
368
+ elif type_id == 6:
369
+ return _parse_int32_variant(data, offset)
370
+
371
+ # UInt32 (4 bytes)
372
+ elif type_id == 7:
373
+ return _parse_uint32_variant(data, offset)
374
+
375
+ # String (length-prefixed)
376
+ elif type_id == 12:
377
+ return _parse_string_variant(data, offset)
378
+
379
+ # NodeId
380
+ elif type_id == 17:
381
+ return _parse_nodeid_variant(data, offset)
382
+
383
+ # Unsupported types - return type indicator
384
+ else:
385
+ return {"type_id": type_id, "unsupported": True}, 0
386
+
387
+
388
+ def _parse_boolean_variant(data: bytes, offset: int) -> tuple[bool | None, int]:
389
+ """Parse Boolean variant (1 byte)."""
390
+ if offset < len(data):
391
+ return bool(data[offset]), 1
392
+ return None, 0
393
+
394
+
395
+ def _parse_byte_variant(data: bytes, offset: int) -> tuple[int | None, int]:
396
+ """Parse Byte variant (1 byte)."""
397
+ if offset < len(data):
398
+ return data[offset], 1
399
+ return None, 0
400
+
401
+
402
+ def _parse_int16_variant(data: bytes, offset: int) -> tuple[int | None, int]:
403
+ """Parse Int16 variant (2 bytes)."""
404
+ if offset + 1 < len(data):
405
+ value = int.from_bytes(data[offset : offset + 2], "little", signed=True)
406
+ return value, 2
407
+ return None, 0
408
+
409
+
410
+ def _parse_uint16_variant(data: bytes, offset: int) -> tuple[int | None, int]:
411
+ """Parse UInt16 variant (2 bytes)."""
412
+ if offset + 1 < len(data):
413
+ value = int.from_bytes(data[offset : offset + 2], "little")
414
+ return value, 2
415
+ return None, 0
416
+
417
+
418
+ def _parse_int32_variant(data: bytes, offset: int) -> tuple[int | None, int]:
419
+ """Parse Int32 variant (4 bytes)."""
420
+ if offset + 3 < len(data):
421
+ value = int.from_bytes(data[offset : offset + 4], "little", signed=True)
422
+ return value, 4
423
+ return None, 0
424
+
425
+
426
+ def _parse_uint32_variant(data: bytes, offset: int) -> tuple[int | None, int]:
427
+ """Parse UInt32 variant (4 bytes)."""
428
+ if offset + 3 < len(data):
429
+ value = int.from_bytes(data[offset : offset + 4], "little")
430
+ return value, 4
431
+ return None, 0
432
+
433
+
434
+ def _parse_string_variant(data: bytes, offset: int) -> tuple[str | None, int]:
435
+ """Parse String variant (length-prefixed)."""
436
+ str_value, str_consumed = parse_string(data, offset)
437
+ return str_value, str_consumed
438
+
439
+
440
+ def _parse_nodeid_variant(data: bytes, offset: int) -> tuple[str, int]:
441
+ """Parse NodeId variant."""
442
+ node_id, node_consumed = parse_node_id(data, offset)
443
+ return node_id, node_consumed
444
+
445
+
446
+ __all__ = ["parse_node_id", "parse_string", "parse_variant"]
@@ -0,0 +1,264 @@
1
+ """OPC UA service request/response decoders.
2
+
3
+ This module implements parsing for OPC UA service-specific payloads including
4
+ Read, Write, Browse, Subscribe, and Publish operations.
5
+
6
+ References:
7
+ OPC UA Part 4: Services
8
+ https://reference.opcfoundation.org/Core/Part4/v105/docs/
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from collections.abc import Callable
14
+ from typing import Any
15
+
16
+
17
+ def parse_read_request(data: bytes) -> dict[str, Any]:
18
+ """Parse ReadRequest service payload.
19
+
20
+ ReadRequest Format:
21
+ - RequestHeader (complex structure)
22
+ - MaxAge (8 bytes, double)
23
+ - TimestampsToReturn (4 bytes, enum)
24
+ - NodesToRead (array of ReadValueId)
25
+
26
+ Args:
27
+ data: Service payload bytes.
28
+
29
+ Returns:
30
+ Parsed request data.
31
+
32
+ Example:
33
+ >>> # Simplified ReadRequest
34
+ >>> request = parse_read_request(b'...')
35
+ >>> assert 'nodes_to_read' in request
36
+ """
37
+ # Simplified implementation - full parsing would be very complex
38
+ result: dict[str, Any] = {
39
+ "service": "ReadRequest",
40
+ "payload_size": len(data),
41
+ }
42
+
43
+ # Try to extract basic information
44
+ if len(data) >= 12:
45
+ # Skip request header (would need complex parsing)
46
+ # MaxAge at some offset
47
+ # TimestampsToReturn enum
48
+ result["partial_parse"] = True
49
+
50
+ return result
51
+
52
+
53
+ def parse_read_response(data: bytes) -> dict[str, Any]:
54
+ """Parse ReadResponse service payload.
55
+
56
+ ReadResponse Format:
57
+ - ResponseHeader (complex structure)
58
+ - Results (array of DataValue)
59
+ - DiagnosticInfos (array, optional)
60
+
61
+ Args:
62
+ data: Service payload bytes.
63
+
64
+ Returns:
65
+ Parsed response data.
66
+
67
+ Example:
68
+ >>> response = parse_read_response(b'...')
69
+ >>> assert 'service' in response
70
+ """
71
+ result: dict[str, Any] = {
72
+ "service": "ReadResponse",
73
+ "payload_size": len(data),
74
+ }
75
+
76
+ return result
77
+
78
+
79
+ def parse_write_request(data: bytes) -> dict[str, Any]:
80
+ """Parse WriteRequest service payload.
81
+
82
+ WriteRequest Format:
83
+ - RequestHeader
84
+ - NodesToWrite (array of WriteValue)
85
+
86
+ Args:
87
+ data: Service payload bytes.
88
+
89
+ Returns:
90
+ Parsed request data.
91
+
92
+ Example:
93
+ >>> request = parse_write_request(b'...')
94
+ >>> assert request['service'] == 'WriteRequest'
95
+ """
96
+ result: dict[str, Any] = {
97
+ "service": "WriteRequest",
98
+ "payload_size": len(data),
99
+ }
100
+
101
+ return result
102
+
103
+
104
+ def parse_browse_request(data: bytes) -> dict[str, Any]:
105
+ """Parse BrowseRequest service payload.
106
+
107
+ BrowseRequest Format:
108
+ - RequestHeader
109
+ - View (ViewDescription)
110
+ - RequestedMaxReferencesPerNode (4 bytes)
111
+ - NodesToBrowse (array of BrowseDescription)
112
+
113
+ Args:
114
+ data: Service payload bytes.
115
+
116
+ Returns:
117
+ Parsed request data.
118
+
119
+ Example:
120
+ >>> request = parse_browse_request(b'...')
121
+ >>> assert 'service' in request
122
+ """
123
+ result: dict[str, Any] = {
124
+ "service": "BrowseRequest",
125
+ "payload_size": len(data),
126
+ }
127
+
128
+ return result
129
+
130
+
131
+ def parse_browse_response(data: bytes) -> dict[str, Any]:
132
+ """Parse BrowseResponse service payload.
133
+
134
+ BrowseResponse Format:
135
+ - ResponseHeader
136
+ - Results (array of BrowseResult)
137
+ - DiagnosticInfos (array, optional)
138
+
139
+ Args:
140
+ data: Service payload bytes.
141
+
142
+ Returns:
143
+ Parsed response data.
144
+
145
+ Example:
146
+ >>> response = parse_browse_response(b'...')
147
+ >>> assert response['service'] == 'BrowseResponse'
148
+ """
149
+ result: dict[str, Any] = {
150
+ "service": "BrowseResponse",
151
+ "payload_size": len(data),
152
+ }
153
+
154
+ return result
155
+
156
+
157
+ def parse_create_subscription_request(data: bytes) -> dict[str, Any]:
158
+ """Parse CreateSubscriptionRequest service payload.
159
+
160
+ CreateSubscriptionRequest Format:
161
+ - RequestHeader
162
+ - RequestedPublishingInterval (8 bytes, double)
163
+ - RequestedLifetimeCount (4 bytes)
164
+ - RequestedMaxKeepAliveCount (4 bytes)
165
+ - MaxNotificationsPerPublish (4 bytes)
166
+ - PublishingEnabled (1 byte, boolean)
167
+ - Priority (1 byte)
168
+
169
+ Args:
170
+ data: Service payload bytes.
171
+
172
+ Returns:
173
+ Parsed request data with subscription parameters.
174
+
175
+ Example:
176
+ >>> request = parse_create_subscription_request(b'...')
177
+ >>> assert 'service' in request
178
+ """
179
+ result: dict[str, Any] = {
180
+ "service": "CreateSubscriptionRequest",
181
+ "payload_size": len(data),
182
+ }
183
+
184
+ return result
185
+
186
+
187
+ def parse_publish_request(data: bytes) -> dict[str, Any]:
188
+ """Parse PublishRequest service payload.
189
+
190
+ PublishRequest Format:
191
+ - RequestHeader
192
+ - SubscriptionAcknowledgements (array)
193
+
194
+ Args:
195
+ data: Service payload bytes.
196
+
197
+ Returns:
198
+ Parsed request data.
199
+
200
+ Example:
201
+ >>> request = parse_publish_request(b'...')
202
+ >>> assert request['service'] == 'PublishRequest'
203
+ """
204
+ result: dict[str, Any] = {
205
+ "service": "PublishRequest",
206
+ "payload_size": len(data),
207
+ }
208
+
209
+ return result
210
+
211
+
212
+ def parse_publish_response(data: bytes) -> dict[str, Any]:
213
+ """Parse PublishResponse service payload.
214
+
215
+ PublishResponse Format:
216
+ - ResponseHeader
217
+ - SubscriptionId (4 bytes)
218
+ - AvailableSequenceNumbers (array)
219
+ - MoreNotifications (1 byte, boolean)
220
+ - NotificationMessage (complex)
221
+ - Results (array of StatusCode)
222
+ - DiagnosticInfos (array, optional)
223
+
224
+ Args:
225
+ data: Service payload bytes.
226
+
227
+ Returns:
228
+ Parsed response data with notification data.
229
+
230
+ Example:
231
+ >>> response = parse_publish_response(b'...')
232
+ >>> assert 'service' in response
233
+ """
234
+ result: dict[str, Any] = {
235
+ "service": "PublishResponse",
236
+ "payload_size": len(data),
237
+ }
238
+
239
+ return result
240
+
241
+
242
+ # Service ID to parser mapping
243
+ SERVICE_PARSERS: dict[
244
+ int, tuple[Callable[[bytes], dict[str, Any]] | None, Callable[[bytes], dict[str, Any]] | None]
245
+ ] = {
246
+ 421: (parse_read_request, parse_read_response), # Read
247
+ 673: (parse_write_request, None), # Write (response is simple StatusCode array)
248
+ 527: (parse_browse_request, parse_browse_response), # Browse
249
+ 631: (parse_create_subscription_request, None), # CreateSubscription
250
+ 826: (parse_publish_request, parse_publish_response), # Publish
251
+ }
252
+
253
+
254
+ __all__ = [
255
+ "SERVICE_PARSERS",
256
+ "parse_browse_request",
257
+ "parse_browse_response",
258
+ "parse_create_subscription_request",
259
+ "parse_publish_request",
260
+ "parse_publish_response",
261
+ "parse_read_request",
262
+ "parse_read_response",
263
+ "parse_write_request",
264
+ ]