From 5bfcbcb8e94c320f172ff832e2582e8634de201e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E4=B9=8B=E9=92=B3?= Date: Mon, 27 Jul 2026 21:54:46 +0800 Subject: [PATCH] feat(v1.0.1): tangent_pull + error codes + version + license management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remaining P3 item: - tangent_pull: maintain G1 tangency when pulling faces System infrastructure: - Unified ErrorCode (0-7xxx): ErrorInfo with Chinese messages - Version system: VDE_VERSION_MAJOR/MINOR/PATCH macros, vde_version() → VersionInfo, vde_version_string() - License management: LicenseTier (Community/Professional/Enterprise), LicenseKey (VDE-XXXX-XXXX, Base32 + HMAC-SHA256 signature), LicenseManager singleton with trial support - Convenience header: vde/vde_version.h Tests: 43 total (26 license + 17 version) --- CMakeLists.txt | 8 + cmake/vde_build_config.h.in | 12 + include/vde/brep/direct_modeling.h | 42 ++ include/vde/core/license_manager.h | 278 ++++++++++++ include/vde/core/version.h | 176 ++++++++ include/vde/foundation/error_codes.h | 304 +++++++++++-- include/vde/vde_version.h | 32 ++ src/CMakeLists.txt | 3 + src/brep/direct_modeling.cpp | 158 +++++++ src/core/license_manager.cpp | 617 +++++++++++++++++++++++++++ src/foundation/error_codes.cpp | 221 ++++++++++ tests/core/CMakeLists.txt | 2 + tests/core/test_license.cpp | 396 +++++++++++++++++ tests/core/test_version.cpp | 130 ++++++ 14 files changed, 2355 insertions(+), 24 deletions(-) create mode 100644 cmake/vde_build_config.h.in create mode 100644 include/vde/core/license_manager.h create mode 100644 include/vde/core/version.h create mode 100644 include/vde/vde_version.h create mode 100644 src/core/license_manager.cpp create mode 100644 src/foundation/error_codes.cpp create mode 100644 tests/core/test_license.cpp create mode 100644 tests/core/test_version.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 904accd..2391e23 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,14 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +# ── 生成构建时间戳 ────────────────────────────────── +string(TIMESTAMP VDE_BUILD_TIMESTAMP "%Y-%m-%dT%H:%M:%SZ" UTC) +configure_file( + ${CMAKE_SOURCE_DIR}/cmake/vde_build_config.h.in + ${CMAKE_BINARY_DIR}/generated/vde_build_config.h + @ONLY +) + option(BUILD_TESTS "Build tests" ON) option(BUILD_BENCHMARKS "Build benchmarks" OFF) option(BUILD_EXAMPLES "Build examples" ON) diff --git a/cmake/vde_build_config.h.in b/cmake/vde_build_config.h.in new file mode 100644 index 0000000..ebfaa28 --- /dev/null +++ b/cmake/vde_build_config.h.in @@ -0,0 +1,12 @@ +#pragma once +/** + * @file vde_build_config.h + * @brief CMake configure_file 生成的构建配置 + * @warning 此文件由 CMake 自动生成,请勿手动编辑。 + */ + +/// 构建时间戳(ISO 8601 UTC 格式) +#define VDE_BUILD_TIMESTAMP "@VDE_BUILD_TIMESTAMP@" + +/// 构建类型 +#define VDE_BUILD_TYPE "@VDE_BUILD_TYPE@" diff --git a/include/vde/brep/direct_modeling.h b/include/vde/brep/direct_modeling.h index 3cdb711..ec152f1 100644 --- a/include/vde/brep/direct_modeling.h +++ b/include/vde/brep/direct_modeling.h @@ -343,4 +343,46 @@ struct DirectModelingResult { int face_id, int target_face_id); +// ═══════════════════════════════════════════════════════════ +// Direct Modeling Enhancements — v5.6 +// ═══════════════════════════════════════════════════════════ + +/** + * @brief 相切联动拉伸:拉伸面并保持相邻面相切 + * + * 将指定面沿其法向拉伸,同时检测并调整相邻面,使其 + * 在共享边处保持与拉伸面的相切关系(G1 连续)。 + * + * 非相切邻面保持原有行为(仅共享边处适配新位置)。 + * + * 算法: + * 1. 检测相邻面(与目标面共享边的面) + * 2. 对每个相邻面检测 G1 连续性 + * - 平面面:通过法向量平行性判定(dot > 0.9999) + * - 曲面:通过 surface_continuity 检测 + * 3. 执行 push_pull 拉伸目标面 + * 4. 对 G1 相邻面,将其非共享顶点沿目标面法向偏移相同距离 + * 以保持相切关系 + * + * @param body 输入 B-Rep 实体 + * @param face_id 被拉伸的面索引 + * @param distance 拉伸距离(正=挤出加材料,负=切除减材料) + * @return DirectModelingResult + * - result.body: 拉伸后保持相切的新模型 + * - result.affected_faces: 受影响的面数(含调整的相邻面) + * - result.new_faces: 新创建的侧面数 + * + * @pre face_id 在 [0, body.num_faces()) 范围内 + * @pre 实体应为封闭水密体 + * @pre distance 的绝对值不超过实体在法线方向的最小厚度 + * + * @code{.cpp} + * auto box = make_box(10, 5, 3); + * // 拉伸顶面 2.0,相切邻面自动联动 + * auto result = tangent_pull(box, 0, 2.0); + * @endcode + */ +[[nodiscard]] DirectModelingResult tangent_pull(const BrepModel& body, + int face_id, double distance); + } // namespace vde::brep diff --git a/include/vde/core/license_manager.h b/include/vde/core/license_manager.h new file mode 100644 index 0000000..b2f3444 --- /dev/null +++ b/include/vde/core/license_manager.h @@ -0,0 +1,278 @@ +#pragma once +/** + * @file license_manager.h + * @brief ViewDesignEngine 许可证管理系统 + * + * 提供三层许可证体系: + * - Community(社区版):基础 B-Rep + STEP/IGES 导入导出 + * - Professional(专业版):+ 5轴CAM + 高级曲面 + GD&T + * - Enterprise(企业版):+ 技术支持 + 自定义格式 + * + * LicenseKey 格式:VDE-XXXX-XXXX-XXXX(Base32编码) + * 包含:版本号 + tier + 过期时间 + 签名(HMAC-SHA256) + * + * @ingroup core + */ + +#include +#include +#include +#include +#include +#include + +namespace vde::core { + +// ═══════════════════════════════════════════════════════════════ +// LicenseTier 枚举 +// ═══════════════════════════════════════════════════════════════ + +/// 许可证层级 +enum class LicenseTier : int { + Community = 0, ///< 社区版 + Professional = 1, ///< 专业版 + Enterprise = 2, ///< 企业版 +}; + +/// 将 LicenseTier 转为字符串 +inline const char* license_tier_name(LicenseTier tier) { + switch (tier) { + case LicenseTier::Community: return "Community"; + case LicenseTier::Professional: return "Professional"; + case LicenseTier::Enterprise: return "Enterprise"; + } + return "Unknown"; +} + +/// 从字符串解析 LicenseTier,失败返回 -1 +int parse_license_tier(const std::string& name); + +// ═══════════════════════════════════════════════════════════════ +// Feature 枚举(按 tier 区分) +// ═══════════════════════════════════════════════════════════════ + +/// 功能特性标识 +enum class Feature : int { + // Community 级别 + BASIC_BREP = 0, ///< 基础B-Rep建模 + STEP_IMPORT_EXPORT = 1, ///< STEP导入导出 + IGES_IMPORT_EXPORT = 2, ///< IGES导入导出 + BASE_IO_FORMATS = 3, ///< 基础格式IO(OBJ/STL/PLY) + MESH_OPERATIONS = 4, ///< 基本网格操作 + SKETCH_SOLVER = 5, ///< 草图约束求解 + + // Professional 级别 + CAM_5AXIS = 10, ///< 5轴CAM加工 + ADVANCED_SURFACING = 11, ///< 高级曲面建模(A级曲面等) + GD_T = 12, ///< GD&T 几何尺寸和公差 + PMI_MBD = 13, ///< PMI / 基于模型的定义 + ASSEMBLY_DESIGN = 14, ///< 装配设计 + SHEET_METAL = 15, ///< 钣金设计 + ADVANCED_HEALING = 16, ///< 高级修复 + FEA_MESH = 17, ///< FEA网格生成 + IGA_ANALYSIS = 18, ///< IGA等几何分析 + COLLISION_DETECTION = 19, ///< 碰撞检测 + DIRECT_MODELING = 20, ///< 直接建模 + SURFACE_FAIRING = 21, ///< 曲面光顺 + + // Enterprise 级别 + TECHNICAL_SUPPORT = 30, ///< 技术支持 + CUSTOM_FORMATS = 31, ///< 自定义格式支持 + SCRIPTING_API = 32, ///< 脚本API + GPU_ACCELERATION = 33, ///< GPU加速 + CLOUD_COLLABORATION = 34, ///< 云端协作 + SDF_MODELING = 35, ///< SDF隐式建模 + LICENSE_MANAGER_SDK = 36, ///< License SDK分发 +}; + +/// Feature 转字符串 +const char* feature_name(Feature f); + +/// 字符串转 Feature,失败返回 -1 +int parse_feature(const std::string& name); + +/// 获取指定 tier 包含的所有 feature +std::set features_for_tier(LicenseTier tier); + +/// 检查某个 feature 是否属于指定 tier +bool feature_available_in_tier(Feature f, LicenseTier tier); + +// ═══════════════════════════════════════════════════════════════ +// LicenseInfo 结构体 +// ═══════════════════════════════════════════════════════════════ + +/// 许可证信息 +struct LicenseInfo { + LicenseTier tier; ///< 层级 + std::string licensee; ///< 被许可方(公司/个人名称) + std::time_t expiry; ///< 过期时间(Unix时间戳,0=永久) + std::set features; ///< 启用的功能列表 + std::string key_string; ///< 原始License Key字符串 + int version; ///< License版本号 + + /// 默认构造(Community试用版) + LicenseInfo() + : tier(LicenseTier::Community) + , licensee("Trial User") + , expiry(0) + , version(1) {} + + /// 是否已过期 + [[nodiscard]] bool is_expired() const; + + /// 距离过期还剩多少天(返回 -1 表示永久有效) + [[nodiscard]] int days_until_expiry() const; +}; + +// ═══════════════════════════════════════════════════════════════ +// LicenseKey 类 +// ═══════════════════════════════════════════════════════════════ + +/** + * @brief LicenseKey 生成/验证/解析类 + * + * License Key 格式:VDE-XXXX-XXXX-XXXX + * 其中 XXXX 段为 Base32 编码的二进制数据。 + * + * 二进制数据布局(17字节): + * - byte 0: version (1) + * - byte 1: tier (0=Community, 1=Professional, 2=Enterprise) + * - byte 2-9: expiry (uint64_t, Unix timestamp, big-endian) + * - byte 10-16: reserved (0) + * + * 签名:HMAC-SHA256 的前 4 字节附加到 Base32 编码后的最后一段。 + * + * 完整的 Key 编码流程: + * payload(17B) → HMAC-SHA256 → 取前4B → payload+signature(21B) → Base32 → VDE-XXXX-XXXX-XXXX + */ +class LicenseKey { +public: + /// 默认构造(无效Key) + LicenseKey() = default; + + /** + * @brief 从字符串解析 LicenseKey + * @param key_str 如 "VDE-ABCD-EFGH-IJKL" + * @return 如果格式有效返回 true + */ + bool parse(const std::string& key_str); + + /** + * @brief 生成 LicenseKey + * @param tier 许可证层级 + * @param expiry 过期时间(Unix时间戳,0=永久) + * @param licensee 被许可方名称 + * @return 格式化的License Key字符串 + */ + static std::string generate(LicenseTier tier, std::time_t expiry, + const std::string& licensee); + + /** + * @brief 验证 LicenseKey 是否有效 + * @param key_str License Key 字符串 + * @return 有效则返回 true + */ + static bool validate(const std::string& key_str); + + /// 获取解析后的 LicenseInfo + [[nodiscard]] const LicenseInfo& info() const { return info_; } + + /// 原始 Key 字符串 + [[nodiscard]] const std::string& key_string() const { return key_string_; } + + /// 签名验证是否通过 + [[nodiscard]] bool signature_valid() const { return sig_valid_; } + +private: + /// Base32 编解码(RFC 4648,无填充) + static std::string base32_encode(const std::vector& data); + static std::vector base32_decode(const std::string& encoded); + + /// HMAC-SHA256 简化实现(使用占位密钥) + static std::vector compute_hmac(const std::vector& data); + + LicenseInfo info_; + std::string key_string_; + bool sig_valid_ = false; +}; + +// ═══════════════════════════════════════════════════════════════ +// LicenseManager 单例 +// ═══════════════════════════════════════════════════════════════ + +/** + * @brief LicenseManager 全局许可证管理器(单例) + * + * 管理当前运行时使用的许可证,提供功能查询接口。 + * + * 用法: + * @code + * auto& mgr = LicenseManager::instance(); + * mgr.set_license("VDE-ABCD-EFGH-IJKL"); + * if (mgr.has_feature("CAM_5AXIS")) { ... } + * if (mgr.is_expired()) { ... } + * @endcode + */ +class LicenseManager { +public: + /// 获取单例实例 + static LicenseManager& instance(); + + // ── 禁止拷贝和移动 ────────────────────────────── + LicenseManager(const LicenseManager&) = delete; + LicenseManager& operator=(const LicenseManager&) = delete; + + /** + * @brief 设置 License Key + * @param key License Key 字符串 + * @return true 设置成功(Key有效且未过期) + */ + bool set_license(const std::string& key); + + /** + * @brief 进入试用模式(30天Community试用) + * @param days 试用天数(默认30) + */ + void start_trial(int days = 30); + + /// 当前许可证层级 + [[nodiscard]] LicenseTier current_tier() const; + + /** + * @brief 检查是否拥有某个功能 + * @param feature_name 功能名称字符串(如 "CAM_5AXIS") + * @return true 表示该功能可用 + */ + [[nodiscard]] bool has_feature(const std::string& feature_name) const; + + /** + * @brief 检查 Feature 枚举是否可用 + * @param f Feature 枚举值 + * @return true 表示该功能可用 + */ + [[nodiscard]] bool has_feature(Feature f) const; + + /// 许可证是否已过期 + [[nodiscard]] bool is_expired() const; + + /// 试用剩余天数(-1 表示已过期或无试用) + [[nodiscard]] int trial_days_left() const; + + /// 获取当前许可证信息 + [[nodiscard]] const LicenseInfo& current_license() const; + + /// 是否处于试用模式 + [[nodiscard]] bool is_trial() const; + +private: + LicenseManager(); + ~LicenseManager() = default; + + LicenseInfo current_; + bool trial_mode_ = false; + std::time_t trial_start_ = 0; + int trial_days_ = 30; + mutable std::mutex mutex_; +}; + +} // namespace vde::core diff --git a/include/vde/core/version.h b/include/vde/core/version.h new file mode 100644 index 0000000..87879ca --- /dev/null +++ b/include/vde/core/version.h @@ -0,0 +1,176 @@ +#pragma once +/** + * @file version.h + * @brief ViewDesignEngine 版本信息 + * + * 提供编译时版本宏和运行时版本查询函数。 + * 编译时间戳通过 CMake 的 configure_file 在构建时注入。 + * + * @ingroup core + */ + +#include +#include + +// 包含 CMake 生成的构建配置 +#if __has_include("vde_build_config.h") +#include "vde_build_config.h" +#endif + +// ═══════════════════════════════════════════════════════════════ +// 编译时版本宏 +// ═══════════════════════════════════════════════════════════════ + +/// 主版本号(不兼容的 API 变更) +#define VDE_VERSION_MAJOR 1 + +/// 次版本号(向后兼容的功能新增) +#define VDE_VERSION_MINOR 0 + +/// 修订版本号(向后兼容的问题修复) +#define VDE_VERSION_PATCH 1 + +/// 版本字符串 +#define VDE_VERSION_STRING "1.0.1" + +// ── 版本号整型(用于比较) ──────────────────────────── +/// 版本号编码为大整数: VDE_VERSION(major, minor, patch) = major*10000 + minor*100 + patch +#define VDE_VERSION_CODE(major, minor, patch) \ + ((major) * 10000 + (minor) * 100 + (patch)) + +/// 当前版本号整型编码 +#define VDE_VERSION_NUM VDE_VERSION_CODE(1, 0, 1) + +// ═══════════════════════════════════════════════════════════════ +// 构建信息(由 CMake configure_file 注入) +// ═══════════════════════════════════════════════════════════════ + +#ifndef VDE_BUILD_TYPE + /// 构建类型:Debug / Release / RelWithDebInfo / MinSizeRel + #ifdef NDEBUG + #define VDE_BUILD_TYPE "Release" + #else + #define VDE_BUILD_TYPE "Debug" + #endif +#endif + +#ifndef VDE_BUILD_TIMESTAMP + /// 构建时间戳(ISO 8601 格式,如 "2026-07-27T12:00:00Z") + /// 由 CMake 的 configure_file 注入 + #define VDE_BUILD_TIMESTAMP "unknown" +#endif + +#ifndef VDE_COMPILER_INFO + /// 编译器信息字符串 + #if defined(__clang__) + #define VDE_COMPILER_INFO "Clang " __clang_version__ + #elif defined(__GNUC__) || defined(__GNUG__) + #define VDE_COMPILER_INFO "GCC " __VERSION__ + #elif defined(_MSC_VER) + #define VDE_COMPILER_INFO "MSVC " _MSC_VER_STR + #else + #define VDE_COMPILER_INFO "Unknown Compiler" + #endif +#endif + +// ═══════════════════════════════════════════════════════════════ +// C++ 标准信息 +// ═══════════════════════════════════════════════════════════════ + +/// C++ 标准字符串(如 "C++20") +#if __cplusplus >= 202002L + #define VDE_CPP_STANDARD_STRING "C++20" +#elif __cplusplus >= 201703L + #define VDE_CPP_STANDARD_STRING "C++17" +#elif __cplusplus >= 201402L + #define VDE_CPP_STANDARD_STRING "C++14" +#elif __cplusplus >= 201103L + #define VDE_CPP_STANDARD_STRING "C++11" +#else + #define VDE_CPP_STANDARD_STRING "C++03" +#endif + +namespace vde::core { + +// ═══════════════════════════════════════════════════════════════ +// VersionInfo 结构体 +// ═══════════════════════════════════════════════════════════════ + +/** + * @brief 版本信息结构体 + * + * 包含库版本号、构建类型、时间戳和编译器信息。 + */ +struct VersionInfo { + int major; ///< 主版本号 + int minor; ///< 次版本号 + int patch; ///< 修订版本号 + std::string version_string; ///< 版本字符串 "1.0.1" + std::string build_type; ///< 构建类型 "Debug"/"Release" + std::string build_timestamp;///< 构建时间戳 + std::string compiler_info; ///< 编译器信息 + std::string cpp_standard; ///< C++ 标准字符串 "C++20" + + /// 默认构造(使用编译时宏填充) + VersionInfo() + : major(VDE_VERSION_MAJOR) + , minor(VDE_VERSION_MINOR) + , patch(VDE_VERSION_PATCH) + , version_string(VDE_VERSION_STRING) + , build_type(VDE_BUILD_TYPE) + , build_timestamp(VDE_BUILD_TIMESTAMP) + , compiler_info(VDE_COMPILER_INFO) + , cpp_standard(VDE_CPP_STANDARD_STRING) {} +}; + +// ═══════════════════════════════════════════════════════════════ +// API 函数 +// ═══════════════════════════════════════════════════════════════ + +/** + * @brief 获取版本信息结构体 + * @return VersionInfo 包含完整的版本和构建信息 + */ +inline VersionInfo vde_version() { + return VersionInfo{}; +} + +/** + * @brief 获取人类可读的版本字符串 + * + * 格式:"ViewDesignEngine v1.0.1 (C++20, GCC 11.4)" + * + * @return 版本信息字符串 + */ +inline std::string vde_version_string() { + std::string result = "ViewDesignEngine v"; + result += VDE_VERSION_STRING; + result += " ("; + result += VDE_CPP_STANDARD_STRING; + result += ", "; + result += VDE_COMPILER_INFO; + result += ")"; + return result; +} + +/** + * @brief 比较两个版本号 + * + * 返回负值表示 lhs < rhs,零表示相等,正值表示 lhs > rhs。 + * + * @param lhs_major 左主版本 + * @param lhs_minor 左次版本 + * @param lhs_patch 左修订版本 + * @param rhs_major 右主版本 + * @param rhs_minor 右次版本 + * @param rhs_patch 右修订版本 + * @return 比较结果 + */ +inline int vde_version_compare(int lhs_major, int lhs_minor, int lhs_patch, + int rhs_major, int rhs_minor, int rhs_patch) { + int lhs = VDE_VERSION_CODE(lhs_major, lhs_minor, lhs_patch); + int rhs = VDE_VERSION_CODE(rhs_major, rhs_minor, rhs_patch); + return lhs - rhs; +} + +} // namespace vde::core diff --git a/include/vde/foundation/error_codes.h b/include/vde/foundation/error_codes.h index b7ac364..abd6eb1 100644 --- a/include/vde/foundation/error_codes.h +++ b/include/vde/foundation/error_codes.h @@ -1,33 +1,289 @@ #pragma once -#include - -namespace vde::foundation { - /** - * @brief 全局错误码枚举 + * @file error_codes.h + * @brief ViewDesignEngine 统一错误码体系 * - * VDE 引擎中所有函数可能返回的错误状态码。 - * 成功用 kSuccess(0) 表示,非零值表示各类错误。 + * 所有模块的错误码集中定义在此,采用分段编码: + * 0 — SUCCESS + * 1xxx — foundation(格式IO错误) + * 2xxx — core(几何计算错误) + * 3xxx — curves(曲线曲面错误) + * 4xxx — mesh(网格错误) + * 5xxx — brep(B-Rep错误) + * 6xxx — sdf(SDF错误) + * 7xxx — capi(C API错误) * * @ingroup foundation */ + +#include +#include +#include + +namespace vde::foundation { + +// ═══════════════════════════════════════════════════════════════ +// ErrorCode 枚举 — 所有模块统一 +// ═══════════════════════════════════════════════════════════════ + enum class ErrorCode : int32_t { - /// 操作成功完成,无错误 - kSuccess = 0, - /// 传入参数无效(空指针、越界、非法值等) - kInvalidArgument, - /// 输入几何数据不合法(自交、非流形、退化等) - kInvalidGeometry, - /// 退化情况(共线、共面等导致无法确定唯一解) - kDegenerateCase, - /// 功能尚未实现 - kNotImplemented, - /// 数值计算误差超出容差范围 - kNumericalError, - /// 内存分配失败 - kOutOfMemory, - /// 内部逻辑错误(不应出现的情况) - kInternalError, + // ── 成功 ─────────────────────────────────────────── + SUCCESS = 0, + + // ── 1xxx:foundation 格式IO错误 ────────────────────── + // 通用文件IO + ERR_FILE_NOT_FOUND = 1001, // 文件未找到 + ERR_FILE_OPEN_FAILED = 1002, // 文件打开失败 + ERR_FILE_WRITE_FAILED = 1003, // 文件写入失败 + ERR_FILE_READ_FAILED = 1004, // 文件读取失败 + ERR_FILE_PERMISSION_DENIED = 1005, // 文件权限不足 + ERR_FILE_TOO_LARGE = 1006, // 文件过大 + ERR_FILE_EMPTY = 1007, // 文件为空 + ERR_FILE_ALREADY_EXISTS = 1008, // 文件已存在 + + // 格式解析 + ERR_FORMAT_INVALID = 1101, // 格式无效 + ERR_FORMAT_UNSUPPORTED = 1102, // 格式不支持 + ERR_FORMAT_VERSION_MISMATCH = 1103, // 格式版本不匹配 + ERR_FORMAT_CORRUPTED = 1104, // 格式数据损坏 + ERR_FORMAT_TRUNCATED = 1105, // 格式数据截断 + ERR_FORMAT_MAGIC_MISMATCH = 1106, // 格式魔术字不匹配 + + // VDE 原生格式 + ERR_VDE_HEADER_INVALID = 1201, // VDE文件头无效 + ERR_VDE_SECTION_MISSING = 1202, // VDE段缺失 + ERR_VDE_SECTION_CORRUPTED = 1203, // VDE段损坏 + ERR_VDE_TOPOLOGY_INVALID = 1204, // VDE拓扑数据无效 + ERR_VDE_GEOMETRY_INVALID = 1205, // VDE几何数据无效 + ERR_VDE_SERIALIZE_FAILED = 1206, // VDE序列化失败 + ERR_VDE_DESERIALIZE_FAILED = 1207, // VDE反序列化失败 + + // 工业格式 IO + ERR_STEP_PARSE = 1301, // STEP解析错误 + ERR_STEP_SCHEMA_MISMATCH = 1302, // STEP模式不匹配 + ERR_STEP_ENTITY_UNKNOWN = 1303, // STEP未知实体 + ERR_STEP_TOPOLOGY = 1304, // STEP拓扑构建失败 + ERR_STEP_GEOMETRY = 1305, // STEP几何构建失败 + ERR_IGES_PARSE = 1311, // IGES解析错误 + ERR_IGES_ENTITY_UNSUPPORTED = 1312, // IGES不支持的实体 + ERR_OBJ_PARSE = 1321, // OBJ解析错误 + ERR_STL_PARSE = 1331, // STL解析错误 + ERR_STL_BINARY_INVALID = 1332, // STL二进制格式无效 + ERR_PLY_PARSE = 1341, // PLY解析错误 + ERR_GLTF_PARSE = 1351, // glTF解析错误 + ERR_3MF_PARSE = 1361, // 3MF解析错误 + ERR_DXF_PARSE = 1371, // DXF解析错误 + + // ── 2xxx:core 几何计算错误 ───────────────────────── + ERR_DEGENERATE_GEOMETRY = 2001, // 退化几何 + ERR_NUMERIC_INSTABILITY = 2002, // 数值不稳定 + ERR_DIVISION_BY_ZERO = 2003, // 除零错误 + ERR_SINGULAR_MATRIX = 2004, // 奇异矩阵 + ERR_CONVERGENCE_FAILURE = 2005, // 收敛失败 + ERR_OUT_OF_MEMORY = 2006, // 内存不足 + ERR_OVERFLOW = 2007, // 数值溢出 + ERR_PRECISION_LOSS = 2008, // 精度丢失 + ERR_BUFFER_TOO_SMALL = 2009, // 缓冲区太小 + ERR_INDEX_OUT_OF_BOUNDS = 2010, // 索引越界 + + // 几何运算 + ERR_INTERSECTION_FAILED = 2101, // 求交失败 + ERR_PROJECTION_FAILED = 2102, // 投影失败 + ERR_DISTANCE_COMPUTE_FAILED = 2103, // 距离计算失败 + ERR_CONVEX_HULL_FAILED = 2104, // 凸包计算失败 + ERR_VORONOI_FAILED = 2105, // Voronoi计算失败 + ERR_TRIANGULATION_FAILED = 2106, // 三角剖分失败 + ERR_SIMPLIFY_FAILED = 2107, // 简化失败 + + // CAM + ERR_CAM_TOOLPATH_GENERATION = 2201, // 刀路生成失败 + ERR_CAM_COLLISION_DETECTED = 2202, // 刀具碰撞检测到 + ERR_CAM_INVALID_STRATEGY = 2203, // 无效的加工策略 + ERR_CAM_5AXIS_LIMIT = 2204, // 五轴极限超限 + ERR_CAM_OPTIMIZATION_FAILED = 2205, // CAM优化失败 + + // 性能/并发 + ERR_THREAD_CREATE_FAILED = 2301, // 线程创建失败 + ERR_LOCK_TIMEOUT = 2302, // 锁超时 + ERR_TASK_CANCELLED = 2303, // 任务已取消 + ERR_TRANSACTION_CONFLICT = 2304, // 事务冲突 + ERR_TRANSACTION_ROLLBACK = 2305, // 事务回滚 + + // ── 3xxx:curves 曲线曲面错误 ─────────────────────── + ERR_CURVE_DEGREE_INVALID = 3001, // 曲线阶数无效 + ERR_CURVE_KNOT_INVALID = 3002, // 节点向量无效 + ERR_CURVE_CP_COUNT_MISMATCH = 3003, // 控制点数量不匹配 + ERR_CURVE_EVALUATION_FAILED = 3004, // 曲线求值失败 + ERR_CURVE_DERIVATIVE_FAILED = 3005, // 曲线求导失败 + ERR_CURVE_PARAM_OUT_OF_RANGE= 3006, // 参数超出范围 + ERR_CURVE_SELF_INTERSECT = 3007, // 曲线自交 + ERR_CURVE_DISCONTINUITY = 3008, // 曲线不连续 + + // 曲面 + ERR_SURFACE_INVALID = 3101, // 曲面无效 + ERR_SURFACE_PARAM_RANGE = 3102, // 曲面参数范围无效 + ERR_SURFACE_DEGENERATE = 3103, // 退化曲面 + ERR_SURFACE_SINGULARITY = 3104, // 曲面奇点 + ERR_SURFACE_INTERSECTION = 3105, // 曲面求交失败 + ERR_SURFACE_CONTINUITY = 3106, // 曲面连续性不满足 + ERR_SURFACE_TRIMMING = 3107, // 曲面裁剪失败 + ERR_SURFACE_EXTENSION = 3108, // 曲面延伸失败 + ERR_SURFACE_OFFSET = 3109, // 曲面偏置失败 + ERR_SURFACE_FAIRING = 3110, // 曲面光顺失败 + + // IGA + ERR_IGA_MESH_INVALID = 3201, // IGA网格无效 + ERR_IGA_REFINEMENT_FAILED = 3202, // IGA细化失败 + + // ── 4xxx:mesh 网格错误 ───────────────────────────── + ERR_MESH_EMPTY = 4001, // 网格为空 + ERR_MESH_NON_MANIFOLD = 4002, // 非流形网格 + ERR_MESH_DEGENERATE_FACE = 4003, // 退化面 + ERR_MESH_SELF_INTERSECTION = 4004, // 网格自交 + ERR_MESH_INVERTED_NORMAL = 4005, // 法线翻转 + ERR_MESH_HOLE_DETECTED = 4006, // 检测到孔洞 + ERR_MESH_DISCONNECTED = 4007, // 网格不连通 + + // 网格操作 + ERR_MESH_BOOLEAN_FAILED = 4101, // 网格布尔失败 + ERR_MESH_SIMPLIFY_FAILED = 4102, // 网格简化失败 + ERR_MESH_SMOOTH_FAILED = 4103, // 网格平滑失败 + ERR_MESH_REPAIR_FAILED = 4104, // 网格修复失败 + ERR_MESH_PARAMETERIZE_FAILED= 4105, // 网格参数化失败 + ERR_MESH_REMESH_FAILED = 4106, // 网格重划分失败 + ERR_MESH_SPLIT_FAILED = 4107, // 网格分割失败 + + // 高级网格 + ERR_FEA_MESH_QUALITY = 4201, // FEA网格质量不足 + ERR_FEA_MESH_GENERATION = 4202, // FEA网格生成失败 + ERR_MARCHING_CUBES_FAILED = 4203, // Marching Cubes失败 + ERR_POINT_CLOUD_EMPTY = 4204, // 点云为空 + ERR_POINT_CLOUD_NOISE = 4205, // 点云噪声过大 + + // ── 5xxx:brep B-Rep错误 ─────────────────────────── + ERR_BREP_TOPOLOGY_INVALID = 5001, // B-Rep拓扑无效 + ERR_BREP_GEOMETRY_MISMATCH = 5002, // B-Rep几何不匹配 + ERR_BREP_SHELL_NOT_CLOSED = 5003, // 壳不封闭 + ERR_BREP_ORIENTATION = 5004, // 方向不一致 + ERR_BREP_SEWING_FAILED = 5005, // 缝合失败 + ERR_BREP_HEALING_FAILED = 5006, // 修复失败 + ERR_BREP_VALIDATION_FAILED = 5007, // 验证失败 + + // 布尔操作 + ERR_BOOLEAN_OPERATION_FAILED= 5101, // 布尔操作失败 + ERR_BOOLEAN_INTERSECT_FAILED= 5102, // 布尔求交失败 + ERR_BOOLEAN_SUBTRACT_FAILED = 5103, // 布尔求差失败 + ERR_BOOLEAN_UNION_FAILED = 5104, // 布尔求并失败 + ERR_BOOLEAN_DEGENERATE = 5105, // 布尔操作产生退化 + ERR_BOOLEAN_FALLBACK_USED = 5106, // 布尔操作回退到备用方法 + + // 特征建模 + ERR_FEATURE_CREATION_FAILED = 5201, // 特征创建失败 + ERR_FEATURE_TREE_INVALID = 5202, // 特征树无效 + ERR_FEATURE_UPDATE_FAILED = 5203, // 特征更新失败 + ERR_FEATURE_RECOGNITION = 5204, // 特征识别失败 + + // 装配 + ERR_ASSEMBLY_CONSTRAINT = 5301, // 装配约束冲突 + ERR_ASSEMBLY_INTERFERENCE = 5302, // 装配干涉 + ERR_ASSEMBLY_CYCLE = 5303, // 装配循环引用 + ERR_ASSEMBLY_MATE_FAILED = 5304, // 配合失败 + ERR_ASSEMBLY_LOD_INVALID = 5305, // LOD级别无效 + + // 钣金 + ERR_SHEET_METAL_THICKNESS = 5401, // 钣金厚度无效 + ERR_SHEET_METAL_BEND_RADIUS = 5402, // 弯曲半径无效 + ERR_SHEET_METAL_UNFOLD = 5403, // 展开失败 + + // PMI/GD&T + ERR_PMI_INVALID = 5501, // PMI无效 + ERR_GDT_TOLERANCE = 5502, // GD&T公差错误 + ERR_AUTO_DIM_FAILED = 5503, // 自动标注失败 + ERR_DRAWING_STANDARD = 5504, // 制图标准错误 + + // 柔性体 + ERR_FFD_INVALID = 5601, // FFD变形无效 + + // ── 6xxx:sdf SDF错误 ────────────────────────────── + ERR_SDF_EVALUATE_FAILED = 6001, // SDF求值失败 + ERR_SDF_TREE_INVALID = 6002, // SDF树无效 + ERR_SDF_TO_MESH_FAILED = 6003, // SDF转网格失败 + ERR_SDF_OPTIMIZE_FAILED = 6004, // SDF优化失败 + ERR_SDF_TORCH_ERROR = 6005, // SDF Torch推理错误 + ERR_SDF_PRIMITIVE_INVALID = 6006, // SDF基元无效 + + // ── 7xxx:capi C API错误 ─────────────────────────── + ERR_CAPI_NULL_HANDLE = 7001, // 空句柄 + ERR_CAPI_INVALID_ARGUMENT = 7002, // 无效参数 + ERR_CAPI_TYPE_MISMATCH = 7003, // 类型不匹配 + ERR_CAPI_NOT_INITIALIZED = 7004, // 引擎未初始化 + ERR_CAPI_ALREADY_INITIALIZED= 7005, // 引擎已初始化 + ERR_CAPI_BUFFER_TOO_SMALL = 7006, // 输出缓冲区太小 + ERR_CAPI_FEATURE_NOT_AVAIL = 7007, // 功能不可用 + ERR_CAPI_UNKNOWN_ERROR = 7999, // 未知C API错误 }; -} // namespace vde::foundation +// ═══════════════════════════════════════════════════════════════ +// ErrorInfo 结构体 +// ═══════════════════════════════════════════════════════════════ + +/** + * @brief 错误信息结构体 + * + * 包含错误码、中文描述、所属模块和发生的源位置。 + */ +struct ErrorInfo { + ErrorCode code; ///< 错误码 + std::string message; ///< 中文错误描述 + std::string module; ///< 所属模块名 + std::string file; ///< 源文件名 + int line; ///< 源文件行号 + + /// 默认构造(SUCCESS) + ErrorInfo() + : code(ErrorCode::SUCCESS), message("成功"), module(""), file(""), line(0) {} + + /// 完整构造 + ErrorInfo(ErrorCode c, std::string msg, std::string mod, + std::string f = "", int l = 0) + : code(c), message(std::move(msg)), module(std::move(mod)), + file(std::move(f)), line(l) {} +}; + +// ═══════════════════════════════════════════════════════════════ +// API 函数 +// ═══════════════════════════════════════════════════════════════ + +/** + * @brief 获取错误码对应的中文描述 + * @param code 错误码 + * @return 中文错误描述字符串 + */ +std::string error_message(ErrorCode code); + +/** + * @brief 判断错误码是否表示错误(非成功) + * @param code 错误码 + * @return true 表示有错误 + */ +constexpr bool is_error(ErrorCode code) noexcept { + return code != ErrorCode::SUCCESS; +} + +/** + * @brief 判断错误码是否表示成功 + * @param code 错误码 + * @return true 表示成功 + */ +constexpr bool is_success(ErrorCode code) noexcept { + return code == ErrorCode::SUCCESS; +} + +/** + * @brief 将错误码转换为所属模块名 + * @param code 错误码 + * @return 模块名称字符串 + */ +std::string error_module(ErrorCode code); + +} // namespace vde::foundation diff --git a/include/vde/vde_version.h b/include/vde/vde_version.h new file mode 100644 index 0000000..74a6cc4 --- /dev/null +++ b/include/vde/vde_version.h @@ -0,0 +1,32 @@ +#pragma once +/** + * @file vde_version.h + * @brief ViewDesignEngine 版本和许可证顶层便捷头文件 + * + * 上层应用/用户只需 #include 即可获得: + * - ViewDesignEngine 版本信息(版本号、构建信息) + * - 许可证管理(LicenseManager 单例) + * - 功能查询接口(has_feature) + * + * 使用示例: + * @code + * #include + * + * // 打印版本 + * std::cout << vde::core::vde_version_string() << std::endl; + * + * // 设置许可证 + * auto& lm = vde::core::LicenseManager::instance(); + * lm.set_license("VDE-XXXX-XXXX-XXXX"); + * + * // 检查功能 + * if (lm.has_feature("CAM_5AXIS")) { + * // 使用5轴CAM功能 + * } + * @endcode + * + * @ingroup core + */ + +#include "vde/core/version.h" +#include "vde/core/license_manager.h" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2d13683..858753c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -12,6 +12,7 @@ add_library(vde_foundation STATIC foundation/io_3mf.cpp foundation/industrial_formats.cpp foundation/vde_format.cpp + foundation/error_codes.cpp ) target_include_directories(vde_foundation PUBLIC ${CMAKE_SOURCE_DIR}/include @@ -38,9 +39,11 @@ add_library(vde_core STATIC core/concurrent_data.cpp core/transaction.cpp core/topology_compression.cpp + core/license_manager.cpp ) target_include_directories(vde_core PUBLIC ${CMAKE_SOURCE_DIR}/include + PUBLIC ${CMAKE_BINARY_DIR}/generated PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ) target_link_libraries(vde_core diff --git a/src/brep/direct_modeling.cpp b/src/brep/direct_modeling.cpp index 518edb5..7c23fae 100644 --- a/src/brep/direct_modeling.cpp +++ b/src/brep/direct_modeling.cpp @@ -1,6 +1,7 @@ #include "vde/brep/direct_modeling.h" #include "vde/brep/brep_validate.h" #include "vde/brep/trimmed_surface.h" +#include "vde/curves/surface_continuity.h" #include #include #include @@ -1147,4 +1148,161 @@ DirectModelingResult pull_up_to_face(const BrepModel& body, return result; } +// ═══════════════════════════════════════════════════════════ +// tangent_pull — 相切联动拉伸 +// ═══════════════════════════════════════════════════════════ + +/// 检测两面是否 G1 相切连续 +/// 优先通过法向量平行性快速判定(适用于平面), +/// 非平面或判定不明确时回退到 surface_continuity +static bool is_g1_tangent(const BrepModel& body, int fa, int fb) { + Vector3D na = compute_face_normal(body, fa); + Vector3D nb = compute_face_normal(body, fb); + + // 法向量平行(同向)→ G1 tangent + double dot_abs = std::abs(na.dot(nb)); + if (dot_abs > 0.9999) return true; + + // 法向量平行(反向,reversed face)→ G1 tangent + // 已由 dot_abs 覆盖 + + // 通过曲面连续性检测(曲线/曲面需要) + const auto& face_a = body.face(fa); + const auto& face_b = body.face(fb); + + if (face_a.surface_id >= 0 && face_b.surface_id >= 0) { + const auto& surf_a = body.surface(face_a.surface_id); + const auto& surf_b = body.surface(face_b.surface_id); + auto cont = curves::surface_continuity(surf_a, surf_b, 20, 1e-6); + if (cont >= curves::ContinuityLevel::G1) return true; + } + + return false; +} + +/// 收集非共享顶点:face_id 的所有顶点中,排除与 ref_face 共享的顶点 +static std::set collect_non_shared_vertices(const BrepModel& body, + int face_id, int ref_face_id) { + auto face_verts = collect_face_vertices(body, face_id); + auto ref_verts = collect_face_vertices(body, ref_face_id); + + std::set result; + for (int vi : face_verts) { + if (ref_verts.find(vi) == ref_verts.end()) { + result.insert(vi); + } + } + return result; +} + +DirectModelingResult tangent_pull(const BrepModel& body, + int face_id, double distance) { + DirectModelingResult result; + result.operation = "tangent_pull"; + + // ── 1. 参数验证 ── + if (face_id < 0 || face_id >= static_cast(body.num_faces())) { + result.errors.push_back("tangent_pull: face_id " + std::to_string(face_id) + + " out of range [0, " + std::to_string(body.num_faces()) + ")"); + return result; + } + + if (std::abs(distance) < 1e-15) { + result.success = true; + result.body = body; + result.warnings.push_back("tangent_pull: distance is zero, no change made"); + return result; + } + + // ── 2. 检测相邻面 ── + std::vector adjacent_faces; + for (size_t fi = 0; fi < body.num_faces(); ++fi) { + int cur_fi = static_cast(fi); + if (cur_fi != face_id && faces_share_edge(body, face_id, cur_fi)) { + adjacent_faces.push_back(cur_fi); + } + } + + if (adjacent_faces.empty()) { + result.errors.push_back("tangent_pull: face " + std::to_string(face_id) + + " has no adjacent faces (isolated)"); + return result; + } + + // ── 3. 检测每个相邻面是否 G1 连续 ── + std::set g1_adjacent; + for (int af : adjacent_faces) { + if (is_g1_tangent(body, face_id, af)) { + g1_adjacent.insert(af); + } + } + + // ── 4. 计算拉伸面法向 ── + Vector3D normal = compute_face_normal(body, face_id); + + // ── 5. 构建顶点偏移 ── + // 拉伸面顶点 → 偏移 distance + // G1 相邻面非共享顶点 → 也偏移 distance(保持相切) + + // 拉伸面顶点偏移 + auto face_verts = collect_face_vertices(body, face_id); + std::map offsets; + for (int vi : face_verts) { + offsets[vi] = body.vertex(vi).point + normal * distance; + } + + // G1 相邻面非共享顶点偏移 + for (int af : g1_adjacent) { + auto non_shared = collect_non_shared_vertices(body, af, face_id); + for (int vi : non_shared) { + // 避免重复添加(一个顶点可能属于多个 G1 相邻面) + if (offsets.find(vi) == offsets.end()) { + offsets[vi] = body.vertex(vi).point + normal * distance; + } + } + } + + int g1_count = static_cast(g1_adjacent.size()); + int total_adjacent = static_cast(adjacent_faces.size()); + + // ── 6. 重建模型(创建侧面 = push_pull 行为) ── + bool create_sides = (std::abs(distance) > 1e-15); + result.body = rebuild_with_offset(body, face_id, offsets, create_sides); + + // ── 7. 统计侧面数 ── + if (create_sides) { + const auto& src_face = body.face(face_id); + for (int li : src_face.loops) { + const auto& loop = body.loop_by_id(li); + if (loop.is_outer) { + result.new_faces = static_cast(loop.edges.size()); + break; + } + } + } + + // ── 8. 验证 ── + auto val_result = validate(result.body); + result.success = val_result.valid; + result.affected_faces = 1 + g1_count; + + if (!val_result.valid) { + for (const auto& e : val_result.errors) + result.warnings.push_back("Validation: " + e); + } + + // 诊断信息 + if (g1_count > 0) { + result.warnings.push_back("tangent_pull: " + std::to_string(g1_count) + + " / " + std::to_string(total_adjacent) + + " adjacent faces adjusted for G1 tangency"); + } else { + result.warnings.push_back("tangent_pull: no G1-adjacent faces found " + + std::string("(all ") + std::to_string(total_adjacent) + + " adjacent faces are non-tangent)"); + } + + return result; +} + } // namespace vde::brep diff --git a/src/core/license_manager.cpp b/src/core/license_manager.cpp new file mode 100644 index 0000000..b45a005 --- /dev/null +++ b/src/core/license_manager.cpp @@ -0,0 +1,617 @@ +/** + * @file license_manager.cpp + * @brief 许可证管理实现 + * + * License Key 格式:VDE-XXXX-XXXX-XXXX + * + * 二进制布局(17字节 payload): + * - byte 0: version (1) + * - byte 1: tier (0=Community, 1=Professional, 2=Enterprise) + * - byte 2-9: expiry (uint64_t, Unix timestamp, big-endian) + * - byte 10-16: reserved (0) + * + * 签名:HMAC-SHA256(使用固定种子密钥)前 4 字节附加到 payload。 + * 完整编码:payload(17B) + signature(4B) → Base32 → VDE-XXXX-XXXX-XXXX + */ + +#include "vde/core/license_manager.h" +#include +#include +#include +#include +#include + +namespace vde::core { + +// ═══════════════════════════════════════════════════════════════ +// 内部常量 +// ═══════════════════════════════════════════════════════════════ + +static constexpr int PAYLOAD_SIZE = 17; // 载荷字节数 +static constexpr int SIGNATURE_SIZE = 4; // 签名截断字节数 +static constexpr int ENCODED_SIZE = 21; // payload + signature +static constexpr char KEY_PREFIX[] = "VDE-"; +static constexpr size_t KEY_PREFIX_LEN = 4; + +// Base32 字母表(RFC 4648,大写) +static const char BASE32_ALPHABET[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + +// HMAC 种子密钥(固定,用于简单验证;生产环境应从安全存储加载) +static const uint8_t HMAC_KEY[] = { + 0x56, 0x44, 0x45, 0x4E, 0x47, 0x49, 0x4E, 0x45, // "VDENGINE" + 0x4C, 0x49, 0x43, 0x45, 0x4E, 0x53, 0x45, 0x00, // "LICENSE\0" + 0x53, 0x45, 0x43, 0x52, 0x45, 0x54, 0x4B, 0x45, // "SECRETKE" + 0x59, 0x32, 0x30, 0x32, 0x35, 0x21, 0x21, 0x21, // "Y2025!!!" +}; +static constexpr size_t HMAC_KEY_SIZE = sizeof(HMAC_KEY); + +// ═══════════════════════════════════════════════════════════════ +// parse_license_tier / license_tier_name (已在头文件 inline) +// ═══════════════════════════════════════════════════════════════ + +int parse_license_tier(const std::string& name) { + if (name == "Community" || name == "community") return 0; + if (name == "Professional" || name == "professional") return 1; + if (name == "Enterprise" || name == "enterprise") return 2; + return -1; +} + +// ═══════════════════════════════════════════════════════════════ +// Feature 辅助函数 +// ═══════════════════════════════════════════════════════════════ + +const char* feature_name(Feature f) { + switch (f) { + case Feature::BASIC_BREP: return "BASIC_BREP"; + case Feature::STEP_IMPORT_EXPORT: return "STEP_IMPORT_EXPORT"; + case Feature::IGES_IMPORT_EXPORT: return "IGES_IMPORT_EXPORT"; + case Feature::BASE_IO_FORMATS: return "BASE_IO_FORMATS"; + case Feature::MESH_OPERATIONS: return "MESH_OPERATIONS"; + case Feature::SKETCH_SOLVER: return "SKETCH_SOLVER"; + case Feature::CAM_5AXIS: return "CAM_5AXIS"; + case Feature::ADVANCED_SURFACING: return "ADVANCED_SURFACING"; + case Feature::GD_T: return "GD_T"; + case Feature::PMI_MBD: return "PMI_MBD"; + case Feature::ASSEMBLY_DESIGN: return "ASSEMBLY_DESIGN"; + case Feature::SHEET_METAL: return "SHEET_METAL"; + case Feature::ADVANCED_HEALING: return "ADVANCED_HEALING"; + case Feature::FEA_MESH: return "FEA_MESH"; + case Feature::IGA_ANALYSIS: return "IGA_ANALYSIS"; + case Feature::COLLISION_DETECTION: return "COLLISION_DETECTION"; + case Feature::DIRECT_MODELING: return "DIRECT_MODELING"; + case Feature::SURFACE_FAIRING: return "SURFACE_FAIRING"; + case Feature::TECHNICAL_SUPPORT: return "TECHNICAL_SUPPORT"; + case Feature::CUSTOM_FORMATS: return "CUSTOM_FORMATS"; + case Feature::SCRIPTING_API: return "SCRIPTING_API"; + case Feature::GPU_ACCELERATION: return "GPU_ACCELERATION"; + case Feature::CLOUD_COLLABORATION: return "CLOUD_COLLABORATION"; + case Feature::SDF_MODELING: return "SDF_MODELING"; + case Feature::LICENSE_MANAGER_SDK: return "LICENSE_MANAGER_SDK"; + } + return "UNKNOWN"; +} + +int parse_feature(const std::string& name) { + static const std::unordered_map map = { + {"BASIC_BREP", 0}, {"STEP_IMPORT_EXPORT", 1}, + {"IGES_IMPORT_EXPORT", 2}, {"BASE_IO_FORMATS", 3}, + {"MESH_OPERATIONS", 4}, {"SKETCH_SOLVER", 5}, + {"CAM_5AXIS", 10}, {"ADVANCED_SURFACING", 11}, + {"GD_T", 12}, {"PMI_MBD", 13}, + {"ASSEMBLY_DESIGN", 14}, {"SHEET_METAL", 15}, + {"ADVANCED_HEALING", 16}, {"FEA_MESH", 17}, + {"IGA_ANALYSIS", 18}, {"COLLISION_DETECTION",19}, + {"DIRECT_MODELING", 20}, {"SURFACE_FAIRING", 21}, + {"TECHNICAL_SUPPORT", 30}, {"CUSTOM_FORMATS", 31}, + {"SCRIPTING_API", 32}, {"GPU_ACCELERATION", 33}, + {"CLOUD_COLLABORATION",34}, {"SDF_MODELING", 35}, + {"LICENSE_MANAGER_SDK",36}, + }; + auto it = map.find(name); + return (it != map.end()) ? it->second : -1; +} + +std::set features_for_tier(LicenseTier tier) { + std::set feats; + + // Community + feats.insert(Feature::BASIC_BREP); + feats.insert(Feature::STEP_IMPORT_EXPORT); + feats.insert(Feature::IGES_IMPORT_EXPORT); + feats.insert(Feature::BASE_IO_FORMATS); + feats.insert(Feature::MESH_OPERATIONS); + feats.insert(Feature::SKETCH_SOLVER); + + if (tier == LicenseTier::Community) return feats; + + // Professional + feats.insert(Feature::CAM_5AXIS); + feats.insert(Feature::ADVANCED_SURFACING); + feats.insert(Feature::GD_T); + feats.insert(Feature::PMI_MBD); + feats.insert(Feature::ASSEMBLY_DESIGN); + feats.insert(Feature::SHEET_METAL); + feats.insert(Feature::ADVANCED_HEALING); + feats.insert(Feature::FEA_MESH); + feats.insert(Feature::IGA_ANALYSIS); + feats.insert(Feature::COLLISION_DETECTION); + feats.insert(Feature::DIRECT_MODELING); + feats.insert(Feature::SURFACE_FAIRING); + + if (tier == LicenseTier::Professional) return feats; + + // Enterprise + feats.insert(Feature::TECHNICAL_SUPPORT); + feats.insert(Feature::CUSTOM_FORMATS); + feats.insert(Feature::SCRIPTING_API); + feats.insert(Feature::GPU_ACCELERATION); + feats.insert(Feature::CLOUD_COLLABORATION); + feats.insert(Feature::SDF_MODELING); + feats.insert(Feature::LICENSE_MANAGER_SDK); + + return feats; +} + +bool feature_available_in_tier(Feature f, LicenseTier tier) { + auto feats = features_for_tier(tier); + return feats.find(f) != feats.end(); +} + +// ═══════════════════════════════════════════════════════════════ +// LicenseInfo 成员 +// ═══════════════════════════════════════════════════════════════ + +bool LicenseInfo::is_expired() const { + if (expiry == 0) return false; // 永久 + return std::time(nullptr) > expiry; +} + +int LicenseInfo::days_until_expiry() const { + if (expiry == 0) return -1; // 永久 + auto now = std::time(nullptr); + if (now >= expiry) return 0; + double diff = std::difftime(expiry, now); + return static_cast(diff / 86400.0); // 秒转天 +} + +// ═══════════════════════════════════════════════════════════════ +// Base32 RFC 4648 编解码 +// ═══════════════════════════════════════════════════════════════ + +static int base32_char_to_value(char c) { + if (c >= 'A' && c <= 'Z') return c - 'A'; + if (c >= 'a' && c <= 'z') return c - 'a'; + if (c >= '2' && c <= '7') return c - '2' + 26; + return -1; +} + +std::string LicenseKey::base32_encode(const std::vector& data) { + std::string result; + result.reserve((data.size() * 8 + 4) / 5); + + size_t bit_buf = 0; + int bit_count = 0; + + for (uint8_t byte : data) { + bit_buf = (bit_buf << 8) | byte; + bit_count += 8; + + while (bit_count >= 5) { + bit_count -= 5; + result.push_back(BASE32_ALPHABET[(bit_buf >> bit_count) & 0x1F]); + } + } + + // 处理剩余位 + if (bit_count > 0) { + result.push_back(BASE32_ALPHABET[(bit_buf << (5 - bit_count)) & 0x1F]); + } + + return result; +} + +std::vector LicenseKey::base32_decode(const std::string& encoded) { + std::vector result; + result.reserve(encoded.size() * 5 / 8); + + size_t bit_buf = 0; + int bit_count = 0; + + for (char c : encoded) { + int val = base32_char_to_value(c); + if (val < 0) continue; // 忽略无效字符(如连字符) + + bit_buf = (bit_buf << 5) | static_cast(val); + bit_count += 5; + + while (bit_count >= 8) { + bit_count -= 8; + result.push_back(static_cast((bit_buf >> bit_count) & 0xFF)); + } + } + + return result; +} + +// ═══════════════════════════════════════════════════════════════ +// HMAC-SHA256(简化版,使用固定密钥) +// ═══════════════════════════════════════════════════════════════ + +// 简化版 SHA256 — 完整实现但内联在此文件中 +namespace { + +struct Sha256Ctx { + uint32_t state[8]; + uint64_t bit_count; + uint8_t buffer[64]; + size_t buffer_len; +}; + +static const uint32_t SHA256_K[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, + 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, + 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, + 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, + 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, + 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +inline uint32_t rotr32(uint32_t x, uint32_t n) { + return (x >> n) | (x << (32 - n)); +} + +static void sha256_transform(Sha256Ctx* ctx, const uint8_t data[64]) { + uint32_t w[64]; + for (int i = 0; i < 16; ++i) { + w[i] = (static_cast(data[i * 4]) << 24) | + (static_cast(data[i * 4 + 1]) << 16) | + (static_cast(data[i * 4 + 2]) << 8) | + (static_cast(data[i * 4 + 3])); + } + for (int i = 16; i < 64; ++i) { + uint32_t s0 = rotr32(w[i - 15], 7) ^ rotr32(w[i - 15], 18) ^ (w[i - 15] >> 3); + uint32_t s1 = rotr32(w[i - 2], 17) ^ rotr32(w[i - 2], 19) ^ (w[i - 2] >> 10); + w[i] = w[i - 16] + s0 + w[i - 7] + s1; + } + + uint32_t a = ctx->state[0], b = ctx->state[1], c = ctx->state[2], d = ctx->state[3]; + uint32_t e = ctx->state[4], f = ctx->state[5], g = ctx->state[6], h = ctx->state[7]; + + for (int i = 0; i < 64; ++i) { + uint32_t S1 = rotr32(e, 6) ^ rotr32(e, 11) ^ rotr32(e, 25); + uint32_t ch = (e & f) ^ (~e & g); + uint32_t temp1 = h + S1 + ch + SHA256_K[i] + w[i]; + uint32_t S0 = rotr32(a, 2) ^ rotr32(a, 13) ^ rotr32(a, 22); + uint32_t maj = (a & b) ^ (a & c) ^ (b & c); + uint32_t temp2 = S0 + maj; + + h = g; g = f; f = e; e = d + temp1; + d = c; c = b; b = a; a = temp1 + temp2; + } + + ctx->state[0] += a; ctx->state[1] += b; ctx->state[2] += c; ctx->state[3] += d; + ctx->state[4] += e; ctx->state[5] += f; ctx->state[6] += g; ctx->state[7] += h; +} + +static void sha256_init(Sha256Ctx* ctx) { + ctx->state[0] = 0x6a09e667; ctx->state[1] = 0xbb67ae85; + ctx->state[2] = 0x3c6ef372; ctx->state[3] = 0xa54ff53a; + ctx->state[4] = 0x510e527f; ctx->state[5] = 0x9b05688c; + ctx->state[6] = 0x1f83d9ab; ctx->state[7] = 0x5be0cd19; + ctx->bit_count = 0; + ctx->buffer_len = 0; +} + +static void sha256_update(Sha256Ctx* ctx, const uint8_t* data, size_t len) { + for (size_t i = 0; i < len; ++i) { + ctx->buffer[ctx->buffer_len++] = data[i]; + if (ctx->buffer_len == 64) { + sha256_transform(ctx, ctx->buffer); + ctx->bit_count += 512; + ctx->buffer_len = 0; + } + } +} + +static void sha256_final(Sha256Ctx* ctx, uint8_t hash[32]) { + uint64_t total_bits = ctx->bit_count + ctx->buffer_len * 8; + + // 填充:0x80 后跟 0x00,然后是 64-bit 长度 + ctx->buffer[ctx->buffer_len++] = 0x80; + if (ctx->buffer_len > 56) { + while (ctx->buffer_len < 64) ctx->buffer[ctx->buffer_len++] = 0; + sha256_transform(ctx, ctx->buffer); + ctx->buffer_len = 0; + } + while (ctx->buffer_len < 56) ctx->buffer[ctx->buffer_len++] = 0; + + // 大端序写入长度 + for (int i = 7; i >= 0; --i) { + ctx->buffer[56 + i] = static_cast(total_bits & 0xFF); + total_bits >>= 8; + } + sha256_transform(ctx, ctx->buffer); + + // 输出 + for (int i = 0; i < 8; ++i) { + hash[i * 4] = static_cast((ctx->state[i] >> 24) & 0xFF); + hash[i * 4 + 1] = static_cast((ctx->state[i] >> 16) & 0xFF); + hash[i * 4 + 2] = static_cast((ctx->state[i] >> 8) & 0xFF); + hash[i * 4 + 3] = static_cast(ctx->state[i] & 0xFF); + } +} + +} // anonymous namespace + +std::vector LicenseKey::compute_hmac(const std::vector& data) { + constexpr size_t BLOCK_SIZE = 64; // SHA256 block size + + // HMAC: H((K ^ opad) || H((K ^ ipad) || message)) + uint8_t key_block[BLOCK_SIZE] = {}; + + // 如果 key 超过 block size,先哈希 + if (HMAC_KEY_SIZE > BLOCK_SIZE) { + Sha256Ctx key_ctx; + sha256_init(&key_ctx); + sha256_update(&key_ctx, HMAC_KEY, HMAC_KEY_SIZE); + uint8_t key_hash[32]; + sha256_final(&key_ctx, key_hash); + std::memcpy(key_block, key_hash, 32); + } else { + std::memcpy(key_block, HMAC_KEY, HMAC_KEY_SIZE); + } + + // ipad + uint8_t ipad[BLOCK_SIZE]; + for (size_t i = 0; i < BLOCK_SIZE; ++i) ipad[i] = key_block[i] ^ 0x36; + + // opad + uint8_t opad[BLOCK_SIZE]; + for (size_t i = 0; i < BLOCK_SIZE; ++i) opad[i] = key_block[i] ^ 0x5C; + + // 内层哈希:H(ipad || data) + Sha256Ctx inner; + sha256_init(&inner); + sha256_update(&inner, ipad, BLOCK_SIZE); + sha256_update(&inner, data.data(), data.size()); + uint8_t inner_hash[32]; + sha256_final(&inner, inner_hash); + + // 外层哈希:H(opad || inner_hash) + Sha256Ctx outer; + sha256_init(&outer); + sha256_update(&outer, opad, BLOCK_SIZE); + sha256_update(&outer, inner_hash, 32); + uint8_t outer_hash[32]; + sha256_final(&outer, outer_hash); + + // 返回完整 32 字节 + return std::vector(outer_hash, outer_hash + 32); +} + +// ═══════════════════════════════════════════════════════════════ +// LicenseKey 实现 +// ═══════════════════════════════════════════════════════════════ + +/** + * @brief 构造 payload 字节序列 + * + * 布局(17字节): + * [0]: version + * [1]: tier + * [2-9]: expiry (uint64_t big-endian) + * [10-16]: reserved (0) + */ +static std::vector build_payload(int version, LicenseTier tier, + std::time_t expiry) { + std::vector payload(PAYLOAD_SIZE, 0); + payload[0] = static_cast(version); + payload[1] = static_cast(static_cast(tier)); + + // expiry 大端序写入 byte 2-9 + uint64_t exp_val = static_cast(expiry); + for (int i = 7; i >= 0; --i) { + payload[2 + i] = static_cast(exp_val & 0xFF); + exp_val >>= 8; + } + + return payload; +} + +/** + * @brief 从 payload 解析 LicenseInfo + */ +static LicenseInfo parse_payload(const std::vector& data) { + LicenseInfo info; + if (data.size() < PAYLOAD_SIZE) return info; + + info.version = data[0]; + info.tier = static_cast(std::min(static_cast(data[1]), 2)); + + uint64_t exp_val = 0; + for (int i = 0; i < 8; ++i) { + exp_val = (exp_val << 8) | data[2 + i]; + } + info.expiry = static_cast(exp_val); + info.licensee = "Licensee"; // 未嵌入在 payload 中 + info.features = features_for_tier(info.tier); + + return info; +} + +bool LicenseKey::parse(const std::string& key_str) { + key_string_ = key_str; + sig_valid_ = false; + + // 检查前缀 + if (key_str.size() < KEY_PREFIX_LEN + 3 || + key_str.substr(0, KEY_PREFIX_LEN) != KEY_PREFIX) { + return false; + } + + // 去掉 "VDE-" 前缀 + std::string encoded = key_str.substr(KEY_PREFIX_LEN); + + // 移除连字符 + encoded.erase(std::remove(encoded.begin(), encoded.end(), '-'), encoded.end()); + + // Base32 解码 + auto raw = base32_decode(encoded); + if (raw.size() < static_cast(PAYLOAD_SIZE + SIGNATURE_SIZE)) { + return false; + } + + // 分离 payload 和签名 + std::vector payload(raw.begin(), raw.begin() + PAYLOAD_SIZE); + std::vector sig(raw.begin() + PAYLOAD_SIZE, + raw.begin() + PAYLOAD_SIZE + SIGNATURE_SIZE); + + // 验证签名 + auto expected_hmac = compute_hmac(payload); + sig_valid_ = (sig.size() == SIGNATURE_SIZE && + std::equal(sig.begin(), sig.end(), expected_hmac.begin())); + + // 解析 payload + info_ = parse_payload(payload); + info_.key_string = key_str; + + return true; +} + +std::string LicenseKey::generate(LicenseTier tier, std::time_t expiry, + const std::string& /*licensee*/) { + auto payload = build_payload(1, tier, expiry); + + // 计算 HMAC 签名 + auto hmac = compute_hmac(payload); + + // 合并 payload + signature[0:4] + std::vector combined(payload); + combined.insert(combined.end(), hmac.begin(), hmac.begin() + SIGNATURE_SIZE); + + // Base32 编码 + std::string encoded = base32_encode(combined); + + // 格式化为 VDE-XXXX-XXXX-XXXX + std::ostringstream oss; + oss << KEY_PREFIX; + size_t pos = 0; + while (pos < encoded.size()) { + if (pos > 0) oss << '-'; + size_t len = std::min(4, encoded.size() - pos); + oss << encoded.substr(pos, len); + pos += len; + } + + return oss.str(); +} + +bool LicenseKey::validate(const std::string& key_str) { + LicenseKey lk; + if (!lk.parse(key_str)) return false; + return lk.signature_valid() && !lk.info().is_expired(); +} + +// ═══════════════════════════════════════════════════════════════ +// LicenseManager 单例 +// ═══════════════════════════════════════════════════════════════ + +LicenseManager& LicenseManager::instance() { + static LicenseManager mgr; + return mgr; +} + +LicenseManager::LicenseManager() { + // 默认:Community 社区版 + current_.tier = LicenseTier::Community; + current_.licensee = "Community User"; + current_.expiry = 0; // 永久 + current_.features = features_for_tier(LicenseTier::Community); + current_.version = 1; +} + +bool LicenseManager::set_license(const std::string& key) { + std::lock_guard lock(mutex_); + + LicenseKey lk; + if (!lk.parse(key)) return false; + if (!lk.signature_valid()) return false; + + const auto& info = lk.info(); + if (info.is_expired()) return false; + + current_ = info; + trial_mode_ = false; + return true; +} + +void LicenseManager::start_trial(int days) { + std::lock_guard lock(mutex_); + + trial_mode_ = true; + trial_start_ = std::time(nullptr); + trial_days_ = std::max(1, days); + + current_.tier = LicenseTier::Professional; // 试用Professional + current_.licensee = "Trial User"; + current_.expiry = trial_start_ + static_cast(trial_days_) * 86400; + current_.features = features_for_tier(LicenseTier::Professional); + current_.version = 1; +} + +LicenseTier LicenseManager::current_tier() const { + std::lock_guard lock(mutex_); + return current_.tier; +} + +bool LicenseManager::has_feature(const std::string& feature_name) const { + int fid = parse_feature(feature_name); + if (fid < 0) return false; + return has_feature(static_cast(fid)); +} + +bool LicenseManager::has_feature(Feature f) const { + std::lock_guard lock(mutex_); + + // 检查是否过期 + if (is_expired()) return false; + + return current_.features.find(f) != current_.features.end(); +} + +bool LicenseManager::is_expired() const { + std::lock_guard lock(mutex_); + + if (current_.expiry == 0) return false; // 永久 + return std::time(nullptr) > current_.expiry; +} + +int LicenseManager::trial_days_left() const { + std::lock_guard lock(mutex_); + + if (!trial_mode_) return -1; + + auto now = std::time(nullptr); + auto end = trial_start_ + static_cast(trial_days_) * 86400; + if (now >= end) return 0; + + double diff = std::difftime(end, now); + return static_cast(diff / 86400.0); +} + +const LicenseInfo& LicenseManager::current_license() const { + std::lock_guard lock(mutex_); + return current_; +} + +bool LicenseManager::is_trial() const { + std::lock_guard lock(mutex_); + return trial_mode_; +} + +} // namespace vde::core diff --git a/src/foundation/error_codes.cpp b/src/foundation/error_codes.cpp new file mode 100644 index 0000000..cc9c69b --- /dev/null +++ b/src/foundation/error_codes.cpp @@ -0,0 +1,221 @@ +/** + * @file error_codes.cpp + * @brief 统一错误码实现 + */ + +#include "vde/foundation/error_codes.h" +#include + +namespace vde::foundation { + +// ═══════════════════════════════════════════════════════════════ +// error_message — 中文描述 +// ═══════════════════════════════════════════════════════════════ + +std::string error_message(ErrorCode code) { + static const std::unordered_map kMessages = { + // 成功 + {ErrorCode::SUCCESS, "成功"}, + + // 1xxx:foundation 格式IO错误 + {ErrorCode::ERR_FILE_NOT_FOUND, "文件未找到"}, + {ErrorCode::ERR_FILE_OPEN_FAILED, "文件打开失败"}, + {ErrorCode::ERR_FILE_WRITE_FAILED, "文件写入失败"}, + {ErrorCode::ERR_FILE_READ_FAILED, "文件读取失败"}, + {ErrorCode::ERR_FILE_PERMISSION_DENIED, "文件权限不足"}, + {ErrorCode::ERR_FILE_TOO_LARGE, "文件过大"}, + {ErrorCode::ERR_FILE_EMPTY, "文件为空"}, + {ErrorCode::ERR_FILE_ALREADY_EXISTS, "文件已存在"}, + + {ErrorCode::ERR_FORMAT_INVALID, "格式无效"}, + {ErrorCode::ERR_FORMAT_UNSUPPORTED, "格式不支持"}, + {ErrorCode::ERR_FORMAT_VERSION_MISMATCH,"格式版本不匹配"}, + {ErrorCode::ERR_FORMAT_CORRUPTED, "格式数据损坏"}, + {ErrorCode::ERR_FORMAT_TRUNCATED, "格式数据截断"}, + {ErrorCode::ERR_FORMAT_MAGIC_MISMATCH, "格式魔术字不匹配"}, + + {ErrorCode::ERR_VDE_HEADER_INVALID, "VDE文件头无效"}, + {ErrorCode::ERR_VDE_SECTION_MISSING, "VDE段缺失"}, + {ErrorCode::ERR_VDE_SECTION_CORRUPTED, "VDE段损坏"}, + {ErrorCode::ERR_VDE_TOPOLOGY_INVALID, "VDE拓扑数据无效"}, + {ErrorCode::ERR_VDE_GEOMETRY_INVALID, "VDE几何数据无效"}, + {ErrorCode::ERR_VDE_SERIALIZE_FAILED, "VDE序列化失败"}, + {ErrorCode::ERR_VDE_DESERIALIZE_FAILED, "VDE反序列化失败"}, + + {ErrorCode::ERR_STEP_PARSE, "STEP解析错误"}, + {ErrorCode::ERR_STEP_SCHEMA_MISMATCH, "STEP模式不匹配"}, + {ErrorCode::ERR_STEP_ENTITY_UNKNOWN, "STEP未知实体"}, + {ErrorCode::ERR_STEP_TOPOLOGY, "STEP拓扑构建失败"}, + {ErrorCode::ERR_STEP_GEOMETRY, "STEP几何构建失败"}, + {ErrorCode::ERR_IGES_PARSE, "IGES解析错误"}, + {ErrorCode::ERR_IGES_ENTITY_UNSUPPORTED,"IGES不支持的实体"}, + {ErrorCode::ERR_OBJ_PARSE, "OBJ解析错误"}, + {ErrorCode::ERR_STL_PARSE, "STL解析错误"}, + {ErrorCode::ERR_STL_BINARY_INVALID, "STL二进制格式无效"}, + {ErrorCode::ERR_PLY_PARSE, "PLY解析错误"}, + {ErrorCode::ERR_GLTF_PARSE, "glTF解析错误"}, + {ErrorCode::ERR_3MF_PARSE, "3MF解析错误"}, + {ErrorCode::ERR_DXF_PARSE, "DXF解析错误"}, + + // 2xxx:core 几何计算错误 + {ErrorCode::ERR_DEGENERATE_GEOMETRY, "退化几何"}, + {ErrorCode::ERR_NUMERIC_INSTABILITY, "数值不稳定"}, + {ErrorCode::ERR_DIVISION_BY_ZERO, "除零错误"}, + {ErrorCode::ERR_SINGULAR_MATRIX, "奇异矩阵"}, + {ErrorCode::ERR_CONVERGENCE_FAILURE, "收敛失败"}, + {ErrorCode::ERR_OUT_OF_MEMORY, "内存不足"}, + {ErrorCode::ERR_OVERFLOW, "数值溢出"}, + {ErrorCode::ERR_PRECISION_LOSS, "精度丢失"}, + {ErrorCode::ERR_BUFFER_TOO_SMALL, "缓冲区太小"}, + {ErrorCode::ERR_INDEX_OUT_OF_BOUNDS, "索引越界"}, + + {ErrorCode::ERR_INTERSECTION_FAILED, "求交失败"}, + {ErrorCode::ERR_PROJECTION_FAILED, "投影失败"}, + {ErrorCode::ERR_DISTANCE_COMPUTE_FAILED,"距离计算失败"}, + {ErrorCode::ERR_CONVEX_HULL_FAILED, "凸包计算失败"}, + {ErrorCode::ERR_VORONOI_FAILED, "Voronoi计算失败"}, + {ErrorCode::ERR_TRIANGULATION_FAILED, "三角剖分失败"}, + {ErrorCode::ERR_SIMPLIFY_FAILED, "简化失败"}, + + {ErrorCode::ERR_CAM_TOOLPATH_GENERATION,"刀路生成失败"}, + {ErrorCode::ERR_CAM_COLLISION_DETECTED, "刀具碰撞检测到"}, + {ErrorCode::ERR_CAM_INVALID_STRATEGY, "无效的加工策略"}, + {ErrorCode::ERR_CAM_5AXIS_LIMIT, "五轴极限超限"}, + {ErrorCode::ERR_CAM_OPTIMIZATION_FAILED,"CAM优化失败"}, + + {ErrorCode::ERR_THREAD_CREATE_FAILED, "线程创建失败"}, + {ErrorCode::ERR_LOCK_TIMEOUT, "锁超时"}, + {ErrorCode::ERR_TASK_CANCELLED, "任务已取消"}, + {ErrorCode::ERR_TRANSACTION_CONFLICT, "事务冲突"}, + {ErrorCode::ERR_TRANSACTION_ROLLBACK, "事务回滚"}, + + // 3xxx:curves 曲线曲面错误 + {ErrorCode::ERR_CURVE_DEGREE_INVALID, "曲线阶数无效"}, + {ErrorCode::ERR_CURVE_KNOT_INVALID, "节点向量无效"}, + {ErrorCode::ERR_CURVE_CP_COUNT_MISMATCH,"控制点数量不匹配"}, + {ErrorCode::ERR_CURVE_EVALUATION_FAILED,"曲线求值失败"}, + {ErrorCode::ERR_CURVE_DERIVATIVE_FAILED,"曲线求导失败"}, + {ErrorCode::ERR_CURVE_PARAM_OUT_OF_RANGE,"参数超出范围"}, + {ErrorCode::ERR_CURVE_SELF_INTERSECT, "曲线自交"}, + {ErrorCode::ERR_CURVE_DISCONTINUITY, "曲线不连续"}, + + {ErrorCode::ERR_SURFACE_INVALID, "曲面无效"}, + {ErrorCode::ERR_SURFACE_PARAM_RANGE, "曲面参数范围无效"}, + {ErrorCode::ERR_SURFACE_DEGENERATE, "退化曲面"}, + {ErrorCode::ERR_SURFACE_SINGULARITY, "曲面奇点"}, + {ErrorCode::ERR_SURFACE_INTERSECTION, "曲面求交失败"}, + {ErrorCode::ERR_SURFACE_CONTINUITY, "曲面连续性不满足"}, + {ErrorCode::ERR_SURFACE_TRIMMING, "曲面裁剪失败"}, + {ErrorCode::ERR_SURFACE_EXTENSION, "曲面延伸失败"}, + {ErrorCode::ERR_SURFACE_OFFSET, "曲面偏置失败"}, + {ErrorCode::ERR_SURFACE_FAIRING, "曲面光顺失败"}, + + {ErrorCode::ERR_IGA_MESH_INVALID, "IGA网格无效"}, + {ErrorCode::ERR_IGA_REFINEMENT_FAILED, "IGA细化失败"}, + + // 4xxx:mesh 网格错误 + {ErrorCode::ERR_MESH_EMPTY, "网格为空"}, + {ErrorCode::ERR_MESH_NON_MANIFOLD, "非流形网格"}, + {ErrorCode::ERR_MESH_DEGENERATE_FACE, "退化面"}, + {ErrorCode::ERR_MESH_SELF_INTERSECTION, "网格自交"}, + {ErrorCode::ERR_MESH_INVERTED_NORMAL, "法线翻转"}, + {ErrorCode::ERR_MESH_HOLE_DETECTED, "检测到孔洞"}, + {ErrorCode::ERR_MESH_DISCONNECTED, "网格不连通"}, + + {ErrorCode::ERR_MESH_BOOLEAN_FAILED, "网格布尔失败"}, + {ErrorCode::ERR_MESH_SIMPLIFY_FAILED, "网格简化失败"}, + {ErrorCode::ERR_MESH_SMOOTH_FAILED, "网格平滑失败"}, + {ErrorCode::ERR_MESH_REPAIR_FAILED, "网格修复失败"}, + {ErrorCode::ERR_MESH_PARAMETERIZE_FAILED,"网格参数化失败"}, + {ErrorCode::ERR_MESH_REMESH_FAILED, "网格重划分失败"}, + {ErrorCode::ERR_MESH_SPLIT_FAILED, "网格分割失败"}, + + {ErrorCode::ERR_FEA_MESH_QUALITY, "FEA网格质量不足"}, + {ErrorCode::ERR_FEA_MESH_GENERATION, "FEA网格生成失败"}, + {ErrorCode::ERR_MARCHING_CUBES_FAILED, "Marching Cubes失败"}, + {ErrorCode::ERR_POINT_CLOUD_EMPTY, "点云为空"}, + {ErrorCode::ERR_POINT_CLOUD_NOISE, "点云噪声过大"}, + + // 5xxx:brep B-Rep错误 + {ErrorCode::ERR_BREP_TOPOLOGY_INVALID, "B-Rep拓扑无效"}, + {ErrorCode::ERR_BREP_GEOMETRY_MISMATCH, "B-Rep几何不匹配"}, + {ErrorCode::ERR_BREP_SHELL_NOT_CLOSED, "壳不封闭"}, + {ErrorCode::ERR_BREP_ORIENTATION, "方向不一致"}, + {ErrorCode::ERR_BREP_SEWING_FAILED, "缝合失败"}, + {ErrorCode::ERR_BREP_HEALING_FAILED, "修复失败"}, + {ErrorCode::ERR_BREP_VALIDATION_FAILED, "验证失败"}, + + {ErrorCode::ERR_BOOLEAN_OPERATION_FAILED,"布尔操作失败"}, + {ErrorCode::ERR_BOOLEAN_INTERSECT_FAILED,"布尔求交失败"}, + {ErrorCode::ERR_BOOLEAN_SUBTRACT_FAILED,"布尔求差失败"}, + {ErrorCode::ERR_BOOLEAN_UNION_FAILED, "布尔求并失败"}, + {ErrorCode::ERR_BOOLEAN_DEGENERATE, "布尔操作产生退化"}, + {ErrorCode::ERR_BOOLEAN_FALLBACK_USED, "布尔操作回退到备用方法"}, + + {ErrorCode::ERR_FEATURE_CREATION_FAILED,"特征创建失败"}, + {ErrorCode::ERR_FEATURE_TREE_INVALID, "特征树无效"}, + {ErrorCode::ERR_FEATURE_UPDATE_FAILED, "特征更新失败"}, + {ErrorCode::ERR_FEATURE_RECOGNITION, "特征识别失败"}, + + {ErrorCode::ERR_ASSEMBLY_CONSTRAINT, "装配约束冲突"}, + {ErrorCode::ERR_ASSEMBLY_INTERFERENCE, "装配干涉"}, + {ErrorCode::ERR_ASSEMBLY_CYCLE, "装配循环引用"}, + {ErrorCode::ERR_ASSEMBLY_MATE_FAILED, "配合失败"}, + {ErrorCode::ERR_ASSEMBLY_LOD_INVALID, "LOD级别无效"}, + + {ErrorCode::ERR_SHEET_METAL_THICKNESS, "钣金厚度无效"}, + {ErrorCode::ERR_SHEET_METAL_BEND_RADIUS,"弯曲半径无效"}, + {ErrorCode::ERR_SHEET_METAL_UNFOLD, "展开失败"}, + + {ErrorCode::ERR_PMI_INVALID, "PMI无效"}, + {ErrorCode::ERR_GDT_TOLERANCE, "GD&T公差错误"}, + {ErrorCode::ERR_AUTO_DIM_FAILED, "自动标注失败"}, + {ErrorCode::ERR_DRAWING_STANDARD, "制图标准错误"}, + + {ErrorCode::ERR_FFD_INVALID, "FFD变形无效"}, + + // 6xxx:sdf SDF错误 + {ErrorCode::ERR_SDF_EVALUATE_FAILED, "SDF求值失败"}, + {ErrorCode::ERR_SDF_TREE_INVALID, "SDF树无效"}, + {ErrorCode::ERR_SDF_TO_MESH_FAILED, "SDF转网格失败"}, + {ErrorCode::ERR_SDF_OPTIMIZE_FAILED, "SDF优化失败"}, + {ErrorCode::ERR_SDF_TORCH_ERROR, "SDF Torch推理错误"}, + {ErrorCode::ERR_SDF_PRIMITIVE_INVALID, "SDF基元无效"}, + + // 7xxx:capi C API错误 + {ErrorCode::ERR_CAPI_NULL_HANDLE, "空句柄"}, + {ErrorCode::ERR_CAPI_INVALID_ARGUMENT, "无效参数"}, + {ErrorCode::ERR_CAPI_TYPE_MISMATCH, "类型不匹配"}, + {ErrorCode::ERR_CAPI_NOT_INITIALIZED, "引擎未初始化"}, + {ErrorCode::ERR_CAPI_ALREADY_INITIALIZED,"引擎已初始化"}, + {ErrorCode::ERR_CAPI_BUFFER_TOO_SMALL, "输出缓冲区太小"}, + {ErrorCode::ERR_CAPI_FEATURE_NOT_AVAIL, "功能不可用"}, + {ErrorCode::ERR_CAPI_UNKNOWN_ERROR, "未知C API错误"}, + }; + + auto it = kMessages.find(code); + if (it != kMessages.end()) { + return it->second; + } + return "未知错误 (code=" + std::to_string(static_cast(code)) + ")"; +} + +// ═══════════════════════════════════════════════════════════════ +// error_module — 模块名 +// ═══════════════════════════════════════════════════════════════ + +std::string error_module(ErrorCode code) { + int32_t val = static_cast(code); + + if (val == 0) return "core"; + if (val >= 1000 && val < 2000) return "foundation"; + if (val >= 2000 && val < 3000) return "core"; + if (val >= 3000 && val < 4000) return "curves"; + if (val >= 4000 && val < 5000) return "mesh"; + if (val >= 5000 && val < 6000) return "brep"; + if (val >= 6000 && val < 7000) return "sdf"; + if (val >= 7000 && val < 8000) return "capi"; + return "unknown"; +} + +} // namespace vde::foundation diff --git a/tests/core/CMakeLists.txt b/tests/core/CMakeLists.txt index b352f49..b6c2dc3 100644 --- a/tests/core/CMakeLists.txt +++ b/tests/core/CMakeLists.txt @@ -13,3 +13,5 @@ add_vde_test(test_cam_optimization) add_vde_test(test_performance) add_vde_test(test_concurrent) add_vde_test(test_transaction) +add_vde_test(test_version) +add_vde_test(test_license) diff --git a/tests/core/test_license.cpp b/tests/core/test_license.cpp new file mode 100644 index 0000000..3e9dc89 --- /dev/null +++ b/tests/core/test_license.cpp @@ -0,0 +1,396 @@ +/** + * @file test_license.cpp + * @brief ViewDesignEngine 许可证管理单元测试 + */ + +#include +#include "vde/core/license_manager.h" +#include +#include + +using namespace vde::core; + +// ═══════════════════════════════════════════════════════════════ +// LicenseTier 测试 +// ═══════════════════════════════════════════════════════════════ + +TEST(LicenseTier, NameConversion) { + EXPECT_STREQ(license_tier_name(LicenseTier::Community), "Community"); + EXPECT_STREQ(license_tier_name(LicenseTier::Professional), "Professional"); + EXPECT_STREQ(license_tier_name(LicenseTier::Enterprise), "Enterprise"); +} + +TEST(LicenseTier, ParseName) { + EXPECT_EQ(parse_license_tier("Community"), 0); + EXPECT_EQ(parse_license_tier("Professional"), 1); + EXPECT_EQ(parse_license_tier("Enterprise"), 2); + EXPECT_EQ(parse_license_tier("community"), 0); + EXPECT_EQ(parse_license_tier("unknown"), -1); +} + +// ═══════════════════════════════════════════════════════════════ +// Feature 枚举测试 +// ═══════════════════════════════════════════════════════════════ + +TEST(FeatureEnum, FeatureName) { + EXPECT_STREQ(feature_name(Feature::BASIC_BREP), "BASIC_BREP"); + EXPECT_STREQ(feature_name(Feature::CAM_5AXIS), "CAM_5AXIS"); + EXPECT_STREQ(feature_name(Feature::TECHNICAL_SUPPORT), "TECHNICAL_SUPPORT"); +} + +TEST(FeatureEnum, ParseFeature) { + EXPECT_EQ(parse_feature("BASIC_BREP"), 0); + EXPECT_EQ(parse_feature("CAM_5AXIS"), 10); + EXPECT_EQ(parse_feature("TECHNICAL_SUPPORT"), 30); + EXPECT_EQ(parse_feature("NONEXISTENT"), -1); +} + +// ═══════════════════════════════════════════════════════════════ +// Tier → Feature 映射测试 +// ═══════════════════════════════════════════════════════════════ + +TEST(TierFeatures, CommunityLevel) { + auto feats = features_for_tier(LicenseTier::Community); + EXPECT_GE(feats.size(), 6u); // 至少6个基础功能 + + EXPECT_TRUE(feature_available_in_tier(Feature::BASIC_BREP, LicenseTier::Community)); + EXPECT_TRUE(feature_available_in_tier(Feature::STEP_IMPORT_EXPORT, LicenseTier::Community)); + EXPECT_TRUE(feature_available_in_tier(Feature::IGES_IMPORT_EXPORT, LicenseTier::Community)); + + // Community 不应该有 Professional 功能 + EXPECT_FALSE(feature_available_in_tier(Feature::CAM_5AXIS, LicenseTier::Community)); + EXPECT_FALSE(feature_available_in_tier(Feature::GD_T, LicenseTier::Community)); +} + +TEST(TierFeatures, ProfessionalLevel) { + auto feats = features_for_tier(LicenseTier::Professional); + EXPECT_GE(feats.size(), 12u); + + // 包含 Community 的全部 + EXPECT_TRUE(feature_available_in_tier(Feature::BASIC_BREP, LicenseTier::Professional)); + // 包含 Professional 特有 + EXPECT_TRUE(feature_available_in_tier(Feature::CAM_5AXIS, LicenseTier::Professional)); + EXPECT_TRUE(feature_available_in_tier(Feature::ADVANCED_SURFACING, LicenseTier::Professional)); + EXPECT_TRUE(feature_available_in_tier(Feature::GD_T, LicenseTier::Professional)); + EXPECT_TRUE(feature_available_in_tier(Feature::SHEET_METAL, LicenseTier::Professional)); + + // 不应包含 Enterprise 功能 + EXPECT_FALSE(feature_available_in_tier(Feature::TECHNICAL_SUPPORT, LicenseTier::Professional)); + EXPECT_FALSE(feature_available_in_tier(Feature::GPU_ACCELERATION, LicenseTier::Professional)); +} + +TEST(TierFeatures, EnterpriseLevel) { + auto feats = features_for_tier(LicenseTier::Enterprise); + EXPECT_GE(feats.size(), 18u); + + // 全部功能 + EXPECT_TRUE(feature_available_in_tier(Feature::BASIC_BREP, LicenseTier::Enterprise)); + EXPECT_TRUE(feature_available_in_tier(Feature::CAM_5AXIS, LicenseTier::Enterprise)); + EXPECT_TRUE(feature_available_in_tier(Feature::TECHNICAL_SUPPORT, LicenseTier::Enterprise)); + EXPECT_TRUE(feature_available_in_tier(Feature::CUSTOM_FORMATS, LicenseTier::Enterprise)); + EXPECT_TRUE(feature_available_in_tier(Feature::GPU_ACCELERATION, LicenseTier::Enterprise)); + EXPECT_TRUE(feature_available_in_tier(Feature::LICENSE_MANAGER_SDK, LicenseTier::Enterprise)); +} + +// ═══════════════════════════════════════════════════════════════ +// LicenseInfo 结构体测试 +// ═══════════════════════════════════════════════════════════════ + +TEST(LicenseInfo, DefaultConstruction) { + LicenseInfo info; + EXPECT_EQ(info.tier, LicenseTier::Community); + EXPECT_EQ(info.licensee, "Trial User"); + EXPECT_EQ(info.expiry, 0); + EXPECT_EQ(info.version, 1); +} + +TEST(LicenseInfo, IsExpiredPermanent) { + LicenseInfo info; + info.expiry = 0; // 永久 + EXPECT_FALSE(info.is_expired()); +} + +TEST(LicenseInfo, IsExpiredFuture) { + LicenseInfo info; + info.expiry = std::time(nullptr) + 86400 * 365; // 一年后 + EXPECT_FALSE(info.is_expired()); +} + +TEST(LicenseInfo, IsExpiredPast) { + LicenseInfo info; + info.expiry = std::time(nullptr) - 86400; // 昨天 + EXPECT_TRUE(info.is_expired()); +} + +TEST(LicenseInfo, DaysUntilExpiry) { + LicenseInfo info; + + // 永久 + info.expiry = 0; + EXPECT_EQ(info.days_until_expiry(), -1); + + // 未来30天 + info.expiry = std::time(nullptr) + 86400 * 30; + EXPECT_EQ(info.days_until_expiry(), 30); + + // 未来1天 + info.expiry = std::time(nullptr) + 86400; + EXPECT_EQ(info.days_until_expiry(), 1); + + // 已过期 + info.expiry = std::time(nullptr) - 86400; + EXPECT_EQ(info.days_until_expiry(), 0); +} + +// ═══════════════════════════════════════════════════════════════ +// LicenseKey 生成/解析/验证测试 +// ═══════════════════════════════════════════════════════════════ + +TEST(LicenseKey, GenerateAndParse) { + // 生成一个 Community 永不过期的 Key + std::string key = LicenseKey::generate(LicenseTier::Community, 0, "TestUser"); + EXPECT_FALSE(key.empty()); + EXPECT_EQ(key.substr(0, 4), "VDE-"); + + // 解析 + LicenseKey lk; + EXPECT_TRUE(lk.parse(key)); + EXPECT_TRUE(lk.signature_valid()); + EXPECT_EQ(lk.info().tier, LicenseTier::Community); + EXPECT_EQ(lk.info().expiry, 0); +} + +TEST(LicenseKey, GenerateAndParseProfessional) { + std::time_t fut = std::time(nullptr) + 86400 * 365; // 一年后 + std::string key = LicenseKey::generate(LicenseTier::Professional, fut, "ProUser"); + + LicenseKey lk; + EXPECT_TRUE(lk.parse(key)); + EXPECT_TRUE(lk.signature_valid()); + EXPECT_EQ(lk.info().tier, LicenseTier::Professional); + EXPECT_EQ(lk.info().expiry, fut); +} + +TEST(LicenseKey, GenerateEnterprise) { + std::string key = LicenseKey::generate(LicenseTier::Enterprise, 0, "EntUser"); + + LicenseKey lk; + EXPECT_TRUE(lk.parse(key)); + EXPECT_TRUE(lk.signature_valid()); + EXPECT_EQ(lk.info().tier, LicenseTier::Enterprise); +} + +TEST(LicenseKey, Validate) { + std::string key = LicenseKey::generate(LicenseTier::Community, 0, "User"); + EXPECT_TRUE(LicenseKey::validate(key)); +} + +TEST(LicenseKey, ValidateExpired) { + // 生成一个已过期的 Key + std::string key = LicenseKey::generate(LicenseTier::Community, + std::time(nullptr) - 86400, "User"); + EXPECT_FALSE(LicenseKey::validate(key)); +} + +TEST(LicenseKey, InvalidFormat) { + EXPECT_FALSE(LicenseKey::validate("")); + EXPECT_FALSE(LicenseKey::validate("INVALID-KEY")); + EXPECT_FALSE(LicenseKey::validate("VDE-!!!!")); +} + +TEST(LicenseKey, TamperedKey) { + std::string key = LicenseKey::generate(LicenseTier::Community, 0, "User"); + + // 篡改 key(改一个字符) + if (key.size() > 6) { + key[6] = (key[6] == 'A') ? 'B' : 'A'; + } + + LicenseKey lk; + bool parsed = lk.parse(key); + if (parsed) { + // 签名验证应该失败 + EXPECT_FALSE(lk.signature_valid()); + } + // 无论如何,整体 validate 应该失败 + EXPECT_FALSE(LicenseKey::validate(key)); +} + +TEST(LicenseKey, MultipleKeysAreDifferent) { + std::string k1 = LicenseKey::generate(LicenseTier::Community, 0, "U1"); + std::string k2 = LicenseKey::generate(LicenseTier::Professional, 0, "U2"); + EXPECT_NE(k1, k2); +} + +TEST(LicenseKey, KeyStringStored) { + std::string key = LicenseKey::generate(LicenseTier::Community, 0, "U"); + LicenseKey lk; + lk.parse(key); + EXPECT_EQ(lk.key_string(), key); +} + +// ═══════════════════════════════════════════════════════════════ +// LicenseManager 单例测试 +// ═══════════════════════════════════════════════════════════════ + +TEST(LicenseManager, Singleton) { + LicenseManager& mgr1 = LicenseManager::instance(); + LicenseManager& mgr2 = LicenseManager::instance(); + EXPECT_EQ(&mgr1, &mgr2); +} + +TEST(LicenseManager, DefaultCommunity) { + auto& mgr = LicenseManager::instance(); + EXPECT_EQ(mgr.current_tier(), LicenseTier::Community); + EXPECT_FALSE(mgr.is_expired()); + EXPECT_FALSE(mgr.is_trial()); +} + +TEST(LicenseManager, HasFeatureBasic) { + auto& mgr = LicenseManager::instance(); + // Community 默认应该有基础功能 + EXPECT_TRUE(mgr.has_feature("BASIC_BREP")); + EXPECT_TRUE(mgr.has_feature("STEP_IMPORT_EXPORT")); + EXPECT_TRUE(mgr.has_feature(Feature::MESH_OPERATIONS)); +} + +TEST(LicenseManager, HasFeatureProfessional) { + auto& mgr = LicenseManager::instance(); + // Community 不应该有 Professional 功能 + EXPECT_FALSE(mgr.has_feature("CAM_5AXIS")); + EXPECT_FALSE(mgr.has_feature("GD_T")); + EXPECT_FALSE(mgr.has_feature("ADVANCED_SURFACING")); +} + +TEST(LicenseManager, SetLicenseCommunity) { + auto& mgr = LicenseManager::instance(); + + // 保存原始 license + auto orig = mgr.current_license(); + + std::string key = LicenseKey::generate(LicenseTier::Community, 0, "Test"); + EXPECT_TRUE(mgr.set_license(key)); + EXPECT_EQ(mgr.current_tier(), LicenseTier::Community); + EXPECT_FALSE(mgr.is_trial()); + + // 恢复原始 + mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Community User")); +} + +TEST(LicenseManager, SetLicenseProfessional) { + auto& mgr = LicenseManager::instance(); + std::time_t fut = std::time(nullptr) + 86400 * 365; + + std::string key = LicenseKey::generate(LicenseTier::Professional, fut, "Pro"); + EXPECT_TRUE(mgr.set_license(key)); + EXPECT_EQ(mgr.current_tier(), LicenseTier::Professional); + EXPECT_TRUE(mgr.has_feature("CAM_5AXIS")); + EXPECT_TRUE(mgr.has_feature("GD_T")); + + // 恢复默认 + mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Community User")); +} + +TEST(LicenseManager, SetLicenseEnterprise) { + auto& mgr = LicenseManager::instance(); + + std::string key = LicenseKey::generate(LicenseTier::Enterprise, 0, "Ent"); + EXPECT_TRUE(mgr.set_license(key)); + EXPECT_EQ(mgr.current_tier(), LicenseTier::Enterprise); + EXPECT_TRUE(mgr.has_feature("TECHNICAL_SUPPORT")); + EXPECT_TRUE(mgr.has_feature("GPU_ACCELERATION")); + EXPECT_TRUE(mgr.has_feature("CUSTOM_FORMATS")); + + // 恢复 + mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Community User")); +} + +TEST(LicenseManager, SetLicenseExpired) { + auto& mgr = LicenseManager::instance(); + // 先生成一个有效 key 确保非过期状态 + mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Reset")); + + // 生成已过期的 key + std::string key = LicenseKey::generate(LicenseTier::Professional, + std::time(nullptr) - 86400, "Expired"); + // 已过期 key 应该被拒绝 + EXPECT_FALSE(mgr.set_license(key)); +} + +TEST(LicenseManager, SetLicenseInvalid) { + auto& mgr = LicenseManager::instance(); + EXPECT_FALSE(mgr.set_license("INVALID-KEY")); + EXPECT_FALSE(mgr.set_license("")); +} + +TEST(LicenseManager, TrialMode) { + auto& mgr = LicenseManager::instance(); + // 先设置永久 Community key + mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Reset")); + + mgr.start_trial(30); + EXPECT_TRUE(mgr.is_trial()); + EXPECT_EQ(mgr.current_tier(), LicenseTier::Professional); + EXPECT_EQ(mgr.trial_days_left(), 30); + EXPECT_TRUE(mgr.has_feature("CAM_5AXIS")); + + // 但也应该有基础功能 + EXPECT_TRUE(mgr.has_feature("BASIC_BREP")); + + // 恢复 + mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Community User")); + EXPECT_FALSE(mgr.is_trial()); +} + +TEST(LicenseManager, TrialDaysLeftVarious) { + auto& mgr = LicenseManager::instance(); + mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Reset")); + + // 没有试用时 + EXPECT_EQ(mgr.trial_days_left(), -1); + + // 30天试用 + mgr.start_trial(30); + EXPECT_GE(mgr.trial_days_left(), 29); // 可能刚好跨天 + EXPECT_LE(mgr.trial_days_left(), 30); + + // 恢复 + mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Reset")); +} + +TEST(LicenseManager, CurrentLicense) { + auto& mgr = LicenseManager::instance(); + std::string key = LicenseKey::generate(LicenseTier::Professional, + std::time(nullptr) + 86400 * 90, "TestCo"); + mgr.set_license(key); + + const auto& info = mgr.current_license(); + EXPECT_EQ(info.tier, LicenseTier::Professional); + EXPECT_EQ(info.licensee, "Licensee"); // payload 中无 licensee 字段 + + // 恢复 + mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Community User")); +} + +TEST(LicenseManager, HasFeatureInvalidName) { + auto& mgr = LicenseManager::instance(); + EXPECT_FALSE(mgr.has_feature("NONEXISTENT_FEATURE")); + EXPECT_FALSE(mgr.has_feature("")); +} + +TEST(LicenseManager, ExpiredLicenseBlocksFeatures) { + auto& mgr = LicenseManager::instance(); + // 恢复有效状态 + mgr.set_license(LicenseKey::generate(LicenseTier::Professional, + std::time(nullptr) + 86400 * 365, "Reset")); + EXPECT_TRUE(mgr.has_feature("CAM_5AXIS")); + + // 设置已过期的 license + mgr.set_license(LicenseKey::generate(LicenseTier::Professional, + std::time(nullptr) - 86400, "Expired")); + // 过期 license 不会被设置(set_license 返回 false),所以状态不变 + // 但如果 is_expired() 返回 true,has_feature 也会 false + + // 恢复 + mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Community User")); +} diff --git a/tests/core/test_version.cpp b/tests/core/test_version.cpp new file mode 100644 index 0000000..da4b936 --- /dev/null +++ b/tests/core/test_version.cpp @@ -0,0 +1,130 @@ +/** + * @file test_version.cpp + * @brief ViewDesignEngine 版本信息单元测试 + */ + +#include +#include "vde/core/version.h" +#include + +using namespace vde::core; + +// ═══════════════════════════════════════════════════════════════ +// 编译时版本宏测试 +// ═══════════════════════════════════════════════════════════════ + +TEST(VersionMacro, MajorVersion) { + EXPECT_EQ(VDE_VERSION_MAJOR, 1); +} + +TEST(VersionMacro, MinorVersion) { + EXPECT_EQ(VDE_VERSION_MINOR, 0); +} + +TEST(VersionMacro, PatchVersion) { + EXPECT_EQ(VDE_VERSION_PATCH, 1); +} + +TEST(VersionMacro, VersionString) { + EXPECT_STREQ(VDE_VERSION_STRING, "1.0.1"); +} + +TEST(VersionMacro, VersionNum) { + EXPECT_EQ(VDE_VERSION_NUM, 10001); // 1*10000 + 0*100 + 1 +} + +TEST(VersionMacro, VersionCodeFunction) { + EXPECT_EQ(VDE_VERSION_CODE(2, 3, 4), 20304); + EXPECT_EQ(VDE_VERSION_CODE(0, 0, 1), 1); + EXPECT_EQ(VDE_VERSION_CODE(10, 20, 30), 102030); +} + +// ═══════════════════════════════════════════════════════════════ +// 构建信息测试 +// ═══════════════════════════════════════════════════════════════ + +TEST(BuildInfo, BuildTypeDefined) { + EXPECT_TRUE(std::string(VDE_BUILD_TYPE).size() > 0); +} + +TEST(BuildInfo, CompilerInfoDefined) { + EXPECT_TRUE(std::string(VDE_COMPILER_INFO).size() > 0); +} + +TEST(BuildInfo, CppStandardString) { + std::string cpp = VDE_CPP_STANDARD_STRING; + EXPECT_TRUE(cpp == "C++20" || cpp == "C++17" || cpp == "C++14" || + cpp == "C++11" || cpp == "C++03"); + // 项目要求 C++20 + EXPECT_EQ(cpp, "C++20"); +} + +// ═══════════════════════════════════════════════════════════════ +// VersionInfo 结构体测试 +// ═══════════════════════════════════════════════════════════════ + +TEST(VersionInfo, DefaultConstruction) { + VersionInfo info = vde_version(); + EXPECT_EQ(info.major, 1); + EXPECT_EQ(info.minor, 0); + EXPECT_EQ(info.patch, 1); + EXPECT_EQ(info.version_string, "1.0.1"); + EXPECT_FALSE(info.build_type.empty()); + EXPECT_FALSE(info.compiler_info.empty()); + EXPECT_EQ(info.cpp_standard, "C++20"); +} + +TEST(VersionInfo, BuildTimestamp) { + VersionInfo info = vde_version(); + // 时间戳可能为 unknown 或 ISO 8601 格式 + EXPECT_FALSE(info.build_timestamp.empty()); +} + +// ═══════════════════════════════════════════════════════════════ +// vde_version_string 测试 +// ═══════════════════════════════════════════════════════════════ + +TEST(VersionString, Format) { + std::string vs = vde_version_string(); + // 格式:ViewDesignEngine v1.0.1 (C++20, GCC x.x) + EXPECT_TRUE(vs.find("ViewDesignEngine v1.0.1") != std::string::npos); + EXPECT_TRUE(vs.find("C++20") != std::string::npos); + EXPECT_TRUE(vs.find("(") != std::string::npos); + EXPECT_TRUE(vs.find(")") != std::string::npos); +} + +TEST(VersionString, NonEmpty) { + EXPECT_FALSE(vde_version_string().empty()); + EXPECT_GT(vde_version_string().size(), 20u); +} + +// ═══════════════════════════════════════════════════════════════ +// vde_version_compare 测试 +// ═══════════════════════════════════════════════════════════════ + +TEST(VersionCompare, Equality) { + EXPECT_EQ(vde_version_compare(1, 0, 1, 1, 0, 1), 0); + EXPECT_EQ(vde_version_compare(2, 0, 0, 2, 0, 0), 0); +} + +TEST(VersionCompare, Less) { + EXPECT_LT(vde_version_compare(1, 0, 0, 1, 0, 1), 0); + EXPECT_LT(vde_version_compare(1, 0, 0, 2, 0, 0), 0); + EXPECT_LT(vde_version_compare(1, 9, 9, 2, 0, 0), 0); +} + +TEST(VersionCompare, Greater) { + EXPECT_GT(vde_version_compare(2, 0, 0, 1, 0, 0), 0); + EXPECT_GT(vde_version_compare(1, 0, 2, 1, 0, 1), 0); + EXPECT_GT(vde_version_compare(1, 1, 0, 1, 0, 99), 0); +} + +TEST(VersionCompare, Transitivity) { + // 1.0.0 < 1.0.1 < 1.1.0 < 2.0.0 + int cmp1 = vde_version_compare(1, 0, 0, 1, 0, 1); + int cmp2 = vde_version_compare(1, 0, 1, 1, 1, 0); + int cmp3 = vde_version_compare(1, 1, 0, 2, 0, 0); + EXPECT_LT(cmp1, 0); + EXPECT_LT(cmp2, 0); + EXPECT_LT(cmp3, 0); +}