oscura 0.0.1__py3-none-any.whl → 0.1.1__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 +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.1.dist-info/METADATA +300 -0
- oscura-0.1.1.dist-info/RECORD +463 -0
- oscura-0.1.1.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,923 @@
|
|
|
1
|
+
"""Checksum and CRC field detection and identification.
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
This module provides tools for detecting checksum and CRC fields in binary
|
|
5
|
+
messages by analyzing field correlations and testing common algorithms.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Literal, Union
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from numpy.typing import NDArray
|
|
15
|
+
|
|
16
|
+
# Type alias for input data
|
|
17
|
+
DataType = Union[bytes, bytearray, "NDArray[np.uint8]"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class ChecksumCandidate:
|
|
22
|
+
"""Candidate checksum field.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
offset: Byte offset in message
|
|
26
|
+
size: Field size in bytes (1, 2, or 4)
|
|
27
|
+
position: Location in message structure
|
|
28
|
+
correlation: Correlation with content (0-1)
|
|
29
|
+
likely_scope: Byte range likely covered by checksum (start, end)
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
offset: int
|
|
33
|
+
size: int
|
|
34
|
+
position: Literal["header", "trailer"]
|
|
35
|
+
correlation: float
|
|
36
|
+
likely_scope: tuple[int, int]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class ChecksumMatch:
|
|
41
|
+
"""Identified checksum algorithm.
|
|
42
|
+
|
|
43
|
+
Attributes:
|
|
44
|
+
algorithm: Algorithm name
|
|
45
|
+
offset: Field offset in message
|
|
46
|
+
size: Field size in bytes
|
|
47
|
+
scope_start: Start of checksummed region
|
|
48
|
+
scope_end: End of checksummed region
|
|
49
|
+
match_rate: Fraction of messages that match (0-1)
|
|
50
|
+
polynomial: CRC polynomial (for CRC algorithms)
|
|
51
|
+
init_value: Initial value (for CRC algorithms)
|
|
52
|
+
xor_out: Final XOR value (for CRC algorithms)
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
algorithm: str
|
|
56
|
+
offset: int
|
|
57
|
+
size: int
|
|
58
|
+
scope_start: int
|
|
59
|
+
scope_end: int
|
|
60
|
+
match_rate: float
|
|
61
|
+
polynomial: int | None = None
|
|
62
|
+
init_value: int | None = None
|
|
63
|
+
xor_out: int | None = None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def detect_checksum_fields(
|
|
67
|
+
messages: list[DataType], candidate_offsets: list[int] | None = None
|
|
68
|
+
) -> list[ChecksumCandidate]:
|
|
69
|
+
"""Detect fields that are correlated with message content.
|
|
70
|
+
|
|
71
|
+
: Checksum and CRC Field Detection
|
|
72
|
+
|
|
73
|
+
Analyzes message fields to find those that vary consistently with
|
|
74
|
+
content changes, indicating potential checksum/CRC fields.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
messages: List of messages to analyze
|
|
78
|
+
candidate_offsets: Optional list of specific offsets to check
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
List of checksum candidates sorted by correlation
|
|
82
|
+
|
|
83
|
+
Example:
|
|
84
|
+
>>> msgs = [b'\\x00\\x00DATA', b'\\x01\\x00DATA']
|
|
85
|
+
>>> candidates = detect_checksum_fields(msgs)
|
|
86
|
+
>>> len(candidates) >= 0
|
|
87
|
+
True
|
|
88
|
+
"""
|
|
89
|
+
if not messages:
|
|
90
|
+
return []
|
|
91
|
+
|
|
92
|
+
# Convert all messages to bytes
|
|
93
|
+
byte_messages = []
|
|
94
|
+
for msg in messages:
|
|
95
|
+
if isinstance(msg, np.ndarray):
|
|
96
|
+
byte_messages.append(msg.tobytes() if msg.dtype == np.uint8 else bytes(msg.flatten()))
|
|
97
|
+
else:
|
|
98
|
+
byte_messages.append(bytes(msg))
|
|
99
|
+
|
|
100
|
+
# Find minimum message length
|
|
101
|
+
min_len = min(len(msg) for msg in byte_messages)
|
|
102
|
+
|
|
103
|
+
if min_len < 2:
|
|
104
|
+
return []
|
|
105
|
+
|
|
106
|
+
# Determine candidate positions
|
|
107
|
+
if candidate_offsets is None:
|
|
108
|
+
# Check header (first 16 bytes) and trailer (last 16 bytes)
|
|
109
|
+
header_positions = list(range(min(16, min_len - 1)))
|
|
110
|
+
trailer_start = max(0, min_len - 16)
|
|
111
|
+
trailer_positions = list(range(trailer_start, min_len - 1))
|
|
112
|
+
candidate_offsets = list(set(header_positions + trailer_positions))
|
|
113
|
+
|
|
114
|
+
candidates = []
|
|
115
|
+
|
|
116
|
+
# Test each candidate offset for different field sizes
|
|
117
|
+
for offset in candidate_offsets:
|
|
118
|
+
for size in [1, 2, 4]:
|
|
119
|
+
if offset + size > min_len:
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
# Extract field values and content
|
|
123
|
+
field_values = []
|
|
124
|
+
content_hashes = []
|
|
125
|
+
|
|
126
|
+
for msg in byte_messages:
|
|
127
|
+
if len(msg) < offset + size:
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
# Extract field value
|
|
131
|
+
field_bytes = msg[offset : offset + size]
|
|
132
|
+
field_value = int.from_bytes(field_bytes, byteorder="big")
|
|
133
|
+
field_values.append(field_value)
|
|
134
|
+
|
|
135
|
+
# Hash content (excluding the field itself)
|
|
136
|
+
content = msg[:offset] + msg[offset + size :]
|
|
137
|
+
content_hash = hash(content)
|
|
138
|
+
content_hashes.append(content_hash)
|
|
139
|
+
|
|
140
|
+
if len(field_values) < 2:
|
|
141
|
+
continue
|
|
142
|
+
|
|
143
|
+
# Calculate correlation between field and content
|
|
144
|
+
# If field varies with content, it's a good candidate
|
|
145
|
+
unique_content = len(set(content_hashes))
|
|
146
|
+
unique_fields = len(set(field_values))
|
|
147
|
+
|
|
148
|
+
if unique_content > 1:
|
|
149
|
+
# Correlation estimate: how much field varies with content
|
|
150
|
+
correlation = min(1.0, unique_fields / unique_content)
|
|
151
|
+
else:
|
|
152
|
+
correlation = 0.0
|
|
153
|
+
|
|
154
|
+
# Skip if correlation is too low
|
|
155
|
+
if correlation < 0.3:
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
# Determine position (header vs trailer)
|
|
159
|
+
position: Literal["header", "trailer"] = (
|
|
160
|
+
"header" if offset < min_len // 2 else "trailer"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Estimate likely scope
|
|
164
|
+
if position == "header":
|
|
165
|
+
likely_scope = (offset + size, min_len)
|
|
166
|
+
else:
|
|
167
|
+
likely_scope = (0, offset)
|
|
168
|
+
|
|
169
|
+
candidates.append(
|
|
170
|
+
ChecksumCandidate(
|
|
171
|
+
offset=offset,
|
|
172
|
+
size=size,
|
|
173
|
+
position=position,
|
|
174
|
+
correlation=correlation,
|
|
175
|
+
likely_scope=likely_scope,
|
|
176
|
+
)
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Sort by correlation descending
|
|
180
|
+
candidates.sort(key=lambda c: c.correlation, reverse=True)
|
|
181
|
+
|
|
182
|
+
return candidates
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def identify_checksum_algorithm(
|
|
186
|
+
messages: list[DataType], field_offset: int, field_size: int | None = None
|
|
187
|
+
) -> ChecksumMatch | None:
|
|
188
|
+
"""Identify which checksum algorithm is used.
|
|
189
|
+
|
|
190
|
+
: Checksum and CRC Field Detection
|
|
191
|
+
|
|
192
|
+
Tests common checksum algorithms to identify which one matches
|
|
193
|
+
the observed field values.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
messages: List of messages to analyze
|
|
197
|
+
field_offset: Offset of checksum field
|
|
198
|
+
field_size: Size of field (1, 2, or 4 bytes), auto-detect if None
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
ChecksumMatch if algorithm identified, None otherwise
|
|
202
|
+
|
|
203
|
+
Example:
|
|
204
|
+
>>> msgs = [b'\\x41ABC', b'\\x42BCD'] # XOR checksum
|
|
205
|
+
>>> match = identify_checksum_algorithm(msgs, 0, 1)
|
|
206
|
+
>>> match is not None
|
|
207
|
+
True
|
|
208
|
+
"""
|
|
209
|
+
if not messages:
|
|
210
|
+
return None
|
|
211
|
+
|
|
212
|
+
# Convert messages to bytes
|
|
213
|
+
byte_messages = []
|
|
214
|
+
for msg in messages:
|
|
215
|
+
if isinstance(msg, np.ndarray):
|
|
216
|
+
byte_messages.append(msg.tobytes() if msg.dtype == np.uint8 else bytes(msg.flatten()))
|
|
217
|
+
else:
|
|
218
|
+
byte_messages.append(bytes(msg))
|
|
219
|
+
|
|
220
|
+
# Determine field size if not specified
|
|
221
|
+
if field_size is None:
|
|
222
|
+
field_sizes = [1, 2, 4]
|
|
223
|
+
else:
|
|
224
|
+
field_sizes = [field_size]
|
|
225
|
+
|
|
226
|
+
best_match = None
|
|
227
|
+
best_rate = 0.0
|
|
228
|
+
|
|
229
|
+
# Try each field size
|
|
230
|
+
for size in field_sizes:
|
|
231
|
+
if any(len(msg) < field_offset + size for msg in byte_messages):
|
|
232
|
+
continue
|
|
233
|
+
|
|
234
|
+
# Define algorithm tests based on field size
|
|
235
|
+
if size == 1:
|
|
236
|
+
algorithms = ["xor", "sum8"]
|
|
237
|
+
elif size == 2:
|
|
238
|
+
# Include both big and little endian CRC variants
|
|
239
|
+
algorithms = [
|
|
240
|
+
"sum16_big",
|
|
241
|
+
"sum16_little",
|
|
242
|
+
"crc16_ccitt",
|
|
243
|
+
"crc16_ibm",
|
|
244
|
+
"crc16",
|
|
245
|
+
"checksum",
|
|
246
|
+
]
|
|
247
|
+
elif size == 4:
|
|
248
|
+
algorithms = ["crc32"]
|
|
249
|
+
else:
|
|
250
|
+
continue
|
|
251
|
+
|
|
252
|
+
# Test each algorithm
|
|
253
|
+
for algo in algorithms:
|
|
254
|
+
# Map algorithm names to computation functions
|
|
255
|
+
actual_algo = algo
|
|
256
|
+
if algo == "crc16":
|
|
257
|
+
actual_algo = "crc16_ccitt"
|
|
258
|
+
elif algo == "checksum":
|
|
259
|
+
actual_algo = "sum16_big"
|
|
260
|
+
|
|
261
|
+
# For CRC algorithms, try different init values
|
|
262
|
+
init_values: list[int | None] = [None]
|
|
263
|
+
if actual_algo in ["crc16_ccitt", "crc16_ibm"]:
|
|
264
|
+
init_values = [0x0000, 0xFFFF]
|
|
265
|
+
|
|
266
|
+
for init_val in init_values:
|
|
267
|
+
# Try different scopes
|
|
268
|
+
for scope_start in [0, field_offset + size]:
|
|
269
|
+
for scope_end in [field_offset, len(byte_messages[0])]:
|
|
270
|
+
if scope_end <= scope_start:
|
|
271
|
+
continue
|
|
272
|
+
|
|
273
|
+
# Test algorithm on all messages
|
|
274
|
+
matches = 0
|
|
275
|
+
total = 0
|
|
276
|
+
|
|
277
|
+
for msg in byte_messages:
|
|
278
|
+
if len(msg) < scope_end:
|
|
279
|
+
continue
|
|
280
|
+
|
|
281
|
+
# Try both big and little endian for field extraction
|
|
282
|
+
endian_val: Literal["big", "little"]
|
|
283
|
+
for endian_val in ("big", "little"): # type: ignore[assignment]
|
|
284
|
+
expected = int.from_bytes(
|
|
285
|
+
msg[field_offset : field_offset + size], byteorder=endian_val
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
# Extract data to checksum
|
|
289
|
+
if scope_start < field_offset < scope_end:
|
|
290
|
+
# Exclude checksum field from data
|
|
291
|
+
data = (
|
|
292
|
+
msg[scope_start:field_offset]
|
|
293
|
+
+ msg[field_offset + size : scope_end]
|
|
294
|
+
)
|
|
295
|
+
else:
|
|
296
|
+
data = msg[scope_start:scope_end]
|
|
297
|
+
|
|
298
|
+
# Compute checksum
|
|
299
|
+
try:
|
|
300
|
+
if init_val is not None:
|
|
301
|
+
computed = compute_checksum(
|
|
302
|
+
data, actual_algo, init=init_val
|
|
303
|
+
)
|
|
304
|
+
else:
|
|
305
|
+
computed = compute_checksum(data, actual_algo)
|
|
306
|
+
if computed == expected:
|
|
307
|
+
matches += 1
|
|
308
|
+
break # Found match with this endian
|
|
309
|
+
except Exception:
|
|
310
|
+
pass
|
|
311
|
+
|
|
312
|
+
total += 1
|
|
313
|
+
|
|
314
|
+
if total == 0:
|
|
315
|
+
continue
|
|
316
|
+
|
|
317
|
+
match_rate = matches / total
|
|
318
|
+
|
|
319
|
+
# Consider it a match if >= 80% of messages match
|
|
320
|
+
if match_rate >= 0.8 and match_rate > best_rate:
|
|
321
|
+
best_rate = match_rate
|
|
322
|
+
best_match = ChecksumMatch(
|
|
323
|
+
algorithm=algo,
|
|
324
|
+
offset=field_offset,
|
|
325
|
+
size=size,
|
|
326
|
+
scope_start=scope_start,
|
|
327
|
+
scope_end=scope_end,
|
|
328
|
+
match_rate=match_rate,
|
|
329
|
+
init_value=init_val,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
return best_match
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def verify_checksums(
|
|
336
|
+
messages: list[DataType],
|
|
337
|
+
algorithm: str,
|
|
338
|
+
field_offset: int,
|
|
339
|
+
scope_start: int = 0,
|
|
340
|
+
scope_end: int | None = None,
|
|
341
|
+
init_value: int | None = None,
|
|
342
|
+
) -> tuple[int, int]:
|
|
343
|
+
"""Verify checksums using identified algorithm.
|
|
344
|
+
|
|
345
|
+
: Checksum and CRC Field Detection
|
|
346
|
+
|
|
347
|
+
Validates checksums across multiple messages using the specified algorithm.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
messages: List of messages to verify
|
|
351
|
+
algorithm: Checksum algorithm name
|
|
352
|
+
field_offset: Offset of checksum field
|
|
353
|
+
scope_start: Start of checksummed data (default: 0)
|
|
354
|
+
scope_end: End of checksummed data (None = message end)
|
|
355
|
+
init_value: Initial value for CRC algorithms (None = use default)
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
Tuple of (passed, failed) counts
|
|
359
|
+
|
|
360
|
+
Example:
|
|
361
|
+
>>> msgs = [b'\\x41ABC']
|
|
362
|
+
>>> passed, failed = verify_checksums(msgs, 'xor', 0, 1)
|
|
363
|
+
>>> passed + failed == len(msgs)
|
|
364
|
+
True
|
|
365
|
+
"""
|
|
366
|
+
if not messages:
|
|
367
|
+
return (0, 0)
|
|
368
|
+
|
|
369
|
+
passed = 0
|
|
370
|
+
failed = 0
|
|
371
|
+
|
|
372
|
+
# Determine field size from algorithm
|
|
373
|
+
if algorithm in ["xor", "sum8"]:
|
|
374
|
+
field_size = 1
|
|
375
|
+
elif algorithm.startswith("sum16") or algorithm.startswith("crc16"):
|
|
376
|
+
field_size = 2
|
|
377
|
+
elif algorithm == "crc32":
|
|
378
|
+
field_size = 4
|
|
379
|
+
else:
|
|
380
|
+
# Try to infer from first message
|
|
381
|
+
field_size = 1
|
|
382
|
+
|
|
383
|
+
for msg in messages:
|
|
384
|
+
if isinstance(msg, np.ndarray):
|
|
385
|
+
msg = msg.tobytes() if msg.dtype == np.uint8 else bytes(msg.flatten())
|
|
386
|
+
else:
|
|
387
|
+
msg = bytes(msg)
|
|
388
|
+
|
|
389
|
+
msg_scope_end = scope_end if scope_end is not None else len(msg)
|
|
390
|
+
|
|
391
|
+
if len(msg) < field_offset + field_size or len(msg) < msg_scope_end:
|
|
392
|
+
failed += 1
|
|
393
|
+
continue
|
|
394
|
+
|
|
395
|
+
# Try both endiannesses
|
|
396
|
+
matched = False
|
|
397
|
+
endian_val2: Literal["big", "little"]
|
|
398
|
+
for endian_val2 in ("big", "little"): # type: ignore[assignment]
|
|
399
|
+
expected = int.from_bytes(
|
|
400
|
+
msg[field_offset : field_offset + field_size], byteorder=endian_val2
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
# Extract data to checksum
|
|
404
|
+
if scope_start < field_offset < msg_scope_end:
|
|
405
|
+
data = (
|
|
406
|
+
msg[scope_start:field_offset] + msg[field_offset + field_size : msg_scope_end]
|
|
407
|
+
)
|
|
408
|
+
else:
|
|
409
|
+
data = msg[scope_start:msg_scope_end]
|
|
410
|
+
|
|
411
|
+
# Compute checksum
|
|
412
|
+
try:
|
|
413
|
+
if init_value is not None:
|
|
414
|
+
computed = compute_checksum(data, algorithm, init=init_value)
|
|
415
|
+
else:
|
|
416
|
+
computed = compute_checksum(data, algorithm)
|
|
417
|
+
if computed == expected:
|
|
418
|
+
matched = True
|
|
419
|
+
break
|
|
420
|
+
except Exception:
|
|
421
|
+
pass
|
|
422
|
+
|
|
423
|
+
if matched:
|
|
424
|
+
passed += 1
|
|
425
|
+
else:
|
|
426
|
+
failed += 1
|
|
427
|
+
|
|
428
|
+
return (passed, failed)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def compute_checksum(data: bytes, algorithm: str, **kwargs: Any) -> int:
|
|
432
|
+
"""Compute checksum using specified algorithm.
|
|
433
|
+
|
|
434
|
+
: Checksum and CRC Field Detection
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
data: Data to checksum
|
|
438
|
+
algorithm: Algorithm name
|
|
439
|
+
**kwargs: Algorithm-specific parameters
|
|
440
|
+
|
|
441
|
+
Returns:
|
|
442
|
+
Computed checksum value
|
|
443
|
+
|
|
444
|
+
Raises:
|
|
445
|
+
ValueError: If algorithm is unknown
|
|
446
|
+
|
|
447
|
+
Example:
|
|
448
|
+
>>> compute_checksum(b'ABC', 'xor')
|
|
449
|
+
2
|
|
450
|
+
"""
|
|
451
|
+
if algorithm == "xor":
|
|
452
|
+
return xor_checksum(data)
|
|
453
|
+
elif algorithm == "sum8":
|
|
454
|
+
return sum8(data)
|
|
455
|
+
elif algorithm == "sum16_big":
|
|
456
|
+
return sum16(data, endian="big")
|
|
457
|
+
elif algorithm == "sum16_little":
|
|
458
|
+
return sum16(data, endian="little")
|
|
459
|
+
elif algorithm == "crc8":
|
|
460
|
+
poly = kwargs.get("poly", 0x07)
|
|
461
|
+
init = kwargs.get("init", 0x00)
|
|
462
|
+
return crc8(data, poly=poly, init=init)
|
|
463
|
+
elif algorithm == "crc16_ccitt":
|
|
464
|
+
init = kwargs.get("init", 0xFFFF)
|
|
465
|
+
return crc16_ccitt(data, init=init)
|
|
466
|
+
elif algorithm == "crc16_ibm":
|
|
467
|
+
init = kwargs.get("init", 0x0000)
|
|
468
|
+
return crc16_ibm(data, init=init)
|
|
469
|
+
elif algorithm == "crc32":
|
|
470
|
+
return crc32(data)
|
|
471
|
+
else:
|
|
472
|
+
raise ValueError(f"Unknown checksum algorithm: {algorithm}")
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def crc8(data: bytes, poly: int = 0x07, init: int = 0x00) -> int:
|
|
476
|
+
"""Calculate CRC-8.
|
|
477
|
+
|
|
478
|
+
: Checksum and CRC Field Detection
|
|
479
|
+
|
|
480
|
+
Standard CRC-8 with configurable polynomial.
|
|
481
|
+
|
|
482
|
+
Args:
|
|
483
|
+
data: Data to checksum
|
|
484
|
+
poly: Polynomial (default: 0x07)
|
|
485
|
+
init: Initial value (default: 0x00)
|
|
486
|
+
|
|
487
|
+
Returns:
|
|
488
|
+
CRC-8 value (0-255)
|
|
489
|
+
|
|
490
|
+
Example:
|
|
491
|
+
>>> crc8(b'123456789')
|
|
492
|
+
244
|
|
493
|
+
"""
|
|
494
|
+
crc = init
|
|
495
|
+
for byte in data:
|
|
496
|
+
crc ^= byte
|
|
497
|
+
for _ in range(8):
|
|
498
|
+
if crc & 0x80:
|
|
499
|
+
crc = (crc << 1) ^ poly
|
|
500
|
+
else:
|
|
501
|
+
crc = crc << 1
|
|
502
|
+
crc &= 0xFF
|
|
503
|
+
return crc
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def crc16_ccitt(data: bytes, init: int = 0xFFFF) -> int:
|
|
507
|
+
"""Calculate CRC-16-CCITT.
|
|
508
|
+
|
|
509
|
+
: Checksum and CRC Field Detection
|
|
510
|
+
|
|
511
|
+
CCITT polynomial: 0x1021
|
|
512
|
+
|
|
513
|
+
Args:
|
|
514
|
+
data: Data to checksum
|
|
515
|
+
init: Initial value (default: 0xFFFF)
|
|
516
|
+
|
|
517
|
+
Returns:
|
|
518
|
+
CRC-16 value (0-65535)
|
|
519
|
+
|
|
520
|
+
Example:
|
|
521
|
+
>>> crc16_ccitt(b'123456789')
|
|
522
|
+
10673
|
|
523
|
+
"""
|
|
524
|
+
poly = 0x1021
|
|
525
|
+
crc = init
|
|
526
|
+
|
|
527
|
+
for byte in data:
|
|
528
|
+
crc ^= byte << 8
|
|
529
|
+
for _ in range(8):
|
|
530
|
+
if crc & 0x8000:
|
|
531
|
+
crc = (crc << 1) ^ poly
|
|
532
|
+
else:
|
|
533
|
+
crc = crc << 1
|
|
534
|
+
crc &= 0xFFFF
|
|
535
|
+
|
|
536
|
+
return crc
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def crc16_ibm(data: bytes, init: int = 0x0000) -> int:
|
|
540
|
+
"""Calculate CRC-16-IBM (also known as CRC-16-ANSI).
|
|
541
|
+
|
|
542
|
+
: Checksum and CRC Field Detection
|
|
543
|
+
|
|
544
|
+
IBM polynomial: 0x8005 (reversed: 0xA001)
|
|
545
|
+
|
|
546
|
+
Args:
|
|
547
|
+
data: Data to checksum
|
|
548
|
+
init: Initial value (default: 0x0000)
|
|
549
|
+
|
|
550
|
+
Returns:
|
|
551
|
+
CRC-16 value (0-65535)
|
|
552
|
+
|
|
553
|
+
Example:
|
|
554
|
+
>>> crc16_ibm(b'123456789')
|
|
555
|
+
47933
|
|
556
|
+
"""
|
|
557
|
+
poly = 0xA001 # Reversed polynomial for LSB-first
|
|
558
|
+
crc = init
|
|
559
|
+
|
|
560
|
+
for byte in data:
|
|
561
|
+
crc ^= byte
|
|
562
|
+
for _ in range(8):
|
|
563
|
+
if crc & 0x0001:
|
|
564
|
+
crc = (crc >> 1) ^ poly
|
|
565
|
+
else:
|
|
566
|
+
crc = crc >> 1
|
|
567
|
+
|
|
568
|
+
return crc
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def crc32(data: bytes) -> int:
|
|
572
|
+
"""Calculate CRC-32 (IEEE 802.3).
|
|
573
|
+
|
|
574
|
+
: Checksum and CRC Field Detection
|
|
575
|
+
|
|
576
|
+
Standard CRC-32 as used in Ethernet, ZIP, etc.
|
|
577
|
+
|
|
578
|
+
Args:
|
|
579
|
+
data: Data to checksum
|
|
580
|
+
|
|
581
|
+
Returns:
|
|
582
|
+
CRC-32 value (0-4294967295)
|
|
583
|
+
|
|
584
|
+
Example:
|
|
585
|
+
>>> crc32(b'123456789')
|
|
586
|
+
3421780262
|
|
587
|
+
"""
|
|
588
|
+
poly = 0xEDB88320 # Reversed polynomial
|
|
589
|
+
crc = 0xFFFFFFFF
|
|
590
|
+
|
|
591
|
+
for byte in data:
|
|
592
|
+
crc ^= byte
|
|
593
|
+
for _ in range(8):
|
|
594
|
+
if crc & 0x00000001:
|
|
595
|
+
crc = (crc >> 1) ^ poly
|
|
596
|
+
else:
|
|
597
|
+
crc = crc >> 1
|
|
598
|
+
|
|
599
|
+
return crc ^ 0xFFFFFFFF
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
def sum8(data: bytes) -> int:
|
|
603
|
+
"""Calculate 8-bit sum checksum.
|
|
604
|
+
|
|
605
|
+
: Checksum and CRC Field Detection
|
|
606
|
+
|
|
607
|
+
Simple sum of all bytes, modulo 256.
|
|
608
|
+
|
|
609
|
+
Args:
|
|
610
|
+
data: Data to checksum
|
|
611
|
+
|
|
612
|
+
Returns:
|
|
613
|
+
Sum modulo 256 (0-255)
|
|
614
|
+
|
|
615
|
+
Example:
|
|
616
|
+
>>> sum8(b'ABC')
|
|
617
|
+
198
|
|
618
|
+
"""
|
|
619
|
+
return sum(data) & 0xFF
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def sum16(data: bytes, endian: Literal["big", "little"] = "big") -> int:
|
|
623
|
+
"""Calculate 16-bit sum checksum.
|
|
624
|
+
|
|
625
|
+
: Checksum and CRC Field Detection
|
|
626
|
+
|
|
627
|
+
Sum of 16-bit words with configurable endianness.
|
|
628
|
+
|
|
629
|
+
Args:
|
|
630
|
+
data: Data to checksum
|
|
631
|
+
endian: Byte order ('big' or 'little', default: 'big')
|
|
632
|
+
|
|
633
|
+
Returns:
|
|
634
|
+
Sum modulo 65536 (0-65535)
|
|
635
|
+
|
|
636
|
+
Example:
|
|
637
|
+
>>> sum16(b'ABCD', endian='big')
|
|
638
|
+
33923
|
|
639
|
+
"""
|
|
640
|
+
total = 0
|
|
641
|
+
|
|
642
|
+
# Process 16-bit words
|
|
643
|
+
for i in range(0, len(data) - 1, 2):
|
|
644
|
+
if endian == "big":
|
|
645
|
+
word = (data[i] << 8) | data[i + 1]
|
|
646
|
+
else:
|
|
647
|
+
word = (data[i + 1] << 8) | data[i]
|
|
648
|
+
total += word
|
|
649
|
+
|
|
650
|
+
# Handle odd byte
|
|
651
|
+
if len(data) % 2 == 1:
|
|
652
|
+
if endian == "big":
|
|
653
|
+
total += data[-1] << 8
|
|
654
|
+
else:
|
|
655
|
+
total += data[-1]
|
|
656
|
+
|
|
657
|
+
return total & 0xFFFF
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
def xor_checksum(data: bytes) -> int:
|
|
661
|
+
"""Calculate XOR checksum.
|
|
662
|
+
|
|
663
|
+
: Checksum and CRC Field Detection
|
|
664
|
+
|
|
665
|
+
XOR of all bytes.
|
|
666
|
+
|
|
667
|
+
Args:
|
|
668
|
+
data: Data to checksum
|
|
669
|
+
|
|
670
|
+
Returns:
|
|
671
|
+
XOR result (0-255)
|
|
672
|
+
|
|
673
|
+
Example:
|
|
674
|
+
>>> xor_checksum(b'ABC')
|
|
675
|
+
2
|
|
676
|
+
"""
|
|
677
|
+
result = 0
|
|
678
|
+
for byte in data:
|
|
679
|
+
result ^= byte
|
|
680
|
+
return result
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
@dataclass
|
|
684
|
+
class ChecksumDetectionResult:
|
|
685
|
+
"""Result of checksum detection.
|
|
686
|
+
|
|
687
|
+
Attributes:
|
|
688
|
+
has_checksum: Whether a checksum was detected.
|
|
689
|
+
offset: Byte offset of the checksum field (None if not detected).
|
|
690
|
+
size: Size of the checksum field in bytes (None if not detected).
|
|
691
|
+
algorithm: Identified algorithm name (None if not identified).
|
|
692
|
+
confidence: Detection confidence (0-1).
|
|
693
|
+
candidates: All candidate positions found.
|
|
694
|
+
scope_start: Start of checksummed region (None if not identified).
|
|
695
|
+
scope_end: End of checksummed region (None if not identified).
|
|
696
|
+
init_value: Initial value for CRC algorithms (None if not applicable).
|
|
697
|
+
"""
|
|
698
|
+
|
|
699
|
+
has_checksum: bool
|
|
700
|
+
offset: int | None = None
|
|
701
|
+
size: int | None = None
|
|
702
|
+
algorithm: str | None = None
|
|
703
|
+
confidence: float = 0.0
|
|
704
|
+
candidates: list[ChecksumCandidate] = field(default_factory=list)
|
|
705
|
+
scope_start: int | None = None
|
|
706
|
+
scope_end: int | None = None
|
|
707
|
+
init_value: int | None = None
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
class ChecksumDetector:
|
|
711
|
+
"""Object-oriented wrapper for checksum detection functionality.
|
|
712
|
+
|
|
713
|
+
Provides a class-based interface for checksum detection operations,
|
|
714
|
+
wrapping the functional API for consistency with test expectations.
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
Example:
|
|
719
|
+
>>> detector = ChecksumDetector()
|
|
720
|
+
>>> result = detector.detect_checksum_field(messages)
|
|
721
|
+
>>> if result.has_checksum:
|
|
722
|
+
... print(f"Checksum at offset {result.offset}")
|
|
723
|
+
"""
|
|
724
|
+
|
|
725
|
+
def __init__(self, correlation_threshold: float = 0.5):
|
|
726
|
+
"""Initialize checksum detector.
|
|
727
|
+
|
|
728
|
+
Args:
|
|
729
|
+
correlation_threshold: Minimum correlation for detection.
|
|
730
|
+
"""
|
|
731
|
+
self.correlation_threshold = correlation_threshold
|
|
732
|
+
self._detection_result: ChecksumDetectionResult | None = None
|
|
733
|
+
self._messages: list[DataType] = []
|
|
734
|
+
|
|
735
|
+
def detect_checksum_field(self, messages: list[DataType]) -> ChecksumDetectionResult:
|
|
736
|
+
"""Detect checksum field in messages.
|
|
737
|
+
|
|
738
|
+
Args:
|
|
739
|
+
messages: List of messages to analyze.
|
|
740
|
+
|
|
741
|
+
Returns:
|
|
742
|
+
ChecksumDetectionResult with detection results.
|
|
743
|
+
|
|
744
|
+
Example:
|
|
745
|
+
>>> detector = ChecksumDetector()
|
|
746
|
+
>>> result = detector.detect_checksum_field(messages)
|
|
747
|
+
"""
|
|
748
|
+
self._messages = messages
|
|
749
|
+
candidates = detect_checksum_fields(messages)
|
|
750
|
+
|
|
751
|
+
if not candidates:
|
|
752
|
+
self._detection_result = ChecksumDetectionResult(has_checksum=False, confidence=0.0)
|
|
753
|
+
return self._detection_result
|
|
754
|
+
|
|
755
|
+
# Filter by correlation threshold
|
|
756
|
+
good_candidates = [c for c in candidates if c.correlation >= self.correlation_threshold]
|
|
757
|
+
|
|
758
|
+
if not good_candidates:
|
|
759
|
+
# Report no checksum with low confidence if candidates exist but none pass threshold
|
|
760
|
+
max_correlation = max(c.correlation for c in candidates) if candidates else 0.0
|
|
761
|
+
self._detection_result = ChecksumDetectionResult(
|
|
762
|
+
has_checksum=False, candidates=candidates, confidence=max_correlation
|
|
763
|
+
)
|
|
764
|
+
return self._detection_result
|
|
765
|
+
|
|
766
|
+
# Use best candidate, preferring trailer checksums when correlation is similar
|
|
767
|
+
best = good_candidates[0]
|
|
768
|
+
|
|
769
|
+
# Check if there's a trailer checksum with similar correlation
|
|
770
|
+
for candidate in good_candidates[1:]:
|
|
771
|
+
if candidate.position == "trailer" and best.position == "header":
|
|
772
|
+
# Prefer trailer if correlation is within 5% of header
|
|
773
|
+
if candidate.correlation >= best.correlation * 0.95:
|
|
774
|
+
best = candidate
|
|
775
|
+
break
|
|
776
|
+
|
|
777
|
+
# Try to identify algorithm for best candidate
|
|
778
|
+
algorithm_match = identify_checksum_algorithm(messages, best.offset, best.size)
|
|
779
|
+
|
|
780
|
+
# If algorithm identification fails, try other high-correlation candidates
|
|
781
|
+
if algorithm_match is None and len(good_candidates) > 1:
|
|
782
|
+
for candidate in good_candidates[1:]:
|
|
783
|
+
# Skip if correlation is too much lower
|
|
784
|
+
if candidate.correlation < best.correlation * 0.9:
|
|
785
|
+
break
|
|
786
|
+
|
|
787
|
+
alt_match = identify_checksum_algorithm(messages, candidate.offset, candidate.size)
|
|
788
|
+
if alt_match is not None:
|
|
789
|
+
# Found a candidate with identifiable algorithm
|
|
790
|
+
best = candidate
|
|
791
|
+
algorithm_match = alt_match
|
|
792
|
+
break
|
|
793
|
+
|
|
794
|
+
# Reduce confidence if algorithm couldn't be identified
|
|
795
|
+
# High correlation but no identifiable algorithm suggests false positive
|
|
796
|
+
final_confidence = best.correlation
|
|
797
|
+
if algorithm_match is None:
|
|
798
|
+
final_confidence = best.correlation * 0.3 # Penalize unidentified algorithms
|
|
799
|
+
|
|
800
|
+
self._detection_result = ChecksumDetectionResult(
|
|
801
|
+
has_checksum=True,
|
|
802
|
+
offset=best.offset,
|
|
803
|
+
size=best.size,
|
|
804
|
+
algorithm=algorithm_match.algorithm if algorithm_match else None,
|
|
805
|
+
confidence=final_confidence,
|
|
806
|
+
candidates=good_candidates,
|
|
807
|
+
scope_start=algorithm_match.scope_start if algorithm_match else None,
|
|
808
|
+
scope_end=algorithm_match.scope_end if algorithm_match else None,
|
|
809
|
+
init_value=algorithm_match.init_value if algorithm_match else None,
|
|
810
|
+
)
|
|
811
|
+
return self._detection_result
|
|
812
|
+
|
|
813
|
+
def identify_algorithm(
|
|
814
|
+
self, messages: list[DataType], offset: int, size: int | None = None
|
|
815
|
+
) -> ChecksumMatch | None:
|
|
816
|
+
"""Identify checksum algorithm at given offset.
|
|
817
|
+
|
|
818
|
+
Args:
|
|
819
|
+
messages: List of messages.
|
|
820
|
+
offset: Checksum field offset.
|
|
821
|
+
size: Field size (auto-detect if None).
|
|
822
|
+
|
|
823
|
+
Returns:
|
|
824
|
+
ChecksumMatch or None if no match found.
|
|
825
|
+
"""
|
|
826
|
+
return identify_checksum_algorithm(messages, offset, size)
|
|
827
|
+
|
|
828
|
+
def verify(
|
|
829
|
+
self, messages: list[DataType], algorithm: str, offset: int, **kwargs: Any
|
|
830
|
+
) -> tuple[int, int]:
|
|
831
|
+
"""Verify checksums in messages.
|
|
832
|
+
|
|
833
|
+
Args:
|
|
834
|
+
messages: List of messages.
|
|
835
|
+
algorithm: Checksum algorithm name.
|
|
836
|
+
offset: Checksum field offset.
|
|
837
|
+
**kwargs: Algorithm-specific parameters.
|
|
838
|
+
|
|
839
|
+
Returns:
|
|
840
|
+
Tuple of (passed, failed) counts.
|
|
841
|
+
"""
|
|
842
|
+
return verify_checksums(messages, algorithm, offset, **kwargs)
|
|
843
|
+
|
|
844
|
+
def verify_checksum(self, message: DataType) -> bool:
|
|
845
|
+
"""Verify checksum for a single message.
|
|
846
|
+
|
|
847
|
+
Uses previously detected checksum parameters if available.
|
|
848
|
+
|
|
849
|
+
Args:
|
|
850
|
+
message: Single message to verify.
|
|
851
|
+
|
|
852
|
+
Returns:
|
|
853
|
+
True if checksum is valid, False otherwise.
|
|
854
|
+
|
|
855
|
+
Example:
|
|
856
|
+
>>> detector = ChecksumDetector()
|
|
857
|
+
>>> detector.detect_checksum_field(messages)
|
|
858
|
+
>>> is_valid = detector.verify_checksum(messages[0])
|
|
859
|
+
"""
|
|
860
|
+
if self._detection_result is None or not self._detection_result.has_checksum:
|
|
861
|
+
# Try to detect checksum from the single message
|
|
862
|
+
return False
|
|
863
|
+
|
|
864
|
+
offset = self._detection_result.offset
|
|
865
|
+
size = self._detection_result.size
|
|
866
|
+
|
|
867
|
+
if offset is None or size is None:
|
|
868
|
+
return False
|
|
869
|
+
|
|
870
|
+
# Convert message to bytes
|
|
871
|
+
if isinstance(message, np.ndarray):
|
|
872
|
+
msg = message.tobytes() if message.dtype == np.uint8 else bytes(message.flatten())
|
|
873
|
+
else:
|
|
874
|
+
msg = bytes(message)
|
|
875
|
+
|
|
876
|
+
if self._detection_result.algorithm is None:
|
|
877
|
+
# No algorithm identified - try common ones
|
|
878
|
+
if size == 1:
|
|
879
|
+
algorithms = ["xor", "sum8"]
|
|
880
|
+
elif size == 2:
|
|
881
|
+
algorithms = ["crc16_ccitt", "crc16_ibm", "sum16_big", "sum16_little"]
|
|
882
|
+
elif size == 4:
|
|
883
|
+
algorithms = ["crc32"]
|
|
884
|
+
else:
|
|
885
|
+
algorithms = ["xor", "sum8", "crc16_ccitt", "crc16_ibm", "sum16_big", "crc32"]
|
|
886
|
+
|
|
887
|
+
# Try each algorithm
|
|
888
|
+
for algo in algorithms:
|
|
889
|
+
passed, _failed = verify_checksums([msg], algo, offset)
|
|
890
|
+
if passed == 1:
|
|
891
|
+
return True
|
|
892
|
+
|
|
893
|
+
return False
|
|
894
|
+
|
|
895
|
+
# Use identified algorithm
|
|
896
|
+
passed, _failed = verify_checksums(
|
|
897
|
+
[msg],
|
|
898
|
+
self._detection_result.algorithm,
|
|
899
|
+
self._detection_result.offset or 0,
|
|
900
|
+
scope_start=self._detection_result.scope_start or 0,
|
|
901
|
+
scope_end=self._detection_result.scope_end,
|
|
902
|
+
init_value=self._detection_result.init_value,
|
|
903
|
+
)
|
|
904
|
+
return passed == 1
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
__all__ = [
|
|
908
|
+
"ChecksumCandidate",
|
|
909
|
+
"ChecksumDetectionResult",
|
|
910
|
+
"ChecksumDetector",
|
|
911
|
+
"ChecksumMatch",
|
|
912
|
+
"compute_checksum",
|
|
913
|
+
"crc8",
|
|
914
|
+
"crc16_ccitt",
|
|
915
|
+
"crc16_ibm",
|
|
916
|
+
"crc32",
|
|
917
|
+
"detect_checksum_fields",
|
|
918
|
+
"identify_checksum_algorithm",
|
|
919
|
+
"sum8",
|
|
920
|
+
"sum16",
|
|
921
|
+
"verify_checksums",
|
|
922
|
+
"xor_checksum",
|
|
923
|
+
]
|