use std::hint::black_box;
use criterion::{criterion_group, criterion_main, Criterion};
use ja4::parse_ja4;
use rand::RngExt;
fn generate_packets(count: usize) -> Vec<Vec<u8>> {
let mut rng = rand::rng();
let mut packets = Vec::with_capacity(count);
for _ in 0..count {
let mut handshake = vec![0x03, 0x03];
for _ in 0..32 {
handshake.push(rng.random::<u8>());
}
let sess_len = rng.random_range(0..=32);
handshake.push(sess_len as u8);
for _ in 0..sess_len {
handshake.push(rng.random::<u8>());
}
let num_ciphers: u16 = rng.random_range(10..=40);
handshake.extend_from_slice(&(num_ciphers * 2).to_be_bytes());
for _ in 0..num_ciphers {
handshake.extend_from_slice(&rng.random::<u16>().to_be_bytes());
}
handshake.extend_from_slice(&[0x01, 0x00]);
let mut extensions_payload = Vec::new();
let num_extensions = rng.random_range(5..=19);
for i in 0..num_extensions {
let ext_type = match i {
0 => 0x0000, 1 => 0x0010, 2 => 0x000d, 3 => 0x002b, _ => rng.random::<u16>(),
};
extensions_payload.extend_from_slice(&ext_type.to_be_bytes());
if ext_type == 0x0000 {
extensions_payload.extend_from_slice(&[0x00, 0x00]); } else if ext_type == 0x0010 {
let alpn_len: u16 = rng.random_range(2..=11);
extensions_payload.extend_from_slice(&(3 + alpn_len).to_be_bytes());
extensions_payload.extend_from_slice(&(1 + alpn_len).to_be_bytes());
extensions_payload.push(alpn_len as u8);
for _ in 0..alpn_len {
extensions_payload.push(b'a' + rng.random_range(0..26) as u8);
}
} else if ext_type == 0x000d {
let num_sigs: u16 = rng.random_range(2..=11);
extensions_payload.extend_from_slice(&(2 + num_sigs * 2).to_be_bytes());
extensions_payload.extend_from_slice(&(num_sigs * 2).to_be_bytes());
for _ in 0..num_sigs {
extensions_payload.extend_from_slice(&rng.random::<u16>().to_be_bytes());
}
} else if ext_type == 0x002b {
extensions_payload.extend_from_slice(&[0x00, 0x03, 0x02, 0x03, 0x04]);
} else {
let ext_len: u16 = rng.random_range(0..=15);
extensions_payload.extend_from_slice(&ext_len.to_be_bytes());
for _ in 0..ext_len {
extensions_payload.push(rng.random::<u8>());
}
}
}
let ext_len = extensions_payload.len();
handshake.extend_from_slice(&(ext_len as u16).to_be_bytes());
handshake.extend_from_slice(&extensions_payload);
let mut packet = vec![
0x01, ((handshake.len() >> 16) & 0xFF) as u8,
((handshake.len() >> 8) & 0xFF) as u8,
(handshake.len() & 0xFF) as u8,
];
packet.extend_from_slice(&handshake);
packets.push(packet);
}
packets
}
fn bench_parse_ja4(c: &mut Criterion) {
const BATCH_SIZE: usize = 1024;
let quic_packets = generate_packets(BATCH_SIZE);
let tcp_packets: Vec<Vec<u8>> = quic_packets
.iter()
.map(|p| {
let mut tcp = vec![0x16, 0x03, 0x01];
tcp.extend_from_slice(&(p.len() as u16).to_be_bytes());
tcp.extend_from_slice(p);
tcp
})
.collect();
c.bench_function("parse_ja4_quic_randomized", |b| {
let mut i = 0;
b.iter(|| {
let packet = &quic_packets[i & (BATCH_SIZE - 1)];
i += 1;
parse_ja4(black_box(packet), black_box(true))
})
});
c.bench_function("parse_ja4_tcp_randomized", |b| {
let mut i = 0;
b.iter(|| {
let packet = &tcp_packets[i & (BATCH_SIZE - 1)];
i += 1;
parse_ja4(black_box(packet), black_box(false))
})
});
}
criterion_group!(benches, bench_parse_ja4);
criterion_main!(benches);