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
oscura/inference/stream.py
CHANGED
|
@@ -182,71 +182,134 @@ class UDPStreamReassembler:
|
|
|
182
182
|
key = flow_key or f"{segment.src}-{segment.dst}"
|
|
183
183
|
self._segments[key].append(segment)
|
|
184
184
|
|
|
185
|
-
def
|
|
186
|
-
"""
|
|
185
|
+
def _get_empty_stream(self) -> ReassembledStream:
|
|
186
|
+
"""Create empty stream result.
|
|
187
187
|
|
|
188
|
-
|
|
188
|
+
Returns:
|
|
189
|
+
Empty ReassembledStream.
|
|
190
|
+
"""
|
|
191
|
+
return ReassembledStream(
|
|
192
|
+
data=b"",
|
|
193
|
+
src="",
|
|
194
|
+
dst="",
|
|
195
|
+
start_time=0.0,
|
|
196
|
+
end_time=0.0,
|
|
197
|
+
segments=0,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
def _resolve_flow_key(self, flow_key: str | None) -> str | None:
|
|
201
|
+
"""Resolve flow key to actual key or None if empty.
|
|
189
202
|
|
|
190
203
|
Args:
|
|
191
|
-
flow_key:
|
|
204
|
+
flow_key: Requested flow key.
|
|
192
205
|
|
|
193
206
|
Returns:
|
|
194
|
-
|
|
207
|
+
Resolved flow key or None.
|
|
195
208
|
"""
|
|
196
209
|
if flow_key is None:
|
|
197
|
-
# Get first/only flow
|
|
198
210
|
if not self._segments:
|
|
199
|
-
return
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
dst="",
|
|
203
|
-
start_time=0.0,
|
|
204
|
-
end_time=0.0,
|
|
205
|
-
segments=0,
|
|
206
|
-
)
|
|
207
|
-
flow_key = next(iter(self._segments.keys()))
|
|
211
|
+
return None
|
|
212
|
+
return next(iter(self._segments.keys()))
|
|
213
|
+
return flow_key
|
|
208
214
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
return ReassembledStream(
|
|
212
|
-
data=b"",
|
|
213
|
-
src="",
|
|
214
|
-
dst="",
|
|
215
|
-
start_time=0.0,
|
|
216
|
-
end_time=0.0,
|
|
217
|
-
segments=0,
|
|
218
|
-
)
|
|
219
|
-
|
|
220
|
-
# Sort by sequence number
|
|
221
|
-
sorted_segments = sorted(segments, key=lambda s: s.sequence_number)
|
|
215
|
+
def _count_out_of_order(self, segments: list[StreamSegment]) -> int:
|
|
216
|
+
"""Count out-of-order segments.
|
|
222
217
|
|
|
223
|
-
|
|
224
|
-
|
|
218
|
+
Args:
|
|
219
|
+
segments: List of segments (unsorted).
|
|
225
220
|
|
|
226
|
-
|
|
221
|
+
Returns:
|
|
222
|
+
Number of out-of-order segments.
|
|
223
|
+
"""
|
|
227
224
|
out_of_order = 0
|
|
228
225
|
max_seq_seen = -1
|
|
229
226
|
for segment in segments:
|
|
230
227
|
if segment.sequence_number < max_seq_seen:
|
|
231
228
|
out_of_order += 1
|
|
232
229
|
max_seq_seen = max(max_seq_seen, segment.sequence_number)
|
|
230
|
+
return out_of_order
|
|
233
231
|
|
|
234
|
-
|
|
235
|
-
gaps
|
|
232
|
+
def _detect_gaps(self, sorted_segments: list[StreamSegment]) -> list[tuple[int, int]]:
|
|
233
|
+
"""Detect gaps in sequence numbers.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
sorted_segments: Segments sorted by sequence number.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
List of (expected, actual) gap tuples.
|
|
240
|
+
"""
|
|
241
|
+
gaps: list[tuple[int, int]] = []
|
|
236
242
|
for i in range(1, len(sorted_segments)):
|
|
237
243
|
expected = sorted_segments[i - 1].sequence_number + len(sorted_segments[i - 1].data)
|
|
238
244
|
actual = sorted_segments[i].sequence_number
|
|
239
245
|
if actual > expected:
|
|
240
246
|
gaps.append((expected, actual))
|
|
247
|
+
return gaps
|
|
248
|
+
|
|
249
|
+
def _get_time_range(self, sorted_segments: list[StreamSegment]) -> tuple[float, float]:
|
|
250
|
+
"""Get start and end times from segments.
|
|
241
251
|
|
|
252
|
+
Args:
|
|
253
|
+
sorted_segments: List of segments.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
Tuple of (start_time, end_time).
|
|
257
|
+
"""
|
|
242
258
|
timestamps = [s.timestamp for s in sorted_segments if s.timestamp > 0]
|
|
259
|
+
if timestamps:
|
|
260
|
+
return min(timestamps), max(timestamps)
|
|
261
|
+
return 0.0, 0.0
|
|
262
|
+
|
|
263
|
+
def _extract_addresses(self, sorted_segments: list[StreamSegment]) -> tuple[str, str]:
|
|
264
|
+
"""Extract source and destination addresses from segments.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
sorted_segments: List of segments.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
Tuple of (src, dst).
|
|
271
|
+
"""
|
|
272
|
+
if sorted_segments:
|
|
273
|
+
return sorted_segments[0].src, sorted_segments[0].dst
|
|
274
|
+
return "", ""
|
|
275
|
+
|
|
276
|
+
def get_stream(self, flow_key: str | None = None) -> ReassembledStream:
|
|
277
|
+
"""Get reassembled stream for a flow.
|
|
278
|
+
|
|
279
|
+
Implements RE-STR-001: UDP stream reconstruction.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
flow_key: Flow identifier.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
ReassembledStream with ordered data.
|
|
286
|
+
"""
|
|
287
|
+
# Resolve flow key
|
|
288
|
+
resolved_key = self._resolve_flow_key(flow_key)
|
|
289
|
+
if resolved_key is None:
|
|
290
|
+
return self._get_empty_stream()
|
|
291
|
+
|
|
292
|
+
# Get segments
|
|
293
|
+
segments = self._segments.get(resolved_key, [])
|
|
294
|
+
if not segments:
|
|
295
|
+
return self._get_empty_stream()
|
|
296
|
+
|
|
297
|
+
# Sort by sequence number
|
|
298
|
+
sorted_segments = sorted(segments, key=lambda s: s.sequence_number)
|
|
299
|
+
|
|
300
|
+
# Build stream components
|
|
301
|
+
data = b"".join(s.data for s in sorted_segments)
|
|
302
|
+
out_of_order = self._count_out_of_order(segments)
|
|
303
|
+
gaps = self._detect_gaps(sorted_segments)
|
|
304
|
+
start_time, end_time = self._get_time_range(sorted_segments)
|
|
305
|
+
src, dst = self._extract_addresses(sorted_segments)
|
|
243
306
|
|
|
244
307
|
return ReassembledStream(
|
|
245
308
|
data=data,
|
|
246
|
-
src=
|
|
247
|
-
dst=
|
|
248
|
-
start_time=
|
|
249
|
-
end_time=
|
|
309
|
+
src=src,
|
|
310
|
+
dst=dst,
|
|
311
|
+
start_time=start_time,
|
|
312
|
+
end_time=end_time,
|
|
250
313
|
segments=len(sorted_segments),
|
|
251
314
|
gaps=gaps,
|
|
252
315
|
retransmits=0,
|
|
@@ -354,77 +417,84 @@ class TCPStreamReassembler:
|
|
|
354
417
|
|
|
355
418
|
self._segments[key].append(seg)
|
|
356
419
|
|
|
357
|
-
def
|
|
358
|
-
"""
|
|
359
|
-
|
|
360
|
-
Implements RE-STR-002: TCP stream reassembly.
|
|
420
|
+
def _extract_addresses(self, sorted_segments: list[StreamSegment]) -> tuple[str, str]:
|
|
421
|
+
"""Extract source and destination addresses from segments.
|
|
361
422
|
|
|
362
423
|
Args:
|
|
363
|
-
|
|
424
|
+
sorted_segments: Sorted list of stream segments.
|
|
364
425
|
|
|
365
426
|
Returns:
|
|
366
|
-
|
|
427
|
+
Tuple of (source, destination) addresses.
|
|
367
428
|
"""
|
|
368
|
-
if
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
data=b"",
|
|
372
|
-
src="",
|
|
373
|
-
dst="",
|
|
374
|
-
start_time=0.0,
|
|
375
|
-
end_time=0.0,
|
|
376
|
-
segments=0,
|
|
377
|
-
)
|
|
378
|
-
flow_key = next(iter(self._segments.keys()))
|
|
429
|
+
if not sorted_segments:
|
|
430
|
+
return ("", "")
|
|
431
|
+
return (sorted_segments[0].src, sorted_segments[0].dst)
|
|
379
432
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
return ReassembledStream(
|
|
383
|
-
data=b"",
|
|
384
|
-
src="",
|
|
385
|
-
dst="",
|
|
386
|
-
start_time=0.0,
|
|
387
|
-
end_time=0.0,
|
|
388
|
-
segments=0,
|
|
389
|
-
)
|
|
433
|
+
def _count_anomalies(self, segments: list[StreamSegment]) -> tuple[int, int]:
|
|
434
|
+
"""Count retransmits and out-of-order segments.
|
|
390
435
|
|
|
391
|
-
|
|
436
|
+
Args:
|
|
437
|
+
segments: List of segments in arrival order.
|
|
392
438
|
|
|
393
|
-
|
|
439
|
+
Returns:
|
|
440
|
+
Tuple of (retransmits, out_of_order) counts.
|
|
441
|
+
"""
|
|
442
|
+
# Count retransmits
|
|
394
443
|
retransmits = sum(1 for seg in segments if seg.is_retransmit)
|
|
395
444
|
|
|
396
|
-
#
|
|
397
|
-
if isn == 0 or isn > min(s.sequence_number for s in segments):
|
|
398
|
-
isn = min(s.sequence_number for s in segments)
|
|
399
|
-
|
|
400
|
-
# Detect out-of-order by checking arrival order vs sequence order
|
|
401
|
-
# Count segments that arrived before a segment with lower sequence
|
|
445
|
+
# Count out-of-order segments
|
|
402
446
|
out_of_order = 0
|
|
403
447
|
for i, seg in enumerate(segments):
|
|
404
|
-
# Check if any earlier segment has higher sequence
|
|
405
448
|
for j in range(i):
|
|
406
449
|
if segments[j].sequence_number > seg.sequence_number:
|
|
407
450
|
out_of_order += 1
|
|
408
451
|
break
|
|
409
452
|
|
|
410
|
-
|
|
411
|
-
|
|
453
|
+
return (retransmits, out_of_order)
|
|
454
|
+
|
|
455
|
+
def _detect_isn(self, flow_key: str, segments: list[StreamSegment]) -> int:
|
|
456
|
+
"""Detect initial sequence number for flow.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
flow_key: Flow identifier.
|
|
460
|
+
segments: List of segments.
|
|
461
|
+
|
|
462
|
+
Returns:
|
|
463
|
+
Initial sequence number.
|
|
464
|
+
"""
|
|
465
|
+
isn = self._isn.get(flow_key, 0) or 0
|
|
466
|
+
|
|
467
|
+
# If ISN wasn't detected via SYN, use minimum sequence number
|
|
468
|
+
if isn == 0 or isn > min(s.sequence_number for s in segments):
|
|
469
|
+
isn = min(s.sequence_number for s in segments)
|
|
412
470
|
|
|
413
|
-
|
|
471
|
+
return isn
|
|
472
|
+
|
|
473
|
+
def _build_data_buffer(
|
|
474
|
+
self, sorted_segments: list[StreamSegment], isn: int
|
|
475
|
+
) -> tuple[bytes, list[tuple[int, int]]]:
|
|
476
|
+
"""Build data buffer from sorted segments, handling gaps and overlaps.
|
|
477
|
+
|
|
478
|
+
Args:
|
|
479
|
+
sorted_segments: Segments sorted by sequence number.
|
|
480
|
+
isn: Initial sequence number.
|
|
481
|
+
|
|
482
|
+
Returns:
|
|
483
|
+
Tuple of (reassembled_data, gaps).
|
|
484
|
+
"""
|
|
414
485
|
data_buffer = bytearray()
|
|
415
486
|
current_offset = 0
|
|
416
487
|
gaps = []
|
|
417
488
|
|
|
418
489
|
for seg in sorted_segments:
|
|
419
490
|
if seg.is_retransmit:
|
|
420
|
-
continue
|
|
491
|
+
continue
|
|
421
492
|
|
|
422
493
|
rel_seq = (seg.sequence_number - isn) % (2**32)
|
|
423
494
|
|
|
424
495
|
if rel_seq > current_offset:
|
|
425
496
|
# Gap detected
|
|
426
497
|
gaps.append((current_offset, rel_seq))
|
|
427
|
-
# Fill gap with zeros
|
|
428
498
|
data_buffer.extend(b"\x00" * (rel_seq - current_offset))
|
|
429
499
|
current_offset = rel_seq
|
|
430
500
|
|
|
@@ -438,12 +508,59 @@ class TCPStreamReassembler:
|
|
|
438
508
|
data_buffer.extend(seg.data)
|
|
439
509
|
current_offset += len(seg.data)
|
|
440
510
|
|
|
511
|
+
return (bytes(data_buffer), gaps)
|
|
512
|
+
|
|
513
|
+
def get_stream(self, flow_key: str | None = None) -> ReassembledStream:
|
|
514
|
+
"""Get reassembled TCP stream.
|
|
515
|
+
|
|
516
|
+
Implements RE-STR-002: TCP stream reassembly.
|
|
517
|
+
|
|
518
|
+
Args:
|
|
519
|
+
flow_key: Flow identifier.
|
|
520
|
+
|
|
521
|
+
Returns:
|
|
522
|
+
ReassembledStream with complete data.
|
|
523
|
+
"""
|
|
524
|
+
# Handle empty or default flow key
|
|
525
|
+
if flow_key is None:
|
|
526
|
+
if not self._segments:
|
|
527
|
+
return ReassembledStream(
|
|
528
|
+
data=b"",
|
|
529
|
+
src="",
|
|
530
|
+
dst="",
|
|
531
|
+
start_time=0.0,
|
|
532
|
+
end_time=0.0,
|
|
533
|
+
segments=0,
|
|
534
|
+
)
|
|
535
|
+
flow_key = next(iter(self._segments.keys()))
|
|
536
|
+
|
|
537
|
+
segments = self._segments.get(flow_key, [])
|
|
538
|
+
if not segments:
|
|
539
|
+
return ReassembledStream(
|
|
540
|
+
data=b"",
|
|
541
|
+
src="",
|
|
542
|
+
dst="",
|
|
543
|
+
start_time=0.0,
|
|
544
|
+
end_time=0.0,
|
|
545
|
+
segments=0,
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
# Detect ISN and count anomalies
|
|
549
|
+
isn = self._detect_isn(flow_key, segments)
|
|
550
|
+
retransmits, out_of_order = self._count_anomalies(segments)
|
|
551
|
+
|
|
552
|
+
# Sort and reassemble
|
|
553
|
+
sorted_segments = sorted(segments, key=lambda s: (s.sequence_number - isn) % (2**32))
|
|
554
|
+
data, gaps = self._build_data_buffer(sorted_segments, isn)
|
|
555
|
+
|
|
556
|
+
# Extract metadata
|
|
557
|
+
src, dst = self._extract_addresses(sorted_segments)
|
|
441
558
|
timestamps = [s.timestamp for s in sorted_segments if s.timestamp > 0]
|
|
442
559
|
|
|
443
560
|
return ReassembledStream(
|
|
444
|
-
data=
|
|
445
|
-
src=
|
|
446
|
-
dst=
|
|
561
|
+
data=data,
|
|
562
|
+
src=src,
|
|
563
|
+
dst=dst,
|
|
447
564
|
start_time=min(timestamps) if timestamps else 0.0,
|
|
448
565
|
end_time=max(timestamps) if timestamps else 0.0,
|
|
449
566
|
segments=len(sorted_segments),
|
|
@@ -542,7 +659,29 @@ class MessageFramer:
|
|
|
542
659
|
Returns:
|
|
543
660
|
Detected framing type string.
|
|
544
661
|
"""
|
|
545
|
-
# Check for
|
|
662
|
+
# Check for delimiter-based framing
|
|
663
|
+
if self._is_delimiter_framed(data):
|
|
664
|
+
return "delimiter"
|
|
665
|
+
|
|
666
|
+
# Check for length-prefixed framing
|
|
667
|
+
if self._is_length_prefixed(data):
|
|
668
|
+
return "length_prefix"
|
|
669
|
+
|
|
670
|
+
# Check for fixed-size framing
|
|
671
|
+
if self._is_fixed_size(data):
|
|
672
|
+
return "fixed"
|
|
673
|
+
|
|
674
|
+
return "unknown"
|
|
675
|
+
|
|
676
|
+
def _is_delimiter_framed(self, data: bytes) -> bool:
|
|
677
|
+
"""Check if data uses delimiter-based framing.
|
|
678
|
+
|
|
679
|
+
Args:
|
|
680
|
+
data: Sample data.
|
|
681
|
+
|
|
682
|
+
Returns:
|
|
683
|
+
True if delimiter framing detected.
|
|
684
|
+
"""
|
|
546
685
|
common_delimiters = [b"\r\n", b"\n", b"\x00", b"\r"]
|
|
547
686
|
for delim in common_delimiters:
|
|
548
687
|
count = data.count(delim)
|
|
@@ -550,35 +689,56 @@ class MessageFramer:
|
|
|
550
689
|
# Check for regular spacing
|
|
551
690
|
parts = data.split(delim)
|
|
552
691
|
if parts and len({len(p) for p in parts if p}) <= 3:
|
|
553
|
-
return
|
|
554
|
-
|
|
555
|
-
# Check for length-prefixed
|
|
556
|
-
if len(data) >= 4:
|
|
557
|
-
# Try big-endian 2-byte length
|
|
558
|
-
for offset in range(min(8, len(data) - 2)):
|
|
559
|
-
length = int.from_bytes(data[offset : offset + 2], "big")
|
|
560
|
-
if 4 < length < len(data) and length < 65536:
|
|
561
|
-
# Check if data continues with similar pattern
|
|
562
|
-
next_offset = offset + length
|
|
563
|
-
if next_offset + 2 < len(data):
|
|
564
|
-
next_length = int.from_bytes(data[next_offset : next_offset + 2], "big")
|
|
565
|
-
if 4 < next_length < len(data):
|
|
566
|
-
return "length_prefix"
|
|
567
|
-
|
|
568
|
-
# Check for fixed size
|
|
569
|
-
if len(data) >= 32:
|
|
570
|
-
# Look for repeating pattern
|
|
571
|
-
for size in range(4, 128):
|
|
572
|
-
if len(data) % size == 0:
|
|
573
|
-
chunks = [data[i : i + size] for i in range(0, len(data), size)]
|
|
574
|
-
if len(chunks) >= 3:
|
|
575
|
-
# Check structural similarity
|
|
576
|
-
first = chunks[0][:4] if len(chunks[0]) >= 4 else chunks[0]
|
|
577
|
-
matches = sum(1 for c in chunks[1:] if c[: len(first)] == first)
|
|
578
|
-
if matches >= len(chunks) * 0.5:
|
|
579
|
-
return "fixed"
|
|
692
|
+
return True
|
|
693
|
+
return False
|
|
580
694
|
|
|
581
|
-
|
|
695
|
+
def _is_length_prefixed(self, data: bytes) -> bool:
|
|
696
|
+
"""Check if data uses length-prefixed framing.
|
|
697
|
+
|
|
698
|
+
Args:
|
|
699
|
+
data: Sample data.
|
|
700
|
+
|
|
701
|
+
Returns:
|
|
702
|
+
True if length-prefixed framing detected.
|
|
703
|
+
"""
|
|
704
|
+
if len(data) < 4:
|
|
705
|
+
return False
|
|
706
|
+
|
|
707
|
+
# Try big-endian 2-byte length
|
|
708
|
+
for offset in range(min(8, len(data) - 2)):
|
|
709
|
+
length = int.from_bytes(data[offset : offset + 2], "big")
|
|
710
|
+
if 4 < length < len(data) and length < 65536:
|
|
711
|
+
# Check if data continues with similar pattern
|
|
712
|
+
next_offset = offset + length
|
|
713
|
+
if next_offset + 2 < len(data):
|
|
714
|
+
next_length = int.from_bytes(data[next_offset : next_offset + 2], "big")
|
|
715
|
+
if 4 < next_length < len(data):
|
|
716
|
+
return True
|
|
717
|
+
return False
|
|
718
|
+
|
|
719
|
+
def _is_fixed_size(self, data: bytes) -> bool:
|
|
720
|
+
"""Check if data uses fixed-size framing.
|
|
721
|
+
|
|
722
|
+
Args:
|
|
723
|
+
data: Sample data.
|
|
724
|
+
|
|
725
|
+
Returns:
|
|
726
|
+
True if fixed-size framing detected.
|
|
727
|
+
"""
|
|
728
|
+
if len(data) < 32:
|
|
729
|
+
return False
|
|
730
|
+
|
|
731
|
+
# Look for repeating pattern
|
|
732
|
+
for size in range(4, 128):
|
|
733
|
+
if len(data) % size == 0:
|
|
734
|
+
chunks = [data[i : i + size] for i in range(0, len(data), size)]
|
|
735
|
+
if len(chunks) >= 3:
|
|
736
|
+
# Check structural similarity
|
|
737
|
+
first = chunks[0][:4] if len(chunks[0]) >= 4 else chunks[0]
|
|
738
|
+
matches = sum(1 for c in chunks[1:] if c[: len(first)] == first)
|
|
739
|
+
if matches >= len(chunks) * 0.5:
|
|
740
|
+
return True
|
|
741
|
+
return False
|
|
582
742
|
|
|
583
743
|
def _auto_frame(self, data: bytes) -> FramingResult:
|
|
584
744
|
"""Automatically detect and apply framing.
|
oscura/iot/__init__.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""IoT protocol decoders package.
|
|
2
|
+
|
|
3
|
+
Provides decoders for IoT wireless protocols including LoRaWAN, Zigbee, BLE, MQTT, CoAP, and more.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from oscura.iot.coap.analyzer import (
|
|
7
|
+
CoAPAnalyzer,
|
|
8
|
+
CoAPExchange,
|
|
9
|
+
CoAPMessage,
|
|
10
|
+
)
|
|
11
|
+
from oscura.iot.lorawan.decoder import (
|
|
12
|
+
LoRaWANDecoder,
|
|
13
|
+
LoRaWANFrame,
|
|
14
|
+
LoRaWANKeys,
|
|
15
|
+
decode_lorawan_frame,
|
|
16
|
+
)
|
|
17
|
+
from oscura.iot.mqtt.analyzer import (
|
|
18
|
+
MQTTAnalyzer,
|
|
19
|
+
MQTTPacket,
|
|
20
|
+
MQTTSession,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"CoAPAnalyzer",
|
|
25
|
+
"CoAPExchange",
|
|
26
|
+
"CoAPMessage",
|
|
27
|
+
"LoRaWANDecoder",
|
|
28
|
+
"LoRaWANFrame",
|
|
29
|
+
"LoRaWANKeys",
|
|
30
|
+
"MQTTAnalyzer",
|
|
31
|
+
"MQTTPacket",
|
|
32
|
+
"MQTTSession",
|
|
33
|
+
"decode_lorawan_frame",
|
|
34
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""CoAP (Constrained Application Protocol) analyzer.
|
|
2
|
+
|
|
3
|
+
This module provides comprehensive CoAP (RFC 7252) protocol analysis including
|
|
4
|
+
message parsing, request/response matching, blockwise transfer support, and
|
|
5
|
+
observe extension support.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.iot.coap import CoAPAnalyzer, CoAPMessage
|
|
9
|
+
>>> analyzer = CoAPAnalyzer()
|
|
10
|
+
>>> data = bytes([0x40, 0x01, 0x00, 0x01]) # CON GET message
|
|
11
|
+
>>> message = analyzer.parse_message(data, timestamp=0.0)
|
|
12
|
+
>>> print(message.msg_type, message.code)
|
|
13
|
+
CON GET
|
|
14
|
+
|
|
15
|
+
References:
|
|
16
|
+
RFC 7252: CoAP (Constrained Application Protocol)
|
|
17
|
+
RFC 7959: Blockwise Transfer in CoAP
|
|
18
|
+
RFC 7641: Observing Resources in CoAP
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from oscura.iot.coap.analyzer import CoAPAnalyzer, CoAPExchange, CoAPMessage
|
|
24
|
+
from oscura.iot.coap.options import CONTENT_FORMATS, OPTIONS
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"CONTENT_FORMATS",
|
|
28
|
+
"OPTIONS",
|
|
29
|
+
"CoAPAnalyzer",
|
|
30
|
+
"CoAPExchange",
|
|
31
|
+
"CoAPMessage",
|
|
32
|
+
]
|