1use std::{
4 collections::{BTreeMap, BTreeSet},
5 sync::Arc,
6};
7
8#[cfg(feature = "local")]
9use std::path::PathBuf;
10
11use async_trait::async_trait;
12use tokio::sync::Mutex;
13
14use crate::{limits, wire};
15
16const MEMORY_BYTES: usize = 64 * 1_024 * 1_024;
17const SYSTEM_METADATA_BYTES: usize = 2 * 1_024;
18
19#[derive(Clone, Debug, Eq, PartialEq)]
21pub enum Condition {
22 IfAbsent,
24 IfMatch(String),
26 IfVersion(String),
28}
29
30#[cfg(any(feature = "grpc", feature = "local"))]
31impl Condition {
32 pub(crate) fn wire(self) -> wire::Preconditions {
33 let condition = match self {
34 Self::IfAbsent => wire::preconditions::Condition::IfAbsent(true),
35 Self::IfMatch(etag) => wire::preconditions::Condition::IfMatch(etag),
36 Self::IfVersion(version_id) => wire::preconditions::Condition::IfVersion(version_id),
37 };
38 wire::Preconditions {
39 condition: Some(condition),
40 }
41 }
42}
43
44#[derive(Clone, Debug, PartialEq)]
46pub enum ReadTarget {
47 Bucket(wire::BucketRef),
49 Snapshot(wire::SnapshotRef),
51}
52
53#[derive(Clone, Debug)]
55pub struct PutRequest {
56 pub bucket: wire::BucketRef,
58 pub object_key: String,
60 pub body: bytes::Bytes,
62 pub metadata: wire::ObjectMetadata,
64 pub condition: Option<Condition>,
66 pub idempotency_key: Option<String>,
68}
69
70#[derive(Clone, Debug)]
72pub struct GetRequest {
73 pub target: ReadTarget,
75 pub object_key: String,
77 pub version_id: Option<String>,
79 pub range: Option<(u64, Option<u64>)>,
81 pub if_match: Option<String>,
83 pub if_none_match: Option<String>,
85 pub maximum_bytes: u64,
87}
88
89#[derive(Clone, Debug, PartialEq)]
91pub struct BufferedObject {
92 pub version: wire::ObjectVersion,
94 pub body: bytes::Bytes,
96}
97
98#[derive(Clone, Debug, PartialEq)]
100pub struct DeleteResult {
101 pub existed: bool,
103 pub marker: Option<wire::ObjectVersion>,
105}
106
107#[derive(Clone, Debug, PartialEq)]
109pub struct ProviderListPage {
110 pub entries: Vec<wire::ListEntry>,
112 pub common_prefixes: Vec<String>,
114 pub continuation: Option<String>,
116}
117
118#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
120pub enum ObjectsError {
121 #[error("invalid objects request: {0}")]
123 Invalid(&'static str),
124 #[error("objects resource not found")]
126 NotFound,
127 #[error("objects resource already exists")]
129 AlreadyExists,
130 #[error("objects precondition failed")]
132 PreconditionFailed,
133 #[error("objects idempotency mismatch")]
135 IdempotencyMismatch,
136 #[error("objects capacity exhausted")]
138 Capacity,
139 #[error("objects capability is unsupported")]
141 Unsupported,
142 #[error("objects provider rejected authorization")]
144 Unauthorized,
145 #[error("objects provider is unavailable")]
147 Unavailable,
148}
149
150#[async_trait]
152pub trait ObjectsProvider: Send + Sync {
153 async fn create_bucket(
155 &self,
156 name: String,
157 idempotency_key: Option<String>,
158 ) -> Result<wire::Bucket, ObjectsError>;
159 async fn head_bucket(&self, bucket: &wire::BucketRef) -> Result<wire::Bucket, ObjectsError>;
161 async fn delete_bucket(
163 &self,
164 bucket: &wire::BucketRef,
165 idempotency_key: Option<String>,
166 ) -> Result<bool, ObjectsError>;
167 async fn put(&self, request: PutRequest) -> Result<wire::ObjectVersion, ObjectsError>;
169 async fn get(&self, request: GetRequest) -> Result<BufferedObject, ObjectsError>;
171 async fn delete(
173 &self,
174 bucket: wire::BucketRef,
175 object_key: String,
176 version_id: Option<String>,
177 condition: Option<Condition>,
178 idempotency_key: Option<String>,
179 ) -> Result<DeleteResult, ObjectsError>;
180 async fn list(
182 &self,
183 target: ReadTarget,
184 prefix: String,
185 delimiter: Option<String>,
186 versions: bool,
187 page_size: u32,
188 continuation: Option<String>,
189 ) -> Result<ProviderListPage, ObjectsError>;
190 async fn snapshot(
192 &self,
193 bucket: wire::BucketRef,
194 idempotency_key: Option<String>,
195 ) -> Result<wire::Snapshot, ObjectsError>;
196 async fn destroy_snapshot(
198 &self,
199 snapshot: wire::SnapshotRef,
200 idempotency_key: Option<String>,
201 ) -> Result<bool, ObjectsError>;
202 async fn fork(
204 &self,
205 source: ReadTarget,
206 destination_name: String,
207 idempotency_key: Option<String>,
208 ) -> Result<wire::Bucket, ObjectsError>;
209 async fn create_multipart(
211 &self,
212 bucket: wire::BucketRef,
213 object_key: String,
214 metadata: wire::ObjectMetadata,
215 condition: Option<Condition>,
216 idempotency_key: Option<String>,
217 ) -> Result<wire::MultipartUpload, ObjectsError>;
218 async fn upload_part(
220 &self,
221 bucket: wire::BucketRef,
222 object_key: String,
223 upload_id: String,
224 part_number: u32,
225 body: bytes::Bytes,
226 idempotency_key: Option<String>,
227 ) -> Result<wire::UploadedPart, ObjectsError>;
228 async fn list_parts(
230 &self,
231 bucket: wire::BucketRef,
232 object_key: String,
233 upload_id: String,
234 ) -> Result<Vec<wire::UploadedPart>, ObjectsError>;
235 async fn complete_multipart(
237 &self,
238 bucket: wire::BucketRef,
239 object_key: String,
240 upload_id: String,
241 parts: Vec<wire::UploadedPart>,
242 idempotency_key: Option<String>,
243 ) -> Result<wire::ObjectVersion, ObjectsError>;
244 async fn abort_multipart(
246 &self,
247 bucket: wire::BucketRef,
248 object_key: String,
249 upload_id: String,
250 idempotency_key: Option<String>,
251 ) -> Result<bool, ObjectsError>;
252}
253
254#[derive(Clone)]
255enum StoredBody {
256 Memory(bytes::Bytes),
257 Composite {
258 parts: Arc<[StoredBody]>,
259 length: usize,
260 },
261 #[cfg(feature = "local")]
262 Local {
263 root: Arc<PathBuf>,
264 digest: [u8; 32],
265 length: usize,
266 },
267}
268
269#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)]
270enum BodyIdentity {
271 Memory {
272 address: usize,
273 length: usize,
274 },
275 #[cfg(feature = "local")]
276 Local([u8; 32]),
277}
278
279impl StoredBody {
280 fn memory(body: bytes::Bytes) -> Self {
281 Self::Memory(body)
282 }
283
284 fn len(&self) -> usize {
285 match self {
286 Self::Memory(body) => body.len(),
287 Self::Composite { length, .. } => *length,
288 #[cfg(feature = "local")]
289 Self::Local { length, .. } => *length,
290 }
291 }
292
293 fn read(&self, start: usize, end: usize) -> Result<bytes::Bytes, ObjectsError> {
294 match self {
295 Self::Memory(body) => Ok(body.slice(start..end)),
296 Self::Composite { parts, .. } => {
297 let mut output = Vec::with_capacity(end.saturating_sub(start));
298 let mut offset = 0usize;
299 for part in parts.iter() {
300 let part_end = offset
301 .checked_add(part.len())
302 .ok_or(ObjectsError::Unavailable)?;
303 if part_end > start && offset < end {
304 let selected_start = start.saturating_sub(offset).min(part.len());
305 let selected_end = end.saturating_sub(offset).min(part.len());
306 output.extend_from_slice(&part.read(selected_start, selected_end)?);
307 }
308 offset = part_end;
309 }
310 if output.len() != end.saturating_sub(start) {
311 return Err(ObjectsError::Unavailable);
312 }
313 Ok(output.into())
314 }
315 #[cfg(feature = "local")]
316 Self::Local {
317 root,
318 digest,
319 length,
320 } => {
321 if end > *length || start > end {
322 return Err(ObjectsError::Invalid("invalid range"));
323 }
324 crate::local::read_body(root, digest, *length, start, end)
325 }
326 }
327 }
328
329 fn update_hash(&self, hasher: &mut blake3::Hasher) -> Result<(), ObjectsError> {
330 match self {
331 Self::Memory(body) => {
332 hasher.update(body);
333 Ok(())
334 }
335 Self::Composite { parts, .. } => {
336 for part in parts.iter() {
337 part.update_hash(hasher)?;
338 }
339 Ok(())
340 }
341 #[cfg(feature = "local")]
342 Self::Local {
343 root,
344 digest,
345 length,
346 } => crate::local::hash_body(root, digest, *length, hasher),
347 }
348 }
349
350 fn leaves(&self, output: &mut Vec<(BodyIdentity, usize)>) {
351 match self {
352 Self::Memory(body) => output.push((
353 BodyIdentity::Memory {
354 address: body.as_ptr() as usize,
355 length: body.len(),
356 },
357 body.len(),
358 )),
359 Self::Composite { parts, .. } => {
360 for part in parts.iter() {
361 part.leaves(output);
362 }
363 }
364 #[cfg(feature = "local")]
365 Self::Local { digest, length, .. } => {
366 output.push((BodyIdentity::Local(*digest), *length));
367 }
368 }
369 }
370
371 async fn read_async(&self, start: usize, end: usize) -> Result<bytes::Bytes, ObjectsError> {
372 #[cfg(feature = "local")]
373 if self.has_local_storage() {
374 let body = self.clone();
375 return tokio::task::spawn_blocking(move || body.read(start, end))
376 .await
377 .map_err(|_| ObjectsError::Unavailable)?;
378 }
379 self.read(start, end)
380 }
381
382 #[cfg(feature = "local")]
383 fn has_local_storage(&self) -> bool {
384 match self {
385 Self::Memory(_) => false,
386 Self::Local { .. } => true,
387 Self::Composite { parts, .. } => parts.iter().any(Self::has_local_storage),
388 }
389 }
390}
391
392#[derive(Clone)]
393struct Version {
394 descriptor: wire::ObjectVersion,
395 body: Option<StoredBody>,
396}
397
398#[derive(Clone)]
399struct BucketState {
400 reference: wire::BucketRef,
401 created_at: prost_types::Timestamp,
402 objects: BTreeMap<String, Vec<Version>>,
403}
404
405#[derive(Clone)]
406struct SnapshotState {
407 reference: wire::SnapshotRef,
408 bucket: BucketState,
409}
410
411struct MultipartState {
412 bucket: wire::BucketRef,
413 object_key: String,
414 metadata: wire::ObjectMetadata,
415 condition: Option<Condition>,
416 parts: BTreeMap<u32, (wire::UploadedPart, StoredBody)>,
417}
418
419struct StoredPartRequest {
420 bucket: wire::BucketRef,
421 object_key: String,
422 upload_id: String,
423 part_number: u32,
424 body: StoredBody,
425 body_digest: [u8; 32],
426 idempotency_key: Option<String>,
427}
428
429#[cfg(feature = "local")]
430pub(crate) struct ExternalBody {
431 pub(crate) root: PathBuf,
432 pub(crate) digest: [u8; 32],
433 pub(crate) length: usize,
434}
435
436#[derive(Clone)]
437enum ListingItem {
438 Entry(Box<wire::ListEntry>),
439 Prefix(String),
440}
441
442struct ListingView {
443 binding: String,
444 items: Vec<ListingItem>,
445}
446
447#[derive(Clone)]
448enum MutationOutcome {
449 Bucket(Result<wire::Bucket, ObjectsError>),
450 Boolean(Result<bool, ObjectsError>),
451 Version(Result<wire::ObjectVersion, ObjectsError>),
452 Delete(Result<DeleteResult, ObjectsError>),
453 Snapshot(Result<wire::Snapshot, ObjectsError>),
454 Multipart(Result<wire::MultipartUpload, ObjectsError>),
455 Part(Result<wire::UploadedPart, ObjectsError>),
456}
457
458struct IdempotencyRecord {
459 fingerprint: blake3::Hash,
460 outcome: MutationOutcome,
461}
462
463struct State {
464 maximum_bytes: usize,
465 maximum_object_bytes: usize,
466 sequence: u64,
467 listing_sequence: u64,
468 bytes: usize,
469 body_references: BTreeMap<BodyIdentity, (usize, usize)>,
470 names: BTreeMap<String, String>,
471 buckets: BTreeMap<String, BucketState>,
472 snapshots: BTreeMap<String, SnapshotState>,
473 multiparts: BTreeMap<String, MultipartState>,
474 listings: BTreeMap<String, ListingView>,
475 idempotency: BTreeMap<String, IdempotencyRecord>,
476}
477
478impl Default for State {
479 fn default() -> Self {
480 Self {
481 maximum_bytes: MEMORY_BYTES,
482 maximum_object_bytes: MEMORY_BYTES,
483 sequence: 0,
484 listing_sequence: 0,
485 bytes: 0,
486 body_references: BTreeMap::new(),
487 names: BTreeMap::new(),
488 buckets: BTreeMap::new(),
489 snapshots: BTreeMap::new(),
490 multiparts: BTreeMap::new(),
491 listings: BTreeMap::new(),
492 idempotency: BTreeMap::new(),
493 }
494 }
495}
496
497impl State {
498 fn additional_body_bytes(&self, body: &StoredBody) -> Result<usize, ObjectsError> {
499 let mut leaves = Vec::new();
500 body.leaves(&mut leaves);
501 leaves
502 .into_iter()
503 .try_fold(0usize, |total, (identity, length)| {
504 if self.body_references.contains_key(&identity) {
505 Ok(total)
506 } else {
507 total.checked_add(length).ok_or(ObjectsError::Capacity)
508 }
509 })
510 }
511
512 fn retain_body(&mut self, body: &StoredBody) -> Result<(), ObjectsError> {
513 let mut leaves = Vec::new();
514 body.leaves(&mut leaves);
515 for (identity, length) in leaves {
516 match self.body_references.entry(identity) {
517 std::collections::btree_map::Entry::Vacant(entry) => {
518 self.bytes = self
519 .bytes
520 .checked_add(length)
521 .ok_or(ObjectsError::Capacity)?;
522 entry.insert((length, 1));
523 }
524 std::collections::btree_map::Entry::Occupied(mut entry) => {
525 if entry.get().0 != length {
526 return Err(ObjectsError::Unavailable);
527 }
528 entry.get_mut().1 =
529 entry.get().1.checked_add(1).ok_or(ObjectsError::Capacity)?;
530 }
531 }
532 }
533 Ok(())
534 }
535
536 fn release_body(&mut self, body: &StoredBody) -> Result<(), ObjectsError> {
537 let mut leaves = Vec::new();
538 body.leaves(&mut leaves);
539 for (identity, length) in leaves {
540 let references = self
541 .body_references
542 .get_mut(&identity)
543 .ok_or(ObjectsError::Unavailable)?;
544 if references.0 != length || references.1 == 0 {
545 return Err(ObjectsError::Unavailable);
546 }
547 references.1 -= 1;
548 if references.1 == 0 {
549 self.body_references.remove(&identity);
550 self.bytes = self
551 .bytes
552 .checked_sub(length)
553 .ok_or(ObjectsError::Unavailable)?;
554 }
555 }
556 Ok(())
557 }
558
559 fn retain_bucket(&mut self, bucket: &BucketState) -> Result<(), ObjectsError> {
560 for body in bucket
561 .objects
562 .values()
563 .flatten()
564 .filter_map(|version| version.body.as_ref())
565 {
566 self.retain_body(body)?;
567 }
568 Ok(())
569 }
570
571 fn release_bucket(&mut self, bucket: &BucketState) -> Result<(), ObjectsError> {
572 for body in bucket
573 .objects
574 .values()
575 .flatten()
576 .filter_map(|version| version.body.as_ref())
577 {
578 self.release_body(body)?;
579 }
580 Ok(())
581 }
582}
583
584#[derive(Clone, Default)]
589pub struct MemoryObjects {
590 state: Arc<Mutex<State>>,
591}
592
593impl MemoryObjects {
594 #[cfg(feature = "local")]
595 pub(crate) async fn local_body_digests(&self) -> BTreeSet<[u8; 32]> {
596 let state = self.state.lock().await;
597 state
598 .body_references
599 .keys()
600 .filter_map(|identity| match identity {
601 BodyIdentity::Local(digest) => Some(*digest),
602 BodyIdentity::Memory { .. } => None,
603 })
604 .collect()
605 }
606
607 pub async fn bucket_named(&self, name: &str) -> Result<Option<wire::BucketRef>, ObjectsError> {
609 Self::validate_bucket_name(name)?;
610 let state = self.state.lock().await;
611 let Some(bucket_id) = state.names.get(name) else {
612 return Ok(None);
613 };
614 let bucket = state
615 .buckets
616 .get(bucket_id)
617 .ok_or(ObjectsError::Unavailable)?;
618 Ok(Some(bucket.reference.clone()))
619 }
620
621 #[must_use]
623 pub fn with_default_bucket() -> (Self, wire::BucketRef) {
624 Self::from_bucket("memory-bucket".to_owned(), MEMORY_BYTES)
625 }
626
627 pub fn new(maximum_bytes: u64) -> Result<Self, ObjectsError> {
633 if maximum_bytes == 0 {
634 return Err(ObjectsError::Invalid("memory byte limit must be positive"));
635 }
636 Self::new_with_limits(maximum_bytes, maximum_bytes)
637 }
638
639 pub(crate) fn new_with_limits(
640 maximum_object_bytes: u64,
641 maximum_bytes: u64,
642 ) -> Result<Self, ObjectsError> {
643 let maximum_object_bytes = usize::try_from(maximum_object_bytes)
644 .map_err(|_| ObjectsError::Invalid("memory object limit is not representable"))?;
645 let maximum_bytes = usize::try_from(maximum_bytes)
646 .map_err(|_| ObjectsError::Invalid("memory byte limit is not representable"))?;
647 if maximum_object_bytes == 0 || maximum_bytes == 0 || maximum_object_bytes > maximum_bytes {
648 return Err(ObjectsError::Invalid("invalid memory object limits"));
649 }
650 Ok(Self {
651 state: Arc::new(Mutex::new(State {
652 maximum_bytes,
653 maximum_object_bytes,
654 ..State::default()
655 })),
656 })
657 }
658
659 pub fn with_bucket(
668 name: impl Into<String>,
669 maximum_bytes: u64,
670 ) -> Result<(Self, wire::BucketRef), ObjectsError> {
671 let maximum_bytes = usize::try_from(maximum_bytes)
672 .map_err(|_| ObjectsError::Invalid("memory byte limit is not representable"))?;
673 if maximum_bytes == 0 {
674 return Err(ObjectsError::Invalid("memory byte limit must be positive"));
675 }
676 let name = name.into();
677 Self::validate_bucket_name(&name)?;
678 Ok(Self::from_bucket(name, maximum_bytes))
679 }
680
681 pub fn with_bucket_limits(
688 name: impl Into<String>,
689 maximum_object_bytes: u64,
690 maximum_bytes: u64,
691 ) -> Result<(Self, wire::BucketRef), ObjectsError> {
692 let maximum_object_bytes = usize::try_from(maximum_object_bytes)
693 .map_err(|_| ObjectsError::Invalid("memory object limit is not representable"))?;
694 let maximum_bytes = usize::try_from(maximum_bytes)
695 .map_err(|_| ObjectsError::Invalid("memory byte limit is not representable"))?;
696 if maximum_object_bytes == 0 || maximum_bytes == 0 || maximum_object_bytes > maximum_bytes {
697 return Err(ObjectsError::Invalid("invalid memory object limits"));
698 }
699 let name = name.into();
700 Self::validate_bucket_name(&name)?;
701 Ok(Self::from_bucket_limits(
702 name,
703 maximum_object_bytes,
704 maximum_bytes,
705 ))
706 }
707
708 fn from_bucket(name: String, maximum_bytes: usize) -> (Self, wire::BucketRef) {
709 Self::from_bucket_limits(name, maximum_bytes, maximum_bytes)
710 }
711
712 fn from_bucket_limits(
713 name: String,
714 maximum_object_bytes: usize,
715 maximum_bytes: usize,
716 ) -> (Self, wire::BucketRef) {
717 let bucket_id = "bucket-0000000000000001".to_owned();
718 let reference = wire::BucketRef {
719 bucket_id: bucket_id.clone(),
720 name: name.clone(),
721 };
722 let mut state = State {
723 maximum_bytes,
724 maximum_object_bytes,
725 sequence: 1,
726 ..State::default()
727 };
728 state.names.insert(name, bucket_id.clone());
729 state.buckets.insert(
730 bucket_id,
731 BucketState {
732 reference: reference.clone(),
733 created_at: Self::timestamp(&state),
734 objects: BTreeMap::new(),
735 },
736 );
737 (
738 Self {
739 state: Arc::new(Mutex::new(state)),
740 },
741 reference,
742 )
743 }
744
745 fn fingerprint(kind: &str, fields: &[&[u8]]) -> blake3::Hash {
746 let mut hasher = blake3::Hasher::new();
747 for field in std::iter::once(kind.as_bytes()).chain(fields.iter().copied()) {
748 hasher.update(&(field.len() as u64).to_le_bytes());
749 hasher.update(field);
750 }
751 hasher.finalize()
752 }
753
754 fn execute<T: Clone>(
755 state: &mut State,
756 idempotency_key: Option<&str>,
757 fingerprint: blake3::Hash,
758 unpack: impl FnOnce(&MutationOutcome) -> Option<Result<T, ObjectsError>>,
759 pack: impl FnOnce(Result<T, ObjectsError>) -> MutationOutcome,
760 operation: impl FnOnce(&mut State) -> Result<T, ObjectsError>,
761 ) -> Result<T, ObjectsError> {
762 let Some(idempotency_key) = idempotency_key else {
763 return operation(state);
764 };
765 if idempotency_key.is_empty() || idempotency_key.len() > 256 {
766 return Err(ObjectsError::Invalid("invalid idempotency key"));
767 }
768 if let Some(record) = state.idempotency.get(idempotency_key) {
769 if record.fingerprint != fingerprint {
770 return Err(ObjectsError::IdempotencyMismatch);
771 }
772 return unpack(&record.outcome).ok_or(ObjectsError::IdempotencyMismatch)?;
773 }
774 let outcome = operation(state);
775 state.idempotency.insert(
776 idempotency_key.to_owned(),
777 IdempotencyRecord {
778 fingerprint,
779 outcome: pack(outcome.clone()),
780 },
781 );
782 outcome
783 }
784
785 fn reference_fields(reference: &wire::BucketRef) -> [&[u8]; 2] {
786 [reference.bucket_id.as_bytes(), reference.name.as_bytes()]
787 }
788
789 fn condition_bytes(condition: &Option<Condition>) -> Vec<u8> {
790 match condition {
791 None => b"none".to_vec(),
792 Some(Condition::IfAbsent) => b"absent".to_vec(),
793 Some(Condition::IfMatch(value)) => [b"etag:".as_slice(), value.as_bytes()].concat(),
794 Some(Condition::IfVersion(value)) => {
795 [b"version:".as_slice(), value.as_bytes()].concat()
796 }
797 }
798 }
799
800 fn metadata_bytes(metadata: &wire::ObjectMetadata) -> Vec<u8> {
801 let mut fields = vec![
802 metadata.content_type.as_str(),
803 metadata.content_encoding.as_str(),
804 metadata.cache_control.as_str(),
805 metadata.content_disposition.as_str(),
806 metadata.content_language.as_str(),
807 ];
808 fields.push(if metadata.expires_unix_seconds.is_some() {
809 "expires"
810 } else {
811 "no-expires"
812 });
813 let mut encoded = Vec::new();
814 for field in fields {
815 encoded.extend_from_slice(&(field.len() as u64).to_le_bytes());
816 encoded.extend_from_slice(field.as_bytes());
817 }
818 if let Some(expires) = metadata.expires_unix_seconds {
819 encoded.extend_from_slice(&expires.to_le_bytes());
820 }
821 let mut user = metadata.user.iter().collect::<Vec<_>>();
822 user.sort_unstable_by(|left, right| left.0.cmp(right.0));
823 for (name, value) in user {
824 for field in [name.as_bytes(), value.as_bytes()] {
825 encoded.extend_from_slice(&(field.len() as u64).to_le_bytes());
826 encoded.extend_from_slice(field);
827 }
828 }
829 encoded
830 }
831
832 fn next(state: &mut State, kind: &str) -> Result<String, ObjectsError> {
833 let sequence = if kind == "listing" {
834 &mut state.listing_sequence
835 } else {
836 &mut state.sequence
837 };
838 *sequence = sequence.checked_add(1).ok_or(ObjectsError::Capacity)?;
839 Ok(format!("{kind}-{sequence:016x}"))
840 }
841
842 fn timestamp(state: &State) -> prost_types::Timestamp {
843 prost_types::Timestamp {
844 seconds: i64::try_from(state.sequence).unwrap_or(i64::MAX),
845 nanos: 0,
846 }
847 }
848
849 fn bucket<'a>(
850 state: &'a State,
851 reference: &wire::BucketRef,
852 ) -> Result<&'a BucketState, ObjectsError> {
853 state
854 .buckets
855 .get(&reference.bucket_id)
856 .filter(|bucket| bucket.reference == *reference)
857 .ok_or(ObjectsError::NotFound)
858 }
859
860 fn bucket_mut<'a>(
861 state: &'a mut State,
862 reference: &wire::BucketRef,
863 ) -> Result<&'a mut BucketState, ObjectsError> {
864 state
865 .buckets
866 .get_mut(&reference.bucket_id)
867 .filter(|bucket| bucket.reference == *reference)
868 .ok_or(ObjectsError::NotFound)
869 }
870
871 fn validate_key(value: &str) -> Result<(), ObjectsError> {
872 if value.is_empty() || value.len() > limits::KEY_BYTES || value.contains('\0') {
873 Err(ObjectsError::Invalid("invalid object key"))
874 } else {
875 Ok(())
876 }
877 }
878
879 fn validate_bucket_name(name: &str) -> Result<(), ObjectsError> {
880 let valid_edges = name
881 .as_bytes()
882 .first()
883 .zip(name.as_bytes().last())
884 .is_some_and(|(first, last)| {
885 first.is_ascii_alphanumeric() && last.is_ascii_alphanumeric()
886 });
887 let valid_body = name
888 .bytes()
889 .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'-');
890 if !(3..=63).contains(&name.len()) || !valid_edges || !valid_body {
891 Err(ObjectsError::Invalid("invalid bucket name"))
892 } else {
893 Ok(())
894 }
895 }
896
897 fn validate_metadata(metadata: &wire::ObjectMetadata) -> Result<(), ObjectsError> {
898 let fixed = [
899 &metadata.content_type,
900 &metadata.content_encoding,
901 &metadata.cache_control,
902 &metadata.content_disposition,
903 &metadata.content_language,
904 ]
905 .into_iter()
906 .map(String::len)
907 .sum::<usize>();
908 let user = metadata
909 .user
910 .iter()
911 .map(|(name, value)| name.len() + value.len())
912 .sum::<usize>();
913 if fixed > SYSTEM_METADATA_BYTES
914 || user > limits::USER_METADATA_BYTES
915 || metadata.user.iter().any(|(name, value)| {
916 !name.bytes().all(|byte| {
917 byte.is_ascii_alphanumeric()
918 || matches!(
919 byte,
920 b'!' | b'#'
921 | b'$'
922 | b'%'
923 | b'&'
924 | b'\''
925 | b'*'
926 | b'+'
927 | b'-'
928 | b'.'
929 | b'^'
930 | b'_'
931 | b'`'
932 | b'|'
933 | b'~'
934 )
935 }) || name.is_empty()
936 || !Self::valid_header_value(value)
937 })
938 || [
939 &metadata.content_type,
940 &metadata.content_encoding,
941 &metadata.cache_control,
942 &metadata.content_disposition,
943 &metadata.content_language,
944 ]
945 .into_iter()
946 .any(|value| !Self::valid_header_value(value))
947 || metadata
948 .expires_unix_seconds
949 .is_some_and(|seconds| !(-62_167_219_200..=253_402_300_799).contains(&seconds))
950 {
951 Err(ObjectsError::Invalid("invalid object metadata"))
952 } else {
953 Ok(())
954 }
955 }
956
957 fn valid_header_value(value: &str) -> bool {
958 value
959 .bytes()
960 .all(|byte| byte == b'\t' || (0x20..=0x7e).contains(&byte))
961 }
962
963 fn target(state: &State, target: &ReadTarget) -> Result<BucketState, ObjectsError> {
964 match target {
965 ReadTarget::Bucket(reference) => Ok(Self::bucket(state, reference)?.clone()),
966 ReadTarget::Snapshot(reference) => state
967 .snapshots
968 .get(&reference.snapshot_id)
969 .filter(|snapshot| snapshot.reference == *reference)
970 .map(|snapshot| snapshot.bucket.clone())
971 .ok_or(ObjectsError::NotFound),
972 }
973 }
974
975 fn target_ref<'a>(
976 state: &'a State,
977 target: &ReadTarget,
978 ) -> Result<&'a BucketState, ObjectsError> {
979 match target {
980 ReadTarget::Bucket(reference) => Self::bucket(state, reference),
981 ReadTarget::Snapshot(reference) => state
982 .snapshots
983 .get(&reference.snapshot_id)
984 .filter(|snapshot| snapshot.reference == *reference)
985 .map(|snapshot| &snapshot.bucket)
986 .ok_or(ObjectsError::NotFound),
987 }
988 }
989
990 fn visible<'a>(
991 bucket: &'a BucketState,
992 object_key: &str,
993 version_id: Option<&str>,
994 ) -> Result<&'a Version, ObjectsError> {
995 let versions = bucket
996 .objects
997 .get(object_key)
998 .ok_or(ObjectsError::NotFound)?;
999 let version = match version_id {
1000 Some(identity) => versions
1001 .iter()
1002 .rev()
1003 .find(|version| version.descriptor.version_id == identity),
1004 None => versions.last(),
1005 }
1006 .ok_or(ObjectsError::NotFound)?;
1007 if version.descriptor.delete_marker {
1008 Err(ObjectsError::NotFound)
1009 } else {
1010 Ok(version)
1011 }
1012 }
1013
1014 fn condition(current: Option<&Version>, condition: &Option<Condition>) -> bool {
1015 match condition {
1016 None => true,
1017 Some(Condition::IfAbsent) => current.is_none_or(|value| value.descriptor.delete_marker),
1018 Some(Condition::IfMatch(etag)) => current.is_some_and(|value| {
1019 !value.descriptor.delete_marker && value.descriptor.etag == *etag
1020 }),
1021 Some(Condition::IfVersion(identity)) => {
1022 current.is_some_and(|value| value.descriptor.version_id == *identity)
1023 }
1024 }
1025 }
1026
1027 fn descriptor(
1028 state: &mut State,
1029 body_digest: &[u8; 32],
1030 body_length: usize,
1031 metadata: wire::ObjectMetadata,
1032 marker: bool,
1033 ) -> Result<wire::ObjectVersion, ObjectsError> {
1034 let version_id = Self::next(state, "version")?;
1035 let etag = if marker {
1036 String::new()
1037 } else {
1038 format!("\"{}\"", blake3::Hash::from_bytes(*body_digest).to_hex())
1039 };
1040 Ok(wire::ObjectVersion {
1041 version_id,
1042 etag,
1043 size: body_length as u64,
1044 delete_marker: marker,
1045 metadata: Some(metadata),
1046 created_at: Some(Self::timestamp(state)),
1047 })
1048 }
1049
1050 fn binding(
1051 target: &ReadTarget,
1052 prefix: &str,
1053 delimiter: Option<&str>,
1054 versions: bool,
1055 ) -> String {
1056 let target = match target {
1057 ReadTarget::Bucket(value) => format!("b:{}:{}", value.bucket_id, value.name),
1058 ReadTarget::Snapshot(value) => {
1059 format!("s:{}:{}", value.snapshot_id, value.source_bucket_id)
1060 }
1061 };
1062 format!(
1063 "{target}|{prefix}|{}|{versions}",
1064 delimiter.unwrap_or_default()
1065 )
1066 }
1067
1068 fn listing_items(
1069 bucket: &BucketState,
1070 prefix: &str,
1071 delimiter: Option<&str>,
1072 versions: bool,
1073 ) -> Vec<ListingItem> {
1074 let mut items = Vec::new();
1075 let mut prefixes = BTreeSet::new();
1076 for (object_key, history) in bucket.objects.range(prefix.to_owned()..) {
1077 if !object_key.starts_with(prefix) {
1078 break;
1079 }
1080 if let Some(delimiter) = delimiter
1081 && let Some(position) = object_key[prefix.len()..].find(delimiter)
1082 {
1083 let end = prefix.len() + position + delimiter.len();
1084 prefixes.insert(object_key[..end].to_owned());
1085 continue;
1086 }
1087 if versions {
1088 items.extend(history.iter().rev().map(|version| {
1089 ListingItem::Entry(Box::new(wire::ListEntry {
1090 object_key: object_key.clone(),
1091 version: Some(version.descriptor.clone()),
1092 }))
1093 }));
1094 } else if let Some(version) = history.last()
1095 && !version.descriptor.delete_marker
1096 {
1097 items.push(ListingItem::Entry(Box::new(wire::ListEntry {
1098 object_key: object_key.clone(),
1099 version: Some(version.descriptor.clone()),
1100 })));
1101 }
1102 }
1103 items.extend(prefixes.into_iter().map(ListingItem::Prefix));
1104 items.sort_by(|left, right| {
1105 let left = match left {
1106 ListingItem::Entry(value) => &value.object_key,
1107 ListingItem::Prefix(value) => value,
1108 };
1109 let right = match right {
1110 ListingItem::Entry(value) => &value.object_key,
1111 ListingItem::Prefix(value) => value,
1112 };
1113 left.cmp(right)
1114 });
1115 items
1116 }
1117
1118 async fn put_stored(
1119 &self,
1120 request: PutRequest,
1121 body: StoredBody,
1122 body_digest: [u8; 32],
1123 ) -> Result<wire::ObjectVersion, ObjectsError> {
1124 let PutRequest {
1125 bucket,
1126 object_key,
1127 body: _,
1128 metadata,
1129 condition,
1130 idempotency_key,
1131 } = request;
1132 Self::validate_key(&object_key)?;
1133 Self::validate_metadata(&metadata)?;
1134 let bucket_fields = Self::reference_fields(&bucket);
1135 let condition_bytes = Self::condition_bytes(&condition);
1136 let metadata_bytes = Self::metadata_bytes(&metadata);
1137 let fingerprint = Self::fingerprint(
1138 "put",
1139 &[
1140 bucket_fields[0],
1141 bucket_fields[1],
1142 object_key.as_bytes(),
1143 &condition_bytes,
1144 &metadata_bytes,
1145 &body_digest,
1146 ],
1147 );
1148 let mut state = self.state.lock().await;
1149 Self::execute(
1150 &mut state,
1151 idempotency_key.as_deref(),
1152 fingerprint,
1153 |outcome| match outcome {
1154 MutationOutcome::Version(value) => Some(value.clone()),
1155 _ => None,
1156 },
1157 MutationOutcome::Version,
1158 |state| {
1159 let current = Self::bucket(state, &bucket)?
1160 .objects
1161 .get(&object_key)
1162 .and_then(|history| history.last())
1163 .cloned();
1164 if !Self::condition(current.as_ref(), &condition) {
1165 return Err(ObjectsError::PreconditionFailed);
1166 }
1167 let size = body.len();
1168 let additional = state.additional_body_bytes(&body)?;
1169 if size > state.maximum_object_bytes
1170 || state
1171 .bytes
1172 .checked_add(additional)
1173 .is_none_or(|bytes| bytes > state.maximum_bytes)
1174 {
1175 return Err(ObjectsError::Capacity);
1176 }
1177 let descriptor = Self::descriptor(state, &body_digest, size, metadata, false)?;
1178 state.retain_body(&body)?;
1179 Self::bucket_mut(state, &bucket)?
1180 .objects
1181 .entry(object_key)
1182 .or_default()
1183 .push(Version {
1184 descriptor: descriptor.clone(),
1185 body: Some(body),
1186 });
1187 Ok(descriptor)
1188 },
1189 )
1190 }
1191
1192 #[cfg(feature = "local")]
1193 pub(crate) async fn put_external(
1194 &self,
1195 request: PutRequest,
1196 external: ExternalBody,
1197 ) -> Result<wire::ObjectVersion, ObjectsError> {
1198 let ExternalBody {
1199 root,
1200 digest,
1201 length,
1202 } = external;
1203 self.put_stored(
1204 request,
1205 StoredBody::Local {
1206 root: Arc::new(root),
1207 digest,
1208 length,
1209 },
1210 digest,
1211 )
1212 .await
1213 }
1214
1215 async fn upload_part_stored(
1216 &self,
1217 request: StoredPartRequest,
1218 ) -> Result<wire::UploadedPart, ObjectsError> {
1219 let StoredPartRequest {
1220 bucket,
1221 object_key,
1222 upload_id,
1223 part_number,
1224 body,
1225 body_digest,
1226 idempotency_key,
1227 } = request;
1228 if !(1..=limits::MULTIPART_PARTS).contains(&part_number)
1229 || body.len() as u64 > limits::MAX_MULTIPART_PART_BYTES
1230 {
1231 return Err(ObjectsError::Invalid("invalid multipart part"));
1232 }
1233 let bucket_fields = Self::reference_fields(&bucket);
1234 let part_number_bytes = part_number.to_le_bytes();
1235 let fingerprint = Self::fingerprint(
1236 "upload-part",
1237 &[
1238 bucket_fields[0],
1239 bucket_fields[1],
1240 object_key.as_bytes(),
1241 upload_id.as_bytes(),
1242 &part_number_bytes,
1243 &body_digest,
1244 ],
1245 );
1246 let mut state = self.state.lock().await;
1247 Self::execute(
1248 &mut state,
1249 idempotency_key.as_deref(),
1250 fingerprint,
1251 |outcome| match outcome {
1252 MutationOutcome::Part(value) => Some(value.clone()),
1253 _ => None,
1254 },
1255 MutationOutcome::Part,
1256 |state| {
1257 let existing_body = state
1258 .multiparts
1259 .get(&upload_id)
1260 .filter(|upload| upload.bucket == bucket && upload.object_key == object_key)
1261 .ok_or(ObjectsError::NotFound)?
1262 .parts
1263 .get(&part_number)
1264 .map(|(_, body)| body.clone());
1265 if let Some(existing) = &existing_body {
1266 state.release_body(existing)?;
1267 }
1268 let additional = state.additional_body_bytes(&body)?;
1269 if state
1270 .bytes
1271 .checked_add(additional)
1272 .is_none_or(|bytes| bytes > state.maximum_bytes)
1273 {
1274 if let Some(existing) = &existing_body {
1275 state.retain_body(existing)?;
1276 }
1277 return Err(ObjectsError::Capacity);
1278 }
1279 let part = wire::UploadedPart {
1280 part_number,
1281 etag: format!("\"{}\"", blake3::Hash::from_bytes(body_digest).to_hex()),
1282 size: body.len() as u64,
1283 };
1284 state
1285 .multiparts
1286 .get_mut(&upload_id)
1287 .ok_or(ObjectsError::NotFound)?
1288 .parts
1289 .insert(part_number, (part.clone(), body));
1290 let admitted = state
1291 .multiparts
1292 .get(&upload_id)
1293 .and_then(|upload| upload.parts.get(&part_number))
1294 .map(|(_, body)| body.clone())
1295 .ok_or(ObjectsError::Unavailable)?;
1296 state.retain_body(&admitted)?;
1297 Ok(part)
1298 },
1299 )
1300 }
1301
1302 #[cfg(feature = "local")]
1303 pub(crate) async fn upload_part_external(
1304 &self,
1305 bucket: wire::BucketRef,
1306 object_key: String,
1307 upload_id: String,
1308 part_number: u32,
1309 external: ExternalBody,
1310 idempotency_key: Option<String>,
1311 ) -> Result<wire::UploadedPart, ObjectsError> {
1312 let ExternalBody {
1313 root,
1314 digest,
1315 length,
1316 } = external;
1317 self.upload_part_stored(StoredPartRequest {
1318 bucket,
1319 object_key,
1320 upload_id,
1321 part_number,
1322 body: StoredBody::Local {
1323 root: Arc::new(root),
1324 digest,
1325 length,
1326 },
1327 body_digest: digest,
1328 idempotency_key,
1329 })
1330 .await
1331 }
1332}
1333
1334#[async_trait]
1335impl ObjectsProvider for MemoryObjects {
1336 async fn create_bucket(
1337 &self,
1338 name: String,
1339 idempotency_key: Option<String>,
1340 ) -> Result<wire::Bucket, ObjectsError> {
1341 Self::validate_bucket_name(&name)?;
1342 let fingerprint = Self::fingerprint("create-bucket", &[name.as_bytes()]);
1343 let mut state = self.state.lock().await;
1344 Self::execute(
1345 &mut state,
1346 idempotency_key.as_deref(),
1347 fingerprint,
1348 |outcome| match outcome {
1349 MutationOutcome::Bucket(value) => Some(value.clone()),
1350 _ => None,
1351 },
1352 MutationOutcome::Bucket,
1353 |state| {
1354 if state.names.contains_key(&name) {
1355 return Err(ObjectsError::AlreadyExists);
1356 }
1357 let bucket_id = Self::next(state, "bucket")?;
1358 let reference = wire::BucketRef {
1359 bucket_id: bucket_id.clone(),
1360 name: name.clone(),
1361 };
1362 let bucket = BucketState {
1363 reference: reference.clone(),
1364 created_at: Self::timestamp(state),
1365 objects: BTreeMap::new(),
1366 };
1367 state.names.insert(name, bucket_id.clone());
1368 state.buckets.insert(bucket_id, bucket.clone());
1369 Ok(wire::Bucket {
1370 bucket: Some(reference),
1371 created_at: Some(bucket.created_at),
1372 })
1373 },
1374 )
1375 }
1376
1377 async fn head_bucket(&self, bucket: &wire::BucketRef) -> Result<wire::Bucket, ObjectsError> {
1378 let state = self.state.lock().await;
1379 let bucket = Self::bucket(&state, bucket)?;
1380 Ok(wire::Bucket {
1381 bucket: Some(bucket.reference.clone()),
1382 created_at: Some(bucket.created_at),
1383 })
1384 }
1385
1386 async fn delete_bucket(
1387 &self,
1388 bucket: &wire::BucketRef,
1389 idempotency_key: Option<String>,
1390 ) -> Result<bool, ObjectsError> {
1391 let fields = Self::reference_fields(bucket);
1392 let fingerprint = Self::fingerprint("delete-bucket", &fields);
1393 let mut state = self.state.lock().await;
1394 Self::execute(
1395 &mut state,
1396 idempotency_key.as_deref(),
1397 fingerprint,
1398 |outcome| match outcome {
1399 MutationOutcome::Boolean(value) => Some(value.clone()),
1400 _ => None,
1401 },
1402 MutationOutcome::Boolean,
1403 |state| {
1404 let existing = Self::bucket(state, bucket)?.clone();
1405 if existing.objects.values().any(|history| !history.is_empty())
1406 || state
1407 .multiparts
1408 .values()
1409 .any(|upload| upload.bucket == *bucket)
1410 {
1411 return Err(ObjectsError::PreconditionFailed);
1412 }
1413 state.buckets.remove(&bucket.bucket_id);
1414 state.names.remove(&bucket.name);
1415 Ok(true)
1416 },
1417 )
1418 }
1419
1420 async fn put(&self, request: PutRequest) -> Result<wire::ObjectVersion, ObjectsError> {
1421 let digest = *blake3::hash(&request.body).as_bytes();
1422 let body = StoredBody::memory(request.body.clone());
1423 self.put_stored(request, body, digest).await
1424 }
1425
1426 async fn get(&self, request: GetRequest) -> Result<BufferedObject, ObjectsError> {
1427 Self::validate_key(&request.object_key)?;
1428 let state = self.state.lock().await;
1429 let bucket = Self::target_ref(&state, &request.target)?;
1430 let version = Self::visible(bucket, &request.object_key, request.version_id.as_deref())?;
1431 if request
1432 .if_match
1433 .as_ref()
1434 .is_some_and(|etag| *etag != version.descriptor.etag)
1435 || request
1436 .if_none_match
1437 .as_ref()
1438 .is_some_and(|etag| *etag == version.descriptor.etag)
1439 {
1440 return Err(ObjectsError::PreconditionFailed);
1441 }
1442 let descriptor = version.descriptor.clone();
1443 let body = version.body.clone().ok_or(ObjectsError::NotFound)?;
1444 drop(state);
1445 let (start, end) = match request.range {
1446 None => (0, body.len()),
1447 Some((start, end)) => {
1448 let start =
1449 usize::try_from(start).map_err(|_| ObjectsError::Invalid("invalid range"))?;
1450 let end = usize::try_from(end.unwrap_or(body.len().saturating_sub(1) as u64))
1451 .map_err(|_| ObjectsError::Invalid("invalid range"))?;
1452 if start > end || end >= body.len() {
1453 return Err(ObjectsError::Invalid("invalid range"));
1454 }
1455 (start, end.saturating_add(1))
1456 }
1457 };
1458 if end.saturating_sub(start) as u64 > request.maximum_bytes {
1459 return Err(ObjectsError::Capacity);
1460 }
1461 let selected = body.read_async(start, end).await?;
1462 Ok(BufferedObject {
1463 version: descriptor,
1464 body: selected,
1465 })
1466 }
1467
1468 async fn delete(
1469 &self,
1470 bucket: wire::BucketRef,
1471 object_key: String,
1472 version_id: Option<String>,
1473 condition: Option<Condition>,
1474 idempotency_key: Option<String>,
1475 ) -> Result<DeleteResult, ObjectsError> {
1476 Self::validate_key(&object_key)?;
1477 let bucket_fields = Self::reference_fields(&bucket);
1478 let condition_bytes = Self::condition_bytes(&condition);
1479 let fingerprint = Self::fingerprint(
1480 "delete",
1481 &[
1482 bucket_fields[0],
1483 bucket_fields[1],
1484 object_key.as_bytes(),
1485 version_id.as_deref().unwrap_or_default().as_bytes(),
1486 &condition_bytes,
1487 ],
1488 );
1489 let mut state = self.state.lock().await;
1490 Self::execute(
1491 &mut state,
1492 idempotency_key.as_deref(),
1493 fingerprint,
1494 |outcome| match outcome {
1495 MutationOutcome::Delete(value) => Some(value.clone()),
1496 _ => None,
1497 },
1498 MutationOutcome::Delete,
1499 |state| {
1500 let current = Self::bucket(state, &bucket)?
1501 .objects
1502 .get(&object_key)
1503 .and_then(|history| history.last())
1504 .cloned();
1505 if !Self::condition(current.as_ref(), &condition) {
1506 return Err(ObjectsError::PreconditionFailed);
1507 }
1508 if let Some(identity) = version_id {
1509 let (removed_body, empty) = {
1510 let Some(history) = Self::bucket_mut(state, &bucket)?
1511 .objects
1512 .get_mut(&object_key)
1513 else {
1514 return Ok(DeleteResult {
1515 existed: false,
1516 marker: None,
1517 });
1518 };
1519 let Some(position) = history
1520 .iter()
1521 .position(|version| version.descriptor.version_id == identity)
1522 else {
1523 return Ok(DeleteResult {
1524 existed: false,
1525 marker: None,
1526 });
1527 };
1528 let removed = history.remove(position);
1529 (removed.body, history.is_empty())
1530 };
1531 if let Some(body) = removed_body {
1532 state.release_body(&body)?;
1533 }
1534 if empty {
1535 Self::bucket_mut(state, &bucket)?
1536 .objects
1537 .remove(&object_key);
1538 }
1539 Ok(DeleteResult {
1540 existed: true,
1541 marker: None,
1542 })
1543 } else {
1544 let marker = Self::descriptor(
1545 state,
1546 blake3::hash(&[]).as_bytes(),
1547 0,
1548 wire::ObjectMetadata::default(),
1549 true,
1550 )?;
1551 Self::bucket_mut(state, &bucket)?
1552 .objects
1553 .entry(object_key)
1554 .or_default()
1555 .push(Version {
1556 descriptor: marker.clone(),
1557 body: None,
1558 });
1559 Ok(DeleteResult {
1560 existed: true,
1561 marker: Some(marker),
1562 })
1563 }
1564 },
1565 )
1566 }
1567
1568 async fn list(
1569 &self,
1570 target: ReadTarget,
1571 prefix: String,
1572 delimiter: Option<String>,
1573 versions: bool,
1574 page_size: u32,
1575 continuation: Option<String>,
1576 ) -> Result<ProviderListPage, ObjectsError> {
1577 if page_size == 0
1578 || page_size > limits::LIST_PAGE_ENTRIES
1579 || delimiter.as_ref().is_some_and(String::is_empty)
1580 {
1581 return Err(ObjectsError::Invalid("invalid listing"));
1582 }
1583 let mut state = self.state.lock().await;
1584 let binding = Self::binding(&target, &prefix, delimiter.as_deref(), versions);
1585 let (view_id, offset) = if let Some(token) = continuation {
1586 let (view, offset) = token
1587 .rsplit_once(':')
1588 .ok_or(ObjectsError::Invalid("invalid continuation"))?;
1589 let offset = offset
1590 .parse::<usize>()
1591 .map_err(|_| ObjectsError::Invalid("invalid continuation"))?;
1592 (view.to_owned(), offset)
1593 } else {
1594 let bucket = Self::target(&state, &target)?;
1595 let view_id = Self::next(&mut state, "listing")?;
1596 state.listings.insert(
1597 view_id.clone(),
1598 ListingView {
1599 binding: binding.clone(),
1600 items: Self::listing_items(&bucket, &prefix, delimiter.as_deref(), versions),
1601 },
1602 );
1603 (view_id, 0)
1604 };
1605 let view = state
1606 .listings
1607 .get(&view_id)
1608 .filter(|view| view.binding == binding)
1609 .ok_or(ObjectsError::Invalid("invalid continuation"))?;
1610 if offset > view.items.len() {
1611 return Err(ObjectsError::Invalid("invalid continuation"));
1612 }
1613 let end = offset
1614 .saturating_add(page_size as usize)
1615 .min(view.items.len());
1616 let mut entries = Vec::new();
1617 let mut common_prefixes = Vec::new();
1618 for item in &view.items[offset..end] {
1619 match item {
1620 ListingItem::Entry(value) => entries.push(value.as_ref().clone()),
1621 ListingItem::Prefix(value) => common_prefixes.push(value.clone()),
1622 }
1623 }
1624 Ok(ProviderListPage {
1625 entries,
1626 common_prefixes,
1627 continuation: (end < view.items.len()).then(|| format!("{view_id}:{end}")),
1628 })
1629 }
1630
1631 async fn snapshot(
1632 &self,
1633 bucket: wire::BucketRef,
1634 idempotency_key: Option<String>,
1635 ) -> Result<wire::Snapshot, ObjectsError> {
1636 let fields = Self::reference_fields(&bucket);
1637 let fingerprint = Self::fingerprint("snapshot", &fields);
1638 let mut state = self.state.lock().await;
1639 Self::execute(
1640 &mut state,
1641 idempotency_key.as_deref(),
1642 fingerprint,
1643 |outcome| match outcome {
1644 MutationOutcome::Snapshot(value) => Some(value.clone()),
1645 _ => None,
1646 },
1647 MutationOutcome::Snapshot,
1648 |state| {
1649 let source = Self::bucket(state, &bucket)?.clone();
1650 state.retain_bucket(&source)?;
1651 let snapshot_id = Self::next(state, "snapshot")?;
1652 let reference = wire::SnapshotRef {
1653 snapshot_id: snapshot_id.clone(),
1654 source_bucket_id: bucket.bucket_id,
1655 };
1656 let created_at = Self::timestamp(state);
1657 state.snapshots.insert(
1658 snapshot_id,
1659 SnapshotState {
1660 reference: reference.clone(),
1661 bucket: source,
1662 },
1663 );
1664 Ok(wire::Snapshot {
1665 snapshot: Some(reference),
1666 created_at: Some(created_at),
1667 })
1668 },
1669 )
1670 }
1671
1672 async fn destroy_snapshot(
1673 &self,
1674 snapshot: wire::SnapshotRef,
1675 idempotency_key: Option<String>,
1676 ) -> Result<bool, ObjectsError> {
1677 let fingerprint = Self::fingerprint(
1678 "destroy-snapshot",
1679 &[
1680 snapshot.snapshot_id.as_bytes(),
1681 snapshot.source_bucket_id.as_bytes(),
1682 ],
1683 );
1684 let mut state = self.state.lock().await;
1685 Self::execute(
1686 &mut state,
1687 idempotency_key.as_deref(),
1688 fingerprint,
1689 |outcome| match outcome {
1690 MutationOutcome::Boolean(value) => Some(value.clone()),
1691 _ => None,
1692 },
1693 MutationOutcome::Boolean,
1694 |state| {
1695 let exists = state
1696 .snapshots
1697 .get(&snapshot.snapshot_id)
1698 .is_some_and(|value| value.reference == snapshot);
1699 if !exists {
1700 return Ok(false);
1701 }
1702 let removed = state
1703 .snapshots
1704 .remove(&snapshot.snapshot_id)
1705 .ok_or(ObjectsError::Unavailable)?;
1706 state.release_bucket(&removed.bucket)?;
1707 Ok(true)
1708 },
1709 )
1710 }
1711
1712 async fn fork(
1713 &self,
1714 source: ReadTarget,
1715 destination_name: String,
1716 idempotency_key: Option<String>,
1717 ) -> Result<wire::Bucket, ObjectsError> {
1718 let source_identity = match &source {
1719 ReadTarget::Bucket(value) => format!("bucket:{}:{}", value.bucket_id, value.name),
1720 ReadTarget::Snapshot(value) => {
1721 format!("snapshot:{}:{}", value.snapshot_id, value.source_bucket_id)
1722 }
1723 };
1724 let fingerprint = Self::fingerprint(
1725 "fork",
1726 &[source_identity.as_bytes(), destination_name.as_bytes()],
1727 );
1728 let mut state = self.state.lock().await;
1729 Self::execute(
1730 &mut state,
1731 idempotency_key.as_deref(),
1732 fingerprint,
1733 |outcome| match outcome {
1734 MutationOutcome::Bucket(value) => Some(value.clone()),
1735 _ => None,
1736 },
1737 MutationOutcome::Bucket,
1738 |state| {
1739 if state.names.contains_key(&destination_name) {
1740 return Err(ObjectsError::AlreadyExists);
1741 }
1742 let source = Self::target(state, &source)?;
1743 state.retain_bucket(&source)?;
1744 let bucket_id = Self::next(state, "bucket")?;
1745 let reference = wire::BucketRef {
1746 bucket_id: bucket_id.clone(),
1747 name: destination_name.clone(),
1748 };
1749 let created_at = Self::timestamp(state);
1750 state.names.insert(destination_name, bucket_id.clone());
1751 state.buckets.insert(
1752 bucket_id,
1753 BucketState {
1754 reference: reference.clone(),
1755 created_at,
1756 objects: source.objects,
1757 },
1758 );
1759 Ok(wire::Bucket {
1760 bucket: Some(reference),
1761 created_at: Some(created_at),
1762 })
1763 },
1764 )
1765 }
1766
1767 async fn create_multipart(
1768 &self,
1769 bucket: wire::BucketRef,
1770 object_key: String,
1771 metadata: wire::ObjectMetadata,
1772 condition: Option<Condition>,
1773 idempotency_key: Option<String>,
1774 ) -> Result<wire::MultipartUpload, ObjectsError> {
1775 Self::validate_key(&object_key)?;
1776 Self::validate_metadata(&metadata)?;
1777 let bucket_fields = Self::reference_fields(&bucket);
1778 let metadata_bytes = Self::metadata_bytes(&metadata);
1779 let condition_bytes = Self::condition_bytes(&condition);
1780 let fingerprint = Self::fingerprint(
1781 "create-multipart",
1782 &[
1783 bucket_fields[0],
1784 bucket_fields[1],
1785 object_key.as_bytes(),
1786 &metadata_bytes,
1787 &condition_bytes,
1788 ],
1789 );
1790 let mut state = self.state.lock().await;
1791 Self::execute(
1792 &mut state,
1793 idempotency_key.as_deref(),
1794 fingerprint,
1795 |outcome| match outcome {
1796 MutationOutcome::Multipart(value) => Some(value.clone()),
1797 _ => None,
1798 },
1799 MutationOutcome::Multipart,
1800 |state| {
1801 Self::bucket(state, &bucket)?;
1802 let upload_id = Self::next(state, "upload")?;
1803 state.multiparts.insert(
1804 upload_id.clone(),
1805 MultipartState {
1806 bucket,
1807 object_key,
1808 metadata,
1809 condition,
1810 parts: BTreeMap::new(),
1811 },
1812 );
1813 Ok(wire::MultipartUpload { upload_id })
1814 },
1815 )
1816 }
1817
1818 async fn upload_part(
1819 &self,
1820 bucket: wire::BucketRef,
1821 object_key: String,
1822 upload_id: String,
1823 part_number: u32,
1824 body: bytes::Bytes,
1825 idempotency_key: Option<String>,
1826 ) -> Result<wire::UploadedPart, ObjectsError> {
1827 let digest = *blake3::hash(&body).as_bytes();
1828 self.upload_part_stored(StoredPartRequest {
1829 bucket,
1830 object_key,
1831 upload_id,
1832 part_number,
1833 body: StoredBody::memory(body),
1834 body_digest: digest,
1835 idempotency_key,
1836 })
1837 .await
1838 }
1839
1840 async fn list_parts(
1841 &self,
1842 bucket: wire::BucketRef,
1843 object_key: String,
1844 upload_id: String,
1845 ) -> Result<Vec<wire::UploadedPart>, ObjectsError> {
1846 let state = self.state.lock().await;
1847 let upload = state
1848 .multiparts
1849 .get(&upload_id)
1850 .filter(|upload| upload.bucket == bucket && upload.object_key == object_key)
1851 .ok_or(ObjectsError::NotFound)?;
1852 Ok(upload
1853 .parts
1854 .values()
1855 .map(|(part, _)| part.clone())
1856 .collect())
1857 }
1858
1859 async fn complete_multipart(
1860 &self,
1861 bucket: wire::BucketRef,
1862 object_key: String,
1863 upload_id: String,
1864 parts: Vec<wire::UploadedPart>,
1865 idempotency_key: Option<String>,
1866 ) -> Result<wire::ObjectVersion, ObjectsError> {
1867 if parts.is_empty() || parts.len() > limits::MULTIPART_PARTS as usize {
1868 return Err(ObjectsError::Invalid("invalid multipart completion"));
1869 }
1870 let bucket_fields = Self::reference_fields(&bucket);
1871 let mut part_bytes = Vec::new();
1872 for part in &parts {
1873 part_bytes.extend_from_slice(&part.part_number.to_le_bytes());
1874 part_bytes.extend_from_slice(&(part.etag.len() as u64).to_le_bytes());
1875 part_bytes.extend_from_slice(part.etag.as_bytes());
1876 part_bytes.extend_from_slice(&part.size.to_le_bytes());
1877 }
1878 let fingerprint = Self::fingerprint(
1879 "complete-multipart",
1880 &[
1881 bucket_fields[0],
1882 bucket_fields[1],
1883 object_key.as_bytes(),
1884 upload_id.as_bytes(),
1885 &part_bytes,
1886 ],
1887 );
1888 let mut state = self.state.lock().await;
1889 Self::execute(
1890 &mut state,
1891 idempotency_key.as_deref(),
1892 fingerprint,
1893 |outcome| match outcome {
1894 MutationOutcome::Version(value) => Some(value.clone()),
1895 _ => None,
1896 },
1897 MutationOutcome::Version,
1898 |state| {
1899 let upload = state
1900 .multiparts
1901 .remove(&upload_id)
1902 .filter(|upload| upload.bucket == bucket && upload.object_key == object_key)
1903 .ok_or(ObjectsError::NotFound)?;
1904 let exact = upload.parts.values().map(|(part, _)| part).eq(parts.iter());
1905 let sizes_valid = parts.iter().enumerate().all(|(index, part)| {
1906 index + 1 == parts.len() || part.size >= limits::MIN_MULTIPART_PART_BYTES
1907 });
1908 if !exact || !sizes_valid {
1909 state.multiparts.insert(upload_id, upload);
1910 return Err(ObjectsError::Invalid("multipart receipts do not match"));
1911 }
1912 let current = Self::bucket(state, &bucket)?
1913 .objects
1914 .get(&object_key)
1915 .and_then(|history| history.last())
1916 .cloned();
1917 if !Self::condition(current.as_ref(), &upload.condition) {
1918 state.multiparts.insert(upload_id, upload);
1919 return Err(ObjectsError::PreconditionFailed);
1920 }
1921 let body_length =
1922 match upload.parts.values().try_fold(0usize, |total, (_, body)| {
1923 total.checked_add(body.len()).ok_or(ObjectsError::Capacity)
1924 }) {
1925 Ok(body_length) => body_length,
1926 Err(error) => {
1927 state.multiparts.insert(upload_id, upload);
1928 return Err(error);
1929 }
1930 };
1931 if body_length > state.maximum_object_bytes {
1932 state.multiparts.insert(upload_id, upload);
1933 return Err(ObjectsError::Capacity);
1934 }
1935 let bodies = upload
1936 .parts
1937 .into_values()
1938 .map(|(_, body)| body)
1939 .collect::<Vec<_>>();
1940 let mut hasher = blake3::Hasher::new();
1941 for body in &bodies {
1942 body.update_hash(&mut hasher)?;
1943 }
1944 let body_digest = *hasher.finalize().as_bytes();
1945 let descriptor =
1946 Self::descriptor(state, &body_digest, body_length, upload.metadata, false)?;
1947 Self::bucket_mut(state, &bucket)?
1948 .objects
1949 .entry(object_key)
1950 .or_default()
1951 .push(Version {
1952 descriptor: descriptor.clone(),
1953 body: Some(StoredBody::Composite {
1954 parts: bodies.into(),
1955 length: body_length,
1956 }),
1957 });
1958 Ok(descriptor)
1959 },
1960 )
1961 }
1962
1963 async fn abort_multipart(
1964 &self,
1965 bucket: wire::BucketRef,
1966 object_key: String,
1967 upload_id: String,
1968 idempotency_key: Option<String>,
1969 ) -> Result<bool, ObjectsError> {
1970 let bucket_fields = Self::reference_fields(&bucket);
1971 let fingerprint = Self::fingerprint(
1972 "abort-multipart",
1973 &[
1974 bucket_fields[0],
1975 bucket_fields[1],
1976 object_key.as_bytes(),
1977 upload_id.as_bytes(),
1978 ],
1979 );
1980 let mut state = self.state.lock().await;
1981 Self::execute(
1982 &mut state,
1983 idempotency_key.as_deref(),
1984 fingerprint,
1985 |outcome| match outcome {
1986 MutationOutcome::Boolean(value) => Some(value.clone()),
1987 _ => None,
1988 },
1989 MutationOutcome::Boolean,
1990 |state| {
1991 let matches = state.multiparts.get(&upload_id).is_some_and(|upload| {
1992 upload.bucket == bucket && upload.object_key == object_key
1993 });
1994 if !matches {
1995 return Ok(false);
1996 }
1997 if let Some(upload) = state.multiparts.remove(&upload_id) {
1998 for (_, body) in upload.parts.values() {
1999 state.release_body(body)?;
2000 }
2001 }
2002 Ok(true)
2003 },
2004 )
2005 }
2006}
2007
2008#[cfg(test)]
2009mod tests {
2010 use super::*;
2011
2012 fn metadata() -> wire::ObjectMetadata {
2013 wire::ObjectMetadata {
2014 content_type: "text/plain".into(),
2015 ..Default::default()
2016 }
2017 }
2018
2019 async fn put(
2020 store: &MemoryObjects,
2021 bucket: &wire::BucketRef,
2022 object_key: &str,
2023 body: &[u8],
2024 condition: Option<Condition>,
2025 ) -> wire::ObjectVersion {
2026 store
2027 .put(PutRequest {
2028 bucket: bucket.clone(),
2029 object_key: object_key.into(),
2030 body: bytes::Bytes::copy_from_slice(body),
2031 metadata: metadata(),
2032 condition,
2033 idempotency_key: None,
2034 })
2035 .await
2036 .unwrap_or_else(|_| unreachable!())
2037 }
2038
2039 #[tokio::test]
2040 async fn synchronous_bucket_composition_is_exact_and_capacity_bounded() {
2041 assert!(matches!(
2042 MemoryObjects::new(0),
2043 Err(ObjectsError::Invalid("memory byte limit must be positive"))
2044 ));
2045 assert!(MemoryObjects::with_bucket("INVALID", 4).is_err());
2046 assert!(MemoryObjects::with_bucket_limits("embedded", 5, 4).is_err());
2047
2048 let (independent, independent_bucket) =
2049 MemoryObjects::with_bucket_limits("independent", 3, 6)
2050 .unwrap_or_else(|_| unreachable!());
2051 put(&independent, &independent_bucket, "first", b"one", None).await;
2052 put(&independent, &independent_bucket, "second", b"two", None).await;
2053 assert_eq!(
2054 independent
2055 .put(PutRequest {
2056 bucket: independent_bucket.clone(),
2057 object_key: "aggregate-overflow".into(),
2058 body: bytes::Bytes::from_static(b"x"),
2059 metadata: metadata(),
2060 condition: None,
2061 idempotency_key: None,
2062 })
2063 .await,
2064 Err(ObjectsError::Capacity)
2065 );
2066 let (per_object, per_object_bucket) = MemoryObjects::with_bucket_limits("per-object", 3, 6)
2067 .unwrap_or_else(|_| unreachable!());
2068 assert_eq!(
2069 per_object
2070 .put(PutRequest {
2071 bucket: per_object_bucket,
2072 object_key: "too-large".into(),
2073 body: bytes::Bytes::from_static(b"four"),
2074 metadata: metadata(),
2075 condition: None,
2076 idempotency_key: None,
2077 })
2078 .await,
2079 Err(ObjectsError::Capacity)
2080 );
2081
2082 let (store, bucket) =
2083 MemoryObjects::with_bucket("embedded", 4).unwrap_or_else(|_| unreachable!());
2084 assert_eq!(
2085 store
2086 .head_bucket(&bucket)
2087 .await
2088 .unwrap_or_else(|_| unreachable!())
2089 .bucket,
2090 Some(bucket.clone())
2091 );
2092 put(&store, &bucket, "full", b"four", None).await;
2093 assert_eq!(
2094 store
2095 .put(PutRequest {
2096 bucket,
2097 object_key: "overflow".into(),
2098 body: bytes::Bytes::from_static(b"x"),
2099 metadata: metadata(),
2100 condition: None,
2101 idempotency_key: None,
2102 })
2103 .await,
2104 Err(ObjectsError::Capacity)
2105 );
2106 }
2107
2108 #[tokio::test]
2109 async fn memory_provider_passes_the_public_conformance_suite() -> Result<(), ObjectsError> {
2110 crate::conformance::verify(&MemoryObjects::default(), "conformance").await
2111 }
2112
2113 #[tokio::test]
2114 async fn shared_snapshot_and_fork_bodies_retain_exact_capacity() {
2115 let store = MemoryObjects::new(3).unwrap_or_else(|_| unreachable!());
2116 let source = store
2117 .create_bucket("source-bucket".into(), Some("create-source".into()))
2118 .await
2119 .unwrap_or_else(|_| unreachable!())
2120 .bucket
2121 .unwrap_or_else(|| unreachable!());
2122 let version = put(&store, &source, "shared", b"abc", None).await;
2123 let version_id = version.version_id.clone();
2124 let snapshot = store
2125 .snapshot(source.clone(), Some("snapshot".into()))
2126 .await
2127 .unwrap_or_else(|_| unreachable!())
2128 .snapshot
2129 .unwrap_or_else(|| unreachable!());
2130 assert!(
2131 store
2132 .delete(
2133 source.clone(),
2134 "shared".into(),
2135 Some(version.version_id),
2136 None,
2137 Some("delete-source".into()),
2138 )
2139 .await
2140 .is_ok()
2141 );
2142 assert!(matches!(
2143 store
2144 .put(PutRequest {
2145 bucket: source.clone(),
2146 object_key: "replacement".into(),
2147 body: bytes::Bytes::from_static(b"def"),
2148 metadata: metadata(),
2149 condition: None,
2150 idempotency_key: None,
2151 })
2152 .await,
2153 Err(ObjectsError::Capacity)
2154 ));
2155 let fork = store
2156 .fork(
2157 ReadTarget::Snapshot(snapshot.clone()),
2158 "fork-bucket".into(),
2159 Some("fork".into()),
2160 )
2161 .await
2162 .unwrap_or_else(|_| unreachable!())
2163 .bucket
2164 .unwrap_or_else(|| unreachable!());
2165 assert!(
2166 store
2167 .destroy_snapshot(snapshot, Some("destroy-snapshot".into()))
2168 .await
2169 .unwrap_or(false)
2170 );
2171 assert!(matches!(
2172 store
2173 .put(PutRequest {
2174 bucket: source.clone(),
2175 object_key: "replacement".into(),
2176 body: bytes::Bytes::from_static(b"def"),
2177 metadata: metadata(),
2178 condition: None,
2179 idempotency_key: None,
2180 })
2181 .await,
2182 Err(ObjectsError::Capacity)
2183 ));
2184 assert!(
2185 store
2186 .delete(
2187 fork,
2188 "shared".into(),
2189 Some(version_id),
2190 None,
2191 Some("delete-fork".into()),
2192 )
2193 .await
2194 .is_ok()
2195 );
2196 assert!(
2197 store
2198 .put(PutRequest {
2199 bucket: source,
2200 object_key: "replacement".into(),
2201 body: bytes::Bytes::from_static(b"def"),
2202 metadata: metadata(),
2203 condition: None,
2204 idempotency_key: None,
2205 })
2206 .await
2207 .is_ok()
2208 );
2209 }
2210}