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
|
@@ -91,97 +91,292 @@ def detect_checksum_fields(
|
|
|
91
91
|
if not messages:
|
|
92
92
|
return []
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
byte_messages = []
|
|
96
|
-
for msg in messages:
|
|
97
|
-
if isinstance(msg, np.ndarray):
|
|
98
|
-
byte_messages.append(msg.tobytes() if msg.dtype == np.uint8 else bytes(msg.flatten()))
|
|
99
|
-
else:
|
|
100
|
-
byte_messages.append(bytes(msg))
|
|
101
|
-
|
|
102
|
-
# Find minimum message length
|
|
94
|
+
byte_messages = _convert_messages_to_bytes(messages)
|
|
103
95
|
min_len = min(len(msg) for msg in byte_messages)
|
|
104
96
|
|
|
105
97
|
if min_len < 2:
|
|
106
98
|
return []
|
|
107
99
|
|
|
108
|
-
# Determine candidate positions
|
|
109
100
|
if candidate_offsets is None:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
101
|
+
candidate_offsets = _generate_default_candidate_offsets(min_len)
|
|
102
|
+
|
|
103
|
+
candidates = _test_candidate_offsets(byte_messages, candidate_offsets, min_len)
|
|
104
|
+
candidates.sort(key=lambda c: c.correlation, reverse=True)
|
|
105
|
+
|
|
106
|
+
return candidates
|
|
107
|
+
|
|
115
108
|
|
|
109
|
+
def _generate_default_candidate_offsets(min_len: int) -> list[int]:
|
|
110
|
+
"""Generate default candidate positions in header and trailer."""
|
|
111
|
+
header_positions = list(range(min(16, min_len - 1)))
|
|
112
|
+
trailer_start = max(0, min_len - 16)
|
|
113
|
+
trailer_positions = list(range(trailer_start, min_len - 1))
|
|
114
|
+
return list(set(header_positions + trailer_positions))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _test_candidate_offsets(
|
|
118
|
+
byte_messages: list[bytes], candidate_offsets: list[int], min_len: int
|
|
119
|
+
) -> list[ChecksumCandidate]:
|
|
120
|
+
"""Test each candidate offset for checksum correlation."""
|
|
116
121
|
candidates = []
|
|
117
122
|
|
|
118
|
-
# Test each candidate offset for different field sizes
|
|
119
123
|
for offset in candidate_offsets:
|
|
120
124
|
for size in [1, 2, 4]:
|
|
121
125
|
if offset + size > min_len:
|
|
122
126
|
continue
|
|
123
127
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
128
|
+
candidate = _analyze_field_correlation(byte_messages, offset, size, min_len)
|
|
129
|
+
if candidate is not None:
|
|
130
|
+
candidates.append(candidate)
|
|
127
131
|
|
|
128
|
-
|
|
129
|
-
if len(msg) < offset + size:
|
|
130
|
-
continue
|
|
132
|
+
return candidates
|
|
131
133
|
|
|
132
|
-
# Extract field value
|
|
133
|
-
field_bytes = msg[offset : offset + size]
|
|
134
|
-
field_value = int.from_bytes(field_bytes, byteorder="big")
|
|
135
|
-
field_values.append(field_value)
|
|
136
134
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
135
|
+
def _analyze_field_correlation(
|
|
136
|
+
byte_messages: list[bytes], offset: int, size: int, min_len: int
|
|
137
|
+
) -> ChecksumCandidate | None:
|
|
138
|
+
"""Analyze correlation between field and message content."""
|
|
139
|
+
field_values = []
|
|
140
|
+
content_hashes = []
|
|
141
141
|
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
for msg in byte_messages:
|
|
143
|
+
if len(msg) < offset + size:
|
|
144
|
+
continue
|
|
144
145
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
unique_fields = len(set(field_values))
|
|
146
|
+
field_bytes = msg[offset : offset + size]
|
|
147
|
+
field_value = int.from_bytes(field_bytes, byteorder="big")
|
|
148
|
+
field_values.append(field_value)
|
|
149
149
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
150
|
+
content = msg[:offset] + msg[offset + size :]
|
|
151
|
+
content_hash = hash(content)
|
|
152
|
+
content_hashes.append(content_hash)
|
|
153
|
+
|
|
154
|
+
if len(field_values) < 2:
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
correlation = _calculate_field_correlation(content_hashes, field_values)
|
|
158
|
+
|
|
159
|
+
if correlation < 0.3:
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
position: Literal["header", "trailer"] = "header" if offset < min_len // 2 else "trailer"
|
|
163
|
+
likely_scope = (offset + size, min_len) if position == "header" else (0, offset)
|
|
164
|
+
|
|
165
|
+
return ChecksumCandidate(
|
|
166
|
+
offset=offset,
|
|
167
|
+
size=size,
|
|
168
|
+
position=position,
|
|
169
|
+
correlation=correlation,
|
|
170
|
+
likely_scope=likely_scope,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _calculate_field_correlation(content_hashes: list[int], field_values: list[int]) -> float:
|
|
175
|
+
"""Calculate correlation between content and field variability."""
|
|
176
|
+
unique_content = len(set(content_hashes))
|
|
177
|
+
unique_fields = len(set(field_values))
|
|
178
|
+
|
|
179
|
+
if unique_content > 1:
|
|
180
|
+
return min(1.0, unique_fields / unique_content)
|
|
181
|
+
|
|
182
|
+
return 0.0
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _convert_messages_to_bytes(messages: list[DataType]) -> list[bytes]:
|
|
186
|
+
"""Convert messages to bytes format.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
messages: List of messages in various formats.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
List of messages as bytes objects.
|
|
193
|
+
"""
|
|
194
|
+
byte_messages = []
|
|
195
|
+
for msg in messages:
|
|
196
|
+
if isinstance(msg, np.ndarray):
|
|
197
|
+
byte_messages.append(msg.tobytes() if msg.dtype == np.uint8 else bytes(msg.flatten()))
|
|
198
|
+
else:
|
|
199
|
+
byte_messages.append(bytes(msg))
|
|
200
|
+
return byte_messages
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _get_algorithms_for_size(size: int) -> list[str]:
|
|
204
|
+
"""Get list of checksum algorithms to test for given field size.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
size: Field size in bytes (1, 2, or 4).
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
List of algorithm names to test.
|
|
211
|
+
"""
|
|
212
|
+
if size == 1:
|
|
213
|
+
return ["xor", "sum8"]
|
|
214
|
+
elif size == 2:
|
|
215
|
+
return ["sum16_big", "sum16_little", "crc16_ccitt", "crc16_ibm", "crc16", "checksum"]
|
|
216
|
+
elif size == 4:
|
|
217
|
+
return ["crc32"]
|
|
218
|
+
return []
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _normalize_algorithm_name(algo: str) -> str:
|
|
222
|
+
"""Normalize algorithm name to actual function name.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
algo: Original algorithm name.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Normalized algorithm name.
|
|
229
|
+
"""
|
|
230
|
+
if algo == "crc16":
|
|
231
|
+
return "crc16_ccitt"
|
|
232
|
+
elif algo == "checksum":
|
|
233
|
+
return "sum16_big"
|
|
234
|
+
return algo
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _get_init_values_for_algorithm(algo: str) -> list[int | None]:
|
|
238
|
+
"""Get list of initialization values to test for algorithm.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
algo: Algorithm name.
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
List of init values to test (None means use default).
|
|
245
|
+
"""
|
|
246
|
+
if algo in ["crc16_ccitt", "crc16_ibm"]:
|
|
247
|
+
return [0x0000, 0xFFFF]
|
|
248
|
+
return [None]
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _extract_checksummed_data(
|
|
252
|
+
msg: bytes, scope_start: int, scope_end: int, field_offset: int, field_size: int
|
|
253
|
+
) -> bytes:
|
|
254
|
+
"""Extract data region for checksum calculation.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
msg: Full message bytes.
|
|
258
|
+
scope_start: Start offset of checksummed region.
|
|
259
|
+
scope_end: End offset of checksummed region.
|
|
260
|
+
field_offset: Offset of checksum field.
|
|
261
|
+
field_size: Size of checksum field.
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
Data bytes to be checksummed (excluding checksum field if in range).
|
|
265
|
+
"""
|
|
266
|
+
if scope_start < field_offset < scope_end:
|
|
267
|
+
# Exclude checksum field from data
|
|
268
|
+
return msg[scope_start:field_offset] + msg[field_offset + field_size : scope_end]
|
|
269
|
+
else:
|
|
270
|
+
return msg[scope_start:scope_end]
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _test_checksum_match(
|
|
274
|
+
byte_messages: list[bytes],
|
|
275
|
+
field_offset: int,
|
|
276
|
+
field_size: int,
|
|
277
|
+
algo: str,
|
|
278
|
+
init_val: int | None,
|
|
279
|
+
scope_start: int,
|
|
280
|
+
scope_end: int,
|
|
281
|
+
) -> int:
|
|
282
|
+
"""Test if checksum algorithm matches messages in given scope.
|
|
155
283
|
|
|
156
|
-
|
|
157
|
-
|
|
284
|
+
Args:
|
|
285
|
+
byte_messages: List of message bytes.
|
|
286
|
+
field_offset: Offset of checksum field.
|
|
287
|
+
field_size: Size of checksum field in bytes.
|
|
288
|
+
algo: Algorithm name.
|
|
289
|
+
init_val: Initialization value for CRC algorithms.
|
|
290
|
+
scope_start: Start of checksummed region.
|
|
291
|
+
scope_end: End of checksummed region.
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
Number of messages that match the algorithm.
|
|
295
|
+
"""
|
|
296
|
+
matches = 0
|
|
297
|
+
|
|
298
|
+
for msg in byte_messages:
|
|
299
|
+
if len(msg) < scope_end:
|
|
300
|
+
continue
|
|
301
|
+
|
|
302
|
+
# Try both big and little endian for field extraction
|
|
303
|
+
endian_val: Literal["big", "little"]
|
|
304
|
+
for endian_val in ("big", "little"):
|
|
305
|
+
expected = int.from_bytes(
|
|
306
|
+
msg[field_offset : field_offset + field_size], byteorder=endian_val
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
data = _extract_checksummed_data(msg, scope_start, scope_end, field_offset, field_size)
|
|
310
|
+
|
|
311
|
+
# Compute checksum
|
|
312
|
+
try:
|
|
313
|
+
if init_val is not None:
|
|
314
|
+
computed = compute_checksum(data, algo, init=init_val)
|
|
315
|
+
else:
|
|
316
|
+
computed = compute_checksum(data, algo)
|
|
317
|
+
|
|
318
|
+
if computed == expected:
|
|
319
|
+
matches += 1
|
|
320
|
+
break # Found match with this endian
|
|
321
|
+
except Exception:
|
|
322
|
+
pass
|
|
323
|
+
|
|
324
|
+
return matches
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _test_algorithm_variant(
|
|
328
|
+
byte_messages: list[bytes],
|
|
329
|
+
field_offset: int,
|
|
330
|
+
size: int,
|
|
331
|
+
algo: str,
|
|
332
|
+
init_val: int | None,
|
|
333
|
+
) -> ChecksumMatch | None:
|
|
334
|
+
"""Test an algorithm variant across different scopes.
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
byte_messages: List of message bytes.
|
|
338
|
+
field_offset: Offset of checksum field.
|
|
339
|
+
size: Field size in bytes.
|
|
340
|
+
algo: Original algorithm name.
|
|
341
|
+
init_val: Initialization value for CRC.
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
ChecksumMatch if match rate >= 80%, None otherwise.
|
|
345
|
+
"""
|
|
346
|
+
actual_algo = _normalize_algorithm_name(algo)
|
|
347
|
+
best_match = None
|
|
348
|
+
best_rate = 0.0
|
|
349
|
+
|
|
350
|
+
# Try different scopes
|
|
351
|
+
for scope_start in [0, field_offset + size]:
|
|
352
|
+
for scope_end in [field_offset, len(byte_messages[0])]:
|
|
353
|
+
if scope_end <= scope_start:
|
|
158
354
|
continue
|
|
159
355
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
"header" if offset < min_len // 2 else "trailer"
|
|
356
|
+
matches = _test_checksum_match(
|
|
357
|
+
byte_messages, field_offset, size, actual_algo, init_val, scope_start, scope_end
|
|
163
358
|
)
|
|
164
359
|
|
|
165
|
-
|
|
166
|
-
if
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
360
|
+
total = sum(1 for msg in byte_messages if len(msg) >= scope_end)
|
|
361
|
+
if total == 0:
|
|
362
|
+
continue
|
|
363
|
+
|
|
364
|
+
match_rate = matches / total
|
|
170
365
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
366
|
+
# Consider it a match if >= 80% of messages match
|
|
367
|
+
if match_rate >= 0.8 and match_rate > best_rate:
|
|
368
|
+
best_rate = match_rate
|
|
369
|
+
best_match = ChecksumMatch(
|
|
370
|
+
algorithm=algo,
|
|
371
|
+
offset=field_offset,
|
|
174
372
|
size=size,
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
373
|
+
scope_start=scope_start,
|
|
374
|
+
scope_end=scope_end,
|
|
375
|
+
match_rate=match_rate,
|
|
376
|
+
init_value=init_val,
|
|
178
377
|
)
|
|
179
|
-
)
|
|
180
378
|
|
|
181
|
-
|
|
182
|
-
candidates.sort(key=lambda c: c.correlation, reverse=True)
|
|
183
|
-
|
|
184
|
-
return candidates
|
|
379
|
+
return best_match
|
|
185
380
|
|
|
186
381
|
|
|
187
382
|
def identify_checksum_algorithm(
|
|
@@ -211,19 +406,10 @@ def identify_checksum_algorithm(
|
|
|
211
406
|
if not messages:
|
|
212
407
|
return None
|
|
213
408
|
|
|
214
|
-
|
|
215
|
-
byte_messages = []
|
|
216
|
-
for msg in messages:
|
|
217
|
-
if isinstance(msg, np.ndarray):
|
|
218
|
-
byte_messages.append(msg.tobytes() if msg.dtype == np.uint8 else bytes(msg.flatten()))
|
|
219
|
-
else:
|
|
220
|
-
byte_messages.append(bytes(msg))
|
|
409
|
+
byte_messages = _convert_messages_to_bytes(messages)
|
|
221
410
|
|
|
222
|
-
# Determine field
|
|
223
|
-
if field_size is None
|
|
224
|
-
field_sizes = [1, 2, 4]
|
|
225
|
-
else:
|
|
226
|
-
field_sizes = [field_size]
|
|
411
|
+
# Determine field sizes to test
|
|
412
|
+
field_sizes = [1, 2, 4] if field_size is None else [field_size]
|
|
227
413
|
|
|
228
414
|
best_match = None
|
|
229
415
|
best_rate = 0.0
|
|
@@ -233,103 +419,21 @@ def identify_checksum_algorithm(
|
|
|
233
419
|
if any(len(msg) < field_offset + size for msg in byte_messages):
|
|
234
420
|
continue
|
|
235
421
|
|
|
236
|
-
|
|
237
|
-
if
|
|
238
|
-
algorithms = ["xor", "sum8"]
|
|
239
|
-
elif size == 2:
|
|
240
|
-
# Include both big and little endian CRC variants
|
|
241
|
-
algorithms = [
|
|
242
|
-
"sum16_big",
|
|
243
|
-
"sum16_little",
|
|
244
|
-
"crc16_ccitt",
|
|
245
|
-
"crc16_ibm",
|
|
246
|
-
"crc16",
|
|
247
|
-
"checksum",
|
|
248
|
-
]
|
|
249
|
-
elif size == 4:
|
|
250
|
-
algorithms = ["crc32"]
|
|
251
|
-
else:
|
|
422
|
+
algorithms = _get_algorithms_for_size(size)
|
|
423
|
+
if not algorithms:
|
|
252
424
|
continue
|
|
253
425
|
|
|
254
426
|
# Test each algorithm
|
|
255
427
|
for algo in algorithms:
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
if algo == "crc16":
|
|
259
|
-
actual_algo = "crc16_ccitt"
|
|
260
|
-
elif algo == "checksum":
|
|
261
|
-
actual_algo = "sum16_big"
|
|
262
|
-
|
|
263
|
-
# For CRC algorithms, try different init values
|
|
264
|
-
init_values: list[int | None] = [None]
|
|
265
|
-
if actual_algo in ["crc16_ccitt", "crc16_ibm"]:
|
|
266
|
-
init_values = [0x0000, 0xFFFF]
|
|
428
|
+
actual_algo = _normalize_algorithm_name(algo)
|
|
429
|
+
init_values = _get_init_values_for_algorithm(actual_algo)
|
|
267
430
|
|
|
268
431
|
for init_val in init_values:
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
# Test algorithm on all messages
|
|
276
|
-
matches = 0
|
|
277
|
-
total = 0
|
|
278
|
-
|
|
279
|
-
for msg in byte_messages:
|
|
280
|
-
if len(msg) < scope_end:
|
|
281
|
-
continue
|
|
282
|
-
|
|
283
|
-
# Try both big and little endian for field extraction
|
|
284
|
-
endian_val: Literal["big", "little"]
|
|
285
|
-
for endian_val in ("big", "little"): # type: ignore[assignment]
|
|
286
|
-
expected = int.from_bytes(
|
|
287
|
-
msg[field_offset : field_offset + size], byteorder=endian_val
|
|
288
|
-
)
|
|
289
|
-
|
|
290
|
-
# Extract data to checksum
|
|
291
|
-
if scope_start < field_offset < scope_end:
|
|
292
|
-
# Exclude checksum field from data
|
|
293
|
-
data = (
|
|
294
|
-
msg[scope_start:field_offset]
|
|
295
|
-
+ msg[field_offset + size : scope_end]
|
|
296
|
-
)
|
|
297
|
-
else:
|
|
298
|
-
data = msg[scope_start:scope_end]
|
|
299
|
-
|
|
300
|
-
# Compute checksum
|
|
301
|
-
try:
|
|
302
|
-
if init_val is not None:
|
|
303
|
-
computed = compute_checksum(
|
|
304
|
-
data, actual_algo, init=init_val
|
|
305
|
-
)
|
|
306
|
-
else:
|
|
307
|
-
computed = compute_checksum(data, actual_algo)
|
|
308
|
-
if computed == expected:
|
|
309
|
-
matches += 1
|
|
310
|
-
break # Found match with this endian
|
|
311
|
-
except Exception:
|
|
312
|
-
pass
|
|
313
|
-
|
|
314
|
-
total += 1
|
|
315
|
-
|
|
316
|
-
if total == 0:
|
|
317
|
-
continue
|
|
318
|
-
|
|
319
|
-
match_rate = matches / total
|
|
320
|
-
|
|
321
|
-
# Consider it a match if >= 80% of messages match
|
|
322
|
-
if match_rate >= 0.8 and match_rate > best_rate:
|
|
323
|
-
best_rate = match_rate
|
|
324
|
-
best_match = ChecksumMatch(
|
|
325
|
-
algorithm=algo,
|
|
326
|
-
offset=field_offset,
|
|
327
|
-
size=size,
|
|
328
|
-
scope_start=scope_start,
|
|
329
|
-
scope_end=scope_end,
|
|
330
|
-
match_rate=match_rate,
|
|
331
|
-
init_value=init_val,
|
|
332
|
-
)
|
|
432
|
+
match = _test_algorithm_variant(byte_messages, field_offset, size, algo, init_val)
|
|
433
|
+
|
|
434
|
+
if match and match.match_rate > best_rate:
|
|
435
|
+
best_rate = match.match_rate
|
|
436
|
+
best_match = match
|
|
333
437
|
|
|
334
438
|
return best_match
|
|
335
439
|
|
|
@@ -344,14 +448,13 @@ def verify_checksums(
|
|
|
344
448
|
) -> tuple[int, int]:
|
|
345
449
|
"""Verify checksums using identified algorithm.
|
|
346
450
|
|
|
347
|
-
: Checksum and CRC Field Detection
|
|
348
|
-
|
|
349
451
|
Validates checksums across multiple messages using the specified algorithm.
|
|
452
|
+
Tries both big-endian and little-endian byte orders.
|
|
350
453
|
|
|
351
454
|
Args:
|
|
352
455
|
messages: List of messages to verify
|
|
353
|
-
algorithm: Checksum algorithm name
|
|
354
|
-
field_offset: Offset of checksum field
|
|
456
|
+
algorithm: Checksum algorithm name (e.g., 'xor', 'sum8', 'crc16', 'crc32')
|
|
457
|
+
field_offset: Offset of checksum field in message
|
|
355
458
|
scope_start: Start of checksummed data (default: 0)
|
|
356
459
|
scope_end: End of checksummed data (None = message end)
|
|
357
460
|
init_value: Initial value for CRC algorithms (None = use default)
|
|
@@ -368,66 +471,122 @@ def verify_checksums(
|
|
|
368
471
|
if not messages:
|
|
369
472
|
return (0, 0)
|
|
370
473
|
|
|
474
|
+
field_size = _get_checksum_field_size(algorithm)
|
|
371
475
|
passed = 0
|
|
372
476
|
failed = 0
|
|
373
477
|
|
|
374
|
-
# Determine field size from algorithm
|
|
375
|
-
if algorithm in ["xor", "sum8"]:
|
|
376
|
-
field_size = 1
|
|
377
|
-
elif algorithm.startswith("sum16") or algorithm.startswith("crc16"):
|
|
378
|
-
field_size = 2
|
|
379
|
-
elif algorithm == "crc32":
|
|
380
|
-
field_size = 4
|
|
381
|
-
else:
|
|
382
|
-
# Try to infer from first message
|
|
383
|
-
field_size = 1
|
|
384
|
-
|
|
385
478
|
for msg in messages:
|
|
386
|
-
|
|
387
|
-
|
|
479
|
+
msg_bytes = _normalize_message_to_bytes(msg)
|
|
480
|
+
if _verify_single_message(
|
|
481
|
+
msg_bytes, algorithm, field_offset, field_size, scope_start, scope_end, init_value
|
|
482
|
+
):
|
|
483
|
+
passed += 1
|
|
388
484
|
else:
|
|
389
|
-
|
|
485
|
+
failed += 1
|
|
390
486
|
|
|
391
|
-
|
|
487
|
+
return (passed, failed)
|
|
392
488
|
|
|
393
|
-
if len(msg) < field_offset + field_size or len(msg) < msg_scope_end:
|
|
394
|
-
failed += 1
|
|
395
|
-
continue
|
|
396
489
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
endian_val2: Literal["big", "little"]
|
|
400
|
-
for endian_val2 in ("big", "little"): # type: ignore[assignment]
|
|
401
|
-
expected = int.from_bytes(
|
|
402
|
-
msg[field_offset : field_offset + field_size], byteorder=endian_val2
|
|
403
|
-
)
|
|
490
|
+
def _get_checksum_field_size(algorithm: str) -> int:
|
|
491
|
+
"""Determine field size in bytes from algorithm name.
|
|
404
492
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
data = (
|
|
408
|
-
msg[scope_start:field_offset] + msg[field_offset + field_size : msg_scope_end]
|
|
409
|
-
)
|
|
410
|
-
else:
|
|
411
|
-
data = msg[scope_start:msg_scope_end]
|
|
493
|
+
Args:
|
|
494
|
+
algorithm: Checksum algorithm name.
|
|
412
495
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
pass
|
|
496
|
+
Returns:
|
|
497
|
+
Field size in bytes (1, 2, or 4).
|
|
498
|
+
"""
|
|
499
|
+
if algorithm in ["xor", "sum8"]:
|
|
500
|
+
return 1
|
|
501
|
+
if algorithm.startswith("sum16") or algorithm.startswith("crc16"):
|
|
502
|
+
return 2
|
|
503
|
+
if algorithm == "crc32":
|
|
504
|
+
return 4
|
|
505
|
+
return 1 # Default to 1 byte
|
|
424
506
|
|
|
425
|
-
if matched:
|
|
426
|
-
passed += 1
|
|
427
|
-
else:
|
|
428
|
-
failed += 1
|
|
429
507
|
|
|
430
|
-
|
|
508
|
+
def _normalize_message_to_bytes(msg: DataType) -> bytes:
|
|
509
|
+
"""Normalize message to bytes.
|
|
510
|
+
|
|
511
|
+
Args:
|
|
512
|
+
msg: Message as bytes, numpy array, or byte-like object.
|
|
513
|
+
|
|
514
|
+
Returns:
|
|
515
|
+
Message as bytes.
|
|
516
|
+
"""
|
|
517
|
+
if isinstance(msg, np.ndarray):
|
|
518
|
+
return msg.tobytes() if msg.dtype == np.uint8 else bytes(msg.flatten())
|
|
519
|
+
return bytes(msg)
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def _verify_single_message(
|
|
523
|
+
msg: bytes,
|
|
524
|
+
algorithm: str,
|
|
525
|
+
field_offset: int,
|
|
526
|
+
field_size: int,
|
|
527
|
+
scope_start: int,
|
|
528
|
+
scope_end: int | None,
|
|
529
|
+
init_value: int | None,
|
|
530
|
+
) -> bool:
|
|
531
|
+
"""Verify checksum for a single message.
|
|
532
|
+
|
|
533
|
+
Args:
|
|
534
|
+
msg: Message bytes.
|
|
535
|
+
algorithm: Checksum algorithm name.
|
|
536
|
+
field_offset: Offset of checksum field.
|
|
537
|
+
field_size: Size of checksum field in bytes.
|
|
538
|
+
scope_start: Start of checksummed data.
|
|
539
|
+
scope_end: End of checksummed data (None = message end).
|
|
540
|
+
init_value: Initial value for CRC algorithms.
|
|
541
|
+
|
|
542
|
+
Returns:
|
|
543
|
+
True if checksum matches, False otherwise.
|
|
544
|
+
"""
|
|
545
|
+
msg_scope_end = scope_end if scope_end is not None else len(msg)
|
|
546
|
+
|
|
547
|
+
# Validate message length
|
|
548
|
+
if len(msg) < field_offset + field_size or len(msg) < msg_scope_end:
|
|
549
|
+
return False
|
|
550
|
+
|
|
551
|
+
# Try both endiannesses
|
|
552
|
+
for endian in ("big", "little"):
|
|
553
|
+
expected = int.from_bytes(msg[field_offset : field_offset + field_size], byteorder=endian)
|
|
554
|
+
|
|
555
|
+
# Extract data to checksum (excluding checksum field)
|
|
556
|
+
data = _extract_checksummed_data(msg, scope_start, msg_scope_end, field_offset, field_size)
|
|
557
|
+
|
|
558
|
+
# Compute and compare checksum
|
|
559
|
+
if _compute_and_compare(data, algorithm, expected, init_value):
|
|
560
|
+
return True
|
|
561
|
+
|
|
562
|
+
return False
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
def _compute_and_compare(
|
|
566
|
+
data: bytes,
|
|
567
|
+
algorithm: str,
|
|
568
|
+
expected: int,
|
|
569
|
+
init_value: int | None,
|
|
570
|
+
) -> bool:
|
|
571
|
+
"""Compute checksum and compare with expected value.
|
|
572
|
+
|
|
573
|
+
Args:
|
|
574
|
+
data: Data to checksum.
|
|
575
|
+
algorithm: Checksum algorithm name.
|
|
576
|
+
expected: Expected checksum value.
|
|
577
|
+
init_value: Initial value for CRC algorithms.
|
|
578
|
+
|
|
579
|
+
Returns:
|
|
580
|
+
True if computed checksum matches expected.
|
|
581
|
+
"""
|
|
582
|
+
try:
|
|
583
|
+
if init_value is not None:
|
|
584
|
+
computed = compute_checksum(data, algorithm, init=init_value)
|
|
585
|
+
else:
|
|
586
|
+
computed = compute_checksum(data, algorithm)
|
|
587
|
+
return computed == expected
|
|
588
|
+
except Exception:
|
|
589
|
+
return False
|
|
431
590
|
|
|
432
591
|
|
|
433
592
|
def compute_checksum(data: bytes, algorithm: str, **kwargs: Any) -> int:
|