use crate::de::{self, Error, ErrorKind, Obj, fail};
use crate::digest::{Digest, hj};
use crate::model::{BranchRef, RepositoryIdentity, UtcInstant};
use super::{
decode_branch_ref, decode_digest, decode_instant, decode_provider_id, decode_provider_run_id,
decode_repository, root,
};
const TRUSTED_TIME_STATEMENT_SCHEMA: &str = "amiss/scanner-trusted-time-statement";
const TRUSTED_TIME_CONTROLLER: &str = "external-required-check-clock";
pub const STATEMENT_TTL_MAX_SECONDS: i64 = 600;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TrustedTimeStatement {
pub digest: Digest,
pub repository: RepositoryIdentity,
pub ref_name: BranchRef,
pub candidate_identity_digest: Digest,
pub provider: String,
pub provider_run_id: String,
pub provider_run_attempt: u64,
pub evaluation_instant: UtcInstant,
pub valid_until: UtcInstant,
}
impl TrustedTimeStatement {
#[must_use]
pub const fn schema(&self) -> &'static str {
TRUSTED_TIME_STATEMENT_SCHEMA
}
#[must_use]
pub const fn controller(&self) -> &'static str {
TRUSTED_TIME_CONTROLLER
}
pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
let value = root(bytes)?;
let digest = hj(TRUSTED_TIME_STATEMENT_SCHEMA, &value);
let mut obj = Obj::new("$", value)?;
de::const_str(
&obj.field("schema"),
obj.take("schema")?,
TRUSTED_TIME_STATEMENT_SCHEMA,
)?;
de::const_str(
&obj.field("controller"),
obj.take("controller")?,
TRUSTED_TIME_CONTROLLER,
)?;
let repository = decode_repository(&obj.field("repository"), obj.take("repository")?)?;
let ref_name = decode_branch_ref(&obj.field("ref"), obj.take("ref")?)?;
let candidate_identity_digest = decode_digest(
&obj.field("candidate_identity_digest"),
obj.take("candidate_identity_digest")?,
)?;
let provider = decode_provider_id(&obj.field("provider"), obj.take("provider")?)?;
let run_id_path = obj.field("provider_run_id");
let provider_run_id = decode_provider_run_id(&run_id_path, obj.take("provider_run_id")?)?;
let attempt_path = obj.field("provider_run_attempt");
let attempt_raw = de::integer(&attempt_path, obj.take("provider_run_attempt")?)?;
let provider_run_attempt = u64::try_from(attempt_raw)
.ok()
.filter(|attempt| *attempt >= 1)
.ok_or_else(|| Error::new(&attempt_path, ErrorKind::InvalidValue))?;
let evaluation_instant = decode_instant(
&obj.field("evaluation_instant"),
obj.take("evaluation_instant")?,
)?;
let until_path = obj.field("valid_until");
let valid_until = decode_instant(&until_path, obj.take("valid_until")?)?;
obj.finish()?;
let lifetime = valid_until
.epoch_seconds()
.saturating_sub(evaluation_instant.epoch_seconds());
if lifetime <= 0 || lifetime > STATEMENT_TTL_MAX_SECONDS {
return fail(&until_path, ErrorKind::InvalidValue);
}
Ok(Self {
digest,
repository,
ref_name,
candidate_identity_digest,
provider,
provider_run_id,
provider_run_attempt,
evaluation_instant,
valid_until,
})
}
}