Skip to content

fix(corekit): IPv4InterfaceField keeps non-netmask masks; OrderedMultiDict deepcopy keeps order - #1346

Merged
JarryShaw merged 1 commit into
mainfrom
fix/1294-1295-ipv4iface-omd-copy
Oct 7, 2026
Merged

JarryShaw merged 1 commit into
mainfrom
fix/1294-1295-ipv4iface-omd-copy

Conversation

@JarryShaw

@JarryShaw JarryShaw commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner
  • Searched for similar pull requests
  • Followed the coding style (make pylint, make mypy, make isort) — mypy/pylint (Makefile flags) run on ipaddress.py only; isort not run
  • make test passes, and a test case covers the change — only the modules listed below, plus tests/project
  • Added a changelog entry under docs/source/changelog/ and regenerated CHANGELOG.md, if the change is user-visible — N/A, added centrally

What is the purpose of your pull request?

  • fix — corrects a defect

Description of your pull request and other information

Closes #1294
Closes #1295

Base: 13aa677be.

Tests: test_ipv4interface_mask_unit checks 100,063 masks (33 netmasks, 33 hostmasks and seeded random ones) for pack(str(unpack(wire))) == wire. It compares every stdlib-legal string with ip_interface(s).netmask (all 33 prefixes, netmasks and hostmasks) and checks 10 malformed strings. With fef87f31e's ipaddress.py: 38 failed, 6 passed. With main's: the earlier proof stands. All pass with the fix, as do test_ordered_multidict_copy_unit, test_fields_ipaddress, test_multidict, test_pcapng_unit and tests/project. The corekit leg reports the same 5 test_sentinel_exports_unit failures with and without this change on 13aa677be.

Fixtures: per-frame info over the 23 examples/captures files is identical before and after. The count is regenerated by make_samples, so it is not stable; this tree measured 1614 frames.

@JarryShaw JarryShaw added fix Pull requests that fix a defect (fix: subject prefix) breaking Breaks public-facing behaviour or API (apply alongside the type label) review: running A cross-review is in flight against the current head - no verdict yet labels Oct 7, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

Cross-review verdict on 69466d9ed: NEEDS CHANGES (Sonnet reviewed; author Opus). The parse→repack fix is sound. One round trip still breaks.

The field cannot pack its own parsed value's string form. I measured it in the PR worktree:

wire str(v) pack(str(v))
0a000001000000ff 10.0.0.1/0.0.0.255 0a000001ffffff00 — wrong
0a000001ff00ff00 10.0.0.1/255.0.255.0 FieldValueError
0a000001ffffff00 10.0.0.1/24 exact

pre_process routes strings through the stdlib, which reads 0.0.0.255 as a hostmask and rejects 255.0.255.0. Teach it to parse an addr/dotted-mask string octet-for-octet into _RawMaskIPv4Interface whenever the mask is not a contiguous netmask, and add the make-from-string case to the tests.

Also fix: __lt__ is inherited and ignores the mask while __eq__ uses it. For a=…/255.0.255.0 and b=…/0.0.0.255, a<b and b<a are both False, but a>=b is True. Make the ordering consistent with __eq__. with_prefixlen reporting /32 should be documented at least.

On rejection vs keeping: no new ruling is needed. The owner's standing every-round-trip-matches ruling means a mask that is on the wire must survive the rebuild. The old rejection test came from #465, whose point was a FieldValueError rather than a bare ValueError, not a policy against non-contiguous masks. The flip is right; it stays breaking.

Correction to the review: it reported with_netmask as /255.255.255.0 for a 0.0.0.255 mask. I measured 10.0.0.1/0.0.0.255, which is consistent with .netmask.

#1295 confirmed:

  • Ported code untouched: multidict.py gains 12 lines and deletes none, so no ported werkzeug line changed.
  • Deepcopy behaviour: order, ==, subclass type and deep-copied values all hold, and shared values stay shared.
  • Self-reference: a self-referencing value raises RecursionError exactly as it does on main.
  • Fixture info and corekit leg: fixture info is identical to main, and the 5 corekit failures are test(corekit): test_sentinel_exports_unit fails 5 tests in the corekit unittest leg #1332's.

@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 7, 2026
@JarryShaw JarryShaw moved this to In review in PyPCAPKit Oct 7, 2026
@JarryShaw
JarryShaw force-pushed the fix/1294-1295-ipv4iface-omd-copy branch from 69466d9 to fef87f3 Compare October 7, 2026 20:17
@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 7, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

Round-2 verdict on fef87f31e: GOOD TO GO (Sonnet reviewed; author Opus). Both round-1 defects are fixed.

  • String round trip. pack(str(unpack(wire))) == wire held for 300,300 cases: 100,100 masks (all 33 contiguous, all 33 hostmask shapes, every pattern over {0, 1, 128, 255}, and random masks) across 3 addresses. There were 0 mismatches.
  • Ordering. Over 2,502 mixed plain and raw values: 300,000 pairs satisfy exactly one of <, == and >, 200,000 triples are transitive, and sorted() is deterministic. Mixing with a plain IPv4Interface never raises. hash is consistent with == on every value the field can produce.
  • Tests. The revert proof fails 9 + 2 + 1 tests. The 5 corekit failures are the ones tracked in test(corekit): test_sentinel_exports_unit fails 5 tests in the corekit unittest leg #1332.

Merge depends on #1294's ruling. The string-parsing change is the one now waiting on the owner: '10.0.0.1/0.0.0.255' packs literally here, and main reads it as a hostmask. The reviewer measured the full blast radius against main. Literal now: /0.0.0.255, /0.0.0.1, /0.255.255.255, and the non-contiguous masks that used to raise. Unchanged: /0.0.0.0, /255.255.255.255, /128.0.0.0, /24, /255.255.255.0, and the IPv6-looking inputs. Malformed masks still raise FieldValueError; only the message text changed. An IPv4Interface object with a 0.0.0.255 hostmask still packs /24. Under option (2) the string half would need to come back out.

Nits: a _RawMaskIPv4Interface built directly with a contiguous mask has a different hash from the equal plain value. The field never builds one, so this is reachable only through the private class. A raw value also compares == to an IPv4Address with the same ip, where a plain interface does not.

@JarryShaw JarryShaw added review: good-to-go Cross-review at the current head says ready; CI state is separate 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 review: good-to-go Cross-review at the current head says ready; CI state is separate labels Oct 7, 2026
…iDict deepcopy keeps order

- IPv4InterfaceField.post_process read a hostmask-shaped mask (0.0.0.255)
  as a prefix length and packed a different netmask; a non-contiguous mask
  raised. A mask that is not a contiguous netmask now unpacks to an
  IPv4Interface subclass that keeps the four octets. Its str() is dotted,
  or 0xXXXXXXXX when the dotted form would read as a hostmask, and
  pre_process reads either form literally only when ip_interface rejects
  the string, so stdlib-legal strings keep their meaning and every wire
  mask survives str() and pack. Equality and ordering use the mask (#1294).
- OrderedMultiDict.deepcopy rebuilds from items(multi=True) instead of the
  inherited, key-grouping to_dict(flat=False); the werkzeug port of
  MultiDict is left verbatim (#1295).

Closes #1294
Closes #1295
@JarryShaw
JarryShaw force-pushed the fix/1294-1295-ipv4iface-omd-copy branch from fef87f3 to 5bb3477 Compare October 7, 2026 21:05
@JarryShaw

Copy link
Copy Markdown
Owner Author

Round-3 verdict on 5bb347792: GOOD TO GO. This round reworks the PR to the owner's #1294 ruling, and I checked it against that ruling myself.

Strings the stdlib accepts keep the stdlib's meaning:

string packs stdlib mask
10.0.0.1/0.0.0.255 (hostmask) …ffffff00 ffffff00
10.0.0.1/24, /255.255.255.0 …ffffff00 ffffff00
10.0.0.1/255.0.255.0 …ff00ff00 (literal) rejects
10.0.0.1/0x000000ff …000000ff (literal) rejects

str() of a parsed value never produces a string the stdlib would read differently, and it still packs back exactly:

  • 000000ff on the wire becomes 10.0.0.1/0x000000ff, which the stdlib rejects, so it cannot be misread.
  • ff00ff00 becomes 10.0.0.1/255.0.255.0.
  • ffffff00 becomes 10.0.0.1/24.

The hex form follows BSD ifconfig and keeps all 32 bits. The class docstring documents it.

Evidence:

  • Round trip: a property test over 100,063 masks gives pack(str(unpack(w))) == w for every one.
  • Stdlib agreement: for 33 prefixes, 33 netmasks and 33 hostmasks, pack(s) matches ip_interface(s).netmask.
  • No regression: across 142 strings compared with main, the only differences are the two that raise on main.
  • Tests: the revert against fef87f31e gives 38 failures. The 5 corekit failures are tracked in test(corekit): test_sentinel_exports_unit fails 5 tests in the corekit unittest leg #1332. Per-frame fixture info is identical to main.

@JarryShaw JarryShaw added review: good-to-go Cross-review at the current head says ready; CI state is separate and removed review: needs-changes Cross-review at the current head says changes are required; see the verdict comment labels Oct 7, 2026
@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Coverage: 88.97% (unit tier, Python 3.14, 5bb347792, Unit Tests run success)

Package Statements Missed Branches Partial Cover
pcapkit (top level) 104 4 20 4 93.55%
pcapkit/const 18777 1032 2348 843 90.27%
pcapkit/corekit 2018 89 628 25 94.71%
pcapkit/dumpkit 189 1 68 1 99.22%
pcapkit/foundation 2443 102 852 39 94.39%
pcapkit/interface 112 7 40 5 92.11%
pcapkit/protocols 16013 206 4178 177 98.06%
pcapkit/toolkit 539 74 168 3 84.58%
pcapkit/utilities 429 4 122 4 98.55%
pcapkit/vendor 4409 2359 1006 158 42.84%

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

@JarryShaw
JarryShaw merged commit 36db422 into main Oct 7, 2026
36 checks passed
@JarryShaw
JarryShaw deleted the fix/1294-1295-ipv4iface-omd-copy branch October 7, 2026 22:59
@JarryShaw JarryShaw removed the review: good-to-go Cross-review at the current head says ready; CI state is separate label Oct 7, 2026
@JarryShaw JarryShaw added this to the PyPCAPKit v1.5 milestone Oct 26, 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) fix Pull requests that fix a defect (fix: subject prefix)

Projects

Status: Done

1 participant