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(remainder) = object_key.get(prefix.len()..)
1082 && let Some(position) = remainder.find(delimiter)
1083 && let Some(group) = object_key.get(..prefix.len() + position + delimiter.len())
1084 {
1085 prefixes.insert(group.to_owned());
1086 continue;
1087 }
1088 if versions {
1089 items.extend(history.iter().rev().map(|version| {
1090 ListingItem::Entry(Box::new(wire::ListEntry {
1091 object_key: object_key.clone(),
1092 version: Some(version.descriptor.clone()),
1093 }))
1094 }));
1095 } else if let Some(version) = history.last()
1096 && !version.descriptor.delete_marker
1097 {
1098 items.push(ListingItem::Entry(Box::new(wire::ListEntry {
1099 object_key: object_key.clone(),
1100 version: Some(version.descriptor.clone()),
1101 })));
1102 }
1103 }
1104 items.extend(prefixes.into_iter().map(ListingItem::Prefix));
1105 items.sort_by(|left, right| {
1106 let left = match left {
1107 ListingItem::Entry(value) => &value.object_key,
1108 ListingItem::Prefix(value) => value,
1109 };
1110 let right = match right {
1111 ListingItem::Entry(value) => &value.object_key,
1112 ListingItem::Prefix(value) => value,
1113 };
1114 left.cmp(right)
1115 });
1116 items
1117 }
1118
1119 async fn put_stored(
1120 &self,
1121 request: PutRequest,
1122 body: StoredBody,
1123 body_digest: [u8; 32],
1124 ) -> Result<wire::ObjectVersion, ObjectsError> {
1125 let PutRequest {
1126 bucket,
1127 object_key,
1128 body: _,
1129 metadata,
1130 condition,
1131 idempotency_key,
1132 } = request;
1133 Self::validate_key(&object_key)?;
1134 Self::validate_metadata(&metadata)?;
1135 let bucket_fields = Self::reference_fields(&bucket);
1136 let condition_bytes = Self::condition_bytes(&condition);
1137 let metadata_bytes = Self::metadata_bytes(&metadata);
1138 let fingerprint = Self::fingerprint(
1139 "put",
1140 &[
1141 bucket_fields[0],
1142 bucket_fields[1],
1143 object_key.as_bytes(),
1144 &condition_bytes,
1145 &metadata_bytes,
1146 &body_digest,
1147 ],
1148 );
1149 let mut state = self.state.lock().await;
1150 Self::execute(
1151 &mut state,
1152 idempotency_key.as_deref(),
1153 fingerprint,
1154 |outcome| match outcome {
1155 MutationOutcome::Version(value) => Some(value.clone()),
1156 _ => None,
1157 },
1158 MutationOutcome::Version,
1159 |state| {
1160 let current = Self::bucket(state, &bucket)?
1161 .objects
1162 .get(&object_key)
1163 .and_then(|history| history.last())
1164 .cloned();
1165 if !Self::condition(current.as_ref(), &condition) {
1166 return Err(ObjectsError::PreconditionFailed);
1167 }
1168 let size = body.len();
1169 let additional = state.additional_body_bytes(&body)?;
1170 if size > state.maximum_object_bytes
1171 || state
1172 .bytes
1173 .checked_add(additional)
1174 .is_none_or(|bytes| bytes > state.maximum_bytes)
1175 {
1176 return Err(ObjectsError::Capacity);
1177 }
1178 let descriptor = Self::descriptor(state, &body_digest, size, metadata, false)?;
1179 state.retain_body(&body)?;
1180 Self::bucket_mut(state, &bucket)?
1181 .objects
1182 .entry(object_key)
1183 .or_default()
1184 .push(Version {
1185 descriptor: descriptor.clone(),
1186 body: Some(body),
1187 });
1188 Ok(descriptor)
1189 },
1190 )
1191 }
1192
1193 #[cfg(feature = "local")]
1194 pub(crate) async fn put_external(
1195 &self,
1196 request: PutRequest,
1197 external: ExternalBody,
1198 ) -> Result<wire::ObjectVersion, ObjectsError> {
1199 let ExternalBody {
1200 root,
1201 digest,
1202 length,
1203 } = external;
1204 self.put_stored(
1205 request,
1206 StoredBody::Local {
1207 root: Arc::new(root),
1208 digest,
1209 length,
1210 },
1211 digest,
1212 )
1213 .await
1214 }
1215
1216 async fn upload_part_stored(
1217 &self,
1218 request: StoredPartRequest,
1219 ) -> Result<wire::UploadedPart, ObjectsError> {
1220 let StoredPartRequest {
1221 bucket,
1222 object_key,
1223 upload_id,
1224 part_number,
1225 body,
1226 body_digest,
1227 idempotency_key,
1228 } = request;
1229 if !(1..=limits::MULTIPART_PARTS).contains(&part_number)
1230 || body.len() as u64 > limits::MAX_MULTIPART_PART_BYTES
1231 {
1232 return Err(ObjectsError::Invalid("invalid multipart part"));
1233 }
1234 let bucket_fields = Self::reference_fields(&bucket);
1235 let part_number_bytes = part_number.to_le_bytes();
1236 let fingerprint = Self::fingerprint(
1237 "upload-part",
1238 &[
1239 bucket_fields[0],
1240 bucket_fields[1],
1241 object_key.as_bytes(),
1242 upload_id.as_bytes(),
1243 &part_number_bytes,
1244 &body_digest,
1245 ],
1246 );
1247 let mut state = self.state.lock().await;
1248 Self::execute(
1249 &mut state,
1250 idempotency_key.as_deref(),
1251 fingerprint,
1252 |outcome| match outcome {
1253 MutationOutcome::Part(value) => Some(value.clone()),
1254 _ => None,
1255 },
1256 MutationOutcome::Part,
1257 |state| {
1258 let existing_body = state
1259 .multiparts
1260 .get(&upload_id)
1261 .filter(|upload| upload.bucket == bucket && upload.object_key == object_key)
1262 .ok_or(ObjectsError::NotFound)?
1263 .parts
1264 .get(&part_number)
1265 .map(|(_, body)| body.clone());
1266 if let Some(existing) = &existing_body {
1267 state.release_body(existing)?;
1268 }
1269 let additional = state.additional_body_bytes(&body)?;
1270 if state
1271 .bytes
1272 .checked_add(additional)
1273 .is_none_or(|bytes| bytes > state.maximum_bytes)
1274 {
1275 if let Some(existing) = &existing_body {
1276 state.retain_body(existing)?;
1277 }
1278 return Err(ObjectsError::Capacity);
1279 }
1280 let part = wire::UploadedPart {
1281 part_number,
1282 etag: format!("\"{}\"", blake3::Hash::from_bytes(body_digest).to_hex()),
1283 size: body.len() as u64,
1284 };
1285 state
1286 .multiparts
1287 .get_mut(&upload_id)
1288 .ok_or(ObjectsError::NotFound)?
1289 .parts
1290 .insert(part_number, (part.clone(), body));
1291 let admitted = state
1292 .multiparts
1293 .get(&upload_id)
1294 .and_then(|upload| upload.parts.get(&part_number))
1295 .map(|(_, body)| body.clone())
1296 .ok_or(ObjectsError::Unavailable)?;
1297 state.retain_body(&admitted)?;
1298 Ok(part)
1299 },
1300 )
1301 }
1302
1303 #[cfg(feature = "local")]
1304 pub(crate) async fn upload_part_external(
1305 &self,
1306 bucket: wire::BucketRef,
1307 object_key: String,
1308 upload_id: String,
1309 part_number: u32,
1310 external: ExternalBody,
1311 idempotency_key: Option<String>,
1312 ) -> Result<wire::UploadedPart, ObjectsError> {
1313 let ExternalBody {
1314 root,
1315 digest,
1316 length,
1317 } = external;
1318 self.upload_part_stored(StoredPartRequest {
1319 bucket,
1320 object_key,
1321 upload_id,
1322 part_number,
1323 body: StoredBody::Local {
1324 root: Arc::new(root),
1325 digest,
1326 length,
1327 },
1328 body_digest: digest,
1329 idempotency_key,
1330 })
1331 .await
1332 }
1333}
1334
1335#[async_trait]
1336impl ObjectsProvider for MemoryObjects {
1337 async fn create_bucket(
1338 &self,
1339 name: String,
1340 idempotency_key: Option<String>,
1341 ) -> Result<wire::Bucket, ObjectsError> {
1342 Self::validate_bucket_name(&name)?;
1343 let fingerprint = Self::fingerprint("create-bucket", &[name.as_bytes()]);
1344 let mut state = self.state.lock().await;
1345 Self::execute(
1346 &mut state,
1347 idempotency_key.as_deref(),
1348 fingerprint,
1349 |outcome| match outcome {
1350 MutationOutcome::Bucket(value) => Some(value.clone()),
1351 _ => None,
1352 },
1353 MutationOutcome::Bucket,
1354 |state| {
1355 if state.names.contains_key(&name) {
1356 return Err(ObjectsError::AlreadyExists);
1357 }
1358 let bucket_id = Self::next(state, "bucket")?;
1359 let reference = wire::BucketRef {
1360 bucket_id: bucket_id.clone(),
1361 name: name.clone(),
1362 };
1363 let bucket = BucketState {
1364 reference: reference.clone(),
1365 created_at: Self::timestamp(state),
1366 objects: BTreeMap::new(),
1367 };
1368 state.names.insert(name, bucket_id.clone());
1369 state.buckets.insert(bucket_id, bucket.clone());
1370 Ok(wire::Bucket {
1371 bucket: Some(reference),
1372 created_at: Some(bucket.created_at),
1373 })
1374 },
1375 )
1376 }
1377
1378 async fn head_bucket(&self, bucket: &wire::BucketRef) -> Result<wire::Bucket, ObjectsError> {
1379 let state = self.state.lock().await;
1380 let bucket = Self::bucket(&state, bucket)?;
1381 Ok(wire::Bucket {
1382 bucket: Some(bucket.reference.clone()),
1383 created_at: Some(bucket.created_at),
1384 })
1385 }
1386
1387 async fn delete_bucket(
1388 &self,
1389 bucket: &wire::BucketRef,
1390 idempotency_key: Option<String>,
1391 ) -> Result<bool, ObjectsError> {
1392 let fields = Self::reference_fields(bucket);
1393 let fingerprint = Self::fingerprint("delete-bucket", &fields);
1394 let mut state = self.state.lock().await;
1395 Self::execute(
1396 &mut state,
1397 idempotency_key.as_deref(),
1398 fingerprint,
1399 |outcome| match outcome {
1400 MutationOutcome::Boolean(value) => Some(value.clone()),
1401 _ => None,
1402 },
1403 MutationOutcome::Boolean,
1404 |state| {
1405 let existing = Self::bucket(state, bucket)?.clone();
1406 if existing.objects.values().any(|history| !history.is_empty())
1407 || state
1408 .multiparts
1409 .values()
1410 .any(|upload| upload.bucket == *bucket)
1411 {
1412 return Err(ObjectsError::PreconditionFailed);
1413 }
1414 state.buckets.remove(&bucket.bucket_id);
1415 state.names.remove(&bucket.name);
1416 Ok(true)
1417 },
1418 )
1419 }
1420
1421 async fn put(&self, request: PutRequest) -> Result<wire::ObjectVersion, ObjectsError> {
1422 let digest = *blake3::hash(&request.body).as_bytes();
1423 let body = StoredBody::memory(request.body.clone());
1424 self.put_stored(request, body, digest).await
1425 }
1426
1427 async fn get(&self, request: GetRequest) -> Result<BufferedObject, ObjectsError> {
1428 Self::validate_key(&request.object_key)?;
1429 let state = self.state.lock().await;
1430 let bucket = Self::target_ref(&state, &request.target)?;
1431 let version = Self::visible(bucket, &request.object_key, request.version_id.as_deref())?;
1432 if request
1433 .if_match
1434 .as_ref()
1435 .is_some_and(|etag| *etag != version.descriptor.etag)
1436 || request
1437 .if_none_match
1438 .as_ref()
1439 .is_some_and(|etag| *etag == version.descriptor.etag)
1440 {
1441 return Err(ObjectsError::PreconditionFailed);
1442 }
1443 let descriptor = version.descriptor.clone();
1444 let body = version.body.clone().ok_or(ObjectsError::NotFound)?;
1445 drop(state);
1446 let (start, end) = match request.range {
1447 None => (0, body.len()),
1448 Some((start, end)) => {
1449 let start =
1450 usize::try_from(start).map_err(|_| ObjectsError::Invalid("invalid range"))?;
1451 let end = usize::try_from(end.unwrap_or(body.len().saturating_sub(1) as u64))
1452 .map_err(|_| ObjectsError::Invalid("invalid range"))?;
1453 if start > end || end >= body.len() {
1454 return Err(ObjectsError::Invalid("invalid range"));
1455 }
1456 (start, end.saturating_add(1))
1457 }
1458 };
1459 if end.saturating_sub(start) as u64 > request.maximum_bytes {
1460 return Err(ObjectsError::Capacity);
1461 }
1462 let selected = body.read_async(start, end).await?;
1463 Ok(BufferedObject {
1464 version: descriptor,
1465 body: selected,
1466 })
1467 }
1468
1469 async fn delete(
1470 &self,
1471 bucket: wire::BucketRef,
1472 object_key: String,
1473 version_id: Option<String>,
1474 condition: Option<Condition>,
1475 idempotency_key: Option<String>,
1476 ) -> Result<DeleteResult, ObjectsError> {
1477 Self::validate_key(&object_key)?;
1478 let bucket_fields = Self::reference_fields(&bucket);
1479 let condition_bytes = Self::condition_bytes(&condition);
1480 let fingerprint = Self::fingerprint(
1481 "delete",
1482 &[
1483 bucket_fields[0],
1484 bucket_fields[1],
1485 object_key.as_bytes(),
1486 version_id.as_deref().unwrap_or_default().as_bytes(),
1487 &condition_bytes,
1488 ],
1489 );
1490 let mut state = self.state.lock().await;
1491 Self::execute(
1492 &mut state,
1493 idempotency_key.as_deref(),
1494 fingerprint,
1495 |outcome| match outcome {
1496 MutationOutcome::Delete(value) => Some(value.clone()),
1497 _ => None,
1498 },
1499 MutationOutcome::Delete,
1500 |state| {
1501 let current = Self::bucket(state, &bucket)?
1502 .objects
1503 .get(&object_key)
1504 .and_then(|history| history.last())
1505 .cloned();
1506 if !Self::condition(current.as_ref(), &condition) {
1507 return Err(ObjectsError::PreconditionFailed);
1508 }
1509 if let Some(identity) = version_id {
1510 let (removed_body, empty) = {
1511 let Some(history) = Self::bucket_mut(state, &bucket)?
1512 .objects
1513 .get_mut(&object_key)
1514 else {
1515 return Ok(DeleteResult {
1516 existed: false,
1517 marker: None,
1518 });
1519 };
1520 let Some(position) = history
1521 .iter()
1522 .position(|version| version.descriptor.version_id == identity)
1523 else {
1524 return Ok(DeleteResult {
1525 existed: false,
1526 marker: None,
1527 });
1528 };
1529 let removed = history.remove(position);
1530 (removed.body, history.is_empty())
1531 };
1532 if let Some(body) = removed_body {
1533 state.release_body(&body)?;
1534 }
1535 if empty {
1536 Self::bucket_mut(state, &bucket)?
1537 .objects
1538 .remove(&object_key);
1539 }
1540 Ok(DeleteResult {
1541 existed: true,
1542 marker: None,
1543 })
1544 } else {
1545 let marker = Self::descriptor(
1546 state,
1547 blake3::hash(&[]).as_bytes(),
1548 0,
1549 wire::ObjectMetadata::default(),
1550 true,
1551 )?;
1552 Self::bucket_mut(state, &bucket)?
1553 .objects
1554 .entry(object_key)
1555 .or_default()
1556 .push(Version {
1557 descriptor: marker.clone(),
1558 body: None,
1559 });
1560 Ok(DeleteResult {
1561 existed: true,
1562 marker: Some(marker),
1563 })
1564 }
1565 },
1566 )
1567 }
1568
1569 async fn list(
1570 &self,
1571 target: ReadTarget,
1572 prefix: String,
1573 delimiter: Option<String>,
1574 versions: bool,
1575 page_size: u32,
1576 continuation: Option<String>,
1577 ) -> Result<ProviderListPage, ObjectsError> {
1578 if page_size == 0
1579 || page_size > limits::LIST_PAGE_ENTRIES
1580 || delimiter.as_ref().is_some_and(String::is_empty)
1581 {
1582 return Err(ObjectsError::Invalid("invalid listing"));
1583 }
1584 let mut state = self.state.lock().await;
1585 let binding = Self::binding(&target, &prefix, delimiter.as_deref(), versions);
1586 let (view_id, offset) = if let Some(token) = continuation {
1587 let (view, offset) = token
1588 .rsplit_once(':')
1589 .ok_or(ObjectsError::Invalid("invalid continuation"))?;
1590 let offset = offset
1591 .parse::<usize>()
1592 .map_err(|_| ObjectsError::Invalid("invalid continuation"))?;
1593 (view.to_owned(), offset)
1594 } else {
1595 let bucket = Self::target(&state, &target)?;
1596 let view_id = Self::next(&mut state, "listing")?;
1597 state.listings.insert(
1598 view_id.clone(),
1599 ListingView {
1600 binding: binding.clone(),
1601 items: Self::listing_items(&bucket, &prefix, delimiter.as_deref(), versions),
1602 },
1603 );
1604 (view_id, 0)
1605 };
1606 let view = state
1607 .listings
1608 .get(&view_id)
1609 .filter(|view| view.binding == binding)
1610 .ok_or(ObjectsError::Invalid("invalid continuation"))?;
1611 if offset > view.items.len() {
1612 return Err(ObjectsError::Invalid("invalid continuation"));
1613 }
1614 let end = offset
1615 .saturating_add(page_size as usize)
1616 .min(view.items.len());
1617 let mut entries = Vec::new();
1618 let mut common_prefixes = Vec::new();
1619 let page = view
1620 .items
1621 .get(offset..end)
1622 .ok_or(ObjectsError::Invalid("invalid continuation"))?;
1623 for item in page {
1624 match item {
1625 ListingItem::Entry(value) => entries.push(value.as_ref().clone()),
1626 ListingItem::Prefix(value) => common_prefixes.push(value.clone()),
1627 }
1628 }
1629 Ok(ProviderListPage {
1630 entries,
1631 common_prefixes,
1632 continuation: (end < view.items.len()).then(|| format!("{view_id}:{end}")),
1633 })
1634 }
1635
1636 async fn snapshot(
1637 &self,
1638 bucket: wire::BucketRef,
1639 idempotency_key: Option<String>,
1640 ) -> Result<wire::Snapshot, ObjectsError> {
1641 let fields = Self::reference_fields(&bucket);
1642 let fingerprint = Self::fingerprint("snapshot", &fields);
1643 let mut state = self.state.lock().await;
1644 Self::execute(
1645 &mut state,
1646 idempotency_key.as_deref(),
1647 fingerprint,
1648 |outcome| match outcome {
1649 MutationOutcome::Snapshot(value) => Some(value.clone()),
1650 _ => None,
1651 },
1652 MutationOutcome::Snapshot,
1653 |state| {
1654 let source = Self::bucket(state, &bucket)?.clone();
1655 state.retain_bucket(&source)?;
1656 let snapshot_id = Self::next(state, "snapshot")?;
1657 let reference = wire::SnapshotRef {
1658 snapshot_id: snapshot_id.clone(),
1659 source_bucket_id: bucket.bucket_id,
1660 };
1661 let created_at = Self::timestamp(state);
1662 state.snapshots.insert(
1663 snapshot_id,
1664 SnapshotState {
1665 reference: reference.clone(),
1666 bucket: source,
1667 },
1668 );
1669 Ok(wire::Snapshot {
1670 snapshot: Some(reference),
1671 created_at: Some(created_at),
1672 })
1673 },
1674 )
1675 }
1676
1677 async fn destroy_snapshot(
1678 &self,
1679 snapshot: wire::SnapshotRef,
1680 idempotency_key: Option<String>,
1681 ) -> Result<bool, ObjectsError> {
1682 let fingerprint = Self::fingerprint(
1683 "destroy-snapshot",
1684 &[
1685 snapshot.snapshot_id.as_bytes(),
1686 snapshot.source_bucket_id.as_bytes(),
1687 ],
1688 );
1689 let mut state = self.state.lock().await;
1690 Self::execute(
1691 &mut state,
1692 idempotency_key.as_deref(),
1693 fingerprint,
1694 |outcome| match outcome {
1695 MutationOutcome::Boolean(value) => Some(value.clone()),
1696 _ => None,
1697 },
1698 MutationOutcome::Boolean,
1699 |state| {
1700 let exists = state
1701 .snapshots
1702 .get(&snapshot.snapshot_id)
1703 .is_some_and(|value| value.reference == snapshot);
1704 if !exists {
1705 return Ok(false);
1706 }
1707 let removed = state
1708 .snapshots
1709 .remove(&snapshot.snapshot_id)
1710 .ok_or(ObjectsError::Unavailable)?;
1711 state.release_bucket(&removed.bucket)?;
1712 Ok(true)
1713 },
1714 )
1715 }
1716
1717 async fn fork(
1718 &self,
1719 source: ReadTarget,
1720 destination_name: String,
1721 idempotency_key: Option<String>,
1722 ) -> Result<wire::Bucket, ObjectsError> {
1723 let source_identity = match &source {
1724 ReadTarget::Bucket(value) => format!("bucket:{}:{}", value.bucket_id, value.name),
1725 ReadTarget::Snapshot(value) => {
1726 format!("snapshot:{}:{}", value.snapshot_id, value.source_bucket_id)
1727 }
1728 };
1729 let fingerprint = Self::fingerprint(
1730 "fork",
1731 &[source_identity.as_bytes(), destination_name.as_bytes()],
1732 );
1733 let mut state = self.state.lock().await;
1734 Self::execute(
1735 &mut state,
1736 idempotency_key.as_deref(),
1737 fingerprint,
1738 |outcome| match outcome {
1739 MutationOutcome::Bucket(value) => Some(value.clone()),
1740 _ => None,
1741 },
1742 MutationOutcome::Bucket,
1743 |state| {
1744 if state.names.contains_key(&destination_name) {
1745 return Err(ObjectsError::AlreadyExists);
1746 }
1747 let source = Self::target(state, &source)?;
1748 state.retain_bucket(&source)?;
1749 let bucket_id = Self::next(state, "bucket")?;
1750 let reference = wire::BucketRef {
1751 bucket_id: bucket_id.clone(),
1752 name: destination_name.clone(),
1753 };
1754 let created_at = Self::timestamp(state);
1755 state.names.insert(destination_name, bucket_id.clone());
1756 state.buckets.insert(
1757 bucket_id,
1758 BucketState {
1759 reference: reference.clone(),
1760 created_at,
1761 objects: source.objects,
1762 },
1763 );
1764 Ok(wire::Bucket {
1765 bucket: Some(reference),
1766 created_at: Some(created_at),
1767 })
1768 },
1769 )
1770 }
1771
1772 async fn create_multipart(
1773 &self,
1774 bucket: wire::BucketRef,
1775 object_key: String,
1776 metadata: wire::ObjectMetadata,
1777 condition: Option<Condition>,
1778 idempotency_key: Option<String>,
1779 ) -> Result<wire::MultipartUpload, ObjectsError> {
1780 Self::validate_key(&object_key)?;
1781 Self::validate_metadata(&metadata)?;
1782 let bucket_fields = Self::reference_fields(&bucket);
1783 let metadata_bytes = Self::metadata_bytes(&metadata);
1784 let condition_bytes = Self::condition_bytes(&condition);
1785 let fingerprint = Self::fingerprint(
1786 "create-multipart",
1787 &[
1788 bucket_fields[0],
1789 bucket_fields[1],
1790 object_key.as_bytes(),
1791 &metadata_bytes,
1792 &condition_bytes,
1793 ],
1794 );
1795 let mut state = self.state.lock().await;
1796 Self::execute(
1797 &mut state,
1798 idempotency_key.as_deref(),
1799 fingerprint,
1800 |outcome| match outcome {
1801 MutationOutcome::Multipart(value) => Some(value.clone()),
1802 _ => None,
1803 },
1804 MutationOutcome::Multipart,
1805 |state| {
1806 Self::bucket(state, &bucket)?;
1807 let upload_id = Self::next(state, "upload")?;
1808 state.multiparts.insert(
1809 upload_id.clone(),
1810 MultipartState {
1811 bucket,
1812 object_key,
1813 metadata,
1814 condition,
1815 parts: BTreeMap::new(),
1816 },
1817 );
1818 Ok(wire::MultipartUpload { upload_id })
1819 },
1820 )
1821 }
1822
1823 async fn upload_part(
1824 &self,
1825 bucket: wire::BucketRef,
1826 object_key: String,
1827 upload_id: String,
1828 part_number: u32,
1829 body: bytes::Bytes,
1830 idempotency_key: Option<String>,
1831 ) -> Result<wire::UploadedPart, ObjectsError> {
1832 let digest = *blake3::hash(&body).as_bytes();
1833 self.upload_part_stored(StoredPartRequest {
1834 bucket,
1835 object_key,
1836 upload_id,
1837 part_number,
1838 body: StoredBody::memory(body),
1839 body_digest: digest,
1840 idempotency_key,
1841 })
1842 .await
1843 }
1844
1845 async fn list_parts(
1846 &self,
1847 bucket: wire::BucketRef,
1848 object_key: String,
1849 upload_id: String,
1850 ) -> Result<Vec<wire::UploadedPart>, ObjectsError> {
1851 let state = self.state.lock().await;
1852 let upload = state
1853 .multiparts
1854 .get(&upload_id)
1855 .filter(|upload| upload.bucket == bucket && upload.object_key == object_key)
1856 .ok_or(ObjectsError::NotFound)?;
1857 Ok(upload
1858 .parts
1859 .values()
1860 .map(|(part, _)| part.clone())
1861 .collect())
1862 }
1863
1864 async fn complete_multipart(
1865 &self,
1866 bucket: wire::BucketRef,
1867 object_key: String,
1868 upload_id: String,
1869 parts: Vec<wire::UploadedPart>,
1870 idempotency_key: Option<String>,
1871 ) -> Result<wire::ObjectVersion, ObjectsError> {
1872 if parts.is_empty() || parts.len() > limits::MULTIPART_PARTS as usize {
1873 return Err(ObjectsError::Invalid("invalid multipart completion"));
1874 }
1875 let bucket_fields = Self::reference_fields(&bucket);
1876 let mut part_bytes = Vec::new();
1877 for part in &parts {
1878 part_bytes.extend_from_slice(&part.part_number.to_le_bytes());
1879 part_bytes.extend_from_slice(&(part.etag.len() as u64).to_le_bytes());
1880 part_bytes.extend_from_slice(part.etag.as_bytes());
1881 part_bytes.extend_from_slice(&part.size.to_le_bytes());
1882 }
1883 let fingerprint = Self::fingerprint(
1884 "complete-multipart",
1885 &[
1886 bucket_fields[0],
1887 bucket_fields[1],
1888 object_key.as_bytes(),
1889 upload_id.as_bytes(),
1890 &part_bytes,
1891 ],
1892 );
1893 let mut state = self.state.lock().await;
1894 Self::execute(
1895 &mut state,
1896 idempotency_key.as_deref(),
1897 fingerprint,
1898 |outcome| match outcome {
1899 MutationOutcome::Version(value) => Some(value.clone()),
1900 _ => None,
1901 },
1902 MutationOutcome::Version,
1903 |state| {
1904 let upload = state
1905 .multiparts
1906 .remove(&upload_id)
1907 .filter(|upload| upload.bucket == bucket && upload.object_key == object_key)
1908 .ok_or(ObjectsError::NotFound)?;
1909 let exact = upload.parts.values().map(|(part, _)| part).eq(parts.iter());
1910 let sizes_valid = parts.iter().enumerate().all(|(index, part)| {
1911 index + 1 == parts.len() || part.size >= limits::MIN_MULTIPART_PART_BYTES
1912 });
1913 if !exact || !sizes_valid {
1914 state.multiparts.insert(upload_id, upload);
1915 return Err(ObjectsError::Invalid("multipart receipts do not match"));
1916 }
1917 let current = Self::bucket(state, &bucket)?
1918 .objects
1919 .get(&object_key)
1920 .and_then(|history| history.last())
1921 .cloned();
1922 if !Self::condition(current.as_ref(), &upload.condition) {
1923 state.multiparts.insert(upload_id, upload);
1924 return Err(ObjectsError::PreconditionFailed);
1925 }
1926 let body_length =
1927 match upload.parts.values().try_fold(0usize, |total, (_, body)| {
1928 total.checked_add(body.len()).ok_or(ObjectsError::Capacity)
1929 }) {
1930 Ok(body_length) => body_length,
1931 Err(error) => {
1932 state.multiparts.insert(upload_id, upload);
1933 return Err(error);
1934 }
1935 };
1936 if body_length > state.maximum_object_bytes {
1937 state.multiparts.insert(upload_id, upload);
1938 return Err(ObjectsError::Capacity);
1939 }
1940 let bodies = upload
1941 .parts
1942 .into_values()
1943 .map(|(_, body)| body)
1944 .collect::<Vec<_>>();
1945 let mut hasher = blake3::Hasher::new();
1946 for body in &bodies {
1947 body.update_hash(&mut hasher)?;
1948 }
1949 let body_digest = *hasher.finalize().as_bytes();
1950 let descriptor =
1951 Self::descriptor(state, &body_digest, body_length, upload.metadata, false)?;
1952 Self::bucket_mut(state, &bucket)?
1953 .objects
1954 .entry(object_key)
1955 .or_default()
1956 .push(Version {
1957 descriptor: descriptor.clone(),
1958 body: Some(StoredBody::Composite {
1959 parts: bodies.into(),
1960 length: body_length,
1961 }),
1962 });
1963 Ok(descriptor)
1964 },
1965 )
1966 }
1967
1968 async fn abort_multipart(
1969 &self,
1970 bucket: wire::BucketRef,
1971 object_key: String,
1972 upload_id: String,
1973 idempotency_key: Option<String>,
1974 ) -> Result<bool, ObjectsError> {
1975 let bucket_fields = Self::reference_fields(&bucket);
1976 let fingerprint = Self::fingerprint(
1977 "abort-multipart",
1978 &[
1979 bucket_fields[0],
1980 bucket_fields[1],
1981 object_key.as_bytes(),
1982 upload_id.as_bytes(),
1983 ],
1984 );
1985 let mut state = self.state.lock().await;
1986 Self::execute(
1987 &mut state,
1988 idempotency_key.as_deref(),
1989 fingerprint,
1990 |outcome| match outcome {
1991 MutationOutcome::Boolean(value) => Some(value.clone()),
1992 _ => None,
1993 },
1994 MutationOutcome::Boolean,
1995 |state| {
1996 let matches = state.multiparts.get(&upload_id).is_some_and(|upload| {
1997 upload.bucket == bucket && upload.object_key == object_key
1998 });
1999 if !matches {
2000 return Ok(false);
2001 }
2002 if let Some(upload) = state.multiparts.remove(&upload_id) {
2003 for (_, body) in upload.parts.values() {
2004 state.release_body(body)?;
2005 }
2006 }
2007 Ok(true)
2008 },
2009 )
2010 }
2011}
2012
2013#[cfg(test)]
2014mod tests {
2015 use super::*;
2016
2017 fn metadata() -> wire::ObjectMetadata {
2018 wire::ObjectMetadata {
2019 content_type: "text/plain".into(),
2020 ..Default::default()
2021 }
2022 }
2023
2024 async fn put(
2025 store: &MemoryObjects,
2026 bucket: &wire::BucketRef,
2027 object_key: &str,
2028 body: &[u8],
2029 condition: Option<Condition>,
2030 ) -> wire::ObjectVersion {
2031 store
2032 .put(PutRequest {
2033 bucket: bucket.clone(),
2034 object_key: object_key.into(),
2035 body: bytes::Bytes::copy_from_slice(body),
2036 metadata: metadata(),
2037 condition,
2038 idempotency_key: None,
2039 })
2040 .await
2041 .unwrap_or_else(|_| unreachable!())
2042 }
2043
2044 #[tokio::test]
2045 async fn synchronous_bucket_composition_is_exact_and_capacity_bounded() {
2046 assert!(matches!(
2047 MemoryObjects::new(0),
2048 Err(ObjectsError::Invalid("memory byte limit must be positive"))
2049 ));
2050 assert!(MemoryObjects::with_bucket("INVALID", 4).is_err());
2051 assert!(MemoryObjects::with_bucket_limits("embedded", 5, 4).is_err());
2052
2053 let (independent, independent_bucket) =
2054 MemoryObjects::with_bucket_limits("independent", 3, 6)
2055 .unwrap_or_else(|_| unreachable!());
2056 put(&independent, &independent_bucket, "first", b"one", None).await;
2057 put(&independent, &independent_bucket, "second", b"two", None).await;
2058 assert_eq!(
2059 independent
2060 .put(PutRequest {
2061 bucket: independent_bucket.clone(),
2062 object_key: "aggregate-overflow".into(),
2063 body: bytes::Bytes::from_static(b"x"),
2064 metadata: metadata(),
2065 condition: None,
2066 idempotency_key: None,
2067 })
2068 .await,
2069 Err(ObjectsError::Capacity)
2070 );
2071 let (per_object, per_object_bucket) = MemoryObjects::with_bucket_limits("per-object", 3, 6)
2072 .unwrap_or_else(|_| unreachable!());
2073 assert_eq!(
2074 per_object
2075 .put(PutRequest {
2076 bucket: per_object_bucket,
2077 object_key: "too-large".into(),
2078 body: bytes::Bytes::from_static(b"four"),
2079 metadata: metadata(),
2080 condition: None,
2081 idempotency_key: None,
2082 })
2083 .await,
2084 Err(ObjectsError::Capacity)
2085 );
2086
2087 let (store, bucket) =
2088 MemoryObjects::with_bucket("embedded", 4).unwrap_or_else(|_| unreachable!());
2089 assert_eq!(
2090 store
2091 .head_bucket(&bucket)
2092 .await
2093 .unwrap_or_else(|_| unreachable!())
2094 .bucket,
2095 Some(bucket.clone())
2096 );
2097 put(&store, &bucket, "full", b"four", None).await;
2098 assert_eq!(
2099 store
2100 .put(PutRequest {
2101 bucket,
2102 object_key: "overflow".into(),
2103 body: bytes::Bytes::from_static(b"x"),
2104 metadata: metadata(),
2105 condition: None,
2106 idempotency_key: None,
2107 })
2108 .await,
2109 Err(ObjectsError::Capacity)
2110 );
2111 }
2112
2113 #[tokio::test]
2114 async fn memory_provider_passes_the_public_conformance_suite() -> Result<(), ObjectsError> {
2115 crate::conformance::verify(&MemoryObjects::default(), "conformance").await
2116 }
2117
2118 #[tokio::test]
2119 async fn shared_snapshot_and_fork_bodies_retain_exact_capacity() {
2120 let store = MemoryObjects::new(3).unwrap_or_else(|_| unreachable!());
2121 let source = store
2122 .create_bucket("source-bucket".into(), Some("create-source".into()))
2123 .await
2124 .unwrap_or_else(|_| unreachable!())
2125 .bucket
2126 .unwrap_or_else(|| unreachable!());
2127 let version = put(&store, &source, "shared", b"abc", None).await;
2128 let version_id = version.version_id.clone();
2129 let snapshot = store
2130 .snapshot(source.clone(), Some("snapshot".into()))
2131 .await
2132 .unwrap_or_else(|_| unreachable!())
2133 .snapshot
2134 .unwrap_or_else(|| unreachable!());
2135 assert!(
2136 store
2137 .delete(
2138 source.clone(),
2139 "shared".into(),
2140 Some(version.version_id),
2141 None,
2142 Some("delete-source".into()),
2143 )
2144 .await
2145 .is_ok()
2146 );
2147 assert!(matches!(
2148 store
2149 .put(PutRequest {
2150 bucket: source.clone(),
2151 object_key: "replacement".into(),
2152 body: bytes::Bytes::from_static(b"def"),
2153 metadata: metadata(),
2154 condition: None,
2155 idempotency_key: None,
2156 })
2157 .await,
2158 Err(ObjectsError::Capacity)
2159 ));
2160 let fork = store
2161 .fork(
2162 ReadTarget::Snapshot(snapshot.clone()),
2163 "fork-bucket".into(),
2164 Some("fork".into()),
2165 )
2166 .await
2167 .unwrap_or_else(|_| unreachable!())
2168 .bucket
2169 .unwrap_or_else(|| unreachable!());
2170 assert!(
2171 store
2172 .destroy_snapshot(snapshot, Some("destroy-snapshot".into()))
2173 .await
2174 .unwrap_or(false)
2175 );
2176 assert!(matches!(
2177 store
2178 .put(PutRequest {
2179 bucket: source.clone(),
2180 object_key: "replacement".into(),
2181 body: bytes::Bytes::from_static(b"def"),
2182 metadata: metadata(),
2183 condition: None,
2184 idempotency_key: None,
2185 })
2186 .await,
2187 Err(ObjectsError::Capacity)
2188 ));
2189 assert!(
2190 store
2191 .delete(
2192 fork,
2193 "shared".into(),
2194 Some(version_id),
2195 None,
2196 Some("delete-fork".into()),
2197 )
2198 .await
2199 .is_ok()
2200 );
2201 assert!(
2202 store
2203 .put(PutRequest {
2204 bucket: source,
2205 object_key: "replacement".into(),
2206 body: bytes::Bytes::from_static(b"def"),
2207 metadata: metadata(),
2208 condition: None,
2209 idempotency_key: None,
2210 })
2211 .await
2212 .is_ok()
2213 );
2214 }
2215}