use crate::{CommitId, RealmId};
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
pub enum PagedbError {
#[error("checksum / AEAD tag verification failed")]
ChecksumFailure,
#[error("required persisted key is unavailable: mk_epoch={mk_epoch} cipher_id={cipher_id}")]
MissingPersistedKey { mk_epoch: u64, cipher_id: u8 },
#[error("corruption: {0:?}")]
Corruption(CorruptionDetail),
#[error("quota exceeded: realm={realm:?} kind={kind:?} used={used} limit={limit}")]
Quota {
realm: RealmId,
kind: QuotaKind,
used: u64,
limit: u64,
},
#[error("no space (VFS-level exhaustion)")]
NoSpace,
#[error("nonce counter exhausted (per-file 2^48 limit reached); rekey required")]
NonceCounterExhausted,
#[error("arithmetic overflow while computing {operation}")]
ArithmeticOverflow { operation: &'static str },
#[error("read-only handle")]
ReadOnly,
#[error("writer already present")]
WriterPresent,
#[error("readers present")]
ReadersPresent,
#[error("already open")]
AlreadyOpen,
#[error("path lock contention")]
AlreadyLocked,
#[error("restored directory not promoted")]
RestoredNotPromoted,
#[error("identity forked; apply_incremental refused")]
IdentityForked,
#[error("incremental snapshot is incompatible: {field}")]
SnapshotIncompatible { field: &'static str },
#[error("commit {commit:?} is durable but unpublished; reopen required")]
DurablyCommittedButUnpublished { commit: CommitId },
#[error("rekey activated a target epoch at commit {commit:?}; reopen required: {source}")]
RekeyTargetEpochActivated {
commit: CommitId,
#[source]
source: Box<PagedbError>,
},
#[error("commit {commit:?} gone; oldest_available={oldest_available:?}")]
CommitGone {
commit: CommitId,
oldest_available: CommitId,
},
#[error("not found")]
NotFound,
#[error("already linked")]
AlreadyLinked,
#[error("not linked")]
NotLinked,
#[error("name too long")]
NameTooLong,
#[error("illegal page kind for segment")]
IllegalPageKind,
#[error("payload too large")]
PayloadTooLarge,
#[error("extent must contain at least one page")]
EmptyExtent,
#[error("manifest too large")]
ManifestTooLarge,
#[error(
"mmap-view quota exceeded: segment_bytes={segment_bytes} available_bytes={available_bytes}"
)]
MmapViewQuotaExceeded {
segment_bytes: u64,
available_bytes: u64,
},
#[error("aborted (reader stall policy)")]
Aborted,
#[error("put_append called with non-monotonic key")]
AppendNotMonotonic,
#[non_exhaustive]
#[error(
"deferred-free backlog of {pages_pending} pages blocked by oldest pinning commit {oldest_pinning_commit}"
)]
DeferredFreeBacklog {
pages_pending: u64,
oldest_pinning_commit: u64,
},
#[error("free list exhausted")]
FreeListExhausted,
#[error("segment tombstone stalled by reader pin")]
SegmentTombstoneStalled,
#[error("readers pinning truncated range")]
ReadersPinningTruncatedRange,
#[error(
"rekey resume requires counterpart key for source epoch {source_epoch} and target epoch {target_epoch}"
)]
RekeyResumeKeyRequired {
source_epoch: u64,
target_epoch: u64,
},
#[error(
"rekey counterpart key does not prove source epoch {source_epoch} for target epoch {target_epoch}"
)]
RekeyCounterpartKeyInvalid {
source_epoch: u64,
target_epoch: u64,
},
#[error("recorded rekey state is invalid: {field}")]
RekeyStateInvalid { field: &'static str },
#[error("recorded rekey replacement segment {replacement_segment_id:?} is missing or invalid")]
RekeyReplacementMissing { replacement_segment_id: [u8; 16] },
#[error("unsupported by backend")]
Unsupported,
#[error("cryptographically secure randomness unavailable: {0}")]
Randomness(#[from] getrandom::Error),
#[error("io: {0}")]
Io(#[from] std::io::Error),
}
#[non_exhaustive]
#[derive(Debug)]
pub enum CorruptionDetail {
ForeignSegment {
realm_id: RealmId,
name: String,
segment_id: [u8; 16],
footer_parent_file_id: [u8; 16],
expected_parent_file_id: [u8; 16],
},
FooterUnverifiable {
realm_id: RealmId,
name: String,
segment_id: [u8; 16],
},
SegmentMetadataMismatch { field: &'static str },
SegmentGeometryInvalid { field: &'static str },
CatalogRowInvalid { field: &'static str },
SegmentMissing {
realm_id: RealmId,
name: String,
segment_id: [u8; 16],
},
StagingMissing {
realm_id: RealmId,
name: String,
segment_id: [u8; 16],
},
PageUnverifiable {
realm_id: RealmId,
segment_id: Option<[u8; 16]>,
page_id: u64,
evictable: Option<Evictable>,
},
ManifestUnverifiable {
realm_id: RealmId,
segment_id: [u8; 16],
},
HeaderUnverifiable,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QuotaKind {
Pages,
DirtyPages,
ScratchPages,
SegmentBytes,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Evictable {
Authoritative,
Replaceable,
}
impl PagedbError {
#[must_use]
pub fn corruption(detail: CorruptionDetail) -> Self {
Self::Corruption(detail)
}
#[must_use]
pub const fn segment_metadata_mismatch(field: &'static str) -> Self {
Self::Corruption(CorruptionDetail::SegmentMetadataMismatch { field })
}
#[must_use]
pub const fn segment_geometry_invalid(field: &'static str) -> Self {
Self::Corruption(CorruptionDetail::SegmentGeometryInvalid { field })
}
#[must_use]
pub const fn catalog_row_invalid(field: &'static str) -> Self {
Self::Corruption(CorruptionDetail::CatalogRowInvalid { field })
}
#[must_use]
pub const fn snapshot_incompatible(field: &'static str) -> Self {
Self::SnapshotIncompatible { field }
}
#[must_use]
pub const fn durably_committed_but_unpublished(commit: CommitId) -> Self {
Self::DurablyCommittedButUnpublished { commit }
}
#[must_use]
pub fn rekey_target_epoch_activated(commit: CommitId, source: PagedbError) -> Self {
Self::RekeyTargetEpochActivated {
commit,
source: Box::new(source),
}
}
#[must_use]
pub const fn arithmetic_overflow(operation: &'static str) -> Self {
Self::ArithmeticOverflow { operation }
}
#[must_use]
pub const fn rekey_resume_key_required(source_epoch: u64, target_epoch: u64) -> Self {
Self::RekeyResumeKeyRequired {
source_epoch,
target_epoch,
}
}
#[must_use]
pub const fn rekey_counterpart_key_invalid(source_epoch: u64, target_epoch: u64) -> Self {
Self::RekeyCounterpartKeyInvalid {
source_epoch,
target_epoch,
}
}
#[must_use]
pub const fn rekey_state_invalid(field: &'static str) -> Self {
Self::RekeyStateInvalid { field }
}
#[must_use]
pub fn deferred_free_backlog(pages_pending: u64, oldest_pinning_commit: u64) -> Self {
Self::DeferredFreeBacklog {
pages_pending,
oldest_pinning_commit,
}
}
#[must_use]
pub fn quota(realm: RealmId, kind: QuotaKind, used: u64, limit: u64) -> Self {
Self::Quota {
realm,
kind,
used,
limit,
}
}
}