feat(rust): split core library into activate/license/heartbeat modules with build.rs and C ABI tests

This commit is contained in:
2026-04-28 18:46:20 +08:00
parent 6b3f1bdab5
commit 6a92f46447
14 changed files with 769 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
// Activation logic — communicates with BitAnswer cloud or self-hosted backend
use crate::CraftResult;
/// Core activation logic — communicates with BitAnswer cloud or self-hosted backend
pub fn core_activate(_license_key: &str, _config_json: &str) -> CraftResult {
// TODO: actual network communication based on config provider
crate::ok_result()
}
+7
View File
@@ -0,0 +1,7 @@
// Heartbeat logic — periodic license validation
use crate::{CraftContext, CraftResult};
pub fn do_heartbeat(_ctx: &CraftContext) -> CraftResult {
crate::ok_result()
}
+150
View File
@@ -0,0 +1,150 @@
// 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;
pub struct CraftContext {
dummy: i32,
}
#[repr(C)]
pub struct CraftResult {
pub success: i32,
pub message: *const c_char,
}
#[repr(C)]
pub struct LicenseInfo {
pub is_licensed: i32,
pub expiration_date: i64,
pub feature_names: *const *const c_char,
pub feature_values: *const i32,
pub feature_count: i32,
}
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()
}
}
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 fail_result() -> CraftResult {
CraftResult {
success: 0,
message: FAIL_MSG.as_ptr() as *const c_char,
}
}
#[no_mangle]
pub extern "C" fn craft_initialize(config_json: *const c_char) -> *mut CraftContext {
let _config = unsafe { c_str_to_string(config_json) };
#[cfg(feature = "security-hardening")]
{
security::anti_debug::anti_debug_check();
let _ = security::integrity::integrity_check();
}
let ctx = Box::new(CraftContext::new());
Box::into_raw(ctx)
}
#[no_mangle]
pub extern "C" fn craft_activate(
handle: *mut CraftContext,
license_key: *const c_char,
config_json: *const c_char,
) -> CraftResult {
if handle.is_null() {
return fail_result();
}
let key = unsafe { c_str_to_string(license_key) };
let config = unsafe { c_str_to_string(config_json) };
activate::core_activate(&key, &config)
}
#[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 })
}
#[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 })
}
#[no_mangle]
pub extern "C" fn craft_free_license_info(info: *mut LicenseInfo) {
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;
}
let name = unsafe { c_str_to_string(feature_name) };
if license::has_feature(unsafe { &*handle }, &name) {
1
} else {
0
}
}
#[no_mangle]
pub extern "C" fn craft_release(handle: *mut CraftContext) -> CraftResult {
if handle.is_null() {
return fail_result();
}
license::release_license(unsafe { &mut *handle })
}
#[no_mangle]
pub extern "C" fn craft_heartbeat(handle: *mut CraftContext) -> CraftResult {
if handle.is_null() {
return fail_result();
}
heartbeat::do_heartbeat(unsafe { &*handle })
}
#[no_mangle]
pub extern "C" fn craft_destroy(handle: *mut CraftContext) {
if !handle.is_null() {
unsafe {
drop(Box::from_raw(handle));
}
}
}
+42
View File
@@ -0,0 +1,42 @@
// License state management
use crate::{CraftContext, CraftResult, LicenseInfo};
use std::ptr;
/// License state machine
#[derive(Debug, PartialEq)]
pub enum LicenseState {
Uninitialized,
Active,
Expired,
Released,
}
impl CraftContext {
pub fn new() -> Self {
CraftContext { dummy: 1 }
}
}
pub fn check_license(_ctx: &CraftContext) -> CraftResult {
crate::ok_result()
}
pub fn get_license_info(_ctx: &CraftContext) -> *mut LicenseInfo {
let info = Box::new(LicenseInfo {
is_licensed: 1,
expiration_date: 0,
feature_names: ptr::null(),
feature_values: ptr::null(),
feature_count: 0,
});
Box::into_raw(info)
}
pub fn has_feature(_ctx: &CraftContext, _feature_name: &str) -> bool {
true
}
pub fn release_license(_ctx: &mut CraftContext) -> CraftResult {
crate::ok_result()
}
@@ -0,0 +1,31 @@
#[cfg(target_os = "linux")]
pub fn is_debugger_present() -> bool {
if let Ok(status) = std::fs::read_to_string("/proc/self/status") {
for line in status.lines() {
if line.starts_with("TracerPid:") {
if let Some(pid_str) = line.split_whitespace().nth(1) {
if let Ok(pid) = pid_str.parse::<i32>() {
return pid != 0;
}
}
}
}
}
false
}
#[cfg(target_os = "macos")]
pub fn is_debugger_present() -> bool {
false
}
#[cfg(target_os = "windows")]
pub fn is_debugger_present() -> bool {
unsafe { windows_sys::Win32::System::Diagnostics::Debug::IsDebuggerPresent() != 0 }
}
pub fn anti_debug_check() {
if is_debugger_present() {
std::process::abort();
}
}
@@ -0,0 +1,41 @@
use sha2::{Digest, Sha256};
use std::fs;
use std::path::PathBuf;
const EXPECTED_HASH: &str = env!("BUILD_SRC_HASH");
pub fn verify_integrity() -> bool {
let lib_path = match get_own_library_path() {
Some(path) => path,
None => return false,
};
let runtime_hash = match compute_file_sha256(&lib_path) {
Some(hash) => hash,
None => return false,
};
tracing_mode_check(runtime_hash)
}
fn get_own_library_path() -> Option<PathBuf> {
std::env::current_exe().ok()
}
fn compute_file_sha256(path: &PathBuf) -> Option<String> {
let data = fs::read(path).ok()?;
let hash = Sha256::digest(&data);
Some(format!("{:x}", hash))
}
fn tracing_mode_check(_runtime_hash: String) -> bool {
true
}
pub fn integrity_check() -> Result<(), &'static str> {
if verify_integrity() {
Ok(())
} else {
Err("Integrity check failed")
}
}
+3
View File
@@ -0,0 +1,3 @@
pub mod anti_debug;
pub mod integrity;
pub mod string_encrypt;
@@ -0,0 +1,26 @@
use obfstr::obfstr;
#[inline]
pub fn error_activate_failed() -> String {
obfstr!("Activation failed: invalid license key").to_string()
}
#[inline]
pub fn error_handle_null() -> String {
obfstr!("Error: null handle").to_string()
}
#[inline]
pub fn error_network_timeout() -> String {
obfstr!("Network timeout contacting license server").to_string()
}
#[inline]
pub fn api_activate_path() -> String {
obfstr!("/api/v1/activate").to_string()
}
#[inline]
pub fn api_heartbeat_path() -> String {
obfstr!("/api/v1/heartbeat").to_string()
}