diff --git a/docs/source/pcapkit/protocols/internet/esp.rst b/docs/source/pcapkit/protocols/internet/esp.rst index 83d3c92ec8..eb356537ea 100644 --- a/docs/source/pcapkit/protocols/internet/esp.rst +++ b/docs/source/pcapkit/protocols/internet/esp.rst @@ -256,7 +256,7 @@ Known Limitations stateless parser cannot recover them; they are required both for the ICV computation and for the AEAD associated data. An ESN protected packet therefore fails the integrity check *cleanly* rather than being decoded. -* **Traffic Flow Confidentiality (TFC) padding (§2.4) is not detected.** +* **Traffic Flow Confidentiality (TFC) padding (§2.7) is not detected.** TFC padding is indistinguishable from real payload without inspecting the inner protocol's own length field, so it is handed to the next layer as part of the plaintext. diff --git a/docs/source/pcapkit/protocols/internet/ipv6_ext.rst b/docs/source/pcapkit/protocols/internet/ipv6_ext.rst index 4d607ddd30..ccb74198cd 100644 --- a/docs/source/pcapkit/protocols/internet/ipv6_ext.rst +++ b/docs/source/pcapkit/protocols/internet/ipv6_ext.rst @@ -8,10 +8,12 @@ IPv6-Ext - IPv6 Extension Header 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 +:attr:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext.payload` and :attr:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext.protochain` -unavailable on a header parsed as part of an IPv6 chain. It also implements a +unavailable on a header parsed as part of an IPv6 chain. Each subclass guards +its own ``protocol`` the same way; this class's +:attr:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext.protocol` deliberately +does not. It also implements a **generic** extractor for IPv6 extension headers [*]_, standing in whenever a header's own dedicated parser is unavailable or has failed. :rfc:`6564#section-4` requires, with an RFC 2119 **MUST**, that any IPv6 extension header defined since @@ -157,20 +159,23 @@ left, then read only what is left. See :func:`pcapkit.protocols.schema.misc.pcapng.bounded_area` and :meth:`pcapkit.protocols.misc.pcapng.PCAPNG.read`. -This class follows that convention's *warning* -(:class:`~pcapkit.utilities.warnings.SchemaWarning`) and deliberately diverges -on the *action*. Clipping is right when the declared length only governs how -much of the current object to read, since there is always a well-defined -"what is left" to fall back to. Here the declared length also decides *where -the next header starts*; a clipped skip distance points at whatever bytes -end the buffer, which are not a header. Continuing from there would -fabricate a layer and record a false entry in +This class follows that convention's *warning* -- a +:class:`~pcapkit.utilities.warnings.SchemaWarning`, as +:func:`~pcapkit.protocols.schema.misc.pcapng.bounded_area` emits -- and +deliberately diverges on the *action*. Clipping is right when the declared +length only governs how much of the current object to read, since there is +always a well-defined "what is left" to fall back to. Here the declared length +also decides *where the next header starts*; a clipped skip distance points at +whatever bytes end the buffer, which are not a header. Continuing from there +would fabricate a layer and record a false entry in :class:`~pcapkit.corekit.protochain.ProtoChain`, which reads as a parsed fact -rather than a guess. So :meth:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext.read` warns in the same -wording as the sites above, then **stops the walk**: this instance absorbs -every remaining octet and reports :data:`None` for ``next``, which the -caller's loop already reads as "no more extension headers". The chain ends -honestly at the bad header instead of inventing what follows it. +rather than a guess. So :meth:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext.read` +warns in the same form as +:func:`~pcapkit.protocols.schema.misc.pcapng.bounded_area`, then **stops the +walk**: this instance absorbs every remaining octet and reports :data:`None` +for ``next``, which the caller's loop already reads as "no more extension +headers". The chain ends honestly at the bad header instead of inventing what +follows it. Header Schemas -------------- diff --git a/pcapkit/protocols/data/internet/__init__.py b/pcapkit/protocols/data/internet/__init__.py index 6344424647..1f89dc2f17 100644 --- a/pcapkit/protocols/data/internet/__init__.py +++ b/pcapkit/protocols/data/internet/__init__.py @@ -133,7 +133,7 @@ # IPv6 Fragment Header from pcapkit.protocols.data.internet.ipv6_frag import IPv6_Frag -# IPv6 Extension Header (base + generic fallback) +# IPv6 Extension Header (generic fallback) from pcapkit.protocols.data.internet.ipv6_ext import IPv6_Ext # IPv6 Destination Options @@ -424,7 +424,7 @@ # IPv6 Fragment Header 'IPv6_Frag', - # IPv6 Extension Header (base + generic fallback) + # IPv6 Extension Header (generic fallback) 'IPv6_Ext', # IPv6 Destination Options Header diff --git a/pcapkit/protocols/data/internet/esp.py b/pcapkit/protocols/data/internet/esp.py index aad8981329..3d12799771 100644 --- a/pcapkit/protocols/data/internet/esp.py +++ b/pcapkit/protocols/data/internet/esp.py @@ -43,10 +43,12 @@ class ESP(Protocol): #: byte of the packet that ESP owns. length: 'int' #: Payload data exactly as transmitted -- ciphertext, prefixed by any - #: cryptographic synchronisation data (IV) -- excluding the ICV. + #: cryptographic synchronisation data (IV) -- excluding the ICV whenever + #: :attr:`icv` holds it. payload_data: 'bytes' - #: Integrity check value as transmitted; empty when absent, or when no - #: Security Association was available to say how long it is. + #: Integrity check value as transmitted; empty when absent, when no + #: Security Association was available to say how long it is, or when the + #: packet is shorter than the ICV its Security Association declares. icv: 'bytes' #: Outcome of the decryption and integrity check. status: 'ESPStatus' diff --git a/pcapkit/protocols/data/internet/hip.py b/pcapkit/protocols/data/internet/hip.py index 25f33a946a..b9c38596a4 100644 --- a/pcapkit/protocols/data/internet/hip.py +++ b/pcapkit/protocols/data/internet/hip.py @@ -87,7 +87,8 @@ class Parameter(Data): type: 'Enum_Parameter' #: Critical flag. critical: 'bool' - #: Content length. + #: Parameter length, in octets -- the whole record, including ``Type``, + #: ``Length`` and ``Padding`` (:rfc:`7401#section-5.2.1`). length: 'int' if TYPE_CHECKING: @@ -130,7 +131,7 @@ class HIP(Protocol): version_fixed: 'int' #: Checksum. chksum: 'bytes' - #: Control + #: Control. control: 'Control' #: Sender's host identity tag. shit: 'int' @@ -416,7 +417,7 @@ class NATTraversalModeParameter(Parameter): #: The two reserved octets, as captured. reserved: 'bytes' - #: Mode IDs + #: Mode IDs. mode_id: 'tuple[NATTraversal, ...]' if TYPE_CHECKING: @@ -718,7 +719,7 @@ class PayloadMICParameter(Parameter): #: The three reserved octets, as captured. reserved: 'bytes' - #: Next header + #: Next header. next: 'TransType' #: Payload data. payload: 'bytes' diff --git a/pcapkit/protocols/data/internet/hopopt.py b/pcapkit/protocols/data/internet/hopopt.py index e984f2beb4..d5fb1afebb 100644 --- a/pcapkit/protocols/data/internet/hopopt.py +++ b/pcapkit/protocols/data/internet/hopopt.py @@ -137,7 +137,8 @@ class CALIPSOOption(Option): #: CALIPSO domain of interpretation. domain: 'int' - #: Compartment length. + #: Compartment bitmap length, in octets: four times the ``Cmpt Length`` + #: field, which counts 32-bit words. cmpt_len: 'int' #: Sensitivity level. level: 'int' @@ -166,7 +167,8 @@ class SMFIdentificationBasedDPDOption(SMFDPDOption): #: TaggerID type. tid_type: 'TaggerID' - #: TaggerID length. + #: ``TidLen`` field as on the wire: the TaggerID length in octets minus + #: one, or ``0`` when there is no TaggerID [:rfc:`6621#section-6.1.1`]. tid_len: 'int' #: TaggerID. tid: 'Optional[bytes | IPv4Address | IPv6Address]' diff --git a/pcapkit/protocols/data/internet/ipv4.py b/pcapkit/protocols/data/internet/ipv4.py index f87ed2420d..f10d1782e6 100644 --- a/pcapkit/protocols/data/internet/ipv4.py +++ b/pcapkit/protocols/data/internet/ipv4.py @@ -112,7 +112,7 @@ class IPv4(Protocol): #: Version. version: 'Literal[4]' - #: Internet header length. + #: Internet header length, in octets. hdr_len: 'int' #: Type of service. tos: 'ToSField' @@ -122,7 +122,7 @@ class IPv4(Protocol): id: 'int' #: Flags. flags: 'Flags' - #: Fragment offset. + #: Fragment offset, in octets. offset: 'int' #: Time to live. ttl: 'timedelta' @@ -150,7 +150,7 @@ def __init__(self, version: 'Literal[4]', hdr_len: 'int', tos: 'ToSField', len: class OptionType(Data): """Data model for IPv4 option type data.""" - #: Change flag. + #: Copied flag, i.e. whether the option is copied into every fragment. change: 'bool' #: Option class. #class: 'int' diff --git a/pcapkit/protocols/data/internet/ipv6_opts.py b/pcapkit/protocols/data/internet/ipv6_opts.py index 2d8f763f55..7c3ef8b002 100644 --- a/pcapkit/protocols/data/internet/ipv6_opts.py +++ b/pcapkit/protocols/data/internet/ipv6_opts.py @@ -142,7 +142,8 @@ class CALIPSOOption(Option): #: CALIPSO domain of interpretation. domain: 'int' - #: Compartment length. + #: Compartment bitmap length, in octets: four times the ``Cmpt Length`` + #: field, which counts 32-bit words. cmpt_len: 'int' #: Sensitivity level. level: 'int' @@ -172,7 +173,8 @@ class SMFIdentificationBasedDPDOption(SMFDPDOption): #: TaggerID type. tid_type: 'TaggerID' - #: TaggerID length. + #: ``TidLen`` field as on the wire: the TaggerID length in octets minus + #: one, or ``0`` when there is no TaggerID [:rfc:`6621#section-6.1.1`]. tid_len: 'int' #: TaggerID. tid: 'Optional[bytes | IPv4Address | IPv6Address]' diff --git a/pcapkit/protocols/internet/esp.py b/pcapkit/protocols/internet/esp.py index db7f6d859c..0e91f8a6a6 100644 --- a/pcapkit/protocols/internet/esp.py +++ b/pcapkit/protocols/internet/esp.py @@ -146,7 +146,7 @@ stateless parser cannot recover them; they are required both for the ICV computation and for the AEAD associated data. An ESN protected packet therefore fails the integrity check *cleanly* rather than being decoded. -* **Traffic Flow Confidentiality (TFC) padding (§2.4) is not detected.** +* **Traffic Flow Confidentiality (TFC) padding (§2.7) is not detected.** TFC padding is indistinguishable from real payload without inspecting the inner protocol's own length field, so it is handed to the next layer as part of the plaintext. @@ -1251,8 +1251,11 @@ def make(self, Raises: ProtocolError: If ``encrypt`` is :data:`True` and no Security - Association is available for ``spi``, or if ``pad_len`` does - not satisfy the algorithm's alignment requirement. + Association is available for ``spi``, if ``pad_len`` is + outside ``0`` to ``255`` or does not satisfy the algorithm's + alignment requirement, or if + :meth:`SecurityAssociation.encrypt` rejects ``iv`` or cannot + run without |cryptography|_. """ if not encrypt: diff --git a/pcapkit/protocols/internet/hip.py b/pcapkit/protocols/internet/hip.py index 26b9e20790..1ac80fe3a0 100644 --- a/pcapkit/protocols/internet/hip.py +++ b/pcapkit/protocols/internet/hip.py @@ -1758,7 +1758,7 @@ def _read_param_cert(self, schema: 'Schema_CertParameter', *, version: 'int', # options: 'Parameter') -> 'Data_CertParameter': # pylint: disable=unused-argument """Read HIP ``CERT`` parameter. - Structure of HIP ``CERT`` parameter [:rfc:`7401`]: + Structure of HIP ``CERT`` parameter [:rfc:`8002`]: .. code-block:: text @@ -3028,7 +3028,7 @@ def _read_param_relay_hmac(self, schema: 'Schema_RelayHMACParameter', version: ' Structure of HIP ``RELAY_HMAC`` parameter [:rfc:`5770`]: - .. code-block:: + .. code-block:: text 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 @@ -3162,7 +3162,7 @@ def _make_param_esp_info(self, code: 'Enum_Parameter', param: 'Optional[Data_ESP code: parameter code param: parameter data version: HIP protocol version - reserved: reserved octets, zero when sent (:rfc:`7402#section-5.1.1`) + reserved: reserved octets (:rfc:`7402#section-5.1.1`) index: KEYMAT index old_spi: old SPI new_spi: new SPI @@ -3355,8 +3355,9 @@ def _make_puzzle_lifetime(code: 'Enum_Parameter', version: 'int', """Encode a ``PUZZLE`` ``Lifetime`` octet. :rfc:`7401#section-5.2.4` gives the puzzle lifetime as ``2^(value - 32)`` - seconds -- and it is the *only* place either RFC defines that encoding, so - it applies to ``PUZZLE`` alone. ``SOLUTION``'s octet at the same offset is + seconds -- and the ``PUZZLE`` section is the only place either RFC defines + that encoding (:rfc:`5201#section-5.2.4` identically), so it applies to + ``PUZZLE`` alone. ``SOLUTION``'s octet at the same offset is ``Reserved`` (:rfc:`7401#section-5.2.5`) and does not come through here. Args: @@ -4022,6 +4023,12 @@ def _make_param_host_id(self, code: 'Enum_Parameter', param: 'Optional[Data_Host Returns: HIP parameter schema. + Raises: + ProtocolError: If neither ``hi`` nor ``param`` is given and + ``hi_curve`` is not an ECDSA, ECDSA_LOW or EdDSA curve, or if + ``hi`` is a :class:`~pcapkit.protocols.data.internet.hip.HostIdentity` + whose ``curve`` is none of those. + """ if param is not None: hi = param.hi @@ -5082,7 +5089,8 @@ def _make_param_relay_from(self, code: 'Enum_Parameter', param: 'Optional[Data_R protocol_default: default transport protocol protocol_namespace: transport protocol namespace protocol_reversed: reverse transport protocol namespace - address: relay address + address: the address the relay server received the relayed + packet from (:rfc:`5770`) reserved: the reserved octet, truncated or zero-filled to 1 octet **kwargs: arbitrary keyword arguments @@ -5130,7 +5138,8 @@ def _make_param_relay_to(self, code: 'Enum_Parameter', param: 'Optional[Data_Rel protocol_default: default transport protocol protocol_namespace: transport protocol namespace protocol_reversed: reverse transport protocol namespace - address: relay address + address: the address the relay server forwards the packet to + (:rfc:`5770`) reserved: the reserved octet, truncated or zero-filled to 1 octet **kwargs: arbitrary keyword arguments @@ -5247,7 +5256,8 @@ def _make_param_from(self, code: 'Enum_Parameter', param: 'Optional[Data_FromPar code: parameter code param: parameter data version: HIP protocol version - address: relay address + address: the original source address of the relayed packet + (:rfc:`8004`) **kwargs: arbitrary keyword arguments Returns: diff --git a/pcapkit/protocols/internet/hopopt.py b/pcapkit/protocols/internet/hopopt.py index 526ce27559..81376f5027 100644 --- a/pcapkit/protocols/internet/hopopt.py +++ b/pcapkit/protocols/internet/hopopt.py @@ -1512,6 +1512,10 @@ def _make_opt_pad(self, code: 'Enum_Option', opt: 'Optional[Data_PadOption]' = N Returns: Constructed option schema. + Warns: + ProtocolWarning: If ``code`` is ``Pad1`` but ``length`` is not + ``0``; a ``PadN`` option is made instead. + Note: :attr:`Data_PadOption.length ` counts the @@ -1817,6 +1821,10 @@ def _make_opt_pdm(self, code: 'Enum_Option', opt: 'Optional[Data_PDMOption]' = N Returns: Constructed option schema. + Warns: + ProtocolWarning: If a scale exceeds ``255``, too large for its + one-octet field. + """ if opt is not None: psntp = opt.psntp @@ -1890,6 +1898,10 @@ def _make_opt_qs(self, code: 'Enum_Option', opt: 'Optional[Data_QuickStartOption Constructed option schema; an unassigned option schema if ``opt`` is a Quick-Start option parsed as unassigned, see :meth:`_read_opt_qs`. + Raises: + ProtocolError: If the QS function is neither a Quick-Start Request + nor a Report of Approved Rate. + """ if isinstance(opt, Data_UnassignedOption): return self._make_opt_none(code, opt) diff --git a/pcapkit/protocols/internet/ipv4.py b/pcapkit/protocols/internet/ipv4.py index a896a6e45a..511996f78a 100644 --- a/pcapkit/protocols/internet/ipv4.py +++ b/pcapkit/protocols/internet/ipv4.py @@ -307,8 +307,8 @@ def read(self, length: 'Optional[int]' = None, *, # pylint: disable=unused-argu Parsed packet data. Raises: - ProtocolError: If the version is not 4, or if ``ihl`` declares - more header than the data holds. + ProtocolError: If the version is not 4, if ``ihl`` declares + more header than the data holds, or if an option is malformed. """ if length is None: @@ -477,7 +477,8 @@ def make(self, reserved: Reserved flag bit, i.e. the high-order bit of the flags. df: Don't fragment flag. mf: More fragments flag. - offset: Fragment offset. + offset: Fragment offset, in the 8-octet units of the wire field; + the parsed ``offset`` is in octets, see :meth:`_make_data`. ttl: Time to live of the packet. protocol: Payload protocol of the packet. protocol_default: Default value of ``protocol``. @@ -1708,6 +1709,9 @@ def _make_opt_ts(self, kind: 'Enum_OptionNumber', option: 'Optional[Data_TSOptio Constructed option schema. Raises: + ProtocolError: If ``option`` is not given and ``timestamp`` is + neither a :obj:`list` nor a :obj:`dict`, which includes the + default :data:`None`. FieldValueError: If an address is a :obj:`bool` or is not a valid IPv4 address. diff --git a/pcapkit/protocols/internet/ipv6_ext.py b/pcapkit/protocols/internet/ipv6_ext.py index d32b4af410..a89f4dafe2 100644 --- a/pcapkit/protocols/internet/ipv6_ext.py +++ b/pcapkit/protocols/internet/ipv6_ext.py @@ -97,9 +97,9 @@ would default it to plain :class:`~pcapkit.protocols.misc.raw.Raw`. That has no ``next`` field, so the walk in :meth:`IPv6._decode_next_layer ` - would crash on it (GitHub issue :issue:`891`). This module registers itself - for that code instead (see the bottom of the module), so dispatch reaches a - working generic parser directly. The registration is global, shared by + would crash on it (GitHub issue :issue:`891`). This module registers this + class for that code instead (see the bottom of the module), so dispatch + reaches a working generic parser directly. The registration is global, shared by every :class:`~pcapkit.protocols.internet.internet.Internet` subclass, so :meth:`__post_init__` gates on ``version == 6`` to keep it from also activating for an IPv4 payload that carries protocol number 140; see its @@ -124,20 +124,22 @@ :func:`pcapkit.protocols.schema.misc.pcapng.bounded_area` and :meth:`pcapkit.protocols.misc.pcapng.PCAPNG.read`. -This class follows that convention's *warning* -(:class:`~pcapkit.utilities.warnings.SchemaWarning`) and deliberately diverges -on the *action*. Clipping is right when the declared length only governs how -much of the current object to read, since there is always a well-defined -"what is left" to fall back to. Here the declared length also decides *where -the next header starts*; a clipped skip distance points at whatever bytes -end the buffer, which are not a header. Continuing from there would -fabricate a layer and record a false entry in +This class follows that convention's *warning* -- a +:class:`~pcapkit.utilities.warnings.SchemaWarning`, as +:func:`~pcapkit.protocols.schema.misc.pcapng.bounded_area` emits -- and +deliberately diverges on the *action*. Clipping is right when the declared +length only governs how much of the current object to read, since there is +always a well-defined "what is left" to fall back to. Here the declared length +also decides *where the next header starts*; a clipped skip distance points at +whatever bytes end the buffer, which are not a header. Continuing from there +would fabricate a layer and record a false entry in :class:`~pcapkit.corekit.protochain.ProtoChain`, which reads as a parsed fact -rather than a guess. So :meth:`read` warns in the same wording as the sites -above, then **stops the walk**: this instance absorbs every remaining octet -and reports :data:`None` for ``next``, which the caller's loop already reads -as "no more extension headers". The chain ends honestly at the bad header -instead of inventing what follows it. +rather than a guess. So :meth:`read` warns in the same form as +:func:`~pcapkit.protocols.schema.misc.pcapng.bounded_area`, then **stops the +walk**: this instance absorbs every remaining octet and reports :data:`None` +for ``next``, which the caller's loop already reads as "no more extension +headers". The chain ends honestly at the bad header instead of inventing what +follows it. """ from typing import TYPE_CHECKING, Generic, cast, overload @@ -162,9 +164,10 @@ 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 every implemented one records it under this - name -- which is what lets :attr:`IPv6_Ext.next` be a *shared* member + :rfc:`8200#section-4` identifies each extension header by the Next + Header field of the header before it, and every implemented one + records its own Next Header under this name, ESP from its decrypted + trailer -- 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 purpose: ``length`` is deliberately absent, because @@ -208,8 +211,8 @@ class keywords implement it. :class:`~pcapkit.protocols.internet.mh.MH`, :class:`~pcapkit.protocols.internet.ah.AH` and :class:`~pcapkit.protocols.internet.esp.ESP`), which is what the - ``_extf`` guards on :attr:`payload`, :attr:`protocol` and - :attr:`protochain` are for. + ``_extf`` guards on :attr:`payload` and :attr:`protochain`, and each + subclass's own on ``protocol``, are for. It is generic in its data and schema types, like :class:`~pcapkit.protocols.internet.ipsec.IPsec`, another base in this @@ -260,8 +263,9 @@ def alias(self) -> 'str': ` and :attr:`IPv6_Opts.alias `, for the same reason: :meth:`IPv6._decode_next_layer - ` builds the - packet-dict key with ``self.alias.lstrip('IPv6-').lower()``, and + ` keys the + packet dict through ``IPv6._exthdr_key``, which is + ``alias.lstrip('IPv6-').lower()``, and :meth:`str.lstrip` strips a character *set*, not a prefix. The default (class-name) alias ``'IPv6_Ext'`` would strip to ``'_Ext'`` and key the dict as ``_ext``; the hyphen makes every leading character (``I``, @@ -489,14 +493,17 @@ def make(self, *, **Declared, and keyword-only**, each for its own reason. 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 + because ``_check_construction_keywords()`` builds its allowlist + from the :func:`inspect.signature` of ``make`` and the other + construction methods of every class in the MRO, plus any + ``__keywords__``. 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 every subclass too. + ``__keywords__`` escape hatch would cover that too, with no + difference for subclasses: signatures and ``__keywords__`` are + both unioned down the MRO, so every subclass's allowlist takes + these names either way. They are keyword-*only* because this class is also a base and each subclass's ``make`` puts different names in the same positions. As diff --git a/pcapkit/protocols/internet/ipv6_opts.py b/pcapkit/protocols/internet/ipv6_opts.py index 1de33b7a80..fe62f868d4 100644 --- a/pcapkit/protocols/internet/ipv6_opts.py +++ b/pcapkit/protocols/internet/ipv6_opts.py @@ -1500,6 +1500,10 @@ def _make_opt_pad(self, code: 'Enum_Option', opt: 'Optional[Data_PadOption]' = N Returns: Constructed option schema. + Warns: + ProtocolWarning: If ``code`` is ``Pad1`` but ``length`` is not + ``0``; a ``PadN`` option is made instead. + Note: :attr:`Data_PadOption.length ` counts @@ -1805,6 +1809,10 @@ def _make_opt_pdm(self, code: 'Enum_Option', opt: 'Optional[Data_PDMOption]' = N Returns: Constructed option schema. + Warns: + ProtocolWarning: If a scale exceeds ``255``, too large for its + one-octet field. + """ if opt is not None: psntp = opt.psntp @@ -1878,6 +1886,10 @@ def _make_opt_qs(self, code: 'Enum_Option', opt: 'Optional[Data_QuickStartOption Constructed option schema; an unassigned option schema if ``opt`` is a Quick-Start option parsed as unassigned, see :meth:`_read_opt_qs`. + Raises: + ProtocolError: If the QS function is neither a Quick-Start Request + nor a Report of Approved Rate. + """ if isinstance(opt, Data_UnassignedOption): return self._make_opt_none(code, opt) diff --git a/pcapkit/protocols/internet/ipv6_route.py b/pcapkit/protocols/internet/ipv6_route.py index 3995614177..cf25b985d1 100644 --- a/pcapkit/protocols/internet/ipv6_route.py +++ b/pcapkit/protocols/internet/ipv6_route.py @@ -306,7 +306,12 @@ def make(self, Raises: FieldValueError: If ``dst`` is a :obj:`bool` (see - :func:`~pcapkit.corekit.fields.ipaddress.parse_ip_address`). + :func:`~pcapkit.corekit.fields.ipaddress.parse_ip_address`) + and ``data`` is a :obj:`dict` or a data model; ``dst`` is not + read for any other ``data``. + ProtocolError: If ``data`` is :obj:`bytes` that do not parse as + the routing data of ``type``, or is of none of the accepted + types. .. versionchanged:: 1.5.0 @@ -577,6 +582,9 @@ def _read_data_type_src(self, schema: 'Schema_SourceRoute', *, header: 'Schema_I Returns: Parsed route data. + Raises: + ProtocolError: If ``Hdr Ext Len`` is odd. + """ # ``header.length`` is ``Hdr Ext Len``, in 8-octet units (:rfc:`8200 # #section-4.4`), not octets. Each 16-octet address costs 2 of those @@ -673,6 +681,11 @@ def _read_data_type_rpl(self, schema: 'Schema_RPL', *, header: 'Schema_IPv6_Rout Returns: Parsed route data. + Raises: + ProtocolError: If ``Hdr Ext Len``, ``Pad``, ``CmprI`` and + ``CmprE`` do not give a whole number of addresses, at least + one (:rfc:`6554#section-4.2`). + """ # NOTE: ``header.length`` is ``Hdr Ext Len``, in the 8-octet units # :rfc:`6554#section-3` specifies, not octets, and the addresses are diff --git a/pcapkit/protocols/internet/mh.py b/pcapkit/protocols/internet/mh.py index 282c069602..03038b3e6f 100644 --- a/pcapkit/protocols/internet/mh.py +++ b/pcapkit/protocols/internet/mh.py @@ -2316,10 +2316,10 @@ 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 of these flags - are registered in the IANA *Handover Acknowledge Flags* registry, - hence all are parsed here. + ``U``, ``P`` and ``F`` flags carved out of it, and code values ``5``, + ``6``, ``131`` and ``132``, are added by :rfc:`5949#section-6.1.2`. + All of these flags are registered in the IANA *Handover Acknowledge + Flags* registry, hence all are parsed here. Args: schema: Parsed message type schema. @@ -2868,10 +2868,13 @@ def _mh_option_length(schema_length: 'int') -> 'int': Length fields"* -- so the whole option, which is what every ``_read_opt_*`` below reports back as the parsed option's own ``.length``, is two octets more. The read-side call sites share this - helper so the arithmetic lives in one place; a ``Pad1`` option -- the - one option with no ``Option Length`` field at all -- is its sole - exception (see the ``Note:`` on :meth:`_read_opt_pad` below). Do NOT - drop the ``+ 2``. + helper so the arithmetic lives in one place. The exceptions are the + options whose ``Option Length`` is not that octet count: a ``Pad1`` + option, which has no ``Option Length`` field at all (see the ``Note:`` + on :meth:`_read_opt_pad` below), the BADF option, whose length leaves + out its SPI (see :meth:`_read_opt_badf`), and the multicast mobility + and acknowledgement options, whose length counts 32-bit words (see + :meth:`_read_opt_mcast`). Do NOT drop the ``+ 2``. The ``+ 2`` is specific to an :rfc:`6275#section-6.2` mobility option, whose Option Type and Option Length are one octet each. It diff --git a/pcapkit/protocols/schema/internet/__init__.py b/pcapkit/protocols/schema/internet/__init__.py index 78f51eba14..a5a8e08013 100644 --- a/pcapkit/protocols/schema/internet/__init__.py +++ b/pcapkit/protocols/schema/internet/__init__.py @@ -134,7 +134,7 @@ # IPv6 Fragment Header from pcapkit.protocols.schema.internet.ipv6_frag import IPv6_Frag -# IPv6 Extension Header (base + generic fallback) +# IPv6 Extension Header (generic fallback) from pcapkit.protocols.schema.internet.ipv6_ext import IPv6_Ext # IPv6 Destination Options @@ -431,7 +431,7 @@ # IPv6 Fragment Header 'IPv6_Frag', - # IPv6 Extension Header (base + generic fallback) + # IPv6 Extension Header (generic fallback) 'IPv6_Ext', # IPv6 Destination Options diff --git a/pcapkit/protocols/schema/internet/hip.py b/pcapkit/protocols/schema/internet/hip.py index 03df155d35..9bf3dd2c48 100644 --- a/pcapkit/protocols/schema/internet/hip.py +++ b/pcapkit/protocols/schema/internet/hip.py @@ -242,8 +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 another call site -- - same ``pkt['len'] - 2`` expression -- but is not: see + :class:`TransportFormatListParameter` is not a call site: see :func:`transport_format_list_len` for why it has no such prefix to subtract. @@ -541,9 +540,10 @@ class ESPInfoParameter(Parameter, code=Enum_Parameter.ESP_INFO): """ - #: 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 - #: parameter reproduces the octets it arrived with. + #: Reserved. :rfc:`7402#section-5.1.1` draws it in the diagram but gives + #: it no description in the field list that follows. Read as a value rather + #: than as padding, so that re-serialising a parsed parameter reproduces the + #: octets it arrived with. reserved: 'int' = UInt16Field() #: Key management index. index: 'int' = UInt16Field() @@ -651,11 +651,11 @@ class LocatorSetParameter(Parameter, code=Enum_Parameter.LOCATOR_SET): #: ``field.length`` octets off the stream and hands only those to #: :meth:`ListField.unpack #: `, so a ``Length`` - #: in locator units would offer a set of *n* plain IPv6 locators ``4n`` - #: octets of a ``24n``-octet contents, parse one truncated locator and leave - #: the rest of the record unconsumed. With ``Length`` a byte count the - #: budget is the contents, and each locator bills the 8 + ``Locator - #: Length`` * 4 octets it actually consumed. + #: summed from the locators' ``Locator Length`` values would offer a set + #: of *n* plain IPv6 locators ``4n`` octets of a ``24n``-octet contents, + #: parse one truncated locator and leave the rest of the record unconsumed. + #: With ``Length`` a byte count the budget is the contents, and each locator + #: bills the 8 + ``Locator Length`` * 4 octets it actually consumed. locators: 'list[Locator]' = ListField( length=lambda pkt: pkt['len'], item_type=SchemaField(schema=Locator), @@ -672,8 +672,8 @@ class LocatorSetParameter(Parameter, code=Enum_Parameter.LOCATOR_SET): #: overwrites it before ``padding`` is reached. #: #: The shadowed ``len`` is 4 for any IPv6 locator, so a padding read off - #: ``pkt['len']`` directly would append exactly four octets whatever the - #: locator count. + #: ``pkt['len']`` directly would append no padding at all whatever the + #: locator count, where a set of plain IPv6 locators needs four octets. padding: 'bytes' = PaddingField(length=locator_set_padding_len) if TYPE_CHECKING: @@ -729,7 +729,8 @@ class SolutionParameter(Parameter, code=Enum_Parameter.SOLUTION): #: Reserved octet -- "zero when sent, ignored when received" #: (:rfc:`7401#section-5.2.5`, and :rfc:`5201#section-5.2.5` identically). #: This octet is *not* a lifetime: only ``PUZZLE`` carries one, at the same - #: offset, and only :rfc:`7401#section-5.2.4` defines the ``2^(value - 32)`` + #: offset, and only the ``PUZZLE`` section (:rfc:`7401#section-5.2.4`, and + #: :rfc:`5201#section-5.2.4` identically) defines the ``2^(value - 32)`` #: seconds encoding that goes in it. reserved: 'int' = UInt8Field() #: Opaque data. diff --git a/pcapkit/protocols/schema/internet/hopopt.py b/pcapkit/protocols/schema/internet/hopopt.py index 578f329ae1..b25a6cb7ca 100644 --- a/pcapkit/protocols/schema/internet/hopopt.py +++ b/pcapkit/protocols/schema/internet/hopopt.py @@ -87,7 +87,7 @@ class MPLFlags(TypedDict): #: number that was received from the MPL Seed. max: int #: Verification flag. ``0`` indicates that the MPL Option - #: conforms to this specification. + #: conforms to :rfc:`7731`. drop: int #: Reserved bits (``rsv``). reserved: int @@ -297,7 +297,7 @@ class CALIPSOOption(Option, code=Enum_Option.CALIPSO): #: CALIPSO domain of interpretation. domain: 'int' = UInt32Field() - #: Compartment length. + #: Compartment length, in 32-bit words. cmpt_len: 'int' = UInt8Field() #: Sens level. level: 'int' = UInt8Field() @@ -327,7 +327,7 @@ class _SMFDPDOption(Schema): choose the schema which then reads the option properly. Its ``namespace`` offsets are therefore bit offsets into the *option*, not into any one field of it: ``Opt Data Len`` is octet 1, i.e. bits 8 to 15, and the mode bit is - the first bit of octet 2 [:rfc:`6621#section-8.1`]. + the first bit of octet 2 [:rfc:`6621#section-6.1.1`]. """ diff --git a/pcapkit/protocols/schema/internet/ipv4.py b/pcapkit/protocols/schema/internet/ipv4.py index 0f156b9911..24108baf91 100644 --- a/pcapkit/protocols/schema/internet/ipv4.py +++ b/pcapkit/protocols/schema/internet/ipv4.py @@ -60,7 +60,7 @@ class VerIHLField(TypedDict): #: IP version. version: int - #: Internet header length. + #: Internet header length, in 32-bit words. ihl: int #: Type of service field. @@ -81,13 +81,13 @@ class Flags(TypedDict): df: int #: More fragments flag. mf: int - #: Fragment offset. + #: Fragment offset, in 8-octet units. offset: int class TSFlags(TypedDict): """Timestamp flags field.""" - #: Timestamp overflow flag. + #: Overflow count. oflw: int #: Timestamp type flag. flag: int diff --git a/pcapkit/protocols/schema/internet/ipv6_option.py b/pcapkit/protocols/schema/internet/ipv6_option.py index d1bd865e7c..3023c9557a 100644 --- a/pcapkit/protocols/schema/internet/ipv6_option.py +++ b/pcapkit/protocols/schema/internet/ipv6_option.py @@ -7,7 +7,7 @@ (:mod:`~pcapkit.protocols.schema.internet.hopopt`) and the Destination Options header (:mod:`~pcapkit.protocols.schema.internet.ipv6_opts`). -Each of those two modules declares its own option schemas, so the helpers take +Each of those modules declares its own option schemas, so the helpers take what differs between them as parameters: ``prefix`` is the name of the header, which leads each error message (``HOPOPT`` or ``IPv6-Opts``), and the selectors take the option schemas they choose from. Each module binds the parameters in diff --git a/pcapkit/protocols/schema/internet/ipv6_opts.py b/pcapkit/protocols/schema/internet/ipv6_opts.py index 8fd7a104a7..a4664aec42 100644 --- a/pcapkit/protocols/schema/internet/ipv6_opts.py +++ b/pcapkit/protocols/schema/internet/ipv6_opts.py @@ -87,7 +87,7 @@ class MPLFlags(TypedDict): #: number that was received from the MPL Seed. max: int #: Verification flag. ``0`` indicates that the MPL Option - #: conforms to this specification. + #: conforms to :rfc:`7731`. drop: int #: Reserved bits (``rsv``). reserved: int @@ -297,7 +297,7 @@ class CALIPSOOption(Option, code=Enum_Option.CALIPSO): #: CALIPSO domain of interpretation. domain: 'int' = UInt32Field() - #: Compartment length. + #: Compartment length, in 32-bit words. cmpt_len: 'int' = UInt8Field() #: Sens level. level: 'int' = UInt8Field() @@ -327,7 +327,7 @@ class _SMFDPDOption(Schema): choose the schema which then reads the option properly. Its ``namespace`` offsets are therefore bit offsets into the *option*, not into any one field of it: ``Opt Data Len`` is octet 1, i.e. bits 8 to 15, and the mode bit is - the first bit of octet 2 [:rfc:`6621#section-8.1`]. + the first bit of octet 2 [:rfc:`6621#section-6.1.1`]. """ diff --git a/pcapkit/protocols/schema/internet/ipv6_route.py b/pcapkit/protocols/schema/internet/ipv6_route.py index 3dd0afede2..d22fc6e1f8 100644 --- a/pcapkit/protocols/schema/internet/ipv6_route.py +++ b/pcapkit/protocols/schema/internet/ipv6_route.py @@ -191,7 +191,7 @@ class RPL(RoutingType, code=Enum_Routing.RPL_Source_Route_Header): .. versionremoved:: 1.5.0 - ``cmpr_i`` and ``cmpr_e``, now the keys of :attr:`cmpr` + ``cmpr_i`` and ``cmpr_e``, which are the keys of :attr:`cmpr` (:issue:`1456`). """