Skip to main content

coven_storage/
provider_check.rs

1use crate::{
2    ByteRange, ObjectPath, ObjectPrefix, Storage, StorageCheck, StorageCheckError, StorageError,
3    StorageFailure,
4};
5
6/// Check a candidate provider before reserving or publishing setup state.
7/// The storage owner supplies a fresh path and sealed bytes: at least three,
8/// and within the single-request limit, because the conditional replacement
9/// it checks is one request.
10/// This calls the provider without committing credentials or local settings.
11/// Cleanup runs even after a lost create reply; a preexisting path is left intact.
12/// The caller must await cleanup even if its app waiter is cancelled.
13/// During setup the connection sends rate-limited requests again (ยง20.8),
14/// so a refusal fails the check only once the setup call's minute is spent.
15pub 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    // A check that passed and failed only to delete its object failed deleting.
96    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;