123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011 |
- // 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::{Invitation, Lox},
- proto::check_blockage::MIN_TRUST_LEVEL,
- scalar_u32,
- };
- use rand::Rng;
- use std::collections::{HashMap, HashSet};
- 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
- }
- // Make sure each bridge is in global bridges set and known by censor
- pub fn give_bucket_to_censor(
- bucket: &[BridgeLine],
- bridges: &mut HashMap<[u8; 20], Bridge>,
- censor: &mut 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);
- }
- }
- }
- // Check if bucket is blocked, according to the LA (regardless of
- // whether the censor actually blocks the bridges)
- pub fn bucket_blocked_lox(bucket: &[BridgeLine], bridges: &HashMap<[u8; 20], Bridge>) -> bool {
- // Count number of non-default bridges (either 1 or 3)
- let mut num_real_bridges = 0;
- let mut num_blocked_bridges = 0;
- for bridge in bucket {
- if *bridge != BridgeLine::default() {
- match bridges.get(&bridge.get_hashed_fingerprint()) {
- Some(b) => {
- num_real_bridges += 1;
- if b.troll_patrol_blocked() {
- num_blocked_bridges += 1;
- }
- }
- None => {
- // Something went wrong, I guess
- println!(
- "Tried to check if bridge was blocked before it was added to simulation"
- );
- }
- }
- }
- }
- // Return true if open-entry bucket with a blocked bridge or
- // invite-only bucket with 2+ blocked bridges
- num_real_bridges == 1 && num_blocked_bridges == 1
- || num_real_bridges == 3 && num_blocked_bridges >= 2
- }
- // Check if bucket contains a bridge that has ever been distributed to a
- // real user
- pub fn bucket_has_been_distributed(
- bucket: &[BridgeLine],
- bridges: &HashMap<[u8; 20], Bridge>,
- ) -> bool {
- for bridge in bucket {
- if *bridge != BridgeLine::default() {
- match bridges.get(&bridge.get_hashed_fingerprint()) {
- Some(b) => {
- if b.has_been_distributed() {
- return true;
- }
- }
- None => {
- // Something went wrong, I guess
- println!(
- "Tried to check if bridge had been distributed before it was added to simulation"
- );
- }
- }
- }
- }
- false
- }
- 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<Lox>,
- // Does the user submit reports to Troll Patrol?
- submits_reports: bool,
- // How likely is this user to use bridges on a given day? This has
- // been converted to a global parameter (prob_user_connects), but we
- // still leave the user implementation for now in case we want to
- // switch back to it.
- 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,
- // Attempted to connect today
- pub attempted_connection: bool,
- }
- impl User {
- pub async fn new(
- config: &Config,
- is_censor: bool,
- bridges: &mut HashMap<[u8; 20], Bridge>,
- censor: &mut Censor,
- ) -> Result<Self> {
- if censor.is_active() && !is_censor {
- return Err(anyhow!("Censor is active; open invites disabled"));
- }
- 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 {
- // Use global value
- let prob_use_bridges = config.prob_user_connects;
- 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 result = 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: false,
- attempted_connection: true,
- };
- // Immediately download and try to use our bridges, or report them to the censor.
- let (bucket, _reachcred) = get_bucket(&config.la_net, &result.primary_cred).await?;
- if is_censor {
- // Give bridges to censor
- give_bucket_to_censor(&bucket, bridges, censor);
- // Various conditions to avoid creating censor users if we don't need to
- // If the bucket is already marked blocked, don't create this user
- if bucket_blocked_lox(&bucket, bridges) {
- return Err(anyhow!("Bucket blocked, don't create new censor user"));
- }
- // If the bucket has never been distributed to a real user, don't create this user
- if !bucket_has_been_distributed(&bucket, bridges) {
- return Err(anyhow!(
- "Bucket never distributed to a real user, don't create new censor user"
- ));
- }
- // If we already have enough agents with this bucket, don't create this user
- if censor.num_agents(&result.primary_cred.bucket) > config.censor_max_pr {
- return Err(anyhow!(
- "We already have enough censor users with this bucket"
- ));
- }
- // If we haven't returned yet, add this user to censor's list
- censor.add_agent(&result.primary_cred.bucket);
- } else {
- // Test bridges to see if they work
- let (s, f) = result.test_bridges(&bucket, config, bridges, censor);
- // Our bridge worked! Yay!
- if !s.is_empty() {
- result.able_to_connect = true;
- }
- // Report any failures if we submit reports
- if submits_reports {
- for bl in f {
- let mut negative_reports = Vec::<NegativeReport>::new();
- negative_reports.push(NegativeReport::from_bridgeline(
- bl,
- config.country.to_string(),
- BridgeDistributor::Lox,
- ));
- Self::send_negative_reports(&config, negative_reports).await?;
- }
- }
- }
- // Return our new user
- Ok(result)
- }
- // Get an invite if able
- pub async fn get_invite(
- &mut self,
- config: &Config,
- bridges: &mut HashMap<[u8; 20], Bridge>,
- censor: &mut Censor,
- ) -> Result<Invitation> {
- 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 user cooperates with censor, give bridges and credential
- // now. Otherwise, wait until the user actually wants to use
- // Tor.
- if self.is_censor {
- let (bucket, _reachcred) = get_bucket(&config.la_net, &self.primary_cred).await?;
- give_bucket_to_censor(&bucket, bridges, censor);
- }
- Ok(invite)
- }
- pub async fn from_invite(
- invite: Invitation,
- config: &Config,
- is_censor: bool,
- bridges: &mut HashMap<[u8; 20], Bridge>,
- censor: &mut Censor,
- ) -> Result<Self> {
- let cred = redeem_invite(
- &config.la_net,
- &invite,
- get_lox_pub(&config.la_pubkeys),
- get_invitation_pub(&config.la_pubkeys),
- )
- .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 {
- // Use global value
- let prob_use_bridges = config.prob_user_connects;
- 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 result = 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: false,
- attempted_connection: true,
- };
- let (bucket, _reachcred) = get_bucket(&config.la_net, &result.primary_cred).await?;
- if is_censor {
- give_bucket_to_censor(&bucket, bridges, censor);
- // Various conditions to avoid creating censor users unnecessarily
- // If the bucket is already marked blocked, don't create this user
- if bucket_blocked_lox(&bucket, bridges) {
- return Err(anyhow!("Bucket blocked, don't create new censor user"));
- }
- // If we already have enough agents with this bucket, don't create this user
- if censor.num_agents(&result.primary_cred.bucket) > config.censor_max_pr {
- return Err(anyhow!(
- "We already have enough censor users with this bucket"
- ));
- }
- // If we haven't returned yet, add this user to censor's list
- censor.add_agent(&result.primary_cred.bucket);
- } else {
- let (s, f) = result.test_bridges(&bucket, config, bridges, censor);
- // Our bridge worked! Yay!
- if !s.is_empty() {
- result.able_to_connect = true;
- }
- // Report any failures if we submit reports
- if submits_reports {
- for bl in f {
- let mut negative_reports = Vec::<NegativeReport>::new();
- negative_reports.push(NegativeReport::from_bridgeline(
- bl,
- config.country.to_string(),
- BridgeDistributor::Lox,
- ));
- Self::send_negative_reports(&config, negative_reports).await?;
- }
- }
- }
- Ok(result)
- }
- // 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
- }
- // Given a Lox credential, download its bucket, test its bridges, and
- // return sets of working and non-working bridges
- pub fn test_bridges(
- &mut self,
- bucket: &[BridgeLine],
- config: &Config,
- bridges: &mut HashMap<[u8; 20], Bridge>,
- censor: &Censor,
- ) -> (HashSet<BridgeLine>, HashSet<BridgeLine>) {
- let mut failed = HashSet::<BridgeLine>::new();
- let mut succeeded = HashSet::<BridgeLine>::new();
- for bridgeline in bucket {
- if *bridgeline != BridgeLine::default() {
- let fingerprint = bridgeline.get_hashed_fingerprint();
- // Make sure bridge is in the global set
- 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();
- }
- // Attempt to connect to the bridge
- if Self::connect(
- self.in_censorship_range,
- config,
- bridges
- .get_mut(&bridgeline.get_hashed_fingerprint())
- .unwrap(),
- censor,
- ) {
- self.able_to_connect = true;
- succeeded.insert(*bridgeline);
- } else {
- failed.insert(*bridgeline);
- }
- }
- }
- (succeeded, failed)
- }
- 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<NegativeReport>,
- ) -> Result<()> {
- let date = get_date();
- let pubkey = match serde_json::from_slice::<Option<PublicKey>>(
- &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<PositiveReport>,
- ) -> 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,
- bridges: &mut HashMap<[u8; 20], Bridge>,
- censor: &mut Censor,
- invites: &mut Vec<Invitation>,
- ) -> Result<(Vec<User>, bool)> {
- if self.is_censor {
- self.daily_tasks_censor(config, bridges, censor).await
- } else {
- match self
- .daily_tasks_non_censor(config, bridges, censor, invites)
- .await
- {
- Ok(users) => Ok((users, false)),
- Err(e) => Err(e),
- }
- }
- }
- // 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<Mutex<>>
- // 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,
- invites: &mut Vec<Invitation>,
- ) -> Result<Vec<User>> {
- // Probabilistically decide if the user should use bridges today
- if event_happens(self.prob_use_bridges) {
- self.attempted_connection = true;
- // 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")),
- };
- // Can we level up the main credential?
- let can_level_up = level == 0
- && eligible_for_trust_promotion(&config.la_net, &self.primary_cred).await
- || reachcred.is_some()
- && 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;
- // Track which connections succeeded and which failed. We use sets
- // instead of vectors for deduplication in case we test a bridge
- // multiple times.
- let mut succeeded = HashSet::<BridgeLine>::new();
- let mut failed = HashSet::<BridgeLine>::new();
- let (s, f) = self.test_bridges(&bucket, config, bridges, censor);
- // Add working bridges to succeeded set
- for b in s {
- succeeded.insert(b);
- }
- // Add non-working bridges to failed set
- for b in f {
- failed.insert(b);
- }
- // If we were not able to connect to any bridges but also
- // cannot migrate, ask for a new invitation
- if !self.able_to_connect && !can_migrate {
- while let Some(invite) = invites.pop() {
- match redeem_invite(
- &config.la_net,
- &invite,
- get_lox_pub(&config.la_pubkeys),
- get_invitation_pub(&config.la_pubkeys),
- )
- .await
- {
- Ok((cred, bucket)) => {
- self.primary_cred = cred;
- // Test our bridges
- let (s, f) = self.test_bridges(&bucket, config, bridges, censor);
- for b in s {
- succeeded.insert(b);
- }
- for b in f {
- failed.insert(b);
- }
- // We got a credential. Stop now.
- break;
- }
- Err(e) => {
- println!("Failed to redeem invite. Error: {}", e);
- }
- }
- }
- }
- // If we were still not able to connect to any bridges, get
- // a second credential
- let second_cred = if !self.able_to_connect {
- if self.secondary_cred.is_some() {
- std::mem::replace(&mut self.secondary_cred, None)
- } else {
- // If the censor is in play, we cannot get open-entry invites
- if censor.is_active() {
- 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();
- // Test our bridges
- let (second_bucket, second_reachcred) =
- get_bucket(&config.la_net, &second_cred).await?;
- let (s, f) = self.test_bridges(&second_bucket, config, bridges, censor);
- // Add each working bridge to succeeded set
- for b in s {
- succeeded.insert(b);
- }
- // Add each non-working bridge to failed set
- for b in f {
- failed.insert(b);
- }
- // If we're able to connect, see if we can level up
- if self.able_to_connect {
- if second_reachcred.is_some()
- && eligible_for_trust_promotion(&config.la_net, &second_cred).await
- {
- second_level_up = true;
- }
- }
- }
- let mut negative_reports = Vec::<NegativeReport>::new();
- let mut positive_reports = Vec::<PositiveReport>::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();
- }
- if let Ok(pr) = PositiveReport::from_lox_credential(
- fingerprint,
- None,
- &self.primary_cred,
- get_lox_pub(&config.la_pubkeys),
- config.country.to_string(),
- ) {
- positive_reports.push(pr)
- }
- }
- }
- }
- // 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 {
- // Trust migration from level 0 to level 1
- 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 from level 1+
- level_up(
- &config.la_net,
- &self.primary_cred,
- &reachcred.unwrap(), // must be Some
- get_lox_pub(&config.la_pubkeys),
- get_reachability_pub(&config.la_pubkeys),
- )
- .await?
- };
- self.primary_cred = cred;
- self.secondary_cred = None;
- } else if can_migrate {
- // If we can't level up, try to 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_level_up {
- // If we can't migrate, try to level up our secondary
- // credential
- 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 second_cred.is_some() {
- // Couldn't connect with primary credential
- if self.able_to_connect {
- // Keep the second credential only if it's useful
- self.secondary_cred = second_cred;
- }
- }
- if !negative_reports.is_empty() {
- Self::send_negative_reports(&config, negative_reports).await?;
- }
- if !positive_reports.is_empty() {
- 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
- };
- // It's just more convenient in the code to do this this way.
- // Invite censors directly as new users. If the invited user is
- // not a censor, instead add the invitation to a global list.
- let mut new_censors = Vec::<User>::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.25,
- Some(3) => 0.5,
- Some(2) => 1.0,
- _ => 1.0, // should not have invitations
- };
- for _ in 0..invitations {
- if event_happens(config.prob_user_invites_friend) {
- match self.get_invite(config, bridges, censor).await {
- Ok(invite) => {
- // 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.
- if censor.is_active()
- && event_happens(config.prob_censor_gets_invite * level_scale)
- {
- // Invite friend (who might be a censor)
- match Self::from_invite(invite, config, true, bridges, censor).await
- {
- Ok(friend) => {
- // You really shouldn't push your friends,
- // especially new ones whose boundaries you
- // might not know well.
- new_censors.push(friend);
- }
- Err(e) => {
- println!("{}", e);
- }
- }
- } else {
- invites.push(invite);
- }
- }
- Err(e) => {
- println!("{}", e);
- }
- }
- }
- }
- Ok(new_censors)
- } else {
- // If we didn't try to connect, indicate so. This is to
- // prevent users from being counted as able/unable to
- // connect when they just haven't tried.
- self.attempted_connection = false;
- Ok(Vec::<User>::new())
- }
- }
- // User cooperates with censor and performs daily tasks to try to
- // learn more bridges. Returns a vector of newly invited users
- // and a boolean indicating whether this censor user should be
- // removed from the global user set (for efficiency).
- pub async fn daily_tasks_censor(
- &mut self,
- config: &Config,
- bridges: &mut HashMap<[u8; 20], Bridge>,
- censor: &mut Censor,
- ) -> Result<(Vec<User>, bool)> {
- // 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
- give_bucket_to_censor(&bucket, bridges, censor);
- // If Lox has marked the bridge blocked, migrate if possible
- if bucket_blocked_lox(&bucket, bridges) {
- // If we can migrate, migrate
- 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
- {
- // Successfully migrated!
- // Update credential
- 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?;
- // Give new bucket to censor
- give_bucket_to_censor(&bucket, bridges, censor);
- // Add this user to censor's list
- censor.add_agent(&self.primary_cred.bucket);
- }
- }
- } else {
- // We can't migrate, and we can't level up or anything.
- // Mark this user for deletion.
- return Ok((Vec::<User>::new(), true));
- }
- }
- // Censor user tries to level up their primary credential
- if level == 0 && eligible_for_trust_promotion(&config.la_net, &self.primary_cred).await
- || reachcred.is_some()
- && level > 0
- && eligible_for_level_up(&config.la_net, &self.primary_cred).await
- {
- let new_cred = if level == 0 {
- let nc = 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?;
- // We now have a new bucket value, so add the user to censor's list
- censor.add_agent(&nc.bucket);
- // New credential as new_cred
- nc
- } else {
- level_up(
- &config.la_net,
- &self.primary_cred,
- &reachcred.unwrap(), // must be Some
- 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);
- if level == 2 {
- // level up to 3
- // Give censor an additional credential
- censor.give_lox_cred(&fingerprint, &self.primary_cred, true);
- } else if level > 2 {
- // level up to 4
- // Replace censor's credential with newer one,
- // but don't add to count of censor's
- // credentials
- censor.give_lox_cred(&fingerprint, &self.primary_cred, false);
- }
- }
- }
- // Censor user invites as many censor friends as possible
- let invitations = scalar_u32(&self.primary_cred.invites_remaining).unwrap();
- let mut new_friends = Vec::<User>::new();
- for _ in 0..invitations {
- match self.get_invite(config, bridges, censor).await {
- Ok(invite) => {
- match Self::from_invite(invite, &config, true, bridges, censor).await {
- Ok(friend) => {
- new_friends.push(friend);
- }
- Err(e) => {
- println!("{}", e);
- }
- }
- }
- Err(e) => {
- println!("{}", e);
- }
- }
- }
- Ok((new_friends, false))
- }
- }
|