Repository navigation
fix(mh): count a CGA extension's 4-octet header in its reported length - #528
Conversation
Closes #512. Closes #517. `MH._mh_option_length` adds the 2 octets an RFC 6275 §6.2 mobility option spends on its 1-octet Option Type and 1-octet Option Length. The three CGA extension readers reused it, but a CGA extension's Extension Type and Extension Data Length are 2 octets each (RFC 4581 §2, which formally updates RFC 3972), so every parsed extension reported a length two octets short of what it consumed: the 8-octet `00120004deadbeef` came back as 6. - mh.py: add `_mh_extension_length`, returning `schema_length + 4`, and route `_read_ext_none`, `_read_ext_multiprefix` and `_read_ext_exp` through it. `_mh_option_length` is correct and is left alone -- the defect was in its callers, not in the helper. - mh.py: convert `_read_opt_pad`'s open-coded `clen + 2` to the helper #509 created to collect exactly that arithmetic, and add the `Note:` its own docstring already pointed at ("the surviving explanation of that fix ... below"), which had never travelled with the copied paragraph. - ipv6_opts.py: cite RFC 8200 §4.2, where the quoted `Opt Data Len` sentence actually appears, rather than §4.3 -- that is the Hop-by-Hop Options header, and this class implements Destination Options (§4.6). - tests: pin both header widths side by side, assert every CGA reader's reported length equals `len(schema.pack())`, and pin `_read_opt_pad`'s delegation by patching the helper with a sentinel. Corrects `test_mh_experimental_cga_extensions_round_trip`, which asserted the packed length as payload+4 and the parsed length as payload+2 four lines apart. tests/protocols/internet: 191 passed, 654 subtests, no regressions.
|
✅ GOOD TO MERGE — the two length helpers ( |
Detailed review (independent verification, falsify-not-bless)Reviewed at head 1. Callers were wrong, not the helper — confirmed from the schema itself, not the PR's prose.
2. RFC 4581 §2 vs RFC 4866 / RFC 3972 §3 — the PR is right. Read the actual RFC text:
3. RFC 8200 §4.2 vs §4.6 (what issue #517 asked for) — the PR's citation is correct for the specific claim. Fetched RFC 8200 §4.2, §4.3, and §4.6 directly: §4.2 ("Options") is exactly where the quoted sentence "the length of the Option Data field of this option, in octets" lives. §4.6 ("Destination Options Header") defines the container that carries the TLVs ( 4. Fails-without-fix, reproduced independently. Reverting only
5. Falsification attempts — no wrong implementation found that slips past the shipped tests.
What could not be independently improved onNothing outstanding. One incidental finding during fixture generation (a VerdictAll five load-bearing claims (callers-not-helper, RFC 4581 authority, RFC 8200 §4.2 judgment call, fails-without-fix proof, and completeness of the caller migration) independently reproduce. Recommend merge. |
Closes #530. - `_hopopt_option_length` quoted "the length of the Option Data field of this option, in octets" and attributed it to RFC 8200 section 4.3. That sentence is the `Opt Data Len` definition from section 4.2; section 4.3 defines no `Opt Data Len` at all and its only length field is `Hdr Ext Len`, the whole header in 8-octet units. Now cites 4.2 for the quote and keeps 4.3 as the header this class implements, matching the two-part treatment #528 landed for the IPv6-Opts sibling. - `tests/test_docstring_contract.py` grows a fourth property: every verbatim RFC 8200 sentence this package quotes must be introduced by a citation naming the section that contains it. Keyed on the quote rather than the file, so it covers the next copy-paste of these paragraphs. - Drops the rotted `KNOWN_DEFECTS` entry for `pcapkit/vendor/ipx/packet.py`, whose `process` now takes and documents `data`. Unrelated to #530: #524 renamed the parameter and #535 added the rot guard four commits later, so `main` has been red on that subtest since the guard landed. No behavioural change: the docstring is prose, and `_hopopt_option_length` still returns `schema_len + 2`. Verified on .venv python 3.14.7 with PYTHONSAFEPATH=1 and pcapkit.__file__ asserted inside the worktree. The new check fails on unfixed hopopt.py (1 defect) and passes with the fix (0). Suite now fully green, exit code read from a file rather than a pipe: 66 passed, 449 subtests passed, EXIT=0.
…#530) (#538) Closes #530. - `_hopopt_option_length` quoted "the length of the Option Data field of this option, in octets" and attributed it to RFC 8200 section 4.3. That sentence is the `Opt Data Len` definition from section 4.2; section 4.3 defines no `Opt Data Len` at all and its only length field is `Hdr Ext Len`, the whole header in 8-octet units. Now cites 4.2 for the quote and keeps 4.3 as the header this class implements, matching the two-part treatment #528 landed for the IPv6-Opts sibling. - `tests/test_docstring_contract.py` grows a fourth property: every verbatim RFC 8200 sentence this package quotes must be introduced by a citation naming the section that contains it. Keyed on the quote rather than the file, so it covers the next copy-paste of these paragraphs. - Drops the rotted `KNOWN_DEFECTS` entry for `pcapkit/vendor/ipx/packet.py`, whose `process` now takes and documents `data`. Unrelated to #530: #524 renamed the parameter and #535 added the rot guard four commits later, so `main` has been red on that subtest since the guard landed. No behavioural change: the docstring is prose, and `_hopopt_option_length` still returns `schema_len + 2`. Verified on .venv python 3.14.7 with PYTHONSAFEPATH=1 and pcapkit.__file__ asserted inside the worktree. The new check fails on unfixed hopopt.py (1 defect) and passes with the fix (0). Suite now fully green, exit code read from a file rather than a pipe: 66 passed, 449 subtests passed, EXIT=0.
Closes #512.
Closes #517.
One commit on top of
c8fd97bcd. Both issues centre onpcapkit/protocols/internet/mh.py, so they land together.#512 — every parsed CGA extension reported a length 2 octets short
MH._mh_option_lengthreturnsschema_length + 2, which is the right arithmetic for an RFC 6275 §6.2 mobility option: its Option Type and Option Length are one octet each. The three CGA-extension readers (_read_ext_none,_read_ext_multiprefix,_read_ext_exp) reused it, but a CGA extension's header is twice as wide —Schema_CGAExtensiondeclarestype: EnumField(length=2)andlength: UInt16Field(), a 4-octet fixed header.The root cause is the callers, not the helper.
_mh_option_lengthis correct and is left untouched: a real mobility option gives_mh_option_length(2) == 4 == len(wire). What was wrong was routing a 4-octet-header structure through a 2-octet-header helper. So this adds a sibling_mh_extension_lengthreturning+ 4and points the three readers at it, rather than changing the shared helper and breaking its 89 correct callers.The write side was already right —
_make_cga_extensionshas always measuredlen(schema.pack())— so only the read side disagreed with the wire.RFC authority
The issue cites RFC 4866 and RFC 3972 §3. Neither is the right source, and the docstrings cite RFC 4581 §2 instead:
Updates: 3972in its header): "Extension Type: 16-bit identifier of the type of the Extension Field." / "Extension Data Length: 16-bit unsigned integer. Length of the Extension Data field of this option, in octets." Two 16-bit fields — a 4-octet header.Option Type/Option Length), a different structure.Ext Lenis the "[l]ength of the Extension in octets, not including the first 4 octets."#517 — the three follow-ups
_read_opt_padstill open-codedclen + 2. Converted toself._mh_option_length(clen), which is what refactor: extract duplicated wire-unit arithmetic across mh/ipv6_route/hopopt/ipv6_opts #509 created the helper to collect.hopopt.pyandipv6_opts.pyhad been converted; only themh.pybranch was left behind, so the arithmetic still existed twice in the file that introduced the helper._mh_option_length's docstring referred the reader to "the surviving explanation of that fix … below", but thatNote:lived only inhopopt.pyandipv6_opts.py— the paragraph was copied and its "below" did not travel with it._read_opt_padnow carries theNote:, explaining whyPad1is the helper's sole exception, and the cross-reference points at it.ipv6_opts.py:467. Now cites RFC 8200 §4.2, not the §4.6 the issue asks for. §4.2 ("Options") is where the sentence the docstring quotes verbatim — "Length of the Option Data field of this option, in octets" — actually appears; §4.6 is the Destination Options header this class implements, and it cross-references §4.2 rather than restating the arithmetic. The issue anticipated this: "(§4.2 is arguably the right citation for both, since it defines the option format itself.)" The docstring now names §4.6 as the header and §4.2 as the format, so both facts are on the page.Fails-without evidence
Environment:
.venv/bin/python3.14.7,PYTHONSAFEPATH=1 PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=<worktree>, withpcapkit.__file__asserted to resolve inside the worktree rather than the editable install.pytest-subtestsis not installed; pytest 9.1.1's native subtests report a failing subtest's parent asPASSED, so the exit code is the signal below, not the summary line.#512 — library reverted to
c8fd97bcd, new tests kept:#517 item 1 — same reverted library:
Fix restored, all four:
What cannot be proved this way, stated plainly
Note:exists or that an:rfc:role names §4.2; claiming a red/green proof for them would be manufacturing one. They are reviewed by reading, not by running.clen + 2and_mh_option_length(clen)return the same number, which is whytest_mh_padding_options_parse_from_the_wirepassed throughout and why refactor: extract duplicated wire-unit arithmetic across mh/ipv6_route/hopopt/ipv6_opts #509 was verified byte-identical. So the test pins the delegation rather than the value: it patches_mh_option_lengthwith a sentinel (lambda n: 1000 + n) and asserts the reader follows it. An open-coded branch ignores the patch and answers7; a converted one answers1005. That is what the issue actually asks about, and it does fail against unfixed code.Pad1is asserted to stay at1under the same patch, pinning the documented exception against a future "simplification" that routes it through the helper too.Regression surface
c8fd97bcd)tests/protocols/internet/test_mh_unit.pytests/protocols/internetEXIT=0tests/protocols/test_option_roundtrip_unit.pyEXIT=0EXPECTED_FAILURESintests/protocols/test_option_roundtrip_unit.pycannot be grepped or AST-parsed (it uses**unpacking), so it was imported and inspected directly: 59 entries, keyed<protocol>-<kind>/<NAME>, zero of them MH-related and zero extension-related. CGA extensions are not in the option/parameter registries that file iterates, so no entry flips and that file is untouched.Call-site accounting for
self._mh_option_length(: 92 → 90. Three removed (the CGA readers) and one added (_read_opt_pad). Verified by counting occurrences rather than lines —grep -ccounts lines and misses this.Out of scope
hopopt.py:456carries the same citation imprecision as #509 follow-ups: one call site left open-coded, a dangling cross-reference, and a wrong RFC section #517 item 3, citing RFC 8200 §4.3 for the quotedOpt Data Lensentence that lives in §4.2. §4.3 is the correct header for HOPOPT, so it is wrong in a milder way thanipv6_opts.pywas — but the quote still comes from §4.2. Not touched here; worth its own issue.pcapkit/protocols/schema/internet/mh.pyandschema/internet/ipv6_opts.pyare read-only inputs here. The schema is the evidence for the 4-octet header, and it is already correct — the defect was entirely in the protocol layer's interpretation of it._mh_extension_lengthand the converted_read_opt_padbranch, but the number is not measured here.