Minting an Unrecognised Value¶
A registry under pcapkit.const resolves a value with no member through a
_missing_ override. Only
ExtensionHeader and
TLSKeyLabel have none anywhere in their
MRO, so they carry no declared-but-unassigned range. The rest define their own, except
the transport subclasses of AppType, which
inherit AppType._missing_.
Which of these behaviours a range gets is a design decision, not a style
preference:
extend_enum(cls, name, value)– mintCreates a permanent member, installed in
_member_map_and_value2member_map_, so iteration, lookup and__members__see it for the life of the process._unregistered_member()– unmintReturns a member-like object without installing it. The registry does not grow, and a second lookup builds an equal but distinct object.
super()._missing_(value)– hand back toaenumFor an
aenumflag base this composes a pseudo-member and caches it in_value2member_map_only, so__members__and iteration are unchanged but a second lookup returns the same object. For a plain enum it returnsNoneand the lookup raisesValueError; the measured case is an in-range hole in an unmint registry,BlockType(11).
Measured against the tree, by class:
Mints:
CGATypealone, whose values are 128-bit CGA extension tags rather than an IANA-style range of codes.Unmint: the bulk of the registries. The transport subclasses (
TCP,UDP,SCTP,DCCP) inherit the method; every other registry here calls_unregistered_memberitself, thoughAppTypenever reaches it: it holds no members and raises for every value through itscls.__registry__ is Noneguard.Some never reach the call either, because their own members cover every range they declare –
ToSDelaydeclares0..1and has members[0, 1], so its_unregistered_membercall is dead code. They areipv4.option_class,ipv4.tos_del,ipv4.tos_ecn,ipv4.tos_pre,ipv4.tos_rel,ipv4.tos_thr,ipv6.option_action,ipv6.seed_id,ipv6.smf_dpd_mode,l2tp.type,mh.dhcp_support_modeandvlan.priority_level.Cache in the value table only:
Flags,CommandType, the Mobility Header flag registries, such asBindingACKFlag, and the radiotap flag registries, such asFlags. They range-check and hand back tosuper()._missing_, which for anaenum.IntFlagreaches_create_pseudo_member_and ends incls._value2member_map_.setdefault(value, pseudo_member).Raise for any unknown value:
AppType,ExtensionHeader,TLSKeyLabel,Transport,TypeandManagementSubtype. The last three raise from their own range checks: each declared range is exactly its member set, so thesuper()._missing_line after the check is unreachable.
These are aenum classes: issubclass(Flags, enum.IntFlag) is False against
the standard library and True against aenum.IntFlag, so a probe against
enum answers about the wrong hierarchy.
So the mint criterion below applies only to the registries that unmint, where it
decides the name an unregistered member carries – the registrar’s label suffixed with
the code, or the bare block label – not whether it registers. Elsewhere it never runs:
CGAType names its tag from the value, the flag
registries compose a name from their members’ bits, and those that raise name nothing.
The crawlers that generate the ranges record this in their comments:
pcapkit/vendor/reg/ethertype.py’s UNASSIGNED_ROW_NAMES and
pcapkit/vendor/ipx/socket.py’s UNASSIGNED_RANGE_NAMES.
---
config:
flowchart:
curve: monotoneX
nodeSpacing: 30
wrappingWidth: 150
---
flowchart TD
LOOKUP["cls(value) finds no member"] --> MISSING["\_missing\_"]
MISSING -->|"out of range, or no block covers it"| RAISE["ValueError"]
MISSING -->|"CGAType only"| MINT["extend_enum<br/>permanent member, registry grows"]
MISSING -->|"flag registries"| FLAG["super().\_missing\_<br/>pseudo-member cached in the value table only"]
MISSING -->|"block label names a party"| SUFFIXED["\_unregistered\_member<br/>label + \_0x<code>"]
MISSING -->|"block label names a procedure"| BARE["\_unregistered\_member<br/>bare block label"]
RAISE ~~~ MINT ~~~ FLAG ~~~ SUFFIXED ~~~ BARE
The Criterion¶
Does the upstream registry treat the label as the final, concrete assigned name, or
only as a notation for a human reading the table? Settled in review of the
Socket._missing_ branch-order fix
(#841) and reaffirmed on #775 as a core concept of the ruling.
So ask what the upstream registry actually did, not what the generated code looks like:
The source assigns a real, specific name to those codes, so it is carried across and suffixed with each code.
The source says only that the codes are spoken for –
Unassigned,Reserved,Reserved for Private Use,Reserved for Experimental Use,Deprecated,Dynamically AssignedandStatically Assigned. These are notation for a human reader, so the bare label stands: suffixing it would manufacture a name nobody assigned, and the value gets its real name when something assigns it.
Note
In the transport registries – TCP,
UDP and the rest – the spans IANA assigns
to a service are an exception to the first case: x11, ircu, swx and the
others resolve with the bare service name, so TCP(6001).name is 'x11', not
'x11_6001'. Suffixing them would change .name, which a caller can observe.
Recorded on #2550.
Worked Examples¶
Bare label. pcapkit.const.ipx.socket’s Dynamically Assigned,
Dynamically Assigned Socket Numbers, Statically Assigned Socket Numbers and
Experimental ranges. Each names how the socket will be allocated, not what
occupies it.
Suffixed. pcapkit.const.reg.ethertype’s company names – Xyplex,
Datability, Qualcomm, Motorola and others, some holding more than one range
block – and pcapkit.const.ipx.socket’s Registered by Xerox. A company name
is the assignment, as the next section explains.
Other suffixed ranges in pcapkit.const.reg.ethertype are not company names:
IEEE802.3 Length Field names a field in a standard and Berkeley Trailer
encap/IP an encapsulation, so the next section’s reason does not cover them.
Note
The line between the two groups is not range versus single code. Registered by
Xerox covers a range and is suffixed per socket, because it names who registered
the socket. Dynamically Assigned covers a range and is not, because it names
only the mechanism by which some future party will take it. Ask whether the label
names a party or a procedure.
Suffixed Company Names¶
The ethertype case is not an exception. Settled on #775, after being raised
in review of the same
Socket._missing_ fix (#841):
a proprietary protocol will never have a public name, so the company name serves in its
place. It is not a note about the code but the best name that will ever exist for
it, which makes it the final concrete assigned name under the test above.
DEC Unassigned goes the other way despite the same attribution: the company holds
the block and assigned nothing, so the notation is “Unassigned” and the attribution is
incidental.
Checking the Current State¶
Measure the split: walk each _missing_ body with ast for the call it makes.
A text search is unreliable, since extend_enum also appears in imports and prose.
Match the attribute name as well as the bare name: extend_enum is a plain function
but _unregistered_member is called on cls, so a walk of ast.Name nodes alone
finds no unmint:
import ast, pathlib
for path in sorted(pathlib.Path('pcapkit/const').rglob('*.py')):
if path.name == '__init__.py':
continue
tree = ast.parse(path.read_text())
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef) and node.name == '_missing_':
calls = {n.func.id if isinstance(n.func, ast.Name) else n.func.attr
for n in ast.walk(node) if isinstance(n, ast.Call)
and isinstance(n.func, (ast.Name, ast.Attribute))}
if 'extend_enum' in calls:
print('MINT ', path)
elif '_unregistered_member' in calls:
print('UNMINT', path)
Note
The walk reads each file’s own _missing_, so it reports
AppType as an unmint although it raises,
prints nothing for the flag registries,
Transport,
Type and
ManagementSubtype (they call
neither function) nor for
ExtensionHeader and
TLSKeyLabel (they define no
_missing_), and does not see the transport subclasses, which inherit theirs.
CommandType is a flag registry; the UNMINT
lines for pcapkit/const/ftp/command.py are the file’s other registries.
Reconcile the walk against a runtime probe for the split above.
Warning
Calling Cls(value) on a registry that mints or hands back to a flag base
mutates the class: every later lookup finds what the probe installed.
CGAType
grows __members__, _member_map_ and _value2member_map_;
Flags,
CommandType and the Mobility Header and
radiotap flag registries grow _value2member_map_ only. Snapshot
{member.value for member in Cls} before any lookup regardless – that snapshot
cannot see the flag registries’ cache, so compare len(Cls._value2member_map_)
too – and use a throwaway process per registry when comparing behaviour across
revisions.