use amiss_wire::controls::ConstraintPlatform;
use amiss_wire::digest::{Digest, RAW_EVIDENCE_DOMAIN, hb, hj, sha256};
use amiss_wire::json::{Value, canonical};
use amiss_wire::manifest::{
DEPENDENCY_LOCK_DOMAIN, DEPENDENCY_LOCK_SCHEMA, ENVIRONMENT_CONTRACT, MANIFEST_DOMAIN,
MANIFEST_SCHEMA, RUNTIME_CONTRACT, RuntimeRole,
};
use crate::ENGINE_DOMAIN;
pub struct StagedFile<'bytes> {
pub path: String,
pub role: RuntimeRole,
pub executable: bool,
pub bytes: &'bytes [u8],
}
pub struct StagedArtifact<'bytes> {
pub platform: ConstraintPlatform,
pub artifact_name: String,
pub files: Vec<StagedFile<'bytes>>,
}
pub struct StagedBuild<'bytes> {
pub engine_version: String,
pub host: String,
pub owner: String,
pub repository: String,
pub object_format: &'static str,
pub commit_oid: String,
pub locks: Vec<(String, &'bytes [u8])>,
}
pub fn build_manifest(
build: &StagedBuild<'_>,
artifacts: &mut [StagedArtifact<'_>],
) -> Result<(Vec<u8>, Digest), &'static str> {
let mut locks: Vec<(String, Digest)> = build
.locks
.iter()
.map(|(path, bytes)| (path.clone(), hb(RAW_EVIDENCE_DOMAIN, bytes)))
.collect();
locks.sort_by(|a, b| a.0.cmp(&b.0));
let lock_value = object(vec![
("schema", string(DEPENDENCY_LOCK_SCHEMA)),
(
"files",
Value::Array(
locks
.iter()
.map(|(path, digest)| {
object(vec![
("path", string(path)),
("raw_digest", string(&digest.to_string())),
])
})
.collect(),
),
),
]);
let lock_digest = hj(DEPENDENCY_LOCK_DOMAIN, &lock_value);
artifacts.sort_by_key(|artifact| artifact.platform.as_str());
let mut rows: Vec<Value> = Vec::with_capacity(artifacts.len());
for artifact in artifacts.iter_mut() {
rows.push(artifact_value(artifact)?);
}
let manifest = object(vec![
("schema", string(MANIFEST_SCHEMA)),
("engine_version", string(&build.engine_version)),
(
"build_source",
object(vec![
(
"repository",
object(vec![
("host", string(&build.host)),
("owner", string(&build.owner)),
("name", string(&build.repository)),
]),
),
("object_format", string(build.object_format)),
("commit_oid", string(&build.commit_oid)),
]),
),
("dependency_lock", lock_value),
("dependency_lock_digest", string(&lock_digest.to_string())),
("artifacts", Value::Array(rows)),
]);
let digest = hj(MANIFEST_DOMAIN, &manifest);
let mut bytes = canonical(&manifest);
bytes.push(b'\n');
Ok((bytes, digest))
}
fn artifact_value(artifact: &mut StagedArtifact<'_>) -> Result<Value, &'static str> {
artifact.files.sort_by(|a, b| a.path.cmp(&b.path));
let mut executables = artifact
.files
.iter()
.filter(|file| file.role == RuntimeRole::Executable);
let engine = executables.next().ok_or("no executable row")?;
if executables.next().is_some() {
return Err("more than one executable row");
}
if !engine.executable {
return Err("the executable row is not mode 100755");
}
let mut launchers = artifact
.files
.iter()
.filter(|file| file.role == RuntimeRole::Launcher);
let launcher = launchers.next().ok_or("no launcher row")?;
if launchers.next().is_some() {
return Err("more than one launcher row");
}
if launcher.executable {
return Err("the launcher row is not mode 100644");
}
let binary_sha256 = sha256(engine.bytes);
let engine_digest = hb(ENGINE_DOMAIN, engine.bytes);
let tree_path = engine.path.clone();
let files: Vec<Value> = artifact
.files
.iter()
.map(|file| {
object(vec![
("path", string(&file.path)),
("role", string(file.role.as_str())),
(
"git_mode",
string(if file.executable { "100755" } else { "100644" }),
),
("file_sha256", string(&sha256(file.bytes).to_string())),
])
})
.collect();
Ok(object(vec![
("platform", string(artifact.platform.as_str())),
("artifact_name", string(&artifact.artifact_name)),
("tree_path", string(&tree_path)),
("binary_sha256", string(&binary_sha256.to_string())),
("engine_digest", string(&engine_digest.to_string())),
("runtime_contract", string(RUNTIME_CONTRACT)),
("environment_contract", string(ENVIRONMENT_CONTRACT)),
("runtime_files", Value::Array(files)),
]))
}
#[must_use]
fn string(text: &str) -> Value {
Value::String(text.to_owned())
}
fn object(members: Vec<(&str, Value)>) -> Value {
Value::Object(
members
.into_iter()
.map(|(key, value)| (key.to_owned(), value))
.collect(),
)
}