use super::validate_response;
use crate::{
model::{
artifacts::ArtifactChecksumRecord,
attempt_journal::{AttemptJournalRecord, ObservationOutcomeRecord},
ic_observation::IcObservationResponse,
ic_snapshot_reply::{IcSnapshotInfo, IcSnapshotReply},
ic_snapshot_upload::IcSnapshotUploadError,
ic_snapshot_upload_observation::{
IcSnapshotUploadAttribution, IcSnapshotUploadObservationRequest,
IcSnapshotUploadSettlement,
},
},
policy::{
ic_observation::{
IcObservationAssociationError, IcObservationReplyView, IcObservationResponseView,
},
inventory_settlement::{
InventorySettlementIdentity, InventorySettlementMismatch, new_candidate_index,
},
snapshot_inventory_delta::{SnapshotInventoryDeltaError, compare_inventories},
},
};
use thiserror::Error;
#[derive(Debug)]
pub struct IcSnapshotUploadSettlementView<'a> {
observation: IcObservationResponseView<'a>,
baseline: &'a IcSnapshotReply<'a>,
settlement: &'a IcSnapshotUploadSettlement,
allocated_snapshot: Option<usize>,
outcome: ObservationOutcomeRecord,
}
impl<'a> IcSnapshotUploadSettlementView<'a> {
#[must_use]
pub const fn outcome(&self) -> ObservationOutcomeRecord {
self.outcome
}
#[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 IcSnapshotUploadSettlement {
self.settlement
}
#[must_use]
pub fn allocated_snapshot(&self) -> Option<&IcSnapshotInfo> {
match self.observation.reply() {
IcObservationReplyView::Inventory(reply) => self
.allocated_snapshot
.map(|index| &reply.snapshots()[index]),
IcObservationReplyView::Status(_) => None,
}
}
}
pub fn validate_settlement<'a>(
request: &IcSnapshotUploadObservationRequest<'a, '_>,
journal: &AttemptJournalRecord,
baseline: &'a IcSnapshotReply<'a>,
response: &'a IcObservationResponse,
challenge: &ArtifactChecksumRecord,
settlement: &'a IcSnapshotUploadSettlement,
) -> Result<IcSnapshotUploadSettlementView<'a>, IcSnapshotUploadSettlementError> {
let observation = validate_response(request, journal, response)?;
let IcObservationReplyView::Inventory(inventory) = observation.reply() else {
return Err(IcSnapshotUploadSettlementError::WrongInventory);
};
let candidates = compare_inventories(request.payload().target(), baseline, inventory)?;
let authority = request.authority().digest();
let baseline_digest = baseline.digest();
let inventory_digest = inventory.digest();
InventorySettlementIdentity {
authority: &settlement.authority,
mutation_attempt: settlement.mutation_attempt,
observation_attempt: settlement.observation_attempt,
challenge: &settlement.challenge,
baseline: &settlement.baseline,
inventory: &settlement.inventory,
observation_evidence: &settlement.observation_evidence,
}
.compare(&InventorySettlementIdentity {
authority: &authority,
mutation_attempt: request.mutation_attempt(),
observation_attempt: request.observation_attempt(),
challenge,
baseline: &baseline_digest,
inventory: &inventory_digest,
observation_evidence: &response.input().evidence,
})?;
let (outcome, allocated_snapshot) = match &settlement.attribution {
IcSnapshotUploadAttribution::Applied { snapshot_id, .. } => {
request.mutation().validate_data_destination(snapshot_id)?;
let index = new_candidate_index(inventory, &candidates, snapshot_id)
.ok_or(IcSnapshotUploadSettlementError::NotNewCandidate)?;
(ObservationOutcomeRecord::Applied, Some(index))
}
IcSnapshotUploadAttribution::NotApplied { .. } => {
(ObservationOutcomeRecord::NotApplied, None)
}
IcSnapshotUploadAttribution::Unresolved { .. } => {
(ObservationOutcomeRecord::Uncertain, None)
}
};
Ok(IcSnapshotUploadSettlementView {
observation,
baseline,
settlement,
allocated_snapshot,
outcome,
})
}
#[derive(Debug, Error)]
pub enum IcSnapshotUploadSettlementError {
#[error(transparent)]
Observation(#[from] IcObservationAssociationError),
#[error(transparent)]
Baseline(#[from] SnapshotInventoryDeltaError),
#[error(transparent)]
Destination(#[from] IcSnapshotUploadError),
#[error("metadata settlement requires snapshot inventory")]
WrongInventory,
#[error("metadata settlement authority differs")]
AuthorityMismatch,
#[error("metadata settlement attempts differ")]
AttemptMismatch,
#[error("metadata settlement challenge differs")]
ChallengeMismatch,
#[error("metadata settlement baseline differs")]
BaselineMismatch,
#[error("metadata settlement inventory differs")]
InventoryMismatch,
#[error("metadata settlement observation evidence differs")]
ObservationEvidenceMismatch,
#[error("metadata settlement ID is not a new candidate")]
NotNewCandidate,
}
impl From<InventorySettlementMismatch> for IcSnapshotUploadSettlementError {
fn from(value: InventorySettlementMismatch) -> Self {
match value {
InventorySettlementMismatch::Authority => Self::AuthorityMismatch,
InventorySettlementMismatch::Attempt => Self::AttemptMismatch,
InventorySettlementMismatch::Challenge => Self::ChallengeMismatch,
InventorySettlementMismatch::Baseline => Self::BaselineMismatch,
InventorySettlementMismatch::Inventory => Self::InventoryMismatch,
InventorySettlementMismatch::ObservationEvidence => Self::ObservationEvidenceMismatch,
}
}
}
#[cfg(test)]
mod tests;