use serde::Serialize; use sha2::Digest; use crate::policy::Policy; #[derive(Clone, Debug, PartialEq, Eq, Serialize)] #[serde(rename_all = "snake_case")] pub enum AuditObjectKind { Manifest, Crl, Certificate, RouterCertificate, Roa, Aspa, Other, } #[derive(Clone, Debug, PartialEq, Eq, Serialize)] #[serde(rename_all = "snake_case")] pub enum AuditObjectResult { Ok, Skipped, Error, } #[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct ObjectAuditEntry { pub rsync_uri: String, pub sha256_hex: String, pub kind: AuditObjectKind, pub result: AuditObjectResult, #[serde(skip_serializing_if = "Option::is_none")] pub detail: Option, } #[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct AuditWarning { pub message: String, pub rfc_refs: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub context: Option, } impl From<&crate::report::Warning> for AuditWarning { fn from(w: &crate::report::Warning) -> Self { Self { message: w.message.clone(), rfc_refs: w.rfc_refs.iter().map(|r| r.0.to_string()).collect(), context: w.context.clone(), } } } #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] pub struct PublicationPointAudit { /// Monotonic node ID assigned by the traversal engine. /// /// Present when running via the Stage2 tree engine; may be absent in ad-hoc runs. #[serde(skip_serializing_if = "Option::is_none")] pub node_id: Option, /// Parent node ID in the traversal tree. #[serde(skip_serializing_if = "Option::is_none")] pub parent_node_id: Option, /// Provenance metadata for non-root nodes (how this CA instance was discovered). #[serde(skip_serializing_if = "Option::is_none")] pub discovered_from: Option, pub rsync_base_uri: String, pub manifest_rsync_uri: String, pub publication_point_rsync_uri: String, #[serde(skip_serializing_if = "Option::is_none")] pub rrdp_notification_uri: Option, pub source: String, #[serde(skip_serializing_if = "Option::is_none")] pub repo_sync_source: Option, #[serde(skip_serializing_if = "Option::is_none")] pub repo_sync_phase: Option, #[serde(skip_serializing_if = "Option::is_none")] pub repo_sync_duration_ms: Option, #[serde(skip_serializing_if = "Option::is_none")] pub repo_sync_error: Option, pub repo_terminal_state: String, pub this_update_rfc3339_utc: String, pub next_update_rfc3339_utc: String, pub verified_at_rfc3339_utc: String, pub warnings: Vec, pub objects: Vec, } #[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct DiscoveredFrom { pub parent_manifest_rsync_uri: String, pub child_ca_certificate_rsync_uri: String, pub child_ca_certificate_sha256_hex: String, } #[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct TreeSummary { pub instances_processed: usize, pub instances_failed: usize, pub warnings: Vec, } #[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct AuditRunMeta { pub validation_time_rfc3339_utc: String, } #[derive(Clone, Debug, PartialEq, Eq, Serialize)] #[serde(rename_all = "snake_case")] pub enum AuditDownloadKind { RrdpNotification, RrdpSnapshot, RrdpDelta, Rsync, } #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] pub struct AuditDownloadObjectsStat { pub objects_count: u64, pub objects_bytes_total: u64, } #[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct AuditDownloadEvent { pub kind: AuditDownloadKind, pub uri: String, pub started_at_rfc3339_utc: String, pub finished_at_rfc3339_utc: String, pub duration_ms: u64, pub success: bool, #[serde(skip_serializing_if = "Option::is_none")] pub error: Option, #[serde(skip_serializing_if = "Option::is_none")] pub bytes: Option, #[serde(skip_serializing_if = "Option::is_none")] pub objects: Option, } #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] pub struct AuditDownloadKindStats { pub ok_total: u64, pub fail_total: u64, pub duration_ms_total: u64, #[serde(skip_serializing_if = "Option::is_none")] pub bytes_total: Option, #[serde(skip_serializing_if = "Option::is_none")] pub objects_count_total: Option, #[serde(skip_serializing_if = "Option::is_none")] pub objects_bytes_total: Option, } #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] pub struct AuditDownloadStats { pub events_total: u64, /// Statistics keyed by serialized `AuditDownloadKind` string (e.g. "rrdp_snapshot"). pub by_kind: std::collections::BTreeMap, } #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] pub struct AuditRepoSyncStateStat { pub count: u64, pub duration_ms_total: u64, } #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] pub struct AuditRepoSyncStats { pub publication_points_total: u64, pub by_phase: std::collections::BTreeMap, pub by_terminal_state: std::collections::BTreeMap, } #[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct AuditReportV1 { pub format_version: u32, pub meta: AuditRunMeta, pub policy: Policy, pub tree: TreeSummary, pub publication_points: Vec, pub vrps: Vec, pub aspas: Vec, } #[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct AuditReportV2 { pub format_version: u32, pub meta: AuditRunMeta, pub policy: Policy, pub tree: TreeSummary, pub publication_points: Vec, pub vrps: Vec, pub aspas: Vec, pub downloads: Vec, pub download_stats: AuditDownloadStats, pub repo_sync_stats: AuditRepoSyncStats, } #[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct VrpOutput { pub asn: u32, pub prefix: String, pub max_length: u16, } #[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct AspaOutput { pub customer_as_id: u32, pub provider_as_ids: Vec, } pub fn sha256_hex_from_32(bytes: &[u8; 32]) -> String { hex::encode(bytes) } pub fn sha256_hex(bytes: &[u8]) -> String { let digest = sha2::Sha256::digest(bytes); hex::encode(digest) } pub fn format_roa_ip_prefix(p: &crate::data_model::roa::IpPrefix) -> String { let addr = p.addr_bytes(); match p.afi { crate::data_model::roa::RoaAfi::Ipv4 => { format!( "{}.{}.{}.{}{}", addr[0], addr[1], addr[2], addr[3], format!("/{}", p.prefix_len) ) } crate::data_model::roa::RoaAfi::Ipv6 => { let mut parts = Vec::with_capacity(8); for i in 0..8 { let hi = addr[i * 2] as u16; let lo = addr[i * 2 + 1] as u16; parts.push(format!("{:x}", (hi << 8) | lo)); } format!("{}{}", parts.join(":"), format!("/{}", p.prefix_len)) } } }