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
73 changes: 66 additions & 7 deletions docs/source/contributing/conventions/protocol-family-grouping.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,72 @@ that member alone, so no registry binds :class:`~pcapkit.protocols.internet.ip.I
the registry only; parsing or building through a base directly is the rule below.

**A versioned family's base dispatches on both paths** (:issue:`2524`). Parsing
octets through it, constructing it from fields and its ``from_data`` give the member
the octets, fields or data model describe, never an instance of the base. In
:class:`~pcapkit.protocols.application.http.HTTP`,
:meth:`~pcapkit.protocols.application.http.HTTP._dispatch_version` picks the member
from the octets, for a payload a transport port binds to HTTP as well, and
:meth:`~pcapkit.protocols.application.http.HTTP._dispatch_fields` from the fields.
The other versioned families are tracked on :issue:`2574`.
octets through it, constructing it from fields and its ``from_data`` give a member,
never an instance of the base, and a subclass of the base is constructed as itself.

**The member is the one the** ``version`` **keyword names, or else the one the octets
or the data model carry, and never one a field picks.** ``version`` names the member
on either path, unchecked against the octets it is given; where their format carries
a version, the member's own parser refuses another's. Without it, octets give the
member the version they carry, where the format has one, and ``from_data`` reads the
data model's class or its ``version``, as the table below gives. No field picks a
member, not even a field only one member takes, and no member is a default: fields
without ``version`` raise :exc:`~pcapkit.utilities.exceptions.ProtocolError`, and a
field the member does not take fails that member's own keyword check. Ruled on
:issue:`2574`. A class built directly takes ``version`` as well, which must then be
its own version: a member's, or, for a subclass outside the members, the one an IGMP
class states in its ``version``, which a subclass of the other bases does not have.
While parsing, the keyword is handed on unread, since an IP layer passes its own
version under that name to the layer it carries.

.. list-table::
:header-rows: 1
:widths: 13 15 42 30

* - Base
- ``version``
- Octets without ``version``
- ``from_data``
* - :class:`~pcapkit.protocols.internet.igmp.IGMP`
- ``1``, ``2``, ``3``
- :meth:`~pcapkit.protocols.internet.igmp.IGMP._dispatch`: type, max response
time and length, placed by
:meth:`~pcapkit.protocols.internet.igmp.IGMP._classify`
- ``version``, which each IGMP data model carries though no octet does,
ruled on :issue:`2574`
* - :class:`~pcapkit.protocols.internet.ip.IP`
- ``4``, ``6``
- the Version nibble
- ``version``
* - :class:`~pcapkit.protocols.internet.icmp.ICMP`
- ``4``, ``6``
- refused, below
- the data model; a :obj:`dict` is refused
* - :class:`~pcapkit.protocols.link.l2tp.L2TP`
- ``2``
- the version nibble
- ``version``
* - :class:`~pcapkit.protocols.link.linux_sll.LinuxSLL`
- ``1``, ``2``
- refused, below
- the data model; a :obj:`dict` is refused

:class:`~pcapkit.protocols.application.http.HTTP` dispatches otherwise. Its octets
go to :meth:`~pcapkit.protocols.application.http.HTTP._dispatch_version`, which reads
the connection preface, a start line or a frame header, for a payload a transport
port binds to HTTP as well. Without ``version``, its
:meth:`~pcapkit.protocols.application.http.HTTP._dispatch_fields` picks by each
member's own fields and builds ``HTTPv1`` from none, and its ``from_data`` reads the
data model, or a :obj:`dict` by its ``receipt`` or ``sid``.

**Octets that carry no version are refused through the base without** ``version``
(:issue:`2574`). An ICMP message and a Linux cooked-capture header have no version
field: the enclosing layer names it, by IP protocol number 1 or 58 and by link type
113 or 276, and those registries bind the members. So ``ICMP(...)`` and
``LinuxSLL(...)`` on octets raise :exc:`~pcapkit.utilities.exceptions.ProtocolError`
rather than guess, as does their ``from_data`` on a :obj:`dict`, whose keys pick no
member. A version a family has no member for, such as L2TP's ``1`` (L2F) and ``3``,
raises the same way.

**Octets neither version takes**, a frameless HTTP/2 connection preface among them,
are :class:`~pcapkit.protocols.misc.raw.Raw` with no ``error`` on the transport path,
Expand Down
12 changes: 11 additions & 1 deletion docs/source/pcapkit/protocols/internet/icmp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,23 @@ ICMP - Internet Control Message Protocol
which is a base class for Internet Control Message
Protocol (ICMP) protocol family [*]_, e.g.
:class:`~pcapkit.protocols.internet.icmpv4.ICMPv4` and
:class:`~pcapkit.protocols.internet.icmpv6.ICMPv6`.
:class:`~pcapkit.protocols.internet.icmpv6.ICMPv6`. Constructing the base gives the
one its ``version`` keyword names, from octets or from fields, and is refused
without it, since an ICMP message carries no version of its own. Its
:meth:`~pcapkit.protocols.internet.icmp.ICMP.from_data` gives the one whose data
model it is handed (:issue:`2574`).

.. autoclass:: pcapkit.protocols.internet.icmp.ICMP
:no-members:
:show-inheritance:

.. automethod:: id
.. automethod:: from_data

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

.. automethod:: _member_of_version

.. rubric:: Footnotes

Expand Down
15 changes: 13 additions & 2 deletions docs/source/pcapkit/protocols/internet/igmp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ data model as-is; IGMPv3's wire format differs materially by message, so
instead -- see :doc:`igmpv3`.
:attr:`~pcapkit.protocols.internet.igmp.IGMP.alias` names the protocol each
message is read as: ``'IGMP'`` for IGMPv1, the name :rfc:`1112` gives it, and
``'IGMPv2'`` or ``'IGMPv3'`` for the others.
``'IGMPv2'`` or ``'IGMPv3'`` for the others. Constructing the base gives the
version class its ``version`` keyword names; without it, octets give the class
the message classifies as, and fields are refused. Its
:meth:`~pcapkit.protocols.internet.igmp.IGMP.from_data` gives the class the
``version`` of its data names: every IGMP data model carries one, ``1``, ``2`` or
``3``, set by the class that read or built the message and on no octet, so an
IGMPv1 and an IGMPv2 message read alike apart from it (:issue:`2574`).

The structure is keyed as for IGMPv1; an IGMPv2 message's keys start ``igmpv2``
instead:
Expand All @@ -42,11 +48,16 @@ Octets Bits Name Description

.. automethod:: read
.. automethod:: make
.. automethod:: from_data

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

.. automethod:: _classify
.. automethod:: _make_data
.. automethod:: _classify
.. automethod:: _dispatch
.. automethod:: _member_of_version

Header Schemas
--------------
Expand Down
12 changes: 11 additions & 1 deletion docs/source/pcapkit/protocols/internet/ip.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,23 @@ IP - Internet Protocol
which is a base class for Internet Protocol (IP)
protocol family [*]_, e.g.
:class:`~pcapkit.protocols.internet.ipv4.IPv4` and
:class:`~pcapkit.protocols.internet.ipv6.IPv6`.
:class:`~pcapkit.protocols.internet.ipv6.IPv6`. Constructing the base gives the one
its ``version`` keyword names; without it, octets give the one their Version field
names, and fields are refused. Its
:meth:`~pcapkit.protocols.internet.ip.IP.from_data` reads the ``version`` of its
data (:issue:`2574`).

.. autoclass:: pcapkit.protocols.internet.ip.IP
:no-members:
:show-inheritance:

.. automethod:: id
.. automethod:: from_data

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

.. automethod:: _member_of_version

.. rubric:: Footnotes

Expand Down
1 change: 1 addition & 0 deletions docs/source/pcapkit/protocols/internet/ipv4.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Octets Bits Name Description

.. automethod:: read
.. automethod:: make
.. automethod:: unpack

.. automethod:: _make_data

Expand Down
1 change: 1 addition & 0 deletions docs/source/pcapkit/protocols/internet/ipv6.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Octets Bits Name Description

.. automethod:: read
.. automethod:: make
.. automethod:: unpack

.. automethod:: _make_data

Expand Down
28 changes: 19 additions & 9 deletions docs/source/pcapkit/protocols/link/l2tp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,12 @@ version's layout for every version.
.. autoproperty:: info_name

.. automethod:: id
.. automethod:: from_data

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

.. automethod:: _member_of_version

Versions Not Implemented
------------------------
Expand All @@ -60,15 +64,21 @@ since callers take element zero as canonical.
Version Dispatch
----------------

Nothing dispatches on the version nibble, because L2TPv2 is the only version
implemented. Another would follow the precedent of
:class:`~pcapkit.protocols.application.http.HTTP`, which reads a version and
delegates to a per-version class. L2TP is the easier case:
:meth:`HTTP._guess_version <pcapkit.protocols.application.http.HTTP._guess_version>`
has to *infer* the version -- recognising the HTTP/2 preface or an HTTP/1 start
line, else trial-parsing each candidate -- because HTTP's wire format carries no
version field, whereas L2TP states its version explicitly in those four bits. A
deterministic switch on ``Ver`` is enough, and no new registry is needed.
Constructing the base gives the member its ``version`` field names, from octets or
from fields; without it, octets give the one their version nibble names, and fields
are refused, since no field picks a member (:issue:`2574`).
:meth:`~pcapkit.protocols.link.l2tp.L2TP._member_of_version` maps ``2`` to
:class:`~pcapkit.protocols.link.l2tpv2.L2TPv2` and refuses any other version, for
that field, for the nibble and for the data
:meth:`~pcapkit.protocols.link.l2tp.L2TP.from_data` is handed. UDP port 1701 binds
L2TPv2 itself, the only member. The switch is where a further member goes, and
binding the port to the base through a hook making the same switch, as
:meth:`IGMP._dispatch <pcapkit.protocols.internet.igmp.IGMP._dispatch>` does for
IP protocol 2, needs no new registry. L2TP is the easier case beside
:class:`~pcapkit.protocols.application.http.HTTP`, whose
:meth:`~pcapkit.protocols.application.http.HTTP._guess_version` has to *infer*
the version, because HTTP's wire format carries no version field, whereas L2TP
states its version explicitly in those four bits.

.. rubric:: Footnotes

Expand Down
11 changes: 10 additions & 1 deletion docs/source/pcapkit/protocols/link/linux_sll.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,25 @@ Linux "cooked" capture encapsulation family:

The versions differ in field order and width, so each parses its own header.
The base holds what they share: the next-layer dispatch, and link-layer address
validation. It has no registry index, so its ``__index__`` raises.
validation. It has no registry index, so its ``__index__`` raises. Constructing
the base gives the version its ``version`` keyword names, from octets or from
fields, and is refused without it, since the link type rather than the header
names the version. Its
:meth:`~pcapkit.protocols.link.linux_sll.LinuxSLL.from_data` gives the version
whose data model it is handed (:issue:`2574`).

.. autoclass:: pcapkit.protocols.link.linux_sll.LinuxSLL
:no-members:
:show-inheritance:

.. automethod:: id
.. automethod:: register
.. automethod:: from_data
.. automethod:: __index__
.. automethod:: __init__
.. automethod:: __new__
.. automethod:: _import_next_layer
.. automethod:: _member_of_version

.. autoattribute:: __proto__
:no-value:
Expand Down
13 changes: 11 additions & 2 deletions pcapkit/protocols/data/internet/igmp.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
if TYPE_CHECKING:
from ipaddress import IPv4Address

from typing_extensions import Literal

from pcapkit.const.igmp.type import Type as Enum_Type

__all__ = ['IGMP']
Expand All @@ -27,6 +29,13 @@ class IGMP(Protocol):

"""

#: Version of IGMP the message was read as, ``1`` or ``2``: the
#: :attr:`~pcapkit.protocols.internet.igmp.IGMP.version` of the class that
#: read or built it. It is on no octet, since the versions share this
#: layout, and :meth:`IGMP.from_data
#: <pcapkit.protocols.internet.igmp.IGMP.from_data>` dispatches on it
#: (:issue:`2574`).
version: 'Literal[1, 2]'
#: Message type.
type: 'Enum_Type'
#: Max response time, in units of 1/10 second. Meaningful only for a
Expand All @@ -41,5 +50,5 @@ class IGMP(Protocol):
group: 'IPv4Address'

if TYPE_CHECKING:
def __init__(self, type: 'Enum_Type', max_resp_time: 'int', checksum: 'bytes', # pylint: disable=unused-argument,super-init-not-called,multiple-statements,redefined-builtin
group: 'IPv4Address') -> 'None': ...
def __init__(self, version: 'Literal[1, 2]', type: 'Enum_Type', max_resp_time: 'int', # pylint: disable=unused-argument,super-init-not-called,multiple-statements,redefined-builtin
checksum: 'bytes', group: 'IPv4Address') -> 'None': ...
12 changes: 10 additions & 2 deletions pcapkit/protocols/data/internet/igmpv3.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
if TYPE_CHECKING:
from ipaddress import IPv4Address

from typing_extensions import Literal

from pcapkit.const.igmp.record_type import RecordType as Enum_RecordType
from pcapkit.const.igmp.type import Type as Enum_Type

Expand All @@ -27,6 +29,11 @@ class IGMPv3(Protocol):

"""

#: Version of IGMP, always ``3``. It is on no octet: it names the class
#: that read or built the message, as on the IGMPv1/v2 data model, and
#: :meth:`IGMP.from_data <pcapkit.protocols.internet.igmp.IGMP.from_data>`
#: dispatches on it (:issue:`2574`).
version: 'Literal[3]'
#: Message type.
type: 'Enum_Type'
#: Header length, in octets. IGMP has no header field of its own that
Expand Down Expand Up @@ -79,7 +86,7 @@ class Query(IGMPv3):
sources: 'tuple[IPv4Address, ...]'

if TYPE_CHECKING:
def __init__(self, type: 'Enum_Type', length: 'int', max_resp_code: 'int',
def __init__(self, version: 'Literal[3]', type: 'Enum_Type', length: 'int', max_resp_code: 'int',
max_resp_time: 'int', checksum: 'bytes', group: 'IPv4Address',
resv: 'int', s_flag: 'bool', qrv: 'int', qqic: 'int', qqi: 'int',
sources: 'tuple[IPv4Address, ...]') -> 'None': ... # pylint: disable=unused-argument,super-init-not-called,multiple-statements,redefined-builtin,line-too-long
Expand Down Expand Up @@ -128,6 +135,7 @@ class Report(IGMPv3):
records: 'tuple[GroupRecord, ...]'

if TYPE_CHECKING:
def __init__(self, type: 'Enum_Type', length: 'int', reserved_1: 'int', checksum: 'bytes',
def __init__(self, version: 'Literal[3]', type: 'Enum_Type', length: 'int', reserved_1: 'int',
checksum: 'bytes',
reserved_2: 'bytes', num_group_records: 'int',
records: 'tuple[GroupRecord, ...]') -> 'None': ... # pylint: disable=unused-argument,super-init-not-called,multiple-statements,redefined-builtin
Loading
Loading