Skip to content

fix(vendor): source ExtensionHeader from the extension-header registry, not the protocol-numbers flag (#925) - #926

Merged
JarryShaw merged 1 commit into
mainfrom
fix/925-extension-header-registry
Sep 29, 2026
Merged

JarryShaw merged 1 commit into
mainfrom
fix/925-extension-header-registry

Conversation

@JarryShaw

@JarryShaw JarryShaw commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

What is the purpose of your pull request?

  • fix — corrects a defect

Description of your pull request and other information

  Fixes #925. ExtensionHeader's crawler pointed at the Protocol Numbers registry filtered on its IPv6 Extension Header flag, not the authoritative IPv6 Extension Header Types registry -- the two disagree on header 147 (BIT-EMU).

  LINK now points at extension-header.csv, and count/process are rewritten for its 3-column shape. That registry has no short Keyword column, so a NAMES override keeps the 8 affected members (HOPOPT, IPv6-Route, IPv6-Frag, ESP, AH, IPv6-Opts, HIP, Shim6) from being silently renamed -- verified by feeding the issue's own fixture CSV straight to the crawler (no network) and pinning all 11 surviving names/values.

  pcapkit/const/ipv6/extension_header.py now drops BIT_EMU, taking it to 11 members. IPv6._decode_next_layer's walk resolves ExtensionHeader(proto) at the top of its loop and used to rely on 147 succeeding there, via test_ipv6_ext_unit.py::test_unimplemented_terminal_code_stops_the_walk_not_the_packet -- but that test's own docstring says 147 was only ever the example: "253/254 are the same code path (also unregistered, also resolve to Raw)". Unlike 147, 253 is in the authoritative registry, so the test now builds its packet around 253 instead -- identical code path, no longer tied to a member being removed. That frees BIT_EMU to actually go. Updated the prose in ipv6.py/ipv6_ext.py that described BIT-EMU as reachable through the walk/generic extractor accordingly; TransType.BIT_EMU (a different, correctly-sourced enumeration) is untouched.

  The hand-applied const file is verified against the crawler itself, not just asserted: a new test feeds the fixture through ExtensionHeader.context() and diffs it byte-for-byte against the committed file. A real vendor regeneration against the live registry was still not run -- this environment disallows live crawler/network invocation -- so that byte-for-byte match is only against the fixture text the issue's own investigation fetched. The owner should run python -m pcapkit.vendor.ipv6.extension_header once to confirm the live registry still matches.

@JarryShaw JarryShaw added fix Pull requests that fix a defect (fix: subject prefix) const Regenerated IANA or vendor constant tables; members keep their numeric values test Pull requests that add or correct tests (test: subject prefix) breaking Breaks public-facing behaviour or API (apply alongside the type label) review: pending No verdict for the current head - never reviewed, or the head moved since the last one labels Sep 29, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

PR #926 is up but incomplete, and I have sent it back rather than labelling it ready. Recording why, because the reason is a genuine finding.

The worker correctly found a dependency and stopped: tests/protocols/internet/test_ipv6_ext_unit.py:353,364 builds its fixture from TransType.BIT_EMU and asserts code == ExtensionHeader.BIT_EMU, and pcapkit/protocols/internet/ipv6.py:378 resolves Enum_ExtensionHeader(proto) under except ValueError: break. Remove the member and that walk stops on 147, failing the test. Stopping to report was the right instinct.

But the conclusion does not follow, and the PR as shaped is a landmine. It repoints the vendor module at the 11-entry registry and leaves the const file at 12, so nothing breaks today — and the next person to run the generator silently loses BIT_EMU and hits that failure with no clue why. That turns a visible inconsistency into a latent one triggered by a future unrelated action.

The dependency is on an arbitrary example, not on 147. That test's subject is #891/#904's behaviour — an unimplemented code must stop the walk while keeping the IPv6 header intact — and its own docstring says so:

253/254 are the same code path (also unregistered, also resolve to Raw); this file covers one to keep the test proportionate, per the review's own framing.

Measured on 5e0ec3889:

147: in ExtensionHeader=True  in TransType=True  in IANA ext-hdr registry=False
253: in ExtensionHeader=True  in TransType=True  in IANA ext-hdr registry=True
254: in ExtensionHeader=True  in TransType=True  in IANA ext-hdr registry=True

So switching the example to 253 exercises the identical code path on a code IANA actually registers, and frees the const file to drop to 11.

Sent back to do that, invert the const-side guard (pin 11 members and ExtensionHeader(147) raising, instead of pinning BIT_EMU's presence), and update the prose at pcapkit/protocols/internet/ipv6_ext.py:67 that currently calls this discrepancy pending. pcapkit/const/reg/transtype.py:488 keeps its BIT_EMU = 147 — different enumeration, 147 legitimately belongs there.

Labelled review: pending; no verdict until the head settles.

…egistry, drop BIT_EMU (#925)

`pcapkit.vendor.ipv6.extension_header.ExtensionHeader` crawled the
*Protocol Numbers* registry (`protocol-numbers-1.csv`), filtered on its
`IPv6 Extension Header` column -- a derived signal, not the registry
RFC 8200 s4 names authoritative for this enumeration. That registry
disagreed with the authoritative *IPv6 Extension Header Types* registry
(`ipv6-parameters/extension-header.csv`) on header 147 (`BIT-EMU`): the
former flagged it `Y`, citing RFC 9801; the latter omits it entirely.

- Point `LINK` at `extension-header.csv` and rewrite `count`/`process`
  for its 3-column shape (`Protocol Number,Description,Reference` --
  no flag to filter on).
- That registry has no short `Keyword` column, unlike the old one, so
  add a `NAMES` override for the 8 members the old registry gave a
  keyword-derived name (`HOPOPT`, `IPv6-Route`, `IPv6-Frag`, `ESP`,
  `AH`, `IPv6-Opts`, `HIP`, `Shim6`) -- deriving names straight from
  the verbose Description column instead would silently rename all
  eight, which is worse than the registry defect being fixed.
- Drop `BIT_EMU` from `pcapkit.const.ipv6.extension_header`, taking it
  to 11 members -- hand-applied to match what the fixed crawler
  produces from the fixture below byte-for-byte (proven by a new test,
  not merely asserted).
- `IPv6._decode_next_layer`'s walk resolves `ExtensionHeader(proto)` at
  the top of its loop and used to rely on 147 succeeding there.
  `test_ipv6_ext_unit.py`'s `test_unimplemented_terminal_code_stops_
  the_walk_not_the_packet` picked 147 only as an *example* of an
  unimplemented extension-header code (its own docstring: "253/254 are
  the same code path"); switched it to 253, which is actually in the
  authoritative registry, so the test exercises the identical code
  path without depending on a member being removed. Updated its
  docstring and the prose in `ipv6.py`/`ipv6_ext.py` that described
  BIT_EMU as one of the codes reachable through the walk/generic
  extractor -- it no longer is, by construction, since it is not an
  `ExtensionHeader` member at all any more.
- `pcapkit.const.reg.transtype.TransType.BIT_EMU` is untouched: a
  different, correctly-sourced enumeration (147 legitimately belongs
  in the Protocol Numbers registry).

New unit tests: the vendor suite feeds the issue's fixture CSV
directly to the crawler (no network) and pins LINK, all 11 names/
values, BIT_EMU's absence, which comments genuinely diverge from
history because the two registries carry different Reference text,
and -- the seam between the two halves of this fix -- that
`ExtensionHeader.context()` run against the fixture reproduces the
committed const file byte-for-byte. The const suite pins the member
count, that `ExtensionHeader(147)` now raises, that `TransType.BIT_EMU`
is unaffected, and that the IPv6 walk still stops cleanly (no crash,
header fields intact, no extension header recorded) on a next-header
byte of 147 now that nothing maps it.

Build: `python -m unittest` green on both new suites, the full
`test_ipv6_ext_unit`, `test_const_registry_protocol` and
`test_isort_clean`. A real vendor regeneration was not run against the
live registry -- network crawls are disallowed in this environment --
so the committed const file is a hand-applied stand-in for that
regeneration's output, verified only against the fixture text this
issue's own investigation fetched. The owner should run the crawler
for real once to confirm the live registry still matches that fixture.
@JarryShaw
JarryShaw force-pushed the fix/925-extension-header-registry branch from 172f50a to 329067a Compare September 29, 2026 17:37
@JarryShaw

Copy link
Copy Markdown
Owner Author

GOOD TO GO at 329067a6b — Opus cross-review (author was Sonnet), and it both closed the PR's admitted gap and corrected a framing error of mine. Three claims re-derived by me.

The "run the regeneration once" caveat is discharged. The reviewer drove the fixed crawler against the live registry it fetched itself and got a byte-for-byte match with the committed const file. I confirmed the chain independently:

curl .../ipv6-parameters/extension-header.csv  -> 11 rows, 504 bytes
  codes: 0 43 44 50 51 60 135 139 140 253 254     147 present: 0
FIXTURE_CSV vs live:  12 rows each, IDENTICAL

So live registry → fixture → committed file, and the fixture-to-file leg is a passing test rather than a claim. The only thing still unexercised is Vendor.request's HTTP call, not the parse or the output.

A correction to what I wrote when I sent this back. I said 253 exercises the same except ValueError: break guard that 147 used to. That is wrong, and the reviewer caught it. pcapkit/protocols/internet/ipv6.py has two exits — the ValueError guard at :382-386 and the structural if not hasattr(info, 'next') check at :448-450. 253 is a registered member, so it resolves past the first and stops on the second. I verified: EH(253) returns, EH(147) raises.

That does not weaken the substitution — 147 was also a member before, so it took the identical structural path, and the test's own coverage is unchanged. But the reviewer's mutation testing shows the PR ends up covering both guards where one test covered one: removing the structural guard breaks the 253 test with the verbatim #891 signature AttributeError: 'Raw' object has no attribute 'next', and removing the ValueError guard breaks the new 147 test instead.

147 now behaves better, not merely differently. With the member gone it dispatches as an ordinary upper-layer protocol through TransType, so a packet with next-header 147 keeps src/dst/hop_limit intact, yields Raw, records no extension headers, and reads IPv6:BIT_EMU.

Scope discipline, checked by AST rather than by eye — my own comparison of ipv6.py at 5e0ec3889 versus this head, docstrings stripped: AST identical, raw text differs. Comment and prose only; no executable line moved.

Also confirmed: the NAMES override is exactly complete — blanking it renames exactly 8 codes [0, 43, 44, 50, 51, 60, 139, 140], and that is precisely its key set. Keyed by protocol number, so a future Description edit cannot bypass it for those 8.

Three non-blocking nits recorded, none needing a revision: count() uses an unguarded int(item[0]) where process() guards with isdigit(); the vendor suite's setUp skips rather than fails if the module resolves outside ROOT, so a leaked import would silently skip the byte-identity test (0 skips on this run); and assertNotIn('147', generated) is a whole-file substring check that a future RFC number containing 147 would trip.

review: good-to-go granted. Unmerged and unpublished — yours to merge.

@JarryShaw JarryShaw added review: good-to-go Cross-review at the current head says ready; CI state is separate and removed review: pending No verdict for the current head - never reviewed, or the head moved since the last one labels Sep 29, 2026
@JarryShaw
JarryShaw merged commit 53d6713 into main Sep 29, 2026
63 checks passed
@JarryShaw
JarryShaw deleted the fix/925-extension-header-registry branch September 29, 2026 18:29
@JarryShaw JarryShaw removed the review: good-to-go Cross-review at the current head says ready; CI state is separate label Sep 29, 2026
JarryShaw added a commit that referenced this pull request Sep 30, 2026
…otocol-numbers-1.csv (#931)

`ESP` and `AH` justified their double inheritance by citing
`protocol-numbers-1.csv`'s *IPv6 Extension Header* column -- the
derived signal #926 repointed the const generator away from, precisely
because it disagreed with the authoritative registry (it flagged
BIT-EMU/147 as an extension header; the real registry omits it).

- esp.py:979-982 -- now cites IANA's *IPv6 Extension Header Types*
  registry (ESP = 50) and RFC 4303 section 3.1.1's IPv6-context
  sentence -- "should appear after hop-by-hop, routing, and
  fragmentation extension headers" -- which supports IPv6
  extension-header membership, the claim being made.
- ah.py:55-58 -- same fix, citing RFC 4302 section 3.1.1's identical
  IPv6-context sentence for AH.
- Kept the second half of each sentence (that the package's own
  ExtensionHeader registry agrees), now a check against the
  authoritative source rather than corroboration of the wrong one.
- Checked hip.py for the same stale citation; it already cites the
  extension-header registry and RFC 7401, so no change needed there.

Both sections also carry an IPv4-context sentence about transport-mode
placement, which supports a different claim (standalone-protocol
status under the extension-header-subclassing convention) and does
not evidence IPv6 membership; quoting it here was caught in review and
corrected to the IPv6-context sentence instead.

Added a regression test per protocol asserting the docstring cites the
IPv6-context wording and not protocol-numbers-1.csv or any IPv4
placement phrasing; both fail against 3823758's docstrings and pass
here.

Build: mypy 321/38 (unchanged), pylint 8.67/10 exit 30 (+0.00,
unchanged), isort clean, targeted esp/ah unit tests 35 passed.
JarryShaw added a commit that referenced this pull request Sep 30, 2026
…otocol-numbers-1.csv (#931)

`ESP` and `AH` justified their double inheritance by citing
`protocol-numbers-1.csv`'s *IPv6 Extension Header* column -- the
derived signal #926 repointed the const generator away from, precisely
because it disagreed with the authoritative registry (it flagged
BIT-EMU/147 as an extension header; the real registry omits it).

- esp.py:978-989 / ah.py:54-63 -- extension-header *membership* now
  rests on IANA's *IPv6 Extension Header Types* registry and this
  package's own `ExtensionHeader`, per conventions.rst's "being in
  that registry is what makes something an extension header" ruling.
  RFC 4303/4302 section 3.1.1 is quoted too, but only for its actual
  IPv6-context placement sentence (after hop-by-hop, routing and
  fragmentation), not as membership evidence -- an earlier revision of
  this fix rephrased that sentence as "places it among the IPv6
  extension headers", which overstated the RFC and contradicted this
  same docstring's `Note:` on RFC 8200 declining ESP's extension-header
  status; caught in review and reworded to a plain placement
  description.
- Added the standalone-protocol half #931 asks for: the same RFC
  section's separate IPv4-context sentence (placed after the IP header,
  before the next-layer protocol) is the primary-source evidence for
  ESP/AH's other base, exactly as hip.py cites its own IPv4 appendix.
- Checked hip.py for the same stale citation; already correct.

Regression tests assert the registry citation, the IPv6-context
placement wording, and the standalone-protocol sentence, while only
banning the specific wrong phrasing from the first fix attempt rather
than "IPv4" outright -- the IPv4 sentence is legitimate prose here.
Both fail against 3823758, and against the fix's two prior (defective)
revisions, before passing here.

Build: mypy 321/38 (unchanged), pylint 8.67/10 exit 30 (unchanged),
isort clean, targeted docstring tests pass (full esp/ah suite not
re-run: test_esp_unit.py is slow, run targeted instead).
JarryShaw added a commit that referenced this pull request Sep 30, 2026
…otocol-numbers-1.csv (#931)

`ESP` and `AH` justified their double inheritance by citing
`protocol-numbers-1.csv`'s *IPv6 Extension Header* column -- the
derived signal #926 repointed the const generator away from, precisely
because it disagreed with the authoritative registry (it flagged
BIT-EMU/147 as an extension header; the real registry omits it).

- esp.py:978-993 / ah.py:54-67 -- extension-header *membership* now
  rests on IANA's *IPv6 Extension Header Types* registry and this
  package's own `ExtensionHeader`, per conventions.rst's "being in
  that registry is what makes something an extension header" ruling.
  RFC 4303/4302 section 3.1.1 is quoted too, but only for its actual
  IPv6-context placement sentence, not as membership evidence -- an
  earlier revision rephrased that sentence as "places it among the
  IPv6 extension headers", overstating the RFC and contradicting this
  docstring's own `Note:` on RFC 8200 declining ESP's extension-header
  status; caught in review and reworded to a plain placement
  description.
- Added the standalone-protocol half #931 asks for: the section's
  separate IPv4-context sentence is the primary-source evidence that
  ESP/AH also travel directly as an IPv4 payload, which *qualifies*
  an already-standalone header for a second base -- it does not *make*
  the header standalone (conventions.rst's MH case settles that), and
  the RFC has no say in which base is named. An earlier revision said
  the RFC "makes it a standalone protocol" and "names IPsec as the
  second base"; the latter was positionally false (`ESP.__bases__`
  puts IPsec first, not second) and unsupported by the cited section.
  Caught in review and reworded to match hip.py's own phrasing.
- Checked hip.py for the same stale citation; already correct.

Regression tests assert the registry citation and the corrected
prose, ban the specific wrong phrasings from all three earlier fix
attempts, and pin the registry-membership clause structurally (via a
regex on the parenthetical immediately following "registry lists ...
at <n>") so that a future rewording of the IPv4 evidence, not just
this exact phrase, would still be caught if attached to the wrong
claim. All fail against 3823758 and against the fix's three prior
revisions, before passing here.

Build: mypy 321/38 (unchanged), pylint 8.67/10 exit 30 (unchanged),
isort clean, targeted docstring tests pass (full esp/ah suite not
re-run: test_esp_unit.py's module import alone is ~11 minutes).
@JarryShaw JarryShaw added this to the 1.5 milestone Oct 6, 2026
@JarryShaw JarryShaw moved this to Done in PyPCAPKit Oct 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking Breaks public-facing behaviour or API (apply alongside the type label) const Regenerated IANA or vendor constant tables; members keep their numeric values fix Pull requests that fix a defect (fix: subject prefix) test Pull requests that add or correct tests (test: subject prefix)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

fix(vendor,const): source ExtensionHeader from IANA's extension-header registry, not the protocol-numbers flag

1 participant