#[cfg(zstd_any)]
use crate::compression::ZstdDictionary;
use crate::{CompressionType, encryption::EncryptionProvider};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub enum EccParams {
Shard {
data_shards: u8,
parity_shards: u8,
},
Secded,
}
impl EccParams {
pub const RS_4_2: Self = Self::Shard {
data_shards: 4,
parity_shards: 2,
};
pub const SECDED: Self = Self::Secded;
pub fn try_new(data_shards: u8, parity_shards: u8) -> crate::Result<Self> {
if data_shards == 0 {
return Err(crate::Error::FeatureUnsupported("ecc_scheme data_shards=0"));
}
if parity_shards == 0 {
return Err(crate::Error::FeatureUnsupported(
"ecc_scheme parity_shards=0",
));
}
Ok(Self::Shard {
data_shards,
parity_shards,
})
}
#[must_use]
pub const fn data_shards(self) -> u8 {
match self {
Self::Shard { data_shards, .. } => data_shards,
Self::Secded => panic!("EccParams::data_shards on a non-shard (SEC-DED) scheme"),
}
}
#[must_use]
pub const fn parity_shards(self) -> u8 {
match self {
Self::Shard { parity_shards, .. } => parity_shards,
Self::Secded => panic!("EccParams::parity_shards on a non-shard (SEC-DED) scheme"),
}
}
#[must_use]
pub const fn as_shards(self) -> (usize, usize) {
match self {
Self::Shard {
data_shards,
parity_shards,
} => (data_shards as usize, parity_shards as usize),
Self::Secded => panic!("EccParams::as_shards on a non-shard (SEC-DED) scheme"),
}
}
}
pub struct CompressionContext<'a> {
kind: CompressionType,
#[cfg(zstd_any)]
zstd_dict: Option<&'a ZstdDictionary>,
#[cfg(not(zstd_any))]
_lifetime: core::marker::PhantomData<&'a ()>,
}
#[cfg_attr(
not(zstd_any),
expect(
clippy::elidable_lifetime_names,
reason = "'a kept for cross-feature-matrix signature stability; \
used by with_dict under any zstd feature"
)
)]
impl<'a> CompressionContext<'a> {
pub fn new(kind: CompressionType) -> crate::Result<Self> {
if kind == CompressionType::None {
return Err(crate::Error::FeatureUnsupported("compression-context-none"));
}
#[cfg(zstd_any)]
if matches!(kind, CompressionType::ZstdDict { .. }) {
return Err(crate::Error::FeatureUnsupported(
"compression-context-zstd-dict-via-new",
));
}
Ok(Self {
kind,
#[cfg(zstd_any)]
zstd_dict: None,
#[cfg(not(zstd_any))]
_lifetime: core::marker::PhantomData,
})
}
#[cfg(zstd_any)]
#[must_use]
pub fn with_dict(level: i32, dict: &'a ZstdDictionary) -> Self {
Self {
kind: CompressionType::ZstdDict {
level,
dict_id: dict.id(),
},
zstd_dict: Some(dict),
}
}
#[must_use]
pub fn kind(&self) -> CompressionType {
self.kind
}
#[cfg(zstd_any)]
#[must_use]
pub fn zstd_dict(&self) -> Option<&ZstdDictionary> {
self.zstd_dict
}
}
pub enum BlockTransform<'a> {
Plain,
Compressed(CompressionContext<'a>),
Encrypted(&'a dyn EncryptionProvider),
CompressedAndEncrypted(CompressionContext<'a>, &'a dyn EncryptionProvider),
#[cfg(feature = "page_ecc")]
PlainEcc(EccParams),
#[cfg(feature = "page_ecc")]
CompressedEcc(CompressionContext<'a>, EccParams),
#[cfg(feature = "page_ecc")]
EncryptedEcc(&'a dyn EncryptionProvider, EccParams),
#[cfg(feature = "page_ecc")]
CompressedAndEncryptedEcc(
CompressionContext<'a>,
&'a dyn EncryptionProvider,
EccParams,
),
}
impl BlockTransform<'_> {
pub const PLAIN: Self = Self::Plain;
#[must_use]
pub fn compression(&self) -> CompressionType {
match self {
Self::Plain | Self::Encrypted(_) => CompressionType::None,
Self::Compressed(ctx) | Self::CompressedAndEncrypted(ctx, _) => ctx.kind(),
#[cfg(feature = "page_ecc")]
Self::PlainEcc(_) | Self::EncryptedEcc(_, _) => CompressionType::None,
#[cfg(feature = "page_ecc")]
Self::CompressedEcc(ctx, _) | Self::CompressedAndEncryptedEcc(ctx, _, _) => ctx.kind(),
}
}
#[cfg(zstd_any)]
#[must_use]
pub fn zstd_dict(&self) -> Option<&ZstdDictionary> {
match self {
Self::Plain | Self::Encrypted(_) => None,
Self::Compressed(ctx) | Self::CompressedAndEncrypted(ctx, _) => ctx.zstd_dict(),
#[cfg(feature = "page_ecc")]
Self::PlainEcc(_) | Self::EncryptedEcc(_, _) => None,
#[cfg(feature = "page_ecc")]
Self::CompressedEcc(ctx, _) | Self::CompressedAndEncryptedEcc(ctx, _, _) => {
ctx.zstd_dict()
}
}
}
#[must_use]
pub fn encryption(&self) -> Option<&dyn EncryptionProvider> {
match self {
Self::Plain | Self::Compressed(_) => None,
Self::Encrypted(enc) | Self::CompressedAndEncrypted(_, enc) => Some(*enc),
#[cfg(feature = "page_ecc")]
Self::PlainEcc(_) | Self::CompressedEcc(_, _) => None,
#[cfg(feature = "page_ecc")]
Self::EncryptedEcc(enc, _) | Self::CompressedAndEncryptedEcc(_, enc, _) => Some(*enc),
}
}
#[must_use]
pub fn page_ecc(&self) -> bool {
match self {
Self::Plain
| Self::Compressed(_)
| Self::Encrypted(_)
| Self::CompressedAndEncrypted(_, _) => false,
#[cfg(feature = "page_ecc")]
Self::PlainEcc(_)
| Self::CompressedEcc(_, _)
| Self::EncryptedEcc(_, _)
| Self::CompressedAndEncryptedEcc(_, _, _) => true,
}
}
#[must_use]
pub fn ecc_params(&self) -> Option<EccParams> {
match self {
Self::Plain
| Self::Compressed(_)
| Self::Encrypted(_)
| Self::CompressedAndEncrypted(_, _) => None,
#[cfg(feature = "page_ecc")]
Self::PlainEcc(p)
| Self::CompressedEcc(_, p)
| Self::EncryptedEcc(_, p)
| Self::CompressedAndEncryptedEcc(_, _, p) => Some(*p),
}
}
#[cfg(feature = "page_ecc")]
#[must_use]
pub fn with_ecc(self, params: EccParams) -> Self {
match self {
Self::Plain | Self::PlainEcc(_) => Self::PlainEcc(params),
Self::Compressed(ctx) | Self::CompressedEcc(ctx, _) => Self::CompressedEcc(ctx, params),
Self::Encrypted(enc) | Self::EncryptedEcc(enc, _) => Self::EncryptedEcc(enc, params),
Self::CompressedAndEncrypted(ctx, enc)
| Self::CompressedAndEncryptedEcc(ctx, enc, _) => {
Self::CompressedAndEncryptedEcc(ctx, enc, params)
}
}
}
#[cfg(not(feature = "page_ecc"))]
#[must_use]
pub fn with_ecc(self, _params: EccParams) -> Self {
self
}
}
impl<'a> BlockTransform<'a> {
pub fn from_parts(
compression: CompressionType,
encryption: Option<&'a dyn EncryptionProvider>,
#[cfg(zstd_any)] zstd_dict: Option<&'a ZstdDictionary>,
) -> crate::Result<Self> {
if compression == CompressionType::None {
return Ok(match encryption {
Some(enc) => Self::Encrypted(enc),
None => Self::Plain,
});
}
#[cfg(zstd_any)]
let ctx = if let CompressionType::ZstdDict { level, dict_id } = compression {
let dict = zstd_dict.ok_or(crate::Error::ZstdDictMismatch {
expected: dict_id,
got: None,
})?;
if dict.id() != dict_id {
return Err(crate::Error::ZstdDictMismatch {
expected: dict_id,
got: Some(dict.id()),
});
}
CompressionContext::with_dict(level, dict)
} else {
let _ = zstd_dict;
CompressionContext::new(compression)?
};
#[cfg(not(zstd_any))]
let ctx = CompressionContext::new(compression)?;
Ok(match encryption {
Some(enc) => Self::CompressedAndEncrypted(ctx, enc),
None => Self::Compressed(ctx),
})
}
}
#[cfg(test)]
#[expect(
clippy::expect_used,
reason = "tests panic on the unhappy paths to surface failures loudly"
)]
mod tests;