chess_vector_engine/
license.rs

1use crate::features::{FeatureError, FeatureTier};
2use serde::{Deserialize, Serialize};
3/// License verification system for open-core business model
4/// Validates subscription tiers and enables feature access
5use std::collections::HashMap;
6use std::time::{Duration, SystemTime, UNIX_EPOCH};
7
8/// License key information
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct LicenseKey {
11    pub key: String,
12    pub tier: FeatureTier,
13    pub expires_at: u64, // Unix timestamp
14    pub issued_at: u64,
15    pub customer_id: String,
16    pub features: Vec<String>, // Specific features enabled
17}
18
19/// License validation result
20#[derive(Debug, Clone)]
21pub enum LicenseStatus {
22    Valid(FeatureTier),
23    Expired(u64), // Expired at timestamp
24    Invalid,
25    NotFound,
26}
27
28/// License verification errors
29#[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/// Local license cache for offline validation
63#[derive(Debug, Clone, Serialize, Deserialize)]
64struct LicenseCache {
65    licenses: HashMap<String, LicenseKey>,
66    last_verification: u64, // Last online verification timestamp
67}
68
69/// Main license verifier
70pub struct LicenseVerifier {
71    cache: LicenseCache,
72    #[allow(dead_code)]
73    verification_url: String,
74    offline_mode: bool,
75    cache_ttl: Duration, // How long to trust cached licenses
76}
77
78impl LicenseVerifier {
79    /// Create new license verifier
80    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), // Cache for 24 hours
89        }
90    }
91
92    /// Create verifier in offline mode (uses only cached licenses)
93    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), // Longer cache for offline mode
102        };
103
104        // Pre-populate with demo licenses for testing
105        verifier.add_demo_licenses();
106        verifier
107    }
108
109    /// Add demo licenses for testing purposes
110    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, // 30 days
116                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, // 1 year
128                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, // 2 years
143                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    /// Load license cache from file
161    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    /// Save license cache to file
173    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    /// Verify a license key
183    pub async fn verify_license(&mut self, key: &str) -> Result<LicenseStatus, LicenseError> {
184        // Check cache first
185        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        // Online verification if not in offline mode
194        if !self.offline_mode {
195            match self.verify_online(key).await {
196                Ok(license) => {
197                    // Cache the verified license
198                    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                    // Fall back to cached license if online verification fails
209                    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            // Offline mode - only use cache
222            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    /// Add a license to the cache (for testing or manual activation)
235    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    /// Check if feature is licensed for given key
241    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                // Check if feature is specifically enabled or tier allows it
257                if cached_license.features.contains(&feature.to_string()) {
258                    Ok(())
259                } else {
260                    // Use feature registry to check tier access
261                    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    /// Online license verification (placeholder for actual API call)
282    async fn verify_online(&self, key: &str) -> Result<LicenseKey, LicenseError> {
283        // TODO: Implement actual HTTP request to license server
284        // For now, return a mock response based on key format
285
286        if key.starts_with("DEMO-") {
287            Ok(LicenseKey {
288                key: key.to_string(),
289                tier: FeatureTier::OpenSource,
290                expires_at: current_timestamp() + 86400 * 30, // 30 days
291                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, // 1 year
304                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, // 2 years
320                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    /// Check if cached license data is still valid (not expired from cache perspective)
336    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    /// Check if license itself is valid (not expired)
342    fn is_license_valid(&self, license: &LicenseKey) -> bool {
343        current_timestamp() < license.expires_at
344    }
345}
346
347/// Enhanced feature checker with license verification
348pub struct LicensedFeatureChecker {
349    verifier: LicenseVerifier,
350    current_license_key: Option<String>,
351    current_tier: FeatureTier,
352}
353
354impl LicensedFeatureChecker {
355    /// Create new licensed feature checker
356    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    /// Create offline-only checker
365    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    /// Activate license key
374    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    /// Check if feature is available with current license
394    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            // No license key - use basic feature registry
409            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    /// Get current tier
425    pub fn get_current_tier(&self) -> &FeatureTier {
426        &self.current_tier
427    }
428
429    /// Load license cache
430    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    /// Save license cache
438    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
446/// Get current Unix timestamp
447pub fn current_timestamp() -> u64 {
448    SystemTime::now()
449        .duration_since(UNIX_EPOCH)
450        .unwrap_or_default()
451        .as_secs()
452}
453
454/// Duration extensions for convenience
455#[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        // Should start as open source
528        assert_eq!(checker.get_current_tier(), &FeatureTier::OpenSource);
529
530        // Activate premium license
531        let tier = checker.activate_license("PREMIUM-789012").await.unwrap();
532        assert_eq!(tier, FeatureTier::Premium);
533
534        // Should now allow premium features
535        assert!(checker.check_feature("gpu_acceleration").await.is_ok());
536
537        // Should still deny enterprise features
538        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, // 1 day
549            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        // Save and load cache
557        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        // Should have the cached license
564        assert!(new_verifier.cache.licenses.contains_key("TEST-123"));
565    }
566}