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,137 @@
1
+ """GPIB (IEEE-488) protocol decoder.
2
+
3
+ IEEE 488-1978 General Purpose Interface Bus (GPIB) decoder for instrument control.
4
+
5
+ References:
6
+ - IEEE 488.1-2003: IEEE Standard Digital Interface for Programmable Instrumentation
7
+ - HP Application Note 1298: Understanding GPIB Addressing
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass
13
+ from enum import Enum
14
+ from typing import TYPE_CHECKING
15
+
16
+ import numpy as np
17
+
18
+ if TYPE_CHECKING:
19
+ from numpy.typing import NDArray
20
+
21
+
22
+ class GPIBMessageType(Enum):
23
+ """GPIB message types."""
24
+
25
+ TALK_ADDRESS = "talk_address"
26
+ LISTEN_ADDRESS = "listen_address"
27
+ DATA = "data"
28
+ COMMAND = "command"
29
+
30
+
31
+ @dataclass
32
+ class GPIBFrame:
33
+ """Decoded GPIB frame.
34
+
35
+ Attributes:
36
+ timestamp: Frame timestamp in seconds
37
+ data: Data byte value
38
+ message_type: Type of GPIB message
39
+ description: Human-readable description
40
+ """
41
+
42
+ timestamp: float
43
+ data: int
44
+ message_type: GPIBMessageType
45
+ description: str
46
+
47
+
48
+ def decode_gpib(
49
+ dio_lines: list[NDArray[np.bool_]],
50
+ dav: NDArray[np.bool_],
51
+ nrfd: NDArray[np.bool_],
52
+ ndac: NDArray[np.bool_],
53
+ eoi: NDArray[np.bool_],
54
+ atn: NDArray[np.bool_],
55
+ sample_rate: float,
56
+ ) -> list[GPIBFrame]:
57
+ """Decode GPIB (IEEE-488) protocol.
58
+
59
+ Args:
60
+ dio_lines: List of 8 DIO line arrays (data lines)
61
+ dav: Data Valid signal (active low)
62
+ nrfd: Not Ready For Data (active low)
63
+ ndac: Not Data Accepted (active low)
64
+ eoi: End or Identify (active low)
65
+ atn: Attention (active low for command, high for data)
66
+ sample_rate: Sample rate in Hz
67
+
68
+ Returns:
69
+ List of decoded GPIB frames
70
+
71
+ Example:
72
+ >>> frames = decode_gpib(dio_lines, dav, nrfd, ndac, eoi, atn, 1e6)
73
+ >>> for frame in frames:
74
+ ... print(f"{frame.timestamp*1e6:.1f}µs: {frame.description}")
75
+ """
76
+ frames: list[GPIBFrame] = []
77
+
78
+ # Find DAV falling edges (data valid transitions)
79
+ dav_falling = np.where((dav[:-1] == True) & (dav[1:] == False))[0] # noqa: E712
80
+
81
+ for edge_idx in dav_falling:
82
+ # Sample data at DAV falling edge
83
+ data_byte = 0
84
+ for bit_idx, dio_line in enumerate(dio_lines):
85
+ if edge_idx < len(dio_line) and dio_line[edge_idx]:
86
+ data_byte |= 1 << bit_idx
87
+
88
+ timestamp = edge_idx / sample_rate
89
+
90
+ # Determine message type from ATN and EOI
91
+ atn_active = not atn[edge_idx] if edge_idx < len(atn) else False
92
+ eoi_active = not eoi[edge_idx] if edge_idx < len(eoi) else False
93
+
94
+ if atn_active:
95
+ # Command/address mode
96
+ if data_byte & 0x40: # Talk address
97
+ address = data_byte & 0x1F
98
+ frame = GPIBFrame(
99
+ timestamp=timestamp,
100
+ data=data_byte,
101
+ message_type=GPIBMessageType.TALK_ADDRESS,
102
+ description=f"Talk address {address}",
103
+ )
104
+ elif data_byte & 0x20: # Listen address
105
+ address = data_byte & 0x1F
106
+ frame = GPIBFrame(
107
+ timestamp=timestamp,
108
+ data=data_byte,
109
+ message_type=GPIBMessageType.LISTEN_ADDRESS,
110
+ description=f"Listen address {address}",
111
+ )
112
+ else: # Command
113
+ frame = GPIBFrame(
114
+ timestamp=timestamp,
115
+ data=data_byte,
116
+ message_type=GPIBMessageType.COMMAND,
117
+ description=f"Command 0x{data_byte:02X}",
118
+ )
119
+ else:
120
+ # Data mode
121
+ char = chr(data_byte) if 32 <= data_byte < 127 else ""
122
+ desc = f"Data 0x{data_byte:02X}"
123
+ if char:
124
+ desc += f" ('{char}')"
125
+ if eoi_active:
126
+ desc += " [EOI]"
127
+
128
+ frame = GPIBFrame(
129
+ timestamp=timestamp,
130
+ data=data_byte,
131
+ message_type=GPIBMessageType.DATA,
132
+ description=desc,
133
+ )
134
+
135
+ frames.append(frame)
136
+
137
+ return frames
@@ -57,17 +57,17 @@ class SPIDecoder(SyncDecoder):
57
57
  longname = "Serial Peripheral Interface"
58
58
  desc = "SPI bus protocol decoder"
59
59
 
60
- channels = [ # noqa: RUF012
60
+ channels = [
61
61
  ChannelDef("clk", "CLK", "Clock signal", required=True),
62
62
  ChannelDef("mosi", "MOSI", "Master Out Slave In", required=True),
63
63
  ]
64
64
 
65
- optional_channels = [ # noqa: RUF012
65
+ optional_channels = [
66
66
  ChannelDef("miso", "MISO", "Master In Slave Out", required=False),
67
67
  ChannelDef("cs", "CS#", "Chip Select (active low)", required=False),
68
68
  ]
69
69
 
70
- options = [ # noqa: RUF012
70
+ options = [
71
71
  OptionDef("cpol", "Clock Polarity", "Clock idle state", default=0, values=[0, 1]),
72
72
  OptionDef("cpha", "Clock Phase", "Sample edge", default=0, values=[0, 1]),
73
73
  OptionDef(
@@ -87,7 +87,7 @@ class SPIDecoder(SyncDecoder):
87
87
  ),
88
88
  ]
89
89
 
90
- annotations = [ # noqa: RUF012
90
+ annotations = [
91
91
  ("bit", "Bit value"),
92
92
  ("byte", "Decoded byte"),
93
93
  ("word", "Decoded word"),
@@ -152,12 +152,78 @@ class SPIDecoder(SyncDecoder):
152
152
  >>> for pkt in decoder.decode(clk=clk, mosi=mosi, miso=miso, sample_rate=1e9):
153
153
  ... print(f"Word: 0x{pkt.annotations['mosi_value']:04X}")
154
154
  """
155
+ clk, mosi, miso, cs, sample_rate = self._prepare_spi_signals(
156
+ trace, clk, mosi, miso, cs, sample_rate
157
+ )
158
+
159
+ if clk is None or mosi is None:
160
+ return
161
+
162
+ edges = self._find_sample_edges(clk)
163
+ if len(edges) == 0:
164
+ return
165
+
166
+ mosi_bits: list[int] = []
167
+ miso_bits: list[int] = []
168
+ word_start_idx = edges[0]
169
+ word_num = 0
170
+
171
+ for edge_idx in edges:
172
+ if not self._is_cs_active(cs, edge_idx):
173
+ mosi_bits = []
174
+ miso_bits = []
175
+ continue
176
+
177
+ mosi_bits, miso_bits = self._sample_data_lines(
178
+ mosi, miso, edge_idx, mosi_bits, miso_bits
179
+ )
180
+
181
+ if len(mosi_bits) >= self._word_size:
182
+ packet = self._build_spi_word_packet(
183
+ mosi_bits, miso_bits, word_start_idx, edge_idx, sample_rate, word_num
184
+ )
185
+
186
+ yield packet
187
+
188
+ mosi_bits = mosi_bits[self._word_size :]
189
+ miso_bits = miso_bits[self._word_size :] if miso_bits else []
190
+ word_start_idx = edge_idx
191
+ word_num += 1
192
+
193
+ def _prepare_spi_signals(
194
+ self,
195
+ trace: DigitalTrace | None,
196
+ clk: NDArray[np.bool_] | None,
197
+ mosi: NDArray[np.bool_] | None,
198
+ miso: NDArray[np.bool_] | None,
199
+ cs: NDArray[np.bool_] | None,
200
+ sample_rate: float,
201
+ ) -> tuple[
202
+ NDArray[np.bool_] | None,
203
+ NDArray[np.bool_] | None,
204
+ NDArray[np.bool_] | None,
205
+ NDArray[np.bool_] | None,
206
+ float,
207
+ ]:
208
+ """Prepare and align all SPI signals.
209
+
210
+ Args:
211
+ trace: Optional trace.
212
+ clk: Clock signal.
213
+ mosi: MOSI signal.
214
+ miso: MISO signal.
215
+ cs: CS signal.
216
+ sample_rate: Sample rate.
217
+
218
+ Returns:
219
+ Tuple of aligned signals and sample rate.
220
+ """
155
221
  if trace is not None:
156
222
  clk = trace.data
157
223
  sample_rate = trace.metadata.sample_rate
158
224
 
159
225
  if clk is None or mosi is None:
160
- return
226
+ return None, None, None, None, sample_rate
161
227
 
162
228
  n_samples = min(len(clk), len(mosi))
163
229
  if miso is not None:
@@ -172,107 +238,138 @@ class SPIDecoder(SyncDecoder):
172
238
  if cs is not None:
173
239
  cs = cs[:n_samples]
174
240
 
175
- # Determine sampling edge based on CPOL and CPHA
176
- # CPOL=0: idle low, first edge is rising
177
- # CPOL=1: idle high, first edge is falling
178
- # CPHA=0: sample on first edge
179
- # CPHA=1: sample on second edge
241
+ return clk, mosi, miso, cs, sample_rate
242
+
243
+ def _find_sample_edges(self, clk: NDArray[np.bool_]) -> NDArray[np.intp]:
244
+ """Find clock edges for sampling based on CPOL/CPHA.
245
+
246
+ Args:
247
+ clk: Clock signal.
248
+
249
+ Returns:
250
+ Array of edge indices.
251
+ """
252
+ sample_edge = self._determine_sample_edge()
253
+
254
+ if sample_edge == "rising":
255
+ return np.where(~clk[:-1] & clk[1:])[0] + 1
256
+ else:
257
+ return np.where(clk[:-1] & ~clk[1:])[0] + 1
258
+
259
+ def _determine_sample_edge(self) -> str:
260
+ """Determine which clock edge to sample on.
261
+
262
+ Returns:
263
+ "rising" or "falling".
264
+ """
180
265
  if self._cpol == 0:
181
- sample_edge = "rising" if self._cpha == 0 else "falling"
266
+ return "rising" if self._cpha == 0 else "falling"
182
267
  elif self._cpha == 0:
183
- sample_edge = "falling"
268
+ return "falling"
184
269
  else:
185
- sample_edge = "rising"
270
+ return "rising"
186
271
 
187
- # Find clock edges
188
- if sample_edge == "rising":
189
- edges = np.where(~clk[:-1] & clk[1:])[0] + 1
190
- else:
191
- edges = np.where(clk[:-1] & ~clk[1:])[0] + 1
272
+ def _is_cs_active(self, cs: NDArray[np.bool_] | None, idx: int) -> bool:
273
+ """Check if chip select is active.
192
274
 
193
- if len(edges) == 0:
194
- return
275
+ Args:
276
+ cs: CS signal or None.
277
+ idx: Index to check.
195
278
 
196
- # Collect bits into words
197
- mosi_bits: list[int] = []
198
- miso_bits: list[int] = []
199
- word_start_idx = edges[0]
200
- word_num = 0
279
+ Returns:
280
+ True if CS active or not provided.
281
+ """
282
+ if cs is None:
283
+ return True
284
+ return bool(cs[idx] == (self._cs_polarity == 1))
201
285
 
202
- for edge_idx in edges:
203
- # Check if CS is active (if provided)
204
- if cs is not None:
205
- cs_active = cs[edge_idx] == (self._cs_polarity == 1)
206
- if not cs_active:
207
- # CS not active, reset and skip
208
- if mosi_bits:
209
- # Emit partial word if any
210
- pass
211
- mosi_bits = []
212
- miso_bits = []
213
- continue
214
-
215
- # Sample MOSI
216
- mosi_bit = 1 if mosi[edge_idx] else 0
217
- mosi_bits.append(mosi_bit)
218
-
219
- # Sample MISO if available
220
- if miso is not None:
221
- miso_bit = 1 if miso[edge_idx] else 0
222
- miso_bits.append(miso_bit)
223
-
224
- # Check if we have a complete word
225
- if len(mosi_bits) >= self._word_size:
226
- # Convert bits to value
227
- mosi_value = self._bits_to_value(mosi_bits[: self._word_size])
228
- miso_value = (
229
- self._bits_to_value(miso_bits[: self._word_size]) if miso_bits else None
230
- )
286
+ def _sample_data_lines(
287
+ self,
288
+ mosi: NDArray[np.bool_],
289
+ miso: NDArray[np.bool_] | None,
290
+ idx: int,
291
+ mosi_bits: list[int],
292
+ miso_bits: list[int],
293
+ ) -> tuple[list[int], list[int]]:
294
+ """Sample MOSI and MISO lines at edge.
231
295
 
232
- # Calculate timing
233
- start_time = word_start_idx / sample_rate
234
- end_time = edge_idx / sample_rate
235
-
236
- # Encode as bytes
237
- byte_count = (self._word_size + 7) // 8
238
- mosi_bytes = mosi_value.to_bytes(byte_count, "big")
239
-
240
- # Add annotations
241
- self.put_annotation(
242
- start_time,
243
- end_time,
244
- AnnotationLevel.WORDS,
245
- f"MOSI: 0x{mosi_value:0{byte_count * 2}X}",
246
- data=mosi_bytes,
247
- )
296
+ Args:
297
+ mosi: MOSI signal.
298
+ miso: MISO signal or None.
299
+ idx: Edge index.
300
+ mosi_bits: Current MOSI bit buffer.
301
+ miso_bits: Current MISO bit buffer.
248
302
 
249
- annotations = {
250
- "word_num": word_num,
251
- "mosi_bits": mosi_bits[: self._word_size],
252
- "mosi_value": mosi_value,
253
- "word_size": self._word_size,
254
- "mode": self._cpol * 2 + self._cpha,
255
- }
256
-
257
- if miso_value is not None:
258
- annotations["miso_bits"] = miso_bits[: self._word_size]
259
- annotations["miso_value"] = miso_value
260
-
261
- packet = ProtocolPacket(
262
- timestamp=start_time,
263
- protocol="spi",
264
- data=mosi_bytes,
265
- annotations=annotations,
266
- errors=[],
267
- )
303
+ Returns:
304
+ Updated (mosi_bits, miso_bits).
305
+ """
306
+ mosi_bit = 1 if mosi[idx] else 0
307
+ mosi_bits.append(mosi_bit)
268
308
 
269
- yield packet
309
+ if miso is not None:
310
+ miso_bit = 1 if miso[idx] else 0
311
+ miso_bits.append(miso_bit)
270
312
 
271
- # Reset for next word
272
- mosi_bits = mosi_bits[self._word_size :]
273
- miso_bits = miso_bits[self._word_size :] if miso_bits else []
274
- word_start_idx = edge_idx
275
- word_num += 1
313
+ return mosi_bits, miso_bits
314
+
315
+ def _build_spi_word_packet(
316
+ self,
317
+ mosi_bits: list[int],
318
+ miso_bits: list[int],
319
+ start_idx: int,
320
+ end_idx: int,
321
+ sample_rate: float,
322
+ word_num: int,
323
+ ) -> ProtocolPacket:
324
+ """Build SPI word packet.
325
+
326
+ Args:
327
+ mosi_bits: MOSI bit buffer.
328
+ miso_bits: MISO bit buffer.
329
+ start_idx: Word start index.
330
+ end_idx: Word end index.
331
+ sample_rate: Sample rate.
332
+ word_num: Word number.
333
+
334
+ Returns:
335
+ Protocol packet.
336
+ """
337
+ mosi_value = self._bits_to_value(mosi_bits[: self._word_size])
338
+ miso_value = self._bits_to_value(miso_bits[: self._word_size]) if miso_bits else None
339
+
340
+ start_time = start_idx / sample_rate
341
+ end_time = end_idx / sample_rate
342
+
343
+ byte_count = (self._word_size + 7) // 8
344
+ mosi_bytes = mosi_value.to_bytes(byte_count, "big")
345
+
346
+ self.put_annotation(
347
+ start_time,
348
+ end_time,
349
+ AnnotationLevel.WORDS,
350
+ f"MOSI: 0x{mosi_value:0{byte_count * 2}X}",
351
+ data=mosi_bytes,
352
+ )
353
+
354
+ annotations = {
355
+ "word_num": word_num,
356
+ "mosi_bits": mosi_bits[: self._word_size],
357
+ "mosi_value": mosi_value,
358
+ "word_size": self._word_size,
359
+ "mode": self._cpol * 2 + self._cpha,
360
+ }
361
+
362
+ if miso_value is not None:
363
+ annotations["miso_bits"] = miso_bits[: self._word_size]
364
+ annotations["miso_value"] = miso_value
365
+
366
+ return ProtocolPacket(
367
+ timestamp=start_time,
368
+ protocol="spi",
369
+ data=mosi_bytes,
370
+ annotations=annotations,
371
+ errors=[],
372
+ )
276
373
 
277
374
  def _bits_to_value(self, bits: list[int]) -> int:
278
375
  """Convert bit list to integer value.