#![allow(dead_code)]
use std::io::Write as _;
use std::path::{Path, PathBuf};
use base64::engine::general_purpose::STANDARD as BASE64;
use base64::Engine as _;
use ed25519_dalek::pkcs8::spki::der::pem::LineEnding;
use ed25519_dalek::pkcs8::spki::EncodePublicKey as _;
use ed25519_dalek::{Signer as _, SigningKey};
use serde_json::{json, Value};
use sha2::{Digest, Sha256};
use scrollcase_consumer::contract::payload_digest::{
payload_digest_stream, PayloadDigestEntry, PayloadDigestKind, PAYLOAD_DIGEST_FILE,
};
use zip::write::SimpleFileOptions;
pub const KEY_ID: &str = "scrollcase-fixture";
const SIGNING_SEED: [u8; 32] = [7u8; 32];
pub fn native_target() -> Value {
match (std::env::consts::OS, std::env::consts::ARCH) {
("macos", "aarch64") => json!({
"platform": "macos", "arch": "aarch64", "accelerator": "metal",
}),
("windows", _) => json!({
"platform": "windows", "arch": "x86_64", "accelerator": "cpu",
}),
_ => json!({ "platform": "linux", "arch": "x86_64", "accelerator": "cpu" }),
}
}
pub fn native_python_entry_point() -> &'static str {
if std::env::consts::OS == "windows" {
"venv/python.exe"
} else {
"venv/bin/python"
}
}
pub enum Entry {
File(&'static str, Vec<u8>, u32),
Link(&'static str, &'static str),
}
pub fn sha256_hex(bytes: &[u8]) -> String {
use std::fmt::Write as _;
Sha256::digest(bytes)
.iter()
.fold(String::with_capacity(64), |mut hex, byte| {
let _ = write!(hex, "{byte:02x}");
hex
})
}
pub struct BoxFixture {
pub directory: PathBuf,
pub archive_path: PathBuf,
pub release_path: PathBuf,
pub key_path: PathBuf,
}
impl Drop for BoxFixture {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.directory);
}
}
fn scratch(name: &str) -> PathBuf {
let unique = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
let directory = std::env::temp_dir().join(format!("scrollcase-{name}-{unique}"));
std::fs::create_dir_all(&directory).unwrap();
directory
}
pub fn box_manifest() -> Value {
json!({
"schemaVersion": 2,
"boxId": "fixture-box",
"modelId": "fixture-model",
"runtimeId": "fixture-runtime",
"version": "1.0.0",
"target": native_target(),
"pythonEntryPoint": native_python_entry_point(),
"modelCacheSubdir": "model-cache/fixture",
"selfTest": { "pythonImports": ["json"], "timeoutSeconds": 30 },
"execution": { "kind": "python-script", "script": "app/main.py", "defaultArgs": [] },
"provenance": {
"scrollId": "fixture-box",
"scrollVersion": "1.0.0",
"builderRevision": "b".repeat(40),
"sourceTreeDirty": false,
"sourceRevision": "c".repeat(40),
"pythonVersion": "3.11.9",
"dependencyLockSha256": "d".repeat(64),
"builtAt": "2026-01-01T00:00:00.000Z",
"pixiVersion": "0.50.0"
}
})
}
pub const FIXTURE_INTERPRETER: &[u8] = b"#!/bin/sh\n\
printf '%s\\n' \"$@\" > invocation.txt\n\
printf '%s' \"${SCROLLCASE_TEST_ENV-}\" > environment.txt\n\
if [ -n \"${SCROLLCASE_TEST_SLEEP-}\" ]; then sleep \"$SCROLLCASE_TEST_SLEEP\"; fi\n\
exit \"${SCROLLCASE_TEST_EXIT-0}\"\n";
pub fn default_entries(manifest: &Value) -> Vec<Entry> {
vec![
Entry::File(
"box.json",
serde_json::to_vec_pretty(manifest).unwrap(),
0o644,
),
Entry::File(native_python_entry_point(), FIXTURE_INTERPRETER.to_vec(), 0o755),
Entry::File("app/main.py", b"print('fixture')\n".to_vec(), 0o644),
]
}
pub fn write_archive(path: &Path, entries: &[Entry]) {
let file = std::fs::File::create(path).unwrap();
let mut writer = zip::ZipWriter::new(file);
for entry in entries {
match entry {
Entry::File(name, bytes, mode) => {
writer
.start_file(
*name,
SimpleFileOptions::default()
.compression_method(zip::CompressionMethod::Deflated)
.unix_permissions(*mode),
)
.unwrap();
writer.write_all(bytes).unwrap();
}
Entry::Link(name, target) => {
writer
.add_symlink(*name, *target, SimpleFileOptions::default())
.unwrap();
}
}
}
writer.finish().unwrap();
}
pub fn release_for(manifest: &Value, archive_path: &Path) -> Value {
let bytes = std::fs::read(archive_path).unwrap();
let mut release = manifest.clone();
let object = release.as_object_mut().unwrap();
object.insert("kind".to_string(), json!("scrollcase.box.release"));
object.insert("compatibility".to_string(), json!({}));
object.insert(
"archive".to_string(),
json!({
"format": "zip",
"url": "https://example.invalid/fixture.zip",
"sha256": sha256_hex(&bytes),
"sizeBytes": bytes.len(),
}),
);
release
}
pub fn sign(payload: &Value) -> Value {
let bytes = serde_json::to_vec(payload).unwrap();
let key = SigningKey::from_bytes(&SIGNING_SEED);
json!({
"schemaVersion": 2,
"payloadEncoding": "base64-json-utf8",
"payloadBase64": BASE64.encode(&bytes),
"payloadSha256": sha256_hex(&bytes),
"signatures": [{
"algorithm": "ed25519",
"keyId": KEY_ID,
"signatureBase64": BASE64.encode(key.sign(&bytes).to_bytes()),
}],
})
}
pub fn write_key(path: &Path) {
let key = SigningKey::from_bytes(&SIGNING_SEED);
let pem = key
.verifying_key()
.to_public_key_pem(LineEnding::LF)
.unwrap();
std::fs::write(
path,
serde_json::to_vec_pretty(&json!({
"algorithm": "ed25519",
"keyId": KEY_ID,
"publicKeyPem": pem,
}))
.unwrap(),
)
.unwrap();
}
pub fn build(
name: &str,
mutate_manifest: impl Fn(&mut Value),
mutate_entries: impl Fn(&mut Vec<Entry>),
mutate_release: impl Fn(&mut Value),
) -> BoxFixture {
let directory = scratch(name);
let mut manifest = box_manifest();
mutate_manifest(&mut manifest);
let mut entries = default_entries(&manifest);
mutate_entries(&mut entries);
let archive_path = directory.join("box.zip");
write_archive(&archive_path, &entries);
let mut release = release_for(&manifest, &archive_path);
mutate_release(&mut release);
let release_path = directory.join("release.json");
std::fs::write(&release_path, serde_json::to_vec_pretty(&sign(&release)).unwrap()).unwrap();
let key_path = directory.join("trusted-key.json");
write_key(&key_path);
BoxFixture {
directory,
archive_path,
release_path,
key_path,
}
}
pub fn valid(name: &str) -> BoxFixture {
build(name, |_| {}, |_| {}, |_| {})
}
pub fn payload_digest_stream_for(entries: &[Entry]) -> Vec<u8> {
let records: Vec<PayloadDigestEntry> = entries
.iter()
.map(|entry| match entry {
Entry::File(name, bytes, _) => PayloadDigestEntry {
path: (*name).to_string(),
kind: PayloadDigestKind::File,
content_sha256: sha256_hex(bytes),
},
Entry::Link(name, target) => PayloadDigestEntry {
path: (*name).to_string(),
kind: PayloadDigestKind::Link,
content_sha256: sha256_hex(target.as_bytes()),
},
})
.collect();
payload_digest_stream(&records).unwrap()
}
pub fn build_with_payload_digest(
name: &str,
mutate_entries: impl Fn(&mut Vec<Entry>),
) -> BoxFixture {
let directory = scratch(name);
let manifest = box_manifest();
let mut entries = default_entries(&manifest);
mutate_entries(&mut entries);
let stream = payload_digest_stream_for(&entries);
let digest = sha256_hex(&stream);
entries.push(Entry::File(PAYLOAD_DIGEST_FILE, stream, 0o644));
let archive_path = directory.join("box.zip");
write_archive(&archive_path, &entries);
let mut release = release_for(&manifest, &archive_path);
release.as_object_mut().unwrap().insert(
"payloadDigest".to_string(),
json!({ "format": "sha256-path-list-v1", "sha256": digest }),
);
let release_path = directory.join("release.json");
std::fs::write(&release_path, serde_json::to_vec_pretty(&sign(&release)).unwrap()).unwrap();
let key_path = directory.join("trusted-key.json");
write_key(&key_path);
BoxFixture {
directory,
archive_path,
release_path,
key_path,
}
}
fn patch_central_directory(
archive: &Path,
entry_name: &str,
mut patch: impl FnMut(&mut [u8]),
) {
const SIGNATURE: [u8; 4] = [b'P', b'K', 1, 2];
const HEADER_LENGTH: usize = 46;
let mut bytes = std::fs::read(archive).unwrap();
let mut cursor = 0usize;
let mut patched = false;
while cursor + HEADER_LENGTH <= bytes.len() {
if bytes[cursor..cursor + 4] != SIGNATURE {
cursor += 1;
continue;
}
let name_length = u16::from_le_bytes([bytes[cursor + 28], bytes[cursor + 29]]) as usize;
let name_start = cursor + HEADER_LENGTH;
if name_start + name_length > bytes.len() {
break;
}
if &bytes[name_start..name_start + name_length] == entry_name.as_bytes() {
patch(&mut bytes[cursor..cursor + HEADER_LENGTH]);
patched = true;
break;
}
cursor = name_start + name_length;
}
assert!(patched, "no central-directory record named {entry_name}");
std::fs::write(archive, bytes).unwrap();
}
pub fn set_entry_file_type(archive: &Path, entry_name: &str, file_type: u32) {
patch_central_directory(archive, entry_name, |header| {
let mode = file_type | 0o644;
header[38..42].copy_from_slice(&(mode << 16).to_le_bytes());
});
}
pub fn mark_entry_encrypted(archive: &Path, entry_name: &str) {
patch_central_directory(archive, entry_name, |header| {
let flag = u16::from_le_bytes([header[8], header[9]]) | 0x0001;
header[8..10].copy_from_slice(&flag.to_le_bytes());
});
}
pub fn rename_entry_bytes(archive: &Path, from: &str, to: &str) {
assert_eq!(
from.len(),
to.len(),
"renaming a ZIP entry in place needs names of equal length"
);
let bytes = std::fs::read(archive).unwrap();
let mut patched = Vec::with_capacity(bytes.len());
let mut cursor = 0usize;
let mut hits = 0usize;
while cursor < bytes.len() {
if bytes[cursor..].starts_with(from.as_bytes()) {
patched.extend_from_slice(to.as_bytes());
cursor += from.len();
hits += 1;
continue;
}
patched.push(bytes[cursor]);
cursor += 1;
}
assert!(hits >= 2, "expected {from} in both headers, found {hits}");
std::fs::write(archive, patched).unwrap();
}