231 lines
8.6 KiB
Rust
231 lines
8.6 KiB
Rust
use std::collections::BTreeSet;
|
|
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_full_and_delta_pair_reuses_shared_static_pool() {
|
|
let script = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("scripts/cir/run_cir_record_full_delta.sh");
|
|
let out_dir = tempfile::tempdir().expect("tempdir");
|
|
let out = out_dir.path().join("cir-pair");
|
|
let fixture_root = out_dir.path().join("fixture");
|
|
std::fs::create_dir_all(&fixture_root).unwrap();
|
|
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 full_obj_hash = {
|
|
use sha2::{Digest, Sha256};
|
|
hex::encode(Sha256::digest(b"full-object"))
|
|
};
|
|
let delta_obj_hash = {
|
|
use sha2::{Digest, Sha256};
|
|
hex::encode(Sha256::digest(b"delta-object"))
|
|
};
|
|
|
|
let full_cir = CanonicalInputRepresentation {
|
|
version: CIR_VERSION_V1,
|
|
hash_alg: CirHashAlgorithm::Sha256,
|
|
validation_time: time::OffsetDateTime::parse(
|
|
"2026-03-16T11:49:15Z",
|
|
&time::format_description::well_known::Rfc3339,
|
|
)
|
|
.unwrap(),
|
|
objects: vec![CirObject {
|
|
rsync_uri: "rsync://example.net/repo/full.roa".to_string(),
|
|
sha256: hex::decode(&full_obj_hash).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 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![
|
|
CirObject {
|
|
rsync_uri: "rsync://example.net/repo/full.roa".to_string(),
|
|
sha256: hex::decode(&full_obj_hash).unwrap(),
|
|
},
|
|
CirObject {
|
|
rsync_uri: "rsync://example.net/repo/delta.roa".to_string(),
|
|
sha256: hex::decode(&delta_obj_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::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();
|
|
|
|
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
|
|
cp "{delta_cir}" "$cir"
|
|
cp "{delta_ccr}" "$ccr"
|
|
cp "{delta_report}" "$report"
|
|
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_obj_hash,
|
|
delta_hash = delta_obj_hash,
|
|
),
|
|
)
|
|
.unwrap();
|
|
std::fs::set_permissions(&stub, std::os::unix::fs::PermissionsExt::from_mode(0o755)).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 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(),
|
|
"--max-depth",
|
|
"0",
|
|
"--max-instances",
|
|
"1",
|
|
"--rpki-bin",
|
|
stub.to_string_lossy().as_ref(),
|
|
])
|
|
.output()
|
|
.expect("run cir record pair");
|
|
assert!(
|
|
proc.status.success(),
|
|
"stderr={}",
|
|
String::from_utf8_lossy(&proc.stderr)
|
|
);
|
|
|
|
let full_cir = rpki::cir::decode_cir(&std::fs::read(out.join("full").join("input.cir")).unwrap())
|
|
.expect("decode full cir");
|
|
let delta_cir =
|
|
rpki::cir::decode_cir(&std::fs::read(out.join("delta-001").join("input.cir")).unwrap())
|
|
.expect("decode delta cir");
|
|
|
|
let mut hashes = BTreeSet::new();
|
|
for item in &full_cir.objects {
|
|
hashes.insert(hex::encode(&item.sha256));
|
|
}
|
|
for item in &delta_cir.objects {
|
|
hashes.insert(hex::encode(&item.sha256));
|
|
}
|
|
|
|
let static_file_count = walk(out.join("static")).len();
|
|
assert_eq!(static_file_count, hashes.len());
|
|
|
|
assert!(out.join("summary.json").is_file());
|
|
assert!(out.join("full").join("result.ccr").is_file());
|
|
assert!(out.join("delta-001").join("result.ccr").is_file());
|
|
}
|
|
|
|
fn walk(path: std::path::PathBuf) -> Vec<std::path::PathBuf> {
|
|
let mut out = Vec::new();
|
|
if path.is_file() {
|
|
out.push(path);
|
|
} else if path.is_dir() {
|
|
for entry in std::fs::read_dir(path).unwrap() {
|
|
out.extend(walk(entry.unwrap().path()));
|
|
}
|
|
}
|
|
out
|
|
}
|