use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Product {
pub slug: String,
pub name: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Entitlement {
pub key: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_at: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LicenseResponse {
pub object: String,
pub key: String,
pub status: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub starts_at: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_at: Option<DateTime<Utc>>,
pub mode: String,
pub plan_key: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub seat_limit: Option<u32>,
pub active_seats: u32,
pub active_entitlements: Vec<Entitlement>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, serde_json::Value>>,
pub product: Product,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ActivationResponse {
pub object: String,
pub id: String,
#[serde(alias = "fingerprint", alias = "device_fingerprint")]
pub device_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub device_name: Option<String>,
pub license_key: String,
pub activated_at: DateTime<Utc>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub deactivated_at: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ip_address: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, serde_json::Value>>,
pub license: LicenseResponse,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeactivationResponse {
pub object: String,
pub activation_id: String,
pub deactivated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ValidationWarning {
pub code: String,
pub message: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ActivationNested {
#[serde(default, skip_serializing_if = "String::is_empty")]
pub object: String,
pub id: String,
#[serde(alias = "fingerprint", alias = "device_fingerprint")]
pub device_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub device_name: Option<String>,
pub license_key: String,
pub activated_at: DateTime<Utc>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub deactivated_at: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ip_address: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ValidationResult {
pub object: String,
pub valid: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub warnings: Option<Vec<ValidationWarning>>,
pub license: LicenseResponse,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub activation: Option<ActivationNested>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub offline: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HeartbeatResponse {
pub object: String,
pub received_at: DateTime<Utc>,
pub license: LicenseResponse,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct HealthResponse {
pub object: String,
pub status: String,
pub api_version: String,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Release {
#[serde(default)]
pub object: String,
pub version: String,
pub channel: String,
pub platform: String,
pub product_slug: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub published_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ReleaseList {
#[serde(default)]
pub object: String,
#[serde(default)]
pub data: Vec<Release>,
#[serde(default)]
pub has_more: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DownloadToken {
#[serde(default)]
pub object: String,
pub token: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct License {
pub license_key: String,
pub device_id: String,
pub activation_id: String,
pub activated_at: DateTime<Utc>,
pub last_validated: DateTime<Utc>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub trusted_license: Option<LicenseResponse>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub validation: Option<ValidationResult>,
}
impl License {
pub fn fingerprint(&self) -> &str {
&self.device_id
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum LicenseStatus {
Inactive {
message: String,
},
Pending {
message: String,
},
Invalid {
message: String,
},
Active {
details: LicenseStatusDetails,
},
OfflineValid {
details: LicenseStatusDetails,
},
OfflineInvalid {
message: String,
},
}
impl LicenseStatus {
pub fn is_active(&self) -> bool {
matches!(self, Self::Active { .. } | Self::OfflineValid { .. })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TrustedLicenseSource {
SnapshotFile,
CachedLicense,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ClientStatus {
Active,
OfflineValid,
OfflineInvalid,
Inactive,
Invalid,
Pending,
}
impl ClientStatus {
pub const fn as_str(self) -> &'static str {
match self {
Self::Active => "active",
Self::OfflineValid => "offline_valid",
Self::OfflineInvalid => "offline_invalid",
Self::Inactive => "inactive",
Self::Invalid => "invalid",
Self::Pending => "pending",
}
}
}
impl std::fmt::Display for ClientStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct LicenseStatusDetails {
pub license: String,
pub device: String,
pub activated_at: DateTime<Utc>,
pub last_validated: DateTime<Utc>,
pub entitlements: Vec<Entitlement>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct EntitlementStatus {
pub active: bool,
pub reason: Option<EntitlementReason>,
pub expires_at: Option<DateTime<Utc>>,
pub entitlement: Option<Entitlement>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntitlementReason {
NoLicense,
NotFound,
Expired,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RestoreResult {
pub restored: bool,
pub status: LicenseStatus,
pub license: Option<License>,
pub validation: Option<ValidationResult>,
pub error: Option<String>,
}
impl Default for RestoreResult {
fn default() -> Self {
Self {
restored: false,
status: LicenseStatus::Inactive {
message: "No cached license".into(),
},
license: None,
validation: None,
error: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OfflineTokenResponse {
pub object: String,
pub token: OfflineTokenPayload,
pub signature: OfflineTokenSignature,
pub canonical: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OfflineTokenPayload {
pub schema_version: u32,
pub license_key: String,
pub product_slug: String,
pub plan_key: String,
pub mode: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub seat_limit: Option<u32>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "fingerprint",
alias = "device_fingerprint"
)]
pub device_id: Option<String>,
pub iat: i64,
pub exp: i64,
pub nbf: i64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub license_expires_at: Option<i64>,
pub kid: String,
pub entitlements: Vec<OfflineEntitlement>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OfflineEntitlement {
pub key: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_at: Option<i64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OfflineTokenSignature {
pub algorithm: String,
#[serde(alias = "kid")]
pub key_id: String,
pub value: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SigningKeyResponse {
pub object: String,
#[serde(alias = "kid")]
pub key_id: String,
pub algorithm: String,
pub public_key: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub created_at: Option<DateTime<Utc>>,
pub status: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MachineFile {
pub certificate: String,
#[serde(default = "default_machine_file_algorithm")]
pub algorithm: String,
#[serde(default)]
pub ttl: i64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub issued_at: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_at: Option<DateTime<Utc>>,
#[serde(default)]
pub license_key: String,
#[serde(default)]
pub fingerprint: String,
}
impl Default for MachineFile {
fn default() -> Self {
Self {
certificate: String::new(),
algorithm: default_machine_file_algorithm(),
ttl: 0,
issued_at: None,
expires_at: None,
license_key: String::new(),
fingerprint: String::new(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MachineFilePayload {
#[serde(default)]
pub schema_version: u32,
#[serde(default)]
pub issued: String,
#[serde(default)]
pub iat: i64,
#[serde(default)]
pub expiry: String,
#[serde(default)]
pub exp: i64,
#[serde(default)]
pub nbf: i64,
#[serde(default)]
pub ttl: i64,
#[serde(default)]
pub grace_period: i64,
#[serde(default)]
pub license_key: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub license_expires_at: Option<i64>,
#[serde(default)]
pub key_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sdk_version: Option<String>,
#[serde(default)]
pub machine_id: String,
#[serde(default)]
pub fingerprint: String,
#[serde(default)]
pub fingerprint_components: HashMap<String, String>,
#[serde(default)]
pub device_name: String,
#[serde(default)]
pub platform: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub created_at: Option<DateTime<Utc>>,
#[serde(default)]
pub metadata: HashMap<String, serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub license: Option<LicenseResponse>,
}
impl MachineFilePayload {
pub fn has_entitlement(&self, entitlement_key: &str) -> bool {
self.license
.as_ref()
.map(|license| {
license
.active_entitlements
.iter()
.any(|entitlement| entitlement.key == entitlement_key)
})
.unwrap_or(false)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MachineFileVerificationResult {
pub valid: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub payload: Option<MachineFilePayload>,
}
fn default_machine_file_algorithm() -> String {
"aes-256-gcm+ed25519".to_string()
}