use super::access::ResolvedAccess;
use super::index::ArchiveIndex;
use super::planning::{PlannedBlock, ReadPlan, check_block_limits};
#[cfg(feature = "crypt4gh")]
use super::reader::AccessKeys;
use super::reader::{ArchiveEntry, BlockListForm, ContentAvailability, OpenLimits, archive_entry};
use super::snapshot::AppendSnapshot;
use super::types::{ArchivePath, FileId, ReadRange, RecipientPair, Span};
use super::validation::IndexLimits;
use crate::block;
use crate::error::PithosError;
use crate::format::block::{BlockIndexEntry, BlockLocation, ProcessingFlags};
use crate::format::header::FormatVersion;
use std::collections::{BTreeMap, HashSet};
use std::ops::Range;
use zeroize::Zeroizing;
pub struct ArchiveView {
pub(super) archive_len: u64,
pub(super) version: FormatVersion,
pub(super) metadata_digest: [u8; 32],
pub(super) terminal_directory: Span,
pub(super) index: ArchiveIndex,
pub(super) recipient_pairs: HashSet<RecipientPair>,
pub(super) index_limits: IndexLimits,
pub(super) access: ResolvedAccess,
#[cfg(feature = "crypt4gh")]
pub(super) access_keys: AccessKeys,
pub(super) limits: OpenLimits,
pub(super) content_availability: BTreeMap<FileId, ContentAvailability>,
pub(super) block_lists: BTreeMap<FileId, BlockListForm>,
}
impl ArchiveView {
pub fn metadata_digest(&self) -> [u8; 32] {
self.metadata_digest
}
pub fn version(&self) -> u16 {
self.version.wire()
}
pub fn entries(&self) -> impl ExactSizeIterator<Item = ArchiveEntry> + '_ {
self.index.entries().map(|entry| {
archive_entry(
&self.index,
entry,
&self.content_availability,
&self.block_lists,
)
})
}
pub fn entry(&self, path: &str) -> Result<Option<ArchiveEntry>, PithosError> {
let path = ArchivePath::new(path)?;
Ok(self.index.entry_at_path(&path).map(|entry| {
archive_entry(
&self.index,
entry,
&self.content_availability,
&self.block_lists,
)
}))
}
pub fn plan_range(&self, path: &str, range: Range<u64>) -> Result<ReadPlan<'_>, PithosError> {
let (id, size) = self.content_id(path)?;
let range = ReadRange::new(range, size)?;
self.require_content_available(id)?;
self.plan(id, range)
}
pub(crate) fn plan(&self, id: FileId, range: ReadRange) -> Result<ReadPlan<'_>, PithosError> {
ReadPlan::new(&self.index, id, range, self.block_limits())
}
pub fn decode_block(
&self,
block: &PlannedBlock,
stored: &[u8],
) -> Result<Zeroizing<Vec<u8>>, PithosError> {
check_block_limits(&block.descriptor, self.block_limits())?;
let expected = block.framed_len();
if stored.len() as u64 != expected {
return Err(PithosError::BlockSizeMismatch {
expected,
actual: stored.len() as u64,
});
}
let (mut marker, payload) = stored.split_at(4);
crate::format::block::decode_block_marker(&mut marker)?;
let key = self
.access
.block_key(block.file, block.position, block.hash)
.ok_or(PithosError::ContentUnavailable)?;
let meta = BlockIndexEntry {
offset: 0,
stored_size: block.descriptor.stored_size,
original_size: block.descriptor.original_size,
flags: ProcessingFlags::from_byte(block.descriptor.processing.to_byte()),
location: BlockLocation::Local,
};
block::verify(
Zeroizing::new(payload.to_vec()),
&key,
block.hash.0,
&meta,
self.block_limits(),
)
}
pub(crate) fn block_limits(&self) -> block::Limits {
block::Limits {
max_stored_bytes: self.limits.max_stored_block_bytes,
max_decoded_bytes: self.limits.max_decoded_block_bytes,
}
}
pub(crate) fn into_append_snapshot(self) -> AppendSnapshot {
let Self {
archive_len,
version,
terminal_directory,
index,
recipient_pairs,
index_limits,
access,
..
} = self;
AppendSnapshot::new(
archive_len,
version,
terminal_directory,
index,
recipient_pairs,
index_limits,
access,
)
}
pub(crate) fn content_id(&self, path: &str) -> Result<(FileId, u64), PithosError> {
let path = ArchivePath::new(path)?;
let entry = self
.index
.entry_at_path(&path)
.ok_or_else(|| PithosError::FileNotFound(path.as_str().to_owned()))?;
let content = entry.entry.content().ok_or_else(|| {
PithosError::InvalidBlockDataState("only data/metadata entries have content".into())
})?;
Ok((entry.id, content.size))
}
#[cfg(feature = "crypt4gh")]
pub(crate) fn content_size(&self, id: FileId) -> Result<u64, PithosError> {
self.index
.entry(id)
.and_then(|entry| entry.entry.content())
.map(|content| content.size)
.ok_or_else(|| {
PithosError::InvalidBlockDataState("only data/metadata entries have content".into())
})
}
pub(crate) fn require_content_available(&self, id: FileId) -> Result<(), PithosError> {
match self.content_availability.get(&id).copied() {
Some(ContentAvailability::Available) => Ok(()),
Some(ContentAvailability::MissingAccess) => Err(PithosError::ContentUnavailable),
Some(ContentAvailability::Unsupported(feature)) => {
Err(PithosError::UnsupportedFeature(feature))
}
None => Err(PithosError::InvalidBlockDataState(
"only data/metadata entries have content".into(),
)),
}
}
}