///|
/// One named canonical document in a reproducible digest manifest.
pub(all) struct DigestEntry {
name : String
digest : String
byte_length : Int
}
///|
pub fn digest_batch(
documents : Array[(String, String)],
) -> Result[Array[DigestEntry], String] {
let entries : Array[DigestEntry] = []
for document in documents {
let prepared = match prepare_document(document.1) {
Ok(prepared) => prepared
Err(error) => return Err(document.0 + ": " + error)
}
entries.push({
name: document.0,
digest: prepared.digest,
byte_length: prepared.byte_length,
})
}
sort_digest_entries(entries)
Ok(entries)
}
///|
/// Build a canonical JSON array describing a named document set. Sorting by
/// name makes the manifest independent of filesystem traversal order.
pub fn digest_manifest(
documents : Array[(String, String)],
) -> Result[String, String] {
let entries = match digest_batch(documents) {
Ok(entries) => entries
Err(error) => return Err(error)
}
let mut output = "["
let mut first = true
for entry in entries {
if !first {
output = output + ","
}
first = false
output = output +
"{\"name\":" +
canonical_string(entry.name) +
",\"sha256\":" +
canonical_string(entry.digest) +
",\"byte_length\":" +
entry.byte_length.to_string() +
"}"
}
let raw = output + "]"
let value = match parse(raw) {
Ok(value) => value
Err(error) => return Err(error.message())
}
match canonicalize(value) {
Ok(canonical) => Ok(canonical)
Err(error) => Err(error.message())
}
}
///|
pub fn verify_digest(input : String, expected : String) -> Result[Bool, String] {
match digest_json(input) {
Ok(actual) => Ok(actual == expected)
Err(error) => Err(error)
}
}
///|
/// Verify a manifest by recomputing every named document and comparing the
/// canonical manifest bytes, including deterministic entry ordering.
pub fn verify_manifest(
manifest : String,
documents : Array[(String, String)],
) -> Result[Bool, String] {
let expected = match digest_manifest(documents) {
Ok(expected) => expected
Err(error) => return Err(error)
}
let parsed = match parse(manifest) {
Ok(value) => value
Err(error) => return Err(error.message())
}
let actual = match canonicalize(parsed) {
Ok(actual) => actual
Err(error) => return Err(error.message())
}
Ok(actual == expected)
}
///|
pub fn manifest_digest(
documents : Array[(String, String)],
) -> Result[String, String] {
let manifest = match digest_manifest(documents) {
Ok(manifest) => manifest
Err(error) => return Err(error)
}
match digest_json(manifest) {
Ok(digest) => Ok(digest)
Err(error) => Err(error)
}
}
///|
fn sort_digest_entries(entries : Array[DigestEntry]) -> Unit {
let mut index = 1
while index < entries.length() {
let current = entries[index]
let mut cursor = index
while cursor > 0 && entries[cursor - 1].name > current.name {
entries[cursor] = entries[cursor - 1]
cursor = cursor - 1
}
entries[cursor] = current
index = index + 1
}
}