// User behavior in simulation use crate::{ get_date, negative_report::NegativeReport, positive_report::PositiveReport, simulation::{ bridge::Bridge, censor::{Censor, Hides::*, Speed::*, Totality::*}, config::Config, }, BridgeDistributor, }; 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::collections::HashMap; 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? 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, } impl User { pub async fn new(config: &Config) -> Self { let cred = get_lox_credential( &config.la_net, &get_open_invitation(&config.la_net).await, get_lox_pub(&config.la_pubkeys), ) .await .0; // Probabilistically decide whether this user cooperates with a censor let is_censor = event_happens(config.prob_user_is_censor); // Probabilistically decide whether this user submits reports let submits_reports = if is_censor { false } else { event_happens(config.prob_user_submits_reports) }; // Randomly determine how likely this user is to use bridges on // a given day let mut rng = rand::thread_rng(); let prob_use_bridges = rng.gen_range(0.0..=1.0); Self { is_censor, primary_cred: cred, secondary_cred: None, submits_reports: submits_reports, prob_use_bridges: prob_use_bridges, } } pub async fn trusted_user(config: &Config) -> Self { let cred = get_lox_credential( &config.la_net, &get_open_invitation(&config.la_net).await, get_lox_pub(&config.la_pubkeys), ) .await .0; Self { is_censor: false, primary_cred: cred, secondary_cred: None, submits_reports: true, prob_use_bridges: 1.0, } } // TODO: This should probably return an actual error type pub async fn invite(&mut self, config: &Config, censor: &mut Censor) -> 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; if self.is_censor { // Make sure censor has access to each bridge and each // credential let (bucket, _reachcred) = get_bucket(&config.la_net, &self.primary_cred).await; for bl in bucket { let fingerprint = bl.get_hashed_fingerprint(); censor.learn_bridge(&fingerprint); censor.give_lox_cred(&fingerprint, &self.primary_cred); } } let friend_cred = redeem_invite( &config.la_net, &invite, get_lox_pub(&config.la_pubkeys), get_invitation_pub(&config.la_pubkeys), ) .await .0; // If the inviting user is a censor, the invitee will also be a // censor. If not, probabilistically decide. let is_censor = if self.is_censor { true } else { event_happens(config.prob_user_is_censor) }; // Probabilistically decide whether this user submits reports let submits_reports = if is_censor { false } else { event_happens(config.prob_user_submits_reports) }; // Randomly determine how likely this user is to use bridges on // a given day let mut rng = rand::thread_rng(); let prob_use_bridges = rng.gen_range(0.0..=1.0); Ok(Self { is_censor, primary_cred: friend_cred, secondary_cred: None, submits_reports: submits_reports, prob_use_bridges: prob_use_bridges, }) } // Attempt to "connect" to the bridge, returns true if successful pub fn connect(&self, config: &Config, bridge: &mut Bridge, censor: &Censor) -> bool { if censor.knows_bridge(&bridge.fingerprint) { if config.censor_speed == Fast || config.censor_speed == Random && censor.delay_date <= get_date() || config.censor_speed == Lox && censor.has_lox_cred(&bridge.fingerprint) { if config.censor_totality == Full || config.censor_totality == Partial && event_happens(censor.partial_blocking_percent) || config.censor_totality == Throttling { // If censor tries to hide its censorship or // throttles rather than actually blocking, record a // false connection if config.censor_hides == Hiding || config.censor_totality == Throttling { bridge.connect_total(); } // Return false because the connection failed return false; } } } // Connection may randomly fail, without censor intervention if event_happens(config.prob_connection_fails) { return false; } // If we haven't returned yet, the connection succeeded bridge.connect_real(); true } pub async fn send_negative_reports(config: &Config, reports: Vec) { let date = get_date(); let pubkey = serde_json::from_slice::>( &config .tp_net .request( "/nrkey".to_string(), serde_json::to_string(&date).unwrap().into(), ) .await, ) .unwrap() .unwrap(); for report in reports { config .tp_net .request( "/negativereport".to_string(), bincode::serialize(&report.encrypt(&pubkey)).unwrap(), ) .await; } } pub async fn send_positive_reports(config: &Config, reports: Vec) { for report in reports { config .tp_net .request("/positivereport".to_string(), report.to_json().into_bytes()) .await; } } pub async fn daily_tasks( &mut self, config: &Config, bridges: &mut HashMap<[u8; 20], Bridge>, censor: &mut Censor, ) -> Vec { if self.is_censor { self.daily_tasks_censor(config, bridges, censor).await } else { self.daily_tasks_non_censor(config, 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, bridges: &mut HashMap<[u8; 20], Bridge>, censor: &mut Censor, ) -> Vec { // Probabilistically decide if the user should use bridges today if event_happens(self.prob_use_bridges) { // 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 = scalar_u32(&self.primary_cred.trust_level).unwrap(); // Make sure each bridge in bucket is in the global bridges set for bridgeline in bucket { if bridgeline != BridgeLine::default() { if !bridges.contains_key(&bridgeline.get_hashed_fingerprint()) { let bridge = Bridge::from_bridge_line(&bridgeline); bridges.insert(bridgeline.get_hashed_fingerprint(), bridge); } } } // 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( &config, bridges .get_mut(&bucket[i].get_hashed_fingerprint()) .unwrap(), &censor, ) { 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 let cred = get_lox_credential( &config.la_net, &get_open_invitation(&config.la_net).await, get_lox_pub(&config.la_pubkeys), ) .await .0; Some(cred) } } 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() { if !bridges.contains_key(&bridgeline.get_hashed_fingerprint()) { bridges.insert( bridgeline.get_hashed_fingerprint(), Bridge::from_bridge_line(&bridgeline), ); } // Attempt to connect to second cred's bridge if self.connect( &config, bridges .get_mut(&bridgeline.get_hashed_fingerprint()) .unwrap(), censor, ) { 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 { positive_reports.push( PositiveReport::from_lox_credential( bridgeline.get_hashed_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 = scalar_u32(&self.primary_cred.invites_remaining).unwrap(); let mut new_friends = Vec::::new(); for _i in 0..invitations { if event_happens(config.prob_user_invites_friend) { match self.invite(&config, 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); } } } } new_friends } else { 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, ) -> Vec { // 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() { if !bridges.contains_key(&bridgeline.get_hashed_fingerprint()) { let bridge = Bridge::from_bridge_line(&bridgeline); bridges.insert(bridgeline.get_hashed_fingerprint(), bridge); } censor.learn_bridge(&bridgeline.get_hashed_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 replace the primary credential // with a new level 0 credential and work on gaining trust // for that one. let (new_cred, bl) = get_lox_credential( &config.la_net, &get_open_invitation(&config.la_net).await, get_lox_pub(&config.la_pubkeys), ) .await; 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 (_new_cred, bl) = get_lox_credential( &config.la_net, &get_open_invitation(&config.la_net).await, get_lox_pub(&config.la_pubkeys), ) .await; 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, censor).await { Ok(friend) => { new_friends.push(friend); } Err(e) => { println!("{}", e); } } } new_friends } }