Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/source/pcapkit/const/linux.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ which is automatically generated from :class:`pcapkit.vendor.linux.arphrd.ARPHRD
:members:
:undoc-members:
:show-inheritance:
:special-members: __proto_dest__

.. rubric:: Footnotes

Expand Down
12 changes: 12 additions & 0 deletions pcapkit/const/linux/arphrd.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from aenum import IntEnum

from pcapkit.corekit.enum import EnumRegistry
from pcapkit.corekit.module import ModuleDescriptor
from pcapkit.utilities.exceptions import EnumValueError

if TYPE_CHECKING:
Expand All @@ -26,6 +27,17 @@
class ARPHRD(EnumRegistry, IntEnum):
"""[ARPHRD] Linux ARP hardware types [``<linux/if_arp.h>``]"""

#: Protocol class(es) owning the ``__proto__`` dispatch registry keyed by
#: this enumeration, read by
#: :func:`~pcapkit.foundation.registry.protocols.register_protocol_code`
#: to resolve a bare member of it into its destination(s). The classes are
#: named as lazy :class:`~pcapkit.corekit.module.ModuleDescriptor`
#: references rather than imported, because :mod:`pcapkit.const` sits below
#: :mod:`pcapkit.protocols` and importing one from here would be circular.
__proto_dest__ = (
ModuleDescriptor('pcapkit.protocols.link.linux_sll', 'LinuxSLL'),
)

#: NET/ROM pseudo.
NETROM = 0

Expand Down
4 changes: 3 additions & 1 deletion pcapkit/foundation/registry/protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,9 @@ def register_protocol_code(protocol: 'Type[ProtocolBase]', code: 'Any') -> 'None
:class:`~pcapkit.const.reg.transtype.TransType` to
:class:`~pcapkit.protocols.internet.internet.Internet`,
:class:`~pcapkit.const.sctp.payload_protocol_identifier.PayloadProtocolIdentifier`
to :class:`~pcapkit.protocols.transport.sctp.SCTP`, and
to :class:`~pcapkit.protocols.transport.sctp.SCTP`,
:class:`~pcapkit.const.linux.arphrd.ARPHRD` to
:class:`~pcapkit.protocols.link.linux_sll.LinuxSLL`, and
:class:`~pcapkit.const.reg.linktype.LinkType` to *both*
:class:`~pcapkit.protocols.misc.pcap.frame.Frame` **and**
:class:`~pcapkit.protocols.misc.pcapng.PCAPNG`; or
Expand Down
5 changes: 5 additions & 0 deletions pcapkit/vendor/linux/arphrd.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import sys
from typing import TYPE_CHECKING

from pcapkit.corekit.module import ModuleDescriptor
from pcapkit.vendor.default import Vendor

if TYPE_CHECKING:
Expand Down Expand Up @@ -59,6 +60,10 @@ class ARPHRD(Vendor):
#: Value limit checker.
FLAG = 'isinstance(value, int) and 0x0000 <= value <= 0xFFFF'

#: Destination of a bare ``ARPHRD`` member; see
#: :attr:`~pcapkit.vendor.default.Vendor.PROTO_DEST`.
PROTO_DEST = (ModuleDescriptor('pcapkit.protocols.link.linux_sll', 'LinuxSLL'),)

def request(self) -> 'dict[int, tuple[str, str]]': # type: ignore[override] # pylint: disable=arguments-differ
"""Fetch registry data.

Expand Down
9 changes: 5 additions & 4 deletions tests/protocols/test_dispatch_reachability_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,11 @@ def test_every_indexed_class_is_reachable_under_the_code_it_declares(self) -> No
f'{gap.found!r}' for gap in gaps))

# A walk that found nothing to check would also report no gaps, so pin
# a floor of 23 claims (measured: the 21 distinct classes plus ``InARP``
# and ``DRARP`` reaching their family bases' entries, and ``Ethernet``
# counted once for ``Frame`` and once for ``PCAPNG``).
self.assertGreaterEqual(checked, 23)
# the measured claim count as a floor: each indexed class once per
# destination its code's ``__proto_dest__`` names -- ``Frame`` and
# ``PCAPNG`` for a ``LinkType`` code, ``LinuxSLL`` for an ``ARPHRD`` one
# -- with ``InARP`` and ``DRARP`` reaching their family bases' entries.
self.assertGreaterEqual(checked, 39)

def test_the_audit_detects_a_code_that_nothing_dispatches(self) -> None:
"""The guard has teeth: inject a gap and confirm the audit reports it.
Expand Down
35 changes: 35 additions & 0 deletions tests/protocols/test_protocol_code_registration_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,19 @@ class FakeProtocol:
reg_frame.assert_called_once_with(LinkType.RAW, FakeProtocol)
reg_pcapng.assert_called_once_with(LinkType.RAW, FakeProtocol)

def test_bare_arphrd_member_infers_linux_sll(self) -> None:
"""``ARPHRD`` names ``LinuxSLL``, the family base owning the registry (#2533)."""
from pcapkit.const.linux.arphrd import ARPHRD
from pcapkit.foundation.registry.protocols import register_protocol_code
from pcapkit.protocols.link.linux_sll import LinuxSLL

class FakeProtocol:
pass

with mock.patch.object(LinuxSLL, 'register') as register:
register_protocol_code(FakeProtocol, ARPHRD.NETROM)
register.assert_called_once_with(ARPHRD.NETROM, FakeProtocol)

def test_raw_int_without_explicit_destination_refuses(self) -> None:
"""A bare ``int`` cannot say whether it means TCP or UDP -- it must refuse.

Expand Down Expand Up @@ -359,6 +372,26 @@ class MyGre(Internet, code=TransType.GRE):
self.assertIs(Internet.__proto__[TransType.GRE], MyGre)
self.assertNotIn(TransType.GRE, Internet.__proto__)

def test_class_keyword_with_an_arphrd_member_reaches_linux_sll_dispatch(self) -> None:
"""``code=ARPHRD.X`` registers where a cooked frame dispatches (#2533)."""
import struct

from pcapkit.const.linux.arphrd import ARPHRD
from pcapkit.protocols.link.linux_sll import LinuxSLL
from pcapkit.protocols.link.linux_sll2 import LinuxSLL2
from pcapkit.protocols.misc.raw import Raw

self.assertNotIn(ARPHRD.NETROM, LinuxSLL.__proto__)
frame = struct.pack('>HHIHBB8s', 0x0800, 0, 0, ARPHRD.NETROM, 0, 0, b'') + b'\xaa\xbb'

with mock.patch.dict(LinuxSLL.__proto__, {}, clear=False):
class NetROM(Raw, code=ARPHRD.NETROM):
pass

self.assertIs(LinuxSLL.__proto__[ARPHRD.NETROM], NetROM)
self.assertEqual(LinuxSLL2(frame, len(frame)).protochain.chain, 'LINUX_SLL2:NetROM')
self.assertNotIn(ARPHRD.NETROM, LinuxSLL.__proto__)

def test_subclass_without_code_does_not_inherit_parent_registration(self) -> None:
"""Opting in is per-subclass, not inherited from a registered parent.

Expand Down Expand Up @@ -724,6 +757,8 @@ def setUp(self) -> None:
('pcapkit.const.reg.linktype', 'LinkType',
(('pcapkit.protocols.misc.pcap.frame', 'Frame'),
('pcapkit.protocols.misc.pcapng', 'PCAPNG'))),
('pcapkit.const.linux.arphrd', 'ARPHRD',
(('pcapkit.protocols.link.linux_sll', 'LinuxSLL'),)),
)

def test_each_registry_declares_the_destinations_it_keys(self) -> None:
Expand Down
44 changes: 37 additions & 7 deletions tests/vendor/test_vendor_proto_dest_2392_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@
touched the other generated modules would rewrite most of :mod:`pcapkit.const`
on the next crawl.
* :class:`CrawlerProtoDestTests` -- the declaring crawlers name exactly the
destinations the registries need, and no other crawler declares one.
destinations the registries need, no other crawler declares one, and the
committed ``ARPHRD`` module is its crawler's rendering.

Nothing here crawls: :meth:`Vendor.__init__` fetches from IANA and writes the
constant file as a side effect of construction, so the one instance built below
uses ``cls.__new__(cls)``, the same bypass
:file:`tests/vendor/test_vendor_dest_path_unit.py` uses for the same reason.
Nothing here crawls: :meth:`Vendor.__init__` fetches its registry and writes the
constant file as a side effect of construction, so the one crawler constructed
below is ``ARPHRD``, whose registry is a ``DATA`` literal, writing to a
temporary file.

"""
from __future__ import annotations
Expand All @@ -53,15 +54,16 @@
HAS_VENDOR_DEPS = all(importlib.util.find_spec(name) is not None for name in VENDOR_DEPS)

#: ``crawler module -> ((module, class), ...)``, the destinations the declaring
#: crawlers name (#2392), with ``LinkType`` naming two: a link-layer header
#: type keys the next-layer registry of both capture file formats.
#: crawlers name (#2392, #2533), with ``LinkType`` naming two: a link-layer
#: header type keys the next-layer registry of both capture file formats.
EXPECTED = {
'pcapkit.vendor.reg.ethertype': (('pcapkit.protocols.link.link', 'Link'),),
'pcapkit.vendor.reg.transtype': (('pcapkit.protocols.internet.internet', 'Internet'),),
'pcapkit.vendor.reg.linktype': (('pcapkit.protocols.misc.pcap.frame', 'Frame'),
('pcapkit.protocols.misc.pcapng', 'PCAPNG')),
'pcapkit.vendor.sctp.payload_protocol_identifier': (('pcapkit.protocols.transport.sctp',
'SCTP'),),
'pcapkit.vendor.linux.arphrd': (('pcapkit.protocols.link.linux_sll', 'LinuxSLL'),),
}


Expand Down Expand Up @@ -253,6 +255,34 @@ def test_every_declared_destination_resolves_to_a_protocol_class(self) -> None:
with self.subTest(crawler=name, destination=descriptor.name):
self.assertTrue(issubclass(descriptor.klass, ProtocolBase))

def test_the_committed_arphrd_module_is_the_crawlers_rendering(self) -> None:
"""Regenerating ``ARPHRD`` reproduces the committed module, declaration included (#2533).

A regeneration pin, not the change's fail-before proof: it holds whenever
crawler and committed module agree, so it passes without the declaration
too. ``EXPECTED`` and the inference tests in
:file:`tests/protocols/test_protocol_code_registration_unit.py` are what
fail without it.

Its crawler reads a ``DATA`` literal rather than fetching, so the whole
render runs offline; it writes to a temporary file here, not to
:file:`pcapkit/const/`.
"""
import pathlib
import tempfile
from unittest import mock

import pcapkit.const.linux.arphrd as committed
from pcapkit.vendor.linux.arphrd import ARPHRD

with tempfile.TemporaryDirectory(prefix='pcapkit-vendor-arphrd-') as tmp:
dest = pathlib.Path(tmp, 'arphrd.py')
with mock.patch.object(ARPHRD, '_dest_path', return_value=str(dest)):
ARPHRD()
rendered = dest.read_text(encoding='utf-8')

self.assertEqual(rendered, pathlib.Path(committed.__file__).read_text(encoding='utf-8'))


if __name__ == '__main__':
unittest.main()
Loading