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
12 changes: 9 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,15 @@ header records which of them are clean and what each of the others would need be
`continue-on-error` line could be deleted. Read the job's run summary, where each step writes
its verdict, and try not to add to the numbers.

`isort` is the exception: no `Lint` step runs it. It appears in `cron-vendor.yml` as a formatter
that rewrites the regenerated constants, and `tests/project/test_isort_clean.py`, which the `test`
extra's `isort` lets run, fails on a file whose imports `make isort` would reorder.
**The job is not entirely advisory, though.** It carries one more step, `actionlint`, which lints
the workflow YAML rather than `pcapkit/`, and that one has no `continue-on-error` and no
`Makefile` target — so a change under `.github/` can turn `Lint` red even though none of the
linters above can. The header records that split, and asks for a promoted step to be moved below
the advisory ones, since a failed step skips every step after it.

`isort` is absent from the job altogether: no `Lint` step runs it. It appears in `cron-vendor.yml`
as a formatter that rewrites the regenerated constants, and `tests/project/test_isort_clean.py`,
which the `test` extra's `isort` lets run, fails on a file whose imports `make isort` would reorder.

One trap: `make vermin` redirects its report into `temp/vermin.txt` rather than to your terminal,
so when vermin finds code that misses `vermin.ini`'s `targets = 3.8` and exits 1,
Expand Down
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@
# Those packages are therefore linked to their project pages instead, through
# :data:`rst_epilog` below (#1936).

#: The label on the note in :file:`pcapkit/foundation/engines/3rdparty.rst` that
#: The label on the note in :file:`docs/source/pcapkit/foundation/engines/3rdparty.rst` that
#: says which distribution installs the ``pcap`` module, which ``|pcap|_`` links
#: to (:issue:`1948`).
PCAP_MODULE_LABEL = 'pcap-module'
Expand Down
18 changes: 12 additions & 6 deletions docs/source/contributing/conventions/protocol-layer-placement.rst
Original file line number Diff line number Diff line change
Expand Up @@ -191,20 +191,26 @@ Counting the names in each class's own namespace, whether new or overriding
:class:`~pcapkit.protocols.protocol.ProtocolBase`'s, and including those
:meth:`ProtocolBase.__init_subclass__ <pcapkit.protocols.protocol.ProtocolBase.__init_subclass__>`
sets there, :class:`~pcapkit.protocols.link.link.Link` has ``__data__``, ``__layer__``,
``__post_init__``, ``__proto__``, ``__schema__``, ``_read_protos``, ``_shipped_proto``,
``from_data``, ``register`` and ``layer``, and
``__post_init__``, ``__proto__``, ``__proto_key__``, ``__schema__``, ``_read_protos``,
``_shipped_proto``, ``from_data``, ``register`` and ``layer``, and
:class:`~pcapkit.protocols.application.application.Application` has what they share
(``__data__``, ``__layer__``, ``__post_init__``, ``__schema__``, ``from_data``, ``layer``)
plus ``__index__``, ``_decode_next_layer`` and ``_import_next_layer``.
:class:`~pcapkit.protocols.link.link.Link` overrides names that
:class:`~pcapkit.protocols.application.application.Application` does not -- ``__proto__``,
``register``, ``_read_protos`` and ``_shipped_proto``, the last set because
:class:`~pcapkit.protocols.link.link.Link` defines its own ``__proto__`` -- but each also
exists on :class:`~pcapkit.protocols.protocol.ProtocolBase`, so the strict difference
:class:`~pcapkit.protocols.link.link.Link` defines its own ``__proto__`` -- and each of
those also exists on :class:`~pcapkit.protocols.protocol.ProtocolBase`, so each still
resolves on :class:`~pcapkit.protocols.application.ospf.OSPF`. The strict difference
:class:`~pcapkit.protocols.link.link.Link` minus
:class:`~pcapkit.protocols.application.application.Application` minus
:class:`~pcapkit.protocols.protocol.ProtocolBase` is **empty** and every one of them still
resolves on :class:`~pcapkit.protocols.application.ospf.OSPF`. What changes is which
:class:`~pcapkit.protocols.protocol.ProtocolBase` is ``__proto_key__`` alone
(:issue:`2343`), which :class:`~pcapkit.protocols.link.link.Link` declares and neither of
the others does, so it does **not** resolve on
:class:`~pcapkit.protocols.application.ospf.OSPF`. Nothing turns on that:
:func:`~pcapkit.foundation.registry.protocols.register_protocol_code` reads the pairing
out of the registry owner's own namespace rather than off the class, and reads its
absence as *declares no pairing* rather than as an error. What changes is which
registry it resolves *to*: ``OSPF.__proto__`` is
:attr:`ProtocolBase.__proto__ <pcapkit.protocols.protocol.ProtocolBase.__proto__>` rather
than :attr:`Link.__proto__ <pcapkit.protocols.link.link.Link.__proto__>`, so OSPF does not
Expand Down
2 changes: 1 addition & 1 deletion docs/source/contributing/conventions/registry-protocol.rst
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ generated registries spell by hand in ``_missing_``:
if not (isinstance(value, int) and 0 <= value <= 0xFF):
raise EnumValueError(f'{value!r} is not a valid {cls.__name__}')

**Every registry overrides it**, so that ``register`` refuses what ``get`` refuses
**Every registry has it overridden**, so that ``register`` refuses what ``get`` refuses
(:issue:`1816`). Each vendor generator emits the override from the check it already
writes at the top of ``_missing_``, with the same message. That is the ``FLAG`` of most
crawlers, but a bespoke template's own range where its ``_missing_`` checks something
Expand Down
2 changes: 1 addition & 1 deletion docs/source/engines.rst
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ preflight described at the top of this page.

Only the engines above carry extra constraints. :mod:`pcapkit` itself and
its ``default``, ``dpkt`` and ``scapy`` engines need nothing beyond their
``pip install`` -- |dpkt|_ 1.9.8 and ``scapy`` 2.7.0 were both measured
``pip install`` -- |dpkt|_ 1.9.8 and ``scapy`` 2.8.0 were both measured
reading a capture on Python 3.14. The ``scapy`` engine needs **scapy 2.6.0 or
newer**, which the ``Scapy`` extra requires: 2.5.0 can drop a PCAP-NG
interface's timestamp resolution, reading nanoseconds as microseconds
Expand Down
16 changes: 8 additions & 8 deletions docs/source/ext.rst
Original file line number Diff line number Diff line change
Expand Up @@ -241,14 +241,14 @@ The following code snippet shows how to create a new protocol class:
silently wrong field. The accepted set is read from :func:`inspect.signature` -- the union of every
keyword-taking parameter of ``make``, ``read``, ``pack``, ``unpack``,
``__post_init__`` and ``__init__`` anywhere in the class's MRO -- which is
wider than ``make`` alone because :meth:`ProtocolBase.__post_init__
<pcapkit.protocols.protocol.ProtocolBase.__post_init__>` hands the same
wider than ``make`` alone because :meth:`Protocol.__post_init__
<pcapkit.protocols.protocol.Protocol.__post_init__>` hands the same
``**kwargs`` to the construction and to the parse, so a keyword only ``read``
declares still travels through ``make``.

Shapes a signature cannot express are declared on the class instead, via
:attr:`ProtocolBase.__keywords__
<pcapkit.protocols.protocol.ProtocolBase.__keywords__>`:
:attr:`Protocol.__keywords__
<pcapkit.protocols.protocol.Protocol.__keywords__>`:

.. code-block:: python

Expand All @@ -271,8 +271,8 @@ The following code snippet shows how to create a new protocol class:
subclass of a dispatcher is checked normally. Prefer a declared parameter to
either: it is also what documents the keyword to your callers.

Parsing is unaffected. :meth:`ProtocolBase.from_data
<pcapkit.protocols.protocol.ProtocolBase.from_data>` checks the keywords its
Parsing is unaffected. :meth:`Protocol.from_data
<pcapkit.protocols.protocol.Protocol.from_data>` checks the keywords its
caller passes just as strictly, raising
:exc:`~pcapkit.utilities.exceptions.UnsupportedCall`, but only warns with
:exc:`~pcapkit.utilities.warnings.UnknownFieldWarning` for a key that comes
Expand All @@ -281,8 +281,8 @@ The following code snippet shows how to create a new protocol class:

.. warning::

The check lives in :meth:`ProtocolBase.__init__
<pcapkit.protocols.protocol.ProtocolBase.__init__>`, where every producer's
The check lives in :meth:`Protocol.__init__
<pcapkit.protocols.protocol.Protocol.__init__>`, where every producer's
keywords converge, so it covers ``SomeProtocol(...)`` and the ``pack`` it
leads to -- but **not** a direct ``SomeProtocol.make(...)`` call, which still
discards an undeclared keyword in silence. ``object.__new__(cls).make(...)``
Expand Down
2 changes: 1 addition & 1 deletion docs/source/pcapkit/protocols/internet/ipv6_ext.rst
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ This class is reached in these ways:
1. **An unrecognised protocol number.**
:attr:`~pcapkit.const.ipv6.extension_header.ExtensionHeader.Shim6` is a real
IANA extension header (:rfc:`5533`) with no dedicated parser, so
:meth:`Internet._lookup_next_layer <pcapkit.protocols.protocol.ProtocolBase._lookup_next_layer>`
:meth:`Internet._lookup_next_layer <pcapkit.protocols.protocol.Protocol._lookup_next_layer>`
would default it to plain :class:`~pcapkit.protocols.misc.raw.Raw`. That
has no ``next`` field, so the walk in
:meth:`IPv6._decode_next_layer <pcapkit.protocols.internet.ipv6.IPv6._decode_next_layer>` would
Expand Down
4 changes: 2 additions & 2 deletions pcapkit/corekit/sentinels.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,8 +571,8 @@ def __bool__(self) -> 'Literal[False]':
#: own ``__keywords__`` out of its :attr:`~object.__dict__`, where
#: :obj:`None` is itself a meaningful value -- the opt-out that says the
#: class cannot enumerate its keywords, cf.
#: :attr:`ProtocolBase.__keywords__
#: <pcapkit.protocols.protocol.ProtocolBase.__keywords__>`. Never leaves
#: :attr:`Protocol.__keywords__
#: <pcapkit.protocols.protocol.Protocol.__keywords__>`. Never leaves
#: :mod:`pcapkit.protocols.protocol`, which imports it from here for exactly
#: that one read. Private by documentation rather than by a
#: leading underscore -- see :class:`AbsentType`'s own docstring.
Expand Down
2 changes: 1 addition & 1 deletion pcapkit/protocols/internet/internet.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ def register(cls, code: 'Enum_TransType', protocol: 'ModuleDescriptor[ProtocolBa
Arguments:
code: protocol code as in :class:`~pcapkit.const.reg.transtype.TransType`
protocol: module descriptor or a
:class:`~pcapkit.protocols.protocol.Protocol` subclass
:class:`~pcapkit.protocols.protocol.ProtocolBase` subclass

Raises:
pcapkit.utilities.exceptions.RegistryError: If ``protocol`` is not a
Expand Down
2 changes: 1 addition & 1 deletion pcapkit/protocols/internet/ipv6_ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@

1. **An unrecognised protocol number.** :class:`~pcapkit.const.ipv6.extension_header.ExtensionHeader.Shim6`
is a real IANA extension header (:rfc:`5533`) with no dedicated parser, so
:meth:`Internet._lookup_next_layer <pcapkit.protocols.protocol.ProtocolBase._lookup_next_layer>`
:meth:`Internet._lookup_next_layer <pcapkit.protocols.protocol.Protocol._lookup_next_layer>`
would default it to plain :class:`~pcapkit.protocols.misc.raw.Raw`. That
has no ``next`` field, so the walk in
:meth:`IPv6._decode_next_layer <pcapkit.protocols.internet.ipv6.IPv6._decode_next_layer>`
Expand Down
4 changes: 2 additions & 2 deletions pcapkit/protocols/link/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def register(cls, code: 'Enum_EtherType', protocol: 'ModuleDescriptor[ProtocolBa
Arguments:
code: protocol code as in :class:`~pcapkit.const.reg.ethertype.EtherType`
protocol: module descriptor or a
:class:`~pcapkit.protocols.protocol.Protocol` subclass
:class:`~pcapkit.protocols.protocol.ProtocolBase` subclass

Raises:
pcapkit.utilities.exceptions.RegistryError: If ``protocol`` is not a
Expand All @@ -158,7 +158,7 @@ def register(cls, code: 'Enum_EtherType', protocol: 'ModuleDescriptor[ProtocolBa

Takes the protocol as one ``protocol`` argument, a
:class:`~pcapkit.corekit.module.ModuleDescriptor` or a
:class:`~pcapkit.protocols.protocol.Protocol` subclass, in place of
:class:`~pcapkit.protocols.protocol.ProtocolBase` subclass, in place of
the ``module`` and ``class_`` names.

"""
Expand Down
4 changes: 2 additions & 2 deletions pcapkit/protocols/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -1006,7 +1006,7 @@ def analyze(cls, proto: 'int', payload: 'bytes', **kwargs: 'Any') -> 'ProtocolBa
**kwargs: Arbitrary keyword arguments.

Returns:
Parsed payload as a :class:`~pcapkit.protocols.protocol.Protocol`
Parsed payload as a :class:`~pcapkit.protocols.protocol.ProtocolBase`
instance.

"""
Expand Down Expand Up @@ -1039,7 +1039,7 @@ def register(cls, code: 'int', protocol: 'ModuleDescriptor | Type[ProtocolBase]'
Arguments:
code: protocol code
protocol: module descriptor or a
:class:`~pcapkit.protocols.protocol.Protocol` subclass
:class:`~pcapkit.protocols.protocol.ProtocolBase` subclass

Raises:
pcapkit.utilities.exceptions.RegistryError: If ``protocol`` is not a
Expand Down
2 changes: 1 addition & 1 deletion pcapkit/protocols/schema/link/ethernet.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def callback_payload(self: 'PayloadField', packet: 'dict[str, Any]') -> 'None':
:obj:`None`; the resolved class is assigned to ``self.protocol``.

Important:
The lookup goes through :meth:`ProtocolBase._lookup_next_layer
The lookup goes through :meth:`Protocol._lookup_next_layer
<pcapkit.protocols.protocol.Protocol._lookup_next_layer>` rather than
subscripting the registry. :attr:`Ethernet.__proto__
<pcapkit.protocols.link.link.Link.__proto__>` *is*
Expand Down
2 changes: 1 addition & 1 deletion pcapkit/protocols/schema/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -1231,7 +1231,7 @@ class _EnumRegistry(collections.defaultdict):
The fallback itself is deliberate -- it is how an unknown option, chunk or
block falls back to its ``Unknown*``/``Unassigned*`` schema -- so this
subclass keeps returning it and just stops recording it.
:meth:`ProtocolBase._lookup_registry
:meth:`Protocol._lookup_registry
<pcapkit.protocols.protocol.Protocol._lookup_registry>`
does the same for the protocol-layer ``__proto__`` family.

Expand Down
2 changes: 1 addition & 1 deletion pcapkit/protocols/transport/sctp.py
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,7 @@ def register(cls, code: 'Enum_PayloadProtocolIdentifier | int', protocol: 'Modul
code: payload protocol identifier (PPID), as in
:class:`~pcapkit.const.sctp.payload_protocol_identifier.PayloadProtocolIdentifier`
protocol: module descriptor or a
:class:`~pcapkit.protocols.protocol.Protocol` subclass
:class:`~pcapkit.protocols.protocol.ProtocolBase` subclass

Important:
SCTP overrides :meth:`Transport.register
Expand Down
6 changes: 3 additions & 3 deletions pcapkit/protocols/transport/transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def register(cls, code: 'int', protocol: 'ModuleDescriptor[ProtocolBase] | Type[
Arguments:
code: port number
protocol: module descriptor or a
:class:`~pcapkit.protocols.protocol.Protocol` subclass
:class:`~pcapkit.protocols.protocol.ProtocolBase` subclass

Important:
This method must be called from a non-abstract class, as the
Expand Down Expand Up @@ -115,7 +115,7 @@ def register(cls, code: 'int', protocol: 'ModuleDescriptor[ProtocolBase] | Type[

Takes the protocol as one ``protocol`` argument, a
:class:`~pcapkit.corekit.module.ModuleDescriptor` or a
:class:`~pcapkit.protocols.protocol.Protocol` subclass, in place of
:class:`~pcapkit.protocols.protocol.ProtocolBase` subclass, in place of
the ``module`` and ``class_`` names.

"""
Expand All @@ -141,7 +141,7 @@ def analyze(cls, ports: 'tuple[int, int]', payload: 'bytes', **kwargs: 'Any') ->
**kwargs: Arbitrary keyword arguments.

Returns:
Parsed payload as a :class:`~pcapkit.protocols.protocol.Protocol`
Parsed payload as a :class:`~pcapkit.protocols.protocol.ProtocolBase`
instance.

"""
Expand Down
3 changes: 2 additions & 1 deletion pcapkit/vendor/ipx/packet.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@
#: ``IPX_PACKET_TYPE_IPX 0``, ``_RIP 1``, ``_ECHO 2``, ``_ERROR 3``, ``_PEP 4``,
#: ``_SPX 5``, ``_NCP 17`` and ``_WANBCAST 20`` -- the same values in the
#: same order. Types 1-5 are inherited from Xerox XNS IDP, which IPX was derived
#: from; no RFC assigns them, and the notes below say so where that is the case.
#: from, and -- type 4 apart -- no RFC assigns them. The note on each row below
#: says which sources do and do not carry its number.
DATA = {
# NOTE: not a protocol assignment. The scraped table listed 0 as "Unknown",
# and it doubles as the IPX protocol's own default for the ``type`` field.
Expand Down
8 changes: 4 additions & 4 deletions pcapkit/vendor/reg/linktype.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ class LinkType(Vendor):
LINK = 'http://www.tcpdump.org/linktypes.html'

#: Destinations of a bare ``LinkType`` member; see
#: :attr:`~pcapkit.vendor.default.Vendor.PROTO_DEST`. Two of them, because a
#: link-layer header type keys the next-layer registry of both capture file
#: formats, and :func:`~pcapkit.foundation.registry.protocols.register_linktype`
#: already registers into both by hand.
#: :attr:`~pcapkit.vendor.default.Vendor.PROTO_DEST`. One per capture file
#: format, because a link-layer header type keys the next-layer registry of
#: each, and :func:`~pcapkit.foundation.registry.protocols.register_linktype`
#: already registers into every one of them by hand.
PROTO_DEST = (
ModuleDescriptor('pcapkit.protocols.misc.pcap.frame', 'Frame'),
ModuleDescriptor('pcapkit.protocols.misc.pcapng', 'PCAPNG'),
Expand Down
10 changes: 5 additions & 5 deletions util/changelog_md.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,11 @@
_RFC_ROLE = re.compile(r':rfc:`(\d+)(?:#([\w.-]+))?`')

#: Anchor prefixes Sphinx renders as words rather than as part of the target,
#: from ``sphinx.roles._format_rfc_target`` (read from Sphinx 9.1.0, the version
#: this repository builds its documentation with): ``#section-3`` is titled
#: ``Section 3``, and an anchor of any other shape is left exactly as written.
#: Kept level with Sphinx so the Markdown link and the rendered documentation say
#: the same thing about the same page, which is the whole point of rule 2.
#: from ``sphinx.roles._format_rfc_target`` (read from Sphinx 9.1.0): ``#section-3``
#: is titled ``Section 3``, and an anchor of any other shape is left exactly as
#: written. Kept level with the Sphinx that ``docs/requirements.txt`` pins, so the
#: Markdown link and the rendered documentation say the same thing about the same
#: page, which is the whole point of rule 2.
_RFC_ANCHORS = frozenset({'appendix', 'page', 'section'})

#: The Sphinx configuration whose ``extlinks`` table :data:`EXTLINKS` mirrors.
Expand Down
Loading