Skip to main content

cloud_sdk/authentication/
attempt_owned.rs

1use alloc::sync::Arc;
2
3use super::attempt::{
4    CredentialAttemptError, CredentialAttemptGeneration, CredentialAttemptStatus,
5    CredentialDispatchGuard, CredentialReconfirmation, SharedCredentialAttemptState,
6};
7
8/// Owned proof that one credential generation was open when execution began.
9///
10/// The proof retains opaque owner identity without exposing an address. It can
11/// cross task boundaries without borrowing the credential owner.
12pub struct OwnedCredentialAttempt {
13    owner: Arc<SharedCredentialAttemptState>,
14    generation: CredentialAttemptGeneration,
15}
16
17impl OwnedCredentialAttempt {
18    /// Returns the generation used by this attempt.
19    #[must_use]
20    pub const fn generation(&self) -> CredentialAttemptGeneration {
21        self.generation
22    }
23}
24
25impl core::fmt::Debug for OwnedCredentialAttempt {
26    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
27        formatter
28            .debug_struct("OwnedCredentialAttempt")
29            .field("owner", &"[bound]")
30            .field("generation", &self.generation)
31            .finish()
32    }
33}
34
35impl PartialEq for OwnedCredentialAttempt {
36    fn eq(&self, other: &Self) -> bool {
37        Arc::ptr_eq(&self.owner, &other.owner) && self.generation == other.generation
38    }
39}
40
41impl Eq for OwnedCredentialAttempt {}
42
43/// Allocation-backed credential lifecycle for attempts that cross task boundaries.
44///
45/// Creating the state allocates one shared lineage. Beginning an attempt only
46/// clones that lineage and performs no new allocation.
47pub struct OwnedCredentialAttemptState {
48    state: Arc<SharedCredentialAttemptState>,
49}
50
51impl OwnedCredentialAttemptState {
52    /// Creates one open initial credential generation and owned lineage.
53    #[must_use]
54    pub fn new() -> Self {
55        Self {
56            state: Arc::new(SharedCredentialAttemptState::new()),
57        }
58    }
59
60    /// Returns the current generation and status.
61    #[must_use]
62    pub fn observe(&self) -> (CredentialAttemptGeneration, CredentialAttemptStatus) {
63        self.state.observe()
64    }
65
66    /// Begins an owned attempt only when the current generation remains open.
67    pub fn begin(&self) -> Result<OwnedCredentialAttempt, CredentialAttemptError> {
68        let attempt = self.state.begin()?;
69        Ok(OwnedCredentialAttempt {
70            owner: Arc::clone(&self.state),
71            generation: attempt.generation(),
72        })
73    }
74
75    /// Revalidates owner identity and generation immediately before use.
76    pub fn validate(&self, attempt: &OwnedCredentialAttempt) -> Result<(), CredentialAttemptError> {
77        self.validate_owner(attempt)?;
78        self.state.validate_generation(attempt.generation)
79    }
80
81    /// Exclusively admits one in-flight dispatch for an owned attempt.
82    pub fn reserve_dispatch(
83        &self,
84        attempt: &OwnedCredentialAttempt,
85    ) -> Result<CredentialDispatchGuard<'_>, CredentialAttemptError> {
86        self.validate_owner(attempt)?;
87        self.state.reserve_generation(attempt.generation)
88    }
89
90    /// Closes the exact owned attempt generation after authentication rejection.
91    pub fn reject(&self, attempt: &OwnedCredentialAttempt) -> Result<(), CredentialAttemptError> {
92        self.validate_owner(attempt)?;
93        self.state.reject_generation(attempt.generation)
94    }
95
96    /// Opens a new generation after replacement credentials were admitted.
97    pub fn replace(
98        &self,
99        expected: CredentialAttemptGeneration,
100    ) -> Result<CredentialAttemptGeneration, CredentialAttemptError> {
101        self.state.replace(expected)
102    }
103
104    /// Opens a new generation after explicit unchanged-credential confirmation.
105    pub fn reconfirm(
106        &self,
107        expected: CredentialAttemptGeneration,
108        acknowledgement: CredentialReconfirmation,
109    ) -> Result<CredentialAttemptGeneration, CredentialAttemptError> {
110        self.state.reconfirm(expected, acknowledgement)
111    }
112
113    fn validate_owner(
114        &self,
115        attempt: &OwnedCredentialAttempt,
116    ) -> Result<(), CredentialAttemptError> {
117        if !Arc::ptr_eq(&self.state, &attempt.owner) {
118            return Err(CredentialAttemptError::ForeignState);
119        }
120        Ok(())
121    }
122}
123
124impl Default for OwnedCredentialAttemptState {
125    fn default() -> Self {
126        Self::new()
127    }
128}
129
130impl core::fmt::Debug for OwnedCredentialAttemptState {
131    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
132        let (generation, status) = self.observe();
133        formatter
134            .debug_struct("OwnedCredentialAttemptState")
135            .field("owner", &"[bound]")
136            .field("generation", &generation)
137            .field("status", &status)
138            .finish()
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::OwnedCredentialAttemptState;
145    use crate::authentication::{CredentialAttemptError, CredentialAttemptGeneration};
146
147    #[test]
148    fn owned_attempts_reject_foreign_and_stale_lineages() {
149        let owner_a = OwnedCredentialAttemptState::new();
150        let owner_b = OwnedCredentialAttemptState::new();
151        let attempt = owner_a
152            .begin()
153            .unwrap_or_else(|_| unreachable!("initial owned attempt was rejected"));
154
155        assert_eq!(
156            owner_b.validate(&attempt),
157            Err(CredentialAttemptError::ForeignState)
158        );
159        assert_eq!(
160            owner_b.reject(&attempt),
161            Err(CredentialAttemptError::ForeignState)
162        );
163        let replacement = owner_a
164            .replace(CredentialAttemptGeneration::INITIAL)
165            .unwrap_or_else(|_| unreachable!("owned replacement was rejected"));
166        assert_eq!(replacement.get(), 2);
167        assert_eq!(
168            owner_a.reject(&attempt),
169            Err(CredentialAttemptError::StaleGeneration)
170        );
171    }
172}