netprotocols 2.2.0__tar.gz → 2.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {netprotocols-2.2.0 → netprotocols-2.2.1}/.github/workflows/release.yml +33 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/CHANGELOG.md +146 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/PKG-INFO +31 -17
- {netprotocols-2.2.0 → netprotocols-2.2.1}/README.md +30 -16
- {netprotocols-2.2.0 → netprotocols-2.2.1}/benchmarks/baseline.json +4 -4
- {netprotocols-2.2.0 → netprotocols-2.2.1}/docs/CLAIMS.md +210 -47
- {netprotocols-2.2.0 → netprotocols-2.2.1}/pyproject.toml +5 -5
- netprotocols-2.2.1/scripts/_pcap.py +42 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/benchmark.py +8 -51
- netprotocols-2.2.1/scripts/check_fixtures.py +332 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/pyodide/check_in_pyodide.py +7 -33
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/__init__.py +1 -1
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/_base.py +22 -4
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/packet.py +24 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/walk.py +1 -1
- {netprotocols-2.2.0 → netprotocols-2.2.1}/uv.lock +1 -1
- netprotocols-2.2.0/scripts/check_fixtures.py +0 -273
- {netprotocols-2.2.0 → netprotocols-2.2.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/.github/ISSUE_TEMPLATE/protocol_request.yml +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/.github/pull_request_template.md +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/.github/workflows/ci.yml +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/.github/workflows/fuzz.yml +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/.gitignore +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/ARCHITECTURE.md +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/CONTRIBUTING.md +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/LICENSE +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/benchmark_encode.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/benchmark_import.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/capture_fixtures.sh +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/capture_fixtures_dhcp.sh +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/capture_fixtures_dns_tcp.sh +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/capture_fixtures_gre.sh +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/capture_fixtures_igmp.sh +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/capture_fixtures_supplement.sh +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/capture_fixtures_vlan.sh +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/pyodide/package-lock.json +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/pyodide/package.json +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/pyodide/run_in_pyodide.mjs +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/_defaults.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/_enums.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/checksum.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/flow.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer2/__init__.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer2/arp.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer2/ethernet.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer2/vlan.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer3/__init__.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer3/gre.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer3/icmp.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer3/igmp.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer3/ip.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer3/ipv6_ext.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer4/__init__.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer4/_ports.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer4/tcp.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer4/udp.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer7/__init__.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer7/dhcp.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer7/dns.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/pcap.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/py.typed +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/registry.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/utils/__init__.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/utils/exceptions.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/utils/ipv4.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/utils/mac.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/conftest.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/MANIFEST.md +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/arp_exchange.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/dhcp.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/dns_tcp.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/gre.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/icmpv4_echo_lo.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/icmpv4_external.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/icmpv4_ttl_exceeded.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/icmpv6_echo_lo.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/igmp.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/ipv4_fragments.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/ipv6_fragments.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/ipv6_mld.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/ipv6_ndp_mld.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/arp_exchange.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/dhcp.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/dns_tcp.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/gre.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/icmpv4_echo_lo.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/icmpv4_external.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/icmpv4_ttl_exceeded.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/icmpv6_echo_lo.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/igmp.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/ipv4_fragments.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/ipv6_fragments.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/ipv6_mld.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/ipv6_ndp_mld.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/tcp_http.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/tcp_https.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/udp_dns.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/vlan_icmp.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/tcp_http.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/tcp_https.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/udp_dns.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/vlan_icmp.pcap +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/strategies.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_arp.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_checksum.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_contract.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_corpus.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_dhcp.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_diagnostics.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_dns.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_docs.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_ethernet.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_exports.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_flow.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_fuzz.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_gre.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_icmp.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_igmp.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_ip.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_ipv6_ext.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_packet.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_pattern_matching.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_pcap.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_registry.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_tcp.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_udp.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_utils.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_version.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_vlan.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_walk.py +0 -0
- {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_workflows.py +0 -0
|
@@ -59,3 +59,36 @@ jobs:
|
|
|
59
59
|
fi
|
|
60
60
|
echo "Tag ${GITHUB_REF_NAME} matches packaged version ${built}."
|
|
61
61
|
- run: uv publish --trusted-publishing always
|
|
62
|
+
|
|
63
|
+
github-release:
|
|
64
|
+
needs: publish
|
|
65
|
+
runs-on: ubuntu-latest
|
|
66
|
+
permissions:
|
|
67
|
+
contents: write
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/checkout@v5
|
|
70
|
+
# CHANGELOG.md is the single source of truth for release notes, so
|
|
71
|
+
# extract this tag's own section rather than maintaining a second
|
|
72
|
+
# copy anywhere. A missing section fails loudly instead of
|
|
73
|
+
# publishing an empty-body release.
|
|
74
|
+
- name: Extract this version's CHANGELOG section
|
|
75
|
+
run: |
|
|
76
|
+
set -euo pipefail
|
|
77
|
+
version="${GITHUB_REF_NAME#v}"
|
|
78
|
+
awk -v ver="$version" '
|
|
79
|
+
found && /^## \[/ { exit }
|
|
80
|
+
found { print }
|
|
81
|
+
$0 ~ "^## \\[" ver "\\]" { found=1 }
|
|
82
|
+
' CHANGELOG.md > release-notes.md
|
|
83
|
+
if [ ! -s release-notes.md ]; then
|
|
84
|
+
echo "::error::No '## [${version}]' section found in CHANGELOG.md."
|
|
85
|
+
exit 1
|
|
86
|
+
fi
|
|
87
|
+
- name: Create GitHub release
|
|
88
|
+
env:
|
|
89
|
+
GITHUB_TOKEN: ${{ github.token }}
|
|
90
|
+
run: |
|
|
91
|
+
gh release create "${GITHUB_REF_NAME}" \
|
|
92
|
+
--title "netprotocols ${GITHUB_REF_NAME#v}" \
|
|
93
|
+
--notes-file release-notes.md \
|
|
94
|
+
--latest
|
|
@@ -5,6 +5,152 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.2.1] - 2026-09-07
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **Closed a decode-throughput regression against dpkt that nobody had
|
|
12
|
+
been watching tier over tier (#147).** Re-measuring every held claim
|
|
13
|
+
after #107/#124 closed showed netprotocols had gone from 1.16× faster
|
|
14
|
+
than dpkt right after Tier 1 to ~11% slower after Tiers 2-4 — a
|
|
15
|
+
reversal none of the five tiers' own re-measurements had caught,
|
|
16
|
+
because none of them profiled, only re-measured before/after.
|
|
17
|
+
`cProfile` against the corpus decode loop found two causes, neither
|
|
18
|
+
of them the tiers first suspected: `bytes_to_ipv6` (added by #99 for
|
|
19
|
+
Pyodide portability, not one of the three tiers originally named)
|
|
20
|
+
formatted its eight address words one f-string at a time through a
|
|
21
|
+
generator, ~18% of total corpus decode time; `decode_frame()`'s
|
|
22
|
+
`Packet` construction paid a redundant `isinstance` check, through
|
|
23
|
+
`Protocol`'s ABC machinery, on a list already guaranteed to hold only
|
|
24
|
+
`Protocol` instances, ~7-8% of `decode_frame()`'s own time. Both
|
|
25
|
+
fixed without changing observable behavior: `bytes_to_ipv6` now
|
|
26
|
+
formats all eight words in one `%`-format call (verified
|
|
27
|
+
byte-identical against glibc's `inet_ntop` via the existing
|
|
28
|
+
hypothesis test, 2.1× faster in isolation); `Packet` gained an
|
|
29
|
+
internal `_from_decoded()` fast-construction path, used only by
|
|
30
|
+
`decode_frame()` — the public `Packet(...)` constructor still
|
|
31
|
+
validates arbitrary arguments exactly as before.
|
|
32
|
+
- **`scripts/benchmark.py` now measures `decode_frame()`, the
|
|
33
|
+
documented public chain-walking API, instead of a hand-rolled loop
|
|
34
|
+
that predated it (#147).** The benchmark's `decode_netprotocols()`
|
|
35
|
+
kept its own copy of the pre-#88 walk loop, never updated when
|
|
36
|
+
`decode_frame()` shipped, so every decode-throughput figure this
|
|
37
|
+
project has published — including every number in `docs/CLAIMS.md`
|
|
38
|
+
section 1 before this release — described code the documented API's
|
|
39
|
+
callers never actually ran. `benchmarks/baseline.json`, five tiers
|
|
40
|
+
and this fix stale at a v1.3.0-era figure, was refreshed to match.
|
|
41
|
+
|
|
42
|
+
See `docs/CLAIMS.md`'s "Re-measured after closing the
|
|
43
|
+
dpkt-throughput regression" section for the full profiling writeup,
|
|
44
|
+
before/after numbers under both the old and new benchmark
|
|
45
|
+
methodology, and updated 1.1/1.2/1.6 figures.
|
|
46
|
+
|
|
47
|
+
### Development
|
|
48
|
+
- **Addressed CodeFactor's static-analysis findings on the tooling
|
|
49
|
+
scripts (#156).** `scripts/pyodide/check_in_pyodide.py` extracted
|
|
50
|
+
the built wheel into a hardcoded, predictable `/tmp/netprotocols-
|
|
51
|
+
wheel` path; it now uses `tempfile.mkdtemp()`, verified against a
|
|
52
|
+
real Pyodide runtime (`node scripts/pyodide/run_in_pyodide.mjs`)
|
|
53
|
+
decoding the full corpus unchanged. `scripts/check_fixtures.py`'s
|
|
54
|
+
`_check_l3` — one 137-line function walking IPv4, IPv6, GRE
|
|
55
|
+
tunneling, VLAN and every upper-layer checksum — is split into one
|
|
56
|
+
small function per protocol; output is byte-identical on the full
|
|
57
|
+
97-frame fixture corpus before and after. `scripts/benchmark.py`'s
|
|
58
|
+
three broad `except Exception: continue` blocks are unchanged but
|
|
59
|
+
now documented: `dpkt` and `scapy` have no shared "malformed frame"
|
|
60
|
+
exception the way this project's own `ProtocolError` does, so a
|
|
61
|
+
bare `except` is the correct interop boundary, not a swallowed bug.
|
|
62
|
+
The flagged duplication between `benchmark.py` and
|
|
63
|
+
`check_in_pyodide.py`'s minimal pcap readers — nearly byte-identical
|
|
64
|
+
`read_pcap`/`corpus_frames` functions — is now shared via a new
|
|
65
|
+
`scripts/_pcap.py`. An initial pass left the duplication in place,
|
|
66
|
+
reasoning both scripts' docstrings required each verification path
|
|
67
|
+
to stand fully independent of the others; re-reading them shows that
|
|
68
|
+
claim is narrower than stated — "standalone" there means not
|
|
69
|
+
depending on netprotocols (the thing each script verifies), the same
|
|
70
|
+
usage `CONTRIBUTING.md` and `check_fixtures.py`'s own docstring use,
|
|
71
|
+
never independence from sibling scripts. `_pcap.py` is itself
|
|
72
|
+
stdlib-only and does not import netprotocols, so it preserves the
|
|
73
|
+
property the docstrings actually document. Verified against a real
|
|
74
|
+
Pyodide runtime after the extraction: the corpus still decodes
|
|
75
|
+
unchanged, and `check_fixtures.py`'s own, materially different
|
|
76
|
+
`read_pcap` (it additionally validates linktype and length) is
|
|
77
|
+
untouched, since it was never part of this duplication.
|
|
78
|
+
|
|
79
|
+
### Documentation
|
|
80
|
+
- **Corrected six README.md claims an independent audit against the
|
|
81
|
+
live repo found stale or overstated, none of them accidental
|
|
82
|
+
regressions — each had simply drifted from what it once described.**
|
|
83
|
+
Wheel size (85.6 KB / ~30× smaller than scapy) was measured against
|
|
84
|
+
the `2.0.0` wheel, two releases behind; rebuilt from `HEAD`, it's
|
|
85
|
+
88.3 KB / ~29× (`docs/CLAIMS.md` §1.5 corrected to match). The typing
|
|
86
|
+
bullet's "107 of [scapy's] files" enable strict mypy checking was a
|
|
87
|
+
raw `wc -l` over scapy's enabled-files list including blank lines
|
|
88
|
+
and comments; filtered the way scapy's own tooling does, it's 89
|
|
89
|
+
(`docs/CLAIMS.md` §2.1 corrected, with the mismeasurement documented
|
|
90
|
+
so it doesn't recur). The Pyodide/browser bullet credited "dpkt and
|
|
91
|
+
pypacker import cleanly" to the real-Pyodide CI job; that job only
|
|
92
|
+
ever imports netprotocols under Pyodide (confirmed by reading
|
|
93
|
+
`scripts/pyodide/check_in_pyodide.py`) — the dpkt/pypacker rows come
|
|
94
|
+
from a separate `sys.meta_path`-blocklist simulation under ordinary
|
|
95
|
+
CPython, not from CI (`docs/CLAIMS.md` §3.1 reworded to say so
|
|
96
|
+
explicitly). The CI-gating bullet's closing clause, "the rest have no
|
|
97
|
+
performance benchmark at all," was contradicted by the very citation
|
|
98
|
+
it points to — `docs/CLAIMS.md` §1.7 documents that scapy and
|
|
99
|
+
PyTCP-net_proto do ship benchmark scripts, just not CI-wired; reworded
|
|
100
|
+
to the claim the table actually supports (none of the ten *gates* on
|
|
101
|
+
a regression). The Contributing section's "`pytest`, `mypy`, and
|
|
102
|
+
`ruff check` — all three are enforced by CI on Python 3.12-3.14"
|
|
103
|
+
overstated the matrix: only `pytest` runs on 3.12-3.14; `mypy` and
|
|
104
|
+
`ruff check` each run on 3.12 only. The Roadmap section's "everything
|
|
105
|
+
through 2.2.0 has landed on `master`" didn't mention that a further,
|
|
106
|
+
unreleased fix (#147) has since landed on top of 2.2.0 — added a
|
|
107
|
+
clause naming it. A seventh finding — the Roadmap's claim that "only
|
|
108
|
+
2.0.0 was an actual PyPI release" — is contradicted by PyPI itself
|
|
109
|
+
(2.2.0, and in fact five earlier minor/patch versions, are also
|
|
110
|
+
published there); left unfixed pending a maintainer decision on
|
|
111
|
+
whether 2.2.0's publication was intentional, since fixing the prose
|
|
112
|
+
first would paper over a real process question.
|
|
113
|
+
- **Resolved the seventh finding above: 2.2.0's PyPI publication was
|
|
114
|
+
confirmed intentional.** The Roadmap section's "per the roadmap's own
|
|
115
|
+
release policy, only 2.0.0 was an actual PyPI release" is now
|
|
116
|
+
"the roadmap's original 'only at a major version bump' release policy
|
|
117
|
+
wasn't actually held to: both 2.0.0 and 2.2.0 — a minor release — are
|
|
118
|
+
on PyPI," and the roadmap table's 2.2.0 row gained the same
|
|
119
|
+
**released** marker 2.0.0 already carried. Issue #107, the source
|
|
120
|
+
both docs draw this from, updated its own stated policy line with a
|
|
121
|
+
dated correction rather than silently rewriting history.
|
|
122
|
+
- **A fresh independent audit for #101 found four more genuine drifts
|
|
123
|
+
and fixed them, none of them regressions — just the register falling
|
|
124
|
+
behind a codebase and a README that both kept moving.** `docs/CLAIMS.md`
|
|
125
|
+
§5.3's statement count (1,935) had drifted from the live count of
|
|
126
|
+
2,091 as the codebase grew — the coverage percentage held steady only
|
|
127
|
+
because the miss count never moved, masking it; `pyproject.toml`'s own
|
|
128
|
+
inline comment was still quoting an even older 1,441 and is now
|
|
129
|
+
corrected too. §5.7's "79 tests" for `tests/test_walk.py` undercounted
|
|
130
|
+
the live 85: one test parametrized over the corpus's first 40 frames
|
|
131
|
+
now collects its full 40 instances because the corpus has since grown
|
|
132
|
+
past that slice, not because tests were added. §1.6 overstated its own
|
|
133
|
+
claim about README.md, saying the decode-depth figure travels with the
|
|
134
|
+
throughput figure "in the same sentence" there; they're adjacent
|
|
135
|
+
bullets, not one sentence — corrected. README's Pyodide/browser bullet
|
|
136
|
+
said outright that "pypacker was not tested", contradicted by
|
|
137
|
+
`docs/CLAIMS.md` §3.1's own simulation table two paragraphs away, which
|
|
138
|
+
shows pypacker importing cleanly under the identical CPython-side
|
|
139
|
+
check used for dpkt; reworded to say so, and to stop implying the
|
|
140
|
+
scapy-import failure was observed directly inside the real Pyodide CI
|
|
141
|
+
run rather than established by source citation plus that simulation.
|
|
142
|
+
README's CI-gating bullet lumped PyTCP-net_proto in with three
|
|
143
|
+
libraries whose benchmark code targets the library being compared;
|
|
144
|
+
`docs/CLAIMS.md` §1.7's own citation notes PyTCP-net_proto's benchmark
|
|
145
|
+
exercises its RX daemon instead — reworded to keep that distinction
|
|
146
|
+
rather than flattening it.
|
|
147
|
+
- **Restored this file's own missing `## [2.2.0]` version header.** A
|
|
148
|
+
prior documentation commit deleted it while inserting an adjacent
|
|
149
|
+
bullet, which silently re-merged the entire tagged, PyPI-published
|
|
150
|
+
2.2.0 release back under `## [Unreleased]` — the section below is
|
|
151
|
+
what a `git show` of the `v2.2.0` tag's own CHANGELOG.md actually
|
|
152
|
+
contained at release time.
|
|
153
|
+
|
|
8
154
|
## [2.2.0] - 2026-09-04
|
|
9
155
|
|
|
10
156
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: netprotocols
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.1
|
|
4
4
|
Summary: Low-level implementations of common networking protocols
|
|
5
5
|
Project-URL: Homepage, https://github.com/EONRaider/NETProtocols
|
|
6
6
|
Project-URL: Repository, https://github.com/EONRaider/NETProtocols
|
|
@@ -25,6 +25,7 @@ Description-Content-Type: text/markdown
|
|
|
25
25
|
|
|
26
26
|
# NETProtocols
|
|
27
27
|
|
|
28
|
+
[](https://www.codefactor.io/repository/github/eonraider/netprotocols/overview/master)
|
|
28
29
|
[](https://github.com/EONRaider/NETProtocols/actions/workflows/ci.yml)
|
|
29
30
|

|
|
30
31
|
[](LICENSE)
|
|
@@ -79,7 +80,7 @@ real captured traffic (CPython 3.12.3, x86-64 Linux; re-measured
|
|
|
79
80
|
2026-09-04 — see [docs/CLAIMS.md](docs/CLAIMS.md) for every number
|
|
80
81
|
below, its reproduction command, and its caveats):
|
|
81
82
|
|
|
82
|
-
- **Within 15% of dpkt on decode, 5.
|
|
83
|
+
- **Within 15% of dpkt on decode, 5.4× faster than scapy** —
|
|
83
84
|
`uv run --group bench python scripts/benchmark.py --compare`. dpkt is
|
|
84
85
|
the faster of the two comparators on this corpus; the gap moves both
|
|
85
86
|
ways as this library and dpkt each change, and this file's job is to
|
|
@@ -95,18 +96,22 @@ below, its reproduction command, and its caveats):
|
|
|
95
96
|
and never touches the host doing it — importing scapy populates live
|
|
96
97
|
interface and routing tables as a side effect of the import
|
|
97
98
|
statement.
|
|
98
|
-
- **An
|
|
99
|
+
- **An 88.3 KB wheel against scapy's 2.47 MB** — about 29× smaller.
|
|
99
100
|
- **Regression-gated in CI, which — as far as we could establish by
|
|
100
101
|
auditing ten comparable Python packet libraries' CI configurations
|
|
101
102
|
(dpkt, scapy, pypacker, construct, pcapkit, dnspython, pyshark,
|
|
102
103
|
nfstream, stackforge, PyTCP-net_proto; full citations in
|
|
103
|
-
[docs/CLAIMS.md §1.7](docs/CLAIMS.md)) — none of them
|
|
104
|
-
the ten ship
|
|
104
|
+
[docs/CLAIMS.md §1.7](docs/CLAIMS.md#17-decode-throughput-is-regression-gated-in-ci)) — none of them gate on one.**
|
|
105
|
+
Three of the ten (scapy, construct, stackforge) ship benchmark code
|
|
106
|
+
that targets the library being compared but never runs in CI
|
|
105
107
|
(construct disables it explicitly; stackforge's Criterion benches are
|
|
106
|
-
never invoked)
|
|
108
|
+
never invoked). A fourth, PyTCP-net_proto, ships a benchmark too, but
|
|
109
|
+
it exercises its RX daemon rather than the compared `net_proto`
|
|
110
|
+
package, and isn't wired to CI either. None of the ten fails a build
|
|
111
|
+
on a performance regression.
|
|
107
112
|
- **Typed where the alternatives are not.** `mypy --strict` over the
|
|
108
113
|
whole of `src/`, enforced in CI. scapy ships `py.typed` but enables
|
|
109
|
-
strict checking on
|
|
114
|
+
strict checking on 89 of its files, 2 of the 121 under
|
|
110
115
|
`scapy/layers/` — the dissectors most code touches stay `Any`. dpkt
|
|
111
116
|
ships no `py.typed` at all, and no third-party stub package exists
|
|
112
117
|
for it.
|
|
@@ -116,11 +121,14 @@ below, its reproduction command, and its caveats):
|
|
|
116
121
|
GPL-3.0. The other permissive option, dpkt (BSD), last released
|
|
117
122
|
2022-08-18 and last committed 2024-05-05, still targets Python 2.7
|
|
118
123
|
and 3.5–3.9, and remains marked Beta after twelve years.
|
|
119
|
-
- **Runs where scapy cannot — including in the browser.**
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
- **Runs where scapy cannot — including in the browser.** Under a real
|
|
125
|
+
Pyodide runtime in CI, netprotocols imports and decodes the full
|
|
126
|
+
corpus cleanly. Scapy cannot even be attempted there: it is absent
|
|
127
|
+
from Pyodide's package set, and `from fcntl import ioctl` is
|
|
128
|
+
unconditional in `scapy/arch/` — confirmed by that same CI run still
|
|
129
|
+
missing the module, plus the source citation. dpkt and pypacker were
|
|
130
|
+
both confirmed to import cleanly under the same missing modules, but
|
|
131
|
+
via a separate CPython-side simulation, not inside the CI job.
|
|
124
132
|
- **The only one of these libraries a `match`/`case` statement
|
|
125
133
|
dissects out of the box.** dpkt builds `__slots__` from a metaclass
|
|
126
134
|
and generates no `__match_args__`; scapy routes fields through
|
|
@@ -417,10 +425,15 @@ comparative-claims embargo lift that closed it out:
|
|
|
417
425
|
| [1.4.0](https://github.com/EONRaider/NETProtocols/issues/103) | Decode performance |
|
|
418
426
|
| [2.0.0](https://github.com/EONRaider/NETProtocols/issues/104) | A public protocol registry, a shipped chain walker, flow keys — **released** |
|
|
419
427
|
| [2.1.0](https://github.com/EONRaider/NETProtocols/issues/105) | Typed accessors and pattern-matching ergonomics |
|
|
420
|
-
| [2.2.0](https://github.com/EONRaider/NETProtocols/issues/106) | Universal round-trip properties, nightly fuzzing, a pcap reader |
|
|
421
|
-
|
|
422
|
-
Everything through 2.2.0 has landed on `master
|
|
423
|
-
|
|
428
|
+
| [2.2.0](https://github.com/EONRaider/NETProtocols/issues/106) | Universal round-trip properties, nightly fuzzing, a pcap reader — **released** |
|
|
429
|
+
|
|
430
|
+
Everything through 2.2.0 has landed on `master`, plus a since-landed
|
|
431
|
+
decode-throughput fix
|
|
432
|
+
([#147](https://github.com/EONRaider/NETProtocols/issues/147), not yet
|
|
433
|
+
versioned). 2.1.0's and 1.3.1/1.4.0's work shipped folded into 2.0.0
|
|
434
|
+
and 2.2.0 rather than as their own versions, but the roadmap's original
|
|
435
|
+
"only at a major version bump" release policy wasn't actually held to:
|
|
436
|
+
both 2.0.0 and 2.2.0 — a minor release — are on PyPI, and the
|
|
424
437
|
comparative claims above draw on the finished tree. New planned work
|
|
425
438
|
will open fresh issues rather than restating a list here, so this
|
|
426
439
|
section stays accurate without upkeep. The wave before this one — TCP
|
|
@@ -433,7 +446,8 @@ corpus fixture and `ipaddress` accessors — shipped in 1.3.0; see
|
|
|
433
446
|
|
|
434
447
|
Development uses [uv](https://docs.astral.sh/uv/): `uv sync`, then
|
|
435
448
|
`uv run pytest`, `uv run mypy`, and `uv run ruff check` — all three are
|
|
436
|
-
enforced by CI
|
|
449
|
+
enforced by CI; the test suite runs across Python 3.12–3.14, while
|
|
450
|
+
`mypy` and `ruff check` run on 3.12. The test suite is anchored by a
|
|
437
451
|
97-frame corpus of real captured traffic across 17 scenarios
|
|
438
452
|
([tests/fixtures/MANIFEST.md](tests/fixtures/MANIFEST.md)) plus
|
|
439
453
|
property-based fuzzing of the decode path. See
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# NETProtocols
|
|
2
2
|
|
|
3
|
+
[](https://www.codefactor.io/repository/github/eonraider/netprotocols/overview/master)
|
|
3
4
|
[](https://github.com/EONRaider/NETProtocols/actions/workflows/ci.yml)
|
|
4
5
|

|
|
5
6
|
[](LICENSE)
|
|
@@ -54,7 +55,7 @@ real captured traffic (CPython 3.12.3, x86-64 Linux; re-measured
|
|
|
54
55
|
2026-09-04 — see [docs/CLAIMS.md](docs/CLAIMS.md) for every number
|
|
55
56
|
below, its reproduction command, and its caveats):
|
|
56
57
|
|
|
57
|
-
- **Within 15% of dpkt on decode, 5.
|
|
58
|
+
- **Within 15% of dpkt on decode, 5.4× faster than scapy** —
|
|
58
59
|
`uv run --group bench python scripts/benchmark.py --compare`. dpkt is
|
|
59
60
|
the faster of the two comparators on this corpus; the gap moves both
|
|
60
61
|
ways as this library and dpkt each change, and this file's job is to
|
|
@@ -70,18 +71,22 @@ below, its reproduction command, and its caveats):
|
|
|
70
71
|
and never touches the host doing it — importing scapy populates live
|
|
71
72
|
interface and routing tables as a side effect of the import
|
|
72
73
|
statement.
|
|
73
|
-
- **An
|
|
74
|
+
- **An 88.3 KB wheel against scapy's 2.47 MB** — about 29× smaller.
|
|
74
75
|
- **Regression-gated in CI, which — as far as we could establish by
|
|
75
76
|
auditing ten comparable Python packet libraries' CI configurations
|
|
76
77
|
(dpkt, scapy, pypacker, construct, pcapkit, dnspython, pyshark,
|
|
77
78
|
nfstream, stackforge, PyTCP-net_proto; full citations in
|
|
78
|
-
[docs/CLAIMS.md §1.7](docs/CLAIMS.md)) — none of them
|
|
79
|
-
the ten ship
|
|
79
|
+
[docs/CLAIMS.md §1.7](docs/CLAIMS.md#17-decode-throughput-is-regression-gated-in-ci)) — none of them gate on one.**
|
|
80
|
+
Three of the ten (scapy, construct, stackforge) ship benchmark code
|
|
81
|
+
that targets the library being compared but never runs in CI
|
|
80
82
|
(construct disables it explicitly; stackforge's Criterion benches are
|
|
81
|
-
never invoked)
|
|
83
|
+
never invoked). A fourth, PyTCP-net_proto, ships a benchmark too, but
|
|
84
|
+
it exercises its RX daemon rather than the compared `net_proto`
|
|
85
|
+
package, and isn't wired to CI either. None of the ten fails a build
|
|
86
|
+
on a performance regression.
|
|
82
87
|
- **Typed where the alternatives are not.** `mypy --strict` over the
|
|
83
88
|
whole of `src/`, enforced in CI. scapy ships `py.typed` but enables
|
|
84
|
-
strict checking on
|
|
89
|
+
strict checking on 89 of its files, 2 of the 121 under
|
|
85
90
|
`scapy/layers/` — the dissectors most code touches stay `Any`. dpkt
|
|
86
91
|
ships no `py.typed` at all, and no third-party stub package exists
|
|
87
92
|
for it.
|
|
@@ -91,11 +96,14 @@ below, its reproduction command, and its caveats):
|
|
|
91
96
|
GPL-3.0. The other permissive option, dpkt (BSD), last released
|
|
92
97
|
2022-08-18 and last committed 2024-05-05, still targets Python 2.7
|
|
93
98
|
and 3.5–3.9, and remains marked Beta after twelve years.
|
|
94
|
-
- **Runs where scapy cannot — including in the browser.**
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
- **Runs where scapy cannot — including in the browser.** Under a real
|
|
100
|
+
Pyodide runtime in CI, netprotocols imports and decodes the full
|
|
101
|
+
corpus cleanly. Scapy cannot even be attempted there: it is absent
|
|
102
|
+
from Pyodide's package set, and `from fcntl import ioctl` is
|
|
103
|
+
unconditional in `scapy/arch/` — confirmed by that same CI run still
|
|
104
|
+
missing the module, plus the source citation. dpkt and pypacker were
|
|
105
|
+
both confirmed to import cleanly under the same missing modules, but
|
|
106
|
+
via a separate CPython-side simulation, not inside the CI job.
|
|
99
107
|
- **The only one of these libraries a `match`/`case` statement
|
|
100
108
|
dissects out of the box.** dpkt builds `__slots__` from a metaclass
|
|
101
109
|
and generates no `__match_args__`; scapy routes fields through
|
|
@@ -392,10 +400,15 @@ comparative-claims embargo lift that closed it out:
|
|
|
392
400
|
| [1.4.0](https://github.com/EONRaider/NETProtocols/issues/103) | Decode performance |
|
|
393
401
|
| [2.0.0](https://github.com/EONRaider/NETProtocols/issues/104) | A public protocol registry, a shipped chain walker, flow keys — **released** |
|
|
394
402
|
| [2.1.0](https://github.com/EONRaider/NETProtocols/issues/105) | Typed accessors and pattern-matching ergonomics |
|
|
395
|
-
| [2.2.0](https://github.com/EONRaider/NETProtocols/issues/106) | Universal round-trip properties, nightly fuzzing, a pcap reader |
|
|
396
|
-
|
|
397
|
-
Everything through 2.2.0 has landed on `master
|
|
398
|
-
|
|
403
|
+
| [2.2.0](https://github.com/EONRaider/NETProtocols/issues/106) | Universal round-trip properties, nightly fuzzing, a pcap reader — **released** |
|
|
404
|
+
|
|
405
|
+
Everything through 2.2.0 has landed on `master`, plus a since-landed
|
|
406
|
+
decode-throughput fix
|
|
407
|
+
([#147](https://github.com/EONRaider/NETProtocols/issues/147), not yet
|
|
408
|
+
versioned). 2.1.0's and 1.3.1/1.4.0's work shipped folded into 2.0.0
|
|
409
|
+
and 2.2.0 rather than as their own versions, but the roadmap's original
|
|
410
|
+
"only at a major version bump" release policy wasn't actually held to:
|
|
411
|
+
both 2.0.0 and 2.2.0 — a minor release — are on PyPI, and the
|
|
399
412
|
comparative claims above draw on the finished tree. New planned work
|
|
400
413
|
will open fresh issues rather than restating a list here, so this
|
|
401
414
|
section stays accurate without upkeep. The wave before this one — TCP
|
|
@@ -408,7 +421,8 @@ corpus fixture and `ipaddress` accessors — shipped in 1.3.0; see
|
|
|
408
421
|
|
|
409
422
|
Development uses [uv](https://docs.astral.sh/uv/): `uv sync`, then
|
|
410
423
|
`uv run pytest`, `uv run mypy`, and `uv run ruff check` — all three are
|
|
411
|
-
enforced by CI
|
|
424
|
+
enforced by CI; the test suite runs across Python 3.12–3.14, while
|
|
425
|
+
`mypy` and `ruff check` run on 3.12. The test suite is anchored by a
|
|
412
426
|
97-frame corpus of real captured traffic across 17 scenarios
|
|
413
427
|
([tests/fixtures/MANIFEST.md](tests/fixtures/MANIFEST.md)) plus
|
|
414
428
|
property-based fuzzing of the decode path. See
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"frames_per_sec":
|
|
3
|
-
"calibration_per_sec":
|
|
4
|
-
"normalized": 6.
|
|
2
|
+
"frames_per_sec": 98007.5,
|
|
3
|
+
"calibration_per_sec": 14922.6,
|
|
4
|
+
"normalized": 6.5677,
|
|
5
5
|
"frames": 97,
|
|
6
6
|
"repetitions": 30,
|
|
7
7
|
"trials": 9,
|
|
8
8
|
"recorded": {
|
|
9
9
|
"on": "CPython 3.12.3 / x86_64 / Linux",
|
|
10
10
|
"python": "3.12.3",
|
|
11
|
-
"netprotocols": "
|
|
11
|
+
"netprotocols": "2.2.1"
|
|
12
12
|
}
|
|
13
13
|
}
|