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.
Files changed (132) hide show
  1. {netprotocols-2.2.0 → netprotocols-2.2.1}/.github/workflows/release.yml +33 -0
  2. {netprotocols-2.2.0 → netprotocols-2.2.1}/CHANGELOG.md +146 -0
  3. {netprotocols-2.2.0 → netprotocols-2.2.1}/PKG-INFO +31 -17
  4. {netprotocols-2.2.0 → netprotocols-2.2.1}/README.md +30 -16
  5. {netprotocols-2.2.0 → netprotocols-2.2.1}/benchmarks/baseline.json +4 -4
  6. {netprotocols-2.2.0 → netprotocols-2.2.1}/docs/CLAIMS.md +210 -47
  7. {netprotocols-2.2.0 → netprotocols-2.2.1}/pyproject.toml +5 -5
  8. netprotocols-2.2.1/scripts/_pcap.py +42 -0
  9. {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/benchmark.py +8 -51
  10. netprotocols-2.2.1/scripts/check_fixtures.py +332 -0
  11. {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/pyodide/check_in_pyodide.py +7 -33
  12. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/__init__.py +1 -1
  13. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/_base.py +22 -4
  14. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/packet.py +24 -0
  15. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/walk.py +1 -1
  16. {netprotocols-2.2.0 → netprotocols-2.2.1}/uv.lock +1 -1
  17. netprotocols-2.2.0/scripts/check_fixtures.py +0 -273
  18. {netprotocols-2.2.0 → netprotocols-2.2.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  19. {netprotocols-2.2.0 → netprotocols-2.2.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  20. {netprotocols-2.2.0 → netprotocols-2.2.1}/.github/ISSUE_TEMPLATE/protocol_request.yml +0 -0
  21. {netprotocols-2.2.0 → netprotocols-2.2.1}/.github/pull_request_template.md +0 -0
  22. {netprotocols-2.2.0 → netprotocols-2.2.1}/.github/workflows/ci.yml +0 -0
  23. {netprotocols-2.2.0 → netprotocols-2.2.1}/.github/workflows/fuzz.yml +0 -0
  24. {netprotocols-2.2.0 → netprotocols-2.2.1}/.gitignore +0 -0
  25. {netprotocols-2.2.0 → netprotocols-2.2.1}/ARCHITECTURE.md +0 -0
  26. {netprotocols-2.2.0 → netprotocols-2.2.1}/CONTRIBUTING.md +0 -0
  27. {netprotocols-2.2.0 → netprotocols-2.2.1}/LICENSE +0 -0
  28. {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/benchmark_encode.py +0 -0
  29. {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/benchmark_import.py +0 -0
  30. {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/capture_fixtures.sh +0 -0
  31. {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/capture_fixtures_dhcp.sh +0 -0
  32. {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/capture_fixtures_dns_tcp.sh +0 -0
  33. {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/capture_fixtures_gre.sh +0 -0
  34. {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/capture_fixtures_igmp.sh +0 -0
  35. {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/capture_fixtures_supplement.sh +0 -0
  36. {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/capture_fixtures_vlan.sh +0 -0
  37. {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/pyodide/package-lock.json +0 -0
  38. {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/pyodide/package.json +0 -0
  39. {netprotocols-2.2.0 → netprotocols-2.2.1}/scripts/pyodide/run_in_pyodide.mjs +0 -0
  40. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/_defaults.py +0 -0
  41. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/_enums.py +0 -0
  42. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/checksum.py +0 -0
  43. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/flow.py +0 -0
  44. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer2/__init__.py +0 -0
  45. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer2/arp.py +0 -0
  46. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer2/ethernet.py +0 -0
  47. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer2/vlan.py +0 -0
  48. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer3/__init__.py +0 -0
  49. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer3/gre.py +0 -0
  50. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer3/icmp.py +0 -0
  51. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer3/igmp.py +0 -0
  52. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer3/ip.py +0 -0
  53. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer3/ipv6_ext.py +0 -0
  54. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer4/__init__.py +0 -0
  55. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer4/_ports.py +0 -0
  56. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer4/tcp.py +0 -0
  57. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer4/udp.py +0 -0
  58. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer7/__init__.py +0 -0
  59. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer7/dhcp.py +0 -0
  60. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/layer7/dns.py +0 -0
  61. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/pcap.py +0 -0
  62. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/py.typed +0 -0
  63. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/registry.py +0 -0
  64. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/utils/__init__.py +0 -0
  65. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/utils/exceptions.py +0 -0
  66. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/utils/ipv4.py +0 -0
  67. {netprotocols-2.2.0 → netprotocols-2.2.1}/src/netprotocols/utils/mac.py +0 -0
  68. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/conftest.py +0 -0
  69. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/MANIFEST.md +0 -0
  70. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/arp_exchange.pcap +0 -0
  71. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/dhcp.pcap +0 -0
  72. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/dns_tcp.pcap +0 -0
  73. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/gre.pcap +0 -0
  74. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/icmpv4_echo_lo.pcap +0 -0
  75. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/icmpv4_external.pcap +0 -0
  76. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/icmpv4_ttl_exceeded.pcap +0 -0
  77. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/icmpv6_echo_lo.pcap +0 -0
  78. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/igmp.pcap +0 -0
  79. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/ipv4_fragments.pcap +0 -0
  80. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/ipv6_fragments.pcap +0 -0
  81. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/ipv6_mld.pcap +0 -0
  82. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/ipv6_ndp_mld.pcap +0 -0
  83. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/arp_exchange.pcap +0 -0
  84. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/dhcp.pcap +0 -0
  85. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/dns_tcp.pcap +0 -0
  86. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/gre.pcap +0 -0
  87. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/icmpv4_echo_lo.pcap +0 -0
  88. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/icmpv4_external.pcap +0 -0
  89. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/icmpv4_ttl_exceeded.pcap +0 -0
  90. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/icmpv6_echo_lo.pcap +0 -0
  91. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/igmp.pcap +0 -0
  92. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/ipv4_fragments.pcap +0 -0
  93. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/ipv6_fragments.pcap +0 -0
  94. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/ipv6_mld.pcap +0 -0
  95. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/ipv6_ndp_mld.pcap +0 -0
  96. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/tcp_http.pcap +0 -0
  97. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/tcp_https.pcap +0 -0
  98. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/udp_dns.pcap +0 -0
  99. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/staging/vlan_icmp.pcap +0 -0
  100. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/tcp_http.pcap +0 -0
  101. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/tcp_https.pcap +0 -0
  102. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/udp_dns.pcap +0 -0
  103. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/fixtures/vlan_icmp.pcap +0 -0
  104. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/strategies.py +0 -0
  105. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_arp.py +0 -0
  106. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_checksum.py +0 -0
  107. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_contract.py +0 -0
  108. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_corpus.py +0 -0
  109. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_dhcp.py +0 -0
  110. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_diagnostics.py +0 -0
  111. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_dns.py +0 -0
  112. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_docs.py +0 -0
  113. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_ethernet.py +0 -0
  114. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_exports.py +0 -0
  115. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_flow.py +0 -0
  116. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_fuzz.py +0 -0
  117. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_gre.py +0 -0
  118. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_icmp.py +0 -0
  119. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_igmp.py +0 -0
  120. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_ip.py +0 -0
  121. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_ipv6_ext.py +0 -0
  122. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_packet.py +0 -0
  123. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_pattern_matching.py +0 -0
  124. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_pcap.py +0 -0
  125. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_registry.py +0 -0
  126. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_tcp.py +0 -0
  127. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_udp.py +0 -0
  128. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_utils.py +0 -0
  129. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_version.py +0 -0
  130. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_vlan.py +0 -0
  131. {netprotocols-2.2.0 → netprotocols-2.2.1}/tests/test_walk.py +0 -0
  132. {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.0
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
+ [![CodeFactor](https://www.codefactor.io/repository/github/eonraider/netprotocols/badge/master)](https://www.codefactor.io/repository/github/eonraider/netprotocols/overview/master)
28
29
  [![CI](https://github.com/EONRaider/NETProtocols/actions/workflows/ci.yml/badge.svg)](https://github.com/EONRaider/NETProtocols/actions/workflows/ci.yml)
29
30
  ![Python Version](https://img.shields.io/badge/python-3.12%2B-blue?logo=python)
30
31
  [![License: MIT](https://img.shields.io/badge/license-MIT-green)](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.6× faster than scapy** —
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 85.6 KB wheel against scapy's 2.47 MB** — about 30× smaller.
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 do.** Two of
104
- the ten ship real benchmark code that simply never runs in CI
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); the rest have no performance benchmark at all.
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 107 of its files, 2 of the 121 under
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.** Verified
120
- under a real Pyodide runtime in CI: scapy fails to import at all
121
- under Pyodide (`from fcntl import ioctl` is unconditional in
122
- `scapy/arch/`), where netprotocols, dpkt and pypacker all import
123
- cleanly.
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`; per the roadmap's own
423
- release policy, only 2.0.0 was an actual PyPI release, and the
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 on Python 3.12–3.14. The test suite is anchored by a
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
+ [![CodeFactor](https://www.codefactor.io/repository/github/eonraider/netprotocols/badge/master)](https://www.codefactor.io/repository/github/eonraider/netprotocols/overview/master)
3
4
  [![CI](https://github.com/EONRaider/NETProtocols/actions/workflows/ci.yml/badge.svg)](https://github.com/EONRaider/NETProtocols/actions/workflows/ci.yml)
4
5
  ![Python Version](https://img.shields.io/badge/python-3.12%2B-blue?logo=python)
5
6
  [![License: MIT](https://img.shields.io/badge/license-MIT-green)](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.6× faster than scapy** —
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 85.6 KB wheel against scapy's 2.47 MB** — about 30× smaller.
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 do.** Two of
79
- the ten ship real benchmark code that simply never runs in CI
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); the rest have no performance benchmark at all.
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 107 of its files, 2 of the 121 under
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.** Verified
95
- under a real Pyodide runtime in CI: scapy fails to import at all
96
- under Pyodide (`from fcntl import ioctl` is unconditional in
97
- `scapy/arch/`), where netprotocols, dpkt and pypacker all import
98
- cleanly.
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`; per the roadmap's own
398
- release policy, only 2.0.0 was an actual PyPI release, and the
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 on Python 3.12–3.14. The test suite is anchored by a
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": 114388.4,
3
- "calibration_per_sec": 17159.4,
4
- "normalized": 6.6662,
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": "1.3.0"
11
+ "netprotocols": "2.2.1"
12
12
  }
13
13
  }