Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/source/pcapkit/foundation/reassembly/ip/ipv4.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----------
Expand Down
2 changes: 2 additions & 0 deletions docs/source/pcapkit/foundation/reassembly/ip/ipv6.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----------
Expand Down
2 changes: 2 additions & 0 deletions docs/source/pcapkit/foundation/reassembly/tcp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ which reconstructs fragmented TCP packets back to origin.

.. autoattribute:: __protocol_name__
.. autoattribute:: __protocol_type__
.. autoattribute:: __callback_fn__
:no-value:

Algorithm
=========
Expand Down
2 changes: 2 additions & 0 deletions docs/source/pcapkit/foundation/traceflow/tcp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----------
Expand Down
2 changes: 1 addition & 1 deletion pcapkit/foundation/engines/_pcap_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pcapkit/foundation/engines/dpkt.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def run(self) -> 'None':
Warns:
AttributeWarning: If :attr:`self.extractor._exlyr <pcapkit.foundation.extraction.Extractor._exlyr>`
and/or :attr:`self.extractor._exptl <pcapkit.foundation.extraction.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 <pcapkit.foundation.extraction.Extractor._exctx>`
is provided, as the DPKT engine does not parse with :mod:`pcapkit`'s own
protocol implementations.
Expand Down
18 changes: 9 additions & 9 deletions pcapkit/foundation/engines/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions pcapkit/foundation/engines/pcap_ct.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -318,7 +318,7 @@ def run(self) -> 'None':

* if :attr:`self.extractor._exlyr <pcapkit.foundation.extraction.Extractor._exlyr>`
and/or :attr:`self.extractor._exptl <pcapkit.foundation.extraction.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
Expand Down
2 changes: 1 addition & 1 deletion pcapkit/foundation/engines/pypcap.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ def run(self) -> 'None':

* if :attr:`self.extractor._exlyr <pcapkit.foundation.extraction.Extractor._exlyr>`
and/or :attr:`self.extractor._exptl <pcapkit.foundation.extraction.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
Expand Down
2 changes: 1 addition & 1 deletion pcapkit/foundation/engines/pypcapfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ def run(self) -> 'None':

* if :attr:`self.extractor._exlyr <pcapkit.foundation.extraction.Extractor._exlyr>`
and/or :attr:`self.extractor._exptl <pcapkit.foundation.extraction.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.
Expand Down
11 changes: 5 additions & 6 deletions pcapkit/foundation/engines/pyshark.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -182,9 +181,9 @@ def run(self) -> 'None':

* if :attr:`self.extractor._exlyr <pcapkit.foundation.extraction.Extractor._exlyr>`
and/or :attr:`self.extractor._exptl <pcapkit.foundation.extraction.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 <pcapkit.foundation.extraction.Extractor._exctx>`
is provided, as the PyShark engine does not parse with
Expand Down
4 changes: 2 additions & 2 deletions pcapkit/foundation/engines/scapy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -145,7 +145,7 @@ def run(self) -> 'None':
Warns:
AttributeWarning: If :attr:`self.extractor._exlyr <pcapkit.foundation.extraction.Extractor._exlyr>`
and/or :attr:`self.extractor._exptl <pcapkit.foundation.extraction.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 <pcapkit.foundation.extraction.Extractor._exctx>`
is provided, as the Scapy engine does not parse with :mod:`pcapkit`'s own
protocol implementations.
Expand Down
63 changes: 50 additions & 13 deletions pcapkit/foundation/extraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,8 @@
#: Every key registered in :attr:`Extractor.__output__` and in
#: :attr:`TraceFlowBase.__output__
#: <pcapkit.foundation.traceflow.traceflow.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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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}'; "
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Loading
Loading