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.py — HTTP
pcapkit/protocols/application/httpv2.py — HTTP
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_comp — pcapkit/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.protocol — pcapkit/foundation/reassembly/reassembly.py:77 |
cls.name.upper() |
Returns Raw |
TraceFlowMeta.protocol — pcapkit/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:
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.
- 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
- 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).
- 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.
- 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.
Follow-up to #675, which is fixed by #681. #675 was about the silence:
register_protocoldisplaced 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 isthe residue.
What is still true after #681
pcapkit.protocols.__proto__is keyed oncls.__name__.upper(), and three dispatchable classesare all named
HTTP:pcapkit/protocols/application/http.py— the generic baseHTTPpcapkit/protocols/application/httpv1.py—HTTPpcapkit/protocols/application/httpv2.py—HTTPAll three still land on the single key
'HTTP'. Whoever registers last still wins; the onlydifference is that you now get told. Measured on
b34f132f6:The import-time seeding at
pcapkit/protocols/__init__.py:74-75avoids the clash only because itkeys off the distinct
__all__namesHTTP,HTTPv1,HTTPv2rather than offcls.__name__.Anything going through
register_protocol— includingProtocol.__init_subclass__, whichregisters 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.
ProtocolBase.expand_comp—pcapkit/protocols/protocol.py:697value.upper()from a caller-supplied stringcomp = (value.upper(),), a bare string with no class identityProtocolBase.__getitem__/__contains__/_check_term_thresholdexpand_comppacket['HTTP'],'HTTP' in packet,extract(protocol=...)ProtoChain.index/count/__contains__—pcapkit/corekit/protochain.py:113,133,207expand_compReassemblyMeta.protocol—pcapkit/foundation/reassembly/reassembly.py:77cls.name.upper()RawTraceFlowMeta.protocol—pcapkit/foundation/traceflow/traceflow.py:78cls.name.upper()RawPayloadField.protocolsetter —pcapkit/corekit/fields/misc.py:267protocolverbatim, no.upper()None, then falls back toRawTwo consequences that make this more than a mechanical rename:
expand_compis 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']" — mostlikely a second, name-to-key index, which is a design decision rather than an edit.
IPsec.id()returns('AH', 'ESP')—'IPSEC'is not in its ownid().frame['IPsec']works only because the registry hitresolves the class and the comparison then uses
id(). On a miss it would raiseProtocolNotFoundinstead of matching an ESP layer.pcapkit.protocols.__proto__is also a documented public attribute —docs/source/pcapkit/protocols/index.rst:154documents it withautodata, cross-referenced toregister_protocol— so its key shape is part of the public contract, not an internal detail.Options, not a prescription
f'{cls.__module__}.{cls.__qualname__}'.upper()and keep a separate name-to-entries map for
expand_comp, which then has a defined answer foran ambiguous bare name (first hit, last hit, or refuse and say which candidates exist).
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, andIPsecshows the class'sown name need not appear in it.
API break —
pcapkit.protocols.application.httpv2.HTTPis importable today.Relationship to #514
#514 is the design issue for adopting the opt-in
EnumMeta/EnumSchemaregistration patternacross
Protocol,Engine,ReassemblyandTraceFlow; #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:
Protocolhas 0 descendants and
ProtocolBasehas 43, so no built-in currently reaches thecollision through
__init_subclass__. Verified onb34f132f6— all threeHTTPclasses derivefrom
ProtocolBase, not from the publicProtocol, and a plainimport pcapkitemits zeroRegistryWarnings. The collision is reachable today by an explicitregister_protocolcall, or byuser code subclassing the public
Protocolunder a name a built-in already holds. That is a realbut narrow blast radius, which is the argument for doing this with #514 rather than in a hurry.