oscura 0.5.0__py3-none-any.whl → 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oscura/__init__.py +169 -167
- oscura/analyzers/__init__.py +3 -0
- oscura/analyzers/classification.py +659 -0
- oscura/analyzers/digital/__init__.py +0 -48
- oscura/analyzers/digital/edges.py +325 -65
- oscura/analyzers/digital/extraction.py +0 -195
- oscura/analyzers/digital/quality.py +293 -166
- oscura/analyzers/digital/timing.py +260 -115
- oscura/analyzers/digital/timing_numba.py +334 -0
- oscura/analyzers/entropy.py +605 -0
- oscura/analyzers/eye/diagram.py +176 -109
- oscura/analyzers/eye/metrics.py +5 -5
- oscura/analyzers/jitter/__init__.py +6 -4
- oscura/analyzers/jitter/ber.py +52 -52
- oscura/analyzers/jitter/classification.py +156 -0
- oscura/analyzers/jitter/decomposition.py +163 -113
- oscura/analyzers/jitter/spectrum.py +80 -64
- oscura/analyzers/ml/__init__.py +39 -0
- oscura/analyzers/ml/features.py +600 -0
- oscura/analyzers/ml/signal_classifier.py +604 -0
- oscura/analyzers/packet/daq.py +246 -158
- oscura/analyzers/packet/parser.py +12 -1
- oscura/analyzers/packet/payload.py +50 -2110
- oscura/analyzers/packet/payload_analysis.py +361 -181
- oscura/analyzers/packet/payload_patterns.py +133 -70
- oscura/analyzers/packet/stream.py +84 -23
- oscura/analyzers/patterns/__init__.py +26 -5
- oscura/analyzers/patterns/anomaly_detection.py +908 -0
- oscura/analyzers/patterns/clustering.py +169 -108
- oscura/analyzers/patterns/clustering_optimized.py +227 -0
- oscura/analyzers/patterns/discovery.py +1 -1
- oscura/analyzers/patterns/matching.py +581 -197
- oscura/analyzers/patterns/pattern_mining.py +778 -0
- oscura/analyzers/patterns/periodic.py +121 -38
- oscura/analyzers/patterns/sequences.py +175 -78
- oscura/analyzers/power/conduction.py +1 -1
- oscura/analyzers/power/soa.py +6 -6
- oscura/analyzers/power/switching.py +250 -110
- oscura/analyzers/protocol/__init__.py +17 -1
- oscura/analyzers/protocols/__init__.py +1 -22
- oscura/analyzers/protocols/base.py +6 -6
- oscura/analyzers/protocols/ble/__init__.py +38 -0
- oscura/analyzers/protocols/ble/analyzer.py +809 -0
- oscura/analyzers/protocols/ble/uuids.py +288 -0
- oscura/analyzers/protocols/can.py +257 -127
- oscura/analyzers/protocols/can_fd.py +107 -80
- oscura/analyzers/protocols/flexray.py +139 -80
- oscura/analyzers/protocols/hdlc.py +93 -58
- oscura/analyzers/protocols/i2c.py +247 -106
- oscura/analyzers/protocols/i2s.py +138 -86
- oscura/analyzers/protocols/industrial/__init__.py +40 -0
- oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
- oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
- oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
- oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
- oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
- oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
- oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
- oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
- oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
- oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
- oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
- oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
- oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
- oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
- oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
- oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
- oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
- oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
- oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
- oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
- oscura/analyzers/protocols/jtag.py +180 -98
- oscura/analyzers/protocols/lin.py +219 -114
- oscura/analyzers/protocols/manchester.py +4 -4
- oscura/analyzers/protocols/onewire.py +253 -149
- oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
- oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
- oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
- oscura/analyzers/protocols/spi.py +192 -95
- oscura/analyzers/protocols/swd.py +321 -167
- oscura/analyzers/protocols/uart.py +267 -125
- oscura/analyzers/protocols/usb.py +235 -131
- oscura/analyzers/side_channel/power.py +17 -12
- oscura/analyzers/signal/__init__.py +15 -0
- oscura/analyzers/signal/timing_analysis.py +1086 -0
- oscura/analyzers/signal_integrity/__init__.py +4 -1
- oscura/analyzers/signal_integrity/sparams.py +2 -19
- oscura/analyzers/spectral/chunked.py +129 -60
- oscura/analyzers/spectral/chunked_fft.py +300 -94
- oscura/analyzers/spectral/chunked_wavelet.py +100 -80
- oscura/analyzers/statistical/checksum.py +376 -217
- oscura/analyzers/statistical/classification.py +229 -107
- oscura/analyzers/statistical/entropy.py +78 -53
- oscura/analyzers/statistics/correlation.py +407 -211
- oscura/analyzers/statistics/outliers.py +2 -2
- oscura/analyzers/statistics/streaming.py +30 -5
- oscura/analyzers/validation.py +216 -101
- oscura/analyzers/waveform/measurements.py +9 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
- oscura/analyzers/waveform/spectral.py +500 -228
- oscura/api/__init__.py +31 -5
- oscura/api/dsl/__init__.py +582 -0
- oscura/{dsl → api/dsl}/commands.py +43 -76
- oscura/{dsl → api/dsl}/interpreter.py +26 -51
- oscura/{dsl → api/dsl}/parser.py +107 -77
- oscura/{dsl → api/dsl}/repl.py +2 -2
- oscura/api/dsl.py +1 -1
- oscura/{integrations → api/integrations}/__init__.py +1 -1
- oscura/{integrations → api/integrations}/llm.py +201 -102
- oscura/api/operators.py +3 -3
- oscura/api/optimization.py +144 -30
- oscura/api/rest_server.py +921 -0
- oscura/api/server/__init__.py +17 -0
- oscura/api/server/dashboard.py +850 -0
- oscura/api/server/static/README.md +34 -0
- oscura/api/server/templates/base.html +181 -0
- oscura/api/server/templates/export.html +120 -0
- oscura/api/server/templates/home.html +284 -0
- oscura/api/server/templates/protocols.html +58 -0
- oscura/api/server/templates/reports.html +43 -0
- oscura/api/server/templates/session_detail.html +89 -0
- oscura/api/server/templates/sessions.html +83 -0
- oscura/api/server/templates/waveforms.html +73 -0
- oscura/automotive/__init__.py +8 -1
- oscura/automotive/can/__init__.py +10 -0
- oscura/automotive/can/checksum.py +3 -1
- oscura/automotive/can/dbc_generator.py +590 -0
- oscura/automotive/can/message_wrapper.py +121 -74
- oscura/automotive/can/patterns.py +98 -21
- oscura/automotive/can/session.py +292 -56
- oscura/automotive/can/state_machine.py +6 -3
- oscura/automotive/can/stimulus_response.py +97 -75
- oscura/automotive/dbc/__init__.py +10 -2
- oscura/automotive/dbc/generator.py +84 -56
- oscura/automotive/dbc/parser.py +6 -6
- oscura/automotive/dtc/data.json +2763 -0
- oscura/automotive/dtc/database.py +2 -2
- oscura/automotive/flexray/__init__.py +31 -0
- oscura/automotive/flexray/analyzer.py +504 -0
- oscura/automotive/flexray/crc.py +185 -0
- oscura/automotive/flexray/fibex.py +449 -0
- oscura/automotive/j1939/__init__.py +45 -8
- oscura/automotive/j1939/analyzer.py +605 -0
- oscura/automotive/j1939/spns.py +326 -0
- oscura/automotive/j1939/transport.py +306 -0
- oscura/automotive/lin/__init__.py +47 -0
- oscura/automotive/lin/analyzer.py +612 -0
- oscura/automotive/loaders/blf.py +13 -2
- oscura/automotive/loaders/csv_can.py +143 -72
- oscura/automotive/loaders/dispatcher.py +50 -2
- oscura/automotive/loaders/mdf.py +86 -45
- oscura/automotive/loaders/pcap.py +111 -61
- oscura/automotive/uds/__init__.py +4 -0
- oscura/automotive/uds/analyzer.py +725 -0
- oscura/automotive/uds/decoder.py +140 -58
- oscura/automotive/uds/models.py +7 -1
- oscura/automotive/visualization.py +1 -1
- oscura/cli/analyze.py +348 -0
- oscura/cli/batch.py +142 -122
- oscura/cli/benchmark.py +275 -0
- oscura/cli/characterize.py +137 -82
- oscura/cli/compare.py +224 -131
- oscura/cli/completion.py +250 -0
- oscura/cli/config_cmd.py +361 -0
- oscura/cli/decode.py +164 -87
- oscura/cli/export.py +286 -0
- oscura/cli/main.py +115 -31
- oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
- oscura/{onboarding → cli/onboarding}/help.py +80 -58
- oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
- oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
- oscura/cli/progress.py +147 -0
- oscura/cli/shell.py +157 -135
- oscura/cli/validate_cmd.py +204 -0
- oscura/cli/visualize.py +158 -0
- oscura/convenience.py +125 -79
- oscura/core/__init__.py +4 -2
- oscura/core/backend_selector.py +3 -3
- oscura/core/cache.py +126 -15
- oscura/core/cancellation.py +1 -1
- oscura/{config → core/config}/__init__.py +20 -11
- oscura/{config → core/config}/defaults.py +1 -1
- oscura/{config → core/config}/loader.py +7 -5
- oscura/{config → core/config}/memory.py +5 -5
- oscura/{config → core/config}/migration.py +1 -1
- oscura/{config → core/config}/pipeline.py +99 -23
- oscura/{config → core/config}/preferences.py +1 -1
- oscura/{config → core/config}/protocol.py +3 -3
- oscura/{config → core/config}/schema.py +426 -272
- oscura/{config → core/config}/settings.py +1 -1
- oscura/{config → core/config}/thresholds.py +195 -153
- oscura/core/correlation.py +5 -6
- oscura/core/cross_domain.py +0 -2
- oscura/core/debug.py +9 -5
- oscura/{extensibility → core/extensibility}/docs.py +158 -70
- oscura/{extensibility → core/extensibility}/extensions.py +160 -76
- oscura/{extensibility → core/extensibility}/logging.py +1 -1
- oscura/{extensibility → core/extensibility}/measurements.py +1 -1
- oscura/{extensibility → core/extensibility}/plugins.py +1 -1
- oscura/{extensibility → core/extensibility}/templates.py +73 -3
- oscura/{extensibility → core/extensibility}/validation.py +1 -1
- oscura/core/gpu_backend.py +11 -7
- oscura/core/log_query.py +101 -11
- oscura/core/logging.py +126 -54
- oscura/core/logging_advanced.py +5 -5
- oscura/core/memory_limits.py +108 -70
- oscura/core/memory_monitor.py +2 -2
- oscura/core/memory_progress.py +7 -7
- oscura/core/memory_warnings.py +1 -1
- oscura/core/numba_backend.py +13 -13
- oscura/{plugins → core/plugins}/__init__.py +9 -9
- oscura/{plugins → core/plugins}/base.py +7 -7
- oscura/{plugins → core/plugins}/cli.py +3 -3
- oscura/{plugins → core/plugins}/discovery.py +186 -106
- oscura/{plugins → core/plugins}/lifecycle.py +1 -1
- oscura/{plugins → core/plugins}/manager.py +7 -7
- oscura/{plugins → core/plugins}/registry.py +3 -3
- oscura/{plugins → core/plugins}/versioning.py +1 -1
- oscura/core/progress.py +16 -1
- oscura/core/provenance.py +8 -2
- oscura/{schemas → core/schemas}/__init__.py +2 -2
- oscura/core/schemas/bus_configuration.json +322 -0
- oscura/core/schemas/device_mapping.json +182 -0
- oscura/core/schemas/packet_format.json +418 -0
- oscura/core/schemas/protocol_definition.json +363 -0
- oscura/core/types.py +4 -0
- oscura/core/uncertainty.py +3 -3
- oscura/correlation/__init__.py +52 -0
- oscura/correlation/multi_protocol.py +811 -0
- oscura/discovery/auto_decoder.py +117 -35
- oscura/discovery/comparison.py +191 -86
- oscura/discovery/quality_validator.py +155 -68
- oscura/discovery/signal_detector.py +196 -79
- oscura/export/__init__.py +18 -20
- oscura/export/kaitai_struct.py +513 -0
- oscura/export/scapy_layer.py +801 -0
- oscura/export/wireshark/README.md +15 -15
- oscura/export/wireshark/generator.py +1 -1
- oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
- oscura/export/wireshark_dissector.py +746 -0
- oscura/guidance/wizard.py +207 -111
- oscura/hardware/__init__.py +19 -0
- oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
- oscura/{acquisition → hardware/acquisition}/file.py +2 -2
- oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
- oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
- oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
- oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
- oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
- oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
- oscura/hardware/firmware/__init__.py +29 -0
- oscura/hardware/firmware/pattern_recognition.py +874 -0
- oscura/hardware/hal_detector.py +736 -0
- oscura/hardware/security/__init__.py +37 -0
- oscura/hardware/security/side_channel_detector.py +1126 -0
- oscura/inference/__init__.py +4 -0
- oscura/inference/active_learning/README.md +7 -7
- oscura/inference/active_learning/observation_table.py +4 -1
- oscura/inference/alignment.py +216 -123
- oscura/inference/bayesian.py +113 -33
- oscura/inference/crc_reverse.py +101 -55
- oscura/inference/logic.py +6 -2
- oscura/inference/message_format.py +342 -183
- oscura/inference/protocol.py +95 -44
- oscura/inference/protocol_dsl.py +180 -82
- oscura/inference/signal_intelligence.py +1439 -706
- oscura/inference/spectral.py +99 -57
- oscura/inference/state_machine.py +810 -158
- oscura/inference/stream.py +270 -110
- oscura/iot/__init__.py +34 -0
- oscura/iot/coap/__init__.py +32 -0
- oscura/iot/coap/analyzer.py +668 -0
- oscura/iot/coap/options.py +212 -0
- oscura/iot/lorawan/__init__.py +21 -0
- oscura/iot/lorawan/crypto.py +206 -0
- oscura/iot/lorawan/decoder.py +801 -0
- oscura/iot/lorawan/mac_commands.py +341 -0
- oscura/iot/mqtt/__init__.py +27 -0
- oscura/iot/mqtt/analyzer.py +999 -0
- oscura/iot/mqtt/properties.py +315 -0
- oscura/iot/zigbee/__init__.py +31 -0
- oscura/iot/zigbee/analyzer.py +615 -0
- oscura/iot/zigbee/security.py +153 -0
- oscura/iot/zigbee/zcl.py +349 -0
- oscura/jupyter/display.py +125 -45
- oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
- oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
- oscura/jupyter/exploratory/fuzzy.py +746 -0
- oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
- oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
- oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
- oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
- oscura/jupyter/exploratory/sync.py +612 -0
- oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
- oscura/jupyter/magic.py +4 -4
- oscura/{ui → jupyter/ui}/__init__.py +2 -2
- oscura/{ui → jupyter/ui}/formatters.py +3 -3
- oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
- oscura/loaders/__init__.py +171 -63
- oscura/loaders/binary.py +88 -1
- oscura/loaders/chipwhisperer.py +153 -137
- oscura/loaders/configurable.py +208 -86
- oscura/loaders/csv_loader.py +458 -215
- oscura/loaders/hdf5_loader.py +278 -119
- oscura/loaders/lazy.py +87 -54
- oscura/loaders/mmap_loader.py +1 -1
- oscura/loaders/numpy_loader.py +253 -116
- oscura/loaders/pcap.py +226 -151
- oscura/loaders/rigol.py +110 -49
- oscura/loaders/sigrok.py +201 -78
- oscura/loaders/tdms.py +81 -58
- oscura/loaders/tektronix.py +291 -174
- oscura/loaders/touchstone.py +182 -87
- oscura/loaders/vcd.py +215 -117
- oscura/loaders/wav.py +155 -68
- oscura/reporting/__init__.py +9 -7
- oscura/reporting/analyze.py +352 -146
- oscura/reporting/argument_preparer.py +69 -14
- oscura/reporting/auto_report.py +97 -61
- oscura/reporting/batch.py +131 -58
- oscura/reporting/chart_selection.py +57 -45
- oscura/reporting/comparison.py +63 -17
- oscura/reporting/content/executive.py +76 -24
- oscura/reporting/core_formats/multi_format.py +11 -8
- oscura/reporting/engine.py +312 -158
- oscura/reporting/enhanced_reports.py +949 -0
- oscura/reporting/export.py +86 -43
- oscura/reporting/formatting/numbers.py +69 -42
- oscura/reporting/html.py +139 -58
- oscura/reporting/index.py +137 -65
- oscura/reporting/output.py +158 -67
- oscura/reporting/pdf.py +67 -102
- oscura/reporting/plots.py +191 -112
- oscura/reporting/sections.py +88 -47
- oscura/reporting/standards.py +104 -61
- oscura/reporting/summary_generator.py +75 -55
- oscura/reporting/tables.py +138 -54
- oscura/reporting/templates/enhanced/protocol_re.html +525 -0
- oscura/reporting/templates/index.md +13 -13
- oscura/sessions/__init__.py +14 -23
- oscura/sessions/base.py +3 -3
- oscura/sessions/blackbox.py +106 -10
- oscura/sessions/generic.py +2 -2
- oscura/sessions/legacy.py +783 -0
- oscura/side_channel/__init__.py +63 -0
- oscura/side_channel/dpa.py +1025 -0
- oscura/utils/__init__.py +15 -1
- oscura/utils/autodetect.py +1 -5
- oscura/utils/bitwise.py +118 -0
- oscura/{builders → utils/builders}/__init__.py +1 -1
- oscura/{comparison → utils/comparison}/__init__.py +6 -6
- oscura/{comparison → utils/comparison}/compare.py +202 -101
- oscura/{comparison → utils/comparison}/golden.py +83 -63
- oscura/{comparison → utils/comparison}/limits.py +313 -89
- oscura/{comparison → utils/comparison}/mask.py +151 -45
- oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
- oscura/{comparison → utils/comparison}/visualization.py +147 -89
- oscura/{component → utils/component}/__init__.py +3 -3
- oscura/{component → utils/component}/impedance.py +122 -58
- oscura/{component → utils/component}/reactive.py +165 -168
- oscura/{component → utils/component}/transmission_line.py +3 -3
- oscura/{filtering → utils/filtering}/__init__.py +6 -6
- oscura/{filtering → utils/filtering}/base.py +1 -1
- oscura/{filtering → utils/filtering}/convenience.py +2 -2
- oscura/{filtering → utils/filtering}/design.py +169 -93
- oscura/{filtering → utils/filtering}/filters.py +2 -2
- oscura/{filtering → utils/filtering}/introspection.py +2 -2
- oscura/utils/geometry.py +31 -0
- oscura/utils/imports.py +184 -0
- oscura/utils/lazy.py +1 -1
- oscura/{math → utils/math}/__init__.py +2 -2
- oscura/{math → utils/math}/arithmetic.py +114 -48
- oscura/{math → utils/math}/interpolation.py +139 -106
- oscura/utils/memory.py +129 -66
- oscura/utils/memory_advanced.py +92 -9
- oscura/utils/memory_extensions.py +10 -8
- oscura/{optimization → utils/optimization}/__init__.py +1 -1
- oscura/{optimization → utils/optimization}/search.py +2 -2
- oscura/utils/performance/__init__.py +58 -0
- oscura/utils/performance/caching.py +889 -0
- oscura/utils/performance/lsh_clustering.py +333 -0
- oscura/utils/performance/memory_optimizer.py +699 -0
- oscura/utils/performance/optimizations.py +675 -0
- oscura/utils/performance/parallel.py +654 -0
- oscura/utils/performance/profiling.py +661 -0
- oscura/{pipeline → utils/pipeline}/base.py +1 -1
- oscura/{pipeline → utils/pipeline}/composition.py +11 -3
- oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
- oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
- oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
- oscura/{search → utils/search}/__init__.py +3 -3
- oscura/{search → utils/search}/anomaly.py +188 -58
- oscura/utils/search/context.py +294 -0
- oscura/{search → utils/search}/pattern.py +138 -10
- oscura/utils/serial.py +51 -0
- oscura/utils/storage/__init__.py +61 -0
- oscura/utils/storage/database.py +1166 -0
- oscura/{streaming → utils/streaming}/chunked.py +302 -143
- oscura/{streaming → utils/streaming}/progressive.py +1 -1
- oscura/{streaming → utils/streaming}/realtime.py +3 -2
- oscura/{triggering → utils/triggering}/__init__.py +6 -6
- oscura/{triggering → utils/triggering}/base.py +6 -6
- oscura/{triggering → utils/triggering}/edge.py +2 -2
- oscura/{triggering → utils/triggering}/pattern.py +2 -2
- oscura/{triggering → utils/triggering}/pulse.py +115 -74
- oscura/{triggering → utils/triggering}/window.py +2 -2
- oscura/utils/validation.py +32 -0
- oscura/validation/__init__.py +121 -0
- oscura/{compliance → validation/compliance}/__init__.py +5 -5
- oscura/{compliance → validation/compliance}/advanced.py +5 -5
- oscura/{compliance → validation/compliance}/masks.py +1 -1
- oscura/{compliance → validation/compliance}/reporting.py +127 -53
- oscura/{compliance → validation/compliance}/testing.py +114 -52
- oscura/validation/compliance_tests.py +915 -0
- oscura/validation/fuzzer.py +990 -0
- oscura/validation/grammar_tests.py +596 -0
- oscura/validation/grammar_validator.py +904 -0
- oscura/validation/hil_testing.py +977 -0
- oscura/{quality → validation/quality}/__init__.py +4 -4
- oscura/{quality → validation/quality}/ensemble.py +251 -171
- oscura/{quality → validation/quality}/explainer.py +3 -3
- oscura/{quality → validation/quality}/scoring.py +1 -1
- oscura/{quality → validation/quality}/warnings.py +4 -4
- oscura/validation/regression_suite.py +808 -0
- oscura/validation/replay.py +788 -0
- oscura/{testing → validation/testing}/__init__.py +2 -2
- oscura/{testing → validation/testing}/synthetic.py +5 -5
- oscura/visualization/__init__.py +9 -0
- oscura/visualization/accessibility.py +1 -1
- oscura/visualization/annotations.py +64 -67
- oscura/visualization/colors.py +7 -7
- oscura/visualization/digital.py +180 -81
- oscura/visualization/eye.py +236 -85
- oscura/visualization/interactive.py +320 -143
- oscura/visualization/jitter.py +587 -247
- oscura/visualization/layout.py +169 -134
- oscura/visualization/optimization.py +103 -52
- oscura/visualization/palettes.py +1 -1
- oscura/visualization/power.py +427 -211
- oscura/visualization/power_extended.py +626 -297
- oscura/visualization/presets.py +2 -0
- oscura/visualization/protocols.py +495 -181
- oscura/visualization/render.py +79 -63
- oscura/visualization/reverse_engineering.py +171 -124
- oscura/visualization/signal_integrity.py +460 -279
- oscura/visualization/specialized.py +190 -100
- oscura/visualization/spectral.py +670 -255
- oscura/visualization/thumbnails.py +166 -137
- oscura/visualization/waveform.py +150 -63
- oscura/workflows/__init__.py +3 -0
- oscura/{batch → workflows/batch}/__init__.py +5 -5
- oscura/{batch → workflows/batch}/advanced.py +150 -75
- oscura/workflows/batch/aggregate.py +531 -0
- oscura/workflows/batch/analyze.py +236 -0
- oscura/{batch → workflows/batch}/logging.py +2 -2
- oscura/{batch → workflows/batch}/metrics.py +1 -1
- oscura/workflows/complete_re.py +1144 -0
- oscura/workflows/compliance.py +44 -54
- oscura/workflows/digital.py +197 -51
- oscura/workflows/legacy/__init__.py +12 -0
- oscura/{workflow → workflows/legacy}/dag.py +4 -1
- oscura/workflows/multi_trace.py +9 -9
- oscura/workflows/power.py +42 -62
- oscura/workflows/protocol.py +82 -49
- oscura/workflows/reverse_engineering.py +351 -150
- oscura/workflows/signal_integrity.py +157 -82
- oscura-0.6.0.dist-info/METADATA +643 -0
- oscura-0.6.0.dist-info/RECORD +590 -0
- oscura/analyzers/digital/ic_database.py +0 -498
- oscura/analyzers/digital/timing_paths.py +0 -339
- oscura/analyzers/digital/vintage.py +0 -377
- oscura/analyzers/digital/vintage_result.py +0 -148
- oscura/analyzers/protocols/parallel_bus.py +0 -449
- oscura/batch/aggregate.py +0 -300
- oscura/batch/analyze.py +0 -139
- oscura/dsl/__init__.py +0 -73
- oscura/exceptions.py +0 -59
- oscura/exploratory/fuzzy.py +0 -513
- oscura/exploratory/sync.py +0 -384
- oscura/export/wavedrom.py +0 -430
- oscura/exporters/__init__.py +0 -94
- oscura/exporters/csv.py +0 -303
- oscura/exporters/exporters.py +0 -44
- oscura/exporters/hdf5.py +0 -217
- oscura/exporters/html_export.py +0 -701
- oscura/exporters/json_export.py +0 -338
- oscura/exporters/markdown_export.py +0 -367
- oscura/exporters/matlab_export.py +0 -354
- oscura/exporters/npz_export.py +0 -219
- oscura/exporters/spice_export.py +0 -210
- oscura/exporters/vintage_logic_csv.py +0 -247
- oscura/reporting/vintage_logic_report.py +0 -523
- oscura/search/context.py +0 -149
- oscura/session/__init__.py +0 -34
- oscura/session/annotations.py +0 -289
- oscura/session/history.py +0 -313
- oscura/session/session.py +0 -520
- oscura/visualization/digital_advanced.py +0 -718
- oscura/visualization/figure_manager.py +0 -156
- oscura/workflow/__init__.py +0 -13
- oscura-0.5.0.dist-info/METADATA +0 -407
- oscura-0.5.0.dist-info/RECORD +0 -486
- /oscura/core/{config.py → config/legacy.py} +0 -0
- /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
- /oscura/{extensibility → core/extensibility}/registry.py +0 -0
- /oscura/{plugins → core/plugins}/isolation.py +0 -0
- /oscura/{builders → utils/builders}/signal_builder.py +0 -0
- /oscura/{optimization → utils/optimization}/parallel.py +0 -0
- /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
- /oscura/{streaming → utils/streaming}/__init__.py +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,801 @@
|
|
|
1
|
+
"""Scapy layer generator from ProtocolSpec.
|
|
2
|
+
|
|
3
|
+
This module generates production-ready Scapy layer classes from ProtocolSpec
|
|
4
|
+
objects (from reverse engineering workflows). The generated layers can be
|
|
5
|
+
imported and used for packet construction, dissection, and network analysis.
|
|
6
|
+
|
|
7
|
+
Features:
|
|
8
|
+
- Generate complete Scapy layer classes from ProtocolSpec
|
|
9
|
+
- Support all field types (uint8, uint16, uint32, string, bytes, enum)
|
|
10
|
+
- Handle endianness (big-endian, little-endian)
|
|
11
|
+
- CRC validation using Scapy's XByteField
|
|
12
|
+
- Generate importable Python modules
|
|
13
|
+
- Validate generated code executes without errors
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
>>> from oscura.export.scapy_layer import (
|
|
17
|
+
... ScapyLayerGenerator,
|
|
18
|
+
... ScapyLayerConfig
|
|
19
|
+
... )
|
|
20
|
+
>>> from oscura.workflows.reverse_engineering import ProtocolSpec, FieldSpec
|
|
21
|
+
>>> spec = ProtocolSpec(
|
|
22
|
+
... name="MyProtocol",
|
|
23
|
+
... baud_rate=115200,
|
|
24
|
+
... frame_format="8N1",
|
|
25
|
+
... sync_pattern="aa55",
|
|
26
|
+
... frame_length=10,
|
|
27
|
+
... fields=[
|
|
28
|
+
... FieldSpec(name="sync", offset=0, size=2, field_type="bytes"),
|
|
29
|
+
... FieldSpec(name="length", offset=2, size=1, field_type="uint8"),
|
|
30
|
+
... ],
|
|
31
|
+
... checksum_type="crc16",
|
|
32
|
+
... checksum_position=-1,
|
|
33
|
+
... confidence=0.95
|
|
34
|
+
... )
|
|
35
|
+
>>> config = ScapyLayerConfig(protocol_name="MyProtocol")
|
|
36
|
+
>>> generator = ScapyLayerGenerator(config)
|
|
37
|
+
>>> layer_path = generator.generate(
|
|
38
|
+
... spec,
|
|
39
|
+
... sample_messages=[b"\\xaa\\x55\\x08test123"],
|
|
40
|
+
... output_path=Path("myproto_layer.py")
|
|
41
|
+
... )
|
|
42
|
+
>>> # Import and use the generated layer
|
|
43
|
+
>>> from myproto_layer import MyProtocol
|
|
44
|
+
>>> pkt = MyProtocol(sync=b"\\xaa\\x55", length=8)
|
|
45
|
+
|
|
46
|
+
Installation:
|
|
47
|
+
Import the generated .py file directly or add it to your Python path:
|
|
48
|
+
>>> import sys
|
|
49
|
+
>>> sys.path.append('/path/to/generated')
|
|
50
|
+
>>> from myproto_layer import MyProtocol
|
|
51
|
+
|
|
52
|
+
References:
|
|
53
|
+
- Scapy Documentation: https://scapy.readthedocs.io/
|
|
54
|
+
- Scapy Layer Creation: https://scapy.readthedocs.io/en/latest/build_dissect.html
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
from __future__ import annotations
|
|
58
|
+
|
|
59
|
+
import ast
|
|
60
|
+
import logging
|
|
61
|
+
from dataclasses import dataclass
|
|
62
|
+
from datetime import UTC, datetime
|
|
63
|
+
from pathlib import Path
|
|
64
|
+
from typing import TYPE_CHECKING
|
|
65
|
+
|
|
66
|
+
from tqdm import tqdm
|
|
67
|
+
|
|
68
|
+
from oscura.utils.validation import validate_protocol_spec
|
|
69
|
+
|
|
70
|
+
if TYPE_CHECKING:
|
|
71
|
+
from oscura.inference.crc_reverse import CRCParameters
|
|
72
|
+
from oscura.workflows.reverse_engineering import FieldSpec, ProtocolSpec
|
|
73
|
+
|
|
74
|
+
__all__ = ["ScapyLayerConfig", "ScapyLayerGenerator"]
|
|
75
|
+
|
|
76
|
+
logger = logging.getLogger(__name__)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class ScapyLayerConfig:
|
|
81
|
+
"""Configuration for Scapy layer generation.
|
|
82
|
+
|
|
83
|
+
Attributes:
|
|
84
|
+
protocol_name: Protocol name for layer class.
|
|
85
|
+
base_class: Scapy base class (default "Packet").
|
|
86
|
+
include_crc_validation: Include CRC validation code (default True).
|
|
87
|
+
generate_examples: Generate usage examples in docstring (default True).
|
|
88
|
+
show_progress: Show progress bar for >100 messages (default True).
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
protocol_name: str
|
|
92
|
+
base_class: str = "Packet"
|
|
93
|
+
include_crc_validation: bool = True
|
|
94
|
+
generate_examples: bool = True
|
|
95
|
+
show_progress: bool = True
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class ScapyLayerGenerator:
|
|
99
|
+
"""Generate production-ready Scapy layers from ProtocolSpec.
|
|
100
|
+
|
|
101
|
+
This class converts ProtocolSpec objects (from reverse engineering workflows)
|
|
102
|
+
into Scapy layer classes that can be imported and used for packet construction,
|
|
103
|
+
dissection, and network analysis.
|
|
104
|
+
|
|
105
|
+
Features:
|
|
106
|
+
- All field types (uint8, uint16, uint32, string, bytes, enum)
|
|
107
|
+
- Endianness handling (big/little)
|
|
108
|
+
- CRC validation
|
|
109
|
+
- Python code validation
|
|
110
|
+
- Usage examples
|
|
111
|
+
|
|
112
|
+
Example:
|
|
113
|
+
>>> config = ScapyLayerConfig(protocol_name="MyProtocol")
|
|
114
|
+
>>> generator = ScapyLayerGenerator(config)
|
|
115
|
+
>>> layer_path = generator.generate(
|
|
116
|
+
... spec,
|
|
117
|
+
... sample_messages=[b"\\x01\\x02\\x03"],
|
|
118
|
+
... output_path=Path("myproto_layer.py")
|
|
119
|
+
... )
|
|
120
|
+
>>> from myproto_layer import MyProtocol
|
|
121
|
+
>>> pkt = MyProtocol()
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
def __init__(self, config: ScapyLayerConfig) -> None:
|
|
125
|
+
"""Initialize Scapy layer generator.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
config: Layer generation configuration.
|
|
129
|
+
"""
|
|
130
|
+
self.config = config
|
|
131
|
+
|
|
132
|
+
def generate(
|
|
133
|
+
self,
|
|
134
|
+
spec: ProtocolSpec,
|
|
135
|
+
sample_messages: list[bytes],
|
|
136
|
+
output_path: Path,
|
|
137
|
+
) -> Path:
|
|
138
|
+
"""Generate Scapy layer Python module.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
spec: Protocol specification from reverse engineering.
|
|
142
|
+
sample_messages: Sample protocol messages for examples.
|
|
143
|
+
output_path: Path for output .py file.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Path to generated Python module.
|
|
147
|
+
|
|
148
|
+
Raises:
|
|
149
|
+
ValueError: If spec is invalid or has missing required fields.
|
|
150
|
+
RuntimeError: If Python syntax validation fails.
|
|
151
|
+
OSError: If file writing fails.
|
|
152
|
+
|
|
153
|
+
Example:
|
|
154
|
+
>>> spec = ProtocolSpec(name="test", ...)
|
|
155
|
+
>>> generator = ScapyLayerGenerator(config)
|
|
156
|
+
>>> layer_path = generator.generate(
|
|
157
|
+
... spec,
|
|
158
|
+
... [b"\\x01\\x02\\x03"],
|
|
159
|
+
... Path("test_layer.py")
|
|
160
|
+
... )
|
|
161
|
+
"""
|
|
162
|
+
# Validate spec
|
|
163
|
+
self._validate_spec(spec)
|
|
164
|
+
|
|
165
|
+
# Generate Python code with optional progress bar
|
|
166
|
+
if self.config.show_progress and len(sample_messages) > 100:
|
|
167
|
+
with tqdm(total=5, desc="Generating Scapy layer") as pbar:
|
|
168
|
+
pbar.set_description("Generating imports")
|
|
169
|
+
imports = self._generate_imports(spec)
|
|
170
|
+
pbar.update(1)
|
|
171
|
+
|
|
172
|
+
pbar.set_description("Generating CRC functions")
|
|
173
|
+
crc_code = self._generate_crc_functions(spec)
|
|
174
|
+
pbar.update(1)
|
|
175
|
+
|
|
176
|
+
pbar.set_description("Generating layer class")
|
|
177
|
+
layer_code = self._generate_layer_class(spec, sample_messages)
|
|
178
|
+
pbar.update(1)
|
|
179
|
+
|
|
180
|
+
pbar.set_description("Generating bind layers")
|
|
181
|
+
bind_code = self._generate_bind_layers(spec)
|
|
182
|
+
pbar.update(1)
|
|
183
|
+
|
|
184
|
+
pbar.set_description("Assembling code")
|
|
185
|
+
python_code = self._assemble_code(imports, crc_code, layer_code, bind_code)
|
|
186
|
+
pbar.update(1)
|
|
187
|
+
else:
|
|
188
|
+
imports = self._generate_imports(spec)
|
|
189
|
+
crc_code = self._generate_crc_functions(spec)
|
|
190
|
+
layer_code = self._generate_layer_class(spec, sample_messages)
|
|
191
|
+
bind_code = self._generate_bind_layers(spec)
|
|
192
|
+
python_code = self._assemble_code(imports, crc_code, layer_code, bind_code)
|
|
193
|
+
|
|
194
|
+
# Validate Python syntax
|
|
195
|
+
if not self._validate_python_syntax(python_code):
|
|
196
|
+
raise RuntimeError("Generated Python code has syntax errors")
|
|
197
|
+
|
|
198
|
+
# Write Python file
|
|
199
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
200
|
+
output_path.write_text(python_code, encoding="utf-8")
|
|
201
|
+
logger.info(f"Generated Scapy layer: {output_path}")
|
|
202
|
+
|
|
203
|
+
return output_path
|
|
204
|
+
|
|
205
|
+
def _validate_spec(self, spec: ProtocolSpec) -> None:
|
|
206
|
+
"""Validate protocol specification.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
spec: Protocol specification to validate.
|
|
210
|
+
|
|
211
|
+
Raises:
|
|
212
|
+
ValueError: If spec is invalid.
|
|
213
|
+
"""
|
|
214
|
+
validate_protocol_spec(spec)
|
|
215
|
+
|
|
216
|
+
# Validate fields
|
|
217
|
+
for field in spec.fields:
|
|
218
|
+
if not field.name:
|
|
219
|
+
raise ValueError("Field name is required")
|
|
220
|
+
if field.field_type not in {
|
|
221
|
+
"uint8",
|
|
222
|
+
"uint16",
|
|
223
|
+
"uint32",
|
|
224
|
+
"bytes",
|
|
225
|
+
"string",
|
|
226
|
+
"constant",
|
|
227
|
+
"checksum",
|
|
228
|
+
}:
|
|
229
|
+
raise ValueError(f"Unsupported field type: {field.field_type}")
|
|
230
|
+
|
|
231
|
+
def _generate_imports(self, spec: ProtocolSpec) -> str:
|
|
232
|
+
"""Generate import statements.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
spec: Protocol specification.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Python import statements.
|
|
239
|
+
"""
|
|
240
|
+
imports = [
|
|
241
|
+
f'"""Scapy layer for {spec.name}.',
|
|
242
|
+
"",
|
|
243
|
+
f"Generated by Oscura on {datetime.now(UTC).isoformat()}",
|
|
244
|
+
"",
|
|
245
|
+
"Usage:",
|
|
246
|
+
f" >>> from {self._safe_module_name(spec.name)} import {self._safe_class_name(spec.name)}",
|
|
247
|
+
f" >>> pkt = {self._safe_class_name(spec.name)}()",
|
|
248
|
+
" >>> pkt.show()",
|
|
249
|
+
'"""',
|
|
250
|
+
"",
|
|
251
|
+
"from scapy.fields import (",
|
|
252
|
+
" ByteField,",
|
|
253
|
+
" ShortField,",
|
|
254
|
+
" IntField,",
|
|
255
|
+
" LEShortField,",
|
|
256
|
+
" LEIntField,",
|
|
257
|
+
" StrFixedLenField,",
|
|
258
|
+
" XByteField,",
|
|
259
|
+
" XShortField,",
|
|
260
|
+
" XIntField,",
|
|
261
|
+
")",
|
|
262
|
+
"from scapy.packet import Packet, bind_layers",
|
|
263
|
+
"",
|
|
264
|
+
]
|
|
265
|
+
|
|
266
|
+
return "\n".join(imports)
|
|
267
|
+
|
|
268
|
+
def _generate_crc_functions(self, spec: ProtocolSpec) -> str:
|
|
269
|
+
"""Generate CRC validation functions.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
spec: Protocol specification.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Python CRC function code.
|
|
276
|
+
"""
|
|
277
|
+
if not self.config.include_crc_validation:
|
|
278
|
+
return ""
|
|
279
|
+
|
|
280
|
+
if not spec.checksum_type or spec.checksum_type not in ("crc8", "crc16", "crc32"):
|
|
281
|
+
return ""
|
|
282
|
+
|
|
283
|
+
# Get CRC parameters if available
|
|
284
|
+
crc_info = getattr(spec, "crc_info", None)
|
|
285
|
+
if crc_info:
|
|
286
|
+
return self._generate_crc_function_from_params(crc_info)
|
|
287
|
+
|
|
288
|
+
# Default CRC implementations
|
|
289
|
+
if spec.checksum_type == "crc16":
|
|
290
|
+
return '''
|
|
291
|
+
def calculate_crc16(data: bytes) -> int:
|
|
292
|
+
"""Calculate CRC-16-CCITT checksum.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
data: Input data bytes.
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
16-bit CRC value.
|
|
299
|
+
"""
|
|
300
|
+
crc = 0xFFFF
|
|
301
|
+
poly = 0x1021
|
|
302
|
+
|
|
303
|
+
for byte in data:
|
|
304
|
+
crc ^= byte << 8
|
|
305
|
+
for _ in range(8):
|
|
306
|
+
if crc & 0x8000:
|
|
307
|
+
crc = ((crc << 1) ^ poly) & 0xFFFF
|
|
308
|
+
else:
|
|
309
|
+
crc = (crc << 1) & 0xFFFF
|
|
310
|
+
|
|
311
|
+
return crc
|
|
312
|
+
'''
|
|
313
|
+
elif spec.checksum_type == "crc8":
|
|
314
|
+
return '''
|
|
315
|
+
def calculate_crc8(data: bytes) -> int:
|
|
316
|
+
"""Calculate CRC-8 checksum.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
data: Input data bytes.
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
8-bit CRC value.
|
|
323
|
+
"""
|
|
324
|
+
crc = 0x00
|
|
325
|
+
poly = 0x07
|
|
326
|
+
|
|
327
|
+
for byte in data:
|
|
328
|
+
crc ^= byte
|
|
329
|
+
for _ in range(8):
|
|
330
|
+
if crc & 0x80:
|
|
331
|
+
crc = ((crc << 1) ^ poly) & 0xFF
|
|
332
|
+
else:
|
|
333
|
+
crc = (crc << 1) & 0xFF
|
|
334
|
+
|
|
335
|
+
return crc
|
|
336
|
+
'''
|
|
337
|
+
else: # crc32
|
|
338
|
+
return '''
|
|
339
|
+
def calculate_crc32(data: bytes) -> int:
|
|
340
|
+
"""Calculate CRC-32 checksum.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
data: Input data bytes.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
32-bit CRC value.
|
|
347
|
+
"""
|
|
348
|
+
crc = 0xFFFFFFFF
|
|
349
|
+
poly = 0x04C11DB7
|
|
350
|
+
|
|
351
|
+
for byte in data:
|
|
352
|
+
crc ^= byte << 24
|
|
353
|
+
for _ in range(8):
|
|
354
|
+
if crc & 0x80000000:
|
|
355
|
+
crc = ((crc << 1) ^ poly) & 0xFFFFFFFF
|
|
356
|
+
else:
|
|
357
|
+
crc = (crc << 1) & 0xFFFFFFFF
|
|
358
|
+
|
|
359
|
+
return crc ^ 0xFFFFFFFF
|
|
360
|
+
'''
|
|
361
|
+
|
|
362
|
+
def _generate_crc_function_from_params(self, crc_info: CRCParameters) -> str:
|
|
363
|
+
"""Generate CRC function from CRCParameters.
|
|
364
|
+
|
|
365
|
+
Args:
|
|
366
|
+
crc_info: CRC parameters from reverse engineering.
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
Python CRC function code.
|
|
370
|
+
"""
|
|
371
|
+
width = crc_info.width
|
|
372
|
+
poly = crc_info.polynomial
|
|
373
|
+
init = crc_info.init
|
|
374
|
+
xor_out = crc_info.xor_out
|
|
375
|
+
mask = (1 << width) - 1
|
|
376
|
+
|
|
377
|
+
func_name = f"calculate_crc{width}"
|
|
378
|
+
|
|
379
|
+
lines = [
|
|
380
|
+
"",
|
|
381
|
+
f"def {func_name}(data: bytes) -> int:",
|
|
382
|
+
f' """Calculate CRC-{width} checksum (custom parameters).',
|
|
383
|
+
" ",
|
|
384
|
+
f" Polynomial: 0x{poly:0{width // 4}x}",
|
|
385
|
+
f" Init: 0x{init:0{width // 4}x}",
|
|
386
|
+
f" XorOut: 0x{xor_out:0{width // 4}x}",
|
|
387
|
+
f" ReflectIn: {crc_info.reflect_in}",
|
|
388
|
+
f" ReflectOut: {crc_info.reflect_out}",
|
|
389
|
+
" ",
|
|
390
|
+
" Args:",
|
|
391
|
+
" data: Input data bytes.",
|
|
392
|
+
" ",
|
|
393
|
+
" Returns:",
|
|
394
|
+
f" {width}-bit CRC value.",
|
|
395
|
+
' """',
|
|
396
|
+
f" crc = 0x{init:0{width // 4}x}",
|
|
397
|
+
f" poly = 0x{poly:0{width // 4}x}",
|
|
398
|
+
f" mask = 0x{mask:0{width // 4}x}",
|
|
399
|
+
" ",
|
|
400
|
+
" for byte in data:",
|
|
401
|
+
]
|
|
402
|
+
|
|
403
|
+
if crc_info.reflect_in:
|
|
404
|
+
lines.extend(
|
|
405
|
+
[
|
|
406
|
+
" # Reflect input byte",
|
|
407
|
+
" reflected = 0",
|
|
408
|
+
" for b in range(8):",
|
|
409
|
+
" if byte & (1 << b):",
|
|
410
|
+
" reflected |= 1 << (7 - b)",
|
|
411
|
+
" byte = reflected",
|
|
412
|
+
]
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
lines.extend(
|
|
416
|
+
[
|
|
417
|
+
f" crc ^= byte << {width - 8}",
|
|
418
|
+
" for _ in range(8):",
|
|
419
|
+
f" if crc & 0x{1 << (width - 1):0{width // 4}x}:",
|
|
420
|
+
" crc = ((crc << 1) ^ poly) & mask",
|
|
421
|
+
" else:",
|
|
422
|
+
" crc = (crc << 1) & mask",
|
|
423
|
+
" ",
|
|
424
|
+
]
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
if crc_info.reflect_out:
|
|
428
|
+
lines.extend(
|
|
429
|
+
[
|
|
430
|
+
" # Reflect output CRC",
|
|
431
|
+
" reflected = 0",
|
|
432
|
+
f" for b in range({width}):",
|
|
433
|
+
" if crc & (1 << b):",
|
|
434
|
+
f" reflected |= 1 << ({width - 1} - b)",
|
|
435
|
+
" crc = reflected",
|
|
436
|
+
" ",
|
|
437
|
+
]
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
lines.extend([f" return crc ^ 0x{xor_out:0{width // 4}x}", ""])
|
|
441
|
+
|
|
442
|
+
return "\n".join(lines)
|
|
443
|
+
|
|
444
|
+
def _generate_layer_class(self, spec: ProtocolSpec, sample_messages: list[bytes]) -> str:
|
|
445
|
+
"""Generate Scapy layer class definition.
|
|
446
|
+
|
|
447
|
+
Args:
|
|
448
|
+
spec: Protocol specification.
|
|
449
|
+
sample_messages: Sample messages for examples.
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
Python class definition code.
|
|
453
|
+
"""
|
|
454
|
+
class_name = self._safe_class_name(spec.name)
|
|
455
|
+
base_class = self.config.base_class
|
|
456
|
+
|
|
457
|
+
lines = [
|
|
458
|
+
f"class {class_name}({base_class}):",
|
|
459
|
+
f' """Scapy layer for {spec.name}.',
|
|
460
|
+
" ",
|
|
461
|
+
f" Baud Rate: {spec.baud_rate} bps",
|
|
462
|
+
f" Frame Format: {spec.frame_format}",
|
|
463
|
+
f" Sync Pattern: {spec.sync_pattern}",
|
|
464
|
+
f" Frame Length: {spec.frame_length if spec.frame_length else 'Variable'} bytes",
|
|
465
|
+
f" Checksum: {spec.checksum_type if spec.checksum_type else 'None'}",
|
|
466
|
+
" ",
|
|
467
|
+
]
|
|
468
|
+
|
|
469
|
+
# Add examples if requested
|
|
470
|
+
if self.config.generate_examples and sample_messages:
|
|
471
|
+
lines.extend(
|
|
472
|
+
[
|
|
473
|
+
" Example:",
|
|
474
|
+
f" >>> pkt = {class_name}()",
|
|
475
|
+
" >>> pkt.show()",
|
|
476
|
+
]
|
|
477
|
+
)
|
|
478
|
+
# Add first sample as example
|
|
479
|
+
if sample_messages:
|
|
480
|
+
first_sample = sample_messages[0]
|
|
481
|
+
lines.append(f" >>> raw_pkt = bytes.fromhex('{first_sample.hex()}')")
|
|
482
|
+
lines.append(f" >>> pkt = {class_name}(raw_pkt)")
|
|
483
|
+
|
|
484
|
+
lines.extend([' """', f' name = "{spec.name}"', " fields_desc = ["])
|
|
485
|
+
|
|
486
|
+
# Generate field definitions
|
|
487
|
+
for field in spec.fields:
|
|
488
|
+
field_def = self._generate_field_definition(field)
|
|
489
|
+
lines.append(f" {field_def},")
|
|
490
|
+
|
|
491
|
+
lines.extend([" ]", ""])
|
|
492
|
+
|
|
493
|
+
# Add post_build for CRC calculation if needed
|
|
494
|
+
if spec.checksum_type and spec.checksum_position is not None:
|
|
495
|
+
lines.extend(self._generate_post_build(spec))
|
|
496
|
+
|
|
497
|
+
# Add do_dissect for CRC validation if needed
|
|
498
|
+
if spec.checksum_type and self.config.include_crc_validation:
|
|
499
|
+
lines.extend(self._generate_do_dissect(spec))
|
|
500
|
+
|
|
501
|
+
return "\n".join(lines)
|
|
502
|
+
|
|
503
|
+
def _generate_field_definition(self, field: FieldSpec) -> str:
|
|
504
|
+
"""Generate Scapy field definition.
|
|
505
|
+
|
|
506
|
+
Args:
|
|
507
|
+
field: Field specification.
|
|
508
|
+
|
|
509
|
+
Returns:
|
|
510
|
+
Python field definition code.
|
|
511
|
+
"""
|
|
512
|
+
field_name = self._safe_field_name(field.name)
|
|
513
|
+
field_size = field.size if isinstance(field.size, int) else 1
|
|
514
|
+
endian = getattr(field, "endian", "big")
|
|
515
|
+
|
|
516
|
+
# Dispatch to type-specific handlers
|
|
517
|
+
if field.field_type == "uint8":
|
|
518
|
+
return self._gen_uint8_field(field_name, field.name)
|
|
519
|
+
elif field.field_type == "uint16":
|
|
520
|
+
return self._gen_uint16_field(field_name, field.name, endian)
|
|
521
|
+
elif field.field_type == "uint32":
|
|
522
|
+
return self._gen_uint32_field(field_name, field.name, endian)
|
|
523
|
+
elif field.field_type == "string":
|
|
524
|
+
return f'StrFixedLenField("{field_name}", b"", length={field_size})'
|
|
525
|
+
elif field.field_type in ("bytes", "constant"):
|
|
526
|
+
return self._gen_bytes_field(field_name, field, field_size)
|
|
527
|
+
else: # checksum
|
|
528
|
+
return self._gen_checksum_field(field_name, field_size)
|
|
529
|
+
|
|
530
|
+
def _gen_uint8_field(self, field_name: str, original_name: str) -> str:
|
|
531
|
+
"""Generate uint8 field definition.
|
|
532
|
+
|
|
533
|
+
Args:
|
|
534
|
+
field_name: Safe field name.
|
|
535
|
+
original_name: Original field name.
|
|
536
|
+
|
|
537
|
+
Returns:
|
|
538
|
+
Field definition code.
|
|
539
|
+
"""
|
|
540
|
+
if original_name == "checksum" or "crc" in original_name.lower():
|
|
541
|
+
return f'XByteField("{field_name}", 0)'
|
|
542
|
+
return f'ByteField("{field_name}", 0)'
|
|
543
|
+
|
|
544
|
+
def _gen_uint16_field(self, field_name: str, original_name: str, endian: str) -> str:
|
|
545
|
+
"""Generate uint16 field definition.
|
|
546
|
+
|
|
547
|
+
Args:
|
|
548
|
+
field_name: Safe field name.
|
|
549
|
+
original_name: Original field name.
|
|
550
|
+
endian: Byte order (big/little).
|
|
551
|
+
|
|
552
|
+
Returns:
|
|
553
|
+
Field definition code.
|
|
554
|
+
"""
|
|
555
|
+
if original_name == "checksum" or "crc" in original_name.lower():
|
|
556
|
+
return f'XShortField("{field_name}", 0)'
|
|
557
|
+
if endian == "little":
|
|
558
|
+
return f'LEShortField("{field_name}", 0)'
|
|
559
|
+
return f'ShortField("{field_name}", 0)'
|
|
560
|
+
|
|
561
|
+
def _gen_uint32_field(self, field_name: str, original_name: str, endian: str) -> str:
|
|
562
|
+
"""Generate uint32 field definition.
|
|
563
|
+
|
|
564
|
+
Args:
|
|
565
|
+
field_name: Safe field name.
|
|
566
|
+
original_name: Original field name.
|
|
567
|
+
endian: Byte order (big/little).
|
|
568
|
+
|
|
569
|
+
Returns:
|
|
570
|
+
Field definition code.
|
|
571
|
+
"""
|
|
572
|
+
if original_name == "checksum" or "crc" in original_name.lower():
|
|
573
|
+
return f'XIntField("{field_name}", 0)'
|
|
574
|
+
if endian == "little":
|
|
575
|
+
return f'LEIntField("{field_name}", 0)'
|
|
576
|
+
return f'IntField("{field_name}", 0)'
|
|
577
|
+
|
|
578
|
+
def _gen_bytes_field(self, field_name: str, field: FieldSpec, field_size: int) -> str:
|
|
579
|
+
"""Generate bytes/constant field definition.
|
|
580
|
+
|
|
581
|
+
Args:
|
|
582
|
+
field_name: Safe field name.
|
|
583
|
+
field: Field specification.
|
|
584
|
+
field_size: Field size in bytes.
|
|
585
|
+
|
|
586
|
+
Returns:
|
|
587
|
+
Field definition code.
|
|
588
|
+
"""
|
|
589
|
+
default_value = getattr(field, "value", None)
|
|
590
|
+
if default_value and isinstance(default_value, str):
|
|
591
|
+
default_bytes = bytes.fromhex(default_value)
|
|
592
|
+
return f'StrFixedLenField("{field_name}", {default_bytes!r}, length={field_size})'
|
|
593
|
+
return f'StrFixedLenField("{field_name}", b"\\x00" * {field_size}, length={field_size})'
|
|
594
|
+
|
|
595
|
+
def _gen_checksum_field(self, field_name: str, field_size: int) -> str:
|
|
596
|
+
"""Generate checksum field definition.
|
|
597
|
+
|
|
598
|
+
Args:
|
|
599
|
+
field_name: Safe field name.
|
|
600
|
+
field_size: Field size in bytes.
|
|
601
|
+
|
|
602
|
+
Returns:
|
|
603
|
+
Field definition code.
|
|
604
|
+
"""
|
|
605
|
+
if field_size == 1:
|
|
606
|
+
return f'XByteField("{field_name}", 0)'
|
|
607
|
+
elif field_size == 2:
|
|
608
|
+
return f'XShortField("{field_name}", 0)'
|
|
609
|
+
else:
|
|
610
|
+
return f'XIntField("{field_name}", 0)'
|
|
611
|
+
|
|
612
|
+
def _generate_post_build(self, spec: ProtocolSpec) -> list[str]:
|
|
613
|
+
"""Generate post_build method for CRC calculation.
|
|
614
|
+
|
|
615
|
+
Args:
|
|
616
|
+
spec: Protocol specification.
|
|
617
|
+
|
|
618
|
+
Returns:
|
|
619
|
+
Python method code lines.
|
|
620
|
+
"""
|
|
621
|
+
if not spec.checksum_type:
|
|
622
|
+
return []
|
|
623
|
+
|
|
624
|
+
width_map = {"crc8": 8, "crc16": 16, "crc32": 32}
|
|
625
|
+
width = width_map.get(spec.checksum_type, 16)
|
|
626
|
+
|
|
627
|
+
lines = [
|
|
628
|
+
" def post_build(self, pkt: bytes, pay: bytes) -> bytes:",
|
|
629
|
+
' """Calculate and insert CRC checksum after packet build.',
|
|
630
|
+
" ",
|
|
631
|
+
" Args:",
|
|
632
|
+
" pkt: Built packet bytes.",
|
|
633
|
+
" pay: Payload bytes.",
|
|
634
|
+
" ",
|
|
635
|
+
" Returns:",
|
|
636
|
+
" Packet with calculated CRC.",
|
|
637
|
+
' """',
|
|
638
|
+
]
|
|
639
|
+
|
|
640
|
+
if spec.checksum_position == -1:
|
|
641
|
+
# CRC at end
|
|
642
|
+
crc_size = width // 8
|
|
643
|
+
lines.extend(
|
|
644
|
+
[
|
|
645
|
+
f" # Calculate CRC over packet minus last {crc_size} bytes",
|
|
646
|
+
f" data = pkt[:-{crc_size}]",
|
|
647
|
+
f" crc = calculate_crc{width}(data)",
|
|
648
|
+
" # Replace CRC field",
|
|
649
|
+
]
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
if width == 8:
|
|
653
|
+
lines.append(" pkt = data + bytes([crc])")
|
|
654
|
+
elif width == 16:
|
|
655
|
+
lines.append(" pkt = data + crc.to_bytes(2, byteorder='big')")
|
|
656
|
+
else:
|
|
657
|
+
lines.append(" pkt = data + crc.to_bytes(4, byteorder='big')")
|
|
658
|
+
|
|
659
|
+
lines.extend([" return pkt + pay", ""])
|
|
660
|
+
|
|
661
|
+
return lines
|
|
662
|
+
|
|
663
|
+
def _generate_do_dissect(self, spec: ProtocolSpec) -> list[str]:
|
|
664
|
+
"""Generate do_dissect method for CRC validation.
|
|
665
|
+
|
|
666
|
+
Args:
|
|
667
|
+
spec: Protocol specification.
|
|
668
|
+
|
|
669
|
+
Returns:
|
|
670
|
+
Python method code lines.
|
|
671
|
+
"""
|
|
672
|
+
if not spec.checksum_type:
|
|
673
|
+
return []
|
|
674
|
+
|
|
675
|
+
width_map = {"crc8": 8, "crc16": 16, "crc32": 32}
|
|
676
|
+
width = width_map.get(spec.checksum_type, 16)
|
|
677
|
+
crc_size = width // 8
|
|
678
|
+
|
|
679
|
+
return [
|
|
680
|
+
" def do_dissect(self, s: bytes) -> bytes:",
|
|
681
|
+
' """Dissect packet and validate CRC.',
|
|
682
|
+
" ",
|
|
683
|
+
" Args:",
|
|
684
|
+
" s: Raw packet bytes.",
|
|
685
|
+
" ",
|
|
686
|
+
" Returns:",
|
|
687
|
+
" Remaining bytes after dissection.",
|
|
688
|
+
' """',
|
|
689
|
+
" # Standard dissection",
|
|
690
|
+
" s = super().do_dissect(s)",
|
|
691
|
+
" # Validate CRC",
|
|
692
|
+
f" data = bytes(self)[:-{crc_size}]",
|
|
693
|
+
f" calculated_crc = calculate_crc{width}(data)",
|
|
694
|
+
f" packet_crc = int.from_bytes(bytes(self)[-{crc_size}:], byteorder='big')",
|
|
695
|
+
" if calculated_crc != packet_crc:",
|
|
696
|
+
' print(f"CRC mismatch: calculated={calculated_crc:04x}, '
|
|
697
|
+
'packet={packet_crc:04x}")',
|
|
698
|
+
" return s",
|
|
699
|
+
"",
|
|
700
|
+
]
|
|
701
|
+
|
|
702
|
+
def _generate_bind_layers(self, spec: ProtocolSpec) -> str:
|
|
703
|
+
"""Generate bind_layers statements.
|
|
704
|
+
|
|
705
|
+
Args:
|
|
706
|
+
spec: Protocol specification.
|
|
707
|
+
|
|
708
|
+
Returns:
|
|
709
|
+
Python bind_layers code.
|
|
710
|
+
"""
|
|
711
|
+
# For now, don't auto-bind to any layers
|
|
712
|
+
# Users can manually bind as needed
|
|
713
|
+
return f"# To bind this layer to another layer, use:\n# bind_layers(UDP, {self._safe_class_name(spec.name)}, dport=YOUR_PORT)\n"
|
|
714
|
+
|
|
715
|
+
def _assemble_code(self, imports: str, crc_code: str, layer_code: str, bind_code: str) -> str:
|
|
716
|
+
"""Assemble complete Python module.
|
|
717
|
+
|
|
718
|
+
Args:
|
|
719
|
+
imports: Import statements.
|
|
720
|
+
crc_code: CRC function code.
|
|
721
|
+
layer_code: Layer class code.
|
|
722
|
+
bind_code: Bind layers code.
|
|
723
|
+
|
|
724
|
+
Returns:
|
|
725
|
+
Complete Python module code.
|
|
726
|
+
"""
|
|
727
|
+
sections = [imports]
|
|
728
|
+
|
|
729
|
+
if crc_code:
|
|
730
|
+
sections.append(crc_code)
|
|
731
|
+
|
|
732
|
+
sections.extend([layer_code, bind_code])
|
|
733
|
+
|
|
734
|
+
return "\n".join(sections)
|
|
735
|
+
|
|
736
|
+
def _validate_python_syntax(self, python_code: str) -> bool:
|
|
737
|
+
"""Validate Python syntax using ast.parse.
|
|
738
|
+
|
|
739
|
+
Args:
|
|
740
|
+
python_code: Python code to validate.
|
|
741
|
+
|
|
742
|
+
Returns:
|
|
743
|
+
True if syntax is valid, False if errors found.
|
|
744
|
+
"""
|
|
745
|
+
try:
|
|
746
|
+
ast.parse(python_code)
|
|
747
|
+
logger.info("Python syntax validation passed")
|
|
748
|
+
return True
|
|
749
|
+
except SyntaxError as e:
|
|
750
|
+
logger.error(f"Python syntax error: {e}")
|
|
751
|
+
return False
|
|
752
|
+
|
|
753
|
+
def _safe_class_name(self, name: str) -> str:
|
|
754
|
+
"""Convert protocol name to safe Python class name.
|
|
755
|
+
|
|
756
|
+
Args:
|
|
757
|
+
name: Protocol name.
|
|
758
|
+
|
|
759
|
+
Returns:
|
|
760
|
+
Safe Python class name (PascalCase).
|
|
761
|
+
"""
|
|
762
|
+
# If name is already PascalCase (first letter uppercase, contains mix of upper/lower),
|
|
763
|
+
# preserve it as-is after removing non-alphanumeric chars
|
|
764
|
+
clean_name = "".join(c if c.isalnum() else "_" for c in name)
|
|
765
|
+
|
|
766
|
+
# Check if already PascalCase (starts with uppercase, has lowercase letters)
|
|
767
|
+
if clean_name and clean_name[0].isupper() and any(c.islower() for c in clean_name):
|
|
768
|
+
# Remove underscores but preserve case
|
|
769
|
+
return clean_name.replace("_", "")
|
|
770
|
+
|
|
771
|
+
# Otherwise convert to PascalCase
|
|
772
|
+
words = clean_name.split("_")
|
|
773
|
+
return "".join(word.capitalize() for word in words if word)
|
|
774
|
+
|
|
775
|
+
def _safe_field_name(self, name: str) -> str:
|
|
776
|
+
"""Convert field name to safe Python identifier.
|
|
777
|
+
|
|
778
|
+
Args:
|
|
779
|
+
name: Field name.
|
|
780
|
+
|
|
781
|
+
Returns:
|
|
782
|
+
Safe Python field name (snake_case).
|
|
783
|
+
"""
|
|
784
|
+
# Remove non-alphanumeric characters
|
|
785
|
+
clean_name = "".join(c if c.isalnum() else "_" for c in name)
|
|
786
|
+
# Convert to snake_case
|
|
787
|
+
return clean_name.lower()
|
|
788
|
+
|
|
789
|
+
def _safe_module_name(self, name: str) -> str:
|
|
790
|
+
"""Convert protocol name to safe Python module name.
|
|
791
|
+
|
|
792
|
+
Args:
|
|
793
|
+
name: Protocol name.
|
|
794
|
+
|
|
795
|
+
Returns:
|
|
796
|
+
Safe Python module name (snake_case).
|
|
797
|
+
"""
|
|
798
|
+
# Remove non-alphanumeric characters
|
|
799
|
+
clean_name = "".join(c if c.isalnum() else "_" for c in name)
|
|
800
|
+
# Convert to snake_case
|
|
801
|
+
return clean_name.lower()
|