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
6 changes: 3 additions & 3 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,9 +543,9 @@ def save_enum_member_docstring(app: 'Sphinx', what: str, name: str, # pylint: d
registry member's docstring is the registry's own text, so the colon is
usually a URL's: ``H.323 [http://standard.pictel.com/...]`` rendered as
*Type:* ``H.323 [http`` above the description ``//standard.pictel.com/...``,
on 51 members. The bogus type was also parsed as a cross-reference, which is
where the ``http`` and ``Echo`` ``more than one target found`` warnings came
from.
and every member whose first docstring line holds a colon split the same
way. The bogus type was also parsed as a cross-reference, which is where the
``http`` and ``Echo`` ``more than one target found`` warnings came from.

Args:
app: Sphinx application.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ class for it, so nothing implements the classification, but a future one inherit
Explicit Base Declarations
~~~~~~~~~~~~~~~~~~~~~~~~~~

Name the second base **explicitly**, even though it is already in the MRO.
Name the second base **explicitly**, even where it is already in the MRO.
:class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext` derives
:class:`~pcapkit.protocols.internet.internet.Internet`, so every extension header
reaches ``Internet`` transitively and an ``__mro__`` check cannot tell the two groups
Expand Down
2 changes: 1 addition & 1 deletion docs/source/contributing/conventions/mint-criterion.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ a ``_missing_`` override, which decides what happens when a value has no member.
MRO, so they carry no declared-but-unassigned range to resolve. The rest define one
themselves, except the transport subclasses of
:class:`~pcapkit.const.reg.apptype.apptype.AppType`, which inherit
``AppType._missing_`` (:file:`pcapkit/const/reg/apptype/apptype.py:2994`). The
``AppType._missing_`` (:file:`pcapkit/const/reg/apptype/apptype.py`). The
behaviours below follow from it, and which one a given range gets is a **design decision, not
a style preference**:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ Why Function and Not Encapsulation
:rfc:`1122` settles this inside one document. It lists **RARP in the application
layer** at :rfc:`1122#section-1.1.3`, among the *"support protocols, used for host
name mapping, booting, and management"*, while **ARP** sits in the Link Layer chapter
at :rfc:`1122#section-2.3.2`. Two sibling protocols, one frame format, one EtherType,
two layers -- decided on function alone.
at :rfc:`1122#section-2.3.2`. Sibling protocols sharing one frame format, each under
its own EtherType, in different layers -- decided on function alone.

OSI says the same by construction. ITU-T X.200 §9.2.4.4 has a protocol declare its own
layer in terms of *"functions which pertain to a particular layer"*, and clause 7
Expand All @@ -65,7 +65,7 @@ IANA Assignments Carry No Layer Claim
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Every registry this package dispatches on -- *Protocol Numbers*, *Ether Types*,
and *Service Name and Transport Protocol Port Number* -- have **no layer field**.
and *Service Name and Transport Protocol Port Number* -- has **no layer field**.
*Protocol Numbers* frames itself as identifying *"the next level protocol"*, i.e. the
**encapsulated** one. A registry assignment is evidence of encapsulation and of
nothing else.
Expand All @@ -87,7 +87,7 @@ Routing Protocols: Control Plane, Not Data Plane
**"APPLICATION LAYER - ROUTING PROTOCOLS"**, with OSPF at §7.2.2, while
:rfc:`1812#section-4.1` reads *"This chapter and chapter 5 discuss the protocols used
at the Internet Layer: IP, ICMP, and IGMP."* So a routing protocol is application-layer
and the internet layer holds three protocols.
and the internet layer holds only IP, ICMP and IGMP.

**X.200 does not contradict this, and must not be cited as though it did.** X.200
§7.5.2.1 says the Network Layer provides transport *"independence of routing and relay
Expand Down
6 changes: 3 additions & 3 deletions docs/source/contributing/conventions/registry-protocol.rst
Original file line number Diff line number Diff line change
Expand Up @@ -261,9 +261,9 @@ course: they should follow the house convention and not be loud.

Both answers are on the issue deliberately, and the reversal is the ruling. What it
settles is not the one keyword -- it is the tie-breaker. A loud
:class:`~pcapkit.utilities.exceptions.BaseError` sets :data:`sys.tracebacklimit` to
``0`` **process-wide**, the
:issue:`362` hazard, so loudness is
:class:`~pcapkit.utilities.exceptions.BaseError` logs a :data:`logging.CRITICAL`
record, the :issue:`362` hazard, and outside development mode installs
:mod:`pcapkit`'s exception hooks **process-wide**, so loudness is
paid for by the whole library rather than by the override's own callers. The argument
against changing them was that ``quiet=True`` exists on the base for a name miss
inside a *successful* call at ``Method.get`` and these two had no such caller;
Expand Down
4 changes: 2 additions & 2 deletions docs/source/contributing/conventions/sentinel-convention.rst
Original file line number Diff line number Diff line change
Expand Up @@ -145,15 +145,15 @@ the differences are **needs, not inconsistencies**:
``__reduce__`` returns its module-level name, so :mod:`pickle` stores a reference to
that name and every protocol loads the same object back. A guarded ``__new__``
alone would not do, since pickle protocols 0 and 1 rebuild through
:func:`copyreg._reconstructor`, which calls :meth:`object.__new__` directly. That
``copyreg._reconstructor``, which calls :meth:`object.__new__` directly. That
path also limits pickles written before :issue:`1842`: one of ``NO_VALUE``,
``NO_DEFAULT`` or ``ABSENT`` at protocol 0 or 1 still loads, but as a second
instance, because nothing on the class can intercept it.

``__repr__``
Every sentinel has it, from the base. It is the object's own name -- ``NULL``,
``NO_VALUE``, ``NO_DEFAULT``, ``ABSENT`` -- as ``repr(None)`` is ``'None'``, and
:func:`str` gives the same. That is what a signature, :func:`help` and a traceback
:class:`str` gives the same. That is what a signature, :func:`help` and a traceback
show.

``__setattr__`` / ``__delattr__``
Expand Down
24 changes: 12 additions & 12 deletions docs/source/contributing/pep.rst
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ either, and are the one item on this list that is not simply a table entry: the
high-order 32 bits are never transmitted, so recovering them is stateful, and
they widen both the ICV coverage and the AEAD associated data.

Some structural notes matter to anyone starting. Adding an integrity algorithm
The structure limits what a new row can do. Adding an integrity algorithm
that is not an HMAC needs more than a row --
:class:`~pcapkit.protocols.internet.esp.IntegritySuite` records the digest as
the name of a :mod:`hashlib` constructor, which AES-XCBC and AES-CMAC are not.
Expand Down Expand Up @@ -421,7 +421,7 @@ process inflates them by up to 73%, so each shape wants its own interpreter.
Recursive Dissection, With a Depth Limit
----------------------------------------

**Decided for now, open to revisit.** Each layer dissects the next from inside
**Decided, open to revisit.** Each layer dissects the next from inside
its own parse, so dissecting a frame recurses once per layer, at 9 to 12 Python
frames a layer. Deep nesting -- IP in IP, stacked 802.1Q tags, long chains of
IPv6 extension headers -- used to exhaust the recursion limit and leave the rest
Expand Down Expand Up @@ -537,10 +537,9 @@ both of the following are worth knowing before reaching for them:
since May 2024. It does mean ``pip install pypcapkit[PyPCAP]`` can fail to
build. Since there is no wheel to fall back on, the extra is kept **out of**
``all``: otherwise ``pip install pypcapkit[all]`` would demand a compiler and
the libpcap development files from every user, and would break the docs, conda
and release workflows -- all of which install ``.[all]`` or ``.[all,dev]`` --
on the macOS runner, where :file:`pcap.h` is present but no
``libpcap.dylib`` is.
the libpcap development files from every user, and would break the docs
build, which installs ``.[all,dev]`` on the macOS runner, where :file:`pcap.h`
is present but no ``libpcap.dylib`` is.

That is solved, though not by changing the ``PyPCAP`` extra. `pcap-ct
<https://pypi.org/project/pcap-ct/>`__ re-implements the ``pypcap`` API in pure
Expand Down Expand Up @@ -571,8 +570,8 @@ keeping IPv4 and TCP. Each gap is announced through an
:class:`~pcapkit.utilities.warnings.AttributeWarning` or an outright exception
rather than by silently returning nothing, save that `pypcapfile`_ leaves TCP
behind a VLAN tag, in a tunnel or behind an extension header (an IPv6 one, or AH
in IPv4) out without one -- :doc:`/pcapkit/foundation/engines/index` tabulates
them.
in IPv4) out of TCP reassembly and flow tracing without a warning --
:doc:`/pcapkit/foundation/engines/index` tabulates them.

Adding a further engine does not mean adding handler methods to
:class:`~pcapkit.foundation.extraction.Extractor`, as the thread describes. A new engine subclasses
Expand Down Expand Up @@ -808,8 +807,8 @@ These protocols parse a checksum or CRC field —
:class:`~pcapkit.protocols.application.ospf.OSPF`,
:class:`~pcapkit.protocols.transport.sctp.SCTP`,
:class:`~pcapkit.protocols.transport.tcp.TCP` and
:class:`~pcapkit.protocols.transport.udp.UDP` — and only one of them checks
whether the value is *right*:
:class:`~pcapkit.protocols.transport.udp.UDP` — and of them only SCTP checks
whether the value is *right*, through
:attr:`SCTP.checksum_valid <pcapkit.protocols.transport.sctp.SCTP.checksum_valid>`.
For the others the field is recorded and never questioned, so a corrupted
capture parses as cleanly as an intact one.
Expand Down Expand Up @@ -956,8 +955,9 @@ produced.

That is why an ordinary commit does not publish: the version string has not
moved, so each job's own check finds its target already there and every one of
them skips, as does the full-suite release test gate ahead of them. Changing the version string is what flips every one of those checks, and
the next push then tags and publishes. So:
them skips, as does the full-suite release test gate ahead of them. Changing the
version string is what flips every one of those checks, and the next push then
tags and publishes. So:

.. list-table::
:header-rows: 1
Expand Down
2 changes: 1 addition & 1 deletion docs/source/contributing/releasing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Either run the script, or move both fields by hand in the same commit.
This is the **only** edit a person makes to get a release started -- the tag,
the Release, and every upload are the workflow's job from here. Other
checks run unconditionally on the same path, though none is a step in *this*
process: the ``changelog`` job (``unit-tests.yml:1624-1643``) fails outright if
process: the ``changelog`` job (``unit-tests.yml:1624-1650``) fails outright if
``CHANGELOG.md`` has drifted from its source entry under
:file:`docs/source/changelog/`, and the release-body step warns, without
failing, if that entry's heading still reads "unreleased"
Expand Down
34 changes: 18 additions & 16 deletions docs/source/contributing/workflows.rst
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,8 @@ rather than the event, so its gate can run whichever way the run was raised, onc
``workflow_run`` Edges
----------------------

These edges come from grepping every ``on:`` block rather than from a
hand-maintained list:
Each edge cites the ``on:`` block it comes from, so grepping those blocks
re-derives the list:

* ``.github/workflows/cron-vendor.yml:14-16`` -- **Vendor Update** fires on
completion of **Unit Tests**.
Expand All @@ -262,9 +262,9 @@ hand-maintained list:
success, failure, or skipped -- and regardless of what triggered it,
including a pull request's own run of Unit Tests. Every
downstream workflow therefore re-checks ``github.event.workflow_run.event``
and ``.head_branch`` (or, for Create Release and Coverage Comment, ``.conclusion``) itself before
doing anything with an outward effect; none of the filtering happens in the
``on:`` block.
and ``.head_branch`` (or, for Create Release and Coverage Comment,
``.conclusion``) itself before doing anything with an outward effect; none of
the filtering happens in the ``on:`` block.

Those checks say nothing about *which* commit was verified. A ``workflow_run``
checks out ``github.sha``, ``main``'s tip when the event is raised, not
Expand Down Expand Up @@ -319,13 +319,13 @@ source entry) and ``python-versions``, which reads ``gate``'s version (see
`Python Versions`_ below). It skips the rest -- the matrix jobs, ``test``,
``test-ktlo``, ``integration``, ``integration-ktlo``, ``engine-tests`` (a job per
engine, each looping over its Python versions), ``pypcap-parity``,
``pypcap-parity-ktlo`` and ``unittest-ordering``, each of which has already run once for this commit
from Unit Tests' own ``push`` trigger (and, all but ``unittest-ordering``,
which runs on ``main`` pushes only, from its ``pull_request`` trigger), so
running any of them again per caller would
test the same commit several times over -- the docs-only path's ``changes``
and ``project-tests``, and ``required-checks`` (see `Required Status
Checks`_ below), gated out by their own ``if:`` rather than by having
``pypcap-parity-ktlo`` and ``unittest-ordering``, each of which has already run
once for this commit from Unit Tests' own ``push`` trigger (and, all but
``unittest-ordering``, which runs on ``main`` pushes only, from its
``pull_request`` trigger), so running any of them again per caller would test
the same commit several times over. It also skips the docs-only path's
``changes`` and ``project-tests``, and ``required-checks`` (see `Required Status
Checks`_ below), which are gated out by their own ``if:`` rather than by having
already run. ``changelog`` carries no
``if:`` at all and runs on every path regardless -- deliberately, per its own
comment (``unit-tests.yml:1612-1618``): ``create-release.yml`` feeds
Expand Down Expand Up @@ -680,8 +680,10 @@ triggers; the ``gate-only: true`` reusable calls documented above skip it via
``if: ${{ always() && inputs.gate-only != true }}`` (``unit-tests.yml:1939``),
so it is never produced -- and never expected -- on those paths.

Python versions come in two tiers (`#1633 <https://github.com/JarryShaw/PyPCAPKit/issues/1633>`__), and an experimental one (`#1660 <https://github.com/JarryShaw/PyPCAPKit/issues/1660>`__,
below), declared in :file:`.github/python-versions.toml` (see `Python Versions`_).
Python versions are tiered -- supported and KTLO
(`#1633 <https://github.com/JarryShaw/PyPCAPKit/issues/1633>`__), and experimental
(`#1660 <https://github.com/JarryShaw/PyPCAPKit/issues/1660>`__, below) -- in
:file:`.github/python-versions.toml` (see `Python Versions`_).
The **supported** tier, 3.11-3.15, blocks: a failed leg fails ``Required checks passed`` or its own
required ``Compat`` context. The **KTLO** tier -- end-of-life 3.7-3.10, still built and
tested -- never blocks, is never a required context, and never shows a commit or a
Expand Down Expand Up @@ -786,8 +788,8 @@ holds ``roundtrip`` (``ROUNDTRIP_SWEEP_GLOB`` in :file:`tests/_tiers.py`), and
``integration`` too: off ``latest`` its legs leave out the
``*roundtrip*_runtime.py`` modules (``ROUNDTRIP_RUNTIME_GLOB``), dropping them from
``fixture_tier_paths()``'s selection and adding the glob as an ``--ignore-glob``,
since pytest applies no ignore to a path it was handed. On 2026-10-14 they were 41 modules
and 8% of the unit tier's time in a serial 3.14 run (86 of 1,031 s), and 10-11% on the
since pytest applies no ignore to a path it was handed. On 2026-10-14 they took
8% of the unit tier's time in a serial 3.14 run (86 of 1,031 s), and 10-11% on the
3.14, 3.15 and 3.14t legs of run 38581834697, so the rest should take GraalPy 88-113
minutes (estimate).

Expand Down
21 changes: 10 additions & 11 deletions docs/source/ext.rst
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ protocols, respectively.
abstract base class for protocols, and it is not recommended to inherit from
it directly, unless you are going to create a new protocol stack, e.g., a new
miscellaneous protocol stack as in :mod:`pcapkit.protocols.misc` module. The
four layer base classes above, like the built-in miscellaneous protocols,
layer base classes above, like the built-in miscellaneous protocols,
derive from :class:`~pcapkit.protocols.protocol.ProtocolBase` rather than
from :class:`~pcapkit.protocols.protocol.Protocol`.

Expand Down Expand Up @@ -426,15 +426,14 @@ The available engines and the file formats they read:
whatsoever, so they support neither reassembly nor flow tracing; `PyPCAPFile`_
has no IPv6 decoder, so it supports IPv4 and TCP reassembly but not IPv6. What
matters is that the gap is *announced* -- each engine warns, or raises, for the
capability it cannot provide, rather than silently producing an empty result,
save that `PyPCAPFile`_ leaves TCP behind a VLAN tag, in a tunnel or behind an
extension header (an IPv6 one, or AH in IPv4) out without a warning; that
`DPKT`_ leaves TCP in an IP-in-IP tunnel or behind an AH or IPv6 extension
header in IPv4, and IPv4 in IPv6 or IPv6 in IPv4, out without one; and the
`Scapy`_ engine reads IPv6 only as far as `Scapy`_ dissects it, which leaves
IPv6 behind some BSD loopback headers, and TCP and IPv6 fragments behind some
IPv6 extension headers, out without one. See
:doc:`pcapkit/foundation/engines/index` for the full table.
capability it cannot provide, rather than silently producing an empty result.
The exceptions pass without a warning: `PyPCAPFile`_ and `DPKT`_ leave TCP
behind some tags, tunnels and extension headers out of TCP reassembly and
flow tracing, `DPKT`_ also leaves IPv4 in IPv6, and IPv6 in IPv4, out of IP
reassembly, and the `Scapy`_ engine reads IPv6 only as far as `Scapy`_
dissects it. See
:doc:`pcapkit/foundation/engines/index` for the full table, which names each
case.

.. note::

Expand Down Expand Up @@ -599,7 +598,7 @@ The following code snippet shows how to create a new engine class:

# NOTE: The following code snippet is to handle the flow tracing of
# the network packets, i.e., to trace a series of packets as they
# will compose as a flow and/or stream. Before reassembly, we need
# will compose as a flow and/or stream. Before flow tracing, we need
# to convert the packet to a tailored format, i.e., a dict-like object,
# which is then passed to the flow tracing function. For more
# information, please refer to the documentation of
Expand Down
25 changes: 15 additions & 10 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -147,15 +147,19 @@ Speed is not free. Every third-party engine supports **less** than the
dissection at all, so they offer neither reassembly nor flow tracing, and
:class:`~pcapkit.foundation.engines.pypcapfile.PyPCAPFile` has no IPv6 decoder,
so IPv6 reassembly is unavailable. Each gap is announced with a warning or an
exception rather than silently returning nothing, save that
:class:`~pcapkit.foundation.engines.pypcapfile.PyPCAPFile` leaves TCP behind a
VLAN tag, in a tunnel or behind an extension header (an IPv6 one, or AH in IPv4)
out without one; that :class:`~pcapkit.foundation.engines.dpkt.DPKT` leaves
TCP in an IP-in-IP tunnel or behind an AH or IPv6 extension header in IPv4, and
IPv4 in IPv6 or IPv6 in IPv4, out without one; and
:class:`~pcapkit.foundation.engines.scapy.Scapy` reads IPv6 only as far as
`Scapy`_ dissects it, which leaves IPv6 behind some BSD loopback headers, and
TCP and IPv6 fragments behind some IPv6 extension headers, out without one.
exception rather than silently returning nothing, except for these, which pass
without a warning:

- :class:`~pcapkit.foundation.engines.pypcapfile.PyPCAPFile` leaves TCP behind a
VLAN tag, in a tunnel or behind an extension header (an IPv6 one, or AH in
IPv4) out of TCP reassembly and flow tracing;
- :class:`~pcapkit.foundation.engines.dpkt.DPKT` leaves TCP in an IP-in-IP
tunnel or behind an AH or IPv6 extension header in IPv4 out of TCP reassembly
and flow tracing, and IPv4 in IPv6 or IPv6 in IPv4 out of IP reassembly;
- :class:`~pcapkit.foundation.engines.scapy.Scapy` reads IPv6 only as far as
`Scapy`_ dissects it, so IPv6 behind some BSD loopback headers, and TCP and
IPv6 fragments behind some IPv6 extension headers, go undissected.

:doc:`pcapkit/foundation/engines/index` tabulates them.

Every engine also answers a preflight check --
Expand Down Expand Up @@ -231,7 +235,8 @@ compared with a measured row.

.. [1] `PyPCAPFile`_ 0.12.0 imports :mod:`imp`, which Python removed in 3.12.

.. [2] `PyPCAP`_ 1.3.0 is unsupported on Python 3.12+.
.. [2] `PyPCAP`_ 1.3.0 does not build on Python 3.12+: its pre-generated
:file:`pcap.c` does not compile against that C API.

.. [3] `PyShark`_ 0.6 cannot create the implicit event loop it expects on Python
3.14; it ran on 3.10--3.13.
Expand Down
2 changes: 1 addition & 1 deletion docs/source/pcapkit/dumpkit/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Dump Utilities
.. module:: pcapkit.dumpkit

:mod:`pcapkit.dumpkit` is the collection of :mod:`pcapkit`'s own dumpers,
alike those in :mod:`dictdumper`.
like those in :mod:`dictdumper`.

.. toctree::
:maxdepth: 2
Expand Down
Loading
Loading