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,596 @@
|
|
|
1
|
+
"""Grammar-based test vector generation for protocol fuzzing and validation.
|
|
2
|
+
|
|
3
|
+
This module provides intelligent test vector generation from protocol specifications,
|
|
4
|
+
supporting coverage-based testing, edge case generation, and mutation-based fuzzing.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
>>> from oscura.validation import GrammarTestGenerator, TestGenerationConfig
|
|
8
|
+
>>> from oscura.sessions import ProtocolSpec, FieldHypothesis
|
|
9
|
+
>>>
|
|
10
|
+
>>> # Define protocol spec
|
|
11
|
+
>>> spec = ProtocolSpec(
|
|
12
|
+
... name="SimpleProtocol",
|
|
13
|
+
... fields=[
|
|
14
|
+
... FieldHypothesis("header", 0, 1, "constant", 0.99, {"value": 0xAA}),
|
|
15
|
+
... FieldHypothesis("cmd", 1, 1, "data", 0.85),
|
|
16
|
+
... FieldHypothesis("length", 2, 1, "data", 0.90),
|
|
17
|
+
... FieldHypothesis("checksum", 3, 1, "checksum", 0.95),
|
|
18
|
+
... ]
|
|
19
|
+
... )
|
|
20
|
+
>>>
|
|
21
|
+
>>> # Generate comprehensive test suite
|
|
22
|
+
>>> config = TestGenerationConfig(strategy="all", num_tests=50)
|
|
23
|
+
>>> generator = GrammarTestGenerator(config)
|
|
24
|
+
>>> tests = generator.generate_tests(spec)
|
|
25
|
+
>>>
|
|
26
|
+
>>> # Export as PCAP
|
|
27
|
+
>>> generator.export_pcap(tests.valid_messages, Path("valid_tests.pcap"))
|
|
28
|
+
>>>
|
|
29
|
+
>>> # Export as pytest
|
|
30
|
+
>>> generator.export_pytest(tests.valid_messages, Path("test_protocol.py"))
|
|
31
|
+
|
|
32
|
+
References:
|
|
33
|
+
AFL Mutation Strategies: https://lcamtuf.coredump.cx/afl/technical_details.txt
|
|
34
|
+
Hypothesis Strategies: https://hypothesis.readthedocs.io/
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
import random
|
|
40
|
+
from dataclasses import dataclass, field
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
43
|
+
|
|
44
|
+
if TYPE_CHECKING:
|
|
45
|
+
from oscura.sessions.blackbox import FieldHypothesis, ProtocolSpec
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class TestGenerationConfig:
|
|
50
|
+
"""Configuration for test vector generation.
|
|
51
|
+
|
|
52
|
+
Attributes:
|
|
53
|
+
strategy: Generation strategy ("coverage", "fuzzing", "edge_cases", "all").
|
|
54
|
+
num_tests: Number of test vectors to generate.
|
|
55
|
+
include_valid: Include valid messages in output.
|
|
56
|
+
include_invalid: Include invalid messages in output.
|
|
57
|
+
mutate_checksums: Generate messages with corrupted checksums.
|
|
58
|
+
boundary_values: Generate boundary value test cases.
|
|
59
|
+
export_format: Export format ("pcap", "binary", "pytest").
|
|
60
|
+
|
|
61
|
+
Example:
|
|
62
|
+
>>> config = TestGenerationConfig(
|
|
63
|
+
... strategy="coverage",
|
|
64
|
+
... num_tests=100,
|
|
65
|
+
... include_valid=True,
|
|
66
|
+
... include_invalid=True
|
|
67
|
+
... )
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
strategy: Literal["coverage", "fuzzing", "edge_cases", "all"] = "coverage"
|
|
71
|
+
num_tests: int = 100
|
|
72
|
+
include_valid: bool = True
|
|
73
|
+
include_invalid: bool = True
|
|
74
|
+
mutate_checksums: bool = True
|
|
75
|
+
boundary_values: bool = True
|
|
76
|
+
export_format: Literal["pcap", "binary", "pytest"] = "pcap"
|
|
77
|
+
|
|
78
|
+
def __post_init__(self) -> None:
|
|
79
|
+
"""Validate configuration after initialization."""
|
|
80
|
+
if self.num_tests <= 0:
|
|
81
|
+
raise ValueError(f"num_tests must be positive, got {self.num_tests}")
|
|
82
|
+
if self.strategy not in {"coverage", "fuzzing", "edge_cases", "all"}:
|
|
83
|
+
raise ValueError(f"Invalid strategy: {self.strategy}")
|
|
84
|
+
if self.export_format not in {"pcap", "binary", "pytest"}:
|
|
85
|
+
raise ValueError(f"Invalid export_format: {self.export_format}")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass
|
|
89
|
+
class GeneratedTests:
|
|
90
|
+
"""Container for generated test vectors.
|
|
91
|
+
|
|
92
|
+
Attributes:
|
|
93
|
+
valid_messages: Valid protocol messages.
|
|
94
|
+
invalid_messages: Invalid messages (corrupted checksums, bad lengths).
|
|
95
|
+
edge_cases: Boundary value test cases.
|
|
96
|
+
fuzzing_corpus: Mutation-based fuzzing inputs.
|
|
97
|
+
test_descriptions: Human-readable description for each test.
|
|
98
|
+
coverage_report: Coverage statistics (fields covered, value ranges).
|
|
99
|
+
|
|
100
|
+
Example:
|
|
101
|
+
>>> tests = GeneratedTests(
|
|
102
|
+
... valid_messages=[b"\\xaa\\x01\\x00\\x12"],
|
|
103
|
+
... invalid_messages=[b"\\xaa\\x01\\x00\\xff"],
|
|
104
|
+
... test_descriptions=["Valid message", "Bad checksum"]
|
|
105
|
+
... )
|
|
106
|
+
>>> print(f"Total tests: {len(tests.all_messages)}")
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
valid_messages: list[bytes] = field(default_factory=list)
|
|
110
|
+
invalid_messages: list[bytes] = field(default_factory=list)
|
|
111
|
+
edge_cases: list[bytes] = field(default_factory=list)
|
|
112
|
+
fuzzing_corpus: list[bytes] = field(default_factory=list)
|
|
113
|
+
test_descriptions: list[str] = field(default_factory=list)
|
|
114
|
+
coverage_report: dict[str, Any] = field(default_factory=dict)
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def all_messages(self) -> list[bytes]:
|
|
118
|
+
"""Get all generated messages.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Combined list of all message types.
|
|
122
|
+
"""
|
|
123
|
+
return self.valid_messages + self.invalid_messages + self.edge_cases + self.fuzzing_corpus
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class GrammarTestGenerator:
|
|
127
|
+
"""Grammar-based test vector generator for protocol validation and fuzzing.
|
|
128
|
+
|
|
129
|
+
Generates comprehensive test suites from protocol specifications using
|
|
130
|
+
coverage-based generation, boundary value analysis, and mutation-based fuzzing.
|
|
131
|
+
|
|
132
|
+
Attributes:
|
|
133
|
+
config: Test generation configuration.
|
|
134
|
+
|
|
135
|
+
Example:
|
|
136
|
+
>>> config = TestGenerationConfig(strategy="coverage", num_tests=50)
|
|
137
|
+
>>> generator = GrammarTestGenerator(config)
|
|
138
|
+
>>> tests = generator.generate_tests(protocol_spec)
|
|
139
|
+
>>> print(f"Generated {len(tests.valid_messages)} valid tests")
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
def __init__(self, config: TestGenerationConfig) -> None:
|
|
143
|
+
"""Initialize test generator.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
config: Test generation configuration.
|
|
147
|
+
"""
|
|
148
|
+
self.config = config
|
|
149
|
+
self._rng = random.Random(42) # Deterministic for reproducibility
|
|
150
|
+
|
|
151
|
+
def generate_tests(self, spec: ProtocolSpec) -> GeneratedTests:
|
|
152
|
+
"""Generate comprehensive test suite from protocol specification.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
spec: Protocol specification with field definitions.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Generated test vectors with coverage report.
|
|
159
|
+
|
|
160
|
+
Example:
|
|
161
|
+
>>> tests = generator.generate_tests(protocol_spec)
|
|
162
|
+
>>> print(f"Valid: {len(tests.valid_messages)}")
|
|
163
|
+
>>> print(f"Invalid: {len(tests.invalid_messages)}")
|
|
164
|
+
"""
|
|
165
|
+
result = GeneratedTests()
|
|
166
|
+
|
|
167
|
+
# Generate based on strategy
|
|
168
|
+
if self.config.strategy in {"coverage", "all"}:
|
|
169
|
+
if self.config.include_valid:
|
|
170
|
+
result.valid_messages = self._generate_valid_messages(spec)
|
|
171
|
+
|
|
172
|
+
if self.config.strategy in {"edge_cases", "all"}:
|
|
173
|
+
if self.config.boundary_values:
|
|
174
|
+
result.edge_cases = self._generate_edge_cases(spec)
|
|
175
|
+
|
|
176
|
+
if self.config.strategy in {"fuzzing", "all"}:
|
|
177
|
+
# Generate base messages first if needed
|
|
178
|
+
base_messages = result.valid_messages or self._generate_valid_messages(spec)
|
|
179
|
+
result.fuzzing_corpus = self._generate_fuzzing_corpus(spec, base_messages)
|
|
180
|
+
|
|
181
|
+
# Generate invalid messages with corrupted checksums
|
|
182
|
+
if self.config.include_invalid and self.config.mutate_checksums:
|
|
183
|
+
base_messages = result.valid_messages or self._generate_valid_messages(spec)
|
|
184
|
+
result.invalid_messages = self._corrupt_checksums(spec, base_messages)
|
|
185
|
+
|
|
186
|
+
# Generate test descriptions
|
|
187
|
+
result.test_descriptions = self._generate_descriptions(result, spec)
|
|
188
|
+
|
|
189
|
+
# Generate coverage report
|
|
190
|
+
result.coverage_report = self._generate_coverage_report(result, spec)
|
|
191
|
+
|
|
192
|
+
return result
|
|
193
|
+
|
|
194
|
+
def _generate_valid_messages(self, spec: ProtocolSpec) -> list[bytes]:
|
|
195
|
+
"""Generate valid messages covering all field combinations.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
spec: Protocol specification.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
List of valid protocol messages.
|
|
202
|
+
|
|
203
|
+
Example:
|
|
204
|
+
>>> messages = generator._generate_valid_messages(spec)
|
|
205
|
+
>>> all(len(msg) == spec_length for msg in messages)
|
|
206
|
+
True
|
|
207
|
+
"""
|
|
208
|
+
messages: list[bytes] = []
|
|
209
|
+
num_messages = min(self.config.num_tests, 50) # Cap at 50 for coverage
|
|
210
|
+
|
|
211
|
+
for _ in range(num_messages):
|
|
212
|
+
msg = bytearray()
|
|
213
|
+
|
|
214
|
+
for field_def in spec.fields:
|
|
215
|
+
field_bytes = self._generate_field_value(field_def, valid=True)
|
|
216
|
+
msg.extend(field_bytes)
|
|
217
|
+
|
|
218
|
+
messages.append(bytes(msg))
|
|
219
|
+
|
|
220
|
+
return messages
|
|
221
|
+
|
|
222
|
+
def _generate_field_value(self, field_def: FieldHypothesis, valid: bool = True) -> bytes:
|
|
223
|
+
"""Generate value for a single field.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
field_def: Field definition.
|
|
227
|
+
valid: Generate valid value (True) or potentially invalid (False).
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Field value as bytes.
|
|
231
|
+
|
|
232
|
+
Example:
|
|
233
|
+
>>> field = FieldHypothesis("counter", 0, 1, "counter", 0.9)
|
|
234
|
+
>>> value = generator._generate_field_value(field)
|
|
235
|
+
>>> len(value) == 1
|
|
236
|
+
True
|
|
237
|
+
"""
|
|
238
|
+
if field_def.field_type == "constant":
|
|
239
|
+
# Constants have fixed values
|
|
240
|
+
const_val = field_def.evidence.get("value", 0)
|
|
241
|
+
return self._pack_value(const_val, field_def.length)
|
|
242
|
+
|
|
243
|
+
if field_def.field_type == "counter":
|
|
244
|
+
# Counters increment
|
|
245
|
+
counter_val = self._rng.randint(0, (256**field_def.length) - 1)
|
|
246
|
+
return self._pack_value(counter_val, field_def.length)
|
|
247
|
+
|
|
248
|
+
if field_def.field_type == "checksum":
|
|
249
|
+
# Checksums are computed later (placeholder for now)
|
|
250
|
+
return b"\x00" * field_def.length
|
|
251
|
+
|
|
252
|
+
# Default: random data
|
|
253
|
+
return bytes(self._rng.randint(0, 255) for _ in range(field_def.length))
|
|
254
|
+
|
|
255
|
+
def _generate_edge_cases(self, spec: ProtocolSpec) -> list[bytes]:
|
|
256
|
+
"""Generate boundary value test cases.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
spec: Protocol specification.
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
List of edge case messages (0x00, 0xFF, overflow, underflow).
|
|
263
|
+
|
|
264
|
+
Example:
|
|
265
|
+
>>> edge_cases = generator._generate_edge_cases(spec)
|
|
266
|
+
>>> any(b"\\xff" in msg for msg in edge_cases)
|
|
267
|
+
True
|
|
268
|
+
"""
|
|
269
|
+
edge_cases: list[bytes] = []
|
|
270
|
+
|
|
271
|
+
# Boundary values: all 0x00, all 0xFF
|
|
272
|
+
msg_len = sum(f.length for f in spec.fields)
|
|
273
|
+
edge_cases.append(b"\x00" * msg_len)
|
|
274
|
+
edge_cases.append(b"\xff" * msg_len)
|
|
275
|
+
|
|
276
|
+
# Field-specific boundary values
|
|
277
|
+
for field_idx, field_def in enumerate(spec.fields):
|
|
278
|
+
if field_def.field_type in {"data", "counter"}:
|
|
279
|
+
# Min value (0)
|
|
280
|
+
msg = self._build_message_with_field_value(spec, field_idx, 0)
|
|
281
|
+
edge_cases.append(msg)
|
|
282
|
+
|
|
283
|
+
# Max value
|
|
284
|
+
max_val = (256**field_def.length) - 1
|
|
285
|
+
msg = self._build_message_with_field_value(spec, field_idx, max_val)
|
|
286
|
+
edge_cases.append(msg)
|
|
287
|
+
|
|
288
|
+
return edge_cases
|
|
289
|
+
|
|
290
|
+
def _generate_fuzzing_corpus(
|
|
291
|
+
self, spec: ProtocolSpec, base_messages: list[bytes]
|
|
292
|
+
) -> list[bytes]:
|
|
293
|
+
"""Generate mutation-based fuzzing corpus.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
spec: Protocol specification.
|
|
297
|
+
base_messages: Valid messages to mutate.
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
List of mutated messages for fuzzing.
|
|
301
|
+
|
|
302
|
+
Example:
|
|
303
|
+
>>> corpus = generator._generate_fuzzing_corpus(spec, [b"\\xaa\\x01\\x00"])
|
|
304
|
+
>>> len(corpus) > 0
|
|
305
|
+
True
|
|
306
|
+
"""
|
|
307
|
+
corpus: list[bytes] = []
|
|
308
|
+
num_mutations = min(self.config.num_tests, 100)
|
|
309
|
+
|
|
310
|
+
for _ in range(num_mutations):
|
|
311
|
+
if not base_messages:
|
|
312
|
+
break
|
|
313
|
+
|
|
314
|
+
# Select random base message
|
|
315
|
+
base_msg = self._rng.choice(base_messages)
|
|
316
|
+
mutated = self._mutate_message(base_msg)
|
|
317
|
+
corpus.append(mutated)
|
|
318
|
+
|
|
319
|
+
return corpus
|
|
320
|
+
|
|
321
|
+
def _mutate_message(self, message: bytes) -> bytes:
|
|
322
|
+
"""Apply random mutation to message (AFL-inspired).
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
message: Original message.
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Mutated message.
|
|
329
|
+
|
|
330
|
+
Mutations:
|
|
331
|
+
- Bit flip (flip single bit)
|
|
332
|
+
- Byte flip (flip entire byte)
|
|
333
|
+
- Byte insert (insert random byte)
|
|
334
|
+
- Byte delete (delete random byte)
|
|
335
|
+
- Arithmetic (increment/decrement value)
|
|
336
|
+
|
|
337
|
+
Example:
|
|
338
|
+
>>> original = b"\\xaa\\x55\\x01"
|
|
339
|
+
>>> mutated = generator._mutate_message(original)
|
|
340
|
+
>>> mutated != original
|
|
341
|
+
True
|
|
342
|
+
"""
|
|
343
|
+
if not message:
|
|
344
|
+
return message
|
|
345
|
+
|
|
346
|
+
msg = bytearray(message)
|
|
347
|
+
mutation_type = self._rng.choice(
|
|
348
|
+
["bit_flip", "byte_flip", "byte_insert", "byte_delete", "arithmetic"]
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
if mutation_type == "bit_flip":
|
|
352
|
+
# Flip random bit
|
|
353
|
+
pos = self._rng.randint(0, len(msg) - 1)
|
|
354
|
+
bit = self._rng.randint(0, 7)
|
|
355
|
+
msg[pos] ^= 1 << bit
|
|
356
|
+
|
|
357
|
+
elif mutation_type == "byte_flip":
|
|
358
|
+
# Flip entire byte
|
|
359
|
+
pos = self._rng.randint(0, len(msg) - 1)
|
|
360
|
+
msg[pos] ^= 0xFF
|
|
361
|
+
|
|
362
|
+
elif mutation_type == "byte_insert":
|
|
363
|
+
# Insert random byte
|
|
364
|
+
pos = self._rng.randint(0, len(msg))
|
|
365
|
+
msg.insert(pos, self._rng.randint(0, 255))
|
|
366
|
+
|
|
367
|
+
elif mutation_type == "byte_delete":
|
|
368
|
+
# Delete random byte (if message > 1 byte)
|
|
369
|
+
if len(msg) > 1:
|
|
370
|
+
pos = self._rng.randint(0, len(msg) - 1)
|
|
371
|
+
del msg[pos]
|
|
372
|
+
|
|
373
|
+
elif mutation_type == "arithmetic":
|
|
374
|
+
# Increment or decrement value
|
|
375
|
+
pos = self._rng.randint(0, len(msg) - 1)
|
|
376
|
+
delta = self._rng.choice([-1, 1])
|
|
377
|
+
msg[pos] = (msg[pos] + delta) % 256
|
|
378
|
+
|
|
379
|
+
return bytes(msg)
|
|
380
|
+
|
|
381
|
+
def _corrupt_checksums(self, spec: ProtocolSpec, messages: list[bytes]) -> list[bytes]:
|
|
382
|
+
"""Generate messages with corrupted checksums.
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
spec: Protocol specification.
|
|
386
|
+
messages: Valid messages.
|
|
387
|
+
|
|
388
|
+
Returns:
|
|
389
|
+
Messages with invalid checksums.
|
|
390
|
+
|
|
391
|
+
Example:
|
|
392
|
+
>>> invalid = generator._corrupt_checksums(spec, valid_messages)
|
|
393
|
+
>>> len(invalid) > 0
|
|
394
|
+
True
|
|
395
|
+
"""
|
|
396
|
+
corrupted: list[bytes] = []
|
|
397
|
+
|
|
398
|
+
# Find checksum field
|
|
399
|
+
checksum_field_idx = None
|
|
400
|
+
checksum_offset = 0
|
|
401
|
+
for idx, field_def in enumerate(spec.fields):
|
|
402
|
+
if field_def.field_type == "checksum":
|
|
403
|
+
checksum_field_idx = idx
|
|
404
|
+
break
|
|
405
|
+
checksum_offset += field_def.length
|
|
406
|
+
|
|
407
|
+
if checksum_field_idx is None:
|
|
408
|
+
return [] # No checksum field
|
|
409
|
+
|
|
410
|
+
# Corrupt checksums in subset of messages
|
|
411
|
+
num_corrupt = min(len(messages), 10)
|
|
412
|
+
for msg in messages[:num_corrupt]:
|
|
413
|
+
msg_arr = bytearray(msg)
|
|
414
|
+
checksum_len = spec.fields[checksum_field_idx].length
|
|
415
|
+
|
|
416
|
+
# XOR checksum with random value
|
|
417
|
+
for i in range(checksum_len):
|
|
418
|
+
msg_arr[checksum_offset + i] ^= self._rng.randint(1, 255)
|
|
419
|
+
|
|
420
|
+
corrupted.append(bytes(msg_arr))
|
|
421
|
+
|
|
422
|
+
return corrupted
|
|
423
|
+
|
|
424
|
+
def _build_message_with_field_value(
|
|
425
|
+
self, spec: ProtocolSpec, field_idx: int, value: int
|
|
426
|
+
) -> bytes:
|
|
427
|
+
"""Build message with specific field set to value.
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
spec: Protocol specification.
|
|
431
|
+
field_idx: Index of field to set.
|
|
432
|
+
value: Value to assign to field.
|
|
433
|
+
|
|
434
|
+
Returns:
|
|
435
|
+
Complete message with field set.
|
|
436
|
+
|
|
437
|
+
Example:
|
|
438
|
+
>>> msg = generator._build_message_with_field_value(spec, 1, 0xFF)
|
|
439
|
+
>>> len(msg) > 0
|
|
440
|
+
True
|
|
441
|
+
"""
|
|
442
|
+
msg = bytearray()
|
|
443
|
+
|
|
444
|
+
for idx, field_def in enumerate(spec.fields):
|
|
445
|
+
if idx == field_idx:
|
|
446
|
+
msg.extend(self._pack_value(value, field_def.length))
|
|
447
|
+
else:
|
|
448
|
+
msg.extend(self._generate_field_value(field_def, valid=True))
|
|
449
|
+
|
|
450
|
+
return bytes(msg)
|
|
451
|
+
|
|
452
|
+
def _pack_value(self, value: int, length: int) -> bytes:
|
|
453
|
+
"""Pack integer value into bytes (little-endian).
|
|
454
|
+
|
|
455
|
+
Args:
|
|
456
|
+
value: Integer value.
|
|
457
|
+
length: Number of bytes.
|
|
458
|
+
|
|
459
|
+
Returns:
|
|
460
|
+
Packed bytes.
|
|
461
|
+
|
|
462
|
+
Example:
|
|
463
|
+
>>> generator._pack_value(0x1234, 2)
|
|
464
|
+
b'\\x34\\x12'
|
|
465
|
+
"""
|
|
466
|
+
return value.to_bytes(length, byteorder="little")
|
|
467
|
+
|
|
468
|
+
def _generate_descriptions(self, tests: GeneratedTests, spec: ProtocolSpec) -> list[str]:
|
|
469
|
+
"""Generate human-readable descriptions for test vectors.
|
|
470
|
+
|
|
471
|
+
Args:
|
|
472
|
+
tests: Generated test vectors.
|
|
473
|
+
spec: Protocol specification.
|
|
474
|
+
|
|
475
|
+
Returns:
|
|
476
|
+
List of test descriptions.
|
|
477
|
+
|
|
478
|
+
Example:
|
|
479
|
+
>>> descriptions = generator._generate_descriptions(tests, spec)
|
|
480
|
+
>>> len(descriptions) == len(tests.all_messages)
|
|
481
|
+
True
|
|
482
|
+
"""
|
|
483
|
+
descriptions: list[str] = []
|
|
484
|
+
|
|
485
|
+
for msg in tests.valid_messages:
|
|
486
|
+
descriptions.append(f"Valid {spec.name} message: {msg.hex()}")
|
|
487
|
+
|
|
488
|
+
for msg in tests.invalid_messages:
|
|
489
|
+
descriptions.append(f"Invalid {spec.name} (bad checksum): {msg.hex()}")
|
|
490
|
+
|
|
491
|
+
for msg in tests.edge_cases:
|
|
492
|
+
descriptions.append(f"Edge case {spec.name}: {msg.hex()}")
|
|
493
|
+
|
|
494
|
+
for msg in tests.fuzzing_corpus:
|
|
495
|
+
descriptions.append(f"Fuzzing input {spec.name}: {msg.hex()}")
|
|
496
|
+
|
|
497
|
+
return descriptions
|
|
498
|
+
|
|
499
|
+
def _generate_coverage_report(
|
|
500
|
+
self, tests: GeneratedTests, spec: ProtocolSpec
|
|
501
|
+
) -> dict[str, Any]:
|
|
502
|
+
"""Generate coverage statistics for test suite.
|
|
503
|
+
|
|
504
|
+
Args:
|
|
505
|
+
tests: Generated test vectors.
|
|
506
|
+
spec: Protocol specification.
|
|
507
|
+
|
|
508
|
+
Returns:
|
|
509
|
+
Coverage report with field coverage and value ranges.
|
|
510
|
+
|
|
511
|
+
Example:
|
|
512
|
+
>>> report = generator._generate_coverage_report(tests, spec)
|
|
513
|
+
>>> "fields_covered" in report
|
|
514
|
+
True
|
|
515
|
+
"""
|
|
516
|
+
report: dict[str, Any] = {
|
|
517
|
+
"total_tests": len(tests.all_messages),
|
|
518
|
+
"valid_tests": len(tests.valid_messages),
|
|
519
|
+
"invalid_tests": len(tests.invalid_messages),
|
|
520
|
+
"edge_cases": len(tests.edge_cases),
|
|
521
|
+
"fuzzing_inputs": len(tests.fuzzing_corpus),
|
|
522
|
+
"fields_covered": len(spec.fields),
|
|
523
|
+
"protocol_name": spec.name,
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return report
|
|
527
|
+
|
|
528
|
+
def export_pcap(self, messages: list[bytes], output: Path) -> None:
|
|
529
|
+
"""Export messages as PCAP file (UDP packets).
|
|
530
|
+
|
|
531
|
+
Args:
|
|
532
|
+
messages: Protocol messages to export.
|
|
533
|
+
output: Output PCAP file path.
|
|
534
|
+
|
|
535
|
+
Example:
|
|
536
|
+
>>> generator.export_pcap(tests.valid_messages, Path("tests.pcap"))
|
|
537
|
+
"""
|
|
538
|
+
try:
|
|
539
|
+
from scapy.all import ( # type: ignore[attr-defined]
|
|
540
|
+
IP,
|
|
541
|
+
UDP,
|
|
542
|
+
Ether,
|
|
543
|
+
wrpcap,
|
|
544
|
+
)
|
|
545
|
+
except ImportError as e:
|
|
546
|
+
raise ImportError(
|
|
547
|
+
"scapy is required for PCAP export. Install with: uv pip install scapy"
|
|
548
|
+
) from e
|
|
549
|
+
|
|
550
|
+
packets = []
|
|
551
|
+
for msg in messages:
|
|
552
|
+
# Wrap in Ethernet/IP/UDP
|
|
553
|
+
pkt = Ether() / IP() / UDP(sport=12345, dport=54321) / msg
|
|
554
|
+
packets.append(pkt)
|
|
555
|
+
|
|
556
|
+
wrpcap(str(output), packets)
|
|
557
|
+
|
|
558
|
+
def export_pytest(self, messages: list[bytes], output: Path) -> None:
|
|
559
|
+
"""Export as pytest parametrized test cases.
|
|
560
|
+
|
|
561
|
+
Args:
|
|
562
|
+
messages: Protocol messages to export.
|
|
563
|
+
output: Output Python test file path.
|
|
564
|
+
|
|
565
|
+
Example:
|
|
566
|
+
>>> generator.export_pytest(tests.valid_messages, Path("test_proto.py"))
|
|
567
|
+
"""
|
|
568
|
+
test_code = [
|
|
569
|
+
'"""Generated protocol test vectors."""',
|
|
570
|
+
"",
|
|
571
|
+
"import pytest",
|
|
572
|
+
"",
|
|
573
|
+
"",
|
|
574
|
+
"@pytest.mark.parametrize(",
|
|
575
|
+
' "message,expected_valid",',
|
|
576
|
+
" [",
|
|
577
|
+
]
|
|
578
|
+
|
|
579
|
+
# Add test cases
|
|
580
|
+
for msg in messages:
|
|
581
|
+
hex_msg = msg.hex()
|
|
582
|
+
test_code.append(f' (bytes.fromhex("{hex_msg}"), True),')
|
|
583
|
+
|
|
584
|
+
test_code.extend(
|
|
585
|
+
[
|
|
586
|
+
" ],",
|
|
587
|
+
")",
|
|
588
|
+
"def test_protocol_parsing(message, expected_valid):",
|
|
589
|
+
' """Test protocol message parsing."""',
|
|
590
|
+
" # TODO: Implement parser validation (user should replace with actual parser)",
|
|
591
|
+
" assert isinstance(message, bytes)",
|
|
592
|
+
" assert len(message) > 0",
|
|
593
|
+
]
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
output.write_text("\n".join(test_code))
|