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
5 changes: 3 additions & 2 deletions docs/source/contributing/conventions/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ House Conventions
* :ref:`mint-criterion` -- :mod:`pcapkit.const`, which is where the settled
questions have mostly arisen;
* :ref:`sentinel-convention` -- :mod:`pcapkit.corekit`;
* :ref:`registry-protocol` -- :mod:`pcapkit.const` again, and the registration
wrappers of :mod:`pcapkit.foundation.registry`;
* :ref:`registry-protocol` -- :mod:`pcapkit.const` again, the registration
wrappers of :mod:`pcapkit.foundation.registry`, and the value a protocol's
:meth:`~pcapkit.protocols.protocol.Protocol.__index__` returns;
* :ref:`extension-header-subclassing` -- a protocol class hierarchy;
* :ref:`protocol-family-grouping` -- how a protocol family's base and members
spell :meth:`~pcapkit.protocols.protocol.Protocol.id`, how a protocol class
Expand Down
83 changes: 83 additions & 0 deletions docs/source/contributing/conventions/registry-protocol.rst
Original file line number Diff line number Diff line change
Expand Up @@ -863,3 +863,86 @@ unless its ``EXCUSED`` table gives the reason. It fails as well on a map whose o
defines no ``register*`` classmethod of its own, unless its ``INHERITED`` table names
the one typed for that map's keys.

.. _registry-protocol-index:

The Registry Index
~~~~~~~~~~~~~~~~~~

**A protocol's** :meth:`~pcapkit.protocols.protocol.Protocol.__index__` **returns its**
:class:`~pcapkit.const.reg.linktype.LinkType`, :class:`~pcapkit.const.reg.ethertype.EtherType`,
:class:`~pcapkit.const.reg.transtype.TransType` **or**
:class:`~pcapkit.const.reg.apptype.apptype.AppType` **member wherever one names it**, ahead of
any other registry's code, such as an :class:`~pcapkit.const.linux.arphrd.ARPHRD` type or an
NLPID. Ruled on :issue:`2627`. So :class:`~pcapkit.protocols.link.radiotap.Radiotap` returns
``LinkType.IEEE802_11_RADIOTAP`` rather than ``ARPHRD.IEEE80211_RADIOTAP``.

* **An AppType value is returned as its port.** An
:class:`~pcapkit.const.reg.apptype.apptype.AppType` member is a :class:`str`, and
:func:`int` and :func:`operator.index` raise :exc:`TypeError` when ``__index__`` returns
anything but an :class:`int`, so a class an AppType member names returns that member's
port: :class:`~pcapkit.protocols.link.l2tpv2.L2TPv2` returns 1701, ``int(UDP.l2tp)``.
:class:`~pcapkit.protocols.application.tls.TLS`, which
:class:`~pcapkit.protocols.application.sslv3.SSLv3` inherits, and
:class:`~pcapkit.protocols.application.dtls.DTLS` return the ports TCP and UDP dispatch
them at, 443 and 5684, although IANA registers those ports as ``https`` and ``coaps``,
the protocols carried over them. Ruled on :issue:`2627`.
* **Netlink keeps** ``ARPHRD.NETLINK``: ``LINKTYPE_NETLINK`` (253) names the SLL-framed
form, not the ``nlmsghdr`` :class:`~pcapkit.protocols.link.netlink.Netlink` reads. Ruled
on :issue:`2627`.
* **The index keys no registration.** Every built-in ``__proto__`` entry spells out its key,
so a class keeps its place in a secondary registry whatever its index:
:class:`~pcapkit.protocols.link.frad.FRAD` stays under ``ARPHRD.FRAD`` in
:attr:`LinuxSLL.__proto__ <pcapkit.protocols.link.linux_sll.LinuxSLL.__proto__>`.
* **A class dispatched under several values returns one of them.**
:class:`~pcapkit.protocols.internet.ipv4.IPv4` and
:class:`~pcapkit.protocols.internet.ipv6.IPv6`, dispatched under LinkType 228 and 229,
EtherType 0x0800 and 0x86DD, and TransType 4 and 41, and
:class:`~pcapkit.protocols.internet.ipx.IPX`, under EtherType 0x8137 and TransType 111,
keep the TransType value they returned in 1.5.x, so no shipped value changes.
:class:`~pcapkit.protocols.link.loopback.Loopback`, at LinkType 0 and 108, returns
``LinkType.NULL``, 0, and :class:`~pcapkit.protocols.application.http.HTTP`, at TCP and
UDP ports 80 and 8080, returns 80.
* **An index is a reachability claim** wherever its enum declares ``__proto_dest__``.
:file:`tests/protocols/test_dispatch_reachability_unit.py` requires the class in the
registry that enum keys, or an entry in its ``UNBOUND`` table giving the reason nothing
dispatches it there, which the binding deletes. A port, being an :class:`int`, makes no
such claim.
* **No value, no index.** :class:`~pcapkit.protocols.link.link.Link`,
:class:`~pcapkit.protocols.internet.internet.Internet` and
:class:`~pcapkit.protocols.transport.transport.Transport`, and the
:class:`~pcapkit.protocols.internet.ip.IP`, :class:`~pcapkit.protocols.internet.ipsec.IPsec`
and :class:`~pcapkit.protocols.internet.icmp.ICMP` family bases, leave the abstract method
unimplemented and return :obj:`None`. These raise:

- :class:`~pcapkit.protocols.link.vlan.VLAN`,
:class:`~pcapkit.protocols.link.linux_sll.LinuxSLL` and
:class:`~pcapkit.protocols.link.l2tp.L2TP`, abstract family bases nothing dispatches
to, whose members each carry a value of their own;
- :class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext`, although
:attr:`Internet.__proto__ <pcapkit.protocols.internet.internet.Internet.__proto__>`
dispatches it at TransType 140, Shim6: one class parses Shim6 and any other
:rfc:`6564`-format header registered to it, so its value differs per instance, and
``__index__`` is a class method;
- :class:`~pcapkit.protocols.application.application.Application`, and the
:class:`~pcapkit.protocols.application.ssl.SSL` family base and
:class:`~pcapkit.protocols.application.sslv2.SSLv2`, which define no ``__index__`` and
inherit its raise. Nothing dispatches to SSLv2: an SSLv2 record on TCP port 443 parses
as a TLS layer adopting SSLv2's data;
- :class:`~pcapkit.protocols.misc.raw.Raw` and
:class:`~pcapkit.protocols.misc.no_payload.NoPayload`, the undissected octets and the
empty payload, and :class:`~pcapkit.protocols.misc.checksum.ip_pseudo.IP_Pseudo`, the
checksum pseudo-header, which no registry value names. TransType 59, under which
:attr:`Internet.__proto__ <pcapkit.protocols.internet.internet.Internet.__proto__>`
dispatches Raw, is IPv6-NoNxt, no next header;
- the PCAP :class:`~pcapkit.protocols.misc.pcap.header.Header` and
:class:`~pcapkit.protocols.misc.pcap.frame.Frame`, and
:class:`~pcapkit.protocols.misc.pcapng.PCAPNG`, the capture file's own records. A PCAP
frame or PCAP-NG packet block instance returns its index number, an ordinal outside
the rule.

A member sharing its family base's value inherits the base's ``__index__`` rather than
overriding it.

:file:`tests/protocols/test_protocol_index_registry_2627_unit.py` holds every protocol
class to the rule, with each value derived from the registry and the number it assigns.

16 changes: 8 additions & 8 deletions docs/source/contributing/pep.rst
Original file line number Diff line number Diff line change
Expand Up @@ -336,9 +336,10 @@ Some wanted only a table entry, because the dissector was already there:

* **Done.** :class:`~pcapkit.protocols.application.ospf.OSPF` is bound at
:class:`~pcapkit.const.reg.transtype.TransType` 89 (``OSPFIGP``) and
:class:`~pcapkit.protocols.link.l2tpv2.L2TPv2` at UDP port 1701.
``__index__`` raises on both, which is correct: neither is reached through a
link-layer EtherType.
:class:`~pcapkit.protocols.link.l2tpv2.L2TPv2` at UDP port 1701, and
``__index__`` returns those values: ``TransType.OSPFIGP``, and 1701 as an
:class:`int`, as an AppType-valued index is the member's port
(:ref:`registry-protocol-index`).
* **Done.** :class:`~pcapkit.protocols.application.ftp_data.FTP_DATA` is bound at TCP
port 20 (IANA ``ftp-data``). It is a thin
:class:`~pcapkit.protocols.misc.raw.Raw` subclass, so this buys the payload a
Expand Down Expand Up @@ -370,11 +371,10 @@ Follow-ups to the bindings above:
reason is structural: it references :rfc:`3931`, i.e. L2TPv3 over IP, and **there is
no** ``L2TPv3`` **class for it to point at**. What exists is
:class:`~pcapkit.protocols.link.l2tpv2.L2TPv2`, the :rfc:`2661` v2 framing, reached over
UDP 1701. So the binding waits on a v3 dissector, which is also the first member of the
family to carry an :meth:`~pcapkit.protocols.protocol.Protocol.__index__` of its own --
115 being that index. :mod:`pcapkit.protocols.link.l2tp` records what v3 needs, and what
``L2F`` needs alongside it: the version nibble reading ``1`` selects L2F [:rfc:`2341`],
a separate protocol, not an earlier L2TP.
UDP 1701, which is its :meth:`~pcapkit.protocols.protocol.Protocol.__index__`. So the
binding waits on a v3 dissector. :mod:`pcapkit.protocols.link.l2tp` records what v3
needs, and what ``L2F`` needs alongside it: the version nibble reading ``1`` selects
L2F [:rfc:`2341`], a separate protocol, not an earlier L2TP.
* The :class:`~pcapkit.protocols.link.l2tp.L2TP` family lives under
:mod:`pcapkit.protocols.link` and so reports ``layer == 'Link'``, although it
is carried inside UDP. That is deliberate: a tunnelling protocol is placed by what
Expand Down
1 change: 1 addition & 0 deletions docs/source/pcapkit/protocols/application/dtls.rst
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ ones on the wire.
.. automethod:: analyze_handshake

.. automethod:: __length_hint__
.. automethod:: __index__

.. automethod:: _make_data

Expand Down
2 changes: 2 additions & 0 deletions docs/source/pcapkit/protocols/application/ftp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ which implements an extractor for File Transfer Protocol
.. automethod:: read
.. automethod:: make

.. automethod:: __index__

.. automethod:: _make_data

Auxiliary Data
Expand Down
2 changes: 2 additions & 0 deletions docs/source/pcapkit/protocols/application/ftp_data.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ Transfer Protocol (FTP) [*]_, whose octets it keeps undissected.

.. autoproperty:: name

.. automethod:: __index__

.. rubric:: Footnotes

.. [*] https://en.wikipedia.org/wiki/File_Transfer_Protocol
1 change: 1 addition & 0 deletions docs/source/pcapkit/protocols/application/http.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ which tells whether a payload opens with an HTTP/1.* start line.

.. automethod:: _make_data

.. automethod:: __index__
.. automethod:: __init__
.. automethod:: __new__

Expand Down
1 change: 1 addition & 0 deletions docs/source/pcapkit/protocols/application/ngap.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ unregistered PPID would, because
.. automethod:: make

.. automethod:: __length_hint__
.. automethod:: __index__
.. automethod:: _make_data

Decoder Dependency
Expand Down
1 change: 1 addition & 0 deletions docs/source/pcapkit/protocols/application/tls.rst
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ this is routine.
.. automethod:: unpack

.. automethod:: __length_hint__
.. automethod:: __index__

.. automethod:: _make_data

Expand Down
6 changes: 3 additions & 3 deletions docs/source/pcapkit/protocols/link/l2tp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ Versions Not Implemented
header from v2, and is reachable two ways: over UDP port 1701 like v2, and
directly over IP as **protocol number 115**. That second route is why
:attr:`Internet.__proto__ <pcapkit.protocols.internet.internet.Internet.__proto__>`
leaves 115 unbound: the binding waits on an ``L2TPv3`` class. It would also be
the first member of this family with a real
:meth:`~pcapkit.protocols.protocol.Protocol.__index__`.
leaves 115 unbound: the binding waits on an ``L2TPv3`` class. Protocol 115 names v3
alone: v2's :meth:`~pcapkit.protocols.protocol.Protocol.__index__` is its UDP
port.

**L2F** [:rfc:`2341`] is reached when the version nibble reads ``1``. It is *not*
an earlier version of L2TP: :rfc:`2661` §3.1 requires ``Ver`` to be 2 and reserves
Expand Down
20 changes: 20 additions & 0 deletions pcapkit/protocols/application/dtls.py
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,26 @@ def __length_hint__(self) -> 'Literal[13]':
"""Return an estimated length for the object."""
return _LEGACY_HEADER_LENGTH # type: ignore[return-value]

@classmethod
def __index__(cls) -> 'int': # type: ignore[override]
"""Numeral registry index of the protocol.

Returns:
Numeral registry index of the protocol in `IANA`_ service names and
port numbers: UDP port 5684, the port of
:attr:`UDP.coaps <pcapkit.const.reg.apptype.udp.UDP.coaps>` as an
:class:`int`, which
:attr:`UDP.__proto__ <pcapkit.protocols.transport.udp.UDP.__proto__>`
dispatches here (:ref:`registry-protocol-index`).

.. _IANA: https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml

"""
# imported here, as ``import pcapkit`` leaves the UDP registry unloaded (#1538)
from pcapkit.const.reg.apptype.udp import UDP # pylint: disable=import-outside-toplevel

return int(UDP.coaps) # type: ignore[call-overload]

##########################################################################
# Utilities.
##########################################################################
Expand Down
29 changes: 29 additions & 0 deletions pcapkit/protocols/application/ftp.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,35 @@ def make(self,
data=b'%s%s%s%s\r\n' % (prefix, mf, sep, suffix) + b''.join(b'%s\r\n' % line for line in lines),
)

##########################################################################
# Data models.
##########################################################################

@classmethod
def __index__(cls) -> 'int': # type: ignore[override]
"""Numeral registry index of the protocol.

Returns:
Numeral registry index of the protocol in `IANA`_ service names and
port numbers: TCP port 21, the port of
:attr:`TCP.ftp <pcapkit.const.reg.apptype.tcp.TCP.ftp>` as an
:class:`int`, which
:attr:`TCP.__proto__ <pcapkit.protocols.transport.tcp.TCP.__proto__>`
dispatches here (:ref:`registry-protocol-index`).

.. _IANA: https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml

.. versionchanged:: 1.6.0

Returns TCP port 21, where it raised
:exc:`~pcapkit.utilities.exceptions.IntError` (:issue:`2627`).

"""
# imported here, as ``import pcapkit`` leaves the TCP registry unloaded (#1538)
from pcapkit.const.reg.apptype.tcp import TCP # pylint: disable=import-outside-toplevel

return int(TCP.ftp) # type: ignore[call-overload]

##########################################################################
# Utilities.
##########################################################################
Expand Down
33 changes: 31 additions & 2 deletions pcapkit/protocols/application/ftp_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ class FTP_DATA(Application, Raw): # pylint: disable=abstract-method

Derives from :class:`~pcapkit.protocols.application.application.Application`
as well as :class:`~pcapkit.protocols.misc.raw.Raw`, so its layer is
``'Application'``, where it was :obj:`None`, and its ``__index__`` is
``'Application'``, where it was :obj:`None`, and its ``__index__`` became
:meth:`Application.__index__
<pcapkit.protocols.application.application.Application.__index__>`,
raising :exc:`~pcapkit.utilities.exceptions.IntError` where it raised
raising :exc:`~pcapkit.utilities.exceptions.IntError` where it had raised
:exc:`~pcapkit.utilities.exceptions.UnsupportedCall` (:issue:`2285`).

"""
Expand All @@ -60,3 +60,32 @@ class FTP_DATA(Application, Raw): # pylint: disable=abstract-method
def name(self) -> 'Literal["FTP_DATA"]': # type: ignore[override]
"""Name of current protocol."""
return 'FTP_DATA'

##########################################################################
# Data models.
##########################################################################

@classmethod
def __index__(cls) -> 'int': # type: ignore[override]
"""Numeral registry index of the protocol.

Returns:
Numeral registry index of the protocol in `IANA`_ service names and
port numbers: TCP port 20, the port of
:attr:`TCP.ftp_data <pcapkit.const.reg.apptype.tcp.TCP.ftp_data>` as
an :class:`int`, which
:attr:`TCP.__proto__ <pcapkit.protocols.transport.tcp.TCP.__proto__>`
dispatches here (:ref:`registry-protocol-index`).

.. _IANA: https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml

.. versionchanged:: 1.6.0

Returns TCP port 20, where it raised
:exc:`~pcapkit.utilities.exceptions.IntError` (:issue:`2627`).

"""
# imported here, as ``import pcapkit`` leaves the TCP registry unloaded (#1538)
from pcapkit.const.reg.apptype.tcp import TCP # pylint: disable=import-outside-toplevel

return int(TCP.ftp_data) # type: ignore[call-overload]
28 changes: 28 additions & 0 deletions pcapkit/protocols/application/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,34 @@ def from_data(cls, data: '_PT | dict[str, Any]', **kwargs: 'Any') -> 'Self':
# Data models.
##########################################################################

@classmethod
def __index__(cls) -> 'int': # type: ignore[override]
"""Numeral registry index of the protocol.

Returns:
Numeral registry index of the protocol in `IANA`_ service names and
port numbers: TCP port 80, the port of
:attr:`TCP.http <pcapkit.const.reg.apptype.tcp.TCP.http>` as an
:class:`int`, which
:attr:`TCP.__proto__ <pcapkit.protocols.transport.tcp.TCP.__proto__>`
dispatches here, as it does port 8080 and as
:attr:`UDP.__proto__ <pcapkit.protocols.transport.udp.UDP.__proto__>`
does both (:ref:`registry-protocol-index`). The versioned classes
inherit it.

.. _IANA: https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml

.. versionchanged:: 1.6.0

Returns TCP port 80, where it raised
:exc:`~pcapkit.utilities.exceptions.IntError` (:issue:`2627`).

"""
# imported here, as ``import pcapkit`` leaves the TCP registry unloaded (#1538)
from pcapkit.const.reg.apptype.tcp import TCP # pylint: disable=import-outside-toplevel

return int(TCP.http) # type: ignore[call-overload]

def __init__(self, file: 'Optional[IO[bytes] | bytes]' = None,
length: 'Optional[int]' = None, *,
version: 'Optional[Literal[1, 2]]' = None, **kwargs: 'Any') -> 'None':
Expand Down
24 changes: 24 additions & 0 deletions pcapkit/protocols/application/ngap.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@

from pcapkit.const.ngap.procedure_code import ProcedureCode as Enum_ProcedureCode
from pcapkit.const.ngap.protocol_ie import ProtocolIE as Enum_ProtocolIE
from pcapkit.const.reg.apptype.sctp import SCTP as Enum_SCTP
from pcapkit.corekit.enum import EnumLookup
from pcapkit.corekit.infoclass import Info
from pcapkit.protocols.application.application import Application
Expand Down Expand Up @@ -544,6 +545,29 @@ def __length_hint__(self) -> 'Literal[4]':
"""
return 4

@classmethod
def __index__(cls) -> 'int': # type: ignore[override]
"""Numeral registry index of the protocol.

Returns:
Numeral registry index of the protocol in `IANA`_ service names and
port numbers: SCTP port 38412, the port of
:attr:`SCTP.ng_control <pcapkit.const.reg.apptype.sctp.SCTP.ng_control>`
as an :class:`int`.
:attr:`SCTP.__proto__ <pcapkit.protocols.transport.sctp.SCTP.__proto__>`
dispatches by payload protocol identifier rather than by port, so
this value keys no dispatch (:ref:`registry-protocol-index`).

.. _IANA: https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml

.. versionchanged:: 1.6.0

Returns SCTP port 38412, where it raised
:exc:`~pcapkit.utilities.exceptions.IntError` (:issue:`2627`).

"""
return int(Enum_SCTP.ng_control) # type: ignore[call-overload]

##########################################################################
# Utilities.
##########################################################################
Expand Down
Loading
Loading