mirror of
https://github.com/librekeys/picoforge.git
synced 2026-07-28 08:01:19 -07:00
feat: read device details implemntation using fido protocol
This commit is contained in:
@@ -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
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user