Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion pcapkit/protocols/data/internet/hip.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,16 @@ class Locator(Data):
length: 'int'
#: Preferred flag.
preferred: 'bool'
#: Reserved bits -- the 7 high-order bits of the flags octet, carried
#: verbatim so that re-serialising reproduces them.
reserved: 'int'
#: Locator lifetime.
lifetime: 'timedelta'
#: Locator data.
locator: 'LocatorData | IPv6Address'

if TYPE_CHECKING:
def __init__(self, traffic: 'int', type: 'int', length: 'int', preferred: 'bool', lifetime: 'timedelta', locator: 'LocatorData | IPv6Address') -> 'None': ... # pylint: disable=super-init-not-called,unused-argument,multiple-statements,redefined-builtin,line-too-long
def __init__(self, traffic: 'int', type: 'int', length: 'int', preferred: 'bool', reserved: 'int', lifetime: 'timedelta', locator: 'LocatorData | IPv6Address') -> 'None': ... # pylint: disable=super-init-not-called,unused-argument,multiple-statements,redefined-builtin,line-too-long


@info_final
Expand Down
7 changes: 7 additions & 0 deletions pcapkit/protocols/internet/hip.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,8 @@ class Locator(TypedDict):
type: 'int'
#: Preferred flag.
preferred: 'bool'
#: Reserved bits of the flags octet.
reserved: 'NotRequired[int]'
#: Lifetime.
lifetime: 'timedelta | int'
#: IP address.
Expand Down Expand Up @@ -1031,6 +1033,7 @@ def _read_locator(locator: 'Schema_Locator') -> 'Data_LocatorData | IPv6Address'
type=_loct,
length=_locl,
preferred=bool(_resp['preferred']),
reserved=_resp['reserved'],
lifetime=datetime.timedelta(seconds=_life),
locator=_lobj,
))
Expand Down Expand Up @@ -3157,6 +3160,7 @@ def _make_locator(locator: 'Optional[Data_Locator]' = None, *,
traffic: 'int' = 0,
type: 'int' = 0,
preferred: 'bool' = False,
reserved: 'int' = 0,
lifetime: 'timedelta | int' = 0,
ip: 'IPv6Address | bytes | int | str' = '::',
spi: 'Optional[int]' = None,
Expand All @@ -3168,6 +3172,7 @@ def _make_locator(locator: 'Optional[Data_Locator]' = None, *,
traffic: traffic type
type: locator type
preferred: preferred flag
reserved: the 7 reserved high-order bits of the flags octet
lifetime: lifetime
ip: IP address
spi: SPI
Expand All @@ -3193,6 +3198,7 @@ def _make_locator(locator: 'Optional[Data_Locator]' = None, *,
type = locator.type
length = locator.length // 4
preferred = locator.preferred
reserved = locator.reserved
lifetime = math.floor(locator.lifetime.total_seconds())
else:
# NOTE: Through ``parse_ip_address`` because the locator is packed
Expand Down Expand Up @@ -3225,6 +3231,7 @@ def _make_locator(locator: 'Optional[Data_Locator]' = None, *,
type=type,
len=length,
flags={
'reserved': reserved,
'preferred': preferred,
},
lifetime=lifetime,
Expand Down
3 changes: 3 additions & 0 deletions pcapkit/protocols/schema/internet/hip.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ class ControlsType(TypedDict):
class LocatorFlags(TypedDict):
"""Locator flags."""

#: Reserved bits.
reserved: int
#: Preferred flag.
preferred: bool

Expand Down Expand Up @@ -594,6 +596,7 @@ class Locator(Schema):
flags: 'LocatorFlags' = BitField(
length=1,
namespace={
'reserved': (0, 7),
'preferred': (7, 1),
},
)
Expand Down
99 changes: 99 additions & 0 deletions tests/protocols/internet/test_hip_locator_flags_unit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# -*- coding: utf-8 -*-
"""HIP ``LOCATOR_SET`` keeps the seven reserved bits of each locator's flags octet.

GitHub issue #1365. :rfc:`8046#section-4` lays the fourth octet of a locator out
as ``Reserved`` (7 bits) followed by ``P`` (1 bit). ``Locator.flags`` declared only
``preferred: (7, 1)``, so the seven high-order bits were dropped on rebuild: flags
``0xfe`` parsed and rebuilt through ``from_data`` as ``0x00``. The fix names them
``reserved: (0, 7)``, per the #654 per-site policy, and carries them through
:class:`~pcapkit.protocols.data.internet.hip.Locator` and ``make()``.

Every case builds its own octets in memory and reads no capture. Classes are
imported inside each test, after :func:`~tests._support.reimport_once_per_class`.
"""

import unittest
import warnings

from tests._support import reimport_once_per_class

#: Header fields shared by every HIP packet built here.
HIP_BASE = {
'next': 6, 'packet': 1, 'version': 2, 'checksum': b'\x00\x00',
'controls_anonymous': False, 'shit': 0, 'rhit': 0, 'payload': b'',
}

#: Offset of the first locator's flags octet: the 40-octet HIP header, the
#: parameter's ``Type`` and ``Length``, then ``Traffic``, ``Type`` and ``Length``.
FLAGS_OFFSET = 40 + 4 + 3


class TestHIPLocatorFlags(unittest.TestCase):
"""Pin every bit of the locator flags octet across parse, rebuild and make."""

def setUp(self) -> None:
reimport_once_per_class(self)

def _frame(self, flags: int) -> bytes:
"""Build a one-locator ``LOCATOR_SET`` packet whose flags octet is ``flags``."""
from pcapkit.const.hip.parameter import Parameter
from pcapkit.protocols.internet.hip import HIP

octets = bytearray(HIP(
parameters=[(Parameter.LOCATOR_SET,
{'locator_set': [{'ip': '2001:db8::1'}]})],
extension=True, **HIP_BASE).data)
self.assertEqual(octets[FLAGS_OFFSET], 0)
octets[FLAGS_OFFSET] = flags
return bytes(octets)

def _parse(self, frame: bytes): # type: ignore[no-untyped-def]
from pcapkit.protocols.internet.hip import HIP

with warnings.catch_warnings():
# Nested ``Locator`` schemas warn about their placeholder length;
# that is pre-existing noise unrelated to the flags octet.
warnings.simplefilter('ignore')
return HIP(frame, len(frame), extension=True)

def test_from_data_rebuilds_every_flags_bit(self) -> None:
from pcapkit.const.hip.parameter import Parameter
from pcapkit.protocols.internet.hip import HIP

for flags in (0x00, 0x01, 0xfe, 0xff, 0x80, 0x55):
with self.subTest(flags=hex(flags)):
frame = self._frame(flags)
parsed = self._parse(frame)
rebuilt = HIP.from_data(parsed.info)
self.assertEqual(rebuilt.data[FLAGS_OFFSET], flags)
self.assertEqual(rebuilt.data, frame)

locator = parsed.info.parameters[Parameter.LOCATOR_SET].locator_set[0]
self.assertEqual(locator.reserved, flags >> 1)
self.assertEqual(locator.preferred, bool(flags & 1))

def test_make_writes_reserved_from_keywords(self) -> None:
from pcapkit.const.hip.parameter import Parameter
from pcapkit.protocols.internet.hip import HIP

built = HIP(
parameters=[(Parameter.LOCATOR_SET,
{'locator_set': [{'ip': '2001:db8::1', 'reserved': 0x7f,
'preferred': False}]})],
extension=True, **HIP_BASE)
self.assertEqual(built.data[FLAGS_OFFSET], 0xfe)
self.assertEqual(built.data, self._frame(0xfe))

def test_make_defaults_reserved_to_zero(self) -> None:
from pcapkit.const.hip.parameter import Parameter
from pcapkit.protocols.internet.hip import HIP

built = HIP(
parameters=[(Parameter.LOCATOR_SET,
{'locator_set': [{'ip': '2001:db8::1', 'preferred': True}]})],
extension=True, **HIP_BASE)
self.assertEqual(built.data[FLAGS_OFFSET], 0x01)


if __name__ == '__main__':
unittest.main()
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ def test_padding_comes_from_the_parameters_length_not_the_locators(self) -> None
from pcapkit.protocols.schema.internet.hip import (Locator, LocatorSetParameter,
locator_set_padding_len)

locator = Locator(traffic=0, type=0, len=4, flags={'preferred': False},
locator = Locator(traffic=0, type=0, len=4, flags={'reserved': 0, 'preferred': False},
lifetime=0, value=b'\x20\x01\x0d\xb8' + b'\x00' * 11 + b'\x01')
schema = LocatorSetParameter(type=Parameter.LOCATOR_SET, len=9,
locators=[locator])
Expand Down
12 changes: 6 additions & 6 deletions tests/protocols/internet/test_hip_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,10 +492,10 @@ def test_hip_parameter_readers_cover_simple_models_and_guards(self) -> None:
len=48,
locators=[
hip_schema.Locator(traffic=1, type=0, len=4,
flags={'preferred': 1}, lifetime=5,
flags={'reserved': 0, 'preferred': 1}, lifetime=5,
value=ip_address('2001:db8::1')),
hip_schema.Locator(traffic=2, type=1, len=5,
flags={'preferred': 0}, lifetime=6,
flags={'reserved': 0, 'preferred': 0}, lifetime=6,
value=hip_schema.LocatorData(
spi=7,
ip=ip_address('2001:db8::2'),
Expand All @@ -513,7 +513,7 @@ def test_hip_parameter_readers_cover_simple_models_and_guards(self) -> None:
type=Parameter.LOCATOR_SET,
len=8,
locators=[hip_schema.Locator(traffic=1, type=3, len=4,
flags={'preferred': 0}, lifetime=1,
flags={'reserved': 0, 'preferred': 0}, lifetime=1,
value=ip_address('2001:db8::1'))],
),
version=2,
Expand Down Expand Up @@ -1392,10 +1392,10 @@ def test_hip_parameter_constructors_cover_data_model_and_default_paths(self) ->
).counter, 2)

locator_ipv6 = hip_data.Locator(traffic=1, type=0, length=16,
preferred=True, lifetime=dt1,
preferred=True, reserved=0, lifetime=dt1,
locator=ip_address('2001:db8::1'))
locator_data = hip_data.Locator(traffic=2, type=1, length=20,
preferred=False, lifetime=dt2,
preferred=False, reserved=0, lifetime=dt2,
locator=hip_data.LocatorData(
spi=7,
ip=ip_address('2001:db8::2'),
Expand All @@ -1417,7 +1417,7 @@ def test_hip_parameter_constructors_cover_data_model_and_default_paths(self) ->
critical=False,
length=8,
locator_set=(hip_data.Locator(
traffic=1, type=0, length=4, preferred=False,
traffic=1, type=0, length=4, preferred=False, reserved=0,
lifetime=dt1, locator=b'bad',
),),
),
Expand Down
Loading