From 4ec3e1d174cd6c905cec1ee1346ab8f33125f985 Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Mon, 26 Oct 2026 16:31:29 -0400 Subject: [PATCH] fix(hip)!: read REG_FAILED's Failure Type and Reg Types as RFC 8003 lays them out (#2309) RFC 8003 section 4.5 gives REG_FAILED a Failure Type octet followed by Reg Type octets, and no lifetime. pcapkit read the first octet as a `lifetime` timedelta and decoded the Reg Types with the RegistrationFailure registry. Replace `lifetime` with `failure_type` (a RegistrationFailure) in the schema, the data model and the maker, with no alias, and decode `reg_type` / `reg_failed` with the Registration registry. The octets are unchanged. Tests: test_hip_roundtrip_unit pins the parsed types, parse->rebuild over Failure Types 0-255 with several Reg Types, and make->parse octets. --- CHANGELOG.md | 1 + docs/source/changelog/1.5.0.rst | 9 +++ pcapkit/protocols/data/internet/hip.py | 20 ++++-- pcapkit/protocols/internet/hip.py | 44 +++++++----- pcapkit/protocols/schema/internet/hip.py | 17 ++--- .../internet/test_hip_roundtrip_unit.py | 71 +++++++++++++++++++ tests/protocols/internet/test_hip_unit.py | 38 +++++----- 7 files changed, 155 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d76e441b02..1f367cc3ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -225,6 +225,7 @@ Layer-limited extraction: `layer='link'` no longer sets the termination flag on - **a breaking change to** `NTPTimestamp`: it and `PMIPv6Timestamp` are `Info` classes in `pcapkit.protocols.data.internet.mh` rather than tuples, and `NTPTimestamp` keeps no alias at its 1.4.x home in `pcapkit.protocols.internet.mh`. As Mappings they no longer index, unpack, compare or hash as a tuple does; `.seconds` and `.fraction` still work, and the dumpers write both as keys ([#1886](https://github.com/JarryShaw/PyPCAPKit/issues/1886), [#1887](https://github.com/JarryShaw/PyPCAPKit/pull/1887)). - **a breaking change to** rebuilding from `to_dict()`: the payload is dispatched only from the `__next_name__` and `__next_type__` keys, and the IPv6 extension-header chain only from `__exthdr__`, never from key order, which on PyPy follows an earlier instance's attribute layout and so picked the wrong layer. `to_dict()`, iteration and `len()` carry the dunder keys a new `Info.__included__` names, and `Info.__excluded__` only keeps a key out of `repr`, `str` and the dumps. A hand-written dict needs those keys, `__short_read__` among them, to rebuild, and a custom `Info` lists its own dunder keys in `__included__` ([#2003](https://github.com/JarryShaw/PyPCAPKit/issues/2003), [#2014](https://github.com/JarryShaw/PyPCAPKit/pull/2014)). - **a breaking change to** `PCAPNG.context`: the block's section `Context`, which it returned from 1.0.0 through 1.4.x, is `PCAPNG.section_context`, with no alias, and `context` is the caller-supplied `ContextRegistry` every protocol inherits from `ProtocolBase`, on a Section Header Block too. `PCAPNG.section_context` raises `UnsupportedCall` where `context` did: on a block that holds no section, such as a Section Header Block the `PCAPNG` engine has not assigned the section it opens. Code reading `block.context` for the section reads `block.section_context` ([#2172](https://github.com/JarryShaw/PyPCAPKit/issues/2172), [#2205](https://github.com/JarryShaw/PyPCAPKit/pull/2205)). +- **a breaking change to** HIP's `REG_FAILED` parameter: its first octet is the Failure Type, and it carries no lifetime ([RFC 8003 Section 4.5](https://datatracker.ietf.org/doc/html/rfc8003#section-4.5)), so `pcapkit.protocols.data.internet.hip.RegFailedParameter` replaces `lifetime` with `failure_type`, a `RegistrationFailure`, with no alias, and its `reg_type` holds `Registration` values rather than failure types. `HIP._make_param_reg_failed()` takes `failure_type` in place of `lifetime`, and `reg_failed` takes registration types. The octets are unchanged ([#2309](https://github.com/JarryShaw/PyPCAPKit/issues/2309)). #### Fixed diff --git a/docs/source/changelog/1.5.0.rst b/docs/source/changelog/1.5.0.rst index 569314c54c..f657f2088a 100644 --- a/docs/source/changelog/1.5.0.rst +++ b/docs/source/changelog/1.5.0.rst @@ -1489,6 +1489,15 @@ Changed :class:`~pcapkit.foundation.engines.pcapng.PCAPNG` engine has not assigned the section it opens. Code reading ``block.context`` for the section reads ``block.section_context`` (:issue:`2172`, :pr:`2205`). +* **a breaking change to** HIP's ``REG_FAILED`` parameter: its first octet is the + Failure Type, and it carries no lifetime (:rfc:`8003#section-4.5`), so + :class:`pcapkit.protocols.data.internet.hip.RegFailedParameter` replaces ``lifetime`` + with ``failure_type``, a :class:`~pcapkit.const.hip.registration_failure.RegistrationFailure`, + with no alias, and its ``reg_type`` holds + :class:`~pcapkit.const.hip.registration.Registration` values rather than failure + types. :meth:`.HIP._make_param_reg_failed` takes ``failure_type`` in place of + ``lifetime``, and ``reg_failed`` takes registration types. The octets are unchanged + (:issue:`2309`). Fixed ~~~~~ diff --git a/pcapkit/protocols/data/internet/hip.py b/pcapkit/protocols/data/internet/hip.py index b9c38596a4..1d621a2d4d 100644 --- a/pcapkit/protocols/data/internet/hip.py +++ b/pcapkit/protocols/data/internet/hip.py @@ -610,15 +610,25 @@ def __init__(self, type: 'Enum_Parameter', critical: 'bool', length: 'int', life @info_final class RegFailedParameter(Parameter): - """Data model for HIP ``REG_FAILED`` parameter.""" + """Data model for HIP ``REG_FAILED`` parameter. + + .. versionchanged:: 1.5.0 + + ``lifetime`` is replaced by :attr:`failure_type`, with no alias, and + :attr:`reg_type` holds :class:`~pcapkit.const.hip.registration.Registration` + values: ``REG_FAILED`` carries a Failure Type octet ahead of its + registration types and no lifetime (:rfc:`8003#section-4.5`, + :issue:`2309`). + + """ - #: Registration lifetime. - lifetime: 'timedelta' #: Registration failure type. - reg_type: 'tuple[RegistrationFailure, ...]' + failure_type: 'RegistrationFailure' + #: Registration types that failed with :attr:`failure_type`. + reg_type: 'tuple[Registration, ...]' if TYPE_CHECKING: - def __init__(self, type: 'Enum_Parameter', critical: 'bool', length: 'int', lifetime: 'timedelta', reg_type: 'tuple[RegistrationFailure, ...]') -> 'None': ... # pylint: disable=unused-argument,multiple-statements,redefined-builtin,super-init-not-called,line-too-long + def __init__(self, type: 'Enum_Parameter', critical: 'bool', length: 'int', failure_type: 'RegistrationFailure', reg_type: 'tuple[Registration, ...]') -> 'None': ... # pylint: disable=unused-argument,multiple-statements,redefined-builtin,super-init-not-called,line-too-long @info_final diff --git a/pcapkit/protocols/internet/hip.py b/pcapkit/protocols/internet/hip.py index 1ac80fe3a0..a32f909306 100644 --- a/pcapkit/protocols/internet/hip.py +++ b/pcapkit/protocols/internet/hip.py @@ -49,6 +49,7 @@ from pcapkit.const.hip.notify_message import NotifyMessage as Enum_NotifyMessage from pcapkit.const.hip.packet import Packet as Enum_Packet from pcapkit.const.hip.parameter import Parameter as Enum_Parameter +from pcapkit.const.hip.registration_failure import RegistrationFailure as Enum_RegistrationFailure from pcapkit.const.reg.transtype import TransType as Enum_TransType from pcapkit.corekit.fields.ipaddress import parse_ip_address from pcapkit.corekit.multidict import OrderedMultiDict @@ -217,8 +218,6 @@ from pcapkit.const.hip.esp_transform_suite import ESPTransformSuite as Enum_ESPTransformSuite from pcapkit.const.hip.nat_traversal import NATTraversal as Enum_NATTraversal from pcapkit.const.hip.registration import Registration as Enum_Registration - from pcapkit.const.hip.registration_failure import \ - RegistrationFailure as Enum_RegistrationFailure from pcapkit.const.hip.suite import Suite as Enum_Suite from pcapkit.const.hip.transport import Transport as Enum_Transport from pcapkit.corekit.protochain import ProtoChain @@ -1998,7 +1997,7 @@ def _read_param_reg_failed(self, schema: 'Schema_RegFailedParameter', *, version +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Lifetime | Reg Type #1 | Reg Type #2 | Reg Type #3 | + | Failure Type | Reg Type #1 | Reg Type #2 | Reg Type #3 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ... | ... | Reg Type #n | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Padding + @@ -2018,7 +2017,7 @@ def _read_param_reg_failed(self, schema: 'Schema_RegFailedParameter', *, version type=schema.type, critical=bool(schema.type & 0b1), length=parameter_total_len(schema.len), - lifetime=datetime.timedelta(seconds=schema.lifetime), + failure_type=schema.failure_type, reg_type=tuple(schema.reg_failed), ) return reg_failed @@ -4432,23 +4431,35 @@ def _make_param_reg_response(self, code: 'Enum_Parameter', param: 'Optional[Data def _make_param_reg_failed(self, code: 'Enum_Parameter', param: 'Optional[Data_RegFailedParameter]' = None, *, # pylint: disable=unused-argument version: 'int', - lifetime: 'int | timedelta' = 0, - reg_failed: 'Optional[list[Enum_RegistrationFailure | StdlibEnum | AenumEnum | str | int]]' = None, + failure_type: 'Enum_RegistrationFailure | StdlibEnum | AenumEnum | str | int' = Enum_RegistrationFailure.Registration_requires_additional_credentials, # pylint: disable=line-too-long + failure_type_default: 'Optional[int]' = None, + failure_type_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long + failure_type_reversed: 'bool' = False, + reg_failed: 'Optional[list[Enum_Registration | StdlibEnum | AenumEnum | str | int]]' = None, reg_failed_default: 'Optional[int]' = None, reg_failed_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long reg_failed_reversed: 'bool' = False, **kwargs: 'Any') -> 'Schema_RegFailedParameter': """Make HIP ``REG_FAILED`` parameter. + .. versionchanged:: 1.5.0 + + ``lifetime`` is replaced by ``failure_type``, with no alias, and + ``reg_failed`` takes registration types rather than failure types + (:issue:`2309`). + Args: code: parameter code param: parameter data version: HIP protocol version - lifetime: lifetime - reg_failed: registration failure list - reg_failed_default: default registration failure - reg_failed_namespace: registration failure namespace - reg_failed_reversed: reverse registration failure namespace + failure_type: registration failure type + failure_type_default: default registration failure type + failure_type_namespace: registration failure type namespace + failure_type_reversed: reverse registration failure type namespace + reg_failed: list of registration types that failed + reg_failed_default: default registration type + reg_failed_namespace: registration type namespace + reg_failed_reversed: reverse registration type namespace **kwargs: arbitrary keyword arguments Returns: @@ -4456,9 +4467,12 @@ def _make_param_reg_failed(self, code: 'Enum_Parameter', param: 'Optional[Data_R """ if param is not None: - reg_type = cast('list[Enum_RegistrationFailure]', param.reg_type) - lt = math.floor(param.lifetime.total_seconds()) + failure = param.failure_type + reg_type = cast('list[Enum_Registration]', param.reg_type) else: + failure = self._make_index(failure_type, failure_type_default, namespace=failure_type_namespace, # type: ignore[assignment] + reversed=failure_type_reversed, pack=False) + if reg_failed is None: reg_failed = [] @@ -4467,12 +4481,10 @@ def _make_param_reg_failed(self, code: 'Enum_Parameter', param: 'Optional[Data_R reg_type.append(self._make_index(reg, reg_failed_default, namespace=reg_failed_namespace, # type: ignore[arg-type] reversed=reg_failed_reversed, pack=False)) - lt = lifetime if isinstance(lifetime, int) else math.floor(lifetime.total_seconds()) - return Schema_RegFailedParameter( type=code, len=1 + len(reg_type), - lifetime=lt, + failure_type=failure, reg_failed=reg_type, ) diff --git a/pcapkit/protocols/schema/internet/hip.py b/pcapkit/protocols/schema/internet/hip.py index 9bf3dd2c48..b39455261f 100644 --- a/pcapkit/protocols/schema/internet/hip.py +++ b/pcapkit/protocols/schema/internet/hip.py @@ -186,8 +186,9 @@ def registration_type_list_len(pkt: 'dict[str, Any]') -> 'int': Used by the ``reg_request``, ``reg_response`` and ``reg_failed`` fields of :class:`RegRequestParameter`, :class:`RegResponseParameter` and :class:`RegFailedParameter` respectively, each of which follows a single - ``lifetime`` octet with a list of registration type octets sized by the - remainder of the parameter. + prefix octet (the ``lifetime`` of the first two, the ``failure_type`` of + the last) with a list of registration type octets sized by the remainder + of the parameter. Args: pkt: Parameter unpacked schema. @@ -197,7 +198,7 @@ def registration_type_list_len(pkt: 'dict[str, Any]') -> 'int': Raises: FieldValueError: If the parameter's ``Length`` on the wire is too - short to hold the ``lifetime`` octet already read, which would + short to hold the prefix octet already read, which would otherwise underflow the list length below zero. """ @@ -1171,18 +1172,18 @@ def __init__(self, type: 'Enum_Parameter', len: 'int', lifetime: 'int', reg_resp class RegFailedParameter(Parameter, code=Enum_Parameter.REG_FAILED): """Header schema for HIP ``REG_FAILED`` parameters.""" - #: Lifetime. - lifetime: 'int' = UInt8Field() + #: Registration failure type. + failure_type: 'Enum_RegistrationFailure' = EnumField(length=1, namespace=Enum_RegistrationFailure) #: Registration types. - reg_failed: 'list[Enum_RegistrationFailure]' = ListField( + reg_failed: 'list[Enum_Registration]' = ListField( length=registration_type_list_len, - item_type=EnumField(length=1, namespace=Enum_RegistrationFailure), + item_type=EnumField(length=1, namespace=Enum_Registration), ) #: Padding. padding: 'bytes' = PaddingField(length=parameter_padding_len) if TYPE_CHECKING: - def __init__(self, type: 'Enum_Parameter', len: 'int', lifetime: 'int', reg_failed: 'list[Enum_RegistrationFailure]') -> 'None': ... + def __init__(self, type: 'Enum_Parameter', len: 'int', failure_type: 'Enum_RegistrationFailure', reg_failed: 'list[Enum_Registration]') -> 'None': ... @schema_final diff --git a/tests/protocols/internet/test_hip_roundtrip_unit.py b/tests/protocols/internet/test_hip_roundtrip_unit.py index 16d47282c7..eab02a354f 100644 --- a/tests/protocols/internet/test_hip_roundtrip_unit.py +++ b/tests/protocols/internet/test_hip_roundtrip_unit.py @@ -13,6 +13,9 @@ :class:`~datetime.timedelta`, so 197 of the 256 octets could not be rebuilt. * #1328 -- the reserved bits of ``Controls`` and of ``ESP_INFO`` were written back as zero. +* #2309 -- ``REG_FAILED`` read its Failure Type octet as a ``lifetime`` and its + Reg Type octets as failure types, rather than as :rfc:`8003#section-4.5` + lays them out. Every case builds its own octets in memory and reads no capture. :class:`HIP` is imported inside each test, after :func:`~tests._support.reimport_once_per_class`, @@ -35,6 +38,13 @@ def packet(params: bytes = b'', controls: bytes = b'\x00\x00') -> bytes: + controls + bytes(32) + params) +def reg_failed(failure_type: int, reg_types: 'list[int]') -> bytes: + """Return a ``REG_FAILED`` parameter, zero-padded to 8 octets.""" + body = (936).to_bytes(2, 'big') + (1 + len(reg_types)).to_bytes(2, 'big') \ + + bytes([failure_type, *reg_types]) + return body + bytes(-len(body) % 8) + + class TestHIPRoundTrip(unittest.TestCase): """Pin the rebuilt octets of parsed HIP packets.""" @@ -106,6 +116,67 @@ def test_esp_info_reserved_round_trips(self) -> None: self.assertRoundTrips(packet(bytes.fromhex('0041000c' 'abcd' '0102') + bytes.fromhex('0a0b0c0d' '01020304'))) + def test_reg_failed_reads_failure_type_then_registration_types(self) -> None: + """#2309: the first octet is a Failure Type, and the rest are Reg Types.""" + from pcapkit.const.hip.parameter import Parameter + from pcapkit.const.hip.registration import Registration + from pcapkit.const.hip.registration_failure import RegistrationFailure + from pcapkit.protocols.internet.hip import HIP + + wire = packet(reg_failed(1, [1, 2, 3])) + param = HIP(io.BytesIO(wire), len(wire), extension=True).info.parameters[Parameter.REG_FAILED] + + # Both registries are integer enums, so ``==`` alone cannot tell a + # Failure Type from a Reg Type of the same value: compare the types too. + self.assertIs(type(param.failure_type), RegistrationFailure) + self.assertEqual(param.failure_type, RegistrationFailure.Registration_type_unavailable) + self.assertEqual([type(reg) for reg in param.reg_type], [Registration] * 3) + self.assertEqual(param.reg_type, (Registration.RENDEZVOUS, + Registration.RELAY_UDP_HIP, + Registration.RELAY_UDP_ESP)) + # No ``lifetime`` alias is kept: ``REG_FAILED`` has no lifetime. + self.assertNotIn('lifetime', param) + self.assertFalse(hasattr(param, 'lifetime')) + + def test_reg_failed_parse_then_rebuild_is_byte_exact(self) -> None: + """#2309: every Failure Type, with several Reg Types, rebuilds unchanged.""" + for failure_type in (0, 1, 2, 3, 9, 201, 255): + for reg_types in ([], [1], [1, 2, 3], [0, 4, 200, 255, 2, 1, 3]): + with self.subTest(failure_type=failure_type, reg_types=reg_types): + self.assertRoundTrips(packet(reg_failed(failure_type, reg_types))) + + def test_reg_failed_make_then_parse_is_byte_exact(self) -> None: + """#2309: ``failure_type`` and ``reg_failed`` build the RFC 8003 octets.""" + from pcapkit.const.hip.parameter import Parameter + from pcapkit.const.hip.registration import Registration + from pcapkit.const.hip.registration_failure import RegistrationFailure + from pcapkit.protocols.internet.hip import HIP + + for failure_type, reg_types, octets in ( + (RegistrationFailure.Insufficient_resources, + [Registration.RELAY_UDP_ESP, Registration.RENDEZVOUS], (2, [3, 1])), + (3, [1, 2, 3], (3, [1, 2, 3])), + (RegistrationFailure.Registration_requires_additional_credentials, [], (0, [])), + ): + with self.subTest(failure_type=failure_type, reg_types=reg_types): + wire = HIP.__new__(HIP).make(parameters=[(Parameter.REG_FAILED, { + 'failure_type': failure_type, + 'reg_failed': reg_types, + })]).pack() + self.assertEqual(wire[40:].hex(), reg_failed(*octets).hex()) + + param = HIP(io.BytesIO(wire), len(wire), extension=True).info.parameters[Parameter.REG_FAILED] + self.assertIs(type(param.failure_type), RegistrationFailure) + self.assertEqual(param.failure_type, octets[0]) + self.assertEqual([type(reg) for reg in param.reg_type], + [Registration] * len(octets[1])) + self.assertEqual(list(param.reg_type), octets[1]) + self.assertRoundTrips(wire) + + # The default Failure Type is octet 0, as the removed ``lifetime`` was. + wire = HIP.__new__(HIP).make(parameters=[(Parameter.REG_FAILED, {})]).pack() + self.assertEqual(wire[40:].hex(), reg_failed(0, []).hex()) + if __name__ == '__main__': unittest.main() diff --git a/tests/protocols/internet/test_hip_unit.py b/tests/protocols/internet/test_hip_unit.py index f6e0d6b1a9..333f57f3cc 100644 --- a/tests/protocols/internet/test_hip_unit.py +++ b/tests/protocols/internet/test_hip_unit.py @@ -700,13 +700,15 @@ def test_hip_parameter_readers_cover_simple_models_and_guards(self) -> None: version=2, options=options, ).lifetime, datetime.timedelta(seconds=4)) - self.assertEqual(proto._read_param_reg_failed( + reg_failed = proto._read_param_reg_failed( hip_schema.RegFailedParameter(type=Parameter.REG_FAILED, len=2, - lifetime=5, - reg_failed=[RegistrationFailure.Insufficient_resources]), + failure_type=RegistrationFailure.Insufficient_resources, + reg_failed=[Registration.RELAY_UDP_HIP]), version=2, options=options, - ).reg_type, (RegistrationFailure.Insufficient_resources,)) + ) + self.assertEqual(reg_failed.failure_type, RegistrationFailure.Insufficient_resources) + self.assertEqual(reg_failed.reg_type, (Registration.RELAY_UDP_HIP,)) self.assertEqual(proto._read_param_reg_from( hip_schema.RegFromParameter(type=Parameter.REG_FROM, len=20, @@ -1150,12 +1152,14 @@ def test_hip_parameter_constructors_cover_keyword_paths(self) -> None: lifetime=4, reg_response=[Registration.RELAY_UDP_ESP], ).reg_response, [Registration.RELAY_UDP_ESP]) - self.assertEqual(proto._make_param_reg_failed( + reg_failed_schema = proto._make_param_reg_failed( Parameter.REG_FAILED, version=2, - lifetime=datetime.timedelta(seconds=5), - reg_failed=[RegistrationFailure.Insufficient_resources], - ).reg_failed, [RegistrationFailure.Insufficient_resources]) + failure_type=RegistrationFailure.Insufficient_resources, + reg_failed=[Registration.RELAY_UDP_HIP], + ) + self.assertEqual(reg_failed_schema.failure_type, RegistrationFailure.Insufficient_resources) + self.assertEqual(reg_failed_schema.reg_failed, [Registration.RELAY_UDP_HIP]) self.assertEqual(proto._make_param_reg_from( Parameter.REG_FROM, version=2, @@ -1620,10 +1624,10 @@ def test_hip_parameter_constructors_cover_data_model_and_default_paths(self) -> Parameter.REG_FAILED, hip_data.RegFailedParameter(type=Parameter.REG_FAILED, critical=False, length=8, - lifetime=dt1, - reg_type=(RegistrationFailure.Insufficient_resources,)), + failure_type=RegistrationFailure.Insufficient_resources, + reg_type=(Registration.RELAY_UDP_HIP,)), version=2, - ).lifetime, 1) + ).failure_type, RegistrationFailure.Insufficient_resources) self.assertEqual(proto._make_param_reg_failed(Parameter.REG_FAILED, version=2).reg_failed, []) self.assertEqual(proto._make_param_reg_from( @@ -1823,7 +1827,8 @@ def test_hip_parameter_constructors_cover_data_model_and_default_paths(self) -> def test_hip_registration_parameters_reject_underflowing_length(self) -> None: """#438: a registration parameter's ``Length`` too small for its own - ``lifetime`` octet must raise, not silently drop the registration list. + prefix octet (``lifetime``, or ``failure_type`` for ``REG_FAILED``) + must raise, not silently drop the registration list. ``reg_request``, ``reg_response`` and ``reg_failed`` each size their list of registration-type octets as ``Length - 1``. Nothing floored @@ -1851,7 +1856,7 @@ def test_hip_registration_parameters_reject_underflowing_length(self) -> None: ('REG_FAILED', 936), ): with self.subTest(parameter=name): - # type(2) len(2)=0 lifetime(1), then 3 octets padding out the + # type(2) len(2)=0 prefix(1), then 3 octets padding out the # 8-octet parameter area the outer header declared. param = code.to_bytes(2, 'big') + (0).to_bytes(2, 'big') + bytes(4) raw = fixed + param @@ -1867,7 +1872,7 @@ def test_hip_reg_info_parameter_rejects_underflowing_length(self) -> None: ``reg_info`` sizes its list of registration-type octets as ``Length - 2`` -- the two octets are ``min_lifetime`` and ``max_lifetime``, which ``REG_INFO`` carries in place of the single - ``lifetime`` octet #438 fixed for ``reg_request``/``reg_response``/ + prefix octet #438 fixed for ``reg_request``/``reg_response``/ ``reg_failed``. Nothing floored that at zero either, so a peer declaring ``Length = 0`` drove the list length to ``-2``. Unlike a :class:`~pcapkit.corekit.fields.strings.BytesField`, @@ -2431,10 +2436,11 @@ def test_hip_schema_selectors_and_encrypted_parameter_branches(self) -> None: # #438: ``reg_request``/``reg_response``/``reg_failed`` size their # registration-type list as ``Length - 1``, the ``- 1`` accounting for - # the ``lifetime`` octet already read unconditionally. + # the prefix octet (``lifetime``, or ``failure_type`` for ``REG_FAILED``) + # already read unconditionally. self.assertEqual(hip_schema.registration_type_list_len({'len': 1}), 0) self.assertEqual(hip_schema.registration_type_list_len({'len': 4}), 3) - # a ``Length`` too small to hold that ``lifetime`` octet must raise + # a ``Length`` too small to hold that prefix octet must raise # rather than drive the list length negative. with self.assertRaisesRegex(FieldValueError, 'invalid parameter length'): hip_schema.registration_type_list_len({'len': 0})