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