libpna/entry/options.rs
1use crate::{compress, error::UnknownValueError};
2pub(crate) use private::*;
3use std::str::FromStr;
4
5mod private {
6 use super::*;
7
8 /// Compression options.
9 #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
10 pub enum Compress {
11 No,
12 Deflate(compress::deflate::DeflateCompressionLevel),
13 ZStandard(compress::zstandard::ZstdCompressionLevel),
14 XZ(compress::xz::XZCompressionLevel),
15 }
16
17 /// Cipher options.
18 #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
19 pub struct Cipher {
20 pub(crate) password: Password,
21 pub(crate) hash_algorithm: HashAlgorithm,
22 pub(crate) cipher_algorithm: CipherAlgorithm,
23 pub(crate) mode: CipherMode,
24 }
25
26 impl Cipher {
27 /// Creates a new [Cipher]
28 #[inline]
29 pub(crate) const fn new(
30 password: Password,
31 hash_algorithm: HashAlgorithm,
32 cipher_algorithm: CipherAlgorithm,
33 mode: CipherMode,
34 ) -> Self {
35 Self {
36 password,
37 hash_algorithm,
38 cipher_algorithm,
39 mode,
40 }
41 }
42 }
43
44 /// Accessors for write options.
45 pub trait WriteOption {
46 fn compress(&self) -> Compress;
47 fn cipher(&self) -> Option<&Cipher>;
48 #[inline]
49 fn compression(&self) -> Compression {
50 match self.compress() {
51 Compress::No => Compression::No,
52 Compress::Deflate(_) => Compression::Deflate,
53 Compress::ZStandard(_) => Compression::ZStandard,
54 Compress::XZ(_) => Compression::XZ,
55 }
56 }
57
58 #[inline]
59 fn encryption(&self) -> Encryption {
60 self.cipher()
61 .map_or(Encryption::No, |it| match it.cipher_algorithm {
62 CipherAlgorithm::Aes => Encryption::Aes,
63 CipherAlgorithm::Camellia => Encryption::Camellia,
64 })
65 }
66
67 #[inline]
68 fn cipher_mode(&self) -> CipherMode {
69 self.cipher().map_or(CipherMode::CTR, |it| it.mode)
70 }
71
72 #[inline]
73 fn hash_algorithm(&self) -> HashAlgorithm {
74 self.cipher()
75 .map_or_else(HashAlgorithm::argon2id, |it| it.hash_algorithm)
76 }
77
78 #[inline]
79 fn password(&self) -> Option<&[u8]> {
80 self.cipher().map(|it| it.password.as_bytes())
81 }
82 }
83
84 impl WriteOption for WriteOptions {
85 #[inline]
86 fn compress(&self) -> Compress {
87 self.compress
88 }
89
90 #[inline]
91 fn cipher(&self) -> Option<&Cipher> {
92 self.cipher.as_ref()
93 }
94 }
95
96 impl<T> WriteOption for &T
97 where
98 T: WriteOption,
99 {
100 #[inline]
101 fn compress(&self) -> Compress {
102 T::compress(self)
103 }
104
105 #[inline]
106 fn cipher(&self) -> Option<&Cipher> {
107 T::cipher(self)
108 }
109 }
110
111 /// Entry read option getter trait.
112 pub trait ReadOption {
113 fn password(&self) -> Option<&[u8]>;
114 }
115
116 impl<T: ReadOption> ReadOption for &T {
117 #[inline]
118 fn password(&self) -> Option<&[u8]> {
119 T::password(self)
120 }
121 }
122
123 impl ReadOption for ReadOptions {
124 #[inline]
125 fn password(&self) -> Option<&[u8]> {
126 self.password.as_deref()
127 }
128 }
129}
130
131/// Compression method.
132#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
133#[repr(u8)]
134pub enum Compression {
135 /// Do not apply any compression.
136 No = 0,
137 /// Zlib format.
138 Deflate = 1,
139 /// ZStandard format.
140 ZStandard = 2,
141 /// Xz format.
142 XZ = 4,
143}
144
145impl TryFrom<u8> for Compression {
146 type Error = UnknownValueError;
147
148 #[inline]
149 fn try_from(value: u8) -> Result<Self, Self::Error> {
150 match value {
151 0 => Ok(Self::No),
152 1 => Ok(Self::Deflate),
153 2 => Ok(Self::ZStandard),
154 4 => Ok(Self::XZ),
155 value => Err(UnknownValueError(value)),
156 }
157 }
158}
159
160#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
161pub(crate) enum CompressionLevelImpl {
162 /// Minimum compression level.
163 Min,
164 /// Maximum compression level.
165 Max,
166 /// Default compression level.
167 Default,
168 /// Custom compression level.
169 Custom(i64),
170}
171
172impl FromStr for CompressionLevelImpl {
173 type Err = core::num::ParseIntError;
174
175 #[inline]
176 fn from_str(s: &str) -> Result<Self, Self::Err> {
177 if s.eq_ignore_ascii_case("min") {
178 Ok(Self::Min)
179 } else if s.eq_ignore_ascii_case("max") {
180 Ok(Self::Max)
181 } else if s.eq_ignore_ascii_case("default") {
182 Ok(Self::Default)
183 } else {
184 Ok(Self::Custom(i64::from_str(s)?))
185 }
186 }
187}
188
189/// Compression level of each algorithm.
190#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
191pub struct CompressionLevel(pub(crate) CompressionLevelImpl);
192
193impl CompressionLevel {
194 pub(crate) const DEFAULT: Self = Self(CompressionLevelImpl::Default);
195
196 /// Minimum compression level.
197 /// This value will be replaced with the minimum level for each algorithm.
198 #[inline]
199 pub const fn min() -> Self {
200 Self(CompressionLevelImpl::Min)
201 }
202
203 /// Maximum compression level.
204 /// This value will be replaced with the maximum level for each algorithm.
205 #[inline]
206 pub const fn max() -> Self {
207 Self(CompressionLevelImpl::Max)
208 }
209}
210
211impl Default for CompressionLevel {
212 #[inline]
213 fn default() -> Self {
214 Self::DEFAULT
215 }
216}
217
218impl<T: Into<i64>> From<T> for CompressionLevel {
219 #[inline]
220 fn from(value: T) -> Self {
221 Self(CompressionLevelImpl::Custom(value.into()))
222 }
223}
224
225impl FromStr for CompressionLevel {
226 type Err = core::num::ParseIntError;
227
228 /// Parses a string `s` to return a value of this type.
229 ///
230 /// If parsing succeeds, return the value inside [`Ok`], otherwise
231 /// when the string is ill-formatted return an error specific to the
232 /// inside [`Err`]. The error type is specific to the implementation of the trait.
233 ///
234 /// # Examples
235 ///
236 /// ```rust
237 /// use libpna::CompressionLevel;
238 /// use std::str::FromStr;
239 ///
240 /// assert_eq!(
241 /// CompressionLevel::min(),
242 /// CompressionLevel::from_str("min").unwrap()
243 /// );
244 /// assert_eq!(
245 /// CompressionLevel::max(),
246 /// CompressionLevel::from_str("max").unwrap()
247 /// );
248 /// assert_eq!(
249 /// CompressionLevel::default(),
250 /// CompressionLevel::from_str("default").unwrap()
251 /// );
252 /// assert_eq!(
253 /// CompressionLevel::from(3),
254 /// CompressionLevel::from_str("3").unwrap()
255 /// );
256 /// ```
257 #[inline]
258 fn from_str(s: &str) -> Result<Self, Self::Err> {
259 Ok(Self(CompressionLevelImpl::from_str(s)?))
260 }
261}
262
263/// Cipher algorithm.
264#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
265pub enum CipherAlgorithm {
266 /// Aes algorithm.
267 Aes,
268 /// Camellia algorithm.
269 Camellia,
270}
271
272/// Password.
273#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
274pub(crate) struct Password(Vec<u8>);
275
276impl Password {
277 #[inline]
278 pub(crate) const fn as_bytes(&self) -> &[u8] {
279 self.0.as_slice()
280 }
281}
282
283impl<T: AsRef<[u8]>> From<T> for Password {
284 #[inline]
285 fn from(value: T) -> Self {
286 Self(value.as_ref().to_vec())
287 }
288}
289
290/// Encryption algorithm.
291#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
292#[repr(u8)]
293pub enum Encryption {
294 /// Do not apply any encryption.
295 No = 0,
296 /// Aes algorithm.
297 Aes = 1,
298 /// Camellia algorithm.
299 Camellia = 2,
300}
301
302impl TryFrom<u8> for Encryption {
303 type Error = UnknownValueError;
304
305 #[inline]
306 fn try_from(value: u8) -> Result<Self, Self::Error> {
307 match value {
308 0 => Ok(Self::No),
309 1 => Ok(Self::Aes),
310 2 => Ok(Self::Camellia),
311 value => Err(UnknownValueError(value)),
312 }
313 }
314}
315
316/// Cipher mode of encryption algorithm.
317#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
318#[repr(u8)]
319pub enum CipherMode {
320 /// Cipher Block Chaining Mode
321 CBC = 0,
322 /// Counter Mode
323 CTR = 1,
324}
325
326impl TryFrom<u8> for CipherMode {
327 type Error = UnknownValueError;
328
329 #[inline]
330 fn try_from(value: u8) -> Result<Self, Self::Error> {
331 match value {
332 0 => Ok(Self::CBC),
333 1 => Ok(Self::CTR),
334 value => Err(UnknownValueError(value)),
335 }
336 }
337}
338
339/// Password hash algorithm parameters.
340#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
341pub(crate) enum HashAlgorithmParams {
342 /// Pbkdf2 with sha256
343 Pbkdf2Sha256 {
344 /// Pbkdf2 rounds, if `None` use default rounds.
345 rounds: Option<u32>,
346 },
347 /// Argon2Id
348 Argon2Id {
349 /// Argon2Id time_cost, if `None` use default time_cost.
350 time_cost: Option<u32>,
351 /// Argon2Id memory_cost, if `None` use default memory_cost.
352 memory_cost: Option<u32>,
353 /// Argon2Id parallelism_cost, if `None` use default parallelism_cost.
354 parallelism_cost: Option<u32>,
355 },
356}
357
358/// Password hash algorithm.
359#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
360pub struct HashAlgorithm(pub(crate) HashAlgorithmParams);
361
362impl HashAlgorithm {
363 /// Creates a PBKDF2-SHA256 password hasher with default iterations.
364 ///
365 /// **Note:** Prefer [`argon2id()`](Self::argon2id) for new archives.
366 /// PBKDF2 is provided for compatibility with systems where Argon2 is unavailable.
367 ///
368 /// # Example
369 ///
370 /// ```rust
371 /// use libpna::{WriteOptions, Encryption, HashAlgorithm};
372 ///
373 /// let opts = WriteOptions::builder()
374 /// .encryption(Encryption::Aes)
375 /// .hash_algorithm(HashAlgorithm::pbkdf2_sha256())
376 /// .password(Some("password"))
377 /// .build();
378 /// ```
379 #[inline]
380 pub const fn pbkdf2_sha256() -> Self {
381 Self::pbkdf2_sha256_with(None)
382 }
383
384 /// Creates a PBKDF2-SHA256 password hasher with custom iteration count.
385 ///
386 /// Higher iteration counts increase security but also increase key derivation time.
387 /// If `rounds` is `None`, the default iteration count is used.
388 ///
389 /// **Note:** Prefer [`argon2id_with()`](Self::argon2id_with) for new archives.
390 ///
391 /// # Example
392 ///
393 /// ```rust
394 /// use libpna::{WriteOptions, Encryption, HashAlgorithm};
395 ///
396 /// let opts = WriteOptions::builder()
397 /// .encryption(Encryption::Aes)
398 /// .hash_algorithm(HashAlgorithm::pbkdf2_sha256_with(Some(100_000)))
399 /// .password(Some("password"))
400 /// .build();
401 /// ```
402 #[inline]
403 pub const fn pbkdf2_sha256_with(rounds: Option<u32>) -> Self {
404 Self(HashAlgorithmParams::Pbkdf2Sha256 { rounds })
405 }
406
407 /// Creates an Argon2id password hasher with default parameters.
408 ///
409 /// **Recommended** for all new archives. Argon2id is memory-hard, providing
410 /// better resistance against GPU/ASIC brute-force attacks compared to PBKDF2.
411 ///
412 /// # Example
413 ///
414 /// ```rust
415 /// use libpna::{WriteOptions, Encryption, HashAlgorithm};
416 ///
417 /// let opts = WriteOptions::builder()
418 /// .encryption(Encryption::Aes)
419 /// .hash_algorithm(HashAlgorithm::argon2id())
420 /// .password(Some("secure_password"))
421 /// .build();
422 /// ```
423 #[inline]
424 pub const fn argon2id() -> Self {
425 Self::argon2id_with(None, None, None)
426 }
427
428 /// Creates an Argon2id password hasher with custom parameters.
429 ///
430 /// - `time_cost`: Number of iterations (higher = slower, more secure)
431 /// - `memory_cost`: Memory usage in KiB (higher = more memory-hard)
432 /// - `parallelism_cost`: Degree of parallelism (threads)
433 ///
434 /// If any parameter is `None`, the default value is used.
435 ///
436 /// **Recommended** for all new archives when custom tuning is needed.
437 ///
438 /// # Example
439 ///
440 /// ```rust
441 /// use libpna::{WriteOptions, Encryption, HashAlgorithm};
442 ///
443 /// // Custom Argon2id with higher security parameters
444 /// let opts = WriteOptions::builder()
445 /// .encryption(Encryption::Aes)
446 /// .hash_algorithm(HashAlgorithm::argon2id_with(
447 /// Some(4), // time_cost: 4 iterations
448 /// Some(65536), // memory_cost: 64 MiB
449 /// Some(2), // parallelism: 2 threads
450 /// ))
451 /// .password(Some("secure_password"))
452 /// .build();
453 /// ```
454 #[inline]
455 pub const fn argon2id_with(
456 time_cost: Option<u32>,
457 memory_cost: Option<u32>,
458 parallelism_cost: Option<u32>,
459 ) -> Self {
460 Self(HashAlgorithmParams::Argon2Id {
461 time_cost,
462 memory_cost,
463 parallelism_cost,
464 })
465 }
466}
467
468/// Type of filesystem object represented by an entry.
469///
470/// Each variant determines how the entry's data should be interpreted
471/// and how the entry should be extracted to the filesystem.
472#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
473#[repr(u8)]
474pub enum DataKind {
475 /// Regular file. Entry data contains the file contents.
476 File = 0,
477 /// Directory. Entry has no data content.
478 Directory = 1,
479 /// Symbolic link. Entry data contains the UTF-8 encoded link target path.
480 SymbolicLink = 2,
481 /// Hard link. Entry data contains the UTF-8 encoded path of the target entry
482 /// within the same archive.
483 HardLink = 3,
484}
485
486impl TryFrom<u8> for DataKind {
487 type Error = UnknownValueError;
488
489 #[inline]
490 fn try_from(value: u8) -> Result<Self, Self::Error> {
491 match value {
492 0 => Ok(Self::File),
493 1 => Ok(Self::Directory),
494 2 => Ok(Self::SymbolicLink),
495 3 => Ok(Self::HardLink),
496 value => Err(UnknownValueError(value)),
497 }
498 }
499}
500
501/// Options for writing entries to a PNA archive.
502///
503/// This type configures compression, encryption, and password hashing for archive entries.
504/// Options are created using the builder pattern via [`WriteOptions::builder()`] or by
505/// using the convenience constructor [`WriteOptions::store()`] for uncompressed entries.
506///
507/// # Compression and Encryption Order
508///
509/// When both compression and encryption are enabled, data is **compressed first, then encrypted**.
510/// This order maximizes compression efficiency since encrypted data is essentially random
511/// and cannot be compressed effectively.
512///
513/// Data flow: `Original → Compress → Encrypt → Write to archive`
514///
515/// # Security Considerations
516///
517/// - **Hash Algorithm**: Always use [`HashAlgorithm::argon2id()`] in production for password-based
518/// encryption. [`HashAlgorithm::pbkdf2_sha256()`] is primarily for compatibility with older
519/// systems or when Argon2 is not available.
520/// - **Cipher Mode**: CTR mode ([`CipherMode::CTR`]) is recommended over CBC for most use cases
521/// as it allows parallel processing and has simpler security requirements.
522/// - **IV Generation**: Initialization vectors (IVs) are automatically generated using
523/// cryptographically secure random number generation. You do not need to provide IVs.
524/// - **Password Strength**: Use strong passwords (12+ characters, mixed case, numbers, symbols)
525/// as the encryption key is derived from the password.
526///
527/// # Examples
528///
529/// Store without compression or encryption:
530/// ```rust
531/// use libpna::WriteOptions;
532///
533/// let opts = WriteOptions::store();
534/// ```
535///
536/// Compress only (no encryption):
537/// ```rust
538/// use libpna::{WriteOptions, Compression, CompressionLevel};
539///
540/// let opts = WriteOptions::builder()
541/// .compression(Compression::ZStandard)
542/// .compression_level(CompressionLevel::max())
543/// .build();
544/// ```
545///
546/// Encrypt only (no compression):
547/// ```rust
548/// use libpna::{WriteOptions, Encryption, CipherMode, HashAlgorithm};
549///
550/// let opts = WriteOptions::builder()
551/// .encryption(Encryption::Aes)
552/// .cipher_mode(CipherMode::CTR)
553/// .hash_algorithm(HashAlgorithm::argon2id())
554/// .password(Some("secure_password"))
555/// .build();
556/// ```
557///
558/// Both compression and encryption (recommended for sensitive data):
559/// ```rust
560/// use libpna::{WriteOptions, Compression, Encryption, CipherMode, HashAlgorithm};
561///
562/// let opts = WriteOptions::builder()
563/// .compression(Compression::ZStandard)
564/// .encryption(Encryption::Aes)
565/// .cipher_mode(CipherMode::CTR)
566/// .hash_algorithm(HashAlgorithm::argon2id())
567/// .password(Some("secure_password"))
568/// .build();
569/// ```
570///
571/// # Relationship to ReadOptions
572///
573/// When reading an archive, use [`ReadOptions`] to provide the password for decryption.
574/// The compression algorithm and cipher mode are stored in the archive metadata, so you
575/// only need to provide the password.
576#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
577pub struct WriteOptions {
578 compress: Compress,
579 cipher: Option<Cipher>,
580}
581
582impl WriteOptions {
583 /// A new [WriteOptions] to simply store.
584 ///
585 /// # Examples
586 ///
587 /// ```rust
588 /// use libpna::{EntryBuilder, WriteOptions};
589 ///
590 /// EntryBuilder::new_file("example.txt".into(), WriteOptions::store()).unwrap();
591 /// ```
592 ///
593 /// [Entry]: crate::Entry
594 #[inline]
595 pub const fn store() -> Self {
596 Self {
597 compress: Compress::No,
598 cipher: None,
599 }
600 }
601
602 /// Returns a builder for [WriteOptions].
603 ///
604 /// # Returns
605 ///
606 /// [WriteOptionsBuilder] Builder object for [WriteOptions].
607 ///
608 /// # Examples
609 ///
610 /// ```rust
611 /// use libpna::WriteOptions;
612 ///
613 /// let builder = WriteOptions::builder();
614 /// ```
615 #[inline]
616 pub const fn builder() -> WriteOptionsBuilder {
617 WriteOptionsBuilder::new()
618 }
619
620 /// Converts [WriteOptions] into a [WriteOptionsBuilder].
621 ///
622 /// # Returns
623 ///
624 /// [WriteOptionsBuilder]: Builder object for [WriteOptions].
625 ///
626 /// # Examples
627 /// ```rust
628 /// use libpna::WriteOptions;
629 ///
630 /// let write_option = WriteOptions::builder().build();
631 /// let builder = write_option.into_builder();
632 /// ```
633 #[inline]
634 pub fn into_builder(self) -> WriteOptionsBuilder {
635 self.into()
636 }
637}
638
639/// Builder for [`WriteOptions`].
640#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
641pub struct WriteOptionsBuilder {
642 compression: Compression,
643 compression_level: CompressionLevel,
644 encryption: Encryption,
645 cipher_mode: CipherMode,
646 hash_algorithm: HashAlgorithm,
647 password: Option<Vec<u8>>,
648}
649
650impl Default for WriteOptionsBuilder {
651 #[inline]
652 fn default() -> Self {
653 Self::new()
654 }
655}
656
657impl From<WriteOptions> for WriteOptionsBuilder {
658 #[inline]
659 fn from(value: WriteOptions) -> Self {
660 let (compression, compression_level) = match value.compress {
661 Compress::No => (Compression::No, CompressionLevel::DEFAULT),
662 Compress::Deflate(level) => (Compression::Deflate, level.into()),
663 Compress::ZStandard(level) => (Compression::ZStandard, level.into()),
664 Compress::XZ(level) => (Compression::XZ, level.into()),
665 };
666 Self {
667 compression,
668 compression_level,
669 encryption: value.encryption(),
670 cipher_mode: value.cipher_mode(),
671 hash_algorithm: value.hash_algorithm(),
672 password: value.password().map(|p| p.to_vec()),
673 }
674 }
675}
676
677impl WriteOptionsBuilder {
678 const fn new() -> Self {
679 Self {
680 compression: Compression::No,
681 compression_level: CompressionLevel::DEFAULT,
682 encryption: Encryption::No,
683 cipher_mode: CipherMode::CTR,
684 hash_algorithm: HashAlgorithm::argon2id(),
685 password: None,
686 }
687 }
688
689 /// Sets the [`Compression`].
690 #[inline]
691 pub fn compression(&mut self, compression: Compression) -> &mut Self {
692 self.compression = compression;
693 self
694 }
695
696 /// Sets the [`CompressionLevel`].
697 #[inline]
698 pub fn compression_level(&mut self, compression_level: CompressionLevel) -> &mut Self {
699 self.compression_level = compression_level;
700 self
701 }
702
703 /// Sets the [`Encryption`].
704 #[inline]
705 pub fn encryption(&mut self, encryption: Encryption) -> &mut Self {
706 self.encryption = encryption;
707 self
708 }
709
710 /// Sets the [`CipherMode`].
711 #[inline]
712 pub fn cipher_mode(&mut self, cipher_mode: CipherMode) -> &mut Self {
713 self.cipher_mode = cipher_mode;
714 self
715 }
716
717 /// Sets the [`HashAlgorithm`].
718 #[inline]
719 pub fn hash_algorithm(&mut self, algorithm: HashAlgorithm) -> &mut Self {
720 self.hash_algorithm = algorithm;
721 self
722 }
723
724 /// Sets the password.
725 ///
726 /// Accepts both UTF-8 strings and arbitrary byte slices.
727 ///
728 /// # Examples
729 /// ```rust
730 /// use libpna::WriteOptions;
731 ///
732 /// // String password
733 /// WriteOptions::builder().password(Some("my_password"));
734 ///
735 /// // Byte slice password
736 /// WriteOptions::builder().password(Some(b"binary_password"));
737 /// WriteOptions::builder().password(Some(&[0x01, 0x02, 0x03, 0x04]));
738 /// ```
739 #[inline]
740 pub fn password<B: AsRef<[u8]>>(&mut self, password: Option<B>) -> &mut Self {
741 self.password = password.map(|it| it.as_ref().to_vec());
742 self
743 }
744
745 /// Creates a new [`WriteOptions`] from this builder.
746 ///
747 /// This finalizes the builder configuration and creates an immutable [`WriteOptions`]
748 /// that can be used when creating entries.
749 ///
750 /// # Panics
751 ///
752 /// Panics if [`encryption()`](Self::encryption) was set to [`Encryption::Aes`] or
753 /// [`Encryption::Camellia`] but [`password()`](Self::password) was not called with
754 /// a password.
755 ///
756 /// **Always provide a password when enabling encryption.** The following code will panic:
757 /// ```no_run
758 /// use libpna::{WriteOptions, Encryption};
759 ///
760 /// let opts = WriteOptions::builder()
761 /// .encryption(Encryption::Aes)
762 /// .build(); // PANICS: "Password was not provided."
763 /// ```
764 ///
765 /// **Correct usage:**
766 /// ```rust
767 /// use libpna::{WriteOptions, Encryption};
768 ///
769 /// let opts = WriteOptions::builder()
770 /// .encryption(Encryption::Aes)
771 /// .password(Some("secure_password"))
772 /// .build(); // OK
773 /// ```
774 #[inline]
775 #[must_use = "building options without using them is wasteful"]
776 pub fn build(&self) -> WriteOptions {
777 let cipher = if self.encryption != Encryption::No {
778 Some(Cipher::new(
779 self.password
780 .as_deref()
781 .expect("Password was not provided.")
782 .into(),
783 self.hash_algorithm,
784 match self.encryption {
785 Encryption::Aes => CipherAlgorithm::Aes,
786 Encryption::Camellia => CipherAlgorithm::Camellia,
787 Encryption::No => unreachable!(),
788 },
789 self.cipher_mode,
790 ))
791 } else {
792 None
793 };
794 WriteOptions {
795 compress: match self.compression {
796 Compression::No => Compress::No,
797 Compression::Deflate => Compress::Deflate(self.compression_level.into()),
798 Compression::ZStandard => Compress::ZStandard(self.compression_level.into()),
799 Compression::XZ => Compress::XZ(self.compression_level.into()),
800 },
801 cipher,
802 }
803 }
804}
805
806/// Options for reading an entry.
807#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
808pub struct ReadOptions {
809 password: Option<Vec<u8>>,
810}
811
812impl ReadOptions {
813 /// Creates a new [`ReadOptions`] with an optional password.
814 ///
815 /// Accepts both UTF-8 strings and arbitrary byte slices.
816 ///
817 /// # Examples
818 /// ```rust
819 /// use libpna::ReadOptions;
820 ///
821 /// // String password
822 /// let read_option = ReadOptions::with_password(Some("password"));
823 ///
824 /// // Byte slice password
825 /// let read_option = ReadOptions::with_password(Some(b"password"));
826 /// let read_option = ReadOptions::with_password(Some(&[0x01, 0x02, 0x03]));
827 /// ```
828 #[inline]
829 pub fn with_password<B: AsRef<[u8]>>(password: Option<B>) -> Self {
830 Self {
831 password: password.map(|p| p.as_ref().to_vec()),
832 }
833 }
834
835 /// Returns a builder for [ReadOptions].
836 ///
837 /// # Returns
838 ///
839 /// [ReadOptionsBuilder]: Builder object for [ReadOptions].
840 ///
841 /// # Examples
842 /// ```rust
843 /// use libpna::ReadOptions;
844 ///
845 /// let builder = ReadOptions::builder();
846 /// ```
847 #[inline]
848 pub const fn builder() -> ReadOptionsBuilder {
849 ReadOptionsBuilder::new()
850 }
851
852 /// Converts [ReadOptions] into a [ReadOptionsBuilder].
853 ///
854 /// # Returns
855 ///
856 /// [ReadOptionsBuilder]: Builder object for [ReadOptions].
857 ///
858 /// # Examples
859 /// ```rust
860 /// use libpna::ReadOptions;
861 ///
862 /// let read_option = ReadOptions::builder().build();
863 /// let builder = read_option.into_builder();
864 /// ```
865 #[inline]
866 pub fn into_builder(self) -> ReadOptionsBuilder {
867 self.into()
868 }
869}
870
871/// Builder for [`ReadOptions`].
872#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
873pub struct ReadOptionsBuilder {
874 password: Option<Vec<u8>>,
875}
876
877impl From<ReadOptions> for ReadOptionsBuilder {
878 #[inline]
879 fn from(value: ReadOptions) -> Self {
880 Self {
881 password: value.password,
882 }
883 }
884}
885
886impl ReadOptionsBuilder {
887 #[inline]
888 const fn new() -> Self {
889 Self { password: None }
890 }
891
892 /// Creates a new [`ReadOptions`].
893 #[inline]
894 #[must_use = "building options without using them is wasteful"]
895 pub fn build(&self) -> ReadOptions {
896 ReadOptions {
897 password: self.password.clone(),
898 }
899 }
900}