mod common; use std::collections::VecDeque; use std::net::{Ipv4Addr, Ipv6Addr}; use std::sync::Arc; use common::test_helper::{ as_route_origin, as_v4_route_origin, indent_block, payloads_to_string, serial_result_detail_to_string, snapshot_hashes_to_string, test_report, v4_origin, v6_origin, }; use rpki::data_model::resources::as_resources::Asn; use rpki::rtr::cache::{ CacheAvailability, Delta, RtrCacheBuilder, SerialResult, SessionIds, Snapshot, validate_payload_updates_for_rtr, validate_payloads_for_rtr, }; use rpki::rtr::payload::{Aspa, Payload, RouterKey, Ski, Timing}; use rpki::rtr::store::RtrStore; fn delta_to_string(delta: &Delta) -> String { format!( "serial: {}\nannounced:\n{}withdrawn:\n{}", delta.serial(), indent_block(&payloads_to_string(delta.announced()), 2), indent_block(&payloads_to_string(delta.withdrawn()), 2), ) } fn deltas_window_to_string(deltas: &VecDeque>) -> String { if deltas.is_empty() { return " \n".to_string(); } let mut out = String::new(); for (idx, delta) in deltas.iter().enumerate() { out.push_str(&format!("delta[{}]:\n", idx)); out.push_str(&indent_block(&delta_to_string(delta), 2)); } out } fn get_deltas_since_input_to_string( cache_session_id: u16, cache_serial: u32, client_serial: u32, ) -> String { format!( "cache.session_id: {}\ncache.serial: {}\nclient_serial: {}\n", cache_session_id, cache_serial, client_serial ) } fn snapshot_hashes_and_sorted_view_to_string(snapshot: &Snapshot) -> String { let payloads = snapshot.payloads_for_rtr(); format!( "hashes:\n{}sorted payloads_for_rtr:\n{}", indent_block(&snapshot_hashes_to_string(snapshot), 2), indent_block(&payloads_to_string(&payloads), 2), ) } /// Snapshot ?hash ? /// payload snapshot_hash / origins_hash ? #[test] fn snapshot_hash_is_stable_for_same_content_with_different_input_order() { let a = v4_origin(192, 0, 2, 0, 24, 24, 64496); let b = v4_origin(198, 51, 100, 0, 24, 24, 64497); let c = v6_origin( Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0), 32, 48, 64498, ); let s1_input = vec![ Payload::RouteOrigin(a.clone()), Payload::RouteOrigin(b.clone()), Payload::RouteOrigin(c.clone()), ]; let s2_input = vec![ Payload::RouteOrigin(c), Payload::RouteOrigin(a), Payload::RouteOrigin(b), ]; let s1 = Snapshot::from_payloads(s1_input.clone()); let s2 = Snapshot::from_payloads(s2_input.clone()); let input = format!( "s1 payloads:\n{}\ns2 payloads:\n{}", indent_block(&payloads_to_string(&s1_input), 2), indent_block(&payloads_to_string(&s2_input), 2), ); let output = format!( "s1:\n{}\ns2:\n{}\n:\n same_content: {}\n same_origins: {}\n snapshot_hash : {}\n origins_hash : {}\n", indent_block(&snapshot_hashes_and_sorted_view_to_string(&s1), 2), indent_block(&snapshot_hashes_and_sorted_view_to_string(&s2), 2), s1.same_content(&s2), s1.same_origins(&s2), s1.snapshot_hash() == s2.snapshot_hash(), s1.origins_hash() == s2.origins_hash(), ); test_report( "snapshot_hash_is_stable_for_same_content_with_different_input_order", "test purpose", &input, &output, ); assert!(s1.same_content(&s2)); assert!(s1.same_origins(&s2)); assert_eq!(s1.snapshot_hash(), s2.snapshot_hash()); assert_eq!(s1.origins_hash(), s2.origins_hash()); } #[tokio::test] async fn init_keeps_cache_running_when_file_loader_returns_no_data() { let dir = tempfile::tempdir().unwrap(); let store = RtrStore::open(dir.path()).unwrap(); let cache = rpki::rtr::cache::RtrCache::default() .init(&store, 16, false, Timing::new(600, 600, 7200), || { Ok(vec![]) }) .unwrap(); assert!(!cache.is_data_available()); assert_eq!(cache.serial(), 0); assert!(cache.snapshot().payloads_for_rtr().is_empty()); } #[tokio::test] async fn init_restores_wraparound_delta_window_from_store() { let dir = tempfile::tempdir().unwrap(); let store = RtrStore::open(dir.path()).unwrap(); let session_ids = SessionIds::from_array([42, 43, 44]); let snapshot = Snapshot::from_payloads(vec![ Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496)), Payload::RouteOrigin(v4_origin(198, 51, 100, 0, 24, 24, 64497)), Payload::RouteOrigin(v4_origin(203, 0, 113, 0, 24, 24, 64498)), ]); let d_max = Delta::new( u32::MAX, vec![Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496))], vec![], ); let d_zero = Delta::new( 0, vec![Payload::RouteOrigin(v4_origin( 198, 51, 100, 0, 24, 24, 64497, ))], vec![], ); let d_one = Delta::new( 1, vec![Payload::RouteOrigin(v4_origin( 203, 0, 113, 0, 24, 24, 64498, ))], vec![], ); store .save_cache_state( CacheAvailability::Ready, &snapshot, &session_ids, u32::MAX, Some(&d_max), Some((u32::MAX, u32::MAX)), false, ) .unwrap(); store .save_cache_state( CacheAvailability::Ready, &snapshot, &session_ids, 0, Some(&d_zero), Some((u32::MAX, 0)), false, ) .unwrap(); store .save_cache_state( CacheAvailability::Ready, &snapshot, &session_ids, 1, Some(&d_one), Some((u32::MAX, 1)), false, ) .unwrap(); let cache = rpki::rtr::cache::RtrCache::default() .init(&store, 16, false, Timing::new(600, 600, 7200), || { Ok(Vec::new()) }) .unwrap(); match cache.get_deltas_since(u32::MAX.wrapping_sub(1)) { SerialResult::Delta(delta) => { assert_eq!(delta.serial(), 1); assert_eq!(delta.announced().len(), 3); } _ => panic!("expected wraparound delta to be restored from store"), } } #[tokio::test] async fn update_prunes_delta_window_when_cumulative_delta_size_reaches_snapshot_size() { let dir = tempfile::tempdir().unwrap(); let store = RtrStore::open(dir.path()).unwrap(); let valid_spki = vec![ 0x30, 0x13, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x02, 0x00, 0x00, ]; let initial_snapshot = Snapshot::from_payloads(vec![Payload::RouterKey(RouterKey::new( Ski::default(), Asn::from(64496u32), valid_spki.clone(), ))]); let mut cache = RtrCacheBuilder::new() .availability(CacheAvailability::Ready) .session_ids(SessionIds::from_array([42, 43, 44])) .serial(1) .snapshot(initial_snapshot) .max_delta(16) .prune_delta_by_snapshot_size(true) .timing(Timing::new(600, 600, 7200)) .build(); cache .update( vec![Payload::RouterKey(RouterKey::new( Ski::from_bytes([1u8; 20]), Asn::from(64496u32), valid_spki, ))], &store, ) .unwrap(); match cache.get_deltas_since(1) { SerialResult::ResetRequired => {} _ => panic!( "expected delta window to be pruned when cumulative delta size exceeds snapshot size" ), } } /// Snapshot::diff() ? /// old_snapshot ?new_snapshot announced?withdrawn? #[test] fn snapshot_diff_reports_announced_and_withdrawn_correctly() { let old_a = v4_origin(192, 0, 2, 0, 24, 24, 64496); let old_b = v4_origin(198, 51, 100, 0, 24, 24, 64497); let new_c = v6_origin( Ipv6Addr::new(0x2001, 0xdb8, 0, 1, 0, 0, 0, 0), 48, 48, 64499, ); let old_input = vec![ Payload::RouteOrigin(old_a.clone()), Payload::RouteOrigin(old_b.clone()), ]; let new_input = vec![ Payload::RouteOrigin(old_b), Payload::RouteOrigin(new_c.clone()), ]; let old_snapshot = Snapshot::from_payloads(old_input.clone()); let new_snapshot = Snapshot::from_payloads(new_input.clone()); let (announced, withdrawn) = old_snapshot.diff(&new_snapshot); let input = format!( "old_snapshot :\n{}\nnew_snapshot :\n{}", indent_block(&payloads_to_string(&old_input), 2), indent_block(&payloads_to_string(&new_input), 2), ); let output = format!( "announced:\n{}withdrawn:\n{}", indent_block(&payloads_to_string(&announced), 2), indent_block(&payloads_to_string(&withdrawn), 2), ); test_report( "snapshot_diff_reports_announced_and_withdrawn_correctly", "test purpose", &input, &output, ); assert_eq!(announced.len(), 1); assert_eq!(withdrawn.len(), 1); match &announced[0] { Payload::RouteOrigin(ro) => assert_eq!(ro, &new_c), _ => panic!("expected announced RouteOrigin"), } match &withdrawn[0] { Payload::RouteOrigin(ro) => assert_eq!(ro, &old_a), _ => panic!("expected withdrawn RouteOrigin"), } } /// Snapshot::payloads_for_rtr() ? /// IPv4 Prefix IPv6 Prefix ?IPv4 announcement ?RTR ? #[test] fn snapshot_payloads_for_rtr_sorts_ipv4_before_ipv6_and_ipv4_announcements_descending() { let v4_low = v4_origin(192, 0, 2, 0, 24, 24, 64496); let v4_high = v4_origin(198, 51, 100, 0, 24, 24, 64497); let v6 = v6_origin( Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0), 32, 48, 64498, ); let input_payloads = vec![ Payload::RouteOrigin(v6.clone()), Payload::RouteOrigin(v4_low.clone()), Payload::RouteOrigin(v4_high.clone()), ]; let snapshot = Snapshot::from_payloads(input_payloads.clone()); let output_payloads = snapshot.payloads_for_rtr(); let input = format!( " payloads?Snapshot :\n{}", indent_block(&payloads_to_string(&input_payloads), 2), ); let output = format!( "?payloads_for_rtr:\n{}", indent_block(&payloads_to_string(&output_payloads), 2), ); test_report( "snapshot_payloads_for_rtr_sorts_ipv4_before_ipv6_and_ipv4_announcements_descending", "test purpose", &input, &output, ); assert_eq!(output_payloads.len(), 3); let first = as_v4_route_origin(&output_payloads[0]); let second = as_v4_route_origin(&output_payloads[1]); assert_eq!( first.prefix().address.to_ipv4(), Some(Ipv4Addr::new(198, 51, 100, 0)) ); assert_eq!( second.prefix().address.to_ipv4(), Some(Ipv4Addr::new(192, 0, 2, 0)) ); let third = as_route_origin(&output_payloads[2]); assert!(third.prefix().address.is_ipv6()); assert_eq!( third.prefix().address.to_ipv6(), Some(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)) ); } /// Delta::new() ?announced ?withdrawn? /// announced announcement ithdrawn withdrawal ? #[test] fn delta_new_sorts_announced_descending_and_withdrawn_ascending() { let announced_low = v4_origin(192, 0, 2, 0, 24, 24, 64496); let announced_high = v4_origin(198, 51, 100, 0, 24, 24, 64497); let withdrawn_high = v4_origin(203, 0, 113, 0, 24, 24, 64501); let withdrawn_low = v4_origin(10, 0, 0, 0, 24, 24, 64500); let input_announced = vec![ Payload::RouteOrigin(announced_low), Payload::RouteOrigin(announced_high), ]; let input_withdrawn = vec![ Payload::RouteOrigin(withdrawn_high), Payload::RouteOrigin(withdrawn_low), ]; let delta = Delta::new(101, input_announced.clone(), input_withdrawn.clone()); let input = format!( "announced?\n{}withdrawn?\n{}", indent_block(&payloads_to_string(&input_announced), 2), indent_block(&payloads_to_string(&input_withdrawn), 2), ); let output = indent_block(&delta_to_string(&delta), 2); test_report( "delta_new_sorts_announced_descending_and_withdrawn_ascending", "test purpose", &input, &output, ); assert_eq!(delta.serial(), 101); assert_eq!(delta.announced().len(), 2); assert_eq!(delta.withdrawn().len(), 2); let a0 = as_v4_route_origin(&delta.announced()[0]); let a1 = as_v4_route_origin(&delta.announced()[1]); assert_eq!( a0.prefix().address.to_ipv4(), Some(Ipv4Addr::new(198, 51, 100, 0)) ); assert_eq!( a1.prefix().address.to_ipv4(), Some(Ipv4Addr::new(192, 0, 2, 0)) ); let w0 = as_v4_route_origin(&delta.withdrawn()[0]); let w1 = as_v4_route_origin(&delta.withdrawn()[1]); assert_eq!( w0.prefix().address.to_ipv4(), Some(Ipv4Addr::new(10, 0, 0, 0)) ); assert_eq!( w1.prefix().address.to_ipv4(), Some(Ipv4Addr::new(203, 0, 113, 0)) ); } /// serial ?serial /// get_deltas_since() ?UpToDate?Delta ?ResetRequired? #[test] fn get_deltas_since_returns_up_to_date_when_client_serial_matches_current() { let cache = RtrCacheBuilder::new() .session_ids(SessionIds::from_array([42, 42, 42])) .serial(100) .timing(Timing::default()) .build(); let result = cache.get_deltas_since(100); let input = get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial(), 100); let output = serial_result_detail_to_string(&result); test_report( "get_deltas_since_returns_up_to_date_when_client_serial_matches_current", "test purpose", &input, &output, ); match result { SerialResult::UpToDate => {} _ => panic!("expected UpToDate"), } } /// serial delta window ?/// get_deltas_since() ?ResetRequired?#[test] fn get_deltas_since_returns_reset_required_when_client_serial_is_too_old() { let d1 = Arc::new(Delta::new( 101, vec![Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496))], vec![], )); let d2 = Arc::new(Delta::new( 102, vec![Payload::RouteOrigin(v4_origin( 198, 51, 100, 0, 24, 24, 64497, ))], vec![], )); let mut deltas = VecDeque::new(); deltas.push_back(d1); deltas.push_back(d2); let cache = RtrCacheBuilder::new() .session_ids(SessionIds::from_array([42, 42, 42])) .serial(102) .timing(Timing::default()) .deltas(deltas.clone()) .build(); let result = cache.get_deltas_since(99); let input = format!( "{}delta_window:\n{}", get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial(), 99), indent_block(&deltas_window_to_string(&deltas), 2), ); let output = serial_result_detail_to_string(&result); test_report( "get_deltas_since_returns_reset_required_when_client_serial_is_too_old", "test purpose", &input, &output, ); match result { SerialResult::ResetRequired => {} _ => panic!("expected ResetRequired"), } } /// serial delta window ? /// get_deltas_since() ?delta ? #[test] fn get_deltas_since_returns_minimal_merged_delta() { let d1 = Arc::new(Delta::new( 101, vec![Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496))], vec![], )); let d2 = Arc::new(Delta::new( 102, vec![Payload::RouteOrigin(v4_origin( 198, 51, 100, 0, 24, 24, 64497, ))], vec![], )); let d3 = Arc::new(Delta::new( 103, vec![Payload::RouteOrigin(v4_origin( 203, 0, 113, 0, 24, 24, 64498, ))], vec![], )); let final_snapshot = Snapshot::from_payloads(vec![ Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496)), Payload::RouteOrigin(v4_origin(198, 51, 100, 0, 24, 24, 64497)), Payload::RouteOrigin(v4_origin(203, 0, 113, 0, 24, 24, 64498)), ]); let mut deltas = VecDeque::new(); deltas.push_back(d1); deltas.push_back(d2); deltas.push_back(d3); let cache = RtrCacheBuilder::new() .session_ids(SessionIds::from_array([42, 42, 42])) .serial(103) .timing(Timing::default()) .snapshot(final_snapshot) .deltas(deltas.clone()) .build(); let result = cache.get_deltas_since(101); let input = format!( "{}delta_window:\n{}", get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial(), 101), indent_block(&deltas_window_to_string(&deltas), 2), ); let output = serial_result_detail_to_string(&result); test_report( "get_deltas_since_returns_minimal_merged_delta", "test purpose", &input, &output, ); match result { SerialResult::Delta(delta) => { assert_eq!(delta.serial(), 103); assert_eq!(delta.announced().len(), 2); assert_eq!(delta.withdrawn().len(), 0); let a0 = as_v4_route_origin(&delta.announced()[0]); let a1 = as_v4_route_origin(&delta.announced()[1]); assert_eq!( a0.prefix().address.to_ipv4(), Some(Ipv4Addr::new(203, 0, 113, 0)) ); assert_eq!( a1.prefix().address.to_ipv4(), Some(Ipv4Addr::new(198, 51, 100, 0)) ); } _ => panic!("expected Delta"), } } /// serial serial /// ResetRequired? #[test] fn get_deltas_since_returns_reset_required_when_client_serial_is_in_future() { let cache = RtrCacheBuilder::new() .session_ids(SessionIds::from_array([42, 42, 42])) .serial(100) .timing(Timing::default()) .build(); let result = cache.get_deltas_since(101); let input = get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial(), 101); let output = serial_result_detail_to_string(&result); test_report( "get_deltas_since_returns_reset_required_when_client_serial_is_in_future", "test purpose", &input, &output, ); match result { SerialResult::ResetRequired => {} _ => panic!("expected ResetRequired"), } } #[test] fn get_deltas_since_supports_incremental_updates_across_serial_wraparound() { let a = v4_origin(192, 0, 2, 0, 24, 24, 64496); let b = v4_origin(198, 51, 100, 0, 24, 24, 64497); let d_max = Arc::new(Delta::new( u32::MAX, vec![Payload::RouteOrigin(a.clone())], vec![], )); let d_zero = Arc::new(Delta::new(0, vec![Payload::RouteOrigin(b.clone())], vec![])); let mut deltas = VecDeque::new(); deltas.push_back(d_max); deltas.push_back(d_zero); let final_snapshot = Snapshot::from_payloads(vec![ Payload::RouteOrigin(a.clone()), Payload::RouteOrigin(b.clone()), ]); let cache = RtrCacheBuilder::new() .session_ids(SessionIds::from_array([42, 42, 42])) .serial(0) .timing(Timing::default()) .snapshot(final_snapshot) .deltas(deltas.clone()) .build(); let result = cache.get_deltas_since(u32::MAX.wrapping_sub(1)); let input = format!( "{}delta_window:\n{}", get_deltas_since_input_to_string( cache.session_id_for_version(1), cache.serial(), u32::MAX.wrapping_sub(1) ), indent_block(&deltas_window_to_string(&deltas), 2), ); let output = serial_result_detail_to_string(&result); test_report( "get_deltas_since_supports_incremental_updates_across_serial_wraparound", "test purpose", &input, &output, ); match result { SerialResult::Delta(delta) => { assert_eq!(delta.serial(), 0); assert_eq!(delta.announced().len(), 2); assert_eq!(delta.withdrawn().len(), 0); match &delta.announced()[0] { Payload::RouteOrigin(ro) => assert_eq!(ro, &b), _ => panic!("expected announced RouteOrigin"), } match &delta.announced()[1] { Payload::RouteOrigin(ro) => assert_eq!(ro, &a), _ => panic!("expected announced RouteOrigin"), } } _ => panic!("expected Delta"), } } #[test] fn get_deltas_since_returns_reset_required_when_client_serial_is_too_old_across_wraparound() { let d_max = Arc::new(Delta::new( u32::MAX, vec![Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496))], vec![], )); let d_zero = Arc::new(Delta::new( 0, vec![Payload::RouteOrigin(v4_origin( 198, 51, 100, 0, 24, 24, 64497, ))], vec![], )); let d_one = Arc::new(Delta::new( 1, vec![Payload::RouteOrigin(v4_origin( 203, 0, 113, 0, 24, 24, 64498, ))], vec![], )); let mut deltas = VecDeque::new(); deltas.push_back(d_max); deltas.push_back(d_zero); deltas.push_back(d_one); let cache = RtrCacheBuilder::new() .session_ids(SessionIds::from_array([42, 42, 42])) .serial(1) .timing(Timing::default()) .snapshot(Snapshot::from_payloads(vec![ Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496)), Payload::RouteOrigin(v4_origin(198, 51, 100, 0, 24, 24, 64497)), Payload::RouteOrigin(v4_origin(203, 0, 113, 0, 24, 24, 64498)), ])) .deltas(deltas.clone()) .build(); let client_serial = u32::MAX.wrapping_sub(2); let result = cache.get_deltas_since(client_serial); let input = format!( "{}delta_window:\n{}", get_deltas_since_input_to_string( cache.session_id_for_version(1), cache.serial(), client_serial ), indent_block(&deltas_window_to_string(&deltas), 2), ); let output = serial_result_detail_to_string(&result); test_report( "get_deltas_since_returns_reset_required_when_client_serial_is_too_old_across_wraparound", "test purpose", &input, &output, ); match result { SerialResult::ResetRequired => {} _ => panic!("expected ResetRequired"), } } #[test] fn get_deltas_since_returns_reset_required_when_client_serial_is_in_future_across_wraparound() { let cache = RtrCacheBuilder::new() .session_ids(SessionIds::from_array([42, 42, 42])) .serial(u32::MAX) .timing(Timing::default()) .build(); let result = cache.get_deltas_since(0); let input = get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial(), 0); let output = serial_result_detail_to_string(&result); test_report( "get_deltas_since_returns_reset_required_when_client_serial_is_in_future_across_wraparound", "test purpose", &input, &output, ); match result { SerialResult::ResetRequired => {} _ => panic!("expected ResetRequired"), } } /// update() ?payload ? /// serial delta? #[tokio::test] async fn update_no_change_keeps_serial_and_produces_no_delta() { let old_a = v4_origin(192, 0, 2, 0, 24, 24, 64496); let old_b = v4_origin(198, 51, 100, 0, 24, 24, 64497); let old_input = vec![ Payload::RouteOrigin(old_a.clone()), Payload::RouteOrigin(old_b.clone()), ]; let snapshot = Snapshot::from_payloads(old_input.clone()); let mut cache = RtrCacheBuilder::new() .session_ids(SessionIds::from_array([42, 42, 42])) .serial(100) .timing(Timing::default()) .snapshot(snapshot.clone()) .build(); let dir = tempfile::tempdir().unwrap(); let store = RtrStore::open(dir.path()).unwrap(); let new_payloads = vec![Payload::RouteOrigin(old_b), Payload::RouteOrigin(old_a)]; cache.update(new_payloads.clone(), &store).unwrap(); let current_snapshot = cache.snapshot(); let result = cache.get_deltas_since(100); let input = format!( "old_snapshot :\n{}new_payloads :\n{}", indent_block(&payloads_to_string(&old_input), 2), indent_block(&payloads_to_string(&new_payloads), 2), ); let output = format!( "cache.serial_after_update: {}\ncurrent_snapshot:\n{}get_deltas_since(100):\n{}", cache.serial(), indent_block( &snapshot_hashes_and_sorted_view_to_string(¤t_snapshot), 2 ), indent_block(&serial_result_detail_to_string(&result), 2), ); test_report( "update_no_change_keeps_serial_and_produces_no_delta", "test purpose", &input, &output, ); assert_eq!(cache.serial(), 100); assert!(cache.snapshot().same_content(&snapshot)); match result { SerialResult::UpToDate => {} _ => panic!("expected UpToDate"), } } /// update() payload ? /// ?serial?announced ? #[tokio::test] async fn update_add_only_increments_serial_and_generates_announced_delta() { let old_a = v4_origin(192, 0, 2, 0, 24, 24, 64496); let new_b = v4_origin(198, 51, 100, 0, 24, 24, 64497); let old_input = vec![Payload::RouteOrigin(old_a.clone())]; let old_snapshot = Snapshot::from_payloads(old_input.clone()); let mut cache = RtrCacheBuilder::new() .session_ids(SessionIds::from_array([42, 42, 42])) .serial(100) .timing(Timing::default()) .snapshot(old_snapshot.clone()) .build(); let dir = tempfile::tempdir().unwrap(); let store = RtrStore::open(dir.path()).unwrap(); let new_payloads = vec![ Payload::RouteOrigin(old_a.clone()), Payload::RouteOrigin(new_b.clone()), ]; cache.update(new_payloads.clone(), &store).unwrap(); let current_snapshot = cache.snapshot(); let result = cache.get_deltas_since(100); let input = format!( "old_snapshot :\n{}new_payloads :\n{}", indent_block(&payloads_to_string(&old_input), 2), indent_block(&payloads_to_string(&new_payloads), 2), ); let output = format!( "cache.serial_after_update: {}\ncurrent_snapshot:\n{}get_deltas_since(100):\n{}", cache.serial(), indent_block( &snapshot_hashes_and_sorted_view_to_string(¤t_snapshot), 2 ), indent_block(&serial_result_detail_to_string(&result), 2), ); test_report( "update_add_only_increments_serial_and_generates_announced_delta", "test purpose", &input, &output, ); assert_eq!(cache.serial(), 101); match result { SerialResult::Delta(delta) => { assert_eq!(delta.serial(), 101); assert_eq!(delta.announced().len(), 1); assert_eq!(delta.withdrawn().len(), 0); match &delta.announced()[0] { Payload::RouteOrigin(ro) => assert_eq!(ro, &new_b), _ => panic!("expected announced RouteOrigin"), } } _ => panic!("expected Delta"), } } /// update() payload ? /// ?serial?withdrawn ? #[tokio::test] async fn update_remove_only_increments_serial_and_generates_withdrawn_delta() { let old_a = v4_origin(192, 0, 2, 0, 24, 24, 64496); let old_b = v4_origin(198, 51, 100, 0, 24, 24, 64497); let old_input = vec![ Payload::RouteOrigin(old_a.clone()), Payload::RouteOrigin(old_b.clone()), ]; let old_snapshot = Snapshot::from_payloads(old_input.clone()); let mut cache = RtrCacheBuilder::new() .session_ids(SessionIds::from_array([42, 42, 42])) .serial(100) .timing(Timing::default()) .snapshot(old_snapshot.clone()) .build(); let dir = tempfile::tempdir().unwrap(); let store = RtrStore::open(dir.path()).unwrap(); let new_payloads = vec![Payload::RouteOrigin(old_b.clone())]; cache.update(new_payloads.clone(), &store).unwrap(); let current_snapshot = cache.snapshot(); let result = cache.get_deltas_since(100); let input = format!( "old_snapshot :\n{}new_payloads :\n{}", indent_block(&payloads_to_string(&old_input), 2), indent_block(&payloads_to_string(&new_payloads), 2), ); let output = format!( "cache.serial_after_update: {}\ncurrent_snapshot:\n{}get_deltas_since(100):\n{}", cache.serial(), indent_block( &snapshot_hashes_and_sorted_view_to_string(¤t_snapshot), 2 ), indent_block(&serial_result_detail_to_string(&result), 2), ); test_report( "update_remove_only_increments_serial_and_generates_withdrawn_delta", "test purpose", &input, &output, ); assert_eq!(cache.serial(), 101); match result { SerialResult::Delta(delta) => { assert_eq!(delta.serial(), 101); assert_eq!(delta.announced().len(), 0); assert_eq!(delta.withdrawn().len(), 1); match &delta.withdrawn()[0] { Payload::RouteOrigin(ro) => assert_eq!(ro, &old_a), _ => panic!("expected withdrawn RouteOrigin"), } } _ => panic!("expected Delta"), } } /// update() payload /// ?serial announced ?withdrawn? #[tokio::test] async fn update_add_and_remove_increments_serial_and_generates_both_sides() { let old_a = v4_origin(192, 0, 2, 0, 24, 24, 64496); let old_b = v4_origin(198, 51, 100, 0, 24, 24, 64497); let new_c = v6_origin( Ipv6Addr::new(0x2001, 0xdb8, 0, 1, 0, 0, 0, 0), 48, 48, 64499, ); let old_input = vec![ Payload::RouteOrigin(old_a.clone()), Payload::RouteOrigin(old_b.clone()), ]; let old_snapshot = Snapshot::from_payloads(old_input.clone()); let mut cache = RtrCacheBuilder::new() .session_ids(SessionIds::from_array([42, 42, 42])) .serial(100) .timing(Timing::default()) .snapshot(old_snapshot.clone()) .build(); let dir = tempfile::tempdir().unwrap(); let store = RtrStore::open(dir.path()).unwrap(); let new_payloads = vec![ Payload::RouteOrigin(old_b.clone()), Payload::RouteOrigin(new_c.clone()), ]; cache.update(new_payloads.clone(), &store).unwrap(); let current_snapshot = cache.snapshot(); let result = cache.get_deltas_since(100); let input = format!( "old_snapshot :\n{}new_payloads :\n{}", indent_block(&payloads_to_string(&old_input), 2), indent_block(&payloads_to_string(&new_payloads), 2), ); let output = format!( "cache.serial_after_update: {}\ncurrent_snapshot:\n{}get_deltas_since(100):\n{}", cache.serial(), indent_block( &snapshot_hashes_and_sorted_view_to_string(¤t_snapshot), 2 ), indent_block(&serial_result_detail_to_string(&result), 2), ); test_report( "update_add_and_remove_increments_serial_and_generates_both_sides", "test purpose", &input, &output, ); assert_eq!(cache.serial(), 101); match result { SerialResult::Delta(delta) => { assert_eq!(delta.serial(), 101); assert_eq!(delta.announced().len(), 1); assert_eq!(delta.withdrawn().len(), 1); match &delta.announced()[0] { Payload::RouteOrigin(ro) => assert_eq!(ro, &new_c), _ => panic!("expected announced RouteOrigin"), } match &delta.withdrawn()[0] { Payload::RouteOrigin(ro) => assert_eq!(ro, &old_a), _ => panic!("expected withdrawn RouteOrigin"), } } _ => panic!("expected Delta"), } } /// ?prefix announce withdraw /// ?UpToDate? #[test] fn get_deltas_since_cancels_announce_then_withdraw_for_same_prefix() { let a = v4_origin(192, 0, 2, 0, 24, 24, 64496); let d1 = Arc::new(Delta::new( 101, vec![Payload::RouteOrigin(a.clone())], vec![], )); let d2 = Arc::new(Delta::new( 102, vec![], vec![Payload::RouteOrigin(a.clone())], )); let mut deltas = VecDeque::new(); deltas.push_back(d1); deltas.push_back(d2); // A ? let final_snapshot = Snapshot::empty(); let cache = RtrCacheBuilder::new() .session_ids(SessionIds::from_array([42, 42, 42])) .serial(102) .timing(Timing::default()) .snapshot(final_snapshot) .deltas(deltas.clone()) .build(); let result = cache.get_deltas_since(100); let input = format!( "{}delta_window:\n{}", get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial(), 100), indent_block(&deltas_window_to_string(&deltas), 2), ); let output = serial_result_detail_to_string(&result); test_report( "get_deltas_since_cancels_announce_then_withdraw_for_same_prefix", "test purpose", &input, &output, ); match result { SerialResult::UpToDate => {} _ => panic!("expected UpToDate"), } } /// ?prefix withdraw announce ? /// ?UpToDate? #[test] fn get_deltas_since_cancels_withdraw_then_announce_for_same_prefix() { let a = v4_origin(192, 0, 2, 0, 24, 24, 64496); let d1 = Arc::new(Delta::new( 101, vec![], vec![Payload::RouteOrigin(a.clone())], )); let d2 = Arc::new(Delta::new( 102, vec![Payload::RouteOrigin(a.clone())], vec![], )); let mut deltas = VecDeque::new(); deltas.push_back(d1); deltas.push_back(d2); // ?A? let final_snapshot = Snapshot::from_payloads(vec![Payload::RouteOrigin(a.clone())]); let cache = RtrCacheBuilder::new() .session_ids(SessionIds::from_array([42, 42, 42])) .serial(102) .timing(Timing::default()) .snapshot(final_snapshot) .deltas(deltas.clone()) .build(); let result = cache.get_deltas_since(100); let input = format!( "{}delta_window:\n{}", get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial(), 100), indent_block(&deltas_window_to_string(&deltas), 2), ); let output = serial_result_detail_to_string(&result); test_report( "get_deltas_since_cancels_withdraw_then_announce_for_same_prefix", "test purpose", &input, &output, ); match result { SerialResult::UpToDate => {} _ => panic!("expected UpToDate"), } } /// ?A ?B /// ?delta?withdraw A + announce B? #[test] fn get_deltas_since_merges_replacement_into_withdraw_and_announce() { let a = v4_origin(192, 0, 2, 0, 24, 24, 64496); let b = v4_origin(192, 0, 2, 0, 24, 25, 64496); let d1 = Arc::new(Delta::new( 101, vec![], vec![Payload::RouteOrigin(a.clone())], )); let d2 = Arc::new(Delta::new( 102, vec![Payload::RouteOrigin(b.clone())], vec![], )); let mut deltas = VecDeque::new(); deltas.push_back(d1); deltas.push_back(d2); let final_snapshot = Snapshot::from_payloads(vec![Payload::RouteOrigin(b.clone())]); let cache = RtrCacheBuilder::new() .session_ids(SessionIds::from_array([42, 42, 42])) .serial(102) .timing(Timing::default()) .snapshot(final_snapshot) .deltas(deltas.clone()) .build(); let result = cache.get_deltas_since(100); let input = format!( "{}delta_window:\n{}", get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial(), 100), indent_block(&deltas_window_to_string(&deltas), 2), ); let output = serial_result_detail_to_string(&result); test_report( "get_deltas_since_merges_replacement_into_withdraw_and_announce", "test purpose", &input, &output, ); match result { SerialResult::Delta(delta) => { assert_eq!(delta.serial(), 102); assert_eq!(delta.announced().len(), 1); assert_eq!(delta.withdrawn().len(), 1); match &delta.withdrawn()[0] { Payload::RouteOrigin(ro) => assert_eq!(ro, &a), _ => panic!("expected withdrawn RouteOrigin"), } match &delta.announced()[0] { Payload::RouteOrigin(ro) => assert_eq!(ro, &b), _ => panic!("expected announced RouteOrigin"), } } _ => panic!("expected Delta"), } } /// delta ? /// ? #[test] fn get_deltas_since_merges_multiple_deltas_to_final_minimal_view() { let a = v4_origin(192, 0, 2, 0, 24, 24, 64496); let b = v4_origin(198, 51, 100, 0, 24, 24, 64497); let c = v4_origin(203, 0, 113, 0, 24, 24, 64498); // 100 -> 101 : +A let d1 = Arc::new(Delta::new( 101, vec![Payload::RouteOrigin(a.clone())], vec![], )); // 101 -> 102 : -A +B let d2 = Arc::new(Delta::new( 102, vec![Payload::RouteOrigin(b.clone())], vec![Payload::RouteOrigin(a.clone())], )); // 102 -> 103 : -B +C let d3 = Arc::new(Delta::new( 103, vec![Payload::RouteOrigin(c.clone())], vec![Payload::RouteOrigin(b.clone())], )); let mut deltas = VecDeque::new(); deltas.push_back(d1); deltas.push_back(d2); deltas.push_back(d3); let final_snapshot = Snapshot::from_payloads(vec![Payload::RouteOrigin(c.clone())]); let cache = RtrCacheBuilder::new() .session_ids(SessionIds::from_array([42, 42, 42])) .serial(103) .timing(Timing::default()) .snapshot(final_snapshot) .deltas(deltas.clone()) .build(); // ?serial=100 A/B ?+C let result = cache.get_deltas_since(100); let input = format!( "{}delta_window:\n{}", get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial(), 100), indent_block(&deltas_window_to_string(&deltas), 2), ); let output = serial_result_detail_to_string(&result); test_report( "get_deltas_since_merges_multiple_deltas_to_final_minimal_view", "test purpose", &input, &output, ); match result { SerialResult::Delta(delta) => { assert_eq!(delta.serial(), 103); assert_eq!(delta.announced().len(), 1); assert_eq!(delta.withdrawn().len(), 0); match &delta.announced()[0] { Payload::RouteOrigin(ro) => assert_eq!(ro, &c), _ => panic!("expected announced RouteOrigin"), } } _ => panic!("expected Delta"), } } #[test] fn snapshot_from_payloads_unions_aspas_by_customer() { let first = Payload::Aspa(Aspa::new(Asn::from(64496u32), vec![Asn::from(64497u32)])); let second = Payload::Aspa(Aspa::new( Asn::from(64496u32), vec![Asn::from(64498u32), Asn::from(64497u32)], )); let snapshot = Snapshot::from_payloads(vec![first, second]); let aspas = snapshot.aspas().iter().collect::>(); assert_eq!(aspas.len(), 1); assert_eq!(aspas[0].customer_asn(), Asn::from(64496u32)); assert_eq!( aspas[0].provider_asns(), &[Asn::from(64497u32), Asn::from(64498u32)] ); } #[test] fn snapshot_diff_replaces_aspa_with_single_announcement() { let old_snapshot = Snapshot::from_payloads(vec![Payload::Aspa(Aspa::new( Asn::from(64496u32), vec![Asn::from(64497u32)], ))]); let new_snapshot = Snapshot::from_payloads(vec![Payload::Aspa(Aspa::new( Asn::from(64496u32), vec![Asn::from(64498u32)], ))]); let (announced, withdrawn) = old_snapshot.diff(&new_snapshot); assert_eq!(announced.len(), 1); assert!(withdrawn.is_empty()); match &announced[0] { Payload::Aspa(aspa) => { assert_eq!(aspa.customer_asn(), Asn::from(64496u32)); assert_eq!(aspa.provider_asns(), &[Asn::from(64498u32)]); } _ => panic!("expected announced ASPA"), } } #[test] fn get_deltas_since_merges_aspa_replacement_into_single_announcement() { let old = Aspa::new(Asn::from(64496u32), vec![Asn::from(64497u32)]); let new = Aspa::new(Asn::from(64496u32), vec![Asn::from(64498u32)]); let d1 = Arc::new(Delta::new(101, vec![], vec![Payload::Aspa(old.clone())])); let d2 = Arc::new(Delta::new(102, vec![Payload::Aspa(new.clone())], vec![])); let mut deltas = VecDeque::new(); deltas.push_back(d1); deltas.push_back(d2); let cache = RtrCacheBuilder::new() .session_ids(SessionIds::from_array([42, 42, 42])) .serial(102) .timing(Timing::default()) .snapshot(Snapshot::from_payloads(vec![Payload::Aspa(new.clone())])) .deltas(deltas) .build(); let result = cache.get_deltas_since(100); match result { SerialResult::Delta(delta) => { assert_eq!(delta.announced().len(), 1); assert!(delta.withdrawn().is_empty()); match &delta.announced()[0] { Payload::Aspa(aspa) => assert_eq!(aspa, &new), _ => panic!("expected announced ASPA"), } } _ => panic!("expected Delta"), } } #[test] fn validate_payloads_for_rtr_rejects_unsorted_snapshot_payloads() { let low = Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496)); let high = Payload::RouteOrigin(v4_origin(198, 51, 100, 0, 24, 24, 64497)); let err = validate_payloads_for_rtr(&[low, high], true).unwrap_err(); assert!(err.to_string().contains("RTR payload ordering violation")); } #[test] fn validate_payload_updates_for_rtr_rejects_unsorted_aspa_updates() { let withdraw = ( false, Payload::Aspa(Aspa::new(Asn::from(64497u32), vec![Asn::from(64500u32)])), ); let announce = ( true, Payload::Aspa(Aspa::new(Asn::from(64496u32), vec![Asn::from(64499u32)])), ); let err = validate_payload_updates_for_rtr(&[withdraw, announce]).unwrap_err(); assert!(err.to_string().contains("withdraw ASPA")); assert!(err.to_string().contains("announce ASPA")); }