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.
- oscura/__init__.py +169 -167
- oscura/analyzers/__init__.py +3 -0
- oscura/analyzers/classification.py +659 -0
- oscura/analyzers/digital/__init__.py +0 -48
- oscura/analyzers/digital/edges.py +325 -65
- oscura/analyzers/digital/extraction.py +0 -195
- oscura/analyzers/digital/quality.py +293 -166
- oscura/analyzers/digital/timing.py +260 -115
- oscura/analyzers/digital/timing_numba.py +334 -0
- oscura/analyzers/entropy.py +605 -0
- oscura/analyzers/eye/diagram.py +176 -109
- oscura/analyzers/eye/metrics.py +5 -5
- oscura/analyzers/jitter/__init__.py +6 -4
- oscura/analyzers/jitter/ber.py +52 -52
- oscura/analyzers/jitter/classification.py +156 -0
- oscura/analyzers/jitter/decomposition.py +163 -113
- oscura/analyzers/jitter/spectrum.py +80 -64
- oscura/analyzers/ml/__init__.py +39 -0
- oscura/analyzers/ml/features.py +600 -0
- oscura/analyzers/ml/signal_classifier.py +604 -0
- oscura/analyzers/packet/daq.py +246 -158
- oscura/analyzers/packet/parser.py +12 -1
- oscura/analyzers/packet/payload.py +50 -2110
- oscura/analyzers/packet/payload_analysis.py +361 -181
- oscura/analyzers/packet/payload_patterns.py +133 -70
- oscura/analyzers/packet/stream.py +84 -23
- oscura/analyzers/patterns/__init__.py +26 -5
- oscura/analyzers/patterns/anomaly_detection.py +908 -0
- oscura/analyzers/patterns/clustering.py +169 -108
- oscura/analyzers/patterns/clustering_optimized.py +227 -0
- oscura/analyzers/patterns/discovery.py +1 -1
- oscura/analyzers/patterns/matching.py +581 -197
- oscura/analyzers/patterns/pattern_mining.py +778 -0
- oscura/analyzers/patterns/periodic.py +121 -38
- oscura/analyzers/patterns/sequences.py +175 -78
- oscura/analyzers/power/conduction.py +1 -1
- oscura/analyzers/power/soa.py +6 -6
- oscura/analyzers/power/switching.py +250 -110
- oscura/analyzers/protocol/__init__.py +17 -1
- oscura/analyzers/protocols/__init__.py +1 -22
- oscura/analyzers/protocols/base.py +6 -6
- oscura/analyzers/protocols/ble/__init__.py +38 -0
- oscura/analyzers/protocols/ble/analyzer.py +809 -0
- oscura/analyzers/protocols/ble/uuids.py +288 -0
- oscura/analyzers/protocols/can.py +257 -127
- oscura/analyzers/protocols/can_fd.py +107 -80
- oscura/analyzers/protocols/flexray.py +139 -80
- oscura/analyzers/protocols/hdlc.py +93 -58
- oscura/analyzers/protocols/i2c.py +247 -106
- oscura/analyzers/protocols/i2s.py +138 -86
- oscura/analyzers/protocols/industrial/__init__.py +40 -0
- oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
- oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
- oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
- oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
- oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
- oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
- oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
- oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
- oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
- oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
- oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
- oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
- oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
- oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
- oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
- oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
- oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
- oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
- oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
- oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
- oscura/analyzers/protocols/jtag.py +180 -98
- oscura/analyzers/protocols/lin.py +219 -114
- oscura/analyzers/protocols/manchester.py +4 -4
- oscura/analyzers/protocols/onewire.py +253 -149
- oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
- oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
- oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
- oscura/analyzers/protocols/spi.py +192 -95
- oscura/analyzers/protocols/swd.py +321 -167
- oscura/analyzers/protocols/uart.py +267 -125
- oscura/analyzers/protocols/usb.py +235 -131
- oscura/analyzers/side_channel/power.py +17 -12
- oscura/analyzers/signal/__init__.py +15 -0
- oscura/analyzers/signal/timing_analysis.py +1086 -0
- oscura/analyzers/signal_integrity/__init__.py +4 -1
- oscura/analyzers/signal_integrity/sparams.py +2 -19
- oscura/analyzers/spectral/chunked.py +129 -60
- oscura/analyzers/spectral/chunked_fft.py +300 -94
- oscura/analyzers/spectral/chunked_wavelet.py +100 -80
- oscura/analyzers/statistical/checksum.py +376 -217
- oscura/analyzers/statistical/classification.py +229 -107
- oscura/analyzers/statistical/entropy.py +78 -53
- oscura/analyzers/statistics/correlation.py +407 -211
- oscura/analyzers/statistics/outliers.py +2 -2
- oscura/analyzers/statistics/streaming.py +30 -5
- oscura/analyzers/validation.py +216 -101
- oscura/analyzers/waveform/measurements.py +9 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
- oscura/analyzers/waveform/spectral.py +500 -228
- oscura/api/__init__.py +31 -5
- oscura/api/dsl/__init__.py +582 -0
- oscura/{dsl → api/dsl}/commands.py +43 -76
- oscura/{dsl → api/dsl}/interpreter.py +26 -51
- oscura/{dsl → api/dsl}/parser.py +107 -77
- oscura/{dsl → api/dsl}/repl.py +2 -2
- oscura/api/dsl.py +1 -1
- oscura/{integrations → api/integrations}/__init__.py +1 -1
- oscura/{integrations → api/integrations}/llm.py +201 -102
- oscura/api/operators.py +3 -3
- oscura/api/optimization.py +144 -30
- oscura/api/rest_server.py +921 -0
- oscura/api/server/__init__.py +17 -0
- oscura/api/server/dashboard.py +850 -0
- oscura/api/server/static/README.md +34 -0
- oscura/api/server/templates/base.html +181 -0
- oscura/api/server/templates/export.html +120 -0
- oscura/api/server/templates/home.html +284 -0
- oscura/api/server/templates/protocols.html +58 -0
- oscura/api/server/templates/reports.html +43 -0
- oscura/api/server/templates/session_detail.html +89 -0
- oscura/api/server/templates/sessions.html +83 -0
- oscura/api/server/templates/waveforms.html +73 -0
- oscura/automotive/__init__.py +8 -1
- oscura/automotive/can/__init__.py +10 -0
- oscura/automotive/can/checksum.py +3 -1
- oscura/automotive/can/dbc_generator.py +590 -0
- oscura/automotive/can/message_wrapper.py +121 -74
- oscura/automotive/can/patterns.py +98 -21
- oscura/automotive/can/session.py +292 -56
- oscura/automotive/can/state_machine.py +6 -3
- oscura/automotive/can/stimulus_response.py +97 -75
- oscura/automotive/dbc/__init__.py +10 -2
- oscura/automotive/dbc/generator.py +84 -56
- oscura/automotive/dbc/parser.py +6 -6
- oscura/automotive/dtc/data.json +2763 -0
- oscura/automotive/dtc/database.py +2 -2
- oscura/automotive/flexray/__init__.py +31 -0
- oscura/automotive/flexray/analyzer.py +504 -0
- oscura/automotive/flexray/crc.py +185 -0
- oscura/automotive/flexray/fibex.py +449 -0
- oscura/automotive/j1939/__init__.py +45 -8
- oscura/automotive/j1939/analyzer.py +605 -0
- oscura/automotive/j1939/spns.py +326 -0
- oscura/automotive/j1939/transport.py +306 -0
- oscura/automotive/lin/__init__.py +47 -0
- oscura/automotive/lin/analyzer.py +612 -0
- oscura/automotive/loaders/blf.py +13 -2
- oscura/automotive/loaders/csv_can.py +143 -72
- oscura/automotive/loaders/dispatcher.py +50 -2
- oscura/automotive/loaders/mdf.py +86 -45
- oscura/automotive/loaders/pcap.py +111 -61
- oscura/automotive/uds/__init__.py +4 -0
- oscura/automotive/uds/analyzer.py +725 -0
- oscura/automotive/uds/decoder.py +140 -58
- oscura/automotive/uds/models.py +7 -1
- oscura/automotive/visualization.py +1 -1
- oscura/cli/analyze.py +348 -0
- oscura/cli/batch.py +142 -122
- oscura/cli/benchmark.py +275 -0
- oscura/cli/characterize.py +137 -82
- oscura/cli/compare.py +224 -131
- oscura/cli/completion.py +250 -0
- oscura/cli/config_cmd.py +361 -0
- oscura/cli/decode.py +164 -87
- oscura/cli/export.py +286 -0
- oscura/cli/main.py +115 -31
- oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
- oscura/{onboarding → cli/onboarding}/help.py +80 -58
- oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
- oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
- oscura/cli/progress.py +147 -0
- oscura/cli/shell.py +157 -135
- oscura/cli/validate_cmd.py +204 -0
- oscura/cli/visualize.py +158 -0
- oscura/convenience.py +125 -79
- oscura/core/__init__.py +4 -2
- oscura/core/backend_selector.py +3 -3
- oscura/core/cache.py +126 -15
- oscura/core/cancellation.py +1 -1
- oscura/{config → core/config}/__init__.py +20 -11
- oscura/{config → core/config}/defaults.py +1 -1
- oscura/{config → core/config}/loader.py +7 -5
- oscura/{config → core/config}/memory.py +5 -5
- oscura/{config → core/config}/migration.py +1 -1
- oscura/{config → core/config}/pipeline.py +99 -23
- oscura/{config → core/config}/preferences.py +1 -1
- oscura/{config → core/config}/protocol.py +3 -3
- oscura/{config → core/config}/schema.py +426 -272
- oscura/{config → core/config}/settings.py +1 -1
- oscura/{config → core/config}/thresholds.py +195 -153
- oscura/core/correlation.py +5 -6
- oscura/core/cross_domain.py +0 -2
- oscura/core/debug.py +9 -5
- oscura/{extensibility → core/extensibility}/docs.py +158 -70
- oscura/{extensibility → core/extensibility}/extensions.py +160 -76
- oscura/{extensibility → core/extensibility}/logging.py +1 -1
- oscura/{extensibility → core/extensibility}/measurements.py +1 -1
- oscura/{extensibility → core/extensibility}/plugins.py +1 -1
- oscura/{extensibility → core/extensibility}/templates.py +73 -3
- oscura/{extensibility → core/extensibility}/validation.py +1 -1
- oscura/core/gpu_backend.py +11 -7
- oscura/core/log_query.py +101 -11
- oscura/core/logging.py +126 -54
- oscura/core/logging_advanced.py +5 -5
- oscura/core/memory_limits.py +108 -70
- oscura/core/memory_monitor.py +2 -2
- oscura/core/memory_progress.py +7 -7
- oscura/core/memory_warnings.py +1 -1
- oscura/core/numba_backend.py +13 -13
- oscura/{plugins → core/plugins}/__init__.py +9 -9
- oscura/{plugins → core/plugins}/base.py +7 -7
- oscura/{plugins → core/plugins}/cli.py +3 -3
- oscura/{plugins → core/plugins}/discovery.py +186 -106
- oscura/{plugins → core/plugins}/lifecycle.py +1 -1
- oscura/{plugins → core/plugins}/manager.py +7 -7
- oscura/{plugins → core/plugins}/registry.py +3 -3
- oscura/{plugins → core/plugins}/versioning.py +1 -1
- oscura/core/progress.py +16 -1
- oscura/core/provenance.py +8 -2
- oscura/{schemas → core/schemas}/__init__.py +2 -2
- oscura/core/schemas/bus_configuration.json +322 -0
- oscura/core/schemas/device_mapping.json +182 -0
- oscura/core/schemas/packet_format.json +418 -0
- oscura/core/schemas/protocol_definition.json +363 -0
- oscura/core/types.py +4 -0
- oscura/core/uncertainty.py +3 -3
- oscura/correlation/__init__.py +52 -0
- oscura/correlation/multi_protocol.py +811 -0
- oscura/discovery/auto_decoder.py +117 -35
- oscura/discovery/comparison.py +191 -86
- oscura/discovery/quality_validator.py +155 -68
- oscura/discovery/signal_detector.py +196 -79
- oscura/export/__init__.py +18 -20
- oscura/export/kaitai_struct.py +513 -0
- oscura/export/scapy_layer.py +801 -0
- oscura/export/wireshark/README.md +15 -15
- oscura/export/wireshark/generator.py +1 -1
- oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
- oscura/export/wireshark_dissector.py +746 -0
- oscura/guidance/wizard.py +207 -111
- oscura/hardware/__init__.py +19 -0
- oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
- oscura/{acquisition → hardware/acquisition}/file.py +2 -2
- oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
- oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
- oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
- oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
- oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
- oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
- oscura/hardware/firmware/__init__.py +29 -0
- oscura/hardware/firmware/pattern_recognition.py +874 -0
- oscura/hardware/hal_detector.py +736 -0
- oscura/hardware/security/__init__.py +37 -0
- oscura/hardware/security/side_channel_detector.py +1126 -0
- oscura/inference/__init__.py +4 -0
- oscura/inference/active_learning/README.md +7 -7
- oscura/inference/active_learning/observation_table.py +4 -1
- oscura/inference/alignment.py +216 -123
- oscura/inference/bayesian.py +113 -33
- oscura/inference/crc_reverse.py +101 -55
- oscura/inference/logic.py +6 -2
- oscura/inference/message_format.py +342 -183
- oscura/inference/protocol.py +95 -44
- oscura/inference/protocol_dsl.py +180 -82
- oscura/inference/signal_intelligence.py +1439 -706
- oscura/inference/spectral.py +99 -57
- oscura/inference/state_machine.py +810 -158
- oscura/inference/stream.py +270 -110
- oscura/iot/__init__.py +34 -0
- oscura/iot/coap/__init__.py +32 -0
- oscura/iot/coap/analyzer.py +668 -0
- oscura/iot/coap/options.py +212 -0
- oscura/iot/lorawan/__init__.py +21 -0
- oscura/iot/lorawan/crypto.py +206 -0
- oscura/iot/lorawan/decoder.py +801 -0
- oscura/iot/lorawan/mac_commands.py +341 -0
- oscura/iot/mqtt/__init__.py +27 -0
- oscura/iot/mqtt/analyzer.py +999 -0
- oscura/iot/mqtt/properties.py +315 -0
- oscura/iot/zigbee/__init__.py +31 -0
- oscura/iot/zigbee/analyzer.py +615 -0
- oscura/iot/zigbee/security.py +153 -0
- oscura/iot/zigbee/zcl.py +349 -0
- oscura/jupyter/display.py +125 -45
- oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
- oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
- oscura/jupyter/exploratory/fuzzy.py +746 -0
- oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
- oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
- oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
- oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
- oscura/jupyter/exploratory/sync.py +612 -0
- oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
- oscura/jupyter/magic.py +4 -4
- oscura/{ui → jupyter/ui}/__init__.py +2 -2
- oscura/{ui → jupyter/ui}/formatters.py +3 -3
- oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
- oscura/loaders/__init__.py +171 -63
- oscura/loaders/binary.py +88 -1
- oscura/loaders/chipwhisperer.py +153 -137
- oscura/loaders/configurable.py +208 -86
- oscura/loaders/csv_loader.py +458 -215
- oscura/loaders/hdf5_loader.py +278 -119
- oscura/loaders/lazy.py +87 -54
- oscura/loaders/mmap_loader.py +1 -1
- oscura/loaders/numpy_loader.py +253 -116
- oscura/loaders/pcap.py +226 -151
- oscura/loaders/rigol.py +110 -49
- oscura/loaders/sigrok.py +201 -78
- oscura/loaders/tdms.py +81 -58
- oscura/loaders/tektronix.py +291 -174
- oscura/loaders/touchstone.py +182 -87
- oscura/loaders/vcd.py +215 -117
- oscura/loaders/wav.py +155 -68
- oscura/reporting/__init__.py +9 -7
- oscura/reporting/analyze.py +352 -146
- oscura/reporting/argument_preparer.py +69 -14
- oscura/reporting/auto_report.py +97 -61
- oscura/reporting/batch.py +131 -58
- oscura/reporting/chart_selection.py +57 -45
- oscura/reporting/comparison.py +63 -17
- oscura/reporting/content/executive.py +76 -24
- oscura/reporting/core_formats/multi_format.py +11 -8
- oscura/reporting/engine.py +312 -158
- oscura/reporting/enhanced_reports.py +949 -0
- oscura/reporting/export.py +86 -43
- oscura/reporting/formatting/numbers.py +69 -42
- oscura/reporting/html.py +139 -58
- oscura/reporting/index.py +137 -65
- oscura/reporting/output.py +158 -67
- oscura/reporting/pdf.py +67 -102
- oscura/reporting/plots.py +191 -112
- oscura/reporting/sections.py +88 -47
- oscura/reporting/standards.py +104 -61
- oscura/reporting/summary_generator.py +75 -55
- oscura/reporting/tables.py +138 -54
- oscura/reporting/templates/enhanced/protocol_re.html +525 -0
- oscura/reporting/templates/index.md +13 -13
- oscura/sessions/__init__.py +14 -23
- oscura/sessions/base.py +3 -3
- oscura/sessions/blackbox.py +106 -10
- oscura/sessions/generic.py +2 -2
- oscura/sessions/legacy.py +783 -0
- oscura/side_channel/__init__.py +63 -0
- oscura/side_channel/dpa.py +1025 -0
- oscura/utils/__init__.py +15 -1
- oscura/utils/autodetect.py +1 -5
- oscura/utils/bitwise.py +118 -0
- oscura/{builders → utils/builders}/__init__.py +1 -1
- oscura/{comparison → utils/comparison}/__init__.py +6 -6
- oscura/{comparison → utils/comparison}/compare.py +202 -101
- oscura/{comparison → utils/comparison}/golden.py +83 -63
- oscura/{comparison → utils/comparison}/limits.py +313 -89
- oscura/{comparison → utils/comparison}/mask.py +151 -45
- oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
- oscura/{comparison → utils/comparison}/visualization.py +147 -89
- oscura/{component → utils/component}/__init__.py +3 -3
- oscura/{component → utils/component}/impedance.py +122 -58
- oscura/{component → utils/component}/reactive.py +165 -168
- oscura/{component → utils/component}/transmission_line.py +3 -3
- oscura/{filtering → utils/filtering}/__init__.py +6 -6
- oscura/{filtering → utils/filtering}/base.py +1 -1
- oscura/{filtering → utils/filtering}/convenience.py +2 -2
- oscura/{filtering → utils/filtering}/design.py +169 -93
- oscura/{filtering → utils/filtering}/filters.py +2 -2
- oscura/{filtering → utils/filtering}/introspection.py +2 -2
- oscura/utils/geometry.py +31 -0
- oscura/utils/imports.py +184 -0
- oscura/utils/lazy.py +1 -1
- oscura/{math → utils/math}/__init__.py +2 -2
- oscura/{math → utils/math}/arithmetic.py +114 -48
- oscura/{math → utils/math}/interpolation.py +139 -106
- oscura/utils/memory.py +129 -66
- oscura/utils/memory_advanced.py +92 -9
- oscura/utils/memory_extensions.py +10 -8
- oscura/{optimization → utils/optimization}/__init__.py +1 -1
- oscura/{optimization → utils/optimization}/search.py +2 -2
- oscura/utils/performance/__init__.py +58 -0
- oscura/utils/performance/caching.py +889 -0
- oscura/utils/performance/lsh_clustering.py +333 -0
- oscura/utils/performance/memory_optimizer.py +699 -0
- oscura/utils/performance/optimizations.py +675 -0
- oscura/utils/performance/parallel.py +654 -0
- oscura/utils/performance/profiling.py +661 -0
- oscura/{pipeline → utils/pipeline}/base.py +1 -1
- oscura/{pipeline → utils/pipeline}/composition.py +11 -3
- oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
- oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
- oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
- oscura/{search → utils/search}/__init__.py +3 -3
- oscura/{search → utils/search}/anomaly.py +188 -58
- oscura/utils/search/context.py +294 -0
- oscura/{search → utils/search}/pattern.py +138 -10
- oscura/utils/serial.py +51 -0
- oscura/utils/storage/__init__.py +61 -0
- oscura/utils/storage/database.py +1166 -0
- oscura/{streaming → utils/streaming}/chunked.py +302 -143
- oscura/{streaming → utils/streaming}/progressive.py +1 -1
- oscura/{streaming → utils/streaming}/realtime.py +3 -2
- oscura/{triggering → utils/triggering}/__init__.py +6 -6
- oscura/{triggering → utils/triggering}/base.py +6 -6
- oscura/{triggering → utils/triggering}/edge.py +2 -2
- oscura/{triggering → utils/triggering}/pattern.py +2 -2
- oscura/{triggering → utils/triggering}/pulse.py +115 -74
- oscura/{triggering → utils/triggering}/window.py +2 -2
- oscura/utils/validation.py +32 -0
- oscura/validation/__init__.py +121 -0
- oscura/{compliance → validation/compliance}/__init__.py +5 -5
- oscura/{compliance → validation/compliance}/advanced.py +5 -5
- oscura/{compliance → validation/compliance}/masks.py +1 -1
- oscura/{compliance → validation/compliance}/reporting.py +127 -53
- oscura/{compliance → validation/compliance}/testing.py +114 -52
- oscura/validation/compliance_tests.py +915 -0
- oscura/validation/fuzzer.py +990 -0
- oscura/validation/grammar_tests.py +596 -0
- oscura/validation/grammar_validator.py +904 -0
- oscura/validation/hil_testing.py +977 -0
- oscura/{quality → validation/quality}/__init__.py +4 -4
- oscura/{quality → validation/quality}/ensemble.py +251 -171
- oscura/{quality → validation/quality}/explainer.py +3 -3
- oscura/{quality → validation/quality}/scoring.py +1 -1
- oscura/{quality → validation/quality}/warnings.py +4 -4
- oscura/validation/regression_suite.py +808 -0
- oscura/validation/replay.py +788 -0
- oscura/{testing → validation/testing}/__init__.py +2 -2
- oscura/{testing → validation/testing}/synthetic.py +5 -5
- oscura/visualization/__init__.py +9 -0
- oscura/visualization/accessibility.py +1 -1
- oscura/visualization/annotations.py +64 -67
- oscura/visualization/colors.py +7 -7
- oscura/visualization/digital.py +180 -81
- oscura/visualization/eye.py +236 -85
- oscura/visualization/interactive.py +320 -143
- oscura/visualization/jitter.py +587 -247
- oscura/visualization/layout.py +169 -134
- oscura/visualization/optimization.py +103 -52
- oscura/visualization/palettes.py +1 -1
- oscura/visualization/power.py +427 -211
- oscura/visualization/power_extended.py +626 -297
- oscura/visualization/presets.py +2 -0
- oscura/visualization/protocols.py +495 -181
- oscura/visualization/render.py +79 -63
- oscura/visualization/reverse_engineering.py +171 -124
- oscura/visualization/signal_integrity.py +460 -279
- oscura/visualization/specialized.py +190 -100
- oscura/visualization/spectral.py +670 -255
- oscura/visualization/thumbnails.py +166 -137
- oscura/visualization/waveform.py +150 -63
- oscura/workflows/__init__.py +3 -0
- oscura/{batch → workflows/batch}/__init__.py +5 -5
- oscura/{batch → workflows/batch}/advanced.py +150 -75
- oscura/workflows/batch/aggregate.py +531 -0
- oscura/workflows/batch/analyze.py +236 -0
- oscura/{batch → workflows/batch}/logging.py +2 -2
- oscura/{batch → workflows/batch}/metrics.py +1 -1
- oscura/workflows/complete_re.py +1144 -0
- oscura/workflows/compliance.py +44 -54
- oscura/workflows/digital.py +197 -51
- oscura/workflows/legacy/__init__.py +12 -0
- oscura/{workflow → workflows/legacy}/dag.py +4 -1
- oscura/workflows/multi_trace.py +9 -9
- oscura/workflows/power.py +42 -62
- oscura/workflows/protocol.py +82 -49
- oscura/workflows/reverse_engineering.py +351 -150
- oscura/workflows/signal_integrity.py +157 -82
- oscura-0.6.0.dist-info/METADATA +643 -0
- oscura-0.6.0.dist-info/RECORD +590 -0
- oscura/analyzers/digital/ic_database.py +0 -498
- oscura/analyzers/digital/timing_paths.py +0 -339
- oscura/analyzers/digital/vintage.py +0 -377
- oscura/analyzers/digital/vintage_result.py +0 -148
- oscura/analyzers/protocols/parallel_bus.py +0 -449
- oscura/batch/aggregate.py +0 -300
- oscura/batch/analyze.py +0 -139
- oscura/dsl/__init__.py +0 -73
- oscura/exceptions.py +0 -59
- oscura/exploratory/fuzzy.py +0 -513
- oscura/exploratory/sync.py +0 -384
- oscura/export/wavedrom.py +0 -430
- oscura/exporters/__init__.py +0 -94
- oscura/exporters/csv.py +0 -303
- oscura/exporters/exporters.py +0 -44
- oscura/exporters/hdf5.py +0 -217
- oscura/exporters/html_export.py +0 -701
- oscura/exporters/json_export.py +0 -338
- oscura/exporters/markdown_export.py +0 -367
- oscura/exporters/matlab_export.py +0 -354
- oscura/exporters/npz_export.py +0 -219
- oscura/exporters/spice_export.py +0 -210
- oscura/exporters/vintage_logic_csv.py +0 -247
- oscura/reporting/vintage_logic_report.py +0 -523
- oscura/search/context.py +0 -149
- oscura/session/__init__.py +0 -34
- oscura/session/annotations.py +0 -289
- oscura/session/history.py +0 -313
- oscura/session/session.py +0 -520
- oscura/visualization/digital_advanced.py +0 -718
- oscura/visualization/figure_manager.py +0 -156
- oscura/workflow/__init__.py +0 -13
- oscura-0.5.0.dist-info/METADATA +0 -407
- oscura-0.5.0.dist-info/RECORD +0 -486
- /oscura/core/{config.py → config/legacy.py} +0 -0
- /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
- /oscura/{extensibility → core/extensibility}/registry.py +0 -0
- /oscura/{plugins → core/plugins}/isolation.py +0 -0
- /oscura/{builders → utils/builders}/signal_builder.py +0 -0
- /oscura/{optimization → utils/optimization}/parallel.py +0 -0
- /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
- /oscura/{streaming → utils/streaming}/__init__.py +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
- {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
|
-
#
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
|
|
277
|
-
|
|
278
|
-
intermediate_mem
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
|
|
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
|
-
|
|
308
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
|
618
|
+
global _memory_config
|
|
556
619
|
|
|
557
620
|
if max_memory is not None:
|
|
558
621
|
if isinstance(max_memory, str):
|
oscura/utils/memory_advanced.py
CHANGED
|
@@ -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 = {
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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))
|
|
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(
|
|
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")
|
|
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]
|
|
@@ -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
|
+
]
|