diff --git a/src/lib.rs b/src/lib.rs index 6a273eb..7294170 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,8 @@ pub mod request_handler; #[cfg(feature = "simulation")] pub mod simulation { + pub mod bridge; + pub mod censor; pub mod extra_infos_server; pub mod state; pub mod user; diff --git a/src/simulation/bridge.rs b/src/simulation/bridge.rs new file mode 100644 index 0000000..1b6c4b1 --- /dev/null +++ b/src/simulation/bridge.rs @@ -0,0 +1,49 @@ +use lox_library::bridge_table::BridgeLine; +use std::collections::HashMap; + +pub struct Bridge { + pub fingerprint: [u8; 20], + real_connections: HashMap, + total_connections: HashMap, +} + +impl Bridge { + pub fn new(fingerprint: &[u8; 20]) -> Self { + Self { + fingerprint: *fingerprint, + real_connections: HashMap::::new(), + total_connections: HashMap::::new(), + } + } + + pub fn from_bridge_line(bridgeline: &BridgeLine) -> Self { + Self::new(&bridgeline.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) + .unwrap(); + } else { + self.real_connections + .insert(country.to_string(), 1) + .unwrap(); + } + self.connect_total(country); + } + + 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) + .unwrap(); + } else { + self.total_connections + .insert(country.to_string(), 1) + .unwrap(); + } + } +} diff --git a/src/simulation/censor.rs b/src/simulation/censor.rs new file mode 100644 index 0000000..4d96245 --- /dev/null +++ b/src/simulation/censor.rs @@ -0,0 +1,99 @@ +use crate::{get_date, simulation::bridge::Bridge}; + +use lox_library::{cred::Lox, scalar_u32}; +use rand::Rng; +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, + // 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%. + pub partial_blocking_percent: f64, +} + +impl Censor { + pub fn new(country: String, speed: Speed, hides: Hides, totality: Totality) -> Self { + let mut rng = rand::thread_rng(); + let delay_date = if 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 + } else { + 1.0 + }; + Censor { + country: country, + known_bridges: HashSet::<[u8; 20]>::new(), + lox_credentials: HashMap::<[u8; 20], Lox>::new(), + speed: speed, + delay_date: delay_date, + hides: hides, + totality: totality, + partial_blocking_percent: partial_blocking_percent, + } + } + + pub fn knows_bridge(&self, bridge: &Bridge) -> bool { + self.known_bridges.contains(&bridge.fingerprint) + } + + pub fn learn_bridge(&mut self, fingerprint: &[u8; 20]) { + self.known_bridges.insert(*fingerprint); + } + + pub fn has_lox_cred(&self, bridge: &Bridge) -> bool { + self.lox_credentials.contains_key(&bridge.fingerprint) + } + + pub fn give_lox_cred(&mut self, bridge: &Bridge, cred: Lox) { + // We only need one level 3+ credential per bridge. (This will + // change if we restrict positive reports to one per bridge per + // credential.) + if !self.lox_credentials.contains_key(&bridge.fingerprint) + && scalar_u32(&cred.trust_level).unwrap() >= 3 + { + self.lox_credentials.insert(bridge.fingerprint, cred); + } + } +} + +#[derive(PartialEq)] +pub enum Speed { + Fast, + Lox, + Random, +} + +#[derive(PartialEq)] +pub enum Hides { + Overt, + Hiding, + Flooding, +} + +#[derive(PartialEq)] +pub enum Totality { + Full, + Partial, + Throttling, +} diff --git a/src/simulation/state.rs b/src/simulation/state.rs index 3e893f4..88842a6 100644 --- a/src/simulation/state.rs +++ b/src/simulation/state.rs @@ -6,6 +6,9 @@ pub struct State { pub net: HyperNet, pub net_test: HyperNet, pub net_tp: 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. diff --git a/src/simulation/user.rs b/src/simulation/user.rs index 84374eb..e255c59 100644 --- a/src/simulation/user.rs +++ b/src/simulation/user.rs @@ -1,16 +1,31 @@ // User behavior in simulation use crate::{ - get_date, negative_report::NegativeReport, positive_report::PositiveReport, - simulation::state::State, BridgeDistributor, + get_date, + negative_report::NegativeReport, + positive_report::PositiveReport, + simulation::{ + bridge::Bridge, + censor::{Censor, Hides::*, Speed::*, Totality::*}, + state::State, + }, + 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? censor: bool, @@ -42,15 +57,13 @@ impl User { .0; // Probabilistically decide whether this user cooperates with a censor - let mut rng = rand::thread_rng(); - let num: f64 = rng.gen_range(0.0..1.0); - let censor = num < state.prob_user_is_censor; + let censor = event_happens(state.prob_user_is_censor); // Probabilistically decide whether this user submits reports - let num: f64 = rng.gen_range(0.0..1.0); - let submits_reports = num < state.prob_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(); @@ -107,20 +120,17 @@ impl User { // 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 mut rng = rand::thread_rng(); - let num: f64 = rng.gen_range(0.0..1.0); - let censor = num < state.prob_user_is_censor; + let censor = event_happens(state.prob_user_is_censor); // Probabilistically decide whether this user submits reports - let num: f64 = rng.gen_range(0.0..1.0); - let submits_reports = num < state.prob_user_submits_reports; + let submits_reports = event_happens(state.prob_user_submits_reports); // Determine user's country - let num: f64 = rng.gen_range(0.0..1.0); - let cc = if num < state.prob_friend_in_same_country { + let cc = if event_happens(state.prob_friend_in_same_country) { self.country.to_string() } 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 { @@ -137,6 +147,7 @@ impl User { // 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 { @@ -150,7 +161,36 @@ impl User { } // Attempt to "connect" to the bridge, returns true if successful - pub fn connect(&self, bridge: &BridgeLine) -> bool { + pub fn connect(&self, state: &State, bridge: &mut Bridge, censor: &Censor) -> bool { + if censor.knows_bridge(bridge) { + if censor.speed == Fast + || censor.speed == Random && censor.delay_date <= get_date() + || censor.speed == Lox && censor.has_lox_cred(bridge) + { + if censor.totality == Full + || censor.totality == Partial && event_happens(censor.partial_blocking_percent) + || 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); + } + + // Return false because the connection failed + return false; + } + } + } + + // Connection may randomly fail, without censor intervention + if event_happens(state.prob_connection_fails) { + return false; + } + + // If we haven't returned yet, the connection succeeded + bridge.connect_real(&self.country); true } @@ -188,20 +228,36 @@ impl User { } } - // User performs daily connection attempts, etc. and returns a vector of - // newly invited friends and a vector of fingerprints of successfully - // contacted bridges. - pub async fn daily_tasks(&mut self, state: &State) -> (Vec, Vec<[u8; 20]>) { + // 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, + bridges: &mut HashMap<[u8; 20], Bridge>, + censors: &mut HashMap, + ) -> (Vec, Vec<[u8; 20]>) { + let censor = censors.get(&self.country).unwrap(); + // Probabilistically decide if the user should use bridges today - let mut rng = rand::thread_rng(); - let num: f64 = rng.gen_range(0.0..1.0); - if num < self.prob_use_bridges { + 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.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 bridge in bucket { + if !bridges.contains_key(&bridge.fingerprint) { + bridges + .insert(bridge.fingerprint, Bridge::from_bridge_line(&bridge)) + .unwrap(); + } + } + // Can we level up the main credential? let can_level_up = reachcred.is_some() && (level == 0 @@ -219,7 +275,11 @@ impl User { for i in 0..bucket.len() { // At level 0, we only have 1 bridge if level > 0 || i == 0 { - if self.connect(&bucket[i]) { + if self.connect( + &state, + bridges.get_mut(&bucket[i].fingerprint).unwrap(), + &censor, + ) { succeeded.push(bucket[i]); } else { failed.push(bucket[i]); @@ -248,7 +308,19 @@ impl User { if second_cred.is_some() { let second_cred = second_cred.as_ref().unwrap(); let (second_bucket, second_reachcred) = get_bucket(&state.net, &second_cred).await; - if self.connect(&second_bucket[0]) { + if !bridges.contains_key(&second_bucket[0].fingerprint) { + bridges + .insert( + second_bucket[0].fingerprint, + Bridge::from_bridge_line(&second_bucket[0]), + ) + .unwrap(); + } + if self.connect( + &state, + bridges.get_mut(&second_bucket[0].fingerprint).unwrap(), + &censor, + ) { succeeded.push(second_bucket[0]); if second_reachcred.is_some() && eligible_for_trust_promotion(&state.net, &second_cred).await @@ -349,9 +421,7 @@ impl User { let invitations = scalar_u32(&self.primary_cred.invites_remaining).unwrap(); let mut new_friends = Vec::::new(); for _i in 0..invitations { - let mut rng = rand::thread_rng(); - let num: f64 = rng.gen_range(0.0..1.0); - if num < state.prob_user_invites_friend { + if event_happens(state.prob_user_invites_friend) { match self.invite(&state).await { Ok(friend) => { // You really shouldn't push your friends, especially