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.
- oscura/__init__.py +169 -167
- oscura/analyzers/__init__.py +3 -0
- oscura/analyzers/classification.py +659 -0
- oscura/analyzers/digital/edges.py +325 -65
- 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/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 +17 -102
- 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/{schemas → core/schemas}/device_mapping.json +2 -8
- oscura/{schemas → core/schemas}/packet_format.json +4 -24
- oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
- 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 -8
- oscura/export/kaitai_struct.py +513 -0
- oscura/export/scapy_layer.py +801 -0
- 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/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 +183 -67
- 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/tss.py +456 -0
- oscura/loaders/vcd.py +215 -117
- oscura/loaders/wav.py +155 -68
- oscura/reporting/__init__.py +9 -0
- 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/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/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 +1 -1
- 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.7.0.dist-info/METADATA +661 -0
- oscura-0.7.0.dist-info/RECORD +591 -0
- 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/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 -291
- 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/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/workflow/__init__.py +0 -13
- oscura-0.5.1.dist-info/METADATA +0 -583
- oscura-0.5.1.dist-info/RECORD +0 -481
- /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/{schemas → core/schemas}/bus_configuration.json +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.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,736 @@
|
|
|
1
|
+
"""Hardware Abstraction Layer (HAL) detection and analysis.
|
|
2
|
+
|
|
3
|
+
- RE-HAL-001: Register Access Pattern Detection
|
|
4
|
+
- RE-HAL-002: Peripheral Driver Identification
|
|
5
|
+
- RE-HAL-003: HAL Framework Recognition
|
|
6
|
+
|
|
7
|
+
This module provides tools for identifying hardware abstraction layer patterns
|
|
8
|
+
in firmware binaries and protocol traffic, detecting register access patterns,
|
|
9
|
+
peripheral drivers, and HAL framework signatures.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
from collections import Counter, defaultdict
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from typing import Any, Literal
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class RegisterAccess:
|
|
24
|
+
"""Register access pattern.
|
|
25
|
+
|
|
26
|
+
Implements RE-HAL-001: Register access pattern representation.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
address: Register address (hex).
|
|
30
|
+
access_type: Type of access (read/write/rmw).
|
|
31
|
+
bit_mask: Bit mask for read-modify-write operations.
|
|
32
|
+
frequency: Number of times this access occurred.
|
|
33
|
+
offset_from_base: Offset from peripheral base address.
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
>>> reg = RegisterAccess(
|
|
37
|
+
... address=0x40021000,
|
|
38
|
+
... access_type="rmw",
|
|
39
|
+
... bit_mask=0x00000001,
|
|
40
|
+
... frequency=5
|
|
41
|
+
... )
|
|
42
|
+
>>> reg.access_type
|
|
43
|
+
'rmw'
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
address: int
|
|
47
|
+
access_type: Literal["read", "write", "rmw"]
|
|
48
|
+
bit_mask: int | None = None
|
|
49
|
+
frequency: int = 1
|
|
50
|
+
offset_from_base: int | None = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class Peripheral:
|
|
55
|
+
"""Peripheral driver information.
|
|
56
|
+
|
|
57
|
+
Implements RE-HAL-002: Peripheral driver representation.
|
|
58
|
+
|
|
59
|
+
Attributes:
|
|
60
|
+
peripheral_type: Type of peripheral (GPIO, UART, SPI, etc.).
|
|
61
|
+
base_address: Base address of peripheral registers.
|
|
62
|
+
registers: Dictionary of register offsets to access patterns.
|
|
63
|
+
access_patterns: List of detected access patterns.
|
|
64
|
+
initialization_sequence: Ordered list of register accesses during init.
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
>>> periph = Peripheral(
|
|
68
|
+
... peripheral_type="UART",
|
|
69
|
+
... base_address=0x40011000,
|
|
70
|
+
... registers={0x00: "Control Register"}
|
|
71
|
+
... )
|
|
72
|
+
>>> periph.peripheral_type
|
|
73
|
+
'UART'
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
peripheral_type: str
|
|
77
|
+
base_address: int
|
|
78
|
+
registers: dict[int, str] = field(default_factory=dict)
|
|
79
|
+
access_patterns: list[RegisterAccess] = field(default_factory=list)
|
|
80
|
+
initialization_sequence: list[int] = field(default_factory=list)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass
|
|
84
|
+
class HALAnalysisResult:
|
|
85
|
+
"""Result of HAL detection analysis.
|
|
86
|
+
|
|
87
|
+
Implements RE-HAL-003: HAL analysis result.
|
|
88
|
+
|
|
89
|
+
Attributes:
|
|
90
|
+
detected_hal: Name of detected HAL framework (or "Unknown").
|
|
91
|
+
peripherals: List of identified peripherals.
|
|
92
|
+
register_map: Complete register address map.
|
|
93
|
+
initialization_sequence: Ordered initialization operations.
|
|
94
|
+
confidence: Detection confidence (0.0-1.0).
|
|
95
|
+
framework_signatures: List of detected framework signatures.
|
|
96
|
+
|
|
97
|
+
Example:
|
|
98
|
+
>>> result = HALAnalysisResult(
|
|
99
|
+
... detected_hal="STM32 HAL",
|
|
100
|
+
... peripherals=[],
|
|
101
|
+
... register_map={},
|
|
102
|
+
... initialization_sequence=[],
|
|
103
|
+
... confidence=0.95
|
|
104
|
+
... )
|
|
105
|
+
>>> result.detected_hal
|
|
106
|
+
'STM32 HAL'
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
detected_hal: str
|
|
110
|
+
peripherals: list[Peripheral]
|
|
111
|
+
register_map: dict[int, str]
|
|
112
|
+
initialization_sequence: list[dict[str, Any]]
|
|
113
|
+
confidence: float = 0.0
|
|
114
|
+
framework_signatures: list[str] = field(default_factory=list)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# Known HAL framework signatures
|
|
118
|
+
HAL_SIGNATURES: dict[str, dict[str, Any]] = {
|
|
119
|
+
"STM32 HAL": {
|
|
120
|
+
"patterns": [
|
|
121
|
+
b"HAL_Init",
|
|
122
|
+
b"HAL_Delay",
|
|
123
|
+
b"HAL_GPIO_",
|
|
124
|
+
b"HAL_UART_",
|
|
125
|
+
b"HAL_SPI_",
|
|
126
|
+
b"HAL_I2C_",
|
|
127
|
+
],
|
|
128
|
+
"base_addresses": {
|
|
129
|
+
0x40000000: "APB1 Peripheral",
|
|
130
|
+
0x40010000: "APB2 Peripheral",
|
|
131
|
+
0x40020000: "AHB1 Peripheral",
|
|
132
|
+
0x50000000: "AHB2 Peripheral",
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
"Nordic SDK": {
|
|
136
|
+
"patterns": [
|
|
137
|
+
b"nrf_",
|
|
138
|
+
b"NRF_",
|
|
139
|
+
b"nrfx_",
|
|
140
|
+
b"NRFX_",
|
|
141
|
+
b"app_uart_",
|
|
142
|
+
b"ble_",
|
|
143
|
+
],
|
|
144
|
+
"base_addresses": {
|
|
145
|
+
0x40000000: "APB Peripheral",
|
|
146
|
+
0x50000000: "AHB Peripheral",
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
"ESP-IDF": {
|
|
150
|
+
"patterns": [
|
|
151
|
+
b"esp_",
|
|
152
|
+
b"ESP_",
|
|
153
|
+
b"gpio_",
|
|
154
|
+
b"uart_",
|
|
155
|
+
b"spi_",
|
|
156
|
+
b"i2c_",
|
|
157
|
+
],
|
|
158
|
+
"base_addresses": {
|
|
159
|
+
0x3FF00000: "DPORT Peripheral",
|
|
160
|
+
0x3FF40000: "UART Peripheral",
|
|
161
|
+
0x3FF44000: "SPI Peripheral",
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
"Arduino": {
|
|
165
|
+
"patterns": [
|
|
166
|
+
b"digitalWrite",
|
|
167
|
+
b"digitalRead",
|
|
168
|
+
b"pinMode",
|
|
169
|
+
b"analogRead",
|
|
170
|
+
b"analogWrite",
|
|
171
|
+
b"Serial.begin",
|
|
172
|
+
],
|
|
173
|
+
"base_addresses": {},
|
|
174
|
+
},
|
|
175
|
+
"CMSIS": {
|
|
176
|
+
"patterns": [
|
|
177
|
+
b"CMSIS",
|
|
178
|
+
b"__NVIC_",
|
|
179
|
+
b"SysTick_",
|
|
180
|
+
b"SCB->",
|
|
181
|
+
b"NVIC->",
|
|
182
|
+
],
|
|
183
|
+
"base_addresses": {
|
|
184
|
+
0xE000E000: "System Control Space",
|
|
185
|
+
0xE000E010: "SysTick",
|
|
186
|
+
0xE000E100: "NVIC",
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
# Common MCU peripheral base addresses (ARM Cortex-M)
|
|
192
|
+
ARM_PERIPHERAL_BASES: dict[int, str] = {
|
|
193
|
+
# STM32F4 family
|
|
194
|
+
0x40000000: "TIM2",
|
|
195
|
+
0x40000400: "TIM3",
|
|
196
|
+
0x40000800: "TIM4",
|
|
197
|
+
0x40000C00: "TIM5",
|
|
198
|
+
0x40001000: "TIM6",
|
|
199
|
+
0x40001400: "TIM7",
|
|
200
|
+
0x40002800: "RTC",
|
|
201
|
+
0x40002C00: "WWDG",
|
|
202
|
+
0x40003000: "IWDG",
|
|
203
|
+
0x40003800: "SPI2",
|
|
204
|
+
0x40003C00: "SPI3",
|
|
205
|
+
0x40004400: "USART2",
|
|
206
|
+
0x40004800: "USART3",
|
|
207
|
+
0x40004C00: "UART4",
|
|
208
|
+
0x40005000: "UART5",
|
|
209
|
+
0x40005400: "I2C1",
|
|
210
|
+
0x40005800: "I2C2",
|
|
211
|
+
0x40005C00: "I2C3",
|
|
212
|
+
0x40007000: "PWR",
|
|
213
|
+
0x40007400: "DAC",
|
|
214
|
+
0x40010000: "TIM1",
|
|
215
|
+
0x40011000: "USART1",
|
|
216
|
+
0x40011400: "USART6",
|
|
217
|
+
0x40012C00: "ADC1",
|
|
218
|
+
0x40013000: "SPI1",
|
|
219
|
+
0x40014000: "SYSCFG",
|
|
220
|
+
0x40020000: "GPIOA",
|
|
221
|
+
0x40020400: "GPIOB",
|
|
222
|
+
0x40020800: "GPIOC",
|
|
223
|
+
0x40020C00: "GPIOD",
|
|
224
|
+
0x40021000: "GPIOE",
|
|
225
|
+
0x40021400: "GPIOF",
|
|
226
|
+
0x40021800: "GPIOG",
|
|
227
|
+
0x40021C00: "GPIOH",
|
|
228
|
+
0x40023000: "CRC",
|
|
229
|
+
0x40023800: "RCC",
|
|
230
|
+
0x40023C00: "FLASH",
|
|
231
|
+
0x40026000: "DMA1",
|
|
232
|
+
0x40026400: "DMA2",
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
# AVR peripheral base addresses
|
|
236
|
+
AVR_PERIPHERAL_BASES: dict[int, str] = {
|
|
237
|
+
0x0020: "PORTA",
|
|
238
|
+
0x0023: "PORTB",
|
|
239
|
+
0x0026: "PORTC",
|
|
240
|
+
0x0029: "PORTD",
|
|
241
|
+
0x00B8: "USART0",
|
|
242
|
+
0x00C0: "USART1",
|
|
243
|
+
0x004C: "SPI",
|
|
244
|
+
0x00B0: "TWI (I2C)",
|
|
245
|
+
0x0080: "ADC",
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
# PIC peripheral base addresses
|
|
249
|
+
PIC_PERIPHERAL_BASES: dict[int, str] = {
|
|
250
|
+
0x0005: "PORTA",
|
|
251
|
+
0x0006: "PORTB",
|
|
252
|
+
0x0007: "PORTC",
|
|
253
|
+
0x0011: "USART",
|
|
254
|
+
0x0015: "SPI",
|
|
255
|
+
0x001E: "ADC",
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class HALDetector:
|
|
260
|
+
"""Hardware Abstraction Layer detector.
|
|
261
|
+
|
|
262
|
+
Identifies HAL patterns, peripheral drivers, and register access
|
|
263
|
+
patterns in firmware binaries and protocol traffic.
|
|
264
|
+
|
|
265
|
+
Attributes:
|
|
266
|
+
register_accesses: Tracked register access patterns.
|
|
267
|
+
peripherals: Detected peripheral drivers.
|
|
268
|
+
hal_framework: Detected HAL framework name.
|
|
269
|
+
|
|
270
|
+
Example:
|
|
271
|
+
>>> detector = HALDetector()
|
|
272
|
+
>>> # Analyze firmware binary
|
|
273
|
+
>>> binary = b"\\x00\\x10\\x02\\x40HAL_Init..."
|
|
274
|
+
>>> result = detector.analyze_firmware(binary)
|
|
275
|
+
>>> result.detected_hal
|
|
276
|
+
'STM32 HAL'
|
|
277
|
+
>>> len(result.peripherals) > 0
|
|
278
|
+
True
|
|
279
|
+
"""
|
|
280
|
+
|
|
281
|
+
def __init__(self) -> None:
|
|
282
|
+
"""Initialize HAL detector."""
|
|
283
|
+
self.register_accesses: list[RegisterAccess] = []
|
|
284
|
+
self.peripherals: list[Peripheral] = []
|
|
285
|
+
self.hal_framework: str = "Unknown"
|
|
286
|
+
self._address_frequency: Counter[int] = Counter()
|
|
287
|
+
|
|
288
|
+
def analyze_firmware(
|
|
289
|
+
self,
|
|
290
|
+
binary_data: bytes,
|
|
291
|
+
*,
|
|
292
|
+
detect_peripherals: bool = True,
|
|
293
|
+
detect_framework: bool = True,
|
|
294
|
+
) -> HALAnalysisResult:
|
|
295
|
+
"""Analyze firmware binary for HAL patterns.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
binary_data: Firmware binary data.
|
|
299
|
+
detect_peripherals: Enable peripheral detection.
|
|
300
|
+
detect_framework: Enable HAL framework detection.
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
HAL analysis result with detected patterns.
|
|
304
|
+
|
|
305
|
+
Raises:
|
|
306
|
+
ValueError: If binary_data is empty.
|
|
307
|
+
|
|
308
|
+
Example:
|
|
309
|
+
>>> detector = HALDetector()
|
|
310
|
+
>>> binary = b"HAL_Init\\x00\\x00\\x10\\x02\\x40"
|
|
311
|
+
>>> result = detector.analyze_firmware(binary)
|
|
312
|
+
>>> result.confidence > 0.0
|
|
313
|
+
True
|
|
314
|
+
"""
|
|
315
|
+
if not binary_data:
|
|
316
|
+
raise ValueError("Binary data cannot be empty")
|
|
317
|
+
|
|
318
|
+
# Reset state
|
|
319
|
+
self.register_accesses = []
|
|
320
|
+
self.peripherals = []
|
|
321
|
+
self.hal_framework = "Unknown"
|
|
322
|
+
self._address_frequency = Counter()
|
|
323
|
+
|
|
324
|
+
# Detect HAL framework
|
|
325
|
+
framework_confidence = 0.0
|
|
326
|
+
if detect_framework:
|
|
327
|
+
self.hal_framework, framework_confidence = self._detect_hal_framework(binary_data)
|
|
328
|
+
|
|
329
|
+
# Extract register access patterns
|
|
330
|
+
self._extract_register_accesses(binary_data)
|
|
331
|
+
|
|
332
|
+
# Detect peripherals
|
|
333
|
+
if detect_peripherals:
|
|
334
|
+
self._detect_peripherals()
|
|
335
|
+
|
|
336
|
+
# Build register map
|
|
337
|
+
register_map = self._build_register_map()
|
|
338
|
+
|
|
339
|
+
# Extract initialization sequence
|
|
340
|
+
init_sequence = self._extract_initialization_sequence()
|
|
341
|
+
|
|
342
|
+
# Calculate overall confidence
|
|
343
|
+
confidence = self._calculate_confidence(framework_confidence)
|
|
344
|
+
|
|
345
|
+
return HALAnalysisResult(
|
|
346
|
+
detected_hal=self.hal_framework,
|
|
347
|
+
peripherals=self.peripherals,
|
|
348
|
+
register_map=register_map,
|
|
349
|
+
initialization_sequence=init_sequence,
|
|
350
|
+
confidence=confidence,
|
|
351
|
+
framework_signatures=self._get_detected_signatures(binary_data),
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
def _detect_hal_framework(self, binary_data: bytes) -> tuple[str, float]:
|
|
355
|
+
"""Detect HAL framework from binary signatures.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
binary_data: Firmware binary data.
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
Tuple of (framework_name, confidence).
|
|
362
|
+
"""
|
|
363
|
+
scores: dict[str, float] = {}
|
|
364
|
+
|
|
365
|
+
for framework, info in HAL_SIGNATURES.items():
|
|
366
|
+
matches = 0
|
|
367
|
+
patterns = info["patterns"]
|
|
368
|
+
|
|
369
|
+
for pattern in patterns:
|
|
370
|
+
if pattern in binary_data:
|
|
371
|
+
matches += 1
|
|
372
|
+
|
|
373
|
+
if len(patterns) > 0:
|
|
374
|
+
score = matches / len(patterns)
|
|
375
|
+
scores[framework] = score
|
|
376
|
+
|
|
377
|
+
if not scores:
|
|
378
|
+
return "Unknown", 0.0
|
|
379
|
+
|
|
380
|
+
best_framework = max(scores, key=scores.get) # type: ignore[arg-type]
|
|
381
|
+
confidence = scores[best_framework]
|
|
382
|
+
|
|
383
|
+
return best_framework, confidence
|
|
384
|
+
|
|
385
|
+
def _extract_register_accesses(self, binary_data: bytes) -> None:
|
|
386
|
+
"""Extract register access patterns from binary.
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
binary_data: Firmware binary data.
|
|
390
|
+
"""
|
|
391
|
+
# Convert to numpy array for easier processing
|
|
392
|
+
data_array = np.frombuffer(binary_data, dtype=np.uint8)
|
|
393
|
+
|
|
394
|
+
# Look for 32-bit addresses (little-endian)
|
|
395
|
+
if len(data_array) < 4:
|
|
396
|
+
return
|
|
397
|
+
|
|
398
|
+
# Scan for potential memory-mapped register addresses
|
|
399
|
+
for i in range(len(data_array) - 3):
|
|
400
|
+
# Extract potential 32-bit address
|
|
401
|
+
addr = int.from_bytes(data_array[i : i + 4].tobytes(), byteorder="little", signed=False)
|
|
402
|
+
|
|
403
|
+
# Check if address looks like a peripheral register
|
|
404
|
+
if self._is_peripheral_address(addr):
|
|
405
|
+
self._address_frequency[addr] += 1
|
|
406
|
+
|
|
407
|
+
# Try to determine access type from surrounding bytes
|
|
408
|
+
access_type = self._infer_access_type(data_array, i)
|
|
409
|
+
|
|
410
|
+
self.register_accesses.append(
|
|
411
|
+
RegisterAccess(address=addr, access_type=access_type, frequency=1)
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
def _is_peripheral_address(self, addr: int) -> bool:
|
|
415
|
+
"""Check if address is likely a peripheral register.
|
|
416
|
+
|
|
417
|
+
Args:
|
|
418
|
+
addr: Memory address.
|
|
419
|
+
|
|
420
|
+
Returns:
|
|
421
|
+
True if address is in peripheral memory range.
|
|
422
|
+
"""
|
|
423
|
+
# ARM Cortex-M peripheral ranges
|
|
424
|
+
if 0x40000000 <= addr < 0x60000000: # Peripheral range
|
|
425
|
+
return True
|
|
426
|
+
if 0xE0000000 <= addr < 0xE0100000: # System control
|
|
427
|
+
return True
|
|
428
|
+
|
|
429
|
+
# AVR peripheral range
|
|
430
|
+
if 0x0020 <= addr < 0x0100:
|
|
431
|
+
return True
|
|
432
|
+
|
|
433
|
+
# PIC peripheral range
|
|
434
|
+
return 0x0000 <= addr < 0x0030
|
|
435
|
+
|
|
436
|
+
def _infer_access_type(
|
|
437
|
+
self, data_array: np.ndarray[tuple[int], np.dtype[np.uint8]], index: int
|
|
438
|
+
) -> Literal["read", "write", "rmw"]:
|
|
439
|
+
"""Infer register access type from context.
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
data_array: Binary data as numpy array.
|
|
443
|
+
index: Index of address in array.
|
|
444
|
+
|
|
445
|
+
Returns:
|
|
446
|
+
Access type (read, write, or rmw).
|
|
447
|
+
"""
|
|
448
|
+
# Simple heuristic: look at nearby opcodes (ARM Thumb)
|
|
449
|
+
if index > 0:
|
|
450
|
+
prev_byte = int(data_array[index - 1])
|
|
451
|
+
|
|
452
|
+
# Common ARM Thumb load/store opcodes
|
|
453
|
+
if prev_byte in (0x68, 0x78, 0x88): # LDR variants
|
|
454
|
+
return "read"
|
|
455
|
+
if prev_byte in (0x60, 0x70, 0x80): # STR variants
|
|
456
|
+
return "write"
|
|
457
|
+
|
|
458
|
+
# Check for bit manipulation patterns (read-modify-write)
|
|
459
|
+
if index + 8 < len(data_array):
|
|
460
|
+
# Look for ORR, AND, BIC patterns
|
|
461
|
+
window = data_array[index : index + 8]
|
|
462
|
+
if any(b in (0x43, 0x40, 0x44) for b in window): # ORR, AND, BIC opcodes
|
|
463
|
+
return "rmw"
|
|
464
|
+
|
|
465
|
+
return "write"
|
|
466
|
+
|
|
467
|
+
def _detect_peripherals(self) -> None:
|
|
468
|
+
"""Detect peripheral drivers from register access patterns."""
|
|
469
|
+
# Group accesses by base address
|
|
470
|
+
base_groups: dict[int, list[RegisterAccess]] = defaultdict(list)
|
|
471
|
+
|
|
472
|
+
for access in self.register_accesses:
|
|
473
|
+
# Try to find matching peripheral base
|
|
474
|
+
base = self._find_peripheral_base(access.address)
|
|
475
|
+
if base is not None:
|
|
476
|
+
access.offset_from_base = access.address - base
|
|
477
|
+
base_groups[base].append(access)
|
|
478
|
+
|
|
479
|
+
# Create peripheral objects
|
|
480
|
+
for base, accesses in base_groups.items():
|
|
481
|
+
periph_type = self._identify_peripheral_type(base, accesses)
|
|
482
|
+
|
|
483
|
+
peripheral = Peripheral(
|
|
484
|
+
peripheral_type=periph_type,
|
|
485
|
+
base_address=base,
|
|
486
|
+
access_patterns=accesses,
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
# Extract register names
|
|
490
|
+
peripheral.registers = self._extract_register_names(base)
|
|
491
|
+
|
|
492
|
+
self.peripherals.append(peripheral)
|
|
493
|
+
|
|
494
|
+
def _find_peripheral_base(self, address: int) -> int | None:
|
|
495
|
+
"""Find peripheral base address for given register address.
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
address: Register address.
|
|
499
|
+
|
|
500
|
+
Returns:
|
|
501
|
+
Base address or None.
|
|
502
|
+
"""
|
|
503
|
+
# Check ARM peripherals
|
|
504
|
+
for base in ARM_PERIPHERAL_BASES:
|
|
505
|
+
if base <= address < base + 0x400: # Typical peripheral size
|
|
506
|
+
return base
|
|
507
|
+
|
|
508
|
+
# Check AVR peripherals
|
|
509
|
+
for base in AVR_PERIPHERAL_BASES:
|
|
510
|
+
if base <= address < base + 0x10:
|
|
511
|
+
return base
|
|
512
|
+
|
|
513
|
+
# Check PIC peripherals
|
|
514
|
+
for base in PIC_PERIPHERAL_BASES:
|
|
515
|
+
if base <= address < base + 0x10:
|
|
516
|
+
return base
|
|
517
|
+
|
|
518
|
+
return None
|
|
519
|
+
|
|
520
|
+
def _identify_peripheral_type(self, base_address: int, accesses: list[RegisterAccess]) -> str:
|
|
521
|
+
"""Identify peripheral type from base address and access patterns.
|
|
522
|
+
|
|
523
|
+
Args:
|
|
524
|
+
base_address: Peripheral base address.
|
|
525
|
+
accesses: List of register accesses.
|
|
526
|
+
|
|
527
|
+
Returns:
|
|
528
|
+
Peripheral type name.
|
|
529
|
+
"""
|
|
530
|
+
# Check known base addresses
|
|
531
|
+
if base_address in ARM_PERIPHERAL_BASES:
|
|
532
|
+
return ARM_PERIPHERAL_BASES[base_address]
|
|
533
|
+
|
|
534
|
+
if base_address in AVR_PERIPHERAL_BASES:
|
|
535
|
+
return AVR_PERIPHERAL_BASES[base_address]
|
|
536
|
+
|
|
537
|
+
if base_address in PIC_PERIPHERAL_BASES:
|
|
538
|
+
return PIC_PERIPHERAL_BASES[base_address]
|
|
539
|
+
|
|
540
|
+
# Heuristic: identify by access patterns
|
|
541
|
+
if len(accesses) > 10 and any(a.access_type == "rmw" for a in accesses):
|
|
542
|
+
return "GPIO"
|
|
543
|
+
|
|
544
|
+
return "Unknown Peripheral"
|
|
545
|
+
|
|
546
|
+
def _extract_register_names(self, base_address: int) -> dict[int, str]:
|
|
547
|
+
"""Extract register names for peripheral.
|
|
548
|
+
|
|
549
|
+
Args:
|
|
550
|
+
base_address: Peripheral base address.
|
|
551
|
+
|
|
552
|
+
Returns:
|
|
553
|
+
Dictionary of offset to register name.
|
|
554
|
+
"""
|
|
555
|
+
registers: dict[int, str] = {}
|
|
556
|
+
|
|
557
|
+
# GPIO registers (common pattern)
|
|
558
|
+
if "GPIO" in self._identify_peripheral_type(base_address, []):
|
|
559
|
+
registers = {
|
|
560
|
+
0x00: "MODER (Mode Register)",
|
|
561
|
+
0x04: "OTYPER (Output Type)",
|
|
562
|
+
0x08: "OSPEEDR (Output Speed)",
|
|
563
|
+
0x0C: "PUPDR (Pull-up/Pull-down)",
|
|
564
|
+
0x10: "IDR (Input Data)",
|
|
565
|
+
0x14: "ODR (Output Data)",
|
|
566
|
+
0x18: "BSRR (Bit Set/Reset)",
|
|
567
|
+
}
|
|
568
|
+
# UART registers
|
|
569
|
+
elif "UART" in self._identify_peripheral_type(base_address, []):
|
|
570
|
+
registers = {
|
|
571
|
+
0x00: "CR1 (Control 1)",
|
|
572
|
+
0x04: "CR2 (Control 2)",
|
|
573
|
+
0x08: "CR3 (Control 3)",
|
|
574
|
+
0x0C: "BRR (Baud Rate)",
|
|
575
|
+
0x1C: "RDR (Receive Data)",
|
|
576
|
+
0x28: "TDR (Transmit Data)",
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
return registers
|
|
580
|
+
|
|
581
|
+
def _build_register_map(self) -> dict[int, str]:
|
|
582
|
+
"""Build complete register address map.
|
|
583
|
+
|
|
584
|
+
Returns:
|
|
585
|
+
Dictionary of address to description.
|
|
586
|
+
"""
|
|
587
|
+
register_map: dict[int, str] = {}
|
|
588
|
+
|
|
589
|
+
for peripheral in self.peripherals:
|
|
590
|
+
for offset, name in peripheral.registers.items():
|
|
591
|
+
addr = peripheral.base_address + offset
|
|
592
|
+
register_map[addr] = f"{peripheral.peripheral_type}.{name}"
|
|
593
|
+
|
|
594
|
+
return register_map
|
|
595
|
+
|
|
596
|
+
def _extract_initialization_sequence(self) -> list[dict[str, Any]]:
|
|
597
|
+
"""Extract initialization sequence from register accesses.
|
|
598
|
+
|
|
599
|
+
Returns:
|
|
600
|
+
List of initialization operations.
|
|
601
|
+
"""
|
|
602
|
+
init_ops: list[dict[str, Any]] = []
|
|
603
|
+
|
|
604
|
+
# Group accesses by peripheral
|
|
605
|
+
for peripheral in self.peripherals:
|
|
606
|
+
# Clock configuration often comes first
|
|
607
|
+
if "RCC" in peripheral.peripheral_type or "Clock" in peripheral.peripheral_type:
|
|
608
|
+
init_ops.append(
|
|
609
|
+
{
|
|
610
|
+
"step": "clock_config",
|
|
611
|
+
"peripheral": peripheral.peripheral_type,
|
|
612
|
+
"base_address": hex(peripheral.base_address),
|
|
613
|
+
}
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
# GPIO configuration
|
|
617
|
+
elif "GPIO" in peripheral.peripheral_type:
|
|
618
|
+
init_ops.append(
|
|
619
|
+
{
|
|
620
|
+
"step": "gpio_init",
|
|
621
|
+
"peripheral": peripheral.peripheral_type,
|
|
622
|
+
"base_address": hex(peripheral.base_address),
|
|
623
|
+
"registers": list(peripheral.registers.keys()),
|
|
624
|
+
}
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
# Peripheral initialization
|
|
628
|
+
elif peripheral.peripheral_type in ("UART", "SPI", "I2C", "ADC"):
|
|
629
|
+
init_ops.append(
|
|
630
|
+
{
|
|
631
|
+
"step": "peripheral_init",
|
|
632
|
+
"peripheral": peripheral.peripheral_type,
|
|
633
|
+
"base_address": hex(peripheral.base_address),
|
|
634
|
+
}
|
|
635
|
+
)
|
|
636
|
+
|
|
637
|
+
return init_ops
|
|
638
|
+
|
|
639
|
+
def _calculate_confidence(self, framework_confidence: float) -> float:
|
|
640
|
+
"""Calculate overall detection confidence.
|
|
641
|
+
|
|
642
|
+
Args:
|
|
643
|
+
framework_confidence: HAL framework detection confidence.
|
|
644
|
+
|
|
645
|
+
Returns:
|
|
646
|
+
Overall confidence score (0.0-1.0).
|
|
647
|
+
"""
|
|
648
|
+
# Weight different factors
|
|
649
|
+
weights = {
|
|
650
|
+
"framework": 0.4,
|
|
651
|
+
"peripherals": 0.3,
|
|
652
|
+
"registers": 0.3,
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
# Framework confidence
|
|
656
|
+
conf = framework_confidence * weights["framework"]
|
|
657
|
+
|
|
658
|
+
# Peripheral detection confidence
|
|
659
|
+
if len(self.peripherals) > 0:
|
|
660
|
+
conf += min(len(self.peripherals) / 5.0, 1.0) * weights["peripherals"]
|
|
661
|
+
|
|
662
|
+
# Register access confidence
|
|
663
|
+
if len(self.register_accesses) > 0:
|
|
664
|
+
conf += min(len(self.register_accesses) / 20.0, 1.0) * weights["registers"]
|
|
665
|
+
|
|
666
|
+
return min(conf, 1.0)
|
|
667
|
+
|
|
668
|
+
def _get_detected_signatures(self, binary_data: bytes) -> list[str]:
|
|
669
|
+
"""Get list of detected HAL framework signatures.
|
|
670
|
+
|
|
671
|
+
Args:
|
|
672
|
+
binary_data: Firmware binary data.
|
|
673
|
+
|
|
674
|
+
Returns:
|
|
675
|
+
List of detected signature strings.
|
|
676
|
+
"""
|
|
677
|
+
signatures: list[str] = []
|
|
678
|
+
|
|
679
|
+
if self.hal_framework in HAL_SIGNATURES:
|
|
680
|
+
patterns = HAL_SIGNATURES[self.hal_framework]["patterns"]
|
|
681
|
+
|
|
682
|
+
for pattern in patterns:
|
|
683
|
+
if pattern in binary_data:
|
|
684
|
+
signatures.append(pattern.decode("utf-8", errors="ignore"))
|
|
685
|
+
|
|
686
|
+
return signatures
|
|
687
|
+
|
|
688
|
+
def export_to_json(self, result: HALAnalysisResult, *, indent: int = 2) -> str:
|
|
689
|
+
"""Export HAL analysis result to JSON.
|
|
690
|
+
|
|
691
|
+
Args:
|
|
692
|
+
result: HAL analysis result.
|
|
693
|
+
indent: JSON indentation level.
|
|
694
|
+
|
|
695
|
+
Returns:
|
|
696
|
+
JSON string representation.
|
|
697
|
+
|
|
698
|
+
Example:
|
|
699
|
+
>>> detector = HALDetector()
|
|
700
|
+
>>> result = HALAnalysisResult(
|
|
701
|
+
... detected_hal="STM32 HAL",
|
|
702
|
+
... peripherals=[],
|
|
703
|
+
... register_map={},
|
|
704
|
+
... initialization_sequence=[],
|
|
705
|
+
... confidence=0.8
|
|
706
|
+
... )
|
|
707
|
+
>>> json_str = detector.export_to_json(result)
|
|
708
|
+
>>> "STM32 HAL" in json_str
|
|
709
|
+
True
|
|
710
|
+
"""
|
|
711
|
+
data = {
|
|
712
|
+
"hal_framework": result.detected_hal,
|
|
713
|
+
"confidence": result.confidence,
|
|
714
|
+
"framework_signatures": result.framework_signatures,
|
|
715
|
+
"peripherals": [
|
|
716
|
+
{
|
|
717
|
+
"type": p.peripheral_type,
|
|
718
|
+
"base_address": hex(p.base_address),
|
|
719
|
+
"registers": {hex(offset): name for offset, name in p.registers.items()},
|
|
720
|
+
"access_count": len(p.access_patterns),
|
|
721
|
+
}
|
|
722
|
+
for p in result.peripherals
|
|
723
|
+
],
|
|
724
|
+
"register_map": {hex(addr): desc for addr, desc in result.register_map.items()},
|
|
725
|
+
"initialization_sequence": result.initialization_sequence,
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
return json.dumps(data, indent=indent)
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
__all__ = [
|
|
732
|
+
"HALAnalysisResult",
|
|
733
|
+
"HALDetector",
|
|
734
|
+
"Peripheral",
|
|
735
|
+
"RegisterAccess",
|
|
736
|
+
]
|