From 9cb967ca61b1b4957e6d60324427016da05670e0 Mon Sep 17 00:00:00 2001 From: Vecna Date: Tue, 28 May 2024 15:23:25 -0400 Subject: [PATCH] Simulate only one censor at a time --- src/bin/simulation.rs | 56 ++--- src/lib.rs | 2 +- src/simulation/bridge.rs | 55 ++--- src/simulation/censor.rs | 95 ++++----- src/simulation/config.rs | 25 +++ src/simulation/state.rs | 21 -- src/simulation/user.rs | 431 +++++++++++++++++++++++++-------------- 7 files changed, 399 insertions(+), 286 deletions(-) create mode 100644 src/simulation/config.rs delete mode 100644 src/simulation/state.rs diff --git a/src/bin/simulation.rs b/src/bin/simulation.rs index cfb9595..a201e5d 100644 --- a/src/bin/simulation.rs +++ b/src/bin/simulation.rs @@ -8,9 +8,9 @@ use troll_patrol::{ increment_simulated_date, simulation::{ bridge::Bridge, - censor::{Censor, Hides::*, Speed::*, Totality::*}, + censor::{self, Censor}, + config::Config as SConfig, extra_infos_server, - state::State, user::User, }, }; @@ -42,17 +42,20 @@ pub struct Config { pub la_test_port: u16, pub tp_port: u16, pub tp_test_port: u16, + pub censor_hides: censor::Hides, + pub censor_speed: censor::Speed, + pub censor_event_duration: u32, + pub censor_totality: censor::Totality, + pub censor_partial_blocking_percent: f64, + pub country: String, pub min_new_users_per_day: u32, pub max_new_users_per_day: u32, // How many days to simulate pub num_days: u32, pub prob_connection_fails: f64, - pub prob_friend_in_same_country: f64, pub prob_user_invites_friend: f64, pub prob_user_is_censor: f64, pub prob_user_submits_reports: f64, - pub probs_user_in_country: Vec<(String, f64)>, - pub sharing: bool, } #[tokio::main] @@ -82,27 +85,26 @@ pub async fn main() { let la_pubkeys = get_lox_auth_keys(&la_net).await; - let state = State { + let sconfig = SConfig { + la_pubkeys, la_net, tp_net, - la_pubkeys, + censor_hides: config.censor_hides, + censor_speed: config.censor_speed, + censor_event_duration: config.censor_event_duration, + censor_totality: config.censor_totality, + censor_partial_blocking_percent: config.censor_partial_blocking_percent, + country: config.country, prob_connection_fails: config.prob_connection_fails, - prob_friend_in_same_country: config.prob_friend_in_same_country, prob_user_invites_friend: config.prob_user_invites_friend, prob_user_is_censor: config.prob_user_is_censor, prob_user_submits_reports: config.prob_user_submits_reports, - probs_user_in_country: config.probs_user_in_country.clone(), - sharing: config.sharing, }; let mut rng = rand::thread_rng(); - // Set up censors - let mut censors = HashMap::::new(); - for i in 0..config.probs_user_in_country.len() { - let cc = config.probs_user_in_country[i].0.clone(); - censors.insert(cc.clone(), Censor::new(cc, Fast, Overt, Full)); - } + // Set up censor + let mut censor = Censor::new(&sconfig); // Set up bridges (no bridges yet) let mut bridges = HashMap::<[u8; 20], Bridge>::new(); @@ -129,15 +131,14 @@ pub async fn main() { let num_new_users: u32 = rng.gen_range(config.min_new_users_per_day..=config.max_new_users_per_day); for _ in 0..num_new_users { - users.push(User::new(&state).await); + users.push(User::new(&sconfig).await); } let mut new_users = Vec::::new(); // Users do daily actions for user in &mut users { - // TODO: Refactor out connections from return - let mut invited_friends = user.daily_tasks(&state, &mut bridges, &mut censors).await; + let mut invited_friends = user.daily_tasks(&sconfig, &mut bridges, &mut censor).await; // If this user invited any friends, add them to the list of users new_users.append(&mut invited_friends); @@ -147,15 +148,13 @@ pub async fn main() { users.append(&mut new_users); // CENSOR TASKS - for (_, censor) in censors.iter_mut() { - censor.end_of_day_tasks(&state, &mut bridges).await; - } + censor.end_of_day_tasks(&sconfig, &mut bridges).await; // BRIDGE TASKS let mut new_extra_infos = HashSet::::new(); for (_, bridge) in bridges.iter_mut() { // Bridge reports its connections for the day - new_extra_infos.insert(bridge.gen_extra_info()); + new_extra_infos.insert(bridge.gen_extra_info(&sconfig.country)); // Bridge resets for tomorrow bridge.reset_for_tomorrow(); @@ -178,11 +177,12 @@ pub async fn main() { for (bridge, ccs) in new_blockages { let fingerprint = array_bytes::hex2array(bridge).unwrap(); for cc in ccs { - let censor = censors.get(&cc).unwrap(); - if censor.knows_bridge(&fingerprint) { - tp += 1; - } else { - fp += 1; + if cc == sconfig.country { + if censor.knows_bridge(&fingerprint) { + tp += 1; + } else { + fp += 1; + } } } } diff --git a/src/lib.rs b/src/lib.rs index 2b6640d..f9aef21 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,8 +31,8 @@ pub mod request_handler; pub mod simulation { pub mod bridge; pub mod censor; + pub mod config; pub mod extra_infos_server; - pub mod state; pub mod user; } diff --git a/src/simulation/bridge.rs b/src/simulation/bridge.rs index 9d55589..6125adc 100644 --- a/src/simulation/bridge.rs +++ b/src/simulation/bridge.rs @@ -1,20 +1,20 @@ use crate::{extra_info::ExtraInfo, get_date}; use lox_library::bridge_table::BridgeLine; -use std::collections::{BTreeMap, HashMap}; +use std::collections::BTreeMap; // The Bridge struct only tracks data for today pub struct Bridge { pub fingerprint: [u8; 20], - real_connections: HashMap, - total_connections: BTreeMap, + real_connections: u32, + total_connections: u32, } impl Bridge { pub fn new(fingerprint: &[u8; 20]) -> Self { Self { fingerprint: *fingerprint, - real_connections: HashMap::::new(), - total_connections: BTreeMap::::new(), + real_connections: 0, + total_connections: 0, } } @@ -22,49 +22,38 @@ impl Bridge { Self::new(&bridgeline.get_hashed_fingerprint()) } - pub fn connect_real(&mut self, country: &str) { - if self.real_connections.contains_key(country) { - let prev = self.real_connections.get(country).unwrap(); - self.real_connections.insert(country.to_string(), prev + 1); - } else { - self.real_connections.insert(country.to_string(), 1); - } - self.connect_total(country); + pub fn connect_real(&mut self) { + self.real_connections += 1; + self.total_connections += 1; } - pub fn connect_total(&mut self, country: &str) { - if self.total_connections.contains_key(country) { - let prev = self.total_connections.get(country).unwrap(); - self.total_connections.insert(country.to_string(), prev + 1); - } else { - self.total_connections.insert(country.to_string(), 1); - } + pub fn connect_total(&mut self) { + self.total_connections += 1; } // Let the censor simulate a bunch of connections at once - pub fn censor_flood(&mut self, country: &str, num_connections: u32) { - if self.total_connections.contains_key(country) { - let prev = self.total_connections.get(country).unwrap(); - self.total_connections - .insert(country.to_string(), prev + num_connections); - } else { - self.total_connections - .insert(country.to_string(), num_connections); - } + pub fn censor_flood(&mut self, num_connections: u32) { + self.total_connections += num_connections; } // Generate an extra-info report for today - pub fn gen_extra_info(&self) -> ExtraInfo { + pub fn gen_extra_info(&self, country: &str) -> ExtraInfo { + let mut bridge_ips = BTreeMap::::new(); + // Round up to a multiple of 8 + let rounded_connection_count = + self.total_connections + 7 - (self.total_connections + 7) % 8; + //let rounded_connection_count = (self.total_connections + 7) / 8 * 8; + bridge_ips.insert(country.to_string(), rounded_connection_count); ExtraInfo { nickname: String::from("simulation-bridge"), fingerprint: self.fingerprint, date: get_date(), - bridge_ips: self.total_connections.clone(), + bridge_ips, } } pub fn reset_for_tomorrow(&mut self) { - self.real_connections = HashMap::::new(); - self.total_connections = BTreeMap::::new(); + self.real_connections = 0; + self.total_connections = 0; } } diff --git a/src/simulation/censor.rs b/src/simulation/censor.rs index f6e1b17..c17858d 100644 --- a/src/simulation/censor.rs +++ b/src/simulation/censor.rs @@ -1,59 +1,52 @@ use crate::{ get_date, - simulation::{bridge::Bridge, state::State}, + simulation::{bridge::Bridge, config::Config}, PositiveReport, }; use lox_cli::{get_lox_pub, networking::Networking}; use lox_library::{cred::Lox, scalar_u32}; use rand::Rng; +use serde::Deserialize; use std::collections::{HashMap, HashSet}; pub struct Censor { - pub country: String, pub known_bridges: HashSet<[u8; 20]>, - pub lox_credentials: HashMap<[u8; 20], Lox>, - // How fast does this censor block bridges after learning about them? - pub speed: Speed, + // We don't actually implement the technical restriction to prevent + // one Lox credential from being used to submit many reports, so we + // just implement this as a map of bridge fingerprint to (most + // recent Lox credential for this bridge, count of unique level 3+ + // credentials we have for this bridge). + pub lox_credentials: HashMap<[u8; 20], (Lox, u32)>, + // If censor implements random blocking, this is the date when it // will start blocking all the bridges it knows. pub delay_date: u32, - // Does the censor attempt to hide the fact that a bridge has been blocked? - pub hides: Hides, - - // Does the censor block bridges uniformly across the country? - pub totality: Totality, // If censor implements partial blocking, what percent of - // connections are blocked? If totality is not partial, this is set - // to 100%. + // connections are blocked? pub partial_blocking_percent: f64, } impl Censor { - pub fn new(country: String, speed: Speed, hides: Hides, totality: Totality) -> Self { + pub fn new(config: &Config) -> Self { let mut rng = rand::thread_rng(); - let delay_date = if speed == Speed::Random { + let delay_date = if config.censor_speed == Speed::Random { let num: u32 = rng.gen_range(1..365); get_date() + num } else { 0 }; - let partial_blocking_percent = if totality == Totality::Partial { - let num: f64 = rng.gen_range(0.0..1.0); - num + let partial_blocking_percent = if config.censor_totality == Totality::Partial { + config.censor_partial_blocking_percent } else { 1.0 }; Censor { - country: country, known_bridges: HashSet::<[u8; 20]>::new(), - lox_credentials: HashMap::<[u8; 20], Lox>::new(), - speed: speed, + lox_credentials: HashMap::<[u8; 20], (Lox, u32)>::new(), delay_date: delay_date, - hides: hides, - totality: totality, partial_blocking_percent: partial_blocking_percent, } } @@ -78,41 +71,52 @@ impl Censor { // We want to clone the credential, but that's not allowed, // so we're going to serialize it and then deserialize it. let cloned_cred = bincode::deserialize(&bincode::serialize(&cred).unwrap()).unwrap(); - self.lox_credentials.insert(*fingerprint, cloned_cred); + + // Insert the new credential and add to the count of unique + // credentials we have. We assume that a duplicate + // credential will never be given. If we don't want to make + // this assumption, we could change the count from a u32 to + // a set of credential IDs and get the count as its length. + let count = match self.lox_credentials.get(fingerprint) { + Some((_cred, count)) => *count, + None => 0, + }; + self.lox_credentials + .insert(*fingerprint, (cloned_cred, count + 1)); } } // Make a bunch of connections and submit positive reports if possible - async fn flood(&self, state: &State, bridges: &mut HashMap<[u8; 20], Bridge>) { + async fn flood(&self, config: &Config, bridges: &mut HashMap<[u8; 20], Bridge>) { // Only do this if Flooding censor - if self.hides == Hides::Flooding { + if config.censor_hides == Hides::Flooding { for fingerprint in &self.known_bridges { // Only do this if we're blocking the bridge - if self.speed == Speed::Fast - || self.speed == Speed::Lox && self.has_lox_cred(fingerprint) - || self.speed == Speed::Random && self.delay_date <= get_date() + if config.censor_speed == Speed::Fast + || config.censor_speed == Speed::Lox && self.has_lox_cred(fingerprint) + || config.censor_speed == Speed::Random && self.delay_date <= get_date() { let bridge = bridges.get_mut(fingerprint).unwrap(); let mut rng = rand::thread_rng(); let num_connections = rng.gen_range(1000..30000); // Make a bunch of connections to the bridge - bridge.censor_flood(&self.country, num_connections); + bridge.censor_flood(num_connections); // If we have a lv3+ credential, submit a bunch of // positive reports if self.has_lox_cred(fingerprint) { - let lox_pub = get_lox_pub(&state.la_pubkeys); + let lox_pub = get_lox_pub(&config.la_pubkeys); for _ in 0..num_connections { let pr = PositiveReport::from_lox_credential( bridge.fingerprint, None, - &self.lox_credentials.get(&bridge.fingerprint).unwrap(), + &self.lox_credentials.get(&bridge.fingerprint).unwrap().0, lox_pub, - self.country.clone(), + config.country.clone(), ) .unwrap(); - state + config .tp_net .request("/positivereport".to_string(), pr.to_json().into_bytes()) .await; @@ -123,11 +127,11 @@ impl Censor { } } - // TODO: How do we want to do this? We don't want to stop blocking - // bridges the day after we start. - fn recompute_delay(&mut self) { + fn recompute_delay(&mut self, config: &Config) { // Only do this if Random censor - if self.speed == Speed::Random && self.delay_date <= get_date() { + if config.censor_speed == Speed::Random + && self.delay_date + config.censor_event_duration <= get_date() + { // Compute new delay date self.delay_date = { let mut rng = rand::thread_rng(); @@ -139,35 +143,34 @@ impl Censor { pub async fn end_of_day_tasks( &mut self, - state: &State, + config: &Config, bridges: &mut HashMap<[u8; 20], Bridge>, ) { - if self.hides == Hides::Flooding - && !(self.speed == Speed::Random && self.delay_date <= get_date()) + if config.censor_hides == Hides::Flooding + && !(config.censor_speed == Speed::Random && self.delay_date <= get_date()) { - self.flood(state, bridges).await; + self.flood(config, bridges).await; } - // TODO: recompute_delay sometimes - //self.recompute_delay(); + self.recompute_delay(config); } } -#[derive(PartialEq)] +#[derive(Debug, Deserialize, PartialEq)] pub enum Speed { Fast, Lox, Random, } -#[derive(PartialEq)] +#[derive(Debug, Deserialize, PartialEq)] pub enum Hides { Overt, Hiding, Flooding, } -#[derive(PartialEq)] +#[derive(Debug, Deserialize, PartialEq)] pub enum Totality { Full, Partial, diff --git a/src/simulation/config.rs b/src/simulation/config.rs new file mode 100644 index 0000000..01b63d6 --- /dev/null +++ b/src/simulation/config.rs @@ -0,0 +1,25 @@ +use crate::simulation::censor; + +use lox_cli::networking::*; +use lox_library::IssuerPubKey; + +pub struct Config { + pub la_pubkeys: Vec, + pub la_net: HyperNet, + pub tp_net: HyperNet, + // Define censor behavior + pub censor_hides: censor::Hides, + pub censor_speed: censor::Speed, + pub censor_event_duration: u32, + pub censor_totality: censor::Totality, + pub censor_partial_blocking_percent: f64, + // We model only one country at a time because Lox assumes censors + // share information with each other. + pub country: String, + // Probability that a connection randomly fails, even though censor + // does not block the bridge + pub prob_connection_fails: f64, + pub prob_user_invites_friend: f64, + pub prob_user_is_censor: f64, + pub prob_user_submits_reports: f64, +} diff --git a/src/simulation/state.rs b/src/simulation/state.rs deleted file mode 100644 index 994bba2..0000000 --- a/src/simulation/state.rs +++ /dev/null @@ -1,21 +0,0 @@ -use lox_cli::networking::*; -use lox_library::IssuerPubKey; - -pub struct State { - pub la_pubkeys: Vec, - pub la_net: HyperNet, - pub tp_net: HyperNet, - // Probability that a connection randomly fails, even though censor - // does not block the bridge - pub prob_connection_fails: f64, - // Probability that if Alice invites Bob, Alice and Bob are in the same - // country. This is in *addition* to the regular probability that Bob is in - // that country by random selection. - pub prob_friend_in_same_country: f64, - pub prob_user_invites_friend: f64, - pub prob_user_is_censor: f64, - pub prob_user_submits_reports: f64, - pub probs_user_in_country: Vec<(String, f64)>, - // Do the censors talk to each other? - pub sharing: bool, -} diff --git a/src/simulation/user.rs b/src/simulation/user.rs index a27d5c5..556f6ef 100644 --- a/src/simulation/user.rs +++ b/src/simulation/user.rs @@ -7,9 +7,9 @@ use crate::{ simulation::{ bridge::Bridge, censor::{Censor, Hides::*, Speed::*, Totality::*}, - state::State, + config::Config, }, - BridgeDistributor, COUNTRY_CODES, + BridgeDistributor, }; use lox_cli::{networking::*, *}; use lox_library::{ @@ -28,10 +28,7 @@ pub fn event_happens(probability: f64) -> bool { pub struct User { // Does this user cooperate with a censor? - censor: bool, - - // 2-character country code - country: String, + 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 @@ -47,46 +44,32 @@ pub struct User { } impl User { - pub async fn new(state: &State) -> Self { + pub async fn new(config: &Config) -> Self { let cred = get_lox_credential( - &state.la_net, - &get_open_invitation(&state.la_net).await, - get_lox_pub(&state.la_pubkeys), + &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 censor = event_happens(state.prob_user_is_censor); + let is_censor = event_happens(config.prob_user_is_censor); // Probabilistically decide whether this user submits reports - let submits_reports = event_happens(state.prob_user_submits_reports); - - // Probabilistically decide user's country - let mut rng = rand::thread_rng(); - let mut num: f64 = rng.gen_range(0.0..1.0); - let cc = { - let mut cc = String::default(); - for (country, prob) in &state.probs_user_in_country { - let prob = *prob; - if num < prob { - cc = country.to_string(); - break; - } else { - num -= prob; - } - } - cc + let submits_reports = if is_censor { + false + } else { + event_happens(config.prob_user_submits_reports) }; - assert!(COUNTRY_CODES.contains(cc.as_str())); // 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 { - censor: censor, - country: cc, + is_censor, primary_cred: cred, secondary_cred: None, submits_reports: submits_reports, @@ -95,55 +78,50 @@ impl User { } // TODO: This should probably return an actual error type - pub async fn invite(&mut self, state: &State) -> Result { - let etable = get_reachability_credential(&state.la_net).await; + 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( - &state.la_net, + &config.la_net, &self.primary_cred, &etable, - get_lox_pub(&state.la_pubkeys), - get_reachability_pub(&state.la_pubkeys), - get_invitation_pub(&state.la_pubkeys), + 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( - &state.la_net, + &config.la_net, &invite, - get_lox_pub(&state.la_pubkeys), - get_invitation_pub(&state.la_pubkeys), + get_lox_pub(&config.la_pubkeys), + get_invitation_pub(&config.la_pubkeys), ) .await .0; - // Probabilistically decide whether this user cooperates with a censor - // We do not influence this by the inviting friend's status. Anyone - // might have friends who are untrustworthy, and censors may invite - // non-censors to maintain an illusion of trustworthiness. Also, a - // "censor" user may not be knowingly helping a censor. - let censor = event_happens(state.prob_user_is_censor); + // 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 = event_happens(state.prob_user_submits_reports); - - // Determine user's country - let cc = if event_happens(state.prob_friend_in_same_country) { - self.country.to_string() + let submits_reports = if is_censor { + false } else { - // Probabilistically decide user's country - let mut rng = rand::thread_rng(); - let mut num: f64 = rng.gen_range(0.0..1.0); - let mut cc = String::default(); - for (country, prob) in &state.probs_user_in_country { - let prob = *prob; - if num < prob { - cc = country.to_string(); - break; - } else { - num -= prob; - } - } - cc + event_happens(config.prob_user_submits_reports) }; // Randomly determine how likely this user is to use bridges on @@ -152,8 +130,7 @@ impl User { let prob_use_bridges = rng.gen_range(0.0..=1.0); Ok(Self { - censor: censor, - country: cc, + is_censor, primary_cred: friend_cred, secondary_cred: None, submits_reports: submits_reports, @@ -162,21 +139,22 @@ impl User { } // Attempt to "connect" to the bridge, returns true if successful - pub fn connect(&self, state: &State, bridge: &mut Bridge, censor: &Censor) -> bool { + pub fn connect(&self, config: &Config, bridge: &mut Bridge, censor: &Censor) -> bool { if censor.knows_bridge(&bridge.fingerprint) { - if censor.speed == Fast - || censor.speed == Random && censor.delay_date <= get_date() - || censor.speed == Lox && censor.has_lox_cred(&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 censor.totality == Full - || censor.totality == Partial && event_happens(censor.partial_blocking_percent) - || censor.totality == Throttling + 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 censor.hides == Hiding || censor.totality == Throttling { - bridge.connect_total(&self.country); + if config.censor_hides == Hiding || config.censor_totality == Throttling { + bridge.connect_total(); } // Return false because the connection failed @@ -186,19 +164,19 @@ impl User { } // Connection may randomly fail, without censor intervention - if event_happens(state.prob_connection_fails) { + if event_happens(config.prob_connection_fails) { return false; } // If we haven't returned yet, the connection succeeded - bridge.connect_real(&self.country); + bridge.connect_real(); true } - pub async fn send_negative_reports(state: &State, reports: Vec) { + pub async fn send_negative_reports(config: &Config, reports: Vec) { let date = get_date(); let pubkey = serde_json::from_slice::>( - &state + &config .tp_net .request( "/nrkey".to_string(), @@ -209,7 +187,7 @@ impl User { .unwrap() .unwrap(); for report in reports { - state + config .tp_net .request( "/negativereport".to_string(), @@ -219,32 +197,44 @@ impl User { } } - pub async fn send_positive_reports(state: &State, reports: Vec) { + pub async fn send_positive_reports(config: &Config, reports: Vec) { for report in reports { - state + config .tp_net .request("/positivereport".to_string(), report.to_json().into_bytes()) .await; } } - // User performs daily connection attempts, etc. and returns a - // vector of newly invited friends and a vector of fingerprints of - // successfully contacted bridges. - // TODO: The maps of bridges and censors should be Arc> or - // something so we can parallelize this. pub async fn daily_tasks( &mut self, - state: &State, + config: &Config, bridges: &mut HashMap<[u8; 20], Bridge>, - censors: &mut HashMap, + 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(&state.la_net, &self.primary_cred).await; + 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 @@ -254,30 +244,15 @@ impl User { let bridge = Bridge::from_bridge_line(&bridgeline); bridges.insert(bridgeline.get_hashed_fingerprint(), bridge); } - // Also, if this user cooperates with censors, make sure - // each applicable censor knows about their bridges. - if self.censor { - if state.sharing { - for c in censors.values_mut() { - if !c.knows_bridge(&bridgeline.get_hashed_fingerprint()) { - c.learn_bridge(&bridgeline.get_hashed_fingerprint()); - } - } - } else { - let censor = censors.get_mut(&self.country).unwrap(); - if !censor.knows_bridge(&bridgeline.get_hashed_fingerprint()) { - censor.learn_bridge(&bridgeline.get_hashed_fingerprint()); - } - } - } } } // Can we level up the main credential? let can_level_up = reachcred.is_some() && (level == 0 - && eligible_for_trust_promotion(&state.la_net, &self.primary_cred).await - || level > 0 && eligible_for_level_up(&state.la_net, &self.primary_cred).await); + && 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; @@ -285,18 +260,18 @@ impl User { // Can we level up the secondary credential? let mut second_level_up = false; - // Attempt to connect to each bridge 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( - &state, + &config, bridges .get_mut(&bucket[i].get_hashed_fingerprint()) .unwrap(), - &censors.get(&self.country).unwrap(), + &censor, ) { succeeded.push(bucket[i]); } else { @@ -304,15 +279,18 @@ impl User { } } } + + // 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( - &state.la_net, - &get_open_invitation(&state.la_net).await, - get_lox_pub(&state.la_pubkeys), + &config.la_net, + &get_open_invitation(&config.la_net).await, + get_lox_pub(&config.la_pubkeys), ) .await .0; @@ -326,7 +304,7 @@ impl User { if second_cred.is_some() { let second_cred = second_cred.as_ref().unwrap(); let (second_bucket, second_reachcred) = - get_bucket(&state.la_net, &second_cred).await; + 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()) { @@ -335,16 +313,17 @@ impl User { Bridge::from_bridge_line(&bridgeline), ); } + // Attempt to connect to second cred's bridge if self.connect( - &state, + &config, bridges .get_mut(&bridgeline.get_hashed_fingerprint()) .unwrap(), - &censors.get(&self.country).unwrap(), + censor, ) { succeeded.push(bridgeline); if second_reachcred.is_some() - && eligible_for_trust_promotion(&state.la_net, &second_cred).await + && eligible_for_trust_promotion(&config.la_net, &second_cred).await { second_level_up = true; } @@ -357,11 +336,12 @@ impl User { 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, - self.country.to_string(), + config.country.to_string(), BridgeDistributor::Lox, )); } @@ -372,8 +352,8 @@ impl User { bridgeline.get_hashed_fingerprint(), None, &self.primary_cred, - get_lox_pub(&state.la_pubkeys), - self.country.to_string(), + get_lox_pub(&config.la_pubkeys), + config.country.to_string(), ) .unwrap(), ); @@ -385,54 +365,68 @@ impl User { // we do assume the user can contact the LA somehow, so // let's just allow it. if can_level_up { - let cred = level_up( - &state.la_net, - &self.primary_cred, - &reachcred.unwrap(), - get_lox_pub(&state.la_pubkeys), - get_reachability_pub(&state.la_pubkeys), - ) - .await; + 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; - - if self.censor { - // Make sure censor has access to each bridge and - // each credential - let censor = censors.get_mut(&self.country).unwrap(); - let (bucket, reachcred) = get_bucket(&state.la_net, &self.primary_cred).await; - for bl in bucket { - censor.learn_bridge(&bl.get_hashed_fingerprint()); - censor.give_lox_cred(&bl.get_hashed_fingerprint(), &self.primary_cred); - } - } } - // We favor starting over at level 1 to migrating - else if second_level_up { + // 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( - &state.la_net, + &config.la_net, &second_cred, - &trust_promotion(&state.la_net, &second_cred, get_lox_pub(&state.la_pubkeys)) - .await, - get_lox_pub(&state.la_pubkeys), - get_migration_pub(&state.la_pubkeys), + &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( - &state.la_net, + &config.la_net, &self.primary_cred, &check_blockage( - &state.la_net, + &config.la_net, &self.primary_cred, - get_lox_pub(&state.la_pubkeys), + get_lox_pub(&config.la_pubkeys), ) .await, - get_lox_pub(&state.la_pubkeys), - get_migration_pub(&state.la_pubkeys), + get_lox_pub(&config.la_pubkeys), + get_migration_pub(&config.la_pubkeys), ) .await; self.primary_cred = cred; @@ -446,18 +440,18 @@ impl User { } if negative_reports.len() > 0 { - Self::send_negative_reports(&state, negative_reports).await; + Self::send_negative_reports(&config, negative_reports).await; } if positive_reports.len() > 0 { - Self::send_positive_reports(&state, positive_reports).await; + 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(state.prob_user_invites_friend) { - match self.invite(&state).await { + 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 @@ -476,4 +470,127 @@ impl User { 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 + } }