use alloc::sync::Arc;
use super::attempt::{
CredentialAttemptError, CredentialAttemptGeneration, CredentialAttemptStatus,
CredentialReconfirmation, SharedCredentialAttemptState,
};
pub struct OwnedCredentialAttempt {
owner: Arc<SharedCredentialAttemptState>,
generation: CredentialAttemptGeneration,
}
impl OwnedCredentialAttempt {
#[must_use]
pub const fn generation(&self) -> CredentialAttemptGeneration {
self.generation
}
}
impl core::fmt::Debug for OwnedCredentialAttempt {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.debug_struct("OwnedCredentialAttempt")
.field("owner", &"[bound]")
.field("generation", &self.generation)
.finish()
}
}
impl PartialEq for OwnedCredentialAttempt {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.owner, &other.owner) && self.generation == other.generation
}
}
impl Eq for OwnedCredentialAttempt {}
pub struct OwnedCredentialAttemptState {
state: Arc<SharedCredentialAttemptState>,
}
impl OwnedCredentialAttemptState {
#[must_use]
pub fn new() -> Self {
Self {
state: Arc::new(SharedCredentialAttemptState::new()),
}
}
#[must_use]
pub fn observe(&self) -> (CredentialAttemptGeneration, CredentialAttemptStatus) {
self.state.observe()
}
pub fn begin(&self) -> Result<OwnedCredentialAttempt, CredentialAttemptError> {
let attempt = self.state.begin()?;
Ok(OwnedCredentialAttempt {
owner: Arc::clone(&self.state),
generation: attempt.generation(),
})
}
pub fn validate(&self, attempt: &OwnedCredentialAttempt) -> Result<(), CredentialAttemptError> {
self.validate_owner(attempt)?;
self.state.validate_generation(attempt.generation)
}
pub fn reject(&self, attempt: &OwnedCredentialAttempt) -> Result<(), CredentialAttemptError> {
self.validate_owner(attempt)?;
self.state.reject_generation(attempt.generation)
}
pub fn replace(
&self,
expected: CredentialAttemptGeneration,
) -> Result<CredentialAttemptGeneration, CredentialAttemptError> {
self.state.replace(expected)
}
pub fn reconfirm(
&self,
expected: CredentialAttemptGeneration,
acknowledgement: CredentialReconfirmation,
) -> Result<CredentialAttemptGeneration, CredentialAttemptError> {
self.state.reconfirm(expected, acknowledgement)
}
fn validate_owner(
&self,
attempt: &OwnedCredentialAttempt,
) -> Result<(), CredentialAttemptError> {
if !Arc::ptr_eq(&self.state, &attempt.owner) {
return Err(CredentialAttemptError::ForeignState);
}
Ok(())
}
}
impl Default for OwnedCredentialAttemptState {
fn default() -> Self {
Self::new()
}
}
impl core::fmt::Debug for OwnedCredentialAttemptState {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let (generation, status) = self.observe();
formatter
.debug_struct("OwnedCredentialAttemptState")
.field("owner", &"[bound]")
.field("generation", &generation)
.field("status", &status)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::OwnedCredentialAttemptState;
use crate::authentication::{CredentialAttemptError, CredentialAttemptGeneration};
#[test]
fn owned_attempts_reject_foreign_and_stale_lineages() {
let owner_a = OwnedCredentialAttemptState::new();
let owner_b = OwnedCredentialAttemptState::new();
let attempt = owner_a
.begin()
.unwrap_or_else(|_| unreachable!("initial owned attempt was rejected"));
assert_eq!(
owner_b.validate(&attempt),
Err(CredentialAttemptError::ForeignState)
);
assert_eq!(
owner_b.reject(&attempt),
Err(CredentialAttemptError::ForeignState)
);
let replacement = owner_a
.replace(CredentialAttemptGeneration::INITIAL)
.unwrap_or_else(|_| unreachable!("owned replacement was rejected"));
assert_eq!(replacement.get(), 2);
assert_eq!(
owner_a.reject(&attempt),
Err(CredentialAttemptError::StaleGeneration)
);
}
}