1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
9pub struct HardwareBinding {
10 #[serde(default, skip_serializing_if = "Vec::is_empty")]
12 pub mac_addresses: Vec<String>,
13
14 #[serde(default, skip_serializing_if = "Vec::is_empty")]
16 pub disk_ids: Vec<String>,
17
18 #[serde(default, skip_serializing_if = "Vec::is_empty")]
20 pub hostnames: Vec<String>,
21
22 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
24 pub custom: HashMap<String, Vec<String>>,
25}
26
27impl HardwareBinding {
28 pub fn new() -> Self {
30 Self::default()
31 }
32
33 pub fn with_mac_address(mut self, mac: impl Into<String>) -> Self {
35 self.mac_addresses.push(mac.into().to_uppercase());
36 self
37 }
38
39 pub fn with_mac_addresses(mut self, macs: impl IntoIterator<Item = impl Into<String>>) -> Self {
41 self.mac_addresses
42 .extend(macs.into_iter().map(|m| m.into().to_uppercase()));
43 self
44 }
45
46 pub fn with_disk_id(mut self, disk_id: impl Into<String>) -> Self {
48 self.disk_ids.push(disk_id.into());
49 self
50 }
51
52 pub fn with_hostname(mut self, hostname: impl Into<String>) -> Self {
54 self.hostnames.push(hostname.into().to_lowercase());
55 self
56 }
57
58 pub fn with_custom(mut self, key: impl Into<String>, values: Vec<String>) -> Self {
60 self.custom.insert(key.into(), values);
61 self
62 }
63
64 pub fn is_empty(&self) -> bool {
66 self.mac_addresses.is_empty()
67 && self.disk_ids.is_empty()
68 && self.hostnames.is_empty()
69 && self.custom.is_empty()
70 }
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct LicenseData {
76 pub id: String,
78
79 pub serial: String,
81
82 pub customer_id: String,
84
85 pub product_id: String,
87
88 #[serde(default = "default_version")]
90 pub version: u32,
91
92 pub valid_from: DateTime<Utc>,
94
95 pub valid_until: DateTime<Utc>,
97
98 #[serde(default)]
100 pub features: Vec<String>,
101
102 #[serde(default)]
104 pub hardware_binding: HardwareBinding,
105
106 #[serde(default)]
108 pub max_seats: u32,
109
110 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
112 pub metadata: HashMap<String, String>,
113
114 pub issued_at: DateTime<Utc>,
116}
117
118fn default_version() -> u32 {
119 1
120}
121
122impl LicenseData {
123 pub fn builder() -> LicenseDataBuilder {
125 LicenseDataBuilder::new()
126 }
127
128 pub fn has_feature(&self, feature: &str) -> bool {
130 self.features
131 .iter()
132 .any(|f| f.eq_ignore_ascii_case(feature))
133 }
134
135 pub fn days_remaining(&self) -> i64 {
137 let now = Utc::now();
138 (self.valid_until - now).num_days()
139 }
140
141 pub fn is_time_valid(&self) -> bool {
143 let now = Utc::now();
144 now >= self.valid_from && now <= self.valid_until
145 }
146}
147
148#[derive(Default)]
150pub struct LicenseDataBuilder {
151 id: Option<String>,
152 serial: Option<String>,
153 customer_id: Option<String>,
154 product_id: Option<String>,
155 version: u32,
156 valid_from: Option<DateTime<Utc>>,
157 valid_until: Option<DateTime<Utc>>,
158 features: Vec<String>,
159 hardware_binding: HardwareBinding,
160 max_seats: u32,
161 metadata: HashMap<String, String>,
162}
163
164impl LicenseDataBuilder {
165 pub fn new() -> Self {
167 Self {
168 version: 1,
169 ..Default::default()
170 }
171 }
172
173 pub fn id(mut self, id: impl Into<String>) -> Self {
175 self.id = Some(id.into());
176 self
177 }
178
179 pub fn serial(mut self, serial: impl Into<String>) -> Self {
181 self.serial = Some(serial.into());
182 self
183 }
184
185 pub fn customer_id(mut self, customer_id: impl Into<String>) -> Self {
187 self.customer_id = Some(customer_id.into());
188 self
189 }
190
191 pub fn product_id(mut self, product_id: impl Into<String>) -> Self {
193 self.product_id = Some(product_id.into());
194 self
195 }
196
197 pub fn version(mut self, version: u32) -> Self {
199 self.version = version;
200 self
201 }
202
203 pub fn valid_from(mut self, valid_from: DateTime<Utc>) -> Self {
205 self.valid_from = Some(valid_from);
206 self
207 }
208
209 pub fn valid_until(mut self, valid_until: DateTime<Utc>) -> Self {
211 self.valid_until = Some(valid_until);
212 self
213 }
214
215 pub fn valid_days(mut self, days: i64) -> Self {
217 let now = Utc::now();
218 self.valid_from = Some(now);
219 self.valid_until = Some(now + chrono::Duration::days(days));
220 self
221 }
222
223 pub fn feature(mut self, feature: impl Into<String>) -> Self {
225 self.features.push(feature.into());
226 self
227 }
228
229 pub fn features(mut self, features: impl IntoIterator<Item = impl Into<String>>) -> Self {
231 self.features.extend(features.into_iter().map(|f| f.into()));
232 self
233 }
234
235 pub fn hardware_binding(mut self, binding: HardwareBinding) -> Self {
237 self.hardware_binding = binding;
238 self
239 }
240
241 pub fn max_seats(mut self, max_seats: u32) -> Self {
243 self.max_seats = max_seats;
244 self
245 }
246
247 pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
249 self.metadata.insert(key.into(), value.into());
250 self
251 }
252
253 pub fn build(self) -> crate::Result<LicenseData> {
255 use crate::error::LicenseError;
256
257 let now = Utc::now();
258
259 Ok(LicenseData {
260 id: self
261 .id
262 .ok_or_else(|| LicenseError::MissingField("id".into()))?,
263 serial: self
264 .serial
265 .ok_or_else(|| LicenseError::MissingField("serial".into()))?,
266 customer_id: self
267 .customer_id
268 .ok_or_else(|| LicenseError::MissingField("customer_id".into()))?,
269 product_id: self
270 .product_id
271 .ok_or_else(|| LicenseError::MissingField("product_id".into()))?,
272 version: self.version,
273 valid_from: self.valid_from.unwrap_or(now),
274 valid_until: self
275 .valid_until
276 .unwrap_or(now + chrono::Duration::days(365)),
277 features: self.features,
278 hardware_binding: self.hardware_binding,
279 max_seats: self.max_seats,
280 metadata: self.metadata,
281 issued_at: now,
282 })
283 }
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize)]
288pub struct SignedLicense {
289 pub data: LicenseData,
291
292 pub signature: String,
294
295 pub algorithm: String,
297}
298
299#[derive(Debug, Clone, Copy, PartialEq, Eq)]
301pub enum LicenseFormat {
302 Binary,
304 Json,
306}
307
308pub const BINARY_MAGIC: &[u8; 4] = b"FLIC";
310pub const BINARY_VERSION: u8 = 1;