From 6aa3f9b99fff6cee0da136428c9aaa59c1d85bb0 Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Fri, 16 Oct 2026 16:09:23 -0400 Subject: [PATCH] docs(protocols): third whole-repo pass over internet (#1103) Directives - versionchanged on the AH, IPv4, IPv6, IPv6-Frag, IPv6-Route, IPX, MH and HIP make() signatures that insert a defaulted parameter ahead of existing positional ones. - OrderedMultiDict to OrderedMultiInfo on the HOPOPT, IPv6-Opts, HIP, IPv4, MH message and CGA data models and on TSOption.timestamp; the MPL seed_id, PUZZLE lifetime and BRA interval types; the schema trailer fields and the reserved fields now read as integers. Counts - Drop counted subclasses, call sites, registry entries, subtypes and list items from the internet docstrings and pages. Rendering - Fix the IPv6-Ext table continuation and class heading, the positional-arguments sections, the selector Returns lead-ins, the attribute comments autodoc split as types, stray rtype lines and an unresolved private-helper reference. Accuracy - Correct the IPv6-Ext make() note, the HIP length checks, the IPX socket field, the options-length notes and the table name cells. Refs #1103 --- .../source/pcapkit/protocols/internet/esp.rst | 4 +- .../pcapkit/protocols/internet/internet.rst | 6 +- docs/source/pcapkit/protocols/internet/ip.rst | 2 +- .../pcapkit/protocols/internet/ipsec.rst | 2 +- .../pcapkit/protocols/internet/ipv4.rst | 20 +-- .../pcapkit/protocols/internet/ipv6.rst | 6 +- .../pcapkit/protocols/internet/ipv6_ext.rst | 8 +- .../source/pcapkit/protocols/internet/ipx.rst | 2 +- pcapkit/protocols/data/internet/esp.py | 4 +- pcapkit/protocols/data/internet/hip.py | 7 +- pcapkit/protocols/data/internet/hopopt.py | 19 ++- pcapkit/protocols/data/internet/ipv4.py | 17 ++- pcapkit/protocols/data/internet/ipv6_opts.py | 19 ++- pcapkit/protocols/data/internet/ipx.py | 4 +- pcapkit/protocols/data/internet/mh.py | 77 +++++++++--- pcapkit/protocols/internet/ah.py | 7 +- pcapkit/protocols/internet/esp.py | 5 +- pcapkit/protocols/internet/hip.py | 41 ++++--- pcapkit/protocols/internet/hopopt.py | 23 ++-- pcapkit/protocols/internet/internet.py | 13 +- pcapkit/protocols/internet/ip.py | 2 +- pcapkit/protocols/internet/ipsec.py | 2 +- pcapkit/protocols/internet/ipv4.py | 10 +- pcapkit/protocols/internet/ipv6.py | 9 +- pcapkit/protocols/internet/ipv6_ext.py | 67 +++++----- pcapkit/protocols/internet/ipv6_frag.py | 8 +- pcapkit/protocols/internet/ipv6_opts.py | 30 +++-- pcapkit/protocols/internet/ipv6_route.py | 11 +- pcapkit/protocols/internet/ipx.py | 9 +- pcapkit/protocols/internet/mh.py | 114 +++++++++--------- pcapkit/protocols/schema/internet/ah.py | 9 +- pcapkit/protocols/schema/internet/hip.py | 27 +++-- pcapkit/protocols/schema/internet/ipv4.py | 27 +++-- pcapkit/protocols/schema/internet/ipv6.py | 15 ++- .../protocols/schema/internet/ipv6_option.py | 21 ++-- pcapkit/protocols/schema/internet/ipx.py | 11 +- pcapkit/protocols/schema/internet/mh.py | 41 +++++-- 37 files changed, 447 insertions(+), 252 deletions(-) diff --git a/docs/source/pcapkit/protocols/internet/esp.rst b/docs/source/pcapkit/protocols/internet/esp.rst index 733c69b0d6..a7bf049b91 100644 --- a/docs/source/pcapkit/protocols/internet/esp.rst +++ b/docs/source/pcapkit/protocols/internet/esp.rst @@ -15,7 +15,7 @@ Octets Bits Name Description 0 0 ``esp.spi`` Security Parameters Index (SPI) 4 32 ``esp.seq`` Sequence Number 8 64 ``esp.payload_data`` Payload Data (variable, encrypted) - ? ? Padding (0-255 bytes, encrypted) + ? ? ``esp.padding`` Padding (0-255 bytes, encrypted) ? ? ``esp.pad_len`` Pad Length (encrypted) ? ? ``esp.next`` Next Header (encrypted) ? ? ``esp.icv`` Integrity Check Value (ICV, variable) @@ -217,8 +217,10 @@ MD5, DES-MAC, KPDK-MD5 MUST NOT **no** registered, delibe :show-inheritance: .. autodata:: pcapkit.protocols.internet.esp.CIPHER_SUITES + :no-value: .. autodata:: pcapkit.protocols.internet.esp.INTEGRITY_SUITES + :no-value: Known Limitations ----------------- diff --git a/docs/source/pcapkit/protocols/internet/internet.rst b/docs/source/pcapkit/protocols/internet/internet.rst index 8c3b9d59c9..8e48c54642 100644 --- a/docs/source/pcapkit/protocols/internet/internet.rst +++ b/docs/source/pcapkit/protocols/internet/internet.rst @@ -4,9 +4,9 @@ Base Protocol .. module:: pcapkit.protocols.internet.internet :mod:`pcapkit.protocols.internet.internet` contains :class:`~pcapkit.protocols.internet.internet.Internet`, -which is a base class for internet layer protocols, eg. :class:`~pcapkit.protocols.internet.ah.AH`, +which is a base class for internet layer protocols, e.g. :class:`~pcapkit.protocols.internet.ah.AH`, :class:`~pcapkit.protocols.internet.ipsec.IPsec`, :class:`~pcapkit.protocols.internet.ipv4.IPv4`, -:class:`~pcapkit.protocols.internet.ipv6.IPv6`, :class:`~pcapkit.protocols.internet.ipx.IPX`, and etc. +:class:`~pcapkit.protocols.internet.ipv6.IPv6` and :class:`~pcapkit.protocols.internet.ipx.IPX`. .. autoclass:: pcapkit.protocols.internet.internet.Internet :no-members: @@ -40,7 +40,7 @@ kept as :class:`~pcapkit.protocols.misc.raw.Raw`, with one :exc:`~pcapkit.utilities.warnings.ProtocolWarning` (:issue:`1604`). The ``pyshark`` engine leaves the walk to its own parser, which does not stop there, so it may still find the upper layer past it (:issue:`1609`). So may the -``dpkt`` toolkit, which reads the chain as :mod:`dpkt` decodes it. So does the +``dpkt`` toolkit, which reads the chain as ``dpkt`` decodes it. So does the ``scapy`` engine behind IPv6, where Scapy dissects each extension header itself; behind IPv4, its toolkit steps over them in the octets to find TCP, up to :data:`~pcapkit.utilities.config.EXTENSION_HEADER_LIMIT` of them in a row. The diff --git a/docs/source/pcapkit/protocols/internet/ip.rst b/docs/source/pcapkit/protocols/internet/ip.rst index 4f30ec63f8..09ef95acf9 100644 --- a/docs/source/pcapkit/protocols/internet/ip.rst +++ b/docs/source/pcapkit/protocols/internet/ip.rst @@ -6,7 +6,7 @@ IP - Internet Protocol :mod:`pcapkit.protocols.internet.ip` contains :class:`~pcapkit.protocols.internet.ip.IP` only, which is a base class for Internet Protocol (IP) -protocol family [*]_, eg. +protocol family [*]_, e.g. :class:`~pcapkit.protocols.internet.ipv4.IPv4` and :class:`~pcapkit.protocols.internet.ipv6.IPv6`. diff --git a/docs/source/pcapkit/protocols/internet/ipsec.rst b/docs/source/pcapkit/protocols/internet/ipsec.rst index e18cf64213..681f499be3 100644 --- a/docs/source/pcapkit/protocols/internet/ipsec.rst +++ b/docs/source/pcapkit/protocols/internet/ipsec.rst @@ -6,7 +6,7 @@ IPsec - Internet Protocol Security :mod:`pcapkit.protocols.internet.ipsec` contains :class:`~pcapkit.protocols.internet.ipsec.IPsec` only, which is a base class for Internet Protocol -Security (IPsec) protocol family [*]_, eg. +Security (IPsec) protocol family [*]_, e.g. :class:`~pcapkit.protocols.internet.ah.AH` and :class:`~pcapkit.protocols.internet.esp.ESP`. diff --git a/docs/source/pcapkit/protocols/internet/ipv4.rst b/docs/source/pcapkit/protocols/internet/ipv4.rst index a6287c9ca9..82697ba52f 100644 --- a/docs/source/pcapkit/protocols/internet/ipv4.rst +++ b/docs/source/pcapkit/protocols/internet/ipv4.rst @@ -9,9 +9,9 @@ which implements extractor for Internet Protocol version 4 (IPv4) [*]_, whose structure is described as below: -======= ========= ====================== ============================================= +======= ========= ======================= ============================================ Octets Bits Name Description -======= ========= ====================== ============================================= +======= ========= ======================= ============================================ 0 0 ``ipv4.version`` Version (``4``) 0 4 ``ipv4.hdr_len`` Internet Header Length (IHL) 1 8 ``ipv4.tos.pre`` Precedence @@ -21,7 +21,7 @@ Octets Bits Name Description 1 14 ``ipv4.tos.ecn`` Explicit Congestion Notification (ECN) 2 16 ``ipv4.len`` Total Length 4 32 ``ipv4.id`` Identification - 6 48 Reserved Bit (must be ``\x00``) + 6 48 ``ipv4.flags.reserved`` Reserved Bit (must be ``0``) 6 49 ``ipv4.flags.df`` Don't Fragment (DF) 6 50 ``ipv4.flags.mf`` More Fragments (MF) 6 51 ``ipv4.offset`` Fragment Offset @@ -31,7 +31,7 @@ Octets Bits Name Description 12 96 ``ipv4.src`` Source IP Address 16 128 ``ipv4.dst`` Destination IP Address 20 160 ``ipv4.options`` IP Options (if IHL > ``5``) -======= ========= ====================== ============================================= +======= ========= ======================= ============================================ .. autoclass:: pcapkit.protocols.internet.ipv4.IPv4 :no-members: @@ -235,8 +235,8 @@ Data Models .. note:: - This field is conflict with :keyword:`del` keyword. To access this field, - directly use :func:`getattr` instead. + This field's name conflicts with the :keyword:`del` keyword, so + access it with :func:`getattr` instead. .. autoclass:: pcapkit.protocols.data.internet.ipv4.Flags :members: @@ -257,8 +257,8 @@ Data Models .. note:: - This field is conflict with :keyword:`class` keyword. To access this field, - directly use :func:`getattr` instead. + This field's name conflicts with the :keyword:`class` keyword, so + access it with :func:`getattr` instead. .. autoclass:: pcapkit.protocols.data.internet.ipv4.UnassignedOption :members: @@ -319,8 +319,8 @@ Data Models .. note:: - This field is conflict with :keyword:`return` keyword. To access this field, - directly use :func:`getattr` instead. + This field's name conflicts with the :keyword:`return` keyword, so + access it with :func:`getattr` instead. .. autoclass:: pcapkit.protocols.data.internet.ipv4.RTRALTOption :members: diff --git a/docs/source/pcapkit/protocols/internet/ipv6.rst b/docs/source/pcapkit/protocols/internet/ipv6.rst index 2468e839dc..878dcaebf2 100644 --- a/docs/source/pcapkit/protocols/internet/ipv6.rst +++ b/docs/source/pcapkit/protocols/internet/ipv6.rst @@ -15,7 +15,7 @@ Octets Bits Name Description 0 0 ``ipv6.version`` Version (``6``) 0 4 ``ipv6.class`` Traffic Class 1 12 ``ipv6.label`` Flow Label - 4 32 ``ipv6.payload`` Payload Length (header excludes) + 4 32 ``ipv6.payload`` Payload Length (header excluded) 6 48 ``ipv6.next`` Next Header 7 56 ``ipv6.limit`` Hop Limit 8 64 ``ipv6.src`` Source Address @@ -110,8 +110,8 @@ Data Models .. note:: - This field is conflict with :keyword:`class` keyword. To access this field, - directly use :func:`getattr` instead. + This field's name conflicts with the :keyword:`class` keyword, so + access it with :func:`getattr` instead. .. rubric:: Footnotes diff --git a/docs/source/pcapkit/protocols/internet/ipv6_ext.rst b/docs/source/pcapkit/protocols/internet/ipv6_ext.rst index d722b6f95b..14aadf1c63 100644 --- a/docs/source/pcapkit/protocols/internet/ipv6_ext.rst +++ b/docs/source/pcapkit/protocols/internet/ipv6_ext.rst @@ -5,8 +5,8 @@ IPv6_Ext - IPv6 Extension Header :mod:`pcapkit.protocols.internet.ipv6_ext` contains :class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext` -only, which serves two roles at once. It is the shared **base class** of all -eight IPv6 extension headers this package implements, supplying the +only, which plays a double role. It is the shared **base class** of every +IPv6 extension header this package implements, supplying the ``extension``-mode contract: the guards that make :attr:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext.payload`, :attr:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext.protocol` and @@ -22,7 +22,7 @@ Octets Bits Name Description ======= ========= ===================== ===================================== 0 0 ``next`` Next Header 1 8 ``len`` Hdr Ext Len (8-octet units, - excluding the first 8 octets) + excluding the first 8 octets) 2 16 ``payload`` Header-specific content ======= ========= ===================== ===================================== @@ -31,7 +31,7 @@ header. The module docstring in :file:`pcapkit/protocols/internet/ipv6_ext.py` holds the closed exception table (``IPv6-Frag`` and ``AH`` each use their own length rule; ``ESP`` has a dedicated parser whose own info reports no next header rather than lacking one, and ``253`` and ``254`` have no dedicated parser at all, -so none of the three ever reaches this class), the two ways this class is +so none of them ever reaches this class), the ways this class is dispatched to, and why an overrun stops the walk instead of clipping it. .. autoclass:: pcapkit.protocols.internet.ipv6_ext.IPv6_Ext diff --git a/docs/source/pcapkit/protocols/internet/ipx.rst b/docs/source/pcapkit/protocols/internet/ipx.rst index 76472609f2..1c80b79282 100644 --- a/docs/source/pcapkit/protocols/internet/ipx.rst +++ b/docs/source/pcapkit/protocols/internet/ipx.rst @@ -13,7 +13,7 @@ as below: Octets Bits Name Description ======= ========= ====================== ===================================== 0 0 ``ipx.chksum`` Checksum - 2 16 ``ipx.len`` Packet Length (header includes) + 2 16 ``ipx.len`` Packet Length (header included) 4 32 ``ipx.count`` Transport Control (hop count) 5 40 ``ipx.type`` Packet Type 6 48 ``ipx.dst`` Destination Address diff --git a/pcapkit/protocols/data/internet/esp.py b/pcapkit/protocols/data/internet/esp.py index 0d13fb0102..aad8981329 100644 --- a/pcapkit/protocols/data/internet/esp.py +++ b/pcapkit/protocols/data/internet/esp.py @@ -23,8 +23,8 @@ class ESP(Protocol): :attr:`plaintext` are recovered from the *decrypted* payload, and are therefore :data:`None` whenever the payload could not be decrypted -- :rfc:`4303` places them inside the ciphertext, so guessing them from an - encrypted payload is not possible. :attr:`status` says which of those - two cases applies, and :attr:`error` says why. + encrypted payload is not possible. :attr:`status` says which case + applies, and :attr:`error` says why. Important: No key material is recorded here, by design. This data model is what diff --git a/pcapkit/protocols/data/internet/hip.py b/pcapkit/protocols/data/internet/hip.py index 80dccb1ca1..819d6df77f 100644 --- a/pcapkit/protocols/data/internet/hip.py +++ b/pcapkit/protocols/data/internet/hip.py @@ -103,7 +103,9 @@ class HIP(Protocol): .. versionchanged:: 1.5.0 New required fields ``packet_fixed``, ``reserved`` and ``version_fixed`` - (:issue:`1417`, :issue:`1459`). + (:issue:`1417`, :issue:`1459`), and ``parameters`` is an immutable + :class:`~pcapkit.corekit.infoclass.OrderedMultiInfo` rather than an + :class:`~pcapkit.corekit.multidict.OrderedMultiDict` (:issue:`1484`). """ @@ -256,7 +258,8 @@ class PuzzleParameter(Parameter): .. versionchanged:: 1.5.0 New required fields ``lifetime_exponent`` and ``rhash_len`` - (:issue:`653`, :issue:`1323`). + (:issue:`653`, :issue:`1323`), and ``lifetime`` is :obj:`None` past + :attr:`datetime.timedelta.max` (:issue:`1323`). """ diff --git a/pcapkit/protocols/data/internet/hopopt.py b/pcapkit/protocols/data/internet/hopopt.py index 0066952d08..8c51ee2b72 100644 --- a/pcapkit/protocols/data/internet/hopopt.py +++ b/pcapkit/protocols/data/internet/hopopt.py @@ -53,7 +53,15 @@ class Option(Data): @info_final class HOPOPT(Protocol): - """Data model for HOPOPT protocol.""" + """Data model for HOPOPT protocol. + + .. versionchanged:: 1.5.0 + + ``options`` is an immutable + :class:`~pcapkit.corekit.infoclass.OrderedMultiInfo` rather than an + :class:`~pcapkit.corekit.multidict.OrderedMultiDict` (:issue:`1484`). + + """ #: Next header. next: 'TransType' @@ -323,7 +331,14 @@ def __init__(self, max: 'bool', drop: 'bool', reserved: 'int') -> 'None': ... # @info_final class MPLOption(Option): - """Data model for HOPOPT Multicast Protocol for Low-Power and Lossy Networks (MPL) option.""" + """Data model for HOPOPT Multicast Protocol for Low-Power and Lossy Networks (MPL) option. + + .. versionchanged:: 1.5.0 + + ``seed_id`` may be an :class:`~ipaddress.IPv6Address`, the source + address, for a seed-id type of ``IPV6_SOURCE_ADDRESS`` (:issue:`1185`). + + """ #: Seed length. seed_type: 'SeedID' diff --git a/pcapkit/protocols/data/internet/ipv4.py b/pcapkit/protocols/data/internet/ipv4.py index 4cdfc478c9..81ccf4abbb 100644 --- a/pcapkit/protocols/data/internet/ipv4.py +++ b/pcapkit/protocols/data/internet/ipv4.py @@ -100,13 +100,21 @@ def __init__(self, reserved: 'int', df: 'bool', mf: 'bool') -> 'None': ... # py @info_final class IPv4(Protocol): - """Data model for IPv4 packet.""" + """Data model for IPv4 packet. + + .. versionchanged:: 1.5.0 + + ``options`` is an immutable + :class:`~pcapkit.corekit.infoclass.OrderedMultiInfo` rather than an + :class:`~pcapkit.corekit.multidict.OrderedMultiDict` (:issue:`1484`). + + """ #: Version. version: 'Literal[4]' #: Internet header length. hdr_len: 'int' - #: Type of services. + #: Type of service. tos: 'ToSField' #: Total length. len: 'int' @@ -247,7 +255,10 @@ class TSOption(Option): .. versionchanged:: 1.5.0 New required fields ``remaining`` and ``remainder`` (:issue:`1349`, - :issue:`1392`). + :issue:`1392`), and a ``timestamp`` that carries addresses is an + immutable :class:`~pcapkit.corekit.infoclass.OrderedMultiInfo` rather + than an :class:`~pcapkit.corekit.multidict.OrderedMultiDict` + (:issue:`1484`). """ diff --git a/pcapkit/protocols/data/internet/ipv6_opts.py b/pcapkit/protocols/data/internet/ipv6_opts.py index 1e42ee5024..0fc63bac5e 100644 --- a/pcapkit/protocols/data/internet/ipv6_opts.py +++ b/pcapkit/protocols/data/internet/ipv6_opts.py @@ -53,7 +53,15 @@ class Option(Data): @info_final class IPv6_Opts(Protocol): - """Data model for IPv6-Opts protocol.""" + """Data model for IPv6-Opts protocol. + + .. versionchanged:: 1.5.0 + + ``options`` is an immutable + :class:`~pcapkit.corekit.infoclass.OrderedMultiInfo` rather than an + :class:`~pcapkit.corekit.multidict.OrderedMultiDict` (:issue:`1484`). + + """ #: Next header. next: 'TransType' @@ -335,7 +343,14 @@ def __init__(self, max: 'bool', drop: 'bool', reserved: 'int') -> 'None': ... # @info_final class MPLOption(Option): - """Data model for IPv6-Opts Multicast Protocol for Low-Power and Lossy Networks (MPL) option.""" + """Data model for IPv6-Opts Multicast Protocol for Low-Power and Lossy Networks (MPL) option. + + .. versionchanged:: 1.5.0 + + ``seed_id`` may be an :class:`~ipaddress.IPv6Address`, the source + address, for a seed-id type of ``IPV6_SOURCE_ADDRESS`` (:issue:`1185`). + + """ #: Seed length. seed_type: 'SeedID' diff --git a/pcapkit/protocols/data/internet/ipx.py b/pcapkit/protocols/data/internet/ipx.py index 4716a0b843..3fcca470f0 100644 --- a/pcapkit/protocols/data/internet/ipx.py +++ b/pcapkit/protocols/data/internet/ipx.py @@ -26,7 +26,7 @@ class Address(Data): network: 'str' #: Node number (``-`` separated). node: 'str' - #: Socket number (``:`` separated). + #: Socket number. socket: 'Socket' #: Full address (``:`` separated). addr: 'str' @@ -41,7 +41,7 @@ class IPX(Protocol): #: Checksum. chksum: 'bytes' - #: Packet length (header includes). + #: Packet length (header included). len: 'int' #: Transport control (hop count). count: 'int' diff --git a/pcapkit/protocols/data/internet/mh.py b/pcapkit/protocols/data/internet/mh.py index 6353963207..83e4255d0e 100644 --- a/pcapkit/protocols/data/internet/mh.py +++ b/pcapkit/protocols/data/internet/mh.py @@ -176,7 +176,10 @@ class BindingRefreshRequestMessage(MH): .. versionchanged:: 1.5.0 - New required field ``msg_reserved`` (:issue:`1329`). + New required field ``msg_reserved`` (:issue:`1329`), and + ``options`` is an immutable + :class:`~pcapkit.corekit.infoclass.OrderedMultiInfo` rather than an + :class:`~pcapkit.corekit.multidict.OrderedMultiDict` (:issue:`1484`). """ @@ -197,7 +200,10 @@ class HomeTestInitMessage(MH): .. versionchanged:: 1.5.0 - New required field ``msg_reserved`` (:issue:`1437`). + New required field ``msg_reserved`` (:issue:`1437`), and + ``options`` is an immutable + :class:`~pcapkit.corekit.infoclass.OrderedMultiInfo` rather than an + :class:`~pcapkit.corekit.multidict.OrderedMultiDict` (:issue:`1484`). """ @@ -221,7 +227,10 @@ class CareofTestInitMessage(MH): .. versionchanged:: 1.5.0 - New required field ``msg_reserved`` (:issue:`1437`). + New required field ``msg_reserved`` (:issue:`1437`), and + ``options`` is an immutable + :class:`~pcapkit.corekit.infoclass.OrderedMultiInfo` rather than an + :class:`~pcapkit.corekit.multidict.OrderedMultiDict` (:issue:`1484`). """ @@ -241,7 +250,15 @@ def __init__(self, next: 'TransType', length: 'int', type: 'Packet', reserved: ' @info_final class HomeTestMessage(MH): - """Data model for MH Home Test (HoT) message type.""" + """Data model for MH Home Test (HoT) message type. + + .. versionchanged:: 1.5.0 + + ``options`` is an immutable + :class:`~pcapkit.corekit.infoclass.OrderedMultiInfo` rather than an + :class:`~pcapkit.corekit.multidict.OrderedMultiDict` (:issue:`1484`). + + """ #: Home nonce index. nonce_index: 'int' @@ -260,7 +277,15 @@ def __init__(self, next: 'TransType', length: 'int', type: 'Packet', reserved: ' @info_final class CareofTestMessage(MH): - """Data model for MH Care-of Test (CoT) message type.""" + """Data model for MH Care-of Test (CoT) message type. + + .. versionchanged:: 1.5.0 + + ``options`` is an immutable + :class:`~pcapkit.corekit.infoclass.OrderedMultiInfo` rather than an + :class:`~pcapkit.corekit.multidict.OrderedMultiDict` (:issue:`1484`). + + """ #: Care-of nonce index. nonce_index: 'int' @@ -283,8 +308,11 @@ class BindingUpdateMessage(MH): .. versionchanged:: 1.5.0 - New required fields for the flag bits later RFCs assign, ``map_reg`` - through ``dmm``, and ``flags_reserved`` for the rest (:issue:`1329`). + * New required fields for the flag bits later RFCs assign, ``map_reg`` + through ``dmm``, and ``flags_reserved`` for the rest (:issue:`1329`). + * ``options`` is an immutable + :class:`~pcapkit.corekit.infoclass.OrderedMultiInfo` rather than an + :class:`~pcapkit.corekit.multidict.OrderedMultiDict` (:issue:`1484`). """ @@ -336,9 +364,12 @@ class BindingAcknowledgementMessage(MH): .. versionchanged:: 1.5.0 - New required fields for the flag bits later RFCs assign, - ``mobile_router`` through ``dmm``, and ``flags_reserved`` for the rest - (:issue:`1329`). + * New required fields for the flag bits later RFCs assign, + ``mobile_router`` through ``dmm``, and ``flags_reserved`` for the rest + (:issue:`1329`). + * ``options`` is an immutable + :class:`~pcapkit.corekit.infoclass.OrderedMultiInfo` rather than an + :class:`~pcapkit.corekit.multidict.OrderedMultiDict` (:issue:`1484`). """ @@ -381,7 +412,10 @@ class BindingErrorMessage(MH): .. versionchanged:: 1.5.0 - New required field ``msg_reserved`` (:issue:`1437`). + New required field ``msg_reserved`` (:issue:`1437`), and + ``options`` is an immutable + :class:`~pcapkit.corekit.infoclass.OrderedMultiInfo` rather than an + :class:`~pcapkit.corekit.multidict.OrderedMultiDict` (:issue:`1484`). """ @@ -830,6 +864,11 @@ class BindingRefreshAdviceOption(Option): re-export ``pcapkit.protocols.data.internet.MH_BindRefreshAdviceOption``, with no alias. + .. versionchanged:: 1.5.0 + + ``interval`` is a :class:`~datetime.timedelta` rather than the raw + count of 4-second units (:issue:`493`). + """ #: Refresh interval before re-registration. Counted on the wire in units of @@ -1026,7 +1065,15 @@ def __init__(self, type: 'Enum_CGAExtension', length: 'int', flag: 'bool', reser @info_final class CGAParameter(Data): - """Data model for CGA parameter.""" + """Data model for CGA parameter. + + .. versionchanged:: 1.5.0 + + ``extensions`` is an immutable + :class:`~pcapkit.corekit.infoclass.OrderedMultiInfo` rather than an + :class:`~pcapkit.corekit.multidict.OrderedMultiDict` (:issue:`1484`). + + """ #: Modifier. modifier: 'Enum_CGAType' @@ -2079,7 +2126,7 @@ class QualityOfServiceOption(Option): #: Differentiated services code point, the significant 6 bits of the traffic #: class octet. dscp: 'int' - #: Operational code: ``0`` response, ``1`` allocate, ``2`` de-allocate, + #: Operational code -- ``0`` response, ``1`` allocate, ``2`` de-allocate, #: ``3`` modify, ``4`` query, ``5`` negotiate. oc: 'int' #: The three reserved octets, as captured. @@ -2113,7 +2160,7 @@ def __init__(self, type: 'Enum_Option', length: 'int', class MulticastMobilityOption(Option): """Data model for MH Multicast Mobility option.""" - #: Option code: ``1`` IGMPv3, ``2`` MLDv2, ``3`` IGMPv3 in IGMPv2 + #: Option code -- ``1`` IGMPv3, ``2`` MLDv2, ``3`` IGMPv3 in IGMPv2 #: compatibility mode, ``4`` MLDv2 in MLDv1 compatibility mode. code: 'int' #: The reserved octet, as captured. @@ -2132,7 +2179,7 @@ class MulticastAcknowledgementOption(Option): #: Option code; always ``0``. code: 'int' - #: Status: ``1`` report payload type unsupported, ``2`` requested group + #: Status -- ``1`` report payload type unsupported, ``2`` requested group #: service unsupported, ``3`` administratively prohibited. status: 'int' #: MLD or IGMP unsupported report payload, kept opaque. diff --git a/pcapkit/protocols/internet/ah.py b/pcapkit/protocols/internet/ah.py index cd2f9959d5..1c3564b081 100644 --- a/pcapkit/protocols/internet/ah.py +++ b/pcapkit/protocols/internet/ah.py @@ -215,13 +215,18 @@ def make(self, but written as given so that a parsed header rebuilds unchanged. spi: Security Parameters Index. seq: Sequence Number Field. - icv: Integrity Check Value-ICV. + icv: Integrity Check Value (ICV). payload: Payload of current instance. **kwargs: Arbitrary keyword arguments. Returns: Constructed packet data. + .. versionchanged:: 1.5.0 + + Takes ``reserved`` ahead of ``spi`` (:issue:`1327`), which shifts + every later positional argument. + """ next_value = self._make_index(next, next_default, namespace=next_namespace, reversed=next_reversed, pack=False) diff --git a/pcapkit/protocols/internet/esp.py b/pcapkit/protocols/internet/esp.py index 6aca80e43b..f393cabb08 100644 --- a/pcapkit/protocols/internet/esp.py +++ b/pcapkit/protocols/internet/esp.py @@ -199,8 +199,9 @@ #: Cached :mod:`cryptography` primitives, c.f. :func:`load_cryptography`. #: :data:`NotImplemented` means the import has not been attempted yet, and -#: :data:`None` that it was attempted and |cryptography|_ is not installed -- -#: three states, so a missing dependency is not retried on every frame. +#: :data:`None` that it was attempted and |cryptography|_ is not installed, and +#: a tuple holds the loaded primitives, so a missing dependency is not retried +#: on every frame. _CRYPTO = NotImplemented # type: Any diff --git a/pcapkit/protocols/internet/hip.py b/pcapkit/protocols/internet/hip.py index 1d3d8811c3..3031cbf08b 100644 --- a/pcapkit/protocols/internet/hip.py +++ b/pcapkit/protocols/internet/hip.py @@ -266,18 +266,18 @@ class HIP(IPv6_Ext[Data_HIP, Schema_HIP], Internet[Data_HIP, Schema_HIP], :class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext` alone, while one also usable as a standalone protocol names :class:`~pcapkit.protocols.internet.internet.Internet` as well. HIP is both, - on two independent grounds: + on independent grounds: * :rfc:`7401#section-5.1` states that "the HIP header is logically an IPv6 extension header", and IANA lists protocol 139 in its *IPv6 - Extension Header Types* registry (11 entries). + Extension Header Types* registry. * :rfc:`7401#appendix-C.2`, "IPv4 HIP Packet (I1 Packet)", works a checksum for an **IPv4** header carrying ``Next Header: 139`` with ``Payload Protocol: 59``. HIP therefore travels directly as an IPv4 payload, as do :class:`~pcapkit.protocols.internet.ah.AH` and :class:`~pcapkit.protocols.internet.esp.ESP`. - The second ground separates HIP from + The IPv4 ground separates HIP from :class:`~pcapkit.protocols.internet.mh.MH` and Shim6, which are protocols in their own right but cannot appear under IPv4: :rfc:`6275#section-6.1.1` defines the Mobility Header checksum over a @@ -417,8 +417,8 @@ class HIP(IPv6_Ext[Data_HIP, Schema_HIP], Internet[Data_HIP, Schema_HIP], # Defaults. ########################################################################## - #: DefaultDict[Enum_Parameter, str | tuple[ParameterParser, ParameterConstructor]]: - #: Parameter code to method mapping, c.f. :meth:`_read_hip_param` and/or + #: DefaultDict[Enum_Parameter, str | tuple[ParameterParser, ParameterConstructor]]: Parameter + #: code to method mapping, c.f. :meth:`_read_hip_param` and/or #: :meth:`_make_hip_param`. A :obj:`str` value names the methods #: ``_read_param_${name}`` and ``_make_param_${name}``, falling back to #: ``_read_param_unassigned`` / ``_make_param_unassigned`` when they do not @@ -504,7 +504,6 @@ def payload(self) -> 'ProtocolBase | NoReturn': Raises: UnsupportedCall: if the protocol is used as an IPv6 extension header - :rtype: pcapkit.protocols.protocol.Protocol """ if self._extf: raise UnsupportedCall(f"'{self.__class__.__name__}' object has no attribute 'payload'") @@ -675,6 +674,14 @@ def make(self, Returns: Constructed packet data. + .. versionchanged:: 1.5.0 + + Takes ``packet_fixed`` ahead of ``version`` and ``version_fixed`` + ahead of ``checksum`` (:issue:`1459`), ``version_reserved`` before + ``version_fixed`` (:issue:`1417`), and ``controls_reserved`` ahead of + ``shit`` (:issue:`1328`), which shifts every later positional + argument. + """ next_value = self._make_index(next, next_default, namespace=next_namespace, reversed=next_reversed, pack=False) @@ -1319,7 +1326,7 @@ def _read_param_ack(self, schema: 'Schema_ACKParameter', *, version: 'int', # p Parsed parameter data. Raises: - ProtocolError: If ``schema.len`` is **NOT** ``4`` modulo. + ProtocolError: If ``schema.len`` is **not** a multiple of ``4``. """ if schema.len % 4 != 0: @@ -1441,7 +1448,7 @@ def _read_param_hip_transform(self, schema: 'Schema_HIPTransformParameter', *, v Raises: ProtocolError: If the parameter is used in a version other than - HIPv1, or ``schema.len`` is **NOT** a ``2`` modulo. + HIPv1, or ``schema.len`` is **not** a multiple of ``2``. """ if version != 1: @@ -1485,7 +1492,7 @@ def _read_param_hip_cipher(self, schema: 'Schema_HIPCipherParameter', *, version Raises: ProtocolError: If the parameter is used in HIPv1, or ``schema.len`` is - **NOT** a ``2`` modulo. + **not** a multiple of ``2``. """ if version == 1: @@ -1541,7 +1548,7 @@ def _read_param_nat_traversal_mode(self, schema: 'Schema_NATTraversalModeParamet Parsed parameter data. Raises: - ProtocolError: If ``schema.len`` is **NOT** a ``2`` modulo. + ProtocolError: If ``schema.len`` is **not** a multiple of ``2``. """ if schema.len % 2 != 0: @@ -2123,7 +2130,7 @@ def _read_param_transport_format_list(self, schema: 'Schema_TransportFormatListP Raises: ProtocolError: If the parameter is used in HIPv1, or ``schema.len`` is - **NOT** ``2`` modulo. + **not** a multiple of ``2``. """ if version == 1: @@ -2168,7 +2175,7 @@ def _read_param_esp_transform(self, schema: 'Schema_ESPTransformParameter', *, v Parsed parameter data. Raises: - ProtocolError: If ``schema.len`` is **NOT** ``2`` modulo. + ProtocolError: If ``schema.len`` is **not** a multiple of ``2``. """ if schema.len % 2 != 0: @@ -2248,7 +2255,7 @@ def _read_param_ack_data(self, schema: 'Schema_AckDataParameter', *, version: 'i Parsed parameter data. Raises: - ProtocolError: If ``schema.len`` is **NOT** ``4`` modulo. + ProtocolError: If ``schema.len`` is **not** a multiple of ``4``. """ if schema.len % 4 != 0: @@ -2462,7 +2469,7 @@ def _read_param_hip_transport_mode(self, schema: 'Schema_HIPTransportModeParamet Parsed parameter data. Raises: - ProtocolError: If ``schema.len`` is **NOT** ``2`` modulo. + ProtocolError: If ``schema.len`` is **not** a multiple of ``2``. """ if schema.len % 2 != 0: @@ -3000,7 +3007,7 @@ def _read_param_via_rvs(self, schema: 'Schema_ViaRVSParameter', *, version: 'int Parsed parameter data. Raises: - ProtocolError: If ``schema.len`` is **NOT** ``16`` modulo. + ProtocolError: If ``schema.len`` is **not** a multiple of ``16``. """ if schema.len % 16 != 0: @@ -3416,8 +3423,8 @@ def _make_puzzle_field_width(code: 'Enum_Parameter', version: 'int', ``Length = 20`` ``SOLUTION`` as ``Length = 6`` and build, under HIPv1, parameters this library's own reader rejects. - Two things this deliberately does *not* reject, both of which look like - oversights and are not: + Things this deliberately does *not* reject, each of which looks like an + oversight and is not: * ``rhash_len == 0``, i.e. a zero-width payload field. No real hash has a zero-length output, so no conformant packet carries one -- but it is what diff --git a/pcapkit/protocols/internet/hopopt.py b/pcapkit/protocols/internet/hopopt.py index 926321475f..1d9da85304 100644 --- a/pcapkit/protocols/internet/hopopt.py +++ b/pcapkit/protocols/internet/hopopt.py @@ -275,7 +275,6 @@ def payload(self) -> 'ProtocolBase | NoReturn': Raises: UnsupportedCall: if the protocol is used as an IPv6 extension header - :rtype: pcapkit.protocols.protocol.Protocol """ if self._extf: raise UnsupportedCall(f"'{self.__class__.__name__}' object has no attribute 'payload'") @@ -546,8 +545,8 @@ class implements is :rfc:`8200#section-4.3`, but it defers option def _read_hopopt_options(self, length: 'int') -> 'Option': """Read HOPOPT options. - Positional arguments: - length: length of options + Args: + length: Length of the options area, in octets. Returns: Extracted HOPOPT options @@ -648,9 +647,9 @@ def _read_opt_pad(self, schema: 'Schema_PadOption', *, options: 'Option') -> 'Da by :class:`~pcapkit.protocols.schema.internet.hopopt.PadOption` itself, which sizes both the length octet and the padding data from the option type; ``clen`` is therefore always ``0`` here for a - parsed ``Pad1``, and the check below only guards a schema built by - hand. A ``PadN`` whose ``Opt Data Len`` is ``0`` is a valid - two-octet padding option [:rfc:`8200#section-4.2`]. + parsed ``Pad1``, and the length check this method makes only guards + a schema built by hand. A ``PadN`` whose ``Opt Data Len`` is ``0`` + is a valid two-octet padding option [:rfc:`8200#section-4.2`]. """ code, clen = schema.type, schema.len @@ -1388,12 +1387,12 @@ def _make_hopopt_options(self, options: 'list[Schema_Option | tuple[Enum_Option, data, are emitted in order with their own padding options kept, and padding is added only at the end if the header is not aligned. - The returned length is that of the options area alone, so it is the - value :meth:`make` needs for ``hdr_ext_len``. Since the two octets - of the fixed header precede it, an options area is well formed only - when it is 6 octets short of a multiple of 8 -- including when it - holds no real options at all, in which case it is 6 octets of pure - padding rather than empty. + The returned length is that of the options area alone, the value + :meth:`make` derives the ``Hdr Ext Len`` field from. Since the two + octets of the fixed header precede it, an options area is well + formed only when it is 6 octets short of a multiple of 8 -- + including when it holds no real options at all, in which case it is + 6 octets of pure padding rather than empty. """ total_length = 0 diff --git a/pcapkit/protocols/internet/internet.py b/pcapkit/protocols/internet/internet.py index fce3f641ad..2af8e23393 100644 --- a/pcapkit/protocols/internet/internet.py +++ b/pcapkit/protocols/internet/internet.py @@ -6,9 +6,9 @@ .. module:: pcapkit.protocols.internet.internet :mod:`pcapkit.protocols.internet.internet` contains :class:`~pcapkit.protocols.internet.internet.Internet`, -which is a base class for internet layer protocols, eg. :class:`~pcapkit.protocols.internet.ah.AH`, +which is a base class for internet layer protocols, e.g. :class:`~pcapkit.protocols.internet.ah.AH`, :class:`~pcapkit.protocols.internet.ipsec.IPsec`, :class:`~pcapkit.protocols.internet.ipv4.IPv4`, -:class:`~pcapkit.protocols.internet.ipv6.IPv6`, :class:`~pcapkit.protocols.internet.ipx.IPX`, and etc. +:class:`~pcapkit.protocols.internet.ipv6.IPv6` and :class:`~pcapkit.protocols.internet.ipx.IPX`. """ import collections @@ -371,9 +371,10 @@ def _next_layer_class(self, proto: 'Optional[int]', file_: 'bytes', length: 'int nothing to parse; :class:`~pcapkit.protocols.misc.raw.Raw` if the parse stops at this layer, or the next layer is an IPv6 extension header past :data:`~pcapkit.utilities.config.EXTENSION_HEADER_LIMIT` in a row; otherwise the - class registered for ``proto``. With it, the keywords only that next - layer is constructed with: the warning given, as ``error``, or the - next layer's place in its chain, as ``_exthdr_depth``. + class registered for ``proto``. Paired with it, the keywords that + only that next layer is constructed with: the warning given, as + ``error``, or the next layer's place in its chain, as + ``_exthdr_depth``. Warns: ProtocolWarning: If the next layer is past @@ -428,7 +429,7 @@ def _decode_next_layer(self, dict_: '_PT', proto: 'Optional[int]' = None, # pyl Current protocol with next layer extracted. Notes: - ``dict_`` gains the key ``__next_type__`` (next layer protocol + ``dict_`` gains the keys ``__next_type__`` (next layer protocol type) and ``__next_name__`` (next layer protocol name). Neither is included when :meth:`Info.to_dict ` is called. diff --git a/pcapkit/protocols/internet/ip.py b/pcapkit/protocols/internet/ip.py index 87bcde0371..a1661b9e47 100644 --- a/pcapkit/protocols/internet/ip.py +++ b/pcapkit/protocols/internet/ip.py @@ -7,7 +7,7 @@ :mod:`pcapkit.protocols.internet.ip` contains :class:`~pcapkit.protocols.internet.ip.IP` only, which is a base class for Internet Protocol (IP) -protocol family [*]_, eg. +protocol family [*]_, e.g. :class:`~pcapkit.protocols.internet.ipv4.IPv4` and :class:`~pcapkit.protocols.internet.ipv6.IPv6`. diff --git a/pcapkit/protocols/internet/ipsec.py b/pcapkit/protocols/internet/ipsec.py index 746fc7cda3..1b01658db4 100644 --- a/pcapkit/protocols/internet/ipsec.py +++ b/pcapkit/protocols/internet/ipsec.py @@ -7,7 +7,7 @@ :mod:`pcapkit.protocols.internet.ipsec` contains :class:`~pcapkit.protocols.internet.ipsec.IPsec` only, which is a base class for Internet Protocol -Security (IPsec) protocol family [*]_, eg. +Security (IPsec) protocol family [*]_, e.g. :class:`~pcapkit.protocols.internet.ah.AH` and :class:`~pcapkit.protocols.internet.esp.ESP`. diff --git a/pcapkit/protocols/internet/ipv4.py b/pcapkit/protocols/internet/ipv4.py index 47c439eac3..267eb97e5e 100644 --- a/pcapkit/protocols/internet/ipv4.py +++ b/pcapkit/protocols/internet/ipv4.py @@ -214,8 +214,8 @@ class IPv4(IP[Data_IPv4, Schema_IPv4], # Defaults. ########################################################################## - #: DefaultDict[Enum_OptionNumber, str | tuple[OptionParser, OptionConstructor]]: - #: Option code to method mapping, c.f. :meth:`_read_ipv4_options` and/or + #: DefaultDict[Enum_OptionNumber, str | tuple[OptionParser, OptionConstructor]]: Option + #: code to method mapping, c.f. :meth:`_read_ipv4_options` and/or #: :meth:`_make_ipv4_options`. A :obj:`str` value names the methods #: ``_read_opt_${name}`` and ``_make_opt_${name}``, falling back to #: ``_read_opt_unassigned`` / ``_make_opt_unassigned`` when they do not @@ -501,6 +501,12 @@ def make(self, Returns: Constructed packet data. + .. versionchanged:: 1.5.0 + + Takes ``reserved`` ahead of ``df`` (:issue:`1326`), and + ``total_length`` (:issue:`1155`) and ``padding`` (:issue:`1317`) + ahead of ``payload``, which shifts every later positional argument. + """ tos_pre_val = self._make_index(tos_pre, tos_pre_default, namespace=tos_pre_namespace, reversed=tos_pre_reversed, pack=False) diff --git a/pcapkit/protocols/internet/ipv6.py b/pcapkit/protocols/internet/ipv6.py index d3801d8bfe..36275e37da 100644 --- a/pcapkit/protocols/internet/ipv6.py +++ b/pcapkit/protocols/internet/ipv6.py @@ -16,7 +16,7 @@ 0 0 ``ipv6.version`` Version (``6``) 0 4 ``ipv6.class`` Traffic Class 1 12 ``ipv6.label`` Flow Label - 4 32 ``ipv6.payload`` Payload Length (header excludes) + 4 32 ``ipv6.payload`` Payload Length (header excluded) 6 48 ``ipv6.next`` Next Header 7 56 ``ipv6.limit`` Hop Limit 8 64 ``ipv6.src`` Source Address @@ -108,7 +108,7 @@ class IPv6(IP[Data_IPv6, Schema_IPv6], #: :class:`~pcapkit.protocols.misc.raw.Raw`, which has no ``next`` field #: and so cannot continue the walk in :meth:`_decode_next_layer`. #: - #: Three groups are deliberately absent: + #: These groups are deliberately absent: #: #: * :attr:`~pcapkit.const.ipv6.extension_header.ExtensionHeader.Shim6` #: has no dedicated parser class in this package, so there is no "own @@ -328,6 +328,11 @@ def make(self, Returns: Constructed packet data. + .. versionchanged:: 1.5.0 + + Takes ``payload_length`` ahead of ``payload`` (:issue:`1155`), which + shifts a positional ``payload``. + """ next_val = self._make_index(next, next_default, namespace=next_namespace, reversed=next_reversed, pack=False) diff --git a/pcapkit/protocols/internet/ipv6_ext.py b/pcapkit/protocols/internet/ipv6_ext.py index e10e3adb93..f4217d3011 100644 --- a/pcapkit/protocols/internet/ipv6_ext.py +++ b/pcapkit/protocols/internet/ipv6_ext.py @@ -6,10 +6,10 @@ :mod:`pcapkit.protocols.internet.ipv6_ext` contains :class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext` -only, which serves two roles (GitHub issue :issue:`917`): the shared -**base class** of every IPv6 extension header in this package, and a -**generic** extractor that stands in for a header whose dedicated parser is -unavailable or has failed. The class docstring divides the two. +only, which is both (GitHub issue :issue:`917`) the shared **base class** of +every IPv6 extension header in this package and a **generic** extractor that +stands in for a header whose dedicated parser is unavailable or has failed. +The class docstring separates these roles. Why this is safe in general ---------------------------- @@ -23,7 +23,7 @@ ======= ========= ===================== ===================================== 0 0 ``next`` Next Header 1 8 ``len`` Hdr Ext Len (8-octet units, - excluding the first 8 octets) + excluding the first 8 octets) 2 16 ``payload`` Header-specific content ======= ========= ===================== ===================================== @@ -59,19 +59,19 @@ next header field is ever read at all, either ======================================================= =========================================== -This table classifies by *wire format* alone, over the eleven codes +This table classifies by *wire format* alone, over every code :class:`~pcapkit.const.ipv6.extension_header.ExtensionHeader` enumerates, which match IANA's *IPv6 Extension Header Types* registry exactly (GitHub issue :issue:`925`). ``Shim6`` conforms to it (:rfc:`5533`) but has no dedicated parser class; see -"Two entry paths" below for how it reaches this class by direct dispatch. +"Entry paths" below for how it reaches this class by direct dispatch. :rfc:`8200#section-4.5` sets Encapsulating Security Payload aside: ESP is not an extension header, and the RFC lists it among upper-layer headers. :rfc:`4303` puts its Next Header inside the encrypted trailer, with no length field in the cleartext part. 253 and 254 are reserved for private -experimentation (:rfc:`3692`) with no wire format at all. None of the three +experimentation (:rfc:`3692`) with no wire format at all. None of them is reachable through this class, by construction; see :meth:`pcapkit.protocols.internet.ipv6.IPv6._import_next_layer`. Each still enters :meth:`IPv6._decode_next_layer @@ -86,10 +86,10 @@ implementation, the walk stops on a *structural* check (does the parsed layer carry a ``next`` at all?) rather than on a list of codes. -Two entry paths ----------------- +Entry paths +----------- -This class is reached two different ways: +This class is reached in these ways: 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 @@ -163,7 +163,7 @@ class _NextHeaderData(Protocol): """The one field every IPv6 extension header's data model carries. :rfc:`8200#section-4.1` puts a Next Header octet first in every - extension header, and all eight implemented ones record it under this + extension header, and every implemented one records it under this name -- which is what lets :attr:`IPv6_Ext.next` be a *shared* member rather than a fallback-role-only one. Narrower than :class:`~pcapkit.protocols.data.internet.ipv6_ext.IPv6_Ext` on @@ -185,20 +185,19 @@ class IPv6_Ext(Internet[_PT, _ST], Generic[_PT, _ST], """This class implements a generic IPv6 extension header parser, and is the shared base of every IPv6 extension header in this package. - The module docstring has the RFC citations behind the length rules, the two + The module docstring has the RFC citations behind the length rules, the ways this class is dispatched to, and why an overrun stops the walk rather than clipping. - The two roles - -------------- + .. rubric:: Base and Fallback Roles One class plays both, on the owner's ruling for GitHub issue :issue:`917`: 1. **The concrete fallback parser** for an :rfc:`6564`-conforming header with no dedicated class, or whose dedicated class raised. :meth:`read`, :meth:`make`, :attr:`name`, :attr:`alias` and the ``schema=``/``data=`` - above implement it. - 2. **The base class** of the eight implemented extension headers + class keywords implement it. + 2. **The base class** of the implemented extension headers (:class:`~pcapkit.protocols.internet.hopopt.HOPOPT`, :class:`~pcapkit.protocols.internet.ipv6_route.IPv6_Route`, :class:`~pcapkit.protocols.internet.ipv6_frag.IPv6_Frag`, @@ -211,15 +210,15 @@ class IPv6_Ext(Internet[_PT, _ST], Generic[_PT, _ST], :attr:`protochain` are for. It is generic in its data and schema types, like - :class:`~pcapkit.protocols.internet.ipsec.IPsec`, the other base in this + :class:`~pcapkit.protocols.internet.ipsec.IPsec`, another base in this package, so that a subclass keeps its *own* ``_PT``/``_ST`` instead of inheriting this class's. ``AH`` and ``ESP`` therefore double-inherit two identically-parameterised generic bases, ``IPsec[…]`` and ``IPv6_Ext[…]``. Warning: A subclass **must** define :attr:`name`, :attr:`alias`, - :attr:`protocol`, :attr:`length` and :meth:`__index__` itself. All five - carry this class's *fallback-role* answers, which are wrong for a + :attr:`protocol`, :attr:`length` and :meth:`__index__` itself. Each + carries this class's *fallback-role* answer, which is wrong for a header with an identity of its own: it would report itself as ``IPv6 Extension Header`` / ``IPv6-Ext``, read its length off a data model that is not its own, and :meth:`__index__` would raise rather @@ -275,7 +274,7 @@ def length(self) -> 'int': Fallback-role member: it reads this module's *own* data model, so a subclass whose data model records its length elsewhere, or not at all (as :class:`~pcapkit.protocols.data.internet.ipv6_frag.IPv6_Frag`), must - override it. All eight implemented headers do, and + override it. Every implemented header does, and ``tests/protocols/internet/test_ipv6_ext_unit.py`` holds them to it. """ @@ -304,7 +303,7 @@ def protocol(self) -> 'Optional[Enum_ExtensionHeader] | Optional[str]': In the *base* role it falls through to ``super()``, restoring the ordinary :class:`~pcapkit.protocols.protocol.ProtocolBase` meaning. - That is load-bearing: all eight subclasses implement their own + That is load-bearing: every subclass implements its own ``_extf``-guarded ``protocol`` as ``return super().protocol``, and this class sits between them and :class:`~pcapkit.protocols.protocol.ProtocolBase` in the MRO, so without @@ -374,8 +373,8 @@ def read(self, length: 'Optional[int]' = None, *, # pylint: disable=arguments-d Args: length: Length of packet data. extension: If the protocol is used as an IPv6 extension header. - **kwargs: Arbitrary keyword arguments, two of which are this - class's own and are supplied by :meth:`IPv6._import_next_layer + **kwargs: Arbitrary keyword arguments, including these, which are + this class's own and are supplied by :meth:`IPv6._import_next_layer ` rather than typed by a caller: @@ -390,10 +389,10 @@ def read(self, length: 'Optional[int]' = None, *, # pylint: disable=arguments-d Parsed packet data. Note: - Those two are read out of ``**kwargs`` rather than declared, and + These are read out of ``**kwargs`` rather than declared, and ``version`` is not declared either, because this class is *also* - the base of eight subclasses whose own ``read`` accepts none of the - three. Declaring them would assert of the whole family an interface + the base of subclasses whose own ``read`` accepts none of them. + Declaring them would assert of the whole family an interface only the fallback has, which mypy (``Signature of "read" incompatible with supertype``, ``[override]``) and pylint (``arguments-differ``) both reject. This method never reads @@ -483,23 +482,23 @@ def make(self, *, Constructed packet data. Note: - **Keyword-only**, for two reasons. + **Declared, and keyword-only**, each for its own reason. - The four stay *declared*, unlike :meth:`read`'s own keywords, - because :func:`~pcapkit.protocols.protocol._check_construction_keywords` + These keywords stay *declared*, unlike :meth:`read`'s own, + because ``_check_construction_keywords()`` builds its allowlist from :func:`inspect.signature` of ``make``. Hiding them in ``**kwargs`` **breaks construction**: a plain ``IPv6_Ext(next=..., len=..., data=..., payload=...)`` raises ``UnsupportedCall: IPv6_Ext: unexpected keyword(s)``. The ``__keywords__`` escape hatch would cover that, but it is unioned down the MRO, so it would widen the allowlist, and weaken that - misspelling check, for all eight subclasses too. + misspelling check, for every subclass too. They are keyword-*only* because this class is also a base and each subclass's ``make`` puts different names in the same positions. As positional parameters they draw pylint ``arguments-renamed`` warnings (``ESP.make`` has ``spi``, ``seq`` and ``next`` where - these have ``next``, ``len`` and ``payload``) and mypy + these have ``next``, ``len`` and ``data``) and mypy ``[override]`` errors. With no positional parameters there is no position to disagree about, and the allowlist is unaffected because ``_declared_keywords`` collects ``KEYWORD_ONLY`` parameters too. @@ -551,7 +550,7 @@ def __post_init__(self, file: 'Optional[IO[bytes] | bytes]' = None, length: 'Opt Raises: ProtocolError: If this is the *fallback* parser (see the class - docstring's "two roles") and ``version`` is not ``6``. + docstring's "Base and Fallback Roles") and ``version`` is not ``6``. See Also: For construction argument, please refer to :meth:`make`. @@ -559,7 +558,7 @@ def __post_init__(self, file: 'Optional[IO[bytes] | bytes]' = None, length: 'Opt Note: The version gate is scoped to the fallback role by the same ``__data__`` discriminator :attr:`protocol` uses. Applying it to - subclasses would break the two that are *not* IPv6-only: + subclasses would break those that are *not* IPv6-only: :class:`~pcapkit.protocols.internet.ah.AH` and :class:`~pcapkit.protocols.internet.esp.ESP` both default to ``version=4`` and are valid under IPv4, so an unconditional gate diff --git a/pcapkit/protocols/internet/ipv6_frag.py b/pcapkit/protocols/internet/ipv6_frag.py index d34f28a24e..f2b5c70329 100644 --- a/pcapkit/protocols/internet/ipv6_frag.py +++ b/pcapkit/protocols/internet/ipv6_frag.py @@ -183,7 +183,7 @@ def make(self, reserved_octet: The reserved octet after the next header, truncated or zero-filled to one octet. offset: Fragment offset, in on-wire 8-octet units (:rfc:`8200#section-4.5`). - Note that :attr:`Data_IPv6_Frag.offset + Note that the data model's :attr:`offset ` is in octets, so it must be divided by 8 before being passed here. reserved: Reserved bits (``Res``) between the offset and the M flag. @@ -195,6 +195,12 @@ def make(self, Returns: Constructed packet data. + .. versionchanged:: 1.5.0 + + Takes ``reserved_octet`` ahead of ``offset`` (:issue:`1223`) and + ``reserved`` ahead of ``mf`` (:issue:`1235`), which shifts every + later positional argument. + """ next_val = self._make_index(next, next_default, namespace=next_namespace, reversed=next_reversed, pack=False) diff --git a/pcapkit/protocols/internet/ipv6_opts.py b/pcapkit/protocols/internet/ipv6_opts.py index a7c30ac01f..800cd76c1c 100644 --- a/pcapkit/protocols/internet/ipv6_opts.py +++ b/pcapkit/protocols/internet/ipv6_opts.py @@ -266,7 +266,6 @@ def payload(self) -> 'ProtocolBase | NoReturn': Raises: UnsupportedCall: if the protocol is used as an IPv6 extension header - :rtype: pcapkit.protocols.protocol.Protocol """ if self._extf: raise UnsupportedCall(f"'{self.__class__.__name__}' object has no attribute 'payload'") @@ -534,8 +533,8 @@ def _ipv6_opts_option_length(schema_len: 'int') -> 'int': def _read_ipv6_opts(self, length: 'int') -> 'Option': """Read IPv6-Opts options. - Positional arguments: - length: length of options + Args: + length: Length of the options area, in octets. Returns: Extracted IPv6-Opts options @@ -631,15 +630,14 @@ def _read_opt_pad(self, schema: 'Schema_PadOption', *, options: 'Option') -> 'Da Note: A ``Pad1`` option occupies a single octet and carries no ``Opt Data Len`` field, so its - :attr:`~pcapkit.protocols.data.internet.ipv6_opts.Option.length` - is ``1`` rather than ``len + 2``. That one-octet wire shape is - enforced by - :class:`~pcapkit.protocols.schema.internet.ipv6_opts.PadOption` + :attr:`~pcapkit.protocols.data.internet.ipv6_opts.Option.length` is + ``1`` rather than ``len + 2``. That one-octet wire shape is enforced + by :class:`~pcapkit.protocols.schema.internet.ipv6_opts.PadOption` itself, which sizes both the length octet and the padding data from the option type; ``clen`` is therefore always ``0`` here for a - parsed ``Pad1``, and the check below only guards a schema built by - hand. A ``PadN`` whose ``Opt Data Len`` is ``0`` is a valid - two-octet padding option [:rfc:`8200#section-4.2`]. + parsed ``Pad1``, and the length check this method makes only guards + a schema built by hand. A ``PadN`` whose ``Opt Data Len`` is ``0`` + is a valid two-octet padding option [:rfc:`8200#section-4.2`]. """ code, clen = schema.type, schema.len @@ -1377,12 +1375,12 @@ def _make_ipv6_opts(self, options: 'list[Schema_Option | tuple[Enum_Option, dict data, are emitted in order with their own padding options kept, and padding is added only at the end if the header is not aligned. - The returned length is that of the options area alone, so it is the - value :meth:`make` needs for ``hdr_ext_len``. Since the two octets - of the fixed header precede it, an options area is well formed only - when it is 6 octets short of a multiple of 8 -- including when it - holds no real options at all, in which case it is 6 octets of pure - padding rather than empty. + The returned length is that of the options area alone, the value + :meth:`make` derives the ``Hdr Ext Len`` field from. Since the two + octets of the fixed header precede it, an options area is well + formed only when it is 6 octets short of a multiple of 8 -- + including when it holds no real options at all, in which case it is + 6 octets of pure padding rather than empty. """ total_length = 0 diff --git a/pcapkit/protocols/internet/ipv6_route.py b/pcapkit/protocols/internet/ipv6_route.py index cf97d64098..82a1a0211f 100644 --- a/pcapkit/protocols/internet/ipv6_route.py +++ b/pcapkit/protocols/internet/ipv6_route.py @@ -161,7 +161,6 @@ def payload(self) -> 'ProtocolBase | NoReturn': Raises: UnsupportedCall: if the protocol is used as an IPv6 extension header - :rtype: pcapkit.protocols.protocol.Protocol """ if self._extf: raise UnsupportedCall(f"'{self.__class__.__name__}' object has no attribute 'payload'") @@ -248,9 +247,8 @@ def _make_hdr_ext_len(data_length: 'int') -> 'int': fixed part (``next``/``length``/``type``/``seg_left``) is 4 octets, so the on-the-wire header is ``4 + data_length`` octets; equating that to ``8 + 8 * hdr_ext_len`` gives ``hdr_ext_len = (data_length - 4) / 8``. - ``ipv6_route_data_length`` in - :mod:`pcapkit.protocols.schema.internet.ipv6_route` is the inverse, - used on the read side. + :func:`~pcapkit.protocols.schema.internet.ipv6_route.ipv6_route_data_length` + is the inverse, used on the read side. This is the *only* place ``Hdr Ext Len`` is computed on the write side: separate per-branch copies in :meth:`make` drift apart, so keep one @@ -311,6 +309,11 @@ def make(self, FieldValueError: If ``dst`` is a :obj:`bool` (c.f. :func:`~pcapkit.corekit.fields.ipaddress.parse_ip_address`). + .. versionchanged:: 1.5.0 + + Takes ``hdr_ext_len`` ahead of ``payload`` (:issue:`1457`), which + shifts a positional ``payload``. + """ next_val = cast('Enum_TransType', self._make_index(next, next_default, namespace=next_namespace, diff --git a/pcapkit/protocols/internet/ipx.py b/pcapkit/protocols/internet/ipx.py index 0a1cb5ac0d..18670a65ca 100644 --- a/pcapkit/protocols/internet/ipx.py +++ b/pcapkit/protocols/internet/ipx.py @@ -14,7 +14,7 @@ Octets Bits Name Description ======= ========= ====================== ===================================== 0 0 ``ipx.chksum`` Checksum - 2 16 ``ipx.len`` Packet Length (header includes) + 2 16 ``ipx.len`` Packet Length (header included) 4 32 ``ipx.count`` Transport Control (hop count) 5 40 ``ipx.type`` Packet Type 6 48 ``ipx.dst`` Destination Address @@ -153,7 +153,7 @@ def make(self, type_default: Default value for undefined packet type. type_namespace: Namespace for packet type. type_reversed: Reverse namespace for packet type. - total_length: Packet length (header includes); computed as ``30`` plus + total_length: Packet length (header included); computed as ``30`` plus the payload length when not given. dst: Destination Address. src: Source Address. @@ -165,6 +165,11 @@ def make(self, Returns: Constructed packet data. + .. versionchanged:: 1.5.0 + + Takes ``total_length`` ahead of ``dst`` (:issue:`1315`), which + shifts every later positional argument. + """ type_val = self._make_index(type, type_default, namespace=type_namespace, reversed=type_reversed, pack=False) diff --git a/pcapkit/protocols/internet/mh.py b/pcapkit/protocols/internet/mh.py index 6376ad8985..96a7e7e05e 100644 --- a/pcapkit/protocols/internet/mh.py +++ b/pcapkit/protocols/internet/mh.py @@ -577,7 +577,7 @@ class FastBindingAcknowledgmentStatus(EnumLookup, IntEnum): :rfc:`5568#section-6.2.3` defines these values inline and IANA keeps no registry of them, so the enumeration lives here rather than in :mod:`pcapkit.const.mh`. It is also **not** interchangeable with the - registered :class:`~pcapkit.const.mh.status_code.StatusCode`, since two + registered :class:`~pcapkit.const.mh.status_code.StatusCode`, since some of its values collide semantically: ``1`` means *NCoA is invalid* here but *accepted but prefix discovery necessary* there, and ``131`` means *incorrect interface identifier length* here but *home registration not @@ -599,7 +599,7 @@ class FastBindingAcknowledgmentStatus(EnumLookup, IntEnum): :exc:`~pcapkit.utilities.exceptions.EnumValueError`. A ``get`` override was rejected (decided on :issue:`935`; the override was deleted, not widened to accept ``default``): this class mints no alias - (``__members__`` and ``list(cls)`` both hold 6 members), so an override + (``__members__`` and ``list(cls)`` hold the same members), so an override could only repeat the base's dual :class:`int`/name resolution. """ @@ -670,7 +670,7 @@ class IPv6AddressPrefixCode(EnumLookup, IntEnum): :exc:`~pcapkit.utilities.exceptions.EnumValueError`. A ``get`` override was rejected (decided on :issue:`935`; the override was deleted, not widened to accept ``default``): this class mints no alias - (``__members__`` and ``list(cls)`` both hold 4 members), so an override + (``__members__`` and ``list(cls)`` hold the same members), so an override could only repeat the base's dual :class:`int`/name resolution. """ @@ -1168,8 +1168,8 @@ class MH(IPv6_Ext[Data_MH, Schema_MH], # Defaults. ########################################################################## - #: DefaultDict[Enum_Packet, str | tuple[PacketParser, PacketConstructor]]: - #: Message type to method mapping. A :obj:`str` value names the methods + #: DefaultDict[Enum_Packet, str | tuple[PacketParser, PacketConstructor]]: Message + #: type to method mapping. A :obj:`str` value names the methods #: ``_read_msg_${name}`` and ``_make_msg_${name}``, falling back to #: ``_read_msg_unknown`` / ``_make_msg_unknown`` when they do not exist; any #: other value is a ``(parser, constructor)`` pair of callables. @@ -1203,8 +1203,8 @@ class MH(IPv6_Ext[Data_MH, Schema_MH], }, ) # type: DefaultDict[Enum_Packet | int, str | tuple[PacketParser, PacketConstructor]] - #: DefaultDict[Enum_Option, str | tuple[OptionParser, OptionConstructor]]: - #: Option type to method mapping. A :obj:`str` value names the methods + #: DefaultDict[Enum_Option, str | tuple[OptionParser, OptionConstructor]]: Option + #: type to method mapping. A :obj:`str` value names the methods #: ``_read_opt_${name}`` and ``_make_opt_${name}``, falling back to #: ``_read_opt_none`` / ``_make_opt_none`` when they do not exist; any other #: value is a ``(parser, constructor)`` pair of callables. @@ -1285,8 +1285,8 @@ class MH(IPv6_Ext[Data_MH, Schema_MH], }, ) # type: DefaultDict[Enum_Option | int, str | tuple[OptionParser, OptionConstructor]] - #: DefaultDict[Enum_CGAExtension, str | tuple[ExtensionParser, ExtensionConstructor]]: - #: CGA extension type to method mapping. A :obj:`str` value names the + #: DefaultDict[Enum_CGAExtension, str | tuple[ExtensionParser, ExtensionConstructor]]: CGA + #: extension type to method mapping. A :obj:`str` value names the #: methods ``_read_ext_${name}`` and ``_make_ext_${name}``, falling back to #: ``_read_ext_none`` / ``_make_ext_none`` when they do not exist; any other #: value is a ``(parser, constructor)`` pair of callables. @@ -1478,6 +1478,11 @@ def make(self, Returns: Constructed packet data. + .. versionchanged:: 1.5.0 + + Takes ``reserved`` ahead of ``chksum`` (:issue:`1329`), which shifts + every later positional argument. + """ next_val = self._make_index(next, next_default, namespace=next_namespace, reversed=next_reversed, pack=False) @@ -1923,7 +1928,7 @@ def _read_msg_bu(self, schema: 'Schema_BindingUpdateMessage', *, | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - Later RFCs assign eight more of the flag bits, ``M``, ``R``, ``P``, + Later RFCs assign more of the flag bits, ``M``, ``R``, ``P``, ``F``, ``T``, ``B``, ``S`` and ``D`` in that order (IANA *Binding Update Flags*); the low four bits remain reserved. @@ -1980,7 +1985,7 @@ def _read_msg_ba(self, schema: 'Schema_BindingAcknowledgementMessage', *, | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - Later RFCs assign six more of the flag bits, ``R``, ``P``, ``T``, + Later RFCs assign more of the flag bits, ``R``, ``P``, ``T``, ``B``, ``S`` and ``D`` in that order (IANA *Binding Acknowledgment Flags*); the lowest bit remains reserved. @@ -2279,8 +2284,8 @@ def _read_msg_hi(self, schema: 'Schema_HandoverInitiateMessage', *, Note: :rfc:`5568` defines only the ``S`` and ``U`` flags; the ``P`` and ``F`` flags, and code values ``2`` and ``3``, are added by - :rfc:`5949#section-6.1.1`. All four flags are registered in the IANA - *Handover Initiate Flags* registry, hence all four are parsed here. + :rfc:`5949#section-6.1.1`. All of these flags are registered in the IANA + *Handover Initiate Flags* registry, hence all are parsed here. Args: schema: Parsed message type schema. @@ -2330,9 +2335,9 @@ def _read_msg_hack(self, schema: 'Schema_HandoverAcknowledgeMessage', *, Note: :rfc:`5568` reserves the whole first octet of the message data; the ``U``, ``P`` and ``F`` flags carved out of it, and code values ``5`` - and ``6``, are added by :rfc:`5949#section-6.1.2`. All three flags + and ``6``, are added by :rfc:`5949#section-6.1.2`. All of these flags are registered in the IANA *Handover Acknowledge Flags* registry, - hence all three are parsed here. + hence all are parsed here. Args: schema: Parsed message type schema. @@ -3003,15 +3008,14 @@ def _read_opt_pad(self, schema: 'Schema_PadOption', *, Note: A ``Pad1`` option occupies a single octet and carries no ``Option Length`` field, so its - :attr:`~pcapkit.protocols.data.internet.mh.Option.length` is - ``1`` rather than ``length + 2``. That one-octet wire shape is - enforced by - :class:`~pcapkit.protocols.schema.internet.mh.PadOption` itself, + :attr:`~pcapkit.protocols.data.internet.mh.Option.length` is ``1`` + rather than ``length + 2``. That one-octet wire shape is enforced + by :class:`~pcapkit.protocols.schema.internet.mh.PadOption` itself, which sizes both the length octet and the padding data from the option type [:rfc:`6275#section-6.2.2`]; ``clen`` is therefore - always ``0`` here for a parsed ``Pad1``, and the check below only - guards a schema built by hand. A ``PadN`` whose ``Option Length`` - is ``0`` is a valid two-octet padding option + always ``0`` here for a parsed ``Pad1``, and the length check this + method makes only guards a schema built by hand. A ``PadN`` whose + ``Option Length`` is ``0`` is a valid two-octet padding option [:rfc:`6275#section-6.2.3`]. """ @@ -4225,7 +4229,7 @@ def _read_opt_ipv4_ack(self, schema: 'Schema_IPv4AddressAcknowledgementOption', option of :rfc:`5844` uses: that one has no value ``133``. Nor is it the pseudo-home-address registry of :rfc:`5726` that :class:`~pcapkit.const.mh.ack_status_code.ACKStatusCode` carries, - whose name is the more obvious trap of the three. + whose name is the most obvious trap of these. Args: schema: Parsed option schema. @@ -4838,7 +4842,7 @@ def _read_fid_suboptions( own :class:`abc.ABCMeta` cache (:issue:`439`). The same reasoning applies to :meth:`_read_ani_suboptions`, :meth:`_read_qos_attributes`, :meth:`_read_lcmp_suboptions` and their - four constructor counterparts. + constructor counterparts. Args: suboptions_schema: Parsed sub-option schemas. @@ -4926,7 +4930,7 @@ def _read_opt_fid(self, schema: 'Schema_FlowIdentificationOption', *, Note: The status draws from the *Flow Identification Mobility Option Status Codes* registry, which is neither the general mobility status codes nor - the flow binding acknowledgement ones -- three similarly named + the flow binding acknowledgement ones -- similarly named registries that must not be confused. Args: @@ -8127,8 +8131,8 @@ def _make_opt_mn_id(self, type: 'Enum_Option', option: 'Optional[Data_MNIDOption :obj:`bool`, which is rejected for every subtype -- see ``Raises`` below. For ``IPv6_Address`` it is converted and validated the same way as any other value - :class:`ipaddress.IPv6Address` accepts. For the other six - subtypes -- all numeric identifiers (an IMSI, a P-TMSI, an + :class:`ipaddress.IPv6Address` accepts. For every other + subtype -- each a numeric identifier (an IMSI, a P-TMSI, an EUI-48/64 address, a GUTI, a DUID) -- it is converted to its own minimal big-endian octets, at least one. ``NAI`` is text (RFC 4283's ``user@realm`` form) rather than a numeric @@ -8143,22 +8147,21 @@ def _make_opt_mn_id(self, type: 'Enum_Option', option: 'Optional[Data_MNIDOption Raises: ProtocolError: If ``identifier`` is a :obj:`bool`, for any subtype -- :obj:`bool` is an :obj:`int` subclass, so ``True`` would - otherwise be converted by two different paths below, to - ``::1`` for ``IPv6_Address`` and to a one-octet identifier for - the other six, neither of which a caller passing a flag can - plausibly have meant; pass ``int(...)`` to get the numeric - value. If ``identifier`` is a negative :obj:`int` - (no subtype has a wire form for one), an :obj:`int` of any value - with the ``NAI`` subtype, an :obj:`int` of ``2**128`` or - above with the ``IPv6_Address`` subtype (whose wire form is a - fixed 16 octets, unlike the other subtypes, which have no - ceiling and simply pack into more), or ``identifier`` is of a - type its subtype's field cannot hold at all: anything but - :obj:`str` for ``NAI``, anything but :obj:`bytes`/ - :obj:`bytearray`/:obj:`int` for the other six -- an :obj:`int` - is converted rather than rejected there -- or anything - :class:`ipaddress.IPv6Address` itself does not accept for - ``IPv6_Address``. + otherwise be converted, to ``::1`` for ``IPv6_Address`` and to + a one-octet identifier for the numeric subtypes, neither of + which a caller passing a flag can plausibly have meant; pass + ``int(...)`` to get the numeric value. If ``identifier`` is a + negative :obj:`int` (no subtype has a wire form for one), an + :obj:`int` of any value with the ``NAI`` subtype, an :obj:`int` + of ``2**128`` or above with the ``IPv6_Address`` subtype (whose + wire form is a fixed 16 octets, unlike the other subtypes, + which have no ceiling and simply pack into more), or + ``identifier`` is of a type its subtype's field cannot hold at + all: anything but :obj:`str` for ``NAI``, anything but + :obj:`bytes`/:obj:`bytearray`/:obj:`int` for the remaining + subtypes -- an :obj:`int` is converted rather than rejected + there -- or anything :class:`ipaddress.IPv6Address` itself does + not accept for ``IPv6_Address``. """ if option is not None: @@ -8195,9 +8198,9 @@ def _make_opt_mn_id(self, type: 'Enum_Option', option: 'Optional[Data_MNIDOption if isinstance(identifier, bool): # NOTE: checked before the subtype dispatch, because ``bool`` is an - # ``int`` subclass and so would otherwise be converted by *two* - # different paths below -- ``IPv6Address(1)``, that is ``::1``, for - # ``IPv6_Address``, and a one-octet identifier for the six + # ``int`` subclass and so would otherwise be converted by + # *different* paths below -- ``IPv6Address(1)``, that is ``::1``, for + # ``IPv6_Address``, and a one-octet identifier for the # ``BytesField`` subtypes. An MN-ID of ``True`` is a caller mistake # in every case rather than a value anyone means, so it is refused # before either path can give it a plausible-looking wire form. A @@ -8219,7 +8222,7 @@ def _make_opt_mn_id(self, type: 'Enum_Option', option: 'Optional[Data_MNIDOption # NOTE: the upper-bound mirror of the negative-int guard above, and # it belongs here rather than up there because this bound is # subtype-*dependent*: ``2**140`` is a perfectly good identifier for - # the six ``BytesField`` subtypes -- it simply packs into more + # the ``BytesField`` subtypes -- it simply packs into more # octets -- and only ``IPv6_Address`` caps at 128 bits. Left # unguarded, :class:`ipaddress.IPv6Address` raises # ``AddressValueError``, itself a bare :exc:`ValueError`, so this @@ -9674,14 +9677,15 @@ def _make_fid_suboption(self, code: 'Enum_FlowIDSuboption', Constructed sub-option schema. Note: - The data model parameter is named ``option`` rather than ``data`` to - match :meth:`_make_opt_fid` and the rest of this module -- and because - ``data`` would **shadow a field**. The traffic selector and unassigned - sub-options both carry a field of their own called ``data``, so a - caller's ``data=`` would bind to the model parameter instead of landing - in ``**kwargs``, and the ``kwargs.get('data')`` fallback below would - never see it: the payload would be silently dropped and the length - written as though it were empty, with no exception. + The data model parameter is named ``option`` rather than ``data`` + to match :meth:`_make_opt_fid` and the rest of this module -- and + because ``data`` would **shadow a field**. The traffic selector and + unassigned sub-options both carry a field of their own called + ``data``, so a caller's ``data=`` would bind to the model parameter + instead of landing in ``**kwargs``, and the fallback that reads + ``data`` from ``**kwargs`` would never see it: the payload would be + silently dropped and the length written as though it were empty, + with no exception. Dispatch is on ``code`` rather than on ``isinstance`` of the schema or data class, for the reason given in :meth:`_read_fid_suboptions`. diff --git a/pcapkit/protocols/schema/internet/ah.py b/pcapkit/protocols/schema/internet/ah.py index d80570933d..e80321ec94 100644 --- a/pcapkit/protocols/schema/internet/ah.py +++ b/pcapkit/protocols/schema/internet/ah.py @@ -27,7 +27,14 @@ @schema_final class AH(Schema): - """Header schema for AH packet.""" + """Header schema for AH packet. + + .. versionchanged:: 1.5.0 + + ``reserved`` is read as an :obj:`int` rather than as zero padding + :obj:`bytes`, and written back as given (:issue:`1327`). + + """ #: Next header. next: 'Enum_TransType' = EnumField(length=1, namespace=Enum_TransType) diff --git a/pcapkit/protocols/schema/internet/hip.py b/pcapkit/protocols/schema/internet/hip.py index 52e9c1610b..b2704ed6ca 100644 --- a/pcapkit/protocols/schema/internet/hip.py +++ b/pcapkit/protocols/schema/internet/hip.py @@ -129,6 +129,8 @@ def locator_value_selector(pkt: 'dict[str, Any]') -> 'Field': pkt: Packet data. Returns: + The field for the locator, chosen by its type and length. + * If ``type`` is ``0`` and ``len`` is ``4``, i.e. a 16-octet locator, returns an :class:`~pcapkit.corekit.fields.ipaddress.IPv6AddressField` instance. * If ``type`` is ``1`` and ``len`` is ``5``, i.e. a 20-octet locator, @@ -156,6 +158,8 @@ def host_id_hi_selector(pkt: 'dict[str, Any]') -> 'Field': pkt: Packet data. Returns: + The field for the host identity, chosen by its algorithm. + * If ``algorithm`` is ``7`` (ECDSA), returns a :class:`~pcapkit.corekit.fields.misc.SchemaField` wrapped :class:`~pcapkit.protocols.schema.internet.hip.ECDSACurveHostIdentity` instance. @@ -238,7 +242,7 @@ def two_octet_prefix_list_len(pkt: 'dict[str, Any]') -> 'int': two-octet ``reserved`` or ``port`` field with a list of items sized by the remainder of the parameter. - :class:`TransportFormatListParameter` looks like a fourth call site -- + :class:`TransportFormatListParameter` looks like another call site -- same ``pkt['len'] - 2`` expression -- but is not: see :func:`transport_format_list_len` for why it has no such prefix to subtract. @@ -265,7 +269,7 @@ def transport_format_list_len(pkt: 'dict[str, Any]') -> 'int': """Return ``TRANSPORT_FORMAT_LIST`` transport format list length. Used by the ``formats`` field of :class:`TransportFormatListParameter`. - Unlike :func:`two_octet_prefix_list_len`'s three call sites, this + Unlike :func:`two_octet_prefix_list_len`'s call sites, this parameter has no ``reserved`` or ``port`` field between ``Length`` and the list: :rfc:`7401` Section 5.2.11 defines ``Length`` as literally "2x number of TF types" and places the list directly after it -- @@ -277,7 +281,7 @@ def transport_format_list_len(pkt: 'dict[str, Any]') -> 'int': | TF type #1 | TF type #2 / -- so the list's byte length *is* ``Length``, with nothing to subtract. - Subtracting 2 anyway, as the three genuine two-octet-prefix sites do, + Subtracting 2 anyway, as the genuine two-octet-prefix sites do, would drop the trailing two octets of *every* non-empty list on parse, and reject the parameter's own legitimate empty-list encoding (``Length = 0``, ``formats = []``) as malformed. @@ -286,7 +290,7 @@ def transport_format_list_len(pkt: 'dict[str, Any]') -> 'int': two octets -- the same width the base :class:`Parameter` class uses for its own ``type`` field, since a TF type *is* a HIP parameter type number -- which is why ``formats``' ``item_type`` is - ``EnumField(length=2, ...)``, the width of the items at all three of + ``EnumField(length=2, ...)``, the width of the items at every one of :func:`two_octet_prefix_list_len`'s call sites as well. This function only answers how many *bytes* the list occupies; getting that number right and the item width wrong -- one octet instead of two -- would still @@ -440,9 +444,9 @@ def parameter_padding_len(pkt: 'dict[str, Any]') -> 'int': #: Packet-context key under which :class:`LocatorSetParameter` keeps its own #: ``Length`` for the benefit of :func:`locator_set_padding_len`. #: -#: It exists because ``LOCATOR_SET`` is the one parameter in this module whose -#: contents are a :class:`~pcapkit.corekit.fields.collections.ListField` of -#: nested schemas, and a nested schema packed through one **shares the +#: It exists because the contents of ``LOCATOR_SET`` are a +#: :class:`~pcapkit.corekit.fields.collections.ListField` of nested schemas, +#: and a nested schema packed through one **shares the #: enclosing packet context**: :meth:`Schema.pack #: ` opens with #: ``packet.update(self.__dict__)``, and :class:`Locator` declares a ``len`` of @@ -528,7 +532,14 @@ def __init__(self, type: 'Enum_Parameter', len: 'int', value: 'bytes') -> 'None' @schema_final class ESPInfoParameter(Parameter, code=Enum_Parameter.ESP_INFO): - """Header schema for HIP ``ESP_INFO`` parameters.""" + """Header schema for HIP ``ESP_INFO`` parameters. + + .. versionchanged:: 1.5.0 + + ``reserved`` is read as an :obj:`int` rather than as zero padding + :obj:`bytes`, and written back as given (:issue:`1328`). + + """ #: Reserved, "zero when sent, ignored when received" (:rfc:`7402#section-5.1.1`). #: Read as a value rather than as padding, so that re-serialising a parsed diff --git a/pcapkit/protocols/schema/internet/ipv4.py b/pcapkit/protocols/schema/internet/ipv4.py index cdcd084278..af2d589578 100644 --- a/pcapkit/protocols/schema/internet/ipv4.py +++ b/pcapkit/protocols/schema/internet/ipv4.py @@ -179,8 +179,8 @@ def quick_start_option_length(schema: 'Type[QSOption]') -> 'int': # a ``PaddingField`` with a length callback, which reports 1024. So the # callback's presence is the only reliable signal, and reaching for it is # worth one private access. A :class:`~pcapkit.corekit.fields.misc.SwitchField` - # is named separately because it is the one field whose width is dynamic - # *without* a length callback: it carries a + # is named separately because its width is dynamic *without* a length + # callback: it carries a # :class:`~pcapkit.corekit.fields.misc.NoValueField` until its own selector # resolves it, and so reports a width of zero rather than refusing to # answer. That is exactly what :class:`_QSOption` -- the outer wrapper, @@ -202,6 +202,8 @@ def quick_start_data_selector(pkt: 'dict[str, Any]') -> 'Field': pkt: Packet data. Returns: + The field for the option data, chosen by the QS function. + * If ``func`` is ``0``, returns a :class:`~pcapkit.corekit.fields.misc.SchemaField` wrapped :class:`~pcapkit.protocols.schema.internet.ipv4.QuickStartRequestOption` instance. @@ -222,7 +224,7 @@ def quick_start_data_selector(pkt: 'dict[str, Any]') -> 'Field': """ # NOTE: The enumeration admits the whole four-bit range, ``0`` to ``15``, # as an ``Unassigned`` member for every code but ``0`` and ``8``, and only - # those two have a schema registered. A value past four bits, which only a + # those have a schema registered. A value past four bits, which only a # caller-built packet can carry, fails the lookup and is just as unassigned. try: func = Enum_QSFunction.get(pkt['flags']['func']) @@ -318,7 +320,7 @@ class LSROption(Option, code=Enum_OptionNumber.LSR): #: Pointer. pointer: 'int' = UInt8Field() - #: Route: every address slot of the option, those before the pointer and + #: Route, i.e. every address slot of the option, those before the pointer and #: those at or beyond it alike. route: 'list[IPv4Address]' = ListField( length=lambda pkt: max(pkt['length'] - 3, 0) // 4 * 4, @@ -344,7 +346,7 @@ class TSOption(Option, code=Enum_OptionNumber.TS): 'oflw': (0, 4), 'flag': (4, 4), }) - #: Timestamps and internet addresses: every 32-bit slot of the option, + #: Timestamps and internet addresses, i.e. every 32-bit slot of the option, #: those before the pointer and those at or beyond it alike. ts_data: 'list[int]' = ListField( length=lambda pkt: max(pkt['length'] - 4, 0) // 4 * 4, @@ -468,7 +470,7 @@ def post_process(self, packet: 'dict[str, Any]') -> 'Schema': # NOTE: The keyword is ``ts_data``, the name of the field above, not # ``data``. ``data`` is the *derived* attribute :meth:`post_process` - # writes and is declared three lines up; passing it as a constructor + # writes and is declared just above; passing it as a constructor # argument would drop the timestamps in silence, because # :meth:`Schema.__update__ # ` answers an unknown @@ -500,7 +502,7 @@ class RROption(Option, code=Enum_OptionNumber.RR): #: Pointer. pointer: 'int' = UInt8Field() - #: Route: every address slot of the option, those before the pointer and + #: Route, i.e. every address slot of the option, those before the pointer and #: those at or beyond it alike. route: 'list[IPv4Address]' = ListField( length=lambda pkt: max(pkt['length'] - 3, 0) // 4 * 4, @@ -534,7 +536,7 @@ class SSROption(Option, code=Enum_OptionNumber.SSR): #: Pointer. pointer: 'int' = UInt8Field() - #: Route: every address slot of the option, those before the pointer and + #: Route, i.e. every address slot of the option, those before the pointer and #: those at or beyond it alike. route: 'list[IPv4Address]' = ListField( length=lambda pkt: max(pkt['length'] - 3, 0) // 4 * 4, @@ -696,7 +698,14 @@ def __init__(self, type: 'Enum_OptionNumber', length: 'int', flags: 'QuickStartF @schema_final class IPv4(Schema): - """Header schema for IPv4 packet.""" + """Header schema for IPv4 packet. + + .. versionchanged:: 1.5.0 + + New field :attr:`trailer`, which keeps the octets captured past the + Total Length rather than dropping them (:issue:`1209`). + + """ #: Version and header length. vihl: 'VerIHLField' = BitField(length=1, namespace={ diff --git a/pcapkit/protocols/schema/internet/ipv6.py b/pcapkit/protocols/schema/internet/ipv6.py index 524d7fd58d..92d3a353a5 100644 --- a/pcapkit/protocols/schema/internet/ipv6.py +++ b/pcapkit/protocols/schema/internet/ipv6.py @@ -21,9 +21,9 @@ from pcapkit.protocols.protocol import ProtocolBase -#: The most octets a 16-bit IP length field declares: IPv4's Total Length, the -#: header included (:rfc:`791#section-3.1`), and IPv6's Payload Length, the -#: fixed header excluded (:rfc:`8200#section-3`) -- so a longer IPv6 payload is +#: The most octets a 16-bit IP length field declares, for IPv4's Total Length +#: the header included (:rfc:`791#section-3.1`) and for IPv6's Payload Length +#: the fixed header excluded (:rfc:`8200#section-3`); a longer IPv6 payload is #: a jumbogram, whose Jumbo Payload Length must exceed it (:rfc:`2675#section-2`). #: The one home of this value (:issue:`1739`), here so that the schema's #: jumbogram check and the protocol modules read the same one; it is fixed by @@ -48,7 +48,14 @@ @schema_final class IPv6(Schema): - """Header schema for IPv6 packet.""" + """Header schema for IPv6 packet. + + .. versionchanged:: 1.5.0 + + New field :attr:`trailer`, which keeps the octets captured past the + Payload Length rather than dropping them (:issue:`1209`). + + """ #: Version, traffic class and flow label. #: diff --git a/pcapkit/protocols/schema/internet/ipv6_option.py b/pcapkit/protocols/schema/internet/ipv6_option.py index 3ddb59ca88..9bb7f543d9 100644 --- a/pcapkit/protocols/schema/internet/ipv6_option.py +++ b/pcapkit/protocols/schema/internet/ipv6_option.py @@ -112,6 +112,8 @@ def smf_dpd_data_selector(pkt: 'dict[str, Any]', *, prefix: 'str', DPD mode to the schema that reads the option. Returns: + The field for the option data, chosen by the DPD mode. + * If ``mode`` is ``0``, returns a :class:`~pcapkit.corekit.fields.misc.SchemaField` wrapped ``SMFIdentificationBasedDPDOption`` instance. * If ``mode`` is ``1``, returns a :class:`~pcapkit.corekit.fields.misc.SchemaField` @@ -152,11 +154,13 @@ def smf_i_dpd_tid_selector(pkt: 'dict[str, Any]', *, prefix: 'str') -> 'Field': prefix: Name of the extension header, leading the error message. Returns: + The field for the TaggerId, chosen by its type. + * If ``tid_type`` is ``0`` (``NULL``), returns a :class:`~pcapkit.corekit.fields.misc.NoValueField` instance. - * If ``tid_type`` is ``2`` (``IPv4``), returns a + * If ``tid_type`` is ``2`` (``IPv4``), returns an :class:`~pcapkit.corekit.fields.ipaddress.IPv4AddressField` instance. - * If ``tid_type`` is ``3`` (``IPv6``), returns a + * If ``tid_type`` is ``3`` (``IPv6``), returns an :class:`~pcapkit.corekit.fields.ipaddress.IPv6AddressField` instance. * Otherwise, returns a :class:`~pcapkit.corekit.fields.strings.BytesField` instance. @@ -197,6 +201,8 @@ def quick_start_data_selector(pkt: 'dict[str, Any]', *, base: 'EnumMeta', unassigned: The header's ``UnassignedOption`` schema. Returns: + The field for the option data, chosen by the QS function. + * If ``func`` is ``0``, returns a :class:`~pcapkit.corekit.fields.misc.SchemaField` wrapped ``QuickStartRequestOption`` instance. * If ``func`` is ``8``, returns a :class:`~pcapkit.corekit.fields.misc.SchemaField` @@ -238,13 +244,14 @@ def pad_opt_data_len(pkt: 'dict[str, Any]') -> 'int': the option's ``len`` field is declared as a :class:`~pcapkit.corekit.fields.misc.ConditionalField` and is skipped for it. A skipped conditional field is *recorded* in the packet data as - :data:`~pcapkit.corekit.sentinels.NO_VALUE`, rather than being left - out of it, so the test below has to be on the **value** and not on the - presence of the key: ``pkt.get('len', 0)`` on its own hands that - :obj:`~pcapkit.corekit.sentinels.NoValueType` straight to + :data:`~pcapkit.corekit.sentinels.NO_VALUE`, rather than being left out + of it, so the test this function makes has to be on the **value** and + not on the presence of the key: ``pkt.get('len', 0)`` on its own hands + that :obj:`~pcapkit.corekit.sentinels.NoValueType` straight to :class:`~pcapkit.corekit.fields.strings.PaddingField`, where it becomes an unusable :mod:`struct` template and surfaces much later as an opaque - :exc:`struct.error`, which is how a ``Pad1`` option would fail to parse. + :exc:`struct.error`, which is how a ``Pad1`` option would fail to + parse. """ length = pkt.get('len', 0) diff --git a/pcapkit/protocols/schema/internet/ipx.py b/pcapkit/protocols/schema/internet/ipx.py index 16b27f4f71..0d69a3de57 100644 --- a/pcapkit/protocols/schema/internet/ipx.py +++ b/pcapkit/protocols/schema/internet/ipx.py @@ -18,11 +18,18 @@ @schema_final class IPX(Schema): - """Header schema for IPX packet.""" + """Header schema for IPX packet. + + .. versionchanged:: 1.5.0 + + New field :attr:`trailer`, which keeps the octets captured past the + Packet Length rather than dropping them (:issue:`1433`). + + """ #: Checksum. chksum: 'bytes' = BytesField(length=2) - #: Packet length (header includes). + #: Packet length (header included). len: 'int' = UInt16Field() #: Transport control (hop count). count: 'int' = UInt8Field() diff --git a/pcapkit/protocols/schema/internet/mh.py b/pcapkit/protocols/schema/internet/mh.py index b4d7f38bfd..0d24131e75 100644 --- a/pcapkit/protocols/schema/internet/mh.py +++ b/pcapkit/protocols/schema/internet/mh.py @@ -408,17 +408,18 @@ def pad_opt_data_len(pkt: 'dict[str, Any]') -> 'int': Note: A ``Pad1`` option is a single octet with neither an ``Option Length`` - field nor any option data (c.f. :rfc:`6275#section-6.2.2`), which is why - :attr:`Option.length` is declared as a + field nor any option data (c.f. :rfc:`6275#section-6.2.2`), which is + why :attr:`Option.length` is declared as a :class:`~pcapkit.corekit.fields.misc.ConditionalField` and is skipped for it. A skipped conditional field is *recorded* in the packet data as - :data:`~pcapkit.corekit.sentinels.NO_VALUE`, rather than being left - out of it, so the test below has to be on the **value** and not on the - presence of the key: ``pkt.get('length', 0)`` on its own hands that - :class:`~pcapkit.corekit.sentinels.NoValueType` straight to + :data:`~pcapkit.corekit.sentinels.NO_VALUE`, rather than being left out + of it, so the test this function makes has to be on the **value** and + not on the presence of the key: ``pkt.get('length', 0)`` on its own + hands that :class:`~pcapkit.corekit.sentinels.NoValueType` straight to :class:`~pcapkit.corekit.fields.strings.PaddingField`, where it becomes an unusable :mod:`struct` template and surfaces much later as an opaque - :exc:`struct.error`, which is how a ``Pad1`` option would fail to parse. + :exc:`struct.error`, which is how a ``Pad1`` option would fail to + parse. """ length = pkt.get('length', 0) @@ -592,7 +593,14 @@ def fb_code_selector(pkt: 'dict[str, Any]') -> 'Field': @schema_final class MH(Schema): - """Header schema for MH packets.""" + """Header schema for MH packets. + + .. versionchanged:: 1.5.0 + + ``reserved`` is read as an :obj:`int` rather than as zero padding + :obj:`bytes`, and written back as given (:issue:`1329`). + + """ #: Next header. next: 'Enum_TransType' = EnumField(length=1, namespace=Enum_TransType) @@ -2129,10 +2137,10 @@ class BitRateAttribute(QoSAttribute, code=[Enum_QoSAttribute.Per_MN_Agg_Max_DL_B """Header schema for the MH Quality-of-Service attributes that carry a bare bit rate. - Six of the eleven registered attributes share this shape -- two reserved - octets and a 32-bit rate [:rfc:`7222#section-4.2.1`, - :rfc:`7222#section-4.2.2`, :rfc:`7222#section-4.2.6` to - :rfc:`7222#section-4.2.9`] -- so one schema is registered against all six. + Several registered attributes share this shape -- two reserved octets and + a 32-bit rate [:rfc:`7222#section-4.2.1`, :rfc:`7222#section-4.2.2`, + :rfc:`7222#section-4.2.6` to :rfc:`7222#section-4.2.9`] -- so one schema is + registered against each of them. Note: The rate is in **bits** per second, not kilobits @@ -2562,7 +2570,14 @@ def __init__(self, data: 'bytes') -> 'None': ... @schema_final class BindingRefreshRequestMessage(Packet, code=Enum_Packet.Binding_Refresh_Request): - """Header schema for MH Binding Refresh Request (BRR) message.""" + """Header schema for MH Binding Refresh Request (BRR) message. + + .. versionchanged:: 1.5.0 + + ``reserved`` is read as an :obj:`int` rather than as zero padding + :obj:`bytes`, and written back as given (:issue:`1329`). + + """ #: Reserved. reserved: 'int' = UInt16Field(default=0)