use crate::session_liveness::EncryptedRefreshToken;
use super::super::sv_cache::CheckResult;
#[derive(Debug)]
#[must_use]
pub(crate) enum SvStep {
QueryCache,
LoadCiphertext,
PasRefresh { ciphertext: EncryptedRefreshToken },
PersistSv { new_sv: i64 },
RecordCache { sv: i64 },
ReFetch,
Done(SvDecision),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum SvDecision {
FreshFromCache,
Refreshed,
Expired(ExpiryCause),
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub(crate) enum ExpiryCause {
RefreshCiphertextAbsent,
RefreshLoadFailed,
CipherMissing,
CipherFailed,
PasRefreshRejected,
PasRefreshTransient,
AccessTokenMissingSv,
PersistFailed,
ReFetchMissing,
}
#[derive(Debug)]
pub(crate) enum CiphertextFeed {
Available { ciphertext: EncryptedRefreshToken },
Absent,
LookupFailed,
NoCipherConfigured,
}
#[derive(Debug)]
pub(crate) enum PasRefreshFeed {
Refreshed { new_sv: Option<i64> },
Rejected,
Transient,
CipherFailed,
}
#[derive(Debug)]
pub(crate) enum PersistFeed {
Ok,
Failed,
}
#[derive(Debug)]
pub(crate) enum RefetchFeed {
Found,
Missing,
}
pub(crate) struct SvCore {
state: State,
}
#[derive(Debug)]
enum State {
AwaitingCheck,
AwaitingCiphertext,
AwaitingPasRefresh,
AwaitingPersist { new_sv: i64 },
AwaitingRecord,
AwaitingRefetch,
Done,
}
impl SvCore {
pub(crate) fn start() -> (Self, SvStep) {
(Self { state: State::AwaitingCheck }, SvStep::QueryCache)
}
pub(crate) fn feed_check(&mut self, result: CheckResult) -> SvStep {
debug_assert!(matches!(self.state, State::AwaitingCheck));
match result {
CheckResult::Fresh => self.terminate(SvDecision::FreshFromCache),
CheckResult::Stale | CheckResult::Unknown => {
self.state = State::AwaitingCiphertext;
SvStep::LoadCiphertext
}
}
}
pub(crate) fn feed_ciphertext(&mut self, result: CiphertextFeed) -> SvStep {
debug_assert!(matches!(self.state, State::AwaitingCiphertext));
match result {
CiphertextFeed::Available { ciphertext } => {
self.state = State::AwaitingPasRefresh;
SvStep::PasRefresh { ciphertext }
}
CiphertextFeed::Absent => self.expire(ExpiryCause::RefreshCiphertextAbsent),
CiphertextFeed::LookupFailed => self.expire(ExpiryCause::RefreshLoadFailed),
CiphertextFeed::NoCipherConfigured => self.expire(ExpiryCause::CipherMissing),
}
}
pub(crate) fn feed_pas_refresh(&mut self, result: PasRefreshFeed) -> SvStep {
debug_assert!(matches!(self.state, State::AwaitingPasRefresh));
match result {
PasRefreshFeed::Refreshed { new_sv: Some(new_sv) } => {
self.state = State::AwaitingPersist { new_sv };
SvStep::PersistSv { new_sv }
}
PasRefreshFeed::Refreshed { new_sv: None } => {
self.expire(ExpiryCause::AccessTokenMissingSv)
}
PasRefreshFeed::Rejected => self.expire(ExpiryCause::PasRefreshRejected),
PasRefreshFeed::Transient => self.expire(ExpiryCause::PasRefreshTransient),
PasRefreshFeed::CipherFailed => self.expire(ExpiryCause::CipherFailed),
}
}
pub(crate) fn feed_persist(&mut self, result: PersistFeed) -> SvStep {
let new_sv = match self.state {
State::AwaitingPersist { new_sv } => new_sv,
_ => {
debug_assert!(false, "feed_persist called in unexpected state");
return self.expire(ExpiryCause::PersistFailed);
}
};
match result {
PersistFeed::Ok => {
self.state = State::AwaitingRecord;
SvStep::RecordCache { sv: new_sv }
}
PersistFeed::Failed => self.expire(ExpiryCause::PersistFailed),
}
}
pub(crate) fn feed_record(&mut self) -> SvStep {
debug_assert!(matches!(self.state, State::AwaitingRecord));
self.state = State::AwaitingRefetch;
SvStep::ReFetch
}
pub(crate) fn feed_refetch(&mut self, result: RefetchFeed) -> SvStep {
debug_assert!(matches!(self.state, State::AwaitingRefetch));
match result {
RefetchFeed::Found => self.terminate(SvDecision::Refreshed),
RefetchFeed::Missing => self.expire(ExpiryCause::ReFetchMissing),
}
}
fn expire(&mut self, cause: ExpiryCause) -> SvStep {
self.terminate(SvDecision::Expired(cause))
}
fn terminate(&mut self, decision: SvDecision) -> SvStep {
self.state = State::Done;
SvStep::Done(decision)
}
}