Skip to main content

amiss_wire/controls/
trusted_time.rs

1use crate::de::{self, Error, ErrorKind, Obj, fail};
2use crate::digest::{Digest, hj};
3use crate::model::{BranchRef, RepositoryIdentity, UtcInstant};
4
5use super::{
6    decode_branch_ref, decode_digest, decode_instant, decode_provider_id, decode_provider_run_id,
7    decode_repository, root,
8};
9
10const TRUSTED_TIME_STATEMENT_SCHEMA: &str = "amiss/scanner-trusted-time-statement";
11const TRUSTED_TIME_CONTROLLER: &str = "external-required-check-clock";
12
13/// The controller's maximum statement lifetime: `evaluation_instant <
14/// valid_until <= evaluation_instant + 600` whole seconds.
15pub const STATEMENT_TTL_MAX_SECONDS: i64 = 600;
16
17/// A trusted-time statement issued by the required-check clock inside the
18/// externally controlled run. Parsing establishes shape and the TTL law; the
19/// evaluation-side bindings (repository, ref, candidate identity, run,
20/// attempt) are separate verification.
21#[derive(Clone, Debug, PartialEq, Eq)]
22pub struct TrustedTimeStatement {
23    pub digest: Digest,
24    pub repository: RepositoryIdentity,
25    pub ref_name: BranchRef,
26    pub candidate_identity_digest: Digest,
27    pub provider: String,
28    pub provider_run_id: String,
29    pub provider_run_attempt: u64,
30    pub evaluation_instant: UtcInstant,
31    pub valid_until: UtcInstant,
32}
33
34impl TrustedTimeStatement {
35    #[must_use]
36    pub const fn schema(&self) -> &'static str {
37        TRUSTED_TIME_STATEMENT_SCHEMA
38    }
39
40    #[must_use]
41    pub const fn controller(&self) -> &'static str {
42        TRUSTED_TIME_CONTROLLER
43    }
44
45    /// # Errors
46    ///
47    /// Fails on strict-JSON defects, schema-shape violations, invalid grammar
48    /// values, and a lifetime outside `0 < valid_until - evaluation_instant
49    /// <= 600` seconds.
50    pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
51        let value = root(bytes)?;
52        let digest = hj(TRUSTED_TIME_STATEMENT_SCHEMA, &value);
53        let mut obj = Obj::new("$", value)?;
54        de::const_str(
55            &obj.field("schema"),
56            obj.take("schema")?,
57            TRUSTED_TIME_STATEMENT_SCHEMA,
58        )?;
59        de::const_str(
60            &obj.field("controller"),
61            obj.take("controller")?,
62            TRUSTED_TIME_CONTROLLER,
63        )?;
64        let repository = decode_repository(&obj.field("repository"), obj.take("repository")?)?;
65        let ref_name = decode_branch_ref(&obj.field("ref"), obj.take("ref")?)?;
66        let candidate_identity_digest = decode_digest(
67            &obj.field("candidate_identity_digest"),
68            obj.take("candidate_identity_digest")?,
69        )?;
70        let provider = decode_provider_id(&obj.field("provider"), obj.take("provider")?)?;
71        let run_id_path = obj.field("provider_run_id");
72        let provider_run_id = decode_provider_run_id(&run_id_path, obj.take("provider_run_id")?)?;
73        let attempt_path = obj.field("provider_run_attempt");
74        let attempt_raw = de::integer(&attempt_path, obj.take("provider_run_attempt")?)?;
75        let provider_run_attempt = u64::try_from(attempt_raw)
76            .ok()
77            .filter(|attempt| *attempt >= 1)
78            .ok_or_else(|| Error::new(&attempt_path, ErrorKind::InvalidValue))?;
79        let evaluation_instant = decode_instant(
80            &obj.field("evaluation_instant"),
81            obj.take("evaluation_instant")?,
82        )?;
83        let until_path = obj.field("valid_until");
84        let valid_until = decode_instant(&until_path, obj.take("valid_until")?)?;
85        obj.finish()?;
86        let lifetime = valid_until
87            .epoch_seconds()
88            .saturating_sub(evaluation_instant.epoch_seconds());
89        if lifetime <= 0 || lifetime > STATEMENT_TTL_MAX_SECONDS {
90            return fail(&until_path, ErrorKind::InvalidValue);
91        }
92        Ok(Self {
93            digest,
94            repository,
95            ref_name,
96            candidate_identity_digest,
97            provider,
98            provider_run_id,
99            provider_run_attempt,
100            evaluation_instant,
101            valid_until,
102        })
103    }
104}