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,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
+ ]