use crate::Timestamp;
pub fn ja4l(latency_micros: u64, ttl: u8) -> String {
format!("{latency_micros}_{ttl}")
}
pub fn half_latency_micros(earlier: Timestamp, later: Timestamp) -> Option<u64> {
let a = earlier.to_duration();
let b = later.to_duration();
if b < a {
return None;
}
Some(((b - a).as_micros() / 2) as u64)
}
pub fn ja4l_from_timestamps(earlier: Timestamp, later: Timestamp, ttl: u8) -> Option<String> {
Some(ja4l(half_latency_micros(earlier, later)?, ttl))
}
pub fn ja4l_client(synack: Timestamp, ack: Timestamp, client_ttl: u8) -> Option<String> {
ja4l_from_timestamps(synack, ack, client_ttl)
}
pub fn ja4l_server(syn: Timestamp, synack: Timestamp, server_ttl: u8) -> Option<String> {
ja4l_from_timestamps(syn, synack, server_ttl)
}
#[cfg(test)]
mod tests {
use super::*;
fn ts(secs: u32, nanos: u32) -> Timestamp {
Timestamp::new(secs, nanos)
}
#[test]
fn golden_format() {
assert_eq!(ja4l(35, 64), "35_64");
assert_eq!(ja4l(14, 128), "14_128");
}
#[test]
fn half_latency_is_half_the_gap() {
assert_eq!(half_latency_micros(ts(0, 0), ts(0, 70_000)), Some(35));
let earlier = ts(0, 0);
let later = ts(0, 37_724_000);
assert_eq!(
ja4l_server(earlier, later, 59),
Some("18862_59".to_string())
);
}
#[test]
fn client_measurement() {
let synack = ts(1, 0);
let ack = ts(1, 28_000); assert_eq!(ja4l_client(synack, ack, 128), Some("14_128".to_string()));
}
#[test]
fn out_of_order_is_none() {
assert_eq!(half_latency_micros(ts(5, 0), ts(1, 0)), None);
assert_eq!(ja4l_client(ts(5, 0), ts(1, 0), 64), None);
}
#[test]
fn spans_seconds() {
assert_eq!(half_latency_micros(ts(10, 0), ts(12, 0)), Some(1_000_000));
}
}