#![allow(dead_code)]
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
pub fn prikk(repo: &Path) -> Command {
let mut cmd = Command::new(env!("CARGO_BIN_EXE_prikk"));
cmd.current_dir(repo);
cmd
}
pub fn ok(output: &Output, what: &str) {
assert!(
output.status.success(),
"{what} failed (status {:?})\nstdout: {}\nstderr: {}",
output.status.code(),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
pub fn unique_suffix() -> String {
static SEQUENCE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
let sequence = SEQUENCE.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
format!("{}-{nanos}-{sequence}", std::process::id())
}
pub fn unique_repo(tag: &str) -> PathBuf {
let mut dir = std::env::temp_dir();
dir.push(format!("prikk-cli-dc67-{tag}-{}", unique_suffix()));
std::fs::create_dir_all(&dir).unwrap();
dir
}
pub const AUTHOR_KEY_ID: &str = "dc67-test-author";
pub const AUTHOR_SEED_HEX: &str =
"3300445566778899001122334455667788990011223344556677889900112233";
pub const MAINTAINER_KEY_ID: &str = "dc67-test-maintainer";
pub const MAINTAINER_SEED: [u8; 32] = [
0x71, 0x71, 0x82, 0x82, 0x93, 0x93, 0xa4, 0xa4, 0xb5, 0xb5, 0xc6, 0xc6, 0xd7, 0xd7, 0xe8, 0xe8,
0xf9, 0xf9, 0x0a, 0x0a, 0x1b, 0x1b, 0x2c, 0x2c, 0x3d, 0x3d, 0x4e, 0x4e, 0x5f, 0x5f, 0x60, 0x60,
];
pub fn hex(bytes: &[u8]) -> String {
bytes.iter().map(|byte| format!("{byte:02x}")).collect()
}
pub fn maintainer_public_key_hex() -> String {
use prikk_store::MaintainerSigner;
let signer =
prikk_store::Ed25519MaintainerSigner::from_seed(MAINTAINER_KEY_ID, &MAINTAINER_SEED)
.expect("fixed maintainer seed derives a valid signer");
hex(&signer.public_key_bytes())
}
pub fn init(repo: &Path) {
ok(&prikk(repo).arg("init").output().unwrap(), "init");
}
pub fn commit(repo: &Path, ref_name: &str, message: &str) -> Output {
prikk(repo)
.env("PRIKK_AUTHOR_KEY_ID", AUTHOR_KEY_ID)
.env("PRIKK_AUTHOR_SEED", AUTHOR_SEED_HEX)
.args(["commit", "--ref", ref_name, "-m", message])
.output()
.unwrap()
}
pub fn trust_maintainer(repo: &Path) {
let _ = prikk(repo)
.args([
"trust",
"maintainer",
"add",
"--key-id",
MAINTAINER_KEY_ID,
"--public-key",
&maintainer_public_key_hex(),
])
.output()
.unwrap();
}
pub fn seal(repo: &Path, ref_name: &str) -> Output {
trust_maintainer(repo);
prikk(repo)
.env("PRIKK_MAINTAINER_KEY_ID", MAINTAINER_KEY_ID)
.env("PRIKK_MAINTAINER_SEED", hex(&MAINTAINER_SEED))
.args(["seal", "--allow-no-audit", "--ref", ref_name])
.output()
.unwrap()
}
pub fn generation(repo: &Path, ref_name: &str, path: &str, content: &[u8], message: &str) {
std::fs::write(repo.join(path), content).unwrap();
ok(
&commit(repo, ref_name, message),
&format!("commit: {message}"),
);
ok(&seal(repo, ref_name), &format!("seal: {message}"));
}
pub fn branch_create(repo: &Path, name: &str, from: &str) -> Output {
trust_maintainer(repo);
prikk(repo)
.env("PRIKK_MAINTAINER_KEY_ID", MAINTAINER_KEY_ID)
.env("PRIKK_MAINTAINER_SEED", hex(&MAINTAINER_SEED))
.args(["branch", "create", name, "--from", from])
.output()
.unwrap()
}
pub fn branch_close(repo: &Path, name: &str) -> Output {
trust_maintainer(repo);
prikk(repo)
.env("PRIKK_MAINTAINER_KEY_ID", MAINTAINER_KEY_ID)
.env("PRIKK_MAINTAINER_SEED", hex(&MAINTAINER_SEED))
.args(["branch", "close", name])
.output()
.unwrap()
}
pub fn tag_create(repo: &Path, name: &str, target: &str) -> Output {
trust_maintainer(repo);
prikk(repo)
.env("PRIKK_MAINTAINER_KEY_ID", MAINTAINER_KEY_ID)
.env("PRIKK_MAINTAINER_SEED", hex(&MAINTAINER_SEED))
.args(["tag", "create", name, "--target", target])
.output()
.unwrap()
}
pub fn verify(repo: &Path) -> Output {
prikk(repo).arg("verify").output().unwrap()
}
pub fn append_torn_ref_log_tail(container_path: &Path) {
const MAGIC: &[u8; 8] = b"PREFCON1";
const HEADER_LEN: usize = 8 + 2 + 32 + 8 + 32;
let bytes = std::fs::read(container_path).unwrap();
let start = bytes
.windows(MAGIC.len())
.rposition(|window| window == MAGIC)
.expect("ref log container has at least one real record to duplicate");
let end = (start + HEADER_LEN + 8).min(bytes.len());
assert!(
end < bytes.len(),
"duplicated span must land inside the real record's body, not consume it entirely, \
or the result would be a complete record rather than a torn one"
);
let torn = bytes[start..end].to_vec();
let mut file = std::fs::OpenOptions::new()
.append(true)
.open(container_path)
.unwrap();
use std::io::Write as _;
file.write_all(&torn).unwrap();
}
pub fn swap_two_equal_length_blob_frames(repo: &Path) {
const MAGIC: &[u8; 8] = b"PCONBLB1";
const HEADER_LEN: usize = 8 + 2 + 8 + 32;
let container_path = repo.join(".prikk/containers/blob/a.container");
let mut bytes = std::fs::read(&container_path).unwrap();
let mut frames = Vec::new();
let mut offset = 0usize;
while offset + HEADER_LEN <= bytes.len() {
assert_eq!(
&bytes[offset..offset + 8],
MAGIC,
"unexpected frame magic at byte offset {offset} in {container_path:?}"
);
let body_len =
u64::from_be_bytes(bytes[offset + 10..offset + 18].try_into().unwrap()) as usize;
let frame_len = HEADER_LEN + body_len;
frames.push((offset, frame_len));
offset += frame_len;
}
assert_eq!(
offset,
bytes.len(),
"blob container has a trailing partial frame -- fixture must seal cleanly first"
);
let mut offsets_by_len: std::collections::HashMap<usize, Vec<usize>> =
std::collections::HashMap::new();
for &(frame_offset, frame_len) in &frames {
offsets_by_len
.entry(frame_len)
.or_default()
.push(frame_offset);
}
let (frame_a, frame_b) = offsets_by_len
.into_values()
.find_map(|offsets| match offsets.as_slice() {
[a, b, ..] => Some((*a, *b)),
_ => None,
})
.expect(
"two blob frames of identical total length to swap -- caller must write two blobs of \
equal byte length before sealing",
);
let frame_len = frames.iter().find(|&&(o, _)| o == frame_a).unwrap().1;
let a_bytes = bytes[frame_a..frame_a + frame_len].to_vec();
let b_bytes = bytes[frame_b..frame_b + frame_len].to_vec();
bytes[frame_a..frame_a + frame_len].copy_from_slice(&b_bytes);
bytes[frame_b..frame_b + frame_len].copy_from_slice(&a_bytes);
std::fs::write(&container_path, &bytes).unwrap();
}
pub fn copy_dir_recursive(src: &Path, dst: &Path) {
std::fs::create_dir_all(dst).unwrap();
for entry in std::fs::read_dir(src).unwrap() {
let entry = entry.unwrap();
let file_type = entry.file_type().unwrap();
let dst_path = dst.join(entry.file_name());
if file_type.is_dir() {
copy_dir_recursive(&entry.path(), &dst_path);
} else {
std::fs::copy(entry.path(), &dst_path).unwrap();
}
}
}
pub fn rebuild_from_sealed_history(repo: &Path, tag: &str) -> PathBuf {
let materialize_root = unique_repo(&format!("{tag}-materialize"));
std::fs::create_dir_all(materialize_root.join(".prikk")).unwrap();
copy_dir_recursive(&repo.join(".prikk"), &materialize_root.join(".prikk"));
let out = prikk(&materialize_root)
.arg("checkout")
.arg("--patch-materialize")
.output()
.unwrap();
ok(&out, "checkout --patch-materialize");
materialize_root
}