Skip to main content

cloud_sdk/authentication/
attempt_owned.rs

1use alloc::sync::Arc;
2
3use super::attempt::{
4    CredentialAttemptError, CredentialAttemptGeneration, CredentialAttemptStatus,
5    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    /// Closes the exact owned attempt generation after authentication rejection.
82    pub fn reject(&self, attempt: &OwnedCredentialAttempt) -> Result<(), CredentialAttemptError> {
83        self.validate_owner(attempt)?;
84        self.state.reject_generation(attempt.generation)
85    }
86
87    /// Opens a new generation after replacement credentials were admitted.
88    pub fn replace(
89        &self,
90        expected: CredentialAttemptGeneration,
91    ) -> Result<CredentialAttemptGeneration, CredentialAttemptError> {
92        self.state.replace(expected)
93    }
94
95    /// Opens a new generation after explicit unchanged-credential confirmation.
96    pub fn reconfirm(
97        &self,
98        expected: CredentialAttemptGeneration,
99        acknowledgement: CredentialReconfirmation,
100    ) -> Result<CredentialAttemptGeneration, CredentialAttemptError> {
101        self.state.reconfirm(expected, acknowledgement)
102    }
103
104    fn validate_owner(
105        &self,
106        attempt: &OwnedCredentialAttempt,
107    ) -> Result<(), CredentialAttemptError> {
108        if !Arc::ptr_eq(&self.state, &attempt.owner) {
109            return Err(CredentialAttemptError::ForeignState);
110        }
111        Ok(())
112    }
113}
114
115impl Default for OwnedCredentialAttemptState {
116    fn default() -> Self {
117        Self::new()
118    }
119}
120
121impl core::fmt::Debug for OwnedCredentialAttemptState {
122    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
123        let (generation, status) = self.observe();
124        formatter
125            .debug_struct("OwnedCredentialAttemptState")
126            .field("owner", &"[bound]")
127            .field("generation", &generation)
128            .field("status", &status)
129            .finish()
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::OwnedCredentialAttemptState;
136    use crate::authentication::{CredentialAttemptError, CredentialAttemptGeneration};
137
138    #[test]
139    fn owned_attempts_reject_foreign_and_stale_lineages() {
140        let owner_a = OwnedCredentialAttemptState::new();
141        let owner_b = OwnedCredentialAttemptState::new();
142        let attempt = owner_a
143            .begin()
144            .unwrap_or_else(|_| unreachable!("initial owned attempt was rejected"));
145
146        assert_eq!(
147            owner_b.validate(&attempt),
148            Err(CredentialAttemptError::ForeignState)
149        );
150        assert_eq!(
151            owner_b.reject(&attempt),
152            Err(CredentialAttemptError::ForeignState)
153        );
154        let replacement = owner_a
155            .replace(CredentialAttemptGeneration::INITIAL)
156            .unwrap_or_else(|_| unreachable!("owned replacement was rejected"));
157        assert_eq!(replacement.get(), 2);
158        assert_eq!(
159            owner_a.reject(&attempt),
160            Err(CredentialAttemptError::StaleGeneration)
161        );
162    }
163}