From e54460d28938bfb0ba259d153d2c5bdc21746114 Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Sun, 1 Nov 2026 11:45:20 -0500 Subject: [PATCH] refactor(corekit): move the Internet checksum into corekit.checksum.internet (#2580) pcapkit.corekit.checksum is a package with one module per checksum family; internet.py holds the protocol-independent part of the old pcapkit.protocols.misc.checksum.checksum: ChecksumStatus, the RFC 1071 sum, verify_checksum, fragment_payload and its _in_fragment, and _rebuilding, moved from pcapkit.protocols.protocol. It imports nothing from pcapkit.protocols. The IP-specific pseudo_header and verify_upper join IP_Pseudo in ip_pseudo.py, and checksum.py is gone. The old path never shipped, so there is no re-export and no versionremoved. Every import and reference is retargeted; the docs get a corekit page, and the placement is recorded on utilities-or-corekit.rst. test_checksum_import_boundary_2580_unit checks the package's imports. --- .../conventions/utilities-or-corekit.rst | 12 +++ docs/source/contributing/pep.rst | 8 +- .../source/pcapkit/corekit/checksum/index.rst | 15 +++ .../pcapkit/corekit/checksum/internet.rst | 31 ++++++ docs/source/pcapkit/corekit/index.rst | 4 +- .../pcapkit/protocols/misc/checksum.rst | 65 ++++------- pcapkit/corekit/checksum/__init__.py | 23 ++++ .../checksum/internet.py} | 102 ++++-------------- pcapkit/protocols/application/ospf.py | 4 +- pcapkit/protocols/data/application/ospf.py | 2 +- pcapkit/protocols/data/internet/icmpv4.py | 2 +- pcapkit/protocols/data/internet/icmpv6.py | 2 +- pcapkit/protocols/data/internet/ipv4.py | 2 +- pcapkit/protocols/data/transport/tcp.py | 2 +- pcapkit/protocols/data/transport/udp.py | 2 +- pcapkit/protocols/internet/icmpv4.py | 4 +- pcapkit/protocols/internet/icmpv6.py | 9 +- pcapkit/protocols/internet/internet.py | 3 +- pcapkit/protocols/internet/ipv4.py | 14 +-- pcapkit/protocols/internet/ipv6.py | 8 +- pcapkit/protocols/misc/checksum/__init__.py | 9 +- pcapkit/protocols/misc/checksum/ip_pseudo.py | 79 +++++++++++++- pcapkit/protocols/protocol.py | 8 +- pcapkit/protocols/transport/tcp.py | 11 +- pcapkit/protocols/transport/udp.py | 11 +- pcapkit/utilities/config.py | 2 +- ...test_checksum_import_boundary_2580_unit.py | 91 ++++++++++++++++ .../test_checksum_internet_2471_unit.py} | 31 +----- .../test_rfc_constants_one_home_1739_unit.py | 4 - .../test_ospf_checksum_2475_unit.py | 2 +- .../internet/test_icmp_checksum_2470_unit.py | 2 +- .../internet/test_ipv4_checksum_2472_unit.py | 2 +- .../misc/checksum/test_ip_pseudo_2471_unit.py | 27 ++++- .../transport/test_tcp_checksum_2473_unit.py | 2 +- .../transport/test_udp_checksum_2474_unit.py | 2 +- 35 files changed, 375 insertions(+), 222 deletions(-) create mode 100644 docs/source/pcapkit/corekit/checksum/index.rst create mode 100644 docs/source/pcapkit/corekit/checksum/internet.rst create mode 100644 pcapkit/corekit/checksum/__init__.py rename pcapkit/{protocols/misc/checksum/checksum.py => corekit/checksum/internet.py} (65%) create mode 100644 tests/corekit/test_checksum_import_boundary_2580_unit.py rename tests/{protocols/misc/checksum/test_internet_checksum_2471_unit.py => corekit/test_checksum_internet_2471_unit.py} (80%) diff --git a/docs/source/contributing/conventions/utilities-or-corekit.rst b/docs/source/contributing/conventions/utilities-or-corekit.rst index 871dd7b5a1..15963faa81 100644 --- a/docs/source/contributing/conventions/utilities-or-corekit.rst +++ b/docs/source/contributing/conventions/utilities-or-corekit.rst @@ -25,3 +25,15 @@ question belongs here instead. :mod:`pcapkit.corekit.decorators` is the worked e protocol's file and falls back to protocol classes, so it moved out of ``utilities`` on :issue:`1735`. ``corekit`` may import ``utilities``; the reverse is what the rule forbids. + +A second worked example is :mod:`pcapkit.corekit.checksum`, settled on :issue:`2580`. +It fails the ``utilities`` question because +:class:`~pcapkit.corekit.checksum.internet.ChecksumStatus` subclasses +:class:`~pcapkit.corekit.enum.EnumLookup`. The protocols compute and verify their +checksums through it, so it imports +:mod:`pcapkit` only from ``corekit`` and ``utilities``, never from +:mod:`pcapkit.protocols`; :file:`tests/corekit/test_checksum_import_boundary_2580_unit.py` +checks that. What needs IPv4 and IPv6 stays beside +:class:`~pcapkit.protocols.misc.checksum.ip_pseudo.IP_Pseudo`. The package holds one +module per checksum family, so a new family sits beside +:mod:`~pcapkit.corekit.checksum.internet`. diff --git a/docs/source/contributing/pep.rst b/docs/source/contributing/pep.rst index 6e46f34539..f3619484b7 100644 --- a/docs/source/contributing/pep.rst +++ b/docs/source/contributing/pep.rst @@ -895,7 +895,7 @@ model for how to spell "expected, not an error". **The one's-complement half is done** (:issue:`2470`, :issue:`2472` to :issue:`2475`), on :class:`~pcapkit.protocols.misc.checksum.ip_pseudo.IP_Pseudo` -(:issue:`2471`) and :mod:`pcapkit.protocols.misc.checksum.checksum`, settled as +(:issue:`2471`) and :mod:`pcapkit.corekit.checksum.internet`, settled as below: the toggles by the owner on :issue:`2471`, the rest at implementation, as the offload question above asked; the owner accepted the toggles and the offload heuristic on :issue:`2470`. @@ -912,17 +912,17 @@ offload heuristic on :issue:`2470`. and :class:`~pcapkit.protocols.internet.ipv6.IPv6` verify their upper layer's checksum once parsed, and compute it for an upper layer constructed as their payload; ``make()`` of TCP, UDP and ICMPv6 also takes the ``pseudo`` itself. -* **Offload is** :attr:`~pcapkit.protocols.misc.checksum.checksum.ChecksumStatus.NOT_COMPUTED`, +* **Offload is** :attr:`~pcapkit.corekit.checksum.internet.ChecksumStatus.NOT_COMPUTED`, modelled on ``NO_SA``: a zero IPv4 or TCP checksum, or a TCP or UDP one holding the pseudo-header's sum alone, as Linux offload leaves it, never warns. So does a zero UDP checksum over IPv4 (:rfc:`768`), but not over IPv6, where it is mandatory (:rfc:`8200#section-8.1`). * **A fragment is** - :attr:`~pcapkit.protocols.misc.checksum.checksum.ChecksumStatus.NO_PSEUDO_HEADER`, + :attr:`~pcapkit.corekit.checksum.internet.ChecksumStatus.NO_PSEUDO_HEADER`, atomic ones aside: TCP, UDP, ICMP and ICMPv6 checksums cover all of the datagram's data, which it carries part of. OSPF's Packet Length says what its checksum covers, so it is verified when the fragment holds that much, and - :attr:`~pcapkit.protocols.misc.checksum.checksum.ChecksumStatus.TRUNCATED` + :attr:`~pcapkit.corekit.checksum.internet.ChecksumStatus.TRUNCATED` otherwise. The cryptographic half is sequenced for **wave 2 or 3**, and could be done sooner diff --git a/docs/source/pcapkit/corekit/checksum/index.rst b/docs/source/pcapkit/corekit/checksum/index.rst new file mode 100644 index 0000000000..330a9ba96e --- /dev/null +++ b/docs/source/pcapkit/corekit/checksum/index.rst @@ -0,0 +1,15 @@ +Checksums +========= + +.. module:: pcapkit.corekit.checksum + +:mod:`pcapkit.corekit.checksum` holds one module per checksum family the +protocols compute and verify, so that a new family sits beside the others +(:issue:`2580`). + +.. versionadded:: 1.6.0 + +.. toctree:: + :maxdepth: 2 + + internet diff --git a/docs/source/pcapkit/corekit/checksum/internet.rst b/docs/source/pcapkit/corekit/checksum/internet.rst new file mode 100644 index 0000000000..72d1300620 --- /dev/null +++ b/docs/source/pcapkit/corekit/checksum/internet.rst @@ -0,0 +1,31 @@ +Internet Checksum +================= + +.. module:: pcapkit.corekit.checksum.internet + +:mod:`pcapkit.corekit.checksum.internet` contains the one's-complement +Internet checksum (:rfc:`1071`) of the IPv4 header, TCP, UDP, ICMP, ICMPv6 +and OSPF. Parsing verifies a checksum only if +:data:`~pcapkit.utilities.config.CHECKSUM_VERIFY` is on, and records the +outcome as ``checksum_status``; construction computes one left out unless +:data:`~pcapkit.utilities.config.CHECKSUM_COMPUTE` is off (:issue:`2471`). +A mismatch warns with a :exc:`~pcapkit.utilities.warnings.ChecksumWarning`, +which a filter can silence alone (:issue:`2470`). + +It imports nothing from :mod:`pcapkit.protocols`, which builds on it +(:issue:`2580`). A checksum over the IP pseudo-header is left to the enclosing +IP layer, see :mod:`pcapkit.protocols.misc.checksum.ip_pseudo`. ICMP's +checksum, over the message alone, is verified as it is parsed, unless the IP +layer it is parsed from is a fragment, see :func:`fragment_payload`. + +.. versionadded:: 1.6.0 + +.. autoclass:: pcapkit.corekit.checksum.internet.ChecksumStatus + :members: + :show-inheritance: + +.. autofunction:: pcapkit.corekit.checksum.internet.internet_checksum +.. autofunction:: pcapkit.corekit.checksum.internet.compute_checksum +.. autofunction:: pcapkit.corekit.checksum.internet.partial_checksum +.. autofunction:: pcapkit.corekit.checksum.internet.verify_checksum +.. autofunction:: pcapkit.corekit.checksum.internet.fragment_payload diff --git a/docs/source/pcapkit/corekit/index.rst b/docs/source/pcapkit/corekit/index.rst index 56fcf1645d..3395348948 100644 --- a/docs/source/pcapkit/corekit/index.rst +++ b/docs/source/pcapkit/corekit/index.rst @@ -5,7 +5,8 @@ Core Utilities :mod:`pcapkit.corekit` holds the core utilities :mod:`pcapkit` is built on: the :class:`~pcapkit.corekit.fields.field.Field` family for data parsing, the -:class:`~pcapkit.corekit.context.ContextRegistry` channel for caller-supplied +:mod:`~pcapkit.corekit.checksum` algorithms the protocols compute and verify, +the :class:`~pcapkit.corekit.context.ContextRegistry` channel for caller-supplied information a protocol needs but the wire does not carry, the :mod:`~pcapkit.corekit.decorators` that protocols and schemas parse through, the :class:`~pcapkit.corekit.enum.EnumLookup`/ @@ -25,6 +26,7 @@ parsed on first read, the protocol collection :maxdepth: 2 fields/index + checksum/index context decorators enum diff --git a/docs/source/pcapkit/protocols/misc/checksum.rst b/docs/source/pcapkit/protocols/misc/checksum.rst index 1ec7f15b08..c6737fa3cb 100644 --- a/docs/source/pcapkit/protocols/misc/checksum.rst +++ b/docs/source/pcapkit/protocols/misc/checksum.rst @@ -8,10 +8,10 @@ Checksums :mod:`pcapkit.protocols.misc.checksum` contains the pseudo-header that upper-layer checksums cover, -:class:`~pcapkit.protocols.misc.checksum.ip_pseudo.IP_Pseudo`, and in -:mod:`~pcapkit.protocols.misc.checksum.checksum` what the one's-complement -checksums share, such as -:class:`~pcapkit.protocols.misc.checksum.checksum.ChecksumStatus`. +:class:`~pcapkit.protocols.misc.checksum.ip_pseudo.IP_Pseudo`. What the +one's-complement checksums share, such as +:class:`~pcapkit.corekit.checksum.internet.ChecksumStatus`, is in +:mod:`pcapkit.corekit.checksum.internet`. IP Pseudo-Header ================ @@ -19,10 +19,11 @@ IP Pseudo-Header .. module:: pcapkit.protocols.misc.checksum.ip_pseudo :mod:`pcapkit.protocols.misc.checksum.ip_pseudo` contains -:class:`~pcapkit.protocols.misc.checksum.ip_pseudo.IP_Pseudo` only, +:class:`~pcapkit.protocols.misc.checksum.ip_pseudo.IP_Pseudo`, which implements the pseudo-header of the IP protocol family -that the upper-layer checksums cover, whose structure is -described as below: +that the upper-layer checksums cover, and :func:`pseudo_header` and +:func:`verify_upper`, through which the IP layers use it. The +pseudo-header's structure is described as below: IPv4 (:rfc:`793#section-3.1`, :rfc:`768`): @@ -48,6 +49,16 @@ Octets Bits Name Description 39 312 ``ip_pseudo.protocol`` Next Header ======= ========= ========================= =============================== +A checksum over the IP pseudo-header is verified and computed by the enclosing +:class:`~pcapkit.protocols.internet.ipv4.IPv4` or +:class:`~pcapkit.protocols.internet.ipv6.IPv6` layer, which builds the +:class:`~pcapkit.protocols.misc.checksum.ip_pseudo.IP_Pseudo`, rather than by +the upper layer reaching back to its parent. So an upper layer constructed +alone, or behind another header such as an IPv6 extension header or an +Authentication Header, has its checksum computed only if given the ``pseudo`` +to compute it over, and one parsed alone records no outcome. The sum itself is +in :mod:`pcapkit.corekit.checksum.internet`. + .. autoclass:: pcapkit.protocols.misc.checksum.ip_pseudo.IP_Pseudo :no-members: :show-inheritance: @@ -71,6 +82,9 @@ Octets Bits Name Description .. automethod:: __post_init__ .. automethod:: __index__ +.. autofunction:: pcapkit.protocols.misc.checksum.ip_pseudo.pseudo_header +.. autofunction:: pcapkit.protocols.misc.checksum.ip_pseudo.verify_upper + Header Schemas -------------- @@ -98,40 +112,3 @@ Data Models .. autoclass:: pcapkit.protocols.data.misc.checksum.ip_pseudo.IP_Pseudo :members: :show-inheritance: - -Internet Checksum -================= - -.. module:: pcapkit.protocols.misc.checksum.checksum - -:mod:`pcapkit.protocols.misc.checksum.checksum` contains what the -one's-complement Internet checksum (:rfc:`1071`) of the IPv4 header, TCP, UDP, -ICMP, ICMPv6 and OSPF shares. Parsing verifies a checksum only if -:data:`~pcapkit.utilities.config.CHECKSUM_VERIFY` is on, and records the -outcome as ``checksum_status``; construction computes one left out unless -:data:`~pcapkit.utilities.config.CHECKSUM_COMPUTE` is off (:issue:`2471`). -A mismatch warns with a :exc:`~pcapkit.utilities.warnings.ChecksumWarning`, -which a filter can silence alone (:issue:`2470`). - -A checksum over the IP pseudo-header is verified and computed by the enclosing -:class:`~pcapkit.protocols.internet.ipv4.IPv4` or -:class:`~pcapkit.protocols.internet.ipv6.IPv6` layer, which builds the -:class:`~pcapkit.protocols.misc.checksum.ip_pseudo.IP_Pseudo`, rather than by -the upper layer reaching back to its parent. So an upper layer constructed -alone, or behind another header such as an IPv6 extension header or an -Authentication Header, has its checksum computed only if given the ``pseudo`` -to compute it over, and one parsed alone records no outcome. ICMP's checksum, -over the message alone, is verified as it is parsed, unless the IP layer it is -parsed from is a fragment. - -.. autoclass:: pcapkit.protocols.misc.checksum.checksum.ChecksumStatus - :members: - :show-inheritance: - -.. autofunction:: pcapkit.protocols.misc.checksum.checksum.internet_checksum -.. autofunction:: pcapkit.protocols.misc.checksum.checksum.compute_checksum -.. autofunction:: pcapkit.protocols.misc.checksum.checksum.partial_checksum -.. autofunction:: pcapkit.protocols.misc.checksum.checksum.verify_checksum -.. autofunction:: pcapkit.protocols.misc.checksum.checksum.pseudo_header -.. autofunction:: pcapkit.protocols.misc.checksum.checksum.verify_upper -.. autofunction:: pcapkit.protocols.misc.checksum.checksum.fragment_payload diff --git a/pcapkit/corekit/checksum/__init__.py b/pcapkit/corekit/checksum/__init__.py new file mode 100644 index 0000000000..669aadef21 --- /dev/null +++ b/pcapkit/corekit/checksum/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# pylint: disable=unused-import +"""Checksums +============= + +.. module:: pcapkit.corekit.checksum + +:mod:`pcapkit.corekit.checksum` holds one module per checksum family the +protocols compute and verify, so that a new family sits beside the others +(:issue:`2580`). :mod:`~pcapkit.corekit.checksum.internet` is the +one's-complement Internet checksum (:rfc:`1071`). + +.. versionadded:: 1.6.0 + +""" +from pcapkit.corekit.checksum.internet import (ChecksumStatus, compute_checksum, fragment_payload, + internet_checksum, partial_checksum, verify_checksum) + +__all__ = [ + 'ChecksumStatus', + 'internet_checksum', 'compute_checksum', 'partial_checksum', 'verify_checksum', + 'fragment_payload', +] diff --git a/pcapkit/protocols/misc/checksum/checksum.py b/pcapkit/corekit/checksum/internet.py similarity index 65% rename from pcapkit/protocols/misc/checksum/checksum.py rename to pcapkit/corekit/checksum/internet.py index 92d1222318..510833d896 100644 --- a/pcapkit/protocols/misc/checksum/checksum.py +++ b/pcapkit/corekit/checksum/internet.py @@ -2,12 +2,13 @@ """Internet Checksum ===================== -.. module:: pcapkit.protocols.misc.checksum.checksum +.. module:: pcapkit.corekit.checksum.internet -:mod:`pcapkit.protocols.misc.checksum.checksum` contains what the -one's-complement Internet checksum (:rfc:`1071`) of the IPv4 header, TCP, UDP, -ICMP, ICMPv6 and OSPF shares: :class:`ChecksumStatus`, the outcome a parse -records, and the functions that compute and verify the checksum. +:mod:`pcapkit.corekit.checksum.internet` contains the one's-complement +Internet checksum (:rfc:`1071`) of the IPv4 header, TCP, UDP, ICMP, ICMPv6 +and OSPF: :class:`ChecksumStatus`, the outcome a parse records, and the +functions that compute and verify the checksum. It imports nothing from +:mod:`pcapkit.protocols`, which builds on it (:issue:`2580`). Verification is opt-in and computation opt-out, by :data:`~pcapkit.utilities.config.CHECKSUM_VERIFY` and @@ -15,23 +16,10 @@ mismatch warns with a :exc:`~pcapkit.utilities.warnings.ChecksumWarning`, which a filter can silence alone (:issue:`2470`). -A checksum over the IP pseudo-header is left to the enclosing IP layer, which -alone knows the pseudo-header, and builds it as an -:class:`~pcapkit.protocols.misc.checksum.ip_pseudo.IP_Pseudo`, rather than the -upper layer reaching back to its parent. The upper layer takes part through -these members: - -* ``_verify_pseudo_checksum(pseudo)`` verifies the checksum against - ``pseudo``, or records :attr:`ChecksumStatus.NO_PSEUDO_HEADER` for - :data:`None`; the IP layer calls it once parsed, see :func:`verify_upper`; -* ``make()`` takes the ``pseudo`` it computes the checksum over, and replaces - the length it carries with the upper layer's own; -* ``_checksum_pending`` is :data:`True` once ``make()`` left out a checksum it - had no ``pseudo`` for. An IP layer constructed with that upper layer as its - payload then rebuilds it with its own pseudo-header. - -A checksum over the upper layer alone, as ICMP's, is verified as it is parsed, -unless the IP layer it is parsed from is a fragment, see :func:`fragment_payload`. +A checksum over the IP pseudo-header is left to the enclosing IP layer, see +:mod:`pcapkit.protocols.misc.checksum.ip_pseudo`. A checksum over the upper +layer alone, as ICMP's, is verified as it is parsed, unless the IP layer it is +parsed from is a fragment, see :func:`fragment_payload`. .. versionadded:: 1.6.0 @@ -43,23 +31,26 @@ from typing import TYPE_CHECKING from pcapkit.corekit.enum import EnumLookup -from pcapkit.protocols.protocol import _rebuilding -from pcapkit.utilities.exceptions import FieldValueError, ProtocolError, stacklevel +from pcapkit.utilities.exceptions import stacklevel from pcapkit.utilities.warnings import ChecksumWarning, warn if TYPE_CHECKING: from typing import Collection, Iterator, Optional - from pcapkit.protocols.internet.ipv4 import IPv4 - from pcapkit.protocols.internet.ipv6 import IPv6 - from pcapkit.protocols.misc.checksum.ip_pseudo import IP_Pseudo - __all__ = [ 'ChecksumStatus', 'internet_checksum', 'compute_checksum', 'partial_checksum', 'verify_checksum', - 'pseudo_header', 'verify_upper', 'fragment_payload', + 'fragment_payload', ] +#: contextvars.ContextVar[bool]: Whether a payload is being rebuilt by +#: :meth:`Protocol._make_payload +#: `, so that the layer +#: it is rebuilt for parses the same octets again; see +#: :data:`~pcapkit.utilities.config.FRAME_LAYER_LIMIT`. It lives here so that +#: :func:`verify_checksum` reads it without importing :mod:`pcapkit.protocols`. +_rebuilding = contextvars.ContextVar('_rebuilding', default=False) + #: contextvars.ContextVar[bool]: Whether the layer being parsed is the payload #: of an IP fragment, see :func:`fragment_payload`. _in_fragment = contextvars.ContextVar('_in_fragment', default=False) @@ -208,59 +199,6 @@ def verify_checksum(alias: 'str', data: 'bytes', field: 'bytes', *, length: 'Opt return ChecksumStatus.INVALID -def pseudo_header(pseudo: 'IP_Pseudo', length: 'int') -> 'bytes': - """Octets of ``pseudo`` with ``length`` as its upper-layer length. - - Args: - pseudo: Pseudo-header of the enclosing IP layer. - length: Upper-layer length the checksum is computed for. - - Returns: - :attr:`pseudo.data `, made - anew if it carries another length. - - Raises: - FieldValueError: If ``length`` does not fit the length field. - - """ - if pseudo.info.len == length: - return pseudo.data - return type(pseudo)(src=pseudo.src, dst=pseudo.dst, protocol=pseudo.protocol, - upper_length=length, zero=pseudo.info.zero).data - - -def verify_upper(ip: 'IPv4 | IPv6') -> 'None': - """Verify the checksum of the upper layer ``ip`` carries over its pseudo-header. - - The upper layer is the payload of ``ip``, past any Authentication Header - of IPv4. It is verified if it defines ``_verify_pseudo_checksum``, against - the :class:`~pcapkit.protocols.misc.checksum.ip_pseudo.IP_Pseudo` of ``ip``, - or against :data:`None` if that cannot be derived or names another protocol. - - Args: - ip: Parsed IPv4 or IPv6 layer. - - """ - from pcapkit.protocols.internet.ah import AH # isort: skip # pylint: disable=import-outside-toplevel - from pcapkit.protocols.misc.checksum.ip_pseudo import IP_Pseudo # isort: skip # pylint: disable=import-outside-toplevel - - upper = ip.payload - # NOTE: IPv6 walks an AH as an extension header; IPv4 carries it as its payload. - while isinstance(upper, AH): - upper = upper.payload - verify = getattr(upper, '_verify_pseudo_checksum', None) - if verify is None: - return - - try: - pseudo = IP_Pseudo(ip=ip) # type: Optional[IP_Pseudo] - except (ProtocolError, FieldValueError): - pseudo = None - if pseudo is not None and pseudo.protocol != upper.__index__(): - pseudo = None - verify(pseudo) - - @contextlib.contextmanager def fragment_payload(fragment: 'bool') -> 'Iterator[None]': """Mark the payload an IP layer parses as a fragment's, or not. diff --git a/pcapkit/protocols/application/ospf.py b/pcapkit/protocols/application/ospf.py index 2e5c8bf64e..15ff6b846c 100644 --- a/pcapkit/protocols/application/ospf.py +++ b/pcapkit/protocols/application/ospf.py @@ -226,7 +226,7 @@ def read(self, length: 'Optional[int]' = None, **kwargs: 'Any') -> 'Data_OSPF': # NOTE: The checksum leaves out the 8-octet Authentication field at # octet 16, and is unused under cryptographic authentication (RFC 2328). if config.CHECKSUM_VERIFY and ospf.version == 2: - from pcapkit.protocols.misc.checksum.checksum import ChecksumStatus, verify_checksum # isort: skip # pylint: disable=import-outside-toplevel + from pcapkit.corekit.checksum.internet import ChecksumStatus, verify_checksum # isort: skip # pylint: disable=import-outside-toplevel if ospf.autype == Enum_Authentication.Cryptographic_authentication: status = ChecksumStatus.NOT_COMPUTED else: @@ -345,7 +345,7 @@ def make(self, if (checksum is None and config.CHECKSUM_COMPUTE and version == 2 and auth_type_ != Enum_Authentication.Cryptographic_authentication): - from pcapkit.protocols.misc.checksum.checksum import compute_checksum # isort: skip # pylint: disable=import-outside-toplevel + from pcapkit.corekit.checksum.internet import compute_checksum # isort: skip # pylint: disable=import-outside-toplevel # NOTE: Over the packet but the Authentication field, as read() checks it (#2475). octets = schema.pack() schema.checksum = compute_checksum(octets[:16] + octets[self.length:max(packet_length, self.length)]) diff --git a/pcapkit/protocols/data/application/ospf.py b/pcapkit/protocols/data/application/ospf.py index 9b2af28480..c34fcafd9a 100644 --- a/pcapkit/protocols/data/application/ospf.py +++ b/pcapkit/protocols/data/application/ospf.py @@ -12,7 +12,7 @@ from pcapkit.const.ospf.authentication import Authentication from pcapkit.const.ospf.packet import Packet - from pcapkit.protocols.misc.checksum.checksum import ChecksumStatus + from pcapkit.corekit.checksum.internet import ChecksumStatus __all__ = ['OSPF', 'CryptographicAuthentication'] diff --git a/pcapkit/protocols/data/internet/icmpv4.py b/pcapkit/protocols/data/internet/icmpv4.py index 4117fc3209..84e3418e65 100644 --- a/pcapkit/protocols/data/internet/icmpv4.py +++ b/pcapkit/protocols/data/internet/icmpv4.py @@ -8,7 +8,7 @@ if TYPE_CHECKING: from pcapkit.const.icmp.type import Type as Enum_Type - from pcapkit.protocols.misc.checksum.checksum import ChecksumStatus + from pcapkit.corekit.checksum.internet import ChecksumStatus __all__ = [ 'ICMPv4', diff --git a/pcapkit/protocols/data/internet/icmpv6.py b/pcapkit/protocols/data/internet/icmpv6.py index ddeddeb7dd..3b94f3ed52 100644 --- a/pcapkit/protocols/data/internet/icmpv6.py +++ b/pcapkit/protocols/data/internet/icmpv6.py @@ -8,7 +8,7 @@ if TYPE_CHECKING: from pcapkit.const.icmpv6.type import Type as Enum_Type - from pcapkit.protocols.misc.checksum.checksum import ChecksumStatus + from pcapkit.corekit.checksum.internet import ChecksumStatus __all__ = [ 'ICMPv6', diff --git a/pcapkit/protocols/data/internet/ipv4.py b/pcapkit/protocols/data/internet/ipv4.py index 5a717de337..47d1194a07 100644 --- a/pcapkit/protocols/data/internet/ipv4.py +++ b/pcapkit/protocols/data/internet/ipv4.py @@ -27,9 +27,9 @@ from pcapkit.const.ipv4.tos_thr import ToSThroughput from pcapkit.const.ipv4.ts_flag import TSFlag from pcapkit.const.reg.transtype import TransType + from pcapkit.corekit.checksum.internet import ChecksumStatus from pcapkit.corekit.infoclass import OrderedMultiInfo from pcapkit.corekit.multidict import OrderedMultiDict - from pcapkit.protocols.misc.checksum.checksum import ChecksumStatus __all__ = [ 'IPv4', diff --git a/pcapkit/protocols/data/transport/tcp.py b/pcapkit/protocols/data/transport/tcp.py index 7d512b2056..c4c3bf8de4 100644 --- a/pcapkit/protocols/data/transport/tcp.py +++ b/pcapkit/protocols/data/transport/tcp.py @@ -17,8 +17,8 @@ from pcapkit.const.tcp.flags import Flags as TCP_Flags from pcapkit.const.tcp.mp_tcp_option import MPTCPOption from pcapkit.const.tcp.option import Option as OptionNumber + from pcapkit.corekit.checksum.internet import ChecksumStatus from pcapkit.corekit.infoclass import OrderedMultiInfo - from pcapkit.protocols.misc.checksum.checksum import ChecksumStatus IPAddress = Union[IPv4Address, IPv6Address] diff --git a/pcapkit/protocols/data/transport/udp.py b/pcapkit/protocols/data/transport/udp.py index 9b3e42e39f..10fa795fbd 100644 --- a/pcapkit/protocols/data/transport/udp.py +++ b/pcapkit/protocols/data/transport/udp.py @@ -8,7 +8,7 @@ if TYPE_CHECKING: from pcapkit.const.reg.apptype import AppType - from pcapkit.protocols.misc.checksum.checksum import ChecksumStatus + from pcapkit.corekit.checksum.internet import ChecksumStatus __all__ = ['UDP'] diff --git a/pcapkit/protocols/internet/icmpv4.py b/pcapkit/protocols/internet/icmpv4.py index f222d873e7..6c3fffbe40 100644 --- a/pcapkit/protocols/internet/icmpv4.py +++ b/pcapkit/protocols/internet/icmpv4.py @@ -204,7 +204,7 @@ def read(self, length: 'Optional[int]' = None, *, # NOTE: The checksum covers the whole message, with no pseudo-header (RFC 792); # an IP fragment carries only part of one. if config.CHECKSUM_VERIFY: - from pcapkit.protocols.misc.checksum.checksum import ( # isort: skip # pylint: disable=import-outside-toplevel + from pcapkit.corekit.checksum.internet import ( # isort: skip # pylint: disable=import-outside-toplevel ChecksumStatus, _in_fragment, verify_checksum) if _in_fragment.get(): status = ChecksumStatus.NO_PSEUDO_HEADER @@ -295,7 +295,7 @@ def make(self, ) if checksum is None and config.CHECKSUM_COMPUTE: - from pcapkit.protocols.misc.checksum.checksum import compute_checksum # isort: skip # pylint: disable=import-outside-toplevel + from pcapkit.corekit.checksum.internet import compute_checksum # isort: skip # pylint: disable=import-outside-toplevel schema.checksum = compute_checksum(schema.pack()) return schema diff --git a/pcapkit/protocols/internet/icmpv6.py b/pcapkit/protocols/internet/icmpv6.py index 6813342493..8c4c645987 100644 --- a/pcapkit/protocols/internet/icmpv6.py +++ b/pcapkit/protocols/internet/icmpv6.py @@ -134,7 +134,7 @@ class ICMPv6(ICMP[Data_ICMPv6, Schema_ICMPv6], ) # type: DefaultDict[Enum_Type | int, str | tuple[TypeParser, TypeConstructor]] #: Whether :meth:`make` left the checksum for the enclosing IP layer to - #: compute, see :mod:`pcapkit.protocols.misc.checksum.checksum`. + #: compute, see :mod:`pcapkit.protocols.misc.checksum.ip_pseudo`. _checksum_pending = False ########################################################################## @@ -299,7 +299,8 @@ def make(self, if pseudo is None: self._checksum_pending = True else: - from pcapkit.protocols.misc.checksum.checksum import compute_checksum, pseudo_header # isort: skip # pylint: disable=import-outside-toplevel + from pcapkit.corekit.checksum.internet import compute_checksum # isort: skip # pylint: disable=import-outside-toplevel + from pcapkit.protocols.misc.checksum.ip_pseudo import pseudo_header # isort: skip # pylint: disable=import-outside-toplevel octets = schema.pack() schema.checksum = compute_checksum(octets, prefix=pseudo_header(pseudo, len(octets))) return schema @@ -372,13 +373,13 @@ def _verify_pseudo_checksum(self, pseudo: 'Optional[IP_Pseudo]') -> 'None': """Verify the checksum over the pseudo-header of the enclosing IP layer. That layer calls this once parsed, see - :func:`~pcapkit.protocols.misc.checksum.checksum.verify_upper`. + :func:`~pcapkit.protocols.misc.checksum.ip_pseudo.verify_upper`. Args: pseudo: Pseudo-header, or :data:`None` if the IP layer gives none. """ - from pcapkit.protocols.misc.checksum.checksum import ChecksumStatus, verify_checksum # isort: skip # pylint: disable=import-outside-toplevel + from pcapkit.corekit.checksum.internet import ChecksumStatus, verify_checksum # isort: skip # pylint: disable=import-outside-toplevel if pseudo is None: status = ChecksumStatus.NO_PSEUDO_HEADER diff --git a/pcapkit/protocols/internet/internet.py b/pcapkit/protocols/internet/internet.py index 2c8515acd8..a5ce07358f 100644 --- a/pcapkit/protocols/internet/internet.py +++ b/pcapkit/protocols/internet/internet.py @@ -18,10 +18,11 @@ from pcapkit.const.ipv6.extension_header import ExtensionHeader as Enum_ExtensionHeader from pcapkit.const.reg.transtype import TransType as Enum_TransType +from pcapkit.corekit.checksum.internet import _rebuilding from pcapkit.corekit.decorators import beholder from pcapkit.corekit.module import ModuleDescriptor from pcapkit.corekit.protochain import ProtoChain -from pcapkit.protocols.protocol import _PT, _ST, ProtocolBase, _rebuilding +from pcapkit.protocols.protocol import _PT, _ST, ProtocolBase from pcapkit.protocols.schema.schema import keep_short_read, replay_short_read from pcapkit.utilities import config from pcapkit.utilities.exceptions import stacklevel diff --git a/pcapkit/protocols/internet/ipv4.py b/pcapkit/protocols/internet/ipv4.py index b85bedc56c..cb6fc8ff0f 100644 --- a/pcapkit/protocols/internet/ipv4.py +++ b/pcapkit/protocols/internet/ipv4.py @@ -56,6 +56,7 @@ from pcapkit.const.ipv4.tos_thr import ToSThroughput as Enum_ToSThroughput from pcapkit.const.ipv4.ts_flag import TSFlag as Enum_TSFlag from pcapkit.const.reg.transtype import TransType as Enum_TransType +from pcapkit.corekit.checksum.internet import _rebuilding from pcapkit.corekit.fields.ipaddress import parse_ip_address from pcapkit.corekit.multidict import OrderedMultiDict from pcapkit.protocols.data.internet.ipv4 import EOOLOption as Data_EOOLOption @@ -82,7 +83,6 @@ from pcapkit.protocols.data.internet.ipv4 import TSOption as Data_TSOption from pcapkit.protocols.data.internet.ipv4 import UnassignedOption as Data_UnassignedOption from pcapkit.protocols.internet.ip import IP -from pcapkit.protocols.protocol import _rebuilding from pcapkit.protocols.schema.internet.ipv4 import IPV4_HEADER_LEN from pcapkit.protocols.schema.internet.ipv4 import EOOLOption as Schema_EOOLOption from pcapkit.protocols.schema.internet.ipv4 import ESECOption as Schema_ESECOption @@ -371,7 +371,7 @@ def read(self, length: 'Optional[int]' = None, *, # pylint: disable=unused-argu # NOTE: The checksum covers the header alone (RFC 791 section 3.1); a zero # one is what header checksum offload leaves on the sending host. if config.CHECKSUM_VERIFY: - from pcapkit.protocols.misc.checksum.checksum import verify_checksum # isort: skip # pylint: disable=import-outside-toplevel + from pcapkit.corekit.checksum.internet import verify_checksum # isort: skip # pylint: disable=import-outside-toplevel ipv4.__update__([ ('checksum_status', verify_checksum(self.alias, self._data[:ipv4.hdr_len], schema.chksum, offload=(b'\x00\x00',))), @@ -427,8 +427,8 @@ def read(self, length: 'Optional[int]' = None, *, # pylint: disable=unused-argu proto = None if ipv4.offset else ipv4.protocol if config.CHECKSUM_VERIFY: # NOTE: An upper layer checksummed alone, as ICMP is, is not verified - # in a fragment; see pcapkit.protocols.misc.checksum.checksum (#2470). - from pcapkit.protocols.misc.checksum.checksum import fragment_payload # isort: skip # pylint: disable=import-outside-toplevel + # in a fragment; see pcapkit.corekit.checksum.internet (#2470). + from pcapkit.corekit.checksum.internet import fragment_payload # isort: skip # pylint: disable=import-outside-toplevel with fragment_payload(bool(ipv4.offset or ipv4.flags.mf)): return self._decode_next_layer(ipv4, proto, _plen, packet=__packet__) return self._decode_next_layer(ipv4, proto, _plen, packet=__packet__) @@ -595,7 +595,7 @@ def make(self, ) if checksum is None and config.CHECKSUM_COMPUTE: - from pcapkit.protocols.misc.checksum.checksum import compute_checksum # isort: skip # pylint: disable=import-outside-toplevel + from pcapkit.corekit.checksum.internet import compute_checksum # isort: skip # pylint: disable=import-outside-toplevel schema.chksum = compute_checksum(schema.pack()[:ihl * 4]) # NOTE: A payload that left its checksum out gets it over this header's @@ -678,7 +678,7 @@ def __post_init__(self, file: 'Optional[IO[bytes] | bytes]' = None, Once parsed, the checksum of the upper layer is verified over this layer's pseudo-header if :data:`~pcapkit.utilities.config.CHECKSUM_VERIFY` - is on, see :func:`~pcapkit.protocols.misc.checksum.checksum.verify_upper`. + is on, see :func:`~pcapkit.protocols.misc.checksum.ip_pseudo.verify_upper`. Args: file: Source packet stream. @@ -691,7 +691,7 @@ def __post_init__(self, file: 'Optional[IO[bytes] | bytes]' = None, """ super().__post_init__(file, length, **kwargs) if config.CHECKSUM_VERIFY: - from pcapkit.protocols.misc.checksum.checksum import verify_upper # isort: skip # pylint: disable=import-outside-toplevel + from pcapkit.protocols.misc.checksum.ip_pseudo import verify_upper # isort: skip # pylint: disable=import-outside-toplevel verify_upper(self) ########################################################################## diff --git a/pcapkit/protocols/internet/ipv6.py b/pcapkit/protocols/internet/ipv6.py index 8795f51dd7..278fe30249 100644 --- a/pcapkit/protocols/internet/ipv6.py +++ b/pcapkit/protocols/internet/ipv6.py @@ -328,7 +328,7 @@ def make(self, ) # NOTE: A payload that left its checksum out gets it over this header's - # pseudo-header; see pcapkit.protocols.misc.checksum.checksum (#2471). + # pseudo-header; see pcapkit.protocols.misc.checksum.ip_pseudo (#2471). if (config.CHECKSUM_COMPUTE and getattr(payload, '_checksum_pending', False) and cast('ProtocolBase', payload).__index__() == next_val): from pcapkit.protocols.misc.checksum.ip_pseudo import IP_Pseudo # isort: skip # pylint: disable=import-outside-toplevel @@ -376,7 +376,7 @@ def __post_init__(self, file: 'Optional[IO[bytes] | bytes]' = None, Once parsed, the checksum of the upper layer is verified over this layer's pseudo-header if :data:`~pcapkit.utilities.config.CHECKSUM_VERIFY` - is on, see :func:`~pcapkit.protocols.misc.checksum.checksum.verify_upper`. + is on, see :func:`~pcapkit.protocols.misc.checksum.ip_pseudo.verify_upper`. Args: file: Source packet stream. @@ -389,7 +389,7 @@ def __post_init__(self, file: 'Optional[IO[bytes] | bytes]' = None, """ super().__post_init__(file, length, **kwargs) if config.CHECKSUM_VERIFY: - from pcapkit.protocols.misc.checksum.checksum import verify_upper # isort: skip # pylint: disable=import-outside-toplevel + from pcapkit.protocols.misc.checksum.ip_pseudo import verify_upper # isort: skip # pylint: disable=import-outside-toplevel verify_upper(self) ########################################################################## @@ -653,7 +653,7 @@ def _decode_next_layer(self, ipv6: 'Data_IPv6', proto: 'Optional[int]' = None, if config.CHECKSUM_VERIFY: # NOTE: As in IPv4.read; an atomic fragment, offset zero and no more # to come, is a whole packet (#2470). - from pcapkit.protocols.misc.checksum.checksum import fragment_payload # isort: skip # pylint: disable=import-outside-toplevel + from pcapkit.corekit.checksum.internet import fragment_payload # isort: skip # pylint: disable=import-outside-toplevel frags = self._exthdr.getlist(Enum_ExtensionHeader.IPv6_Frag) # type: ignore[arg-type] scope = fragment_payload(any(frag.info.get('offset') or frag.info.get('mf') for frag in frags)) token = _frame_depth.set(depth + len(_protos)) diff --git a/pcapkit/protocols/misc/checksum/__init__.py b/pcapkit/protocols/misc/checksum/__init__.py index 99197224a6..f9e250788a 100644 --- a/pcapkit/protocols/misc/checksum/__init__.py +++ b/pcapkit/protocols/misc/checksum/__init__.py @@ -6,13 +6,10 @@ :mod:`pcapkit.protocols.misc.checksum` contains the pseudo-header that upper-layer checksums cover, -:class:`~pcapkit.protocols.misc.checksum.ip_pseudo.IP_Pseudo`, and in -:mod:`~pcapkit.protocols.misc.checksum.checksum` what the one's-complement -checksums share, such as -:class:`~pcapkit.protocols.misc.checksum.checksum.ChecksumStatus`. +:class:`~pcapkit.protocols.misc.checksum.ip_pseudo.IP_Pseudo`. The checksum +itself is in :mod:`pcapkit.corekit.checksum`. """ -from pcapkit.protocols.misc.checksum.checksum import ChecksumStatus from pcapkit.protocols.misc.checksum.ip_pseudo import IP_Pseudo -__all__ = ['IP_Pseudo', 'ChecksumStatus'] +__all__ = ['IP_Pseudo'] diff --git a/pcapkit/protocols/misc/checksum/ip_pseudo.py b/pcapkit/protocols/misc/checksum/ip_pseudo.py index 79442adf97..f9f093f266 100644 --- a/pcapkit/protocols/misc/checksum/ip_pseudo.py +++ b/pcapkit/protocols/misc/checksum/ip_pseudo.py @@ -5,10 +5,11 @@ .. module:: pcapkit.protocols.misc.checksum.ip_pseudo :mod:`pcapkit.protocols.misc.checksum.ip_pseudo` contains -:class:`~pcapkit.protocols.misc.checksum.ip_pseudo.IP_Pseudo` only, +:class:`~pcapkit.protocols.misc.checksum.ip_pseudo.IP_Pseudo`, which implements the pseudo-header of the IP protocol family -that the upper-layer checksums cover, whose structure is -described as below: +that the upper-layer checksums cover, and :func:`pseudo_header` and +:func:`verify_upper`, through which the IP layers use it. The +pseudo-header's structure is described as below: IPv4 (:rfc:`793#section-3.1`, :rfc:`768`): @@ -34,6 +35,24 @@ 39 312 ``ip_pseudo.protocol`` Next Header ======= ========= ========================= =============================== +A checksum over the IP pseudo-header is left to the enclosing IP layer, which +alone knows the pseudo-header, and builds it as an +:class:`~pcapkit.protocols.misc.checksum.ip_pseudo.IP_Pseudo`, rather than the +upper layer reaching back to its parent. The upper layer takes part through +these members: + +* ``_verify_pseudo_checksum(pseudo)`` verifies the checksum against + ``pseudo``, or records + :attr:`~pcapkit.corekit.checksum.internet.ChecksumStatus.NO_PSEUDO_HEADER` + for :data:`None`; the IP layer calls it once parsed, see :func:`verify_upper`; +* ``make()`` takes the ``pseudo`` it computes the checksum over, and replaces + the length it carries with the upper layer's own, see :func:`pseudo_header`; +* ``_checksum_pending`` is :data:`True` once ``make()`` left out a checksum it + had no ``pseudo`` for. An IP layer constructed with that upper layer as its + payload then rebuilds it with its own pseudo-header. + +The sum itself is in :mod:`pcapkit.corekit.checksum.internet`. + """ import ipaddress from typing import TYPE_CHECKING, overload @@ -62,7 +81,7 @@ from aenum import IntEnum as AenumEnum from typing_extensions import Literal -__all__ = ['IP_Pseudo'] +__all__ = ['IP_Pseudo', 'pseudo_header', 'verify_upper'] #: Extension headers that end the chain, as the upper layer: the Mobility Header #: (:rfc:`6275#section-6.1.1`) and HIP (:rfc:`7401#section-5.1.1`). @@ -616,3 +635,55 @@ def _udp_length(self, ip: 'IPv4 | IPv6', protocol: 'int', hdr_len: 'int', length if len(octets) < 2: return length return int.from_bytes(octets, 'big') or length + + +def pseudo_header(pseudo: 'IP_Pseudo', length: 'int') -> 'bytes': + """Octets of ``pseudo`` with ``length`` as its upper-layer length. + + Args: + pseudo: Pseudo-header of the enclosing IP layer. + length: Upper-layer length the checksum is computed for. + + Returns: + :attr:`pseudo.data `, made + anew if it carries another length. + + Raises: + FieldValueError: If ``length`` does not fit the length field. + + """ + if pseudo.info.len == length: + return pseudo.data + return type(pseudo)(src=pseudo.src, dst=pseudo.dst, protocol=pseudo.protocol, + upper_length=length, zero=pseudo.info.zero).data + + +def verify_upper(ip: 'IPv4 | IPv6') -> 'None': + """Verify the checksum of the upper layer ``ip`` carries over its pseudo-header. + + The upper layer is the payload of ``ip``, past any Authentication Header + of IPv4. It is verified if it defines ``_verify_pseudo_checksum``, against + the :class:`~pcapkit.protocols.misc.checksum.ip_pseudo.IP_Pseudo` of ``ip``, + or against :data:`None` if that cannot be derived or names another protocol. + + Args: + ip: Parsed IPv4 or IPv6 layer. + + """ + from pcapkit.protocols.internet.ah import AH # isort: skip # pylint: disable=import-outside-toplevel + + upper = ip.payload + # NOTE: IPv6 walks an AH as an extension header; IPv4 carries it as its payload. + while isinstance(upper, AH): + upper = upper.payload + verify = getattr(upper, '_verify_pseudo_checksum', None) + if verify is None: + return + + try: + pseudo = IP_Pseudo(ip=ip) # type: Optional[IP_Pseudo] + except (ProtocolError, FieldValueError): + pseudo = None + if pseudo is not None and pseudo.protocol != upper.__index__(): + pseudo = None + verify(pseudo) diff --git a/pcapkit/protocols/protocol.py b/pcapkit/protocols/protocol.py index a35f471812..e89f64aa5f 100644 --- a/pcapkit/protocols/protocol.py +++ b/pcapkit/protocols/protocol.py @@ -43,6 +43,7 @@ import aenum +from pcapkit.corekit.checksum.internet import _rebuilding from pcapkit.corekit.context import ContextRegistry from pcapkit.corekit.decorators import _beholding, beholder, seekset from pcapkit.corekit.fields.misc import ConditionalField, SchemaField, SwitchField @@ -431,13 +432,6 @@ def _check_construction_keywords(cls: 'type', kwargs: 'dict[str, Any]', #: :data:`~pcapkit.utilities.config.FRAME_LAYER_LIMIT`. _frame_depth = contextvars.ContextVar('_frame_depth', default=0) -#: contextvars.ContextVar[bool]: Whether a payload is being rebuilt by -#: :meth:`Protocol._make_payload -#: `, so that the layer -#: it is rebuilt for parses the same octets again; see -#: :data:`~pcapkit.utilities.config.FRAME_LAYER_LIMIT`. -_rebuilding = contextvars.ContextVar('_rebuilding', default=False) - def _short_of_hint(klass: 'Type[ProtocolBase]', captured: 'int') -> 'bool': """Tell whether ``captured`` octets may be too few for a ``klass`` header. diff --git a/pcapkit/protocols/transport/tcp.py b/pcapkit/protocols/transport/tcp.py index dfbd53921a..25daebde9e 100644 --- a/pcapkit/protocols/transport/tcp.py +++ b/pcapkit/protocols/transport/tcp.py @@ -447,7 +447,7 @@ class TCP(Transport[Data_TCP, Schema_TCP], ) # type: DefaultDict[int, str | tuple[MPOptionParser, MPOptionConstructor]] #: Whether :meth:`make` left the checksum for the enclosing IP layer to - #: compute, see :mod:`pcapkit.protocols.misc.checksum.checksum`. + #: compute, see :mod:`pcapkit.protocols.misc.checksum.ip_pseudo`. _checksum_pending = False ########################################################################## @@ -725,7 +725,8 @@ def make(self, if pseudo is None: self._checksum_pending = True else: - from pcapkit.protocols.misc.checksum.checksum import compute_checksum, pseudo_header # isort: skip # pylint: disable=import-outside-toplevel + from pcapkit.corekit.checksum.internet import compute_checksum # isort: skip # pylint: disable=import-outside-toplevel + from pcapkit.protocols.misc.checksum.ip_pseudo import pseudo_header # isort: skip # pylint: disable=import-outside-toplevel octets = schema.pack() schema.checksum = compute_checksum(octets, prefix=pseudo_header(pseudo, len(octets))) return schema @@ -842,16 +843,16 @@ def _verify_pseudo_checksum(self, pseudo: 'Optional[IP_Pseudo]') -> 'None': """Verify the checksum over the pseudo-header of the enclosing IP layer. That layer calls this once parsed, see - :func:`~pcapkit.protocols.misc.checksum.checksum.verify_upper`. A zero + :func:`~pcapkit.protocols.misc.checksum.ip_pseudo.verify_upper`. A zero checksum, or the pseudo-header's sum alone, is what checksum offload leaves on the sending host, and is - :attr:`~pcapkit.protocols.misc.checksum.checksum.ChecksumStatus.NOT_COMPUTED`. + :attr:`~pcapkit.corekit.checksum.internet.ChecksumStatus.NOT_COMPUTED`. Args: pseudo: Pseudo-header, or :data:`None` if the IP layer gives none. """ - from pcapkit.protocols.misc.checksum.checksum import ( # isort: skip # pylint: disable=import-outside-toplevel + from pcapkit.corekit.checksum.internet import ( # isort: skip # pylint: disable=import-outside-toplevel ChecksumStatus, partial_checksum, verify_checksum) if pseudo is None: diff --git a/pcapkit/protocols/transport/udp.py b/pcapkit/protocols/transport/udp.py index 782a28955d..f769cf95c6 100644 --- a/pcapkit/protocols/transport/udp.py +++ b/pcapkit/protocols/transport/udp.py @@ -150,7 +150,7 @@ class UDP(Transport[Data_UDP, Schema_UDP], __proto_key__: 'ClassVar[tuple[Type[EnumRegistry], bool]]' = (Enum_AppType, True) #: Whether :meth:`make` left the checksum for the enclosing IP layer to - #: compute, see :mod:`pcapkit.protocols.misc.checksum.checksum`. + #: compute, see :mod:`pcapkit.protocols.misc.checksum.ip_pseudo`. _checksum_pending = False ########################################################################## @@ -294,7 +294,8 @@ def make(self, if pseudo is None: self._checksum_pending = True else: - from pcapkit.protocols.misc.checksum.checksum import compute_checksum, pseudo_header # isort: skip # pylint: disable=import-outside-toplevel + from pcapkit.corekit.checksum.internet import compute_checksum # isort: skip # pylint: disable=import-outside-toplevel + from pcapkit.protocols.misc.checksum.ip_pseudo import pseudo_header # isort: skip # pylint: disable=import-outside-toplevel # NOTE: Over the datagram the Length field gives, or all but the trailer # for a jumbogram's zero one (RFC 2675); a computed zero is all ones. @@ -356,11 +357,11 @@ def _verify_pseudo_checksum(self, pseudo: 'Optional[IP_Pseudo]') -> 'None': """Verify the checksum over the pseudo-header of the enclosing IP layer. That layer calls this once parsed, see - :func:`~pcapkit.protocols.misc.checksum.checksum.verify_upper`. A zero + :func:`~pcapkit.protocols.misc.checksum.ip_pseudo.verify_upper`. A zero checksum over IPv4, which means none (:rfc:`768`), and the pseudo-header's sum alone, which checksum offload leaves on the sending host, are - :attr:`~pcapkit.protocols.misc.checksum.checksum.ChecksumStatus.NOT_COMPUTED`. + :attr:`~pcapkit.corekit.checksum.internet.ChecksumStatus.NOT_COMPUTED`. A zero one over IPv6 is wrong, as one is mandatory there (:rfc:`8200#section-8.1`). @@ -368,7 +369,7 @@ def _verify_pseudo_checksum(self, pseudo: 'Optional[IP_Pseudo]') -> 'None': pseudo: Pseudo-header, or :data:`None` if the IP layer gives none. """ - from pcapkit.protocols.misc.checksum.checksum import ( # isort: skip # pylint: disable=import-outside-toplevel + from pcapkit.corekit.checksum.internet import ( # isort: skip # pylint: disable=import-outside-toplevel ChecksumStatus, partial_checksum, verify_checksum) if pseudo is None: diff --git a/pcapkit/utilities/config.py b/pcapkit/utilities/config.py index 5454808c4e..865627d885 100644 --- a/pcapkit/utilities/config.py +++ b/pcapkit/utilities/config.py @@ -418,7 +418,7 @@ def read_flag(name: 'str', default: 'bool' = False) -> 'bool': #: #: Off by default, since checksumming every packet costs time on a large #: capture (:issue:`2471`). When on, each records a -#: :class:`~pcapkit.protocols.misc.checksum.checksum.ChecksumStatus` as +#: :class:`~pcapkit.corekit.checksum.internet.ChecksumStatus` as #: ``checksum_status`` in its info, and a wrong checksum warns with a #: :exc:`~pcapkit.utilities.warnings.ChecksumWarning` rather than raising. #: diff --git a/tests/corekit/test_checksum_import_boundary_2580_unit.py b/tests/corekit/test_checksum_import_boundary_2580_unit.py new file mode 100644 index 0000000000..b2ece3adcc --- /dev/null +++ b/tests/corekit/test_checksum_import_boundary_2580_unit.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +"""Nothing under :mod:`pcapkit.corekit.checksum` imports :mod:`pcapkit.protocols`. + +GitHub issue #2580 moved the Internet checksum out of :mod:`pcapkit.protocols` +into :mod:`pcapkit.corekit.checksum.internet`, so that the protocols build on it +rather than it on them; the IP pseudo-header, which needs IPv4 and IPv6, stayed +in :mod:`pcapkit.protocols.misc.checksum`. A checksum family added beside it +keeps to the same rule, so the whole package is checked: it imports +:mod:`pcapkit` only from ``corekit`` and ``utilities``. + +The scan is the one :mod:`tests.project.test_utilities_import_boundary_1735_unit` +runs over :mod:`pcapkit.utilities`: every import, at top level, inside a function +and under ``TYPE_CHECKING`` alike, read with :mod:`ast`. + +This module is unit-tier and reads no capture. + +""" +from __future__ import annotations + +import unittest + +from tests.project.test_utilities_import_boundary_1735_unit import (ROOT, _module_name, + imported_names) + +#: The package the rule governs. +CHECKSUM = ROOT / 'pcapkit' / 'corekit' / 'checksum' + +#: The :mod:`pcapkit` packages it may import. +ALLOWED = ('pcapkit.corekit', 'pcapkit.utilities') + + +def crosses_the_boundary(name: 'str') -> 'bool': + """Whether importing ``name`` reaches :mod:`pcapkit` outside :data:`ALLOWED`.""" + if any(name == allowed or name.startswith(f'{allowed}.') for allowed in ALLOWED): + return False + return name == 'pcapkit' or name.startswith('pcapkit.') + + +def violations() -> 'list[str]': + """Every import under :data:`CHECKSUM` that crosses the boundary, as ``path:line: name``.""" + found = [] # type: list[str] + for path in sorted(CHECKSUM.rglob('*.py')): + source = path.read_text(encoding='utf-8') + for line, name in imported_names(source, _module_name(path), + is_package=path.name == '__init__.py'): + if crosses_the_boundary(name): + found.append(f'{path.relative_to(ROOT).as_posix()}:{line}: {name}') + return found + + +class ChecksumImportBoundaryTests(unittest.TestCase): + """:mod:`pcapkit.corekit.checksum` stays below :mod:`pcapkit.protocols`.""" + + def test_the_walk_found_the_package(self) -> None: + names = {path.name for path in CHECKSUM.rglob('*.py')} + self.assertIn('__init__.py', names) + self.assertIn('internet.py', names) + + def test_no_module_imports_pcapkit_outside_corekit_and_utilities(self) -> None: + found = violations() + self.assertEqual(found, [], + 'these modules under pcapkit/corekit/checksum/ import pcapkit from ' + 'outside corekit and utilities, which #2580 rules out; what needs ' + 'the protocols belongs beside IP_Pseudo in ' + 'pcapkit.protocols.misc.checksum:\n' + '\n'.join(found)) + + def test_what_the_move_took_out_would_cross(self) -> None: + source = '\n'.join([ + 'from typing import TYPE_CHECKING', # 1 + 'from pcapkit.corekit.enum import EnumLookup', # 2 + 'from pcapkit.utilities.warnings import ChecksumWarning, warn', # 3 + 'from pcapkit.protocols.protocol import _rebuilding', # 4 + 'if TYPE_CHECKING:', # 5 + ' from pcapkit.protocols.misc.checksum.ip_pseudo import IP_Pseudo', # 6 + 'def verify_upper(ip):', # 7 + ' from pcapkit.protocols.internet.ah import AH', # 8 + ' from pcapkit import foundation', # 9 + ]) + crossing = [(line, name) + for line, name in imported_names(source, 'pcapkit.corekit.checksum.demo') + if crosses_the_boundary(name)] + self.assertEqual(crossing, [ + (4, 'pcapkit.protocols.protocol'), + (6, 'pcapkit.protocols.misc.checksum.ip_pseudo'), + (8, 'pcapkit.protocols.internet.ah'), + (9, 'pcapkit.foundation'), + ]) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/protocols/misc/checksum/test_internet_checksum_2471_unit.py b/tests/corekit/test_checksum_internet_2471_unit.py similarity index 80% rename from tests/protocols/misc/checksum/test_internet_checksum_2471_unit.py rename to tests/corekit/test_checksum_internet_2471_unit.py index 6faba98ab7..ba8a793fde 100644 --- a/tests/protocols/misc/checksum/test_internet_checksum_2471_unit.py +++ b/tests/corekit/test_checksum_internet_2471_unit.py @@ -4,8 +4,8 @@ GitHub issue #2471 settled the policy: verification is opt-in and computation opt-out, each a ``PCAPKIT_*`` flag; a wrong checksum warns rather than raises; and the outcome is recorded as a -:class:`~pcapkit.protocols.misc.checksum.checksum.ChecksumStatus`. These tests pin -:mod:`pcapkit.protocols.misc.checksum.checksum` against :rfc:`1071`, cross-checked +:class:`~pcapkit.corekit.checksum.internet.ChecksumStatus`. These tests pin +:mod:`pcapkit.corekit.checksum.internet` against :rfc:`1071`, cross-checked with the hand-written sum of ``tests/foundation/_roundtrip.py``. """ @@ -28,7 +28,7 @@ class InternetChecksumTests(unittest.TestCase): def setUp(self) -> None: reimport_once_per_class(self) - from pcapkit.protocols.misc.checksum import checksum + from pcapkit.corekit.checksum import internet as checksum self.checksum = checksum @@ -63,7 +63,7 @@ class VerifyChecksumTests(unittest.TestCase): def setUp(self) -> None: reimport_once_per_class(self) - from pcapkit.protocols.misc.checksum import checksum + from pcapkit.corekit.checksum import internet as checksum from pcapkit.utilities.warnings import ChecksumWarning, ProtocolWarning self.checksum = checksum @@ -125,28 +125,5 @@ def test_the_status_is_shaped_like_esp_status(self) -> None: self.assertIs(self.Status.get('NOT_COMPUTED'), self.Status.NOT_COMPUTED) -class PseudoHeaderTests(unittest.TestCase): - """:func:`pseudo_header` gives the upper layer's own length.""" - - def setUp(self) -> None: - reimport_once_per_class(self) - from pcapkit.protocols.misc.checksum.checksum import pseudo_header - from pcapkit.protocols.misc.checksum.ip_pseudo import IP_Pseudo - - self.pseudo_header = pseudo_header - self.IP_Pseudo = IP_Pseudo - - def test_the_length_is_replaced(self) -> None: - for src, dst, offset, width in (('192.0.2.1', '198.51.100.2', 10, 2), - ('2001:db8::1', '2001:db8::2', 32, 4)): - pseudo = self.IP_Pseudo(src=src, dst=dst, protocol=6, upper_length=0) - with self.subTest(src=src): - self.assertIs(self.pseudo_header(pseudo, 0), pseudo.data) - octets = self.pseudo_header(pseudo, 0x1234) - self.assertEqual(octets[offset:offset + width], (0x1234).to_bytes(width, 'big')) - self.assertEqual(octets[:offset] + octets[offset + width:], - pseudo.data[:offset] + pseudo.data[offset + width:]) - - if __name__ == '__main__': unittest.main() diff --git a/tests/project/test_rfc_constants_one_home_1739_unit.py b/tests/project/test_rfc_constants_one_home_1739_unit.py index 06a4c78e97..b55282106e 100644 --- a/tests/project/test_rfc_constants_one_home_1739_unit.py +++ b/tests/project/test_rfc_constants_one_home_1739_unit.py @@ -460,10 +460,6 @@ 'pcapkit/protocols/schema/link/l2tp.py': ((6, 12), 'an L2TP header field offset, and the ' 'version bit-field'), 'pcapkit/protocols/schema/link/vlan.py': ((12,), 'the 12-bit VLAN identifier'), - 'pcapkit/protocols/misc/checksum/checksum.py': ((16, 65535), - "the one's-complement sum's 16-bit word mask " - 'and carry shift (RFC 1071), not an IP total ' - 'length or a PCAP record header'), 'pcapkit/protocols/misc/pcapng.py': ((65535,), 'a 16-bit mask'), 'pcapkit/protocols/schema/misc/pcapng.py': ((9, 65535), 'an epb_flags bit offset, and a ' '16-bit field default'), diff --git a/tests/protocols/application/test_ospf_checksum_2475_unit.py b/tests/protocols/application/test_ospf_checksum_2475_unit.py index 08bb92dcb2..e40812a493 100644 --- a/tests/protocols/application/test_ospf_checksum_2475_unit.py +++ b/tests/protocols/application/test_ospf_checksum_2475_unit.py @@ -55,7 +55,7 @@ class OSPFChecksumTests(unittest.TestCase): def setUp(self) -> None: reimport_once_per_class(self) from pcapkit.protocols.application.ospf import OSPF - from pcapkit.protocols.misc.checksum.checksum import ChecksumStatus + from pcapkit.corekit.checksum.internet import ChecksumStatus from pcapkit.utilities import config from pcapkit.utilities.warnings import ChecksumWarning, ProtocolWarning diff --git a/tests/protocols/internet/test_icmp_checksum_2470_unit.py b/tests/protocols/internet/test_icmp_checksum_2470_unit.py index 25003a6a06..581cd807e6 100644 --- a/tests/protocols/internet/test_icmp_checksum_2470_unit.py +++ b/tests/protocols/internet/test_icmp_checksum_2470_unit.py @@ -59,11 +59,11 @@ class ChecksumTestCase(unittest.TestCase): def setUp(self) -> None: reimport_once_per_class(self) + from pcapkit.corekit.checksum.internet import ChecksumStatus from pcapkit.protocols.internet.icmpv4 import ICMPv4 from pcapkit.protocols.internet.icmpv6 import ICMPv6 from pcapkit.protocols.internet.ipv4 import IPv4 from pcapkit.protocols.internet.ipv6 import IPv6 - from pcapkit.protocols.misc.checksum.checksum import ChecksumStatus from pcapkit.protocols.misc.checksum.ip_pseudo import IP_Pseudo from pcapkit.utilities import config from pcapkit.utilities.warnings import ChecksumWarning, ProtocolWarning diff --git a/tests/protocols/internet/test_ipv4_checksum_2472_unit.py b/tests/protocols/internet/test_ipv4_checksum_2472_unit.py index a838f8e1bf..678ab8ae58 100644 --- a/tests/protocols/internet/test_ipv4_checksum_2472_unit.py +++ b/tests/protocols/internet/test_ipv4_checksum_2472_unit.py @@ -43,7 +43,7 @@ class IPv4ChecksumTests(unittest.TestCase): def setUp(self) -> None: reimport_once_per_class(self) from pcapkit.protocols.internet.ipv4 import IPv4 - from pcapkit.protocols.misc.checksum.checksum import ChecksumStatus + from pcapkit.corekit.checksum.internet import ChecksumStatus from pcapkit.utilities import config from pcapkit.utilities.warnings import ChecksumWarning, ProtocolWarning diff --git a/tests/protocols/misc/checksum/test_ip_pseudo_2471_unit.py b/tests/protocols/misc/checksum/test_ip_pseudo_2471_unit.py index bb0dc50251..a9d847c238 100644 --- a/tests/protocols/misc/checksum/test_ip_pseudo_2471_unit.py +++ b/tests/protocols/misc/checksum/test_ip_pseudo_2471_unit.py @@ -3,7 +3,9 @@ GitHub issue #2471: :class:`~pcapkit.protocols.misc.checksum.ip_pseudo.IP_Pseudo` is built from a parsed IPv4 or IPv6 layer, or from explicit values, and packs to -the :rfc:`793#section-3.1` or :rfc:`8200#section-8.1` layout. +the :rfc:`793#section-3.1` or :rfc:`8200#section-8.1` layout; +:func:`~pcapkit.protocols.misc.checksum.ip_pseudo.pseudo_header` gives it the +upper layer's own length. The captured cases come from the tracked :file:`examples/captures/in.pcap` and :file:`examples/captures/dhcp.pcapng`. Each expected pseudo-header is sliced @@ -701,5 +703,28 @@ def test_duplicate_header_types_are_each_summed(self) -> None: self.assertEqual(IP_Pseudo(ip=ip).data[32:], bytes.fromhex('00000008' '000000' '3a')) +@unittest.skipUnless(HAS_RUNTIME, 'runtime dependencies not installed') +class PseudoHeaderTests(unittest.TestCase): + """:func:`pseudo_header` gives the upper layer's own length.""" + + def setUp(self) -> None: + reimport_once_per_class(self) + from pcapkit.protocols.misc.checksum.ip_pseudo import IP_Pseudo, pseudo_header + + self.pseudo_header = pseudo_header + self.IP_Pseudo = IP_Pseudo + + def test_the_length_is_replaced(self) -> None: + for src, dst, offset, width in (('192.0.2.1', '198.51.100.2', 10, 2), + ('2001:db8::1', '2001:db8::2', 32, 4)): + pseudo = self.IP_Pseudo(src=src, dst=dst, protocol=6, upper_length=0) + with self.subTest(src=src): + self.assertIs(self.pseudo_header(pseudo, 0), pseudo.data) + octets = self.pseudo_header(pseudo, 0x1234) + self.assertEqual(octets[offset:offset + width], (0x1234).to_bytes(width, 'big')) + self.assertEqual(octets[:offset] + octets[offset + width:], + pseudo.data[:offset] + pseudo.data[offset + width:]) + + if __name__ == '__main__': unittest.main() diff --git a/tests/protocols/transport/test_tcp_checksum_2473_unit.py b/tests/protocols/transport/test_tcp_checksum_2473_unit.py index 03f599c702..52c3b7f8a3 100644 --- a/tests/protocols/transport/test_tcp_checksum_2473_unit.py +++ b/tests/protocols/transport/test_tcp_checksum_2473_unit.py @@ -60,9 +60,9 @@ class TCPChecksumTests(unittest.TestCase): def setUp(self) -> None: reimport_once_per_class(self) + from pcapkit.corekit.checksum.internet import ChecksumStatus from pcapkit.protocols.internet.ipv4 import IPv4 from pcapkit.protocols.internet.ipv6 import IPv6 - from pcapkit.protocols.misc.checksum.checksum import ChecksumStatus from pcapkit.protocols.misc.checksum.ip_pseudo import IP_Pseudo from pcapkit.protocols.transport.tcp import TCP from pcapkit.utilities import config diff --git a/tests/protocols/transport/test_udp_checksum_2474_unit.py b/tests/protocols/transport/test_udp_checksum_2474_unit.py index e65dd1df4e..1f545c8e59 100644 --- a/tests/protocols/transport/test_udp_checksum_2474_unit.py +++ b/tests/protocols/transport/test_udp_checksum_2474_unit.py @@ -62,9 +62,9 @@ class UDPChecksumTests(unittest.TestCase): def setUp(self) -> None: reimport_once_per_class(self) + from pcapkit.corekit.checksum.internet import ChecksumStatus from pcapkit.protocols.internet.ipv4 import IPv4 from pcapkit.protocols.internet.ipv6 import IPv6 - from pcapkit.protocols.misc.checksum.checksum import ChecksumStatus from pcapkit.protocols.misc.checksum.ip_pseudo import IP_Pseudo from pcapkit.protocols.transport.udp import UDP from pcapkit.utilities import config