ctaphid 0.3.1

Rust implementation of the CTAPHID protocol
Documentation
// Copyright (C) 2022 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: CC0-1.0

// The tests in this module are ignored as they require a connected CTAPHID device.

use std::{collections::BTreeMap, sync::Mutex};

use ctaphid::Device;
use hidapi::{HidApi, HidDevice};
use once_cell::sync::Lazy;
use serde_cbor::Value;

static HIDAPI: Lazy<Mutex<HidApi>> = Lazy::new(|| {
    HidApi::new()
        .expect("failed to create hidapi instance")
        .into()
});

fn with_device<F: Fn(&Device<HidDevice>)>(f: F) {
    let hidapi = match HIDAPI.lock() {
        Ok(hidapi) => hidapi,
        Err(error) => error.into_inner(),
    };
    let devices = hidapi
        .device_list()
        .filter(|device| ctaphid::is_known_device(*device));
    for device_info in devices {
        let hid_device = device_info
            .open_device(&hidapi)
            .expect("failed to open hidapi device");
        let device =
            Device::new(hid_device, device_info.to_owned()).expect("failed to open ctaphid device");
        println!("{:?}", device);
        f(&device);
    }
}

#[test]
#[ignore]
fn init() {
    with_device(|_| {});
}

#[test]
#[ignore]
fn ping() {
    with_device(|device| {
        let result = device.ping(&[0xde, 0xad, 0xbe, 0xef]);
        assert!(result.is_ok(), "{:?}", result);
    });
}

#[test]
#[ignore]
fn ctap1() {
    with_device(|device| {
        assert!(
            device.capabilities().has_msg(),
            "{:?}",
            device.capabilities()
        );
        let response = device
            .ctap1(&[0x00, 0x03, 0x00, 0x00, 0x00])
            .expect("failed to execute ctap1 command");
        let n = response.len();
        assert!(n > 2, "{}", n);
        assert_eq!(&response[n - 2..], &[0x90, 0x00]);
        let version = String::from_utf8_lossy(&response[..n - 2]);
        assert!(version.starts_with("U2F"), "{}", version);
    });
}

#[test]
#[ignore]
fn ctap2() {
    with_device(|device| {
        assert!(
            device.capabilities().has_cbor(),
            "{:?}",
            device.capabilities()
        );
        let response = device
            .ctap2(0x04, &[])
            .expect("failed to execute ctap2 command");
        let data: BTreeMap<u8, Value> =
            serde_cbor::from_slice(&response).expect("failed to parse ctap2 response");
        assert!(data.contains_key(&0x03), "{:?}", data);
    });
}