canlink-tscan 0.3.3

Rust CAN backend via LibTSCAN, validated on TOSUN hardware
Documentation
use canlink_tscan::daemon::server::run_server_with_io;
use canlink_tscan::daemon::{read_frame, write_frame, Op, Request, Response, Status};
use std::sync::{Mutex, OnceLock};
use std::time::{SystemTime, UNIX_EPOCH};

static ENV_LOCK: OnceLock<Mutex<()>> = OnceLock::new();

fn unique_trace_file() -> std::path::PathBuf {
    let nanos = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("system time error")
        .as_nanos();
    std::env::temp_dir().join(format!("canlink-tscan-daemon-server-trace-{nanos}.log"))
}

#[test]
fn server_hello_success() {
    let _guard = ENV_LOCK
        .get_or_init(|| Mutex::new(()))
        .lock()
        .unwrap_or_else(|err| err.into_inner());

    let mut input = Vec::new();
    write_frame(
        &mut input,
        &Request::new(
            1,
            Op::Hello {
                protocol_version: 1,
                client_version: "test".to_string(),
            },
        ),
    )
    .expect("failed to write hello frame");

    let mut output = Vec::new();
    run_server_with_io(&mut &input[..], &mut output).expect("server run failed");

    let response: Response = read_frame(&mut &output[..]).expect("failed to read response");
    assert_eq!(response.id, 1);
    assert_eq!(response.status, Status::Ok);
}

#[test]
fn server_hello_version_mismatch() {
    let _guard = ENV_LOCK
        .get_or_init(|| Mutex::new(()))
        .lock()
        .unwrap_or_else(|err| err.into_inner());

    let mut input = Vec::new();
    write_frame(
        &mut input,
        &Request::new(
            9,
            Op::Hello {
                protocol_version: 2,
                client_version: "test".to_string(),
            },
        ),
    )
    .expect("failed to write hello frame");

    let mut output = Vec::new();
    run_server_with_io(&mut &input[..], &mut output).expect("server run failed");

    let response: Response = read_frame(&mut &output[..]).expect("failed to read response");
    assert_eq!(response.id, 9);
    assert_eq!(response.status, Status::Error);
}

#[test]
fn server_delay_injection_traces_once_per_process() {
    let _guard = ENV_LOCK
        .get_or_init(|| Mutex::new(()))
        .lock()
        .unwrap_or_else(|err| err.into_inner());

    let trace_path = unique_trace_file();
    std::env::set_var("TRACE_PATH", &trace_path);
    std::env::set_var("DELAY_ON_OP_ONCE", "HELLO");
    std::env::set_var("DELAY_MS", "1");
    std::env::remove_var("EXIT_ON_OP_ONCE");

    let mut input = Vec::new();
    for id in [1_u64, 2_u64] {
        write_frame(
            &mut input,
            &Request::new(
                id,
                Op::Hello {
                    protocol_version: 1,
                    client_version: "test".to_string(),
                },
            ),
        )
        .expect("failed to write hello frame");
    }

    let mut output = Vec::new();
    run_server_with_io(&mut &input[..], &mut output).expect("server run failed");

    let response1: Response = read_frame(&mut &output[..]).expect("failed to read first response");
    assert_eq!(response1.id, 1);
    assert_eq!(response1.status, Status::Ok);

    let remaining = {
        let mut slice = &output[..];
        let _ = read_frame::<_, Response>(&mut slice).expect("failed to read first response");
        slice
    };
    let response2: Response =
        read_frame(&mut &remaining[..]).expect("failed to read second response");
    assert_eq!(response2.id, 2);
    assert_eq!(response2.status, Status::Ok);

    std::env::remove_var("TRACE_PATH");
    std::env::remove_var("DELAY_ON_OP_ONCE");
    std::env::remove_var("DELAY_MS");

    let trace = std::fs::read_to_string(trace_path).expect("read trace file failed");
    let hello_count = trace.lines().filter(|line| *line == "OP:HELLO").count();
    let delay_count = trace
        .lines()
        .filter(|line| *line == "INJECT_DELAY_ONCE:HELLO:1")
        .count();

    assert_eq!(hello_count, 2, "unexpected trace:\n{trace}");
    assert_eq!(delay_count, 1, "unexpected trace:\n{trace}");
}