use anyhow::bail;
pub const CHUNK_CODEC_TAG: &str = "chunk_codec";
#[cfg(feature = "zstd")]
const ZSTD_LEVEL: i32 = 3;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ChunkCodec {
#[default]
None,
Lz4,
Zstd,
}
impl ChunkCodec {
pub fn from_u8(v: u8) -> anyhow::Result<Self> {
Ok(match v {
0 => ChunkCodec::None,
1 => ChunkCodec::Lz4,
2 => ChunkCodec::Zstd,
x => bail!("unknown RAD chunk codec id {x}; this build cannot read it"),
})
}
pub fn as_u8(self) -> u8 {
match self {
ChunkCodec::None => 0,
ChunkCodec::Lz4 => 1,
ChunkCodec::Zstd => 2,
}
}
}
pub fn compress_payload(codec: ChunkCodec, payload: &[u8]) -> anyhow::Result<Vec<u8>> {
match codec {
ChunkCodec::None => Ok(payload.to_vec()),
ChunkCodec::Lz4 => Ok(lz4_flex::compress_prepend_size(payload)),
ChunkCodec::Zstd => {
#[cfg(feature = "zstd")]
{
Ok(zstd::encode_all(payload, ZSTD_LEVEL)?)
}
#[cfg(not(feature = "zstd"))]
{
bail!("writing zstd-compressed chunks requires the libradicl `zstd` feature")
}
}
}
}
pub fn decompress_payload(codec: ChunkCodec, data: &[u8]) -> anyhow::Result<Vec<u8>> {
match codec {
ChunkCodec::None => Ok(data.to_vec()),
ChunkCodec::Lz4 => lz4_flex::decompress_size_prepended(data)
.map_err(|e| anyhow::anyhow!("lz4 chunk decompression failed: {e}")),
ChunkCodec::Zstd => {
#[cfg(feature = "zstd")]
{
Ok(zstd::decode_all(data)?)
}
#[cfg(not(feature = "zstd"))]
{
bail!(
"this RAD file uses zstd-compressed chunks (codec id 2), but this build of \
libradicl was compiled without the `zstd` feature"
)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lz4_roundtrip() {
let data: Vec<u8> = (0..10_000u32)
.flat_map(|i| (i as u16).to_le_bytes())
.collect();
let c = compress_payload(ChunkCodec::Lz4, &data).unwrap();
let d = decompress_payload(ChunkCodec::Lz4, &c).unwrap();
assert_eq!(d, data);
}
#[test]
fn codec_id_roundtrip() {
for c in [ChunkCodec::None, ChunkCodec::Lz4, ChunkCodec::Zstd] {
assert_eq!(ChunkCodec::from_u8(c.as_u8()).unwrap(), c);
}
assert!(ChunkCodec::from_u8(7).is_err());
}
#[cfg(feature = "zstd")]
#[test]
fn zstd_roundtrip() {
let data: Vec<u8> = (0..10_000u32)
.flat_map(|i| (i as u16).to_le_bytes())
.collect();
let c = compress_payload(ChunkCodec::Zstd, &data).unwrap();
let d = decompress_payload(ChunkCodec::Zstd, &c).unwrap();
assert_eq!(d, data);
}
#[cfg(not(feature = "zstd"))]
#[test]
fn zstd_without_feature_errors() {
let err = decompress_payload(ChunkCodec::Zstd, &[1, 2, 3]).unwrap_err();
assert!(
err.to_string().contains("zstd"),
"error should mention the missing zstd feature: {err}"
);
assert!(compress_payload(ChunkCodec::Zstd, &[1, 2, 3]).is_err());
}
}