use std::{future::Future, path::Path, pin::Pin, sync::Arc};
use sha2::{Digest, Sha256};
use crate::error::{Error, Result};
mod backend;
mod contract;
mod memory;
pub use contract::{ObjectClient, qualify_object_store_reclamation, verify_object_client_contract};
pub use memory::InMemoryObjectStore;
pub(crate) use backend::{ObjectStoreBackend, ObjectStoreReadObject};
pub(crate) use contract::verify_object_client_contract_for_open;
#[cfg(test)]
mod tests;
pub(crate) fn canonical_object_prefix(value: &str) -> Result<String> {
if value.as_bytes().contains(&0) {
return Err(Error::invalid_options(
"object-store prefix cannot contain a NUL byte",
));
}
let mut components = Vec::new();
for component in value.split(['/', '\\']) {
match component {
"" | "." => {}
".." => {
return Err(Error::invalid_options(
"object-store prefix cannot contain a parent component",
));
}
component => components.push(component),
}
}
Ok(components.join("/"))
}
pub(crate) fn canonical_object_key(path: &Path) -> Result<String> {
let value = path.to_str().ok_or_else(|| {
Error::invalid_options("object-store keys must contain valid UTF-8 components")
})?;
canonical_object_prefix(value)
}
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
pub type ObjectFuture<'op, T> = Pin<Box<dyn Future<Output = Result<T>> + 'op>>;
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
pub type ObjectFuture<'op, T> = Pin<Box<dyn Future<Output = Result<T>> + Send + 'op>>;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ETag(Arc<str>);
impl ETag {
#[must_use]
pub fn new(tag: impl Into<Arc<str>>) -> Self {
Self(tag.into())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ObjectVersion(Arc<str>);
impl ObjectVersion {
#[must_use]
pub fn new(version: impl Into<Arc<str>>) -> Self {
Self(version.into())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
const OBJECT_RECLAMATION_EVIDENCE_SHA256_TAG: u8 = 1;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct ObjectStoreReclamationEvidenceDigest([u8; 33]);
impl ObjectStoreReclamationEvidenceDigest {
#[must_use]
pub fn for_bytes(evidence: &[u8]) -> Self {
let digest = Sha256::digest(evidence);
let mut bytes = [0_u8; 33];
bytes[0] = OBJECT_RECLAMATION_EVIDENCE_SHA256_TAG;
bytes[1..].copy_from_slice(&digest);
Self(bytes)
}
pub fn from_bytes(bytes: [u8; 33]) -> Result<Self> {
if bytes[0] != OBJECT_RECLAMATION_EVIDENCE_SHA256_TAG {
return Err(Error::InvalidFormat {
message: "unknown object-store reclamation evidence digest".to_owned(),
});
}
Ok(Self(bytes))
}
#[must_use]
pub const fn to_bytes(self) -> [u8; 33] {
self.0
}
}
impl std::fmt::Debug for ObjectStoreReclamationEvidenceDigest {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("ObjectStoreReclamationEvidenceDigest(..)")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ObjectStoreReclamationAttestation {
evidence_digest: ObjectStoreReclamationEvidenceDigest,
}
impl ObjectStoreReclamationAttestation {
#[must_use]
pub const fn new(evidence_digest: ObjectStoreReclamationEvidenceDigest) -> Self {
Self { evidence_digest }
}
#[must_use]
pub const fn evidence_digest(self) -> ObjectStoreReclamationEvidenceDigest {
self.evidence_digest
}
}
#[derive(Clone)]
pub struct QualifiedObjectStoreReclamation {
evidence_digest: ObjectStoreReclamationEvidenceDigest,
namespace_digest: [u8; 32],
client: Arc<dyn ObjectClient>,
}
impl QualifiedObjectStoreReclamation {
#[must_use]
pub const fn evidence_digest(&self) -> ObjectStoreReclamationEvidenceDigest {
self.evidence_digest
}
pub(crate) fn matches_prefix(&self, prefix: &Path) -> bool {
contract::object_store_reclamation_namespace_digest(prefix)
.is_ok_and(|digest| self.namespace_digest == digest)
}
pub(crate) fn matches_client(&self, client: &Arc<dyn ObjectClient>) -> bool {
Arc::ptr_eq(&self.client, client)
}
}
impl std::fmt::Debug for QualifiedObjectStoreReclamation {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("QualifiedObjectStoreReclamation")
.field("evidence_digest", &self.evidence_digest)
.field("namespace_digest", &self.namespace_digest)
.finish_non_exhaustive()
}
}
impl PartialEq for QualifiedObjectStoreReclamation {
fn eq(&self, other: &Self) -> bool {
self.evidence_digest == other.evidence_digest
&& self.namespace_digest == other.namespace_digest
&& Arc::ptr_eq(&self.client, &other.client)
}
}
impl Eq for QualifiedObjectStoreReclamation {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Precondition {
IfNoneMatch,
IfMatch(ETag),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PutIf {
Stored {
etag: ETag,
},
PreconditionFailed {
current: Option<ETag>,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ObjectMeta {
pub key: String,
pub size: u64,
pub etag: ETag,
pub version: Option<ObjectVersion>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ObjectListPage {
pub objects: Vec<ObjectMeta>,
pub next_after: Option<String>,
}