leyline-http 0.1.5

HTTP client that impersonates Chrome, Firefox, and Safari on the wire: TLS (JA3/JA4), HTTP/2, and HTTP/3 fingerprints
Documentation
use leyline::audit::{FieldOutcome, Observed};
use leyline::{Browser, Platform, Session};

const ECHO_URL: &str = "https://tls.peet.ws/api/all";

fn show(name: &str, outcome: &FieldOutcome) {
    match outcome {
        FieldOutcome::Match => println!("{name:<15} match"),
        FieldOutcome::Mismatch { expected, observed } => {
            println!("{name:<15} MISMATCH sent {expected}, echo saw {observed}")
        }
        FieldOutcome::Informational { expected, observed } => {
            println!(
                "{name:<15} differs, expected for this profile: sent {expected}, echo saw {observed}"
            )
        }
        FieldOutcome::NotReported => println!("{name:<15} not reported"),
        _ => println!("{name:<15} {outcome:?}"),
    }
}

#[tokio::main]
async fn main() -> leyline::Result<()> {
    let session = Session::builder()
        .browser(Browser::default())
        .platform(Platform::Linux)
        .audit(true)
        .build()?;

    let resp = session.get(ECHO_URL).send().await?;
    let Some(audit) = resp.audit().cloned() else {
        eprintln!("no audit data on this response");
        return Ok(());
    };
    println!("JA4T:           {}", audit.ja4t);
    println!("JA4H:           {}", audit.ja4h);

    let observed = Observed::from_json(&resp.text().await?)?;
    let report = audit.compare(&observed);
    show("JA4", &report.ja4);
    show("JA3", &report.ja3);
    show("H2 fingerprint", &report.h2_fingerprint);
    println!("fingerprint matches: {}", report.is_match());
    Ok(())
}