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
@@ -94,98 +94,122 @@ class DataQuality:
94
94
  improvement_suggestions: list[dict[str, str]] = field(default_factory=list)
95
95
 
96
96
 
97
- def assess_data_quality(
97
+ def _extract_trace_data(
98
98
  trace: WaveformTrace | DigitalTrace,
99
- *,
100
- scenario: AnalysisScenario = "general",
101
- protocol_params: dict[str, Any] | None = None,
102
- strict_mode: bool = False,
103
- ) -> DataQuality:
104
- """Assess whether captured data is adequate for analysis.
105
-
106
- Evaluates sample rate, resolution, duration, and noise level against
107
- scenario-specific requirements.
99
+ ) -> tuple[NDArray[np.floating[Any]], float, bool]:
100
+ """Extract data array, sample rate, and type from trace.
108
101
 
109
102
  Args:
110
- trace: Input waveform or digital trace.
111
- scenario: Analysis scenario for scenario-specific thresholds.
112
- protocol_params: Protocol-specific parameters (e.g., clock frequency).
113
- strict_mode: If True, fail on any warnings.
103
+ trace: Input trace.
114
104
 
115
105
  Returns:
116
- DataQuality assessment with overall status and individual metrics.
106
+ Tuple of (data, sample_rate, is_analog).
117
107
 
118
108
  Raises:
119
- ValueError: If trace is empty or invalid.
120
-
121
- Example:
122
- >>> quality = assess_data_quality(trace, scenario='protocol_decode')
123
- >>> print(f"Overall: {quality.status} (confidence: {quality.confidence:.2f})")
124
- >>> for metric in quality.metrics:
125
- ... if metric.status != 'PASS':
126
- ... print(f"Issue: {metric.name} - {metric.explanation}")
127
- ... print(f"Fix: {metric.recommendation}")
128
-
129
- References:
130
- DISC-009: Data Quality Assessment
109
+ ValueError: If trace is empty.
131
110
  """
132
- # Validate input
133
111
  if len(trace) == 0:
134
112
  raise ValueError("Cannot assess quality of empty trace")
135
113
 
136
- # Get signal data
137
114
  if isinstance(trace, WaveformTrace):
138
- data = trace.data
139
- sample_rate = trace.metadata.sample_rate
140
- is_analog = True
115
+ return trace.data, trace.metadata.sample_rate, True
141
116
  else:
142
- data = trace.data.astype(np.float64)
143
- sample_rate = trace.metadata.sample_rate
144
- is_analog = False
117
+ return trace.data.astype(np.float64), trace.metadata.sample_rate, False
118
+
145
119
 
146
- # Compute basic statistics
120
+ def _prepare_quality_assessment(
121
+ data: NDArray[np.floating[Any]], protocol_params: dict[str, Any] | None
122
+ ) -> tuple[dict[str, float], float, dict[str, Any]]:
123
+ """Prepare data for quality assessment.
124
+
125
+ Args:
126
+ data: Signal data array.
127
+ protocol_params: Optional protocol parameters.
128
+
129
+ Returns:
130
+ Tuple of (stats, voltage_swing, protocol_params).
131
+ """
147
132
  stats = basic_stats(data)
148
133
  voltage_swing = stats["max"] - stats["min"]
134
+ return stats, voltage_swing, protocol_params or {}
135
+
149
136
 
150
- # Protocol parameters
151
- if protocol_params is None:
152
- protocol_params = {}
137
+ def _assess_all_quality_metrics(
138
+ data: NDArray[np.floating[Any]],
139
+ sample_rate: float,
140
+ voltage_swing: float,
141
+ stats: dict[str, float],
142
+ is_analog: bool,
143
+ scenario: AnalysisScenario,
144
+ protocol_params: dict[str, Any],
145
+ ) -> list[QualityMetric]:
146
+ """Assess all quality metrics.
153
147
 
154
- # Assess individual metrics
155
- metrics: list[QualityMetric] = []
148
+ Args:
149
+ data: Signal data array.
150
+ sample_rate: Sample rate in Hz.
151
+ voltage_swing: Peak-to-peak voltage.
152
+ stats: Basic statistics.
153
+ is_analog: Whether signal is analog.
154
+ scenario: Analysis scenario.
155
+ protocol_params: Protocol parameters.
156
156
 
157
- # 1. Sample Rate Assessment
158
- sample_rate_metric = _assess_sample_rate(sample_rate, data, stats, scenario, protocol_params)
159
- metrics.append(sample_rate_metric)
157
+ Returns:
158
+ List of quality metrics.
159
+ """
160
+ return [
161
+ _assess_sample_rate(sample_rate, data, stats, scenario, protocol_params),
162
+ _assess_resolution(data, voltage_swing, stats, is_analog, scenario),
163
+ _assess_duration(len(data), sample_rate, data, scenario, protocol_params),
164
+ _assess_noise(data, voltage_swing, stats, scenario),
165
+ ]
160
166
 
161
- # 2. Resolution Assessment
162
- resolution_metric = _assess_resolution(data, voltage_swing, stats, is_analog, scenario)
163
- metrics.append(resolution_metric)
164
167
 
165
- # 3. Duration Assessment
166
- duration_metric = _assess_duration(len(data), sample_rate, data, scenario, protocol_params)
167
- metrics.append(duration_metric)
168
+ def _determine_overall_quality_status(
169
+ metrics: list[QualityMetric], strict_mode: bool
170
+ ) -> QualityStatus:
171
+ """Determine overall quality status from individual metrics.
168
172
 
169
- # 4. Noise Level Assessment
170
- noise_metric = _assess_noise(data, voltage_swing, stats, scenario)
171
- metrics.append(noise_metric)
173
+ Args:
174
+ metrics: List of quality metrics.
175
+ strict_mode: Whether to fail on warnings.
172
176
 
173
- # Determine overall status
174
- failed_metrics = [m for m in metrics if m.status == "FAIL"]
175
- warning_metrics = [m for m in metrics if m.status == "WARNING"]
177
+ Returns:
178
+ Overall quality status.
179
+ """
180
+ failed = [m for m in metrics if m.status == "FAIL"]
181
+ warnings = [m for m in metrics if m.status == "WARNING"]
176
182
 
177
- if failed_metrics or (strict_mode and warning_metrics):
178
- overall_status: QualityStatus = "FAIL"
179
- elif warning_metrics:
180
- overall_status = "WARNING"
183
+ if failed or (strict_mode and warnings):
184
+ return "FAIL"
185
+ elif warnings:
186
+ return "WARNING"
181
187
  else:
182
- overall_status = "PASS"
188
+ return "PASS"
189
+
190
+
191
+ def _calculate_quality_confidence(metrics: list[QualityMetric]) -> float:
192
+ """Calculate quality confidence score.
183
193
 
184
- # Calculate confidence (higher when more metrics pass)
194
+ Args:
195
+ metrics: List of quality metrics.
196
+
197
+ Returns:
198
+ Confidence score (0.5-1.0).
199
+ """
185
200
  passed_count = sum(1 for m in metrics if m.passed)
186
- confidence = round(0.5 + (passed_count / len(metrics)) * 0.5, 2)
201
+ return round(0.5 + (passed_count / len(metrics)) * 0.5, 2)
202
+
203
+
204
+ def _generate_improvement_suggestions(metrics: list[QualityMetric]) -> list[dict[str, str]]:
205
+ """Generate improvement suggestions from failed metrics.
206
+
207
+ Args:
208
+ metrics: List of quality metrics.
187
209
 
188
- # Generate improvement suggestions
210
+ Returns:
211
+ List of suggestion dictionaries.
212
+ """
189
213
  suggestions = []
190
214
  for metric in metrics:
191
215
  if not metric.passed and metric.recommendation:
@@ -198,6 +222,57 @@ def assess_data_quality(
198
222
  else "Medium",
199
223
  }
200
224
  )
225
+ return suggestions
226
+
227
+
228
+ def assess_data_quality(
229
+ trace: WaveformTrace | DigitalTrace,
230
+ *,
231
+ scenario: AnalysisScenario = "general",
232
+ protocol_params: dict[str, Any] | None = None,
233
+ strict_mode: bool = False,
234
+ ) -> DataQuality:
235
+ """Assess whether captured data is adequate for analysis.
236
+
237
+ Evaluates sample rate, resolution, duration, and noise level against
238
+ scenario-specific requirements.
239
+
240
+ Args:
241
+ trace: Input waveform or digital trace.
242
+ scenario: Analysis scenario for scenario-specific thresholds.
243
+ protocol_params: Protocol-specific parameters (e.g., clock frequency).
244
+ strict_mode: If True, fail on any warnings.
245
+
246
+ Returns:
247
+ DataQuality assessment with overall status and individual metrics.
248
+
249
+ Raises:
250
+ ValueError: If trace is empty or invalid.
251
+
252
+ Example:
253
+ >>> quality = assess_data_quality(trace, scenario='protocol_decode')
254
+ >>> print(f"Overall: {quality.status} (confidence: {quality.confidence:.2f})")
255
+ >>> for metric in quality.metrics:
256
+ ... if metric.status != 'PASS':
257
+ ... print(f"Issue: {metric.name} - {metric.explanation}")
258
+ ... print(f"Fix: {metric.recommendation}")
259
+
260
+ References:
261
+ DISC-009: Data Quality Assessment
262
+ """
263
+ # Setup: extract and prepare signal data
264
+ data, sample_rate, is_analog = _extract_trace_data(trace)
265
+ stats, voltage_swing, protocol_params = _prepare_quality_assessment(data, protocol_params)
266
+
267
+ # Processing: assess individual metrics
268
+ metrics = _assess_all_quality_metrics(
269
+ data, sample_rate, voltage_swing, stats, is_analog, scenario, protocol_params
270
+ )
271
+
272
+ # Formatting: determine overall status and generate report
273
+ overall_status = _determine_overall_quality_status(metrics, strict_mode)
274
+ confidence = _calculate_quality_confidence(metrics)
275
+ suggestions = _generate_improvement_suggestions(metrics)
201
276
 
202
277
  return DataQuality(
203
278
  status=overall_status,
@@ -272,7 +347,10 @@ def _assess_sample_rate(
272
347
  status = "WARNING"
273
348
  passed = False
274
349
  explanation = f"Sample rate is {abs(margin_percent):.0f}% below recommended"
275
- recommendation = f"Increase sample rate to {required_rate / 1e6:.0f} MS/s (currently {sample_rate / 1e6:.0f} MS/s)"
350
+ recommendation = (
351
+ f"Increase sample rate to {required_rate / 1e6:.0f} MS/s "
352
+ f"(currently {sample_rate / 1e6:.0f} MS/s)"
353
+ )
276
354
  else:
277
355
  status = "FAIL"
278
356
  passed = False
@@ -423,8 +501,14 @@ def _assess_duration(
423
501
  elif num_periods >= required_periods * 0.5:
424
502
  status = "WARNING"
425
503
  passed = False
426
- explanation = f"Captured only {num_periods:.0f} signal periods, recommended minimum is {required_periods}"
427
- recommendation = f"Increase capture duration to at least {required_duration * 1e3:.1f} ms (currently {duration_sec * 1e3:.1f} ms)"
504
+ explanation = (
505
+ f"Captured only {num_periods:.0f} signal periods, "
506
+ f"recommended minimum is {required_periods}"
507
+ )
508
+ recommendation = (
509
+ f"Increase capture duration to at least {required_duration * 1e3:.1f} ms "
510
+ f"(currently {duration_sec * 1e3:.1f} ms)"
511
+ )
428
512
  else:
429
513
  status = "FAIL"
430
514
  passed = False
@@ -496,7 +580,10 @@ def _assess_noise(
496
580
  elif noise_percent <= max_noise_percent * 1.5:
497
581
  status = "WARNING"
498
582
  passed = False
499
- explanation = f"Noise level is {noise_percent:.1f}% of signal swing (max recommended: {max_noise_percent:.0f}%)"
583
+ explanation = (
584
+ f"Noise level is {noise_percent:.1f}% of signal swing "
585
+ f"(max recommended: {max_noise_percent:.0f}%)"
586
+ )
500
587
  recommendation = "Reduce noise sources, check grounding, or use averaging"
501
588
  else:
502
589
  status = "FAIL"
@@ -98,121 +98,238 @@ def characterize_signal(
98
98
  References:
99
99
  DISC-001: Automatic Signal Characterization
100
100
  """
101
- # Validate input
101
+ # Validate and extract trace data
102
+ data, sample_rate, is_analog = _validate_and_extract_trace(trace)
103
+
104
+ # Compute basic statistics and voltage levels
105
+ stats = basic_stats(data)
106
+ voltage_low, voltage_high, voltage_swing = _compute_voltage_levels(data)
107
+
108
+ # Run all signal type detectors
109
+ candidates = _detect_all_signal_types(data, sample_rate, voltage_swing, is_analog)
110
+
111
+ # Select best signal type with refinement logic
112
+ best_type, best_confidence = _select_best_signal_type(candidates)
113
+
114
+ # Estimate dominant frequency
115
+ frequency_hz = _estimate_frequency(data, sample_rate)
116
+
117
+ # Extract type-specific parameters and quality metrics
118
+ parameters = _extract_parameters(best_type, data, sample_rate, voltage_low, voltage_high)
119
+ quality_metrics = _compute_quality_metrics(
120
+ data, stats, sample_rate, voltage_low, voltage_high, candidates["digital"]
121
+ )
122
+
123
+ # Prepare alternatives if requested
124
+ alternatives = _build_alternatives(
125
+ candidates,
126
+ best_type,
127
+ best_confidence,
128
+ include_alternatives,
129
+ confidence_threshold,
130
+ min_alternatives,
131
+ )
132
+
133
+ return SignalCharacterization(
134
+ signal_type=best_type,
135
+ confidence=round(best_confidence, 2),
136
+ voltage_low=voltage_low,
137
+ voltage_high=voltage_high,
138
+ frequency_hz=frequency_hz,
139
+ parameters=parameters,
140
+ quality_metrics=quality_metrics,
141
+ alternatives=alternatives,
142
+ )
143
+
144
+
145
+ def _validate_and_extract_trace(
146
+ trace: WaveformTrace | DigitalTrace,
147
+ ) -> tuple[NDArray[np.floating[Any]], float, bool]:
148
+ """Validate trace and extract data, sample rate, and analog flag.
149
+
150
+ Args:
151
+ trace: Input waveform or digital trace.
152
+
153
+ Returns:
154
+ Tuple of (data, sample_rate, is_analog).
155
+
156
+ Raises:
157
+ ValueError: If trace is empty.
158
+ """
102
159
  if len(trace) == 0:
103
160
  raise ValueError("Cannot characterize empty trace")
104
161
 
105
- # Get signal data
106
162
  if isinstance(trace, WaveformTrace):
107
- data = trace.data
108
- sample_rate = trace.metadata.sample_rate
109
- is_analog = True
163
+ return trace.data, trace.metadata.sample_rate, True
110
164
  else:
111
- data = trace.data.astype(np.float64)
112
- sample_rate = trace.metadata.sample_rate
113
- is_analog = False
165
+ return trace.data.astype(np.float64), trace.metadata.sample_rate, False
114
166
 
115
- # Compute basic statistics
116
- stats = basic_stats(data)
117
167
 
118
- # Determine voltage levels using percentiles to be robust to noise
119
- # Use 5th and 95th percentiles to ignore outliers from noise
168
+ def _compute_voltage_levels(
169
+ data: NDArray[np.floating[Any]],
170
+ ) -> tuple[float, float, float]:
171
+ """Compute voltage levels using robust percentiles.
172
+
173
+ Args:
174
+ data: Signal data array.
175
+
176
+ Returns:
177
+ Tuple of (voltage_low, voltage_high, voltage_swing).
178
+ """
120
179
  voltage_low = float(np.percentile(data, 5))
121
180
  voltage_high = float(np.percentile(data, 95))
122
181
  voltage_swing = voltage_high - voltage_low
182
+ return voltage_low, voltage_high, voltage_swing
123
183
 
124
- # Analyze signal characteristics
125
- candidates: dict[SignalType, float] = {}
126
184
 
127
- # Check for digital signal (bimodal distribution)
128
- digital_confidence = _detect_digital(data, voltage_swing)
129
- candidates["digital"] = digital_confidence
185
+ def _detect_all_signal_types(
186
+ data: NDArray[np.floating[Any]],
187
+ sample_rate: float,
188
+ voltage_swing: float,
189
+ is_analog: bool,
190
+ ) -> dict[SignalType, float]:
191
+ """Run all signal type detectors and return confidence scores.
130
192
 
131
- # Check for analog signal (continuous distribution)
132
- analog_confidence = _detect_analog(data, voltage_swing, is_analog)
133
- candidates["analog"] = analog_confidence
193
+ Args:
194
+ data: Signal data array.
195
+ sample_rate: Sample rate in Hz.
196
+ voltage_swing: Peak-to-peak voltage swing.
197
+ is_analog: Whether input is from analog trace.
134
198
 
135
- # Check for PWM (periodic square wave with varying duty cycle)
136
- pwm_confidence = _detect_pwm(data, sample_rate, voltage_swing)
137
- candidates["pwm"] = pwm_confidence
199
+ Returns:
200
+ Dictionary mapping signal types to confidence scores.
201
+ """
202
+ return {
203
+ "digital": _detect_digital(data, voltage_swing),
204
+ "analog": _detect_analog(data, voltage_swing, is_analog),
205
+ "pwm": _detect_pwm(data, sample_rate, voltage_swing),
206
+ "uart": _detect_uart(data, sample_rate, voltage_swing),
207
+ "spi": _detect_spi(data, sample_rate, voltage_swing),
208
+ "i2c": _detect_i2c(data, sample_rate, voltage_swing),
209
+ }
138
210
 
139
- # Check for UART (asynchronous serial with start/stop bits)
140
- uart_confidence = _detect_uart(data, sample_rate, voltage_swing)
141
- candidates["uart"] = uart_confidence
142
211
 
143
- # Check for SPI (synchronous with clock and data)
144
- spi_confidence = _detect_spi(data, sample_rate, voltage_swing)
145
- candidates["spi"] = spi_confidence
212
+ def _select_best_signal_type(
213
+ candidates: dict[SignalType, float],
214
+ ) -> tuple[SignalType, float]:
215
+ """Select best signal type with refinement logic.
146
216
 
147
- # Check for I2C (two-wire with specific patterns)
148
- i2c_confidence = _detect_i2c(data, sample_rate, voltage_swing)
149
- candidates["i2c"] = i2c_confidence
217
+ Args:
218
+ candidates: Dictionary of signal types and confidence scores.
150
219
 
151
- # Select best match
220
+ Returns:
221
+ Tuple of (best_type, best_confidence).
222
+ """
152
223
  sorted_candidates = sorted(candidates.items(), key=lambda x: x[1], reverse=True)
153
224
  best_type, best_confidence = sorted_candidates[0]
154
225
 
155
226
  # If confidence is too low, mark as unknown
156
227
  if best_confidence < 0.5:
157
- best_type = "unknown"
228
+ return "unknown", best_confidence
158
229
 
159
- # If analog won but digital score is meaningful, prefer digital/unknown
160
- # This handles noisy digital signals that look analog-ish
230
+ # Refine analog classification if digital characteristics present
161
231
  if best_type == "analog":
162
- # Check if any protocol detector had reasonable confidence
163
- protocol_confidence = max(
164
- candidates.get("uart", 0),
165
- candidates.get("spi", 0),
166
- candidates.get("pwm", 0),
167
- )
168
-
169
- # If digital or protocol detectors have some confidence, don't call it purely analog
170
- if digital_confidence > 0.3 or protocol_confidence > 0.2:
171
- # Signal has digital characteristics - don't call it analog
172
- if protocol_confidence > 0.3:
173
- best_type = "unknown" # Too noisy/ambiguous to classify as specific protocol
174
- best_confidence = protocol_confidence
175
- elif digital_confidence > 0.4:
176
- best_type = "digital" # Generic digital signal
177
- best_confidence = digital_confidence
178
- else:
179
- best_type = "unknown" # Too ambiguous
180
- best_confidence = max(digital_confidence, protocol_confidence, analog_confidence)
232
+ return _refine_analog_classification(candidates)
181
233
 
182
- # Estimate dominant frequency
183
- frequency_hz = _estimate_frequency(data, sample_rate)
234
+ return best_type, best_confidence
184
235
 
185
- # Extract type-specific parameters
186
- parameters = _extract_parameters(best_type, data, sample_rate, voltage_low, voltage_high)
187
236
 
188
- # Calculate quality metrics with improved noise estimation
237
+ def _refine_analog_classification(
238
+ candidates: dict[SignalType, float],
239
+ ) -> tuple[SignalType, float]:
240
+ """Refine analog classification when digital characteristics are present.
241
+
242
+ Args:
243
+ candidates: Dictionary of signal types and confidence scores.
244
+
245
+ Returns:
246
+ Tuple of (refined_type, confidence).
247
+ """
248
+ digital_confidence = candidates.get("digital", 0)
249
+ analog_confidence = candidates.get("analog", 0)
250
+ protocol_confidence = max(
251
+ candidates.get("uart", 0),
252
+ candidates.get("spi", 0),
253
+ candidates.get("pwm", 0),
254
+ )
255
+
256
+ # If digital or protocol detectors have some confidence, don't call it purely analog
257
+ if digital_confidence > 0.3 or protocol_confidence > 0.2:
258
+ if protocol_confidence > 0.3:
259
+ return "unknown", protocol_confidence
260
+ elif digital_confidence > 0.4:
261
+ return "digital", digital_confidence
262
+ else:
263
+ return "unknown", max(digital_confidence, protocol_confidence, analog_confidence)
264
+
265
+ return "analog", analog_confidence
266
+
267
+
268
+ def _compute_quality_metrics(
269
+ data: NDArray[np.floating[Any]],
270
+ stats: dict[str, float],
271
+ sample_rate: float,
272
+ voltage_low: float,
273
+ voltage_high: float,
274
+ digital_confidence: float,
275
+ ) -> dict[str, float]:
276
+ """Compute signal quality metrics.
277
+
278
+ Args:
279
+ data: Signal data array.
280
+ stats: Basic statistics dictionary.
281
+ sample_rate: Sample rate in Hz.
282
+ voltage_low: Low voltage level.
283
+ voltage_high: High voltage level.
284
+ digital_confidence: Confidence that signal is digital.
285
+
286
+ Returns:
287
+ Dictionary of quality metrics.
288
+ """
189
289
  noise_level = _estimate_noise_level(data, voltage_low, voltage_high, digital_confidence)
190
- quality_metrics = {
290
+ return {
191
291
  "snr_db": _estimate_snr(data, stats),
192
292
  "jitter_ns": _estimate_jitter(data, sample_rate) * 1e9,
193
293
  "noise_level": noise_level,
194
294
  }
195
295
 
196
- # Prepare alternatives
296
+
297
+ def _build_alternatives(
298
+ candidates: dict[SignalType, float],
299
+ best_type: SignalType,
300
+ best_confidence: float,
301
+ include_alternatives: bool,
302
+ confidence_threshold: float,
303
+ min_alternatives: int,
304
+ ) -> list[tuple[SignalType, float]]:
305
+ """Build list of alternative signal type suggestions.
306
+
307
+ Args:
308
+ candidates: All candidate types with confidence scores.
309
+ best_type: Best detected signal type.
310
+ best_confidence: Confidence of best type.
311
+ include_alternatives: Whether to include alternatives.
312
+ confidence_threshold: Threshold for including alternatives.
313
+ min_alternatives: Minimum number of alternatives.
314
+
315
+ Returns:
316
+ List of (signal_type, confidence) tuples.
317
+ """
197
318
  alternatives: list[tuple[SignalType, float]] = []
198
- if include_alternatives or best_confidence < confidence_threshold:
199
- # Include top alternatives (excluding the winner)
200
- for sig_type, conf in sorted_candidates[1:]:
201
- if len(alternatives) >= min_alternatives:
202
- break
203
- if conf >= 0.3: # Only include reasonable alternatives
204
- alternatives.append((sig_type, conf))
205
319
 
206
- return SignalCharacterization(
207
- signal_type=best_type,
208
- confidence=round(best_confidence, 2),
209
- voltage_low=voltage_low,
210
- voltage_high=voltage_high,
211
- frequency_hz=frequency_hz,
212
- parameters=parameters,
213
- quality_metrics=quality_metrics,
214
- alternatives=alternatives,
215
- )
320
+ if not (include_alternatives or best_confidence < confidence_threshold):
321
+ return alternatives
322
+
323
+ sorted_candidates = sorted(candidates.items(), key=lambda x: x[1], reverse=True)
324
+
325
+ # Include top alternatives (excluding the winner)
326
+ for sig_type, conf in sorted_candidates[1:]:
327
+ if len(alternatives) >= min_alternatives:
328
+ break
329
+ if conf >= 0.3: # Only include reasonable alternatives
330
+ alternatives.append((sig_type, conf))
331
+
332
+ return alternatives
216
333
 
217
334
 
218
335
  def _estimate_noise_level(
oscura/export/__init__.py CHANGED
@@ -5,33 +5,31 @@ to various formats for integration with other tools and workflows.
5
5
 
6
6
  Supported export formats:
7
7
  - Wireshark Lua dissectors
8
- - (Future) Scapy packet definitions
9
- - (Future) Kaitai Struct definitions
8
+ - Kaitai Struct definitions (.ksy)
9
+ - Scapy packet layers
10
10
  - (Future) C/C++ parser code
11
11
 
12
12
  Example:
13
- >>> from oscura.export.wireshark import WiresharkDissectorGenerator
14
- >>> from oscura.inference.protocol_dsl import ProtocolDefinition
15
- >>> protocol = ProtocolDefinition(name="myproto", description="My Protocol")
16
- >>> generator = WiresharkDissectorGenerator()
17
- >>> generator.generate(protocol, Path("myproto.lua"))
13
+ >>> from oscura.export.wireshark_dissector import WiresharkDissectorGenerator
14
+ >>> from oscura.export.kaitai_struct import KaitaiStructGenerator
15
+ >>> from oscura.export.scapy_layer import ScapyLayerGenerator
16
+ >>> from oscura.workflows.reverse_engineering import ProtocolSpec
17
+ >>> # Generate Wireshark dissector
18
+ >>> wireshark_gen = WiresharkDissectorGenerator(config)
19
+ >>> wireshark_gen.generate(spec, Path("myproto.lua"))
20
+ >>> # Generate Kaitai Struct definition
21
+ >>> kaitai_gen = KaitaiStructGenerator(config)
22
+ >>> kaitai_gen.generate(spec, Path("myproto.ksy"))
23
+ >>> # Generate Scapy layer
24
+ >>> scapy_gen = ScapyLayerGenerator(config)
25
+ >>> scapy_gen.generate(spec, messages, Path("proto_layer.py"))
18
26
  """
19
27
 
20
28
  # Import main exports
21
- from . import wireshark
22
- from .wavedrom import (
23
- WaveDromBuilder,
24
- WaveDromEdge,
25
- WaveDromSignal,
26
- export_wavedrom,
27
- from_digital_trace,
28
- )
29
+ from . import kaitai_struct, scapy_layer, wireshark
29
30
 
30
31
  __all__ = [
31
- "WaveDromBuilder",
32
- "WaveDromEdge",
33
- "WaveDromSignal",
34
- "export_wavedrom",
35
- "from_digital_trace",
32
+ "kaitai_struct",
33
+ "scapy_layer",
36
34
  "wireshark",
37
35
  ]