use crate::model::inventory_settlement::{IcInventoryAttribution, IcInventorySettlement};
use super::ic_observation::IcObservationResponseView;
use super::{
settlement_identity::{SettlementIdentity, SettlementIdentityMismatch},
snapshot_inventory_delta::{SnapshotInventoryDeltaError, compare_inventories},
};
use crate::model::{
artifacts::ArtifactChecksumRecord,
attempt_journal::ObservationOutcomeRecord,
ic_snapshot_reply::{IcSnapshotInfo, IcSnapshotReply},
};
fn compare_identity(
claimed: &IcInventorySettlement,
operation: &SettlementIdentity<'_>,
baseline: &ArtifactChecksumRecord,
inventory: &ArtifactChecksumRecord,
observation_evidence: &ArtifactChecksumRecord,
) -> Result<(), InventorySettlementMismatch> {
SettlementIdentity::from(&claimed.context).compare(operation)?;
if &claimed.baseline != baseline {
return Err(InventorySettlementMismatch::Baseline);
}
if &claimed.inventory != inventory {
return Err(InventorySettlementMismatch::Inventory);
}
if &claimed.context.observation_evidence != observation_evidence {
return Err(InventorySettlementMismatch::ObservationEvidence);
}
Ok(())
}
#[derive(Debug, thiserror::Error, Eq, PartialEq)]
pub enum InventorySettlementMismatch {
#[error("inventory settlement authority differs")]
Authority,
#[error("inventory settlement attempts differ")]
Attempt,
#[error("inventory settlement challenge differs")]
Challenge,
#[error("inventory settlement baseline differs")]
Baseline,
#[error("inventory settlement inventory differs")]
Inventory,
#[error("inventory settlement observation evidence differs")]
ObservationEvidence,
}
impl From<SettlementIdentityMismatch> for InventorySettlementMismatch {
fn from(value: SettlementIdentityMismatch) -> Self {
match value {
SettlementIdentityMismatch::Authority => Self::Authority,
SettlementIdentityMismatch::Attempt => Self::Attempt,
SettlementIdentityMismatch::Challenge => Self::Challenge,
}
}
}
pub(crate) fn validate_pair<'a, E>(
target: &str,
baseline: &IcSnapshotReply<'_>,
inventory: &'a IcSnapshotReply<'_>,
claimed: &IcInventorySettlement,
operation: &SettlementIdentity<'_>,
observation_evidence: &ArtifactChecksumRecord,
) -> Result<Vec<&'a IcSnapshotInfo>, E>
where
E: From<SnapshotInventoryDeltaError> + From<InventorySettlementMismatch>,
{
let candidates = compare_inventories(target, baseline, inventory)?;
compare_identity(
claimed,
operation,
&baseline.digest(),
&inventory.digest(),
observation_evidence,
)?;
Ok(candidates)
}
pub(crate) fn admitted_snapshot_index(
inventory: &IcSnapshotReply<'_>,
candidates: &[&IcSnapshotInfo],
attribution: &IcInventoryAttribution,
) -> Result<Option<usize>, ()> {
match attribution {
IcInventoryAttribution::Applied { snapshot_id, .. } => {
new_candidate_index(inventory, candidates, snapshot_id)
.map(Some)
.ok_or(())
}
IcInventoryAttribution::NotApplied { .. } | IcInventoryAttribution::Unresolved { .. } => {
Ok(None)
}
}
}
pub(crate) fn new_candidate_index(
inventory: &IcSnapshotReply<'_>,
candidates: &[&IcSnapshotInfo],
snapshot_id: &[u8],
) -> Option<usize> {
if !candidates
.iter()
.any(|snapshot| snapshot.id() == snapshot_id)
{
return None;
}
inventory
.snapshots()
.binary_search_by(|snapshot| snapshot.id().cmp(snapshot_id))
.ok()
}
#[derive(Debug)]
pub struct IcInventorySettlementView<'a> {
pub(crate) observation: IcObservationResponseView<'a>,
pub(crate) baseline: &'a IcSnapshotReply<'a>,
pub(crate) settlement: &'a IcInventorySettlement,
pub(crate) snapshot_index: Option<usize>,
}
impl<'a> IcInventorySettlementView<'a> {
#[must_use]
pub const fn outcome(&self) -> ObservationOutcomeRecord {
match &self.settlement.attribution {
IcInventoryAttribution::Applied { .. } => ObservationOutcomeRecord::Applied,
IcInventoryAttribution::NotApplied { .. } => ObservationOutcomeRecord::NotApplied,
IcInventoryAttribution::Unresolved { .. } => ObservationOutcomeRecord::Uncertain,
}
}
#[must_use]
pub const fn baseline(&self) -> &'a IcSnapshotReply<'a> {
self.baseline
}
#[must_use]
pub const fn observation(&self) -> &IcObservationResponseView<'a> {
&self.observation
}
#[must_use]
pub const fn settlement(&self) -> &'a IcInventorySettlement {
self.settlement
}
#[must_use]
pub fn snapshot(&self) -> Option<&IcSnapshotInfo> {
self.observation.inventory_snapshot(self.snapshot_index?)
}
}
#[cfg(test)]
mod tests;