Repository navigation
fix(foundation): check every register_protocol_code key before registering any (#2268) - #2343
Conversation
|
Review: NEEDS CHANGES at
|
0ff7478 to
b621dc5
Compare
|
Amended |
|
Review: GOOD TO GO at
|
|
for |
|
Coverage: 91.58% (unit tier, Python 3.15,
Per-file detail: the |
b621dc5 to
79b88ee
Compare
|
Done for Pushed as |
|
i'd still prefer using |
|
Ruling noted: Type[ProtocolBase] with a type: ignore on the two abstract keys (Link, Internet). Dispatching the change. |
…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).
79b88ee to
3010e54
Compare
|
Pushed |
|
Review: GOOD TO GO at
|
|
Review: GOOD TO GO at
|
|
Ready to merge. Review: GOOD TO GO (Sonnet, round 4) at |
|
On hold for a design change: moving |
|
This merged before the class-attribute redesign I started above could land on it — opened as a follow-up instead: #2392. |
…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.
…tribute, not a module table (#2343) (#2392) 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.
…registries themselves Follow-up to #2392/#2343. _CODE_DESTINATIONS was a module-level dict mapping an enum registry type to the protocol class(es) owning the __proto__ dispatch registry keyed by that enum, so register_protocol_code could infer a destination from a bare enum member. Mirrors __proto_key__, which #2392 moved the opposite direction (destination -> enum) onto the destination classes themselves. Each of the 4 enum registry classes (EtherType, TransType, LinkType, PayloadProtocolIdentifier) now declares __proto_dest__, a tuple of ModuleDescriptor naming its destination class(es) lazily -- const sits below protocols in the dependency order, so a real class reference would make const import protocols; ModuleDescriptor (already used for __proto__ dispatch tables elsewhere) avoids that without inventing a parallel string-path scheme. _iter_code_targets reads the dunder off type(code) via getattr() and resolves each descriptor's .klass. getattr(), not vars(): verified aenum refuses to extend an enumeration that already has members, so none of these four registries can gain a subclass with its own registry -- the hazard __proto_key__'s vars() call guards against (Loopback/IPX owning a differently-keyed registry) is structurally impossible here. Where an enum registry is subclassed (AppType -> TCP/UDP/SCTP/DCCP) the base has no members and the parts share one key space, so inheriting the pairing is correct. const/reg/*.py and const/sctp/payload_protocol_identifier.py are regenerated, not hand-edited: the vendor crawlers (vendor/reg/*.py, vendor/sctp/payload_protocol_identifier.py) gain a PROTO_DEST class attribute, threaded through vendor/default.py's LINE template and context(); a throwaway script recovered each committed file's ENUM/MISS blocks and re-rendered through the unmodified template first, asserting byte-identity with the committed file, before re-rendering with PROTO_DEST to confirm the diff is purely the template's effect. Tests: tests/vendor/test_vendor_proto_dest_2392_unit.py (new) plus ProtoDestDeclarationTests in test_protocol_code_registration_unit.py and an update to test_dispatch_reachability_unit.py, which read the dunder instead of the deleted dict.
…registries themselves Follow-up to #2392/#2343. _CODE_DESTINATIONS was a module-level dict mapping an enum registry type to the protocol class(es) owning the __proto__ dispatch registry keyed by that enum, so register_protocol_code could infer a destination from a bare enum member. Mirrors __proto_key__, which #2392 moved the opposite direction (destination -> enum) onto the destination classes themselves. Each of the 4 enum registry classes (EtherType, TransType, LinkType, PayloadProtocolIdentifier) now declares __proto_dest__, a tuple of ModuleDescriptor naming its destination class(es) lazily -- const sits below protocols in the dependency order, so a real class reference would make const import protocols; ModuleDescriptor (already used for __proto__ dispatch tables elsewhere) avoids that without inventing a parallel string-path scheme. _iter_code_targets reads the dunder off type(code) via getattr() and resolves each descriptor's .klass. getattr(), not vars(): verified aenum refuses to extend an enumeration that already has members, so none of these four registries can gain a subclass with its own registry -- the hazard __proto_key__'s vars() call guards against (Loopback/IPX owning a differently-keyed registry) is structurally impossible here. Where an enum registry is subclassed (AppType -> TCP/UDP/SCTP/DCCP) the base has no members and the parts share one key space, so inheriting the pairing is correct. const/reg/*.py and const/sctp/payload_protocol_identifier.py are regenerated, not hand-edited: the vendor crawlers (vendor/reg/*.py, vendor/sctp/payload_protocol_identifier.py) gain a PROTO_DEST class attribute, threaded through vendor/default.py's LINE template and context(); a throwaway script recovered each committed file's ENUM/MISS blocks and re-rendered through the unmodified template first, asserting byte-identity with the committed file, before re-rendering with PROTO_DEST to confirm the diff is purely the template's effect. Tests: tests/vendor/test_vendor_proto_dest_2392_unit.py (new) plus ProtoDestDeclarationTests in test_protocol_code_registration_unit.py and an update to test_dispatch_reachability_unit.py, which read the dunder instead of the deleted dict.
…num registries themselves Follow-up to #2392/#2343. _CODE_DESTINATIONS was a module-level dict mapping an enum registry type to the protocol class(es) owning the __proto__ dispatch registry keyed by that enum, so register_protocol_code could infer a destination from a bare enum member. Mirrors __proto_key__, which #2392 moved the opposite direction (destination -> enum) onto the destination classes themselves. Each of the 4 enum registry classes (EtherType, TransType, LinkType, PayloadProtocolIdentifier) now declares __proto_dest__, a tuple of ModuleDescriptor naming its destination class(es) lazily -- const sits below protocols in the dependency order, so a real class reference would make const import protocols; ModuleDescriptor (already used for __proto__ dispatch tables elsewhere) avoids that without inventing a parallel string-path scheme. _iter_code_targets reads the dunder off type(code) via getattr() and resolves each descriptor's .klass. getattr(), not vars(): verified aenum refuses to extend an enumeration that already has members, so none of these four registries can gain a subclass with its own registry -- the hazard __proto_key__'s vars() call guards against (Loopback/IPX owning a differently-keyed registry) is structurally impossible here. The one enum registry that is subclassed, AppType, is irrelevant here only because it declares no __proto_dest__ at all -- a class-level destination would in fact be the wrong model for it, since register_apptype routes each member to the registry named by that member's own transport protocol, which no single shared destination could express. const/reg/*.py and const/sctp/payload_protocol_identifier.py are regenerated, not hand-edited: the vendor crawlers (vendor/reg/*.py, vendor/sctp/payload_protocol_identifier.py) gain a PROTO_DEST class attribute, threaded through vendor/default.py's LINE template and context(); a throwaway script recovered each committed file's ENUM/MISS blocks and re-rendered through the unmodified template first, asserting byte-identity with the committed file, before re-rendering with PROTO_DEST to confirm the diff is purely the template's effect. Tests: tests/vendor/test_vendor_proto_dest_2392_unit.py (new) plus ProtoDestDeclarationTests in test_protocol_code_registration_unit.py and an update to test_dispatch_reachability_unit.py, which read the dunder instead of the deleted dict.
…num registries themselves (#2403) Follow-up to #2392/#2343. _CODE_DESTINATIONS was a module-level dict mapping an enum registry type to the protocol class(es) owning the __proto__ dispatch registry keyed by that enum, so register_protocol_code could infer a destination from a bare enum member. Mirrors __proto_key__, which #2392 moved the opposite direction (destination -> enum) onto the destination classes themselves. Each of the 4 enum registry classes (EtherType, TransType, LinkType, PayloadProtocolIdentifier) now declares __proto_dest__, a tuple of ModuleDescriptor naming its destination class(es) lazily -- const sits below protocols in the dependency order, so a real class reference would make const import protocols; ModuleDescriptor (already used for __proto__ dispatch tables elsewhere) avoids that without inventing a parallel string-path scheme. _iter_code_targets reads the dunder off type(code) via getattr() and resolves each descriptor's .klass. getattr(), not vars(): verified aenum refuses to extend an enumeration that already has members, so none of these four registries can gain a subclass with its own registry -- the hazard __proto_key__'s vars() call guards against (Loopback/IPX owning a differently-keyed registry) is structurally impossible here. The one enum registry that is subclassed, AppType, is irrelevant here only because it declares no __proto_dest__ at all -- a class-level destination would in fact be the wrong model for it, since register_apptype routes each member to the registry named by that member's own transport protocol, which no single shared destination could express. const/reg/*.py and const/sctp/payload_protocol_identifier.py are regenerated, not hand-edited: the vendor crawlers (vendor/reg/*.py, vendor/sctp/payload_protocol_identifier.py) gain a PROTO_DEST class attribute, threaded through vendor/default.py's LINE template and context(); a throwaway script recovered each committed file's ENUM/MISS blocks and re-rendered through the unmodified template first, asserting byte-identity with the committed file, before re-rendering with PROTO_DEST to confirm the diff is purely the template's effect. Tests: tests/vendor/test_vendor_proto_dest_2392_unit.py (new) plus ProtoDestDeclarationTests in test_protocol_code_registration_unit.py and an update to test_dispatch_reachability_unit.py, which read the dunder instead of the deleted dict.
…ce sweep, fix other prose drift (#2428) Round 8 of the whole-repo prose sweep (#1103). Fanned out 8 shard agents by directory; most of the repo was clean. Changes: * pcapkit/protocols/internet/ipv6_ext.py + its .rst: finish the carry-over miss from round 7 -- the `_lookup_next_layer` plain pointer still targeted `ProtocolBase`, flipped to `Protocol` per #2420's ruling. * Same miss in corekit/sentinels.py (`__keywords__`), docs/source/ext.rst (three sites, including a verbatim twin of a paragraph #2420 already fixed in protocol.py), schema/schema.py and schema/link/ethernet.py. * internet.py, link.py, transport.py, sctp.py, protocol.py: `register()`'s Arguments: and a couple of Returns:/versionchanged clauses said `Protocol` subclass/instance while the signature types `ProtocolBase` and the adjacent Raises: clause already said so -- internal contradiction, fixed to `ProtocolBase` to match. * docs/source/contributing/conventions/protocol-layer-placement.rst: the Link-Application-ProtocolBase set difference is `__proto_key__`, not empty as claimed (#2343 added it after this page was written). * docs/source/contributing/conventions/registry-protocol.rst: "every registry overrides `_validate_value`" contradicted its own next paragraph, where the AppType transports inherit rather than override. * pcapkit/vendor/ipx/packet.py, vendor/reg/linktype.py: a self-contradicting claim and a stale counted-number, both in vendor docstrings. * docs/source/engines.rst, docs/source/conf.py, util/changelog_md.py, CONTRIBUTING.md: a stale scapy version, a wrong file path, a false "matches the Sphinx we build with" claim, and a missing caveat about actionlint making Lint non-advisory. No executable code changed -- every edit is inside a docstring, comment, or .rst file; confirmed by diff review and `import pcapkit` from this worktree. Four behavioral defects found but deliberately left unfixed (reported separately): 16 of 24 pcapkit/vendor crawlers' `process(self, data)` reads the module-global `DATA` instead of its own `data` argument (latent, harmless only because `request()` returns that same global); an empty `FilterType` registry behind a live TODO; three open "handle this programmatically" TODOs in pcapkit/vendor/mh; and dead `multiprocessing.Pool` scaffolding in vendor/__main__.py.
make testpasses, and a test case covers the changeWhat is the purpose of your pull request?
fix — bug fixDescription
Closes #2268.
register_protocol_codenow 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.origin/main. Passing on the rebased head.