Skip to content

HIPv1 R1_Counter (128) parses as UnassignedParameter: R1CounterParameter registers code=129 only #690

Description

@JarryShaw

R1CounterParameter in pcapkit/protocols/schema/internet/hip.py is declared

class R1CounterParameter(Parameter, code=Enum_Parameter.R1_COUNTER):

i.e. for code 129 only. The HIP parameter registry that _read_hip_param consults is
keyed on that code=, so code 128 — R1_Counter, the HIPv1 spelling of the same
parameter — resolves to Parameter.__default__, which is UnassignedParameter. The
reader is then handed a schema with a value: bytes and no counter, and fails with

AttributeError: 'UnassignedParameter' object has no attribute 'counter'

The asymmetry is that the method dispatch does not have the problem. _read_param_* and
_make_param_* are found by enumeration member name, and pcapkit/protocols/internet/hip.py
maps both codes to the same handler:

Enum_Parameter.R1_Counter:             'r1_counter',              # [RFC 5201] 128, v1 only
Enum_Parameter.R1_COUNTER:             'r1_counter',              # [RFC 7401] 129

So code 128 constructs correctly — _make_param_r1_counter builds a real
R1CounterParameter, and _read_param_r1_counter even guards it, raising for
schema.type == 128 and version != 1. It is only the parse direction that cannot find the
schema.

Measured

CPython 3.14.7, editable finder stripped via getattr(f, '__module__', ''),
pcapkit.__file__ asserted to the worktree before any other import. On the tree with #672
applied, so the width is already correct and this is the only defect left in the parameter:

R1_COUNTER counter=0xaabbccdd    OK  packet=56 octets, 1 param(s), counter=0xaabbccdd, reported length=16
R1_Counter counter=0xaabbccdd    FAILED: AttributeError: 'UnassignedParameter' object has no attribute 'counter'

Same failure at one copy and at two, so the HIP_COPIES pair never routed around it.

Consequence

A HIPv1 R1 packet read off the wire loses its R1 generation counter, and pcapkit raises
rather than degrading. RFC 5201 §5.2.3 gives code 128 the same 4 + 8 layout RFC 7401
§5.2.3 gives 129, so the two are the same parameter under two numbers and one schema class
should serve both.

Suggested fix

Register the class for both codes. EnumSchema's code= takes a single member, so this
wants either a second registration for 128 against the same class or a widening of
code= to accept several — whichever the schema metaclass supports more honestly. Worth
checking whether any other parameter in the module has the same v1/v2 code pair and the
same gap.

tests/protocols/test_option_roundtrip_unit.py records this today as
hip-parameter/R1_Counter, Gap('PARSE', "no attribute 'counter'", ...); fixing it means
deleting that entry, which the table's own note asks for rather than leaving it behind.

Found while fixing #672. #672 widened the counter field to the 8 octets RFC 7401 §5.2.3
requires and deliberately did not touch this, since it is a registry-keying defect rather
than a width one and the R1_Counter expected-failure entry is unchanged by it. It is also
the sole remaining blocker to the tally in #689.

Activity

  1. added
    bugIssues reporting a defect (set by the bug report template; a default, not an assessment)
    on Sep 23, 2026
  2. JarryShaw commented on Sep 23, 2026

    @JarryShaw
    OwnerAuthor

    Two things measured while fixing #672, both of which change how this issue should be picked up.

    The mechanism is already there — this does not need a metaclass change

    EnumSchema in pcapkit/protocols/schema/schema.py already documents a multi-code registration in
    its own class docstring:

    class MultipleSchema(MySchema, code=[MyEnum.TWO, MyEnum.THREE]):
        ...

    So the fix is plausibly the one-line

    class R1CounterParameter(Parameter, code=[Enum_Parameter.R1_Counter, Enum_Parameter.R1_COUNTER]):

    rather than anything in schema.py itself. Worth saying because the issue text above asks "whichever
    the schema metaclass supports more honestly", which reads as an invitation to go widen it — and
    widening schema.py would collide with the registry-generalisation work currently in flight against
    that file. Not verified end to end here (this was out of scope for #672), so treat it as a starting
    point rather than a finished answer: the HIPv1 guard in _read_param_r1_counter
    (schema.type == 128 and version != 1) and the generator's HIP_VERSION = {129: 2, 128: 1} both
    need to still hold once 128 resolves to a real schema.

    Note it will also require deleting hip-parameter/R1_Counter from EXPECTED_FAILURES in
    tests/protocols/test_option_roundtrip_unit.py in the same change: that table's
    test_round_trip_is_identity_or_a_recorded_gap fails an entry whose case has started passing, so the
    round-trip suite goes red the moment this lands without the deletion.

    This is an independent cause from #672, and each was masking the other

    Recorded because #664's worker attributed R1_Counter's failure to the counter width (#672), and
    whether both were in play was not established. Both are, and which one you see depends entirely on
    HIP_COPIES. Measured with the round trip over the 49 HIP codes, CPython 3.14.7, editable finder
    stripped via getattr(f, '__module__', '') and pcapkit.__file__ asserted to the tree under test:

    tree HIP_COPIES=1 HIP_COPIES=2
    f0999858e CONSTRUCT — ProtocolError: HIPv1: invalid format PARSE — AttributeError: 'UnassignedParameter' object has no attribute 'counter'
    #672 + #679 PARSE — AttributeError: 'UnassignedParameter' object has no attribute 'counter' PARSE — same

    Reading that:

    • At one copy on main, the width defect fires first. A 12-octet record is 4 (mod 8), so
      HIP.make's total_length // 8 + 4 loses the remainder and _read_hip_param's exact length check
      rejects it before dispatch is ever reached. The registry defect is invisible behind it — which is
      why attributing the failure to the width is correct for that measurement.
    • At two copies, the pair cancels the four-octet shortfall, construction succeeds, and the
      registry defect is what fails. That is the setting the case table actually runs at, which is why
      EXPECTED_FAILURES records PARSE / "no attribute 'counter'" — correctly attributed to this
      issue, not to R1_COUNTER packs 12 octets where RFC 7401 §5.2.3 requires 16: counter is 4 octets, not the stated 8 #672.
    • After R1_COUNTER packs 12 octets where RFC 7401 §5.2.3 requires 16: counter is 4 octets, not the stated 8 #672 the width defect is gone at both settings and this one remains at both. So neither
      subsumes the other: two independent defects in one parameter, each of which hid the other under a
      different HIP_COPIES.

    Concretely for this issue: #672 does not move hip-parameter/R1_Counter. Verified by importing
    EXPECTED_FAILURES rather than grepping it (the table cannot be grepped reliably) on both trees —
    44 entries on each, byte-identical, with this entry's status, message and file:line unchanged.
    The entry stays until this issue is fixed, which is also why it is the sole remaining blocker to the
    tally in #689.

  3. added 7 commits that reference this issue on Sep 24, 2026
    8f41e97
    1749cc0
    b2ac58b
    7f23f98
    f0fbf62
    5e95b48
    86d16c5
  4. added this to the 1.5 milestone on Oct 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugIssues reporting a defect (set by the bug report template; a default, not an assessment)

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions