rama_net/fingerprint/ja4/
tls.rs

1use itertools::Itertools as _;
2use std::{borrow::Cow, fmt};
3
4use rama_core::context::Extensions;
5
6use crate::{
7    fingerprint::ClientHelloProvider,
8    tls::{
9        ApplicationProtocol, CipherSuite, ExtensionId, ProtocolVersion, SecureTransport,
10        SignatureScheme, client::NegotiatedTlsParameters,
11    },
12};
13
14#[derive(Clone)]
15/// Input data for a "ja4" hash.
16///
17/// Computed using [`Ja4::compute`].
18pub struct Ja4 {
19    protocol: TransportProtocol,
20    version: TlsVersion,
21    has_sni: bool,
22    alpn: Option<ApplicationProtocol>,
23    cipher_suites: Vec<CipherSuite>,
24    extensions: Option<Vec<ExtensionId>>,
25    signature_algorithms: Option<Vec<SignatureScheme>>,
26}
27
28impl Ja4 {
29    /// Compute the [`Ja4`] (hash).
30    ///
31    /// As specified by <https://blog.foxio.io/ja4%2B-network-fingerprinting>
32    /// and reference implementations found at <https://github.com/FoxIO-LLC/ja4>.
33    pub fn compute(ext: &Extensions) -> Result<Self, Ja4ComputeError> {
34        let client_hello = ext
35            .get::<SecureTransport>()
36            .and_then(|st| st.client_hello())
37            .ok_or(Ja4ComputeError::MissingClientHello)?;
38        let negotiated_tls_version = ext
39            .get::<NegotiatedTlsParameters>()
40            .map(|param| param.protocol_version);
41        Self::compute_from_client_hello(client_hello, negotiated_tls_version)
42    }
43
44    /// Compute the [`Ja4`] (hash) from a reference to either a
45    /// [`ClientHello`] or a [`ClientConfig`] data structure.
46    ///
47    /// In case your source is [`Extensions`] you can use [`Self::compute`] instead.
48    ///
49    /// [`ClientHello`]: crate::tls::client::ClientHello
50    /// [`ClientConfig`]: crate::tls::client::ClientConfig
51    pub fn compute_from_client_hello(
52        client_hello: impl ClientHelloProvider,
53        negotiated_tls_version: Option<ProtocolVersion>,
54    ) -> Result<Self, Ja4ComputeError> {
55        let version: TlsVersion = negotiated_tls_version
56            .unwrap_or_else(|| {
57                tracing::trace!("negotiated tls version missing: fallback to client hello tls");
58                client_hello.protocol_version()
59            })
60            .try_into()?;
61
62        let mut cipher_suites: Vec<_> = client_hello
63            .cipher_suites()
64            .filter(|c| !c.is_grease())
65            .collect();
66        if cipher_suites.is_empty() {
67            return Err(Ja4ComputeError::EmptyCipherSuites);
68        }
69        cipher_suites.sort_unstable_by_key(|k| format!("{k:04x}"));
70
71        let mut extensions = None;
72        let mut alpn = None;
73        let mut signature_algorithms = None;
74        let mut protocol = TransportProtocol::Tcp;
75        let mut has_sni = false;
76
77        for ext in client_hello.extensions() {
78            let id = ext.id();
79
80            match id {
81                ExtensionId::QUIC_TRANSPORT_PARAMETERS => {
82                    protocol = TransportProtocol::Quic;
83                }
84                ExtensionId::SERVER_NAME => {
85                    has_sni = true;
86                }
87                _ => {
88                    if id.is_grease() {
89                        continue;
90                    }
91                }
92            }
93
94            extensions.get_or_insert_with(Vec::default).push(id);
95
96            match ext {
97                crate::tls::client::ClientHelloExtension::ApplicationLayerProtocolNegotiation(
98                    alpns,
99                ) => {
100                    alpn = alpns.iter().next().cloned();
101                }
102                crate::tls::client::ClientHelloExtension::SignatureAlgorithms(vec) => {
103                    // this one is the only one not sorted
104                    let vec: Vec<_> = vec.iter().filter(|g| !g.is_grease()).copied().collect();
105                    if !vec.is_empty() {
106                        signature_algorithms = Some(vec)
107                    }
108                }
109                _ => (),
110            }
111        }
112
113        if let Some(extensions) = extensions.as_mut() {
114            extensions.sort_unstable_by_key(|k| format!("{k:04x}"));
115        }
116
117        Ok(Self {
118            protocol,
119            version,
120            has_sni,
121            alpn,
122            cipher_suites,
123            extensions,
124            signature_algorithms,
125        })
126    }
127
128    #[inline]
129    pub fn to_human_string(&self) -> String {
130        format!("{self:?}")
131    }
132
133    fn fmt_as(&self, f: &mut fmt::Formatter<'_>, hash_chunks: bool) -> fmt::Result {
134        let protocol = self.protocol;
135        let version = self.version;
136        let sni_marker = if self.has_sni { 'd' } else { 'i' };
137        let nr_ciphers = 99.min(self.cipher_suites.len());
138        let nr_exts = 99.min(
139            self.extensions
140                .as_ref()
141                .map(|ext| ext.len())
142                .unwrap_or_default(),
143        );
144        let mut alpn_it = self
145            .alpn
146            .as_ref()
147            .and_then(|alpn| std::str::from_utf8(alpn.as_bytes()).ok())
148            .map(|s| s.chars())
149            .into_iter()
150            .flatten();
151        let alpn_0 = alpn_it.next().unwrap_or('0');
152        let alpn_1 = alpn_it.last().unwrap_or('0');
153
154        // JA4_a (AKA first chunk)
155        write!(
156            f,
157            "{protocol}{version}{sni_marker}{nr_ciphers:02}{nr_exts:02}{alpn_0}{alpn_1}"
158        )?;
159
160        // JA4_b (AKA Cipher Suites, sorted)
161        let cipher_suites = self
162            .cipher_suites
163            .iter()
164            .map(|c| format!("{c:04x}"))
165            .join(",");
166
167        // JA4_c (AKA Exts + Sigs)
168        let extensions =
169            self.extensions
170                .as_ref()
171                .map(|e| e.iter())
172                .into_iter()
173                .flatten()
174                .filter_map(|e| match e {
175                    ExtensionId::SERVER_NAME
176                    | ExtensionId::APPLICATION_LAYER_PROTOCOL_NEGOTIATION => None,
177                    _ => Some(format!("{e:04x}")),
178                })
179                .join(",");
180        let signature_algorithms = self
181            .signature_algorithms
182            .as_ref()
183            .map(|s| s.iter())
184            .into_iter()
185            .flatten()
186            .map(|s| format!("{s:04x}"))
187            .join(",");
188        let ext_sig_sep = if signature_algorithms.is_empty() {
189            ""
190        } else {
191            "_"
192        };
193
194        if hash_chunks {
195            write!(
196                f,
197                "_{}_{}",
198                hash12(cipher_suites),
199                hash12(format!(
200                    "{}{}{}",
201                    extensions, ext_sig_sep, signature_algorithms,
202                )),
203            )
204        } else {
205            write!(
206                f,
207                "_{}_{}{}{}",
208                cipher_suites, extensions, ext_sig_sep, signature_algorithms,
209            )
210        }
211    }
212}
213
214impl fmt::Display for Ja4 {
215    #[inline]
216    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217        self.fmt_as(f, true)
218    }
219}
220
221impl fmt::Debug for Ja4 {
222    #[inline]
223    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224        self.fmt_as(f, false)
225    }
226}
227
228fn hash12(s: impl AsRef<str>) -> Cow<'static, str> {
229    use sha2::{Digest as _, Sha256};
230
231    let s = s.as_ref();
232    if s.is_empty() {
233        "000000000000".into()
234    } else {
235        let sha256 = Sha256::digest(s);
236        hex::encode(&sha256.as_slice()[..6]).into()
237    }
238}
239
240#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
241enum TransportProtocol {
242    Tcp,
243    Quic,
244}
245
246impl fmt::Display for TransportProtocol {
247    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248        let s = match self {
249            TransportProtocol::Tcp => "t",
250            TransportProtocol::Quic => "q",
251        };
252        f.write_str(s)
253    }
254}
255
256#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
257enum TlsVersion {
258    Tls1_0,
259    Tls1_1,
260    Tls1_2,
261    Tls1_3,
262}
263
264impl fmt::Display for TlsVersion {
265    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266        let s = match self {
267            TlsVersion::Tls1_0 => "10",
268            TlsVersion::Tls1_1 => "11",
269            TlsVersion::Tls1_2 => "12",
270            TlsVersion::Tls1_3 => "13",
271        };
272        f.write_str(s)
273    }
274}
275
276impl TryFrom<ProtocolVersion> for TlsVersion {
277    type Error = Ja4ComputeError;
278
279    fn try_from(value: ProtocolVersion) -> Result<Self, Self::Error> {
280        match value {
281            ProtocolVersion::SSLv2
282            | ProtocolVersion::SSLv3
283            | ProtocolVersion::TLSv1_0
284            | ProtocolVersion::DTLSv1_0 => Ok(Self::Tls1_0),
285            ProtocolVersion::TLSv1_1 => Ok(Self::Tls1_1),
286            ProtocolVersion::TLSv1_2 | ProtocolVersion::DTLSv1_2 => Ok(Self::Tls1_2),
287            ProtocolVersion::TLSv1_3 | ProtocolVersion::DTLSv1_3 => Ok(Self::Tls1_3),
288            ProtocolVersion::Unknown(_) => Err(Ja4ComputeError::InvalidTlsVersion),
289        }
290    }
291}
292
293#[derive(Debug, Clone)]
294/// error identifying a failure in [`Ja4::compute`]
295pub enum Ja4ComputeError {
296    /// missing [`ClientHello`]
297    MissingClientHello,
298    /// cipher suites was empty
299    EmptyCipherSuites,
300    /// invalid tls version
301    InvalidTlsVersion,
302}
303
304impl fmt::Display for Ja4ComputeError {
305    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306        match self {
307            Ja4ComputeError::MissingClientHello => {
308                write!(f, "Ja4 Compute Error: missing client hello")
309            }
310            Ja4ComputeError::EmptyCipherSuites => {
311                write!(f, "Ja4 Compute Error: empty cipher suites")
312            }
313            Ja4ComputeError::InvalidTlsVersion => {
314                write!(f, "Ja4 Compute Error: invalid tls version")
315            }
316        }
317    }
318}
319
320impl std::error::Error for Ja4ComputeError {}
321
322#[cfg(test)]
323mod tests {
324    use crate::tls::client::parse_client_hello;
325
326    use super::*;
327
328    #[derive(Debug)]
329    struct TestCase {
330        client_hello: Vec<u8>,
331        negotiated_protocol_version: Option<ProtocolVersion>,
332        pcap: &'static str,
333        expected_ja4_str: &'static str,
334        expected_ja4_hash: &'static str,
335    }
336
337    #[test]
338    fn test_ja4_compute() {
339        // src: <https://github.com/jabedude/ja3-rs/blob/a30d1bea03d2230b1239d437c3f6af7fb7699338/src/lib.rs#L380>
340        // + random wireshark
341        // + random curl to echo.ramaproxy.org over http/1.1
342        let test_cases = [
343            TestCase {
344                client_hello: vec![
345                    0x3, 0x3, 0x86, 0xad, 0xa4, 0xcc, 0x19, 0xe7, 0x14, 0x54, 0x54, 0xfd, 0xe7,
346                    0x37, 0x33, 0xdf, 0x66, 0xcb, 0xf6, 0xef, 0x3e, 0xc0, 0xa1, 0x54, 0xc6, 0xdd,
347                    0x14, 0x5e, 0xc0, 0x83, 0xac, 0xb9, 0xb4, 0xe7, 0x20, 0x1c, 0x64, 0xae, 0xa7,
348                    0xa2, 0xc3, 0xe1, 0x8c, 0xd1, 0x25, 0x2, 0x4d, 0xf7, 0x86, 0x4a, 0xc7, 0x19,
349                    0xd0, 0xc4, 0xbd, 0xfb, 0x40, 0xc2, 0xef, 0x7f, 0x6d, 0xd3, 0x9a, 0xa7, 0x53,
350                    0xdf, 0xdd, 0x0, 0x22, 0x1a, 0x1a, 0x13, 0x1, 0x13, 0x2, 0x13, 0x3, 0xc0, 0x2b,
351                    0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30, 0xcc, 0xa9, 0xcc, 0xa8, 0xc0, 0x13, 0xc0,
352                    0x14, 0x0, 0x9c, 0x0, 0x9d, 0x0, 0x2f, 0x0, 0x35, 0x0, 0xa, 0x1, 0x0, 0x1,
353                    0x91, 0xa, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x1e, 0x0, 0x0, 0x1b, 0x67,
354                    0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x61, 0x64, 0x73, 0x2e, 0x67, 0x2e, 0x64, 0x6f,
355                    0x75, 0x62, 0x6c, 0x65, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x2e, 0x6e, 0x65, 0x74,
356                    0x0, 0x17, 0x0, 0x0, 0xff, 0x1, 0x0, 0x1, 0x0, 0x0, 0xa, 0x0, 0xa, 0x0, 0x8,
357                    0x9a, 0x9a, 0x0, 0x1d, 0x0, 0x17, 0x0, 0x18, 0x0, 0xb, 0x0, 0x2, 0x1, 0x0, 0x0,
358                    0x23, 0x0, 0x0, 0x0, 0x10, 0x0, 0xe, 0x0, 0xc, 0x2, 0x68, 0x32, 0x8, 0x68,
359                    0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31, 0x0, 0x5, 0x0, 0x5, 0x1, 0x0, 0x0,
360                    0x0, 0x0, 0x0, 0xd, 0x0, 0x14, 0x0, 0x12, 0x4, 0x3, 0x8, 0x4, 0x4, 0x1, 0x5,
361                    0x3, 0x8, 0x5, 0x5, 0x1, 0x8, 0x6, 0x6, 0x1, 0x2, 0x1, 0x0, 0x12, 0x0, 0x0,
362                    0x0, 0x33, 0x0, 0x2b, 0x0, 0x29, 0x9a, 0x9a, 0x0, 0x1, 0x0, 0x0, 0x1d, 0x0,
363                    0x20, 0x59, 0x8, 0x6f, 0x41, 0x9a, 0xa5, 0xaa, 0x1d, 0x81, 0xe3, 0x47, 0xf0,
364                    0x25, 0x5f, 0x92, 0x7, 0xfc, 0x4b, 0x13, 0x74, 0x51, 0x46, 0x98, 0x8, 0x74,
365                    0x3b, 0xde, 0x57, 0x86, 0xe8, 0x2c, 0x74, 0x0, 0x2d, 0x0, 0x2, 0x1, 0x1, 0x0,
366                    0x2b, 0x0, 0xb, 0xa, 0xfa, 0xfa, 0x3, 0x4, 0x3, 0x3, 0x3, 0x2, 0x3, 0x1, 0x0,
367                    0x1b, 0x0, 0x3, 0x2, 0x0, 0x2, 0xba, 0xba, 0x0, 0x1, 0x0, 0x0, 0x15, 0x0, 0xbd,
368                    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
369                    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
370                    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
371                    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
372                    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
373                    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
374                    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
375                    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
376                    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
377                    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
378                    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
379                    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
380                ],
381                negotiated_protocol_version: Some(ProtocolVersion::TLSv1_3),
382                pcap: "chrome-grease-single.pcap",
383                expected_ja4_str: "t13d1615h2_000a,002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0005,000a,000b,000d,0012,0015,0017,001b,0023,002b,002d,0033,ff01_0403,0804,0401,0503,0805,0501,0806,0601,0201",
384                expected_ja4_hash: "t13d1615h2_46e7e9700bed_45f260be83e2",
385            },
386            TestCase {
387                client_hello: vec![
388                    0x03, 0x03, 0x95, 0xb9, 0xc5, 0xa1, 0x35, 0x0d, 0xc2, 0x47, 0x9d, 0x37, 0x77,
389                    0x94, 0x51, 0x39, 0x08, 0xc1, 0x67, 0x43, 0x08, 0xa4, 0x53, 0xb3, 0x18, 0x7e,
390                    0x0c, 0xde, 0x18, 0xd6, 0x77, 0x1d, 0xd7, 0x0c, 0x20, 0x5b, 0x41, 0xe2, 0xb4,
391                    0xe3, 0x28, 0x26, 0xfd, 0x1a, 0x14, 0xab, 0x14, 0x04, 0x0b, 0xe2, 0xe1, 0x66,
392                    0x12, 0xbd, 0x44, 0x41, 0x38, 0xcd, 0xb3, 0xcf, 0xa1, 0x44, 0xe0, 0xa4, 0xf7,
393                    0x5d, 0x90, 0x00, 0x3e, 0x13, 0x02, 0x13, 0x03, 0x13, 0x01, 0xc0, 0x2c, 0xc0,
394                    0x30, 0x00, 0x9f, 0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0xaa, 0xc0, 0x2b, 0xc0, 0x2f,
395                    0x00, 0x9e, 0xc0, 0x24, 0xc0, 0x28, 0x00, 0x6b, 0xc0, 0x23, 0xc0, 0x27, 0x00,
396                    0x67, 0xc0, 0x0a, 0xc0, 0x14, 0x00, 0x39, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x33,
397                    0x00, 0x9d, 0x00, 0x9c, 0x00, 0x3d, 0x00, 0x3c, 0x00, 0x35, 0x00, 0x2f, 0x00,
398                    0xff, 0x01, 0x00, 0x01, 0x75, 0x00, 0x00, 0x00, 0x17, 0x00, 0x15, 0x00, 0x00,
399                    0x12, 0x65, 0x63, 0x68, 0x6f, 0x2e, 0x72, 0x61, 0x6d, 0x61, 0x70, 0x72, 0x6f,
400                    0x78, 0x79, 0x2e, 0x6f, 0x72, 0x67, 0x00, 0x0b, 0x00, 0x04, 0x03, 0x00, 0x01,
401                    0x02, 0x00, 0x0a, 0x00, 0x16, 0x00, 0x14, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x1e,
402                    0x00, 0x19, 0x00, 0x18, 0x01, 0x00, 0x01, 0x01, 0x01, 0x02, 0x01, 0x03, 0x01,
403                    0x04, 0x33, 0x74, 0x00, 0x00, 0x00, 0x10, 0x00, 0x0b, 0x00, 0x09, 0x08, 0x68,
404                    0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17,
405                    0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x2a, 0x00, 0x28, 0x04,
406                    0x03, 0x05, 0x03, 0x06, 0x03, 0x08, 0x07, 0x08, 0x08, 0x08, 0x09, 0x08, 0x0a,
407                    0x08, 0x0b, 0x08, 0x04, 0x08, 0x05, 0x08, 0x06, 0x04, 0x01, 0x05, 0x01, 0x06,
408                    0x01, 0x03, 0x03, 0x03, 0x01, 0x03, 0x02, 0x04, 0x02, 0x05, 0x02, 0x06, 0x02,
409                    0x00, 0x2b, 0x00, 0x05, 0x04, 0x03, 0x04, 0x03, 0x03, 0x00, 0x2d, 0x00, 0x02,
410                    0x01, 0x01, 0x00, 0x33, 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0xe3,
411                    0x86, 0xb6, 0x7d, 0x52, 0x0e, 0xd1, 0x7f, 0xbe, 0xed, 0xc0, 0xe8, 0xd9, 0x94,
412                    0x4a, 0x7b, 0xff, 0xb8, 0xa0, 0x13, 0xa8, 0x5f, 0xbd, 0x2b, 0x10, 0x51, 0xa1,
413                    0x3f, 0xb2, 0xe3, 0x37, 0x5d, 0x00, 0x15, 0x00, 0xae, 0x00, 0x00, 0x00, 0x00,
414                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
415                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
416                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
417                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
418                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
419                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
420                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
421                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
422                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
423                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
424                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
425                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
426                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
427                    0x00,
428                ],
429                negotiated_protocol_version: Some(ProtocolVersion::TLSv1_3),
430                pcap: "curl_http1.1.pcap",
431                expected_ja4_str: "t13d3113h1_002f,0033,0035,0039,003c,003d,0067,006b,009c,009d,009e,009f,00ff,1301,1302,1303,c009,c00a,c013,c014,c023,c024,c027,c028,c02b,c02c,c02f,c030,cca8,cca9,ccaa_000a,000b,000d,0015,0016,0017,002b,002d,0031,0033,3374_0403,0503,0603,0807,0808,0809,080a,080b,0804,0805,0806,0401,0501,0601,0303,0301,0302,0402,0502,0602",
432                expected_ja4_hash: "t13d3113h1_e8f1e7e78f70_ce5650b735ce",
433            },
434            TestCase {
435                client_hello: vec![
436                    0x3, 0x3, 0xf6, 0x65, 0xb, 0x22, 0x13, 0xf1, 0xc3, 0xe9, 0xe7, 0xb3, 0xdc, 0x9,
437                    0xe4, 0x4b, 0xcb, 0x6e, 0x5, 0xaf, 0x8f, 0x2f, 0x41, 0x8d, 0x15, 0xa8, 0x88,
438                    0x46, 0x24, 0x83, 0xca, 0x9, 0x7c, 0x95, 0x20, 0x12, 0xc4, 0x5e, 0x71, 0x8b,
439                    0xb9, 0xc9, 0xa9, 0x37, 0x93, 0x4c, 0x41, 0xa6, 0xe8, 0x9e, 0x8f, 0x15, 0x78,
440                    0x52, 0xe, 0x3c, 0x28, 0xba, 0xab, 0xa3, 0x34, 0x8b, 0x53, 0x82, 0x83, 0x75,
441                    0x24, 0x0, 0x3e, 0x13, 0x2, 0x13, 0x3, 0x13, 0x1, 0xc0, 0x2c, 0xc0, 0x30, 0x0,
442                    0x9f, 0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0xaa, 0xc0, 0x2b, 0xc0, 0x2f, 0x0, 0x9e,
443                    0xc0, 0x24, 0xc0, 0x28, 0x0, 0x6b, 0xc0, 0x23, 0xc0, 0x27, 0x0, 0x67, 0xc0,
444                    0xa, 0xc0, 0x14, 0x0, 0x39, 0xc0, 0x9, 0xc0, 0x13, 0x0, 0x33, 0x0, 0x9d, 0x0,
445                    0x9c, 0x0, 0x3d, 0x0, 0x3c, 0x0, 0x35, 0x0, 0x2f, 0x0, 0xff, 0x1, 0x0, 0x1,
446                    0x75, 0x0, 0x0, 0x0, 0x10, 0x0, 0xe, 0x0, 0x0, 0xb, 0x65, 0x78, 0x61, 0x6d,
447                    0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x0, 0xb, 0x0, 0x4, 0x3, 0x0, 0x1,
448                    0x2, 0x0, 0xa, 0x0, 0xc, 0x0, 0xa, 0x0, 0x1d, 0x0, 0x17, 0x0, 0x1e, 0x0, 0x19,
449                    0x0, 0x18, 0x33, 0x74, 0x0, 0x0, 0x0, 0x10, 0x0, 0xe, 0x0, 0xc, 0x2, 0x68,
450                    0x32, 0x8, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31, 0x0, 0x16, 0x0, 0x0,
451                    0x0, 0x17, 0x0, 0x0, 0x0, 0xd, 0x0, 0x30, 0x0, 0x2e, 0x4, 0x3, 0x5, 0x3, 0x6,
452                    0x3, 0x8, 0x7, 0x8, 0x8, 0x8, 0x9, 0x8, 0xa, 0x8, 0xb, 0x8, 0x4, 0x8, 0x5, 0x8,
453                    0x6, 0x4, 0x1, 0x5, 0x1, 0x6, 0x1, 0x3, 0x3, 0x2, 0x3, 0x3, 0x1, 0x2, 0x1, 0x3,
454                    0x2, 0x2, 0x2, 0x4, 0x2, 0x5, 0x2, 0x6, 0x2, 0x0, 0x2b, 0x0, 0x9, 0x8, 0x3,
455                    0x4, 0x3, 0x3, 0x3, 0x2, 0x3, 0x1, 0x0, 0x2d, 0x0, 0x2, 0x1, 0x1, 0x0, 0x33,
456                    0x0, 0x26, 0x0, 0x24, 0x0, 0x1d, 0x0, 0x20, 0x37, 0x98, 0x48, 0x7f, 0x2f, 0xbc,
457                    0x86, 0xf9, 0xb8, 0x2, 0xcd, 0x31, 0xf0, 0x4, 0x30, 0xa9, 0x2f, 0x29, 0x61,
458                    0xac, 0xec, 0xc9, 0x2f, 0xf7, 0x45, 0xad, 0xd9, 0x67, 0x7, 0x14, 0x62, 0x1,
459                    0x0, 0x15, 0x0, 0xb6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
460                    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
461                    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
462                    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
463                    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
464                    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
465                    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
466                    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
467                    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
468                    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
469                    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
470                    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
471                ],
472                negotiated_protocol_version: Some(ProtocolVersion::TLSv1_3),
473                pcap: "curl.pcap",
474                expected_ja4_str: "t13d3112h2_002f,0033,0035,0039,003c,003d,0067,006b,009c,009d,009e,009f,00ff,1301,1302,1303,c009,c00a,c013,c014,c023,c024,c027,c028,c02b,c02c,c02f,c030,cca8,cca9,ccaa_000a,000b,000d,0015,0016,0017,002b,002d,0033,3374_0403,0503,0603,0807,0808,0809,080a,080b,0804,0805,0806,0401,0501,0601,0303,0203,0301,0201,0302,0202,0402,0502,0602",
475                expected_ja4_hash: "t13d3112h2_e8f1e7e78f70_f4b9272caa35",
476            },
477            TestCase {
478                client_hello: vec![
479                    0x3, 0x3, 0x14, 0x67, 0xca, 0x9a, 0xe4, 0x41, 0xc2, 0x31, 0xe7, 0xa4, 0x87,
480                    0xfa, 0x83, 0xdf, 0x5c, 0xe4, 0xa1, 0x9d, 0xa1, 0x42, 0x39, 0xda, 0xd, 0xf0,
481                    0x3e, 0xc3, 0xfb, 0xb3, 0xaf, 0xec, 0x5b, 0x14, 0x20, 0x6e, 0xd5, 0x9f, 0x39,
482                    0x1d, 0x5e, 0x20, 0x51, 0x38, 0xdc, 0x63, 0x5d, 0xe0, 0xbf, 0x1b, 0xff, 0xa0,
483                    0x3d, 0xde, 0x20, 0x59, 0x33, 0x40, 0x30, 0x6e, 0x31, 0x2c, 0xdf, 0x8e, 0x7a,
484                    0xd5, 0xe9, 0x0, 0x22, 0x13, 0x1, 0x13, 0x3, 0x13, 0x2, 0xc0, 0x2b, 0xc0, 0x2f,
485                    0xcc, 0xa9, 0xcc, 0xa8, 0xc0, 0x2c, 0xc0, 0x30, 0xc0, 0xa, 0xc0, 0x9, 0xc0,
486                    0x13, 0xc0, 0x14, 0x0, 0x9c, 0x0, 0x9d, 0x0, 0x2f, 0x0, 0x35, 0x1, 0x0, 0x6,
487                    0xf2, 0x0, 0x0, 0x0, 0x12, 0x0, 0x10, 0x0, 0x0, 0xd, 0x72, 0x61, 0x6d, 0x61,
488                    0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x6f, 0x72, 0x67, 0x0, 0x17, 0x0, 0x0,
489                    0xff, 0x1, 0x0, 0x1, 0x0, 0x0, 0xa, 0x0, 0x10, 0x0, 0xe, 0x11, 0xec, 0x0, 0x1d,
490                    0x0, 0x17, 0x0, 0x18, 0x0, 0x19, 0x1, 0x0, 0x1, 0x1, 0x0, 0xb, 0x0, 0x2, 0x1,
491                    0x0, 0x0, 0x23, 0x0, 0x0, 0x0, 0x10, 0x0, 0xe, 0x0, 0xc, 0x2, 0x68, 0x32, 0x8,
492                    0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31, 0x0, 0x5, 0x0, 0x5, 0x1, 0x0,
493                    0x0, 0x0, 0x0, 0x0, 0x22, 0x0, 0xa, 0x0, 0x8, 0x4, 0x3, 0x5, 0x3, 0x6, 0x3,
494                    0x2, 0x3, 0x0, 0x33, 0x5, 0x2f, 0x5, 0x2d, 0x11, 0xec, 0x4, 0xc0, 0x75, 0xe5,
495                    0x3, 0xee, 0x1c, 0xb6, 0x50, 0xc2, 0x40, 0x22, 0xfc, 0xa1, 0x70, 0x8, 0xcd,
496                    0xda, 0x74, 0xbc, 0x49, 0xd0, 0xb, 0xad, 0x34, 0xb4, 0xdf, 0x78, 0xb, 0x90,
497                    0x61, 0x29, 0xd0, 0xd6, 0x67, 0x98, 0xa0, 0x2a, 0x50, 0x95, 0x10, 0x65, 0x94,
498                    0x8d, 0xe3, 0x9, 0x38, 0xe7, 0xf5, 0xc5, 0xae, 0xfb, 0x43, 0xf9, 0x86, 0xa8,
499                    0xf2, 0xdc, 0x78, 0xfd, 0xd3, 0x31, 0x87, 0x16, 0xbf, 0xa8, 0x90, 0x58, 0xd1,
500                    0xa7, 0x6b, 0x56, 0x2a, 0xb1, 0xd5, 0x92, 0x6f, 0x9a, 0x89, 0x25, 0x20, 0xa,
501                    0x7b, 0x87, 0xcc, 0x6d, 0x61, 0xf8, 0x9f, 0x70, 0xb3, 0x97, 0x84, 0x10, 0xbd,
502                    0x58, 0x46, 0xb, 0x88, 0xbc, 0x39, 0x53, 0xfa, 0x6c, 0x48, 0x5a, 0xbd, 0x67,
503                    0x3, 0x3a, 0x7, 0x2, 0x58, 0xb9, 0x25, 0x2e, 0xb0, 0xe5, 0xa, 0x52, 0xa, 0xba,
504                    0x11, 0xcb, 0x1e, 0xdf, 0x63, 0xa0, 0x3, 0x98, 0x1e, 0x14, 0x3a, 0x6b, 0x8a,
505                    0x94, 0x9d, 0x48, 0xd7, 0xc, 0xa5, 0xd3, 0x71, 0x6a, 0x16, 0x97, 0xf1, 0xba,
506                    0x8b, 0x15, 0xbc, 0xa1, 0x51, 0x67, 0x2, 0xfd, 0xfc, 0x5d, 0xc0, 0x72, 0x2a,
507                    0x95, 0x9c, 0x1d, 0x15, 0xe6, 0xb7, 0xab, 0x12, 0x9a, 0xd3, 0x49, 0x83, 0x19,
508                    0xfc, 0x10, 0x6e, 0x6a, 0x3d, 0x89, 0xf2, 0xa1, 0x64, 0x3, 0x6a, 0x4d, 0xc,
509                    0xcd, 0x46, 0x53, 0x75, 0xb3, 0x77, 0x69, 0xd4, 0x61, 0x81, 0x8d, 0x3a, 0x94,
510                    0x64, 0xac, 0xa2, 0xa7, 0x7c, 0xc, 0x2a, 0x5c, 0xe, 0xf, 0x45, 0x9e, 0x92,
511                    0xf4, 0x1, 0x42, 0x3b, 0x85, 0x15, 0xd9, 0x9a, 0xa5, 0xb6, 0x5b, 0xd0, 0x26,
512                    0x7e, 0x49, 0xcc, 0x3e, 0x2f, 0x82, 0x7, 0xc1, 0x81, 0xaa, 0xaf, 0xa4, 0x13,
513                    0x32, 0xb0, 0x96, 0x82, 0xc2, 0xcb, 0x1, 0xf2, 0x54, 0x49, 0x93, 0x44, 0x1,
514                    0x15, 0x90, 0x3a, 0xd1, 0x52, 0x2a, 0x78, 0x23, 0x2d, 0x78, 0x61, 0xa2, 0xa7,
515                    0xaa, 0x83, 0xd3, 0xbb, 0x8e, 0x2a, 0x6e, 0xd, 0xc8, 0x95, 0x73, 0x6, 0x2f,
516                    0xf0, 0xd2, 0x7a, 0x80, 0xda, 0xb, 0xdf, 0x4, 0x85, 0xcb, 0x19, 0x81, 0x16,
517                    0x99, 0x47, 0xd3, 0xbc, 0x3c, 0x9d, 0xb4, 0x19, 0x1c, 0x40, 0x9c, 0x6e, 0x95,
518                    0x1, 0xe, 0x94, 0x82, 0x26, 0xd1, 0x10, 0x55, 0x97, 0x76, 0xe, 0x2a, 0x53,
519                    0x2a, 0x75, 0x7b, 0xdc, 0xf7, 0x16, 0x2d, 0x84, 0x69, 0x3e, 0xfa, 0x3f, 0xed,
520                    0x4, 0x20, 0x58, 0x7c, 0x9, 0xee, 0x41, 0x9c, 0x4a, 0x25, 0x6, 0x2f, 0x29,
521                    0x3d, 0x6, 0xac, 0x48, 0x2e, 0xd1, 0x65, 0xd9, 0x85, 0x74, 0xf0, 0xf8, 0x35,
522                    0xcd, 0x14, 0x5f, 0x9c, 0x89, 0x4b, 0x39, 0xc0, 0xa4, 0x6f, 0x36, 0x39, 0x8,
523                    0x70, 0xb4, 0xa4, 0x8, 0x4e, 0x6e, 0xd4, 0x27, 0x93, 0xb0, 0x22, 0x34, 0xfc,
524                    0x52, 0xd8, 0x4a, 0x48, 0xd4, 0xf9, 0x9a, 0x89, 0xdc, 0xbf, 0xc8, 0x73, 0x77,
525                    0xca, 0x64, 0x7, 0x8c, 0x2c, 0x95, 0x23, 0x43, 0x4a, 0x8a, 0xa6, 0xa5, 0xcc,
526                    0xc, 0xc3, 0xc9, 0x6, 0x7e, 0xcd, 0xbc, 0x7, 0xbd, 0x55, 0x1f, 0x32, 0x64,
527                    0x1b, 0x9b, 0xc9, 0x7e, 0xc7, 0xa, 0x79, 0x96, 0x48, 0xb9, 0xfa, 0x26, 0xa9,
528                    0x9c, 0xf7, 0x3d, 0x8f, 0xb4, 0xa9, 0x90, 0x36, 0x23, 0xe4, 0x93, 0x9b, 0x9b,
529                    0xda, 0x5a, 0x44, 0x10, 0xcf, 0xcd, 0xb5, 0x1d, 0x55, 0xe4, 0xaa, 0x11, 0x6a,
530                    0x89, 0xca, 0x53, 0x94, 0xc8, 0xa1, 0x0, 0x11, 0x96, 0xca, 0xb4, 0x5a, 0xb4,
531                    0x1d, 0x50, 0x1e, 0x3a, 0xd0, 0x5f, 0xa1, 0x41, 0x58, 0x11, 0xf6, 0x62, 0x61,
532                    0x65, 0xc4, 0x4a, 0x28, 0x9a, 0x81, 0x6b, 0x9f, 0x8a, 0x67, 0x7e, 0x1a, 0x55,
533                    0x10, 0xa4, 0xe7, 0x54, 0x25, 0xc6, 0x83, 0xf9, 0xe8, 0x54, 0x75, 0x39, 0x76,
534                    0x69, 0x27, 0x1e, 0x72, 0xc5, 0x3c, 0xdf, 0x43, 0x9b, 0xbc, 0x9c, 0x4a, 0x1a,
535                    0x91, 0x63, 0xd, 0x94, 0x58, 0x22, 0xf2, 0xa7, 0x99, 0x27, 0x5, 0x51, 0x13,
536                    0x1f, 0xfa, 0xf8, 0x5c, 0x46, 0xf6, 0x83, 0xab, 0x82, 0xa5, 0xe, 0xc2, 0xaf,
537                    0x96, 0x48, 0xa8, 0xf8, 0x1a, 0x32, 0x3d, 0xc1, 0xb0, 0x2d, 0x41, 0x71, 0x85,
538                    0xf2, 0xc6, 0x27, 0x9b, 0xbc, 0x23, 0xa9, 0x57, 0x8, 0xf5, 0xf, 0xa9, 0x4c,
539                    0x92, 0xbd, 0xd1, 0xa4, 0x13, 0x9a, 0xad, 0x3, 0x16, 0x34, 0xbe, 0xf1, 0xa3,
540                    0xe0, 0x50, 0x56, 0x46, 0xfc, 0x49, 0x4, 0xc3, 0x2c, 0xdb, 0x55, 0x6, 0xcb,
541                    0x78, 0x4e, 0xa4, 0xc7, 0x3f, 0xb3, 0xf2, 0x44, 0x56, 0x30, 0xb9, 0x76, 0x32,
542                    0x36, 0x2, 0x4b, 0xaa, 0x9, 0x63, 0xd, 0xd4, 0x40, 0x98, 0xfd, 0x13, 0x99,
543                    0x3b, 0x1b, 0x6b, 0x87, 0xdb, 0xa8, 0xc, 0xe2, 0xe, 0x38, 0x6b, 0x6d, 0x41,
544                    0xf1, 0x1c, 0x56, 0x25, 0x1b, 0x8b, 0x1b, 0x67, 0x8c, 0xe7, 0x2b, 0xea, 0x42,
545                    0x61, 0xbe, 0x5b, 0xa7, 0x64, 0x8a, 0xa4, 0xb1, 0x57, 0x19, 0x2e, 0xf2, 0x71,
546                    0xe3, 0xa8, 0x27, 0xd1, 0xa9, 0x1, 0x2, 0x87, 0xf, 0x23, 0x88, 0x1a, 0x10,
547                    0x54, 0x7f, 0x0, 0xaa, 0x56, 0x1d, 0x28, 0x6f, 0xff, 0xb9, 0x87, 0x8d, 0xc0,
548                    0x54, 0x67, 0xd8, 0x3e, 0x52, 0x6a, 0x3d, 0x25, 0xab, 0x62, 0x8a, 0x78, 0x94,
549                    0xf0, 0x4, 0xbb, 0x8c, 0x1a, 0x4b, 0x13, 0xf4, 0x95, 0x16, 0xe7, 0x55, 0xdf,
550                    0x21, 0x1d, 0xfb, 0x86, 0xc8, 0x70, 0xb9, 0xcd, 0xef, 0x7b, 0x8c, 0xbd, 0x13,
551                    0x1f, 0x6b, 0xbc, 0x5f, 0xff, 0xa5, 0x14, 0x7a, 0x81, 0x31, 0x28, 0x41, 0xc0,
552                    0xbf, 0x87, 0x84, 0xa8, 0xdb, 0x39, 0x5e, 0xf5, 0x51, 0x4f, 0x5a, 0x3f, 0xa4,
553                    0x4c, 0x4f, 0x6b, 0xca, 0x64, 0xe1, 0x46, 0x10, 0x6b, 0xe8, 0xa7, 0x12, 0x9a,
554                    0x4d, 0xe0, 0xe1, 0x45, 0x4a, 0xf8, 0xf, 0xfe, 0x36, 0x76, 0x1a, 0x7a, 0x17,
555                    0xe5, 0x4b, 0x5c, 0x8f, 0x98, 0x76, 0x41, 0x74, 0x8e, 0xfc, 0x47, 0x4f, 0x22,
556                    0xe2, 0x4, 0x23, 0x63, 0xa3, 0x56, 0xac, 0x6, 0x47, 0xa3, 0x47, 0x80, 0x2a,
557                    0x49, 0xbc, 0x76, 0x84, 0x70, 0x54, 0x52, 0xd1, 0xf5, 0x74, 0x2f, 0xe1, 0xba,
558                    0x26, 0xa1, 0x72, 0xf0, 0x8b, 0x4a, 0xee, 0xa4, 0x12, 0x3, 0x78, 0x17, 0x1f,
559                    0x20, 0xbf, 0xa5, 0x52, 0x93, 0x70, 0xe1, 0x73, 0x6d, 0x99, 0x93, 0x7e, 0xe5,
560                    0x59, 0x11, 0x23, 0x9a, 0xb1, 0x47, 0xa2, 0xd6, 0xc1, 0x48, 0x3a, 0x71, 0x84,
561                    0x7a, 0x27, 0x6f, 0x6, 0xc6, 0x45, 0x24, 0xd5, 0x48, 0xe5, 0x88, 0x22, 0x4f,
562                    0xdb, 0xb4, 0x97, 0x94, 0x93, 0x1b, 0x8a, 0x61, 0xca, 0x94, 0xcc, 0x7b, 0x89,
563                    0x58, 0x55, 0xd9, 0x3a, 0x4b, 0x9c, 0x4b, 0xd2, 0xfc, 0xc4, 0x5f, 0x7c, 0x9d,
564                    0x53, 0xf8, 0x70, 0xcb, 0xf8, 0x40, 0x52, 0x1b, 0x7e, 0x60, 0xf9, 0x64, 0xa,
565                    0x20, 0x5d, 0xe2, 0x62, 0xa3, 0x6b, 0x83, 0xc4, 0x8b, 0x25, 0x54, 0xde, 0xc3,
566                    0x40, 0x77, 0x65, 0xb1, 0xbc, 0xc3, 0xaa, 0xe8, 0xb2, 0x29, 0xd3, 0xa5, 0x42,
567                    0x1c, 0xe7, 0xcb, 0x8f, 0x22, 0xc6, 0x3d, 0x1b, 0x1a, 0x72, 0x1c, 0xba, 0xd7,
568                    0x6a, 0x7b, 0xf, 0x96, 0xc6, 0x47, 0x57, 0x30, 0x88, 0xa7, 0x9f, 0x97, 0xf1,
569                    0x7c, 0x7d, 0x55, 0xbf, 0xf4, 0x1, 0xcd, 0xa1, 0xe0, 0xc6, 0x29, 0xba, 0x26,
570                    0x86, 0x9a, 0x35, 0x3b, 0xb9, 0x39, 0x39, 0x24, 0x32, 0x19, 0x12, 0x6b, 0xb6,
571                    0x2b, 0x39, 0xee, 0x8a, 0x21, 0xe5, 0x17, 0x3b, 0xd4, 0x5b, 0x2d, 0x6c, 0xdb,
572                    0xa7, 0x49, 0xf8, 0x47, 0x68, 0x9b, 0x73, 0xfa, 0xc9, 0x33, 0x23, 0xf0, 0x47,
573                    0x4a, 0x82, 0xa5, 0x7f, 0x37, 0x45, 0x4e, 0x56, 0x83, 0x4c, 0xb2, 0x7f, 0x3,
574                    0x70, 0x34, 0xd3, 0xcb, 0x37, 0xe9, 0x7a, 0x88, 0x52, 0x2b, 0xd, 0x6f, 0xfc,
575                    0x40, 0x80, 0x75, 0x8a, 0x9a, 0xbb, 0x40, 0x53, 0x4a, 0x55, 0xe8, 0xca, 0xaa,
576                    0xa1, 0x79, 0x54, 0x22, 0x8a, 0x72, 0x81, 0x85, 0x71, 0xeb, 0x95, 0x2d, 0x15,
577                    0xeb, 0xbb, 0xa5, 0xb6, 0x9e, 0x99, 0xa9, 0x58, 0x1b, 0x15, 0x3d, 0xe0, 0x12,
578                    0x70, 0xf5, 0xba, 0x45, 0xee, 0x94, 0x92, 0x3d, 0xbb, 0xbd, 0xeb, 0xa9, 0x4e,
579                    0xc9, 0x7a, 0x15, 0x33, 0xb2, 0x8b, 0x32, 0xf0, 0x8f, 0x4, 0xd6, 0x66, 0x42,
580                    0x86, 0x30, 0xd8, 0x40, 0xb4, 0xda, 0xa3, 0x63, 0xab, 0x17, 0x9, 0x57, 0x83,
581                    0x5a, 0xb2, 0x75, 0xb9, 0x9, 0xb2, 0x3d, 0x34, 0xfb, 0x1, 0xfe, 0x29, 0x4b,
582                    0x91, 0xd5, 0x8c, 0x42, 0x5b, 0xb6, 0x37, 0x52, 0xcf, 0xf2, 0xfb, 0x9, 0x17,
583                    0x37, 0x88, 0x2, 0x2a, 0x8, 0x45, 0x33, 0x5b, 0xab, 0xba, 0x65, 0x4d, 0x9f,
584                    0x4e, 0x8a, 0xaa, 0xc2, 0xdf, 0xa8, 0x39, 0xa2, 0x4b, 0xad, 0xf0, 0x67, 0xd9,
585                    0x9e, 0x1, 0x9, 0x85, 0x77, 0x6, 0x4e, 0x7b, 0xd1, 0x54, 0xa5, 0xd5, 0x86,
586                    0xbe, 0x29, 0xdc, 0x49, 0x4b, 0xc4, 0xd7, 0xef, 0xee, 0x4f, 0xd1, 0x92, 0x35,
587                    0xb4, 0xc, 0xeb, 0x8, 0xfc, 0x2b, 0x8f, 0x27, 0x1, 0xa9, 0xc8, 0x7e, 0x6a,
588                    0x67, 0xb1, 0x3b, 0x2, 0x0, 0x1d, 0x0, 0x20, 0xd5, 0x86, 0xbe, 0x29, 0xdc,
589                    0x49, 0x4b, 0xc4, 0xd7, 0xef, 0xee, 0x4f, 0xd1, 0x92, 0x35, 0xb4, 0xc, 0xeb,
590                    0x8, 0xfc, 0x2b, 0x8f, 0x27, 0x1, 0xa9, 0xc8, 0x7e, 0x6a, 0x67, 0xb1, 0x3b,
591                    0x2, 0x0, 0x17, 0x0, 0x41, 0x4, 0x31, 0xca, 0xf3, 0xfb, 0x90, 0xe5, 0x48, 0x3f,
592                    0x20, 0xd6, 0xbb, 0x7d, 0x93, 0x4f, 0xdb, 0x66, 0x9a, 0x76, 0x9a, 0x1a, 0x5,
593                    0x6e, 0xf5, 0xc, 0x87, 0xb1, 0x18, 0xf8, 0x53, 0xdb, 0x3e, 0xa3, 0x45, 0xf,
594                    0x92, 0x1e, 0x72, 0xc5, 0x8a, 0x3, 0x81, 0xe6, 0xa, 0x3d, 0xcf, 0xa7, 0x21,
595                    0xf3, 0x11, 0x2d, 0xe6, 0x74, 0x98, 0x5f, 0xdb, 0x10, 0x8b, 0x3c, 0xf, 0xc5,
596                    0x81, 0x14, 0xc9, 0x2d, 0x0, 0x2b, 0x0, 0x5, 0x4, 0x3, 0x4, 0x3, 0x3, 0x0, 0xd,
597                    0x0, 0x18, 0x0, 0x16, 0x4, 0x3, 0x5, 0x3, 0x6, 0x3, 0x8, 0x4, 0x8, 0x5, 0x8,
598                    0x6, 0x4, 0x1, 0x5, 0x1, 0x6, 0x1, 0x2, 0x3, 0x2, 0x1, 0x0, 0x2d, 0x0, 0x2,
599                    0x1, 0x1, 0x0, 0x1c, 0x0, 0x2, 0x40, 0x1, 0x0, 0x1b, 0x0, 0x7, 0x6, 0x0, 0x1,
600                    0x0, 0x2, 0x0, 0x3, 0xfe, 0xd, 0x1, 0x19, 0x0, 0x0, 0x1, 0x0, 0x3, 0x27, 0x0,
601                    0x20, 0x22, 0x99, 0x27, 0x41, 0x4c, 0x83, 0x54, 0xfc, 0x61, 0x30, 0x2f, 0x43,
602                    0xb8, 0xce, 0xdc, 0xdf, 0xae, 0xee, 0xb6, 0xe0, 0x48, 0xfe, 0x92, 0x3, 0x32,
603                    0x44, 0x97, 0xfb, 0xd3, 0xa6, 0x0, 0x76, 0x0, 0xef, 0x50, 0x2e, 0x32, 0x7f,
604                    0x5c, 0x8f, 0xaf, 0xb5, 0x59, 0xdd, 0x60, 0xa3, 0x54, 0xbc, 0x16, 0xe3, 0x15,
605                    0xd8, 0x14, 0xa2, 0x13, 0x7e, 0xe, 0xb6, 0x6b, 0x5b, 0xf1, 0x97, 0xa3, 0x52,
606                    0x16, 0xa6, 0x3f, 0x9b, 0xd4, 0x70, 0x9e, 0xec, 0x3a, 0x7b, 0xf4, 0x30, 0x28,
607                    0x8b, 0x71, 0x93, 0x29, 0x6, 0xda, 0xc1, 0x18, 0x40, 0xf, 0xf7, 0xd2, 0x19,
608                    0x3c, 0x76, 0x32, 0x38, 0x66, 0xe6, 0x78, 0x19, 0x76, 0x5b, 0x99, 0x2, 0xeb,
609                    0x6b, 0xbc, 0x61, 0x37, 0xd4, 0x42, 0x3d, 0x74, 0x74, 0xf3, 0xca, 0xf9, 0x38,
610                    0xb6, 0x9f, 0x8b, 0xfb, 0xea, 0x3b, 0x18, 0x2e, 0x0, 0x58, 0x71, 0x3, 0xd0,
611                    0xa6, 0xaf, 0xe1, 0x66, 0x64, 0x17, 0x73, 0xeb, 0xc9, 0x38, 0x4c, 0xa, 0xf6,
612                    0xaf, 0x7a, 0x9b, 0xe, 0xbe, 0x52, 0x92, 0x8a, 0xf0, 0x7c, 0x82, 0x70, 0xe,
613                    0xbe, 0xe3, 0x65, 0xe0, 0xbc, 0x95, 0xdf, 0x3c, 0xe8, 0x13, 0x38, 0xf4, 0x41,
614                    0xb0, 0x29, 0xb9, 0xdd, 0x8a, 0xb, 0x4c, 0xc6, 0x0, 0xd, 0x20, 0x76, 0xd9,
615                    0xaa, 0x82, 0x14, 0xb9, 0xfa, 0x34, 0x23, 0x83, 0xb8, 0xd2, 0xb3, 0x97, 0xc1,
616                    0x26, 0x44, 0x3a, 0x22, 0x55, 0xe9, 0x7f, 0x4c, 0x3f, 0xf5, 0xac, 0xf1, 0xd2,
617                    0x95, 0x94, 0xa7, 0x2a, 0x33, 0x20, 0x53, 0xcc, 0xac, 0xd6, 0xd6, 0x89, 0x84,
618                    0xed, 0xcf, 0xc9, 0x6f, 0x85, 0x2a, 0x14, 0x42, 0x3, 0x74, 0x9, 0xd3, 0xd3,
619                    0xb, 0xfb, 0x6, 0xf3, 0xcb, 0x37, 0x41, 0xc3, 0x13, 0xd6, 0xca, 0x9b, 0x53,
620                    0x17, 0x22, 0xfd, 0x52, 0xdf, 0x28, 0x9e, 0x13, 0xd8, 0xfd, 0x95, 0x3b, 0xb1,
621                    0x5a, 0xc8, 0x14, 0x23, 0xb, 0x4b, 0xf, 0x22, 0x85, 0xe7, 0x1c, 0x3b, 0xbc,
622                    0xd3,
623                ],
624                negotiated_protocol_version: Some(ProtocolVersion::TLSv1_3),
625                pcap: "wireshark_macos_firefox_133_ramaproxy.org.pcap",
626                expected_ja4_str: "t13d1716h2_002f,0035,009c,009d,1301,1302,1303,c009,c00a,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0005,000a,000b,000d,0017,001b,001c,0022,0023,002b,002d,0033,fe0d,ff01_0403,0503,0603,0804,0805,0806,0401,0501,0601,0203,0201",
627                expected_ja4_hash: "t13d1716h2_5b57614c22b0_eeeea6562960",
628            },
629        ];
630        for test_case in test_cases {
631            let mut ext = Extensions::new();
632            ext.insert(SecureTransport::with_client_hello(
633                parse_client_hello(&test_case.client_hello).expect(test_case.pcap),
634            ));
635            if let Some(negotiated_protocol_version) = test_case.negotiated_protocol_version {
636                ext.insert(NegotiatedTlsParameters {
637                    protocol_version: negotiated_protocol_version,
638                    application_layer_protocol: None,
639                    peer_certificate_chain: None,
640                });
641            }
642
643            let ja4 = Ja4::compute(&ext).expect(test_case.pcap);
644
645            assert_eq!(
646                test_case.expected_ja4_str,
647                format!("{ja4:?}"),
648                "pcap: {}",
649                test_case.pcap,
650            );
651
652            assert_eq!(
653                test_case.expected_ja4_hash,
654                format!("{ja4}"),
655                "pcap: {}",
656                test_case.pcap,
657            );
658        }
659    }
660}