From 13041bef6f5bb946aff87eecd1b7edb40c80e290 Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Fri, 16 Oct 2026 15:54:03 -0400 Subject: [PATCH 1/3] docs(foundation): third whole-repo pass over foundation, toolkit, dumpkit, interface and utilities (#1103) Cross-references: - point TraceFlow.finish/index/dump, register_tcp/udp/sctp, Reassembly.expire and the class name properties at their documented targets; qualify Completion, Deferred, Index, Extractor and LinkType so they resolve from any module - document the per-protocol __timeout__ and TCP __window__ hooks, and the public dpkt timestamp and buffer helpers the adapters cite Rendered output: - move each toolkit adapter's reassembly condition into the body and keep Returns to one paragraph, which drops the empty paragraph Returns opened with - reword first lines napoleon split as a type, drop a python: prefix and a raw type prefix, hide the PyShark table reprs, drop a duplicated BufferID entry Counts: remove counted numbers from living prose; spec quantities, constants and measurements stay Accuracy and wording: Raises and Warns for TraceFlowBase.register_dumper, the __output__ value shape, the MH registrar parameters, trace()'s protocol, the IPv6 overlap note, the traceflow packet shape, make_dumper's return, the PCAP-NG Context summary, timed "this tree" wording, and typos and grammar Refs #1103 --- docs/source/pcapkit/dumpkit/null.rst | 2 +- docs/source/pcapkit/dumpkit/pcap.rst | 2 +- .../pcapkit/foundation/engines/3rdparty.rst | 21 ++-- .../pcapkit/foundation/engines/engine.rst | 4 +- docs/source/pcapkit/foundation/extraction.rst | 10 +- .../pcapkit/foundation/reassembly/ip/ip.rst | 4 +- .../pcapkit/foundation/reassembly/ip/ipv4.rst | 9 +- .../pcapkit/foundation/reassembly/ip/ipv6.rst | 25 ++--- .../foundation/reassembly/reassembly.rst | 2 +- .../pcapkit/foundation/reassembly/tcp.rst | 15 ++- .../pcapkit/foundation/traceflow/tcp.rst | 22 +++-- .../foundation/traceflow/traceflow.rst | 2 +- docs/source/pcapkit/interface/core.rst | 2 +- docs/source/pcapkit/toolkit/3rdparty.rst | 24 +++++ docs/source/pcapkit/utilities/config.rst | 4 +- docs/source/pcapkit/utilities/exceptions.rst | 2 +- docs/source/pcapkit/utilities/logging.rst | 3 +- docs/source/pcapkit/utilities/warnings.rst | 4 +- pcapkit/__init__.py | 4 +- pcapkit/dumpkit/__init__.py | 2 +- pcapkit/dumpkit/common.py | 18 ++-- pcapkit/dumpkit/null.py | 2 +- pcapkit/dumpkit/pcap.py | 2 +- pcapkit/foundation/engines/_pcap_backend.py | 8 +- pcapkit/foundation/engines/dpkt.py | 2 +- pcapkit/foundation/engines/engine.py | 9 +- pcapkit/foundation/engines/pcap.py | 2 +- pcapkit/foundation/engines/pcap_ct.py | 18 ++-- pcapkit/foundation/engines/pcapng.py | 6 +- pcapkit/foundation/engines/pypcap.py | 4 +- pcapkit/foundation/engines/pypcapfile.py | 8 +- pcapkit/foundation/engines/pyshark.py | 8 +- pcapkit/foundation/engines/scapy.py | 2 +- pcapkit/foundation/extraction.py | 49 +++++----- pcapkit/foundation/reassembly/data/data.py | 19 ++-- pcapkit/foundation/reassembly/data/ip.py | 16 ++-- pcapkit/foundation/reassembly/data/tcp.py | 26 ++--- pcapkit/foundation/reassembly/ip.py | 6 +- pcapkit/foundation/reassembly/ipv4.py | 4 +- pcapkit/foundation/reassembly/ipv6.py | 4 +- pcapkit/foundation/reassembly/reassembly.py | 9 +- pcapkit/foundation/reassembly/tcp.py | 6 +- pcapkit/foundation/registry/foundation.py | 24 ++--- pcapkit/foundation/registry/protocols.py | 95 ++++++++++--------- pcapkit/foundation/traceflow/data/data.py | 10 +- pcapkit/foundation/traceflow/data/tcp.py | 18 ++-- pcapkit/foundation/traceflow/tcp.py | 18 ++-- pcapkit/foundation/traceflow/traceflow.py | 20 ++-- pcapkit/interface/core.py | 5 +- pcapkit/interface/misc.py | 23 +++-- pcapkit/toolkit/dpkt.py | 79 ++++++++------- pcapkit/toolkit/pcap.py | 58 +++++------ pcapkit/toolkit/pcap_ct.py | 10 +- pcapkit/toolkit/pcapng.py | 58 +++++------ pcapkit/toolkit/pypcap.py | 10 +- pcapkit/toolkit/pypcapfile.py | 56 +++++------ pcapkit/toolkit/pyshark.py | 60 ++++++------ pcapkit/toolkit/scapy.py | 79 ++++++++------- pcapkit/utilities/chardet.py | 4 +- pcapkit/utilities/compat.py | 2 +- pcapkit/utilities/config.py | 22 +++-- pcapkit/utilities/exceptions.py | 21 ++-- pcapkit/utilities/warnings.py | 6 +- 63 files changed, 551 insertions(+), 518 deletions(-) diff --git a/docs/source/pcapkit/dumpkit/null.rst b/docs/source/pcapkit/dumpkit/null.rst index 44261827f2..4cd9dee756 100644 --- a/docs/source/pcapkit/dumpkit/null.rst +++ b/docs/source/pcapkit/dumpkit/null.rst @@ -4,7 +4,7 @@ Null Dumper .. module:: pcapkit.dumpkit.null :mod:`pcapkit.dumpkit.null` is the dumper for the **NotImplemented** format, -alike those in :mod:`dictdumper`. +like those in :mod:`dictdumper`. .. note:: diff --git a/docs/source/pcapkit/dumpkit/pcap.rst b/docs/source/pcapkit/dumpkit/pcap.rst index 382c285dae..c3d8d7db30 100644 --- a/docs/source/pcapkit/dumpkit/pcap.rst +++ b/docs/source/pcapkit/dumpkit/pcap.rst @@ -3,7 +3,7 @@ PCAP Dumper .. module:: pcapkit.dumpkit.pcap -:mod:`pcapkit.dumpkit.pcap` is the dumper for the PCAP format, alike those in +:mod:`pcapkit.dumpkit.pcap` is the dumper for the PCAP format, like those in :mod:`dictdumper`. .. autoclass:: pcapkit.dumpkit.pcap.PCAPIO diff --git a/docs/source/pcapkit/foundation/engines/3rdparty.rst b/docs/source/pcapkit/foundation/engines/3rdparty.rst index 96d2aa78c3..12592a3134 100644 --- a/docs/source/pcapkit/foundation/engines/3rdparty.rst +++ b/docs/source/pcapkit/foundation/engines/3rdparty.rst @@ -20,8 +20,11 @@ support, as is used by :class:`pcapkit.foundation.extraction.Extractor`. Importing a narrower submodule leaves them empty, and :class:`~scapy.utils.PcapReader` then returns every frame as one opaque :class:`~scapy.packet.Raw` layer without raising, so the engine would dissect - nothing and say so only on :data:`sys.stderr`. See :meth:`Scapy.__init__` for - why naming the layer modules individually is not a cheaper route. + nothing and say so only on :data:`sys.stderr`. Naming the layer modules + individually is not a cheaper route: it repairs the link layer but leaves the + payload table short, so :file:`dhcp.pcapng` still comes back as + ``Ethernet / IP / UDP / Raw``, and such a list would go stale silently as + `Scapy`_ gains layer types. One side effect: :mod:`scapy.all` loads :mod:`scapy.layers.dcerpc`, which reaches `Scapy`_'s TLS layer and there triggers a @@ -107,7 +110,7 @@ support, as is used by :class:`pcapkit.foundation.extraction.Extractor`. .. important:: - `PyShark`_ has **two** requirements beyond installing it, and neither is + `PyShark`_ has requirements beyond installing it, and none is visible to an import -- the package imports cleanly and then fails when used, which is why :meth:`~pcapkit.foundation.engines.pyshark.PyShark.unsupported_reason` checks @@ -264,7 +267,7 @@ support, as is used by :class:`pcapkit.foundation.extraction.Extractor`. pip install pypcapkit[PCAP_CT] - The two engines together cover every interpreter the project supports, and + Together these engines cover every interpreter the project supports, and ``PCAP_CT`` covers all of it on its own: ========================================================================= ================== @@ -342,10 +345,10 @@ support, as is used by :class:`pcapkit.foundation.extraction.Extractor`. ================ =========================================================== System libpcap Result ================ =========================================================== - 1.11.0 correct -- the same four frames and the same sub-second + 1.11.0 correct -- the same frames and the same sub-second timestamps as :class:`~pcapkit.foundation.engines.pcapng.PCAPNG` - 1.5.3 four frames, but nonsense sub-second timestamps + 1.5.3 the same frames, but nonsense sub-second timestamps (``1102274184.0000002`` and the like), silently and with no error ================ =========================================================== @@ -438,15 +441,15 @@ Backend Detection .. module:: pcapkit.foundation.engines._pcap_backend -Two unrelated PyPI distributions install a top-level module named :mod:`pcap` -- +Unrelated PyPI distributions install a top-level module named :mod:`pcap` -- `PyPCAP`_, a Cython binding shipped as a single extension module, and `pcap-ct`_, a :mod:`ctypes` reimplementation shipped as a package. They therefore collide, and ``import pcap`` resolves to whichever the import system finds first. :class:`~pcapkit.foundation.engines.pypcap.PyPCAP` and :class:`~pcapkit.foundation.engines.pcap_ct.PCAP_CT` each have to know which one they actually got rather than assume, and this module is the one place that -answers it -- deliberately shared, since the two engines must agree and two -copies of the detection would be two chances to disagree. It is *only* detection, +answers it -- deliberately shared, since the engines must agree and a copy of +the detection in each would be a chance to disagree. It is *only* detection, and it imports nothing from :mod:`pcapkit`, so it cannot introduce an import cycle. diff --git a/docs/source/pcapkit/foundation/engines/engine.rst b/docs/source/pcapkit/foundation/engines/engine.rst index c5186ff648..7660d8614b 100644 --- a/docs/source/pcapkit/foundation/engines/engine.rst +++ b/docs/source/pcapkit/foundation/engines/engine.rst @@ -42,7 +42,7 @@ every extraction engine. :attr:`__engine_module__` class attribute. .. property:: registry - :type: python:dict[str, ModuleDescriptor[EngineBase] | typing.Type[EngineBase]] + :type: dict[str, ModuleDescriptor[EngineBase] | typing.Type[EngineBase]] Mapping of engine names to engine classes. @@ -80,3 +80,5 @@ Type Variables .. data:: _T :type: typing.Any + + Frame type the engine reads. diff --git a/docs/source/pcapkit/foundation/extraction.rst b/docs/source/pcapkit/foundation/extraction.rst index 06ec711411..b589790cc5 100644 --- a/docs/source/pcapkit/foundation/extraction.rst +++ b/docs/source/pcapkit/foundation/extraction.rst @@ -7,7 +7,7 @@ File Extractor :class:`~pcapkit.foundation.extraction.Extractor` only, which synthesises file I/O and protocol analysis, coordinates information exchange in all network layers, -extracts parameters from a PCAP file. +and extracts parameters from a PCAP file. .. seealso:: @@ -15,9 +15,9 @@ extracts parameters from a PCAP file. :class:`pcapkit.foundation.engines.pypcap.PyPCAP` (``engine='pypcap'``), :class:`pcapkit.foundation.engines.pcap_ct.PCAP_CT` (``engine='pcap_ct'``) and :class:`pcapkit.foundation.engines.pypcapfile.PyPCAPFile` - (``engine='pypcapfile'``). All three support less than the ``default`` - engine does; :doc:`engines/index` tabulates the gaps, and :doc:`../../index` - documents their installation prerequisites. + (``engine='pypcapfile'``). Each supports less than the ``default`` + engine does; :doc:`engines/index` tabulates the gaps, and the + :doc:`main page <../../index>` documents their installation prerequisites. .. autoclass:: pcapkit.foundation.extraction.Extractor :no-members: @@ -107,6 +107,8 @@ Type Variables .. data:: _P :type: typing.Any + Frame type the extractor yields, as read by its engine. + .. |pypcap| replace:: ``pypcap`` .. _pypcap: https://github.com/pynetwork/pypcap .. |pcap-ct| replace:: ``pcap-ct`` diff --git a/docs/source/pcapkit/foundation/reassembly/ip/ip.rst b/docs/source/pcapkit/foundation/reassembly/ip/ip.rst index a377d0d504..566158bef8 100644 --- a/docs/source/pcapkit/foundation/reassembly/ip/ip.rst +++ b/docs/source/pcapkit/foundation/reassembly/ip/ip.rst @@ -29,7 +29,7 @@ Data Models :module: pcapkit.foundation.reassembly.data.ip :type: typing.Tuple[~pcapkit.foundation.reassembly.data.ip._AT, ~pcapkit.foundation.reassembly.data.ip._AT, int, pcapkit.const.reg.transtype.TransType] - Data module for buffer ID. + Data model for buffer ID. .. autoclass:: pcapkit.foundation.reassembly.data.ip.Buffer :members: @@ -49,3 +49,5 @@ Type Variables .. data:: _AT :module: pcapkit.foundation.reassembly.data.ip :type: ipaddress.IPv4Address | ipaddress.IPv6Address + + IP address type. diff --git a/docs/source/pcapkit/foundation/reassembly/ip/ipv4.rst b/docs/source/pcapkit/foundation/reassembly/ip/ipv4.rst index 9b9eb9d127..0d9245b5f7 100644 --- a/docs/source/pcapkit/foundation/reassembly/ip/ipv4.rst +++ b/docs/source/pcapkit/foundation/reassembly/ip/ipv4.rst @@ -16,6 +16,7 @@ origin. Please refer to :doc:`ip` for more information. .. autoattribute:: __protocol_type__ .. autoattribute:: __callback_fn__ :no-value: + .. autoattribute:: __timeout__ Terminology ----------- @@ -25,7 +26,7 @@ Terminology reasm.ipv4.packet Data structure for **IPv4 datagram reassembly** (:meth:`IPv4.reassembly `) - is as following, with ``ipv4`` the protocol instance + is as follows, with ``ipv4`` the protocol instance (``frame['IPv4']``) and ``ipv4_info`` its :attr:`~pcapkit.protocols.protocol.Protocol.info` -- the header fields come off the latter, the raw octets off the former: @@ -53,7 +54,7 @@ Terminology reasm.ipv4.datagram Data structure for **reassembled IPv4 datagram** (element from :attr:`IPv4.datagram ` - *tuple*) is as following: + *tuple*) is as follows: .. code-block:: text @@ -118,7 +119,7 @@ Terminology reasm.ipv4.buffer Data structure for internal buffering when performing reassembly algorithms (:attr:`IPv4._buffer `) - is as following: + is as follows: .. code-block:: text @@ -175,4 +176,4 @@ Terminology :attr:`~pcapkit.foundation.reassembly.data.data.Completion.PARTIAL`, which is what tells "these fragments are gone" apart from "these fragments had not arrived yet". See - :meth:`~pcapkit.foundation.reassembly.reassembly.ReassemblyBase.expire`. + :meth:`~pcapkit.foundation.reassembly.reassembly.Reassembly.expire`. diff --git a/docs/source/pcapkit/foundation/reassembly/ip/ipv6.rst b/docs/source/pcapkit/foundation/reassembly/ip/ipv6.rst index 3ae310ac6e..d89cbf281f 100644 --- a/docs/source/pcapkit/foundation/reassembly/ip/ipv6.rst +++ b/docs/source/pcapkit/foundation/reassembly/ip/ipv6.rst @@ -16,6 +16,7 @@ origin. Please refer to :doc:`ip` for more information. .. autoattribute:: __protocol_type__ .. autoattribute:: __callback_fn__ :no-value: + .. autoattribute:: __timeout__ Terminology ----------- @@ -25,7 +26,7 @@ Terminology reasm.ipv6.packet Data structure for **IPv6 datagram reassembly** (:meth:`IPv6.reassembly `) - is as following, with ``ipv6_info`` the IPv6 + is as follows, with ``ipv6_info`` the IPv6 :attr:`~pcapkit.protocols.protocol.Protocol.info` and ``ipv6_frag_info`` the Fragment header's: @@ -61,7 +62,7 @@ Terminology does count it -- it is a header length, and the Fragment header is one of the extension headers it has walked -- so the ``pcap`` and ``pcapng`` adapters subtract it back off. The ``dpkt`` and ``scapy`` - adapters leave it out of the three fields too, but as best effort, + adapters leave it out of those fields too, but as best effort, not as a contract (:issue:`1687`). .. note:: @@ -86,7 +87,7 @@ Terminology reasm.ipv6.datagram Data structure for **reassembled IPv6 datagram** (element from :attr:`IPv6.datagram ` - *tuple*) is as following: + *tuple*) is as follows: .. code-block:: text @@ -151,18 +152,20 @@ Terminology ``completed`` and ``conflict`` are independent signals: a datagram can be :attr:`~pcapkit.foundation.reassembly.data.data.Completion.COMPLETE` - and still carry a non-empty ``conflict``. :rfc:`791` resolves an - overlapping fragment's disagreement itself -- "this procedure will - use the more recently arrived copy in the data buffer" -- the - opposite resolution from TCP's first-write-wins - (:rfc:`9293#section-3.10`) -- so a contested - range never leaves a hole on its own, and ``conflict`` is what lets - a caller tell a clean datagram from a contested one. + and still carry a non-empty ``conflict``. IPv6 reassembly shares + IPv4's resolution of an overlapping fragment's disagreement, the one + :rfc:`791` gives -- "this procedure will use the more recently + arrived copy in the data buffer" -- the opposite resolution from + TCP's first-write-wins (:rfc:`9293#section-3.10`), rather than + abandoning the datagram as :rfc:`8200#section-4.5` has a receiving + node do. So a contested range never leaves a hole on its own, and + ``conflict`` is what lets a caller tell a clean datagram from a + contested one. reasm.ipv6.buffer Data structure for internal buffering when performing reassembly algorithms (:attr:`IPv6._buffer `) - is as following: + is as follows: .. code-block:: text diff --git a/docs/source/pcapkit/foundation/reassembly/reassembly.rst b/docs/source/pcapkit/foundation/reassembly/reassembly.rst index 7867ad4afe..45b8102713 100644 --- a/docs/source/pcapkit/foundation/reassembly/reassembly.rst +++ b/docs/source/pcapkit/foundation/reassembly/reassembly.rst @@ -40,7 +40,7 @@ that implement datagram reassembly of IP and TCP packets. value can be set by :attr:`__protocol_type__` class attribute. .. property:: registry - :type: python:dict[str, ModuleDescriptor[ReassemblyBase] | typing.Type[ReassemblyBase]] + :type: dict[str, ModuleDescriptor[ReassemblyBase] | typing.Type[ReassemblyBase]] Mapping of protocol names to reassembly classes. diff --git a/docs/source/pcapkit/foundation/reassembly/tcp.rst b/docs/source/pcapkit/foundation/reassembly/tcp.rst index 5fadfe420f..83b5ec2eba 100644 --- a/docs/source/pcapkit/foundation/reassembly/tcp.rst +++ b/docs/source/pcapkit/foundation/reassembly/tcp.rst @@ -19,6 +19,8 @@ which reconstructs fragmented TCP packets back to origin. .. autoattribute:: __protocol_type__ .. autoattribute:: __callback_fn__ :no-value: + .. autoattribute:: __timeout__ + .. autoattribute:: __window__ Algorithm ========= @@ -157,7 +159,7 @@ Terminology reasm.tcp.packet Data structure for **TCP datagram reassembly** (:meth:`TCP.reassembly `) - is as following: + is as follows: .. code-block:: python @@ -195,7 +197,7 @@ Terminology reasm.tcp.datagram Data structure for **reassembled TCP datagram** (element from :attr:`TCP.datagram ` - *tuple*) is as following: + *tuple*) is as follows: .. code-block:: text @@ -254,7 +256,7 @@ Terminology reasm.tcp.buffer Data structure for internal buffering when performing reassembly algorithms (:attr:`TCP._buffer `) - is as following: + is as follows: .. code-block:: text @@ -375,9 +377,4 @@ Type Variables :module: pcapkit.foundation.reassembly.data.tcp :type: ipaddress.IPv4Address | ipaddress.IPv6Address -.. data:: BufferID - :module: pcapkit.foundation.reassembly.data.tcp - :type: typing.Tuple[~pcapkit.foundation.reassembly.data.tcp._AT, int, ~pcapkit.foundation.reassembly.data.tcp._AT, int] - :no-index: - - Buffer ID data structure. + IP address type. diff --git a/docs/source/pcapkit/foundation/traceflow/tcp.rst b/docs/source/pcapkit/foundation/traceflow/tcp.rst index 3a4eae0349..0c184ec336 100644 --- a/docs/source/pcapkit/foundation/traceflow/tcp.rst +++ b/docs/source/pcapkit/foundation/traceflow/tcp.rst @@ -32,7 +32,7 @@ Terminology trace.tcp.packet Data structure for **TCP flow tracing** (:meth:`TraceFlow.dump `) - is as following: + is as follows: .. code-block:: python @@ -60,7 +60,7 @@ Terminology trace.tcp.buffer Data structure for internal buffering when performing flow tracing algorithms (:attr:`TraceFlow._buffer `) - is as following: + is as follows: .. code-block:: text @@ -110,7 +110,7 @@ Terminology trace.tcp.index Data structure for **TCP flow tracing** (element from :attr:`TraceFlow.index ` - *tuple*) is as following: + *tuple*) is as follows: .. code-block:: text @@ -126,8 +126,9 @@ Terminology | | opened the flow | |--> 'reverse': (tuple) frame index the other way; empty when | | tracing unidirectionally - | |--> 'packet': (Optional[tuple]) one reassembled datagram per - | direction, or None when analyse is off + | |--> 'packet': (Optional[tuple]) reassembled datagrams, one per + | direction and acknowledgement number, or None + | when analyse is off |--> (Info) data ... ``forward`` and ``reverse`` partition ``index``, so @@ -135,12 +136,13 @@ Terminology taking the label apart. ``forward`` is the direction of the packet that opened the flow, whose endpoints the label names first. - ``packet`` is the conversation's application layer: one reassembled datagram - per direction, present only when the tracer was constructed with - ``analyse=True``. It is reassembled on the *first read*, and each datagram's + ``packet`` is the conversation's application layer: its reassembled + datagrams, one per direction and acknowledgement number, present only when + the tracer was constructed with ``analyse=True``. It is reassembled on the + *first read*, and each datagram's own :attr:`~pcapkit.foundation.reassembly.data.tcp.Datagram.packet` is parsed later still, so a caller that wanted only frame numbers pays for - neither. The tracer does not reassemble the stream itself -- it feeds + none of it. The tracer does not reassemble the stream itself -- it feeds :class:`~pcapkit.foundation.reassembly.tcp.TCP`, whose :rfc:`815` algorithm handles the reordering and retransmission that concatenating payloads in capture order would corrupt. @@ -173,3 +175,5 @@ Type Variables .. data:: _AT :module: pcapkit.foundation.traceflow.data.tcp :type: ipaddress.IPv4Address | ipaddress.IPv6Address + + IP address type. diff --git a/docs/source/pcapkit/foundation/traceflow/traceflow.rst b/docs/source/pcapkit/foundation/traceflow/traceflow.rst index 5a46929a76..1d7f586683 100644 --- a/docs/source/pcapkit/foundation/traceflow/traceflow.rst +++ b/docs/source/pcapkit/foundation/traceflow/traceflow.rst @@ -37,7 +37,7 @@ which is an abstract base class for all flow tracing classes. value can be set by :attr:`__protocol_type__` class attribute. .. property:: registry - :type: python:dict[str, ModuleDescriptor[TraceFlowBase] | typing.Type[TraceFlowBase]] + :type: dict[str, ModuleDescriptor[TraceFlowBase] | typing.Type[TraceFlowBase]] Mapping of protocol names to flow tracing classes. diff --git a/docs/source/pcapkit/interface/core.rst b/docs/source/pcapkit/interface/core.rst index 0ee36bb8ee..112bfd044f 100644 --- a/docs/source/pcapkit/interface/core.rst +++ b/docs/source/pcapkit/interface/core.rst @@ -4,7 +4,7 @@ Core Interface .. module:: pcapkit.interface.core :mod:`pcapkit.interface.core` defines core user-oriented -interfaces, variables, and etc., which wraps around the +interfaces and variables, which wrap around the foundation classes from :mod:`pcapkit.foundation`. .. autofunction:: pcapkit.interface.core.extract diff --git a/docs/source/pcapkit/toolkit/3rdparty.rst b/docs/source/pcapkit/toolkit/3rdparty.rst index 489a285c6d..0918f3b1af 100644 --- a/docs/source/pcapkit/toolkit/3rdparty.rst +++ b/docs/source/pcapkit/toolkit/3rdparty.rst @@ -67,12 +67,34 @@ cannot be used for it. Auxiliary Functions ------------------- +.. autofunction:: pcapkit.toolkit.dpkt.attach_timestamp + +.. autofunction:: pcapkit.toolkit.dpkt.packet2timestamp + +.. autofunction:: pcapkit.toolkit.dpkt.attach_buffer + +.. autofunction:: pcapkit.toolkit.dpkt.packet2bytes + +.. autofunction:: pcapkit.toolkit.dpkt.packet2frame + .. autofunction:: pcapkit.toolkit.dpkt.ipv6_hdr_len .. autofunction:: pcapkit.toolkit.dpkt.packet2chain .. autofunction:: pcapkit.toolkit.dpkt.packet2dict +Auxiliary Data +-------------- + +.. autoclass:: pcapkit.toolkit.dpkt.RecordTimestamp + :show-inheritance: + +.. autoclass:: pcapkit.toolkit.dpkt.Timestamp + :show-inheritance: + +.. autoclass:: pcapkit.toolkit.dpkt.DecimalTimestamp + :show-inheritance: + PyShark Tools ============= @@ -93,8 +115,10 @@ cannot be used for it. .. autofunction:: pcapkit.toolkit.pyshark.tcp_traceflow .. autodata:: pcapkit.toolkit.pyshark.ENCAP_TYPE_TO_LINKTYPE + :no-value: .. autodata:: pcapkit.toolkit.pyshark.FILTER_NAME_TO_LINKTYPE + :no-value: Auxiliary Functions ------------------- diff --git a/docs/source/pcapkit/utilities/config.rst b/docs/source/pcapkit/utilities/config.rst index 13d5ea9a84..5d7b6e14c1 100644 --- a/docs/source/pcapkit/utilities/config.rst +++ b/docs/source/pcapkit/utilities/config.rst @@ -26,8 +26,8 @@ here reaches every engine and protocol that reads it. The :mod:`pcapkit.vendor` crawlers' :envvar:`PCAPKIT_VENDOR_RETRY`, :envvar:`PCAPKIT_CI_MODE`, :envvar:`PCAPKIT_HTTP_PROXY` and :envvar:`PCAPKIT_HTTPS_PROXY` are not held here. They are read by -:mod:`pcapkit.vendor.default`, the first two with the :ref:`readers -` below, and documented with it under :ref:`vendor-variables`. +:mod:`pcapkit.vendor.default`, :envvar:`PCAPKIT_VENDOR_RETRY` and +:envvar:`PCAPKIT_CI_MODE` with the :ref:`readers ` below, and documented with it under :ref:`vendor-variables`. :envvar:`PCAPKIT_DEVMODE`, :envvar:`PCAPKIT_VERBOSE` and :envvar:`PCAPKIT_SPHINX` are read by :mod:`pcapkit.utilities.logging`, which needs them while it loads diff --git a/docs/source/pcapkit/utilities/exceptions.rst b/docs/source/pcapkit/utilities/exceptions.rst index 1ad0943ad3..f6349c610f 100644 --- a/docs/source/pcapkit/utilities/exceptions.rst +++ b/docs/source/pcapkit/utilities/exceptions.rst @@ -12,7 +12,7 @@ Loud and Quiet Errors Raising a :class:`~pcapkit.utilities.exceptions.BaseError` is, by default, a **loud** act: the error is logged once at :data:`logging.CRITICAL` on the -:data:`~pcapkit.utilities.logging.logger` logger, and outside development mode +``pcapkit`` logger (:data:`~pcapkit.utilities.logging.logger`), and outside development mode an exception hook is installed on :data:`sys.excepthook` and :data:`threading.excepthook`, the first time a loud error needs it, so the user sees the exception line rather than a walk through :mod:`pcapkit`'s internals. diff --git a/docs/source/pcapkit/utilities/logging.rst b/docs/source/pcapkit/utilities/logging.rst index 2041d88198..4c69b8f434 100644 --- a/docs/source/pcapkit/utilities/logging.rst +++ b/docs/source/pcapkit/utilities/logging.rst @@ -150,7 +150,7 @@ Compatibility Note :data:`logging.INFO` (or :data:`logging.DEBUG` under :envvar:`PCAPKIT_DEVMODE`). That hijacked the logging configuration of every application importing :mod:`pcapkit`, so 1.5.0 does it only under - :envvar:`PCAPKIT_DEVMODE`. Two consequences are visible to existing code: + :envvar:`PCAPKIT_DEVMODE`. That has consequences visible to existing code: 1. **Nothing reaches stderr by default.** The ``registered ...`` bookkeeping is at :data:`logging.DEBUG`, not :data:`logging.INFO`. To restore the @@ -167,6 +167,7 @@ Compatibility Note .. code-block:: python + import logging from pcapkit.utilities.logging import handler, logger logger.setLevel(logging.INFO) logger.addHandler(handler) diff --git a/docs/source/pcapkit/utilities/warnings.rst b/docs/source/pcapkit/utilities/warnings.rst index 3cd14f4cd6..f37bdf6443 100644 --- a/docs/source/pcapkit/utilities/warnings.rst +++ b/docs/source/pcapkit/utilities/warnings.rst @@ -16,9 +16,9 @@ How a Warning Is Reported Every warning :mod:`pcapkit` reports goes through :func:`~pcapkit.utilities.warnings.warn`, which reports it **exactly once on each -of two channels**: +channel**: -1. the :data:`~pcapkit.utilities.logging.logger` logger, at +1. the ``pcapkit`` logger (:data:`~pcapkit.utilities.logging.logger`), at :data:`logging.WARNING` level, unconditionally -- so a consumer watching the ``pcapkit`` logger sees every complaint whatever the warning filters say; 2. the standard :mod:`warnings` machinery, via :func:`warnings.warn`, subject to diff --git a/pcapkit/__init__.py b/pcapkit/__init__.py index e968ca39fc..79a0e499d0 100644 --- a/pcapkit/__init__.py +++ b/pcapkit/__init__.py @@ -20,8 +20,8 @@ .. _DPKT: https://dpkt.readthedocs.io .. _PyShark: https://kiminewt.github.io/pyshark -In :mod:`pcapkit`, all files can be described as following nine -different components. +In :mod:`pcapkit`, all files can be described as the following +components. - Interface (:mod:`pcapkit.interface`) diff --git a/pcapkit/dumpkit/__init__.py b/pcapkit/dumpkit/__init__.py index 28d64b3542..4415b1aedd 100644 --- a/pcapkit/dumpkit/__init__.py +++ b/pcapkit/dumpkit/__init__.py @@ -5,7 +5,7 @@ .. module:: pcapkit.dumpkit :mod:`pcapkit.dumpkit` is the collection of dumpers for -:mod:`pcapkit` implementation, which is alike those described +:mod:`pcapkit` implementation, like those described in :mod:`dictdumper`. """ diff --git a/pcapkit/dumpkit/common.py b/pcapkit/dumpkit/common.py index bc50096658..7f182fc52c 100644 --- a/pcapkit/dumpkit/common.py +++ b/pcapkit/dumpkit/common.py @@ -100,8 +100,8 @@ class MyMixin(Dumper): # not registered ... Such a class can still be registered later, on demand. Note this hook - writes *both* output registries, so the equivalent manual call is the - module-level one that does the same, not either class' own method: + writes every output registry, so the equivalent manual call is the + module-level one that does the same, not any one class' own method: .. code-block:: python @@ -161,7 +161,7 @@ def __init_subclass__(cls, /, fmt: 'Optional[str]' = None, - :meth:`pcapkit.foundation.traceflow.traceflow.TraceFlow.register_dumper` """ - # NOTE: as in the four sibling hooks, an unrecognised class keyword would + # NOTE: as in the sibling hooks, an unrecognised class keyword would # otherwise land in ``**kwargs`` and be dropped by the bare # ``super().__init_subclass__()`` below, silently skipping registration. if args or kwargs: @@ -199,7 +199,7 @@ def render_enum(o: 'enum.Enum | aenum.Enum') -> 'str': ``tree``, ``text``, ``txt``, ``plist`` and ``xml`` output of both :class:`~pcapkit.foundation.extraction.Extractor` and :class:`~pcapkit.foundation.traceflow.traceflow.TraceFlow`, so it lives in - one function rather than being spelled out at each of the three places + one function rather than being spelled out at each place :func:`make_dumper`'s hook needs it. Args: @@ -216,7 +216,7 @@ def render_enum(o: 'enum.Enum | aenum.Enum') -> 'str': :class:`~pcapkit.const.tcp.flags.Flags` ``(0)`` as ``'Flags::None [0]'`` (GitHub issue :issue:`648`). - Two things make that worth a guard rather than a shrug. ``'None'`` is a + That is worth a guard rather than a shrug. ``'None'`` is a plausible member name, so a consumer splitting the rendering on ``::`` cannot tell it from a member genuinely so named -- and ``NONE`` *is* a declared name elsewhere in the library. And the defect is not confined to @@ -300,13 +300,13 @@ def _object_name(value: 'Any') -> 'str': def make_dumper(output: 'Type[ABCDumper]') -> 'Type[ABCDumper]': - """Create a customised :class:`~dictdumper.dumper.Dumper` object. + """Create a customised :class:`~dictdumper.dumper.Dumper` class. Args: output: Output class to customise. Returns: - Customised :class:`~dictdumper.dumper.Dumper` object. + Customised :class:`~dictdumper.dumper.Dumper` subclass of ``output``. .. versionchanged:: 1.5.0 @@ -378,7 +378,7 @@ def escape_key(key: 'Any') -> 'Any': into ``'{item}'``, and :meth:`~dictdumper.json.JSON._append_object` into ``'"{item}": '``; both call :meth:`~dictdumper.dumper.Dumper._encode_value` on the - *value* two lines later, never on the key -- so + *value* that follows, never on the key -- so :meth:`DictDumper.object_hook` is handed every value the writer will interpolate but no key at all, and cannot escape one on the way out the way it does a value. Each branch below that builds a mapping @@ -639,7 +639,7 @@ def _append_date(self, value: 'datetime.date', file: 'TextIO') -> 'None': that carries it exactly -- a :class:`~decimal.Decimal` ``*_epoch`` for frames and PCAP-NG blocks and options, e.g. ``time_epoch``, and the raw ``ntp_timestamp`` or - ``pmip_timestamp`` for the two MH options. + ``pmip_timestamp`` for the MH options that carry one. """ if isinstance(value, datetime.datetime) and value.tzinfo is not None: diff --git a/pcapkit/dumpkit/null.py b/pcapkit/dumpkit/null.py index d453e0f5fb..2ec84f3dd3 100644 --- a/pcapkit/dumpkit/null.py +++ b/pcapkit/dumpkit/null.py @@ -5,7 +5,7 @@ .. module:: pcapkit.dumpkit.null :mod:`pcapkit.dumpkit.null` is the dumper for :mod:`pcapkit` implementation, -specifically for **NotImplemented** format, which is alike those described in +specifically for **NotImplemented** format, like those described in :mod:`dictdumper`. Notes: diff --git a/pcapkit/dumpkit/pcap.py b/pcapkit/dumpkit/pcap.py index 40e22d49b6..80157dc19b 100644 --- a/pcapkit/dumpkit/pcap.py +++ b/pcapkit/dumpkit/pcap.py @@ -5,7 +5,7 @@ .. module:: pcapkit.dumpkit.pcap :mod:`pcapkit.dumpkit.pcap` is the dumper for :mod:`pcapkit` implementation, -specifically for PCAP format, which is alike those described in +specifically for PCAP format, like those described in :mod:`dictdumper`. """ diff --git a/pcapkit/foundation/engines/_pcap_backend.py b/pcapkit/foundation/engines/_pcap_backend.py index 326bc09bb6..ed7c6282ec 100644 --- a/pcapkit/foundation/engines/_pcap_backend.py +++ b/pcapkit/foundation/engines/_pcap_backend.py @@ -4,7 +4,7 @@ .. module:: pcapkit.foundation.engines._pcap_backend -Two unrelated PyPI distributions install a top-level module named :mod:`pcap`: +Unrelated PyPI distributions install a top-level module named :mod:`pcap`: * `PyPCAP`_ -- a Cython binding, shipped as a single extension module (:file:`pcap.cpython-*.so`), installable only up to Python 3.11. @@ -12,7 +12,7 @@ interface, shipped as a *package* (:file:`pcap/__init__.py`), installable on 3.10 and newer. -They therefore **collide**: nothing stops both from being installed, and +They therefore **collide**: nothing stops more than one from being installed, and ``import pcap`` then silently resolves to whichever the import system finds first. Measured on Python 3.10 with both present, the ``pcap/`` package wins and upstream's extension module is shadowed and unreachable -- so ``pcap-ct`` always @@ -21,8 +21,8 @@ :class:`~pcapkit.foundation.engines.pypcap.PyPCAP` and :class:`~pcapkit.foundation.engines.pcap_ct.PCAP_CT` are separate engines, so each has to know which distribution it actually got rather than assume. This module is -the one place that answers that, deliberately shared: the two engines must agree -on the answer, and two copies of the detection would be two chances to disagree. +the one place that answers that, deliberately shared: the engines must agree on +the answer, and a copy of the detection in each would be a chance to disagree. It is *only* detection -- nothing here is engine behaviour -- and it imports nothing from :mod:`pcapkit`, so it cannot introduce an import cycle. diff --git a/pcapkit/foundation/engines/dpkt.py b/pcapkit/foundation/engines/dpkt.py index a1b9576b01..508f5bdae1 100644 --- a/pcapkit/foundation/engines/dpkt.py +++ b/pcapkit/foundation/engines/dpkt.py @@ -243,7 +243,7 @@ def _pcap_reader() -> 'Type[PCAPReader]': resolution are lost by the time a PCAP frame record of the packet is built (:issue:`1507`). This subclass yields the same timestamp, as a :class:`~pcapkit.toolkit.dpkt.Timestamp` or, for a nanosecond file, a - :class:`~pcapkit.toolkit.dpkt.DecimalTimestamp`, which carries the two. + :class:`~pcapkit.toolkit.dpkt.DecimalTimestamp`, which carries them. Built when called, so that importing this module does not import `DPKT`_, and so that it always subclasses the :class:`dpkt.pcap.Reader` of the moment. diff --git a/pcapkit/foundation/engines/engine.py b/pcapkit/foundation/engines/engine.py index 09b802fd62..c6aa0535db 100644 --- a/pcapkit/foundation/engines/engine.py +++ b/pcapkit/foundation/engines/engine.py @@ -166,6 +166,7 @@ def unsupported_reason(cls) -> 'Optional[str]': ########################################################################## def __init__(self, extractor: 'Extractor') -> 'None': + #: Extractor instance this engine reads for. self._extractor = extractor def __call__(self) -> 'None': @@ -283,7 +284,7 @@ def __init_subclass__(cls, /, engine: 'Optional[str]' = None, *args: 'Any', **kw :attr:`__engine_name__ ` is *not* an opt-in. It supplies the - :attr:`name ` + :attr:`name ` the engine reports, which it does whether or not the engine is registered; only the keyword decides registration. @@ -292,12 +293,12 @@ def __init_subclass__(cls, /, engine: 'Optional[str]' = None, *args: 'Any', **kw cannot be passed as a class keyword at all on Python 3.10: :meth:`abc.ABCMeta.__new__` takes ``mcls``, ``name``, ``bases`` and ``namespace`` as positional-*or-keyword* parameters - before 3.11, so a class keyword by any of those four names collides + before 3.11, so a class keyword by any of those names collides with one of them and the class statement raises :exc:`TypeError` from the metaclass before this method is reached. ``engine`` is outside that set, so the documented registration path works on every supported version. Measured on 3.10.21, 3.11.15 and 3.14.7; those - four are the whole of the :meth:`abc.ABCMeta.__new__` collision + names are the whole of the :meth:`abc.ABCMeta.__new__` collision surface. Separately, and for an unrelated reason that holds on every version, ``metaclass`` is not usable as a class keyword either: a ``class`` statement consumes it to choose the metaclass, so it never @@ -316,7 +317,7 @@ def __init_subclass__(cls, /, engine: 'Optional[str]' = None, *args: 'Any', **kw # no exception, no warning. So reject it. # # One typo this cannot catch is ``name=``, and only on Python 3.10: it is - # one of the four names that collide with ``ABCMeta.__new__``, so it fails + # one of the names that collide with ``ABCMeta.__new__``, so it fails # in the metaclass before reaching here. It is still loud, just as a # ``TypeError`` rather than an ``UnsupportedCall``. # diff --git a/pcapkit/foundation/engines/pcap.py b/pcapkit/foundation/engines/pcap.py index d4524f7edf..53054a94f6 100644 --- a/pcapkit/foundation/engines/pcap.py +++ b/pcapkit/foundation/engines/pcap.py @@ -141,7 +141,7 @@ def run(self) -> 'None': def read_frame(self) -> 'Frame': """Read frames. - This method performs following operations: + This method performs the following operations: - extract frames and each layer of packets; - make :class:`~pcapkit.corekit.infoclass.Info` object out of frame properties; diff --git a/pcapkit/foundation/engines/pcap_ct.py b/pcapkit/foundation/engines/pcap_ct.py index 928123bd6b..51f42ef19f 100644 --- a/pcapkit/foundation/engines/pcap_ct.py +++ b/pcapkit/foundation/engines/pcap_ct.py @@ -61,16 +61,16 @@ class PCAP_CT(EngineBase['RawFrame']): It does still need a **system** :manpage:`libpcap(3)` at *run* time, and this is easy to get wrong: `libpcap`_ ships a vendored - :file:`libpcap.so` under :file:`_platform/{linux,macos,windows}/` but does + :file:`libpcap.so` under ``_platform/{linux,macos,windows}/`` but does **not** use it by default. Its :file:`libpcap.cfg` reads ``LIBPCAP = None`` as published, which sends its loader to :func:`ctypes.util.find_library`, so the library actually mapped is the - host's ``libpcap.so.1``. Measured on this host: the same + host's ``libpcap.so.1``. Measured on one Linux host: the same ``libpcap`` 1.11.0b29 wheel loaded ``/usr/lib64/libpcap.so.1.5.3`` under one interpreter and linuxbrew's 1.11.0 under another, purely because their loader search paths differ. - Two consequences. With no system :manpage:`libpcap(3)` at all, + This has consequences. With no system :manpage:`libpcap(3)` at all, ``import pcap`` raises :exc:`OSError` -- not :exc:`ImportError` -- which :meth:`unsupported_reason` exists partly to catch. And the :manpage:`libpcap(3)` *version* in play is a property of the host rather @@ -183,7 +183,7 @@ def unsupported_reason(cls) -> 'Optional[str]': """Why this engine cannot run here, or :data:`None` when it can. Consulted by :meth:`pcapkit.foundation.extraction.Extractor.run` *before* - the import test, and it answers two questions the import test cannot. + the import test, and it answers questions the import test cannot. **Which distribution owns** :mod:`pcap`. Upstream `PyPCAP`_ owns that name just as legitimately, and ``import pcap._pcap`` -- what @@ -214,7 +214,7 @@ def unsupported_reason(cls) -> 'Optional[str]': which the import test then reports correctly on its own. Returns: - A phrase naming the real cause, or :data:`None`. An :mod:`pcap` that is + A phrase naming the real cause, or :data:`None`. A :mod:`pcap` that is merely absent returns :data:`None`, since the import test reports that case in its own words. @@ -238,7 +238,7 @@ def dlink(self) -> 'Enum_LinkType': def backend(self) -> 'str': """The distribution, version and location this engine is driving. - Two distributions provide :mod:`pcap`, so "the pcap-ct engine failed" is + More than one distribution provides :mod:`pcap`, so "the pcap-ct engine failed" is not a complete statement of what ran. This says which one it was, e.g. ``pcap-ct 1.3.0b3 (/.../pcap/__init__.py)``. @@ -314,7 +314,7 @@ def run(self) -> 'None': ` as an iterator over it. Warns: - AttributeWarning: Warns under following circumstances: + AttributeWarning: Warns under the following circumstances: * if :attr:`self.extractor._exlyr ` and/or :attr:`self.extractor._exptl ` @@ -341,9 +341,9 @@ def run(self) -> 'None': :file:`examples/captures/dhcp.pcapng`: * against libpcap **1.11.0** it reads correctly, yielding the same - four frames and the same sub-second timestamps as + frames and the same sub-second timestamps as :class:`~pcapkit.foundation.engines.pcapng.PCAPNG`; - * against libpcap **1.5.3** it yields the four frames but with + * against libpcap **1.5.3** it yields the same frames but with nonsense sub-second timestamps -- ``1102274184.0000002`` and the like -- silently, with no error. diff --git a/pcapkit/foundation/engines/pcapng.py b/pcapkit/foundation/engines/pcapng.py index 5e1206329b..444d787135 100644 --- a/pcapkit/foundation/engines/pcapng.py +++ b/pcapkit/foundation/engines/pcapng.py @@ -51,7 +51,7 @@ @info_final class Context(Info): - """Context manager for PCAP-NG file format.""" + """Per-section context of a PCAP-NG file: its section header and the blocks read under it.""" #: Section header. section: 'Data_SectionHeaderBlock' @@ -194,7 +194,7 @@ def run(self) -> 'None': def read_frame(self) -> 'P_PCAPNG': """Read frames. - This method performs following tasks: + This method performs the following tasks: - read the next block from input file; - check if the block is a packet block; @@ -426,7 +426,7 @@ def _bound_read_ahead(self) -> 'None': which is as far as a file read goes too. How far is enough is the block's Total Length, in the section's byte order - or, for a section header block, the one its Byte-Order Magic declares. Two + or, for a section header block, the one its Byte-Order Magic declares. Some kinds of block are parsed past it, and so are read as far as from a file: one shorter than its own fixed fields, or not a whole number of words, keeps the buffer's worth; a Decryption Secrets Block reads as far as its diff --git a/pcapkit/foundation/engines/pypcap.py b/pcapkit/foundation/engines/pypcap.py index a7e979e285..be79e4e402 100644 --- a/pcapkit/foundation/engines/pypcap.py +++ b/pcapkit/foundation/engines/pypcap.py @@ -177,7 +177,7 @@ def dlink(self) -> 'Enum_LinkType': def backend(self) -> 'str': """The distribution, version and location this engine is driving. - Two distributions provide :mod:`pcap`, so "the PyPCAP engine failed" is + More than one distribution provides :mod:`pcap`, so "the PyPCAP engine failed" is not a complete statement of what ran. This says which one it was, e.g. ``pypcap 1.3.0 (/.../pcap.cpython-310-x86_64-linux-gnu.so)``. @@ -255,7 +255,7 @@ def run(self) -> 'None': as an iterator from :class:`pcap.pcap`. Warns: - AttributeWarning: Warns under following circumstances: + AttributeWarning: Warns under the following circumstances: * if :attr:`self.extractor._exlyr ` and/or :attr:`self.extractor._exptl ` diff --git a/pcapkit/foundation/engines/pypcapfile.py b/pcapkit/foundation/engines/pypcapfile.py index bb1e514f59..42a28a6f7a 100644 --- a/pcapkit/foundation/engines/pypcapfile.py +++ b/pcapkit/foundation/engines/pypcapfile.py @@ -179,7 +179,7 @@ def run(self) -> 'None': first frame is decoded. Warns: - AttributeWarning: Warns under following circumstances: + AttributeWarning: Warns under the following circumstances: * if :attr:`self.extractor._exlyr ` and/or :attr:`self.extractor._exptl ` @@ -327,16 +327,16 @@ def _read_records(self, file: 'NamedStream', """Read the savefile's records, every one of them. :func:`pcapfile.savefile._read_a_packet` (pypcapfile 0.12.0) drops - records in two ways: + records: * It picks the byte order of each record header by testing ``hdrp[0].byteorder == 'big'`` -- :class:`bytes` against :class:`str`, never true on Python 3. A big-endian record header is unpacked little-endian, its byte-swapped capture length overruns the file, and - the savefile ends at its first record (#1512). + the savefile ends at its first record (:issue:`1512`). * It ends the savefile at any record whose frame data is empty or shorter than its capture length, so a zero-length record drops itself and every - record after it, and a truncated final record is dropped (#1571). + record after it, and a truncated final record is dropped (:issue:`1571`). This reads each record in the byte order of the header :mod:`pcapfile` loaded, into the :class:`pcapfile.structs.pcap_packet` that function diff --git a/pcapkit/foundation/engines/pyshark.py b/pcapkit/foundation/engines/pyshark.py index 35c548ffab..2113ce1eff 100644 --- a/pcapkit/foundation/engines/pyshark.py +++ b/pcapkit/foundation/engines/pyshark.py @@ -115,15 +115,15 @@ def unsupported_reason(cls) -> 'Optional[str]': """Why this engine cannot run here, or :data:`None` when it can. Consulted by :meth:`pcapkit.foundation.extraction.Extractor.run` *before* - the import test, because neither of the two things that stop this engine - is visible to an import. `PyShark`_ imports perfectly well and then fails + the import test, because none of the things that stop this engine is + visible to an import. `PyShark`_ imports perfectly well and then fails when it is used, which without this hook escapes from :meth:`run` as a hard error rather than degrading to the default engine with a warning. **The interpreter.** ``pyshark`` 0.6 does ``asyncio.get_event_loop_policy().get_event_loop()`` at :file:`pyshark/capture/capture.py:183`, in a fresh interpreter with no - running loop. Measured on four interpreters: 3.10 and 3.11 return a loop + running loop. Measured: 3.10 and 3.11 return a loop silently, 3.12 returns one with a :exc:`DeprecationWarning`, and 3.14 raises ``RuntimeError: There is no current event loop in thread 'MainThread'``. Hence :attr:`PYTHON_CEILING` is ``(3, 14)``. Python 3.13 was @@ -220,7 +220,7 @@ def run(self) -> 'None': :meth:`self.extractor.record_header `. Warns: - AttributeWarning: Warns under following circumstances: + AttributeWarning: Warns under the following circumstances: * if :attr:`self.extractor._exlyr ` and/or :attr:`self.extractor._exptl ` diff --git a/pcapkit/foundation/engines/scapy.py b/pcapkit/foundation/engines/scapy.py index c3d6678718..4509cc30ba 100644 --- a/pcapkit/foundation/engines/scapy.py +++ b/pcapkit/foundation/engines/scapy.py @@ -124,7 +124,7 @@ def _reader_type() -> 'Type[PcapReader]': to one table across Section Header Blocks, so a later section's interface 0 resolved to the first section's, with its link type and resolution (#1522). - And it dates two kinds of packet that `Scapy`_ misdates. A Simple Packet + And it dates packets that `Scapy`_ misdates. A Simple Packet Block has no timestamp, and `Scapy`_ leaves its packet with the time it was built, so it is set to 0 (#1503). `Scapy`_ ignores an interface's ``if_tsoffset``, the seconds to add to each of its timestamps, so the diff --git a/pcapkit/foundation/extraction.py b/pcapkit/foundation/extraction.py index abe8395ab6..f8b990f2cb 100644 --- a/pcapkit/foundation/extraction.py +++ b/pcapkit/foundation/extraction.py @@ -10,7 +10,7 @@ :class:`~pcapkit.foundation.extraction.Extractor` only, which synthesises file I/O and protocol analysis, coordinates information exchange in all network layers, -extracts parameters from a PCAP file. +and extracts parameters from a PCAP file. """ import collections @@ -66,12 +66,12 @@ #: Every key registered in :attr:`Extractor.__output__` and in #: :attr:`TraceFlowBase.__output__ - #: ` -- the two - #: registries expose the same eight keys, so ``'cap'`` and the ``'txt'``/``'xml'`` + #: ` -- both + #: registries expose the same keys, so ``'cap'`` and the ``'txt'``/``'xml'`` #: aliases are spellable for a type checker as they are accepted at runtime. Formats = Literal['pcap', 'cap', 'json', 'tree', 'text', 'txt', 'plist', 'xml'] - # NOTE: this alias is duplicated verbatim in ``pcapkit.interface.misc``; both - # copies need updating when a new engine lands. The duplication is left + # NOTE: this alias is duplicated verbatim in ``pcapkit.interface.misc``; each + # copy needs updating when a new engine lands. The duplication is left # as-is on purpose. Engines = Literal['default', 'pcapkit', 'dpkt', 'scapy', 'pyshark', 'pypcap', 'pcap_ct', 'pypcapfile'] @@ -229,16 +229,16 @@ class Extractor(Generic[_P]): #: Output format. _offmt: 'Formats' - #: List of potential PCAP file extentions. + #: List of potential PCAP file extensions. PCAP_EXT = ['.pcap', '.cap', '.pcapng'] ########################################################################## # Defaults. ########################################################################## - #: Format dumper mapping for writing output files. The values should be a - #: tuple representing the module name and class name, or a - #: :class:`dictdumper.dumper.Dumper` subclass, and corresponding file extension. + #: Format dumper mapping for writing output files. Each value pairs the dumper, + #: as a module descriptor or a :class:`dictdumper.dumper.Dumper` subclass, with + #: the file extension of its format. __output__ = collections.defaultdict( lambda: (ModuleDescriptor('pcapkit.dumpkit', 'NotImplementedIO'), None), { @@ -293,7 +293,7 @@ class Extractor(Generic[_P]): 'tcp': ModuleDescriptor('pcapkit.foundation.traceflow.tcp', 'TCP'), } # type: dict[str, ModuleDescriptor[TraceFlowBase] | Type[TraceFlowBase]] - # Read-only copies of the four registries as shipped. A public registrar + # Read-only copies of the registries as shipped. A public registrar # handed the built-in a name shipped with restores this entry, so an # override can be undone (:issue:`1363`). _shipped_output = types.MappingProxyType(dict(__output__)) @@ -428,8 +428,8 @@ def register_dumper(cls, format: 'str', dumper: 'ModuleDescriptor[Dumper] | Type r"""Register a new dumper class. Notes: - The full qualified class name of the new dumper class - should be as ``{dumper.module}.{dumper.name}``. + The fully qualified class name of the new dumper class + should be ``{dumper.module}.{dumper.name}``. The overwrite guard fires only when the incumbent dumper differs from the replacement, so re-registering the exact same object is @@ -491,8 +491,8 @@ def register_engine(cls, name: 'str', engine: 'ModuleDescriptor[Engine] | Type[E r"""Register a new extraction engine. Notes: - The full qualified class name of the new extraction engine - should be as ``{engine.module}.{engine.name}``. + The fully qualified class name of the new extraction engine + should be ``{engine.module}.{engine.name}``. The overwrite guard fires only when the incumbent differs from the replacement, so re-registering the exact same object is a @@ -585,8 +585,8 @@ def register_reassembly(cls, protocol: 'str', reassembly: 'ModuleDescriptor[Reas r"""Register a new reassembly engine. Notes: - The full qualified class name of the new reassembly engine - should be as ``{reassembly.module}.{reassembly.name}``. + The fully qualified class name of the new reassembly engine + should be ``{reassembly.module}.{reassembly.name}``. The overwrite guard fires only when the incumbent differs from the replacement, so re-registering the exact same object is a @@ -671,8 +671,8 @@ def register_traceflow(cls, protocol: 'str', traceflow: 'ModuleDescriptor[TraceF r"""Register a new flow tracing engine. Notes: - The full qualified class name of the new flow tracing engine - should be as ``{traceflow.module}.{traceflow.name}``. + The fully qualified class name of the new flow tracing engine + should be ``{traceflow.module}.{traceflow.name}``. The overwrite guard fires only when the incumbent differs from the replacement, so re-registering the exact same object is a @@ -938,7 +938,7 @@ def make_name(cls, fin: 'str | IO[bytes]' = 'in.pcap', fout: 'str' = 'out', Generated input and output filenames. Raises: - FileNotFound: If input file does not exists. + FileNotFound: If input file does not exist. FormatError: If no file extension is registered for ``fmt``. """ @@ -978,7 +978,8 @@ def make_name(cls, fin: 'str | IO[bytes]' = 'in.pcap', fout: 'str' = 'out', # bare -- ``Extractor._fext`` reaches the engines, which each # compose a per-frame name as ``f'{name}.{ext._fext}'``. Leaving the # dot on is what produced ``Frame 1..json`` (see #358), so - # normalise it away once, here, rather than at six call sites. + # normalise it away once, here, rather than in every engine that + # composes a per-frame name. ext = registered[1:] if registered.startswith('.') else registered if (parent := os.path.split(fout)[0]): @@ -1196,7 +1197,7 @@ def __init__(self, engines ignore it. Warns: - pcapkit.utilities.warnings.FormatWarning: Warns under following circumstances: + pcapkit.utilities.warnings.FormatWarning: Warns under the following circumstances: * If using PCAP output for TCP flow tracing while the extraction engine is PyShark or PyPCAPFile. @@ -1583,7 +1584,7 @@ def _owns_input(self) -> 'bool': all. Note: - The two :data:`True` cases are written as separate tests rather than + The :data:`True` cases are written as separate tests rather than folded together because they *can* in principle coincide -- a path naming something non-seekable would be opened here and then wrapped -- and the answer has to be :data:`True` for both halves of it. That @@ -1652,7 +1653,7 @@ def _note_eof_progress(self) -> 'bool': **Not idempotent.** Each call consumes one end-of-stream observation by overwriting :attr:`_eof_mark`, so calling it twice for one end of stream spends the retry it would have granted. Call it exactly once - per handler, which is what the three call sites do. + per handler, which is what each call site does. Returns: :data:`True` when retrying may yet produce a frame -- this is the first @@ -1684,7 +1685,7 @@ def _cleanup(self) -> 'None': when it is done with it. It also tells the flow tracer the capture has ended, via - :meth:`TraceFlow.finish `. + :meth:`TraceFlow.finish `. That is the point at which a traced flow nothing has superseded can be said to be over, so it is where such a flow is finalised and its callbacks run. This method can run more than once for one extraction -- each call to diff --git a/pcapkit/foundation/reassembly/data/data.py b/pcapkit/foundation/reassembly/data/data.py index cf60a3f122..24ebe0a61f 100644 --- a/pcapkit/foundation/reassembly/data/data.py +++ b/pcapkit/foundation/reassembly/data/data.py @@ -28,9 +28,9 @@ class Completion(EnumLookup, StrEnum): This is the value of :attr:`Datagram.completed `, which widens a plain :obj:`bool` rather than adding a second channel beside - it: reassembly has *three* outcomes to report, not two, since a buffer - abandoned under the :rfc:`1122` / :rfc:`8200` reassembly timeout is a different - event from one that simply had not finished when the capture did. An expired + it: reassembly has more outcomes to report than a :obj:`bool` can hold, since + a buffer abandoned under the :rfc:`1122` / :rfc:`8200` reassembly timeout is + a different event from one that simply had not finished when the capture did. An expired datagram says "these fragments are gone", a partial one says "these fragments had not arrived yet". @@ -42,8 +42,8 @@ class Completion(EnumLookup, StrEnum): instead. It derives from :class:`~pcapkit.utilities.compat.StrEnum`, as - :class:`~pcapkit.protocols.application.httpv1.Type` does, which buys two - things a plain :class:`enum.Enum` does not: the value survives + :class:`~pcapkit.protocols.application.httpv1.Type` does, which buys what a + plain :class:`enum.Enum` does not: the value survives :func:`json.dumps` -- a plain enumeration raises :exc:`TypeError` there, and :meth:`Datagram.to_dict ` hands this field straight out -- and ``datagram.completed == 'timeout'`` works, so a @@ -51,7 +51,7 @@ class Completion(EnumLookup, StrEnum): :class:`pcapkit.const.pcapng.tls_key_label.TLSKeyLabel` is the same kind of string enumeration, but it is generated like its :mod:`pcapkit.const.pcapng` siblings (:issue:`886`), so it derives from :class:`aenum`'s own - ``StrEnum`` (via :class:`~pcapkit.corekit.enum.EnumRegistry`). Both + ``StrEnum`` (via :class:`~pcapkit.corekit.enum.EnumRegistry`). The properties above hold for it, as ``aenum.StrEnum`` is a :class:`str` subclass, but ``isinstance``/``issubclass`` against :class:`pcapkit.utilities.compat.StrEnum` does not. @@ -124,9 +124,10 @@ class Deferred: :file:`http.pcap` pass against 1117. Holding the call here defers it to the first read of - :attr:`Datagram.packet`, where :class:`~pcapkit.corekit.packet.DeferredPacket` - runs it, so a caller that wants the parsed payload still gets exactly the - object an eager call would produce, and one that does not never pays for it. + :attr:`Datagram.packet `, + where :class:`~pcapkit.corekit.packet.DeferredPacket` runs it, so a caller that + wants the parsed payload still gets exactly the object an eager call would + produce, and one that does not never pays for it. Args: analyze: The analyser to call, i.e. diff --git a/pcapkit/foundation/reassembly/data/ip.py b/pcapkit/foundation/reassembly/data/ip.py index 942f2378c8..e349bf8a17 100644 --- a/pcapkit/foundation/reassembly/data/ip.py +++ b/pcapkit/foundation/reassembly/data/ip.py @@ -85,8 +85,9 @@ class Datagram(DeferredPacket, Info, Generic[_AT]): #: stores a field whose name is a *builtin* name under a mangled key and maps #: it back on the way out, so ``packet`` never lands in :attr:`__dict__` #: itself -- which routes reading it through :meth:`__getattr__`, where a - #: :class:`Deferred` analysis can be run, while ``dict(datagram)``, - #: :meth:`to_dict` and iteration still report the field under its own name. + #: :class:`~pcapkit.foundation.reassembly.data.data.Deferred` analysis can be + #: run, while ``dict(datagram)``, :meth:`to_dict` and iteration still report the + #: field under its own name. #: See :class:`~pcapkit.corekit.packet.DeferredPacket`. __additional__ = ['packet'] #: Placeholder class :attr:`packet` may hold, for @@ -94,9 +95,10 @@ class Datagram(DeferredPacket, Info, Generic[_AT]): __deferred__ = Deferred #: How completely the datagram was reassembled, and why reassembly stopped. - #: Only :attr:`Completion.COMPLETE` is truthy, so ``if datagram.completed:`` - #: reads as it would for a :obj:`bool`; equality against :obj:`True` or - #: :obj:`False` does not hold. + #: Only :attr:`Completion.COMPLETE + #: ` is truthy, so + #: ``if datagram.completed:`` reads as it would for a :obj:`bool`; equality + #: against :obj:`True` or :obj:`False` does not hold. completed: 'Completion' #: Original packet identifier. id: 'DatagramID[_AT]' @@ -107,8 +109,8 @@ class Datagram(DeferredPacket, Info, Generic[_AT]): #: Reassembled IP payload. payload: 'bytes | tuple[bytes, ...]' #: Parsed IP payload. Analysed on first read, not at construction time; a - #: :class:`Deferred` may be passed in its place, and reading this attribute - #: then runs it and keeps the result. + #: :class:`~pcapkit.foundation.reassembly.data.data.Deferred` may be passed in its + #: place, and reading this attribute then runs it and keeps the result. packet: 'Optional[ProtocolBase]' #: Octet ranges, absolute into the reassembled payload and both #: **inclusive** -- the same convention as :attr:`Packet.fo` combined with diff --git a/pcapkit/foundation/reassembly/data/tcp.py b/pcapkit/foundation/reassembly/data/tcp.py index d84a8a23ee..cf199a7016 100644 --- a/pcapkit/foundation/reassembly/data/tcp.py +++ b/pcapkit/foundation/reassembly/data/tcp.py @@ -85,12 +85,13 @@ def __init__(self, src: 'tuple[_AT, int]', dst: 'tuple[_AT, int]', ack: 'int') - @info_final class Datagram(DeferredPacket, Info, Generic[_AT]): - """Data model for :term:`TCP `.""" + """Data model for :term:`TCP ` reassembled datagram.""" #: How completely the datagram was reassembled, and why reassembly stopped; #: see :class:`~pcapkit.foundation.reassembly.data.data.Completion`. Only - #: :attr:`Completion.COMPLETE` is truthy, so ``if datagram.completed:`` reads - #: as it would for a :obj:`bool`. + #: :attr:`Completion.COMPLETE + #: ` is truthy, so + #: ``if datagram.completed:`` reads as it would for a :obj:`bool`. completed: 'Completion' #: Listing ``packet`` here is what makes it lazy -- see #: :class:`~pcapkit.corekit.packet.DeferredPacket`. @@ -108,8 +109,8 @@ class Datagram(DeferredPacket, Info, Generic[_AT]): #: Reassembled payload (application layer data). payload: 'bytes | tuple[bytes, ...]' #: Parsed TCP payload. Analysed on first read rather than at construction; - #: a :class:`Deferred` may be passed in its place, and reading this - #: attribute then runs it and keeps the result. + #: a :class:`~pcapkit.foundation.reassembly.data.data.Deferred` may be passed in + #: its place, and reading this attribute then runs it and keeps the result. packet: 'Optional[ProtocolBase]' #: Sequence ranges on which two segments disagreed, i.e. where an arriving #: segment overlapped bytes already buffered but did not repeat them. @@ -186,11 +187,11 @@ class Fragment(Info): isn: 'int' #: Length of payload buffer. len: 'int' - #: Reassembled payload holes set to b'\x00'. + #: Reassembled payload, holes set to ``b'\x00'``. raw: 'bytearray' #: Sequence ranges, absolute and inclusive, still zero-fill placeholder in #: :attr:`raw` rather than an actually-received byte *of this fragment*. - #: Only the two gap-creating sites in :meth:`TCP.reassembly + #: Only the gap-creating sites in :meth:`TCP.reassembly #: ` -- the forward #: append and the reach-back prepend, the only places that ever splice a #: ``bytearray(GAP)`` filler into :attr:`raw` -- add an entry; an overlap @@ -216,12 +217,11 @@ class Fragment(Info): #: to have received something at the same absolute sequence numbers. #: #: Absolute sequence numbers rather than offsets into :attr:`raw` -- - #: :attr:`conflict` below uses the same convention -- for two reasons: - #: it is what :meth:`TCP.reassembly - #: ` already computes - #: (``GAP = PSN - (ISN + LEN)`` and its mirror), so this reuses an - #: existing concept rather than adding a second one; and it means a gap - #: entry never needs shifting when :attr:`isn` is revised downward by the + #: :attr:`conflict` below uses the same convention -- because it is what + #: :meth:`TCP.reassembly ` + #: already computes (``GAP = PSN - (ISN + LEN)`` and its mirror), so this + #: reuses an existing concept rather than adding a second one; and because a + #: gap entry then never needs shifting when :attr:`isn` is revised downward by the #: reach-back path, unlike an offset-based or a per-octet representation. #: A typical fragment carries zero or a handful of entries, against a #: per-octet marker the length of the whole payload -- the difference diff --git a/pcapkit/foundation/reassembly/ip.py b/pcapkit/foundation/reassembly/ip.py index a395237259..26638f1b62 100644 --- a/pcapkit/foundation/reassembly/ip.py +++ b/pcapkit/foundation/reassembly/ip.py @@ -50,7 +50,9 @@ class IP(ReassemblyBase[Packet[_AT], Datagram[_AT], BufferID, Buffer[_AT]], Gene :attr:`self._dtgram ` (if not, datagram will be discarded after callback) timeout: reassembly timeout in seconds, on the capture's own clock; - :data:`None` selects the protocol's :attr:`__timeout__` default + :data:`None` selects the protocol's + :attr:`__timeout__ ` + default Important: This class is not intended to be instantiated directly, @@ -391,7 +393,7 @@ def submit(self, buf: 'Buffer[_AT]', *, bufid: 'tuple[_AT, _AT, int, TransType]' bufid: buffer identifier checked: buffer consistency checked flag timeout: whether this buffer is being submitted because - :meth:`~pcapkit.foundation.reassembly.reassembly.ReassemblyBase.expire` + :meth:`~pcapkit.foundation.reassembly.reassembly.Reassembly.expire` abandoned it under the reassembly timeout, which is what separates :attr:`Completion.TIMEOUT ` diff --git a/pcapkit/foundation/reassembly/ipv4.py b/pcapkit/foundation/reassembly/ipv4.py index 88858324cc..aca3189187 100644 --- a/pcapkit/foundation/reassembly/ipv4.py +++ b/pcapkit/foundation/reassembly/ipv4.py @@ -67,8 +67,8 @@ class IPv4(IP): #: scheme is ``TIMER <- MAX(TIMER,TTL)`` seeded from a "Timer Lower Bound", #: for which "the current recommendation for the initial timer setting is 15 #: seconds" -- an initial lower bound that a later fragment's TTL raises, - #: up to the 4.25-minute TTL ceiling, not a 15-second deadline. Two things - #: rule it out here: the recommendation is superseded, and TTL is not in + #: up to the 4.25-minute TTL ceiling, not a 15-second deadline. It is ruled + #: out here: the recommendation is superseded, and TTL is not in #: :class:`~pcapkit.foundation.reassembly.data.ip.Packet` at all, so the #: fragment model would have to grow a field to express a scheme the current #: requirement asks implementations not to use. diff --git a/pcapkit/foundation/reassembly/ipv6.py b/pcapkit/foundation/reassembly/ipv6.py index 7615511ba9..6152d314e7 100644 --- a/pcapkit/foundation/reassembly/ipv6.py +++ b/pcapkit/foundation/reassembly/ipv6.py @@ -120,7 +120,7 @@ class IPv6(IP): #: is set once and never revised. The offset-zero fragment matters only to #: the second half of the rule, the ICMP Time Exceeded that a live stack #: "should" send and that an offline parser cannot -- see - #: :meth:`~pcapkit.foundation.reassembly.reassembly.ReassemblyBase.expire`, + #: :meth:`~pcapkit.foundation.reassembly.reassembly.Reassembly.expire`, #: which reports the condition in its log record instead. __timeout__ = 60.0 @@ -135,7 +135,7 @@ def _rectify_header(self, header: 'bytes', proto: 'TransType') -> 'bytes': the reassembled packet, and that the Next Header field of the last header of the unfragmentable part comes from the Fragment header's. Left alone, the reassembled datagram would advertise a Fragment header on a datagram - that is by definition no longer a fragment. The four toolkit adapters + that is by definition no longer a fragment. The toolkit adapters that build IPv6 reassembly data, :mod:`~pcapkit.toolkit.pcap`, :mod:`~pcapkit.toolkit.pcapng`, :mod:`~pcapkit.toolkit.dpkt` and :mod:`~pcapkit.toolkit.scapy`, leave the Fragment header's *octets* out diff --git a/pcapkit/foundation/reassembly/reassembly.py b/pcapkit/foundation/reassembly/reassembly.py index a4532109f6..604e904410 100644 --- a/pcapkit/foundation/reassembly/reassembly.py +++ b/pcapkit/foundation/reassembly/reassembly.py @@ -115,11 +115,12 @@ class ReassemblyBase(Generic[_PT, _DT, _IT, _BT], metaclass=ReassemblyMeta): contiguous payload, its holes zero-filled -- or, for IP while the total length is unknown, only the prefix up to the first hole store: if store reassembled datagram in memory, i.e., - :attr:`self._dtgram <_dtgram>` (if not, datagram - will be discarded after callback) + :attr:`self._dtgram ` + (if not, datagram will be discarded after callback) timeout: reassembly timeout in seconds, measured on the *capture's* clock; :data:`None` selects the protocol's - own :attr:`__timeout__` default + own :attr:`__timeout__ ` + default Note: This class is for internal use only. For customisation, please use @@ -583,7 +584,7 @@ def __init_subclass__(cls, /, protocol: 'Optional[str]' = None, *args: 'Any', ** Note: :attr:`__protocol_name__` is *not* an opt-in. It supplies the - :attr:`name ` + :attr:`name ` the reassembly reports, which it does whether or not the class is registered; only the keyword decides registration. diff --git a/pcapkit/foundation/reassembly/tcp.py b/pcapkit/foundation/reassembly/tcp.py index b936dcc126..23351b0718 100644 --- a/pcapkit/foundation/reassembly/tcp.py +++ b/pcapkit/foundation/reassembly/tcp.py @@ -597,8 +597,8 @@ def _merge_overlap(gap: 'list[tuple[int, int]]', old: 'bytes', new: 'bytes', old: already-buffered bytes of this fragment over the range. new: the arriving segment's bytes over the same range. start: absolute sequence number of ``old[0]``/``new[0]``, which - cover the same range by construction -- see the two call - sites, :meth:`_reassemble_append` and :meth:`_reassemble_prepend`. + cover the same range by construction -- see the call sites, + :meth:`_reassemble_append` and :meth:`_reassemble_prepend`. Returns: The bytes to keep for the range, and any ``(first, last)`` @@ -662,7 +662,7 @@ def submit(self, buf: 'Buffer', *, bufid: 'BufferID', # type: ignore[override] buf: :term:`buffer ` dict of reassembled packets bufid: buffer identifier timeout: whether this buffer is being submitted because - :meth:`~pcapkit.foundation.reassembly.reassembly.ReassemblyBase.expire` + :meth:`~pcapkit.foundation.reassembly.reassembly.Reassembly.expire` abandoned it under the reassembly timeout Returns: diff --git a/pcapkit/foundation/registry/foundation.py b/pcapkit/foundation/registry/foundation.py index d22eae4838..881721e2d2 100644 --- a/pcapkit/foundation/registry/foundation.py +++ b/pcapkit/foundation/registry/foundation.py @@ -70,8 +70,8 @@ def register_extractor_engine(name: 'str', module: 'ModuleDescriptor[Engine] | T r"""Register a new engine class. Notes: - The full qualified class name of the new engine class - should be as ``{module}.{class_}``. + The fully qualified class name of the new engine class + should be ``{module}.{class_}``. The function will register the given engine class to the :data:`pcapkit.foundation.extraction.Extractor.__engine__` registry. @@ -113,8 +113,8 @@ def register_dumper(format: 'str', module: 'ModuleDescriptor[Dumper] | Type[Dump r"""Register a new dumper class. Notes: - The full qualified class name of the new dumper class - should be as ``{module}.{class_}``. + The fully qualified class name of the new dumper class + should be ``{module}.{class_}``. The function will register the given dumper class to the :data:`pcapkit.foundation.traceflow.traceflow.TraceFlow.__output__` and @@ -156,8 +156,8 @@ def register_extractor_dumper(format: 'str', module: 'ModuleDescriptor[Dumper] | r"""Register a new dumper class. Notes: - The full qualified class name of the new dumper class - should be as ``{module}.{class_}``. + The fully qualified class name of the new dumper class + should be ``{module}.{class_}``. The function will register the given dumper class to the :data:`pcapkit.foundation.extraction.Extractor.__output__` registry. @@ -193,8 +193,8 @@ def register_traceflow_dumper(format: 'str', module: 'ModuleDescriptor[Dumper] | r"""Register a new dumper class. Notes: - The full qualified class name of the new dumper class - should be as ``{module}.{class_}``. + The fully qualified class name of the new dumper class + should be ``{module}.{class_}``. The function will register the given dumper class to the :data:`pcapkit.foundation.traceflow.traceflow.TraceFlow.__output__` registry. @@ -304,8 +304,8 @@ def register_extractor_reassembly(protocol: 'str', module: 'str | ModuleDescript r"""Register a new reassembly class. Notes: - The full qualified class name of the new reassembly class - should be as ``{module}.{class_}``. + The fully qualified class name of the new reassembly class + should be ``{module}.{class_}``. The function will register the given reassembly class to the :data:`pcapkit.foundation.extraction.Extractor.__reassembly__` registry. @@ -339,8 +339,8 @@ def register_extractor_traceflow(protocol: 'str', module: 'str | ModuleDescripto r"""Register a new flow tracing class. Notes: - The full qualified class name of the new flow tracing class - should be as ``{module}.{class_}``. + The fully qualified class name of the new flow tracing class + should be ``{module}.{class_}``. The function will register the given flow tracing class to the :data:`pcapkit.foundation.extraction.Extractor.__traceflow__` registry. diff --git a/pcapkit/foundation/registry/protocols.py b/pcapkit/foundation/registry/protocols.py index a2d0b9e9f5..b78eea4634 100644 --- a/pcapkit/foundation/registry/protocols.py +++ b/pcapkit/foundation/registry/protocols.py @@ -159,11 +159,11 @@ def register_protocol(protocol: 'Type[ProtocolBase]') -> 'None': the :data:`pcapkit.protocols.__proto__` registry. The registry is keyed on ``protocol.__name__.upper()``, which is **not** - unique across this package: three dispatchable protocol classes are all + unique across this package: several dispatchable protocol classes are named ``HTTP`` -- the generic base - :class:`pcapkit.protocols.application.http.HTTP` and the two version + :class:`pcapkit.protocols.application.http.HTTP` and the version implementations :class:`pcapkit.protocols.application.httpv1.HTTP` and - :class:`pcapkit.protocols.application.httpv2.HTTP` -- so all three compete + :class:`pcapkit.protocols.application.httpv2.HTTP` -- so they all compete for the single key ``'HTTP'``. Registering one of them replaces whichever was there, and the replacement is observable through every reader of the registry, e.g. :meth:`ProtocolBase.expand_comp @@ -286,7 +286,7 @@ def __init__(self, name: 'str', protocol: 'Type[ProtocolBase]', displaced: 'Any' _NAME_LEASES = {} # type: dict[str, list[_NameLease]] #: ``(destination, code)`` -> the lease its registration holds. _CODE_LEASES = {} # type: dict[tuple[type, Any], _NameLease] -#: Serialises the two tables above, so concurrent registrars cannot tear them. +#: Serialises the lease tables above, so concurrent registrars cannot tear them. _LEASE_LOCK = threading.RLock() @@ -404,16 +404,17 @@ def _register_name(protocol: 'Type[ProtocolBase]', *targets: 'tuple[type, Any]') _CODE_LEASES[target] = lease -#: Enum type -> the class(es) owning the :attr:`ProtocolBase.__proto__ -#: ` dispatch registry -#: keyed by that enum type -- the "registry-of-registries" that lets -#: ``code=`` infer a destination from a key's own type, per :issue:`514`. It is -#: the same targeting -#: :func:`register_ethertype`, :func:`register_transtype`, -#: :func:`register_linktype` and :func:`register_sctp` hard-code by -#: hand, in one table so :func:`register_protocol_code` can consult -#: it. ``LinkType`` naming two classes is deliberate, not ambiguous -- -#: :func:`register_linktype` already fans out to both. +#: Destination classes for each enum type a ``code=`` key can carry. +#: +#: Maps an enum type to the class(es) owning the +#: :attr:`ProtocolBase.__proto__ ` +#: dispatch registry keyed by that enum type -- the "registry-of-registries" that +#: lets ``code=`` infer a destination from a key's own type, per :issue:`514`. It +#: is the same targeting :func:`register_ethertype`, :func:`register_transtype`, +#: :func:`register_linktype` and :func:`register_sctp` hard-code by hand, in one +#: table so :func:`register_protocol_code` can consult it. ``LinkType`` naming +#: more than one class is deliberate, not ambiguous -- :func:`register_linktype` +#: already fans out to each of them. _CODE_DESTINATIONS: 'dict[type, tuple[Type[ProtocolBase], ...]]' = { Enum_EtherType: (Link,), Enum_TransType: (Internet,), @@ -523,8 +524,8 @@ def register_protocol_code(protocol: 'Type[ProtocolBase]', code: 'Any') -> 'None code: Registration key(s); see above. Raises: - RegistryError: If a bare :class:`int` (or any other non-enum, non- - mapping value) is given without an explicit destination, or if + RegistryError: If a bare :class:`int` (or any other non-enum, + non-mapping value) is given without an explicit destination, or if an enum member's type names no known destination registry -- inference refuses rather than guesses. @@ -551,8 +552,8 @@ def register_linktype(code: 'LinkType', module: 'str | ModuleDescriptor[Protocol r"""Register a new protocol class. Notes: - The full qualified class name of the new protocol class - should be as ``{module}.{class_}``. + The fully qualified class name of the new protocol class + should be ``{module}.{class_}``. The function will register the given protocol class to the following registries: @@ -596,8 +597,8 @@ def register_pcap(code: 'LinkType', module: 'str | ModuleDescriptor[ProtocolBase r"""Register a new protocol class. Notes: - The full qualified class name of the new protocol class - should be as ``{module}.{class_}``. + The fully qualified class name of the new protocol class + should be ``{module}.{class_}``. The function will register the given protocol class to the :data:`pcapkit.protocols.misc.pcap.frame.Frame.__proto__` registry. @@ -633,8 +634,8 @@ def register_pcapng(code: 'LinkType', module: 'str | ModuleDescriptor[ProtocolBa r"""Register a new protocol class. Notes: - The full qualified class name of the new protocol class - should be as ``{module}.{class_}``. + The fully qualified class name of the new protocol class + should be ``{module}.{class_}``. The function will register the given protocol class to the :data:`pcapkit.protocols.misc.pcapng.PCAPNG.__proto__` registry. @@ -675,8 +676,8 @@ def register_ethertype(code: 'EtherType', module: 'str | ModuleDescriptor[Protoc r"""Register a new protocol class. Notes: - The full qualified class name of the new protocol class - should be as ``{module}.{class_}``. + The fully qualified class name of the new protocol class + should be ``{module}.{class_}``. The function will register the given protocol class to the :data:`pcapkit.protocols.link.link.Link.__proto__` registry. @@ -717,8 +718,8 @@ def register_transtype(code: 'TransType', module: 'str | ModuleDescriptor[Protoc r"""Register a new protocol class. Notes: - The full qualified class name of the new protocol class - should be as ``{module}.{class_}``. + The fully qualified class name of the new protocol class + should be ``{module}.{class_}``. The function will register the given protocol class to the :data:`pcapkit.protocols.internet.internet.Internet.__proto__` registry. @@ -870,14 +871,14 @@ def register_ipv6_route_routing(code: 'IPv6_Routing', meth: 'str | tuple[IPv6_Ro # NOTE: pcapkit.protocols.internet.mh.MH.__message__ def register_mh_message(code: 'MH_Packet', meth: 'str | tuple[MH_PacketParser, MH_PacketConstructor]', *, schema: 'Optional[Type[Schema_MH_Packet]]' = None) -> 'None': - """Register a MH message type parser. + """Register an MH message type parser. The function will register the given message type parser to the :data:`pcapkit.protocols.internet.mh.MH.__message__` registry. Args: code: :class:`~pcapkit.protocols.internet.mh.MH` - data type code as in :class:`~pcapkit.const.mh.packet.Packet`. + message type code as in :class:`~pcapkit.const.mh.packet.Packet`. meth: Method name or callable to parse and/or construct the data. schema: :class:`~pcapkit.protocols.schema.schema.Schema` class for the message type. It should be a subclass of :class:`pcapkit.protocols.schema.internet.mh.Packet`. @@ -895,16 +896,16 @@ def register_mh_message(code: 'MH_Packet', meth: 'str | tuple[MH_PacketParser, M # NOTE: pcapkit.protocols.internet.mh.MH.__option__ def register_mh_option(code: 'MH_Option', meth: 'str | tuple[MH_OptionParser, MH_OptionConstructor]', *, schema: 'Optional[Type[Schema_MH_Option]]' = None) -> 'None': - """Register a MH option parser. + """Register an MH option parser. The function will register the given option parser to the :data:`pcapkit.protocols.internet.mh.MH.__option__` registry. Args: code: :class:`~pcapkit.protocols.internet.mh.MH` - data type code as in :class:`~pcapkit.const.mh.option.Option`. + option code as in :class:`~pcapkit.const.mh.option.Option`. meth: Method name or callable to parse and/or construct the data. - schema: :class:`~pcapkit.protocols.schema.schema.Schema` class for the message type. + schema: :class:`~pcapkit.protocols.schema.schema.Schema` class for the option. It should be a subclass of :class:`pcapkit.protocols.schema.internet.mh.Option`. """ @@ -922,14 +923,14 @@ def register_mh_extension(code: 'MH_CGAExtension', meth: 'str | tuple[MH_Extensi schema: 'Optional[Type[Schema_MH_CGAExtension]]' = None) -> 'None': """Register a CGA extension parser. - The function will register the given CGA extension to the + The function will register the given CGA extension parser to the :data:`pcapkit.protocols.internet.mh.MH.__extension__` registry. Args: code: :class:`~pcapkit.protocols.internet.mh.MH` - data type code as in :class:`~pcapkit.const.mh.cga_extension.CGAExtension`. + CGA extension code as in :class:`~pcapkit.const.mh.cga_extension.CGAExtension`. meth: Method name or callable to parse and/or construct the data. - schema: :class:`~pcapkit.protocols.schema.schema.Schema` class for the message type. + schema: :class:`~pcapkit.protocols.schema.schema.Schema` class for the CGA extension. It should be a subclass of :class:`pcapkit.protocols.schema.internet.mh.CGAExtension`. .. versionchanged:: 1.5.0 @@ -968,8 +969,8 @@ def register_apptype(code: 'int | Enum_AppType', module: 'str | ModuleDescriptor r"""Register a new protocol class. Notes: - The full qualified class name of the new protocol class - should be as ``{module}.{class_}``. + The fully qualified class name of the new protocol class + should be ``{module}.{class_}``. The function will register the given protocol class to the :data:`pcapkit.protocols.transport.tcp.TCP.__proto__` and/or @@ -1030,12 +1031,12 @@ def register_apptype(code: 'int | Enum_AppType', module: 'str | ModuleDescriptor keyed by the DATA chunk's payload protocol identifier rather than by port number, so writing a port number into it would dispatch on a number from the wrong registry. Use - :func:`pcapkit.foundation.registry.register_sctp` instead. + :func:`pcapkit.foundation.registry.protocols.register_sctp` instead. See Also: - * :func:`pcapkit.foundation.registry.register_tcp` - * :func:`pcapkit.foundation.registry.register_udp` - * :func:`pcapkit.foundation.registry.register_sctp` + * :func:`pcapkit.foundation.registry.protocols.register_tcp` + * :func:`pcapkit.foundation.registry.protocols.register_udp` + * :func:`pcapkit.foundation.registry.protocols.register_sctp` .. versionremoved:: 1.3.1 @@ -1085,7 +1086,7 @@ def register_apptype(code: 'int | Enum_AppType', module: 'str | ModuleDescriptor # being an ``IntEnum`` rather than an ``IntFlag`` changes neither -- so # ``registries.get(1)`` resolves to ``TCP`` just as # ``registries.get(TransportProtocol.tcp)`` does, and would otherwise - # register the port before the ``proto.name`` access two lines below it + # register the port before the ``proto.name`` access below it # raised -- leaving the port written into ``TCP.__proto__`` with # ``register_protocol`` never called. coerced: 'list[TransportProtocol]' = [] @@ -1155,8 +1156,8 @@ def register_tcp(code: 'int | Enum_AppType', module: 'str | ModuleDescriptor[Pro r"""Register a new protocol class. Notes: - The full qualified class name of the new protocol class - should be as ``{module}.{class_}``. + The fully qualified class name of the new protocol class + should be ``{module}.{class_}``. The function will register the given protocol class to the :data:`pcapkit.protocols.transport.tcp.TCP.__proto__` registry. @@ -1244,8 +1245,8 @@ def register_udp(code: 'int | Enum_AppType', module: 'str | ModuleDescriptor[Pro r"""Register a new protocol class. Notes: - The full qualified class name of the new protocol class - should be as ``{module}.{class_}``. + The fully qualified class name of the new protocol class + should be ``{module}.{class_}``. The function will register the given protocol class to the :data:`pcapkit.protocols.transport.udp.UDP.__proto__` registry. @@ -1283,8 +1284,8 @@ def register_sctp(code: 'int | SCTP_PayloadProtocolIdentifier', module: 'str | M r"""Register a new protocol class. Notes: - The full qualified class name of the new protocol class - should be as ``{module}.{class_}``. + The fully qualified class name of the new protocol class + should be ``{module}.{class_}``. The function will register the given protocol class to the :data:`pcapkit.protocols.transport.sctp.SCTP.__proto__` registry. diff --git a/pcapkit/foundation/traceflow/data/data.py b/pcapkit/foundation/traceflow/data/data.py index 00fa6517f2..d36545722e 100644 --- a/pcapkit/foundation/traceflow/data/data.py +++ b/pcapkit/foundation/traceflow/data/data.py @@ -33,11 +33,11 @@ class Deferred: Deliberately not :class:`pcapkit.foundation.reassembly.data.data.Deferred`, and not shared with it. That one postpones a single ``analyze()`` call over bytes already - in hand; this postpones a *submit* over a reassembler's buffers. The two - subpackages are siblings and neither should depend on the other, so the - few lines are written twice rather than one importing the other -- the - same reason the two ``data/data.py`` modules mirror each other instead of - merging. + in hand; this postpones a *submit* over a reassembler's buffers. The + reassembly and flow tracing subpackages are siblings and neither should + depend on the other, so the few lines are written in each rather than one + importing the other -- the same reason their ``data/data.py`` modules + mirror each other instead of merging. Args: reassembly: The flow's own diff --git a/pcapkit/foundation/traceflow/data/tcp.py b/pcapkit/foundation/traceflow/data/tcp.py index f1b082a2a9..4ef253bf3f 100644 --- a/pcapkit/foundation/traceflow/data/tcp.py +++ b/pcapkit/foundation/traceflow/data/tcp.py @@ -41,10 +41,10 @@ @info_final class Packet(Info, Generic[_AT]): - """Data structure for **TCP flow tracing**. + """Data structure for a packet submitted to **TCP flow tracing**. See Also: - * :meth:`pcapkit.foundation.traceflow.TraceFlow.dump` + * :meth:`pcapkit.foundation.traceflow.traceflow.TraceFlow.dump` * :term:`trace.tcp.packet` """ @@ -95,10 +95,10 @@ def __init__(self, protocol: 'Enum_LinkType', index: 'int', frame: 'Data_Frame | @info_final class Buffer(Info, Generic[_AT]): - """Data structure for **TCP flow tracing**. + """Data structure for a **TCP flow tracing** buffer. See Also: - * :attr:`pcapkit.foundation.traceflow.TraceFlow.index` + * :attr:`pcapkit.foundation.traceflow.traceflow.TraceFlow.index` * :term:`trace.tcp.buffer` """ @@ -149,10 +149,10 @@ def __init__(self, fpout: 'Dumper', index: 'list[int]', label: 'str', origin: 't @info_final class Index(DeferredPacket, Info): - """Data structure for **TCP flow tracing**. + """Data structure for a traced **TCP flow**, as an index entry. See Also: - * element from :attr:`pcapkit.foundation.traceflow.TraceFlow.index` + * element from :attr:`pcapkit.foundation.traceflow.traceflow.TraceFlow.index` *tuple* * :term:`trace.tcp.index` @@ -180,15 +180,15 @@ class Index(DeferredPacket, Info): #: Empty when tracing unidirectionally, in which case #: :attr:`forward` ``==`` :attr:`index`. reverse: 'tuple[int, ...]' - #: The conversation's **application layer**: its reassembled datagrams, one + #: Reassembled datagrams of the conversation's **application layer**, one #: per direction and acknowledgement number, or :data:`None` when the tracer #: was not asked for it (``analyse=False``, the default). #: #: Reassembled on the first read, not when the flow is finalised, and each #: datagram's own #: :attr:`~pcapkit.foundation.reassembly.data.tcp.Datagram.packet` is parsed - #: later still -- two layers of the same postponement, so a caller that only - #: wanted frame numbers pays for neither. + #: later still -- the same postponement at each layer, so a caller that only + #: wanted frame numbers pays for none of it. packet: 'Optional[tuple[TCP_Datagram, ...]]' if TYPE_CHECKING: diff --git a/pcapkit/foundation/traceflow/tcp.py b/pcapkit/foundation/traceflow/tcp.py index 9a1dd7bce2..d40d0b7243 100644 --- a/pcapkit/foundation/traceflow/tcp.py +++ b/pcapkit/foundation/traceflow/tcp.py @@ -53,7 +53,7 @@ class TCP(TraceFlowBase[BufferID, Buffer[_AT], Index, Packet[_AT]], Generic[_AT] server's replies in separate flows, separate labels and separate output files, leaving a caller to pair them up by inspecting the labels. - Two consequences are worth knowing: + This has consequences worth knowing: * The reverse half of a conversation does not produce a flow of its own, so a capture of *n* connections yields *n* flows rather than @@ -493,9 +493,8 @@ def _finalise(self, bufid: 'BufferID') -> 'Index': def finish(self) -> 'None': """Finalise every flow still being traced. - The end of the capture is the second of the two things that can prove a - bidirectional flow is over -- the first being a new connection on the same - endpoints. Draining the buffer here is what lets a flow keep the + The end of the capture can prove a bidirectional flow is over, as a new + connection on the same endpoints can. Draining the buffer here is what lets a flow keep the acknowledgement that completes its close and *still* have its callback fired, rather than having to choose between the two. @@ -515,7 +514,7 @@ def submit(self) -> 'tuple[Index, ...]': Note: This reports flows still being traced **without** finalising them, so that reading - :attr:`TraceFlow.index ` + :attr:`TraceFlow.index ` part-way through a capture cannot disturb the tracing -- popping a buffer there would strand the rest of its conversation in a second flow. Such a flow is therefore reported here but has not fired its @@ -530,15 +529,18 @@ def submit(self) -> 'tuple[Index, ...]': not as it will stand once the conversation ends. Read it while the flow is open and the datagrams cover only the segments seen so far; segments arriving afterwards are reassembled into the buffer but cannot reach an - already-resolved snapshot, and nothing on the returned :class:`Index` + already-resolved snapshot, and nothing on the returned + :class:`~pcapkit.foundation.traceflow.data.tcp.Index` distinguishes one from the final result that :meth:`finish` produces. This call's own cache is not the cause and does not soften it: both :meth:`trace` and :meth:`finish` clear ``__cached__['submit']``, so a later call rebuilds the tuple with fresh :class:`~pcapkit.foundation.traceflow.data.data.Deferred` objects. It is - the *previously returned* :class:`Index`, if a caller kept one, that - cannot catch up. For a result that is final, read after :meth:`finish`. + the *previously returned* + :class:`~pcapkit.foundation.traceflow.data.tcp.Index`, if a caller kept + one, that cannot catch up. For a result that is final, read after + :meth:`finish`. """ if (cached := self.__cached__.get('submit')) is not None: diff --git a/pcapkit/foundation/traceflow/traceflow.py b/pcapkit/foundation/traceflow/traceflow.py index 865034e14f..e232a34e63 100644 --- a/pcapkit/foundation/traceflow/traceflow.py +++ b/pcapkit/foundation/traceflow/traceflow.py @@ -144,10 +144,9 @@ class TraceFlowBase(Generic[_DT, _BT, _IT, _PT], metaclass=TraceFlowMeta): # Defaults. ########################################################################## - #: DefaultDict[str, tuple[ModuleDescriptor[Dumper] | ~typing.Type[Dumper], str | None]]: - #: Format dumper mapping for writing output files. The values should be a - #: tuple representing the module name and class name, or a - #: :class:`dictdumper.dumper.Dumper` subclass, and corresponding file extension. + #: Format dumper mapping for writing output files. Each value pairs the dumper, + #: as a module descriptor or a :class:`dictdumper.dumper.Dumper` subclass, with + #: the file extension of its format. __output__ = collections.defaultdict( lambda: (ModuleDescriptor('pcapkit.dumpkit', 'NotImplementedIO'), None), { @@ -213,8 +212,8 @@ def register_dumper(cls, format: 'str', dumper: 'ModuleDescriptor[Dumper] | Type r"""Register a new dumper class. Notes: - The full qualified class name of the new dumper class - should be as ``{dumper.module}.{dumper.name}``. + The fully qualified class name of the new dumper class + should be ``{dumper.module}.{dumper.name}``. The overwrite guard fires only when the incumbent dumper differs from the replacement, so re-registering the exact same object is @@ -242,6 +241,13 @@ class it names, restores the shipped entry rather than storing dumper: module descriptor or a :class:`dictdumper.dumper.Dumper` subclass ext: file extension + Raises: + RegistryError: If ``dumper`` is not a class, or not a ``Dumper`` subclass. + + Warns: + RegistryWarning: If a different dumper is already registered for + ``format``; it is overwritten. + .. versionremoved:: 1.2.0 ``register``, which this replaces with no alias. @@ -561,7 +567,7 @@ def __init_subclass__(cls, /, protocol: 'Optional[str]' = None, *args: 'Any', ** Note: :attr:`__protocol_name__` is *not* an opt-in. It supplies the - :attr:`name ` + :attr:`name ` the class reports, which it does whether or not the class is registered; only the keyword decides registration. diff --git a/pcapkit/interface/core.py b/pcapkit/interface/core.py index 5a58cf420c..f84004ec1f 100644 --- a/pcapkit/interface/core.py +++ b/pcapkit/interface/core.py @@ -98,7 +98,8 @@ def extract(fin: 'Optional[str | IO[bytes]]' = None, fout: 'Optional[str]' = Non files: if split each frame into different files nofile: if no output file is to be dumped - verbose: a :obj:`bool` value or a function takes the :class:`Extractor` + verbose: a :obj:`bool` value or a function takes the + :class:`~pcapkit.foundation.extraction.Extractor` instance and current parsed frame (depends on engine selected) as parameters to print verbose output information @@ -232,7 +233,7 @@ def trace(protocol: 'str | Type[ProtocolBase]', fout: 'Optional[str]', """Trace flows. Arguments: - protocol: protocol to be reassembled + protocol: protocol whose flows are to be traced fout: output path format: output format byteorder: output file byte order diff --git a/pcapkit/interface/misc.py b/pcapkit/interface/misc.py index 2b1f0d828e..1bfea72628 100644 --- a/pcapkit/interface/misc.py +++ b/pcapkit/interface/misc.py @@ -37,16 +37,19 @@ ReassemblyAdapter = Callable[..., 'Optional[TCP_Data]'] ByteOrder = Literal['little', 'big'] - #: Every key registered in :attr:`Extractor.__output__ - #: ` and in + #: Every output format key the dumper registries ship with. + #: + #: The keys are those of :attr:`Extractor.__output__ + #: ` and of #: :attr:`TraceFlowBase.__output__ - #: ` -- the two - #: registries expose the same eight keys, which resolve to four dumpers (``'cap'`` to - #: ``'pcap'``, ``'xml'`` to ``'plist'``, ``'text'`` and ``'txt'`` to ``'tree'``), so the - #: ``Literal`` lists all eight for a type checker to accept every spelling the runtime does. + #: ` -- both + #: registries expose the same keys, the aliases among them resolving to a shared + #: dumper (``'cap'`` to ``'pcap'``, ``'xml'`` to ``'plist'``, ``'text'`` and ``'txt'`` + #: to ``'tree'``), so the ``Literal`` lists every key for a type checker to accept + #: every spelling the runtime does. Formats = Literal['pcap', 'cap', 'json', 'tree', 'text', 'txt', 'plist', 'xml'] # NOTE: this alias duplicates the one in ``pcapkit.foundation.extraction``; - # both copies need updating when a new engine lands. + # each copy needs updating when a new engine lands. Engines = Literal['default', 'pcapkit', 'dpkt', 'scapy', 'pyshark', 'pypcap', 'pcap_ct', 'pypcapfile'] @@ -100,8 +103,8 @@ def follow_tcp_stream(fin: 'Optional[str]' = None, verbose: 'bool' = False, layer, so that ``Index.packet`` can be read off :attr:`Extractor.trace `. Off by default, and independent of the ``conversations`` this - function returns -- those come from the reassembly below, which runs - either way. + function returns -- those come from the TCP reassembly this function + always runs. Returns: Tuple of extracted TCP streams. @@ -148,7 +151,7 @@ def follow_tcp_stream(fin: 'Optional[str]' = None, verbose: 'bool' = False, exeng = extraction.engine tcp_reassembly = None # type: Optional[ReassemblyAdapter] pass_count = True - #: Reads a frame's capture timestamp back off the frame, for the one adapter + #: Reads a frame's capture timestamp back off the frame, for an adapter #: whose signature takes it. :data:`None` for the adapters that find it #: themselves. timestamp_of = None # type: Optional[Callable[[Any], float]] diff --git a/pcapkit/toolkit/dpkt.py b/pcapkit/toolkit/dpkt.py index f7cdd62bb0..bde5196dc1 100644 --- a/pcapkit/toolkit/dpkt.py +++ b/pcapkit/toolkit/dpkt.py @@ -83,13 +83,14 @@ class RecordTimestamp: :func:`packet2frame`. The engine's readers yield a :class:`Timestamp` or a :class:`DecimalTimestamp` instead, which *is* the value `DPKT`_ yields -- so a stored frame's timestamp is exactly what it was before -- and also - carries those three. Reassembly and flow tracing are handed a plain + carries what would otherwise be lost. Reassembly and flow tracing are handed a plain :obj:`float` of it (:issue:`1501`): of its exact value, since `DPKT`_'s own for a microsecond PCAP rounds twice (:issue:`1668`; see :func:`_capture_time`). A copy, a deep copy and a pickle at any protocol all rebuild a timestamp - from what its :meth:`~object.__reduce__` returns, so all keep the three. + from what its :meth:`~object.__reduce__` returns, so each keeps the exact + timestamp, the resolution and the original length. This class answers each of those protocols itself, ahead of the numeric base class: the pure-Python :class:`~decimal.Decimal` copies a subclass as ``type(self)(str(self))``, with the constructor's defaults for the rest @@ -486,7 +487,7 @@ def ipv6_hdr_len(ipv6: 'IP6') -> 'int': Routing header :class:`dpkt.ip6.IP6RoutingHeader` (:rfc:`2460#section-4.4`). An Authentication header (:rfc:`4302`) reaches here as well, though, so the headers are walked in the order they were parsed rather than looked up by a fixed list of types. - Walking them is also what counts a header type occurring twice twice, and what leaves + Walking them is also what counts a repeated header type once per occurrence, and what leaves out one that sits *after* the Fragment header rather than before it. """ @@ -528,9 +529,9 @@ def packet2dict(packet: 'Packet', timestamp: 'float', *, data_link: Data link type. Returns: - Dict[str, Any]: A :obj:`dict` mapping of packet data. A layer's payload - is mapped as a layer of its own only where it is one, i.e. has the - header fields a :class:`dpkt.dpkt.Packet` has; any other is leaf data. + A :obj:`dict` mapping of packet data. A layer's payload is mapped as a + layer of its own only where it is one, i.e. has the header fields a + :class:`dpkt.dpkt.Packet` has; any other is leaf data. `DPKT`_ leaves some undecoded payloads as a :obj:`str` rather than as :obj:`bytes`, e.g. ``''`` for VRRP's. A frame the engine keeps as raw data, as it keeps one `DPKT`_ cannot decode, has no header fields, so @@ -559,6 +560,10 @@ def ipv4_reassembly(packet: 'Packet', timestamp: 'float', *, count: 'int' = -1) -> 'IP_Packet[IPv4Address] | None': """Make data for IPv4 reassembly. + A packet can be reassembled if its network layer is IPv4 (:class:`dpkt.ip.IP`) -- + one carried in IPv6 is not read -- whose header was captured whole, and the **DF** + (:attr:`dpkt.ip.IP.df`) flag is :data:`False`. + Args: packet: DPKT packet. timestamp: Capture timestamp of the packet, which drives the reassembly @@ -572,17 +577,11 @@ def ipv4_reassembly(packet: 'Packet', timestamp: 'float', *, and as the one nearest the exact value it carries, if any, where `DPKT`_'s own may be an ulp off (:issue:`1668`; see :func:`_capture_time`). - count: Packet index. If not provided, default to ``-1``. + count: Packet index. If not provided, defaults to ``-1``. Returns: - Data for IPv4 reassembly. - - * If the ``packet`` can be used for IPv4 reassembly. A packet can be reassembled - if its network layer is IPv4 (:class:`dpkt.ip.IP`) -- one carried in IPv6 is not - read -- whose header was captured whole, and the **DF** (:attr:`dpkt.ip.IP.df`) - flag is :data:`False`. - * If the ``packet`` can be reassembled, then the :obj:`dict` mapping of data for IPv4 - reassembly (:term:`reasm.ipv4.packet`) will be returned; otherwise, returns :data:`None`. + Data for IPv4 reassembly (:term:`reasm.ipv4.packet`) if the ``packet`` can be + reassembled; otherwise :data:`None`. Note: The header and payload are sliced out of the record octets @@ -656,22 +655,20 @@ def ipv6_reassembly(packet: 'Packet', timestamp: 'float', *, count: 'int' = -1) -> 'IP_Packet[IPv6Address] | None': """Make data for IPv6 reassembly. + A packet can be reassembled if its network layer is IPv6 (:class:`dpkt.ip6.IP6`) -- + one carried in IPv4 is not read -- and has an IPv6 Fragment header + (:rfc:`2460#section-4.5`, i.e., :class:`dpkt.ip6.IP6FragmentHeader`). + Args: packet: DPKT packet. timestamp: Capture timestamp of the packet, which drives the reassembly timeout; :func:`packet2timestamp` reads it back off a stored frame. Handed on as a :obj:`float`, as for :func:`ipv4_reassembly`. - count: Packet index. If not provided, default to ``-1``. + count: Packet index. If not provided, defaults to ``-1``. Returns: - Data for IPv6 reassembly. - - * If the ``packet`` can be used for IPv6 reassembly. A packet can be reassembled - if its network layer is IPv6 (:class:`dpkt.ip6.IP6`) -- one carried in IPv4 is not - read -- and has IPv6 Fragment header (:rfc:`2460#section-4.5`, i.e., - :class:`dpkt.ip6.IP6FragmentHeader`). - * If the ``packet`` can be reassembled, then the :obj:`dict` mapping of data for IPv6 - reassembly (:term:`reasm.ipv6.packet`) will be returned; otherwise, returns :data:`None`. + Data for IPv6 reassembly (:term:`reasm.ipv6.packet`) if the ``packet`` can be + reassembled; otherwise :data:`None`. Note: The header and payload are sliced out of the record octets @@ -770,22 +767,21 @@ def tcp_reassembly(packet: 'Packet', timestamp: 'float', *, count: 'int' = -1) -> 'TCP_Packet | None': """Make data for TCP reassembly. + A packet can be reassembled if its network layer carries a TCP layer + (:class:`dpkt.tcp.TCP`), behind any IPv6 extension headers :mod:`dpkt` decodes. TCP + in an IP-in-IP tunnel, or behind an AH or IPv6 extension header carried in IPv4, is + not read. + Args: packet: DPKT packet. timestamp: Capture timestamp of the packet, which drives the reassembly timeout; :func:`packet2timestamp` reads it back off a stored frame. Handed on as a :obj:`float`, as for :func:`ipv4_reassembly`. - count: Packet index. If not provided, default to ``-1``. + count: Packet index. If not provided, defaults to ``-1``. Returns: - Data for TCP reassembly. - - * If the ``packet`` can be used for TCP reassembly. A packet can be reassembled - if its network layer carries TCP layer (:class:`dpkt.tcp.TCP`), behind any IPv6 - extension headers :mod:`dpkt` decodes. TCP in an IP-in-IP tunnel, or behind an AH or - IPv6 extension header carried in IPv4, is not read. - * If the ``packet`` can be reassembled, then the :obj:`dict` mapping of data for TCP - reassembly (:term:`reasm.tcp.packet`) will be returned; otherwise, returns :data:`None`. + Data for TCP reassembly (:term:`reasm.tcp.packet`) if the ``packet`` can be + reassembled; otherwise :data:`None`. Note: The header and payload are sliced out of the record octets @@ -847,6 +843,11 @@ def tcp_traceflow(packet: 'Packet', timestamp: 'float', *, data_link: 'Enum_LinkType', count: 'int' = -1) -> 'TF_TCP_Packet | None': """Trace packet flow for TCP. + A packet can be traced if its network layer carries a TCP layer + (:class:`dpkt.tcp.TCP`), behind any IPv6 extension headers :mod:`dpkt` decodes. TCP + in an IP-in-IP tunnel, or behind an AH or IPv6 extension header carried in IPv4, is + not read. + Args: packet: DPKT packet. timestamp: Timestamp of the packet, handed on as a :obj:`float`, as for @@ -854,17 +855,11 @@ def tcp_traceflow(packet: 'Packet', timestamp: 'float', *, exact timestamp attached by :func:`attach_timestamp`, if any; see :func:`packet2frame`. data_link: Data link layer protocol (from global header). - count: Packet index. If not provided, default to ``-1``. + count: Packet index. If not provided, defaults to ``-1``. Returns: - Data for TCP flow tracing. - - * If the ``packet`` can be used for TCP flow tracing. A packet can be traced - if its network layer carries TCP layer (:class:`dpkt.tcp.TCP`), behind any IPv6 - extension headers :mod:`dpkt` decodes. TCP in an IP-in-IP tunnel, or behind an AH or - IPv6 extension header carried in IPv4, is not read. - * If the ``packet`` can be traced, then the :obj:`dict` mapping of data for TCP - flow tracing (:term:`trace.tcp.packet`) will be returned; otherwise, returns :data:`None`. + Data for TCP flow tracing (:term:`trace.tcp.packet`) if the ``packet`` can be + traced; otherwise :data:`None`. Note: The header and payload are sliced out of the record octets diff --git a/pcapkit/toolkit/pcap.py b/pcapkit/toolkit/pcap.py index 39021d965b..fe31ed69c4 100644 --- a/pcapkit/toolkit/pcap.py +++ b/pcapkit/toolkit/pcap.py @@ -196,18 +196,17 @@ def tcp_segment(frame: 'ProtocolBase') -> 'Optional[TCPSegment]': def ipv4_reassembly(frame: 'Frame') -> 'IP_Packet[IPv4Address] | None': """Make data for IPv4 reassembly. + A frame can be reassembled if it contains an IPv4 layer + (:class:`~pcapkit.protocols.internet.ipv4.IPv4`) and the **DF** + (:attr:`IPv4.flags.df `) flag is + :data:`False`. + Args: frame: PCAP frame. Returns: - Data for IPv4 reassembly. - - * If the ``frame`` can be used for IPv4 reassembly. A frame can be reassembled - if it contains IPv4 layer (:class:`~pcapkit.protocols.internet.ipv4.IPv4`) and - the **DF** (:attr:`IPv4.flags.df `) - flag is :data:`False`. - * If the ``frame`` can be reassembled, then the :obj:`dict` mapping of data for IPv4 - reassembly (c.f. :term:`reasm.ipv4.packet`) will be returned; otherwise, returns :data:`None`. + Data for IPv4 reassembly (:term:`reasm.ipv4.packet`) if the ``frame`` can be + reassembled; otherwise :data:`None`. See Also: :class:`pcapkit.foundation.reassembly.ipv4.IPv4` @@ -242,18 +241,17 @@ def ipv4_reassembly(frame: 'Frame') -> 'IP_Packet[IPv4Address] | None': def ipv6_reassembly(frame: 'Frame') -> 'IP_Packet[IPv6Address] | None': """Make data for IPv6 reassembly. + A frame can be reassembled if it contains an IPv6 layer + (:class:`~pcapkit.protocols.internet.ipv6.IPv6`) and an IPv6 Fragment header + (:rfc:`2460#section-4.5`, i.e., + :class:`~pcapkit.protocols.internet.ipv6_frag.IPv6_Frag`). + Args: frame: PCAP frame. Returns: - Data for IPv6 reassembly. - - * If the ``frame`` can be used for IPv6 reassembly. A frame can be reassembled - if it contains IPv6 layer (:class:`~pcapkit.protocols.internet.ipv6.IPv6`) and - IPv6 Fragment header (:rfc:`2460#section-4.5`, i.e., - :class:`~pcapkit.protocols.internet.ipv6_frag.IPv6_Frag`). - * If the ``frame`` can be reassembled, then the :obj:`dict` mapping of data for IPv6 - reassembly (:term:`reasm.ipv6.packet`) will be returned; otherwise, returns :data:`None`. + Data for IPv6 reassembly (:term:`reasm.ipv6.packet`) if the ``frame`` can be + reassembled; otherwise :data:`None`. See Also: :class:`pcapkit.foundation.reassembly.ipv6.IPv6` @@ -306,18 +304,16 @@ def ipv6_reassembly(frame: 'Frame') -> 'IP_Packet[IPv6Address] | None': def tcp_reassembly(frame: 'Frame') -> 'TCP_Packet | None': """Make data for TCP reassembly. + A frame can be reassembled if it contains a TCP layer + (:class:`~pcapkit.protocols.transport.tcp.TCP`), or a TCP segment whose header the + TCP parser rejected, as it does one the snapshot length cut short (:issue:`1518`). + Args: frame: PCAP frame. Returns: - Data for TCP reassembly. - - * If the ``frame`` can be used for TCP reassembly. A frame can be reassembled - if it contains TCP layer (:class:`~pcapkit.protocols.transport.tcp.TCP`), - or a TCP segment whose header the TCP parser rejected, as it does one the - snapshot length cut short (:issue:`1518`). - * If the ``frame`` can be reassembled, then the :obj:`dict` mapping of data for TCP - reassembly (:term:`reasm.tcp.packet`) will be returned; otherwise, returns :data:`None`. + Data for TCP reassembly (:term:`reasm.tcp.packet`) if the ``frame`` can be + reassembled; otherwise :data:`None`. See Also: :class:`pcapkit.foundation.reassembly.tcp.TCP` @@ -352,19 +348,17 @@ def tcp_reassembly(frame: 'Frame') -> 'TCP_Packet | None': def tcp_traceflow(frame: 'Frame', *, data_link: 'LinkType') -> 'TF_TCP_Packet | None': """Trace packet flow for TCP. + A frame can be traced if it contains a TCP layer + (:class:`~pcapkit.protocols.transport.tcp.TCP`), or a TCP segment whose header the + TCP parser rejected, as it does one the snapshot length cut short (:issue:`1518`). + Args: frame: PCAP frame. data_link: Data link layer protocol (from global header). Returns: - Data for TCP flow tracing. - - * If the ``frame`` can be used for TCP flow tracing. A frame can be traced - if it contains TCP layer (:class:`~pcapkit.protocols.transport.tcp.TCP`), - or a TCP segment whose header the TCP parser rejected, as it does one the - snapshot length cut short (:issue:`1518`). - * If the ``frame`` can be traced, then the :obj:`dict` mapping of data for TCP - flow tracing (:term:`trace.tcp.packet`) will be returned; otherwise, returns :data:`None`. + Data for TCP flow tracing (:term:`trace.tcp.packet`) if the ``frame`` can be + traced; otherwise :data:`None`. See Also: :class:`pcapkit.foundation.traceflow.tcp.TCP` diff --git a/pcapkit/toolkit/pcap_ct.py b/pcapkit/toolkit/pcap_ct.py index 4c0895da5a..cbe2fda89d 100644 --- a/pcapkit/toolkit/pcap_ct.py +++ b/pcapkit/toolkit/pcap_ct.py @@ -87,7 +87,7 @@ def packet2dict(packet: 'bytes', timestamp: 'float', *, data_link: Data link type, from the capture handle. Returns: - Dict[str, Any]: A :obj:`dict` mapping of packet data. + A :obj:`dict` mapping of packet data. Note: The mapping carries the captured bytes verbatim rather than a decoded @@ -111,7 +111,7 @@ def ipv4_reassembly(packet: 'bytes', *, count: 'int' = -1) -> 'IP_Packet[IPv4Add Args: packet: Raw packet bytes. - count: Packet index. If not provided, default to ``-1``. + count: Packet index. If not provided, defaults to ``-1``. Raises: UnsupportedCall: Always, as `pcap-ct`_ provides no IPv4 layer to read. @@ -127,7 +127,7 @@ def ipv6_reassembly(packet: 'bytes', *, count: 'int' = -1) -> 'IP_Packet[IPv6Add Args: packet: Raw packet bytes. - count: Packet index. If not provided, default to ``-1``. + count: Packet index. If not provided, defaults to ``-1``. Raises: UnsupportedCall: Always, as `pcap-ct`_ provides no IPv6 layer to read. @@ -143,7 +143,7 @@ def tcp_reassembly(packet: 'bytes', *, count: 'int' = -1) -> 'TCP_Packet | None' Args: packet: Raw packet bytes. - count: Packet index. If not provided, default to ``-1``. + count: Packet index. If not provided, defaults to ``-1``. Raises: UnsupportedCall: Always, as `pcap-ct`_ provides no TCP layer to read. @@ -162,7 +162,7 @@ def tcp_traceflow(packet: 'bytes', timestamp: 'float', *, data_link: 'Enum_LinkT packet: Raw packet bytes. timestamp: Timestamp of the packet. data_link: Data link layer protocol (from the capture handle). - count: Packet index. If not provided, default to ``-1``. + count: Packet index. If not provided, defaults to ``-1``. Raises: UnsupportedCall: Always, as `pcap-ct`_ provides no TCP layer to read. diff --git a/pcapkit/toolkit/pcapng.py b/pcapkit/toolkit/pcapng.py index 0e0a4100e9..cd991379c7 100644 --- a/pcapkit/toolkit/pcapng.py +++ b/pcapkit/toolkit/pcapng.py @@ -36,18 +36,17 @@ def ipv4_reassembly(frame: 'PCAPNG') -> 'IP_Packet[IPv4Address] | None': """Make data for IPv4 reassembly. + A frame can be reassembled if it contains an IPv4 layer + (:class:`~pcapkit.protocols.internet.ipv4.IPv4`) and the **DF** + (:attr:`IPv4.flags.df `) flag is + :data:`False`. + Args: frame: PCAP-NG frame. Returns: - Data for IPv4 reassembly. - - * If the ``frame`` can be used for IPv4 reassembly. A frame can be reassembled - if it contains IPv4 layer (:class:`~pcapkit.protocols.internet.ipv4.IPv4`) and - the **DF** (:attr:`IPv4.flags.df `) - flag is :data:`False`. - * If the ``frame`` can be reassembled, then the :obj:`dict` mapping of data for IPv4 - reassembly (c.f. :term:`reasm.ipv4.packet`) will be returned; otherwise, returns :data:`None`. + Data for IPv4 reassembly (:term:`reasm.ipv4.packet`) if the ``frame`` can be + reassembled; otherwise :data:`None`. See Also: :class:`pcapkit.foundation.reassembly.ipv4.IPv4` @@ -83,18 +82,17 @@ def ipv4_reassembly(frame: 'PCAPNG') -> 'IP_Packet[IPv4Address] | None': def ipv6_reassembly(frame: 'PCAPNG') -> 'IP_Packet[IPv6Address] | None': """Make data for IPv6 reassembly. + A frame can be reassembled if it contains an IPv6 layer + (:class:`~pcapkit.protocols.internet.ipv6.IPv6`) and an IPv6 Fragment header + (:rfc:`2460#section-4.5`, i.e., + :class:`~pcapkit.protocols.internet.ipv6_frag.IPv6_Frag`). + Args: frame: PCAP-NG frame. Returns: - Data for IPv6 reassembly. - - * If the ``frame`` can be used for IPv6 reassembly. A frame can be reassembled - if it contains IPv6 layer (:class:`~pcapkit.protocols.internet.ipv6.IPv6`) and - IPv6 Fragment header (:rfc:`2460#section-4.5`, i.e., - :class:`~pcapkit.protocols.internet.ipv6_frag.IPv6_Frag`). - * If the ``frame`` can be reassembled, then the :obj:`dict` mapping of data for IPv6 - reassembly (:term:`reasm.ipv6.packet`) will be returned; otherwise, returns :data:`None`. + Data for IPv6 reassembly (:term:`reasm.ipv6.packet`) if the ``frame`` can be + reassembled; otherwise :data:`None`. See Also: :class:`pcapkit.foundation.reassembly.ipv6.IPv6` @@ -148,18 +146,16 @@ def ipv6_reassembly(frame: 'PCAPNG') -> 'IP_Packet[IPv6Address] | None': def tcp_reassembly(frame: 'PCAPNG') -> 'TCP_Packet | None': """Make data for TCP reassembly. + A frame can be reassembled if it contains a TCP layer + (:class:`~pcapkit.protocols.transport.tcp.TCP`), or a TCP segment whose header the + TCP parser rejected, as it does one the snapshot length cut short (:issue:`1518`). + Args: frame: PCAP-NG frame. Returns: - Data for TCP reassembly. - - * If the ``frame`` can be used for TCP reassembly. A frame can be reassembled - if it contains TCP layer (:class:`~pcapkit.protocols.transport.tcp.TCP`), - or a TCP segment whose header the TCP parser rejected, as it does one the - snapshot length cut short (:issue:`1518`). - * If the ``frame`` can be reassembled, then the :obj:`dict` mapping of data for TCP - reassembly (:term:`reasm.tcp.packet`) will be returned; otherwise, returns :data:`None`. + Data for TCP reassembly (:term:`reasm.tcp.packet`) if the ``frame`` can be + reassembled; otherwise :data:`None`. See Also: :class:`pcapkit.foundation.reassembly.tcp.TCP` @@ -196,19 +192,17 @@ def tcp_reassembly(frame: 'PCAPNG') -> 'TCP_Packet | None': def tcp_traceflow(frame: 'PCAPNG', *, nanosecond: 'bool' = False) -> 'TF_TCP_Packet | None': """Trace packet flow for TCP. + A frame can be traced if it contains a TCP layer + (:class:`~pcapkit.protocols.transport.tcp.TCP`), or a TCP segment whose header the + TCP parser rejected, as it does one the snapshot length cut short (:issue:`1518`). + Args: frame: PCAP-NG frame. nanosecond: Whether to use nanosecond precision. Returns: - Data for TCP flow tracing. - - * If the ``frame`` can be used for TCP flow tracing. A frame can be traced - if it contains TCP layer (:class:`~pcapkit.protocols.transport.tcp.TCP`), - or a TCP segment whose header the TCP parser rejected, as it does one the - snapshot length cut short (:issue:`1518`). - * If the ``frame`` can be traced, then the :obj:`dict` mapping of data for TCP - flow tracing (:term:`trace.tcp.packet`) will be returned; otherwise, returns :data:`None`. + Data for TCP flow tracing (:term:`trace.tcp.packet`) if the ``frame`` can be + traced; otherwise :data:`None`. See Also: :class:`pcapkit.foundation.traceflow.tcp.TCP` diff --git a/pcapkit/toolkit/pypcap.py b/pcapkit/toolkit/pypcap.py index 6a26c0fa31..204015d1fe 100644 --- a/pcapkit/toolkit/pypcap.py +++ b/pcapkit/toolkit/pypcap.py @@ -86,7 +86,7 @@ def packet2dict(packet: 'bytes', timestamp: 'float', *, data_link: Data link type, from the capture handle. Returns: - Dict[str, Any]: A :obj:`dict` mapping of packet data. + A :obj:`dict` mapping of packet data. Note: The mapping carries the captured bytes verbatim rather than a decoded @@ -110,7 +110,7 @@ def ipv4_reassembly(packet: 'bytes', *, count: 'int' = -1) -> 'IP_Packet[IPv4Add Args: packet: Raw packet bytes. - count: Packet index. If not provided, default to ``-1``. + count: Packet index. If not provided, defaults to ``-1``. Raises: UnsupportedCall: Always, as `PyPCAP`_ provides no IPv4 layer to read. @@ -126,7 +126,7 @@ def ipv6_reassembly(packet: 'bytes', *, count: 'int' = -1) -> 'IP_Packet[IPv6Add Args: packet: Raw packet bytes. - count: Packet index. If not provided, default to ``-1``. + count: Packet index. If not provided, defaults to ``-1``. Raises: UnsupportedCall: Always, as `PyPCAP`_ provides no IPv6 layer to read. @@ -142,7 +142,7 @@ def tcp_reassembly(packet: 'bytes', *, count: 'int' = -1) -> 'TCP_Packet | None' Args: packet: Raw packet bytes. - count: Packet index. If not provided, default to ``-1``. + count: Packet index. If not provided, defaults to ``-1``. Raises: UnsupportedCall: Always, as `PyPCAP`_ provides no TCP layer to read. @@ -161,7 +161,7 @@ def tcp_traceflow(packet: 'bytes', timestamp: 'float', *, data_link: 'Enum_LinkT packet: Raw packet bytes. timestamp: Timestamp of the packet. data_link: Data link layer protocol (from the capture handle). - count: Packet index. If not provided, default to ``-1``. + count: Packet index. If not provided, defaults to ``-1``. Raises: UnsupportedCall: Always, as `PyPCAP`_ provides no TCP layer to read. diff --git a/pcapkit/toolkit/pypcapfile.py b/pcapkit/toolkit/pypcapfile.py index c3bfdf8cb0..86f829d1e0 100644 --- a/pcapkit/toolkit/pypcapfile.py +++ b/pcapkit/toolkit/pypcapfile.py @@ -125,7 +125,7 @@ def packet2timestamp(packet: 'Packet') -> 'float': packet: PyPCAPFile packet. Returns: - Timestamp of the packet, in seconds since the epoch: the :obj:`float` + Timestamp of the packet, in seconds since the epoch, as the :obj:`float` nearest the exact value. Note: @@ -492,8 +492,8 @@ def _default_accepts(name: 'str', header: 'bytes') -> 'bool': Note: The parser stops at its own layer, so nothing past the header is - dissected. The answer depends on the octets alone, so the last two are - remembered. + dissected. The answer depends on the octets alone, so the most recent + answers are remembered, as many as the cache's ``maxsize``. """ protocol = Protocol_IPv4 if name == 'IPv4' else Protocol_TCP @@ -622,7 +622,7 @@ def _layer2dict(layer: 'Any') -> 'dict[str, Any]': and :class:`~pcapfile.protocols.network.ip.IP` decoders hex-encode the payload they retain rather than decoding it further (see :func:`_maybe_unhex`), so a raw payload reached by recursing *from* one of - those two classes is un-hexed before being reported here. Without that, + those classes is un-hexed before being reported here. Without that, an ``IP`` layer with options would report its own ``opt`` correctly (:func:`_ipv4_2dict` already un-hexes it) while its nested ``'Raw'`` payload stayed hex-encoded and twice the true length -- an inconsistency @@ -692,7 +692,7 @@ def packet2dict(packet: 'Packet', *, data_link: 'Enum_LinkType') -> 'dict[str, A data_link: Data link type, from the savefile header. Returns: - Dict[str, Any]: A :obj:`dict` mapping of packet data. + A :obj:`dict` mapping of packet data. Raises: ProtocolError: If a decoded IPv4 layer's ``src``/``dst`` cannot be @@ -723,19 +723,17 @@ def packet2dict(packet: 'Packet', *, data_link: 'Enum_LinkType') -> 'dict[str, A def ipv4_reassembly(packet: 'Packet', *, count: 'int' = -1) -> 'IP_Packet[IPv4Address] | None': """Make data for IPv4 reassembly. + A packet can be reassembled if it contains an IPv4 layer + (:class:`pcapfile.protocols.network.ip.IP`) whose header was captured whole (see + :func:`_network`), and the **DF** flag is :data:`False`. + Args: packet: PyPCAPFile packet. - count: Packet index. If not provided, default to ``-1``. + count: Packet index. If not provided, defaults to ``-1``. Returns: - Data for IPv4 reassembly. - - * If the ``packet`` can be used for IPv4 reassembly. A packet can be reassembled - if it contains an IPv4 layer (:class:`pcapfile.protocols.network.ip.IP`) whose - header was captured whole (see :func:`_network`), and the **DF** flag is - :data:`False`. - * If the ``packet`` can be reassembled, then the :obj:`dict` mapping of data for IPv4 - reassembly (:term:`reasm.ipv4.packet`) will be returned; otherwise, returns :data:`None`. + Data for IPv4 reassembly (:term:`reasm.ipv4.packet`) if the ``packet`` can be + reassembled; otherwise :data:`None`. Raises: ProtocolError: If ``ipv4.src`` or ``ipv4.dst`` cannot be parsed as an @@ -775,7 +773,7 @@ def ipv6_reassembly(packet: 'Packet', *, count: 'int' = -1) -> 'IP_Packet[IPv6Ad Args: packet: PyPCAPFile packet. - count: Packet index. If not provided, default to ``-1``. + count: Packet index. If not provided, defaults to ``-1``. Raises: UnsupportedCall: Always, as `PyPCAPFile`_ has no IPv6 decoder. @@ -791,18 +789,16 @@ def ipv6_reassembly(packet: 'Packet', *, count: 'int' = -1) -> 'IP_Packet[IPv6Ad def tcp_reassembly(packet: 'Packet', *, count: 'int' = -1) -> 'TCP_Packet | None': """Make data for TCP reassembly. + A packet can be reassembled if it contains an IPv4 layer carrying a TCP segment (see + :func:`_transport`); TCP over IPv6 cannot be, as `PyPCAPFile`_ has no IPv6 decoder. + Args: packet: PyPCAPFile packet. - count: Packet index. If not provided, default to ``-1``. + count: Packet index. If not provided, defaults to ``-1``. Returns: - Data for TCP reassembly. - - * If the ``packet`` can be used for TCP reassembly. A packet can be reassembled - if it contains an IPv4 layer carrying a TCP segment (see :func:`_transport`); - TCP over IPv6 cannot be, as `PyPCAPFile`_ has no IPv6 decoder. - * If the ``packet`` can be reassembled, then the :obj:`dict` mapping of data for TCP - reassembly (:term:`reasm.tcp.packet`) will be returned; otherwise, returns :data:`None`. + Data for TCP reassembly (:term:`reasm.tcp.packet`) if the ``packet`` can be + reassembled; otherwise :data:`None`. Raises: ProtocolError: If ``ipv4.src`` or ``ipv4.dst`` cannot be parsed as an @@ -864,19 +860,17 @@ def tcp_traceflow(packet: 'Packet', *, data_link: 'Enum_LinkType', count: 'int' = -1) -> 'TF_TCP_Packet | None': """Trace packet flow for TCP. + A packet can be traced if it contains an IPv4 layer carrying a TCP segment (see + :func:`_transport`); TCP over IPv6 cannot be, as `PyPCAPFile`_ has no IPv6 decoder. + Args: packet: PyPCAPFile packet. data_link: Data link layer protocol (from the savefile header). - count: Packet index. If not provided, default to ``-1``. + count: Packet index. If not provided, defaults to ``-1``. Returns: - Data for TCP flow tracing. - - * If the ``packet`` can be used for TCP flow tracing. A packet can be traced - if it contains an IPv4 layer carrying a TCP segment (see :func:`_transport`); - TCP over IPv6 cannot be, as `PyPCAPFile`_ has no IPv6 decoder. - * If the ``packet`` can be traced, then the :obj:`dict` mapping of data for TCP - flow tracing (:term:`trace.tcp.packet`) will be returned; otherwise, returns :data:`None`. + Data for TCP flow tracing (:term:`trace.tcp.packet`) if the ``packet`` can be + traced; otherwise :data:`None`. Raises: ProtocolError: If ``ipv4.src`` or ``ipv4.dst`` cannot be parsed as an diff --git a/pcapkit/toolkit/pyshark.py b/pcapkit/toolkit/pyshark.py index 4aec25cb4c..a9b4d20ad1 100644 --- a/pcapkit/toolkit/pyshark.py +++ b/pcapkit/toolkit/pyshark.py @@ -51,16 +51,16 @@ #: # key := the frame.encap_type that `tshark -r out.pcap -c 1 -T pdml` then reports #: #: so each pair is one round trip through Wireshark's own ``wiretap/pcap-common.c``, in the read -#: direction :mod:`pyshark` actually exercises. The trailing comment on each line names the -#: ``editcap -T`` encapsulation that produced it. +#: direction :mod:`pyshark` actually exercises. In the source, the trailing comment on each line +#: names the ``editcap -T`` encapsulation that produced it. #: #: What the sweep does **not** cover, since that bounds this table: of the 226 encapsulations #: ``editcap -T`` accepts, 157 could be written as pcap from the Ethernet source above and the #: other 69 refused the rewrite, so they are untested. Those 157 yielded 153 distinct #: ``frame.encap_type`` keys, with no key mapping to two DLTs and no DLT reading back as two keys. -#: 152 of them are below; ``frame.encap_type`` 32 (``editcap -T hhdlc``, DLT 121) is left out -#: because :class:`LinkType` has no member for 121. An encapsulation absent from this table raises -#: rather than resolving to a near-miss DLT. +#: Each is below except ``frame.encap_type`` 32 (``editcap -T hhdlc``, DLT 121), left out because +#: :class:`~pcapkit.const.reg.linktype.LinkType` has no member for 121. An encapsulation absent +#: from this table raises rather than resolving to a near-miss DLT. ENCAP_TYPE_TO_LINKTYPE = { 1: Enum_LinkType.ETHERNET, # ether 2: Enum_LinkType.IEEE802_5, # tr @@ -223,19 +223,21 @@ #: the PDML #: ```` attribute, which is Wireshark's own dissector *filter* name -- the third #: argument to ``proto_register_protocol()`` in the relevant ``epan/dissectors/packet-*.c`` -- and -#: that vocabulary is not :class:`LinkType`'s: Ethernet's filter name is ``eth``, never +#: that vocabulary is not :class:`~pcapkit.const.reg.linktype.LinkType`'s: Ethernet's filter +#: name is ``eth``, never #: ``ETHERNET``. #: #: Every entry comes from the same sweep as ``ENCAP_TYPE_TO_LINKTYPE`` above: the root ```` of ``tshark -r out.pcap -c 1 -T pdml`` against the DLT in ``out.pcap``'s own file -#: header. The trailing comment on each line names the ``editcap -T`` encapsulation that produced -#: it, so an entry can be re-checked, or corrected when upstream moves, by rerunning that one -#: rewrite. Names are listed even where they already spell their :class:`LinkType` member +#: header. In the source, the trailing comment on each line names the ``editcap -T`` encapsulation +#: that produced it, so an entry can be re-checked, or corrected when upstream moves, by rerunning +#: that one rewrite. Names are listed even where they already spell their +#: :class:`~pcapkit.const.reg.linktype.LinkType` member #: (``docsis``, ``fddi``, ``pflog``, ...): there is deliberately **no** fallback onto a like-named #: member, because upper-casing the name is exactly what would answer 101 for a ``rawip6`` capture #: and 0 for a ``DLT_LOOP`` one -- valid DLTs, wrong ones, and silent (:issue:`843`). #: -#: Three classes of name are absent, all three measured rather than assumed: +#: These classes of name are absent, each measured rather than assumed: #: #: * **Ambiguous** -- one name, several DLTs, and the PDML node does not say which arrived. These #: cannot be mapped at any size: ``user_dlt`` (16 DLTs), ``bluetooth`` (6), ``usb`` (5), @@ -247,12 +249,12 @@ #: * **Unswept** -- the 69 encapsulations ``editcap -T`` would not write as pcap from an Ethernet #: source. No evidence either way was gathered for them. #: -#: Two caveats on what "unambiguous" means here. It means unambiguous *within* the 157 swept +#: Caveats on what "unambiguous" means here. It means unambiguous *within* the 157 swept #: encapsulations: one of the 69 unswept could still root at the same name (``ether-nettl`` and -#: ``tr-nettl`` are the obvious candidates, for ``eth`` and ``tr``). And five of the entries name a -#: *payload* dissector that happened to be the outermost PDML node for exactly one encapsulation -#: -- ``llc``, ``sctp``, ``pppoes``, ``fpp`` and ``irlap`` -- rather than a link-layer dissector -#: registered against a ``WTAP_ENCAP_*``. Both are why this table is the fallback and +#: ``tr-nettl`` are the obvious candidates, for ``eth`` and ``tr``). And some entries -- ``llc``, +#: ``sctp``, ``pppoes``, ``fpp`` and ``irlap`` -- name a *payload* dissector that happened to be the +#: outermost PDML node for exactly one encapsulation, rather than a link-layer dissector +#: registered against a ``WTAP_ENCAP_*``. These caveats are why this table is the fallback and #: ``frame.encap_type`` is the primary key; ``eth`` and ``tr`` additionally check out against #: Wireshark's own registrations (``packet-eth.c`` on ``WTAP_ENCAP_ETHERNET``, ``packet-tr.c`` on #: ``WTAP_ENCAP_TOKEN_RING``). @@ -404,7 +406,7 @@ def _frame_layer(packet: 'Packet') -> 'Any': PyShark takes the second PDML ```` as ``frame_info``. On a packet with a comment that is ``pkt_comment``, and the frame layer is ``layers[0]`` (measured on ``test.pcapng`` frames 1 and 6 with tshark - 4.6.9), so the layer is looked up by name, as the engine does (#1515). + 4.6.9), so the layer is looked up by name, as the engine does (:issue:`1515`). """ for layer in (packet.frame_info, *packet.layers): @@ -420,8 +422,8 @@ def packet2dict(packet: 'Packet') -> 'dict[str, Any]': packet: PyShark packet. Returns: - A :obj:`dict` mapping of packet data: the ``frame`` layer's fields at - the top level, and each later layer nested in the one before it. A + A :obj:`dict` mapping of packet data, with the ``frame`` layer's fields + at the top level and each later layer nested in the one before it. A packet comment, which PyShark reports as ``frame_info`` in place of the frame layer, is kept under its own ``PKT_COMMENT`` key. A record with no ``frame`` layer keeps PyShark's ``frame_info`` at the top level. @@ -530,22 +532,20 @@ def _has_addresses(ip: 'Any') -> 'bool': def tcp_traceflow(packet: 'Packet', *, count: 'int' = -1) -> 'TF_TCP_Packet | None': """Trace packet flow for TCP. + A packet can be traced if it contains a TCP layer with a whole fixed header (see + :func:`_has_fixed_header`), keyed by the IP layer that carries it -- in an IP-in-IP + tunnel, the innermost (see :func:`_carrier`) -- if tshark reports both its addresses + (see :func:`_has_addresses`). + Args: packet: PyShark packet. - count: Packet index. If not provided, default to ``-1``. The engine + count: Packet index. If not provided, defaults to ``-1``. The engine passes its own frame count, not tshark's ``frame.number``, which - also numbers records that are not packets (#1515). + also numbers records that are not packets (:issue:`1515`). Returns: - Data for TCP flow tracing. - - * If the ``packet`` can be used for TCP flow tracing. A packet can be traced - if it contains TCP layer with a whole fixed header (see - :func:`_has_fixed_header`), keyed by the IP layer that carries it -- in an - IP-in-IP tunnel, the innermost (see :func:`_carrier`) -- if tshark reports - both its addresses (see :func:`_has_addresses`). - * If the ``packet`` can be traced, then the :obj:`dict` mapping of data for TCP - flow tracing (:term:`trace.tcp.packet`) will be returned; otherwise, returns :data:`None`. + Data for TCP flow tracing (:term:`trace.tcp.packet`) if the ``packet`` can be + traced; otherwise :data:`None`. Raises: MissingKeyError: If the frame's link type cannot be resolved. @@ -642,7 +642,7 @@ def tcp_traceflow(packet: 'Packet', *, count: 'int' = -1) -> 'TF_TCP_Packet | No # them, so there is no header or payload to hand over -- which is # the same reason this module carries no ``tcp_reassembly`` at all. # ``Extractor`` refuses ``trace_analyse=True`` on this engine, so - # nothing reads these two. + # nothing reads them. header=b'', # unavailable payload=bytearray(), # unavailable ) diff --git a/pcapkit/toolkit/scapy.py b/pcapkit/toolkit/scapy.py index b38b8f9f2f..26622db118 100644 --- a/pcapkit/toolkit/scapy.py +++ b/pcapkit/toolkit/scapy.py @@ -454,22 +454,21 @@ def _tcp_carrier(packet: 'Packet') -> 'tuple[IP | IPv6, TCP, bytes, bytes] | Non def ipv4_reassembly(packet: 'Packet', *, count: 'int' = -1) -> 'IP_Packet[IPv4Address] | None': """Make data for IPv4 reassembly. + A packet can be reassembled if it contains an IPv4 layer + (:class:`scapy.layers.inet.IP`) whose header -- as many octets as its IHL counts, + and at least 20 -- lies inside the frame and every IP datagram carrying it (see + :func:`_within_datagrams`), and the **DF** (:attr:`scapy.layers.inet.IP.flags.DF`) + flag is :data:`False`. A zero-length frame has none, though `Scapy`_ hands it back + as an IPv4 layer of defaults at the ``RAW`` and ``IPV4`` link types (see + :func:`_frame`). + Args: packet: Scapy packet. - count: Packet index. If not provided, default to ``-1``. + count: Packet index. If not provided, defaults to ``-1``. Returns: - Data for IPv4 reassembly. - - * If the ``packet`` can be used for IPv4 reassembly. A packet can be reassembled - if it contains IPv4 layer (:class:`scapy.layers.inet.IP`) whose header -- as - many octets as its IHL counts, and at least 20 -- lies inside the frame and - every IP datagram carrying it (see :func:`_within_datagrams`), and the **DF** - (:attr:`scapy.layers.inet.IP.flags.DF`) flag is :data:`False`. A zero-length - frame has none, though `Scapy`_ hands it back as an IPv4 layer of defaults at - the ``RAW`` and ``IPV4`` link types (see :func:`_frame`). - * If the ``packet`` can be reassembled, then the :obj:`dict` mapping of data for IPv4 - reassembly (:term:`reasm.ipv4.packet`) will be returned; otherwise, returns :data:`None`. + Data for IPv4 reassembly (:term:`reasm.ipv4.packet`) if the ``packet`` can be + reassembled; otherwise :data:`None`. See Also: :class:`pcapkit.foundation.reassembly.ipv4.IPv4` @@ -526,20 +525,19 @@ def ipv4_reassembly(packet: 'Packet', *, count: 'int' = -1) -> 'IP_Packet[IPv4Ad def ipv6_reassembly(packet: 'Packet', *, count: 'int' = -1) -> 'IP_Packet[IPv6Address] | None': """Make data for IPv6 reassembly. + A packet can be reassembled if it contains an IPv6 layer + (:class:`scapy.layers.inet6.IPv6`), in no later IPv4 fragment, and an IPv6 Fragment + header (:rfc:`2460#section-4.5`, i.e., + :class:`scapy.layers.inet6.IPv6ExtHdrFragment`) among the extension headers `Scapy`_ + dissects in that layer's own chain. + Args: packet: Scapy packet. - count: Packet index. If not provided, default to ``-1``. + count: Packet index. If not provided, defaults to ``-1``. Returns: - Data for IPv6 reassembly. - - * If the ``packet`` can be used for IPv6 reassembly. A packet can be reassembled - if it contains IPv6 layer (:class:`scapy.layers.inet6.IPv6`), in no later IPv4 - fragment, and IPv6 Fragment header (:rfc:`2460#section-4.5`, i.e., - :class:`scapy.layers.inet6.IPv6ExtHdrFragment`) among the extension headers - `Scapy`_ dissects in that layer's own chain. - * If the ``packet`` can be reassembled, then the :obj:`dict` mapping of data for IPv6 - reassembly (:term:`reasm.ipv6.packet`) will be returned; otherwise, returns :data:`None`. + Data for IPv6 reassembly (:term:`reasm.ipv6.packet`) if the ``packet`` can be + reassembled; otherwise :data:`None`. Raises: ModuleNotFound: If `Scapy`_ is not installed. @@ -613,23 +611,22 @@ def ipv6_reassembly(packet: 'Packet', *, count: 'int' = -1) -> 'IP_Packet[IPv6Ad def tcp_reassembly(packet: 'Packet', *, count: 'int' = -1) -> 'TCP_Packet | None': - """Store data for TCP reassembly. + """Make data for TCP reassembly. + + A packet can be reassembled if it contains a TCP layer + (:class:`scapy.layers.inet.TCP`) whose fixed header lies inside its datagram, and + whose Data Offset counts at least that header, in no later IPv4 fragment; or a TCP + segment behind an AH or IPv6 extension header in IPv4, read off the octets. It is + keyed by the IP layer that carries it -- in an IP-in-IP tunnel, the innermost (see + :func:`_tcp_carrier`). Args: packet: Scapy packet. - count: Packet index. If not provided, default to ``-1``. + count: Packet index. If not provided, defaults to ``-1``. Returns: - Data for TCP reassembly. - - * If the ``packet`` can be used for TCP reassembly. A packet can be reassembled - if it contains TCP layer (:class:`scapy.layers.inet.TCP`) whose fixed header - lies inside its datagram, and whose Data Offset counts at least that header, - in no later IPv4 fragment; or a TCP segment behind an AH or IPv6 extension - header in IPv4, read off the octets. It is keyed by the IP layer that carries - it -- in an IP-in-IP tunnel, the innermost (see :func:`_tcp_carrier`). - * If the ``packet`` can be reassembled, then the :obj:`dict` mapping of data for TCP - reassembly (:term:`reasm.tcp.packet`) will be returned; otherwise, returns :data:`None`. + Data for TCP reassembly (:term:`reasm.tcp.packet`) if the ``packet`` can be + reassembled; otherwise :data:`None`. See Also: :class:`pcapkit.foundation.reassembly.tcp.TCP` @@ -666,18 +663,16 @@ def tcp_reassembly(packet: 'Packet', *, count: 'int' = -1) -> 'TCP_Packet | None def tcp_traceflow(packet: 'Packet', *, count: 'int' = -1) -> 'TF_TCP_Packet | None': """Trace packet flow for TCP. + A packet can be traced if it contains a TCP layer (:class:`scapy.layers.inet.TCP`), + on the terms :func:`tcp_reassembly` sets. + Args: packet: Scapy packet. - count: Packet index. If not provided, default to ``-1``. + count: Packet index. If not provided, defaults to ``-1``. Returns: - Data for TCP flow tracing. - - * If the ``packet`` can be used for TCP flow tracing. A packet can be traced - if it contains TCP layer (:class:`scapy.layers.inet.TCP`), on the terms - :func:`tcp_reassembly` sets. - * If the ``packet`` can be traced, then the :obj:`dict` mapping of data for TCP - flow tracing (:term:`trace.tcp.packet`) will be returned; otherwise, returns :data:`None`. + Data for TCP flow tracing (:term:`trace.tcp.packet`) if the ``packet`` can be + traced; otherwise :data:`None`. See Also: :class:`pcapkit.foundation.traceflow.tcp.TCP` diff --git a/pcapkit/utilities/chardet.py b/pcapkit/utilities/chardet.py index 350034d300..9718aa791a 100644 --- a/pcapkit/utilities/chardet.py +++ b/pcapkit/utilities/chardet.py @@ -45,7 +45,7 @@ def detect(value: 'bytes') -> 'str': ` to release it in a long-running process. - Two alternatives were tried and rejected. Keying on a *prefix* is + Alternatives were tried and rejected. Keying on a *prefix* is unsound, since :func:`chardet.detect` is statistical over the whole sequence: an ASCII header followed by a UTF-8, Latin-1 or CP1251 body is detected as ``ascii`` from its first 256 octets and correctly otherwise, @@ -53,7 +53,7 @@ def detect(value: 'bytes') -> 'str': footprint exactly and was measured retaining 0.0 MB for the same 19 MB of input, but it cannot be expressed with :func:`~functools.lru_cache` -- which keys on what it is passed -- and hand-rolling the eviction was - judged not worth the six lines. + judged not worth the extra code. Args: value: Bytestring whose encoding is to be detected. diff --git a/pcapkit/utilities/compat.py b/pcapkit/utilities/compat.py index 64c2b73cc6..c0d0ef3cdd 100644 --- a/pcapkit/utilities/compat.py +++ b/pcapkit/utilities/compat.py @@ -154,7 +154,7 @@ def unquote(string: 'str | bytes', encoding: 'str' = 'utf-8', errors: 'str' = 'r # that only exists on the newer interpreter, so it never executes on the older # one the guard is there to support. Vermin's analysis is static and cannot see # that, so it counts these imports anyway and reports the whole library as -# requiring 3.11 on the strength of three lines that never run below it. +# requiring 3.11 on the strength of lines that never run below it. # ``# novermin`` is Vermin's documented escape hatch for precisely this shape. # It is applied per line rather than by turning on ``lax`` mode, so that an # *unguarded* use of a new feature anywhere else is still caught. diff --git a/pcapkit/utilities/config.py b/pcapkit/utilities/config.py index 8a6d0cad4e..de40efdc73 100644 --- a/pcapkit/utilities/config.py +++ b/pcapkit/utilities/config.py @@ -32,7 +32,7 @@ Besides the standard library, this module imports :mod:`pcapkit.utilities.warnings` and nothing else from :mod:`pcapkit`, which is -what lets :mod:`pcapkit.utilities.logging` load before it. The three flags that +what lets :mod:`pcapkit.utilities.logging` load before it. The flags that :mod:`pcapkit.utilities.logging` needs while it is loading, :envvar:`PCAPKIT_DEVMODE`, :envvar:`PCAPKIT_VERBOSE` and :envvar:`PCAPKIT_SPHINX`, are therefore read there instead. @@ -222,9 +222,9 @@ def read_flag(name: 'str', default: 'bool' = False) -> 'bool': #: expands for one top-level object, counting an object once per time it is met, #: :envvar:`PCAPKIT_FALLBACK_LIMIT`; ``-1`` expands without bound. #: -#: No fallback object in the ``foundation``, ``dumpkit`` or ``interface`` test -#: legs expands into more than 7, so the default leaves three orders of -#: magnitude of room. +#: Measured over the ``foundation``, ``dumpkit`` and ``interface`` test legs, no +#: fallback object expands into more than 7, so the default leaves three orders +#: of magnitude of room. #: #: .. versionadded:: 1.5.0 #: @@ -275,9 +275,11 @@ def read_flag(name: 'str', default: 'bool' = False) -> 'bool': #: should ever declare a length past it. #: #: The bound is deliberately *not* "any declared length beyond what the -#: buffer holds": :meth:`ListField.unpack ` and :meth:`OptionField.unpack ` depend on reading a short, sometimes +#: buffer holds": +#: :meth:`ListField.unpack ` +#: and +#: :meth:`OptionField.unpack ` +#: depend on reading a short, sometimes #: empty, tail past a truncated area and having it decode as zero -- that is #: how an over-long ``ihl``, or a capture cut short by the snapshot length, #: reads as end-of-option-list or ``Pad1`` instead of wedging or raising (see @@ -305,19 +307,19 @@ def read_flag(name: 'str', default: 'bool' = False) -> 'bool': #: #: :data:`MAX_ZERO_PAD_LENGTH` bounds each field on its own, and a packet holds #: many fields, so it leaves the *sum* unbounded: a declared length just under the -#: ceiling is honoured however often it is declared. Measured on this tree, 200 +#: ceiling is honoured however often it is declared. Measured for :issue:`573`, 200 #: minimal PCAP-NG Decryption Secrets Blocks -- 4,800 wire octets, each block #: declaring ``secrets_length`` of 262,142 against two supplied octets through #: ``UnknownSecrets.data`` (``pcapkit/protocols/schema/misc/pcapng.py``) -- #: retained 50.0 MiB, an amplification of 10,922x per block. Every individual -#: field was under the ceiling, so nothing refused any of them (:issue:`573`). +#: field was under the ceiling, so nothing refused any of them. #: #: The sum therefore wants a budget, and this is the figure that makes one #: *safe*. A budget on its own is not: a capture cut short by its snapshot length #: pads legitimately and must keep parsing (:issue:`431`, and the reasoning that #: declined :issue:`554`), and it pads far more than it reads, so any running #: budget tight enough to matter starts refusing real captures. Worse, it refuses -#: them *sometimes* -- measured on this tree with a running budget alone, the +#: them *sometimes* -- measured with a running budget alone, the #: same legitimate 54-octet frame parsed to one result on 37 of 40 calls and to #: another on calls 26, 33 and 39, because whether it fit depended on what had #: been parsed before it. A guard whose answer moves with history is not a guard. diff --git a/pcapkit/utilities/exceptions.py b/pcapkit/utilities/exceptions.py index 55522e374d..a9344ee250 100644 --- a/pcapkit/utilities/exceptions.py +++ b/pcapkit/utilities/exceptions.py @@ -162,8 +162,8 @@ class BaseError(Exception): """Base error class of all kinds. A loud error -- the default -- is reported once, at - :data:`logging.CRITICAL` level, on the - :data:`~pcapkit.utilities.logging.logger` logger. Outside development mode it + :data:`logging.CRITICAL` level, on the ``pcapkit`` logger + (:data:`~pcapkit.utilities.logging.logger`). Outside development mode it also installs :func:`_excepthook` as :data:`sys.excepthook` and :func:`_threading_excepthook` as :data:`threading.excepthook`, the first time a loud error needs them, so that error -- and every loud error reaching the @@ -184,15 +184,15 @@ class BaseError(Exception): * The terseness is deliberately not done by setting :data:`sys.tracebacklimit` to ``0``: that is process-global, so one loud error would truncate the tracebacks of every *other*, unrelated - exception for the rest of the process. Each of the two hooks here + exception for the rest of the process. Each hook here shortens the printing of a :class:`BaseError` only, and hands every other exception, unchanged, to whatever hook :mod:`pcapkit` found installed before it first needed its own. - * Both hooks are installed lazily, from here, rather than at import + * The hooks are installed lazily, from here, rather than at import time, so a program that imports :mod:`pcapkit` but never raises one of its errors never has :data:`sys.excepthook` or :data:`threading.excepthook` touched. - * The two hooks still do not cover *every* path an exception can take. + * The hooks still do not cover *every* path an exception can take. A traceback a caller formats itself with :mod:`traceback` -- rather than letting it reach the top level uncaught -- passes through neither. See GitHub issue :issue:`719`. @@ -392,8 +392,7 @@ def _install_excepthook() -> 'None': :data:`sys.excepthook` or :data:`threading.excepthook` touched merely for having imported the package. - Idempotent for *this* module instance, independently for each of the two - hooks: if a given hook slot already holds this instance's own function -- + Idempotent for *this* module instance, independently for each hook: if a given hook slot already holds this instance's own function -- checked by identity, ``is``, not by the ``installed_by_pcapkit`` marker -- installing it again does nothing, so a loud error later in the same run never wraps either hook in another copy of itself. That distinction matters @@ -438,11 +437,11 @@ class IntError(BaseError, TypeError): class RealError(BaseError, TypeError): - """The function is not defined for real number.""" + """The function is not defined for real numbers.""" class ComplexError(BaseError, TypeError): - """The function is not defined for complex instance.""" + """The function is not defined for complex numbers.""" class BytesError(BaseError, TypeError): @@ -717,8 +716,8 @@ class EnumKeyError(BaseError, KeyError): circumstances, and raise it from this module rather than as a builtin. Deriving from :exc:`KeyError` is what makes that ruling cheap to carry out: - six in-library call sites catch :meth:`~pcapkit.corekit.enum.EnumLookup.get`'s - name miss as :exc:`KeyError` -- :meth:`~pcapkit.const.http.method.Method.get` + the in-library call sites that catch + :meth:`~pcapkit.corekit.enum.EnumLookup.get`'s name miss as :exc:`KeyError` -- :meth:`~pcapkit.const.http.method.Method.get` catches it in order to build an unregistered member, so for that one a failed name lookup is part of a successful call -- and every one of them keeps catching. diff --git a/pcapkit/utilities/warnings.py b/pcapkit/utilities/warnings.py index be96fbb5ef..3af4b1b41f 100644 --- a/pcapkit/utilities/warnings.py +++ b/pcapkit/utilities/warnings.py @@ -7,9 +7,9 @@ :mod:`pcapkit.utilities.warnings` refined built-in warnings. Every warning :mod:`pcapkit` reports goes through :func:`warn`, which reports it -**exactly once on each of two channels**: +**exactly once on each channel**: -1. the :data:`~pcapkit.utilities.logging.logger` logger, at +1. the ``pcapkit`` logger (:data:`~pcapkit.utilities.logging.logger`), at :data:`logging.WARNING` level, unconditionally -- so a consumer watching the ``pcapkit`` logger sees every complaint whatever the warning filters say; 2. the standard :mod:`warnings` machinery, via :func:`warnings.warn`, subject to @@ -361,7 +361,7 @@ class EngineWarning(BaseWarning, ImportWarning): class InvalidVendorWarning(BaseWarning, ImportWarning): - """Vendor CLI invalid updater.""" + """Invalid updater given to the vendor CLI.""" ############################################################################## From 6db91b69e97a1f540c3657db88e4e08755f2ed94 Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Fri, 16 Oct 2026 16:48:46 -0400 Subject: [PATCH 2/3] docs(foundation): fix the review findings on the foundation pass (#1103) - EnumKeyError: restore the predicate a count removal dropped; re-wrap - config.rst: vendor.default reads and documents the vendor variables - Document Completion so the references to its members resolve - Point the __output__ references at TraceFlow, where it is documented - packet2timestamp: a nanosecond file yields a DecimalTimestamp - Render RecordTimestamp's attribute docs with :members: - Name the PyPCAP/PCAP_CT pair rather than count it; "each channel" - Re-wrap over-long lines in exceptions.py, reassembly.py and data.py Refs #1103 --- docs/source/pcapkit/foundation/engines/index.rst | 4 ++-- docs/source/pcapkit/foundation/reassembly/index.rst | 4 ++++ docs/source/pcapkit/toolkit/3rdparty.rst | 1 + docs/source/pcapkit/utilities/config.rst | 5 +++-- docs/source/pcapkit/utilities/warnings.rst | 2 +- pcapkit/foundation/engines/pypcap.py | 10 +++++----- pcapkit/foundation/extraction.py | 4 ++-- pcapkit/foundation/reassembly/data/data.py | 6 +++--- pcapkit/foundation/reassembly/reassembly.py | 8 +++++--- pcapkit/foundation/traceflow/traceflow.py | 2 +- pcapkit/interface/misc.py | 4 ++-- pcapkit/toolkit/dpkt.py | 6 +++++- pcapkit/toolkit/pcap_ct.py | 10 +++++----- pcapkit/utilities/exceptions.py | 13 +++++++------ pcapkit/utilities/warnings.py | 2 +- 15 files changed, 47 insertions(+), 34 deletions(-) diff --git a/docs/source/pcapkit/foundation/engines/index.rst b/docs/source/pcapkit/foundation/engines/index.rst index 9e69d054b1..9ecbe95c52 100644 --- a/docs/source/pcapkit/foundation/engines/index.rst +++ b/docs/source/pcapkit/foundation/engines/index.rst @@ -236,8 +236,8 @@ actually got, via * declines to run on the other one, through ``unsupported_reason()``, with a message naming the ``engine=`` string that does want it; and * warns with an :class:`~pcapkit.utilities.warnings.EngineWarning` when it finds - both installed, since that state makes one of the two engines permanently - unselectable and nothing else would explain why. + both installed, since that state leaves one engine permanently unselectable + and nothing else would explain why. .. _PCAP-NG: https://wiki.wireshark.org/Development/PcapNg .. _libpcap: https://pypi.org/project/libpcap/ diff --git a/docs/source/pcapkit/foundation/reassembly/index.rst b/docs/source/pcapkit/foundation/reassembly/index.rst index 054834605b..0d10928ec4 100644 --- a/docs/source/pcapkit/foundation/reassembly/index.rst +++ b/docs/source/pcapkit/foundation/reassembly/index.rst @@ -61,6 +61,10 @@ Auxiliary Data :members: :show-inheritance: +.. autoclass:: pcapkit.foundation.reassembly.data.data.Completion + :members: + :show-inheritance: + .. autoclass:: pcapkit.foundation.reassembly.data.data.Deferred :members: :show-inheritance: diff --git a/docs/source/pcapkit/toolkit/3rdparty.rst b/docs/source/pcapkit/toolkit/3rdparty.rst index 0918f3b1af..cde0b4960a 100644 --- a/docs/source/pcapkit/toolkit/3rdparty.rst +++ b/docs/source/pcapkit/toolkit/3rdparty.rst @@ -87,6 +87,7 @@ Auxiliary Data -------------- .. autoclass:: pcapkit.toolkit.dpkt.RecordTimestamp + :members: :show-inheritance: .. autoclass:: pcapkit.toolkit.dpkt.Timestamp diff --git a/docs/source/pcapkit/utilities/config.rst b/docs/source/pcapkit/utilities/config.rst index 5d7b6e14c1..7bdbd780d8 100644 --- a/docs/source/pcapkit/utilities/config.rst +++ b/docs/source/pcapkit/utilities/config.rst @@ -26,8 +26,9 @@ here reaches every engine and protocol that reads it. The :mod:`pcapkit.vendor` crawlers' :envvar:`PCAPKIT_VENDOR_RETRY`, :envvar:`PCAPKIT_CI_MODE`, :envvar:`PCAPKIT_HTTP_PROXY` and :envvar:`PCAPKIT_HTTPS_PROXY` are not held here. They are read by -:mod:`pcapkit.vendor.default`, :envvar:`PCAPKIT_VENDOR_RETRY` and -:envvar:`PCAPKIT_CI_MODE` with the :ref:`readers ` below, and documented with it under :ref:`vendor-variables`. +:mod:`pcapkit.vendor.default` and documented with it under +:ref:`vendor-variables`; it reads :envvar:`PCAPKIT_VENDOR_RETRY` and +:envvar:`PCAPKIT_CI_MODE` with the :ref:`readers ` below. :envvar:`PCAPKIT_DEVMODE`, :envvar:`PCAPKIT_VERBOSE` and :envvar:`PCAPKIT_SPHINX` are read by :mod:`pcapkit.utilities.logging`, which needs them while it loads diff --git a/docs/source/pcapkit/utilities/warnings.rst b/docs/source/pcapkit/utilities/warnings.rst index f37bdf6443..3252ecfcdf 100644 --- a/docs/source/pcapkit/utilities/warnings.rst +++ b/docs/source/pcapkit/utilities/warnings.rst @@ -37,7 +37,7 @@ A next layer captured shorter than its length hint is parsed on trial, and replaced with :class:`~pcapkit.protocols.misc.raw.Raw` when its header was not captured. The trial's warnings are held back by :class:`~pcapkit.utilities.warnings.hold_warnings`: a replaced layer's are dropped -on both channels, since they describe a layer the result does not have, and a kept +on each channel, since they describe a layer the result does not have, and a kept layer's are reported as above when the trial ends (:issue:`1580`). .. autofunction:: pcapkit.utilities.warnings.warn diff --git a/pcapkit/foundation/engines/pypcap.py b/pcapkit/foundation/engines/pypcap.py index be79e4e402..2121d9782e 100644 --- a/pcapkit/foundation/engines/pypcap.py +++ b/pcapkit/foundation/engines/pypcap.py @@ -214,11 +214,11 @@ def __init__(self, extractor: 'Extractor') -> 'None': .. _pcap-ct: https://pypi.org/project/pcap-ct/ """ - # NOTE: the probe comes *before* ``import pcap`` so that the two engines - # behave the same way here. It matters more for - # :class:`~pcapkit.foundation.engines.pcap_ct.PCAP_CT`, whose import can - # raise ``OSError`` rather than ``ImportError``, but a shared order is one - # fewer difference between them. + # NOTE: the probe comes *before* ``import pcap`` so that this engine and + # :class:`~pcapkit.foundation.engines.pcap_ct.PCAP_CT` behave the same way + # here. It matters more for the latter, whose import can raise + # ``OSError`` rather than ``ImportError``, but a shared order is one fewer + # difference between them. self._backend = _pcap_backend.probe() collision = _pcap_backend.collision_reason(self._backend) diff --git a/pcapkit/foundation/extraction.py b/pcapkit/foundation/extraction.py index f8b990f2cb..dd4acb10b6 100644 --- a/pcapkit/foundation/extraction.py +++ b/pcapkit/foundation/extraction.py @@ -65,8 +65,8 @@ from pcapkit.protocols.protocol import ProtocolBase #: Every key registered in :attr:`Extractor.__output__` and in - #: :attr:`TraceFlowBase.__output__ - #: ` -- both + #: :attr:`TraceFlow.__output__ + #: ` -- both #: registries expose the same keys, so ``'cap'`` and the ``'txt'``/``'xml'`` #: aliases are spellable for a type checker as they are accepted at runtime. Formats = Literal['pcap', 'cap', 'json', 'tree', 'text', 'txt', 'plist', 'xml'] diff --git a/pcapkit/foundation/reassembly/data/data.py b/pcapkit/foundation/reassembly/data/data.py index 24ebe0a61f..d686093021 100644 --- a/pcapkit/foundation/reassembly/data/data.py +++ b/pcapkit/foundation/reassembly/data/data.py @@ -30,9 +30,9 @@ class Completion(EnumLookup, StrEnum): which widens a plain :obj:`bool` rather than adding a second channel beside it: reassembly has more outcomes to report than a :obj:`bool` can hold, since a buffer abandoned under the :rfc:`1122` / :rfc:`8200` reassembly timeout is - a different event from one that simply had not finished when the capture did. An expired - datagram says "these fragments are gone", a partial one says "these fragments - had not arrived yet". + a different event from one that simply had not finished when the capture + did. An expired datagram says "these fragments are gone", a partial one says + "these fragments had not arrived yet". Truthiness is that of a :obj:`bool`: :attr:`COMPLETE` is the only truthy member, so ``if datagram.completed:`` reads as it would for a boolean. diff --git a/pcapkit/foundation/reassembly/reassembly.py b/pcapkit/foundation/reassembly/reassembly.py index 604e904410..feb3ffdc0a 100644 --- a/pcapkit/foundation/reassembly/reassembly.py +++ b/pcapkit/foundation/reassembly/reassembly.py @@ -115,11 +115,13 @@ class ReassemblyBase(Generic[_PT, _DT, _IT, _BT], metaclass=ReassemblyMeta): contiguous payload, its holes zero-filled -- or, for IP while the total length is unknown, only the prefix up to the first hole store: if store reassembled datagram in memory, i.e., - :attr:`self._dtgram ` + :attr:`self._dtgram + ` (if not, datagram will be discarded after callback) timeout: reassembly timeout in seconds, measured on the - *capture's* clock; :data:`None` selects the protocol's - own :attr:`__timeout__ ` + *capture's* clock; :data:`None` selects the protocol's own + :attr:`__timeout__ + ` default Note: diff --git a/pcapkit/foundation/traceflow/traceflow.py b/pcapkit/foundation/traceflow/traceflow.py index e232a34e63..f1f41c38ad 100644 --- a/pcapkit/foundation/traceflow/traceflow.py +++ b/pcapkit/foundation/traceflow/traceflow.py @@ -102,7 +102,7 @@ def registry(cls) -> 'dict[str, ModuleDescriptor[TraceFlowBase] | Type[TraceFlow so it is available as a class attribute and **not** on an instance. This is *not* :attr:`TraceFlow.__output__ - `, + `, which is the separate output-dumper table this class also owns. """ diff --git a/pcapkit/interface/misc.py b/pcapkit/interface/misc.py index 1bfea72628..3fa29338b3 100644 --- a/pcapkit/interface/misc.py +++ b/pcapkit/interface/misc.py @@ -41,8 +41,8 @@ #: #: The keys are those of :attr:`Extractor.__output__ #: ` and of - #: :attr:`TraceFlowBase.__output__ - #: ` -- both + #: :attr:`TraceFlow.__output__ + #: ` -- both #: registries expose the same keys, the aliases among them resolving to a shared #: dumper (``'cap'`` to ``'pcap'``, ``'xml'`` to ``'plist'``, ``'text'`` and ``'txt'`` #: to ``'tree'``), so the ``Literal`` lists every key for a type checker to accept diff --git a/pcapkit/toolkit/dpkt.py b/pcapkit/toolkit/dpkt.py index bde5196dc1..ab109b4c64 100644 --- a/pcapkit/toolkit/dpkt.py +++ b/pcapkit/toolkit/dpkt.py @@ -202,7 +202,11 @@ def packet2timestamp(packet: 'Packet') -> 'float': :class:`~pcapkit.foundation.engines.dpkt.DPKT`. Returns: - Capture timestamp of the packet, in seconds since the epoch. + Capture timestamp of the packet, in seconds since the epoch. As + :class:`~pcapkit.foundation.engines.dpkt.DPKT` attaches it, that is a + :class:`Timestamp`, or for a nanosecond PCAP file a + :class:`DecimalTimestamp`, which is a :class:`~decimal.Decimal` + subclass rather than a :obj:`float`. Raises: UnsupportedCall: If the packet carries no timestamp, i.e. it did not come diff --git a/pcapkit/toolkit/pcap_ct.py b/pcapkit/toolkit/pcap_ct.py index cbe2fda89d..d9f674f428 100644 --- a/pcapkit/toolkit/pcap_ct.py +++ b/pcapkit/toolkit/pcap_ct.py @@ -25,11 +25,11 @@ .. seealso:: - :mod:`pcapkit.toolkit.pypcap` is the same adapter for upstream `PyPCAP`_. The - two are kept apart because the engines are: the distributions are independent - projects that happen to share the :mod:`pcap` module name, and each engine - names its own adapter so that a change made for one cannot quietly alter the - other. + :mod:`pcapkit.toolkit.pypcap` is the same adapter for upstream `PyPCAP`_, + kept apart from this module because the engines are: the distributions are + independent projects that happen to share the :mod:`pcap` module name, and + each engine names its own adapter so that a change made for one cannot + quietly alter the other. .. _PyPCAP: https://github.com/pynetwork/pypcap diff --git a/pcapkit/utilities/exceptions.py b/pcapkit/utilities/exceptions.py index a9344ee250..367fa81865 100644 --- a/pcapkit/utilities/exceptions.py +++ b/pcapkit/utilities/exceptions.py @@ -392,10 +392,11 @@ def _install_excepthook() -> 'None': :data:`sys.excepthook` or :data:`threading.excepthook` touched merely for having imported the package. - Idempotent for *this* module instance, independently for each hook: if a given hook slot already holds this instance's own function -- - checked by identity, ``is``, not by the ``installed_by_pcapkit`` marker -- - installing it again does nothing, so a loud error later in the same run - never wraps either hook in another copy of itself. That distinction matters + Idempotent for *this* module instance, independently for each hook: if a + given hook slot already holds this instance's own function -- checked by + identity, ``is``, not by the ``installed_by_pcapkit`` marker -- installing + it again does nothing, so a loud error later in the same run never wraps + either hook in another copy of itself. That distinction matters because the identical check is wrong one level up: a hook slot already holding *some* function of the same name from an *earlier* loaded copy of this module -- a genuine :func:`importlib.reload`, or a test harness @@ -716,8 +717,8 @@ class EnumKeyError(BaseError, KeyError): circumstances, and raise it from this module rather than as a builtin. Deriving from :exc:`KeyError` is what makes that ruling cheap to carry out: - the in-library call sites that catch - :meth:`~pcapkit.corekit.enum.EnumLookup.get`'s name miss as :exc:`KeyError` -- :meth:`~pcapkit.const.http.method.Method.get` + the in-library call sites catch :meth:`~pcapkit.corekit.enum.EnumLookup.get`'s + name miss as :exc:`KeyError` -- :meth:`~pcapkit.const.http.method.Method.get` catches it in order to build an unregistered member, so for that one a failed name lookup is part of a successful call -- and every one of them keeps catching. diff --git a/pcapkit/utilities/warnings.py b/pcapkit/utilities/warnings.py index 3af4b1b41f..b5595ffd8a 100644 --- a/pcapkit/utilities/warnings.py +++ b/pcapkit/utilities/warnings.py @@ -52,7 +52,7 @@ ``logging.getLogger('pcapkit').setLevel(logging.ERROR)``. The one exception is a parse :mod:`pcapkit` runs on trial: :class:`hold_warnings` -holds back its warnings, on both channels, until the parse is kept, and drops +holds back its warnings, on each channel, until the parse is kept, and drops them if it is discarded (:issue:`1580`). .. versionremoved:: 1.5.0 From af77aaceb920b3e545b96128ca36f360801db16a Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Fri, 16 Oct 2026 16:58:41 -0400 Subject: [PATCH 3/3] docs(foundation): resolve the Completion cross-references (#1103) - Retarget the TIMEOUT comment's timeout link to Reassembly.timeout - Spell compat's StrEnum, aenum and __str__ as literals, since none of them is a documented object - Qualify the TLSKeyLabel isinstance claim to Python 3.11 and later, where compat.StrEnum is the stdlib class rather than aenum's - EnumKeyError: link Method and spell its private get as a literal Refs #1103 --- pcapkit/foundation/reassembly/data/data.py | 16 +++++++++------- pcapkit/utilities/exceptions.py | 6 +++--- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/pcapkit/foundation/reassembly/data/data.py b/pcapkit/foundation/reassembly/data/data.py index d686093021..26ca5e29ea 100644 --- a/pcapkit/foundation/reassembly/data/data.py +++ b/pcapkit/foundation/reassembly/data/data.py @@ -41,7 +41,7 @@ class Completion(EnumLookup, StrEnum): so a caller comparing against a boolean has to compare against a member instead. - It derives from :class:`~pcapkit.utilities.compat.StrEnum`, as + It derives from :mod:`pcapkit.utilities.compat`'s ``StrEnum``, as :class:`~pcapkit.protocols.application.httpv1.Type` does, which buys what a plain :class:`enum.Enum` does not: the value survives :func:`json.dumps` -- a plain enumeration raises :exc:`TypeError` there, and @@ -50,11 +50,12 @@ class Completion(EnumLookup, StrEnum): state can be tested for without importing this class. :class:`pcapkit.const.pcapng.tls_key_label.TLSKeyLabel` is the same kind of string enumeration, but it is generated like its :mod:`pcapkit.const.pcapng` - siblings (:issue:`886`), so it derives from :class:`aenum`'s own + siblings (:issue:`886`), so it derives from ``aenum``'s own ``StrEnum`` (via :class:`~pcapkit.corekit.enum.EnumRegistry`). The properties above hold for it, as ``aenum.StrEnum`` is a :class:`str` subclass, but ``isinstance``/``issubclass`` against - :class:`pcapkit.utilities.compat.StrEnum` does not. + ``pcapkit.utilities.compat.StrEnum`` does not on Python 3.11 and later, + where that name is the stdlib ``enum.StrEnum``. Warning: Being a :class:`str` whose :attr:`PARTIAL` and :attr:`TIMEOUT` members are @@ -77,7 +78,8 @@ class Completion(EnumLookup, StrEnum): #: Reassembly was **abandoned** under the reassembly timeout, i.e. the #: capture clock advanced past the deadline of - #: :attr:`Reassembly.timeout ` + #: :attr:`Reassembly.timeout + #: ` #: seconds after the first-arriving fragment while the datagram was still #: incomplete. :rfc:`8200#section-4.5` requires the held fragments be #: discarded, so no further fragment will ever be added to this datagram. @@ -92,9 +94,9 @@ def __bool__(self) -> 'bool': Note: This override is what a :class:`str` base does *not* give -- every non-empty string is otherwise truthy, which would make an incomplete - datagram read as a complete one. :meth:`__str__` needs no such - override: :class:`~pcapkit.utilities.compat.StrEnum` already renders a - member as its value. + datagram read as a complete one. ``__str__`` needs no such + override: :mod:`pcapkit.utilities.compat`'s ``StrEnum`` already + renders a member as its value. """ return self is Completion.COMPLETE diff --git a/pcapkit/utilities/exceptions.py b/pcapkit/utilities/exceptions.py index 367fa81865..338d086104 100644 --- a/pcapkit/utilities/exceptions.py +++ b/pcapkit/utilities/exceptions.py @@ -718,9 +718,9 @@ class EnumKeyError(BaseError, KeyError): Deriving from :exc:`KeyError` is what makes that ruling cheap to carry out: the in-library call sites catch :meth:`~pcapkit.corekit.enum.EnumLookup.get`'s - name miss as :exc:`KeyError` -- :meth:`~pcapkit.const.http.method.Method.get` - catches it in order to build an unregistered member, so for that one a - failed name lookup is part of a successful call -- and every one of them + name miss as :exc:`KeyError` -- :class:`~pcapkit.const.http.method.Method`'s + ``get`` catches it in order to build an unregistered member, so for that one + a failed name lookup is part of a successful call -- and every one of them keeps catching. Note: