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
224 changes: 118 additions & 106 deletions pcapkit/const/reg/apptype/apptype.py

Large diffs are not rendered by default.

23 changes: 15 additions & 8 deletions pcapkit/foundation/registry/protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -888,14 +888,21 @@ def register_apptype(code: 'int | Enum_AppType', module: 'str | ModuleDescriptor
# ``code.proto`` default and the registries lookup below -- sees members
# only. Resolution is by the member's own ``name``, case-insensitively --
# maintainer ruling on #815 -- via ``__members__`` directly rather than
# ``TransportProtocol[name]``: aenum's ``Flag.__getitem__`` parses a
# ``'|'``-joined name into a composite value on its own
# (``TransportProtocol['tcp|udp']`` silently returns the value ``3``),
# which is exactly the composite this function has to refuse, and
# lowercasing does not change that: ``'tcp|udp'`` is not a member name
# either. Anything that is neither a ``str`` nor a ``TransportProtocol``
# member is rejected here too, rather than falling through to the
# registries lookup below: ``TransportProtocol`` is an ``IntFlag``, so
# ``TransportProtocol[name]``: this function's contract is
# :exc:`~pcapkit.utilities.exceptions.RegistryError` for anything
# unrecognised, composite-spelled or not, and ``__getitem__`` raises a
# bare :exc:`KeyError` on a miss instead of that -- both
# ``TransportProtocol['tcp|udp']`` and ``TransportProtocol['bogus']`` do,
# now that GitHub issue #808 dropped the ``IntFlag`` base that used to
# make the first of those two silently compose into the value ``3``
# rather than miss at all. ``__members__.get(...)`` lets this function
# raise its own exception on a miss instead of letting ``__getitem__``'s
# propagate, and lowercasing does not turn ``'tcp|udp'`` into a member
# name either way. Anything that is neither a ``str`` nor a
# ``TransportProtocol`` member is rejected here too, rather than falling
# through to the registries lookup below: a bare ``int`` still hashes
# and compares equal to its matching member -- ``TransportProtocol``
# being an ``IntEnum`` rather than an ``IntFlag`` changes neither -- so
# ``registries.get(1)`` resolves to ``TCP`` just as
# ``registries.get(TransportProtocol.tcp)`` does, and would otherwise
# register the port before the ``proto.name`` access two lines below it
Expand Down
226 changes: 119 additions & 107 deletions pcapkit/vendor/reg/apptype/apptype.py

Large diffs are not rendered by default.

297 changes: 229 additions & 68 deletions tests/const/test_const_apptype_split_unit.py

Large diffs are not rendered by default.

114 changes: 86 additions & 28 deletions tests/const/test_const_enum_builtin_parity.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,17 +348,20 @@ def test_the_sweep_size_is_pinned(self) -> None:
if issubclass(obj, int) and not issubclass(obj, aenum.Flag)]

# The decomposition is asserted, not just the total, so this sweep stays
# in step with the three narrower ones it overlaps: 111 non-flag IntEnum
# and 7 IntFlag in tests.const.test_const_enum_lookup, and 118 -- their
# sum -- in tests.const.test_const_enum_get.
# in step with the three narrower ones it overlaps: 112 non-flag IntEnum
# and 6 IntFlag in tests.const.test_const_enum_lookup, and 118 -- their
# sum, unchanged -- in tests.const.test_const_enum_get. GitHub issue #808
# moved ``TransportProtocol`` from the flag count to the int count by
# dropping its ``IntFlag`` base, one for one, so the sum each of those
# counts on stays the same even though the two addends moved.
#
# Five of the nine string registries are the application layer one, which
# GitHub issue #732 split into a package: the memberless
# pcapkit.const.reg.apptype.apptype.AppType base plus one registry per
# transport protocol. It is discovered exactly like a member-bearing
# registry, since this sweep is structural and never looks at members.
self.assertEqual(len(ints), 111)
self.assertEqual(len(flags), 7)
self.assertEqual(len(ints), 112)
self.assertEqual(len(flags), 6)
self.assertEqual(len(strs), 9)
self.assertEqual(len(self.enums), 127)
self.assertEqual(len({obj.__module__ for obj in self.enums}), 121)
Expand Down Expand Up @@ -474,7 +477,18 @@ def test_every_new_guard_is_a_classmethod(self) -> None:

# The six of issue #647 specifically, since the sweep above would still
# pass if they had no ``_missing_`` at all -- which was the defect.
#
# ``TransportProtocol`` is the one deliberate exception: GitHub issue
# #808 dropped its ``IntFlag`` base once nothing built a composite, and
# with it the custom ``_missing_`` this guard checks for -- a plain
# ``IntEnum``'s own default ``_missing_`` already rejects everything
# undeclared, so declaring one here would only reproduce the base
# class. Its ``-1`` rejection is still checked, just not through this
# guard shape; see ``test_the_registries_named_in_issue_647`` above,
# which does not skip it.
for module_name, class_name, _ in ISSUE_647_OUTLIERS:
if (module_name, class_name) == ('pcapkit.const.reg.apptype.apptype', 'TransportProtocol'):
continue
obj = getattr(importlib.import_module(module_name), class_name)
with self.subTest(enum=f'{module_name}.{class_name}'):
self.assertIn('_missing_', vars(obj),
Expand Down Expand Up @@ -541,19 +555,41 @@ def test_tcp_flag_composites_resolve(self) -> None:
# being rejected: bits 0-3 of that field are the data offset.
self.assertEqual(int(Flags(1)), 1)

def test_the_other_two_flag_registries_compose(self) -> None:
def test_the_other_flag_registry_composes(self) -> None:
"""GitHub issue #808 dropped ``TransportProtocol`` out of this group.

This used to test ``TransportProtocol`` here too, as the third
registry in the codebase sharing Flag semantics alongside ``Flags``
(:class:`ConstFlagCompositeTests` above) and ``CommandType``. #808
dropped ``TransportProtocol``'s ``IntFlag`` base once nothing built a
composite, so it no longer belongs to this group; its own -- now
negative -- assertions live in
:meth:`test_transport_protocol_no_longer_composes` below instead.
"""
from pcapkit.const.ftp.command import CommandType
from pcapkit.const.reg.apptype import TransportProtocol

self.assertEqual(int(CommandType(0)), 0)
self.assertEqual(CommandType(0x07), CommandType.A | CommandType.P | CommandType.S)
with self.assertRaises(ValueError):
CommandType(0x08)

def test_transport_protocol_no_longer_composes(self) -> None:
"""GitHub issue #808: dropping the ``IntFlag`` base removed composing.

``TransportProtocol(0x0F)`` used to equal the union of all four
declared bits, and ``TransportProtocol(0x10)`` -- one past the widest
legitimate combination -- was rejected by the range check
``_missing_`` used to carry. Both mechanisms are gone now rather than
dormant: a plain :class:`~aenum.IntEnum` recognises only the five
declared values, so any other integer -- in range for the old bound
or not -- is rejected by the base class's own default ``_missing_``,
with no custom one declared here to widen it.
"""
from pcapkit.const.reg.apptype import TransportProtocol

self.assertEqual(int(TransportProtocol(0)), 0)
self.assertEqual(TransportProtocol(0x0F),
TransportProtocol.tcp | TransportProtocol.udp
| TransportProtocol.sctp | TransportProtocol.dccp)
with self.assertRaises(ValueError):
TransportProtocol(0x0F)
with self.assertRaises(ValueError):
TransportProtocol(0x10)

Expand Down Expand Up @@ -674,32 +710,54 @@ def test_apptype_still_registers_an_unassigned_port(self) -> None:
self.assertEqual(int(registered), 65000)
self.assertIs(AppType.get(65000, proto=TransportProtocol.tcp), registered)

def test_transport_protocol_can_still_be_extended_at_runtime(self) -> None:
"""Why this registry's bound is derived rather than written down.

``TransportProtocol.get`` registers an unknown protocol name at
``max * 2``, so a literal upper bound -- the shape the Mobility Header
flag guards use -- would reject the very member the registry had just
grown, and every composite containing it. The guard reads the bound off
the current members instead, and this test is what pins that: it fails
against a hard-coded ``0x0F``.
def test_transport_protocol_can_no_longer_be_extended_at_runtime(self) -> None:
"""Maintainer ruling on PR #836: extension refused, not renumbered.

This used to pin the *shape* of ``TransportProtocol.get``'s
registration. GitHub issue #808 dropped the ``IntFlag`` base -- see
:meth:`ConstFlagCompositeTests.test_transport_protocol_no_longer_composes`
for why composing stopped mattering -- but left the doubling itself
alone: stock ``ad4805f5f`` still mints at ``max_val * 2``, so
``.get('quic')`` there returns ``16``, right after ``dccp``'s ``8``.
This PR's own intermediate revision, not #808, is what switched an
unrecognised name to ``max_val + 1`` instead, minting ``9``. PR
#836's own inline comment on ``TransportProtocol.get`` removes the
registration entirely regardless of which scheme numbered it: "Do
not allow extension of TransportProtocol at all." Unlike
:class:`~pcapkit.const.ipv4.protection_authority.ProtectionAuthority`
and :class:`~pcapkit.const.mh.cga_type.CGAType` below,
``TransportProtocol`` was never one of :data:`EXPECTED_TO_REGISTER`
-- it reached the "look up, miss, then register" shape through its
own hand-written ``get`` rather than through a generated
``_missing_`` -- so pulling it back out of that shape is this
module's ruling changing which registries are mutable, not a
regression the sweep above would otherwise have to catch.

Regression check: this fails against this PR's own prior head,
``3567359e2``, which still registers ``'quic'`` at value 9 rather
than raising -- see the session report for the quoted failure.
"""
from pcapkit.const.reg.apptype import TransportProtocol

self.assertNotIn('quic', TransportProtocol.__members__)
with self.assertRaises(ValueError):
TransportProtocol(0x10)
TransportProtocol(9)

grown = TransportProtocol.get('quic')
self.assertEqual(int(grown), 0x10)
self.assertIn('quic', TransportProtocol.__members__)
before = len(TransportProtocol.__members__)
with self.assertRaises(ValueError):
TransportProtocol.get('quic')
self.assertNotIn('quic', TransportProtocol.__members__)
self.assertEqual(len(TransportProtocol.__members__), before)

# The new member composes with the old ones, which is the assertion a
# literal bound fails.
self.assertEqual(int(TransportProtocol(0x11)), 0x11)
self.assertEqual(TransportProtocol(0x11), grown | TransportProtocol.tcp)
# A second unrecognised name is refused identically -- there is no
# ``max + 1`` left to walk to, since nothing registers in the first
# place.
with self.assertRaises(ValueError):
TransportProtocol.get('quic2')
self.assertEqual(len(TransportProtocol.__members__), before)

# And the bound moved with it rather than disappearing.
# An int naming no declared member is still rejected exactly as
# before -- this half of the guard is untouched by the ruling.
with self.assertRaises(ValueError):
TransportProtocol(0x20)

Expand Down
15 changes: 11 additions & 4 deletions tests/const/test_const_enum_lookup.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,11 @@ def test_every_registry_enum_was_discovered(self) -> None:
# Pins the size of the sweep itself: if this drifts, a const enum was
# added, removed, or renamed, and EXPECTED_TO_REJECT_ZERO (and the
# analysis in GitHub issue #492) needs a fresh look rather than a
# silent pass.
# silent pass. 112 rather than 111 since GitHub issue #808: dropping
# ``TransportProtocol``'s ``IntFlag`` base moved it from
# ``_iter_const_int_flags`` into this sweep.
names = {f'{obj.__module__}.{obj.__qualname__}' for obj in self.enums}
self.assertEqual(len(self.enums), 111)
self.assertEqual(len(self.enums), 112)
self.assertTrue(EXPECTED_TO_REJECT_ZERO.issubset(names),
f'expected reject-list entries missing from the sweep: '
f'{EXPECTED_TO_REJECT_ZERO - names}')
Expand Down Expand Up @@ -304,9 +306,14 @@ def setUpClass(cls) -> None:
cls.addClassCleanup(restore_modules, snapshot, ISOLATED_PREFIXES)

def test_the_sweep_size_is_pinned(self) -> None:
"""A flag enum added or removed needs a fresh look, not a silent pass."""
"""A flag enum added or removed needs a fresh look, not a silent pass.

6 rather than 7 since GitHub issue #808: dropping
``TransportProtocol``'s ``IntFlag`` base moved it out of this sweep and
into ``_iter_const_int_enums`` instead.
"""
names = {f'{obj.__module__}.{obj.__qualname__}' for obj in self.flags}
self.assertEqual(len(self.flags), 7)
self.assertEqual(len(self.flags), 6)

expected = {f'pcapkit.const.mh.{stem}.{name}'
for stem, name, _, _ in MH_FLAG_ENUMS}
Expand Down
Loading
Loading