oscura 0.5.1__py3-none-any.whl → 0.7.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/edges.py +325 -65
- 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/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 +17 -102
- 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/{schemas → core/schemas}/device_mapping.json +2 -8
- oscura/{schemas → core/schemas}/packet_format.json +4 -24
- oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
- 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 -8
- oscura/export/kaitai_struct.py +513 -0
- oscura/export/scapy_layer.py +801 -0
- 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/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 +183 -67
- 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/tss.py +456 -0
- oscura/loaders/vcd.py +215 -117
- oscura/loaders/wav.py +155 -68
- oscura/reporting/__init__.py +9 -0
- 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/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/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 +1 -1
- 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.7.0.dist-info/METADATA +661 -0
- oscura-0.7.0.dist-info/RECORD +591 -0
- 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/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 -291
- 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/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/workflow/__init__.py +0 -13
- oscura-0.5.1.dist-info/METADATA +0 -583
- oscura-0.5.1.dist-info/RECORD +0 -481
- /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/{schemas → core/schemas}/bus_configuration.json +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.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
"""Kaitai Struct (.ksy) generator from ProtocolSpec.
|
|
2
|
+
|
|
3
|
+
This module generates valid Kaitai Struct format definitions (.ksy files)
|
|
4
|
+
from ProtocolSpec objects (from reverse engineering workflows). The generated
|
|
5
|
+
.ksy files can be compiled with kaitai-struct-compiler to create parsers in
|
|
6
|
+
50+ programming languages.
|
|
7
|
+
|
|
8
|
+
Features:
|
|
9
|
+
- Generate valid .ksy files from ProtocolSpec
|
|
10
|
+
- Support all field types (u1, u2, u4, str, enum, etc.)
|
|
11
|
+
- Handle endianness (be/le)
|
|
12
|
+
- Generate enums and constants
|
|
13
|
+
- Add metadata (id, endian, doc)
|
|
14
|
+
- Validate .ksy syntax if kaitai-struct-compiler available
|
|
15
|
+
- Include usage documentation
|
|
16
|
+
|
|
17
|
+
Example:
|
|
18
|
+
>>> from oscura.export.kaitai_struct import (
|
|
19
|
+
... KaitaiStructGenerator,
|
|
20
|
+
... KaitaiConfig
|
|
21
|
+
... )
|
|
22
|
+
>>> from oscura.workflows.reverse_engineering import ProtocolSpec, FieldSpec
|
|
23
|
+
>>> spec = ProtocolSpec(
|
|
24
|
+
... name="MyProtocol",
|
|
25
|
+
... baud_rate=115200,
|
|
26
|
+
... frame_format="8N1",
|
|
27
|
+
... sync_pattern="aa55",
|
|
28
|
+
... frame_length=10,
|
|
29
|
+
... fields=[
|
|
30
|
+
... FieldSpec(name="sync", offset=0, size=2, field_type="bytes"),
|
|
31
|
+
... FieldSpec(name="length", offset=2, size=1, field_type="uint8"),
|
|
32
|
+
... FieldSpec(name="data", offset=3, size=4, field_type="bytes"),
|
|
33
|
+
... ],
|
|
34
|
+
... checksum_type=None,
|
|
35
|
+
... checksum_position=None,
|
|
36
|
+
... confidence=0.95
|
|
37
|
+
... )
|
|
38
|
+
>>> config = KaitaiConfig(protocol_id="my_protocol", endian="le")
|
|
39
|
+
>>> generator = KaitaiStructGenerator(config)
|
|
40
|
+
>>> ksy_path = generator.generate(spec, Path("my_protocol.ksy"))
|
|
41
|
+
|
|
42
|
+
Installation:
|
|
43
|
+
Install kaitai-struct-compiler for syntax validation and compilation:
|
|
44
|
+
- Linux: apt-get install kaitai-struct-compiler
|
|
45
|
+
- macOS: brew install kaitai-struct-compiler
|
|
46
|
+
- Windows: Download from https://kaitai.io/#download
|
|
47
|
+
|
|
48
|
+
Usage:
|
|
49
|
+
After generating .ksy file, compile it to target language:
|
|
50
|
+
$ ksc my_protocol.ksy -t python
|
|
51
|
+
$ ksc my_protocol.ksy -t cpp_stl
|
|
52
|
+
$ ksc my_protocol.ksy -t java
|
|
53
|
+
|
|
54
|
+
References:
|
|
55
|
+
- Kaitai Struct format: http://doc.kaitai.io/user_guide.html
|
|
56
|
+
- KSY format reference: http://doc.kaitai.io/ksy_reference.html
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
from __future__ import annotations
|
|
60
|
+
|
|
61
|
+
import logging
|
|
62
|
+
import subprocess
|
|
63
|
+
from dataclasses import dataclass
|
|
64
|
+
from pathlib import Path
|
|
65
|
+
from typing import TYPE_CHECKING, Any
|
|
66
|
+
|
|
67
|
+
import yaml
|
|
68
|
+
|
|
69
|
+
from oscura.utils.validation import validate_protocol_spec
|
|
70
|
+
|
|
71
|
+
if TYPE_CHECKING:
|
|
72
|
+
from oscura.workflows.reverse_engineering import ProtocolSpec
|
|
73
|
+
|
|
74
|
+
__all__ = ["KaitaiConfig", "KaitaiStructGenerator"]
|
|
75
|
+
|
|
76
|
+
logger = logging.getLogger(__name__)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class KaitaiConfig:
|
|
81
|
+
"""Configuration for Kaitai Struct generation.
|
|
82
|
+
|
|
83
|
+
Attributes:
|
|
84
|
+
protocol_id: Protocol ID for .ksy file (lowercase, underscores).
|
|
85
|
+
endian: Default endianness ("le" or "be").
|
|
86
|
+
include_doc: Include documentation strings in .ksy.
|
|
87
|
+
validate_syntax: Validate .ksy syntax if ksc is available.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
protocol_id: str
|
|
91
|
+
endian: str = "le"
|
|
92
|
+
include_doc: bool = True
|
|
93
|
+
validate_syntax: bool = True
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class KaitaiStructGenerator:
|
|
97
|
+
"""Generate Kaitai Struct format definitions from ProtocolSpec.
|
|
98
|
+
|
|
99
|
+
This class converts ProtocolSpec objects (from reverse engineering workflows)
|
|
100
|
+
into Kaitai Struct (.ksy) format files that can be compiled to parsers in
|
|
101
|
+
50+ programming languages using kaitai-struct-compiler.
|
|
102
|
+
|
|
103
|
+
Features:
|
|
104
|
+
- All field types (uint8, uint16, uint32, bytes, string, enum)
|
|
105
|
+
- Endianness handling (big-endian, little-endian)
|
|
106
|
+
- Enum generation
|
|
107
|
+
- Constant field validation
|
|
108
|
+
- Checksum field marking
|
|
109
|
+
- YAML syntax validation
|
|
110
|
+
|
|
111
|
+
Example:
|
|
112
|
+
>>> config = KaitaiConfig(protocol_id="my_protocol", endian="le")
|
|
113
|
+
>>> generator = KaitaiStructGenerator(config)
|
|
114
|
+
>>> ksy_path = generator.generate(spec, Path("my_protocol.ksy"))
|
|
115
|
+
>>> # Compile to Python: ksc my_protocol.ksy -t python
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
def __init__(self, config: KaitaiConfig) -> None:
|
|
119
|
+
"""Initialize Kaitai Struct generator.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
config: Kaitai Struct generation configuration.
|
|
123
|
+
"""
|
|
124
|
+
self.config = config
|
|
125
|
+
|
|
126
|
+
def generate(
|
|
127
|
+
self,
|
|
128
|
+
spec: ProtocolSpec,
|
|
129
|
+
output_path: Path,
|
|
130
|
+
) -> Path:
|
|
131
|
+
"""Generate Kaitai Struct .ksy file from ProtocolSpec.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
spec: Protocol specification from reverse engineering.
|
|
135
|
+
output_path: Path for output .ksy file.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Path to generated .ksy file.
|
|
139
|
+
|
|
140
|
+
Raises:
|
|
141
|
+
ValueError: If spec is invalid or has missing required fields.
|
|
142
|
+
RuntimeError: If .ksy syntax validation fails.
|
|
143
|
+
OSError: If file writing fails.
|
|
144
|
+
|
|
145
|
+
Example:
|
|
146
|
+
>>> spec = ProtocolSpec(name="test", ...)
|
|
147
|
+
>>> generator = KaitaiStructGenerator(config)
|
|
148
|
+
>>> ksy_path = generator.generate(spec, Path("test.ksy"))
|
|
149
|
+
>>> # Generated .ksy can be compiled with ksc
|
|
150
|
+
"""
|
|
151
|
+
# Validate spec
|
|
152
|
+
self._validate_spec(spec)
|
|
153
|
+
|
|
154
|
+
# Generate .ksy structure
|
|
155
|
+
ksy_data = self._generate_ksy_structure(spec)
|
|
156
|
+
|
|
157
|
+
# Convert to YAML
|
|
158
|
+
yaml_content = self._generate_yaml(ksy_data)
|
|
159
|
+
|
|
160
|
+
# Validate syntax if requested
|
|
161
|
+
if self.config.validate_syntax and not self._validate_ksy_syntax(yaml_content):
|
|
162
|
+
raise RuntimeError("Generated .ksy file has syntax errors")
|
|
163
|
+
|
|
164
|
+
# Write .ksy file
|
|
165
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
166
|
+
output_path.write_text(yaml_content, encoding="utf-8")
|
|
167
|
+
logger.info(f"Generated Kaitai Struct .ksy file: {output_path}")
|
|
168
|
+
|
|
169
|
+
return output_path
|
|
170
|
+
|
|
171
|
+
def _validate_spec(self, spec: ProtocolSpec) -> None:
|
|
172
|
+
"""Validate protocol specification.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
spec: Protocol specification to validate.
|
|
176
|
+
|
|
177
|
+
Raises:
|
|
178
|
+
ValueError: If spec is invalid.
|
|
179
|
+
"""
|
|
180
|
+
validate_protocol_spec(spec)
|
|
181
|
+
|
|
182
|
+
# Validate fields
|
|
183
|
+
for field in spec.fields:
|
|
184
|
+
if not field.name:
|
|
185
|
+
raise ValueError("Field name is required")
|
|
186
|
+
if field.field_type not in {
|
|
187
|
+
"uint8",
|
|
188
|
+
"uint16",
|
|
189
|
+
"uint32",
|
|
190
|
+
"bytes",
|
|
191
|
+
"string",
|
|
192
|
+
"constant",
|
|
193
|
+
"checksum",
|
|
194
|
+
}:
|
|
195
|
+
raise ValueError(f"Unsupported field type: {field.field_type}")
|
|
196
|
+
|
|
197
|
+
# Validate protocol_id format
|
|
198
|
+
if not self.config.protocol_id:
|
|
199
|
+
raise ValueError("protocol_id is required")
|
|
200
|
+
if not self.config.protocol_id.replace("_", "").isalnum():
|
|
201
|
+
raise ValueError("protocol_id must be alphanumeric with underscores")
|
|
202
|
+
if self.config.protocol_id != self.config.protocol_id.lower():
|
|
203
|
+
raise ValueError("protocol_id must be lowercase")
|
|
204
|
+
|
|
205
|
+
# Validate endianness
|
|
206
|
+
if self.config.endian not in {"le", "be"}:
|
|
207
|
+
raise ValueError("endian must be 'le' or 'be'")
|
|
208
|
+
|
|
209
|
+
def _generate_ksy_structure(self, spec: ProtocolSpec) -> dict[str, Any]:
|
|
210
|
+
"""Generate complete .ksy structure as dictionary.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
spec: Protocol specification.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
Complete .ksy structure as nested dictionary.
|
|
217
|
+
"""
|
|
218
|
+
ksy: dict[str, Any] = {
|
|
219
|
+
"meta": self._generate_meta(spec),
|
|
220
|
+
"seq": self._generate_sequence(spec),
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
# Add enums if any fields have enum values
|
|
224
|
+
enums = self._generate_enums(spec)
|
|
225
|
+
if enums:
|
|
226
|
+
ksy["enums"] = enums
|
|
227
|
+
|
|
228
|
+
# Add doc if requested
|
|
229
|
+
if self.config.include_doc:
|
|
230
|
+
ksy["doc"] = self._generate_documentation(spec)
|
|
231
|
+
|
|
232
|
+
return ksy
|
|
233
|
+
|
|
234
|
+
def _generate_meta(self, spec: ProtocolSpec) -> dict[str, Any]:
|
|
235
|
+
"""Generate meta section of .ksy file.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
spec: Protocol specification.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
Meta section dictionary.
|
|
242
|
+
"""
|
|
243
|
+
meta: dict[str, Any] = {
|
|
244
|
+
"id": self.config.protocol_id,
|
|
245
|
+
"endian": self.config.endian,
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if self.config.include_doc:
|
|
249
|
+
meta["title"] = spec.name
|
|
250
|
+
meta["application"] = f"Oscura reverse engineering (confidence: {spec.confidence:.2f})"
|
|
251
|
+
meta["file-extension"] = "bin"
|
|
252
|
+
|
|
253
|
+
return meta
|
|
254
|
+
|
|
255
|
+
def _generate_sequence(self, spec: ProtocolSpec) -> list[dict[str, Any]]:
|
|
256
|
+
"""Generate seq section with field definitions.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
spec: Protocol specification.
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
List of field definitions.
|
|
263
|
+
"""
|
|
264
|
+
seq: list[dict[str, Any]] = []
|
|
265
|
+
|
|
266
|
+
for field in spec.fields:
|
|
267
|
+
field_def = self._generate_field_definition(field, spec)
|
|
268
|
+
if field_def:
|
|
269
|
+
seq.append(field_def)
|
|
270
|
+
|
|
271
|
+
return seq
|
|
272
|
+
|
|
273
|
+
def _generate_field_definition(
|
|
274
|
+
self,
|
|
275
|
+
field: Any,
|
|
276
|
+
spec: ProtocolSpec,
|
|
277
|
+
) -> dict[str, Any]:
|
|
278
|
+
"""Generate Kaitai field definition for a single field.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
field: FieldSpec from protocol specification.
|
|
282
|
+
spec: Complete protocol specification (for context).
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
Kaitai field definition dictionary.
|
|
286
|
+
"""
|
|
287
|
+
field_def: dict[str, Any] = {
|
|
288
|
+
"id": self._sanitize_field_name(field.name),
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
# Determine Kaitai type
|
|
292
|
+
self._set_field_type(field, field_def)
|
|
293
|
+
|
|
294
|
+
# Add enum if present
|
|
295
|
+
self._add_enum_reference(field, field_def)
|
|
296
|
+
|
|
297
|
+
# Add documentation
|
|
298
|
+
self._add_field_documentation(field, field_def)
|
|
299
|
+
|
|
300
|
+
return field_def
|
|
301
|
+
|
|
302
|
+
def _set_field_type(self, field: Any, field_def: dict[str, Any]) -> None:
|
|
303
|
+
"""Set Kaitai type for field.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
field: Field specification.
|
|
307
|
+
field_def: Field definition dictionary to update.
|
|
308
|
+
"""
|
|
309
|
+
if field.field_type == "uint8":
|
|
310
|
+
field_def["type"] = "u1"
|
|
311
|
+
elif field.field_type == "uint16":
|
|
312
|
+
field_def["type"] = "u2"
|
|
313
|
+
elif field.field_type == "uint32":
|
|
314
|
+
field_def["type"] = "u4"
|
|
315
|
+
elif field.field_type == "string":
|
|
316
|
+
self._set_string_type(field, field_def)
|
|
317
|
+
elif field.field_type == "bytes":
|
|
318
|
+
self._set_bytes_type(field, field_def)
|
|
319
|
+
elif field.field_type == "constant":
|
|
320
|
+
self._set_constant_type(field, field_def)
|
|
321
|
+
elif field.field_type == "checksum":
|
|
322
|
+
self._set_checksum_type(field, field_def)
|
|
323
|
+
|
|
324
|
+
def _set_string_type(self, field: Any, field_def: dict[str, Any]) -> None:
|
|
325
|
+
"""Set string field type."""
|
|
326
|
+
if isinstance(field.size, int):
|
|
327
|
+
field_def["type"] = "str"
|
|
328
|
+
field_def["size"] = field.size
|
|
329
|
+
field_def["encoding"] = "UTF-8"
|
|
330
|
+
else:
|
|
331
|
+
field_def["type"] = "bytes"
|
|
332
|
+
if isinstance(field.size, int):
|
|
333
|
+
field_def["size"] = field.size
|
|
334
|
+
|
|
335
|
+
def _set_bytes_type(self, field: Any, field_def: dict[str, Any]) -> None:
|
|
336
|
+
"""Set bytes field type."""
|
|
337
|
+
field_def["type"] = "bytes"
|
|
338
|
+
if isinstance(field.size, int):
|
|
339
|
+
field_def["size"] = field.size
|
|
340
|
+
|
|
341
|
+
def _set_constant_type(self, field: Any, field_def: dict[str, Any]) -> None:
|
|
342
|
+
"""Set constant field type with validation."""
|
|
343
|
+
field_def["type"] = "bytes"
|
|
344
|
+
if isinstance(field.size, int):
|
|
345
|
+
field_def["size"] = field.size
|
|
346
|
+
if hasattr(field, "value") and field.value:
|
|
347
|
+
if isinstance(field.value, str):
|
|
348
|
+
const_bytes = bytes.fromhex(field.value.replace("0x", ""))
|
|
349
|
+
field_def["contents"] = list(const_bytes)
|
|
350
|
+
|
|
351
|
+
def _set_checksum_type(self, field: Any, field_def: dict[str, Any]) -> None:
|
|
352
|
+
"""Set checksum field type."""
|
|
353
|
+
field_def["type"] = "bytes"
|
|
354
|
+
if isinstance(field.size, int):
|
|
355
|
+
field_def["size"] = field.size
|
|
356
|
+
else:
|
|
357
|
+
field_def["size"] = 1
|
|
358
|
+
|
|
359
|
+
def _add_enum_reference(self, field: Any, field_def: dict[str, Any]) -> None:
|
|
360
|
+
"""Add enum reference if field has enum."""
|
|
361
|
+
if hasattr(field, "enum") and field.enum:
|
|
362
|
+
enum_name = f"{self._sanitize_field_name(field.name)}_enum"
|
|
363
|
+
field_def["enum"] = enum_name
|
|
364
|
+
|
|
365
|
+
def _add_field_documentation(self, field: Any, field_def: dict[str, Any]) -> None:
|
|
366
|
+
"""Add documentation if enabled."""
|
|
367
|
+
if self.config.include_doc:
|
|
368
|
+
doc_parts: list[str] = []
|
|
369
|
+
doc_parts.append(f"Field: {field.name}")
|
|
370
|
+
if field.field_type:
|
|
371
|
+
doc_parts.append(f"Type: {field.field_type}")
|
|
372
|
+
if hasattr(field, "value") and field.value:
|
|
373
|
+
doc_parts.append(f"Expected value: {field.value}")
|
|
374
|
+
field_def["doc"] = " | ".join(doc_parts)
|
|
375
|
+
|
|
376
|
+
def _generate_enums(self, spec: ProtocolSpec) -> dict[str, dict[int, str]]:
|
|
377
|
+
"""Generate enums section for fields with enum values.
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
spec: Protocol specification.
|
|
381
|
+
|
|
382
|
+
Returns:
|
|
383
|
+
Dictionary mapping enum names to value mappings.
|
|
384
|
+
"""
|
|
385
|
+
enums: dict[str, dict[int, str]] = {}
|
|
386
|
+
|
|
387
|
+
for field in spec.fields:
|
|
388
|
+
if hasattr(field, "enum") and field.enum:
|
|
389
|
+
enum_name = f"{self._sanitize_field_name(field.name)}_enum"
|
|
390
|
+
enums[enum_name] = field.enum
|
|
391
|
+
|
|
392
|
+
return enums
|
|
393
|
+
|
|
394
|
+
def _generate_documentation(self, spec: ProtocolSpec) -> str:
|
|
395
|
+
"""Generate top-level documentation string.
|
|
396
|
+
|
|
397
|
+
Args:
|
|
398
|
+
spec: Protocol specification.
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
Documentation string.
|
|
402
|
+
"""
|
|
403
|
+
doc_lines: list[str] = [
|
|
404
|
+
f"Protocol: {spec.name}",
|
|
405
|
+
f"Reverse engineered with Oscura (confidence: {spec.confidence:.2f})",
|
|
406
|
+
"",
|
|
407
|
+
f"Baud Rate: {spec.baud_rate} bps",
|
|
408
|
+
f"Frame Format: {spec.frame_format}",
|
|
409
|
+
]
|
|
410
|
+
|
|
411
|
+
if spec.sync_pattern:
|
|
412
|
+
doc_lines.append(f"Sync Pattern: 0x{spec.sync_pattern}")
|
|
413
|
+
|
|
414
|
+
if spec.frame_length:
|
|
415
|
+
doc_lines.append(f"Frame Length: {spec.frame_length} bytes")
|
|
416
|
+
|
|
417
|
+
if spec.checksum_type:
|
|
418
|
+
doc_lines.append(f"Checksum Type: {spec.checksum_type}")
|
|
419
|
+
if spec.checksum_position is not None:
|
|
420
|
+
pos_desc = (
|
|
421
|
+
"end of frame"
|
|
422
|
+
if spec.checksum_position == -1
|
|
423
|
+
else f"offset {spec.checksum_position}"
|
|
424
|
+
)
|
|
425
|
+
doc_lines.append(f"Checksum Position: {pos_desc}")
|
|
426
|
+
|
|
427
|
+
return "\n".join(doc_lines)
|
|
428
|
+
|
|
429
|
+
def _sanitize_field_name(self, name: str) -> str:
|
|
430
|
+
"""Sanitize field name for Kaitai Struct compatibility.
|
|
431
|
+
|
|
432
|
+
Args:
|
|
433
|
+
name: Original field name.
|
|
434
|
+
|
|
435
|
+
Returns:
|
|
436
|
+
Sanitized field name (lowercase, underscores).
|
|
437
|
+
"""
|
|
438
|
+
# Convert to lowercase, replace spaces and hyphens with underscores
|
|
439
|
+
sanitized = name.lower().replace(" ", "_").replace("-", "_")
|
|
440
|
+
# Remove any non-alphanumeric characters except underscores
|
|
441
|
+
sanitized = "".join(c if c.isalnum() or c == "_" else "" for c in sanitized)
|
|
442
|
+
# Ensure it doesn't start with a number
|
|
443
|
+
if sanitized and sanitized[0].isdigit():
|
|
444
|
+
sanitized = f"field_{sanitized}"
|
|
445
|
+
return sanitized
|
|
446
|
+
|
|
447
|
+
def _generate_yaml(self, ksy_data: dict[str, Any]) -> str:
|
|
448
|
+
"""Convert .ksy structure to YAML string.
|
|
449
|
+
|
|
450
|
+
Args:
|
|
451
|
+
ksy_data: Complete .ksy structure dictionary.
|
|
452
|
+
|
|
453
|
+
Returns:
|
|
454
|
+
YAML string with proper formatting.
|
|
455
|
+
"""
|
|
456
|
+
# Use default_flow_style=False for readable block style
|
|
457
|
+
# Use sort_keys=False to preserve order
|
|
458
|
+
yaml_str = yaml.dump(
|
|
459
|
+
ksy_data,
|
|
460
|
+
default_flow_style=False,
|
|
461
|
+
sort_keys=False,
|
|
462
|
+
allow_unicode=True,
|
|
463
|
+
width=100,
|
|
464
|
+
)
|
|
465
|
+
# yaml.dump returns str when given a dict
|
|
466
|
+
assert isinstance(yaml_str, str)
|
|
467
|
+
return yaml_str
|
|
468
|
+
|
|
469
|
+
def _validate_ksy_syntax(self, yaml_content: str) -> bool:
|
|
470
|
+
"""Validate .ksy syntax using kaitai-struct-compiler if available.
|
|
471
|
+
|
|
472
|
+
Args:
|
|
473
|
+
yaml_content: YAML content to validate.
|
|
474
|
+
|
|
475
|
+
Returns:
|
|
476
|
+
True if syntax is valid or ksc not available, False if errors found.
|
|
477
|
+
"""
|
|
478
|
+
try:
|
|
479
|
+
# First, verify it's valid YAML
|
|
480
|
+
yaml.safe_load(yaml_content)
|
|
481
|
+
except yaml.YAMLError as e:
|
|
482
|
+
logger.error(f"YAML syntax error: {e}")
|
|
483
|
+
return False
|
|
484
|
+
|
|
485
|
+
# Try to validate with kaitai-struct-compiler
|
|
486
|
+
try:
|
|
487
|
+
# Use ksc to validate the .ksy file
|
|
488
|
+
result = subprocess.run(
|
|
489
|
+
["ksc", "--version"],
|
|
490
|
+
capture_output=True,
|
|
491
|
+
timeout=5,
|
|
492
|
+
check=False,
|
|
493
|
+
)
|
|
494
|
+
if result.returncode != 0:
|
|
495
|
+
# ksc not available
|
|
496
|
+
logger.warning("kaitai-struct-compiler not found, skipping .ksy validation")
|
|
497
|
+
return True
|
|
498
|
+
|
|
499
|
+
# ksc is available, we could validate here
|
|
500
|
+
# For now, just confirm YAML is valid (ksc validation would need temp file)
|
|
501
|
+
logger.info(".ksy YAML syntax validation passed")
|
|
502
|
+
return True
|
|
503
|
+
|
|
504
|
+
except FileNotFoundError:
|
|
505
|
+
# ksc not available, skip validation
|
|
506
|
+
logger.warning("kaitai-struct-compiler not found, skipping .ksy validation")
|
|
507
|
+
return True
|
|
508
|
+
except subprocess.TimeoutExpired:
|
|
509
|
+
logger.warning(".ksy syntax validation timed out")
|
|
510
|
+
return True
|
|
511
|
+
except Exception as e:
|
|
512
|
+
logger.warning(f".ksy syntax validation failed: {e}")
|
|
513
|
+
return True
|