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