use crate::error::PithosError;
use crate::format::block::ProcessingFlags;
use crate::format::header::FormatVersion;
use std::cmp::Ordering;
use std::fmt::{Debug, Formatter};
use std::ops::Range;
use std::sync::Arc;
#[derive(Clone, Eq, Hash, PartialEq)]
pub struct ArchivePath(Arc<str>);
impl ArchivePath {
pub fn new(path: impl AsRef<str>) -> Result<Self, PithosError> {
let path = path.as_ref();
if path.is_empty() {
return Err(invalid_path(path, "path is empty"));
}
if path.contains('\0') {
return Err(invalid_path(path, "NUL is not allowed"));
}
if path.contains('\\') {
return Err(invalid_path(path, "backslash is not allowed"));
}
if path.starts_with('/') || path.ends_with('/') {
return Err(invalid_path(path, "path must not start or end with /"));
}
if path.as_bytes().get(1) == Some(&b':') {
return Err(invalid_path(path, "drive forms are not allowed"));
}
if path
.split('/')
.any(|part| part.is_empty() || part == "." || part == "..")
{
return Err(invalid_path(
path,
"empty or dot path components are not allowed",
));
}
Ok(Self(Arc::from(path)))
}
pub fn as_str(&self) -> &str {
&self.0
}
pub(crate) fn is_ancestor_of(&self, other: &Self) -> bool {
other
.as_str()
.strip_prefix(self.as_str())
.is_some_and(|suffix| suffix.starts_with('/'))
}
}
impl Debug for ArchivePath {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ArchivePath").field(&self.as_str()).finish()
}
}
impl Ord for ArchivePath {
fn cmp(&self, other: &Self) -> Ordering {
self.as_str().split('/').cmp(other.as_str().split('/'))
}
}
impl PartialOrd for ArchivePath {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
fn invalid_path(path: &str, reason: impl Into<String>) -> PithosError {
PithosError::InvalidArchivePath {
path: path.to_owned(),
reason: reason.into(),
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub(crate) struct FileId(pub(crate) u64);
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub(crate) struct RelationId(pub(crate) u64);
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub(crate) struct BlockHash(pub(crate) [u8; 32]);
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub(crate) struct Span {
start: u64,
end: u64,
}
impl Span {
pub(crate) fn new(start: u64, len: u64) -> Result<Self, PithosError> {
let end = start
.checked_add(len)
.ok_or(PithosError::InvalidDirectoryRange {
operation: "construct span",
})?;
Ok(Self { start, end })
}
pub(crate) fn start(self) -> u64 {
self.start
}
pub(crate) fn end(self) -> u64 {
self.end
}
pub(crate) fn len(self) -> u64 {
self.end - self.start
}
pub(crate) fn overlaps(self, other: Self) -> bool {
self.start < other.end && other.start < self.end
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct ReadRange {
start: u64,
end: u64,
}
impl ReadRange {
pub(crate) fn new(range: Range<u64>, file_size: u64) -> Result<Self, PithosError> {
if range.start > range.end || range.end > file_size {
return Err(PithosError::InvalidReadRange {
start: range.start,
end: range.end,
file_size,
});
}
Ok(Self {
start: range.start,
end: range.end,
})
}
pub(crate) fn start(self) -> u64 {
self.start
}
pub(crate) fn end(self) -> u64 {
self.end
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct Compression(u8);
impl Compression {
pub(crate) fn new(level: u8) -> Result<Self, PithosError> {
if level <= 7 {
Ok(Self(level))
} else {
Err(PithosError::ReservedProcessingBits(level))
}
}
pub(crate) fn level(self) -> u8 {
self.0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct Processing {
compression: Compression,
encrypted: bool,
unique_key: bool,
aes_256_gcm: bool,
}
impl Processing {
pub(crate) fn from_byte(value: u8, version: FormatVersion) -> Result<Self, PithosError> {
if value & ProcessingFlags::RESERVED_MASK != 0 {
return Err(PithosError::ReservedProcessingBits(value));
}
let encrypted = value & 0x08 != 0;
if value & ProcessingFlags::VERSION_1_1_MASK != 0 {
if version != FormatVersion::V1_1 {
return Err(PithosError::UnsupportedProcessingFlags(value));
}
if !encrypted {
return Err(PithosError::ProcessingRequiresEncryption(value));
}
}
Ok(Self {
compression: Compression::new(value & 0x07)?,
encrypted,
unique_key: value & ProcessingFlags::UNIQUE_KEY_MASK != 0,
aes_256_gcm: value & ProcessingFlags::AES_256_GCM_MASK != 0,
})
}
pub(crate) fn to_byte(self) -> u8 {
self.compression.level()
| (u8::from(self.encrypted) << 3)
| (u8::from(self.unique_key) << 4)
| (u8::from(self.aes_256_gcm) << 5)
}
}
#[derive(Clone, Eq, PartialEq)]
pub struct ExternalLocation(Arc<str>);
impl ExternalLocation {
pub(crate) fn new(value: impl AsRef<str>) -> Self {
Self(Arc::from(value.as_ref()))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Debug for ExternalLocation {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str("ExternalLocation(..)")
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum BlockLocation {
Local(Span),
External(ExternalLocation),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct BlockDescriptor {
pub(crate) stored_size: u64,
pub(crate) original_size: u64,
pub(crate) processing: Processing,
pub(crate) location: BlockLocation,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Reference {
pub(crate) target: FileId,
pub(crate) relationship: RelationId,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct EntryMetadata {
pub(crate) created: u64,
pub(crate) modified: u64,
pub(crate) permissions: u32,
pub(crate) references: Vec<Reference>,
}
pub(crate) const CHECKPOINT_STRIDE: usize = 1024;
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct BlockReferences {
blocks: Vec<BlockHash>,
checkpoints: Vec<u64>,
}
impl BlockReferences {
pub(crate) fn new(blocks: Vec<BlockHash>) -> Self {
Self {
blocks,
checkpoints: Vec::new(),
}
}
pub(crate) fn iter(&self) -> impl ExactSizeIterator<Item = BlockHash> + '_ {
self.blocks.iter().copied()
}
pub(crate) fn as_slice(&self) -> &[BlockHash] {
&self.blocks
}
pub(crate) fn set_checkpoints(&mut self, checkpoints: Vec<u64>) {
self.checkpoints = checkpoints;
}
pub(crate) fn start_near(&self, offset: u64) -> (usize, u64) {
let index = self
.checkpoints
.partition_point(|checkpoint| *checkpoint <= offset)
.saturating_sub(1);
match self.checkpoints.get(index) {
Some(checkpoint) => (index * CHECKPOINT_STRIDE, *checkpoint),
None => (0, 0),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum ContentState {
Available(BlockReferences),
Unavailable,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct ContentEntry {
pub(crate) metadata: EntryMetadata,
pub(crate) size: u64,
pub(crate) content: ContentState,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum Entry {
File(ContentEntry),
Metadata(ContentEntry),
Directory(EntryMetadata),
Symlink {
metadata: EntryMetadata,
target: Arc<str>,
},
}
impl Entry {
pub(crate) fn metadata(&self) -> &EntryMetadata {
match self {
Self::File(content) | Self::Metadata(content) => &content.metadata,
Self::Directory(metadata) | Self::Symlink { metadata, .. } => metadata,
}
}
pub(crate) fn content(&self) -> Option<&ContentEntry> {
match self {
Self::File(content) | Self::Metadata(content) => Some(content),
Self::Directory(_) | Self::Symlink { .. } => None,
}
}
pub(crate) fn is_directory(&self) -> bool {
matches!(self, Self::Directory(_))
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct SegmentEntry {
pub(crate) id: FileId,
pub(crate) path: ArchivePath,
pub(crate) entry: Entry,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct ValidatedSegment {
pub(crate) span: Span,
pub(crate) parent: Option<Span>,
pub(crate) entries: Vec<SegmentEntry>,
pub(crate) descriptors: Vec<(BlockHash, BlockDescriptor)>,
pub(crate) relationships: Vec<(RelationId, Arc<str>)>,
pub(crate) recipient_pairs: Vec<RecipientPair>,
}
pub(crate) type RecipientPair = ([u8; 32], [u8; 32]);