Skip to content

test(const): audit every registry case sensitivity against its source specification #903

Closed

Description

@JarryShaw

Is your feature request related to a problem? Please describe.

Case sensitivity of registry lookups is currently decided ad hoc. Four registries override get and one helper case-folds, and until now nobody had checked whether each one's behaviour matches what its source specification actually says.

The ruling that governs it, from #877:

okay, then we've found the verdict. and that's the logic - if RFC states the values are case-insensitive, then our enum should also treat them that way. otherwise, we should treat them case sensitive.

and the follow-up that makes this its own piece of work:

on (2) - we should audit all registries and then decide if case (in)sensitive.

Describe the solution you'd like

Audit every registry under pcapkit/const/ plus the non-registry helper enums — 124 and 27 respectively by the last count — against the RFC or IANA registry its values come from, and record per registry whether the specification makes those values case-insensitive. Then apply the ruling: case-insensitive lookup only where the source says so, case-sensitive everywhere else.

The deliverable is the audit table plus the changes it implies, and the table belongs in docs/source/conventions.rst beside the ruling so the next registry added has something to check itself against.

What is already established, so it need not be re-derived

class behaviour verdict
pcapkit/const/ftp/command.py Command value.upper() at :104 correct, keep — RFC 959 §4.1: "The command codes are four or fewer alphabetic characters. Upper and lower case alphabetic characters are to be treated identically."
pcapkit/const/http/method.py Method key.upper() at :232-242 defect — RFC 9110 §9.1: "The method token is case-sensitive because it might be used as a gateway to object-based systems with case-sensitive method names." Tracked as #896.
helper TransportProtocol case-insensitive, load-bearing: pcapkit/const/reg/apptype/apptype.py:101-102 does key.lower() against __members__, and :2480 calls TransportProtocol.get(proto.lower()) open — the maintainer named this as his example of legitimate case-insensitivity, so if nothing backs it the ruling bites two real call sites
pcapkit/const/pcapng/option_type.py OptionType get(key: 'int | str', …) open
pcapkit/const/reg/apptype/apptype.py AppType get(cls, key: 'int', …), int-keyed open, probably moot

Also settled: HTTP field names genuinely are case-insensitive (RFC 9110 §5.1), which is a different thing from method tokens and must not be conflated.

Method notes

Fetch RFC text with curl https://www.rfc-editor.org/rfc/rfcNNNN.txt and grep it — a WebFetch of these documents truncates before the relevant sections, which cost two wasted attempts.

Calling Cls(value) on a registry whose _missing_ mints mutates the class, so a probe is not a read: snapshot {m.value for m in Cls} first and use a throwaway process per registry.

Additional context

Depends on #877, whose phase 1 establishes the bare lookup base and records the convention. The base's get is case-sensitive by that ruling, so every case-insensitive registry becomes an explicit, justified override — which is what makes this audit actionable rather than academic.

Activity

  1. added
    testPull requests that add or correct tests (test: subject prefix)
    constRegenerated IANA or vendor constant tables; members keep their numeric values
    designA design or decision issue: a pattern being decided rather than a defect or a request
    on Sep 29, 2026
  2. JarryShaw commented on Sep 29, 2026

    @JarryShaw
    OwnerAuthor

    Checkable blocker, recorded so this label is not taken on trust: #877 phase 1.

    This audit's whole premise is that the bare lookup base's get is case-sensitive by default, so that every case-insensitive registry becomes an explicit, justified override. Until that base exists, "is this registry case-insensitive on purpose" has no baseline to be measured against, and the audit would have to be redone once the base lands.

    gh pr list -R JarryShaw/PyPCAPKit --state open --search "877 in:title,body" --json number,title
    gh issue view 877 -R JarryShaw/PyPCAPKit --json labels -q '[.labels[].name]|join(",")'

    Unblocks when #877 phase 1 merges. The two already-settled rows carry over and need no re-derivation: ftp.command.Command correct per RFC 959 §4.1, http.method.Method a defect per RFC 9110 §9.1 (tracked as #896).

  3. added
    blockedDeferred pending another issue or decision; see the last comment for what unblocks it
    on Sep 29, 2026
  4. JarryShaw commented on Sep 29, 2026

    @JarryShaw
    OwnerAuthor

    Three of this issue's starting rows are already answered, and one of them contradicts the ruling that created it. Carried back from #877 phase 1, which completed the audit before I narrowed its scope. I have verified the load-bearing parts myself.

    First, a count correction to this issue's own body: it is 125 registries, not 124. #890 (5e7f91ebf) moved TLSKeyLabel from a hand-written StrEnum helper in pcapkit/protocols/misc/pcapng.py into a generated registry at pcapkit/const/pcapng/tls_key_label.py, so one class changed columns. Measured on current origin/main: 125. I have been quoting 124 since before that merge. (The non-registry figure depends on whether nested classes like FrameType.Flags are counted — 19 by module-member walk, 26 including the six concrete nested Flags — so I am not asserting a single number for it here.)

    pcapkit/const/pcapng/option_type.py — nothing to justify; it does no case folding at all. :263-264 is if key in OptionType.__members__: return getattr(OptionType, key) — an exact match. It already conforms to the case-sensitive default. Verified directly. Close that row.

    reg/apptype/apptype.py AppType.get — moot as suspected, but it inherits the question. _dispatch refuses a non-int key outright, so no service-name lookup exists to fold. But :2480 does TransportProtocol.get(proto.lower()), so whatever is decided for TransportProtocol propagates here. (Harmless redundancy noted: that site lowers, and TransportProtocol.get lowers again.)

    TransportProtocol — and this is the one for you, because nothing in an RFC backs its case-insensitivity. RFC 6335 governs (it defines the IANA registry the crawler reads at pcapkit/vendor/reg/apptype/apptype.py:942). Both of its case-insensitivity statements are explicitly about Service Names, not transport protocols:

    §5.1: "Although service names may contain both upper-case and lower-case letters, case is ignored for comparison purposes, so both "http" and "HTTP" denote the same service."

    §8.1.1: "Service names are case-insensitive; they may be provided and entered into the registry with mixed case for clarity, but case is ignored otherwise."

    For the Transport Protocol field it says only "This field is currently limited to one or more of TCP, UDP, SCTP, and DCCP" — no comparison rule anywhere.

    So under a strict reading of your ruling — "if RFC states the values are case-insensitive, then our enum should also treat them that way. otherwise, we should treat them case sensitive" — apptype.py:101-102's key.lower() has no RFC statement behind it, and you named this class as your example of legitimate case-insensitivity.

    There is a real adjacent justification, though it is a different claim: the two authorities disagree on the field's spelling. RFC 6335 spells it upper case throughout (§8.1.1 and the §10.2 registry templates read | Transport Protocol | DCCP, SCTP, TCP, UDP |), while the live IANA CSV is exclusively lower case — measured over 14,536 rows: tcp 6608, udp 6357, empty 1467, sctp 93, dccp 11, zero upper-case. pcapkit's members follow IANA. So a caller holding the RFC's own spelling 'TCP' would fail a case-sensitive lookup against a member named tcp.

    The ask: does "the RFC says so" mean a comparison rule in the spec (strict — TransportProtocol loses case-insensitivity and two call sites change), or does a documented spelling disagreement between the RFC and the registry also count (lenient — it keeps it, justified by that mismatch rather than by a case rule)? Marking needs: decision for this one row only; the rest of the audit proceeds either way.

    An inverse finding worth this issue's attention: RFC 6335 §5.1 does make service names case-insensitive, and AppType's members are service names — but no service-name lookup exists in the code to fold. So there is an RFC-sanctioned case-insensitivity that is simply unimplemented, which is the opposite shape from every other row here.

    And a trap for the implementation, measured rather than assumed: Flag registries do grow _value2member_map_ on a composite lookup — _Flag.get(99) caches first|second|96. _member_names_ and __members__ stay untouched, so this is your ruling's Flag carve-out rather than minting, but a blanket "closed sets never grow" assertion will be false for flags.

  5. added
    needs: decisionWaiting on the maintainer to decide — not blocked by other work
    on Sep 29, 2026
  6. JarryShaw commented on Sep 29, 2026

    @JarryShaw
    OwnerAuthor

    I say lenient. TransportProtocol for example should be case-insensitive. Upper or lower cases are being used everywhere in RFC and IANA themselves so that's an indication of case insensitivity.

  7. JarryShaw commented on Sep 29, 2026

    @JarryShaw
    OwnerAuthor

    Ruling recorded, verbatim:

    I say lenient. TransportProtocol for example should be case-insensitive. Upper or lower cases are being used everywhere in RFC and IANA themselves so that's an indication of case insensitivity.

    So the criterion is settled and it is broader than "the spec states a comparison rule". A documented spelling inconsistency between the authorities is itself evidence of case-insensitivity — if RFC 6335 writes TCP throughout while the IANA registry writes tcp exclusively, neither authority is treating case as significant, and a lookup that does would reject a caller holding the spec's own spelling.

    TransportProtocol keeps its case-insensitivity, reg/apptype/apptype.py:101-102's key.lower() stays, and the two dependent call sites are unaffected. The measurement that motivated the question stands as the justification rather than as a problem: RFC 6335 §8.1.1 and its §10.2 templates write the field upper case, while the live CSV is lower case in all 14,536 rows (tcp 6608, udp 6357, empty 1467, sctp 93, dccp 11, zero upper-case).

    needs: decision removed. And blocked removed too — that blocker discharged while this was waiting. #903 was blocked on #877 phase 1, which merged as #906 (c50f442db) at 04:54:03Z. So EnumLookup now exists, its get is case-sensitive by default, and every case-insensitive registry is an explicit override needing justification — which is exactly the baseline this audit needs to measure against. Worker dispatched.

    Where the audit now stands, with your ruling applied:

    class behaviour verdict
    ftp.command.Command value.upper() at :104 keep — RFC 959 §4.1 states it outright
    helper TransportProtocol case-insensitive keep — this ruling; RFC/IANA spelling disagreement
    http.method.Method was key.upper() fixed — RFC 9110 §9.1 makes the token case-sensitive; #907
    pcapng.option_type.OptionType exact match, no folding nothing to do — verified, already conforms
    reg.apptype.apptype.AppType int-keyed only moot, but inherits TransportProtocol's behaviour via :2480

    That leaves the sweep over the remaining registries, which is what the worker is on. One shape to watch that the lenient reading makes more likely to recur: RFC 6335 §5.1 does explicitly make service names case-insensitive, and AppType's members are service names — but no service-name lookup exists in the code to fold. An RFC-sanctioned case-insensitivity that is simply unimplemented is the opposite shape from every other row here, and under the lenient criterion it is a candidate to implement rather than to leave.

  8. added
    wipWork in flight - a covering PR is open or an agent is actively on it
    and removed
    needs: decisionWaiting on the maintainer to decide — not blocked by other work
    blockedDeferred pending another issue or decision; see the last comment for what unblocks it
    on Sep 29, 2026
  9. removed
    wipWork in flight - a covering PR is open or an agent is actively on it
    on Sep 29, 2026
  10. added this to the 1.5 milestone on Oct 6, 2026
  11. moved this to Done in PyPCAPKiton Oct 6, 2026
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

    constRegenerated IANA or vendor constant tables; members keep their numeric valuesdesignA design or decision issue: a pattern being decided rather than a defect or a requesttestPull requests that add or correct tests (test: subject prefix)

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions