1use crate::error::{Error, Result};
10
11pub const LICENSE_BLOCK_FIXED_SIZE: usize = 71;
13
14pub mod flags {
16 pub const SEATS_ENFORCED: u8 = 0b0000_0001;
18 pub const EXPIRATION_ENFORCED: u8 = 0b0000_0010;
20 pub const QUERY_LIMITED: u8 = 0b0000_0100;
22 pub const WATERMARKED: u8 = 0b0000_1000;
24 pub const REVOCABLE: u8 = 0b0001_0000;
26 pub const TRANSFERABLE: u8 = 0b0010_0000;
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct LicenseBlock {
33 pub license_id: [u8; 16],
35 pub licensee_hash: [u8; 32],
37 pub issued_at: u64,
39 pub expires_at: u64,
41 pub flags: u8,
43 pub seat_limit: u16,
45 pub query_limit: u32,
47 pub custom_terms: Vec<u8>,
49}
50
51impl LicenseBlock {
52 #[must_use]
54 pub fn new(license_id: [u8; 16], licensee_hash: [u8; 32]) -> Self {
55 Self {
56 license_id,
57 licensee_hash,
58 issued_at: current_unix_time(),
59 expires_at: 0,
60 flags: 0,
61 seat_limit: 0,
62 query_limit: 0,
63 custom_terms: Vec::new(),
64 }
65 }
66
67 #[must_use]
69 pub fn with_expiration(mut self, expires_at: u64) -> Self {
70 self.expires_at = expires_at;
71 self.flags |= flags::EXPIRATION_ENFORCED;
72 self
73 }
74
75 #[must_use]
77 pub fn with_seat_limit(mut self, limit: u16) -> Self {
78 self.seat_limit = limit;
79 self.flags |= flags::SEATS_ENFORCED;
80 self
81 }
82
83 #[must_use]
85 pub fn with_query_limit(mut self, limit: u32) -> Self {
86 self.query_limit = limit;
87 self.flags |= flags::QUERY_LIMITED;
88 self
89 }
90
91 #[must_use]
93 pub fn with_watermark(mut self) -> Self {
94 self.flags |= flags::WATERMARKED;
95 self
96 }
97
98 #[must_use]
100 pub fn with_revocable(mut self) -> Self {
101 self.flags |= flags::REVOCABLE;
102 self
103 }
104
105 #[must_use]
107 pub fn with_transferable(mut self) -> Self {
108 self.flags |= flags::TRANSFERABLE;
109 self
110 }
111
112 #[must_use]
114 pub fn with_custom_terms(mut self, terms: Vec<u8>) -> Self {
115 self.custom_terms = terms;
116 self
117 }
118
119 #[must_use]
121 pub fn size(&self) -> usize {
122 LICENSE_BLOCK_FIXED_SIZE + self.custom_terms.len()
123 }
124
125 #[must_use]
138 pub fn to_bytes(&self) -> Vec<u8> {
139 let mut buf = Vec::with_capacity(self.size() + 4); buf.extend_from_slice(&self.license_id);
143
144 buf.extend_from_slice(&self.licensee_hash);
146
147 buf.extend_from_slice(&self.issued_at.to_le_bytes());
149
150 buf.extend_from_slice(&self.expires_at.to_le_bytes());
152
153 buf.push(self.flags);
155
156 buf.extend_from_slice(&self.seat_limit.to_le_bytes());
158
159 buf.extend_from_slice(&self.query_limit.to_le_bytes());
161
162 #[allow(clippy::cast_possible_truncation)]
165 let terms_len = self.custom_terms.len() as u32;
166 buf.extend_from_slice(&terms_len.to_le_bytes());
167
168 buf.extend_from_slice(&self.custom_terms);
170
171 buf
172 }
173
174 pub fn from_bytes(buf: &[u8]) -> Result<Self> {
180 const MIN_SIZE: usize = LICENSE_BLOCK_FIXED_SIZE + 4;
182
183 if buf.len() < MIN_SIZE {
184 return Err(Error::Format(format!(
185 "License block too small: {} bytes, expected at least {}",
186 buf.len(),
187 MIN_SIZE
188 )));
189 }
190
191 let mut offset = 0;
192
193 let mut license_id = [0u8; 16];
195 license_id.copy_from_slice(&buf[offset..offset + 16]);
196 offset += 16;
197
198 let mut licensee_hash = [0u8; 32];
200 licensee_hash.copy_from_slice(&buf[offset..offset + 32]);
201 offset += 32;
202
203 let issued_at = u64::from_le_bytes([
205 buf[offset],
206 buf[offset + 1],
207 buf[offset + 2],
208 buf[offset + 3],
209 buf[offset + 4],
210 buf[offset + 5],
211 buf[offset + 6],
212 buf[offset + 7],
213 ]);
214 offset += 8;
215
216 let expires_at = u64::from_le_bytes([
218 buf[offset],
219 buf[offset + 1],
220 buf[offset + 2],
221 buf[offset + 3],
222 buf[offset + 4],
223 buf[offset + 5],
224 buf[offset + 6],
225 buf[offset + 7],
226 ]);
227 offset += 8;
228
229 let flags = buf[offset];
231 offset += 1;
232
233 let seat_limit = u16::from_le_bytes([buf[offset], buf[offset + 1]]);
235 offset += 2;
236
237 let query_limit = u32::from_le_bytes([
239 buf[offset],
240 buf[offset + 1],
241 buf[offset + 2],
242 buf[offset + 3],
243 ]);
244 offset += 4;
245
246 let terms_len = u32::from_le_bytes([
248 buf[offset],
249 buf[offset + 1],
250 buf[offset + 2],
251 buf[offset + 3],
252 ]) as usize;
253 offset += 4;
254
255 if buf.len() < offset + terms_len {
257 return Err(Error::Format(format!(
258 "License block truncated: expected {} bytes for custom terms",
259 terms_len
260 )));
261 }
262
263 let custom_terms = buf[offset..offset + terms_len].to_vec();
264
265 Ok(Self {
266 license_id,
267 licensee_hash,
268 issued_at,
269 expires_at,
270 flags,
271 seat_limit,
272 query_limit,
273 custom_terms,
274 })
275 }
276
277 #[must_use]
279 pub const fn is_expiration_enforced(&self) -> bool {
280 self.flags & flags::EXPIRATION_ENFORCED != 0
281 }
282
283 #[must_use]
285 pub const fn is_seats_enforced(&self) -> bool {
286 self.flags & flags::SEATS_ENFORCED != 0
287 }
288
289 #[must_use]
291 pub const fn is_query_limited(&self) -> bool {
292 self.flags & flags::QUERY_LIMITED != 0
293 }
294
295 #[must_use]
297 pub const fn is_watermarked(&self) -> bool {
298 self.flags & flags::WATERMARKED != 0
299 }
300
301 #[must_use]
303 pub const fn is_revocable(&self) -> bool {
304 self.flags & flags::REVOCABLE != 0
305 }
306
307 #[must_use]
309 pub const fn is_transferable(&self) -> bool {
310 self.flags & flags::TRANSFERABLE != 0
311 }
312
313 pub fn verify(&self) -> Result<()> {
319 if self.is_expiration_enforced() && self.expires_at > 0 {
320 let now = current_unix_time();
321 if now > self.expires_at {
322 return Err(Error::LicenseExpired {
323 expired_at: self.expires_at,
324 current_time: now,
325 });
326 }
327 }
328 Ok(())
329 }
330
331 #[must_use]
333 pub fn license_id_string(&self) -> String {
334 format!(
335 "{:08x}-{:04x}-{:04x}-{:04x}-{:012x}",
336 u32::from_be_bytes([
337 self.license_id[0],
338 self.license_id[1],
339 self.license_id[2],
340 self.license_id[3]
341 ]),
342 u16::from_be_bytes([self.license_id[4], self.license_id[5]]),
343 u16::from_be_bytes([self.license_id[6], self.license_id[7]]),
344 u16::from_be_bytes([self.license_id[8], self.license_id[9]]),
345 u64::from_be_bytes([
346 0,
347 0,
348 self.license_id[10],
349 self.license_id[11],
350 self.license_id[12],
351 self.license_id[13],
352 self.license_id[14],
353 self.license_id[15]
354 ])
355 )
356 }
357}
358
359fn current_unix_time() -> u64 {
361 use std::time::{SystemTime, UNIX_EPOCH};
362
363 SystemTime::now()
364 .duration_since(UNIX_EPOCH)
365 .map(|d| d.as_secs())
366 .unwrap_or(0)
367}
368
369#[cfg(feature = "format-encryption")]
375pub fn generate_license_id() -> Result<[u8; 16]> {
376 let mut id = [0u8; 16];
377 getrandom::getrandom(&mut id).map_err(|e| Error::Format(format!("RNG error: {e}")))?;
378
379 id[6] = (id[6] & 0x0F) | 0x40; id[8] = (id[8] & 0x3F) | 0x80; Ok(id)
384}
385
386#[cfg(feature = "format-encryption")]
391pub fn hash_licensee(identifier: &str) -> [u8; 32] {
392 use sha2::{Digest, Sha256};
393
394 let mut hasher = Sha256::new();
395 hasher.update(identifier.as_bytes());
396 let result = hasher.finalize();
397
398 let mut hash = [0u8; 32];
399 hash.copy_from_slice(&result);
400 hash
401}
402
403#[derive(Debug)]
405pub struct LicenseBuilder {
406 license_id: [u8; 16],
407 licensee_hash: [u8; 32],
408 expires_at: Option<u64>,
409 seat_limit: Option<u16>,
410 query_limit: Option<u32>,
411 watermarked: bool,
412 revocable: bool,
413 transferable: bool,
414 custom_terms: Option<Vec<u8>>,
415}
416
417impl LicenseBuilder {
418 #[must_use]
420 pub fn new(license_id: [u8; 16], licensee_hash: [u8; 32]) -> Self {
421 Self {
422 license_id,
423 licensee_hash,
424 expires_at: None,
425 seat_limit: None,
426 query_limit: None,
427 watermarked: false,
428 revocable: false,
429 transferable: false,
430 custom_terms: None,
431 }
432 }
433
434 #[must_use]
436 pub fn expires_at(mut self, timestamp: u64) -> Self {
437 self.expires_at = Some(timestamp);
438 self
439 }
440
441 #[must_use]
443 pub fn expires_in(mut self, seconds: u64) -> Self {
444 self.expires_at = Some(current_unix_time() + seconds);
445 self
446 }
447
448 #[must_use]
450 pub fn seat_limit(mut self, limit: u16) -> Self {
451 self.seat_limit = Some(limit);
452 self
453 }
454
455 #[must_use]
457 pub fn query_limit(mut self, limit: u32) -> Self {
458 self.query_limit = Some(limit);
459 self
460 }
461
462 #[must_use]
464 pub fn watermarked(mut self) -> Self {
465 self.watermarked = true;
466 self
467 }
468
469 #[must_use]
471 pub fn revocable(mut self) -> Self {
472 self.revocable = true;
473 self
474 }
475
476 #[must_use]
478 pub fn transferable(mut self) -> Self {
479 self.transferable = true;
480 self
481 }
482
483 #[must_use]
485 pub fn custom_terms(mut self, terms: Vec<u8>) -> Self {
486 self.custom_terms = Some(terms);
487 self
488 }
489
490 #[must_use]
492 pub fn build(self) -> LicenseBlock {
493 let mut license = LicenseBlock::new(self.license_id, self.licensee_hash);
494
495 if let Some(expires) = self.expires_at {
496 license = license.with_expiration(expires);
497 }
498
499 if let Some(seats) = self.seat_limit {
500 license = license.with_seat_limit(seats);
501 }
502
503 if let Some(queries) = self.query_limit {
504 license = license.with_query_limit(queries);
505 }
506
507 if self.watermarked {
508 license = license.with_watermark();
509 }
510
511 if self.revocable {
512 license = license.with_revocable();
513 }
514
515 if self.transferable {
516 license = license.with_transferable();
517 }
518
519 if let Some(terms) = self.custom_terms {
520 license = license.with_custom_terms(terms);
521 }
522
523 license
524 }
525}
526
527#[cfg(test)]
528mod tests {
529 use super::*;
530
531 #[test]
532 fn test_license_block_roundtrip() {
533 let license_id = [1u8; 16];
534 let licensee_hash = [2u8; 32];
535
536 let license = LicenseBlock::new(license_id, licensee_hash)
537 .with_expiration(1_800_000_000)
538 .with_seat_limit(5)
539 .with_query_limit(10_000)
540 .with_watermark()
541 .with_custom_terms(b"Custom license terms".to_vec());
542
543 let bytes = license.to_bytes();
544 let restored = LicenseBlock::from_bytes(&bytes).expect("parse failed");
545
546 assert_eq!(restored.license_id, license_id);
547 assert_eq!(restored.licensee_hash, licensee_hash);
548 assert_eq!(restored.expires_at, 1_800_000_000);
549 assert_eq!(restored.seat_limit, 5);
550 assert_eq!(restored.query_limit, 10_000);
551 assert!(restored.is_expiration_enforced());
552 assert!(restored.is_seats_enforced());
553 assert!(restored.is_query_limited());
554 assert!(restored.is_watermarked());
555 assert!(!restored.is_revocable());
556 assert!(!restored.is_transferable());
557 assert_eq!(restored.custom_terms, b"Custom license terms");
558 }
559
560 #[test]
561 fn test_license_block_minimal() {
562 let license_id = [0u8; 16];
563 let licensee_hash = [0u8; 32];
564
565 let license = LicenseBlock::new(license_id, licensee_hash);
566 let bytes = license.to_bytes();
567 let restored = LicenseBlock::from_bytes(&bytes).expect("parse failed");
568
569 assert_eq!(restored.flags, 0);
570 assert_eq!(restored.seat_limit, 0);
571 assert_eq!(restored.query_limit, 0);
572 assert!(restored.custom_terms.is_empty());
573 }
574
575 #[test]
576 fn test_license_expiration_check() {
577 let license_id = [1u8; 16];
578 let licensee_hash = [2u8; 32];
579
580 let future_time = current_unix_time() + 3600; let valid_license =
583 LicenseBlock::new(license_id, licensee_hash).with_expiration(future_time);
584 assert!(valid_license.verify().is_ok());
585
586 let past_time = current_unix_time() - 3600; let expired_license =
589 LicenseBlock::new(license_id, licensee_hash).with_expiration(past_time);
590 assert!(expired_license.verify().is_err());
591 }
592
593 #[test]
594 fn test_license_no_expiration() {
595 let license_id = [1u8; 16];
596 let licensee_hash = [2u8; 32];
597
598 let license = LicenseBlock::new(license_id, licensee_hash);
600 assert!(license.verify().is_ok());
601 }
602
603 #[test]
604 fn test_license_builder() {
605 let license_id = [3u8; 16];
606 let licensee_hash = [4u8; 32];
607
608 let license = LicenseBuilder::new(license_id, licensee_hash)
609 .expires_in(86400) .seat_limit(10)
611 .query_limit(50_000)
612 .watermarked()
613 .revocable()
614 .build();
615
616 assert!(license.is_expiration_enforced());
617 assert!(license.is_seats_enforced());
618 assert!(license.is_query_limited());
619 assert!(license.is_watermarked());
620 assert!(license.is_revocable());
621 assert!(!license.is_transferable());
622 assert_eq!(license.seat_limit, 10);
623 assert_eq!(license.query_limit, 50_000);
624 }
625
626 #[test]
627 fn test_license_id_string() {
628 let license_id = [
629 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, ];
635 let licensee_hash = [0u8; 32];
636
637 let license = LicenseBlock::new(license_id, licensee_hash);
638 let uuid_str = license.license_id_string();
639
640 assert_eq!(uuid_str, "12345678-9abc-def0-1234-56789abcdef0");
641 }
642
643 #[test]
644 fn test_all_flags() {
645 let license_id = [5u8; 16];
646 let licensee_hash = [6u8; 32];
647
648 let license = LicenseBlock::new(license_id, licensee_hash)
649 .with_expiration(1_900_000_000)
650 .with_seat_limit(1)
651 .with_query_limit(1)
652 .with_watermark()
653 .with_revocable()
654 .with_transferable();
655
656 let expected_flags = flags::EXPIRATION_ENFORCED
657 | flags::SEATS_ENFORCED
658 | flags::QUERY_LIMITED
659 | flags::WATERMARKED
660 | flags::REVOCABLE
661 | flags::TRANSFERABLE;
662
663 assert_eq!(license.flags, expected_flags);
664
665 assert!(license.is_expiration_enforced());
667 assert!(license.is_seats_enforced());
668 assert!(license.is_query_limited());
669 assert!(license.is_watermarked());
670 assert!(license.is_revocable());
671 assert!(license.is_transferable());
672 }
673
674 #[test]
675 fn test_buffer_too_small() {
676 let small_buf = [0u8; 10];
677 let result = LicenseBlock::from_bytes(&small_buf);
678 assert!(result.is_err());
679 assert!(result.unwrap_err().to_string().contains("too small"));
680 }
681
682 #[cfg(feature = "format-encryption")]
683 #[test]
684 fn test_generate_license_id() {
685 let id1 = generate_license_id().expect("generate failed");
686 let id2 = generate_license_id().expect("generate failed");
687
688 assert_ne!(id1, id2);
690
691 assert_eq!((id1[6] >> 4) & 0x0F, 4);
693 assert_eq!((id2[6] >> 4) & 0x0F, 4);
694
695 assert_eq!((id1[8] >> 6) & 0x03, 2);
697 assert_eq!((id2[8] >> 6) & 0x03, 2);
698 }
699
700 #[cfg(feature = "format-encryption")]
701 #[test]
702 fn test_hash_licensee() {
703 let hash1 = hash_licensee("user@example.com");
704 let hash2 = hash_licensee("user@example.com");
705 let hash3 = hash_licensee("other@example.com");
706
707 assert_eq!(hash1, hash2);
709
710 assert_ne!(hash1, hash3);
712
713 assert_eq!(hash1.len(), 32);
715 }
716}