1use crate::features::{FeatureError, FeatureTier};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
6use std::time::{Duration, SystemTime, UNIX_EPOCH};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct LicenseKey {
11 pub key: String,
12 pub tier: FeatureTier,
13 pub expires_at: u64, pub issued_at: u64,
15 pub customer_id: String,
16 pub features: Vec<String>, }
18
19#[derive(Debug, Clone)]
21pub enum LicenseStatus {
22 Valid(FeatureTier),
23 Expired(u64), Invalid,
25 NotFound,
26}
27
28#[derive(Debug, Clone)]
30pub enum LicenseError {
31 InvalidKey(String),
32 Expired { key: String, expired_at: u64 },
33 NetworkError(String),
34 InvalidFormat(String),
35 FeatureNotLicensed { feature: String, tier: FeatureTier },
36}
37
38impl std::fmt::Display for LicenseError {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 match self {
41 LicenseError::InvalidKey(key) => {
42 write!(f, "Invalid license key: {key}")
43 }
44 LicenseError::Expired { key, expired_at } => {
45 write!(f, "License key '{key}' expired at {expired_at}")
46 }
47 LicenseError::NetworkError(msg) => {
48 write!(f, "Network error during license verification: {msg}")
49 }
50 LicenseError::InvalidFormat(msg) => {
51 write!(f, "Invalid license format: {msg}")
52 }
53 LicenseError::FeatureNotLicensed { feature, tier } => {
54 write!(f, "Feature '{feature}' not licensed for {tier:?} tier")
55 }
56 }
57 }
58}
59
60impl std::error::Error for LicenseError {}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64struct LicenseCache {
65 licenses: HashMap<String, LicenseKey>,
66 last_verification: u64, }
68
69pub struct LicenseVerifier {
71 cache: LicenseCache,
72 #[allow(dead_code)]
73 verification_url: String,
74 offline_mode: bool,
75 cache_ttl: Duration, }
77
78impl LicenseVerifier {
79 pub fn new(verification_url: String) -> Self {
81 Self {
82 cache: LicenseCache {
83 licenses: HashMap::new(),
84 last_verification: 0,
85 },
86 verification_url,
87 offline_mode: false,
88 cache_ttl: Duration::from_secs(24 * 60 * 60), }
90 }
91
92 pub fn new_offline() -> Self {
94 let mut verifier = Self {
95 cache: LicenseCache {
96 licenses: HashMap::new(),
97 last_verification: current_timestamp(),
98 },
99 verification_url: String::new(),
100 offline_mode: true,
101 cache_ttl: Duration::from_secs(30 * 24 * 60 * 60), };
103
104 verifier.add_demo_licenses();
106 verifier
107 }
108
109 fn add_demo_licenses(&mut self) {
111 let demo_licenses = vec![
112 LicenseKey {
113 key: "DEMO-123456".to_string(),
114 tier: FeatureTier::OpenSource,
115 expires_at: current_timestamp() + 86400 * 30, issued_at: current_timestamp(),
117 customer_id: "demo-user".to_string(),
118 features: vec![
119 "basic_position_encoding".to_string(),
120 "similarity_search".to_string(),
121 "opening_book".to_string(),
122 ],
123 },
124 LicenseKey {
125 key: "PREMIUM-789012".to_string(),
126 tier: FeatureTier::Premium,
127 expires_at: current_timestamp() + 86400 * 365, issued_at: current_timestamp(),
129 customer_id: "premium-user".to_string(),
130 features: vec![
131 "gpu_acceleration".to_string(),
132 "ultra_fast_loading".to_string(),
133 "memory_mapped_files".to_string(),
134 "advanced_tactical_search".to_string(),
135 "pondering".to_string(),
136 "multi_pv_analysis".to_string(),
137 ],
138 },
139 LicenseKey {
140 key: "ENTERPRISE-345678".to_string(),
141 tier: FeatureTier::Enterprise,
142 expires_at: current_timestamp() + 86400 * 365 * 2, issued_at: current_timestamp(),
144 customer_id: "enterprise-user".to_string(),
145 features: vec![
146 "distributed_training".to_string(),
147 "cloud_deployment".to_string(),
148 "enterprise_analytics".to_string(),
149 "custom_algorithms".to_string(),
150 "unlimited_positions".to_string(),
151 ],
152 },
153 ];
154
155 for license in demo_licenses {
156 self.cache.licenses.insert(license.key.clone(), license);
157 }
158 }
159
160 pub fn load_cache<P: AsRef<std::path::Path>>(
162 &mut self,
163 path: P,
164 ) -> Result<(), Box<dyn std::error::Error>> {
165 if path.as_ref().exists() {
166 let content = std::fs::read_to_string(path)?;
167 self.cache = serde_json::from_str(&content)?;
168 }
169 Ok(())
170 }
171
172 pub fn save_cache<P: AsRef<std::path::Path>>(
174 &self,
175 path: P,
176 ) -> Result<(), Box<dyn std::error::Error>> {
177 let content = serde_json::to_string_pretty(&self.cache)?;
178 std::fs::write(path, content)?;
179 Ok(())
180 }
181
182 pub async fn verify_license(&mut self, key: &str) -> Result<LicenseStatus, LicenseError> {
184 if let Some(cached_license) = self.cache.licenses.get(key) {
186 if self.is_cache_valid() && self.is_license_valid(cached_license) {
187 return Ok(LicenseStatus::Valid(cached_license.tier.clone()));
188 } else if !self.is_license_valid(cached_license) {
189 return Ok(LicenseStatus::Expired(cached_license.expires_at));
190 }
191 }
192
193 if !self.offline_mode {
195 match self.verify_online(key).await {
196 Ok(license) => {
197 self.cache.licenses.insert(key.to_string(), license.clone());
199 self.cache.last_verification = current_timestamp();
200
201 if self.is_license_valid(&license) {
202 Ok(LicenseStatus::Valid(license.tier))
203 } else {
204 Ok(LicenseStatus::Expired(license.expires_at))
205 }
206 }
207 Err(e) => {
208 if let Some(cached_license) = self.cache.licenses.get(key) {
210 if self.is_license_valid(cached_license) {
211 Ok(LicenseStatus::Valid(cached_license.tier.clone()))
212 } else {
213 Ok(LicenseStatus::Expired(cached_license.expires_at))
214 }
215 } else {
216 Err(e)
217 }
218 }
219 }
220 } else {
221 if let Some(cached_license) = self.cache.licenses.get(key) {
223 if self.is_license_valid(cached_license) {
224 Ok(LicenseStatus::Valid(cached_license.tier.clone()))
225 } else {
226 Ok(LicenseStatus::Expired(cached_license.expires_at))
227 }
228 } else {
229 Ok(LicenseStatus::NotFound)
230 }
231 }
232 }
233
234 pub fn add_license(&mut self, license: LicenseKey) {
236 self.cache.licenses.insert(license.key.clone(), license);
237 self.cache.last_verification = current_timestamp();
238 }
239
240 pub async fn check_feature_license(
242 &mut self,
243 key: &str,
244 feature: &str,
245 ) -> Result<(), LicenseError> {
246 let license_status = self.verify_license(key).await?;
247
248 match license_status {
249 LicenseStatus::Valid(tier) => {
250 let cached_license = self
251 .cache
252 .licenses
253 .get(key)
254 .ok_or_else(|| LicenseError::InvalidKey(key.to_string()))?;
255
256 if cached_license.features.contains(&feature.to_string()) {
258 Ok(())
259 } else {
260 let registry = crate::features::FeatureRegistry::new();
262 if registry.is_feature_available(feature, &tier) {
263 Ok(())
264 } else {
265 Err(LicenseError::FeatureNotLicensed {
266 feature: feature.to_string(),
267 tier,
268 })
269 }
270 }
271 }
272 LicenseStatus::Expired(expired_at) => Err(LicenseError::Expired {
273 key: key.to_string(),
274 expired_at,
275 }),
276 LicenseStatus::Invalid => Err(LicenseError::InvalidKey(key.to_string())),
277 LicenseStatus::NotFound => Err(LicenseError::InvalidKey(key.to_string())),
278 }
279 }
280
281 async fn verify_online(&self, key: &str) -> Result<LicenseKey, LicenseError> {
283 if key.starts_with("DEMO-") {
287 Ok(LicenseKey {
288 key: key.to_string(),
289 tier: FeatureTier::OpenSource,
290 expires_at: current_timestamp() + 86400 * 30, issued_at: current_timestamp(),
292 customer_id: "demo-user".to_string(),
293 features: vec![
294 "basic_position_encoding".to_string(),
295 "similarity_search".to_string(),
296 "opening_book".to_string(),
297 ],
298 })
299 } else if key.starts_with("PREMIUM-") {
300 Ok(LicenseKey {
301 key: key.to_string(),
302 tier: FeatureTier::Premium,
303 expires_at: current_timestamp() + 86400 * 365, issued_at: current_timestamp(),
305 customer_id: "premium-user".to_string(),
306 features: vec![
307 "gpu_acceleration".to_string(),
308 "ultra_fast_loading".to_string(),
309 "memory_mapped_files".to_string(),
310 "advanced_tactical_search".to_string(),
311 "pondering".to_string(),
312 "multi_pv_analysis".to_string(),
313 ],
314 })
315 } else if key.starts_with("ENTERPRISE-") {
316 Ok(LicenseKey {
317 key: key.to_string(),
318 tier: FeatureTier::Enterprise,
319 expires_at: current_timestamp() + 86400 * 365 * 2, issued_at: current_timestamp(),
321 customer_id: "enterprise-user".to_string(),
322 features: vec![
323 "distributed_training".to_string(),
324 "cloud_deployment".to_string(),
325 "enterprise_analytics".to_string(),
326 "custom_algorithms".to_string(),
327 "unlimited_positions".to_string(),
328 ],
329 })
330 } else {
331 Err(LicenseError::InvalidKey(key.to_string()))
332 }
333 }
334
335 fn is_cache_valid(&self) -> bool {
337 let now = current_timestamp();
338 (now - self.cache.last_verification) < self.cache_ttl.as_secs()
339 }
340
341 fn is_license_valid(&self, license: &LicenseKey) -> bool {
343 current_timestamp() < license.expires_at
344 }
345}
346
347pub struct LicensedFeatureChecker {
349 verifier: LicenseVerifier,
350 current_license_key: Option<String>,
351 current_tier: FeatureTier,
352}
353
354impl LicensedFeatureChecker {
355 pub fn new(verification_url: String) -> Self {
357 Self {
358 verifier: LicenseVerifier::new(verification_url),
359 current_license_key: None,
360 current_tier: FeatureTier::OpenSource,
361 }
362 }
363
364 pub fn new_offline() -> Self {
366 Self {
367 verifier: LicenseVerifier::new_offline(),
368 current_license_key: None,
369 current_tier: FeatureTier::OpenSource,
370 }
371 }
372
373 pub async fn activate_license(&mut self, key: &str) -> Result<FeatureTier, LicenseError> {
375 let status = self.verifier.verify_license(key).await?;
376
377 match status {
378 LicenseStatus::Valid(tier) => {
379 self.current_license_key = Some(key.to_string());
380 self.current_tier = tier.clone();
381 Ok(tier)
382 }
383 LicenseStatus::Expired(expired_at) => Err(LicenseError::Expired {
384 key: key.to_string(),
385 expired_at,
386 }),
387 LicenseStatus::Invalid | LicenseStatus::NotFound => {
388 Err(LicenseError::InvalidKey(key.to_string()))
389 }
390 }
391 }
392
393 pub async fn check_feature(&mut self, feature: &str) -> Result<(), FeatureError> {
395 if let Some(key) = &self.current_license_key {
396 match self.verifier.check_feature_license(key, feature).await {
397 Ok(()) => Ok(()),
398 Err(LicenseError::FeatureNotLicensed { feature, tier }) => {
399 Err(FeatureError::InsufficientTier {
400 feature,
401 required: tier,
402 current: self.current_tier.clone(),
403 })
404 }
405 Err(e) => Err(FeatureError::UnknownFeature(e.to_string())),
406 }
407 } else {
408 let registry = crate::features::FeatureRegistry::new();
410 if registry.is_feature_available(feature, &self.current_tier) {
411 Ok(())
412 } else if let Some(required_tier) = registry.get_feature_tier(feature) {
413 Err(FeatureError::InsufficientTier {
414 feature: feature.to_string(),
415 required: required_tier.clone(),
416 current: self.current_tier.clone(),
417 })
418 } else {
419 Err(FeatureError::UnknownFeature(feature.to_string()))
420 }
421 }
422 }
423
424 pub fn get_current_tier(&self) -> &FeatureTier {
426 &self.current_tier
427 }
428
429 pub fn load_cache<P: AsRef<std::path::Path>>(
431 &mut self,
432 path: P,
433 ) -> Result<(), Box<dyn std::error::Error>> {
434 self.verifier.load_cache(path)
435 }
436
437 pub fn save_cache<P: AsRef<std::path::Path>>(
439 &self,
440 path: P,
441 ) -> Result<(), Box<dyn std::error::Error>> {
442 self.verifier.save_cache(path)
443 }
444}
445
446pub fn current_timestamp() -> u64 {
448 SystemTime::now()
449 .duration_since(UNIX_EPOCH)
450 .unwrap_or_default()
451 .as_secs()
452}
453
454#[allow(dead_code)]
456trait DurationExt {
457 fn from_hours(hours: u64) -> Duration;
458 fn from_days(days: u64) -> Duration;
459}
460
461impl DurationExt for Duration {
462 fn from_hours(hours: u64) -> Duration {
463 Duration::from_secs(hours * 3600)
464 }
465
466 fn from_days(days: u64) -> Duration {
467 Duration::from_secs(days * 86400)
468 }
469}
470
471#[cfg(test)]
472mod tests {
473 use super::*;
474 use tokio;
475
476 #[tokio::test]
477 async fn test_demo_license() {
478 let mut verifier = LicenseVerifier::new("https://api.example.com/license".to_string());
479
480 let status = verifier.verify_license("DEMO-123456").await.unwrap();
481 match status {
482 LicenseStatus::Valid(tier) => {
483 assert_eq!(tier, FeatureTier::OpenSource);
484 }
485 _ => panic!("Expected valid demo license"),
486 }
487 }
488
489 #[tokio::test]
490 async fn test_premium_license() {
491 let mut verifier = LicenseVerifier::new("https://api.example.com/license".to_string());
492
493 let status = verifier.verify_license("PREMIUM-789012").await.unwrap();
494 match status {
495 LicenseStatus::Valid(tier) => {
496 assert_eq!(tier, FeatureTier::Premium);
497 }
498 _ => panic!("Expected valid premium license"),
499 }
500 }
501
502 #[tokio::test]
503 async fn test_enterprise_license() {
504 let mut verifier = LicenseVerifier::new("https://api.example.com/license".to_string());
505
506 let status = verifier.verify_license("ENTERPRISE-345678").await.unwrap();
507 match status {
508 LicenseStatus::Valid(tier) => {
509 assert_eq!(tier, FeatureTier::Enterprise);
510 }
511 _ => panic!("Expected valid enterprise license"),
512 }
513 }
514
515 #[tokio::test]
516 async fn test_invalid_license() {
517 let mut verifier = LicenseVerifier::new("https://api.example.com/license".to_string());
518
519 let result = verifier.verify_license("INVALID-123").await;
520 assert!(result.is_err());
521 }
522
523 #[tokio::test]
524 async fn test_licensed_feature_checker() {
525 let mut checker = LicensedFeatureChecker::new_offline();
526
527 assert_eq!(checker.get_current_tier(), &FeatureTier::OpenSource);
529
530 let tier = checker.activate_license("PREMIUM-789012").await.unwrap();
532 assert_eq!(tier, FeatureTier::Premium);
533
534 assert!(checker.check_feature("gpu_acceleration").await.is_ok());
536
537 assert!(checker.check_feature("distributed_training").await.is_err());
539 }
540
541 #[test]
542 fn test_license_cache() {
543 let mut verifier = LicenseVerifier::new_offline();
544
545 let license = LicenseKey {
546 key: "TEST-123".to_string(),
547 tier: FeatureTier::Premium,
548 expires_at: current_timestamp() + 86400, issued_at: current_timestamp(),
550 customer_id: "test-user".to_string(),
551 features: vec!["gpu_acceleration".to_string()],
552 };
553
554 verifier.add_license(license);
555
556 let temp_file = tempfile::NamedTempFile::new().unwrap();
558 verifier.save_cache(temp_file.path()).unwrap();
559
560 let mut new_verifier = LicenseVerifier::new_offline();
561 new_verifier.load_cache(temp_file.path()).unwrap();
562
563 assert!(new_verifier.cache.licenses.contains_key("TEST-123"));
565 }
566}