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
@@ -61,15 +61,15 @@ class UARTDecoder(AsyncDecoder):
61
61
  longname = "Universal Asynchronous Receiver/Transmitter"
62
62
  desc = "UART/RS-232 serial protocol decoder"
63
63
 
64
- channels = [ # noqa: RUF012
64
+ channels = [
65
65
  ChannelDef("rx", "RX", "Receive data line", required=True),
66
66
  ]
67
67
 
68
- optional_channels = [ # noqa: RUF012
68
+ optional_channels = [
69
69
  ChannelDef("tx", "TX", "Transmit data line", required=False),
70
70
  ]
71
71
 
72
- options = [ # noqa: RUF012
72
+ options = [
73
73
  OptionDef("baudrate", "Baud rate", "Bits per second", default=0, values=None),
74
74
  OptionDef(
75
75
  "data_bits",
@@ -102,7 +102,7 @@ class UARTDecoder(AsyncDecoder):
102
102
  OptionDef("idle_level", "Idle level", "Idle line level", default=1, values=[0, 1]),
103
103
  ]
104
104
 
105
- annotations = [ # noqa: RUF012
105
+ annotations = [
106
106
  ("bit", "Bit value"),
107
107
  ("start", "Start bit"),
108
108
  ("data", "Data bits"),
@@ -164,155 +164,297 @@ class UARTDecoder(AsyncDecoder):
164
164
  >>> for packet in decoder.decode(trace):
165
165
  ... print(f"Byte: {packet.data.hex()}")
166
166
  """
167
- # Convert to digital if needed
167
+ digital_trace = self._convert_to_digital(trace)
168
+ data = digital_trace.data
169
+ sample_rate = digital_trace.metadata.sample_rate
170
+
171
+ self._auto_detect_baudrate(digital_trace)
172
+ bit_period = sample_rate / self._baudrate
173
+ frame_bits = self._calculate_frame_bits()
174
+
175
+ idx = 0
176
+ frame_num = 0
177
+
178
+ while idx < len(data) - int(frame_bits * bit_period):
179
+ start_idx = self._find_start_bit(data, idx)
180
+ if start_idx is None:
181
+ break
182
+
183
+ sample_points = self._get_sample_points(start_idx, bit_period, frame_bits, len(data))
184
+ if len(sample_points) < 1 + self._data_bits:
185
+ break
186
+
187
+ if not self._verify_start_bit(data, sample_points):
188
+ idx = start_idx + 1
189
+ continue
190
+
191
+ data_value, data_bits = self._extract_data_bits(data, sample_points)
192
+ errors = self._validate_frame(data, sample_points, data_bits)
193
+
194
+ packet = self._build_packet(
195
+ start_idx,
196
+ sample_rate,
197
+ bit_period,
198
+ frame_bits,
199
+ data_value,
200
+ data_bits,
201
+ frame_num,
202
+ errors,
203
+ )
204
+
205
+ yield packet
206
+
207
+ frame_num += 1
208
+ last_sample = sample_points[-1] if sample_points else start_idx
209
+ idx = last_sample + 1
210
+
211
+ def _convert_to_digital(self, trace: DigitalTrace | WaveformTrace) -> DigitalTrace:
212
+ """Convert trace to digital format if needed.
213
+
214
+ Args:
215
+ trace: Input trace.
216
+
217
+ Returns:
218
+ Digital trace.
219
+ """
168
220
  if isinstance(trace, WaveformTrace):
169
221
  from oscura.analyzers.digital.extraction import to_digital
170
222
 
171
- digital_trace = to_digital(trace, threshold="auto")
172
- else:
173
- digital_trace = trace
223
+ return to_digital(trace, threshold="auto")
224
+ return trace
174
225
 
175
- data = digital_trace.data
176
- sample_rate = digital_trace.metadata.sample_rate
226
+ def _auto_detect_baudrate(self, trace: DigitalTrace) -> None:
227
+ """Auto-detect baud rate if not specified.
177
228
 
178
- # Auto-detect baud rate if not specified
229
+ Args:
230
+ trace: Digital trace for detection.
231
+ """
179
232
  if self._baudrate == 0:
180
233
  from oscura.utils.autodetect import detect_baud_rate
181
234
 
182
- self._baudrate = detect_baud_rate(digital_trace) # type: ignore[assignment]
235
+ self._baudrate = detect_baud_rate(trace) # type: ignore[assignment]
183
236
  if self._baudrate == 0:
184
237
  self._baudrate = 9600 # Fallback
185
238
 
186
- bit_period = sample_rate / self._baudrate
187
- half_bit = bit_period / 2
239
+ def _calculate_frame_bits(self) -> float:
240
+ """Calculate total bits per frame.
188
241
 
189
- # Frame structure
242
+ Returns:
243
+ Number of bits in frame.
244
+ """
190
245
  frame_bits = 1 + self._data_bits # Start + data
191
246
  if self._parity != "none":
192
247
  frame_bits += 1
193
248
  frame_bits += self._stop_bits # type: ignore[assignment]
249
+ return frame_bits
194
250
 
195
- idx = 0
196
- frame_num = 0
251
+ def _get_sample_points(
252
+ self, start_idx: int, bit_period: float, frame_bits: float, data_len: int
253
+ ) -> list[int]:
254
+ """Get sample points for each bit in frame.
197
255
 
198
- while idx < len(data) - int(frame_bits * bit_period):
199
- # Look for start bit (transition from idle)
200
- start_idx = self._find_start_bit(data, idx)
201
- if start_idx is None:
202
- break
256
+ Args:
257
+ start_idx: Frame start index.
258
+ bit_period: Samples per bit.
259
+ frame_bits: Total bits in frame.
260
+ data_len: Length of data array.
203
261
 
204
- # Sample at center of each bit
205
- sample_points = []
206
- for bit_num in range(int(frame_bits)):
207
- sample_idx = int(start_idx + half_bit + bit_num * bit_period)
208
- if sample_idx < len(data):
209
- sample_points.append(sample_idx)
262
+ Returns:
263
+ List of sample indices.
264
+ """
265
+ half_bit = bit_period / 2
266
+ sample_points = []
267
+ for bit_num in range(int(frame_bits)):
268
+ sample_idx = int(start_idx + half_bit + bit_num * bit_period)
269
+ if sample_idx < data_len:
270
+ sample_points.append(sample_idx)
271
+ return sample_points
210
272
 
211
- if len(sample_points) < 1 + self._data_bits:
212
- break
273
+ def _verify_start_bit(self, data: NDArray[np.bool_], sample_points: list[int]) -> bool:
274
+ """Verify start bit is valid.
213
275
 
214
- # Verify start bit (should be opposite of idle)
215
- start_bit = data[sample_points[0]]
216
- if (self._idle_level == 1 and start_bit) or (self._idle_level == 0 and not start_bit):
217
- # Not a valid start bit
218
- idx = start_idx + 1
219
- continue
276
+ Args:
277
+ data: Digital data array.
278
+ sample_points: Sample point indices.
220
279
 
221
- # Extract data bits
222
- data_value = 0
223
- data_bits = []
224
-
225
- for i in range(self._data_bits):
226
- bit_idx = sample_points[1 + i]
227
- bit_val = 1 if data[bit_idx] else 0
228
- data_bits.append(bit_val)
229
-
230
- if self._bit_order == "lsb":
231
- data_value |= bit_val << i
232
- else:
233
- data_value |= bit_val << (self._data_bits - 1 - i)
234
-
235
- # Check parity if enabled
236
- errors = []
237
- parity_idx = 1 + self._data_bits
238
-
239
- if self._parity != "none" and parity_idx < len(sample_points):
240
- parity_bit = 1 if data[sample_points[parity_idx]] else 0
241
- ones_count = sum(data_bits)
242
-
243
- if self._parity == "odd":
244
- expected = (ones_count + 1) % 2
245
- elif self._parity == "even":
246
- expected = ones_count % 2
247
- elif self._parity == "mark":
248
- expected = 1
249
- else: # space
250
- expected = 0
251
-
252
- if parity_bit != expected:
253
- errors.append("Parity error")
254
-
255
- # Verify stop bit(s)
256
- stop_idx = parity_idx + (1 if self._parity != "none" else 0)
257
- if stop_idx < len(sample_points):
258
- stop_bit = data[sample_points[stop_idx]]
259
- expected_stop = self._idle_level == 1
260
-
261
- if stop_bit != expected_stop:
262
- errors.append("Framing error")
263
-
264
- # Calculate timestamps
265
- start_time = start_idx / sample_rate
266
- end_time = (start_idx + frame_bits * bit_period) / sample_rate
267
-
268
- # Add annotations
269
- self.put_annotation(
270
- start_time,
271
- start_time + bit_period / sample_rate,
272
- AnnotationLevel.BITS,
273
- "START",
274
- )
280
+ Returns:
281
+ True if start bit valid.
282
+ """
283
+ start_bit = data[sample_points[0]]
284
+ expected_start = self._idle_level == 0
285
+ return bool(start_bit == expected_start)
275
286
 
276
- for i, bit_val in enumerate(data_bits):
277
- bit_start = start_time + (1 + i) * bit_period / sample_rate
278
- bit_end = bit_start + bit_period / sample_rate
279
- self.put_annotation(
280
- bit_start,
281
- bit_end,
282
- AnnotationLevel.BITS,
283
- str(bit_val),
284
- )
287
+ def _extract_data_bits(
288
+ self, data: NDArray[np.bool_], sample_points: list[int]
289
+ ) -> tuple[int, list[int]]:
290
+ """Extract data bits and convert to byte value.
285
291
 
286
- self.put_annotation(
287
- start_time,
288
- end_time,
289
- AnnotationLevel.BYTES,
290
- f"0x{data_value:02X}",
291
- data=bytes([data_value]),
292
- )
292
+ Args:
293
+ data: Digital data array.
294
+ sample_points: Sample point indices.
293
295
 
294
- # Create packet
295
- packet = ProtocolPacket(
296
- timestamp=start_time,
297
- protocol="uart",
298
- data=bytes([data_value]),
299
- annotations={
300
- "frame_num": frame_num,
301
- "data_bits": data_bits,
302
- "baudrate": self._baudrate,
303
- },
304
- errors=errors,
305
- )
296
+ Returns:
297
+ Tuple of (byte_value, bit_list).
298
+ """
299
+ data_value = 0
300
+ data_bits = []
306
301
 
307
- self.put_packet(start_time, bytes([data_value]), packet.annotations, errors)
302
+ for i in range(self._data_bits):
303
+ bit_idx = sample_points[1 + i]
304
+ bit_val = 1 if data[bit_idx] else 0
305
+ data_bits.append(bit_val)
308
306
 
309
- yield packet
307
+ if self._bit_order == "lsb":
308
+ data_value |= bit_val << i
309
+ else:
310
+ data_value |= bit_val << (self._data_bits - 1 - i)
310
311
 
311
- frame_num += 1
312
- # Advance to the end of the frame
313
- # Use the last sample point + 1 to avoid re-detecting the same frame
314
- last_sample = sample_points[-1] if sample_points else start_idx
315
- idx = last_sample + 1
312
+ return data_value, data_bits
313
+
314
+ def _validate_frame(
315
+ self, data: NDArray[np.bool_], sample_points: list[int], data_bits: list[int]
316
+ ) -> list[str]:
317
+ """Validate parity and stop bits.
318
+
319
+ Args:
320
+ data: Digital data array.
321
+ sample_points: Sample point indices.
322
+ data_bits: Extracted data bits.
323
+
324
+ Returns:
325
+ List of error messages.
326
+ """
327
+ errors = []
328
+ parity_idx = 1 + self._data_bits
329
+
330
+ # Check parity
331
+ if self._parity != "none" and parity_idx < len(sample_points):
332
+ parity_bit = 1 if data[sample_points[parity_idx]] else 0
333
+ expected_parity = self._calculate_parity(data_bits)
334
+ if parity_bit != expected_parity:
335
+ errors.append("Parity error")
336
+
337
+ # Verify stop bit
338
+ stop_idx = parity_idx + (1 if self._parity != "none" else 0)
339
+ if stop_idx < len(sample_points):
340
+ stop_bit = data[sample_points[stop_idx]]
341
+ expected_stop = self._idle_level == 1
342
+ if stop_bit != expected_stop:
343
+ errors.append("Framing error")
344
+
345
+ return errors
346
+
347
+ def _calculate_parity(self, data_bits: list[int]) -> int:
348
+ """Calculate expected parity bit.
349
+
350
+ Args:
351
+ data_bits: Data bit values.
352
+
353
+ Returns:
354
+ Expected parity bit (0 or 1).
355
+ """
356
+ ones_count = sum(data_bits)
357
+
358
+ if self._parity == "odd":
359
+ return (ones_count + 1) % 2
360
+ elif self._parity == "even":
361
+ return ones_count % 2
362
+ elif self._parity == "mark":
363
+ return 1
364
+ else: # space
365
+ return 0
366
+
367
+ def _build_packet(
368
+ self,
369
+ start_idx: int,
370
+ sample_rate: float,
371
+ bit_period: float,
372
+ frame_bits: float,
373
+ data_value: int,
374
+ data_bits: list[int],
375
+ frame_num: int,
376
+ errors: list[str],
377
+ ) -> ProtocolPacket:
378
+ """Build protocol packet with annotations.
379
+
380
+ Args:
381
+ start_idx: Frame start index.
382
+ sample_rate: Sample rate in Hz.
383
+ bit_period: Samples per bit.
384
+ frame_bits: Total bits in frame.
385
+ data_value: Decoded byte value.
386
+ data_bits: Individual bit values.
387
+ frame_num: Frame number.
388
+ errors: List of errors.
389
+
390
+ Returns:
391
+ Protocol packet.
392
+ """
393
+ start_time = start_idx / sample_rate
394
+
395
+ # Add annotations
396
+ self._add_frame_annotations(start_time, bit_period, sample_rate, data_value, data_bits)
397
+
398
+ packet = ProtocolPacket(
399
+ timestamp=start_time,
400
+ protocol="uart",
401
+ data=bytes([data_value]),
402
+ annotations={
403
+ "frame_num": frame_num,
404
+ "data_bits": data_bits,
405
+ "baudrate": self._baudrate,
406
+ },
407
+ errors=errors,
408
+ )
409
+
410
+ self.put_packet(start_time, bytes([data_value]), packet.annotations, errors)
411
+ return packet
412
+
413
+ def _add_frame_annotations(
414
+ self,
415
+ start_time: float,
416
+ bit_period: float,
417
+ sample_rate: float,
418
+ data_value: int,
419
+ data_bits: list[int],
420
+ ) -> None:
421
+ """Add bit-level and byte-level annotations.
422
+
423
+ Args:
424
+ start_time: Frame start time.
425
+ bit_period: Samples per bit.
426
+ sample_rate: Sample rate in Hz.
427
+ data_value: Decoded byte value.
428
+ data_bits: Individual bit values.
429
+ """
430
+ # Start bit annotation
431
+ self.put_annotation(
432
+ start_time,
433
+ start_time + bit_period / sample_rate,
434
+ AnnotationLevel.BITS,
435
+ "START",
436
+ )
437
+
438
+ # Data bit annotations
439
+ for i, bit_val in enumerate(data_bits):
440
+ bit_start = start_time + (1 + i) * bit_period / sample_rate
441
+ bit_end = bit_start + bit_period / sample_rate
442
+ self.put_annotation(
443
+ bit_start,
444
+ bit_end,
445
+ AnnotationLevel.BITS,
446
+ str(bit_val),
447
+ )
448
+
449
+ # Byte annotation
450
+ end_time = start_time + (1 + len(data_bits)) * bit_period / sample_rate
451
+ self.put_annotation(
452
+ start_time,
453
+ end_time,
454
+ AnnotationLevel.BYTES,
455
+ f"0x{data_value:02X}",
456
+ data=bytes([data_value]),
457
+ )
316
458
 
317
459
  def _find_start_bit(
318
460
  self,