diff --git a/native/Cargo.lock b/native/Cargo.lock index 898daa1..c4dc5d6 100644 --- a/native/Cargo.lock +++ b/native/Cargo.lock @@ -46,6 +46,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -143,6 +193,52 @@ dependencies = [ "inout", ] +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "const-oid" version = "0.9.6" @@ -187,6 +283,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "craftlabs-auth-cli" +version = "1.0.0" +dependencies = [ + "chrono", + "clap", + "craft-core", + "serde_json", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -336,6 +442,12 @@ dependencies = [ "polyval", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hex" version = "0.4.3" @@ -600,6 +712,12 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itoa" version = "1.0.18" @@ -742,6 +860,12 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "opaque-debug" version = "0.3.1" @@ -1210,6 +1334,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -1444,6 +1574,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "version_check" version = "0.9.5" diff --git a/native/Cargo.toml b/native/Cargo.toml index 516899c..5268ded 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["craft-core"] +members = ["craft-core", "craftlabs-auth-cli"] resolver = "2" [workspace.package] diff --git a/native/craft-core/Cargo.toml b/native/craft-core/Cargo.toml index 542cccb..cafe4d2 100644 --- a/native/craft-core/Cargo.toml +++ b/native/craft-core/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" description = "CraftLabs 授权核心库 — Rust 实现,导出 craft_* C ABI。目标平台:Linux(主)> Windows(次)> macOS(最低)" [lib] -crate-type = ["cdylib", "staticlib"] +crate-type = ["cdylib", "staticlib", "lib"] name = "craftlabs_auth_core" [dependencies] diff --git a/native/craft-core/src/lib.rs b/native/craft-core/src/lib.rs index 6fcc7e6..90546b5 100644 --- a/native/craft-core/src/lib.rs +++ b/native/craft-core/src/lib.rs @@ -143,6 +143,11 @@ pub extern "C" fn craft_heartbeat(handle: *mut CraftContext) -> CraftResult { .map_or_else(fail_result, |_| ok_result()) } +pub fn craft_initialize_with_config(config: &str) -> *mut CraftContext { + let c_str = std::ffi::CString::new(config).unwrap_or_default(); + craft_initialize(c_str.as_ptr()) +} + #[no_mangle] pub extern "C" fn craft_destroy(handle: *mut CraftContext) { if !handle.is_null() { diff --git a/native/craftlabs-auth-cli/Cargo.toml b/native/craftlabs-auth-cli/Cargo.toml new file mode 100644 index 0000000..9e36a0e --- /dev/null +++ b/native/craftlabs-auth-cli/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "craftlabs-auth-cli" +version = "1.0.0" +edition = "2021" +description = "CraftLabs 授权客户端 CLI — 设备授权/查看/撤销/迁移" + +[[bin]] +name = "craft" +path = "src/main.rs" + +[dependencies] +craft-core = { path = "../craft-core" } +clap = { version = "4", features = ["derive"] } +serde_json = "1" +chrono = "0.4" diff --git a/native/craftlabs-auth-cli/src/main.rs b/native/craftlabs-auth-cli/src/main.rs new file mode 100644 index 0000000..4aa5ea5 --- /dev/null +++ b/native/craftlabs-auth-cli/src/main.rs @@ -0,0 +1,175 @@ +use clap::{Parser, Subcommand}; +use craftlabs_auth_core::{ + craft_activate, craft_check_license, craft_destroy, craft_free_license_info, + craft_get_license_info, craft_heartbeat, craft_initialize, craft_release, +}; +use craftlabs_auth_core::device; +use std::ffi::CString; +use std::path::PathBuf; + +#[derive(Parser)] +#[command(name = "craft", version, about = "CraftLabs 授权客户端")] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// 查看本地授权状态 + Status, + /// 使用 SN 激活本机 + Activate { sn: String }, + /// 检查授权是否有效 + Check, + /// 显示授权详情 + 功能特性 + Info, + /// 撤销本机授权 + Release, + /// 显示本机设备指纹 + DeviceId, + /// 手动触发心跳 + Heartbeat, +} + +fn get_config_path() -> PathBuf { + let mut path = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); + path.push("craftlabs-auth-config.json"); + path +} + +fn default_config() -> String { + r#"{"provider":"selfhosted","schemaVersion":1,"scenario":"floating"}"#.to_string() +} + +fn load_or_create_config() -> String { + let path = get_config_path(); + std::fs::read_to_string(&path).unwrap_or_else(|_| { + let cfg = default_config(); + std::fs::write(&path, &cfg).ok(); + cfg + }) +} + +fn main() { + let cli = Cli::parse(); + let config = load_or_create_config(); + let c_config = CString::new(config).unwrap(); + + match &cli.command { + Commands::Status => { + print_status(); + } + Commands::Activate { sn } => { + let handle = craft_initialize(c_config.as_ptr()); + if handle.is_null() { + eprintln!("错误: 初始化授权引擎失败"); + std::process::exit(1); + } + let c_sn = CString::new(sn.as_str()).unwrap(); + let result = craft_activate(handle, c_sn.as_ptr(), std::ptr::null()); + if result.success != 0 { + println!("✅ 激活成功"); + save_config_with_sn(sn); + } else { + let msg = if result.message.is_null() { "未知错误" } else { + unsafe { std::ffi::CStr::from_ptr(result.message) }.to_str().unwrap_or("未知错误") + }; + eprintln!("❌ 激活失败: {}", msg); + } + craft_destroy(handle); + } + Commands::Check => { + let handle = craft_initialize(c_config.as_ptr()); + if handle.is_null() { eprintln!("初始化失败"); return; } + let result = craft_check_license(handle); + if result.success != 0 { + println!("✅ 授权有效"); + } else { + println!("❌ 授权无效"); + } + craft_destroy(handle); + } + Commands::Info => { + let handle = craft_initialize(c_config.as_ptr()); + if handle.is_null() { eprintln!("初始化失败"); return; } + let info = craft_get_license_info(handle); + if !info.is_null() { + let i = unsafe { &*info }; + let names = unsafe { std::slice::from_raw_parts(i.feature_names, i.feature_count as usize) }; + let values = unsafe { std::slice::from_raw_parts(i.feature_values, i.feature_count as usize) }; + println!("授权状态: {}", if i.is_licensed != 0 { "已授权" } else { "未授权" }); + if i.expiration_date > 0 { + println!("过期时间: {}", i.expiration_date); + } + if i.feature_count > 0 { + println!("功能特性 ({}):", i.feature_count); + for idx in 0..i.feature_count as usize { + let name = if idx < names.len() && !names[idx].is_null() { + unsafe { std::ffi::CStr::from_ptr(names[idx]) } + .to_str().unwrap_or("?") + } else { "?" }; + let val = if idx < values.len() { values[idx] } else { 0 }; + println!(" {}: {}", name, if val != 0 { "✅" } else { "❌" }); + } + } + craft_free_license_info(info); + } else { + println!("无法获取授权信息"); + } + craft_destroy(handle); + } + Commands::Release => { + let handle = craft_initialize(c_config.as_ptr()); + if handle.is_null() { eprintln!("初始化失败"); return; } + let result = craft_release(handle); + if result.success != 0 { + println!("✅ 授权已撤销"); + } else { + println!("❌ 撤销失败"); + } + craft_destroy(handle); + } + Commands::DeviceId => { + let fp = device::collect(); + println!("设备指纹: {}", fp.composite_hash); + } + Commands::Heartbeat => { + let handle = craft_initialize(c_config.as_ptr()); + if handle.is_null() { eprintln!("初始化失败"); return; } + let result = craft_heartbeat(handle); + if result.success != 0 { + println!("✅ 心跳成功"); + } else { + println!("❌ 心跳失败"); + } + craft_destroy(handle); + } + } +} + +fn print_status() { + let config = load_or_create_config(); + let c_config = CString::new(config).unwrap(); + let handle = craft_initialize(c_config.as_ptr()); + if handle.is_null() { eprintln!("初始化失败"); return; } + + let check = craft_check_license(handle); + println!("授权状态: {}", if check.success != 0 { "✅ 有效" } else { "❌ 无效" }); + + let fp = device::collect(); + println!("设备指纹: {}", fp.composite_hash); + + craft_destroy(handle); +} + +fn save_config_with_sn(sn: &str) { + let config = serde_json::json!({ + "provider": "selfhosted", + "schemaVersion": 1, + "scenario": "floating", + "floating": { "sn": sn } + }); + let path = get_config_path(); + std::fs::write(&path, serde_json::to_string_pretty(&config).unwrap()).ok(); +}