Two pre-existing defects in LOCATOR_SET cancel each other exactly, so the parameter is RFC-conformant on main by coincidence rather than by construction. Fixing either one alone makes it non-conformant. #664 fixes the padding half and the parameter is now four octets short at every locator count.
Measured on both trees, independently of the PR author
CPython 3.14.7, __editable__* stripped from sys.meta_path, pcapkit.__file__ asserted to each worktree before any other import, HIP._make_param_locator_set(Parameter.LOCATOR_SET, version=2, locator_set=[...]) then .pack():
| locators |
contents |
schema.len |
main @ 0c7f2b7c9 |
#664 @ 819dbbd6a |
RFC 7401 for Length = 24n |
| 1 |
24 |
4 |
32 OK |
28 short by 4 |
32 |
| 2 |
48 |
8 |
56 OK |
52 short by 4 |
56 |
| 5 |
120 |
20 |
128 OK |
124 short by 4 |
128 |
RFC total from §5.2.1's Total Length = 11 + Length - (Length + 3) % 8.
The two defects, and why they cancel
1. LocatorSetParameter.padding reads the nested Locator.len, not the parameter's. ListField packs each Locator into the shared packet context, and the nested len shadows the parameter's own. An IPv6 locator always has Locator.len == 4, so the old padding expression always appended exactly 4 octets regardless of locator count.
2. The parameter's len is sum(Locator.len) — in 4-octet units — where the RFC's Length is a byte count. Visible in the table above: contents of 24, 48 and 120 octets give schema.len of 4, 8 and 20, i.e. 4n rather than 24n.
Old total: 4 + 24n + 4 = 24n + 8. And because 24n ≡ 0 (mod 8), the RFC total for Length = 24n is also 24n + 8. Identical for every n — which is why no test and no round trip has ever caught either defect.
Why this is filed rather than folded into #664
The repair is not a one-expression change like EncryptedParameter.data was. It changes a Length field on the wire, changes the public Data_LocatorSetParameter.length, and requires re-checking the reader's ListField(length=lambda pkt: pkt['len']), which consumes the same quantity. That is the same class of change as #672, not a coupled fix that #664 can absorb safely.
Consequence for #664 as it stands
#664 must not merge in its current state. It would take LOCATOR_SET from conformant to four octets short, which is a regression in on-wire output even though every individual change in it is correct. Two ways out, and the choice is the owner's:
Found by the #664 worker while fixing a stale test pin, disclosed rather than absorbed, and reproduced here independently before filing.
Two pre-existing defects in
LOCATOR_SETcancel each other exactly, so the parameter is RFC-conformant onmainby coincidence rather than by construction. Fixing either one alone makes it non-conformant. #664 fixes the padding half and the parameter is now four octets short at every locator count.Measured on both trees, independently of the PR author
CPython 3.14.7,
__editable__*stripped fromsys.meta_path,pcapkit.__file__asserted to each worktree before any other import,HIP._make_param_locator_set(Parameter.LOCATOR_SET, version=2, locator_set=[...])then.pack():schema.lenmain@0c7f2b7c9819dbbd6aLength = 24nRFC total from §5.2.1's
Total Length = 11 + Length - (Length + 3) % 8.The two defects, and why they cancel
1.
LocatorSetParameter.paddingreads the nestedLocator.len, not the parameter's.ListFieldpacks eachLocatorinto the shared packet context, and the nestedlenshadows the parameter's own. An IPv6 locator always hasLocator.len == 4, so the old padding expression always appended exactly 4 octets regardless of locator count.2. The parameter's
lenissum(Locator.len)— in 4-octet units — where the RFC'sLengthis a byte count. Visible in the table above: contents of 24, 48 and 120 octets giveschema.lenof 4, 8 and 20, i.e.4nrather than24n.Old total:
4 + 24n + 4 = 24n + 8. And because24n ≡ 0 (mod 8), the RFC total forLength = 24nis also24n + 8. Identical for every n — which is why no test and no round trip has ever caught either defect.Why this is filed rather than folded into #664
The repair is not a one-expression change like
EncryptedParameter.datawas. It changes aLengthfield on the wire, changes the publicData_LocatorSetParameter.length, and requires re-checking the reader'sListField(length=lambda pkt: pkt['len']), which consumes the same quantity. That is the same class of change as #672, not a coupled fix that #664 can absorb safely.Consequence for #664 as it stands
#664 must not merge in its current state. It would take
LOCATOR_SETfrom conformant to four octets short, which is a regression in on-wire output even though every individual change in it is correct. Two ways out, and the choice is the owner's:leninside fix(hip): pad HIP parameters to the record length RFC 7401 gives, not the contents (#651) #664 — correct on both counts, but widens an already-breakingPR into a wireLengthchange plus a reader re-check.LOCATOR_SET's padding alone — keeps the accidental conformance, keeps the PR small, and this issue then carries both defects. My recommendation, because it separates a 46-parameter padding correction from a single parameter's length-unit bug, and because the cancellation means (b) ships nothing wrong.Found by the #664 worker while fixing a stale test pin, disclosed rather than absorbed, and reproduced here independently before filing.