use std::num::NonZeroU64;
use kernel::{AxCode, AxError};
use wire::{PrivacyControl, PrivacyFaultCode};
#[derive(Debug)]
pub(super) enum HistoryFault {
Io(std::io::Error),
Decode {
line: usize,
column: usize,
category: serde_json::error::Category,
},
Invalid(&'static str),
Busy,
Capacity,
}
impl HistoryFault {
pub(super) fn into_ax(self, action: &'static str) -> AxError {
let (code, subject, recovery) = match self {
Self::Io(source) => (
AxCode::StorageFatal,
format!("history IO: {source}"),
"check local history access and retry",
),
Self::Decode {
line,
column,
category,
} => (
AxCode::StorageFatal,
format!("history decoding at line {line} column {column} ({category:?})"),
"preserve the history bytes and inspect the malformed line; do not truncate it",
),
Self::Invalid(reason) => (
AxCode::StorageFatal,
reason.to_owned(),
"preserve the history and resolve its incompatible or unresolved operation before any write",
),
Self::Busy => (
AxCode::LedgerHeld,
"privacy history is held by another process".to_owned(),
"wait for that operation to end, then read status again",
),
Self::Capacity => (
AxCode::StorageFatal,
"privacy history exceeds its reader capacity".to_owned(),
"preserve the history and use a reader that supports its size; do not remove original values",
),
};
AxError::failure(code, action, subject).with_recovery(recovery)
}
}
#[derive(Debug)]
pub(crate) enum ReadFault {
#[cfg_attr(
not(any(test, windows)),
expect(dead_code, reason = "only the Windows adapters construct it")
)]
AccessDenied,
Failed(AxError),
}
#[derive(Debug)]
pub(crate) enum WriteFault {
#[cfg_attr(
not(any(test, windows)),
expect(dead_code, reason = "only the Windows adapters construct it")
)]
AccessDenied,
#[cfg_attr(
not(any(test, windows)),
expect(dead_code, reason = "only the Windows adapters construct it")
)]
Declined,
Failed(AxError),
}
#[derive(Debug)]
pub(super) enum PrivacyFault {
Identity(AxError),
Clock(AxError),
History(HistoryFault),
HistoryFull,
Unreadable {
control: PrivacyControl,
fault: ReadFault,
},
Unresolved,
Changed {
control: PrivacyControl,
},
TargetAbsent {
control: PrivacyControl,
},
NothingOwned {
control: PrivacyControl,
},
Conflict {
control: PrivacyControl,
},
NothingUnresolved,
Expired {
control: PrivacyControl,
operation: Option<NonZeroU64>,
},
NotApplied {
control: PrivacyControl,
operation: NonZeroU64,
cause: Option<WriteFault>,
},
RolledBack {
control: PrivacyControl,
operation: NonZeroU64,
},
Unknown {
control: PrivacyControl,
operation: NonZeroU64,
why: Unconfirmed,
},
ReceiptLost {
control: PrivacyControl,
operation: NonZeroU64,
fault: HistoryFault,
},
}
#[derive(Debug)]
pub(super) enum Unconfirmed {
ReadbackUnreadable(ReadFault),
RollbackMissed(Option<WriteFault>),
RollbackUnreadable(ReadFault),
}
impl PrivacyFault {
pub(super) fn code(&self) -> PrivacyFaultCode {
match self {
Self::Identity(_) => PrivacyFaultCode::Identity,
Self::Clock(_) => PrivacyFaultCode::Clock,
Self::History(_) => PrivacyFaultCode::History,
Self::HistoryFull => PrivacyFaultCode::HistoryFull,
Self::Unreadable { .. } => PrivacyFaultCode::Unreadable,
Self::Unresolved => PrivacyFaultCode::Unresolved,
Self::Changed { .. } => PrivacyFaultCode::Changed,
Self::TargetAbsent { .. } => PrivacyFaultCode::TargetAbsent,
Self::NothingOwned { .. } => PrivacyFaultCode::NothingOwned,
Self::Conflict { .. } => PrivacyFaultCode::Conflict,
Self::NothingUnresolved => PrivacyFaultCode::NothingUnresolved,
Self::Expired { .. } => PrivacyFaultCode::Expired,
Self::NotApplied { cause, .. } => match cause {
Some(WriteFault::AccessDenied) => PrivacyFaultCode::AccessDenied,
Some(WriteFault::Declined) => PrivacyFaultCode::ElevationDeclined,
Some(WriteFault::Failed(_)) | None => PrivacyFaultCode::NotApplied,
},
Self::RolledBack { .. } => PrivacyFaultCode::ReadbackMismatch,
Self::Unknown { .. } => PrivacyFaultCode::Unknown,
Self::ReceiptLost { .. } => PrivacyFaultCode::ReceiptLost,
}
}
}
impl PrivacyFault {
pub(super) fn into_ax(self, action: &'static str) -> AxError {
let code = self.code().as_str();
let (ax, subject, recovery) = match self {
Self::Identity(error) | Self::Clock(error) => return error,
Self::History(fault) => return fault.into_ax(action),
Self::HistoryFull => (
AxCode::StorageFatal,
format!("{code}: the privacy history has no operation number left"),
"keep the history; nothing was written",
),
Self::Unreadable { control, fault } => (
match fault {
ReadFault::AccessDenied => AxCode::SandboxDenied,
ReadFault::Failed(_) => AxCode::ToolUnavailable,
},
format!("{code}: {control:?} could not be read: {fault}"),
"nothing was written; check that this account may read the setting and ask again",
),
Self::Unresolved => (
AxCode::ToolOutcomeUnknown,
format!("{code}: an earlier privacy operation has no conclusion"),
"check the setting the page shows for that operation and reconcile it; nothing is written until then",
),
Self::Changed { control } => (
AxCode::VersionConflict,
format!("{code}: {control:?} no longer reads the value you confirmed"),
"nothing was written; read the page again and confirm the value it shows now",
),
Self::TargetAbsent { control } => (
AxCode::PathNotFound,
format!("{code}: this host has no target for {control:?}"),
"nothing was written; this host does not have what this control changes",
),
Self::NothingOwned { control } => (
AxCode::InvalidArgs,
format!("{code}: this app owns no change of {control:?}"),
"nothing was written; only a value this app wrote can be restored",
),
Self::Conflict { control } => (
AxCode::VersionConflict,
format!("{code}: {control:?} was changed after this app wrote it"),
"nothing was written; the later value is not overwritten, change it in Windows if you want the original back",
),
Self::NothingUnresolved => (
AxCode::InvalidArgs,
format!("{code}: no privacy operation waits for a check"),
"nothing to reconcile; read the page again",
),
Self::Expired { control, operation } => (
AxCode::Timeout,
match operation {
None => format!("{code}: the write of {control:?} did not start in time"),
Some(operation) => format!(
"{code}: the write of {control:?} did not start in time; operation {operation} is recorded as not applied"
),
},
"nothing was written; read the page again and confirm once more",
),
Self::NotApplied {
control,
operation,
cause,
} => (
match cause {
Some(WriteFault::AccessDenied | WriteFault::Declined) => AxCode::SandboxDenied,
Some(WriteFault::Failed(_)) | None => AxCode::ToolUnavailable,
},
match &cause {
Some(fault) => format!(
"{code}: {control:?} still reads its original value after operation {operation}; the write reported: {fault}"
),
None => format!(
"{code}: {control:?} still reads its original value after operation {operation}"
),
},
match cause {
Some(WriteFault::AccessDenied) => {
"this account may not write this policy key; nothing changed"
}
Some(WriteFault::Declined) => {
"administrator approval was declined; nothing changed, approve the prompt to apply"
}
Some(WriteFault::Failed(_)) | None => {
"nothing changed; Windows kept the original value"
}
},
),
Self::RolledBack { control, operation } => (
AxCode::EvidenceMissing,
format!(
"{code}: {control:?} read back a value nobody asked for in operation {operation}"
),
"the original value was written back and checked; read the page before trying again",
),
Self::Unknown {
control,
operation,
why,
} => (
AxCode::ToolOutcomeUnknown,
format!(
"{code}: operation {operation} on {control:?} could not be confirmed: {why}"
),
"check the setting and reconcile the operation on the page; nothing is written until then",
),
Self::ReceiptLost {
control,
operation,
fault,
} => (
AxCode::ToolOutcomeUnknown,
format!(
"{code}: the outcome of operation {operation} on {control:?} is not recorded: {}",
fault.into_ax(action).subject()
),
"the setting may have changed; read the page and reconcile the operation",
),
};
AxError::failure(ax, action, subject).with_recovery(recovery)
}
}
impl std::fmt::Display for ReadFault {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::AccessDenied => f.write_str("access denied"),
Self::Failed(error) => write!(f, "{error}"),
}
}
}
impl std::fmt::Display for WriteFault {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::AccessDenied => f.write_str("access denied"),
Self::Declined => f.write_str("administrator approval declined"),
Self::Failed(error) => write!(f, "{error}"),
}
}
}
impl std::fmt::Display for Unconfirmed {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ReadbackUnreadable(fault) => write!(f, "the readback failed: {fault}"),
Self::RollbackMissed(None) => {
f.write_str("the original was written back but does not read back")
}
Self::RollbackMissed(Some(fault)) => {
write!(f, "writing the original back failed: {fault}")
}
Self::RollbackUnreadable(fault) => {
write!(
f,
"the read after writing the original back failed: {fault}"
)
}
}
}
}