feat: read device details implemntation using fido protocol

This commit is contained in:
Suyog Tandel
2026-01-11 01:48:58 +05:30
parent 2a155428b4
commit 0315a6f1e8
8 changed files with 888 additions and 402 deletions
+259
View File
@@ -0,0 +1,259 @@
use anyhow::{Result, anyhow};
use rand::Rng;
use std::time::Duration;
// HID Transport Constants
const HID_REPORT_SIZE: usize = 64;
const HID_USAGE_PAGE_FIDO: u16 = 0xF1D0;
const CTAPHID_CID_BROADCAST: u32 = 0xFFFFFFFF;
const CTAPHID_INIT: u8 = 0x86;
pub const CTAPHID_CBOR: u8 = 0x90;
const CTAPHID_ERROR: u8 = 0xBF;
const CTAPHID_KEEPALIVE: u8 = 0xBB;
pub struct HidTransport {
device: hidapi::HidDevice,
cid: u32,
}
impl HidTransport {
pub fn open() -> Result<Self> {
log::info!("Attempting to open HID transport for FIDO device...");
let api = hidapi::HidApi::new().map_err(|e| {
log::error!("Failed to initialize HidApi: {}", e);
e
})?;
// Find device with FIDO Usage Page (0xF1D0)
let info = api
.device_list()
.find(|d| d.usage_page() == HID_USAGE_PAGE_FIDO)
.ok_or_else(|| {
log::warn!("No FIDO device found with Usage Page 0xF1D0.");
anyhow!("No FIDO device found. Is it plugged in?")
})?;
log::debug!(
"Found FIDO device: VendorID=0x{:04X}, ProductID=0x{:04X}",
info.vendor_id(),
info.product_id()
);
let device = info.open_device(&api).map_err(|e| {
log::error!("Failed to open HID device: {}", e);
e
})?;
// Negotiate Channel ID (CID)
let cid = Self::init_channel(&device).map_err(|e| {
log::error!("Failed to negotiate Channel ID: {}", e);
e
})?;
log::info!("HID Transport established successfully. CID: 0x{:08X}", cid);
Ok(Self { device, cid })
}
fn init_channel(device: &hidapi::HidDevice) -> Result<u32> {
log::debug!("Initializing CTAPHID channel...");
let mut nonce = [0u8; 8];
rand::rng().fill(&mut nonce);
// Construct Init Packet: [CID(4) | CMD(1) | LEN(2) | NONCE(8)]
let mut report = [0u8; HID_REPORT_SIZE + 1]; // +1 for Report ID (always 0)
report[1..5].copy_from_slice(&CTAPHID_CID_BROADCAST.to_be_bytes());
report[5] = CTAPHID_INIT;
report[6] = 0; // Len MSB
report[7] = 8; // Len LSB
report[8..16].copy_from_slice(&nonce);
log::trace!("Sending CTAPHID_INIT broadcast with nonce: {:02X?}", nonce);
device.write(&report).map_err(|e| {
log::error!("Failed to write INIT packet: {}", e);
e
})?;
// Read Response until we find our nonce
let start = std::time::Instant::now();
while start.elapsed() < Duration::from_secs(1) {
let mut buf = [0u8; HID_REPORT_SIZE];
if device.read_timeout(&mut buf, 100).is_ok() {
// Check if response matches our broadcast and nonce
if buf[0..4] == CTAPHID_CID_BROADCAST.to_be_bytes()
&& buf[4] == CTAPHID_INIT
&& &buf[7..15] == &nonce
{
// New CID is at bytes 16..20
let new_cid = u32::from_be_bytes([buf[15], buf[16], buf[17], buf[18]]);
log::debug!("Channel negotiation successful. New CID: 0x{:08X}", new_cid);
return Ok(new_cid);
}
}
}
log::error!("Timeout waiting for CTAPHID_INIT response.");
Err(anyhow!("Timeout waiting for FIDO Init response"))
}
pub fn send_cbor(&self, cmd: u8, payload: &[u8]) -> Result<Vec<u8>> {
log::debug!(
"Sending CBOR Command: 0x{:02X}, Payload Size: {} bytes",
cmd,
payload.len()
);
// --- Transmit ---
let mut sequence = 0u8;
let total_len = payload.len();
let mut sent = 0;
// 1. Init Packet
let mut report = [0u8; HID_REPORT_SIZE + 1];
report[1..5].copy_from_slice(&self.cid.to_be_bytes());
report[5] = cmd;
report[6] = (total_len >> 8) as u8;
report[7] = (total_len & 0xFF) as u8;
let to_copy = std::cmp::min(total_len, HID_REPORT_SIZE - 7);
report[8..8 + to_copy].copy_from_slice(&payload[0..to_copy]);
sent += to_copy;
// log::trace!("Writing Init Packet (Sent: {}/{})", sent, total_len);
if let Err(e) = self.device.write(&report) {
log::error!("Failed to write initial HID packet: {}", e);
return Err(e.into());
}
// 2. Continuation Packets
while sent < total_len {
let mut report = [0u8; HID_REPORT_SIZE + 1];
report[1..5].copy_from_slice(&self.cid.to_be_bytes());
report[5] = 0x7F & sequence; // SEQ
sequence += 1;
let to_copy = std::cmp::min(total_len - sent, HID_REPORT_SIZE - 5);
report[6..6 + to_copy].copy_from_slice(&payload[sent..sent + to_copy]);
sent += to_copy;
// log::trace!("Writing Cont Packet Seq {} (Sent: {}/{})", sequence - 1, sent, total_len);
if let Err(e) = self.device.write(&report) {
log::error!(
"Failed to write continuation HID packet (Seq {}): {}",
sequence - 1,
e
);
return Err(e.into());
}
}
// --- Receive ---
let mut response_data = Vec::new();
let mut expected_len = 0;
let mut read_len = 0;
let mut last_seq = 0;
log::debug!("Waiting for response...");
// Read First Packet (Loop to handle Keepalives)
let mut buf = [0u8; HID_REPORT_SIZE];
let mut buf = [0u8; HID_REPORT_SIZE];
loop {
if let Err(e) = self.device.read_timeout(&mut buf, 2000) {
log::error!("Timeout reading response packet: {}", e);
return Err(e.into());
}
// Check CID mismatch
if u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]) != self.cid {
log::warn!("Received packet from different CID, ignoring...");
continue;
}
// Check for KEEPALIVE (0xBB)
if buf[4] == CTAPHID_KEEPALIVE {
let status = buf[5]; // Keepalive status byte
log::debug!(
"Device sent KEEPALIVE (Status: 0x{:02X}), waiting...",
status
);
continue; // Go back to start of loop and read again
}
// If we are here, it's a real response
break;
}
if buf[4] == CTAPHID_ERROR {
log::error!("Device returned CTAP Error code: 0x{:02X}", buf[5]);
return Err(anyhow!("Device returned CTAP Error: 0x{:02X}", buf[5]));
}
if buf[4] == cmd {
expected_len = u16::from_be_bytes([buf[5], buf[6]]) as usize;
let in_pkt = std::cmp::min(expected_len, HID_REPORT_SIZE - 7);
response_data.extend_from_slice(&buf[7..7 + in_pkt]);
read_len += in_pkt;
// log::trace!("Received Init Response. Expecting {} bytes total.", expected_len);
} else {
log::error!(
"Unexpected command response: 0x{:02X} (Expected 0x{:02X})",
buf[4],
cmd
);
return Err(anyhow!(
"Unexpected command response: 0x{:02X} (Expected 0x{:02X})",
buf[4],
cmd
));
}
// 2. Read Continuation Packets
while read_len < expected_len {
if let Err(e) = self.device.read_timeout(&mut buf, 500) {
log::error!("Timeout reading continuation packet: {}", e);
return Err(e.into());
}
if u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]) != self.cid {
continue; // Ignore packets from other channels
}
let seq = buf[4];
if seq != last_seq {
log::error!(
"Sequence mismatch in response. Expected {}, got {}",
last_seq,
seq
);
return Err(anyhow!("Sequence mismatch"));
}
last_seq += 1;
let in_pkt = std::cmp::min(expected_len - read_len, HID_REPORT_SIZE - 5);
response_data.extend_from_slice(&buf[5..5 + in_pkt]);
read_len += in_pkt;
}
// 3. Check CTAP Status Byte (First byte of payload)
if response_data.is_empty() {
log::error!("Device sent empty payload response.");
return Err(anyhow!("Empty response"));
}
let status = response_data[0];
if status != 0x00 {
log::error!("FIDO Operation returned failure status: 0x{:02X}", status);
return Err(anyhow!(
"FIDO Operation Failed with Status: 0x{:02X}",
status
));
}
log::debug!(
"Command 0x{:02X} successful. Response payload len: {}",
cmd,
response_data.len() - 1
);
// Return payload without status byte
Ok(response_data[1..].to_vec())
}
}
+191 -2
View File
@@ -1,8 +1,14 @@
pub mod constants;
pub mod hid;
use crate::types::FidoDeviceInfo;
use crate::types::{AppConfig, AppError, DeviceInfo, FidoDeviceInfo, FullDeviceStatus};
use constants::*;
use ctap_hid_fido2::{Cfg, FidoKeyHidFactory};
use std::collections::HashMap;
use hid::*;
use serde_cbor_2::{Value, from_slice, to_vec};
use std::collections::{BTreeMap, HashMap};
// Fido functions that require pin: ( Uses ctap_hid_fido2 crate)
pub(crate) fn get_fido_info() -> Result<FidoDeviceInfo, String> {
let cfg = Cfg::init();
@@ -69,3 +75,186 @@ pub(crate) fn set_min_pin_length(
min_pin_length
))
}
// Custom Fido functions ( works only with pico-fido firmware )
pub fn read_device_details() -> Result<FullDeviceStatus, AppError> {
log::info!("Starting FIDO device details read...");
let transport = HidTransport::open().map_err(|e| {
log::error!("Failed to open HID transport: {}", e);
AppError::Device(e.to_string())
})?;
// --- 1. Get Info ---
log::debug!("Sending GetInfo command (0x04)...");
let info_payload = [CtapCommand::GetInfo as u8];
let info_res = transport
.send_cbor(CTAPHID_CBOR, &info_payload)
.map_err(|e| {
log::error!("GetInfo CTAP command failed: {}", e);
AppError::Device(format!("GetInfo failed: {}", e))
})?;
log::debug!("GetInfo response received ({} bytes)", info_res.len());
let info_val: Value = from_slice(&info_res).map_err(|e| {
log::error!("Failed to parse GetInfo CBOR: {}", e);
AppError::Io(e.to_string())
})?;
// NOTE: Key 0x03 is AAGUID, not the unique device Serial.
let aaguid_str = if let Value::Map(m) = &info_val {
m.get(&Value::Integer(0x03))
.and_then(|v| {
if let Value::Bytes(b) = v {
Some(hex::encode_upper(b))
} else {
None
}
})
.unwrap_or_else(|| {
log::warn!("AAGUID not found in GetInfo response");
"Unknown".into()
})
} else {
"Unknown".into()
};
let fw_version = if let Value::Map(m) = &info_val {
m.get(&Value::Integer(0x0E))
.and_then(|v| {
if let Value::Integer(i) = v {
Some(format!("0x{:X}", i))
} else {
None
}
})
.unwrap_or_else(|| {
log::warn!("Firmware version not found in GetInfo response");
"Unknown".into()
})
} else {
"Unknown".into()
};
log::info!(
"Device identified: AAGUID={}, FW={}",
aaguid_str,
fw_version
);
// --- 2. Get Memory Stats ---
log::debug!("Preparing Memory Stats vendor command...");
// FIX: The CBOR map should only contain the arguments ({1: 1}), not the command category.
let mut mem_req = BTreeMap::new();
mem_req.insert(
Value::Integer(1), // Sub-command key (usually 1)
Value::Integer(MemorySubCommand::GetStats as i128),
);
let mem_cbor = to_vec(&Value::Map(mem_req)).map_err(|e| {
log::error!("Failed to encode Memory Stats CBOR: {}", e);
AppError::Io(format!("CBOR encode error: {}", e))
})?;
// FIX: Prepend the Vendor Command ID (0x06 for Memory) to the payload
// The firmware expects: [VendorCmdByte] [CBOR Map]
let mut mem_payload = vec![VendorCommand::Memory as u8];
mem_payload.extend(mem_cbor);
log::debug!("Sending Memory Stats command...");
let mem_res = transport
.send_cbor(CTAP_VENDOR_CBOR_CMD, &mem_payload)
.unwrap_or_else(|e| {
log::warn!("Failed to fetch memory stats (Vendor Cmd): {}", e);
Vec::new()
});
let mem_map: BTreeMap<i128, i128> = if !mem_res.is_empty() {
from_slice(&mem_res).unwrap_or_else(|e| {
log::error!("Failed to parse Memory Stats CBOR response: {}", e);
BTreeMap::new()
})
} else {
BTreeMap::new()
};
let used = mem_map
.get(&(MemoryResponseKey::UsedSpace as i128))
.cloned()
.unwrap_or(0) as u32;
let total = mem_map
.get(&(MemoryResponseKey::TotalSpace as i128))
.cloned()
.unwrap_or(0) as u32;
log::debug!(
"Memory Stats: Used={}KB, Total={}KB",
used / 1024,
total / 1024
);
// --- 3. Get Physical Config ---
log::debug!("Preparing Physical Config vendor command...");
// FIX: Only arguments in CBOR map
let mut phy_params = BTreeMap::new();
phy_params.insert(
Value::Integer(1), // Sub-command key
Value::Integer(PhysicalOptionsSubCommand::GetOptions as i128),
);
// Note: The previous code nested this inside another map with key 2.
// Based on cbor_vendor.c, we usually just send the sub-command params directly
// or wrapped depending on the specific vendor command logic.
// For 'PhysicalOptions', looking at cbor_vendor.c, it expects a map where key 1 is subcommand.
// So the map we built above `phy_params` ( {1: GetOptions} ) is correct as the top-level CBOR.
let phy_cbor = to_vec(&Value::Map(phy_params)).map_err(|e| {
log::error!("Failed to encode Physical Config CBOR: {}", e);
AppError::Io(format!("CBOR encode error: {}", e))
})?;
// FIX: Prepend Vendor Command ID (0x05 for PhysicalOptions)
let mut phy_payload = vec![VendorCommand::PhysicalOptions as u8];
phy_payload.extend(phy_cbor);
log::debug!("Sending Physical Config command...");
let phy_res = transport
.send_cbor(CTAP_VENDOR_CBOR_CMD, &phy_payload)
.unwrap_or_else(|e| {
log::warn!("Failed to fetch physical config (Vendor Cmd): {}", e);
Vec::new()
});
let mut config = AppConfig::default();
if let Ok(Value::Map(m)) = from_slice(&phy_res) {
log::debug!("Parsed Physical Config map successfully");
// These keys might need adjustment based on exact firmware response structure
// usually they are integer keys in CBOR, but if your firmware returns text keys:
if let Some(Value::Integer(v)) = m.get(&Value::Text("gpio".into())) {
config.led_gpio = *v as u8;
}
if let Some(Value::Integer(v)) = m.get(&Value::Text("brightness".into())) {
config.led_brightness = *v as u8;
}
} else if !phy_res.is_empty() {
log::warn!("Physical config response was not a valid CBOR map or empty");
}
log::info!("Successfully read all device details.");
Ok(FullDeviceStatus {
info: DeviceInfo {
serial: aaguid_str, // Using AAGUID as serial since unique serial isn't available
flash_used: used / 1024,
flash_total: total / 1024,
firmware_version: fw_version,
},
config,
secure_boot: false,
secure_lock: false,
})
}
+2
View File
@@ -4,6 +4,7 @@ use crate::{fido, rescue, types::*};
#[tauri::command]
pub fn read_device_details() -> Result<FullDeviceStatus, AppError> {
rescue::read_device_details()
// fido::read_device_details()
}
#[tauri::command]
@@ -29,6 +30,7 @@ pub(crate) fn change_fido_pin(
fido::change_fido_pin(current_pin, new_pin)
}
/// UNSTABLE!
#[tauri::command]
pub(crate) fn set_min_pin_length(
current_pin: String,
+5
View File
@@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize};
// --- Data Structures ---
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DeviceInfo {
pub serial: String,
pub flash_used: u32,
@@ -11,6 +12,7 @@ pub struct DeviceInfo {
}
#[derive(Serialize, Deserialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct AppConfig {
pub vid: String,
pub pid: String,
@@ -28,6 +30,7 @@ pub struct AppConfig {
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct AppConfigInput {
pub vid: Option<String>,
pub pid: Option<String>,
@@ -43,6 +46,7 @@ pub struct AppConfigInput {
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FullDeviceStatus {
pub info: DeviceInfo,
pub config: AppConfig,
@@ -53,6 +57,7 @@ pub struct FullDeviceStatus {
// Fido stuff:
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FidoDeviceInfo {
pub versions: Vec<String>,
pub extensions: Vec<String>,
+27 -43
View File
@@ -1,7 +1,14 @@
import { invoke } from "@tauri-apps/api/core";
import { logger } from "$lib/utils/log.svelte";
import { DEFAULT_CONFIG, DEFAULT_DEVICE_INFO, VENDORS } from "$lib/device/constants.svelte";
import type { DeviceConfig, DeviceInfo, FidoInfo, SecurityState } from "$lib/device/types.svelte";
import type {
DeviceConfig,
DeviceInfo,
FidoInfo,
SecurityState,
FullDeviceStatus,
DeviceConfigInput,
} from "$lib/device/types.svelte";
class DeviceManager {
loading = $state(false);
@@ -30,49 +37,26 @@ class DeviceManager {
try {
logger.add("Attempting to connect to device...", "info");
const status: any = await invoke("read_device_details");
const status = await invoke<FullDeviceStatus>("read_device_details");
this.info = {
serial: status.info.serial,
flashUsed: status.info.flash_used,
flashTotal: status.info.flash_total,
firmwareVersion: status.info.firmware_version,
};
this.info = status.info;
this.config = {
vid: status.config.vid,
pid: status.config.pid,
productName: status.config.product_name,
ledGpio: status.config.led_gpio,
ledBrightness: status.config.led_brightness,
touchTimeout: status.config.touch_timeout,
ledDimmable: status.config.led_dimmable,
powerCycleOnReset: status.config.power_cycle_on_reset,
ledSteady: status.config.led_steady,
enableSecp256k1: status.config.enable_secp256k1,
ledDriver: status.config.led_driver ? String(status.config.led_driver) : "1",
...status.config,
ledDriver: status.config.ledDriver ? String(status.config.ledDriver) : "1",
};
this.#originalConfig = JSON.parse(JSON.stringify(this.config));
this.security = {
secureBoot: status.secure_boot,
secureLock: status.secure_lock,
secureBoot: status.secureBoot,
secureLock: status.secureLock,
confirmed: false,
};
const fido: any = await invoke("get_fido_info");
this.fidoInfo = {
versions: fido.versions,
extensions: fido.extensions,
aaguid: fido.aaguid,
options: fido.options,
maxMsgSize: fido.max_msg_size,
pinProtocols: fido.pin_protocols,
// remainingDiscCreds: fido.remaining_disc_creds,
minPinLength: fido.min_pin_length,
firmwareVersion: fido.firmware_version,
};
const fido = await invoke<FidoInfo>("get_fido_info");
this.fidoInfo = fido;
if (!this.connected) {
logger.add(`Device Connected! Serial: ${this.info.serial}, FW: v${this.info.firmwareVersion}`, "success");
@@ -96,7 +80,7 @@ class DeviceManager {
logger.add("Analyzing configuration changes...", "info");
try {
const rustConfig: any = {};
const rustConfig: DeviceConfigInput = {};
// Diffing logic
if (this.config.vid !== this.#originalConfig.vid || this.config.pid !== this.#originalConfig.pid) {
@@ -106,22 +90,22 @@ class DeviceManager {
}
if (this.config.productName !== this.#originalConfig.productName) {
rustConfig.product_name = this.config.productName;
rustConfig.productName = this.config.productName;
logger.add(`Queuing change: Product Name -> ${this.config.productName}`, "info");
}
if (Number(this.config.ledGpio) !== Number(this.#originalConfig.ledGpio)) {
rustConfig.led_gpio = Number(this.config.ledGpio);
rustConfig.ledGpio = Number(this.config.ledGpio);
logger.add(`Queuing change: LED GPIO -> ${this.config.ledGpio}`, "info");
}
if (Number(this.config.ledBrightness) !== Number(this.#originalConfig.ledBrightness)) {
rustConfig.led_brightness = Number(this.config.ledBrightness);
rustConfig.ledBrightness = Number(this.config.ledBrightness);
logger.add(`Queuing change: Brightness -> ${this.config.ledBrightness}`, "info");
}
if (Number(this.config.touchTimeout) !== Number(this.#originalConfig.touchTimeout)) {
rustConfig.touch_timeout = Number(this.config.touchTimeout);
rustConfig.touchTimeout = Number(this.config.touchTimeout);
logger.add(`Queuing change: Timeout -> ${this.config.touchTimeout}`, "info");
}
@@ -131,19 +115,19 @@ class DeviceManager {
this.config.ledSteady !== this.#originalConfig.ledSteady;
if (optionsChanged) {
rustConfig.led_dimmable = this.config.ledDimmable;
rustConfig.power_cycle_on_reset = this.config.powerCycleOnReset;
rustConfig.led_steady = this.config.ledSteady;
rustConfig.ledDimmable = this.config.ledDimmable;
rustConfig.powerCycleOnReset = this.config.powerCycleOnReset;
rustConfig.ledSteady = this.config.ledSteady;
logger.add("Queuing change: Device Options (Bitmask updated)", "info");
}
if (this.config.enableSecp256k1 !== this.#originalConfig.enableSecp256k1) {
rustConfig.enable_secp256k1 = this.config.enableSecp256k1;
rustConfig.enableSecp256k1 = this.config.enableSecp256k1;
logger.add(`Queuing change: Secp256k1 -> ${this.config.enableSecp256k1}`, "info");
}
if (Number(this.config.ledDriver) !== Number(this.#originalConfig.ledDriver)) {
rustConfig.led_driver = Number(this.config.ledDriver);
rustConfig.ledDriver = Number(this.config.ledDriver);
logger.add(`Queuing change: LED Driver -> ${this.config.ledDriver}`, "info");
}
+21
View File
@@ -12,6 +12,20 @@ export interface DeviceConfig {
ledDriver: string;
}
export interface DeviceConfigInput {
vid?: string;
pid?: string;
productName?: string;
ledGpio?: number;
ledBrightness?: number;
touchTimeout?: number;
ledDriver?: number;
ledDimmable?: boolean;
powerCycleOnReset?: boolean;
ledSteady?: boolean;
enableSecp256k1?: boolean;
}
export interface DeviceInfo {
serial: string;
flashUsed: number;
@@ -19,6 +33,13 @@ export interface DeviceInfo {
firmwareVersion: string;
}
export interface FullDeviceStatus {
info: DeviceInfo;
config: DeviceConfig;
secureBoot: boolean;
secureLock: boolean;
}
export interface SecurityState {
secureBoot: boolean;
secureLock: boolean;
+16 -16
View File
@@ -1,16 +1,16 @@
0.242134309s INFO prepare_target{force=false package_id=picoforge v0.1.1 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge_lib"}: cargo::core::compiler::fingerprint: stale: changed "/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri/src/fido/constants.rs"
0.242157953s INFO prepare_target{force=false package_id=picoforge v0.1.1 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge_lib"}: cargo::core::compiler::fingerprint: (vs) "/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri/target/debug/.fingerprint/picoforge-56fb0ca8e81c4e13/dep-lib-picoforge_lib"
0.242161871s INFO prepare_target{force=false package_id=picoforge v0.1.1 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge_lib"}: cargo::core::compiler::fingerprint: FileTime { seconds: 1768061271, nanos: 489253606 } < FileTime { seconds: 1768061821, nanos: 358155125 }
0.242649340s INFO prepare_target{force=false package_id=picoforge v0.1.1 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge_lib"}: cargo::core::compiler::fingerprint: fingerprint dirty for picoforge v0.1.1 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri)/Check { test: false }/TargetInner { ..: lib_target("picoforge_lib", ["staticlib", "cdylib", "rlib"], "/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri/src/lib.rs", Edition2024) }
0.242665801s INFO prepare_target{force=false package_id=picoforge v0.1.1 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge_lib"}: cargo::core::compiler::fingerprint: dirty: FsStatusOutdated(StaleItem(ChangedFile { reference: "/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri/target/debug/.fingerprint/picoforge-56fb0ca8e81c4e13/dep-lib-picoforge_lib", reference_mtime: FileTime { seconds: 1768061271, nanos: 489253606 }, stale: "/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri/src/fido/constants.rs", stale_mtime: FileTime { seconds: 1768061821, nanos: 358155125 } }))
0.273064024s INFO prepare_target{force=false package_id=picoforge v0.1.1 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge_lib"}: cargo::core::compiler::fingerprint: stale: changed "/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri/src/fido/constants.rs"
0.273075716s INFO prepare_target{force=false package_id=picoforge v0.1.1 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge_lib"}: cargo::core::compiler::fingerprint: (vs) "/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri/target/debug/.fingerprint/picoforge-f3cbf802c867c96f/dep-test-lib-picoforge_lib"
0.273079223s INFO prepare_target{force=false package_id=picoforge v0.1.1 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge_lib"}: cargo::core::compiler::fingerprint: FileTime { seconds: 1768061271, nanos: 489253606 } < FileTime { seconds: 1768061821, nanos: 358155125 }
0.273138885s INFO prepare_target{force=false package_id=picoforge v0.1.1 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge_lib"}: cargo::core::compiler::fingerprint: fingerprint dirty for picoforge v0.1.1 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri)/Check { test: true }/TargetInner { ..: lib_target("picoforge_lib", ["staticlib", "cdylib", "rlib"], "/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri/src/lib.rs", Edition2024) }
0.273149866s INFO prepare_target{force=false package_id=picoforge v0.1.1 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge_lib"}: cargo::core::compiler::fingerprint: dirty: FsStatusOutdated(StaleItem(ChangedFile { reference: "/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri/target/debug/.fingerprint/picoforge-f3cbf802c867c96f/dep-test-lib-picoforge_lib", reference_mtime: FileTime { seconds: 1768061271, nanos: 489253606 }, stale: "/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri/src/fido/constants.rs", stale_mtime: FileTime { seconds: 1768061821, nanos: 358155125 } }))
0.274953068s INFO prepare_target{force=false package_id=picoforge v0.1.1 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge"}: cargo::core::compiler::fingerprint: fingerprint dirty for picoforge v0.1.1 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri)/Check { test: false }/TargetInner { name: "picoforge", doc: true, ..: with_path("/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri/src/main.rs", Edition2024) }
0.274964469s INFO prepare_target{force=false package_id=picoforge v0.1.1 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge"}: cargo::core::compiler::fingerprint: dirty: FsStatusOutdated(StaleDepFingerprint { name: "picoforge_lib" })
0.276214187s INFO prepare_target{force=false package_id=picoforge v0.1.1 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge"}: cargo::core::compiler::fingerprint: fingerprint dirty for picoforge v0.1.1 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri)/Check { test: true }/TargetInner { name: "picoforge", doc: true, ..: with_path("/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri/src/main.rs", Edition2024) }
0.276222522s INFO prepare_target{force=false package_id=picoforge v0.1.1 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge"}: cargo::core::compiler::fingerprint: dirty: FsStatusOutdated(StaleDepFingerprint { name: "picoforge_lib" })
Checking picoforge v0.1.1 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.88s
0.250245340s INFO prepare_target{force=false package_id=picoforge v0.1.3 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge_lib"}: cargo::core::compiler::fingerprint: stale: changed "/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri/src/io.rs"
0.250267702s INFO prepare_target{force=false package_id=picoforge v0.1.3 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge_lib"}: cargo::core::compiler::fingerprint: (vs) "/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri/target/debug/.fingerprint/picoforge-45a6e84c004a50f2/dep-lib-picoforge_lib"
0.250272141s INFO prepare_target{force=false package_id=picoforge v0.1.3 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge_lib"}: cargo::core::compiler::fingerprint: FileTime { seconds: 1768075173, nanos: 492735589 } < FileTime { seconds: 1768075857, nanos: 695093316 }
0.251581160s INFO prepare_target{force=false package_id=picoforge v0.1.3 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge_lib"}: cargo::core::compiler::fingerprint: fingerprint dirty for picoforge v0.1.3 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri)/Check { test: false }/TargetInner { ..: lib_target("picoforge_lib", ["staticlib", "cdylib", "rlib"], "/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri/src/lib.rs", Edition2024) }
0.251610195s INFO prepare_target{force=false package_id=picoforge v0.1.3 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge_lib"}: cargo::core::compiler::fingerprint: dirty: FsStatusOutdated(StaleItem(ChangedFile { reference: "/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri/target/debug/.fingerprint/picoforge-45a6e84c004a50f2/dep-lib-picoforge_lib", reference_mtime: FileTime { seconds: 1768075173, nanos: 492735589 }, stale: "/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri/src/io.rs", stale_mtime: FileTime { seconds: 1768075857, nanos: 695093316 } }))
0.292680708s INFO prepare_target{force=false package_id=picoforge v0.1.3 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge_lib"}: cargo::core::compiler::fingerprint: stale: changed "/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri/src/io.rs"
0.292709272s INFO prepare_target{force=false package_id=picoforge v0.1.3 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge_lib"}: cargo::core::compiler::fingerprint: (vs) "/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri/target/debug/.fingerprint/picoforge-317817c2b167a4f9/dep-test-lib-picoforge_lib"
0.292714492s INFO prepare_target{force=false package_id=picoforge v0.1.3 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge_lib"}: cargo::core::compiler::fingerprint: FileTime { seconds: 1768075173, nanos: 492735589 } < FileTime { seconds: 1768075857, nanos: 695093316 }
0.293221488s INFO prepare_target{force=false package_id=picoforge v0.1.3 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge_lib"}: cargo::core::compiler::fingerprint: fingerprint dirty for picoforge v0.1.3 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri)/Check { test: true }/TargetInner { ..: lib_target("picoforge_lib", ["staticlib", "cdylib", "rlib"], "/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri/src/lib.rs", Edition2024) }
0.293248690s INFO prepare_target{force=false package_id=picoforge v0.1.3 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge_lib"}: cargo::core::compiler::fingerprint: dirty: FsStatusOutdated(StaleItem(ChangedFile { reference: "/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri/target/debug/.fingerprint/picoforge-317817c2b167a4f9/dep-test-lib-picoforge_lib", reference_mtime: FileTime { seconds: 1768075173, nanos: 492735589 }, stale: "/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri/src/io.rs", stale_mtime: FileTime { seconds: 1768075857, nanos: 695093316 } }))
0.296506816s INFO prepare_target{force=false package_id=picoforge v0.1.3 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge"}: cargo::core::compiler::fingerprint: fingerprint dirty for picoforge v0.1.3 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri)/Check { test: false }/TargetInner { name: "picoforge", doc: true, ..: with_path("/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri/src/main.rs", Edition2024) }
0.296518057s INFO prepare_target{force=false package_id=picoforge v0.1.3 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge"}: cargo::core::compiler::fingerprint: dirty: FsStatusOutdated(StaleDepFingerprint { name: "picoforge_lib" })
0.298220929s INFO prepare_target{force=false package_id=picoforge v0.1.3 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge"}: cargo::core::compiler::fingerprint: fingerprint dirty for picoforge v0.1.3 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri)/Check { test: true }/TargetInner { name: "picoforge", doc: true, ..: with_path("/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri/src/main.rs", Edition2024) }
0.298231599s INFO prepare_target{force=false package_id=picoforge v0.1.3 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri) target="picoforge"}: cargo::core::compiler::fingerprint: dirty: FsStatusOutdated(StaleDepFingerprint { name: "picoforge_lib" })
Checking picoforge v0.1.3 (/run/media/suyog/SuyogSSD0/git/Personal/Code/pico-forge/src-tauri)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.06s
File diff suppressed because it is too large Load Diff