oscura 0.0.1__py3-none-any.whl → 0.1.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 +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.0.dist-info/METADATA +300 -0
- oscura-0.1.0.dist-info/RECORD +463 -0
- oscura-0.1.0.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/WHEEL +0 -0
oscura/loaders/pcap.py
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
"""PCAP/PCAPNG packet capture file loader.
|
|
2
|
+
|
|
3
|
+
This module provides loading of packet capture files using dpkt
|
|
4
|
+
when available, with a basic fallback implementation.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.loaders.pcap import load_pcap
|
|
9
|
+
>>> packets = load_pcap("capture.pcap")
|
|
10
|
+
>>> for packet in packets:
|
|
11
|
+
... print(f"Time: {packet.timestamp}, Size: {len(packet.data)} bytes")
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import struct
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import TYPE_CHECKING, Any
|
|
20
|
+
|
|
21
|
+
from oscura.core.exceptions import FormatError, LoaderError
|
|
22
|
+
from oscura.core.types import ProtocolPacket
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from collections.abc import Iterator
|
|
26
|
+
from os import PathLike
|
|
27
|
+
|
|
28
|
+
# Try to import dpkt for full PCAP support
|
|
29
|
+
try:
|
|
30
|
+
import dpkt # type: ignore[import-not-found]
|
|
31
|
+
|
|
32
|
+
DPKT_AVAILABLE = True
|
|
33
|
+
except ImportError:
|
|
34
|
+
DPKT_AVAILABLE = False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# PCAP file format constants
|
|
38
|
+
PCAP_MAGIC_LE = 0xA1B2C3D4
|
|
39
|
+
PCAP_MAGIC_BE = 0xD4C3B2A1
|
|
40
|
+
PCAP_MAGIC_NS_LE = 0xA1B23C4D # Nanosecond resolution
|
|
41
|
+
PCAP_MAGIC_NS_BE = 0x4D3CB2A1
|
|
42
|
+
PCAPNG_MAGIC = 0x0A0D0D0A
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class PcapPacketList:
|
|
47
|
+
"""Container for PCAP packets with metadata.
|
|
48
|
+
|
|
49
|
+
Allows iteration over packets while preserving capture metadata.
|
|
50
|
+
|
|
51
|
+
Attributes:
|
|
52
|
+
packets: List of ProtocolPacket objects.
|
|
53
|
+
link_type: Link layer type (e.g., Ethernet = 1).
|
|
54
|
+
snaplen: Maximum capture length per packet.
|
|
55
|
+
source_file: Path to the source PCAP file.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
packets: list[ProtocolPacket]
|
|
59
|
+
link_type: int = 1 # Ethernet
|
|
60
|
+
snaplen: int = 65535
|
|
61
|
+
source_file: str = ""
|
|
62
|
+
|
|
63
|
+
def __iter__(self) -> Iterator[ProtocolPacket]:
|
|
64
|
+
"""Iterate over packets."""
|
|
65
|
+
return iter(self.packets)
|
|
66
|
+
|
|
67
|
+
def __len__(self) -> int:
|
|
68
|
+
"""Return number of packets."""
|
|
69
|
+
return len(self.packets)
|
|
70
|
+
|
|
71
|
+
def __getitem__(self, index: int) -> ProtocolPacket:
|
|
72
|
+
"""Get packet by index."""
|
|
73
|
+
return self.packets[index]
|
|
74
|
+
|
|
75
|
+
def filter(
|
|
76
|
+
self,
|
|
77
|
+
protocol: str | None = None,
|
|
78
|
+
min_size: int | None = None,
|
|
79
|
+
max_size: int | None = None,
|
|
80
|
+
) -> list[ProtocolPacket]:
|
|
81
|
+
"""Filter packets by criteria.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
protocol: Filter by protocol annotation.
|
|
85
|
+
min_size: Minimum packet size in bytes.
|
|
86
|
+
max_size: Maximum packet size in bytes.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Filtered list of packets.
|
|
90
|
+
"""
|
|
91
|
+
result = self.packets
|
|
92
|
+
|
|
93
|
+
if protocol is not None:
|
|
94
|
+
result = [
|
|
95
|
+
p
|
|
96
|
+
for p in result
|
|
97
|
+
if p.annotations.get("layer3_protocol") == protocol
|
|
98
|
+
or p.annotations.get("layer4_protocol") == protocol
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
if min_size is not None:
|
|
102
|
+
result = [p for p in result if len(p.data) >= min_size]
|
|
103
|
+
|
|
104
|
+
if max_size is not None:
|
|
105
|
+
result = [p for p in result if len(p.data) <= max_size]
|
|
106
|
+
|
|
107
|
+
return result
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def load_pcap(
|
|
111
|
+
path: str | PathLike[str],
|
|
112
|
+
*,
|
|
113
|
+
protocol_filter: str | None = None,
|
|
114
|
+
max_packets: int | None = None,
|
|
115
|
+
) -> PcapPacketList:
|
|
116
|
+
"""Load a PCAP or PCAPNG packet capture file.
|
|
117
|
+
|
|
118
|
+
Extracts packets with timestamps and optional protocol annotations.
|
|
119
|
+
Uses dpkt library when available for full protocol dissection.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
path: Path to the PCAP/PCAPNG file.
|
|
123
|
+
protocol_filter: Optional protocol filter (e.g., "TCP", "UDP").
|
|
124
|
+
max_packets: Maximum number of packets to load.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
PcapPacketList containing packets and capture metadata.
|
|
128
|
+
|
|
129
|
+
Raises:
|
|
130
|
+
LoaderError: If the file cannot be loaded.
|
|
131
|
+
|
|
132
|
+
Example:
|
|
133
|
+
>>> packets = load_pcap("network.pcap")
|
|
134
|
+
>>> print(f"Captured {len(packets)} packets")
|
|
135
|
+
>>> for pkt in packets[:5]:
|
|
136
|
+
... print(f" {pkt.timestamp:.6f}s: {len(pkt.data)} bytes")
|
|
137
|
+
|
|
138
|
+
>>> # Filter by protocol
|
|
139
|
+
>>> tcp_packets = packets.filter(protocol="TCP")
|
|
140
|
+
"""
|
|
141
|
+
path = Path(path)
|
|
142
|
+
|
|
143
|
+
if not path.exists():
|
|
144
|
+
raise LoaderError(
|
|
145
|
+
"File not found",
|
|
146
|
+
file_path=str(path),
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
if DPKT_AVAILABLE:
|
|
150
|
+
return _load_with_dpkt(
|
|
151
|
+
path,
|
|
152
|
+
protocol_filter=protocol_filter,
|
|
153
|
+
max_packets=max_packets,
|
|
154
|
+
)
|
|
155
|
+
else:
|
|
156
|
+
return _load_basic(
|
|
157
|
+
path,
|
|
158
|
+
protocol_filter=protocol_filter,
|
|
159
|
+
max_packets=max_packets,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _load_with_dpkt(
|
|
164
|
+
path: Path,
|
|
165
|
+
*,
|
|
166
|
+
protocol_filter: str | None = None,
|
|
167
|
+
max_packets: int | None = None,
|
|
168
|
+
) -> PcapPacketList:
|
|
169
|
+
"""Load PCAP using dpkt library.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
path: Path to the PCAP file.
|
|
173
|
+
protocol_filter: Optional protocol filter.
|
|
174
|
+
max_packets: Maximum packets to load.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
PcapPacketList with parsed packets.
|
|
178
|
+
|
|
179
|
+
Raises:
|
|
180
|
+
LoaderError: If file cannot be read or dpkt version is incompatible.
|
|
181
|
+
"""
|
|
182
|
+
try:
|
|
183
|
+
with open(path, "rb") as f:
|
|
184
|
+
# Detect file format
|
|
185
|
+
magic = f.read(4)
|
|
186
|
+
f.seek(0)
|
|
187
|
+
|
|
188
|
+
magic_int = struct.unpack("<I", magic)[0]
|
|
189
|
+
|
|
190
|
+
if magic_int == PCAPNG_MAGIC:
|
|
191
|
+
# PCAPNG format
|
|
192
|
+
try:
|
|
193
|
+
pcap_reader = dpkt.pcapng.Reader(f)
|
|
194
|
+
except AttributeError:
|
|
195
|
+
raise LoaderError( # noqa: B904
|
|
196
|
+
"PCAPNG support requires newer dpkt version",
|
|
197
|
+
file_path=str(path),
|
|
198
|
+
fix_hint="Install dpkt >= 1.9: pip install dpkt>=1.9",
|
|
199
|
+
)
|
|
200
|
+
else:
|
|
201
|
+
# Standard PCAP format
|
|
202
|
+
pcap_reader = dpkt.pcap.Reader(f)
|
|
203
|
+
|
|
204
|
+
packets: list[ProtocolPacket] = []
|
|
205
|
+
link_type = getattr(pcap_reader, "datalink", lambda: 1)()
|
|
206
|
+
|
|
207
|
+
for timestamp, raw_data in pcap_reader:
|
|
208
|
+
if max_packets is not None and len(packets) >= max_packets:
|
|
209
|
+
break
|
|
210
|
+
|
|
211
|
+
# Parse Ethernet frame
|
|
212
|
+
annotations: dict[str, Any] = {}
|
|
213
|
+
protocol = "RAW"
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
if link_type == 1: # Ethernet
|
|
217
|
+
eth = dpkt.ethernet.Ethernet(raw_data)
|
|
218
|
+
annotations["src_mac"] = _format_mac(eth.src)
|
|
219
|
+
annotations["dst_mac"] = _format_mac(eth.dst)
|
|
220
|
+
|
|
221
|
+
# Parse IP layer
|
|
222
|
+
if isinstance(eth.data, dpkt.ip.IP):
|
|
223
|
+
ip = eth.data
|
|
224
|
+
protocol = "IP"
|
|
225
|
+
annotations["src_ip"] = _format_ip(ip.src)
|
|
226
|
+
annotations["dst_ip"] = _format_ip(ip.dst)
|
|
227
|
+
annotations["layer3_protocol"] = "IP"
|
|
228
|
+
|
|
229
|
+
# Parse transport layer
|
|
230
|
+
if isinstance(ip.data, dpkt.tcp.TCP):
|
|
231
|
+
tcp = ip.data
|
|
232
|
+
protocol = "TCP"
|
|
233
|
+
annotations["src_port"] = tcp.sport
|
|
234
|
+
annotations["dst_port"] = tcp.dport
|
|
235
|
+
annotations["layer4_protocol"] = "TCP"
|
|
236
|
+
annotations["tcp_flags"] = tcp.flags
|
|
237
|
+
|
|
238
|
+
elif isinstance(ip.data, dpkt.udp.UDP):
|
|
239
|
+
udp = ip.data
|
|
240
|
+
protocol = "UDP"
|
|
241
|
+
annotations["src_port"] = udp.sport
|
|
242
|
+
annotations["dst_port"] = udp.dport
|
|
243
|
+
annotations["layer4_protocol"] = "UDP"
|
|
244
|
+
|
|
245
|
+
elif isinstance(ip.data, dpkt.icmp.ICMP):
|
|
246
|
+
protocol = "ICMP"
|
|
247
|
+
annotations["layer4_protocol"] = "ICMP"
|
|
248
|
+
|
|
249
|
+
elif isinstance(eth.data, dpkt.ip6.IP6):
|
|
250
|
+
protocol = "IPv6"
|
|
251
|
+
annotations["layer3_protocol"] = "IPv6"
|
|
252
|
+
|
|
253
|
+
elif isinstance(eth.data, dpkt.arp.ARP):
|
|
254
|
+
protocol = "ARP"
|
|
255
|
+
annotations["layer3_protocol"] = "ARP"
|
|
256
|
+
|
|
257
|
+
except Exception:
|
|
258
|
+
# If parsing fails, store raw data
|
|
259
|
+
pass
|
|
260
|
+
|
|
261
|
+
# Apply protocol filter
|
|
262
|
+
if protocol_filter is not None and (
|
|
263
|
+
annotations.get("layer3_protocol") != protocol_filter
|
|
264
|
+
and annotations.get("layer4_protocol") != protocol_filter
|
|
265
|
+
and protocol != protocol_filter
|
|
266
|
+
):
|
|
267
|
+
continue
|
|
268
|
+
|
|
269
|
+
packet = ProtocolPacket(
|
|
270
|
+
timestamp=float(timestamp),
|
|
271
|
+
protocol=protocol,
|
|
272
|
+
data=bytes(raw_data),
|
|
273
|
+
annotations=annotations,
|
|
274
|
+
)
|
|
275
|
+
packets.append(packet)
|
|
276
|
+
|
|
277
|
+
return PcapPacketList(
|
|
278
|
+
packets=packets,
|
|
279
|
+
link_type=link_type,
|
|
280
|
+
source_file=str(path),
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
except Exception as e:
|
|
284
|
+
if isinstance(e, LoaderError | FormatError):
|
|
285
|
+
raise
|
|
286
|
+
raise LoaderError(
|
|
287
|
+
"Failed to load PCAP file",
|
|
288
|
+
file_path=str(path),
|
|
289
|
+
details=str(e),
|
|
290
|
+
fix_hint="Ensure the file is a valid PCAP/PCAPNG format.",
|
|
291
|
+
) from e
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _load_basic(
|
|
295
|
+
path: Path,
|
|
296
|
+
*,
|
|
297
|
+
protocol_filter: str | None = None,
|
|
298
|
+
max_packets: int | None = None,
|
|
299
|
+
) -> PcapPacketList:
|
|
300
|
+
"""Basic PCAP loader without dpkt.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
path: Path to the PCAP file.
|
|
304
|
+
protocol_filter: Optional protocol filter (not supported in basic mode).
|
|
305
|
+
max_packets: Maximum packets to load.
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
PcapPacketList with raw packet data.
|
|
309
|
+
|
|
310
|
+
Raises:
|
|
311
|
+
FormatError: If file is not a valid PCAP.
|
|
312
|
+
LoaderError: If file cannot be read.
|
|
313
|
+
"""
|
|
314
|
+
try:
|
|
315
|
+
with open(path, "rb") as f:
|
|
316
|
+
# Read global header (24 bytes)
|
|
317
|
+
header = f.read(24)
|
|
318
|
+
if len(header) < 24:
|
|
319
|
+
raise FormatError(
|
|
320
|
+
"File too small to be a valid PCAP",
|
|
321
|
+
file_path=str(path),
|
|
322
|
+
expected="At least 24 bytes",
|
|
323
|
+
got=f"{len(header)} bytes",
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
# Parse magic number
|
|
327
|
+
magic = struct.unpack("<I", header[:4])[0]
|
|
328
|
+
|
|
329
|
+
if magic in (PCAP_MAGIC_LE, PCAP_MAGIC_NS_LE):
|
|
330
|
+
byte_order = "<"
|
|
331
|
+
nanosecond = magic == PCAP_MAGIC_NS_LE
|
|
332
|
+
elif magic in (PCAP_MAGIC_BE, PCAP_MAGIC_NS_BE):
|
|
333
|
+
byte_order = ">"
|
|
334
|
+
nanosecond = magic == PCAP_MAGIC_NS_BE
|
|
335
|
+
elif magic == PCAPNG_MAGIC:
|
|
336
|
+
raise LoaderError(
|
|
337
|
+
"PCAPNG format requires dpkt library",
|
|
338
|
+
file_path=str(path),
|
|
339
|
+
fix_hint="Install dpkt: pip install dpkt",
|
|
340
|
+
)
|
|
341
|
+
else:
|
|
342
|
+
raise FormatError(
|
|
343
|
+
"Invalid PCAP magic number",
|
|
344
|
+
file_path=str(path),
|
|
345
|
+
expected="PCAP magic (0xa1b2c3d4)",
|
|
346
|
+
got=f"0x{magic:08x}",
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
# Parse rest of header (version_major, version_minor, thiszone, sigfigs, snaplen, network)
|
|
350
|
+
_, _, _, _, snaplen, link_type = struct.unpack(f"{byte_order}HHiIII", header[4:])
|
|
351
|
+
|
|
352
|
+
packets: list[ProtocolPacket] = []
|
|
353
|
+
|
|
354
|
+
# Read packets
|
|
355
|
+
while True:
|
|
356
|
+
if max_packets is not None and len(packets) >= max_packets:
|
|
357
|
+
break
|
|
358
|
+
|
|
359
|
+
# Read packet header (16 bytes)
|
|
360
|
+
pkt_header = f.read(16)
|
|
361
|
+
if len(pkt_header) < 16:
|
|
362
|
+
break
|
|
363
|
+
|
|
364
|
+
ts_sec, ts_usec, incl_len, orig_len = struct.unpack(f"{byte_order}IIII", pkt_header)
|
|
365
|
+
|
|
366
|
+
# Calculate timestamp
|
|
367
|
+
if nanosecond:
|
|
368
|
+
timestamp = ts_sec + ts_usec / 1e9
|
|
369
|
+
else:
|
|
370
|
+
timestamp = ts_sec + ts_usec / 1e6
|
|
371
|
+
|
|
372
|
+
# Read packet data
|
|
373
|
+
pkt_data = f.read(incl_len)
|
|
374
|
+
if len(pkt_data) < incl_len:
|
|
375
|
+
break
|
|
376
|
+
|
|
377
|
+
packet = ProtocolPacket(
|
|
378
|
+
timestamp=timestamp,
|
|
379
|
+
protocol="RAW",
|
|
380
|
+
data=bytes(pkt_data),
|
|
381
|
+
annotations={"original_length": orig_len},
|
|
382
|
+
)
|
|
383
|
+
packets.append(packet)
|
|
384
|
+
|
|
385
|
+
return PcapPacketList(
|
|
386
|
+
packets=packets,
|
|
387
|
+
link_type=link_type,
|
|
388
|
+
snaplen=snaplen,
|
|
389
|
+
source_file=str(path),
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
except struct.error as e:
|
|
393
|
+
raise FormatError(
|
|
394
|
+
"Corrupted PCAP file",
|
|
395
|
+
file_path=str(path),
|
|
396
|
+
) from e
|
|
397
|
+
except Exception as e:
|
|
398
|
+
if isinstance(e, LoaderError | FormatError):
|
|
399
|
+
raise
|
|
400
|
+
raise LoaderError(
|
|
401
|
+
"Failed to load PCAP file",
|
|
402
|
+
file_path=str(path),
|
|
403
|
+
details=str(e),
|
|
404
|
+
fix_hint="Install dpkt for full PCAP support: pip install dpkt",
|
|
405
|
+
) from e
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def _format_mac(mac_bytes: bytes) -> str:
|
|
409
|
+
"""Format MAC address bytes to string.
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
mac_bytes: 6-byte MAC address.
|
|
413
|
+
|
|
414
|
+
Returns:
|
|
415
|
+
MAC address string (e.g., "00:11:22:33:44:55").
|
|
416
|
+
"""
|
|
417
|
+
return ":".join(f"{b:02x}" for b in mac_bytes)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def _format_ip(ip_bytes: bytes) -> str:
|
|
421
|
+
"""Format IPv4 address bytes to string.
|
|
422
|
+
|
|
423
|
+
Args:
|
|
424
|
+
ip_bytes: 4-byte IPv4 address.
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
IPv4 address string (e.g., "192.168.1.1").
|
|
428
|
+
"""
|
|
429
|
+
return ".".join(str(b) for b in ip_bytes)
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
__all__ = ["PcapPacketList", "load_pcap"]
|