From 42cc77f7556a650d7fe17215d7a6e65f0e7d4ca6 Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Sun, 4 Oct 2026 18:32:40 -0400 Subject: [PATCH] docs(foundation): correct the registrar contracts and cut timed context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `Extractor.register_engine`, `register_reassembly` and `register_traceflow` said the argument must be an `Engine`, `Reassembly` or `TraceFlow` subclass. The checks are `issubclass(..., EngineBase / ReassemblyBase / TraceFlowBase)` (`extraction.py:452`, `:491`, `:530`), and the shipped classes derive from the base directly, so the prose understated what is accepted. The wrapper in `registry/foundation.py` carried the same error. - `register_protocol` and nine argument descriptions said `Protocol` where the check is `issubclass(protocol, ProtocolBase)`. - All four `Extractor.register_*` methods raise `RegistryError` on a bad class and emit `RegistryWarning` on an overwrite, and documented neither. Added `Raises:` and `Warns:` sections, which is why this slice adds lines rather than cutting. - `Deferred`'s docstring said TCP reassembly builds `packet` eagerly and that deferring it was left for its own change. `reassembly/tcp.py` already passes `packet=Deferred(...)`, so the claim was false. - Cuts timed context per #719 — "currently", "today", "at the time of writing", "yet", and the "used to"/"no longer"/"previously" history in `Completion`, the `conflict` docs, the reassembly comments and the `trace_format` notes. The `no_eof` behaviour-change note and its measurement are kept as a version-bounded note, citation included. - Brings all four registrar docstrings to one phrasing. Two wrappers in `registry/foundation.py` still named `Reassembly`/`TraceFlow`, and the two `extraction.py` siblings still led with the public class, so one contract read three ways. Measured: **no** built-in class subclasses the public class -- 8 engines, 3 reassembly, 1 trace flow all derive from the base directly. - Repoints four `__callback_fn__` cross-references from `Reassembly`/`TraceFlow` to the concrete `IPv4`/`IPv6`/`TCP` classes. The attribute is in `Reassembly.__dict__` but not in `ReassemblyBase`, and each concrete class owns its own copy, so the docs named a different object from the one written to. - Notes that the `Type[Engine]` hint is narrower than the check, and that a non-class argument raises `TypeError` before `RegistryError` is reached. - Adds the four missing `.. autoattribute:: __callback_fn__` entries under `docs/source/pcapkit/foundation/`. Repointing the cross-references above left them dangling: the attribute was autodoc'd only on `Reassembly` and `TraceFlow`, and `autodoc_default_options` sets no `inherited-members`, so the new targets had no inventory entry and rendered as plain text. `nitpicky` is unset, so the build would not have complained. Verified by a Sphinx build: all four anchors now exist and `registry.html` links to them. No code changed: for all 21 files the AST with docstrings stripped is identical to `main`. Citations are frozen — 41 per-file multisets, byte-identical. `tests/project`: 268 passed, 1 skipped, 864 subtests passed. Part of #719. --- .../pcapkit/foundation/reassembly/ip/ipv4.rst | 2 + .../pcapkit/foundation/reassembly/ip/ipv6.rst | 2 + .../pcapkit/foundation/reassembly/tcp.rst | 2 + .../pcapkit/foundation/traceflow/tcp.rst | 2 + pcapkit/foundation/engines/_pcap_backend.py | 2 +- pcapkit/foundation/engines/dpkt.py | 2 +- pcapkit/foundation/engines/engine.py | 18 ++-- pcapkit/foundation/engines/pcap_ct.py | 4 +- pcapkit/foundation/engines/pypcap.py | 2 +- pcapkit/foundation/engines/pypcapfile.py | 2 +- pcapkit/foundation/engines/pyshark.py | 11 ++- pcapkit/foundation/engines/scapy.py | 4 +- pcapkit/foundation/extraction.py | 63 ++++++++++--- pcapkit/foundation/reassembly/data/data.py | 67 +++++++------- pcapkit/foundation/reassembly/data/ip.py | 12 +-- pcapkit/foundation/reassembly/data/tcp.py | 4 +- pcapkit/foundation/reassembly/ip.py | 7 +- pcapkit/foundation/reassembly/ipv6.py | 13 ++- pcapkit/foundation/reassembly/reassembly.py | 8 +- pcapkit/foundation/reassembly/tcp.py | 7 +- pcapkit/foundation/registry/foundation.py | 43 +++++---- pcapkit/foundation/registry/protocols.py | 90 +++++++++---------- pcapkit/foundation/traceflow/data/tcp.py | 5 +- pcapkit/foundation/traceflow/tcp.py | 10 +-- pcapkit/foundation/traceflow/traceflow.py | 8 +- 25 files changed, 214 insertions(+), 176 deletions(-) diff --git a/docs/source/pcapkit/foundation/reassembly/ip/ipv4.rst b/docs/source/pcapkit/foundation/reassembly/ip/ipv4.rst index fd5f3736de..21bec1d165 100644 --- a/docs/source/pcapkit/foundation/reassembly/ip/ipv4.rst +++ b/docs/source/pcapkit/foundation/reassembly/ip/ipv4.rst @@ -14,6 +14,8 @@ origin. Please refer to :doc:`ip` for more information. .. autoattribute:: __protocol_name__ .. autoattribute:: __protocol_type__ + .. autoattribute:: __callback_fn__ + :no-value: Terminology ----------- diff --git a/docs/source/pcapkit/foundation/reassembly/ip/ipv6.rst b/docs/source/pcapkit/foundation/reassembly/ip/ipv6.rst index 15194e91c9..4283844ab9 100644 --- a/docs/source/pcapkit/foundation/reassembly/ip/ipv6.rst +++ b/docs/source/pcapkit/foundation/reassembly/ip/ipv6.rst @@ -14,6 +14,8 @@ origin. Please refer to :doc:`ip` for more information. .. autoattribute:: __protocol_name__ .. autoattribute:: __protocol_type__ + .. autoattribute:: __callback_fn__ + :no-value: Terminology ----------- diff --git a/docs/source/pcapkit/foundation/reassembly/tcp.rst b/docs/source/pcapkit/foundation/reassembly/tcp.rst index ae331faed5..8658b250a7 100644 --- a/docs/source/pcapkit/foundation/reassembly/tcp.rst +++ b/docs/source/pcapkit/foundation/reassembly/tcp.rst @@ -15,6 +15,8 @@ which reconstructs fragmented TCP packets back to origin. .. autoattribute:: __protocol_name__ .. autoattribute:: __protocol_type__ + .. autoattribute:: __callback_fn__ + :no-value: Algorithm ========= diff --git a/docs/source/pcapkit/foundation/traceflow/tcp.rst b/docs/source/pcapkit/foundation/traceflow/tcp.rst index cff16823b4..e62ff397a9 100644 --- a/docs/source/pcapkit/foundation/traceflow/tcp.rst +++ b/docs/source/pcapkit/foundation/traceflow/tcp.rst @@ -21,6 +21,8 @@ TCP flows from a series of packets and connections. .. autoattribute:: __protocol_name__ .. autoattribute:: __protocol_type__ + .. autoattribute:: __callback_fn__ + :no-value: Terminology ----------- diff --git a/pcapkit/foundation/engines/_pcap_backend.py b/pcapkit/foundation/engines/_pcap_backend.py index 53576355c0..326bc09bb6 100644 --- a/pcapkit/foundation/engines/_pcap_backend.py +++ b/pcapkit/foundation/engines/_pcap_backend.py @@ -112,7 +112,7 @@ def identify(module: 'ModuleType') -> 'str': is a structural difference rather than a cosmetic one, which is why it is preferred here over the alternatives: - * ``pcap.__version__`` is ``1.3.0b3`` against ``1.3.0`` today, but that is a + * ``pcap.__version__`` is ``1.3.0b3`` against ``1.3.0`` upstream, but that is a coincidence of release timing and would stop separating them the moment ``pcap-ct`` cuts a 1.3.0 final. * ``pcap.ex_name`` looked like a ``pcap-ct`` marker and is **not** -- measured diff --git a/pcapkit/foundation/engines/dpkt.py b/pcapkit/foundation/engines/dpkt.py index 623e55721c..e6f791fbb7 100644 --- a/pcapkit/foundation/engines/dpkt.py +++ b/pcapkit/foundation/engines/dpkt.py @@ -87,7 +87,7 @@ def run(self) -> 'None': Warns: AttributeWarning: If :attr:`self.extractor._exlyr ` and/or :attr:`self.extractor._exptl ` - is provided as the DPKT engine currently does not support such operations; + is provided as the DPKT engine does not support such operations; or if :attr:`self.extractor._exctx ` is provided, as the DPKT engine does not parse with :mod:`pcapkit`'s own protocol implementations. diff --git a/pcapkit/foundation/engines/engine.py b/pcapkit/foundation/engines/engine.py index 822acf7a5b..a13c399e18 100644 --- a/pcapkit/foundation/engines/engine.py +++ b/pcapkit/foundation/engines/engine.py @@ -271,14 +271,14 @@ def __init_subclass__(cls, /, engine: 'Optional[str]' = None, *args: 'Any', **kw registered; only the keyword decides registration. Note: - This keyword was ``name`` when opt-in registration landed, and was - renamed because ``name`` 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 + The keyword is ``engine`` rather than ``name`` because ``name`` + 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 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 now works on every + 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 surface. Separately, and for an unrelated reason that holds on every @@ -294,10 +294,10 @@ def __init_subclass__(cls, /, engine: 'Optional[str]' = None, *args: 'Any', **kw # NOTE: an unrecognised class keyword lands in ``**kwargs`` and is then # dropped by the bare ``super().__init_subclass__()`` below, since # ``object.__init_subclass__`` takes none. Silently swallowing it is how - # ``class MyEngine(Engine, engnie='x')`` used to register under its class - # name instead -- no exception, no warning. Now that a missing keyword - # means "do not register", the same typo would silently skip - # registration altogether, which is quieter still. So reject it. + # ``class MyEngine(Engine, engnie='x')`` would register under its class + # name instead -- no exception, no warning. Since a missing keyword means + # "do not register", the same typo would silently skip registration + # altogether, which is quieter still. 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 diff --git a/pcapkit/foundation/engines/pcap_ct.py b/pcapkit/foundation/engines/pcap_ct.py index c22414f2b4..1b4493c944 100644 --- a/pcapkit/foundation/engines/pcap_ct.py +++ b/pcapkit/foundation/engines/pcap_ct.py @@ -95,7 +95,7 @@ class PCAP_CT(EngineBase['RawFrame']): .. important:: Both distributions are published as pre-releases only -- ``pcap-ct`` - 1.3.0b3 and ``libpcap`` 1.11.0b29 at the time of writing -- and + 1.3.0b3 and ``libpcap`` 1.11.0b29 when measured -- and ``pcap-ct`` documents itself as tracking the `PyPCAP`_ **1.2.3** API rather than 1.3.0. Every attribute this engine touches is present and behaves identically on both (measured on ``pcap-ct`` 1.3.0b3 against @@ -318,7 +318,7 @@ def run(self) -> 'None': * if :attr:`self.extractor._exlyr ` and/or :attr:`self.extractor._exptl ` - is provided as the pcap-ct engine currently does not + is provided as the pcap-ct engine does not support such operations. * if reassembly and/or flow tracing is enabled, as the pcap-ct engine performs no protocol dissection and so cannot support diff --git a/pcapkit/foundation/engines/pypcap.py b/pcapkit/foundation/engines/pypcap.py index 604849e47d..067bdda3ec 100644 --- a/pcapkit/foundation/engines/pypcap.py +++ b/pcapkit/foundation/engines/pypcap.py @@ -259,7 +259,7 @@ def run(self) -> 'None': * if :attr:`self.extractor._exlyr ` and/or :attr:`self.extractor._exptl ` - is provided as the PyPCAP engine currently does not + is provided as the PyPCAP engine does not support such operations. * if reassembly and/or flow tracing is enabled, as the PyPCAP engine performs no protocol dissection and so cannot support diff --git a/pcapkit/foundation/engines/pypcapfile.py b/pcapkit/foundation/engines/pypcapfile.py index d3000e8f2c..f13c6cb704 100644 --- a/pcapkit/foundation/engines/pypcapfile.py +++ b/pcapkit/foundation/engines/pypcapfile.py @@ -200,7 +200,7 @@ def run(self) -> 'None': * if :attr:`self.extractor._exlyr ` and/or :attr:`self.extractor._exptl ` - is provided as the PyPCAPFile engine currently does not + is provided as the PyPCAPFile engine does not support such operations. * if IPv6 reassembly is enabled, as :mod:`pcapfile` has no IPv6 decoder. diff --git a/pcapkit/foundation/engines/pyshark.py b/pcapkit/foundation/engines/pyshark.py index b1723be67c..f0629e472e 100644 --- a/pcapkit/foundation/engines/pyshark.py +++ b/pcapkit/foundation/engines/pyshark.py @@ -87,10 +87,9 @@ def unsupported_reason(cls) -> 'Optional[str]': 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 - not available on the machine this was measured on; it is expected to work, - since it is on the deprecated-but-functional side of that progression, and - that expectation is the one thing here that is inferred rather than - observed. + not measured; it is expected to work, since it is on the + deprecated-but-functional side of that progression, and that expectation + is the one thing here that is inferred rather than observed. **The** :program:`tshark` **binary.** ``pyshark`` is a wrapper around Wireshark's command-line tool and does no parsing itself, so it is useless @@ -182,9 +181,9 @@ def run(self) -> 'None': * if :attr:`self.extractor._exlyr ` and/or :attr:`self.extractor._exptl ` - is provided as the PyShark engine currently does not + is provided as the PyShark engine does not support such operations. - * if reassembly is enabled, as the PyShark engine currently + * if reassembly is enabled, as the PyShark engine does not support such operation. * if :attr:`self.extractor._exctx ` is provided, as the PyShark engine does not parse with diff --git a/pcapkit/foundation/engines/scapy.py b/pcapkit/foundation/engines/scapy.py index ae7aa38e38..903d45bcac 100644 --- a/pcapkit/foundation/engines/scapy.py +++ b/pcapkit/foundation/engines/scapy.py @@ -103,7 +103,7 @@ def __init__(self, extractor: 'Extractor') -> 'None': # :class:`~scapy.utils.PcapReader` then cannot map even link type 1 (plain # Ethernet), writes ``unknown LL type [1]/[0x1]`` to stderr and returns every # frame as one opaque :class:`~scapy.packet.Raw` layer. Nothing raises, so the - # engine used to deliver no dissection whatsoever and announce it only on + # engine would deliver no dissection whatsoever and announce it only on # stderr (#406). # # Naming the layer modules individually is not a cheaper way to the same @@ -145,7 +145,7 @@ def run(self) -> 'None': Warns: AttributeWarning: If :attr:`self.extractor._exlyr ` and/or :attr:`self.extractor._exptl ` - is provided as the Scapy engine currently does not support such operations; + is provided as the Scapy engine does not support such operations; or if :attr:`self.extractor._exctx ` is provided, as the Scapy engine does not parse with :mod:`pcapkit`'s own protocol implementations. diff --git a/pcapkit/foundation/extraction.py b/pcapkit/foundation/extraction.py index a5d0847fc3..45850be05e 100644 --- a/pcapkit/foundation/extraction.py +++ b/pcapkit/foundation/extraction.py @@ -64,9 +64,8 @@ #: Every key registered in :attr:`Extractor.__output__` and in #: :attr:`TraceFlowBase.__output__ #: ` -- the two - #: registries expose the same eight keys. This used to name only four of them, - #: which made ``'cap'`` and the ``'txt'``/``'xml'`` aliases unspellable for a - #: type checker even though every one of them is accepted at runtime. + #: registries expose the same eight 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 predates the @@ -394,6 +393,13 @@ def register_dumper(cls, format: 'str', dumper: 'ModuleDescriptor[Dumper] | Type 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. + """ if isinstance(dumper, ModuleDescriptor): dumper = dumper.klass @@ -422,7 +428,18 @@ def register_engine(cls, name: 'str', engine: 'ModuleDescriptor[Engine] | Type[E Arguments: name: engine name engine: module descriptor or an - :class:`~pcapkit.foundation.engines.engine.Engine` subclass + :class:`~pcapkit.foundation.engines.engine.EngineBase` subclass + (an :class:`~pcapkit.foundation.engines.engine.Engine` subclass + is one too, but no built-in engine is: they all derive from the + base directly); the ``Type[Engine]`` hint in the signature is + narrower than this check + + Raises: + RegistryError: If ``engine`` is not a class, or not an ``EngineBase`` subclass. + + Warns: + RegistryWarning: If a different class is already registered under + ``name``; it is overwritten. """ if isinstance(engine, ModuleDescriptor): @@ -457,7 +474,17 @@ def register_reassembly(cls, protocol: 'str', reassembly: 'ModuleDescriptor[Reas Arguments: protocol: protocol name reassembly: module descriptor or a - :class:`~pcapkit.foundation.reassembly.reassembly.Reassembly` subclass + :class:`~pcapkit.foundation.reassembly.reassembly.ReassemblyBase` + subclass (a :class:`~pcapkit.foundation.reassembly.reassembly.Reassembly` + subclass is one too, but no built-in reassembly class is: they all + derive from the base directly) + + Raises: + RegistryError: If ``reassembly`` is not a class, or not a ``ReassemblyBase`` subclass. + + Warns: + RegistryWarning: If a different class is already registered under + ``protocol``; it is overwritten. """ if isinstance(reassembly, ModuleDescriptor): @@ -488,7 +515,17 @@ def register_traceflow(cls, protocol: 'str', traceflow: 'ModuleDescriptor[TraceF Arguments: protocol: protocol name traceflow: module descriptor or a - :class:`~pcapkit.foundation.traceflow.traceflow.TraceFlow` subclass + :class:`~pcapkit.foundation.traceflow.traceflow.TraceFlowBase` + subclass (a :class:`~pcapkit.foundation.traceflow.traceflow.TraceFlow` + subclass is one too, but no built-in flow tracing class is: they all + derive from the base directly) + + Raises: + RegistryError: If ``traceflow`` is not a class, or not a ``TraceFlowBase`` subclass. + + Warns: + RegistryWarning: If a different class is already registered under + ``protocol``; it is overwritten. """ if isinstance(traceflow, ModuleDescriptor): @@ -982,7 +1019,7 @@ def __init__(self, # hides this defect rather than avoiding it: register the link types -- # as importing :mod:`scapy.all` does -- and the same ``AttributeError`` # appears. So the guard is written from what the adapters produce, not - # from which engines happen to crash today. + # from which engines happen to crash. if (self._exnam in ('dpkt', 'scapy', 'pyshark', 'pypcapfile') and trace_format in ('pcap', 'cap', None)): warn(f"'Extractor(engine={self._exnam})' does not support 'trace_format={trace_format}'; " @@ -1200,7 +1237,7 @@ def _owns_input(self) -> 'bool': 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 - cannot arise today, since :meth:`make_name` admits a path only through + cannot arise, since :meth:`make_name` admits a path only through :func:`os.path.isfile`, which is :data:`False` for a FIFO or a device, and a regular file is always seekable. The second test is therefore defensive rather than dead, and is the reason @@ -1313,11 +1350,11 @@ def _cleanup(self) -> 'None': self._trace.tcp.finish() # NOTE: *Ownership* decides who closes the input, not seekability -- - # see :meth:`_owns_input`. Before #610 this read ``not self._flag_s``, - # which got both halves wrong at once: the handle this class opened - # itself was never closed, leaking a descriptor and emitting the - # ``ResourceWarning`` #606 tripped over, while a stream the caller - # supplied and still needed *was* closed. + # see :meth:`_owns_input` and #610. Keying on seekability + # (``not self._flag_s``) gets both halves wrong at once: the handle this + # class opened itself is never closed, leaking a descriptor and emitting + # the ``ResourceWarning`` #606 tripped over, while a stream the caller + # supplied and still needs *is* closed. if self._owns_input(): self._ifile.close() self._exeng.close() diff --git a/pcapkit/foundation/reassembly/data/data.py b/pcapkit/foundation/reassembly/data/data.py index f8fdd5d1f0..967e0ecc9a 100644 --- a/pcapkit/foundation/reassembly/data/data.py +++ b/pcapkit/foundation/reassembly/data/data.py @@ -21,46 +21,40 @@ class Completion(EnumLookup, StrEnum): """How completely a datagram was reassembled, and why it stopped. - Re-parented onto :class:`~pcapkit.corekit.enum.EnumLookup` per GitHub - issue :issue:`877`'s ruling that every non-registry enumeration shares that - lookup contract -- pure re-parenting, since this class defines neither - ``get`` nor ``_missing_`` of its own to reconcile with the base. + Derives from :class:`~pcapkit.corekit.enum.EnumLookup` per GitHub issue + :issue:`877`'s ruling that every non-registry enumeration shares that lookup + contract; the class defines neither ``get`` nor ``_missing_`` of its own. This is the value of - :attr:`Datagram.completed `. - That field used to be a plain :obj:`bool`, and this enumeration is a widening - of it rather than a second channel beside it: reassembly now has *three* - outcomes to report, not two, since a buffer abandoned under the :rfc:`791` / - :rfc:`8200` reassembly timeout is a different event from one that simply had - not finished when the capture did. Telling them apart is the whole point of - having a timeout at all -- an expired datagram says "these fragments are - gone", a partial one says "these fragments had not arrived yet". - - Truthiness is preserved, so ``if datagram.completed:`` reads exactly as it - did while ``completed`` was a :obj:`bool`: :attr:`COMPLETE` is the only - truthy member. Equality against :obj:`True` and :obj:`False` is *not* - preserved -- ``datagram.completed == True`` is now :data:`False` even for a - complete datagram -- so a caller comparing against a boolean has to compare - against a member instead. + :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:`791` / :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". + + 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. + Equality against :obj:`True` and :obj:`False` is *not* preserved -- + ``datagram.completed == True`` is :data:`False` even for a complete datagram -- + so a caller comparing against a boolean has to compare against a member + 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 :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 the - new state can be tested for without importing this class. - :class:`~pcapkit.protocols.misc.pcapng.TLSKeyLabel` used to be a third - precedent for the same :class:`~pcapkit.utilities.compat.StrEnum` base, but - GitHub issue :issue:`886` moved its canonical definition to - :class:`pcapkit.const.pcapng.tls_key_label.TLSKeyLabel`, generated like its - :mod:`pcapkit.const.pcapng` siblings: it now derives from :class:`aenum`'s - own ``StrEnum`` (via :class:`~pcapkit.corekit.enum.EnumRegistry`) rather than - from :mod:`pcapkit.utilities.compat`'s version-branched one. Both - properties above still hold for it either way -- ``aenum.StrEnum`` is a - :class:`str` subclass same as the stdlib one -- but ``isinstance``/ - ``issubclass`` against :class:`pcapkit.utilities.compat.StrEnum` no longer - does, which is why it is called out here rather than left silently stale. + field straight out -- and ``datagram.completed == 'timeout'`` works, so a + 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 GitHub issue :issue:`886` made it generated like its + :mod:`pcapkit.const.pcapng` siblings, so it derives from :class:`aenum`'s own + ``StrEnum`` (via :class:`~pcapkit.corekit.enum.EnumRegistry`). Both + properties above hold for it, as ``aenum.StrEnum`` is a :class:`str` + subclass, but ``isinstance``/``issubclass`` against + :class:`pcapkit.utilities.compat.StrEnum` does not. Warning: Being a :class:`str` whose :attr:`PARTIAL` and :attr:`TIMEOUT` members are @@ -124,11 +118,10 @@ class Deferred: IPv4 frames, none of them fragmented, and the parse was 86% of the cost of IP reassembly over it. - TCP reassembly builds its ``packet`` eagerly too - (:meth:`TCP.submit `). It is a - far smaller cost there, being FIN/RST-driven rather than per-frame -- 222 - submits per :file:`http.pcap` pass against 1117 -- so it is left for its own - change, but it can use this unmodified when someone gets to it. + TCP reassembly defers its ``packet`` as well + (:meth:`TCP.submit `), at a far + smaller saving, being FIN/RST-driven rather than per-frame -- 222 submits per + :file:`http.pcap` pass against 1117. Holding the call here defers it to the first read of :attr:`Datagram.packet`, so a caller that wants the parsed payload still gets diff --git a/pcapkit/foundation/reassembly/data/ip.py b/pcapkit/foundation/reassembly/data/ip.py index 26598a570f..98bdcd0718 100644 --- a/pcapkit/foundation/reassembly/data/ip.py +++ b/pcapkit/foundation/reassembly/data/ip.py @@ -90,8 +90,8 @@ class Datagram(DeferredPacket, Info, Generic[_AT]): #: How completely the datagram was reassembled, and why reassembly stopped. #: Only :attr:`Completion.COMPLETE` is truthy, so ``if datagram.completed:`` - #: still reads as it did while this was a :obj:`bool`; equality against - #: :obj:`True` or :obj:`False` no longer holds. + #: 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]' @@ -136,15 +136,15 @@ class Datagram(DeferredPacket, Info, Generic[_AT]): if TYPE_CHECKING: # NOTE: one signature, not a pair of ``@overload``\\ s keyed on - # ``completed``. There used to be two, correlating a complete datagram with - # a ``bytes`` payload and a parsed ``packet``, and an incomplete one with a - # tuple of fragments and ``packet=None``. That correlation does not hold: + # ``completed``. Such a pair would correlate a complete datagram with a + # ``bytes`` payload and a parsed ``packet``, and an incomplete one with a + # tuple of fragments and ``packet=None``, but that correlation does not hold: # under ``strict=False`` an *incomplete* datagram is reported as one # contiguous ``bytes`` with its holes zero-filled, and analysed, because # that is the payload buffer as it stands -- which is what # :func:`~pcapkit.interface.misc.follow_tcp_stream` reconstructs a stream # from. Overloads keyed on a literal cannot be selected from a ``completed`` - # computed at runtime anyway, so they only made the reassemblers' own calls + # computed at runtime anyway, so they would make the reassemblers' own calls # untypeable while promising a correlation the code does not keep. def __init__(self, completed: 'Completion', id: 'DatagramID[_AT]', index: 'tuple[int, ...]', header: 'bytes', payload: 'bytes | tuple[bytes, ...]', packet: 'Optional[ProtocolBase | Deferred]', conflict: 'tuple[tuple[int, int], ...]') -> 'None': ... # pylint: disable=unused-argument,super-init-not-called,multiple-statements,line-too-long,redefined-builtin diff --git a/pcapkit/foundation/reassembly/data/tcp.py b/pcapkit/foundation/reassembly/data/tcp.py index feaf16a116..c3761f3d02 100644 --- a/pcapkit/foundation/reassembly/data/tcp.py +++ b/pcapkit/foundation/reassembly/data/tcp.py @@ -117,8 +117,8 @@ class Datagram(DeferredPacket, Info, Generic[_AT]): #: conflicting portion of whichever segment arrived later, per #: :rfc:`9293#section-3.10` ("we reconstruct the segment to contain just #: the new data"); this field is what lets a caller tell a clean stream - #: from a contested one now that :attr:`completed` no longer does, since a - #: contested range does not, on its own, leave a hole. + #: from a contested one, since a contested range does not, on its own, leave a + #: hole for :attr:`completed` to report. conflict: 'tuple[tuple[int, int], ...]' if TYPE_CHECKING: diff --git a/pcapkit/foundation/reassembly/ip.py b/pcapkit/foundation/reassembly/ip.py index 8fff49ec12..472dd50831 100644 --- a/pcapkit/foundation/reassembly/ip.py +++ b/pcapkit/foundation/reassembly/ip.py @@ -323,10 +323,9 @@ def submit(self, buf: 'Buffer[_AT]', *, bufid: 'tuple[_AT, _AT, int, TransType]' # zero-filled uses ``strict=True`` and gets the runs. stop = TDL else: - # The length is not known, and this is the case that used to slice - # ``datagram[:-1]`` -- handing back 65534 octets of the - # preallocated buffer, almost all of them zeros the sender never - # sent, and calling the result complete. + # The length is not known. Slicing ``datagram[:-1]`` here would + # hand back 65534 octets of the preallocated buffer, almost all of + # them zeros the sender never sent, and call the result complete. # # Reporting nothing at all would be the other extreme, and it # discards data that really did arrive. So report the **contiguous diff --git a/pcapkit/foundation/reassembly/ipv6.py b/pcapkit/foundation/reassembly/ipv6.py index 6959461252..e89ba2e97c 100644 --- a/pcapkit/foundation/reassembly/ipv6.py +++ b/pcapkit/foundation/reassembly/ipv6.py @@ -132,14 +132,11 @@ def _rectify_header(self, header: 'bytes', proto: 'TransType') -> 'bytes': :rfc:`8200#section-4.5` states that the Fragment header is not present in 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 advertises a Fragment header on a datagram that - is by definition no longer a fragment, which is what every engine used to - report -- the toolkit adapters differ over whether the Fragment header's - *octets* belong to ``header``, but none of them rewrote the field that - points at it. - - The Fragment header's own octets are already excluded by the adapters, so - only the field pointing at it is left to fix. + the reassembled datagram would advertise a Fragment header on a datagram + that is by definition no longer a fragment. The toolkit adapters differ + over whether the Fragment header's *octets* belong to ``header``, but none + rewrites the field that points at it; the octets are already excluded, so + only that field is left to fix. Args: header: Raw header octets of the fragment at fragment offset zero. diff --git a/pcapkit/foundation/reassembly/reassembly.py b/pcapkit/foundation/reassembly/reassembly.py index fe4fdff729..2e48160d2e 100644 --- a/pcapkit/foundation/reassembly/reassembly.py +++ b/pcapkit/foundation/reassembly/reassembly.py @@ -571,10 +571,10 @@ def __init_subclass__(cls, /, protocol: 'Optional[str]' = None, *args: 'Any', ** """ # NOTE: the keyword here is ``protocol``, but ``Engine`` spells the same # idea ``name`` -- so guessing ``name=`` by analogy is the expected - # mistake, not a careless one. It used to land in ``**kwargs``, get - # dropped by the bare ``super().__init_subclass__()`` below, and leave - # the class registered under its own class name instead: no exception, no - # warning. See the sibling note in ``Engine.__init_subclass__``. + # mistake, not a careless one. Left in ``**kwargs`` it would be + # dropped by the bare ``super().__init_subclass__()`` below, leaving the + # class registered under its own class name: no exception, no warning. + # See the sibling note in ``Engine.__init_subclass__``. if args or kwargs: unexpected = ', '.join([*map(repr, args), *sorted(kwargs)]) raise UnsupportedCall(f'{cls.__name__}: unexpected class keyword(s): {unexpected}') diff --git a/pcapkit/foundation/reassembly/tcp.py b/pcapkit/foundation/reassembly/tcp.py index fdf692a69b..3b256bd1b3 100644 --- a/pcapkit/foundation/reassembly/tcp.py +++ b/pcapkit/foundation/reassembly/tcp.py @@ -386,7 +386,7 @@ def _merge_overlap(gap: 'list[tuple[int, int]]', old: 'bytes', new: 'bytes', which is shared across every acknowledgement number under the same buffer ID: a *different* fragment closing a hole there says nothing about what *this* fragment has received, and - using it here previously discarded this fragment's own real + using it here would discard this fragment's own real bytes whenever another fragment happened to cover the same absolute sequence numbers first. old: already-buffered bytes of this fragment over the range. @@ -528,9 +528,8 @@ def submit(self, buf: 'Buffer', *, bufid: 'BufferID', # type: ignore[override] # NOTE: ``strict=False`` deliberately keeps reporting the whole # payload buffer with its holes zero-filled, which is what # :func:`~pcapkit.interface.misc.follow_tcp_stream` wants of a stream - # it is reconstructing best-effort. What changes is only that - # ``completed`` now says so: this branch used to report - # :attr:`Completion.COMPLETE` for a buffer it knew had holes in it. + # it is reconstructing best-effort. ``completed`` says so, rather than + # reporting :attr:`Completion.COMPLETE` for a buffer with holes in it. else: payload = buffer.raw if payload: # strip empty buffer diff --git a/pcapkit/foundation/registry/foundation.py b/pcapkit/foundation/registry/foundation.py index 427209c02e..5c52f59247 100644 --- a/pcapkit/foundation/registry/foundation.py +++ b/pcapkit/foundation/registry/foundation.py @@ -60,7 +60,7 @@ def register_extractor_engine(name: 'str', module: 'str', class_: 'str') -> 'Non # NOTE: pcapkit.foundation.extraction.Extractor.__engine__ def register_extractor_engine(name: 'str', module: 'ModuleDescriptor[Engine] | Type[Engine] | str', class_: 'str | NullType' = NULL) -> 'None': # pylint: disable=redefined-builtin - r"""Registered a new engine class. + r"""Register a new engine class. Notes: The full qualified class name of the new engine class @@ -72,7 +72,10 @@ def register_extractor_engine(name: 'str', module: 'ModuleDescriptor[Engine] | T Arguments: name: engine name module: module name or module descriptor or an - :class:`~pcapkit.foundation.engines.engine.Engine` subclass + :class:`~pcapkit.foundation.engines.engine.EngineBase` subclass + (an :class:`~pcapkit.foundation.engines.engine.Engine` subclass + is one too, but no built-in engine is: they all derive from the + base directly) class\_: class name """ @@ -96,7 +99,7 @@ def register_dumper(format: 'str', module: 'str', class_: 'str', *, ext: 'str') def register_dumper(format: 'str', module: 'ModuleDescriptor[Dumper] | Type[Dumper] | str', class_: 'str | NullType' = NULL, *, ext: 'str') -> 'None': # pylint: disable=redefined-builtin - r"""Registered a new dumper class. + r"""Register a new dumper class. Notes: The full qualified class name of the new dumper class @@ -135,7 +138,7 @@ def register_extractor_dumper(format: 'str', module: 'str', class_: 'str', *, ex # NOTE: pcapkit.foundation.extraction.Extractor.__output__ def register_extractor_dumper(format: 'str', module: 'ModuleDescriptor[Dumper] | Type[Dumper] | str', class_: 'str | NullType' = NULL, *, ext: 'str') -> 'None': # pylint: disable=redefined-builtin - r"""Registered a new dumper class. + r"""Register a new dumper class. Notes: The full qualified class name of the new dumper class @@ -168,7 +171,7 @@ def register_traceflow_dumper(format: 'str', module: 'str', class_: 'str', *, ex # NOTE: pcapkit.foundation.traceflow.traceflow.TraceFlow.__output__ def register_traceflow_dumper(format: 'str', module: 'ModuleDescriptor[Dumper] | Type[Dumper] | str', class_: 'str | NullType' = NULL, *, ext: 'str') -> 'None': # pylint: disable=redefined-builtin - r"""Registered a new dumper class. + r"""Register a new dumper class. Notes: The full qualified class name of the new dumper class @@ -199,10 +202,10 @@ def register_traceflow_dumper(format: 'str', module: 'ModuleDescriptor[Dumper] | # NOTE: pcapkit.foundation.reassembly.ipv4.IPv4.__callback_fn__ def register_reassembly_ipv4_callback(callback: 'Reasm_CallbackFn') -> 'None': - """Registered a new callback function. + """Register a new callback function. The function will register the given callback function to the - :attr:`IPv4.__callback_fn__ ` + :attr:`IPv4.__callback_fn__ ` registry. Arguments: @@ -215,10 +218,10 @@ def register_reassembly_ipv4_callback(callback: 'Reasm_CallbackFn') -> 'None': # NOTE: pcapkit.foundation.reassembly.ipv6.IPv6.__callback_fn__ def register_reassembly_ipv6_callback(callback: 'Reasm_CallbackFn') -> 'None': - """Registered a new callback function. + """Register a new callback function. The function will register the given callback function to the - :attr:`IPv6.__callback_fn__ ` + :attr:`IPv6.__callback_fn__ ` registry. Arguments: @@ -231,10 +234,10 @@ def register_reassembly_ipv6_callback(callback: 'Reasm_CallbackFn') -> 'None': # NOTE: pcapkit.foundation.reassembly.tcp.TCP.__callback_fn__ def register_reassembly_tcp_callback(callback: 'Reasm_CallbackFn') -> 'None': - """Registered a new callback function. + """Register a new callback function. The function will register the given callback function to the - :attr:`TCP.__callback_fn__ ` + :attr:`TCP.__callback_fn__ ` registry. Arguments: @@ -247,10 +250,10 @@ def register_reassembly_tcp_callback(callback: 'Reasm_CallbackFn') -> 'None': # NOTE: pcapkit.foundation.traceflow.tcp.TCP.__callback_fn__ def register_traceflow_tcp_callback(callback: 'Trace_CallbackFn') -> 'None': - """Registered a new callback function. + """Register a new callback function. The function will register the given callback function to the - :attr:`TCP.__callback_fn__ ` + :attr:`TCP.__callback_fn__ ` registry. Arguments: @@ -275,7 +278,7 @@ def register_extractor_reassembly(protocol: 'str', module: 'str', class_: 'str') # NOTE: pcapkit.foundation.extraction.Extractor.__reassembly__ def register_extractor_reassembly(protocol: 'str', module: 'str | ModuleDescriptor[Reassembly] | Type[Reassembly]', class_: 'str | NullType' = NULL) -> 'None': # pylint: disable=redefined-builtin - r"""Registered a new reassembly class. + r"""Register a new reassembly class. Notes: The full qualified class name of the new reassembly class @@ -287,7 +290,10 @@ def register_extractor_reassembly(protocol: 'str', module: 'str | ModuleDescript Arguments: protocol: protocol name module: module name or module descriptor or a - :class:`~pcapkit.foundation.reassembly.reassembly.Reassembly` subclass + :class:`~pcapkit.foundation.reassembly.reassembly.ReassemblyBase` subclass + (a :class:`~pcapkit.foundation.reassembly.reassembly.Reassembly` + subclass is one too, but no built-in reassembly class is: they all + derive from the base directly) class\_: class name """ @@ -307,7 +313,7 @@ def register_extractor_traceflow(protocol: 'str', module: 'str', class_: 'str') # NOTE: pcapkit.foundation.extraction.Extractor.__traceflow__ def register_extractor_traceflow(protocol: 'str', module: 'str | ModuleDescriptor[TraceFlow] | Type[TraceFlow]', class_: 'str | NullType' = NULL) -> 'None': # pylint: disable=redefined-builtin - r"""Registered a new flow tracing class. + r"""Register a new flow tracing class. Notes: The full qualified class name of the new flow tracing class @@ -319,7 +325,10 @@ def register_extractor_traceflow(protocol: 'str', module: 'str | ModuleDescripto Arguments: protocol: protocol name module: module name or module descriptor or a - :class:`~pcapkit.foundation.traceflow.traceflow.TraceFlow` subclass + :class:`~pcapkit.foundation.traceflow.traceflow.TraceFlowBase` subclass + (a :class:`~pcapkit.foundation.traceflow.traceflow.TraceFlow` + subclass is one too, but no built-in flow tracing class is: they all + derive from the base directly) class\_: class name """ diff --git a/pcapkit/foundation/registry/protocols.py b/pcapkit/foundation/registry/protocols.py index 014450ffea..f7f3ccd87d 100644 --- a/pcapkit/foundation/registry/protocols.py +++ b/pcapkit/foundation/registry/protocols.py @@ -144,10 +144,10 @@ # NOTE: pcapkit.protocols.__proto__ def register_protocol(protocol: 'Type[ProtocolBase]') -> 'None': - """Registered protocol class. + """Register a protocol class. The protocol class must be a subclass of - :class:`~pcapkit.protocols.protocol.Protocol`, and will be registered to + :class:`~pcapkit.protocols.protocol.ProtocolBase`, and will be registered to the :data:`pcapkit.protocols.__proto__` registry. The registry is keyed on ``protocol.__name__.upper()``, which is **not** @@ -160,29 +160,26 @@ def register_protocol(protocol: 'Type[ProtocolBase]') -> 'None': was there, and the replacement is observable through every reader of the registry, e.g. :meth:`ProtocolBase.expand_comp `, which resolves a - bare protocol name through it. - - Per :issue:`675` the overwrite is now reported rather than silent, matching - :meth:`ProtocolBase.register + bare protocol name through it. Per :issue:`675` the overwrite is reported, + matching :meth:`ProtocolBase.register ` and the other overwrite-warning registries. - The guard here reads "key present **and** incumbent is a different - class" -- presence alone is not enough. This registry's key is *derived* - from the class rather than supplied by a caller, and this function is the - funnel every wrapper registrar calls -- :func:`register_tcp`, - :func:`register_udp`, :func:`register_apptype`, :func:`register_linktype` - and the rest all end in ``register_protocol(module)``. So registering one - class under two codes, a supported and documented thing to do, reaches - this function twice with the same class and nothing at stake; a - presence-only guard would warn about an overwrite that overwrote nothing. - Warning on the harmless case is not free: it is what teaches a caller to - filter :exc:`~pcapkit.utilities.warnings.RegistryWarning` wholesale, and - that filter is what would then hide the ``HTTP`` collision this warning - exists to surface. The sibling ``register`` methods across the package -- - each keyed on a caller-supplied ``code`` rather than a name derived from - the class -- apply the same identity criterion as of GitHub issue :issue:`718`; - before that they warned on presence alone, and none of them does now. + The guard reads "key present **and** incumbent is a different class" -- + presence alone is not enough. This registry's key is *derived* from the + class rather than supplied by a caller, and this function is the funnel + every wrapper registrar calls -- :func:`register_tcp`, :func:`register_udp`, + :func:`register_apptype`, :func:`register_linktype` and the rest all end in + ``register_protocol(module)``. So registering one class under two codes, a + supported and documented thing to do, reaches this function twice with the + same class and nothing at stake; a presence-only guard would warn about an + overwrite that overwrote nothing. Warning on the harmless case is not free: + it teaches a caller to filter + :exc:`~pcapkit.utilities.warnings.RegistryWarning` wholesale, and that + filter would then hide the ``HTTP`` collision this warning exists to + surface. The sibling ``register`` methods across the package, each keyed on a + caller-supplied ``code``, apply the same identity criterion as of GitHub + issue :issue:`718`. Making the key itself unique would resolve the collision rather than merely reporting it, but it is a registry-format change that the bare-name @@ -195,8 +192,8 @@ class under two codes, a supported and documented thing to do, reaches :class:`~pcapkit.foundation.reassembly.reassembly.ReassemblyMeta` and :class:`~pcapkit.foundation.traceflow.traceflow.TraceFlowMeta` fall back to :class:`~pcapkit.protocols.misc.raw.Raw`. Re-keying is therefore part of the - registry redesign in :issue:`514`, and reporting the collision here is the step that - redesign is sequenced behind. + registry redesign in :issue:`514`, and reporting the collision here is the + step that redesign is sequenced behind. Args: protocol: Protocol class. @@ -343,8 +340,8 @@ def register_protocol_code(protocol: 'Type[ProtocolBase]', code: 'Any') -> 'None error. Note: - That example names ``L2TPv3``, which this package does not implement - yet, rather than :class:`~pcapkit.protocols.link.l2tpv2.L2TPv2`. It is + That example names ``L2TPv3``, which this package does not implement, + rather than :class:`~pcapkit.protocols.link.l2tpv2.L2TPv2`. It is v3 that is genuinely reachable both ways: :rfc:`3931` §4.1.1 puts it directly over IP on protocol 115 and §4.1.2 puts it over UDP on port 1701. :class:`L2TPv2 ` answers on @@ -398,7 +395,7 @@ def register_linktype(code: 'LinkType', module: 'str | ModuleDescriptor[Protocol Arguments: code: protocol code as in :class:`~pcapkit.const.reg.linktype.LinkType` module: module name or module descriptor or a - :class:`~pcapkit.protocols.protocol.Protocol` subclass + :class:`~pcapkit.protocols.protocol.ProtocolBase` subclass class\_: class name See Also: @@ -440,7 +437,7 @@ def register_pcap(code: 'LinkType', module: 'str | ModuleDescriptor[ProtocolBase Arguments: code: protocol code as in :class:`~pcapkit.const.reg.linktype.LinkType` module: module name or module descriptor or a - :class:`~pcapkit.protocols.protocol.Protocol` subclass + :class:`~pcapkit.protocols.protocol.ProtocolBase` subclass class\_: class name """ @@ -477,7 +474,7 @@ def register_pcapng(code: 'LinkType', module: 'str | ModuleDescriptor[ProtocolBa Arguments: code: protocol code as in :class:`~pcapkit.const.reg.linktype.LinkType` module: module name or module descriptor or a - :class:`~pcapkit.protocols.protocol.Protocol` subclass + :class:`~pcapkit.protocols.protocol.ProtocolBase` subclass class\_: class name """ @@ -519,7 +516,7 @@ def register_ethertype(code: 'EtherType', module: 'str | ModuleDescriptor[Protoc Arguments: code: protocol code as in :class:`~pcapkit.const.reg.ethertype.EtherType` module: module name or module descriptor or a - :class:`~pcapkit.protocols.protocol.Protocol` subclass + :class:`~pcapkit.protocols.protocol.ProtocolBase` subclass class\_: class name """ @@ -561,7 +558,7 @@ def register_transtype(code: 'TransType', module: 'str | ModuleDescriptor[Protoc Arguments: code: protocol code as in :class:`~pcapkit.const.reg.transtype.TransType` module: module name or module descriptor or a - :class:`~pcapkit.protocols.protocol.Protocol` subclass + :class:`~pcapkit.protocols.protocol.ProtocolBase` subclass class\_: class name """ @@ -808,7 +805,7 @@ def register_apptype(code: 'int | Enum_AppType', module: 'str | ModuleDescriptor Arguments: code: port number module: module name or module descriptor or a - :class:`~pcapkit.protocols.protocol.Protocol` subclass + :class:`~pcapkit.protocols.protocol.ProtocolBase` subclass class\_: class name, meaningful only when ``module`` is a :class:`str`. Positional, at the same position as the sibling ``register_*`` functions -- but unlike them, a third positional argument here is @@ -868,10 +865,11 @@ def register_apptype(code: 'int | Enum_AppType', module: 'str | ModuleDescriptor * :func:`pcapkit.foundation.registry.register_sctp` """ - # NOTE: ``class_`` is back at the sibling position -- positional, ahead of - # ``*transport`` -- per maintainer ruling, and the swallow that made it - # keyword-only in the first place is disambiguated here by ``type(module)`` - # alone, never by what ``class_`` itself looks like. A ``str`` module means + # NOTE: ``class_`` sits at the sibling position -- positional, ahead of + # ``*transport`` -- per maintainer ruling, so a third positional that is + # really a transport would be swallowed as a class name. That is + # disambiguated here by ``type(module)`` alone, never by what ``class_`` + # itself looks like. A ``str`` module means # ``class_`` really is a class name, so it is left untouched -- including # when it happens to be a string like ``'tcp'``, which is not sniffed for # looking like a transport. Any other module type means the third @@ -893,9 +891,9 @@ def register_apptype(code: 'int | Enum_AppType', module: 'str | ModuleDescriptor # anything unrecognised, composite-spelled or not, and ``__getitem__`` # raises a bare :exc:`KeyError` on a miss instead of that -- both # ``TransportProtocol['tcp|udp']`` and ``TransportProtocol['bogus']`` do, - # now that GitHub issue #808 dropped the ``IntFlag`` base that used to - # make the first of those two silently compose into the value ``3`` - # rather than miss at all. ``__members__.get(...)`` lets this function + # since GitHub issue #808 dropped the ``IntFlag`` base, which made the + # first of those two silently compose into the value ``3`` rather than + # miss at all. ``__members__.get(...)`` lets this function # raise its own exception on a miss instead of letting ``__getitem__``'s # propagate, and lowercasing does not turn ``'tcp|udp'`` into a member # name either way. Anything that is neither a ``str`` nor a @@ -926,10 +924,10 @@ def register_apptype(code: 'int | Enum_AppType', module: 'str | ModuleDescriptor # NOTE: the member's own ``proto`` is the single transport protocol of the # registry it lives in -- GitHub issue #806 -- so it names one destination - # rather than fanning out across every transport IANA gave the service. The - # fan-out this replaced made ``register_apptype(TCP.http, Dummy)`` displace - # the UDP handler for port 80 as well, which no caller naming the TCP member - # asked for. + # rather than fanning out across every transport IANA gave the service. A + # fan-out would make ``register_apptype(TCP.http, Dummy)`` displace the UDP + # handler for port 80 as well, which no caller naming the TCP member asked + # for. if not transport: if not isinstance(code, Enum_AppType): raise RegistryError(f'no transport protocol given for port {code}; name each ' @@ -982,7 +980,7 @@ def register_tcp(code: 'int | Enum_AppType', module: 'str | ModuleDescriptor[Pro Arguments: code: port number module: module name or module descriptor or a - :class:`~pcapkit.protocols.protocol.Protocol` subclass + :class:`~pcapkit.protocols.protocol.ProtocolBase` subclass class\_: class name """ @@ -1071,7 +1069,7 @@ def register_udp(code: 'int | Enum_AppType', module: 'str | ModuleDescriptor[Pro Arguments: code: port number module: module name or module descriptor or a - :class:`~pcapkit.protocols.protocol.Protocol` subclass + :class:`~pcapkit.protocols.protocol.ProtocolBase` subclass class\_: class name """ @@ -1111,7 +1109,7 @@ def register_sctp(code: 'int | SCTP_PayloadProtocolIdentifier', module: 'str | M code: payload protocol identifier (PPID), as in :class:`~pcapkit.const.sctp.payload_protocol_identifier.PayloadProtocolIdentifier` module: module name or module descriptor or a - :class:`~pcapkit.protocols.protocol.Protocol` subclass + :class:`~pcapkit.protocols.protocol.ProtocolBase` subclass class\_: class name Important: diff --git a/pcapkit/foundation/traceflow/data/tcp.py b/pcapkit/foundation/traceflow/data/tcp.py index daeeaa7b8c..42d9590fe3 100644 --- a/pcapkit/foundation/traceflow/data/tcp.py +++ b/pcapkit/foundation/traceflow/data/tcp.py @@ -60,9 +60,8 @@ class Packet(Info, Generic[_AT]): fin: 'bool' #: TCP reset (RST) flag. A connection can end abruptly as well as politely #: (:rfc:`9293#section-3.5.2`), and the tracer cannot notice that unless the - #: flag reaches it -- which it did not, so a reset connection used to look - #: merely idle and a later connection reusing the same endpoints merged into - #: it. + #: flag reaches it, otherwise a reset connection looks merely idle and a later + #: connection reusing the same endpoints merges into it. rst: 'bool' #: Source IP. src: '_AT' diff --git a/pcapkit/foundation/traceflow/tcp.py b/pcapkit/foundation/traceflow/tcp.py index 8cf3f4ee6b..e655ca2b6a 100644 --- a/pcapkit/foundation/traceflow/tcp.py +++ b/pcapkit/foundation/traceflow/tcp.py @@ -51,13 +51,13 @@ class TCP(TraceFlowBase[BufferID, Buffer[_AT], Index, Packet[_AT]], Generic[_AT] A TCP connection has two halves, and by default they are traced as **one flow** -- which is what "following a TCP stream" means everywhere else, and what this module's own title claims to do. Keying a flow on - (source, destination) instead put a client's packets and the server's - replies in separate flows, separate labels and separate output files, - leaving a caller to pair them up by inspecting the labels. + (source, destination) instead would put a client's packets and the + 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 of the change are worth knowing: + Two consequences are worth knowing: - * The reverse half of a conversation no longer produces a flow of its + * 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 ``2n``, and one output file each rather than two. * **A teardown does not end a flow.** Seeing a connection close is not the diff --git a/pcapkit/foundation/traceflow/traceflow.py b/pcapkit/foundation/traceflow/traceflow.py index 86423403b2..073280311f 100644 --- a/pcapkit/foundation/traceflow/traceflow.py +++ b/pcapkit/foundation/traceflow/traceflow.py @@ -523,10 +523,10 @@ def __init_subclass__(cls, /, protocol: 'Optional[str]' = None, *args: 'Any', ** """ # NOTE: the keyword here is ``protocol``, but ``Engine`` spells the same # idea ``name`` -- so guessing ``name=`` by analogy is the expected - # mistake, not a careless one. It used to land in ``**kwargs``, get - # dropped by the bare ``super().__init_subclass__()`` below, and leave - # the class registered under its own class name instead: no exception, no - # warning. See the sibling note in ``Engine.__init_subclass__``. + # mistake, not a careless one. Left in ``**kwargs`` it would be + # dropped by the bare ``super().__init_subclass__()`` below, leaving the + # class registered under its own class name: no exception, no warning. + # See the sibling note in ``Engine.__init_subclass__``. if args or kwargs: unexpected = ', '.join([*map(repr, args), *sorted(kwargs)]) raise UnsupportedCall(f'{cls.__name__}: unexpected class keyword(s): {unexpected}')