oscura 0.5.1__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (497) hide show
  1. oscura/__init__.py +169 -167
  2. oscura/analyzers/__init__.py +3 -0
  3. oscura/analyzers/classification.py +659 -0
  4. oscura/analyzers/digital/edges.py +325 -65
  5. oscura/analyzers/digital/quality.py +293 -166
  6. oscura/analyzers/digital/timing.py +260 -115
  7. oscura/analyzers/digital/timing_numba.py +334 -0
  8. oscura/analyzers/entropy.py +605 -0
  9. oscura/analyzers/eye/diagram.py +176 -109
  10. oscura/analyzers/eye/metrics.py +5 -5
  11. oscura/analyzers/jitter/__init__.py +6 -4
  12. oscura/analyzers/jitter/ber.py +52 -52
  13. oscura/analyzers/jitter/classification.py +156 -0
  14. oscura/analyzers/jitter/decomposition.py +163 -113
  15. oscura/analyzers/jitter/spectrum.py +80 -64
  16. oscura/analyzers/ml/__init__.py +39 -0
  17. oscura/analyzers/ml/features.py +600 -0
  18. oscura/analyzers/ml/signal_classifier.py +604 -0
  19. oscura/analyzers/packet/daq.py +246 -158
  20. oscura/analyzers/packet/parser.py +12 -1
  21. oscura/analyzers/packet/payload.py +50 -2110
  22. oscura/analyzers/packet/payload_analysis.py +361 -181
  23. oscura/analyzers/packet/payload_patterns.py +133 -70
  24. oscura/analyzers/packet/stream.py +84 -23
  25. oscura/analyzers/patterns/__init__.py +26 -5
  26. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  27. oscura/analyzers/patterns/clustering.py +169 -108
  28. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  29. oscura/analyzers/patterns/discovery.py +1 -1
  30. oscura/analyzers/patterns/matching.py +581 -197
  31. oscura/analyzers/patterns/pattern_mining.py +778 -0
  32. oscura/analyzers/patterns/periodic.py +121 -38
  33. oscura/analyzers/patterns/sequences.py +175 -78
  34. oscura/analyzers/power/conduction.py +1 -1
  35. oscura/analyzers/power/soa.py +6 -6
  36. oscura/analyzers/power/switching.py +250 -110
  37. oscura/analyzers/protocol/__init__.py +17 -1
  38. oscura/analyzers/protocols/base.py +6 -6
  39. oscura/analyzers/protocols/ble/__init__.py +38 -0
  40. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  41. oscura/analyzers/protocols/ble/uuids.py +288 -0
  42. oscura/analyzers/protocols/can.py +257 -127
  43. oscura/analyzers/protocols/can_fd.py +107 -80
  44. oscura/analyzers/protocols/flexray.py +139 -80
  45. oscura/analyzers/protocols/hdlc.py +93 -58
  46. oscura/analyzers/protocols/i2c.py +247 -106
  47. oscura/analyzers/protocols/i2s.py +138 -86
  48. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  49. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  50. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  51. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  52. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  53. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  54. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  55. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  56. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  57. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  58. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  59. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  60. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  61. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  62. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  63. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  64. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  65. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  66. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  67. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  68. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  69. oscura/analyzers/protocols/jtag.py +180 -98
  70. oscura/analyzers/protocols/lin.py +219 -114
  71. oscura/analyzers/protocols/manchester.py +4 -4
  72. oscura/analyzers/protocols/onewire.py +253 -149
  73. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  74. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  75. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  76. oscura/analyzers/protocols/spi.py +192 -95
  77. oscura/analyzers/protocols/swd.py +321 -167
  78. oscura/analyzers/protocols/uart.py +267 -125
  79. oscura/analyzers/protocols/usb.py +235 -131
  80. oscura/analyzers/side_channel/power.py +17 -12
  81. oscura/analyzers/signal/__init__.py +15 -0
  82. oscura/analyzers/signal/timing_analysis.py +1086 -0
  83. oscura/analyzers/signal_integrity/__init__.py +4 -1
  84. oscura/analyzers/signal_integrity/sparams.py +2 -19
  85. oscura/analyzers/spectral/chunked.py +129 -60
  86. oscura/analyzers/spectral/chunked_fft.py +300 -94
  87. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  88. oscura/analyzers/statistical/checksum.py +376 -217
  89. oscura/analyzers/statistical/classification.py +229 -107
  90. oscura/analyzers/statistical/entropy.py +78 -53
  91. oscura/analyzers/statistics/correlation.py +407 -211
  92. oscura/analyzers/statistics/outliers.py +2 -2
  93. oscura/analyzers/statistics/streaming.py +30 -5
  94. oscura/analyzers/validation.py +216 -101
  95. oscura/analyzers/waveform/measurements.py +9 -0
  96. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  97. oscura/analyzers/waveform/spectral.py +500 -228
  98. oscura/api/__init__.py +31 -5
  99. oscura/api/dsl/__init__.py +582 -0
  100. oscura/{dsl → api/dsl}/commands.py +43 -76
  101. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  102. oscura/{dsl → api/dsl}/parser.py +107 -77
  103. oscura/{dsl → api/dsl}/repl.py +2 -2
  104. oscura/api/dsl.py +1 -1
  105. oscura/{integrations → api/integrations}/__init__.py +1 -1
  106. oscura/{integrations → api/integrations}/llm.py +201 -102
  107. oscura/api/operators.py +3 -3
  108. oscura/api/optimization.py +144 -30
  109. oscura/api/rest_server.py +921 -0
  110. oscura/api/server/__init__.py +17 -0
  111. oscura/api/server/dashboard.py +850 -0
  112. oscura/api/server/static/README.md +34 -0
  113. oscura/api/server/templates/base.html +181 -0
  114. oscura/api/server/templates/export.html +120 -0
  115. oscura/api/server/templates/home.html +284 -0
  116. oscura/api/server/templates/protocols.html +58 -0
  117. oscura/api/server/templates/reports.html +43 -0
  118. oscura/api/server/templates/session_detail.html +89 -0
  119. oscura/api/server/templates/sessions.html +83 -0
  120. oscura/api/server/templates/waveforms.html +73 -0
  121. oscura/automotive/__init__.py +8 -1
  122. oscura/automotive/can/__init__.py +10 -0
  123. oscura/automotive/can/checksum.py +3 -1
  124. oscura/automotive/can/dbc_generator.py +590 -0
  125. oscura/automotive/can/message_wrapper.py +121 -74
  126. oscura/automotive/can/patterns.py +98 -21
  127. oscura/automotive/can/session.py +292 -56
  128. oscura/automotive/can/state_machine.py +6 -3
  129. oscura/automotive/can/stimulus_response.py +97 -75
  130. oscura/automotive/dbc/__init__.py +10 -2
  131. oscura/automotive/dbc/generator.py +84 -56
  132. oscura/automotive/dbc/parser.py +6 -6
  133. oscura/automotive/dtc/data.json +17 -102
  134. oscura/automotive/dtc/database.py +2 -2
  135. oscura/automotive/flexray/__init__.py +31 -0
  136. oscura/automotive/flexray/analyzer.py +504 -0
  137. oscura/automotive/flexray/crc.py +185 -0
  138. oscura/automotive/flexray/fibex.py +449 -0
  139. oscura/automotive/j1939/__init__.py +45 -8
  140. oscura/automotive/j1939/analyzer.py +605 -0
  141. oscura/automotive/j1939/spns.py +326 -0
  142. oscura/automotive/j1939/transport.py +306 -0
  143. oscura/automotive/lin/__init__.py +47 -0
  144. oscura/automotive/lin/analyzer.py +612 -0
  145. oscura/automotive/loaders/blf.py +13 -2
  146. oscura/automotive/loaders/csv_can.py +143 -72
  147. oscura/automotive/loaders/dispatcher.py +50 -2
  148. oscura/automotive/loaders/mdf.py +86 -45
  149. oscura/automotive/loaders/pcap.py +111 -61
  150. oscura/automotive/uds/__init__.py +4 -0
  151. oscura/automotive/uds/analyzer.py +725 -0
  152. oscura/automotive/uds/decoder.py +140 -58
  153. oscura/automotive/uds/models.py +7 -1
  154. oscura/automotive/visualization.py +1 -1
  155. oscura/cli/analyze.py +348 -0
  156. oscura/cli/batch.py +142 -122
  157. oscura/cli/benchmark.py +275 -0
  158. oscura/cli/characterize.py +137 -82
  159. oscura/cli/compare.py +224 -131
  160. oscura/cli/completion.py +250 -0
  161. oscura/cli/config_cmd.py +361 -0
  162. oscura/cli/decode.py +164 -87
  163. oscura/cli/export.py +286 -0
  164. oscura/cli/main.py +115 -31
  165. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  166. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  167. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  168. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  169. oscura/cli/progress.py +147 -0
  170. oscura/cli/shell.py +157 -135
  171. oscura/cli/validate_cmd.py +204 -0
  172. oscura/cli/visualize.py +158 -0
  173. oscura/convenience.py +125 -79
  174. oscura/core/__init__.py +4 -2
  175. oscura/core/backend_selector.py +3 -3
  176. oscura/core/cache.py +126 -15
  177. oscura/core/cancellation.py +1 -1
  178. oscura/{config → core/config}/__init__.py +20 -11
  179. oscura/{config → core/config}/defaults.py +1 -1
  180. oscura/{config → core/config}/loader.py +7 -5
  181. oscura/{config → core/config}/memory.py +5 -5
  182. oscura/{config → core/config}/migration.py +1 -1
  183. oscura/{config → core/config}/pipeline.py +99 -23
  184. oscura/{config → core/config}/preferences.py +1 -1
  185. oscura/{config → core/config}/protocol.py +3 -3
  186. oscura/{config → core/config}/schema.py +426 -272
  187. oscura/{config → core/config}/settings.py +1 -1
  188. oscura/{config → core/config}/thresholds.py +195 -153
  189. oscura/core/correlation.py +5 -6
  190. oscura/core/cross_domain.py +0 -2
  191. oscura/core/debug.py +9 -5
  192. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  193. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  194. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  195. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  196. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  197. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  198. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  199. oscura/core/gpu_backend.py +11 -7
  200. oscura/core/log_query.py +101 -11
  201. oscura/core/logging.py +126 -54
  202. oscura/core/logging_advanced.py +5 -5
  203. oscura/core/memory_limits.py +108 -70
  204. oscura/core/memory_monitor.py +2 -2
  205. oscura/core/memory_progress.py +7 -7
  206. oscura/core/memory_warnings.py +1 -1
  207. oscura/core/numba_backend.py +13 -13
  208. oscura/{plugins → core/plugins}/__init__.py +9 -9
  209. oscura/{plugins → core/plugins}/base.py +7 -7
  210. oscura/{plugins → core/plugins}/cli.py +3 -3
  211. oscura/{plugins → core/plugins}/discovery.py +186 -106
  212. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  213. oscura/{plugins → core/plugins}/manager.py +7 -7
  214. oscura/{plugins → core/plugins}/registry.py +3 -3
  215. oscura/{plugins → core/plugins}/versioning.py +1 -1
  216. oscura/core/progress.py +16 -1
  217. oscura/core/provenance.py +8 -2
  218. oscura/{schemas → core/schemas}/__init__.py +2 -2
  219. oscura/{schemas → core/schemas}/device_mapping.json +2 -8
  220. oscura/{schemas → core/schemas}/packet_format.json +4 -24
  221. oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
  222. oscura/core/types.py +4 -0
  223. oscura/core/uncertainty.py +3 -3
  224. oscura/correlation/__init__.py +52 -0
  225. oscura/correlation/multi_protocol.py +811 -0
  226. oscura/discovery/auto_decoder.py +117 -35
  227. oscura/discovery/comparison.py +191 -86
  228. oscura/discovery/quality_validator.py +155 -68
  229. oscura/discovery/signal_detector.py +196 -79
  230. oscura/export/__init__.py +18 -8
  231. oscura/export/kaitai_struct.py +513 -0
  232. oscura/export/scapy_layer.py +801 -0
  233. oscura/export/wireshark/generator.py +1 -1
  234. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  235. oscura/export/wireshark_dissector.py +746 -0
  236. oscura/guidance/wizard.py +207 -111
  237. oscura/hardware/__init__.py +19 -0
  238. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  239. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  240. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  241. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  242. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  243. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  244. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  245. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  246. oscura/hardware/firmware/__init__.py +29 -0
  247. oscura/hardware/firmware/pattern_recognition.py +874 -0
  248. oscura/hardware/hal_detector.py +736 -0
  249. oscura/hardware/security/__init__.py +37 -0
  250. oscura/hardware/security/side_channel_detector.py +1126 -0
  251. oscura/inference/__init__.py +4 -0
  252. oscura/inference/active_learning/observation_table.py +4 -1
  253. oscura/inference/alignment.py +216 -123
  254. oscura/inference/bayesian.py +113 -33
  255. oscura/inference/crc_reverse.py +101 -55
  256. oscura/inference/logic.py +6 -2
  257. oscura/inference/message_format.py +342 -183
  258. oscura/inference/protocol.py +95 -44
  259. oscura/inference/protocol_dsl.py +180 -82
  260. oscura/inference/signal_intelligence.py +1439 -706
  261. oscura/inference/spectral.py +99 -57
  262. oscura/inference/state_machine.py +810 -158
  263. oscura/inference/stream.py +270 -110
  264. oscura/iot/__init__.py +34 -0
  265. oscura/iot/coap/__init__.py +32 -0
  266. oscura/iot/coap/analyzer.py +668 -0
  267. oscura/iot/coap/options.py +212 -0
  268. oscura/iot/lorawan/__init__.py +21 -0
  269. oscura/iot/lorawan/crypto.py +206 -0
  270. oscura/iot/lorawan/decoder.py +801 -0
  271. oscura/iot/lorawan/mac_commands.py +341 -0
  272. oscura/iot/mqtt/__init__.py +27 -0
  273. oscura/iot/mqtt/analyzer.py +999 -0
  274. oscura/iot/mqtt/properties.py +315 -0
  275. oscura/iot/zigbee/__init__.py +31 -0
  276. oscura/iot/zigbee/analyzer.py +615 -0
  277. oscura/iot/zigbee/security.py +153 -0
  278. oscura/iot/zigbee/zcl.py +349 -0
  279. oscura/jupyter/display.py +125 -45
  280. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  281. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  282. oscura/jupyter/exploratory/fuzzy.py +746 -0
  283. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  284. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  285. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  286. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  287. oscura/jupyter/exploratory/sync.py +612 -0
  288. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  289. oscura/jupyter/magic.py +4 -4
  290. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  291. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  292. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  293. oscura/loaders/__init__.py +183 -67
  294. oscura/loaders/binary.py +88 -1
  295. oscura/loaders/chipwhisperer.py +153 -137
  296. oscura/loaders/configurable.py +208 -86
  297. oscura/loaders/csv_loader.py +458 -215
  298. oscura/loaders/hdf5_loader.py +278 -119
  299. oscura/loaders/lazy.py +87 -54
  300. oscura/loaders/mmap_loader.py +1 -1
  301. oscura/loaders/numpy_loader.py +253 -116
  302. oscura/loaders/pcap.py +226 -151
  303. oscura/loaders/rigol.py +110 -49
  304. oscura/loaders/sigrok.py +201 -78
  305. oscura/loaders/tdms.py +81 -58
  306. oscura/loaders/tektronix.py +291 -174
  307. oscura/loaders/touchstone.py +182 -87
  308. oscura/loaders/tss.py +456 -0
  309. oscura/loaders/vcd.py +215 -117
  310. oscura/loaders/wav.py +155 -68
  311. oscura/reporting/__init__.py +9 -0
  312. oscura/reporting/analyze.py +352 -146
  313. oscura/reporting/argument_preparer.py +69 -14
  314. oscura/reporting/auto_report.py +97 -61
  315. oscura/reporting/batch.py +131 -58
  316. oscura/reporting/chart_selection.py +57 -45
  317. oscura/reporting/comparison.py +63 -17
  318. oscura/reporting/content/executive.py +76 -24
  319. oscura/reporting/core_formats/multi_format.py +11 -8
  320. oscura/reporting/engine.py +312 -158
  321. oscura/reporting/enhanced_reports.py +949 -0
  322. oscura/reporting/export.py +86 -43
  323. oscura/reporting/formatting/numbers.py +69 -42
  324. oscura/reporting/html.py +139 -58
  325. oscura/reporting/index.py +137 -65
  326. oscura/reporting/output.py +158 -67
  327. oscura/reporting/pdf.py +67 -102
  328. oscura/reporting/plots.py +191 -112
  329. oscura/reporting/sections.py +88 -47
  330. oscura/reporting/standards.py +104 -61
  331. oscura/reporting/summary_generator.py +75 -55
  332. oscura/reporting/tables.py +138 -54
  333. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  334. oscura/sessions/__init__.py +14 -23
  335. oscura/sessions/base.py +3 -3
  336. oscura/sessions/blackbox.py +106 -10
  337. oscura/sessions/generic.py +2 -2
  338. oscura/sessions/legacy.py +783 -0
  339. oscura/side_channel/__init__.py +63 -0
  340. oscura/side_channel/dpa.py +1025 -0
  341. oscura/utils/__init__.py +15 -1
  342. oscura/utils/bitwise.py +118 -0
  343. oscura/{builders → utils/builders}/__init__.py +1 -1
  344. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  345. oscura/{comparison → utils/comparison}/compare.py +202 -101
  346. oscura/{comparison → utils/comparison}/golden.py +83 -63
  347. oscura/{comparison → utils/comparison}/limits.py +313 -89
  348. oscura/{comparison → utils/comparison}/mask.py +151 -45
  349. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  350. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  351. oscura/{component → utils/component}/__init__.py +3 -3
  352. oscura/{component → utils/component}/impedance.py +122 -58
  353. oscura/{component → utils/component}/reactive.py +165 -168
  354. oscura/{component → utils/component}/transmission_line.py +3 -3
  355. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  356. oscura/{filtering → utils/filtering}/base.py +1 -1
  357. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  358. oscura/{filtering → utils/filtering}/design.py +169 -93
  359. oscura/{filtering → utils/filtering}/filters.py +2 -2
  360. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  361. oscura/utils/geometry.py +31 -0
  362. oscura/utils/imports.py +184 -0
  363. oscura/utils/lazy.py +1 -1
  364. oscura/{math → utils/math}/__init__.py +2 -2
  365. oscura/{math → utils/math}/arithmetic.py +114 -48
  366. oscura/{math → utils/math}/interpolation.py +139 -106
  367. oscura/utils/memory.py +129 -66
  368. oscura/utils/memory_advanced.py +92 -9
  369. oscura/utils/memory_extensions.py +10 -8
  370. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  371. oscura/{optimization → utils/optimization}/search.py +2 -2
  372. oscura/utils/performance/__init__.py +58 -0
  373. oscura/utils/performance/caching.py +889 -0
  374. oscura/utils/performance/lsh_clustering.py +333 -0
  375. oscura/utils/performance/memory_optimizer.py +699 -0
  376. oscura/utils/performance/optimizations.py +675 -0
  377. oscura/utils/performance/parallel.py +654 -0
  378. oscura/utils/performance/profiling.py +661 -0
  379. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  380. oscura/{pipeline → utils/pipeline}/composition.py +1 -1
  381. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  382. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  383. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  384. oscura/{search → utils/search}/__init__.py +3 -3
  385. oscura/{search → utils/search}/anomaly.py +188 -58
  386. oscura/utils/search/context.py +294 -0
  387. oscura/{search → utils/search}/pattern.py +138 -10
  388. oscura/utils/serial.py +51 -0
  389. oscura/utils/storage/__init__.py +61 -0
  390. oscura/utils/storage/database.py +1166 -0
  391. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  392. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  393. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  394. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  395. oscura/{triggering → utils/triggering}/base.py +6 -6
  396. oscura/{triggering → utils/triggering}/edge.py +2 -2
  397. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  398. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  399. oscura/{triggering → utils/triggering}/window.py +2 -2
  400. oscura/utils/validation.py +32 -0
  401. oscura/validation/__init__.py +121 -0
  402. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  403. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  404. oscura/{compliance → validation/compliance}/masks.py +1 -1
  405. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  406. oscura/{compliance → validation/compliance}/testing.py +114 -52
  407. oscura/validation/compliance_tests.py +915 -0
  408. oscura/validation/fuzzer.py +990 -0
  409. oscura/validation/grammar_tests.py +596 -0
  410. oscura/validation/grammar_validator.py +904 -0
  411. oscura/validation/hil_testing.py +977 -0
  412. oscura/{quality → validation/quality}/__init__.py +4 -4
  413. oscura/{quality → validation/quality}/ensemble.py +251 -171
  414. oscura/{quality → validation/quality}/explainer.py +3 -3
  415. oscura/{quality → validation/quality}/scoring.py +1 -1
  416. oscura/{quality → validation/quality}/warnings.py +4 -4
  417. oscura/validation/regression_suite.py +808 -0
  418. oscura/validation/replay.py +788 -0
  419. oscura/{testing → validation/testing}/__init__.py +2 -2
  420. oscura/{testing → validation/testing}/synthetic.py +5 -5
  421. oscura/visualization/__init__.py +9 -0
  422. oscura/visualization/accessibility.py +1 -1
  423. oscura/visualization/annotations.py +64 -67
  424. oscura/visualization/colors.py +7 -7
  425. oscura/visualization/digital.py +180 -81
  426. oscura/visualization/eye.py +236 -85
  427. oscura/visualization/interactive.py +320 -143
  428. oscura/visualization/jitter.py +587 -247
  429. oscura/visualization/layout.py +169 -134
  430. oscura/visualization/optimization.py +103 -52
  431. oscura/visualization/palettes.py +1 -1
  432. oscura/visualization/power.py +427 -211
  433. oscura/visualization/power_extended.py +626 -297
  434. oscura/visualization/presets.py +2 -0
  435. oscura/visualization/protocols.py +495 -181
  436. oscura/visualization/render.py +79 -63
  437. oscura/visualization/reverse_engineering.py +171 -124
  438. oscura/visualization/signal_integrity.py +460 -279
  439. oscura/visualization/specialized.py +190 -100
  440. oscura/visualization/spectral.py +670 -255
  441. oscura/visualization/thumbnails.py +166 -137
  442. oscura/visualization/waveform.py +150 -63
  443. oscura/workflows/__init__.py +3 -0
  444. oscura/{batch → workflows/batch}/__init__.py +5 -5
  445. oscura/{batch → workflows/batch}/advanced.py +150 -75
  446. oscura/workflows/batch/aggregate.py +531 -0
  447. oscura/workflows/batch/analyze.py +236 -0
  448. oscura/{batch → workflows/batch}/logging.py +2 -2
  449. oscura/{batch → workflows/batch}/metrics.py +1 -1
  450. oscura/workflows/complete_re.py +1144 -0
  451. oscura/workflows/compliance.py +44 -54
  452. oscura/workflows/digital.py +197 -51
  453. oscura/workflows/legacy/__init__.py +12 -0
  454. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  455. oscura/workflows/multi_trace.py +9 -9
  456. oscura/workflows/power.py +42 -62
  457. oscura/workflows/protocol.py +82 -49
  458. oscura/workflows/reverse_engineering.py +351 -150
  459. oscura/workflows/signal_integrity.py +157 -82
  460. oscura-0.7.0.dist-info/METADATA +661 -0
  461. oscura-0.7.0.dist-info/RECORD +591 -0
  462. oscura/batch/aggregate.py +0 -300
  463. oscura/batch/analyze.py +0 -139
  464. oscura/dsl/__init__.py +0 -73
  465. oscura/exceptions.py +0 -59
  466. oscura/exploratory/fuzzy.py +0 -513
  467. oscura/exploratory/sync.py +0 -384
  468. oscura/exporters/__init__.py +0 -94
  469. oscura/exporters/csv.py +0 -303
  470. oscura/exporters/exporters.py +0 -44
  471. oscura/exporters/hdf5.py +0 -217
  472. oscura/exporters/html_export.py +0 -701
  473. oscura/exporters/json_export.py +0 -291
  474. oscura/exporters/markdown_export.py +0 -367
  475. oscura/exporters/matlab_export.py +0 -354
  476. oscura/exporters/npz_export.py +0 -219
  477. oscura/exporters/spice_export.py +0 -210
  478. oscura/search/context.py +0 -149
  479. oscura/session/__init__.py +0 -34
  480. oscura/session/annotations.py +0 -289
  481. oscura/session/history.py +0 -313
  482. oscura/session/session.py +0 -520
  483. oscura/workflow/__init__.py +0 -13
  484. oscura-0.5.1.dist-info/METADATA +0 -583
  485. oscura-0.5.1.dist-info/RECORD +0 -481
  486. /oscura/core/{config.py → config/legacy.py} +0 -0
  487. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  488. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  489. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  490. /oscura/{schemas → core/schemas}/bus_configuration.json +0 -0
  491. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  492. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  493. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  494. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  495. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
  496. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
  497. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,250 @@
1
+ """Shell completion support for Oscura CLI.
2
+
3
+ Generates completion scripts for bash, zsh, and fish shells.
4
+
5
+
6
+ Example:
7
+ $ oscura --install-completion bash
8
+ $ oscura --show-completion bash > ~/.bash_completion.d/oscura
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import sys
14
+ from pathlib import Path
15
+
16
+
17
+ def get_completion_script(shell: str) -> str:
18
+ """Get completion script for specified shell.
19
+
20
+ Args:
21
+ shell: Shell type ('bash', 'zsh', or 'fish').
22
+
23
+ Returns:
24
+ Completion script content.
25
+
26
+ Raises:
27
+ ValueError: If shell type is unsupported.
28
+ """
29
+ if shell == "bash":
30
+ return _get_bash_completion()
31
+ elif shell == "zsh":
32
+ return _get_zsh_completion()
33
+ elif shell == "fish":
34
+ return _get_fish_completion()
35
+ else:
36
+ raise ValueError(f"Unsupported shell: {shell}")
37
+
38
+
39
+ def install_completion(shell: str) -> Path:
40
+ """Install completion script for specified shell.
41
+
42
+ Args:
43
+ shell: Shell type ('bash', 'zsh', or 'fish').
44
+
45
+ Returns:
46
+ Path where completion was installed.
47
+
48
+ Raises:
49
+ ValueError: If shell type is unsupported.
50
+ """
51
+ script = get_completion_script(shell)
52
+ home = Path.home()
53
+
54
+ if shell == "bash":
55
+ completion_dir = home / ".bash_completion.d"
56
+ completion_dir.mkdir(exist_ok=True)
57
+ completion_file = completion_dir / "oscura"
58
+ elif shell == "zsh":
59
+ completion_dir = home / ".zsh" / "completion"
60
+ completion_dir.mkdir(parents=True, exist_ok=True)
61
+ completion_file = completion_dir / "_oscura"
62
+ elif shell == "fish":
63
+ completion_dir = home / ".config" / "fish" / "completions"
64
+ completion_dir.mkdir(parents=True, exist_ok=True)
65
+ completion_file = completion_dir / "oscura.fish"
66
+ else:
67
+ raise ValueError(f"Unsupported shell: {shell}")
68
+
69
+ with open(completion_file, "w") as f:
70
+ f.write(script)
71
+
72
+ return completion_file
73
+
74
+
75
+ def _get_bash_completion() -> str:
76
+ """Get bash completion script."""
77
+ return """# Bash completion for oscura
78
+
79
+ _oscura_completion() {
80
+ local cur prev opts
81
+ COMPREPLY=()
82
+ cur="${COMP_WORDS[COMP_CWORD]}"
83
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
84
+
85
+ # Main commands
86
+ local commands="analyze decode export visualize benchmark validate config plugins characterize batch compare shell tutorial"
87
+
88
+ # Global options
89
+ local global_opts="--help --version --verbose --quiet --config --json"
90
+
91
+ # If we're on the first argument, complete commands or global options
92
+ if [[ ${COMP_CWORD} -eq 1 ]]; then
93
+ COMPREPLY=( $(compgen -W "${commands} ${global_opts}" -- ${cur}) )
94
+ return 0
95
+ fi
96
+
97
+ # Get the command
98
+ local cmd="${COMP_WORDS[1]}"
99
+
100
+ # Command-specific completions
101
+ case "${cmd}" in
102
+ analyze|decode|export|visualize)
103
+ # Complete file paths and help
104
+ if [[ ${cur} == -* ]]; then
105
+ COMPREPLY=( $(compgen -W "--help" -- ${cur}) )
106
+ else
107
+ COMPREPLY=( $(compgen -f -X '!*.@(wfm|vcd|csv|pcap|wav)' -- ${cur}) )
108
+ fi
109
+ ;;
110
+ config)
111
+ local config_opts="--show --set --edit --init --path --help"
112
+ COMPREPLY=( $(compgen -W "${config_opts}" -- ${cur}) )
113
+ ;;
114
+ plugins)
115
+ local plugin_cmds="list info install remove update"
116
+ if [[ ${COMP_CWORD} -eq 2 ]]; then
117
+ COMPREPLY=( $(compgen -W "${plugin_cmds}" -- ${cur}) )
118
+ fi
119
+ ;;
120
+ *)
121
+ # Default to --help for other commands
122
+ if [[ ${cur} == -* ]]; then
123
+ COMPREPLY=( $(compgen -W "--help" -- ${cur}) )
124
+ fi
125
+ ;;
126
+ esac
127
+ }
128
+
129
+ complete -F _oscura_completion oscura
130
+ """
131
+
132
+
133
+ def _get_zsh_completion() -> str:
134
+ """Get zsh completion script."""
135
+ return """#compdef oscura
136
+
137
+ _oscura() {
138
+ local -a commands
139
+ commands=(
140
+ 'analyze:Run full analysis workflow'
141
+ 'decode:Decode protocol data'
142
+ 'export:Export analysis results'
143
+ 'visualize:Launch interactive viewer'
144
+ 'benchmark:Run performance benchmarks'
145
+ 'validate:Validate protocol specification'
146
+ 'config:Manage configuration'
147
+ 'plugins:Manage plugins'
148
+ 'characterize:Characterize signal'
149
+ 'batch:Batch process files'
150
+ 'compare:Compare signals'
151
+ 'shell:Start interactive shell'
152
+ 'tutorial:Run interactive tutorial'
153
+ )
154
+
155
+ local -a file_args
156
+ file_args=(
157
+ '*:waveform file:_files -g "*.{wfm,vcd,csv,pcap,wav}"'
158
+ )
159
+
160
+ _arguments -C \\
161
+ '(-h --help)'{-h,--help}'[Show help message]' \\
162
+ '(-v --verbose)'{-v,--verbose}'[Increase verbosity]' \\
163
+ '--config[Configuration file]:config file:_files' \\
164
+ '(-q --quiet)'{-q,--quiet}'[Quiet mode]' \\
165
+ '--json[JSON output mode]' \\
166
+ '1: :->command' \\
167
+ '*:: :->args'
168
+
169
+ case $state in
170
+ command)
171
+ _describe -t commands 'oscura commands' commands
172
+ ;;
173
+ args)
174
+ case $words[1] in
175
+ analyze|decode|visualize)
176
+ _files -g "*.{wfm,vcd,csv,pcap,wav}"
177
+ ;;
178
+ config)
179
+ _arguments \\
180
+ '--show[Show configuration]' \\
181
+ '--set[Set value]:key=value:' \\
182
+ '--edit[Edit configuration]' \\
183
+ '--init[Initialize configuration]' \\
184
+ '--path[Show config path]'
185
+ ;;
186
+ esac
187
+ ;;
188
+ esac
189
+ }
190
+
191
+ _oscura
192
+ """
193
+
194
+
195
+ def _get_fish_completion() -> str:
196
+ """Get fish completion script."""
197
+ return """# Fish completion for oscura
198
+
199
+ # Main commands
200
+ complete -c oscura -n "__fish_use_subcommand" -a analyze -d "Run full analysis workflow"
201
+ complete -c oscura -n "__fish_use_subcommand" -a decode -d "Decode protocol data"
202
+ complete -c oscura -n "__fish_use_subcommand" -a export -d "Export analysis results"
203
+ complete -c oscura -n "__fish_use_subcommand" -a visualize -d "Launch interactive viewer"
204
+ complete -c oscura -n "__fish_use_subcommand" -a benchmark -d "Run performance benchmarks"
205
+ complete -c oscura -n "__fish_use_subcommand" -a validate -d "Validate protocol specification"
206
+ complete -c oscura -n "__fish_use_subcommand" -a config -d "Manage configuration"
207
+ complete -c oscura -n "__fish_use_subcommand" -a plugins -d "Manage plugins"
208
+ complete -c oscura -n "__fish_use_subcommand" -a characterize -d "Characterize signal"
209
+ complete -c oscura -n "__fish_use_subcommand" -a batch -d "Batch process files"
210
+ complete -c oscura -n "__fish_use_subcommand" -a compare -d "Compare signals"
211
+ complete -c oscura -n "__fish_use_subcommand" -a shell -d "Start interactive shell"
212
+ complete -c oscura -n "__fish_use_subcommand" -a tutorial -d "Run interactive tutorial"
213
+
214
+ # Global options
215
+ complete -c oscura -s h -l help -d "Show help message"
216
+ complete -c oscura -s v -l verbose -d "Increase verbosity"
217
+ complete -c oscura -s q -l quiet -d "Quiet mode"
218
+ complete -c oscura -l config -d "Configuration file" -r
219
+ complete -c oscura -l json -d "JSON output mode"
220
+
221
+ # analyze subcommand
222
+ complete -c oscura -n "__fish_seen_subcommand_from analyze" -l protocol -d "Protocol hint"
223
+ complete -c oscura -n "__fish_seen_subcommand_from analyze" -l export-dir -d "Export directory" -r
224
+ complete -c oscura -n "__fish_seen_subcommand_from analyze" -s i -l interactive -d "Interactive mode"
225
+ complete -c oscura -n "__fish_seen_subcommand_from analyze" -l output -d "Output format" -a "json csv html table"
226
+
227
+ # decode subcommand
228
+ complete -c oscura -n "__fish_seen_subcommand_from decode" -l protocol -d "Protocol type" -a "uart spi i2c can auto"
229
+ complete -c oscura -n "__fish_seen_subcommand_from decode" -l baud-rate -d "Baud rate" -r
230
+ complete -c oscura -n "__fish_seen_subcommand_from decode" -l show-errors -d "Show only errors"
231
+
232
+ # config subcommand
233
+ complete -c oscura -n "__fish_seen_subcommand_from config" -l show -d "Show configuration"
234
+ complete -c oscura -n "__fish_seen_subcommand_from config" -l set -d "Set value" -r
235
+ complete -c oscura -n "__fish_seen_subcommand_from config" -l edit -d "Edit configuration"
236
+ complete -c oscura -n "__fish_seen_subcommand_from config" -l init -d "Initialize configuration"
237
+ complete -c oscura -n "__fish_seen_subcommand_from config" -l path -d "Show config path"
238
+
239
+ # File completions for commands that take file arguments
240
+ for cmd in analyze decode visualize export
241
+ complete -c oscura -n "__fish_seen_subcommand_from $cmd" -F -a '(__fish_complete_suffix .wfm .vcd .csv .pcap .wav)'
242
+ end
243
+ """
244
+
245
+
246
+ if __name__ == "__main__":
247
+ # Allow running as: python -m oscura.cli.completion bash
248
+ if len(sys.argv) > 1:
249
+ shell_type = sys.argv[1]
250
+ print(get_completion_script(shell_type))
@@ -0,0 +1,361 @@
1
+ """Oscura Config Command - Configuration Management.
2
+
3
+ Provides CLI for viewing and editing Oscura configuration.
4
+
5
+
6
+ Example:
7
+ $ oscura config --show
8
+ $ oscura config --set analysis.default_protocol=uart
9
+ $ oscura config --edit
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+ import os
16
+ import shlex
17
+ import subprocess
18
+ from pathlib import Path
19
+ from typing import Any
20
+
21
+ import click
22
+
23
+ logger = logging.getLogger("oscura.cli.config")
24
+
25
+ # Allowlist of trusted editors (SEC-004 fix)
26
+ ALLOWED_EDITORS = {
27
+ "nano",
28
+ "vim",
29
+ "vi",
30
+ "emacs",
31
+ "nvim",
32
+ "code",
33
+ "subl",
34
+ "atom",
35
+ "gedit",
36
+ "kate",
37
+ "micro",
38
+ "helix",
39
+ }
40
+
41
+
42
+ def _get_safe_editor() -> str:
43
+ """Get validated editor from environment.
44
+
45
+ Returns:
46
+ Safe editor command.
47
+
48
+ Security:
49
+ SEC-004 fix: Validates editor against allowlist to prevent command injection
50
+ via $EDITOR environment variable. Falls back to 'nano' for untrusted editors.
51
+
52
+ Example:
53
+ >>> os.environ["EDITOR"] = "vim"
54
+ >>> editor = _get_safe_editor()
55
+ >>> assert editor == "vim"
56
+
57
+ >>> os.environ["EDITOR"] = "rm -rf /"
58
+ >>> editor = _get_safe_editor()
59
+ >>> assert editor == "nano" # Fallback to safe default
60
+
61
+ References:
62
+ https://owasp.org/www-project-top-ten/
63
+ """
64
+ editor_env = os.environ.get("EDITOR", "nano")
65
+
66
+ # Check for command substitution and newlines before parsing
67
+ # These are shell injection attempts that shlex may not detect
68
+ if "`" in editor_env or "$(" in editor_env or "\n" in editor_env or "\r" in editor_env:
69
+ logger.warning(
70
+ "Command substitution or newline detected in EDITOR, falling back to nano for safety"
71
+ )
72
+ return "nano"
73
+
74
+ # Extract base command (handle args like "code --wait")
75
+ try:
76
+ editor_parts = shlex.split(editor_env)
77
+ if not editor_parts:
78
+ logger.warning("Empty EDITOR value, using nano")
79
+ return "nano"
80
+
81
+ editor_cmd = Path(editor_parts[0]).name
82
+ except ValueError as e:
83
+ logger.warning(f"Invalid EDITOR value '{editor_env}': {e}, using nano")
84
+ return "nano"
85
+
86
+ # Validate against allowlist
87
+ if editor_cmd not in ALLOWED_EDITORS:
88
+ logger.warning(
89
+ f"Untrusted editor '{editor_cmd}' not in allowlist, falling back to nano. "
90
+ f"Allowed editors: {', '.join(sorted(ALLOWED_EDITORS))}"
91
+ )
92
+ return "nano"
93
+
94
+ # Check for shell metacharacters that indicate command injection attempts
95
+ # Shell metacharacters parsed as separate tokens by shlex indicate injection
96
+ shell_metacharacters = {"&&", "||", ";", "|", ">", "<", ">>", "<<", "&"}
97
+ if len(editor_parts) > 1 and any(part in shell_metacharacters for part in editor_parts[1:]):
98
+ logger.warning(
99
+ f"Shell metacharacters detected in EDITOR '{editor_env}', "
100
+ f"falling back to nano for safety"
101
+ )
102
+ return "nano"
103
+
104
+ return editor_env # Return full command with args if valid
105
+
106
+
107
+ @click.command()
108
+ @click.option(
109
+ "--show",
110
+ is_flag=True,
111
+ help="Show current configuration.",
112
+ )
113
+ @click.option(
114
+ "--set",
115
+ "set_value",
116
+ type=str,
117
+ default=None,
118
+ help="Set configuration value (key=value).",
119
+ )
120
+ @click.option(
121
+ "--edit",
122
+ is_flag=True,
123
+ help="Open configuration file in editor.",
124
+ )
125
+ @click.option(
126
+ "--init",
127
+ is_flag=True,
128
+ help="Initialize default configuration file.",
129
+ )
130
+ @click.option(
131
+ "--path",
132
+ is_flag=True,
133
+ help="Show configuration file path.",
134
+ )
135
+ @click.pass_context
136
+ def config(
137
+ ctx: click.Context,
138
+ show: bool,
139
+ set_value: str | None,
140
+ edit: bool,
141
+ init: bool,
142
+ path: bool,
143
+ ) -> None:
144
+ """Manage Oscura configuration.
145
+
146
+ View, edit, and initialize configuration files.
147
+
148
+ Args:
149
+ ctx: Click context object.
150
+ show: Show configuration.
151
+ set_value: Set configuration value.
152
+ edit: Edit configuration file.
153
+ init: Initialize configuration.
154
+ path: Show config path.
155
+
156
+ Examples:
157
+
158
+ \b
159
+ # Show current config
160
+ $ oscura config --show
161
+
162
+ \b
163
+ # Set a value
164
+ $ oscura config --set analysis.default_protocol=uart
165
+
166
+ \b
167
+ # Edit config file
168
+ $ oscura config --edit
169
+
170
+ \b
171
+ # Initialize config
172
+ $ oscura config --init
173
+ """
174
+ verbose = ctx.obj.get("verbose", 0)
175
+
176
+ try:
177
+ config_path = _get_config_path()
178
+
179
+ if path:
180
+ click.echo(f"Configuration file: {config_path}")
181
+ return
182
+
183
+ if init:
184
+ _initialize_config(config_path)
185
+ click.echo(f"Initialized configuration at: {config_path}")
186
+ return
187
+
188
+ if show:
189
+ _show_config(config_path)
190
+ return
191
+
192
+ if set_value:
193
+ _set_config_value(config_path, set_value)
194
+ click.echo(f"Updated configuration: {set_value}")
195
+ return
196
+
197
+ if edit:
198
+ _edit_config(config_path)
199
+ return
200
+
201
+ # No options provided, show help
202
+ click.echo(ctx.get_help())
203
+
204
+ except Exception as e:
205
+ logger.error(f"Config operation failed: {e}")
206
+ if verbose > 1:
207
+ raise
208
+ click.echo(f"Error: {e}", err=True)
209
+ ctx.exit(1)
210
+
211
+
212
+ def _get_config_path() -> Path:
213
+ """Get configuration file path.
214
+
215
+ Returns:
216
+ Path to configuration file (absolute path).
217
+ """
218
+ # Check for local config first
219
+ local_config = Path(".oscura.yaml").resolve()
220
+ if local_config.exists():
221
+ return local_config
222
+
223
+ # Use user config
224
+ user_config = Path.home() / ".config" / "oscura" / "config.yaml"
225
+ return user_config
226
+
227
+
228
+ def _initialize_config(config_path: Path) -> None:
229
+ """Initialize default configuration file.
230
+
231
+ Args:
232
+ config_path: Path to configuration file.
233
+ """
234
+ config_path.parent.mkdir(parents=True, exist_ok=True)
235
+
236
+ default_config = """# Oscura Configuration
237
+
238
+ analysis:
239
+ default_protocol: auto
240
+ auto_detect_threshold: 0.7
241
+ max_packets: 10000
242
+
243
+ export:
244
+ default_format: json
245
+ output_dir: oscura_output
246
+
247
+ visualization:
248
+ default_backend: matplotlib
249
+ figure_size: [12, 6]
250
+ dpi: 100
251
+
252
+ cli:
253
+ color_output: true
254
+ progress_bars: true
255
+
256
+ logging:
257
+ level: WARNING
258
+ format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
259
+ """
260
+
261
+ with open(config_path, "w") as f:
262
+ f.write(default_config)
263
+
264
+
265
+ def _show_config(config_path: Path) -> None:
266
+ """Show configuration.
267
+
268
+ Args:
269
+ config_path: Path to configuration file.
270
+ """
271
+ if not config_path.exists():
272
+ click.echo("No configuration file found. Use --init to create one.")
273
+ return
274
+
275
+ import yaml
276
+
277
+ with open(config_path) as f:
278
+ config = yaml.safe_load(f)
279
+
280
+ click.echo(f"\nConfiguration ({config_path}):\n")
281
+ click.echo(yaml.dump(config, default_flow_style=False))
282
+
283
+
284
+ def _set_config_value(config_path: Path, set_value: str) -> None:
285
+ """Set configuration value.
286
+
287
+ Args:
288
+ config_path: Path to configuration file.
289
+ set_value: Value to set (key=value format).
290
+ """
291
+ import yaml
292
+
293
+ if "=" not in set_value:
294
+ raise ValueError("Invalid format. Use: key=value")
295
+
296
+ key, value = set_value.split("=", 1)
297
+ keys = key.split(".")
298
+
299
+ # Load existing config
300
+ config: dict[str, Any] = {}
301
+ if config_path.exists():
302
+ with open(config_path) as f:
303
+ config = yaml.safe_load(f) or {}
304
+
305
+ # Set nested value
306
+ current = config
307
+ for k in keys[:-1]:
308
+ if k not in current:
309
+ current[k] = {}
310
+ current = current[k]
311
+
312
+ # Try to parse value
313
+ try:
314
+ # Try as number
315
+ if "." in value:
316
+ parsed_value: Any = float(value)
317
+ else:
318
+ parsed_value = int(value)
319
+ except ValueError:
320
+ # Try as boolean
321
+ if value.lower() in ["true", "false"]:
322
+ parsed_value = value.lower() == "true"
323
+ else:
324
+ # Keep as string
325
+ parsed_value = value
326
+
327
+ current[keys[-1]] = parsed_value
328
+
329
+ # Save config
330
+ config_path.parent.mkdir(parents=True, exist_ok=True)
331
+ with open(config_path, "w") as f:
332
+ yaml.dump(config, f, default_flow_style=False)
333
+
334
+
335
+ def _edit_config(config_path: Path) -> None:
336
+ """Edit configuration file with safe editor validation.
337
+
338
+ Args:
339
+ config_path: Path to configuration file.
340
+
341
+ Security:
342
+ SEC-004 fix: Uses _get_safe_editor() to validate $EDITOR against allowlist,
343
+ preventing command injection attacks via malicious editor values.
344
+
345
+ Raises:
346
+ RuntimeError: If editor execution fails.
347
+ """
348
+ # Create if doesn't exist
349
+ if not config_path.exists():
350
+ _initialize_config(config_path)
351
+
352
+ # Get validated editor (SEC-004 fix)
353
+ editor_cmd = _get_safe_editor()
354
+
355
+ # Open editor with validated command
356
+ try:
357
+ # Parse editor command (may include args like "code --wait")
358
+ editor_parts = shlex.split(editor_cmd)
359
+ subprocess.run([*editor_parts, str(config_path)], check=True)
360
+ except (subprocess.CalledProcessError, OSError, ValueError) as e:
361
+ raise RuntimeError(f"Editor failed: {e}") from e