/*! Unit tests that require access to the testing-only function BridgeLine::random() or private fields */ use super::bridge_table::{BridgeLine, BRIDGE_BYTES}; use super::proto::*; use super::*; use rand::Rng; use std::time::{Duration, Instant}; struct PerfStat { // Report performance metrics for each test req_len: usize, resp_len: usize, req_t: Duration, resp_t: Duration, resp_handle_t: Duration, } struct TestHarness { bdb: BridgeDb, pub ba: BridgeAuth, } impl TestHarness { fn new() -> Self { TestHarness::new_buckets(5, 5) } fn new_buckets(num_buckets: u16, hot_spare: u16) -> Self { // Create a BridegDb let mut bdb = BridgeDb::new(); // Create a BridgeAuth let mut ba = BridgeAuth::new(bdb.pubkey); // Make 3 x num_buckets open invitation bridges, in sets of 3 for _ in 0..num_buckets { let bucket = [ BridgeLine::random(), BridgeLine::random(), BridgeLine::random(), ]; let _ = ba.add_openinv_bridges(bucket, &mut bdb); } // Add hot_spare more hot spare buckets for _ in 0..hot_spare { let bucket = [ BridgeLine::random(), BridgeLine::random(), BridgeLine::random(), ]; let _ = ba.add_spare_bucket(bucket, &mut bdb); } // Create the encrypted bridge table ba.enc_bridge_table(); Self { bdb, ba } } fn advance_days(&mut self, days: u16) { self.ba.advance_days(days); } fn open_invite(&mut self) -> (PerfStat, (cred::Lox, bridge_table::BridgeLine)) { // Issue an open invitation let inv = self.bdb.invite().unwrap(); let req_start = Instant::now(); // Use it to get a Lox credential let (req, state) = open_invite::request(&inv); let encoded: Vec = bincode::serialize(&req).unwrap(); let req_t = req_start.elapsed(); let req_len = encoded.len(); let resp_start = Instant::now(); let decoded = bincode::deserialize(&encoded[..]).unwrap(); let resp = self.ba.handle_open_invite(decoded).unwrap(); let encoded_resp: Vec = bincode::serialize(&resp).unwrap(); let resp_t = resp_start.elapsed(); let resp_len = encoded_resp.len(); let resp_handle_start = Instant::now(); let decode_resp = bincode::deserialize(&encoded_resp[..]).unwrap(); let (cred, bridgeline) = open_invite::handle_response(state, decode_resp, &self.ba.lox_pub).unwrap(); let resp_handle_t = resp_handle_start.elapsed(); ( PerfStat { req_len, resp_len, req_t, resp_t, resp_handle_t, }, (cred, bridgeline), ) } fn trust_promotion(&mut self, cred: &cred::Lox) -> (PerfStat, cred::Migration) { let req_start = Instant::now(); let (promreq, promstate) = trust_promotion::request(cred, &self.ba.lox_pub, self.ba.today()).unwrap(); let encoded: Vec = bincode::serialize(&promreq).unwrap(); let req_t = req_start.elapsed(); let req_len = encoded.len(); let resp_start = Instant::now(); let decoded = bincode::deserialize(&encoded[..]).unwrap(); let promresp = self.ba.handle_trust_promotion(decoded).unwrap(); let encoded_resp: Vec = bincode::serialize(&promresp).unwrap(); let resp_t = resp_start.elapsed(); let resp_len = encoded_resp.len(); let resp_handle_start = Instant::now(); let decode_resp = bincode::deserialize(&encoded_resp[..]).unwrap(); let migcred = trust_promotion::handle_response(promstate, decode_resp).unwrap(); let resp_handle_t = resp_handle_start.elapsed(); ( PerfStat { req_len, resp_len, req_t, resp_t, resp_handle_t, }, migcred, ) } fn level0_migration( &mut self, loxcred: &cred::Lox, migcred: &cred::Migration, ) -> (PerfStat, cred::Lox) { let req_start = Instant::now(); let (migreq, migstate) = migration::request(loxcred, migcred, &self.ba.lox_pub, &self.ba.migration_pub).unwrap(); let encoded: Vec = bincode::serialize(&migreq).unwrap(); let req_t = req_start.elapsed(); let req_len = encoded.len(); let resp_start = Instant::now(); let decoded = bincode::deserialize(&encoded[..]).unwrap(); let migresp = self.ba.handle_migration(decoded).unwrap(); let encoded_resp: Vec = bincode::serialize(&migresp).unwrap(); let resp_t = resp_start.elapsed(); let resp_len = encoded_resp.len(); let resp_handle_start = Instant::now(); let decode_resp: migration::Response = bincode::deserialize(&encoded_resp[..]).unwrap(); let cred = migration::handle_response(migstate, decode_resp, &self.ba.lox_pub).unwrap(); let resp_handle_t = resp_handle_start.elapsed(); ( PerfStat { req_len, resp_len, req_t, resp_t, resp_handle_t, }, cred, ) } fn level_up(&mut self, cred: &cred::Lox) -> (PerfStat, cred::Lox) { // Read the bucket in the credential to get today's Bucket // Reachability credential let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap(); let encbuckets = self.ba.enc_bridge_table(); let bucket = bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap()) .unwrap(); let reachcred = bucket.1.unwrap(); // Use the Bucket Reachability credential to advance to the next // level let req_start = Instant::now(); let (req, state) = level_up::request( cred, &reachcred, &self.ba.lox_pub, &self.ba.reachability_pub, self.ba.today(), ) .unwrap(); let encoded: Vec = bincode::serialize(&req).unwrap(); let req_t = req_start.elapsed(); let req_len = encoded.len(); let resp_start = Instant::now(); let decoded = bincode::deserialize(&encoded[..]).unwrap(); let resp = self.ba.handle_level_up(decoded).unwrap(); let encoded_resp: Vec = bincode::serialize(&resp).unwrap(); let resp_t = resp_start.elapsed(); let resp_len = encoded_resp.len(); let resp_handle_start = Instant::now(); let decode_resp = bincode::deserialize(&encoded_resp[..]).unwrap(); let cred = level_up::handle_response(state, decode_resp, &self.ba.lox_pub).unwrap(); let resp_handle_t = resp_handle_start.elapsed(); ( PerfStat { req_len, resp_len, req_t, resp_t, resp_handle_t, }, cred, ) } fn issue_invite(&mut self, cred: &cred::Lox) -> (PerfStat, (cred::Lox, cred::Invitation)) { // Read the bucket in the credential to get today's Bucket // Reachability credential let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap(); let encbuckets = self.ba.enc_bridge_table(); let bucket = bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap()) .unwrap(); let reachcred = bucket.1.unwrap(); let req_start = Instant::now(); let (req, state) = issue_invite::request( cred, &reachcred, &self.ba.lox_pub, &self.ba.reachability_pub, self.ba.today(), ) .unwrap(); let encoded: Vec = bincode::serialize(&req).unwrap(); let req_t = req_start.elapsed(); let req_len = encoded.len(); let resp_start = Instant::now(); let decoded = bincode::deserialize(&encoded[..]).unwrap(); let resp = self.ba.handle_issue_invite(decoded).unwrap(); let encoded_resp: Vec = bincode::serialize(&resp).unwrap(); let resp_t = resp_start.elapsed(); let resp_len = encoded_resp.len(); let resp_handle_start = Instant::now(); let decode_resp = bincode::deserialize(&encoded_resp[..]).unwrap(); let (cred, invite) = issue_invite::handle_response( state, decode_resp, &self.ba.lox_pub, &self.ba.invitation_pub, ) .unwrap(); let resp_handle_t = resp_handle_start.elapsed(); ( PerfStat { req_len, resp_len, req_t, resp_t, resp_handle_t, }, (cred, invite), ) } fn redeem_invite(&mut self, inv: &cred::Invitation) -> (PerfStat, cred::Lox) { let req_start = Instant::now(); let (req, state) = redeem_invite::request(inv, &self.ba.invitation_pub, self.ba.today()).unwrap(); let encoded: Vec = bincode::serialize(&req).unwrap(); let req_t = req_start.elapsed(); let req_len = encoded.len(); let resp_start = Instant::now(); let decoded = bincode::deserialize(&encoded[..]).unwrap(); let resp = self.ba.handle_redeem_invite(decoded).unwrap(); let encoded_resp: Vec = bincode::serialize(&resp).unwrap(); let resp_t = resp_start.elapsed(); let resp_len = encoded_resp.len(); let resp_handle_start = Instant::now(); let decode_resp = bincode::deserialize(&encoded_resp[..]).unwrap(); let cred = redeem_invite::handle_response(state, decode_resp, &self.ba.lox_pub).unwrap(); let resp_handle_t = resp_handle_start.elapsed(); ( PerfStat { req_len, resp_len, req_t, resp_t, resp_handle_t, }, cred, ) } fn check_blockage(&mut self, cred: &cred::Lox) -> (PerfStat, cred::Migration) { let req_start = Instant::now(); let (req, state) = check_blockage::request(cred, &self.ba.lox_pub).unwrap(); let encoded: Vec = bincode::serialize(&req).unwrap(); let req_t = req_start.elapsed(); let req_len = encoded.len(); let resp_start = Instant::now(); let decoded = bincode::deserialize(&encoded[..]).unwrap(); let resp = self.ba.handle_check_blockage(decoded).unwrap(); let encoded_resp: Vec = bincode::serialize(&resp).unwrap(); let resp_t = resp_start.elapsed(); let resp_len = encoded_resp.len(); let resp_handle_start = Instant::now(); let decode_resp = bincode::deserialize(&encoded_resp[..]).unwrap(); let migcred = check_blockage::handle_response(state, decode_resp).unwrap(); let resp_handle_t = resp_handle_start.elapsed(); ( PerfStat { req_len, resp_len, req_t, resp_t, resp_handle_t, }, migcred, ) } fn blockage_migration( &mut self, cred: &cred::Lox, mig: &cred::Migration, ) -> (PerfStat, cred::Lox) { let req_start = Instant::now(); let (req, state) = blockage_migration::request(cred, mig, &self.ba.lox_pub, &self.ba.migration_pub) .unwrap(); let encoded: Vec = bincode::serialize(&req).unwrap(); let req_t = req_start.elapsed(); let req_len = encoded.len(); let resp_start = Instant::now(); let decoded = bincode::deserialize(&encoded[..]).unwrap(); let resp = self.ba.handle_blockage_migration(decoded).unwrap(); let encoded_resp: Vec = bincode::serialize(&resp).unwrap(); let resp_t = resp_start.elapsed(); let resp_len = encoded_resp.len(); let resp_handle_start = Instant::now(); let decode_resp: blockage_migration::Response = bincode::deserialize(&encoded_resp[..]).unwrap(); let cred = blockage_migration::handle_response(state, decode_resp, &self.ba.lox_pub).unwrap(); let resp_handle_t = resp_handle_start.elapsed(); ( PerfStat { req_len, resp_len, req_t, resp_t, resp_handle_t, }, cred, ) } fn positive_report(&mut self, cred: &cred::Lox) -> PerfStat { let req_start = Instant::now(); let req = positive_report::request(cred, &self.ba.lox_pub).unwrap(); let encoded: Vec = bincode::serialize(&req).unwrap(); let req_t = req_start.elapsed(); let req_len = encoded.len(); let resp_start = Instant::now(); let decoded: positive_report::Request = bincode::deserialize(&encoded[..]).unwrap(); let H = positive_report::compute_H(decoded.date); let Htable: RistrettoBasepointTable = RistrettoBasepointTable::create(&H); self.ba.handle_positive_report(decoded, &Htable).unwrap(); let resp_t = resp_start.elapsed(); let resp_len = 0; let resp_handle_start = Instant::now(); let resp_handle_t = resp_handle_start.elapsed(); PerfStat { req_len, resp_len, req_t, resp_t, resp_handle_t, } } } #[test] fn test_open_invite() { let mut th = TestHarness::new(); // Join an untrusted user let (perf_stat, (cred, bridgeline)) = th.open_invite(); // Check that we can use the credential to read a bucket let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap(); let encbuckets = th.ba.enc_bridge_table(); let bucket = bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap()).unwrap(); print_test_results(perf_stat); println!("cred = {:?}", cred); println!("bucket = {:?}", bucket); println!("bridgeline = {:?}", bridgeline); assert!(bucket.1.is_none()); assert!(th.ba.verify_lox(&cred)); assert!(bridgeline == bucket.0[0]); } #[test] fn test_k_invites() { let mut th = TestHarness::new(); for i in 0..25 { let _ = th.open_invite(); if (i + 1) % OPENINV_K != 0 { assert!( th.bdb.current_k == (i + 1) % OPENINV_K, "the current_k should be (i+1)%OPENINV_K" ); } else { assert!( th.bdb.current_k == OPENINV_K, "the current_k should be OPENINV_K" ); } } } #[test] fn test_trust_promotion() { let mut th = TestHarness::new(); let cred = th.open_invite().1 .0; assert!(th.ba.verify_lox(&cred)); // Time passes th.advance_days(47); let (perf_stat, migcred) = th.trust_promotion(&cred); assert!(th.ba.verify_migration(&migcred)); // Check that we can use the to_bucket in the Migration credenital // to read a bucket let (id, key) = bridge_table::from_scalar(migcred.to_bucket).unwrap(); let encbuckets = th.ba.enc_bridge_table(); let bucket = bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap()).unwrap(); print_test_results(perf_stat); println!("bucket = {:?}", bucket); assert!(th.ba.verify_reachability(&bucket.1.unwrap())); } #[test] fn test_level0_migration() { let mut th = TestHarness::new(); let cred = th.open_invite().1 .0; assert!(th.ba.verify_lox(&cred)); // Time passes th.advance_days(47); let (perf_stat, migcred) = th.trust_promotion(&cred); assert!(th.ba.verify_migration(&migcred)); println!("--Trust Promotion to 1--\n"); print_test_results(perf_stat); let (mperf_stat, newloxcred) = th.level0_migration(&cred, &migcred); println!("--Level 0 migration--\n"); print_test_results(mperf_stat); assert!(th.ba.verify_lox(&newloxcred)); println!("newloxcred = {:?}", newloxcred); // Check that we can use the credenital to read a bucket let (id, key) = bridge_table::from_scalar(newloxcred.bucket).unwrap(); let encbuckets = th.ba.enc_bridge_table(); let bucket = bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap()).unwrap(); println!("bucket = {:?}", bucket); assert!(th.ba.verify_reachability(&bucket.1.unwrap())); } #[test] fn test_level_up() { let mut th = TestHarness::new(); // Join an untrusted user let cred = th.open_invite().1 .0; // Time passes th.advance_days(47); // Go up to level 1 let (perf_stat, migcred) = th.trust_promotion(&cred); println!("--Trust Promotion to 1--\n"); print_test_results(perf_stat); let (mperf_stat, cred1) = th.level0_migration(&cred, &migcred); println!("--New Level 1 Credential--\n"); print_test_results(mperf_stat); assert!(scalar_u32(&cred1.trust_level).unwrap() == 1); // Time passes th.advance_days(20); let (two_perf_stat, cred2) = th.level_up(&cred1); assert!(scalar_u32(&cred2.trust_level).unwrap() == 2); println!("--Upgrade to Level 2--\n"); print_test_results(two_perf_stat); println!("cred2 = {:?}", cred2); assert!(th.ba.verify_lox(&cred2)); // Time passes th.advance_days(30); let (three_perf_stat, cred3) = th.level_up(&cred2); assert!(scalar_u32(&cred3.trust_level).unwrap() == 3); println!("--Upgrade to Level 3--\n"); print_test_results(three_perf_stat); println!("cred3 = {:?}", cred3); assert!(th.ba.verify_lox(&cred3)); // Time passes th.advance_days(60); let (four_perf_stat, cred4) = th.level_up(&cred3); assert!(scalar_u32(&cred3.trust_level).unwrap() == 3); println!("--Upgrade to Level 4--\n"); print_test_results(four_perf_stat); println!("cred4 = {:?}", cred4); assert!(th.ba.verify_lox(&cred4)); } #[test] fn test_issue_invite() { let mut th = TestHarness::new(); // Join an untrusted user let cred = th.open_invite().1 .0; // Time passes th.advance_days(47); // Go up to level 1 let (perf_stat, migcred) = th.trust_promotion(&cred); println!("--Trust Promotion to 1--\n"); print_test_results(perf_stat); let (mperf_stat, cred1) = th.level0_migration(&cred, &migcred); println!("--New Level 1 Credential--\n"); print_test_results(mperf_stat); assert!(scalar_u32(&cred1.trust_level).unwrap() == 1); // Time passes th.advance_days(20); // Go up to level 2 let (two_perf_stat, cred2) = th.level_up(&cred1); println!("--Upgrade to Level 2--\n"); print_test_results(two_perf_stat); assert!(scalar_u32(&cred2.trust_level).unwrap() == 2); println!("cred2 = {:?}", cred2); assert!(th.ba.verify_lox(&cred2)); // Issue an invitation let (invite_perf_stat, (cred2a, invite)) = th.issue_invite(&cred2); println!("--Issue Invitation--\n"); print_test_results(invite_perf_stat); assert!(th.ba.verify_lox(&cred2a)); assert!(th.ba.verify_invitation(&invite)); println!("cred2a = {:?}", cred2a); println!("invite = {:?}", invite); } #[test] fn test_redeem_invite() { let mut th = TestHarness::new(); // Join an untrusted user let cred = th.open_invite().1 .0; // Time passes th.advance_days(47); // Go up to level 1 let (perf_stat, migcred) = th.trust_promotion(&cred); println!("--Trust Promotion to 1--\n"); print_test_results(perf_stat); let (mperf_stat, cred1) = th.level0_migration(&cred, &migcred); println!("--New Level 1 Credential--\n"); print_test_results(mperf_stat); assert!(scalar_u32(&cred1.trust_level).unwrap() == 1); // Time passes th.advance_days(20); // Go up to level 2 let (two_perf_stat, cred2) = th.level_up(&cred1); println!("--Upgrade to Level 2--\n"); print_test_results(two_perf_stat); assert!(scalar_u32(&cred2.trust_level).unwrap() == 2); println!("cred2 = {:?}", cred2); assert!(th.ba.verify_lox(&cred2)); // Issue an invitation to Bob let (invite_perf_stat, (cred2a, bob_invite)) = th.issue_invite(&cred2); println!("--Issue Invitation--\n"); print_test_results(invite_perf_stat); assert!(th.ba.verify_lox(&cred2a)); assert!(th.ba.verify_invitation(&bob_invite)); println!("cred2a = {:?}", cred2a); println!("bob_invite = {:?}", bob_invite); // Time passes th.advance_days(12); // Bob joins the system let (bob_perf_stat, bob_cred) = th.redeem_invite(&bob_invite); println!("--Bob joins the system--\n"); print_test_results(bob_perf_stat); assert!(th.ba.verify_lox(&bob_cred)); println!("bob_cred = {:?}", bob_cred); } #[test] fn test_clean_up_blocked() { let mut th = TestHarness::new_buckets(50, 50); // Users for _ in 0..25 { let cred = th.open_invite().1 .0; th.advance_days(30); let (_, migcred) = th.trust_promotion(&cred); let (_, cred1) = th.level0_migration(&cred, &migcred); th.advance_days(14); let (_, cred2) = th.level_up(&cred1); let (_, (cred2a, invite)) = th.issue_invite(&cred2); let (_, bob_cred) = th.redeem_invite(&invite); th.advance_days(28); let (_, _) = th.level_up(&bob_cred); let (_, _cred3) = th.level_up(&cred2a); } // Block 25% == 25 bridges let blocked = 37; block_bridges(&mut th, blocked); println!("\n**AFTER 25% BLOCKING EVENT**\n"); assert!( blocked == th.ba.bridge_table.blocked_keys.len(), "Blocked keys does not equal the number of blocked buckets" ); assert!(th.ba.bridge_table.recycleable_keys.is_empty(), ""); // Each open invitation bucket creates 4 buckets -> 50*4 = 200 + 50 hotspare buckets = 250 total assert!( th.ba.bridge_table.counter == 250, "Total number of buckets should be 250" ); // Advance beyond the time that blocked buckets expire th.advance_days(512); th.ba.clean_up_blocked(); assert!( th.ba.bridge_table.blocked_keys.is_empty(), "Blocked keys should be empty after cleanup" ); assert!(th.ba.bridge_table.recycleable_keys.len() == blocked, "The number of recycleable keys should be equal to the number of previously blocked buckets"); // Each open invitation bucket creates 4 buckets -> 50*4 = 200 + 50 hotspare buckets = 250 total assert!( th.ba.bridge_table.counter == 250, "Total number of buckets should be 250" ); for _ in 0..10 { let bucket = [ BridgeLine::random(), BridgeLine::random(), BridgeLine::random(), ]; // Add new bridges to trigger bucket cleanup, but also open invitation cleanup so 150 buckets - blocked let _ = th.ba.add_openinv_bridges(bucket, &mut th.bdb); } // 150 is the number of open invitation buckets that will be cleared, + the number of blocked bridges // 40 is the number of keys that will have had to be used after creating 10 new open invitation buckets assert!( th.ba.bridge_table.recycleable_keys.len() == 150 + blocked - 40, "Size of recyclable keys should be the same as 150+blocked-40" ); println!("\n**AFTER NEW BUCKETS ADDED**\n"); assert!( th.ba.bridge_table.blocked_keys.is_empty(), "After adding new buckets, blocked keys should be empty" ); assert!( th.ba.bridge_table.recycleable_keys.len() == 150 + blocked - 40, "After adding new buckets, recycleable keys should be empty" ); // Because of open-entry buckets added and open-entry cleanup, the counter will not have changed assert!( th.ba.bridge_table.counter == 250, "Counter should not have changed due to many recyclable keys", ); } #[test] fn test_clean_up_open_entry() { let mut th = TestHarness::new_buckets(50, 50); let mut credentials: Vec = Vec::new(); let mut level_1_credentials: Vec = Vec::new(); // Users for _ in 0..25 { let cred = th.open_invite().1 .0; credentials.push(cred); } assert!( th.ba.trustup_migration_table.table.len() == 150, "There should be 50*3 eligible trust up migrations" ); th.advance_days(30); for cred in credentials { let (_, migcred) = th.trust_promotion(&cred); let (_, cred1) = th.level0_migration(&cred, &migcred); level_1_credentials.push(cred1); } th.advance_days(512); assert!( th.ba.bridge_table.open_inv_keys.len() == 150, "Open Inv keys does not equal 50*3 = 150" ); assert!(th.ba.bridge_table.recycleable_keys.is_empty(), ""); th.ba.clean_up_open_entry(&mut th.bdb); assert!( th.ba.bridge_table.open_inv_keys.is_empty(), "Open entry keys should be 0" ); assert!( th.ba.trustup_migration_table.table.is_empty(), "There should be no remaining eligible trust up migrations" ); assert!( th.ba.bridge_table.recycleable_keys.len() == 150, "Recycleable keys is 150" ); // Each open invitation bucket creates 4 buckets -> 50*4 = 200 + 50 hotspare buckets = 250 total assert!( th.ba.bridge_table.counter == 250, "Total number of buckets should be 250" ); for cred in level_1_credentials { let (_, _cred2) = th.level_up(&cred); } println!("Succeeded upgrading all creds to level 2"); // Open Invite will fail since there are no open invitation buckets // Lets add more for _ in 0..10 { let bucket = [ BridgeLine::random(), BridgeLine::random(), BridgeLine::random(), ]; // Add new bridges to trigger bucket cleanup let _ = th.ba.add_openinv_bridges(bucket, &mut th.bdb); } assert!( th.ba.trustup_migration_table.table.len() == 30, "There should be 10*3 eligible trust up migrations" ); // Let's also make sure that open invitation works again let _cred = th.open_invite().1 .0; th.advance_days(30); assert!( th.ba.bridge_table.recycleable_keys.len() == 110, "Size of recycleable keys should be 150 - (10*4) = 110" ); assert!( th.ba.bridge_table.counter == 250, "Counter should be unchanged" ); assert!( th.ba.bridge_table.open_inv_keys.len() == 30, "Open inv keys should be 10*3 = 30" ); } #[test] fn test_find_next_available_key() { let mut th = TestHarness::new_buckets(50, 50); let mut credentials: Vec = Vec::new(); // Users for _ in 0..25 { let cred = th.open_invite().1 .0; credentials.push(cred); } assert!( th.ba.bridge_table.counter == 250, "There should be 50*3 openinv buckets + 50 superset buckets +50 spare buckets" ); assert!( th.ba.bridge_table.recycleable_keys.is_empty(), "There should be no recyclable keys" ); assert!( th.ba.bridge_table.blocked_keys.is_empty(), "There should be no blocked keys" ); assert!( th.ba.bridge_table.open_inv_keys.len() == 150, "There should be 150 open inv keys" ); block_bridges(&mut th, 45); assert!( th.ba.bridge_table.counter == 250, "There should be 50*3 openinv buckets + 50 superset buckets +50 spare buckets" ); assert!( th.ba.bridge_table.blocked_keys.len() == 45, "Blocked keys should be 45" ); assert!( th.ba.bridge_table.recycleable_keys.is_empty(), "There should be no recyclable keys" ); let bucket = [ BridgeLine::random(), BridgeLine::random(), BridgeLine::random(), ]; // Add new bucket to trigger bucket cleanup and find_next_available_key let _ = th.ba.add_spare_bucket(bucket, &mut th.bdb); // No recyclable keys yet so counter should increase assert!( th.ba.bridge_table.counter == 251, "There should be 50*3 openinv buckets + 50 superset buckets +50 spare buckets" ); assert!( th.ba.bridge_table.recycleable_keys.is_empty(), "There should be no recyclable keys" ); assert!( th.ba.bridge_table.blocked_keys.len() == 45, "There should still be 45 blocked keys" ); assert!( th.ba.bridge_table.open_inv_keys.len() == 150, "There should still be 150 open inv keys" ); // Advance to all open inv and blocked bridges being expired th.advance_days(512); let bucket = [ BridgeLine::random(), BridgeLine::random(), BridgeLine::random(), ]; // Add new bridges to trigger bucket cleanup let _ = th.ba.add_spare_bucket(bucket, &mut th.bdb); // Now all keys should be cleaned up so the counter won't move assert!( th.ba.bridge_table.counter == 251, "There should be 50*3 openinv buckets + 50 superset buckets +50 spare buckets" ); // This should be equal to the previous keys - 1 for the new spare bucket assert!( th.ba.bridge_table.recycleable_keys.len() == 45 + 150 - 1, "There should be no recyclable keys" ); assert!( th.ba.bridge_table.blocked_keys.is_empty(), "There should be no blocked keys" ); assert!( th.ba.bridge_table.open_inv_keys.is_empty(), "There should be 150 open inv keys" ); } /// Blocks a percentage of the bridges for the passed Test Harness /// excluding the hot spare buckets as they will not have been handed out. /// The logic assumes hot spare buckets are appended to the end of the bridge_table /// bucket list. fn block_bridges(th: &mut TestHarness, to_block: usize) { let blockable_range = th.ba.bridge_table.buckets.len() - th.ba.bridge_table.spares.len(); let mut block_index: HashSet = HashSet::new(); let mut rng = rand::thread_rng(); while block_index.len() < to_block { let rand_num = rng.gen_range(1..blockable_range); if !th.bdb.openinv_buckets.contains(&(rand_num as u32)) && !th.bdb.distributed_buckets.contains(&(rand_num as u32)) && !block_index.contains(&rand_num) { block_index.insert(rand_num); } } for index in block_index { let ba_clone = th.ba.bridge_table.buckets.clone(); if let Some(bridgelines) = ba_clone.get(&u32::try_from(index).unwrap()) { for bridgeline in bridgelines { th.ba.bridge_blocked(bridgeline, &mut th.bdb); } } } } #[test] fn test_allocate_bridges() { let mut th = TestHarness::new(); let distributor_bridges: &mut Vec = &mut Vec::new(); let table_size = th.ba.bridge_table.buckets.len(); for _ in 0..3 { distributor_bridges.push(BridgeLine::random()); } assert!( !distributor_bridges.is_empty(), "No BridgeLines in distributor_bridges" ); th.ba.allocate_bridges(distributor_bridges, &mut th.bdb); assert!( distributor_bridges.is_empty(), "BridgeLines in distributor_bridges were not allocated" ); assert!( th.ba.bridge_table.buckets.len() > table_size, "Size of bridge table did not increase" ); let table_size = th.ba.bridge_table.buckets.len(); for _ in 0..2 { distributor_bridges.push(BridgeLine::random()); th.ba .bridge_table .unallocated_bridges .push(BridgeLine::random()); } assert!( !th.ba.bridge_table.unallocated_bridges.is_empty(), "No BridgeLines in unallocated bridges" ); assert!( !distributor_bridges.is_empty(), "No BridgeLines in distributor_bridges" ); th.ba.allocate_bridges(distributor_bridges, &mut th.bdb); assert!( th.ba.bridge_table.unallocated_bridges.len() == 1, "Incorrect number of bridges remain unallocated" ); assert!( distributor_bridges.is_empty(), "BridgeLines in distributor_bridges were not allocated" ); assert!( th.ba.bridge_table.buckets.len() > table_size, "Size of bridge table did not increase" ); } #[test] fn test_update_bridge() { let mut th = TestHarness::new(); // Add new bridge to table with known values, // check that I can find and update the values and that everything else stays the same // Create 3 bridges to test harness let bucket = [ BridgeLine::random(), BridgeLine::random(), BridgeLine::random(), ]; // Store first bridgeline to update later let bridgeline_to_update = bucket[0]; // Create changed info for bridgeline to be updated to let infostr: String = format!( "type={} blocked_in={:?} protocol={} distribution={}", "obfs2 test bridge", {}, "obfs2", "moat", ); let mut updated_info_bytes: [u8; BRIDGE_BYTES - 26] = [0; BRIDGE_BYTES - 26]; updated_info_bytes[..infostr.len()].copy_from_slice(infostr.as_bytes()); let updated_bridgeline = BridgeLine { addr: bridgeline_to_update.addr, port: bridgeline_to_update.port, uid_fingerprint: bridgeline_to_update.uid_fingerprint, info: updated_info_bytes, }; assert!( updated_bridgeline.uid_fingerprint == bridgeline_to_update.uid_fingerprint, "Bridge entering the bridgepool {:?} did not have the same fingerprint as the updating bridge {:?}", bridgeline_to_update, updated_bridgeline.uid_fingerprint ); assert!(updated_bridgeline.info != bridgeline_to_update.info); println!( "Bridge entering the bridgepool {:?} has different info than the updating bridge {:?}", bridgeline_to_update.info, updated_bridgeline.info ); assert!(updated_bridgeline != bridgeline_to_update); println!("The two bridgelines are not equal before the update"); // Add 3 bridges to test harness let _ = th.ba.add_openinv_bridges(bucket, &mut th.bdb); println!("Before update spares = {:?}", th.ba.bridge_table.spares); println!( "Before update tmig = {:?}", th.ba.trustup_migration_table.table ); println!( "Before update bmig = {:?}", th.ba.blockage_migration_table.table ); println!("Before update openinv = {:?}\n", th.bdb.openinv_buckets); // Update the info of a bridge with matching IP and Port to a bridge in the bridge table let result = th.ba.bridge_update(&updated_bridgeline); assert!(result, "Bridge failed to update successfully!!"); let found_bridge = th .ba .bridge_table .reachable .get_key_value(&updated_bridgeline); assert!(*found_bridge.unwrap().0 != bridgeline_to_update); assert!(*found_bridge.unwrap().0 == updated_bridgeline); println!("After update spares = {:?}", th.ba.bridge_table.spares); println!( "After update tmig = {:?}", th.ba.trustup_migration_table.table ); println!( "After update bmig = {:?}", th.ba.blockage_migration_table.table ); println!("After update openinv = {:?}\n", th.bdb.openinv_buckets); } #[test] fn test_bridge_replace() { // Create 3 open invitation buckets and 3 spare buckets let cases = vec!["not found", "available", "unallocated", "spare", "failed"]; let num_buckets = 5; let hot_spare = 0; for case in cases { let mut th: TestHarness; if String::from(case) != "failed" { th = TestHarness::new(); } else { th = TestHarness::new_buckets(num_buckets, hot_spare); } // Randomly select a bridge to replace let table_size = th.ba.bridge_table.buckets.len(); let mut num = 100000; while !th.ba.bridge_table.buckets.contains_key(&num) { num = rand::thread_rng().gen_range(0..th.ba.bridge_table.counter); } println!("chosen num is: {:?}", num); let replaceable_bucket = *th.ba.bridge_table.buckets.get(&num).unwrap(); let replacement_bridge = &replaceable_bucket[0]; assert!( th.ba .bridge_table .reachable .contains_key(replacement_bridge), "Random bridge to replace not in reachable bridges" ); match case { "not found" => { // Case zero: bridge to be replaced is not in the bridgetable let random_bridgeline = &BridgeLine::random(); assert!( !th.ba.bridge_table.reachable.contains_key(random_bridgeline), "Random bridgeline happens to be in the bridge_table (and should not be)" ); assert!( th.ba .bridge_replace(random_bridgeline, Some(random_bridgeline)) == ReplaceSuccess::NotFound, "Bridge should be marked as NotFound" ); } "available" => { // Case one: available_bridge != null let random_bridgeline = &BridgeLine::random(); let unallocated_bridgeline = &BridgeLine::random(); th.ba .bridge_table .unallocated_bridges .push(*unallocated_bridgeline); assert!( th.ba .bridge_table .reachable .get(random_bridgeline) .is_none(), "Random bridge already in table" ); assert!( th.ba .bridge_replace(replacement_bridge, Some(random_bridgeline)) == ReplaceSuccess::Replaced, "Bridge was not replaced with available bridge" ); assert!( th.ba .bridge_table .reachable .get(random_bridgeline) .is_some(), "Replacement bridge not added to reachable bridges" ); assert!( table_size == th.ba.bridge_table.buckets.len(), "Number of buckets changed size" ); assert!( th.ba.bridge_table.unallocated_bridges.len() == 1, "Extra bridge added to unallocated bridges" ); println!("Successfully added passed bridgeline"); } // Case two: available_bridge == null and unallocated_bridges !=null "unallocated" => { let unallocated_bridgeline = &BridgeLine::random(); th.ba .bridge_table .unallocated_bridges .push(*unallocated_bridgeline); assert!( th.ba.bridge_table.unallocated_bridges.len() == 1, "Not enough bridges in unallocated bridges" ); assert!( th.ba .bridge_table .reachable .get(unallocated_bridgeline) .is_none(), "Unallocated bridge already marked as reachable" ); assert!( th.ba.bridge_replace(replacement_bridge, None) == ReplaceSuccess::Replaced, "Bridge was not replaced with available bridge" ); assert!( th.ba .bridge_table .reachable .get(unallocated_bridgeline) .is_some(), "Replacement bridge not added to reachable bridges" ); assert!( table_size == th.ba.bridge_table.buckets.len(), "Number of buckets changed size" ); assert!( th.ba.bridge_table.unallocated_bridges.is_empty(), "Allocated bridge still in unallocated bridges" ); println!("Successfully added unallocated bridgeline"); } "spare" => { // Case three: available_bridge == null and unallocated_bridges == null assert!( th.ba.bridge_table.unallocated_bridges.is_empty(), "Unallocated bridges should have a length of 0" ); assert!( th.ba.bridge_replace(replacement_bridge, None) == ReplaceSuccess::Replaced, "Bridge was not replaced with available spare bridge" ); assert!( th.ba .bridge_table .reachable .get(replacement_bridge) .is_none(), "Replacement bridge still marked as reachable" ); // Remove a spare bucket to replace bridge, buckets decrease by 1 assert!( (table_size - 1) == th.ba.bridge_table.buckets.len(), "Number of buckets changed size" ); assert!( th.ba.bridge_table.unallocated_bridges.len() == 2, "Extra spare bridges not added to unallocated bridges" ); println!("Successfully added bridgeline from spare"); } "failed" => { // Case four: available_bridge == None and unallocated_bridges == None and spare buckets == None assert!( th.ba.bridge_table.unallocated_bridges.is_empty(), "Unallocated bridges should have a length of 0" ); assert!( th.ba.bridge_replace(replacement_bridge, None) == ReplaceSuccess::NotReplaced, "Bridge was somehow marked as replaced despite no replaceable bridges" ); assert!( th.ba .bridge_table .reachable .get(replacement_bridge) .is_some(), "Replacement bridge marked as unreachable despite not being replaced" ); assert!( table_size == th.ba.bridge_table.buckets.len(), "Number of buckets changed size" ); assert!( th.ba.bridge_table.unallocated_bridges.is_empty(), "Unallocated bridges changed size" ); println!("No bridges available to replace bridge so replacement gracefully failed"); } _ => {} } } } #[test] fn test_mark_unreachable() { let mut th = TestHarness::new(); println!("spares = {:?}", th.ba.bridge_table.spares); println!("tmig = {:?}", th.ba.trustup_migration_table.table); println!("bmig = {:?}", th.ba.blockage_migration_table.table); println!("openinv = {:?}\n", th.bdb.openinv_buckets); // Mark a bridge in an untrusted bucket as unreachable let bucket6 = th.ba.bridge_table.buckets.get(&6u32).unwrap(); let b6 = bucket6[0]; th.ba.bridge_blocked(&b6, &mut th.bdb); println!("spares = {:?}", th.ba.bridge_table.spares); println!("tmig = {:?}", th.ba.trustup_migration_table.table); println!("bmig = {:?}", th.ba.blockage_migration_table.table); println!("openinv = {:?}\n", th.bdb.openinv_buckets); // Mark another bridge grouped to the same trusted bucket as // unreachable let bucket7 = th.ba.bridge_table.buckets.get(&7u32).unwrap(); let b7 = bucket7[0]; th.ba.bridge_blocked(&b7, &mut th.bdb); println!("spares = {:?}", th.ba.bridge_table.spares); println!("tmig = {:?}", th.ba.trustup_migration_table.table); println!("bmig = {:?}", th.ba.blockage_migration_table.table); println!("openinv = {:?}\n", th.bdb.openinv_buckets); // That will have introduced a blockage migration. Get the target let target: u32 = *th .ba .blockage_migration_table .table .iter() .next() .unwrap() .1; // Block two of the bridges in that target bucket let bucket1 = th.ba.bridge_table.buckets.get(&target).unwrap(); let bt1 = bucket1[1]; let bucket2 = th.ba.bridge_table.buckets.get(&target).unwrap(); let bt2 = bucket2[2]; th.ba.bridge_blocked(&bt1, &mut th.bdb); th.ba.bridge_blocked(&bt2, &mut th.bdb); println!("spares = {:?}", th.ba.bridge_table.spares); println!("tmig = {:?}", th.ba.trustup_migration_table.table); println!("bmig = {:?}", th.ba.blockage_migration_table.table); println!("openinv = {:?}\n", th.bdb.openinv_buckets); } #[test] fn test_blockage_migration() { let mut th = TestHarness::new(); // Join an untrusted user let cred = th.open_invite().1 .0; // Time passes th.advance_days(47); // Go up to level 1 let (_mperf_stat, migcred) = th.trust_promotion(&cred); let (_perf_stat, cred1) = th.level0_migration(&cred, &migcred); assert!(scalar_u32(&cred1.trust_level).unwrap() == 1); // Time passes th.advance_days(20); // Go up to level 2 let (_two_perf_stat, cred2) = th.level_up(&cred1); assert!(scalar_u32(&cred2.trust_level).unwrap() == 2); println!("cred2 = {:?}", cred2); assert!(th.ba.verify_lox(&cred2)); // Time passes th.advance_days(29); // Go up to level 3 let (_three_perf_stat, cred3) = th.level_up(&cred2); assert!(scalar_u32(&cred3.trust_level).unwrap() == 3); println!("cred3 = {:?}", cred3); assert!(th.ba.verify_lox(&cred3)); // Get our bridges let (id, key) = bridge_table::from_scalar(cred3.bucket).unwrap(); let encbuckets = th.ba.enc_bridge_table(); let bucket = bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap()).unwrap(); // We should have a Bridge Reachability credential assert!(bucket.1.is_some()); // Oh, no! Two of our bridges are blocked! th.ba.bridge_blocked(&bucket.0[0], &mut th.bdb); th.ba.bridge_blocked(&bucket.0[2], &mut th.bdb); println!("spares = {:?}", th.ba.bridge_table.spares); println!("tmig = {:?}", th.ba.trustup_migration_table.table); println!("bmig = {:?}", th.ba.blockage_migration_table.table); println!("openinv = {:?}\n", th.bdb.openinv_buckets); // Time passes th.advance_days(1); let encbuckets2 = th.ba.enc_bridge_table(); let bucket2 = bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets2.get(&id).unwrap()).unwrap(); // We should no longer have a Bridge Reachability credential assert!(bucket2.1.is_none()); // See about getting a Migration credential for the blockage let (_block_perf_stat, migration) = th.check_blockage(&cred3); println!("migration = {:?}", migration); // Migrate let (_four_perf_stat, cred4) = th.blockage_migration(&cred3, &migration); println!("cred4 = {:?}", cred4); assert!(th.ba.verify_lox(&cred4)); } #[test] fn test_positive_report() { let mut th = TestHarness::new(); // Join an untrusted user let cred = th.open_invite().1 .0; // Time passes th.advance_days(47); // Go up to level 1 let (_mperf_stat, migcred) = th.trust_promotion(&cred); let (_perf_stat, cred1) = th.level0_migration(&cred, &migcred); assert!(scalar_u32(&cred1.trust_level).unwrap() == 1); // Time passes th.advance_days(20); // Go up to level 2 let (_two_perf_stat, cred2) = th.level_up(&cred1); assert!(scalar_u32(&cred2.trust_level).unwrap() == 2); // println!("cred2 = {:?}", cred2); assert!(th.ba.verify_lox(&cred2)); // Time passes th.advance_days(29); // Go up to level 3 let (_three_perf_stat, cred3) = th.level_up(&cred2); assert!(scalar_u32(&cred3.trust_level).unwrap() == 3); // println!("cred3 = {:?}", cred3); assert!(th.ba.verify_lox(&cred3)); // Submit positive report let _pr_perf_stat = th.positive_report(&cred3); // Time passes th.advance_days(60); // Go up to level 4 let (_four_perf_stat, cred4) = th.level_up(&cred3); assert!(scalar_u32(&cred4.trust_level).unwrap() == 4); assert!(th.ba.verify_lox(&cred4)); // Submit positive report let _pr_perf_stat2 = th.positive_report(&cred4); } fn print_test_results(perf_stat: PerfStat) { println!("Request size = {:?} bytes", perf_stat.req_len); println!("Request time = {:?}", perf_stat.req_t); println!("Response size = {:?} bytes", perf_stat.resp_len); println!("Response time = {:?}", perf_stat.resp_t); println!("Response handle time = {:?}", perf_stat.resp_handle_t); }