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,746 @@
|
|
|
1
|
+
"""Wireshark Lua dissector generator from ProtocolSpec.
|
|
2
|
+
|
|
3
|
+
This module generates functional Wireshark Lua dissectors from ProtocolSpec
|
|
4
|
+
objects (from reverse engineering workflows). The generated dissectors can
|
|
5
|
+
be loaded into Wireshark for interactive protocol analysis and validation.
|
|
6
|
+
|
|
7
|
+
Features:
|
|
8
|
+
- Generate working Lua dissectors from ProtocolSpec
|
|
9
|
+
- Support all field types (uint8, uint16, uint32, string, bytes, enum)
|
|
10
|
+
- CRC validation in Lua
|
|
11
|
+
- Test PCAP generation for validation
|
|
12
|
+
- Lua syntax validation
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
>>> from oscura.export.wireshark_dissector import (
|
|
16
|
+
... WiresharkDissectorGenerator,
|
|
17
|
+
... DissectorConfig
|
|
18
|
+
... )
|
|
19
|
+
>>> from oscura.workflows.reverse_engineering import ProtocolSpec, FieldSpec
|
|
20
|
+
>>> spec = ProtocolSpec(
|
|
21
|
+
... name="MyProtocol",
|
|
22
|
+
... baud_rate=115200,
|
|
23
|
+
... frame_format="8N1",
|
|
24
|
+
... sync_pattern="aa55",
|
|
25
|
+
... frame_length=10,
|
|
26
|
+
... fields=[
|
|
27
|
+
... FieldSpec(name="sync", offset=0, size=2, field_type="bytes"),
|
|
28
|
+
... FieldSpec(name="length", offset=2, size=1, field_type="uint8"),
|
|
29
|
+
... ],
|
|
30
|
+
... checksum_type=None,
|
|
31
|
+
... checksum_position=None,
|
|
32
|
+
... confidence=0.95
|
|
33
|
+
... )
|
|
34
|
+
>>> config = DissectorConfig(protocol_name="MyProtocol", port=5000)
|
|
35
|
+
>>> generator = WiresharkDissectorGenerator(config)
|
|
36
|
+
>>> dissector_path, pcap_path = generator.generate(
|
|
37
|
+
... spec,
|
|
38
|
+
... sample_messages=[b"\\xaa\\x55\\x08test123"],
|
|
39
|
+
... output_path=Path("myproto.lua")
|
|
40
|
+
... )
|
|
41
|
+
|
|
42
|
+
Installation:
|
|
43
|
+
Copy the generated .lua file to your Wireshark plugins directory:
|
|
44
|
+
- Linux: ~/.local/lib/wireshark/plugins/
|
|
45
|
+
- macOS: ~/.config/wireshark/plugins/
|
|
46
|
+
- Windows: %APPDATA%\\Wireshark\\plugins\\
|
|
47
|
+
|
|
48
|
+
References:
|
|
49
|
+
- Wireshark Lua API: https://wiki.wireshark.org/LuaAPI
|
|
50
|
+
- Lua Dissectors: https://wiki.wireshark.org/Lua/Dissectors
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
from __future__ import annotations
|
|
54
|
+
|
|
55
|
+
import logging
|
|
56
|
+
import struct
|
|
57
|
+
import subprocess
|
|
58
|
+
from dataclasses import dataclass
|
|
59
|
+
from datetime import UTC, datetime
|
|
60
|
+
from pathlib import Path
|
|
61
|
+
from typing import TYPE_CHECKING
|
|
62
|
+
|
|
63
|
+
from oscura.utils.validation import validate_protocol_spec
|
|
64
|
+
|
|
65
|
+
if TYPE_CHECKING:
|
|
66
|
+
from oscura.inference.crc_reverse import CRCParameters
|
|
67
|
+
from oscura.workflows.reverse_engineering import ProtocolSpec
|
|
68
|
+
|
|
69
|
+
__all__ = ["DissectorConfig", "WiresharkDissectorGenerator"]
|
|
70
|
+
|
|
71
|
+
logger = logging.getLogger(__name__)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass
|
|
75
|
+
class DissectorConfig:
|
|
76
|
+
"""Configuration for Wireshark dissector generation.
|
|
77
|
+
|
|
78
|
+
Attributes:
|
|
79
|
+
protocol_name: Protocol name for dissector.
|
|
80
|
+
port: UDP/TCP port number for registration (None for no registration).
|
|
81
|
+
include_crc_validation: Include CRC validation code in dissector.
|
|
82
|
+
generate_test_pcap: Generate test PCAP file with sample messages.
|
|
83
|
+
wireshark_version: Target Wireshark version (default "3.0+").
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
protocol_name: str
|
|
87
|
+
port: int | None = None
|
|
88
|
+
include_crc_validation: bool = True
|
|
89
|
+
generate_test_pcap: bool = True
|
|
90
|
+
wireshark_version: str = "3.0+"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class WiresharkDissectorGenerator:
|
|
94
|
+
"""Generate functional Wireshark Lua dissectors from ProtocolSpec.
|
|
95
|
+
|
|
96
|
+
This class converts ProtocolSpec objects (from reverse engineering workflows)
|
|
97
|
+
into Wireshark Lua dissectors that can be loaded into Wireshark for protocol
|
|
98
|
+
analysis and validation.
|
|
99
|
+
|
|
100
|
+
Features:
|
|
101
|
+
- All field types (uint8, uint16, uint32, string, bytes, enum)
|
|
102
|
+
- CRC validation in Lua
|
|
103
|
+
- Test PCAP generation
|
|
104
|
+
- Lua syntax validation
|
|
105
|
+
- UDP/TCP port registration
|
|
106
|
+
|
|
107
|
+
Example:
|
|
108
|
+
>>> config = DissectorConfig(protocol_name="MyProtocol", port=5000)
|
|
109
|
+
>>> generator = WiresharkDissectorGenerator(config)
|
|
110
|
+
>>> dissector_path, pcap_path = generator.generate(
|
|
111
|
+
... spec,
|
|
112
|
+
... sample_messages=[b"\\x01\\x02\\x03"],
|
|
113
|
+
... output_path=Path("myproto.lua")
|
|
114
|
+
... )
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
def __init__(self, config: DissectorConfig) -> None:
|
|
118
|
+
"""Initialize dissector generator.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
config: Dissector generation configuration.
|
|
122
|
+
"""
|
|
123
|
+
self.config = config
|
|
124
|
+
|
|
125
|
+
def generate(
|
|
126
|
+
self,
|
|
127
|
+
spec: ProtocolSpec,
|
|
128
|
+
sample_messages: list[bytes],
|
|
129
|
+
output_path: Path,
|
|
130
|
+
) -> tuple[Path, Path | None]:
|
|
131
|
+
"""Generate Wireshark Lua dissector and optional test PCAP.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
spec: Protocol specification from reverse engineering.
|
|
135
|
+
sample_messages: Sample protocol messages for test PCAP.
|
|
136
|
+
output_path: Path for output .lua file.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Tuple of (dissector_lua_path, test_pcap_path).
|
|
140
|
+
test_pcap_path is None if generate_test_pcap is False.
|
|
141
|
+
|
|
142
|
+
Raises:
|
|
143
|
+
ValueError: If spec is invalid or has missing required fields.
|
|
144
|
+
RuntimeError: If Lua syntax validation fails.
|
|
145
|
+
OSError: If file writing fails.
|
|
146
|
+
|
|
147
|
+
Example:
|
|
148
|
+
>>> spec = ProtocolSpec(name="test", ...)
|
|
149
|
+
>>> generator = WiresharkDissectorGenerator(config)
|
|
150
|
+
>>> lua_path, pcap_path = generator.generate(
|
|
151
|
+
... spec,
|
|
152
|
+
... [b"\\x01\\x02\\x03"],
|
|
153
|
+
... Path("test.lua")
|
|
154
|
+
... )
|
|
155
|
+
"""
|
|
156
|
+
# Validate spec
|
|
157
|
+
self._validate_spec(spec)
|
|
158
|
+
|
|
159
|
+
# Generate Lua code
|
|
160
|
+
lua_code = self._generate_lua_dissector(spec)
|
|
161
|
+
|
|
162
|
+
# Validate Lua syntax
|
|
163
|
+
if not self._validate_lua_syntax(lua_code):
|
|
164
|
+
raise RuntimeError("Generated Lua code has syntax errors")
|
|
165
|
+
|
|
166
|
+
# Write Lua file
|
|
167
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
168
|
+
output_path.write_text(lua_code, encoding="utf-8")
|
|
169
|
+
logger.info(f"Generated Lua dissector: {output_path}")
|
|
170
|
+
|
|
171
|
+
# Generate test PCAP if requested
|
|
172
|
+
pcap_path = None
|
|
173
|
+
if self.config.generate_test_pcap and sample_messages:
|
|
174
|
+
pcap_path = output_path.with_suffix(".pcap")
|
|
175
|
+
self._generate_test_pcap(sample_messages, pcap_path)
|
|
176
|
+
logger.info(f"Generated test PCAP: {pcap_path}")
|
|
177
|
+
|
|
178
|
+
return output_path, pcap_path
|
|
179
|
+
|
|
180
|
+
def _validate_spec(self, spec: ProtocolSpec) -> None:
|
|
181
|
+
"""Validate protocol specification.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
spec: Protocol specification to validate.
|
|
185
|
+
|
|
186
|
+
Raises:
|
|
187
|
+
ValueError: If spec is invalid.
|
|
188
|
+
"""
|
|
189
|
+
validate_protocol_spec(spec)
|
|
190
|
+
|
|
191
|
+
# Validate fields
|
|
192
|
+
for field in spec.fields:
|
|
193
|
+
if not field.name:
|
|
194
|
+
raise ValueError("Field name is required")
|
|
195
|
+
if field.field_type not in {
|
|
196
|
+
"uint8",
|
|
197
|
+
"uint16",
|
|
198
|
+
"uint32",
|
|
199
|
+
"bytes",
|
|
200
|
+
"string",
|
|
201
|
+
"constant",
|
|
202
|
+
"checksum",
|
|
203
|
+
}:
|
|
204
|
+
raise ValueError(f"Unsupported field type: {field.field_type}")
|
|
205
|
+
|
|
206
|
+
def _generate_lua_dissector(self, spec: ProtocolSpec) -> str:
|
|
207
|
+
"""Generate complete Lua dissector code.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
spec: Protocol specification.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Complete Lua dissector code as string.
|
|
214
|
+
"""
|
|
215
|
+
sections = [
|
|
216
|
+
self._generate_header(spec),
|
|
217
|
+
self._generate_protocol_declaration(spec),
|
|
218
|
+
self._generate_field_declarations(spec),
|
|
219
|
+
self._generate_crc_validator(spec) if self.config.include_crc_validation else "",
|
|
220
|
+
self._generate_dissector_function(spec),
|
|
221
|
+
self._generate_registration(spec),
|
|
222
|
+
]
|
|
223
|
+
|
|
224
|
+
return "\n\n".join(s for s in sections if s)
|
|
225
|
+
|
|
226
|
+
def _generate_header(self, spec: ProtocolSpec) -> str:
|
|
227
|
+
"""Generate Lua file header with installation instructions.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
spec: Protocol specification.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
Lua header comment block.
|
|
234
|
+
"""
|
|
235
|
+
return f"""-- Wireshark Lua Dissector for {spec.name}
|
|
236
|
+
-- Generated by Oscura on {datetime.now(UTC).isoformat()}
|
|
237
|
+
--
|
|
238
|
+
-- Installation:
|
|
239
|
+
-- Copy this file to your Wireshark plugins directory:
|
|
240
|
+
-- - Linux: ~/.local/lib/wireshark/plugins/
|
|
241
|
+
-- - macOS: ~/.config/wireshark/plugins/
|
|
242
|
+
-- - Windows: %APPDATA%\\Wireshark\\plugins\\
|
|
243
|
+
--
|
|
244
|
+
-- Protocol: {spec.name}
|
|
245
|
+
-- Frame Format: {spec.frame_format}
|
|
246
|
+
-- Baud Rate: {spec.baud_rate} bps
|
|
247
|
+
-- Frame Length: {spec.frame_length if spec.frame_length else "Variable"} bytes
|
|
248
|
+
-- Sync Pattern: {spec.sync_pattern}
|
|
249
|
+
-- Checksum: {spec.checksum_type if spec.checksum_type else "None"}
|
|
250
|
+
-- Confidence: {spec.confidence:.2f}
|
|
251
|
+
--
|
|
252
|
+
-- Wireshark Version: {self.config.wireshark_version}
|
|
253
|
+
"""
|
|
254
|
+
|
|
255
|
+
def _generate_protocol_declaration(self, spec: ProtocolSpec) -> str:
|
|
256
|
+
"""Generate protocol declaration (Proto object).
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
spec: Protocol specification.
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
Lua code for protocol declaration.
|
|
263
|
+
"""
|
|
264
|
+
proto_var = spec.name.lower().replace(" ", "_").replace("-", "_")
|
|
265
|
+
return f"""-- Protocol declaration
|
|
266
|
+
local {proto_var}_proto = Proto("{proto_var}", "{spec.name}")"""
|
|
267
|
+
|
|
268
|
+
def _generate_field_declarations(self, spec: ProtocolSpec) -> str:
|
|
269
|
+
"""Generate ProtoField declarations for all fields.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
spec: Protocol specification.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Lua code for field declarations.
|
|
276
|
+
"""
|
|
277
|
+
proto_var = spec.name.lower().replace(" ", "_").replace("-", "_")
|
|
278
|
+
lines = ["-- Field declarations"]
|
|
279
|
+
|
|
280
|
+
for field in spec.fields:
|
|
281
|
+
field_var = f"f_{field.name}"
|
|
282
|
+
field_path = f"{proto_var}.{field.name}"
|
|
283
|
+
display_name = field.name.replace("_", " ").title()
|
|
284
|
+
|
|
285
|
+
# Determine ProtoField type and base
|
|
286
|
+
if field.field_type == "uint8":
|
|
287
|
+
proto_type = "uint8"
|
|
288
|
+
base = "base.HEX"
|
|
289
|
+
elif field.field_type == "uint16":
|
|
290
|
+
proto_type = "uint16"
|
|
291
|
+
base = "base.HEX"
|
|
292
|
+
elif field.field_type == "uint32":
|
|
293
|
+
proto_type = "uint32"
|
|
294
|
+
base = "base.HEX"
|
|
295
|
+
elif field.field_type == "string":
|
|
296
|
+
proto_type = "string"
|
|
297
|
+
base = "base.UNICODE"
|
|
298
|
+
elif field.field_type in ("bytes", "constant"):
|
|
299
|
+
proto_type = "bytes"
|
|
300
|
+
base = "base.SPACE"
|
|
301
|
+
else: # checksum
|
|
302
|
+
proto_type = "uint16" # Assume 16-bit checksum
|
|
303
|
+
base = "base.HEX"
|
|
304
|
+
|
|
305
|
+
# Handle enum values
|
|
306
|
+
if hasattr(field, "enum") and field.enum:
|
|
307
|
+
# Generate value_string table
|
|
308
|
+
enum_var = f"vs_{field.name}"
|
|
309
|
+
lines.append(f"local {enum_var} = {{")
|
|
310
|
+
for key, value in field.enum.items():
|
|
311
|
+
lines.append(f' [{key}] = "{value}",')
|
|
312
|
+
lines.append("}")
|
|
313
|
+
lines.append(
|
|
314
|
+
f"local {field_var} = ProtoField.{proto_type}("
|
|
315
|
+
f'"{field_path}", "{display_name}", {base}, {enum_var})'
|
|
316
|
+
)
|
|
317
|
+
else:
|
|
318
|
+
lines.append(
|
|
319
|
+
f"local {field_var} = ProtoField.{proto_type}("
|
|
320
|
+
f'"{field_path}", "{display_name}", {base})'
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
# Register fields with protocol
|
|
324
|
+
lines.append("")
|
|
325
|
+
lines.append(f"{proto_var}_proto.fields = {{")
|
|
326
|
+
for field in spec.fields:
|
|
327
|
+
lines.append(f" f_{field.name},")
|
|
328
|
+
lines.append("}")
|
|
329
|
+
|
|
330
|
+
return "\n".join(lines)
|
|
331
|
+
|
|
332
|
+
def _generate_crc_validator(self, spec: ProtocolSpec) -> str:
|
|
333
|
+
"""Generate CRC validation function in Lua.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
spec: Protocol specification.
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
Lua CRC validation function code.
|
|
340
|
+
"""
|
|
341
|
+
if not spec.checksum_type or spec.checksum_type not in ("crc8", "crc16", "crc32"):
|
|
342
|
+
return ""
|
|
343
|
+
|
|
344
|
+
# Get CRC parameters if available
|
|
345
|
+
crc_info = getattr(spec, "crc_info", None)
|
|
346
|
+
if crc_info:
|
|
347
|
+
return self._generate_crc_function_from_params(crc_info)
|
|
348
|
+
|
|
349
|
+
# Default CRC implementations for common types
|
|
350
|
+
if spec.checksum_type == "crc16":
|
|
351
|
+
return """-- CRC-16-CCITT validation
|
|
352
|
+
function validate_crc16(buffer, offset, length)
|
|
353
|
+
local crc = 0xFFFF
|
|
354
|
+
local poly = 0x1021
|
|
355
|
+
|
|
356
|
+
for i = 0, length - 1 do
|
|
357
|
+
local byte = buffer(offset + i, 1):uint()
|
|
358
|
+
crc = bit32.bxor(crc, bit32.lshift(byte, 8))
|
|
359
|
+
|
|
360
|
+
for bit = 0, 7 do
|
|
361
|
+
if bit32.band(crc, 0x8000) ~= 0 then
|
|
362
|
+
crc = bit32.band(bit32.bxor(bit32.lshift(crc, 1), poly), 0xFFFF)
|
|
363
|
+
else
|
|
364
|
+
crc = bit32.band(bit32.lshift(crc, 1), 0xFFFF)
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
return crc
|
|
370
|
+
end"""
|
|
371
|
+
elif spec.checksum_type == "crc8":
|
|
372
|
+
return """-- CRC-8 validation
|
|
373
|
+
function validate_crc8(buffer, offset, length)
|
|
374
|
+
local crc = 0x00
|
|
375
|
+
local poly = 0x07
|
|
376
|
+
|
|
377
|
+
for i = 0, length - 1 do
|
|
378
|
+
local byte = buffer(offset + i, 1):uint()
|
|
379
|
+
crc = bit32.bxor(crc, byte)
|
|
380
|
+
|
|
381
|
+
for bit = 0, 7 do
|
|
382
|
+
if bit32.band(crc, 0x80) ~= 0 then
|
|
383
|
+
crc = bit32.band(bit32.bxor(bit32.lshift(crc, 1), poly), 0xFF)
|
|
384
|
+
else
|
|
385
|
+
crc = bit32.band(bit32.lshift(crc, 1), 0xFF)
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
return crc
|
|
391
|
+
end"""
|
|
392
|
+
else: # crc32
|
|
393
|
+
return """-- CRC-32 validation
|
|
394
|
+
function validate_crc32(buffer, offset, length)
|
|
395
|
+
local crc = 0xFFFFFFFF
|
|
396
|
+
local poly = 0x04C11DB7
|
|
397
|
+
|
|
398
|
+
for i = 0, length - 1 do
|
|
399
|
+
local byte = buffer(offset + i, 1):uint()
|
|
400
|
+
crc = bit32.bxor(crc, bit32.lshift(byte, 24))
|
|
401
|
+
|
|
402
|
+
for bit = 0, 7 do
|
|
403
|
+
if bit32.band(crc, 0x80000000) ~= 0 then
|
|
404
|
+
crc = bit32.band(bit32.bxor(bit32.lshift(crc, 1), poly), 0xFFFFFFFF)
|
|
405
|
+
else
|
|
406
|
+
crc = bit32.band(bit32.lshift(crc, 1), 0xFFFFFFFF)
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
return bit32.bxor(crc, 0xFFFFFFFF)
|
|
412
|
+
end"""
|
|
413
|
+
|
|
414
|
+
def _generate_crc_function_from_params(self, crc_info: CRCParameters) -> str:
|
|
415
|
+
"""Generate CRC function from CRCParameters.
|
|
416
|
+
|
|
417
|
+
Args:
|
|
418
|
+
crc_info: CRC parameters from reverse engineering.
|
|
419
|
+
|
|
420
|
+
Returns:
|
|
421
|
+
Lua CRC function code.
|
|
422
|
+
"""
|
|
423
|
+
width = crc_info.width
|
|
424
|
+
poly = crc_info.polynomial
|
|
425
|
+
init = crc_info.init
|
|
426
|
+
xor_out = crc_info.xor_out
|
|
427
|
+
mask = (1 << width) - 1
|
|
428
|
+
|
|
429
|
+
func_name = f"validate_crc{width}"
|
|
430
|
+
|
|
431
|
+
code = [
|
|
432
|
+
f"-- CRC-{width} validation (Custom parameters)",
|
|
433
|
+
f"-- Polynomial: 0x{poly:0{width // 4}x}",
|
|
434
|
+
f"-- Init: 0x{init:0{width // 4}x}",
|
|
435
|
+
f"-- XorOut: 0x{xor_out:0{width // 4}x}",
|
|
436
|
+
f"-- ReflectIn: {str(crc_info.reflect_in).lower()}",
|
|
437
|
+
f"-- ReflectOut: {str(crc_info.reflect_out).lower()}",
|
|
438
|
+
f"function {func_name}(buffer, offset, length)",
|
|
439
|
+
f" local crc = 0x{init:0{width // 4}x}",
|
|
440
|
+
f" local poly = 0x{poly:0{width // 4}x}",
|
|
441
|
+
f" local mask = 0x{mask:0{width // 4}x}",
|
|
442
|
+
" ",
|
|
443
|
+
" for i = 0, length - 1 do",
|
|
444
|
+
" local byte = buffer(offset + i, 1):uint()",
|
|
445
|
+
]
|
|
446
|
+
|
|
447
|
+
if crc_info.reflect_in:
|
|
448
|
+
code.extend(
|
|
449
|
+
[
|
|
450
|
+
" -- Reflect input byte",
|
|
451
|
+
" local reflected = 0",
|
|
452
|
+
" for b = 0, 7 do",
|
|
453
|
+
" if bit32.band(byte, bit32.lshift(1, b)) ~= 0 then",
|
|
454
|
+
" reflected = bit32.bor(reflected, bit32.lshift(1, 7 - b))",
|
|
455
|
+
" end",
|
|
456
|
+
" end",
|
|
457
|
+
" byte = reflected",
|
|
458
|
+
]
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
code.extend(
|
|
462
|
+
[
|
|
463
|
+
f" crc = bit32.bxor(crc, bit32.lshift(byte, {width - 8}))",
|
|
464
|
+
" ",
|
|
465
|
+
" for bit = 0, 7 do",
|
|
466
|
+
f" if bit32.band(crc, 0x{1 << (width - 1):0{width // 4}x}) ~= 0 then",
|
|
467
|
+
" crc = bit32.band(bit32.bxor(bit32.lshift(crc, 1), poly), mask)",
|
|
468
|
+
" else",
|
|
469
|
+
" crc = bit32.band(bit32.lshift(crc, 1), mask)",
|
|
470
|
+
" end",
|
|
471
|
+
" end",
|
|
472
|
+
" end",
|
|
473
|
+
" ",
|
|
474
|
+
]
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
if crc_info.reflect_out:
|
|
478
|
+
code.extend(
|
|
479
|
+
[
|
|
480
|
+
" -- Reflect output CRC",
|
|
481
|
+
" local reflected = 0",
|
|
482
|
+
f" for b = 0, {width - 1} do",
|
|
483
|
+
" if bit32.band(crc, bit32.lshift(1, b)) ~= 0 then",
|
|
484
|
+
f" reflected = bit32.bor(reflected, bit32.lshift(1, {width - 1} - b))",
|
|
485
|
+
" end",
|
|
486
|
+
" end",
|
|
487
|
+
" crc = reflected",
|
|
488
|
+
]
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
code.extend(
|
|
492
|
+
[
|
|
493
|
+
f" return bit32.bxor(crc, 0x{xor_out:0{width // 4}x})",
|
|
494
|
+
"end",
|
|
495
|
+
]
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
return "\n".join(code)
|
|
499
|
+
|
|
500
|
+
def _generate_dissector_function(self, spec: ProtocolSpec) -> str:
|
|
501
|
+
"""Generate main dissector function.
|
|
502
|
+
|
|
503
|
+
Args:
|
|
504
|
+
spec: Protocol specification.
|
|
505
|
+
|
|
506
|
+
Returns:
|
|
507
|
+
Lua dissector function code.
|
|
508
|
+
"""
|
|
509
|
+
proto_var = spec.name.lower().replace(" ", "_").replace("-", "_")
|
|
510
|
+
min_length = spec.frame_length if spec.frame_length else 1
|
|
511
|
+
|
|
512
|
+
lines = [
|
|
513
|
+
"-- Main dissector function",
|
|
514
|
+
f"function {proto_var}_proto.dissector(buffer, pinfo, tree)",
|
|
515
|
+
" -- Check minimum length",
|
|
516
|
+
f" if buffer:len() < {min_length} then",
|
|
517
|
+
" return 0",
|
|
518
|
+
" end",
|
|
519
|
+
" ",
|
|
520
|
+
f' pinfo.cols.protocol = "{spec.name}"',
|
|
521
|
+
" ",
|
|
522
|
+
f' local subtree = tree:add({proto_var}_proto, buffer(), "{spec.name}")',
|
|
523
|
+
" local offset = 0",
|
|
524
|
+
" ",
|
|
525
|
+
]
|
|
526
|
+
|
|
527
|
+
# Extract fields
|
|
528
|
+
for field in spec.fields:
|
|
529
|
+
field_size = field.size if isinstance(field.size, int) else 1
|
|
530
|
+
|
|
531
|
+
# Determine buffer reader
|
|
532
|
+
if field.field_type == "uint8":
|
|
533
|
+
reader = f"buffer(offset, {field_size}):uint()"
|
|
534
|
+
elif field.field_type == "uint16" or field.field_type == "uint32":
|
|
535
|
+
endian = getattr(field, "endian", "big")
|
|
536
|
+
if endian == "little":
|
|
537
|
+
reader = f"buffer(offset, {field_size}):le_uint()"
|
|
538
|
+
else:
|
|
539
|
+
reader = f"buffer(offset, {field_size}):uint()"
|
|
540
|
+
elif field.field_type == "string":
|
|
541
|
+
reader = f"buffer(offset, {field_size}):string()"
|
|
542
|
+
else: # bytes, constant, checksum
|
|
543
|
+
reader = f"buffer(offset, {field_size})"
|
|
544
|
+
|
|
545
|
+
lines.append(f" -- Field: {field.name}")
|
|
546
|
+
lines.append(f" subtree:add(f_{field.name}, {reader})")
|
|
547
|
+
lines.append(f" offset = offset + {field_size}")
|
|
548
|
+
lines.append(" ")
|
|
549
|
+
|
|
550
|
+
# Add CRC validation
|
|
551
|
+
if spec.checksum_type and spec.checksum_position is not None:
|
|
552
|
+
width_map = {"crc8": 8, "crc16": 16, "crc32": 32}
|
|
553
|
+
width = width_map.get(spec.checksum_type, 16)
|
|
554
|
+
crc_size = width // 8
|
|
555
|
+
|
|
556
|
+
if spec.checksum_position == -1:
|
|
557
|
+
# CRC is at the end
|
|
558
|
+
lines.extend(
|
|
559
|
+
[
|
|
560
|
+
" -- Validate CRC",
|
|
561
|
+
f" local data_length = buffer:len() - {crc_size}",
|
|
562
|
+
f" local computed_crc = validate_crc{width}(buffer, 0, data_length)",
|
|
563
|
+
f" local packet_crc = buffer(data_length, {crc_size}):uint()",
|
|
564
|
+
" if computed_crc == packet_crc then",
|
|
565
|
+
f' subtree:add(buffer(data_length, {crc_size}), "CRC: Valid")',
|
|
566
|
+
" else",
|
|
567
|
+
' subtree:add_expert_info(PI_CHECKSUM, PI_ERROR, "CRC: Invalid")',
|
|
568
|
+
" end",
|
|
569
|
+
" ",
|
|
570
|
+
]
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
lines.extend(
|
|
574
|
+
[
|
|
575
|
+
" return buffer:len()",
|
|
576
|
+
"end",
|
|
577
|
+
]
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
return "\n".join(lines)
|
|
581
|
+
|
|
582
|
+
def _generate_registration(self, spec: ProtocolSpec) -> str:
|
|
583
|
+
"""Generate protocol registration code.
|
|
584
|
+
|
|
585
|
+
Args:
|
|
586
|
+
spec: Protocol specification.
|
|
587
|
+
|
|
588
|
+
Returns:
|
|
589
|
+
Lua registration code.
|
|
590
|
+
"""
|
|
591
|
+
proto_var = spec.name.lower().replace(" ", "_").replace("-", "_")
|
|
592
|
+
|
|
593
|
+
if self.config.port is None:
|
|
594
|
+
return f"""-- Protocol registration (manual)
|
|
595
|
+
-- To use this dissector:
|
|
596
|
+
-- 1. Open Wireshark
|
|
597
|
+
-- 2. Right-click on a packet
|
|
598
|
+
-- 3. Select "Decode As..."
|
|
599
|
+
-- 4. Choose "{spec.name}"
|
|
600
|
+
--
|
|
601
|
+
-- Or register on a specific port by adding:
|
|
602
|
+
-- DissectorTable.get("udp.port"):add(YOUR_PORT, {proto_var}_proto)"""
|
|
603
|
+
|
|
604
|
+
return f"""-- Protocol registration
|
|
605
|
+
-- Register on UDP port {self.config.port}
|
|
606
|
+
local udp_port = DissectorTable.get("udp.port")
|
|
607
|
+
udp_port:add({self.config.port}, {proto_var}_proto)
|
|
608
|
+
|
|
609
|
+
-- Also register on TCP port {self.config.port}
|
|
610
|
+
local tcp_port = DissectorTable.get("tcp.port")
|
|
611
|
+
tcp_port:add({self.config.port}, {proto_var}_proto)"""
|
|
612
|
+
|
|
613
|
+
def _validate_lua_syntax(self, lua_code: str) -> bool:
|
|
614
|
+
"""Validate Lua syntax using luac if available.
|
|
615
|
+
|
|
616
|
+
Args:
|
|
617
|
+
lua_code: Lua code to validate.
|
|
618
|
+
|
|
619
|
+
Returns:
|
|
620
|
+
True if syntax is valid or luac not available, False if errors found.
|
|
621
|
+
"""
|
|
622
|
+
try:
|
|
623
|
+
# Try to run luac syntax check
|
|
624
|
+
result = subprocess.run(
|
|
625
|
+
["luac", "-p", "-"],
|
|
626
|
+
input=lua_code.encode("utf-8"),
|
|
627
|
+
capture_output=True,
|
|
628
|
+
timeout=5,
|
|
629
|
+
check=False,
|
|
630
|
+
)
|
|
631
|
+
if result.returncode != 0:
|
|
632
|
+
logger.error(f"Lua syntax error: {result.stderr.decode('utf-8')}")
|
|
633
|
+
return False
|
|
634
|
+
logger.info("Lua syntax validation passed")
|
|
635
|
+
return True
|
|
636
|
+
except FileNotFoundError:
|
|
637
|
+
# luac not available, skip validation
|
|
638
|
+
logger.warning("luac not found, skipping Lua syntax validation")
|
|
639
|
+
return True
|
|
640
|
+
except subprocess.TimeoutExpired:
|
|
641
|
+
logger.warning("Lua syntax validation timed out")
|
|
642
|
+
return True
|
|
643
|
+
except Exception as e:
|
|
644
|
+
logger.warning(f"Lua syntax validation failed: {e}")
|
|
645
|
+
return True
|
|
646
|
+
|
|
647
|
+
def _generate_test_pcap(self, sample_messages: list[bytes], output_path: Path) -> None:
|
|
648
|
+
"""Generate test PCAP file with sample messages.
|
|
649
|
+
|
|
650
|
+
Creates a PCAP file with UDP packets containing the sample messages.
|
|
651
|
+
This allows testing the dissector in Wireshark.
|
|
652
|
+
|
|
653
|
+
Args:
|
|
654
|
+
sample_messages: List of protocol messages to include.
|
|
655
|
+
output_path: Path for output .pcap file.
|
|
656
|
+
|
|
657
|
+
Raises:
|
|
658
|
+
OSError: If file writing fails.
|
|
659
|
+
"""
|
|
660
|
+
pcap_data = bytearray(self._build_pcap_header())
|
|
661
|
+
dst_port = self.config.port if self.config.port else 5000
|
|
662
|
+
|
|
663
|
+
for i, message in enumerate(sample_messages):
|
|
664
|
+
packet = self._build_udp_packet(i, message, dst_port)
|
|
665
|
+
pcap_data.extend(self._build_packet_header(packet))
|
|
666
|
+
|
|
667
|
+
output_path.write_bytes(pcap_data)
|
|
668
|
+
|
|
669
|
+
def _build_pcap_header(self) -> bytes:
|
|
670
|
+
"""Build PCAP global header."""
|
|
671
|
+
return struct.pack(
|
|
672
|
+
"<IHHIIII",
|
|
673
|
+
0xA1B2C3D4,
|
|
674
|
+
2,
|
|
675
|
+
4,
|
|
676
|
+
0,
|
|
677
|
+
0,
|
|
678
|
+
65535,
|
|
679
|
+
1, # Magic, versions, timezone, accuracy, snaplen, linktype
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
def _build_udp_packet(self, index: int, message: bytes, dst_port: int) -> bytes:
|
|
683
|
+
"""Build complete UDP packet with Ethernet/IP/UDP headers."""
|
|
684
|
+
src_ip = bytes([192, 168, 1, 1])
|
|
685
|
+
dst_ip = bytes([192, 168, 1, 2])
|
|
686
|
+
|
|
687
|
+
eth_header = (
|
|
688
|
+
bytes([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) # Dst MAC
|
|
689
|
+
+ bytes([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]) # Src MAC
|
|
690
|
+
+ bytes([0x08, 0x00]) # IPv4
|
|
691
|
+
)
|
|
692
|
+
|
|
693
|
+
ip_header = self._build_ip_header(index, len(message), src_ip, dst_ip)
|
|
694
|
+
udp_header = struct.pack(">HHHH", 50000 + index, dst_port, 8 + len(message), 0)
|
|
695
|
+
|
|
696
|
+
return eth_header + ip_header + udp_header + message
|
|
697
|
+
|
|
698
|
+
def _build_ip_header(
|
|
699
|
+
self, packet_id: int, payload_len: int, src_ip: bytes, dst_ip: bytes
|
|
700
|
+
) -> bytes:
|
|
701
|
+
"""Build IP header with checksum calculation."""
|
|
702
|
+
ip_total_length = 20 + 8 + payload_len
|
|
703
|
+
header = struct.pack(
|
|
704
|
+
">BBHHHBBH4s4s", 0x45, 0, ip_total_length, packet_id + 1, 0, 64, 17, 0, src_ip, dst_ip
|
|
705
|
+
)
|
|
706
|
+
checksum = self._calculate_ip_checksum(header)
|
|
707
|
+
return struct.pack(
|
|
708
|
+
">BBHHHBBH4s4s",
|
|
709
|
+
0x45,
|
|
710
|
+
0,
|
|
711
|
+
ip_total_length,
|
|
712
|
+
packet_id + 1,
|
|
713
|
+
0,
|
|
714
|
+
64,
|
|
715
|
+
17,
|
|
716
|
+
checksum,
|
|
717
|
+
src_ip,
|
|
718
|
+
dst_ip,
|
|
719
|
+
)
|
|
720
|
+
|
|
721
|
+
def _build_packet_header(self, packet: bytes) -> bytes:
|
|
722
|
+
"""Build PCAP packet header."""
|
|
723
|
+
timestamp = int(datetime.now(UTC).timestamp())
|
|
724
|
+
return struct.pack("<IIII", timestamp, 0, len(packet), len(packet)) + packet
|
|
725
|
+
|
|
726
|
+
def _calculate_ip_checksum(self, header: bytes) -> int:
|
|
727
|
+
"""Calculate IP header checksum.
|
|
728
|
+
|
|
729
|
+
Args:
|
|
730
|
+
header: IP header bytes.
|
|
731
|
+
|
|
732
|
+
Returns:
|
|
733
|
+
Checksum value.
|
|
734
|
+
"""
|
|
735
|
+
# Sum all 16-bit words
|
|
736
|
+
checksum = 0
|
|
737
|
+
for i in range(0, len(header), 2):
|
|
738
|
+
word = (header[i] << 8) + (header[i + 1] if i + 1 < len(header) else 0)
|
|
739
|
+
checksum += word
|
|
740
|
+
|
|
741
|
+
# Add carry bits
|
|
742
|
+
while checksum >> 16:
|
|
743
|
+
checksum = (checksum & 0xFFFF) + (checksum >> 16)
|
|
744
|
+
|
|
745
|
+
# One's complement
|
|
746
|
+
return ~checksum & 0xFFFF
|