Skip to main content

coven_database/
database_provider_access.rs

1//! Atomic provider intentions and request confirmations.
2use crate::provider_access::{adopt, finish, read, retake};
3use crate::{
4    DbError, NewOperation, OperationId, PendingValue, ProviderAccess, ProviderAccessChange,
5    ProviderAccessRequest,
6};
7use coven_crypto::SecretBytes;
8
9impl crate::Database {
10    /// Read confirmed sharing and its unfinished request from one committed row.
11    pub async fn provider_access(
12        &self,
13        account: String,
14    ) -> Result<Option<ProviderAccess>, DbError> {
15        self.call(move |inner| inner.access.with_writer(|db| read(db, &account))).await
16    }
17
18    /// Record a new invitation and its account intention atomically.
19    pub async fn start_operation_with_access<P: PendingValue>(
20        &self,
21        operation: NewOperation,
22        changes: Vec<ProviderAccessChange>,
23    ) -> Result<OperationId, DbError> {
24        self.call(move |inner| {
25            inner.access.with_writer(|db| {
26                db.transaction(|db| {
27                    let id = crate::operation::insert(db, &operation)?;
28                    adopt::<P>(db, &changes)?;
29                    Ok(id)
30                })
31            })
32        })
33        .await
34    }
35
36    /// Advance an invitation and its account intention atomically, ending
37    /// the operation it started for the step it leaves, `child`, with its
38    /// outcome, in the same transaction (§13.2).
39    pub async fn advance_operation_with_access<P: PendingValue>(
40        &self,
41        update: crate::OperationUpdate,
42        changes: Vec<ProviderAccessChange>,
43        child: Option<(OperationId, crate::OperationOutcome)>,
44    ) -> Result<(), DbError> {
45        self.call(move |inner| {
46            inner.access.with_writer(|db| {
47                db.transaction(|db| {
48                    crate::operation::advance(db, &update)?;
49                    if let Some((child, outcome)) = &child {
50                        crate::operation::finish_with_outcome(db, *child, outcome)?;
51                    }
52                    adopt::<P>(db, &changes)
53                })
54            })
55        })
56        .await
57    }
58
59    /// Forget each account's confirmed grant and queue a request for its
60    /// intention, atomically (`provider_access::retake`).
61    pub async fn retake_provider_access<P: PendingValue>(
62        &self,
63        changes: Vec<ProviderAccessChange>,
64    ) -> Result<(), DbError> {
65        self.call(move |inner| {
66            inner.access.with_writer(|db| db.transaction(|db| retake::<P>(db, &changes)))
67        })
68        .await
69    }
70
71    /// Fix the request's target before calling storage. A failed or interrupted
72    /// request has unknown provider state, so retry must confirm the target again.
73    pub async fn start_provider_request<P: PendingValue>(
74        &self,
75        account: String,
76        id: OperationId,
77    ) -> Result<bool, DbError> {
78        self.call(move |inner| {
79            inner.access.with_writer(|db| {
80                db.transaction(|db| {
81                    let state = read(db, &account)?.ok_or(DbError::OperationChanged(id))?;
82                    if state.request.as_ref().map(|r| r.operation) != Some(id) {
83                        return Err(DbError::OperationChanged(id));
84                    }
85                    let record =
86                        crate::operation::find(db, id)?.ok_or(DbError::OperationChanged(id))?;
87                    if record.last_step == crate::provider_access::REQUEST_FIXED_STEP {
88                        return Err(DbError::OperationChanged(id));
89                    }
90                    crate::provider_access::fix_target(db, &account)?;
91                    crate::operation::set_step(db, id, crate::provider_access::REQUEST_FIXED_STEP)?;
92                    crate::retry::clear(db, crate::RetrySubject::Operation(id))?;
93                    crate::pending::save(db, &P::provider_access(id, account, state.intended))?;
94                    Ok(state.intended)
95                })
96            })
97        })
98        .await
99    }
100
101    /// Record a revocation's reply that listed grants left in place: the
102    /// operation `id` moves from its fixed request to a reported failure,
103    /// recording `data`, with `pending` as its record. An entry that made
104    /// the account intended again while the request ran makes the reply
105    /// obsolete: the opposite request is queued on the same pending subject
106    /// instead, as a confirmation's is, and this returns false (§20.9).
107    pub async fn fail_provider_request<P: PendingValue>(
108        &self,
109        account: String,
110        id: OperationId,
111        data: Vec<u8>,
112        pending: P,
113    ) -> Result<bool, DbError> {
114        let update = crate::OperationUpdate {
115            id,
116            previous: crate::provider_access::REQUEST_FIXED_STEP,
117            last_step: crate::provider_access::REQUEST_FAILED_STEP,
118            data,
119        };
120        self.call(move |inner| {
121            inner.access.with_writer(|db| {
122                db.transaction(|db| {
123                    let state = read(db, &account)?.ok_or(DbError::OperationChanged(update.id))?;
124                    let request = state.request.ok_or(DbError::OperationChanged(update.id))?;
125                    if request.operation != update.id || pending.operation() != Some(update.id) {
126                        return Err(DbError::Invariant {
127                            what: "a provider request's condition names its own operation",
128                        });
129                    }
130                    let target = request.target.ok_or(DbError::OperationChanged(update.id))?;
131                    if state.intended != target {
132                        crate::provider_access::requeue(db, &account, update.id)?;
133                        crate::pending::save(
134                            db,
135                            &P::provider_access(update.id, account, state.intended),
136                        )?;
137                        return Ok(false);
138                    }
139                    crate::operation::advance(db, &update)?;
140                    crate::pending::save(db, &pending)?;
141                    Ok(true)
142                })
143            })
144        })
145        .await
146    }
147
148    /// Advance an invite whose grant the provider refused for good, and end
149    /// the account's grant request `grant` as not granted, in one commit: the
150    /// refusal leaves nothing shared, and retrying the grant would be refused
151    /// the same way. The account's intention is the invite's end's to change
152    /// (§13.2). A request that is not `grant`, fixed on granting, fails with
153    /// `OperationChanged`.
154    pub async fn advance_operation_refusing_grant(
155        &self,
156        update: crate::OperationUpdate,
157        account: String,
158        grant: OperationId,
159    ) -> Result<(), DbError> {
160        self.call(move |inner| {
161            inner.access.with_writer(|db| {
162                db.transaction(|db| {
163                    crate::operation::advance(db, &update)?;
164                    let state = read(db, &account)?.ok_or(DbError::OperationChanged(grant))?;
165                    if state.request
166                        != Some(ProviderAccessRequest { operation: grant, target: Some(true) })
167                    {
168                        return Err(DbError::OperationChanged(grant));
169                    }
170                    crate::provider_access::confirm(db, &account, false, None)?;
171                    finish(db, &account, grant)
172                })
173            })
174        })
175        .await
176    }
177
178    /// Confirm a fixed request and either finish it or queue the opposite target
179    /// on the same pending subject, in one commit.
180    pub async fn complete_provider_request<P: PendingValue>(
181        &self,
182        account: String,
183        id: OperationId,
184        target: bool,
185        invitation: Option<SecretBytes>,
186    ) -> Result<bool, DbError> {
187        self.call(move |inner| {
188            inner.access.with_writer(|db| {
189                db.transaction(|db| {
190                    let state = read(db, &account)?.ok_or(DbError::OperationChanged(id))?;
191                    if state.request
192                        != Some(ProviderAccessRequest { operation: id, target: Some(target) })
193                    {
194                        return Err(DbError::OperationChanged(id));
195                    }
196                    crate::provider_access::confirm(db, &account, target, invitation.as_ref())?;
197                    if state.intended == target {
198                        finish(db, &account, id)?;
199                        Ok(true)
200                    } else {
201                        crate::provider_access::requeue(db, &account, id)?;
202                        crate::pending::save(db, &P::provider_access(id, account, state.intended))?;
203                        Ok(false)
204                    }
205                })
206            })
207        })
208        .await
209    }
210}
211
212#[cfg(test)]
213#[path = "database_provider_access_tests.rs"]
214mod tests;