diff --git a/src/analyzer.rs b/src/analyzer.rs new file mode 100644 index 0000000..a3eba6a --- /dev/null +++ b/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; +} + +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 { + let mut blocked_in = HashSet::::new(); + for (country, info) in &bridge_info.info_by_country { + if info.blocked { + blocked_in.insert(country.to_string()); + } + } + blocked_in + } +} diff --git a/src/bin/server.rs b/src/bin/server.rs index 3dbdbf6..58411c9 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -61,6 +61,8 @@ async fn update_daily_info(db: &Db, distributors: &BTreeMap 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::(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 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 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 HashMap<[u8; 20], HashSet> { + // Map of bridge fingerprint to set of countries which newly block it + let mut blockages = HashMap::<[u8; 20], HashSet>::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::::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, + blockages: HashMap<[u8; 20], HashSet>, +) { + // 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::>::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); +}