Forráskód Böngészése

Store bridge data with key 'bridges', guess and report blockages

Vecna 2 hónapja
szülő
commit
1ccd676e5c
3 módosított fájl, 139 hozzáadás és 25 törlés
  1. 22 0
      src/analyzer.rs
  2. 2 0
      src/bin/server.rs
  3. 115 25
      src/lib.rs

+ 22 - 0
src/analyzer.rs

@@ -0,0 +1,22 @@
+use crate::BridgeInfo;
+use std::collections::HashSet;
+
+/// Provides a function for predicting which countries block this bridge
+pub trait Analyzer {
+    fn blocked_in(&self, bridge_info: &BridgeInfo) -> HashSet<String>;
+}
+
+pub struct ExampleAnalyzer {}
+
+/// Dummy example which just tells us about blockages we already know about
+impl Analyzer for ExampleAnalyzer {
+    fn blocked_in(&self, bridge_info: &BridgeInfo) -> HashSet<String> {
+        let mut blocked_in = HashSet::<String>::new();
+        for (country, info) in &bridge_info.info_by_country {
+            if info.blocked {
+                blocked_in.insert(country.to_string());
+            }
+        }
+        blocked_in
+    }
+}

+ 2 - 0
src/bin/server.rs

@@ -61,6 +61,8 @@ async fn update_daily_info(db: &Db, distributors: &BTreeMap<BridgeDistributor, S
     update_extra_infos(&db).await;
     update_negative_reports(&db, &distributors).await;
     update_positive_reports(&db, &distributors).await;
+    let new_blockages = guess_blockages(&db, &analyzer::ExampleAnalyzer {});
+    report_blockages(&distributors, new_blockages).await;
 }
 
 async fn create_context_manager(

+ 115 - 25
src/lib.rs

@@ -7,12 +7,14 @@ use std::{
     fmt,
 };
 
+pub mod analyzer;
 pub mod bridge_verification_info;
 pub mod extra_info;
 pub mod negative_report;
 pub mod positive_report;
 pub mod request_handler;
 
+use analyzer::Analyzer;
 use extra_info::*;
 use negative_report::*;
 use positive_report::*;
@@ -170,11 +172,18 @@ impl fmt::Display for BridgeCountryInfo {
 /// but this extra-info contains different data for some reason, use the
 /// greater count of connections from each country.
 pub fn add_extra_info_to_db(db: &Db, extra_info: ExtraInfo) {
-    let fingerprint = extra_info.fingerprint;
-    let mut bridge_info = match db.get(&fingerprint).unwrap() {
+    let mut bridges = match db.get("bridges").unwrap() {
         Some(v) => bincode::deserialize(&v).unwrap(),
-        None => BridgeInfo::new(fingerprint, &extra_info.nickname),
+        None => BTreeMap::<[u8; 20], BridgeInfo>::new(),
     };
+    let fingerprint = extra_info.fingerprint;
+    if !bridges.contains_key(&fingerprint) {
+        bridges.insert(
+            fingerprint,
+            BridgeInfo::new(fingerprint, &extra_info.nickname),
+        );
+    }
+    let bridge_info = bridges.get_mut(&fingerprint).unwrap();
     for country in extra_info.bridge_ips.keys() {
         if bridge_info.info_by_country.contains_key::<String>(country) {
             bridge_info
@@ -200,7 +209,7 @@ pub fn add_extra_info_to_db(db: &Db, extra_info: ExtraInfo) {
         }
     }
     // Commit changes to database
-    db.insert(fingerprint, bincode::serialize(&bridge_info).unwrap())
+    db.insert("bridges", bincode::serialize(&bridges).unwrap())
         .unwrap();
 }
 
@@ -322,19 +331,23 @@ pub async fn update_negative_reports(db: &Db, distributors: &BTreeMap<BridgeDist
             let country = first_report.country;
             let count_valid = verify_negative_reports(&distributors, reports).await;
 
-            let mut bridge_info = match db.get(&fingerprint).unwrap() {
+            let mut bridges = match db.get("bridges").unwrap() {
                 Some(v) => bincode::deserialize(&v).unwrap(),
-                // It should already exist, unless the bridge hasn't published
-                // any bridge stats.
-                None => BridgeInfo::new(fingerprint, &"".to_string()),
+                None => BTreeMap::<[u8; 20], BridgeInfo>::new(),
             };
+
+            // Get bridge info or make new one
+            if !bridges.contains_key(&fingerprint) {
+                // This case shouldn't happen unless the bridge hasn't published
+                // any bridge stats.
+                bridges.insert(fingerprint, BridgeInfo::new(fingerprint, &"".to_string()));
+            }
+            let bridge_info = bridges.get_mut(&fingerprint).unwrap();
+
             // Add the new report count to it
             if bridge_info.info_by_country.contains_key(&country) {
                 let bridge_country_info = bridge_info.info_by_country.get_mut(&country).unwrap();
                 bridge_country_info.add_info(BridgeInfoType::NegativeReports, date, count_valid);
-                // Commit changes to database
-                db.insert(fingerprint, bincode::serialize(&bridge_info).unwrap())
-                    .unwrap();
             } else {
                 // No existing entry; make a new one.
                 let mut bridge_country_info = BridgeCountryInfo::new();
@@ -342,10 +355,11 @@ pub async fn update_negative_reports(db: &Db, distributors: &BTreeMap<BridgeDist
                 bridge_info
                     .info_by_country
                     .insert(country, bridge_country_info);
-                // Commit changes to database
-                db.insert(fingerprint, bincode::serialize(&bridge_info).unwrap())
-                    .unwrap();
             }
+
+            // Commit changes to database
+            db.insert("bridges", bincode::serialize(&bridges).unwrap())
+                .unwrap();
         }
     }
     // TODO: Would it be cheaper to just recreate it?
@@ -434,19 +448,25 @@ pub async fn update_positive_reports(db: &Db, distributors: &BTreeMap<BridgeDist
             let date = first_report.date;
             let country = first_report.country.clone();
             let count_valid = verify_positive_reports(&distributors, reports).await;
-            let mut bridge_info = match db.get(&fingerprint).unwrap() {
+
+            // Get bridge data from database
+            let mut bridges = match db.get("bridges").unwrap() {
                 Some(v) => bincode::deserialize(&v).unwrap(),
-                // It should already exist, unless the bridge hasn't published
-                // any bridge stats.
-                None => BridgeInfo::new(fingerprint, &"".to_string()),
+                None => BTreeMap::<[u8; 20], BridgeInfo>::new(),
             };
+
+            // Get bridge info or make new one
+            if !bridges.contains_key(&fingerprint) {
+                // This case shouldn't happen unless the bridge hasn't published
+                // any bridge stats.
+                bridges.insert(fingerprint, BridgeInfo::new(fingerprint, &"".to_string()));
+            }
+            let bridge_info = bridges.get_mut(&fingerprint).unwrap();
+
             // Add the new report count to it
             if bridge_info.info_by_country.contains_key(&country) {
                 let bridge_country_info = bridge_info.info_by_country.get_mut(&country).unwrap();
                 bridge_country_info.add_info(BridgeInfoType::PositiveReports, date, count_valid);
-                // Commit changes to database
-                db.insert(fingerprint, bincode::serialize(&bridge_info).unwrap())
-                    .unwrap();
             } else {
                 // No existing entry; make a new one.
                 let mut bridge_country_info = BridgeCountryInfo::new();
@@ -454,10 +474,10 @@ pub async fn update_positive_reports(db: &Db, distributors: &BTreeMap<BridgeDist
                 bridge_info
                     .info_by_country
                     .insert(country, bridge_country_info);
-                // Commit changes to database
-                db.insert(fingerprint, bincode::serialize(&bridge_info).unwrap())
-                    .unwrap();
             }
+            // Commit changes to database
+            db.insert("bridges", bincode::serialize(&bridges).unwrap())
+                .unwrap();
         }
     }
     // TODO: Would it be cheaper to just recreate it?
@@ -470,4 +490,74 @@ pub async fn update_positive_reports(db: &Db, distributors: &BTreeMap<BridgeDist
     .unwrap();
 }
 
-// TODO: function to mark a bridge as blocked
+// Verdict on bridge reachability
+
+/// Guess which countries block a bridge. This function returns a map of new
+/// blockages (fingerprint : set of countries which block the bridge)
+pub fn guess_blockages(db: &Db, analyzer: &dyn Analyzer) -> HashMap<[u8; 20], HashSet<String>> {
+    // Map of bridge fingerprint to set of countries which newly block it
+    let mut blockages = HashMap::<[u8; 20], HashSet<String>>::new();
+
+    // Get bridge data from database
+    let mut bridges = match db.get("bridges").unwrap() {
+        Some(v) => bincode::deserialize(&v).unwrap(),
+        None => BTreeMap::<[u8; 20], BridgeInfo>::new(),
+    };
+
+    // Guess for each bridge
+    for (fingerprint, bridge_info) in &mut bridges {
+        let mut new_blockages = HashSet::<String>::new();
+        let blocked_in = analyzer.blocked_in(&bridge_info);
+        for country in blocked_in {
+            let bridge_country_info = bridge_info.info_by_country.get_mut(&country).unwrap();
+            if !bridge_country_info.blocked {
+                new_blockages.insert(country.to_string());
+                // Mark bridge as blocked when db gets updated
+                bridge_country_info.blocked = true;
+            }
+        }
+        blockages.insert(*fingerprint, new_blockages);
+    }
+
+    // Commit changes to database
+    db.insert("bridges", bincode::serialize(&bridges).unwrap())
+        .unwrap();
+
+    // Return map of new blockages
+    blockages
+}
+
+/// Report blocked bridges to bridge distributor
+pub async fn report_blockages(
+    distributors: &BTreeMap<BridgeDistributor, String>,
+    blockages: HashMap<[u8; 20], HashSet<String>>,
+) {
+    // For now, only report to Lox
+    // TODO: Support more distributors
+    let uri: String = (distributors
+        .get(&BridgeDistributor::Lox)
+        .unwrap()
+        .to_owned()
+        + "/reportblocked")
+        .parse()
+        .unwrap();
+
+    // Convert map keys from [u8; 20] to 40-character hex strings
+    let mut blockages_str = HashMap::<String, HashSet<String>>::new();
+    for (fingerprint, countries) in blockages {
+        let fpr_string = array_bytes::bytes2hex("", fingerprint);
+        blockages_str.insert(fpr_string, countries);
+    }
+
+    // Report blocked bridges to bridge distributor
+    let client = Client::new();
+    let req = Request::builder()
+        .method(Method::POST)
+        .uri(uri)
+        .body(Body::from(serde_json::to_string(&blockages_str).unwrap()))
+        .unwrap();
+    let resp = client.request(req).await.unwrap();
+    let buf = hyper::body::to_bytes(resp).await.unwrap();
+    let resp_str: String = serde_json::from_slice(&buf).unwrap();
+    assert_eq!("OK", resp_str);
+}