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
|
@@ -284,90 +284,117 @@ def detect_delimiter(
|
|
|
284
284
|
>>> result = detect_delimiter(data)
|
|
285
285
|
>>> print(f"Delimiter: {result.delimiter!r}")
|
|
286
286
|
"""
|
|
287
|
-
|
|
288
|
-
if isinstance(payloads, list | tuple):
|
|
289
|
-
data: bytes = b"".join(payloads)
|
|
290
|
-
else:
|
|
291
|
-
# Type narrowing: payloads is bytes here
|
|
292
|
-
data = cast("bytes", payloads)
|
|
293
|
-
|
|
287
|
+
data = _combine_payloads(payloads)
|
|
294
288
|
if not data:
|
|
295
|
-
return DelimiterResult(
|
|
296
|
-
delimiter=b"",
|
|
297
|
-
delimiter_type="fixed",
|
|
298
|
-
confidence=0.0,
|
|
299
|
-
occurrences=0,
|
|
300
|
-
)
|
|
289
|
+
return DelimiterResult(delimiter=b"", delimiter_type="fixed", confidence=0.0, occurrences=0)
|
|
301
290
|
|
|
302
|
-
|
|
303
|
-
if candidates is None:
|
|
304
|
-
candidates = [
|
|
305
|
-
b"\r\n", # CRLF
|
|
306
|
-
b"\n", # LF
|
|
307
|
-
b"\x00", # Null
|
|
308
|
-
b"\r", # CR
|
|
309
|
-
b"\x0d\x0a", # CRLF (explicit)
|
|
310
|
-
]
|
|
291
|
+
candidates = candidates or _default_delimiter_candidates()
|
|
311
292
|
|
|
312
293
|
best_result = None
|
|
313
294
|
best_score = 0.0
|
|
314
295
|
|
|
315
296
|
for delim in candidates:
|
|
316
|
-
|
|
317
|
-
|
|
297
|
+
result, score = _evaluate_delimiter_candidate(data, delim)
|
|
298
|
+
if score > best_score:
|
|
299
|
+
best_score = score
|
|
300
|
+
best_result = result
|
|
318
301
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
302
|
+
return best_result or DelimiterResult(
|
|
303
|
+
delimiter=b"", delimiter_type="fixed", confidence=0.0, occurrences=0
|
|
304
|
+
)
|
|
322
305
|
|
|
323
|
-
# Calculate score based on frequency and regularity
|
|
324
|
-
positions = []
|
|
325
|
-
pos = 0
|
|
326
|
-
while True:
|
|
327
|
-
pos = data.find(delim, pos)
|
|
328
|
-
if pos == -1:
|
|
329
|
-
break
|
|
330
|
-
positions.append(pos)
|
|
331
|
-
pos += len(delim)
|
|
332
306
|
|
|
333
|
-
|
|
334
|
-
|
|
307
|
+
def _combine_payloads(payloads: Sequence[bytes] | bytes) -> bytes:
|
|
308
|
+
"""Combine payloads into single bytes object."""
|
|
309
|
+
if isinstance(payloads, list | tuple):
|
|
310
|
+
return b"".join(payloads)
|
|
311
|
+
return cast("bytes", payloads)
|
|
335
312
|
|
|
336
|
-
# Calculate interval regularity
|
|
337
|
-
intervals = [positions[i + 1] - positions[i] for i in range(len(positions) - 1)]
|
|
338
|
-
if len(intervals) > 0:
|
|
339
|
-
mean_interval = sum(intervals) / len(intervals)
|
|
340
|
-
if mean_interval > 0:
|
|
341
|
-
variance = sum((x - mean_interval) ** 2 for x in intervals) / len(intervals)
|
|
342
|
-
cv = (variance**0.5) / mean_interval
|
|
343
|
-
regularity = 1.0 / (1.0 + cv)
|
|
344
|
-
else:
|
|
345
|
-
regularity = 0.0
|
|
346
|
-
else:
|
|
347
|
-
regularity = 0.0
|
|
348
313
|
|
|
349
|
-
|
|
350
|
-
|
|
314
|
+
def _default_delimiter_candidates() -> list[bytes]:
|
|
315
|
+
"""Return default delimiter candidates."""
|
|
316
|
+
return [
|
|
317
|
+
b"\r\n", # CRLF
|
|
318
|
+
b"\n", # LF
|
|
319
|
+
b"\x00", # Null
|
|
320
|
+
b"\r", # CR
|
|
321
|
+
b"\x0d\x0a", # CRLF (explicit)
|
|
322
|
+
]
|
|
351
323
|
|
|
352
|
-
if score > best_score:
|
|
353
|
-
best_score = score
|
|
354
|
-
best_result = DelimiterResult(
|
|
355
|
-
delimiter=delim,
|
|
356
|
-
delimiter_type="fixed",
|
|
357
|
-
confidence=min(1.0, regularity * 0.8 + 0.2 * min(1.0, count / 10)),
|
|
358
|
-
occurrences=count,
|
|
359
|
-
positions=positions,
|
|
360
|
-
)
|
|
361
324
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
)
|
|
325
|
+
def _evaluate_delimiter_candidate(
|
|
326
|
+
data: bytes, delim: bytes
|
|
327
|
+
) -> tuple[DelimiterResult | None, float]:
|
|
328
|
+
"""Evaluate a delimiter candidate and return result with score."""
|
|
329
|
+
if len(delim) == 0:
|
|
330
|
+
return None, 0.0
|
|
369
331
|
|
|
370
|
-
|
|
332
|
+
count = data.count(delim)
|
|
333
|
+
if count < 2:
|
|
334
|
+
return None, 0.0
|
|
335
|
+
|
|
336
|
+
positions = _find_delimiter_positions(data, delim)
|
|
337
|
+
if len(positions) < 2:
|
|
338
|
+
return None, 0.0
|
|
339
|
+
|
|
340
|
+
regularity = _calculate_interval_regularity(positions)
|
|
341
|
+
score = count * (0.5 + 0.5 * regularity)
|
|
342
|
+
confidence = min(1.0, regularity * 0.8 + 0.2 * min(1.0, count / 10))
|
|
343
|
+
|
|
344
|
+
result = DelimiterResult(
|
|
345
|
+
delimiter=delim,
|
|
346
|
+
delimiter_type="fixed",
|
|
347
|
+
confidence=confidence,
|
|
348
|
+
occurrences=count,
|
|
349
|
+
positions=positions,
|
|
350
|
+
)
|
|
351
|
+
return result, score
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _find_delimiter_positions(data: bytes, delim: bytes) -> list[int]:
|
|
355
|
+
"""Find all positions of delimiter in data.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
data: Data to search.
|
|
359
|
+
delim: Delimiter bytes to find.
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
List of positions where delimiter occurs.
|
|
363
|
+
|
|
364
|
+
Raises:
|
|
365
|
+
ValueError: If delimiter is empty.
|
|
366
|
+
"""
|
|
367
|
+
if len(delim) == 0:
|
|
368
|
+
raise ValueError("Delimiter cannot be empty")
|
|
369
|
+
|
|
370
|
+
positions = []
|
|
371
|
+
pos = 0
|
|
372
|
+
while True:
|
|
373
|
+
pos = data.find(delim, pos)
|
|
374
|
+
if pos == -1:
|
|
375
|
+
break
|
|
376
|
+
positions.append(pos)
|
|
377
|
+
pos += len(delim)
|
|
378
|
+
return positions
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def _calculate_interval_regularity(positions: list[int]) -> float:
|
|
382
|
+
"""Calculate regularity score from delimiter positions."""
|
|
383
|
+
if len(positions) < 2:
|
|
384
|
+
return 0.0
|
|
385
|
+
|
|
386
|
+
intervals = [positions[i + 1] - positions[i] for i in range(len(positions) - 1)]
|
|
387
|
+
if len(intervals) == 0:
|
|
388
|
+
return 0.0
|
|
389
|
+
|
|
390
|
+
mean_interval = sum(intervals) / len(intervals)
|
|
391
|
+
if mean_interval <= 0:
|
|
392
|
+
return 0.0
|
|
393
|
+
|
|
394
|
+
variance = sum((x - mean_interval) ** 2 for x in intervals) / len(intervals)
|
|
395
|
+
cv = (variance**0.5) / mean_interval
|
|
396
|
+
regularity: float = 1.0 / (1.0 + cv)
|
|
397
|
+
return regularity
|
|
371
398
|
|
|
372
399
|
|
|
373
400
|
def detect_length_prefix(
|
|
@@ -535,8 +562,27 @@ def _find_pattern_in_data(
|
|
|
535
562
|
data: bytes,
|
|
536
563
|
pattern: bytes | str,
|
|
537
564
|
pattern_type: str,
|
|
565
|
+
max_matches: int = 100000,
|
|
538
566
|
) -> list[tuple[int, bytes]]:
|
|
539
|
-
"""Find pattern occurrences in data.
|
|
567
|
+
"""Find pattern occurrences in data.
|
|
568
|
+
|
|
569
|
+
Args:
|
|
570
|
+
data: Data to search.
|
|
571
|
+
pattern: Pattern to find.
|
|
572
|
+
pattern_type: Type of pattern (exact, wildcard, regex).
|
|
573
|
+
max_matches: Maximum number of matches to return (default 100000).
|
|
574
|
+
|
|
575
|
+
Returns:
|
|
576
|
+
List of (offset, matched_bytes) tuples.
|
|
577
|
+
|
|
578
|
+
Raises:
|
|
579
|
+
ValueError: If max_matches exceeded (prevents infinite loops) or pattern is empty.
|
|
580
|
+
"""
|
|
581
|
+
# Validate pattern is not empty (prevents infinite loops)
|
|
582
|
+
if isinstance(pattern, (str, bytes)):
|
|
583
|
+
if len(pattern) == 0:
|
|
584
|
+
raise ValueError("Pattern cannot be empty")
|
|
585
|
+
|
|
540
586
|
matches = []
|
|
541
587
|
|
|
542
588
|
if pattern_type == "exact":
|
|
@@ -550,6 +596,13 @@ def _find_pattern_in_data(
|
|
|
550
596
|
matches.append((pos, pattern))
|
|
551
597
|
pos += 1
|
|
552
598
|
|
|
599
|
+
# Prevent infinite loops from excessive matches
|
|
600
|
+
if len(matches) >= max_matches:
|
|
601
|
+
raise ValueError(
|
|
602
|
+
f"Pattern match limit exceeded ({max_matches} matches). "
|
|
603
|
+
"This may indicate a problematic pattern (e.g., empty or too common)."
|
|
604
|
+
)
|
|
605
|
+
|
|
553
606
|
elif pattern_type == "wildcard":
|
|
554
607
|
# Convert wildcard pattern to regex
|
|
555
608
|
if isinstance(pattern, bytes):
|
|
@@ -558,6 +611,11 @@ def _find_pattern_in_data(
|
|
|
558
611
|
try:
|
|
559
612
|
for match in re.finditer(regex_pattern, data, re.DOTALL):
|
|
560
613
|
matches.append((match.start(), match.group()))
|
|
614
|
+
if len(matches) >= max_matches:
|
|
615
|
+
raise ValueError(
|
|
616
|
+
f"Pattern match limit exceeded ({max_matches} matches). "
|
|
617
|
+
"Wildcard pattern may be too permissive."
|
|
618
|
+
)
|
|
561
619
|
except re.error:
|
|
562
620
|
pass
|
|
563
621
|
|
|
@@ -567,6 +625,11 @@ def _find_pattern_in_data(
|
|
|
567
625
|
try:
|
|
568
626
|
for match in re.finditer(pattern, data, re.DOTALL):
|
|
569
627
|
matches.append((match.start(), match.group()))
|
|
628
|
+
if len(matches) >= max_matches:
|
|
629
|
+
raise ValueError(
|
|
630
|
+
f"Pattern match limit exceeded ({max_matches} matches). "
|
|
631
|
+
"Regex pattern may be too broad."
|
|
632
|
+
)
|
|
570
633
|
except re.error:
|
|
571
634
|
pass
|
|
572
635
|
|
|
@@ -60,6 +60,9 @@ def stream_file(
|
|
|
60
60
|
>>> for chunk in stream_file("large_capture.bin"):
|
|
61
61
|
... process_chunk(chunk)
|
|
62
62
|
"""
|
|
63
|
+
if chunk_size <= 0:
|
|
64
|
+
raise ValueError(f"chunk_size must be positive, got {chunk_size}")
|
|
65
|
+
|
|
63
66
|
path = Path(file_path)
|
|
64
67
|
|
|
65
68
|
with open(path, "rb") as f:
|
|
@@ -87,20 +90,26 @@ def stream_records(
|
|
|
87
90
|
>>> for record in stream_records("data.bin", record_size=128):
|
|
88
91
|
... parse_record(record)
|
|
89
92
|
"""
|
|
93
|
+
if record_size <= 0:
|
|
94
|
+
raise ValueError(f"record_size must be positive, got {record_size}")
|
|
95
|
+
|
|
90
96
|
if isinstance(file_or_buffer, bytes):
|
|
91
97
|
buffer: BinaryIO = io.BytesIO(file_or_buffer)
|
|
92
98
|
should_close = True
|
|
93
99
|
elif isinstance(file_or_buffer, str | Path):
|
|
94
|
-
buffer = open(file_or_buffer, "rb")
|
|
100
|
+
buffer = open(file_or_buffer, "rb")
|
|
95
101
|
should_close = True
|
|
96
102
|
else:
|
|
97
103
|
buffer = file_or_buffer
|
|
98
104
|
should_close = False
|
|
99
105
|
|
|
106
|
+
# Cache record_size to avoid attribute lookup in tight loop
|
|
107
|
+
_record_size = record_size
|
|
100
108
|
try:
|
|
101
109
|
while True:
|
|
102
|
-
record = buffer.read(
|
|
103
|
-
|
|
110
|
+
record = buffer.read(_record_size)
|
|
111
|
+
# Equality check is faster than inequality
|
|
112
|
+
if len(record) != _record_size:
|
|
104
113
|
break
|
|
105
114
|
yield record
|
|
106
115
|
finally:
|
|
@@ -128,11 +137,16 @@ def stream_packets(
|
|
|
128
137
|
Yields:
|
|
129
138
|
StreamPacket objects.
|
|
130
139
|
|
|
140
|
+
Raises:
|
|
141
|
+
ValueError: If packet size exceeds MAX_PACKET_SIZE (100MB).
|
|
142
|
+
|
|
131
143
|
Example:
|
|
132
144
|
>>> header = BinaryParser(">HH") # sync, length
|
|
133
145
|
>>> for pkt in stream_packets("capture.bin", header_parser=header):
|
|
134
146
|
... print(f"Packet: {len(pkt.data)} bytes")
|
|
135
147
|
"""
|
|
148
|
+
MAX_PACKET_SIZE = 100 * 1024 * 1024 # 100MB limit to prevent memory exhaustion
|
|
149
|
+
|
|
136
150
|
if header_parser is None:
|
|
137
151
|
# Default: 2-byte big-endian length prefix
|
|
138
152
|
header_parser = BinaryParser(">H")
|
|
@@ -144,33 +158,50 @@ def stream_packets(
|
|
|
144
158
|
buffer: BinaryIO = io.BytesIO(file_or_buffer)
|
|
145
159
|
should_close = True
|
|
146
160
|
elif isinstance(file_or_buffer, str | Path):
|
|
147
|
-
buffer = open(file_or_buffer, "rb")
|
|
161
|
+
buffer = open(file_or_buffer, "rb")
|
|
148
162
|
should_close = True
|
|
149
163
|
else:
|
|
150
164
|
buffer = file_or_buffer
|
|
151
165
|
should_close = False
|
|
152
166
|
|
|
167
|
+
# Cache sizes and flags to avoid repeated lookups in tight loop
|
|
168
|
+
_header_size = header_size
|
|
169
|
+
_length_field = length_field
|
|
170
|
+
_header_included = header_included
|
|
171
|
+
|
|
153
172
|
try:
|
|
154
173
|
packet_num = 0
|
|
155
174
|
|
|
156
175
|
while True:
|
|
157
176
|
# Read header
|
|
158
|
-
header_bytes = buffer.read(
|
|
159
|
-
if len(header_bytes)
|
|
177
|
+
header_bytes = buffer.read(_header_size)
|
|
178
|
+
if len(header_bytes) != _header_size:
|
|
160
179
|
break
|
|
161
180
|
|
|
162
181
|
header = header_parser.unpack(header_bytes)
|
|
163
|
-
length = header[
|
|
164
|
-
|
|
165
|
-
#
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
182
|
+
length = header[_length_field]
|
|
183
|
+
|
|
184
|
+
# Validate packet size before allocation
|
|
185
|
+
total_size = length if _header_included else length + _header_size
|
|
186
|
+
if total_size > MAX_PACKET_SIZE:
|
|
187
|
+
raise ValueError(
|
|
188
|
+
f"Packet size {total_size} bytes exceeds maximum allowed size "
|
|
189
|
+
f"{MAX_PACKET_SIZE} bytes (packet {packet_num + 1})"
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# Compute payload size with optimized branching
|
|
193
|
+
if _header_included:
|
|
194
|
+
if length < _header_size:
|
|
195
|
+
break
|
|
196
|
+
payload_size = length - _header_size
|
|
197
|
+
else:
|
|
198
|
+
if length < 0:
|
|
199
|
+
break
|
|
200
|
+
payload_size = length
|
|
170
201
|
|
|
171
202
|
# Read payload
|
|
172
203
|
payload = buffer.read(payload_size)
|
|
173
|
-
if len(payload)
|
|
204
|
+
if len(payload) != payload_size:
|
|
174
205
|
break
|
|
175
206
|
|
|
176
207
|
packet_num += 1
|
|
@@ -197,20 +228,29 @@ def stream_delimited(
|
|
|
197
228
|
Args:
|
|
198
229
|
file_or_buffer: Source.
|
|
199
230
|
delimiter: Record delimiter (default newline).
|
|
200
|
-
max_record_size: Maximum record size (default 1MB).
|
|
231
|
+
max_record_size: Maximum record size (default 1MB). Records exceeding
|
|
232
|
+
this size will raise ValueError instead of being silently truncated.
|
|
201
233
|
|
|
202
234
|
Yields:
|
|
203
235
|
Records as bytes (without delimiter).
|
|
204
236
|
|
|
237
|
+
Raises:
|
|
238
|
+
ValueError: If record exceeds max_record_size.
|
|
239
|
+
|
|
205
240
|
Example:
|
|
206
241
|
>>> for line in stream_delimited("log.txt", b"\\n"):
|
|
207
242
|
... process_line(line)
|
|
208
243
|
"""
|
|
244
|
+
if max_record_size <= 0:
|
|
245
|
+
raise ValueError(f"max_record_size must be positive, got {max_record_size}")
|
|
246
|
+
if not delimiter:
|
|
247
|
+
raise ValueError("delimiter cannot be empty")
|
|
248
|
+
|
|
209
249
|
if isinstance(file_or_buffer, bytes):
|
|
210
250
|
buffer: BinaryIO = io.BytesIO(file_or_buffer)
|
|
211
251
|
should_close = True
|
|
212
252
|
elif isinstance(file_or_buffer, str | Path):
|
|
213
|
-
buffer = open(file_or_buffer, "rb")
|
|
253
|
+
buffer = open(file_or_buffer, "rb")
|
|
214
254
|
should_close = True
|
|
215
255
|
else:
|
|
216
256
|
buffer = file_or_buffer
|
|
@@ -223,6 +263,12 @@ def stream_delimited(
|
|
|
223
263
|
chunk = buffer.read(65536)
|
|
224
264
|
if not chunk:
|
|
225
265
|
if partial:
|
|
266
|
+
# Check final partial record
|
|
267
|
+
if len(partial) > max_record_size:
|
|
268
|
+
raise ValueError(
|
|
269
|
+
f"Record size {len(partial)} bytes exceeds maximum allowed size "
|
|
270
|
+
f"{max_record_size} bytes"
|
|
271
|
+
)
|
|
226
272
|
yield partial
|
|
227
273
|
break
|
|
228
274
|
|
|
@@ -231,16 +277,22 @@ def stream_delimited(
|
|
|
231
277
|
|
|
232
278
|
# Yield complete records
|
|
233
279
|
for part in parts[:-1]:
|
|
234
|
-
if len(part)
|
|
235
|
-
|
|
280
|
+
if len(part) > max_record_size:
|
|
281
|
+
raise ValueError(
|
|
282
|
+
f"Record size {len(part)} bytes exceeds maximum allowed size "
|
|
283
|
+
f"{max_record_size} bytes"
|
|
284
|
+
)
|
|
285
|
+
yield part
|
|
236
286
|
|
|
237
287
|
# Keep partial record for next iteration
|
|
238
288
|
partial = parts[-1]
|
|
239
289
|
|
|
240
|
-
# Guard against memory issues
|
|
290
|
+
# Guard against memory issues - raise error instead of truncating
|
|
241
291
|
if len(partial) > max_record_size:
|
|
242
|
-
|
|
243
|
-
|
|
292
|
+
raise ValueError(
|
|
293
|
+
f"Partial record size {len(partial)} bytes exceeds maximum allowed size "
|
|
294
|
+
f"{max_record_size} bytes"
|
|
295
|
+
)
|
|
244
296
|
|
|
245
297
|
finally:
|
|
246
298
|
if should_close:
|
|
@@ -299,6 +351,9 @@ def batch(
|
|
|
299
351
|
>>> for batch_items in batch(stream_packets(f), size=100):
|
|
300
352
|
... process_batch(batch_items)
|
|
301
353
|
"""
|
|
354
|
+
if size <= 0:
|
|
355
|
+
raise ValueError(f"size must be positive, got {size}")
|
|
356
|
+
|
|
302
357
|
current_batch: list[T] = []
|
|
303
358
|
|
|
304
359
|
for item in source:
|
|
@@ -321,12 +376,15 @@ def take(source: Iterator[T], n: int) -> Iterator[T]:
|
|
|
321
376
|
Yields:
|
|
322
377
|
First n items.
|
|
323
378
|
"""
|
|
379
|
+
if n < 0:
|
|
380
|
+
raise ValueError(f"n must be non-negative, got {n}")
|
|
381
|
+
|
|
324
382
|
count = 0
|
|
325
383
|
for item in source:
|
|
326
384
|
if count >= n:
|
|
327
385
|
break
|
|
328
386
|
yield item
|
|
329
|
-
count += 1
|
|
387
|
+
count += 1
|
|
330
388
|
|
|
331
389
|
|
|
332
390
|
def skip(source: Iterator[T], n: int) -> Iterator[T]:
|
|
@@ -339,11 +397,14 @@ def skip(source: Iterator[T], n: int) -> Iterator[T]:
|
|
|
339
397
|
Yields:
|
|
340
398
|
Items after first n.
|
|
341
399
|
"""
|
|
400
|
+
if n < 0:
|
|
401
|
+
raise ValueError(f"n must be non-negative, got {n}")
|
|
402
|
+
|
|
342
403
|
count = 0
|
|
343
404
|
for item in source:
|
|
344
405
|
if count >= n:
|
|
345
406
|
yield item
|
|
346
|
-
count += 1
|
|
407
|
+
count += 1
|
|
347
408
|
|
|
348
409
|
|
|
349
410
|
__all__ = [
|
|
@@ -127,21 +127,42 @@ def find_motifs(
|
|
|
127
127
|
return results
|
|
128
128
|
|
|
129
129
|
|
|
130
|
-
def extract_motif(data: Any, start: int, length: int) -> NDArray[np.generic]:
|
|
130
|
+
def extract_motif(data: Any, start: int = 0, length: int | None = None) -> NDArray[np.generic]:
|
|
131
131
|
"""Extract a motif from data.
|
|
132
132
|
|
|
133
133
|
Args:
|
|
134
|
-
data: Input data array.
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
data: Input data array. If start and length not provided, attempts to detect and extract
|
|
135
|
+
the first repeating motif automatically.
|
|
136
|
+
start: Start index for extraction (default: 0).
|
|
137
|
+
length: Length to extract. If None, attempts to detect motif length automatically.
|
|
137
138
|
|
|
138
139
|
Returns:
|
|
139
140
|
Extracted motif as numpy array.
|
|
141
|
+
|
|
142
|
+
Raises:
|
|
143
|
+
ValueError: If automatic detection fails and no length specified.
|
|
140
144
|
"""
|
|
141
145
|
import numpy as np
|
|
142
146
|
|
|
143
147
|
data_arr = np.asarray(data)
|
|
144
|
-
|
|
148
|
+
|
|
149
|
+
# If length not specified, try to detect motif automatically
|
|
150
|
+
if length is None:
|
|
151
|
+
# Try to find repeating pattern using period detection
|
|
152
|
+
try:
|
|
153
|
+
period_result = detect_period(data_arr)
|
|
154
|
+
if period_result is not None and hasattr(period_result, "period"):
|
|
155
|
+
length = int(period_result.period)
|
|
156
|
+
else:
|
|
157
|
+
# Default to 8 samples if detection fails
|
|
158
|
+
length = min(8, len(data_arr))
|
|
159
|
+
except Exception:
|
|
160
|
+
# Fallback to reasonable default
|
|
161
|
+
length = min(8, len(data_arr))
|
|
162
|
+
|
|
163
|
+
# Ensure we don't exceed array bounds
|
|
164
|
+
end = min(start + length, len(data_arr))
|
|
165
|
+
result = data_arr[start:end]
|
|
145
166
|
return result
|
|
146
167
|
|
|
147
168
|
|