1use amiss_wire::controls::ConstraintPlatform;
2use amiss_wire::digest::{Digest, RAW_EVIDENCE_DOMAIN, hb, hj, sha256};
3use amiss_wire::json::{Value, canonical};
4use amiss_wire::manifest::{
5 DEPENDENCY_LOCK_DOMAIN, DEPENDENCY_LOCK_SCHEMA, ENVIRONMENT_CONTRACT, MANIFEST_DOMAIN,
6 MANIFEST_SCHEMA, RUNTIME_CONTRACT, RuntimeRole,
7};
8
9use crate::ENGINE_DOMAIN;
10
11pub struct StagedFile<'bytes> {
14 pub path: String,
15 pub role: RuntimeRole,
16 pub executable: bool,
17 pub bytes: &'bytes [u8],
18}
19
20pub struct StagedArtifact<'bytes> {
24 pub platform: ConstraintPlatform,
25 pub artifact_name: String,
26 pub files: Vec<StagedFile<'bytes>>,
27}
28
29pub struct StagedBuild<'bytes> {
31 pub engine_version: String,
32 pub owner: String,
33 pub repository: String,
34 pub object_format: &'static str,
35 pub commit_oid: String,
36 pub locks: Vec<(String, &'bytes [u8])>,
37}
38
39pub fn build_manifest(
51 build: &StagedBuild<'_>,
52 artifacts: &mut [StagedArtifact<'_>],
53) -> Result<(Vec<u8>, Digest), &'static str> {
54 let mut locks: Vec<(String, Digest)> = build
55 .locks
56 .iter()
57 .map(|(path, bytes)| (path.clone(), hb(RAW_EVIDENCE_DOMAIN, bytes)))
58 .collect();
59 locks.sort_by(|a, b| a.0.cmp(&b.0));
60 let lock_value = object(vec![
61 ("schema", string(DEPENDENCY_LOCK_SCHEMA)),
62 (
63 "files",
64 Value::Array(
65 locks
66 .iter()
67 .map(|(path, digest)| {
68 object(vec![
69 ("path", string(path)),
70 ("raw_digest", string(&digest.to_string())),
71 ])
72 })
73 .collect(),
74 ),
75 ),
76 ]);
77 let lock_digest = hj(DEPENDENCY_LOCK_DOMAIN, &lock_value);
78
79 artifacts.sort_by_key(|artifact| artifact.platform.as_str());
80 let mut rows: Vec<Value> = Vec::with_capacity(artifacts.len());
81 for artifact in artifacts.iter_mut() {
82 rows.push(artifact_value(artifact)?);
83 }
84
85 let manifest = object(vec![
86 ("schema", string(MANIFEST_SCHEMA)),
87 ("engine_version", string(&build.engine_version)),
88 (
89 "build_source",
90 object(vec![
91 (
92 "repository",
93 object(vec![
94 ("host", string("github.com")),
95 ("owner", string(&build.owner)),
96 ("name", string(&build.repository)),
97 ]),
98 ),
99 ("object_format", string(build.object_format)),
100 ("commit_oid", string(&build.commit_oid)),
101 ]),
102 ),
103 ("dependency_lock", lock_value),
104 ("dependency_lock_digest", string(&lock_digest.to_string())),
105 ("artifacts", Value::Array(rows)),
106 ]);
107 let digest = hj(MANIFEST_DOMAIN, &manifest);
108 let mut bytes = canonical(&manifest);
109 bytes.push(b'\n');
110 Ok((bytes, digest))
111}
112
113fn artifact_value(artifact: &mut StagedArtifact<'_>) -> Result<Value, &'static str> {
114 artifact.files.sort_by(|a, b| a.path.cmp(&b.path));
115 let mut executables = artifact
116 .files
117 .iter()
118 .filter(|file| file.role == RuntimeRole::Executable);
119 let engine = executables.next().ok_or("no executable row")?;
120 if executables.next().is_some() {
121 return Err("more than one executable row");
122 }
123 if !engine.executable {
124 return Err("the executable row is not mode 100755");
125 }
126 let mut launchers = artifact
127 .files
128 .iter()
129 .filter(|file| file.role == RuntimeRole::Launcher);
130 let launcher = launchers.next().ok_or("no launcher row")?;
131 if launchers.next().is_some() {
132 return Err("more than one launcher row");
133 }
134 if launcher.executable {
135 return Err("the launcher row is not mode 100644");
136 }
137 let binary_sha256 = sha256(engine.bytes);
138 let engine_digest = hb(ENGINE_DOMAIN, engine.bytes);
139 let tree_path = engine.path.clone();
140
141 let files: Vec<Value> = artifact
142 .files
143 .iter()
144 .map(|file| {
145 object(vec![
146 ("path", string(&file.path)),
147 ("role", string(file.role.as_str())),
148 (
149 "git_mode",
150 string(if file.executable { "100755" } else { "100644" }),
151 ),
152 ("file_sha256", string(&sha256(file.bytes).to_string())),
153 ])
154 })
155 .collect();
156
157 Ok(object(vec![
158 ("platform", string(artifact.platform.as_str())),
159 ("artifact_name", string(&artifact.artifact_name)),
160 ("tree_path", string(&tree_path)),
161 ("binary_sha256", string(&binary_sha256.to_string())),
162 ("engine_digest", string(&engine_digest.to_string())),
163 ("runtime_contract", string(RUNTIME_CONTRACT)),
164 ("environment_contract", string(ENVIRONMENT_CONTRACT)),
165 ("runtime_files", Value::Array(files)),
166 ]))
167}
168
169#[must_use]
173fn string(text: &str) -> Value {
174 Value::String(text.to_owned())
175}
176
177fn object(members: Vec<(&str, Value)>) -> Value {
178 Value::Object(
179 members
180 .into_iter()
181 .map(|(key, value)| (key.to_owned(), value))
182 .collect(),
183 )
184}