cloud_sdk/authentication/
attempt.rs1use core::sync::atomic::{AtomicU32, Ordering};
2
3const REJECTED: u32 = 1;
4const GENERATION_SHIFT: u32 = 1;
5const MAX_GENERATION: u32 = u32::MAX >> GENERATION_SHIFT;
6
7#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub struct CredentialAttemptGeneration(u32);
10
11impl CredentialAttemptGeneration {
12 pub const INITIAL: Self = Self(1);
14
15 #[must_use]
17 pub const fn get(self) -> u32 {
18 self.0
19 }
20}
21
22#[derive(Clone, Copy)]
33pub struct CredentialAttempt<'a> {
34 owner: &'a SharedCredentialAttemptState,
35 generation: CredentialAttemptGeneration,
36}
37
38impl CredentialAttempt<'_> {
39 #[must_use]
41 pub const fn generation(self) -> CredentialAttemptGeneration {
42 self.generation
43 }
44}
45
46impl core::fmt::Debug for CredentialAttempt<'_> {
47 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
48 formatter
49 .debug_struct("CredentialAttempt")
50 .field("owner", &"[bound]")
51 .field("generation", &self.generation)
52 .finish()
53 }
54}
55
56impl PartialEq for CredentialAttempt<'_> {
57 fn eq(&self, other: &Self) -> bool {
58 core::ptr::eq(self.owner, other.owner) && self.generation == other.generation
59 }
60}
61
62impl Eq for CredentialAttempt<'_> {}
63
64#[derive(Debug)]
69pub struct CredentialReconfirmation {
70 _private: (),
71}
72
73impl CredentialReconfirmation {
74 #[must_use]
76 pub const fn acknowledge_same_credentials() -> Self {
77 Self { _private: () }
78 }
79}
80
81#[derive(Clone, Copy, Debug, Eq, PartialEq)]
83pub enum CredentialAttemptStatus {
84 Open,
86 Rejected,
88}
89
90#[derive(Clone, Copy, Debug, Eq, PartialEq)]
92pub enum CredentialAttemptError {
93 ForeignState,
95 GenerationRejected,
97 StaleGeneration,
99 ReconfirmationNotRequired,
101 GenerationExhausted,
103}
104
105impl_static_error!(CredentialAttemptError,
106 Self::ForeignState => "credential attempt belongs to another state",
107 Self::GenerationRejected => "credential attempt generation was rejected",
108 Self::StaleGeneration => "credential attempt generation is stale",
109 Self::ReconfirmationNotRequired => "credential attempt generation is still open",
110 Self::GenerationExhausted => "credential attempt generation is exhausted",
111);
112
113pub struct SharedCredentialAttemptState {
120 packed: AtomicU32,
121}
122
123impl SharedCredentialAttemptState {
124 #[must_use]
126 pub const fn new() -> Self {
127 Self {
128 packed: AtomicU32::new(pack(CredentialAttemptGeneration::INITIAL, false)),
129 }
130 }
131
132 #[must_use]
134 pub fn observe(&self) -> (CredentialAttemptGeneration, CredentialAttemptStatus) {
135 unpack(self.packed.load(Ordering::Acquire))
136 }
137
138 pub fn begin(&self) -> Result<CredentialAttempt<'_>, CredentialAttemptError> {
140 let (generation, status) = self.observe();
141 if status == CredentialAttemptStatus::Rejected {
142 return Err(CredentialAttemptError::GenerationRejected);
143 }
144 Ok(CredentialAttempt {
145 owner: self,
146 generation,
147 })
148 }
149
150 pub fn validate(&self, attempt: CredentialAttempt<'_>) -> Result<(), CredentialAttemptError> {
152 self.validate_owner(attempt)?;
153 self.validate_generation(attempt.generation)
154 }
155
156 pub(crate) fn validate_generation(
157 &self,
158 expected: CredentialAttemptGeneration,
159 ) -> Result<(), CredentialAttemptError> {
160 let (generation, status) = self.observe();
161 if generation != expected {
162 return Err(CredentialAttemptError::StaleGeneration);
163 }
164 if status == CredentialAttemptStatus::Rejected {
165 return Err(CredentialAttemptError::GenerationRejected);
166 }
167 Ok(())
168 }
169
170 pub fn reject(&self, attempt: CredentialAttempt<'_>) -> Result<(), CredentialAttemptError> {
175 self.validate_owner(attempt)?;
176 self.reject_generation(attempt.generation)
177 }
178
179 pub(crate) fn reject_generation(
180 &self,
181 expected: CredentialAttemptGeneration,
182 ) -> Result<(), CredentialAttemptError> {
183 loop {
184 let current = self.packed.load(Ordering::Acquire);
185 let (generation, status) = unpack(current);
186 if generation != expected {
187 return Err(CredentialAttemptError::StaleGeneration);
188 }
189 if status == CredentialAttemptStatus::Rejected {
190 return Ok(());
191 }
192 let next = pack(generation, true);
193 if self
194 .packed
195 .compare_exchange(current, next, Ordering::AcqRel, Ordering::Acquire)
196 .is_ok()
197 {
198 return Ok(());
199 }
200 }
201 }
202
203 pub fn replace(
205 &self,
206 expected: CredentialAttemptGeneration,
207 ) -> Result<CredentialAttemptGeneration, CredentialAttemptError> {
208 self.advance(expected)
209 }
210
211 pub fn reconfirm(
213 &self,
214 expected: CredentialAttemptGeneration,
215 _acknowledgement: CredentialReconfirmation,
216 ) -> Result<CredentialAttemptGeneration, CredentialAttemptError> {
217 loop {
218 let current = self.packed.load(Ordering::Acquire);
219 let (generation, status) = unpack(current);
220 if generation != expected {
221 return Err(CredentialAttemptError::StaleGeneration);
222 }
223 if status != CredentialAttemptStatus::Rejected {
224 return Err(CredentialAttemptError::ReconfirmationNotRequired);
225 }
226 let next_generation = checked_next(generation)?;
227 let next = pack(next_generation, false);
228 if self
229 .packed
230 .compare_exchange(current, next, Ordering::AcqRel, Ordering::Acquire)
231 .is_ok()
232 {
233 return Ok(next_generation);
234 }
235 }
236 }
237
238 fn advance(
239 &self,
240 expected: CredentialAttemptGeneration,
241 ) -> Result<CredentialAttemptGeneration, CredentialAttemptError> {
242 loop {
243 let current = self.packed.load(Ordering::Acquire);
244 let (generation, _) = unpack(current);
245 if generation != expected {
246 return Err(CredentialAttemptError::StaleGeneration);
247 }
248 let next_generation = checked_next(generation)?;
249 let next = pack(next_generation, false);
250 if self
251 .packed
252 .compare_exchange(current, next, Ordering::AcqRel, Ordering::Acquire)
253 .is_ok()
254 {
255 return Ok(next_generation);
256 }
257 }
258 }
259
260 fn validate_owner(&self, attempt: CredentialAttempt<'_>) -> Result<(), CredentialAttemptError> {
261 if !core::ptr::eq(self, attempt.owner) {
262 return Err(CredentialAttemptError::ForeignState);
263 }
264 Ok(())
265 }
266
267 #[cfg(test)]
268 fn set_generation_for_test(&mut self, generation: u32, rejected: bool) {
269 *self.packed.get_mut() = pack(CredentialAttemptGeneration(generation), rejected);
270 }
271}
272
273fn checked_next(
274 generation: CredentialAttemptGeneration,
275) -> Result<CredentialAttemptGeneration, CredentialAttemptError> {
276 generation
277 .0
278 .checked_add(1)
279 .filter(|candidate| *candidate <= MAX_GENERATION)
280 .map(CredentialAttemptGeneration)
281 .ok_or(CredentialAttemptError::GenerationExhausted)
282}
283
284impl Default for SharedCredentialAttemptState {
285 fn default() -> Self {
286 Self::new()
287 }
288}
289
290impl core::fmt::Debug for SharedCredentialAttemptState {
291 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
292 let (generation, status) = self.observe();
293 formatter
294 .debug_struct("SharedCredentialAttemptState")
295 .field("generation", &generation)
296 .field("status", &status)
297 .finish()
298 }
299}
300
301const fn pack(generation: CredentialAttemptGeneration, rejected: bool) -> u32 {
302 (generation.0 << GENERATION_SHIFT) | (rejected as u32)
303}
304
305const fn unpack(value: u32) -> (CredentialAttemptGeneration, CredentialAttemptStatus) {
306 let generation = CredentialAttemptGeneration(value >> GENERATION_SHIFT);
307 let status = if value & REJECTED == 0 {
308 CredentialAttemptStatus::Open
309 } else {
310 CredentialAttemptStatus::Rejected
311 };
312 (generation, status)
313}
314
315#[cfg(test)]
316mod tests {
317 use super::{
318 CredentialAttemptError, CredentialAttemptGeneration, CredentialAttemptStatus,
319 CredentialReconfirmation, MAX_GENERATION, SharedCredentialAttemptState,
320 };
321
322 #[test]
323 fn rejection_closes_one_generation_until_replaced_or_reconfirmed() {
324 let state = SharedCredentialAttemptState::new();
325 let first = state
326 .begin()
327 .unwrap_or_else(|_| unreachable!("initial credential generation was closed"));
328 assert_eq!(first.generation(), CredentialAttemptGeneration::INITIAL);
329 assert_eq!(
330 state.reconfirm(
331 first.generation(),
332 CredentialReconfirmation::acknowledge_same_credentials(),
333 ),
334 Err(CredentialAttemptError::ReconfirmationNotRequired)
335 );
336 assert_eq!(state.reject(first), Ok(()));
337 assert_eq!(
338 state.validate(first),
339 Err(CredentialAttemptError::GenerationRejected)
340 );
341 assert_eq!(state.reject(first), Ok(()));
342 assert_eq!(
343 state.begin(),
344 Err(CredentialAttemptError::GenerationRejected)
345 );
346
347 let second = state
348 .reconfirm(
349 first.generation(),
350 CredentialReconfirmation::acknowledge_same_credentials(),
351 )
352 .unwrap_or_else(|_| unreachable!("explicit reconfirmation was rejected"));
353 assert_eq!(second.get(), 2);
354 assert!(state.begin().is_ok());
355
356 let third = state
357 .replace(second)
358 .unwrap_or_else(|_| unreachable!("replacement generation was rejected"));
359 assert_eq!(third.get(), 3);
360 assert!(state.begin().is_ok());
361 }
362
363 #[test]
364 fn stale_transitions_cannot_close_or_reopen_replacement_credentials() {
365 let state = SharedCredentialAttemptState::new();
366 let stale = state
367 .begin()
368 .unwrap_or_else(|_| unreachable!("initial credential generation was closed"));
369 let current = state
370 .replace(stale.generation())
371 .unwrap_or_else(|_| unreachable!("replacement generation was rejected"));
372 assert_eq!(
373 state.reject(stale),
374 Err(CredentialAttemptError::StaleGeneration)
375 );
376 assert_eq!(
377 state.validate(stale),
378 Err(CredentialAttemptError::StaleGeneration)
379 );
380 assert_eq!(
381 state.replace(stale.generation()),
382 Err(CredentialAttemptError::StaleGeneration)
383 );
384 assert_eq!(
385 state.reconfirm(
386 stale.generation(),
387 CredentialReconfirmation::acknowledge_same_credentials(),
388 ),
389 Err(CredentialAttemptError::StaleGeneration)
390 );
391 assert_eq!(state.observe(), (current, CredentialAttemptStatus::Open));
392 }
393
394 #[test]
395 fn foreign_attempts_never_validate_or_close_equal_generations() {
396 let owner_a = SharedCredentialAttemptState::new();
397 let owner_b = SharedCredentialAttemptState::new();
398 let foreign = owner_a
399 .begin()
400 .unwrap_or_else(|_| unreachable!("owner A generation was closed"));
401
402 assert_eq!(
403 owner_b.validate(foreign),
404 Err(CredentialAttemptError::ForeignState)
405 );
406 assert_eq!(
407 owner_b.reject(foreign),
408 Err(CredentialAttemptError::ForeignState)
409 );
410 assert_eq!(
411 owner_b.observe(),
412 (
413 CredentialAttemptGeneration::INITIAL,
414 CredentialAttemptStatus::Open
415 )
416 );
417
418 let generation_a = owner_a
419 .replace(CredentialAttemptGeneration::INITIAL)
420 .unwrap_or_else(|_| unreachable!("owner A replacement failed"));
421 let generation_b = owner_b
422 .replace(CredentialAttemptGeneration::INITIAL)
423 .unwrap_or_else(|_| unreachable!("owner B replacement failed"));
424 assert_eq!(generation_a, generation_b);
425 let foreign_replacement = owner_a
426 .begin()
427 .unwrap_or_else(|_| unreachable!("owner A replacement was closed"));
428 assert_eq!(
429 owner_b.reject(foreign_replacement),
430 Err(CredentialAttemptError::ForeignState)
431 );
432 assert_eq!(
433 owner_b.observe(),
434 (generation_b, CredentialAttemptStatus::Open)
435 );
436 }
437
438 #[test]
439 fn generation_exhaustion_fails_closed_without_wrapping() {
440 let mut state = SharedCredentialAttemptState::new();
441 state.set_generation_for_test(MAX_GENERATION, true);
442 let generation = state.observe().0;
443 assert_eq!(
444 state.replace(generation),
445 Err(CredentialAttemptError::GenerationExhausted)
446 );
447 assert_eq!(
448 state.reconfirm(
449 generation,
450 CredentialReconfirmation::acknowledge_same_credentials(),
451 ),
452 Err(CredentialAttemptError::GenerationExhausted)
453 );
454 assert_eq!(
455 state.observe(),
456 (generation, CredentialAttemptStatus::Rejected)
457 );
458 }
459}