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

This commit is contained in:
Vecna 2024-03-23 23:16:20 -04:00
parent 12e699f979
commit 1ccd676e5c
3 changed files with 139 additions and 25 deletions

22
src/analyzer.rs Normal file
View File

@ -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
}
}

View File

@ -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(

View File

@ -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);
}