diff --git a/native/craft-core/src/lib.rs b/native/craft-core/src/lib.rs index 430a8d0..e383074 100644 --- a/native/craft-core/src/lib.rs +++ b/native/craft-core/src/lib.rs @@ -1,23 +1,27 @@ -// CraftLabs 授权核心库 — Rust 实现 -// 导出 C ABI 接口,与 native/include/craftlabs_auth.h 一致 -// 对齐 docs/平台架构思路.md §3.1 - use std::ffi::CStr; use std::os::raw::c_char; use std::ptr; -mod activate; -mod heartbeat; -mod license; -mod security; +mod trait_provider; mod error; +mod security; mod session; pub mod crypto; pub mod device; pub mod provider_selfhosted; +use trait_provider::{Provider, ActivateResponse, HeartbeatResponse, LicenseStatus}; +use provider_selfhosted::SelfHostedProvider; + pub struct CraftContext { - dummy: i32, + pub provider: Option>, + pub initialized: bool, +} + +impl CraftContext { + pub fn new() -> Self { + CraftContext { provider: None, initialized: false } + } } #[repr(C)] @@ -36,41 +40,43 @@ pub struct LicenseInfo { } unsafe fn c_str_to_string(ptr: *const c_char) -> String { - if ptr.is_null() { - String::new() - } else { - CStr::from_ptr(ptr).to_string_lossy().into_owned() - } + if ptr.is_null() { String::new() } else { CStr::from_ptr(ptr).to_string_lossy().into_owned() } } static OK_MSG: &[u8] = b"ok\0"; -static FAIL_MSG: &[u8] = b"failure\0"; - -fn ok_result() -> CraftResult { - CraftResult { - success: 1, - message: OK_MSG.as_ptr() as *const c_char, - } +fn ok_result() -> CraftResult { CraftResult { success: 1, message: OK_MSG.as_ptr() as *const c_char } } +fn fail_result() -> CraftResult { + static FAIL: &[u8] = b"failure\0"; + CraftResult { success: 0, message: FAIL.as_ptr() as *const c_char } } -fn fail_result() -> CraftResult { - CraftResult { - success: 0, - message: FAIL_MSG.as_ptr() as *const c_char, - } +fn parse_provider(config: &str) -> String { + serde_json::from_str::(config) + .ok() + .and_then(|v| v.get("provider").and_then(|p| p.as_str().map(|s| s.to_string()))) + .unwrap_or_else(|| "selfhosted".to_string()) } #[no_mangle] pub extern "C" fn craft_initialize(config_json: *const c_char) -> *mut CraftContext { - let _config = unsafe { c_str_to_string(config_json) }; + let config_str = unsafe { c_str_to_string(config_json) }; - #[cfg(feature = "security-hardening")] - { - security::anti_debug::anti_debug_check(); - let _ = security::integrity::integrity_check(); + let mut ctx = Box::new(CraftContext::new()); + let mut provider: Box = match parse_provider(&config_str).as_str() { + "selfhosted" => Box::new(SelfHostedProvider::new()), + _ => Box::new(SelfHostedProvider::new()), + }; + + match provider.initialize(&ctx, &config_str) { + Ok(()) => { + ctx.provider = Some(provider); + ctx.initialized = true; + } + Err(_) => { + ctx.initialized = false; + } } - let ctx = Box::new(CraftContext::new()); Box::into_raw(ctx) } @@ -78,77 +84,72 @@ pub extern "C" fn craft_initialize(config_json: *const c_char) -> *mut CraftCont pub extern "C" fn craft_activate( handle: *mut CraftContext, license_key: *const c_char, - config_json: *const c_char, + _config_json: *const c_char, ) -> CraftResult { - if handle.is_null() { - return fail_result(); - } + if handle.is_null() { return fail_result(); } + let ctx = unsafe { &*handle }; let key = unsafe { c_str_to_string(license_key) }; - let config = unsafe { c_str_to_string(config_json) }; - activate::core_activate(&key, &config) + ctx.provider.as_ref() + .and_then(|p| p.activate(ctx, &key).ok()) + .map_or_else(fail_result, |_| ok_result()) } #[no_mangle] pub extern "C" fn craft_check_license(handle: *mut CraftContext) -> CraftResult { - if handle.is_null() { - return fail_result(); - } - license::check_license(unsafe { &*handle }) + if handle.is_null() { return fail_result(); } + let ctx = unsafe { &*handle }; + ctx.provider.as_ref() + .and_then(|p| p.check_license(ctx).ok()) + .map_or_else(fail_result, |s| if s.licensed { ok_result() } else { fail_result() }) } #[no_mangle] pub extern "C" fn craft_get_license_info(handle: *mut CraftContext) -> *mut LicenseInfo { - if handle.is_null() { - return ptr::null_mut(); - } - license::get_license_info(unsafe { &*handle }) + if handle.is_null() { return ptr::null_mut(); } + let ctx = unsafe { &*handle }; + ctx.provider.as_ref() + .map(|p| p.get_license_info(ctx)) + .map_or(ptr::null_mut(), |info| Box::into_raw(Box::new(info))) } #[no_mangle] pub extern "C" fn craft_free_license_info(info: *mut LicenseInfo) { - if !info.is_null() { - unsafe { - drop(Box::from_raw(info)); - } - } + if !info.is_null() { unsafe { drop(Box::from_raw(info)); } } } #[no_mangle] -pub extern "C" fn craft_has_feature( - handle: *mut CraftContext, - feature_name: *const c_char, -) -> i32 { - if handle.is_null() { - return 0; - } +pub extern "C" fn craft_has_feature(handle: *mut CraftContext, feature_name: *const c_char) -> i32 { + if handle.is_null() { return 0; } + let ctx = unsafe { &*handle }; let name = unsafe { c_str_to_string(feature_name) }; - if license::has_feature(unsafe { &*handle }, &name) { - 1 - } else { - 0 - } + ctx.provider.as_ref().map_or(0, |p| if p.has_feature(ctx, &name) { 1 } else { 0 }) } #[no_mangle] pub extern "C" fn craft_release(handle: *mut CraftContext) -> CraftResult { - if handle.is_null() { - return fail_result(); + if handle.is_null() { return fail_result(); } + let ctx = unsafe { &mut *handle }; + if let Some(ref mut p) = ctx.provider { + let _ = p.release(); } - license::release_license(unsafe { &mut *handle }) + ok_result() } #[no_mangle] pub extern "C" fn craft_heartbeat(handle: *mut CraftContext) -> CraftResult { - if handle.is_null() { - return fail_result(); - } - heartbeat::do_heartbeat(unsafe { &*handle }) + if handle.is_null() { return fail_result(); } + let ctx = unsafe { &*handle }; + ctx.provider.as_ref() + .and_then(|p| p.heartbeat(ctx).ok()) + .map_or_else(fail_result, |_| ok_result()) } #[no_mangle] pub extern "C" fn craft_destroy(handle: *mut CraftContext) { if !handle.is_null() { unsafe { + let ctx = &mut *handle; + if let Some(ref mut p) = ctx.provider { p.close(); } drop(Box::from_raw(handle)); } } diff --git a/native/craft-core/src/provider_selfhosted/mod.rs b/native/craft-core/src/provider_selfhosted/mod.rs index ce7c4c3..cc5747a 100644 --- a/native/craft-core/src/provider_selfhosted/mod.rs +++ b/native/craft-core/src/provider_selfhosted/mod.rs @@ -6,6 +6,7 @@ use crate::{ CraftContext, LicenseInfo, device::DeviceFingerprint, error::LicenseError, + trait_provider::{Provider, ActivateResponse, HeartbeatResponse, LicenseStatus}, }; pub struct SelfHostedConfig { @@ -131,3 +132,58 @@ impl SelfHostedProvider { self.cache.persist(&self.device_fp) } } + +// ── Provider trait 实现 ── + +impl Provider for SelfHostedProvider { + fn initialize(&mut self, _ctx: &CraftContext, config_json: &str) -> Result<(), LicenseError> { + let cfg: serde_json::Value = serde_json::from_str(config_json) + .map_err(|_| LicenseError::InvalidFormat("config json"))?; + let sh = cfg.get("selfhosted").ok_or(LicenseError::ConfigMissing("selfhosted section"))?; + + let base_url = sh.get("baseUrl").and_then(|v| v.as_str()).unwrap_or("").to_string(); + let tenant_key = sh.get("tenantKey").and_then(|v| v.as_str()).unwrap_or("").to_string(); + let offline_grace_days = sh.get("offlineGraceDays").and_then(|v| v.as_u64()).unwrap_or(7) as u32; + let heartbeat_interval_hours = sh.get("heartbeatIntervalHours").and_then(|v| v.as_u64()).unwrap_or(24) as u32; + let public_key_pem = option_env!("CRAFTLABS_SELFHOSTED_PUBKEY").unwrap_or("").to_string(); + + self.initialize(base_url, tenant_key, offline_grace_days, heartbeat_interval_hours, public_key_pem) + } + + fn activate(&self, _ctx: &CraftContext, _license_key: &str) -> Result { + Err(LicenseError::Network("online activation not yet implemented".into())) + } + + fn check_license(&self, _ctx: &CraftContext) -> Result { + self.check_license_offline()?; + let cached = self.cache.license.as_ref().ok_or(LicenseError::NoCachedLicense)?; + Ok(LicenseStatus { + licensed: true, + not_after: cached.not_after, + features: cached.features.clone(), + device_count: 0, + max_devices: cached.max_devices, + heartbeat_due: None, + }) + } + + fn heartbeat(&self, _ctx: &CraftContext) -> Result { + Err(LicenseError::Network("heartbeat not yet implemented".into())) + } + + fn has_feature(&self, _ctx: &CraftContext, name: &str) -> bool { + self.has_feature_offline(name) + } + + fn release(&mut self) -> Result<(), LicenseError> { + Ok(()) + } + + fn get_license_info(&self, _ctx: &CraftContext) -> LicenseInfo { + self.get_license_info_offline() + } + + fn close(&mut self) { + let _ = self.persist_cache(); + } +} diff --git a/native/craft-core/src/trait_provider.rs b/native/craft-core/src/trait_provider.rs new file mode 100644 index 0000000..0ee1c64 --- /dev/null +++ b/native/craft-core/src/trait_provider.rs @@ -0,0 +1,36 @@ +use std::collections::HashMap; +use crate::{CraftContext, LicenseInfo, error::LicenseError}; + +#[derive(Debug)] +pub struct LicenseStatus { + pub licensed: bool, + pub not_after: Option, + pub features: HashMap, + pub device_count: u32, + pub max_devices: u32, + pub heartbeat_due: Option, +} + +pub struct ActivateResponse { + pub success: bool, + pub device_id: String, + pub license_payload: Vec, +} + +pub struct HeartbeatResponse { + pub valid: bool, + pub lease_until: Option, + pub update_available: bool, + pub new_license_payload: Option>, +} + +pub trait Provider: Send + Sync { + fn initialize(&mut self, ctx: &CraftContext, config_json: &str) -> Result<(), LicenseError>; + fn activate(&self, ctx: &CraftContext, license_key: &str) -> Result; + fn check_license(&self, ctx: &CraftContext) -> Result; + fn heartbeat(&self, ctx: &CraftContext) -> Result; + fn has_feature(&self, ctx: &CraftContext, name: &str) -> bool; + fn release(&mut self) -> Result<(), LicenseError>; + fn get_license_info(&self, ctx: &CraftContext) -> LicenseInfo; + fn close(&mut self); +}