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
12 changes: 7 additions & 5 deletions pcapkit/foundation/reassembly/ip.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ class IP(ReassemblyBase[Packet[_AT], Datagram[_AT], BufferID, Buffer[_AT]], Gene
<pcapkit.foundation.reassembly.data.ip.Datagram.conflict>`
(:issue:`477`). An incomplete buffer expires
after ``timeout`` seconds of capture time, 60 by default for both IPv4
and IPv6. A datagram with fragment offset zero and **MF** clear is
reported even when its BUFID is already buffered, rather than dropped
(:issue:`2270`).
and IPv6. A datagram with fragment offset zero and **MF** (**M** in
IPv6) clear is reported even when its BUFID is already buffered,
rather than dropped (:issue:`2270`).
"""
if TYPE_CHECKING:
Expand Down Expand Up @@ -156,8 +156,10 @@ def reassembly(self, info: 'Packet[_AT]') -> 'None':
# belong to.
self._dtgram.extend(self.expire(TS))

# when non-fragmented (possibly discarded) packet received: it is
# reassembled on its own, in a buffer of its own (#2270)
# a datagram with fragment offset zero and MF clear is reported even
# when its BUFID is already buffered, rather than dropped: isolated
# from that buffer (IPv6 atomic fragments) or after flushing it first
# (IPv4) (#2270)
isolated = False
if not FO and not MF:
if self.__isolate_atomic__:
Expand Down
34 changes: 19 additions & 15 deletions pcapkit/foundation/registry/protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,9 @@ class rather than supplied by a caller, and this function is the funnel
:class:`~pcapkit.foundation.reassembly.reassembly.ReassemblyMeta` and
:class:`~pcapkit.foundation.traceflow.traceflow.TraceFlowMeta` fall back to
:class:`~pcapkit.protocols.misc.raw.Raw`. The registry redesign of
:issue:`514` left this key unchanged and recorded the shared namespace as a
risk it did not fix, so the collision is reported here rather than
resolved.
:issue:`514` keeps this key, and its thread records the shared namespace as
a risk that design does not resolve, so the collision is reported here
rather than fixed.
Args:
protocol: Protocol class.
Expand Down Expand Up @@ -256,10 +256,11 @@ class _NameLease:
Each code registrar below ends by naming its class in the by-name registry,
and :mod:`pcapkit.foundation.registry` has no ``unregister``: a code is
undone by registering the entry it displaced (:issue:`1363`). Without a
record of that by-name write, handing a code back restored the code and left
the name behind (:issue:`1505`). A lease records what the write displaced and
which codes still dispatch to the class, so the last of them to be handed
back puts the displaced entry back, or removes the name if it was new.
record of that by-name write, handing a code back would restore the code and
leave the name behind (:issue:`1505`). A lease records what the write
displaced and which codes still dispatch to the class, so the last of them
to be handed back puts the displaced entry back, or removes the name if it
was new.
A lease is one write, not one class: the same class can hold two leases on
a name -- e.g. a built-in registered at one port over a custom class
Expand Down Expand Up @@ -366,9 +367,9 @@ def _register_name(protocol: 'Type[ProtocolBase]', *targets: 'tuple[type, Any]')
releasing whatever each target code held before, and followed by a lease
when the write changed the name, so that registering the code back undoes
it (:issue:`1505`). A name already showing ``protocol`` -- shipped, defined
by a :class:`~pcapkit.protocols.protocol.Protocol` subclass, or registered
directly -- is not leased, so no restore takes it away; one showing it
through a lease extends that lease to ``targets``.
by a :class:`~pcapkit.protocols.protocol.ProtocolBase` subclass, or
registered directly -- is not leased, so no restore takes it away; one
showing it through a lease extends that lease to ``targets``.
Args:
protocol: Protocol class just registered -- and so validated -- at ``targets``.
Expand Down Expand Up @@ -565,12 +566,15 @@ def register_protocol_code(protocol: 'Type[ProtocolBase]', code: 'Any') -> 'None

def _check_code(code: 'Any', kind: 'type', target: 'Type[ProtocolBase]', *,
number: 'bool' = False) -> 'None':
"""Reject a registration key that is not a member of the registrar's enum.
"""Reject a registration key that is not a member of the enum its
destination registry is keyed by.
Each registrar typed on one enum calls this before touching any registry,
so a bad ``code`` leaves every registry as it was (:issue:`2186`). The check is on the type,
not the value: the registry enums compare equal to their values, so a
member of another enum would key the entry of the member it equals, e.g.
as does :func:`register_protocol_code` for an inferred or explicitly
named destination, so a bad ``code`` leaves every registry as it was
(:issue:`2186`). The check is on the type, not the value: the registry
enums compare equal to their values, so a member of another enum would
key the entry of the member it equals, e.g.
:attr:`TransType.ICMP <pcapkit.const.reg.transtype.TransType.ICMP>` would
replace :attr:`LinkType.ETHERNET <pcapkit.const.reg.linktype.LinkType.ETHERNET>`
(:issue:`2224`).
Expand All @@ -580,7 +584,7 @@ def _check_code(code: 'Any', kind: 'type', target: 'Type[ProtocolBase]', *,
kind: Enum the registrar is typed on.
target: Protocol class whose registry ``code`` is destined for, named in
the error message so a rejection says *whose* registry rejected the
key rather than only what it wanted -- the registrars share one
key rather than only what it wanted -- its callers share one
message, and several of them are typed on the same enum.
number: Whether a bare :class:`int` is a valid key too, as for the
registrars keyed on a port number or a payload protocol identifier.
Expand Down
6 changes: 4 additions & 2 deletions pcapkit/protocols/application/httpv1.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,8 @@ def make(self, # type: ignore[override]
Raises:
ProtocolError: If the values are neither a request's nor a
response's, or if ``raw_line`` or a ``raw_header`` entry that
response's, if they are a request's and ``uri`` is
:obj:`None`, or if ``raw_line`` or a ``raw_header`` entry that
reads back as the given values carries a CR or LF that the
values do not (other than the CRLF of an ``obs-fold``).
Expand Down Expand Up @@ -328,7 +329,8 @@ def _make_start_line(self, http_version: 'str | bytes', *,
Raises:
ProtocolError: If the values are neither a request's nor a
response's.
response's, or if they are a request's and ``uri`` is
:obj:`None`.
"""
version = http_version.encode() if isinstance(http_version, str) else http_version
Expand Down
5 changes: 3 additions & 2 deletions pcapkit/protocols/internet/hip.py
Original file line number Diff line number Diff line change
Expand Up @@ -1955,8 +1955,9 @@ def _make_lifetime_octet(self, code: 'Enum_Parameter', version: 'int',
An explicit ``exponent`` is the octet and wins, as a parsed parameter's
is; otherwise ``lifetime`` is a duration and
:meth:`_make_registration_lifetime` encodes it. Shared by the three
:rfc:`8003` makers, which between them carry four such octets.
:meth:`_make_registration_lifetime` encodes it. Shared by the
:rfc:`8003` registration makers, wherever a ``Lifetime`` octet
appears.
Args:
code: parameter code
Expand Down
2 changes: 1 addition & 1 deletion pcapkit/protocols/internet/internet.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ def register(cls, code: 'Enum_TransType', protocol: 'ModuleDescriptor[ProtocolBa
Arguments:
code: protocol code as in :class:`~pcapkit.const.reg.transtype.TransType`
protocol: module descriptor or a
:class:`~pcapkit.protocols.protocol.Protocol` subclass
:class:`~pcapkit.protocols.protocol.ProtocolBase` subclass
Raises:
pcapkit.utilities.exceptions.RegistryError: If ``protocol`` is not a
Expand Down
6 changes: 3 additions & 3 deletions pcapkit/protocols/link/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def register(cls, code: 'Enum_EtherType', protocol: 'ModuleDescriptor[ProtocolBa
Arguments:
code: protocol code as in :class:`~pcapkit.const.reg.ethertype.EtherType`
protocol: module descriptor or a
:class:`~pcapkit.protocols.protocol.Protocol` subclass
:class:`~pcapkit.protocols.protocol.ProtocolBase` subclass
Raises:
pcapkit.utilities.exceptions.RegistryError: If ``protocol`` is not a
Expand All @@ -150,15 +150,15 @@ def register(cls, code: 'Enum_EtherType', protocol: 'ModuleDescriptor[ProtocolBa
replacement so a caller can tell *what* was lost. Fires only
when the incumbent differs from the replacement -- see
:meth:`ProtocolBase.register
<pcapkit.protocols.protocol.Protocol.register>` for the
<pcapkit.protocols.protocol.ProtocolBase.register>` for the
guard this shares with ``register_protocol``, and for the
shipped descriptor that a restore puts back (:issue:`1504`).
.. versionchanged:: 1.3.0
Takes the protocol as one ``protocol`` argument, a
:class:`~pcapkit.corekit.module.ModuleDescriptor` or a
:class:`~pcapkit.protocols.protocol.Protocol` subclass, in place of
:class:`~pcapkit.protocols.protocol.ProtocolBase` subclass, in place of
the ``module`` and ``class_`` names.
"""
Expand Down
12 changes: 11 additions & 1 deletion pcapkit/protocols/misc/null.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,17 @@ def protocol(self) -> 'NoReturn':
@property
def protochain(self) -> 'None': # type: ignore[override]
"""Protocol chain of current instance, which an empty payload does not
have."""
have.
.. versionchanged:: 1.5.0
Narrows :attr:`Protocol.protochain
<pcapkit.protocols.protocol.ProtocolBase.protochain>`'s declared
type from :class:`~pcapkit.corekit.protochain.ProtoChain` to
:obj:`None`, the value ``self._protos`` already held here; the fix
is to the declaration, not the runtime value (:issue:`2317`).
"""
return None

##########################################################################
Expand Down
21 changes: 16 additions & 5 deletions pcapkit/protocols/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,15 @@ def protocol(self) -> 'Optional[str]':
# protocol chain of current instance
@property
def protochain(self) -> 'ProtoChain':
"""Protocol chain of current instance."""
"""Protocol chain of current instance.
Note:
:class:`~pcapkit.protocols.misc.null.NoPayload` has no chain and
narrows this to :obj:`None`; :meth:`_decode_next_layer` passes it
on as ``basis``, which
:class:`~pcapkit.corekit.protochain.ProtoChain` accepts.
"""
return self._protos

# packet data
Expand Down Expand Up @@ -1031,7 +1039,7 @@ def register(cls, code: 'int', protocol: 'ModuleDescriptor | Type[ProtocolBase]'
Arguments:
code: protocol code
protocol: module descriptor or a
:class:`~pcapkit.protocols.protocol.Protocol` subclass
:class:`~pcapkit.protocols.protocol.ProtocolBase` subclass
Raises:
pcapkit.utilities.exceptions.RegistryError: If ``protocol`` is not a
Expand Down Expand Up @@ -1271,9 +1279,12 @@ def __init__(self, file: 'Optional[IO[bytes] | bytes]' = None, length: 'Optional
UnsupportedCall: When constructing (``file`` is :obj:`None`), if a
keyword names no parameter of this protocol's :meth:`make`,
:meth:`read`, :meth:`pack`, :meth:`unpack`,
:meth:`__post_init__` or :meth:`__init__`, anywhere in the MRO,
and is not listed in :attr:`__keywords__`. Parsing (``file`` is
given) is unaffected.
:meth:`__post_init__` or :meth:`__init__`, anywhere in the MRO
-- except a ``make`` whose class sets
:attr:`__own_make_keywords__`, which counts only while it is
the ``make`` of the protocol being constructed -- and is not
listed in :attr:`__keywords__`. Parsing (``file`` is given) is
unaffected.
Note:
The ``_layer``, ``_protocol`` and ``__context__`` keywords are
Expand Down
6 changes: 5 additions & 1 deletion pcapkit/protocols/transport/sctp.py
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,10 @@ def make(self,
Returns:
Constructed packet data.
Raises:
ProtocolError: If ``srcport`` or ``dstport`` is a :obj:`bool`
(:issue:`2287`).
Note:
There is no ``payload`` argument: SCTP carries its user data in the
``data`` field of a DATA chunk, so the payload is supplied as part
Expand Down Expand Up @@ -730,7 +734,7 @@ def register(cls, code: 'Enum_PayloadProtocolIdentifier | int', protocol: 'Modul
code: payload protocol identifier (PPID), as in
:class:`~pcapkit.const.sctp.payload_protocol_identifier.PayloadProtocolIdentifier`
protocol: module descriptor or a
:class:`~pcapkit.protocols.protocol.Protocol` subclass
:class:`~pcapkit.protocols.protocol.ProtocolBase` subclass
Important:
SCTP overrides :meth:`Transport.register
Expand Down
18 changes: 11 additions & 7 deletions pcapkit/protocols/transport/tcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,10 @@ def make(self,
Returns:
Constructed packet data.
Raises:
ProtocolError: If ``srcport`` or ``dstport`` is a :obj:`bool`
(:issue:`2287`).
.. versionchanged:: 1.5.0
An out-of-range ``srcport`` or ``dstport`` raises
Expand Down Expand Up @@ -2900,13 +2904,13 @@ def _make_mptcp_unknown(self, subtype: 'Enum_MPTCPOption', opt: 'Optional[Data_M
# three octets of header -- ``kind``, ``length`` and the subtype/flags
# octet whose low nibble takes ``data[0]`` -- and then ``data[1:]`` as
# its body, so the option is never shorter than 3 octets -- the smallest
# Multipath TCP option :rfc:`8684` section 3 permits. The former
# ``2 + len(data)`` declared 2 for ``data=b''`` while still packing those
# three octets, and the segment could not be read back:
# ``MPTCPUnknown.data`` sizes itself ``pkt['length'] - 3``, which went
# negative and raised ``ProtocolError: Field data resolved to a negative
# length``. For every non-empty ``data`` the two expressions agree, so
# this only changes the empty case.
# Multipath TCP option :rfc:`8684` section 3 permits. A plain
# ``2 + len(data)`` would declare 2 for ``data=b''`` while those three
# octets are packed anyway, and the segment could not be read back:
# ``MPTCPUnknown.data`` sizes itself ``pkt['length'] - 3``, which would
# go negative and raise ``ProtocolError: Field data resolved to a
# negative length``. For every non-empty ``data`` the two expressions
# agree, so only the empty case tells them apart.
return Schema_MPTCPUnknown(
kind=cast('Enum_Option', Enum_Option.Multipath_TCP),
length=3 + len(data[1:]),
Expand Down
6 changes: 3 additions & 3 deletions pcapkit/protocols/transport/transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def register(cls, code: 'int', protocol: 'ModuleDescriptor[ProtocolBase] | Type[
Arguments:
code: port number
protocol: module descriptor or a
:class:`~pcapkit.protocols.protocol.Protocol` subclass
:class:`~pcapkit.protocols.protocol.ProtocolBase` subclass
Important:
This method must be called from a non-abstract class, as the
Expand All @@ -99,7 +99,7 @@ def register(cls, code: 'int', protocol: 'ModuleDescriptor[ProtocolBase] | Type[
:class:`~pcapkit.corekit.module.ModuleDescriptor` counts as
different from the class it names. See
:meth:`ProtocolBase.register
<pcapkit.protocols.protocol.Protocol.register>` for the
<pcapkit.protocols.protocol.ProtocolBase.register>` for the
guard this shares with ``register_protocol``, and for the
shipped descriptor that a restore puts back (:issue:`1504`).
Expand All @@ -115,7 +115,7 @@ def register(cls, code: 'int', protocol: 'ModuleDescriptor[ProtocolBase] | Type[
Takes the protocol as one ``protocol`` argument, a
:class:`~pcapkit.corekit.module.ModuleDescriptor` or a
:class:`~pcapkit.protocols.protocol.Protocol` subclass, in place of
:class:`~pcapkit.protocols.protocol.ProtocolBase` subclass, in place of
the ``module`` and ``class_`` names.
"""
Expand Down
4 changes: 4 additions & 0 deletions pcapkit/protocols/transport/udp.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,10 @@ def make(self,
Returns:
Constructed packet data.
Raises:
ProtocolError: If ``srcport`` or ``dstport`` is a :obj:`bool`
(:issue:`2287`).
.. versionchanged:: 1.5.0
An out-of-range ``srcport`` or ``dstport`` raises
Expand Down
22 changes: 11 additions & 11 deletions tests/protocols/test_maker_invalid_format_messages_2296_unit.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# -*- coding: utf-8 -*-
"""GitHub issue #2296: makers name the argument they rejected.
Several ``_make_*`` builders refused a caller-supplied argument with the
*reader's* text -- ``<alias>: [Chunk|Param|Cause|OptNo N] invalid format``.
Nothing had been parsed at that point, so the message described wire data that
did not exist and told the caller neither which keyword was wrong nor what it
had passed. Each site now uses the house form established by #2232 and #2257,
``<prefix>: <lowercase description>: must be X, not {value!r}``.
The parse side is deliberately untouched: ``invalid format`` is the right thing
for a reader to say, which is why the ``SCTP: [Chunk 0] invalid format`` pin in
The ``_make_*`` builders pinned below refuse a caller-supplied argument in the
house form established by #2232 and #2257,
``<prefix>: <lowercase description>: must be X, not {value!r}``, which names
the keyword at fault and the value passed. A reader instead says
``<alias>: [Chunk|Param|Cause|OptNo N] invalid format``, which describes wire
data -- something a maker has not parsed -- and identifies neither.
Readers are deliberately exempt: ``invalid format`` is the right thing for a
reader to say, which is why the ``SCTP: [Chunk 0] invalid format`` pin in
:mod:`tests.protocols.transport.test_transport_edge_roundtrip_unit` -- a parse
case -- is left alone. :class:`ReadersKeepInvalidFormat` below pins that
case -- expects exactly that. :class:`ReadersKeepInvalidFormat` below pins that
division for the one maker/reader pair where the two guards check the same
thing.
Expand Down Expand Up @@ -427,7 +427,7 @@ def test_raw_secrets_data_still_builds(self) -> None:


class ReadersKeepInvalidFormat(MakerMessageTestCase):
"""The parse side still says ``invalid format``, as #2296 asks.
"""The parse side says ``invalid format``, as #2296 asks.
``_read_chunk_data`` and ``_make_chunk_data`` guard the same thing -- a DATA
chunk with no user data -- so the pair is where a sweep over the makers is
Expand Down
Loading
Loading