Skip to content

fix(mh): read/make FBU and FBack Lifetime as plain seconds per RFC 5568 - #502

Merged
JarryShaw merged 2 commits into
mainfrom
fix/493-mh-fbu-lifetime-seconds
Sep 19, 2026
Merged

JarryShaw merged 2 commits into
mainfrom
fix/493-mh-fbu-lifetime-seconds

Conversation

@JarryShaw

Copy link
Copy Markdown
Owner

Fixes #493.

The RFC contrast is what makes this decisive

RFC 6275 §6.1.7, the Binding Update Lifetime — the field the ×4 scaling is correct for:

16-bit unsigned integer. The number of time units remaining before the binding MUST be considered expired. ... One time unit is 4 seconds.

RFC 5568 §6.2.2 (FBU) and §6.2.3 (FBack) — the fields the code applied the same scaling to:

Lifetime: The requested time in seconds for which the sender wishes to have a binding.
Lifetime: The granted lifetime in seconds for which the sender of this message will retain a binding for traffic redirection.

Both say "in seconds", and the phrase "time unit" appears in RFC 5568 zero times across the whole document. Both RFCs were fetched and read rather than quoted from memory.

The docstring cited, as its authority, the very section that refutes it. The Note: above _read_msg_fbu said RFC 5568 §6.2.2 states the FBU is identical to the BU message, "so the lifetime is read in units of 4 seconds". §6.2.2 does say identical — about message layout; two paragraphs later the same section defines the Lifetime as seconds. That Note is rewritten, not just the arithmetic, because leaving it would re-justify the bug for the next reader.

What changed

The * 4 / / 4 removed from the four sites in #493: _read_msg_fbu, _read_msg_fback, _make_msg_fbu, _make_msg_fback, plus the misleading schema and data-model comments, which now cite RFC 5568 §6.2.2/§6.2.3.

Measured, on both trees, with the control that proves it is surgical

[MAIN ] FBU:            caller 100s -> wire 25    (wrong)
[MAIN ] FBack:          caller 100s -> wire 25    (wrong)
[MAIN ] BA (control):   caller 400s -> wire 100
[FIXED] FBU:            caller 100s -> wire 100
[FIXED] FBack:          caller 100s -> wire 100
[FIXED] BA (control):   caller 400s -> wire 100   <- unchanged

The BA control reading 100 on both trees is the point: the ×4 came out of FBU/FBack only, not out of a whole-module convention. Read side likewise: wire 100 was reported as 400.0s, now 100.0s, while BA still reports 400.0s.

Secondary — the Binding Refresh Advice interval, converted to timedelta

RFC 6275 §6.2.4, also fetched:

The Refresh Interval is measured in units of four seconds, and indicates remaining time until the mobile node SHOULD send a new home registration.

Every other 4-second-unit field in this module is a timedelta — BU/BA lifetime, the ANI Update-Timer, the LMA-controlled-MAG re-registration start time. BindingRefreshAdviceOption.interval was the lone bare int. It is now a timedelta, read as interval * 4 and made with ceil(total_seconds() / 4), with _make_opt_bra accepting int | timedelta (a bare int still means wire ticks, mirroring the LMA-controlled-MAG convention).

BRA read: wire interval=7  -> data.interval = 0:00:28   (was int 7)
BRA make: caller 36s       -> wire field = 9
BRA make: caller 9 (ticks) -> wire field = 9            (unchanged)

This is a behaviour break for anyone reading .interval as a bare number. It is deliberate: the wire bytes always round-tripped correctly, so this is a representation fix, and leaving one field in a module of timedeltas as a raw tick count is the thing that misleads.

Verification

Revert-proof. Restoring the ×4 / ÷4 at the FBU/FBack sites:

5 failed, 38 passed, 421 subtests passed
  FAILED ...test_mh_fbu_fback_lifetime_is_plain_seconds_unlike_bu_ba
  FAILED ...test_mh_fmipv6_message_readers_and_constructors
  SUBFAILED[FBU, RFC 5568 section 6.2.2]   ...test_mh_fmipv6_wire_format_matches_rfc
  SUBFAILED[FBack, RFC 5568 section 6.2.3] ...test_mh_fmipv6_wire_format_matches_rfc
  SUBFAILED(option='Transient_Binding')    ...test_mh_pmipv6_options_round_trip_byte_for_byte

Restored: test_mh_unit.py 46 passed, 782 subtests passed; test_option_roundtrip_unit.py 6 passed, 358 subtests passed, unchanged from baseline. EXPECTED_FAILURES inspected by importing the module (it cannot be grepped — ** unpacking): 59 entries before and after, none referencing mh, so no entry went stale.

About the changed existing tests

Several existing assertions had enshrined the bug — including a golden-bytes test expecting timedelta(seconds=40) for wire value 10, with a comment citing the very reasoning that was wrong. Those are corrected to match the RFCs.

No wire-bytes literal was altered. I checked the diff for every b'...'/\x literal in the test file: the only addition is a new stub's chksum=b'\x12\x34' in a new helper. The golden wire format is fixed by the RFC and is untouched; only its interpretation changed, which is the whole defect.

Note on the broader suite

A full tests/protocols/ run in the working tree showed 28 failures in test_pcapng_regression.py and the TCP/UDP runtime tests. I verified these are fixture artefacts, not regressions: that worktree had 1 generated capture instead of 13, and after running examples/generators/make_samples.py all three files pass (11 passed, 3 subtests) — identical to main. Nothing in them imports MH.

…its (#493)

RFC 5568 section 6.2.2/6.2.3 define the Fast Binding Update and Fast
Binding Acknowledgment Lifetime fields as plain seconds ("the requested
time in seconds" / "the granted lifetime ... in seconds"); the phrase
"time unit" never appears in the RFC. RFC 6275's 4-second unit applies
only to the Binding Update/Acknowledgement Lifetime it explicitly
defines that way (section 6.1.7).

- pcapkit/protocols/internet/mh.py: drop the `* 4` / `/ 4` scaling in
  _read_msg_fbu, _read_msg_fback, _make_msg_fbu, _make_msg_fback, and
  correct the _read_msg_fbu Note that had cited RFC 5568 6.2.2's
  "identical to BU" wording -- about message layout, not field units --
  as justification for the wrong scaling.
- pcapkit/protocols/schema/internet/mh.py and data/internet/mh.py:
  update the FBU/FBack Lifetime field comments to cite the RFC 5568
  sections that define the unit as seconds.
- Also converts the Binding Refresh Advice option's interval from a
  raw int to a timedelta, for consistency with every other 4-second-
  unit field in this module (RFC 6275 section 6.2.4); the wire bytes
  already round-tripped correctly, so this is a representation fix
  only, with _read_opt_bra/_make_opt_bra updated to match.
- tests/protocols/internet/test_mh_unit.py: correct the stale FBU/FBack
  lifetime assertions (including one enshrining the bug in a comment),
  update BRA interval assertions for the timedelta, and add a dedicated
  test with a BU/BA control case proving the 4-second scaling is
  preserved where it is correct.

Build: brazil n/a (GitHub project). Tests: tests/protocols/internet/
test_mh_unit.py (46 passed, 782 subtests) and
tests/protocols/test_option_roundtrip_unit.py (6 passed, 358 subtests,
unchanged from baseline) both green.
@JarryShaw
JarryShaw merged commit 7d44a7a into main Sep 19, 2026
16 checks passed
@JarryShaw
JarryShaw deleted the fix/493-mh-fbu-lifetime-seconds branch September 19, 2026 04:10
@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) labels Sep 22, 2026
@JarryShaw JarryShaw added this to the 1.5 milestone 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) fix Pull requests that fix a defect (fix: subject prefix)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

MH: FBU/FBack Lifetime scaled by 4, but RFC 5568 specifies plain seconds

1 participant