use crate::{compress, error::UnknownValueError};
pub(crate) use private::*;
use std::str::FromStr;
mod private {
use super::*;
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum Compress {
No,
Deflate(compress::deflate::DeflateCompressionLevel),
ZStandard(compress::zstandard::ZstdCompressionLevel),
XZ(compress::xz::XZCompressionLevel),
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct Cipher {
pub(crate) password: Password,
pub(crate) hash_algorithm: HashAlgorithm,
pub(crate) cipher_algorithm: CipherAlgorithm,
pub(crate) mode: CipherMode,
}
impl Cipher {
#[inline]
pub(crate) const fn new(
password: Password,
hash_algorithm: HashAlgorithm,
cipher_algorithm: CipherAlgorithm,
mode: CipherMode,
) -> Self {
Self {
password,
hash_algorithm,
cipher_algorithm,
mode,
}
}
}
pub trait WriteOption {
fn compress(&self) -> Compress;
fn cipher(&self) -> Option<&Cipher>;
#[inline]
fn compression(&self) -> Compression {
match self.compress() {
Compress::No => Compression::No,
Compress::Deflate(_) => Compression::Deflate,
Compress::ZStandard(_) => Compression::ZStandard,
Compress::XZ(_) => Compression::XZ,
}
}
#[inline]
fn encryption(&self) -> Encryption {
self.cipher()
.map_or(Encryption::No, |it| match it.cipher_algorithm {
CipherAlgorithm::Aes => Encryption::Aes,
CipherAlgorithm::Camellia => Encryption::Camellia,
})
}
#[inline]
fn cipher_mode(&self) -> CipherMode {
self.cipher().map_or(CipherMode::CTR, |it| it.mode)
}
#[inline]
fn hash_algorithm(&self) -> HashAlgorithm {
self.cipher()
.map_or_else(HashAlgorithm::argon2id, |it| it.hash_algorithm)
}
#[inline]
fn password(&self) -> Option<&[u8]> {
self.cipher().map(|it| it.password.as_bytes())
}
}
impl WriteOption for WriteOptions {
#[inline]
fn compress(&self) -> Compress {
self.compress
}
#[inline]
fn cipher(&self) -> Option<&Cipher> {
self.cipher.as_ref()
}
}
impl<T> WriteOption for &T
where
T: WriteOption,
{
#[inline]
fn compress(&self) -> Compress {
T::compress(self)
}
#[inline]
fn cipher(&self) -> Option<&Cipher> {
T::cipher(self)
}
}
pub trait ReadOption {
fn password(&self) -> Option<&[u8]>;
}
impl<T: ReadOption> ReadOption for &T {
#[inline]
fn password(&self) -> Option<&[u8]> {
T::password(self)
}
}
impl ReadOption for ReadOptions {
#[inline]
fn password(&self) -> Option<&[u8]> {
self.password.as_deref()
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[repr(u8)]
pub enum Compression {
No = 0,
Deflate = 1,
ZStandard = 2,
XZ = 4,
}
impl TryFrom<u8> for Compression {
type Error = UnknownValueError;
#[inline]
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::No),
1 => Ok(Self::Deflate),
2 => Ok(Self::ZStandard),
4 => Ok(Self::XZ),
value => Err(UnknownValueError(value)),
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub(crate) enum CompressionLevelImpl {
Min,
Max,
Default,
Custom(i64),
}
impl FromStr for CompressionLevelImpl {
type Err = core::num::ParseIntError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.eq_ignore_ascii_case("min") {
Ok(Self::Min)
} else if s.eq_ignore_ascii_case("max") {
Ok(Self::Max)
} else if s.eq_ignore_ascii_case("default") {
Ok(Self::Default)
} else {
Ok(Self::Custom(i64::from_str(s)?))
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct CompressionLevel(pub(crate) CompressionLevelImpl);
impl CompressionLevel {
pub(crate) const DEFAULT: Self = Self(CompressionLevelImpl::Default);
#[inline]
pub const fn min() -> Self {
Self(CompressionLevelImpl::Min)
}
#[inline]
pub const fn max() -> Self {
Self(CompressionLevelImpl::Max)
}
}
impl Default for CompressionLevel {
#[inline]
fn default() -> Self {
Self::DEFAULT
}
}
impl<T: Into<i64>> From<T> for CompressionLevel {
#[inline]
fn from(value: T) -> Self {
Self(CompressionLevelImpl::Custom(value.into()))
}
}
impl FromStr for CompressionLevel {
type Err = core::num::ParseIntError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(CompressionLevelImpl::from_str(s)?))
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum CipherAlgorithm {
Aes,
Camellia,
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub(crate) struct Password(Vec<u8>);
impl Password {
#[inline]
pub(crate) const fn as_bytes(&self) -> &[u8] {
self.0.as_slice()
}
}
impl<T: AsRef<[u8]>> From<T> for Password {
#[inline]
fn from(value: T) -> Self {
Self(value.as_ref().to_vec())
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[repr(u8)]
pub enum Encryption {
No = 0,
Aes = 1,
Camellia = 2,
}
impl TryFrom<u8> for Encryption {
type Error = UnknownValueError;
#[inline]
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::No),
1 => Ok(Self::Aes),
2 => Ok(Self::Camellia),
value => Err(UnknownValueError(value)),
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[repr(u8)]
pub enum CipherMode {
CBC = 0,
CTR = 1,
}
impl TryFrom<u8> for CipherMode {
type Error = UnknownValueError;
#[inline]
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::CBC),
1 => Ok(Self::CTR),
value => Err(UnknownValueError(value)),
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub(crate) enum HashAlgorithmParams {
Pbkdf2Sha256 {
rounds: Option<u32>,
},
Argon2Id {
time_cost: Option<u32>,
memory_cost: Option<u32>,
parallelism_cost: Option<u32>,
},
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct HashAlgorithm(pub(crate) HashAlgorithmParams);
impl HashAlgorithm {
#[inline]
pub const fn pbkdf2_sha256() -> Self {
Self::pbkdf2_sha256_with(None)
}
#[inline]
pub const fn pbkdf2_sha256_with(rounds: Option<u32>) -> Self {
Self(HashAlgorithmParams::Pbkdf2Sha256 { rounds })
}
#[inline]
pub const fn argon2id() -> Self {
Self::argon2id_with(None, None, None)
}
#[inline]
pub const fn argon2id_with(
time_cost: Option<u32>,
memory_cost: Option<u32>,
parallelism_cost: Option<u32>,
) -> Self {
Self(HashAlgorithmParams::Argon2Id {
time_cost,
memory_cost,
parallelism_cost,
})
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[repr(u8)]
pub enum DataKind {
File = 0,
Directory = 1,
SymbolicLink = 2,
HardLink = 3,
}
impl TryFrom<u8> for DataKind {
type Error = UnknownValueError;
#[inline]
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::File),
1 => Ok(Self::Directory),
2 => Ok(Self::SymbolicLink),
3 => Ok(Self::HardLink),
value => Err(UnknownValueError(value)),
}
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct WriteOptions {
compress: Compress,
cipher: Option<Cipher>,
}
impl WriteOptions {
#[inline]
pub const fn store() -> Self {
Self {
compress: Compress::No,
cipher: None,
}
}
#[inline]
pub const fn builder() -> WriteOptionsBuilder {
WriteOptionsBuilder::new()
}
#[inline]
pub fn into_builder(self) -> WriteOptionsBuilder {
self.into()
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct WriteOptionsBuilder {
compression: Compression,
compression_level: CompressionLevel,
encryption: Encryption,
cipher_mode: CipherMode,
hash_algorithm: HashAlgorithm,
password: Option<Vec<u8>>,
}
impl Default for WriteOptionsBuilder {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl From<WriteOptions> for WriteOptionsBuilder {
#[inline]
fn from(value: WriteOptions) -> Self {
let (compression, compression_level) = match value.compress {
Compress::No => (Compression::No, CompressionLevel::DEFAULT),
Compress::Deflate(level) => (Compression::Deflate, level.into()),
Compress::ZStandard(level) => (Compression::ZStandard, level.into()),
Compress::XZ(level) => (Compression::XZ, level.into()),
};
Self {
compression,
compression_level,
encryption: value.encryption(),
cipher_mode: value.cipher_mode(),
hash_algorithm: value.hash_algorithm(),
password: value.password().map(|p| p.to_vec()),
}
}
}
impl WriteOptionsBuilder {
const fn new() -> Self {
Self {
compression: Compression::No,
compression_level: CompressionLevel::DEFAULT,
encryption: Encryption::No,
cipher_mode: CipherMode::CTR,
hash_algorithm: HashAlgorithm::argon2id(),
password: None,
}
}
#[inline]
pub fn compression(&mut self, compression: Compression) -> &mut Self {
self.compression = compression;
self
}
#[inline]
pub fn compression_level(&mut self, compression_level: CompressionLevel) -> &mut Self {
self.compression_level = compression_level;
self
}
#[inline]
pub fn encryption(&mut self, encryption: Encryption) -> &mut Self {
self.encryption = encryption;
self
}
#[inline]
pub fn cipher_mode(&mut self, cipher_mode: CipherMode) -> &mut Self {
self.cipher_mode = cipher_mode;
self
}
#[inline]
pub fn hash_algorithm(&mut self, algorithm: HashAlgorithm) -> &mut Self {
self.hash_algorithm = algorithm;
self
}
#[inline]
pub fn password<B: AsRef<[u8]>>(&mut self, password: Option<B>) -> &mut Self {
self.password = password.map(|it| it.as_ref().to_vec());
self
}
#[inline]
#[must_use = "building options without using them is wasteful"]
pub fn build(&self) -> WriteOptions {
let cipher = if self.encryption != Encryption::No {
Some(Cipher::new(
self.password
.as_deref()
.expect("Password was not provided.")
.into(),
self.hash_algorithm,
match self.encryption {
Encryption::Aes => CipherAlgorithm::Aes,
Encryption::Camellia => CipherAlgorithm::Camellia,
Encryption::No => unreachable!(),
},
self.cipher_mode,
))
} else {
None
};
WriteOptions {
compress: match self.compression {
Compression::No => Compress::No,
Compression::Deflate => Compress::Deflate(self.compression_level.into()),
Compression::ZStandard => Compress::ZStandard(self.compression_level.into()),
Compression::XZ => Compress::XZ(self.compression_level.into()),
},
cipher,
}
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct ReadOptions {
password: Option<Vec<u8>>,
}
impl ReadOptions {
#[inline]
pub fn with_password<B: AsRef<[u8]>>(password: Option<B>) -> Self {
Self {
password: password.map(|p| p.as_ref().to_vec()),
}
}
#[inline]
pub const fn builder() -> ReadOptionsBuilder {
ReadOptionsBuilder::new()
}
#[inline]
pub fn into_builder(self) -> ReadOptionsBuilder {
self.into()
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
pub struct ReadOptionsBuilder {
password: Option<Vec<u8>>,
}
impl From<ReadOptions> for ReadOptionsBuilder {
#[inline]
fn from(value: ReadOptions) -> Self {
Self {
password: value.password,
}
}
}
impl ReadOptionsBuilder {
#[inline]
const fn new() -> Self {
Self { password: None }
}
#[inline]
#[must_use = "building options without using them is wasteful"]
pub fn build(&self) -> ReadOptions {
ReadOptions {
password: self.password.clone(),
}
}
}