use crate::user_discovery::stores::InMemoryStore; use crate::user_discovery::traits::tests::TestingUtils; use crate::user_discovery::{UserDiscovery, UserDiscoveryStore, UserDiscoveryVersion, UserID}; use prost::Message; use rand::seq::SliceRandom; use std::collections::{HashMap, HashSet}; use std::vec; struct TestNetwork { ids_by_name: HashMap<&'static str, usize>, names: Vec<&'static str>, friends: Vec>, message_flows: Vec<(usize, usize)>, announced_users_expected: Vec)>>, uds: Vec>, } impl TestNetwork { fn new() -> Self { Self { ids_by_name: HashMap::new(), names: vec![], friends: vec![], message_flows: vec![], announced_users_expected: vec![], uds: vec![], } } async fn add_user(&mut self, name: &'static str, threshold: u8) { let id = self.names.len(); self.ids_by_name.insert(name, id); self.names.push(name); self.friends.push(vec![]); self.announced_users_expected.push(vec![]); let store = S::default(); self.uds.push(get_ud(id, threshold, store).await); } fn set_friends(&mut self, user: &str, friends: &[&str]) { let id = self.ids_by_name[user]; let f_ids: Vec = friends.iter().map(|f| self.ids_by_name[*f]).collect(); self.friends[id] = f_ids; } fn add_message_flow(&mut self, user: &str, count: usize) { let id = self.ids_by_name[user]; self.message_flows.push((id, count)); } fn expect_announced_user(&mut self, user: &str, contact: &str, friends_of_contact: &[&str]) { let user_id = self.ids_by_name[user]; let contact_id = self.ids_by_name[contact]; let f_ids: Vec = friends_of_contact .iter() .map(|f| self.ids_by_name[*f]) .collect(); self.announced_users_expected[user_id].push((contact_id, f_ids)); } } async fn get_with_five_users() -> TestNetwork { let mut network = TestNetwork::new(); network.add_user("ALICE", 2).await; network.add_user("BOB", 2).await; network.add_user("CHARLIE", 2).await; network.add_user("DAVID", 2).await; network.add_user("FRANK", 2).await; network.set_friends("ALICE", &["BOB", "CHARLIE"]); network.set_friends("BOB", &["ALICE", "CHARLIE", "DAVID"]); network.set_friends("CHARLIE", &["ALICE", "BOB", "DAVID", "FRANK"]); network.set_friends("DAVID", &["BOB", "CHARLIE"]); network.set_friends("FRANK", &["CHARLIE"]); network.add_message_flow("ALICE", 1); // ALICE: own announcement sending to BOB and CHARLIE network.add_message_flow("BOB", 2); // BOB: own announcement + promotion for ALICE network.add_message_flow("BOB", 0); // BOBs version should not have any new messages for his friends network.add_message_flow("ALICE", 1); // ALICE: promotion for BOB network.add_message_flow("CHARLIE", 3); // CHARLIE: own announcement + promotion for ALICE, BOB network.add_message_flow("DAVID", 3); // DAVID: own announcement + promotion for BOB, CHARLIE network.add_message_flow("BOB", 2); // BOB: promotion for CHARLIE, DAVID network.add_message_flow("CHARLIE", 1); // CHARLIE: promotion for DAVID network.add_message_flow("FRANK", 2); // FRANK: own announcement + promotion for CHARLIE network.add_message_flow("CHARLIE", 1); // CHARLIE: promotion for FRANK network.add_message_flow("ALICE", 1); // ALICE: promotion for CHARLIE // ALICE should now know that BOB and CHARLIE, BOB and DAVID and CHARLIE and DAVID are friends. // Alice should also have one protected share from Frank. network.expect_announced_user("ALICE", "BOB", &["CHARLIE"]); // ALICE knows Bob and that CHARLIE is connected with BOB network.expect_announced_user("ALICE", "CHARLIE", &["BOB"]); // ALICE knows CHARLIE and that BOB is connected with CHARLIE network.expect_announced_user("ALICE", "DAVID", &["BOB", "CHARLIE"]); // ALICE knows DAVID and that BOB and CHARLIE are connected with DAVID network.expect_announced_user("BOB", "ALICE", &["CHARLIE"]); network.expect_announced_user("BOB", "CHARLIE", &["ALICE", "DAVID"]); network.expect_announced_user("BOB", "DAVID", &["CHARLIE"]); network.expect_announced_user("CHARLIE", "ALICE", &["BOB"]); network.expect_announced_user("CHARLIE", "BOB", &["ALICE", "DAVID"]); network.expect_announced_user("CHARLIE", "DAVID", &["BOB"]); network.expect_announced_user("CHARLIE", "FRANK", &[]); network.expect_announced_user("DAVID", "ALICE", &["BOB", "CHARLIE"]); network.expect_announced_user("DAVID", "BOB", &["CHARLIE"]); network.expect_announced_user("DAVID", "CHARLIE", &["BOB"]); network.expect_announced_user("FRANK", "CHARLIE", &[]); network } #[tokio::test] async fn test_user_discovery_decreased_threshold_in_memory_store() { let _ = pretty_env_logger::try_init(); let mut network = TestNetwork::::new(); // Start ALICE with a more strict threshold of 3. // David only has 2 paths to Alice (via Bob and Charlie). Since 2 < 3, David cannot discover Alice. network.add_user("ALICE", 3).await; network.add_user("BOB", 2).await; network.add_user("CHARLIE", 2).await; network.add_user("DAVID", 2).await; network.add_user("FRANK", 2).await; // Same topology as the initial 5 users network.set_friends("ALICE", &["BOB", "CHARLIE"]); network.set_friends("BOB", &["ALICE", "CHARLIE", "DAVID"]); network.set_friends("CHARLIE", &["ALICE", "BOB", "DAVID", "FRANK"]); network.set_friends("DAVID", &["BOB", "CHARLIE"]); network.set_friends("FRANK", &["CHARLIE"]); let david_idx = network.ids_by_name["DAVID"]; let alice_idx = network.ids_by_name["ALICE"]; let charlie_idx = network.ids_by_name["CHARLIE"]; // Phase 1: Exchange with ALICE threshold = 3 step0_exchange_random::(&network).await; step1_verify_no_new_messages::(&network).await; let version = network.uds[david_idx] .get_contact_version(charlie_idx as UserID) .await .unwrap() .unwrap(); assert_eq!(version, get_version_bytes(1, 4)); // DAVID should NOT know ALICE yet because ALICE's threshold is 3, but David only has 2 shares. let david_knows = network.uds[david_idx] .get_all_announced_users() .await .unwrap(); let knows_alice = david_knows .iter() .any(|(u, _)| u.user_id == alice_idx as UserID); assert!(!knows_alice, "David should not know Alice yet because Alice's threshold is 3 and David only receives 2 shares"); // Phase 2: Update ALICE's threshold to 2 network.uds[alice_idx] .initialize_or_update(2, alice_idx as UserID, vec![alice_idx as u8; 32]) .await .unwrap(); let version = network.uds[alice_idx].get_current_version().await.unwrap(); assert_eq!(version, get_version_bytes(2, 2)); // ALICE's new announcement with threshold 2 should propagate further. step0_exchange_random::(&network).await; step1_verify_no_new_messages::(&network).await; let version = network.uds[david_idx] .get_contact_version(charlie_idx as UserID) .await .unwrap() .unwrap(); assert_eq!(version, get_version_bytes(1, 5)); let version = network.uds[alice_idx].get_current_version().await.unwrap(); assert_eq!(version, get_version_bytes(2, 2)); // Now DAVID SHOULD know ALICE let david_knows = network.uds[david_idx] .get_all_announced_users() .await .unwrap(); let knows_alice_now = david_knows .iter() .any(|(u, _)| u.user_id == alice_idx as UserID); assert!( knows_alice_now, "David should know Alice now because her threshold was updated to 2" ); } #[tokio::test] async fn test_user_discovery_increased_threshold_in_memory_store() { let _ = pretty_env_logger::try_init(); let mut network = TestNetwork::::new(); // Start ALICE with a more strict threshold of 3. // David only has 2 paths to Alice (via Bob and Charlie). Since 2 < 3, David cannot discover Alice. network.add_user("ALICE", 2).await; network.add_user("BOB", 2).await; network.add_user("CHARLIE", 2).await; network.add_user("DAVID", 2).await; network.add_user("FRANK", 2).await; // Same topology as the initial 5 users network.set_friends("ALICE", &["BOB", "CHARLIE"]); network.set_friends("BOB", &["ALICE", "CHARLIE", "DAVID"]); // CHARLIE IS NOT YET A FRIEND OF DAVID -> DAVID SHOULD NOT BE ABLE TO BE DECODE ALICE network.set_friends("CHARLIE", &["ALICE", "BOB", "FRANK"]); network.set_friends("DAVID", &["BOB", "CHARLIE"]); network.set_friends("FRANK", &["CHARLIE"]); let david_idx = network.ids_by_name["DAVID"]; let alice_idx = network.ids_by_name["ALICE"]; // Phase 1: Exchange with ALICE threshold = 2 step0_exchange_random::(&network).await; step1_verify_no_new_messages::(&network).await; // DAVID should NOT know ALICE yet because ALICE's threshold is 2, and David has only 1 shares. { let david_knows = network.uds[david_idx] .get_all_announced_users() .await .unwrap(); let knows_alice = david_knows .iter() .any(|(u, _)| u.user_id == alice_idx as UserID); assert!(!knows_alice, "David should not know Alice yet because Alice's threshold is 3 and David only receives 2 shares"); } // Phase 2: Update ALICE's threshold to 3 network.uds[alice_idx] .initialize_or_update(3, alice_idx as UserID, vec![alice_idx as u8; 32]) .await .unwrap(); // ALICE's new announcement with threshold 3 should propagate further. // This SHOULD REPLACE THE OLD VERSION... step0_exchange_random::(&network).await; step1_verify_no_new_messages::(&network).await; // Now Charlie is a friend of David, so he should exchange ALICE with him network.set_friends("CHARLIE", &["ALICE", "BOB", "DAVID", "FRANK"]); // ALICE's new announcement with threshold 3 should propagate further. // This SHOULD REPLACE THE OLD VERSION... step0_exchange_random::(&network).await; step1_verify_no_new_messages::(&network).await; // DAVID should still NOT know ALICE yet because ALICE's new threshold is 3, and David has only 2 shares. { let david_knows = network.uds[david_idx] .get_all_announced_users() .await .unwrap(); let knows_alice = david_knows .iter() .any(|(u, _)| u.user_id == alice_idx as UserID); assert!(!knows_alice, "David should not know Alice yet because Alice's threshold is 3 and David only receives 2 shares"); } } #[tokio::test] async fn test_user_discovery_in_memory_store() { let _ = pretty_env_logger::try_init(); let users = get_with_five_users().await; step0_exchange_in_order::(&users).await; step1_verify_no_new_messages::(&users).await; step2_verify_announced_users_expected::(&users).await; let alice_idx = users.ids_by_name["ALICE"]; let bob_idx = users.ids_by_name["BOB"]; let david_idx = users.ids_by_name["DAVID"]; users.uds[bob_idx] .update_verification_state_for_user(alice_idx as UserID, Some(10)) .await .unwrap(); step0_exchange_random::(&users).await; step1_verify_no_new_messages::(&users).await; { let david_knows = users.uds[david_idx] .get_all_announced_users() .await .unwrap(); let knows_alice = david_knows .iter() .find(|(u, _)| u.user_id == alice_idx as UserID); assert!(knows_alice.is_some(), "David should know Alice"); let bob_has_verified = knows_alice .unwrap() .1 .iter() .find(|(user_id, _)| *user_id == bob_idx as UserID) .unwrap(); assert_eq!(bob_has_verified.1, Some(10)); } } #[tokio::test] async fn test_user_discovery_random_order_in_memory_store() { let _ = pretty_env_logger::try_init(); let users = get_with_five_users().await; step0_exchange_random::(&users).await; step1_verify_no_new_messages::(&users).await; step2_verify_announced_users_expected::(&users).await; } async fn step0_exchange_in_order(users: &TestNetwork) { for (i, (from, count)) in users.message_flows.iter().enumerate() { tracing::debug!("MESSAGE FLOW: {i}"); to_all_friends(*from, *count, &users).await; } } async fn step0_exchange_random(users: &TestNetwork) { let mut user_ids: Vec = (0..users.names.len()).collect(); for _ in 0..100 { user_ids.shuffle(&mut rand::rng()); for from in user_ids.clone() { for friend in &users.friends[from] { request_and_handle_messages( (from, &users.uds[from]), (*friend, &users.uds[*friend]), None, ) .await; } } } } async fn step1_verify_no_new_messages( users: &TestNetwork, ) { tracing::debug!("Now all users should have the newest version."); for from in 0..users.names.len() { for to in &users.friends[from] { tracing::debug!( "Does {} has open messages for {}?", &users.names[from], &users.names[*to] ); assert_new_messages((from, &users.uds[from]), (*to, &users.uds[*to]), false).await; } } } async fn step2_verify_announced_users_expected( users: &TestNetwork, ) { tracing::debug!("Test if all exchanges where successful."); for (user, announcements) in users.announced_users_expected.iter().enumerate() { let announced_users2 = users.uds[user].get_all_announced_users().await.unwrap(); let mut announced_users = HashMap::new(); for a in announced_users2 { announced_users.insert(a.0.user_id, a.1.iter().map(|x| x.0).collect::>()); } tracing::debug!("{} knows now: {}", users.names[user], announced_users.len()); assert_eq!(announced_users.len(), announcements.len()); for (contact_id, announced_users_expected) in announcements { let announced_users = announced_users.get(&(*contact_id as i64)).unwrap(); tracing::debug!( "{} knows now that {} has the following friends: {}", users.names[user], users.names[*contact_id], announced_users .iter() .map(|x| users.names[*x as usize]) .collect::>() .join(", ") ); let announced_users: HashSet = announced_users.iter().cloned().collect(); let announced_users_expected: HashSet = announced_users_expected .iter() .cloned() .map(|x| x as i64) .collect(); assert_eq!(announced_users, announced_users_expected); } } } async fn get_ud( user_id: usize, threshold: u8, store: S, ) -> UserDiscovery { let ud = UserDiscovery::new(store.to_owned(), TestingUtils::default()).unwrap(); ud.initialize_or_update(threshold, user_id as UserID, vec![user_id as u8; 32]) .await .unwrap(); let version = ud.get_current_version().await.unwrap(); assert_eq!(version, get_version_bytes(1, 0)); ud } async fn assert_new_messages( from: (usize, &UserDiscovery), to: (usize, &UserDiscovery), has_new_messages: bool, ) { // From sends a message with his current version to To let to_received_version = &from.1.get_current_version().await.unwrap(); assert_eq!( to.1.should_request_new_messages(from.0 as UserID, to_received_version) .await .unwrap() .is_some(), has_new_messages ); } async fn request_and_handle_messages( from: (usize, &UserDiscovery), to: (usize, &UserDiscovery), messages_count: Option, ) { // From sends a message with his current version to To if messages_count.is_some() { let to_received_version = &from.1.get_current_version().await.unwrap(); assert_eq!( to.1.should_request_new_messages(from.0 as UserID, to_received_version) .await .unwrap() .is_some(), true ); } // As To has a older version stored he sends a request to From: Give me all messages since version. let from_request_version_from_to = to.1.get_contact_version(from.0 as UserID) .await .unwrap() .unwrap_or(get_version_bytes(0, 0)); let new_messages = from .1 .get_new_messages(to.0 as UserID, &from_request_version_from_to) .await .unwrap(); if let Some(messages_count) = messages_count { assert!(new_messages.len() <= messages_count); } to.1.handle_new_messages(from.0 as UserID, None, new_messages) .await .unwrap(); if messages_count.is_some() { assert_eq!( to.1.should_request_new_messages( from.0 as UserID, &from.1.get_current_version().await.unwrap() ) .await .unwrap() .is_some(), false ); } } async fn to_all_friends( from: usize, message_count: usize, users: &TestNetwork, ) { for friend in &users.friends[from] { tracing::debug!("From {} to {}", users.names[from], users.names[*friend]); if message_count == 0 { assert_new_messages( (from, &users.uds[from]), (*friend, &users.uds[*friend]), false, ) .await; } else { request_and_handle_messages( (from, &users.uds[from]), (*friend, &users.uds[*friend]), Some(message_count), ) .await; } } } fn get_version_bytes(announcement: u32, promotion: u32) -> Vec { UserDiscoveryVersion { announcement, promotion, } .encode_to_vec() }