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,412 @@
|
|
|
1
|
+
"""BACnet encoding and decoding utilities.
|
|
2
|
+
|
|
3
|
+
This module provides low-level BACnet encoding/decoding functions for tags,
|
|
4
|
+
object identifiers, and application data types according to ASHRAE 135-2020.
|
|
5
|
+
|
|
6
|
+
References:
|
|
7
|
+
ANSI/ASHRAE Standard 135-2020, Clause 20: Encoding of BACnet Tags
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def parse_tag(data: bytes, offset: int) -> tuple[dict[str, Any], int]:
|
|
16
|
+
"""Parse BACnet tag (application or context).
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
data: Raw bytes to parse.
|
|
20
|
+
offset: Starting offset in data.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Tuple of (tag_dict, bytes_consumed) where tag_dict contains:
|
|
24
|
+
- tag_number: Tag number (0-254)
|
|
25
|
+
- context_specific: True if context tag, False if application tag
|
|
26
|
+
- length: Value/length field
|
|
27
|
+
- value: Decoded value (if applicable)
|
|
28
|
+
|
|
29
|
+
Raises:
|
|
30
|
+
ValueError: If data is too short or invalid tag format.
|
|
31
|
+
|
|
32
|
+
Example:
|
|
33
|
+
>>> data = bytes([0xC4, 0x02, 0x00, 0x00, 0x08])
|
|
34
|
+
>>> tag, consumed = parse_tag(data, 0)
|
|
35
|
+
>>> print(f"Tag {tag['tag_number']}, length {tag['length']}")
|
|
36
|
+
"""
|
|
37
|
+
if offset >= len(data):
|
|
38
|
+
raise ValueError("Offset beyond data length")
|
|
39
|
+
|
|
40
|
+
initial_offset = offset
|
|
41
|
+
tag_byte = data[offset]
|
|
42
|
+
offset += 1
|
|
43
|
+
|
|
44
|
+
# Parse tag number (bits 4-7)
|
|
45
|
+
tag_number = (tag_byte >> 4) & 0x0F
|
|
46
|
+
# Bit 3: class (0=application, 1=context)
|
|
47
|
+
context_specific = bool(tag_byte & 0x08)
|
|
48
|
+
# Bits 0-2: length/value/type
|
|
49
|
+
length_value_type = tag_byte & 0x07
|
|
50
|
+
|
|
51
|
+
# Extended tag number (tag number 15 means next byte has actual tag)
|
|
52
|
+
if tag_number == 15:
|
|
53
|
+
if offset >= len(data):
|
|
54
|
+
raise ValueError("Extended tag number missing")
|
|
55
|
+
tag_number = data[offset]
|
|
56
|
+
offset += 1
|
|
57
|
+
|
|
58
|
+
# Decode length/value/type field
|
|
59
|
+
# For context tags: lvt=6 is opening tag, lvt=7 is closing tag
|
|
60
|
+
# For application tags: lvt=5-7 are special (5 is used normally for length 5, but 6/7 are reserved)
|
|
61
|
+
# Actually in BACnet: for application tags, lvt=7 is NOT used for opening/closing (those are context-only)
|
|
62
|
+
# Instead, lvt in 0-4 gives the length directly, lvt=5 means length 5 for most tags except
|
|
63
|
+
# for constructed data (which uses context tags with opening/closing)
|
|
64
|
+
# Let me simplify: lvt=6/7 only have special meaning for context tags
|
|
65
|
+
if context_specific and length_value_type == 6: # Opening tag (context only)
|
|
66
|
+
length = 0
|
|
67
|
+
is_opening = True
|
|
68
|
+
is_closing = False
|
|
69
|
+
elif context_specific and length_value_type == 7: # Closing tag (context only)
|
|
70
|
+
length = 0
|
|
71
|
+
is_opening = False
|
|
72
|
+
is_closing = True
|
|
73
|
+
elif not context_specific and length_value_type == 5: # Extended length (application only)
|
|
74
|
+
# For application tags, lvt=5 means extended length follows
|
|
75
|
+
if offset >= len(data):
|
|
76
|
+
raise ValueError("Extended length missing")
|
|
77
|
+
length = data[offset]
|
|
78
|
+
offset += 1
|
|
79
|
+
if length == 254: # 16-bit length
|
|
80
|
+
if offset + 1 >= len(data):
|
|
81
|
+
raise ValueError("16-bit length incomplete")
|
|
82
|
+
length = int.from_bytes(data[offset : offset + 2], "big")
|
|
83
|
+
offset += 2
|
|
84
|
+
elif length == 255: # 32-bit length
|
|
85
|
+
if offset + 3 >= len(data):
|
|
86
|
+
raise ValueError("32-bit length incomplete")
|
|
87
|
+
length = int.from_bytes(data[offset : offset + 4], "big")
|
|
88
|
+
offset += 4
|
|
89
|
+
is_opening = False
|
|
90
|
+
is_closing = False
|
|
91
|
+
else:
|
|
92
|
+
# For lengths 0-4, lvt directly encodes the length
|
|
93
|
+
length = length_value_type
|
|
94
|
+
is_opening = False
|
|
95
|
+
is_closing = False
|
|
96
|
+
|
|
97
|
+
tag_dict = {
|
|
98
|
+
"tag_number": tag_number,
|
|
99
|
+
"context_specific": context_specific,
|
|
100
|
+
"length": length,
|
|
101
|
+
"is_opening": is_opening,
|
|
102
|
+
"is_closing": is_closing,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
bytes_consumed = offset - initial_offset
|
|
106
|
+
return tag_dict, bytes_consumed
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def parse_unsigned(data: bytes, offset: int, length: int) -> tuple[int, int]:
|
|
110
|
+
"""Parse BACnet unsigned integer.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
data: Raw bytes to parse.
|
|
114
|
+
offset: Starting offset in data.
|
|
115
|
+
length: Number of bytes (1-4).
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Tuple of (value, bytes_consumed).
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
ValueError: If length is invalid or data too short.
|
|
122
|
+
|
|
123
|
+
Example:
|
|
124
|
+
>>> data = bytes([0x00, 0x01, 0x23, 0x45])
|
|
125
|
+
>>> value, consumed = parse_unsigned(data, 0, 4)
|
|
126
|
+
>>> print(f"Value: {value}") # 74565
|
|
127
|
+
"""
|
|
128
|
+
if length < 1 or length > 4:
|
|
129
|
+
raise ValueError(f"Invalid unsigned integer length: {length}")
|
|
130
|
+
if offset + length > len(data):
|
|
131
|
+
raise ValueError("Data too short for unsigned integer")
|
|
132
|
+
|
|
133
|
+
value = int.from_bytes(data[offset : offset + length], "big")
|
|
134
|
+
return value, length
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def parse_enumerated(data: bytes, offset: int, length: int) -> tuple[int, int]:
|
|
138
|
+
"""Parse BACnet enumerated value (same encoding as unsigned).
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
data: Raw bytes to parse.
|
|
142
|
+
offset: Starting offset in data.
|
|
143
|
+
length: Number of bytes (1-4).
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Tuple of (value, bytes_consumed).
|
|
147
|
+
|
|
148
|
+
Raises:
|
|
149
|
+
ValueError: If length is invalid or data too short.
|
|
150
|
+
|
|
151
|
+
Example:
|
|
152
|
+
>>> data = bytes([0x03]) # Segmentation: both
|
|
153
|
+
>>> value, consumed = parse_enumerated(data, 0, 1)
|
|
154
|
+
"""
|
|
155
|
+
return parse_unsigned(data, offset, length)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def parse_object_identifier(data: bytes, offset: int) -> tuple[dict[str, Any], int]:
|
|
159
|
+
"""Parse BACnet object identifier (32-bit: 10-bit type + 22-bit instance).
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
data: Raw bytes to parse.
|
|
163
|
+
offset: Starting offset in data.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Tuple of (obj_id_dict, bytes_consumed) where obj_id_dict contains:
|
|
167
|
+
- object_type: Object type number
|
|
168
|
+
- object_type_name: Human-readable type name
|
|
169
|
+
- instance: Instance number
|
|
170
|
+
|
|
171
|
+
Raises:
|
|
172
|
+
ValueError: If data too short.
|
|
173
|
+
|
|
174
|
+
Example:
|
|
175
|
+
>>> data = bytes([0x02, 0x00, 0x00, 0x08])
|
|
176
|
+
>>> obj_id, consumed = parse_object_identifier(data, 0)
|
|
177
|
+
>>> print(f"{obj_id['object_type_name']} #{obj_id['instance']}")
|
|
178
|
+
"""
|
|
179
|
+
if offset + 4 > len(data):
|
|
180
|
+
raise ValueError("Data too short for object identifier")
|
|
181
|
+
|
|
182
|
+
obj_id_bytes = int.from_bytes(data[offset : offset + 4], "big")
|
|
183
|
+
object_type = (obj_id_bytes >> 22) & 0x3FF # Top 10 bits
|
|
184
|
+
instance = obj_id_bytes & 0x3FFFFF # Bottom 22 bits
|
|
185
|
+
|
|
186
|
+
# Object type names (ASHRAE 135-2020, Clause 21)
|
|
187
|
+
object_type_names = {
|
|
188
|
+
0: "analog-input",
|
|
189
|
+
1: "analog-output",
|
|
190
|
+
2: "analog-value",
|
|
191
|
+
3: "binary-input",
|
|
192
|
+
4: "binary-output",
|
|
193
|
+
5: "binary-value",
|
|
194
|
+
6: "calendar",
|
|
195
|
+
7: "command",
|
|
196
|
+
8: "device",
|
|
197
|
+
9: "event-enrollment",
|
|
198
|
+
10: "file",
|
|
199
|
+
11: "group",
|
|
200
|
+
12: "loop",
|
|
201
|
+
13: "multi-state-input",
|
|
202
|
+
14: "multi-state-output",
|
|
203
|
+
15: "notification-class",
|
|
204
|
+
16: "program",
|
|
205
|
+
17: "schedule",
|
|
206
|
+
18: "averaging",
|
|
207
|
+
19: "multi-state-value",
|
|
208
|
+
20: "trend-log",
|
|
209
|
+
21: "life-safety-point",
|
|
210
|
+
22: "life-safety-zone",
|
|
211
|
+
23: "accumulator",
|
|
212
|
+
24: "pulse-converter",
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
obj_id_dict = {
|
|
216
|
+
"object_type": object_type,
|
|
217
|
+
"object_type_name": object_type_names.get(object_type, f"type-{object_type}"),
|
|
218
|
+
"instance": instance,
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return obj_id_dict, 4
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def parse_character_string(data: bytes, offset: int, length: int) -> tuple[str, int]:
|
|
225
|
+
"""Parse BACnet character string (encoding byte + UTF-8 string).
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
data: Raw bytes to parse.
|
|
229
|
+
offset: Starting offset in data.
|
|
230
|
+
length: Total length including encoding byte.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
Tuple of (string_value, bytes_consumed).
|
|
234
|
+
|
|
235
|
+
Raises:
|
|
236
|
+
ValueError: If data too short.
|
|
237
|
+
|
|
238
|
+
Example:
|
|
239
|
+
>>> data = bytes([0x00]) + b"Building 1" # 0x00 = UTF-8
|
|
240
|
+
>>> string, consumed = parse_character_string(data, 0, 11)
|
|
241
|
+
"""
|
|
242
|
+
if offset + length > len(data):
|
|
243
|
+
raise ValueError("Data too short for character string")
|
|
244
|
+
|
|
245
|
+
encoding = data[offset] # 0=UTF-8, 1=IBM/MS DBCS, 2=JIS C 6226, 3=UCS-4, 4=UCS-2, 5=ISO 8859-1
|
|
246
|
+
string_data = data[offset + 1 : offset + length]
|
|
247
|
+
|
|
248
|
+
if encoding == 0: # UTF-8
|
|
249
|
+
string_value = string_data.decode("utf-8", errors="replace")
|
|
250
|
+
elif encoding == 5: # ISO 8859-1 (Latin-1)
|
|
251
|
+
string_value = string_data.decode("latin-1", errors="replace")
|
|
252
|
+
else:
|
|
253
|
+
# For unsupported encodings, return hex representation
|
|
254
|
+
string_value = string_data.hex()
|
|
255
|
+
|
|
256
|
+
return string_value, length
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _parse_signed_integer(
|
|
260
|
+
data: bytes, value_offset: int, length: int, tag_size: int
|
|
261
|
+
) -> tuple[int, int]:
|
|
262
|
+
"""Parse signed integer application tag.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
data: Raw bytes.
|
|
266
|
+
value_offset: Offset to value data.
|
|
267
|
+
length: Value length.
|
|
268
|
+
tag_size: Size of tag header.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Tuple of (value, bytes_consumed).
|
|
272
|
+
|
|
273
|
+
Raises:
|
|
274
|
+
ValueError: If data too short.
|
|
275
|
+
"""
|
|
276
|
+
if value_offset + length > len(data):
|
|
277
|
+
raise ValueError("Data too short for signed integer")
|
|
278
|
+
int_value = int.from_bytes(data[value_offset : value_offset + length], "big", signed=True)
|
|
279
|
+
return int_value, tag_size + length
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _parse_real(data: bytes, value_offset: int, length: int, tag_size: int) -> tuple[float, int]:
|
|
283
|
+
"""Parse real (float) application tag.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
data: Raw bytes.
|
|
287
|
+
value_offset: Offset to value data.
|
|
288
|
+
length: Value length.
|
|
289
|
+
tag_size: Size of tag header.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
Tuple of (value, bytes_consumed).
|
|
293
|
+
|
|
294
|
+
Raises:
|
|
295
|
+
ValueError: If invalid length or data too short.
|
|
296
|
+
"""
|
|
297
|
+
import struct
|
|
298
|
+
|
|
299
|
+
if length != 4:
|
|
300
|
+
raise ValueError(f"Invalid real length: {length}")
|
|
301
|
+
if value_offset + 4 > len(data):
|
|
302
|
+
raise ValueError("Data too short for real")
|
|
303
|
+
|
|
304
|
+
float_value = struct.unpack(">f", data[value_offset : value_offset + 4])[0]
|
|
305
|
+
return float_value, tag_size + 4
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _parse_double(data: bytes, value_offset: int, length: int, tag_size: int) -> tuple[float, int]:
|
|
309
|
+
"""Parse double application tag.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
data: Raw bytes.
|
|
313
|
+
value_offset: Offset to value data.
|
|
314
|
+
length: Value length.
|
|
315
|
+
tag_size: Size of tag header.
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
Tuple of (value, bytes_consumed).
|
|
319
|
+
|
|
320
|
+
Raises:
|
|
321
|
+
ValueError: If invalid length or data too short.
|
|
322
|
+
"""
|
|
323
|
+
import struct
|
|
324
|
+
|
|
325
|
+
if length != 8:
|
|
326
|
+
raise ValueError(f"Invalid double length: {length}")
|
|
327
|
+
if value_offset + 8 > len(data):
|
|
328
|
+
raise ValueError("Data too short for double")
|
|
329
|
+
|
|
330
|
+
double_value = struct.unpack(">d", data[value_offset : value_offset + 8])[0]
|
|
331
|
+
return double_value, tag_size + 8
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _parse_object_id_tag(
|
|
335
|
+
data: bytes, value_offset: int, length: int, tag_size: int
|
|
336
|
+
) -> tuple[dict[str, Any], int]:
|
|
337
|
+
"""Parse object identifier application tag.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
data: Raw bytes.
|
|
341
|
+
value_offset: Offset to value data.
|
|
342
|
+
length: Value length.
|
|
343
|
+
tag_size: Size of tag header.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
Tuple of (obj_id_dict, bytes_consumed).
|
|
347
|
+
|
|
348
|
+
Raises:
|
|
349
|
+
ValueError: If invalid length.
|
|
350
|
+
"""
|
|
351
|
+
if length != 4:
|
|
352
|
+
raise ValueError(f"Invalid object identifier length: {length}")
|
|
353
|
+
obj_id, _ = parse_object_identifier(data, value_offset)
|
|
354
|
+
return obj_id, tag_size + 4
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def parse_application_tag(data: bytes, offset: int) -> tuple[Any, int]:
|
|
358
|
+
"""Parse application-tagged data (standard BACnet data types).
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
data: Raw bytes to parse.
|
|
362
|
+
offset: Starting offset in data.
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
Tuple of (decoded_value, bytes_consumed).
|
|
366
|
+
|
|
367
|
+
Raises:
|
|
368
|
+
ValueError: If invalid tag or data too short.
|
|
369
|
+
|
|
370
|
+
Example:
|
|
371
|
+
>>> data = bytes([0x21, 0x05]) # Unsigned int, length 1, value 5
|
|
372
|
+
>>> value, consumed = parse_application_tag(data, 0)
|
|
373
|
+
"""
|
|
374
|
+
tag, tag_size = parse_tag(data, offset)
|
|
375
|
+
|
|
376
|
+
if tag["context_specific"]:
|
|
377
|
+
raise ValueError("Expected application tag, got context tag")
|
|
378
|
+
|
|
379
|
+
if tag["is_opening"] or tag["is_closing"]:
|
|
380
|
+
raise ValueError("Unexpected opening/closing tag")
|
|
381
|
+
|
|
382
|
+
value_offset = offset + tag_size
|
|
383
|
+
length = tag["length"]
|
|
384
|
+
tag_number = tag["tag_number"]
|
|
385
|
+
|
|
386
|
+
# Application tag numbers (ASHRAE 135-2020, Clause 20.2.1)
|
|
387
|
+
if tag_number == 0: # Null
|
|
388
|
+
return None, tag_size
|
|
389
|
+
elif tag_number == 1: # Boolean
|
|
390
|
+
return bool(length), tag_size
|
|
391
|
+
elif tag_number == 2: # Unsigned Integer
|
|
392
|
+
uint_value, _ = parse_unsigned(data, value_offset, length)
|
|
393
|
+
return uint_value, tag_size + length
|
|
394
|
+
elif tag_number == 3: # Signed Integer
|
|
395
|
+
return _parse_signed_integer(data, value_offset, length, tag_size)
|
|
396
|
+
elif tag_number == 4: # Real
|
|
397
|
+
return _parse_real(data, value_offset, length, tag_size)
|
|
398
|
+
elif tag_number == 5: # Double
|
|
399
|
+
return _parse_double(data, value_offset, length, tag_size)
|
|
400
|
+
elif tag_number == 7: # Character String
|
|
401
|
+
str_value, _ = parse_character_string(data, value_offset, length)
|
|
402
|
+
return str_value, tag_size + length
|
|
403
|
+
elif tag_number == 9: # Enumerated
|
|
404
|
+
enum_value, _ = parse_enumerated(data, value_offset, length)
|
|
405
|
+
return enum_value, tag_size + length
|
|
406
|
+
elif tag_number == 12: # Object Identifier
|
|
407
|
+
return _parse_object_id_tag(data, value_offset, length, tag_size)
|
|
408
|
+
else:
|
|
409
|
+
# Unknown/unsupported type - return raw bytes
|
|
410
|
+
if value_offset + length > len(data):
|
|
411
|
+
raise ValueError(f"Data too short for tag type {tag_number}")
|
|
412
|
+
return data[value_offset : value_offset + length], tag_size + length
|