From 3a46515896fce4954e2e06461cd6da6bb685036a Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Fri, 30 Oct 2026 19:01:39 -0400 Subject: [PATCH 1/3] docs(conventions): add the protocol family grouping page (#2476) - New protocol-family-grouping.rst records the id() criterion (a family base lists only its direct concrete subclasses), abstract family names composing their members, grouping by name and history rather than wire format, the member shapes in use, RFC canonical names, what selectors match, and the ban on invented _Family suffixes. - Listed in the conventions index and toctree, and added to ANCHORS in test_conventions_doc_claims.py so its anchor and toctree slot are pinned. Sphinx: no new warnings; tests/project conventions tests pass. --- .../source/contributing/conventions/index.rst | 3 + .../conventions/protocol-family-grouping.rst | 145 ++++++++++++++++++ tests/project/test_conventions_doc_claims.py | 1 + 3 files changed, 149 insertions(+) create mode 100644 docs/source/contributing/conventions/protocol-family-grouping.rst diff --git a/docs/source/contributing/conventions/index.rst b/docs/source/contributing/conventions/index.rst index 5c75d45d03..54ea601b7d 100644 --- a/docs/source/contributing/conventions/index.rst +++ b/docs/source/contributing/conventions/index.rst @@ -15,6 +15,8 @@ House Conventions * :ref:`sentinel-convention` -- :mod:`pcapkit.corekit`; * :ref:`registry-protocol` -- :mod:`pcapkit.const` again; * :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`; * :ref:`protocol-layer-placement` -- which subpackage a dissector belongs in; * :ref:`process` -- the repository rather than any of its code; * :ref:`documentation` -- the prose itself, on these pages and in the API @@ -38,6 +40,7 @@ House Conventions sentinel-convention registry-protocol extension-header-subclassing + protocol-family-grouping protocol-layer-placement process documentation diff --git a/docs/source/contributing/conventions/protocol-family-grouping.rst b/docs/source/contributing/conventions/protocol-family-grouping.rst new file mode 100644 index 0000000000..e2e68cc8c2 --- /dev/null +++ b/docs/source/contributing/conventions/protocol-family-grouping.rst @@ -0,0 +1,145 @@ +.. _protocol-family-grouping: + +Protocol Family Grouping +------------------------ + +A protocol family is a base class named after the family plus its member classes. +What the base and each member return from +:meth:`~pcapkit.protocols.protocol.Protocol.id` decides what every selector matches, +so the tuples are settled here rather than re-derived per family. Set on +:issue:`2441`, :issue:`2446` and :issue:`2462`. + +The ``id()`` Criterion +~~~~~~~~~~~~~~~~~~~~~~ + +**A family base lists its direct concrete subclasses, and nothing else.** An abstract +intermediate is left out, and so is everything beneath it, since that intermediate +carries its own tuple. :issue:`2441`. + +* :meth:`IP.id() ` is ``('IPv4', 'IPv6')``. + :class:`~pcapkit.protocols.internet.ah.AH` and + :class:`~pcapkit.protocols.internet.esp.ESP` are not in it: they belong to the + separate abstract :class:`~pcapkit.protocols.internet.ipsec.IPsec`. +* :meth:`IPsec.id() ` is + ``('AH', 'ESP')``. + +Abstract Family Names +~~~~~~~~~~~~~~~~~~~~~ + +**A family name composes its members even when the base also parses.** +:class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext` is concrete -- it is the +fallback :class:`~pcapkit.protocols.internet.ipv6.IPv6` substitutes when a dedicated +extension-header parser fails -- but its name is the family's, so its ``id()`` is to +list its direct concrete members, and each member narrows its own ``id()`` to itself. +:issue:`2462`. + +The rejected alternative gave the base only its own name, because it also parses +``Shim6``, which has no member class and which every member's name would then select. +That case ends once ``Shim6`` gets its own parser, so the ruling does not wait for it. + +One consequence is accepted: once composed, an +:class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext` standing in for a header whose +own parser failed matches every member's name. + +Grouping by Name and History +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Members share a base because they share a name and a lineage, not a wire format.** +SSL is the case that settled it: its family covers every SSL version, TLS (a rebrand +of SSL) and DTLS, although the SSL versions are not wire-compatible with each other. +The same reading puts HTTP/1 and HTTP/2 under +:class:`~pcapkit.protocols.application.http.HTTP`, IPv4 and IPv6 under +:class:`~pcapkit.protocols.internet.ip.IP`, and AH and ESP under +:class:`~pcapkit.protocols.internet.ipsec.IPsec`. :issue:`2441`. + +Shared parsing code is an implementation detail and does not decide the grouping. +Nor does a family base need dispatcher logic: :class:`~pcapkit.protocols.internet.ip.IP` +and :class:`~pcapkit.protocols.internet.ipsec.IPsec` have none, and each member is +bound directly. :class:`~pcapkit.protocols.application.http.HTTP` dispatches +because it has to guess a version its wire format does not state, not because it is +a family base. + +Member Shapes +~~~~~~~~~~~~~ + +Three shapes are in use. Pick by how far a sibling's selector should reach. + +.. list-table:: + :header-rows: 1 + :widths: 22 38 40 + + * - Shape + - ``id()`` + - In the tree + * - Narrow to self + - the member's own name only + - :class:`~pcapkit.protocols.internet.ah.AH` ``('AH',)``, + :class:`~pcapkit.protocols.internet.esp.ESP` ``('ESP',)`` + * - Family-inclusive + - the member's label plus the family name + - :class:`~pcapkit.protocols.link.c_tag.C_Tag` ``('C_Tag', 'VLAN')`` and + :class:`~pcapkit.protocols.link.s_tag.S_Tag`; + :class:`HTTPv1 ` + ``('HTTP', 'HTTPv1')`` and + :class:`HTTPv2 `; + :class:`~pcapkit.protocols.link.l2tpv2.L2TPv2` ``('L2TP', 'L2TPv2')`` + * - One parser, dynamic alias + - fixed, whichever protocol was read + - :class:`~pcapkit.protocols.link.arp.ARP` ``('ARP',)`` parses ARP, InARP, + RARP and DRARP and reports which through + :attr:`~pcapkit.protocols.protocol.Protocol.alias`; + :class:`~pcapkit.protocols.link.arp.InARP` ``('InARP',)`` is a thin + subclass that exists for explicit construction + +A family-inclusive member is selected by any sibling's registry key, because both +expand to the shared family name (see :ref:`below `). A +narrow member is selected only by its own name and its family's key. + +.. _protocol-family-canonical-names: + +Canonical Names +~~~~~~~~~~~~~~~ + +**A class name may carry a version that the protocol's own name does not.** Where +the defining RFC names the protocol without a version, a versioned class and module +name are fine for telling the members apart in code, but ``id()`` returns the RFC's +canonical name and :attr:`~pcapkit.protocols.protocol.Protocol.name` follows the +RFC's title, with no version string added. :issue:`2446`. + +.. _protocol-family-selectors: + +Selector Matching +~~~~~~~~~~~~~~~~~ + +What a selector reaches is defined by +:meth:`~pcapkit.protocols.protocol.Protocol.expand_comp`, +:meth:`~pcapkit.protocols.protocol.Protocol.__contains__`, +:meth:`~pcapkit.protocols.protocol.Protocol.__getitem__` and ``Protocol.__eq__``. +All comparisons are case-insensitive. + +* **String** ``==`` compares against the class and its own ``id()`` only; the + selector is not expanded. On a parsed IPv4 layer, ``ipv4 == 'IPv4'`` is ``True`` + and ``ipv4 == 'IP'`` is ``False``. +* ``in`` **and** ``[]`` expand the selector first. A registry key becomes its class + plus that class's ``id()``, as does a class object, and a layer matches when any + of those appears among its own class and ``id()``. So ``'IP' in frame`` is + ``True`` for an IPv4 frame, and ``'HTTPv2' in frame`` is ``True`` for an HTTP/1.1 + frame, since both sides carry ``'HTTP'``. +* A string that is **not** a registry key is matched by name alone against each + layer's ``id()``. An alias such as ``'HTTP/1.1'`` matches nothing. + +So adding a name to an ``id()`` tuple widens every ``in`` and ``[]`` selector that +can reach it; check the reach before choosing a shape. + +No Invented Suffixes +~~~~~~~~~~~~~~~~~~~~ + +**The family base takes the family's own name.** +:class:`~pcapkit.protocols.internet.ip.IP`, +:class:`~pcapkit.protocols.internet.ipsec.IPsec`, +:class:`~pcapkit.protocols.application.http.HTTP`, +:class:`~pcapkit.protocols.link.vlan.VLAN` and +:class:`~pcapkit.protocols.link.l2tp.L2TP` are the precedent. No ``_Family`` base +exists, and none is to be minted. Where a member already holds the family name, the +member takes its versioned class name instead, as :ref:`above +`. :issue:`2446`. diff --git a/tests/project/test_conventions_doc_claims.py b/tests/project/test_conventions_doc_claims.py index 2372c18c97..91eb980fea 100644 --- a/tests/project/test_conventions_doc_claims.py +++ b/tests/project/test_conventions_doc_claims.py @@ -113,6 +113,7 @@ 'sentinel-convention', 'registry-protocol', 'extension-header-subclassing', + 'protocol-family-grouping', 'protocol-layer-placement', 'process', 'documentation', From 80a11f953c32ad1d1860b3ac4e003bce1c2130cd Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Fri, 30 Oct 2026 19:37:51 -0400 Subject: [PATCH 2/3] docs(conventions): scope the family grouping page to the rulings (#2476) - id() criterion: members-only and own-name-first tuples; ARP alone. - Drop the Shim6 narration and the SSL/TLS/DTLS grouping. - Versioned class names "may"; add #2443's selector ruling. - ProtoChain also matches aliases; ARP row names RARP/DRARP on 0x8035. - Cite pull requests with :pr:; no count. --- .../conventions/protocol-family-grouping.rst | 126 ++++++++++-------- 1 file changed, 69 insertions(+), 57 deletions(-) diff --git a/docs/source/contributing/conventions/protocol-family-grouping.rst b/docs/source/contributing/conventions/protocol-family-grouping.rst index e2e68cc8c2..b883094eb2 100644 --- a/docs/source/contributing/conventions/protocol-family-grouping.rst +++ b/docs/source/contributing/conventions/protocol-family-grouping.rst @@ -5,64 +5,60 @@ Protocol Family Grouping A protocol family is a base class named after the family plus its member classes. What the base and each member return from -:meth:`~pcapkit.protocols.protocol.Protocol.id` decides what every selector matches, -so the tuples are settled here rather than re-derived per family. Set on -:issue:`2441`, :issue:`2446` and :issue:`2462`. +:meth:`~pcapkit.protocols.protocol.Protocol.id` is what the selectors on +:class:`~pcapkit.protocols.protocol.Protocol` match against, so the tuples are +settled here rather than re-derived per family. Set on :pr:`2441`, :pr:`2443`, +:pr:`2446` and :issue:`2462`. The ``id()`` Criterion ~~~~~~~~~~~~~~~~~~~~~~ -**A family base lists its direct concrete subclasses, and nothing else.** An abstract -intermediate is left out, and so is everything beneath it, since that intermediate -carries its own tuple. :issue:`2441`. +**A family base lists its direct concrete subclasses.** An abstract intermediate is +left out, and so is everything beneath it, since that intermediate carries its own +tuple. :pr:`2441`. -* :meth:`IP.id() ` is ``('IPv4', 'IPv6')``. - :class:`~pcapkit.protocols.internet.ah.AH` and - :class:`~pcapkit.protocols.internet.esp.ESP` are not in it: they belong to the - separate abstract :class:`~pcapkit.protocols.internet.ipsec.IPsec`. -* :meth:`IPsec.id() ` is - ``('AH', 'ESP')``. +The member list takes either of these forms: + +* **Members only.** :meth:`IP.id() ` is + ``('IPv4', 'IPv6')``; :class:`~pcapkit.protocols.internet.ah.AH` and + :class:`~pcapkit.protocols.internet.esp.ESP` are not in it, since they belong to + the separate abstract :class:`~pcapkit.protocols.internet.ipsec.IPsec`, whose + :meth:`~pcapkit.protocols.internet.ipsec.IPsec.id` is ``('AH', 'ESP')``. +* **The base's own name, then the members.** + :meth:`VLAN.id() ` is + ``('VLAN', 'C_Tag', 'S_Tag')``, + :meth:`L2TP.id() ` is ``('L2TP', 'L2TPv2')`` + and :meth:`HTTP.id() ` is + ``('HTTP', 'HTTPv1', 'HTTPv2')``. + +:meth:`ARP.id() ` is ``('ARP',)`` alone; see +:ref:`protocol-family-shapes`. Abstract Family Names ~~~~~~~~~~~~~~~~~~~~~ -**A family name composes its members even when the base also parses.** -:class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext` is concrete -- it is the -fallback :class:`~pcapkit.protocols.internet.ipv6.IPv6` substitutes when a dedicated -extension-header parser fails -- but its name is the family's, so its ``id()`` is to -list its direct concrete members, and each member narrows its own ``id()`` to itself. -:issue:`2462`. - -The rejected alternative gave the base only its own name, because it also parses -``Shim6``, which has no member class and which every member's name would then select. -That case ends once ``Shim6`` gets its own parser, so the ruling does not wait for it. - -One consequence is accepted: once composed, an -:class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext` standing in for a header whose -own parser failed matches every member's name. - -Grouping by Name and History -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**Members share a base because they share a name and a lineage, not a wire format.** -SSL is the case that settled it: its family covers every SSL version, TLS (a rebrand -of SSL) and DTLS, although the SSL versions are not wire-compatible with each other. -The same reading puts HTTP/1 and HTTP/2 under -:class:`~pcapkit.protocols.application.http.HTTP`, IPv4 and IPv6 under -:class:`~pcapkit.protocols.internet.ip.IP`, and AH and ESP under -:class:`~pcapkit.protocols.internet.ipsec.IPsec`. :issue:`2441`. - -Shared parsing code is an implementation detail and does not decide the grouping. -Nor does a family base need dispatcher logic: :class:`~pcapkit.protocols.internet.ip.IP` -and :class:`~pcapkit.protocols.internet.ipsec.IPsec` have none, and each member is -bound directly. :class:`~pcapkit.protocols.application.http.HTTP` dispatches -because it has to guess a version its wire format does not state, not because it is -a family base. +**An abstract family name composes its members.** +:class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext` carries the family's name, so +its ``id()`` is to list its direct concrete members, and each member narrows its own +``id()`` to itself. :issue:`2462`. + +Grouping by Name +~~~~~~~~~~~~~~~~ + +**Members share a base because they share a name, not a wire format.** HTTP/1 and +HTTP/2 share :class:`~pcapkit.protocols.application.http.HTTP`, IPv4 and IPv6 share +:class:`~pcapkit.protocols.internet.ip.IP`, and AH and ESP share +:class:`~pcapkit.protocols.internet.ipsec.IPsec`. A family base needs no dispatcher +logic: :class:`~pcapkit.protocols.internet.ip.IP` and +:class:`~pcapkit.protocols.internet.ipsec.IPsec` have none, and each member is bound +directly. :pr:`2441`. + +.. _protocol-family-shapes: Member Shapes ~~~~~~~~~~~~~ -Three shapes are in use. Pick by how far a sibling's selector should reach. +Pick a member's shape by how far a sibling's selector should reach. .. list-table:: :header-rows: 1 @@ -85,11 +81,15 @@ Three shapes are in use. Pick by how far a sibling's selector should reach. :class:`~pcapkit.protocols.link.l2tpv2.L2TPv2` ``('L2TP', 'L2TPv2')`` * - One parser, dynamic alias - fixed, whichever protocol was read - - :class:`~pcapkit.protocols.link.arp.ARP` ``('ARP',)`` parses ARP, InARP, - RARP and DRARP and reports which through + - :class:`~pcapkit.protocols.link.arp.ARP` ``('ARP',)`` reads the operation + code and reports ARP, InARP, RARP or DRARP through :attr:`~pcapkit.protocols.protocol.Protocol.alias`; :class:`~pcapkit.protocols.link.arp.InARP` ``('InARP',)`` is a thin - subclass that exists for explicit construction + subclass that exists for explicit construction. + :class:`~pcapkit.protocols.application.rarp.RARP` ``('RARP', 'DRARP')`` and + :class:`~pcapkit.protocols.application.rarp.DRARP` ``('DRARP',)`` are + separate classes, bound to EtherType ``0x8035`` + (:attr:`~pcapkit.const.reg.ethertype.EtherType.Reverse_Address_Resolution_Protocol`) A family-inclusive member is selected by any sibling's registry key, because both expand to the shared family name (see :ref:`below `). A @@ -102,16 +102,23 @@ Canonical Names **A class name may carry a version that the protocol's own name does not.** Where the defining RFC names the protocol without a version, a versioned class and module -name are fine for telling the members apart in code, but ``id()`` returns the RFC's -canonical name and :attr:`~pcapkit.protocols.protocol.Protocol.name` follows the -RFC's title, with no version string added. :issue:`2446`. +name may tell the members apart in code; ``id()`` is then expected to drop the +version for the RFC's canonical name, and +:attr:`~pcapkit.protocols.protocol.Protocol.name` to follow the RFC's title with no +version string. :pr:`2446`. .. _protocol-family-selectors: Selector Matching ~~~~~~~~~~~~~~~~~ -What a selector reaches is defined by +**A family name selects every version, and each version name selects its own.** +:pr:`2443`. The narrow-to-self shape gives exactly that: ``'IP' in frame`` is +``True`` for an IPv4 frame and an IPv6 frame alike, and ``'IPv6' in frame`` is +``False`` for an IPv4 frame. Under the family-inclusive shape a version name also +selects its siblings, as the second bullet below shows for HTTP. + +What a selector on a frame or a layer reaches is defined by :meth:`~pcapkit.protocols.protocol.Protocol.expand_comp`, :meth:`~pcapkit.protocols.protocol.Protocol.__contains__`, :meth:`~pcapkit.protocols.protocol.Protocol.__getitem__` and ``Protocol.__eq__``. @@ -126,7 +133,13 @@ All comparisons are case-insensitive. ``True`` for an IPv4 frame, and ``'HTTPv2' in frame`` is ``True`` for an HTTP/1.1 frame, since both sides carry ``'HTTP'``. * A string that is **not** a registry key is matched by name alone against each - layer's ``id()``. An alias such as ``'HTTP/1.1'`` matches nothing. + layer's class and ``id()``, so an alias such as ``'HTTP/1.1'`` selects no layer: + ``'HTTP/1.1' in frame`` is ``False``. + +:class:`~pcapkit.corekit.protochain.ProtoChain` matches more widely: its +:meth:`~pcapkit.corekit.protochain.ProtoChain.index`, +:meth:`~pcapkit.corekit.protochain.ProtoChain.count` and ``in`` also compare each +layer's alias, so ``'HTTP/1.1' in frame.protochain`` is ``True``. So adding a name to an ``id()`` tuple widens every ``in`` and ``[]`` selector that can reach it; check the reach before choosing a shape. @@ -139,7 +152,6 @@ No Invented Suffixes :class:`~pcapkit.protocols.internet.ipsec.IPsec`, :class:`~pcapkit.protocols.application.http.HTTP`, :class:`~pcapkit.protocols.link.vlan.VLAN` and -:class:`~pcapkit.protocols.link.l2tp.L2TP` are the precedent. No ``_Family`` base -exists, and none is to be minted. Where a member already holds the family name, the -member takes its versioned class name instead, as :ref:`above -`. :issue:`2446`. +:class:`~pcapkit.protocols.link.l2tp.L2TP` are the precedent. No ``_Family`` base is +to be minted. A member that shares the family's protocol name may take a versioned +class name, as :ref:`above `. :pr:`2446`. From ebd7730f61cd9987546bdb6207c5ba5e123acf60 Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Fri, 30 Oct 2026 20:08:19 -0400 Subject: [PATCH 3/3] docs(conventions): measure the family grouping page against main - HTTP selector example replaced by VLAN: 'S_Tag' in a C-Tag frame. - ARP row: only RARP is bound to 0x8035; DRARP is its operation-5 alias. - Drop the unsourced inferences and the InARP construction claim. - State both canonical-name positions with their sources (#2444, #2446), as preference and proposal rather than settled rule. - Cite the _Family rule (#2446); define registry key. - Drop the stale IPv6_Ext claim about main, which #2464 changed. --- .../conventions/protocol-family-grouping.rst | 116 +++++++++--------- 1 file changed, 60 insertions(+), 56 deletions(-) diff --git a/docs/source/contributing/conventions/protocol-family-grouping.rst b/docs/source/contributing/conventions/protocol-family-grouping.rst index b883094eb2..0f82014671 100644 --- a/docs/source/contributing/conventions/protocol-family-grouping.rst +++ b/docs/source/contributing/conventions/protocol-family-grouping.rst @@ -4,18 +4,15 @@ Protocol Family Grouping ------------------------ A protocol family is a base class named after the family plus its member classes. -What the base and each member return from -:meth:`~pcapkit.protocols.protocol.Protocol.id` is what the selectors on -:class:`~pcapkit.protocols.protocol.Protocol` match against, so the tuples are -settled here rather than re-derived per family. Set on :pr:`2441`, :pr:`2443`, -:pr:`2446` and :issue:`2462`. +What each class returns from :meth:`~pcapkit.protocols.protocol.Protocol.id` is what +the selectors on :class:`~pcapkit.protocols.protocol.Protocol` match against. The +rulings were set on :pr:`2441`, :pr:`2443`, :pr:`2446` and :issue:`2462`. The ``id()`` Criterion ~~~~~~~~~~~~~~~~~~~~~~ **A family base lists its direct concrete subclasses.** An abstract intermediate is -left out, and so is everything beneath it, since that intermediate carries its own -tuple. :pr:`2441`. +left out, and so is everything beneath it. :pr:`2441`. The member list takes either of these forms: @@ -37,10 +34,9 @@ The member list takes either of these forms: Abstract Family Names ~~~~~~~~~~~~~~~~~~~~~ -**An abstract family name composes its members.** -:class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext` carries the family's name, so -its ``id()`` is to list its direct concrete members, and each member narrows its own -``id()`` to itself. :issue:`2462`. +**An abstract family name composes its members.** The ruling on :issue:`2462` is that +:class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext` lists its direct concrete +members in its ``id()``, and that each member narrows its own ``id()`` to itself. Grouping by Name ~~~~~~~~~~~~~~~~ @@ -48,18 +44,17 @@ Grouping by Name **Members share a base because they share a name, not a wire format.** HTTP/1 and HTTP/2 share :class:`~pcapkit.protocols.application.http.HTTP`, IPv4 and IPv6 share :class:`~pcapkit.protocols.internet.ip.IP`, and AH and ESP share -:class:`~pcapkit.protocols.internet.ipsec.IPsec`. A family base needs no dispatcher -logic: :class:`~pcapkit.protocols.internet.ip.IP` and -:class:`~pcapkit.protocols.internet.ipsec.IPsec` have none, and each member is bound -directly. :pr:`2441`. +:class:`~pcapkit.protocols.internet.ipsec.IPsec`. :pr:`2441`. + +:class:`~pcapkit.protocols.internet.ip.IP` and +:class:`~pcapkit.protocols.internet.ipsec.IPsec` define no ``read()``. The HTTP class +does, and a parsed HTTP/1.1 or HTTP/2 layer is the HTTP class itself. .. _protocol-family-shapes: Member Shapes ~~~~~~~~~~~~~ -Pick a member's shape by how far a sibling's selector should reach. - .. list-table:: :header-rows: 1 :widths: 22 38 40 @@ -73,39 +68,51 @@ Pick a member's shape by how far a sibling's selector should reach. :class:`~pcapkit.protocols.internet.esp.ESP` ``('ESP',)`` * - Family-inclusive - the member's label plus the family name - - :class:`~pcapkit.protocols.link.c_tag.C_Tag` ``('C_Tag', 'VLAN')`` and - :class:`~pcapkit.protocols.link.s_tag.S_Tag`; + - :class:`~pcapkit.protocols.link.c_tag.C_Tag` ``('C_Tag', 'VLAN')``, + :class:`~pcapkit.protocols.link.s_tag.S_Tag` ``('S_Tag', 'VLAN')``; :class:`HTTPv1 ` ``('HTTP', 'HTTPv1')`` and - :class:`HTTPv2 `; + :class:`HTTPv2 ` + ``('HTTP', 'HTTPv2')``; :class:`~pcapkit.protocols.link.l2tpv2.L2TPv2` ``('L2TP', 'L2TPv2')`` * - One parser, dynamic alias - fixed, whichever protocol was read - :class:`~pcapkit.protocols.link.arp.ARP` ``('ARP',)`` reads the operation - code and reports ARP, InARP, RARP or DRARP through - :attr:`~pcapkit.protocols.protocol.Protocol.alias`; - :class:`~pcapkit.protocols.link.arp.InARP` ``('InARP',)`` is a thin - subclass that exists for explicit construction. - :class:`~pcapkit.protocols.application.rarp.RARP` ``('RARP', 'DRARP')`` and - :class:`~pcapkit.protocols.application.rarp.DRARP` ``('DRARP',)`` are - separate classes, bound to EtherType ``0x8035`` - (:attr:`~pcapkit.const.reg.ethertype.EtherType.Reverse_Address_Resolution_Protocol`) - -A family-inclusive member is selected by any sibling's registry key, because both -expand to the shared family name (see :ref:`below `). A -narrow member is selected only by its own name and its family's key. + code and reports ARP, RARP, DRARP or InARP through + :attr:`~pcapkit.protocols.protocol.Protocol.alias` + +Nothing dispatches to :class:`~pcapkit.protocols.link.arp.InARP` ``('InARP',)``: +under EtherType ``0x0806``, operation 8 parses as ARP with alias ``InARP``. +Only :class:`~pcapkit.protocols.application.rarp.RARP` ``('RARP', 'DRARP')`` is bound +to EtherType ``0x8035`` +(:attr:`~pcapkit.const.reg.ethertype.EtherType.Reverse_Address_Resolution_Protocol`). +A parsed RARP takes ``DRARP`` as its alias for operation 5, and +:class:`~pcapkit.protocols.application.rarp.DRARP` ``('DRARP',)`` inherits the +``__index__`` of RARP. + +A family-inclusive member is selected by any sibling's registry key (a key of +``pcapkit.protocols.__proto__``), because each expands to the shared family name +(:ref:`below `). A narrow member is selected by its own name +and by the key of each family whose ``id()`` lists it: ``AH`` and ``ESP`` by ``IPsec`` +and by ``IPv6_Ext``. .. _protocol-family-canonical-names: Canonical Names ~~~~~~~~~~~~~~~ -**A class name may carry a version that the protocol's own name does not.** Where -the defining RFC names the protocol without a version, a versioned class and module -name may tell the members apart in code; ``id()`` is then expected to drop the -version for the RFC's canonical name, and -:attr:`~pcapkit.protocols.protocol.Protocol.name` to follow the RFC's title with no -version string. :pr:`2446`. +**A versioned protocol's display name includes its version.** This is the stated +preference on :pr:`2444`, not a settled rule. +:class:`~pcapkit.protocols.link.l2tpv2.L2TPv2`'s ``name`` is +``Layer 2 Tunnelling Protocol version 2``. The HTTP/1.1 and HTTP/2 classes define no +``name`` of their own, so they report ``Hypertext Transfer Protocol``, which carries +no version. + +**ICMPv4 is the case where the RFC's own name carries no version.** The owner's +reply on :pr:`2446` proposes that ``ICMPv4`` keeps its versioned class and module name, +that its ``id()`` should probably drop the version for the RFC canonical name, and +that its ``name`` carries no "version 4", following RFC 792. It is a proposal, not a +ruling. .. _protocol-family-selectors: @@ -113,25 +120,25 @@ Selector Matching ~~~~~~~~~~~~~~~~~ **A family name selects every version, and each version name selects its own.** -:pr:`2443`. The narrow-to-self shape gives exactly that: ``'IP' in frame`` is -``True`` for an IPv4 frame and an IPv6 frame alike, and ``'IPv6' in frame`` is -``False`` for an IPv4 frame. Under the family-inclusive shape a version name also -selects its siblings, as the second bullet below shows for HTTP. +:pr:`2443`. On an IPv4 frame, ``'IP' in`` is ``True`` and ``'IPv6' in`` is ``False``. +On an IPv6 frame, ``'IP' in`` is ``True`` and ``'IPv4' in`` is ``False``. What a selector on a frame or a layer reaches is defined by :meth:`~pcapkit.protocols.protocol.Protocol.expand_comp`, :meth:`~pcapkit.protocols.protocol.Protocol.__contains__`, :meth:`~pcapkit.protocols.protocol.Protocol.__getitem__` and ``Protocol.__eq__``. -All comparisons are case-insensitive. +Selectors are case-insensitive: ``'ipv4' in`` an IPv4 frame is ``True``. -* **String** ``==`` compares against the class and its own ``id()`` only; the - selector is not expanded. On a parsed IPv4 layer, ``ipv4 == 'IPv4'`` is ``True`` - and ``ipv4 == 'IP'`` is ``False``. +* **String** ``==`` compares against the class and its own ``id()`` only; the selector + is not expanded. On a parsed IPv4 layer, ``ipv4 == 'IPv4'`` is ``True`` and + ``ipv4 == 'IP'`` is ``False``. On a parsed C-Tag layer, ``== 'VLAN'`` is ``True``, + since ``VLAN`` is in its ``id()``, and ``== 'S_Tag'`` is ``False``, though + ``'S_Tag' in`` the layer is ``True``. * ``in`` **and** ``[]`` expand the selector first. A registry key becomes its class - plus that class's ``id()``, as does a class object, and a layer matches when any - of those appears among its own class and ``id()``. So ``'IP' in frame`` is - ``True`` for an IPv4 frame, and ``'HTTPv2' in frame`` is ``True`` for an HTTP/1.1 - frame, since both sides carry ``'HTTP'``. + plus that class's ``id()``, as does a class object, and a layer matches when any of + those appears among its own class and ``id()``. So ``'S_Tag' in`` a C-Tag frame is + ``True``: ``S_Tag`` expands to ``S_Tag`` and ``VLAN``, and the C-Tag layer's + ``id()`` carries ``VLAN``. * A string that is **not** a registry key is matched by name alone against each layer's class and ``id()``, so an alias such as ``'HTTP/1.1'`` selects no layer: ``'HTTP/1.1' in frame`` is ``False``. @@ -141,9 +148,6 @@ All comparisons are case-insensitive. :meth:`~pcapkit.corekit.protochain.ProtoChain.count` and ``in`` also compare each layer's alias, so ``'HTTP/1.1' in frame.protochain`` is ``True``. -So adding a name to an ``id()`` tuple widens every ``in`` and ``[]`` selector that -can reach it; check the reach before choosing a shape. - No Invented Suffixes ~~~~~~~~~~~~~~~~~~~~ @@ -152,6 +156,6 @@ No Invented Suffixes :class:`~pcapkit.protocols.internet.ipsec.IPsec`, :class:`~pcapkit.protocols.application.http.HTTP`, :class:`~pcapkit.protocols.link.vlan.VLAN` and -:class:`~pcapkit.protocols.link.l2tp.L2TP` are the precedent. No ``_Family`` base is -to be minted. A member that shares the family's protocol name may take a versioned -class name, as :ref:`above `. :pr:`2446`. +:class:`~pcapkit.protocols.link.l2tp.L2TP` are the precedent. No ``_Family`` suffix is +added (:pr:`2446`). A member may take a versioned class name, as +:ref:`above ` describes.