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
3 changes: 2 additions & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ tbtrim = "~=0.3.1"
pypcapkit = {editable = true,path = "."}
pyshark = "*"
dpkt = "*"
scapy = "*"
# The floor and marker match the Scapy extra in pyproject.toml (#1572).
scapy = {version = ">=2.6.0",markers = "python_version >= '3.7'"}
cryptography = "*"
# NB: ``pypcap`` is deliberately absent. It builds a C extension against
# libpcap, so listing it here would break ``pipenv install --dev`` -- and even
Expand Down
10 changes: 7 additions & 3 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -436,9 +436,13 @@ preflight described under `Engine Comparison`_.
.. note::

Only the four engines above carry extra constraints. :mod:`pcapkit` itself and
its ``default``, ``dpkt`` and ``scapy`` engines need nothing further --
``dpkt`` 1.9.8 and ``scapy`` 2.7.0 were both measured reading a capture on
Python 3.14.
its ``default``, ``dpkt`` and ``scapy`` engines need nothing beyond their
``pip install`` -- ``dpkt`` 1.9.8 and ``scapy`` 2.7.0 were both measured
reading a capture on Python 3.14. The ``scapy`` engine needs **scapy 2.6.0 or
newer**, which the ``Scapy`` extra requires: 2.5.0 can drop a PCAP-NG
interface's timestamp resolution, reading nanoseconds as microseconds
(:issue:`1572`). :mod:`pcapkit` does not check the version at run time, so
upgrade a scapy installed some other way.

For CLI usage, you will need to install the optional packages:

Expand Down
10 changes: 8 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,12 @@ crypto = [ "cryptography>=3.4" ]
NGAP = [ "pycrate" ]
# for normal users
DPKT = [ "dpkt" ]
Scapy = [ "scapy" ]
# The floor is measured, #1572: on scapy 2.5.0 a PCAP-NG interface's
# ``if_tsresol`` is lost after a comment option without a newline, so a
# nanosecond interface reads as microseconds, and ``packet2dict`` reports DNS
# records and field order differently. 2.6.0, the next release, fixes all three.
# It needs Python 3.7, hence the marker, as ``PCAP_CT``'s below.
Scapy = [ "scapy>=2.6.0; python_version >= '3.7'" ]
PyShark = [ "pyshark" ]
# pypcap ships no wheels, so this compiles a C extension against libpcap and
# needs both its headers and its shared library present; deliberately kept out
Expand Down Expand Up @@ -255,7 +260,8 @@ all = [ "emoji", "cryptography>=3.4", "pycrate" ]
# and it is not a place to add ``pypcap``/``pcap-ct`` just to silence a
# finding CI has already decided to carry as advisory.
dev = [
"dpkt", "scapy", "pyshark",
# scapy: the same floor and marker as the Scapy extra above, #1572
"dpkt", "scapy>=2.6.0; python_version >= '3.7'", "pyshark",
# same marker and reasoning as the PyPCAPFile extra above and the old
# ``all``: on 3.12+ the package installs cleanly and is then unusable, so
# the marker keeps the resolver from fetching something that could never
Expand Down
14 changes: 14 additions & 0 deletions tests/test_tier_guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,20 @@ def test_dev_carries_what_all_used_to_for_the_toolchain(self) -> None:
with self.subTest(absent=absent):
self.assertFalse(_dependency_gates.provided_by(declared['dev'], absent))

def test_scapy_is_floored_at_the_first_release_main_passes_on(self) -> None:
"""#1572: ``Scapy`` and ``dev`` both require ``scapy>=2.6.0``.

On 2.5.0 ``main`` reads a PCAP-NG nanosecond interface as microseconds and
``packet2dict`` reports DNS records and field order differently; 2.6.0, the
next release, fixes all three. The marker is ``PCAP_CT``'s courtesy: 2.6.0
needs Python 3.7, above this project's ``requires-python`` floor.

"""
declared = _dependency_gates.declared_requirements()
floor = "scapy>=2.6.0; python_version >= '3.7'"
self.assertEqual([requirement.text for requirement in declared['Scapy']], [floor])
self.assertIn(floor, [requirement.text for requirement in declared['dev']])

def test_the_core_dependencies_are_read_from_project_not_build_system(self) -> None:
"""``[build-system] requires`` also holds ``setuptools``; this is not it."""
core = _dependency_gates.declared_requirements()[_dependency_gates.CORE]
Expand Down
Loading