Skip to content
Closed
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ This resolves [#548](https://github.com/JarryShaw/PyPCAPKit/issues/548), which r
- message text: the `epb_flags` and `pack_flags` length errors say `expected 4`, the length they check, rather than `expected 8` ([#2168](https://github.com/JarryShaw/PyPCAPKit/issues/2168), [#2179](https://github.com/JarryShaw/PyPCAPKit/pull/2179)), and the IPv4 TS option's unknown-flag warning and the Mobility Header's prefix error spell `timestamp` and `mobile` ([#2171](https://github.com/JarryShaw/PyPCAPKit/issues/2171), [#2189](https://github.com/JarryShaw/PyPCAPKit/pull/2189)).
- IPX dissects its payload from a registry of its own, `IPX.__proto__`, keyed by IPX Packet Type, where it looked the Packet Type up as an IP protocol number, so a type 17 (NCP) payload parsed as `IPX:UDP` and type 4 as `IPX:IPv4`. No IPX-native protocol ships, so the payload is `Raw` unless one is registered ([#2169](https://github.com/JarryShaw/PyPCAPKit/issues/2169), [#2187](https://github.com/JarryShaw/PyPCAPKit/pull/2187)).
- HIP's `ECHO_RESPONSE_UNSIGNED` maker builds an `EchoResponseUnsignedParameter` schema, where it built the request's; the octets were already right ([#2170](https://github.com/JarryShaw/PyPCAPKit/issues/2170), [#2188](https://github.com/JarryShaw/PyPCAPKit/pull/2188)).
- The MPL option's `seed_type` and the SMF_DPD option's `tid_type` makers (of both HOPOPT and IPv6-Opts), and the HTTP/1.* start line's `status`, resolve a `str` enum name against their own enum when no `..._namespace` is given, where the `None` default made a name raise `ProtocolNotImplemented` ([#2356](https://github.com/JarryShaw/PyPCAPKit/issues/2356)).

### pcapkit.toolkit

Expand Down
5 changes: 5 additions & 0 deletions docs/source/changelog/1.5.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2755,6 +2755,11 @@ Fixed
* HIP's ``ECHO_RESPONSE_UNSIGNED`` maker builds an ``EchoResponseUnsignedParameter``
schema, where it built the request's; the octets were already right (:issue:`2170`,
:pr:`2188`).
* The MPL option's ``seed_type`` and the SMF_DPD option's ``tid_type`` makers (of
both HOPOPT and IPv6-Opts), and the HTTP/1.* start line's ``status``, resolve a
``str`` enum name against their own enum when no ``..._namespace`` is given,
where the ``None`` default made a name raise :exc:`.ProtocolNotImplemented`
(:issue:`2356`).

pcapkit.toolkit
---------------
Expand Down
5 changes: 4 additions & 1 deletion pcapkit/protocols/application/httpv1.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,10 @@ def _make_start_line(self, http_version: 'str | bytes', *,

header_line = b'%s %s HTTP/%s\r\n' % (meth, uri_val, version)
elif method is None and status is not None:
status_code = self._make_index(status, status_default, namespace=status_namespace,
# NOTE: ``status`` defaults to ``None``, so a :obj:`str` name has to be
# resolved against StatusCode when no namespace is given (#2356).
status_ns = status_namespace if status_namespace is not None else Enum_StatusCode
status_code = self._make_index(status, status_default, namespace=status_ns,
reversed=status_reversed, pack=False)
status_code_val = int(status_code)

Expand Down
10 changes: 8 additions & 2 deletions pcapkit/protocols/internet/hopopt.py
Original file line number Diff line number Diff line change
Expand Up @@ -1744,7 +1744,10 @@ def _make_opt_smf_dpd(self, code: 'Enum_Option', opt: 'Optional[Data_SMFIdentifi
tid_len = len(tid_bytes)

if tid_type is not None:
tid_kind = self._make_index(tid_type, tid_type_default, namespace=tid_type_namespace,
# NOTE: ``tid_type`` defaults to ``None``, so a :obj:`str` name has to be
# resolved against TaggerID when no namespace is given (#2356).
tid_type_ns = tid_type_namespace if tid_type_namespace is not None else Enum_TaggerID
tid_kind = self._make_index(tid_type, tid_type_default, namespace=tid_type_ns,
reversed=tid_type_reversed, pack=False)
elif tid_len == 0:
tid_kind = Enum_TaggerID.NULL
Expand Down Expand Up @@ -2054,7 +2057,10 @@ def _make_opt_mpl(self, code: 'Enum_Option', opt: 'Optional[Data_MPLOption]' = N
seed = opt.seed_id

if seed_type is not None:
kind = self._make_index(seed_type, seed_type_default, namespace=seed_type_namespace,
# NOTE: ``seed_type`` defaults to ``None``, so a :obj:`str` name has to be
# resolved against SeedID when no namespace is given (#2356).
seed_type_ns = seed_type_namespace if seed_type_namespace is not None else Enum_SeedID
kind = self._make_index(seed_type, seed_type_default, namespace=seed_type_ns,
reversed=seed_type_reversed, pack=False)
elif seed is None or isinstance(seed, ipaddress.IPv6Address):
kind = Enum_SeedID.IPV6_SOURCE_ADDRESS
Expand Down
10 changes: 8 additions & 2 deletions pcapkit/protocols/internet/ipv6_opts.py
Original file line number Diff line number Diff line change
Expand Up @@ -1732,7 +1732,10 @@ def _make_opt_smf_dpd(self, code: 'Enum_Option', opt: 'Optional[Data_SMFIdentifi
tid_len = len(tid_bytes)

if tid_type is not None:
tid_kind = self._make_index(tid_type, tid_type_default, namespace=tid_type_namespace,
# NOTE: ``tid_type`` defaults to ``None``, so a :obj:`str` name has to be
# resolved against TaggerID when no namespace is given (#2356).
tid_type_ns = tid_type_namespace if tid_type_namespace is not None else Enum_TaggerID
tid_kind = self._make_index(tid_type, tid_type_default, namespace=tid_type_ns,
reversed=tid_type_reversed, pack=False)
elif tid_len == 0:
tid_kind = Enum_TaggerID.NULL
Expand Down Expand Up @@ -2042,7 +2045,10 @@ def _make_opt_mpl(self, code: 'Enum_Option', opt: 'Optional[Data_MPLOption]' = N
seed = opt.seed_id

if seed_type is not None:
kind = self._make_index(seed_type, seed_type_default, namespace=seed_type_namespace,
# NOTE: ``seed_type`` defaults to ``None``, so a :obj:`str` name has to be
# resolved against SeedID when no namespace is given (#2356).
seed_type_ns = seed_type_namespace if seed_type_namespace is not None else Enum_SeedID
kind = self._make_index(seed_type, seed_type_default, namespace=seed_type_ns,
reversed=seed_type_reversed, pack=False)
elif seed is None or isinstance(seed, ipaddress.IPv6Address):
kind = Enum_SeedID.IPV6_SOURCE_ADDRESS
Expand Down
91 changes: 91 additions & 0 deletions tests/protocols/test_make_str_enum_name_none_default_2356_unit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
"""GitHub issue #2356: makers resolve a :obj:`str` enum name for a ``None`` default.

:meth:`~pcapkit.protocols.protocol.Protocol._make_index` resolves a :obj:`str`
name against the enum of the argument's default when no ``..._namespace`` is
given, so the makers whose argument defaults to ``None`` -- the MPL option's
``seed_type`` and the SMF_DPD option's ``tid_type`` (of both HOPOPT and
IPv6-Opts), and the HTTP/1.* start line's ``status`` -- raised
:exc:`~pcapkit.utilities.exceptions.ProtocolNotImplemented` for a name such as
``'IPv4'``. They now fall back to the argument's own enum, and a name builds
the same octets as the member it names.

The classes are imported inside each test, after
:func:`~tests._support.reimport_once_per_class`, so that they belong to the
:mod:`pcapkit` import that is live when the test runs.

"""

import importlib
import unittest

from tests._support import reimport_once_per_class

#: The protocol classes sharing the option makers, by module and class name.
PROTOCOLS = (
('pcapkit.protocols.internet.hopopt', 'HOPOPT'),
('pcapkit.protocols.internet.ipv6_opts', 'IPv6_Opts'),
)


class TestMakeStrEnumNameNoneDefault(unittest.TestCase):
"""Pin :obj:`str` enum names for the makers whose argument defaults to ``None``."""

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

def _protocols(self) -> 'list[type]':
return [getattr(importlib.import_module(mod), name) for mod, name in PROTOCOLS]

def test_mpl_seed_type_by_name(self) -> None:
from pcapkit.const.ipv6.option import Option
from pcapkit.const.ipv6.seed_id import SeedID

for cls in self._protocols():
proto = object.__new__(cls)
for member in (SeedID.SEEDID_16_BIT_UNSIGNED_INTEGER, SeedID.SEEDID_64_BIT_UNSIGNED_INTEGER):
with self.subTest(protocol=cls.__name__, seed_type=member.name):
by_member = proto._make_opt_mpl(Option.MPL_Option, seed_type=member, seed=1)
by_name = proto._make_opt_mpl(Option.MPL_Option, seed_type=member.name, seed=1)
self.assertEqual(by_name.pack(), by_member.pack())
self.assertEqual(by_name.flags['type'], int(member))

def test_smf_dpd_tid_type_by_name(self) -> None:
from pcapkit.const.ipv6.option import Option
from pcapkit.const.ipv6.tagger_id import TaggerID

for cls in self._protocols():
proto = object.__new__(cls)
for member, tid in ((TaggerID.IPv4, b'\x0a\x00\x00\x01'), (TaggerID.DEFAULT, b'\x01\x02')):
with self.subTest(protocol=cls.__name__, tid_type=member.name):
by_member = proto._make_opt_smf_dpd(Option.SMF_DPD, mode='I_DPD', tid_type=member,
tid=tid, id=b'\x01')
by_name = proto._make_opt_smf_dpd(Option.SMF_DPD, mode='I_DPD', tid_type=member.name,
tid=tid, id=b'\x01')
self.assertEqual(by_name.pack(), by_member.pack())

def test_http_status_by_name(self) -> None:
from pcapkit.const.http.status_code import StatusCode
from pcapkit.protocols.application.httpv1 import HTTP

proto = object.__new__(HTTP)
for member in (StatusCode.CODE_200, StatusCode.CODE_404):
with self.subTest(status=member.name):
line = proto._make_start_line('1.1', status=member.name)
self.assertTrue(line.startswith(b'HTTP/1.1 %d ' % int(member)), line)

def test_an_explicit_namespace_still_wins(self) -> None:
from pcapkit.const.ipv6.option import Option
from pcapkit.const.ipv6.seed_id import SeedID

for cls in self._protocols():
proto = object.__new__(cls)
with self.subTest(protocol=cls.__name__):
code = int(SeedID.SEEDID_16_BIT_UNSIGNED_INTEGER)
made = proto._make_opt_mpl(Option.MPL_Option, seed_type='short', seed=1,
seed_type_namespace={code: 'short'})
self.assertEqual(made.flags['type'], code)


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