#[non_exhaustive]pub struct TlsHandshake {Show 15 fields
pub sni: Option<String>,
pub client_alpn: Vec<String>,
pub server_alpn: Option<String>,
pub ja3: Option<String>,
pub ja4: Option<String>,
pub ja4s: Option<String>,
pub version: Option<TlsVersion>,
pub cipher_suite: Option<u16>,
pub resumption_attempted: bool,
pub outcome: HandshakeOutcome,
pub ech_outcome: EchOutcome,
pub ech_config_id: Option<u8>,
pub pq_key_share: bool,
pub certificate_chain: Vec<Bytes>,
pub ja4x: Option<String>,
}tls only.Expand description
Aggregated TLS handshake event.
One emitted per observed handshake. Carries enough fields for the common “log TLS handshake” use case (SNI, ALPN, version, cipher, JA3/JA4 if features on, outcome) without consumers hand-rolling correlation across the per-message stream.
Fields (Non-exhaustive)§
This struct is marked as non-exhaustive
Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.sni: Option<String>§client_alpn: Vec<String>§server_alpn: Option<String>§ja3: Option<String>JA3 fingerprint (MD5 hex). Set when ja3 feature on.
ja4: Option<String>JA4 fingerprint (FoxIO format). Set when ja4 feature on.
ja4s: Option<String>ja4plus only.JA4S server fingerprint (FoxIO format), from the ServerHello.
Set when the ja4 config + opt-in ja4plus feature are on.
New in 0.15.0; moved behind ja4plus in 0.16.0 (FoxIO License 1.1 —
see NOTICE).
version: Option<TlsVersion>Negotiated TLS version (from ServerHello supported_versions if present, else legacy_version).
cipher_suite: Option<u16>Server’s selected cipher suite.
resumption_attempted: boolTrue iff the client sent PSK / session-ticket extensions (indicates resumption was attempted).
outcome: HandshakeOutcomeFinal outcome.
ech_outcome: EchOutcomeAggregated ECH outcome derived from the client’s
ech_present flag plus the server’s
ech_retry_configs plaintext-fallback signal.
ech_config_id: Option<u8>HPKE config_id from the client’s outer ECHClientHello,
when ECH was offered.
true if the client offered a post-quantum hybrid
key-exchange group (X25519MLKEM768 family). Mirrors
TlsClientHello::pq_key_share;
the dominant reason a modern ClientHello spans multiple
segments. Issue #135 (0.22.0).
certificate_chain: Vec<Bytes>Server’s certificate chain from a TLS 1.2 Certificate
handshake record (leaf first per RFC 5246 §7.4.2). Each
entry is an X.509 DER blob. Empty for TLS 1.3
(cert chain is encrypted) and for sessions where no cert
record was observed before the parser stopped. New in
0.18.0 (issue #24 prereq).
ja4x: Option<String>ja4plus only.JA4X server-certificate fingerprint of the leaf
(first) cert in Self::certificate_chain. Behind
the FoxIO-licensed ja4plus feature. None when no
cert chain was observed (TLS 1.3 / truncated /
ja4plus off) or the DER failed to parse.
Issue #24 (0.18.0).
Implementations§
Source§impl TlsHandshake
impl TlsHandshake
Sourcepub fn ech_state(&self) -> EchState
pub fn ech_state(&self) -> EchState
Classify the ECH (Encrypted ClientHello) state from the handshake-level evidence — the client’s ECH offer and the server’s plaintext-observable rejection signal.
Refines crate::tls::TlsClientHello::ech_state with
the server side:
ech_outcome == NotOffered→super::EchState::NotPresentech_outcome == Rejected→super::EchState::Rejected- else: classify by outer SNI via
super::ech::classify_client_hello(Accepted / Unknown withoutretry_configscan’t be distinguished from GREASE without further evidence).
Issue #8 (0.18).
Sourcepub fn routing_alpn(&self) -> Option<&str>
pub fn routing_alpn(&self) -> Option<&str>
The ALPN protocol to route on: the server’s selection if the handshake got that far, otherwise the client’s first offer.
The server’s choice is the negotiated outcome; the client’s list is only a wish. A terminating proxy uses this to decide whether to speak HTTP/1 or HTTP/2 on the connection.
use flowscope::tls::TlsHandshake;
let mut hs = TlsHandshake::default();
hs.client_alpn = vec!["h2".into(), "http/1.1".into()];
assert_eq!(hs.routing_alpn(), Some("h2"));
// Once the server picks, that wins over the offer order.
hs.server_alpn = Some("http/1.1".into());
assert_eq!(hs.routing_alpn(), Some("http/1.1"));Sourcepub fn routing_sni(&self) -> Option<(&str, bool)>
pub fn routing_sni(&self) -> Option<(&str, bool)>
The name to route on, and whether it can be trusted as the client’s real target.
Returns (name, is_real_target). With ECH in play the SNI on
the wire is the outer cover domain, so is_real_target is
false and the caller should expect to fall further down the
ladder in docs/tls-routing.md. None means no SNI at all —
route by JA4, first-byte class, or pass through.
This never fails on ECH: degrading is the designed behaviour, and refusing connections that offer ECH would refuse a growing share of ordinary traffic.
use flowscope::tls::{EchOutcome, TlsHandshake};
let mut hs = TlsHandshake::default();
hs.sni = Some("api.example.com".into());
assert_eq!(hs.routing_sni(), Some(("api.example.com", true)));
// With ECH offered, the same field is only a cover name.
hs.ech_outcome = EchOutcome::Accepted;
assert_eq!(hs.routing_sni(), Some(("api.example.com", false)));Trait Implementations§
Source§impl Clone for TlsHandshake
impl Clone for TlsHandshake
Source§impl Debug for TlsHandshake
impl Debug for TlsHandshake
Source§impl Default for TlsHandshake
impl Default for TlsHandshake
Source§impl<'de> Deserialize<'de> for TlsHandshake
Available on crate feature serde only.
impl<'de> Deserialize<'de> for TlsHandshake
serde only.Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl PartialEq for TlsHandshake
impl PartialEq for TlsHandshake
Source§impl Serialize for TlsHandshake
Available on crate feature serde only.
impl Serialize for TlsHandshake
serde only.