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
@@ -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,21 +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
29
+ from . import kaitai_struct, scapy_layer, wireshark
22
30
 
23
31
  __all__ = [
32
+ "kaitai_struct",
33
+ "scapy_layer",
24
34
  "wireshark",
25
35
  ]