Skip to content

fix(foundation): check every register_protocol_code key before registering any (#2268) - #2343

Merged
JarryShaw merged 1 commit into
mainfrom
fix/2268-protocol-code-mapping-check
Oct 27, 2026
Merged

JarryShaw merged 1 commit into
mainfrom
fix/2268-protocol-code-mapping-check

Conversation

@JarryShaw

Copy link
Copy Markdown
Owner

What is the purpose of your pull request?

  • fix — bug fix

Description

Closes #2268.

  • Fix: register_protocol_code now flattens every target and checks each key against its destination's enum before registering any — a wrong code can no longer replace a shipped entry, and a list that fails partway no longer leaves its earlier entries registered.
  • Fail-without-fix: 6 failed on origin/main. Passing on the rebased head.

@JarryShaw JarryShaw added this to the PyPCAPKit v1.5 milestone Oct 27, 2026
@JarryShaw JarryShaw added bug Issues reporting a defect (set by the bug report template; a default, not an assessment) fix Pull requests that fix a defect (fix: subject prefix) review: running A cross-review is in flight against the current head - no verdict yet labels Oct 27, 2026
@JarryShaw JarryShaw moved this to In review in PyPCAPKit Oct 27, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

Review: NEEDS CHANGES at 928fecc9f (Sonnet) — test-only gap, the code is correct.

  • Confirmed: checking happens before any registration (a mixed-bad mapping/list changes nothing); the destination table matches the 11 typed registrars and all 43 shipped keys; Loopback/IPX are correctly left unchecked (they have no typed registrar to mirror); the int and list forms both work. Fail-without-fix: 6 failed on origin/main. On the head, 107+262+150 passed.
  • Required: add a table-driven test pinning all 7 _DESTINATION_KINDS rows — mutating 8 of 28 table cells (dropping a row, or flipping number on SCTP/Link/Frame/PCAPNG) currently survives the test selection.
  • Non-blocking: Loopback/IPX stay genuinely unchecked by design, letting a numerically-equal wrong enum through with only a warning; a non-class destination mid-list raises a bare AttributeError rather than the house RegistryError; the bool gap is fix(foundation): _check_code(number=True) takes a bool, so register_tcp(True) adds port 1 #2269.

@JarryShaw JarryShaw added review: needs-changes Cross-review at the current head says changes are required; see the verdict comment and removed review: running A cross-review is in flight against the current head - no verdict yet labels Oct 27, 2026
@JarryShaw
JarryShaw force-pushed the fix/2268-protocol-code-mapping-check branch from 0ff7478 to b621dc5 Compare October 27, 2026 02:26
@JarryShaw

Copy link
Copy Markdown
Owner Author

Amended 0ff7478ba → b621dc51b: added test_every_destination_kind_row_is_enforced, which pins all 7 _DESTINATION_KINDS rows against the real registrars — a wrong-enum key, the number flag, and the row's own enum type, plus a set-equality check against the table. 14/14 single-cell mutants (dropping a row, flipping number) are killed. test_protocol_code_registration_unit.py + test_docstring_contract.py + test_tier_guard.py give 169 passed (unittest), and test_dispatch_registry_unit.py run alongside confirms nothing leaked into its exact-count assertions.

@JarryShaw JarryShaw added review: running A cross-review is in flight against the current head - no verdict yet and removed review: needs-changes Cross-review at the current head says changes are required; see the verdict comment labels Oct 27, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

Review: GOOD TO GO at b621dc51b (Sonnet, round 2) — test-only, the round-1 gap is closed.

  • Confirmed: all 14 single-cell mutants (7 row drops, 7 number flips) are killed, and the harness confirms it can tell a kill from a survivor by reproducing round 1 on the unamended test. No mocking loophole — _check_code runs for real before the mocked register is reached. The diff is test-only; the pcapkit/ tree hash is identical across all three heads.
  • Fail-without-fix: 6 distinct tests fail on origin/main (one ImportError plus the five original rejection cases). On the head, 26+8+135 passed.
  • Non-blocking: the new test's docstring overstates "by behaviour rather than a copy of the table" — the assertIs/set(...) checks are exactly that, and carry most of the kind-swap coverage; no positive case sends a valid AppType member through the mapping form.

@JarryShaw JarryShaw added review: good-to-go Cross-review at the current head says ready; CI state is separate and removed review: running A cross-review is in flight against the current head - no verdict yet labels Oct 27, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

for _CODE_DESTINATIONS and _DESTINATION_KINDS, can we use Type[EnumRegistry] and Type]ProtocolBase] types to replace the type annotations? to be more specific.

@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Coverage: 91.58% (unit tier, Python 3.15, 3010e54d1, Unit Tests run success)

Package Statements Missed Branches Partial Cover
pcapkit (top level) 104 4 20 2 95.16%
pcapkit/const 19601 881 2620 867 92.12%
pcapkit/corekit 2596 74 818 37 96.51%
pcapkit/dumpkit 282 3 102 3 98.44%
pcapkit/foundation 3036 23 1054 25 98.83%
pcapkit/interface 138 4 52 3 96.32%
pcapkit/protocols 17455 218 4598 185 98.15%
pcapkit/toolkit 914 36 334 10 95.51%
pcapkit/utilities 502 7 144 5 98.14%
pcapkit/vendor 4432 1988 1018 167 51.87%

Per-file detail: the coverage-html artifact of this run.

@JarryShaw
JarryShaw force-pushed the fix/2268-protocol-code-mapping-check branch from b621dc5 to 79b88ee Compare October 27, 2026 04:31
@JarryShaw

Copy link
Copy Markdown
Owner Author

Done for _CODE_DESTINATIONS (enum keys are now Type[EnumRegistry], confirmed every key is an EnumRegistry subclass). _DESTINATION_KINDS's values are also tightened the same way, but its keys stay as a bare type: Link and Internet are genuinely abstract in this codebase (they don't implement name/length/make/__index__/read — only their concrete subclasses like Ethernet and IPv4 do), and mypy's type-abstract check specifically refuses an abstract class where a dict key is typed Type[ProtocolBase] (it doesn't mind one in a tuple value, which is why _CODE_DESTINATIONS was fine). Narrowing the key type there would need a # type: ignore[type-abstract] on Link:/Internet:, which seemed worse than leaving the key bare. Say if you'd rather have the ignore instead.

Pushed as 79b88eee1. mypy on the file is back to its baseline 3 pre-existing errors (unrelated TransportProtocol | str lines), and the registry tests give 82 passed.

@JarryShaw JarryShaw added review: running A cross-review is in flight against the current head - no verdict yet and removed review: good-to-go Cross-review at the current head says ready; CI state is separate labels Oct 27, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

i'd still prefer using Type[ProtocolBase] and ignore mypy error if necessary

@JarryShaw

Copy link
Copy Markdown
Owner Author

Ruling noted: Type[ProtocolBase] with a type: ignore on the two abstract keys (Link, Internet). Dispatching the change.

@JarryShaw JarryShaw added review: running A cross-review is in flight against the current head - no verdict yet and removed review: running A cross-review is in flight against the current head - no verdict yet labels Oct 27, 2026
…ering any (#2268)

register_protocol_code (and the code= class keyword) handed each
{destination: key} pair straight to destination.register, so a member
of another enum, e.g. {Frame: TransType.ICMP}, replaced the shipped
LinkType.ETHERNET entry; and it registered targets as it flattened
them, so a bad later target left the earlier ones registered.

Flatten every target first, check each key with _check_code against
the enum of the class owning its destination's __proto__
(_DESTINATION_KINDS), then register. A destination keeping a registry
of its own (Loopback, IPX) is not checked.

Tests: RegisterProtocolCodeKeyCheckTests snapshot and restore the real
registries; the five rejection cases fail on main. A table-driven test
pins every _DESTINATION_KINDS row against the real registrars, killing
all 14 single-cell mutants (dropping a row, or flipping its number
flag).
@JarryShaw
JarryShaw force-pushed the fix/2268-protocol-code-mapping-check branch from 79b88ee to 3010e54 Compare October 27, 2026 04:41
@JarryShaw

Copy link
Copy Markdown
Owner Author

Pushed 79b88eee1 → 3010e54d1: _DESTINATION_KINDS's key is now Type[ProtocolBase], with # type: ignore[type-abstract] on exactly the two abstract keys (Link, Internet). mypy --warn-unused-ignores confirms both are load-bearing. Single-file mypy is back to the 3 pre-existing errors; the whole-package run is error-set-identical to origin/main (100 errors in both). Registry tests: 82 passed.

@JarryShaw

Copy link
Copy Markdown
Owner Author

Review: GOOD TO GO at 3010e54d1 (Sonnet, round 3) — also covers the owner-preferred Type[ProtocolBase]+ignore version pushed mid-review.

  • Confirmed: Link/Internet are the only two abstract classes among the destinations; the two # type: ignore[type-abstract] are both load-bearing (dropping either reintroduces exactly one error; a spurious ignore on a concrete row is flagged unused-ignore). Whole-package mypy is error-set-identical to origin/main (100 errors, both). The diff from the already-reviewed head is annotation/import/cast only — bytecode comparison confirms zero behavior change. Fail-without-fix: 7 failed (6 distinct tests) on origin/main. 265+262 passed on the head.
  • No cleaner alternative found: a TypeVar bound fails at module level; cast/deriving the table are equally mypy-clean trade-offs, not clearly better.
  • Non-blocking: a one-line comment above the two ignored rows explaining why would help, since the reason currently lives only in this thread.

@JarryShaw

Copy link
Copy Markdown
Owner Author

Review: GOOD TO GO at 3010e54d1 (Sonnet, round 4) — a fresh, independent pass, not a rubber-stamp.

  • Confirmed: mypy clean to the 3-error baseline, both single-file and whole-package (error sets genuinely identical, mapped through the real diff hunks). Both ignores are exactly load-bearing — stripping either reintroduces one error, adding one elsewhere is flagged unused-ignore. The owner's two comments are both satisfied. 268 passed; a simulated merge onto current main (which now includes fix(foundation): _check_code(number=True) takes a bool, so register_tcp(True) adds port 1 #2269) stays clean at 235 passed.
  • Fail-without-fix: 7 failed (6 distinct tests) on origin/main.
  • Non-blocking: while a key is abstract, mypy skips checking that row's value tuple — inherent to the design, covered at runtime by the existing test; a one-line comment on the two ignored rows is optional, no house precedent requires one.

@JarryShaw JarryShaw added review: good-to-go Cross-review at the current head says ready; CI state is separate and removed review: running A cross-review is in flight against the current head - no verdict yet labels Oct 27, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

Ready to merge.

Review: GOOD TO GO (Sonnet, round 4) at 3010e54d1 — fresh independent re-verification of the owner's final Type[ProtocolBase]/Type[EnumRegistry] preference with ignores.
CI: Required checks passed, 0 failures.
Mergeable, 0 unresolved threads.

@JarryShaw
JarryShaw merged commit 8d5c743 into main Oct 27, 2026
63 checks passed
@JarryShaw
JarryShaw deleted the fix/2268-protocol-code-mapping-check branch October 27, 2026 14:42
@github-project-automation github-project-automation Bot moved this from In review to Done in PyPCAPKit Oct 27, 2026
@JarryShaw JarryShaw added review: running A cross-review is in flight against the current head - no verdict yet and removed review: good-to-go Cross-review at the current head says ready; CI state is separate labels Oct 27, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

On hold for a design change: moving _DESTINATION_KINDS from a module-level mapping to a class attribute declared directly on each of Link/Internet/Frame/PCAPNG/TCP/UDP/SCTP, so the Enum registry and target protocol class _check_code validates against are explicitly specified where each class already declares __proto__, rather than kept in a separate table that has to stay in sync. Will post the revised diff shortly.

@JarryShaw

Copy link
Copy Markdown
Owner Author

This merged before the class-attribute redesign I started above could land on it — opened as a follow-up instead: #2392.

JarryShaw added a commit that referenced this pull request Oct 27, 2026
…tribute, not a module table (#2343)

Problem: _DESTINATION_KINDS mapped a protocol class to the Enum registry
_check_code validates its code against, found by walking the destination's
MRO for whichever ancestor owns __proto__ and looking that owner up in the
table. The pairing lived apart from the __proto__ it describes, and a new
destination needed both a new __proto__ and a new row in a module dict
elsewhere.

Fix: each of the seven destination classes (Link, Internet, Frame, PCAPNG,
TCP, UDP, SCTP) now declares its own __proto_key__ class attribute, alongside
__proto__, pairing its Enum registry with whether a bare int is also valid.
register_protocol_code reads it straight off the owner the MRO-walk already
finds (via vars(), not getattr, so a class keeping a registry of its own --
Loopback, IPX -- does not inherit its parent's pairing). _check_code also
takes the destination class explicitly now, so its error names which
registry refused, not just which enum: "TCP: code must be ... not True"
rather than "code must be ... not True". Every other call site of
_check_code (register_ethertype, register_transtype, every option/parameter
registrar) passes its own target the same way.

Two call sites write into two registries keyed alike and pass one of the two
as target, rather than checking twice for no behavioural difference:
register_linktype (Frame and PCAPNG) names Frame; register_apptype (TCP
and/or UDP, resolved after the check) names TCP.

Test: test_a_registry_of_its_own_does_not_inherit_its_parent_pairing pins the
Loopback/IPX case; test_a_rejection_names_the_destination_registry and
test_every_rejection_names_the_destination_registry (24 subtests, one per
call site) pin the new error prefix; test_every_destination_kind_row_is_enforced
now walks every ProtocolBase subclass declaring __proto_key__ rather than a
fixed dict's keys, so a new undeclared pairing is caught too.
@JarryShaw JarryShaw removed the review: running A cross-review is in flight against the current head - no verdict yet label Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Issues reporting a defect (set by the bug report template; a default, not an assessment) fix Pull requests that fix a defect (fix: subject prefix)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

fix(foundation): register_protocol_code's mapping form skips _check_code, replaces entries

1 participant