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,915 @@
|
|
|
1
|
+
"""Compliance test generator for protocol standards validation.
|
|
2
|
+
|
|
3
|
+
This module provides comprehensive test suite generation for validating protocol
|
|
4
|
+
implementations against industry standards (IEEE, ISO, SAE, ANSI). Generates test
|
|
5
|
+
vectors for conformance, boundary conditions, and interoperability testing.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.validation import ComplianceTestGenerator, ComplianceConfig
|
|
9
|
+
>>> from oscura.sessions import ProtocolSpec
|
|
10
|
+
>>>
|
|
11
|
+
>>> # Generate IEEE 802.3 Ethernet compliance tests
|
|
12
|
+
>>> config = ComplianceConfig(standard="IEEE_802_3", test_types=["conformance"])
|
|
13
|
+
>>> generator = ComplianceTestGenerator(config)
|
|
14
|
+
>>> suite = generator.generate_suite(protocol_spec)
|
|
15
|
+
>>>
|
|
16
|
+
>>> # Export to pytest
|
|
17
|
+
>>> generator.export_pytest(suite, Path("test_ethernet_compliance.py"))
|
|
18
|
+
>>>
|
|
19
|
+
>>> # Export to JSON test vectors
|
|
20
|
+
>>> generator.export_json(suite, Path("ethernet_test_vectors.json"))
|
|
21
|
+
|
|
22
|
+
References:
|
|
23
|
+
IEEE 802.3 Ethernet Standard
|
|
24
|
+
SAE J1939 CAN Bus Protocol
|
|
25
|
+
ISO 14229 Unified Diagnostic Services
|
|
26
|
+
ISO 15765 ISO-TP (CAN Transport Protocol)
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import json
|
|
32
|
+
import random
|
|
33
|
+
from dataclasses import dataclass, field
|
|
34
|
+
from enum import Enum
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
37
|
+
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from oscura.sessions.blackbox import FieldHypothesis, ProtocolSpec
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class StandardType(str, Enum):
|
|
43
|
+
"""Supported industry standards for compliance testing.
|
|
44
|
+
|
|
45
|
+
Attributes:
|
|
46
|
+
IEEE_802_3: Ethernet (CSMA/CD) protocol
|
|
47
|
+
IEEE_1149_1: JTAG boundary scan
|
|
48
|
+
SAE_J1939: CAN-based vehicle network
|
|
49
|
+
ISO_14229: UDS diagnostic protocol
|
|
50
|
+
ISO_15765: ISO-TP CAN transport
|
|
51
|
+
MODBUS: Industrial Modbus RTU/TCP
|
|
52
|
+
PROFINET: Real-time industrial Ethernet
|
|
53
|
+
ETHERCAT: Industrial fieldbus
|
|
54
|
+
MQTT: IoT messaging protocol
|
|
55
|
+
COAP: Constrained application protocol
|
|
56
|
+
LORAWAN: Long-range wide-area network
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
IEEE_802_3 = "IEEE_802_3"
|
|
60
|
+
IEEE_1149_1 = "IEEE_1149_1"
|
|
61
|
+
SAE_J1939 = "SAE_J1939"
|
|
62
|
+
ISO_14229 = "ISO_14229"
|
|
63
|
+
ISO_15765 = "ISO_15765"
|
|
64
|
+
MODBUS = "MODBUS"
|
|
65
|
+
PROFINET = "PROFINET"
|
|
66
|
+
ETHERCAT = "ETHERCAT"
|
|
67
|
+
MQTT = "MQTT"
|
|
68
|
+
COAP = "COAP"
|
|
69
|
+
LORAWAN = "LORAWAN"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class TestType(str, Enum):
|
|
73
|
+
"""Types of compliance tests to generate.
|
|
74
|
+
|
|
75
|
+
Attributes:
|
|
76
|
+
CONFORMANCE: Protocol conformance tests (message structure, timing, sequencing)
|
|
77
|
+
BOUNDARY: Boundary value tests (min/max values, overflow, underflow)
|
|
78
|
+
ERROR_HANDLING: Error handling tests (invalid checksums, malformed messages)
|
|
79
|
+
STATE_MACHINE: State machine coverage tests (all transitions exercised)
|
|
80
|
+
INTEROPERABILITY: Interoperability tests (multi-vendor compatibility)
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
CONFORMANCE = "conformance"
|
|
84
|
+
BOUNDARY = "boundary"
|
|
85
|
+
ERROR_HANDLING = "error_handling"
|
|
86
|
+
STATE_MACHINE = "state_machine"
|
|
87
|
+
INTEROPERABILITY = "interoperability"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass
|
|
91
|
+
class ComplianceConfig:
|
|
92
|
+
"""Configuration for compliance test generation.
|
|
93
|
+
|
|
94
|
+
Attributes:
|
|
95
|
+
standard: Target industry standard for compliance.
|
|
96
|
+
test_types: Types of tests to generate.
|
|
97
|
+
num_tests_per_type: Number of tests per test type.
|
|
98
|
+
include_documentation: Include test documentation in output.
|
|
99
|
+
export_format: Export format ("pytest", "json", "pcap", "markdown").
|
|
100
|
+
strict_mode: Enforce strict compliance (no deviations allowed).
|
|
101
|
+
|
|
102
|
+
Example:
|
|
103
|
+
>>> config = ComplianceConfig(
|
|
104
|
+
... standard=StandardType.SAE_J1939,
|
|
105
|
+
... test_types=[TestType.CONFORMANCE, TestType.BOUNDARY],
|
|
106
|
+
... num_tests_per_type=50
|
|
107
|
+
... )
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
standard: StandardType | str = StandardType.SAE_J1939
|
|
111
|
+
test_types: list[TestType | str] = field(
|
|
112
|
+
default_factory=lambda: [TestType.CONFORMANCE, TestType.BOUNDARY]
|
|
113
|
+
)
|
|
114
|
+
num_tests_per_type: int = 20
|
|
115
|
+
include_documentation: bool = True
|
|
116
|
+
export_format: Literal["pytest", "json", "pcap", "markdown"] = "pytest"
|
|
117
|
+
strict_mode: bool = True
|
|
118
|
+
|
|
119
|
+
def __post_init__(self) -> None:
|
|
120
|
+
"""Validate configuration after initialization."""
|
|
121
|
+
if self.num_tests_per_type <= 0:
|
|
122
|
+
raise ValueError(f"num_tests_per_type must be positive, got {self.num_tests_per_type}")
|
|
123
|
+
|
|
124
|
+
# Convert string to enum if needed
|
|
125
|
+
if isinstance(self.standard, str):
|
|
126
|
+
try:
|
|
127
|
+
self.standard = StandardType(self.standard)
|
|
128
|
+
except ValueError:
|
|
129
|
+
# Allow custom standards (not in enum)
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
# Convert test type strings to enums
|
|
133
|
+
converted_types: list[TestType | str] = []
|
|
134
|
+
for test_type in self.test_types:
|
|
135
|
+
if isinstance(test_type, TestType):
|
|
136
|
+
converted_types.append(test_type)
|
|
137
|
+
elif isinstance(test_type, str):
|
|
138
|
+
try:
|
|
139
|
+
converted_types.append(TestType(test_type))
|
|
140
|
+
except ValueError:
|
|
141
|
+
converted_types.append(test_type) # Keep custom string
|
|
142
|
+
self.test_types = converted_types
|
|
143
|
+
|
|
144
|
+
if self.export_format not in {"pytest", "json", "pcap", "markdown"}:
|
|
145
|
+
raise ValueError(f"Invalid export_format: {self.export_format}")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@dataclass
|
|
149
|
+
class TestCase:
|
|
150
|
+
"""Individual compliance test case.
|
|
151
|
+
|
|
152
|
+
Attributes:
|
|
153
|
+
name: Test case name.
|
|
154
|
+
description: Human-readable description.
|
|
155
|
+
test_type: Type of test (conformance, boundary, etc.).
|
|
156
|
+
input_data: Input data for test (bytes or dict).
|
|
157
|
+
expected_output: Expected output (bytes, dict, or validation result).
|
|
158
|
+
standard_reference: Reference to standard section (e.g., "IEEE 802.3 §4.2.1").
|
|
159
|
+
severity: Test severity ("critical", "high", "medium", "low").
|
|
160
|
+
metadata: Additional test metadata.
|
|
161
|
+
|
|
162
|
+
Example:
|
|
163
|
+
>>> test = TestCase(
|
|
164
|
+
... name="ethernet_min_frame_size",
|
|
165
|
+
... description="Verify minimum Ethernet frame size of 64 bytes",
|
|
166
|
+
... test_type=TestType.BOUNDARY,
|
|
167
|
+
... input_data=b"\\x00" * 64,
|
|
168
|
+
... expected_output={"valid": True},
|
|
169
|
+
... standard_reference="IEEE 802.3 §3.2.8",
|
|
170
|
+
... severity="critical"
|
|
171
|
+
... )
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
name: str
|
|
175
|
+
description: str
|
|
176
|
+
test_type: TestType | str
|
|
177
|
+
input_data: bytes | dict[str, Any]
|
|
178
|
+
expected_output: bytes | dict[str, Any] | bool | None
|
|
179
|
+
standard_reference: str
|
|
180
|
+
severity: Literal["critical", "high", "medium", "low"] = "medium"
|
|
181
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@dataclass
|
|
185
|
+
class ComplianceTestSuite:
|
|
186
|
+
"""Complete compliance test suite.
|
|
187
|
+
|
|
188
|
+
Attributes:
|
|
189
|
+
standard: Target standard.
|
|
190
|
+
test_cases: List of test cases.
|
|
191
|
+
metadata: Suite metadata (version, date, coverage stats).
|
|
192
|
+
documentation: Test suite documentation.
|
|
193
|
+
|
|
194
|
+
Example:
|
|
195
|
+
>>> suite = ComplianceTestSuite(
|
|
196
|
+
... standard=StandardType.SAE_J1939,
|
|
197
|
+
... test_cases=[test1, test2, test3],
|
|
198
|
+
... metadata={"total_tests": 3, "coverage": 85.5}
|
|
199
|
+
... )
|
|
200
|
+
>>> print(f"Tests: {len(suite.test_cases)}")
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
standard: StandardType | str
|
|
204
|
+
test_cases: list[TestCase] = field(default_factory=list)
|
|
205
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
206
|
+
documentation: str = ""
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
def total_tests(self) -> int:
|
|
210
|
+
"""Get total number of test cases."""
|
|
211
|
+
return len(self.test_cases)
|
|
212
|
+
|
|
213
|
+
def get_tests_by_type(self, test_type: TestType | str) -> list[TestCase]:
|
|
214
|
+
"""Get test cases by type.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
test_type: Type of tests to retrieve.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
List of matching test cases.
|
|
221
|
+
"""
|
|
222
|
+
return [tc for tc in self.test_cases if tc.test_type == test_type]
|
|
223
|
+
|
|
224
|
+
def get_tests_by_severity(
|
|
225
|
+
self, severity: Literal["critical", "high", "medium", "low"]
|
|
226
|
+
) -> list[TestCase]:
|
|
227
|
+
"""Get test cases by severity.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
severity: Severity level to filter.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
List of matching test cases.
|
|
234
|
+
"""
|
|
235
|
+
return [tc for tc in self.test_cases if tc.severity == severity]
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class ComplianceTestGenerator:
|
|
239
|
+
"""Compliance test suite generator for protocol standards validation.
|
|
240
|
+
|
|
241
|
+
Generates comprehensive test suites from protocol specifications using
|
|
242
|
+
industry standard compliance requirements. Supports IEEE, SAE, ISO, ANSI standards.
|
|
243
|
+
|
|
244
|
+
Attributes:
|
|
245
|
+
config: Compliance test configuration.
|
|
246
|
+
|
|
247
|
+
Example:
|
|
248
|
+
>>> config = ComplianceConfig(standard=StandardType.IEEE_802_3)
|
|
249
|
+
>>> generator = ComplianceTestGenerator(config)
|
|
250
|
+
>>> suite = generator.generate_suite(protocol_spec)
|
|
251
|
+
>>> generator.export_pytest(suite, Path("test_compliance.py"))
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
def __init__(self, config: ComplianceConfig) -> None:
|
|
255
|
+
"""Initialize compliance test generator.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
config: Compliance test configuration.
|
|
259
|
+
"""
|
|
260
|
+
self.config = config
|
|
261
|
+
self._rng = random.Random(42) # Deterministic for reproducibility
|
|
262
|
+
|
|
263
|
+
# Standard-specific constraints
|
|
264
|
+
self._standard_constraints = self._load_standard_constraints()
|
|
265
|
+
|
|
266
|
+
def generate_suite(self, spec: ProtocolSpec) -> ComplianceTestSuite:
|
|
267
|
+
"""Generate complete compliance test suite from protocol specification.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
spec: Protocol specification with field definitions.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
Complete compliance test suite with all test types.
|
|
274
|
+
|
|
275
|
+
Example:
|
|
276
|
+
>>> suite = generator.generate_suite(protocol_spec)
|
|
277
|
+
>>> print(f"Generated {suite.total_tests} tests")
|
|
278
|
+
>>> print(f"Conformance: {len(suite.get_tests_by_type('conformance'))}")
|
|
279
|
+
"""
|
|
280
|
+
suite = ComplianceTestSuite(
|
|
281
|
+
standard=self.config.standard,
|
|
282
|
+
metadata={
|
|
283
|
+
"protocol_name": spec.name,
|
|
284
|
+
"standard": str(self.config.standard),
|
|
285
|
+
"strict_mode": self.config.strict_mode,
|
|
286
|
+
"test_types": [str(tt) for tt in self.config.test_types],
|
|
287
|
+
},
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# Generate tests for each type
|
|
291
|
+
for test_type in self.config.test_types:
|
|
292
|
+
if test_type == TestType.CONFORMANCE:
|
|
293
|
+
suite.test_cases.extend(self._generate_conformance_tests(spec))
|
|
294
|
+
elif test_type == TestType.BOUNDARY:
|
|
295
|
+
suite.test_cases.extend(self._generate_boundary_tests(spec))
|
|
296
|
+
elif test_type == TestType.ERROR_HANDLING:
|
|
297
|
+
suite.test_cases.extend(self._generate_error_handling_tests(spec))
|
|
298
|
+
elif test_type == TestType.STATE_MACHINE:
|
|
299
|
+
suite.test_cases.extend(self._generate_state_machine_tests(spec))
|
|
300
|
+
elif test_type == TestType.INTEROPERABILITY:
|
|
301
|
+
suite.test_cases.extend(self._generate_interoperability_tests(spec))
|
|
302
|
+
|
|
303
|
+
# Generate documentation
|
|
304
|
+
if self.config.include_documentation:
|
|
305
|
+
suite.documentation = self._generate_documentation(suite, spec)
|
|
306
|
+
|
|
307
|
+
# Update metadata
|
|
308
|
+
suite.metadata.update(
|
|
309
|
+
{
|
|
310
|
+
"total_tests": suite.total_tests,
|
|
311
|
+
"conformance_tests": len(suite.get_tests_by_type(TestType.CONFORMANCE)),
|
|
312
|
+
"boundary_tests": len(suite.get_tests_by_type(TestType.BOUNDARY)),
|
|
313
|
+
"error_handling_tests": len(suite.get_tests_by_type(TestType.ERROR_HANDLING)),
|
|
314
|
+
"state_machine_tests": len(suite.get_tests_by_type(TestType.STATE_MACHINE)),
|
|
315
|
+
"critical_tests": len(suite.get_tests_by_severity("critical")),
|
|
316
|
+
"high_tests": len(suite.get_tests_by_severity("high")),
|
|
317
|
+
}
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
return suite
|
|
321
|
+
|
|
322
|
+
def _generate_conformance_tests(self, spec: ProtocolSpec) -> list[TestCase]:
|
|
323
|
+
"""Generate protocol conformance tests.
|
|
324
|
+
|
|
325
|
+
Tests message structure, field ordering, data types, and protocol sequences
|
|
326
|
+
according to standard specifications.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
spec: Protocol specification.
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
List of conformance test cases.
|
|
333
|
+
"""
|
|
334
|
+
tests: list[TestCase] = []
|
|
335
|
+
constraints = self._standard_constraints.get(str(self.config.standard), {})
|
|
336
|
+
|
|
337
|
+
# Test 1: Valid message structure
|
|
338
|
+
for i in range(min(self.config.num_tests_per_type, 10)):
|
|
339
|
+
msg = self._build_valid_message(spec, constraints)
|
|
340
|
+
tests.append(
|
|
341
|
+
TestCase(
|
|
342
|
+
name=f"{spec.name.lower()}_conformance_valid_{i}",
|
|
343
|
+
description=f"Valid {spec.name} message conforming to standard",
|
|
344
|
+
test_type=TestType.CONFORMANCE,
|
|
345
|
+
input_data=msg,
|
|
346
|
+
expected_output={"valid": True, "errors": []},
|
|
347
|
+
standard_reference=constraints.get(
|
|
348
|
+
"reference", f"{self.config.standard} General"
|
|
349
|
+
),
|
|
350
|
+
severity="critical",
|
|
351
|
+
metadata={"test_id": f"CONF_{i:03d}"},
|
|
352
|
+
)
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
# Test 2: Field ordering compliance
|
|
356
|
+
if len(spec.fields) > 1:
|
|
357
|
+
tests.append(
|
|
358
|
+
TestCase(
|
|
359
|
+
name=f"{spec.name.lower()}_field_ordering",
|
|
360
|
+
description="Verify correct field ordering per standard",
|
|
361
|
+
test_type=TestType.CONFORMANCE,
|
|
362
|
+
input_data=self._build_valid_message(spec, constraints),
|
|
363
|
+
expected_output={"field_order_valid": True},
|
|
364
|
+
standard_reference=constraints.get("reference", "Field Order"),
|
|
365
|
+
severity="high",
|
|
366
|
+
metadata={"fields": [f.name for f in spec.fields]},
|
|
367
|
+
)
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
# Test 3: Minimum message length
|
|
371
|
+
min_length = sum(f.length for f in spec.fields)
|
|
372
|
+
tests.append(
|
|
373
|
+
TestCase(
|
|
374
|
+
name=f"{spec.name.lower()}_min_message_length",
|
|
375
|
+
description=f"Verify minimum message length of {min_length} bytes",
|
|
376
|
+
test_type=TestType.CONFORMANCE,
|
|
377
|
+
input_data=self._build_valid_message(spec, constraints),
|
|
378
|
+
expected_output={"length": min_length, "valid": True},
|
|
379
|
+
standard_reference=constraints.get("reference", "Message Length"),
|
|
380
|
+
severity="critical",
|
|
381
|
+
metadata={"min_length": min_length},
|
|
382
|
+
)
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
return tests
|
|
386
|
+
|
|
387
|
+
def _generate_boundary_tests(self, spec: ProtocolSpec) -> list[TestCase]:
|
|
388
|
+
"""Generate boundary value tests.
|
|
389
|
+
|
|
390
|
+
Tests minimum/maximum values, overflow, underflow, and edge cases
|
|
391
|
+
for all protocol fields.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
spec: Protocol specification.
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
List of boundary test cases.
|
|
398
|
+
"""
|
|
399
|
+
tests: list[TestCase] = []
|
|
400
|
+
|
|
401
|
+
# For each field, test min/max values
|
|
402
|
+
for field_idx, field_def in enumerate(spec.fields):
|
|
403
|
+
if field_def.field_type in {"data", "counter"}:
|
|
404
|
+
# Min value (0)
|
|
405
|
+
msg_min = self._build_message_with_field_value(spec, field_idx, 0)
|
|
406
|
+
tests.append(
|
|
407
|
+
TestCase(
|
|
408
|
+
name=f"{spec.name.lower()}_field_{field_def.name}_min",
|
|
409
|
+
description=f"Test minimum value (0) for {field_def.name}",
|
|
410
|
+
test_type=TestType.BOUNDARY,
|
|
411
|
+
input_data=msg_min,
|
|
412
|
+
expected_output={"valid": True, "field_value": 0},
|
|
413
|
+
standard_reference=f"{self.config.standard} Boundary Values",
|
|
414
|
+
severity="high",
|
|
415
|
+
metadata={"field": field_def.name, "boundary": "min"},
|
|
416
|
+
)
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
# Max value
|
|
420
|
+
max_val = (256**field_def.length) - 1
|
|
421
|
+
msg_max = self._build_message_with_field_value(spec, field_idx, max_val)
|
|
422
|
+
tests.append(
|
|
423
|
+
TestCase(
|
|
424
|
+
name=f"{spec.name.lower()}_field_{field_def.name}_max",
|
|
425
|
+
description=f"Test maximum value ({max_val}) for {field_def.name}",
|
|
426
|
+
test_type=TestType.BOUNDARY,
|
|
427
|
+
input_data=msg_max,
|
|
428
|
+
expected_output={"valid": True, "field_value": max_val},
|
|
429
|
+
standard_reference=f"{self.config.standard} Boundary Values",
|
|
430
|
+
severity="high",
|
|
431
|
+
metadata={"field": field_def.name, "boundary": "max"},
|
|
432
|
+
)
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
# All zeros message
|
|
436
|
+
msg_len = sum(f.length for f in spec.fields)
|
|
437
|
+
tests.append(
|
|
438
|
+
TestCase(
|
|
439
|
+
name=f"{spec.name.lower()}_all_zeros",
|
|
440
|
+
description="Test message with all zero bytes",
|
|
441
|
+
test_type=TestType.BOUNDARY,
|
|
442
|
+
input_data=b"\x00" * msg_len,
|
|
443
|
+
expected_output={"valid": self.config.strict_mode is False},
|
|
444
|
+
standard_reference=f"{self.config.standard} Edge Cases",
|
|
445
|
+
severity="medium",
|
|
446
|
+
metadata={"pattern": "all_zeros"},
|
|
447
|
+
)
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
# All ones message
|
|
451
|
+
tests.append(
|
|
452
|
+
TestCase(
|
|
453
|
+
name=f"{spec.name.lower()}_all_ones",
|
|
454
|
+
description="Test message with all 0xFF bytes",
|
|
455
|
+
test_type=TestType.BOUNDARY,
|
|
456
|
+
input_data=b"\xff" * msg_len,
|
|
457
|
+
expected_output={"valid": self.config.strict_mode is False},
|
|
458
|
+
standard_reference=f"{self.config.standard} Edge Cases",
|
|
459
|
+
severity="medium",
|
|
460
|
+
metadata={"pattern": "all_ones"},
|
|
461
|
+
)
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
return tests
|
|
465
|
+
|
|
466
|
+
def _generate_error_handling_tests(self, spec: ProtocolSpec) -> list[TestCase]:
|
|
467
|
+
"""Generate error handling tests.
|
|
468
|
+
|
|
469
|
+
Tests invalid checksums, malformed messages, and protocol violations.
|
|
470
|
+
|
|
471
|
+
Args:
|
|
472
|
+
spec: Protocol specification.
|
|
473
|
+
|
|
474
|
+
Returns:
|
|
475
|
+
List of error handling test cases.
|
|
476
|
+
"""
|
|
477
|
+
tests: list[TestCase] = []
|
|
478
|
+
|
|
479
|
+
# Find checksum field
|
|
480
|
+
checksum_field_idx = None
|
|
481
|
+
checksum_offset = 0
|
|
482
|
+
for idx, field_def in enumerate(spec.fields):
|
|
483
|
+
if field_def.field_type == "checksum":
|
|
484
|
+
checksum_field_idx = idx
|
|
485
|
+
break
|
|
486
|
+
checksum_offset += field_def.length
|
|
487
|
+
|
|
488
|
+
# Test 1: Invalid checksum
|
|
489
|
+
if checksum_field_idx is not None:
|
|
490
|
+
msg = self._build_valid_message(spec, {})
|
|
491
|
+
msg_arr = bytearray(msg)
|
|
492
|
+
# Corrupt checksum
|
|
493
|
+
checksum_len = spec.fields[checksum_field_idx].length
|
|
494
|
+
for i in range(checksum_len):
|
|
495
|
+
msg_arr[checksum_offset + i] ^= 0xFF
|
|
496
|
+
|
|
497
|
+
tests.append(
|
|
498
|
+
TestCase(
|
|
499
|
+
name=f"{spec.name.lower()}_invalid_checksum",
|
|
500
|
+
description="Test message with corrupted checksum",
|
|
501
|
+
test_type=TestType.ERROR_HANDLING,
|
|
502
|
+
input_data=bytes(msg_arr),
|
|
503
|
+
expected_output={"valid": False, "error": "checksum_mismatch"},
|
|
504
|
+
standard_reference=f"{self.config.standard} Error Detection",
|
|
505
|
+
severity="critical",
|
|
506
|
+
metadata={"error_type": "checksum"},
|
|
507
|
+
)
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
# Test 2: Truncated message
|
|
511
|
+
msg = self._build_valid_message(spec, {})
|
|
512
|
+
truncated = msg[: len(msg) // 2]
|
|
513
|
+
tests.append(
|
|
514
|
+
TestCase(
|
|
515
|
+
name=f"{spec.name.lower()}_truncated_message",
|
|
516
|
+
description="Test truncated message (incomplete data)",
|
|
517
|
+
test_type=TestType.ERROR_HANDLING,
|
|
518
|
+
input_data=truncated,
|
|
519
|
+
expected_output={"valid": False, "error": "incomplete_message"},
|
|
520
|
+
standard_reference=f"{self.config.standard} Message Format",
|
|
521
|
+
severity="high",
|
|
522
|
+
metadata={"error_type": "truncation"},
|
|
523
|
+
)
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
# Test 3: Oversized message
|
|
527
|
+
msg = self._build_valid_message(spec, {})
|
|
528
|
+
oversized = msg + b"\x00" * 10
|
|
529
|
+
tests.append(
|
|
530
|
+
TestCase(
|
|
531
|
+
name=f"{spec.name.lower()}_oversized_message",
|
|
532
|
+
description="Test oversized message (extra bytes)",
|
|
533
|
+
test_type=TestType.ERROR_HANDLING,
|
|
534
|
+
input_data=oversized,
|
|
535
|
+
expected_output={"valid": self.config.strict_mode is False},
|
|
536
|
+
standard_reference=f"{self.config.standard} Message Format",
|
|
537
|
+
severity="medium",
|
|
538
|
+
metadata={"error_type": "oversized"},
|
|
539
|
+
)
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
return tests
|
|
543
|
+
|
|
544
|
+
def _generate_state_machine_tests(self, spec: ProtocolSpec) -> list[TestCase]:
|
|
545
|
+
"""Generate state machine coverage tests.
|
|
546
|
+
|
|
547
|
+
Tests all state transitions and verifies protocol state handling.
|
|
548
|
+
|
|
549
|
+
Args:
|
|
550
|
+
spec: Protocol specification.
|
|
551
|
+
|
|
552
|
+
Returns:
|
|
553
|
+
List of state machine test cases.
|
|
554
|
+
"""
|
|
555
|
+
tests: list[TestCase] = []
|
|
556
|
+
|
|
557
|
+
# Generate basic state transition tests
|
|
558
|
+
transitions = [
|
|
559
|
+
("IDLE", "ACTIVE", "initialize"),
|
|
560
|
+
("ACTIVE", "ERROR", "fault"),
|
|
561
|
+
("ERROR", "RECOVERY", "reset"),
|
|
562
|
+
("RECOVERY", "IDLE", "complete"),
|
|
563
|
+
]
|
|
564
|
+
|
|
565
|
+
for from_state, to_state, event in transitions:
|
|
566
|
+
tests.append(
|
|
567
|
+
TestCase(
|
|
568
|
+
name=f"{spec.name.lower()}_transition_{from_state}_to_{to_state}",
|
|
569
|
+
description=f"Test {from_state} -> {to_state} transition on {event}",
|
|
570
|
+
test_type=TestType.STATE_MACHINE,
|
|
571
|
+
input_data={"state": from_state, "event": event},
|
|
572
|
+
expected_output={"next_state": to_state, "valid": True},
|
|
573
|
+
standard_reference=f"{self.config.standard} State Machine",
|
|
574
|
+
severity="high",
|
|
575
|
+
metadata={"from": from_state, "to": to_state, "event": event},
|
|
576
|
+
)
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
return tests
|
|
580
|
+
|
|
581
|
+
def _generate_interoperability_tests(self, spec: ProtocolSpec) -> list[TestCase]:
|
|
582
|
+
"""Generate interoperability tests.
|
|
583
|
+
|
|
584
|
+
Tests multi-vendor compatibility and protocol variant handling.
|
|
585
|
+
|
|
586
|
+
Args:
|
|
587
|
+
spec: Protocol specification.
|
|
588
|
+
|
|
589
|
+
Returns:
|
|
590
|
+
List of interoperability test cases.
|
|
591
|
+
"""
|
|
592
|
+
tests: list[TestCase] = []
|
|
593
|
+
|
|
594
|
+
# Generate tests for different protocol variants/implementations
|
|
595
|
+
variants = ["vendor_a", "vendor_b", "reference_impl"]
|
|
596
|
+
|
|
597
|
+
for variant in variants:
|
|
598
|
+
msg = self._build_valid_message(spec, {})
|
|
599
|
+
tests.append(
|
|
600
|
+
TestCase(
|
|
601
|
+
name=f"{spec.name.lower()}_interop_{variant}",
|
|
602
|
+
description=f"Test interoperability with {variant} implementation",
|
|
603
|
+
test_type=TestType.INTEROPERABILITY,
|
|
604
|
+
input_data=msg,
|
|
605
|
+
expected_output={"compatible": True, "variant": variant},
|
|
606
|
+
standard_reference=f"{self.config.standard} Interoperability",
|
|
607
|
+
severity="medium",
|
|
608
|
+
metadata={"variant": variant},
|
|
609
|
+
)
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
return tests
|
|
613
|
+
|
|
614
|
+
def _build_valid_message(self, spec: ProtocolSpec, constraints: dict[str, Any]) -> bytes:
|
|
615
|
+
"""Build valid protocol message conforming to standard.
|
|
616
|
+
|
|
617
|
+
Args:
|
|
618
|
+
spec: Protocol specification.
|
|
619
|
+
constraints: Standard-specific constraints.
|
|
620
|
+
|
|
621
|
+
Returns:
|
|
622
|
+
Valid message bytes.
|
|
623
|
+
"""
|
|
624
|
+
msg = bytearray()
|
|
625
|
+
|
|
626
|
+
for field_def in spec.fields:
|
|
627
|
+
field_bytes = self._generate_field_value(field_def, constraints)
|
|
628
|
+
msg.extend(field_bytes)
|
|
629
|
+
|
|
630
|
+
return bytes(msg)
|
|
631
|
+
|
|
632
|
+
def _generate_field_value(
|
|
633
|
+
self, field_def: FieldHypothesis, constraints: dict[str, Any]
|
|
634
|
+
) -> bytes:
|
|
635
|
+
"""Generate value for a single field.
|
|
636
|
+
|
|
637
|
+
Args:
|
|
638
|
+
field_def: Field definition.
|
|
639
|
+
constraints: Standard-specific constraints.
|
|
640
|
+
|
|
641
|
+
Returns:
|
|
642
|
+
Field value as bytes.
|
|
643
|
+
"""
|
|
644
|
+
if field_def.field_type == "constant":
|
|
645
|
+
const_val = field_def.evidence.get("value", 0)
|
|
646
|
+
return self._pack_value(const_val, field_def.length)
|
|
647
|
+
|
|
648
|
+
if field_def.field_type == "counter":
|
|
649
|
+
counter_val = self._rng.randint(0, (256**field_def.length) - 1)
|
|
650
|
+
return self._pack_value(counter_val, field_def.length)
|
|
651
|
+
|
|
652
|
+
if field_def.field_type == "checksum":
|
|
653
|
+
# Placeholder for checksum (computed later)
|
|
654
|
+
return b"\x00" * field_def.length
|
|
655
|
+
|
|
656
|
+
# Default: random data
|
|
657
|
+
return bytes(self._rng.randint(0, 255) for _ in range(field_def.length))
|
|
658
|
+
|
|
659
|
+
def _build_message_with_field_value(
|
|
660
|
+
self, spec: ProtocolSpec, field_idx: int, value: int
|
|
661
|
+
) -> bytes:
|
|
662
|
+
"""Build message with specific field set to value.
|
|
663
|
+
|
|
664
|
+
Args:
|
|
665
|
+
spec: Protocol specification.
|
|
666
|
+
field_idx: Index of field to set.
|
|
667
|
+
value: Value to assign to field.
|
|
668
|
+
|
|
669
|
+
Returns:
|
|
670
|
+
Complete message with field set.
|
|
671
|
+
"""
|
|
672
|
+
msg = bytearray()
|
|
673
|
+
|
|
674
|
+
for idx, field_def in enumerate(spec.fields):
|
|
675
|
+
if idx == field_idx:
|
|
676
|
+
msg.extend(self._pack_value(value, field_def.length))
|
|
677
|
+
else:
|
|
678
|
+
msg.extend(self._generate_field_value(field_def, {}))
|
|
679
|
+
|
|
680
|
+
return bytes(msg)
|
|
681
|
+
|
|
682
|
+
def _pack_value(self, value: int, length: int) -> bytes:
|
|
683
|
+
"""Pack integer value into bytes (little-endian).
|
|
684
|
+
|
|
685
|
+
Args:
|
|
686
|
+
value: Integer value.
|
|
687
|
+
length: Number of bytes.
|
|
688
|
+
|
|
689
|
+
Returns:
|
|
690
|
+
Packed bytes.
|
|
691
|
+
"""
|
|
692
|
+
return value.to_bytes(length, byteorder="little")
|
|
693
|
+
|
|
694
|
+
def _load_standard_constraints(self) -> dict[str, Any]:
|
|
695
|
+
"""Load standard-specific constraints and requirements.
|
|
696
|
+
|
|
697
|
+
Returns:
|
|
698
|
+
Dictionary of constraints by standard name.
|
|
699
|
+
"""
|
|
700
|
+
return {
|
|
701
|
+
"StandardType.IEEE_802_3": {
|
|
702
|
+
"min_frame_size": 64,
|
|
703
|
+
"max_frame_size": 1518,
|
|
704
|
+
"reference": "IEEE 802.3 §3.2.8",
|
|
705
|
+
},
|
|
706
|
+
"StandardType.SAE_J1939": {
|
|
707
|
+
"max_pgn": 0x1FFFF,
|
|
708
|
+
"priority_range": (0, 7),
|
|
709
|
+
"reference": "SAE J1939/21",
|
|
710
|
+
},
|
|
711
|
+
"StandardType.ISO_14229": {
|
|
712
|
+
"service_id_range": (0x01, 0xFF),
|
|
713
|
+
"negative_response": 0x7F,
|
|
714
|
+
"reference": "ISO 14229-1:2020",
|
|
715
|
+
},
|
|
716
|
+
"StandardType.MODBUS": {
|
|
717
|
+
"max_address": 247,
|
|
718
|
+
"function_code_range": (1, 127),
|
|
719
|
+
"reference": "Modbus Application Protocol V1.1b3",
|
|
720
|
+
},
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
def _generate_documentation(self, suite: ComplianceTestSuite, spec: ProtocolSpec) -> str:
|
|
724
|
+
"""Generate comprehensive test suite documentation.
|
|
725
|
+
|
|
726
|
+
Args:
|
|
727
|
+
suite: Test suite.
|
|
728
|
+
spec: Protocol specification.
|
|
729
|
+
|
|
730
|
+
Returns:
|
|
731
|
+
Markdown documentation.
|
|
732
|
+
"""
|
|
733
|
+
doc = [
|
|
734
|
+
f"# Compliance Test Suite: {spec.name}",
|
|
735
|
+
f"\n## Standard: {suite.standard}",
|
|
736
|
+
f"\n**Total Tests:** {suite.total_tests}",
|
|
737
|
+
"\n### Test Coverage",
|
|
738
|
+
"",
|
|
739
|
+
]
|
|
740
|
+
|
|
741
|
+
for test_type in self.config.test_types:
|
|
742
|
+
count = len(suite.get_tests_by_type(test_type))
|
|
743
|
+
doc.append(f"- **{test_type}**: {count} tests")
|
|
744
|
+
|
|
745
|
+
doc.extend(
|
|
746
|
+
[
|
|
747
|
+
"\n### Severity Distribution",
|
|
748
|
+
"",
|
|
749
|
+
f"- Critical: {len(suite.get_tests_by_severity('critical'))}",
|
|
750
|
+
f"- High: {len(suite.get_tests_by_severity('high'))}",
|
|
751
|
+
f"- Medium: {len(suite.get_tests_by_severity('medium'))}",
|
|
752
|
+
f"- Low: {len(suite.get_tests_by_severity('low'))}",
|
|
753
|
+
"\n## Test Cases",
|
|
754
|
+
"",
|
|
755
|
+
]
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
for test_case in suite.test_cases[:10]: # First 10 for brevity
|
|
759
|
+
doc.append(f"### {test_case.name}")
|
|
760
|
+
doc.append(f"\n**Description:** {test_case.description}")
|
|
761
|
+
doc.append(f"**Type:** {test_case.test_type}")
|
|
762
|
+
doc.append(f"**Severity:** {test_case.severity}")
|
|
763
|
+
doc.append(f"**Standard Reference:** {test_case.standard_reference}")
|
|
764
|
+
doc.append("")
|
|
765
|
+
|
|
766
|
+
if suite.total_tests > 10:
|
|
767
|
+
doc.append(f"\n*... and {suite.total_tests - 10} more test cases*")
|
|
768
|
+
|
|
769
|
+
return "\n".join(doc)
|
|
770
|
+
|
|
771
|
+
def export_pytest(self, suite: ComplianceTestSuite, output: Path) -> None:
|
|
772
|
+
"""Export compliance tests as pytest parametrized test cases.
|
|
773
|
+
|
|
774
|
+
Args:
|
|
775
|
+
suite: Compliance test suite.
|
|
776
|
+
output: Output Python test file path.
|
|
777
|
+
|
|
778
|
+
Example:
|
|
779
|
+
>>> generator.export_pytest(suite, Path("test_compliance.py"))
|
|
780
|
+
"""
|
|
781
|
+
test_code = [
|
|
782
|
+
f'"""Generated compliance tests for {suite.standard}."""',
|
|
783
|
+
"",
|
|
784
|
+
"import pytest",
|
|
785
|
+
"",
|
|
786
|
+
"",
|
|
787
|
+
"@pytest.mark.parametrize(",
|
|
788
|
+
' "test_case",',
|
|
789
|
+
" [",
|
|
790
|
+
]
|
|
791
|
+
|
|
792
|
+
# Add test cases
|
|
793
|
+
for test_case in suite.test_cases:
|
|
794
|
+
# Serialize test case
|
|
795
|
+
test_dict = {
|
|
796
|
+
"name": test_case.name,
|
|
797
|
+
"description": test_case.description,
|
|
798
|
+
"test_type": str(test_case.test_type),
|
|
799
|
+
"input_data": (
|
|
800
|
+
test_case.input_data.hex()
|
|
801
|
+
if isinstance(test_case.input_data, bytes)
|
|
802
|
+
else test_case.input_data
|
|
803
|
+
),
|
|
804
|
+
"expected_output": test_case.expected_output,
|
|
805
|
+
"standard_reference": test_case.standard_reference,
|
|
806
|
+
"severity": test_case.severity,
|
|
807
|
+
}
|
|
808
|
+
test_code.append(f" {test_dict!r},")
|
|
809
|
+
|
|
810
|
+
test_code.extend(
|
|
811
|
+
[
|
|
812
|
+
" ],",
|
|
813
|
+
")",
|
|
814
|
+
"def test_compliance(test_case):",
|
|
815
|
+
f' """Test compliance with {suite.standard}."""',
|
|
816
|
+
" # TODO: Implement compliance validation (user should replace with actual validator)",
|
|
817
|
+
' assert test_case["name"]',
|
|
818
|
+
' assert test_case["standard_reference"]',
|
|
819
|
+
"",
|
|
820
|
+
"",
|
|
821
|
+
"def test_suite_coverage():",
|
|
822
|
+
f' """Verify test suite coverage for {suite.standard}."""',
|
|
823
|
+
f" assert {suite.total_tests} > 0 # Total tests",
|
|
824
|
+
]
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
output.write_text("\n".join(test_code))
|
|
828
|
+
|
|
829
|
+
def export_json(self, suite: ComplianceTestSuite, output: Path) -> None:
|
|
830
|
+
"""Export compliance tests as JSON test vectors.
|
|
831
|
+
|
|
832
|
+
Args:
|
|
833
|
+
suite: Compliance test suite.
|
|
834
|
+
output: Output JSON file path.
|
|
835
|
+
|
|
836
|
+
Example:
|
|
837
|
+
>>> generator.export_json(suite, Path("test_vectors.json"))
|
|
838
|
+
"""
|
|
839
|
+
json_data = {
|
|
840
|
+
"standard": str(suite.standard),
|
|
841
|
+
"metadata": suite.metadata,
|
|
842
|
+
"documentation": suite.documentation,
|
|
843
|
+
"test_cases": [
|
|
844
|
+
{
|
|
845
|
+
"name": tc.name,
|
|
846
|
+
"description": tc.description,
|
|
847
|
+
"test_type": str(tc.test_type),
|
|
848
|
+
"input_data": (
|
|
849
|
+
tc.input_data.hex() if isinstance(tc.input_data, bytes) else tc.input_data
|
|
850
|
+
),
|
|
851
|
+
"expected_output": tc.expected_output,
|
|
852
|
+
"standard_reference": tc.standard_reference,
|
|
853
|
+
"severity": tc.severity,
|
|
854
|
+
"metadata": tc.metadata,
|
|
855
|
+
}
|
|
856
|
+
for tc in suite.test_cases
|
|
857
|
+
],
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
with output.open("w") as f:
|
|
861
|
+
json.dump(json_data, f, indent=2)
|
|
862
|
+
|
|
863
|
+
def export_pcap(self, suite: ComplianceTestSuite, output: Path) -> None:
|
|
864
|
+
"""Export compliance tests as PCAP file (UDP packets).
|
|
865
|
+
|
|
866
|
+
Args:
|
|
867
|
+
suite: Compliance test suite.
|
|
868
|
+
output: Output PCAP file path.
|
|
869
|
+
|
|
870
|
+
Example:
|
|
871
|
+
>>> generator.export_pcap(suite, Path("compliance_tests.pcap"))
|
|
872
|
+
"""
|
|
873
|
+
try:
|
|
874
|
+
from scapy.all import ( # type: ignore[attr-defined]
|
|
875
|
+
IP,
|
|
876
|
+
UDP,
|
|
877
|
+
Ether,
|
|
878
|
+
wrpcap,
|
|
879
|
+
)
|
|
880
|
+
except ImportError as e:
|
|
881
|
+
raise ImportError(
|
|
882
|
+
"scapy is required for PCAP export. Install with: uv pip install scapy"
|
|
883
|
+
) from e
|
|
884
|
+
|
|
885
|
+
packets = []
|
|
886
|
+
for test_case in suite.test_cases:
|
|
887
|
+
if isinstance(test_case.input_data, bytes):
|
|
888
|
+
# Wrap in Ethernet/IP/UDP
|
|
889
|
+
pkt = Ether() / IP() / UDP(sport=12345, dport=54321) / test_case.input_data
|
|
890
|
+
packets.append(pkt)
|
|
891
|
+
|
|
892
|
+
if packets:
|
|
893
|
+
wrpcap(str(output), packets)
|
|
894
|
+
|
|
895
|
+
def export_markdown(self, suite: ComplianceTestSuite, output: Path) -> None:
|
|
896
|
+
"""Export compliance tests as Markdown documentation.
|
|
897
|
+
|
|
898
|
+
Args:
|
|
899
|
+
suite: Compliance test suite.
|
|
900
|
+
output: Output Markdown file path.
|
|
901
|
+
|
|
902
|
+
Example:
|
|
903
|
+
>>> generator.export_markdown(suite, Path("compliance_tests.md"))
|
|
904
|
+
"""
|
|
905
|
+
output.write_text(suite.documentation)
|
|
906
|
+
|
|
907
|
+
|
|
908
|
+
__all__ = [
|
|
909
|
+
"ComplianceConfig",
|
|
910
|
+
"ComplianceTestGenerator",
|
|
911
|
+
"ComplianceTestSuite",
|
|
912
|
+
"StandardType",
|
|
913
|
+
"TestCase",
|
|
914
|
+
"TestType",
|
|
915
|
+
]
|