use omnizip_filters::Filter;
use crate::codec::composite;
use crate::codec::{Codec, CodecTunables, CoreError, CODEC_LZ4, CODEC_ZSTD};
pub const CODEC_BCJ_X86_LZ4: u8 = 0x20;
pub const CODEC_BCJ_X86_ZSTD: u8 = 0x21;
pub const CODEC_BCJ_ARM64_LZ4: u8 = 0x23;
pub const CODEC_BCJ_ARM64_ZSTD: u8 = 0x24;
const MIN_BCJ_SIZE: usize = 1024;
pub type BcjX86Lz4Codec = composite::FilterCodecComposite<omnizip_filters::BcjX86Filter>;
pub type BcjX86ZstdCodec = composite::FilterCodecComposite<omnizip_filters::BcjX86Filter>;
pub type BcjArm64Lz4Codec = composite::FilterCodecComposite<omnizip_filters::BcjArm64Filter>;
pub type BcjArm64ZstdCodec = composite::FilterCodecComposite<omnizip_filters::BcjArm64Filter>;
#[must_use]
pub fn bcj_x86_lz4() -> BcjX86Lz4Codec {
BcjX86Lz4Codec::new(
omnizip_filters::BcjX86Filter,
crate::codec::CODEC_LZ4,
CODEC_BCJ_X86_LZ4,
"bcj-x86-lz4",
MIN_BCJ_SIZE,
)
}
#[must_use]
pub fn bcj_x86_zstd() -> BcjX86ZstdCodec {
BcjX86ZstdCodec::new(
omnizip_filters::BcjX86Filter,
crate::codec::CODEC_ZSTD,
CODEC_BCJ_X86_ZSTD,
"bcj-x86-zstd",
MIN_BCJ_SIZE,
)
}
#[must_use]
pub fn bcj_arm64_lz4() -> BcjArm64Lz4Codec {
BcjArm64Lz4Codec::new(
omnizip_filters::BcjArm64Filter,
crate::codec::CODEC_LZ4,
CODEC_BCJ_ARM64_LZ4,
"bcj-arm64-lz4",
MIN_BCJ_SIZE,
)
}
#[must_use]
pub fn bcj_arm64_zstd() -> BcjArm64ZstdCodec {
BcjArm64ZstdCodec::new(
omnizip_filters::BcjArm64Filter,
crate::codec::CODEC_ZSTD,
CODEC_BCJ_ARM64_ZSTD,
"bcj-arm64-zstd",
MIN_BCJ_SIZE,
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codec::Codec;
fn synthetic_x86_calls(size_bytes: usize) -> Vec<u8> {
let mut out = Vec::with_capacity(size_bytes);
let targets: [u32; 4] = [0x401000, 0x401234, 0x402000, 0x405060];
let mut i = 0u32;
while out.len() + 5 <= size_bytes {
let opcode = if i & 1 == 0 { 0xE8 } else { 0xE9 };
let target = targets[(i as usize) & 3];
let here = out.len() as u32 + 5;
let rel: i32 = (target as i32) - (here as i32);
out.push(opcode);
out.extend_from_slice(&rel.to_le_bytes());
i += 1;
}
out
}
#[test]
fn bcj_x86_lz4_round_trips() {
let input = synthetic_x86_calls(64 * 1024);
let codec = bcj_x86_lz4();
let compressed = codec.compress(&input).expect("compress");
let recovered = codec
.decompress(&compressed, input.len() as u32)
.expect("decompress");
assert_eq!(recovered, input);
}
#[test]
fn bcj_x86_lz4_beats_plain_lz4_on_synthetic_exec() {
let input = synthetic_x86_calls(64 * 1024);
let plain = crate::codec::compress(CODEC_LZ4, &input).expect("plain lz4");
let bcj = bcj_x86_lz4().compress(&input).expect("bcj+lz4");
assert!(
bcj.len() < plain.len(),
"BCJ+LZ4 ({}) should beat plain LZ4 ({}) on synthetic x86 calls",
bcj.len(),
plain.len()
);
}
#[test]
fn bcj_x86_zstd_round_trips() {
let input = synthetic_x86_calls(32 * 1024);
let codec = bcj_x86_zstd();
let compressed = codec.compress(&input).expect("compress");
let recovered = codec
.decompress(&compressed, input.len() as u32)
.expect("decompress");
assert_eq!(recovered, input);
}
#[test]
fn bcj_arm64_lz4_round_trips() {
let input = synthetic_x86_calls(32 * 1024);
let codec = bcj_arm64_lz4();
let compressed = codec.compress(&input).expect("compress");
let recovered = codec
.decompress(&compressed, input.len() as u32)
.expect("decompress");
assert_eq!(recovered, input);
}
#[test]
fn bcj_x86_lz4_handles_tiny_input() {
let input = b"hello world";
let codec = bcj_x86_lz4();
let compressed = codec.compress(input).expect("compress");
let recovered = codec
.decompress(&compressed, input.len() as u32)
.expect("decompress");
assert_eq!(recovered.as_slice(), &input[..]);
}
}