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
|
@@ -166,65 +166,165 @@ def reverse_engineer_signal(
|
|
|
166
166
|
>>> for frame in result.frames[:5]:
|
|
167
167
|
... print(f" {frame.raw_bytes.hex()}")
|
|
168
168
|
"""
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
169
|
+
expected_baud_rates = expected_baud_rates or [
|
|
170
|
+
9600,
|
|
171
|
+
19200,
|
|
172
|
+
38400,
|
|
173
|
+
57600,
|
|
174
|
+
115200,
|
|
175
|
+
230400,
|
|
176
|
+
460800,
|
|
177
|
+
]
|
|
178
|
+
checksum_types = checksum_types or ["xor", "sum8", "crc8", "crc16"]
|
|
174
179
|
|
|
175
180
|
warnings: list[str] = []
|
|
176
181
|
data = trace.data
|
|
177
182
|
sample_rate = trace.metadata.sample_rate
|
|
178
183
|
|
|
179
|
-
# ========== Step 1: Signal
|
|
184
|
+
# ========== Step 1-3: Signal Analysis ==========
|
|
180
185
|
characterization = _characterize_signal(data, sample_rate)
|
|
181
|
-
|
|
182
|
-
# ========== Step 2: Clock Recovery ==========
|
|
183
186
|
baud_rate, baud_confidence = _detect_baud_rate(
|
|
184
187
|
data, sample_rate, expected_baud_rates, characterization["threshold"]
|
|
185
188
|
)
|
|
189
|
+
_validate_baud_confidence(baud_confidence, warnings)
|
|
190
|
+
|
|
191
|
+
bit_stream = _extract_bit_stream(data, sample_rate, baud_rate, characterization["threshold"])
|
|
192
|
+
_validate_bit_stream(bit_stream, warnings)
|
|
193
|
+
|
|
194
|
+
# ========== Step 4-5: Byte and Sync Analysis ==========
|
|
195
|
+
byte_positions, byte_stream = _extract_bytes(bit_stream)
|
|
196
|
+
_validate_byte_stream(byte_stream, warnings)
|
|
197
|
+
|
|
198
|
+
sync_pattern, sync_positions, sync_confidence = _detect_sync_pattern(
|
|
199
|
+
byte_stream, max_frame_length
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
# ========== Step 6-8: Frame and Protocol Analysis ==========
|
|
203
|
+
frames = _extract_frames(bit_stream, byte_stream, byte_positions, sync_positions, sync_pattern)
|
|
204
|
+
_validate_frame_count(frames, min_frames, warnings)
|
|
205
|
+
|
|
206
|
+
field_specs = _infer_fields(frames, sync_pattern)
|
|
207
|
+
checksum_type, checksum_pos, checksum_confidence = _detect_checksum(frames, checksum_types)
|
|
208
|
+
_validate_frame_checksums(frames, checksum_type, checksum_pos)
|
|
209
|
+
|
|
210
|
+
# ========== Build Results ==========
|
|
211
|
+
protocol_spec = _build_protocol_spec(
|
|
212
|
+
baud_rate,
|
|
213
|
+
sync_pattern,
|
|
214
|
+
frames,
|
|
215
|
+
field_specs,
|
|
216
|
+
checksum_type,
|
|
217
|
+
checksum_pos,
|
|
218
|
+
baud_confidence,
|
|
219
|
+
sync_confidence,
|
|
220
|
+
checksum_confidence,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
return ReverseEngineeringResult(
|
|
224
|
+
protocol_spec=protocol_spec,
|
|
225
|
+
frames=frames,
|
|
226
|
+
baud_rate=baud_rate,
|
|
227
|
+
bit_stream=bit_stream,
|
|
228
|
+
byte_stream=byte_stream,
|
|
229
|
+
sync_positions=sync_positions,
|
|
230
|
+
characterization=characterization,
|
|
231
|
+
confidence=protocol_spec.confidence,
|
|
232
|
+
warnings=warnings,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _validate_baud_confidence(baud_confidence: float, warnings: list[str]) -> None:
|
|
237
|
+
"""Validate baud rate confidence and add warning if low.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
baud_confidence: Confidence score for baud rate detection.
|
|
241
|
+
warnings: List to append warnings to.
|
|
242
|
+
"""
|
|
186
243
|
if baud_confidence < 0.7:
|
|
187
244
|
warnings.append(f"Low baud rate confidence: {baud_confidence:.2f}")
|
|
188
245
|
|
|
189
|
-
# ========== Step 3: Bit Stream Extraction ==========
|
|
190
|
-
bit_stream = _extract_bit_stream(data, sample_rate, baud_rate, characterization["threshold"])
|
|
191
246
|
|
|
247
|
+
def _validate_bit_stream(bit_stream: str, warnings: list[str]) -> None:
|
|
248
|
+
"""Validate bit stream length and add warning if short.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
bit_stream: Extracted bit stream.
|
|
252
|
+
warnings: List to append warnings to.
|
|
253
|
+
"""
|
|
192
254
|
if len(bit_stream) < 100:
|
|
193
255
|
warnings.append("Short bit stream extracted")
|
|
194
256
|
|
|
195
|
-
# ========== Step 4: Byte Extraction ==========
|
|
196
|
-
byte_positions, byte_stream = _extract_bytes(bit_stream)
|
|
197
257
|
|
|
258
|
+
def _validate_byte_stream(byte_stream: bytes, warnings: list[str]) -> None:
|
|
259
|
+
"""Validate byte stream length and add warning if short.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
byte_stream: Extracted byte stream.
|
|
263
|
+
warnings: List to append warnings to.
|
|
264
|
+
"""
|
|
198
265
|
if len(byte_stream) < 10:
|
|
199
266
|
warnings.append("Few bytes extracted")
|
|
200
267
|
|
|
201
|
-
# ========== Step 5: Sync Pattern Detection ==========
|
|
202
|
-
sync_pattern, sync_positions, sync_confidence = _detect_sync_pattern(
|
|
203
|
-
byte_stream, max_frame_length
|
|
204
|
-
)
|
|
205
268
|
|
|
206
|
-
|
|
207
|
-
frames
|
|
269
|
+
def _validate_frame_count(
|
|
270
|
+
frames: list[InferredFrame], min_frames: int, warnings: list[str]
|
|
271
|
+
) -> None:
|
|
272
|
+
"""Validate frame count and add warning if insufficient.
|
|
208
273
|
|
|
274
|
+
Args:
|
|
275
|
+
frames: List of extracted frames.
|
|
276
|
+
min_frames: Minimum required frames.
|
|
277
|
+
warnings: List to append warnings to.
|
|
278
|
+
"""
|
|
209
279
|
if len(frames) < min_frames:
|
|
210
280
|
warnings.append(f"Only {len(frames)} frames found (minimum {min_frames})")
|
|
211
281
|
|
|
212
|
-
# ========== Step 7: Field Analysis ==========
|
|
213
|
-
field_specs = _infer_fields(frames, sync_pattern)
|
|
214
282
|
|
|
215
|
-
|
|
216
|
-
checksum_type, checksum_pos
|
|
283
|
+
def _validate_frame_checksums(
|
|
284
|
+
frames: list[InferredFrame], checksum_type: str | None, checksum_pos: int | None
|
|
285
|
+
) -> None:
|
|
286
|
+
"""Validate checksums for all frames.
|
|
217
287
|
|
|
288
|
+
Args:
|
|
289
|
+
frames: List of frames to validate.
|
|
290
|
+
checksum_type: Detected checksum type.
|
|
291
|
+
checksum_pos: Position of checksum in frame.
|
|
292
|
+
"""
|
|
218
293
|
if checksum_type:
|
|
219
|
-
# Validate frames with checksum
|
|
220
294
|
for frame in frames:
|
|
221
295
|
frame.checksum_valid = _verify_checksum(frame.raw_bytes, checksum_type, checksum_pos)
|
|
222
296
|
|
|
223
|
-
|
|
297
|
+
|
|
298
|
+
def _build_protocol_spec(
|
|
299
|
+
baud_rate: float,
|
|
300
|
+
sync_pattern: bytes,
|
|
301
|
+
frames: list[InferredFrame],
|
|
302
|
+
field_specs: list[FieldSpec],
|
|
303
|
+
checksum_type: str | None,
|
|
304
|
+
checksum_pos: int | None,
|
|
305
|
+
baud_confidence: float,
|
|
306
|
+
sync_confidence: float,
|
|
307
|
+
checksum_confidence: float,
|
|
308
|
+
) -> ProtocolSpec:
|
|
309
|
+
"""Build protocol specification from analysis results.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
baud_rate: Detected baud rate.
|
|
313
|
+
sync_pattern: Detected sync pattern.
|
|
314
|
+
frames: List of extracted frames.
|
|
315
|
+
field_specs: List of field specifications.
|
|
316
|
+
checksum_type: Detected checksum type.
|
|
317
|
+
checksum_pos: Position of checksum.
|
|
318
|
+
baud_confidence: Baud rate detection confidence.
|
|
319
|
+
sync_confidence: Sync pattern detection confidence.
|
|
320
|
+
checksum_confidence: Checksum detection confidence.
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
Complete protocol specification.
|
|
324
|
+
"""
|
|
224
325
|
frame_lengths = [len(f.raw_bytes) for f in frames]
|
|
225
326
|
frame_length = int(np.median(frame_lengths)) if len(set(frame_lengths)) == 1 else None
|
|
226
327
|
|
|
227
|
-
# Calculate overall confidence
|
|
228
328
|
overall_confidence = (
|
|
229
329
|
baud_confidence * 0.3
|
|
230
330
|
+ sync_confidence * 0.3
|
|
@@ -232,10 +332,10 @@ def reverse_engineer_signal(
|
|
|
232
332
|
+ min(len(frames) / 10, 1.0) * 0.2
|
|
233
333
|
)
|
|
234
334
|
|
|
235
|
-
|
|
335
|
+
return ProtocolSpec(
|
|
236
336
|
name="Unknown Protocol (Inferred)",
|
|
237
337
|
baud_rate=baud_rate,
|
|
238
|
-
frame_format="8N1",
|
|
338
|
+
frame_format="8N1",
|
|
239
339
|
sync_pattern=sync_pattern.hex() if sync_pattern else "",
|
|
240
340
|
frame_length=frame_length,
|
|
241
341
|
fields=field_specs,
|
|
@@ -244,18 +344,6 @@ def reverse_engineer_signal(
|
|
|
244
344
|
confidence=overall_confidence,
|
|
245
345
|
)
|
|
246
346
|
|
|
247
|
-
return ReverseEngineeringResult(
|
|
248
|
-
protocol_spec=protocol_spec,
|
|
249
|
-
frames=frames,
|
|
250
|
-
baud_rate=baud_rate,
|
|
251
|
-
bit_stream=bit_stream,
|
|
252
|
-
byte_stream=byte_stream,
|
|
253
|
-
sync_positions=sync_positions,
|
|
254
|
-
characterization=characterization,
|
|
255
|
-
confidence=overall_confidence,
|
|
256
|
-
warnings=warnings,
|
|
257
|
-
)
|
|
258
|
-
|
|
259
347
|
|
|
260
348
|
def _characterize_signal(
|
|
261
349
|
data: np.ndarray[Any, np.dtype[np.float64]], sample_rate: float
|
|
@@ -371,13 +459,39 @@ def _detect_sync_pattern(
|
|
|
371
459
|
byte_stream: bytes,
|
|
372
460
|
max_frame_length: int,
|
|
373
461
|
) -> tuple[bytes, list[int], float]:
|
|
374
|
-
"""Detect sync pattern by finding repeated byte sequences.
|
|
462
|
+
"""Detect sync pattern by finding repeated byte sequences.
|
|
463
|
+
|
|
464
|
+
Args:
|
|
465
|
+
byte_stream: Stream of bytes to analyze.
|
|
466
|
+
max_frame_length: Maximum expected frame length.
|
|
467
|
+
|
|
468
|
+
Returns:
|
|
469
|
+
Tuple of (pattern, positions, confidence).
|
|
470
|
+
"""
|
|
375
471
|
if len(byte_stream) < 20:
|
|
376
472
|
return b"", [], 0.0
|
|
377
473
|
|
|
378
|
-
list(byte_stream)
|
|
379
|
-
|
|
380
474
|
# Try common sync patterns first
|
|
475
|
+
best_pattern, best_positions, best_confidence = _find_common_sync_patterns(byte_stream)
|
|
476
|
+
|
|
477
|
+
# If no common pattern found, try to find repeating sequences
|
|
478
|
+
if best_confidence < 0.5:
|
|
479
|
+
pattern, positions, confidence = _find_repeating_patterns(byte_stream)
|
|
480
|
+
if confidence > best_confidence:
|
|
481
|
+
best_pattern, best_positions, best_confidence = pattern, positions, confidence
|
|
482
|
+
|
|
483
|
+
return best_pattern, best_positions, best_confidence
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def _find_common_sync_patterns(byte_stream: bytes) -> tuple[bytes, list[int], float]:
|
|
487
|
+
"""Search for common sync patterns.
|
|
488
|
+
|
|
489
|
+
Args:
|
|
490
|
+
byte_stream: Stream of bytes to analyze.
|
|
491
|
+
|
|
492
|
+
Returns:
|
|
493
|
+
Tuple of (best_pattern, positions, confidence).
|
|
494
|
+
"""
|
|
381
495
|
common_patterns = [
|
|
382
496
|
bytes([0xAA, 0x55]),
|
|
383
497
|
bytes([0x55, 0xAA]),
|
|
@@ -392,47 +506,85 @@ def _detect_sync_pattern(
|
|
|
392
506
|
best_confidence = 0.0
|
|
393
507
|
|
|
394
508
|
for pattern in common_patterns:
|
|
395
|
-
positions =
|
|
396
|
-
for i in range(len(byte_stream) - len(pattern)):
|
|
397
|
-
if byte_stream[i : i + len(pattern)] == pattern:
|
|
398
|
-
positions.append(i)
|
|
509
|
+
positions = _find_pattern_positions(byte_stream, pattern)
|
|
399
510
|
|
|
400
511
|
if len(positions) >= 3:
|
|
401
|
-
|
|
402
|
-
spacings = np.diff(positions)
|
|
403
|
-
if len(spacings) > 0:
|
|
404
|
-
median_spacing = np.median(spacings)
|
|
405
|
-
regularity = 1.0 - np.std(spacings) / (median_spacing + 1)
|
|
512
|
+
confidence = _calculate_pattern_confidence(positions)
|
|
406
513
|
|
|
407
|
-
|
|
514
|
+
if confidence > best_confidence:
|
|
515
|
+
best_pattern = pattern
|
|
516
|
+
best_positions = positions
|
|
517
|
+
best_confidence = confidence
|
|
408
518
|
|
|
409
|
-
|
|
410
|
-
best_pattern = pattern
|
|
411
|
-
best_positions = positions
|
|
412
|
-
best_confidence = confidence
|
|
519
|
+
return best_pattern, best_positions, best_confidence
|
|
413
520
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
521
|
+
|
|
522
|
+
def _find_repeating_patterns(byte_stream: bytes) -> tuple[bytes, list[int], float]:
|
|
523
|
+
"""Search for repeating byte sequences.
|
|
524
|
+
|
|
525
|
+
Args:
|
|
526
|
+
byte_stream: Stream of bytes to analyze.
|
|
527
|
+
|
|
528
|
+
Returns:
|
|
529
|
+
Tuple of (best_pattern, positions, confidence).
|
|
530
|
+
"""
|
|
531
|
+
best_pattern = b""
|
|
532
|
+
best_positions: list[int] = []
|
|
533
|
+
best_confidence = 0.0
|
|
534
|
+
|
|
535
|
+
for pattern_len in range(1, 4):
|
|
536
|
+
for start in range(min(50, len(byte_stream) - pattern_len)):
|
|
537
|
+
pattern = byte_stream[start : start + pattern_len]
|
|
538
|
+
positions = _find_pattern_positions(byte_stream, pattern)
|
|
539
|
+
|
|
540
|
+
if len(positions) >= 5:
|
|
541
|
+
spacings = np.diff(positions)
|
|
542
|
+
if len(spacings) > 0 and np.std(spacings) / (np.median(spacings) + 1) < 0.2:
|
|
543
|
+
confidence = min(len(positions) / 10, 1.0)
|
|
544
|
+
if confidence > best_confidence:
|
|
545
|
+
best_pattern = pattern
|
|
546
|
+
best_positions = positions
|
|
547
|
+
best_confidence = confidence
|
|
432
548
|
|
|
433
549
|
return best_pattern, best_positions, best_confidence
|
|
434
550
|
|
|
435
551
|
|
|
552
|
+
def _find_pattern_positions(byte_stream: bytes, pattern: bytes) -> list[int]:
|
|
553
|
+
"""Find all positions where pattern occurs in byte stream.
|
|
554
|
+
|
|
555
|
+
Args:
|
|
556
|
+
byte_stream: Stream to search.
|
|
557
|
+
pattern: Pattern to find.
|
|
558
|
+
|
|
559
|
+
Returns:
|
|
560
|
+
List of byte positions where pattern occurs.
|
|
561
|
+
"""
|
|
562
|
+
positions = []
|
|
563
|
+
for i in range(len(byte_stream) - len(pattern)):
|
|
564
|
+
if byte_stream[i : i + len(pattern)] == pattern:
|
|
565
|
+
positions.append(i)
|
|
566
|
+
return positions
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def _calculate_pattern_confidence(positions: list[int]) -> float:
|
|
570
|
+
"""Calculate confidence score for pattern regularity.
|
|
571
|
+
|
|
572
|
+
Args:
|
|
573
|
+
positions: List of pattern positions.
|
|
574
|
+
|
|
575
|
+
Returns:
|
|
576
|
+
Confidence score (0.0 to 1.0).
|
|
577
|
+
"""
|
|
578
|
+
spacings = np.diff(positions)
|
|
579
|
+
if len(spacings) == 0:
|
|
580
|
+
return 0.0
|
|
581
|
+
|
|
582
|
+
median_spacing = float(np.median(spacings))
|
|
583
|
+
regularity = 1.0 - float(np.std(spacings)) / (median_spacing + 1)
|
|
584
|
+
|
|
585
|
+
return min(len(positions) / 10, 1.0) * max(regularity, 0)
|
|
586
|
+
|
|
587
|
+
|
|
436
588
|
def _extract_frames(
|
|
437
589
|
bit_stream: str,
|
|
438
590
|
byte_stream: bytes,
|
|
@@ -481,70 +633,105 @@ def _infer_fields(frames: list[InferredFrame], sync_pattern: bytes) -> list[Fiel
|
|
|
481
633
|
fields: list[FieldSpec] = []
|
|
482
634
|
sync_len = len(sync_pattern)
|
|
483
635
|
|
|
484
|
-
|
|
485
|
-
|
|
636
|
+
_add_sync_field(fields, sync_pattern)
|
|
637
|
+
length_offset = _try_add_length_field(fields, frames, sync_len)
|
|
638
|
+
_add_data_field(fields, frames, length_offset, sync_len)
|
|
639
|
+
_add_checksum_field(fields)
|
|
640
|
+
|
|
641
|
+
return fields
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def _add_sync_field(fields: list[FieldSpec], sync_pattern: bytes) -> None:
|
|
645
|
+
"""Add sync field if pattern exists.
|
|
646
|
+
|
|
647
|
+
Args:
|
|
648
|
+
fields: Field list to append to.
|
|
649
|
+
sync_pattern: Sync pattern bytes.
|
|
650
|
+
"""
|
|
651
|
+
if len(sync_pattern) > 0:
|
|
486
652
|
fields.append(
|
|
487
653
|
FieldSpec(
|
|
488
654
|
name="sync",
|
|
489
655
|
offset=0,
|
|
490
|
-
size=
|
|
656
|
+
size=len(sync_pattern),
|
|
491
657
|
field_type="constant",
|
|
492
658
|
value=sync_pattern.hex(),
|
|
493
659
|
)
|
|
494
660
|
)
|
|
495
661
|
|
|
496
|
-
|
|
662
|
+
|
|
663
|
+
def _try_add_length_field(
|
|
664
|
+
fields: list[FieldSpec], frames: list[InferredFrame], sync_len: int
|
|
665
|
+
) -> int:
|
|
666
|
+
"""Try to detect and add length field.
|
|
667
|
+
|
|
668
|
+
Args:
|
|
669
|
+
fields: Field list to append to.
|
|
670
|
+
frames: Frame list to analyze.
|
|
671
|
+
sync_len: Length of sync pattern.
|
|
672
|
+
|
|
673
|
+
Returns:
|
|
674
|
+
Offset after length field (or sync_len if no length detected).
|
|
675
|
+
"""
|
|
497
676
|
frame_lengths = [len(f.raw_bytes) for f in frames]
|
|
498
677
|
min_len = min(frame_lengths)
|
|
499
|
-
|
|
500
|
-
# Check for length field (common at offset 2 or after sync)
|
|
501
678
|
length_offset = sync_len
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
679
|
+
|
|
680
|
+
if min_len <= length_offset:
|
|
681
|
+
return length_offset
|
|
682
|
+
|
|
683
|
+
length_values = [f.raw_bytes[length_offset] for f in frames if len(f.raw_bytes) > length_offset]
|
|
684
|
+
|
|
685
|
+
if len(length_values) <= 2:
|
|
686
|
+
return length_offset
|
|
687
|
+
|
|
688
|
+
# Check correlation
|
|
689
|
+
if len(set(length_values)) > 1:
|
|
690
|
+
correlation = np.corrcoef(length_values, frame_lengths)[0, 1]
|
|
691
|
+
else:
|
|
692
|
+
correlation = 0
|
|
693
|
+
|
|
694
|
+
if correlation > 0.8 or (len(set(length_values)) == 1 and length_values[0] == frame_lengths[0]):
|
|
695
|
+
fields.append(FieldSpec(name="length", offset=length_offset, size=1, field_type="uint8"))
|
|
696
|
+
return length_offset + 1
|
|
697
|
+
|
|
698
|
+
return length_offset
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
def _add_data_field(
|
|
702
|
+
fields: list[FieldSpec],
|
|
703
|
+
frames: list[InferredFrame],
|
|
704
|
+
length_offset: int,
|
|
705
|
+
sync_len: int,
|
|
706
|
+
) -> None:
|
|
707
|
+
"""Add data field for payload.
|
|
708
|
+
|
|
709
|
+
Args:
|
|
710
|
+
fields: Field list to append to.
|
|
711
|
+
frames: Frame list.
|
|
712
|
+
length_offset: Offset after length field.
|
|
713
|
+
sync_len: Length of sync pattern.
|
|
714
|
+
"""
|
|
715
|
+
min_len = min(len(f.raw_bytes) for f in frames)
|
|
716
|
+
|
|
527
717
|
if min_len > length_offset + 1:
|
|
528
718
|
fields.append(
|
|
529
719
|
FieldSpec(
|
|
530
720
|
name="data",
|
|
531
721
|
offset=length_offset,
|
|
532
|
-
size="length - sync_len - 2",
|
|
722
|
+
size="length - sync_len - 2",
|
|
533
723
|
field_type="bytes",
|
|
534
724
|
)
|
|
535
725
|
)
|
|
536
726
|
|
|
537
|
-
# Last byte likely checksum
|
|
538
|
-
fields.append(
|
|
539
|
-
FieldSpec(
|
|
540
|
-
name="checksum",
|
|
541
|
-
offset=-1,
|
|
542
|
-
size=1,
|
|
543
|
-
field_type="checksum",
|
|
544
|
-
)
|
|
545
|
-
)
|
|
546
727
|
|
|
547
|
-
|
|
728
|
+
def _add_checksum_field(fields: list[FieldSpec]) -> None:
|
|
729
|
+
"""Add checksum field at end.
|
|
730
|
+
|
|
731
|
+
Args:
|
|
732
|
+
fields: Field list to append to.
|
|
733
|
+
"""
|
|
734
|
+
fields.append(FieldSpec(name="checksum", offset=-1, size=1, field_type="checksum"))
|
|
548
735
|
|
|
549
736
|
|
|
550
737
|
def _detect_checksum(
|
|
@@ -589,40 +776,54 @@ def _detect_checksum(
|
|
|
589
776
|
|
|
590
777
|
def _calculate_checksum(data: bytes, checksum_type: str) -> int:
|
|
591
778
|
"""Calculate checksum using specified algorithm."""
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
779
|
+
checksum_funcs = {
|
|
780
|
+
"xor": _checksum_xor,
|
|
781
|
+
"sum8": _checksum_sum8,
|
|
782
|
+
"crc8": _checksum_crc8,
|
|
783
|
+
"crc16": _checksum_crc16,
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
func = checksum_funcs.get(checksum_type)
|
|
787
|
+
return func(data) if func else 0
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
def _checksum_xor(data: bytes) -> int:
|
|
791
|
+
"""Calculate XOR checksum."""
|
|
792
|
+
result = 0
|
|
793
|
+
for b in data:
|
|
794
|
+
result ^= b
|
|
795
|
+
return result
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
def _checksum_sum8(data: bytes) -> int:
|
|
799
|
+
"""Calculate 8-bit sum checksum."""
|
|
800
|
+
return sum(data) & 0xFF
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
def _checksum_crc8(data: bytes) -> int:
|
|
804
|
+
"""Calculate simple CRC-8."""
|
|
805
|
+
crc = 0
|
|
806
|
+
for b in data:
|
|
807
|
+
crc ^= b
|
|
808
|
+
for _ in range(8):
|
|
809
|
+
if crc & 0x80:
|
|
810
|
+
crc = ((crc << 1) ^ 0x07) & 0xFF
|
|
811
|
+
else:
|
|
812
|
+
crc = (crc << 1) & 0xFF
|
|
813
|
+
return crc
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
def _checksum_crc16(data: bytes) -> int:
|
|
817
|
+
"""Calculate CRC-16-CCITT (return low byte only)."""
|
|
818
|
+
crc = 0xFFFF
|
|
819
|
+
for b in data:
|
|
820
|
+
crc ^= b << 8
|
|
821
|
+
for _ in range(8):
|
|
822
|
+
if crc & 0x8000:
|
|
823
|
+
crc = ((crc << 1) ^ 0x1021) & 0xFFFF
|
|
824
|
+
else:
|
|
825
|
+
crc = (crc << 1) & 0xFFFF
|
|
826
|
+
return crc & 0xFF
|
|
626
827
|
|
|
627
828
|
|
|
628
829
|
def _verify_checksum(data: bytes, checksum_type: str, checksum_pos: int | None) -> bool:
|