1use crate::audit::{non_grease_cipher_ids, non_grease_curve_ids, non_grease_ext_ids};
2
3#[derive(Debug)]
4pub struct Ja3Input<'a> {
5 pub ciphers: &'a [String],
6 pub curves: &'a [String],
7 pub extension_ids: &'a [u16],
8 pub tls_record_version: u16,
9}
10
11pub fn compute_ja3(input: &Ja3Input<'_>) -> String {
12 let raw = input.compute_ja3_raw();
13 use md5::Digest as _;
14 format!("{:x}", md5::Md5::digest(raw.as_bytes()))
15}
16
17impl Ja3Input<'_> {
18 pub(crate) fn compute_ja3_raw(&self) -> String {
19 let version = self.tls_record_version;
20
21 let ciphers: String = non_grease_cipher_ids(self.ciphers)
22 .iter()
23 .map(|id| id.to_string())
24 .collect::<Vec<_>>()
25 .join("-");
26
27 let extensions: String = non_grease_ext_ids(self.extension_ids)
28 .iter()
29 .map(|id| id.to_string())
30 .collect::<Vec<_>>()
31 .join("-");
32
33 let curves: String = non_grease_curve_ids(self.curves)
34 .iter()
35 .map(|id| id.to_string())
36 .collect::<Vec<_>>()
37 .join("-");
38
39 let point_formats = "0";
40
41 format!("{version},{ciphers},{extensions},{curves},{point_formats}")
42 }
43}
44
45#[cfg(test)]
46mod tests;