Skip to content

Commit e41b18a

Browse files
authored
fix(hip): from_data no longer reads alias before _info is set (#1184) (#1188)
1 parent 5fd2f22 commit e41b18a

2 files changed

Lines changed: 92 additions & 0 deletions

File tree

‎pcapkit/protocols/internet/hip.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,11 @@ def read(self, length: 'Optional[int]' = None, *, extension: bool = False, **kwa
592592

593593
if extension:
594594
return hip
595+
596+
# NOTE: ``_decode_next_layer`` builds the protocol chain from
597+
# :attr:`self.alias <HIP.alias>`, which reads ``self._info``, and
598+
# ``__post_init__`` assigns that only once this method returns.
599+
self._info = hip
595600
return self._decode_next_layer(hip, schema.next, length - hip.length)
596601

597602
def make(self,
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# -*- coding: utf-8 -*-
2+
"""HIP's ``alias`` is readable while the header is still being decoded.
3+
4+
GitHub issue #1184: :attr:`HIP.alias <pcapkit.protocols.internet.hip.HIP.alias>`
5+
read ``self._info.version``. Outside an IPv6 extension chain,
6+
:meth:`HIP.read <pcapkit.protocols.internet.hip.HIP.read>` ends in
7+
``_decode_next_layer``, which reads ``alias`` for the protocol chain, but
8+
``_info`` is assigned only once ``read`` has returned. So parsing or
9+
constructing a standalone HIP header, and
10+
:meth:`~pcapkit.protocols.protocol.ProtocolBase.from_data` over a parsed one,
11+
raised ``AttributeError: 'HIP' object has no attribute '_info'``.
12+
13+
Every case builds its own octets in memory and reads no capture. :class:`HIP`
14+
is imported inside each test, after
15+
:func:`~tests._support.reimport_once_per_class`, not at module load.
16+
17+
"""
18+
19+
import importlib.util
20+
import unittest
21+
22+
from tests._support import reimport_once_per_class
23+
24+
RUNTIME_DEPS = ('tbtrim', 'aenum', 'chardet', 'dictdumper')
25+
HAS_RUNTIME = all(importlib.util.find_spec(name) is not None for name in RUNTIME_DEPS)
26+
27+
#: Header fields shared by every HIP packet built here, as in
28+
#: :data:`examples.generators.options.HIP_BASE`.
29+
HIP_BASE = {
30+
'next': 59, 'packet': 1, 'checksum': b'\x00\x00',
31+
'controls_anonymous': False, 'shit': 0, 'rhit': 0, 'payload': b'',
32+
}
33+
34+
35+
@unittest.skipUnless(HAS_RUNTIME, 'runtime dependencies not installed')
36+
class TestHIPFromDataAlias(unittest.TestCase):
37+
"""Pin ``alias`` on the paths that decode a next layer."""
38+
39+
def setUp(self) -> None:
40+
reimport_once_per_class(self)
41+
42+
@staticmethod
43+
def _parameters() -> 'list':
44+
from pcapkit.const.hip.parameter import Parameter
45+
46+
return [(Parameter.R1_COUNTER, {'counter': 0x0123456789abcdef})]
47+
48+
def _octets(self, version: 'int') -> 'bytes':
49+
from pcapkit.protocols.internet.hip import HIP
50+
51+
return HIP(parameters=self._parameters(), extension=True,
52+
version=version, **HIP_BASE).data
53+
54+
def test_construct_standalone_header(self) -> None:
55+
from pcapkit.protocols.internet.hip import HIP
56+
57+
for version in (1, 2):
58+
with self.subTest(version=version):
59+
proto = HIP(parameters=self._parameters(), version=version, **HIP_BASE)
60+
self.assertEqual(proto.alias, f'HIPv{version}')
61+
self.assertEqual(proto.data, self._octets(version))
62+
63+
def test_parse_standalone_header(self) -> None:
64+
from pcapkit.protocols.internet.hip import HIP
65+
66+
for version in (1, 2):
67+
with self.subTest(version=version):
68+
octets = self._octets(version)
69+
proto = HIP(octets, len(octets))
70+
self.assertEqual(proto.alias, f'HIPv{version}')
71+
self.assertEqual(str(proto.protochain).split(':')[0], f'HIPv{version}')
72+
73+
def test_from_data_rebuilds_extension_header_byte_for_byte(self) -> None:
74+
from pcapkit.protocols.internet.hip import HIP
75+
76+
for version in (1, 2):
77+
with self.subTest(version=version):
78+
octets = self._octets(version)
79+
parsed = HIP(octets, len(octets), extension=True)
80+
rebuilt = HIP.from_data(parsed.info)
81+
self.assertEqual(rebuilt.data, octets)
82+
self.assertEqual(rebuilt.alias, f'HIPv{version}')
83+
self.assertEqual(rebuilt.info.version, version)
84+
85+
86+
if __name__ == '__main__':
87+
unittest.main()

0 commit comments

Comments
 (0)