ja4plus 1.2.1__tar.gz → 1.2.2__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 (502) hide show
  1. {ja4plus-1.2.1 → ja4plus-1.2.2}/.github/workflows/docs.yml +1 -1
  2. {ja4plus-1.2.1 → ja4plus-1.2.2}/CHANGELOG.md +44 -0
  3. {ja4plus-1.2.1 → ja4plus-1.2.2}/CLAUDE.md +1 -1
  4. {ja4plus-1.2.1/ja4plus.egg-info → ja4plus-1.2.2}/PKG-INFO +5 -5
  5. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/migration-0.6-to-1.0.md +4 -2
  6. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/specs/spec.md +1 -0
  7. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/__init__.py +1 -1
  8. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/utils/quic_utils.py +9 -5
  9. {ja4plus-1.2.1 → ja4plus-1.2.2/ja4plus.egg-info}/PKG-INFO +5 -5
  10. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus.egg-info/SOURCES.txt +1 -0
  11. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus.egg-info/requires.txt +4 -4
  12. {ja4plus-1.2.1 → ja4plus-1.2.2}/pyproject.toml +4 -4
  13. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_documentation_site.py +32 -21
  14. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_quic_crypto_buffer_bound.py +9 -2
  15. ja4plus-1.2.2/tests/test_quic_crypto_gap.py +132 -0
  16. {ja4plus-1.2.1 → ja4plus-1.2.2}/.claude/rules/agent-reports.md +0 -0
  17. {ja4plus-1.2.1 → ja4plus-1.2.2}/.claude/rules/batch-gate.md +0 -0
  18. {ja4plus-1.2.1 → ja4plus-1.2.2}/.claude/rules/conformance.md +0 -0
  19. {ja4plus-1.2.1 → ja4plus-1.2.2}/.claude/rules/external-apis.md +0 -0
  20. {ja4plus-1.2.1 → ja4plus-1.2.2}/.claude/rules/ste.md +0 -0
  21. {ja4plus-1.2.1 → ja4plus-1.2.2}/.claude/settings.json +0 -0
  22. {ja4plus-1.2.1 → ja4plus-1.2.2}/.claude/skills/conformance/SKILL.md +0 -0
  23. {ja4plus-1.2.1 → ja4plus-1.2.2}/.claude/skills/release/SKILL.md +0 -0
  24. {ja4plus-1.2.1 → ja4plus-1.2.2}/.claude/skills/test/SKILL.md +0 -0
  25. {ja4plus-1.2.1 → ja4plus-1.2.2}/.git-blame-ignore-revs +0 -0
  26. {ja4plus-1.2.1 → ja4plus-1.2.2}/.github/dependabot.yml +0 -0
  27. {ja4plus-1.2.1 → ja4plus-1.2.2}/.github/workflows/docs-build.yml +0 -0
  28. {ja4plus-1.2.1 → ja4plus-1.2.2}/.github/workflows/publish.yml +0 -0
  29. {ja4plus-1.2.1 → ja4plus-1.2.2}/.github/workflows/test.yml +0 -0
  30. {ja4plus-1.2.1 → ja4plus-1.2.2}/.github/workflows/update-db.yml +0 -0
  31. {ja4plus-1.2.1 → ja4plus-1.2.2}/.gitignore +0 -0
  32. {ja4plus-1.2.1 → ja4plus-1.2.2}/.issue-flow.json +0 -0
  33. {ja4plus-1.2.1 → ja4plus-1.2.2}/LICENSE +0 -0
  34. {ja4plus-1.2.1 → ja4plus-1.2.2}/README.md +0 -0
  35. {ja4plus-1.2.1 → ja4plus-1.2.2}/assets/logo.png +0 -0
  36. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/README.md +0 -0
  37. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/api_reference.md +0 -0
  38. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/concurrency.md +0 -0
  39. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/implementation_notes.md +0 -0
  40. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/methods/index.md +0 -0
  41. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/methods/ja4.md +0 -0
  42. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/methods/ja4d.md +0 -0
  43. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/methods/ja4d6.md +0 -0
  44. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/methods/ja4h.md +0 -0
  45. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/methods/ja4l.md +0 -0
  46. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/methods/ja4ls.md +0 -0
  47. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/methods/ja4s.md +0 -0
  48. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/methods/ja4ssh.md +0 -0
  49. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/methods/ja4t.md +0 -0
  50. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/methods/ja4ts.md +0 -0
  51. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/methods/ja4x.md +0 -0
  52. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/mutation_reports/412-utils.json +0 -0
  53. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/mutation_reports/413-fingerprinters.json +0 -0
  54. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/mutation_reports/414-interface.json +0 -0
  55. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/mutation_settlements/412-utils.json +0 -0
  56. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/mutation_settlements/413-fingerprinters.json +0 -0
  57. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/mutation_settlements/414-interface.json +0 -0
  58. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/mutation_sweep.md +0 -0
  59. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/output-schema.md +0 -0
  60. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/performance.md +0 -0
  61. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/reference/fingerprinters.md +0 -0
  62. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/reference/index.md +0 -0
  63. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/reference/ja4db.md +0 -0
  64. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/reference/processor.md +0 -0
  65. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/reference/types.md +0 -0
  66. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/specs/features/00-foundation.md +0 -0
  67. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/specs/features/01-spec-conformance.md +0 -0
  68. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/specs/features/02-correctness-audit.md +0 -0
  69. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/specs/features/03-concurrency-safety.md +0 -0
  70. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/specs/features/04-typed-api.md +0 -0
  71. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/specs/features/05-structured-output.md +0 -0
  72. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/specs/features/06-live-capture.md +0 -0
  73. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/specs/features/07-db-enrichment.md +0 -0
  74. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/specs/features/08-documentation.md +0 -0
  75. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/specs/features/09-release.md +0 -0
  76. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/specs/features/11-pre-release-validation.md +0 -0
  77. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/specs/foxio/JA4.md +0 -0
  78. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/specs/foxio/JA4D.md +0 -0
  79. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/specs/foxio/JA4H.md +0 -0
  80. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/specs/foxio/JA4L.md +0 -0
  81. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/specs/foxio/JA4S.md +0 -0
  82. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/specs/foxio/JA4SSH.md +0 -0
  83. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/specs/foxio/JA4T.md +0 -0
  84. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/specs/foxio/JA4X.md +0 -0
  85. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/specs/foxio/README.md +0 -0
  86. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/specs/foxio/deleted-text-specifications.md +0 -0
  87. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/specs/foxio/zeek.md +0 -0
  88. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/specs/mockups/01-cli-output.html +0 -0
  89. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/specs/spec.html +0 -0
  90. {ja4plus-1.2.1 → ja4plus-1.2.2}/docs/usage.md +0 -0
  91. {ja4plus-1.2.1 → ja4plus-1.2.2}/examples/demo_fingerprints.py +0 -0
  92. {ja4plus-1.2.1 → ja4plus-1.2.2}/examples/live_traffic_fingerprinting.py +0 -0
  93. {ja4plus-1.2.1 → ja4plus-1.2.2}/examples/pcap_analysis.py +0 -0
  94. {ja4plus-1.2.1 → ja4plus-1.2.2}/examples/tcpdump_workflows.py +0 -0
  95. {ja4plus-1.2.1 → ja4plus-1.2.2}/examples/threat_detection.py +0 -0
  96. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/cli.py +0 -0
  97. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/data/ja4plus-mapping.csv +0 -0
  98. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/fingerprinters/__init__.py +0 -0
  99. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/fingerprinters/base.py +0 -0
  100. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/fingerprinters/ja4.py +0 -0
  101. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/fingerprinters/ja4d.py +0 -0
  102. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/fingerprinters/ja4d6.py +0 -0
  103. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/fingerprinters/ja4h.py +0 -0
  104. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/fingerprinters/ja4l.py +0 -0
  105. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/fingerprinters/ja4s.py +0 -0
  106. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/fingerprinters/ja4ssh.py +0 -0
  107. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/fingerprinters/ja4t.py +0 -0
  108. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/fingerprinters/ja4ts.py +0 -0
  109. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/fingerprinters/ja4x.py +0 -0
  110. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/ja4db.py +0 -0
  111. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/output.py +0 -0
  112. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/processor.py +0 -0
  113. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/py.typed +0 -0
  114. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/types.py +0 -0
  115. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/utils/__init__.py +0 -0
  116. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/utils/http_utils.py +0 -0
  117. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/utils/icmp_quoted.py +0 -0
  118. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/utils/loopback.py +0 -0
  119. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/utils/packet_utils.py +0 -0
  120. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/utils/ssh_utils.py +0 -0
  121. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/utils/state_table.py +0 -0
  122. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/utils/tcp_options.py +0 -0
  123. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/utils/tcp_stream.py +0 -0
  124. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/utils/tls_utils.py +0 -0
  125. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/utils/tunnels.py +0 -0
  126. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/utils/x509_utils.py +0 -0
  127. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus/watch.py +0 -0
  128. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus.egg-info/dependency_links.txt +0 -0
  129. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus.egg-info/entry_points.txt +0 -0
  130. {ja4plus-1.2.1 → ja4plus-1.2.2}/ja4plus.egg-info/top_level.txt +0 -0
  131. {ja4plus-1.2.1 → ja4plus-1.2.2}/mkdocs.yml +0 -0
  132. {ja4plus-1.2.1 → ja4plus-1.2.2}/setup.cfg +0 -0
  133. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/__init__.py +0 -0
  134. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/batch_gate.py +0 -0
  135. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/build_alpn_condition_capture.py +0 -0
  136. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/build_ja4ts_rst.py +0 -0
  137. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/build_quic_ja4l_captures.py +0 -0
  138. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/build_ssh_retransmission.py +0 -0
  139. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/citation_line_allowlist.json +0 -0
  140. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/compare_zeek_baselines.py +0 -0
  141. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/conformance_index.py +0 -0
  142. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/conftest.py +0 -0
  143. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/data/dns.cap +0 -0
  144. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/data/example_cert.der +0 -0
  145. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/data/filedownload.dmp +0 -0
  146. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/data/http.cap +0 -0
  147. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/data/suspectemployeechat.dmp +0 -0
  148. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/data/test_cert.der +0 -0
  149. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/data/wrongdissector.dmp +0 -0
  150. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/dependency_entries.py +0 -0
  151. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/documentation_samples.py +0 -0
  152. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/download_test_vectors.py +0 -0
  153. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_citation_lines.py +0 -0
  154. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_deviation_owners.json +0 -0
  155. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_deviations.json +0 -0
  156. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_deviations.py +0 -0
  157. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_manifest.py +0 -0
  158. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vector_manifest.json +0 -0
  159. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/CVE-2018-6794.pcap +0 -0
  160. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/CVE-2018-6794.pcap.json +0 -0
  161. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/NOTICE +0 -0
  162. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/alpn-condition.pcap +0 -0
  163. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/alpn-condition.pcap.json +0 -0
  164. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/badcurveball.pcap +0 -0
  165. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/badcurveball.pcap.json +0 -0
  166. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/browsers-x509.pcapng +0 -0
  167. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/browsers-x509.pcapng.json +0 -0
  168. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/chrome-cloudflare-quic-with-secrets.pcapng +0 -0
  169. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/chrome-cloudflare-quic-with-secrets.pcapng.json +0 -0
  170. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/dhcp.pcapng +0 -0
  171. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/dhcp.pcapng.json +0 -0
  172. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/dhcpv6.pcap +0 -0
  173. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/dhcpv6.pcap.json +0 -0
  174. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/gre-erspan-vxlan.pcap +0 -0
  175. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/gre-erspan-vxlan.pcap.json +0 -0
  176. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/gre-sample.pcap +0 -0
  177. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/gre-sample.pcap.json +0 -0
  178. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/http-empty-useragent.pcap +0 -0
  179. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/http-empty-useragent.pcap.json +0 -0
  180. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/http1-with-cookies.pcapng +0 -0
  181. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/http1-with-cookies.pcapng.json +0 -0
  182. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/http1.pcapng +0 -0
  183. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/http1.pcapng.json +0 -0
  184. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/http2-with-cookies.pcapng +0 -0
  185. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/http2-with-cookies.pcapng.json +0 -0
  186. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/https-connect.pcap +0 -0
  187. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/https-connect.pcap.json +0 -0
  188. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/https3-301-get.pcap +0 -0
  189. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/https3-301-get.pcap.json +0 -0
  190. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/ipv6.pcapng +0 -0
  191. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/ipv6.pcapng.json +0 -0
  192. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/latest.pcapng +0 -0
  193. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/latest.pcapng.json +0 -0
  194. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/macos_tcp_flags.pcap +0 -0
  195. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/macos_tcp_flags.pcap.json +0 -0
  196. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/quic-tls-handshake.pcapng +0 -0
  197. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/quic-tls-handshake.pcapng.json +0 -0
  198. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/quic-with-several-tls-frames.pcapng +0 -0
  199. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/quic-with-several-tls-frames.pcapng.json +0 -0
  200. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/rust_expected/ja4__insta@browsers-x509.pcapng.snap +0 -0
  201. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/rust_expected/ja4__insta@chrome-cloudflare-quic-with-secrets.pcapng.snap +0 -0
  202. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/rust_expected/ja4__insta@gre-erspan-vxlan.pcap.snap +0 -0
  203. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/rust_expected/ja4__insta@https-connect.pcap.snap +0 -0
  204. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/rust_expected/ja4__insta@latest.pcapng.snap +0 -0
  205. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/rust_expected/ja4__insta@quic-tls-handshake.pcapng.snap +0 -0
  206. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/rust_expected/ja4__insta@quic-with-several-tls-frames.pcapng.snap +0 -0
  207. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/rust_expected/ja4__insta@ssh2.pcapng.snap +0 -0
  208. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/rust_expected/ja4__insta@tls-handshake.pcapng.snap +0 -0
  209. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/rust_expected/ja4__insta@tls-sni.pcapng.snap +0 -0
  210. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/rust_expected/ja4__insta@tls3.pcapng.snap +0 -0
  211. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/single-packets.pcap +0 -0
  212. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/single-packets.pcap.json +0 -0
  213. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/socks-https-example.pcap +0 -0
  214. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/socks-https-example.pcap.json +0 -0
  215. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/socks4-https.pcap +0 -0
  216. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/socks4-https.pcap.json +0 -0
  217. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/ssh-r.pcap +0 -0
  218. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/ssh-r.pcap.json +0 -0
  219. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/ssh-scp-1050.pcap +0 -0
  220. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/ssh-scp-1050.pcap.json +0 -0
  221. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/ssh.pcapng +0 -0
  222. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/ssh.pcapng.json +0 -0
  223. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/ssh2-malformed.pcap +0 -0
  224. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/ssh2-malformed.pcap.json +0 -0
  225. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/ssh2-moloch-crash.pcap +0 -0
  226. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/ssh2-moloch-crash.pcap.json +0 -0
  227. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/ssh2.pcapng +0 -0
  228. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/ssh2.pcapng.json +0 -0
  229. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/sshv1.pcap +0 -0
  230. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/sshv1.pcap.json +0 -0
  231. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/tcpdump-geneve.pcap +0 -0
  232. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/tcpdump-geneve.pcap.json +0 -0
  233. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/tls-alpn-h2.pcap +0 -0
  234. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/tls-alpn-h2.pcap.json +0 -0
  235. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/tls-handshake.pcapng +0 -0
  236. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/tls-handshake.pcapng.json +0 -0
  237. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/tls-non-ascii-alpn.pcapng +0 -0
  238. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/tls-non-ascii-alpn.pcapng.json +0 -0
  239. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/tls-sni.pcapng +0 -0
  240. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/tls-sni.pcapng.json +0 -0
  241. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/tls12.pcap +0 -0
  242. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/tls12.pcap.json +0 -0
  243. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/tls3.pcapng +0 -0
  244. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/tls3.pcapng.json +0 -0
  245. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/v6.pcap +0 -0
  246. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/v6.pcap.json +0 -0
  247. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/CVE-2018-6794.pcap.json +0 -0
  248. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/badcurveball.pcap.json +0 -0
  249. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/browsers-x509.pcapng.json +0 -0
  250. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/chrome-cloudflare-quic-with-secrets.pcapng.json +0 -0
  251. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/dhcp.pcapng.json +0 -0
  252. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/dhcpv6.pcap.json +0 -0
  253. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/gre-erspan-vxlan.pcap.json +0 -0
  254. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/http-empty-useragent.pcap.json +0 -0
  255. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/http1-with-cookies.pcapng.json +0 -0
  256. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/http2-with-cookies.pcapng.json +0 -0
  257. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/https-connect.pcap.json +0 -0
  258. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/https3-301-get.pcap.json +0 -0
  259. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/ipv6.pcapng.json +0 -0
  260. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/latest.pcapng.json +0 -0
  261. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/socks-https-example.pcap.json +0 -0
  262. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/socks4-https.pcap.json +0 -0
  263. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/ssh-r.pcap.json +0 -0
  264. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/ssh-scp-1050.pcap.json +0 -0
  265. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/ssh2-malformed.pcap.json +0 -0
  266. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/ssh2-moloch-crash.pcap.json +0 -0
  267. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/ssh2.pcapng.json +0 -0
  268. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/sshv1.pcap.json +0 -0
  269. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/tcpdump-geneve.pcap.json +0 -0
  270. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/tls-alpn-h2.pcap.json +0 -0
  271. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/tls3.pcapng.json +0 -0
  272. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/wireshark_expected/v6.pcap.json +0 -0
  273. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/zeek_expected/Scripts.ja4-conn/conn.log +0 -0
  274. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/zeek_expected/Scripts.ja4-conn-quic/conn.log +0 -0
  275. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/zeek_expected/Scripts.ja4-conn-tls3/conn.log +0 -0
  276. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/zeek_expected/Scripts.ja4-dhcp/ja4d.log +0 -0
  277. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/zeek_expected/Scripts.ja4-http1-with-cookies/http.log +0 -0
  278. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/zeek_expected/Scripts.ja4-ssh2/ja4ssh.log +0 -0
  279. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/foxio_vectors/zeek_expected/Scripts.ja4-tls-handshake/ssl.log +0 -0
  280. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/fuzz/README.md +0 -0
  281. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/fuzz/__init__.py +0 -0
  282. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/fuzz/conftest.py +0 -0
  283. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/fuzz/support.py +0 -0
  284. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/fuzz/test_icmp_quoted_header.py +0 -0
  285. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/fuzz/test_length_fields.py +0 -0
  286. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/fuzz/test_malformed_captures.py +0 -0
  287. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/fuzz/test_server_hello_extensions.py +0 -0
  288. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/fuzz/test_structural_validity.py +0 -0
  289. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/fuzz/test_synthetic_hostile_input.py +0 -0
  290. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/fuzz/test_truncated_captures.py +0 -0
  291. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/generate_foxio_baseline.py +0 -0
  292. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/measure_random_client_hello.py +0 -0
  293. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/memory_ceiling_run.py +0 -0
  294. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/mutation_census.py +0 -0
  295. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/mutation_cover.py +0 -0
  296. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/mutation_sweep.py +0 -0
  297. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/quic_builder.py +0 -0
  298. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/release_body.py +0 -0
  299. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/release_verification.py +0 -0
  300. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/skip_gate.py +0 -0
  301. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/state_readers.py +0 -0
  302. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_agent_report_absence_rule.py +0 -0
  303. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_alpn_ruling_register.py +0 -0
  304. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_api_reference_interface_table.py +0 -0
  305. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_bare_exception_reading.py +0 -0
  306. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_batch_gate.py +0 -0
  307. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_batch_gate_protection_rule.py +0 -0
  308. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_bounded_state_table_adoption.py +0 -0
  309. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_breaking_change_record.py +0 -0
  310. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_changelog_round_agreement.py +0 -0
  311. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_changelog_sentence_exemption.py +0 -0
  312. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_cleanup_connection.py +0 -0
  313. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_cli.py +0 -0
  314. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_cli_closed_window_endpoints.py +0 -0
  315. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_cli_lookup_opt_in.py +0 -0
  316. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_cli_output_option.py +0 -0
  317. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_cli_processor.py +0 -0
  318. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_cli_types_ja4ls.py +0 -0
  319. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_compare_zeek_baselines.py +0 -0
  320. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_comprehensive.py +0 -0
  321. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_conformance_frame_discrimination.py +0 -0
  322. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_criterion_counts.py +0 -0
  323. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_db_cache_directory.py +0 -0
  324. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_db_item_access.py +0 -0
  325. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_db_lookup_many.py +0 -0
  326. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_db_offline.py +0 -0
  327. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_decryption_non_goal_ruling.py +0 -0
  328. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_delegated_ruling_rule.py +0 -0
  329. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_dependency_entries.py +0 -0
  330. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_dhcp_tunnel_inner_udp.py +0 -0
  331. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_documentation_behaviour_rule.py +0 -0
  332. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_documentation_fallback_rule.py +0 -0
  333. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_documentation_image_count.py +0 -0
  334. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_documentation_publish.py +0 -0
  335. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_documentation_samples.py +0 -0
  336. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_documentation_version_claims.py +0 -0
  337. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_documented_method_count.py +0 -0
  338. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_documented_state_table_count.py +0 -0
  339. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_download_filter_fields.py +0 -0
  340. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_edge_cases.py +0 -0
  341. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_empty_list_sentinel_ruling.py +0 -0
  342. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_examples.py +0 -0
  343. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_foxio_citation_lines.py +0 -0
  344. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_foxio_deviations.py +0 -0
  345. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_foxio_ja4_transcription.py +0 -0
  346. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_foxio_license_register.py +0 -0
  347. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_foxio_manifest.py +0 -0
  348. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_foxio_rust_parity.py +0 -0
  349. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_foxio_wireshark_ja4ts.py +0 -0
  350. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_foxio_zeek_ja4ts.py +0 -0
  351. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_future_dated_packet_eviction.py +0 -0
  352. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_generate_foxio_baseline.py +0 -0
  353. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_get_stream_byte_cap_truncation.py +0 -0
  354. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_http_header_block_terminator.py +0 -0
  355. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_installed_wheel.py +0 -0
  356. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_installed_wheel_selection.py +0 -0
  357. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_integration.py +0 -0
  358. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_interpreter_set.py +0 -0
  359. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ipv6_support.py +0 -0
  360. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4.py +0 -0
  361. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4_alpn.py +0 -0
  362. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4_alpn_condition.py +0 -0
  363. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4_deep.py +0 -0
  364. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4_empty_ext.py +0 -0
  365. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4_hello_retry.py +0 -0
  366. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4_original_order.py +0 -0
  367. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4_raw_branches.py +0 -0
  368. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4_ssl2_version.py +0 -0
  369. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4d.py +0 -0
  370. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4d6_foxio.py +0 -0
  371. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4d_decisions.py +0 -0
  372. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4d_foxio.py +0 -0
  373. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4d_fqdn_name_ruling.py +0 -0
  374. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4db.py +0 -0
  375. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4db_empty_list_alias.py +0 -0
  376. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4h.py +0 -0
  377. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4h_cookie_list.py +0 -0
  378. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4h_deep.py +0 -0
  379. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4h_emission_frame_ruling.py +0 -0
  380. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4h_large_cookie_header.py +0 -0
  381. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4h_line_endings.py +0 -0
  382. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4h_part_a_readings.py +0 -0
  383. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4h_raw.py +0 -0
  384. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4h_raw_sorted.py +0 -0
  385. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4h_reassembly.py +0 -0
  386. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4h_request_line_and_empty_header_list.py +0 -0
  387. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4h_retransmission.py +0 -0
  388. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4h_spec.py +0 -0
  389. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4l.py +0 -0
  390. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4l_connection_state.py +0 -0
  391. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4l_foxio.py +0 -0
  392. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4l_header_block_terminator.py +0 -0
  393. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4l_quic_eviction_ruling.py +0 -0
  394. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4l_quic_marker.py +0 -0
  395. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4l_quic_point_b_ruling.py +0 -0
  396. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4l_quic_point_d_emission.py +0 -0
  397. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4l_quic_repeated_connection.py +0 -0
  398. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4l_quic_server_hello.py +0 -0
  399. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4l_return_path.py +0 -0
  400. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4l_timing_fork_ruling.py +0 -0
  401. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4l_udp_direction.py +0 -0
  402. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4s.py +0 -0
  403. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4s_quic_direction.py +0 -0
  404. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4s_quic_split_server_hello.py +0 -0
  405. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4s_raw_order.py +0 -0
  406. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4ssh.py +0 -0
  407. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4ssh_close_connection_window.py +0 -0
  408. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4ssh_deep.py +0 -0
  409. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4ssh_direction.py +0 -0
  410. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4ssh_interpret.py +0 -0
  411. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4ssh_message_count.py +0 -0
  412. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4ssh_packet_selection_ruling.py +0 -0
  413. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4ssh_parity_record.py +0 -0
  414. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4ssh_server_side.py +0 -0
  415. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4ssh_spec.py +0 -0
  416. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4ssh_window.py +0 -0
  417. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4ssh_windows.py +0 -0
  418. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4t.py +0 -0
  419. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4t_citation_lines.py +0 -0
  420. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4t_deep.py +0 -0
  421. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4t_emission_model_ruling.py +0 -0
  422. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4t_form.py +0 -0
  423. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4t_icmp_quoted.py +0 -0
  424. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4t_syn_selection_ruling.py +0 -0
  425. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4ts.py +0 -0
  426. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4ts_delay_rounding_ruling.py +0 -0
  427. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4ts_part_e.py +0 -0
  428. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4ts_prefix_bound.py +0 -0
  429. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4ts_reset.py +0 -0
  430. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4x.py +0 -0
  431. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4x_deep.py +0 -0
  432. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4x_empty_ext.py +0 -0
  433. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4x_named_exceptions.py +0 -0
  434. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4x_raw.py +0 -0
  435. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4x_reassembly.py +0 -0
  436. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ja4x_record_scan.py +0 -0
  437. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_key_log_interface_ruling.py +0 -0
  438. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_lint_gate_pin.py +0 -0
  439. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_loopback_link_type.py +0 -0
  440. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_memory_bounds.py +0 -0
  441. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_method_pages.py +0 -0
  442. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_mutation_census.py +0 -0
  443. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_mutation_cover.py +0 -0
  444. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_mutation_sweep_annotation_skip.py +0 -0
  445. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_mutation_sweep_commit.py +0 -0
  446. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_mutation_sweep_module_list.py +0 -0
  447. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_mutation_sweep_timeout.py +0 -0
  448. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_no_retained_packets.py +0 -0
  449. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_output_schema.py +0 -0
  450. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_packaging.py +0 -0
  451. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_packet_utils.py +0 -0
  452. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_parity.py +0 -0
  453. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_parser_description.py +0 -0
  454. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ported_pattern_cost.py +0 -0
  455. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_precedence_exception.py +0 -0
  456. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_processor.py +0 -0
  457. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_processor_stats.py +0 -0
  458. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_processor_typed_results.py +0 -0
  459. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_public_interface.py +0 -0
  460. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_publish_workflow.py +0 -0
  461. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_quic_coalesced_datagram_ruling.py +0 -0
  462. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_quic_integration.py +0 -0
  463. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_quic_multipacket.py +0 -0
  464. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_quic_server_initial.py +0 -0
  465. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_quic_tunnel_inner_udp.py +0 -0
  466. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_quic_utils.py +0 -0
  467. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_readme_contracts.py +0 -0
  468. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_release_body.py +0 -0
  469. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_release_notes.py +0 -0
  470. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_release_skill.py +0 -0
  471. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_requirement_scope.py +0 -0
  472. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_round_entry_existence.py +0 -0
  473. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_ruling_vocabulary.py +0 -0
  474. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_settlement_procedure.py +0 -0
  475. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_skip_gate.py +0 -0
  476. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_spec_validation.py +0 -0
  477. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_specification_changelog.py +0 -0
  478. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_specification_terms.py +0 -0
  479. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_state_table.py +0 -0
  480. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_statistics_thread_term.py +0 -0
  481. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_tcp_byte_cap_admission_ruling.py +0 -0
  482. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_tcp_segment_bound_ruling.py +0 -0
  483. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_tcp_stream.py +0 -0
  484. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_thread_safety.py +0 -0
  485. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_throughput.py +0 -0
  486. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_tls_utils.py +0 -0
  487. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_tunnels.py +0 -0
  488. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_types.py +0 -0
  489. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_us_english_spelling.py +0 -0
  490. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_utils.py +0 -0
  491. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_version_gate.py +0 -0
  492. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_watch.py +0 -0
  493. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_watch_capture.py +0 -0
  494. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_watch_drop_count.py +0 -0
  495. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_watch_statistics.py +0 -0
  496. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_watch_stop.py +0 -0
  497. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_wireshark_expected_counts.py +0 -0
  498. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_workflow_action_pins.py +0 -0
  499. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/test_x509_certificate_reader.py +0 -0
  500. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/throughput_run.py +0 -0
  501. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/universal_skips.json +0 -0
  502. {ja4plus-1.2.1 → ja4plus-1.2.2}/tests/version_gate.py +0 -0
@@ -120,4 +120,4 @@ jobs:
120
120
  # default that version 4 gave them, and `page_url` stays the one output. The
121
121
  # runtime moves from `node20` to `node24`.
122
122
  id: deployment
123
- uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
123
+ uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
@@ -6,6 +6,24 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ - **The QUIC CRYPTO reader stops at the first byte that has not arrived** (#762).
10
+ Round
11
+ 267. **An outside reporter measured the defect on Windows against a live capture.**
12
+ `reassemble_crypto_fragments` allocated a buffer to the highest fragment end, and each gap
13
+ read as zero bytes. **A ClientHello whose tail arrived before its middle passed the length
14
+ check**, and the TLS reader parsed the zero bytes as extension type 0. The reader now
15
+ returns the bytes from offset 0 to the first gap, which its docstring already stated. The
16
+ change reaches the ClientHello path, the ServerHello check and the JA4S reader.
17
+ `tests/test_quic_crypto_gap.py` replays the fragment layout of the report against the
18
+ ClientHello of `quic-with-several-tls-frames.pcapng`. **Before the repair the first
19
+ datagram wrote `q12d032700_55b375c5d22e_c65986f5733c`**, and after it the first datagram
20
+ writes nothing and the second writes the FoxIO value. **A replay of all 38 committed
21
+ captures writes 788 values at version 1.2.1 and 788 here, byte-identical.** The dependency
22
+ reader cases of `tests/test_documentation_site.py` now compare distribution names (#768),
23
+ so a pin bump fails no case there. Three pins of the `dev` and `docs` extras move: `build`
24
+ 1.6.1, `ruff` 0.16.9 and `mkdocstrings-python` 2.0.9 (#764, #766, #767). **The conformance
25
+ suite reports 1684 passed, 142 skipped and 138 xfailed before this round and after it.**
26
+
9
27
  - **Every live prose of the repository takes the US spelling** (#663).
10
28
  Round
11
29
  266. **The maintainer ruled on 2026-08-16, and rule 17 of `.claude/rules/ste.md` records
@@ -2069,6 +2087,32 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
2069
2087
  **No file under `ja4plus/` changes and no fingerprint moves**, and the conformance suite
2070
2088
  reports 1635 passed, 143 skipped and 140 xfailed.
2071
2089
 
2090
+ ## [1.2.2] - 2026-09-29
2091
+
2092
+ Version 1.2.2 follows version 1.2.1, and no version stands between them. The date of the
2093
+ heading above is the date this release reaches `master`. It carries round 267.
2094
+
2095
+ **The bump is patch and never minor.** This release adds no published name, it removes
2096
+ none, and it renames none.
2097
+
2098
+ **One reader stops producing a wrong value.** The QUIC CRYPTO reader filled each gap
2099
+ between fragments with zero bytes (#762). Where a ClientHello arrived out of order across
2100
+ datagrams, and its last fragment arrived before a middle one, the reader parsed the zero
2101
+ bytes and wrote a JA4 value that describes no traffic. **The reader now waits for every
2102
+ byte**, and it writes the correct value when the gap fills. The same reader serves the
2103
+ ServerHello check and the JA4S reader, so each one waits in the same way.
2104
+
2105
+ **No value of the committed corpus moves.** A replay of all 38 committed captures writes
2106
+ 788 values at version 1.2.1 and 788 here, and the two dumps are byte-identical. No committed
2107
+ capture carries a CRYPTO gap. **A caller whose traffic carried such a gap sees a wrong JA4
2108
+ value go away, and the correct one takes its place.**
2109
+
2110
+ **The conformance suite holds its reading.** It reports 1684 passed, 142 skipped and 138
2111
+ xfailed at version 1.2.1 and here.
2112
+
2113
+ **Three pins of the `dev` and `docs` extras move**, and no runtime dependency moves: `build`
2114
+ 1.6.1, `ruff` 0.16.9 and `mkdocstrings-python` 2.0.9.
2115
+
2072
2116
  ## [1.2.1] - 2026-08-16
2073
2117
 
2074
2118
  Version 1.2.1 follows version 1.2.0, and no version stands between them. The date of the
@@ -4,7 +4,7 @@
4
4
  produces JA4+ fingerprints. FoxIO publishes the JA4+ standard. This project is an
5
5
  independent implementation of it.
6
6
 
7
- This repository declares version 1.1.1, and `ja4plus/__init__.py:101` holds that one
7
+ This repository declares version 1.2.2, and `ja4plus/__init__.py:101` holds that one
8
8
  declaration. A publish to PyPI is a separate step, and the maintainer runs it.
9
9
 
10
10
  Design lives in `docs/specs/`. Read `docs/specs/spec.md` and the relevant
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ja4plus
3
- Version: 1.2.1
3
+ Version: 1.2.2
4
4
  Summary: JA4+ network fingerprinting library for TLS, TCP, HTTP, SSH, X.509, and DHCP analysis
5
5
  License: BSD-3-Clause AND LicenseRef-FoxIO-1.1
6
6
  Project-URL: Homepage, https://github.com/Crank-Git/ja4plus
@@ -29,9 +29,9 @@ Requires-Dist: cryptography>=42.0.0
29
29
  Provides-Extra: dev
30
30
  Requires-Dist: pytest==9.1.1; extra == "dev"
31
31
  Requires-Dist: pytest-cov==7.1.0; extra == "dev"
32
- Requires-Dist: ruff==0.16.2; extra == "dev"
32
+ Requires-Dist: ruff==0.16.9; extra == "dev"
33
33
  Requires-Dist: mypy>=1.11; extra == "dev"
34
- Requires-Dist: build==1.5.0; extra == "dev"
34
+ Requires-Dist: build==1.6.1; extra == "dev"
35
35
  Requires-Dist: twine==7.0.0; extra == "dev"
36
36
  Provides-Extra: lookup
37
37
  Requires-Dist: requests>=2.20.0; extra == "lookup"
@@ -39,8 +39,8 @@ Provides-Extra: docs
39
39
  Requires-Dist: mkdocs==1.6.1; extra == "docs"
40
40
  Requires-Dist: mkdocs-material==9.7.7; extra == "docs"
41
41
  Requires-Dist: mkdocstrings==1.0.6; extra == "docs"
42
- Requires-Dist: mkdocstrings-python==2.0.5; extra == "docs"
43
- Requires-Dist: griffe==2.1.0; extra == "docs"
42
+ Requires-Dist: mkdocstrings-python==2.0.9; extra == "docs"
43
+ Requires-Dist: griffe==2.3.0; extra == "docs"
44
44
  Dynamic: license-file
45
45
 
46
46
  <p align="center"><img src="assets/logo.png" width="300"></p>
@@ -3,7 +3,7 @@
3
3
  Where `ja4plus --version` reports 0.6.0, read this page before you install version 1.0.0.
4
4
  That command names the version of the package your environment holds.
5
5
 
6
- This repository declares version 1.2.1, which is a patch release of version 1.2.0.
6
+ This repository declares version 1.2.2, which is a patch release of version 1.2.1.
7
7
  **Python 3.9 leaves the supported set at version 1.1.0**, and version 1.0.1 is the release
8
8
  that still installs on it. Version 1.1.0 moves no other interface and no fingerprint, and
9
9
  version 1.1.1 repairs one crash of the ServerHello reader and moves neither.
@@ -13,7 +13,9 @@ rows below state what version 1.0.0 changed against version 0.6.0, and version 1
13
13
  changes none of them, so every row reads the same here. **Version 1.2.1 moves no
14
14
  fingerprint value at all**, so it breaks no row either. It repairs the address pair the
15
15
  command prints for a value that no packet released, and it moves the frame that carries
16
- the QUIC `JA4L-S` value without moving the text of that value. **A reader who moves from version
16
+ the QUIC `JA4L-S` value without moving the text of that value. **Version 1.2.2 moves no
17
+ value of the committed corpus either**, so it breaks no row. It stops the QUIC ClientHello
18
+ reader at the first CRYPTO byte that has not arrived (#762). **A reader who moves from version
17
19
  1.1.1 rather than from version 0.6.0 reads the `## [1.2.0]` section of `CHANGELOG.md`
18
20
  instead**, which names each value that moves and the issue that carries it. Those values
19
21
  are JA4X and JA4H on an empty list, JA4TS on a reset of a one-SYN-ACK connection, JA4T on
@@ -971,6 +971,7 @@ RST of a connection that holds one SYN-ACK. Four counts make the 68.
971
971
  | 264 | 2026-08-16 | Batch #731 recorded the last five ruled questions of the divergence register, and no member moved a fingerprint value. **#714 cited the JA4 transcription in the empty-list sentinel row.** **#622 recorded the JA4L timing fork as permanent**, because FoxIO publishes four readings that split two against two. **#625 recorded that point `B` stays the Initial packet that completes the ServerHello**, and the ruling of 2026-08-16 superseded the reason of the 2026-08-15 ruling and kept the outcome: the four references state four mechanisms, which is a plurality and no majority. **#621 kept the decryption non-goal of #129 and replaced the reversal condition**, because the earlier condition fired in form and not in substance. The new condition requires a release of the port that carries the capability, and a re-measurement of the yield against the 138 keys and 38 captures of this project. **#593 declined the eight key-material names of the port** under that same condition. **It corrected two stale premises of its own issue body**: the port pins `github.com/gopacket/gopacket v1.7.1`, which reads block type 10 at `pcapgo/pcapng.go:43`, and `scapy` reads a Decryption Secrets Block at `scapy/utils.py:1645`, so this project carries no reader gap. **The sub-merge of `dev` repaired six citations that #606 moved, across two rows.** The #625 row moved to `ja4plus/fingerprinters/ja4l.py:580-623`, `:614-615`, `:618` and `:620-622`, and it names the new emission site at `ja4plus/fingerprinters/ja4l.py:689`. The #622 row moved to `ja4plus/fingerprinters/ja4l.py:580-623` and `:621`. **The batch review measured that count**, because the record first stated three. **This is the first round one batch records under the model of #727**, and no member wrote a record of its own. The conformance suite reported 1676 passed, 142 skipped and 138 xfailed before the batch and after it. |
972
972
  | 265 | 2026-08-16 | Batch #738 read four guards, and no member moved a fingerprint value. A replay of all 38 committed captures wrote 788 values before the batch and 788 after it, byte-identical. **#736 measured that the conformance suite discriminates no emission frame move**, because only the FoxIO Wireshark dissector states the frame that carries a value, on all 724 values of 26 files. The FoxIO Python expected output, the FoxIO Rust snapshots and the seven Zeek baselines each state none. `.claude/rules/conformance.md` records what a green conformance run does not measure, and it names the seven frame moves of #606. **#720 states that a report an agent did not return is reported as absent.** A census of 1842 records found one that carries the shape, which is the record #720 was filed for. **No case refuses the shape**, because a case would need the session transcript and git tracks no copy of it, so `.claude/rules/agent-reports.md` states that rather than ships a guard that guards nothing. **#697 gives `get_stream` the byte-cap truncation the port holds**, under parity rule 2, and the truncation removes no byte today. **#742 repairs a user-visible output no guard could read**: `Processor.close_open_windows` dropped the four endpoint fields, so the command printed `unknown` for 22 of the 28 values that reach that path. **The loss sat downstream of every guard**, because each one read an entry that carried the correct pair. |
973
973
  | 266 | 2026-08-16 | The maintainer ruled US English on 2026-08-16, and rule 17 of `.claude/rules/ste.md` records it. **The prose of this repository is largely agent-written**, so a worker that matched the surrounding spelling strengthened a convention no person chose. **The sweep of #663 found 282 matches across 48 files and moved 109 words across 55 files**: 59 of `docs/`, 38 of `tests/`, 8 of `.claude/rules/`, 2 of `ja4plus/`, one of `CLAUDE.md` and one of `README.md`. **A record moved nowhere.** `CHANGELOG.md` holds 33 matches and the change set holds no line of it, and every hunk of `docs/specs/spec.md` stands above the `## Changelog` table. **No identifier moved and no name a caller reads moved.** `## Behaviour rules` stays because a case reads that heading by string, the seven `NEIGHBOUR` constants stay, and 26 occurrences of `acknowledgement` stay because American English holds that form. **`tests/test_us_english_spelling.py` reads every exemption from the rule file** rather than from a list it keeps itself. The conformance suite reported 1684 passed, 142 skipped and 138 xfailed before this round and after it. |
974
+ | 267 | 2026-09-29 | #762 landed and the QUIC CRYPTO reader stops at the first byte that has not arrived. **The reader filled each gap with zero bytes**, so a ClientHello whose tail arrived before its middle passed the length check and wrote a wrong JA4 value. `tests/test_quic_crypto_gap.py` replays the reported layout against the ClientHello of `quic-with-several-tls-frames.pcapng`. **A replay of all 38 committed captures wrote 788 values before the round and 788 after it, byte-identical.** #768 made the dependency reader cases compare distribution names, and #764, #766 and #767 moved three pins of the `dev` and `docs` extras. Version 1.2.2 carries this round. |
974
975
 
975
976
  ## Issue map
976
977
 
@@ -98,7 +98,7 @@ def compute_ja4x_from_pem(cert_pem_bytes: bytes | str) -> str | None:
98
98
  #
99
99
  # **Keep the plain string assignment.** `setuptools` reads the value from the syntax tree,
100
100
  # so a computed value would make a build import this module and every dependency it loads.
101
- __version__ = "1.2.1"
101
+ __version__ = "1.2.2"
102
102
  __author__ = "ja4plus contributors"
103
103
  __license__ = "BSD-3-Clause"
104
104
 
@@ -329,11 +329,15 @@ def reassemble_crypto_fragments(fragments: Iterable[tuple[int, bytes]]) -> bytes
329
329
  if not by_offset:
330
330
  return b""
331
331
 
332
- sorted_frags = sorted(by_offset.items())
333
- total_len = max(off + len(data) for off, data in sorted_frags)
334
- buf = bytearray(total_len)
335
- for off, data in sorted_frags:
336
- buf[off : off + len(data)] = data
332
+ # The reader returns the bytes from offset zero up to the first unreceived byte. A
333
+ # buffer that reached the highest offset held each gap as zero bytes, and a length
334
+ # check reads those as received. #762 measured a ClientHello whose tail arrived
335
+ # before its middle: the TLS reader parsed the zero bytes and produced a wrong JA4.
336
+ buf = bytearray()
337
+ for off, data in sorted(by_offset.items()):
338
+ if off > len(buf):
339
+ break
340
+ buf.extend(data[len(buf) - off :])
337
341
  return bytes(buf)
338
342
 
339
343
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ja4plus
3
- Version: 1.2.1
3
+ Version: 1.2.2
4
4
  Summary: JA4+ network fingerprinting library for TLS, TCP, HTTP, SSH, X.509, and DHCP analysis
5
5
  License: BSD-3-Clause AND LicenseRef-FoxIO-1.1
6
6
  Project-URL: Homepage, https://github.com/Crank-Git/ja4plus
@@ -29,9 +29,9 @@ Requires-Dist: cryptography>=42.0.0
29
29
  Provides-Extra: dev
30
30
  Requires-Dist: pytest==9.1.1; extra == "dev"
31
31
  Requires-Dist: pytest-cov==7.1.0; extra == "dev"
32
- Requires-Dist: ruff==0.16.2; extra == "dev"
32
+ Requires-Dist: ruff==0.16.9; extra == "dev"
33
33
  Requires-Dist: mypy>=1.11; extra == "dev"
34
- Requires-Dist: build==1.5.0; extra == "dev"
34
+ Requires-Dist: build==1.6.1; extra == "dev"
35
35
  Requires-Dist: twine==7.0.0; extra == "dev"
36
36
  Provides-Extra: lookup
37
37
  Requires-Dist: requests>=2.20.0; extra == "lookup"
@@ -39,8 +39,8 @@ Provides-Extra: docs
39
39
  Requires-Dist: mkdocs==1.6.1; extra == "docs"
40
40
  Requires-Dist: mkdocs-material==9.7.7; extra == "docs"
41
41
  Requires-Dist: mkdocstrings==1.0.6; extra == "docs"
42
- Requires-Dist: mkdocstrings-python==2.0.5; extra == "docs"
43
- Requires-Dist: griffe==2.1.0; extra == "docs"
42
+ Requires-Dist: mkdocstrings-python==2.0.9; extra == "docs"
43
+ Requires-Dist: griffe==2.3.0; extra == "docs"
44
44
  Dynamic: license-file
45
45
 
46
46
  <p align="center"><img src="assets/logo.png" width="300"></p>
@@ -317,6 +317,7 @@ tests/test_public_interface.py
317
317
  tests/test_publish_workflow.py
318
318
  tests/test_quic_coalesced_datagram_ruling.py
319
319
  tests/test_quic_crypto_buffer_bound.py
320
+ tests/test_quic_crypto_gap.py
320
321
  tests/test_quic_integration.py
321
322
  tests/test_quic_multipacket.py
322
323
  tests/test_quic_server_initial.py
@@ -4,17 +4,17 @@ cryptography>=42.0.0
4
4
  [dev]
5
5
  pytest==9.1.1
6
6
  pytest-cov==7.1.0
7
- ruff==0.16.2
7
+ ruff==0.16.9
8
8
  mypy>=1.11
9
- build==1.5.0
9
+ build==1.6.1
10
10
  twine==7.0.0
11
11
 
12
12
  [docs]
13
13
  mkdocs==1.6.1
14
14
  mkdocs-material==9.7.7
15
15
  mkdocstrings==1.0.6
16
- mkdocstrings-python==2.0.5
17
- griffe==2.1.0
16
+ mkdocstrings-python==2.0.9
17
+ griffe==2.3.0
18
18
 
19
19
  [lookup]
20
20
  requests>=2.20.0
@@ -96,7 +96,7 @@ dev = [
96
96
  # file under `.github/workflows/` and every second dependency record this repository
97
97
  # could carry. The `dev` extra therefore stays the one place a tool reads the version
98
98
  # from.
99
- "ruff==0.16.2",
99
+ "ruff==0.16.9",
100
100
  # **The version floats, and #446 chose that shape against the four exact pins beside
101
101
  # it.** A pinned type checker falls behind, and a frozen release hides a defect that a
102
102
  # later release reports. A floating formatter turns a green tree red, which is noisy
@@ -122,7 +122,7 @@ dev = [
122
122
  # `python -m tests.release_verification --dist dist` reads the wheel this release
123
123
  # builds, and #576 ran it by hand because no continuous-integration job runs it.
124
124
  # Verified against https://pypi.org/pypi/build/1.5.0/json, retrieved 2026-08-10.
125
- "build==1.5.0",
125
+ "build==1.6.1",
126
126
  # `FR-release-9` states `twine check` on the built files, and
127
127
  # `tests/release_verification.py` runs it. The publish workflow installs this extra, so
128
128
  # the version lives here and no workflow states one of its own.
@@ -182,8 +182,8 @@ docs = [
182
182
  "mkdocs==1.6.1",
183
183
  "mkdocs-material==9.7.7",
184
184
  "mkdocstrings==1.0.6",
185
- "mkdocstrings-python==2.0.5",
186
- "griffe==2.1.0",
185
+ "mkdocstrings-python==2.0.9",
186
+ "griffe==2.3.0",
187
187
  ]
188
188
 
189
189
  [project.urls]
@@ -504,34 +504,45 @@ def _dependency_block(text: str, opener: str) -> list[str]:
504
504
  return dependency_entries(text, opener)
505
505
 
506
506
 
507
- # The entries of the `dev` extra, in file order. **Every one of them carries a comment
507
+ def _distribution_names(entries: list[str]) -> list[str]:
508
+ """Return the distribution name of each entry, without its version specifier.
509
+
510
+ The cases below read which entries the reader returns, and no version. A literal
511
+ that named each version failed every dependabot pull request that moved a pin. #760
512
+ and the pull requests #764, #766 and #767 measured that failure.
513
+ """
514
+ return [re.split(r"[=<>!~ ]", entry, maxsplit=1)[0] for entry in entries]
515
+
516
+
517
+ # The distribution names of the `dev` extra, in file order. **Every entry carries a comment
508
518
  # above it, and that is the shape no caller of this reader read before #452.** The runtime
509
519
  # block and the `docs` extra carry no comment inside their brackets. A reader that collects
510
520
  # a comment therefore stays correct against those two blocks, and it fails here.
511
521
  DEV_ENTRIES = [
512
- "pytest==9.1.1",
513
- "pytest-cov==7.1.0",
514
- "ruff==0.16.2",
515
- "mypy>=1.11",
516
- "build==1.5.0",
517
- "twine==7.0.0",
522
+ "pytest",
523
+ "pytest-cov",
524
+ "ruff",
525
+ "mypy",
526
+ "build",
527
+ "twine",
518
528
  ]
519
529
 
520
- # The entries of the runtime block, in file order. A user who installs `ja4plus` installs
521
- # these two distributions and no other.
530
+ # The distribution names of the runtime block, in file order. A user who installs
531
+ # `ja4plus` installs these two distributions and no other.
522
532
  RUNTIME_ENTRIES = [
523
- "scapy>=2.4.0",
524
- "cryptography>=42.0.0",
533
+ "scapy",
534
+ "cryptography",
525
535
  ]
526
536
 
527
- # The entries of the `docs` extra, in file order. #391 records the pair of versions that
528
- # the site build needs, so a case names every entry rather than the two it reads.
537
+ # The distribution names of the `docs` extra, in file order. #391 records the pair of
538
+ # versions that the site build needs, so a case names every entry rather than the two it
539
+ # reads.
529
540
  DOCS_ENTRIES = [
530
- "mkdocs==1.6.1",
531
- "mkdocs-material==9.7.7",
532
- "mkdocstrings==1.0.6",
533
- "mkdocstrings-python==2.0.5",
534
- "griffe==2.1.0",
541
+ "mkdocs",
542
+ "mkdocs-material",
543
+ "mkdocstrings",
544
+ "mkdocstrings-python",
545
+ "griffe",
535
546
  ]
536
547
 
537
548
 
@@ -544,7 +555,7 @@ def test_the_reader_returns_the_entries_of_the_dev_extra_and_no_comment_fragment
544
555
  defect.
545
556
  """
546
557
  entries = _dependency_block(PYPROJECT.read_text(encoding="utf-8"), "dev = [")
547
- assert entries == DEV_ENTRIES
558
+ assert _distribution_names(entries) == DEV_ENTRIES
548
559
 
549
560
 
550
561
  def test_the_reader_returns_every_entry_of_the_runtime_block_by_name() -> None:
@@ -554,13 +565,13 @@ def test_the_reader_returns_every_entry_of_the_runtime_block_by_name() -> None:
554
565
  fragment. This case therefore names every entry the block holds.
555
566
  """
556
567
  entries = _dependency_block(PYPROJECT.read_text(encoding="utf-8"), "dependencies = [")
557
- assert entries == RUNTIME_ENTRIES
568
+ assert _distribution_names(entries) == RUNTIME_ENTRIES
558
569
 
559
570
 
560
571
  def test_the_reader_returns_every_entry_of_the_docs_extra_by_name() -> None:
561
572
  """`_dependency_block` returns the documentation dependencies and drops none of them."""
562
573
  entries = _dependency_block(PYPROJECT.read_text(encoding="utf-8"), "docs = [")
563
- assert entries == DOCS_ENTRIES
574
+ assert _distribution_names(entries) == DOCS_ENTRIES
564
575
 
565
576
 
566
577
  def test_the_documentation_dependency_stays_out_of_the_runtime_dependencies() -> None:
@@ -52,8 +52,15 @@ def test_the_reassembler_keeps_the_real_fragment_beside_a_hostile_one():
52
52
 
53
53
 
54
54
  def test_the_reassembler_allocates_no_more_than_the_limit():
55
- """The buffer never passes `MAXIMUM_CRYPTO_BUFFER_BYTES`."""
56
- fragments = [(MAXIMUM_CRYPTO_BUFFER_BYTES - 1, b"x")]
55
+ """The buffer never passes `MAXIMUM_CRYPTO_BUFFER_BYTES`.
56
+
57
+ The reader returns no byte past a gap, so the fragments cover every offset. #762
58
+ records that change.
59
+ """
60
+ fragments = [
61
+ (0, bytes(MAXIMUM_CRYPTO_BUFFER_BYTES - 1)),
62
+ (MAXIMUM_CRYPTO_BUFFER_BYTES - 1, b"x"),
63
+ ]
57
64
  assert len(reassemble_crypto_fragments(fragments)) == MAXIMUM_CRYPTO_BUFFER_BYTES
58
65
 
59
66
 
@@ -0,0 +1,132 @@
1
+ """A gap in the QUIC CRYPTO stream stops the reassembler.
2
+
3
+ #762 reports the defect. The reassembler allocated a buffer up to the highest fragment
4
+ end and left each unreceived range as zero bytes. A ClientHello whose last fragment
5
+ arrived before a middle fragment then passed the length check, and the TLS reader parsed
6
+ the zero bytes as extensions. The reader produced a JA4 value that no reference holds.
7
+
8
+ The cases below replay the fragment layout of #762 against the ClientHello of the FoxIO
9
+ capture `quic-with-several-tls-frames.pcapng`. That ClientHello holds 295 bytes, so the
10
+ layout keeps the shape of the report and scales the offsets.
11
+ """
12
+
13
+ from pathlib import Path
14
+
15
+ from scapy.all import IP, UDP, Raw, rdpcap
16
+
17
+ from ja4plus.fingerprinters.ja4 import JA4Fingerprinter
18
+ from ja4plus.utils import quic_utils
19
+ from ja4plus.utils.quic_utils import (
20
+ client_hello_from_crypto_fragments,
21
+ decrypt_quic_initial_crypto,
22
+ reassemble_crypto_fragments,
23
+ server_hello_is_complete,
24
+ )
25
+
26
+ VECTORS_DIR = Path(__file__).parent / "foxio_vectors"
27
+ CAPTURE_PATH = VECTORS_DIR / "quic-with-several-tls-frames.pcapng"
28
+ EXPECTED_PATH = (
29
+ VECTORS_DIR / "rust_expected" / "ja4__insta@quic-with-several-tls-frames.pcapng.snap"
30
+ )
31
+
32
+
33
+ def _client_hello_bytes():
34
+ """Return the whole ClientHello that the one Initial packet of the capture carries."""
35
+ for packet in rdpcap(str(CAPTURE_PATH)):
36
+ if UDP not in packet:
37
+ continue
38
+ fragments, _ = decrypt_quic_initial_crypto(bytes(packet[UDP].payload))
39
+ if fragments:
40
+ return reassemble_crypto_fragments(fragments)
41
+ raise AssertionError(f"{CAPTURE_PATH} holds no QUIC client Initial packet")
42
+
43
+
44
+ def _reference_ja4():
45
+ """Return the one JA4 value the FoxIO snapshot holds for the capture."""
46
+ values = [
47
+ line.strip()[len("ja4: ") :]
48
+ for line in EXPECTED_PATH.read_text().splitlines()
49
+ if line.strip().startswith("ja4: ")
50
+ ]
51
+ assert len(values) == 1, f"{EXPECTED_PATH} holds {len(values)} JA4 values"
52
+ return values[0]
53
+
54
+
55
+ def _split(data, bounds):
56
+ """Return the (offset, bytes) pair of each half-open range in `bounds`."""
57
+ return [(start, data[start:end]) for start, end in bounds]
58
+
59
+
60
+ def _issue_layout(client_hello):
61
+ """Return the fragments of the two datagrams, in the shape #762 reports.
62
+
63
+ The first datagram carries the head and the tail, and the second one carries the
64
+ middle. The first datagram alone therefore reaches the last byte and leaves a gap.
65
+ """
66
+ end = len(client_hello)
67
+ first = _split(client_hello, [(0, 36), (36, 70), (200, end)])
68
+ second = _split(client_hello, [(70, 115), (115, 200)])
69
+ return first, second
70
+
71
+
72
+ def test_the_reassembler_stops_at_the_first_gap():
73
+ assert reassemble_crypto_fragments([(0, b"ab"), (5, b"xy")]) == b"ab"
74
+
75
+
76
+ def test_the_reassembler_returns_nothing_when_no_fragment_starts_at_offset_zero():
77
+ assert reassemble_crypto_fragments([(3, b"xyz")]) == b""
78
+
79
+
80
+ def test_the_reassembler_joins_overlapping_fragments():
81
+ assert reassemble_crypto_fragments([(0, b"hello"), (3, b"lo world")]) == b"hello world"
82
+
83
+
84
+ def test_the_reassembler_keeps_a_fragment_that_a_longer_one_covers():
85
+ fragments = [(0, b"hello world"), (6, b"wor")]
86
+ assert reassemble_crypto_fragments(fragments) == b"hello world"
87
+
88
+
89
+ def test_a_client_hello_with_a_gap_produces_no_parse():
90
+ client_hello = _client_hello_bytes()
91
+ first, _ = _issue_layout(client_hello)
92
+ assert client_hello_from_crypto_fragments(first) is None
93
+
94
+
95
+ def test_the_client_hello_parses_once_the_gap_fills():
96
+ client_hello = _client_hello_bytes()
97
+ first, second = _issue_layout(client_hello)
98
+ whole = client_hello_from_crypto_fragments([(0, client_hello)])
99
+ assert whole is not None
100
+ assert client_hello_from_crypto_fragments(first + second) == whole
101
+
102
+
103
+ def test_a_server_hello_with_a_gap_is_not_complete():
104
+ server_hello = bytes([0x02, 0x00, 0x00, 0x06]) + b"abcdef"
105
+ assert not server_hello_is_complete([(0, server_hello[:4]), (8, server_hello[8:])])
106
+ assert server_hello_is_complete([(0, server_hello[:4]), (4, server_hello[4:])])
107
+
108
+
109
+ def _quic_packet():
110
+ """Return one UDP datagram whose payload starts a QUIC long header."""
111
+ return (
112
+ IP(src="1.1.1.1", dst="2.2.2.2")
113
+ / UDP(sport=50000, dport=443)
114
+ / Raw(load=b"\x80" + b"\x00" * 30)
115
+ )
116
+
117
+
118
+ def test_the_fingerprinter_waits_for_the_gap_and_then_produces_the_reference_ja4(
119
+ monkeypatch,
120
+ ):
121
+ """The first datagram produces nothing, and the second one produces the FoxIO value.
122
+
123
+ The `monkeypatch` fixture restores the decryption function on teardown.
124
+ """
125
+ first, second = _issue_layout(_client_hello_bytes())
126
+ dcid = b"\xaa\xbb\xcc\xdd"
127
+ datagrams = iter([(first, dcid), (second, dcid)])
128
+ monkeypatch.setattr(quic_utils, "decrypt_quic_initial_crypto", lambda _: next(datagrams))
129
+
130
+ fingerprinter = JA4Fingerprinter()
131
+ assert fingerprinter.process_packet(_quic_packet()) is None
132
+ assert fingerprinter.process_packet(_quic_packet()) == _reference_ja4()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes