Skip to content

register_protocol's key space is still not unique: the three HTTP classes share one key (follow-up to #675) #682

Description

@JarryShaw

Follow-up to #675, which is fixed by #681. #675 was about the silence: register_protocol
displaced one protocol class with another and said nothing. #681 makes the displacement audible
with a RegistryWarning. It deliberately does not resolve the collision, and this issue is
the residue.

What is still true after #681

pcapkit.protocols.__proto__ is keyed on cls.__name__.upper(), and three dispatchable classes
are all named HTTP:

  • pcapkit/protocols/application/http.py — the generic base HTTP
  • pcapkit/protocols/application/httpv1.pyHTTP
  • pcapkit/protocols/application/httpv2.pyHTTP

All three still land on the single key 'HTTP'. Whoever registers last still wins; the only
difference is that you now get told. Measured on b34f132f6:

distinct keys the three classes land on: ['HTTP']

The import-time seeding at pcapkit/protocols/__init__.py:74-75 avoids the clash only because it
keys off the distinct __all__ names HTTP, HTTPv1, HTTPv2 rather than off cls.__name__.
Anything going through register_protocol — including Protocol.__init_subclass__, which
registers unconditionally — gets the collision.

Why #681 stopped at the warning: every reader takes a bare name and fails silently

This is the part worth not re-deriving. A unique key (qualified name, or the protocol's own
declared identifier) is a registry-format change, and not one reader raises on a miss — they
all degrade quietly, which means a re-keying would be a second silent behaviour change rather
than a fix.

Reader Key it looks up Behaviour on a miss today
ProtocolBase.expand_comppcapkit/protocols/protocol.py:697 value.upper() from a caller-supplied string Falls back to comp = (value.upper(),), a bare string with no class identity
ProtocolBase.__getitem__ / __contains__ / _check_term_threshold via expand_comp Inherits the above — this is packet['HTTP'], 'HTTP' in packet, extract(protocol=...)
ProtoChain.index / count / __contains__pcapkit/corekit/protochain.py:113,133,207 via expand_comp Inherits the above
ReassemblyMeta.protocolpcapkit/foundation/reassembly/reassembly.py:77 cls.name.upper() Returns Raw
TraceFlowMeta.protocolpcapkit/foundation/traceflow/traceflow.py:78 cls.name.upper() Returns Raw
PayloadField.protocol setter — pcapkit/corekit/fields/misc.py:267 protocol verbatim, no .upper() None, then falls back to Raw

Two consequences that make this more than a mechanical rename:

  1. expand_comp is handed a bare name by construction. It takes a user string like 'HTTP'.
    A qualified key does not relocate that lookup, it removes the ability to perform it. So
    re-keying needs a deliberate answer for "how does a user still say packet['HTTP']" — most
    likely a second, name-to-key index, which is a design decision rather than an edit.
  2. It breaks alias classes today if done naively. IPsec.id() returns ('AH', 'ESP')
    'IPSEC' is not in its own id(). frame['IPsec'] works only because the registry hit
    resolves the class and the comparison then uses id(). On a miss it would raise
    ProtocolNotFound instead of matching an ESP layer.

pcapkit.protocols.__proto__ is also a documented public attribute
docs/source/pcapkit/protocols/index.rst:154 documents it with autodata, cross-referenced to
register_protocol — so its key shape is part of the public contract, not an internal detail.

Options, not a prescription

  1. Qualified key plus a bare-name index. Key on f'{cls.__module__}.{cls.__qualname__}'.upper()
    and keep a separate name-to-entries map for expand_comp, which then has a defined answer for
    an ambiguous bare name (first hit, last hit, or refuse and say which candidates exist).
  2. Key on the protocol's own declared identifier rather than its Python class name. id()
    already exists and is already what the comparison path uses, which makes it the more principled
    key. Needs a survey: id() returns a tuple for several protocols, and IPsec shows the class's
    own name need not appear in it.
  3. Rename the classes so the key space is naturally unique. Smallest conceptual change, largest
    API break — pcapkit.protocols.application.httpv2.HTTP is importable today.

Relationship to #514

#514 is the design issue for adopting the opt-in EnumMeta/EnumSchema registration pattern
across Protocol, Engine, Reassembly and TraceFlow; #575 is gated behind it. Its part-(c)
investigation recommended fixing this collision first, as c1, before the alias work — #681 is
that c1 step, and it was scoped to observability precisely so that it constrains nothing about the
eventual key. Whichever option above is taken belongs with #514's decision rather than ahead of it,
since #514 is what decides whether registration is opt-in at all, and an opt-in registry has a
materially smaller key space to keep unique.

Worth recording from #514's own measurements, because it bears on how urgent this is: Protocol
has 0 descendants and ProtocolBase has 43, so no built-in currently reaches the
collision through __init_subclass__. Verified on b34f132f6 — all three HTTP classes derive
from ProtocolBase, not from the public Protocol, and a plain import pcapkit emits zero
RegistryWarnings. The collision is reachable today by an explicit register_protocol call, or by
user code subclassing the public Protocol under a name a built-in already holds. That is a real
but narrow blast radius, which is the argument for doing this with #514 rather than in a hurry.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    blockedDeferred pending another issue or decision; see the last comment for what unblocks itdesignA design or decision issue: a pattern being decided rather than a defect or a request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions