优化数据对象decode + profile validate;benchmark对比routinator geomean 0.8
This commit is contained in:
parent
1cc3351bef
commit
68cbd3c500
@ -9,6 +9,7 @@ default = ["full"]
|
||||
full = ["dep:rocksdb"]
|
||||
|
||||
[dependencies]
|
||||
asn1-rs = "0.7.1"
|
||||
der-parser = { version = "10.0.0", features = ["serialize"] }
|
||||
hex = "0.4.3"
|
||||
base64 = "0.22.1"
|
||||
|
||||
8
benchmark/routinator_object_bench/Cargo.toml
Normal file
8
benchmark/routinator_object_bench/Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "routinator-object-bench"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
rpki = { version = "=0.19.1", features = ["repository"] }
|
||||
552
benchmark/routinator_object_bench/src/main.rs
Normal file
552
benchmark/routinator_object_bench/src/main.rs
Normal file
@ -0,0 +1,552 @@
|
||||
use rpki::repository::cert::Cert;
|
||||
use rpki::repository::crl::Crl;
|
||||
use rpki::repository::manifest::Manifest;
|
||||
use rpki::repository::roa::Roa;
|
||||
use rpki::repository::aspa::Aspa;
|
||||
use rpki::repository::resources::{AsResources, IpResources};
|
||||
|
||||
use std::hint::black_box;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Instant;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum ObjType {
|
||||
Cer,
|
||||
Crl,
|
||||
Manifest,
|
||||
Roa,
|
||||
Aspa,
|
||||
}
|
||||
|
||||
impl ObjType {
|
||||
fn parse(s: &str) -> Result<Self, String> {
|
||||
match s {
|
||||
"cer" => Ok(Self::Cer),
|
||||
"crl" => Ok(Self::Crl),
|
||||
"manifest" => Ok(Self::Manifest),
|
||||
"roa" => Ok(Self::Roa),
|
||||
"aspa" => Ok(Self::Aspa),
|
||||
_ => Err("type must be one of: cer, crl, manifest, roa, aspa".into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
ObjType::Cer => "cer",
|
||||
ObjType::Crl => "crl",
|
||||
ObjType::Manifest => "manifest",
|
||||
ObjType::Roa => "roa",
|
||||
ObjType::Aspa => "aspa",
|
||||
}
|
||||
}
|
||||
|
||||
fn ext(self) -> &'static str {
|
||||
match self {
|
||||
ObjType::Cer => "cer",
|
||||
ObjType::Crl => "crl",
|
||||
ObjType::Manifest => "mft",
|
||||
ObjType::Roa => "roa",
|
||||
ObjType::Aspa => "asa",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Sample {
|
||||
obj_type: ObjType,
|
||||
name: String,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Config {
|
||||
dir: PathBuf,
|
||||
type_filter: Option<ObjType>,
|
||||
sample_filter: Option<String>,
|
||||
fixed_iters: Option<u64>,
|
||||
warmup_iters: u64,
|
||||
rounds: u64,
|
||||
min_round_ms: u64,
|
||||
max_adaptive_iters: u64,
|
||||
strict: bool,
|
||||
cert_inspect: bool,
|
||||
out_csv: Option<PathBuf>,
|
||||
out_md: Option<PathBuf>,
|
||||
}
|
||||
|
||||
fn usage_and_exit(err: Option<&str>) -> ! {
|
||||
if let Some(err) = err {
|
||||
eprintln!("error: {err}");
|
||||
eprintln!();
|
||||
}
|
||||
eprintln!(
|
||||
"Usage:\n\
|
||||
cargo run --release --manifest-path rpki/benchmark/routinator_object_bench/Cargo.toml -- [OPTIONS]\n\
|
||||
\n\
|
||||
Options:\n\
|
||||
--dir <PATH> Fixtures root dir (default: ../../tests/benchmark/selected_der_v2)\n\
|
||||
--type <cer|crl|manifest|roa|aspa> Filter by type\n\
|
||||
--sample <NAME> Filter by sample name (e.g. p50)\n\
|
||||
--iters <N> Fixed iterations per round (optional; otherwise adaptive)\n\
|
||||
--warmup-iters <N> Warmup iterations (default: 50)\n\
|
||||
--rounds <N> Rounds (default: 5)\n\
|
||||
--min-round-ms <MS> Adaptive: minimum round time (default: 200)\n\
|
||||
--max-iters <N> Adaptive: maximum iters (default: 1_000_000)\n\
|
||||
--strict <true|false> Strict DER where applicable (default: true)\n\
|
||||
--cert-inspect Also run Cert::inspect_ca/inspect_ee where applicable (default: false)\n\
|
||||
--out-csv <PATH> Write CSV output\n\
|
||||
--out-md <PATH> Write Markdown output\n\
|
||||
"
|
||||
);
|
||||
std::process::exit(2);
|
||||
}
|
||||
|
||||
fn parse_bool(s: &str, name: &str) -> bool {
|
||||
match s {
|
||||
"1" | "true" | "TRUE" | "yes" | "YES" => true,
|
||||
"0" | "false" | "FALSE" | "no" | "NO" => false,
|
||||
_ => usage_and_exit(Some(&format!("{name} must be true/false"))),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_u64(s: &str, name: &str) -> u64 {
|
||||
s.parse::<u64>()
|
||||
.unwrap_or_else(|_| usage_and_exit(Some(&format!("{name} must be an integer"))))
|
||||
}
|
||||
|
||||
fn default_samples_dir() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../tests/benchmark/selected_der_v2")
|
||||
}
|
||||
|
||||
fn parse_args() -> Config {
|
||||
let mut dir: PathBuf = default_samples_dir();
|
||||
let mut type_filter: Option<ObjType> = None;
|
||||
let mut sample_filter: Option<String> = None;
|
||||
let mut fixed_iters: Option<u64> = None;
|
||||
let mut warmup_iters: u64 = 50;
|
||||
let mut rounds: u64 = 5;
|
||||
let mut min_round_ms: u64 = 200;
|
||||
let mut max_adaptive_iters: u64 = 1_000_000;
|
||||
let mut strict: bool = true;
|
||||
let mut cert_inspect: bool = false;
|
||||
let mut out_csv: Option<PathBuf> = None;
|
||||
let mut out_md: Option<PathBuf> = None;
|
||||
|
||||
let mut args = std::env::args().skip(1);
|
||||
while let Some(arg) = args.next() {
|
||||
match arg.as_str() {
|
||||
"--dir" => dir = PathBuf::from(args.next().unwrap_or_else(|| usage_and_exit(None))),
|
||||
"--type" => {
|
||||
type_filter = Some(ObjType::parse(
|
||||
&args.next().unwrap_or_else(|| usage_and_exit(None)),
|
||||
)
|
||||
.unwrap_or_else(|e| usage_and_exit(Some(&e))))
|
||||
}
|
||||
"--sample" => {
|
||||
sample_filter = Some(args.next().unwrap_or_else(|| usage_and_exit(None)))
|
||||
}
|
||||
"--iters" => {
|
||||
fixed_iters = Some(parse_u64(
|
||||
&args.next().unwrap_or_else(|| usage_and_exit(None)),
|
||||
"--iters",
|
||||
))
|
||||
}
|
||||
"--warmup-iters" => {
|
||||
warmup_iters = parse_u64(
|
||||
&args.next().unwrap_or_else(|| usage_and_exit(None)),
|
||||
"--warmup-iters",
|
||||
)
|
||||
}
|
||||
"--rounds" => {
|
||||
rounds = parse_u64(&args.next().unwrap_or_else(|| usage_and_exit(None)), "--rounds")
|
||||
}
|
||||
"--min-round-ms" => {
|
||||
min_round_ms = parse_u64(
|
||||
&args.next().unwrap_or_else(|| usage_and_exit(None)),
|
||||
"--min-round-ms",
|
||||
)
|
||||
}
|
||||
"--max-iters" => {
|
||||
max_adaptive_iters = parse_u64(
|
||||
&args.next().unwrap_or_else(|| usage_and_exit(None)),
|
||||
"--max-iters",
|
||||
)
|
||||
}
|
||||
"--strict" => {
|
||||
strict = parse_bool(
|
||||
&args.next().unwrap_or_else(|| usage_and_exit(None)),
|
||||
"--strict",
|
||||
)
|
||||
}
|
||||
"--cert-inspect" => cert_inspect = true,
|
||||
"--out-csv" => out_csv = Some(PathBuf::from(args.next().unwrap_or_else(|| usage_and_exit(None)))),
|
||||
"--out-md" => out_md = Some(PathBuf::from(args.next().unwrap_or_else(|| usage_and_exit(None)))),
|
||||
"-h" | "--help" => usage_and_exit(None),
|
||||
_ => usage_and_exit(Some(&format!("unknown argument: {arg}"))),
|
||||
}
|
||||
}
|
||||
|
||||
if warmup_iters == 0 {
|
||||
usage_and_exit(Some("--warmup-iters must be > 0"));
|
||||
}
|
||||
if rounds == 0 {
|
||||
usage_and_exit(Some("--rounds must be > 0"));
|
||||
}
|
||||
if min_round_ms == 0 {
|
||||
usage_and_exit(Some("--min-round-ms must be > 0"));
|
||||
}
|
||||
if max_adaptive_iters == 0 {
|
||||
usage_and_exit(Some("--max-iters must be > 0"));
|
||||
}
|
||||
if let Some(n) = fixed_iters {
|
||||
if n == 0 {
|
||||
usage_and_exit(Some("--iters must be > 0"));
|
||||
}
|
||||
}
|
||||
|
||||
Config {
|
||||
dir,
|
||||
type_filter,
|
||||
sample_filter,
|
||||
fixed_iters,
|
||||
warmup_iters,
|
||||
rounds,
|
||||
min_round_ms,
|
||||
max_adaptive_iters,
|
||||
strict,
|
||||
cert_inspect,
|
||||
out_csv,
|
||||
out_md,
|
||||
}
|
||||
}
|
||||
|
||||
fn read_samples(root: &Path) -> Vec<Sample> {
|
||||
let mut out = Vec::new();
|
||||
for obj_type in [
|
||||
ObjType::Cer,
|
||||
ObjType::Crl,
|
||||
ObjType::Manifest,
|
||||
ObjType::Roa,
|
||||
ObjType::Aspa,
|
||||
] {
|
||||
let dir = root.join(obj_type.as_str());
|
||||
let rd = match std::fs::read_dir(&dir) {
|
||||
Ok(rd) => rd,
|
||||
Err(_) => continue,
|
||||
};
|
||||
for ent in rd.flatten() {
|
||||
let path = ent.path();
|
||||
if path.extension().and_then(|s| s.to_str()) != Some(obj_type.ext()) {
|
||||
continue;
|
||||
}
|
||||
let name = path
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
out.push(Sample { obj_type, name, path });
|
||||
}
|
||||
}
|
||||
out.sort_by(|a, b| a.obj_type.cmp(&b.obj_type).then_with(|| a.name.cmp(&b.name)));
|
||||
out
|
||||
}
|
||||
|
||||
fn choose_iters_adaptive<F: FnMut()>(mut op: F, min_round_ms: u64, max_iters: u64) -> u64 {
|
||||
let min_secs = (min_round_ms as f64) / 1e3;
|
||||
let mut iters: u64 = 1;
|
||||
loop {
|
||||
let start = Instant::now();
|
||||
for _ in 0..iters {
|
||||
op();
|
||||
}
|
||||
let elapsed = start.elapsed().as_secs_f64();
|
||||
if elapsed >= min_secs {
|
||||
return iters;
|
||||
}
|
||||
if iters >= max_iters {
|
||||
return iters;
|
||||
}
|
||||
iters = (iters.saturating_mul(2)).min(max_iters);
|
||||
}
|
||||
}
|
||||
|
||||
fn count_ip(res: &IpResources) -> u64 {
|
||||
if res.is_inherited() {
|
||||
return 1;
|
||||
}
|
||||
let Ok(blocks) = res.to_blocks() else {
|
||||
return 0;
|
||||
};
|
||||
blocks.iter().count() as u64
|
||||
}
|
||||
|
||||
fn count_as(res: &AsResources) -> u64 {
|
||||
if res.is_inherited() {
|
||||
return 1;
|
||||
}
|
||||
let Ok(blocks) = res.to_blocks() else {
|
||||
return 0;
|
||||
};
|
||||
blocks.iter().count() as u64
|
||||
}
|
||||
|
||||
fn complexity(obj_type: ObjType, bytes: &[u8], strict: bool, cert_inspect: bool) -> u64 {
|
||||
match obj_type {
|
||||
ObjType::Cer => {
|
||||
let cert = Cert::decode(bytes).expect("decode cert");
|
||||
if cert_inspect {
|
||||
if cert.is_ca() {
|
||||
cert.inspect_ca(strict).expect("inspect ca");
|
||||
} else {
|
||||
cert.inspect_ee(strict).expect("inspect ee");
|
||||
}
|
||||
}
|
||||
count_ip(cert.v4_resources())
|
||||
.saturating_add(count_ip(cert.v6_resources()))
|
||||
.saturating_add(count_as(cert.as_resources()))
|
||||
}
|
||||
ObjType::Crl => {
|
||||
let crl = Crl::decode(bytes).expect("decode crl");
|
||||
crl.revoked_certs().iter().count() as u64
|
||||
}
|
||||
ObjType::Manifest => {
|
||||
let mft = Manifest::decode(bytes, strict).expect("decode manifest");
|
||||
if cert_inspect {
|
||||
mft.cert().inspect_ee(strict).expect("inspect ee");
|
||||
}
|
||||
mft.content().len() as u64
|
||||
}
|
||||
ObjType::Roa => {
|
||||
let roa = Roa::decode(bytes, strict).expect("decode roa");
|
||||
if cert_inspect {
|
||||
roa.cert().inspect_ee(strict).expect("inspect ee");
|
||||
}
|
||||
roa.content().iter().count() as u64
|
||||
}
|
||||
ObjType::Aspa => {
|
||||
let asa = Aspa::decode(bytes, strict).expect("decode aspa");
|
||||
if cert_inspect {
|
||||
asa.cert().inspect_ee(strict).expect("inspect ee");
|
||||
}
|
||||
asa.content().provider_as_set().len() as u64
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_profile(obj_type: ObjType, bytes: &[u8], strict: bool, cert_inspect: bool) {
|
||||
match obj_type {
|
||||
ObjType::Cer => {
|
||||
let cert = Cert::decode(black_box(bytes)).expect("decode cert");
|
||||
if cert_inspect {
|
||||
if cert.is_ca() {
|
||||
cert.inspect_ca(strict).expect("inspect ca");
|
||||
} else {
|
||||
cert.inspect_ee(strict).expect("inspect ee");
|
||||
}
|
||||
}
|
||||
black_box(cert);
|
||||
}
|
||||
ObjType::Crl => {
|
||||
let crl = Crl::decode(black_box(bytes)).expect("decode crl");
|
||||
black_box(crl);
|
||||
}
|
||||
ObjType::Manifest => {
|
||||
let mft = Manifest::decode(black_box(bytes), strict).expect("decode manifest");
|
||||
if cert_inspect {
|
||||
mft.cert().inspect_ee(strict).expect("inspect ee");
|
||||
}
|
||||
black_box(mft);
|
||||
}
|
||||
ObjType::Roa => {
|
||||
let roa = Roa::decode(black_box(bytes), strict).expect("decode roa");
|
||||
if cert_inspect {
|
||||
roa.cert().inspect_ee(strict).expect("inspect ee");
|
||||
}
|
||||
black_box(roa);
|
||||
}
|
||||
ObjType::Aspa => {
|
||||
let asa = Aspa::decode(black_box(bytes), strict).expect("decode aspa");
|
||||
if cert_inspect {
|
||||
asa.cert().inspect_ee(strict).expect("inspect ee");
|
||||
}
|
||||
black_box(asa);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct ResultRow {
|
||||
obj_type: String,
|
||||
sample: String,
|
||||
size_bytes: usize,
|
||||
complexity: u64,
|
||||
avg_ns_per_op: f64,
|
||||
ops_per_sec: f64,
|
||||
}
|
||||
|
||||
fn render_markdown(title: &str, rows: &[ResultRow]) -> String {
|
||||
let mut out = String::new();
|
||||
out.push_str(&format!("# {title}\n\n"));
|
||||
out.push_str("| type | sample | size_bytes | complexity | avg ns/op | ops/s |\n");
|
||||
out.push_str("|---|---|---:|---:|---:|---:|\n");
|
||||
for r in rows {
|
||||
out.push_str(&format!(
|
||||
"| {} | {} | {} | {} | {:.2} | {:.2} |\n",
|
||||
r.obj_type, r.sample, r.size_bytes, r.complexity, r.avg_ns_per_op, r.ops_per_sec
|
||||
));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn render_csv(rows: &[ResultRow]) -> String {
|
||||
let mut out = String::new();
|
||||
out.push_str("type,sample,size_bytes,complexity,avg_ns_per_op,ops_per_sec\n");
|
||||
for r in rows {
|
||||
let sample = r.sample.replace('"', "\"\"");
|
||||
out.push_str(&format!(
|
||||
"{},{},{},{},{:.6},{:.6}\n",
|
||||
r.obj_type,
|
||||
format!("\"{}\"", sample),
|
||||
r.size_bytes,
|
||||
r.complexity,
|
||||
r.avg_ns_per_op,
|
||||
r.ops_per_sec
|
||||
));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn create_parent_dirs(path: &Path) {
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent).unwrap_or_else(|e| {
|
||||
panic!("create_dir_all {}: {e}", parent.display());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn write_text_file(path: &Path, content: &str) {
|
||||
create_parent_dirs(path);
|
||||
std::fs::write(path, content).unwrap_or_else(|e| panic!("write {}: {e}", path.display()));
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cfg = parse_args();
|
||||
let mut samples = read_samples(&cfg.dir);
|
||||
if samples.is_empty() {
|
||||
usage_and_exit(Some(&format!(
|
||||
"no samples found under: {}",
|
||||
cfg.dir.display()
|
||||
)));
|
||||
}
|
||||
|
||||
if let Some(t) = cfg.type_filter {
|
||||
samples.retain(|s| s.obj_type == t);
|
||||
if samples.is_empty() {
|
||||
usage_and_exit(Some(&format!("no sample matched --type {}", t.as_str())));
|
||||
}
|
||||
}
|
||||
if let Some(filter) = cfg.sample_filter.as_deref() {
|
||||
samples.retain(|s| s.name == filter);
|
||||
if samples.is_empty() {
|
||||
usage_and_exit(Some(&format!("no sample matched --sample {filter}")));
|
||||
}
|
||||
}
|
||||
|
||||
println!("# Routinator baseline (rpki crate) decode benchmark (selected_der_v2)");
|
||||
println!();
|
||||
println!("- dir: {}", cfg.dir.display());
|
||||
println!("- strict: {}", cfg.strict);
|
||||
println!("- cert_inspect: {}", cfg.cert_inspect);
|
||||
if let Some(t) = cfg.type_filter {
|
||||
println!("- type: {}", t.as_str());
|
||||
}
|
||||
if let Some(s) = cfg.sample_filter.as_deref() {
|
||||
println!("- sample: {}", s);
|
||||
}
|
||||
if let Some(n) = cfg.fixed_iters {
|
||||
println!("- iters: {} (fixed)", n);
|
||||
} else {
|
||||
println!(
|
||||
"- warmup: {} iters, rounds: {}, min_round: {}ms (adaptive iters, max {})",
|
||||
cfg.warmup_iters, cfg.rounds, cfg.min_round_ms, cfg.max_adaptive_iters
|
||||
);
|
||||
}
|
||||
if let Some(p) = cfg.out_csv.as_ref() {
|
||||
println!("- out_csv: {}", p.display());
|
||||
}
|
||||
if let Some(p) = cfg.out_md.as_ref() {
|
||||
println!("- out_md: {}", p.display());
|
||||
}
|
||||
println!();
|
||||
|
||||
println!("| type | sample | size_bytes | complexity | avg ns/op | ops/s |");
|
||||
println!("|---|---|---:|---:|---:|---:|");
|
||||
|
||||
let mut rows: Vec<ResultRow> = Vec::with_capacity(samples.len());
|
||||
for sample in &samples {
|
||||
let bytes = std::fs::read(&sample.path)
|
||||
.unwrap_or_else(|e| panic!("read {}: {e}", sample.path.display()));
|
||||
let size_bytes = bytes.len();
|
||||
let complexity = complexity(sample.obj_type, bytes.as_slice(), cfg.strict, cfg.cert_inspect);
|
||||
|
||||
for _ in 0..cfg.warmup_iters {
|
||||
decode_profile(sample.obj_type, bytes.as_slice(), cfg.strict, cfg.cert_inspect);
|
||||
}
|
||||
|
||||
let mut per_round_ns_per_op = Vec::with_capacity(cfg.rounds as usize);
|
||||
for _round in 0..cfg.rounds {
|
||||
let iters = if let Some(n) = cfg.fixed_iters {
|
||||
n
|
||||
} else {
|
||||
choose_iters_adaptive(
|
||||
|| decode_profile(sample.obj_type, bytes.as_slice(), cfg.strict, cfg.cert_inspect),
|
||||
cfg.min_round_ms,
|
||||
cfg.max_adaptive_iters,
|
||||
)
|
||||
};
|
||||
let start = Instant::now();
|
||||
for _ in 0..iters {
|
||||
decode_profile(sample.obj_type, bytes.as_slice(), cfg.strict, cfg.cert_inspect);
|
||||
}
|
||||
let elapsed = start.elapsed();
|
||||
let total_ns = elapsed.as_secs_f64() * 1e9;
|
||||
per_round_ns_per_op.push(total_ns / (iters as f64));
|
||||
}
|
||||
|
||||
let avg_ns = per_round_ns_per_op.iter().sum::<f64>() / (per_round_ns_per_op.len() as f64);
|
||||
let ops_per_sec = 1e9_f64 / avg_ns;
|
||||
|
||||
println!(
|
||||
"| {} | {} | {} | {} | {:.2} | {:.2} |",
|
||||
sample.obj_type.as_str(),
|
||||
sample.name,
|
||||
size_bytes,
|
||||
complexity,
|
||||
avg_ns,
|
||||
ops_per_sec
|
||||
);
|
||||
|
||||
rows.push(ResultRow {
|
||||
obj_type: sample.obj_type.as_str().to_string(),
|
||||
sample: sample.name.clone(),
|
||||
size_bytes,
|
||||
complexity,
|
||||
avg_ns_per_op: avg_ns,
|
||||
ops_per_sec,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(path) = cfg.out_md.as_ref() {
|
||||
let md = render_markdown(
|
||||
"Routinator baseline (rpki crate) decode+inspect (selected_der_v2)",
|
||||
&rows,
|
||||
);
|
||||
write_text_file(path, &md);
|
||||
eprintln!("Wrote {}", path.display());
|
||||
}
|
||||
if let Some(path) = cfg.out_csv.as_ref() {
|
||||
let csv = render_csv(&rows);
|
||||
write_text_file(path, &csv);
|
||||
eprintln!("Wrote {}", path.display());
|
||||
}
|
||||
}
|
||||
123
scripts/stage2_perf_compare_m4.sh
Executable file
123
scripts/stage2_perf_compare_m4.sh
Executable file
@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# M4: Compare decode+profile (OURS) vs routinator baseline (rpki crate 0.19.1)
|
||||
# on selected_der_v2 fixtures (cer/crl/manifest/roa/aspa).
|
||||
#
|
||||
# Outputs under:
|
||||
# - rpki/target/bench/stage2_selected_der_v2_routinator_decode_release.{csv,md}
|
||||
# - rpki/target/bench/stage2_selected_der_v2_compare_ours_vs_routinator_decode_release.{csv,md}
|
||||
#
|
||||
# Notes:
|
||||
# - OURS decode benchmark is produced by:
|
||||
# `cargo test --release --test bench_stage2_decode_profile_selected_der_v2 -- --ignored --nocapture`
|
||||
# and writes `stage2_selected_der_v2_decode_release.csv` when BENCH_OUT_CSV is set.
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
RPKI_DIR="$ROOT_DIR"
|
||||
|
||||
OUT_DIR="$RPKI_DIR/target/bench"
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
OURS_CSV="${OURS_CSV:-$OUT_DIR/stage2_selected_der_v2_decode_release.csv}"
|
||||
|
||||
ROUT_CSV="${ROUT_CSV:-$OUT_DIR/stage2_selected_der_v2_routinator_decode_release.csv}"
|
||||
ROUT_MD="${ROUT_MD:-$OUT_DIR/stage2_selected_der_v2_routinator_decode_release.md}"
|
||||
|
||||
COMPARE_CSV="${COMPARE_CSV:-$OUT_DIR/stage2_selected_der_v2_compare_ours_vs_routinator_decode_release.csv}"
|
||||
COMPARE_MD="${COMPARE_MD:-$OUT_DIR/stage2_selected_der_v2_compare_ours_vs_routinator_decode_release.md}"
|
||||
|
||||
WARMUP_ITERS="${WARMUP_ITERS:-10}"
|
||||
ROUNDS="${ROUNDS:-3}"
|
||||
MIN_ROUND_MS="${MIN_ROUND_MS:-200}"
|
||||
|
||||
if [[ ! -f "$OURS_CSV" ]]; then
|
||||
echo "ERROR: missing OURS CSV: $OURS_CSV" >&2
|
||||
echo "Hint: run:" >&2
|
||||
echo " cd rpki && BENCH_WARMUP_ITERS=$WARMUP_ITERS BENCH_ROUNDS=$ROUNDS BENCH_MIN_ROUND_MS=$MIN_ROUND_MS \\" >&2
|
||||
echo " BENCH_OUT_CSV=target/bench/stage2_selected_der_v2_decode_release.csv \\" >&2
|
||||
echo " cargo test --release --test bench_stage2_decode_profile_selected_der_v2 -- --ignored --nocapture" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
echo "[1/2] Run routinator baseline bench (release)..." >&2
|
||||
(cd "$RPKI_DIR/benchmark/routinator_object_bench" && cargo run --release -q -- \
|
||||
--dir "$RPKI_DIR/tests/benchmark/selected_der_v2" \
|
||||
--warmup-iters "$WARMUP_ITERS" \
|
||||
--rounds "$ROUNDS" \
|
||||
--min-round-ms "$MIN_ROUND_MS" \
|
||||
--out-csv "$ROUT_CSV" \
|
||||
--out-md "$ROUT_MD")
|
||||
|
||||
echo "[2/2] Join CSVs + compute ratios..." >&2
|
||||
python3 - "$OURS_CSV" "$ROUT_CSV" "$COMPARE_CSV" "$COMPARE_MD" <<'PY'
|
||||
import csv
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ours_path = Path(sys.argv[1])
|
||||
rout_path = Path(sys.argv[2])
|
||||
out_csv_path = Path(sys.argv[3])
|
||||
out_md_path = Path(sys.argv[4])
|
||||
|
||||
def read_csv(path: Path):
|
||||
with path.open(newline="") as f:
|
||||
return list(csv.DictReader(f))
|
||||
|
||||
ours_rows = read_csv(ours_path)
|
||||
rout_rows = read_csv(rout_path)
|
||||
|
||||
rout_by_key = {(r["type"], r["sample"]): r for r in rout_rows}
|
||||
|
||||
out_rows = []
|
||||
for r in ours_rows:
|
||||
key = (r["type"], r["sample"])
|
||||
rr = rout_by_key.get(key)
|
||||
if rr is None:
|
||||
raise SystemExit(f"missing routinator row for {key}")
|
||||
|
||||
ours_ns = float(r["avg_ns_per_op"])
|
||||
rout_ns = float(rr["avg_ns_per_op"])
|
||||
ratio = (ours_ns / rout_ns) if rout_ns != 0.0 else float("inf")
|
||||
|
||||
out_rows.append({
|
||||
"type": r["type"],
|
||||
"sample": r["sample"],
|
||||
"size_bytes": r["size_bytes"],
|
||||
"complexity": r["complexity"],
|
||||
"ours_avg_ns_per_op": f"{ours_ns:.2f}",
|
||||
"ours_ops_per_sec": f"{float(r['ops_per_sec']):.2f}",
|
||||
"rout_avg_ns_per_op": f"{rout_ns:.2f}",
|
||||
"rout_ops_per_sec": f"{float(rr['ops_per_sec']):.2f}",
|
||||
"ratio_ours_over_rout": f"{ratio:.4f}",
|
||||
})
|
||||
|
||||
out_rows.sort(key=lambda x: (x["type"], x["sample"]))
|
||||
|
||||
out_csv_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with out_csv_path.open("w", newline="") as f:
|
||||
w = csv.DictWriter(f, fieldnames=list(out_rows[0].keys()))
|
||||
w.writeheader()
|
||||
w.writerows(out_rows)
|
||||
|
||||
lines = []
|
||||
lines.append("# Stage2 ours vs routinator (decode+profile, selected_der_v2)\n")
|
||||
lines.append(f"- ours_csv: `{ours_path}`\n")
|
||||
lines.append(f"- rout_csv: `{rout_path}`\n")
|
||||
lines.append("\n")
|
||||
lines.append("| type | sample | size_bytes | complexity | ours ns/op | rout ns/op | ratio |\n")
|
||||
lines.append("|---|---|---:|---:|---:|---:|---:|\n")
|
||||
for r in out_rows:
|
||||
lines.append(
|
||||
f"| {r['type']} | {r['sample']} | {r['size_bytes']} | {r['complexity']} | "
|
||||
f"{r['ours_avg_ns_per_op']} | {r['rout_avg_ns_per_op']} | {r['ratio_ours_over_rout']} |\n"
|
||||
)
|
||||
|
||||
out_md_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
out_md_path.write_text("".join(lines), encoding="utf-8")
|
||||
PY
|
||||
|
||||
echo "Done." >&2
|
||||
echo "- routinator CSV: $ROUT_CSV" >&2
|
||||
echo "- compare CSV: $COMPARE_CSV" >&2
|
||||
echo "- compare MD: $COMPARE_MD" >&2
|
||||
@ -4601,6 +4601,925 @@
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": false
|
||||
},
|
||||
{
|
||||
"id": "nz1R30GbU3V27e8siGpj0",
|
||||
"type": "text",
|
||||
"x": 163.01948306749506,
|
||||
"y": 1851.404535362782,
|
||||
"width": 474.99176025390625,
|
||||
"height": 35,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "b17",
|
||||
"roundness": null,
|
||||
"seed": 1508242701,
|
||||
"version": 456,
|
||||
"versionNonce": 167514925,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1772001228159,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "V2 Parallel processing, multi worker",
|
||||
"fontSize": 28,
|
||||
"fontFamily": 5,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "V2 Parallel processing, multi worker",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "NIt4aSy4mw5_vhn-d1TAT",
|
||||
"type": "rectangle",
|
||||
"x": 355.40690519895884,
|
||||
"y": 2085.1589227446907,
|
||||
"width": 273.0000305175781,
|
||||
"height": 73.3333740234375,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "b18",
|
||||
"roundness": null,
|
||||
"seed": 1210072707,
|
||||
"version": 273,
|
||||
"versionNonce": 330766435,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1772001252588,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "a_hHz-mHKABQQ_yB-RBIj",
|
||||
"type": "rectangle",
|
||||
"x": 367.0735922106776,
|
||||
"y": 2092.4923272857063,
|
||||
"width": 34,
|
||||
"height": 57.33331298828125,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "b19",
|
||||
"roundness": null,
|
||||
"seed": 658208291,
|
||||
"version": 200,
|
||||
"versionNonce": 1922831363,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1772001252588,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "eSio7xtVYJ4ar8wKPeH8d",
|
||||
"type": "rectangle",
|
||||
"x": 582.0735616930995,
|
||||
"y": 2092.492296768128,
|
||||
"width": 34,
|
||||
"height": 57.33331298828125,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "b1A",
|
||||
"roundness": null,
|
||||
"seed": 1498331587,
|
||||
"version": 270,
|
||||
"versionNonce": 245406627,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1772001252588,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "rf9t-wepa665vlJ95Mk85",
|
||||
"type": "text",
|
||||
"x": 469.7402487048182,
|
||||
"y": 2107.158953262269,
|
||||
"width": 27.399978637695312,
|
||||
"height": 25,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "b1B",
|
||||
"roundness": null,
|
||||
"seed": 1534626147,
|
||||
"version": 189,
|
||||
"versionNonce": 871575363,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1772001252588,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": ".....",
|
||||
"fontSize": 20,
|
||||
"fontFamily": 5,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": ".....",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "DQYVJDhTuFzzErQYWLhaI",
|
||||
"type": "rectangle",
|
||||
"x": 357.53235615026176,
|
||||
"y": 2195.678869684095,
|
||||
"width": 273.0000305175781,
|
||||
"height": 73.3333740234375,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "b1C",
|
||||
"roundness": null,
|
||||
"seed": 1540194797,
|
||||
"version": 336,
|
||||
"versionNonce": 379213443,
|
||||
"isDeleted": false,
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "cx18t9XNHa3SxhzWynvq8",
|
||||
"type": "arrow"
|
||||
}
|
||||
],
|
||||
"updated": 1772001381161,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "F8lD1sLFlDaKou9hjWJYH",
|
||||
"type": "rectangle",
|
||||
"x": 369.1990431619805,
|
||||
"y": 2203.0122742251106,
|
||||
"width": 34,
|
||||
"height": 57.33331298828125,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "b1D",
|
||||
"roundness": null,
|
||||
"seed": 2080422989,
|
||||
"version": 262,
|
||||
"versionNonce": 1666833389,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1772001314228,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "J-xIvXgkMjPMHt6t6QsU5",
|
||||
"type": "rectangle",
|
||||
"x": 584.1990126444024,
|
||||
"y": 2203.0122437075324,
|
||||
"width": 34,
|
||||
"height": 57.33331298828125,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "b1E",
|
||||
"roundness": null,
|
||||
"seed": 2057153197,
|
||||
"version": 332,
|
||||
"versionNonce": 1156699725,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1772001314228,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "RCjALdkMhYpWm5oLR6Fhg",
|
||||
"type": "text",
|
||||
"x": 471.86569965612114,
|
||||
"y": 2217.678900201673,
|
||||
"width": 27.399978637695312,
|
||||
"height": 25,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "b1F",
|
||||
"roundness": null,
|
||||
"seed": 1213058317,
|
||||
"version": 251,
|
||||
"versionNonce": 2052818093,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1772001314228,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": ".....",
|
||||
"fontSize": 20,
|
||||
"fontFamily": 5,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": ".....",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "aaiPz7Zgg6qrhotinSLMg",
|
||||
"type": "rectangle",
|
||||
"x": 357.5324047964888,
|
||||
"y": 2307.2616393916046,
|
||||
"width": 273.0000305175781,
|
||||
"height": 73.3333740234375,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "b1G",
|
||||
"roundness": null,
|
||||
"seed": 949960483,
|
||||
"version": 326,
|
||||
"versionNonce": 1452020173,
|
||||
"isDeleted": false,
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "1y4vVTwY-t4qgzTFGRHkw",
|
||||
"type": "arrow"
|
||||
}
|
||||
],
|
||||
"updated": 1772001385208,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "tQQePVBD3t4lhQvSw-a3x",
|
||||
"type": "rectangle",
|
||||
"x": 369.1990918082075,
|
||||
"y": 2314.5950439326202,
|
||||
"width": 34,
|
||||
"height": 57.33331298828125,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "b1H",
|
||||
"roundness": null,
|
||||
"seed": 2102485699,
|
||||
"version": 252,
|
||||
"versionNonce": 1087497251,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1772001320310,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "czuCaayGMFyZ8Tx-nkBi4",
|
||||
"type": "rectangle",
|
||||
"x": 584.1990612906294,
|
||||
"y": 2314.595013415042,
|
||||
"width": 34,
|
||||
"height": 57.33331298828125,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "b1I",
|
||||
"roundness": null,
|
||||
"seed": 457052771,
|
||||
"version": 322,
|
||||
"versionNonce": 300378051,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1772001320310,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "LYQgQkkRnMtDIMw-XVjGv",
|
||||
"type": "text",
|
||||
"x": 471.86574830234815,
|
||||
"y": 2329.2616699091827,
|
||||
"width": 27.399978637695312,
|
||||
"height": 25,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "b1J",
|
||||
"roundness": null,
|
||||
"seed": 590597635,
|
||||
"version": 241,
|
||||
"versionNonce": 415987555,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1772001320310,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": ".....",
|
||||
"fontSize": 20,
|
||||
"fontFamily": 5,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": ".....",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "VXaedscRg23gtj4VAfx3n",
|
||||
"type": "ellipse",
|
||||
"x": 757.5801654506954,
|
||||
"y": 2205.7782954366144,
|
||||
"width": 156.21587759051351,
|
||||
"height": 91.39166671731073,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffc9c9",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "b1K",
|
||||
"roundness": {
|
||||
"type": 2
|
||||
},
|
||||
"seed": 71609603,
|
||||
"version": 93,
|
||||
"versionNonce": 180497411,
|
||||
"isDeleted": false,
|
||||
"boundElements": [
|
||||
{
|
||||
"type": "text",
|
||||
"id": "UdgzV1ZK-Eu4Ic_-Dzbun"
|
||||
},
|
||||
{
|
||||
"id": "u5himsZ3owA4D1i_lwkWo",
|
||||
"type": "arrow"
|
||||
},
|
||||
{
|
||||
"id": "GVzVTq13ZZTqxbwQA1LJt",
|
||||
"type": "arrow"
|
||||
}
|
||||
],
|
||||
"updated": 1772001524347,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "UdgzV1ZK-Eu4Ic_-Dzbun",
|
||||
"type": "text",
|
||||
"x": 798.1074831027793,
|
||||
"y": 2239.162295155394,
|
||||
"width": 74.69993591308594,
|
||||
"height": 25,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffc9c9",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "b1L",
|
||||
"roundness": null,
|
||||
"seed": 1863312717,
|
||||
"version": 12,
|
||||
"versionNonce": 1551635843,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1772001370485,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "shuffler",
|
||||
"fontSize": 20,
|
||||
"fontFamily": 5,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": "VXaedscRg23gtj4VAfx3n",
|
||||
"originalText": "shuffler",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "Y2RlD_fFLoEw5QJ5QcMR0",
|
||||
"type": "arrow",
|
||||
"x": 650.2482003533373,
|
||||
"y": 2131.3897498674564,
|
||||
"width": 94.57974585181091,
|
||||
"height": 74.38854556915794,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffc9c9",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "b1M",
|
||||
"roundness": {
|
||||
"type": 2
|
||||
},
|
||||
"seed": 849259149,
|
||||
"version": 40,
|
||||
"versionNonce": 1370636813,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1772001376501,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
94.57974585181091,
|
||||
74.38854556915794
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": null,
|
||||
"endBinding": null,
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": false
|
||||
},
|
||||
{
|
||||
"id": "cx18t9XNHa3SxhzWynvq8",
|
||||
"type": "arrow",
|
||||
"x": 648.1228466944885,
|
||||
"y": 2231.2829285126163,
|
||||
"width": 85.01541115585599,
|
||||
"height": 13.814944721198572,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffc9c9",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "b1N",
|
||||
"roundness": {
|
||||
"type": 2
|
||||
},
|
||||
"seed": 865630317,
|
||||
"version": 33,
|
||||
"versionNonce": 921414371,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1772001381161,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
85.01541115585599,
|
||||
13.814944721198572
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "DQYVJDhTuFzzErQYWLhaI",
|
||||
"focus": -0.4435544400037994,
|
||||
"gap": 17.590460026648657
|
||||
},
|
||||
"endBinding": null,
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": false
|
||||
},
|
||||
{
|
||||
"id": "1y4vVTwY-t4qgzTFGRHkw",
|
||||
"type": "arrow",
|
||||
"x": 649.1855235239129,
|
||||
"y": 2337.552168134323,
|
||||
"width": 85.01545980208311,
|
||||
"height": 40.38220598039834,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffc9c9",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "b1O",
|
||||
"roundness": {
|
||||
"type": 2
|
||||
},
|
||||
"seed": 371136909,
|
||||
"version": 42,
|
||||
"versionNonce": 1853196141,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1772001385208,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
85.01545980208311,
|
||||
-40.38220598039834
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "aaiPz7Zgg6qrhotinSLMg",
|
||||
"focus": 0.6632382229237758,
|
||||
"gap": 14
|
||||
},
|
||||
"endBinding": null,
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": false
|
||||
},
|
||||
{
|
||||
"id": "4BHU-kOejBKiEg7WbwFrO",
|
||||
"type": "text",
|
||||
"x": 700.9183968092904,
|
||||
"y": 2105.0747078538043,
|
||||
"width": 130.55987548828125,
|
||||
"height": 50,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "b1P",
|
||||
"roundness": null,
|
||||
"seed": 1849944301,
|
||||
"version": 257,
|
||||
"versionNonce": 1581124931,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1772001406467,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "child\nCA Instances",
|
||||
"fontSize": 20,
|
||||
"fontFamily": 5,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "child\nCA Instances",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "u5himsZ3owA4D1i_lwkWo",
|
||||
"type": "arrow",
|
||||
"x": 892.5422145753583,
|
||||
"y": 2296.107139385819,
|
||||
"width": 639.7412048820175,
|
||||
"height": 215.7267529628216,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffc9c9",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "b1Q",
|
||||
"roundness": {
|
||||
"type": 2
|
||||
},
|
||||
"seed": 1943525507,
|
||||
"version": 158,
|
||||
"versionNonce": 1536842819,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1772001434749,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
-78.63925288685539,
|
||||
215.7267529628216
|
||||
],
|
||||
[
|
||||
-639.7412048820175,
|
||||
189.15949170362182
|
||||
],
|
||||
[
|
||||
-560.0392021963971,
|
||||
59.510875372307964
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "VXaedscRg23gtj4VAfx3n",
|
||||
"focus": -0.7716335982433741,
|
||||
"gap": 11.587857989220094
|
||||
},
|
||||
"endBinding": null,
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": false
|
||||
},
|
||||
{
|
||||
"id": "H9brdXKovhIkuVtj3tFS1",
|
||||
"type": "arrow",
|
||||
"x": 226.23367546480065,
|
||||
"y": 2442.758779572832,
|
||||
"width": 108.39469057300934,
|
||||
"height": 199.78625999786573,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffc9c9",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "b1S",
|
||||
"roundness": {
|
||||
"type": 2
|
||||
},
|
||||
"seed": 1940678531,
|
||||
"version": 160,
|
||||
"versionNonce": 655212835,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1772001444761,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
108.39469057300934,
|
||||
-199.78625999786573
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": null,
|
||||
"endBinding": null,
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": false
|
||||
},
|
||||
{
|
||||
"id": "k_HeiQKWqjcVaepIiUy53",
|
||||
"type": "arrow",
|
||||
"x": 224.1082974828383,
|
||||
"y": 2424.6931275339357,
|
||||
"width": 106.269312591047,
|
||||
"height": 295.42873132532804,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffc9c9",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "b1T",
|
||||
"roundness": {
|
||||
"type": 2
|
||||
},
|
||||
"seed": 2104759395,
|
||||
"version": 41,
|
||||
"versionNonce": 213595907,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1772001452767,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
106.269312591047,
|
||||
-295.42873132532804
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": null,
|
||||
"endBinding": null,
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": false
|
||||
},
|
||||
{
|
||||
"id": "_JLEThxgSltZ1tj_es8Pd",
|
||||
"type": "text",
|
||||
"x": 962.6484905842728,
|
||||
"y": 2092.3223913158026,
|
||||
"width": 187.73983764648438,
|
||||
"height": 25,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "b1V",
|
||||
"roundness": null,
|
||||
"seed": 1067471331,
|
||||
"version": 121,
|
||||
"versionNonce": 8657635,
|
||||
"isDeleted": false,
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "GVzVTq13ZZTqxbwQA1LJt",
|
||||
"type": "arrow"
|
||||
}
|
||||
],
|
||||
"updated": 1772001539469,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "TALs CA Instances",
|
||||
"fontSize": 20,
|
||||
"fontFamily": 5,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "TALs CA Instances",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "GVzVTq13ZZTqxbwQA1LJt",
|
||||
"type": "arrow",
|
||||
"x": 1076.8824351390615,
|
||||
"y": 2131.3223913158026,
|
||||
"width": 158.6413207145456,
|
||||
"height": 83.92501467310194,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffc9c9",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": "b1W",
|
||||
"roundness": {
|
||||
"type": 2
|
||||
},
|
||||
"seed": 2140227085,
|
||||
"version": 39,
|
||||
"versionNonce": 1940178563,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1772001539470,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
-158.6413207145456,
|
||||
83.92501467310194
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "_JLEThxgSltZ1tj_es8Pd",
|
||||
"focus": -0.604699684061838,
|
||||
"gap": 14
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "VXaedscRg23gtj4VAfx3n",
|
||||
"focus": 0.10814148120478088,
|
||||
"gap": 20.516226547403985
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": false
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
|
||||
19
src/audit.rs
19
src/audit.rs
@ -133,28 +133,23 @@ pub fn sha256_hex(bytes: &[u8]) -> String {
|
||||
}
|
||||
|
||||
pub fn format_roa_ip_prefix(p: &crate::data_model::roa::IpPrefix) -> String {
|
||||
let addr = p.addr_bytes();
|
||||
match p.afi {
|
||||
crate::data_model::roa::RoaAfi::Ipv4 => {
|
||||
if p.addr.len() != 4 {
|
||||
return format!("ipv4:{:02X?}/{}", p.addr, p.prefix_len);
|
||||
}
|
||||
format!(
|
||||
"{}.{}.{}.{}{}",
|
||||
p.addr[0],
|
||||
p.addr[1],
|
||||
p.addr[2],
|
||||
p.addr[3],
|
||||
addr[0],
|
||||
addr[1],
|
||||
addr[2],
|
||||
addr[3],
|
||||
format!("/{}", p.prefix_len)
|
||||
)
|
||||
}
|
||||
crate::data_model::roa::RoaAfi::Ipv6 => {
|
||||
if p.addr.len() != 16 {
|
||||
return format!("ipv6:{:02X?}/{}", p.addr, p.prefix_len);
|
||||
}
|
||||
let mut parts = Vec::with_capacity(8);
|
||||
for i in 0..8 {
|
||||
let hi = p.addr[i * 2] as u16;
|
||||
let lo = p.addr[i * 2 + 1] as u16;
|
||||
let hi = addr[i * 2] as u16;
|
||||
let lo = addr[i * 2 + 1] as u16;
|
||||
parts.push(format!("{:x}", (hi << 8) | lo));
|
||||
}
|
||||
format!("{}{}", parts.join(":"), format!("/{}", p.prefix_len))
|
||||
|
||||
@ -612,7 +612,7 @@ mod tests {
|
||||
prefix: crate::data_model::roa::IpPrefix {
|
||||
afi: crate::data_model::roa::RoaAfi::Ipv4,
|
||||
prefix_len: 24,
|
||||
addr: vec![192, 0, 2, 0],
|
||||
addr: [192, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
},
|
||||
max_length: 24,
|
||||
}],
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
use crate::data_model::oid::OID_CT_ASPA;
|
||||
use crate::data_model::common::{DerReader, der_take_tlv};
|
||||
use crate::data_model::rc::ResourceCertificate;
|
||||
use crate::data_model::signed_object::{
|
||||
RpkiSignedObject, RpkiSignedObjectParsed, SignedObjectParseError, SignedObjectValidateError,
|
||||
};
|
||||
use der_parser::ber::Class;
|
||||
use der_parser::der::{DerObject, Tag, parse_der};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct AspaObject {
|
||||
@ -203,7 +202,7 @@ impl AspaObject {
|
||||
impl AspaEContent {
|
||||
/// Parse step of scheme A (`parse → validate → verify`).
|
||||
pub fn parse_der(der: &[u8]) -> Result<AspaEContentParsed, AspaParseError> {
|
||||
let (rem, _obj) = parse_der(der).map_err(|e| AspaParseError::Parse(e.to_string()))?;
|
||||
let (_tag, _value, rem) = der_take_tlv(der).map_err(AspaParseError::Parse)?;
|
||||
if !rem.is_empty() {
|
||||
return Err(AspaParseError::TrailingBytes(rem.len()));
|
||||
}
|
||||
@ -292,47 +291,66 @@ impl AspaObjectParsed {
|
||||
|
||||
impl AspaEContentParsed {
|
||||
pub fn validate_profile(self) -> Result<AspaEContent, AspaProfileError> {
|
||||
let (_rem, obj) =
|
||||
parse_der(&self.der).map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
|
||||
fn count_elements(mut r: DerReader<'_>) -> Result<usize, String> {
|
||||
let mut n = 0usize;
|
||||
while !r.is_empty() {
|
||||
r.skip_any()?;
|
||||
n += 1;
|
||||
}
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
let seq = obj
|
||||
.as_sequence()
|
||||
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
|
||||
if seq.len() != 3 {
|
||||
let mut r = DerReader::new(&self.der);
|
||||
let mut seq = r
|
||||
.take_sequence()
|
||||
.map_err(|e| AspaProfileError::ProfileDecode(e))?;
|
||||
if !r.is_empty() {
|
||||
return Err(AspaProfileError::ProfileDecode(
|
||||
"trailing bytes after ASProviderAttestation".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let elem_count =
|
||||
count_elements(seq).map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
|
||||
if elem_count != 3 {
|
||||
return Err(AspaProfileError::InvalidAttestationSequence);
|
||||
}
|
||||
|
||||
// version [0] EXPLICIT INTEGER MUST be present and MUST be 1.
|
||||
let v_obj = &seq[0];
|
||||
if v_obj.class() != Class::ContextSpecific || v_obj.tag() != Tag(0) {
|
||||
if seq
|
||||
.peek_tag()
|
||||
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?
|
||||
!= 0xA0
|
||||
{
|
||||
return Err(AspaProfileError::VersionMustBeExplicitOne);
|
||||
}
|
||||
let inner_der = v_obj
|
||||
.as_slice()
|
||||
let (inner_tag, inner_val) = seq
|
||||
.take_explicit(0xA0)
|
||||
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
|
||||
let (rem, inner) =
|
||||
parse_der(inner_der).map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
|
||||
if !rem.is_empty() {
|
||||
return Err(AspaProfileError::ProfileDecode(
|
||||
"trailing bytes inside ASProviderAttestation.version".into(),
|
||||
));
|
||||
if inner_tag != 0x02 {
|
||||
return Err(AspaProfileError::VersionMustBeExplicitOne);
|
||||
}
|
||||
let v = inner
|
||||
.as_u64()
|
||||
let v = crate::data_model::common::der_uint_from_bytes(inner_val)
|
||||
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
|
||||
if v != 1 {
|
||||
return Err(AspaProfileError::VersionMustBeExplicitOne);
|
||||
}
|
||||
|
||||
let customer_u64 = seq[1]
|
||||
.as_u64()
|
||||
let customer_u64 = seq
|
||||
.take_uint_u64()
|
||||
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
|
||||
if customer_u64 > u32::MAX as u64 {
|
||||
return Err(AspaProfileError::CustomerAsIdOutOfRange(customer_u64));
|
||||
}
|
||||
let customer_as_id = customer_u64 as u32;
|
||||
|
||||
let providers = parse_providers(&seq[2], customer_as_id)?;
|
||||
let providers_seq = seq
|
||||
.take_sequence()
|
||||
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
|
||||
if !seq.is_empty() {
|
||||
return Err(AspaProfileError::InvalidAttestationSequence);
|
||||
}
|
||||
let providers = parse_providers_cursor(providers_seq, customer_as_id)?;
|
||||
|
||||
Ok(AspaEContent {
|
||||
version: 1,
|
||||
@ -342,19 +360,19 @@ impl AspaEContentParsed {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_providers(obj: &DerObject<'_>, customer_as_id: u32) -> Result<Vec<u32>, AspaProfileError> {
|
||||
let seq = obj
|
||||
.as_sequence()
|
||||
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
|
||||
fn parse_providers_cursor(
|
||||
mut seq: DerReader<'_>,
|
||||
customer_as_id: u32,
|
||||
) -> Result<Vec<u32>, AspaProfileError> {
|
||||
if seq.is_empty() {
|
||||
return Err(AspaProfileError::EmptyProviders);
|
||||
}
|
||||
|
||||
let mut out: Vec<u32> = Vec::with_capacity(seq.len());
|
||||
let mut out: Vec<u32> = Vec::new();
|
||||
let mut prev: Option<u32> = None;
|
||||
for item in seq {
|
||||
let v = item
|
||||
.as_u64()
|
||||
while !seq.is_empty() {
|
||||
let v = seq
|
||||
.take_uint_u64()
|
||||
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
|
||||
if v > u32::MAX as u64 {
|
||||
return Err(AspaProfileError::ProviderAsIdOutOfRange(v));
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
use x509_parser::asn1_rs::Tag;
|
||||
use x509_parser::x509::AlgorithmIdentifier;
|
||||
use x509_parser::prelude::FromDer;
|
||||
|
||||
pub type UtcTime = time::OffsetDateTime;
|
||||
|
||||
@ -100,6 +101,190 @@ pub fn algorithm_params_absent_or_null(sig: &AlgorithmIdentifier<'_>) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Take a single DER TLV (Tag-Length-Value) from the start of `input`.
|
||||
///
|
||||
/// This helper supports:
|
||||
/// - short- and long-form lengths (up to 8 length bytes)
|
||||
/// - only low-tag-number form tags (no high-tag-number form)
|
||||
/// - definite length only (DER forbids indefinite length)
|
||||
///
|
||||
/// Returns: `(tag_byte, value_bytes, remaining_bytes)`.
|
||||
pub(crate) fn der_take_tlv(input: &[u8]) -> Result<(u8, &[u8], &[u8]), String> {
|
||||
if input.len() < 2 {
|
||||
return Err("truncated DER (need tag+len)".into());
|
||||
}
|
||||
let tag = input[0];
|
||||
if (tag & 0x1F) == 0x1F {
|
||||
return Err("high-tag-number form not supported".into());
|
||||
}
|
||||
let len0 = input[1];
|
||||
if len0 == 0x80 {
|
||||
return Err("indefinite length not allowed in DER".into());
|
||||
}
|
||||
let (len, hdr_len) = if len0 & 0x80 == 0 {
|
||||
(len0 as usize, 2usize)
|
||||
} else {
|
||||
let n = (len0 & 0x7F) as usize;
|
||||
if n == 0 || n > 8 {
|
||||
return Err("invalid DER length".into());
|
||||
}
|
||||
if input.len() < 2 + n {
|
||||
return Err("truncated DER (length bytes)".into());
|
||||
}
|
||||
let mut l: usize = 0;
|
||||
for &b in &input[2..2 + n] {
|
||||
l = (l << 8) | (b as usize);
|
||||
}
|
||||
(l, 2 + n)
|
||||
};
|
||||
if input.len() < hdr_len + len {
|
||||
return Err("truncated DER (value bytes)".into());
|
||||
}
|
||||
let value = &input[hdr_len..hdr_len + len];
|
||||
let rem = &input[hdr_len + len..];
|
||||
Ok((tag, value, rem))
|
||||
}
|
||||
|
||||
/// Minimal streaming DER reader built on `der_take_tlv`.
|
||||
///
|
||||
/// This is intentionally small and only supports the subset of DER needed by
|
||||
/// RPKI object eContent decoders (ROA/ASPA), to avoid constructing a generic AST
|
||||
/// (which is expensive on large objects such as ROAs with thousands of prefixes).
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct DerReader<'a> {
|
||||
buf: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> DerReader<'a> {
|
||||
pub(crate) fn new(buf: &'a [u8]) -> Self {
|
||||
Self { buf }
|
||||
}
|
||||
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.buf.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn remaining_len(&self) -> usize {
|
||||
self.buf.len()
|
||||
}
|
||||
|
||||
pub(crate) fn peek_tag(&self) -> Result<u8, String> {
|
||||
self.buf.first().copied().ok_or_else(|| "truncated DER".into())
|
||||
}
|
||||
|
||||
pub(crate) fn take_any(&mut self) -> Result<(u8, &'a [u8]), String> {
|
||||
let (tag, value, rem) = der_take_tlv(self.buf)?;
|
||||
self.buf = rem;
|
||||
Ok((tag, value))
|
||||
}
|
||||
|
||||
pub(crate) fn take_any_full(&mut self) -> Result<(u8, &'a [u8], &'a [u8]), String> {
|
||||
let (tag, value, rem) = der_take_tlv(self.buf)?;
|
||||
let consumed = self.buf.len() - rem.len();
|
||||
let full = &self.buf[..consumed];
|
||||
self.buf = rem;
|
||||
Ok((tag, full, value))
|
||||
}
|
||||
|
||||
pub(crate) fn skip_any(&mut self) -> Result<(), String> {
|
||||
let _ = self.take_any()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn take_tag(&mut self, expected_tag: u8) -> Result<&'a [u8], String> {
|
||||
let (tag, value) = self.take_any()?;
|
||||
if tag != expected_tag {
|
||||
return Err(format!(
|
||||
"unexpected tag: got 0x{tag:02X}, expected 0x{expected_tag:02X}"
|
||||
));
|
||||
}
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub(crate) fn take_sequence(&mut self) -> Result<DerReader<'a>, String> {
|
||||
let value = self.take_tag(0x30)?;
|
||||
Ok(DerReader::new(value))
|
||||
}
|
||||
|
||||
pub(crate) fn take_octet_string(&mut self) -> Result<&'a [u8], String> {
|
||||
self.take_tag(0x04)
|
||||
}
|
||||
|
||||
pub(crate) fn take_bit_string(&mut self) -> Result<(u8, &'a [u8]), String> {
|
||||
let v = self.take_tag(0x03)?;
|
||||
if v.is_empty() {
|
||||
return Err("BIT STRING content is empty".into());
|
||||
}
|
||||
Ok((v[0], &v[1..]))
|
||||
}
|
||||
|
||||
pub(crate) fn take_uint_u64(&mut self) -> Result<u64, String> {
|
||||
let v = self.take_tag(0x02)?;
|
||||
der_uint_from_bytes(v)
|
||||
}
|
||||
|
||||
pub(crate) fn take_explicit(&mut self, expected_outer_tag: u8) -> Result<(u8, &'a [u8]), String> {
|
||||
let inner_der = self.take_tag(expected_outer_tag)?;
|
||||
let (tag, value, rem) = der_take_tlv(inner_der)?;
|
||||
if !rem.is_empty() {
|
||||
return Err("trailing bytes inside EXPLICIT value".into());
|
||||
}
|
||||
Ok((tag, value))
|
||||
}
|
||||
|
||||
pub(crate) fn take_explicit_der(&mut self, expected_outer_tag: u8) -> Result<&'a [u8], String> {
|
||||
let inner_der = self.take_tag(expected_outer_tag)?;
|
||||
let (_tag, _value, rem) = der_take_tlv(inner_der)?;
|
||||
if !rem.is_empty() {
|
||||
return Err("trailing bytes inside EXPLICIT value".into());
|
||||
}
|
||||
Ok(inner_der)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn der_uint_from_bytes(bytes: &[u8]) -> Result<u64, String> {
|
||||
if bytes.is_empty() {
|
||||
return Err("INTEGER has empty content".into());
|
||||
}
|
||||
// Disallow negative values.
|
||||
if (bytes[0] & 0x80) != 0 {
|
||||
return Err("INTEGER is negative".into());
|
||||
}
|
||||
// DER requires minimal encoding for INTEGER.
|
||||
if bytes.len() > 1 && bytes[0] == 0x00 && (bytes[1] & 0x80) == 0 {
|
||||
return Err("INTEGER not minimally encoded".into());
|
||||
}
|
||||
if bytes.len() > 8 {
|
||||
return Err("INTEGER does not fit u64".into());
|
||||
}
|
||||
let mut v: u64 = 0;
|
||||
for &b in bytes {
|
||||
v = (v << 8) | (b as u64);
|
||||
}
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct X509NameDer(pub Vec<u8>);
|
||||
|
||||
impl X509NameDer {
|
||||
pub fn as_raw(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for X509NameDer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Ok((rem, name)) = x509_parser::x509::X509Name::from_der(&self.0) else {
|
||||
return write!(f, "<invalid X.509 Name DER>");
|
||||
};
|
||||
if !rem.is_empty() {
|
||||
return write!(f, "<invalid X.509 Name DER (trailing bytes)>");
|
||||
}
|
||||
write!(f, "{name}")
|
||||
}
|
||||
}
|
||||
|
||||
/// Filename extensions registered in IANA "RPKI Repository Name Schemes".
|
||||
///
|
||||
/// Source: <https://www.iana.org/assignments/rpki/rpki.xhtml>
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
pub use crate::data_model::common::{Asn1TimeEncoding, Asn1TimeUtc, BigUnsigned};
|
||||
use crate::data_model::oid::{
|
||||
OID_AUTHORITY_KEY_IDENTIFIER, OID_CRL_NUMBER, OID_SHA256_WITH_RSA_ENCRYPTION,
|
||||
OID_SUBJECT_KEY_IDENTIFIER,
|
||||
OID_AUTHORITY_KEY_IDENTIFIER, OID_AUTHORITY_KEY_IDENTIFIER_RAW, OID_CRL_NUMBER,
|
||||
OID_CRL_NUMBER_RAW, OID_SHA256_WITH_RSA_ENCRYPTION, OID_SHA256_WITH_RSA_ENCRYPTION_RAW,
|
||||
OID_SUBJECT_KEY_IDENTIFIER_RAW,
|
||||
};
|
||||
use x509_parser::certificate::X509Certificate;
|
||||
use x509_parser::extensions::{ParsedExtension, X509Extension};
|
||||
@ -425,8 +426,15 @@ fn algorithm_identifier_value(ai: &AlgorithmIdentifier<'_>) -> AlgorithmIdentifi
|
||||
tag: p.tag(),
|
||||
data: p.as_bytes().to_vec(),
|
||||
});
|
||||
// NOTE(perf): Avoid `to_id_string()` allocations for the signature algorithms we expect
|
||||
// in RPKI CRLs. Fall back to `to_id_string()` for unexpected algorithms (mostly error paths).
|
||||
let oid = if ai.algorithm.as_bytes() == OID_SHA256_WITH_RSA_ENCRYPTION_RAW {
|
||||
OID_SHA256_WITH_RSA_ENCRYPTION.to_string()
|
||||
} else {
|
||||
ai.algorithm.to_id_string()
|
||||
};
|
||||
AlgorithmIdentifierValue {
|
||||
oid: ai.algorithm.to_id_string(),
|
||||
oid,
|
||||
parameters,
|
||||
}
|
||||
}
|
||||
@ -434,9 +442,8 @@ fn algorithm_identifier_value(ai: &AlgorithmIdentifier<'_>) -> AlgorithmIdentifi
|
||||
fn parse_extensions_parse(exts: &[X509Extension<'_>]) -> Result<Vec<CrlExtensionParsed>, String> {
|
||||
let mut out = Vec::with_capacity(exts.len());
|
||||
for ext in exts {
|
||||
let oid = ext.oid.to_id_string();
|
||||
match oid.as_str() {
|
||||
OID_AUTHORITY_KEY_IDENTIFIER => {
|
||||
let oid = ext.oid.as_bytes();
|
||||
if oid == OID_AUTHORITY_KEY_IDENTIFIER_RAW {
|
||||
let ParsedExtension::AuthorityKeyIdentifier(aki) = ext.parsed_extension() else {
|
||||
return Err("AKI extension parse failed".to_string());
|
||||
};
|
||||
@ -446,18 +453,19 @@ fn parse_extensions_parse(exts: &[X509Extension<'_>]) -> Result<Vec<CrlExtension
|
||||
|| aki.authority_cert_serial.is_some(),
|
||||
critical: ext.critical,
|
||||
});
|
||||
}
|
||||
OID_CRL_NUMBER => match ext.parsed_extension() {
|
||||
} else if oid == OID_CRL_NUMBER_RAW {
|
||||
match ext.parsed_extension() {
|
||||
ParsedExtension::CRLNumber(n) => out.push(CrlExtensionParsed::CrlNumber {
|
||||
number: n.clone(),
|
||||
critical: ext.critical,
|
||||
}),
|
||||
_ => return Err("CRLNumber extension parse failed".to_string()),
|
||||
},
|
||||
_ => out.push(CrlExtensionParsed::Other {
|
||||
oid,
|
||||
}
|
||||
} else {
|
||||
out.push(CrlExtensionParsed::Other {
|
||||
oid: ext.oid.to_id_string(),
|
||||
critical: ext.critical,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
@ -525,7 +533,7 @@ fn validate_extensions_profile(
|
||||
fn get_subject_key_identifier(cert: &X509Certificate<'_>) -> Option<Vec<u8>> {
|
||||
cert.extensions()
|
||||
.iter()
|
||||
.find(|ext| ext.oid.to_id_string() == OID_SUBJECT_KEY_IDENTIFIER)
|
||||
.find(|ext| ext.oid.as_bytes() == OID_SUBJECT_KEY_IDENTIFIER_RAW)
|
||||
.and_then(|ext| match ext.parsed_extension() {
|
||||
ParsedExtension::SubjectKeyIdentifier(ki) => Some(ki.0.to_vec()),
|
||||
_ => None,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use crate::data_model::common::{BigUnsigned, UtcTime};
|
||||
use crate::data_model::common::der_take_tlv;
|
||||
use crate::data_model::oid::{OID_CT_RPKI_MANIFEST, OID_SHA256};
|
||||
use crate::data_model::rc::ResourceCertificate;
|
||||
use crate::data_model::signed_object::{
|
||||
@ -821,42 +822,6 @@ fn oid_content_iter(bytes: &[u8]) -> impl Iterator<Item = u64> + '_ {
|
||||
}
|
||||
}
|
||||
|
||||
fn der_take_tlv(input: &[u8]) -> Result<(u8, &[u8], &[u8]), String> {
|
||||
if input.len() < 2 {
|
||||
return Err("truncated DER (need tag+len)".into());
|
||||
}
|
||||
let tag = input[0];
|
||||
if (tag & 0x1F) == 0x1F {
|
||||
return Err("high-tag-number form not supported".into());
|
||||
}
|
||||
let len0 = input[1];
|
||||
if len0 == 0x80 {
|
||||
return Err("indefinite length not allowed in DER".into());
|
||||
}
|
||||
let (len, hdr_len) = if len0 & 0x80 == 0 {
|
||||
(len0 as usize, 2usize)
|
||||
} else {
|
||||
let n = (len0 & 0x7F) as usize;
|
||||
if n == 0 || n > 8 {
|
||||
return Err("invalid DER length".into());
|
||||
}
|
||||
if input.len() < 2 + n {
|
||||
return Err("truncated DER (length bytes)".into());
|
||||
}
|
||||
let mut l: usize = 0;
|
||||
for &b in &input[2..2 + n] {
|
||||
l = (l << 8) | (b as usize);
|
||||
}
|
||||
(l, 2 + n)
|
||||
};
|
||||
if input.len() < hdr_len + len {
|
||||
return Err("truncated DER (value bytes)".into());
|
||||
}
|
||||
let value = &input[hdr_len..hdr_len + len];
|
||||
let rem = &input[hdr_len + len..];
|
||||
Ok((tag, value, rem))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@ -1,42 +1,72 @@
|
||||
pub const OID_SHA256: &str = "2.16.840.1.101.3.4.2.1";
|
||||
pub const OID_SHA256_RAW: &[u8] = &asn1_rs::oid!(raw 2.16.840.1.101.3.4.2.1);
|
||||
|
||||
pub const OID_SIGNED_DATA: &str = "1.2.840.113549.1.7.2";
|
||||
pub const OID_SIGNED_DATA_RAW: &[u8] = &asn1_rs::oid!(raw 1.2.840.113549.1.7.2);
|
||||
|
||||
pub const OID_CMS_ATTR_CONTENT_TYPE: &str = "1.2.840.113549.1.9.3";
|
||||
pub const OID_CMS_ATTR_CONTENT_TYPE_RAW: &[u8] = &asn1_rs::oid!(raw 1.2.840.113549.1.9.3);
|
||||
pub const OID_CMS_ATTR_MESSAGE_DIGEST: &str = "1.2.840.113549.1.9.4";
|
||||
pub const OID_CMS_ATTR_MESSAGE_DIGEST_RAW: &[u8] = &asn1_rs::oid!(raw 1.2.840.113549.1.9.4);
|
||||
pub const OID_CMS_ATTR_SIGNING_TIME: &str = "1.2.840.113549.1.9.5";
|
||||
pub const OID_CMS_ATTR_SIGNING_TIME_RAW: &[u8] = &asn1_rs::oid!(raw 1.2.840.113549.1.9.5);
|
||||
|
||||
pub const OID_RSA_ENCRYPTION: &str = "1.2.840.113549.1.1.1";
|
||||
pub const OID_RSA_ENCRYPTION_RAW: &[u8] = &asn1_rs::oid!(raw 1.2.840.113549.1.1.1);
|
||||
pub const OID_SHA256_WITH_RSA_ENCRYPTION: &str = "1.2.840.113549.1.1.11";
|
||||
pub const OID_SHA256_WITH_RSA_ENCRYPTION_RAW: &[u8] = &asn1_rs::oid!(raw 1.2.840.113549.1.1.11);
|
||||
|
||||
// X.509 extensions (RFC 5280 / RFC 6487)
|
||||
pub const OID_BASIC_CONSTRAINTS: &str = "2.5.29.19";
|
||||
pub const OID_BASIC_CONSTRAINTS_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.19);
|
||||
pub const OID_KEY_USAGE: &str = "2.5.29.15";
|
||||
pub const OID_KEY_USAGE_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.15);
|
||||
pub const OID_EXTENDED_KEY_USAGE: &str = "2.5.29.37";
|
||||
pub const OID_EXTENDED_KEY_USAGE_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.37);
|
||||
pub const OID_CRL_DISTRIBUTION_POINTS: &str = "2.5.29.31";
|
||||
pub const OID_CRL_DISTRIBUTION_POINTS_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.31);
|
||||
pub const OID_AUTHORITY_INFO_ACCESS: &str = "1.3.6.1.5.5.7.1.1";
|
||||
pub const OID_AUTHORITY_INFO_ACCESS_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.1.1);
|
||||
pub const OID_CERTIFICATE_POLICIES: &str = "2.5.29.32";
|
||||
pub const OID_CERTIFICATE_POLICIES_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.32);
|
||||
|
||||
pub const OID_AUTHORITY_KEY_IDENTIFIER: &str = "2.5.29.35";
|
||||
pub const OID_AUTHORITY_KEY_IDENTIFIER_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.35);
|
||||
pub const OID_CRL_NUMBER: &str = "2.5.29.20";
|
||||
pub const OID_CRL_NUMBER_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.20);
|
||||
pub const OID_SUBJECT_KEY_IDENTIFIER: &str = "2.5.29.14";
|
||||
pub const OID_SUBJECT_KEY_IDENTIFIER_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.14);
|
||||
|
||||
pub const OID_CT_RPKI_MANIFEST: &str = "1.2.840.113549.1.9.16.1.26";
|
||||
pub const OID_CT_RPKI_MANIFEST_RAW: &[u8] =
|
||||
&asn1_rs::oid!(raw 1.2.840.113549.1.9.16.1.26);
|
||||
pub const OID_CT_ROUTE_ORIGIN_AUTHZ: &str = "1.2.840.113549.1.9.16.1.24";
|
||||
pub const OID_CT_ROUTE_ORIGIN_AUTHZ_RAW: &[u8] =
|
||||
&asn1_rs::oid!(raw 1.2.840.113549.1.9.16.1.24);
|
||||
pub const OID_CT_ASPA: &str = "1.2.840.113549.1.9.16.1.49";
|
||||
pub const OID_CT_ASPA_RAW: &[u8] = &asn1_rs::oid!(raw 1.2.840.113549.1.9.16.1.49);
|
||||
|
||||
// X.509 extensions / access methods (RFC 5280 / RFC 6487)
|
||||
pub const OID_SUBJECT_INFO_ACCESS: &str = "1.3.6.1.5.5.7.1.11";
|
||||
pub const OID_SUBJECT_INFO_ACCESS_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.1.11);
|
||||
pub const OID_AD_SIGNED_OBJECT: &str = "1.3.6.1.5.5.7.48.11";
|
||||
pub const OID_AD_SIGNED_OBJECT_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.48.11);
|
||||
|
||||
pub const OID_AD_CA_ISSUERS: &str = "1.3.6.1.5.5.7.48.2";
|
||||
pub const OID_AD_CA_ISSUERS_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.48.2);
|
||||
pub const OID_AD_CA_REPOSITORY: &str = "1.3.6.1.5.5.7.48.5";
|
||||
pub const OID_AD_CA_REPOSITORY_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.48.5);
|
||||
pub const OID_AD_RPKI_MANIFEST: &str = "1.3.6.1.5.5.7.48.10";
|
||||
pub const OID_AD_RPKI_MANIFEST_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.48.10);
|
||||
pub const OID_AD_RPKI_NOTIFY: &str = "1.3.6.1.5.5.7.48.13";
|
||||
pub const OID_AD_RPKI_NOTIFY_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.48.13);
|
||||
|
||||
// RFC 3779 resource extensions (RFC 6487 profile)
|
||||
pub const OID_IP_ADDR_BLOCKS: &str = "1.3.6.1.5.5.7.1.7";
|
||||
pub const OID_IP_ADDR_BLOCKS_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.1.7);
|
||||
pub const OID_AUTONOMOUS_SYS_IDS: &str = "1.3.6.1.5.5.7.1.8";
|
||||
pub const OID_AUTONOMOUS_SYS_IDS_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.1.8);
|
||||
|
||||
// RPKI CP (RFC 6484 / RFC 6487)
|
||||
pub const OID_CP_IPADDR_ASNUMBER: &str = "1.3.6.1.5.5.7.14.2";
|
||||
pub const OID_CP_IPADDR_ASNUMBER_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.14.2);
|
||||
|
||||
@ -1,19 +1,21 @@
|
||||
use der_parser::ber::{BerObjectContent, Class};
|
||||
use der_parser::der::{DerObject, Tag, parse_der};
|
||||
use der_parser::num_bigint::BigUint;
|
||||
use url::Url;
|
||||
use x509_parser::asn1_rs::{Class as Asn1Class, Tag as Asn1Tag};
|
||||
use x509_parser::extensions::ParsedExtension;
|
||||
use x509_parser::prelude::{FromDer, X509Certificate, X509Extension, X509Version};
|
||||
|
||||
use crate::data_model::common::{
|
||||
Asn1TimeUtc, InvalidTimeEncodingError, UtcTime, asn1_time_to_model,
|
||||
Asn1TimeUtc, InvalidTimeEncodingError, UtcTime, X509NameDer, asn1_time_to_model,
|
||||
};
|
||||
use crate::data_model::oid::{
|
||||
OID_AD_CA_ISSUERS, OID_AD_SIGNED_OBJECT, OID_AUTHORITY_INFO_ACCESS,
|
||||
OID_AUTHORITY_KEY_IDENTIFIER, OID_AUTONOMOUS_SYS_IDS, OID_CP_IPADDR_ASNUMBER,
|
||||
OID_CRL_DISTRIBUTION_POINTS, OID_IP_ADDR_BLOCKS, OID_SHA256_WITH_RSA_ENCRYPTION,
|
||||
OID_SUBJECT_INFO_ACCESS, OID_SUBJECT_KEY_IDENTIFIER,
|
||||
OID_AD_CA_ISSUERS_RAW, OID_AD_CA_REPOSITORY, OID_AD_CA_REPOSITORY_RAW, OID_AD_RPKI_MANIFEST,
|
||||
OID_AD_RPKI_MANIFEST_RAW, OID_AD_RPKI_NOTIFY, OID_AD_RPKI_NOTIFY_RAW, OID_AD_SIGNED_OBJECT,
|
||||
OID_AD_SIGNED_OBJECT_RAW, OID_AUTHORITY_INFO_ACCESS_RAW, OID_AUTHORITY_KEY_IDENTIFIER_RAW,
|
||||
OID_AUTONOMOUS_SYS_IDS_RAW, OID_BASIC_CONSTRAINTS_RAW, OID_CERTIFICATE_POLICIES_RAW,
|
||||
OID_CP_IPADDR_ASNUMBER, OID_CP_IPADDR_ASNUMBER_RAW, OID_CRL_DISTRIBUTION_POINTS_RAW,
|
||||
OID_IP_ADDR_BLOCKS_RAW, OID_SHA256_WITH_RSA_ENCRYPTION, OID_SHA256_WITH_RSA_ENCRYPTION_RAW,
|
||||
OID_SUBJECT_INFO_ACCESS_RAW, OID_SUBJECT_KEY_IDENTIFIER_RAW,
|
||||
};
|
||||
|
||||
/// Resource Certificate kind (semantic classification).
|
||||
@ -43,8 +45,8 @@ pub struct RpkixTbsCertificate {
|
||||
pub version: u32,
|
||||
pub serial_number: BigUint,
|
||||
pub signature_algorithm: String,
|
||||
pub issuer_dn: String,
|
||||
pub subject_dn: String,
|
||||
pub issuer_name: X509NameDer,
|
||||
pub subject_name: X509NameDer,
|
||||
pub validity_not_before: UtcTime,
|
||||
pub validity_not_after: UtcTime,
|
||||
/// DER encoding of SubjectPublicKeyInfo.
|
||||
@ -59,9 +61,9 @@ pub struct RcExtensions {
|
||||
/// Authority Key Identifier (AKI) keyIdentifier value.
|
||||
pub authority_key_identifier: Option<Vec<u8>>,
|
||||
/// CRL Distribution Points URIs (fullName).
|
||||
pub crl_distribution_points_uris: Option<Vec<Url>>,
|
||||
pub crl_distribution_points_uris: Option<Vec<String>>,
|
||||
/// Authority Information Access (AIA) caIssuers URIs.
|
||||
pub ca_issuers_uris: Option<Vec<Url>>,
|
||||
pub ca_issuers_uris: Option<Vec<String>>,
|
||||
pub subject_info_access: Option<SubjectInfoAccess>,
|
||||
pub certificate_policies_oid: Option<String>,
|
||||
|
||||
@ -76,8 +78,8 @@ pub struct ResourceCertificateParsed {
|
||||
pub serial_number: BigUint,
|
||||
pub signature_algorithm: AlgorithmIdentifierValue,
|
||||
pub tbs_signature_algorithm: AlgorithmIdentifierValue,
|
||||
pub issuer_dn: String,
|
||||
pub subject_dn: String,
|
||||
pub issuer_name: X509NameDer,
|
||||
pub subject_name: X509NameDer,
|
||||
pub validity_not_before: Asn1TimeUtc,
|
||||
pub validity_not_after: Asn1TimeUtc,
|
||||
/// DER encoding of SubjectPublicKeyInfo.
|
||||
@ -130,7 +132,7 @@ pub struct AuthorityKeyIdentifierParsed {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct AuthorityInfoAccessParsed {
|
||||
pub ca_issuers_uris: Vec<Url>,
|
||||
pub ca_issuers_uris: Vec<String>,
|
||||
pub ca_issuers_access_location_not_uri: bool,
|
||||
}
|
||||
|
||||
@ -145,7 +147,7 @@ pub struct CrlDistributionPointParsed {
|
||||
pub reasons_present: bool,
|
||||
pub crl_issuer_present: bool,
|
||||
pub name_relative_to_crl_issuer_present: bool,
|
||||
pub full_name_uris: Vec<Url>,
|
||||
pub full_name_uris: Vec<String>,
|
||||
pub full_name_not_uri: bool,
|
||||
pub full_name_present: bool,
|
||||
}
|
||||
@ -153,7 +155,7 @@ pub struct CrlDistributionPointParsed {
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SubjectInfoAccessParsed {
|
||||
pub access_descriptions: Vec<AccessDescription>,
|
||||
pub signed_object_uris: Vec<Url>,
|
||||
pub signed_object_uris: Vec<String>,
|
||||
pub signed_object_access_location_not_uri: bool,
|
||||
}
|
||||
|
||||
@ -170,7 +172,7 @@ pub struct SubjectInfoAccessCa {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SubjectInfoAccessEe {
|
||||
pub signed_object_uris: Vec<Url>,
|
||||
pub signed_object_uris: Vec<String>,
|
||||
/// The full list of access descriptions as carried in the SIA extension.
|
||||
pub access_descriptions: Vec<AccessDescription>,
|
||||
}
|
||||
@ -178,7 +180,7 @@ pub struct SubjectInfoAccessEe {
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct AccessDescription {
|
||||
pub access_method_oid: String,
|
||||
pub access_location: Url,
|
||||
pub access_location: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
@ -541,8 +543,8 @@ impl ResourceCertificate {
|
||||
serial_number: cert.tbs_certificate.serial.clone(),
|
||||
signature_algorithm,
|
||||
tbs_signature_algorithm,
|
||||
issuer_dn: cert.issuer().to_string(),
|
||||
subject_dn: cert.subject().to_string(),
|
||||
issuer_name: X509NameDer(cert.issuer().as_raw().to_vec()),
|
||||
subject_name: X509NameDer(cert.subject().as_raw().to_vec()),
|
||||
validity_not_before,
|
||||
validity_not_after,
|
||||
subject_public_key_info,
|
||||
@ -591,7 +593,7 @@ impl ResourceCertificateParsed {
|
||||
return Err(ResourceCertificateProfileError::InvalidSignatureAlgorithmParameters);
|
||||
}
|
||||
|
||||
let is_self_signed = self.issuer_dn == self.subject_dn;
|
||||
let is_self_signed = self.issuer_name == self.subject_name;
|
||||
let extensions = self.extensions.validate_profile(is_self_signed)?;
|
||||
let kind = if extensions.basic_constraints_ca {
|
||||
ResourceCertKind::Ca
|
||||
@ -605,8 +607,8 @@ impl ResourceCertificateParsed {
|
||||
version,
|
||||
serial_number: self.serial_number,
|
||||
signature_algorithm: self.signature_algorithm.oid,
|
||||
issuer_dn: self.issuer_dn,
|
||||
subject_dn: self.subject_dn,
|
||||
issuer_name: self.issuer_name,
|
||||
subject_name: self.subject_name,
|
||||
validity_not_before: self.validity_not_before.utc,
|
||||
validity_not_after: self.validity_not_after.utc,
|
||||
subject_public_key_info: self.subject_public_key_info,
|
||||
@ -622,20 +624,38 @@ impl RcExtensionsParsed {
|
||||
self,
|
||||
is_self_signed: bool,
|
||||
) -> Result<RcExtensions, ResourceCertificateProfileError> {
|
||||
if self.basic_constraints_ca.len() > 1 {
|
||||
// NOTE(perf): `self` is consumed. Prefer moving decoded fields out rather than cloning,
|
||||
// especially for large resource sets and URI lists.
|
||||
let RcExtensionsParsed {
|
||||
basic_constraints_ca,
|
||||
subject_key_identifier,
|
||||
authority_key_identifier,
|
||||
crl_distribution_points,
|
||||
authority_info_access,
|
||||
subject_info_access,
|
||||
certificate_policies,
|
||||
ip_resources,
|
||||
as_resources,
|
||||
} = self;
|
||||
|
||||
if basic_constraints_ca.len() > 1 {
|
||||
return Err(ResourceCertificateProfileError::DuplicateExtension(
|
||||
"basicConstraints",
|
||||
));
|
||||
}
|
||||
let basic_constraints_ca = self.basic_constraints_ca.first().copied().unwrap_or(false);
|
||||
let basic_constraints_ca = basic_constraints_ca.first().copied().unwrap_or(false);
|
||||
|
||||
let subject_key_identifier = match self.subject_key_identifier.as_slice() {
|
||||
[] => None,
|
||||
[(ski, critical)] => {
|
||||
if *critical {
|
||||
let subject_key_identifier = match subject_key_identifier.len() {
|
||||
0 => None,
|
||||
1 => {
|
||||
let (ski, critical) = subject_key_identifier
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("len==1");
|
||||
if critical {
|
||||
return Err(ResourceCertificateProfileError::SkiCriticality);
|
||||
}
|
||||
Some(ski.clone())
|
||||
Some(ski)
|
||||
}
|
||||
_ => {
|
||||
return Err(ResourceCertificateProfileError::DuplicateExtension(
|
||||
@ -644,16 +664,20 @@ impl RcExtensionsParsed {
|
||||
}
|
||||
};
|
||||
|
||||
let authority_key_identifier = match self.authority_key_identifier.as_slice() {
|
||||
[] => {
|
||||
let authority_key_identifier = match authority_key_identifier.len() {
|
||||
0 => {
|
||||
if is_self_signed {
|
||||
None
|
||||
} else {
|
||||
return Err(ResourceCertificateProfileError::AkiMissing);
|
||||
}
|
||||
}
|
||||
[(aki, critical)] => {
|
||||
if *critical {
|
||||
1 => {
|
||||
let (aki, critical) = authority_key_identifier
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("len==1");
|
||||
if critical {
|
||||
return Err(ResourceCertificateProfileError::AkiCriticality);
|
||||
}
|
||||
if aki.has_authority_cert_issuer {
|
||||
@ -662,7 +686,7 @@ impl RcExtensionsParsed {
|
||||
if aki.has_authority_cert_serial {
|
||||
return Err(ResourceCertificateProfileError::AkiAuthorityCertSerialPresent);
|
||||
}
|
||||
let keyid = aki.key_identifier.clone();
|
||||
let keyid = aki.key_identifier;
|
||||
if is_self_signed {
|
||||
if let (Some(keyid), Some(ski)) =
|
||||
(keyid.as_ref(), subject_key_identifier.as_ref())
|
||||
@ -683,16 +707,20 @@ impl RcExtensionsParsed {
|
||||
}
|
||||
};
|
||||
|
||||
let crl_distribution_points_uris = match self.crl_distribution_points.as_slice() {
|
||||
[] => {
|
||||
let crl_distribution_points_uris = match crl_distribution_points.len() {
|
||||
0 => {
|
||||
if is_self_signed {
|
||||
None
|
||||
} else {
|
||||
return Err(ResourceCertificateProfileError::CrlDistributionPointsMissing);
|
||||
}
|
||||
}
|
||||
[(crldp, critical)] => {
|
||||
if *critical {
|
||||
1 => {
|
||||
let (crldp, critical) = crl_distribution_points
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("len==1");
|
||||
if critical {
|
||||
return Err(ResourceCertificateProfileError::CrlDistributionPointsCriticality);
|
||||
}
|
||||
if is_self_signed {
|
||||
@ -703,7 +731,11 @@ impl RcExtensionsParsed {
|
||||
if crldp.distribution_points.len() != 1 {
|
||||
return Err(ResourceCertificateProfileError::CrlDistributionPointsNotSingle);
|
||||
}
|
||||
let dp = &crldp.distribution_points[0];
|
||||
let dp = crldp
|
||||
.distribution_points
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("len==1");
|
||||
if dp.reasons_present {
|
||||
return Err(ResourceCertificateProfileError::CrlDistributionPointsHasReasons);
|
||||
}
|
||||
@ -723,10 +755,10 @@ impl RcExtensionsParsed {
|
||||
ResourceCertificateProfileError::CrlDistributionPointsFullNameNotUri,
|
||||
);
|
||||
}
|
||||
if !dp.full_name_uris.iter().any(|u| u.scheme() == "rsync") {
|
||||
if !dp.full_name_uris.iter().any(|u| u.starts_with("rsync://")) {
|
||||
return Err(ResourceCertificateProfileError::CrlDistributionPointsNoRsync);
|
||||
}
|
||||
Some(dp.full_name_uris.clone())
|
||||
Some(dp.full_name_uris)
|
||||
}
|
||||
_ => {
|
||||
return Err(ResourceCertificateProfileError::DuplicateExtension(
|
||||
@ -735,16 +767,17 @@ impl RcExtensionsParsed {
|
||||
}
|
||||
};
|
||||
|
||||
let ca_issuers_uris = match self.authority_info_access.as_slice() {
|
||||
[] => {
|
||||
let ca_issuers_uris = match authority_info_access.len() {
|
||||
0 => {
|
||||
if is_self_signed {
|
||||
None
|
||||
} else {
|
||||
return Err(ResourceCertificateProfileError::AuthorityInfoAccessMissing);
|
||||
}
|
||||
}
|
||||
[(aia, critical)] => {
|
||||
if *critical {
|
||||
1 => {
|
||||
let (aia, critical) = authority_info_access.into_iter().next().expect("len==1");
|
||||
if critical {
|
||||
return Err(ResourceCertificateProfileError::AuthorityInfoAccessCriticality);
|
||||
}
|
||||
if is_self_signed {
|
||||
@ -762,10 +795,10 @@ impl RcExtensionsParsed {
|
||||
ResourceCertificateProfileError::AuthorityInfoAccessMissingCaIssuers,
|
||||
);
|
||||
}
|
||||
if !aia.ca_issuers_uris.iter().any(|u| u.scheme() == "rsync") {
|
||||
if !aia.ca_issuers_uris.iter().any(|u| u.starts_with("rsync://")) {
|
||||
return Err(ResourceCertificateProfileError::AuthorityInfoAccessNoRsync);
|
||||
}
|
||||
Some(aia.ca_issuers_uris.clone())
|
||||
Some(aia.ca_issuers_uris)
|
||||
}
|
||||
_ => {
|
||||
return Err(ResourceCertificateProfileError::DuplicateExtension(
|
||||
@ -774,28 +807,32 @@ impl RcExtensionsParsed {
|
||||
}
|
||||
};
|
||||
|
||||
let subject_info_access = match self.subject_info_access.as_slice() {
|
||||
[] => None,
|
||||
[(sia, critical)] => {
|
||||
if *critical {
|
||||
let subject_info_access = match subject_info_access.len() {
|
||||
0 => None,
|
||||
1 => {
|
||||
let (sia, critical) = subject_info_access.into_iter().next().expect("len==1");
|
||||
if critical {
|
||||
return Err(ResourceCertificateProfileError::SiaCriticality);
|
||||
}
|
||||
if sia.signed_object_access_location_not_uri {
|
||||
return Err(ResourceCertificateProfileError::SignedObjectSiaNotUri);
|
||||
}
|
||||
if !sia.signed_object_uris.is_empty()
|
||||
&& !sia.signed_object_uris.iter().any(|u| u.scheme() == "rsync")
|
||||
&& !sia
|
||||
.signed_object_uris
|
||||
.iter()
|
||||
.any(|u| u.starts_with("rsync://"))
|
||||
{
|
||||
return Err(ResourceCertificateProfileError::SignedObjectSiaNoRsync);
|
||||
}
|
||||
if sia.signed_object_uris.is_empty() {
|
||||
Some(SubjectInfoAccess::Ca(SubjectInfoAccessCa {
|
||||
access_descriptions: sia.access_descriptions.clone(),
|
||||
access_descriptions: sia.access_descriptions,
|
||||
}))
|
||||
} else {
|
||||
Some(SubjectInfoAccess::Ee(SubjectInfoAccessEe {
|
||||
signed_object_uris: sia.signed_object_uris.clone(),
|
||||
access_descriptions: sia.access_descriptions.clone(),
|
||||
signed_object_uris: sia.signed_object_uris,
|
||||
access_descriptions: sia.access_descriptions,
|
||||
}))
|
||||
}
|
||||
}
|
||||
@ -806,10 +843,11 @@ impl RcExtensionsParsed {
|
||||
}
|
||||
};
|
||||
|
||||
let certificate_policies_oid = match self.certificate_policies.as_slice() {
|
||||
[] => None,
|
||||
[(oids, critical)] => {
|
||||
if !*critical {
|
||||
let certificate_policies_oid = match certificate_policies.len() {
|
||||
0 => None,
|
||||
1 => {
|
||||
let (oids, critical) = certificate_policies.into_iter().next().expect("len==1");
|
||||
if !critical {
|
||||
return Err(ResourceCertificateProfileError::CertificatePoliciesCriticality);
|
||||
}
|
||||
if oids.len() != 1 {
|
||||
@ -817,7 +855,7 @@ impl RcExtensionsParsed {
|
||||
"expected exactly one policy".into(),
|
||||
));
|
||||
}
|
||||
let policy_oid = oids[0].clone();
|
||||
let policy_oid = oids.into_iter().next().expect("len==1");
|
||||
if policy_oid != OID_CP_IPADDR_ASNUMBER {
|
||||
return Err(ResourceCertificateProfileError::InvalidCertificatePolicy(
|
||||
policy_oid,
|
||||
@ -832,13 +870,14 @@ impl RcExtensionsParsed {
|
||||
}
|
||||
};
|
||||
|
||||
let ip_resources = match self.ip_resources.as_slice() {
|
||||
[] => None,
|
||||
[(ip, critical)] => {
|
||||
if !*critical {
|
||||
let ip_resources = match ip_resources.len() {
|
||||
0 => None,
|
||||
1 => {
|
||||
let (ip, critical) = ip_resources.into_iter().next().expect("len==1");
|
||||
if !critical {
|
||||
return Err(ResourceCertificateProfileError::IpResourcesCriticality);
|
||||
}
|
||||
Some(ip.clone())
|
||||
Some(ip)
|
||||
}
|
||||
_ => {
|
||||
return Err(ResourceCertificateProfileError::DuplicateExtension(
|
||||
@ -847,13 +886,14 @@ impl RcExtensionsParsed {
|
||||
}
|
||||
};
|
||||
|
||||
let as_resources = match self.as_resources.as_slice() {
|
||||
[] => None,
|
||||
[(asn, critical)] => {
|
||||
if !*critical {
|
||||
let as_resources = match as_resources.len() {
|
||||
0 => None,
|
||||
1 => {
|
||||
let (asn, critical) = as_resources.into_iter().next().expect("len==1");
|
||||
if !critical {
|
||||
return Err(ResourceCertificateProfileError::AsResourcesCriticality);
|
||||
}
|
||||
Some(asn.clone())
|
||||
Some(asn)
|
||||
}
|
||||
_ => {
|
||||
return Err(ResourceCertificateProfileError::DuplicateExtension(
|
||||
@ -884,8 +924,16 @@ fn algorithm_identifier_value(
|
||||
tag: p.tag(),
|
||||
data: p.as_bytes().to_vec(),
|
||||
});
|
||||
// NOTE(perf): Avoid `to_id_string()` allocations for the algorithms we expect
|
||||
// in RPKI resource certificates. Fall back to `to_id_string()` for unexpected
|
||||
// algorithms (mostly error paths).
|
||||
let oid = if ai.algorithm.as_bytes() == OID_SHA256_WITH_RSA_ENCRYPTION_RAW {
|
||||
OID_SHA256_WITH_RSA_ENCRYPTION.to_string()
|
||||
} else {
|
||||
ai.algorithm.to_id_string()
|
||||
};
|
||||
AlgorithmIdentifierValue {
|
||||
oid: ai.algorithm.to_id_string(),
|
||||
oid,
|
||||
parameters,
|
||||
}
|
||||
}
|
||||
@ -905,25 +953,22 @@ fn parse_extensions_parse(
|
||||
let mut as_resources: Vec<(AsResourceSet, bool)> = Vec::new();
|
||||
|
||||
for ext in exts {
|
||||
let oid = ext.oid.to_id_string();
|
||||
match oid.as_str() {
|
||||
crate::data_model::oid::OID_BASIC_CONSTRAINTS => {
|
||||
let oid = ext.oid.as_bytes();
|
||||
if oid == OID_BASIC_CONSTRAINTS_RAW {
|
||||
let ParsedExtension::BasicConstraints(bc) = ext.parsed_extension() else {
|
||||
return Err(ResourceCertificateParseError::Parse(
|
||||
"basicConstraints parse failed".into(),
|
||||
));
|
||||
};
|
||||
basic_constraints_ca.push(bc.ca);
|
||||
}
|
||||
OID_SUBJECT_KEY_IDENTIFIER => {
|
||||
} else if oid == OID_SUBJECT_KEY_IDENTIFIER_RAW {
|
||||
let ParsedExtension::SubjectKeyIdentifier(s) = ext.parsed_extension() else {
|
||||
return Err(ResourceCertificateParseError::Parse(
|
||||
"subjectKeyIdentifier parse failed".into(),
|
||||
));
|
||||
};
|
||||
ski.push((s.0.to_vec(), ext.critical));
|
||||
}
|
||||
OID_AUTHORITY_KEY_IDENTIFIER => {
|
||||
} else if oid == OID_AUTHORITY_KEY_IDENTIFIER_RAW {
|
||||
let ParsedExtension::AuthorityKeyIdentifier(a) = ext.parsed_extension() else {
|
||||
return Err(ResourceCertificateParseError::Parse(
|
||||
"authorityKeyIdentifier parse failed".into(),
|
||||
@ -937,52 +982,52 @@ fn parse_extensions_parse(
|
||||
},
|
||||
ext.critical,
|
||||
));
|
||||
}
|
||||
OID_CRL_DISTRIBUTION_POINTS => {
|
||||
} else if oid == OID_CRL_DISTRIBUTION_POINTS_RAW {
|
||||
let ParsedExtension::CRLDistributionPoints(p) = ext.parsed_extension() else {
|
||||
return Err(ResourceCertificateParseError::Parse(
|
||||
"cRLDistributionPoints parse failed".into(),
|
||||
));
|
||||
};
|
||||
crldp.push((parse_crldp_parse(p)?, ext.critical));
|
||||
}
|
||||
OID_AUTHORITY_INFO_ACCESS => {
|
||||
} else if oid == OID_AUTHORITY_INFO_ACCESS_RAW {
|
||||
let ParsedExtension::AuthorityInfoAccess(p) = ext.parsed_extension() else {
|
||||
return Err(ResourceCertificateParseError::Parse(
|
||||
"authorityInfoAccess parse failed".into(),
|
||||
));
|
||||
};
|
||||
aia.push((parse_aia_parse(p.accessdescs.as_slice())?, ext.critical));
|
||||
}
|
||||
OID_SUBJECT_INFO_ACCESS => {
|
||||
} else if oid == OID_SUBJECT_INFO_ACCESS_RAW {
|
||||
let ParsedExtension::SubjectInfoAccess(s) = ext.parsed_extension() else {
|
||||
return Err(ResourceCertificateParseError::Parse(
|
||||
"subjectInfoAccess parse failed".into(),
|
||||
));
|
||||
};
|
||||
sia.push((parse_sia_parse(s.accessdescs.as_slice())?, ext.critical));
|
||||
}
|
||||
crate::data_model::oid::OID_CERTIFICATE_POLICIES => {
|
||||
} else if oid == OID_CERTIFICATE_POLICIES_RAW {
|
||||
let ParsedExtension::CertificatePolicies(cp) = ext.parsed_extension() else {
|
||||
return Err(ResourceCertificateParseError::Parse(
|
||||
"certificatePolicies parse failed".into(),
|
||||
));
|
||||
};
|
||||
let oids: Vec<String> = cp.iter().map(|p| p.policy_id.to_id_string()).collect();
|
||||
cert_policies.push((oids, ext.critical));
|
||||
let mut oids: Vec<String> = Vec::with_capacity(cp.len());
|
||||
for p in cp.iter() {
|
||||
let b = p.policy_id.as_bytes();
|
||||
if b == OID_CP_IPADDR_ASNUMBER_RAW {
|
||||
oids.push(OID_CP_IPADDR_ASNUMBER.to_string());
|
||||
} else {
|
||||
oids.push(p.policy_id.to_id_string());
|
||||
}
|
||||
OID_IP_ADDR_BLOCKS => {
|
||||
}
|
||||
cert_policies.push((oids, ext.critical));
|
||||
} else if oid == OID_IP_ADDR_BLOCKS_RAW {
|
||||
let parsed = IpResourceSet::decode_extn_value(ext.value)
|
||||
.map_err(|_e| ResourceCertificateParseError::InvalidIpResourcesEncoding)?;
|
||||
ip_resources.push((parsed, ext.critical));
|
||||
}
|
||||
OID_AUTONOMOUS_SYS_IDS => {
|
||||
} else if oid == OID_AUTONOMOUS_SYS_IDS_RAW {
|
||||
let parsed = AsResourceSet::decode_extn_value(ext.value)
|
||||
.map_err(|_e| ResourceCertificateParseError::InvalidAsResourcesEncoding)?;
|
||||
as_resources.push((parsed, ext.critical));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(RcExtensionsParsed {
|
||||
@ -1001,12 +1046,11 @@ fn parse_extensions_parse(
|
||||
fn parse_aia_parse(
|
||||
access: &[x509_parser::extensions::AccessDescription<'_>],
|
||||
) -> Result<AuthorityInfoAccessParsed, ResourceCertificateParseError> {
|
||||
let mut ca_issuers_uris: Vec<Url> = Vec::new();
|
||||
let mut ca_issuers_uris: Vec<String> = Vec::new();
|
||||
let mut ca_issuers_access_location_not_uri = false;
|
||||
|
||||
for ad in access {
|
||||
let access_method_oid = ad.access_method.to_id_string();
|
||||
if access_method_oid != OID_AD_CA_ISSUERS {
|
||||
if ad.access_method.as_bytes() != OID_AD_CA_ISSUERS_RAW {
|
||||
continue;
|
||||
}
|
||||
let uri = match &ad.access_location {
|
||||
@ -1016,9 +1060,7 @@ fn parse_aia_parse(
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let url = Url::parse(uri)
|
||||
.map_err(|_| ResourceCertificateParseError::Parse(format!("invalid URI: {uri}")))?;
|
||||
ca_issuers_uris.push(url);
|
||||
ca_issuers_uris.push(uri.to_string());
|
||||
}
|
||||
|
||||
Ok(AuthorityInfoAccessParsed {
|
||||
@ -1032,7 +1074,7 @@ fn parse_crldp_parse(
|
||||
) -> Result<CrlDistributionPointsParsed, ResourceCertificateParseError> {
|
||||
let mut out: Vec<CrlDistributionPointParsed> = Vec::new();
|
||||
for p in crldp.iter() {
|
||||
let mut full_name_uris: Vec<Url> = Vec::new();
|
||||
let mut full_name_uris: Vec<String> = Vec::new();
|
||||
let mut full_name_not_uri = false;
|
||||
let mut full_name_present = false;
|
||||
let mut name_relative_to_crl_issuer_present = false;
|
||||
@ -1046,12 +1088,7 @@ fn parse_crldp_parse(
|
||||
for n in names {
|
||||
match n {
|
||||
x509_parser::extensions::GeneralName::URI(u) => {
|
||||
let url = Url::parse(u).map_err(|_| {
|
||||
ResourceCertificateParseError::Parse(format!(
|
||||
"invalid URI: {u}"
|
||||
))
|
||||
})?;
|
||||
full_name_uris.push(url);
|
||||
full_name_uris.push(u.to_string());
|
||||
}
|
||||
_ => {
|
||||
full_name_not_uri = true;
|
||||
@ -1084,28 +1121,37 @@ fn parse_sia_parse(
|
||||
access: &[x509_parser::extensions::AccessDescription<'_>],
|
||||
) -> Result<SubjectInfoAccessParsed, ResourceCertificateParseError> {
|
||||
let mut all = Vec::with_capacity(access.len());
|
||||
let mut signed_object_uris: Vec<Url> = Vec::new();
|
||||
let mut signed_object_uris: Vec<String> = Vec::new();
|
||||
let mut signed_object_access_location_not_uri = false;
|
||||
|
||||
for ad in access {
|
||||
let access_method_oid = ad.access_method.to_id_string();
|
||||
let access_method_oid = if ad.access_method.as_bytes() == OID_AD_CA_REPOSITORY_RAW {
|
||||
OID_AD_CA_REPOSITORY.to_string()
|
||||
} else if ad.access_method.as_bytes() == OID_AD_RPKI_MANIFEST_RAW {
|
||||
OID_AD_RPKI_MANIFEST.to_string()
|
||||
} else if ad.access_method.as_bytes() == OID_AD_RPKI_NOTIFY_RAW {
|
||||
OID_AD_RPKI_NOTIFY.to_string()
|
||||
} else if ad.access_method.as_bytes() == OID_AD_SIGNED_OBJECT_RAW {
|
||||
OID_AD_SIGNED_OBJECT.to_string()
|
||||
} else {
|
||||
ad.access_method.to_id_string()
|
||||
};
|
||||
let is_signed_object = access_method_oid == OID_AD_SIGNED_OBJECT;
|
||||
let uri = match &ad.access_location {
|
||||
x509_parser::extensions::GeneralName::URI(u) => u,
|
||||
_ => {
|
||||
if access_method_oid == OID_AD_SIGNED_OBJECT {
|
||||
if is_signed_object {
|
||||
signed_object_access_location_not_uri = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let url = Url::parse(uri)
|
||||
.map_err(|_| ResourceCertificateParseError::Parse(format!("invalid URI: {uri}")))?;
|
||||
if access_method_oid == OID_AD_SIGNED_OBJECT {
|
||||
signed_object_uris.push(url.clone());
|
||||
if is_signed_object {
|
||||
signed_object_uris.push(uri.to_string());
|
||||
}
|
||||
all.push(AccessDescription {
|
||||
access_method_oid,
|
||||
access_location: url,
|
||||
access_location: uri.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
use crate::data_model::oid::OID_CT_ROUTE_ORIGIN_AUTHZ;
|
||||
use crate::data_model::common::{DerReader, der_take_tlv};
|
||||
use crate::data_model::rc::{Afi as RcAfi, IpPrefix as RcIpPrefix, ResourceCertificate};
|
||||
use crate::data_model::signed_object::{
|
||||
RpkiSignedObject, RpkiSignedObjectParsed, SignedObjectParseError, SignedObjectValidateError,
|
||||
};
|
||||
use der_parser::ber::{BerObjectContent, Class};
|
||||
use der_parser::der::{DerObject, Tag, parse_der};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RoaObject {
|
||||
@ -229,14 +228,25 @@ pub struct IpPrefix {
|
||||
pub afi: RoaAfi,
|
||||
/// Prefix length in bits.
|
||||
pub prefix_len: u16,
|
||||
/// Network order address bytes (IPv4 4 bytes / IPv6 16 bytes), with host bits cleared.
|
||||
pub addr: Vec<u8>,
|
||||
/// Network order address bytes (always 16 bytes), with host bits cleared.
|
||||
///
|
||||
/// For IPv4 prefixes, only the first 4 bytes are used and the remaining 12 bytes are zero.
|
||||
pub addr: [u8; 16],
|
||||
}
|
||||
|
||||
impl IpPrefix {
|
||||
pub fn addr_bytes(&self) -> &[u8] {
|
||||
match self.afi {
|
||||
RoaAfi::Ipv4 => &self.addr[..4],
|
||||
RoaAfi::Ipv6 => &self.addr[..16],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RoaEContent {
|
||||
/// Parse step of scheme A (`parse → validate → verify`).
|
||||
pub fn parse_der(der: &[u8]) -> Result<RoaEContentParsed, RoaParseError> {
|
||||
let (rem, _obj) = parse_der(der).map_err(|e| RoaParseError::Parse(e.to_string()))?;
|
||||
let (_tag, _value, rem) = der_take_tlv(der).map_err(RoaParseError::Parse)?;
|
||||
if !rem.is_empty() {
|
||||
return Err(RoaParseError::TrailingBytes(rem.len()));
|
||||
}
|
||||
@ -293,7 +303,7 @@ impl RoaEContent {
|
||||
if !ip.contains_prefix(&rc_prefix) {
|
||||
return Err(RoaValidateError::PrefixNotInEeResources {
|
||||
afi: entry.prefix.afi,
|
||||
addr: entry.prefix.addr.clone(),
|
||||
addr: entry.prefix.addr_bytes().to_vec(),
|
||||
prefix_len: entry.prefix.prefix_len,
|
||||
});
|
||||
}
|
||||
@ -328,55 +338,75 @@ impl RoaObjectParsed {
|
||||
|
||||
impl RoaEContentParsed {
|
||||
pub fn validate_profile(self) -> Result<RoaEContent, RoaProfileError> {
|
||||
let (_rem, obj) =
|
||||
parse_der(&self.der).map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
||||
|
||||
let seq = obj
|
||||
.as_sequence()
|
||||
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
||||
if seq.len() != 2 && seq.len() != 3 {
|
||||
return Err(RoaProfileError::InvalidAttestationSequenceLen(seq.len()));
|
||||
fn count_elements(mut r: DerReader<'_>) -> Result<usize, String> {
|
||||
let mut n = 0usize;
|
||||
while !r.is_empty() {
|
||||
r.skip_any()?;
|
||||
n += 1;
|
||||
}
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
let mut r = DerReader::new(&self.der);
|
||||
let mut seq = r
|
||||
.take_sequence()
|
||||
.map_err(|e| RoaProfileError::ProfileDecode(e))?;
|
||||
if !r.is_empty() {
|
||||
return Err(RoaProfileError::ProfileDecode(
|
||||
"trailing bytes after RouteOriginAttestation".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let elem_count =
|
||||
count_elements(seq).map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
||||
if elem_count != 2 && elem_count != 3 {
|
||||
return Err(RoaProfileError::InvalidAttestationSequenceLen(elem_count));
|
||||
}
|
||||
|
||||
let mut idx = 0;
|
||||
let mut version: u32 = 0;
|
||||
if seq.len() == 3 {
|
||||
let v_obj = &seq[0];
|
||||
if v_obj.class() != Class::ContextSpecific || v_obj.tag() != Tag(0) {
|
||||
if elem_count == 3 {
|
||||
if seq
|
||||
.peek_tag()
|
||||
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?
|
||||
!= 0xA0
|
||||
{
|
||||
return Err(RoaProfileError::ProfileDecode(
|
||||
"RouteOriginAttestation.version must be [0] EXPLICIT INTEGER".into(),
|
||||
));
|
||||
}
|
||||
let inner_der = v_obj
|
||||
.as_slice()
|
||||
let (inner_tag, inner_val) = seq
|
||||
.take_explicit(0xA0)
|
||||
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
||||
let (rem, inner) =
|
||||
parse_der(inner_der).map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
||||
if !rem.is_empty() {
|
||||
if inner_tag != 0x02 {
|
||||
return Err(RoaProfileError::ProfileDecode(
|
||||
"trailing bytes inside RouteOriginAttestation.version".into(),
|
||||
"RouteOriginAttestation.version must be [0] EXPLICIT INTEGER".into(),
|
||||
));
|
||||
}
|
||||
let v = inner
|
||||
.as_u64()
|
||||
let v = crate::data_model::common::der_uint_from_bytes(inner_val)
|
||||
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
||||
if v != 0 {
|
||||
return Err(RoaProfileError::InvalidVersion(v));
|
||||
}
|
||||
version = 0;
|
||||
idx = 1;
|
||||
}
|
||||
|
||||
let as_id_u64 = seq[idx]
|
||||
.as_u64()
|
||||
let as_id_u64 = seq
|
||||
.take_uint_u64()
|
||||
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
||||
if as_id_u64 > u32::MAX as u64 {
|
||||
return Err(RoaProfileError::AsIdOutOfRange(as_id_u64));
|
||||
}
|
||||
let as_id = as_id_u64 as u32;
|
||||
idx += 1;
|
||||
let ip_addr_blocks = parse_ip_addr_blocks_cursor(seq.take_sequence().map_err(|e| {
|
||||
RoaProfileError::ProfileDecode(format!("ipAddrBlocks: {e}"))
|
||||
})?)?;
|
||||
|
||||
let ip_addr_blocks = parse_ip_addr_blocks(&seq[idx])?;
|
||||
if !seq.is_empty() {
|
||||
// Extra elements beyond the expected 2..3.
|
||||
let extra =
|
||||
count_elements(seq).map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
||||
return Err(RoaProfileError::InvalidAttestationSequenceLen(elem_count + extra));
|
||||
}
|
||||
|
||||
let mut out = RoaEContent {
|
||||
version,
|
||||
@ -396,21 +426,34 @@ fn roa_prefix_to_rc(p: &IpPrefix) -> RcIpPrefix {
|
||||
RcIpPrefix {
|
||||
afi,
|
||||
prefix_len: p.prefix_len,
|
||||
addr: p.addr.clone(),
|
||||
addr: p.addr_bytes().to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_ip_addr_blocks(obj: &DerObject<'_>) -> Result<Vec<RoaIpAddressFamily>, RoaProfileError> {
|
||||
let seq = obj
|
||||
.as_sequence()
|
||||
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
||||
if seq.is_empty() || seq.len() > 2 {
|
||||
return Err(RoaProfileError::InvalidIpAddrBlocksLen(seq.len()));
|
||||
fn parse_ip_addr_blocks_cursor(
|
||||
mut seq: DerReader<'_>,
|
||||
) -> Result<Vec<RoaIpAddressFamily>, RoaProfileError> {
|
||||
fn count_elements(mut r: DerReader<'_>) -> Result<usize, String> {
|
||||
let mut n = 0usize;
|
||||
while !r.is_empty() {
|
||||
r.skip_any()?;
|
||||
n += 1;
|
||||
}
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
let mut out: Vec<RoaIpAddressFamily> = Vec::new();
|
||||
for fam in seq {
|
||||
let family = parse_ip_address_family(fam)?;
|
||||
let fam_count =
|
||||
count_elements(seq).map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
||||
if fam_count == 0 || fam_count > 2 {
|
||||
return Err(RoaProfileError::InvalidIpAddrBlocksLen(fam_count));
|
||||
}
|
||||
|
||||
let mut out: Vec<RoaIpAddressFamily> = Vec::with_capacity(fam_count);
|
||||
while !seq.is_empty() {
|
||||
let family = parse_ip_address_family_cursor(
|
||||
seq.take_sequence()
|
||||
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?,
|
||||
)?;
|
||||
if out.iter().any(|f| f.afi == family.afi) {
|
||||
return Err(RoaProfileError::DuplicateAfi(family.afi));
|
||||
}
|
||||
@ -419,77 +462,75 @@ fn parse_ip_addr_blocks(obj: &DerObject<'_>) -> Result<Vec<RoaIpAddressFamily>,
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn parse_ip_address_family(obj: &DerObject<'_>) -> Result<RoaIpAddressFamily, RoaProfileError> {
|
||||
let seq = obj
|
||||
.as_sequence()
|
||||
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
||||
if seq.len() != 2 {
|
||||
return Err(RoaProfileError::InvalidIpAddressFamily);
|
||||
}
|
||||
|
||||
let afi = parse_afi(&seq[0])?;
|
||||
let addresses = parse_roa_addresses(afi, &seq[1])?;
|
||||
if addresses.is_empty() {
|
||||
return Err(RoaProfileError::EmptyAddressList);
|
||||
}
|
||||
|
||||
Ok(RoaIpAddressFamily { afi, addresses })
|
||||
}
|
||||
|
||||
fn parse_afi(obj: &DerObject<'_>) -> Result<RoaAfi, RoaProfileError> {
|
||||
let bytes = obj
|
||||
.as_slice()
|
||||
fn parse_ip_address_family_cursor(
|
||||
mut fam: DerReader<'_>,
|
||||
) -> Result<RoaIpAddressFamily, RoaProfileError> {
|
||||
let afi = {
|
||||
let bytes = fam
|
||||
.take_octet_string()
|
||||
.map_err(|_e| RoaProfileError::InvalidAddressFamily)?;
|
||||
if bytes.len() != 2 {
|
||||
return Err(RoaProfileError::InvalidAddressFamily);
|
||||
}
|
||||
match bytes {
|
||||
[0x00, 0x01] => Ok(RoaAfi::Ipv4),
|
||||
[0x00, 0x02] => Ok(RoaAfi::Ipv6),
|
||||
_ => Err(RoaProfileError::UnsupportedAfi(bytes.to_vec())),
|
||||
[0x00, 0x01] => RoaAfi::Ipv4,
|
||||
[0x00, 0x02] => RoaAfi::Ipv6,
|
||||
_ => return Err(RoaProfileError::UnsupportedAfi(bytes.to_vec())),
|
||||
}
|
||||
};
|
||||
|
||||
let mut addrs = fam
|
||||
.take_sequence()
|
||||
.map_err(|_e| RoaProfileError::InvalidIpAddressFamily)?;
|
||||
if !fam.is_empty() {
|
||||
return Err(RoaProfileError::InvalidIpAddressFamily);
|
||||
}
|
||||
|
||||
fn parse_roa_addresses(
|
||||
if addrs.is_empty() {
|
||||
return Err(RoaProfileError::EmptyAddressList);
|
||||
}
|
||||
let mut addresses: Vec<RoaIpAddress> = Vec::new();
|
||||
while !addrs.is_empty() {
|
||||
let entry = addrs
|
||||
.take_sequence()
|
||||
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
||||
addresses.push(parse_roa_ip_address_cursor(afi, entry)?);
|
||||
}
|
||||
|
||||
Ok(RoaIpAddressFamily { afi, addresses })
|
||||
}
|
||||
|
||||
fn parse_roa_ip_address_cursor(
|
||||
afi: RoaAfi,
|
||||
obj: &DerObject<'_>,
|
||||
) -> Result<Vec<RoaIpAddress>, RoaProfileError> {
|
||||
let seq = obj
|
||||
.as_sequence()
|
||||
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
||||
let mut out = Vec::with_capacity(seq.len());
|
||||
for entry in seq {
|
||||
out.push(parse_roa_ip_address(afi, entry)?);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn parse_roa_ip_address(afi: RoaAfi, obj: &DerObject<'_>) -> Result<RoaIpAddress, RoaProfileError> {
|
||||
let seq = obj
|
||||
.as_sequence()
|
||||
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
||||
if seq.is_empty() || seq.len() > 2 {
|
||||
mut seq: DerReader<'_>,
|
||||
) -> Result<RoaIpAddress, RoaProfileError> {
|
||||
if seq.is_empty() {
|
||||
return Err(RoaProfileError::InvalidRoaIpAddress);
|
||||
}
|
||||
|
||||
let prefix = parse_prefix_bits(afi, &seq[0])?;
|
||||
let max_length = match seq.get(1) {
|
||||
None => None,
|
||||
Some(m) => {
|
||||
let v = m
|
||||
.as_u64()
|
||||
let (unused_bits, bytes) = seq
|
||||
.take_bit_string()
|
||||
.map_err(|_e| RoaProfileError::InvalidPrefixBitString)?;
|
||||
let prefix = parse_prefix_bits_bytes(afi, unused_bits, bytes)?;
|
||||
|
||||
let max_length = if !seq.is_empty() {
|
||||
let v = seq
|
||||
.take_uint_u64()
|
||||
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
||||
let max_len: u16 = v
|
||||
.try_into()
|
||||
.map_err(|_e| RoaProfileError::InvalidMaxLength {
|
||||
let max_len: u16 = v.try_into().map_err(|_e| RoaProfileError::InvalidMaxLength {
|
||||
afi,
|
||||
prefix_len: prefix.prefix_len,
|
||||
max_len: u16::MAX,
|
||||
})?;
|
||||
Some(max_len)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if !seq.is_empty() {
|
||||
return Err(RoaProfileError::InvalidRoaIpAddress);
|
||||
}
|
||||
|
||||
if let Some(max_len) = max_length {
|
||||
let ub = afi.ub();
|
||||
if max_len > ub || max_len < prefix.prefix_len {
|
||||
@ -504,12 +545,11 @@ fn parse_roa_ip_address(afi: RoaAfi, obj: &DerObject<'_>) -> Result<RoaIpAddress
|
||||
Ok(RoaIpAddress { prefix, max_length })
|
||||
}
|
||||
|
||||
fn parse_prefix_bits(afi: RoaAfi, obj: &DerObject<'_>) -> Result<IpPrefix, RoaProfileError> {
|
||||
let (unused_bits, bytes) = match &obj.content {
|
||||
BerObjectContent::BitString(unused, bso) => (*unused, bso.data.to_vec()),
|
||||
_ => return Err(RoaProfileError::InvalidPrefixBitString),
|
||||
};
|
||||
|
||||
fn parse_prefix_bits_bytes(
|
||||
afi: RoaAfi,
|
||||
unused_bits: u8,
|
||||
bytes: &[u8],
|
||||
) -> Result<IpPrefix, RoaProfileError> {
|
||||
if unused_bits > 7 {
|
||||
return Err(RoaProfileError::InvalidPrefixUnusedBits);
|
||||
}
|
||||
@ -531,7 +571,7 @@ fn parse_prefix_bits(afi: RoaAfi, obj: &DerObject<'_>) -> Result<IpPrefix, RoaPr
|
||||
return Err(RoaProfileError::PrefixLenOutOfRange { afi, prefix_len });
|
||||
}
|
||||
|
||||
let addr = canonicalize_prefix_addr(afi, prefix_len, &bytes);
|
||||
let addr = canonicalize_prefix_addr(afi, prefix_len, bytes);
|
||||
Ok(IpPrefix {
|
||||
afi,
|
||||
prefix_len,
|
||||
@ -539,12 +579,12 @@ fn parse_prefix_bits(afi: RoaAfi, obj: &DerObject<'_>) -> Result<IpPrefix, RoaPr
|
||||
})
|
||||
}
|
||||
|
||||
fn canonicalize_prefix_addr(afi: RoaAfi, prefix_len: u16, bytes: &[u8]) -> Vec<u8> {
|
||||
fn canonicalize_prefix_addr(afi: RoaAfi, prefix_len: u16, bytes: &[u8]) -> [u8; 16] {
|
||||
let full_len = match afi {
|
||||
RoaAfi::Ipv4 => 4,
|
||||
RoaAfi::Ipv6 => 16,
|
||||
};
|
||||
let mut addr = vec![0u8; full_len];
|
||||
let mut addr = [0u8; 16];
|
||||
let copy_len = bytes.len().min(full_len);
|
||||
addr[..copy_len].copy_from_slice(&bytes[..copy_len]);
|
||||
|
||||
@ -557,7 +597,7 @@ fn canonicalize_prefix_addr(afi: RoaAfi, prefix_len: u16, bytes: &[u8]) -> Vec<u
|
||||
let rem = (prefix_len % 8) as u8;
|
||||
if rem != 0 {
|
||||
let mask: u8 = 0xFF << (8 - rem);
|
||||
if last_prefix_byte < addr.len() {
|
||||
if last_prefix_byte < full_len {
|
||||
addr[last_prefix_byte] &= mask;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
use crate::data_model::common::{Asn1TimeEncoding, Asn1TimeUtc};
|
||||
use crate::data_model::common::{der_take_tlv, Asn1TimeEncoding, Asn1TimeUtc, DerReader};
|
||||
use crate::data_model::oid::{
|
||||
OID_AD_SIGNED_OBJECT, OID_CMS_ATTR_CONTENT_TYPE, OID_CMS_ATTR_MESSAGE_DIGEST,
|
||||
OID_CMS_ATTR_SIGNING_TIME, OID_RSA_ENCRYPTION, OID_SHA256, OID_SHA256_WITH_RSA_ENCRYPTION,
|
||||
OID_SIGNED_DATA, OID_SUBJECT_INFO_ACCESS,
|
||||
OID_CMS_ATTR_CONTENT_TYPE_RAW, OID_CMS_ATTR_MESSAGE_DIGEST_RAW, OID_CMS_ATTR_SIGNING_TIME,
|
||||
OID_CMS_ATTR_SIGNING_TIME_RAW, OID_CT_ASPA, OID_CT_ASPA_RAW, OID_CT_RPKI_MANIFEST,
|
||||
OID_CT_RPKI_MANIFEST_RAW, OID_CT_ROUTE_ORIGIN_AUTHZ, OID_CT_ROUTE_ORIGIN_AUTHZ_RAW,
|
||||
OID_RSA_ENCRYPTION, OID_RSA_ENCRYPTION_RAW, OID_SHA256, OID_SHA256_RAW,
|
||||
OID_SHA256_WITH_RSA_ENCRYPTION, OID_SHA256_WITH_RSA_ENCRYPTION_RAW, OID_SIGNED_DATA,
|
||||
OID_SIGNED_DATA_RAW, OID_SUBJECT_INFO_ACCESS,
|
||||
};
|
||||
use crate::data_model::rc::{ResourceCertificate, SubjectInfoAccess};
|
||||
use der_parser::ber::Class;
|
||||
use der_parser::der::{DerObject, Tag, parse_der};
|
||||
use ring::digest;
|
||||
use x509_parser::prelude::FromDer;
|
||||
use x509_parser::public_key::PublicKey;
|
||||
@ -303,24 +305,22 @@ impl RpkiSignedObject {
|
||||
/// This performs encoding/structure parsing only. Profile constraints are enforced by
|
||||
/// `RpkiSignedObjectParsed::validate_profile`.
|
||||
pub fn parse_der(der: &[u8]) -> Result<RpkiSignedObjectParsed, SignedObjectParseError> {
|
||||
let (rem, obj) =
|
||||
parse_der(der).map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
if !rem.is_empty() {
|
||||
return Err(SignedObjectParseError::TrailingBytes(rem.len()));
|
||||
let mut r = DerReader::new(der);
|
||||
let mut content_info_seq = r
|
||||
.take_sequence()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
if !r.is_empty() {
|
||||
return Err(SignedObjectParseError::TrailingBytes(r.remaining_len()));
|
||||
}
|
||||
|
||||
let content_info_seq = obj
|
||||
.as_sequence()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
if content_info_seq.len() != 2 {
|
||||
let content_type = take_oid_string(&mut content_info_seq)?;
|
||||
let signed_data = parse_signed_data_from_contentinfo_cursor(&mut content_info_seq)?;
|
||||
if !content_info_seq.is_empty() {
|
||||
return Err(SignedObjectParseError::Parse(
|
||||
"ContentInfo must be a SEQUENCE of 2 elements".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let content_type = oid_to_string_parse(&content_info_seq[0])?;
|
||||
let signed_data = parse_signed_data_from_contentinfo_parse(&content_info_seq[1])?;
|
||||
|
||||
Ok(RpkiSignedObjectParsed {
|
||||
raw_der: der.to_vec(),
|
||||
content_info_content_type: content_type,
|
||||
@ -411,91 +411,94 @@ impl RpkiSignedObjectParsed {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_signed_data_from_contentinfo_parse(
|
||||
obj: &DerObject<'_>,
|
||||
fn parse_signed_data_from_contentinfo_cursor(
|
||||
seq: &mut DerReader<'_>,
|
||||
) -> Result<SignedDataParsed, SignedObjectParseError> {
|
||||
// ContentInfo.content is `[0] EXPLICIT`, but `der-parser` will represent unknown tagged
|
||||
// objects as `Unknown(Any)`. For EXPLICIT tags, the content octets are the full encoding of
|
||||
// the inner object, so we parse it from the object's slice.
|
||||
if obj.class() != Class::ContextSpecific || obj.tag() != Tag(0) {
|
||||
return Err(SignedObjectParseError::Parse(
|
||||
"ContentInfo.content must be [0] EXPLICIT".into(),
|
||||
));
|
||||
}
|
||||
let inner_der = obj
|
||||
.as_slice()
|
||||
let inner_der = seq.take_explicit_der(0xA0).map_err(|_e| {
|
||||
SignedObjectParseError::Parse("ContentInfo.content must be [0] EXPLICIT".into())
|
||||
})?;
|
||||
let mut r = DerReader::new(inner_der);
|
||||
let signed_data_seq = r
|
||||
.take_sequence()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
let (rem, inner_obj) =
|
||||
parse_der(inner_der).map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
if !rem.is_empty() {
|
||||
if !r.is_empty() {
|
||||
return Err(SignedObjectParseError::Parse(
|
||||
"trailing bytes inside ContentInfo.content".into(),
|
||||
));
|
||||
}
|
||||
parse_signed_data_parse(&inner_obj)
|
||||
parse_signed_data_cursor(signed_data_seq)
|
||||
}
|
||||
|
||||
fn parse_signed_data_parse(
|
||||
obj: &DerObject<'_>,
|
||||
) -> Result<SignedDataParsed, SignedObjectParseError> {
|
||||
let seq = obj
|
||||
.as_sequence()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
if seq.len() < 4 || seq.len() > 6 {
|
||||
return Err(SignedObjectParseError::Parse(
|
||||
"SignedData must be a SEQUENCE of 4..6 elements".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let version = seq[0]
|
||||
.as_u64()
|
||||
fn parse_signed_data_cursor(mut seq: DerReader<'_>) -> Result<SignedDataParsed, SignedObjectParseError> {
|
||||
let version = seq
|
||||
.take_uint_u64()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
|
||||
let digest_set = seq[1]
|
||||
.as_set()
|
||||
let digest_set_bytes = seq
|
||||
.take_tag(0x31)
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
let mut digest_algorithms: Vec<AlgorithmIdentifierParsed> =
|
||||
Vec::with_capacity(digest_set.len());
|
||||
for item in digest_set {
|
||||
let (oid, params_ok) = parse_algorithm_identifier_parse(item)?;
|
||||
let mut digest_set = DerReader::new(digest_set_bytes);
|
||||
let mut digest_algorithms: Vec<AlgorithmIdentifierParsed> = Vec::new();
|
||||
while !digest_set.is_empty() {
|
||||
let alg = digest_set
|
||||
.take_sequence()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
let (oid, params_ok) = parse_algorithm_identifier_cursor(alg)?;
|
||||
digest_algorithms.push(AlgorithmIdentifierParsed { oid, params_ok });
|
||||
}
|
||||
|
||||
let encap_content_info = parse_encapsulated_content_info_parse(&seq[2])?;
|
||||
let encap_content_info = parse_encapsulated_content_info_cursor(
|
||||
seq.take_sequence()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?,
|
||||
)?;
|
||||
|
||||
let mut certificates: Option<Vec<Vec<u8>>> = None;
|
||||
let mut crls_present = false;
|
||||
let mut signer_infos_obj: Option<&DerObject<'_>> = None;
|
||||
let mut signer_infos: Option<Vec<SignerInfoParsed>> = None;
|
||||
|
||||
for item in &seq[3..] {
|
||||
if item.class() == Class::ContextSpecific && item.tag() == Tag(0) {
|
||||
while !seq.is_empty() {
|
||||
let tag = seq
|
||||
.peek_tag()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
match tag {
|
||||
0xA0 => {
|
||||
if certificates.is_some() {
|
||||
return Err(SignedObjectParseError::Parse(
|
||||
"SignedData.certificates appears more than once".into(),
|
||||
));
|
||||
}
|
||||
certificates = Some(parse_certificate_set_implicit_parse(item)?);
|
||||
} else if item.class() == Class::ContextSpecific && item.tag() == Tag(1) {
|
||||
let content = seq
|
||||
.take_tag(0xA0)
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
certificates = Some(split_der_objects(content)?);
|
||||
}
|
||||
0xA1 => {
|
||||
crls_present = true;
|
||||
} else if item.class() == Class::Universal && item.tag() == Tag::Set {
|
||||
signer_infos_obj = Some(item);
|
||||
} else {
|
||||
seq.skip_any()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
}
|
||||
0x31 => {
|
||||
if signer_infos.is_some() {
|
||||
return Err(SignedObjectParseError::Parse(
|
||||
"SignedData.signerInfos appears more than once".into(),
|
||||
));
|
||||
}
|
||||
let set_bytes = seq
|
||||
.take_tag(0x31)
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
signer_infos = Some(parse_signer_infos_set_cursor(set_bytes)?);
|
||||
}
|
||||
_ => {
|
||||
return Err(SignedObjectParseError::Parse(
|
||||
"unexpected field in SignedData".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let signer_infos_obj = signer_infos_obj
|
||||
.ok_or_else(|| SignedObjectParseError::Parse("SignedData.signerInfos missing".into()))?;
|
||||
let signer_infos_set = signer_infos_obj
|
||||
.as_set()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
let mut signer_infos: Vec<SignerInfoParsed> = Vec::with_capacity(signer_infos_set.len());
|
||||
for si in signer_infos_set {
|
||||
signer_infos.push(parse_signer_info_parse(si)?);
|
||||
}
|
||||
|
||||
let signer_infos =
|
||||
signer_infos.ok_or_else(|| SignedObjectParseError::Parse("SignedData.signerInfos missing".into()))?;
|
||||
|
||||
Ok(SignedDataParsed {
|
||||
version,
|
||||
digest_algorithms,
|
||||
@ -506,46 +509,39 @@ fn parse_signed_data_parse(
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_encapsulated_content_info_parse(
|
||||
obj: &DerObject<'_>,
|
||||
fn parse_encapsulated_content_info_cursor(
|
||||
mut seq: DerReader<'_>,
|
||||
) -> Result<EncapsulatedContentInfoParsed, SignedObjectParseError> {
|
||||
let seq = obj
|
||||
.as_sequence()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
if seq.is_empty() || seq.len() > 2 {
|
||||
if seq.is_empty() {
|
||||
return Err(SignedObjectParseError::Parse(
|
||||
"EncapsulatedContentInfo must be SEQUENCE of 1..2".into(),
|
||||
));
|
||||
}
|
||||
let econtent_type = oid_to_string_parse(&seq[0])?;
|
||||
|
||||
let econtent = match seq.get(1) {
|
||||
None => None,
|
||||
Some(econtent_tagged) => {
|
||||
if econtent_tagged.class() != Class::ContextSpecific || econtent_tagged.tag() != Tag(0)
|
||||
{
|
||||
return Err(SignedObjectParseError::Parse(
|
||||
"EncapsulatedContentInfo.eContent must be [0] EXPLICIT".into(),
|
||||
));
|
||||
}
|
||||
let inner_der = econtent_tagged
|
||||
.as_slice()
|
||||
let econtent_type = take_oid_string(&mut seq)?;
|
||||
|
||||
let econtent = if seq.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let inner_der = seq.take_explicit_der(0xA0).map_err(|_e| {
|
||||
SignedObjectParseError::Parse("EncapsulatedContentInfo.eContent must be [0] EXPLICIT".into())
|
||||
})?;
|
||||
let mut inner = DerReader::new(inner_der);
|
||||
let octets = inner
|
||||
.take_octet_string()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
let (rem, inner_obj) =
|
||||
parse_der(inner_der).map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
if !rem.is_empty() {
|
||||
if !inner.is_empty() {
|
||||
return Err(SignedObjectParseError::Parse(
|
||||
"trailing bytes inside EncapsulatedContentInfo.eContent".into(),
|
||||
));
|
||||
}
|
||||
Some(
|
||||
inner_obj
|
||||
.as_slice()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?
|
||||
.to_vec(),
|
||||
)
|
||||
}
|
||||
Some(octets.to_vec())
|
||||
};
|
||||
if !seq.is_empty() {
|
||||
return Err(SignedObjectParseError::Parse(
|
||||
"EncapsulatedContentInfo must be SEQUENCE of 1..2".into(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(EncapsulatedContentInfoParsed {
|
||||
econtent_type,
|
||||
@ -553,22 +549,28 @@ fn parse_encapsulated_content_info_parse(
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_certificate_set_implicit_parse(
|
||||
obj: &DerObject<'_>,
|
||||
) -> Result<Vec<Vec<u8>>, SignedObjectParseError> {
|
||||
let content = obj
|
||||
.as_slice()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
let mut input = content;
|
||||
let mut certs = Vec::new();
|
||||
fn split_der_objects(mut input: &[u8]) -> Result<Vec<Vec<u8>>, SignedObjectParseError> {
|
||||
let mut out: Vec<Vec<u8>> = Vec::new();
|
||||
while !input.is_empty() {
|
||||
let (rem, _any_obj) =
|
||||
parse_der(input).map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
let (_tag, _value, rem) =
|
||||
der_take_tlv(input).map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
let consumed = input.len() - rem.len();
|
||||
certs.push(input[..consumed].to_vec());
|
||||
out.push(input[..consumed].to_vec());
|
||||
input = rem;
|
||||
}
|
||||
Ok(certs)
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn parse_signer_infos_set_cursor(set_bytes: &[u8]) -> Result<Vec<SignerInfoParsed>, SignedObjectParseError> {
|
||||
let mut set = DerReader::new(set_bytes);
|
||||
let mut out: Vec<SignerInfoParsed> = Vec::new();
|
||||
while !set.is_empty() {
|
||||
let si = set
|
||||
.take_sequence()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
out.push(parse_signer_info_cursor(si)?);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn validate_ee_certificate(der: &[u8]) -> Result<ResourceEeCertificate, SignedObjectValidateError> {
|
||||
@ -603,11 +605,7 @@ fn validate_ee_certificate(der: &[u8]) -> Result<ResourceEeCertificate, SignedOb
|
||||
.as_ref()
|
||||
.ok_or(SignedObjectValidateError::EeCertificateMissingSia)?;
|
||||
let signed_object_uris: Vec<String> = match sia {
|
||||
SubjectInfoAccess::Ee(ee) => ee
|
||||
.signed_object_uris
|
||||
.iter()
|
||||
.map(|u| u.as_str().to_string())
|
||||
.collect(),
|
||||
SubjectInfoAccess::Ee(ee) => ee.signed_object_uris.clone(),
|
||||
SubjectInfoAccess::Ca(_ca) => Vec::new(),
|
||||
};
|
||||
if signed_object_uris.is_empty() {
|
||||
@ -626,69 +624,64 @@ fn validate_ee_certificate(der: &[u8]) -> Result<ResourceEeCertificate, SignedOb
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_signer_info_parse(
|
||||
obj: &DerObject<'_>,
|
||||
) -> Result<SignerInfoParsed, SignedObjectParseError> {
|
||||
let seq = obj
|
||||
.as_sequence()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
if seq.len() < 5 || seq.len() > 7 {
|
||||
return Err(SignedObjectParseError::Parse(
|
||||
"SignerInfo must be a SEQUENCE of 5..7 elements".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let version = seq[0]
|
||||
.as_u64()
|
||||
fn parse_signer_info_cursor(mut seq: DerReader<'_>) -> Result<SignerInfoParsed, SignedObjectParseError> {
|
||||
let version = seq
|
||||
.take_uint_u64()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
|
||||
let sid = &seq[1];
|
||||
let sid = if sid.class() == Class::ContextSpecific && sid.tag() == Tag(0) {
|
||||
let ski = sid
|
||||
.as_slice()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?
|
||||
.to_vec();
|
||||
SignerIdentifierParsed::SubjectKeyIdentifier(ski)
|
||||
let (sid_tag, sid_bytes) = seq
|
||||
.take_any()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
let sid = if (sid_tag & 0xC0) == 0x80 && (sid_tag & 0x1F) == 0 {
|
||||
SignerIdentifierParsed::SubjectKeyIdentifier(sid_bytes.to_vec())
|
||||
} else {
|
||||
SignerIdentifierParsed::Other
|
||||
};
|
||||
|
||||
let (digest_oid, digest_params_ok) = parse_algorithm_identifier_parse(&seq[2])?;
|
||||
let digest_alg_seq = seq
|
||||
.take_sequence()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
let (digest_oid, digest_params_ok) = parse_algorithm_identifier_cursor(digest_alg_seq)?;
|
||||
let digest_algorithm = AlgorithmIdentifierParsed {
|
||||
oid: digest_oid,
|
||||
params_ok: digest_params_ok,
|
||||
};
|
||||
|
||||
let mut idx = 3;
|
||||
let mut signed_attrs_content: Option<Vec<u8>> = None;
|
||||
let mut signed_attrs_der_for_signature: Option<Vec<u8>> = None;
|
||||
|
||||
if seq[idx].class() == Class::ContextSpecific && seq[idx].tag() == Tag(0) {
|
||||
let signed_attrs_obj = &seq[idx];
|
||||
let content = signed_attrs_obj
|
||||
.as_slice()
|
||||
if seq
|
||||
.peek_tag()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?
|
||||
.to_vec();
|
||||
signed_attrs_content = Some(content);
|
||||
signed_attrs_der_for_signature =
|
||||
Some(make_signed_attrs_der_for_signature_parse(signed_attrs_obj)?);
|
||||
idx += 1;
|
||||
== 0xA0
|
||||
{
|
||||
let (tag, full_tlv, value) = seq
|
||||
.take_any_full()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
if tag != 0xA0 {
|
||||
return Err(SignedObjectParseError::Parse(
|
||||
"SignerInfo.signedAttrs must be [0] IMPLICIT".into(),
|
||||
));
|
||||
}
|
||||
signed_attrs_content = Some(value.to_vec());
|
||||
signed_attrs_der_for_signature = Some(make_signed_attrs_der_for_signature(full_tlv)?);
|
||||
}
|
||||
|
||||
let (signature_oid, signature_params_ok) = parse_algorithm_identifier_parse(&seq[idx])?;
|
||||
let sig_alg_seq = seq
|
||||
.take_sequence()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
let (signature_oid, signature_params_ok) = parse_algorithm_identifier_cursor(sig_alg_seq)?;
|
||||
let signature_algorithm = AlgorithmIdentifierParsed {
|
||||
oid: signature_oid,
|
||||
params_ok: signature_params_ok,
|
||||
};
|
||||
idx += 1;
|
||||
|
||||
let signature = seq[idx]
|
||||
.as_slice()
|
||||
let signature = seq
|
||||
.take_octet_string()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?
|
||||
.to_vec();
|
||||
idx += 1;
|
||||
|
||||
let unsigned_attrs_present = seq.get(idx).is_some();
|
||||
let unsigned_attrs_present = !seq.is_empty();
|
||||
|
||||
Ok(SignerInfoParsed {
|
||||
version,
|
||||
@ -696,9 +689,9 @@ fn parse_signer_info_parse(
|
||||
digest_algorithm,
|
||||
signature_algorithm,
|
||||
signed_attrs_content,
|
||||
signed_attrs_der_for_signature,
|
||||
unsigned_attrs_present,
|
||||
signature,
|
||||
signed_attrs_der_for_signature,
|
||||
})
|
||||
}
|
||||
|
||||
@ -843,58 +836,90 @@ fn parse_signed_attrs_implicit(
|
||||
let mut message_digest: Option<Vec<u8>> = None;
|
||||
let mut signing_time: Option<Asn1TimeUtc> = None;
|
||||
|
||||
let mut remaining = input;
|
||||
while !remaining.is_empty() {
|
||||
let (rem, attr_obj) = parse_der(remaining)
|
||||
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
|
||||
remaining = rem;
|
||||
fn count_elements(mut r: DerReader<'_>) -> Result<usize, String> {
|
||||
let mut n = 0usize;
|
||||
while !r.is_empty() {
|
||||
r.skip_any()?;
|
||||
n += 1;
|
||||
}
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
let attr_seq = attr_obj
|
||||
.as_sequence()
|
||||
let mut remaining = DerReader::new(input);
|
||||
while !remaining.is_empty() {
|
||||
let mut attr = remaining
|
||||
.take_sequence()
|
||||
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
|
||||
if attr_seq.len() != 2 {
|
||||
|
||||
let oid_bytes = attr
|
||||
.take_tag(0x06)
|
||||
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
|
||||
let oid = oid_value_bytes_to_string(oid_bytes);
|
||||
|
||||
let values_bytes = attr
|
||||
.take_tag(0x31)
|
||||
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
|
||||
if !attr.is_empty() {
|
||||
return Err(SignedObjectValidateError::SignedAttrsParse(
|
||||
"Attribute must be SEQUENCE of 2".into(),
|
||||
));
|
||||
}
|
||||
let oid = oid_to_string_parse(&attr_seq[0])
|
||||
|
||||
let mut values = DerReader::new(values_bytes);
|
||||
let count = if values.is_empty() {
|
||||
0
|
||||
} else {
|
||||
values
|
||||
.skip_any()
|
||||
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
|
||||
let values_set = attr_seq[1]
|
||||
.as_set()
|
||||
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
|
||||
if values_set.len() != 1 {
|
||||
return Err(
|
||||
SignedObjectValidateError::InvalidSignedAttributeValuesCount {
|
||||
oid,
|
||||
count: values_set.len(),
|
||||
},
|
||||
);
|
||||
if values.is_empty() {
|
||||
1
|
||||
} else {
|
||||
1 + count_elements(values)
|
||||
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?
|
||||
}
|
||||
};
|
||||
if count != 1 {
|
||||
return Err(SignedObjectValidateError::InvalidSignedAttributeValuesCount {
|
||||
oid,
|
||||
count,
|
||||
});
|
||||
}
|
||||
|
||||
// Re-parse the sole value.
|
||||
let mut values = DerReader::new(values_bytes);
|
||||
let (val_tag, val_bytes) = values
|
||||
.take_any()
|
||||
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
|
||||
|
||||
match oid.as_str() {
|
||||
OID_CMS_ATTR_CONTENT_TYPE => {
|
||||
if content_type.is_some() {
|
||||
return Err(SignedObjectValidateError::DuplicateSignedAttribute(oid));
|
||||
}
|
||||
let v = oid_to_string_parse(&values_set[0])
|
||||
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
|
||||
content_type = Some(v);
|
||||
if val_tag != 0x06 {
|
||||
return Err(SignedObjectValidateError::SignedAttrsParse(
|
||||
"content-type attr value must be OBJECT IDENTIFIER".into(),
|
||||
));
|
||||
}
|
||||
content_type = Some(oid_value_bytes_to_string(val_bytes));
|
||||
}
|
||||
OID_CMS_ATTR_MESSAGE_DIGEST => {
|
||||
if message_digest.is_some() {
|
||||
return Err(SignedObjectValidateError::DuplicateSignedAttribute(oid));
|
||||
}
|
||||
let v = values_set[0]
|
||||
.as_slice()
|
||||
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?
|
||||
.to_vec();
|
||||
message_digest = Some(v);
|
||||
if val_tag != 0x04 {
|
||||
return Err(SignedObjectValidateError::SignedAttrsParse(
|
||||
"message-digest attr value must be OCTET STRING".into(),
|
||||
));
|
||||
}
|
||||
message_digest = Some(val_bytes.to_vec());
|
||||
}
|
||||
OID_CMS_ATTR_SIGNING_TIME => {
|
||||
if signing_time.is_some() {
|
||||
return Err(SignedObjectValidateError::DuplicateSignedAttribute(oid));
|
||||
}
|
||||
signing_time = Some(parse_signing_time_value(&values_set[0])?);
|
||||
signing_time = Some(parse_signing_time_value_tlv(val_tag, val_bytes)?);
|
||||
}
|
||||
_ => {
|
||||
return Err(SignedObjectValidateError::UnsupportedSignedAttribute(oid));
|
||||
@ -913,35 +938,27 @@ fn parse_signed_attrs_implicit(
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_signing_time_value(obj: &DerObject<'_>) -> Result<Asn1TimeUtc, SignedObjectValidateError> {
|
||||
match &obj.content {
|
||||
der_parser::ber::BerObjectContent::UTCTime(dt) => Ok(Asn1TimeUtc {
|
||||
utc: dt
|
||||
.to_datetime()
|
||||
.map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?,
|
||||
fn parse_signing_time_value_tlv(tag: u8, value: &[u8]) -> Result<Asn1TimeUtc, SignedObjectValidateError> {
|
||||
match tag {
|
||||
0x17 => Ok(Asn1TimeUtc {
|
||||
utc: parse_utctime(value)?,
|
||||
encoding: Asn1TimeEncoding::UtcTime,
|
||||
}),
|
||||
der_parser::ber::BerObjectContent::GeneralizedTime(dt) => Ok(Asn1TimeUtc {
|
||||
utc: dt
|
||||
.to_datetime()
|
||||
.map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?,
|
||||
0x18 => Ok(Asn1TimeUtc {
|
||||
utc: parse_generalized_time(value)?,
|
||||
encoding: Asn1TimeEncoding::GeneralizedTime,
|
||||
}),
|
||||
_ => Err(SignedObjectValidateError::InvalidSigningTimeValue),
|
||||
}
|
||||
}
|
||||
|
||||
fn make_signed_attrs_der_for_signature_parse(
|
||||
obj: &DerObject<'_>,
|
||||
) -> Result<Vec<u8>, SignedObjectParseError> {
|
||||
fn make_signed_attrs_der_for_signature(full_tlv: &[u8]) -> Result<Vec<u8>, SignedObjectParseError> {
|
||||
// We need the DER encoding of SignedAttributes (SET OF Attribute) as signature input.
|
||||
// The SignedAttributes field in SignerInfo is `[0] IMPLICIT`, so the on-wire bytes start with
|
||||
// a context-specific constructed tag (0xA0 for tag 0). For signature verification, this tag
|
||||
// is replaced with the universal SET tag (0x31), leaving length+content unchanged.
|
||||
//
|
||||
let mut cs_der = obj
|
||||
.to_vec()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
let mut cs_der = full_tlv.to_vec();
|
||||
if cs_der.is_empty() {
|
||||
return Err(SignedObjectParseError::Parse(
|
||||
"signedAttrs encoding is empty".into(),
|
||||
@ -953,32 +970,162 @@ fn make_signed_attrs_der_for_signature_parse(
|
||||
Ok(cs_der)
|
||||
}
|
||||
|
||||
fn oid_to_string_parse(obj: &DerObject<'_>) -> Result<String, SignedObjectParseError> {
|
||||
let oid = obj
|
||||
.as_oid()
|
||||
fn take_oid_string(seq: &mut DerReader<'_>) -> Result<String, SignedObjectParseError> {
|
||||
let oid = seq
|
||||
.take_tag(0x06)
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
Ok(oid.to_id_string())
|
||||
Ok(oid_value_bytes_to_string(oid))
|
||||
}
|
||||
|
||||
fn parse_algorithm_identifier_parse(
|
||||
obj: &DerObject<'_>,
|
||||
fn oid_value_bytes_to_string(oid_value: &[u8]) -> String {
|
||||
if oid_value == OID_SHA256_RAW {
|
||||
return OID_SHA256.to_string();
|
||||
}
|
||||
if oid_value == OID_SIGNED_DATA_RAW {
|
||||
return OID_SIGNED_DATA.to_string();
|
||||
}
|
||||
if oid_value == OID_CMS_ATTR_CONTENT_TYPE_RAW {
|
||||
return OID_CMS_ATTR_CONTENT_TYPE.to_string();
|
||||
}
|
||||
if oid_value == OID_CMS_ATTR_MESSAGE_DIGEST_RAW {
|
||||
return OID_CMS_ATTR_MESSAGE_DIGEST.to_string();
|
||||
}
|
||||
if oid_value == OID_CMS_ATTR_SIGNING_TIME_RAW {
|
||||
return OID_CMS_ATTR_SIGNING_TIME.to_string();
|
||||
}
|
||||
if oid_value == OID_RSA_ENCRYPTION_RAW {
|
||||
return OID_RSA_ENCRYPTION.to_string();
|
||||
}
|
||||
if oid_value == OID_SHA256_WITH_RSA_ENCRYPTION_RAW {
|
||||
return OID_SHA256_WITH_RSA_ENCRYPTION.to_string();
|
||||
}
|
||||
if oid_value == OID_CT_RPKI_MANIFEST_RAW {
|
||||
return OID_CT_RPKI_MANIFEST.to_string();
|
||||
}
|
||||
if oid_value == OID_CT_ROUTE_ORIGIN_AUTHZ_RAW {
|
||||
return OID_CT_ROUTE_ORIGIN_AUTHZ.to_string();
|
||||
}
|
||||
if oid_value == OID_CT_ASPA_RAW {
|
||||
return OID_CT_ASPA.to_string();
|
||||
}
|
||||
decode_oid_to_dotted_string(oid_value)
|
||||
}
|
||||
|
||||
fn decode_oid_to_dotted_string(value: &[u8]) -> String {
|
||||
if value.is_empty() {
|
||||
return "<empty-oid>".into();
|
||||
}
|
||||
let first = value[0];
|
||||
let a = (first / 40) as u32;
|
||||
let b = (first % 40) as u32;
|
||||
let mut out = String::new();
|
||||
out.push_str(&a.to_string());
|
||||
out.push('.');
|
||||
out.push_str(&b.to_string());
|
||||
|
||||
let mut idx = 1usize;
|
||||
while idx < value.len() {
|
||||
let mut v: u32 = 0;
|
||||
loop {
|
||||
if idx >= value.len() {
|
||||
out.push_str(".<truncated>");
|
||||
return out;
|
||||
}
|
||||
let byte = value[idx];
|
||||
idx += 1;
|
||||
v = (v << 7) | (byte as u32 & 0x7F);
|
||||
if (byte & 0x80) == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
out.push('.');
|
||||
out.push_str(&v.to_string());
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn parse_algorithm_identifier_cursor(
|
||||
mut seq: DerReader<'_>,
|
||||
) -> Result<(String, bool), SignedObjectParseError> {
|
||||
let seq = obj
|
||||
.as_sequence()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
if seq.is_empty() || seq.len() > 2 {
|
||||
if seq.is_empty() {
|
||||
return Err(SignedObjectParseError::Parse(
|
||||
"AlgorithmIdentifier must be SEQUENCE of 1..2".into(),
|
||||
));
|
||||
}
|
||||
let oid = oid_to_string_parse(&seq[0])?;
|
||||
let params_ok = match seq.get(1) {
|
||||
None => true,
|
||||
Some(p) => matches!(p.content, der_parser::ber::BerObjectContent::Null),
|
||||
let oid = take_oid_string(&mut seq)?;
|
||||
let params_ok = if seq.is_empty() {
|
||||
true
|
||||
} else {
|
||||
let (tag, value) = seq
|
||||
.take_any()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
tag == 0x05 && value.is_empty()
|
||||
};
|
||||
if !seq.is_empty() {
|
||||
return Err(SignedObjectParseError::Parse(
|
||||
"AlgorithmIdentifier must be SEQUENCE of 1..2".into(),
|
||||
));
|
||||
}
|
||||
Ok((oid, params_ok))
|
||||
}
|
||||
|
||||
fn parse_utctime(value: &[u8]) -> Result<time::OffsetDateTime, SignedObjectValidateError> {
|
||||
let s = std::str::from_utf8(value).map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
||||
if !s.ends_with('Z') {
|
||||
return Err(SignedObjectValidateError::InvalidSigningTimeValue);
|
||||
}
|
||||
let digits = &s[..s.len() - 1];
|
||||
if digits.len() != 10 && digits.len() != 12 {
|
||||
return Err(SignedObjectValidateError::InvalidSigningTimeValue);
|
||||
}
|
||||
if !digits.as_bytes().iter().all(|b| b.is_ascii_digit()) {
|
||||
return Err(SignedObjectValidateError::InvalidSigningTimeValue);
|
||||
}
|
||||
let yy: i32 = digits[0..2].parse().map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
||||
let year = if yy <= 49 { 2000 + yy } else { 1900 + yy };
|
||||
let mon: u8 = digits[2..4].parse().map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
||||
let day: u8 = digits[4..6].parse().map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
||||
let hour: u8 = digits[6..8].parse().map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
||||
let min: u8 = digits[8..10].parse().map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
||||
let sec: u8 = if digits.len() == 12 {
|
||||
digits[10..12].parse().map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let month = time::Month::try_from(mon).map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
||||
let date = time::Date::from_calendar_date(year, month, day).map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
||||
let time = time::Time::from_hms(hour, min, sec).map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
||||
Ok(time::OffsetDateTime::new_utc(date, time))
|
||||
}
|
||||
|
||||
fn parse_generalized_time(value: &[u8]) -> Result<time::OffsetDateTime, SignedObjectValidateError> {
|
||||
let s = std::str::from_utf8(value).map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
||||
if !s.ends_with('Z') {
|
||||
return Err(SignedObjectValidateError::InvalidSigningTimeValue);
|
||||
}
|
||||
let digits = &s[..s.len() - 1];
|
||||
if digits.len() != 12 && digits.len() != 14 {
|
||||
return Err(SignedObjectValidateError::InvalidSigningTimeValue);
|
||||
}
|
||||
if !digits.as_bytes().iter().all(|b| b.is_ascii_digit()) {
|
||||
return Err(SignedObjectValidateError::InvalidSigningTimeValue);
|
||||
}
|
||||
let year: i32 = digits[0..4].parse().map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
||||
let mon: u8 = digits[4..6].parse().map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
||||
let day: u8 = digits[6..8].parse().map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
||||
let hour: u8 = digits[8..10].parse().map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
||||
let min: u8 = digits[10..12].parse().map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
||||
let sec: u8 = if digits.len() == 14 {
|
||||
digits[12..14].parse().map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let month = time::Month::try_from(mon).map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
||||
let date = time::Date::from_calendar_date(year, month, day).map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
||||
let time = time::Time::from_hms(hour, min, sec).map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
||||
Ok(time::OffsetDateTime::new_utc(date, time))
|
||||
}
|
||||
|
||||
fn strip_leading_zeros(bytes: &[u8]) -> &[u8] {
|
||||
let mut idx = 0;
|
||||
while idx < bytes.len() && bytes[idx] == 0 {
|
||||
|
||||
@ -211,7 +211,7 @@ impl TaCertificateParsed {
|
||||
return Err(TaCertificateProfileError::NotCa);
|
||||
}
|
||||
|
||||
if rc_ca.tbs.issuer_dn != rc_ca.tbs.subject_dn {
|
||||
if rc_ca.tbs.issuer_name != rc_ca.tbs.subject_name {
|
||||
return Err(TaCertificateProfileError::NotSelfSignedIssuerSubject);
|
||||
}
|
||||
|
||||
|
||||
@ -78,23 +78,23 @@ pub fn ca_instance_uris_from_ca_certificate(
|
||||
|
||||
for ad in access_descriptions {
|
||||
if ad.access_method_oid == OID_AD_CA_REPOSITORY {
|
||||
let u = ad.access_location.to_string();
|
||||
if ad.access_location.scheme() != "rsync" {
|
||||
return Err(CaInstanceUrisError::CaRepositoryNotRsync(u));
|
||||
let u = ad.access_location.as_str();
|
||||
if !u.starts_with("rsync://") {
|
||||
return Err(CaInstanceUrisError::CaRepositoryNotRsync(u.to_string()));
|
||||
}
|
||||
ca_repo.get_or_insert(u);
|
||||
ca_repo.get_or_insert(u.to_string());
|
||||
} else if ad.access_method_oid == OID_AD_RPKI_MANIFEST {
|
||||
let u = ad.access_location.to_string();
|
||||
if ad.access_location.scheme() != "rsync" {
|
||||
return Err(CaInstanceUrisError::RpkiManifestNotRsync(u));
|
||||
let u = ad.access_location.as_str();
|
||||
if !u.starts_with("rsync://") {
|
||||
return Err(CaInstanceUrisError::RpkiManifestNotRsync(u.to_string()));
|
||||
}
|
||||
manifest.get_or_insert(u);
|
||||
manifest.get_or_insert(u.to_string());
|
||||
} else if ad.access_method_oid == OID_AD_RPKI_NOTIFY {
|
||||
let u = ad.access_location.to_string();
|
||||
if ad.access_location.scheme() != "https" {
|
||||
return Err(CaInstanceUrisError::RpkiNotifyNotHttps(u));
|
||||
let u = ad.access_location.as_str();
|
||||
if !u.starts_with("https://") {
|
||||
return Err(CaInstanceUrisError::RpkiNotifyNotHttps(u.to_string()));
|
||||
}
|
||||
notify.get_or_insert(u);
|
||||
notify.get_or_insert(u.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use crate::data_model::common::BigUnsigned;
|
||||
use crate::data_model::crl::{CrlDecodeError, CrlVerifyError, RpkixCrl};
|
||||
use crate::data_model::oid::OID_KEY_USAGE;
|
||||
use crate::data_model::oid::OID_KEY_USAGE_RAW;
|
||||
use crate::data_model::rc::{
|
||||
AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpResourceSet, ResourceCertKind,
|
||||
ResourceCertificate, ResourceCertificateDecodeError,
|
||||
@ -135,10 +135,10 @@ pub fn validate_subordinate_ca_cert(
|
||||
return Err(CaPathError::IssuerNotCa);
|
||||
}
|
||||
|
||||
if child_ca.tbs.issuer_dn != issuer_ca.tbs.subject_dn {
|
||||
if child_ca.tbs.issuer_name != issuer_ca.tbs.subject_name {
|
||||
return Err(CaPathError::IssuerSubjectMismatch {
|
||||
child_issuer_dn: child_ca.tbs.issuer_dn.clone(),
|
||||
issuer_subject_dn: issuer_ca.tbs.subject_dn.clone(),
|
||||
child_issuer_dn: child_ca.tbs.issuer_name.to_string(),
|
||||
issuer_subject_dn: issuer_ca.tbs.subject_name.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -247,7 +247,7 @@ fn validate_child_ca_key_usage(child_ca_der: &[u8]) -> Result<(), CaPathError> {
|
||||
|
||||
let mut ku_critical: Option<bool> = None;
|
||||
for ext in cert.extensions() {
|
||||
if ext.oid.to_id_string() == OID_KEY_USAGE {
|
||||
if ext.oid.as_bytes() == OID_KEY_USAGE_RAW {
|
||||
ku_critical = Some(ext.critical);
|
||||
break;
|
||||
}
|
||||
@ -695,9 +695,8 @@ mod tests {
|
||||
use crate::data_model::rc::{
|
||||
RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
|
||||
};
|
||||
use crate::data_model::common::X509NameDer;
|
||||
use der_parser::num_bigint::BigUint;
|
||||
use url::Url;
|
||||
|
||||
fn dummy_cert(
|
||||
kind: ResourceCertKind,
|
||||
subject_dn: &str,
|
||||
@ -707,16 +706,8 @@ mod tests {
|
||||
aia: Option<Vec<&str>>,
|
||||
crldp: Option<Vec<&str>>,
|
||||
) -> ResourceCertificate {
|
||||
let aia = aia.map(|v| {
|
||||
v.into_iter()
|
||||
.map(|s| Url::parse(s).expect("url"))
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
let crldp = crldp.map(|v| {
|
||||
v.into_iter()
|
||||
.map(|s| Url::parse(s).expect("url"))
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
let aia = aia.map(|v| v.into_iter().map(|s| s.to_string()).collect::<Vec<_>>());
|
||||
let crldp = crldp.map(|v| v.into_iter().map(|s| s.to_string()).collect::<Vec<_>>());
|
||||
|
||||
ResourceCertificate {
|
||||
raw_der: Vec::new(),
|
||||
@ -725,8 +716,8 @@ mod tests {
|
||||
version: 2,
|
||||
serial_number: BigUint::from(1u8),
|
||||
signature_algorithm: "1.2.840.113549.1.1.11".to_string(),
|
||||
issuer_dn: issuer_dn.to_string(),
|
||||
subject_dn: subject_dn.to_string(),
|
||||
issuer_name: X509NameDer(issuer_dn.as_bytes().to_vec()),
|
||||
subject_name: X509NameDer(subject_dn.as_bytes().to_vec()),
|
||||
validity_not_before: time::OffsetDateTime::UNIX_EPOCH,
|
||||
validity_not_after: time::OffsetDateTime::UNIX_EPOCH,
|
||||
subject_public_key_info: Vec::new(),
|
||||
|
||||
@ -112,10 +112,10 @@ pub fn validate_ee_cert_path(
|
||||
return Err(CertPathError::IssuerNotCa);
|
||||
}
|
||||
|
||||
if ee.tbs.issuer_dn != issuer_ca.tbs.subject_dn {
|
||||
if ee.tbs.issuer_name != issuer_ca.tbs.subject_name {
|
||||
return Err(CertPathError::IssuerSubjectMismatch {
|
||||
ee_issuer_dn: ee.tbs.issuer_dn.clone(),
|
||||
issuer_subject_dn: issuer_ca.tbs.subject_dn.clone(),
|
||||
ee_issuer_dn: ee.tbs.issuer_name.to_string(),
|
||||
issuer_subject_dn: issuer_ca.tbs.subject_name.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -212,7 +212,7 @@ fn validate_ee_key_usage(ee_cert_der: &[u8]) -> Result<(), CertPathError> {
|
||||
|
||||
let mut ku_critical: Option<bool> = None;
|
||||
for ext in cert.extensions() {
|
||||
if ext.oid.to_id_string() == crate::data_model::oid::OID_KEY_USAGE {
|
||||
if ext.oid.as_bytes() == crate::data_model::oid::OID_KEY_USAGE_RAW {
|
||||
ku_critical = Some(ext.critical);
|
||||
break;
|
||||
}
|
||||
@ -302,8 +302,8 @@ mod tests {
|
||||
use crate::data_model::rc::{
|
||||
RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
|
||||
};
|
||||
use crate::data_model::common::X509NameDer;
|
||||
use der_parser::num_bigint::BigUint;
|
||||
use url::Url;
|
||||
|
||||
fn dummy_cert(
|
||||
kind: ResourceCertKind,
|
||||
@ -314,16 +314,8 @@ mod tests {
|
||||
aia: Option<Vec<&str>>,
|
||||
crldp: Option<Vec<&str>>,
|
||||
) -> ResourceCertificate {
|
||||
let aia = aia.map(|v| {
|
||||
v.into_iter()
|
||||
.map(|s| Url::parse(s).expect("url"))
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
let crldp = crldp.map(|v| {
|
||||
v.into_iter()
|
||||
.map(|s| Url::parse(s).expect("url"))
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
let aia = aia.map(|v| v.into_iter().map(|s| s.to_string()).collect::<Vec<_>>());
|
||||
let crldp = crldp.map(|v| v.into_iter().map(|s| s.to_string()).collect::<Vec<_>>());
|
||||
ResourceCertificate {
|
||||
raw_der: Vec::new(),
|
||||
kind,
|
||||
@ -331,8 +323,8 @@ mod tests {
|
||||
version: 2,
|
||||
serial_number: BigUint::from(1u8),
|
||||
signature_algorithm: "1.2.840.113549.1.1.11".to_string(),
|
||||
issuer_dn: issuer_dn.to_string(),
|
||||
subject_dn: subject_dn.to_string(),
|
||||
issuer_name: X509NameDer(issuer_dn.as_bytes().to_vec()),
|
||||
subject_name: X509NameDer(subject_dn.as_bytes().to_vec()),
|
||||
validity_not_before: time::OffsetDateTime::UNIX_EPOCH,
|
||||
validity_not_after: time::OffsetDateTime::UNIX_EPOCH,
|
||||
subject_public_key_info: Vec::new(),
|
||||
|
||||
@ -462,7 +462,7 @@ fn process_aspa_with_issuer(
|
||||
}
|
||||
|
||||
fn choose_crl_for_certificate(
|
||||
crldp_uris: Option<&Vec<url::Url>>,
|
||||
crldp_uris: Option<&Vec<String>>,
|
||||
crl_files: &[(String, Vec<u8>)],
|
||||
) -> Result<(String, Vec<u8>), ObjectValidateError> {
|
||||
if crl_files.is_empty() {
|
||||
|
||||
933
tests/bench_stage2_collect_selected_der_v2.rs
Normal file
933
tests/bench_stage2_collect_selected_der_v2.rs
Normal file
@ -0,0 +1,933 @@
|
||||
use serde::Serialize;
|
||||
use sha2::Digest;
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use rpki::data_model::aspa::AspaObject;
|
||||
use rpki::data_model::crl::RpkixCrl;
|
||||
use rpki::data_model::manifest::ManifestObject;
|
||||
use rpki::data_model::rc::{AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpResourceSet, ResourceCertificate};
|
||||
use rpki::data_model::roa::RoaObject;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
enum ObjType {
|
||||
Cer,
|
||||
Crl,
|
||||
Manifest,
|
||||
Roa,
|
||||
Aspa,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
enum Part {
|
||||
PackManifest,
|
||||
PackCrl,
|
||||
StoredObject { index: u32 },
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct CandidateRef {
|
||||
pack_index: u32,
|
||||
part: Part,
|
||||
size_bytes: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
struct SampleMeta {
|
||||
obj_type: ObjType,
|
||||
label: String,
|
||||
file_rel_path: String,
|
||||
size_bytes: u32,
|
||||
sha256_hex: String,
|
||||
|
||||
// Provenance (relative to BENCH_STORE_DIR)
|
||||
pack_rel_path: String,
|
||||
pack_update_time_rfc3339_utc: String,
|
||||
manifest_uri: String,
|
||||
object_uri: String,
|
||||
|
||||
// From StoredManifest (if available)
|
||||
manifest_this_update_rfc3339_utc: String,
|
||||
manifest_not_after_rfc3339_utc: String,
|
||||
|
||||
metrics: Metrics,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
enum Metrics {
|
||||
Cer {
|
||||
spki_len: u32,
|
||||
ext_count: u32,
|
||||
ip_resource_count: u32,
|
||||
as_resource_count: u32,
|
||||
},
|
||||
Crl {
|
||||
revoked_count: u32,
|
||||
},
|
||||
Manifest {
|
||||
file_count: u32,
|
||||
},
|
||||
Roa {
|
||||
addr_family_count: u32,
|
||||
prefix_count: u32,
|
||||
max_length_present: u32,
|
||||
},
|
||||
Aspa {
|
||||
provider_count: u32,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
struct SamplesManifest {
|
||||
created_at_rfc3339_utc: String,
|
||||
store_dir_hint: String,
|
||||
per_type: BTreeMap<String, u32>,
|
||||
samples: Vec<SampleMeta>,
|
||||
}
|
||||
|
||||
fn env_string(name: &str) -> Option<String> {
|
||||
std::env::var(name).ok().filter(|s| !s.trim().is_empty())
|
||||
}
|
||||
|
||||
fn env_u64_opt(name: &str) -> Option<u64> {
|
||||
std::env::var(name).ok().and_then(|s| s.parse::<u64>().ok())
|
||||
}
|
||||
|
||||
fn env_bool(name: &str) -> bool {
|
||||
matches!(
|
||||
std::env::var(name).as_deref(),
|
||||
Ok("1") | Ok("true") | Ok("TRUE") | Ok("yes") | Ok("YES")
|
||||
)
|
||||
}
|
||||
|
||||
fn create_parent_dirs(path: &Path) {
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent).unwrap_or_else(|e| {
|
||||
panic!("create_dir_all {}: {e}", parent.display());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn walk_collect_files(root: &Path, out: &mut Vec<PathBuf>) -> std::io::Result<()> {
|
||||
for entry in std::fs::read_dir(root)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
let meta = entry.metadata()?;
|
||||
if meta.is_dir() {
|
||||
walk_collect_files(&path, out)?;
|
||||
continue;
|
||||
}
|
||||
if meta.is_file() {
|
||||
out.push(path);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ext_from_uri_bytes(uri: &[u8]) -> Option<&[u8]> {
|
||||
let mut last_dot = None;
|
||||
for (i, &b) in uri.iter().enumerate() {
|
||||
if b == b'.' {
|
||||
last_dot = Some(i);
|
||||
}
|
||||
}
|
||||
let dot = last_dot?;
|
||||
if dot + 1 >= uri.len() {
|
||||
return None;
|
||||
}
|
||||
Some(&uri[dot + 1..])
|
||||
}
|
||||
|
||||
fn ext_matches_ascii_case_insensitive(ext: &[u8], needle_lower: &[u8]) -> bool {
|
||||
if ext.len() != needle_lower.len() {
|
||||
return false;
|
||||
}
|
||||
ext.iter()
|
||||
.zip(needle_lower.iter())
|
||||
.all(|(&a, &b)| a.to_ascii_lowercase() == b)
|
||||
}
|
||||
|
||||
fn percentile_index(len: usize, q: f64) -> usize {
|
||||
if len == 0 {
|
||||
return 0;
|
||||
}
|
||||
if len == 1 {
|
||||
return 0;
|
||||
}
|
||||
let q = q.clamp(0.0, 1.0);
|
||||
(((len - 1) as f64) * q).round() as usize
|
||||
}
|
||||
|
||||
fn quantile_labels() -> Vec<(&'static str, f64)> {
|
||||
vec![
|
||||
("min", 0.0),
|
||||
("p01", 0.01),
|
||||
("p10", 0.10),
|
||||
("p25", 0.25),
|
||||
("p50", 0.50),
|
||||
("p75", 0.75),
|
||||
("p90", 0.90),
|
||||
("p95", 0.95),
|
||||
("p99", 0.99),
|
||||
("max", 1.0),
|
||||
]
|
||||
}
|
||||
|
||||
fn fmt_rfc3339_utc(t: time::OffsetDateTime) -> String {
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
t.to_offset(time::UtcOffset::UTC)
|
||||
.format(&Rfc3339)
|
||||
.expect("format RFC3339")
|
||||
}
|
||||
|
||||
fn scan_pack_candidates(
|
||||
pack_index: u32,
|
||||
bytes: &[u8],
|
||||
out_cer: &mut Vec<CandidateRef>,
|
||||
out_crl: &mut Vec<CandidateRef>,
|
||||
out_manifest: &mut Vec<CandidateRef>,
|
||||
out_roa: &mut Vec<CandidateRef>,
|
||||
out_aspa: &mut Vec<CandidateRef>,
|
||||
) -> Result<(), String> {
|
||||
let mut cur = ByteCursor::new(bytes);
|
||||
|
||||
let _version = cur.read_u8()?;
|
||||
cur.skip_string_u32()?; // manifest_uri
|
||||
cur.skip_option_string_u32()?; // rpki_notify
|
||||
|
||||
let update_kind = cur.read_u8()?;
|
||||
cur.skip_i64()?; // update time
|
||||
if update_kind != 0 {
|
||||
// LastAttempt: no StoredManifest + objects
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
cur.skip_i64()?; // not_after
|
||||
cur.skip(20)?; // manifest_number
|
||||
cur.skip_i64()?; // this_update
|
||||
cur.skip_string_u32()?; // ca_repository
|
||||
|
||||
let mft_len = cur.peek_u64_as_usize()?;
|
||||
cur.skip_bytes_u64()?; // manifest DER
|
||||
out_manifest.push(CandidateRef {
|
||||
pack_index,
|
||||
part: Part::PackManifest,
|
||||
size_bytes: mft_len as u32,
|
||||
});
|
||||
|
||||
cur.skip_string_u32()?; // crl_uri
|
||||
let crl_len = cur.peek_u64_as_usize()?;
|
||||
cur.skip_bytes_u64()?;
|
||||
out_crl.push(CandidateRef {
|
||||
pack_index,
|
||||
part: Part::PackCrl,
|
||||
size_bytes: crl_len as u32,
|
||||
});
|
||||
|
||||
let mut object_index = 0u32;
|
||||
while cur.remaining() > 0 {
|
||||
let uri_bytes = cur.read_bytes_u32()?;
|
||||
let ext = ext_from_uri_bytes(uri_bytes);
|
||||
|
||||
let hash_type = cur.read_u8()?;
|
||||
if hash_type == 1 {
|
||||
cur.skip(32)?;
|
||||
} else if hash_type != 0 {
|
||||
return Err(format!("unsupported stored object hash_type {hash_type}"));
|
||||
}
|
||||
|
||||
let content_len = cur.peek_u64_as_usize()?;
|
||||
cur.skip_bytes_u64()?;
|
||||
|
||||
let cand = CandidateRef {
|
||||
pack_index,
|
||||
part: Part::StoredObject { index: object_index },
|
||||
size_bytes: content_len as u32,
|
||||
};
|
||||
if let Some(ext) = ext {
|
||||
if ext_matches_ascii_case_insensitive(ext, b"cer") {
|
||||
out_cer.push(cand);
|
||||
} else if ext_matches_ascii_case_insensitive(ext, b"roa") {
|
||||
out_roa.push(cand);
|
||||
} else if ext_matches_ascii_case_insensitive(ext, b"asa") {
|
||||
out_aspa.push(cand);
|
||||
}
|
||||
}
|
||||
|
||||
object_index += 1;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct Extracted {
|
||||
pack_update_time_utc: time::OffsetDateTime,
|
||||
manifest_uri: String,
|
||||
manifest_this_update_utc: time::OffsetDateTime,
|
||||
manifest_not_after_utc: time::OffsetDateTime,
|
||||
object_uri: String,
|
||||
der: Vec<u8>,
|
||||
}
|
||||
|
||||
fn extract_candidate(pack_path: &Path, cand: CandidateRef) -> Result<Extracted, String> {
|
||||
let bytes = std::fs::read(pack_path).map_err(|e| format!("read {}: {e}", pack_path.display()))?;
|
||||
let mut cur = ByteCursor::new(bytes.as_slice());
|
||||
|
||||
let _version = cur.read_u8()?;
|
||||
let manifest_uri = cur.read_string_u32()?;
|
||||
cur.skip_option_string_u32()?; // rpki_notify
|
||||
|
||||
let update_kind = cur.read_u8()?;
|
||||
let pack_update_time_utc = cur.read_time_utc_i64_be()?;
|
||||
if update_kind != 0 {
|
||||
return Err("pack is LastAttempt".to_string());
|
||||
}
|
||||
|
||||
let manifest_not_after_utc = cur.read_time_utc_i64_be()?;
|
||||
cur.skip(20)?; // manifest_number
|
||||
let manifest_this_update_utc = cur.read_time_utc_i64_be()?;
|
||||
cur.skip_string_u32()?; // ca_repository
|
||||
|
||||
let manifest_der = cur.read_bytes_u64_vec()?; // manifest DER
|
||||
|
||||
if cand.part == Part::PackManifest {
|
||||
return Ok(Extracted {
|
||||
pack_update_time_utc,
|
||||
manifest_uri: manifest_uri.clone(),
|
||||
manifest_this_update_utc,
|
||||
manifest_not_after_utc,
|
||||
object_uri: manifest_uri,
|
||||
der: manifest_der,
|
||||
});
|
||||
}
|
||||
|
||||
let crl_uri = cur.read_string_u32()?;
|
||||
let crl_der = cur.read_bytes_u64_vec()?;
|
||||
|
||||
match cand.part {
|
||||
Part::PackManifest => unreachable!("handled above"),
|
||||
Part::PackCrl => Ok(Extracted {
|
||||
pack_update_time_utc,
|
||||
manifest_uri,
|
||||
manifest_this_update_utc,
|
||||
manifest_not_after_utc,
|
||||
object_uri: crl_uri,
|
||||
der: crl_der,
|
||||
}),
|
||||
Part::StoredObject { index } => {
|
||||
let mut object_index = 0u32;
|
||||
while cur.remaining() > 0 {
|
||||
let uri = cur.read_string_u32()?;
|
||||
let hash_type = cur.read_u8()?;
|
||||
if hash_type == 1 {
|
||||
cur.skip(32)?;
|
||||
} else if hash_type != 0 {
|
||||
return Err(format!("unsupported stored object hash_type {hash_type}"));
|
||||
}
|
||||
let content = cur.read_bytes_u64_vec()?;
|
||||
if object_index == index {
|
||||
return Ok(Extracted {
|
||||
pack_update_time_utc,
|
||||
manifest_uri,
|
||||
manifest_this_update_utc,
|
||||
manifest_not_after_utc,
|
||||
object_uri: uri,
|
||||
der: content,
|
||||
});
|
||||
}
|
||||
object_index += 1;
|
||||
}
|
||||
Err(format!("stored object index {index} out of range"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ip_resource_count(set: &IpResourceSet) -> u32 {
|
||||
let mut n = 0u32;
|
||||
for fam in &set.families {
|
||||
match &fam.choice {
|
||||
IpAddressChoice::Inherit => n = n.saturating_add(1),
|
||||
IpAddressChoice::AddressesOrRanges(items) => {
|
||||
n = n.saturating_add(items.len() as u32)
|
||||
}
|
||||
}
|
||||
}
|
||||
n
|
||||
}
|
||||
|
||||
fn as_choice_count(choice: &AsIdentifierChoice) -> u32 {
|
||||
match choice {
|
||||
AsIdentifierChoice::Inherit => 1,
|
||||
AsIdentifierChoice::AsIdsOrRanges(items) => items.len() as u32,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_resource_count(set: &AsResourceSet) -> u32 {
|
||||
let mut n = 0u32;
|
||||
if let Some(c) = set.asnum.as_ref() {
|
||||
n = n.saturating_add(as_choice_count(c));
|
||||
}
|
||||
if let Some(c) = set.rdi.as_ref() {
|
||||
n = n.saturating_add(as_choice_count(c));
|
||||
}
|
||||
n
|
||||
}
|
||||
|
||||
fn metrics_for(obj_type: ObjType, der: &[u8]) -> Result<Metrics, String> {
|
||||
match obj_type {
|
||||
ObjType::Cer => {
|
||||
let parsed = ResourceCertificate::parse_der(der).map_err(|e| e.to_string())?;
|
||||
let spki_len = parsed.subject_public_key_info.len() as u32;
|
||||
let ext_count = (parsed.extensions.basic_constraints_ca.len()
|
||||
+ parsed.extensions.subject_key_identifier.len()
|
||||
+ parsed.extensions.authority_key_identifier.len()
|
||||
+ parsed.extensions.crl_distribution_points.len()
|
||||
+ parsed.extensions.authority_info_access.len()
|
||||
+ parsed.extensions.subject_info_access.len()
|
||||
+ parsed.extensions.certificate_policies.len()
|
||||
+ parsed.extensions.ip_resources.len()
|
||||
+ parsed.extensions.as_resources.len()) as u32;
|
||||
let cert = parsed.validate_profile().map_err(|e| e.to_string())?;
|
||||
let ip_resource_count = cert
|
||||
.tbs
|
||||
.extensions
|
||||
.ip_resources
|
||||
.as_ref()
|
||||
.map(ip_resource_count)
|
||||
.unwrap_or(0);
|
||||
let as_resource_count = cert
|
||||
.tbs
|
||||
.extensions
|
||||
.as_resources
|
||||
.as_ref()
|
||||
.map(as_resource_count)
|
||||
.unwrap_or(0);
|
||||
Ok(Metrics::Cer {
|
||||
spki_len,
|
||||
ext_count,
|
||||
ip_resource_count,
|
||||
as_resource_count,
|
||||
})
|
||||
}
|
||||
ObjType::Crl => {
|
||||
let parsed = RpkixCrl::parse_der(der).map_err(|e| e.to_string())?;
|
||||
let crl = parsed.validate_profile().map_err(|e| e.to_string())?;
|
||||
Ok(Metrics::Crl {
|
||||
revoked_count: crl.revoked_certs.len() as u32,
|
||||
})
|
||||
}
|
||||
ObjType::Manifest => {
|
||||
let mft = ManifestObject::decode_der(der).map_err(|e| e.to_string())?;
|
||||
Ok(Metrics::Manifest {
|
||||
file_count: mft.manifest.file_count() as u32,
|
||||
})
|
||||
}
|
||||
ObjType::Roa => {
|
||||
let roa = RoaObject::decode_der(der).map_err(|e| e.to_string())?;
|
||||
let addr_family_count = roa.roa.ip_addr_blocks.len() as u32;
|
||||
let mut prefix_count = 0u32;
|
||||
let mut max_length_present = 0u32;
|
||||
for fam in &roa.roa.ip_addr_blocks {
|
||||
for a in &fam.addresses {
|
||||
prefix_count += 1;
|
||||
if a.max_length.is_some() {
|
||||
max_length_present += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Metrics::Roa {
|
||||
addr_family_count,
|
||||
prefix_count,
|
||||
max_length_present,
|
||||
})
|
||||
}
|
||||
ObjType::Aspa => {
|
||||
let asa = AspaObject::decode_der(der).map_err(|e| e.to_string())?;
|
||||
Ok(Metrics::Aspa {
|
||||
provider_count: asa.aspa.provider_as_ids.len() as u32,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_sample(out_dir: &Path, obj_type: ObjType, label: &str, der: &[u8]) -> PathBuf {
|
||||
let (subdir, ext) = match obj_type {
|
||||
ObjType::Cer => ("cer", "cer"),
|
||||
ObjType::Crl => ("crl", "crl"),
|
||||
ObjType::Manifest => ("manifest", "mft"),
|
||||
ObjType::Roa => ("roa", "roa"),
|
||||
ObjType::Aspa => ("aspa", "asa"),
|
||||
};
|
||||
let path = out_dir.join(subdir).join(format!("{label}.{ext}"));
|
||||
create_parent_dirs(&path);
|
||||
std::fs::write(&path, der).unwrap_or_else(|e| panic!("write {}: {e}", path.display()));
|
||||
path
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "manual: collect representative CER/CRL/MFT/ROA/ASA DER fixtures from routinator stored/ SAP packs"]
|
||||
fn stage2_collect_selected_der_v2_from_routinator_store() {
|
||||
let store_dir = env_string("BENCH_STORE_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| panic!("set BENCH_STORE_DIR to routinator stored/ dir"));
|
||||
let out_dir = env_string("BENCH_OUT_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/benchmark/selected_der_v2")
|
||||
});
|
||||
let max_packs = env_u64_opt("BENCH_MAX_PACKS");
|
||||
let clean = env_bool("BENCH_CLEAN");
|
||||
let dry_run = env_bool("BENCH_DRY_RUN");
|
||||
let verbose = env_bool("BENCH_VERBOSE");
|
||||
|
||||
if clean && !dry_run && out_dir.exists() {
|
||||
std::fs::remove_dir_all(&out_dir)
|
||||
.unwrap_or_else(|e| panic!("remove_dir_all {}: {e}", out_dir.display()));
|
||||
}
|
||||
|
||||
let mut pack_paths = Vec::new();
|
||||
walk_collect_files(&store_dir, &mut pack_paths)
|
||||
.unwrap_or_else(|e| panic!("walk {}: {e}", store_dir.display()));
|
||||
pack_paths.retain(|p| p.extension().and_then(|s| s.to_str()) == Some("mft"));
|
||||
pack_paths.sort();
|
||||
if let Some(max) = max_packs {
|
||||
pack_paths.truncate(max as usize);
|
||||
}
|
||||
|
||||
println!("# Stage2 collect: selected_der_v2 (from routinator stored/ SAP packs)");
|
||||
println!();
|
||||
println!("- store_dir: {}", store_dir.display());
|
||||
println!("- out_dir: {}", out_dir.display());
|
||||
println!("- pack_files_found: {}", pack_paths.len());
|
||||
if let Some(max) = max_packs {
|
||||
println!("- max_packs: {} (truncating input list)", max);
|
||||
}
|
||||
println!("- clean: {}", clean);
|
||||
println!("- dry_run: {}", dry_run);
|
||||
println!();
|
||||
|
||||
let mut cer = Vec::<CandidateRef>::new();
|
||||
let mut crl = Vec::<CandidateRef>::new();
|
||||
let mut mft = Vec::<CandidateRef>::new();
|
||||
let mut roa = Vec::<CandidateRef>::new();
|
||||
let mut asa = Vec::<CandidateRef>::new();
|
||||
|
||||
for (i, p) in pack_paths.iter().enumerate() {
|
||||
let bytes = match std::fs::read(p) {
|
||||
Ok(v) => v,
|
||||
Err(_) => continue,
|
||||
};
|
||||
if let Err(e) = scan_pack_candidates(
|
||||
i as u32,
|
||||
bytes.as_slice(),
|
||||
&mut cer,
|
||||
&mut crl,
|
||||
&mut mft,
|
||||
&mut roa,
|
||||
&mut asa,
|
||||
) {
|
||||
if verbose {
|
||||
eprintln!("# scan error {}: {}", p.display(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut by_type: BTreeMap<ObjType, Vec<CandidateRef>> = BTreeMap::new();
|
||||
by_type.insert(ObjType::Cer, cer);
|
||||
by_type.insert(ObjType::Crl, crl);
|
||||
by_type.insert(ObjType::Manifest, mft);
|
||||
by_type.insert(ObjType::Roa, roa);
|
||||
by_type.insert(ObjType::Aspa, asa);
|
||||
|
||||
let mut samples = Vec::<SampleMeta>::new();
|
||||
let mut per_type_out = BTreeMap::<String, u32>::new();
|
||||
|
||||
for (obj_type, mut cands) in by_type {
|
||||
cands.sort_by(|a, b| {
|
||||
a.size_bytes
|
||||
.cmp(&b.size_bytes)
|
||||
.then_with(|| a.pack_index.cmp(&b.pack_index))
|
||||
.then_with(|| match (a.part, b.part) {
|
||||
(Part::PackManifest, Part::PackManifest) => std::cmp::Ordering::Equal,
|
||||
(Part::PackManifest, _) => std::cmp::Ordering::Less,
|
||||
(_, Part::PackManifest) => std::cmp::Ordering::Greater,
|
||||
(Part::PackCrl, Part::PackCrl) => std::cmp::Ordering::Equal,
|
||||
(Part::PackCrl, Part::StoredObject { .. }) => std::cmp::Ordering::Less,
|
||||
(Part::StoredObject { .. }, Part::PackCrl) => std::cmp::Ordering::Greater,
|
||||
(Part::StoredObject { index: ai }, Part::StoredObject { index: bi }) => ai.cmp(&bi),
|
||||
})
|
||||
});
|
||||
|
||||
println!("## {:?}", obj_type);
|
||||
println!();
|
||||
println!("- candidates: {}", cands.len());
|
||||
if cands.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut selected_sha = HashSet::<String>::new();
|
||||
let mut selected_ptrs = HashSet::<(u32, Part)>::new();
|
||||
|
||||
for (label, q) in quantile_labels() {
|
||||
let target = percentile_index(cands.len(), q);
|
||||
|
||||
let mut found = None;
|
||||
for off in 0.. {
|
||||
let mut tried_any = false;
|
||||
if off == 0 {
|
||||
let j = target as i32;
|
||||
if j < 0 || (j as usize) >= cands.len() {
|
||||
tried_any = false;
|
||||
} else {
|
||||
tried_any = true;
|
||||
let cand = cands[j as usize];
|
||||
if selected_ptrs.contains(&(cand.pack_index, cand.part)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let pack_path = &pack_paths[cand.pack_index as usize];
|
||||
let extracted = match extract_candidate(pack_path.as_path(), cand) {
|
||||
Ok(v) => v,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
if extracted.der.len() != cand.size_bytes as usize {
|
||||
continue;
|
||||
}
|
||||
|
||||
let sha256 = sha2::Sha256::digest(extracted.der.as_slice());
|
||||
let sha256_hex = hex::encode(sha256);
|
||||
if selected_sha.contains(&sha256_hex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let metrics = match metrics_for(obj_type, extracted.der.as_slice()) {
|
||||
Ok(m) => m,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
if !dry_run {
|
||||
let p = write_sample(
|
||||
out_dir.as_path(),
|
||||
obj_type,
|
||||
label,
|
||||
extracted.der.as_slice(),
|
||||
);
|
||||
if verbose {
|
||||
println!(
|
||||
"- wrote {} -> {}",
|
||||
label,
|
||||
p.strip_prefix(PathBuf::from(env!("CARGO_MANIFEST_DIR")))
|
||||
.unwrap_or(&p)
|
||||
.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let pack_rel_path = pack_path
|
||||
.strip_prefix(&store_dir)
|
||||
.unwrap_or(pack_path.as_path())
|
||||
.display()
|
||||
.to_string();
|
||||
|
||||
let file_rel_path = format!(
|
||||
"tests/benchmark/selected_der_v2/{}/{}.{}",
|
||||
match obj_type {
|
||||
ObjType::Cer => "cer",
|
||||
ObjType::Crl => "crl",
|
||||
ObjType::Manifest => "manifest",
|
||||
ObjType::Roa => "roa",
|
||||
ObjType::Aspa => "aspa",
|
||||
},
|
||||
label,
|
||||
match obj_type {
|
||||
ObjType::Cer => "cer",
|
||||
ObjType::Crl => "crl",
|
||||
ObjType::Manifest => "mft",
|
||||
ObjType::Roa => "roa",
|
||||
ObjType::Aspa => "asa",
|
||||
}
|
||||
);
|
||||
|
||||
let sha256_hex_for_set = sha256_hex.clone();
|
||||
samples.push(SampleMeta {
|
||||
obj_type,
|
||||
label: label.to_string(),
|
||||
file_rel_path,
|
||||
size_bytes: cand.size_bytes,
|
||||
sha256_hex,
|
||||
pack_rel_path,
|
||||
pack_update_time_rfc3339_utc: fmt_rfc3339_utc(
|
||||
extracted.pack_update_time_utc,
|
||||
),
|
||||
manifest_uri: extracted.manifest_uri,
|
||||
object_uri: extracted.object_uri,
|
||||
manifest_this_update_rfc3339_utc: fmt_rfc3339_utc(
|
||||
extracted.manifest_this_update_utc,
|
||||
),
|
||||
manifest_not_after_rfc3339_utc: fmt_rfc3339_utc(
|
||||
extracted.manifest_not_after_utc,
|
||||
),
|
||||
metrics,
|
||||
});
|
||||
|
||||
selected_ptrs.insert((cand.pack_index, cand.part));
|
||||
selected_sha.insert(sha256_hex_for_set);
|
||||
found = Some(());
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
for delta in [off as i32, -(off as i32)] {
|
||||
let j = target as i32 + delta;
|
||||
if j < 0 || (j as usize) >= cands.len() {
|
||||
continue;
|
||||
}
|
||||
tried_any = true;
|
||||
let cand = cands[j as usize];
|
||||
if selected_ptrs.contains(&(cand.pack_index, cand.part)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let pack_path = &pack_paths[cand.pack_index as usize];
|
||||
let extracted = match extract_candidate(pack_path.as_path(), cand) {
|
||||
Ok(v) => v,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
if extracted.der.len() != cand.size_bytes as usize {
|
||||
continue;
|
||||
}
|
||||
|
||||
let sha256 = sha2::Sha256::digest(extracted.der.as_slice());
|
||||
let sha256_hex = hex::encode(sha256);
|
||||
if selected_sha.contains(&sha256_hex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let metrics = match metrics_for(obj_type, extracted.der.as_slice()) {
|
||||
Ok(m) => m,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
if !dry_run {
|
||||
let p = write_sample(
|
||||
out_dir.as_path(),
|
||||
obj_type,
|
||||
label,
|
||||
extracted.der.as_slice(),
|
||||
);
|
||||
if verbose {
|
||||
println!(
|
||||
"- wrote {} -> {}",
|
||||
label,
|
||||
p.strip_prefix(PathBuf::from(env!("CARGO_MANIFEST_DIR")))
|
||||
.unwrap_or(&p)
|
||||
.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let pack_rel_path = pack_path
|
||||
.strip_prefix(&store_dir)
|
||||
.unwrap_or(pack_path.as_path())
|
||||
.display()
|
||||
.to_string();
|
||||
|
||||
let file_rel_path = format!(
|
||||
"tests/benchmark/selected_der_v2/{}/{}.{}",
|
||||
match obj_type {
|
||||
ObjType::Cer => "cer",
|
||||
ObjType::Crl => "crl",
|
||||
ObjType::Manifest => "manifest",
|
||||
ObjType::Roa => "roa",
|
||||
ObjType::Aspa => "aspa",
|
||||
},
|
||||
label,
|
||||
match obj_type {
|
||||
ObjType::Cer => "cer",
|
||||
ObjType::Crl => "crl",
|
||||
ObjType::Manifest => "mft",
|
||||
ObjType::Roa => "roa",
|
||||
ObjType::Aspa => "asa",
|
||||
}
|
||||
);
|
||||
|
||||
let sha256_hex_for_set = sha256_hex.clone();
|
||||
samples.push(SampleMeta {
|
||||
obj_type,
|
||||
label: label.to_string(),
|
||||
file_rel_path,
|
||||
size_bytes: cand.size_bytes,
|
||||
sha256_hex,
|
||||
pack_rel_path,
|
||||
pack_update_time_rfc3339_utc: fmt_rfc3339_utc(
|
||||
extracted.pack_update_time_utc,
|
||||
),
|
||||
manifest_uri: extracted.manifest_uri,
|
||||
object_uri: extracted.object_uri,
|
||||
manifest_this_update_rfc3339_utc: fmt_rfc3339_utc(
|
||||
extracted.manifest_this_update_utc,
|
||||
),
|
||||
manifest_not_after_rfc3339_utc: fmt_rfc3339_utc(
|
||||
extracted.manifest_not_after_utc,
|
||||
),
|
||||
metrics,
|
||||
});
|
||||
|
||||
selected_ptrs.insert((cand.pack_index, cand.part));
|
||||
selected_sha.insert(sha256_hex_for_set);
|
||||
found = Some(());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if found.is_some() {
|
||||
break;
|
||||
}
|
||||
if !tried_any {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
per_type_out.insert(format!("{:?}", obj_type).to_lowercase(), selected_sha.len() as u32);
|
||||
println!();
|
||||
}
|
||||
|
||||
samples.sort_by(|a, b| a.obj_type.cmp(&b.obj_type).then_with(|| a.label.cmp(&b.label)));
|
||||
|
||||
let created_at_rfc3339_utc = fmt_rfc3339_utc(time::OffsetDateTime::now_utc());
|
||||
let manifest = SamplesManifest {
|
||||
created_at_rfc3339_utc,
|
||||
store_dir_hint: store_dir.display().to_string(),
|
||||
per_type: per_type_out,
|
||||
samples,
|
||||
};
|
||||
|
||||
if !dry_run {
|
||||
let meta_path = out_dir.join("meta").join("samples.json");
|
||||
create_parent_dirs(&meta_path);
|
||||
let bytes = serde_json::to_vec_pretty(&manifest).expect("encode samples manifest");
|
||||
std::fs::write(&meta_path, bytes)
|
||||
.unwrap_or_else(|e| panic!("write {}: {e}", meta_path.display()));
|
||||
println!("- wrote meta: {}", meta_path.display());
|
||||
}
|
||||
}
|
||||
|
||||
struct ByteCursor<'a> {
|
||||
bytes: &'a [u8],
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl<'a> ByteCursor<'a> {
|
||||
fn new(bytes: &'a [u8]) -> Self {
|
||||
Self { bytes, pos: 0 }
|
||||
}
|
||||
|
||||
fn remaining(&self) -> usize {
|
||||
self.bytes.len().saturating_sub(self.pos)
|
||||
}
|
||||
|
||||
fn skip(&mut self, len: usize) -> Result<(), String> {
|
||||
if len > self.remaining() {
|
||||
return Err("truncated input".to_string());
|
||||
}
|
||||
self.pos += len;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_exact(&mut self, len: usize) -> Result<&'a [u8], String> {
|
||||
if len > self.remaining() {
|
||||
return Err(format!(
|
||||
"truncated input: need {len} bytes, have {}",
|
||||
self.remaining()
|
||||
));
|
||||
}
|
||||
let start = self.pos;
|
||||
self.pos += len;
|
||||
Ok(&self.bytes[start..start + len])
|
||||
}
|
||||
|
||||
fn read_u8(&mut self) -> Result<u8, String> {
|
||||
Ok(self.read_exact(1)?[0])
|
||||
}
|
||||
|
||||
fn read_u32_be(&mut self) -> Result<u32, String> {
|
||||
let b = self.read_exact(4)?;
|
||||
Ok(u32::from_be_bytes([b[0], b[1], b[2], b[3]]))
|
||||
}
|
||||
|
||||
fn read_u64_be(&mut self) -> Result<u64, String> {
|
||||
let b = self.read_exact(8)?;
|
||||
Ok(u64::from_be_bytes([
|
||||
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
|
||||
]))
|
||||
}
|
||||
|
||||
fn peek_u64_as_usize(&mut self) -> Result<usize, String> {
|
||||
let start = self.pos;
|
||||
let v = self.read_u64_be()?;
|
||||
self.pos = start;
|
||||
usize::try_from(v).map_err(|_| format!("length too large: {v}"))
|
||||
}
|
||||
|
||||
fn skip_i64(&mut self) -> Result<(), String> {
|
||||
self.skip(8)
|
||||
}
|
||||
|
||||
fn read_i64_be(&mut self) -> Result<i64, String> {
|
||||
let b = self.read_exact(8)?;
|
||||
Ok(i64::from_be_bytes([
|
||||
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
|
||||
]))
|
||||
}
|
||||
|
||||
fn read_time_utc_i64_be(&mut self) -> Result<time::OffsetDateTime, String> {
|
||||
let ts = self.read_i64_be()?;
|
||||
time::OffsetDateTime::from_unix_timestamp(ts).map_err(|e| {
|
||||
format!("invalid unix timestamp {ts}: {e}")
|
||||
})
|
||||
}
|
||||
|
||||
fn read_bytes_u32(&mut self) -> Result<&'a [u8], String> {
|
||||
let len = self.read_u32_be()? as usize;
|
||||
self.read_exact(len)
|
||||
}
|
||||
|
||||
fn skip_string_u32(&mut self) -> Result<(), String> {
|
||||
let len = self.read_u32_be()? as usize;
|
||||
self.skip(len)
|
||||
}
|
||||
|
||||
fn read_string_u32(&mut self) -> Result<String, String> {
|
||||
let b = self.read_bytes_u32()?;
|
||||
std::str::from_utf8(b)
|
||||
.map(|s| s.to_string())
|
||||
.map_err(|e| format!("invalid UTF-8 string: {e}"))
|
||||
}
|
||||
|
||||
fn skip_option_string_u32(&mut self) -> Result<(), String> {
|
||||
let len = self.read_u32_be()? as usize;
|
||||
if len == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
self.skip(len)
|
||||
}
|
||||
|
||||
fn skip_bytes_u64(&mut self) -> Result<(), String> {
|
||||
let len_u64 = self.read_u64_be()?;
|
||||
let len = usize::try_from(len_u64).map_err(|_| {
|
||||
format!("data block too large for this system: {len_u64}")
|
||||
})?;
|
||||
self.skip(len)
|
||||
}
|
||||
|
||||
fn read_bytes_u64_vec(&mut self) -> Result<Vec<u8>, String> {
|
||||
let len_u64 = self.read_u64_be()?;
|
||||
let len = usize::try_from(len_u64).map_err(|_| {
|
||||
format!("data block too large for this system: {len_u64}")
|
||||
})?;
|
||||
Ok(self.read_exact(len)?.to_vec())
|
||||
}
|
||||
}
|
||||
700
tests/bench_stage2_decode_profile_selected_der_v2.rs
Normal file
700
tests/bench_stage2_decode_profile_selected_der_v2.rs
Normal file
@ -0,0 +1,700 @@
|
||||
use rpki::data_model::aspa::AspaObject;
|
||||
use rpki::data_model::crl::RpkixCrl;
|
||||
use rpki::data_model::manifest::ManifestObject;
|
||||
use rpki::data_model::rc::{AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpResourceSet, ResourceCertificate};
|
||||
use rpki::data_model::roa::RoaObject;
|
||||
|
||||
use rpki::storage::pack::PackFile;
|
||||
use rpki::storage::RocksStore;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Instant;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum ObjType {
|
||||
Cer,
|
||||
Crl,
|
||||
Manifest,
|
||||
Roa,
|
||||
Aspa,
|
||||
}
|
||||
|
||||
impl ObjType {
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
ObjType::Cer => "cer",
|
||||
ObjType::Crl => "crl",
|
||||
ObjType::Manifest => "manifest",
|
||||
ObjType::Roa => "roa",
|
||||
ObjType::Aspa => "aspa",
|
||||
}
|
||||
}
|
||||
|
||||
fn ext(self) -> &'static str {
|
||||
match self {
|
||||
ObjType::Cer => "cer",
|
||||
ObjType::Crl => "crl",
|
||||
ObjType::Manifest => "mft",
|
||||
ObjType::Roa => "roa",
|
||||
ObjType::Aspa => "asa",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Sample {
|
||||
obj_type: ObjType,
|
||||
name: String,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
fn default_samples_dir() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/benchmark/selected_der_v2")
|
||||
}
|
||||
|
||||
fn read_samples(root: &Path) -> Vec<Sample> {
|
||||
let mut out = Vec::new();
|
||||
for obj_type in [
|
||||
ObjType::Cer,
|
||||
ObjType::Crl,
|
||||
ObjType::Manifest,
|
||||
ObjType::Roa,
|
||||
ObjType::Aspa,
|
||||
] {
|
||||
let dir = root.join(obj_type.as_str());
|
||||
let rd = match std::fs::read_dir(&dir) {
|
||||
Ok(rd) => rd,
|
||||
Err(_) => continue,
|
||||
};
|
||||
for ent in rd.flatten() {
|
||||
let path = ent.path();
|
||||
if path.extension().and_then(|s| s.to_str()) != Some(obj_type.ext()) {
|
||||
continue;
|
||||
}
|
||||
let name = path
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
out.push(Sample { obj_type, name, path });
|
||||
}
|
||||
}
|
||||
out.sort_by(|a, b| a.obj_type.cmp(&b.obj_type).then_with(|| a.name.cmp(&b.name)));
|
||||
out
|
||||
}
|
||||
|
||||
fn env_u64(name: &str, default: u64) -> u64 {
|
||||
std::env::var(name)
|
||||
.ok()
|
||||
.and_then(|s| s.parse::<u64>().ok())
|
||||
.unwrap_or(default)
|
||||
}
|
||||
|
||||
fn env_u64_opt(name: &str) -> Option<u64> {
|
||||
std::env::var(name).ok().and_then(|s| s.parse::<u64>().ok())
|
||||
}
|
||||
|
||||
fn env_bool(name: &str) -> bool {
|
||||
matches!(
|
||||
std::env::var(name).as_deref(),
|
||||
Ok("1") | Ok("true") | Ok("TRUE") | Ok("yes") | Ok("YES")
|
||||
)
|
||||
}
|
||||
|
||||
fn env_string(name: &str) -> Option<String> {
|
||||
std::env::var(name).ok().filter(|s| !s.trim().is_empty())
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum BenchMode {
|
||||
Both,
|
||||
DecodeValidate,
|
||||
Landing,
|
||||
}
|
||||
|
||||
impl BenchMode {
|
||||
fn parse(s: &str) -> Result<Self, String> {
|
||||
match s {
|
||||
"" | "both" => Ok(Self::Both),
|
||||
"decode" | "decode_validate" | "decode+validate" => Ok(Self::DecodeValidate),
|
||||
"landing" => Ok(Self::Landing),
|
||||
_ => Err(format!(
|
||||
"invalid BENCH_MODE='{s}', expected one of: both, decode_validate, landing"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn do_decode(self) -> bool {
|
||||
matches!(self, BenchMode::Both | BenchMode::DecodeValidate)
|
||||
}
|
||||
|
||||
fn do_landing(self) -> bool {
|
||||
matches!(self, BenchMode::Both | BenchMode::Landing)
|
||||
}
|
||||
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
BenchMode::Both => "both",
|
||||
BenchMode::DecodeValidate => "decode_validate",
|
||||
BenchMode::Landing => "landing",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn escape_md(s: &str) -> String {
|
||||
s.replace('|', "\\|").replace('\n', " ")
|
||||
}
|
||||
|
||||
fn create_parent_dirs(path: &Path) {
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent).unwrap_or_else(|e| {
|
||||
panic!("create_dir_all {}: {e}", parent.display());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn write_text_file(path: &Path, content: &str) {
|
||||
create_parent_dirs(path);
|
||||
std::fs::write(path, content).unwrap_or_else(|e| panic!("write {}: {e}", path.display()));
|
||||
}
|
||||
|
||||
fn fmt_rfc3339_utc_now() -> String {
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
time::OffsetDateTime::now_utc()
|
||||
.to_offset(time::UtcOffset::UTC)
|
||||
.format(&Rfc3339)
|
||||
.unwrap_or_else(|_| "unknown".to_string())
|
||||
}
|
||||
|
||||
fn choose_iters_adaptive<F: FnMut()>(mut op: F, min_round_ms: u64, max_iters: u64) -> u64 {
|
||||
let min_secs = (min_round_ms as f64) / 1e3;
|
||||
let mut iters: u64 = 1;
|
||||
loop {
|
||||
let start = Instant::now();
|
||||
for _ in 0..iters {
|
||||
op();
|
||||
}
|
||||
let elapsed = start.elapsed().as_secs_f64();
|
||||
if elapsed >= min_secs {
|
||||
return iters;
|
||||
}
|
||||
if iters >= max_iters {
|
||||
return iters;
|
||||
}
|
||||
iters = (iters.saturating_mul(2)).min(max_iters);
|
||||
}
|
||||
}
|
||||
|
||||
fn ip_resource_count(set: &IpResourceSet) -> u64 {
|
||||
let mut n = 0u64;
|
||||
for fam in &set.families {
|
||||
match &fam.choice {
|
||||
IpAddressChoice::Inherit => n = n.saturating_add(1),
|
||||
IpAddressChoice::AddressesOrRanges(items) => n = n.saturating_add(items.len() as u64),
|
||||
}
|
||||
}
|
||||
n
|
||||
}
|
||||
|
||||
fn as_choice_count(choice: &AsIdentifierChoice) -> u64 {
|
||||
match choice {
|
||||
AsIdentifierChoice::Inherit => 1,
|
||||
AsIdentifierChoice::AsIdsOrRanges(items) => items.len() as u64,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_resource_count(set: &AsResourceSet) -> u64 {
|
||||
let mut n = 0u64;
|
||||
if let Some(c) = set.asnum.as_ref() {
|
||||
n = n.saturating_add(as_choice_count(c));
|
||||
}
|
||||
if let Some(c) = set.rdi.as_ref() {
|
||||
n = n.saturating_add(as_choice_count(c));
|
||||
}
|
||||
n
|
||||
}
|
||||
|
||||
fn complexity_main(obj_type: ObjType, bytes: &[u8]) -> u64 {
|
||||
match obj_type {
|
||||
ObjType::Cer => {
|
||||
let cert = ResourceCertificate::decode_der(bytes).expect("decode cert");
|
||||
let ip = cert
|
||||
.tbs
|
||||
.extensions
|
||||
.ip_resources
|
||||
.as_ref()
|
||||
.map(ip_resource_count)
|
||||
.unwrap_or(0);
|
||||
let asn = cert
|
||||
.tbs
|
||||
.extensions
|
||||
.as_resources
|
||||
.as_ref()
|
||||
.map(as_resource_count)
|
||||
.unwrap_or(0);
|
||||
ip.saturating_add(asn)
|
||||
}
|
||||
ObjType::Crl => {
|
||||
let crl = RpkixCrl::decode_der(bytes).expect("decode crl");
|
||||
crl.revoked_certs.len() as u64
|
||||
}
|
||||
ObjType::Manifest => {
|
||||
let mft = ManifestObject::decode_der(bytes).expect("decode manifest");
|
||||
mft.manifest.file_count() as u64
|
||||
}
|
||||
ObjType::Roa => {
|
||||
let roa = RoaObject::decode_der(bytes).expect("decode roa");
|
||||
let mut n = 0u64;
|
||||
for fam in &roa.roa.ip_addr_blocks {
|
||||
n = n.saturating_add(fam.addresses.len() as u64);
|
||||
}
|
||||
n
|
||||
}
|
||||
ObjType::Aspa => {
|
||||
let asa = AspaObject::decode_der(bytes).expect("decode aspa");
|
||||
asa.aspa.provider_as_ids.len() as u64
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_validate(obj_type: ObjType, bytes: &[u8]) {
|
||||
match obj_type {
|
||||
ObjType::Cer => {
|
||||
let decoded = ResourceCertificate::decode_der(std::hint::black_box(bytes))
|
||||
.expect("decode cert");
|
||||
std::hint::black_box(decoded);
|
||||
}
|
||||
ObjType::Crl => {
|
||||
let decoded = RpkixCrl::decode_der(std::hint::black_box(bytes)).expect("decode crl");
|
||||
std::hint::black_box(decoded);
|
||||
}
|
||||
ObjType::Manifest => {
|
||||
let decoded =
|
||||
ManifestObject::decode_der(std::hint::black_box(bytes)).expect("decode manifest");
|
||||
std::hint::black_box(decoded);
|
||||
}
|
||||
ObjType::Roa => {
|
||||
let decoded = RoaObject::decode_der(std::hint::black_box(bytes)).expect("decode roa");
|
||||
std::hint::black_box(decoded);
|
||||
}
|
||||
ObjType::Aspa => {
|
||||
let decoded = AspaObject::decode_der(std::hint::black_box(bytes)).expect("decode aspa");
|
||||
std::hint::black_box(decoded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn landing_packfile_cbor_put(
|
||||
store: &RocksStore,
|
||||
obj_type: ObjType,
|
||||
sample: &str,
|
||||
bytes: &[u8],
|
||||
) {
|
||||
let rsync_uri = format!("rsync://bench.invalid/{}/{}.{}", obj_type.as_str(), sample, obj_type.ext());
|
||||
let pf = PackFile::from_bytes_compute_sha256(rsync_uri, bytes.to_vec());
|
||||
let encoded = serde_cbor::to_vec(std::hint::black_box(&pf)).expect("cbor encode packfile");
|
||||
let key = format!("bench:packfile:{}:{}", obj_type.as_str(), sample);
|
||||
store.put_raw(&key, &encoded).expect("store put_raw");
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize)]
|
||||
struct RunConfig {
|
||||
dir: String,
|
||||
mode: String,
|
||||
obj_type: Option<String>,
|
||||
sample: Option<String>,
|
||||
fixed_iters: Option<u64>,
|
||||
warmup_iters: u64,
|
||||
rounds: u64,
|
||||
min_round_ms: u64,
|
||||
max_adaptive_iters: u64,
|
||||
timestamp_utc: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize)]
|
||||
struct ResultRow {
|
||||
obj_type: String,
|
||||
sample: String,
|
||||
size_bytes: usize,
|
||||
complexity: u64,
|
||||
avg_ns_per_op: f64,
|
||||
ops_per_sec: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize)]
|
||||
struct BenchmarkOutput {
|
||||
config: RunConfig,
|
||||
rows: Vec<ResultRow>,
|
||||
}
|
||||
|
||||
fn render_markdown(title: &str, rows: &[ResultRow]) -> String {
|
||||
let mut out = String::new();
|
||||
out.push_str(&format!("# {title}\n\n"));
|
||||
out.push_str("| type | sample | size_bytes | complexity | avg ns/op | ops/s |\n");
|
||||
out.push_str("|---|---|---:|---:|---:|---:|\n");
|
||||
for r in rows {
|
||||
out.push_str(&format!(
|
||||
"| {} | {} | {} | {} | {:.2} | {:.2} |\n",
|
||||
escape_md(&r.obj_type),
|
||||
escape_md(&r.sample),
|
||||
r.size_bytes,
|
||||
r.complexity,
|
||||
r.avg_ns_per_op,
|
||||
r.ops_per_sec
|
||||
));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn render_csv(rows: &[ResultRow]) -> String {
|
||||
let mut out = String::new();
|
||||
out.push_str("type,sample,size_bytes,complexity,avg_ns_per_op,ops_per_sec\n");
|
||||
for r in rows {
|
||||
// sample names are controlled by our fixtures; keep it simple but safe for commas/quotes.
|
||||
let sample = r.sample.replace('"', "\"\"");
|
||||
out.push_str(&format!(
|
||||
"{},{},{},{},{:.6},{:.6}\n",
|
||||
r.obj_type,
|
||||
format!("\"{}\"", sample),
|
||||
r.size_bytes,
|
||||
r.complexity,
|
||||
r.avg_ns_per_op,
|
||||
r.ops_per_sec
|
||||
));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "manual performance benchmark; decode+validate and landing for selected_der_v2"]
|
||||
fn stage2_decode_validate_and_landing_benchmark_selected_der_v2() {
|
||||
let dir = env_string("BENCH_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(default_samples_dir);
|
||||
|
||||
let mode = env_string("BENCH_MODE")
|
||||
.as_deref()
|
||||
.map(BenchMode::parse)
|
||||
.transpose()
|
||||
.unwrap_or_else(|e| panic!("{e}"))
|
||||
.unwrap_or(BenchMode::Both);
|
||||
|
||||
let type_filter = env_string("BENCH_TYPE");
|
||||
let sample_filter = env_string("BENCH_SAMPLE");
|
||||
let fixed_iters = env_u64_opt("BENCH_ITERS");
|
||||
let warmup_iters = env_u64("BENCH_WARMUP_ITERS", 50);
|
||||
let rounds = env_u64("BENCH_ROUNDS", 5);
|
||||
let min_round_ms = env_u64("BENCH_MIN_ROUND_MS", 200);
|
||||
let max_adaptive_iters = env_u64("BENCH_MAX_ITERS", 1_000_000);
|
||||
let verbose = env_bool("BENCH_VERBOSE");
|
||||
let out_md = env_string("BENCH_OUT_MD").map(PathBuf::from);
|
||||
let out_json = env_string("BENCH_OUT_JSON").map(PathBuf::from);
|
||||
let out_csv = env_string("BENCH_OUT_CSV").map(PathBuf::from);
|
||||
let out_md_landing = env_string("BENCH_OUT_MD_LANDING").map(PathBuf::from);
|
||||
let out_json_landing = env_string("BENCH_OUT_JSON_LANDING").map(PathBuf::from);
|
||||
let out_csv_landing = env_string("BENCH_OUT_CSV_LANDING").map(PathBuf::from);
|
||||
|
||||
if let Some(n) = fixed_iters {
|
||||
assert!(n >= 1, "BENCH_ITERS must be >= 1");
|
||||
}
|
||||
assert!(rounds >= 1, "BENCH_ROUNDS must be >= 1");
|
||||
assert!(min_round_ms >= 1, "BENCH_MIN_ROUND_MS must be >= 1");
|
||||
assert!(max_adaptive_iters >= 1, "BENCH_MAX_ITERS must be >= 1");
|
||||
|
||||
let mut samples = read_samples(&dir);
|
||||
assert!(
|
||||
!samples.is_empty(),
|
||||
"no samples found under: {}",
|
||||
dir.display()
|
||||
);
|
||||
|
||||
if let Some(t) = type_filter.as_deref() {
|
||||
samples.retain(|s| s.obj_type.as_str() == t);
|
||||
assert!(!samples.is_empty(), "no sample matched BENCH_TYPE={t}");
|
||||
}
|
||||
if let Some(filter) = sample_filter.as_deref() {
|
||||
samples.retain(|s| s.name == filter);
|
||||
assert!(!samples.is_empty(), "no sample matched BENCH_SAMPLE={filter}");
|
||||
}
|
||||
|
||||
println!("# Stage2 decode+validate benchmark (selected_der_v2)");
|
||||
println!();
|
||||
println!("- dir: {}", dir.display());
|
||||
println!("- mode: {}", mode.as_str());
|
||||
if let Some(t) = type_filter.as_deref() {
|
||||
println!("- type: {}", t);
|
||||
}
|
||||
if let Some(s) = sample_filter.as_deref() {
|
||||
println!("- sample: {}", s);
|
||||
}
|
||||
if let Some(n) = fixed_iters {
|
||||
println!("- iters: {} (fixed)", n);
|
||||
} else {
|
||||
println!(
|
||||
"- warmup: {} iters, rounds: {}, min_round: {}ms (adaptive iters, max {})",
|
||||
warmup_iters, rounds, min_round_ms, max_adaptive_iters
|
||||
);
|
||||
}
|
||||
if verbose {
|
||||
println!("- verbose: true");
|
||||
}
|
||||
if let Some(p) = out_md.as_ref() {
|
||||
println!("- out_md: {}", p.display());
|
||||
}
|
||||
if let Some(p) = out_json.as_ref() {
|
||||
println!("- out_json: {}", p.display());
|
||||
}
|
||||
if let Some(p) = out_csv.as_ref() {
|
||||
println!("- out_csv: {}", p.display());
|
||||
}
|
||||
if let Some(p) = out_md_landing.as_ref() {
|
||||
println!("- out_md_landing: {}", p.display());
|
||||
}
|
||||
if let Some(p) = out_json_landing.as_ref() {
|
||||
println!("- out_json_landing: {}", p.display());
|
||||
}
|
||||
if let Some(p) = out_csv_landing.as_ref() {
|
||||
println!("- out_csv_landing: {}", p.display());
|
||||
}
|
||||
println!();
|
||||
|
||||
if mode.do_decode() {
|
||||
println!("## decode+validate");
|
||||
println!();
|
||||
println!("| type | sample | size_bytes | complexity | avg ns/op | ops/s |");
|
||||
println!("|---|---|---:|---:|---:|---:|");
|
||||
}
|
||||
|
||||
let mut decode_rows: Vec<ResultRow> = Vec::with_capacity(samples.len());
|
||||
|
||||
let temp = tempfile::tempdir().expect("tempdir");
|
||||
let store = RocksStore::open(temp.path()).expect("open rocksdb");
|
||||
|
||||
let mut landing_rows: Vec<ResultRow> = Vec::with_capacity(samples.len());
|
||||
|
||||
for s in &samples {
|
||||
let bytes = std::fs::read(&s.path).unwrap_or_else(|e| panic!("read {}: {e}", s.path.display()));
|
||||
let size_bytes = bytes.len();
|
||||
let complexity = if mode.do_decode() {
|
||||
complexity_main(s.obj_type, bytes.as_slice())
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
if mode.do_decode() {
|
||||
// Warm-up decode+validate.
|
||||
for _ in 0..warmup_iters {
|
||||
decode_validate(s.obj_type, bytes.as_slice());
|
||||
}
|
||||
|
||||
let mut per_round_ns_per_op = Vec::with_capacity(rounds as usize);
|
||||
for round in 0..rounds {
|
||||
let iters = if let Some(n) = fixed_iters {
|
||||
n
|
||||
} else {
|
||||
choose_iters_adaptive(
|
||||
|| decode_validate(s.obj_type, bytes.as_slice()),
|
||||
min_round_ms,
|
||||
max_adaptive_iters,
|
||||
)
|
||||
};
|
||||
|
||||
let start = Instant::now();
|
||||
for _ in 0..iters {
|
||||
decode_validate(s.obj_type, bytes.as_slice());
|
||||
}
|
||||
let elapsed = start.elapsed();
|
||||
let total_ns = elapsed.as_secs_f64() * 1e9;
|
||||
let ns_per_op = total_ns / (iters as f64);
|
||||
per_round_ns_per_op.push(ns_per_op);
|
||||
|
||||
if verbose {
|
||||
println!(
|
||||
"# {}.{} round {}: iters={} total_ms={:.2} ns/op={:.2}",
|
||||
s.obj_type.as_str(),
|
||||
s.name,
|
||||
round + 1,
|
||||
iters,
|
||||
elapsed.as_secs_f64() * 1e3,
|
||||
ns_per_op
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let avg_ns =
|
||||
per_round_ns_per_op.iter().sum::<f64>() / (per_round_ns_per_op.len() as f64);
|
||||
let ops_per_sec = 1e9_f64 / avg_ns;
|
||||
|
||||
println!(
|
||||
"| {} | {} | {} | {} | {:.2} | {:.2} |",
|
||||
s.obj_type.as_str(),
|
||||
s.name,
|
||||
size_bytes,
|
||||
complexity,
|
||||
avg_ns,
|
||||
ops_per_sec
|
||||
);
|
||||
|
||||
decode_rows.push(ResultRow {
|
||||
obj_type: s.obj_type.as_str().to_string(),
|
||||
sample: s.name.clone(),
|
||||
size_bytes,
|
||||
complexity,
|
||||
avg_ns_per_op: avg_ns,
|
||||
ops_per_sec,
|
||||
});
|
||||
}
|
||||
|
||||
if mode.do_landing() {
|
||||
// Landing benchmark: PackFile(from_bytes_compute_sha256) + CBOR + RocksDB put_raw.
|
||||
let mut per_round_ns_per_op = Vec::with_capacity(rounds as usize);
|
||||
for _ in 0..warmup_iters {
|
||||
landing_packfile_cbor_put(&store, s.obj_type, &s.name, bytes.as_slice());
|
||||
}
|
||||
for round in 0..rounds {
|
||||
let iters = if let Some(n) = fixed_iters {
|
||||
n
|
||||
} else {
|
||||
choose_iters_adaptive(
|
||||
|| landing_packfile_cbor_put(&store, s.obj_type, &s.name, bytes.as_slice()),
|
||||
min_round_ms,
|
||||
max_adaptive_iters,
|
||||
)
|
||||
};
|
||||
|
||||
let start = Instant::now();
|
||||
for _ in 0..iters {
|
||||
landing_packfile_cbor_put(&store, s.obj_type, &s.name, bytes.as_slice());
|
||||
}
|
||||
let elapsed = start.elapsed();
|
||||
let total_ns = elapsed.as_secs_f64() * 1e9;
|
||||
let ns_per_op = total_ns / (iters as f64);
|
||||
per_round_ns_per_op.push(ns_per_op);
|
||||
|
||||
if verbose {
|
||||
println!(
|
||||
"# landing {}.{} round {}: iters={} total_ms={:.2} ns/op={:.2}",
|
||||
s.obj_type.as_str(),
|
||||
s.name,
|
||||
round + 1,
|
||||
iters,
|
||||
elapsed.as_secs_f64() * 1e3,
|
||||
ns_per_op
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let avg_ns =
|
||||
per_round_ns_per_op.iter().sum::<f64>() / (per_round_ns_per_op.len() as f64);
|
||||
let ops_per_sec = 1e9_f64 / avg_ns;
|
||||
|
||||
landing_rows.push(ResultRow {
|
||||
obj_type: s.obj_type.as_str().to_string(),
|
||||
sample: s.name.clone(),
|
||||
size_bytes,
|
||||
complexity,
|
||||
avg_ns_per_op: avg_ns,
|
||||
ops_per_sec,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if mode.do_decode() {
|
||||
println!();
|
||||
}
|
||||
if mode.do_landing() {
|
||||
println!("## landing (PackFile::from_bytes_compute_sha256 + CBOR + RocksDB put_raw)");
|
||||
println!();
|
||||
println!("| type | sample | size_bytes | complexity | avg ns/op | ops/s |");
|
||||
println!("|---|---|---:|---:|---:|---:|");
|
||||
for r in &landing_rows {
|
||||
println!(
|
||||
"| {} | {} | {} | {} | {:.2} | {:.2} |",
|
||||
escape_md(&r.obj_type),
|
||||
escape_md(&r.sample),
|
||||
r.size_bytes,
|
||||
r.complexity,
|
||||
r.avg_ns_per_op,
|
||||
r.ops_per_sec
|
||||
);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
if mode.do_decode() && (out_md.is_some() || out_json.is_some() || out_csv.is_some()) {
|
||||
let timestamp_utc = fmt_rfc3339_utc_now();
|
||||
let cfg = RunConfig {
|
||||
dir: dir.display().to_string(),
|
||||
mode: "decode_validate".to_string(),
|
||||
obj_type: type_filter.clone(),
|
||||
sample: sample_filter.clone(),
|
||||
fixed_iters,
|
||||
warmup_iters,
|
||||
rounds,
|
||||
min_round_ms,
|
||||
max_adaptive_iters,
|
||||
timestamp_utc,
|
||||
};
|
||||
|
||||
if let Some(path) = out_md {
|
||||
let md = render_markdown("Stage2 decode+validate benchmark (selected_der_v2)", &decode_rows);
|
||||
write_text_file(&path, &md);
|
||||
eprintln!("Wrote {}", path.display());
|
||||
}
|
||||
if let Some(path) = out_json {
|
||||
let json = serde_json::to_string_pretty(&BenchmarkOutput {
|
||||
config: cfg,
|
||||
rows: decode_rows.clone(),
|
||||
})
|
||||
.expect("serialize json");
|
||||
write_text_file(&path, &json);
|
||||
eprintln!("Wrote {}", path.display());
|
||||
}
|
||||
if let Some(path) = out_csv {
|
||||
let csv = render_csv(&decode_rows);
|
||||
write_text_file(&path, &csv);
|
||||
eprintln!("Wrote {}", path.display());
|
||||
}
|
||||
}
|
||||
|
||||
if mode.do_landing()
|
||||
&& (out_md_landing.is_some() || out_json_landing.is_some() || out_csv_landing.is_some())
|
||||
{
|
||||
let timestamp_utc = fmt_rfc3339_utc_now();
|
||||
let cfg = RunConfig {
|
||||
dir: dir.display().to_string(),
|
||||
mode: "landing_packfile_cbor_put".to_string(),
|
||||
obj_type: type_filter,
|
||||
sample: sample_filter,
|
||||
fixed_iters,
|
||||
warmup_iters,
|
||||
rounds,
|
||||
min_round_ms,
|
||||
max_adaptive_iters,
|
||||
timestamp_utc,
|
||||
};
|
||||
|
||||
if let Some(path) = out_md_landing {
|
||||
let md = render_markdown(
|
||||
"Stage2 landing benchmark (PackFile CBOR + RocksDB put_raw)",
|
||||
&landing_rows,
|
||||
);
|
||||
write_text_file(&path, &md);
|
||||
eprintln!("Wrote {}", path.display());
|
||||
}
|
||||
if let Some(path) = out_json_landing {
|
||||
let json = serde_json::to_string_pretty(&BenchmarkOutput {
|
||||
config: cfg,
|
||||
rows: landing_rows.clone(),
|
||||
})
|
||||
.expect("serialize json");
|
||||
write_text_file(&path, &json);
|
||||
eprintln!("Wrote {}", path.display());
|
||||
}
|
||||
if let Some(path) = out_csv_landing {
|
||||
let csv = render_csv(&landing_rows);
|
||||
write_text_file(&path, &csv);
|
||||
eprintln!("Wrote {}", path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
235
tests/bench_stage2_inventory_sap.rs
Normal file
235
tests/bench_stage2_inventory_sap.rs
Normal file
@ -0,0 +1,235 @@
|
||||
#[path = "benchmark/sap.rs"]
|
||||
mod sap;
|
||||
|
||||
use serde::Serialize;
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct Sizes {
|
||||
vals: Vec<u64>,
|
||||
total: u64,
|
||||
}
|
||||
|
||||
impl Sizes {
|
||||
fn push(&mut self, v: u64) {
|
||||
self.vals.push(v);
|
||||
self.total = self.total.saturating_add(v);
|
||||
}
|
||||
|
||||
fn summarize(&mut self) -> Option<SizesSummary> {
|
||||
if self.vals.is_empty() {
|
||||
return None;
|
||||
}
|
||||
self.vals.sort_unstable();
|
||||
let min = *self.vals.first().unwrap();
|
||||
let max = *self.vals.last().unwrap();
|
||||
let p50 = percentile(&self.vals, 0.50);
|
||||
let p90 = percentile(&self.vals, 0.90);
|
||||
let p99 = percentile(&self.vals, 0.99);
|
||||
Some(SizesSummary {
|
||||
count: self.vals.len() as u64,
|
||||
total_bytes: self.total,
|
||||
min,
|
||||
p50,
|
||||
p90,
|
||||
p99,
|
||||
max,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
struct SizesSummary {
|
||||
count: u64,
|
||||
total_bytes: u64,
|
||||
min: u64,
|
||||
p50: u64,
|
||||
p90: u64,
|
||||
p99: u64,
|
||||
max: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
struct InventoryReport {
|
||||
store_dir: String,
|
||||
pack_files_found: u64,
|
||||
packs_success: u64,
|
||||
packs_last_attempt: u64,
|
||||
packs_decode_error: u64,
|
||||
objects_total: u64,
|
||||
by_type: BTreeMap<String, SizesSummary>,
|
||||
}
|
||||
|
||||
fn percentile(sorted: &[u64], p: f64) -> u64 {
|
||||
if sorted.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
if sorted.len() == 1 {
|
||||
return sorted[0];
|
||||
}
|
||||
let p = p.clamp(0.0, 1.0);
|
||||
let idx = ((sorted.len() - 1) as f64 * p).round() as usize;
|
||||
sorted[idx]
|
||||
}
|
||||
|
||||
fn env_string(name: &str) -> Option<String> {
|
||||
std::env::var(name).ok().filter(|s| !s.trim().is_empty())
|
||||
}
|
||||
|
||||
fn env_u64_opt(name: &str) -> Option<u64> {
|
||||
std::env::var(name).ok().and_then(|s| s.parse::<u64>().ok())
|
||||
}
|
||||
|
||||
fn create_parent_dirs(path: &Path) {
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent).unwrap_or_else(|e| {
|
||||
panic!("create_dir_all {}: {e}", parent.display());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn walk_collect_files(root: &Path, out: &mut Vec<PathBuf>) -> std::io::Result<()> {
|
||||
for entry in std::fs::read_dir(root)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
let meta = entry.metadata()?;
|
||||
if meta.is_dir() {
|
||||
walk_collect_files(&path, out)?;
|
||||
continue;
|
||||
}
|
||||
if !meta.is_file() {
|
||||
continue;
|
||||
}
|
||||
out.push(path);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn object_type_from_rsync_uri(uri: &str) -> String {
|
||||
let file = uri.rsplit('/').next().unwrap_or(uri);
|
||||
if let Some((_, ext)) = file.rsplit_once('.') {
|
||||
return ext.to_ascii_lowercase();
|
||||
}
|
||||
"unknown".to_string()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "manual inventory of routinator stored publication point packs (SAP)"]
|
||||
fn stage2_inventory_routinator_sap_store() {
|
||||
let store_dir = env_string("BENCH_STORE_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"set BENCH_STORE_DIR to routinator stored/ dir, e.g. /home/yuyr/dev/rust_playground/routinator/bench/full/cache/stored"
|
||||
)
|
||||
});
|
||||
let max_packs = env_u64_opt("BENCH_MAX_PACKS");
|
||||
let out_json = env_string("BENCH_OUT_JSON").map(PathBuf::from);
|
||||
|
||||
let mut pack_paths = Vec::new();
|
||||
walk_collect_files(&store_dir, &mut pack_paths)
|
||||
.unwrap_or_else(|e| panic!("walk {}: {e}", store_dir.display()));
|
||||
pack_paths.retain(|p| p.extension().and_then(|s| s.to_str()) == Some("mft"));
|
||||
pack_paths.sort();
|
||||
|
||||
if let Some(max) = max_packs {
|
||||
pack_paths.truncate(max as usize);
|
||||
}
|
||||
|
||||
println!("# Stage2 inventory: routinator SAP packs");
|
||||
println!();
|
||||
println!("- store_dir: {}", store_dir.display());
|
||||
println!("- pack_files_found: {}", pack_paths.len());
|
||||
if let Some(max) = max_packs {
|
||||
println!("- max_packs: {} (truncating input list)", max);
|
||||
}
|
||||
if let Some(p) = out_json.as_ref() {
|
||||
println!("- out_json: {}", p.display());
|
||||
}
|
||||
println!();
|
||||
|
||||
let mut by_type: BTreeMap<String, Sizes> = BTreeMap::new();
|
||||
let mut packs_success = 0u64;
|
||||
let mut packs_last_attempt = 0u64;
|
||||
let mut packs_decode_error = 0u64;
|
||||
let mut objects_total = 0u64;
|
||||
|
||||
for p in &pack_paths {
|
||||
let sap = match sap::SapPublicationPoint::decode_path(p.as_path()) {
|
||||
Ok(v) => v,
|
||||
Err(_e) => {
|
||||
packs_decode_error += 1;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
match sap.header.update_status {
|
||||
sap::SapUpdateStatus::LastAttempt { .. } => {
|
||||
packs_last_attempt += 1;
|
||||
continue;
|
||||
}
|
||||
sap::SapUpdateStatus::Success { .. } => {
|
||||
packs_success += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let Some(m) = sap.manifest.as_ref() else {
|
||||
packs_decode_error += 1;
|
||||
continue;
|
||||
};
|
||||
|
||||
by_type
|
||||
.entry("mft".to_string())
|
||||
.or_default()
|
||||
.push(m.manifest_der.len() as u64);
|
||||
by_type
|
||||
.entry("crl".to_string())
|
||||
.or_default()
|
||||
.push(m.crl_der.len() as u64);
|
||||
objects_total += 2;
|
||||
|
||||
for o in &sap.objects {
|
||||
let typ = object_type_from_rsync_uri(&o.uri);
|
||||
by_type
|
||||
.entry(typ)
|
||||
.or_default()
|
||||
.push(o.content_der.len() as u64);
|
||||
objects_total += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let mut summaries: BTreeMap<String, SizesSummary> = BTreeMap::new();
|
||||
for (typ, mut sizes) in by_type {
|
||||
if let Some(summary) = sizes.summarize() {
|
||||
summaries.insert(typ, summary);
|
||||
}
|
||||
}
|
||||
|
||||
let report = InventoryReport {
|
||||
store_dir: store_dir.display().to_string(),
|
||||
pack_files_found: pack_paths.len() as u64,
|
||||
packs_success,
|
||||
packs_last_attempt,
|
||||
packs_decode_error,
|
||||
objects_total,
|
||||
by_type: summaries.clone(),
|
||||
};
|
||||
|
||||
println!("| type | count | total_bytes | p50 | p90 | p99 | max |");
|
||||
println!("|---|---:|---:|---:|---:|---:|---:|");
|
||||
for (typ, s) in &summaries {
|
||||
println!(
|
||||
"| {} | {} | {} | {} | {} | {} | {} |",
|
||||
typ, s.count, s.total_bytes, s.p50, s.p90, s.p99, s.max
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(path) = out_json.as_ref() {
|
||||
create_parent_dirs(path);
|
||||
let bytes = serde_json::to_vec_pretty(&report).expect("encode json");
|
||||
std::fs::write(path, bytes)
|
||||
.unwrap_or_else(|e| panic!("write {}: {e}", path.display()));
|
||||
}
|
||||
}
|
||||
|
||||
191
tests/benchmark/profile_stage2_m5_hotspots.sh
Executable file
191
tests/benchmark/profile_stage2_m5_hotspots.sh
Executable file
@ -0,0 +1,191 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# M5: Generate perf profiles + flamegraphs + hotspots for Stage2 objects.
|
||||
# Default target is ROA max sample since it is currently the largest gap in
|
||||
# ours-vs-routinator decode comparison.
|
||||
#
|
||||
# Outputs (under rpki/target/bench):
|
||||
# - perf_stage2_ours_<type>_<sample>_decode.data
|
||||
# - flamegraph_stage2_ours_<type>_<sample>_decode_release.svg
|
||||
# - hotspots_stage2_ours_<type>_<sample>_decode_release.txt
|
||||
# - perf_stage2_ours_<type>_<sample>_landing.data
|
||||
# - flamegraph_stage2_ours_<type>_<sample>_landing_release.svg
|
||||
# - hotspots_stage2_ours_<type>_<sample>_landing_release.txt
|
||||
# - perf_stage2_routinator_<type>_<sample>_decode.data
|
||||
# - flamegraph_stage2_routinator_<type>_<sample>_decode_release.svg
|
||||
# - hotspots_stage2_routinator_<type>_<sample>_decode_release.txt
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
OUT_DIR="$ROOT_DIR/target/bench"
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
TYPE="${BENCH_TYPE:-roa}" # cer|crl|roa
|
||||
SAMPLE="${BENCH_SAMPLE:-max}" # e.g. max|p99|p50
|
||||
|
||||
ITERS="${BENCH_ITERS:-2000}"
|
||||
WARMUP="${BENCH_WARMUP_ITERS:-50}"
|
||||
FREQ="${FLAMEGRAPH_FREQ:-99}"
|
||||
|
||||
PROFILE_LANDING="${PROFILE_LANDING:-1}" # 0|1
|
||||
|
||||
if ! command -v perf >/dev/null 2>&1; then
|
||||
echo "ERROR: perf not found." >&2
|
||||
exit 2
|
||||
fi
|
||||
if ! command -v flamegraph >/dev/null 2>&1; then
|
||||
echo "ERROR: flamegraph not found. Install with: cargo install flamegraph" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# On WSL2, /usr/bin/perf is often a wrapper that errors because there is no
|
||||
# kernel-matched perf binary. In that case, prefer a real perf binary under
|
||||
# /usr/lib/linux-tools/*/perf by putting a shim earlier in PATH.
|
||||
PERF_WRAPPER_OK=1
|
||||
PERF_VERSION_OUT="$(perf --version 2>&1 || true)"
|
||||
if echo "${PERF_VERSION_OUT}" | grep -q "WARNING: perf not found for kernel"; then
|
||||
PERF_WRAPPER_OK=0
|
||||
fi
|
||||
|
||||
if [[ "${PERF_WRAPPER_OK}" == "0" ]]; then
|
||||
PERF_REAL="$(ls -1 /usr/lib/linux-tools/*/perf 2>/dev/null | head -n 1 || true)"
|
||||
if [[ -z "${PERF_REAL}" ]]; then
|
||||
echo "ERROR: perf wrapper found, but no real perf binary under /usr/lib/linux-tools/*/perf" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
SHIM_DIR="$OUT_DIR/tools"
|
||||
mkdir -p "${SHIM_DIR}"
|
||||
cat > "${SHIM_DIR}/perf" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
exec "${PERF_REAL}" "\$@"
|
||||
EOF
|
||||
chmod +x "${SHIM_DIR}/perf"
|
||||
export PATH="${SHIM_DIR}:${PATH}"
|
||||
echo "Using perf shim -> ${PERF_REAL}" >&2
|
||||
fi
|
||||
|
||||
echo "type=${TYPE} sample=${SAMPLE} iters=${ITERS} warmup=${WARMUP} freq=${FREQ}" >&2
|
||||
|
||||
build_ours_test_bin() {
|
||||
(cd "$ROOT_DIR" && cargo test --release --test bench_stage2_decode_profile_selected_der_v2 --no-run -q)
|
||||
local bin
|
||||
bin="$(find "$ROOT_DIR/target/release/deps" -maxdepth 1 -type f -executable -name 'bench_stage2_decode_profile_selected_der_v2-*' | head -n 1 || true)"
|
||||
if [[ -z "${bin}" ]]; then
|
||||
echo "ERROR: failed to locate ours test binary under target/release/deps" >&2
|
||||
exit 2
|
||||
fi
|
||||
echo "${bin}"
|
||||
}
|
||||
|
||||
build_routinator_bin() {
|
||||
(cd "$ROOT_DIR/benchmark/routinator_object_bench" && cargo build --release -q)
|
||||
local bin="$ROOT_DIR/benchmark/routinator_object_bench/target/release/routinator-object-bench"
|
||||
if [[ ! -x "${bin}" ]]; then
|
||||
echo "ERROR: failed to locate routinator-object-bench binary: ${bin}" >&2
|
||||
exit 2
|
||||
fi
|
||||
echo "${bin}"
|
||||
}
|
||||
|
||||
run_perf_profile() {
|
||||
local perfdata="$1"
|
||||
shift
|
||||
echo "[perf record] $perfdata" >&2
|
||||
perf record -o "$perfdata" -F "$FREQ" --call-graph dwarf -g -- "$@"
|
||||
}
|
||||
|
||||
gen_flamegraph() {
|
||||
local perfdata="$1"
|
||||
local out_svg="$2"
|
||||
local title="$3"
|
||||
local subtitle="$4"
|
||||
echo "[flamegraph] $out_svg" >&2
|
||||
flamegraph --perfdata "$perfdata" --output "$out_svg" --title "$title" --subtitle "$subtitle" >/dev/null
|
||||
}
|
||||
|
||||
gen_hotspots() {
|
||||
local perfdata="$1"
|
||||
local out_txt="$2"
|
||||
echo "[perf report] $out_txt" >&2
|
||||
# Keep it deterministic enough for diffing; avoid call-graph children view.
|
||||
perf report --stdio -i "$perfdata" --no-children --sort overhead,symbol,dso > "$out_txt" || true
|
||||
}
|
||||
|
||||
OURS_BIN="$(build_ours_test_bin)"
|
||||
ROUT_BIN="$(build_routinator_bin)"
|
||||
|
||||
# OURS decode+validate
|
||||
OURS_DECODE_PERF="$OUT_DIR/perf_stage2_ours_${TYPE}_${SAMPLE}_decode.data"
|
||||
OURS_DECODE_SVG="$OUT_DIR/flamegraph_stage2_ours_${TYPE}_${SAMPLE}_decode_release.svg"
|
||||
OURS_DECODE_HOT="$OUT_DIR/hotspots_stage2_ours_${TYPE}_${SAMPLE}_decode_release.txt"
|
||||
|
||||
run_perf_profile "$OURS_DECODE_PERF" \
|
||||
env \
|
||||
BENCH_MODE=decode_validate \
|
||||
BENCH_TYPE="$TYPE" \
|
||||
BENCH_SAMPLE="$SAMPLE" \
|
||||
BENCH_ITERS="$ITERS" \
|
||||
BENCH_ROUNDS=1 \
|
||||
BENCH_WARMUP_ITERS="$WARMUP" \
|
||||
"$OURS_BIN" --ignored --nocapture >/dev/null
|
||||
|
||||
gen_flamegraph "$OURS_DECODE_PERF" "$OURS_DECODE_SVG" \
|
||||
"stage2 ours decode_validate ${TYPE}.${SAMPLE}" "iters=${ITERS} warmup=${WARMUP} freq=${FREQ}"
|
||||
gen_hotspots "$OURS_DECODE_PERF" "$OURS_DECODE_HOT"
|
||||
|
||||
# OURS landing
|
||||
if [[ "${PROFILE_LANDING}" == "1" ]]; then
|
||||
OURS_LAND_PERF="$OUT_DIR/perf_stage2_ours_${TYPE}_${SAMPLE}_landing.data"
|
||||
OURS_LAND_SVG="$OUT_DIR/flamegraph_stage2_ours_${TYPE}_${SAMPLE}_landing_release.svg"
|
||||
OURS_LAND_HOT="$OUT_DIR/hotspots_stage2_ours_${TYPE}_${SAMPLE}_landing_release.txt"
|
||||
|
||||
run_perf_profile "$OURS_LAND_PERF" \
|
||||
env \
|
||||
BENCH_MODE=landing \
|
||||
BENCH_TYPE="$TYPE" \
|
||||
BENCH_SAMPLE="$SAMPLE" \
|
||||
BENCH_ITERS="$ITERS" \
|
||||
BENCH_ROUNDS=1 \
|
||||
BENCH_WARMUP_ITERS="$WARMUP" \
|
||||
"$OURS_BIN" --ignored --nocapture >/dev/null
|
||||
|
||||
gen_flamegraph "$OURS_LAND_PERF" "$OURS_LAND_SVG" \
|
||||
"stage2 ours landing ${TYPE}.${SAMPLE}" "iters=${ITERS} warmup=${WARMUP} freq=${FREQ}"
|
||||
gen_hotspots "$OURS_LAND_PERF" "$OURS_LAND_HOT"
|
||||
fi
|
||||
|
||||
# Routinator baseline decode
|
||||
ROUT_DECODE_PERF="$OUT_DIR/perf_stage2_routinator_${TYPE}_${SAMPLE}_decode.data"
|
||||
ROUT_DECODE_SVG="$OUT_DIR/flamegraph_stage2_routinator_${TYPE}_${SAMPLE}_decode_release.svg"
|
||||
ROUT_DECODE_HOT="$OUT_DIR/hotspots_stage2_routinator_${TYPE}_${SAMPLE}_decode_release.txt"
|
||||
|
||||
run_perf_profile "$ROUT_DECODE_PERF" \
|
||||
"$ROUT_BIN" \
|
||||
--dir "$ROOT_DIR/tests/benchmark/selected_der_v2" \
|
||||
--type "$TYPE" \
|
||||
--sample "$SAMPLE" \
|
||||
--iters "$ITERS" \
|
||||
--rounds 1 \
|
||||
--warmup-iters "$WARMUP" \
|
||||
--min-round-ms 1 \
|
||||
--max-iters 1000000 \
|
||||
--strict true \
|
||||
--out-csv "$OUT_DIR/_tmp_routinator_profile.csv" \
|
||||
--out-md "$OUT_DIR/_tmp_routinator_profile.md" \
|
||||
>/dev/null
|
||||
|
||||
gen_flamegraph "$ROUT_DECODE_PERF" "$ROUT_DECODE_SVG" \
|
||||
"stage2 routinator decode ${TYPE}.${SAMPLE}" "iters=${ITERS} warmup=${WARMUP} freq=${FREQ}"
|
||||
gen_hotspots "$ROUT_DECODE_PERF" "$ROUT_DECODE_HOT"
|
||||
|
||||
echo "Done." >&2
|
||||
echo "- ours decode flamegraph: $OURS_DECODE_SVG" >&2
|
||||
echo "- ours decode hotspots: $OURS_DECODE_HOT" >&2
|
||||
if [[ "${PROFILE_LANDING}" == "1" ]]; then
|
||||
echo "- ours landing flamegraph: $OURS_LAND_SVG" >&2
|
||||
echo "- ours landing hotspots: $OURS_LAND_HOT" >&2
|
||||
fi
|
||||
echo "- rout decode flamegraph: $ROUT_DECODE_SVG" >&2
|
||||
echo "- rout decode hotspots: $ROUT_DECODE_HOT" >&2
|
||||
|
||||
255
tests/benchmark/sap.rs
Normal file
255
tests/benchmark/sap.rs
Normal file
@ -0,0 +1,255 @@
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SapPublicationPoint {
|
||||
pub header: SapPointHeader,
|
||||
pub manifest: Option<SapStoredManifest>,
|
||||
pub objects: Vec<SapStoredObject>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SapPointHeader {
|
||||
pub version: u8,
|
||||
pub manifest_uri: String,
|
||||
pub rpki_notify: Option<String>,
|
||||
pub update_status: SapUpdateStatus,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum SapUpdateStatus {
|
||||
Success { time_utc: time::OffsetDateTime },
|
||||
LastAttempt { time_utc: time::OffsetDateTime },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SapStoredManifest {
|
||||
pub not_after_utc: time::OffsetDateTime,
|
||||
pub manifest_number: [u8; 20],
|
||||
pub this_update_utc: time::OffsetDateTime,
|
||||
pub ca_repository: String,
|
||||
pub manifest_der: Vec<u8>,
|
||||
pub crl_uri: String,
|
||||
pub crl_der: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SapStoredObject {
|
||||
pub uri: String,
|
||||
pub hash_sha256: Option<[u8; 32]>,
|
||||
pub content_der: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum SapDecodeError {
|
||||
#[error("I/O error reading {path}: {err}")]
|
||||
Io { path: String, err: String },
|
||||
|
||||
#[error("SAP decode error: {0}")]
|
||||
Decode(String),
|
||||
}
|
||||
|
||||
impl SapPublicationPoint {
|
||||
pub fn decode_path(path: &Path) -> Result<Self, SapDecodeError> {
|
||||
let bytes = std::fs::read(path).map_err(|e| SapDecodeError::Io {
|
||||
path: path.display().to_string(),
|
||||
err: e.to_string(),
|
||||
})?;
|
||||
Self::decode_bytes(&bytes)
|
||||
}
|
||||
|
||||
pub fn decode_bytes(bytes: &[u8]) -> Result<Self, SapDecodeError> {
|
||||
let mut cur = Cursor::new(bytes);
|
||||
|
||||
let version = cur.read_u8()?;
|
||||
let manifest_uri = cur.read_string_u32()?;
|
||||
let rpki_notify = cur.read_option_string_u32()?;
|
||||
|
||||
let update_kind = cur.read_u8()?;
|
||||
let update_time_utc = cur.read_time_utc_i64_be()?;
|
||||
let update_status = match update_kind {
|
||||
0 => SapUpdateStatus::Success {
|
||||
time_utc: update_time_utc,
|
||||
},
|
||||
1 => SapUpdateStatus::LastAttempt {
|
||||
time_utc: update_time_utc,
|
||||
},
|
||||
_ => {
|
||||
return Err(SapDecodeError::Decode(format!(
|
||||
"invalid update_status kind: {update_kind}"
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let header = SapPointHeader {
|
||||
version,
|
||||
manifest_uri,
|
||||
rpki_notify,
|
||||
update_status: update_status.clone(),
|
||||
};
|
||||
|
||||
match update_status {
|
||||
SapUpdateStatus::LastAttempt { .. } => Ok(SapPublicationPoint {
|
||||
header,
|
||||
manifest: None,
|
||||
objects: Vec::new(),
|
||||
}),
|
||||
SapUpdateStatus::Success { .. } => {
|
||||
let not_after_utc = cur.read_time_utc_i64_be()?;
|
||||
let manifest_number = cur.read_array_20()?;
|
||||
let this_update_utc = cur.read_time_utc_i64_be()?;
|
||||
let ca_repository = cur.read_string_u32()?;
|
||||
let manifest_der = cur.read_bytes_u64_vec()?;
|
||||
let crl_uri = cur.read_string_u32()?;
|
||||
let crl_der = cur.read_bytes_u64_vec()?;
|
||||
|
||||
let manifest = SapStoredManifest {
|
||||
not_after_utc,
|
||||
manifest_number,
|
||||
this_update_utc,
|
||||
ca_repository,
|
||||
manifest_der,
|
||||
crl_uri,
|
||||
crl_der,
|
||||
};
|
||||
|
||||
let mut objects = Vec::new();
|
||||
while cur.remaining() > 0 {
|
||||
let uri = match cur.try_read_string_u32() {
|
||||
Ok(Some(v)) => v,
|
||||
Ok(None) => break,
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
let hash_type = cur.read_u8()?;
|
||||
let hash_sha256 = match hash_type {
|
||||
0 => None,
|
||||
1 => Some(cur.read_array_32()?),
|
||||
other => {
|
||||
return Err(SapDecodeError::Decode(format!(
|
||||
"unsupported hash_type {other} for stored object {uri}"
|
||||
)));
|
||||
}
|
||||
};
|
||||
let content_der = cur.read_bytes_u64_vec()?;
|
||||
objects.push(SapStoredObject {
|
||||
uri,
|
||||
hash_sha256,
|
||||
content_der,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(SapPublicationPoint {
|
||||
header,
|
||||
manifest: Some(manifest),
|
||||
objects,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Cursor<'a> {
|
||||
bytes: &'a [u8],
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl<'a> Cursor<'a> {
|
||||
fn new(bytes: &'a [u8]) -> Self {
|
||||
Self { bytes, pos: 0 }
|
||||
}
|
||||
|
||||
fn remaining(&self) -> usize {
|
||||
self.bytes.len().saturating_sub(self.pos)
|
||||
}
|
||||
|
||||
fn read_exact(&mut self, len: usize) -> Result<&'a [u8], SapDecodeError> {
|
||||
if len > self.remaining() {
|
||||
return Err(SapDecodeError::Decode(format!(
|
||||
"truncated input: need {len} bytes, have {}",
|
||||
self.remaining()
|
||||
)));
|
||||
}
|
||||
let start = self.pos;
|
||||
self.pos += len;
|
||||
Ok(&self.bytes[start..start + len])
|
||||
}
|
||||
|
||||
fn read_u8(&mut self) -> Result<u8, SapDecodeError> {
|
||||
Ok(self.read_exact(1)?[0])
|
||||
}
|
||||
|
||||
fn read_u32_be(&mut self) -> Result<u32, SapDecodeError> {
|
||||
let b = self.read_exact(4)?;
|
||||
Ok(u32::from_be_bytes([b[0], b[1], b[2], b[3]]))
|
||||
}
|
||||
|
||||
fn read_u64_be(&mut self) -> Result<u64, SapDecodeError> {
|
||||
let b = self.read_exact(8)?;
|
||||
Ok(u64::from_be_bytes([
|
||||
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
|
||||
]))
|
||||
}
|
||||
|
||||
fn read_i64_be(&mut self) -> Result<i64, SapDecodeError> {
|
||||
let b = self.read_exact(8)?;
|
||||
Ok(i64::from_be_bytes([
|
||||
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
|
||||
]))
|
||||
}
|
||||
|
||||
fn read_time_utc_i64_be(&mut self) -> Result<time::OffsetDateTime, SapDecodeError> {
|
||||
let ts = self.read_i64_be()?;
|
||||
time::OffsetDateTime::from_unix_timestamp(ts).map_err(|e| {
|
||||
SapDecodeError::Decode(format!("invalid unix timestamp {ts}: {e}"))
|
||||
})
|
||||
}
|
||||
|
||||
fn read_string_u32(&mut self) -> Result<String, SapDecodeError> {
|
||||
let len = self.read_u32_be()? as usize;
|
||||
let b = self.read_exact(len)?;
|
||||
std::str::from_utf8(b)
|
||||
.map(|s| s.to_string())
|
||||
.map_err(|e| SapDecodeError::Decode(format!("invalid UTF-8 string: {e}")))
|
||||
}
|
||||
|
||||
fn try_read_string_u32(&mut self) -> Result<Option<String>, SapDecodeError> {
|
||||
if self.remaining() == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
Ok(Some(self.read_string_u32()?))
|
||||
}
|
||||
|
||||
fn read_option_string_u32(&mut self) -> Result<Option<String>, SapDecodeError> {
|
||||
let len = self.read_u32_be()? as usize;
|
||||
if len == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
let b = self.read_exact(len)?;
|
||||
let s = std::str::from_utf8(b)
|
||||
.map_err(|e| SapDecodeError::Decode(format!("invalid UTF-8 string: {e}")))?;
|
||||
Ok(Some(s.to_string()))
|
||||
}
|
||||
|
||||
fn read_bytes_u64_vec(&mut self) -> Result<Vec<u8>, SapDecodeError> {
|
||||
let len_u64 = self.read_u64_be()?;
|
||||
let len = usize::try_from(len_u64).map_err(|_| {
|
||||
SapDecodeError::Decode(format!("data block too large for this system: {len_u64}"))
|
||||
})?;
|
||||
Ok(self.read_exact(len)?.to_vec())
|
||||
}
|
||||
|
||||
fn read_array_20(&mut self) -> Result<[u8; 20], SapDecodeError> {
|
||||
let b = self.read_exact(20)?;
|
||||
let mut out = [0u8; 20];
|
||||
out.copy_from_slice(b);
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn read_array_32(&mut self) -> Result<[u8; 32], SapDecodeError> {
|
||||
let b = self.read_exact(32)?;
|
||||
let mut out = [0u8; 32];
|
||||
out.copy_from_slice(b);
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
BIN
tests/benchmark/selected_der_v2/aspa/max.asa
Normal file
BIN
tests/benchmark/selected_der_v2/aspa/max.asa
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/aspa/min.asa
Normal file
BIN
tests/benchmark/selected_der_v2/aspa/min.asa
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/aspa/p01.asa
Normal file
BIN
tests/benchmark/selected_der_v2/aspa/p01.asa
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/aspa/p10.asa
Normal file
BIN
tests/benchmark/selected_der_v2/aspa/p10.asa
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/aspa/p25.asa
Normal file
BIN
tests/benchmark/selected_der_v2/aspa/p25.asa
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/aspa/p50.asa
Normal file
BIN
tests/benchmark/selected_der_v2/aspa/p50.asa
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/aspa/p75.asa
Normal file
BIN
tests/benchmark/selected_der_v2/aspa/p75.asa
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/aspa/p90.asa
Normal file
BIN
tests/benchmark/selected_der_v2/aspa/p90.asa
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/aspa/p95.asa
Normal file
BIN
tests/benchmark/selected_der_v2/aspa/p95.asa
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/aspa/p99.asa
Normal file
BIN
tests/benchmark/selected_der_v2/aspa/p99.asa
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/cer/max.cer
Normal file
BIN
tests/benchmark/selected_der_v2/cer/max.cer
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/cer/min.cer
Normal file
BIN
tests/benchmark/selected_der_v2/cer/min.cer
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/cer/p01.cer
Normal file
BIN
tests/benchmark/selected_der_v2/cer/p01.cer
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/cer/p10.cer
Normal file
BIN
tests/benchmark/selected_der_v2/cer/p10.cer
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/cer/p25.cer
Normal file
BIN
tests/benchmark/selected_der_v2/cer/p25.cer
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/cer/p50.cer
Normal file
BIN
tests/benchmark/selected_der_v2/cer/p50.cer
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/cer/p75.cer
Normal file
BIN
tests/benchmark/selected_der_v2/cer/p75.cer
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/cer/p90.cer
Normal file
BIN
tests/benchmark/selected_der_v2/cer/p90.cer
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/cer/p95.cer
Normal file
BIN
tests/benchmark/selected_der_v2/cer/p95.cer
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/cer/p99.cer
Normal file
BIN
tests/benchmark/selected_der_v2/cer/p99.cer
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/crl/max.crl
Normal file
BIN
tests/benchmark/selected_der_v2/crl/max.crl
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/crl/min.crl
Normal file
BIN
tests/benchmark/selected_der_v2/crl/min.crl
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/crl/p01.crl
Normal file
BIN
tests/benchmark/selected_der_v2/crl/p01.crl
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/crl/p10.crl
Normal file
BIN
tests/benchmark/selected_der_v2/crl/p10.crl
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/crl/p25.crl
Normal file
BIN
tests/benchmark/selected_der_v2/crl/p25.crl
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/crl/p50.crl
Normal file
BIN
tests/benchmark/selected_der_v2/crl/p50.crl
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/crl/p75.crl
Normal file
BIN
tests/benchmark/selected_der_v2/crl/p75.crl
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/crl/p90.crl
Normal file
BIN
tests/benchmark/selected_der_v2/crl/p90.crl
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/crl/p95.crl
Normal file
BIN
tests/benchmark/selected_der_v2/crl/p95.crl
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/crl/p99.crl
Normal file
BIN
tests/benchmark/selected_der_v2/crl/p99.crl
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/manifest/max.mft
Normal file
BIN
tests/benchmark/selected_der_v2/manifest/max.mft
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/manifest/min.mft
Normal file
BIN
tests/benchmark/selected_der_v2/manifest/min.mft
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/manifest/p01.mft
Normal file
BIN
tests/benchmark/selected_der_v2/manifest/p01.mft
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/manifest/p10.mft
Normal file
BIN
tests/benchmark/selected_der_v2/manifest/p10.mft
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/manifest/p25.mft
Normal file
BIN
tests/benchmark/selected_der_v2/manifest/p25.mft
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/manifest/p50.mft
Normal file
BIN
tests/benchmark/selected_der_v2/manifest/p50.mft
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/manifest/p75.mft
Normal file
BIN
tests/benchmark/selected_der_v2/manifest/p75.mft
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/manifest/p90.mft
Normal file
BIN
tests/benchmark/selected_der_v2/manifest/p90.mft
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/manifest/p95.mft
Normal file
BIN
tests/benchmark/selected_der_v2/manifest/p95.mft
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/manifest/p99.mft
Normal file
BIN
tests/benchmark/selected_der_v2/manifest/p99.mft
Normal file
Binary file not shown.
913
tests/benchmark/selected_der_v2/meta/samples.json
Normal file
913
tests/benchmark/selected_der_v2/meta/samples.json
Normal file
@ -0,0 +1,913 @@
|
||||
{
|
||||
"created_at_rfc3339_utc": "2026-02-25T07:45:23.460363033Z",
|
||||
"store_dir_hint": "/home/yuyr/dev/rust_playground/routinator/bench/full/cache/stored",
|
||||
"per_type": {
|
||||
"aspa": 10,
|
||||
"cer": 10,
|
||||
"crl": 10,
|
||||
"manifest": 10,
|
||||
"roa": 10
|
||||
},
|
||||
"samples": [
|
||||
{
|
||||
"obj_type": "cer",
|
||||
"label": "max",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/cer/max.cer",
|
||||
"size_bytes": 101541,
|
||||
"sha256_hex": "6eb39fd890725623c7fb02fd9eb7940118264c24bbd5a1cf5fe508375d94f2a6",
|
||||
"pack_rel_path": "rrdp/rrdp.lacnic.net/b4460caf6e2ae89d1e7133d19861729c100528f5bf30cc8ffec1eabb2279b4bf/rsync/repository.lacnic.net/rpki/lacnic/A1531B24BF50C461C7F574CD65267A8B0DC325DAAA10075F67165B98C4F4EFC3/0/05BAF2939E37DDDE1793A803162A35594ACBB405.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T02:24:35Z",
|
||||
"manifest_uri": "rsync://repository.lacnic.net/rpki/lacnic/A1531B24BF50C461C7F574CD65267A8B0DC325DAAA10075F67165B98C4F4EFC3/0/05BAF2939E37DDDE1793A803162A35594ACBB405.mft",
|
||||
"object_uri": "rsync://repository.lacnic.net/rpki/lacnic/A1531B24BF50C461C7F574CD65267A8B0DC325DAAA10075F67165B98C4F4EFC3/0/605432E9E1B05A7E6C208B2946FDC9C967CA8A4B.cer",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-02T15:00:09Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-07T21:50:09Z",
|
||||
"metrics": {
|
||||
"kind": "cer",
|
||||
"spki_len": 294,
|
||||
"ext_count": 9,
|
||||
"ip_resource_count": 12560,
|
||||
"as_resource_count": 387
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "cer",
|
||||
"label": "min",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/cer/min.cer",
|
||||
"size_bytes": 898,
|
||||
"sha256_hex": "0eb0d58bf262a6149f7ff2949581eb4a36eb5b5ce055d7ea51cb1ce9375ecfbe",
|
||||
"pack_rel_path": "rrdp/chloe.sobornost.net/a4ae76c5702d98db9df10a8a83f3450d9d07f06afd431e60721be71a84cf15b4/rsync/chloe.sobornost.net/rpki/RIPE-nljobsnijders/yqgF26w2R0m5sRVZCrbvD5cM29g.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T04:32:08Z",
|
||||
"manifest_uri": "rsync://chloe.sobornost.net/rpki/RIPE-nljobsnijders/yqgF26w2R0m5sRVZCrbvD5cM29g.mft",
|
||||
"object_uri": "rsync://chloe.sobornost.net/rpki/RIPE-nljobsnijders/XUJQ4tgdREjYop786R0p_wdeyeI.cer",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T04:24:59Z",
|
||||
"manifest_not_after_rfc3339_utc": "2027-07-01T00:00:00Z",
|
||||
"metrics": {
|
||||
"kind": "cer",
|
||||
"spki_len": 91,
|
||||
"ext_count": 6,
|
||||
"ip_resource_count": 0,
|
||||
"as_resource_count": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "cer",
|
||||
"label": "p01",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/cer/p01.cer",
|
||||
"size_bytes": 1378,
|
||||
"sha256_hex": "b00ff8d2746d735f23592abdfb812a6d48d3f8ea3df3f25431711535fb6e1644",
|
||||
"pack_rel_path": "rrdp/rrdp.twnic.tw/9e5ef8f1eb47c1bd46d2c578ebefa6a774fe140dbfbfeff1a018f1ebfc1de14b/rsync/rpkica.twnic.tw/rpki/TWNICCA/ojp8Y1RxGKrkl_A-ExIclqs0VH4.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T03:25:43Z",
|
||||
"manifest_uri": "rsync://rpkica.twnic.tw/rpki/TWNICCA/ojp8Y1RxGKrkl_A-ExIclqs0VH4.mft",
|
||||
"object_uri": "rsync://rpkica.twnic.tw/rpki/TWNICCA/6Vlz64b1l8rmEsZ5Ke2TUucLVSg.cer",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T03:18:26Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-09-30T00:00:00Z",
|
||||
"metrics": {
|
||||
"kind": "cer",
|
||||
"spki_len": 294,
|
||||
"ext_count": 8,
|
||||
"ip_resource_count": 2,
|
||||
"as_resource_count": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "cer",
|
||||
"label": "p10",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/cer/p10.cer",
|
||||
"size_bytes": 1537,
|
||||
"sha256_hex": "2ebde61f5f5587561b6d390db9a76bfa86cdfd1571e9147e956dc2288be7ec7d",
|
||||
"pack_rel_path": "rrdp/rrdp.apnic.net/020675249928c63c162a11d00ab299c0b2d0e2c3ca4d27a8ebf933ace26a66a6/rsync/rpki.apnic.net/repository/B3A24F201D6611E28AC8837C72FD1FF2/dAFlqA0QcZcKvAnAK3HBrHwdbg4.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T03:25:13Z",
|
||||
"manifest_uri": "rsync://rpki.apnic.net/repository/B3A24F201D6611E28AC8837C72FD1FF2/dAFlqA0QcZcKvAnAK3HBrHwdbg4.mft",
|
||||
"object_uri": "rsync://rpki.apnic.net/repository/B3A24F201D6611E28AC8837C72FD1FF2/gmqoO74CQNE5BqurRkMX6eercO8.cer",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T02:50:14Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-10T02:50:14Z",
|
||||
"metrics": {
|
||||
"kind": "cer",
|
||||
"spki_len": 294,
|
||||
"ext_count": 8,
|
||||
"ip_resource_count": 1,
|
||||
"as_resource_count": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "cer",
|
||||
"label": "p25",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/cer/p25.cer",
|
||||
"size_bytes": 1581,
|
||||
"sha256_hex": "4b519d26e07bbce5407b09717a0102ff9361b80e3d18bc9ef3ff5764b87853ec",
|
||||
"pack_rel_path": "rrdp/rrdp.apnic.net/020675249928c63c162a11d00ab299c0b2d0e2c3ca4d27a8ebf933ace26a66a6/rsync/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/DmWk9f02tb1o6zySNAiXjJB6p58.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T04:29:30Z",
|
||||
"manifest_uri": "rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/DmWk9f02tb1o6zySNAiXjJB6p58.mft",
|
||||
"object_uri": "rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/J39oYmhAcrYqFW3fdd13JcOPUdA.cer",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T03:47:20Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-10T03:47:20Z",
|
||||
"metrics": {
|
||||
"kind": "cer",
|
||||
"spki_len": 294,
|
||||
"ext_count": 9,
|
||||
"ip_resource_count": 2,
|
||||
"as_resource_count": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "cer",
|
||||
"label": "p50",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/cer/p50.cer",
|
||||
"size_bytes": 1594,
|
||||
"sha256_hex": "818d24d7dd2fcafc97a8e3783f2b7bd2844096850e2fdea81f4d69d2b45764f4",
|
||||
"pack_rel_path": "rrdp/rrdp.apnic.net/020675249928c63c162a11d00ab299c0b2d0e2c3ca4d27a8ebf933ace26a66a6/rsync/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/DmWk9f02tb1o6zySNAiXjJB6p58.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T04:29:30Z",
|
||||
"manifest_uri": "rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/DmWk9f02tb1o6zySNAiXjJB6p58.mft",
|
||||
"object_uri": "rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/TxeC6ZVkwoo27twZ-XPR2SdHgf4.cer",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T03:47:20Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-10T03:47:20Z",
|
||||
"metrics": {
|
||||
"kind": "cer",
|
||||
"spki_len": 294,
|
||||
"ext_count": 9,
|
||||
"ip_resource_count": 3,
|
||||
"as_resource_count": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "cer",
|
||||
"label": "p75",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/cer/p75.cer",
|
||||
"size_bytes": 1793,
|
||||
"sha256_hex": "f59769ebe6b3114db70579fc8dc64c256dac4eff714a8e192a6f34ae5424445c",
|
||||
"pack_rel_path": "rrdp/rrdp.arin.net/e2c8cb4b372d841061fc231dcdc28a679c244f4f630d41cfe01c8c3aaa3a36f7/rsync/rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/fde169ed-d0d2-4165-8308-df2597e343f8/fde169ed-d0d2-4165-8308-df2597e343f8.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-04T03:22:56Z",
|
||||
"manifest_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/fde169ed-d0d2-4165-8308-df2597e343f8/fde169ed-d0d2-4165-8308-df2597e343f8.mft",
|
||||
"object_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/fde169ed-d0d2-4165-8308-df2597e343f8/be801a76-17f5-4096-843c-c6728fe01188.cer",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-04T02:10:03Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-06T18:00:00Z",
|
||||
"metrics": {
|
||||
"kind": "cer",
|
||||
"spki_len": 294,
|
||||
"ext_count": 8,
|
||||
"ip_resource_count": 2,
|
||||
"as_resource_count": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "cer",
|
||||
"label": "p90",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/cer/p90.cer",
|
||||
"size_bytes": 1825,
|
||||
"sha256_hex": "6930e4f019b59d64a135514da8da6bff6a2725e03178ae4914c68c2bc1af8d78",
|
||||
"pack_rel_path": "rrdp/rrdp.arin.net/e2c8cb4b372d841061fc231dcdc28a679c244f4f630d41cfe01c8c3aaa3a36f7/rsync/rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/fde169ed-d0d2-4165-8308-df2597e343f8/fde169ed-d0d2-4165-8308-df2597e343f8.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-04T03:22:56Z",
|
||||
"manifest_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/fde169ed-d0d2-4165-8308-df2597e343f8/fde169ed-d0d2-4165-8308-df2597e343f8.mft",
|
||||
"object_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/fde169ed-d0d2-4165-8308-df2597e343f8/2ce98e57-da60-49a3-b167-e6998e0d2945.cer",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-04T02:10:03Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-06T18:00:00Z",
|
||||
"metrics": {
|
||||
"kind": "cer",
|
||||
"spki_len": 294,
|
||||
"ext_count": 9,
|
||||
"ip_resource_count": 3,
|
||||
"as_resource_count": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "cer",
|
||||
"label": "p95",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/cer/p95.cer",
|
||||
"size_bytes": 1839,
|
||||
"sha256_hex": "de8a89f3089560b21d0bf2021d79e41d4d259b8efcfb5fd795b349b031b3d527",
|
||||
"pack_rel_path": "rrdp/rrdp.arin.net/e2c8cb4b372d841061fc231dcdc28a679c244f4f630d41cfe01c8c3aaa3a36f7/rsync/rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/0357272c-a79a-45bf-9586-92dd49ef3223/0357272c-a79a-45bf-9586-92dd49ef3223.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-04T03:24:34Z",
|
||||
"manifest_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/0357272c-a79a-45bf-9586-92dd49ef3223/0357272c-a79a-45bf-9586-92dd49ef3223.mft",
|
||||
"object_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/0357272c-a79a-45bf-9586-92dd49ef3223/24597156-139e-456b-b7b5-78502f61a0ca.cer",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-04T01:00:09Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-06T01:00:00Z",
|
||||
"metrics": {
|
||||
"kind": "cer",
|
||||
"spki_len": 294,
|
||||
"ext_count": 9,
|
||||
"ip_resource_count": 4,
|
||||
"as_resource_count": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "cer",
|
||||
"label": "p99",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/cer/p99.cer",
|
||||
"size_bytes": 1957,
|
||||
"sha256_hex": "9bc72415e60011489d1d4e87e60e18511ee57997d5597805702306e83adc5c32",
|
||||
"pack_rel_path": "rrdp/rrdp.arin.net/e2c8cb4b372d841061fc231dcdc28a679c244f4f630d41cfe01c8c3aaa3a36f7/rsync/rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/f60c9f32-a87c-4339-a2f3-6299a3b02e29/f60c9f32-a87c-4339-a2f3-6299a3b02e29.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-04T03:24:59Z",
|
||||
"manifest_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/f60c9f32-a87c-4339-a2f3-6299a3b02e29/f60c9f32-a87c-4339-a2f3-6299a3b02e29.mft",
|
||||
"object_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/f60c9f32-a87c-4339-a2f3-6299a3b02e29/45de47ca-0b52-44ab-af26-767d8a671192.cer",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T21:00:08Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-05T21:00:00Z",
|
||||
"metrics": {
|
||||
"kind": "cer",
|
||||
"spki_len": 294,
|
||||
"ext_count": 9,
|
||||
"ip_resource_count": 22,
|
||||
"as_resource_count": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "crl",
|
||||
"label": "max",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/crl/max.crl",
|
||||
"size_bytes": 895053,
|
||||
"sha256_hex": "87bb4d0cced69cf0be5dd94d0e7fe77dd6de4f2e6f5df71b42c78f47ad50b253",
|
||||
"pack_rel_path": "rrdp/rpki.cnnic.cn/d91215e7f18122c7165f486789e5ec6859e60d4acc113741c389b13efb42c6bc/rsync/rpki.cnnic.cn/rpki/A9162E3D0000/1663/iuTPeLSd8LLB0p0y5IqUOuT0Gsw.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T02:26:01Z",
|
||||
"manifest_uri": "rsync://rpki.cnnic.cn/rpki/A9162E3D0000/1663/iuTPeLSd8LLB0p0y5IqUOuT0Gsw.mft",
|
||||
"object_uri": "rsync://rpki.cnnic.cn/rpki/A9162E3D0000/1663/iuTPeLSd8LLB0p0y5IqUOuT0Gsw.crl",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-02T23:28:58Z",
|
||||
"manifest_not_after_rfc3339_utc": "2027-01-09T08:23:18Z",
|
||||
"metrics": {
|
||||
"kind": "crl",
|
||||
"revoked_count": 41411
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "crl",
|
||||
"label": "min",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/crl/min.crl",
|
||||
"size_bytes": 409,
|
||||
"sha256_hex": "80919a481ad03799f996a7911635f43e2ce0709a940d95b84ea059dc5aa7cbea",
|
||||
"pack_rel_path": "rrdp/rrdp.arin.net/e2c8cb4b372d841061fc231dcdc28a679c244f4f630d41cfe01c8c3aaa3a36f7/rsync/rpki.arin.net/repository/arin-rpki-ta/arin-rpki-ta.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T02:28:48Z",
|
||||
"manifest_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/arin-rpki-ta.mft",
|
||||
"object_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/arin-rpki-ta.crl",
|
||||
"manifest_this_update_rfc3339_utc": "2025-08-19T17:19:47Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-04-19T17:19:47Z",
|
||||
"metrics": {
|
||||
"kind": "crl",
|
||||
"revoked_count": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "crl",
|
||||
"label": "p01",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/crl/p01.crl",
|
||||
"size_bytes": 433,
|
||||
"sha256_hex": "e570e2fb424749381c8b331f37504cfa7ee4ff2ae2438c92d972ccdef47eaaeb",
|
||||
"pack_rel_path": "rrdp/rrdp.lacnic.net/b4460caf6e2ae89d1e7133d19861729c100528f5bf30cc8ffec1eabb2279b4bf/rsync/repository.lacnic.net/rpki/lacnic/91274329588638C015CCBA8F9D89E1FEDD9BFDA8B46FE1567059F96B0BA87379/0/5F5C0DFD76D6DC1D850EE2157BD0F4E5F6ED2EBB.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T02:24:39Z",
|
||||
"manifest_uri": "rsync://repository.lacnic.net/rpki/lacnic/91274329588638C015CCBA8F9D89E1FEDD9BFDA8B46FE1567059F96B0BA87379/0/5F5C0DFD76D6DC1D850EE2157BD0F4E5F6ED2EBB.mft",
|
||||
"object_uri": "rsync://repository.lacnic.net/rpki/lacnic/91274329588638C015CCBA8F9D89E1FEDD9BFDA8B46FE1567059F96B0BA87379/0/5F5C0DFD76D6DC1D850EE2157BD0F4E5F6ED2EBB.crl",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-01T23:22:13Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-07T06:44:13Z",
|
||||
"metrics": {
|
||||
"kind": "crl",
|
||||
"revoked_count": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "crl",
|
||||
"label": "p10",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/crl/p10.crl",
|
||||
"size_bytes": 475,
|
||||
"sha256_hex": "62754c72974e31156253f95e30d07cd3072f184c9ae37df665b4ecbbe20b3d94",
|
||||
"pack_rel_path": "rrdp/rrdp.arin.net/e2c8cb4b372d841061fc231dcdc28a679c244f4f630d41cfe01c8c3aaa3a36f7/rsync/rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/0357272c-a79a-45bf-9586-92dd49ef3223/93ec9d2b-8293-4a44-a996-77893f2d4008/93ec9d2b-8293-4a44-a996-77893f2d4008.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-04T03:24:47Z",
|
||||
"manifest_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/0357272c-a79a-45bf-9586-92dd49ef3223/93ec9d2b-8293-4a44-a996-77893f2d4008/93ec9d2b-8293-4a44-a996-77893f2d4008.mft",
|
||||
"object_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/0357272c-a79a-45bf-9586-92dd49ef3223/93ec9d2b-8293-4a44-a996-77893f2d4008/93ec9d2b-8293-4a44-a996-77893f2d4008.crl",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T19:00:04Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-05T19:00:00Z",
|
||||
"metrics": {
|
||||
"kind": "crl",
|
||||
"revoked_count": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "crl",
|
||||
"label": "p25",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/crl/p25.crl",
|
||||
"size_bytes": 514,
|
||||
"sha256_hex": "5cb9aca6321edea55bcffa73faba4b128dfc6f065b44247496b5fc0b988e8589",
|
||||
"pack_rel_path": "rrdp/rrdp.arin.net/e2c8cb4b372d841061fc231dcdc28a679c244f4f630d41cfe01c8c3aaa3a36f7/rsync/rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/0357272c-a79a-45bf-9586-92dd49ef3223/9b13d137-bc85-4935-b740-e59f0078def5/9b13d137-bc85-4935-b740-e59f0078def5.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-04T03:24:42Z",
|
||||
"manifest_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/0357272c-a79a-45bf-9586-92dd49ef3223/9b13d137-bc85-4935-b740-e59f0078def5/9b13d137-bc85-4935-b740-e59f0078def5.mft",
|
||||
"object_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/0357272c-a79a-45bf-9586-92dd49ef3223/9b13d137-bc85-4935-b740-e59f0078def5/9b13d137-bc85-4935-b740-e59f0078def5.crl",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T20:00:08Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-06T20:00:00Z",
|
||||
"metrics": {
|
||||
"kind": "crl",
|
||||
"revoked_count": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "crl",
|
||||
"label": "p50",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/crl/p50.crl",
|
||||
"size_bytes": 540,
|
||||
"sha256_hex": "738ea6345fdda75fe00f10b66eec108502ce1495f0688e9f0ca69a441ed4a1ea",
|
||||
"pack_rel_path": "rrdp/rrdp.apnic.net/020675249928c63c162a11d00ab299c0b2d0e2c3ca4d27a8ebf933ace26a66a6/rsync/rpki.apnic.net/member_repository/A913E23F/C64CAAA43E5211EDAAD4B64FC4F9AE02/AgtgbdnoiYp9XfXqHKPZlgKOddk.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T02:25:14Z",
|
||||
"manifest_uri": "rsync://rpki.apnic.net/member_repository/A913E23F/C64CAAA43E5211EDAAD4B64FC4F9AE02/AgtgbdnoiYp9XfXqHKPZlgKOddk.mft",
|
||||
"object_uri": "rsync://rpki.apnic.net/member_repository/A913E23F/C64CAAA43E5211EDAAD4B64FC4F9AE02/AgtgbdnoiYp9XfXqHKPZlgKOddk.crl",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T01:03:21Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-10T01:03:21Z",
|
||||
"metrics": {
|
||||
"kind": "crl",
|
||||
"revoked_count": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "crl",
|
||||
"label": "p75",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/crl/p75.crl",
|
||||
"size_bytes": 554,
|
||||
"sha256_hex": "5eb9bbca1c8fb200fe68541671b62a3bd654cb50dc0946780d49c9a191f37f5a",
|
||||
"pack_rel_path": "rrdp/rrdp.arin.net/e2c8cb4b372d841061fc231dcdc28a679c244f4f630d41cfe01c8c3aaa3a36f7/rsync/rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/69fd0156-bb1f-48b6-bf32-c9492286f195/44b921f7-65dd-4b33-b399-9d372fb4a9e5/44b921f7-65dd-4b33-b399-9d372fb4a9e5.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-04T03:24:12Z",
|
||||
"manifest_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/69fd0156-bb1f-48b6-bf32-c9492286f195/44b921f7-65dd-4b33-b399-9d372fb4a9e5/44b921f7-65dd-4b33-b399-9d372fb4a9e5.mft",
|
||||
"object_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/69fd0156-bb1f-48b6-bf32-c9492286f195/44b921f7-65dd-4b33-b399-9d372fb4a9e5/44b921f7-65dd-4b33-b399-9d372fb4a9e5.crl",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T19:00:04Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-06T19:00:00Z",
|
||||
"metrics": {
|
||||
"kind": "crl",
|
||||
"revoked_count": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "crl",
|
||||
"label": "p90",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/crl/p90.crl",
|
||||
"size_bytes": 633,
|
||||
"sha256_hex": "a7aff3478dc5f602ee15aba331163a53f843a83837281544f19e2d6fa6987f2e",
|
||||
"pack_rel_path": "rrdp/rrdp.lacnic.net/b4460caf6e2ae89d1e7133d19861729c100528f5bf30cc8ffec1eabb2279b4bf/rsync/repository.lacnic.net/rpki/lacnic/4343B2828241D18D49CED12666F5034D90C1A11663270CCFE62C8E67AE1937D7/0/7A06C1864524D4D6B3F3D941A758895DEA60A24F.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T02:25:22Z",
|
||||
"manifest_uri": "rsync://repository.lacnic.net/rpki/lacnic/4343B2828241D18D49CED12666F5034D90C1A11663270CCFE62C8E67AE1937D7/0/7A06C1864524D4D6B3F3D941A758895DEA60A24F.mft",
|
||||
"object_uri": "rsync://repository.lacnic.net/rpki/lacnic/4343B2828241D18D49CED12666F5034D90C1A11663270CCFE62C8E67AE1937D7/0/7A06C1864524D4D6B3F3D941A758895DEA60A24F.crl",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-02T13:33:21Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-07T14:30:21Z",
|
||||
"metrics": {
|
||||
"kind": "crl",
|
||||
"revoked_count": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "crl",
|
||||
"label": "p95",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/crl/p95.crl",
|
||||
"size_bytes": 772,
|
||||
"sha256_hex": "a0fb838e8b072a928748972895d8f837427b1ba797ca6c901a9e009b652da88b",
|
||||
"pack_rel_path": "rrdp/rrdp.ripe.net/2315d99a99627f34bc597569abc7c177ad45108a37f173f3d06dbabd64962f3c/rsync/rpki.ripe.net/repository/DEFAULT/61/17e450-8818-4a27-9f35-518cd14713eb/1/t3M-nelIW0RnHI5RHaIh5BU8cRs.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T04:30:53Z",
|
||||
"manifest_uri": "rsync://rpki.ripe.net/repository/DEFAULT/61/17e450-8818-4a27-9f35-518cd14713eb/1/t3M-nelIW0RnHI5RHaIh5BU8cRs.mft",
|
||||
"object_uri": "rsync://rpki.ripe.net/repository/DEFAULT/61/17e450-8818-4a27-9f35-518cd14713eb/1/t3M-nelIW0RnHI5RHaIh5BU8cRs.crl",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T04:01:11Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-04T04:01:11Z",
|
||||
"metrics": {
|
||||
"kind": "crl",
|
||||
"revoked_count": 9
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "crl",
|
||||
"label": "p99",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/crl/p99.crl",
|
||||
"size_bytes": 1843,
|
||||
"sha256_hex": "7a511d49d8f7972b4055d2da2fb213dceb1ae30a6fab896073fd9773f3264777",
|
||||
"pack_rel_path": "rrdp/rrdp.arin.net/e2c8cb4b372d841061fc231dcdc28a679c244f4f630d41cfe01c8c3aaa3a36f7/rsync/rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/746e0111-fafb-430f-b778-d204cfcd99a8/5ef79eb4-544e-4504-9a6f-d1ddff5bcd45/5ef79eb4-544e-4504-9a6f-d1ddff5bcd45.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-04T03:23:07Z",
|
||||
"manifest_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/746e0111-fafb-430f-b778-d204cfcd99a8/5ef79eb4-544e-4504-9a6f-d1ddff5bcd45/5ef79eb4-544e-4504-9a6f-d1ddff5bcd45.mft",
|
||||
"object_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/746e0111-fafb-430f-b778-d204cfcd99a8/5ef79eb4-544e-4504-9a6f-d1ddff5bcd45/5ef79eb4-544e-4504-9a6f-d1ddff5bcd45.crl",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T14:00:03Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-06T14:00:00Z",
|
||||
"metrics": {
|
||||
"kind": "crl",
|
||||
"revoked_count": 36
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "manifest",
|
||||
"label": "max",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/manifest/max.mft",
|
||||
"size_bytes": 3867132,
|
||||
"sha256_hex": "64a9a90da435b53a45289d869e21b80daca2a242508f18dff371a546d414c792",
|
||||
"pack_rel_path": "rrdp/rrdp.arin.net/e2c8cb4b372d841061fc231dcdc28a679c244f4f630d41cfe01c8c3aaa3a36f7/rsync/rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/2a246947-2d62-4a6c-ba05-87187f0099b2/4e95a28e-27fe-479a-b086-2cc9809d54f6/4e95a28e-27fe-479a-b086-2cc9809d54f6.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-04T03:23:24Z",
|
||||
"manifest_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/2a246947-2d62-4a6c-ba05-87187f0099b2/4e95a28e-27fe-479a-b086-2cc9809d54f6/4e95a28e-27fe-479a-b086-2cc9809d54f6.mft",
|
||||
"object_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/2a246947-2d62-4a6c-ba05-87187f0099b2/4e95a28e-27fe-479a-b086-2cc9809d54f6/4e95a28e-27fe-479a-b086-2cc9809d54f6.mft",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-04T02:02:08Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-06T10:00:00Z",
|
||||
"metrics": {
|
||||
"kind": "manifest",
|
||||
"file_count": 48923
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "manifest",
|
||||
"label": "min",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/manifest/min.mft",
|
||||
"size_bytes": 1786,
|
||||
"sha256_hex": "aa3a23ab546dece484fbb43d6ba1546a610f2317ef4e89e0b381e9b24538e7cd",
|
||||
"pack_rel_path": "rrdp/rrdp.ripe.net/2315d99a99627f34bc597569abc7c177ad45108a37f173f3d06dbabd64962f3c/rsync/rpki.ripe.net/repository/ripe-ncc-ta.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T02:24:12Z",
|
||||
"manifest_uri": "rsync://rpki.ripe.net/repository/ripe-ncc-ta.mft",
|
||||
"object_uri": "rsync://rpki.ripe.net/repository/ripe-ncc-ta.mft",
|
||||
"manifest_this_update_rfc3339_utc": "2026-01-14T10:50:01Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-04-14T10:50:01Z",
|
||||
"metrics": {
|
||||
"kind": "manifest",
|
||||
"file_count": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "manifest",
|
||||
"label": "p01",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/manifest/p01.mft",
|
||||
"size_bytes": 1924,
|
||||
"sha256_hex": "be00f9a0bc379fd15f89cb5ff0397629dd3bc8334cc0cddefd9fb54f39929cfb",
|
||||
"pack_rel_path": "rrdp/rrdp.ripe.net/2315d99a99627f34bc597569abc7c177ad45108a37f173f3d06dbabd64962f3c/rsync/rpki.ripe.net/repository/DEFAULT/45/031138-86e1-4182-a7a6-0107ec33e5b7/1/1W_iwM_XaRBE7jeEXPz1ThqEmXQ.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T04:30:08Z",
|
||||
"manifest_uri": "rsync://rpki.ripe.net/repository/DEFAULT/45/031138-86e1-4182-a7a6-0107ec33e5b7/1/1W_iwM_XaRBE7jeEXPz1ThqEmXQ.mft",
|
||||
"object_uri": "rsync://rpki.ripe.net/repository/DEFAULT/45/031138-86e1-4182-a7a6-0107ec33e5b7/1/1W_iwM_XaRBE7jeEXPz1ThqEmXQ.mft",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T04:01:07Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-04T04:01:07Z",
|
||||
"metrics": {
|
||||
"kind": "manifest",
|
||||
"file_count": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "manifest",
|
||||
"label": "p10",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/manifest/p10.mft",
|
||||
"size_bytes": 2012,
|
||||
"sha256_hex": "f9654d60136983a5c8fce1c7d46d1051d570e801bfd1e95b9486c4583c142e57",
|
||||
"pack_rel_path": "rrdp/rpki-repository.nic.ad.jp/aa83379c70e8dfe1337af6d0ae686a02631ded2a4138538d1a34917c84c123f2/rsync/rpki-repository.nic.ad.jp/ap/A91A73810000/30366/GUkc8TjsVYI8fTvhsuweTIDV8uU.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T02:25:35Z",
|
||||
"manifest_uri": "rsync://rpki-repository.nic.ad.jp/ap/A91A73810000/30366/GUkc8TjsVYI8fTvhsuweTIDV8uU.mft",
|
||||
"object_uri": "rsync://rpki-repository.nic.ad.jp/ap/A91A73810000/30366/GUkc8TjsVYI8fTvhsuweTIDV8uU.mft",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T01:25:11Z",
|
||||
"manifest_not_after_rfc3339_utc": "2027-01-15T01:30:02Z",
|
||||
"metrics": {
|
||||
"kind": "manifest",
|
||||
"file_count": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "manifest",
|
||||
"label": "p25",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/manifest/p25.mft",
|
||||
"size_bytes": 2113,
|
||||
"sha256_hex": "72981b97caffddf00fcf13d11bbd3df8d2a9276b37f6c7877bfa3e47c46f9b0a",
|
||||
"pack_rel_path": "rrdp/rrdp.apnic.net/020675249928c63c162a11d00ab299c0b2d0e2c3ca4d27a8ebf933ace26a66a6/rsync/rpki.apnic.net/member_repository/A915343B/6A0383E650A511EA976CB77FC4F9AE02/1mAaggCAx5DwpRpsN2X1tLDQGzc.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T02:24:06Z",
|
||||
"manifest_uri": "rsync://rpki.apnic.net/member_repository/A915343B/6A0383E650A511EA976CB77FC4F9AE02/1mAaggCAx5DwpRpsN2X1tLDQGzc.mft",
|
||||
"object_uri": "rsync://rpki.apnic.net/member_repository/A915343B/6A0383E650A511EA976CB77FC4F9AE02/1mAaggCAx5DwpRpsN2X1tLDQGzc.mft",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-02T18:58:11Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-09T18:58:11Z",
|
||||
"metrics": {
|
||||
"kind": "manifest",
|
||||
"file_count": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "manifest",
|
||||
"label": "p50",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/manifest/p50.mft",
|
||||
"size_bytes": 2220,
|
||||
"sha256_hex": "cb94f3b73bacfa2014461574e7f3afdd380bb53fa9efe6d90e074be97fbcb47f",
|
||||
"pack_rel_path": "rrdp/rrdp.lacnic.net/b4460caf6e2ae89d1e7133d19861729c100528f5bf30cc8ffec1eabb2279b4bf/rsync/repository.lacnic.net/rpki/lacnic/F192D3E65B741BC96E61B6810943E6A8F832DFB178388046815B4288805D4D08/0/3F0974925580D1783B4D99DCE063F669C26152F5.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T02:24:59Z",
|
||||
"manifest_uri": "rsync://repository.lacnic.net/rpki/lacnic/F192D3E65B741BC96E61B6810943E6A8F832DFB178388046815B4288805D4D08/0/3F0974925580D1783B4D99DCE063F669C26152F5.mft",
|
||||
"object_uri": "rsync://repository.lacnic.net/rpki/lacnic/F192D3E65B741BC96E61B6810943E6A8F832DFB178388046815B4288805D4D08/0/3F0974925580D1783B4D99DCE063F669C26152F5.mft",
|
||||
"manifest_this_update_rfc3339_utc": "2026-01-31T13:10:28Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-06T08:23:28Z",
|
||||
"metrics": {
|
||||
"kind": "manifest",
|
||||
"file_count": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "manifest",
|
||||
"label": "p75",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/manifest/p75.mft",
|
||||
"size_bytes": 2424,
|
||||
"sha256_hex": "30930241ccd9af792a212f4f9b1e8e01d306aae97fa3c3f1946592a6a1e6188a",
|
||||
"pack_rel_path": "rrdp/rrdp.lacnic.net/b4460caf6e2ae89d1e7133d19861729c100528f5bf30cc8ffec1eabb2279b4bf/rsync/repository.lacnic.net/rpki/lacnic/C4D72FDA9D9D4649BACD178B2C0FCCBD56680A1D743605C0A204D1487A56E154/0/6F2E85684756BECBED992F0B17433B71F691DBB2.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T02:24:47Z",
|
||||
"manifest_uri": "rsync://repository.lacnic.net/rpki/lacnic/C4D72FDA9D9D4649BACD178B2C0FCCBD56680A1D743605C0A204D1487A56E154/0/6F2E85684756BECBED992F0B17433B71F691DBB2.mft",
|
||||
"object_uri": "rsync://repository.lacnic.net/rpki/lacnic/C4D72FDA9D9D4649BACD178B2C0FCCBD56680A1D743605C0A204D1487A56E154/0/6F2E85684756BECBED992F0B17433B71F691DBB2.mft",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-01T10:49:31Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-06T21:25:31Z",
|
||||
"metrics": {
|
||||
"kind": "manifest",
|
||||
"file_count": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "manifest",
|
||||
"label": "p90",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/manifest/p90.mft",
|
||||
"size_bytes": 2707,
|
||||
"sha256_hex": "c5712b14980d481591699815a64be415df697d085220395e042418a3fb7269ed",
|
||||
"pack_rel_path": "rrdp/rrdp.lacnic.net/b4460caf6e2ae89d1e7133d19861729c100528f5bf30cc8ffec1eabb2279b4bf/rsync/repository.lacnic.net/rpki/lacnic/760CC0DA4C90C1F0023C05DFEE0070E5744BD89B60F4CD9C02EF81695E71099D/0/17444832D4DFC5B5F0EDB45E0DD0380E36A684B7.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T02:25:01Z",
|
||||
"manifest_uri": "rsync://repository.lacnic.net/rpki/lacnic/760CC0DA4C90C1F0023C05DFEE0070E5744BD89B60F4CD9C02EF81695E71099D/0/17444832D4DFC5B5F0EDB45E0DD0380E36A684B7.mft",
|
||||
"object_uri": "rsync://repository.lacnic.net/rpki/lacnic/760CC0DA4C90C1F0023C05DFEE0070E5744BD89B60F4CD9C02EF81695E71099D/0/17444832D4DFC5B5F0EDB45E0DD0380E36A684B7.mft",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-01T22:41:49Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-06T13:29:49Z",
|
||||
"metrics": {
|
||||
"kind": "manifest",
|
||||
"file_count": 7
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "manifest",
|
||||
"label": "p95",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/manifest/p95.mft",
|
||||
"size_bytes": 3189,
|
||||
"sha256_hex": "748b46f40db2de36e38baf74f636bafbe65f87750cd11885fdfdfee595872ebb",
|
||||
"pack_rel_path": "rrdp/rpki-repo.registro.br/026b1847a231e62a6bcf46a3794665f4dcbe0e609f90c0e54d7062dbf72114d9/rsync/rpki-repo.registro.br/repo/31ECeNa6JFrQNNySZguSi82Sz114gA5jRxQYM2Por1qQ/1/8BB23FB0F38F7CDE17852DC61BD234DCEBC3BB90.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T02:27:22Z",
|
||||
"manifest_uri": "rsync://rpki-repo.registro.br/repo/31ECeNa6JFrQNNySZguSi82Sz114gA5jRxQYM2Por1qQ/1/8BB23FB0F38F7CDE17852DC61BD234DCEBC3BB90.mft",
|
||||
"object_uri": "rsync://rpki-repo.registro.br/repo/31ECeNa6JFrQNNySZguSi82Sz114gA5jRxQYM2Por1qQ/1/8BB23FB0F38F7CDE17852DC61BD234DCEBC3BB90.mft",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T01:45:17Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-04T05:48:17Z",
|
||||
"metrics": {
|
||||
"kind": "manifest",
|
||||
"file_count": 13
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "manifest",
|
||||
"label": "p99",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/manifest/p99.mft",
|
||||
"size_bytes": 7579,
|
||||
"sha256_hex": "d70a0cb76cef6eae4222cf2e9c530fe76c4d0e1ca790d8f7a69435193d317f9c",
|
||||
"pack_rel_path": "rrdp/rrdp.arin.net/e2c8cb4b372d841061fc231dcdc28a679c244f4f630d41cfe01c8c3aaa3a36f7/rsync/rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/521eb33f-9672-4cd9-acce-137227e971ac/dcf48eb5-c08a-4793-8dd8-a83f660c3f82/dcf48eb5-c08a-4793-8dd8-a83f660c3f82.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-04T03:24:25Z",
|
||||
"manifest_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/521eb33f-9672-4cd9-acce-137227e971ac/dcf48eb5-c08a-4793-8dd8-a83f660c3f82/dcf48eb5-c08a-4793-8dd8-a83f660c3f82.mft",
|
||||
"object_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/521eb33f-9672-4cd9-acce-137227e971ac/dcf48eb5-c08a-4793-8dd8-a83f660c3f82/dcf48eb5-c08a-4793-8dd8-a83f660c3f82.mft",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T17:00:03Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-05T17:00:00Z",
|
||||
"metrics": {
|
||||
"kind": "manifest",
|
||||
"file_count": 68
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "roa",
|
||||
"label": "max",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/roa/max.roa",
|
||||
"size_bytes": 49026,
|
||||
"sha256_hex": "92e302f8d81a2a339c592508ddd598577f4fbf54374cc359843e7ee0ebd3461f",
|
||||
"pack_rel_path": "rrdp/rrdp.apnic.net/020675249928c63c162a11d00ab299c0b2d0e2c3ca4d27a8ebf933ace26a66a6/rsync/rpki.apnic.net/member_repository/A91DFB70/2983647C838F11E586FC5812C4F9AE02/XS3RVLXc4h-3hsUm297xsEWSirg.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T02:24:53Z",
|
||||
"manifest_uri": "rsync://rpki.apnic.net/member_repository/A91DFB70/2983647C838F11E586FC5812C4F9AE02/XS3RVLXc4h-3hsUm297xsEWSirg.mft",
|
||||
"object_uri": "rsync://rpki.apnic.net/member_repository/A91DFB70/2983647C838F11E586FC5812C4F9AE02/9B22F928BFE311EE949C2918C4F9AE02.roa",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-02T15:44:10Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-09T15:44:10Z",
|
||||
"metrics": {
|
||||
"kind": "roa",
|
||||
"addr_family_count": 2,
|
||||
"prefix_count": 4268,
|
||||
"max_length_present": 4268
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "roa",
|
||||
"label": "min",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/roa/min.roa",
|
||||
"size_bytes": 1724,
|
||||
"sha256_hex": "5e2b9467c52689d5cdf2104b49a68aff4cbf03fd2620f3c85b863245cb36a548",
|
||||
"pack_rel_path": "rrdp/chloe.sobornost.net/a4ae76c5702d98db9df10a8a83f3450d9d07f06afd431e60721be71a84cf15b4/rsync/chloe.sobornost.net/rpki/RIPE-nljobsnijders/yqgF26w2R0m5sRVZCrbvD5cM29g.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T04:32:08Z",
|
||||
"manifest_uri": "rsync://chloe.sobornost.net/rpki/RIPE-nljobsnijders/yqgF26w2R0m5sRVZCrbvD5cM29g.mft",
|
||||
"object_uri": "rsync://chloe.sobornost.net/rpki/RIPE-nljobsnijders/euv64En05073B5-r95s2Uu_UwJg.roa",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T04:24:59Z",
|
||||
"manifest_not_after_rfc3339_utc": "2027-07-01T00:00:00Z",
|
||||
"metrics": {
|
||||
"kind": "roa",
|
||||
"addr_family_count": 1,
|
||||
"prefix_count": 1,
|
||||
"max_length_present": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "roa",
|
||||
"label": "p01",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/roa/p01.roa",
|
||||
"size_bytes": 1757,
|
||||
"sha256_hex": "141cbcbd8482f8cc17d014ee6475ce725d5250c61c1d04be80a56cd238c45446",
|
||||
"pack_rel_path": "rrdp/rrdp.twnic.tw/9e5ef8f1eb47c1bd46d2c578ebefa6a774fe140dbfbfeff1a018f1ebfc1de14b/rsync/rpkica.twnic.tw/rpki/TWNICCA/TUNGHO/62dfk4yFAhN0yrHhr1CZMZsRCwc.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T02:27:51Z",
|
||||
"manifest_uri": "rsync://rpkica.twnic.tw/rpki/TWNICCA/TUNGHO/62dfk4yFAhN0yrHhr1CZMZsRCwc.mft",
|
||||
"object_uri": "rsync://rpkica.twnic.tw/rpki/TWNICCA/TUNGHO/cIJ9FaRp5lfrLAxp7ucVIU2RW-M.roa",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-02T04:17:15Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-08-22T08:14:28Z",
|
||||
"metrics": {
|
||||
"kind": "roa",
|
||||
"addr_family_count": 1,
|
||||
"prefix_count": 1,
|
||||
"max_length_present": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "roa",
|
||||
"label": "p10",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/roa/p10.roa",
|
||||
"size_bytes": 1919,
|
||||
"sha256_hex": "9a69ca473003754b53c38566b062e12154a53d65814d5dd6a48c23686cf4ef8a",
|
||||
"pack_rel_path": "rrdp/rrdp.apnic.net/020675249928c63c162a11d00ab299c0b2d0e2c3ca4d27a8ebf933ace26a66a6/rsync/rpki.apnic.net/member_repository/A918ACB2/9FE760A6B83C11EE8741902EC4F9AE02/6Ihh_0pFOAJnqBtY-pnkia0AG70.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T04:29:43Z",
|
||||
"manifest_uri": "rsync://rpki.apnic.net/member_repository/A918ACB2/9FE760A6B83C11EE8741902EC4F9AE02/6Ihh_0pFOAJnqBtY-pnkia0AG70.mft",
|
||||
"object_uri": "rsync://rpki.apnic.net/member_repository/A918ACB2/9FE760A6B83C11EE8741902EC4F9AE02/BD1A38D4984B11EFB342C220C4F9AE02.roa",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T03:28:59Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-10T03:28:59Z",
|
||||
"metrics": {
|
||||
"kind": "roa",
|
||||
"addr_family_count": 1,
|
||||
"prefix_count": 1,
|
||||
"max_length_present": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "roa",
|
||||
"label": "p25",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/roa/p25.roa",
|
||||
"size_bytes": 2044,
|
||||
"sha256_hex": "27473f640b696fd5701c0f17dc6e9192992ab7c8889d20e05336aa7b709480fe",
|
||||
"pack_rel_path": "rrdp/rrdp.afrinic.net/6a5a58f9bfe26843ee174cd41e801eeecbfd15b8bb0dd7fa3952941d726730b6/rsync/rpki.afrinic.net/repository/member_repository/F368F2D0/92F86E1C6E0511E8A1B5854BF8AEA228/eX2I2BPiD_-YLMdBnpabrqa_1ps.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T04:29:29Z",
|
||||
"manifest_uri": "rsync://rpki.afrinic.net/repository/member_repository/F368F2D0/92F86E1C6E0511E8A1B5854BF8AEA228/eX2I2BPiD_-YLMdBnpabrqa_1ps.mft",
|
||||
"object_uri": "rsync://rpki.afrinic.net/repository/member_repository/F368F2D0/92F86E1C6E0511E8A1B5854BF8AEA228/618ABDFCD9BF11F0B2E65198DAE4EC9C.roa",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T03:40:48Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-05T03:40:48Z",
|
||||
"metrics": {
|
||||
"kind": "roa",
|
||||
"addr_family_count": 1,
|
||||
"prefix_count": 10,
|
||||
"max_length_present": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "roa",
|
||||
"label": "p50",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/roa/p50.roa",
|
||||
"size_bytes": 2125,
|
||||
"sha256_hex": "57dc478a33980a05922d9789cac407295532b109d10e8a4b9e014dc8469b19e7",
|
||||
"pack_rel_path": "rrdp/rrdp.arin.net/e2c8cb4b372d841061fc231dcdc28a679c244f4f630d41cfe01c8c3aaa3a36f7/rsync/rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/746e0111-fafb-430f-b778-d204cfcd99a8/18800324-5150-4981-a144-bdb80e6bcb7c/18800324-5150-4981-a144-bdb80e6bcb7c.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-04T03:23:03Z",
|
||||
"manifest_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/746e0111-fafb-430f-b778-d204cfcd99a8/18800324-5150-4981-a144-bdb80e6bcb7c/18800324-5150-4981-a144-bdb80e6bcb7c.mft",
|
||||
"object_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/746e0111-fafb-430f-b778-d204cfcd99a8/18800324-5150-4981-a144-bdb80e6bcb7c/9293d640-8dc0-3613-862d-4dfc5d30586c.roa",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-04T02:02:08Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-05T19:00:00Z",
|
||||
"metrics": {
|
||||
"kind": "roa",
|
||||
"addr_family_count": 1,
|
||||
"prefix_count": 1,
|
||||
"max_length_present": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "roa",
|
||||
"label": "p75",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/roa/p75.roa",
|
||||
"size_bytes": 2126,
|
||||
"sha256_hex": "fa0855d366f4dc657d5911b82806370366c19b9fb7ca2b125e4776162b90cc64",
|
||||
"pack_rel_path": "rrdp/rrdp.arin.net/e2c8cb4b372d841061fc231dcdc28a679c244f4f630d41cfe01c8c3aaa3a36f7/rsync/rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/746e0111-fafb-430f-b778-d204cfcd99a8/d7c5f272-a2ae-458a-a75f-3cf41f19b603/d7c5f272-a2ae-458a-a75f-3cf41f19b603.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-04T03:23:05Z",
|
||||
"manifest_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/746e0111-fafb-430f-b778-d204cfcd99a8/d7c5f272-a2ae-458a-a75f-3cf41f19b603/d7c5f272-a2ae-458a-a75f-3cf41f19b603.mft",
|
||||
"object_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/746e0111-fafb-430f-b778-d204cfcd99a8/d7c5f272-a2ae-458a-a75f-3cf41f19b603/2b935f54-a6b7-3f61-8adb-bce43ad4b075.roa",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T14:02:08Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-06T13:00:00Z",
|
||||
"metrics": {
|
||||
"kind": "roa",
|
||||
"addr_family_count": 1,
|
||||
"prefix_count": 1,
|
||||
"max_length_present": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "roa",
|
||||
"label": "p90",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/roa/p90.roa",
|
||||
"size_bytes": 2132,
|
||||
"sha256_hex": "8ec9f949cf6f499672357ccaa2c560b12ef5b5f2acba3e627be97582dc01d73d",
|
||||
"pack_rel_path": "rrdp/rrdp.arin.net/e2c8cb4b372d841061fc231dcdc28a679c244f4f630d41cfe01c8c3aaa3a36f7/rsync/rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/2a246947-2d62-4a6c-ba05-87187f0099b2/4e95a28e-27fe-479a-b086-2cc9809d54f6/4e95a28e-27fe-479a-b086-2cc9809d54f6.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-04T03:23:24Z",
|
||||
"manifest_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/2a246947-2d62-4a6c-ba05-87187f0099b2/4e95a28e-27fe-479a-b086-2cc9809d54f6/4e95a28e-27fe-479a-b086-2cc9809d54f6.mft",
|
||||
"object_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/2a246947-2d62-4a6c-ba05-87187f0099b2/4e95a28e-27fe-479a-b086-2cc9809d54f6/300d5263-ba19-312e-9ba8-7d527b888d9e.roa",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-04T02:02:08Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-06T10:00:00Z",
|
||||
"metrics": {
|
||||
"kind": "roa",
|
||||
"addr_family_count": 1,
|
||||
"prefix_count": 1,
|
||||
"max_length_present": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "roa",
|
||||
"label": "p95",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/roa/p95.roa",
|
||||
"size_bytes": 2136,
|
||||
"sha256_hex": "97fcd42de1a2f346d17edc58b671e04aafabef818f3ef9498609aa1fc684eee7",
|
||||
"pack_rel_path": "rrdp/rrdp.arin.net/e2c8cb4b372d841061fc231dcdc28a679c244f4f630d41cfe01c8c3aaa3a36f7/rsync/rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/521eb33f-9672-4cd9-acce-137227e971ac/90dcf2c0-6606-4bd9-b445-77b183ef2ff4/90dcf2c0-6606-4bd9-b445-77b183ef2ff4.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-04T03:24:29Z",
|
||||
"manifest_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/521eb33f-9672-4cd9-acce-137227e971ac/90dcf2c0-6606-4bd9-b445-77b183ef2ff4/90dcf2c0-6606-4bd9-b445-77b183ef2ff4.mft",
|
||||
"object_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/521eb33f-9672-4cd9-acce-137227e971ac/90dcf2c0-6606-4bd9-b445-77b183ef2ff4/5fdeae31-baab-3255-a593-abfb66a6396f.roa",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-04T02:02:08Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-06T02:00:00Z",
|
||||
"metrics": {
|
||||
"kind": "roa",
|
||||
"addr_family_count": 1,
|
||||
"prefix_count": 1,
|
||||
"max_length_present": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "roa",
|
||||
"label": "p99",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/roa/p99.roa",
|
||||
"size_bytes": 2209,
|
||||
"sha256_hex": "7bce2c12746b2f665624aa3f192ebdb4fc8cbc4f3d5eed2fa8e2661ad7135f7c",
|
||||
"pack_rel_path": "rrdp/rrdp.afrinic.net/6a5a58f9bfe26843ee174cd41e801eeecbfd15b8bb0dd7fa3952941d726730b6/rsync/rpki.afrinic.net/repository/member_repository/F368F2D0/7F4A98EA6E0511E89C0D6E4BF8AEA228/JdY-COq-fPpnhdTB1tNBFt4Vs9w.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T03:24:56Z",
|
||||
"manifest_uri": "rsync://rpki.afrinic.net/repository/member_repository/F368F2D0/7F4A98EA6E0511E89C0D6E4BF8AEA228/JdY-COq-fPpnhdTB1tNBFt4Vs9w.mft",
|
||||
"object_uri": "rsync://rpki.afrinic.net/repository/member_repository/F368F2D0/7F4A98EA6E0511E89C0D6E4BF8AEA228/E77E611EE6E611F0825B6ED4DAE4EC9C.roa",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T03:02:20Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-05T03:02:20Z",
|
||||
"metrics": {
|
||||
"kind": "roa",
|
||||
"addr_family_count": 1,
|
||||
"prefix_count": 20,
|
||||
"max_length_present": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "aspa",
|
||||
"label": "max",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/aspa/max.asa",
|
||||
"size_bytes": 2157,
|
||||
"sha256_hex": "2e1daefad00193eb31dfa0e66706005e3c873fe4f079badf3b5b2c6d10462505",
|
||||
"pack_rel_path": "rrdp/rrdp.arin.net/e2c8cb4b372d841061fc231dcdc28a679c244f4f630d41cfe01c8c3aaa3a36f7/rsync/rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/fde169ed-d0d2-4165-8308-df2597e343f8/c0efcdab-fc76-4717-8db9-db07467f9fe0/c0efcdab-fc76-4717-8db9-db07467f9fe0.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-04T03:22:58Z",
|
||||
"manifest_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/fde169ed-d0d2-4165-8308-df2597e343f8/c0efcdab-fc76-4717-8db9-db07467f9fe0/c0efcdab-fc76-4717-8db9-db07467f9fe0.mft",
|
||||
"object_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/fde169ed-d0d2-4165-8308-df2597e343f8/c0efcdab-fc76-4717-8db9-db07467f9fe0/053373c0-f494-3063-a255-8d4430bf6133.asa",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T13:00:09Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-05T13:00:00Z",
|
||||
"metrics": {
|
||||
"kind": "aspa",
|
||||
"provider_count": 12
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "aspa",
|
||||
"label": "min",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/aspa/min.asa",
|
||||
"size_bytes": 1685,
|
||||
"sha256_hex": "5a6451ce5349f7483a74d555c44c946c17db8314c6707fc95950bdac282eae58",
|
||||
"pack_rel_path": "rrdp/rpki.pudu.be/8d06bbb84ab1cf6124ee8f0f6bbdd9c28fc22ede46a3b55e9744a28a80263fe1/rsync/rpki.pudu.be/repo/pudu/1/CF7DC5A4F702D3DC9D56EA35B9EE202EC549647E.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T04:31:47Z",
|
||||
"manifest_uri": "rsync://rpki.pudu.be/repo/pudu/1/CF7DC5A4F702D3DC9D56EA35B9EE202EC549647E.mft",
|
||||
"object_uri": "rsync://rpki.pudu.be/repo/pudu/1/AS56762.asa",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T04:07:32Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-04T05:17:32Z",
|
||||
"metrics": {
|
||||
"kind": "aspa",
|
||||
"provider_count": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "aspa",
|
||||
"label": "p01",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/aspa/p01.asa",
|
||||
"size_bytes": 1736,
|
||||
"sha256_hex": "28a98f95d617b77cac316d739a40dcb871306558fef582eaa671cfa4d899eb02",
|
||||
"pack_rel_path": "rrdp/rpki.roa.net/78e0c5f52b7514947e9ef499c811d15797db7c65ea9051c27b11555439a0c010/rsync/rpki.roa.net/rrdp/xTom/58/5B1AD82F0E7DC771819A9A26674992A3951B9373.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T02:26:07Z",
|
||||
"manifest_uri": "rsync://rpki.roa.net/rrdp/xTom/58/5B1AD82F0E7DC771819A9A26674992A3951B9373.mft",
|
||||
"object_uri": "rsync://rpki.roa.net/rrdp/xTom/58/AS138038.asa",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T02:13:19Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-04T05:43:19Z",
|
||||
"metrics": {
|
||||
"kind": "aspa",
|
||||
"provider_count": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "aspa",
|
||||
"label": "p10",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/aspa/p10.asa",
|
||||
"size_bytes": 1789,
|
||||
"sha256_hex": "857a667b95f8962e94b19425d65e00fee0a2e2886b9408e94a644873f6bcb20c",
|
||||
"pack_rel_path": "rrdp/rrdp.ripe.net/2315d99a99627f34bc597569abc7c177ad45108a37f173f3d06dbabd64962f3c/rsync/rpki.ripe.net/repository/DEFAULT/31/de9d72-fcd4-4c7c-89ca-de4ba786f872/1/3l5sIzCRG8HcjIPUEe9rEgizu9Y.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T04:30:59Z",
|
||||
"manifest_uri": "rsync://rpki.ripe.net/repository/DEFAULT/31/de9d72-fcd4-4c7c-89ca-de4ba786f872/1/3l5sIzCRG8HcjIPUEe9rEgizu9Y.mft",
|
||||
"object_uri": "rsync://rpki.ripe.net/repository/DEFAULT/31/de9d72-fcd4-4c7c-89ca-de4ba786f872/1/G0H1NZGAbtsyoA2e--eIMeks25w.asa",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T04:00:42Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-04T04:00:42Z",
|
||||
"metrics": {
|
||||
"kind": "aspa",
|
||||
"provider_count": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "aspa",
|
||||
"label": "p25",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/aspa/p25.asa",
|
||||
"size_bytes": 1796,
|
||||
"sha256_hex": "512afb7f399ac0559b88bd6a0bc6f369f9b6ad3f3e46aa1cd9e067087cf10cc9",
|
||||
"pack_rel_path": "rrdp/rrdp.ripe.net/2315d99a99627f34bc597569abc7c177ad45108a37f173f3d06dbabd64962f3c/rsync/rpki.ripe.net/repository/DEFAULT/46/cadd6e-65c6-44da-9ee9-efea65dc3020/1/1-EAlIIyFaM6OWN4a59g--NKydCQ.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T04:30:18Z",
|
||||
"manifest_uri": "rsync://rpki.ripe.net/repository/DEFAULT/46/cadd6e-65c6-44da-9ee9-efea65dc3020/1/1-EAlIIyFaM6OWN4a59g--NKydCQ.mft",
|
||||
"object_uri": "rsync://rpki.ripe.net/repository/DEFAULT/46/cadd6e-65c6-44da-9ee9-efea65dc3020/1/BNbYCTwC4UEQcd5nV0lyvO8_FF4.asa",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T04:00:31Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-04T04:00:31Z",
|
||||
"metrics": {
|
||||
"kind": "aspa",
|
||||
"provider_count": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "aspa",
|
||||
"label": "p50",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/aspa/p50.asa",
|
||||
"size_bytes": 1856,
|
||||
"sha256_hex": "2895646ffad9bac7df603e6bee1f535a68154d0d39735819de8a1f2d9eea6d06",
|
||||
"pack_rel_path": "rrdp/rrdp.paas.rpki.ripe.net/bcd30c8fe6a4120dcd88ad3eeb82a7ea26f8ecce57fe1d5cb45e5e34c3a94a9b/rsync/rsync.paas.rpki.ripe.net/repository/97b5f6f4-24be-4ae0-82a8-e5d82842e229/0/A0BBB954570CFA6E856937043DD2C1745A0A05C2.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-03T03:27:39Z",
|
||||
"manifest_uri": "rsync://rsync.paas.rpki.ripe.net/repository/97b5f6f4-24be-4ae0-82a8-e5d82842e229/0/A0BBB954570CFA6E856937043DD2C1745A0A05C2.mft",
|
||||
"object_uri": "rsync://rsync.paas.rpki.ripe.net/repository/97b5f6f4-24be-4ae0-82a8-e5d82842e229/0/AS214757.asa",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T03:09:43Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-04T05:59:43Z",
|
||||
"metrics": {
|
||||
"kind": "aspa",
|
||||
"provider_count": 14
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "aspa",
|
||||
"label": "p75",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/aspa/p75.asa",
|
||||
"size_bytes": 2116,
|
||||
"sha256_hex": "e9ea6e871e0e435a5a3b7a019fd9f8761628504668c9ba0a744ad7f337fe4825",
|
||||
"pack_rel_path": "rrdp/rrdp.arin.net/e2c8cb4b372d841061fc231dcdc28a679c244f4f630d41cfe01c8c3aaa3a36f7/rsync/rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/0357272c-a79a-45bf-9586-92dd49ef3223/59adcb6d-cc06-4e2c-b5df-20bfcd83176e/59adcb6d-cc06-4e2c-b5df-20bfcd83176e.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-04T03:24:35Z",
|
||||
"manifest_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/0357272c-a79a-45bf-9586-92dd49ef3223/59adcb6d-cc06-4e2c-b5df-20bfcd83176e/59adcb6d-cc06-4e2c-b5df-20bfcd83176e.mft",
|
||||
"object_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/0357272c-a79a-45bf-9586-92dd49ef3223/59adcb6d-cc06-4e2c-b5df-20bfcd83176e/8314341a-405c-33d6-ad94-3e8c6d770291.asa",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T19:00:04Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-06T19:00:00Z",
|
||||
"metrics": {
|
||||
"kind": "aspa",
|
||||
"provider_count": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "aspa",
|
||||
"label": "p90",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/aspa/p90.asa",
|
||||
"size_bytes": 2124,
|
||||
"sha256_hex": "e1c33906ef3bdbec47fe0cc7c9fc163d427234b4f5581895dc5279df7f278614",
|
||||
"pack_rel_path": "rrdp/rrdp.arin.net/e2c8cb4b372d841061fc231dcdc28a679c244f4f630d41cfe01c8c3aaa3a36f7/rsync/rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/746e0111-fafb-430f-b778-d204cfcd99a8/e7060a03-da54-4766-9b77-667ebbb66ffe/e7060a03-da54-4766-9b77-667ebbb66ffe.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-04T03:23:06Z",
|
||||
"manifest_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/746e0111-fafb-430f-b778-d204cfcd99a8/e7060a03-da54-4766-9b77-667ebbb66ffe/e7060a03-da54-4766-9b77-667ebbb66ffe.mft",
|
||||
"object_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/746e0111-fafb-430f-b778-d204cfcd99a8/e7060a03-da54-4766-9b77-667ebbb66ffe/4f4bee4f-0c5a-38c0-a421-5b81029d9452.asa",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T19:00:04Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-05T19:00:00Z",
|
||||
"metrics": {
|
||||
"kind": "aspa",
|
||||
"provider_count": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "aspa",
|
||||
"label": "p95",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/aspa/p95.asa",
|
||||
"size_bytes": 2131,
|
||||
"sha256_hex": "87f9d2611bda2851a348619505c8b872bd34c26ce1ddfc46cb5d21bc8d055e5d",
|
||||
"pack_rel_path": "rrdp/rrdp.arin.net/e2c8cb4b372d841061fc231dcdc28a679c244f4f630d41cfe01c8c3aaa3a36f7/rsync/rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/f60c9f32-a87c-4339-a2f3-6299a3b02e29/f2f0585e-a28e-4ead-9b1c-defd291ce54d/f2f0585e-a28e-4ead-9b1c-defd291ce54d.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-04T03:24:59Z",
|
||||
"manifest_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/f60c9f32-a87c-4339-a2f3-6299a3b02e29/f2f0585e-a28e-4ead-9b1c-defd291ce54d/f2f0585e-a28e-4ead-9b1c-defd291ce54d.mft",
|
||||
"object_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/f60c9f32-a87c-4339-a2f3-6299a3b02e29/f2f0585e-a28e-4ead-9b1c-defd291ce54d/3dfa4a5a-70d2-39a3-8e69-18afc586e9ec.asa",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-03T13:00:09Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-06T13:00:00Z",
|
||||
"metrics": {
|
||||
"kind": "aspa",
|
||||
"provider_count": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"obj_type": "aspa",
|
||||
"label": "p99",
|
||||
"file_rel_path": "tests/benchmark/selected_der_v2/aspa/p99.asa",
|
||||
"size_bytes": 2147,
|
||||
"sha256_hex": "8a7e34f96cbb16a3dce913dbf38f1f3fa806130100f6d38cc0b7a399f1a77e83",
|
||||
"pack_rel_path": "rrdp/rrdp.arin.net/e2c8cb4b372d841061fc231dcdc28a679c244f4f630d41cfe01c8c3aaa3a36f7/rsync/rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/d9d1572f-6cbb-4cf7-b599-e9d0e981d9bf/4ee37184-86de-48c9-af35-5d102d140fa6/4ee37184-86de-48c9-af35-5d102d140fa6.mft",
|
||||
"pack_update_time_rfc3339_utc": "2026-02-04T03:24:17Z",
|
||||
"manifest_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/d9d1572f-6cbb-4cf7-b599-e9d0e981d9bf/4ee37184-86de-48c9-af35-5d102d140fa6/4ee37184-86de-48c9-af35-5d102d140fa6.mft",
|
||||
"object_uri": "rsync://rpki.arin.net/repository/arin-rpki-ta/5e4a23ea-e80a-403e-b08c-2171da2157d3/d9d1572f-6cbb-4cf7-b599-e9d0e981d9bf/4ee37184-86de-48c9-af35-5d102d140fa6/c00a162c-b8fa-3da5-8257-7074076c1ed6.asa",
|
||||
"manifest_this_update_rfc3339_utc": "2026-02-04T03:00:08Z",
|
||||
"manifest_not_after_rfc3339_utc": "2026-02-07T03:00:00Z",
|
||||
"metrics": {
|
||||
"kind": "aspa",
|
||||
"provider_count": 8
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
tests/benchmark/selected_der_v2/roa/max.roa
Normal file
BIN
tests/benchmark/selected_der_v2/roa/max.roa
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/roa/min.roa
Normal file
BIN
tests/benchmark/selected_der_v2/roa/min.roa
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/roa/p01.roa
Normal file
BIN
tests/benchmark/selected_der_v2/roa/p01.roa
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/roa/p10.roa
Normal file
BIN
tests/benchmark/selected_der_v2/roa/p10.roa
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/roa/p25.roa
Normal file
BIN
tests/benchmark/selected_der_v2/roa/p25.roa
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/roa/p50.roa
Normal file
BIN
tests/benchmark/selected_der_v2/roa/p50.roa
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/roa/p75.roa
Normal file
BIN
tests/benchmark/selected_der_v2/roa/p75.roa
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/roa/p90.roa
Normal file
BIN
tests/benchmark/selected_der_v2/roa/p90.roa
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/roa/p95.roa
Normal file
BIN
tests/benchmark/selected_der_v2/roa/p95.roa
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der_v2/roa/p99.roa
Normal file
BIN
tests/benchmark/selected_der_v2/roa/p99.roa
Normal file
Binary file not shown.
@ -2,6 +2,7 @@ use der_parser::num_bigint::BigUint;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use rpki::data_model::aspa::{AspaEContent, AspaValidateError};
|
||||
use rpki::data_model::common::X509NameDer;
|
||||
use rpki::data_model::rc::{
|
||||
AsIdOrRange, AsIdentifierChoice, AsResourceSet, IpResourceSet, RcExtensions, ResourceCertKind,
|
||||
ResourceCertificate, RpkixTbsCertificate, SubjectInfoAccess,
|
||||
@ -17,8 +18,8 @@ fn dummy_ee(
|
||||
version: 2,
|
||||
serial_number: BigUint::from(1u8),
|
||||
signature_algorithm: "1.2.840.113549.1.1.11".to_string(),
|
||||
issuer_dn: "CN=issuer".to_string(),
|
||||
subject_dn: "CN=subject".to_string(),
|
||||
issuer_name: X509NameDer(b"CN=issuer".to_vec()),
|
||||
subject_name: X509NameDer(b"CN=subject".to_vec()),
|
||||
validity_not_before: OffsetDateTime::UNIX_EPOCH,
|
||||
validity_not_after: OffsetDateTime::UNIX_EPOCH,
|
||||
subject_public_key_info: vec![],
|
||||
|
||||
@ -127,8 +127,8 @@ impl From<&RpkixTbsCertificate> for RpkixTbsCertificatePretty {
|
||||
version: v.version,
|
||||
serial_number: hex::encode(v.serial_number.to_bytes_be()),
|
||||
signature_algorithm: v.signature_algorithm.clone(),
|
||||
issuer_dn: v.issuer_dn.clone(),
|
||||
subject_dn: v.subject_dn.clone(),
|
||||
issuer_dn: v.issuer_name.to_string(),
|
||||
subject_dn: v.subject_name.to_string(),
|
||||
validity_not_before: v.validity_not_before,
|
||||
validity_not_after: v.validity_not_after,
|
||||
subject_public_key_info: bytes_fmt(&v.subject_public_key_info),
|
||||
|
||||
@ -120,7 +120,7 @@ fn canonicalize_sorts_families_sorts_and_dedups_addresses() {
|
||||
IpPrefix {
|
||||
afi: RoaAfi::Ipv4,
|
||||
prefix_len: 24,
|
||||
addr: vec![192, 0, 2, 0],
|
||||
addr: [192, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
use der_parser::num_bigint::BigUint;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use rpki::data_model::common::X509NameDer;
|
||||
use rpki::data_model::rc::{
|
||||
Afi, AsResourceSet, IpAddressChoice, IpAddressFamily, IpAddressOrRange, IpPrefix,
|
||||
IpResourceSet, RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
|
||||
@ -20,8 +21,8 @@ fn dummy_ee(
|
||||
version: 2,
|
||||
serial_number: BigUint::from(1u8),
|
||||
signature_algorithm: "1.2.840.113549.1.1.11".to_string(),
|
||||
issuer_dn: "CN=issuer".to_string(),
|
||||
subject_dn: "CN=subject".to_string(),
|
||||
issuer_name: X509NameDer(b"CN=issuer".to_vec()),
|
||||
subject_name: X509NameDer(b"CN=subject".to_vec()),
|
||||
validity_not_before: OffsetDateTime::UNIX_EPOCH,
|
||||
validity_not_after: OffsetDateTime::UNIX_EPOCH,
|
||||
subject_public_key_info: vec![],
|
||||
@ -55,7 +56,7 @@ fn test_roa_single_v4_prefix() -> RoaEContent {
|
||||
prefix: rpki::data_model::roa::IpPrefix {
|
||||
afi: RoaAfi::Ipv4,
|
||||
prefix_len: 8,
|
||||
addr: vec![10, 0, 0, 0],
|
||||
addr: [10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
},
|
||||
max_length: Some(24),
|
||||
}],
|
||||
@ -159,7 +160,24 @@ fn contains_prefix_handles_non_octet_boundary_prefix_len() {
|
||||
prefix: rpki::data_model::roa::IpPrefix {
|
||||
afi: RoaAfi::Ipv4,
|
||||
prefix_len: 16,
|
||||
addr: vec![0b1010_0000, 0x12, 0, 0], // 160.18.0.0/16
|
||||
addr: [
|
||||
0b1010_0000,
|
||||
0x12,
|
||||
0,
|
||||
0, // 160.18.0.0/16
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
},
|
||||
max_length: None,
|
||||
}],
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use der_parser::num_bigint::BigUint;
|
||||
use rpki::data_model::common::X509NameDer;
|
||||
use rpki::data_model::oid::OID_CP_IPADDR_ASNUMBER;
|
||||
use rpki::data_model::rc::{
|
||||
Afi, AsIdOrRange, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressFamily,
|
||||
@ -17,8 +18,8 @@ fn dummy_rc_ca(ext: RcExtensions) -> ResourceCertificate {
|
||||
version: 2,
|
||||
serial_number: BigUint::from(1u32),
|
||||
signature_algorithm: "1.2.840.113549.1.1.11".into(),
|
||||
issuer_dn: "CN=TA".into(),
|
||||
subject_dn: "CN=TA".into(),
|
||||
issuer_name: X509NameDer(b"CN=TA".to_vec()),
|
||||
subject_name: X509NameDer(b"CN=TA".to_vec()),
|
||||
validity_not_before: t,
|
||||
validity_not_after: t,
|
||||
subject_public_key_info: Vec::new(),
|
||||
|
||||
@ -107,23 +107,16 @@ fn audit_helpers_format_roa_ip_prefix_smoke() {
|
||||
let v4 = IpPrefix {
|
||||
afi: RoaAfi::Ipv4,
|
||||
prefix_len: 24,
|
||||
addr: vec![192, 0, 2, 0],
|
||||
addr: [192, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
};
|
||||
assert_eq!(rpki::audit::format_roa_ip_prefix(&v4), "192.0.2.0/24");
|
||||
|
||||
let v6 = IpPrefix {
|
||||
afi: RoaAfi::Ipv6,
|
||||
prefix_len: 32,
|
||||
addr: vec![0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
addr: [0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
};
|
||||
assert!(rpki::audit::format_roa_ip_prefix(&v6).ends_with("/32"));
|
||||
|
||||
let bad = IpPrefix {
|
||||
afi: RoaAfi::Ipv4,
|
||||
prefix_len: 8,
|
||||
addr: vec![1, 2, 3],
|
||||
};
|
||||
assert!(rpki::audit::format_roa_ip_prefix(&bad).starts_with("ipv4:"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user