coven_storage/
provider_check.rs1use crate::{
2 ByteRange, ObjectPath, ObjectPrefix, Storage, StorageCheck, StorageCheckError, StorageError,
3 StorageFailure,
4};
5
6pub async fn check_provider(
16 storage: &crate::StorageConnection<impl crate::ProviderOps + ?Sized>,
17 path: &ObjectPath,
18 encrypted_bytes: &[u8],
19) -> Result<(), StorageCheckError> {
20 let end = encrypted_bytes
21 .len()
22 .checked_sub(1)
23 .ok_or(StorageError::Failure(StorageFailure::InvalidRange))?;
24 let range = ByteRange::new(1, end as u64)?;
25 let created = storage.create(path, encrypted_bytes).await;
26 if created.as_ref().is_err_and(|error| error.failure() == StorageFailure::AlreadyExists) {
27 return created.map(drop).map_err(|source| StorageCheckError::ProviderCheck {
28 check: StorageCheck::Create,
29 source,
30 });
31 }
32 let mut check = StorageCheck::Create;
33 let result = async {
34 created?;
35 check = StorageCheck::CreateOnce;
36 match storage.create(path, encrypted_bytes).await {
37 Err(error) if error.own_failure() == Some(StorageFailure::AlreadyExists) => {}
38 Err(error) => return Err(error),
39 Ok(_) => {
40 return Err(
41 StorageFailure::Protocol.with_source("provider accepted a second create")
42 )
43 }
44 }
45 check = StorageCheck::Read;
46 if storage.read(path).await? != encrypted_bytes {
47 return Err(StorageFailure::Protocol.with_source("whole read disagrees with upload"));
48 }
49 check = StorageCheck::ReadRange;
50 if storage.read_range(path, range).await? != encrypted_bytes[1..end] {
51 return Err(StorageFailure::Protocol.with_source("range read disagrees with upload"));
52 }
53 check = StorageCheck::ConditionalReplace;
54 let previous = storage.status(path).await?.ok_or(StorageFailure::NotFound)?;
55 let replacement = encrypted_bytes.iter().map(|byte| byte ^ 0x80).collect::<Vec<_>>();
56 storage.replace_object(&previous, &replacement).await?;
57 match storage.replace_object(&previous, encrypted_bytes).await {
58 Err(error) if error.failure() == StorageFailure::Conflict => {}
59 Err(error) => return Err(error),
60 Ok(()) => {
61 return Err(
62 StorageFailure::Protocol.with_source("provider accepted a stale replacement")
63 )
64 }
65 }
66 if storage.read(path).await? != replacement {
67 return Err(StorageFailure::Protocol
68 .with_source("conditional replacement changed the wrong bytes"));
69 }
70 check = StorageCheck::List;
71 if storage
72 .list(&ObjectPrefix::all(path.store()))
73 .await?
74 .iter()
75 .any(|object| &object.path == path && object.size != encrypted_bytes.len() as u64)
76 {
77 return Err(
78 StorageFailure::Protocol.with_source("test object has wrong size in listing")
79 );
80 }
81 Ok(())
82 }
83 .await;
84 let cleanup = async {
85 storage.delete(path).await?;
86 match storage.read(path).await {
87 Err(error) if error.failure() == StorageFailure::NotFound => Ok(()),
88 Err(error) => Err(error),
89 Ok(_) => {
90 Err(StorageFailure::Protocol.with_source("deleted test object is still readable"))
91 }
92 }
93 }
94 .await;
95 if result.is_ok() && cleanup.is_err() {
97 check = StorageCheck::Delete;
98 }
99 crate::transfer::with_cleanup(result, cleanup)
100 .map_err(|source| StorageCheckError::ProviderCheck { check, source })
101}
102
103#[cfg(test)]
104#[path = "provider_check_tests.rs"]
105mod tests;