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
oscura/utils/memory.py CHANGED
@@ -245,86 +245,149 @@ def estimate_memory(
245
245
  >>> estimate = estimate_memory('fft', samples=1e9, nfft=8192)
246
246
  >>> print(f"Required: {estimate.total / 1e9:.2f} GB")
247
247
  """
248
- # Bytes per element
249
248
  bytes_per_sample = 4 if dtype == "float32" else 8
250
-
251
249
  samples = int(samples or 0)
252
250
 
253
- # Calculate based on operation
254
- if operation == "fft":
255
- nfft = nfft or _next_power_of_2(samples)
256
- data_mem = samples * bytes_per_sample * channels
257
- # FFT needs complex output (2x) plus work buffer
258
- intermediate_mem = nfft * bytes_per_sample * 2 * 2 # complex, work buffer
259
- output_mem = (nfft // 2 + 1) * bytes_per_sample * 2 * channels # complex output
251
+ # Dispatch to operation-specific estimator (returns computed params)
252
+ data_mem, intermediate_mem, output_mem, computed_params = _estimate_for_operation(
253
+ operation, samples, bytes_per_sample, channels, nfft, nperseg, noverlap, kwargs
254
+ )
260
255
 
261
- elif operation == "psd":
262
- nperseg = nperseg or 256
263
- nfft = nfft or nperseg
264
- data_mem = samples * bytes_per_sample * channels
265
- # Welch needs segment buffer plus FFT work
266
- intermediate_mem = nperseg * bytes_per_sample * 2 + nfft * bytes_per_sample * 2
267
- output_mem = (nfft // 2 + 1) * bytes_per_sample * channels
256
+ total_mem = data_mem + intermediate_mem + output_mem
268
257
 
269
- elif operation == "spectrogram":
270
- nperseg = nperseg or 256
271
- noverlap = noverlap or nperseg // 2
272
- nfft = nfft or nperseg
273
- hop = nperseg - noverlap
274
- num_segments = max(1, (samples - noverlap) // hop)
258
+ # Merge provided and computed parameters
259
+ all_params = {
260
+ "samples": samples,
261
+ "dtype": dtype,
262
+ "channels": channels,
263
+ **computed_params, # Include computed defaults
264
+ **kwargs,
265
+ }
275
266
 
276
- data_mem = samples * bytes_per_sample * channels
277
- # STFT needs segment buffer
278
- intermediate_mem = nperseg * bytes_per_sample * 2 + nfft * bytes_per_sample * 2
279
- # Output: (nfft//2+1) frequencies x num_segments times
280
- output_mem = (nfft // 2 + 1) * num_segments * bytes_per_sample * 2 * channels
267
+ return MemoryEstimate(
268
+ data=data_mem,
269
+ intermediate=intermediate_mem,
270
+ output=output_mem,
271
+ total=total_mem,
272
+ operation=operation,
273
+ parameters=all_params,
274
+ )
281
275
 
282
- elif operation == "eye_diagram":
283
- samples_per_ui = kwargs.get("samples_per_ui", 100)
284
- num_uis = kwargs.get("num_uis", 1000)
285
- data_mem = samples * bytes_per_sample * channels
286
- # Eye diagram accumulates traces
287
- intermediate_mem = samples_per_ui * num_uis * bytes_per_sample
288
- output_mem = samples_per_ui * num_uis * bytes_per_sample
289
276
 
290
- elif operation == "correlate":
291
- data_mem = samples * bytes_per_sample * 2 * channels # Two signals
292
- # FFT-based correlation
293
- nfft = _next_power_of_2(samples * 2)
294
- intermediate_mem = nfft * bytes_per_sample * 2 * 2 # Two FFTs
295
- output_mem = (samples * 2 - 1) * bytes_per_sample * channels
277
+ def _estimate_for_operation(
278
+ operation: str,
279
+ samples: int,
280
+ bytes_per_sample: int,
281
+ channels: int,
282
+ nfft: int | None,
283
+ nperseg: int | None,
284
+ noverlap: int | None,
285
+ kwargs: dict[str, Any],
286
+ ) -> tuple[int, int, int, dict[str, Any]]:
287
+ """Estimate memory for specific operation.
296
288
 
289
+ Returns:
290
+ Tuple of (data_mem, intermediate_mem, output_mem, computed_params)
291
+ """
292
+ if operation == "fft":
293
+ return _estimate_fft(samples, bytes_per_sample, channels, nfft)
294
+ elif operation == "psd":
295
+ return _estimate_psd(samples, bytes_per_sample, channels, nfft, nperseg)
296
+ elif operation == "spectrogram":
297
+ return _estimate_spectrogram(samples, bytes_per_sample, channels, nfft, nperseg, noverlap)
298
+ elif operation == "eye_diagram":
299
+ return _estimate_eye_diagram(samples, bytes_per_sample, channels, kwargs)
300
+ elif operation == "correlate":
301
+ return _estimate_correlate(samples, bytes_per_sample, channels)
297
302
  elif operation == "filter":
298
- filter_order = kwargs.get("filter_order", 8)
299
- data_mem = samples * bytes_per_sample * channels
300
- # Filter state and buffer
301
- intermediate_mem = (filter_order + samples) * bytes_per_sample
302
- output_mem = samples * bytes_per_sample * channels
303
-
303
+ return _estimate_filter(samples, bytes_per_sample, channels, kwargs)
304
304
  else:
305
305
  # Generic estimate
306
306
  data_mem = samples * bytes_per_sample * channels
307
- intermediate_mem = samples * bytes_per_sample
308
- output_mem = samples * bytes_per_sample * channels
307
+ return data_mem, data_mem, data_mem, {}
308
+
309
+
310
+ def _estimate_fft(
311
+ samples: int, bytes_per_sample: int, channels: int, nfft: int | None
312
+ ) -> tuple[int, int, int, dict[str, Any]]:
313
+ """Estimate memory for FFT operation."""
314
+ nfft = nfft or _next_power_of_2(samples)
315
+ data_mem = samples * bytes_per_sample * channels
316
+ intermediate_mem = nfft * bytes_per_sample * 2 * 2 # complex, work buffer
317
+ output_mem = (nfft // 2 + 1) * bytes_per_sample * 2 * channels
318
+ return data_mem, intermediate_mem, output_mem, {"nfft": nfft}
319
+
320
+
321
+ def _estimate_psd(
322
+ samples: int, bytes_per_sample: int, channels: int, nfft: int | None, nperseg: int | None
323
+ ) -> tuple[int, int, int, dict[str, Any]]:
324
+ """Estimate memory for PSD (Welch) operation."""
325
+ nperseg = nperseg or 256
326
+ nfft = nfft or nperseg
327
+ data_mem = samples * bytes_per_sample * channels
328
+ intermediate_mem = nperseg * bytes_per_sample * 2 + nfft * bytes_per_sample * 2
329
+ output_mem = (nfft // 2 + 1) * bytes_per_sample * channels
330
+ return data_mem, intermediate_mem, output_mem, {"nfft": nfft, "nperseg": nperseg}
331
+
332
+
333
+ def _estimate_spectrogram(
334
+ samples: int,
335
+ bytes_per_sample: int,
336
+ channels: int,
337
+ nfft: int | None,
338
+ nperseg: int | None,
339
+ noverlap: int | None,
340
+ ) -> tuple[int, int, int, dict[str, Any]]:
341
+ """Estimate memory for spectrogram (STFT) operation."""
342
+ nperseg = nperseg or 256
343
+ noverlap = noverlap or nperseg // 2
344
+ nfft = nfft or nperseg
345
+ hop = nperseg - noverlap
346
+ num_segments = max(1, (samples - noverlap) // hop)
347
+
348
+ data_mem = samples * bytes_per_sample * channels
349
+ intermediate_mem = nperseg * bytes_per_sample * 2 + nfft * bytes_per_sample * 2
350
+ output_mem = (nfft // 2 + 1) * num_segments * bytes_per_sample * 2 * channels
351
+ return (
352
+ data_mem,
353
+ intermediate_mem,
354
+ output_mem,
355
+ {"nfft": nfft, "nperseg": nperseg, "noverlap": noverlap},
356
+ )
309
357
 
310
- total_mem = data_mem + intermediate_mem + output_mem
311
358
 
312
- return MemoryEstimate(
313
- data=data_mem,
314
- intermediate=intermediate_mem,
315
- output=output_mem,
316
- total=total_mem,
317
- operation=operation,
318
- parameters={
319
- "samples": samples,
320
- "nfft": nfft,
321
- "nperseg": nperseg,
322
- "noverlap": noverlap,
323
- "dtype": dtype,
324
- "channels": channels,
325
- **kwargs,
326
- },
327
- )
359
+ def _estimate_eye_diagram(
360
+ samples: int, bytes_per_sample: int, channels: int, kwargs: dict[str, Any]
361
+ ) -> tuple[int, int, int, dict[str, Any]]:
362
+ """Estimate memory for eye diagram generation."""
363
+ samples_per_ui = kwargs.get("samples_per_ui", 100)
364
+ num_uis = kwargs.get("num_uis", 1000)
365
+ data_mem = samples * bytes_per_sample * channels
366
+ intermediate_mem = samples_per_ui * num_uis * bytes_per_sample
367
+ output_mem = samples_per_ui * num_uis * bytes_per_sample
368
+ return data_mem, intermediate_mem, output_mem, {}
369
+
370
+
371
+ def _estimate_correlate(
372
+ samples: int, bytes_per_sample: int, channels: int
373
+ ) -> tuple[int, int, int, dict[str, Any]]:
374
+ """Estimate memory for correlation operation."""
375
+ data_mem = samples * bytes_per_sample * 2 * channels # Two signals
376
+ nfft = _next_power_of_2(samples * 2)
377
+ intermediate_mem = nfft * bytes_per_sample * 2 * 2 # Two FFTs
378
+ output_mem = (samples * 2 - 1) * bytes_per_sample * channels
379
+ return data_mem, intermediate_mem, output_mem, {"nfft": nfft}
380
+
381
+
382
+ def _estimate_filter(
383
+ samples: int, bytes_per_sample: int, channels: int, kwargs: dict[str, Any]
384
+ ) -> tuple[int, int, int, dict[str, Any]]:
385
+ """Estimate memory for filter operation."""
386
+ filter_order = kwargs.get("filter_order", 8)
387
+ data_mem = samples * bytes_per_sample * channels
388
+ intermediate_mem = (filter_order + samples) * bytes_per_sample
389
+ output_mem = samples * bytes_per_sample * channels
390
+ return data_mem, intermediate_mem, output_mem, {}
328
391
 
329
392
 
330
393
  def check_memory_available(
@@ -552,7 +615,7 @@ def configure_memory(
552
615
  >>> configure_memory(max_memory="4GB", warn_threshold=0.7, critical_threshold=0.9)
553
616
  >>> configure_memory(auto_degrade=True)
554
617
  """
555
- global _memory_config # noqa: PLW0602
618
+ global _memory_config
556
619
 
557
620
  if max_memory is not None:
558
621
  if isinstance(max_memory, str):
@@ -10,9 +10,11 @@ from __future__ import annotations
10
10
  import contextlib
11
11
  import gc
12
12
  import hashlib
13
+ import hmac
13
14
  import json
14
15
  import logging
15
16
  import pickle
17
+ import secrets
16
18
  import tempfile
17
19
  import threading
18
20
  import time
@@ -24,6 +26,8 @@ from typing import TYPE_CHECKING, Any, Generic, TypeVar
24
26
 
25
27
  import numpy as np
26
28
 
29
+ from oscura.core.exceptions import SecurityError
30
+
27
31
  if TYPE_CHECKING:
28
32
  from collections.abc import Iterator
29
33
 
@@ -573,7 +577,7 @@ class AdaptiveMeasurementSelector:
573
577
  """
574
578
 
575
579
  # Default size thresholds (in samples)
576
- THRESHOLDS = { # noqa: RUF012
580
+ THRESHOLDS = {
577
581
  "eye_diagram": 1e8, # 100M samples
578
582
  "spectrogram": 5e8, # 500M samples
579
583
  "full_correlation": 1e9, # 1B samples
@@ -730,13 +734,21 @@ class CacheInvalidationStrategy:
730
734
  self._misses = 0
731
735
 
732
736
  def _compute_hash(self, data: Any) -> str:
733
- """Compute hash of data for comparison."""
737
+ """Compute hash of data for comparison.
738
+
739
+ Note:
740
+ Uses MD5 for cache invalidation checksums only (not for security).
741
+ MD5 is appropriate here for non-cryptographic data comparison.
742
+ """
734
743
  if isinstance(data, np.ndarray):
735
- return hashlib.md5(data.tobytes()[:1024]).hexdigest()
744
+ # Sample first 1KB for performance (cache invalidation only, not security)
745
+ return hashlib.md5(data.tobytes()[:1024], usedforsecurity=False).hexdigest()
736
746
  elif isinstance(data, dict | list):
737
- return hashlib.md5(json.dumps(data, sort_keys=True).encode()).hexdigest()
747
+ return hashlib.md5(
748
+ json.dumps(data, sort_keys=True).encode(), usedforsecurity=False
749
+ ).hexdigest()
738
750
  else:
739
- return hashlib.md5(str(data).encode()).hexdigest()
751
+ return hashlib.md5(str(data).encode(), usedforsecurity=False).hexdigest()
740
752
 
741
753
  def get(
742
754
  self,
@@ -938,6 +950,9 @@ class DiskCache:
938
950
  self._memory_used = 0
939
951
  self._lock = threading.Lock()
940
952
 
953
+ # Security: HMAC signing key for cache integrity (SEC-003 fix)
954
+ self._cache_key = self._load_or_create_cache_key()
955
+
941
956
  def _get_cache_path(self, key: str) -> Path:
942
957
  """Get cache file path for key."""
943
958
  key_hash = hashlib.sha256(key.encode()).hexdigest()[:16]
@@ -946,10 +961,44 @@ class DiskCache:
946
961
  def _estimate_size(self, value: Any) -> int:
947
962
  """Estimate memory size of value."""
948
963
  if isinstance(value, np.ndarray):
949
- return value.nbytes # type: ignore[no-any-return]
964
+ return value.nbytes
950
965
  else:
951
966
  return len(pickle.dumps(value))
952
967
 
968
+ def _load_or_create_cache_key(self) -> bytes:
969
+ """Load or create HMAC signing key for cache integrity.
970
+
971
+ Returns:
972
+ 256-bit signing key.
973
+
974
+ Security:
975
+ SEC-003 fix: Protects cached pickle files from tampering.
976
+ Key is persistent per cache directory and stored with 0o600 permissions.
977
+ Each cache directory has its own unique key.
978
+
979
+ References:
980
+ https://owasp.org/www-project-top-ten/
981
+ """
982
+ key_file = self._cache_dir / ".cache_key"
983
+
984
+ # Load existing key
985
+ if key_file.exists():
986
+ with open(key_file, "rb") as f:
987
+ return f.read()
988
+
989
+ # Create new 256-bit key
990
+ key = secrets.token_bytes(32)
991
+
992
+ # Save with restrictive permissions
993
+ with open(key_file, "wb") as f:
994
+ f.write(key)
995
+
996
+ # Set owner read/write only (0o600)
997
+ key_file.chmod(0o600)
998
+
999
+ logger.info(f"Created new cache signing key: {key_file}")
1000
+ return key
1001
+
953
1002
  def get(self, key: str) -> tuple[Any, bool]:
954
1003
  """Get value from cache.
955
1004
 
@@ -976,7 +1025,23 @@ class DiskCache:
976
1025
 
977
1026
  try:
978
1027
  with open(cache_path, "rb") as f:
979
- value = pickle.load(f)
1028
+ signature = f.read(32) # SHA256 = 32 bytes
1029
+ data = f.read()
1030
+
1031
+ # Verify HMAC signature (SEC-003 fix)
1032
+ expected_signature = hmac.new(self._cache_key, data, hashlib.sha256).digest()
1033
+
1034
+ if not hmac.compare_digest(signature, expected_signature):
1035
+ logger.error(f"Cache integrity check failed for {key}")
1036
+ # Delete corrupted cache file
1037
+ cache_path.unlink()
1038
+ raise SecurityError(
1039
+ f"Cache file integrity verification failed: {key}. "
1040
+ "File may have been tampered with and has been removed."
1041
+ )
1042
+
1043
+ # Deserialize only after HMAC verification
1044
+ value = pickle.loads(data)
980
1045
 
981
1046
  # Promote to memory cache if space
982
1047
  size = self._estimate_size(value)
@@ -984,6 +1049,9 @@ class DiskCache:
984
1049
  self._add_to_memory(key, value, size)
985
1050
 
986
1051
  return value, True
1052
+
1053
+ except SecurityError:
1054
+ raise # Re-raise security errors
987
1055
  except Exception as e:
988
1056
  logger.warning(f"Failed to load cache: {e}")
989
1057
  return None, False
@@ -1004,14 +1072,29 @@ class DiskCache:
1004
1072
  self._memory_used += size
1005
1073
 
1006
1074
  def _write_to_disk(self, key: str, value: Any) -> None:
1007
- """Write value to disk cache."""
1075
+ """Write value to disk cache with HMAC signature.
1076
+
1077
+ Security:
1078
+ SEC-003 fix: Writes HMAC-SHA256 signature + pickled data.
1079
+ Format: [32 bytes signature][pickled data]
1080
+ Signature computed over pickled data using self._cache_key.
1081
+ """
1008
1082
  # Check disk space
1009
1083
  self._cleanup_disk()
1010
1084
 
1011
1085
  cache_path = self._get_cache_path(key)
1012
1086
  try:
1087
+ # Serialize value
1088
+ data = pickle.dumps(value, protocol=pickle.HIGHEST_PROTOCOL)
1089
+
1090
+ # Compute HMAC-SHA256 signature
1091
+ signature = hmac.new(self._cache_key, data, hashlib.sha256).digest()
1092
+
1093
+ # Write signature + data
1013
1094
  with open(cache_path, "wb") as f:
1014
- pickle.dump(value, f)
1095
+ f.write(signature) # First 32 bytes
1096
+ f.write(data) # Rest is pickled data
1097
+
1015
1098
  except Exception as e:
1016
1099
  logger.warning(f"Failed to write cache: {e}")
1017
1100
 
@@ -94,7 +94,7 @@ class ArrayManager(ResourceManager):
94
94
  """
95
95
 
96
96
  def __init__(self, array: NDArray[Any]) -> None:
97
- super().__init__(array, cleanup_func=lambda x: None) # noqa: ARG005
97
+ super().__init__(array, cleanup_func=lambda x: None)
98
98
 
99
99
 
100
100
  # =============================================================================
@@ -252,7 +252,7 @@ def get_result_cache() -> LRUCache[Any]:
252
252
  global _result_cache
253
253
  if _result_cache is None:
254
254
  # Default: 2 GB cache
255
- max_cache_size = int(os.environ.get("TK_CACHE_SIZE", 2 * 1024 * 1024 * 1024)) # noqa: PLW1508
255
+ max_cache_size = int(os.environ.get("TK_CACHE_SIZE", 2 * 1024 * 1024 * 1024))
256
256
  _result_cache = LRUCache(max_memory_bytes=max_cache_size)
257
257
  return _result_cache
258
258
 
@@ -286,6 +286,10 @@ def show_cache_stats() -> dict[str, int | float]:
286
286
  def cache_key(*args: Any, **kwargs: Any) -> str:
287
287
  """Generate cache key from arguments.
288
288
 
289
+ Note:
290
+ Uses MD5 for cache key generation only (not for security).
291
+ MD5 is appropriate here for non-cryptographic checksums.
292
+
289
293
  Args:
290
294
  *args: Positional arguments.
291
295
  **kwargs: Keyword arguments.
@@ -302,8 +306,8 @@ def cache_key(*args: Any, **kwargs: Any) -> str:
302
306
  parts.extend(f"{k}={v}" for k, v in sorted(kwargs.items()))
303
307
  key_str = "|".join(parts)
304
308
 
305
- # Hash for consistent key
306
- return hashlib.md5(key_str.encode()).hexdigest()
309
+ # Hash for consistent key (MD5 used for cache keys only, not security)
310
+ return hashlib.md5(key_str.encode(), usedforsecurity=False).hexdigest()
307
311
 
308
312
 
309
313
  # =============================================================================
@@ -344,9 +348,7 @@ def load_hdf5_lazy(
344
348
  try:
345
349
  import h5py
346
350
  except ImportError:
347
- raise ImportError( # noqa: B904
348
- "h5py required for lazy HDF5 loading. Install with: pip install h5py"
349
- )
351
+ raise ImportError("h5py required for lazy HDF5 loading. Install with: pip install h5py")
350
352
 
351
353
  from pathlib import Path
352
354
 
@@ -404,7 +406,7 @@ class LazyHDF5Array:
404
406
  try:
405
407
  import h5py
406
408
  except ImportError:
407
- raise ImportError("h5py required. Install with: pip install h5py") # noqa: B904
409
+ raise ImportError("h5py required. Install with: pip install h5py")
408
410
 
409
411
  self._file = h5py.File(self._file_path, "r")
410
412
  self._dataset = self._file[self._dataset_path] # type: ignore[index]
@@ -4,7 +4,7 @@ This module provides grid search and randomized search for finding optimal
4
4
  analysis parameters.
5
5
  """
6
6
 
7
- from oscura.optimization.search import (
7
+ from oscura.utils.optimization.search import (
8
8
  GridSearchCV,
9
9
  RandomizedSearchCV,
10
10
  ScoringFunction,
@@ -99,7 +99,7 @@ class GridSearchCV:
99
99
  optimal configuration.
100
100
 
101
101
  Example:
102
- >>> from oscura.optimization.search import GridSearchCV
102
+ >>> from oscura.utils.optimization.search import GridSearchCV
103
103
  >>> param_grid = {
104
104
  ... 'cutoff': [1e5, 5e5, 1e6],
105
105
  ... 'order': [2, 4, 6]
@@ -373,7 +373,7 @@ class RandomizedSearchCV:
373
373
  exhaustively evaluating all combinations.
374
374
 
375
375
  Example:
376
- >>> from oscura.optimization.search import RandomizedSearchCV
376
+ >>> from oscura.utils.optimization.search import RandomizedSearchCV
377
377
  >>> import numpy as np
378
378
  >>> param_distributions = {
379
379
  ... 'cutoff': lambda: np.random.uniform(1e5, 1e7),
@@ -0,0 +1,58 @@
1
+ """Performance optimization utilities for Oscura.
2
+
3
+ This package provides memory optimization, streaming analysis, parallel processing,
4
+ performance profiling, caching, and monitoring capabilities for processing large
5
+ signal files efficiently.
6
+ """
7
+
8
+ from oscura.utils.performance.caching import (
9
+ CacheBackend,
10
+ CacheEntry,
11
+ CacheManager,
12
+ CachePolicy,
13
+ CacheStats,
14
+ cache,
15
+ get_global_cache,
16
+ )
17
+ from oscura.utils.performance.memory_optimizer import (
18
+ ChunkingConfig,
19
+ ChunkingStrategy,
20
+ MemoryOptimizer,
21
+ MemoryStats,
22
+ StreamProcessor,
23
+ )
24
+ from oscura.utils.performance.parallel import (
25
+ ParallelConfig,
26
+ ParallelProcessor,
27
+ ParallelResult,
28
+ WorkerStats,
29
+ )
30
+ from oscura.utils.performance.profiling import (
31
+ FunctionStats,
32
+ PerformanceProfiler,
33
+ ProfilingMode,
34
+ ProfilingResult,
35
+ )
36
+
37
+ __all__ = [
38
+ "CacheBackend",
39
+ "CacheEntry",
40
+ "CacheManager",
41
+ "CachePolicy",
42
+ "CacheStats",
43
+ "ChunkingConfig",
44
+ "ChunkingStrategy",
45
+ "FunctionStats",
46
+ "MemoryOptimizer",
47
+ "MemoryStats",
48
+ "ParallelConfig",
49
+ "ParallelProcessor",
50
+ "ParallelResult",
51
+ "PerformanceProfiler",
52
+ "ProfilingMode",
53
+ "ProfilingResult",
54
+ "StreamProcessor",
55
+ "WorkerStats",
56
+ "cache",
57
+ "get_global_cache",
58
+ ]