ja4plus 1.2.2__tar.gz → 1.4.0__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 (526) hide show
  1. {ja4plus-1.2.2 → ja4plus-1.4.0}/.claude/rules/conformance.md +4 -3
  2. {ja4plus-1.2.2 → ja4plus-1.4.0}/.claude/rules/external-apis.md +18 -11
  3. {ja4plus-1.2.2 → ja4plus-1.4.0}/CHANGELOG.md +116 -0
  4. {ja4plus-1.2.2 → ja4plus-1.4.0}/CLAUDE.md +1 -1
  5. {ja4plus-1.2.2 → ja4plus-1.4.0}/PKG-INFO +17 -12
  6. {ja4plus-1.2.2 → ja4plus-1.4.0}/README.md +15 -11
  7. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/api_reference.md +63 -8
  8. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/implementation_notes.md +60 -15
  9. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/methods/index.md +20 -9
  10. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/methods/ja4.md +4 -4
  11. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/methods/ja4s.md +1 -1
  12. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/methods/ja4t.md +7 -3
  13. ja4plus-1.4.0/docs/methods/ja4tscan.md +141 -0
  14. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/migration-0.6-to-1.0.md +17 -13
  15. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/output-schema.md +12 -5
  16. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/performance.md +6 -1
  17. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/specs/features/00-foundation.md +3 -2
  18. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/specs/features/01-spec-conformance.md +15 -5
  19. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/specs/features/03-concurrency-safety.md +14 -12
  20. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/specs/features/08-documentation.md +8 -4
  21. ja4plus-1.4.0/docs/specs/features/12-active-scan.md +406 -0
  22. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/specs/foxio/JA4.md +37 -15
  23. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/specs/foxio/JA4L.md +6 -6
  24. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/specs/foxio/JA4S.md +17 -10
  25. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/specs/foxio/JA4SSH.md +1 -1
  26. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/specs/foxio/JA4T.md +28 -3
  27. ja4plus-1.4.0/docs/specs/foxio/JA4TScan.md +408 -0
  28. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/specs/foxio/JA4X.md +2 -2
  29. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/specs/foxio/README.md +11 -4
  30. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/specs/foxio/deleted-text-specifications.md +1 -1
  31. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/specs/foxio/zeek.md +11 -3
  32. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/specs/spec.html +9 -4
  33. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/specs/spec.md +66 -23
  34. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/usage.md +28 -1
  35. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/__init__.py +1 -1
  36. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/cli.py +128 -0
  37. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/fingerprinters/ja4.py +179 -45
  38. ja4plus-1.4.0/ja4plus/scan/__init__.py +11 -0
  39. ja4plus-1.4.0/ja4plus/scan/command.py +192 -0
  40. ja4plus-1.4.0/ja4plus/scan/frames.py +198 -0
  41. ja4plus-1.4.0/ja4plus/scan/link.py +183 -0
  42. ja4plus-1.4.0/ja4plus/scan/scanner.py +404 -0
  43. ja4plus-1.4.0/ja4plus/scan/value.py +83 -0
  44. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/types.py +3 -2
  45. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/utils/tcp_options.py +29 -12
  46. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/utils/tls_utils.py +40 -0
  47. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus.egg-info/PKG-INFO +17 -12
  48. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus.egg-info/SOURCES.txt +23 -0
  49. ja4plus-1.4.0/ja4plus.egg-info/entry_points.txt +5 -0
  50. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus.egg-info/requires.txt +2 -0
  51. {ja4plus-1.2.2 → ja4plus-1.4.0}/mkdocs.yml +3 -2
  52. {ja4plus-1.2.2 → ja4plus-1.4.0}/pyproject.toml +13 -0
  53. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/build_alpn_condition_capture.py +7 -5
  54. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/download_test_vectors.py +31 -6
  55. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_citation_lines.py +11 -1
  56. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_deviation_owners.json +5 -0
  57. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_deviations.json +32 -14
  58. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_deviations.py +1 -1
  59. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vector_manifest.json +1 -0
  60. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/NOTICE +18 -7
  61. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/rust_expected/ja4__insta@chrome-cloudflare-quic-with-secrets.pcapng.snap +1 -2
  62. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/rust_expected/ja4__insta@gre-erspan-vxlan.pcap.snap +1 -2
  63. ja4plus-1.4.0/tests/foxio_vectors/rust_expected/ja4__insta@sigalg-grease.pcapng.snap +16 -0
  64. ja4plus-1.4.0/tests/foxio_vectors/sigalg-grease.pcapng +0 -0
  65. ja4plus-1.4.0/tests/foxio_vectors/sigalg-grease.pcapng.json +20 -0
  66. ja4plus-1.4.0/tests/scan_fakes.py +95 -0
  67. ja4plus-1.4.0/tests/test_alpn_end_byte_ruling.py +120 -0
  68. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_alpn_ruling_register.py +52 -19
  69. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_conformance_frame_discrimination.py +4 -4
  70. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_documentation_image_count.py +4 -3
  71. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_documented_method_count.py +71 -9
  72. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_documented_state_table_count.py +1 -1
  73. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_download_filter_fields.py +3 -3
  74. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_empty_list_sentinel_ruling.py +4 -4
  75. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_foxio_rust_parity.py +29 -29
  76. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4_alpn.py +13 -8
  77. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4_alpn_condition.py +75 -27
  78. ja4plus-1.4.0/tests/test_ja4_signature_algorithm_grease.py +127 -0
  79. ja4plus-1.4.0/tests/test_ja4_tcp_client_hello_reassembly.py +328 -0
  80. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4s_raw_order.py +4 -4
  81. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4t_form.py +48 -17
  82. ja4plus-1.4.0/tests/test_ja4tscan_boundary.py +245 -0
  83. ja4plus-1.4.0/tests/test_ja4tscan_command.py +476 -0
  84. ja4plus-1.4.0/tests/test_ja4tscan_frames.py +196 -0
  85. ja4plus-1.4.0/tests/test_ja4tscan_link.py +254 -0
  86. ja4plus-1.4.0/tests/test_ja4tscan_scanner.py +390 -0
  87. ja4plus-1.4.0/tests/test_ja4tscan_specification.py +119 -0
  88. ja4plus-1.4.0/tests/test_ja4tscan_value.py +227 -0
  89. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_key_log_interface_ruling.py +1 -1
  90. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_memory_bounds.py +10 -9
  91. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_method_pages.py +45 -35
  92. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_precedence_exception.py +54 -29
  93. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_processor_stats.py +4 -4
  94. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_readme_contracts.py +16 -15
  95. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_tcp_segment_bound_ruling.py +2 -2
  96. ja4plus-1.2.2/ja4plus.egg-info/entry_points.txt +0 -2
  97. {ja4plus-1.2.2 → ja4plus-1.4.0}/.claude/rules/agent-reports.md +0 -0
  98. {ja4plus-1.2.2 → ja4plus-1.4.0}/.claude/rules/batch-gate.md +0 -0
  99. {ja4plus-1.2.2 → ja4plus-1.4.0}/.claude/rules/ste.md +0 -0
  100. {ja4plus-1.2.2 → ja4plus-1.4.0}/.claude/settings.json +0 -0
  101. {ja4plus-1.2.2 → ja4plus-1.4.0}/.claude/skills/conformance/SKILL.md +0 -0
  102. {ja4plus-1.2.2 → ja4plus-1.4.0}/.claude/skills/release/SKILL.md +0 -0
  103. {ja4plus-1.2.2 → ja4plus-1.4.0}/.claude/skills/test/SKILL.md +0 -0
  104. {ja4plus-1.2.2 → ja4plus-1.4.0}/.git-blame-ignore-revs +0 -0
  105. {ja4plus-1.2.2 → ja4plus-1.4.0}/.github/dependabot.yml +0 -0
  106. {ja4plus-1.2.2 → ja4plus-1.4.0}/.github/workflows/docs-build.yml +0 -0
  107. {ja4plus-1.2.2 → ja4plus-1.4.0}/.github/workflows/docs.yml +0 -0
  108. {ja4plus-1.2.2 → ja4plus-1.4.0}/.github/workflows/publish.yml +0 -0
  109. {ja4plus-1.2.2 → ja4plus-1.4.0}/.github/workflows/test.yml +0 -0
  110. {ja4plus-1.2.2 → ja4plus-1.4.0}/.github/workflows/update-db.yml +0 -0
  111. {ja4plus-1.2.2 → ja4plus-1.4.0}/.gitignore +0 -0
  112. {ja4plus-1.2.2 → ja4plus-1.4.0}/.issue-flow.json +0 -0
  113. {ja4plus-1.2.2 → ja4plus-1.4.0}/LICENSE +0 -0
  114. {ja4plus-1.2.2 → ja4plus-1.4.0}/assets/logo.png +0 -0
  115. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/README.md +0 -0
  116. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/concurrency.md +0 -0
  117. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/methods/ja4d.md +0 -0
  118. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/methods/ja4d6.md +0 -0
  119. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/methods/ja4h.md +0 -0
  120. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/methods/ja4l.md +0 -0
  121. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/methods/ja4ls.md +0 -0
  122. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/methods/ja4ssh.md +0 -0
  123. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/methods/ja4ts.md +0 -0
  124. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/methods/ja4x.md +0 -0
  125. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/mutation_reports/412-utils.json +0 -0
  126. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/mutation_reports/413-fingerprinters.json +0 -0
  127. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/mutation_reports/414-interface.json +0 -0
  128. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/mutation_settlements/412-utils.json +0 -0
  129. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/mutation_settlements/413-fingerprinters.json +0 -0
  130. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/mutation_settlements/414-interface.json +0 -0
  131. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/mutation_sweep.md +0 -0
  132. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/reference/fingerprinters.md +0 -0
  133. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/reference/index.md +0 -0
  134. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/reference/ja4db.md +0 -0
  135. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/reference/processor.md +0 -0
  136. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/reference/types.md +0 -0
  137. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/specs/features/02-correctness-audit.md +0 -0
  138. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/specs/features/04-typed-api.md +0 -0
  139. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/specs/features/05-structured-output.md +0 -0
  140. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/specs/features/06-live-capture.md +0 -0
  141. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/specs/features/07-db-enrichment.md +0 -0
  142. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/specs/features/09-release.md +0 -0
  143. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/specs/features/11-pre-release-validation.md +0 -0
  144. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/specs/foxio/JA4D.md +0 -0
  145. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/specs/foxio/JA4H.md +0 -0
  146. {ja4plus-1.2.2 → ja4plus-1.4.0}/docs/specs/mockups/01-cli-output.html +0 -0
  147. {ja4plus-1.2.2 → ja4plus-1.4.0}/examples/demo_fingerprints.py +0 -0
  148. {ja4plus-1.2.2 → ja4plus-1.4.0}/examples/live_traffic_fingerprinting.py +0 -0
  149. {ja4plus-1.2.2 → ja4plus-1.4.0}/examples/pcap_analysis.py +0 -0
  150. {ja4plus-1.2.2 → ja4plus-1.4.0}/examples/tcpdump_workflows.py +0 -0
  151. {ja4plus-1.2.2 → ja4plus-1.4.0}/examples/threat_detection.py +0 -0
  152. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/data/ja4plus-mapping.csv +0 -0
  153. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/fingerprinters/__init__.py +0 -0
  154. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/fingerprinters/base.py +0 -0
  155. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/fingerprinters/ja4d.py +0 -0
  156. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/fingerprinters/ja4d6.py +0 -0
  157. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/fingerprinters/ja4h.py +0 -0
  158. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/fingerprinters/ja4l.py +0 -0
  159. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/fingerprinters/ja4s.py +0 -0
  160. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/fingerprinters/ja4ssh.py +0 -0
  161. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/fingerprinters/ja4t.py +0 -0
  162. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/fingerprinters/ja4ts.py +0 -0
  163. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/fingerprinters/ja4x.py +0 -0
  164. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/ja4db.py +0 -0
  165. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/output.py +0 -0
  166. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/processor.py +0 -0
  167. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/py.typed +0 -0
  168. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/utils/__init__.py +0 -0
  169. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/utils/http_utils.py +0 -0
  170. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/utils/icmp_quoted.py +0 -0
  171. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/utils/loopback.py +0 -0
  172. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/utils/packet_utils.py +0 -0
  173. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/utils/quic_utils.py +0 -0
  174. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/utils/ssh_utils.py +0 -0
  175. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/utils/state_table.py +0 -0
  176. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/utils/tcp_stream.py +0 -0
  177. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/utils/tunnels.py +0 -0
  178. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/utils/x509_utils.py +0 -0
  179. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus/watch.py +0 -0
  180. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus.egg-info/dependency_links.txt +0 -0
  181. {ja4plus-1.2.2 → ja4plus-1.4.0}/ja4plus.egg-info/top_level.txt +0 -0
  182. {ja4plus-1.2.2 → ja4plus-1.4.0}/setup.cfg +0 -0
  183. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/__init__.py +0 -0
  184. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/batch_gate.py +0 -0
  185. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/build_ja4ts_rst.py +0 -0
  186. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/build_quic_ja4l_captures.py +0 -0
  187. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/build_ssh_retransmission.py +0 -0
  188. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/citation_line_allowlist.json +0 -0
  189. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/compare_zeek_baselines.py +0 -0
  190. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/conformance_index.py +0 -0
  191. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/conftest.py +0 -0
  192. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/data/dns.cap +0 -0
  193. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/data/example_cert.der +0 -0
  194. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/data/filedownload.dmp +0 -0
  195. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/data/http.cap +0 -0
  196. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/data/suspectemployeechat.dmp +0 -0
  197. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/data/test_cert.der +0 -0
  198. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/data/wrongdissector.dmp +0 -0
  199. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/dependency_entries.py +0 -0
  200. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/documentation_samples.py +0 -0
  201. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_manifest.py +0 -0
  202. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/CVE-2018-6794.pcap +0 -0
  203. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/CVE-2018-6794.pcap.json +0 -0
  204. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/alpn-condition.pcap +0 -0
  205. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/alpn-condition.pcap.json +0 -0
  206. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/badcurveball.pcap +0 -0
  207. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/badcurveball.pcap.json +0 -0
  208. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/browsers-x509.pcapng +0 -0
  209. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/browsers-x509.pcapng.json +0 -0
  210. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/chrome-cloudflare-quic-with-secrets.pcapng +0 -0
  211. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/chrome-cloudflare-quic-with-secrets.pcapng.json +0 -0
  212. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/dhcp.pcapng +0 -0
  213. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/dhcp.pcapng.json +0 -0
  214. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/dhcpv6.pcap +0 -0
  215. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/dhcpv6.pcap.json +0 -0
  216. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/gre-erspan-vxlan.pcap +0 -0
  217. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/gre-erspan-vxlan.pcap.json +0 -0
  218. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/gre-sample.pcap +0 -0
  219. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/gre-sample.pcap.json +0 -0
  220. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/http-empty-useragent.pcap +0 -0
  221. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/http-empty-useragent.pcap.json +0 -0
  222. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/http1-with-cookies.pcapng +0 -0
  223. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/http1-with-cookies.pcapng.json +0 -0
  224. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/http1.pcapng +0 -0
  225. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/http1.pcapng.json +0 -0
  226. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/http2-with-cookies.pcapng +0 -0
  227. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/http2-with-cookies.pcapng.json +0 -0
  228. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/https-connect.pcap +0 -0
  229. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/https-connect.pcap.json +0 -0
  230. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/https3-301-get.pcap +0 -0
  231. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/https3-301-get.pcap.json +0 -0
  232. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/ipv6.pcapng +0 -0
  233. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/ipv6.pcapng.json +0 -0
  234. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/latest.pcapng +0 -0
  235. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/latest.pcapng.json +0 -0
  236. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/macos_tcp_flags.pcap +0 -0
  237. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/macos_tcp_flags.pcap.json +0 -0
  238. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/quic-tls-handshake.pcapng +0 -0
  239. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/quic-tls-handshake.pcapng.json +0 -0
  240. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/quic-with-several-tls-frames.pcapng +0 -0
  241. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/quic-with-several-tls-frames.pcapng.json +0 -0
  242. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/rust_expected/ja4__insta@browsers-x509.pcapng.snap +0 -0
  243. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/rust_expected/ja4__insta@https-connect.pcap.snap +0 -0
  244. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/rust_expected/ja4__insta@latest.pcapng.snap +0 -0
  245. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/rust_expected/ja4__insta@quic-tls-handshake.pcapng.snap +0 -0
  246. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/rust_expected/ja4__insta@quic-with-several-tls-frames.pcapng.snap +0 -0
  247. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/rust_expected/ja4__insta@ssh2.pcapng.snap +0 -0
  248. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/rust_expected/ja4__insta@tls-handshake.pcapng.snap +0 -0
  249. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/rust_expected/ja4__insta@tls-sni.pcapng.snap +0 -0
  250. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/rust_expected/ja4__insta@tls3.pcapng.snap +0 -0
  251. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/single-packets.pcap +0 -0
  252. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/single-packets.pcap.json +0 -0
  253. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/socks-https-example.pcap +0 -0
  254. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/socks-https-example.pcap.json +0 -0
  255. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/socks4-https.pcap +0 -0
  256. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/socks4-https.pcap.json +0 -0
  257. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/ssh-r.pcap +0 -0
  258. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/ssh-r.pcap.json +0 -0
  259. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/ssh-scp-1050.pcap +0 -0
  260. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/ssh-scp-1050.pcap.json +0 -0
  261. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/ssh.pcapng +0 -0
  262. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/ssh.pcapng.json +0 -0
  263. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/ssh2-malformed.pcap +0 -0
  264. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/ssh2-malformed.pcap.json +0 -0
  265. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/ssh2-moloch-crash.pcap +0 -0
  266. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/ssh2-moloch-crash.pcap.json +0 -0
  267. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/ssh2.pcapng +0 -0
  268. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/ssh2.pcapng.json +0 -0
  269. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/sshv1.pcap +0 -0
  270. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/sshv1.pcap.json +0 -0
  271. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/tcpdump-geneve.pcap +0 -0
  272. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/tcpdump-geneve.pcap.json +0 -0
  273. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/tls-alpn-h2.pcap +0 -0
  274. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/tls-alpn-h2.pcap.json +0 -0
  275. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/tls-handshake.pcapng +0 -0
  276. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/tls-handshake.pcapng.json +0 -0
  277. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/tls-non-ascii-alpn.pcapng +0 -0
  278. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/tls-non-ascii-alpn.pcapng.json +0 -0
  279. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/tls-sni.pcapng +0 -0
  280. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/tls-sni.pcapng.json +0 -0
  281. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/tls12.pcap +0 -0
  282. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/tls12.pcap.json +0 -0
  283. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/tls3.pcapng +0 -0
  284. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/tls3.pcapng.json +0 -0
  285. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/v6.pcap +0 -0
  286. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/v6.pcap.json +0 -0
  287. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/CVE-2018-6794.pcap.json +0 -0
  288. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/badcurveball.pcap.json +0 -0
  289. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/browsers-x509.pcapng.json +0 -0
  290. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/chrome-cloudflare-quic-with-secrets.pcapng.json +0 -0
  291. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/dhcp.pcapng.json +0 -0
  292. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/dhcpv6.pcap.json +0 -0
  293. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/gre-erspan-vxlan.pcap.json +0 -0
  294. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/http-empty-useragent.pcap.json +0 -0
  295. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/http1-with-cookies.pcapng.json +0 -0
  296. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/http2-with-cookies.pcapng.json +0 -0
  297. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/https-connect.pcap.json +0 -0
  298. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/https3-301-get.pcap.json +0 -0
  299. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/ipv6.pcapng.json +0 -0
  300. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/latest.pcapng.json +0 -0
  301. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/socks-https-example.pcap.json +0 -0
  302. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/socks4-https.pcap.json +0 -0
  303. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/ssh-r.pcap.json +0 -0
  304. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/ssh-scp-1050.pcap.json +0 -0
  305. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/ssh2-malformed.pcap.json +0 -0
  306. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/ssh2-moloch-crash.pcap.json +0 -0
  307. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/ssh2.pcapng.json +0 -0
  308. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/sshv1.pcap.json +0 -0
  309. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/tcpdump-geneve.pcap.json +0 -0
  310. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/tls-alpn-h2.pcap.json +0 -0
  311. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/tls3.pcapng.json +0 -0
  312. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/wireshark_expected/v6.pcap.json +0 -0
  313. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/zeek_expected/Scripts.ja4-conn/conn.log +0 -0
  314. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/zeek_expected/Scripts.ja4-conn-quic/conn.log +0 -0
  315. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/zeek_expected/Scripts.ja4-conn-tls3/conn.log +0 -0
  316. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/zeek_expected/Scripts.ja4-dhcp/ja4d.log +0 -0
  317. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/zeek_expected/Scripts.ja4-http1-with-cookies/http.log +0 -0
  318. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/zeek_expected/Scripts.ja4-ssh2/ja4ssh.log +0 -0
  319. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/foxio_vectors/zeek_expected/Scripts.ja4-tls-handshake/ssl.log +0 -0
  320. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/fuzz/README.md +0 -0
  321. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/fuzz/__init__.py +0 -0
  322. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/fuzz/conftest.py +0 -0
  323. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/fuzz/support.py +0 -0
  324. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/fuzz/test_icmp_quoted_header.py +0 -0
  325. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/fuzz/test_length_fields.py +0 -0
  326. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/fuzz/test_malformed_captures.py +0 -0
  327. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/fuzz/test_server_hello_extensions.py +0 -0
  328. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/fuzz/test_structural_validity.py +0 -0
  329. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/fuzz/test_synthetic_hostile_input.py +0 -0
  330. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/fuzz/test_truncated_captures.py +0 -0
  331. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/generate_foxio_baseline.py +0 -0
  332. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/measure_random_client_hello.py +0 -0
  333. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/memory_ceiling_run.py +0 -0
  334. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/mutation_census.py +0 -0
  335. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/mutation_cover.py +0 -0
  336. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/mutation_sweep.py +0 -0
  337. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/quic_builder.py +0 -0
  338. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/release_body.py +0 -0
  339. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/release_verification.py +0 -0
  340. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/skip_gate.py +0 -0
  341. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/state_readers.py +0 -0
  342. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_agent_report_absence_rule.py +0 -0
  343. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_api_reference_interface_table.py +0 -0
  344. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_bare_exception_reading.py +0 -0
  345. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_batch_gate.py +0 -0
  346. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_batch_gate_protection_rule.py +0 -0
  347. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_bounded_state_table_adoption.py +0 -0
  348. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_breaking_change_record.py +0 -0
  349. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_changelog_round_agreement.py +0 -0
  350. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_changelog_sentence_exemption.py +0 -0
  351. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_cleanup_connection.py +0 -0
  352. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_cli.py +0 -0
  353. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_cli_closed_window_endpoints.py +0 -0
  354. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_cli_lookup_opt_in.py +0 -0
  355. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_cli_output_option.py +0 -0
  356. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_cli_processor.py +0 -0
  357. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_cli_types_ja4ls.py +0 -0
  358. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_compare_zeek_baselines.py +0 -0
  359. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_comprehensive.py +0 -0
  360. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_criterion_counts.py +0 -0
  361. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_db_cache_directory.py +0 -0
  362. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_db_item_access.py +0 -0
  363. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_db_lookup_many.py +0 -0
  364. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_db_offline.py +0 -0
  365. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_decryption_non_goal_ruling.py +0 -0
  366. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_delegated_ruling_rule.py +0 -0
  367. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_dependency_entries.py +0 -0
  368. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_dhcp_tunnel_inner_udp.py +0 -0
  369. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_documentation_behaviour_rule.py +0 -0
  370. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_documentation_fallback_rule.py +0 -0
  371. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_documentation_publish.py +0 -0
  372. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_documentation_samples.py +0 -0
  373. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_documentation_site.py +0 -0
  374. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_documentation_version_claims.py +0 -0
  375. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_edge_cases.py +0 -0
  376. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_examples.py +0 -0
  377. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_foxio_citation_lines.py +0 -0
  378. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_foxio_deviations.py +0 -0
  379. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_foxio_ja4_transcription.py +0 -0
  380. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_foxio_license_register.py +0 -0
  381. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_foxio_manifest.py +0 -0
  382. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_foxio_wireshark_ja4ts.py +0 -0
  383. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_foxio_zeek_ja4ts.py +0 -0
  384. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_future_dated_packet_eviction.py +0 -0
  385. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_generate_foxio_baseline.py +0 -0
  386. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_get_stream_byte_cap_truncation.py +0 -0
  387. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_http_header_block_terminator.py +0 -0
  388. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_installed_wheel.py +0 -0
  389. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_installed_wheel_selection.py +0 -0
  390. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_integration.py +0 -0
  391. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_interpreter_set.py +0 -0
  392. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ipv6_support.py +0 -0
  393. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4.py +0 -0
  394. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4_deep.py +0 -0
  395. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4_empty_ext.py +0 -0
  396. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4_hello_retry.py +0 -0
  397. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4_original_order.py +0 -0
  398. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4_raw_branches.py +0 -0
  399. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4_ssl2_version.py +0 -0
  400. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4d.py +0 -0
  401. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4d6_foxio.py +0 -0
  402. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4d_decisions.py +0 -0
  403. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4d_foxio.py +0 -0
  404. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4d_fqdn_name_ruling.py +0 -0
  405. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4db.py +0 -0
  406. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4db_empty_list_alias.py +0 -0
  407. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4h.py +0 -0
  408. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4h_cookie_list.py +0 -0
  409. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4h_deep.py +0 -0
  410. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4h_emission_frame_ruling.py +0 -0
  411. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4h_large_cookie_header.py +0 -0
  412. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4h_line_endings.py +0 -0
  413. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4h_part_a_readings.py +0 -0
  414. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4h_raw.py +0 -0
  415. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4h_raw_sorted.py +0 -0
  416. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4h_reassembly.py +0 -0
  417. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4h_request_line_and_empty_header_list.py +0 -0
  418. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4h_retransmission.py +0 -0
  419. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4h_spec.py +0 -0
  420. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4l.py +0 -0
  421. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4l_connection_state.py +0 -0
  422. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4l_foxio.py +0 -0
  423. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4l_header_block_terminator.py +0 -0
  424. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4l_quic_eviction_ruling.py +0 -0
  425. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4l_quic_marker.py +0 -0
  426. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4l_quic_point_b_ruling.py +0 -0
  427. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4l_quic_point_d_emission.py +0 -0
  428. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4l_quic_repeated_connection.py +0 -0
  429. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4l_quic_server_hello.py +0 -0
  430. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4l_return_path.py +0 -0
  431. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4l_timing_fork_ruling.py +0 -0
  432. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4l_udp_direction.py +0 -0
  433. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4s.py +0 -0
  434. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4s_quic_direction.py +0 -0
  435. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4s_quic_split_server_hello.py +0 -0
  436. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4ssh.py +0 -0
  437. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4ssh_close_connection_window.py +0 -0
  438. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4ssh_deep.py +0 -0
  439. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4ssh_direction.py +0 -0
  440. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4ssh_interpret.py +0 -0
  441. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4ssh_message_count.py +0 -0
  442. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4ssh_packet_selection_ruling.py +0 -0
  443. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4ssh_parity_record.py +0 -0
  444. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4ssh_server_side.py +0 -0
  445. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4ssh_spec.py +0 -0
  446. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4ssh_window.py +0 -0
  447. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4ssh_windows.py +0 -0
  448. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4t.py +0 -0
  449. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4t_citation_lines.py +0 -0
  450. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4t_deep.py +0 -0
  451. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4t_emission_model_ruling.py +0 -0
  452. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4t_icmp_quoted.py +0 -0
  453. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4t_syn_selection_ruling.py +0 -0
  454. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4ts.py +0 -0
  455. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4ts_delay_rounding_ruling.py +0 -0
  456. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4ts_part_e.py +0 -0
  457. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4ts_prefix_bound.py +0 -0
  458. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4ts_reset.py +0 -0
  459. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4x.py +0 -0
  460. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4x_deep.py +0 -0
  461. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4x_empty_ext.py +0 -0
  462. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4x_named_exceptions.py +0 -0
  463. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4x_raw.py +0 -0
  464. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4x_reassembly.py +0 -0
  465. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ja4x_record_scan.py +0 -0
  466. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_lint_gate_pin.py +0 -0
  467. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_loopback_link_type.py +0 -0
  468. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_mutation_census.py +0 -0
  469. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_mutation_cover.py +0 -0
  470. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_mutation_sweep_annotation_skip.py +0 -0
  471. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_mutation_sweep_commit.py +0 -0
  472. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_mutation_sweep_module_list.py +0 -0
  473. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_mutation_sweep_timeout.py +0 -0
  474. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_no_retained_packets.py +0 -0
  475. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_output_schema.py +0 -0
  476. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_packaging.py +0 -0
  477. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_packet_utils.py +0 -0
  478. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_parity.py +0 -0
  479. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_parser_description.py +0 -0
  480. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ported_pattern_cost.py +0 -0
  481. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_processor.py +0 -0
  482. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_processor_typed_results.py +0 -0
  483. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_public_interface.py +0 -0
  484. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_publish_workflow.py +0 -0
  485. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_quic_coalesced_datagram_ruling.py +0 -0
  486. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_quic_crypto_buffer_bound.py +0 -0
  487. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_quic_crypto_gap.py +0 -0
  488. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_quic_integration.py +0 -0
  489. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_quic_multipacket.py +0 -0
  490. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_quic_server_initial.py +0 -0
  491. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_quic_tunnel_inner_udp.py +0 -0
  492. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_quic_utils.py +0 -0
  493. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_release_body.py +0 -0
  494. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_release_notes.py +0 -0
  495. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_release_skill.py +0 -0
  496. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_requirement_scope.py +0 -0
  497. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_round_entry_existence.py +0 -0
  498. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_ruling_vocabulary.py +0 -0
  499. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_settlement_procedure.py +0 -0
  500. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_skip_gate.py +0 -0
  501. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_spec_validation.py +0 -0
  502. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_specification_changelog.py +0 -0
  503. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_specification_terms.py +0 -0
  504. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_state_table.py +0 -0
  505. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_statistics_thread_term.py +0 -0
  506. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_tcp_byte_cap_admission_ruling.py +0 -0
  507. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_tcp_stream.py +0 -0
  508. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_thread_safety.py +0 -0
  509. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_throughput.py +0 -0
  510. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_tls_utils.py +0 -0
  511. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_tunnels.py +0 -0
  512. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_types.py +0 -0
  513. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_us_english_spelling.py +0 -0
  514. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_utils.py +0 -0
  515. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_version_gate.py +0 -0
  516. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_watch.py +0 -0
  517. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_watch_capture.py +0 -0
  518. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_watch_drop_count.py +0 -0
  519. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_watch_statistics.py +0 -0
  520. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_watch_stop.py +0 -0
  521. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_wireshark_expected_counts.py +0 -0
  522. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_workflow_action_pins.py +0 -0
  523. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/test_x509_certificate_reader.py +0 -0
  524. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/throughput_run.py +0 -0
  525. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/universal_skips.json +0 -0
  526. {ja4plus-1.2.2 → ja4plus-1.4.0}/tests/version_gate.py +0 -0
@@ -88,11 +88,12 @@ and under the restored defect. These are the seven moves.
88
88
 
89
89
  **The conformance value comparison reads the one source that frames nothing.** A per-frame
90
90
  comparison is possible only where a source states the frame, and #736 read all four.
91
+ #772 added `sigalg-grease.pcapng`, and the first two counts below include it.
91
92
 
92
93
  | Source | States a frame | Values |
93
94
  |---|---|---|
94
- | FoxIO Python expected output, `tests/foxio_vectors/*.json` | No | 1203 |
95
- | FoxIO Rust snapshots, `tests/foxio_vectors/rust_expected/*.snap` | No | 460 |
95
+ | FoxIO Python expected output, `tests/foxio_vectors/*.json` | No | 1211 |
96
+ | FoxIO Rust snapshots, `tests/foxio_vectors/rust_expected/*.snap` | No | 465 |
96
97
  | Zeek baselines, `tests/foxio_vectors/zeek_expected/` | No | 7 baselines |
97
98
  | FoxIO Wireshark dissector, `tests/foxio_vectors/wireshark_expected/*.json` | Yes, `frame.number` | 724 |
98
99
 
@@ -101,7 +102,7 @@ comparison is possible only where a source states the frame, and #736 read all f
101
102
  names one of them. **The dissector frames every value it writes**, and #736 measured 724
102
103
  framed values and none unframed across 26 files.
103
104
 
104
- **The conformance suite builds all 1203 of its value cases from the first source.** No case
105
+ **The conformance suite builds all 1211 of its value cases from the first source.** No case
105
106
  of the suite can read a frame, because the source it reads states none.
106
107
 
107
108
  ### Why the suite compares no frame
@@ -153,10 +153,12 @@ reader checks each one.
153
153
  5. The remaining sources hold one value between them.
154
154
 
155
155
  **Read fact 1 from the vectors, and not from the key form alone.** A value-form key
156
- states that the Python file holds a value, and on one row that statement is false.
157
- `gre-erspan-vxlan.pcap/0:65174/JA4T.1` carries the value form, the FoxIO Python file
158
- holds no JA4T value, and #215 declines the FoxIO Rust value `8192__0_0` rather than a
159
- Python one. The exception rests on a declined Python value, so it passes over that row.
156
+ states that the Python file holds a value, and that statement can be false. Such a row
157
+ declines the value of another source, and the exception passes over it, because the
158
+ exception rests on a declined Python value. `gre-erspan-vxlan.pcap/0:65174/JA4T.1` was
159
+ that row until #772. The FoxIO Python file holds no JA4T value, and #215 declined the
160
+ FoxIO Rust value `8192__0_0`. The Rust snapshot at FoxIO commit `16b96d95` holds
161
+ `8192_00_00_00`, which `ja4plus` writes, so #772 removed the row.
160
162
 
161
163
  **A capability decline bars the row.** The exception reaches a row only where the decline
162
164
  records a disagreement about the value. It does not reach a row whose decline records a
@@ -179,9 +181,11 @@ prose of its cause.** Read the field.
179
181
  values of #129 out of `SOURCE_VALUES`, so the exclusion enforced the bar a second time.
180
182
  No case could then read what the field does on the one issue the bar exists for. The
181
183
  table now holds all 35, measured against the pinned commit. With `capability` false on
182
- every #129 entry the reach rises from 6 rows to 25. The same flip on the earlier table
183
- moved nothing. The 16 JA4H rows of the 35 stay out under the disagreement bar, because
184
- the Rust value and the Wireshark value differ on each one.
184
+ every #129 entry the reach rises from 6 rows to 41. The same flip on the earlier table
185
+ moved nothing. **#772 read the table again at FoxIO `16b96d95`, and the flip reached 25
186
+ rows before that read.** FoxIO `478359f3` moved the 16 Rust JA4H values, and each one now
187
+ equals the Wireshark value. The disagreement bar therefore holds none of the 35, and the
188
+ field alone bars all of them.
185
189
 
186
190
  **A disagreement between the remaining sources bars the row.** Where the remaining FoxIO
187
191
  sources hold different values, **no source holds the reference and the row stays declined
@@ -211,7 +215,7 @@ decides what the bar covers.
211
215
  reference value, and adoption is its own ruling. "Which baselines are usable as
212
216
  vectors" in `docs/specs/foxio/zeek.md` holds that ruling for the Zeek package.
213
217
 
214
- **The exception reaches 6 rows of the 138 the register holds.**
218
+ **The exception reaches 6 rows of the 141 the register holds.**
215
219
  `tests/test_precedence_exception.py` measures the reach and both counts, and #334 records
216
220
  the search. A case reads this sentence, so a register move fails the gate here.
217
221
 
@@ -224,9 +228,12 @@ the search. A case reads this sentence, so a register move fails the gate here.
224
228
  | `ssh2.pcapng/33:51810/JA4L-S.1` | #225 | Wireshark alone |
225
229
  | `tls3.pcapng/25:61884/JA4L-S.1` | #225 | Wireshark alone |
226
230
 
227
- **`FoxIO-LLC/ja4tscan` holds prose and no baseline.** Its `README.md` gives eight
228
- JA4TScan example values against named operating systems, and two of them record TCP
229
- option kind 0 inside the JA4T option list. #197 owns the scope ruling.
231
+ **`FoxIO-LLC/ja4tscan` holds source code and no baseline.** Its `README.md` gives eight
232
+ JA4TScan example values against named operating systems, and one of them records TCP
233
+ option kind 0 twice inside the option list. **`module_ja4tscan.c` is the normative
234
+ source**, and the maintainer named it so on 2026-09-30, in #775. Three of the eight
235
+ values do not fit that module. `docs/specs/foxio/JA4TScan.md` holds the transcription and
236
+ the measurement.
230
237
 
231
238
  **Warning: FoxIO states that `FoxIO-LLC/ja4-nginx-module` is not correct.** Its
232
239
  `README.md` opens with a `# NOTICE` section that reads "This version of JA4 has known
@@ -6,6 +6,56 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ - **JA4 and JA4S write `9` for each ALPN end byte of `0x80` or higher** (#789).
10
+ Round
11
+ 270. **The maintainer ruled on 2026-10-01 UTC, and the ruling binds both repositories.**
12
+ Crank-Git/ja4plus-go#801 holds the Go half, and the Go library ships it in `v1.3.0`. The
13
+ ruling follows `python/ja4.py:156-157` and `rust/ja4/src/tls.rs:635-647` at `16b96d95`.
14
+ Each end byte of `0x80` or higher now writes `9`, one per end, so `68 ff` writes `h9` and
15
+ `ff 68` writes `9h`, where both wrote `99`. A one-byte printable value now writes the byte
16
+ twice, so `2d` writes `--` where it wrote `99`. A control byte below `0x20`, or the byte
17
+ `0x7F`, at either end still writes `99`, as #162 states. The vector
18
+ `tls-non-ascii-alpn.pcapng` holds `ba ad`, and it still writes `99`. **The ruling reverses
19
+ #127, #141 and #162 for those inputs.** `tests/test_alpn_end_byte_ruling.py` builds a
20
+ ClientHello and a ServerHello for each of 14 inputs. **A replay of the 41 committed
21
+ captures writes 826 values before the change and 826 after, and 2 differ.** Both sit on
22
+ `alpn-condition.pcap`, which this project built: stream 2 writes `h9` and stream 3 writes
23
+ `9h`, which are the FoxIO Rust values. No value of a FoxIO capture moves. The register
24
+ moves the four entries of stream 2 from #162 to #789 and adds four entries for stream 3, so
25
+ it holds 141 keys where it held 137. The conformance suite reports 1702 passed, 145 skipped
26
+ and 137 xfailed before this round, and 1698 passed, 145 skipped and 141 xfailed after it.
27
+
28
+ - **`ja4plus scan` adds JA4TScan, the FoxIO active TCP server fingerprint** (#775, #776).
29
+ Round
30
+ 269. **The maintainer reversed the decline of 2026-08-08 on 2026-09-30.** The scanner sends
31
+ one SYN to each IPv4 target and reads the SYN-ACK and its retransmissions. Parts a to d
32
+ use the JA4TS form of this project, so a scan value and the passive JA4TS value of one
33
+ SYN-ACK hold the same four parts. A target whose first answer carries RST writes
34
+ `0_rst-ack`. **The library changes no firewall state.** The command prints the four
35
+ `iptables` rules of the FoxIO wrapper, or four pf rules on macOS, and it warns when a
36
+ SYN-ACK arrives with no retransmission. The scanner lives in `ja4plus/scan/` behind the
37
+ `scan` extra, and no passive module imports it. `docs/specs/foxio/JA4TScan.md` transcribes
38
+ `FoxIO-LLC/ja4tscan` at `d01bfec4`, and `docs/specs/features/12-active-scan.md` states 35
39
+ acceptance criteria. The divergence register records the three places where the FoxIO
40
+ module writes another form. **The project now implements all twelve FoxIO methods.**
41
+
42
+ - **JA4 reads a ClientHello that spans TCP segments, and the FoxIO pin moves to `16b96d95`** (#772, #773, #774).
43
+ Round
44
+ 268. **A post-quantum key share makes a browser ClientHello longer than one TCP segment**,
45
+ and the JA4 fingerprinter read each segment alone, so it wrote no value for such a hello.
46
+ `sigalg-grease.pcapng` of FoxIO release v1.0.1 holds a 2034-byte hello in two segments.
47
+ The fingerprinter now holds the client payload of a connection in a bounded table until
48
+ the hello is complete, and a gap stops the read at the first byte that has not arrived
49
+ (#772). **JA4 removes GREASE values from the signature algorithms** at both call sites, as
50
+ FoxIO `d66336ef` does, and the value of that capture now equals the FoxIO value in all
51
+ four forms (#773). **A repeated MSS option or window scale option keeps its last value**,
52
+ which the FoxIO Rust, Wireshark and Zeek sources each do at `16b96d95` (#774). The FoxIO
53
+ pin moves from `27f0cbf9` to `16b96d95`. The seven Zeek baselines stay at `27f0cbf9`
54
+ under `ZEEK_COMMIT`, because FoxIO `4e91886c` removed `zeek/tests/Traces`. **A replay of
55
+ 77 captures writes one new value, the JA4 of `sigalg-grease.pcapng` frame 5, and every
56
+ other value is byte-identical.** The conformance suite reports 1684 passed, 142 skipped
57
+ and 138 xfailed before this round, and 1702 passed, 145 skipped and 137 xfailed after it.
58
+
9
59
  - **The QUIC CRYPTO reader stops at the first byte that has not arrived** (#762).
10
60
  Round
11
61
  267. **An outside reporter measured the defect on Windows against a live capture.**
@@ -2087,6 +2137,72 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
2087
2137
  **No file under `ja4plus/` changes and no fingerprint moves**, and the conformance suite
2088
2138
  reports 1635 passed, 143 skipped and 140 xfailed.
2089
2139
 
2140
+ ## [1.4.0] - 2026-10-01
2141
+
2142
+ Version 1.4.0 follows version 1.3.0, and no version stands between them. The date of the
2143
+ heading above is the date this release reaches `master`. It carries round 270.
2144
+
2145
+ **The bump is minor and never patch**, because this release moves published values by a
2146
+ ruling that changes a rule, and not by the repair of a defect. It is never major, because it
2147
+ adds no name, it removes no name and it renames none.
2148
+
2149
+ **The ALPN characters of JA4 and JA4S move on edge inputs** (#789). The maintainer ruled on
2150
+ 2026-10-01 UTC, and Crank-Git/ja4plus-go#801 ships the same rule in the Go library at
2151
+ `v1.3.0`, so the two libraries write one value for each input.
2152
+
2153
+ | The first ALPN value | Version 1.3.0 | Version 1.4.0 |
2154
+ |---|---|---|
2155
+ | `68 ff` | `99` | `h9` |
2156
+ | `ff 68` | `99` | `9h` |
2157
+ | `2d` | `99` | `--` |
2158
+ | `20` | `99` | two spaces |
2159
+ | `ba ad` | `99` | `99` |
2160
+ | A control byte at either end | `99` | `99` |
2161
+
2162
+ **No value of a FoxIO capture moves.** A replay of the 41 committed captures writes 826 values
2163
+ at version 1.3.0 and 826 here, and 2 differ. Both sit on `alpn-condition.pcap`, which this
2164
+ project built. The conformance suite reports 1702 passed, 145 skipped and 137 xfailed at
2165
+ version 1.3.0, and 1698 passed, 145 skipped and 141 xfailed here.
2166
+
2167
+ ## [1.3.0] - 2026-09-30
2168
+
2169
+ Version 1.3.0 follows version 1.2.2, and no version stands between them. The date of the
2170
+ heading above is the date this release reaches `master`. It carries rounds 268 and 269.
2171
+
2172
+ **The bump is minor and never patch.** This release adds published names: the
2173
+ `ja4plus.scan` package, the `ja4plus scan` subcommand and the `scan` extra. It removes no
2174
+ name and it renames none.
2175
+
2176
+ **JA4 reads a ClientHello that spans TCP segments** (#772). A post-quantum key share makes
2177
+ a current browser ClientHello longer than one TCP segment, and the fingerprinter read each
2178
+ segment alone, so it wrote no JA4 value for such a hello. **A caller now receives a JA4
2179
+ value for that traffic where it received none before.** The fingerprinter holds the client
2180
+ payload of a connection in a bounded table until the hello is complete, and a gap stops
2181
+ the read at the first byte that has not arrived.
2182
+
2183
+ **Two readers now write the FoxIO value.**
2184
+
2185
+ | Method | Input | Version 1.2.2 | Version 1.3.0 | Issue |
2186
+ |---|---|---|---|---|
2187
+ | JA4 | A ClientHello with a GREASE value among its signature algorithms | Hashes the GREASE value | Removes it, as FoxIO `d66336ef` does | #773 |
2188
+ | JA4T, JA4TS | A SYN or SYN-ACK with a repeated MSS option or window scale option | Keeps the first value | Keeps the last value, as the FoxIO Rust, Wireshark and Zeek sources do | #774 |
2189
+
2190
+ **No value of the committed corpus moves.** A replay of 77 captures writes one new value,
2191
+ the JA4 of `sigalg-grease.pcapng` frame 5, which equals the FoxIO value. Every other value
2192
+ is byte-identical. No committed capture carries a repeated TCP option or a signature
2193
+ algorithm GREASE value.
2194
+
2195
+ **`ja4plus scan` adds JA4TScan, the FoxIO active TCP server fingerprint** (#775, #776). The
2196
+ scanner sends one SYN to each IPv4 target and reads the SYN-ACK and its retransmissions.
2197
+ **It is the one part of the package that sends packets**, so it lives behind the `scan`
2198
+ extra and no passive module imports it. **The library changes no firewall state**, and
2199
+ the command prints the rules the operator adds. `docs/specs/features/12-active-scan.md`
2200
+ states the requirements. The project now implements all twelve FoxIO methods.
2201
+
2202
+ **The FoxIO pin moves from `27f0cbf9` to `16b96d95`**, and `sigalg-grease.pcapng` joins the
2203
+ vectors. The conformance suite reports 1684 passed, 142 skipped and 138 xfailed at version
2204
+ 1.2.2, and 1702 passed, 145 skipped and 137 xfailed here.
2205
+
2090
2206
  ## [1.2.2] - 2026-09-29
2091
2207
 
2092
2208
  Version 1.2.2 follows version 1.2.1, 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.2.2, and `ja4plus/__init__.py:101` holds that one
7
+ This repository declares version 1.4.0, 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.2
3
+ Version: 1.4.0
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
@@ -41,6 +41,7 @@ Requires-Dist: mkdocs-material==9.7.7; extra == "docs"
41
41
  Requires-Dist: mkdocstrings==1.0.6; extra == "docs"
42
42
  Requires-Dist: mkdocstrings-python==2.0.9; extra == "docs"
43
43
  Requires-Dist: griffe==2.3.0; extra == "docs"
44
+ Provides-Extra: scan
44
45
  Dynamic: license-file
45
46
 
46
47
  <p align="center"><img src="assets/logo.png" width="300"></p>
@@ -49,9 +50,10 @@ A Python library and command-line program that produces JA4+ fingerprints. It re
49
50
  TCP, HTTP, SSH and DHCP traffic, and it reads an X.509 certificate. It supports QUIC,
50
51
  IPv4, IPv6 and multi-segment TCP reassembly.
51
52
 
52
- **FoxIO publishes twelve JA4+ methods, and this project implements eleven of them.** The
53
- twelfth is JA4TScan. `Methods` below names each method, and it states whether this
54
- project builds it.
53
+ **FoxIO publishes twelve JA4+ methods, and this project implements twelve of them.**
54
+ Eleven read traffic that already exists. The twelfth is JA4TScan, which sends packets, and
55
+ the `ja4plus scan` subcommand runs it. Ten fingerprinter classes carry the other eleven,
56
+ because `JA4LFingerprinter` writes both JA4L and JA4LS. `Methods` below names each method.
55
57
 
56
58
  FoxIO owns the JA4+ standard, and [FoxIO](https://foxio.io) publishes it. This library is
57
59
  an independent implementation of that standard. FoxIO wrote no part of it. For the
@@ -80,16 +82,19 @@ The `Implemented` column states whether this project builds the method.
80
82
  | JA4SSH | SSH | Session type classification from traffic patterns | Yes |
81
83
  | JA4T | TCP | Client OS fingerprint from SYN packets | Yes |
82
84
  | JA4TS | TCP | Server fingerprint from SYN-ACK packets | Yes |
83
- | JA4TScan | TCP | Active TCP fingerprint scanner | No |
85
+ | JA4TScan | TCP | Active TCP server fingerprint from SYN-ACK retransmissions, through `ja4plus scan` | Yes |
84
86
  | JA4D | DHCPv4 | DHCP client/server fingerprint (FoxIO PR #267/#270) | Yes |
85
87
  | JA4D6 | DHCPv6 | DHCPv6 client/server fingerprint (FoxIO PR #267/#270) | Yes |
86
88
 
87
- **JA4TScan is declined by ruling, and it is not an omission.** It sends crafted packets
88
- to a host the operator names and reads the responses. Every other method reads traffic
89
- that already exists, so JA4TScan reaches a network the operator did not capture. That
90
- capability is larger than fingerprint production.
91
- [`docs/specs/spec.md`](docs/specs/spec.md) holds the ruling under `Non-goals`, and the
92
- ruling is reversible.
89
+ **JA4TScan sends packets, so this project keeps it apart from the other eleven.** It
90
+ sends one TCP SYN to each host the operator names and reads the responses. The maintainer
91
+ reversed the decline of 2026-08-08 on 2026-09-30, in #775, and #776 built the scanner.
92
+ The scanner installs with `pip install ja4plus[scan]` and runs as `ja4plus scan`. The
93
+ `ja4plus.scan` module holds it, and no passive fingerprinter can send a packet. The
94
+ scanner changes no firewall state, and it states the rule the operator adds.
95
+ [`docs/methods/ja4tscan.md`](docs/methods/ja4tscan.md) describes the command, and
96
+ [`docs/specs/features/12-active-scan.md`](docs/specs/features/12-active-scan.md) holds the
97
+ design.
93
98
 
94
99
  `JA4LFingerprinter` builds both JA4L and JA4LS, so ten fingerprinters carry eleven
95
100
  methods. `--types ja4l` names the two together, and `--types ja4ls` names JA4LS alone.
@@ -535,7 +540,7 @@ pytest tests/ -v
535
540
 
536
541
  This library is released under the **BSD 3-Clause License**.
537
542
 
538
- The JA4+ fingerprinting specifications were created by [FoxIO](https://foxio.io). JA4 (TLS Client) is open source under BSD-3-Clause per FoxIO. The other methods this project implements carry the [FoxIO License 1.1](https://github.com/FoxIO-LLC/ja4/blob/main/LICENSE): JA4S, JA4H, JA4L, JA4LS, JA4X, JA4SSH, JA4T, JA4TS, JA4D and JA4D6. That license is permissive for academic, internal business, and security research use. FoxIO names each of them at line 5 of [`License FAQ.md`](https://github.com/FoxIO-LLC/ja4/blob/main/License%20FAQ.md), at commit `27f0cbf9fd3000c072f82a0f7d0361dc99acf6c8`. That FoxIO list is wider than this one, because it also names methods this project does not implement.
543
+ The JA4+ fingerprinting specifications were created by [FoxIO](https://foxio.io). JA4 (TLS Client) is open source under BSD-3-Clause per FoxIO. The other methods this project implements carry the [FoxIO License 1.1](https://github.com/FoxIO-LLC/ja4/blob/main/LICENSE): JA4S, JA4H, JA4L, JA4LS, JA4X, JA4SSH, JA4T, JA4TS, JA4TScan, JA4D and JA4D6. That license is permissive for academic, internal business, and security research use. FoxIO names each of them at line 5 of [`License FAQ.md`](https://github.com/FoxIO-LLC/ja4/blob/main/License%20FAQ.md), at commit `27f0cbf9fd3000c072f82a0f7d0361dc99acf6c8`. That FoxIO list is wider than this one, because it also names methods this project does not implement.
539
544
 
540
545
  See [LICENSE](LICENSE) for full details.
541
546
 
@@ -4,9 +4,10 @@ A Python library and command-line program that produces JA4+ fingerprints. It re
4
4
  TCP, HTTP, SSH and DHCP traffic, and it reads an X.509 certificate. It supports QUIC,
5
5
  IPv4, IPv6 and multi-segment TCP reassembly.
6
6
 
7
- **FoxIO publishes twelve JA4+ methods, and this project implements eleven of them.** The
8
- twelfth is JA4TScan. `Methods` below names each method, and it states whether this
9
- project builds it.
7
+ **FoxIO publishes twelve JA4+ methods, and this project implements twelve of them.**
8
+ Eleven read traffic that already exists. The twelfth is JA4TScan, which sends packets, and
9
+ the `ja4plus scan` subcommand runs it. Ten fingerprinter classes carry the other eleven,
10
+ because `JA4LFingerprinter` writes both JA4L and JA4LS. `Methods` below names each method.
10
11
 
11
12
  FoxIO owns the JA4+ standard, and [FoxIO](https://foxio.io) publishes it. This library is
12
13
  an independent implementation of that standard. FoxIO wrote no part of it. For the
@@ -35,16 +36,19 @@ The `Implemented` column states whether this project builds the method.
35
36
  | JA4SSH | SSH | Session type classification from traffic patterns | Yes |
36
37
  | JA4T | TCP | Client OS fingerprint from SYN packets | Yes |
37
38
  | JA4TS | TCP | Server fingerprint from SYN-ACK packets | Yes |
38
- | JA4TScan | TCP | Active TCP fingerprint scanner | No |
39
+ | JA4TScan | TCP | Active TCP server fingerprint from SYN-ACK retransmissions, through `ja4plus scan` | Yes |
39
40
  | JA4D | DHCPv4 | DHCP client/server fingerprint (FoxIO PR #267/#270) | Yes |
40
41
  | JA4D6 | DHCPv6 | DHCPv6 client/server fingerprint (FoxIO PR #267/#270) | Yes |
41
42
 
42
- **JA4TScan is declined by ruling, and it is not an omission.** It sends crafted packets
43
- to a host the operator names and reads the responses. Every other method reads traffic
44
- that already exists, so JA4TScan reaches a network the operator did not capture. That
45
- capability is larger than fingerprint production.
46
- [`docs/specs/spec.md`](docs/specs/spec.md) holds the ruling under `Non-goals`, and the
47
- ruling is reversible.
43
+ **JA4TScan sends packets, so this project keeps it apart from the other eleven.** It
44
+ sends one TCP SYN to each host the operator names and reads the responses. The maintainer
45
+ reversed the decline of 2026-08-08 on 2026-09-30, in #775, and #776 built the scanner.
46
+ The scanner installs with `pip install ja4plus[scan]` and runs as `ja4plus scan`. The
47
+ `ja4plus.scan` module holds it, and no passive fingerprinter can send a packet. The
48
+ scanner changes no firewall state, and it states the rule the operator adds.
49
+ [`docs/methods/ja4tscan.md`](docs/methods/ja4tscan.md) describes the command, and
50
+ [`docs/specs/features/12-active-scan.md`](docs/specs/features/12-active-scan.md) holds the
51
+ design.
48
52
 
49
53
  `JA4LFingerprinter` builds both JA4L and JA4LS, so ten fingerprinters carry eleven
50
54
  methods. `--types ja4l` names the two together, and `--types ja4ls` names JA4LS alone.
@@ -490,7 +494,7 @@ pytest tests/ -v
490
494
 
491
495
  This library is released under the **BSD 3-Clause License**.
492
496
 
493
- The JA4+ fingerprinting specifications were created by [FoxIO](https://foxio.io). JA4 (TLS Client) is open source under BSD-3-Clause per FoxIO. The other methods this project implements carry the [FoxIO License 1.1](https://github.com/FoxIO-LLC/ja4/blob/main/LICENSE): JA4S, JA4H, JA4L, JA4LS, JA4X, JA4SSH, JA4T, JA4TS, JA4D and JA4D6. That license is permissive for academic, internal business, and security research use. FoxIO names each of them at line 5 of [`License FAQ.md`](https://github.com/FoxIO-LLC/ja4/blob/main/License%20FAQ.md), at commit `27f0cbf9fd3000c072f82a0f7d0361dc99acf6c8`. That FoxIO list is wider than this one, because it also names methods this project does not implement.
497
+ The JA4+ fingerprinting specifications were created by [FoxIO](https://foxio.io). JA4 (TLS Client) is open source under BSD-3-Clause per FoxIO. The other methods this project implements carry the [FoxIO License 1.1](https://github.com/FoxIO-LLC/ja4/blob/main/LICENSE): JA4S, JA4H, JA4L, JA4LS, JA4X, JA4SSH, JA4T, JA4TS, JA4TScan, JA4D and JA4D6. That license is permissive for academic, internal business, and security research use. FoxIO names each of them at line 5 of [`License FAQ.md`](https://github.com/FoxIO-LLC/ja4/blob/main/License%20FAQ.md), at commit `27f0cbf9fd3000c072f82a0f7d0361dc99acf6c8`. That FoxIO list is wider than this one, because it also names methods this project does not implement.
494
498
 
495
499
  See [LICENSE](LICENSE) for full details.
496
500
 
@@ -280,7 +280,7 @@ dataclass, because a result describes something that already happened.
280
280
 
281
281
  | Field | Type | Constraint |
282
282
  |---|---|---|
283
- | `type` | `str` | The method name, lowercase. One of ten values, and ten values carry eleven methods. `--types` accepts these ten tokens and the token `ja4ls`. |
283
+ | `type` | `str` | The method name, lowercase. One of eleven values. Ten values carry the eleven passive methods, and `ja4plus scan` writes the value `ja4tscan`. `--types` accepts the ten passive tokens and the token `ja4ls`. |
284
284
  | `fingerprint` | `str` | The fingerprint string. Never empty. |
285
285
  | `raw` | `str \| None` | The raw form, when the method defines one. |
286
286
  | `raw_original_order` | `str \| None` | The original-order raw form, when the method defines one. |
@@ -382,8 +382,8 @@ log the error inside the loop that reads it.
382
382
  reads no packet timestamp. The field holds `None`.
383
383
 
384
384
  `stats()` reports what the state tables hold, and #41 built it. One processor holds
385
- **seventeen** state tables across the ten fingerprinters: the fifteen `BoundedStateTable`
386
- instances and the two `TCPStreamReassembler` instances of JA4H and JA4X. A method that
385
+ **eighteen** state tables across the ten fingerprinters: the fifteen `BoundedStateTable`
386
+ instances and the three `TCPStreamReassembler` instances of JA4H, JA4X and JA4 (#772). A method that
387
387
  holds no state reports an empty `tables` list.
388
388
 
389
389
  | Field of `ProcessorStats` | Description |
@@ -435,8 +435,8 @@ one processor without that arrangement, and it guards a `reset` that runs beside
435
435
 
436
436
  #### The memory bound of the processor
437
437
 
438
- One processor holds seventeen state tables: fifteen `BoundedStateTable` instances and
439
- two `TCPStreamReassembler` instances. `features/03-concurrency-safety.md` states the maximum
438
+ One processor holds eighteen state tables: fifteen `BoundedStateTable` instances and
439
+ three `TCPStreamReassembler` instances. `features/03-concurrency-safety.md` states the maximum
440
440
  entry count and the maximum age of each one. A table that reaches its maximum entry count
441
441
  evicts the least recently used entry. A long capture can therefore evict a connection
442
442
  that later returns, and the fingerprint of a returned connection may be incomplete.
@@ -546,11 +546,11 @@ caller removed with `cleanup_connection` counts as a first sighting when it retu
546
546
  because the caller asked for that removal.
547
547
 
548
548
  A table remembers the keys it evicted, so that it can recognize a return. The memory
549
- holds the entry bound of its own table. The seventeen tables of one processor hold 57400
550
- remembered keys between them, at 187 bytes for one key, so the memory costs 10.2 MiB
549
+ holds the entry bound of its own table. The eighteen tables of one processor hold 58400
550
+ remembered keys between them, at 187 bytes for one key, so the memory costs 10.4 MiB
551
551
  when every table is full and every entry of every table has been replaced.
552
552
 
553
- Ten fingerprinters hold seventeen state tables between them. `JA4DFingerprinter` and
553
+ Ten fingerprinters hold eighteen state tables between them. `JA4DFingerprinter` and
554
554
  `JA4D6Fingerprinter` hold none, and each reports an empty `tables` dict.
555
555
 
556
556
  `stats()` holds the lock of one fingerprinter across the read of that fingerprinter, so
@@ -562,6 +562,61 @@ tables that the counts describe.
562
562
 
563
563
  `ProcessorStats` is a plain object. Epic 4 makes it a typed dataclass.
564
564
 
565
+ ## Active scan
566
+
567
+ `ja4plus.scan` holds JA4TScan, the one method that sends packets. `import ja4plus` loads
568
+ no module of it, and no module outside it imports it. The `ja4plus scan` subcommand
569
+ reaches it through the `ja4plus.commands` entry point.
570
+ [The JA4TScan page](methods/ja4tscan.md) describes the command.
571
+
572
+ **The scan loop takes the send function, the receive function and the clock as
573
+ parameters.** A caller therefore runs it on a fake network, and `tests/scan_fakes.py`
574
+ holds one.
575
+
576
+ ### ja4plus.scan.value
577
+
578
+ | Class/Function | Description |
579
+ |---|---|
580
+ | `Response(seconds, flags, window, options)` | The four fields of one TCP response that the value reads |
581
+ | `ja4tscan_value(responses)` | Return the JA4TScan value of one target, or `None` when it sent no response |
582
+ | `RST_ACK_VALUE` | The value `0_rst-ack`, which a target whose first response carries RST produces |
583
+
584
+ ### ja4plus.scan.frames
585
+
586
+ | Class/Function | Description |
587
+ |---|---|
588
+ | `build_syn(*, src_mac, dst_mac, src_ip, dst_ip, src_port, dst_port, sequence, timestamp)` | Return the Ethernet frame of one FoxIO SYN, 74 bytes |
589
+ | `parse_frame(frame)` | Return the TCP fields of one received Ethernet frame, or `None` for a frame it cannot read |
590
+ | `Reply` | The TCP fields of one received segment that the scanner reads |
591
+
592
+ ### ja4plus.scan.scanner
593
+
594
+ | Class/Function | Description |
595
+ |---|---|
596
+ | `Scanner(*, port, rate, retransmit, send, receive, clock, on_result, on_warning, rng)` | The scan loop. `run(targets)` sends one SYN to each target and waits for its responses |
597
+ | `ScanResult` | The value of one target and the endpoints of its responses |
598
+ | `parse_targets(text)` | Return the IPv4 targets that one address, one network or one file names |
599
+ | `TargetError` | The error `parse_targets` raises for a target the scanner cannot read |
600
+ | `firewall_rules(platform)` | Return the firewall rules the operator adds before a scan |
601
+ | `MAX_TARGETS` | The largest target count the state table holds, 10000 |
602
+ | `RETRANSMIT_WAIT_SECONDS` | The wait for later responses after each SYN, 120 seconds |
603
+ | `NO_RETRANSMIT_WAIT_SECONDS` | The wait under `--retransmit no`, 8 seconds |
604
+
605
+ ### ja4plus.scan.command
606
+
607
+ | Class/Function | Description |
608
+ |---|---|
609
+ | `run(args, result_stream)` | Run the scan on the network of this host. The `ja4plus.commands` entry point names it |
610
+ | `scan_command(args, *, result_stream, platform, open_network, clock, rng)` | Run the scan over the network that `open_network` returns |
611
+ | `METHOD_TYPE` | The `type` value of a scan output line, `ja4tscan` |
612
+
613
+ ### ja4plus.scan.link
614
+
615
+ | Class/Function | Description |
616
+ |---|---|
617
+ | `LinkNetwork(port, first_target, on_warning)` | The link-layer socket that sends each SYN and reads each response. It needs the privilege to open a raw socket |
618
+ | `privilege_refused(error)` | Return `True` when the error states that the host refused that privilege |
619
+
565
620
  ## Utility Modules
566
621
 
567
622
  ### ja4plus.utils.tls_utils
@@ -98,16 +98,17 @@ a FoxIO vector holds the value, and because a fingerprint exists so that one too
98
98
  can be compared against another tool output. The register holds no entry for this vector,
99
99
  and `tests/test_ja4_alpn.py` compares the produced value against the reference value.
100
100
 
101
- `compute_alpn_value` returns `99` when the first byte or the last byte of the first ALPN
102
- value falls outside `0x20-0x7E`. `ja4s.py` reads the same function, so JA4 and JA4S
103
- carry one rule.
101
+ `compute_alpn_value` writes `9` for each end byte of `0x80` or higher, and it writes `99`
102
+ when a control byte sits at either end. The ruling of #789 set that rule on 2026-10-01 UTC,
103
+ and the section `### The ruling of #789 writes 9 for each high end byte` below states it.
104
+ `ja4s.py` reads the same function, so JA4 and JA4S carry one rule.
104
105
 
105
106
  #127 settled the value that this vector produces. It settled no condition, because
106
107
  every rule fires on `0xba 0xad`. #141 measured the condition against a capture it built.
107
108
  The next section holds that measurement, and it holds the part of the condition the
108
109
  measurement leaves open.
109
110
 
110
- **Location:** `ja4plus/fingerprinters/ja4.py:36`, in `compute_alpn_value`.
111
+ **Location:** `ja4plus/fingerprinters/ja4.py:74`, in `compute_alpn_value`.
111
112
 
112
113
  ### The ALPN condition passes a printable ASCII byte through
113
114
 
@@ -220,10 +221,48 @@ in its invariant field.
220
221
  `test_every_stream_differs_from_the_others_in_the_alpn_characters_alone` proves the
221
222
  invariance.
222
223
 
223
- **Location:** `ja4plus/fingerprinters/ja4.py:36`, in `compute_alpn_value`. #162 changed
224
+ **Location:** `ja4plus/fingerprinters/ja4.py:74`, in `compute_alpn_value`. #162 changed
224
225
  no line of it. `tests/test_ja4_alpn_condition.py` holds the measurement against
225
226
  `ja4plus`.
226
227
 
228
+ **The tables above record the state before #789.** The next section states the values
229
+ that `ja4plus` writes since that ruling.
230
+
231
+ ### The ruling of #789 writes 9 for each high end byte
232
+
233
+ **The maintainer ruled on 2026-10-01 UTC, and the ruling binds both repositories.**
234
+ `Crank-Git/ja4plus-go#801` holds the Go half, and the Go library ships it in `v1.3.0`.
235
+
236
+ - Each end byte of `0x80` or higher writes `9`, one per end.
237
+ - A one-byte printable value writes the byte twice.
238
+ - A control byte below `0x20`, or the byte `0x7F`, at either end still writes `99`.
239
+
240
+ The ruling follows `python/ja4.py:156-157` and `rust/ja4/src/tls.rs:635-647` at the
241
+ pinned commit `16b96d95`. At that commit FoxIO Python tests each end character against
242
+ 128, and it no longer tests the first character alone.
243
+
244
+ | The first ALPN value | Before #789 | Since #789 |
245
+ |---|---|---|
246
+ | `68` | `hh` | `hh` |
247
+ | `2d` | `99` | `--` |
248
+ | `20` | `99` | two spaces |
249
+ | `68 ff` | `99` | `h9` |
250
+ | `ff 68` | `99` | `9h` |
251
+ | `ba ad` | `99` | `99` |
252
+ | `68 1f` | `99` | `99` |
253
+ | `01 68` | `99` | `99` |
254
+
255
+ **Two streams of `alpn-condition.pcap` move.** Stream 2 (`h\xab`) now writes `h9`, and
256
+ stream 3 (`\xabh`) now writes `9h`. Both values equal the FoxIO Rust measurement. The
257
+ expected-output file holds the FoxIO Python measurement at `27f0cbf9`, so both streams
258
+ differ from it. The register holds four entries for each stream under #789. **No FoxIO
259
+ vector moves**, because `tls-non-ascii-alpn.pcapng` holds `ba ad` and it still writes `99`.
260
+
261
+ `tests/test_alpn_end_byte_ruling.py` builds a ClientHello and a ServerHello for each input,
262
+ so it holds the ruling for JA4 and for JA4S.
263
+
264
+ **Location:** `ja4plus/fingerprinters/ja4.py:57`, in `_alpn_end_character`.
265
+
227
266
  ### The QUIC reference value comes from the FoxIO Rust implementation
228
267
 
229
268
  `tests/foxio_vectors/quic-with-several-tls-frames.pcapng` holds one QUIC Initial packet.
@@ -300,17 +339,23 @@ reported green on a comparison it never made. #216 closed it, and
300
339
  | Measurement | Count |
301
340
  |---|---|
302
341
  | JA4T values in `python/test/testdata/` | 0 |
303
- | JA4T values in the seven local Rust snapshots that hold one | 39 |
304
- | Values `ja4plus` reproduces exactly | 38 |
305
- | Values that differ | 1 |
342
+ | JA4T values in the eight local Rust snapshots that hold one | 40 |
343
+ | Values `ja4plus` reproduces exactly | 40 |
344
+ | Values that differ | 0 |
306
345
  | Streams on which `ja4plus` emits more than one value | 0 |
307
- | Cases that stop running when `("JA4T", "ja4t")` leaves `SNAPSHOT_METHODS` | 46 |
346
+ | Cases that stop running when `("JA4T", "ja4t")` leaves `SNAPSHOT_METHODS` | 44 |
347
+
348
+ **#772 took the four counts above again on 2026-09-30, after it moved the pin of
349
+ `tests/foxio_vectors/` to FoxIO `16b96d95`.** `sigalg-grease.pcapng` adds one snapshot and
350
+ one value. The last count is the drop in the collected cases of
351
+ `tests/test_foxio_rust_parity.py` when the JA4T entry leaves `SNAPSHOT_METHODS`.
308
352
 
309
- **The one entry.** `tests/foxio_deviations.json` holds one JA4T key today, and it is
310
- `gre-erspan-vxlan.pcap/0:65174/JA4T.1`. The FoxIO Rust snapshot holds `8192__0_0` and
311
- `ja4plus` writes `8192_00_00_00`. The SYN carries no TCP option, and the three FoxIO forms
312
- disagree. The user chose the two-digit form on 2026-08-08, so the entry is `decided` and it
313
- stays. #215 records the ruling.
353
+ **The register holds no JA4T key.** It held one until #772, which was
354
+ `gre-erspan-vxlan.pcap/0:65174/JA4T.1`. The FoxIO Rust snapshot held `8192__0_0` at the
355
+ earlier pin, and `ja4plus` writes `8192_00_00_00`. The user chose the two-digit form on
356
+ 2026-08-08, and #215 records the ruling. FoxIO `08617cc3` moved the Rust implementation to
357
+ the same form, so the snapshot at `16b96d95` holds `8192_00_00_00` and #772 removed the
358
+ entry.
314
359
 
315
360
  **Two entries this section named have gone, and the repairs removed them.**
316
361
  `chrome-cloudflare-quic-with-secrets.pcapng/0:57098/JA4T.1` recorded a part b that held one
@@ -540,7 +585,7 @@ value. #132 holds the command and the full output.
540
585
  Before #132, `ja4plus` emitted `9af15b336e6a` on these four streams. `JA4_o` now matches
541
586
  all 160 reference values, and the count was 156 of 160.
542
587
 
543
- **Location:** `ja4plus/fingerprinters/ja4.py:226`.
588
+ **Location:** `ja4plus/fingerprinters/ja4.py:228`.
544
589
 
545
590
  The JA4S section below records the `JA4S_o` reading.
546
591
 
@@ -1,6 +1,8 @@
1
1
  # The methods
2
2
 
3
- FoxIO publishes twelve JA4+ methods. This project implements eleven of them.
3
+ FoxIO publishes twelve JA4+ methods. This project implements twelve of them. Ten
4
+ fingerprinter classes carry eleven of them, because `JA4LFingerprinter` writes both JA4L
5
+ and JA4LS. The `ja4plus scan` subcommand carries JA4TScan.
4
6
 
5
7
  Each page below describes one method. It states the output format, the parts of the
6
8
  value, the hash rule, and the FoxIO file that publishes the method. It also names one
@@ -23,7 +25,7 @@ the standard itself, read the
23
25
  | JA4SSH | SSH | [The SSH session fingerprint](ja4ssh.md) | Yes |
24
26
  | JA4T | TCP | [The TCP client fingerprint](ja4t.md) | Yes |
25
27
  | JA4TS | TCP | [The TCP server fingerprint](ja4ts.md) | Yes |
26
- | JA4TScan | TCP | No page. Read the decline below. | No |
28
+ | JA4TScan | TCP | [The active TCP server fingerprint](ja4tscan.md) | Yes |
27
29
  | JA4D | DHCPv4 | [The DHCPv4 fingerprint](ja4d.md) | Yes |
28
30
  | JA4D6 | DHCPv6 | [The DHCPv6 fingerprint](ja4d6.md) | Yes |
29
31
 
@@ -40,15 +42,24 @@ under parity rule 2.
40
42
  **Read the ten as a count of fingerprinter classes, and never as a count of methods.**
41
43
  #387 records the three documents that made that mistake.
42
44
 
43
- ## Why this project builds no JA4TScan
45
+ ## Why JA4TScan stands apart from the other methods
44
46
 
45
- **This project declines JA4TScan by ruling, and the absence is no omission.** JA4TScan
46
- sends crafted packets to a host the operator names, and it reads the responses. Every
47
- other method reads traffic that already exists, so JA4TScan reaches a network the
48
- operator did not capture. That capability is larger than fingerprint production.
47
+ **JA4TScan sends packets, and every other method reads traffic that already exists.**
48
+ JA4TScan sends one TCP SYN to each host the operator names, and it reads the responses.
49
+ #776 built it as the `ja4plus scan` subcommand, in the `ja4plus.scan` module. No
50
+ fingerprinter of this table can send a packet.
49
51
 
50
- `docs/specs/spec.md` holds the ruling under `Non-goals`, and #197 holds the reading.
51
- The ruling is reversible.
52
+ The maintainer reversed the decline of 2026-08-08 on 2026-09-30, in #775.
53
+ `docs/specs/features/12-active-scan.md` holds the design, and [the JA4TScan page](ja4tscan.md)
54
+ describes the command. The superseded text is quoted below rather than rewritten.
55
+
56
+ > **This project declines JA4TScan by ruling, and the absence is no omission.** JA4TScan
57
+ > sends crafted packets to a host the operator names, and it reads the responses. Every
58
+ > other method reads traffic that already exists, so JA4TScan reaches a network the
59
+ > operator did not capture. That capability is larger than fingerprint production.
60
+ >
61
+ > `docs/specs/spec.md` holds the ruling under `Non-goals`, and #197 holds the reading.
62
+ > The ruling is reversible.
52
63
 
53
64
  ## What a fingerprint is evidence of
54
65