Repository navigation
protocols: migrate IPv4 option and HIP parameter dispatch to the registry pattern (#429) - #434
Merged
Merged
Conversation
#429) IPv4 and HIP were the last two of sixteen dispatch families to build a method name from the enum member and `getattr` it off the instance, with `register_option` / `register_parameter` installing handlers as class attributes via `setattr`. Both now carry an explicit class-level `defaultdict` read through `ProtocolBase._lookup_registry`, as TCP, SCTP, HOPOPT, IPv6-Opts, IPv6-Route, MH, HTTP/2 and PCAP-NG already do. - `IPv4.__option__`, 14 entries, fallback `'unassigned'`; `HIP.__parameter__`, 49 entries, fallback `'unassigned'`. `R1_Counter` (128) and `R1_COUNTER` (129) are keyed separately, since `code.name.lower()` collapsed them. - Both parse and construct sides migrated, including both branches of `_make_ipv4_options` and `_make_hip_param`. All six read sites go through `_lookup_registry`, so an unrecognised code no longer needs a leak guard it never had. - `register_option` / `register_parameter` warn on `code in cls.__option__` rather than on `hasattr(cls, f'_read_opt_{name}')`, which could not tell a user registration from a shipped handler. - A registered `(parser, constructor)` pair is now called with the signature `OptionParser` / `OptionConstructor` declares. Under `setattr` the pair became a descriptor, so dispatch passed `self` as the first positional argument and a handler written to the declared signature could not be called at all. - Docs: `__option__` / `__parameter__` documented on both pages, and the extensibility table in `ext.rst` collapses to the one uniform form. Dispatch is unchanged for every code: traced over all 30 IPv4 option numbers and 62 HIP parameter codes plus 13 synthesised off-enum codes, in both directions and both constructor branches, the selected handler is identical before and after. The 14 sample captures regenerate byte-identically and produce byte-identical `tree` and `json` output. Full suite 883 passed, 17 skipped; mypy clean on the changed files; pylint no worse.
Owner
Author
|
Reviewed at head No findings. Every claim in the PR body is independently reproducible, and I could not make any of the described equivalences fail. What I actually ran, all with
Nothing here rises to a blocking concern, and I did not find a claim in the body that the diff fails to support. Good to merge, from my read. |
JarryShaw
added a commit
that referenced
this pull request
Sep 17, 2026
…erpreter, not the code Three things, all consequences of #432, #434 and #439 landing under the branch. - `INTERPRETER_GAPS`: seven PCAP-NG name-resolution cases round-trip from Python 3.11 on and fail to construct on 3.10, so one recorded outcome per case no longer suffices. The root cause is #439 in the *schema* hierarchy: `NameResolutionBlock.post_process` asks `isinstance(record, (IPv4Record, IPv6Record))` at `pcapkit/protocols/schema/misc/pcapng.py:1248`, every `Schema` subclass shares one `_abc_impl` on <= 3.10 (measured: `EndRecord._abc_impl is IPv4Record._abc_impl` is True on 3.10.20, False on 3.14.7), and the block's terminating `EndRecord` therefore tests True and has `.names` read off it. Measured in the real path, not inferred. The `Info` data models are unaffected on both interpreters, and that boundary is asserted too. The table overrides rather than sits beside `EXPECTED_FAILURES`, because the three `ns_dns*` options fail on every interpreter but for different reasons. - `test_schema_isinstance_is_interpreter_dependent` pins that mechanism, so the seven are excused by evidence about a named library bug rather than by a version comparison. On >= 3.11 they are still held to `OK`; fixing #439 turns 3.10 red. - The two SMF_DPD entries: `hopopt-option/SMF_DPD` now round-trips and its entry is deleted, since #429's over-read fix landed with #432's progress guard. `ipv6-opts-option/SMF_DPD` raises instead of hanging and is re-recorded as `PARSE`. The two schema modules are line-for-line duplicates, so that is one fix applied once where it was needed twice. The sweep keeps its deadline: it guards the next non-progress defect, not this one. Also: #434 gave IPv4 and HIP real registries, so the generator now reads `HIP.__parameter__` instead of falling back to enum-crossed-with-handler. No library file is touched. 3.14.7: 258 cases, 173 round-trip. 3.10.20: 258 cases, 169 round-trip -- the difference is exactly the four cases in `INTERPRETER_GAPS` that pass on 3.14.
This was referenced Sep 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #429.
IPv4 and HIP were the last two of sixteen dispatch families still building a handler name from
code.name.lower()and reaching for it withgetattr. Both now carry an explicit class-leveldefaultdictread throughProtocolBase._lookup_registry, following HOPOPT/TCP/SCTP exactly — same declaration comment style, samelambda: '<fallback>', sameisinstance(name, str)branch, samewarnon re-registration.pcapkit/protocols/internet/ipv4.py:__option__at :196 (14 entries, fallback'unassigned'),register_optionat :481, reads at :590, :1234, :1265.pcapkit/protocols/internet/hip.py:__parameter__at :377 (49 entries, fallback'unassigned', named to matchSCTP.__parameter__),register_parameterat :648, reads at :745, :2890, :2907._make_*branches on each side — the list-of-tuples one and theOrderedMultiDictone — not just the parse side. That asymmetry is how Option, chunk and block registries leak on lookup miss, the same way __proto__ did #425 came to under-count its own scope.HIP_Parameter.R1_Counter(128, v1) andR1_COUNTER(129) are now keyed separately;code.name.lower()had collapsed them onto one handler._lookup_registry, so a miss cannot insert:grepfinds exactly two subscriptions in the two files, both theregister_*writes.docs/source/ext.rst's extensibility table collapses IPv4's and HIP's two-row_read_opt_${name}/_make_opt_${name}form into the uniform__registry__row, so all 18 rows are the same shape.pcapkit/foundation/registry/protocols.py:361also referencedpcapkit.protocols.internet.internet.IPv4, a module that does not exist; it now points at the registry.One behaviour change, and it is the reason to read this PR carefully
The string form is bit-for-bit unchanged, including the
RegistryErroron an unknown handler name. The tuple form changes calling convention, and the change is from broken to working.On
main,setattrinstalled the pair as class attributes, so dispatch bound them as methods and passedselfas the first positional argument. A pair written to the declaredOptionParser/OptionConstructorsignature could therefore never be called at all. Measured, registering a parser written exactly toCallable[[Schema_Option, NamedArg(Option, 'options')], Data_Option]:So a handler pair written as documented works now and did not before. The inverse is the breaking case: a pair written with an explicit leading
self— the only shape that could work onmain— will now break. Narrower second change: naming a tuple-registered handler by string afterwards used to succeed, becausesetattrhad created the attribute, and now raisesRegistryError. Every other registry-form family already behaved that way.No new public
registerfunction.IPv4.register_option,HIP.register_parameter,register_ipv4_optionandregister_hip_parameterkeep their signatures. What is new is inspectable surface —IPv4.__option__andHIP.__parameter__as documented class attributes, which is the issue's inspectability ask;len(IPv4.__option__)is 14 andlen(HIP.__parameter__)is 49.Evidence
The captures do not exercise this code, and saying otherwise would be the easy mistake here. Instrumenting
_read_ipv4_optionsand_read_hip_paramacross all 15 captures records 0 IPv4 options and 0 HIP parameters on the wire — every IPv4 header isihl=5and there is no HIP at all. So the byte-identical comparison proves no collateral regression and nothing about dispatch.Dispatch was therefore traced directly: every handler replaced by a self-recording stub, over all 30 IPv4 option numbers and all 62 HIP parameter codes plus 13 synthesised off-enum codes, in both directions and both constructor branches — 315 dispatch decisions, byte-identical between trees, all reached without error. Separately, resolving each of the 92 enum members through the registry lands on the same function object the
code.name.lower()path did, 0 mismatches, and_lookup_registryinserts nothing on a miss.Alongside that:
make_samples.pyregenerates all captures identically; 15 captures ×treeandjsonwithip=True, tcp=True, reassembly=Truegives 30 files, 17 MB,diff -rqclean; mypy 0 errors on the three changed modules;sphinx-buildexits 0 with 0 unresolved references and both new attributes rendering;isort --check-onlyclean.Suite: 877 passed / 17 skipped / 852 subtests on
origin/mainagainst 883 / 17 / 919 here — the delta is exactly the 6 new tests and their 67 subtests. One measurement trap worth recording: a first baseline run read 859/35, becausegit archiveproduces a directory that is not a git checkout andtests/test_tier_guard.pyskips all 18 of its tests there;git initin the baseline moves all 18 to passed.Known defects carried across unchanged, and three found on the way
Carried across deliberately, so this stays a refactor:
IPv4._make_opt_tspassesdata=where the schema field ists_data(nowipv4.py:1523, was :1488), still emittingUnknownFieldWarning. AndHIP._make_param_encryptedpassescipher=cipher_idwhereEncryptedParameterhas nocipherfield (nowhip.py:3519) — the brief had cited :3445.Found while working, all left alone as schema and behaviour changes rather than dispatch:
quick_start_data_selectorreturnsSchemaField(length=5, …)atpcapkit/protocols/schema/internet/ipv4.py:128whileQuickStartRequestOptiondeclares 8 octets. Measured identically on both trees:_make_opt_qsemits a correct 8-octet option19 08 00 40 44 88 cd 10, and reparsing consumes 5, resynchronises on the nonce's second byte0x88as if it were option 136, and diesProtocolError: IPv4: [OptNo 136] invalid format, withSchemaWarning: packet length < 0: -3.QuickStartReportOptionseparately emits 7 octets while writinglength=8. And_make_opt_qscomputesrate_val = floor(log2(rate*1000/40000)), which goes negative below 40 kbps and dies with a bareValueError: invalid literal for int() with base 2: b'0000-011'rather than an in-library error.IPv4._make_opt_unassigneddeclaresdata: 'bytes'keyword-only with no default (ipv4.py:1291) where every sibling fallback constructor defaults it tob'', so constructing an option with no dedicated constructor raisesTypeError: missing 1 required keyword-only argument: 'data'from both branches — including theOrderedMultiDictbranch, where the payload is sitting inoption.data. Pre-existing and identical on both trees; the leak test asserts theTypeErrorwith a comment saying why.origin/mainreportsipv4.py:1238: E1125: Missing mandatory keyword argument 'data' in method call; here it disappears, because the registry lookup widens the inferred type past static resolution. The runtime defect is unchanged — proved above — but the static warning that flagged it is gone, which a reviewer should know. That is the only pylint message that differs; the other change isW0404 Reimport 'timedelta'shifting a line.One thing deliberately not done: HIP's class docstring table still has only a "Parameter Parser" column where IPv4/HOPOPT/TCP have two. Adding 49 constructor rows is a docs change with no bearing on the migration.