use core::sync::atomic::{AtomicU32, Ordering};
const REJECTED: u32 = 1;
const GENERATION_SHIFT: u32 = 1;
const MAX_GENERATION: u32 = u32::MAX >> GENERATION_SHIFT;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct CredentialAttemptGeneration(u32);
impl CredentialAttemptGeneration {
pub const INITIAL: Self = Self(1);
#[must_use]
pub const fn get(self) -> u32 {
self.0
}
}
#[derive(Clone, Copy)]
pub struct CredentialAttempt<'a> {
owner: &'a SharedCredentialAttemptState,
generation: CredentialAttemptGeneration,
}
impl CredentialAttempt<'_> {
#[must_use]
pub const fn generation(self) -> CredentialAttemptGeneration {
self.generation
}
}
impl core::fmt::Debug for CredentialAttempt<'_> {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.debug_struct("CredentialAttempt")
.field("owner", &"[bound]")
.field("generation", &self.generation)
.finish()
}
}
impl PartialEq for CredentialAttempt<'_> {
fn eq(&self, other: &Self) -> bool {
core::ptr::eq(self.owner, other.owner) && self.generation == other.generation
}
}
impl Eq for CredentialAttempt<'_> {}
#[derive(Debug)]
pub struct CredentialReconfirmation {
_private: (),
}
impl CredentialReconfirmation {
#[must_use]
pub const fn acknowledge_same_credentials() -> Self {
Self { _private: () }
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CredentialAttemptStatus {
Open,
Rejected,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CredentialAttemptError {
ForeignState,
GenerationRejected,
StaleGeneration,
ReconfirmationNotRequired,
GenerationExhausted,
}
impl_static_error!(CredentialAttemptError,
Self::ForeignState => "credential attempt belongs to another state",
Self::GenerationRejected => "credential attempt generation was rejected",
Self::StaleGeneration => "credential attempt generation is stale",
Self::ReconfirmationNotRequired => "credential attempt generation is still open",
Self::GenerationExhausted => "credential attempt generation is exhausted",
);
pub struct SharedCredentialAttemptState {
packed: AtomicU32,
}
impl SharedCredentialAttemptState {
#[must_use]
pub const fn new() -> Self {
Self {
packed: AtomicU32::new(pack(CredentialAttemptGeneration::INITIAL, false)),
}
}
#[must_use]
pub fn observe(&self) -> (CredentialAttemptGeneration, CredentialAttemptStatus) {
unpack(self.packed.load(Ordering::Acquire))
}
pub fn begin(&self) -> Result<CredentialAttempt<'_>, CredentialAttemptError> {
let (generation, status) = self.observe();
if status == CredentialAttemptStatus::Rejected {
return Err(CredentialAttemptError::GenerationRejected);
}
Ok(CredentialAttempt {
owner: self,
generation,
})
}
pub fn validate(&self, attempt: CredentialAttempt<'_>) -> Result<(), CredentialAttemptError> {
self.validate_owner(attempt)?;
self.validate_generation(attempt.generation)
}
pub(crate) fn validate_generation(
&self,
expected: CredentialAttemptGeneration,
) -> Result<(), CredentialAttemptError> {
let (generation, status) = self.observe();
if generation != expected {
return Err(CredentialAttemptError::StaleGeneration);
}
if status == CredentialAttemptStatus::Rejected {
return Err(CredentialAttemptError::GenerationRejected);
}
Ok(())
}
pub fn reject(&self, attempt: CredentialAttempt<'_>) -> Result<(), CredentialAttemptError> {
self.validate_owner(attempt)?;
self.reject_generation(attempt.generation)
}
pub(crate) fn reject_generation(
&self,
expected: CredentialAttemptGeneration,
) -> Result<(), CredentialAttemptError> {
loop {
let current = self.packed.load(Ordering::Acquire);
let (generation, status) = unpack(current);
if generation != expected {
return Err(CredentialAttemptError::StaleGeneration);
}
if status == CredentialAttemptStatus::Rejected {
return Ok(());
}
let next = pack(generation, true);
if self
.packed
.compare_exchange(current, next, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
return Ok(());
}
}
}
pub fn replace(
&self,
expected: CredentialAttemptGeneration,
) -> Result<CredentialAttemptGeneration, CredentialAttemptError> {
self.advance(expected)
}
pub fn reconfirm(
&self,
expected: CredentialAttemptGeneration,
_acknowledgement: CredentialReconfirmation,
) -> Result<CredentialAttemptGeneration, CredentialAttemptError> {
loop {
let current = self.packed.load(Ordering::Acquire);
let (generation, status) = unpack(current);
if generation != expected {
return Err(CredentialAttemptError::StaleGeneration);
}
if status != CredentialAttemptStatus::Rejected {
return Err(CredentialAttemptError::ReconfirmationNotRequired);
}
let next_generation = checked_next(generation)?;
let next = pack(next_generation, false);
if self
.packed
.compare_exchange(current, next, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
return Ok(next_generation);
}
}
}
fn advance(
&self,
expected: CredentialAttemptGeneration,
) -> Result<CredentialAttemptGeneration, CredentialAttemptError> {
loop {
let current = self.packed.load(Ordering::Acquire);
let (generation, _) = unpack(current);
if generation != expected {
return Err(CredentialAttemptError::StaleGeneration);
}
let next_generation = checked_next(generation)?;
let next = pack(next_generation, false);
if self
.packed
.compare_exchange(current, next, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
return Ok(next_generation);
}
}
}
fn validate_owner(&self, attempt: CredentialAttempt<'_>) -> Result<(), CredentialAttemptError> {
if !core::ptr::eq(self, attempt.owner) {
return Err(CredentialAttemptError::ForeignState);
}
Ok(())
}
#[cfg(test)]
fn set_generation_for_test(&mut self, generation: u32, rejected: bool) {
*self.packed.get_mut() = pack(CredentialAttemptGeneration(generation), rejected);
}
}
fn checked_next(
generation: CredentialAttemptGeneration,
) -> Result<CredentialAttemptGeneration, CredentialAttemptError> {
generation
.0
.checked_add(1)
.filter(|candidate| *candidate <= MAX_GENERATION)
.map(CredentialAttemptGeneration)
.ok_or(CredentialAttemptError::GenerationExhausted)
}
impl Default for SharedCredentialAttemptState {
fn default() -> Self {
Self::new()
}
}
impl core::fmt::Debug for SharedCredentialAttemptState {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let (generation, status) = self.observe();
formatter
.debug_struct("SharedCredentialAttemptState")
.field("generation", &generation)
.field("status", &status)
.finish()
}
}
const fn pack(generation: CredentialAttemptGeneration, rejected: bool) -> u32 {
(generation.0 << GENERATION_SHIFT) | (rejected as u32)
}
const fn unpack(value: u32) -> (CredentialAttemptGeneration, CredentialAttemptStatus) {
let generation = CredentialAttemptGeneration(value >> GENERATION_SHIFT);
let status = if value & REJECTED == 0 {
CredentialAttemptStatus::Open
} else {
CredentialAttemptStatus::Rejected
};
(generation, status)
}
#[cfg(test)]
mod tests {
use super::{
CredentialAttemptError, CredentialAttemptGeneration, CredentialAttemptStatus,
CredentialReconfirmation, MAX_GENERATION, SharedCredentialAttemptState,
};
#[test]
fn rejection_closes_one_generation_until_replaced_or_reconfirmed() {
let state = SharedCredentialAttemptState::new();
let first = state
.begin()
.unwrap_or_else(|_| unreachable!("initial credential generation was closed"));
assert_eq!(first.generation(), CredentialAttemptGeneration::INITIAL);
assert_eq!(
state.reconfirm(
first.generation(),
CredentialReconfirmation::acknowledge_same_credentials(),
),
Err(CredentialAttemptError::ReconfirmationNotRequired)
);
assert_eq!(state.reject(first), Ok(()));
assert_eq!(
state.validate(first),
Err(CredentialAttemptError::GenerationRejected)
);
assert_eq!(state.reject(first), Ok(()));
assert_eq!(
state.begin(),
Err(CredentialAttemptError::GenerationRejected)
);
let second = state
.reconfirm(
first.generation(),
CredentialReconfirmation::acknowledge_same_credentials(),
)
.unwrap_or_else(|_| unreachable!("explicit reconfirmation was rejected"));
assert_eq!(second.get(), 2);
assert!(state.begin().is_ok());
let third = state
.replace(second)
.unwrap_or_else(|_| unreachable!("replacement generation was rejected"));
assert_eq!(third.get(), 3);
assert!(state.begin().is_ok());
}
#[test]
fn stale_transitions_cannot_close_or_reopen_replacement_credentials() {
let state = SharedCredentialAttemptState::new();
let stale = state
.begin()
.unwrap_or_else(|_| unreachable!("initial credential generation was closed"));
let current = state
.replace(stale.generation())
.unwrap_or_else(|_| unreachable!("replacement generation was rejected"));
assert_eq!(
state.reject(stale),
Err(CredentialAttemptError::StaleGeneration)
);
assert_eq!(
state.validate(stale),
Err(CredentialAttemptError::StaleGeneration)
);
assert_eq!(
state.replace(stale.generation()),
Err(CredentialAttemptError::StaleGeneration)
);
assert_eq!(
state.reconfirm(
stale.generation(),
CredentialReconfirmation::acknowledge_same_credentials(),
),
Err(CredentialAttemptError::StaleGeneration)
);
assert_eq!(state.observe(), (current, CredentialAttemptStatus::Open));
}
#[test]
fn foreign_attempts_never_validate_or_close_equal_generations() {
let owner_a = SharedCredentialAttemptState::new();
let owner_b = SharedCredentialAttemptState::new();
let foreign = owner_a
.begin()
.unwrap_or_else(|_| unreachable!("owner A generation was closed"));
assert_eq!(
owner_b.validate(foreign),
Err(CredentialAttemptError::ForeignState)
);
assert_eq!(
owner_b.reject(foreign),
Err(CredentialAttemptError::ForeignState)
);
assert_eq!(
owner_b.observe(),
(
CredentialAttemptGeneration::INITIAL,
CredentialAttemptStatus::Open
)
);
let generation_a = owner_a
.replace(CredentialAttemptGeneration::INITIAL)
.unwrap_or_else(|_| unreachable!("owner A replacement failed"));
let generation_b = owner_b
.replace(CredentialAttemptGeneration::INITIAL)
.unwrap_or_else(|_| unreachable!("owner B replacement failed"));
assert_eq!(generation_a, generation_b);
let foreign_replacement = owner_a
.begin()
.unwrap_or_else(|_| unreachable!("owner A replacement was closed"));
assert_eq!(
owner_b.reject(foreign_replacement),
Err(CredentialAttemptError::ForeignState)
);
assert_eq!(
owner_b.observe(),
(generation_b, CredentialAttemptStatus::Open)
);
}
#[test]
fn generation_exhaustion_fails_closed_without_wrapping() {
let mut state = SharedCredentialAttemptState::new();
state.set_generation_for_test(MAX_GENERATION, true);
let generation = state.observe().0;
assert_eq!(
state.replace(generation),
Err(CredentialAttemptError::GenerationExhausted)
);
assert_eq!(
state.reconfirm(
generation,
CredentialReconfirmation::acknowledge_same_credentials(),
),
Err(CredentialAttemptError::GenerationExhausted)
);
assert_eq!(
state.observe(),
(generation, CredentialAttemptStatus::Rejected)
);
}
}