1use std::fs::{File, OpenOptions};
12use std::io::{Read, Seek, SeekFrom, Write};
13use std::path::{Path, PathBuf};
14
15use serde::{Deserialize, Serialize};
16
17use crate::alphabet::Alphabet;
18use crate::audit::{AuditInfo, GenerationLogEntry, History};
19use crate::counter::{CounterManager, CounterMode, counter_from_bytes, counter_to_bytes};
20use crate::damm::DammTable;
21use crate::encryption::{
22 ENCRYPTED_KEY_SIZE, NONCE_SIZE, SALT_SIZE, decrypt_code_from_storage, decrypt_data,
23 decrypt_data_key, encrypt_code_for_storage, encrypt_data, encrypt_data_key, generate_nonce,
24 generate_random_key, generate_salt, hash_secret,
25};
26use crate::error::{PromocryptError, Result, ValidationResult};
27use crate::generator::{CheckPosition, CodeFormat, generate_batch, generate_code};
28use crate::machine_id::get_machine_id;
29
30const MAGIC: &[u8; 8] = b"PROMOCRY";
32const VERSION: u8 = 0x02;
33
34const OFFSET_MAGIC: usize = 0;
36const OFFSET_VERSION: usize = 8;
37const OFFSET_FLAGS: usize = 9;
38const OFFSET_HEADER_CRC: usize = 10;
39const OFFSET_SALT: usize = 14;
40const OFFSET_MACHINE_NONCE: usize = 30;
41const OFFSET_MACHINE_KEY: usize = 42;
42const OFFSET_SECRET_NONCE: usize = 90;
43const OFFSET_SECRET_KEY: usize = 102;
44const OFFSET_DATA_NONCE: usize = 150;
45const OFFSET_DATA_LENGTH: usize = 162;
46const OFFSET_DATA: usize = 166;
47
48const HEADER_SIZE: usize = OFFSET_DATA;
50
51const FLAG_IS_BOUND: u8 = 0x01;
53const FLAG_HAS_MUTABLE: u8 = 0x02;
54
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57pub enum AccessLevel {
58 ReadOnly,
60 FullAccess,
62}
63
64#[derive(Clone, Debug, Serialize, Deserialize)]
66pub struct BinConfig {
67 pub name: String,
69 pub alphabet: Alphabet,
71 pub code_length: usize,
73 pub check_position: CheckPosition,
75 pub secret_key: String,
77 #[serde(default, skip_serializing_if = "String::is_empty")]
79 pub storage_key: String,
80 #[serde(default)]
82 pub storage_encryption_enabled: bool,
83 pub damm_table: DammTable,
85 pub counter_mode: CounterMode,
87 #[serde(default, skip_serializing_if = "CodeFormat::is_default")]
89 pub format: CodeFormat,
90 pub audit: AuditInfo,
92}
93
94impl CodeFormat {
95 pub fn is_default(&self) -> bool {
97 self.prefix.is_none()
98 && self.suffix.is_none()
99 && self.separator.is_none()
100 && self.separator_positions.is_empty()
101 }
102}
103
104impl BinConfig {
105 pub fn base_code_length(&self) -> usize {
107 self.code_length + 1
108 }
109
110 pub fn total_code_length(&self) -> usize {
112 self.format.total_length(self.base_code_length())
113 }
114}
115
116pub struct BinFile {
118 path: Option<PathBuf>,
120 raw_data: Option<Vec<u8>>,
122 config: BinConfig,
124 data_key: [u8; 32],
126 secret_key: [u8; 32],
128 storage_key: [u8; 32],
130 counter: CounterManager,
132 access_level: AccessLevel,
134 salt: [u8; SALT_SIZE],
136 is_bound: bool,
138 has_mutable: bool,
140 current_machine_id: Option<[u8; 32]>,
142}
143
144impl BinFile {
145 pub fn create(path: impl AsRef<Path>, secret: &str, mut config: BinConfig) -> Result<Self> {
176 let path = path.as_ref();
177
178 let machine_id = get_machine_id()?;
180
181 let data_key = generate_random_key()?;
183 let secret_key = generate_random_key()?;
184 let storage_key = generate_random_key()?;
185 let salt = generate_salt()?;
186
187 config.secret_key = hex::encode(secret_key);
189 config.storage_key = hex::encode(storage_key);
190
191 config.audit = AuditInfo::new(&machine_id);
193
194 let config_json = serde_json::to_vec(&config)?;
196
197 let machine_nonce = generate_nonce()?;
199 let secret_nonce = generate_nonce()?;
200 let data_nonce = generate_nonce()?;
201
202 let machine_encrypted_key =
204 encrypt_data_key(&data_key, &machine_id, &salt, &machine_nonce)?;
205 let secret_encrypted_key =
206 encrypt_data_key(&data_key, secret.as_bytes(), &salt, &secret_nonce)?;
207
208 let encrypted_data = encrypt_data(&data_key, &config_json, &data_nonce)?;
210
211 let flags = FLAG_IS_BOUND; let has_mutable = config.counter_mode.is_in_bin();
214
215 let mut file_data = Vec::new();
216
217 file_data.extend_from_slice(MAGIC);
219 file_data.push(VERSION);
220 file_data.push(flags | if has_mutable { FLAG_HAS_MUTABLE } else { 0 });
221
222 let crc_offset = file_data.len();
224 file_data.extend_from_slice(&[0u8; 4]);
225
226 file_data.extend_from_slice(&salt);
228 file_data.extend_from_slice(&machine_nonce);
229 file_data.extend_from_slice(&machine_encrypted_key);
230 file_data.extend_from_slice(&secret_nonce);
231 file_data.extend_from_slice(&secret_encrypted_key);
232
233 file_data.extend_from_slice(&data_nonce);
235 file_data.extend_from_slice(&(encrypted_data.len() as u32).to_le_bytes());
236 file_data.extend_from_slice(&encrypted_data);
237
238 if has_mutable {
240 let mutable_nonce = generate_nonce()?;
241 let counter_bytes = counter_to_bytes(0);
242 let encrypted_counter = encrypt_data(&data_key, &counter_bytes, &mutable_nonce)?;
243
244 file_data.extend_from_slice(&mutable_nonce);
245 file_data.extend_from_slice(&(encrypted_counter.len() as u32).to_le_bytes());
246 file_data.extend_from_slice(&encrypted_counter);
247 }
248
249 let crc = crc32fast::hash(&file_data[0..10]);
251 file_data[crc_offset..crc_offset + 4].copy_from_slice(&crc.to_le_bytes());
252
253 let mut file = File::create(path)?;
255 file.write_all(&file_data)?;
256 file.sync_all()?;
257
258 let counter = CounterManager::new(config.counter_mode.clone());
260
261 Ok(Self {
262 path: Some(path.to_path_buf()),
263 raw_data: Some(file_data), config,
265 data_key,
266 secret_key,
267 storage_key,
268 counter,
269 access_level: AccessLevel::FullAccess,
270 salt,
271 is_bound: true,
272 has_mutable,
273 current_machine_id: Some(machine_id),
274 })
275 }
276
277 pub fn create_in_memory(secret: &str, config: BinConfig) -> Result<Vec<u8>> {
279 let temp_path =
281 std::env::temp_dir().join(format!("promocrypt_{}.bin", rand::random::<u64>()));
282 let bin = Self::create(&temp_path, secret, config)?;
283
284 let data = std::fs::read(&temp_path)?;
286
287 std::fs::remove_file(&temp_path).ok();
289
290 drop(bin);
291 Ok(data)
292 }
293
294 pub fn open_readonly(path: impl AsRef<Path>) -> Result<Self> {
298 let path = path.as_ref();
299 let data = std::fs::read(path)
300 .map_err(|_| PromocryptError::FileNotFound(path.display().to_string()))?;
301
302 let machine_id = get_machine_id()?;
303 Self::open_from_bytes_internal(&data, Some(&machine_id), None, Some(path.to_path_buf()))
304 }
305
306 pub fn open_with_secret(path: impl AsRef<Path>, secret: &str) -> Result<Self> {
308 let path = path.as_ref();
309 let data = std::fs::read(path)
310 .map_err(|_| PromocryptError::FileNotFound(path.display().to_string()))?;
311
312 let machine_id = get_machine_id().ok();
314 Self::open_from_bytes_internal(
315 &data,
316 machine_id.as_ref(),
317 Some(secret),
318 Some(path.to_path_buf()),
319 )
320 }
321
322 pub fn from_bytes_readonly(data: &[u8]) -> Result<Self> {
324 let machine_id = get_machine_id()?;
325 Self::open_from_bytes_internal(data, Some(&machine_id), None, None)
326 }
327
328 pub fn from_bytes_with_secret(data: &[u8], secret: &str) -> Result<Self> {
330 let machine_id = get_machine_id().ok();
332 Self::open_from_bytes_internal(data, machine_id.as_ref(), Some(secret), None)
333 }
334
335 fn open_from_bytes_internal(
337 data: &[u8],
338 machine_id: Option<&[u8; 32]>,
339 secret: Option<&str>,
340 path: Option<PathBuf>,
341 ) -> Result<Self> {
342 if data.len() < HEADER_SIZE {
344 return Err(PromocryptError::InvalidFileFormat);
345 }
346
347 if &data[OFFSET_MAGIC..OFFSET_MAGIC + 8] != MAGIC {
349 return Err(PromocryptError::InvalidFileFormat);
350 }
351
352 let version = data[OFFSET_VERSION];
354 if version != VERSION {
355 return Err(PromocryptError::UnsupportedVersion(version));
356 }
357
358 let flags = data[OFFSET_FLAGS];
360 let is_bound = (flags & FLAG_IS_BOUND) != 0;
361 let has_mutable = (flags & FLAG_HAS_MUTABLE) != 0;
362
363 let stored_crc = u32::from_le_bytes([
365 data[OFFSET_HEADER_CRC],
366 data[OFFSET_HEADER_CRC + 1],
367 data[OFFSET_HEADER_CRC + 2],
368 data[OFFSET_HEADER_CRC + 3],
369 ]);
370 let computed_crc = crc32fast::hash(&data[0..10]);
371 if stored_crc != computed_crc {
372 return Err(PromocryptError::HeaderChecksumMismatch);
373 }
374
375 let mut salt = [0u8; SALT_SIZE];
377 salt.copy_from_slice(&data[OFFSET_SALT..OFFSET_SALT + SALT_SIZE]);
378
379 let (data_key, access_level) = if let Some(secret) = secret {
381 let mut secret_nonce = [0u8; NONCE_SIZE];
383 secret_nonce
384 .copy_from_slice(&data[OFFSET_SECRET_NONCE..OFFSET_SECRET_NONCE + NONCE_SIZE]);
385
386 let mut secret_encrypted = [0u8; ENCRYPTED_KEY_SIZE];
387 secret_encrypted
388 .copy_from_slice(&data[OFFSET_SECRET_KEY..OFFSET_SECRET_KEY + ENCRYPTED_KEY_SIZE]);
389
390 let data_key =
391 decrypt_data_key(&secret_encrypted, secret.as_bytes(), &salt, &secret_nonce)?;
392 (data_key, AccessLevel::FullAccess)
393 } else if let Some(machine_id) = machine_id {
394 if !is_bound {
396 return Err(PromocryptError::MachineMismatch);
397 }
398
399 let mut machine_nonce = [0u8; NONCE_SIZE];
400 machine_nonce
401 .copy_from_slice(&data[OFFSET_MACHINE_NONCE..OFFSET_MACHINE_NONCE + NONCE_SIZE]);
402
403 let mut machine_encrypted = [0u8; ENCRYPTED_KEY_SIZE];
404 machine_encrypted.copy_from_slice(
405 &data[OFFSET_MACHINE_KEY..OFFSET_MACHINE_KEY + ENCRYPTED_KEY_SIZE],
406 );
407
408 let data_key = decrypt_data_key(&machine_encrypted, machine_id, &salt, &machine_nonce)
409 .map_err(|_| PromocryptError::MachineMismatch)?;
410 (data_key, AccessLevel::ReadOnly)
411 } else {
412 return Err(PromocryptError::InvalidArgument(
413 "Either machine_id or secret must be provided".to_string(),
414 ));
415 };
416
417 let mut data_nonce = [0u8; NONCE_SIZE];
419 data_nonce.copy_from_slice(&data[OFFSET_DATA_NONCE..OFFSET_DATA_NONCE + NONCE_SIZE]);
420
421 let data_length = u32::from_le_bytes([
422 data[OFFSET_DATA_LENGTH],
423 data[OFFSET_DATA_LENGTH + 1],
424 data[OFFSET_DATA_LENGTH + 2],
425 data[OFFSET_DATA_LENGTH + 3],
426 ]) as usize;
427
428 let data_end = OFFSET_DATA + data_length;
429 if data.len() < data_end {
430 return Err(PromocryptError::InvalidFileFormat);
431 }
432
433 let encrypted_config = &data[OFFSET_DATA..data_end];
434 let config_json = decrypt_data(&data_key, encrypted_config, &data_nonce)?;
435 let config: BinConfig = serde_json::from_slice(&config_json)?;
436
437 let secret_key_bytes = hex::decode(&config.secret_key).map_err(|_| {
439 PromocryptError::InvalidFileFormatDetails("Invalid secret key hex".to_string())
440 })?;
441 if secret_key_bytes.len() != 32 {
442 return Err(PromocryptError::InvalidFileFormatDetails(
443 "Secret key must be 32 bytes".to_string(),
444 ));
445 }
446 let mut secret_key = [0u8; 32];
447 secret_key.copy_from_slice(&secret_key_bytes);
448
449 let mut storage_key = [0u8; 32];
451 if !config.storage_key.is_empty() {
452 let storage_key_bytes = hex::decode(&config.storage_key).map_err(|_| {
453 PromocryptError::InvalidFileFormatDetails("Invalid storage key hex".to_string())
454 })?;
455 if storage_key_bytes.len() == 32 {
456 storage_key.copy_from_slice(&storage_key_bytes);
457 }
458 }
459
460 let mut counter_value = 0u64;
462 if has_mutable && data.len() > data_end {
463 let mutable_offset = data_end;
464 if data.len() >= mutable_offset + NONCE_SIZE + 4 {
465 let mut mutable_nonce = [0u8; NONCE_SIZE];
466 mutable_nonce.copy_from_slice(&data[mutable_offset..mutable_offset + NONCE_SIZE]);
467
468 let mutable_length = u32::from_le_bytes([
469 data[mutable_offset + NONCE_SIZE],
470 data[mutable_offset + NONCE_SIZE + 1],
471 data[mutable_offset + NONCE_SIZE + 2],
472 data[mutable_offset + NONCE_SIZE + 3],
473 ]) as usize;
474
475 let mutable_start = mutable_offset + NONCE_SIZE + 4;
476 let mutable_end = mutable_start + mutable_length;
477
478 if data.len() >= mutable_end {
479 let encrypted_counter = &data[mutable_start..mutable_end];
480 if let Ok(counter_bytes) =
481 decrypt_data(&data_key, encrypted_counter, &mutable_nonce)
482 && let Ok(val) = counter_from_bytes(&counter_bytes)
483 {
484 counter_value = val;
485 }
486 }
487 }
488 }
489
490 let counter = CounterManager::with_in_bin_value(config.counter_mode.clone(), counter_value);
491
492 Ok(Self {
493 path,
494 raw_data: Some(data.to_vec()),
495 config,
496 data_key,
497 secret_key,
498 storage_key,
499 counter,
500 access_level,
501 salt,
502 is_bound,
503 has_mutable,
504 current_machine_id: machine_id.copied(),
505 })
506 }
507
508 pub fn try_open_readonly(path: impl AsRef<Path>) -> Option<Self> {
510 Self::open_readonly(path).ok()
511 }
512
513 pub fn try_open_with_secret(path: impl AsRef<Path>, secret: &str) -> Option<Self> {
515 Self::open_with_secret(path, secret).ok()
516 }
517
518 pub fn config(&self) -> &BinConfig {
522 &self.config
523 }
524
525 pub fn name(&self) -> &str {
527 &self.config.name
528 }
529
530 pub fn alphabet(&self) -> &Alphabet {
532 &self.config.alphabet
533 }
534
535 pub fn code_length(&self) -> usize {
537 self.config.code_length
538 }
539
540 pub fn total_length(&self) -> usize {
542 self.config.code_length + 1
543 }
544
545 pub fn check_position(&self) -> CheckPosition {
547 self.config.check_position
548 }
549
550 pub fn counter_mode(&self) -> &CounterMode {
552 &self.config.counter_mode
553 }
554
555 pub fn audit(&self) -> &AuditInfo {
557 &self.config.audit
558 }
559
560 pub fn access_level(&self) -> AccessLevel {
562 self.access_level
563 }
564
565 pub fn is_bound(&self) -> bool {
567 self.is_bound
568 }
569
570 pub fn format(&self) -> &CodeFormat {
572 &self.config.format
573 }
574
575 pub fn is_storage_encryption_enabled(&self) -> bool {
577 self.config.storage_encryption_enabled
578 }
579
580 pub fn get_history(&self) -> &History {
582 &self.config.audit.history
583 }
584
585 pub fn get_generation_log(&self) -> &[GenerationLogEntry] {
587 self.config.audit.get_generation_log()
588 }
589
590 pub fn total_codes_generated(&self) -> u64 {
592 self.config.audit.generation_count
593 }
594
595 pub fn validate(&self, code: &str) -> ValidationResult {
600 let base_code = if self.config.format.has_formatting() {
602 match self.config.format.strip(code) {
603 Some(c) => c,
604 None => {
605 return ValidationResult::InvalidFormat {
606 reason: "prefix or suffix mismatch".to_string(),
607 };
608 }
609 }
610 } else {
611 code.to_string()
612 };
613
614 crate::validator::validate(
615 &base_code,
616 &self.config.alphabet,
617 self.total_length(),
618 &self.config.damm_table,
619 )
620 }
621
622 pub fn is_valid(&self, code: &str) -> bool {
624 self.validate(code).is_valid()
625 }
626
627 pub fn validate_batch(&self, codes: &[&str]) -> Vec<ValidationResult> {
629 codes.iter().map(|c| self.validate(c)).collect()
630 }
631
632 pub fn generate(&mut self) -> Result<String> {
638 self.require_full_access()?;
639
640 let counter = self.counter.reserve(1)?;
641 let base_code = generate_code(
642 &self.secret_key,
643 counter,
644 &self.config.alphabet,
645 self.config.code_length,
646 self.config.check_position,
647 &self.config.damm_table,
648 );
649
650 let formatted = self.config.format.format(&base_code);
652
653 if let Some(machine_id) = &self.current_machine_id {
655 self.config
656 .audit
657 .record_generation_with_log(machine_id, 1, counter);
658 } else {
659 self.config.audit.record_generation(1);
660 }
661 self.save()?;
663
664 if self.config.storage_encryption_enabled {
666 encrypt_code_for_storage(&self.storage_key, &formatted)
667 } else {
668 Ok(formatted)
669 }
670 }
671
672 pub fn generate_batch(&mut self, count: usize) -> Result<Vec<String>> {
674 self.require_full_access()?;
675
676 let start_counter = self.counter.reserve(count as u64)?;
677 let base_codes = generate_batch(
678 &self.secret_key,
679 start_counter,
680 count,
681 &self.config.alphabet,
682 self.config.code_length,
683 self.config.check_position,
684 &self.config.damm_table,
685 );
686
687 let formatted: Vec<String> = base_codes
689 .iter()
690 .map(|c| self.config.format.format(c))
691 .collect();
692
693 if let Some(machine_id) = &self.current_machine_id {
695 self.config
696 .audit
697 .record_generation_with_log(machine_id, count as u64, start_counter);
698 } else {
699 self.config.audit.record_generation(count as u64);
700 }
701 self.save()?;
703
704 if self.config.storage_encryption_enabled {
706 let code_refs: Vec<&str> = formatted.iter().map(|s| s.as_str()).collect();
707 crate::encryption::encrypt_codes_for_storage(&self.storage_key, &code_refs)
708 } else {
709 Ok(formatted)
710 }
711 }
712
713 pub fn generate_at(&self, counter: u64) -> Result<String> {
715 self.require_full_access()?;
716
717 let base_code = generate_code(
718 &self.secret_key,
719 counter,
720 &self.config.alphabet,
721 self.config.code_length,
722 self.config.check_position,
723 &self.config.damm_table,
724 );
725
726 Ok(self.config.format.format(&base_code))
727 }
728
729 pub fn generate_batch_at(&self, start_counter: u64, count: usize) -> Result<Vec<String>> {
731 self.require_full_access()?;
732
733 let base_codes = generate_batch(
734 &self.secret_key,
735 start_counter,
736 count,
737 &self.config.alphabet,
738 self.config.code_length,
739 self.config.check_position,
740 &self.config.damm_table,
741 );
742
743 Ok(base_codes
744 .iter()
745 .map(|c| self.config.format.format(c))
746 .collect())
747 }
748
749 pub fn get_counter(&self) -> Result<u64> {
753 self.counter.get()
754 }
755
756 pub fn set_counter(&mut self, value: u64) -> Result<()> {
779 self.require_full_access()?;
780
781 let capacity = self.capacity();
783 if value as u128 >= capacity {
784 return Err(PromocryptError::InvalidArgument(format!(
785 "Counter value {} exceeds capacity {}. This would cause duplicate codes.",
786 value, capacity
787 )));
788 }
789
790 self.counter.set(value)?;
791
792 if self.path.is_some() {
794 self.save()?;
795 }
796
797 Ok(())
798 }
799
800 pub fn capacity(&self) -> u128 {
804 let alphabet_size = self.config.alphabet.len();
805 let code_length = self.config.code_length;
806 (alphabet_size as u128).pow(code_length as u32)
807 }
808
809 pub fn reserve_counter_range(&mut self, count: u64) -> Result<u64> {
843 self.require_full_access()?;
844
845 let capacity = self.capacity();
847 let current = self.counter.get().unwrap_or(0);
848 let end = current.saturating_add(count);
849 if end as u128 > capacity {
850 return Err(PromocryptError::InvalidArgument(format!(
851 "Reserving {} codes would exceed capacity. Current: {}, Capacity: {}",
852 count, current, capacity
853 )));
854 }
855
856 let start = self.counter.reserve(count)?;
857
858 if self.path.is_some() {
860 self.save()?;
861 }
862
863 Ok(start)
864 }
865
866 pub fn master_for_machine(
870 &self,
871 output_path: impl AsRef<Path>,
872 target_machine_id: &[u8; 32],
873 ) -> Result<()> {
874 self.require_full_access()?;
875
876 let path = output_path.as_ref();
877
878 let mut new_config = self.config.clone();
880 new_config.audit.record_master(target_machine_id);
881
882 let config_json = serde_json::to_vec(&new_config)?;
884
885 let machine_nonce = generate_nonce()?;
887 let data_nonce = generate_nonce()?;
888
889 let machine_encrypted_key = encrypt_data_key(
891 &self.data_key,
892 target_machine_id,
893 &self.salt,
894 &machine_nonce,
895 )?;
896
897 let raw_data = self.raw_data.as_ref().ok_or_else(|| {
899 PromocryptError::InvalidArgument("No raw data available for mastering".to_string())
900 })?;
901
902 let mut secret_nonce = [0u8; NONCE_SIZE];
903 secret_nonce
904 .copy_from_slice(&raw_data[OFFSET_SECRET_NONCE..OFFSET_SECRET_NONCE + NONCE_SIZE]);
905
906 let mut secret_encrypted = [0u8; ENCRYPTED_KEY_SIZE];
907 secret_encrypted
908 .copy_from_slice(&raw_data[OFFSET_SECRET_KEY..OFFSET_SECRET_KEY + ENCRYPTED_KEY_SIZE]);
909
910 let encrypted_data = encrypt_data(&self.data_key, &config_json, &data_nonce)?;
912
913 let flags = FLAG_IS_BOUND
915 | if self.has_mutable {
916 FLAG_HAS_MUTABLE
917 } else {
918 0
919 };
920
921 let mut file_data = Vec::new();
922
923 file_data.extend_from_slice(MAGIC);
925 file_data.push(VERSION);
926 file_data.push(flags);
927
928 let crc_offset = file_data.len();
930 file_data.extend_from_slice(&[0u8; 4]);
931
932 file_data.extend_from_slice(&self.salt);
934 file_data.extend_from_slice(&machine_nonce);
935 file_data.extend_from_slice(&machine_encrypted_key);
936 file_data.extend_from_slice(&secret_nonce);
937 file_data.extend_from_slice(&secret_encrypted);
938
939 file_data.extend_from_slice(&data_nonce);
941 file_data.extend_from_slice(&(encrypted_data.len() as u32).to_le_bytes());
942 file_data.extend_from_slice(&encrypted_data);
943
944 if self.has_mutable {
946 let mutable_nonce = generate_nonce()?;
947 let counter_bytes = counter_to_bytes(self.counter.in_bin_value());
948 let encrypted_counter = encrypt_data(&self.data_key, &counter_bytes, &mutable_nonce)?;
949
950 file_data.extend_from_slice(&mutable_nonce);
951 file_data.extend_from_slice(&(encrypted_counter.len() as u32).to_le_bytes());
952 file_data.extend_from_slice(&encrypted_counter);
953 }
954
955 let crc = crc32fast::hash(&file_data[0..10]);
957 file_data[crc_offset..crc_offset + 4].copy_from_slice(&crc.to_le_bytes());
958
959 let mut file = File::create(path)?;
961 file.write_all(&file_data)?;
962 file.sync_all()?;
963
964 Ok(())
965 }
966
967 pub fn export_unbound(&self, output_path: impl AsRef<Path>) -> Result<()> {
969 self.require_full_access()?;
970
971 let path = output_path.as_ref();
972
973 let mut new_config = self.config.clone();
975 new_config.audit.mastered_at = None;
976 new_config.audit.mastered_for = None;
977
978 let config_json = serde_json::to_vec(&new_config)?;
979
980 let data_nonce = generate_nonce()?;
982
983 let raw_data = self
985 .raw_data
986 .as_ref()
987 .ok_or_else(|| PromocryptError::InvalidArgument("No raw data available".to_string()))?;
988
989 let mut secret_nonce = [0u8; NONCE_SIZE];
990 secret_nonce
991 .copy_from_slice(&raw_data[OFFSET_SECRET_NONCE..OFFSET_SECRET_NONCE + NONCE_SIZE]);
992
993 let mut secret_encrypted = [0u8; ENCRYPTED_KEY_SIZE];
994 secret_encrypted
995 .copy_from_slice(&raw_data[OFFSET_SECRET_KEY..OFFSET_SECRET_KEY + ENCRYPTED_KEY_SIZE]);
996
997 let encrypted_data = encrypt_data(&self.data_key, &config_json, &data_nonce)?;
999
1000 let flags = if self.has_mutable {
1002 FLAG_HAS_MUTABLE
1003 } else {
1004 0
1005 };
1006
1007 let mut file_data = Vec::new();
1008
1009 file_data.extend_from_slice(MAGIC);
1011 file_data.push(VERSION);
1012 file_data.push(flags);
1013
1014 let crc_offset = file_data.len();
1016 file_data.extend_from_slice(&[0u8; 4]);
1017
1018 file_data.extend_from_slice(&self.salt);
1020 file_data.extend_from_slice(&[0u8; NONCE_SIZE]); file_data.extend_from_slice(&[0u8; ENCRYPTED_KEY_SIZE]); file_data.extend_from_slice(&secret_nonce);
1023 file_data.extend_from_slice(&secret_encrypted);
1024
1025 file_data.extend_from_slice(&data_nonce);
1027 file_data.extend_from_slice(&(encrypted_data.len() as u32).to_le_bytes());
1028 file_data.extend_from_slice(&encrypted_data);
1029
1030 let crc = crc32fast::hash(&file_data[0..10]);
1032 file_data[crc_offset..crc_offset + 4].copy_from_slice(&crc.to_le_bytes());
1033
1034 let mut file = File::create(path)?;
1036 file.write_all(&file_data)?;
1037 file.sync_all()?;
1038
1039 Ok(())
1040 }
1041
1042 pub fn encrypt_code(&self, code: &str) -> Result<String> {
1046 self.require_full_access()?;
1047 encrypt_code_for_storage(&self.storage_key, code)
1048 }
1049
1050 pub fn decrypt_code(&self, encrypted: &str) -> Result<String> {
1052 self.require_full_access()?;
1053 decrypt_code_from_storage(&self.storage_key, encrypted)
1054 }
1055
1056 pub fn encrypt_codes(&self, codes: &[&str]) -> Result<Vec<String>> {
1058 self.require_full_access()?;
1059 crate::encryption::encrypt_codes_for_storage(&self.storage_key, codes)
1060 }
1061
1062 pub fn decrypt_codes(&self, encrypted: &[&str]) -> Result<Vec<String>> {
1064 self.require_full_access()?;
1065 crate::encryption::decrypt_codes_from_storage(&self.storage_key, encrypted)
1066 }
1067
1068 pub fn set_storage_encryption(&mut self, enabled: bool) -> Result<()> {
1070 self.require_full_access()?;
1071
1072 let old_value = self.config.storage_encryption_enabled.to_string();
1073 let new_value = enabled.to_string();
1074
1075 if let Some(machine_id) = &self.current_machine_id {
1076 self.config.audit.history.record_config_change(
1077 machine_id,
1078 "storage_encryption_enabled",
1079 Some(&old_value),
1080 Some(&new_value),
1081 );
1082 }
1083
1084 self.config.storage_encryption_enabled = enabled;
1085 Ok(())
1086 }
1087
1088 pub fn set_counter_mode(&mut self, mode: CounterMode) -> Result<()> {
1092 self.require_full_access()?;
1093
1094 self.config.counter_mode = mode.clone();
1095 self.counter.set_mode(mode);
1096 self.save_if_needed()?;
1097
1098 Ok(())
1099 }
1100
1101 pub fn set_format(&mut self, format: CodeFormat) -> Result<()> {
1103 self.require_full_access()?;
1104
1105 if let Some(machine_id) = &self.current_machine_id {
1106 self.config.audit.history.record_config_change(
1107 machine_id,
1108 "format",
1109 Some(&serde_json::to_string(&self.config.format).unwrap_or_default()),
1110 Some(&serde_json::to_string(&format).unwrap_or_default()),
1111 );
1112 }
1113
1114 self.config.format = format;
1115 Ok(())
1116 }
1117
1118 pub fn set_check_position(&mut self, position: CheckPosition) -> Result<()> {
1120 self.require_full_access()?;
1121
1122 if let Some(machine_id) = &self.current_machine_id {
1123 self.config.audit.history.record_config_change(
1124 machine_id,
1125 "check_position",
1126 Some(&self.config.check_position.to_string()),
1127 Some(&position.to_string()),
1128 );
1129 }
1130
1131 self.config.check_position = position;
1132 Ok(())
1133 }
1134
1135 pub fn set_prefix(&mut self, prefix: Option<String>) -> Result<()> {
1140 self.require_full_access()?;
1141
1142 let old_value = self.config.format.prefix.as_deref().map(|s| s.to_string());
1143
1144 if let Some(machine_id) = &self.current_machine_id {
1145 self.config.audit.history.record_config_change(
1146 machine_id,
1147 "prefix",
1148 old_value.as_deref(),
1149 prefix.as_deref(),
1150 );
1151 }
1152
1153 self.config.format.prefix = prefix;
1154 Ok(())
1155 }
1156
1157 pub fn set_suffix(&mut self, suffix: Option<String>) -> Result<()> {
1162 self.require_full_access()?;
1163
1164 let old_value = self.config.format.suffix.as_deref().map(|s| s.to_string());
1165
1166 if let Some(machine_id) = &self.current_machine_id {
1167 self.config.audit.history.record_config_change(
1168 machine_id,
1169 "suffix",
1170 old_value.as_deref(),
1171 suffix.as_deref(),
1172 );
1173 }
1174
1175 self.config.format.suffix = suffix;
1176 Ok(())
1177 }
1178
1179 pub fn set_separator(&mut self, separator: Option<char>) -> Result<()> {
1184 self.require_full_access()?;
1185
1186 let old_value = self.config.format.separator.map(|c| c.to_string());
1187 let new_value = separator.map(|c| c.to_string());
1188
1189 if let Some(machine_id) = &self.current_machine_id {
1190 self.config.audit.history.record_config_change(
1191 machine_id,
1192 "separator",
1193 old_value.as_deref(),
1194 new_value.as_deref(),
1195 );
1196 }
1197
1198 self.config.format.separator = separator;
1199 Ok(())
1200 }
1201
1202 pub fn set_separator_positions(&mut self, positions: Vec<usize>) -> Result<()> {
1207 self.require_full_access()?;
1208
1209 let old_value = serde_json::to_string(&self.config.format.separator_positions).ok();
1210 let new_value = serde_json::to_string(&positions).ok();
1211
1212 if let Some(machine_id) = &self.current_machine_id {
1213 self.config.audit.history.record_config_change(
1214 machine_id,
1215 "separator_positions",
1216 old_value.as_deref(),
1217 new_value.as_deref(),
1218 );
1219 }
1220
1221 self.config.format.separator_positions = positions;
1222 Ok(())
1223 }
1224
1225 pub fn save(&mut self) -> Result<()> {
1240 self.require_full_access()?;
1241
1242 let path = self.path.as_ref().ok_or_else(|| {
1243 PromocryptError::InvalidArgument("Cannot save in-memory file without path".to_string())
1244 })?;
1245
1246 let raw_data = self.raw_data.as_ref().ok_or_else(|| {
1248 PromocryptError::InvalidArgument("No raw data available for save".to_string())
1249 })?;
1250
1251 let config_json = serde_json::to_vec(&self.config)?;
1253
1254 let data_nonce = generate_nonce()?;
1256
1257 let encrypted_data = encrypt_data(&self.data_key, &config_json, &data_nonce)?;
1259
1260 let mut machine_nonce = [0u8; NONCE_SIZE];
1262 machine_nonce
1263 .copy_from_slice(&raw_data[OFFSET_MACHINE_NONCE..OFFSET_MACHINE_NONCE + NONCE_SIZE]);
1264
1265 let mut machine_encrypted = [0u8; ENCRYPTED_KEY_SIZE];
1266 machine_encrypted.copy_from_slice(
1267 &raw_data[OFFSET_MACHINE_KEY..OFFSET_MACHINE_KEY + ENCRYPTED_KEY_SIZE],
1268 );
1269
1270 let mut secret_nonce = [0u8; NONCE_SIZE];
1272 secret_nonce
1273 .copy_from_slice(&raw_data[OFFSET_SECRET_NONCE..OFFSET_SECRET_NONCE + NONCE_SIZE]);
1274
1275 let mut secret_encrypted = [0u8; ENCRYPTED_KEY_SIZE];
1276 secret_encrypted
1277 .copy_from_slice(&raw_data[OFFSET_SECRET_KEY..OFFSET_SECRET_KEY + ENCRYPTED_KEY_SIZE]);
1278
1279 let flags = (if self.is_bound { FLAG_IS_BOUND } else { 0 })
1281 | (if self.has_mutable {
1282 FLAG_HAS_MUTABLE
1283 } else {
1284 0
1285 });
1286
1287 let mut file_data = Vec::new();
1288
1289 file_data.extend_from_slice(MAGIC);
1291 file_data.push(VERSION);
1292 file_data.push(flags);
1293
1294 let crc_offset = file_data.len();
1296 file_data.extend_from_slice(&[0u8; 4]);
1297
1298 file_data.extend_from_slice(&self.salt);
1300 file_data.extend_from_slice(&machine_nonce);
1301 file_data.extend_from_slice(&machine_encrypted);
1302 file_data.extend_from_slice(&secret_nonce);
1303 file_data.extend_from_slice(&secret_encrypted);
1304
1305 file_data.extend_from_slice(&data_nonce);
1307 file_data.extend_from_slice(&(encrypted_data.len() as u32).to_le_bytes());
1308 file_data.extend_from_slice(&encrypted_data);
1309
1310 if self.has_mutable {
1312 let mutable_nonce = generate_nonce()?;
1313 let counter_bytes = counter_to_bytes(self.counter.in_bin_value());
1314 let encrypted_counter = encrypt_data(&self.data_key, &counter_bytes, &mutable_nonce)?;
1315
1316 file_data.extend_from_slice(&mutable_nonce);
1317 file_data.extend_from_slice(&(encrypted_counter.len() as u32).to_le_bytes());
1318 file_data.extend_from_slice(&encrypted_counter);
1319 }
1320
1321 let crc = crc32fast::hash(&file_data[0..10]);
1323 file_data[crc_offset..crc_offset + 4].copy_from_slice(&crc.to_le_bytes());
1324
1325 let mut file = File::create(path)?;
1327 file.write_all(&file_data)?;
1328 file.sync_all()?;
1329
1330 self.raw_data = Some(file_data);
1332
1333 if self.counter.is_in_bin_modified() {
1335 self.counter.mark_saved();
1336 }
1337
1338 Ok(())
1339 }
1340
1341 pub fn rotate_secret(&mut self, old_secret: &str, new_secret: &str) -> Result<()> {
1346 self.require_full_access()?;
1347
1348 let path = self.path.as_ref().ok_or_else(|| {
1350 PromocryptError::InvalidArgument("Cannot rotate secret for in-memory file".to_string())
1351 })?;
1352
1353 let raw_data = self
1355 .raw_data
1356 .as_ref()
1357 .ok_or_else(|| PromocryptError::InvalidArgument("No raw data available".to_string()))?;
1358
1359 let mut secret_nonce = [0u8; NONCE_SIZE];
1361 secret_nonce
1362 .copy_from_slice(&raw_data[OFFSET_SECRET_NONCE..OFFSET_SECRET_NONCE + NONCE_SIZE]);
1363
1364 let mut secret_encrypted = [0u8; ENCRYPTED_KEY_SIZE];
1365 secret_encrypted
1366 .copy_from_slice(&raw_data[OFFSET_SECRET_KEY..OFFSET_SECRET_KEY + ENCRYPTED_KEY_SIZE]);
1367
1368 let _verified_data_key = decrypt_data_key(
1370 &secret_encrypted,
1371 old_secret.as_bytes(),
1372 &self.salt,
1373 &secret_nonce,
1374 )
1375 .map_err(|_| PromocryptError::InvalidArgument("Old secret is incorrect".to_string()))?;
1376
1377 let new_secret_nonce = generate_nonce()?;
1379
1380 let new_secret_encrypted = encrypt_data_key(
1382 &self.data_key,
1383 new_secret.as_bytes(),
1384 &self.salt,
1385 &new_secret_nonce,
1386 )?;
1387
1388 let old_hash = hash_secret(old_secret);
1390 let new_hash = hash_secret(new_secret);
1391
1392 if let Some(machine_id) = &self.current_machine_id {
1393 self.config
1394 .audit
1395 .history
1396 .record_secret_rotation(machine_id, &old_hash, &new_hash);
1397 }
1398
1399 let config_json = serde_json::to_vec(&self.config)?;
1401 let data_nonce = generate_nonce()?;
1402 let encrypted_data = encrypt_data(&self.data_key, &config_json, &data_nonce)?;
1403
1404 let mut machine_nonce = [0u8; NONCE_SIZE];
1406 machine_nonce
1407 .copy_from_slice(&raw_data[OFFSET_MACHINE_NONCE..OFFSET_MACHINE_NONCE + NONCE_SIZE]);
1408
1409 let mut machine_encrypted = [0u8; ENCRYPTED_KEY_SIZE];
1410 machine_encrypted.copy_from_slice(
1411 &raw_data[OFFSET_MACHINE_KEY..OFFSET_MACHINE_KEY + ENCRYPTED_KEY_SIZE],
1412 );
1413
1414 let flags = (if self.is_bound { FLAG_IS_BOUND } else { 0 })
1416 | (if self.has_mutable {
1417 FLAG_HAS_MUTABLE
1418 } else {
1419 0
1420 });
1421
1422 let mut file_data = Vec::new();
1423
1424 file_data.extend_from_slice(MAGIC);
1426 file_data.push(VERSION);
1427 file_data.push(flags);
1428
1429 let crc_offset = file_data.len();
1431 file_data.extend_from_slice(&[0u8; 4]);
1432
1433 file_data.extend_from_slice(&self.salt);
1435 file_data.extend_from_slice(&machine_nonce);
1436 file_data.extend_from_slice(&machine_encrypted);
1437 file_data.extend_from_slice(&new_secret_nonce);
1438 file_data.extend_from_slice(&new_secret_encrypted);
1439
1440 file_data.extend_from_slice(&data_nonce);
1442 file_data.extend_from_slice(&(encrypted_data.len() as u32).to_le_bytes());
1443 file_data.extend_from_slice(&encrypted_data);
1444
1445 if self.has_mutable {
1447 let mutable_nonce = generate_nonce()?;
1448 let counter_bytes = counter_to_bytes(self.counter.in_bin_value());
1449 let encrypted_counter = encrypt_data(&self.data_key, &counter_bytes, &mutable_nonce)?;
1450
1451 file_data.extend_from_slice(&mutable_nonce);
1452 file_data.extend_from_slice(&(encrypted_counter.len() as u32).to_le_bytes());
1453 file_data.extend_from_slice(&encrypted_counter);
1454 }
1455
1456 let crc = crc32fast::hash(&file_data[0..10]);
1458 file_data[crc_offset..crc_offset + 4].copy_from_slice(&crc.to_le_bytes());
1459
1460 let mut file = File::create(path)?;
1462 file.write_all(&file_data)?;
1463 file.sync_all()?;
1464
1465 self.raw_data = Some(file_data);
1467
1468 Ok(())
1469 }
1470
1471 pub fn export_history(&self) -> String {
1475 self.config.audit.export_history()
1476 }
1477
1478 pub fn export_generation_log(&self) -> String {
1480 self.config.audit.export_generation_log()
1481 }
1482
1483 pub fn clear_history(&mut self, keep_last: Option<usize>) -> Result<()> {
1485 self.require_full_access()?;
1486 self.config.audit.clear_history(keep_last);
1487 Ok(())
1488 }
1489
1490 pub fn clear_generation_log(&mut self, keep_last: Option<usize>) -> Result<()> {
1492 self.require_full_access()?;
1493 self.config.audit.clear_generation_log(keep_last);
1494 Ok(())
1495 }
1496
1497 pub fn get_stats(&self) -> BinStats {
1501 BinStats::from_bin_file(self)
1502 }
1503
1504 fn require_full_access(&self) -> Result<()> {
1507 if self.access_level != AccessLevel::FullAccess {
1508 return Err(PromocryptError::SecretRequired);
1509 }
1510 Ok(())
1511 }
1512
1513 fn save_if_needed(&mut self) -> Result<()> {
1514 if self.counter.is_in_bin_modified() {
1515 if let Some(path) = &self.path {
1516 self.save_mutable_section(path)?;
1517 }
1518 self.counter.mark_saved();
1519 }
1520 Ok(())
1521 }
1522
1523 fn save_mutable_section(&self, path: &Path) -> Result<()> {
1524 if !self.has_mutable {
1525 return Ok(());
1526 }
1527
1528 let mut file = OpenOptions::new().read(true).write(true).open(path)?;
1530
1531 let mut header = [0u8; HEADER_SIZE];
1533 file.read_exact(&mut header)?;
1534
1535 let data_length = u32::from_le_bytes([
1536 header[OFFSET_DATA_LENGTH],
1537 header[OFFSET_DATA_LENGTH + 1],
1538 header[OFFSET_DATA_LENGTH + 2],
1539 header[OFFSET_DATA_LENGTH + 3],
1540 ]) as usize;
1541
1542 let mutable_offset = OFFSET_DATA + data_length;
1543
1544 let mutable_nonce = generate_nonce()?;
1546 let counter_bytes = counter_to_bytes(self.counter.in_bin_value());
1547 let encrypted_counter = encrypt_data(&self.data_key, &counter_bytes, &mutable_nonce)?;
1548
1549 file.seek(SeekFrom::Start(mutable_offset as u64))?;
1551 file.write_all(&mutable_nonce)?;
1552 file.write_all(&(encrypted_counter.len() as u32).to_le_bytes())?;
1553 file.write_all(&encrypted_counter)?;
1554 file.sync_all()?;
1555
1556 Ok(())
1557 }
1558}
1559
1560pub fn create_config(name: &str) -> BinConfig {
1562 let machine_id = get_machine_id().unwrap_or([0u8; 32]);
1563 let alphabet = Alphabet::default_alphabet();
1564 let damm_table = DammTable::new(alphabet.len());
1565
1566 BinConfig {
1567 name: name.to_string(),
1568 alphabet,
1569 code_length: 9,
1570 check_position: CheckPosition::End,
1571 secret_key: String::new(),
1572 storage_key: String::new(),
1573 storage_encryption_enabled: false,
1574 damm_table,
1575 counter_mode: CounterMode::External,
1576 format: CodeFormat::default(),
1577 audit: AuditInfo::new(&machine_id),
1578 }
1579}
1580
1581#[derive(Clone, Debug)]
1583pub struct BinStats {
1584 pub name: String,
1586 pub alphabet_size: usize,
1588 pub code_length: usize,
1590 pub base_code_length: usize,
1592 pub formatted_code_length: usize,
1594 pub capacity: u128,
1596 pub current_counter: u64,
1598 pub total_generated: u64,
1600 pub utilization_percent: f64,
1602 pub remaining_capacity: u128,
1604 pub counter_mode: CounterMode,
1606 pub has_formatting: bool,
1608 pub storage_encryption_enabled: bool,
1610 pub created_at: Option<String>,
1612 pub last_generated_at: Option<String>,
1614 pub secret_rotation_count: usize,
1616 pub machine_mastering_count: usize,
1618 pub config_change_count: usize,
1620 pub generation_log_count: usize,
1622}
1623
1624impl BinStats {
1625 pub fn from_bin_file(bin: &BinFile) -> Self {
1627 let config = bin.config();
1628 let audit = bin.audit();
1629
1630 let alphabet_size = config.alphabet.len();
1631 let code_length = config.code_length;
1632 let base_code_length = code_length + 1; let formatted_code_length = config.format.total_length(base_code_length);
1634
1635 let capacity = (alphabet_size as u128).pow(code_length as u32);
1636 let current_counter = bin.get_counter().unwrap_or(0);
1637
1638 let utilization_percent = if capacity > 0 {
1639 (current_counter as f64 / capacity as f64) * 100.0
1640 } else {
1641 0.0
1642 };
1643
1644 let remaining_capacity = capacity.saturating_sub(current_counter as u128);
1645
1646 let has_formatting = config.format.has_formatting();
1647
1648 Self {
1649 name: config.name.clone(),
1650 alphabet_size,
1651 code_length,
1652 base_code_length,
1653 formatted_code_length,
1654 capacity,
1655 current_counter,
1656 total_generated: audit.generation_count,
1657 utilization_percent,
1658 remaining_capacity,
1659 counter_mode: config.counter_mode.clone(),
1660 has_formatting,
1661 storage_encryption_enabled: config.storage_encryption_enabled,
1662 created_at: Some(audit.created_at.to_rfc3339()),
1663 last_generated_at: audit.last_generated_at.map(|dt| dt.to_rfc3339()),
1664 secret_rotation_count: audit.history.secret_rotations.len(),
1665 machine_mastering_count: audit.history.machine_masterings.len(),
1666 config_change_count: audit.history.config_changes.len(),
1667 generation_log_count: audit.generation_log.len(),
1668 }
1669 }
1670
1671 pub fn summary(&self) -> String {
1673 let mut lines = Vec::new();
1674
1675 lines.push(format!("Name: {}", self.name));
1676 lines.push(format!("Alphabet size: {}", self.alphabet_size));
1677 lines.push(format!(
1678 "Code length: {} (base: {}, formatted: {})",
1679 self.code_length, self.base_code_length, self.formatted_code_length
1680 ));
1681 lines.push(format!("Capacity: {}", self.capacity));
1682 lines.push(format!("Current counter: {}", self.current_counter));
1683 lines.push(format!("Total generated: {}", self.total_generated));
1684 lines.push(format!("Utilization: {:.6}%", self.utilization_percent));
1685 lines.push(format!("Remaining: {}", self.remaining_capacity));
1686 lines.push(format!("Counter mode: {:?}", self.counter_mode));
1687 lines.push(format!("Has formatting: {}", self.has_formatting));
1688 lines.push(format!(
1689 "Storage encryption: {}",
1690 self.storage_encryption_enabled
1691 ));
1692
1693 if let Some(ref created) = self.created_at {
1694 lines.push(format!("Created: {}", created));
1695 }
1696
1697 if let Some(ref last) = self.last_generated_at {
1698 lines.push(format!("Last generated: {}", last));
1699 }
1700
1701 if self.secret_rotation_count > 0
1702 || self.machine_mastering_count > 0
1703 || self.config_change_count > 0
1704 {
1705 lines.push(format!(
1706 "History: {} rotations, {} masterings, {} config changes",
1707 self.secret_rotation_count, self.machine_mastering_count, self.config_change_count
1708 ));
1709 }
1710
1711 if self.generation_log_count > 0 {
1712 lines.push(format!(
1713 "Generation log entries: {}",
1714 self.generation_log_count
1715 ));
1716 }
1717
1718 lines.join("\n")
1719 }
1720}
1721
1722#[cfg(test)]
1723mod tests {
1724 use super::*;
1725 use tempfile::TempDir;
1726
1727 fn create_test_config(name: &str) -> BinConfig {
1728 let alphabet = Alphabet::default_alphabet();
1729 BinConfig {
1730 name: name.to_string(),
1731 alphabet: alphabet.clone(),
1732 code_length: 9,
1733 check_position: CheckPosition::End,
1734 secret_key: String::new(),
1735 storage_key: String::new(),
1736 storage_encryption_enabled: false,
1737 damm_table: DammTable::new(alphabet.len()),
1738 counter_mode: CounterMode::External,
1739 format: CodeFormat::default(),
1740 audit: AuditInfo::default(),
1741 }
1742 }
1743
1744 #[test]
1745 fn test_create_and_open_with_secret() {
1746 let temp_dir = TempDir::new().unwrap();
1747 let path = temp_dir.path().join("test.bin");
1748
1749 let config = create_test_config("test");
1750 let _bin = BinFile::create(&path, "my-secret", config).unwrap();
1751
1752 let bin = BinFile::open_with_secret(&path, "my-secret").unwrap();
1754 assert_eq!(bin.name(), "test");
1755 assert_eq!(bin.access_level(), AccessLevel::FullAccess);
1756 }
1757
1758 #[test]
1759 fn test_wrong_secret() {
1760 let temp_dir = TempDir::new().unwrap();
1761 let path = temp_dir.path().join("test.bin");
1762
1763 let config = create_test_config("test");
1764 let _bin = BinFile::create(&path, "correct-secret", config).unwrap();
1765
1766 let result = BinFile::open_with_secret(&path, "wrong-secret");
1768 assert!(result.is_err());
1769 }
1770
1771 #[test]
1772 fn test_generate_and_validate() {
1773 let temp_dir = TempDir::new().unwrap();
1774 let path = temp_dir.path().join("test.bin");
1775
1776 let mut config = create_test_config("test");
1777 config.counter_mode = CounterMode::InBin;
1778
1779 let mut bin = BinFile::create(&path, "secret", config).unwrap();
1780
1781 let codes = bin.generate_batch(10).unwrap();
1783 assert_eq!(codes.len(), 10);
1784
1785 for code in &codes {
1787 assert!(bin.is_valid(code));
1788 }
1789 }
1790
1791 #[test]
1792 fn test_generate_at() {
1793 let temp_dir = TempDir::new().unwrap();
1794 let path = temp_dir.path().join("test.bin");
1795
1796 let config = create_test_config("test");
1797 let bin = BinFile::create(&path, "secret", config).unwrap();
1798
1799 let code1 = bin.generate_at(1000).unwrap();
1801 let code2 = bin.generate_at(1000).unwrap();
1802
1803 assert_eq!(code1, code2); assert!(bin.is_valid(&code1));
1805 }
1806
1807 #[test]
1808 fn test_readonly_cannot_generate() {
1809 let temp_dir = TempDir::new().unwrap();
1810 let path = temp_dir.path().join("test.bin");
1811
1812 let config = create_test_config("test");
1813 let _bin = BinFile::create(&path, "secret", config).unwrap();
1814
1815 let mut bin = BinFile::open_readonly(&path).unwrap();
1817 assert_eq!(bin.access_level(), AccessLevel::ReadOnly);
1818
1819 let result = bin.generate();
1821 assert!(result.is_err());
1822 assert!(matches!(
1823 result.unwrap_err(),
1824 PromocryptError::SecretRequired
1825 ));
1826 }
1827
1828 #[test]
1829 fn test_code_length() {
1830 let temp_dir = TempDir::new().unwrap();
1831 let path = temp_dir.path().join("test.bin");
1832
1833 let mut config = create_test_config("test");
1834 config.code_length = 12;
1835 config.counter_mode = CounterMode::InBin;
1836
1837 let mut bin = BinFile::create(&path, "secret", config).unwrap();
1838
1839 let code = bin.generate().unwrap();
1840 assert_eq!(code.len(), 13); }
1842
1843 #[test]
1844 fn test_audit_tracking() {
1845 let temp_dir = TempDir::new().unwrap();
1846 let path = temp_dir.path().join("test.bin");
1847
1848 let mut config = create_test_config("test");
1849 config.counter_mode = CounterMode::InBin;
1850
1851 let mut bin = BinFile::create(&path, "secret", config).unwrap();
1852
1853 assert_eq!(bin.audit().generation_count, 0);
1854
1855 bin.generate_batch(100).unwrap();
1856 assert_eq!(bin.audit().generation_count, 100);
1857
1858 bin.generate().unwrap();
1859 assert_eq!(bin.audit().generation_count, 101);
1860 }
1861
1862 #[test]
1863 fn test_from_bytes() {
1864 let config = create_test_config("test");
1865 let data = BinFile::create_in_memory("secret", config).unwrap();
1866
1867 let bin = BinFile::from_bytes_with_secret(&data, "secret").unwrap();
1869 assert_eq!(bin.name(), "test");
1870 }
1871
1872 #[test]
1873 fn test_set_counter() {
1874 let temp_dir = TempDir::new().unwrap();
1875 let path = temp_dir.path().join("test.bin");
1876
1877 let mut config = create_test_config("test");
1878 config.counter_mode = CounterMode::InBin;
1879
1880 let mut bin = BinFile::create(&path, "secret", config).unwrap();
1881
1882 assert_eq!(bin.get_counter().unwrap(), 0);
1884
1885 bin.set_counter(1000).unwrap();
1887 assert_eq!(bin.get_counter().unwrap(), 1000);
1888
1889 drop(bin);
1891 let bin = BinFile::open_with_secret(&path, "secret").unwrap();
1892 assert_eq!(bin.get_counter().unwrap(), 1000);
1893 }
1894
1895 #[test]
1896 fn test_set_counter_requires_full_access() {
1897 let temp_dir = TempDir::new().unwrap();
1898 let path = temp_dir.path().join("test.bin");
1899
1900 let mut config = create_test_config("test");
1901 config.counter_mode = CounterMode::InBin;
1902
1903 let _bin = BinFile::create(&path, "secret", config).unwrap();
1904
1905 let mut bin = BinFile::open_readonly(&path).unwrap();
1907
1908 let result = bin.set_counter(100);
1910 assert!(result.is_err());
1911 assert!(matches!(
1912 result.unwrap_err(),
1913 PromocryptError::SecretRequired
1914 ));
1915 }
1916
1917 #[test]
1918 fn test_reserve_counter_range() {
1919 let temp_dir = TempDir::new().unwrap();
1920 let path = temp_dir.path().join("test.bin");
1921
1922 let mut config = create_test_config("test");
1923 config.counter_mode = CounterMode::InBin;
1924
1925 let mut bin = BinFile::create(&path, "secret", config).unwrap();
1926
1927 assert_eq!(bin.get_counter().unwrap(), 0);
1929
1930 let start = bin.reserve_counter_range(100).unwrap();
1932 assert_eq!(start, 0);
1933 assert_eq!(bin.get_counter().unwrap(), 100);
1934
1935 let start2 = bin.reserve_counter_range(50).unwrap();
1937 assert_eq!(start2, 100);
1938 assert_eq!(bin.get_counter().unwrap(), 150);
1939
1940 drop(bin);
1942 let bin = BinFile::open_with_secret(&path, "secret").unwrap();
1943 assert_eq!(bin.get_counter().unwrap(), 150);
1944 }
1945
1946 #[test]
1947 fn test_reserve_counter_range_for_parallel_generation() {
1948 let temp_dir = TempDir::new().unwrap();
1949 let path = temp_dir.path().join("test.bin");
1950
1951 let mut config = create_test_config("test");
1952 config.counter_mode = CounterMode::InBin;
1953
1954 let mut bin = BinFile::create(&path, "secret", config).unwrap();
1955
1956 let start = bin.reserve_counter_range(1000).unwrap();
1958
1959 let codes = bin.generate_batch_at(start, 10).unwrap();
1961 assert_eq!(codes.len(), 10);
1962
1963 assert_eq!(bin.get_counter().unwrap(), 1000);
1965
1966 for code in &codes {
1968 assert!(bin.is_valid(code));
1969 }
1970 }
1971
1972 #[test]
1973 fn test_reserve_counter_requires_full_access() {
1974 let temp_dir = TempDir::new().unwrap();
1975 let path = temp_dir.path().join("test.bin");
1976
1977 let mut config = create_test_config("test");
1978 config.counter_mode = CounterMode::InBin;
1979
1980 let _bin = BinFile::create(&path, "secret", config).unwrap();
1981
1982 let mut bin = BinFile::open_readonly(&path).unwrap();
1984
1985 let result = bin.reserve_counter_range(100);
1987 assert!(result.is_err());
1988 assert!(matches!(
1989 result.unwrap_err(),
1990 PromocryptError::SecretRequired
1991 ));
1992 }
1993}