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
13 changes: 10 additions & 3 deletions pcapkit/protocols/transport/transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from pcapkit.corekit.module import ModuleDescriptor
from pcapkit.protocols.protocol import _PT, _ST, ProtocolBase
from pcapkit.protocols.schema.schema import keep_short_read, replay_short_read
from pcapkit.utilities.exceptions import StructError, UnsupportedCall, stacklevel
from pcapkit.utilities.exceptions import ProtocolError, StructError, UnsupportedCall, stacklevel
from pcapkit.utilities.logging import DEVMODE, get_logger
from pcapkit.utilities.warnings import RegistryWarning, warn

Expand Down Expand Up @@ -206,8 +206,8 @@ def __post_init__(self, file: 'Optional[IO[bytes] | bytes]' = None,
# Utilities.
##########################################################################

@staticmethod
def _make_port(port: 'Enum_AppType | int',
@classmethod
def _make_port(cls, port: 'Enum_AppType | int',
proto: 'Enum_TransportProtocol') -> 'Enum_AppType':
"""Resolve a port number to its application type.

Expand All @@ -219,6 +219,9 @@ def _make_port(port: 'Enum_AppType | int',
Returns:
The :class:`~pcapkit.const.reg.apptype.apptype.AppType` for ``port``.

Raises:
ProtocolError: If ``port`` is a :obj:`bool` (:issue:`2287`).

Important:
:meth:`self.make <pcapkit.protocols.protocol.Protocol.make>` accepts
a bare :obj:`int` for a port, but the schema field converts it only
Expand All @@ -233,6 +236,10 @@ def _make_port(port: 'Enum_AppType | int',
"""
if isinstance(port, Enum_AppType):
return port
# NOTE: ``bool`` is an ``int`` subclass, so ``AppType.get`` would look
# ``True``/``False`` up as port 1/0 rather than refuse them (#2287).
if isinstance(port, bool):
raise ProtocolError(f'{cls.__name__}: invalid port: {port!r}')
return Enum_AppType.get(port, proto=proto)

def _decode_next_layer(self, dict_: '_PT', ports: 'tuple[int, int]', length: 'Optional[int]' = None, *, # type: ignore[override]
Expand Down
19 changes: 19 additions & 0 deletions tests/protocols/transport/test_transport_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,25 @@ def test_make_port_resolves_an_integer_and_passes_an_apptype_through(self) -> No
# which would lose the protocol it was resolved for.
self.assertIs(Transport._make_port(over_sctp, TransportProtocol.tcp), over_sctp)

def test_make_refuses_a_bool_port(self) -> None:
# #2287: ``bool`` is an ``int`` subclass, so ``AppType.get`` resolving
# ``True``/``False`` to port 1/0 is a defect this guards against.
from pcapkit.protocols.transport.sctp import SCTP
from pcapkit.protocols.transport.tcp import TCP
from pcapkit.protocols.transport.udp import UDP
from pcapkit.utilities.exceptions import ProtocolError

for cls in (TCP, UDP, SCTP):
for value in (True, False):
for kwargs in ({'srcport': value, 'dstport': 53}, {'srcport': 53, 'dstport': value}):
with self.subTest(protocol=cls.__name__, **kwargs):
with self.assertRaises(ProtocolError) as ctx:
cls(**kwargs)
self.assertEqual(str(ctx.exception), f'{cls.__name__}: invalid port: {value!r}')

# An ``int`` port resolves to its application type.
self.assertEqual(UDP(srcport=1, dstport=53).info.srcport.port, 1)


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