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
22 changes: 10 additions & 12 deletions tests/protocols/misc/pcap/test_frame_endian_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,18 @@
Classic PCAP declares its byte order in the magic number of the global header,
and the four fields of every record header -- ``ts_sec``, ``ts_usec``,
``incl_len`` and ``orig_len`` -- are written in that order rather than in the
reading host's. Nothing in this suite read a big-endian ``.pcap`` before, because
no such capture existed here: every fixture was little-endian, so on a
little-endian runner a reader that ignored the file's declared order and used
:data:`sys.byteorder` produced exactly the right answer.
reading host's. Little-endian fixtures alone cannot show whether that order is
honoured: on a little-endian runner, a reader that ignores the file's declared
order and uses :data:`sys.byteorder` produces exactly the right answer.

That is what GitHub issue #605 was.
This guards against GitHub issue #605, where
:meth:`Frame.unpack <pcapkit.protocols.misc.pcap.frame.Frame.unpack>` seeded the
order under the key ``bytesorder``, while ``byteorder_callback`` -- then in
:file:`pcapkit/protocols/schema/misc/pcap/frame.py`, now in
:file:`pcapkit/corekit/fields/byteorder.py` -- reads ``byteorder``, so the
order under the key ``bytesorder``, while ``byteorder_callback``
(:file:`pcapkit/corekit/fields/byteorder.py`) reads ``byteorder``, so the
``.get()`` never found the key and always fell back to the host's order -- the
sibling :meth:`Frame.pack <pcapkit.protocols.misc.pcap.frame.Frame.pack>`
eleven lines earlier spelled it correctly, which is what marks it as a slip
rather than a second key. Measured on the unfixed tree against
spelled it correctly, which is what marks it as a slip rather than a second
key. Measured without the fix against
``big_endian.pcap``, whose first record really holds ``ts_sec=1500000000``,
``ts_usec=123456`` and ``incl_len=74``: frame 1 came back with
``ts_sec=3106905``, ``ts_usec=1088553216`` and ``incl_len=1241513984``, i.e.
Expand Down Expand Up @@ -179,7 +177,7 @@ def assertRecords(self, name: 'str', magic: 'bytes', endian: 'str',
def test_big_endian_record_headers_are_read_in_the_files_byte_order(self) -> None:
"""#605: ``a1 b2 c3 d4``, microsecond timestamps.

On the unfixed tree this raises ``ValueError: read length must be
Without the fix this raises ``ValueError: read length must be
non-negative or -1`` before reaching any assertion: frame 1's swapped
``incl_len`` of 1241513984 consumes the whole file, and the read that
follows is handed a negative payload length.
Expand All @@ -203,7 +201,7 @@ def test_big_endian_nanosecond_record_headers_are_read_in_the_files_byte_order(s
def test_little_endian_twin_is_unaffected(self) -> None:
"""The control: the same three records in the little-endian container.

This passes on the unfixed tree too, and that is what it is for. It
This passes without the fix too, and that is what it is for. It
pins the expectations of the big-endian tests to a file whose byte
order was never in question, so a failure there cannot be blamed on the
records themselves being wrong.
Expand Down
9 changes: 5 additions & 4 deletions tests/protocols/misc/pcap/test_frame_from_data_runtime.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# -*- coding: utf-8 -*-
"""``Frame.from_data`` rebuilds a captured frame.

GitHub issue #1126: :meth:`~pcapkit.protocols.protocol.ProtocolBase.from_data`
took nothing but the data, so the ``num`` and ``header`` that
This guards against GitHub issue #1126, where
:meth:`~pcapkit.protocols.protocol.ProtocolBase.from_data` took nothing but the
data, so the ``num`` and ``header`` that
:class:`~pcapkit.protocols.misc.pcap.frame.Frame` requires could not reach it,
and every call raised :exc:`TypeError`. ``from_data`` now forwards extra
keywords to the construction, so the caller passes both alongside the data.
and every call raised :exc:`TypeError`. ``from_data`` forwards extra keywords to
the construction, so the caller passes both alongside the data.

The frames are read from :file:`examples/captures/in.pcap`, so this belongs to
the runtime tier.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
"""A PCAP record claiming more octets than its file holds warns, and is kept as captured.

GitHub issue #1541. A record whose ``incl_len`` runs past the end of the file
parsed silently. It is now reported the way the PCAP-NG reader reports a
This guards against GitHub issue #1541, where a record whose ``incl_len`` runs
past the end of the file parsed silently. It is reported the way the PCAP-NG
reader reports a
captured length running past its block (#1405), and is kept as captured in the
same way: ``incl_len`` and ``cap_len`` stay as declared, the packet is the octets
present, and the frame rebuilds as exactly the octets read, from ``info`` and
Expand Down
26 changes: 13 additions & 13 deletions tests/protocols/misc/pcap/test_frame_length_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

Both readers that build a
:class:`~pcapkit.protocols.data.misc.pcap.frame.Frame` have to agree about which
of the two wire fields lands in which attribute, and until GitHub issue #618 they
did not: the classic-PCAP reader in
of the two wire fields lands in which attribute. GitHub issue #618 is the
defect where they did not: the classic-PCAP reader in
:mod:`pcapkit.protocols.misc.pcap.frame` wrote ``len=incl_len,
cap_len=orig_len`` while :func:`pcapkit.toolkit.pcapng.block2frame` wrote
``len=original_len, cap_len=captured_len``. Exactly inverted, so ``frame.len``
Expand All @@ -31,12 +31,12 @@
been truncated", which "SHOULD NOT be less than the Captured Packet Length". So
neither format is the odd one out -- only the reader was.

Why it survived so long: **the two lengths are equal unless the snapshot length
Why fixtures can miss it: **the two lengths are equal unless the snapshot length
cut the frame short**, so a fixture without a truncated frame compares a value
against itself and cannot tell the two assignments apart. There was no such
fixture here until #614 added one. This module therefore leans on the two that
exist, and asserts that they really are truncated before asserting anything
about them -- a fixture regenerated into something untruncated fails
against itself and cannot tell the two assignments apart. This module therefore
leans on the two truncated fixtures (#614), and asserts that they really are
truncated before asserting anything about them -- a fixture regenerated into
something untruncated fails
:meth:`PcapFrameLengthRuntimeTests.test_the_fixture_really_holds_a_truncated_frame`
loudly rather than making everything below vacuous:

Expand Down Expand Up @@ -226,7 +226,7 @@ def test_the_fixture_really_holds_a_truncated_frame(self) -> None:
def test_len_is_the_on_wire_length_and_cap_len_the_captured_one(self) -> None:
"""#618: the PCAP reader had these two the other way round.

On the unfixed tree the truncated frame comes back with ``len=96`` and
Without the fix the truncated frame comes back with ``len=96`` and
``cap_len=1200`` -- the captured length under the name that means
on-wire, and vice versa -- so this fails on that frame in every fixture
while passing on the two untruncated ones, which is exactly the shape of
Expand Down Expand Up @@ -283,9 +283,9 @@ def test_cap_len_counts_the_octets_the_frame_actually_carries(self) -> None:
class PcapngFrameLengthRuntimeTests(unittest.TestCase):
"""#618, on the PCAP-NG reader -- the one that was already right.

This is the control. It passes on the unfixed tree too, and that is what it
is for: it pins the convention the PCAP reader was brought into line with, so
a later change that "fixes" the disagreement from the other end fails here.
This is the control. It passes without the fix too, and that is what it is
for: it pins the convention the PCAP reader follows, so a change that
"fixes" the disagreement from the other end fails here.

"""

Expand Down Expand Up @@ -383,7 +383,7 @@ def setUp(self) -> None:
def test_a_truncated_frame_reads_alike_whichever_container_it_came_from(self) -> None:
"""One caller, two capture formats, one meaning per attribute.

On the unfixed tree the PCAP side reports ``len < cap_len`` and the
Without the fix the PCAP side reports ``len < cap_len`` and the
PCAP-NG side ``len > cap_len`` for the same kind of frame, and this fails
on the first assertion of the pair.

Expand Down Expand Up @@ -411,7 +411,7 @@ def test_a_truncated_frame_reads_alike_whichever_container_it_came_from(self) ->
for frame in truncated:
with self.subTest(cap_len=frame.cap_len):
# the on-wire length is the larger of the two, in both formats.
# This is the assertion #618 breaks: before the fix the PCAP
# This is the assertion #618 breaks: without the fix the PCAP
# frame reports 96 against 1200 and fails here, while the
# PCAP-NG one reports 314 against 32 and passes
self.assertGreater(frame.len, frame.cap_len)
Expand Down
13 changes: 6 additions & 7 deletions tests/protocols/misc/pcap/test_frame_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,15 @@ def test_extracted_frames_carry_their_own_record_bytes(self) -> None:
self.assertEqual(bytes(frame), expected)
self.assertEqual(frame.packet.header, expected[:16])
self.assertEqual(frame.packet.payload, expected[16:])
# the nested layer was always right -- it is parsed from the
# schema's payload rather than from the frame's raw data -- so this
# pins the two to each other, which is what was broken.
# the nested layer is parsed from the schema's payload rather than
# from the frame's raw data, so this pins the two to each other.
#
# Sliced by ``cap_len`` rather than by ``len``: the octets in the
# file are the *captured* ones, and since #618 ``len`` is the on-wire
# length, which for a truncated frame runs past the end of the
# file are the *captured* ones, and ``len`` is the on-wire length
# (#618), which for a truncated frame runs past the end of the
# record. ``arp.pcap`` is not truncated, so the two are equal here
# and this is a correctness fix rather than a behaviour change --
# which is exactly why it needs stating.
# and the choice changes no result -- which is exactly why it needs
# stating.
self.assertEqual(bytes(frame.payload), expected[16:16 + frame.info.cap_len])


Expand Down
13 changes: 7 additions & 6 deletions tests/protocols/misc/pcap/test_frame_snaplen_zero_1385_unit.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# -*- coding: utf-8 -*-
"""Building a PCAP frame under a zero snaplen keeps the whole packet.

GitHub issue #1385: :meth:`Frame.make <pcapkit.protocols.misc.pcap.frame.Frame.make>`
defaulted ``incl_len`` to ``min(len(packet), snaplen)``, so a global header with
snaplen 0 produced a 16-octet record holding no packet data. The PCAP draft says
snaplen "MUST NOT be zero" (draft-ietf-opsawg-pcap, section 4), and libpcap reads
a zero value as the maximum for the link type (``pcapint_adjust_snapshot``), so
a zero snaplen is now no limit, as it already is for PCAP-NG.
This guards against GitHub issue #1385, where
:meth:`Frame.make <pcapkit.protocols.misc.pcap.frame.Frame.make>` defaulted
``incl_len`` to ``min(len(packet), snaplen)``, so a global header with snaplen 0
produced a 16-octet record holding no packet data. The PCAP draft says snaplen
"MUST NOT be zero" (draft-ietf-opsawg-pcap, section 4), and libpcap reads a zero
value as the maximum for the link type (``pcapint_adjust_snapshot``), so a zero
snaplen is no limit, as it is for PCAP-NG.

Every case builds its own octets and reads no capture. Classes are imported
inside each test, after :func:`~tests._support.reimport_once_per_class`.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
"""A PCAP file ending in a partial record header stops cleanly at EOF.

GitHub issue #1265: with fewer than sixteen octets left after the last record,
This guards against GitHub issue #1265, where, with fewer than sixteen octets
left after the last record,
:meth:`Frame.unpack <pcapkit.protocols.misc.pcap.frame.Frame.unpack>` parsed a
record header out of zero padding, and the payload read got a negative length
(``ValueError: read length must be non-negative or -1``). A tail that short now
(``ValueError: read length must be non-negative or -1``). A tail that short
raises :exc:`~pcapkit.utilities.exceptions.StreamEOFError`, which the extractor
treats as the end of the file, as it does for a truncated PCAP-NG block (#678).

Expand Down
20 changes: 9 additions & 11 deletions tests/protocols/misc/pcap/test_header_frame_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,8 @@ class Other(Raw):

# ``register`` resolves a ``ModuleDescriptor`` before storing it,
# so the entry above is already the plain ``Raw`` class, not the
# descriptor -- passing ``Raw`` again would now be the same-object
# no-op GitHub issue #718 added an identity guard for (see
# descriptor -- passing ``Raw`` again would be the same-object
# no-op covered by the identity guard for GitHub issue #718 (see
# ``test_frame_register_stays_quiet_on_same_object_reregistration``).
# A genuinely different class is needed to still warn here.
with mock.patch('pcapkit.protocols.misc.pcap.frame.warn') as warn:
Expand Down Expand Up @@ -297,7 +297,7 @@ class Other(Raw):
bad_time._file = io.BytesIO(b'')
# Faked through frame.py's own binding of
# :func:`pcapkit.utilities.datetime.fromtimestamp`, which it calls
# instead of :meth:`datetime.datetime.fromtimestamp` since #2099.
# instead of :meth:`datetime.datetime.fromtimestamp` (#2099).
with mock.patch('pcapkit.protocols.misc.pcap.frame.fromtimestamp', side_effect=ValueError), \
mock.patch.object(Frame, '_decode_next_layer',
lambda self, info, proto=None, length=None, packet=None: info), \
Expand Down Expand Up @@ -326,9 +326,9 @@ class Other(Raw):
def test_frame_register_stays_quiet_on_same_object_reregistration(self) -> None:
"""GitHub issue #718: replaying a call with the identical class is a no-op.

Before the fix the guard fired on presence alone, so the *second* of
Without the fix the guard fires on presence alone, so the *second* of
two calls registering the exact same class under the exact same code
warned about an overwrite that never happened. Matches the identity
warns about an overwrite that never happens. Matches the identity
guard :func:`register_protocol
<pcapkit.foundation.registry.protocols.register_protocol>` already
applies.
Expand Down Expand Up @@ -441,13 +441,11 @@ def test_frame_header_is_read_in_the_files_byte_order(self) -> None:

:meth:`Frame.unpack <pcapkit.protocols.misc.pcap.frame.Frame.unpack>`
seeded the file's byte order under the key ``bytesorder``, where
``byteorder_callback`` -- then in
:file:`pcapkit/protocols/schema/misc/pcap/frame.py`, now in
:file:`pcapkit/corekit/fields/byteorder.py` -- reads ``byteorder``,
so the lookup always missed and always fell back to
``byteorder_callback`` (:file:`pcapkit/corekit/fields/byteorder.py`)
reads ``byteorder``, so the lookup always missed and always fell back to
:data:`sys.byteorder`. On a little-endian host reading a little-endian
capture that is the right answer by coincidence, which is why every
fixture here passed; on a big-endian capture all four record-header
capture that is the right answer by coincidence, so little-endian
fixtures cannot catch it; on a big-endian capture all four record-header
fields came back byte-swapped.

The capture is built here rather than read from
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
"""GitHub issue #1405: a captured length running past the block rebuilds byte-exactly.

An Enhanced Packet Block whose ``captured_len`` runs past the block read that
many octets as packet data: the trailing Block Total Length and whatever
followed went with them, so a 36-octet block rebuilt as 40 or 41. The packet
data is now bounded by the block, the padding and option area are sized from
Without the fix, an Enhanced Packet Block whose ``captured_len`` runs past the
block reads that many octets as packet data: the trailing Block Total Length and
whatever follows go with them, so a 36-octet block rebuilds as 40 or 41. The
packet data is bounded by the block, the padding and option area are sized from
the octets read, and the declared ``captured_len`` is kept, so the block rebuilds
as captured. The obsolete Packet Block has the same shape and the same fix.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# -*- coding: utf-8 -*-
"""GitHub issue #1980: a Decryption Secrets Block's secrets are bounded by the block.

A Decryption Secrets Block whose ``secrets_length`` ran far past the block read
that many octets as secrets: the trailing Block Total Length and the blocks after
it went with them, and past the zero-pad ceiling the read raised
:exc:`~pcapkit.utilities.exceptions.FieldValueError` from inside the block's
schema, which ended the whole extraction. The secrets are bounded by the
Without the fix, a Decryption Secrets Block whose ``secrets_length`` runs far
past the block reads that many octets as secrets: the trailing Block Total Length
and the blocks after it go with them, and past the zero-pad ceiling the read
raises :exc:`~pcapkit.utilities.exceptions.FieldValueError` from inside the
block's schema, which ends the whole extraction. The secrets are bounded by the
block, as an Enhanced Packet Block's packet data is (#1405): the declared
``secrets_length`` is kept, a :class:`~pcapkit.utilities.warnings.ProtocolWarning`
reports the overrun, and extraction carries on to the next block.
Expand Down
8 changes: 4 additions & 4 deletions tests/protocols/misc/test_pcapng_flags_undefined_1382_unit.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
"""GitHub issue #1382: undefined ``epb_flags`` sub-field values round-trip.

A direction of ``0b11`` or a reception type of ``0b101`` to ``0b111`` raised
:exc:`~pcapkit.utilities.exceptions.ProtocolError`, so a capture carrying one
could be neither parsed nor rebuilt. Both now resolve to an ``Unassigned``
Without the fix, a direction of ``0b11`` or a reception type of ``0b101`` to
``0b111`` raises :exc:`~pcapkit.utilities.exceptions.ProtocolError`, so a capture
carrying one can be neither parsed nor rebuilt. Both resolve to an ``Unassigned``
member that keeps the wire value. Bits 9 to 11, which pcapng-06 Table 5 defines
(checksum not ready, checksum valid, TCP segmentation offloaded), are named
instead of sitting in ``reserved``.
Expand All @@ -26,7 +26,7 @@ def _pb_with_flags(word: 'bytes') -> 'bytes':


class TestPCAPNGFlagsUndefined(PCAPNGTestCase):
"""Pin how undefined and newly named flags bits parse and rebuild."""
"""Pin how undefined flags bits, and the named bits 9 to 11, parse and rebuild."""

def _flags(self, word: 'str') -> 'tuple[bytes, object]':
from pcapkit.const.pcapng.option_type import OptionType
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# -*- coding: utf-8 -*-
"""GitHub issues #1413 and #1414: malformed PCAP-NG blocks rebuild byte-exactly.

* #1413 -- a Journal Export binary field filling its entry with no closing
newline gained one on rebuild (28 octets in, 32 out), and a bare ``KEY`` with
no ``=`` and no newline was dropped (16 in, 12 out). Under the #1406 ruling the
whole entry is now kept as captured, as ``entry_raw``.
* #1414 -- a Simple Packet Block holding more data than its ``original_len``
lost the excess (24 in, 20 out), and an Enhanced Packet Block or Packet Block
below its 32-octet minimum rebuilt at 32. Both now keep the octets captured.
* #1413 -- guards against a Journal Export binary field filling its entry with
no closing newline gaining one on rebuild (28 octets in, 32 out), and a bare
``KEY`` with no ``=`` and no newline being dropped (16 in, 12 out). Under the
#1406 ruling the whole entry is kept as captured, as ``entry_raw``.
* #1414 -- guards against a Simple Packet Block holding more data than its
``original_len`` losing the excess (24 in, 20 out), and an Enhanced Packet
Block or Packet Block below its 32-octet minimum rebuilding at 32. Both keep
the octets captured.

:mod:`pcapkit` is imported inside each test, after
:func:`~tests._support.reimport_once_per_class`.
Expand Down
Loading
Loading