// User behavior in simulation use crate::{ bridge::Bridge, censor::{Censor, Secrecy::*, Totality::*}, config::Config, }; use anyhow::{anyhow, Result}; use lox_cli::{networking::*, *}; use lox_library::{ bridge_table::BridgeLine, cred::Lox, proto::check_blockage::MIN_TRUST_LEVEL, scalar_u32, }; use rand::Rng; use std::{cmp::min, collections::HashMap}; use troll_patrol::{ get_date, negative_report::NegativeReport, positive_report::PositiveReport, BridgeDistributor, }; use x25519_dalek::PublicKey; // Helper function to probabilistically return true or false pub fn event_happens(probability: f64) -> bool { let mut rng = rand::thread_rng(); let num: f64 = rng.gen_range(0.0..1.0); num < probability } pub struct User { // Does this user cooperate with a censor? pub is_censor: bool, // The user always has a primary credential. If this credential's bucket is // blocked, the user may replace it or temporarily hold two credentials // while waiting to migrate from the primary credential. pub primary_cred: Lox, secondary_cred: Option, // Does the user submit reports to Troll Patrol? submits_reports: bool, // How likely is this user to use bridges on a given day? prob_use_bridges: f64, // If the censor implements partial blocking, is the user blocked? in_censorship_range: bool, // Track date the user joined and whether they're able to connect pub join_date: u32, pub able_to_connect: bool, } impl User { pub async fn new( config: &Config, is_censor: bool, bridges: &mut HashMap<[u8; 20], Bridge>, censor: &mut Censor, ) -> Result { let cred = Self::get_new_credential(&config).await?.0; // Decide how likely this user is to use bridges on a given day // and whether they submit reports let (prob_use_bridges, submits_reports) = if is_censor { (0.0, false) } else { let mut rng = rand::thread_rng(); let prob_use_bridges = rng.gen_range(0.0..=1.0); let submits_reports = event_happens(config.prob_user_submits_reports); (prob_use_bridges, submits_reports) }; let in_censorship_range = if config.censor_totality == Partial { event_happens(config.censor_partial_blocking_percent) } else { true }; let mut able_to_connect = false; // Immediately download and try to use our bridges, or report them to the censor. let (bucket, _reachcred) = get_bucket(&config.la_net, &cred).await?; for bridgeline in bucket { if bridgeline != BridgeLine::default() { let fingerprint = bridgeline.get_hashed_fingerprint(); if !bridges.contains_key(&fingerprint) { let bridge = Bridge::from_bridge_line(&bridgeline); bridges.insert(fingerprint, bridge); } let bridge = bridges.get_mut(&fingerprint).unwrap(); if is_censor { censor.learn_bridge(&fingerprint); } else { // If this is the first time the bridge has been // distributed to a real user, store that info if bridge.first_real_user == 0 { bridge.first_real_user = get_date(); } if Self::connect(in_censorship_range, config, bridge, censor) { able_to_connect = true; } else if submits_reports { // New user only has one bridge, so no need // to collect the negative reports before // sending. Just send one now. let mut negative_reports = Vec::::new(); negative_reports.push(NegativeReport::from_bridgeline( bridgeline, config.country.to_string(), BridgeDistributor::Lox, )); Self::send_negative_reports(&config, negative_reports).await?; } } } } Ok(Self { is_censor, primary_cred: cred, secondary_cred: None, submits_reports: submits_reports, prob_use_bridges: prob_use_bridges, in_censorship_range, join_date: get_date(), able_to_connect, }) } // TODO: This should probably return an actual error type pub async fn invite( &mut self, config: &Config, bridges: &mut HashMap<[u8; 20], Bridge>, censor: &mut Censor, invited_user_is_censor: bool, ) -> Result { let etable = get_reachability_credential(&config.la_net).await?; let (new_cred, invite) = issue_invite( &config.la_net, &self.primary_cred, &etable, get_lox_pub(&config.la_pubkeys), get_reachability_pub(&config.la_pubkeys), get_invitation_pub(&config.la_pubkeys), ) .await?; self.primary_cred = new_cred; // Make sure bridge is in list of bridges and that censor has // access if applicable let (bucket, _reachcred) = get_bucket(&config.la_net, &self.primary_cred).await?; for bl in bucket { if bl != BridgeLine::default() { let fingerprint = bl.get_hashed_fingerprint(); if !bridges.contains_key(&fingerprint) { let bridge = Bridge::from_bridge_line(&bl); bridges.insert(fingerprint, bridge); } let bridge = bridges.get_mut(&fingerprint).unwrap(); if self.is_censor { censor.learn_bridge(&fingerprint); censor.give_lox_cred(&fingerprint, &self.primary_cred); // If this is the first time the bridge has been // distributed to a real user, store that info } else if bridge.first_real_user == 0 { bridge.first_real_user = get_date(); } } } let friend_cred = redeem_invite( &config.la_net, &invite, get_lox_pub(&config.la_pubkeys), get_invitation_pub(&config.la_pubkeys), ) .await? .0; // Calling function decides if the invited user is a censor let is_censor = invited_user_is_censor; // Decide how likely this user is to use bridges on a given day // and whether they submit reports let (prob_use_bridges, submits_reports) = if is_censor { (0.0, false) } else { let mut rng = rand::thread_rng(); let prob_use_bridges = rng.gen_range(0.0..=1.0); let submits_reports = event_happens(config.prob_user_submits_reports); (prob_use_bridges, submits_reports) }; let in_censorship_range = if config.censor_totality == Partial { event_happens(config.censor_partial_blocking_percent) } else { true }; let mut able_to_connect = false; // Immediately download bucket and test bridges or give them to // the censor let mut negative_reports = Vec::::new(); let (bucket, _reachcred) = get_bucket(&config.la_net, &friend_cred).await?; for bridgeline in bucket { let fingerprint = bridgeline.get_hashed_fingerprint(); if bridgeline != BridgeLine::default() { if !bridges.contains_key(&fingerprint) { let bridge = Bridge::from_bridge_line(&bridgeline); bridges.insert(fingerprint, bridge); } let bridge = bridges.get_mut(&fingerprint).unwrap(); if is_censor { censor.learn_bridge(&fingerprint); } else { // If this is the first time the bridge has been // distributed to a real user, store that info if bridge.first_real_user == 0 { bridge.first_real_user = get_date(); } if Self::connect(in_censorship_range, config, bridge, censor) { able_to_connect = true; } else if submits_reports { negative_reports.push(NegativeReport::from_bridgeline( bridgeline, config.country.to_string(), BridgeDistributor::Lox, )); } } } } // Submit reports if we have them if negative_reports.len() > 0 { Self::send_negative_reports(&config, negative_reports).await?; } let in_censorship_range = if config.censor_totality == Partial { event_happens(config.censor_partial_blocking_percent) } else { true }; Ok(Self { is_censor, primary_cred: friend_cred, secondary_cred: None, submits_reports: submits_reports, prob_use_bridges: prob_use_bridges, in_censorship_range, join_date: get_date(), able_to_connect, }) } // Attempt to "connect" to the bridge, returns true if successful. // Note that this does not involve making a real connection to a // real bridge. pub fn connect( in_censorship_range: bool, config: &Config, bridge: &mut Bridge, censor: &Censor, ) -> bool { if censor.blocks_bridge(config, &bridge.fingerprint) { if config.censor_totality == Full || config.censor_totality == Partial && in_censorship_range { // If censor tries to hide its censorship, record a // false connection if config.censor_secrecy == Hiding { bridge.connect_total(); } // Return false because the connection failed return false; } else if config.censor_totality == Throttling { // With some probability, the user connects but gives up // because there is too much interference. In this case, // a real connection occurs, but we treat it like a // false connection from the censor. if event_happens(config.prob_user_treats_throttling_as_blocking) { bridge.connect_total(); // Return false because there was interference // detected in the connection return false; } } } // Connection may randomly fail, without censor intervention let mut connection_fails = true; // The user retries some number of times for _ in 0..=config.num_connection_retries { if !event_happens(config.prob_connection_fails) { connection_fails = false; break; } } if connection_fails { return false; } // If we haven't returned yet, the connection succeeded bridge.connect_real(); true } pub async fn get_new_credential(config: &Config) -> Result<(Lox, BridgeLine)> { get_lox_credential( &config.la_net, &get_open_invitation(&config.la_net).await?, get_lox_pub(&config.la_pubkeys), ) .await } pub async fn send_negative_reports( config: &Config, reports: Vec, ) -> Result<()> { let date = get_date(); let pubkey = match serde_json::from_slice::>( &config .tp_net .request("/nrkey".to_string(), serde_json::to_string(&date)?.into()) .await?, )? { Some(v) => v, None => return Err(anyhow!("No available negative report encryption key")), }; for report in reports { config .tp_net .request( "/negativereport".to_string(), bincode::serialize(&report.encrypt(&pubkey))?, ) .await?; } Ok(()) } pub async fn send_positive_reports( config: &Config, reports: Vec, ) -> Result<()> { for report in reports { config .tp_net .request("/positivereport".to_string(), report.to_json().into_bytes()) .await?; } Ok(()) } pub async fn daily_tasks( &mut self, config: &Config, num_users_requesting_invites: u32, bridges: &mut HashMap<[u8; 20], Bridge>, censor: &mut Censor, ) -> Result> { if self.is_censor { self.daily_tasks_censor(config, bridges, censor).await } else { self.daily_tasks_non_censor(config, num_users_requesting_invites, bridges, censor) .await } } // User performs daily connection attempts, etc. and returns a // vector of newly invited friends. // TODO: The map of bridges and the censor should be Arc> // or something so we can parallelize this. pub async fn daily_tasks_non_censor( &mut self, config: &Config, num_users_requesting_invites: u32, bridges: &mut HashMap<[u8; 20], Bridge>, censor: &mut Censor, ) -> Result> { // Probabilistically decide if the user should use bridges today if event_happens(self.prob_use_bridges) { // Start with the assumption that we can't connect, change // only if we can self.able_to_connect = false; // Download bucket to see if bridge is still reachable. (We // assume that this step can be done even if the user can't // actually talk to the LA.) let (bucket, reachcred) = get_bucket(&config.la_net, &self.primary_cred).await?; let level = match scalar_u32(&self.primary_cred.trust_level) { Some(v) => v, None => return Err(anyhow!("Failed to get trust level from credential")), }; // Make sure each bridge in bucket is in the global bridges set for bridgeline in bucket { if bridgeline != BridgeLine::default() { let fingerprint = bridgeline.get_hashed_fingerprint(); if !bridges.contains_key(&fingerprint) { let bridge = Bridge::from_bridge_line(&bridgeline); bridges.insert(fingerprint, bridge); } // If this is the first time the bridge has been // distributed to a real user, store that info let bridge = bridges.get_mut(&fingerprint).unwrap(); if bridge.first_real_user == 0 { bridge.first_real_user = get_date(); } } } // Can we level up the main credential? let can_level_up = reachcred.is_some() && (level == 0 && eligible_for_trust_promotion(&config.la_net, &self.primary_cred).await || level > 0 && eligible_for_level_up(&config.la_net, &self.primary_cred).await); // Can we migrate the main credential? let can_migrate = reachcred.is_none() && level >= MIN_TRUST_LEVEL; // Can we level up the secondary credential? let mut second_level_up = false; let mut failed = Vec::::new(); let mut succeeded = Vec::::new(); // Try to connect to each bridge for i in 0..bucket.len() { // At level 0, we only have 1 bridge if bucket[i] != BridgeLine::default() { if Self::connect( self.in_censorship_range, &config, bridges .get_mut(&bucket[i].get_hashed_fingerprint()) .unwrap(), &censor, ) { self.able_to_connect = true; succeeded.push(bucket[i]); } else { failed.push(bucket[i]); } } } // If we were not able to connect to any bridges, get a // second credential let second_cred = if succeeded.len() < 1 { if self.secondary_cred.is_some() { std::mem::replace(&mut self.secondary_cred, None) } else { // Get new credential match Self::get_new_credential(&config).await { Ok((cred, _bl)) => Some(cred), Err(e) => { eprintln!("Failed to get new Lox credential. Error: {}", e); None } } } } else { // If we're able to connect with the primary credential, don't // keep a secondary one. None }; if second_cred.is_some() { let second_cred = second_cred.as_ref().unwrap(); let (second_bucket, second_reachcred) = get_bucket(&config.la_net, &second_cred).await?; for bridgeline in second_bucket { if bridgeline != BridgeLine::default() { let fingerprint = bridgeline.get_hashed_fingerprint(); if !bridges.contains_key(&fingerprint) { bridges.insert(fingerprint, Bridge::from_bridge_line(&bridgeline)); } // If this is the first time the bridge has been // distributed to a real user, store that info let bridge = bridges.get_mut(&fingerprint).unwrap(); if bridge.first_real_user == 0 { bridge.first_real_user = get_date(); } // Attempt to connect to second cred's bridge if Self::connect(self.in_censorship_range, &config, bridge, censor) { self.able_to_connect = true; succeeded.push(bridgeline); if second_reachcred.is_some() && eligible_for_trust_promotion(&config.la_net, &second_cred).await { second_level_up = true; } } else { failed.push(bridgeline); } } } } let mut negative_reports = Vec::::new(); let mut positive_reports = Vec::::new(); if self.submits_reports { for bridgeline in &failed { negative_reports.push(NegativeReport::from_bridgeline( *bridgeline, config.country.to_string(), BridgeDistributor::Lox, )); } if level >= 3 { for bridgeline in &succeeded { // If we haven't received a positive report yet, // add a record about it with today's date let fingerprint = bridgeline.get_hashed_fingerprint(); let bridge = bridges.get_mut(&fingerprint).unwrap(); if bridge.first_positive_report == 0 { bridge.first_positive_report = get_date(); } positive_reports.push( PositiveReport::from_lox_credential( fingerprint, None, &self.primary_cred, get_lox_pub(&config.la_pubkeys), config.country.to_string(), ) .unwrap(), ); } } } // We might restrict these steps to succeeded.len() > 0, but // we do assume the user can contact the LA somehow, so // let's just allow it. if can_level_up { let cred = if level == 0 { trust_migration( &config.la_net, &self.primary_cred, &trust_promotion( &config.la_net, &self.primary_cred, get_lox_pub(&config.la_pubkeys), ) .await?, get_lox_pub(&config.la_pubkeys), get_migration_pub(&config.la_pubkeys), ) .await? } else { level_up( &config.la_net, &self.primary_cred, &reachcred.unwrap(), get_lox_pub(&config.la_pubkeys), get_reachability_pub(&config.la_pubkeys), ) .await? }; self.primary_cred = cred; self.secondary_cred = None; } // We favor starting over at level 1 to migrating to level // 1, but if we have a level 4 credential for a bridge that // hasn't been marked blocked, save the credential so we can // migrate to a level 2 cred. Note that second_level_up is // only true if we were unable to connect with bridges from // our primary credential. else if second_level_up && (level <= MIN_TRUST_LEVEL || reachcred.is_none()) { let second_cred = second_cred.as_ref().unwrap(); let cred = trust_migration( &config.la_net, &second_cred, &trust_promotion( &config.la_net, &second_cred, get_lox_pub(&config.la_pubkeys), ) .await?, get_lox_pub(&config.la_pubkeys), get_migration_pub(&config.la_pubkeys), ) .await?; self.primary_cred = cred; self.secondary_cred = None; } else if can_migrate { let cred = blockage_migration( &config.la_net, &self.primary_cred, &check_blockage( &config.la_net, &self.primary_cred, get_lox_pub(&config.la_pubkeys), ) .await?, get_lox_pub(&config.la_pubkeys), get_migration_pub(&config.la_pubkeys), ) .await?; self.primary_cred = cred; self.secondary_cred = None; } else if second_cred.is_some() { // Couldn't connect with primary credential if succeeded.len() > 0 { // Keep the second credential only if it's useful self.secondary_cred = second_cred; } } if negative_reports.len() > 0 { Self::send_negative_reports(&config, negative_reports).await?; } if positive_reports.len() > 0 { Self::send_positive_reports(&config, positive_reports).await?; } // Invite friends if applicable let invitations = match scalar_u32(&self.primary_cred.invites_remaining) { Some(v) => v, None => 0, // This is probably an error case that should not happen }; let mut new_friends = Vec::::new(); // Scale the probability of inviting a censor, based on the // user's own trust level. We assume that users with // more-trusted credentials are less likely to invite // censors because they have more to lose. let level_scale = match scalar_u32(&self.primary_cred.trust_level) { // These numbers are fairly arbitrary. Some(4) => 0.01, Some(3) => 0.1, Some(2) => 0.5, _ => 1.0, }; for _i in 0..min(invitations, num_users_requesting_invites) { if event_happens(config.prob_user_invites_friend) { // With some probability, the user is convinced to // invite a censor. We assume users with higher // trust levels will be more cautious with // invitations because they have more to lose. let invited_friend_is_censor = censor.is_active() && event_happens(config.prob_censor_gets_invite * level_scale); // Invite friend (who might be a censor) match self .invite(&config, bridges, censor, invited_friend_is_censor) .await { Ok(friend) => { // You really shouldn't push your friends, // especially new ones whose boundaries you // might not know well. new_friends.push(friend); } Err(e) => { println!("{}", e); } } } } Ok(new_friends) } else { Ok(Vec::::new()) } } // User cooperates with censor and performs daily tasks to try to // learn more bridges. pub async fn daily_tasks_censor( &mut self, config: &Config, bridges: &mut HashMap<[u8; 20], Bridge>, censor: &mut Censor, ) -> Result> { // Download bucket to see if bridge is still reachable and if we // have any new bridges let (bucket, reachcred) = get_bucket(&config.la_net, &self.primary_cred).await?; let level = scalar_u32(&self.primary_cred.trust_level).unwrap(); // Make sure each bridge is in global bridges set and known by // censor for bridgeline in bucket { if bridgeline != BridgeLine::default() { let fingerprint = bridgeline.get_hashed_fingerprint(); if !bridges.contains_key(&fingerprint) { let bridge = Bridge::from_bridge_line(&bridgeline); bridges.insert(fingerprint, bridge); } censor.learn_bridge(&fingerprint); } } // Censor user tries to level up their primary credential if reachcred.is_some() { if level == 0 && eligible_for_trust_promotion(&config.la_net, &self.primary_cred).await || level > 0 && eligible_for_level_up(&config.la_net, &self.primary_cred).await { let new_cred = if level == 0 { trust_migration( &config.la_net, &self.primary_cred, &trust_promotion( &config.la_net, &self.primary_cred, get_lox_pub(&config.la_pubkeys), ) .await?, get_lox_pub(&config.la_pubkeys), get_migration_pub(&config.la_pubkeys), ) .await? } else { level_up( &config.la_net, &self.primary_cred, &reachcred.unwrap(), get_lox_pub(&config.la_pubkeys), get_reachability_pub(&config.la_pubkeys), ) .await? }; self.primary_cred = new_cred; let (bucket, _reachcred) = get_bucket(&config.la_net, &self.primary_cred).await?; // Make sure each bridge is in global bridges set and // known by censor for bl in bucket { let fingerprint = bl.get_hashed_fingerprint(); if !bridges.contains_key(&fingerprint) { let bridge = Bridge::from_bridge_line(&bl); bridges.insert(fingerprint, bridge); } censor.learn_bridge(&fingerprint); censor.give_lox_cred(&fingerprint, &self.primary_cred); } } } else { // LA has identified this bucket as blocked. This change // will not be reverted, so either migrate or replace the // primary credential with a new level 0 credential and work // on gaining trust for that one. // Migrate if able if level >= MIN_TRUST_LEVEL { if let Ok(migcred) = check_blockage( &config.la_net, &self.primary_cred, get_lox_pub(&config.la_pubkeys), ) .await { if let Ok(cred) = blockage_migration( &config.la_net, &self.primary_cred, &migcred, get_lox_pub(&config.la_pubkeys), get_migration_pub(&config.la_pubkeys), ) .await { self.primary_cred = cred; // You can't migrate to level 3 or 4, so the // censor doesn't want this new credential // Download bucket to see if bridge is still // reachable and if we have any new bridges let (bucket, _reachcred) = get_bucket(&config.la_net, &self.primary_cred).await?; // Make sure each bridge is in global bridges // set and known by censor for bridgeline in bucket { if bridgeline != BridgeLine::default() { let fingerprint = bridgeline.get_hashed_fingerprint(); if !bridges.contains_key(&fingerprint) { let bridge = Bridge::from_bridge_line(&bridgeline); bridges.insert(fingerprint, bridge); } censor.learn_bridge(&fingerprint); } } } } } else { // If unable to migrate, try to get a new open-entry // credential and start over let res = Self::get_new_credential(&config).await; if res.is_ok() { let (new_cred, bl) = res.unwrap(); let fingerprint = bl.get_hashed_fingerprint(); if !bridges.contains_key(&fingerprint) { let bridge = Bridge::from_bridge_line(&bl); bridges.insert(fingerprint, bridge); } censor.learn_bridge(&fingerprint); // Censor doesn't want new_cred yet self.primary_cred = new_cred; } } } // Separately from primary credential, censor user requests a // new secondary credential each day just to block the // open-entry bridges. This is stored but not reused. let res = Self::get_new_credential(&config).await; if res.is_ok() { let (_new_cred, bl) = res.unwrap(); let fingerprint = bl.get_hashed_fingerprint(); if !bridges.contains_key(&fingerprint) { let bridge = Bridge::from_bridge_line(&bl); bridges.insert(fingerprint, bridge); } censor.learn_bridge(&fingerprint); // Censor doesn't want new_cred. User doesn't actually use // secondary_cred, so don't store it. } // Censor user invites as many censor friends as possible let invitations = scalar_u32(&self.primary_cred.invites_remaining).unwrap(); let mut new_friends = Vec::::new(); for _ in 0..invitations { match self.invite(&config, bridges, censor, true).await { Ok(friend) => { new_friends.push(friend); } Err(e) => { println!("{}", e); } } } Ok(new_friends) } }