use crate::de::{self, Error, ErrorKind, Obj, fail};
use crate::digest::{Digest, hj};
use crate::json::{Value, canonical};
use crate::model::{BranchRef, RepositoryIdentity, UtcInstant};
use super::value::{object, positive_safe_integer, repository, text};
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,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TrustedTimeInput {
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 {
pub fn new(input: TrustedTimeInput) -> Result<Self, Error> {
Self::parse(&canonical(&trusted_time_value(input)?))
}
#[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,
})
}
pub fn canonical_bytes(&self) -> Result<Vec<u8>, Error> {
let bytes = canonical(&trusted_time_value(self.into())?);
let parsed = Self::parse(&bytes)?;
if parsed.digest != self.digest {
return fail("$.digest", ErrorKind::DigestMismatch);
}
Ok(bytes)
}
}
impl From<&TrustedTimeStatement> for TrustedTimeInput {
fn from(statement: &TrustedTimeStatement) -> Self {
Self {
repository: statement.repository.clone(),
ref_name: statement.ref_name.clone(),
candidate_identity_digest: statement.candidate_identity_digest,
provider: statement.provider.clone(),
provider_run_id: statement.provider_run_id.clone(),
provider_run_attempt: statement.provider_run_attempt,
evaluation_instant: statement.evaluation_instant.clone(),
valid_until: statement.valid_until.clone(),
}
}
}
fn trusted_time_value(input: TrustedTimeInput) -> Result<Value, Error> {
let provider_run_attempt =
positive_safe_integer("$.provider_run_attempt", input.provider_run_attempt)?;
let TrustedTimeInput {
repository: repository_identity,
ref_name,
candidate_identity_digest,
provider,
provider_run_id,
provider_run_attempt: _,
evaluation_instant,
valid_until,
} = input;
Ok(object(vec![
("schema", text(TRUSTED_TIME_STATEMENT_SCHEMA)),
("controller", text(TRUSTED_TIME_CONTROLLER)),
("repository", repository(&repository_identity)),
("ref", text(ref_name.as_str())),
(
"candidate_identity_digest",
text(&candidate_identity_digest.to_string()),
),
("provider", Value::String(provider)),
("provider_run_id", Value::String(provider_run_id)),
("provider_run_attempt", provider_run_attempt),
("evaluation_instant", text(evaluation_instant.as_str())),
("valid_until", text(valid_until.as_str())),
]))
}