rpki/tests/test_cir_sequence_m2.rs

213 lines
8.2 KiB
Rust

use std::path::PathBuf;
use std::process::Command;
use rpki::ccr::{encode_content_info, CcrContentInfo, CcrDigestAlgorithm, RpkiCanonicalCacheRepresentation, TrustAnchorState};
use rpki::cir::{encode_cir, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirTal, CIR_VERSION_V1};
#[test]
fn cir_offline_sequence_writes_parseable_sequence_json_and_steps() {
let out_dir = tempfile::tempdir().expect("tempdir");
let out = out_dir.path().join("cir-sequence");
let script =
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("scripts/cir/run_cir_record_sequence_offline.sh");
let fixture_root = out_dir.path().join("fixture");
let static_payload_root = fixture_root.join("payloads");
std::fs::create_dir_all(&static_payload_root).unwrap();
let base_locks = fixture_root.join("base-locks.json");
let delta_locks = fixture_root.join("locks-delta.json");
std::fs::write(&base_locks, br#"{"validationTime":"2026-03-16T11:49:15Z"}"#).unwrap();
std::fs::write(&delta_locks, br#"{"validationTime":"2026-03-16T11:50:15Z"}"#).unwrap();
let mk_cir = |uri: &str, hash_hex: &str, vt: &str| CanonicalInputRepresentation {
version: CIR_VERSION_V1,
hash_alg: CirHashAlgorithm::Sha256,
validation_time: time::OffsetDateTime::parse(vt, &time::format_description::well_known::Rfc3339).unwrap(),
objects: vec![CirObject {
rsync_uri: uri.to_string(),
sha256: hex::decode(hash_hex).unwrap(),
}],
tals: vec![CirTal {
tal_uri: "https://rpki.apnic.net/tal/apnic-rfc7730-https.tal".to_string(),
tal_bytes: b"rsync://example.net/repo/root.cer\nMIIB".to_vec(),
}],
};
let full_hash = {
use sha2::{Digest, Sha256};
hex::encode(Sha256::digest(b"full-object"))
};
let delta_hash = {
use sha2::{Digest, Sha256};
hex::encode(Sha256::digest(b"delta-object"))
};
let full_cir = mk_cir("rsync://example.net/repo/full.roa", &full_hash, "2026-03-16T11:49:15Z");
let delta_cir = CanonicalInputRepresentation {
version: CIR_VERSION_V1,
hash_alg: CirHashAlgorithm::Sha256,
validation_time: time::OffsetDateTime::parse(
"2026-03-16T11:50:15Z",
&time::format_description::well_known::Rfc3339,
)
.unwrap(),
objects: {
let mut objects = vec![
full_cir.objects[0].clone(),
CirObject {
rsync_uri: "rsync://example.net/repo/delta.roa".to_string(),
sha256: hex::decode(&delta_hash).unwrap(),
},
];
objects.sort_by(|a, b| a.rsync_uri.cmp(&b.rsync_uri));
objects
},
tals: full_cir.tals.clone(),
};
let empty_ccr = CcrContentInfo::new(RpkiCanonicalCacheRepresentation {
version: 0,
hash_alg: CcrDigestAlgorithm::Sha256,
produced_at: full_cir.validation_time,
mfts: None,
vrps: None,
vaps: None,
tas: Some(TrustAnchorState { skis: vec![vec![0x11; 20]], hash: vec![0x22; 32] }),
rks: None,
});
let full_cir_path = fixture_root.join("full.cir");
let delta_cir_path = fixture_root.join("delta.cir");
let full_ccr_path = fixture_root.join("full.ccr");
let delta_ccr_path = fixture_root.join("delta.ccr");
let full_report_path = fixture_root.join("full-report.json");
let delta_report_path = fixture_root.join("delta-report.json");
std::fs::create_dir_all(&fixture_root).unwrap();
std::fs::write(&full_cir_path, encode_cir(&full_cir).unwrap()).unwrap();
std::fs::write(&delta_cir_path, encode_cir(&delta_cir).unwrap()).unwrap();
std::fs::write(&full_ccr_path, encode_content_info(&empty_ccr).unwrap()).unwrap();
std::fs::write(&delta_ccr_path, encode_content_info(&empty_ccr).unwrap()).unwrap();
std::fs::write(&full_report_path, br#"{"format_version":2,"publication_points":[]}"#).unwrap();
std::fs::write(&delta_report_path, br#"{"format_version":2,"publication_points":[]}"#).unwrap();
std::fs::write(static_payload_root.join("full-object"), b"full-object").unwrap();
std::fs::write(static_payload_root.join("delta-object"), b"delta-object").unwrap();
let stub = out_dir.path().join("stub-rpki.sh");
std::fs::write(
&stub,
format!(
r#"#!/usr/bin/env bash
set -euo pipefail
MODE=""
cir=""
ccr=""
report=""
static_root=""
while [[ $# -gt 0 ]]; do
case "$1" in
--payload-replay-archive) MODE="full"; shift 2 ;;
--payload-base-archive) MODE="delta"; shift 2 ;;
--cir-out) cir="$2"; shift 2 ;;
--ccr-out) ccr="$2"; shift 2 ;;
--report-json) report="$2"; shift 2 ;;
--cir-static-root) static_root="$2"; shift 2 ;;
*) shift ;;
esac
done
mkdir -p "$(dirname "$cir")" "$(dirname "$ccr")" "$(dirname "$report")" "$static_root/20260316/{{ab,cd,ef}}/00"
if [[ "$MODE" == "full" ]]; then
cp "{full_cir}" "$cir"
cp "{full_ccr}" "$ccr"
cp "{full_report}" "$report"
install -D -m 0644 "{payload_root}/full-object" "$static_root/20260316/ab/cd/{full_hash}"
else
if [[ "$cir" == *delta-001* ]]; then
cp "{delta_cir}" "$cir"
cp "{delta_ccr}" "$ccr"
cp "{delta_report}" "$report"
else
cp "{delta_cir}" "$cir"
cp "{delta_ccr}" "$ccr"
cp "{delta_report}" "$report"
fi
install -D -m 0644 "{payload_root}/full-object" "$static_root/20260316/ab/cd/{full_hash}"
install -D -m 0644 "{payload_root}/delta-object" "$static_root/20260316/ef/00/{delta_hash}"
fi
"#,
full_cir = full_cir_path.display(),
delta_cir = delta_cir_path.display(),
full_ccr = full_ccr_path.display(),
delta_ccr = delta_ccr_path.display(),
full_report = full_report_path.display(),
delta_report = delta_report_path.display(),
payload_root = static_payload_root.display(),
full_hash = full_hash,
delta_hash = delta_hash,
),
)
.unwrap();
std::fs::set_permissions(&stub, std::os::unix::fs::PermissionsExt::from_mode(0o755)).unwrap();
let proc = Command::new(script)
.args([
"--out-dir",
out.to_string_lossy().as_ref(),
"--tal-path",
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/tal/apnic-rfc7730-https.tal")
.to_string_lossy()
.as_ref(),
"--ta-path",
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/ta/apnic-ta.cer")
.to_string_lossy()
.as_ref(),
"--cir-tal-uri",
"https://rpki.apnic.net/tal/apnic-rfc7730-https.tal",
"--payload-replay-archive",
"/tmp/base-payload-archive",
"--payload-replay-locks",
base_locks.to_string_lossy().as_ref(),
"--payload-base-archive",
"/tmp/base-payload-archive",
"--payload-base-locks",
base_locks.to_string_lossy().as_ref(),
"--payload-delta-archive",
"/tmp/payload-delta-archive",
"--payload-delta-locks",
delta_locks.to_string_lossy().as_ref(),
"--delta-count",
"2",
"--max-depth",
"0",
"--max-instances",
"1",
"--rpki-bin",
stub.to_string_lossy().as_ref(),
])
.output()
.expect("run cir sequence script");
assert!(
proc.status.success(),
"stderr={}",
String::from_utf8_lossy(&proc.stderr)
);
let sequence_json: serde_json::Value =
serde_json::from_slice(&std::fs::read(out.join("sequence.json")).unwrap()).unwrap();
let steps = sequence_json["steps"].as_array().expect("steps");
assert_eq!(steps.len(), 3);
assert_eq!(steps[0]["stepId"], "full");
assert_eq!(steps[1]["stepId"], "delta-001");
assert_eq!(steps[2]["stepId"], "delta-002");
for rel in [
"full/input.cir",
"full/result.ccr",
"full/report.json",
"delta-001/input.cir",
"delta-001/result.ccr",
"delta-001/report.json",
"delta-002/input.cir",
"delta-002/result.ccr",
"delta-002/report.json",
] {
assert!(out.join(rel).is_file(), "missing {}", rel);
}
}