Skip to content

fix(toolkit): dpkt and pypcapfile hand over the correctly rounded timestamp (#1668) - #1690

Merged
JarryShaw merged 1 commit into
mainfrom
fix/1668-timestamp-double-rounding
Oct 14, 2026
Merged

JarryShaw merged 1 commit into
mainfrom
fix/1668-timestamp-double-rounding

Conversation

@JarryShaw

Copy link
Copy Markdown
Owner

Please follow the guide below

  • You will be asked some questions, please read them carefully and answer honestly

  • Put an x into all the boxes [ ] relevant to your pull request (like that [x])

  • Use Preview tab to see how your pull request will actually look like

  • Searched for similar pull requests

  • Followed the coding style (make pylint, make mypy, make isort) — run on the changed files; no new findings

  • make test passes, and a test case covers the change — new module added; related modules run, not the whole suite

  • Added a changelog entry under docs/source/changelog/ and regenerated CHANGELOG.md, if the change is user-visible — N/A — centralised in docs(changelog): shared 1.5.0 changelog — long-lived, merges last (#610, #616, #617, #618, #620) #657

What is the purpose of your pull request?

Tick the commit type your subject line carries.

  • fix — corrects a defect
  • feat — adds a feature
  • perf — changes performance, not behaviour
  • refactor — changes neither behaviour nor performance
  • test — tests only
  • docs — documentation only
  • ci — workflows or build tooling
  • release — bumps the version or rolls up a distribution
  • chore — anything else

Description of your pull request and other information

dpkt on a microsecond PCAP, and pypcapfile on any PCAP, handed reassembly and flow tracing ts_sec + frac / divisor. That rounds twice, so at ts_sec=1 they gave 1.0036909999999999 where the default engine gives 1.003691. The flow label and trace file name were off by the same amount.

  • dpkt: the four adapters now go through _capture_time, which returns float(timestamp.exact) for a RecordTimestamp and float(timestamp) for anything else, so a plain float no longer risks an AttributeError. A stored frame keeps dpkt's own value, as the Timestamp docstring says. The reader now computes a µs PCAP's exact value under localcontext(prec=64), as the default engine does: in the caller's context, a precision of 12 gave 1500000000.12 for 1500000000.123456.
  • pypcapfile: packet2timestamp computes (sec * divisor + frac) / divisor, which rounds once.
  • Tests: tests/toolkit/test_dpkt_pypcapfile_timestamp_rounding_1668_unit.py builds its captures with struct at ts_sec=1: frac 3691 µs and 3691000 ns, plus 415-frac seeded sweeps with 62 µs and 66 ns double-rounding cases, and a µs PCAP at ts_sec=1 and 1500000000 read at decimal precision 10 and 12. They assert that the IPv4/IPv6/TCP/trace inputs and trace file names equal the default engine's, for dpkt on PCAP and PCAP-NG and for pypcapfile on PCAP. A stand-in check of packet2timestamp runs without pypcapfile. Every test method that runs fails on origin/main (23 subtest failures on 3.11.15; 17 on 3.14.8, where the pypcapfile engine class skips because pcapfile cannot be imported) and passes with the fix.
  • After the fix, on those captures, default, dpkt, scapy, pypcapfile (PCAP) and pyshark (trace) all hand over 1.003691 and write …_9-1.003691.json, sweeps included.
  • Left as is: dpkt's _upstream reader fallback (it warns DPKTWarning) still yields dpkt's own float on a µs PCAP, the same as on main. dpkt on a ns PCAP, and scapy on PCAP and PCAP-NG, already lose digits at a low decimal precision on main; that is fix(engines): dpkt on a nanosecond PCAP and scapy truncate the timestamp under a low decimal precision #1689.

Closes #1668

…estamp (#1668)

dpkt on a microsecond PCAP, and pypcapfile on any PCAP, handed reassembly
and flow tracing ts_sec + frac / divisor, which rounds twice: at ts_sec=1
it gave 1.0036909999999999 for 1.003691, so the inputs, the flow label and
the trace file name differed from the default engine's.

- dpkt: the four adapters read a RecordTimestamp's exact value; any other
  timestamp is handed on as before. The reader, and so a stored frame's
  value, is unchanged, but it now computes a microsecond PCAP's exact
  value at 64 digits, as the default engine does, not in the caller's
  decimal context.
- pypcapfile: packet2timestamp joins the halves in one int division.
- tests: captures built at ts_sec=1 (3691 us, 3691000 ns) and 415-frac
  sweeps, and a microsecond PCAP at ts_sec=1 and 1500000000 read at a
  decimal precision of 10 and 12; both engines' inputs and trace names
  equal the default's.
@JarryShaw JarryShaw added fix Pull requests that fix a defect (fix: subject prefix) review: good-to-go Cross-review at the current head says ready; CI state is separate labels Oct 14, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

Verdict: GOOD TO GO at a28a89742. A Sonnet cross-review re-derived the fix on its own captures. dpkt and pypcapfile now match the default engine at decimal precision 28, 12 and 10. Stored dpkt frames and the packet2dict/packet2frame output are unchanged from main, and no other engine moved. The new module fails on main (17 subtests on 3.14, 23 on 3.11) and passes on 3.14, 3.11 and 3.10. Round 1 drew NEEDS CHANGES: the µs exact was computed in the caller's decimal context. That is fixed at engines/dpkt.py:296. The pre-existing low-precision loss in dpkt (ns) and scapy is #1689.

@github-actions

Copy link
Copy Markdown
Contributor

Coverage: 89.47% (unit tier, Python 3.14, a28a89742, Unit Tests run success)

Package Statements Missed Branches Partial Cover
pcapkit (top level) 104 3 20 3 95.16%
pcapkit/const 18858 954 2366 868 91.06%
pcapkit/corekit 2366 67 760 35 96.48%
pcapkit/dumpkit 275 3 100 3 98.40%
pcapkit/foundation 3034 151 1076 34 94.28%
pcapkit/interface 138 7 52 5 93.68%
pcapkit/protocols 17166 226 4580 190 98.07%
pcapkit/toolkit 1420 278 586 4 77.97%
pcapkit/utilities 494 4 146 4 98.75%
pcapkit/vendor 4417 2342 1010 157 43.38%

Per-file detail: the coverage-html artifact of this run.

@JarryShaw
JarryShaw merged commit 8035434 into main Oct 14, 2026
50 checks passed
@github-project-automation github-project-automation Bot moved this from In review to Done in PyPCAPKit Oct 14, 2026
@JarryShaw
JarryShaw deleted the fix/1668-timestamp-double-rounding branch October 14, 2026 01:20
@JarryShaw JarryShaw removed the review: good-to-go Cross-review at the current head says ready; CI state is separate label Oct 14, 2026
JarryShaw added a commit that referenced this pull request Oct 14, 2026
…1721)

Third-party engines are best effort (#1687), so the pypcapfile code that
exists only to match the default engine comes out:

- revert #1682, #1600, #1575 and #1535, and the pypcapfile hunks of #1655,
  #1608 and #1605: reading loopback and raw-IP link types, the frame layer
  bound, the VLAN and tunnel walks, keying tunnelled TCP by the innermost
  header, stepping over AH and extension headers, and checking options
  with the default engine's parsers
- keep the helpers the dpkt and scapy adapters import, unchanged, until
  their own reverts drop them; their docstrings no longer say pypcapfile
  asks the default engine's parsers too
- re-land #1576, #1577, #1590 and #1591 as guards in _network and
  _transport, #1513's once-per-capture warning for TCP straight over
  IPv6, and #1667's packet2dict fix: an undecoded frame is reported as
  its octets, not as hex text twice as long
- keep #1569's record reader and #1690's packet2timestamp
- delete the three test modules those reverts added, the pypcapfile
  timestamp agreement class, and #1682's link-type warning test; the
  re-landed guards' tests assert constants
- drop what the reverts removed from the docs, READ_LINKTYPES included,
  and say that TCP behind a VLAN tag, in a tunnel or behind an
  extension header (an IPv6 one, or AH in IPv4) is now left out
  without a warning

The pypcapfile modules, test_new_engine_parity_runtime.py,
test_engine_runtime.py, the timestamp module and the project guards pass
on 3.11 (pypcapfile 0.12.0) and 3.14.
JarryShaw added a commit that referenced this pull request Oct 14, 2026
Third-party engines are best effort (#1687), so the dpkt code that exists
only to match the default engine comes out:

- revert #1662, #1655, #1624, #1622, #1614 and the dpkt hunks of #1605:
  the octet walk and its link-type stash, IPv6 in IPv4 and IPv4 in IPv6,
  refusing MPLS and ISL, the tunnel and extension-header walks, the IPv4
  header checks and the layer bounds
- keep #1690's timestamps, #1594's parser choice, #1621 and #1653
- re-land as guards: a layer dpkt's own lengths misplace (#1646) is
  re-serialised, not refused with ProtocolError; a Total Length below the
  IHL keeps the header whole (#1616), and a header the capture cuts is no
  input; packet2dict and packet2chain take a str payload and a raw frame
- delete the modules #1662 and #1655 added; the 1615/1616 module keeps
  only what its #1616 tests use, as no scapy test imports it now; dpkt's
  comparisons with the default engine assert constants or the capture's
  octets, or are dropped
- drop pypcapfile's VLAN_TAGS, VLAN_TAG_LEN and IPV6_EXTENSION_HEADERS,
  which only dpkt imported, and the 1668 module's _Agreement base, which
  nothing subclasses now
- the docs drop the depth claims and name what dpkt leaves out silently

The dpkt modules, the shared modules that run dpkt, tests/foundation/engines
and the project guards pass on 3.14 and 3.11 (dpkt 1.9.8, pypcapfile 0.12.0).
JarryShaw added a commit that referenced this pull request Oct 14, 2026
Third-party engines are best effort (#1687), so the dpkt code that exists
only to match the default engine comes out:

- revert #1662, #1655, #1624, #1622, #1614 and the dpkt hunks of #1605:
  the octet walk and its link-type stash, IPv6 in IPv4 and IPv4 in IPv6,
  refusing MPLS and ISL, the tunnel and extension-header walks, the IPv4
  header checks and the layer bounds
- keep #1690's timestamps, #1594's parser choice, #1621 and #1653
- re-land as guards: a layer dpkt's own lengths misplace (#1646) is
  re-serialised, not refused with ProtocolError; a Total Length below the
  IHL keeps the header whole (#1616), and a header the capture cuts is no
  input; packet2dict and packet2chain take a str payload and a raw frame
- delete the modules #1662 and #1655 added; the 1615/1616 module keeps
  only what its #1616 tests use, as no scapy test imports it now; dpkt's
  comparisons with the default engine assert constants or the capture's
  octets, or are dropped
- drop pypcapfile's VLAN_TAGS, VLAN_TAG_LEN and IPV6_EXTENSION_HEADERS,
  which only dpkt imported, and the 1668 module's _Agreement base, which
  nothing subclasses now
- the docs drop the depth claims and name what dpkt leaves out silently

The dpkt modules, the shared modules that run dpkt, tests/foundation/engines
and the project guards pass on 3.14 and 3.11 (dpkt 1.9.8, pypcapfile 0.12.0).
@JarryShaw JarryShaw added this to the PyPCAPKit v1.5 milestone Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix Pull requests that fix a defect (fix: subject prefix)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

fix(toolkit): dpkt and pypcapfile round a PCAP timestamp twice, so a small ts_sec hands over a float 1 ulp off the default engine's

1 participant