rpki/src/sync/repo.rs

111 lines
3.6 KiB
Rust

use crate::fetch::rsync::{RsyncFetchError, RsyncFetcher};
use crate::policy::{Policy, SyncPreference};
use crate::report::{RfcRef, Warning};
use crate::storage::RocksStore;
use crate::sync::rrdp::{
Fetcher as HttpFetcher, RrdpSyncError, sync_from_notification,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RepoSyncSource {
Rrdp,
Rsync,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RepoSyncResult {
pub source: RepoSyncSource,
pub objects_written: usize,
pub warnings: Vec<Warning>,
}
#[derive(Debug, thiserror::Error)]
pub enum RepoSyncError {
#[error("RRDP sync failed: {0}")]
Rrdp(#[from] RrdpSyncError),
#[error("rsync fallback failed: {0}")]
Rsync(#[from] RsyncFetchError),
#[error("storage error: {0}")]
Storage(String),
}
/// Sync a publication point into `raw_objects`.
///
/// v1 behavior:
/// - If `rrdp_notification_uri` is present and `policy.sync_preference` is `rrdp_then_rsync`,
/// try RRDP snapshot sync first (RFC 8182 §3.4.1-§3.4.3).
/// - On RRDP failure, fall back to rsync (RFC 8182 §3.4.5).
/// - If `sync_preference` is `rsync_only` or there is no RRDP URI, use rsync.
pub fn sync_publication_point(
store: &RocksStore,
policy: &Policy,
rrdp_notification_uri: Option<&str>,
rsync_base_uri: &str,
http_fetcher: &dyn HttpFetcher,
rsync_fetcher: &dyn RsyncFetcher,
) -> Result<RepoSyncResult, RepoSyncError> {
match (policy.sync_preference, rrdp_notification_uri) {
(SyncPreference::RrdpThenRsync, Some(notification_uri)) => {
match try_rrdp_sync(store, notification_uri, http_fetcher) {
Ok(written) => Ok(RepoSyncResult {
source: RepoSyncSource::Rrdp,
objects_written: written,
warnings: Vec::new(),
}),
Err(err) => {
let warnings = vec![
Warning::new(format!("RRDP failed; falling back to rsync: {err}"))
.with_rfc_refs(&[RfcRef("RFC 8182 §3.4.5")])
.with_context(notification_uri),
];
let written =
rsync_sync_into_raw_objects(store, rsync_base_uri, rsync_fetcher)?;
Ok(RepoSyncResult {
source: RepoSyncSource::Rsync,
objects_written: written,
warnings,
})
}
}
}
_ => {
let written = rsync_sync_into_raw_objects(store, rsync_base_uri, rsync_fetcher)?;
Ok(RepoSyncResult {
source: RepoSyncSource::Rsync,
objects_written: written,
warnings: Vec::new(),
})
}
}
}
fn try_rrdp_sync(
store: &RocksStore,
notification_uri: &str,
http_fetcher: &dyn HttpFetcher,
) -> Result<usize, RrdpSyncError> {
let notification_xml = http_fetcher
.fetch(notification_uri)
.map_err(RrdpSyncError::Fetch)?;
sync_from_notification(store, notification_uri, &notification_xml, http_fetcher)
}
fn rsync_sync_into_raw_objects(
store: &RocksStore,
rsync_base_uri: &str,
rsync_fetcher: &dyn RsyncFetcher,
) -> Result<usize, RepoSyncError> {
let objects = rsync_fetcher.fetch_objects(rsync_base_uri)?;
let mut written = 0usize;
for (rsync_uri, bytes) in objects {
store
.put_raw(&rsync_uri, &bytes)
.map_err(|e| RepoSyncError::Storage(e.to_string()))?;
written += 1;
}
Ok(written)
}