oscura 0.5.1__py3-none-any.whl → 0.7.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 (497) 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/edges.py +325 -65
  5. oscura/analyzers/digital/quality.py +293 -166
  6. oscura/analyzers/digital/timing.py +260 -115
  7. oscura/analyzers/digital/timing_numba.py +334 -0
  8. oscura/analyzers/entropy.py +605 -0
  9. oscura/analyzers/eye/diagram.py +176 -109
  10. oscura/analyzers/eye/metrics.py +5 -5
  11. oscura/analyzers/jitter/__init__.py +6 -4
  12. oscura/analyzers/jitter/ber.py +52 -52
  13. oscura/analyzers/jitter/classification.py +156 -0
  14. oscura/analyzers/jitter/decomposition.py +163 -113
  15. oscura/analyzers/jitter/spectrum.py +80 -64
  16. oscura/analyzers/ml/__init__.py +39 -0
  17. oscura/analyzers/ml/features.py +600 -0
  18. oscura/analyzers/ml/signal_classifier.py +604 -0
  19. oscura/analyzers/packet/daq.py +246 -158
  20. oscura/analyzers/packet/parser.py +12 -1
  21. oscura/analyzers/packet/payload.py +50 -2110
  22. oscura/analyzers/packet/payload_analysis.py +361 -181
  23. oscura/analyzers/packet/payload_patterns.py +133 -70
  24. oscura/analyzers/packet/stream.py +84 -23
  25. oscura/analyzers/patterns/__init__.py +26 -5
  26. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  27. oscura/analyzers/patterns/clustering.py +169 -108
  28. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  29. oscura/analyzers/patterns/discovery.py +1 -1
  30. oscura/analyzers/patterns/matching.py +581 -197
  31. oscura/analyzers/patterns/pattern_mining.py +778 -0
  32. oscura/analyzers/patterns/periodic.py +121 -38
  33. oscura/analyzers/patterns/sequences.py +175 -78
  34. oscura/analyzers/power/conduction.py +1 -1
  35. oscura/analyzers/power/soa.py +6 -6
  36. oscura/analyzers/power/switching.py +250 -110
  37. oscura/analyzers/protocol/__init__.py +17 -1
  38. oscura/analyzers/protocols/base.py +6 -6
  39. oscura/analyzers/protocols/ble/__init__.py +38 -0
  40. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  41. oscura/analyzers/protocols/ble/uuids.py +288 -0
  42. oscura/analyzers/protocols/can.py +257 -127
  43. oscura/analyzers/protocols/can_fd.py +107 -80
  44. oscura/analyzers/protocols/flexray.py +139 -80
  45. oscura/analyzers/protocols/hdlc.py +93 -58
  46. oscura/analyzers/protocols/i2c.py +247 -106
  47. oscura/analyzers/protocols/i2s.py +138 -86
  48. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  49. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  50. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  51. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  52. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  53. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  54. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  55. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  56. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  57. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  58. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  59. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  60. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  61. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  62. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  63. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  64. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  65. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  66. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  67. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  68. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  69. oscura/analyzers/protocols/jtag.py +180 -98
  70. oscura/analyzers/protocols/lin.py +219 -114
  71. oscura/analyzers/protocols/manchester.py +4 -4
  72. oscura/analyzers/protocols/onewire.py +253 -149
  73. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  74. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  75. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  76. oscura/analyzers/protocols/spi.py +192 -95
  77. oscura/analyzers/protocols/swd.py +321 -167
  78. oscura/analyzers/protocols/uart.py +267 -125
  79. oscura/analyzers/protocols/usb.py +235 -131
  80. oscura/analyzers/side_channel/power.py +17 -12
  81. oscura/analyzers/signal/__init__.py +15 -0
  82. oscura/analyzers/signal/timing_analysis.py +1086 -0
  83. oscura/analyzers/signal_integrity/__init__.py +4 -1
  84. oscura/analyzers/signal_integrity/sparams.py +2 -19
  85. oscura/analyzers/spectral/chunked.py +129 -60
  86. oscura/analyzers/spectral/chunked_fft.py +300 -94
  87. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  88. oscura/analyzers/statistical/checksum.py +376 -217
  89. oscura/analyzers/statistical/classification.py +229 -107
  90. oscura/analyzers/statistical/entropy.py +78 -53
  91. oscura/analyzers/statistics/correlation.py +407 -211
  92. oscura/analyzers/statistics/outliers.py +2 -2
  93. oscura/analyzers/statistics/streaming.py +30 -5
  94. oscura/analyzers/validation.py +216 -101
  95. oscura/analyzers/waveform/measurements.py +9 -0
  96. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  97. oscura/analyzers/waveform/spectral.py +500 -228
  98. oscura/api/__init__.py +31 -5
  99. oscura/api/dsl/__init__.py +582 -0
  100. oscura/{dsl → api/dsl}/commands.py +43 -76
  101. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  102. oscura/{dsl → api/dsl}/parser.py +107 -77
  103. oscura/{dsl → api/dsl}/repl.py +2 -2
  104. oscura/api/dsl.py +1 -1
  105. oscura/{integrations → api/integrations}/__init__.py +1 -1
  106. oscura/{integrations → api/integrations}/llm.py +201 -102
  107. oscura/api/operators.py +3 -3
  108. oscura/api/optimization.py +144 -30
  109. oscura/api/rest_server.py +921 -0
  110. oscura/api/server/__init__.py +17 -0
  111. oscura/api/server/dashboard.py +850 -0
  112. oscura/api/server/static/README.md +34 -0
  113. oscura/api/server/templates/base.html +181 -0
  114. oscura/api/server/templates/export.html +120 -0
  115. oscura/api/server/templates/home.html +284 -0
  116. oscura/api/server/templates/protocols.html +58 -0
  117. oscura/api/server/templates/reports.html +43 -0
  118. oscura/api/server/templates/session_detail.html +89 -0
  119. oscura/api/server/templates/sessions.html +83 -0
  120. oscura/api/server/templates/waveforms.html +73 -0
  121. oscura/automotive/__init__.py +8 -1
  122. oscura/automotive/can/__init__.py +10 -0
  123. oscura/automotive/can/checksum.py +3 -1
  124. oscura/automotive/can/dbc_generator.py +590 -0
  125. oscura/automotive/can/message_wrapper.py +121 -74
  126. oscura/automotive/can/patterns.py +98 -21
  127. oscura/automotive/can/session.py +292 -56
  128. oscura/automotive/can/state_machine.py +6 -3
  129. oscura/automotive/can/stimulus_response.py +97 -75
  130. oscura/automotive/dbc/__init__.py +10 -2
  131. oscura/automotive/dbc/generator.py +84 -56
  132. oscura/automotive/dbc/parser.py +6 -6
  133. oscura/automotive/dtc/data.json +17 -102
  134. oscura/automotive/dtc/database.py +2 -2
  135. oscura/automotive/flexray/__init__.py +31 -0
  136. oscura/automotive/flexray/analyzer.py +504 -0
  137. oscura/automotive/flexray/crc.py +185 -0
  138. oscura/automotive/flexray/fibex.py +449 -0
  139. oscura/automotive/j1939/__init__.py +45 -8
  140. oscura/automotive/j1939/analyzer.py +605 -0
  141. oscura/automotive/j1939/spns.py +326 -0
  142. oscura/automotive/j1939/transport.py +306 -0
  143. oscura/automotive/lin/__init__.py +47 -0
  144. oscura/automotive/lin/analyzer.py +612 -0
  145. oscura/automotive/loaders/blf.py +13 -2
  146. oscura/automotive/loaders/csv_can.py +143 -72
  147. oscura/automotive/loaders/dispatcher.py +50 -2
  148. oscura/automotive/loaders/mdf.py +86 -45
  149. oscura/automotive/loaders/pcap.py +111 -61
  150. oscura/automotive/uds/__init__.py +4 -0
  151. oscura/automotive/uds/analyzer.py +725 -0
  152. oscura/automotive/uds/decoder.py +140 -58
  153. oscura/automotive/uds/models.py +7 -1
  154. oscura/automotive/visualization.py +1 -1
  155. oscura/cli/analyze.py +348 -0
  156. oscura/cli/batch.py +142 -122
  157. oscura/cli/benchmark.py +275 -0
  158. oscura/cli/characterize.py +137 -82
  159. oscura/cli/compare.py +224 -131
  160. oscura/cli/completion.py +250 -0
  161. oscura/cli/config_cmd.py +361 -0
  162. oscura/cli/decode.py +164 -87
  163. oscura/cli/export.py +286 -0
  164. oscura/cli/main.py +115 -31
  165. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  166. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  167. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  168. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  169. oscura/cli/progress.py +147 -0
  170. oscura/cli/shell.py +157 -135
  171. oscura/cli/validate_cmd.py +204 -0
  172. oscura/cli/visualize.py +158 -0
  173. oscura/convenience.py +125 -79
  174. oscura/core/__init__.py +4 -2
  175. oscura/core/backend_selector.py +3 -3
  176. oscura/core/cache.py +126 -15
  177. oscura/core/cancellation.py +1 -1
  178. oscura/{config → core/config}/__init__.py +20 -11
  179. oscura/{config → core/config}/defaults.py +1 -1
  180. oscura/{config → core/config}/loader.py +7 -5
  181. oscura/{config → core/config}/memory.py +5 -5
  182. oscura/{config → core/config}/migration.py +1 -1
  183. oscura/{config → core/config}/pipeline.py +99 -23
  184. oscura/{config → core/config}/preferences.py +1 -1
  185. oscura/{config → core/config}/protocol.py +3 -3
  186. oscura/{config → core/config}/schema.py +426 -272
  187. oscura/{config → core/config}/settings.py +1 -1
  188. oscura/{config → core/config}/thresholds.py +195 -153
  189. oscura/core/correlation.py +5 -6
  190. oscura/core/cross_domain.py +0 -2
  191. oscura/core/debug.py +9 -5
  192. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  193. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  194. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  195. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  196. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  197. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  198. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  199. oscura/core/gpu_backend.py +11 -7
  200. oscura/core/log_query.py +101 -11
  201. oscura/core/logging.py +126 -54
  202. oscura/core/logging_advanced.py +5 -5
  203. oscura/core/memory_limits.py +108 -70
  204. oscura/core/memory_monitor.py +2 -2
  205. oscura/core/memory_progress.py +7 -7
  206. oscura/core/memory_warnings.py +1 -1
  207. oscura/core/numba_backend.py +13 -13
  208. oscura/{plugins → core/plugins}/__init__.py +9 -9
  209. oscura/{plugins → core/plugins}/base.py +7 -7
  210. oscura/{plugins → core/plugins}/cli.py +3 -3
  211. oscura/{plugins → core/plugins}/discovery.py +186 -106
  212. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  213. oscura/{plugins → core/plugins}/manager.py +7 -7
  214. oscura/{plugins → core/plugins}/registry.py +3 -3
  215. oscura/{plugins → core/plugins}/versioning.py +1 -1
  216. oscura/core/progress.py +16 -1
  217. oscura/core/provenance.py +8 -2
  218. oscura/{schemas → core/schemas}/__init__.py +2 -2
  219. oscura/{schemas → core/schemas}/device_mapping.json +2 -8
  220. oscura/{schemas → core/schemas}/packet_format.json +4 -24
  221. oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
  222. oscura/core/types.py +4 -0
  223. oscura/core/uncertainty.py +3 -3
  224. oscura/correlation/__init__.py +52 -0
  225. oscura/correlation/multi_protocol.py +811 -0
  226. oscura/discovery/auto_decoder.py +117 -35
  227. oscura/discovery/comparison.py +191 -86
  228. oscura/discovery/quality_validator.py +155 -68
  229. oscura/discovery/signal_detector.py +196 -79
  230. oscura/export/__init__.py +18 -8
  231. oscura/export/kaitai_struct.py +513 -0
  232. oscura/export/scapy_layer.py +801 -0
  233. oscura/export/wireshark/generator.py +1 -1
  234. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  235. oscura/export/wireshark_dissector.py +746 -0
  236. oscura/guidance/wizard.py +207 -111
  237. oscura/hardware/__init__.py +19 -0
  238. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  239. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  240. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  241. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  242. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  243. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  244. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  245. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  246. oscura/hardware/firmware/__init__.py +29 -0
  247. oscura/hardware/firmware/pattern_recognition.py +874 -0
  248. oscura/hardware/hal_detector.py +736 -0
  249. oscura/hardware/security/__init__.py +37 -0
  250. oscura/hardware/security/side_channel_detector.py +1126 -0
  251. oscura/inference/__init__.py +4 -0
  252. oscura/inference/active_learning/observation_table.py +4 -1
  253. oscura/inference/alignment.py +216 -123
  254. oscura/inference/bayesian.py +113 -33
  255. oscura/inference/crc_reverse.py +101 -55
  256. oscura/inference/logic.py +6 -2
  257. oscura/inference/message_format.py +342 -183
  258. oscura/inference/protocol.py +95 -44
  259. oscura/inference/protocol_dsl.py +180 -82
  260. oscura/inference/signal_intelligence.py +1439 -706
  261. oscura/inference/spectral.py +99 -57
  262. oscura/inference/state_machine.py +810 -158
  263. oscura/inference/stream.py +270 -110
  264. oscura/iot/__init__.py +34 -0
  265. oscura/iot/coap/__init__.py +32 -0
  266. oscura/iot/coap/analyzer.py +668 -0
  267. oscura/iot/coap/options.py +212 -0
  268. oscura/iot/lorawan/__init__.py +21 -0
  269. oscura/iot/lorawan/crypto.py +206 -0
  270. oscura/iot/lorawan/decoder.py +801 -0
  271. oscura/iot/lorawan/mac_commands.py +341 -0
  272. oscura/iot/mqtt/__init__.py +27 -0
  273. oscura/iot/mqtt/analyzer.py +999 -0
  274. oscura/iot/mqtt/properties.py +315 -0
  275. oscura/iot/zigbee/__init__.py +31 -0
  276. oscura/iot/zigbee/analyzer.py +615 -0
  277. oscura/iot/zigbee/security.py +153 -0
  278. oscura/iot/zigbee/zcl.py +349 -0
  279. oscura/jupyter/display.py +125 -45
  280. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  281. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  282. oscura/jupyter/exploratory/fuzzy.py +746 -0
  283. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  284. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  285. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  286. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  287. oscura/jupyter/exploratory/sync.py +612 -0
  288. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  289. oscura/jupyter/magic.py +4 -4
  290. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  291. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  292. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  293. oscura/loaders/__init__.py +183 -67
  294. oscura/loaders/binary.py +88 -1
  295. oscura/loaders/chipwhisperer.py +153 -137
  296. oscura/loaders/configurable.py +208 -86
  297. oscura/loaders/csv_loader.py +458 -215
  298. oscura/loaders/hdf5_loader.py +278 -119
  299. oscura/loaders/lazy.py +87 -54
  300. oscura/loaders/mmap_loader.py +1 -1
  301. oscura/loaders/numpy_loader.py +253 -116
  302. oscura/loaders/pcap.py +226 -151
  303. oscura/loaders/rigol.py +110 -49
  304. oscura/loaders/sigrok.py +201 -78
  305. oscura/loaders/tdms.py +81 -58
  306. oscura/loaders/tektronix.py +291 -174
  307. oscura/loaders/touchstone.py +182 -87
  308. oscura/loaders/tss.py +456 -0
  309. oscura/loaders/vcd.py +215 -117
  310. oscura/loaders/wav.py +155 -68
  311. oscura/reporting/__init__.py +9 -0
  312. oscura/reporting/analyze.py +352 -146
  313. oscura/reporting/argument_preparer.py +69 -14
  314. oscura/reporting/auto_report.py +97 -61
  315. oscura/reporting/batch.py +131 -58
  316. oscura/reporting/chart_selection.py +57 -45
  317. oscura/reporting/comparison.py +63 -17
  318. oscura/reporting/content/executive.py +76 -24
  319. oscura/reporting/core_formats/multi_format.py +11 -8
  320. oscura/reporting/engine.py +312 -158
  321. oscura/reporting/enhanced_reports.py +949 -0
  322. oscura/reporting/export.py +86 -43
  323. oscura/reporting/formatting/numbers.py +69 -42
  324. oscura/reporting/html.py +139 -58
  325. oscura/reporting/index.py +137 -65
  326. oscura/reporting/output.py +158 -67
  327. oscura/reporting/pdf.py +67 -102
  328. oscura/reporting/plots.py +191 -112
  329. oscura/reporting/sections.py +88 -47
  330. oscura/reporting/standards.py +104 -61
  331. oscura/reporting/summary_generator.py +75 -55
  332. oscura/reporting/tables.py +138 -54
  333. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  334. oscura/sessions/__init__.py +14 -23
  335. oscura/sessions/base.py +3 -3
  336. oscura/sessions/blackbox.py +106 -10
  337. oscura/sessions/generic.py +2 -2
  338. oscura/sessions/legacy.py +783 -0
  339. oscura/side_channel/__init__.py +63 -0
  340. oscura/side_channel/dpa.py +1025 -0
  341. oscura/utils/__init__.py +15 -1
  342. oscura/utils/bitwise.py +118 -0
  343. oscura/{builders → utils/builders}/__init__.py +1 -1
  344. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  345. oscura/{comparison → utils/comparison}/compare.py +202 -101
  346. oscura/{comparison → utils/comparison}/golden.py +83 -63
  347. oscura/{comparison → utils/comparison}/limits.py +313 -89
  348. oscura/{comparison → utils/comparison}/mask.py +151 -45
  349. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  350. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  351. oscura/{component → utils/component}/__init__.py +3 -3
  352. oscura/{component → utils/component}/impedance.py +122 -58
  353. oscura/{component → utils/component}/reactive.py +165 -168
  354. oscura/{component → utils/component}/transmission_line.py +3 -3
  355. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  356. oscura/{filtering → utils/filtering}/base.py +1 -1
  357. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  358. oscura/{filtering → utils/filtering}/design.py +169 -93
  359. oscura/{filtering → utils/filtering}/filters.py +2 -2
  360. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  361. oscura/utils/geometry.py +31 -0
  362. oscura/utils/imports.py +184 -0
  363. oscura/utils/lazy.py +1 -1
  364. oscura/{math → utils/math}/__init__.py +2 -2
  365. oscura/{math → utils/math}/arithmetic.py +114 -48
  366. oscura/{math → utils/math}/interpolation.py +139 -106
  367. oscura/utils/memory.py +129 -66
  368. oscura/utils/memory_advanced.py +92 -9
  369. oscura/utils/memory_extensions.py +10 -8
  370. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  371. oscura/{optimization → utils/optimization}/search.py +2 -2
  372. oscura/utils/performance/__init__.py +58 -0
  373. oscura/utils/performance/caching.py +889 -0
  374. oscura/utils/performance/lsh_clustering.py +333 -0
  375. oscura/utils/performance/memory_optimizer.py +699 -0
  376. oscura/utils/performance/optimizations.py +675 -0
  377. oscura/utils/performance/parallel.py +654 -0
  378. oscura/utils/performance/profiling.py +661 -0
  379. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  380. oscura/{pipeline → utils/pipeline}/composition.py +1 -1
  381. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  382. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  383. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  384. oscura/{search → utils/search}/__init__.py +3 -3
  385. oscura/{search → utils/search}/anomaly.py +188 -58
  386. oscura/utils/search/context.py +294 -0
  387. oscura/{search → utils/search}/pattern.py +138 -10
  388. oscura/utils/serial.py +51 -0
  389. oscura/utils/storage/__init__.py +61 -0
  390. oscura/utils/storage/database.py +1166 -0
  391. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  392. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  393. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  394. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  395. oscura/{triggering → utils/triggering}/base.py +6 -6
  396. oscura/{triggering → utils/triggering}/edge.py +2 -2
  397. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  398. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  399. oscura/{triggering → utils/triggering}/window.py +2 -2
  400. oscura/utils/validation.py +32 -0
  401. oscura/validation/__init__.py +121 -0
  402. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  403. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  404. oscura/{compliance → validation/compliance}/masks.py +1 -1
  405. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  406. oscura/{compliance → validation/compliance}/testing.py +114 -52
  407. oscura/validation/compliance_tests.py +915 -0
  408. oscura/validation/fuzzer.py +990 -0
  409. oscura/validation/grammar_tests.py +596 -0
  410. oscura/validation/grammar_validator.py +904 -0
  411. oscura/validation/hil_testing.py +977 -0
  412. oscura/{quality → validation/quality}/__init__.py +4 -4
  413. oscura/{quality → validation/quality}/ensemble.py +251 -171
  414. oscura/{quality → validation/quality}/explainer.py +3 -3
  415. oscura/{quality → validation/quality}/scoring.py +1 -1
  416. oscura/{quality → validation/quality}/warnings.py +4 -4
  417. oscura/validation/regression_suite.py +808 -0
  418. oscura/validation/replay.py +788 -0
  419. oscura/{testing → validation/testing}/__init__.py +2 -2
  420. oscura/{testing → validation/testing}/synthetic.py +5 -5
  421. oscura/visualization/__init__.py +9 -0
  422. oscura/visualization/accessibility.py +1 -1
  423. oscura/visualization/annotations.py +64 -67
  424. oscura/visualization/colors.py +7 -7
  425. oscura/visualization/digital.py +180 -81
  426. oscura/visualization/eye.py +236 -85
  427. oscura/visualization/interactive.py +320 -143
  428. oscura/visualization/jitter.py +587 -247
  429. oscura/visualization/layout.py +169 -134
  430. oscura/visualization/optimization.py +103 -52
  431. oscura/visualization/palettes.py +1 -1
  432. oscura/visualization/power.py +427 -211
  433. oscura/visualization/power_extended.py +626 -297
  434. oscura/visualization/presets.py +2 -0
  435. oscura/visualization/protocols.py +495 -181
  436. oscura/visualization/render.py +79 -63
  437. oscura/visualization/reverse_engineering.py +171 -124
  438. oscura/visualization/signal_integrity.py +460 -279
  439. oscura/visualization/specialized.py +190 -100
  440. oscura/visualization/spectral.py +670 -255
  441. oscura/visualization/thumbnails.py +166 -137
  442. oscura/visualization/waveform.py +150 -63
  443. oscura/workflows/__init__.py +3 -0
  444. oscura/{batch → workflows/batch}/__init__.py +5 -5
  445. oscura/{batch → workflows/batch}/advanced.py +150 -75
  446. oscura/workflows/batch/aggregate.py +531 -0
  447. oscura/workflows/batch/analyze.py +236 -0
  448. oscura/{batch → workflows/batch}/logging.py +2 -2
  449. oscura/{batch → workflows/batch}/metrics.py +1 -1
  450. oscura/workflows/complete_re.py +1144 -0
  451. oscura/workflows/compliance.py +44 -54
  452. oscura/workflows/digital.py +197 -51
  453. oscura/workflows/legacy/__init__.py +12 -0
  454. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  455. oscura/workflows/multi_trace.py +9 -9
  456. oscura/workflows/power.py +42 -62
  457. oscura/workflows/protocol.py +82 -49
  458. oscura/workflows/reverse_engineering.py +351 -150
  459. oscura/workflows/signal_integrity.py +157 -82
  460. oscura-0.7.0.dist-info/METADATA +661 -0
  461. oscura-0.7.0.dist-info/RECORD +591 -0
  462. oscura/batch/aggregate.py +0 -300
  463. oscura/batch/analyze.py +0 -139
  464. oscura/dsl/__init__.py +0 -73
  465. oscura/exceptions.py +0 -59
  466. oscura/exploratory/fuzzy.py +0 -513
  467. oscura/exploratory/sync.py +0 -384
  468. oscura/exporters/__init__.py +0 -94
  469. oscura/exporters/csv.py +0 -303
  470. oscura/exporters/exporters.py +0 -44
  471. oscura/exporters/hdf5.py +0 -217
  472. oscura/exporters/html_export.py +0 -701
  473. oscura/exporters/json_export.py +0 -291
  474. oscura/exporters/markdown_export.py +0 -367
  475. oscura/exporters/matlab_export.py +0 -354
  476. oscura/exporters/npz_export.py +0 -219
  477. oscura/exporters/spice_export.py +0 -210
  478. oscura/search/context.py +0 -149
  479. oscura/session/__init__.py +0 -34
  480. oscura/session/annotations.py +0 -289
  481. oscura/session/history.py +0 -313
  482. oscura/session/session.py +0 -520
  483. oscura/workflow/__init__.py +0 -13
  484. oscura-0.5.1.dist-info/METADATA +0 -583
  485. oscura-0.5.1.dist-info/RECORD +0 -481
  486. /oscura/core/{config.py → config/legacy.py} +0 -0
  487. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  488. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  489. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  490. /oscura/{schemas → core/schemas}/bus_configuration.json +0 -0
  491. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  492. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  493. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  494. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  495. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
  496. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
  497. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,339 @@
1
+ """EtherCAT mailbox protocol parsers.
2
+
3
+ This module provides parsers for EtherCAT mailbox protocols including
4
+ CoE (CAN over EtherCAT), FoE (File over EtherCAT), SoE (Servo over EtherCAT),
5
+ and EoE (Ethernet over EtherCAT).
6
+
7
+ Example:
8
+ >>> from oscura.analyzers.protocols.industrial.ethercat.mailbox import parse_mailbox
9
+ >>> mailbox_data = bytes([0x06, 0x00, 0x00, 0x00, 0x02, ...])
10
+ >>> result = parse_mailbox(mailbox_data)
11
+ >>> print(f"Protocol: {result['protocol']}")
12
+
13
+ References:
14
+ ETG.1000.6 CoE Protocol
15
+ ETG.8200 FoE Protocol
16
+ ETG.1000.4 SoE Protocol
17
+ ETG.8500 EoE Protocol
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from typing import Any
23
+
24
+
25
+ def parse_mailbox(data: bytes) -> dict[str, Any]:
26
+ """Parse EtherCAT mailbox protocol data.
27
+
28
+ Mailbox Header Format:
29
+ - Length (2 bytes, little-endian) - Data length
30
+ - Address (2 bytes, little-endian) - Station address
31
+ - Channel (6 bits) - Priority and channel info
32
+ - Priority (2 bits)
33
+ - Type (4 bits) - Protocol type
34
+ - Counter (3 bits)
35
+ - Reserved (1 bit)
36
+
37
+ Args:
38
+ data: Mailbox data bytes.
39
+
40
+ Returns:
41
+ Parsed mailbox data dictionary.
42
+
43
+ Raises:
44
+ ValueError: If data is invalid.
45
+
46
+ Example:
47
+ >>> # CoE mailbox data
48
+ >>> data = bytes([0x0A, 0x00, 0x01, 0x00, 0x02, 0x00, ...])
49
+ >>> result = parse_mailbox(data)
50
+ >>> assert result['protocol'] == 'CoE'
51
+ """
52
+ if len(data) < 6:
53
+ raise ValueError(f"Mailbox data too short: {len(data)} bytes (minimum 6)")
54
+
55
+ # Parse mailbox header
56
+ length = int.from_bytes(data[0:2], "little")
57
+ address = int.from_bytes(data[2:4], "little")
58
+ channel_priority = data[4]
59
+ type_counter = data[5]
60
+
61
+ # Extract fields
62
+ channel = channel_priority & 0x3F # Lower 6 bits
63
+ priority = (channel_priority >> 6) & 0x03 # Upper 2 bits
64
+ protocol_type = type_counter & 0x0F # Lower 4 bits
65
+ counter = (type_counter >> 4) & 0x07 # Bits 4-6
66
+
67
+ # Map protocol type
68
+ protocol_names = {
69
+ 0x00: "ERR", # Error
70
+ 0x02: "CoE", # CAN application protocol over EtherCAT
71
+ 0x03: "FoE", # File access over EtherCAT
72
+ 0x04: "SoE", # Servo drive profile over EtherCAT
73
+ 0x05: "EoE", # Ethernet over EtherCAT
74
+ }
75
+
76
+ protocol = protocol_names.get(protocol_type, f"Unknown (0x{protocol_type:X})")
77
+
78
+ result: dict[str, Any] = {
79
+ "length": length,
80
+ "address": address,
81
+ "channel": channel,
82
+ "priority": priority,
83
+ "protocol": protocol,
84
+ "protocol_type": protocol_type,
85
+ "counter": counter,
86
+ }
87
+
88
+ # Parse protocol-specific data
89
+ if len(data) > 6:
90
+ protocol_data = data[6:]
91
+
92
+ if protocol_type == 0x02: # CoE
93
+ result["coe"] = _parse_coe(protocol_data)
94
+ elif protocol_type == 0x03: # FoE
95
+ result["foe"] = _parse_foe(protocol_data)
96
+ elif protocol_type == 0x04: # SoE
97
+ result["soe"] = _parse_soe(protocol_data)
98
+ elif protocol_type == 0x05: # EoE
99
+ result["eoe"] = _parse_eoe(protocol_data)
100
+
101
+ return result
102
+
103
+
104
+ def _parse_coe(data: bytes) -> dict[str, Any]:
105
+ """Parse CoE (CAN over EtherCAT) protocol data.
106
+
107
+ CoE Header Format:
108
+ - Number (2 bytes, little-endian) - CAN ID or SDO index
109
+ - Service (4 bits) - CoE service type
110
+ - Reserved (4 bits)
111
+
112
+ Args:
113
+ data: CoE protocol data.
114
+
115
+ Returns:
116
+ Parsed CoE data.
117
+ """
118
+ if len(data) < 2:
119
+ return {"error": "CoE data too short"}
120
+
121
+ number = int.from_bytes(data[0:2], "little")
122
+
123
+ result: dict[str, Any] = {
124
+ "number": number,
125
+ }
126
+
127
+ if len(data) >= 3:
128
+ service = data[2] & 0x0F
129
+
130
+ # CoE service types
131
+ service_names = {
132
+ 0x01: "SDO Request",
133
+ 0x02: "SDO Response",
134
+ 0x03: "TxPDO",
135
+ 0x04: "RxPDO",
136
+ 0x05: "TxPDO Remote Request",
137
+ 0x06: "RxPDO Remote Request",
138
+ 0x07: "SDO Information",
139
+ }
140
+
141
+ result["service"] = service_names.get(service, f"Unknown (0x{service:X})")
142
+ result["service_type"] = service
143
+
144
+ # Parse SDO data if present
145
+ if service in {0x01, 0x02} and len(data) >= 10: # SDO Request/Response
146
+ result["sdo"] = _parse_sdo(data[3:]) # Skip number (2 bytes) + service (1 byte)
147
+
148
+ return result
149
+
150
+
151
+ def _parse_sdo(data: bytes) -> dict[str, Any]:
152
+ """Parse SDO (Service Data Object) data.
153
+
154
+ Args:
155
+ data: SDO data bytes.
156
+
157
+ Returns:
158
+ Parsed SDO data.
159
+ """
160
+ if len(data) < 8:
161
+ return {"error": "SDO data too short"}
162
+
163
+ command = data[0]
164
+ index = int.from_bytes(data[1:3], "little")
165
+ subindex = data[3]
166
+ data_bytes = data[4:8]
167
+
168
+ # SDO command specifiers
169
+ command_type = command >> 5 # Upper 3 bits
170
+
171
+ command_names = {
172
+ 0x01: "Download Initiate",
173
+ 0x02: "Download Segment",
174
+ 0x03: "Upload Initiate",
175
+ 0x04: "Upload Segment",
176
+ 0x05: "Abort",
177
+ }
178
+
179
+ return {
180
+ "command": command_names.get(command_type, f"Unknown (0x{command_type:X})"),
181
+ "command_code": command,
182
+ "index": index,
183
+ "subindex": subindex,
184
+ "data": data_bytes.hex(),
185
+ }
186
+
187
+
188
+ def _parse_foe(data: bytes) -> dict[str, Any]:
189
+ """Parse FoE (File over EtherCAT) protocol data.
190
+
191
+ FoE Header Format:
192
+ - OpCode (1 byte) - Operation code
193
+ - Reserved (1 byte)
194
+ - Packet Number (4 bytes, little-endian) - For data transfer
195
+
196
+ Args:
197
+ data: FoE protocol data.
198
+
199
+ Returns:
200
+ Parsed FoE data.
201
+ """
202
+ if len(data) < 1:
203
+ return {"error": "FoE data too short"}
204
+
205
+ opcode = data[0]
206
+
207
+ # FoE operation codes
208
+ opcode_names = {
209
+ 0x01: "Read Request",
210
+ 0x02: "Write Request",
211
+ 0x03: "Data",
212
+ 0x04: "Ack",
213
+ 0x05: "Error",
214
+ 0x06: "Busy",
215
+ }
216
+
217
+ result: dict[str, Any] = {
218
+ "opcode": opcode_names.get(opcode, f"Unknown (0x{opcode:02X})"),
219
+ "opcode_code": opcode,
220
+ }
221
+
222
+ if len(data) >= 6:
223
+ packet_number = int.from_bytes(data[2:6], "little")
224
+ result["packet_number"] = packet_number
225
+
226
+ return result
227
+
228
+
229
+ def _parse_soe(data: bytes) -> dict[str, Any]:
230
+ """Parse SoE (Servo over EtherCAT) protocol data.
231
+
232
+ SoE Header Format:
233
+ - OpCode (3 bits) - Operation code
234
+ - InComplete (1 bit)
235
+ - Error (1 bit)
236
+ - DriveNo (3 bits)
237
+ - Elements (2 bytes, little-endian) - Number of elements
238
+ - IDN (2 bytes, little-endian) - Identification number
239
+
240
+ Args:
241
+ data: SoE protocol data.
242
+
243
+ Returns:
244
+ Parsed SoE data.
245
+ """
246
+ if len(data) < 4:
247
+ return {"error": "SoE data too short"}
248
+
249
+ header = data[0]
250
+ opcode = header & 0x07 # Lower 3 bits
251
+ incomplete = bool(header & 0x08) # Bit 3
252
+ error = bool(header & 0x10) # Bit 4
253
+ drive_no = (header >> 5) & 0x07 # Upper 3 bits
254
+
255
+ # SoE operation codes
256
+ opcode_names = {
257
+ 0x00: "No Operation",
258
+ 0x01: "Read Request",
259
+ 0x02: "Read Response",
260
+ 0x03: "Write Request",
261
+ 0x04: "Write Response",
262
+ 0x05: "Notification Request",
263
+ 0x06: "Emergency",
264
+ }
265
+
266
+ result: dict[str, Any] = {
267
+ "opcode": opcode_names.get(opcode, f"Unknown (0x{opcode:X})"),
268
+ "opcode_code": opcode,
269
+ "incomplete": incomplete,
270
+ "error": error,
271
+ "drive_number": drive_no,
272
+ }
273
+
274
+ if len(data) >= 4:
275
+ idn = int.from_bytes(data[2:4], "little")
276
+ result["idn"] = idn
277
+
278
+ return result
279
+
280
+
281
+ def _parse_eoe(data: bytes) -> dict[str, Any]:
282
+ """Parse EoE (Ethernet over EtherCAT) protocol data.
283
+
284
+ EoE Header Format:
285
+ - Type (4 bits) - Fragment type
286
+ - Port (4 bits) - Port number
287
+ - Last Fragment (1 bit)
288
+ - Time Append (1 bit)
289
+ - Time Request (1 bit)
290
+ - Reserved (5 bits)
291
+ - Fragment Number (6 bits)
292
+ - Frame Offset (6 bits)
293
+ - Frame Number (4 bytes, little-endian)
294
+
295
+ Args:
296
+ data: EoE protocol data.
297
+
298
+ Returns:
299
+ Parsed EoE data.
300
+ """
301
+ if len(data) < 4:
302
+ return {"error": "EoE data too short"}
303
+
304
+ type_port = data[0]
305
+ flags = data[1]
306
+ frag_offset = int.from_bytes(data[2:4], "little")
307
+
308
+ frame_type = type_port & 0x0F # Lower 4 bits
309
+ port = (type_port >> 4) & 0x0F # Upper 4 bits
310
+
311
+ last_fragment = bool(flags & 0x01) # Bit 0
312
+ time_append = bool(flags & 0x02) # Bit 1
313
+ time_request = bool(flags & 0x04) # Bit 2
314
+
315
+ fragment_number = frag_offset & 0x3F # Lower 6 bits
316
+ frame_offset = (frag_offset >> 6) & 0x3F # Bits 6-11
317
+
318
+ # EoE frame types
319
+ type_names = {
320
+ 0x00: "Fragment",
321
+ 0x01: "Init Request",
322
+ 0x02: "Init Response",
323
+ }
324
+
325
+ result: dict[str, Any] = {
326
+ "type": type_names.get(frame_type, f"Unknown (0x{frame_type:X})"),
327
+ "type_code": frame_type,
328
+ "port": port,
329
+ "last_fragment": last_fragment,
330
+ "time_append": time_append,
331
+ "time_request": time_request,
332
+ "fragment_number": fragment_number,
333
+ "frame_offset": frame_offset,
334
+ }
335
+
336
+ return result
337
+
338
+
339
+ __all__ = ["parse_mailbox"]
@@ -0,0 +1,166 @@
1
+ """EtherCAT topology discovery utilities.
2
+
3
+ This module provides utilities for discovering and analyzing EtherCAT network
4
+ topology, including ring/line detection and slave ordering.
5
+
6
+ Example:
7
+ >>> from oscura.analyzers.protocols.industrial.ethercat.topology import TopologyAnalyzer
8
+ >>> from oscura.analyzers.protocols.industrial.ethercat.analyzer import EtherCATAnalyzer
9
+ >>> analyzer = EtherCATAnalyzer()
10
+ >>> # Parse frames...
11
+ >>> topology = TopologyAnalyzer(analyzer)
12
+ >>> network_type = topology.detect_network_type()
13
+ >>> print(f"Network: {network_type}")
14
+
15
+ References:
16
+ ETG.1000 Section 5: Topology and Addressing
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ from dataclasses import dataclass
22
+ from typing import TYPE_CHECKING
23
+
24
+ if TYPE_CHECKING:
25
+ from oscura.analyzers.protocols.industrial.ethercat.analyzer import (
26
+ EtherCATAnalyzer,
27
+ )
28
+
29
+
30
+ @dataclass
31
+ class TopologyInfo:
32
+ """EtherCAT network topology information.
33
+
34
+ Attributes:
35
+ network_type: Network topology type ("line", "ring", "star", "unknown").
36
+ slave_count: Number of discovered slaves.
37
+ slave_addresses: List of slave addresses in order.
38
+ open_ports: List of detected open ports (for line topology).
39
+ """
40
+
41
+ network_type: str
42
+ slave_count: int
43
+ slave_addresses: list[int]
44
+ open_ports: list[int]
45
+
46
+
47
+ class TopologyAnalyzer:
48
+ """EtherCAT topology analyzer.
49
+
50
+ Analyzes EtherCAT frames to determine network topology and slave ordering.
51
+
52
+ Attributes:
53
+ analyzer: EtherCAT analyzer instance.
54
+
55
+ Example:
56
+ >>> from oscura.analyzers.protocols.industrial.ethercat.analyzer import EtherCATAnalyzer
57
+ >>> analyzer = EtherCATAnalyzer()
58
+ >>> # Parse some frames...
59
+ >>> topology = TopologyAnalyzer(analyzer)
60
+ >>> info = topology.analyze()
61
+ >>> print(f"Topology: {info.network_type}, Slaves: {info.slave_count}")
62
+ """
63
+
64
+ def __init__(self, analyzer: EtherCATAnalyzer) -> None:
65
+ """Initialize topology analyzer.
66
+
67
+ Args:
68
+ analyzer: EtherCAT analyzer with parsed frames.
69
+ """
70
+ self.analyzer = analyzer
71
+
72
+ def detect_network_type(self) -> str:
73
+ """Detect network topology type.
74
+
75
+ Analyzes port descriptor registers and working counters to determine
76
+ whether the network is configured as a line or ring topology.
77
+
78
+ Returns:
79
+ Network type: "line", "ring", "star", or "unknown".
80
+
81
+ Example:
82
+ >>> topology = TopologyAnalyzer(analyzer)
83
+ >>> network_type = topology.detect_network_type()
84
+ >>> assert network_type in ["line", "ring", "star", "unknown"]
85
+ """
86
+ # Analyze frames for topology indicators
87
+ open_ports = self._detect_open_ports()
88
+
89
+ if len(open_ports) == 0:
90
+ # All ports connected - likely ring topology
91
+ return "ring"
92
+ elif len(open_ports) == 2:
93
+ # Two open ports - line topology (first and last slave)
94
+ return "line"
95
+ elif len(open_ports) > 2:
96
+ # Multiple open ports - star or complex topology
97
+ return "star"
98
+ else:
99
+ return "unknown"
100
+
101
+ def _detect_open_ports(self) -> list[int]:
102
+ """Detect open (unconnected) ports in the network.
103
+
104
+ Returns:
105
+ List of slave addresses with open ports.
106
+ """
107
+ open_ports: list[int] = []
108
+
109
+ # Port descriptor register is at address 0x0007
110
+ # Open ports show as not connected in the port descriptor
111
+ for frame in self.analyzer.frames:
112
+ for datagram in frame.datagrams:
113
+ if datagram.ado == 0x0007: # Port descriptor register
114
+ if len(datagram.data) >= 1:
115
+ port_desc = datagram.data[0]
116
+ # Bits indicate port types: 0=not implemented, 1=not configured,
117
+ # 2=EBUS, 3=MII
118
+ # Open ports show as 0 or 1
119
+ if port_desc & 0x03 in {0, 1}: # Port 0 or 1 open
120
+ open_ports.append(datagram.adp)
121
+
122
+ return open_ports
123
+
124
+ def analyze(self) -> TopologyInfo:
125
+ """Analyze complete topology information.
126
+
127
+ Returns:
128
+ Complete topology information.
129
+
130
+ Example:
131
+ >>> topology = TopologyAnalyzer(analyzer)
132
+ >>> info = topology.analyze()
133
+ >>> print(f"Found {info.slave_count} slaves in {info.network_type} topology")
134
+ """
135
+ network_type = self.detect_network_type()
136
+ slave_addresses = self.analyzer.discover_topology()
137
+ open_ports = self._detect_open_ports()
138
+
139
+ return TopologyInfo(
140
+ network_type=network_type,
141
+ slave_count=len(slave_addresses),
142
+ slave_addresses=slave_addresses,
143
+ open_ports=open_ports,
144
+ )
145
+
146
+ def get_slave_order(self) -> list[int]:
147
+ """Get slaves in physical connection order.
148
+
149
+ For line topology, returns slaves ordered from master to end of chain.
150
+ For ring topology, returns slaves in ring order.
151
+
152
+ Returns:
153
+ Ordered list of slave addresses.
154
+
155
+ Example:
156
+ >>> topology = TopologyAnalyzer(analyzer)
157
+ >>> order = topology.get_slave_order()
158
+ >>> print(f"Slave chain: {order}")
159
+ """
160
+ # Use auto-increment addressing order as physical order
161
+ # Slaves are enumerated in the order they appear in the segment
162
+ slave_addresses = self.analyzer.discover_topology()
163
+ return slave_addresses
164
+
165
+
166
+ __all__ = ["TopologyAnalyzer", "TopologyInfo"]
@@ -0,0 +1,31 @@
1
+ """Modbus RTU/TCP protocol support.
2
+
3
+ This module provides comprehensive Modbus protocol analysis for both RTU (serial)
4
+ and TCP (Ethernet) variants, including all standard function codes, CRC validation,
5
+ and device state tracking.
6
+
7
+ Example:
8
+ >>> from oscura.analyzers.protocols.industrial.modbus import ModbusAnalyzer
9
+ >>> analyzer = ModbusAnalyzer()
10
+ >>> message = analyzer.parse_rtu(frame_bytes)
11
+ >>> print(f"{message.function_name}: {message.parsed_data}")
12
+
13
+ References:
14
+ Modbus Application Protocol V1.1b3
15
+ Modbus over Serial Line V1.02
16
+ """
17
+
18
+ from oscura.analyzers.protocols.industrial.modbus.analyzer import (
19
+ ModbusAnalyzer,
20
+ ModbusDevice,
21
+ ModbusMessage,
22
+ )
23
+ from oscura.analyzers.protocols.industrial.modbus.crc import calculate_crc, verify_crc
24
+
25
+ __all__ = [
26
+ "ModbusAnalyzer",
27
+ "ModbusDevice",
28
+ "ModbusMessage",
29
+ "calculate_crc",
30
+ "verify_crc",
31
+ ]