use super::types::{BLOB_SIZE_C_BIT, BROTLI_MAGIC, XZ_MAGIC};
use crate::crypto;
use anyhow::{Context, Result};
use super::types::{Compression, Encryption, StoreOptions};
pub(super) fn compress_and_encrypt(
name: &str,
payload: &[u8],
options: &StoreOptions<'_>,
store_key_slot: u8,
) -> Result<(Vec<u8>, u8, bool)> {
let (candidate, is_compressed) = match options.compression {
Compression::None => (payload.to_vec(), false),
Compression::Auto => compress_payload(name, payload)?,
};
let (data, blob_key_slot) = match options.encryption {
Encryption::Encrypted(pk) => {
let enc = crypto::hybrid_encrypt(&candidate, pk).context("encrypting blob")?;
(enc, store_key_slot)
}
Encryption::None => (candidate, 0u8),
};
if data.len() & BLOB_SIZE_C_BIT != 0 {
anyhow::bail!(
"blob '{}': stored size too large ({} bytes, max 0x7FFFFF)",
name,
data.len()
);
}
Ok((data, blob_key_slot, is_compressed))
}
pub(super) fn compress_payload(name: &str, payload: &[u8]) -> Result<(Vec<u8>, bool)> {
use std::io::Write as _;
let mut brotli_body = Vec::new();
{
let mut enc = brotli::CompressorWriter::new(&mut brotli_body, 4096, 11, 22);
enc.write_all(payload)
.with_context(|| format!("brotli compress blob '{name}'"))?;
}
let mut brotli_out = Vec::with_capacity(BROTLI_MAGIC.len() + brotli_body.len());
brotli_out.extend_from_slice(BROTLI_MAGIC);
brotli_out.extend_from_slice(&brotli_body);
let mut xz_out = Vec::new();
{
let mut enc = lzma_rust2::XzWriter::new(&mut xz_out, lzma_rust2::XzOptions::with_preset(9))
.with_context(|| format!("xz compress blob '{name}'"))?;
enc.write_all(payload)
.with_context(|| format!("xz compress blob '{name}'"))?;
enc.finish()
.with_context(|| format!("xz compress blob '{name}'"))?;
}
if !xz_out.starts_with(XZ_MAGIC) {
anyhow::bail!("xz output for blob '{name}' has unexpected magic — library bug");
}
let pick = if brotli_out.len() <= xz_out.len() {
brotli_out
} else {
xz_out
};
if pick.len() < payload.len() {
Ok((pick, true))
} else {
Ok((payload.to_vec(), false))
}
}
pub(super) fn decompress_payload(name: &str, payload: &[u8]) -> Result<Vec<u8>> {
use std::io::Read as _;
if payload.starts_with(XZ_MAGIC) {
let mut out = Vec::new();
let mut dec = lzma_rust2::XzReader::new(payload, false);
dec.read_to_end(&mut out)
.with_context(|| format!("decompressing blob '{name}'"))?;
return Ok(out);
}
if payload.starts_with(BROTLI_MAGIC) {
let compressed = &payload[BROTLI_MAGIC.len()..];
let mut dec = brotli::Decompressor::new(compressed, 4096);
let mut out = Vec::new();
dec.read_to_end(&mut out)
.with_context(|| format!("decompressing blob '{name}'"))?;
return Ok(out);
}
anyhow::bail!("unknown compression format in blob '{name}'")
}