优化数据对象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"]
|
full = ["dep:rocksdb"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
asn1-rs = "0.7.1"
|
||||||
der-parser = { version = "10.0.0", features = ["serialize"] }
|
der-parser = { version = "10.0.0", features = ["serialize"] }
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
base64 = "0.22.1"
|
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,
|
"startArrowhead": null,
|
||||||
"endArrowhead": "arrow",
|
"endArrowhead": "arrow",
|
||||||
"elbowed": false
|
"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": {
|
"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 {
|
pub fn format_roa_ip_prefix(p: &crate::data_model::roa::IpPrefix) -> String {
|
||||||
|
let addr = p.addr_bytes();
|
||||||
match p.afi {
|
match p.afi {
|
||||||
crate::data_model::roa::RoaAfi::Ipv4 => {
|
crate::data_model::roa::RoaAfi::Ipv4 => {
|
||||||
if p.addr.len() != 4 {
|
|
||||||
return format!("ipv4:{:02X?}/{}", p.addr, p.prefix_len);
|
|
||||||
}
|
|
||||||
format!(
|
format!(
|
||||||
"{}.{}.{}.{}{}",
|
"{}.{}.{}.{}{}",
|
||||||
p.addr[0],
|
addr[0],
|
||||||
p.addr[1],
|
addr[1],
|
||||||
p.addr[2],
|
addr[2],
|
||||||
p.addr[3],
|
addr[3],
|
||||||
format!("/{}", p.prefix_len)
|
format!("/{}", p.prefix_len)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
crate::data_model::roa::RoaAfi::Ipv6 => {
|
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);
|
let mut parts = Vec::with_capacity(8);
|
||||||
for i in 0..8 {
|
for i in 0..8 {
|
||||||
let hi = p.addr[i * 2] as u16;
|
let hi = addr[i * 2] as u16;
|
||||||
let lo = p.addr[i * 2 + 1] as u16;
|
let lo = addr[i * 2 + 1] as u16;
|
||||||
parts.push(format!("{:x}", (hi << 8) | lo));
|
parts.push(format!("{:x}", (hi << 8) | lo));
|
||||||
}
|
}
|
||||||
format!("{}{}", parts.join(":"), format!("/{}", p.prefix_len))
|
format!("{}{}", parts.join(":"), format!("/{}", p.prefix_len))
|
||||||
|
|||||||
@ -612,7 +612,7 @@ mod tests {
|
|||||||
prefix: crate::data_model::roa::IpPrefix {
|
prefix: crate::data_model::roa::IpPrefix {
|
||||||
afi: crate::data_model::roa::RoaAfi::Ipv4,
|
afi: crate::data_model::roa::RoaAfi::Ipv4,
|
||||||
prefix_len: 24,
|
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,
|
max_length: 24,
|
||||||
}],
|
}],
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
use crate::data_model::oid::OID_CT_ASPA;
|
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::rc::ResourceCertificate;
|
||||||
use crate::data_model::signed_object::{
|
use crate::data_model::signed_object::{
|
||||||
RpkiSignedObject, RpkiSignedObjectParsed, SignedObjectParseError, SignedObjectValidateError,
|
RpkiSignedObject, RpkiSignedObjectParsed, SignedObjectParseError, SignedObjectValidateError,
|
||||||
};
|
};
|
||||||
use der_parser::ber::Class;
|
|
||||||
use der_parser::der::{DerObject, Tag, parse_der};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct AspaObject {
|
pub struct AspaObject {
|
||||||
@ -203,7 +202,7 @@ impl AspaObject {
|
|||||||
impl AspaEContent {
|
impl AspaEContent {
|
||||||
/// Parse step of scheme A (`parse → validate → verify`).
|
/// Parse step of scheme A (`parse → validate → verify`).
|
||||||
pub fn parse_der(der: &[u8]) -> Result<AspaEContentParsed, AspaParseError> {
|
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() {
|
if !rem.is_empty() {
|
||||||
return Err(AspaParseError::TrailingBytes(rem.len()));
|
return Err(AspaParseError::TrailingBytes(rem.len()));
|
||||||
}
|
}
|
||||||
@ -292,47 +291,66 @@ impl AspaObjectParsed {
|
|||||||
|
|
||||||
impl AspaEContentParsed {
|
impl AspaEContentParsed {
|
||||||
pub fn validate_profile(self) -> Result<AspaEContent, AspaProfileError> {
|
pub fn validate_profile(self) -> Result<AspaEContent, AspaProfileError> {
|
||||||
let (_rem, obj) =
|
fn count_elements(mut r: DerReader<'_>) -> Result<usize, String> {
|
||||||
parse_der(&self.der).map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
|
let mut n = 0usize;
|
||||||
|
while !r.is_empty() {
|
||||||
|
r.skip_any()?;
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
Ok(n)
|
||||||
|
}
|
||||||
|
|
||||||
let seq = obj
|
let mut r = DerReader::new(&self.der);
|
||||||
.as_sequence()
|
let mut seq = r
|
||||||
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
|
.take_sequence()
|
||||||
if seq.len() != 3 {
|
.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);
|
return Err(AspaProfileError::InvalidAttestationSequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
// version [0] EXPLICIT INTEGER MUST be present and MUST be 1.
|
// version [0] EXPLICIT INTEGER MUST be present and MUST be 1.
|
||||||
let v_obj = &seq[0];
|
if seq
|
||||||
if v_obj.class() != Class::ContextSpecific || v_obj.tag() != Tag(0) {
|
.peek_tag()
|
||||||
|
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?
|
||||||
|
!= 0xA0
|
||||||
|
{
|
||||||
return Err(AspaProfileError::VersionMustBeExplicitOne);
|
return Err(AspaProfileError::VersionMustBeExplicitOne);
|
||||||
}
|
}
|
||||||
let inner_der = v_obj
|
let (inner_tag, inner_val) = seq
|
||||||
.as_slice()
|
.take_explicit(0xA0)
|
||||||
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
|
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
|
||||||
let (rem, inner) =
|
if inner_tag != 0x02 {
|
||||||
parse_der(inner_der).map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
|
return Err(AspaProfileError::VersionMustBeExplicitOne);
|
||||||
if !rem.is_empty() {
|
|
||||||
return Err(AspaProfileError::ProfileDecode(
|
|
||||||
"trailing bytes inside ASProviderAttestation.version".into(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
let v = inner
|
let v = crate::data_model::common::der_uint_from_bytes(inner_val)
|
||||||
.as_u64()
|
|
||||||
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
|
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
|
||||||
if v != 1 {
|
if v != 1 {
|
||||||
return Err(AspaProfileError::VersionMustBeExplicitOne);
|
return Err(AspaProfileError::VersionMustBeExplicitOne);
|
||||||
}
|
}
|
||||||
|
|
||||||
let customer_u64 = seq[1]
|
let customer_u64 = seq
|
||||||
.as_u64()
|
.take_uint_u64()
|
||||||
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
|
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
|
||||||
if customer_u64 > u32::MAX as u64 {
|
if customer_u64 > u32::MAX as u64 {
|
||||||
return Err(AspaProfileError::CustomerAsIdOutOfRange(customer_u64));
|
return Err(AspaProfileError::CustomerAsIdOutOfRange(customer_u64));
|
||||||
}
|
}
|
||||||
let customer_as_id = customer_u64 as u32;
|
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 {
|
Ok(AspaEContent {
|
||||||
version: 1,
|
version: 1,
|
||||||
@ -342,19 +360,19 @@ impl AspaEContentParsed {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_providers(obj: &DerObject<'_>, customer_as_id: u32) -> Result<Vec<u32>, AspaProfileError> {
|
fn parse_providers_cursor(
|
||||||
let seq = obj
|
mut seq: DerReader<'_>,
|
||||||
.as_sequence()
|
customer_as_id: u32,
|
||||||
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
|
) -> Result<Vec<u32>, AspaProfileError> {
|
||||||
if seq.is_empty() {
|
if seq.is_empty() {
|
||||||
return Err(AspaProfileError::EmptyProviders);
|
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;
|
let mut prev: Option<u32> = None;
|
||||||
for item in seq {
|
while !seq.is_empty() {
|
||||||
let v = item
|
let v = seq
|
||||||
.as_u64()
|
.take_uint_u64()
|
||||||
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
|
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
|
||||||
if v > u32::MAX as u64 {
|
if v > u32::MAX as u64 {
|
||||||
return Err(AspaProfileError::ProviderAsIdOutOfRange(v));
|
return Err(AspaProfileError::ProviderAsIdOutOfRange(v));
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
use x509_parser::asn1_rs::Tag;
|
use x509_parser::asn1_rs::Tag;
|
||||||
use x509_parser::x509::AlgorithmIdentifier;
|
use x509_parser::x509::AlgorithmIdentifier;
|
||||||
|
use x509_parser::prelude::FromDer;
|
||||||
|
|
||||||
pub type UtcTime = time::OffsetDateTime;
|
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".
|
/// Filename extensions registered in IANA "RPKI Repository Name Schemes".
|
||||||
///
|
///
|
||||||
/// Source: <https://www.iana.org/assignments/rpki/rpki.xhtml>
|
/// Source: <https://www.iana.org/assignments/rpki/rpki.xhtml>
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
pub use crate::data_model::common::{Asn1TimeEncoding, Asn1TimeUtc, BigUnsigned};
|
pub use crate::data_model::common::{Asn1TimeEncoding, Asn1TimeUtc, BigUnsigned};
|
||||||
use crate::data_model::oid::{
|
use crate::data_model::oid::{
|
||||||
OID_AUTHORITY_KEY_IDENTIFIER, OID_CRL_NUMBER, OID_SHA256_WITH_RSA_ENCRYPTION,
|
OID_AUTHORITY_KEY_IDENTIFIER, OID_AUTHORITY_KEY_IDENTIFIER_RAW, OID_CRL_NUMBER,
|
||||||
OID_SUBJECT_KEY_IDENTIFIER,
|
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::certificate::X509Certificate;
|
||||||
use x509_parser::extensions::{ParsedExtension, X509Extension};
|
use x509_parser::extensions::{ParsedExtension, X509Extension};
|
||||||
@ -425,8 +426,15 @@ fn algorithm_identifier_value(ai: &AlgorithmIdentifier<'_>) -> AlgorithmIdentifi
|
|||||||
tag: p.tag(),
|
tag: p.tag(),
|
||||||
data: p.as_bytes().to_vec(),
|
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 {
|
AlgorithmIdentifierValue {
|
||||||
oid: ai.algorithm.to_id_string(),
|
oid,
|
||||||
parameters,
|
parameters,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -434,9 +442,8 @@ fn algorithm_identifier_value(ai: &AlgorithmIdentifier<'_>) -> AlgorithmIdentifi
|
|||||||
fn parse_extensions_parse(exts: &[X509Extension<'_>]) -> Result<Vec<CrlExtensionParsed>, String> {
|
fn parse_extensions_parse(exts: &[X509Extension<'_>]) -> Result<Vec<CrlExtensionParsed>, String> {
|
||||||
let mut out = Vec::with_capacity(exts.len());
|
let mut out = Vec::with_capacity(exts.len());
|
||||||
for ext in exts {
|
for ext in exts {
|
||||||
let oid = ext.oid.to_id_string();
|
let oid = ext.oid.as_bytes();
|
||||||
match oid.as_str() {
|
if oid == OID_AUTHORITY_KEY_IDENTIFIER_RAW {
|
||||||
OID_AUTHORITY_KEY_IDENTIFIER => {
|
|
||||||
let ParsedExtension::AuthorityKeyIdentifier(aki) = ext.parsed_extension() else {
|
let ParsedExtension::AuthorityKeyIdentifier(aki) = ext.parsed_extension() else {
|
||||||
return Err("AKI extension parse failed".to_string());
|
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(),
|
|| aki.authority_cert_serial.is_some(),
|
||||||
critical: ext.critical,
|
critical: ext.critical,
|
||||||
});
|
});
|
||||||
}
|
} else if oid == OID_CRL_NUMBER_RAW {
|
||||||
OID_CRL_NUMBER => match ext.parsed_extension() {
|
match ext.parsed_extension() {
|
||||||
ParsedExtension::CRLNumber(n) => out.push(CrlExtensionParsed::CrlNumber {
|
ParsedExtension::CRLNumber(n) => out.push(CrlExtensionParsed::CrlNumber {
|
||||||
number: n.clone(),
|
number: n.clone(),
|
||||||
critical: ext.critical,
|
critical: ext.critical,
|
||||||
}),
|
}),
|
||||||
_ => return Err("CRLNumber extension parse failed".to_string()),
|
_ => return Err("CRLNumber extension parse failed".to_string()),
|
||||||
},
|
}
|
||||||
_ => out.push(CrlExtensionParsed::Other {
|
} else {
|
||||||
oid,
|
out.push(CrlExtensionParsed::Other {
|
||||||
|
oid: ext.oid.to_id_string(),
|
||||||
critical: ext.critical,
|
critical: ext.critical,
|
||||||
}),
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(out)
|
Ok(out)
|
||||||
@ -525,7 +533,7 @@ fn validate_extensions_profile(
|
|||||||
fn get_subject_key_identifier(cert: &X509Certificate<'_>) -> Option<Vec<u8>> {
|
fn get_subject_key_identifier(cert: &X509Certificate<'_>) -> Option<Vec<u8>> {
|
||||||
cert.extensions()
|
cert.extensions()
|
||||||
.iter()
|
.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() {
|
.and_then(|ext| match ext.parsed_extension() {
|
||||||
ParsedExtension::SubjectKeyIdentifier(ki) => Some(ki.0.to_vec()),
|
ParsedExtension::SubjectKeyIdentifier(ki) => Some(ki.0.to_vec()),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
use crate::data_model::common::{BigUnsigned, UtcTime};
|
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::oid::{OID_CT_RPKI_MANIFEST, OID_SHA256};
|
||||||
use crate::data_model::rc::ResourceCertificate;
|
use crate::data_model::rc::ResourceCertificate;
|
||||||
use crate::data_model::signed_object::{
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@ -1,42 +1,72 @@
|
|||||||
pub const OID_SHA256: &str = "2.16.840.1.101.3.4.2.1";
|
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: &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: &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: &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: &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: &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: &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)
|
// X.509 extensions (RFC 5280 / RFC 6487)
|
||||||
pub const OID_BASIC_CONSTRAINTS: &str = "2.5.29.19";
|
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: &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: &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: &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: &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: &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: &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: &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: &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: &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: &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: &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)
|
// 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: &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: &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: &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: &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: &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: &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)
|
// 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: &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: &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)
|
// 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: &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::ber::{BerObjectContent, Class};
|
||||||
use der_parser::der::{DerObject, Tag, parse_der};
|
use der_parser::der::{DerObject, Tag, parse_der};
|
||||||
use der_parser::num_bigint::BigUint;
|
use der_parser::num_bigint::BigUint;
|
||||||
use url::Url;
|
|
||||||
use x509_parser::asn1_rs::{Class as Asn1Class, Tag as Asn1Tag};
|
use x509_parser::asn1_rs::{Class as Asn1Class, Tag as Asn1Tag};
|
||||||
use x509_parser::extensions::ParsedExtension;
|
use x509_parser::extensions::ParsedExtension;
|
||||||
use x509_parser::prelude::{FromDer, X509Certificate, X509Extension, X509Version};
|
use x509_parser::prelude::{FromDer, X509Certificate, X509Extension, X509Version};
|
||||||
|
|
||||||
use crate::data_model::common::{
|
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::{
|
use crate::data_model::oid::{
|
||||||
OID_AD_CA_ISSUERS, OID_AD_SIGNED_OBJECT, OID_AUTHORITY_INFO_ACCESS,
|
OID_AD_CA_ISSUERS_RAW, OID_AD_CA_REPOSITORY, OID_AD_CA_REPOSITORY_RAW, OID_AD_RPKI_MANIFEST,
|
||||||
OID_AUTHORITY_KEY_IDENTIFIER, OID_AUTONOMOUS_SYS_IDS, OID_CP_IPADDR_ASNUMBER,
|
OID_AD_RPKI_MANIFEST_RAW, OID_AD_RPKI_NOTIFY, OID_AD_RPKI_NOTIFY_RAW, OID_AD_SIGNED_OBJECT,
|
||||||
OID_CRL_DISTRIBUTION_POINTS, OID_IP_ADDR_BLOCKS, OID_SHA256_WITH_RSA_ENCRYPTION,
|
OID_AD_SIGNED_OBJECT_RAW, OID_AUTHORITY_INFO_ACCESS_RAW, OID_AUTHORITY_KEY_IDENTIFIER_RAW,
|
||||||
OID_SUBJECT_INFO_ACCESS, OID_SUBJECT_KEY_IDENTIFIER,
|
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).
|
/// Resource Certificate kind (semantic classification).
|
||||||
@ -43,8 +45,8 @@ pub struct RpkixTbsCertificate {
|
|||||||
pub version: u32,
|
pub version: u32,
|
||||||
pub serial_number: BigUint,
|
pub serial_number: BigUint,
|
||||||
pub signature_algorithm: String,
|
pub signature_algorithm: String,
|
||||||
pub issuer_dn: String,
|
pub issuer_name: X509NameDer,
|
||||||
pub subject_dn: String,
|
pub subject_name: X509NameDer,
|
||||||
pub validity_not_before: UtcTime,
|
pub validity_not_before: UtcTime,
|
||||||
pub validity_not_after: UtcTime,
|
pub validity_not_after: UtcTime,
|
||||||
/// DER encoding of SubjectPublicKeyInfo.
|
/// DER encoding of SubjectPublicKeyInfo.
|
||||||
@ -59,9 +61,9 @@ pub struct RcExtensions {
|
|||||||
/// Authority Key Identifier (AKI) keyIdentifier value.
|
/// Authority Key Identifier (AKI) keyIdentifier value.
|
||||||
pub authority_key_identifier: Option<Vec<u8>>,
|
pub authority_key_identifier: Option<Vec<u8>>,
|
||||||
/// CRL Distribution Points URIs (fullName).
|
/// 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.
|
/// 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 subject_info_access: Option<SubjectInfoAccess>,
|
||||||
pub certificate_policies_oid: Option<String>,
|
pub certificate_policies_oid: Option<String>,
|
||||||
|
|
||||||
@ -76,8 +78,8 @@ pub struct ResourceCertificateParsed {
|
|||||||
pub serial_number: BigUint,
|
pub serial_number: BigUint,
|
||||||
pub signature_algorithm: AlgorithmIdentifierValue,
|
pub signature_algorithm: AlgorithmIdentifierValue,
|
||||||
pub tbs_signature_algorithm: AlgorithmIdentifierValue,
|
pub tbs_signature_algorithm: AlgorithmIdentifierValue,
|
||||||
pub issuer_dn: String,
|
pub issuer_name: X509NameDer,
|
||||||
pub subject_dn: String,
|
pub subject_name: X509NameDer,
|
||||||
pub validity_not_before: Asn1TimeUtc,
|
pub validity_not_before: Asn1TimeUtc,
|
||||||
pub validity_not_after: Asn1TimeUtc,
|
pub validity_not_after: Asn1TimeUtc,
|
||||||
/// DER encoding of SubjectPublicKeyInfo.
|
/// DER encoding of SubjectPublicKeyInfo.
|
||||||
@ -130,7 +132,7 @@ pub struct AuthorityKeyIdentifierParsed {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct AuthorityInfoAccessParsed {
|
pub struct AuthorityInfoAccessParsed {
|
||||||
pub ca_issuers_uris: Vec<Url>,
|
pub ca_issuers_uris: Vec<String>,
|
||||||
pub ca_issuers_access_location_not_uri: bool,
|
pub ca_issuers_access_location_not_uri: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,7 +147,7 @@ pub struct CrlDistributionPointParsed {
|
|||||||
pub reasons_present: bool,
|
pub reasons_present: bool,
|
||||||
pub crl_issuer_present: bool,
|
pub crl_issuer_present: bool,
|
||||||
pub name_relative_to_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_not_uri: bool,
|
||||||
pub full_name_present: bool,
|
pub full_name_present: bool,
|
||||||
}
|
}
|
||||||
@ -153,7 +155,7 @@ pub struct CrlDistributionPointParsed {
|
|||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct SubjectInfoAccessParsed {
|
pub struct SubjectInfoAccessParsed {
|
||||||
pub access_descriptions: Vec<AccessDescription>,
|
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,
|
pub signed_object_access_location_not_uri: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,7 +172,7 @@ pub struct SubjectInfoAccessCa {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct SubjectInfoAccessEe {
|
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.
|
/// The full list of access descriptions as carried in the SIA extension.
|
||||||
pub access_descriptions: Vec<AccessDescription>,
|
pub access_descriptions: Vec<AccessDescription>,
|
||||||
}
|
}
|
||||||
@ -178,7 +180,7 @@ pub struct SubjectInfoAccessEe {
|
|||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct AccessDescription {
|
pub struct AccessDescription {
|
||||||
pub access_method_oid: String,
|
pub access_method_oid: String,
|
||||||
pub access_location: Url,
|
pub access_location: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
@ -541,8 +543,8 @@ impl ResourceCertificate {
|
|||||||
serial_number: cert.tbs_certificate.serial.clone(),
|
serial_number: cert.tbs_certificate.serial.clone(),
|
||||||
signature_algorithm,
|
signature_algorithm,
|
||||||
tbs_signature_algorithm,
|
tbs_signature_algorithm,
|
||||||
issuer_dn: cert.issuer().to_string(),
|
issuer_name: X509NameDer(cert.issuer().as_raw().to_vec()),
|
||||||
subject_dn: cert.subject().to_string(),
|
subject_name: X509NameDer(cert.subject().as_raw().to_vec()),
|
||||||
validity_not_before,
|
validity_not_before,
|
||||||
validity_not_after,
|
validity_not_after,
|
||||||
subject_public_key_info,
|
subject_public_key_info,
|
||||||
@ -591,7 +593,7 @@ impl ResourceCertificateParsed {
|
|||||||
return Err(ResourceCertificateProfileError::InvalidSignatureAlgorithmParameters);
|
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 extensions = self.extensions.validate_profile(is_self_signed)?;
|
||||||
let kind = if extensions.basic_constraints_ca {
|
let kind = if extensions.basic_constraints_ca {
|
||||||
ResourceCertKind::Ca
|
ResourceCertKind::Ca
|
||||||
@ -605,8 +607,8 @@ impl ResourceCertificateParsed {
|
|||||||
version,
|
version,
|
||||||
serial_number: self.serial_number,
|
serial_number: self.serial_number,
|
||||||
signature_algorithm: self.signature_algorithm.oid,
|
signature_algorithm: self.signature_algorithm.oid,
|
||||||
issuer_dn: self.issuer_dn,
|
issuer_name: self.issuer_name,
|
||||||
subject_dn: self.subject_dn,
|
subject_name: self.subject_name,
|
||||||
validity_not_before: self.validity_not_before.utc,
|
validity_not_before: self.validity_not_before.utc,
|
||||||
validity_not_after: self.validity_not_after.utc,
|
validity_not_after: self.validity_not_after.utc,
|
||||||
subject_public_key_info: self.subject_public_key_info,
|
subject_public_key_info: self.subject_public_key_info,
|
||||||
@ -622,20 +624,38 @@ impl RcExtensionsParsed {
|
|||||||
self,
|
self,
|
||||||
is_self_signed: bool,
|
is_self_signed: bool,
|
||||||
) -> Result<RcExtensions, ResourceCertificateProfileError> {
|
) -> 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(
|
return Err(ResourceCertificateProfileError::DuplicateExtension(
|
||||||
"basicConstraints",
|
"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() {
|
let subject_key_identifier = match subject_key_identifier.len() {
|
||||||
[] => None,
|
0 => None,
|
||||||
[(ski, critical)] => {
|
1 => {
|
||||||
if *critical {
|
let (ski, critical) = subject_key_identifier
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.expect("len==1");
|
||||||
|
if critical {
|
||||||
return Err(ResourceCertificateProfileError::SkiCriticality);
|
return Err(ResourceCertificateProfileError::SkiCriticality);
|
||||||
}
|
}
|
||||||
Some(ski.clone())
|
Some(ski)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ResourceCertificateProfileError::DuplicateExtension(
|
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 {
|
if is_self_signed {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
return Err(ResourceCertificateProfileError::AkiMissing);
|
return Err(ResourceCertificateProfileError::AkiMissing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[(aki, critical)] => {
|
1 => {
|
||||||
if *critical {
|
let (aki, critical) = authority_key_identifier
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.expect("len==1");
|
||||||
|
if critical {
|
||||||
return Err(ResourceCertificateProfileError::AkiCriticality);
|
return Err(ResourceCertificateProfileError::AkiCriticality);
|
||||||
}
|
}
|
||||||
if aki.has_authority_cert_issuer {
|
if aki.has_authority_cert_issuer {
|
||||||
@ -662,7 +686,7 @@ impl RcExtensionsParsed {
|
|||||||
if aki.has_authority_cert_serial {
|
if aki.has_authority_cert_serial {
|
||||||
return Err(ResourceCertificateProfileError::AkiAuthorityCertSerialPresent);
|
return Err(ResourceCertificateProfileError::AkiAuthorityCertSerialPresent);
|
||||||
}
|
}
|
||||||
let keyid = aki.key_identifier.clone();
|
let keyid = aki.key_identifier;
|
||||||
if is_self_signed {
|
if is_self_signed {
|
||||||
if let (Some(keyid), Some(ski)) =
|
if let (Some(keyid), Some(ski)) =
|
||||||
(keyid.as_ref(), subject_key_identifier.as_ref())
|
(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 {
|
if is_self_signed {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
return Err(ResourceCertificateProfileError::CrlDistributionPointsMissing);
|
return Err(ResourceCertificateProfileError::CrlDistributionPointsMissing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[(crldp, critical)] => {
|
1 => {
|
||||||
if *critical {
|
let (crldp, critical) = crl_distribution_points
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.expect("len==1");
|
||||||
|
if critical {
|
||||||
return Err(ResourceCertificateProfileError::CrlDistributionPointsCriticality);
|
return Err(ResourceCertificateProfileError::CrlDistributionPointsCriticality);
|
||||||
}
|
}
|
||||||
if is_self_signed {
|
if is_self_signed {
|
||||||
@ -703,7 +731,11 @@ impl RcExtensionsParsed {
|
|||||||
if crldp.distribution_points.len() != 1 {
|
if crldp.distribution_points.len() != 1 {
|
||||||
return Err(ResourceCertificateProfileError::CrlDistributionPointsNotSingle);
|
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 {
|
if dp.reasons_present {
|
||||||
return Err(ResourceCertificateProfileError::CrlDistributionPointsHasReasons);
|
return Err(ResourceCertificateProfileError::CrlDistributionPointsHasReasons);
|
||||||
}
|
}
|
||||||
@ -723,10 +755,10 @@ impl RcExtensionsParsed {
|
|||||||
ResourceCertificateProfileError::CrlDistributionPointsFullNameNotUri,
|
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);
|
return Err(ResourceCertificateProfileError::CrlDistributionPointsNoRsync);
|
||||||
}
|
}
|
||||||
Some(dp.full_name_uris.clone())
|
Some(dp.full_name_uris)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ResourceCertificateProfileError::DuplicateExtension(
|
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 {
|
if is_self_signed {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
return Err(ResourceCertificateProfileError::AuthorityInfoAccessMissing);
|
return Err(ResourceCertificateProfileError::AuthorityInfoAccessMissing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[(aia, critical)] => {
|
1 => {
|
||||||
if *critical {
|
let (aia, critical) = authority_info_access.into_iter().next().expect("len==1");
|
||||||
|
if critical {
|
||||||
return Err(ResourceCertificateProfileError::AuthorityInfoAccessCriticality);
|
return Err(ResourceCertificateProfileError::AuthorityInfoAccessCriticality);
|
||||||
}
|
}
|
||||||
if is_self_signed {
|
if is_self_signed {
|
||||||
@ -762,10 +795,10 @@ impl RcExtensionsParsed {
|
|||||||
ResourceCertificateProfileError::AuthorityInfoAccessMissingCaIssuers,
|
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);
|
return Err(ResourceCertificateProfileError::AuthorityInfoAccessNoRsync);
|
||||||
}
|
}
|
||||||
Some(aia.ca_issuers_uris.clone())
|
Some(aia.ca_issuers_uris)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ResourceCertificateProfileError::DuplicateExtension(
|
return Err(ResourceCertificateProfileError::DuplicateExtension(
|
||||||
@ -774,28 +807,32 @@ impl RcExtensionsParsed {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let subject_info_access = match self.subject_info_access.as_slice() {
|
let subject_info_access = match subject_info_access.len() {
|
||||||
[] => None,
|
0 => None,
|
||||||
[(sia, critical)] => {
|
1 => {
|
||||||
if *critical {
|
let (sia, critical) = subject_info_access.into_iter().next().expect("len==1");
|
||||||
|
if critical {
|
||||||
return Err(ResourceCertificateProfileError::SiaCriticality);
|
return Err(ResourceCertificateProfileError::SiaCriticality);
|
||||||
}
|
}
|
||||||
if sia.signed_object_access_location_not_uri {
|
if sia.signed_object_access_location_not_uri {
|
||||||
return Err(ResourceCertificateProfileError::SignedObjectSiaNotUri);
|
return Err(ResourceCertificateProfileError::SignedObjectSiaNotUri);
|
||||||
}
|
}
|
||||||
if !sia.signed_object_uris.is_empty()
|
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);
|
return Err(ResourceCertificateProfileError::SignedObjectSiaNoRsync);
|
||||||
}
|
}
|
||||||
if sia.signed_object_uris.is_empty() {
|
if sia.signed_object_uris.is_empty() {
|
||||||
Some(SubjectInfoAccess::Ca(SubjectInfoAccessCa {
|
Some(SubjectInfoAccess::Ca(SubjectInfoAccessCa {
|
||||||
access_descriptions: sia.access_descriptions.clone(),
|
access_descriptions: sia.access_descriptions,
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
Some(SubjectInfoAccess::Ee(SubjectInfoAccessEe {
|
Some(SubjectInfoAccess::Ee(SubjectInfoAccessEe {
|
||||||
signed_object_uris: sia.signed_object_uris.clone(),
|
signed_object_uris: sia.signed_object_uris,
|
||||||
access_descriptions: sia.access_descriptions.clone(),
|
access_descriptions: sia.access_descriptions,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -806,10 +843,11 @@ impl RcExtensionsParsed {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let certificate_policies_oid = match self.certificate_policies.as_slice() {
|
let certificate_policies_oid = match certificate_policies.len() {
|
||||||
[] => None,
|
0 => None,
|
||||||
[(oids, critical)] => {
|
1 => {
|
||||||
if !*critical {
|
let (oids, critical) = certificate_policies.into_iter().next().expect("len==1");
|
||||||
|
if !critical {
|
||||||
return Err(ResourceCertificateProfileError::CertificatePoliciesCriticality);
|
return Err(ResourceCertificateProfileError::CertificatePoliciesCriticality);
|
||||||
}
|
}
|
||||||
if oids.len() != 1 {
|
if oids.len() != 1 {
|
||||||
@ -817,7 +855,7 @@ impl RcExtensionsParsed {
|
|||||||
"expected exactly one policy".into(),
|
"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 {
|
if policy_oid != OID_CP_IPADDR_ASNUMBER {
|
||||||
return Err(ResourceCertificateProfileError::InvalidCertificatePolicy(
|
return Err(ResourceCertificateProfileError::InvalidCertificatePolicy(
|
||||||
policy_oid,
|
policy_oid,
|
||||||
@ -832,13 +870,14 @@ impl RcExtensionsParsed {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let ip_resources = match self.ip_resources.as_slice() {
|
let ip_resources = match ip_resources.len() {
|
||||||
[] => None,
|
0 => None,
|
||||||
[(ip, critical)] => {
|
1 => {
|
||||||
if !*critical {
|
let (ip, critical) = ip_resources.into_iter().next().expect("len==1");
|
||||||
|
if !critical {
|
||||||
return Err(ResourceCertificateProfileError::IpResourcesCriticality);
|
return Err(ResourceCertificateProfileError::IpResourcesCriticality);
|
||||||
}
|
}
|
||||||
Some(ip.clone())
|
Some(ip)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ResourceCertificateProfileError::DuplicateExtension(
|
return Err(ResourceCertificateProfileError::DuplicateExtension(
|
||||||
@ -847,13 +886,14 @@ impl RcExtensionsParsed {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let as_resources = match self.as_resources.as_slice() {
|
let as_resources = match as_resources.len() {
|
||||||
[] => None,
|
0 => None,
|
||||||
[(asn, critical)] => {
|
1 => {
|
||||||
if !*critical {
|
let (asn, critical) = as_resources.into_iter().next().expect("len==1");
|
||||||
|
if !critical {
|
||||||
return Err(ResourceCertificateProfileError::AsResourcesCriticality);
|
return Err(ResourceCertificateProfileError::AsResourcesCriticality);
|
||||||
}
|
}
|
||||||
Some(asn.clone())
|
Some(asn)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ResourceCertificateProfileError::DuplicateExtension(
|
return Err(ResourceCertificateProfileError::DuplicateExtension(
|
||||||
@ -884,8 +924,16 @@ fn algorithm_identifier_value(
|
|||||||
tag: p.tag(),
|
tag: p.tag(),
|
||||||
data: p.as_bytes().to_vec(),
|
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 {
|
AlgorithmIdentifierValue {
|
||||||
oid: ai.algorithm.to_id_string(),
|
oid,
|
||||||
parameters,
|
parameters,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -905,25 +953,22 @@ fn parse_extensions_parse(
|
|||||||
let mut as_resources: Vec<(AsResourceSet, bool)> = Vec::new();
|
let mut as_resources: Vec<(AsResourceSet, bool)> = Vec::new();
|
||||||
|
|
||||||
for ext in exts {
|
for ext in exts {
|
||||||
let oid = ext.oid.to_id_string();
|
let oid = ext.oid.as_bytes();
|
||||||
match oid.as_str() {
|
if oid == OID_BASIC_CONSTRAINTS_RAW {
|
||||||
crate::data_model::oid::OID_BASIC_CONSTRAINTS => {
|
|
||||||
let ParsedExtension::BasicConstraints(bc) = ext.parsed_extension() else {
|
let ParsedExtension::BasicConstraints(bc) = ext.parsed_extension() else {
|
||||||
return Err(ResourceCertificateParseError::Parse(
|
return Err(ResourceCertificateParseError::Parse(
|
||||||
"basicConstraints parse failed".into(),
|
"basicConstraints parse failed".into(),
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
basic_constraints_ca.push(bc.ca);
|
basic_constraints_ca.push(bc.ca);
|
||||||
}
|
} else if oid == OID_SUBJECT_KEY_IDENTIFIER_RAW {
|
||||||
OID_SUBJECT_KEY_IDENTIFIER => {
|
|
||||||
let ParsedExtension::SubjectKeyIdentifier(s) = ext.parsed_extension() else {
|
let ParsedExtension::SubjectKeyIdentifier(s) = ext.parsed_extension() else {
|
||||||
return Err(ResourceCertificateParseError::Parse(
|
return Err(ResourceCertificateParseError::Parse(
|
||||||
"subjectKeyIdentifier parse failed".into(),
|
"subjectKeyIdentifier parse failed".into(),
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
ski.push((s.0.to_vec(), ext.critical));
|
ski.push((s.0.to_vec(), ext.critical));
|
||||||
}
|
} else if oid == OID_AUTHORITY_KEY_IDENTIFIER_RAW {
|
||||||
OID_AUTHORITY_KEY_IDENTIFIER => {
|
|
||||||
let ParsedExtension::AuthorityKeyIdentifier(a) = ext.parsed_extension() else {
|
let ParsedExtension::AuthorityKeyIdentifier(a) = ext.parsed_extension() else {
|
||||||
return Err(ResourceCertificateParseError::Parse(
|
return Err(ResourceCertificateParseError::Parse(
|
||||||
"authorityKeyIdentifier parse failed".into(),
|
"authorityKeyIdentifier parse failed".into(),
|
||||||
@ -937,52 +982,52 @@ fn parse_extensions_parse(
|
|||||||
},
|
},
|
||||||
ext.critical,
|
ext.critical,
|
||||||
));
|
));
|
||||||
}
|
} else if oid == OID_CRL_DISTRIBUTION_POINTS_RAW {
|
||||||
OID_CRL_DISTRIBUTION_POINTS => {
|
|
||||||
let ParsedExtension::CRLDistributionPoints(p) = ext.parsed_extension() else {
|
let ParsedExtension::CRLDistributionPoints(p) = ext.parsed_extension() else {
|
||||||
return Err(ResourceCertificateParseError::Parse(
|
return Err(ResourceCertificateParseError::Parse(
|
||||||
"cRLDistributionPoints parse failed".into(),
|
"cRLDistributionPoints parse failed".into(),
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
crldp.push((parse_crldp_parse(p)?, ext.critical));
|
crldp.push((parse_crldp_parse(p)?, ext.critical));
|
||||||
}
|
} else if oid == OID_AUTHORITY_INFO_ACCESS_RAW {
|
||||||
OID_AUTHORITY_INFO_ACCESS => {
|
|
||||||
let ParsedExtension::AuthorityInfoAccess(p) = ext.parsed_extension() else {
|
let ParsedExtension::AuthorityInfoAccess(p) = ext.parsed_extension() else {
|
||||||
return Err(ResourceCertificateParseError::Parse(
|
return Err(ResourceCertificateParseError::Parse(
|
||||||
"authorityInfoAccess parse failed".into(),
|
"authorityInfoAccess parse failed".into(),
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
aia.push((parse_aia_parse(p.accessdescs.as_slice())?, ext.critical));
|
aia.push((parse_aia_parse(p.accessdescs.as_slice())?, ext.critical));
|
||||||
}
|
} else if oid == OID_SUBJECT_INFO_ACCESS_RAW {
|
||||||
OID_SUBJECT_INFO_ACCESS => {
|
|
||||||
let ParsedExtension::SubjectInfoAccess(s) = ext.parsed_extension() else {
|
let ParsedExtension::SubjectInfoAccess(s) = ext.parsed_extension() else {
|
||||||
return Err(ResourceCertificateParseError::Parse(
|
return Err(ResourceCertificateParseError::Parse(
|
||||||
"subjectInfoAccess parse failed".into(),
|
"subjectInfoAccess parse failed".into(),
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
sia.push((parse_sia_parse(s.accessdescs.as_slice())?, ext.critical));
|
sia.push((parse_sia_parse(s.accessdescs.as_slice())?, ext.critical));
|
||||||
}
|
} else if oid == OID_CERTIFICATE_POLICIES_RAW {
|
||||||
crate::data_model::oid::OID_CERTIFICATE_POLICIES => {
|
|
||||||
let ParsedExtension::CertificatePolicies(cp) = ext.parsed_extension() else {
|
let ParsedExtension::CertificatePolicies(cp) = ext.parsed_extension() else {
|
||||||
return Err(ResourceCertificateParseError::Parse(
|
return Err(ResourceCertificateParseError::Parse(
|
||||||
"certificatePolicies parse failed".into(),
|
"certificatePolicies parse failed".into(),
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
let oids: Vec<String> = cp.iter().map(|p| p.policy_id.to_id_string()).collect();
|
let mut oids: Vec<String> = Vec::with_capacity(cp.len());
|
||||||
cert_policies.push((oids, ext.critical));
|
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)
|
let parsed = IpResourceSet::decode_extn_value(ext.value)
|
||||||
.map_err(|_e| ResourceCertificateParseError::InvalidIpResourcesEncoding)?;
|
.map_err(|_e| ResourceCertificateParseError::InvalidIpResourcesEncoding)?;
|
||||||
ip_resources.push((parsed, ext.critical));
|
ip_resources.push((parsed, ext.critical));
|
||||||
}
|
} else if oid == OID_AUTONOMOUS_SYS_IDS_RAW {
|
||||||
OID_AUTONOMOUS_SYS_IDS => {
|
|
||||||
let parsed = AsResourceSet::decode_extn_value(ext.value)
|
let parsed = AsResourceSet::decode_extn_value(ext.value)
|
||||||
.map_err(|_e| ResourceCertificateParseError::InvalidAsResourcesEncoding)?;
|
.map_err(|_e| ResourceCertificateParseError::InvalidAsResourcesEncoding)?;
|
||||||
as_resources.push((parsed, ext.critical));
|
as_resources.push((parsed, ext.critical));
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(RcExtensionsParsed {
|
Ok(RcExtensionsParsed {
|
||||||
@ -1001,12 +1046,11 @@ fn parse_extensions_parse(
|
|||||||
fn parse_aia_parse(
|
fn parse_aia_parse(
|
||||||
access: &[x509_parser::extensions::AccessDescription<'_>],
|
access: &[x509_parser::extensions::AccessDescription<'_>],
|
||||||
) -> Result<AuthorityInfoAccessParsed, ResourceCertificateParseError> {
|
) -> 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;
|
let mut ca_issuers_access_location_not_uri = false;
|
||||||
|
|
||||||
for ad in access {
|
for ad in access {
|
||||||
let access_method_oid = ad.access_method.to_id_string();
|
if ad.access_method.as_bytes() != OID_AD_CA_ISSUERS_RAW {
|
||||||
if access_method_oid != OID_AD_CA_ISSUERS {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let uri = match &ad.access_location {
|
let uri = match &ad.access_location {
|
||||||
@ -1016,9 +1060,7 @@ fn parse_aia_parse(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let url = Url::parse(uri)
|
ca_issuers_uris.push(uri.to_string());
|
||||||
.map_err(|_| ResourceCertificateParseError::Parse(format!("invalid URI: {uri}")))?;
|
|
||||||
ca_issuers_uris.push(url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(AuthorityInfoAccessParsed {
|
Ok(AuthorityInfoAccessParsed {
|
||||||
@ -1032,7 +1074,7 @@ fn parse_crldp_parse(
|
|||||||
) -> Result<CrlDistributionPointsParsed, ResourceCertificateParseError> {
|
) -> Result<CrlDistributionPointsParsed, ResourceCertificateParseError> {
|
||||||
let mut out: Vec<CrlDistributionPointParsed> = Vec::new();
|
let mut out: Vec<CrlDistributionPointParsed> = Vec::new();
|
||||||
for p in crldp.iter() {
|
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_not_uri = false;
|
||||||
let mut full_name_present = false;
|
let mut full_name_present = false;
|
||||||
let mut name_relative_to_crl_issuer_present = false;
|
let mut name_relative_to_crl_issuer_present = false;
|
||||||
@ -1046,12 +1088,7 @@ fn parse_crldp_parse(
|
|||||||
for n in names {
|
for n in names {
|
||||||
match n {
|
match n {
|
||||||
x509_parser::extensions::GeneralName::URI(u) => {
|
x509_parser::extensions::GeneralName::URI(u) => {
|
||||||
let url = Url::parse(u).map_err(|_| {
|
full_name_uris.push(u.to_string());
|
||||||
ResourceCertificateParseError::Parse(format!(
|
|
||||||
"invalid URI: {u}"
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
full_name_uris.push(url);
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
full_name_not_uri = true;
|
full_name_not_uri = true;
|
||||||
@ -1084,28 +1121,37 @@ fn parse_sia_parse(
|
|||||||
access: &[x509_parser::extensions::AccessDescription<'_>],
|
access: &[x509_parser::extensions::AccessDescription<'_>],
|
||||||
) -> Result<SubjectInfoAccessParsed, ResourceCertificateParseError> {
|
) -> Result<SubjectInfoAccessParsed, ResourceCertificateParseError> {
|
||||||
let mut all = Vec::with_capacity(access.len());
|
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;
|
let mut signed_object_access_location_not_uri = false;
|
||||||
|
|
||||||
for ad in access {
|
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 {
|
let uri = match &ad.access_location {
|
||||||
x509_parser::extensions::GeneralName::URI(u) => u,
|
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;
|
signed_object_access_location_not_uri = true;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let url = Url::parse(uri)
|
if is_signed_object {
|
||||||
.map_err(|_| ResourceCertificateParseError::Parse(format!("invalid URI: {uri}")))?;
|
signed_object_uris.push(uri.to_string());
|
||||||
if access_method_oid == OID_AD_SIGNED_OBJECT {
|
|
||||||
signed_object_uris.push(url.clone());
|
|
||||||
}
|
}
|
||||||
all.push(AccessDescription {
|
all.push(AccessDescription {
|
||||||
access_method_oid,
|
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::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::rc::{Afi as RcAfi, IpPrefix as RcIpPrefix, ResourceCertificate};
|
||||||
use crate::data_model::signed_object::{
|
use crate::data_model::signed_object::{
|
||||||
RpkiSignedObject, RpkiSignedObjectParsed, SignedObjectParseError, SignedObjectValidateError,
|
RpkiSignedObject, RpkiSignedObjectParsed, SignedObjectParseError, SignedObjectValidateError,
|
||||||
};
|
};
|
||||||
use der_parser::ber::{BerObjectContent, Class};
|
|
||||||
use der_parser::der::{DerObject, Tag, parse_der};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct RoaObject {
|
pub struct RoaObject {
|
||||||
@ -229,14 +228,25 @@ pub struct IpPrefix {
|
|||||||
pub afi: RoaAfi,
|
pub afi: RoaAfi,
|
||||||
/// Prefix length in bits.
|
/// Prefix length in bits.
|
||||||
pub prefix_len: u16,
|
pub prefix_len: u16,
|
||||||
/// Network order address bytes (IPv4 4 bytes / IPv6 16 bytes), with host bits cleared.
|
/// Network order address bytes (always 16 bytes), with host bits cleared.
|
||||||
pub addr: Vec<u8>,
|
///
|
||||||
|
/// 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 {
|
impl RoaEContent {
|
||||||
/// Parse step of scheme A (`parse → validate → verify`).
|
/// Parse step of scheme A (`parse → validate → verify`).
|
||||||
pub fn parse_der(der: &[u8]) -> Result<RoaEContentParsed, RoaParseError> {
|
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() {
|
if !rem.is_empty() {
|
||||||
return Err(RoaParseError::TrailingBytes(rem.len()));
|
return Err(RoaParseError::TrailingBytes(rem.len()));
|
||||||
}
|
}
|
||||||
@ -293,7 +303,7 @@ impl RoaEContent {
|
|||||||
if !ip.contains_prefix(&rc_prefix) {
|
if !ip.contains_prefix(&rc_prefix) {
|
||||||
return Err(RoaValidateError::PrefixNotInEeResources {
|
return Err(RoaValidateError::PrefixNotInEeResources {
|
||||||
afi: entry.prefix.afi,
|
afi: entry.prefix.afi,
|
||||||
addr: entry.prefix.addr.clone(),
|
addr: entry.prefix.addr_bytes().to_vec(),
|
||||||
prefix_len: entry.prefix.prefix_len,
|
prefix_len: entry.prefix.prefix_len,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -328,55 +338,75 @@ impl RoaObjectParsed {
|
|||||||
|
|
||||||
impl RoaEContentParsed {
|
impl RoaEContentParsed {
|
||||||
pub fn validate_profile(self) -> Result<RoaEContent, RoaProfileError> {
|
pub fn validate_profile(self) -> Result<RoaEContent, RoaProfileError> {
|
||||||
let (_rem, obj) =
|
fn count_elements(mut r: DerReader<'_>) -> Result<usize, String> {
|
||||||
parse_der(&self.der).map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
let mut n = 0usize;
|
||||||
|
while !r.is_empty() {
|
||||||
let seq = obj
|
r.skip_any()?;
|
||||||
.as_sequence()
|
n += 1;
|
||||||
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
}
|
||||||
if seq.len() != 2 && seq.len() != 3 {
|
Ok(n)
|
||||||
return Err(RoaProfileError::InvalidAttestationSequenceLen(seq.len()));
|
}
|
||||||
|
|
||||||
|
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;
|
let mut version: u32 = 0;
|
||||||
if seq.len() == 3 {
|
if elem_count == 3 {
|
||||||
let v_obj = &seq[0];
|
if seq
|
||||||
if v_obj.class() != Class::ContextSpecific || v_obj.tag() != Tag(0) {
|
.peek_tag()
|
||||||
|
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?
|
||||||
|
!= 0xA0
|
||||||
|
{
|
||||||
return Err(RoaProfileError::ProfileDecode(
|
return Err(RoaProfileError::ProfileDecode(
|
||||||
"RouteOriginAttestation.version must be [0] EXPLICIT INTEGER".into(),
|
"RouteOriginAttestation.version must be [0] EXPLICIT INTEGER".into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let inner_der = v_obj
|
let (inner_tag, inner_val) = seq
|
||||||
.as_slice()
|
.take_explicit(0xA0)
|
||||||
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
||||||
let (rem, inner) =
|
if inner_tag != 0x02 {
|
||||||
parse_der(inner_der).map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
|
||||||
if !rem.is_empty() {
|
|
||||||
return Err(RoaProfileError::ProfileDecode(
|
return Err(RoaProfileError::ProfileDecode(
|
||||||
"trailing bytes inside RouteOriginAttestation.version".into(),
|
"RouteOriginAttestation.version must be [0] EXPLICIT INTEGER".into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let v = inner
|
let v = crate::data_model::common::der_uint_from_bytes(inner_val)
|
||||||
.as_u64()
|
|
||||||
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
||||||
if v != 0 {
|
if v != 0 {
|
||||||
return Err(RoaProfileError::InvalidVersion(v));
|
return Err(RoaProfileError::InvalidVersion(v));
|
||||||
}
|
}
|
||||||
version = 0;
|
version = 0;
|
||||||
idx = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let as_id_u64 = seq[idx]
|
let as_id_u64 = seq
|
||||||
.as_u64()
|
.take_uint_u64()
|
||||||
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
||||||
if as_id_u64 > u32::MAX as u64 {
|
if as_id_u64 > u32::MAX as u64 {
|
||||||
return Err(RoaProfileError::AsIdOutOfRange(as_id_u64));
|
return Err(RoaProfileError::AsIdOutOfRange(as_id_u64));
|
||||||
}
|
}
|
||||||
let as_id = as_id_u64 as u32;
|
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 {
|
let mut out = RoaEContent {
|
||||||
version,
|
version,
|
||||||
@ -396,21 +426,34 @@ fn roa_prefix_to_rc(p: &IpPrefix) -> RcIpPrefix {
|
|||||||
RcIpPrefix {
|
RcIpPrefix {
|
||||||
afi,
|
afi,
|
||||||
prefix_len: p.prefix_len,
|
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> {
|
fn parse_ip_addr_blocks_cursor(
|
||||||
let seq = obj
|
mut seq: DerReader<'_>,
|
||||||
.as_sequence()
|
) -> Result<Vec<RoaIpAddressFamily>, RoaProfileError> {
|
||||||
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
fn count_elements(mut r: DerReader<'_>) -> Result<usize, String> {
|
||||||
if seq.is_empty() || seq.len() > 2 {
|
let mut n = 0usize;
|
||||||
return Err(RoaProfileError::InvalidIpAddrBlocksLen(seq.len()));
|
while !r.is_empty() {
|
||||||
|
r.skip_any()?;
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
Ok(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut out: Vec<RoaIpAddressFamily> = Vec::new();
|
let fam_count =
|
||||||
for fam in seq {
|
count_elements(seq).map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
||||||
let family = parse_ip_address_family(fam)?;
|
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) {
|
if out.iter().any(|f| f.afi == family.afi) {
|
||||||
return Err(RoaProfileError::DuplicateAfi(family.afi));
|
return Err(RoaProfileError::DuplicateAfi(family.afi));
|
||||||
}
|
}
|
||||||
@ -419,77 +462,75 @@ fn parse_ip_addr_blocks(obj: &DerObject<'_>) -> Result<Vec<RoaIpAddressFamily>,
|
|||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_ip_address_family(obj: &DerObject<'_>) -> Result<RoaIpAddressFamily, RoaProfileError> {
|
fn parse_ip_address_family_cursor(
|
||||||
let seq = obj
|
mut fam: DerReader<'_>,
|
||||||
.as_sequence()
|
) -> Result<RoaIpAddressFamily, RoaProfileError> {
|
||||||
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
let afi = {
|
||||||
if seq.len() != 2 {
|
let bytes = fam
|
||||||
return Err(RoaProfileError::InvalidIpAddressFamily);
|
.take_octet_string()
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
.map_err(|_e| RoaProfileError::InvalidAddressFamily)?;
|
.map_err(|_e| RoaProfileError::InvalidAddressFamily)?;
|
||||||
if bytes.len() != 2 {
|
if bytes.len() != 2 {
|
||||||
return Err(RoaProfileError::InvalidAddressFamily);
|
return Err(RoaProfileError::InvalidAddressFamily);
|
||||||
}
|
}
|
||||||
match bytes {
|
match bytes {
|
||||||
[0x00, 0x01] => Ok(RoaAfi::Ipv4),
|
[0x00, 0x01] => RoaAfi::Ipv4,
|
||||||
[0x00, 0x02] => Ok(RoaAfi::Ipv6),
|
[0x00, 0x02] => RoaAfi::Ipv6,
|
||||||
_ => Err(RoaProfileError::UnsupportedAfi(bytes.to_vec())),
|
_ => 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,
|
afi: RoaAfi,
|
||||||
obj: &DerObject<'_>,
|
mut seq: DerReader<'_>,
|
||||||
) -> Result<Vec<RoaIpAddress>, RoaProfileError> {
|
) -> Result<RoaIpAddress, RoaProfileError> {
|
||||||
let seq = obj
|
if seq.is_empty() {
|
||||||
.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 {
|
|
||||||
return Err(RoaProfileError::InvalidRoaIpAddress);
|
return Err(RoaProfileError::InvalidRoaIpAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
let prefix = parse_prefix_bits(afi, &seq[0])?;
|
let (unused_bits, bytes) = seq
|
||||||
let max_length = match seq.get(1) {
|
.take_bit_string()
|
||||||
None => None,
|
.map_err(|_e| RoaProfileError::InvalidPrefixBitString)?;
|
||||||
Some(m) => {
|
let prefix = parse_prefix_bits_bytes(afi, unused_bits, bytes)?;
|
||||||
let v = m
|
|
||||||
.as_u64()
|
let max_length = if !seq.is_empty() {
|
||||||
|
let v = seq
|
||||||
|
.take_uint_u64()
|
||||||
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
|
||||||
let max_len: u16 = v
|
let max_len: u16 = v.try_into().map_err(|_e| RoaProfileError::InvalidMaxLength {
|
||||||
.try_into()
|
|
||||||
.map_err(|_e| RoaProfileError::InvalidMaxLength {
|
|
||||||
afi,
|
afi,
|
||||||
prefix_len: prefix.prefix_len,
|
prefix_len: prefix.prefix_len,
|
||||||
max_len: u16::MAX,
|
max_len: u16::MAX,
|
||||||
})?;
|
})?;
|
||||||
Some(max_len)
|
Some(max_len)
|
||||||
}
|
} else {
|
||||||
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if !seq.is_empty() {
|
||||||
|
return Err(RoaProfileError::InvalidRoaIpAddress);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(max_len) = max_length {
|
if let Some(max_len) = max_length {
|
||||||
let ub = afi.ub();
|
let ub = afi.ub();
|
||||||
if max_len > ub || max_len < prefix.prefix_len {
|
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 })
|
Ok(RoaIpAddress { prefix, max_length })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_prefix_bits(afi: RoaAfi, obj: &DerObject<'_>) -> Result<IpPrefix, RoaProfileError> {
|
fn parse_prefix_bits_bytes(
|
||||||
let (unused_bits, bytes) = match &obj.content {
|
afi: RoaAfi,
|
||||||
BerObjectContent::BitString(unused, bso) => (*unused, bso.data.to_vec()),
|
unused_bits: u8,
|
||||||
_ => return Err(RoaProfileError::InvalidPrefixBitString),
|
bytes: &[u8],
|
||||||
};
|
) -> Result<IpPrefix, RoaProfileError> {
|
||||||
|
|
||||||
if unused_bits > 7 {
|
if unused_bits > 7 {
|
||||||
return Err(RoaProfileError::InvalidPrefixUnusedBits);
|
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 });
|
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 {
|
Ok(IpPrefix {
|
||||||
afi,
|
afi,
|
||||||
prefix_len,
|
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 {
|
let full_len = match afi {
|
||||||
RoaAfi::Ipv4 => 4,
|
RoaAfi::Ipv4 => 4,
|
||||||
RoaAfi::Ipv6 => 16,
|
RoaAfi::Ipv6 => 16,
|
||||||
};
|
};
|
||||||
let mut addr = vec![0u8; full_len];
|
let mut addr = [0u8; 16];
|
||||||
let copy_len = bytes.len().min(full_len);
|
let copy_len = bytes.len().min(full_len);
|
||||||
addr[..copy_len].copy_from_slice(&bytes[..copy_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;
|
let rem = (prefix_len % 8) as u8;
|
||||||
if rem != 0 {
|
if rem != 0 {
|
||||||
let mask: u8 = 0xFF << (8 - rem);
|
let mask: u8 = 0xFF << (8 - rem);
|
||||||
if last_prefix_byte < addr.len() {
|
if last_prefix_byte < full_len {
|
||||||
addr[last_prefix_byte] &= mask;
|
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::{
|
use crate::data_model::oid::{
|
||||||
OID_AD_SIGNED_OBJECT, OID_CMS_ATTR_CONTENT_TYPE, OID_CMS_ATTR_MESSAGE_DIGEST,
|
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_CMS_ATTR_CONTENT_TYPE_RAW, OID_CMS_ATTR_MESSAGE_DIGEST_RAW, OID_CMS_ATTR_SIGNING_TIME,
|
||||||
OID_SIGNED_DATA, OID_SUBJECT_INFO_ACCESS,
|
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 crate::data_model::rc::{ResourceCertificate, SubjectInfoAccess};
|
||||||
use der_parser::ber::Class;
|
|
||||||
use der_parser::der::{DerObject, Tag, parse_der};
|
|
||||||
use ring::digest;
|
use ring::digest;
|
||||||
use x509_parser::prelude::FromDer;
|
use x509_parser::prelude::FromDer;
|
||||||
use x509_parser::public_key::PublicKey;
|
use x509_parser::public_key::PublicKey;
|
||||||
@ -303,24 +305,22 @@ impl RpkiSignedObject {
|
|||||||
/// This performs encoding/structure parsing only. Profile constraints are enforced by
|
/// This performs encoding/structure parsing only. Profile constraints are enforced by
|
||||||
/// `RpkiSignedObjectParsed::validate_profile`.
|
/// `RpkiSignedObjectParsed::validate_profile`.
|
||||||
pub fn parse_der(der: &[u8]) -> Result<RpkiSignedObjectParsed, SignedObjectParseError> {
|
pub fn parse_der(der: &[u8]) -> Result<RpkiSignedObjectParsed, SignedObjectParseError> {
|
||||||
let (rem, obj) =
|
let mut r = DerReader::new(der);
|
||||||
parse_der(der).map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
let mut content_info_seq = r
|
||||||
if !rem.is_empty() {
|
.take_sequence()
|
||||||
return Err(SignedObjectParseError::TrailingBytes(rem.len()));
|
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||||
|
if !r.is_empty() {
|
||||||
|
return Err(SignedObjectParseError::TrailingBytes(r.remaining_len()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let content_info_seq = obj
|
let content_type = take_oid_string(&mut content_info_seq)?;
|
||||||
.as_sequence()
|
let signed_data = parse_signed_data_from_contentinfo_cursor(&mut content_info_seq)?;
|
||||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
if !content_info_seq.is_empty() {
|
||||||
if content_info_seq.len() != 2 {
|
|
||||||
return Err(SignedObjectParseError::Parse(
|
return Err(SignedObjectParseError::Parse(
|
||||||
"ContentInfo must be a SEQUENCE of 2 elements".into(),
|
"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 {
|
Ok(RpkiSignedObjectParsed {
|
||||||
raw_der: der.to_vec(),
|
raw_der: der.to_vec(),
|
||||||
content_info_content_type: content_type,
|
content_info_content_type: content_type,
|
||||||
@ -411,91 +411,94 @@ impl RpkiSignedObjectParsed {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_signed_data_from_contentinfo_parse(
|
fn parse_signed_data_from_contentinfo_cursor(
|
||||||
obj: &DerObject<'_>,
|
seq: &mut DerReader<'_>,
|
||||||
) -> Result<SignedDataParsed, SignedObjectParseError> {
|
) -> Result<SignedDataParsed, SignedObjectParseError> {
|
||||||
// ContentInfo.content is `[0] EXPLICIT`, but `der-parser` will represent unknown tagged
|
let inner_der = seq.take_explicit_der(0xA0).map_err(|_e| {
|
||||||
// objects as `Unknown(Any)`. For EXPLICIT tags, the content octets are the full encoding of
|
SignedObjectParseError::Parse("ContentInfo.content must be [0] EXPLICIT".into())
|
||||||
// the inner object, so we parse it from the object's slice.
|
})?;
|
||||||
if obj.class() != Class::ContextSpecific || obj.tag() != Tag(0) {
|
let mut r = DerReader::new(inner_der);
|
||||||
return Err(SignedObjectParseError::Parse(
|
let signed_data_seq = r
|
||||||
"ContentInfo.content must be [0] EXPLICIT".into(),
|
.take_sequence()
|
||||||
));
|
|
||||||
}
|
|
||||||
let inner_der = obj
|
|
||||||
.as_slice()
|
|
||||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||||
let (rem, inner_obj) =
|
if !r.is_empty() {
|
||||||
parse_der(inner_der).map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
|
||||||
if !rem.is_empty() {
|
|
||||||
return Err(SignedObjectParseError::Parse(
|
return Err(SignedObjectParseError::Parse(
|
||||||
"trailing bytes inside ContentInfo.content".into(),
|
"trailing bytes inside ContentInfo.content".into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
parse_signed_data_parse(&inner_obj)
|
parse_signed_data_cursor(signed_data_seq)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_signed_data_parse(
|
fn parse_signed_data_cursor(mut seq: DerReader<'_>) -> Result<SignedDataParsed, SignedObjectParseError> {
|
||||||
obj: &DerObject<'_>,
|
let version = seq
|
||||||
) -> Result<SignedDataParsed, SignedObjectParseError> {
|
.take_uint_u64()
|
||||||
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()
|
|
||||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||||
|
|
||||||
let digest_set = seq[1]
|
let digest_set_bytes = seq
|
||||||
.as_set()
|
.take_tag(0x31)
|
||||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||||
let mut digest_algorithms: Vec<AlgorithmIdentifierParsed> =
|
let mut digest_set = DerReader::new(digest_set_bytes);
|
||||||
Vec::with_capacity(digest_set.len());
|
let mut digest_algorithms: Vec<AlgorithmIdentifierParsed> = Vec::new();
|
||||||
for item in digest_set {
|
while !digest_set.is_empty() {
|
||||||
let (oid, params_ok) = parse_algorithm_identifier_parse(item)?;
|
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 });
|
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 certificates: Option<Vec<Vec<u8>>> = None;
|
||||||
let mut crls_present = false;
|
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..] {
|
while !seq.is_empty() {
|
||||||
if item.class() == Class::ContextSpecific && item.tag() == Tag(0) {
|
let tag = seq
|
||||||
|
.peek_tag()
|
||||||
|
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||||
|
match tag {
|
||||||
|
0xA0 => {
|
||||||
if certificates.is_some() {
|
if certificates.is_some() {
|
||||||
return Err(SignedObjectParseError::Parse(
|
return Err(SignedObjectParseError::Parse(
|
||||||
"SignedData.certificates appears more than once".into(),
|
"SignedData.certificates appears more than once".into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
certificates = Some(parse_certificate_set_implicit_parse(item)?);
|
let content = seq
|
||||||
} else if item.class() == Class::ContextSpecific && item.tag() == Tag(1) {
|
.take_tag(0xA0)
|
||||||
|
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||||
|
certificates = Some(split_der_objects(content)?);
|
||||||
|
}
|
||||||
|
0xA1 => {
|
||||||
crls_present = true;
|
crls_present = true;
|
||||||
} else if item.class() == Class::Universal && item.tag() == Tag::Set {
|
seq.skip_any()
|
||||||
signer_infos_obj = Some(item);
|
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||||
} else {
|
}
|
||||||
|
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(
|
return Err(SignedObjectParseError::Parse(
|
||||||
"unexpected field in SignedData".into(),
|
"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 {
|
Ok(SignedDataParsed {
|
||||||
version,
|
version,
|
||||||
digest_algorithms,
|
digest_algorithms,
|
||||||
@ -506,46 +509,39 @@ fn parse_signed_data_parse(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_encapsulated_content_info_parse(
|
fn parse_encapsulated_content_info_cursor(
|
||||||
obj: &DerObject<'_>,
|
mut seq: DerReader<'_>,
|
||||||
) -> Result<EncapsulatedContentInfoParsed, SignedObjectParseError> {
|
) -> Result<EncapsulatedContentInfoParsed, SignedObjectParseError> {
|
||||||
let seq = obj
|
if seq.is_empty() {
|
||||||
.as_sequence()
|
|
||||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
|
||||||
if seq.is_empty() || seq.len() > 2 {
|
|
||||||
return Err(SignedObjectParseError::Parse(
|
return Err(SignedObjectParseError::Parse(
|
||||||
"EncapsulatedContentInfo must be SEQUENCE of 1..2".into(),
|
"EncapsulatedContentInfo must be SEQUENCE of 1..2".into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let econtent_type = oid_to_string_parse(&seq[0])?;
|
|
||||||
|
|
||||||
let econtent = match seq.get(1) {
|
let econtent_type = take_oid_string(&mut seq)?;
|
||||||
None => None,
|
|
||||||
Some(econtent_tagged) => {
|
let econtent = if seq.is_empty() {
|
||||||
if econtent_tagged.class() != Class::ContextSpecific || econtent_tagged.tag() != Tag(0)
|
None
|
||||||
{
|
} else {
|
||||||
return Err(SignedObjectParseError::Parse(
|
let inner_der = seq.take_explicit_der(0xA0).map_err(|_e| {
|
||||||
"EncapsulatedContentInfo.eContent must be [0] EXPLICIT".into(),
|
SignedObjectParseError::Parse("EncapsulatedContentInfo.eContent must be [0] EXPLICIT".into())
|
||||||
));
|
})?;
|
||||||
}
|
let mut inner = DerReader::new(inner_der);
|
||||||
let inner_der = econtent_tagged
|
let octets = inner
|
||||||
.as_slice()
|
.take_octet_string()
|
||||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||||
let (rem, inner_obj) =
|
if !inner.is_empty() {
|
||||||
parse_der(inner_der).map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
|
||||||
if !rem.is_empty() {
|
|
||||||
return Err(SignedObjectParseError::Parse(
|
return Err(SignedObjectParseError::Parse(
|
||||||
"trailing bytes inside EncapsulatedContentInfo.eContent".into(),
|
"trailing bytes inside EncapsulatedContentInfo.eContent".into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Some(
|
Some(octets.to_vec())
|
||||||
inner_obj
|
|
||||||
.as_slice()
|
|
||||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?
|
|
||||||
.to_vec(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
if !seq.is_empty() {
|
||||||
|
return Err(SignedObjectParseError::Parse(
|
||||||
|
"EncapsulatedContentInfo must be SEQUENCE of 1..2".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
Ok(EncapsulatedContentInfoParsed {
|
Ok(EncapsulatedContentInfoParsed {
|
||||||
econtent_type,
|
econtent_type,
|
||||||
@ -553,22 +549,28 @@ fn parse_encapsulated_content_info_parse(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_certificate_set_implicit_parse(
|
fn split_der_objects(mut input: &[u8]) -> Result<Vec<Vec<u8>>, SignedObjectParseError> {
|
||||||
obj: &DerObject<'_>,
|
let mut out: Vec<Vec<u8>> = Vec::new();
|
||||||
) -> 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();
|
|
||||||
while !input.is_empty() {
|
while !input.is_empty() {
|
||||||
let (rem, _any_obj) =
|
let (_tag, _value, rem) =
|
||||||
parse_der(input).map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
der_take_tlv(input).map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||||
let consumed = input.len() - rem.len();
|
let consumed = input.len() - rem.len();
|
||||||
certs.push(input[..consumed].to_vec());
|
out.push(input[..consumed].to_vec());
|
||||||
input = rem;
|
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> {
|
fn validate_ee_certificate(der: &[u8]) -> Result<ResourceEeCertificate, SignedObjectValidateError> {
|
||||||
@ -603,11 +605,7 @@ fn validate_ee_certificate(der: &[u8]) -> Result<ResourceEeCertificate, SignedOb
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or(SignedObjectValidateError::EeCertificateMissingSia)?;
|
.ok_or(SignedObjectValidateError::EeCertificateMissingSia)?;
|
||||||
let signed_object_uris: Vec<String> = match sia {
|
let signed_object_uris: Vec<String> = match sia {
|
||||||
SubjectInfoAccess::Ee(ee) => ee
|
SubjectInfoAccess::Ee(ee) => ee.signed_object_uris.clone(),
|
||||||
.signed_object_uris
|
|
||||||
.iter()
|
|
||||||
.map(|u| u.as_str().to_string())
|
|
||||||
.collect(),
|
|
||||||
SubjectInfoAccess::Ca(_ca) => Vec::new(),
|
SubjectInfoAccess::Ca(_ca) => Vec::new(),
|
||||||
};
|
};
|
||||||
if signed_object_uris.is_empty() {
|
if signed_object_uris.is_empty() {
|
||||||
@ -626,69 +624,64 @@ fn validate_ee_certificate(der: &[u8]) -> Result<ResourceEeCertificate, SignedOb
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_signer_info_parse(
|
fn parse_signer_info_cursor(mut seq: DerReader<'_>) -> Result<SignerInfoParsed, SignedObjectParseError> {
|
||||||
obj: &DerObject<'_>,
|
let version = seq
|
||||||
) -> Result<SignerInfoParsed, SignedObjectParseError> {
|
.take_uint_u64()
|
||||||
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()
|
|
||||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||||
|
|
||||||
let sid = &seq[1];
|
let (sid_tag, sid_bytes) = seq
|
||||||
let sid = if sid.class() == Class::ContextSpecific && sid.tag() == Tag(0) {
|
.take_any()
|
||||||
let ski = sid
|
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||||
.as_slice()
|
let sid = if (sid_tag & 0xC0) == 0x80 && (sid_tag & 0x1F) == 0 {
|
||||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?
|
SignerIdentifierParsed::SubjectKeyIdentifier(sid_bytes.to_vec())
|
||||||
.to_vec();
|
|
||||||
SignerIdentifierParsed::SubjectKeyIdentifier(ski)
|
|
||||||
} else {
|
} else {
|
||||||
SignerIdentifierParsed::Other
|
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 {
|
let digest_algorithm = AlgorithmIdentifierParsed {
|
||||||
oid: digest_oid,
|
oid: digest_oid,
|
||||||
params_ok: digest_params_ok,
|
params_ok: digest_params_ok,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut idx = 3;
|
|
||||||
let mut signed_attrs_content: Option<Vec<u8>> = None;
|
let mut signed_attrs_content: Option<Vec<u8>> = None;
|
||||||
let mut signed_attrs_der_for_signature: 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) {
|
if seq
|
||||||
let signed_attrs_obj = &seq[idx];
|
.peek_tag()
|
||||||
let content = signed_attrs_obj
|
|
||||||
.as_slice()
|
|
||||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?
|
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?
|
||||||
.to_vec();
|
== 0xA0
|
||||||
signed_attrs_content = Some(content);
|
{
|
||||||
signed_attrs_der_for_signature =
|
let (tag, full_tlv, value) = seq
|
||||||
Some(make_signed_attrs_der_for_signature_parse(signed_attrs_obj)?);
|
.take_any_full()
|
||||||
idx += 1;
|
.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 {
|
let signature_algorithm = AlgorithmIdentifierParsed {
|
||||||
oid: signature_oid,
|
oid: signature_oid,
|
||||||
params_ok: signature_params_ok,
|
params_ok: signature_params_ok,
|
||||||
};
|
};
|
||||||
idx += 1;
|
|
||||||
|
|
||||||
let signature = seq[idx]
|
let signature = seq
|
||||||
.as_slice()
|
.take_octet_string()
|
||||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?
|
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?
|
||||||
.to_vec();
|
.to_vec();
|
||||||
idx += 1;
|
|
||||||
|
|
||||||
let unsigned_attrs_present = seq.get(idx).is_some();
|
let unsigned_attrs_present = !seq.is_empty();
|
||||||
|
|
||||||
Ok(SignerInfoParsed {
|
Ok(SignerInfoParsed {
|
||||||
version,
|
version,
|
||||||
@ -696,9 +689,9 @@ fn parse_signer_info_parse(
|
|||||||
digest_algorithm,
|
digest_algorithm,
|
||||||
signature_algorithm,
|
signature_algorithm,
|
||||||
signed_attrs_content,
|
signed_attrs_content,
|
||||||
|
signed_attrs_der_for_signature,
|
||||||
unsigned_attrs_present,
|
unsigned_attrs_present,
|
||||||
signature,
|
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 message_digest: Option<Vec<u8>> = None;
|
||||||
let mut signing_time: Option<Asn1TimeUtc> = None;
|
let mut signing_time: Option<Asn1TimeUtc> = None;
|
||||||
|
|
||||||
let mut remaining = input;
|
fn count_elements(mut r: DerReader<'_>) -> Result<usize, String> {
|
||||||
while !remaining.is_empty() {
|
let mut n = 0usize;
|
||||||
let (rem, attr_obj) = parse_der(remaining)
|
while !r.is_empty() {
|
||||||
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
|
r.skip_any()?;
|
||||||
remaining = rem;
|
n += 1;
|
||||||
|
}
|
||||||
|
Ok(n)
|
||||||
|
}
|
||||||
|
|
||||||
let attr_seq = attr_obj
|
let mut remaining = DerReader::new(input);
|
||||||
.as_sequence()
|
while !remaining.is_empty() {
|
||||||
|
let mut attr = remaining
|
||||||
|
.take_sequence()
|
||||||
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
|
.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(
|
return Err(SignedObjectValidateError::SignedAttrsParse(
|
||||||
"Attribute must be SEQUENCE of 2".into(),
|
"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()))?;
|
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
|
||||||
let values_set = attr_seq[1]
|
if values.is_empty() {
|
||||||
.as_set()
|
1
|
||||||
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
|
} else {
|
||||||
if values_set.len() != 1 {
|
1 + count_elements(values)
|
||||||
return Err(
|
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?
|
||||||
SignedObjectValidateError::InvalidSignedAttributeValuesCount {
|
|
||||||
oid,
|
|
||||||
count: values_set.len(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
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() {
|
match oid.as_str() {
|
||||||
OID_CMS_ATTR_CONTENT_TYPE => {
|
OID_CMS_ATTR_CONTENT_TYPE => {
|
||||||
if content_type.is_some() {
|
if content_type.is_some() {
|
||||||
return Err(SignedObjectValidateError::DuplicateSignedAttribute(oid));
|
return Err(SignedObjectValidateError::DuplicateSignedAttribute(oid));
|
||||||
}
|
}
|
||||||
let v = oid_to_string_parse(&values_set[0])
|
if val_tag != 0x06 {
|
||||||
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
|
return Err(SignedObjectValidateError::SignedAttrsParse(
|
||||||
content_type = Some(v);
|
"content-type attr value must be OBJECT IDENTIFIER".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
content_type = Some(oid_value_bytes_to_string(val_bytes));
|
||||||
}
|
}
|
||||||
OID_CMS_ATTR_MESSAGE_DIGEST => {
|
OID_CMS_ATTR_MESSAGE_DIGEST => {
|
||||||
if message_digest.is_some() {
|
if message_digest.is_some() {
|
||||||
return Err(SignedObjectValidateError::DuplicateSignedAttribute(oid));
|
return Err(SignedObjectValidateError::DuplicateSignedAttribute(oid));
|
||||||
}
|
}
|
||||||
let v = values_set[0]
|
if val_tag != 0x04 {
|
||||||
.as_slice()
|
return Err(SignedObjectValidateError::SignedAttrsParse(
|
||||||
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?
|
"message-digest attr value must be OCTET STRING".into(),
|
||||||
.to_vec();
|
));
|
||||||
message_digest = Some(v);
|
}
|
||||||
|
message_digest = Some(val_bytes.to_vec());
|
||||||
}
|
}
|
||||||
OID_CMS_ATTR_SIGNING_TIME => {
|
OID_CMS_ATTR_SIGNING_TIME => {
|
||||||
if signing_time.is_some() {
|
if signing_time.is_some() {
|
||||||
return Err(SignedObjectValidateError::DuplicateSignedAttribute(oid));
|
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));
|
return Err(SignedObjectValidateError::UnsupportedSignedAttribute(oid));
|
||||||
@ -913,35 +938,27 @@ fn parse_signed_attrs_implicit(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_signing_time_value(obj: &DerObject<'_>) -> Result<Asn1TimeUtc, SignedObjectValidateError> {
|
fn parse_signing_time_value_tlv(tag: u8, value: &[u8]) -> Result<Asn1TimeUtc, SignedObjectValidateError> {
|
||||||
match &obj.content {
|
match tag {
|
||||||
der_parser::ber::BerObjectContent::UTCTime(dt) => Ok(Asn1TimeUtc {
|
0x17 => Ok(Asn1TimeUtc {
|
||||||
utc: dt
|
utc: parse_utctime(value)?,
|
||||||
.to_datetime()
|
|
||||||
.map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?,
|
|
||||||
encoding: Asn1TimeEncoding::UtcTime,
|
encoding: Asn1TimeEncoding::UtcTime,
|
||||||
}),
|
}),
|
||||||
der_parser::ber::BerObjectContent::GeneralizedTime(dt) => Ok(Asn1TimeUtc {
|
0x18 => Ok(Asn1TimeUtc {
|
||||||
utc: dt
|
utc: parse_generalized_time(value)?,
|
||||||
.to_datetime()
|
|
||||||
.map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?,
|
|
||||||
encoding: Asn1TimeEncoding::GeneralizedTime,
|
encoding: Asn1TimeEncoding::GeneralizedTime,
|
||||||
}),
|
}),
|
||||||
_ => Err(SignedObjectValidateError::InvalidSigningTimeValue),
|
_ => Err(SignedObjectValidateError::InvalidSigningTimeValue),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_signed_attrs_der_for_signature_parse(
|
fn make_signed_attrs_der_for_signature(full_tlv: &[u8]) -> Result<Vec<u8>, SignedObjectParseError> {
|
||||||
obj: &DerObject<'_>,
|
|
||||||
) -> Result<Vec<u8>, SignedObjectParseError> {
|
|
||||||
// We need the DER encoding of SignedAttributes (SET OF Attribute) as signature input.
|
// 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
|
// 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
|
// 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.
|
// is replaced with the universal SET tag (0x31), leaving length+content unchanged.
|
||||||
//
|
//
|
||||||
let mut cs_der = obj
|
let mut cs_der = full_tlv.to_vec();
|
||||||
.to_vec()
|
|
||||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
|
||||||
if cs_der.is_empty() {
|
if cs_der.is_empty() {
|
||||||
return Err(SignedObjectParseError::Parse(
|
return Err(SignedObjectParseError::Parse(
|
||||||
"signedAttrs encoding is empty".into(),
|
"signedAttrs encoding is empty".into(),
|
||||||
@ -953,32 +970,162 @@ fn make_signed_attrs_der_for_signature_parse(
|
|||||||
Ok(cs_der)
|
Ok(cs_der)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn oid_to_string_parse(obj: &DerObject<'_>) -> Result<String, SignedObjectParseError> {
|
fn take_oid_string(seq: &mut DerReader<'_>) -> Result<String, SignedObjectParseError> {
|
||||||
let oid = obj
|
let oid = seq
|
||||||
.as_oid()
|
.take_tag(0x06)
|
||||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
.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(
|
fn oid_value_bytes_to_string(oid_value: &[u8]) -> String {
|
||||||
obj: &DerObject<'_>,
|
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> {
|
) -> Result<(String, bool), SignedObjectParseError> {
|
||||||
let seq = obj
|
if seq.is_empty() {
|
||||||
.as_sequence()
|
|
||||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
|
||||||
if seq.is_empty() || seq.len() > 2 {
|
|
||||||
return Err(SignedObjectParseError::Parse(
|
return Err(SignedObjectParseError::Parse(
|
||||||
"AlgorithmIdentifier must be SEQUENCE of 1..2".into(),
|
"AlgorithmIdentifier must be SEQUENCE of 1..2".into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let oid = oid_to_string_parse(&seq[0])?;
|
let oid = take_oid_string(&mut seq)?;
|
||||||
let params_ok = match seq.get(1) {
|
let params_ok = if seq.is_empty() {
|
||||||
None => true,
|
true
|
||||||
Some(p) => matches!(p.content, der_parser::ber::BerObjectContent::Null),
|
} 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))
|
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] {
|
fn strip_leading_zeros(bytes: &[u8]) -> &[u8] {
|
||||||
let mut idx = 0;
|
let mut idx = 0;
|
||||||
while idx < bytes.len() && bytes[idx] == 0 {
|
while idx < bytes.len() && bytes[idx] == 0 {
|
||||||
|
|||||||
@ -211,7 +211,7 @@ impl TaCertificateParsed {
|
|||||||
return Err(TaCertificateProfileError::NotCa);
|
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);
|
return Err(TaCertificateProfileError::NotSelfSignedIssuerSubject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -78,23 +78,23 @@ pub fn ca_instance_uris_from_ca_certificate(
|
|||||||
|
|
||||||
for ad in access_descriptions {
|
for ad in access_descriptions {
|
||||||
if ad.access_method_oid == OID_AD_CA_REPOSITORY {
|
if ad.access_method_oid == OID_AD_CA_REPOSITORY {
|
||||||
let u = ad.access_location.to_string();
|
let u = ad.access_location.as_str();
|
||||||
if ad.access_location.scheme() != "rsync" {
|
if !u.starts_with("rsync://") {
|
||||||
return Err(CaInstanceUrisError::CaRepositoryNotRsync(u));
|
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 {
|
} else if ad.access_method_oid == OID_AD_RPKI_MANIFEST {
|
||||||
let u = ad.access_location.to_string();
|
let u = ad.access_location.as_str();
|
||||||
if ad.access_location.scheme() != "rsync" {
|
if !u.starts_with("rsync://") {
|
||||||
return Err(CaInstanceUrisError::RpkiManifestNotRsync(u));
|
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 {
|
} else if ad.access_method_oid == OID_AD_RPKI_NOTIFY {
|
||||||
let u = ad.access_location.to_string();
|
let u = ad.access_location.as_str();
|
||||||
if ad.access_location.scheme() != "https" {
|
if !u.starts_with("https://") {
|
||||||
return Err(CaInstanceUrisError::RpkiNotifyNotHttps(u));
|
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::common::BigUnsigned;
|
||||||
use crate::data_model::crl::{CrlDecodeError, CrlVerifyError, RpkixCrl};
|
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::{
|
use crate::data_model::rc::{
|
||||||
AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpResourceSet, ResourceCertKind,
|
AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpResourceSet, ResourceCertKind,
|
||||||
ResourceCertificate, ResourceCertificateDecodeError,
|
ResourceCertificate, ResourceCertificateDecodeError,
|
||||||
@ -135,10 +135,10 @@ pub fn validate_subordinate_ca_cert(
|
|||||||
return Err(CaPathError::IssuerNotCa);
|
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 {
|
return Err(CaPathError::IssuerSubjectMismatch {
|
||||||
child_issuer_dn: child_ca.tbs.issuer_dn.clone(),
|
child_issuer_dn: child_ca.tbs.issuer_name.to_string(),
|
||||||
issuer_subject_dn: issuer_ca.tbs.subject_dn.clone(),
|
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;
|
let mut ku_critical: Option<bool> = None;
|
||||||
for ext in cert.extensions() {
|
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);
|
ku_critical = Some(ext.critical);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -695,9 +695,8 @@ mod tests {
|
|||||||
use crate::data_model::rc::{
|
use crate::data_model::rc::{
|
||||||
RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
|
RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
|
||||||
};
|
};
|
||||||
|
use crate::data_model::common::X509NameDer;
|
||||||
use der_parser::num_bigint::BigUint;
|
use der_parser::num_bigint::BigUint;
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
fn dummy_cert(
|
fn dummy_cert(
|
||||||
kind: ResourceCertKind,
|
kind: ResourceCertKind,
|
||||||
subject_dn: &str,
|
subject_dn: &str,
|
||||||
@ -707,16 +706,8 @@ mod tests {
|
|||||||
aia: Option<Vec<&str>>,
|
aia: Option<Vec<&str>>,
|
||||||
crldp: Option<Vec<&str>>,
|
crldp: Option<Vec<&str>>,
|
||||||
) -> ResourceCertificate {
|
) -> ResourceCertificate {
|
||||||
let aia = aia.map(|v| {
|
let aia = aia.map(|v| v.into_iter().map(|s| s.to_string()).collect::<Vec<_>>());
|
||||||
v.into_iter()
|
let crldp = crldp.map(|v| v.into_iter().map(|s| s.to_string()).collect::<Vec<_>>());
|
||||||
.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<_>>()
|
|
||||||
});
|
|
||||||
|
|
||||||
ResourceCertificate {
|
ResourceCertificate {
|
||||||
raw_der: Vec::new(),
|
raw_der: Vec::new(),
|
||||||
@ -725,8 +716,8 @@ mod tests {
|
|||||||
version: 2,
|
version: 2,
|
||||||
serial_number: BigUint::from(1u8),
|
serial_number: BigUint::from(1u8),
|
||||||
signature_algorithm: "1.2.840.113549.1.1.11".to_string(),
|
signature_algorithm: "1.2.840.113549.1.1.11".to_string(),
|
||||||
issuer_dn: issuer_dn.to_string(),
|
issuer_name: X509NameDer(issuer_dn.as_bytes().to_vec()),
|
||||||
subject_dn: subject_dn.to_string(),
|
subject_name: X509NameDer(subject_dn.as_bytes().to_vec()),
|
||||||
validity_not_before: time::OffsetDateTime::UNIX_EPOCH,
|
validity_not_before: time::OffsetDateTime::UNIX_EPOCH,
|
||||||
validity_not_after: time::OffsetDateTime::UNIX_EPOCH,
|
validity_not_after: time::OffsetDateTime::UNIX_EPOCH,
|
||||||
subject_public_key_info: Vec::new(),
|
subject_public_key_info: Vec::new(),
|
||||||
|
|||||||
@ -112,10 +112,10 @@ pub fn validate_ee_cert_path(
|
|||||||
return Err(CertPathError::IssuerNotCa);
|
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 {
|
return Err(CertPathError::IssuerSubjectMismatch {
|
||||||
ee_issuer_dn: ee.tbs.issuer_dn.clone(),
|
ee_issuer_dn: ee.tbs.issuer_name.to_string(),
|
||||||
issuer_subject_dn: issuer_ca.tbs.subject_dn.clone(),
|
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;
|
let mut ku_critical: Option<bool> = None;
|
||||||
for ext in cert.extensions() {
|
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);
|
ku_critical = Some(ext.critical);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -302,8 +302,8 @@ mod tests {
|
|||||||
use crate::data_model::rc::{
|
use crate::data_model::rc::{
|
||||||
RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
|
RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
|
||||||
};
|
};
|
||||||
|
use crate::data_model::common::X509NameDer;
|
||||||
use der_parser::num_bigint::BigUint;
|
use der_parser::num_bigint::BigUint;
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
fn dummy_cert(
|
fn dummy_cert(
|
||||||
kind: ResourceCertKind,
|
kind: ResourceCertKind,
|
||||||
@ -314,16 +314,8 @@ mod tests {
|
|||||||
aia: Option<Vec<&str>>,
|
aia: Option<Vec<&str>>,
|
||||||
crldp: Option<Vec<&str>>,
|
crldp: Option<Vec<&str>>,
|
||||||
) -> ResourceCertificate {
|
) -> ResourceCertificate {
|
||||||
let aia = aia.map(|v| {
|
let aia = aia.map(|v| v.into_iter().map(|s| s.to_string()).collect::<Vec<_>>());
|
||||||
v.into_iter()
|
let crldp = crldp.map(|v| v.into_iter().map(|s| s.to_string()).collect::<Vec<_>>());
|
||||||
.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<_>>()
|
|
||||||
});
|
|
||||||
ResourceCertificate {
|
ResourceCertificate {
|
||||||
raw_der: Vec::new(),
|
raw_der: Vec::new(),
|
||||||
kind,
|
kind,
|
||||||
@ -331,8 +323,8 @@ mod tests {
|
|||||||
version: 2,
|
version: 2,
|
||||||
serial_number: BigUint::from(1u8),
|
serial_number: BigUint::from(1u8),
|
||||||
signature_algorithm: "1.2.840.113549.1.1.11".to_string(),
|
signature_algorithm: "1.2.840.113549.1.1.11".to_string(),
|
||||||
issuer_dn: issuer_dn.to_string(),
|
issuer_name: X509NameDer(issuer_dn.as_bytes().to_vec()),
|
||||||
subject_dn: subject_dn.to_string(),
|
subject_name: X509NameDer(subject_dn.as_bytes().to_vec()),
|
||||||
validity_not_before: time::OffsetDateTime::UNIX_EPOCH,
|
validity_not_before: time::OffsetDateTime::UNIX_EPOCH,
|
||||||
validity_not_after: time::OffsetDateTime::UNIX_EPOCH,
|
validity_not_after: time::OffsetDateTime::UNIX_EPOCH,
|
||||||
subject_public_key_info: Vec::new(),
|
subject_public_key_info: Vec::new(),
|
||||||
|
|||||||
@ -462,7 +462,7 @@ fn process_aspa_with_issuer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn choose_crl_for_certificate(
|
fn choose_crl_for_certificate(
|
||||||
crldp_uris: Option<&Vec<url::Url>>,
|
crldp_uris: Option<&Vec<String>>,
|
||||||
crl_files: &[(String, Vec<u8>)],
|
crl_files: &[(String, Vec<u8>)],
|
||||||
) -> Result<(String, Vec<u8>), ObjectValidateError> {
|
) -> Result<(String, Vec<u8>), ObjectValidateError> {
|
||||||
if crl_files.is_empty() {
|
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 time::OffsetDateTime;
|
||||||
|
|
||||||
use rpki::data_model::aspa::{AspaEContent, AspaValidateError};
|
use rpki::data_model::aspa::{AspaEContent, AspaValidateError};
|
||||||
|
use rpki::data_model::common::X509NameDer;
|
||||||
use rpki::data_model::rc::{
|
use rpki::data_model::rc::{
|
||||||
AsIdOrRange, AsIdentifierChoice, AsResourceSet, IpResourceSet, RcExtensions, ResourceCertKind,
|
AsIdOrRange, AsIdentifierChoice, AsResourceSet, IpResourceSet, RcExtensions, ResourceCertKind,
|
||||||
ResourceCertificate, RpkixTbsCertificate, SubjectInfoAccess,
|
ResourceCertificate, RpkixTbsCertificate, SubjectInfoAccess,
|
||||||
@ -17,8 +18,8 @@ fn dummy_ee(
|
|||||||
version: 2,
|
version: 2,
|
||||||
serial_number: BigUint::from(1u8),
|
serial_number: BigUint::from(1u8),
|
||||||
signature_algorithm: "1.2.840.113549.1.1.11".to_string(),
|
signature_algorithm: "1.2.840.113549.1.1.11".to_string(),
|
||||||
issuer_dn: "CN=issuer".to_string(),
|
issuer_name: X509NameDer(b"CN=issuer".to_vec()),
|
||||||
subject_dn: "CN=subject".to_string(),
|
subject_name: X509NameDer(b"CN=subject".to_vec()),
|
||||||
validity_not_before: OffsetDateTime::UNIX_EPOCH,
|
validity_not_before: OffsetDateTime::UNIX_EPOCH,
|
||||||
validity_not_after: OffsetDateTime::UNIX_EPOCH,
|
validity_not_after: OffsetDateTime::UNIX_EPOCH,
|
||||||
subject_public_key_info: vec![],
|
subject_public_key_info: vec![],
|
||||||
|
|||||||
@ -127,8 +127,8 @@ impl From<&RpkixTbsCertificate> for RpkixTbsCertificatePretty {
|
|||||||
version: v.version,
|
version: v.version,
|
||||||
serial_number: hex::encode(v.serial_number.to_bytes_be()),
|
serial_number: hex::encode(v.serial_number.to_bytes_be()),
|
||||||
signature_algorithm: v.signature_algorithm.clone(),
|
signature_algorithm: v.signature_algorithm.clone(),
|
||||||
issuer_dn: v.issuer_dn.clone(),
|
issuer_dn: v.issuer_name.to_string(),
|
||||||
subject_dn: v.subject_dn.clone(),
|
subject_dn: v.subject_name.to_string(),
|
||||||
validity_not_before: v.validity_not_before,
|
validity_not_before: v.validity_not_before,
|
||||||
validity_not_after: v.validity_not_after,
|
validity_not_after: v.validity_not_after,
|
||||||
subject_public_key_info: bytes_fmt(&v.subject_public_key_info),
|
subject_public_key_info: bytes_fmt(&v.subject_public_key_info),
|
||||||
|
|||||||
@ -120,7 +120,7 @@ fn canonicalize_sorts_families_sorts_and_dedups_addresses() {
|
|||||||
IpPrefix {
|
IpPrefix {
|
||||||
afi: RoaAfi::Ipv4,
|
afi: RoaAfi::Ipv4,
|
||||||
prefix_len: 24,
|
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 der_parser::num_bigint::BigUint;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
|
use rpki::data_model::common::X509NameDer;
|
||||||
use rpki::data_model::rc::{
|
use rpki::data_model::rc::{
|
||||||
Afi, AsResourceSet, IpAddressChoice, IpAddressFamily, IpAddressOrRange, IpPrefix,
|
Afi, AsResourceSet, IpAddressChoice, IpAddressFamily, IpAddressOrRange, IpPrefix,
|
||||||
IpResourceSet, RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
|
IpResourceSet, RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
|
||||||
@ -20,8 +21,8 @@ fn dummy_ee(
|
|||||||
version: 2,
|
version: 2,
|
||||||
serial_number: BigUint::from(1u8),
|
serial_number: BigUint::from(1u8),
|
||||||
signature_algorithm: "1.2.840.113549.1.1.11".to_string(),
|
signature_algorithm: "1.2.840.113549.1.1.11".to_string(),
|
||||||
issuer_dn: "CN=issuer".to_string(),
|
issuer_name: X509NameDer(b"CN=issuer".to_vec()),
|
||||||
subject_dn: "CN=subject".to_string(),
|
subject_name: X509NameDer(b"CN=subject".to_vec()),
|
||||||
validity_not_before: OffsetDateTime::UNIX_EPOCH,
|
validity_not_before: OffsetDateTime::UNIX_EPOCH,
|
||||||
validity_not_after: OffsetDateTime::UNIX_EPOCH,
|
validity_not_after: OffsetDateTime::UNIX_EPOCH,
|
||||||
subject_public_key_info: vec![],
|
subject_public_key_info: vec![],
|
||||||
@ -55,7 +56,7 @@ fn test_roa_single_v4_prefix() -> RoaEContent {
|
|||||||
prefix: rpki::data_model::roa::IpPrefix {
|
prefix: rpki::data_model::roa::IpPrefix {
|
||||||
afi: RoaAfi::Ipv4,
|
afi: RoaAfi::Ipv4,
|
||||||
prefix_len: 8,
|
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),
|
max_length: Some(24),
|
||||||
}],
|
}],
|
||||||
@ -159,7 +160,24 @@ fn contains_prefix_handles_non_octet_boundary_prefix_len() {
|
|||||||
prefix: rpki::data_model::roa::IpPrefix {
|
prefix: rpki::data_model::roa::IpPrefix {
|
||||||
afi: RoaAfi::Ipv4,
|
afi: RoaAfi::Ipv4,
|
||||||
prefix_len: 16,
|
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,
|
max_length: None,
|
||||||
}],
|
}],
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
use der_parser::num_bigint::BigUint;
|
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::oid::OID_CP_IPADDR_ASNUMBER;
|
||||||
use rpki::data_model::rc::{
|
use rpki::data_model::rc::{
|
||||||
Afi, AsIdOrRange, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressFamily,
|
Afi, AsIdOrRange, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressFamily,
|
||||||
@ -17,8 +18,8 @@ fn dummy_rc_ca(ext: RcExtensions) -> ResourceCertificate {
|
|||||||
version: 2,
|
version: 2,
|
||||||
serial_number: BigUint::from(1u32),
|
serial_number: BigUint::from(1u32),
|
||||||
signature_algorithm: "1.2.840.113549.1.1.11".into(),
|
signature_algorithm: "1.2.840.113549.1.1.11".into(),
|
||||||
issuer_dn: "CN=TA".into(),
|
issuer_name: X509NameDer(b"CN=TA".to_vec()),
|
||||||
subject_dn: "CN=TA".into(),
|
subject_name: X509NameDer(b"CN=TA".to_vec()),
|
||||||
validity_not_before: t,
|
validity_not_before: t,
|
||||||
validity_not_after: t,
|
validity_not_after: t,
|
||||||
subject_public_key_info: Vec::new(),
|
subject_public_key_info: Vec::new(),
|
||||||
|
|||||||
@ -107,23 +107,16 @@ fn audit_helpers_format_roa_ip_prefix_smoke() {
|
|||||||
let v4 = IpPrefix {
|
let v4 = IpPrefix {
|
||||||
afi: RoaAfi::Ipv4,
|
afi: RoaAfi::Ipv4,
|
||||||
prefix_len: 24,
|
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");
|
assert_eq!(rpki::audit::format_roa_ip_prefix(&v4), "192.0.2.0/24");
|
||||||
|
|
||||||
let v6 = IpPrefix {
|
let v6 = IpPrefix {
|
||||||
afi: RoaAfi::Ipv6,
|
afi: RoaAfi::Ipv6,
|
||||||
prefix_len: 32,
|
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"));
|
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]
|
#[test]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user