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)]
15pub 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 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 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 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 write!(
156 f,
157 "{protocol}{version}{sni_marker}{nr_ciphers:02}{nr_exts:02}{alpn_0}{alpn_1}"
158 )?;
159
160 let cipher_suites = self
162 .cipher_suites
163 .iter()
164 .map(|c| format!("{c:04x}"))
165 .join(",");
166
167 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)]
294pub enum Ja4ComputeError {
296 MissingClientHello,
298 EmptyCipherSuites,
300 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 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}