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
8 changes: 6 additions & 2 deletions pcapkit/protocols/internet/hopopt.py
Original file line number Diff line number Diff line change
Expand Up @@ -1611,8 +1611,9 @@ def _make_opt_smf_dpd(self, code: 'Enum_Option', opt: 'Optional[Data_SMFIdentifi
Constructed option schema.

Raises:
ProtocolError: If the DPD mode is invalid, or the Tagger ID does
not fit the TaggerID type.
ProtocolError: If the DPD mode is invalid, the Tagger ID does
not fit the TaggerID type, or the H-DPD Hash Assist Value is
empty.

"""
if opt is not None:
Expand Down Expand Up @@ -1673,6 +1674,9 @@ def _make_opt_smf_dpd(self, code: 'Enum_Option', opt: 'Optional[Data_SMFIdentifi
id=id,
) # type: Schema_SMFDPDOption
elif dpd_type == Enum_SMFDPDMode.H_DPD:
# The first octet of the HAV carries the H-DPD mode bit.
if not hav:
raise ProtocolError(f'{self.alias}: [OptNo {code}] empty Hash Assist Value (HAV)')
hav_ba = bytearray(hav)
hav_ba[0] = hav[0] | 0x80

Expand Down
8 changes: 6 additions & 2 deletions pcapkit/protocols/internet/ipv6_opts.py
Original file line number Diff line number Diff line change
Expand Up @@ -1600,8 +1600,9 @@ def _make_opt_smf_dpd(self, code: 'Enum_Option', opt: 'Optional[Data_SMFIdentifi
Constructed option schema.

Raises:
ProtocolError: If the DPD mode is invalid, or the Tagger ID does
not fit the TaggerID type.
ProtocolError: If the DPD mode is invalid, the Tagger ID does
not fit the TaggerID type, or the H-DPD Hash Assist Value is
empty.

"""
if opt is not None:
Expand Down Expand Up @@ -1662,6 +1663,9 @@ def _make_opt_smf_dpd(self, code: 'Enum_Option', opt: 'Optional[Data_SMFIdentifi
id=id,
) # type: Schema_SMFDPDOption
elif dpd_type == Enum_SMFDPDMode.H_DPD:
# The first octet of the HAV carries the H-DPD mode bit.
if not hav:
raise ProtocolError(f'{self.alias}: [OptNo {code}] empty Hash Assist Value (HAV)')
hav_ba = bytearray(hav)
hav_ba[0] = hav[0] | 0x80

Expand Down
59 changes: 59 additions & 0 deletions tests/protocols/internet/test_smf_dpd_empty_hav_unit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
"""HOPOPT and IPv6-Opts reject an H-DPD SMF_DPD option with an empty HAV.

GitHub issue #1193: ``_make_opt_smf_dpd`` set the H-DPD mode bit with
``hav[0] | 0x80`` and no length check, so an empty Hash Assist Value raised a
bare :exc:`IndexError` rather than an in-library
:exc:`~pcapkit.utilities.exceptions.ProtocolError`.

Every case builds its options in memory and reads no capture. The protocol
classes are imported inside each test, after
:func:`~tests._support.reimport_once_per_class`, as in
:mod:`tests.protocols.link.test_ethernet_mac_roundtrip_unit`.

"""

import unittest

from tests._support import reimport_once_per_class


def _classes() -> 'dict[str, type]':
from pcapkit.protocols.internet.hopopt import HOPOPT
from pcapkit.protocols.internet.ipv6_opts import IPv6_Opts
return {'HOPOPT': HOPOPT, 'IPv6_Opts': IPv6_Opts}


class TestSMFDPDEmptyHAV(unittest.TestCase):
"""Pin the error raised for an empty H-DPD Hash Assist Value."""

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

def test_empty_hav_raises_protocol_error(self) -> None:
from pcapkit.const.ipv6.option import Option
from pcapkit.const.ipv6.smf_dpd_mode import SMFDPDMode
from pcapkit.const.reg.transtype import TransType
from pcapkit.utilities.exceptions import ProtocolError

for name, cls in _classes().items():
with self.subTest(proto=name):
with self.assertRaisesRegex(ProtocolError, r'\[OptNo 8\] empty Hash Assist Value'):
cls(options=[(Option.SMF_DPD, {'mode': SMFDPDMode.H_DPD, 'hav': b''})],
next=TransType.IPv6_NoNxt, payload=b'')

def test_one_octet_hav_still_builds(self) -> None:
from pcapkit.const.ipv6.option import Option
from pcapkit.const.ipv6.smf_dpd_mode import SMFDPDMode
from pcapkit.const.reg.transtype import TransType

for name, cls in _classes().items():
with self.subTest(proto=name):
built = cls(options=[(Option.SMF_DPD, {'mode': SMFDPDMode.H_DPD, 'hav': b'\x01'})],
next=TransType.IPv6_NoNxt, payload=b'')
# SMF_DPD, length 1, HAV 0x01 with the H-DPD bit set, then PadN.
self.assertEqual(built.data, bytes.fromhex('3b00080181010100'))


if __name__ == '__main__':
unittest.main()
Loading