use crate::check::Check;
use crate::chunks;
use crate::container::{ChunkDescriptor, ChunkType};
use crate::crypto::{self, Cipher, SessionKey};
use crate::error::{Error, Result};
use std::borrow::Cow;
pub(crate) fn stored<'a>(buf: &'a [u8], d: &ChunkDescriptor) -> Result<&'a [u8]> {
let start = d.offset as usize;
let end = start
.checked_add(d.stored_len() as usize)
.ok_or_else(|| Error::new(Check::DirChunkExtent, "chunk extent overflows"))?;
buf.get(start..end).ok_or_else(|| {
Error::new(
Check::DirChunkExtent,
format!(
"chunk {} at {start}..{end} lies outside the {} byte file",
d.chunk_type,
buf.len()
),
)
})
}
pub(crate) fn plaintext(
d: &ChunkDescriptor,
stored: &[u8],
cipher: Option<Cipher>,
key: Option<&SessionKey>,
) -> Result<Option<Vec<u8>>> {
if !d.is_encrypted() {
return Ok(Some(stored.to_vec()));
}
match (cipher, key) {
(Some(cipher), Some(key)) => Ok(Some(crypto::open(cipher, key, d.chunk_type, 0, stored)?)),
_ => Ok(None),
}
}
pub(crate) fn layr_frame<'a>(
d: &ChunkDescriptor,
unit_index: u32,
stored: &'a [u8],
cipher: Option<Cipher>,
key: Option<&SessionKey>,
) -> Result<Option<Cow<'a, [u8]>>> {
let frame = chunks::layr::frame(stored)?;
if !d.is_encrypted() {
return Ok(Some(Cow::Borrowed(frame)));
}
match (cipher, key) {
(Some(cipher), Some(key)) => {
let open = |unit: u32| crypto::open(cipher, key, ChunkType::LAYR, unit, frame);
match open(unit_index) {
Ok(plain) => Ok(Some(Cow::Owned(plain))),
Err(reason) => {
if open(0).is_ok() {
Err(Error::new(
Check::CryptUnitIndexBinding,
format!(
"the frame at directory index {unit_index} does not authenticate \
there, but does under unit index 0"
),
))
} else {
Err(Error::new(
Check::CryptTagVerify,
format!(
"the frame at directory index {unit_index} does not authenticate: \
{reason}"
),
))
}
}
}
}
_ => Ok(None),
}
}
pub(crate) fn payload(
d: &ChunkDescriptor,
stored: &[u8],
cipher: Option<Cipher>,
key: Option<&SessionKey>,
) -> Result<Option<Vec<u8>>> {
let Some(plain) = plaintext(d, stored, cipher, key)? else {
return Ok(None);
};
let compressed = d.chunk_type.is_compressed()
|| (d.chunk_type == ChunkType::EXTD && !d.is_encrypted() && d.size_compressed != 0);
if !compressed {
return Ok(Some(plain));
}
Ok(Some(chunks::decompress(&plain, d.size_uncompressed, None)?))
}