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/loaders/binary.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"""Binary file loader for raw signal data.
|
|
2
2
|
|
|
3
3
|
Loads raw binary files containing signal data with user-specified format.
|
|
4
|
+
Supports memory-mapped I/O for efficient handling of large files.
|
|
4
5
|
"""
|
|
5
6
|
|
|
6
7
|
from __future__ import annotations
|
|
7
8
|
|
|
9
|
+
import mmap
|
|
8
10
|
from pathlib import Path
|
|
9
11
|
from typing import TYPE_CHECKING, Any
|
|
10
12
|
|
|
@@ -25,9 +27,14 @@ def load_binary(
|
|
|
25
27
|
channel: int = 0,
|
|
26
28
|
offset: int = 0,
|
|
27
29
|
count: int = -1,
|
|
30
|
+
mmap_mode: bool = False,
|
|
28
31
|
) -> WaveformTrace:
|
|
29
32
|
"""Load raw binary file as waveform trace.
|
|
30
33
|
|
|
34
|
+
Supports memory-mapped I/O for efficient handling of large files (>1GB).
|
|
35
|
+
Memory mapping provides 5-10x speedup by eliminating syscall overhead
|
|
36
|
+
and leveraging OS-level page caching.
|
|
37
|
+
|
|
31
38
|
Args:
|
|
32
39
|
path: Path to the binary file.
|
|
33
40
|
dtype: NumPy dtype for the data (default: float64).
|
|
@@ -36,18 +43,33 @@ def load_binary(
|
|
|
36
43
|
channel: Channel index to load (0-based).
|
|
37
44
|
offset: Number of samples to skip from start.
|
|
38
45
|
count: Number of samples to read (-1 for all).
|
|
46
|
+
mmap_mode: If True, use memory-mapped I/O for large files.
|
|
47
|
+
Recommended for files >1GB. Data stays on disk until accessed.
|
|
39
48
|
|
|
40
49
|
Returns:
|
|
41
50
|
WaveformTrace containing the loaded data.
|
|
42
51
|
|
|
52
|
+
Performance:
|
|
53
|
+
- Traditional I/O: ~100MB/s for large files
|
|
54
|
+
- Memory-mapped: ~500-1000MB/s for large files
|
|
55
|
+
- Speedup: 5-10x depending on file size and access pattern
|
|
56
|
+
|
|
43
57
|
Example:
|
|
44
58
|
>>> from oscura.loaders.binary import load_binary
|
|
59
|
+
>>> # Standard loading for small files
|
|
45
60
|
>>> trace = load_binary("signal.bin", dtype="int16", sample_rate=1e6)
|
|
61
|
+
>>>
|
|
62
|
+
>>> # Memory-mapped loading for large files (>1GB)
|
|
63
|
+
>>> trace = load_binary("large.bin", dtype="float32", sample_rate=1e9, mmap_mode=True)
|
|
64
|
+
>>> # Access subset efficiently: trace.data[1000:2000]
|
|
46
65
|
"""
|
|
47
66
|
path = Path(path)
|
|
48
67
|
|
|
49
68
|
# Load raw data
|
|
50
|
-
|
|
69
|
+
if mmap_mode:
|
|
70
|
+
data = _load_binary_mmap(path, dtype, offset, count)
|
|
71
|
+
else:
|
|
72
|
+
data = np.fromfile(path, dtype=dtype, count=count, offset=offset * np.dtype(dtype).itemsize)
|
|
51
73
|
|
|
52
74
|
# Handle multi-channel data
|
|
53
75
|
if channels > 1:
|
|
@@ -66,4 +88,69 @@ def load_binary(
|
|
|
66
88
|
return WaveformTrace(data=data.astype(np.float64), metadata=metadata)
|
|
67
89
|
|
|
68
90
|
|
|
91
|
+
def _load_binary_mmap(
|
|
92
|
+
path: Path,
|
|
93
|
+
dtype: str | np.dtype[Any],
|
|
94
|
+
offset: int,
|
|
95
|
+
count: int,
|
|
96
|
+
) -> np.ndarray[Any, np.dtype[Any]]:
|
|
97
|
+
"""Load binary data using memory-mapped I/O.
|
|
98
|
+
|
|
99
|
+
Uses memory mapping for 5-10x speedup on large files by eliminating
|
|
100
|
+
repeated syscalls and leveraging OS-level page caching.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
path: Path to binary file.
|
|
104
|
+
dtype: NumPy dtype for the data.
|
|
105
|
+
offset: Number of samples to skip from start.
|
|
106
|
+
count: Number of samples to read (-1 for all).
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
NumPy array backed by memory-mapped file.
|
|
110
|
+
|
|
111
|
+
Note:
|
|
112
|
+
Memory mapping creates virtual memory view of file without loading
|
|
113
|
+
entire file into RAM. OS handles paging automatically, making this
|
|
114
|
+
efficient even for files larger than physical memory.
|
|
115
|
+
"""
|
|
116
|
+
np_dtype = np.dtype(dtype)
|
|
117
|
+
bytes_per_sample = np_dtype.itemsize
|
|
118
|
+
byte_offset = offset * bytes_per_sample
|
|
119
|
+
|
|
120
|
+
# Get file size and calculate total samples
|
|
121
|
+
file_size = path.stat().st_size
|
|
122
|
+
|
|
123
|
+
# Handle empty file
|
|
124
|
+
if file_size == 0 or file_size <= byte_offset:
|
|
125
|
+
return np.array([], dtype=np_dtype)
|
|
126
|
+
|
|
127
|
+
available_bytes = file_size - byte_offset
|
|
128
|
+
available_samples = available_bytes // bytes_per_sample
|
|
129
|
+
|
|
130
|
+
# Determine how many samples to read
|
|
131
|
+
samples_to_read = available_samples if count == -1 else min(count, available_samples)
|
|
132
|
+
bytes_to_read = samples_to_read * bytes_per_sample
|
|
133
|
+
|
|
134
|
+
# Handle no data to read
|
|
135
|
+
if samples_to_read == 0:
|
|
136
|
+
return np.array([], dtype=np_dtype)
|
|
137
|
+
|
|
138
|
+
with open(path, "rb") as f:
|
|
139
|
+
# Create read-only memory map of entire file
|
|
140
|
+
mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
|
|
141
|
+
try:
|
|
142
|
+
# Extract requested range from memory map
|
|
143
|
+
start_byte = byte_offset
|
|
144
|
+
end_byte = byte_offset + bytes_to_read
|
|
145
|
+
|
|
146
|
+
# Create array from memory-mapped region (no syscalls, OS handles paging)
|
|
147
|
+
data: np.ndarray[Any, np.dtype[Any]] = np.frombuffer(
|
|
148
|
+
mm[start_byte:end_byte], dtype=np_dtype
|
|
149
|
+
).copy() # Copy to ensure data persists after mmap closes
|
|
150
|
+
|
|
151
|
+
return data
|
|
152
|
+
finally:
|
|
153
|
+
mm.close()
|
|
154
|
+
|
|
155
|
+
|
|
69
156
|
__all__ = ["load_binary"]
|
oscura/loaders/chipwhisperer.py
CHANGED
|
@@ -141,80 +141,68 @@ def load_chipwhisperer_npy(
|
|
|
141
141
|
... print("Plaintexts available")
|
|
142
142
|
"""
|
|
143
143
|
path = Path(path)
|
|
144
|
+
traces = _load_chipwhisperer_traces(path)
|
|
145
|
+
|
|
146
|
+
# Load associated metadata files
|
|
144
147
|
base_path = path.parent
|
|
145
148
|
base_name = path.stem
|
|
149
|
+
plaintexts = _load_optional_npy(base_path, base_name, "textin")
|
|
150
|
+
ciphertexts = _load_optional_npy(base_path, base_name, "textout")
|
|
151
|
+
keys = _load_optional_npy(base_path, base_name, "keys")
|
|
152
|
+
|
|
153
|
+
sample_rate = sample_rate or 1e6 # Default 1 MS/s
|
|
154
|
+
|
|
155
|
+
return ChipWhispererTraceSet(
|
|
156
|
+
traces=traces.astype(np.float64),
|
|
157
|
+
plaintexts=plaintexts.astype(np.uint8) if plaintexts is not None else None,
|
|
158
|
+
ciphertexts=ciphertexts.astype(np.uint8) if ciphertexts is not None else None,
|
|
159
|
+
keys=keys.astype(np.uint8) if keys is not None else None,
|
|
160
|
+
sample_rate=sample_rate,
|
|
161
|
+
metadata={"source_file": str(path), "format": "chipwhisperer_npy"},
|
|
162
|
+
)
|
|
146
163
|
|
|
147
|
-
try:
|
|
148
|
-
# Load main trace data
|
|
149
|
-
traces = np.load(path)
|
|
150
164
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
165
|
+
def _load_chipwhisperer_traces(path: Path) -> NDArray[np.float64]:
|
|
166
|
+
"""Load and validate ChipWhisperer trace array."""
|
|
167
|
+
try:
|
|
168
|
+
loaded_data: NDArray[Any] = np.load(path)
|
|
169
|
+
|
|
170
|
+
result: NDArray[np.float64]
|
|
171
|
+
if loaded_data.ndim == 1:
|
|
172
|
+
result = loaded_data.reshape(1, -1).astype(np.float64)
|
|
173
|
+
elif loaded_data.ndim == 2:
|
|
174
|
+
result = loaded_data.astype(np.float64)
|
|
175
|
+
else:
|
|
155
176
|
raise FormatError(
|
|
156
|
-
f"Expected 1D or 2D trace array, got {
|
|
177
|
+
f"Expected 1D or 2D trace array, got {loaded_data.ndim}D",
|
|
157
178
|
file_path=str(path),
|
|
158
179
|
)
|
|
180
|
+
return result
|
|
159
181
|
|
|
160
182
|
except (OSError, ValueError) as e:
|
|
161
|
-
# Catch file I/O errors, but let FormatError propagate
|
|
162
183
|
raise LoaderError(
|
|
163
184
|
"Failed to load trace file",
|
|
164
185
|
file_path=str(path),
|
|
165
186
|
details=str(e),
|
|
166
187
|
) from e
|
|
167
188
|
|
|
168
|
-
# Try to load associated files (common ChipWhisperer naming)
|
|
169
|
-
plaintexts = None
|
|
170
|
-
ciphertexts = None
|
|
171
|
-
keys = None
|
|
172
189
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if
|
|
178
|
-
|
|
179
|
-
plaintexts = np.load(textin_path)
|
|
180
|
-
except Exception:
|
|
181
|
-
pass # Optional metadata file, silently ignore if missing or corrupt # Not critical
|
|
190
|
+
def _load_optional_npy(base_path: Path, base_name: str, suffix: str) -> NDArray[np.float64] | None:
|
|
191
|
+
"""Load optional ChipWhisperer metadata file."""
|
|
192
|
+
# Try with base name prefix first
|
|
193
|
+
filepath = base_path / f"{base_name}_{suffix}.npy"
|
|
194
|
+
if not filepath.exists():
|
|
195
|
+
filepath = base_path / f"{suffix}.npy"
|
|
182
196
|
|
|
183
|
-
|
|
184
|
-
textout_path = base_path / f"{base_name}_textout.npy"
|
|
185
|
-
if not textout_path.exists():
|
|
186
|
-
textout_path = base_path / "textout.npy"
|
|
187
|
-
if textout_path.exists():
|
|
197
|
+
if filepath.exists():
|
|
188
198
|
try:
|
|
189
|
-
|
|
199
|
+
loaded: NDArray[Any] = np.load(filepath)
|
|
200
|
+
result: NDArray[np.float64] = loaded.astype(np.float64)
|
|
201
|
+
return result
|
|
190
202
|
except Exception:
|
|
191
|
-
pass
|
|
192
|
-
|
|
193
|
-
# Look for keys.npy
|
|
194
|
-
keys_path = base_path / f"{base_name}_keys.npy"
|
|
195
|
-
if not keys_path.exists():
|
|
196
|
-
keys_path = base_path / "keys.npy"
|
|
197
|
-
if keys_path.exists():
|
|
198
|
-
try:
|
|
199
|
-
keys = np.load(keys_path)
|
|
200
|
-
except Exception:
|
|
201
|
-
pass # Optional metadata file, silently ignore if corrupt
|
|
202
|
-
|
|
203
|
-
# Use default sample rate if not specified
|
|
204
|
-
if sample_rate is None:
|
|
205
|
-
sample_rate = 1e6 # Default 1 MS/s
|
|
203
|
+
pass # Optional file, silently ignore if corrupt
|
|
206
204
|
|
|
207
|
-
return
|
|
208
|
-
traces=traces.astype(np.float64),
|
|
209
|
-
plaintexts=plaintexts.astype(np.uint8) if plaintexts is not None else None,
|
|
210
|
-
ciphertexts=ciphertexts.astype(np.uint8) if ciphertexts is not None else None,
|
|
211
|
-
keys=keys.astype(np.uint8) if keys is not None else None,
|
|
212
|
-
sample_rate=sample_rate,
|
|
213
|
-
metadata={
|
|
214
|
-
"source_file": str(path),
|
|
215
|
-
"format": "chipwhisperer_npy",
|
|
216
|
-
},
|
|
217
|
-
)
|
|
205
|
+
return None
|
|
218
206
|
|
|
219
207
|
|
|
220
208
|
def load_chipwhisperer_trs(
|
|
@@ -252,99 +240,24 @@ def load_chipwhisperer_trs(
|
|
|
252
240
|
|
|
253
241
|
try:
|
|
254
242
|
with open(path, "rb") as f:
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
while True:
|
|
260
|
-
tag_byte = f.read(1)
|
|
261
|
-
if not tag_byte or tag_byte == b"\x5f": # End of header
|
|
262
|
-
break
|
|
263
|
-
|
|
264
|
-
tag = tag_byte[0]
|
|
265
|
-
length = int.from_bytes(f.read(1), byteorder="little")
|
|
266
|
-
|
|
267
|
-
# Extended length for large values
|
|
268
|
-
if length == 0xFF:
|
|
269
|
-
length = int.from_bytes(f.read(4), byteorder="little")
|
|
270
|
-
|
|
271
|
-
value = f.read(length)
|
|
272
|
-
tags[tag] = value
|
|
273
|
-
|
|
274
|
-
# Parse critical tags
|
|
275
|
-
# 0x41: Number of traces
|
|
276
|
-
n_traces = int.from_bytes(tags.get(0x41, b"\x00\x00"), byteorder="little")
|
|
277
|
-
|
|
278
|
-
# 0x42: Number of samples per trace
|
|
279
|
-
n_samples = int.from_bytes(tags.get(0x42, b"\x00\x00"), byteorder="little")
|
|
280
|
-
|
|
281
|
-
# 0x43: Sample coding (1=byte, 2=short, 4=float)
|
|
282
|
-
sample_coding = tags.get(0x43, b"\x01")[0]
|
|
283
|
-
|
|
284
|
-
# 0x44: Data length (plaintext/ciphertext)
|
|
285
|
-
data_length = int.from_bytes(tags.get(0x44, b"\x00\x00"), byteorder="little")
|
|
286
|
-
|
|
287
|
-
if n_traces == 0 or n_samples == 0:
|
|
288
|
-
raise FormatError(
|
|
289
|
-
"Invalid TRS file: zero traces or samples",
|
|
290
|
-
file_path=str(path),
|
|
291
|
-
)
|
|
292
|
-
|
|
293
|
-
# Determine numpy dtype from sample coding
|
|
294
|
-
dtype: type[np.int8] | type[np.int16] | type[np.float32]
|
|
295
|
-
if sample_coding == 1:
|
|
296
|
-
dtype = np.int8
|
|
297
|
-
elif sample_coding == 2:
|
|
298
|
-
dtype = np.int16
|
|
299
|
-
elif sample_coding == 4:
|
|
300
|
-
dtype = np.float32
|
|
301
|
-
else:
|
|
302
|
-
raise FormatError(
|
|
303
|
-
f"Unsupported sample coding: {sample_coding}",
|
|
304
|
-
file_path=str(path),
|
|
305
|
-
)
|
|
306
|
-
|
|
307
|
-
# Read traces
|
|
308
|
-
traces = np.zeros((n_traces, n_samples), dtype=np.float64)
|
|
309
|
-
plaintexts = (
|
|
310
|
-
np.zeros((n_traces, data_length), dtype=np.uint8) if data_length > 0 else None
|
|
311
|
-
)
|
|
312
|
-
ciphertexts = None # Not typically in TRS files
|
|
313
|
-
|
|
314
|
-
for trace_idx in range(n_traces):
|
|
315
|
-
# Read trace-specific data (plaintext/key)
|
|
316
|
-
if data_length > 0:
|
|
317
|
-
trace_data = np.frombuffer(f.read(data_length), dtype=np.uint8)
|
|
318
|
-
if plaintexts is not None:
|
|
319
|
-
plaintexts[trace_idx] = trace_data
|
|
320
|
-
|
|
321
|
-
# Read trace samples
|
|
322
|
-
trace_samples = np.frombuffer(f.read(n_samples * dtype(0).itemsize), dtype=dtype)
|
|
323
|
-
traces[trace_idx] = trace_samples.astype(np.float64)
|
|
243
|
+
tags = _read_trs_header(f)
|
|
244
|
+
n_traces, n_samples, sample_coding, data_length = _parse_trs_tags(tags, str(path))
|
|
245
|
+
dtype = _get_trs_dtype(sample_coding, str(path))
|
|
246
|
+
traces, plaintexts = _read_trs_traces(f, n_traces, n_samples, data_length, dtype)
|
|
324
247
|
|
|
325
248
|
except OSError as e:
|
|
326
|
-
raise LoaderError(
|
|
327
|
-
"Failed to read TRS file",
|
|
328
|
-
file_path=str(path),
|
|
329
|
-
details=str(e),
|
|
330
|
-
) from e
|
|
249
|
+
raise LoaderError("Failed to read TRS file", file_path=str(path), details=str(e)) from e
|
|
331
250
|
except Exception as e:
|
|
332
251
|
if isinstance(e, (LoaderError, FormatError)):
|
|
333
252
|
raise
|
|
334
|
-
raise LoaderError(
|
|
335
|
-
"Failed to parse TRS file",
|
|
336
|
-
file_path=str(path),
|
|
337
|
-
details=str(e),
|
|
338
|
-
) from e
|
|
253
|
+
raise LoaderError("Failed to parse TRS file", file_path=str(path), details=str(e)) from e
|
|
339
254
|
|
|
340
|
-
|
|
341
|
-
if sample_rate is None:
|
|
342
|
-
sample_rate = 1e6 # Default 1 MS/s
|
|
255
|
+
sample_rate = sample_rate or 1e6
|
|
343
256
|
|
|
344
257
|
return ChipWhispererTraceSet(
|
|
345
258
|
traces=traces,
|
|
346
259
|
plaintexts=plaintexts,
|
|
347
|
-
ciphertexts=
|
|
260
|
+
ciphertexts=None,
|
|
348
261
|
keys=None,
|
|
349
262
|
sample_rate=sample_rate,
|
|
350
263
|
metadata={
|
|
@@ -357,6 +270,109 @@ def load_chipwhisperer_trs(
|
|
|
357
270
|
)
|
|
358
271
|
|
|
359
272
|
|
|
273
|
+
def _read_trs_header(f: Any) -> dict[int, bytes]:
|
|
274
|
+
"""Read TRS header tags in Tag-Length-Value format.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
f: File object to read from.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
Dictionary mapping tag IDs to values.
|
|
281
|
+
|
|
282
|
+
Raises:
|
|
283
|
+
FormatError: If tag length exceeds MAX_TAG_LENGTH (10MB) or
|
|
284
|
+
tag count exceeds MAX_TAG_COUNT (1000).
|
|
285
|
+
"""
|
|
286
|
+
MAX_TAG_LENGTH = 10 * 1024 * 1024 # 10MB per tag
|
|
287
|
+
MAX_TAG_COUNT = 1000 # Maximum number of tags
|
|
288
|
+
|
|
289
|
+
tags: dict[int, bytes] = {}
|
|
290
|
+
while True:
|
|
291
|
+
# Safety check: prevent excessive tag counts
|
|
292
|
+
if len(tags) >= MAX_TAG_COUNT:
|
|
293
|
+
raise FormatError(
|
|
294
|
+
f"TRS header tag count exceeded {MAX_TAG_COUNT} limit. "
|
|
295
|
+
"This may indicate malformed or malicious TRS file.",
|
|
296
|
+
file_path="<unknown>",
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
tag_byte = f.read(1)
|
|
300
|
+
if not tag_byte or tag_byte == b"\x5f":
|
|
301
|
+
break
|
|
302
|
+
|
|
303
|
+
tag = tag_byte[0]
|
|
304
|
+
length = int.from_bytes(f.read(1), byteorder="little")
|
|
305
|
+
|
|
306
|
+
if length == 0xFF:
|
|
307
|
+
length = int.from_bytes(f.read(4), byteorder="little")
|
|
308
|
+
|
|
309
|
+
# Safety check: prevent excessive tag lengths
|
|
310
|
+
if length > MAX_TAG_LENGTH:
|
|
311
|
+
raise FormatError(
|
|
312
|
+
f"TRS tag length {length} bytes exceeded {MAX_TAG_LENGTH // (1024 * 1024)}MB limit. "
|
|
313
|
+
"This may indicate malformed or malicious TRS file.",
|
|
314
|
+
file_path="<unknown>",
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
value = f.read(length)
|
|
318
|
+
tags[tag] = value
|
|
319
|
+
|
|
320
|
+
return tags
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _parse_trs_tags(tags: dict[int, bytes], file_path: str) -> tuple[int, int, int, int]:
|
|
324
|
+
"""Parse critical TRS tags and validate."""
|
|
325
|
+
n_traces = int.from_bytes(tags.get(0x41, b"\x00\x00"), byteorder="little")
|
|
326
|
+
n_samples = int.from_bytes(tags.get(0x42, b"\x00\x00"), byteorder="little")
|
|
327
|
+
sample_coding = tags.get(0x43, b"\x01")[0]
|
|
328
|
+
data_length = int.from_bytes(tags.get(0x44, b"\x00\x00"), byteorder="little")
|
|
329
|
+
|
|
330
|
+
if n_traces == 0 or n_samples == 0:
|
|
331
|
+
raise FormatError("Invalid TRS file: zero traces or samples", file_path=file_path)
|
|
332
|
+
|
|
333
|
+
return n_traces, n_samples, sample_coding, data_length
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _get_trs_dtype(
|
|
337
|
+
sample_coding: int, file_path: str
|
|
338
|
+
) -> type[np.int8] | type[np.int16] | type[np.float32]:
|
|
339
|
+
"""Map TRS sample coding to numpy dtype."""
|
|
340
|
+
dtype_map: dict[int, type[np.int8] | type[np.int16] | type[np.float32]] = {
|
|
341
|
+
1: np.int8,
|
|
342
|
+
2: np.int16,
|
|
343
|
+
4: np.float32,
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if sample_coding not in dtype_map:
|
|
347
|
+
raise FormatError(f"Unsupported sample coding: {sample_coding}", file_path=file_path)
|
|
348
|
+
|
|
349
|
+
result = dtype_map[sample_coding]
|
|
350
|
+
return result
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _read_trs_traces(
|
|
354
|
+
f: Any,
|
|
355
|
+
n_traces: int,
|
|
356
|
+
n_samples: int,
|
|
357
|
+
data_length: int,
|
|
358
|
+
dtype: type[np.int8] | type[np.int16] | type[np.float32],
|
|
359
|
+
) -> tuple[NDArray[np.float64], NDArray[np.uint8] | None]:
|
|
360
|
+
"""Read trace data from TRS file."""
|
|
361
|
+
traces = np.zeros((n_traces, n_samples), dtype=np.float64)
|
|
362
|
+
plaintexts = np.zeros((n_traces, data_length), dtype=np.uint8) if data_length > 0 else None
|
|
363
|
+
|
|
364
|
+
for trace_idx in range(n_traces):
|
|
365
|
+
if data_length > 0:
|
|
366
|
+
trace_data = np.frombuffer(f.read(data_length), dtype=np.uint8)
|
|
367
|
+
if plaintexts is not None:
|
|
368
|
+
plaintexts[trace_idx] = trace_data
|
|
369
|
+
|
|
370
|
+
trace_samples = np.frombuffer(f.read(n_samples * dtype(0).itemsize), dtype=dtype)
|
|
371
|
+
traces[trace_idx] = trace_samples.astype(np.float64)
|
|
372
|
+
|
|
373
|
+
return traces, plaintexts
|
|
374
|
+
|
|
375
|
+
|
|
360
376
|
def to_waveform_trace(
|
|
361
377
|
traceset: ChipWhispererTraceSet,
|
|
362
378
|
trace_index: int = 0,
|