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
2 changes: 1 addition & 1 deletion pcapkit/foundation/engines/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

.. _pcap-ct: https://pypi.org/project/pcap-ct/

.. _PCAPNG: https://wiki.wireshark.org/Development/PcapNg
.. _PCAP-NG: https://wiki.wireshark.org/Development/PcapNg

"""
# Base class
Expand Down
11 changes: 5 additions & 6 deletions pcapkit/foundation/engines/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ def __init_subclass__(cls, /, engine: 'Optional[str]' = None, *args: 'Any', **kw
rather than having to inherit :class:`EngineBase` to avoid it, and it
matches :meth:`EnumSchema.__init_subclass__
<pcapkit.protocols.schema.schema.EnumSchema.__init_subclass__>`, which
has guarded on its own ``code`` keyword all along.
guards on its own ``code`` keyword the same way.

Note:
:attr:`__engine_name__
Expand Down Expand Up @@ -295,11 +295,10 @@ def __init_subclass__(cls, /, engine: 'Optional[str]' = None, *args: 'Any', **kw
"""
# NOTE: an unrecognised class keyword lands in ``**kwargs`` and is then
# dropped by the bare ``super().__init_subclass__()`` below, since
# ``object.__init_subclass__`` takes none. Silently swallowing it is how
# ``class MyEngine(Engine, engnie='x')`` would register under its class
# name instead -- no exception, no warning. Since a missing keyword means
# "do not register", the same typo would silently skip registration
# altogether, which is quieter still. So reject it.
# ``object.__init_subclass__`` takes none. Since a missing keyword means
# "do not register", silently swallowing it would make
# ``class MyEngine(Engine, engnie='x')`` skip registration altogether --
# no exception, no warning. So reject it.
#
# One typo this cannot catch is ``name=``, and only on Python 3.10: it is
# one of the four names that collide with ``ABCMeta.__new__``, so it fails
Expand Down
4 changes: 2 additions & 2 deletions pcapkit/foundation/engines/pcap.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ def run(self) -> 'None':

The method will parse the PCAP global header and save the parsed result
as :attr:`self.header <header>`. Information such as PCAP version, data
link layer protocol type, nanosecond flag and byteorder are also saved
on the current :class:`PCAP` engine instance.
link layer protocol type and nanosecond flag are also saved on the
current :class:`PCAP` engine instance.

For output, the method will dump the parsed PCAP global header under
the name of ``Global Header``.
Expand Down
2 changes: 1 addition & 1 deletion pcapkit/foundation/engines/pcap_ct.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ def run(self) -> 'None':

Raises:
FormatError: If the file format is not supported, i.e., not a PCAP
file. PCAP-NG is rejected explicitly even though the vendored
file. PCAP-NG is rejected explicitly even though
:manpage:`libpcap(3)` can read one -- see the note below.
UnsupportedCall: If the input is not a file on disk, as
:c:func:`pcap_open_offline` can only open a savefile by name.
Expand Down
2 changes: 1 addition & 1 deletion pcapkit/foundation/engines/pyshark.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def unsupported_reason(cls) -> 'Optional[str]':
# reports that case in its own words, and answering here as well would
# produce two warnings for one problem. An ImportError from a *renamed*
# upstream helper lands here too, and the engine then simply proceeds
# as it did before this check existed.
# without the ``tshark`` check.
return None

try:
Expand Down
142 changes: 75 additions & 67 deletions pcapkit/foundation/extraction.py

Large diffs are not rendered by default.

23 changes: 12 additions & 11 deletions pcapkit/foundation/reassembly/data/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class Completion(EnumLookup, StrEnum):
:attr:`Datagram.completed <pcapkit.foundation.reassembly.data.ip.Datagram.completed>`,
which widens a plain :obj:`bool` rather than adding a second channel beside
it: reassembly has *three* outcomes to report, not two, since a buffer
abandoned under the :rfc:`791` / :rfc:`8200` reassembly timeout is a different
abandoned under the :rfc:`1122` / :rfc:`8200` reassembly timeout is a different
event from one that simply had not finished when the capture did. An expired
datagram says "these fragments are gone", a partial one says "these fragments
had not arrived yet".
Expand All @@ -49,8 +49,8 @@ class Completion(EnumLookup, StrEnum):
field straight out -- and ``datagram.completed == 'timeout'`` works, so a
state can be tested for without importing this class.
:class:`pcapkit.const.pcapng.tls_key_label.TLSKeyLabel` is the same kind of
string enumeration, but GitHub issue :issue:`886` made it generated like its
:mod:`pcapkit.const.pcapng` siblings, so it derives from :class:`aenum`'s own
string enumeration, but it is generated like its :mod:`pcapkit.const.pcapng`
siblings (:issue:`886`), so it derives from :class:`aenum`'s own
``StrEnum`` (via :class:`~pcapkit.corekit.enum.EnumRegistry`). Both
properties above hold for it, as ``aenum.StrEnum`` is a :class:`str`
subclass, but ``isinstance``/``issubclass`` against
Expand All @@ -60,7 +60,7 @@ class Completion(EnumLookup, StrEnum):
Being a :class:`str` whose :attr:`PARTIAL` and :attr:`TIMEOUT` members are
**falsy** makes this a non-empty string that tests false, so ``bool(x)``
and ``bool(str(x))`` disagree. That is deliberate -- the truthiness above
is the property callers of a former :obj:`bool` field rely on -- but code
is what lets ``if datagram.completed:`` read as a boolean test -- but code
that takes this for an ordinary string and tests it for truth will read it
backwards.

Expand Down Expand Up @@ -113,10 +113,10 @@ class Deferred:
*every* frame -- not only the fragmented ones, since a frame that is not
fragmented in any sense still reaches
:meth:`IP.reassembly <pcapkit.foundation.reassembly.ip.IP.reassembly>` and is
submitted there as a trivially complete datagram -- so the eager parse
re-parsed captures holding no fragments at all: :file:`http.pcap` has 1117
IPv4 frames, none of them fragmented, and the parse was 86% of the cost of IP
reassembly over it.
submitted there as a trivially complete datagram -- so an eager parse would
re-parse captures holding no fragments at all: :file:`http.pcap` has 1117
IPv4 frames, none of them fragmented, and the eager parse measured 86% of the
cost of IP reassembly over it.

TCP reassembly defers its ``packet`` as well
(:meth:`TCP.submit <pcapkit.foundation.reassembly.tcp.TCP.submit>`), at a far
Expand All @@ -125,8 +125,8 @@ class Deferred:

Holding the call here defers it to the first read of
:attr:`Datagram.packet`, so a caller that wants the parsed payload still gets
exactly the object the eager call produced, and one that does not never pays
for it.
exactly the object an eager call would produce, and one that does not never
pays for it.

Args:
analyze: The analyser to call, i.e.
Expand Down Expand Up @@ -184,7 +184,8 @@ def __analyse__(self) -> 'Optional[ProtocolBase]':
"""Resolve a deferred analysis, at most once.

Returns:
Parsed IP payload, or :data:`None` for an incomplete datagram.
Parsed payload, or :data:`None` for a datagram reported as the tuple
of its received runs.

"""
key = self.__map__.get('packet', 'packet')
Expand Down
6 changes: 3 additions & 3 deletions pcapkit/foundation/reassembly/data/ip.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
@info_final
class Packet(Info, Generic[_AT]):
"""Data model for :term:`IPv4 <reasm.ipv4.packet>` and/or
:term:`IPv6 <reasm.ipv6.packet>` packet representation.."""
:term:`IPv6 <reasm.ipv6.packet>` packet representation."""

#: Buffer ID.
bufid: 'BufferID'
Expand All @@ -48,7 +48,7 @@ class Packet(Info, Generic[_AT]):
#: Raw :obj:`bytearray` type payload.
payload: 'bytearray'
#: Capture timestamp of the fragment, in seconds since the Unix epoch. This
#: is the *capture's* clock, not the host's: it is what drives the :rfc:`791`
#: is the *capture's* clock, not the host's: it is what drives the :rfc:`1122#section-3.3.2`
#: and :rfc:`8200#section-4.5` reassembly timeout, since an offline parser
#: replaying a file has no other notion of time passing.
timestamp: 'float'
Expand Down Expand Up @@ -157,7 +157,7 @@ class Buffer(Info, Generic[_AT]):
TDL: 'int'
#: Fragment received bit table.
RCVBT: 'bytearray'
#: List of reassembled packets.
#: Numbers of the packets merged into this buffer.
index: 'list[int]'
#: Header buffer.
header: 'bytes'
Expand Down
4 changes: 2 additions & 2 deletions pcapkit/foundation/reassembly/data/tcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class Datagram(DeferredPacket, Info, Generic[_AT]):
#: How completely the datagram was reassembled, and why reassembly stopped;
#: see :class:`~pcapkit.foundation.reassembly.data.data.Completion`. Only
#: :attr:`Completion.COMPLETE` is truthy, so ``if datagram.completed:`` reads
#: as it did while this was a :obj:`bool`.
#: as it would for a :obj:`bool`.
completed: 'Completion'
#: Listing ``packet`` here is what makes it lazy -- see
#: :class:`~pcapkit.foundation.reassembly.data.data.DeferredPacket`.
Expand Down Expand Up @@ -157,7 +157,7 @@ def __init__(self, first: 'int', last: 'int') -> 'None': ... # pylint: disable=
class Fragment(Info):
"""Data model for :term:`TCP <reasm.tcp.buffer>` ACK list fragment item."""

#: List of reassembled packets.
#: Numbers of the packets merged into this fragment.
ind: 'list[int]'
#: Sequence number of the octet held in ``raw[0]``, i.e. the origin this
#: buffer is indexed from: ``raw[n]`` holds the octet whose sequence number
Expand Down
14 changes: 8 additions & 6 deletions pcapkit/foundation/reassembly/ip.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
only, which reconstructs fragmented IP packets back to
origin. The following algorithm implement is based on IP
reassembly procedure introduced in :rfc:`791`, using
``RCVBT`` (fragment receivedbit table). Though another
``RCVBT`` (fragment received bit table). Though another
algorithm is explained in :rfc:`815`, replacing ``RCVBT``,
however, this implement still used the elder one.

Expand All @@ -34,8 +34,10 @@ class IP(ReassemblyBase[Packet[_AT], Datagram[_AT], BufferID, Buffer[_AT]], Gene
"""Reassembly for IP payload.

Args:
strict: if return all datagrams (including those not
implemented) when submit
strict: if :data:`True`, report a datagram that is not completely
reassembled as the tuple of its received runs; otherwise as one
contiguous payload, its holes zero-filled -- or, while the total
length is unknown, only the prefix up to the first hole
store: if store reassembled datagram in memory, i.e.,
:attr:`self._dtgram <pcapkit.foundation.reassembly.reassembly.Reassembly._dtgram>`
(if not, datagram will be discarded after callback)
Expand Down Expand Up @@ -147,7 +149,7 @@ def reassembly(self, info: 'Packet[_AT]') -> 'None':
# TCP's first-write-wins (:rfc:`9293#section-3.10`) -- so the arriving
# payload always overwrites here; ``conflicts`` above is what records
# that it *disagreed* with what it overwrote, which is the part RFC 791
# leaves unrecorded and this fix adds.
# leaves unrecorded.
buf.datagram[start:stop] = info.payload

# set RCVBT bits (in 8 octets)
Expand Down Expand Up @@ -318,8 +320,8 @@ def submit(self, buf: 'Buffer[_AT]', *, bufid: 'tuple[_AT, _AT, int, TransType]'
if TDL > 0:
# The length is known. Report it, holes and all: the gaps read as
# zeros, exactly as they do in the TCP reassembler's loose mode.
# This is unchanged behaviour, and the reason ``strict=False``
# exists -- a caller who wants the gaps *marked* rather than
# This is the reason ``strict=False`` exists -- a caller who
# wants the gaps *marked* rather than
# zero-filled uses ``strict=True`` and gets the runs.
stop = TDL
else:
Expand Down
6 changes: 4 additions & 2 deletions pcapkit/foundation/reassembly/ipv4.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ class IPv4(IP):
"""Reassembly for IPv4 payload.

Args:
strict: if return all datagrams (including those not
implemented) when submit
strict: if :data:`True`, report a datagram that is not completely
reassembled as the tuple of its received runs; otherwise as one
contiguous payload, its holes zero-filled -- or, while the total
length is unknown, only the prefix up to the first hole
store: if store reassembled datagram in memory, i.e.,
:attr:`self._dtgram <pcapkit.foundation.reassembly.reassembly.Reassembly._dtgram>`
(if not, datagram will be discarded after callback)
Expand Down
6 changes: 4 additions & 2 deletions pcapkit/foundation/reassembly/ipv6.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,10 @@ class IPv6(IP):
"""Reassembly for IPv6 payload.

Args:
strict: if return all datagrams (including those not
implemented) when submit
strict: if :data:`True`, report a datagram that is not completely
reassembled as the tuple of its received runs; otherwise as one
contiguous payload, its holes zero-filled -- or, while the total
length is unknown, only the prefix up to the first hole
store: if store reassembled datagram in memory, i.e.,
:attr:`self._dtgram <pcapkit.foundation.reassembly.reassembly.Reassembly._dtgram>`
(if not, datagram will be discarded after callback)
Expand Down
39 changes: 23 additions & 16 deletions pcapkit/foundation/reassembly/reassembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,10 @@ class ReassemblyBase(Generic[_PT, _DT, _IT, _BT], metaclass=ReassemblyMeta):
"""Base class for reassembly procedure.

Args:
strict: if return all datagrams (including those not
implemented) when submit
strict: if :data:`True`, report a datagram that is not completely
reassembled as the tuple of its received runs; otherwise as one
contiguous payload, its holes zero-filled -- or, for IP while the
total length is unknown, only the prefix up to the first hole
store: if store reassembled datagram in memory, i.e.,
:attr:`self._dtgram <_dtgram>` (if not, datagram
will be discarded after callback)
Expand Down Expand Up @@ -401,7 +403,7 @@ def register(cls, callback: 'CallbackFn', *, index: 'Optional[int]' = None) -> '
callback: callback function, which will be called
when reassembled datagram is obtained, with the
list of reassembled datagrams as its only argument
index: index to be inserted in the callback list,; by
index: index to be inserted in the callback list; by
default, the callback will be appended to the end
of the list

Expand Down Expand Up @@ -429,8 +431,10 @@ def __init__(self, *, strict: 'bool' = True, store: 'bool' = True,
"""Initialise packet reassembly.

Args:
strict: if return all datagrams (including those not
implemented) when submit
strict: if :data:`True`, report a datagram that is not completely
reassembled as the tuple of its received runs; otherwise as one
contiguous payload, its holes zero-filled -- or, for IP while the
total length is unknown, only the prefix up to the first hole
store: if store reassembled datagram in memory, i.e.,
:attr:`self._dtgram <_dtgram>` (if not, datagram
will be discarded after callback)
Expand All @@ -443,10 +447,11 @@ def __init__(self, *, strict: 'bool' = True, store: 'bool' = True,
FieldValueError: If ``timeout`` is negative.

"""
#: bool: Strict mode flag. If set to :data:`True`, all
#: data will be returned, including those not completely
#: reassembled; otherwise, only completely reassembled
#: data will be returned.
#: bool: Strict mode flag. If set to :data:`True`, a datagram
#: not completely reassembled is reported as the tuple of its
#: received runs; otherwise, as one contiguous payload, its
#: holes zero-filled -- or, for IP while the total length is
#: unknown, only the prefix up to the first hole.
self._flag_s = strict
#: bool: Store mode flag. If set to :data:`True`, all
#: reassembled datagram will be stored in memory, i.e.,
Expand Down Expand Up @@ -526,8 +531,10 @@ class MyMixin(Reassembly): # not registered
Extractor.register_reassembly('my_mixin', MyMixin)

Arguments:
strict: if return all datagrams (including those not
implemented) when submit
strict: if :data:`True`, report a datagram that is not completely
reassembled as the tuple of its received runs; otherwise as one
contiguous payload, its holes zero-filled -- or, for IP while the
total length is unknown, only the prefix up to the first hole
store: if store reassembled datagram in memory, i.e.,
:attr:`self._dtgram <_dtgram>` (if not, datagram
will be discarded after callback)
Expand Down Expand Up @@ -556,7 +563,7 @@ def __init_subclass__(cls, /, protocol: 'Optional[str]' = None, *args: 'Any', **
rather than having to inherit :class:`ReassemblyBase` to avoid it, and it
matches :meth:`EnumSchema.__init_subclass__
<pcapkit.protocols.schema.schema.EnumSchema.__init_subclass__>`, which
has guarded on its own ``code`` keyword all along.
guards on its own ``code`` keyword the same way.

Note:
:attr:`__protocol_name__` is *not* an opt-in. It supplies the
Expand All @@ -570,10 +577,10 @@ def __init_subclass__(cls, /, protocol: 'Optional[str]' = None, *args: 'Any', **

"""
# NOTE: the keyword here is ``protocol``, but ``Engine`` spells the same
# idea ``name`` -- so guessing ``name=`` by analogy is the expected
# mistake, not a careless one. Left in ``**kwargs`` it would be
# dropped by the bare ``super().__init_subclass__()`` below, leaving the
# class registered under its own class name: no exception, no warning.
# idea ``engine`` -- so guessing the other base's keyword, or ``name=``,
# is the expected mistake, not a careless one. Left in ``**kwargs`` it
# would be dropped by the bare ``super().__init_subclass__()`` below,
# leaving the class silently unregistered: no exception, no warning.
# See the sibling note in ``Engine.__init_subclass__``.
if args or kwargs:
unexpected = ', '.join([*map(repr, args), *sorted(kwargs)])
Expand Down
9 changes: 5 additions & 4 deletions pcapkit/foundation/reassembly/tcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
.. module:: pcapkit.foundation.reassembly.tcp

:mod:`pcapkit.foundation.reassembly.tcp` contains
:class:`~pcapkit.foundation.reassembly.reassembly.Reassembly` only,
:class:`~pcapkit.foundation.reassembly.tcp.TCP` only,
which reconstructs fragmented TCP packets back to origin.

"""
Expand All @@ -29,8 +29,9 @@ class TCP(ReassemblyBase[Packet, Datagram, BufferID, Buffer]):
"""Reassembly for TCP payload.

Args:
strict: if return all datagrams (including those not
implemented) when submit
strict: if :data:`True`, report a datagram that is not completely
reassembled as the tuple of its received runs; otherwise as one
contiguous payload, its holes zero-filled
store: if store reassembled datagram in memory, i.e.,
:attr:`self._dtgram <pcapkit.foundation.reassembly.reassembly.Reassembly._dtgram>`
(if not, datagram will be discarded after callback)
Expand Down Expand Up @@ -393,7 +394,7 @@ def _merge_overlap(gap: 'list[tuple[int, int]]', old: 'bytes', new: 'bytes',
new: the arriving segment's bytes over the same range.
start: absolute sequence number of ``old[0]``/``new[0]``, which
cover the same range by construction -- see the two call
sites in :meth:`reassembly`.
sites, :meth:`_reassemble_append` and :meth:`_reassemble_prepend`.

Returns:
The bytes to keep for the range, and any ``(first, last)``
Expand Down
Loading
Loading